/// <summary> /// Computes interpolated vertices on all cube edges. /// </summary> /// <param name="voxels">Voxels representing a cube.</param> /// <returns>Array of interpolated vertices.</returns> private static VoxelMeshVertex[] ComputeEdges(Voxel[] voxels) { VoxelMeshVertex[] interpolatedVertices = new VoxelMeshVertex[12]; interpolatedVertices[0] = ComputeEdge(voxels[0], voxels[1]); interpolatedVertices[1] = ComputeEdge(voxels[1], voxels[2]); interpolatedVertices[2] = ComputeEdge(voxels[2], voxels[3]); interpolatedVertices[3] = ComputeEdge(voxels[3], voxels[0]); interpolatedVertices[4] = ComputeEdge(voxels[4], voxels[5]); interpolatedVertices[5] = ComputeEdge(voxels[5], voxels[6]); interpolatedVertices[6] = ComputeEdge(voxels[6], voxels[7]); interpolatedVertices[7] = ComputeEdge(voxels[7], voxels[4]); interpolatedVertices[8] = ComputeEdge(voxels[0], voxels[4]); interpolatedVertices[9] = ComputeEdge(voxels[1], voxels[5]); interpolatedVertices[10] = ComputeEdge(voxels[3], voxels[7]); interpolatedVertices[11] = ComputeEdge(voxels[2], voxels[6]); return interpolatedVertices; }
/// <summary> /// Computes voxel mesh triangles based on voxel cube points. /// </summary> /// <param name="cubeVoxels">Voxel cube points.</param> /// <returns>List with triangles.</returns> public static List<VoxelMeshVertex> ComputeTriangles(Voxel[] cubeVoxels) { List<VoxelMeshVertex> result = new List<VoxelMeshVertex>(); VoxelMeshVertex[] interpolatedVertices = ComputeEdges(cubeVoxels); int caseNumber = CaseNumber(cubeVoxels); bool useDirectTable = !(IsAmbigous(caseNumber) && !useAmbigous); int offset = useDirectTable ? caseNumber * 15 : (255 - caseNumber) * 15; for (int i = 0; i < 5; i++, offset += 3) { if (faces[offset] != -1) { result.Add(interpolatedVertices[faces[offset + 0]]); result.Add(interpolatedVertices[faces[offset + 1]]); result.Add(interpolatedVertices[faces[offset + 2]]); } } return result; }
/// <summary> /// Computes case number based on voxels. /// </summary> /// <param name="voxels">Voxels used in computation.</param> /// <returns>Case number.</returns> private static int CaseNumber(Voxel[] voxels) { int caseNumber = 0; for (int i = -1; ++i < voxels.Length; caseNumber += voxels[i].Weight - seekValue > 0 ? 1 << i : 0) ; return caseNumber; }
/// <summary> /// Computes interpolated vertex on a cube edge between two voxels. /// </summary> /// <param name="v1">First voxel.</param> /// <param name="v2">Second voxel.</param> /// <returns>Interpolated vertex.</returns> private static VoxelMeshVertex ComputeEdge(Voxel v1, Voxel v2) { VoxelMeshVertex vertex = new VoxelMeshVertex(); float interpolation = (seekValue - v1.Weight) / (v2.Weight - v1.Weight); if (interpolation >= 0 && interpolation <= 1) { vertex.Position = v1.Position + (v2.Position - v1.Position) * interpolation; vertex.Normal = v1.Normal + (v2.Normal - v1.Normal) * interpolation; vertex.Ambient = v1.Ambient + (v2.Ambient - v1.Ambient) * interpolation; } return vertex; }
/// <summary> /// Generates mesh using mathematical functions. /// </summary> /// <param name="width">Mesh width.</param> /// <param name="height">Mesh height.</param> /// <param name="depth">Mesh depth.</param> public void GenerateFromFormula(int width, int height, int depth) { Voxel[, ,] voxels = new Voxel[width, height, depth]; float area = (float)Math.Sqrt(width * depth); Vector3 center = new Vector3(width / 2.0f, height / 2.0f, depth / 2.0f); Vector3[] pillars = new Vector3[] { new Vector3(width / 4.0f, 0, depth / 4.0f), new Vector3(width * 3.0f / 4.0f, 0, depth * 3.0f / 4.0f), new Vector3(width * 2.0f / 4.0f, 0, depth / 4.0f) }; NoiseCube n1 = new NoiseCube(16, 16, 16); NoiseCube n2 = new NoiseCube(16, 16, 16); NoiseCube n3 = new NoiseCube(16, 16, 16); NoiseCube n4 = new NoiseCube(16, 16, 16); Parallel.For(0, width, (x) => { for (int y = 0; y < height; y++) { for (int z = 0; z < depth; z++) { Vector3 position = new Vector3(x, y, z); float weight = 0; float distanceFromCenter = new Vector2(x - center.X, z - center.Z).Length(); distanceFromCenter = distanceFromCenter < 0.1f ? 0.1f : distanceFromCenter; // Create pillars. for (int i = 0; i < pillars.Length; i++) { float distance = new Vector2(x - pillars[i].X, z - pillars[i].Z).Length(); distance = distance < 0.1f ? 0.1f : distance; weight += area / distance; } // Subtract values near center. weight -= area / distanceFromCenter; // Subtract big values at outer edge. weight -= (float)Math.Pow(distanceFromCenter, 3) / (float)Math.Pow(area, 1.5f); // Create helix. double coordinate = 3 * Math.PI * y / height; Vector2 helix = new Vector2((float)Math.Cos(coordinate), (float)Math.Sin(coordinate)); weight += Vector2.Dot(helix, new Vector2(x - center.X, z - center.Z)); // Create shelves. weight += 10 * (float)Math.Cos(coordinate * 4 / 3); // Add a little randomness. weight += n1.GetInterpolatedValue(x / 32.04, y / 32.01, z / 31.97) * 8.0f; weight += n2.GetInterpolatedValue(x / 8.01, y / 7.96, z / 7.98) * 4.0f; weight += n3.GetInterpolatedValue(x / 4.01, y / 4.04, z / 3.96) * 2.0f; weight += n4.GetInterpolatedValue(x / 2.02, y / 1.98, z / 1.97) * 1.0f; voxels[x, y, z] = new Voxel() { Position = position, Weight = weight }; } } }); ComputeNormal(voxels); ComputeAmbient(voxels); CreateGeometryBuffer(ComputeTriangles(voxels, container.Settings.LevelOfDetail)); }
/// <summary> /// Computes triangle positions and normal vectors based on array of voxels. /// </summary> /// <param name="voxels">Array of voxels.</param> /// <param name="levelOfDetail">Lower value makes more triangles.</param> /// <returns>List of triangles.</returns> private List<VoxelMeshVertex> ComputeTriangles(Voxel[, ,] voxels, int levelOfDetail) { List<VoxelMeshVertex> triangles = new List<VoxelMeshVertex>(); for (int x = 0; x < voxels.GetLength(0) - levelOfDetail; x += levelOfDetail) { for (int y = 0; y < voxels.GetLength(1) - levelOfDetail; y += levelOfDetail) { for (int z = 0; z < voxels.GetLength(2) - levelOfDetail; z += levelOfDetail) { Voxel[] cubeVoxels = new Voxel[] { voxels[x, y, z], voxels[x, y, z + levelOfDetail], voxels[x + levelOfDetail, y, z + levelOfDetail], voxels[x + levelOfDetail, y, z], voxels[x, y + levelOfDetail, z], voxels[x, y + levelOfDetail, z + levelOfDetail], voxels[x + levelOfDetail, y + levelOfDetail, z + levelOfDetail], voxels[x + levelOfDetail, y + levelOfDetail, z] }; triangles.AddRange(VoxelCube.ComputeTriangles(cubeVoxels)); } } } return triangles; }
/// <summary> /// Computes normal vectors based on weight of each voxel. /// </summary> /// <param name="voxels">Array of voxels.</param> private void ComputeNormal(Voxel[, ,] voxels) { int width = voxels.GetLength(0); int height = voxels.GetLength(1); int depth = voxels.GetLength(2); Parallel.For(0, width, (x) => { for (int y = 0; y < height; y++) { for (int z = 0; z < depth; z++) { int xi = x == width - 1 ? x : x + 1; int xd = x == 0 ? x : x - 1; int yi = y == height - 1 ? y : y + 1; int yd = y == 0 ? y : y - 1; int zi = z == depth - 1 ? z : z + 1; int zd = z == 0 ? z : z - 1; Vector3 normal = new Vector3() { X = voxels[xi, y, z].Weight - voxels[xd, y, z].Weight, Y = voxels[x, yi, z].Weight - voxels[x, yd, z].Weight, Z = voxels[x, y, zi].Weight - voxels[x, y, zd].Weight }; normal = -normal; normal.Normalize(); voxels[x, y, z].Normal = normal; } } }); }
/// <summary> /// Computes ambient color for each voxel. /// </summary> /// <param name="voxels">Array of voxels.</param> private void ComputeAmbient(Voxel[, ,] voxels) { int width = voxels.GetLength(0); int height = voxels.GetLength(1); int depth = voxels.GetLength(2); float stepLength = (width * container.Settings.AmbientRayWidth / 100.0f) / container.Settings.AmbientSamplesCount; Parallel.For(0, width, (x) => { for (int y = 0; y < height; y++) { for (int z = 0; z < depth; z++) { float ambient = 0; Vector3 position = voxels[x, y, z].Position; for (int i = 0; i < poissonDisc.Length; i++) { float sample = 0; for (int j = 0; j < container.Settings.AmbientSamplesCount; j++) { // Ray starting point is situated in a small distance from center to avoid rendering artifacts. int stepNumber = j + 2; int cx = (int)Helper.Clamp(position.X + stepNumber * stepLength * poissonDisc[i].X, 0, width - 1); int cy = (int)Helper.Clamp(position.Y + stepNumber * stepLength * poissonDisc[i].Y, 0, height - 1); int cz = (int)Helper.Clamp(position.Z + stepNumber * stepLength * poissonDisc[i].Z, 0, depth - 1); sample += voxels[cx, cy, cz].Weight > 0 ? 0 : 1; } ambient += sample / container.Settings.AmbientSamplesCount; } voxels[x, y, z].Ambient = ambient / poissonDisc.Length; } } }); }
/// <summary> /// Generates mesh using some random values interpolated with trilinear interpolation. /// Uses coordinates transformed with warp values. /// </summary> /// <param name="width">Mesh widht.</param> /// <param name="height">Mesh height.</param> /// <param name="depth">Mesh depth.</param> public void GenerateFromNoiseCubeWithWarp(int width, int height, int depth) { Voxel[, ,] voxels = new Voxel[width, height, depth]; Vector3 center = new Vector3(width / 2, height / 2, depth / 2); NoiseCube n0 = new NoiseCube(16, 16, 16); NoiseCube n1 = new NoiseCube(16, 16, 16); NoiseCube n2 = new NoiseCube(16, 16, 16); NoiseCube n3 = new NoiseCube(16, 16, 16); NoiseCube n4 = new NoiseCube(16, 16, 16); Parallel.For(0, width, (x) => { for (int y = 0; y < height; y++) { for (int z = 0; z < depth; z++) { Vector3 position = new Vector3(x, y, z); float weight = center.Y - y; float warp = n0.GetInterpolatedValue(x * 0.004, y * 0.004, z * 0.004); float cx = x + warp * 8; float cy = y + warp * 8; float cz = z + warp * 8; weight += n1.GetInterpolatedValue(cx / 32.04, cy / 32.01, cz / 31.97) * 64.0f; weight += n2.GetInterpolatedValue(cx / 8.01, cy / 7.96, cz / 7.98) * 4.0f; weight += n3.GetInterpolatedValue(cx / 4.01, cy / 4.04, cz / 3.96) * 2.0f; weight += n4.GetInterpolatedValue(cx / 2.02, cy / 1.98, cz / 1.97) * 1.0f; voxels[x, y, z] = new Voxel() { Position = position, Weight = weight }; } } }); ComputeNormal(voxels); ComputeAmbient(voxels); CreateGeometryBuffer(ComputeTriangles(voxels, container.Settings.LevelOfDetail)); }