diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java index 7d910a7c7d..87dfac4796 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java @@ -28,6 +28,7 @@ import com.sk89q.worldedit.extension.platform.Watchdog; import com.sk89q.worldedit.extent.ChangeSetExtent; import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.extent.InputExtent; import com.sk89q.worldedit.extent.MaskingExtent; import com.sk89q.worldedit.extent.NullExtent; import com.sk89q.worldedit.extent.TracingExtent; @@ -95,6 +96,8 @@ import com.sk89q.worldedit.math.interpolation.Node; import com.sk89q.worldedit.math.noise.RandomNoise; import com.sk89q.worldedit.math.transform.AffineTransform; +import com.sk89q.worldedit.math.transform.ScaleAndTranslateTransform; +import com.sk89q.worldedit.math.transform.Transform; import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.CylinderRegion; import com.sk89q.worldedit.regions.EllipsoidRegion; @@ -2310,7 +2313,9 @@ public List> getBlockDistribution(Region region, @Nullable * @return number of blocks changed * @throws ExpressionException if there is a problem with the expression * @throws MaxChangedBlocksException if the maximum block change limit is exceeded + * @deprecated Use {@link EditSession#makeShape(Region, Transform, Pattern, String, boolean, int)} and pass a {@link ScaleAndTranslateTransform}. */ + @Deprecated public int makeShape(final Region region, final Vector3 zero, final Vector3 unit, final Pattern pattern, final String expressionString, final boolean hollow) throws ExpressionException, MaxChangedBlocksException { @@ -2330,13 +2335,34 @@ public int makeShape(final Region region, final Vector3 zero, final Vector3 unit * @return number of blocks changed * @throws ExpressionException if there is a problem with the expression * @throws MaxChangedBlocksException if the maximum block change limit is exceeded + * @deprecated Use {@link EditSession#makeShape(Region, Transform, Pattern, String, boolean, int)} and pass a {@link ScaleAndTranslateTransform}. */ + @Deprecated public int makeShape(final Region region, final Vector3 zero, final Vector3 unit, final Pattern pattern, final String expressionString, final boolean hollow, final int timeout) throws ExpressionException, MaxChangedBlocksException { + return makeShape(region, new ScaleAndTranslateTransform(zero, unit), pattern, expressionString, hollow, timeout); + } + + /** + * Generate a shape for the given expression. + * + * @param region the region to generate the shape in + * @param transform the transformation for x/y/z variables + * @param pattern the default material to make the shape from + * @param expressionString the expression defining the shape + * @param hollow whether the shape should be hollow + * @param timeout the time, in milliseconds, to wait for each expression evaluation before halting it. -1 to disable + * @return number of blocks changed + * @throws ExpressionException if there is a problem with the expression + * @throws MaxChangedBlocksException if the maximum block change limit is exceeded + */ + public int makeShape(final Region region, + Transform transform, final Pattern pattern, final String expressionString, final boolean hollow, final int timeout) + throws ExpressionException, MaxChangedBlocksException { final Expression expression = Expression.compile(expressionString, "x", "y", "z", "type", "data"); expression.optimize(); - return makeShape(region, zero, unit, pattern, expression, hollow, timeout); + return makeShape(region, transform, pattern, expression, hollow, timeout); } /** @@ -2346,7 +2372,7 @@ public int makeShape(final Region region, final Vector3 zero, final Vector3 unit * The Expression class is subject to change. Expressions should be provided via the string overload. *

*/ - public int makeShape(final Region region, final Vector3 zero, final Vector3 unit, + public int makeShape(final Region region, Transform transform, final Pattern pattern, final Expression expression, final boolean hollow, final int timeout) throws ExpressionException, MaxChangedBlocksException { @@ -2362,16 +2388,17 @@ public int makeShape(final Region region, final Vector3 zero, final Vector3 unit final Variable dataVariable = expression.getSlots().getVariable("data") .orElseThrow(IllegalStateException::new); - final WorldEditExpressionEnvironment environment = new WorldEditExpressionEnvironment(this, unit, zero); + final WorldEditExpressionEnvironment environment = new WorldEditExpressionEnvironment(this, transform); expression.setEnvironment(environment); final int[] timedOut = {0}; + final Transform transformInverse = transform.inverse(); final ArbitraryShape shape = new ArbitraryShape(region) { @Override protected BaseBlock getMaterial(int x, int y, int z, BaseBlock defaultMaterial) { final Vector3 current = Vector3.at(x, y, z); environment.setCurrentBlock(current); - final Vector3 scaled = current.subtract(zero).divide(unit); + final Vector3 inputPosition = transformInverse.apply(current); try { int[] legacy = LegacyMapper.getInstance().getLegacyFromBlock(defaultMaterial.toImmutableState()); @@ -2383,7 +2410,7 @@ protected BaseBlock getMaterial(int x, int y, int z, BaseBlock defaultMaterial) dataVar = legacy[1]; } } - if (expression.evaluate(new double[]{ scaled.x(), scaled.y(), scaled.z(), typeVar, dataVar}, timeout) <= 0) { + if (expression.evaluate(new double[]{ inputPosition.x(), inputPosition.y(), inputPosition.z(), typeVar, dataVar}, timeout) <= 0) { return null; } int newType = (int) typeVariable.value(); @@ -2427,10 +2454,12 @@ protected BaseBlock getMaterial(int x, int y, int z, BaseBlock defaultMaterial) * * @throws ExpressionException thrown on invalid expression input * @throws MaxChangedBlocksException thrown if too many blocks are changed + * @deprecated Use {@link EditSession#deformRegion(Region, Transform, String, int, InputExtent, Transform)} and pass a {@link ScaleAndTranslateTransform}. */ + @Deprecated public int deformRegion(final Region region, final Vector3 zero, final Vector3 unit, final String expressionString) throws ExpressionException, MaxChangedBlocksException { - return deformRegion(region, zero, unit, expressionString, WorldEdit.getInstance().getConfiguration().calculationTimeout); + return deformRegion(region, new ScaleAndTranslateTransform(zero, unit), expressionString, WorldEdit.getInstance().getConfiguration().calculationTimeout); } /** @@ -2448,12 +2477,55 @@ public int deformRegion(final Region region, final Vector3 zero, final Vector3 u * * @throws ExpressionException thrown on invalid expression input * @throws MaxChangedBlocksException thrown if too many blocks are changed + * @deprecated Use {@link EditSession#deformRegion(Region, Transform, String, int, InputExtent, Transform)} and pass a {@link ScaleAndTranslateTransform}. */ + @Deprecated public int deformRegion(final Region region, final Vector3 zero, final Vector3 unit, final String expressionString, final int timeout) throws ExpressionException, MaxChangedBlocksException { + final Transform transform = new ScaleAndTranslateTransform(zero, unit); + return deformRegion(region, transform, expressionString, timeout); + } + + /** + * Deforms the region by a given expression. A deform provides a block's x, y, and z coordinates (possibly scaled) + * to an expression, and then sets the block to the block given by the resulting values of the variables, if they + * have changed. + * + * @param region the region to deform + * @param targetTransform the target coordinate system + * @param expressionString the expression to evaluate for each block + * @param timeout maximum time for the expression to evaluate for each block. -1 for unlimited. + * @param sourceExtent the InputExtent to fetch blocks from, for instance a World or a Clipboard + * @param sourceTransform the source coordinate system + * @return number of blocks changed + * @throws ExpressionException thrown on invalid expression input + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + public int deformRegion(final Region region, final Transform targetTransform, final String expressionString, + final int timeout, InputExtent sourceExtent, Transform sourceTransform) throws ExpressionException, MaxChangedBlocksException { final Expression expression = Expression.compile(expressionString, "x", "y", "z"); expression.optimize(); - return deformRegion(region, zero, unit, expression, timeout); + return deformRegion(region, targetTransform, expression, timeout, sourceExtent, sourceTransform); + } + + /** + * Deforms the region by a given expression. A deform provides a block's x, y, and z coordinates (possibly scaled) + * to an expression, and then sets the block to the block given by the resulting values of the variables, if they + * have changed. + * + * @param region the region to deform + * @param transform the coordinate system + * @param expressionString the expression to evaluate for each block + * @param timeout maximum time for the expression to evaluate for each block. -1 for unlimited. + * @return number of blocks changed + * @throws ExpressionException thrown on invalid expression input + * @throws MaxChangedBlocksException thrown if too many blocks are changed + * @deprecated Use {@link EditSession#deformRegion(Region, Transform, String, int, InputExtent, Transform)}. + */ + @Deprecated + public int deformRegion(final Region region, final Transform transform, final String expressionString, + final int timeout) throws ExpressionException, MaxChangedBlocksException { + return deformRegion(region, transform, expressionString, timeout, world, transform); } /** @@ -2463,8 +2535,8 @@ public int deformRegion(final Region region, final Vector3 zero, final Vector3 u * The Expression class is subject to change. Expressions should be provided via the string overload. *

*/ - public int deformRegion(final Region region, final Vector3 zero, final Vector3 unit, final Expression expression, - final int timeout) throws ExpressionException, MaxChangedBlocksException { + public int deformRegion(final Region region, final Transform targetTransform, final Expression expression, + final int timeout, InputExtent sourceExtent, final Transform sourceTransform) throws ExpressionException, MaxChangedBlocksException { final Variable x = expression.getSlots().getVariable("x") .orElseThrow(IllegalStateException::new); final Variable y = expression.getSlots().getVariable("y") @@ -2472,25 +2544,28 @@ public int deformRegion(final Region region, final Vector3 zero, final Vector3 u final Variable z = expression.getSlots().getVariable("z") .orElseThrow(IllegalStateException::new); - final WorldEditExpressionEnvironment environment = new WorldEditExpressionEnvironment(this, unit, zero); + final WorldEditExpressionEnvironment environment = new WorldEditExpressionEnvironment(this, targetTransform); expression.setEnvironment(environment); final DoubleArrayList queue = new DoubleArrayList<>(false); + final Transform targetTransformInverse = targetTransform.inverse(); for (BlockVector3 targetBlockPosition : region) { final Vector3 targetPosition = targetBlockPosition.toVector3(); environment.setCurrentBlock(targetPosition); - // offset, scale - final Vector3 scaled = targetPosition.subtract(zero).divide(unit); + // transform from target coordinates + final Vector3 inputPosition = targetTransformInverse.apply(targetPosition); - // transform - expression.evaluate(new double[]{ scaled.x(), scaled.y(), scaled.z() }, timeout); + // deform + expression.evaluate(new double[]{ inputPosition.x(), inputPosition.y(), inputPosition.z() }, timeout); + final Vector3 outputPosition = Vector3.at(x.value(), y.value(), z.value()); - final BlockVector3 sourcePosition = environment.toWorld(x.value(), y.value(), z.value()); + // transform to source coordinates, round-nearest + final BlockVector3 sourcePosition = sourceTransform.apply(outputPosition).add(0.5, 0.5, 0.5).toBlockPoint(); - // read block from world - final BaseBlock material = world.getFullBlock(sourcePosition); + // read block from source extent (e.g. world/clipboard) + final BaseBlock material = sourceExtent.getFullBlock(sourcePosition); // queue operation queue.put(targetBlockPosition, material); @@ -2498,11 +2573,11 @@ public int deformRegion(final Region region, final Vector3 zero, final Vector3 u int affected = 0; for (Map.Entry entry : queue) { - BlockVector3 position = entry.getKey(); + BlockVector3 targetPosition = entry.getKey(); BaseBlock material = entry.getValue(); - // set at new position - if (setBlock(position, material)) { + // set at new targetPosition + if (setBlock(targetPosition, material)) { ++affected; } } @@ -2795,31 +2870,57 @@ private void recurseHollow(Region region, BlockVector3 origin, Set } } + /** + * @deprecated Use {@link EditSession#makeBiomeShape(Region, Transform, BiomeType, String, boolean, int)} and pass a {@link ScaleAndTranslateTransform}. + */ + @Deprecated public int makeBiomeShape(final Region region, final Vector3 zero, final Vector3 unit, final BiomeType biomeType, final String expressionString, final boolean hollow) throws ExpressionException { return makeBiomeShape(region, zero, unit, biomeType, expressionString, hollow, WorldEdit.getInstance().getConfiguration().calculationTimeout); } + /** + * @deprecated Use {@link EditSession#makeBiomeShape(Region, Transform, BiomeType, String, boolean, int)} and pass a {@link ScaleAndTranslateTransform}. + */ + @Deprecated public int makeBiomeShape(final Region region, final Vector3 zero, final Vector3 unit, final BiomeType biomeType, final String expressionString, final boolean hollow, final int timeout) throws ExpressionException { + return makeBiomeShape(region, new ScaleAndTranslateTransform(zero, unit), biomeType, expressionString, hollow, timeout); + } + + /** + * Generate a biome shape for the given expression. + * + * @param region the region to generate the shape in + * @param transform the transformation for x/y/z variables + * @param biomeType the biome to make the shape from + * @param expressionString the expression defining the shape + * @param hollow whether the shape should be hollow + * @param timeout + * @return number of blocks changed + * @throws ExpressionException if there is a problem with the expression + */ + public int makeBiomeShape(final Region region, Transform transform, final BiomeType biomeType, + final String expressionString, final boolean hollow, final int timeout) throws ExpressionException { final Expression expression = Expression.compile(expressionString, "x", "y", "z"); expression.optimize(); final EditSession editSession = this; - final WorldEditExpressionEnvironment environment = new WorldEditExpressionEnvironment(editSession, unit, zero); + final WorldEditExpressionEnvironment environment = new WorldEditExpressionEnvironment(editSession, transform); expression.setEnvironment(environment); AtomicInteger timedOut = new AtomicInteger(); + final Transform transformInverse = transform.inverse(); final ArbitraryBiomeShape shape = new ArbitraryBiomeShape(region) { @Override protected BiomeType getBiome(int x, int y, int z, BiomeType defaultBiomeType) { final Vector3 current = Vector3.at(x, y, z); environment.setCurrentBlock(current); - final Vector3 scaled = current.subtract(zero).divide(unit); + final Vector3 inputPosition = transformInverse.apply(current); try { - if (expression.evaluate(new double[]{ scaled.x(), scaled.y(), scaled.z() }, timeout) <= 0) { + if (expression.evaluate(new double[]{ inputPosition.x(), inputPosition.y(), inputPosition.z() }, timeout) <= 0) { return null; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java b/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java index 8de47a2857..260cd5d11d 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java @@ -71,6 +71,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.TimeZone; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; @@ -453,6 +454,15 @@ public ClipboardHolder getClipboard() throws EmptyClipboardException { return clipboard; } + /** + * Gets the clipboard. + * + * @return clipboard + */ + public Optional getClipboardOptional() { + return Optional.ofNullable(clipboard); + } + /** * Sets the clipboard. * diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java index 120ba27db8..f2155b3e96 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java @@ -506,7 +506,9 @@ public void deform(Player player, LocalSession localSession, @Switch(name = 'r', desc = "Use the game's coordinate origin") boolean useRawCoords, @Switch(name = 'o', desc = "Use the placement position as the origin") - boolean usePlacement) throws WorldEditException { + boolean usePlacement, + @Switch(name = 'l', desc = "Fetch from the clipboard instead of the world") + boolean useClipboard) throws WorldEditException { Deform deform = new Deform(expression); if (useRawCoords) { deform.setMode(Deform.Mode.RAW_COORD); @@ -514,6 +516,7 @@ public void deform(Player player, LocalSession localSession, deform.setMode(Deform.Mode.OFFSET); deform.setOffset(localSession.getPlacementPosition(player).toVector3()); } + deform.setUseClipboard(useClipboard); setOperationBasedBrush(player, localSession, radius, deform, shape, "worldedit.brush.deform"); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java index 1c7bb86655..7cd222f742 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java @@ -32,8 +32,9 @@ import com.sk89q.worldedit.internal.annotation.Radii; import com.sk89q.worldedit.internal.annotation.Selection; import com.sk89q.worldedit.internal.expression.ExpressionException; +import com.sk89q.worldedit.internal.util.TransformUtil; import com.sk89q.worldedit.math.BlockVector3; -import com.sk89q.worldedit.math.Vector3; +import com.sk89q.worldedit.math.transform.Transform; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.util.TreeGenerator.TreeType; import com.sk89q.worldedit.util.formatting.text.TextComponent; @@ -370,45 +371,13 @@ public int generate(Actor actor, LocalSession session, EditSession editSession, @Switch(name = 'r', desc = "Use the game's coordinate origin") boolean useRawCoords, @Switch(name = 'o', desc = "Use the placement's coordinate origin") - boolean offset, + boolean offsetPlacement, @Switch(name = 'c', desc = "Use the selection's center as origin") boolean offsetCenter) throws WorldEditException { - - final Vector3 zero; - Vector3 unit; - - if (useRawCoords) { - zero = Vector3.ZERO; - unit = Vector3.ONE; - } else if (offset) { - zero = session.getPlacementPosition(actor).toVector3(); - unit = Vector3.ONE; - } else if (offsetCenter) { - final Vector3 min = region.getMinimumPoint().toVector3(); - final Vector3 max = region.getMaximumPoint().toVector3(); - - zero = max.add(min).multiply(0.5); - unit = Vector3.ONE; - } else { - final Vector3 min = region.getMinimumPoint().toVector3(); - final Vector3 max = region.getMaximumPoint().toVector3(); - - zero = max.add(min).multiply(0.5); - unit = max.subtract(zero); - - if (unit.x() == 0) { - unit = unit.withX(1.0); - } - if (unit.y() == 0) { - unit = unit.withY(1.0); - } - if (unit.z() == 0) { - unit = unit.withZ(1.0); - } - } + final Transform transform = TransformUtil.createTransformForExpressionCommand(actor, session, region, useRawCoords, offsetPlacement, offsetCenter); try { - final int affected = editSession.makeShape(region, zero, unit, pattern, String.join(" ", expression), hollow, session.getTimeout()); + final int affected = editSession.makeShape(region, transform, pattern, String.join(" ", expression), hollow, session.getTimeout()); if (actor instanceof Player) { ((Player) actor).findFreePosition(); } @@ -439,44 +408,13 @@ public int generateBiome(Actor actor, LocalSession session, EditSession editSess @Switch(name = 'r', desc = "Use the game's coordinate origin") boolean useRawCoords, @Switch(name = 'o', desc = "Use the placement's coordinate origin") - boolean offset, + boolean offsetPlacement, @Switch(name = 'c', desc = "Use the selection's center as origin") boolean offsetCenter) throws WorldEditException { - final Vector3 zero; - Vector3 unit; - - if (useRawCoords) { - zero = Vector3.ZERO; - unit = Vector3.ONE; - } else if (offset) { - zero = session.getPlacementPosition(actor).toVector3(); - unit = Vector3.ONE; - } else if (offsetCenter) { - final Vector3 min = region.getMinimumPoint().toVector3(); - final Vector3 max = region.getMaximumPoint().toVector3(); - - zero = max.add(min).multiply(0.5); - unit = Vector3.ONE; - } else { - final Vector3 min = region.getMinimumPoint().toVector3(); - final Vector3 max = region.getMaximumPoint().toVector3(); - - zero = max.add(min).multiply(0.5); - unit = max.subtract(zero); - - if (unit.x() == 0) { - unit = unit.withX(1.0); - } - if (unit.y() == 0) { - unit = unit.withY(1.0); - } - if (unit.z() == 0) { - unit = unit.withZ(1.0); - } - } + final Transform transform = TransformUtil.createTransformForExpressionCommand(actor, session, region, useRawCoords, offsetPlacement, offsetCenter); try { - final int affected = editSession.makeBiomeShape(region, zero, unit, target, String.join(" ", expression), hollow, session.getTimeout()); + final int affected = editSession.makeBiomeShape(region, transform, target, String.join(" ", expression), hollow, session.getTimeout()); actor.printInfo(TranslatableComponent.of("worldedit.generatebiome.changed", TextComponent.of(affected))); return affected; } catch (ExpressionException e) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java index 5555bd399e..cdd0d70142 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java @@ -31,7 +31,9 @@ import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.extent.InputExtent; import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; +import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.function.GroundFunction; import com.sk89q.worldedit.function.RegionFunction; import com.sk89q.worldedit.function.RegionMaskingFilter; @@ -49,6 +51,7 @@ import com.sk89q.worldedit.internal.annotation.Offset; import com.sk89q.worldedit.internal.annotation.Selection; import com.sk89q.worldedit.internal.expression.ExpressionException; +import com.sk89q.worldedit.internal.util.TransformUtil; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.Vector3; import com.sk89q.worldedit.math.convolution.GaussianKernel; @@ -56,6 +59,7 @@ import com.sk89q.worldedit.math.convolution.HeightMapFilter; import com.sk89q.worldedit.math.convolution.SnowHeightMap; import com.sk89q.worldedit.math.noise.RandomNoise; +import com.sk89q.worldedit.math.transform.Transform; import com.sk89q.worldedit.regions.ConvexPolyhedralRegion; import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Region; @@ -498,44 +502,30 @@ public int deform(Actor actor, LocalSession session, EditSession editSession, @Switch(name = 'r', desc = "Use the game's coordinate origin") boolean useRawCoords, @Switch(name = 'o', desc = "Use the placement's coordinate origin") - boolean offset, + boolean offsetPlacement, @Switch(name = 'c', desc = "Use the selection's center as origin") - boolean offsetCenter) throws WorldEditException { - final Vector3 zero; - Vector3 unit; - - if (useRawCoords) { - zero = Vector3.ZERO; - unit = Vector3.ONE; - } else if (offset) { - zero = session.getPlacementPosition(actor).toVector3(); - unit = Vector3.ONE; - } else if (offsetCenter) { - final Vector3 min = region.getMinimumPoint().toVector3(); - final Vector3 max = region.getMaximumPoint().toVector3(); - - zero = max.add(min).multiply(0.5); - unit = Vector3.ONE; - } else { - final Vector3 min = region.getMinimumPoint().toVector3(); - final Vector3 max = region.getMaximumPoint().toVector3(); + boolean offsetCenter, + @Switch(name = 'l', desc = "Fetch from the clipboard instead of the world") + boolean useClipboard) throws WorldEditException { + final Transform targetTransform = TransformUtil.createTransformForExpressionCommand(actor, session, region, useRawCoords, offsetPlacement, offsetCenter); - zero = max.add(min).divide(2); - unit = max.subtract(zero); + final InputExtent sourceExtent; + final Transform sourceTransform; + if (useClipboard) { + final Clipboard clipboard = session.getClipboard().getClipboard(); + sourceExtent = clipboard; - if (unit.x() == 0) { - unit = unit.withX(1.0); - } - if (unit.y() == 0) { - unit = unit.withY(1.0); - } - if (unit.z() == 0) { - unit = unit.withZ(1.0); - } + final Vector3 clipboardMin = clipboard.getMinimumPoint().toVector3(); + final Vector3 clipboardMax = clipboard.getMaximumPoint().toVector3(); + + sourceTransform = TransformUtil.createTransformForExpressionCommand(useRawCoords, offsetPlacement, offsetCenter, clipboardMin, clipboardMax, clipboardMin); + } else { + sourceExtent = editSession.getWorld(); + sourceTransform = targetTransform; } try { - final int affected = editSession.deformRegion(region, zero, unit, String.join(" ", expression), session.getTimeout()); + final int affected = editSession.deformRegion(region, targetTransform, String.join(" ", expression), session.getTimeout(), sourceExtent, sourceTransform); if (actor instanceof Player) { ((Player) actor).findFreePosition(); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/ExpressionMaskParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/ExpressionMaskParser.java index c58af7d566..09f684cf2e 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/ExpressionMaskParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/ExpressionMaskParser.java @@ -27,7 +27,7 @@ import com.sk89q.worldedit.internal.expression.Expression; import com.sk89q.worldedit.internal.expression.ExpressionException; import com.sk89q.worldedit.internal.registry.InputParser; -import com.sk89q.worldedit.math.Vector3; +import com.sk89q.worldedit.math.transform.Identity; import com.sk89q.worldedit.regions.shape.WorldEditExpressionEnvironment; import com.sk89q.worldedit.session.SessionOwner; import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; @@ -58,7 +58,7 @@ public Mask parseFromInput(String input, ParserContext context) throws InputPars try { Expression exp = Expression.compile(input.substring(1), "x", "y", "z"); WorldEditExpressionEnvironment env = new WorldEditExpressionEnvironment( - context.requireExtent(), Vector3.ONE, Vector3.ZERO); + context.requireExtent(), new Identity()); exp.setEnvironment(env); if (context.getActor() != null) { SessionOwner owner = context.getActor(); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/factory/Deform.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/factory/Deform.java index 017e8b3a3e..6136a969b2 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/factory/Deform.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/factory/Deform.java @@ -25,21 +25,28 @@ import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.extent.InputExtent; import com.sk89q.worldedit.extent.NullExtent; +import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.function.Contextual; import com.sk89q.worldedit.function.EditContext; import com.sk89q.worldedit.function.operation.Operation; import com.sk89q.worldedit.function.operation.RunContext; import com.sk89q.worldedit.internal.expression.Expression; import com.sk89q.worldedit.internal.expression.ExpressionException; +import com.sk89q.worldedit.internal.util.TransformUtil; import com.sk89q.worldedit.math.Vector3; +import com.sk89q.worldedit.math.transform.Transform; import com.sk89q.worldedit.regions.NullRegion; import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.session.ClipboardHolder; import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; import com.sk89q.worldedit.util.formatting.text.format.TextColor; +import java.util.Optional; + import static com.google.common.base.Preconditions.checkNotNull; import static com.sk89q.worldedit.util.GuavaUtil.firstNonNull; @@ -50,6 +57,7 @@ public class Deform implements Contextual { private final Expression expression; private Mode mode; private Vector3 offset = Vector3.ZERO; + private boolean useClipboard; public Deform(String expression) { this(new NullExtent(), new NullRegion(), expression); @@ -111,6 +119,14 @@ public void setOffset(Vector3 offset) { this.offset = offset; } + public boolean useClipboard() { + return useClipboard; + } + + public void setUseClipboard(boolean useClipboard) { + this.useClipboard = useClipboard; + } + @Override public String toString() { return "deformation of " + expression.getSource(); @@ -118,57 +134,51 @@ public String toString() { @Override public Operation createFromContext(final EditContext context) { - final Vector3 zero; - Vector3 unit; Region region = firstNonNull(context.getRegion(), this.region); + final Vector3 min = region.getMinimumPoint().toVector3(); + final Vector3 max = region.getMaximumPoint().toVector3(); - switch (mode) { - case UNIT_CUBE: - final Vector3 min = region.getMinimumPoint().toVector3(); - final Vector3 max = region.getMaximumPoint().toVector3(); - - zero = max.add(min).multiply(0.5); - unit = max.subtract(zero); - - if (unit.x() == 0) { - unit = unit.withX(1.0); - } - if (unit.y() == 0) { - unit = unit.withY(1.0); - } - if (unit.z() == 0) { - unit = unit.withZ(1.0); - } - break; - case RAW_COORD: - zero = Vector3.ZERO; - unit = Vector3.ONE; - break; - case OFFSET: - default: - zero = offset; - unit = Vector3.ONE; + LocalSession session = context.getSession(); + EditSession editSession = (EditSession) context.getDestination(); + final Optional clipboardOptional = Optional.ofNullable(session) + .flatMap(LocalSession::getClipboardOptional) + .map(ClipboardHolder::getClipboard); + + final Transform targetTransform = TransformUtil.createTransformForExpressionCommand(mode, min, max, offset); + final InputExtent sourceExtent; + final Transform sourceTransform; + if (useClipboard && clipboardOptional.isPresent()) { + final Clipboard clipboard = clipboardOptional.get(); + final Vector3 clipboardMin = clipboard.getMinimumPoint().toVector3(); + final Vector3 clipboardMax = clipboard.getMaximumPoint().toVector3(); + + sourceExtent = clipboard; + sourceTransform = TransformUtil.createTransformForExpressionCommand(mode, clipboardMin, clipboardMax, offset); + } else { + sourceExtent = editSession.getWorld(); + sourceTransform = targetTransform; } - LocalSession session = context.getSession(); - return new DeformOperation(context.getDestination(), region, zero, unit, expression, - session == null ? WorldEdit.getInstance().getConfiguration().calculationTimeout : session.getTimeout()); + return new DeformOperation(context.getDestination(), region, targetTransform, expression, + session == null ? WorldEdit.getInstance().getConfiguration().calculationTimeout : session.getTimeout(), sourceExtent, sourceTransform); } private record DeformOperation( Extent destination, Region region, - Vector3 zero, - Vector3 unit, + Transform targetTransform, Expression expression, - int timeout + int timeout, + InputExtent sourceExtent, + Transform sourceTransform ) implements Operation { @Override public Operation resume(RunContext run) throws WorldEditException { try { // TODO: Move deformation code - ((EditSession) destination).deformRegion(region, zero, unit, expression, timeout); + final EditSession editSession = (EditSession) destination; + editSession.deformRegion(region, targetTransform, expression, timeout, sourceExtent, sourceTransform); return null; } catch (ExpressionException e) { throw new RuntimeException("Failed to execute expression", e); // TODO: Better exception to throw here? diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/util/TransformUtil.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/util/TransformUtil.java new file mode 100644 index 0000000000..a1c1ee5151 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/util/TransformUtil.java @@ -0,0 +1,121 @@ +/* + * 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.internal.util; + +import com.sk89q.worldedit.IncompleteRegionException; +import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.function.factory.Deform; +import com.sk89q.worldedit.math.Vector3; +import com.sk89q.worldedit.math.transform.Identity; +import com.sk89q.worldedit.math.transform.ScaleAndTranslateTransform; +import com.sk89q.worldedit.math.transform.Transform; +import com.sk89q.worldedit.regions.Region; + +/** + * Various internal utility methods related to {@link Transform}s. + */ +public final class TransformUtil { + + private TransformUtil() { + } + + /** + * Creates a {@link Transform} for various expression commands. + * + * @param actor Actor that ran the command + * @param session Session that the command was run in + * @param region Selection that the command was run in + * @param useRawCoords Use the game's coordinate origin + * @param offsetPlacement Use the placement's coordinate origin + * @param offsetCenter Use the selection's center as origin + * @return A transform from the expression coordinate system to the raw coordinate system + */ + public static Transform createTransformForExpressionCommand(Actor actor, LocalSession session, Region region, boolean useRawCoords, boolean offsetPlacement, boolean offsetCenter) throws IncompleteRegionException { + final Vector3 placement = session.getPlacementPosition(actor).toVector3(); + final Vector3 min = region.getMinimumPoint().toVector3(); + final Vector3 max = region.getMaximumPoint().toVector3(); + + return createTransformForExpressionCommand(useRawCoords, offsetPlacement, offsetCenter, min, max, placement); + } + + /** + * Creates a {@link Transform} for various expression commands from raw min/max/placement values. + * + * @param useRawCoords Use the game's coordinate origin + * @param offsetPlacement Use the placement's coordinate origin + * @param offsetCenter Use the selection's center as origin + * @param min Minimum of the selection/clipboard + * @param max Maximum of the selection/clipboard + * @param placement Placement position + * @return A transform from the expression coordinate system to the world/clipboard coordinate system + */ + public static Transform createTransformForExpressionCommand(boolean useRawCoords, boolean offsetPlacement, boolean offsetCenter, Vector3 min, Vector3 max, Vector3 placement) { + if (useRawCoords) { + return new Identity(); + } + + if (offsetPlacement) { + return new ScaleAndTranslateTransform(placement, Vector3.ONE); + } + + final Vector3 center = max.add(min).multiply(0.5); + + if (offsetCenter) { + return new ScaleAndTranslateTransform(center, Vector3.ONE); + } + + Vector3 scale = max.subtract(center); + + if (scale.x() == 0) { + scale = scale.withX(1.0); + } + if (scale.y() == 0) { + scale = scale.withY(1.0); + } + if (scale.z() == 0) { + scale = scale.withZ(1.0); + } + return new ScaleAndTranslateTransform(center, scale); + } + + /** + * Creates a {@link Transform} for the //deform command with clipboard support. + * + * @param mode The coordinate mode to use + * @param min Minimum of the selection/clipboard + * @param max Maximum of the selection/clipboard + * @param placement Placement position + * @return A transform from the expression coordinate system to the world/clipboard coordinate system + */ + public static Transform createTransformForExpressionCommand(Deform.Mode mode, Vector3 min, Vector3 max, Vector3 placement) { + switch (mode) { + case UNIT_CUBE: + return createTransformForExpressionCommand(false, false, false, min, max, placement); + + case RAW_COORD: + return createTransformForExpressionCommand(true, false, false, min, max, placement); + + case OFFSET: + default: + return createTransformForExpressionCommand(false, true, false, min, max, placement); + } + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/math/transform/ScaleAndTranslateTransform.java b/worldedit-core/src/main/java/com/sk89q/worldedit/math/transform/ScaleAndTranslateTransform.java new file mode 100644 index 0000000000..93630a8688 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/math/transform/ScaleAndTranslateTransform.java @@ -0,0 +1,68 @@ +/* + * 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.math.transform; + +import com.sk89q.worldedit.math.Vector3; + +/** + * A more light-weight {@link Transform} than {@link AffineTransform}, supporting only translation and scaling. + */ +public record ScaleAndTranslateTransform(Vector3 offset, Vector3 scale) implements Transform { + + @Override + public boolean isIdentity() { + return offset.equals(Vector3.ZERO) && scale.equals(Vector3.ONE); + } + + @Override + public Vector3 apply(Vector3 input) { + return input.multiply(scale).add(offset); + } + + @Override + public Transform inverse() { + return new Transform() { + @Override + public boolean isIdentity() { + return ScaleAndTranslateTransform.this.isIdentity(); + } + + @Override + public Vector3 apply(Vector3 input) { + return input.subtract(offset).divide(scale); + } + + @Override + public Transform inverse() { + return ScaleAndTranslateTransform.this; + } + + @Override + public Transform combine(Transform other) { + return new CombinedTransform(this, other); + } + }; + } + + @Override + public Transform combine(Transform other) { + return new CombinedTransform(this, other); + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/shape/WorldEditExpressionEnvironment.java b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/shape/WorldEditExpressionEnvironment.java index 56763d3bc2..a9f785ce3a 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/shape/WorldEditExpressionEnvironment.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/shape/WorldEditExpressionEnvironment.java @@ -23,24 +23,31 @@ import com.sk89q.worldedit.internal.expression.ExpressionEnvironment; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.Vector3; +import com.sk89q.worldedit.math.transform.ScaleAndTranslateTransform; +import com.sk89q.worldedit.math.transform.Transform; import com.sk89q.worldedit.world.registry.LegacyMapper; public class WorldEditExpressionEnvironment implements ExpressionEnvironment { - private final Vector3 unit; - private final Vector3 zero2; + private final Transform transform; private Vector3 current = Vector3.ZERO; private final Extent extent; + /** + * @deprecated Use {@link EditSession#makeBiomeShape(Region, Transform, BiomeType, String, boolean, int)} and pass a {@link ScaleAndTranslateTransform}. + */ + @Deprecated public WorldEditExpressionEnvironment(Extent extent, Vector3 unit, Vector3 zero) { + this(extent, new ScaleAndTranslateTransform(zero, unit)); + } + + public WorldEditExpressionEnvironment(Extent extent, Transform transform) { this.extent = extent; - this.unit = unit; - this.zero2 = zero.add(0.5, 0.5, 0.5); + this.transform = transform; } public BlockVector3 toWorld(double x, double y, double z) { - // unscale, unoffset, round-nearest - return Vector3.at(x, y, z).multiply(unit).add(zero2).toBlockPoint(); + return transform.apply(Vector3.at(x, y, z)).add(0.5, 0.5, 0.5).toBlockPoint(); } public Vector3 toWorldRel(double x, double y, double z) {