diff --git a/worldedit-core/src/main/java/com/boydti/fawe/object/brush/SplineBrush.java b/worldedit-core/src/main/java/com/boydti/fawe/object/brush/SplineBrush.java index aa1151639..9e750d140 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/object/brush/SplineBrush.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/object/brush/SplineBrush.java @@ -59,7 +59,7 @@ public class SplineBrush implements Brush, ResettableTool { return; } int originalSize = numSplines; - boolean newPos = this.position == null || !position.equals(this.position); + boolean newPos = !position.equals(this.position); this.position = position; if (newPos) { if (positionSets.size() >= MAX_POINTS) { diff --git a/worldedit-core/src/main/java/com/boydti/fawe/object/brush/sweep/ClipboardSpline.java b/worldedit-core/src/main/java/com/boydti/fawe/object/brush/sweep/ClipboardSpline.java index cfe5f99f9..849ba060e 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/object/brush/sweep/ClipboardSpline.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/object/brush/sweep/ClipboardSpline.java @@ -8,6 +8,7 @@ import com.sk89q.worldedit.function.operation.ForwardExtentCopy; import com.sk89q.worldedit.function.operation.Operation; import com.sk89q.worldedit.function.operation.Operations; import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.math.Vector3; import com.sk89q.worldedit.math.interpolation.Interpolation; import com.sk89q.worldedit.math.transform.AffineTransform; import com.sk89q.worldedit.math.transform.RoundedTransform; @@ -80,7 +81,7 @@ public class ClipboardSpline extends Spline { } @Override - protected int pasteBlocks(BlockVector3 target, BlockVector3 offset, double angle) throws MaxChangedBlocksException { + protected int pasteBlocks(BlockVector3 target, Vector3 offset, double angle) throws MaxChangedBlocksException { RoundedTransform transform = new RoundedTransform(new AffineTransform() .translate(offset) .rotateY(angle)); diff --git a/worldedit-core/src/main/java/com/boydti/fawe/object/brush/sweep/Spline.java b/worldedit-core/src/main/java/com/boydti/fawe/object/brush/sweep/Spline.java index b7f2eafe4..1f4da3cb7 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/object/brush/sweep/Spline.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/object/brush/sweep/Spline.java @@ -21,7 +21,7 @@ import java.util.List; */ public abstract class Spline { - private BlockVector2 direction = BlockVector2.at(1, 0); + private Vector2 direction = Vector2.at(1, 0); private final int nodeCount; protected EditSession editSession; @@ -77,10 +77,10 @@ public abstract class Spline { * 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 + * @param direction A vector representing the horizontal forward direction of the clipboard content */ - public void setDirection(BlockVector2 direction) { - this.direction = direction; + public void setDirection(Vector2 direction) { + this.direction = direction.normalize(); } /** @@ -93,7 +93,7 @@ public abstract class Spline { * 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 BlockVector2 getDirection() { + public Vector2 getDirection() { return direction; } @@ -115,6 +115,13 @@ public abstract class Spline { } } + /** + * 2 dimensional "cross" product. cross2D(v1, v2) = |v1|*|v2|*sin(theta) or v1 X v2 taking Y to be 0 + */ + private double cross2D(Vector2 v1, Vector2 v2) { + return v1.getX() * v2.getZ() - v2.getX() * v1.getZ(); + } + /** * 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. @@ -127,22 +134,24 @@ public abstract class Spline { Preconditions.checkArgument(position <= 1); // Calculate position from spline - BlockVector3 target = interpolation.getPosition(position).toBlockPoint(); - BlockVector3 offset = target.subtract(target.round()); - target = target.subtract(offset); + Vector3 target = interpolation.getPosition(position); + BlockVector3 blockTarget = target.toBlockPoint(); + Vector3 offset = target.subtract(target.floor()); // Calculate rotation from spline Vector3 deriv = interpolation.get1stDerivative(position); Vector2 deriv2D = Vector2.at(deriv.getX(), deriv.getZ()).normalize(); double angle = Math.toDegrees( - Math.atan2(direction.getZ(), direction.getX()) - Math.atan2(deriv2D.getZ(), deriv2D.getX()) + -Math.atan2(cross2D(direction, deriv2D), direction.dot(deriv2D)) ); + + angle = ((angle % 360) + 360) % 360; // Wrap to 360 degrees - return pasteBlocks(target, offset, angle); + return pasteBlocks(blockTarget, offset, angle); } - protected abstract int pasteBlocks(BlockVector3 target, BlockVector3 offset, double angle) throws MaxChangedBlocksException; + protected abstract int pasteBlocks(BlockVector3 target, Vector3 offset, double angle) throws MaxChangedBlocksException; private void initSections() { int sectionCount = nodeCount - 1; @@ -176,7 +185,15 @@ public abstract class Spline { double flexOffset = flexPosition - previousSection.flexStart; double uniOffset = flexOffset / previousSection.flexLength * previousSection.uniLength; - return previousSection.uniStart + uniOffset; + double finalPosition = previousSection.uniStart + uniOffset; + + //Really rough fix, but fixes a bug with no visual artifacts so it's probably ok? + //flexPosition very close to 1 causes outputs very slightly higher than 1 on rare occasions + if (finalPosition > 1) { + return 1; + } + + return finalPosition; } private class Section { diff --git a/worldedit-core/src/main/java/com/boydti/fawe/object/brush/sweep/SweepBrush.java b/worldedit-core/src/main/java/com/boydti/fawe/object/brush/sweep/SweepBrush.java index e2d05508f..429c7b9a9 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/object/brush/sweep/SweepBrush.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/object/brush/sweep/SweepBrush.java @@ -13,11 +13,14 @@ import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.MutableVector3; +import com.sk89q.worldedit.math.Vector2; import com.sk89q.worldedit.math.Vector3; 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.interpolation.ReparametrisingInterpolation; import com.sk89q.worldedit.math.transform.AffineTransform; +import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.session.ClipboardHolder; import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; @@ -64,7 +67,7 @@ public class SweepBrush implements Brush, ResettableTool { return; } - Interpolation interpol = new KochanekBartelsInterpolation(); + Interpolation interpol = new ReparametrisingInterpolation(new KochanekBartelsInterpolation()); List nodes = positions.stream().map(v -> { Node n = new Node(v.toVector3()); n.setTension(tension); @@ -82,37 +85,26 @@ public class SweepBrush implements Brush, ResettableTool { Clipboard clipboard = holder.getClipboard(); BlockVector3 dimensions = clipboard.getDimensions(); - AffineTransform transform = new AffineTransform(); - if (dimensions.getBlockX() > dimensions.getBlockZ()) { - transform = transform.rotateY(90); - } double quality = Math.max(dimensions.getBlockX(), dimensions.getBlockZ()); + AffineTransform transform = new AffineTransform(); + ClipboardSpline spline = new ClipboardSpline(editSession, holder, interpol, transform, nodes.size()); + if (dimensions.getBlockX() > dimensions.getBlockZ()) { + spline.setDirection(Vector2.at(0, 1)); + } + 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; - MutableVector3 last = new MutableVector3(0, 0, 0); - for (double pos = 0D; pos <= 1D; pos += step) { - Vector3 gradient = 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); - } + double length = interpol.arcLength(0, 1); + double step = 1 / (length * quality); + for (double pos = 0; pos <= 1; pos += step) { + spline.pastePosition(pos); } break; } diff --git a/worldedit-core/src/main/java/com/boydti/fawe/object/visitor/DFSVisitor.java b/worldedit-core/src/main/java/com/boydti/fawe/object/visitor/DFSVisitor.java index 152e0ef4e..5d6b9d05d 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/object/visitor/DFSVisitor.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/object/visitor/DFSVisitor.java @@ -200,6 +200,10 @@ public abstract class DFSVisitor implements Operation { @Override public boolean equals(Object obj) { + if (obj == null) { + return false; + } + Node other = (Node) obj; return other.x == x && other.z == z && other.y == y; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/math/BlockVector3.java b/worldedit-core/src/main/java/com/sk89q/worldedit/math/BlockVector3.java index 6656bf67a..49cd531c5 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/math/BlockVector3.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/math/BlockVector3.java @@ -775,6 +775,10 @@ public abstract class BlockVector3 { } public final boolean equals(BlockVector3 other) { + if (other == null) { + return false; + } + return other.getX() == this.getX() && other.getY() == this.getY() && other.getZ() == this.getZ(); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/math/interpolation/KochanekBartelsInterpolation.java b/worldedit-core/src/main/java/com/sk89q/worldedit/math/interpolation/KochanekBartelsInterpolation.java index b4ae79cea..3024e8a2f 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/math/interpolation/KochanekBartelsInterpolation.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/math/interpolation/KochanekBartelsInterpolation.java @@ -22,6 +22,7 @@ package com.sk89q.worldedit.math.interpolation; import com.sk89q.worldedit.math.MutableBlockVector3; +import com.sk89q.worldedit.math.MutableVector3; import com.sk89q.worldedit.math.Vector3; import java.util.Collections; @@ -43,7 +44,7 @@ public class KochanekBartelsInterpolation implements Interpolation { private Vector3[] coeffC; private Vector3[] coeffD; private double scaling; - private final MutableBlockVector3 mutable = new MutableBlockVector3(); + private final MutableVector3 mutable = new MutableVector3(); public KochanekBartelsInterpolation() { setNodes(Collections.emptyList()); @@ -166,7 +167,7 @@ public class KochanekBartelsInterpolation implements Interpolation { mutable.mutX((a.getX() * r3 + b.getX() * r2 + c.getX() * remainder + d.getX())); mutable.mutY((a.getY() * r3 + b.getY() * r2 + c.getY() * remainder + d.getY())); mutable.mutZ((a.getZ() * r3 + b.getZ() * r2 + c.getZ() * remainder + d.getZ())); - return mutable.toVector3(); + return Vector3.at(mutable.getX(), mutable.getY(), mutable.getZ()); } @Override diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/math/transform/RoundedTransform.java b/worldedit-core/src/main/java/com/sk89q/worldedit/math/transform/RoundedTransform.java index 0679f9ebe..8bdca82e8 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/math/transform/RoundedTransform.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/math/transform/RoundedTransform.java @@ -17,8 +17,7 @@ public class RoundedTransform implements Transform { @Override public Vector3 apply(Vector3 input) { Vector3 val = transform.apply(input); - return Vector3.at(Math.floor(val.getX() + 0.5), Math.floor(val.getY() + 0.5), Math - .floor(val.getY() + 0.5)); + return val.round(); } @Override diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/CuboidRegionSelector.java b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/CuboidRegionSelector.java index 07123d8d8..20759cd1a 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/CuboidRegionSelector.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/CuboidRegionSelector.java @@ -128,7 +128,7 @@ public class CuboidRegionSelector implements RegionSelector, CUIRegion { public boolean selectPrimary(BlockVector3 position, SelectorLimits limits) { checkNotNull(position); - if (position1 != null && position.equals(position1)) { + if (position.equals(position1)) { return false; } @@ -141,7 +141,7 @@ public class CuboidRegionSelector implements RegionSelector, CUIRegion { public boolean selectSecondary(BlockVector3 position, SelectorLimits limits) { checkNotNull(position); - if (position2 != null && position.equals(position2)) { + if (position.equals(position2)) { return false; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/ExtendingCuboidRegionSelector.java b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/ExtendingCuboidRegionSelector.java index a7f760a14..82cd540d3 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/ExtendingCuboidRegionSelector.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/ExtendingCuboidRegionSelector.java @@ -87,7 +87,7 @@ public class ExtendingCuboidRegionSelector extends CuboidRegionSelector { @Override public boolean selectPrimary(BlockVector3 position, SelectorLimits limits) { - if (position1 != null && position2 != null && position.equals(position1) && position.equals(position2)) { + if (position.equals(position1) && position.equals(position2)) { return false; }