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:
Jesse Boyd 2017-09-05 01:13:33 +10:00
parent 27152ef8ac
commit 2649824761
No known key found for this signature in database
GPG Key ID: 59F1DE6293AF6E1F
15 changed files with 566 additions and 14 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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]",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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