Not finished yet (spline brush)

This commit is contained in:
Jesse Boyd 2016-09-28 03:14:05 +10:00
parent 42a3203777
commit 5b96a52e99
8 changed files with 501 additions and 17 deletions

View File

@ -92,6 +92,8 @@ public enum BBC {
BRUSH_HEIGHT_INVALID("Invalid height map file (%s0)", "WorldEdit.Brush"),
BRUSH_SMOOTH("Smooth brush equipped (%s0 x %s1 using %s2).", "WorldEdit.Brush"),
BRUSH_SPHERE("Sphere brush shape equipped (%s0).", "WorldEdit.Brush"),
BRUSH_LINE("Line brush shape equipped (%s0).", "WorldEdit.Brush"),
BRUSH_SPLINE("Line brush shape equipped (%s0). Right click to select points, left click to execute.", "WorldEdit.Brush"),
BRUSH_BLEND_BALL("Blend ball brush equipped (%s0).", "WorldEdit.Brush"),
BRUSH_ERODE("Erode brush equipped (%s0).", "WorldEdit.Brush"),
BRUSH_PASTE_NONE("Nothing to paste", "WorldEdit.Brush"),

View File

@ -1,5 +1,7 @@
package com.boydti.fawe.object.brush;
import com.boydti.fawe.object.mask.RadiusMask;
import com.boydti.fawe.object.visitor.DFSRecursiveVisitor;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.Vector;
@ -16,9 +18,11 @@ import com.sk89q.worldedit.function.visitor.RecursiveVisitor;
public class RecurseBrush implements Brush {
private final BrushTool tool;
private final boolean dfs;
public RecurseBrush(BrushTool tool) {
public RecurseBrush(BrushTool tool, boolean dfs) {
this.tool = tool;
this.dfs = dfs;
}
@Override
@ -34,8 +38,28 @@ public class RecurseBrush implements Brush {
}
final BlockReplace replace = new BlockReplace(editSession, to);
editSession.setMask((Mask) null);
RecursiveVisitor visitor = new RecursiveVisitor(mask, replace, radius);
visitor.visit(position);
Operations.completeBlindly(visitor);
final int maxY = editSession.getMaxY();
if (dfs) {
final Mask radMask = new RadiusMask(0, (int) size);
DFSRecursiveVisitor visitor = new DFSRecursiveVisitor(mask, replace, Integer.MAX_VALUE, Integer.MAX_VALUE) {
@Override
public boolean isVisitable(Vector from, Vector to) {
int y = to.getBlockY();
return y >= y && y < maxY && radMask.test(to) && super.isVisitable(from, to);
}
};
visitor.visit(position);
Operations.completeBlindly(visitor);
} else {
RecursiveVisitor visitor = new RecursiveVisitor(mask, replace, radius) {
@Override
public boolean isVisitable(Vector from, Vector to) {
int y = to.getBlockY();
return y >= y && y < maxY && super.isVisitable(from, to);
}
};
visitor.visit(position);
Operations.completeBlindly(visitor);
}
}
}

View File

@ -0,0 +1,187 @@
package com.boydti.fawe.object.brush;
import com.boydti.fawe.config.BBC;
import com.boydti.fawe.object.exception.FaweException;
import com.boydti.fawe.object.mask.IdMask;
import com.boydti.fawe.object.visitor.DFSRecursiveVisitor;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.function.RegionFunction;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.mask.MaskIntersection;
import com.sk89q.worldedit.function.operation.Operations;
import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.function.pattern.Patterns;
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 java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class SplineBrush implements DoubleActionBrush {
public static int MAX_POINTS = 15;
private ArrayList<ArrayList<Vector>> positionSets;
private int numSplines;
private final DoubleActionBrushTool tool;
private final LocalSession session;
private final Player player;
public SplineBrush(Player player, LocalSession session, DoubleActionBrushTool tool) {
this.tool = tool;
this.session = session;
this.player = player;
this.positionSets = new ArrayList<>();
}
@Override
public void build(DoubleActionBrushTool.BrushAction action, EditSession editSession, final Vector position, Pattern pattern, double size) throws MaxChangedBlocksException {
Mask mask = tool.getMask();
if (mask == null) {
mask = new IdMask(editSession);
} else {
mask = new MaskIntersection(mask, new IdMask(editSession));
}
switch (action) {
case PRIMARY: { // Right
if (positionSets.size() >= MAX_POINTS) {
throw new FaweException(BBC.WORLDEDIT_CANCEL_REASON_MAX_CHECKS);
}
final ArrayList<Vector> points = new ArrayList<>();
DFSRecursiveVisitor visitor = new DFSRecursiveVisitor(mask, new RegionFunction() {
@Override
public boolean apply(Vector p) throws WorldEditException {
points.add(new Vector(p));
return true;
}
}, (int) size, 1);
visitor.visit(position);
Operations.completeBlindly(visitor);
if (points.size() > numSplines) {
numSplines = points.size();
}
this.positionSets.add(points);
player.print("Added position, right click to spline them together!");
break;
}
case SECONDARY: {
if (positionSets.size() < 2) {
player.print("Not enough positions set!");
return;
}
List<Vector> centroids = new ArrayList<>();
for (List<Vector> points : positionSets) {
centroids.add(getCentroid(points));
}
double tension = 0;
double bias = 0;
double continuity = 0;
double quality = 10;
final List<Node> nodes = new ArrayList<Node>(centroids.size());
final Interpolation interpol = new KochanekBartelsInterpolation();
for (final Vector nodevector : centroids) {
final Node n = new Node(nodevector);
n.setTension(tension);
n.setBias(bias);
n.setContinuity(continuity);
nodes.add(n);
}
Vector up = new Vector(0, 1, 0);
AffineTransform transform = new AffineTransform();
// TODO index offset based on transform
int samples = numSplines;
for (int i = 0; i < numSplines; i++) {
List<Vector> currentSpline = new ArrayList<>();
for (ArrayList<Vector> points : positionSets) {
int listSize = points.size();
int index = (int) (i * listSize / (double) (numSplines));
currentSpline.add(points.get(index));
}
editSession.drawSpline(Patterns.wrap(pattern), currentSpline, 0, 0, 0, 10, 0, true);
}
player.print("Created spline");
positionSets.clear();
numSplines = 0;
break;
}
}
}
private Vector getCentroid(Collection<Vector> points) {
Vector sum = new Vector();
for (Vector p : points) {
sum.x += p.x;
sum.y += p.y;
sum.z += p.z;
}
return sum.multiply(1.0 / points.size());
}
private Vector normal(Collection<Vector> points, Vector centroid) {
int n = points.size();
switch (n) {
case 1: {
return null;
}
case 2: {
return null;
}
}
// Calc full 3x3 covariance matrix, excluding symmetries:
double xx = 0.0; double xy = 0.0; double xz = 0.0;
double yy = 0.0; double yz = 0.0; double zz = 0.0;
Vector r = new Vector();
for (Vector p : points) {
r.x = p.x - centroid.x;
r.y = p.y - centroid.y;
r.z = p.z - centroid.z;
xx += r.x * r.x;
xy += r.x * r.y;
xz += r.x * r.z;
yy += r.y * r.y;
yz += r.y * r.z;
zz += r.z * r.z;
}
double det_x = yy*zz - yz*yz;
double det_y = xx*zz - xz*xz;
double det_z = xx*yy - xy*xy;
double det_max = Math.max(Math.max(det_x, det_y), det_z);
if (det_max <= 0.0) {
return null;
}
// Pick path with best conditioning:
Vector dir;
if (det_max == det_x) {
double a = (xz*yz - xy*zz) / det_x;
double b = (xy*yz - xz*yy) / det_x;
dir = new Vector(1.0, a, b);
} else if (det_max == det_y) {
double a = (yz*xz - xy*zz) / det_y;
double b = (xy*xz - yz*xx) / det_y;
dir = new Vector(a, 1.0, b);
} else {
double a = (yz*xy - xz*yy) / det_z;
double b = (xz*xy - yz*xx) / det_z;
dir = new Vector(a, b, 1.0);
};
return dir.normalize();
}
}

View File

@ -0,0 +1,43 @@
package com.boydti.fawe.object.visitor;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.function.RegionFunction;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.visitor.RecursiveVisitor;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* An implementation of an {@link com.sk89q.worldedit.function.visitor.BreadthFirstSearch} that uses a mask to
* determine where a block should be visited.
*/
public class DFSRecursiveVisitor extends DFSVisitor {
private final Mask mask;
public DFSRecursiveVisitor(final Mask mask, final RegionFunction function) {
this(mask, function, Integer.MAX_VALUE, Integer.MAX_VALUE);
}
/**
* Create a new recursive visitor.
*
* @param mask the mask
* @param function the function
*/
public DFSRecursiveVisitor(final Mask mask, final RegionFunction function, int maxDepth, int maxBranching) {
super(function, maxDepth, maxBranching);
checkNotNull(mask);
this.mask = mask;
}
@Override
public boolean isVisitable(final Vector from, final Vector to) {
return this.mask.test(to);
}
public static Class<?> inject() {
return RecursiveVisitor.class;
}
}

View File

@ -0,0 +1,211 @@
package com.boydti.fawe.object.visitor;
import com.boydti.fawe.config.BBC;
import com.boydti.fawe.object.IntegerTrio;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.function.RegionFunction;
import com.sk89q.worldedit.function.operation.Operation;
import com.sk89q.worldedit.function.operation.RunContext;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
public abstract class DFSVisitor implements Operation {
private final RegionFunction function;
private final List<Vector> directions = new ArrayList<>();
private final Map<Node, AtomicInteger> visited;
private final ArrayDeque<NodePair> queue;
private final HashSet<Node> hashQueue;
private final int maxDepth;
private final int maxBranch;
private int affected = 0;
public DFSVisitor(final RegionFunction function) {
this(function, Integer.MAX_VALUE, Integer.MAX_VALUE);
}
public DFSVisitor(final RegionFunction function, int maxDepth, int maxBranching) {
this.queue = new ArrayDeque<>();
this.hashQueue = new LinkedHashSet<>();
this.visited = new LinkedHashMap<>();
this.function = function;
this.directions.add(new Vector(0, -1, 0));
this.directions.add(new Vector(0, 1, 0));
this.directions.add(new Vector(-1, 0, 0));
this.directions.add(new Vector(1, 0, 0));
this.directions.add(new Vector(0, 0, -1));
this.directions.add(new Vector(0, 0, 1));
this.maxDepth = maxDepth;
this.maxBranch = maxBranching;
}
public abstract boolean isVisitable(Vector from, Vector to);
public Collection<Vector> getDirections() {
return this.directions;
}
private IntegerTrio[] getIntDirections() {
IntegerTrio[] array = new IntegerTrio[directions.size()];
for (int i = 0; i < array.length; i++) {
Vector dir = directions.get(i);
array[i] = new IntegerTrio(dir.getBlockX(), dir.getBlockY(), dir.getBlockZ());
}
return array;
}
public void visit(final Vector pos) {
Node node = new Node((int) pos.x, (int) pos.y, (int) pos.z);
if (!this.hashQueue.contains(node)) {
isVisitable(pos, pos); // Ignore this, just to initialize mask on this point
queue.addFirst(new NodePair(null, node, 0));
hashQueue.add(node);
}
}
@Override
public Operation resume(RunContext run) throws WorldEditException {
NodePair current;
Node from;
Node adjacent;
Vector mutable = new Vector();
Vector mutable2 = new Vector();
int countAdd,countAttempt;
IntegerTrio[] dirs = getIntDirections();
for (int layer = 0; !queue.isEmpty(); layer++) {
current = queue.poll();
from = current.to;
hashQueue.remove(from);
if (visited.containsKey(from)) {
continue;
}
mutable.x = from.getX();
mutable.y = from.getY();
mutable.z = from.getZ();
function.apply(mutable);
countAdd = 0;
countAttempt = 0;
for (IntegerTrio direction : dirs) {
mutable2.x = from.getX() + direction.x;
mutable2.y = from.getY() + direction.y;
mutable2.z = from.getZ() + direction.z;
if (isVisitable(mutable, mutable2)) {
adjacent = new Node(mutable2.getBlockX(), mutable2.getBlockY(), mutable2.getBlockZ());
if ((current.from == null || !adjacent.equals(current.from))) {
AtomicInteger adjacentCount = visited.get(adjacent);
if (adjacentCount == null) {
if (countAdd++ < maxBranch) {
if (!hashQueue.contains(adjacent)) {
if (current.depth == maxDepth) {
countAttempt++;
} else {
hashQueue.add(adjacent);
queue.addFirst(new NodePair(from, adjacent, current.depth + 1));
}
} else {
countAttempt++;
}
} else {
countAttempt++;
}
} else if (adjacentCount.decrementAndGet() == 0) {
visited.remove(adjacent);
} else if (hashQueue.contains(adjacent)) {
countAttempt++;
}
}
}
}
if (countAttempt > 0) {
visited.put(from, new AtomicInteger(countAttempt));
}
affected++;
}
return null;
}
@Override
public void cancel() {
}
@Override
public void addStatusMessages(List<String> messages) {
messages.add(BBC.VISITOR_BLOCK.format(getAffected()));
}
public int getAffected() {
return this.affected;
}
public class NodePair {
public final Node to;
public final Node from;
private final int depth;
public NodePair(Node from, Node to, int depth) {
this.from = from;
this.to = to;
this.depth = depth;
}
}
public static final class Node {
private int x,y,z;
public Node(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
private final void set(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
private final void set(Node node) {
this.x = node.x;
this.y = node.y;
this.z = node.z;
}
@Override
public final int hashCode() {
return (x ^ (z << 12)) ^ (y << 24);
}
private final int getX() {
return x;
}
private final int getY() {
return y;
}
private final int getZ() {
return z;
}
@Override
public String toString() {
return x + "," + y + "," + z;
}
@Override
public boolean equals(Object obj) {
Node other = (Node) obj;
return other.x == x && other.z == z && other.y == y;
}
}
}

View File

@ -102,7 +102,6 @@ import com.sk89q.worldedit.history.changeset.ChangeSet;
import com.sk89q.worldedit.internal.expression.Expression;
import com.sk89q.worldedit.internal.expression.ExpressionException;
import com.sk89q.worldedit.internal.expression.runtime.RValue;
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.noise.RandomNoise;
@ -2684,7 +2683,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue {
Set<Vector> vset = new HashSet<Vector>();
final List<Node> nodes = new ArrayList<Node>(nodevectors.size());
final Interpolation interpol = new KochanekBartelsInterpolation();
final KochanekBartelsInterpolation interpol = new KochanekBartelsInterpolation();
for (final Vector nodevector : nodevectors) {
final Node n = new Node(nodevector);
@ -2701,7 +2700,6 @@ public class EditSession extends AbstractWorld implements HasFaweQueue {
final int tipx = (int) Math.round(tipv.getX());
final int tipy = (int) Math.round(tipv.getY());
final int tipz = (int) Math.round(tipv.getZ());
vset.add(new Vector(tipx, tipy, tipz));
}

View File

@ -24,14 +24,15 @@ import com.boydti.fawe.config.BBC;
import com.boydti.fawe.config.Settings;
import com.boydti.fawe.object.FaweLimit;
import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.brush.BlendBall;
import com.boydti.fawe.object.brush.CommandBrush;
import com.boydti.fawe.object.brush.CopyPastaBrush;
import com.boydti.fawe.object.brush.HeightBrush;
import com.boydti.fawe.object.brush.BlendBall;
import com.boydti.fawe.object.brush.DoubleActionBrushTool;
import com.boydti.fawe.object.brush.ErodeBrush;
import com.boydti.fawe.object.brush.HeightBrush;
import com.boydti.fawe.object.brush.LineBrush;
import com.boydti.fawe.object.brush.RecurseBrush;
import com.boydti.fawe.object.brush.SplineBrush;
import com.boydti.fawe.object.mask.IdMask;
import com.sk89q.minecraft.util.commands.Command;
import com.sk89q.minecraft.util.commands.CommandContext;
@ -123,16 +124,17 @@ public class BrushCommands {
aliases = { "recursive", "recurse", "r" },
usage = "<pattern-to> [radius]",
desc = "Choose the recursive brush",
help = "Chooses the recursive brush",
help = "Chooses the recursive brush\n" +
"The -d flag Will apply in depth first order",
min = 0,
max = 2
max = 3
)
@CommandPermissions("worldedit.brush.recursive")
public void recursiveBrush(Player player, LocalSession session, EditSession editSession, Pattern fill, @Optional("2") double radius) throws WorldEditException {
public void recursiveBrush(Player player, LocalSession session, EditSession editSession, Pattern fill, @Optional("2") double radius, @Switch('d') boolean depthFirst) throws WorldEditException {
worldEdit.checkMaxBrushRadius(radius);
BrushTool tool = session.getBrushTool(player.getItemInHand());
tool.setSize(radius);
tool.setBrush(new RecurseBrush(tool), "worldedit.brush.recursive");
tool.setBrush(new RecurseBrush(tool, depthFirst), "worldedit.brush.recursive");
tool.setMask(new IdMask(editSession));
tool.setFill(fill);
BBC.BRUSH_SPHERE.send(player, radius);
@ -158,7 +160,25 @@ public class BrushCommands {
tool.setFill(fill);
tool.setSize(radius);
tool.setBrush(new LineBrush(shell, select, flat), "worldedit.brush.line");
BBC.BRUSH_SPHERE.send(player, radius);
BBC.BRUSH_LINE.send(player, radius);
}
@Command(
aliases = { "spline", "spl" },
usage = "<pattern>",
desc = "Choose the spline brush",
help = "Chooses the spline brush",
min = 0,
max = 2
)
@CommandPermissions("worldedit.brush.spline")
public void splineBrush(Player player, LocalSession session, EditSession editSession, Pattern fill, @Optional("25") double radius) throws WorldEditException {
worldEdit.checkMaxBrushRadius(radius);
DoubleActionBrushTool tool = session.getDoubleActionBrushTool(player.getItemInHand());
tool.setFill(fill);
tool.setSize(radius);
tool.setBrush(new SplineBrush(player, session, tool), "worldedit.brush.spline");
BBC.BRUSH_SPLINE.send(player, radius);
}
@Command(

View File

@ -67,7 +67,6 @@ public abstract class BreadthFirstSearch implements Operation {
@Override
public Operation resume(RunContext run) throws WorldEditException {
Runtime runtime = Runtime.getRuntime();
Node from;
Node adjacent;
Vector mutable = new Vector();
@ -143,7 +142,7 @@ public abstract class BreadthFirstSearch implements Operation {
public Node(int x, int y, int z) {
this.x = x;
this.y = (short) y;
this.y = y;
this.z = z;
}
@ -157,7 +156,7 @@ public abstract class BreadthFirstSearch implements Operation {
private final void set(int x, int y, int z) {
this.x = x;
this.y = (short) y;
this.y = y;
this.z = z;
}