diff --git a/core/src/main/java/com/boydti/fawe/object/brush/BrushSettings.java b/core/src/main/java/com/boydti/fawe/object/brush/BrushSettings.java index a44e0879..bcdbd909 100644 --- a/core/src/main/java/com/boydti/fawe/object/brush/BrushSettings.java +++ b/core/src/main/java/com/boydti/fawe/object/brush/BrushSettings.java @@ -69,7 +69,7 @@ public class BrushSettings { CommandCallable sphereCommand = ((ProcessedCallable) brushDispatcher.get(split[0]).getCallable()).getParent(); CommandLocals locals = new CommandLocals(); locals.put(Actor.class, player); - String args = constructor.substring(constructor.indexOf(' ') + 1); + String args = constructor.replaceAll(split[0] + "[ ]?", ""); String[] parentArgs = new String[]{"brush", split[0]}; BrushSettings bs = (BrushSettings) sphereCommand.call(args, locals, parentArgs); bs.constructor.put(SettingType.BRUSH, constructor); diff --git a/core/src/main/java/com/boydti/fawe/object/brush/SplineBrush.java b/core/src/main/java/com/boydti/fawe/object/brush/SplineBrush.java index b1f596f7..238a4428 100644 --- a/core/src/main/java/com/boydti/fawe/object/brush/SplineBrush.java +++ b/core/src/main/java/com/boydti/fawe/object/brush/SplineBrush.java @@ -24,7 +24,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; -public class SplineBrush implements Brush { +public class SplineBrush implements Brush, ResettableTool { public static int MAX_POINTS = 15; private ArrayList> positionSets; @@ -40,6 +40,15 @@ public class SplineBrush implements Brush { this.positionSets = new ArrayList<>(); } + + @Override + public boolean reset() { + numSplines = 0; + positionSets.clear(); + position = null; + return true; + } + @Override public void build(EditSession editSession, final Vector position, Pattern pattern, double size) throws MaxChangedBlocksException { Mask mask = editSession.getMask(); diff --git a/core/src/main/java/com/boydti/fawe/object/brush/sweep/ClipboardSpline.java b/core/src/main/java/com/boydti/fawe/object/brush/sweep/ClipboardSpline.java new file mode 100644 index 00000000..6ce98436 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/brush/sweep/ClipboardSpline.java @@ -0,0 +1,113 @@ +package com.boydti.fawe.object.brush.sweep; + +import com.boydti.fawe.object.collection.LocalBlockVectorSet; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.function.operation.ForwardExtentCopy; +import com.sk89q.worldedit.function.operation.Operations; +import com.sk89q.worldedit.math.interpolation.Interpolation; +import com.sk89q.worldedit.math.transform.AffineTransform; +import com.sk89q.worldedit.math.transform.RoundedTransform; +import com.sk89q.worldedit.math.transform.Transform; +import com.sk89q.worldedit.session.ClipboardHolder; + +/** + * An implementation of a {@link Spline} using a Clipboard as source for the structure. + * @author Schuwi + * @version 1.0 + */ +public class ClipboardSpline extends Spline { + + private final Transform transform; + private ClipboardHolder clipboardHolder; + private Vector originalOrigin; + private Transform originalTransform; + + private Vector center; + private Vector centerOffset; + private LocalBlockVectorSet buffer; + + /** + * Constructor without position-correction. Use this constructor for an interpolation implementation which does not need position-correction. + *

+ * Be advised that currently subsequent changes to the interpolation parameters may not be supported. + * @param editSession The EditSession which will be used when pasting the clipboard content + * @param clipboardHolder The clipboard that will be pasted along the spline + * @param interpolation An implementation of the interpolation algorithm used to calculate the curve + */ + public ClipboardSpline(EditSession editSession, ClipboardHolder clipboardHolder, Interpolation interpolation) { + this(editSession, clipboardHolder, interpolation, new AffineTransform(), -1); + } + + /** + * Constructor with position-correction. Use this constructor for an interpolation implementation that needs position-correction. + *

+ * Some interpolation implementations calculate the position on the curve (used by {@link #pastePosition(double)}) + * based on an equidistant distribution of the nodes on the curve. For example: on a spline with 5 nodes position 0.0 would refer + * to the first node, 0.25 to the second, 0.5 to the third, ... .
+ * By providing this method with the amount of nodes used by the interpolation implementation the distribution of the + * nodes is converted to a proportional distribution based on the length between two adjacent nodes calculated by {@link Interpolation#arcLength(double, double)}.
+ * This means that the distance between two positions used to paste the clipboard (e.g. 0.75 - 0.5 = 0.25) on the curve + * will always amount to that part of the length (e.g. 40 units) of the curve. In this example it would amount to + * 0.25 * 40 = 10 units of curve length between these two positions. + *

+ * Be advised that currently subsequent changes to the interpolation parameters may not be supported. + * @param editSession The EditSession which will be used when pasting the clipboard content + * @param clipboardHolder The clipboard that will be pasted along the spline + * @param interpolation An implementation of the interpolation algorithm used to calculate the curve + * @param nodeCount The number of nodes provided to the interpolation object + */ + public ClipboardSpline(EditSession editSession, ClipboardHolder clipboardHolder, Interpolation interpolation, Transform transform, int nodeCount) { + super(editSession, interpolation, nodeCount); + this.clipboardHolder = clipboardHolder; + + this.originalTransform = clipboardHolder.getTransform(); + Clipboard clipboard = clipboardHolder.getClipboard(); + this.originalOrigin = clipboard.getOrigin(); + + center = clipboard.getRegion().getCenter(); + this.centerOffset = center.subtract(center.round()); + this.center = center.subtract(centerOffset); + this.transform = transform; + this.buffer = new LocalBlockVectorSet(); + } + + @Override + protected int pasteBlocks(Vector target, Vector offset, double angle) throws MaxChangedBlocksException { + RoundedTransform transform = new RoundedTransform(new AffineTransform() + .translate(offset) + .rotateY(angle)); + if (!this.transform.isIdentity()) { + transform = transform.combine(this.transform); + } + if (!originalTransform.isIdentity()) { + transform = transform.combine(originalTransform); + } + + // Pasting + Clipboard clipboard = clipboardHolder.getClipboard(); + clipboard.setOrigin(center.subtract(centerOffset).round()); + clipboardHolder.setTransform(transform); + + Vector functionOffset = target.subtract(clipboard.getOrigin()); + final int offX = functionOffset.getBlockX(); + final int offY = functionOffset.getBlockY(); + final int offZ = functionOffset.getBlockZ(); + + ForwardExtentCopy operation = clipboardHolder + .createPaste(editSession, editSession.getWorldData()) + .to(target) + .ignoreAirBlocks(true) + .filter(v -> buffer.add(v.getBlockX() + offX, v.getBlockY() + offY, v.getBlockZ() + offZ)) + .build(); + Operations.completeLegacy(operation); + + // Cleanup + clipboardHolder.setTransform(originalTransform); + clipboard.setOrigin(originalOrigin); + + return operation.getAffected(); + } +} \ No newline at end of file diff --git a/core/src/main/java/com/boydti/fawe/object/brush/sweep/Spline.java b/core/src/main/java/com/boydti/fawe/object/brush/sweep/Spline.java new file mode 100644 index 00000000..e04eb193 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/brush/sweep/Spline.java @@ -0,0 +1,193 @@ +package com.boydti.fawe.object.brush.sweep; + +import com.google.common.base.Preconditions; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.Vector2D; +import com.sk89q.worldedit.math.interpolation.Interpolation; + +import java.util.ArrayList; +import java.util.List; + +/** + * Embodies an abstract implementation for pasting structures along a spline.
+ * A curve is being interpolated by the provided {@link Interpolation} implementation + * and the structure is pasted along this curve by the specific Spline implementation. + * @author Schuwi + * @version 1.0 + */ +public abstract class Spline { + + private Vector2D direction = new Vector2D(1, 0); + private final int nodeCount; + + protected EditSession editSession; + private Interpolation interpolation; + + private List

sections; + private double splineLength; + + /** + * Constructor without position-correction. Use this constructor for an interpolation implementation which does not need position-correction. + *

+ * Be advised that currently subsequent changes to the interpolation parameters may not be supported. + * @param editSession The EditSession which will be used when pasting the structure + * @param interpolation An implementation of the interpolation algorithm used to calculate the curve + */ + protected Spline(EditSession editSession, Interpolation interpolation) { + this(editSession, interpolation, -1); + } + + /** + * Constructor with position-correction. Use this constructor for an interpolation implementation that needs position-correction. + *

+ * Some interpolation implementations calculate the position on the curve (used by {@link #pastePosition(double)}) + * based on an equidistant distribution of the nodes on the curve. For example: on a spline with 5 nodes position 0.0 would refer + * to the first node, 0.25 to the second, 0.5 to the third, ... .
+ * By providing this method with the amount of nodes used by the interpolation implementation the distribution of the + * nodes is converted to a proportional distribution based on the length between two adjacent nodes calculated by {@link Interpolation#arcLength(double, double)}.
+ * This means that the distance between two positions used to paste the clipboard (e.g. 0.75 - 0.5 = 0.25) on the curve + * will always amount to that part of the length (e.g. 40 units) of the curve. In this example it would amount to + * 0.25 * 40 = 10 units of curve length between these two positions. + *

+ * Be advised that currently subsequent changes to the interpolation parameters may not be supported. + * @param editSession The EditSession which will be used when pasting the structure + * @param interpolation An implementation of the interpolation algorithm used to calculate the curve + * @param nodeCount The number of nodes provided to the interpolation object + */ + protected Spline(EditSession editSession, Interpolation interpolation, int nodeCount) { + this.editSession = editSession; + this.interpolation = interpolation; + this.nodeCount = nodeCount; + + this.splineLength = interpolation.arcLength(0D, 1D); + if (nodeCount > 2) { + initSections(); + } + } + + /** + * Set the forward direction of the structure.
+ * This direction is used to determine the rotation of the clipboard to align to the curve. The horizontal slope + * of the curve for a specific point is calculated by {@link Interpolation#get1stDerivative(double)}. + * Subsequently this angle between this vector and the gradient vector is calculated and the clipboard content + * is rotated by that angle to follow the curve slope. + *

+ * The default direction is a (1;0) vector (pointing in the positive x-direction). + * @param direction A normalized vector representing the horizontal forward direction of the clipboard content + */ + public void setDirection(Vector2D direction) { + this.direction = direction; + } + + /** + * Get the forward direction of the structure.
+ * This direction is used to determine the rotation of the clipboard to align to the curve. The horizontal slope + * of the curve for a specific point is calculated by {@link Interpolation#get1stDerivative(double)}. + * Subsequently this angle between this vector and the gradient vector is calculated and the clipboard content + * is rotated by that angle to follow the curve slope. + *

+ * The default direction is a (1;0) vector (pointing in the positive x-direction). + * @return A vector representing the horizontal forward direction of the clipboard content + */ + public Vector2D getDirection() { + return direction; + } + + /** + * Paste the structure at the provided position on the curve. The position will be position-corrected if the + * nodeCount provided to the constructor is bigger than 2. + * @param position The position on the curve. Must be between 0.0 and 1.0 (both inclusive) + * @return The amount of blocks that have been changed + * @throws MaxChangedBlocksException Thrown by WorldEdit if the limit of block changes for the {@link EditSession} has been reached + */ + public int pastePosition(double position) throws MaxChangedBlocksException { + Preconditions.checkArgument(position >= 0); + Preconditions.checkArgument(position <= 1); + + if (nodeCount > 2) { + return pastePositionDirect(translatePosition(position)); + } else { + return pastePositionDirect(position); + } + } + + /** + * Paste structure at the provided position on the curve. The position will not be position-corrected + * but will be passed directly to the interpolation algorithm. + * @param position The position on the curve. Must be between 0.0 and 1.0 (both inclusive) + * @return The amount of blocks that have been changed + * @throws MaxChangedBlocksException Thrown by WorldEdit if the limit of block changes for the {@link EditSession} has been reached + */ + public int pastePositionDirect(double position) throws MaxChangedBlocksException { + Preconditions.checkArgument(position >= 0); + Preconditions.checkArgument(position <= 1); + + // Calculate position from spline + Vector target = interpolation.getPosition(position); + Vector offset = target.subtract(target.round()); + target = target.subtract(offset); + + // Calculate rotation from spline + + Vector deriv = interpolation.get1stDerivative(position); + Vector2D deriv2D = new Vector2D(deriv.getX(), deriv.getZ()).normalize(); + double angle = Math.toDegrees( + Math.atan2(direction.getZ(), direction.getX()) - Math.atan2(deriv2D.getZ(), deriv2D.getX()) + ); + + return pasteBlocks(target, offset, angle); + } + + protected abstract int pasteBlocks(Vector target, Vector offset, double angle) throws MaxChangedBlocksException; + + private void initSections() { + int sectionCount = nodeCount - 1; + sections = new ArrayList<>(sectionCount); + double sectionLength = 1D / sectionCount; + + double position = 0; + for (int i = 0; i < sectionCount; i++) { + double length; + if (i == sectionCount - 1) { // maybe unnecessary precaution + length = interpolation.arcLength(i * sectionLength, 1D) / splineLength; + } else { + length = interpolation.arcLength(i * sectionLength, (i + 1) * sectionLength) / splineLength; + } + sections.add(new Section(i * sectionLength, sectionLength, position, length)); + position += length; + } + } + + private double translatePosition(double flexPosition) { + Section previousSection = sections.get(0); // start with first section + for (int i = 1; i < sections.size(); i++) { + Section section = sections.get(i); + if (flexPosition < section.flexStart) { + // break if the desired position is to the left of the current section -> the previous section contained the position + break; + } + previousSection = section; + } + + double flexOffset = flexPosition - previousSection.flexStart; + double uniOffset = flexOffset / previousSection.flexLength * previousSection.uniLength; + + return previousSection.uniStart + uniOffset; + } + + private class Section { + final double uniStart; + final double uniLength; + final double flexStart; + final double flexLength; + + Section(double uniStart, double uniLength, double flexStart, double flexLength) { + this.uniStart = uniStart; + this.uniLength = uniLength; + this.flexStart = flexStart; + this.flexLength = flexLength; + } + } +} \ No newline at end of file diff --git a/core/src/main/java/com/boydti/fawe/object/brush/sweep/SweepBrush.java b/core/src/main/java/com/boydti/fawe/object/brush/sweep/SweepBrush.java new file mode 100644 index 00000000..85faa05c --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/brush/sweep/SweepBrush.java @@ -0,0 +1,128 @@ +package com.boydti.fawe.object.brush.sweep; + +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.object.brush.ResettableTool; +import com.boydti.fawe.object.brush.visualization.VisualExtent; +import com.boydti.fawe.util.MathMan; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.EmptyClipboardException; +import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.command.tool.brush.Brush; +import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.function.pattern.Pattern; +import com.sk89q.worldedit.math.interpolation.Interpolation; +import com.sk89q.worldedit.math.interpolation.KochanekBartelsInterpolation; +import com.sk89q.worldedit.math.interpolation.Node; +import com.sk89q.worldedit.math.transform.AffineTransform; +import com.sk89q.worldedit.session.ClipboardHolder; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class SweepBrush implements Brush, ResettableTool { + private List positions; + private Vector position; + private int copies; + + private static final double tension = 0D; + private static final double bias = 0D; + private static final double continuity = 0D; + + public SweepBrush(int copies) { + this.positions = new ArrayList<>(); + this.copies = copies > 0 ? copies : -1; + } + + @Override + public void build(EditSession editSession, Vector position, Pattern pattern, double size) throws MaxChangedBlocksException { + boolean visualization = editSession.getExtent() instanceof VisualExtent; + if (visualization && positions.isEmpty()) { + return; + } + + boolean newPos = this.position == null || !position.equals(this.position); + this.position = position; + if (newPos) { + positions.add(position.add(0, 1, 0)); + return; + } + + FawePlayer player = editSession.getPlayer(); + if (positions.size() < 2) { + BBC.BRUSH_SPLINE_SECONDARY_ERROR.send(player); + return; + } + + Interpolation interpol = new KochanekBartelsInterpolation(); + List nodes = positions.stream().map(v -> { + Node n = new Node(v); + n.setTension(tension); + n.setBias(bias); + n.setContinuity(continuity); + return n; + }).collect(Collectors.toList()); + interpol.setNodes(nodes); + + LocalSession session = player.getSession(); + ClipboardHolder holder = session.getExistingClipboard(); + if (holder == null) { + throw new RuntimeException(new EmptyClipboardException()); + } + Clipboard clipboard = holder.getClipboard(); + + Vector dimensions = clipboard.getDimensions(); + AffineTransform transform = new AffineTransform(); + if (dimensions.getBlockX() > dimensions.getBlockZ()) { + transform = transform.rotateY(90); + } + double quality = Math.max(dimensions.getBlockX(), dimensions.getBlockZ()); + + ClipboardSpline spline = new ClipboardSpline(editSession, holder, interpol, transform, nodes.size()); + + switch (copies) { + case 1: { + spline.pastePosition(0D); + break; + } + case -1: { + double splineLength = interpol.arcLength(0D, 1D); + double blockDistance = 1d / splineLength; + double step = blockDistance / quality; + double accumulation = 0; + Vector last = null; + for (double pos = 0D; pos <= 1D; pos += step) { + Vector gradient = interpol.get1stDerivative(pos); + if (last == null) last = new Vector(interpol.get1stDerivative(pos)); + double dist = MathMan.sqrtApprox(last.distanceSq(gradient)); + last.mutX(gradient.getX()); + last.mutY(gradient.getY()); + last.mutZ(gradient.getZ()); + double change = dist * step; + // Accumulation is arbitrary, but much faster than calculation overlapping regions + if ((accumulation += change + step * 2) > blockDistance) { + accumulation -= blockDistance; + spline.pastePosition(pos); + } + } + break; + } + default: { + for (double pos = 0D; pos <= 1D; pos += 1D / (copies - 1)) { + spline.pastePosition(pos); + } + break; + } + } + reset(); + } + + @Override + public boolean reset() { + positions.clear(); + position = null; + return true; + } +} diff --git a/core/src/main/java/com/boydti/fawe/object/clipboard/DiskOptimizedClipboard.java b/core/src/main/java/com/boydti/fawe/object/clipboard/DiskOptimizedClipboard.java index c29da98d..2cfc344f 100644 --- a/core/src/main/java/com/boydti/fawe/object/clipboard/DiskOptimizedClipboard.java +++ b/core/src/main/java/com/boydti/fawe/object/clipboard/DiskOptimizedClipboard.java @@ -441,6 +441,7 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable { } } return block; + } catch (IndexOutOfBoundsException ignore) { } catch (Exception e) { MainUtil.handleError(e); } @@ -479,6 +480,7 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable { } } return block; + } catch (IndexOutOfBoundsException ignore) { } catch (Exception e) { MainUtil.handleError(e); } diff --git a/core/src/main/java/com/sk89q/worldedit/MutableBlockVector.java b/core/src/main/java/com/sk89q/worldedit/MutableBlockVector.java index c48b18b9..afb08708 100644 --- a/core/src/main/java/com/sk89q/worldedit/MutableBlockVector.java +++ b/core/src/main/java/com/sk89q/worldedit/MutableBlockVector.java @@ -1,5 +1,6 @@ package com.sk89q.worldedit; +import com.boydti.fawe.util.MathMan; import java.io.IOException; import java.io.Serializable; @@ -51,17 +52,17 @@ public class MutableBlockVector extends BlockVector implements Serializable { @Override public final void mutX(double x) { - this.x = (int) x; + this.x = MathMan.roundInt(x); } @Override public final void mutY(double y) { - this.y = (int) y; + this.y = MathMan.roundInt(y); } @Override public final void mutZ(double z) { - this.z = (int) z; + this.z = MathMan.roundInt(z); } @Override diff --git a/core/src/main/java/com/sk89q/worldedit/Vector.java b/core/src/main/java/com/sk89q/worldedit/Vector.java index 514a4d50..6c69e83f 100644 --- a/core/src/main/java/com/sk89q/worldedit/Vector.java +++ b/core/src/main/java/com/sk89q/worldedit/Vector.java @@ -19,6 +19,7 @@ package com.sk89q.worldedit; +import com.boydti.fawe.util.MathMan; import com.sk89q.worldedit.math.transform.AffineTransform; import java.io.IOException; import java.io.Serializable; @@ -157,7 +158,7 @@ public class Vector implements Comparable, Serializable { * @return the x coordinate */ public int getBlockX() { - return (int) getX(); + return MathMan.roundInt(getX()); } /** @@ -195,7 +196,7 @@ public class Vector implements Comparable, Serializable { * @return the y coordinate */ public int getBlockY() { - return (int) (getY()); + return MathMan.roundInt(getY()); } /** @@ -233,7 +234,7 @@ public class Vector implements Comparable, Serializable { * @return the z coordinate */ public int getBlockZ() { - return (int) (getZ()); + return MathMan.roundInt(getZ()); } /** diff --git a/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java b/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java index 5aa82e04..b2efe8b4 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java @@ -50,6 +50,7 @@ import com.boydti.fawe.object.brush.StencilBrush; import com.boydti.fawe.object.brush.SurfaceSphereBrush; import com.boydti.fawe.object.brush.SurfaceSpline; import com.boydti.fawe.object.brush.heightmap.ScalableHeightMap; +import com.boydti.fawe.object.brush.sweep.SweepBrush; import com.boydti.fawe.object.mask.IdMask; import com.boydti.fawe.util.ColorUtil; import com.boydti.fawe.util.MathMan; @@ -249,6 +250,22 @@ public class BrushCommands extends MethodCommands { .setFill(fill); } + // Adapted from: https://github.com/Rafessor/VaeronTools + @Command( + aliases = {"sweep", "sw", "vaesweep"}, + usage = "[copies=-1]", + desc = "Sweep your clipboard content along a curve", + help = "Sweeps your clipboard content along a curve.\n" + + "Define a curve by selecting the individual points with a brush\n" + + "Set [copies] to a value > 0 if you want to have your selection pasted a limited amount of times equally spaced on the curve", + max = 1 + ) + @CommandPermissions("worldedit.brush.sweep") + public BrushSettings sweepBrush(Player player, LocalSession session, EditSession editSession, @Optional("-1") int copies, CommandContext context) throws WorldEditException { + player.print(BBC.getPrefix() + BBC.BRUSH_SPLINE.s()); + return get(context).setBrush(new SweepBrush(copies)); + } + @Command( aliases = {"catenary", "cat", "gravityline", "saggedline"}, usage = " [lengthFactor=1.2] [size=0]", diff --git a/core/src/main/java/com/sk89q/worldedit/function/operation/BackwardsExtentBlockCopy.java b/core/src/main/java/com/sk89q/worldedit/function/operation/BackwardsExtentBlockCopy.java index 16bf2aec..fccfbdd8 100644 --- a/core/src/main/java/com/sk89q/worldedit/function/operation/BackwardsExtentBlockCopy.java +++ b/core/src/main/java/com/sk89q/worldedit/function/operation/BackwardsExtentBlockCopy.java @@ -45,8 +45,8 @@ public class BackwardsExtentBlockCopy implements Operation { private CuboidRegion transform(Transform transform, Region region) { Vector min = new MutableBlockVector(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE); Vector max = new MutableBlockVector(Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE); - Vector pos1 = region.getMinimumPoint(); - Vector pos2 = region.getMaximumPoint(); + Vector pos1 = region.getMinimumPoint().subtract(1, 1, 1); + Vector pos2 = region.getMaximumPoint().add(1, 1, 1); for (int x : new int[] { pos1.getBlockX(), pos2.getBlockX() }) { for (int y : new int[] { pos1.getBlockY(), pos2.getBlockY() }) { for (int z : new int[] { pos1.getBlockZ(), pos2.getBlockZ() }) { diff --git a/core/src/main/java/com/sk89q/worldedit/function/operation/ForwardExtentCopy.java b/core/src/main/java/com/sk89q/worldedit/function/operation/ForwardExtentCopy.java index 9a8e6108..02560d84 100644 --- a/core/src/main/java/com/sk89q/worldedit/function/operation/ForwardExtentCopy.java +++ b/core/src/main/java/com/sk89q/worldedit/function/operation/ForwardExtentCopy.java @@ -39,6 +39,7 @@ import com.sk89q.worldedit.function.entity.ExtentEntityCopy; import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.mask.Masks; import com.sk89q.worldedit.function.visitor.EntityVisitor; +import com.sk89q.worldedit.function.visitor.IntersectRegionFunction; import com.sk89q.worldedit.function.visitor.RegionVisitor; import com.sk89q.worldedit.math.transform.AffineTransform; import com.sk89q.worldedit.math.transform.Identity; @@ -74,6 +75,7 @@ public class ForwardExtentCopy implements Operation { private int affected; private boolean copyEntities = true; private boolean copyBiomes = false; + private RegionFunction filterFunction; /** * Create a new copy using the region's lowest minimum point as the @@ -171,6 +173,10 @@ public class ForwardExtentCopy implements Operation { this.sourceMask = sourceMask; } + public void setFilterFunction(RegionFunction filterFunction) { + this.filterFunction = filterFunction; + } + /** * Get the function that gets applied to all source blocks after * the copy has been made. @@ -266,6 +272,9 @@ public class ForwardExtentCopy implements Operation { transExt.setOrigin(from); RegionFunction copy = new SimpleBlockCopy(transExt, finalDest); + if (this.filterFunction != null) { + copy = new IntersectRegionFunction(filterFunction, copy); + } if (sourceMask != Masks.alwaysTrue()) { new MaskTraverser(sourceMask).reset(transExt); copy = new RegionMaskingFilter(sourceMask, copy); @@ -286,6 +295,9 @@ public class ForwardExtentCopy implements Operation { if (blockCopy == null) { RegionFunction copy = new SimpleBlockCopy(source, finalDest); + if (this.filterFunction != null) { + copy = new IntersectRegionFunction(filterFunction, copy); + } if (sourceMask != Masks.alwaysTrue()) { copy = new RegionMaskingFilter(sourceMask, copy); } diff --git a/core/src/main/java/com/sk89q/worldedit/function/visitor/IntersectRegionFunction.java b/core/src/main/java/com/sk89q/worldedit/function/visitor/IntersectRegionFunction.java new file mode 100644 index 00000000..b08c4a22 --- /dev/null +++ b/core/src/main/java/com/sk89q/worldedit/function/visitor/IntersectRegionFunction.java @@ -0,0 +1,27 @@ +package com.sk89q.worldedit.function.visitor; + +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.function.RegionFunction; + +public class IntersectRegionFunction implements RegionFunction { + private final RegionFunction[] functions; + + public IntersectRegionFunction(RegionFunction... functions) { + this.functions = functions; + } + + + @Override + public boolean apply(Vector position) throws WorldEditException { + boolean ret = false; + for (RegionFunction function : functions) { + if (!function.apply(position)) { + return ret; + } else { + ret = true; + } + } + return ret; + } +} diff --git a/core/src/main/java/com/sk89q/worldedit/math/interpolation/KochanekBartelsInterpolation.java b/core/src/main/java/com/sk89q/worldedit/math/interpolation/KochanekBartelsInterpolation.java index f0f4692d..b8e9e42e 100644 --- a/core/src/main/java/com/sk89q/worldedit/math/interpolation/KochanekBartelsInterpolation.java +++ b/core/src/main/java/com/sk89q/worldedit/math/interpolation/KochanekBartelsInterpolation.java @@ -21,7 +21,6 @@ package com.sk89q.worldedit.math.interpolation; -import com.sk89q.worldedit.MutableBlockVector; import com.sk89q.worldedit.Vector; import java.util.Collections; import java.util.List; @@ -137,7 +136,7 @@ public class KochanekBartelsInterpolation implements Interpolation { return nodes.get(index).getPosition(); } - private MutableBlockVector mutable = new MutableBlockVector(); + private Vector mutable = new Vector(); @Override public Vector getPosition(double position) { diff --git a/core/src/main/java/com/sk89q/worldedit/math/transform/RoundedTransform.java b/core/src/main/java/com/sk89q/worldedit/math/transform/RoundedTransform.java new file mode 100644 index 00000000..08ebbbb4 --- /dev/null +++ b/core/src/main/java/com/sk89q/worldedit/math/transform/RoundedTransform.java @@ -0,0 +1,41 @@ +package com.sk89q.worldedit.math.transform; + +import com.sk89q.worldedit.MutableBlockVector; +import com.sk89q.worldedit.Vector; + +public class RoundedTransform implements Transform{ + private final Transform transform; + private MutableBlockVector mutable = new MutableBlockVector(); + + public RoundedTransform(Transform transform) { + this.transform = transform; + } + + @Override + public boolean isIdentity() { + return transform.isIdentity(); + } + + @Override + public Vector apply(Vector input) { + Vector val = transform.apply(input); + mutable.mutX((int) Math.floor(val.getX() + 0.5)); + mutable.mutY((int) Math.floor(val.getY() + 0.5)); + mutable.mutZ((int) Math.floor(val.getZ() + 0.5)); + return mutable; + } + + @Override + public RoundedTransform inverse() { + return new RoundedTransform(transform.inverse()); + } + + @Override + public RoundedTransform combine(Transform other) { + return new RoundedTransform(transform.combine(other)); + } + + public Transform getTransform() { + return transform; + } +} diff --git a/core/src/main/java/com/sk89q/worldedit/session/PasteBuilder.java b/core/src/main/java/com/sk89q/worldedit/session/PasteBuilder.java index 993ef3af..06d9b7cc 100644 --- a/core/src/main/java/com/sk89q/worldedit/session/PasteBuilder.java +++ b/core/src/main/java/com/sk89q/worldedit/session/PasteBuilder.java @@ -26,10 +26,10 @@ import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.extent.transform.BlockTransformExtent; +import com.sk89q.worldedit.function.RegionFunction; import com.sk89q.worldedit.function.mask.ExistingBlockMask; import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.operation.ForwardExtentCopy; -import com.sk89q.worldedit.function.operation.Operation; import com.sk89q.worldedit.math.transform.Transform; import com.sk89q.worldedit.world.registry.WorldData; @@ -51,6 +51,7 @@ public class PasteBuilder { private boolean ignoreAirBlocks; private boolean ignoreBiomes; private boolean ignoreEntities; + private RegionFunction canApply; /** * Create a new instance. @@ -101,12 +102,17 @@ public class PasteBuilder { return this; } + public PasteBuilder filter(RegionFunction function) { + this.canApply = function; + return this; + } + /** * Build the operation. * * @return the operation */ - public Operation build() { + public ForwardExtentCopy build() { Extent extent = clipboard; if (!transform.isIdentity()) { extent = new BlockTransformExtent(extent, transform, targetWorldData.getBlockRegistry()); @@ -115,6 +121,9 @@ public class PasteBuilder { copy.setTransform(transform); copy.setCopyEntities(!ignoreEntities); copy.setCopyBiomes((!ignoreBiomes) && (!(clipboard instanceof BlockArrayClipboard) || ((BlockArrayClipboard) clipboard).IMP.hasBiomes())); + if (this.canApply != null) { + copy.setFilterFunction(this.canApply); + } if (targetExtent instanceof EditSession) { Mask sourceMask = ((EditSession) targetExtent).getSourceMask(); if (sourceMask != null) {