/// <summary>
        /// Gets the chunk if exits or create it if forceCreation is set to true.
        /// </summary>
        /// <returns><c>true</c>, if chunk fast was gotten, <c>false</c> otherwise.</returns>
        /// <param name="chunkX">Chunk x.</param>
        /// <param name="chunkY">Chunk y.</param>
        /// <param name="chunkZ">Chunk z.</param>
        /// <param name="chunk">Chunk.</param>
        /// <param name="createIfNotAvailable">If set to <c>true</c> force creation if chunk doesn't exist.</param>
        bool GetChunkFast(int chunkX, int chunkY, int chunkZ, out VoxelChunk chunk, bool createIfNotAvailable = false)
        {
            lock (lockLastChunkFetch) {
                if (lastChunkFetchX == chunkX && lastChunkFetchY == chunkY && lastChunkFetchZ == chunkZ && (object)lastChunkFetch != null)
                {
                    chunk = lastChunkFetch;
                    return(true);
                }
            }
            int hash = GetChunkHash(chunkX, chunkY, chunkZ);

            STAGE = 501;
            CachedChunk cachedChunk;

            cachedChunks.TryGetValue(hash, out cachedChunk);
            bool exists = (object)cachedChunk != null;

            if (createIfNotAvailable)
            {
                if (!exists)
                {
                    STAGE = 502;
                    // not yet created, create it
                    cachedChunk       = new CachedChunk();
                    cachedChunk.chunk = CreateChunk(hash, chunkX, chunkY, chunkZ, false);
                    exists            = true;
                }
                if ((object)cachedChunk.chunk == null)                   // chunk is really empty, create it with empty space
                {
                    STAGE             = 503;
                    cachedChunk.chunk = CreateChunk(hash, chunkX, chunkY, chunkZ, true);
                }
            }
            STAGE = 0;
            if (exists)
            {
                chunk = cachedChunk.chunk;
                lock (lockLastChunkFetch) {
                    lastChunkFetchX = chunkX;
                    lastChunkFetchY = chunkY;
                    lastChunkFetchZ = chunkZ;
                    lastChunkFetch  = chunk;
                }
                return(chunk != null);
            }
            else
            {
                chunk = null;
                return(false);
            }
        }
        /// <summary>
        /// Destroys all chunks and internal engine structures - should be used only during shutdown
        /// </summary>
        public void DisposeAll()
        {
            StopGenerationThread();

            DestroyNavMesh();
            chunksPool             = null;
            chunksPoolCurrentIndex = -1;
            chunksPoolLoadIndex    = 0;
            chunksPoolFetchNew     = true;
            lastChunkFetch         = null;

            // Empty mesh jobs
            if (meshJobs != null)
            {
                for (int k = 0; k < meshJobs.Length; k++)
                {
                    if (meshJobs [k].vertices != null)
                    {
                        meshJobs [k].vertices.Clear();
                        meshJobs [k].vertices = null;
                    }
                    if (meshJobs [k].uv0 != null)
                    {
                        meshJobs [k].uv0.Clear();
                        meshJobs [k].uv0 = null;
                    }
                    if (meshJobs [k].uv2 != null)
                    {
                        meshJobs [k].uv2.Clear();
                        meshJobs [k].uv2 = null;
                    }
                    if (meshJobs [k].colors != null)
                    {
                        meshJobs [k].colors.Clear();
                        meshJobs [k].colors = null;
                    }
                    if (meshJobs [k].normals != null)
                    {
                        meshJobs [k].normals.Clear();
                        meshJobs [k].normals = null;
                    }
                    if (meshJobs [k].buffers != null)
                    {
                        for (int j = 0; j < MAX_MATERIALS_PER_CHUNK; j++)
                        {
                            if (meshJobs [k].buffers [j].indices != null)
                            {
                                meshJobs [k].buffers [j].indices.Clear();
                                meshJobs [k].buffers [j].indices      = null;
                                meshJobs [k].buffers [j].indicesArray = null;
                            }
                        }
                    }
                    if (meshJobs [k].colliderVertices != null)
                    {
                        meshJobs [k].colliderVertices.Clear();
                        meshJobs [k].colliderVertices = null;
                    }
                    if (meshJobs [k].colliderIndices != null)
                    {
                        meshJobs [k].colliderIndices.Clear();
                        meshJobs [k].colliderIndices = null;
                    }
                    if (meshJobs [k].navMeshVertices != null)
                    {
                        meshJobs [k].navMeshVertices.Clear();
                        meshJobs [k].navMeshVertices = null;
                    }
                    if (meshJobs [k].navMeshIndices != null)
                    {
                        meshJobs [k].navMeshIndices.Clear();
                        meshJobs [k].navMeshIndices = null;
                    }
                    if (meshJobs [k].mivs != null)
                    {
                        meshJobs [k].mivs.Clear();
                        meshJobs [k].mivs = null;
                    }
                }
            }

            // Destroy chunks
            if (cachedChunks != null)
            {
                foreach (KeyValuePair <int, CachedChunk> kv in cachedChunks)
                {
                    CachedChunk cc = kv.Value;
                    if (cc != null && cc.chunk != null)
                    {
                        DestroyImmediate(cc.chunk.gameObject);
                    }
                }
                cachedChunks.Clear();
            }
            chunkRequestLast = -1;
            cachedChunks     = null;

            if (worldRoot != null)
            {
                while (worldRoot.childCount > 0)
                {
                    DestroyImmediate(worldRoot.GetChild(0).gameObject);
                }
            }
            worldRoot  = null;
            cloudsRoot = null;
            chunksRoot = null;
            fxRoot     = null;

            lastChunkX = int.MaxValue;

            // Clear heightmap
            if (heightMapCache != null)
            {
                heightMapCache.Clear();
            }

            // Clear render queue
            chunkQueueRoot       = 0;
            lastChunkDistanceSqr = 0;

            ClearStats();

            // reset voxel definitions state values
            if (voxelDefinitions != null)
            {
                for (int k = 0; k < voxelDefinitions.Length; k++)
                {
                    VoxelDefinition vd = voxelDefinitions [k];
                    if (vd != null)
                    {
                        vd.Reset();
                    }
                }
            }

            initialized = false;
        }
        /// <summary>
        /// Creates the chunk.
        /// </summary>
        /// <returns>The chunk.</returns>
        /// <param name="hash">Hash.</param>
        /// <param name="chunkX">Chunk x.</param>
        /// <param name="chunkY">Chunk y.</param>
        /// <param name="chunkZ">Chunk z.</param>
        /// <param name="createEmptyChunk">If set to <c>true</c> create empty chunk.</param>
        /// <param name="complete">If set to <c>true</c> detail generators will fire as well as OnChunkCreated event. Chunk will be marked as populated and a refresh will be triggered if within view distance.</param>
        VoxelChunk CreateChunk(int hash, int chunkX, int chunkY, int chunkZ, bool createEmptyChunk, bool complete = true)
        {
            STAGE = 101;
            Vector3 position;

            position.x = chunkX * 16 + 8;
            position.y = chunkY * 16 + 8;
            position.z = chunkZ * 16 + 8;

            STAGE = 102;
            CachedChunk cachedChunk;

            // Create entry in the dictionary
            if (!cachedChunks.TryGetValue(hash, out cachedChunk))
            {
                cachedChunk         = new CachedChunk();
                cachedChunks [hash] = cachedChunk;
            }

            STAGE = 103;
            VoxelChunk chunk;

            if ((object)cachedChunk.chunk == null)
            {
                // Fetch a new entry in the chunks pool
                if (chunksPoolFetchNew)
                {
                    chunksPoolFetchNew = false;
                    FetchNewChunkIndex(position);
                }
                chunk = chunksPool [chunksPoolCurrentIndex];
            }
            else
            {
                chunk = cachedChunk.chunk;
            }

            // Paint voxels
            bool chunkHasContents = false;

            chunk.position = position;


            STAGE = 104;
            if (createEmptyChunk)
            {
                chunk.isAboveSurface = CheckIfChunkAboveTerrain(position);
            }
            else
            {
                if (world.infinite || (position.x >= -world.extents.x && position.x <= world.extents.x && position.y >= -world.extents.y && position.y <= world.extents.y && position.z >= -world.extents.z && position.z <= world.extents.z))
                {
                    if (OnChunkBeforeCreate != null)
                    {
                        // allows a external function to fill the contents of this new chunk
                        bool isAboveSurface = true;
                        OnChunkBeforeCreate(position, out chunkHasContents, chunk.voxels, out isAboveSurface);
                        chunk.isAboveSurface = isAboveSurface;
                    }
                    if (!chunkHasContents)
                    {
                        if (position.y < CLOUDS_SPECIAL_ALTITUDE)
                        {
                            chunkHasContents = world.terrainGenerator.PaintChunk(chunk);
                        }
                        if (!chunkHasContents)
                        {
                            chunk.isAboveSurface = true;
                        }
                    }
                }
            }

            STAGE = 105;
            VoxelChunk nchunk;

            if (chunkHasContents || createEmptyChunk)
            {
                // lit chunk if not global illumination
                if (!effectiveGlobalIllumination)
                {
                    chunk.ClearLightmap(15);
                }
                chunksPoolFetchNew = true;
                chunksCreated++;

                cachedChunk.chunk = chunk;

                if (complete)
                {
                    chunk.isPopulated = true;

                    // Check for detail generators
                    if (worldHasDetailGenerators)
                    {
                        for (int d = 0; d < world.detailGenerators.Length; d++)
                        {
                            if (world.detailGenerators [d].enabled)
                            {
                                world.detailGenerators [d].AddDetail(chunk);
                            }
                        }
                    }

                    if (chunkHasContents)
                    {
                        // if chunk is near camera, request a render refresh
                        bool sendRefresh = (chunkX >= visible_xmin && chunkX <= visible_xmax && chunkZ >= visible_zmin && chunkZ <= visible_zmax && chunkY >= visible_ymin && chunkY <= visible_ymax);
                        if (sendRefresh)
                        {
                            ChunkRequestRefresh(chunk, false, true);
                        }
                        // Check if neighbours are inconclusive because this chunk was not present
                        nchunk = chunk.bottom;
                        if (nchunk != null && (nchunk.inconclusiveNeighbours & CHUNK_TOP) != 0)
                        {
                            ChunkRequestRefresh(nchunk, true, true);
                        }
                        nchunk = chunk.top;
                        if (nchunk != null && (nchunk.inconclusiveNeighbours & CHUNK_BOTTOM) != 0)
                        {
                            ChunkRequestRefresh(nchunk, true, true);
                        }
                        nchunk = chunk.left;
                        if (nchunk != null && (nchunk.inconclusiveNeighbours & CHUNK_RIGHT) != 0)
                        {
                            ChunkRequestRefresh(nchunk, true, true);
                        }
                        nchunk = chunk.right;
                        if (nchunk != null && (nchunk.inconclusiveNeighbours & CHUNK_LEFT) != 0)
                        {
                            ChunkRequestRefresh(nchunk, true, true);
                        }
                        nchunk = chunk.back;
                        if (nchunk != null && (nchunk.inconclusiveNeighbours & CHUNK_FORWARD) != 0)
                        {
                            ChunkRequestRefresh(nchunk, true, true);
                        }
                        nchunk = chunk.forward;
                        if (nchunk != null && (nchunk.inconclusiveNeighbours & CHUNK_BACK) != 0)
                        {
                            ChunkRequestRefresh(nchunk, true, true);
                        }
                    }
                    else
                    {
                        chunk.renderState = ChunkRenderState.RenderingComplete;
                    }

                    if ((object)OnChunkAfterCreate != null)
                    {
                        OnChunkAfterCreate(chunk);
                    }
                }

                STAGE = 0;
                return(chunk);
            }
            else
            {
                chunk.renderState = ChunkRenderState.RenderingComplete;
                STAGE             = 0;
                return(null);
            }
        }
        /// <summary>
        /// Destroys all chunks and internal engine structures - should be used only during shutdown
        /// </summary>
        public void DisposeAll()
        {
            StopGenerationThreads();
            DisposeRenderer();
            DestroyNavMesh();
            chunksPool             = null;
            chunksPoolCurrentIndex = -1;
            chunksPoolLoadIndex    = 0;
            chunksPoolFetchNew     = true;
            lastChunkFetch         = null;

            // Destroy chunks
            if (cachedChunks != null)
            {
                foreach (KeyValuePair <int, CachedChunk> kv in cachedChunks)
                {
                    CachedChunk cc = kv.Value;
                    if (cc != null && cc.chunk != null)
                    {
                        if (cc.chunk.mf != null && cc.chunk.mf.sharedMesh != null)
                        {
                            DestroyImmediate(cc.chunk.mf.sharedMesh);
                        }
                        if (cc.chunk.mc != null && cc.chunk.mc.sharedMesh != null)
                        {
                            DestroyImmediate(cc.chunk.mc.sharedMesh);
                        }
                        if (cc.chunk.navMesh != null)
                        {
                            DestroyImmediate(cc.chunk.navMesh);
                        }
                    }
                }
                cachedChunks.Clear();
            }
            chunkRequestLast = -1;
            cachedChunks     = null;

            DisposeTextures();

            if (worldRoot != null)
            {
                while (worldRoot.childCount > 0)
                {
                    DestroyImmediate(worldRoot.GetChild(0).gameObject);
                }
            }
            worldRoot  = null;
            cloudsRoot = null;
            chunksRoot = null;
            DestroyParticles();

            lastChunkX = int.MaxValue;

            // Clear heightmap
            if (heightMapCache != null)
            {
                heightMapCache.Clear();
            }

            // Clear render queue
            chunkQueueRoot       = 0;
            lastChunkDistanceSqr = 0;

            ClearStats();

            // reset voxel definitions state values
            if (voxelDefinitions != null)
            {
                for (int k = 0; k < voxelDefinitions.Length; k++)
                {
                    VoxelDefinition vd = voxelDefinitions [k];
                    if (vd != null)
                    {
                        vd.Reset();
                    }
                }
            }

            Resources.UnloadUnusedAssets();
            GC.Collect();

            initialized = false;
        }