public void LoadTextureMap(Stream stream) { Log.Info("Loading terrain texture-map"); var textureMap = NibbleGrid.Load(stream); if (textureMap.Width != _size.X / 3 || textureMap.Height != _size.Y / 3) { throw new IOException(Resources.TextureMapSizeThirdOfTerrain); } _textureMap = textureMap; }
public void TestSaveLoad() { using (var tempFile = new TemporaryFile("unit-tests")) { var grid = new NibbleGrid(new byte[,] {{2, 4}, {5, 10}}); grid.Save(tempFile); using (var stream = File.OpenRead(tempFile)) grid = NibbleGrid.Load(stream); grid[0, 0].Should().Be(2); grid[0, 1].Should().Be(4); grid[1, 0].Should().Be(5); grid[1, 1].Should().Be(10); } }
/// <summary> /// Creates a new terrain. It is completely flat and has only one texture initially. /// </summary> /// <param name="size">The size of the terrain to create.</param> public Terrain(TerrainSize size) { Size = size; _heightMap = new ByteGrid(size.X, size.Y); _textureMap = new NibbleGrid(size.X / 3, size.Y / 3); // Try to use "Grass" as the default Terrain type try { Templates[0] = Template <TTemplate> .All["Grass"]; } catch (KeyNotFoundException ex) { Log.Warn(ex); } catch (InvalidOperationException ex) { Log.Warn(ex); } }
private static PositionMultiTextured[] GenerateVertexes(Size size, float stretchH, float stretchV, ByteGrid heightMap, NibbleGrid textureMap, ByteVector4Grid occlusionIntervalMap) { var vertexes = new PositionMultiTextured[size.Width * size.Height]; #if NETFX4 Parallel.For(0, size.Width, x => #else for (int x = 0; x < size.Width; x++) #endif { for (int y = 0; y < size.Height; y++) { #region Texture blending var texWeights = new float[16]; // Perform integer division (texture map has 1/3 of height map accuracy) but keep remainders int xRemainder; int xCoord = Math.DivRem(x, 3, out xRemainder); int yRemainder; int yCoord = Math.DivRem(y, 3, out yRemainder); // Use remainders to determine intermediate blending levels for vertexes switch (xRemainder) { case 0: TextureBlendHelper(textureMap, xCoord, yCoord, texWeights, 0.25f); TextureBlendHelper(textureMap, xCoord - 1, yCoord, texWeights, 0.25f); break; case 1: TextureBlendHelper(textureMap, xCoord, yCoord, texWeights, 0.5f); break; case 2: TextureBlendHelper(textureMap, xCoord, yCoord, texWeights, 0.25f); TextureBlendHelper(textureMap, xCoord + 1, yCoord, texWeights, 0.25f); break; } switch (yRemainder) { case 0: TextureBlendHelper(textureMap, xCoord, yCoord, texWeights, 0.25f); TextureBlendHelper(textureMap, xCoord, yCoord - 1, texWeights, 0.25f); break; case 1: TextureBlendHelper(textureMap, xCoord, yCoord, texWeights, 0.5f); break; case 2: TextureBlendHelper(textureMap, xCoord, yCoord, texWeights, 0.25f); TextureBlendHelper(textureMap, xCoord, yCoord + 1, texWeights, 0.25f); break; } #endregion var occlusionIntervals = occlusionIntervalMap?[x, y] ?? new ByteVector4(0, 255, 255, 255); // Generate vertex using 2D coords, stretch factors (and tex-coords based on them) // Map X = Engine +X // Map Y = Engine -Z // Map height = Engine +Y vertexes[x + size.Width * y] = new PositionMultiTextured( new Vector3(x * stretchH, heightMap[x, y] * stretchV, -y * stretchH), x * stretchH / 500f, y * stretchH / 500f, occlusionIntervals.ByteToAngle(), texWeights, Color.White); } }
/// <summary> /// Creates a new textured model from a vertex and an index array. /// The <see cref="PrimitiveType"/> is <see cref="PrimitiveType.TriangleList"/>. /// </summary> /// <param name="engine">The <see cref="Engine"/> to create the mesh in</param> /// <param name="size">The size of the terrain</param> /// <param name="stretchH">A factor by which all horizontal distances are multiplied</param> /// <param name="stretchV">A factor by which all vertical distances are multiplied</param> /// <param name="heightMap">The height values of the terrain in a 2D grid. /// Grid size = Terrain size</param> /// <param name="textureMap">The texture values of the terrain in a 2D grid. /// Grid size = Terrain size / 3</param> /// <param name="occlusionIntervalMap">The angles at which the global light source occlusion begins and ends. /// Grid size = Terrain size; may be <c>null</c> for no shadowing</param> /// <param name="lighting">Shall this mesh be prepared for lighting? (calculate normal vectors, make shaders support lighting, ...)</param> /// <param name="blockSize">How many points in X and Y direction shall one block for culling be?</param> /// <param name="subsetShaders">Shaders for all subsets the mesh was split into</param> /// <param name="subsetBoundingBoxes">Bounding boxes for all subsets the mesh was split into</param> /// <returns>The model that was created</returns> private static Mesh BuildMesh(Engine engine, Size size, float stretchH, float stretchV, ByteGrid heightMap, NibbleGrid textureMap, ByteVector4Grid occlusionIntervalMap, bool lighting, int blockSize, out SurfaceShader[] subsetShaders, out BoundingBox[] subsetBoundingBoxes) { #region Sanity checks if (heightMap.Width != size.Width || heightMap.Height != size.Height) throw new ArgumentException(Resources.WrongHeightMapSize, nameof(heightMap)); if (occlusionIntervalMap != null && (occlusionIntervalMap.Width != size.Width || occlusionIntervalMap.Height != size.Height)) throw new ArgumentException(Resources.WrongOcclusionIntervalMapSize, nameof(occlusionIntervalMap)); if ((textureMap.Width) * 3 != size.Width || (textureMap.Height) * 3 != size.Height) throw new ArgumentException(Resources.WrongTextureMapSize, nameof(textureMap)); #endregion using (new TimedLogEvent("Building terrain mesh")) { var vertexes = GenerateVertexes(size, stretchH, stretchV, heightMap, textureMap, occlusionIntervalMap); int[] attributes; ushort[] subsetTextureMasks; var indexes = GenerateIndexes(size, stretchH, stretchV, blockSize, vertexes, out attributes, out subsetTextureMasks, out subsetBoundingBoxes); subsetShaders = LoadShaders(engine, lighting, subsetTextureMasks); return CompileMesh(engine, vertexes, indexes, attributes, lighting); } }
/// <summary> /// Creates a new terrain from a height-map and a texture-map /// </summary> /// <param name="engine">The <see cref="Engine"/> to create the terrain in</param> /// <param name="size">The size of the terrain</param> /// <param name="stretchH">A factor by which all horizontal distances are multiplied</param> /// <param name="stretchV">A factor by which all vertical distances are multiplied</param> /// <param name="heightMap">The height values of the terrain in a 2D array. /// Grid size = Terrain size</param> /// <param name="occlusionIntervalMap">The angles at which the global light source occlusion begins and ends. /// Grid size = Terrain size; may be <c>null</c> for no shadowing</param> /// <param name="textureMap">The texture values of the terrain in a 2D array. /// Grid size = Terrain size / 3</param> /// <param name="textures">An array with a maximum of 16 texture names associated to <paramref name="textureMap"/></param> /// <param name="lighting">Shall this mesh be prepared for lighting? (calculate normal vectors, make shaders support lighting, ...)</param> /// <param name="blockSize">How many points in X and Y direction shall one block for culling be?</param> /// <returns>The newly created terrain</returns> /// <exception cref="FileNotFoundException">On of the specified texture files could not be found.</exception> /// <exception cref="IOException">There was an error reading one of the texture files.</exception> /// <exception cref="UnauthorizedAccessException">Read access to one of the texture files is not permitted.</exception> /// <exception cref="InvalidDataException">One of the texture files does not contain a valid texture.</exception> public static Terrain Create(Engine engine, Size size, float stretchH, float stretchV, ByteGrid heightMap, NibbleGrid textureMap, string[] textures, ByteVector4Grid occlusionIntervalMap, bool lighting, int blockSize) { #region Sanity checks if (engine == null) { throw new ArgumentNullException(nameof(engine)); } if (heightMap == null) { throw new ArgumentNullException(nameof(heightMap)); } if (textureMap == null) { throw new ArgumentNullException(nameof(textureMap)); } if (textures == null) { throw new ArgumentNullException(nameof(textures)); } #endregion if (TerrainShader.MinShaderModel > engine.Capabilities.MaxShaderModel) { throw new NotSupportedException(Resources.NotSupportedShader); } // Generate mesh with subsets and bounding bodies BoundingBox[] subsetBoundingBoxes; SurfaceShader[] subsetShaders; var terrain = new Terrain( BuildMesh(engine, size, stretchH, stretchV, heightMap, textureMap, occlusionIntervalMap, lighting, blockSize, out subsetShaders, out subsetBoundingBoxes), BuildMaterial(engine, textures), lighting) { // Set properties here to keep contructor nice and simple Size = size, StretchH = stretchH, StretchV = stretchV, _blockSize = blockSize, _subsetBoundingBoxes = subsetBoundingBoxes, _subsetShaders = subsetShaders, NumberSubsets = subsetBoundingBoxes.Length }; return(terrain); }