Example #1
0
        public void UpdatesVoxelsInARectangle()
        {
            int3 offset   = int3.zero;
            int3 size     = new int3(1, 2, 2);
            int  flatSize = size.x * size.y * size.z;

            NativeArray <byte>  voxels         = new NativeArray <byte>(flatSize, Allocator.Persistent);
            NativeArray <Block> blockLibrary   = new NativeArray <Block>(2, Allocator.Persistent);
            NativeArray <byte>  blocksToIgnore = new NativeArray <byte>(0, Allocator.Persistent);

            blockLibrary[0] = new Block()
            {
                editable = true
            };
            blockLibrary[1] = new Block()
            {
                editable = true
            };

            ModifyRectangle job = new ModifyRectangle()
            {
                block          = 1,
                chunkOffset    = offset,
                chunkSize      = size,
                voxels         = voxels,
                end            = new int3(0, 1, 0),
                start          = new int3(0, 0, 0),
                blockLibrary   = blockLibrary,
                blocksToIgnore = blocksToIgnore,
            };

            job.Schedule().Complete();

            Assert.AreEqual(1, voxels[0]);
            Assert.AreEqual(0, voxels[1]);
            Assert.AreEqual(1, voxels[2]);
            Assert.AreEqual(0, voxels[3]);

            voxels.Dispose();
            blockLibrary.Dispose();
            blocksToIgnore.Dispose();
        }
Example #2
0
        /// <summary>
        /// Updates a rectangle of blocks in the voxel world and flags the affected chunks as modified
        /// </summary>
        /// <param name="world">A reference to the voxel world</param>
        /// <param name="start">The starting global position in the voxel world</param>
        /// <param name="end">The end global position in the voxel world</param>
        /// <param name="block">The new block index to set</param>
        /// <param name="handle">The job handle(s) for updating the world</param>
        /// <param name="blocksToIgnore">A list of block indexes that will be ignored when updating the world</param>
        /// <param name="notifyListeners">Flags if world and chunk listeners should be notified of the change</param>
        public static void SetRectangle(World world, Vector3Int start, Vector3Int end,
                                        byte block, ref JobHandle handle, NativeArray <byte> blocksToIgnore = default,
                                        bool notifyListeners = true)
        {
            // calculate min and delta of the rectangle so the
            // orientation of the start and end positions will not matter

            Vector3Int rectDelta = new Vector3Int();
            Vector3Int rectMin   = new Vector3Int();

            rectDelta.x = Mathf.Abs(end.x - start.x) + 1;
            rectDelta.y = Mathf.Abs(end.y - start.y) + 1;
            rectDelta.z = Mathf.Abs(end.z - start.z) + 1;

            rectMin.x = Mathf.Min(start.x, end.x);
            rectMin.y = Mathf.Min(start.y, end.y);
            rectMin.z = Mathf.Min(start.z, end.z);

            // find the chunks that the rectangle intersects
            // and schedule jobs to update their voxel data

            Vector3Int chunkMin   = Vector3Int.zero;
            Vector3Int chunkDelta = Vector3Int.zero;
            Vector3Int chunkEnd   = world.chunkManager.IndexFrom(end);
            Vector3Int chunkStart = world.chunkManager.IndexFrom(start);
            Vector3Int chunkSize  = world.chunkManager.configuration.size;
            Vector3Int chunkMax   = new Vector3Int(
                world.size.x / chunkSize.x,
                world.size.y / chunkSize.y,
                world.size.z / chunkSize.z
                );

            chunkDelta.x = Mathf.Abs(chunkEnd.x - chunkStart.x) + 1;
            chunkDelta.y = Mathf.Abs(chunkEnd.y - chunkStart.y) + 1;
            chunkDelta.z = Mathf.Abs(chunkEnd.z - chunkStart.z) + 1;

            chunkMin.x = Mathf.Min(chunkStart.x, chunkEnd.x);
            chunkMin.y = Mathf.Min(chunkStart.y, chunkEnd.y);
            chunkMin.z = Mathf.Min(chunkStart.z, chunkEnd.z);

            Chunk      chunk;
            int        jobIndex          = 0;
            Vector3Int chunkIndex        = Vector3Int.zero;
            int        chunkCount        = chunkDelta.x * chunkDelta.y * chunkDelta.z;
            NativeArray <JobHandle> jobs = new NativeArray <JobHandle>(chunkCount, Allocator.Temp);

            for (int x = chunkMin.x; x < chunkMin.x + chunkDelta.x; x++)
            {
                chunkIndex.x = x;
                for (int z = chunkMin.z; z < chunkMin.z + chunkDelta.z; z++)
                {
                    chunkIndex.z = z;
                    for (int y = chunkMin.y; y < chunkMin.y + chunkDelta.y; y++)
                    {
                        chunkIndex.y = y;
                        chunk        = world.chunkManager.Get(chunkIndex);
                        if (chunk == null)
                        {
                            continue;
                        }

                        // ensure all readers are complete before updating voxel data
                        chunk.buildingMesh.Complete();
                        if (chunk.neighbors.up)
                        {
                            chunk.neighbors.up.buildingMesh.Complete();
                        }
                        if (chunk.neighbors.down)
                        {
                            chunk.neighbors.down.buildingMesh.Complete();
                        }
                        if (chunk.neighbors.north)
                        {
                            chunk.neighbors.north.buildingMesh.Complete();
                        }
                        if (chunk.neighbors.south)
                        {
                            chunk.neighbors.south.buildingMesh.Complete();
                        }
                        if (chunk.neighbors.east)
                        {
                            chunk.neighbors.east.buildingMesh.Complete();
                        }
                        if (chunk.neighbors.west)
                        {
                            chunk.neighbors.west.buildingMesh.Complete();
                        }

                        world.chunkManager.Refresh(chunkIndex);
                        // update neighboring chunks
                        //
                        // check if the rectangles minimum x is a local minimum for the chunk
                        // and the chunk is not the first chunk on the X axis
                        if (rectMin.x - (chunkIndex.x * chunkSize.x) == 0 && chunkIndex.x != 0)
                        {
                            world.chunkManager.Refresh(chunkIndex + Direction3Int.West);
                        }

                        // check if the rectangles maximum x is a local maximum for the chunk
                        // and the chunk is not the last chunk on the X axis
                        if (rectMin.x + rectDelta.x - (chunkIndex.x * chunkSize.x) == chunkSize.x && chunkIndex.x != chunkMax.x - 1)
                        {
                            world.chunkManager.Refresh(chunkIndex + Direction3Int.East);
                        }

                        // check if the rectangles minimum y is a local minimum for the chunk
                        // and the chunk is not the first chunk on the Y axis
                        if (rectMin.y - (chunkIndex.y * chunkSize.y) == 0 && chunkIndex.y != 0)
                        {
                            world.chunkManager.Refresh(chunkIndex + Direction3Int.Down);
                        }

                        // check if the rectangles maximum y is a local maximum for the chunk
                        // and the chunk is not the last chunk on the Y axis
                        if (rectMin.y + rectDelta.y - (chunkIndex.y * chunkSize.y) == chunkSize.y && chunkIndex.y != chunkMax.y - 1)
                        {
                            world.chunkManager.Refresh(chunkIndex + Direction3Int.Up);
                        }

                        // check if the rectangles minimum z is a local minimum for the chunk
                        // and the chunk is not the first chunk on the Z axis
                        if (rectMin.z - (chunkIndex.z * chunkSize.z) == 0 && chunkIndex.z != 0)
                        {
                            world.chunkManager.Refresh(chunkIndex + Direction3Int.South);
                        }

                        // check if the rectangles maximum z is a local maximum for the chunk
                        // and the chunk is not the last chunk on the Z axis
                        if (rectMin.z + rectDelta.z - (chunkIndex.z * chunkSize.z) == chunkSize.z && chunkIndex.z != chunkMax.z - 1)
                        {
                            world.chunkManager.Refresh(chunkIndex + Direction3Int.North);
                        }

                        // schedule a background job to update the chunks voxel data
                        ModifyRectangle job = new ModifyRectangle();
                        job.blocksToIgnore = blocksToIgnore == default ? world.blockManager.emptyIgnoreList : blocksToIgnore;
                        job.blockLibrary   = world.chunkManager.meshGenerator.blockLibrary;
                        job.chunkOffset    = new int3(chunk.offset.x, chunk.offset.y, chunk.offset.z);
                        job.chunkSize      = new int3(chunkSize.x, chunkSize.y, chunkSize.z);
                        job.start          = new int3(start.x, start.y, start.z);
                        job.end            = new int3(end.x, end.y, end.z);
                        job.voxels         = chunk.voxels;
                        job.block          = block;

                        JobHandle modifyChunk = job.Schedule();
                        jobs[jobIndex] = modifyChunk;
                        jobIndex++;

                        // notify any listeners about the change
                        if (notifyListeners && chunk.modified != null)
                        {
                            chunk.modified.Invoke(modifyChunk);
                        }
                    }
                }
            }

            // deprecated but still required for agent navigation
            EditVoxelJob navJob = new EditVoxelJob()
            {
                blocksToIgnore = blocksToIgnore == default ? world.blockManager.emptyIgnoreList : blocksToIgnore,
                blockLibrary   = world.chunkManager.meshGenerator.blockLibrary,
                size           = new int3(world.size.x, world.size.y, world.size.z),
                start          = new int3(start.x, start.y, start.z),
                end            = new int3(end.x, end.y, end.z),
                voxels         = world.data.voxels,
                block          = block
            };
            JobHandle navHandle = navJob.Schedule();

            // combine job dependencies
            handle = JobHandle.CombineDependencies(jobs);
            handle = JobHandle.CombineDependencies(handle, navHandle);
            jobs.Dispose();

            // update chunk manager and any notify listeners
            world.chunkManager.refreshDependsOn = handle;
            if (notifyListeners && world.modified != null)
            {
                world.modified.Invoke(handle);
            }
        }