/// <summary> /// Gets the shared indices at the position in the direction relative to the given position. /// </summary> /// <param name="pos">The position from which the direction is applied.</param> /// <param name="direction">The bitmask indicating the directional of the shared cell.</param> /// <param name="index">The index of the vertex index.</param> /// <returns>The vertex index.</returns> public int GetIndexInDirection(Vector3I pos, byte direction, byte index) { // Offset the position by the direction mask pos.X -= direction & 0x01; pos.Z -= (direction >> 1) & 0x01; pos.Y -= (direction >> 2) & 0x01; return this[pos, index]; }
/// <summary> /// Calculate the normal at the given position. /// </summary> /// <param name="pos">The position.</param> /// <returns>The normal.</returns> private Vector3 CalculateNormal(Vector3I pos) { byte x0 = this.terrain.GetDensity(pos - Vector3I.UnitX); byte x1 = this.terrain.GetDensity(pos + Vector3I.UnitX); byte y0 = this.terrain.GetDensity(pos - Vector3I.UnitY); byte y1 = this.terrain.GetDensity(pos + Vector3I.UnitY); byte z0 = this.terrain.GetDensity(pos - Vector3I.UnitZ); byte z1 = this.terrain.GetDensity(pos + Vector3I.UnitZ); Vector3 normal = new Vector3((x1 - x0) * 0.5f, (y1 - y0) * 0.5f, (z1 - z0) * 0.5f); normal.Normalize(); return normal; }
/// <summary> /// Gets the point at the given world position. /// </summary> /// <param name="pos">The world position.</param> /// <returns>The point.</returns> public TerrainPoint GetPoint(Vector3I pos) { TerrainChunk chunk; if (this.chunks.TryGetValue(Metrics.ChunkIndex(pos.X, pos.Y), out chunk)) { return chunk.GetPoint(Metrics.WorldToChunk(pos)); } else { return null; } }
/// <summary> /// Gets the density at the given world position. /// </summary> /// <param name="pos">The world position.</param> /// <returns>The density.</returns> public byte GetDensity(Vector3I pos) { TerrainChunk chunk; if (this.chunks.TryGetValue(Metrics.ChunkIndex(pos.X, pos.Y), out chunk)) { return chunk.GetDensity(Metrics.WorldToChunk(pos)); } else { return TerrainPoint.DensityMax; } }
/// <summary> /// Determine if the vector is equal. /// </summary> /// <param name="other">The vector to test.</param> /// <returns>True if the vectors are equal.</returns> public bool Equals(Vector3I other) { return other.X == this.X && other.Y == this.Y && other.Z == this.Z; }
/// <summary> /// Gets the point at the given chunk position. /// </summary> /// <param name="pos">The chunk position.</param> /// <returns>The point.</returns> public TerrainPoint GetPoint(Vector3I pos) { return this.Points[pos.X, pos.Y]; }
/// <summary> /// Gets the density at the given chunk position. /// </summary> /// <param name="pos">The chunk position.</param> /// <returns>The density.</returns> public byte GetDensity(Vector3I pos) { TerrainPoint point = this.Points[pos.X, pos.Y]; if (point != null) { return point.GetDensity(pos.Z); } else { return TerrainPoint.DensityMax; } }
/// <summary> /// Create the vertex for the given point. /// </summary> /// <param name="pos">The position.</param> /// <param name="mesh">The mesh.</param> /// <param name="corners">The density at each corner of the cell.</param> /// <param name="light">The light data for cell corners.</param> /// <param name="cornerA">The first corner index of the of the edge on which the vertex lies.</param> /// <param name="cornerB">The second corner index of the of the edge on which the vertex lies.</param> private void CreateVertex( Vector3I pos, MeshData mesh, byte[] corners, Color[] light, byte cornerA, byte cornerB) { // Calculate the position of the two end points between which the vertex lies Vector3I pAI = pos + MarchingCubes.CornerVector[cornerA]; Vector3I pBI = pos + MarchingCubes.CornerVector[cornerB]; Vector3 pA = new Vector3(pAI.X, pAI.Y, pAI.Z); Vector3 pB = new Vector3(pBI.X, pBI.Y, pBI.Z); // Get the end point densities byte densityA = corners[cornerA]; byte densityB = corners[cornerB]; // Calculate the interpolation ratio float ratio = this.CalculateRatio(densityA, densityB); // Interpolate the vertex position between the two end points Vector3 point = this.InterpolatePoint(pA, pB, ratio); // Calculate the normals at each end point and then interpolate the two normals Vector3 nA = this.CalculateNormal(pAI); Vector3 nB = this.CalculateNormal(pBI); Vector3 normal = this.InterpolatePoint(nA, nB, ratio); // Interpolate the color value between the two end points Color color = this.InterpolateColor(light[cornerA], light[cornerB], ratio); // Add the vertex to the mesh mesh.Vertices.Add(point); mesh.Normals.Add(normal); mesh.Light.Add(color); }
/// <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]]); } } }
/// <summary> /// Gets or sets the shared vertex index at the given position. /// </summary> /// <param name="pos">The position.</param> /// <param name="index">The index of the vertex index.</param> /// <returns>The vertex index.</returns> public int this[Vector3I pos, byte index] { get { return this.indices[pos.Z & 1][pos.X, pos.Y][index]; } set { this.indices[pos.Z & 1][pos.X, pos.Y][index] = value; } }
/// <summary> /// Convert the world coordinates into chunk coordinates. /// </summary> /// <param name="worldPos">The position.</param> /// <returns>The position in chunk coordinates.</returns> public static Vector3I WorldToChunk(Vector3I worldPos) { return new Vector3I( worldPos.X & (Metrics.ChunkWidth - 1), worldPos.Y & (Metrics.ChunkHeight - 1), worldPos.Z); }