/// <summary> /// Rebuild the MeshData for the given chunk. This is a heavy operation which takes a few frames to complete, /// so is best handled asynchronously. /// </summary> /// <param name="chunkIndex">The chunk index.</param> public void RebuildMesh(Vector2I chunkIndex) { TerrainChunk chunk = this.terrain.GetChunk(chunkIndex); // Initialise the arrays of shared indices var sharedIndices = new SharedIndices(Metrics.ChunkWidth, Metrics.ChunkWidth); // Clear the mesh data chunk.Mesh.Data.Clear(); // Create the mesh for each cell in the chunk var chunkOrigin = Metrics.GetChunkOrigin(chunkIndex); for (int z = -1; z < Metrics.ChunkDepth; z++) { for (int x = chunkOrigin.X; x < chunkOrigin.X + Metrics.ChunkWidth; x++) { for (int y = chunkOrigin.Y; y < chunkOrigin.Y + Metrics.ChunkHeight; y++) { this.CreateMeshCell(new Vector3I(x, y, z), chunk.Mesh.Data, sharedIndices); } } } }
/// <summary> /// Create the cell at the given position. /// </summary> /// <param name="pos">The position.</param> /// <param name="mesh">The mesh data.</param> /// <param name="sharedIndices">The shared indices for the chunk.</param> private void CreateMeshCell(Vector3I pos, MeshData mesh, SharedIndices sharedIndices) { // Get the voxels and light at each corner of the cell var corners = new byte[8]; var light = new Color[8]; for (int i = 0; i < corners.Length; i++) { Vector3I cornerPos = pos + MarchingCubes.CornerVector[i]; TerrainPoint point = this.terrain.GetPoint(cornerPos); if (point != null) { corners[i] = point.GetDensity(cornerPos.Z); light[i] = point.Light.ToColor(); } else { corners[i] = TerrainPoint.DensityMax; light[i] = Color.black; } } // Get the case code byte caseCode = MarchingCubes.GetCaseCode(corners); if ((caseCode ^ ((corners[7] >> 7) & 0xFF)) == 0) { // For these cases there is no triangulation return; } else if ((caseCode & 0xCC) == 0xCC) { // For this case the surface is fully facing away from the camera (behind the terrain) return; } // Look up the geometry and vertex data for this case code CellGeometry geometry = MarchingCubes.CellGeometry[MarchingCubes.CellClass[caseCode]]; ushort[] vertexData = MarchingCubes.VertexData[caseCode]; // Calculate the mask which indicates whether vertices can be shared for a given direction Vector3I chunkPos = Metrics.WorldToChunk(pos); byte directionMask = (byte)((chunkPos.X > 0 ? 1 : 0) | ((chunkPos.Z > 0 ? 1 : 0) << 1) | ((chunkPos.Y > 0 ? 1 : 0) << 2)); // Get the indices for each vertex in this cell, creating the vertices if necessary (otherwise use shared) var actualIndices = new ushort[geometry.Indices.Length]; for (int i = 0; i < geometry.VertexCount; i++) { // Determine which edge this vertex lies on byte edge = (byte)(vertexData[i] >> 8); // Get the corner indices for the edge's end points byte cornerA = (byte)((vertexData[i] >> 4) & 0x0F); byte cornerB = (byte)(vertexData[i] & 0x0F); // Get the direction and index for reusing indices of a shared cell byte sharedDirection = (byte)(edge >> 4); byte sharedIndex = (byte)(edge & 0xF); // Check if a vertex should be re-used rather than creating a new one int actualIndex = -1; if (cornerB != 7 && (sharedDirection & directionMask) == sharedDirection) { actualIndex = sharedIndices.GetIndexInDirection(chunkPos, sharedDirection, sharedIndex); } // Check if a new vertex should be created if (actualIndex == -1) { this.CreateVertex(pos, mesh, corners, light, cornerA, cornerB); actualIndex = mesh.LatestVertexIndex(); } // Cache this vertex index so it can be used by other cells if ((sharedDirection & 8) != 0) { sharedIndices[chunkPos, sharedIndex] = mesh.LatestVertexIndex(); } actualIndices[i] = (ushort)actualIndex; } // Add the triangle indices to the mesh for (int t = 0; t < geometry.TriangleCount; t++) { // Step through the 3 vertices of this triangle int indexBase = t * 3; for (int i = 0; i < 3; i++) { mesh.Indices.Add(actualIndices[geometry.Indices[indexBase + i]]); } } }