410 lines
13 KiB
C#
410 lines
13 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
public class CaveMeshGenerator : MonoBehaviour
|
|
{
|
|
public SquareGrid squareGrid;
|
|
public MeshFilter walls;
|
|
|
|
List<Vector3> vertices;
|
|
List<int> triangles;
|
|
|
|
Dictionary<int, List<Triangle>> triangleDic = new Dictionary<int, List<Triangle>>();
|
|
List<List<int>> outlines = new List<List<int>>();
|
|
HashSet<int> checkedVertices = new HashSet<int>();
|
|
|
|
public void GenerateMesh(int[,] map, float squareSize)
|
|
{
|
|
outlines.Clear();
|
|
checkedVertices.Clear();
|
|
triangleDic.Clear();
|
|
|
|
squareGrid = new SquareGrid(map, squareSize);
|
|
|
|
vertices = new List<Vector3>();
|
|
triangles = new List<int>();
|
|
|
|
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<MeshFilter>().mesh = mesh;
|
|
mesh.vertices = vertices.ToArray();
|
|
mesh.triangles = triangles.ToArray();
|
|
mesh.RecalculateNormals();
|
|
|
|
CreateWallMesh();
|
|
}
|
|
|
|
void CreateWallMesh()
|
|
{
|
|
CalculateMeshOutlines();
|
|
|
|
List<Vector3> wallVertices = new List<Vector3>();
|
|
List<int> wallTriangles = new List<int>();
|
|
Mesh wallMesh = new Mesh();
|
|
float wallHeight = 5;
|
|
|
|
foreach (List<int> 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<Triangle> triangles = new List<Triangle>();
|
|
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<int> newOutline = new List<int>();
|
|
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<Triangle> 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<Triangle> 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);
|
|
}
|
|
}
|
|
}
|