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) {