Ejemplo n.º 1
0
        public void GenerateVein(OreVein vein, ChunkData chunks)
        {
            Voxel   vox           = new Voxel();
            Vector3 curr          = vein.Start;
            Vector3 directionBias = MathFunctions.RandVector3Box(-1, 1, -0.1f, 0.1f, -1, 1);

            for (float t = 0; t < vein.Length; t++)
            {
                if (curr.Y > vein.Type.MaxSpawnHeight ||
                    curr.Y < vein.Type.MinSpawnHeight)
                {
                    continue;
                }
                Vector3 p = new Vector3(curr.X, curr.Y, curr.Z);
                if (!chunks.GetVoxel(p, ref vox))
                {
                    continue;
                }

                if (vox.IsEmpty)
                {
                    continue;
                }

                if (!MathFunctions.RandEvent(vein.Type.SpawnProbability))
                {
                    continue;
                }

                if (!vein.Type.SpawnInSoil && vox.Type.IsSoil)
                {
                    continue;
                }

                vox.Type = vein.Type;
                Vector3 step = directionBias + MathFunctions.RandVector3Box(-1, 1, -1, 1, -1, 1) * 0.25f;
                step.Normalize();
                curr += step;
            }
        }
Ejemplo n.º 2
0
        public void GenerateVein(OreVein vein, ChunkData chunks)
        {
            Vector3 curr          = vein.Start;
            Vector3 directionBias = MathFunctions.RandVector3Box(-1, 1, -0.1f, 0.1f, -1, 1);

            for (float t = 0; t < vein.Length; t++)
            {
                if (curr.Y > vein.Type.MaxSpawnHeight ||
                    curr.Y < vein.Type.MinSpawnHeight ||
                    curr.Y <= 1)
                {
                    continue;
                }
                Vector3 p = new Vector3(curr.X, curr.Y, curr.Z);

                var vox = new VoxelHandle(chunks, GlobalVoxelCoordinate.FromVector3(p));

                if (!vox.IsValid || vox.IsEmpty)
                {
                    continue;
                }

                if (!MathFunctions.RandEvent(vein.Type.SpawnProbability))
                {
                    continue;
                }

                if (!vein.Type.SpawnOnSurface && (vox.Type.IsSurface || vox.Type.IsSoil))
                {
                    continue;
                }

                vox.RawSetType(vein.Type);
                Vector3 step = directionBias + MathFunctions.RandVector3Box(-1, 1, -1, 1, -1, 1) * 0.25f;
                step.Normalize();
                curr += step;
            }
        }
Ejemplo n.º 3
0
        private static BoxTransition ComputeTransitions(
            ChunkData Data,
            VoxelHandle V,
            VoxelType Type)
        {
            if (Type.Transitions == VoxelType.TransitionType.Horizontal)
            {
                var value = ComputeTransitionValueOnPlane(
                    VoxelHelpers.EnumerateManhattanNeighbors2D(V.Coordinate)
                    .Select(c => new VoxelHandle(Data, c)), Type);

                return(new BoxTransition()
                {
                    Top = (TransitionTexture)value
                });
            }
            else
            {
                var transitionFrontBack = ComputeTransitionValueOnPlane(
                    VoxelHelpers.EnumerateManhattanNeighbors2D(V.Coordinate, ChunkManager.SliceMode.Z)
                    .Select(c => new VoxelHandle(Data, c)),
                    Type);

                var transitionLeftRight = ComputeTransitionValueOnPlane(
                    VoxelHelpers.EnumerateManhattanNeighbors2D(V.Coordinate, ChunkManager.SliceMode.X)
                    .Select(c => new VoxelHandle(Data, c)),
                    Type);

                return(new BoxTransition()
                {
                    Front = (TransitionTexture)transitionFrontBack,
                    Back = (TransitionTexture)transitionFrontBack,
                    Left = (TransitionTexture)transitionLeftRight,
                    Right = (TransitionTexture)transitionLeftRight
                });
            }
        }
Ejemplo n.º 4
0
        public void CreateGraphics(ref string message, ChunkData chunkData)
        {
            message = "Creating Graphics";
            List <VoxelChunk> toRebuild = new List <VoxelChunk>();

            while (RebuildList.Count > 0)
            {
                VoxelChunk chunk = null;
                if (!RebuildList.TryDequeue(out chunk))
                {
                    break;
                }

                if (chunk == null)
                {
                    continue;
                }

                toRebuild.Add(chunk);
            }

            message = "Creating Graphics : Updating Ramps";
            foreach (VoxelChunk chunk in toRebuild.Where(chunk => GameSettings.Default.CalculateRamps))
            {
                chunk.UpdateRamps();
            }

            message = "Creating Graphics : Calculating lighting ";
            int j = 0;

            foreach (VoxelChunk chunk in toRebuild)
            {
                j++;
                message = "Creating Graphics : Calculating lighting " + j + "/" + toRebuild.Count;
                if (chunk.ShouldRecalculateLighting)
                {
                    chunk.CalculateGlobalLight();
                    chunk.ShouldRecalculateLighting = false;
                }
            }

            j = 0;
            foreach (VoxelChunk chunk in toRebuild)
            {
                j++;
                message = "Creating Graphics : Calculating vertex light " + j + "/" + toRebuild.Count;
                chunk.CalculateVertexLighting();
            }

            message = "Creating Graphics: Building Vertices";
            j       = 0;
            foreach (VoxelChunk chunk in toRebuild)
            {
                j++;
                message = "Creating Graphics : Building Vertices " + j + "/" + toRebuild.Count;

                if (!chunk.ShouldRebuild)
                {
                    continue;
                }

                chunk.Rebuild(Graphics);
                chunk.ShouldRebuild        = false;
                chunk.RebuildPending       = false;
                chunk.RebuildLiquidPending = false;
            }

            chunkData.RecomputeNeighbors();


            message = "Cleaning Up.";
        }
Ejemplo n.º 5
0
        public void Update(DwarfTime gameTime, Camera camera, GraphicsDevice g)
        {
            UpdateRenderList(camera);

            if (waterUpdateTimer.Update(gameTime))
            {
                WaterUpdateEvent.Set();
            }

            UpdateRebuildList();

            generateChunksTimer.Update(gameTime);
            if (generateChunksTimer.HasTriggered)
            {
                if (ToGenerate.Count > 0)
                {
                    NeedsGenerationEvent.Set();
                    ChunkData.RecomputeNeighbors();
                }

                foreach (VoxelChunk chunk in GeneratedChunks)
                {
                    if (!ChunkData.ChunkMap.ContainsKey(chunk.ID))
                    {
                        ChunkData.AddChunk(chunk);
                        ChunkGen.GenerateVegetation(chunk, Components, Content, Graphics);
                        ChunkGen.GenerateFauna(chunk, Components, Content, Graphics, PlayState.ComponentManager.Factions);
                        List <VoxelChunk> adjacents = ChunkData.GetAdjacentChunks(chunk);
                        foreach (VoxelChunk c in adjacents)
                        {
                            c.ShouldRecalculateLighting = true;
                            c.ShouldRebuild             = true;
                        }
                        RecalculateBounds();
                    }
                }

                while (GeneratedChunks.Count > 0)
                {
                    VoxelChunk gen = null;
                    if (!GeneratedChunks.TryDequeue(out gen))
                    {
                        break;
                    }
                }
            }


            visibilityChunksTimer.Update(gameTime);
            if (visibilityChunksTimer.HasTriggered)
            {
                visibleSet.Clear();
                GetChunksIntersecting(camera.GetFrustrum(), visibleSet);
                //RemoveDistantBlocks(camera);
            }


            foreach (VoxelChunk chunk in ChunkData.ChunkMap.Values)
            {
                chunk.Update(gameTime);
            }

            Water.Splash(gameTime);
            Water.HandleTransfers(gameTime);

            HashSet <VoxelChunk> affectedChunks = new HashSet <VoxelChunk>();

            foreach (Voxel voxel in KilledVoxels)
            {
                affectedChunks.Add(voxel.Chunk);
                voxel.Chunk.NotifyDestroyed(new Point3(voxel.GridPosition));
                if (!voxel.IsInterior)
                {
                    foreach (KeyValuePair <Point3, VoxelChunk> n in voxel.Chunk.Neighbors)
                    {
                        affectedChunks.Add(n.Value);
                    }
                }
            }

            if (GameSettings.Default.FogofWar)
            {
                ChunkData.Reveal(KilledVoxels);
            }

            lock (RebuildList)
            {
                foreach (VoxelChunk affected in affectedChunks)
                {
                    affected.NotifyTotalRebuild(false);
                }
            }
            KilledVoxels.Clear();
        }
Ejemplo n.º 6
0
        public void GenerateInitialChunks(Point3 origin, ref string message)
        {
            float origBuildRadius = GenerateDistance;

            GenerateDistance = origBuildRadius * 2.0f;

            int i     = 0;
            int iters = WorldSize.X * WorldSize.Y * WorldSize.Z;

            for (int dx = origin.X - WorldSize.X / 2 + 1; dx < origin.X + WorldSize.X / 2; dx++)
            {
                for (int dy = origin.Y - WorldSize.Y / 2; dy <= origin.Y + WorldSize.Y / 2; dy++)
                {
                    for (int dz = origin.Z - WorldSize.Z / 2 + 1; dz < origin.Z + WorldSize.Z / 2; dz++)
                    {
                        message = "Generating : " + (i + 1) + "/" + iters;
                        i++;

                        Point3 box = new Point3(dx, dy, dz);

                        if (!ChunkData.ChunkMap.ContainsKey(box))
                        {
                            Vector3    worldPos = new Vector3(box.X * ChunkData.ChunkSizeX, box.Y * ChunkData.ChunkSizeY, box.Z * ChunkData.ChunkSizeZ);
                            VoxelChunk chunk    = ChunkGen.GenerateChunk(worldPos, (int)ChunkData.ChunkSizeX, (int)ChunkData.ChunkSizeY, (int)ChunkData.ChunkSizeZ, Components, Content, Graphics);
                            chunk.ShouldRebuild             = true;
                            chunk.ShouldRecalculateLighting = true;
                            chunk.IsVisible = true;
                            chunk.ResetSunlight(0);
                            GeneratedChunks.Enqueue(chunk);
                            foreach (VoxelChunk chunk2 in GeneratedChunks)
                            {
                                if (!ChunkData.ChunkMap.ContainsKey(chunk2.ID))
                                {
                                    ChunkData.AddChunk(chunk2);
                                    RecalculateBounds();
                                }
                            }
                        }
                    }
                }
            }
            RecalculateBounds();
            message = "Generating Ores...";

            GenerateOres();

            message = "Fog of war...";
            ChunkData.Reveal(GeneratedChunks.First().MakeVoxel(0, (int)ChunkData.ChunkSizeY - 1, 0));

            UpdateRebuildList();
            GenerateDistance = origBuildRadius;

            while (GeneratedChunks.Count > 0)
            {
                VoxelChunk gen = null;
                if (!GeneratedChunks.TryDequeue(out gen))
                {
                    break;
                }
            }

            ChunkData.ChunkManager.CreateGraphics(ref message, ChunkData);
        }
Ejemplo n.º 7
0
        public ChunkManager(ContentManager content,
                            uint chunkSizeX, uint chunkSizeY, uint chunkSizeZ,
                            Camera camera, GraphicsDevice graphics, Texture2D tilemap,
                            Texture2D illumMap, Texture2D sunMap, Texture2D ambientMap,
                            Texture2D torchMap, ChunkGenerator chunkGen, int maxChunksX, int maxChunksY, int maxChunksZ)
        {
            KilledVoxels = new List <Voxel>();
            ExitThreads  = false;
            drawDistSq   = DrawDistance * DrawDistance;
            Content      = content;

            chunkData          = new ChunkData(chunkSizeX, chunkSizeY, chunkSizeZ, 1.0f / chunkSizeX, 1.0f / chunkSizeY, 1.0f / chunkSizeZ, tilemap, illumMap, this);
            ChunkData.ChunkMap = new ConcurrentDictionary <Point3, VoxelChunk>();
            RenderList         = new ConcurrentQueue <VoxelChunk>();
            RebuildList        = new ConcurrentQueue <VoxelChunk>();
            RebuildLiquidsList = new ConcurrentQueue <VoxelChunk>();
            ChunkGen           = chunkGen;


            GeneratedChunks = new ConcurrentQueue <VoxelChunk>();
            GeneratorThread = new Thread(GenerateThread);

            RebuildThread       = new Thread(RebuildVoxelsThread);
            RebuildLiquidThread = new Thread(RebuildLiquidsThread);

            WaterThread = new Thread(UpdateWaterThread);

            ToGenerate = new List <Point3>();
            Graphics   = graphics;

            chunkGen.Manager = this;

            ChunkData.MaxViewingLevel = chunkSizeY;

            GameSettings.Default.ChunkGenerateTime = 0.5f;
            generateChunksTimer = new Timer(GameSettings.Default.ChunkGenerateTime, false, Timer.TimerMode.Real);
            GameSettings.Default.ChunkRebuildTime = 0.1f;
            Timer rebuildChunksTimer = new Timer(GameSettings.Default.ChunkRebuildTime, false, Timer.TimerMode.Real);

            GameSettings.Default.VisibilityUpdateTime = 0.05f;
            visibilityChunksTimer              = new Timer(GameSettings.Default.VisibilityUpdateTime, false, Timer.TimerMode.Real);
            generateChunksTimer.HasTriggered   = true;
            rebuildChunksTimer.HasTriggered    = true;
            visibilityChunksTimer.HasTriggered = true;
            this.camera = camera;

            Water = new WaterManager(this);

            ChunkData.SunMap     = sunMap;
            ChunkData.AmbientMap = ambientMap;
            ChunkData.TorchMap   = torchMap;

            DynamicLights = new List <DynamicLight>();

            ChunkData.Slice = SliceMode.Y;
            PauseThreads    = false;

            WorldSize = new Point3(maxChunksX, maxChunksY, maxChunksZ);

            Vector3 maxBounds = new Vector3(maxChunksX * chunkSizeX * 0.5f, maxChunksY * chunkSizeY * 0.5f, maxChunksZ * chunkSizeZ * 0.5f);
            Vector3 minBounds = -maxBounds;

            Bounds = new BoundingBox(minBounds, maxBounds);
        }
Ejemplo n.º 8
0
        public ChunkManager(ContentManager content, 
            uint chunkSizeX, uint chunkSizeY, uint chunkSizeZ,
            Camera camera, GraphicsDevice graphics, Texture2D tilemap,
            Texture2D illumMap, Texture2D sunMap, Texture2D ambientMap,
            Texture2D torchMap, ChunkGenerator chunkGen, int maxChunksX, int maxChunksY, int maxChunksZ)
        {
            KilledVoxels = new List<Voxel>();
            ExitThreads = false;
            drawDistSq = DrawDistance * DrawDistance;
            Content = content;

            chunkData = new ChunkData(chunkSizeX, chunkSizeY, chunkSizeZ, 1.0f / chunkSizeX, 1.0f / chunkSizeY, 1.0f / chunkSizeZ, tilemap, illumMap, this);
            ChunkData.ChunkMap = new ConcurrentDictionary<Point3, VoxelChunk>();
            RenderList = new ConcurrentQueue<VoxelChunk>();
            RebuildList = new ConcurrentQueue<VoxelChunk>();
            RebuildLiquidsList = new ConcurrentQueue<VoxelChunk>();
            ChunkGen = chunkGen;

            GeneratedChunks = new ConcurrentQueue<VoxelChunk>();
            GeneratorThread = new Thread(GenerateThread);

            RebuildThread = new Thread(RebuildVoxelsThread);
            RebuildLiquidThread = new Thread(RebuildLiquidsThread);

            WaterThread = new Thread(UpdateWaterThread);

            ToGenerate = new List<Point3>();
            Graphics = graphics;

            chunkGen.Manager = this;

            ChunkData.MaxViewingLevel = chunkSizeY;

            GameSettings.Default.ChunkGenerateTime = 0.5f;
            generateChunksTimer = new Timer(GameSettings.Default.ChunkGenerateTime, false, Timer.TimerMode.Real);
            GameSettings.Default.ChunkRebuildTime = 0.1f;
            Timer rebuildChunksTimer = new Timer(GameSettings.Default.ChunkRebuildTime, false, Timer.TimerMode.Real);
            GameSettings.Default.VisibilityUpdateTime = 0.05f;
            visibilityChunksTimer = new Timer(GameSettings.Default.VisibilityUpdateTime, false, Timer.TimerMode.Real);
            generateChunksTimer.HasTriggered = true;
            rebuildChunksTimer.HasTriggered = true;
            visibilityChunksTimer.HasTriggered = true;
            this.camera = camera;

            Water = new WaterManager(this);

            ChunkData.SunMap = sunMap;
            ChunkData.AmbientMap = ambientMap;
            ChunkData.TorchMap = torchMap;

            DynamicLights = new List<DynamicLight>();

            ChunkData.Slice = SliceMode.Y;
            PauseThreads = false;

            WorldSize = new Point3(maxChunksX, maxChunksY, maxChunksZ);

            Vector3 maxBounds = new Vector3(maxChunksX * chunkSizeX * 0.5f, maxChunksY * chunkSizeY * 0.5f, maxChunksZ * chunkSizeZ * 0.5f);
            Vector3 minBounds = -maxBounds;
            Bounds = new BoundingBox(minBounds, maxBounds);
        }
Ejemplo n.º 9
0
        public void CreateGraphics(ref string message, ChunkData chunkData)
        {
            message = "Creating Graphics";
            List<VoxelChunk> toRebuild = new List<VoxelChunk>();

            while(RebuildList.Count > 0)
            {
                VoxelChunk chunk = null;
                if(!RebuildList.TryDequeue(out chunk))
                {
                    break;
                }

                if(chunk == null)
                {
                    continue;
                }

                toRebuild.Add(chunk);
            }

            message = "Creating Graphics : Updating Max Viewing Level";
            foreach(VoxelChunk chunk in toRebuild)
            {
                chunk.UpdateMaxViewingLevel();
            }

            message = "Creating Graphics : Updating Ramps";
            foreach(VoxelChunk chunk in toRebuild.Where(chunk => GameSettings.Default.CalculateRamps))
            {
                chunk.UpdateRamps();
            }

            message = "Creating Graphics : Calculating lighting ";
            int j = 0;
            foreach(VoxelChunk chunk in toRebuild)
            {
                j++;
                message = "Creating Graphics : Calculating lighting " + j + "/" + toRebuild.Count;
                if(chunk.ShouldRecalculateLighting)
                {
                    chunk.CalculateGlobalLight();
                    chunk.ShouldRecalculateLighting = false;
                }
            }

            j = 0;
            foreach(VoxelChunk chunk in toRebuild)
            {
                j++;
                message = "Creating Graphics : Calculating vertex light " + j + "/" + toRebuild.Count;
                chunk.CalculateVertexLighting();
            }

            message = "Creating Graphics: Building Vertices";
            j = 0;
            foreach(VoxelChunk chunk in toRebuild)
            {
                j++;
                message = "Creating Graphics : Building Vertices " + j + "/" + toRebuild.Count;

                if(!chunk.ShouldRebuild)
                {
                    continue;
                }

                chunk.Rebuild(Graphics);
                chunk.ShouldRebuild = false;
                chunk.RebuildPending = false;
                chunk.RebuildLiquidPending = false;
            }

            chunkData.RecomputeNeighbors();

            message = "Cleaning Up.";
        }
Ejemplo n.º 10
0
        public void GenerateInitialChunks(Point3 origin, Action <String> SetLoadingMessage)
        {
            float origBuildRadius = GenerateDistance;

            GenerateDistance = origBuildRadius * 2.0f;

            int i     = 0;
            int iters = WorldSize.X * WorldSize.Y * WorldSize.Z;

            for (int dx = origin.X - WorldSize.X / 2 + 1; dx < origin.X + WorldSize.X / 2; dx++)
            {
                for (int dy = origin.Y - WorldSize.Y / 2; dy <= origin.Y + WorldSize.Y / 2; dy++)
                {
                    for (int dz = origin.Z - WorldSize.Z / 2 + 1; dz < origin.Z + WorldSize.Z / 2; dz++)
                    {
                        SetLoadingMessage("Generating : " + (i + 1) + "/" + iters);
                        i++;

                        Point3 box = new Point3(dx, dy, dz);

                        if (!ChunkData.ChunkMap.ContainsKey(box))
                        {
                            Vector3    worldPos = new Vector3(box.X * ChunkData.ChunkSizeX, box.Y * ChunkData.ChunkSizeY, box.Z * ChunkData.ChunkSizeZ);
                            VoxelChunk chunk    = ChunkGen.GenerateChunk(worldPos, (int)ChunkData.ChunkSizeX, (int)ChunkData.ChunkSizeY, (int)ChunkData.ChunkSizeZ, Components, Content, Graphics);
                            chunk.ShouldRebuild             = true;
                            chunk.ShouldRecalculateLighting = true;
                            chunk.IsVisible = true;
                            chunk.ResetSunlight(0);
                            GeneratedChunks.Enqueue(chunk);
                            foreach (VoxelChunk chunk2 in GeneratedChunks)
                            {
                                if (!ChunkData.ChunkMap.ContainsKey(chunk2.ID))
                                {
                                    ChunkData.AddChunk(chunk2);
                                    RecalculateBounds();
                                }
                            }
                        }
                    }
                }
            }
            RecalculateBounds();
            chunkData.RecomputeNeighbors();
            SetLoadingMessage("Generating Ores...");

            GenerateOres();

            SetLoadingMessage("Fog of war...");
            // We are going to force fog of war to be on for the first reveal then reset it back to it's previous setting after.
            // This is a pseudo hack to stop worlds created with Fog of War off then looking awful if it is turned back on.
            bool fogOfWar = GameSettings.Default.FogofWar;

            GameSettings.Default.FogofWar = true;
            ChunkData.Reveal(GeneratedChunks.First().MakeVoxel(0, (int)ChunkData.ChunkSizeY - 1, 0));
            GameSettings.Default.FogofWar = fogOfWar;

            UpdateRebuildList();
            GenerateDistance = origBuildRadius;

            while (GeneratedChunks.Count > 0)
            {
                VoxelChunk gen = null;
                if (!GeneratedChunks.TryDequeue(out gen))
                {
                    break;
                }
            }

            ChunkData.ChunkManager.CreateGraphics(SetLoadingMessage, ChunkData);
        }
Ejemplo n.º 11
0
        public void CreateGraphics(Action <String> SetLoadingMessage, ChunkData chunkData)
        {
            SetLoadingMessage("Creating Graphics");
            List <VoxelChunk> toRebuild = new List <VoxelChunk>();

            while (RebuildList.Count > 0)
            {
                VoxelChunk chunk = null;
                if (!RebuildList.TryDequeue(out chunk))
                {
                    break;
                }

                if (chunk == null)
                {
                    continue;
                }

                toRebuild.Add(chunk);
            }

            SetLoadingMessage("Updating Ramps");
            foreach (VoxelChunk chunk in toRebuild.Where(chunk => GameSettings.Default.CalculateRamps))
            {
                chunk.UpdateRamps();
            }

            SetLoadingMessage("Calculating lighting ");
            int j = 0;

            foreach (VoxelChunk chunk in toRebuild)
            {
                j++;
                SetLoadingMessage("Calculating lighting " + j + "/" + toRebuild.Count);
                if (chunk.ShouldRecalculateLighting)
                {
                    chunk.CalculateGlobalLight();
                    chunk.ShouldRecalculateLighting = false;
                }
            }

            j = 0;
            foreach (VoxelChunk chunk in toRebuild)
            {
                j++;
                SetLoadingMessage("Calculating vertex light " + j + "/" + toRebuild.Count);
                chunk.CalculateVertexLighting();
            }

            SetLoadingMessage("Building Vertices");
            j = 0;
            foreach (VoxelChunk chunk in toRebuild)
            {
                j++;
                SetLoadingMessage("Building Vertices " + j + "/" + toRebuild.Count);

                if (!chunk.ShouldRebuild)
                {
                    continue;
                }

                chunk.Rebuild(Graphics);
                chunk.ShouldRebuild        = false;
                chunk.RebuildPending       = false;
                chunk.RebuildLiquidPending = false;
            }

            SetLoadingMessage("Cleaning Up.");
        }
Ejemplo n.º 12
0
        public static void RadiusReveal(
            ChunkData Data,
            VoxelHandle Voxel,
            int Radius)
        {
            var queue    = new Queue <VisitedVoxel>(128);
            var visited  = new HashSet <ulong>();
            var explored = new List <VoxelHandle>();

            if (Voxel.IsValid)
            {
                queue.Enqueue(new VisitedVoxel {
                    Depth = 1, Voxel = Voxel
                });

                if (!Voxel.IsExplored)
                {
                    Voxel.IsExplored = true;
                    explored.Add(Voxel);
                }
            }

            while (queue.Count > 0)
            {
                var v = queue.Dequeue();
                if (v.Depth >= Radius)
                {
                    continue;
                }

                foreach (var neighborCoordinate in VoxelHelpers.EnumerateManhattanNeighbors(v.Voxel.Coordinate))
                {
                    var neighbor = new VoxelHandle(Data, neighborCoordinate);
                    if (!neighbor.IsValid)
                    {
                        continue;
                    }

                    var longHash = neighborCoordinate.GetLongHash();
                    if (visited.Contains(longHash))
                    {
                        continue;
                    }
                    visited.Add(longHash);

                    if (neighbor.IsExplored == false)
                    {
                        explored.Add(neighbor);
                        neighbor.IsExplored = true;
                    }

                    if (neighbor.IsEmpty)
                    {
                        queue.Enqueue(new VisitedVoxel
                        {
                            Depth = v.Depth + 1,
                            Voxel = neighbor,
                        });
                    }
                }
            }

            foreach (var voxel in explored)
            {
                voxel.Chunk.NotifyExplored(voxel.Coordinate.GetLocalVoxelCoordinate());
            }
        }