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); } } }