Merge pull request #568 from HazelTheWitch/fix-spline-sweep

Fixed NullPointerException in Spline and Sweep brush
This commit is contained in:
NotMyFault 2020-08-21 18:25:09 +02:00 committed by GitHub
commit 75cbae8261
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 61 additions and 43 deletions

View File

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

View File

@ -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));

View File

@ -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.
* <p>
* 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))
);
return pasteBlocks(target, offset, angle);
angle = ((angle % 360) + 360) % 360; // Wrap to 360 degrees
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 {

View File

@ -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<Node> nodes = positions.stream().map(v -> {
Node n = new Node(v.toVector3());
n.setTension(tension);
@ -82,38 +85,27 @@ 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;
double length = interpol.arcLength(0, 1);
double step = 1 / (length * quality);
for (double pos = 0; pos <= 1; pos += step) {
spline.pastePosition(pos);
}
}
break;
}
default: {

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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;
}