using System.Collections; using System.Collections.Generic; using UnityEngine; public class CaveMeshGenerator : MonoBehaviour { public SquareGrid squareGrid; public MeshFilter walls; List vertices; List triangles; Dictionary> triangleDic = new Dictionary>(); List> outlines = new List>(); HashSet checkedVertices = new HashSet(); public void GenerateMesh(int[,] map, float squareSize) { outlines.Clear(); checkedVertices.Clear(); triangleDic.Clear(); squareGrid = new SquareGrid(map, squareSize); vertices = new List(); triangles = new List(); for (int x = 0; x < squareGrid.squares.GetLength(0); ++x) { for (int y = 0; y < squareGrid.squares.GetLength(1); ++y) { TriangulateSquare(squareGrid.squares[x, y]); } } Mesh mesh = new Mesh(); GetComponent().mesh = mesh; mesh.vertices = vertices.ToArray(); mesh.triangles = triangles.ToArray(); mesh.RecalculateNormals(); CreateWallMesh(); } void CreateWallMesh() { CalculateMeshOutlines(); List wallVertices = new List(); List wallTriangles = new List(); Mesh wallMesh = new Mesh(); float wallHeight = 5; foreach (List outline in outlines) { for(int i = 0; i < outline.Count - 1; ++i) { int startIndex = wallVertices.Count; wallVertices.Add(vertices[outline[i]]); wallVertices.Add(vertices[outline[i + 1]]); wallVertices.Add(vertices[outline[i]] - Vector3.up * wallHeight); wallVertices.Add(vertices[outline[i + 1]] - Vector3.up * wallHeight); wallTriangles.Add(startIndex); wallTriangles.Add(startIndex + 2); wallTriangles.Add(startIndex + 3); wallTriangles.Add(startIndex + 3); wallTriangles.Add(startIndex + 1); wallTriangles.Add(startIndex); } } wallMesh.vertices = wallVertices.ToArray(); wallMesh.triangles = wallTriangles.ToArray(); walls.mesh = wallMesh; walls.sharedMesh = wallMesh; } void TriangulateSquare(Square square) { switch(square.m_Config) { case 0: break; case 1: MeshFromPoints(square.m_CentreLeft, square.m_CentreBottom, square.m_BottomLeft); break; case 2: MeshFromPoints(square.m_BottomRight, square.m_CentreBottom, square.m_CentreRight); break; case 4: MeshFromPoints(square.m_TopRight, square.m_CentreRight, square.m_CentreTop); break; case 8: MeshFromPoints(square.m_TopLeft, square.m_CentreTop, square.m_CentreLeft); break; case 3: MeshFromPoints(square.m_CentreRight, square.m_BottomRight, square.m_BottomLeft, square.m_CentreLeft); break; case 6: MeshFromPoints(square.m_CentreTop, square.m_TopRight, square.m_BottomRight, square.m_CentreBottom); break; case 9: MeshFromPoints(square.m_TopLeft, square.m_CentreTop, square.m_CentreBottom, square.m_BottomLeft); break; case 12: MeshFromPoints(square.m_TopLeft, square.m_TopRight, square.m_CentreRight, square.m_CentreLeft); break; case 5: MeshFromPoints(square.m_CentreTop, square.m_TopRight, square.m_CentreRight, square.m_CentreBottom, square.m_BottomLeft, square.m_CentreLeft); break; case 10: MeshFromPoints(square.m_TopLeft, square.m_CentreTop, square.m_CentreRight, square.m_BottomRight, square.m_CentreBottom, square.m_CentreLeft); break; case 7: MeshFromPoints(square.m_CentreTop, square.m_TopRight, square.m_BottomRight, square.m_BottomLeft, square.m_CentreLeft); break; case 11: MeshFromPoints(square.m_TopLeft, square.m_CentreTop, square.m_CentreRight, square.m_BottomRight, square.m_BottomLeft); break; case 13: MeshFromPoints(square.m_TopLeft, square.m_TopRight, square.m_CentreRight, square.m_CentreBottom, square.m_BottomLeft); break; case 14: MeshFromPoints(square.m_TopLeft, square.m_TopRight, square.m_BottomRight, square.m_CentreBottom, square.m_CentreLeft); break; case 15: MeshFromPoints(square.m_TopLeft, square.m_TopRight, square.m_BottomRight, square.m_BottomLeft); checkedVertices.Add(square.m_TopLeft.m_VertexIndex); checkedVertices.Add(square.m_TopRight.m_VertexIndex); checkedVertices.Add(square.m_BottomRight.m_VertexIndex); checkedVertices.Add(square.m_BottomLeft.m_VertexIndex); break; } } void MeshFromPoints(params Node[] points) { AssignVertices(points); if(points.Length >= 3) { CreateTriangle(points[0], points[1], points[2]); } if(points.Length >= 4) { CreateTriangle(points[0], points[2], points[3]); } if (points.Length >= 5) { CreateTriangle(points[0], points[3], points[4]); } if (points.Length >= 6) { CreateTriangle(points[0], points[4], points[5]); } } void AssignVertices(Node[] points) { for(int i = 0; i < points.Length; ++i) { if(points[i].m_VertexIndex == -1) { points[i].m_VertexIndex = vertices.Count; vertices.Add(points[i].m_Position); } } } void CreateTriangle(Node a, Node b, Node c) { triangles.Add(a.m_VertexIndex); triangles.Add(b.m_VertexIndex); triangles.Add(c.m_VertexIndex); Triangle triangle = new Triangle(a.m_VertexIndex, b.m_VertexIndex, c.m_VertexIndex); AddTriangleToDictionary(triangle.vertexIndexA, triangle); AddTriangleToDictionary(triangle.vertexIndexB, triangle); AddTriangleToDictionary(triangle.vertexIndexC, triangle); } void AddTriangleToDictionary(int vertexIndexKey, Triangle triangle) { if (triangleDic.ContainsKey(vertexIndexKey)) { triangleDic[vertexIndexKey].Add(triangle); } else { List triangles = new List(); triangles.Add(triangle); triangleDic.Add(vertexIndexKey, triangles); } } void CalculateMeshOutlines() { for(int vertexIndex = 0; vertexIndex < vertices.Count; ++vertexIndex) { if (!checkedVertices.Contains(vertexIndex)) { int newOutlineVertex = GetConnectedOutlineVertex(vertexIndex); if (newOutlineVertex != -1) { checkedVertices.Add(vertexIndex); List newOutline = new List(); newOutline.Add(vertexIndex); outlines.Add(newOutline); FollowOutline(newOutlineVertex, outlines.Count - 1); outlines[outlines.Count - 1].Add(vertexIndex); } } } } // TODO: change to non recursive void FollowOutline(int vertexIndex, int outlineIndex) { outlines[outlineIndex].Add(vertexIndex); checkedVertices.Add(vertexIndex); int nextVertexIndex = GetConnectedOutlineVertex(vertexIndex); if (nextVertexIndex != -1) { FollowOutline(nextVertexIndex, outlineIndex); } } int GetConnectedOutlineVertex(int vertexIndex) { List trianglesContainingVertex = triangleDic[vertexIndex]; for (int i = 0; i < trianglesContainingVertex.Count; ++i) { Triangle triangle = trianglesContainingVertex[i]; for (int j = 0; j < 3; ++j) { int vertexB = triangle[j]; if (vertexB == vertexIndex || checkedVertices.Contains(vertexB)) { continue; } if (IsOutlineEdge(vertexIndex, vertexB)) { return vertexB; } } } return -1; } bool IsOutlineEdge(int vertexA, int vertexB) { List trianglesContainingVertexA = triangleDic[vertexA]; int sharedTriangleCount = 0; for (int i = 0; i < trianglesContainingVertexA.Count; ++i) { if (trianglesContainingVertexA[i].Contains(vertexB)) { ++sharedTriangleCount; if (sharedTriangleCount == 2) { return false; } } } return true; } struct Triangle { public int vertexIndexA; public int vertexIndexB; public int vertexIndexC; int[] vertices; public Triangle(int a, int b, int c) { vertexIndexA = a; vertexIndexB = b; vertexIndexC = c; vertices = new int[3]; vertices[0] = a; vertices[1] = b; vertices[2] = c; } public int this[int i] { get { return vertices[i]; } } public bool Contains(int vertexIndex) { return vertexIndex == vertexIndexA || vertexIndex == vertexIndexB || vertexIndex == vertexIndexC; } } public class SquareGrid { public Square[,] squares; public SquareGrid(int[,] map, float squareSize) { int nodeCountX = map.GetLength(0); int nodeCountY = map.GetLength(1); float mapWidth = nodeCountX * squareSize; float mapHeight = nodeCountY * squareSize; ControlNode[,] controlNodes = new ControlNode[nodeCountX, nodeCountY]; for(int x = 0; x < nodeCountX; ++x) { for (int y = 0; y < nodeCountY; ++y) { Vector3 position = new Vector3(-mapWidth / 2 + x * squareSize + squareSize / 2, 0, -mapHeight / 2 + y * squareSize + squareSize / 2); controlNodes[x, y] = new ControlNode(position, map[x, y] == 1, squareSize); } } squares = new Square[nodeCountX - 1, nodeCountY - 1]; for (int x = 0; x < nodeCountX - 1; ++x) { for (int y = 0; y < nodeCountY - 1; ++y) { squares[x, y] = new Square(controlNodes[x, y + 1], controlNodes[x + 1, y + 1], controlNodes[x + 1, y], controlNodes[x, y]); } } } } public class Square { public ControlNode m_TopLeft, m_TopRight, m_BottomLeft, m_BottomRight; public Node m_CentreTop, m_CentreRight, m_CentreBottom, m_CentreLeft; public int m_Config; public Square( ControlNode topLeft, ControlNode topRight, ControlNode bottomRight, ControlNode bottomLeft ) { m_TopLeft = topLeft; m_TopRight = topRight; m_BottomLeft = bottomLeft; m_BottomRight = bottomRight; m_CentreTop = m_TopLeft.m_RightNode; m_CentreRight = m_BottomRight.m_AboveNode; m_CentreBottom = m_BottomLeft.m_RightNode; m_CentreLeft = m_BottomLeft.m_AboveNode; m_Config = ((m_TopLeft.m_IsActive) ? 8 : 0) | ((m_TopRight.m_IsActive) ? 4 : 0) | ((m_BottomRight.m_IsActive) ? 2 : 0) | (m_BottomLeft.m_IsActive ? 1 : 0); } } public class Node { public Vector3 m_Position; public int m_VertexIndex = -1; public Node(Vector3 pos) { m_Position = pos; } } public class ControlNode : Node { public bool m_IsActive; public Node m_AboveNode, m_RightNode; public ControlNode(Vector3 pos, bool isActive, float squareSize) : base(pos) { m_IsActive = isActive; m_AboveNode = new Node(pos + Vector3.forward * squareSize / 2f); m_RightNode = new Node(pos + Vector3.right * squareSize / 2f); } } }