// Initialize the terrain geometry from height textures. public void InitializeHeightsAndNormals() { var content = _services.GetInstance<ContentManager>(); // Create the height and normal texture for the 4 tiles. // We can do this in parallel. We use following lock for parts which are not thread-safe. var lockObject = new object(); Parallel.For(0, 2, row => //for (int row = 0; row < 2; row++) { Parallel.For(0, 2, column => //for (int column = 0; column < 2; column++) { string tilePostfix = "-" + row + "-" + column; // e.g. "-0-1" var tile = _tiles[row, column]; var terrainTile = tile.TerrainTile; // The height data is loaded from a 16-bit grayscale texture. Texture2D inputHeightTexture; lock (lockObject) inputHeightTexture = content.Load<Texture2D>("Terrain/Terrain001-Height" + tilePostfix); Debug.Assert(inputHeightTexture.Width == inputHeightTexture.Height, "This code assumes that terrain tiles are square."); // The height textures of the tiles overlap by one texel to avoid gaps. That means, the // input height texture for each tile is 1025 x 1025. The last texture column of the top // left tile is the same as the first column of the top right tile. The last row of the // top left tile is the same as the first row of the bottom left tile. // The tile size in world space units is: float tileSize = (inputHeightTexture.Width - 1) * terrainTile.CellSize; terrainTile.OriginX = -tileSize + tileSize * column; terrainTile.OriginZ = -tileSize + tileSize * row; Debug.Assert(Numeric.IsZero(terrainTile.OriginX % terrainTile.CellSize), "The tile origin must be an integer multiple of the cell size."); Debug.Assert(Numeric.IsZero(terrainTile.OriginZ % terrainTile.CellSize), "The tile origin must be an integer multiple of the cell size."); // Extract the height values from the texture. float[] heights = TerrainHelper.GetTextureLevelSingle(inputHeightTexture, 0); // Transform height values from [0, 1] to [_minHeight, _maxheight]. TerrainHelper.TransformTexture(heights, (_maxHeight - _minHeight), _minHeight); // Optional: Smooth the processed height map. (Very useful to remove artifacts from 8-bit // height maps.) TerrainHelper.SmoothTexture(heights, inputHeightTexture.Width, inputHeightTexture.Height, _smoothness); // We have to create a height and normal texture for each tile. // Reuse existing textures to avoid unnecessary allocation. (XNA can run out of memory // if we allocate to many textures in a short time.) Texture2D heightTexture = terrainTile.HeightTexture; Texture2D normalTexture = terrainTile.NormalTexture; // Convert the height value array into a suitable height texture. TerrainHelper.CreateHeightTexture( _graphicsService.GraphicsDevice, heights, inputHeightTexture.Width, inputHeightTexture.Height, _useNearestNeighborMipmaps, ref heightTexture); // Create a normal texture from the height values. TerrainHelper.CreateNormalTexture( _graphicsService.GraphicsDevice, heights, inputHeightTexture.Width, inputHeightTexture.Height, terrainTile.CellSize, _useNearestNeighborMipmaps, ref normalTexture); // If terrainTile.HeightTexture/NormalTexture was null, then CreateHeight/CreateNormal // has created a new texture. We have to dispose this textures in Dispose(). terrainTile.HeightTexture = heightTexture; terrainTile.NormalTexture = normalTexture; // Set the height field data for collision detection. var heightField = (HeightField)tile.RigidBody.Shape; heightField.OriginX = terrainTile.OriginX; heightField.OriginZ = terrainTile.OriginZ; heightField.WidthX = tileSize; heightField.WidthZ = tileSize; lock (lockObject) { heightField.SetSamples(heights, heightTexture.Width, heightTexture.Height); heightField.Invalidate(); } }); }); // Inform the terrain that the old texture content is invalid. TerrainNode.Terrain.Invalidate(); }