Skip to content

Commit f49ff47

Browse files
committed
Improve //hollow command
- Add -p flag to iterate from placement position instead of the selection boundaries - Add -g flag to consider geometry in visibility calculations - By default, //hollow will no longer open up faces that are touching the selection bounding box. The old behaviour can be access with the -o flag.
1 parent 1434aca commit f49ff47

3 files changed

Lines changed: 215 additions & 34 deletions

File tree

verification/src/changes/accepted-core-public-api-changes.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@
2222
"changes": [
2323
"METHOD_REMOVED"
2424
]
25+
},
26+
{
27+
"type": "com.sk89q.worldedit.EditSession",
28+
"member": "Method com.sk89q.worldedit.EditSession.makeCuboidFaces(com.sk89q.worldedit.regions.Region,int,com.sk89q.worldedit.function.pattern.Pattern)",
29+
"changes": [
30+
"METHOD_NOW_FINAL"
31+
]
2532
}
2633
],
2734
"Why on earth is this even an issue?": [

worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java

Lines changed: 181 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import com.google.common.collect.ImmutableList;
2323
import com.google.errorprone.annotations.InlineMe;
24+
import com.sk89q.worldedit.blocks.ShapeType;
2425
import com.sk89q.worldedit.entity.BaseEntity;
2526
import com.sk89q.worldedit.entity.Entity;
2627
import com.sk89q.worldedit.event.extent.EditSessionEvent;
@@ -129,12 +130,14 @@
129130
import com.sk89q.worldedit.world.block.BlockType;
130131
import com.sk89q.worldedit.world.block.BlockTypes;
131132
import com.sk89q.worldedit.world.generation.TreeType;
133+
import com.sk89q.worldedit.world.registry.BlockMaterial;
132134
import com.sk89q.worldedit.world.registry.LegacyMapper;
133135
import org.apache.logging.log4j.Logger;
134136

135137
import java.util.ArrayDeque;
136138
import java.util.ArrayList;
137139
import java.util.Arrays;
140+
import java.util.Collection;
138141
import java.util.Collections;
139142
import java.util.HashMap;
140143
import java.util.HashSet;
@@ -2657,6 +2660,43 @@ public int deformRegion(final Region region, final Transform targetTransform, fi
26572660
return affected;
26582661
}
26592662

2663+
private static Direction[] getBlockedDirections(Extent extent, BlockVector3 position) {
2664+
BlockState blockState = extent.getBlock(position);
2665+
BlockType blockType = blockState.getBlockType();
2666+
BlockMaterial material = blockState.getMaterial();
2667+
2668+
if (material.isAir()) {
2669+
return NO_DIRECTIONS;
2670+
}
2671+
if (material.isLiquid()) {
2672+
return NO_DIRECTIONS;
2673+
}
2674+
Direction[] blockedDirections = BLOCKED_DIRECTIONS_OVERRIDE.get(blockType);
2675+
if (blockedDirections != null) {
2676+
return blockedDirections;
2677+
}
2678+
if (material.isFullCube(ShapeType.VISUAL_SHAPE)) {
2679+
return CARDINAL_UPRIGHT_DIRECTIONS;
2680+
}
2681+
2682+
Direction[] result = new Direction[CARDINAL_UPRIGHT_DIRECTIONS.length];
2683+
int count = 0;
2684+
2685+
for (Direction direction : CARDINAL_UPRIGHT_DIRECTIONS) {
2686+
if (material.isFullFace(ShapeType.VISUAL_SHAPE, direction)) {
2687+
result[count++] = direction;
2688+
}
2689+
}
2690+
2691+
// Short-cut for blocks that are blocked in all directions but for some reason not isFullCube
2692+
// This enables == comparison later
2693+
if (count == 6) {
2694+
return CARDINAL_UPRIGHT_DIRECTIONS;
2695+
}
2696+
2697+
return Arrays.copyOf(result, count);
2698+
}
2699+
26602700
/**
26612701
* Hollows out the region (Semi-well-defined for non-cuboid selections).
26622702
*
@@ -2666,59 +2706,107 @@ public int deformRegion(final Region region, final Transform targetTransform, fi
26662706
*
26672707
* @return number of blocks affected
26682708
* @throws MaxChangedBlocksException thrown if too many blocks are changed
2709+
* @deprecated Use {@link EditSession#hollowOutRegion(Region, int, Pattern, boolean, Collection, boolean)} instead.
26692710
*/
2670-
public int hollowOutRegion(Region region, int thickness, Pattern pattern) throws MaxChangedBlocksException {
2711+
@InlineMe(replacement = "this.hollowOutRegion(region, thickness, pattern, true, null, false)")
2712+
@Deprecated
2713+
public final int hollowOutRegion(Region region, int thickness, Pattern pattern) throws MaxChangedBlocksException {
2714+
return hollowOutRegion(region, thickness, pattern, true, null, false);
2715+
}
2716+
2717+
/**
2718+
* Hollows out the region (bounding-box mode is semi-well-defined for non-cuboid selections).
2719+
* The startingPositions parameter takes precedence over usePlacementPosition.
2720+
*
2721+
* @param region the region to hollow out.
2722+
* @param thickness the thickness of the shell to leave (manhattan distance)
2723+
* @param pattern The block pattern to use
2724+
* @param openSides Open up faces touching the bounding box. This matches the legacy behaviour.
2725+
* @param startingPositions Positions to consider as 'outside'. If null, use the selection bounding box
2726+
* @param useBlockGeometry Consider block geometry for visibility calculation
2727+
* @return number of blocks affected
2728+
* @throws MaxChangedBlocksException thrown if too many blocks are changed
2729+
*/
2730+
public int hollowOutRegion(Region region, int thickness, Pattern pattern, boolean openSides, Collection<BlockVector3> startingPositions, boolean useBlockGeometry) throws MaxChangedBlocksException {
26712731
int affected = 0;
26722732

26732733
final Set<BlockVector3> visible = new HashSet<>();
2734+
if (startingPositions == null) {
2735+
// Initialize BFS with selection bounding box
2736+
final BlockVector3 min = region.getMinimumPoint();
2737+
final BlockVector3 max = region.getMaximumPoint();
2738+
2739+
final int minX = min.x();
2740+
final int minY = min.y();
2741+
final int minZ = min.z();
2742+
final int maxX = max.x();
2743+
final int maxY = max.y();
2744+
final int maxZ = max.z();
26742745

2675-
// Initialize BFS with selection bounding box
2676-
final BlockVector3 min = region.getMinimumPoint();
2677-
final BlockVector3 max = region.getMaximumPoint();
2678-
2679-
final int minX = min.x();
2680-
final int minY = min.y();
2681-
final int minZ = min.z();
2682-
final int maxX = max.x();
2683-
final int maxY = max.y();
2684-
final int maxZ = max.z();
2746+
for (int x = minX; x <= maxX; ++x) {
2747+
for (int y = minY; y <= maxY; ++y) {
2748+
visible.add(BlockVector3.at(x, y, minZ));
2749+
visible.add(BlockVector3.at(x, y, maxZ));
2750+
}
2751+
}
26852752

2686-
for (int x = minX; x <= maxX; ++x) {
26872753
for (int y = minY; y <= maxY; ++y) {
2688-
visible.add(BlockVector3.at(x, y, minZ));
2689-
visible.add(BlockVector3.at(x, y, maxZ));
2754+
for (int z = minZ; z <= maxZ; ++z) {
2755+
visible.add(BlockVector3.at(minX, y, z));
2756+
visible.add(BlockVector3.at(maxX, y, z));
2757+
}
26902758
}
2691-
}
26922759

2693-
for (int y = minY; y <= maxY; ++y) {
26942760
for (int z = minZ; z <= maxZ; ++z) {
2695-
visible.add(BlockVector3.at(minX, y, z));
2696-
visible.add(BlockVector3.at(maxX, y, z));
2761+
for (int x = minX; x <= maxX; ++x) {
2762+
visible.add(BlockVector3.at(x, minY, z));
2763+
visible.add(BlockVector3.at(x, maxY, z));
2764+
}
26972765
}
2698-
}
26992766

2700-
for (int z = minZ; z <= maxZ; ++z) {
2701-
for (int x = minX; x <= maxX; ++x) {
2702-
visible.add(BlockVector3.at(x, minY, z));
2703-
visible.add(BlockVector3.at(x, maxY, z));
2767+
if (openSides) {
2768+
// Remove movement blockers from visible list
2769+
visible.removeIf(blockVector3 -> getBlock(blockVector3).getBlockType().getMaterial().isMovementBlocker());
27042770
}
2771+
} else {
2772+
visible.addAll(startingPositions);
27052773
}
27062774

2707-
// Remove movement blockers from visible list
2708-
visible.removeIf(blockVector3 -> getBlock(blockVector3).getBlockType().getMaterial().isMovementBlocker());
2709-
27102775
// Do BFS to find more visible blocks
27112776
final Queue<BlockVector3> queue = new ArrayDeque<>(visible);
27122777
while (!queue.isEmpty()) {
27132778
final BlockVector3 current = queue.poll();
27142779

2715-
final BlockState block = getBlock(current);
2716-
if (block.getBlockType().getMaterial().isMovementBlocker()) {
2717-
continue;
2780+
Direction[] blockedDirections;
2781+
if (useBlockGeometry) {
2782+
// Get blocked directions for this block
2783+
blockedDirections = getBlockedDirections(this, current);
2784+
2785+
// Short-cut if blocked in all directions
2786+
if (blockedDirections == CARDINAL_UPRIGHT_DIRECTIONS) {
2787+
continue;
2788+
}
2789+
} else {
2790+
final BlockState block = getBlock(current);
2791+
if (block.getBlockType().getMaterial().isMovementBlocker()) {
2792+
// In non-geometry mode, all movement blockers are assumed to be blocked in all directions, so short-cut here
2793+
continue;
2794+
}
2795+
2796+
// All other blocks are assumed to have no blocked directions
2797+
blockedDirections = NO_DIRECTIONS;
27182798
}
27192799

2720-
for (BlockVector3 recurseDirection : CARDINAL_UPRIGHT_OFFSETS) {
2721-
final BlockVector3 neighbor = current.add(recurseDirection);
2800+
outer:
2801+
for (Direction direction : CARDINAL_UPRIGHT_DIRECTIONS) {
2802+
// Check if this direction is blocked
2803+
for (Direction blockedDirection : blockedDirections) {
2804+
if (blockedDirection == direction) {
2805+
continue outer; // skip this direction
2806+
}
2807+
}
2808+
2809+
final BlockVector3 neighbor = current.add(direction.toBlockVector());
27222810

27232811
if (!region.contains(neighbor)) {
27242812
continue;
@@ -3171,6 +3259,10 @@ public int morph(BlockVector3 position, double brushSize, int minErodeFaces, int
31713259
return changed;
31723260
}
31733261

3262+
/**
3263+
* Contains no directions.
3264+
*/
3265+
private static final Direction[] NO_DIRECTIONS = {};
31743266
/**
31753267
* Contains all cardinal and upright directions.
31763268
*/
@@ -3184,6 +3276,64 @@ public int morph(BlockVector3 position, double brushSize, int minErodeFaces, int
31843276
.map(Direction::toBlockVector)
31853277
.toArray(BlockVector3[]::new);
31863278

3279+
/**
3280+
* Some blocks need a special case for one reason or another.
3281+
*/
3282+
private static final Map<BlockType, Direction[]> BLOCKED_DIRECTIONS_OVERRIDE = new HashMap<>();
3283+
3284+
static {
3285+
Arrays.asList(
3286+
// fullcubes that you can see through on all 6 sides
3287+
BlockTypes.SPAWNER,
3288+
BlockTypes.BEACON,
3289+
BlockTypes.MANGROVE_ROOTS,
3290+
BlockTypes.ICE,
3291+
3292+
// You can see through some of the doors/trapdoors, which is not reflected in their visual shape
3293+
// Commented lines are opaque and kept for reference
3294+
BlockTypes.OAK_DOOR,
3295+
BlockTypes.OAK_TRAPDOOR,
3296+
// BlockTypes.SPRUCE_DOOR,
3297+
// BlockTypes.SPRUCE_TRAPDOOR,
3298+
// BlockTypes.BIRCH_DOOR,
3299+
// BlockTypes.BIRCH_TRAPDOOR,
3300+
BlockTypes.JUNGLE_DOOR,
3301+
BlockTypes.JUNGLE_TRAPDOOR,
3302+
BlockTypes.ACACIA_DOOR,
3303+
BlockTypes.ACACIA_TRAPDOOR,
3304+
// BlockTypes.DARK_OAK_DOOR,
3305+
// BlockTypes.DARK_OAK_TRAPDOOR,
3306+
// BlockTypes.MANGROVE_DOOR,
3307+
BlockTypes.MANGROVE_TRAPDOOR,
3308+
BlockTypes.CHERRY_DOOR,
3309+
BlockTypes.CHERRY_TRAPDOOR,
3310+
// BlockTypes.PALE_OAK_DOOR,
3311+
// BlockTypes.PALE_OAK_TRAPDOOR,
3312+
BlockTypes.BAMBOO_DOOR,
3313+
BlockTypes.BAMBOO_TRAPDOOR,
3314+
BlockTypes.CRIMSON_DOOR,
3315+
BlockTypes.CRIMSON_TRAPDOOR,
3316+
BlockTypes.WARPED_DOOR,
3317+
BlockTypes.WARPED_TRAPDOOR,
3318+
BlockTypes.IRON_DOOR,
3319+
BlockTypes.IRON_TRAPDOOR
3320+
).forEach(blockType -> {
3321+
BLOCKED_DIRECTIONS_OVERRIDE.put(blockType, NO_DIRECTIONS);
3322+
});
3323+
3324+
// The copper doors/trapdoors are too many to list individually
3325+
java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("^.*:(?:.*_)?(?:copper_door|copper_trapdoor)(?:_.*)?$");
3326+
for (BlockType blockType : BlockType.REGISTRY) {
3327+
if (pattern.matcher(blockType.id()).matches()) {
3328+
BLOCKED_DIRECTIONS_OVERRIDE.put(blockType, NO_DIRECTIONS);
3329+
}
3330+
}
3331+
3332+
// These are visual fullcubes, but they're mostly open:
3333+
BLOCKED_DIRECTIONS_OVERRIDE.put(BlockTypes.TRIAL_SPAWNER, new Direction[] { Direction.UP });
3334+
BLOCKED_DIRECTIONS_OVERRIDE.put(BlockTypes.VAULT, new Direction[] { Direction.UP, Direction.DOWN });
3335+
}
3336+
31873337
private static double lengthSq(double x, double y, double z) {
31883338
return (x * x) + (y * y) + (z * z);
31893339
}

worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,11 @@
7979
import org.enginehub.piston.annotation.param.Arg;
8080
import org.enginehub.piston.annotation.param.ArgFlag;
8181
import org.enginehub.piston.annotation.param.Switch;
82+
import org.enginehub.piston.exception.CommandException;
8283

8384
import java.util.ArrayList;
85+
import java.util.Collection;
86+
import java.util.Collections;
8487
import java.util.List;
8588

8689
import static com.sk89q.worldedit.command.util.Logging.LogMode.ALL;
@@ -548,15 +551,36 @@ public int deform(Actor actor, LocalSession session, EditSession editSession,
548551
)
549552
@CommandPermissions("worldedit.region.hollow")
550553
@Logging(REGION)
551-
public int hollow(Actor actor, EditSession editSession,
554+
public int hollow(Actor actor, EditSession editSession, LocalSession session,
552555
@Selection Region region,
553556
@Arg(desc = "Thickness of the shell to leave", def = "1")
554557
int thickness,
555558
@Arg(desc = "The pattern of blocks to replace the hollowed area with", def = "air")
556-
Pattern pattern) throws WorldEditException {
559+
Pattern pattern,
560+
@Switch(name = 'o', desc = "Open up faces touching the bounding box. This matches the legacy behaviour.")
561+
boolean openSides,
562+
@Switch(name = 'p', desc = "Consider placement position as 'outside' instead of the selection bounding box. Overrides -o.")
563+
boolean usePlacementPosition,
564+
@Switch(name = 'g', desc = "Consider block geometry for visibility calculation")
565+
boolean useBlockGeometry) throws WorldEditException {
557566
checkCommandArgument(thickness >= 1, "Thickness must be >= 1");
558567

559-
int affected = editSession.hollowOutRegion(region, thickness, pattern);
568+
final Collection<BlockVector3> startingPositions;
569+
if (usePlacementPosition) {
570+
BlockVector3 placementPosition = session.getPlacementPosition(actor);
571+
if (!region.contains(placementPosition)) {
572+
throw new CommandException(
573+
TextComponent.of("Placement position must be inside selection (")
574+
.append(session.getPlacement().getInfo())
575+
.append(TextComponent.of(")")),
576+
ImmutableList.of()
577+
);
578+
}
579+
startingPositions = Collections.singletonList(placementPosition);
580+
} else {
581+
startingPositions = null;
582+
}
583+
int affected = editSession.hollowOutRegion(region, thickness, pattern, openSides, startingPositions, useBlockGeometry);
560584
actor.printInfo(TranslatableComponent.of("worldedit.hollow.changed", TextComponent.of(affected)));
561585
return affected;
562586
}

0 commit comments

Comments
 (0)