/// <summary> /// Fully build a terrain node - used for root nodes /// </summary> /// <param name="item">Contains data to manage building the node</param> public static void BuildNode(TerrainNodeSplitItem item) { //// preserve current depth buffer and detach it from the device //DepthStencilBuffer previousDepth = game.GraphicsDevice.DepthStencilBuffer; //game.GraphicsDevice.DepthStencilBuffer = null; ///// geometry map ///// GenerateGeometryMap(item); item.GeometrySurface.ResolveHeightData(); item.CopyHeightData(ref item.GeometrySurface.HeightData); if (!item.IsSphere) { ///// height map ///// GenerateHeightmap(item); ///// normal texture ///// GenerateNormalTexture(item); ///// diffuse texture ///// GenerateDiffuseTexture(item); } //// re-attach the normal depth buffer //game.GraphicsDevice.DepthStencilBuffer = previousDepth; }
public static void QueueTerrainNodeSplit(TerrainNodeSplitItem item) { lock (splitQueue) { splitQueue.Enqueue(item); dataAvailableEvent.Set(); } }
/// <summary> /// Construct a terrain node instance from a pre-generated geometry map /// </summary> /// <param name="item"></param> /// <param name="level"></param> /// <param name="radius"></param> /// <param name="createTerrainNodeVertexBuffer"></param> public TerrainNode(TerrainNodeSplitItem item, int level, double radius, CreateTerrainNodeVertexBufferDelegate createTerrainNodeVertexBuffer, bool isSphere) { this.bounds = item.Bounds; this.level = level; this.radius = radius; this.createPosition = null; this.createTerrainNodeVertexBuffer = createTerrainNodeVertexBuffer; this.isSphere = isSphere; this.diffuseSurface = item.DiffuseSurface; this.normalSurface = item.NormalSurface; Initialize(item); }
public void Save(TerrainNodeSplitItem item) { float lowest = 999999; float highest = -9999999; // normalize heights for (int i = 0; i < width * height; i++) { if (HeightData[i] > highest) highest = HeightData[i]; if (HeightData[i] < lowest) lowest = HeightData[i]; } float range = highest - lowest; highest = 100; Color[] data = new Color[width * height]; for (int i = 0; i < width * height; i++) { // float c = (heightMap[i] - lowest) / range; // map lowest value to 0 float c = HeightData[i] / highest; // negative values will show as black, below sea level data[i] = new Color(c, c, c); } //#if !XBOX // using (FileStream stream = new FileStream(String.Format(@"screenshots\height_{0}_{1}.png", Globals.NormalMapNumber++, item.ChildIndex), FileMode.Create)) // { // Texture2D tex = new Texture2D(device, width, height, false, SurfaceFormat.Color); // tex.SetData<Color>(data); // tex.SaveAsPng(stream, tex.Width, tex.Height); // } //#endif }
private TerrainNode CreateRootTerrainNode(Face face) { // generate geometry map TerrainNodeSplitItem item = new TerrainNodeSplitItem(null, 0, new TerrainNodeBounds(-1.0, -1.0, 2.0, 2.0, TerrainNodeBounds.NodeQuadrant.None, face, 0), isSphere); TerrainNodeSplitManager.BuildNode(item); // build terrain node using geometry map return new TerrainNode(item, 0, radius, createTerrainNodeVertexBuffer, isSphere); }
/// <summary> /// Initialize the terrain node /// </summary> private void Initialize(TerrainNodeSplitItem item) { geometricError = Constants.ErrorFactor * (float)Math.Pow(0.5, level + 1); geometricError *= (float)(radius / Constants.EarthRadius); // TODO : adjust error based on earth-sized planet settings CalculatePatchPosition(item); GenerateMesh(item); }
/// <summary> /// Calculate the sphere-space position of the patch by determining where the center vertex lies on the sphere /// </summary> private void CalculatePatchPosition(TerrainNodeSplitItem item) { // find the cube position of the center vertex double x = bounds.Left + bounds.Width / 2; double y = bounds.Bottom + bounds.Height / 2; // get the matrix used to rotate the a position to the appropriate cube face Matrix faceMatrix = CubeFaces.GetFace(bounds.Face).FaceMatrix; // create the vertex position in cube-space and rotate it to the appropriate face Position3 cubePosition = new Position3(x, y, 1); cubePosition.Transform(ref faceMatrix); // get the vertex position on the unit sphere and rotate it to the appropriate face Position3 spherePosition = Tools.CubeToSphereMapping(x, y, 1); spherePosition.Transform(ref faceMatrix); spherePosition.Normalize(); Vector2 patchPosition = new Vector2(Constants.PatchWidth / 2, Constants.PatchHeight / 2); if (item == null) { // transform the vertex based on the user defined delegate - this returns the height value of the point on the sphere createPosition(ref spherePosition, ref cubePosition, ref patchPosition, radius, out position); } else { double h = item.HeightData[(int)((patchPosition.Y + 1) * (Constants.PatchWidth + 2) + (patchPosition.X + 1))]; if (h < 0) h = 0; position = spherePosition * (radius + h); } }
/// <summary> /// Generate terrain mesh for this node /// </summary> public void GenerateMesh(TerrainNodeSplitItem item) { // the minus one here is correct: e.g. 3x3 vertices labeled 0, 1, 2: v0.x = 0, v1.x = 0.5, v2.x = 1. The increment = 1 / (3 - 1) = 0.5 double horizontalStep = bounds.Width / (Constants.PatchWidth - 1); double verticalStep = bounds.Height / (Constants.PatchHeight - 1); float horizontalTextureStep = 1.0f / (Constants.PatchWidth - 1); float verticalTextureStep = 1.0f / (Constants.PatchHeight - 1); float MinX, MinY, MinZ; float MaxX, MaxY, MaxZ; // initialize min and max vertex tracking MinX = MinY = MinZ = 999999; MaxX = MaxY = MaxZ = -999999; // get matrix used to transform vertices to the proper cube face Matrix faceMatrix = CubeFaces.GetFace(bounds.Face).FaceMatrix; // create vertex storage using user supplied delegate vertexBuffer = createTerrainNodeVertexBuffer(item.HeightData.Length); //int cubeVertexIndex = 0; //CubeVertices = new Vector2[item.HeightData.Length]; hasMeshBorder = false; int vertexIndex = 0; int Rows = Constants.PatchHeight; int Columns = Constants.PatchWidth; if (item.HeightData.Length > Constants.PatchWidth * Constants.PatchHeight) { hasMeshBorder = true; Rows += 2; Columns += 2; } patchRows = Rows; patchColumns = Columns; float v = 0; double y = bounds.Bottom; if (hasMeshBorder) { v -= verticalTextureStep; y -= verticalStep; } for (int hy = 0; hy < Rows; hy++) { float u = 0; double x = bounds.Left; if (hasMeshBorder) { u -= horizontalTextureStep; x -= horizontalStep; } for (int hx = 0; hx < Columns; hx++) { // create the vertex position and rotate it to the appropriate face Position3 cubePosition = new Position3(x, y, 1); Position3 spherePosition = Tools.CubeToSphereMapping(x, y, 1); spherePosition.Transform(ref faceMatrix); cubePosition.Transform(ref faceMatrix); // transform the vertex based on the user defined delegate double height; Position3 finalPosition; Vector2 patchPosition = new Vector2(hx, hy); if (item == null) CreatePosition(ref spherePosition, ref cubePosition, ref patchPosition, out finalPosition, out height); else { height = item.HeightData[hy * Columns + hx]; if (height < 0) height = 0; finalPosition = spherePosition * (radius + height); TranslateToPatchSpace(ref finalPosition); } VertexPositionNormalTextureHeight vertex = new VertexPositionNormalTextureHeight(); vertex.Position = (Vector3)finalPosition; vertex.Height = (float)height; vertex.Normal = (Vector3)spherePosition; vertex.TextureCoordinate = new Vector2(u, v); vertex.Tangent = Vector4.Zero; vertexBuffer.Vertices[vertexIndex++] = vertex; // track min and max coordinates, but only for the vertices that will be in the final mesh if (hx >= 1 && hx < Columns - 1 && hy >= 1 && hy < Rows - 1) { if (vertex.Position.X < MinX) MinX = vertex.Position.X; if (vertex.Position.Y < MinY) MinY = vertex.Position.Y; if (vertex.Position.Z < MinZ) MinZ = vertex.Position.Z; if (vertex.Position.X > MaxX) MaxX = vertex.Position.X; if (vertex.Position.Y > MaxY) MaxY = vertex.Position.Y; if (vertex.Position.Z > MaxZ) MaxZ = vertex.Position.Z; } x += horizontalStep; u += horizontalTextureStep; } y += verticalStep; v += verticalTextureStep; } // create min and max bounding vertices, in patch-space minVertex = new Vector3(MinX, MinY, MinZ); maxVertex = new Vector3(MaxX, MaxY, MaxZ); // calculate normals and tangents CalculateNormals(); CalculateTangents(); // save vertex buffer changes - this effectively copies the raw data to a vertex buffer on the device vertexBuffer.CommitChanges(Constants.PatchWidth, Constants.PatchHeight); }
/// <summary> /// Executed inside a worker thread to handle generating a single child node /// </summary> /// <param name="item"></param> public void GenerateChild(TerrainNodeSplitItem item) { if (!CancelSplitting) { if (workingChildren == null) workingChildren = new TerrainNode[4]; workingChildren[item.ChildIndex] = new TerrainNode(item, level + 1, radius, createTerrainNodeVertexBuffer, item.IsSphere); } // if this was the last child then we're done! if (item.ChildIndex == 3) { if (!CancelSplitting) { // transfer working children over to actual children if (children == null) children = new TerrainNode[4]; for (int i = 0; i < 4; i++) children[i] = workingChildren[i]; ClearWorkingChildren(); } else { ClearWorkingChildren(); } CancelSplitting = false; Splitting = false; } }
private static void GenerateNormalTexture(TerrainNodeSplitItem item) { if (!Constants.DisableNormalMapGeneration) { // get normal surface from pool item.NormalSurface = normalSurfacePool.New(); // generate normal map based on heightmap normalGenerator.Execute(item.HeightmapSurface, item.NormalSurface, item.Bounds.Level); } }
private static void GenerateHeightmap(TerrainNodeSplitItem item) { if (Constants.ForceHeightmapGeneration || !Constants.DisableDiffuseTextureGeneration || !Constants.DisableNormalMapGeneration) { // get surface from pool item.HeightmapSurface = heightmapSurfacePool.New(); // generate the heightmap on the gpu heightmapGenerator.Execute(item.HeightmapSurface, item.Bounds); } }
private static void GenerateGeometryMap(TerrainNodeSplitItem item) { // get surface from pool item.GeometrySurface = geometrySurfacePool.New(); // generate the geometry map on the gpu if (item.IsSphere) terrainNodeSphereGenerator.Execute(item.GeometrySurface, item.Bounds); else terrainNodeGenerator.Execute(item.GeometrySurface, item.Bounds); }
private static void GenerateDiffuseTexture(TerrainNodeSplitItem item) { if (!Constants.DisableDiffuseTextureGeneration) { // get diffuse surface from pool item.DiffuseSurface = textureSurfacePool.New(); // generate diffuse texture based on heightmap textureGenerator.Execute(item.HeightmapSurface, item.NormalSurface, item.DiffuseSurface, 1.0f / 135.0f); // 135.0f); } }
public static void QueueNodeSplit(TerrainNodeSplitItem item) { geometryQueue.Enqueue(item); }