Add sweep brush
Adapted from: https://github.com/Rafessor/VaeronTools Credit @Schuwi todo: Schematic pasting needs to be optimized for lots of small pastes, as this is kinda slow.
This commit is contained in:
parent
27152ef8ac
commit
2649824761
@ -69,7 +69,7 @@ public class BrushSettings {
|
|||||||
CommandCallable sphereCommand = ((ProcessedCallable) brushDispatcher.get(split[0]).getCallable()).getParent();
|
CommandCallable sphereCommand = ((ProcessedCallable) brushDispatcher.get(split[0]).getCallable()).getParent();
|
||||||
CommandLocals locals = new CommandLocals();
|
CommandLocals locals = new CommandLocals();
|
||||||
locals.put(Actor.class, player);
|
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]};
|
String[] parentArgs = new String[]{"brush", split[0]};
|
||||||
BrushSettings bs = (BrushSettings) sphereCommand.call(args, locals, parentArgs);
|
BrushSettings bs = (BrushSettings) sphereCommand.call(args, locals, parentArgs);
|
||||||
bs.constructor.put(SettingType.BRUSH, constructor);
|
bs.constructor.put(SettingType.BRUSH, constructor);
|
||||||
|
@ -24,7 +24,7 @@ import java.util.Collections;
|
|||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class SplineBrush implements Brush {
|
public class SplineBrush implements Brush, ResettableTool {
|
||||||
|
|
||||||
public static int MAX_POINTS = 15;
|
public static int MAX_POINTS = 15;
|
||||||
private ArrayList<ArrayList<Vector>> positionSets;
|
private ArrayList<ArrayList<Vector>> positionSets;
|
||||||
@ -40,6 +40,15 @@ public class SplineBrush implements Brush {
|
|||||||
this.positionSets = new ArrayList<>();
|
this.positionSets = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean reset() {
|
||||||
|
numSplines = 0;
|
||||||
|
positionSets.clear();
|
||||||
|
position = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void build(EditSession editSession, final Vector position, Pattern pattern, double size) throws MaxChangedBlocksException {
|
public void build(EditSession editSession, final Vector position, Pattern pattern, double size) throws MaxChangedBlocksException {
|
||||||
Mask mask = editSession.getMask();
|
Mask mask = editSession.getMask();
|
||||||
|
@ -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.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
* <p>
|
||||||
|
* 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, ... .<br>
|
||||||
|
* 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)}.<br>
|
||||||
|
* 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.
|
||||||
|
* <p>
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
}
|
@ -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.<br>
|
||||||
|
* 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<Section> sections;
|
||||||
|
private double splineLength;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor without position-correction. Use this constructor for an interpolation implementation which does not need position-correction.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
* <p>
|
||||||
|
* 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, ... .<br>
|
||||||
|
* 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)}.<br>
|
||||||
|
* 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.
|
||||||
|
* <p>
|
||||||
|
* 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.<br>
|
||||||
|
* 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.
|
||||||
|
* <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
|
||||||
|
*/
|
||||||
|
public void setDirection(Vector2D direction) {
|
||||||
|
this.direction = direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the forward direction of the structure.<br>
|
||||||
|
* 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.
|
||||||
|
* <p>
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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<Vector> 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<Node> 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;
|
||||||
|
}
|
||||||
|
}
|
@ -441,6 +441,7 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return block;
|
return block;
|
||||||
|
} catch (IndexOutOfBoundsException ignore) {
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
MainUtil.handleError(e);
|
MainUtil.handleError(e);
|
||||||
}
|
}
|
||||||
@ -479,6 +480,7 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return block;
|
return block;
|
||||||
|
} catch (IndexOutOfBoundsException ignore) {
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
MainUtil.handleError(e);
|
MainUtil.handleError(e);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.sk89q.worldedit;
|
package com.sk89q.worldedit;
|
||||||
|
|
||||||
|
import com.boydti.fawe.util.MathMan;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
@ -51,17 +52,17 @@ public class MutableBlockVector extends BlockVector implements Serializable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void mutX(double x) {
|
public final void mutX(double x) {
|
||||||
this.x = (int) x;
|
this.x = MathMan.roundInt(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void mutY(double y) {
|
public final void mutY(double y) {
|
||||||
this.y = (int) y;
|
this.y = MathMan.roundInt(y);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void mutZ(double z) {
|
public final void mutZ(double z) {
|
||||||
this.z = (int) z;
|
this.z = MathMan.roundInt(z);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
package com.sk89q.worldedit;
|
package com.sk89q.worldedit;
|
||||||
|
|
||||||
|
import com.boydti.fawe.util.MathMan;
|
||||||
import com.sk89q.worldedit.math.transform.AffineTransform;
|
import com.sk89q.worldedit.math.transform.AffineTransform;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
@ -157,7 +158,7 @@ public class Vector implements Comparable<Vector>, Serializable {
|
|||||||
* @return the x coordinate
|
* @return the x coordinate
|
||||||
*/
|
*/
|
||||||
public int getBlockX() {
|
public int getBlockX() {
|
||||||
return (int) getX();
|
return MathMan.roundInt(getX());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -195,7 +196,7 @@ public class Vector implements Comparable<Vector>, Serializable {
|
|||||||
* @return the y coordinate
|
* @return the y coordinate
|
||||||
*/
|
*/
|
||||||
public int getBlockY() {
|
public int getBlockY() {
|
||||||
return (int) (getY());
|
return MathMan.roundInt(getY());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -233,7 +234,7 @@ public class Vector implements Comparable<Vector>, Serializable {
|
|||||||
* @return the z coordinate
|
* @return the z coordinate
|
||||||
*/
|
*/
|
||||||
public int getBlockZ() {
|
public int getBlockZ() {
|
||||||
return (int) (getZ());
|
return MathMan.roundInt(getZ());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -50,6 +50,7 @@ import com.boydti.fawe.object.brush.StencilBrush;
|
|||||||
import com.boydti.fawe.object.brush.SurfaceSphereBrush;
|
import com.boydti.fawe.object.brush.SurfaceSphereBrush;
|
||||||
import com.boydti.fawe.object.brush.SurfaceSpline;
|
import com.boydti.fawe.object.brush.SurfaceSpline;
|
||||||
import com.boydti.fawe.object.brush.heightmap.ScalableHeightMap;
|
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.object.mask.IdMask;
|
||||||
import com.boydti.fawe.util.ColorUtil;
|
import com.boydti.fawe.util.ColorUtil;
|
||||||
import com.boydti.fawe.util.MathMan;
|
import com.boydti.fawe.util.MathMan;
|
||||||
@ -249,6 +250,22 @@ public class BrushCommands extends MethodCommands {
|
|||||||
.setFill(fill);
|
.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(
|
@Command(
|
||||||
aliases = {"catenary", "cat", "gravityline", "saggedline"},
|
aliases = {"catenary", "cat", "gravityline", "saggedline"},
|
||||||
usage = "<pattern> [lengthFactor=1.2] [size=0]",
|
usage = "<pattern> [lengthFactor=1.2] [size=0]",
|
||||||
|
@ -45,8 +45,8 @@ public class BackwardsExtentBlockCopy implements Operation {
|
|||||||
private CuboidRegion transform(Transform transform, Region region) {
|
private CuboidRegion transform(Transform transform, Region region) {
|
||||||
Vector min = new MutableBlockVector(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
|
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 max = new MutableBlockVector(Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE);
|
||||||
Vector pos1 = region.getMinimumPoint();
|
Vector pos1 = region.getMinimumPoint().subtract(1, 1, 1);
|
||||||
Vector pos2 = region.getMaximumPoint();
|
Vector pos2 = region.getMaximumPoint().add(1, 1, 1);
|
||||||
for (int x : new int[] { pos1.getBlockX(), pos2.getBlockX() }) {
|
for (int x : new int[] { pos1.getBlockX(), pos2.getBlockX() }) {
|
||||||
for (int y : new int[] { pos1.getBlockY(), pos2.getBlockY() }) {
|
for (int y : new int[] { pos1.getBlockY(), pos2.getBlockY() }) {
|
||||||
for (int z : new int[] { pos1.getBlockZ(), pos2.getBlockZ() }) {
|
for (int z : new int[] { pos1.getBlockZ(), pos2.getBlockZ() }) {
|
||||||
|
@ -39,6 +39,7 @@ import com.sk89q.worldedit.function.entity.ExtentEntityCopy;
|
|||||||
import com.sk89q.worldedit.function.mask.Mask;
|
import com.sk89q.worldedit.function.mask.Mask;
|
||||||
import com.sk89q.worldedit.function.mask.Masks;
|
import com.sk89q.worldedit.function.mask.Masks;
|
||||||
import com.sk89q.worldedit.function.visitor.EntityVisitor;
|
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.function.visitor.RegionVisitor;
|
||||||
import com.sk89q.worldedit.math.transform.AffineTransform;
|
import com.sk89q.worldedit.math.transform.AffineTransform;
|
||||||
import com.sk89q.worldedit.math.transform.Identity;
|
import com.sk89q.worldedit.math.transform.Identity;
|
||||||
@ -74,6 +75,7 @@ public class ForwardExtentCopy implements Operation {
|
|||||||
private int affected;
|
private int affected;
|
||||||
private boolean copyEntities = true;
|
private boolean copyEntities = true;
|
||||||
private boolean copyBiomes = false;
|
private boolean copyBiomes = false;
|
||||||
|
private RegionFunction filterFunction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new copy using the region's lowest minimum point as the
|
* 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;
|
this.sourceMask = sourceMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setFilterFunction(RegionFunction filterFunction) {
|
||||||
|
this.filterFunction = filterFunction;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the function that gets applied to all source blocks <em>after</em>
|
* Get the function that gets applied to all source blocks <em>after</em>
|
||||||
* the copy has been made.
|
* the copy has been made.
|
||||||
@ -266,6 +272,9 @@ public class ForwardExtentCopy implements Operation {
|
|||||||
transExt.setOrigin(from);
|
transExt.setOrigin(from);
|
||||||
|
|
||||||
RegionFunction copy = new SimpleBlockCopy(transExt, finalDest);
|
RegionFunction copy = new SimpleBlockCopy(transExt, finalDest);
|
||||||
|
if (this.filterFunction != null) {
|
||||||
|
copy = new IntersectRegionFunction(filterFunction, copy);
|
||||||
|
}
|
||||||
if (sourceMask != Masks.alwaysTrue()) {
|
if (sourceMask != Masks.alwaysTrue()) {
|
||||||
new MaskTraverser(sourceMask).reset(transExt);
|
new MaskTraverser(sourceMask).reset(transExt);
|
||||||
copy = new RegionMaskingFilter(sourceMask, copy);
|
copy = new RegionMaskingFilter(sourceMask, copy);
|
||||||
@ -286,6 +295,9 @@ public class ForwardExtentCopy implements Operation {
|
|||||||
|
|
||||||
if (blockCopy == null) {
|
if (blockCopy == null) {
|
||||||
RegionFunction copy = new SimpleBlockCopy(source, finalDest);
|
RegionFunction copy = new SimpleBlockCopy(source, finalDest);
|
||||||
|
if (this.filterFunction != null) {
|
||||||
|
copy = new IntersectRegionFunction(filterFunction, copy);
|
||||||
|
}
|
||||||
if (sourceMask != Masks.alwaysTrue()) {
|
if (sourceMask != Masks.alwaysTrue()) {
|
||||||
copy = new RegionMaskingFilter(sourceMask, copy);
|
copy = new RegionMaskingFilter(sourceMask, copy);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -21,7 +21,6 @@
|
|||||||
|
|
||||||
package com.sk89q.worldedit.math.interpolation;
|
package com.sk89q.worldedit.math.interpolation;
|
||||||
|
|
||||||
import com.sk89q.worldedit.MutableBlockVector;
|
|
||||||
import com.sk89q.worldedit.Vector;
|
import com.sk89q.worldedit.Vector;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -137,7 +136,7 @@ public class KochanekBartelsInterpolation implements Interpolation {
|
|||||||
return nodes.get(index).getPosition();
|
return nodes.get(index).getPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
private MutableBlockVector mutable = new MutableBlockVector();
|
private Vector mutable = new Vector();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Vector getPosition(double position) {
|
public Vector getPosition(double position) {
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -26,10 +26,10 @@ import com.sk89q.worldedit.extent.Extent;
|
|||||||
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
|
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
|
||||||
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||||
import com.sk89q.worldedit.extent.transform.BlockTransformExtent;
|
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.ExistingBlockMask;
|
||||||
import com.sk89q.worldedit.function.mask.Mask;
|
import com.sk89q.worldedit.function.mask.Mask;
|
||||||
import com.sk89q.worldedit.function.operation.ForwardExtentCopy;
|
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.math.transform.Transform;
|
||||||
import com.sk89q.worldedit.world.registry.WorldData;
|
import com.sk89q.worldedit.world.registry.WorldData;
|
||||||
|
|
||||||
@ -51,6 +51,7 @@ public class PasteBuilder {
|
|||||||
private boolean ignoreAirBlocks;
|
private boolean ignoreAirBlocks;
|
||||||
private boolean ignoreBiomes;
|
private boolean ignoreBiomes;
|
||||||
private boolean ignoreEntities;
|
private boolean ignoreEntities;
|
||||||
|
private RegionFunction canApply;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new instance.
|
* Create a new instance.
|
||||||
@ -101,12 +102,17 @@ public class PasteBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PasteBuilder filter(RegionFunction function) {
|
||||||
|
this.canApply = function;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build the operation.
|
* Build the operation.
|
||||||
*
|
*
|
||||||
* @return the operation
|
* @return the operation
|
||||||
*/
|
*/
|
||||||
public Operation build() {
|
public ForwardExtentCopy build() {
|
||||||
Extent extent = clipboard;
|
Extent extent = clipboard;
|
||||||
if (!transform.isIdentity()) {
|
if (!transform.isIdentity()) {
|
||||||
extent = new BlockTransformExtent(extent, transform, targetWorldData.getBlockRegistry());
|
extent = new BlockTransformExtent(extent, transform, targetWorldData.getBlockRegistry());
|
||||||
@ -115,6 +121,9 @@ public class PasteBuilder {
|
|||||||
copy.setTransform(transform);
|
copy.setTransform(transform);
|
||||||
copy.setCopyEntities(!ignoreEntities);
|
copy.setCopyEntities(!ignoreEntities);
|
||||||
copy.setCopyBiomes((!ignoreBiomes) && (!(clipboard instanceof BlockArrayClipboard) || ((BlockArrayClipboard) clipboard).IMP.hasBiomes()));
|
copy.setCopyBiomes((!ignoreBiomes) && (!(clipboard instanceof BlockArrayClipboard) || ((BlockArrayClipboard) clipboard).IMP.hasBiomes()));
|
||||||
|
if (this.canApply != null) {
|
||||||
|
copy.setFilterFunction(this.canApply);
|
||||||
|
}
|
||||||
if (targetExtent instanceof EditSession) {
|
if (targetExtent instanceof EditSession) {
|
||||||
Mask sourceMask = ((EditSession) targetExtent).getSourceMask();
|
Mask sourceMask = ((EditSession) targetExtent).getSourceMask();
|
||||||
if (sourceMask != null) {
|
if (sourceMask != null) {
|
||||||
|
Loading…
Reference in New Issue
Block a user