From b5b29a2962a90e3b4582ddf8ad388eb3b8346d7e Mon Sep 17 00:00:00 2001 From: haykam821 <24855774+haykam821@users.noreply.github.com> Date: Sat, 24 Jun 2023 02:36:24 -0400 Subject: [PATCH] Add the trim command (#2278) * Add the trim command Fixes #1629 * Fix issues from PR review --------- Co-authored-by: Madeline Miller --- .../worldedit/command/SelectionCommands.java | 139 ++++++++++++++++++ .../src/main/resources/lang/strings.json | 2 + 2 files changed, 141 insertions(+) diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java index 41ca7b17d..bd0429e96 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java @@ -41,12 +41,16 @@ import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.function.block.BlockDistributionCounter; import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.function.mask.MaskIntersection; +import com.sk89q.worldedit.function.mask.Masks; +import com.sk89q.worldedit.function.mask.RegionMask; import com.sk89q.worldedit.function.operation.Operations; import com.sk89q.worldedit.function.visitor.RegionVisitor; import com.sk89q.worldedit.internal.annotation.Chunk3d; import com.sk89q.worldedit.internal.annotation.Direction; import com.sk89q.worldedit.internal.annotation.MultiDirection; import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.regions.RegionOperationException; import com.sk89q.worldedit.regions.RegionSelector; @@ -504,6 +508,141 @@ private BlockVector3[] getChangesForEachDir(int amount, boolean onlyHorizontal, return changes.build().map(v -> v.multiply(amount)).toArray(BlockVector3[]::new); } + @Command( + name = "/trim", + desc = "Minimize the selection to encompass matching blocks" + ) + @Logging(REGION) + @CommandPermissions("worldedit.selection.trim") + public void trim(Actor actor, World world, LocalSession session, + @Arg(desc = "Mask of blocks to keep within the selection", def = "#existing") + Mask mask) throws WorldEditException { + // Avoid checking blocks outside the original region but within the cuboid region + Region originalRegion = session.getSelection(world); + if (!(originalRegion instanceof CuboidRegion)) { + mask = new MaskIntersection(new RegionMask(originalRegion), mask); + } + // Memoize the mask to reduce duplicated lookups + mask = Masks.memoize(mask); + + // Result region will be cuboid + CuboidRegion region = originalRegion.getBoundingBox(); + + BlockVector3 min = region.getMinimumPoint(); + BlockVector3 max = region.getMaximumPoint(); + + int minY = 0; + boolean found = false; + + outer: for (int y = min.getBlockY(); y <= max.getBlockY(); y++) { + for (int x = min.getBlockX(); x <= max.getBlockX(); x++) { + for (int z = min.getBlockZ(); z <= max.getBlockZ(); z++) { + BlockVector3 vec = BlockVector3.at(x, y, z); + + if (mask.test(vec)) { + found = true; + minY = y; + + break outer; + } + } + } + } + + // If anything was found in the first pass, then the remaining variables are guaranteed to be set + if (!found) { + throw new StopExecutionException(TranslatableComponent.of( + "worldedit.trim.no-blocks")); + } + + int maxY = minY; + + outer: for (int y = max.getBlockY(); y > minY; y--) { + for (int x = min.getBlockX(); x <= max.getBlockX(); x++) { + for (int z = min.getBlockZ(); z <= max.getBlockZ(); z++) { + BlockVector3 vec = BlockVector3.at(x, y, z); + + if (mask.test(vec)) { + maxY = y; + break outer; + } + } + } + } + + int minX = 0; + + outer: for (int x = min.getBlockX(); x <= max.getBlockX(); x++) { + for (int z = min.getBlockZ(); z <= max.getBlockZ(); z++) { + for (int y = minY; y <= maxY; y++) { + BlockVector3 vec = BlockVector3.at(x, y, z); + + if (mask.test(vec)) { + minX = x; + break outer; + } + } + } + } + + int maxX = minX; + + outer: for (int x = max.getBlockX(); x > minX; x--) { + for (int z = min.getBlockZ(); z <= max.getBlockZ(); z++) { + for (int y = minY; y <= maxY; y++) { + BlockVector3 vec = BlockVector3.at(x, y, z); + + if (mask.test(vec)) { + maxX = x; + break outer; + } + } + } + } + + int minZ = 0; + + outer: for (int z = min.getBlockZ(); z <= max.getBlockZ(); z++) { + for (int x = minX; x <= maxX; x++) { + for (int y = minY; y <= maxY; y++) { + BlockVector3 vec = BlockVector3.at(x, y, z); + + if (mask.test(vec)) { + minZ = z; + break outer; + } + } + } + } + + int maxZ = minZ; + + outer: for (int z = max.getBlockZ(); z > minZ; z--) { + for (int x = minX; x <= maxX; x++) { + for (int y = minY; y <= maxY; y++) { + BlockVector3 vec = BlockVector3.at(x, y, z); + + if (mask.test(vec)) { + maxZ = z; + break outer; + } + } + } + } + + final CuboidRegionSelector selector; + if (session.getRegionSelector(world) instanceof ExtendingCuboidRegionSelector) { + selector = new ExtendingCuboidRegionSelector(world, BlockVector3.at(minX, minY, minZ), BlockVector3.at(maxX, maxY, maxZ)); + } else { + selector = new CuboidRegionSelector(world, BlockVector3.at(minX, minY, minZ), BlockVector3.at(maxX, maxY, maxZ)); + } + session.setRegionSelector(world, selector); + + session.getRegionSelector(world).learnChanges(); + session.getRegionSelector(world).explainRegionAdjust(actor, session); + actor.printInfo(TranslatableComponent.of("worldedit.trim.trim")); + } + @Command( name = "/size", desc = "Get information about the selection" diff --git a/worldedit-core/src/main/resources/lang/strings.json b/worldedit-core/src/main/resources/lang/strings.json index e9317d0b8..ff16b280a 100644 --- a/worldedit-core/src/main/resources/lang/strings.json +++ b/worldedit-core/src/main/resources/lang/strings.json @@ -150,6 +150,8 @@ "worldedit.shift.shifted": "Region shifted.", "worldedit.outset.outset": "Region outset.", "worldedit.inset.inset": "Region inset.", + "worldedit.trim.trim": "Region trimmed.", + "worldedit.trim.no-blocks": "No blocks matched the trim mask.", "worldedit.size.offset": "Offset: {0}", "worldedit.size.type": "Type: {0}", "worldedit.size.size": "Size: {0}",