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..9169a6231e 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,56 @@ 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 zero the coordinate origin for x/y/z variables + * @param unit the scale of the x/y/z/ variables + * @param pattern the default material to make the shape from + * @param expression 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 + * @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 Expression expression, final boolean hollow, final int timeout) + throws ExpressionException, MaxChangedBlocksException { + return makeShape(region, new ScaleAndTranslateTransform(zero, unit), pattern, expression, 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 +2394,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 +2410,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 +2432,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,7 +2476,9 @@ 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); @@ -2448,7 +2499,9 @@ 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 Expression expression = Expression.compile(expressionString, "x", "y", "z"); @@ -2456,6 +2509,50 @@ public int deformRegion(final Region region, final Vector3 zero, final Vector3 u return deformRegion(region, zero, unit, expression, 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, 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 zero the coordinate origin for x/y/z variables + * @param unit the scale of the x/y/z/ variables + * @param expression 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 Vector3 zero, final Vector3 unit, final Expression expression, + final int timeout) throws ExpressionException, MaxChangedBlocksException { + var transform = new ScaleAndTranslateTransform(zero, unit); + return deformRegion(region, transform, expression, timeout, world, transform); + } + /** * Internal version of {@link EditSession#deformRegion(Region, Vector3, Vector3, String, int)}. * @@ -2463,8 +2560,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 +2569,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 +2598,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 +2895,61 @@ private void recurseHollow(Region region, BlockVector3 origin, Set } } + /** + * Generate a biome shape for the given expression. + * + * @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); } + /** + * Generate a biome shape for the given expression. + * + * @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 maximum time for the expression to evaluate for each block. -1 for unlimited. + * @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/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..6ae469b4c1 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 @@ -33,6 +33,9 @@ import com.sk89q.worldedit.internal.expression.Expression; import com.sk89q.worldedit.internal.expression.ExpressionException; 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.NullRegion; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.util.formatting.text.Component; @@ -118,18 +121,16 @@ public String toString() { @Override public Operation createFromContext(final EditContext context) { - final Vector3 zero; - Vector3 unit; Region region = firstNonNull(context.getRegion(), this.region); + final Transform transform; 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); + final Vector3 zero = max.add(min).multiply(0.5); + Vector3 unit = max.subtract(zero); if (unit.x() == 0) { unit = unit.withX(1.0); @@ -140,27 +141,28 @@ public Operation createFromContext(final EditContext context) { if (unit.z() == 0) { unit = unit.withZ(1.0); } + + transform = new ScaleAndTranslateTransform(zero, unit); break; + case RAW_COORD: - zero = Vector3.ZERO; - unit = Vector3.ONE; + transform = new Identity(); break; + case OFFSET: default: - zero = offset; - unit = Vector3.ONE; + transform = new ScaleAndTranslateTransform(offset, Vector3.ONE); + break; } - LocalSession session = context.getSession(); - return new DeformOperation(context.getDestination(), region, zero, unit, expression, + return new DeformOperation(context.getDestination(), region, transform, expression, session == null ? WorldEdit.getInstance().getConfiguration().calculationTimeout : session.getTimeout()); } private record DeformOperation( Extent destination, Region region, - Vector3 zero, - Vector3 unit, + Transform transform, Expression expression, int timeout ) implements Operation { @@ -168,7 +170,8 @@ private record DeformOperation( 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, transform, expression, timeout, editSession.getWorld(), transform); 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..c58c8a501f --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/util/TransformUtil.java @@ -0,0 +1,97 @@ +/* + * 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.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); + } +} 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..27926d3da1 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,33 @@ 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; + /** + * Creates a new WorldEditExpressionEnvironment. + * + * @deprecated Use {@link #WorldEditExpressionEnvironment(Extent, Transform)} 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) {