using System;
using System.Collections.Generic;
using System.Numerics;
using ACE.DatLoader;
using ACE.DatLoader.FileTypes;
using ACE.Entity;
namespace ACE.Server.Entity
{
///
/// The polygonal landblock mesh
///
public class LandblockMesh: Mesh
{
public LandblockId LandblockId;
///
/// The heights of the landblock cell vertices
///
public float[,] VertexHeights;
///
/// A landblock has this many cells squared
///
public const int CellDim = 8;
///
/// A landblock is this unit size squared
///
public const int LandblockSize = 192;
///
/// A landblock cell is this unit size squared
///
public const int CellSize = LandblockSize / CellDim;
///
/// A landblock has this many vertices squared
///
public const int VertexDim = CellDim + 1;
///
/// LandHeightTable mapping non-linear heights
///
public static RegionDesc RegionDesc;
///
/// Static constructor
///
static LandblockMesh()
{
// load the region file from portal.dat
RegionDesc = DatManager.PortalDat.ReadFromDat(0x13000000);
}
///
/// Constructs a new mesh for a landblock
///
public LandblockMesh(LandblockId id)
{
LandblockId = id;
BuildVertices();
BuildTriangles();
}
///
/// Builds the vertices for the landblock cells
///
public void BuildVertices()
{
VertexHeights = GetVertexHeights();
LoadVertices(VertexHeights);
}
///
/// Reads the heights for each vertex in the landblock cells
///
/// The vertex heights for the landblock cells
public float[,] GetVertexHeights()
{
// The vertex heights in the cell database are stored in bytes,
// which map to offsets in the land height table from the region file in the portal database.
var cellLandblock = DatManager.CellDat.ReadFromDat(LandblockId.Raw | 0xFFFF);
var heights = new float[VertexDim, VertexDim];
for (int x = 0; x < VertexDim; x++)
{
for (int y = 0; y < VertexDim; y++)
{
heights[x, y] = RegionDesc.LandDefs.LandHeightTable[cellLandblock.Height[x * VertexDim + y]];
}
}
return heights;
}
///
/// Loads the vertices for a landblock mesh
///
/// The height of each vertex in the landblock cells
public void LoadVertices(float[,] height)
{
var xSize = height.GetLength(0);
var ySize = height.GetLength(1);
Vertices = new List(xSize * ySize);
for (int x = 0; x < xSize; x++)
{
for (int y = 0; y < ySize; y++)
{
Vertices.Add(new Vector3(x * CellSize, y * CellSize, height[x, y]));
}
}
}
///
/// Generates the triangles from the mesh vertices
///
/// The list of triangles generated for the landblock mesh
public void BuildTriangles()
{
Triangles = new List();
for (int x = 0; x < CellDim; x++)
{
for (int y = 0; y < CellDim; y++)
{
int lowerLeft = x + y * VertexDim;
int lowerRight = (x + 1) + y * VertexDim;
int topLeft = x + (y + 1) * VertexDim;
int topRight = (x + 1) + (y + 1) * VertexDim;
// determine where to draw the split line
if (GetSplitDir(LandblockId, x, y))
{
// clockwise winding order
Triangles.Add(new Triangle(topLeft, lowerRight, lowerLeft));
Triangles.Add(new Triangle(topLeft, topRight, lowerRight));
}
else
{
Triangles.Add(new Triangle(topRight, lowerRight, lowerLeft));
Triangles.Add(new Triangle(topRight, lowerLeft, topLeft));
}
}
}
}
///
/// Determines the split line direction
/// for a cell triangulation
///
/// A reference to the landblock ID
/// The horizontal cell position within the landblock
/// The vertical cell position within the landblock
/// TRUE if NW-SE split, FALSE if NE-SW split
public bool GetSplitDir(LandblockId id, int cellX, int cellY)
{
// get the global tile offsets
var x = (id.LandblockX * 8) + cellX;
var y = (id.LandblockY * 8) + cellY;
// Thanks to https://github.com/deregtd/AC2D for this bit
var dw = x * y * 0x0CCAC033 - x * 0x421BE3BD + y * 0x6C1AC587 - 0x519B8F25;
return (dw & 0x80000000) == 0;
}
///
/// Returns the shared line between 2 triangles
///
public Line2 GetSplitter(List triangles)
{
if (triangles[0].Indices[1] == triangles[1].Indices[2])
return new Line2(Vertices[triangles[0].Indices[0]], Vertices[triangles[0].Indices[1]]);
else
return new Line2(Vertices[triangles[0].Indices[0]], Vertices[triangles[0].Indices[2]]);
}
///
/// Returns the triangle containing a pair of x,y coordinates
///
public Triangle GetTriangle(Vector2 point)
{
// find the cell which contains these coordinates
Vector2 cellOffset = GetCell(point);
// get the triangles for this cell
var cellTriangles = GetCellTriangles(cellOffset);
// return the triangle containing this point
if (cellTriangles[0].Contains(point, Vertices))
return cellTriangles[0];
else
return cellTriangles[1];
}
///
/// Given a pair of 2D coordinates within a landblock,
/// Returns the cell that contains these coordinates
///
/// The 2D coordinates within the landblock
/// The cell that contains these coordinates
public static Vector2 GetCell(Vector2 point)
{
var cellX = (float)Math.Floor(point.X / CellSize);
var cellY = (float)Math.Floor(point.Y / CellSize);
if (cellX < 0) cellX = 0;
if (cellY < 0) cellY = 0;
if (cellX >= CellDim) cellX = CellDim - 1;
if (cellY >= CellDim) cellY = CellDim - 1;
return new Vector2(cellX, cellY);
}
///
/// Returns the 2 triangles for a cell
///
public List GetCellTriangles(Vector2 cellOffset)
{
var offset = (int)((cellOffset.Y * CellDim + cellOffset.X) * 2);
// TODO: ensure within bounds
return new List() { Triangles[offset], Triangles[offset + 1] };
}
///
/// Returns the z height coordinate for a 2D position within a landblock
///
public float GetZ(Vector2 point)
{
// find the triangle that contains this point
var triangle = GetTriangle(point);
// calculate the z coordinate at x,y
// for the plane defined by this triangle
return triangle.GetZ(Vertices, point);
}
}
}