/// <summary> /// Constructor to set up terrain patch /// </summary> public TerrainPatch(GraphicsDevice graphicsDevice, Vector2 offset) { neighbors = new TerrainPatch[4]; meshes = new TerrainMesh[mipLevels]; mapOffset = offset; worldOffset = new Vector3(mapOffset.X * patchSize, 0f, -mapOffset.Y * patchSize); for (int i = 0; i < mipLevels; i++) { meshes[i] = new TerrainMesh(graphicsDevice, this, i); } }
/// <summary> /// Find the normals for each mesh vertex /// </summary> private void CalculateNormals(ushort[] indices, float heightScale) { int fullMeshSize = TerrainPatch.patchSize + 1; // store temporary normals Vector3[] vertexNormals = new Vector3[vertices.Length]; for (int i = 0; i < vertices.Length; i++) { vertexNormals[i] = new Vector3(0.5f, 0.5f, 1f); } for (int i = 0; i < indices.Length / 3; i++) { int index0 = indices[i * 3]; int index1 = indices[i * 3 + 1]; int index2 = indices[i * 3 + 2]; vertexNormals[index0] += Vector3.Up; vertexNormals[index1] += Vector3.Up; vertexNormals[index2] += Vector3.Up; //continue; Vector3 vertexPos0 = new Vector3 ( vertices[index0].VertexID % fullMeshSize, vertices[index0].VertexHeight * heightScale, -(int)(vertices[index0].VertexID / fullMeshSize) ); Vector3 vertexPos1 = new Vector3 ( vertices[index1].VertexID % fullMeshSize, vertices[index1].VertexHeight * heightScale, -(int)(vertices[index1].VertexID / fullMeshSize) ); Vector3 vertexPos2 = new Vector3 ( vertices[index2].VertexID % fullMeshSize, vertices[index2].VertexHeight * heightScale, -(int)(vertices[index2].VertexID / fullMeshSize) ); Vector3 side1 = vertexPos0 - vertexPos2; Vector3 side2 = vertexPos0 - vertexPos1; Vector3 normal = Vector3.Cross(side1, side2); vertexNormals[index0] += normal; vertexNormals[index1] += normal; vertexNormals[index2] += normal; } float epsilon = 0.0000001f; // Correct normalization for (int i = 0; i < vertices.Length; i++) { vertexNormals[i].Normalize(); //continue; // Fix side edges and we're done for this vertex if (i % meshSize == meshSize - 1 && terrainPatch.mapOffset.X < Terrain.gridSize.X - 1) { int adjacent = i - (meshSize - 1); TerrainMesh adjacentMesh = terrainPatch.neighbors[3].Meshes[mipLevel]; vertices[i].NormalX = adjacentMesh.vertices[adjacent].NormalX; vertices[i].NormalY = adjacentMesh.vertices[adjacent].NormalY; continue; } // Fix top and bottom edges and we're done for this vertex if (i / meshSize == meshSize - 1 && terrainPatch.mapOffset.Y < Terrain.gridSize.Y - 1) { int adjacent = i - (meshSize * (meshSize - 1)); TerrainMesh adjacentMesh = terrainPatch.neighbors[1].Meshes[mipLevel]; vertices[i].NormalX = adjacentMesh.vertices[adjacent].NormalX; vertices[i].NormalY = adjacentMesh.vertices[adjacent].NormalY; continue; } // Encode normal into 2 components Vector2 projectedNormal; float absZ = Math.Abs(vertexNormals[i].Z) + 1.0f; projectedNormal.X = vertexNormals[i].X / absZ; projectedNormal.Y = vertexNormals[i].Y / absZ; // Convert unit circle to square // We add epsilon to avoid division by zero float d = Math.Abs(projectedNormal.X) + Math.Abs(projectedNormal.Y) + epsilon; float r = Vector2.Distance(projectedNormal, Vector2.Zero); Vector2 q = projectedNormal * r / d; // Mirror triangles to outer edge if z is negative float negativeZ = Math.Max(-Math.Sign(vertexNormals[i].Z), 0f); Vector2 qSign = new Vector2(Math.Sign(q.X), Math.Sign(q.Y)); qSign.X = Math.Sign(qSign.X + 0.5f); qSign.Y = Math.Sign(qSign.Y + 0.5f); // Reflection: qr = q - 2 * n * (dot (q, n) - d) / dot (n, n) q -= negativeZ * (float)(Vector2.Dot(q, qSign) - 1.0) * qSign; vertices[i].NormalX = (short)(q.X * 32767); vertices[i].NormalY = (short)(q.Y * 32767); } // Finish reading normals }