From 0c8c8d24c4434ff59050ccb3f90de6fd532a7e70 Mon Sep 17 00:00:00 2001 From: TomyLobo Date: Sat, 27 Sep 2025 16:55:27 +0200 Subject: [PATCH 1/2] Add //sel polyextend This selector works by inserting a new point into the closest edge. --- .../worldedit/command/SelectionCommands.java | 1 + .../command/argument/SelectorChoice.java | 11 ++ .../worldedit/regions/Polygonal2DRegion.java | 11 ++ .../ExtendingPolygonal2DRegionSelector.java | 167 ++++++++++++++++++ .../selector/Polygonal2DRegionSelector.java | 2 +- .../src/main/resources/lang/strings.json | 2 + 6 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/ExtendingPolygonal2DRegionSelector.java 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 be92d866e7..a6f2fc3a02 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 @@ -768,6 +768,7 @@ public void select(Actor actor, World world, LocalSession session, box.appendCommand("cuboid", TranslatableComponent.of("worldedit.select.cuboid.description"), "//sel cuboid"); box.appendCommand("extend", TranslatableComponent.of("worldedit.select.extend.description"), "//sel extend"); box.appendCommand("poly", TranslatableComponent.of("worldedit.select.poly.description"), "//sel poly"); + box.appendCommand("polyextend", TranslatableComponent.of("worldedit.select.polyextend.description"), "//sel polyextend"); box.appendCommand("ellipsoid", TranslatableComponent.of("worldedit.select.ellipsoid.description"), "//sel ellipsoid"); box.appendCommand("sphere", TranslatableComponent.of("worldedit.select.sphere.description"), "//sel sphere"); box.appendCommand("cyl", TranslatableComponent.of("worldedit.select.cyl.description"), "//sel cyl"); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/SelectorChoice.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/SelectorChoice.java index e6a87e724b..f4279bf55f 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/SelectorChoice.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/SelectorChoice.java @@ -27,6 +27,7 @@ import com.sk89q.worldedit.regions.selector.CylinderRegionSelector; import com.sk89q.worldedit.regions.selector.EllipsoidRegionSelector; import com.sk89q.worldedit.regions.selector.ExtendingCuboidRegionSelector; +import com.sk89q.worldedit.regions.selector.ExtendingPolygonal2DRegionSelector; import com.sk89q.worldedit.regions.selector.Polygonal2DRegionSelector; import com.sk89q.worldedit.regions.selector.SphereRegionSelector; import com.sk89q.worldedit.util.formatting.text.Component; @@ -50,6 +51,16 @@ public void explainNewSelector(Actor actor) { ))); } }, + POLYEXTEND(ExtendingPolygonal2DRegionSelector::new, ExtendingPolygonal2DRegionSelector::new, "worldedit.select.polyextend.message") { + @Override + public void explainNewSelector(Actor actor) { + super.explainNewSelector(actor); + Optional limit = ActorSelectorLimits.forActor(actor).getPolygonVertexLimit(); + limit.ifPresent(integer -> actor.printInfo(TranslatableComponent.of( + "worldedit.select.poly.limit-message", TextComponent.of(integer) + ))); + } + }, ELLIPSOID(EllipsoidRegionSelector::new, EllipsoidRegionSelector::new, "worldedit.select.ellipsoid.message"), SPHERE(SphereRegionSelector::new, SphereRegionSelector::new, "worldedit.select.sphere.message"), CYL(CylinderRegionSelector::new, CylinderRegionSelector::new, "worldedit.select.cyl.message"), diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/Polygonal2DRegion.java b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/Polygonal2DRegion.java index b54baed6e0..b5e525ab2d 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/Polygonal2DRegion.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/Polygonal2DRegion.java @@ -155,6 +155,17 @@ public void addPoint(BlockVector2 position) { recalculate(); } + /** + * Insert a point into the list. + * + * @param i index to insert after + * @param position the position + */ + public void addPoint(int i, BlockVector2 position) { + points.add(i, position); + recalculate(); + } + /** * Add a point to the list. * diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/ExtendingPolygonal2DRegionSelector.java b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/ExtendingPolygonal2DRegionSelector.java new file mode 100644 index 0000000000..1385e6cf37 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/ExtendingPolygonal2DRegionSelector.java @@ -0,0 +1,167 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.regions.selector; + +import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.math.Vector2; +import com.sk89q.worldedit.regions.RegionSelector; +import com.sk89q.worldedit.regions.selector.limit.SelectorLimits; +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; +import com.sk89q.worldedit.world.World; + +import java.util.List; +import javax.annotation.Nullable; + +/** + * Creates a {@code Polygonal2DRegion} from a user's selections by expanding + * the region on every right click. + */ +public class ExtendingPolygonal2DRegionSelector extends Polygonal2DRegionSelector { + + /** + * Create a new selector with a {@code null} world. + */ + public ExtendingPolygonal2DRegionSelector() { + super((World) null); + } + + /** + * Create a new selector. + * + * @param world the world, which may be {@code null} + */ + public ExtendingPolygonal2DRegionSelector(@Nullable World world) { + super(world); + } + + /** + * Create a new selector from another one. + * + * @param oldSelector the other selector + */ + public ExtendingPolygonal2DRegionSelector(RegionSelector oldSelector) { + super(oldSelector); + } + + /** + * Create a new selector. + * + * @param world the world + * @param points a list of points + * @param minY the minimum Y + * @param maxY the maximum Y + */ + public ExtendingPolygonal2DRegionSelector(@Nullable World world, List points, int minY, int maxY) { + super(world, points, minY, maxY); + } + + private static double distanceFromLine(Vector2 position, Vector2 start, Vector2 end) { + // Vector from start to end + final Vector2 difference = end.subtract(start); + + // Distance between start and end + final double length = difference.length(); + + // Normalized direction vector pointing from start towards end + final Vector2 normalizedDifference = difference.divide(length); + + // Distance from start to position, projected onto the line + final double determinant = position.subtract(start).dot(normalizedDifference); + + if (determinant <= 0) { + // We're before the line start + return position.distance(start); + } else if (determinant >= length) { + // We're beyond the line end + return position.distance(end); + } else { + // We're next to the line + + // Calculate normalized normal by rotating the normalized difference 90° + final Vector2 normalizedNormal = new Vector2(-normalizedDifference.z(), normalizedDifference.x()); + + // Calculate and return the absolute distance along that normal + return Math.abs(position.subtract(start).dot(normalizedNormal)); + } + } + + @Override + public boolean selectSecondary(BlockVector3 position, SelectorLimits limits) { + // Use the regular algo until we have at least 3 points + if (region.size() < 3) { + return super.selectSecondary(position, limits); + } + + final BlockVector2 blockPosition2D = position.toBlockVector2(); + final Vector2 position2D = blockPosition2D.toVector2(); + + // Find the closest edge + final List points = region.getPoints(); + double minDistance = Integer.MAX_VALUE; + int insertBeforeIndex = -1; + + // Start with the last vertex to close the loop + Vector2 previousPoint = points.getLast().toVector2(); + + for (int i = 0; i < points.size(); i++) { + final BlockVector2 blockPoint = points.get(i); + + // Don't duplicate points + if (blockPoint.equals(blockPosition2D)) { + return false; + } + + final Vector2 point = blockPoint.toVector2(); + + // Calculate distance from edge + final double currentDistance = distanceFromLine(position2D, previousPoint, point); + if (currentDistance < minDistance) { + minDistance = currentDistance; + insertBeforeIndex = i; + } + + // Keep previousPoint for the next iteration + previousPoint = point; + } + + assert insertBeforeIndex >= 0; + + // Insert new point on the determined edge + region.addPoint(insertBeforeIndex, blockPosition2D); + region.expandY(position.y()); + + return true; + } + + @Override + public void explainSecondarySelection(Actor player, LocalSession session, BlockVector3 pos) { + player.printInfo(TranslatableComponent.of( + "worldedit.selection.polygon2d.explain.secondary", + TextComponent.of(region.size()), + TextComponent.of(pos.toString()) + )); + + explainRegionAdjust(player, session); + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/Polygonal2DRegionSelector.java b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/Polygonal2DRegionSelector.java index 962f40b55b..5d7ad4defc 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/Polygonal2DRegionSelector.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/Polygonal2DRegionSelector.java @@ -50,7 +50,7 @@ public class Polygonal2DRegionSelector implements RegionSelector, CUIRegion { private transient BlockVector3 pos1; - private transient Polygonal2DRegion region; + protected transient Polygonal2DRegion region; /** * Create a new selector with a {@code null} world. diff --git a/worldedit-core/src/main/resources/lang/strings.json b/worldedit-core/src/main/resources/lang/strings.json index 37dbc32cb9..bbfea26f0e 100644 --- a/worldedit-core/src/main/resources/lang/strings.json +++ b/worldedit-core/src/main/resources/lang/strings.json @@ -170,6 +170,8 @@ "worldedit.select.poly.message": "2D polygon selector: Left/right click to add a point.", "worldedit.select.poly.limit-message": "{0} points maximum.", "worldedit.select.poly.description": "Select a 2D polygon with height", + "worldedit.select.polyextend.message": "2D polygon selector: left click for a starting point, right click to extend", + "worldedit.select.polyextend.description": "Fast 2D polygonal selection mode", "worldedit.select.ellipsoid.message": "Ellipsoid selector: left click=center, right click to extend", "worldedit.select.ellipsoid.description": "Select an ellipsoid", "worldedit.select.sphere.message": "Sphere selector: left click=center, right click to set radius", From 01093291dc8e3977290ab0ad7ef644580003e542 Mon Sep 17 00:00:00 2001 From: TomyLobo Date: Mon, 13 Oct 2025 05:41:01 +0200 Subject: [PATCH 2/2] Change wording of extending selector descriptions --- worldedit-core/src/main/resources/lang/strings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worldedit-core/src/main/resources/lang/strings.json b/worldedit-core/src/main/resources/lang/strings.json index bbfea26f0e..32da7e0956 100644 --- a/worldedit-core/src/main/resources/lang/strings.json +++ b/worldedit-core/src/main/resources/lang/strings.json @@ -166,12 +166,12 @@ "worldedit.select.cuboid.message": "Cuboid: left click for point 1, right click for point 2", "worldedit.select.cuboid.description": "Select two corners of a cuboid", "worldedit.select.extend.message": "Cuboid: left click for a starting point, right click to extend", - "worldedit.select.extend.description": "Fast cuboid selection mode", + "worldedit.select.extend.description": "Extending cuboid selection mode", "worldedit.select.poly.message": "2D polygon selector: Left/right click to add a point.", "worldedit.select.poly.limit-message": "{0} points maximum.", "worldedit.select.poly.description": "Select a 2D polygon with height", "worldedit.select.polyextend.message": "2D polygon selector: left click for a starting point, right click to extend", - "worldedit.select.polyextend.description": "Fast 2D polygonal selection mode", + "worldedit.select.polyextend.description": "Extending 2D polygonal selection mode", "worldedit.select.ellipsoid.message": "Ellipsoid selector: left click=center, right click to extend", "worldedit.select.ellipsoid.description": "Select an ellipsoid", "worldedit.select.sphere.message": "Sphere selector: left click=center, right click to set radius",