public void LoadOcclusionIntervalMap(Stream stream) { Log.Info("Loading occlusion interval map"); var occlusionIntervalMap = ByteVector4Grid.Load(stream); if (occlusionIntervalMap.Width != _size.X || occlusionIntervalMap.Height != _size.Y) { throw new IOException(Resources.OcclusionIntervalMapSizeEqualTerrain); } _occlusionIntervalMap = occlusionIntervalMap; }
public void TestSaveLoad() { using (var tempFile = new TemporaryFile("unit-tests")) { var grid = new ByteVector4Grid(new[,] { {new ByteVector4(0, 1, 2, 3), new ByteVector4(3, 2, 1, 0)}, {new ByteVector4(0, 10, 20, 30), new ByteVector4(30, 20, 10, 0)} }); grid.Save(tempFile); using (var stream = File.OpenRead(tempFile)) grid = ByteVector4Grid.Load(stream); grid[0, 0].Should().Be(new ByteVector4(0, 1, 2, 3)); grid[0, 1].Should().Be(new ByteVector4(3, 2, 1, 0)); grid[1, 0].Should().Be(new ByteVector4(0, 10, 20, 30)); grid[1, 1].Should().Be(new ByteVector4(30, 20, 10, 0)); } }
/// <inheritdoc/> protected override void Execute() { _result = new ByteVector4Grid(_heightMap.Width, _heightMap.Height); State = TaskState.Data; var progressLock = new object(); Parallel.For(0, _heightMap.Width, x => { for (int y = 0; y < _heightMap.Height; y++) { _result[x, y] = GetOcclusionVector(x, y); } lock (progressLock) UnitsProcessed += _heightMap.Height; }, CancellationToken); State = TaskState.Complete; }
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); }