コード例 #1
0
ファイル: Chunk.cs プロジェクト: shaunrandall/SeedWorld
        public void Build(VoxelCache cache, Biome biome)
        {
            // Check if neighbors are loaded for next time
            if (state == ChunkState.VoxelsLoaded && NeighborsLoaded())
            {
                state = ChunkState.NeighborsLoaded;
            }

            // Next, build the mesh
            if (state == ChunkState.NeighborsLoaded)
            {
                if (!isEmpty && !isCompletelySolid)
                {
                    // Second pass: Add visible voxels
                    FindVisible(cache, biome);

                    // Third pass: Build mesh out of visible voxels
                    BuildMesh(cache, biome);
                }

                state = ChunkState.Meshed;

                // Remove data from cache and reset
                cache.voxels.Remove(offset);
                cache.ResetData();

                // Set matrix transformation
                transformMatrix = Matrix.CreateTranslation(offset.X, offset.Y, offset.Z);
            }
        }
コード例 #2
0
ファイル: VoxelSprite.cs プロジェクト: shaunrandall/SeedWorld
        /// <summary>
        /// Copy voxel data to 3D array
        /// </summary>
        /// <param name="voxels"></param>
        private void BuildVoxels(VoxelCache cache)
        {
            for (int i = 0; i < 256; i++)
            {
                byte b = voxelData[i * 3];
                byte g = voxelData[i * 3 + 1];
                byte r = voxelData[i * 3 + 2];
                colors[i] = (uint)((r << 24) | (g << 16) | (b << 8));
            }

            // Add non-empty voxels to the array
            for (int i = 768; i < voxelData.Length - 6; i += 4)
            {
                int x = voxelData[i + 1];
                int z = voxelData[i + 2];
                int y = voxelData[i + 3];
                cache.voxels[Vector3.One][x + 1, z + 1, y + 1] = voxelData[i];
            }

            // Add bounding box extents
            int offset = voxelData.Length - 6;

            extents[0] = new Vector3(voxelData[offset], voxelData[offset + 2], voxelData[offset + 1]);
            extents[1] = new Vector3(voxelData[offset + 3] + 1, voxelData[offset + 5] + 1, voxelData[offset + 4] + 1);
        }
コード例 #3
0
ファイル: Chunk.cs プロジェクト: shaunrandall/SeedWorld
        /// <summary>
        /// Generate additional 3D objects using the heightmap and surrounding blocks
        /// </summary>
        private void GenerateExtraObjects(Biome biome, VoxelCache cache, int x, int z)
        {
            int newOffsetX = (int)(offset.X + (x * cache.SizeX));
            int newOffsetZ = (int)(offset.Z + (z * cache.SizeZ));
            int seed       = cache.GetHashCode() ^ ((newOffsetX << 13) + (newOffsetZ >> 5));// ^ cache.MapSeed;

            Random rnd    = new Random(seed);
            int    localX = rnd.Next(cache.SizeX) ^ (mapSeed & 0x1f);
            int    localZ = rnd.Next(cache.SizeZ) ^ (mapSeed & 0x1f);

            localX++;
            localZ++;

            float objectDist = Noise.Simplex.Generate(
                (float)(((float)newOffsetX / 800f) + 120f),
                (float)(((float)newOffsetZ / 800f) + 100f));

            objectDist = (float)Math.Pow((objectDist * 0.5f) + 0.5f, 1.2f);

            int objectChance = rnd.Next(100);
            int chance       = (int)(objectDist * 100f) / 2;

            // We can grab the height value instantly from the noise pattern
            float height = biome.GetBaseHeight(localX + newOffsetX, localZ + newOffsetZ, false);

            if (height < 80 && objectChance < chance)
            {
                Engine.TreePopulator treePopulator = new Engine.TreePopulator(biome, cache.voxels[offset], rnd);

                treePopulator.Populate(
                    localX + (x * cache.SizeX),
                    localZ + (z * cache.SizeZ),
                    (int)height, cache.SizeX);
            }
        }
コード例 #4
0
ファイル: VoxelSprite.cs プロジェクト: shaunrandall/SeedWorld
        /// <summary>
        /// Second step in voxel mesh generation
        /// Add visible voxels to the list.
        /// </summary>
        private void FindVisible(VoxelCache cache)
        {
            // Temporary array for visible voxels
            byte[, ,] visibleVoxels = new byte[cache.SizeX + 2, cache.SizeZ + 2, cache.SizeY + 2];

            for (int y = cache.SizeY - 1; y >= 0; --y)
            {
                Parallel.For(0, cache.SizeX, x =>
                {
                    for (int z = 0; z < cache.SizeZ; ++z)
                    {
                        byte voxel = (byte)cache.voxels[Vector3.One][x, z, y];
                        if (voxel != 0)
                        {
                            int neighbors = 0;

                            if (x == 0 || y == 0 || z == 0 ||
                                x == cache.SizeX + 1 || y == cache.SizeY + 1 || z == cache.SizeZ + 1)
                            {
                                visibleVoxels[x, z, y] = voxel;
                            }
                            else
                            {
                                // Immediate side neighbors
                                neighbors |= Convert.ToInt32(cache.voxels[Vector3.One][x - 1, z, y] > 0) << NeighborBlock.Middle_W;
                                neighbors |= Convert.ToInt32(cache.voxels[Vector3.One][x + 1, z, y] > 0) << NeighborBlock.Middle_E;
                                neighbors |= Convert.ToInt32(cache.voxels[Vector3.One][x, z - 1, y] > 0) << NeighborBlock.Middle_N;
                                neighbors |= Convert.ToInt32(cache.voxels[Vector3.One][x, z + 1, y] > 0) << NeighborBlock.Middle_S;

                                // Check top and bottom
                                neighbors |= Convert.ToInt32(cache.voxels[Vector3.One][x, z, y - 1] > 0) << NeighborBlock.Bottom_Center;
                                neighbors |= Convert.ToInt32(cache.voxels[Vector3.One][x, z, y + 1] > 0) << NeighborBlock.Top_Center;

                                // Add to the visible list if not occluded on all sides
                                if (neighbors != 0x3f)
                                {
                                    visibleVoxels[x, z, y] = voxel;
                                    cache.visibleNeighbors[x - 1, z - 1, y - 1] = neighbors;
                                }
                            }
                        }
                        // Finish neighbor search on this voxel
                    }
                });
            }

            // Finish adding all visible voxels.
            // Copy them back to the original voxel array
            cache.voxels[Vector3.One] = visibleVoxels;
        }
コード例 #5
0
        /// <summary>
        /// Load chunks for the first time
        /// </summary>
        public ChunkManager(GraphicsDevice graphicsDevice, int seed)
        {
            // Set the initial seed
            this.mapSeed = seed;

            // Get the bounds of the visible world and set up chunk structures
            int totalChunks = (int)Math.Pow((visibleRadius * 2 + 1), 2);

            chunkStacks        = new Dictionary <Point, ChunkStack>(totalChunks);
            orderedChunkStacks = new List <ChunkStack>(totalChunks);

            // Initialize lists for chunk distance ordering
            chunks        = new SpeedyDictionary();// Dictionary<Vector3, Chunk>(totalChunks * chunksPerStack);
            orderedChunks = new List <Chunk>(totalChunks * chunksPerStack);

            // Setup biome and cache
            voxelCache = new VoxelCache(chunkSize, chunkSize, chunkSize);

            // Populator to add additional objects to chunks
            //populator = new Engine.TreePopulator(biome, voxelCache, new Random(seed));
        }
コード例 #6
0
ファイル: VoxelSprite.cs プロジェクト: shaunrandall/SeedWorld
        /// <summary>
        /// Load voxel data and create a mesh from it
        /// </summary>
        public void Load(VoxelCache cache, String filename)
        {
            this.voxelData = MagicaVoxImporter.Load(filename);

            // Setup cache
            cache.ResetData();
            cache.voxels.Add(Vector3.One,
                             new byte[cache.SizeX + 2, cache.SizeZ + 2, cache.SizeY + 2]);

            // First generate the height map and dense voxel data
            BuildVoxels(cache);

            // Second pass: Add visible voxels
            FindVisible(cache);

            // Third pass: Build mesh out of visible voxels
            BuildMesh(cache);

            // Reset cache
            cache.voxels.Remove(Vector3.One);
            cache.ResetData();
        }
コード例 #7
0
ファイル: Chunk.cs プロジェクト: shaunrandall/SeedWorld
        /// <summary>
        /// Generate a world chunk
        /// </summary>
        public void Setup(VoxelCache cache, Biome biome)
        {
            // Create lightweight voxel array. At 2 bits/voxel, Y axis shares 16 voxels per int
            voxels = new uint[cache.SizeX, cache.SizeZ, cache.SizeY / 16];

            cache.voxels.Remove(previousOffset);
            cache.voxels.Add(offset,
                             new byte[cache.SizeX + 2, cache.SizeZ + 2, cache.SizeY + 2]);

            // First generate the height map and dense voxel data
            BuildVoxels(cache, biome);
            state = ChunkState.VoxelsLoaded;

            // Remove voxels from cache if chunk doesn't need a mesh
            if (isEmpty || isCompletelySolid)
            {
                cache.voxels.Remove(offset);
            }

            // Reset cache
            cache.ResetData();
        }
コード例 #8
0
ファイル: VoxelSprite.cs プロジェクト: shaunrandall/SeedWorld
        /// <summary>
        /// Third step in mesh generation
        /// Turn visible voxels into meshes
        /// </summary>
        private void BuildMesh(VoxelCache cache)
        {
            int nextVertex = 0;
            int nextIndex  = 0;

            Object lockObject = new object();

            // Initialize vertex and index data
            VertexPositionColorNormal[] chunkVertices = new VertexPositionColorNormal[vertexMaxSize];
            ushort[] chunkIndices = new ushort[indexMaxSize];

            // Create the mesh cubes
            Parallel.For(1, sizeX + 1, x =>
            {
                for (int z = 1; z <= sizeZ; ++z)
                {
                    for (int y = 1; y <= sizeY; ++y)
                    {
                        byte voxel = (byte)cache.voxels[Vector3.One][x, z, y];

                        if (nextIndex + 72 > indexMaxSize)
                        {
                            // Skip
                        }
                        else if (voxel > 0)
                        {
                            int neighbors = cache.visibleNeighbors[x - 1, z - 1, y - 1];

                            // Get the corner and edge neighbors
                            // Top corners (bits 7-10)
                            neighbors |= Convert.ToInt32(cache.voxels[Vector3.One][x - 1, z - 1, y + 1] > 0) << NeighborBlock.Top_NW;
                            neighbors |= Convert.ToInt32(cache.voxels[Vector3.One][x + 1, z - 1, y + 1] > 0) << NeighborBlock.Top_NE;
                            neighbors |= Convert.ToInt32(cache.voxels[Vector3.One][x - 1, z + 1, y + 1] > 0) << NeighborBlock.Top_SW;
                            neighbors |= Convert.ToInt32(cache.voxels[Vector3.One][x + 1, z + 1, y + 1] > 0) << NeighborBlock.Top_SE;

                            // Top edges (bits 11-14)
                            neighbors |= Convert.ToInt32(cache.voxels[Vector3.One][x - 1, z, y + 1] > 0) << NeighborBlock.Top_W;
                            neighbors |= Convert.ToInt32(cache.voxels[Vector3.One][x + 1, z, y + 1] > 0) << NeighborBlock.Top_E;
                            neighbors |= Convert.ToInt32(cache.voxels[Vector3.One][x, z - 1, y + 1] > 0) << NeighborBlock.Top_N;
                            neighbors |= Convert.ToInt32(cache.voxels[Vector3.One][x, z + 1, y + 1] > 0) << NeighborBlock.Top_S;

                            // Bottom corners (bits 15-18)
                            neighbors |= Convert.ToInt32(cache.voxels[Vector3.One][x - 1, z - 1, y - 1] > 0) << NeighborBlock.Bottom_NW;
                            neighbors |= Convert.ToInt32(cache.voxels[Vector3.One][x + 1, z - 1, y - 1] > 0) << NeighborBlock.Bottom_NE;
                            neighbors |= Convert.ToInt32(cache.voxels[Vector3.One][x - 1, z + 1, y - 1] > 0) << NeighborBlock.Bottom_SW;
                            neighbors |= Convert.ToInt32(cache.voxels[Vector3.One][x + 1, z + 1, y - 1] > 0) << NeighborBlock.Bottom_SE;

                            // Bottom edges (bits 19-22)
                            neighbors |= Convert.ToInt32(cache.voxels[Vector3.One][x - 1, z, y - 1] > 0) << NeighborBlock.Bottom_W;
                            neighbors |= Convert.ToInt32(cache.voxels[Vector3.One][x + 1, z, y - 1] > 0) << NeighborBlock.Bottom_E;
                            neighbors |= Convert.ToInt32(cache.voxels[Vector3.One][x, z - 1, y - 1] > 0) << NeighborBlock.Bottom_N;
                            neighbors |= Convert.ToInt32(cache.voxels[Vector3.One][x, z + 1, y - 1] > 0) << NeighborBlock.Bottom_S;

                            // Middle side corners (bits 23-26)
                            neighbors |= Convert.ToInt32(cache.voxels[Vector3.One][x - 1, z - 1, y] > 0) << NeighborBlock.Middle_NW;
                            neighbors |= Convert.ToInt32(cache.voxels[Vector3.One][x + 1, z - 1, y] > 0) << NeighborBlock.Middle_NE;
                            neighbors |= Convert.ToInt32(cache.voxels[Vector3.One][x - 1, z + 1, y] > 0) << NeighborBlock.Middle_SW;
                            neighbors |= Convert.ToInt32(cache.voxels[Vector3.One][x + 1, z + 1, y] > 0) << NeighborBlock.Middle_SE;

                            // Set the cube color and shade
                            uint color = colors[voxel];

                            float[] shades = { 1f, 1f, 1f, 1f, 1f, 1f };

                            // Add the cube
                            Cube cube = new Cube(new Vector3(x, y, z), color, shades, neighbors);

                            lock (lockObject)
                            {
                                // Point next index value to number of vertices
                                int indexOffset = nextVertex;

                                for (int v = 0; v < cube.Vertices.Length; v++)
                                {
                                    chunkVertices[nextVertex++] = cube.Vertices[v];
                                }

                                for (int i = 0; i < cube.Indices.Length; i++)
                                {
                                    chunkIndices[nextIndex++] = (ushort)(indexOffset + cube.Indices[i]);
                                }
                            }
                        }
                        // Finish adding cube for this voxel
                    }
                }
            });

            // Create a mesh
            mesh = new MeshData();

            // Create bounding box
            mesh.bBox = new BoundingBox(extents[0], extents[1]);

            if (nextIndex > 0)
            {
                // Create vertex and index buffers
                mesh.vb = new VertexBuffer(
                    graphicsDevice,
                    VertexPositionColorNormal.VertexDeclaration,
                    nextVertex,
                    BufferUsage.None
                    );

                mesh.ib = new IndexBuffer(
                    graphicsDevice, IndexElementSize.SixteenBits,
                    (ushort)nextIndex,
                    BufferUsage.None
                    );

                // Set up vertex and index buffer
                mesh.vb.SetData(chunkVertices, 0, nextVertex);
                mesh.ib.SetData(chunkIndices, 0, nextIndex);
            }
            // Finished building meshes
        }
コード例 #9
0
ファイル: Chunk.cs プロジェクト: shaunrandall/SeedWorld
        /// <summary>
        /// Calculate shading for all sides of a cube
        /// </summary>
        private float[] ComputeShade(VoxelCache cache, Biome biome, int neighbors, int x, int y, int z)
        {
            // Get shade contributions using a ray cast
            float altShade = (float)(cache.lightVoxels[x, z, y] / 20f);

            altShade = 1 - (float)Math.Pow(altShade, 1.25f);

            // Get shade contributions using a ray cast
            float[] shade = new float[6];

            // Shade all the cube sides individually
            for (int s = 0; s < shade.Length; s++)
            {
                // Start at full light
                shade[s] = 1f;
                float step = 3f / points[s].Length;

                // Skipped sides that have neighbors
                if ((neighbors & 1 << s) == 1)
                {
                    continue;
                }

                for (int j = 0; j < points[s].Length; j++)
                {
                    if (shade[s] <= 0.1f)
                    {
                        break;
                    }

                    bool found = false;

                    for (float i = 2.5f; i <= 10.5f; i += 1f)
                    {
                        if (found)
                        {
                            continue;
                        }

                        Vector3 newBlock;
                        int     chunkIndex = 4;

                        newBlock.X = i * points[s][j].X + x;
                        newBlock.Y = i * points[s][j].Y + y;
                        newBlock.Z = i * points[s][j].Z + z;

                        int nx = (int)Math.Round(newBlock.X) - 1;
                        int ny = (int)Math.Round(newBlock.Y);
                        int nz = (int)Math.Round(newBlock.Z) - 1;

                        // Set the new voxel offset if ray enters another chunk
                        if (nx < 0)
                        {
                            chunkIndex--;
                        }
                        if (nz < 0)
                        {
                            chunkIndex -= 3;
                        }
                        if (nx >= cache.SizeX)
                        {
                            chunkIndex++;
                        }
                        if (nz >= cache.SizeZ)
                        {
                            chunkIndex += 3;
                        }

                        if (nx < 0)
                        {
                            nx += cache.SizeX;
                        }
                        if (nz < 0)
                        {
                            nz += cache.SizeZ;
                        }
                        if (nx >= cache.SizeX)
                        {
                            nx -= cache.SizeX;
                        }
                        if (nz >= cache.SizeZ)
                        {
                            nz -= cache.SizeZ;
                        }

                        // Continue if Y goes out of bounds
                        if (ny < 0 || ny >= cache.SizeY)
                        {
                            continue;
                        }

                        byte voxel = GetVoxel(nx, ny, nz);
                        if (chunkIndex != 4)
                        {
                            if (chunkIndex > 4)
                            {
                                chunkIndex--;
                            }
                            voxel = sideNeighbors[chunkIndex].GetVoxel(nx, ny, nz);
                        }

                        float falloff = 1f / i;
                        if (voxel != (byte)BlockType.Empty)
                        {
                            shade[s] -= (step * falloff);
                            found     = true;
                        }
                    }
                }

                shade[s] = (altShade > shade[s]) ? shade[s] : altShade;
                shade[s] = (shade[s] < 0.15f) ? 0.15f : shade[s];
            }

            return(shade);
        }
コード例 #10
0
ファイル: Chunk.cs プロジェクト: shaunrandall/SeedWorld
        /// <summary>
        /// Second step in chunk generation
        /// Add visible voxels to the list.
        /// </summary>
        private void FindVisible(VoxelCache cache, Biome biome)
        {
            // Assign temporary arrays for visible voxels
            byte[, ,] visibleVoxels = new byte[cache.SizeX + 2, cache.SizeX + 2, cache.SizeY + 2];
            byte[, ,] cacheVoxels   = cache.voxels[offset];

            Parallel.For(1, cache.SizeX + 1, x =>
            {
                for (int z = 0; z <= cache.SizeZ + 1; ++z)
                {
                    // Get last shade for x, z location
                    byte shade  = biome.lightMask[x, z];
                    bool shaded = (shade == 15);

                    for (int y = cache.SizeY + 1; y >= 0; --y)
                    {
                        byte voxel = cacheVoxels[x, z, y];

                        if (voxel != 0)
                        {
                            int neighbors = 0;

                            if (x == 0 || y == 0 || z == 0 ||
                                x == cache.SizeX + 1 || y == cache.SizeY + 1 || z == cache.SizeZ + 1)
                            {
                                visibleVoxels[x, z, y] = voxel;
                            }
                            else
                            {
                                // Immediate side neighbors
                                neighbors |= Convert.ToInt32(cacheVoxels[x - 1, z, y] > 0) << NeighborBlock.Middle_W;
                                neighbors |= Convert.ToInt32(cacheVoxels[x + 1, z, y] > 0) << NeighborBlock.Middle_E;
                                neighbors |= Convert.ToInt32(cacheVoxels[x, z - 1, y] > 0) << NeighborBlock.Middle_N;
                                neighbors |= Convert.ToInt32(cacheVoxels[x, z + 1, y] > 0) << NeighborBlock.Middle_S;

                                // Check top and bottom
                                neighbors |= Convert.ToInt32(cacheVoxels[x, z, y - 1] > 0) << NeighborBlock.Bottom_Center;
                                neighbors |= Convert.ToInt32(cacheVoxels[x, z, y + 1] > 0) << NeighborBlock.Top_Center;

                                // Add to the visible list if not occluded on all sides
                                if (neighbors != 0x3f)
                                {
                                    visibleVoxels[x, z, y] = voxel;
                                    cache.visibleNeighbors[x - 1, z - 1, y - 1] = neighbors;
                                }

                                cache.lightVoxels[x, z, y] = shade;
                                shade = 8;
                            }
                        }

                        // Finish checking this voxel
                        if (y == 1)
                        {
                            biome.lightMask[x, z] = shade;
                        }
                    }
                }
            });

            // Final voxel list is truncated


            cache.voxels[offset] = visibleVoxels;
        }
コード例 #11
0
ファイル: Chunk.cs プロジェクト: shaunrandall/SeedWorld
        /// <summary>
        /// First step in voxel chunk generation
        /// This could be used to get voxel data only (when mesh is not needed)
        /// </summary>
        private void BuildVoxels(VoxelCache cache, Biome biome)
        {
            // Generate the dense voxel data
            Parallel.For(0, cache.SizeX + 2, x =>
            {
                for (int z = 0; z < cache.SizeX + 2; ++z)
                {
                    //float slope = heightMap.CalculateSlope(x, z);
                    cache.heightValues[x, z] = biome.SourceHeight[x, z];
                    int yHeight = (int)cache.heightValues[x, z];

                    // Add some rivers
                    bool riverbed = false;

                    // Create an alternate height for river banks.
                    // Higher altitudes will have narrower streams
                    float maxRiverHeight = 120f;

                    // Create noise for the river depths
                    float noise = Noise.Simplex.Generate(
                        (float)(((offset.X + x) / (float)(noiseRes * 24f)) + 200f),
                        (float)(((offset.Z + z) / (float)(noiseRes * 24f)) + 200f));
                    noise += Noise.Simplex.Generate(
                        (float)(((offset.X + x) / (noiseRes * 4f)) + 200f),
                        (float)(((offset.Z + z) / (noiseRes * 4f)) + 200f)) / 5f;
                    noise += Noise.Simplex.Generate(
                        (float)(((offset.X + x) / (noiseRes / 2f)) + 200f),
                        (float)(((offset.Z + z) / (noiseRes / 2f)) + 200f)) / 50f;

                    noise *= MathHelper.Lerp(0, 1.5f, yHeight / maxRiverHeight);
                    noise  = (float)Math.Pow(Math.Abs(noise), 0.7f);

                    // Increase humidity around river areas
                    float riverDepth = MathHelper.Lerp(0.8f, 0.98f,
                                                       (yHeight > maxRiverHeight) ? 1 : yHeight / maxRiverHeight);
                    float surfaceHeight = cache.heightValues[x, z];

                    // Set depth of river beds
                    float maxRiverBankNoise = 0.10f;
                    if (noise >= 0.05f && noise < maxRiverBankNoise && yHeight < maxRiverHeight)
                    {
                        surfaceHeight -= (0.03f - (noise - 0.05f)) * 50f;
                        surfaceHeight--;
                    }

                    // Set the water level
                    float riverWaterLevel = surfaceHeight - (maxRiverBankNoise - 0.05f) * 100f;
                    riverWaterLevel++;

                    if (noise < 0.05f && yHeight < maxRiverHeight)
                    {
                        riverbed      = true;
                        surfaceHeight = (int)(yHeight * riverDepth);
                        surfaceHeight--;
                    }

                    // Add plateau height, first by setting a maximum height
                    float noise2 = Noise.Simplex.Generate(
                        (float)(((offset.X + x) / (float)(noiseRes * 5f)) + 200f),
                        (float)(((offset.Z + z) / (float)(noiseRes * 3.2f)) + 200f));
                    noise2 += Noise.Simplex.Generate(
                        (float)(((offset.X + x) / (float)(noiseRes * 1.6f)) + 200f),
                        (float)(((offset.Z + z) / (float)(noiseRes * 2)) + 200f)) / 5f;

                    float plateauRange = 50f;
                    int plateauHeight  = (int)((noise2 / 2f + 0.5f) * plateauRange);
                    plateauHeight     += 70;

                    for (int y = 0; y < cache.SizeY + 2; ++y)
                    {
                        // Get voxels from height indexes
                        BlockType block = BlockType.Empty;

                        // Add block type depending on relation to surface
                        if (y + offset.Y < (int)surfaceHeight)
                        {
                            block = BlockType.Surface;
                        }

                        if (riverbed && y + offset.Y < riverWaterLevel)
                        {
                            block = BlockType.Water;
                        }

                        if (y + offset.Y == (int)surfaceHeight && y + offset.Y < yHeight)
                        {
                            block = BlockType.Surface;
                        }

                        // Add actual plateau blocks here
                        if (y + offset.Y >= yHeight && y + offset.Y <= plateauHeight)
                        {
                            noise2 = Noise.Simplex.Generate(
                                (float)(((offset.X + x) / (noiseRes * 1.5f)) + 200f),
                                (float)(((offset.Y + y) / (noiseRes * 3f)) + 200f),
                                (float)(((offset.Z + z) / (noiseRes * 1.25f)) + 200f));
                            noise2 += Noise.Simplex.Generate(
                                (float)(((offset.X + x) / (noiseRes / 1.5f)) + 200f),
                                (float)(((offset.Y + y) / (noiseRes)) + 200f),
                                (float)(((offset.Z + z) / (noiseRes / 1.25f)) + 200f)) / 5f;
                            noise2 += Noise.Simplex.Generate(
                                (float)(((offset.X + x) / (noiseRes / 5f)) + 200f),
                                (float)(((offset.Y + y) / (noiseRes / 2f)) + 200f),
                                (float)(((offset.Z + z) / (noiseRes / 5f)) + 200f)) / 10f;

                            // Adjust plateau height around rivers
                            float threshold = 0.3f;
                            if (noise < maxRiverBankNoise * 3f)
                            {
                                noise2 *= (float)Math.Pow((noise / (maxRiverBankNoise * 3f)), 0.5);
                            }

                            if (noise2 > threshold)
                            {
                                // Inner blocks
                                block = BlockType.Dirt;

                                if (y + offset.Y == plateauHeight)
                                {
                                    block = BlockType.Surface;
                                }
                            }
                        }

                        // Set upper and lower bounds
                        if (block != BlockType.Empty)
                        {
                            cache.voxels[offset][x, z, y] = (byte)block;
                            isEmpty = false;
                        }
                        else if (isCompletelySolid)
                        {
                            isCompletelySolid = false;
                        }
                    }
                    // Finished updating this voxel
                }
            });

            // Add extra objects to the surface such as trees
            for (int i = 0; i < 9; i++)
            {
                int x = i / 3 - 1;
                int z = i % 3 - 1;
                GenerateExtraObjects(biome, cache, x, z);
            }

            // Set lightweight voxel data for collisions/physics
            Parallel.For(1, cache.SizeX + 1, x =>
            {
                for (int z = 1; z < cache.SizeZ + 1; ++z)
                {
                    for (int y = 0; y < cache.SizeY; ++y)
                    {
                        int voxelY    = y / 16;
                        int voxelBits = (y % 16) * 2;

                        uint voxel = voxels[x - 1, z - 1, voxelY];

                        // Add 1 to voxel data
                        BlockType block = (BlockType)cache.voxels[offset][x, z, y];

                        if (block != BlockType.Empty)
                        {
                            voxel = voxel | ((uint)1 << voxelBits);
                        }

                        voxels[x - 1, z - 1, voxelY] = voxel;
                    }
                }
            });

            // Set bounding box extents
            extents[0] = new Vector3(offset.X, offset.Y, offset.Z);
            extents[1] = new Vector3(offset.X + cache.SizeX, offset.Y + cache.SizeY, offset.Z + cache.SizeX);

            // Create temp mesh and bounding box
            mesh      = new MeshData();
            mesh.bBox = new BoundingBox(extents[0], extents[1]);
        }