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 e7bd59bd69..1c4699fa22 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.SimpleTransform; +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; @@ -2247,7 +2250,7 @@ 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, new SimpleTransform(zero, unit)); expression.setEnvironment(environment); final int[] timedOut = {0}; @@ -2314,7 +2317,8 @@ protected BaseBlock getMaterial(int x, int y, int z, BaseBlock defaultMaterial) */ 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); + final Transform transform = new SimpleTransform(zero, unit); + return deformRegion(region, transform, expressionString, WorldEdit.getInstance().getConfiguration().calculationTimeout, world, transform); } /** @@ -2333,11 +2337,33 @@ 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 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 SimpleTransform(zero, unit); + return deformRegion(region, transform, expressionString, timeout, world, transform); + } + + /** + * 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 outputTransform the output 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 inputExtent the InputExtent to fetch blocks from, for instance a World or a Clipboard + * @param inputTransform the input 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 outputTransform, final String expressionString, + final int timeout, final InputExtent inputExtent, final Transform inputTransform) throws ExpressionException, MaxChangedBlocksException { final Expression expression = Expression.compile(expressionString, "x", "y", "z"); expression.optimize(); - return deformRegion(region, zero, unit, expression, timeout); + return deformRegion(region, outputTransform, expression, timeout, inputExtent, inputTransform); } /** @@ -2347,8 +2373,22 @@ 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. *

*/ + @Deprecated public int deformRegion(final Region region, final Vector3 zero, final Vector3 unit, final Expression expression, final int timeout) throws ExpressionException, MaxChangedBlocksException { + final Transform transform = new SimpleTransform(zero, unit); + return deformRegion(region, transform, expression, timeout, world, transform); + } + + /** + * Internal version of {@link EditSession#deformRegion(Region, Vector3, Vector3, String, int)}. + * + *

+ * The Expression class is subject to change. Expressions should be provided via the string overload. + *

+ */ + public int deformRegion(final Region region, final Transform outputTransform, final Expression expression, + final int timeout, InputExtent inputExtent, final Transform inputTransform) throws ExpressionException, MaxChangedBlocksException { final Variable x = expression.getSlots().getVariable("x") .orElseThrow(IllegalStateException::new); final Variable y = expression.getSlots().getVariable("y") @@ -2356,23 +2396,24 @@ 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, outputTransform); expression.setEnvironment(environment); final DoubleArrayList queue = new DoubleArrayList<>(false); + final Transform outputTransformInverse = outputTransform.inverse(); for (BlockVector3 position : region) { // transform - final Vector3 scaled = position.toVector3().subtract(zero).divide(unit); + final Vector3 scaled = outputTransformInverse.apply(position.toVector3()); // deform expression.evaluate(new double[]{scaled.getX(), scaled.getY(), scaled.getZ()}, timeout); // untransform, round-nearest - final BlockVector3 sourcePosition = environment.toWorld(x.getValue(), y.getValue(), z.getValue()); + final BlockVector3 sourcePosition = inputTransform.apply(Vector3.at(x.getValue(), y.getValue(), z.getValue())).add(0.5, 0.5, 0.5).toBlockPoint(); - // read block from world - final BaseBlock material = world.getFullBlock(sourcePosition); + // read block from world/clipboard + final BaseBlock material = inputExtent.getFullBlock(sourcePosition); // queue operation queue.put(position, material); 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 38bbd93eb9..ace6d22461 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 @@ -502,7 +502,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); @@ -510,6 +512,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/RegionCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java index 2e24cb8ae1..9188421105 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 @@ -29,6 +29,8 @@ import com.sk89q.worldedit.command.util.Logging; import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.extent.InputExtent; +import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.function.GroundFunction; import com.sk89q.worldedit.function.RegionFunction; import com.sk89q.worldedit.function.block.BlockReplace; @@ -51,6 +53,9 @@ 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.Identity; +import com.sk89q.worldedit.math.transform.SimpleTransform; +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; @@ -464,6 +469,44 @@ void regenerate(Actor actor, World world, LocalSession session, EditSession edit } } + /** + * Creates a {@link Transform} for the //deform command. + * + * @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 sytem to the world/clipboard coordinate system + */ + private static Transform createTransform(boolean useRawCoords, boolean offsetPlacement, boolean offsetCenter, Vector3 min, Vector3 max, Vector3 placement) { + if (useRawCoords) { + return new Identity(); + } else if (offsetPlacement) { + return new SimpleTransform(placement, Vector3.ONE); + } else { + final Vector3 offset = max.add(min).multiply(0.5); + + if (offsetCenter) { + return new SimpleTransform(offset, Vector3.ONE); + } + + Vector3 scale = max.subtract(offset); + + if (scale.getX() == 0) { + scale = scale.withX(1.0); + } + if (scale.getY() == 0) { + scale = scale.withY(1.0); + } + if (scale.getZ() == 0) { + scale = scale.withZ(1.0); + } + return new SimpleTransform(offset, scale); + } + } + @Command( name = "/deform", desc = "Deforms a selected region with an expression", @@ -482,42 +525,33 @@ public int deform(Actor actor, LocalSession session, EditSession editSession, @Switch(name = 'o', desc = "Use the placement's coordinate origin") boolean offsetPlacement, @Switch(name = 'c', desc = "Use the selection's center as origin") - boolean offsetCenter) throws WorldEditException { - final Vector3 zero; - Vector3 unit; + boolean offsetCenter, + @Switch(name = 'l', desc = "Fetch from the clipboard instead of the world") + boolean useClipboard) throws WorldEditException { - if (useRawCoords) { - zero = Vector3.ZERO; - unit = Vector3.ONE; - } else if (offsetPlacement) { - 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(); + final Vector3 min = region.getMinimumPoint().toVector3(); + final Vector3 max = region.getMaximumPoint().toVector3(); + final Vector3 placement = session.getPlacementPosition(actor).toVector3(); - zero = max.add(min).divide(2); - unit = max.subtract(zero); + final Transform outputTransform = createTransform(useRawCoords, offsetPlacement, offsetCenter, min, max, placement); - if (unit.getX() == 0) { - unit = unit.withX(1.0); - } - if (unit.getY() == 0) { - unit = unit.withY(1.0); - } - if (unit.getZ() == 0) { - unit = unit.withZ(1.0); - } + final InputExtent inputExtent; + final Transform inputTransform; + if (useClipboard) { + final Clipboard clipboard = session.getClipboard().getClipboard(); + inputExtent = clipboard; + + final Vector3 clipboardMin = clipboard.getMinimumPoint().toVector3(); + final Vector3 clipboardMax = clipboard.getMaximumPoint().toVector3(); + + inputTransform = createTransform(useRawCoords, offsetPlacement, offsetCenter, clipboardMin, clipboardMax, clipboardMin); + } else { + inputExtent = editSession.getWorld(); + inputTransform = outputTransform; } try { - final int affected = editSession.deformRegion(region, zero, unit, String.join(" ", expression), session.getTimeout()); + final int affected = editSession.deformRegion(region, outputTransform, String.join(" ", expression), session.getTimeout(), inputExtent, inputTransform); if (actor instanceof Player) { ((Player) actor).findFreePosition(); } 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 06579cbc3d..3d4a8a602c 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,7 +25,9 @@ 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; @@ -33,6 +35,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.SimpleTransform; +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; @@ -50,6 +55,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); @@ -102,6 +108,14 @@ public void setMode(Mode mode) { this.mode = mode; } + public boolean getUseClipboard() { + return useClipboard; + } + + public void setUseClipboard(boolean useClipboard) { + this.useClipboard = useClipboard; + } + public Vector3 getOffset() { return offset; } @@ -116,20 +130,11 @@ public String toString() { return "deformation of " + expression.getSource(); } - @Override - public Operation createFromContext(final EditContext context) { - final Vector3 zero; - Vector3 unit; - - Region region = firstNonNull(context.getRegion(), this.region); - + private Transform createTransform(Vector3 min, Vector3 max, Vector3 placement) { 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.getX() == 0) { unit = unit.withX(1.0); @@ -140,45 +145,60 @@ public Operation createFromContext(final EditContext context) { if (unit.getZ() == 0) { unit = unit.withZ(1.0); } - break; + + return new SimpleTransform(zero, unit); + case RAW_COORD: - zero = Vector3.ZERO; - unit = Vector3.ONE; - break; + return new Identity(); + case OFFSET: default: - zero = offset; - unit = Vector3.ONE; + return new SimpleTransform(placement, Vector3.ONE); } + } - LocalSession session = context.getSession(); - return new DeformOperation(context.getDestination(), region, zero, unit, expression, - session == null ? WorldEdit.getInstance().getConfiguration().calculationTimeout : session.getTimeout()); - } - - private static final class DeformOperation implements Operation { - private final Extent destination; - private final Region region; - private final Vector3 zero; - private final Vector3 unit; - private final Expression expression; - private final int timeout; - - private DeformOperation(Extent destination, Region region, Vector3 zero, Vector3 unit, Expression expression, - int timeout) { - this.destination = destination; - this.region = region; - this.zero = zero; - this.unit = unit; - this.expression = expression; - this.timeout = timeout; + @Override + public Operation createFromContext(final EditContext context) { + return new DeformOperation(context); + } + + private class DeformOperation implements Operation { + private final EditContext context; + + private DeformOperation(EditContext context) { + this.context = context; } @Override public Operation resume(RunContext run) throws WorldEditException { try { + final Region region = firstNonNull(context.getRegion(), Deform.this.region); + + final Vector3 min = region.getMinimumPoint().toVector3(); + final Vector3 max = region.getMaximumPoint().toVector3(); + final Transform outputTransform = createTransform(min, max, Deform.this.offset); + + final LocalSession session = context.getSession(); + final EditSession editSession = (EditSession) context.getDestination(); + + final InputExtent inputExtent; + final Transform inputTransform; + if (Deform.this.useClipboard && session != null) { + final Clipboard clipboard = session.getClipboard().getClipboard(); + inputExtent = clipboard; + + final Vector3 clipboardMin = clipboard.getMinimumPoint().toVector3(); + final Vector3 clipboardMax = clipboard.getMaximumPoint().toVector3(); + + inputTransform = createTransform(clipboardMin, clipboardMax, clipboardMin); + } else { + inputExtent = editSession.getWorld(); + inputTransform = outputTransform; + } + final int timeout = session == null ? WorldEdit.getInstance().getConfiguration().calculationTimeout : session.getTimeout(); + // TODO: Move deformation code - ((EditSession) destination).deformRegion(region, zero, unit, expression, timeout); + editSession.deformRegion(region, outputTransform, Deform.this.expression, timeout, inputExtent, inputTransform); return null; } catch (ExpressionException e) { throw new RuntimeException("Failed to execute expression", e); // TODO: Better exception to throw here? @@ -193,7 +213,7 @@ public void cancel() { @Override public Iterable getStatusMessages() { return ImmutableList.of(TranslatableComponent.of("worldedit.operation.deform.expression", - TextComponent.of(expression.getSource()).color(TextColor.LIGHT_PURPLE))); + TextComponent.of(Deform.this.expression.getSource()).color(TextColor.LIGHT_PURPLE))); } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/math/transform/SimpleTransform.java b/worldedit-core/src/main/java/com/sk89q/worldedit/math/transform/SimpleTransform.java new file mode 100644 index 0000000000..f0db680954 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/math/transform/SimpleTransform.java @@ -0,0 +1,75 @@ +/* + * 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 class SimpleTransform implements Transform { + public final Vector3 offset; + public final Vector3 scale; + + public SimpleTransform(Vector3 offset, Vector3 scale) { + this.offset = offset; + this.scale = scale; + } + + @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 SimpleTransform.this.isIdentity(); + } + + @Override + public Vector3 apply(Vector3 input) { + return input.subtract(offset).divide(scale); + } + + @Override + public Transform inverse() { + return SimpleTransform.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 da1825259d..d2aa303bf0 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,23 +23,28 @@ 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.SimpleTransform; +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 public WorldEditExpressionEnvironment(Extent extent, Vector3 unit, Vector3 zero) { + this(extent, new SimpleTransform(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) { - 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) {