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(); }
/// <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); } }