Add surface spline

This commit is contained in:
Jesse Boyd 2017-03-13 03:17:22 +11:00
parent b20120a1f2
commit 147cfeed10
No known key found for this signature in database
GPG Key ID: 59F1DE6293AF6E1F
7 changed files with 158 additions and 50 deletions

View File

@ -18,7 +18,6 @@ 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.math.interpolation.Node;
import com.sk89q.worldedit.math.transform.AffineTransform;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@ -125,12 +124,6 @@ public class SplineBrush implements Brush {
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<>();

View File

@ -0,0 +1,82 @@
package com.boydti.fawe.object.brush;
import com.boydti.fawe.config.BBC;
import com.boydti.fawe.object.collection.LocalBlockVectorSet;
import com.boydti.fawe.util.MathMan;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.command.tool.brush.Brush;
import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.math.interpolation.KochanekBartelsInterpolation;
import com.sk89q.worldedit.math.interpolation.Node;
import java.util.ArrayList;
import java.util.List;
public class SurfaceSpline implements Brush {
final double tension, bias,continuity, quality;
public SurfaceSpline(final double tension, final double bias, final double continuity, final double quality) {
this.tension = tension;
this.bias = bias;
this.continuity = continuity;
this.quality = quality;
}
private ArrayList<Vector> path = new ArrayList<>();
@Override
public void build(EditSession editSession, Vector pos, Pattern pattern, double radius) throws MaxChangedBlocksException {
int maxY = editSession.getMaxY();
if (path.isEmpty() || !pos.equals(path.get(path.size() - 1))) {
int max = editSession.getNearestSurfaceTerrainBlock(pos.getBlockX(), pos.getBlockZ(), pos.getBlockY(), 0, editSession.getMaxY());
pos.mutY(max);
path.add(pos);
editSession.getPlayer().sendMessage(BBC.getPrefix() + BBC.BRUSH_SPLINE_PRIMARY_2.s());
} else{
LocalBlockVectorSet vset = new LocalBlockVectorSet();
final List<Node> nodes = new ArrayList<>(path.size());
final KochanekBartelsInterpolation interpol = new KochanekBartelsInterpolation();
for (final Vector nodevector : path) {
final Node n = new Node(nodevector);
n.setTension(tension);
n.setBias(bias);
n.setContinuity(continuity);
nodes.add(n);
}
interpol.setNodes(nodes);
final double splinelength = interpol.arcLength(0, 1);
for (double loop = 0; loop <= 1; loop += 1D / splinelength / quality) {
final Vector tipv = interpol.getPosition(loop);
final int tipx = (int) Math.round(tipv.getX());
final int tipz = (int) tipv.getZ();
int tipy = (int) Math.round(tipv.getY());
tipy = editSession.getNearestSurfaceTerrainBlock(tipx, tipz, tipy, 0, maxY);
if (radius == 0) {
editSession.setBlock(tipx, tipy, tipz, pattern.next(tipx, tipy, tipz));
} else {
vset.add(tipx, tipy, tipz);
}
}
if (radius != 0) {
double radius2 = (radius * radius);
LocalBlockVectorSet newSet = new LocalBlockVectorSet();
final int ceilrad = (int) Math.ceil(radius);
for (final Vector v : vset) {
final int tipx = v.getBlockX(), tipy = v.getBlockY(), tipz = v.getBlockZ();
for (int loopx = tipx - ceilrad; loopx <= (tipx + ceilrad); loopx++) {
for (int loopz = tipz - ceilrad; loopz <= (tipz + ceilrad); loopz++) {
if (MathMan.hypot2(loopx - tipx, 0, loopz - tipz) <= radius2) {
int y = editSession.getNearestSurfaceTerrainBlock(loopx, loopz, v.getBlockY(), 0, maxY);
newSet.add(loopx, y, loopz);
}
}
}
}
editSession.setBlocks(newSet, pattern);
}
editSession.getPlayer().sendMessage(BBC.getPrefix() + BBC.BRUSH_SPLINE_SECONDARY.s());
}
}
}

View File

@ -36,7 +36,6 @@ import java.util.UUID;
* - Uses an auto closable RandomAccessFile for getting / setting id / data
* - I don't know how to reduce nbt / entities to O(2) complexity, so it is stored in memory.
*
* TODO load on join
*/
public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {

View File

@ -43,6 +43,22 @@ public class MathMan {
253, 254, 254, 255
};
public static double hypot(final double... pars) {
double sum = 0;
for (final double d : pars) {
sum += Math.pow(d, 2);
}
return Math.sqrt(sum);
}
public static double hypot2(final double... pars) {
double sum = 0;
for (final double d : pars) {
sum += Math.pow(d, 2);
}
return sum;
}
public static final int wrap(int value, int min, int max) {
if (max < min) {
return value;

View File

@ -58,6 +58,7 @@ import com.boydti.fawe.object.progress.DefaultProgressTracker;
import com.boydti.fawe.object.visitor.FastChunkIterator;
import com.boydti.fawe.util.ExtentTraverser;
import com.boydti.fawe.util.MaskTraverser;
import com.boydti.fawe.util.MathMan;
import com.boydti.fawe.util.MemUtil;
import com.boydti.fawe.util.Perm;
import com.boydti.fawe.util.SetQueue;
@ -1142,7 +1143,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
}
@SuppressWarnings("deprecation")
private int setBlocks(final Set<Vector> vset, final Pattern pattern) throws MaxChangedBlocksException {
public int setBlocks(final Set<Vector> vset, final Pattern pattern) throws MaxChangedBlocksException {
RegionVisitor visitor = new RegionVisitor(vset, new BlockReplace(extent, pattern), this);
Operations.completeBlindly(visitor);
changes += visitor.getAffected();
@ -1713,7 +1714,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
}
// public int replaceBlocks(final Region region, final Mask mask, final BaseBlock block) throws MaxChangedBlocksException {
// TODO
// TODO fast replace
// }
/**
@ -2663,7 +2664,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
for (int y = basePosition.getBlockY(); y >= (basePosition.getBlockY() - 10); --y) {
final int t = getLazyBlock(x, y, z).getType();
if ((t == BlockID.GRASS) || (t == BlockID.DIRT)) {
treeGenerator.generate(EditSession.this, new Vector(x, y + 1, z));
treeGenerator.generate(EditSession.this, mutable.setComponents(x, y + 1, z));
break;
} else if (t == BlockID.SNOW) {
setBlock(x, y, z, nullBlock);
@ -2810,7 +2811,8 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
final ArbitraryShape shape = new ArbitraryShape(region) {
@Override
public BaseBlock getMaterial(final int x, final int y, final int z, final BaseBlock defaultMaterial) {
final Vector current = new Vector(x, y, z);
//TODO Optimize - avoid vector creation (math)
final Vector current = mutable.setComponents(x, y, z);
environment.setCurrentBlock(current);
final Vector scaled = current.subtract(zero).divide(unit);
@ -2973,7 +2975,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
*/
public int drawLine(final Pattern pattern, final Vector pos1, final Vector pos2, final double radius, final boolean filled, boolean flat) throws MaxChangedBlocksException {
Set vset = new LocalBlockVectorSet();
LocalBlockVectorSet vset = new LocalBlockVectorSet();
boolean notdrawn = true;
final int x1 = pos1.getBlockX(), y1 = pos1.getBlockY(), z1 = pos1.getBlockZ();
@ -2982,7 +2984,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
final int dx = Math.abs(x2 - x1), dy = Math.abs(y2 - y1), dz = Math.abs(z2 - z1);
if ((dx + dy + dz) == 0) {
vset.add(new Vector(tipx, tipy, tipz));
vset.add(tipx, tipy, tipz);
notdrawn = false;
}
@ -2991,7 +2993,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
tipx = x1 + (domstep * ((x2 - x1) > 0 ? 1 : -1));
tipy = (int) Math.round(y1 + (((domstep * ((double) dy)) / (dx)) * ((y2 - y1) > 0 ? 1 : -1)));
tipz = (int) Math.round(z1 + (((domstep * ((double) dz)) / (dx)) * ((z2 - z1) > 0 ? 1 : -1)));
vset.add(new Vector(tipx, tipy, tipz));
vset.add(tipx, tipy, tipz);
}
notdrawn = false;
}
@ -3002,7 +3004,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
tipx = (int) Math.round(x1 + (((domstep * ((double) dx)) / (dy)) * ((x2 - x1) > 0 ? 1 : -1)));
tipz = (int) Math.round(z1 + (((domstep * ((double) dz)) / (dy)) * ((z2 - z1) > 0 ? 1 : -1)));
vset.add(new Vector(tipx, tipy, tipz));
vset.add(tipx, tipy, tipz);
}
notdrawn = false;
}
@ -3012,21 +3014,22 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
tipz = z1 + (domstep * ((z2 - z1) > 0 ? 1 : -1));
tipy = (int) Math.round(y1 + (((domstep * ((double) dy)) / (dz)) * ((y2 - y1) > 0 ? 1 : -1)));
tipx = (int) Math.round(x1 + (((domstep * ((double) dx)) / (dz)) * ((x2 - x1) > 0 ? 1 : -1)));
vset.add(new Vector(tipx, tipy, tipz));
vset.add(tipx, tipy, tipz);
}
}
Set<Vector> newVset;
if (flat) {
vset = this.getStretched(vset, radius);
newVset = this.getStretched(vset, radius);
if (!filled) {
vset = this.getOutline(vset);
newVset = this.getOutline(newVset);
}
} else {
vset = this.getBallooned(vset, radius);
newVset = this.getBallooned(vset, radius);
if (!filled) {
vset = this.getHollowed(vset);
newVset = this.getHollowed(newVset);
}
}
return this.setBlocks(vset, pattern);
return this.setBlocks(newVset, pattern);
}
/**
@ -3044,10 +3047,8 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @return number of blocks affected
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
public int drawSpline(final Pattern pattern, final List<Vector> nodevectors, final double tension, final double bias, final double continuity, final double quality, final double radius,
final boolean filled) throws MaxChangedBlocksException {
Set vset = new LocalBlockVectorSet();
public int drawSpline(final Pattern pattern, final List<Vector> nodevectors, final double tension, final double bias, final double continuity, final double quality, final double radius, final boolean filled) throws MaxChangedBlocksException {
LocalBlockVectorSet vset = new LocalBlockVectorSet();
final List<Node> nodes = new ArrayList<Node>(nodevectors.size());
final KochanekBartelsInterpolation interpol = new KochanekBartelsInterpolation();
@ -3070,40 +3071,33 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
if (radius == 0) {
setBlock(tipx, tipy, tipz, pattern.next(tipx, tipy, tipz));
} else {
vset.add(new Vector(tipx, tipy, tipz));
vset.add(tipx, tipy, tipz);
}
}
Set<Vector> newVset;
if (radius != 0) {
vset = this.getBallooned(vset, radius);
newVset = this.getBallooned(vset, radius);
if (!filled) {
vset = this.getHollowed(vset);
newVset = this.getHollowed(newVset);
}
return this.setBlocks(vset, pattern);
return this.setBlocks(newVset, pattern);
}
return changes;
}
private double hypot(final double... pars) {
double sum = 0;
for (final double d : pars) {
sum += Math.pow(d, 2);
}
return Math.sqrt(sum);
}
private Set<Vector> getBallooned(final Set<Vector> vset, final double radius) {
if (radius < 1) {
return vset;
}
final Set returnset = new LocalBlockVectorSet();
final LocalBlockVectorSet returnset = new LocalBlockVectorSet();
final int ceilrad = (int) Math.ceil(radius);
for (final Vector v : vset) {
final int tipx = v.getBlockX(), tipy = v.getBlockY(), tipz = v.getBlockZ();
for (int loopx = tipx - ceilrad; loopx <= (tipx + ceilrad); loopx++) {
for (int loopy = tipy - ceilrad; loopy <= (tipy + ceilrad); loopy++) {
for (int loopz = tipz - ceilrad; loopz <= (tipz + ceilrad); loopz++) {
if (this.hypot(loopx - tipx, loopy - tipy, loopz - tipz) <= radius) {
returnset.add(new Vector(loopx, loopy, loopz));
if (MathMan.hypot(loopx - tipx, loopy - tipy, loopz - tipz) <= radius) {
returnset.add(loopx, loopy, loopz);
}
}
}
@ -3112,18 +3106,18 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
return returnset;
}
private Set<Vector> getStretched(final Set<Vector> vset, final double radius) {
public Set<Vector> getStretched(final Set<Vector> vset, final double radius) {
if (radius < 1) {
return vset;
}
final Set returnset = new LocalBlockVectorSet();
final LocalBlockVectorSet returnset = new LocalBlockVectorSet();
final int ceilrad = (int) Math.ceil(radius);
for (final Vector v : vset) {
final int tipx = v.getBlockX(), tipy = v.getBlockY(), tipz = v.getBlockZ();
for (int loopx = tipx - ceilrad; loopx <= (tipx + ceilrad); loopx++) {
for (int loopz = tipz - ceilrad; loopz <= (tipz + ceilrad); loopz++) {
if (this.hypot(loopx - tipx, 0, loopz - tipz) <= radius) {
returnset.add(new Vector(loopx, v.getY(), loopz));
if (MathMan.hypot(loopx - tipx, 0, loopz - tipz) <= radius) {
returnset.add(loopx, v.getBlockY(), loopz);
}
}
}
@ -3131,8 +3125,9 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
return returnset;
}
private Set<Vector> getOutline(final Set<Vector> vset) {
final Set returnset = new LocalBlockVectorSet();
public Set<Vector> getOutline(final Set<Vector> vset) {
// TODO optimize - vset instanceof LocalBlockVectorSet -> avoid Vector creation
final LocalBlockVectorSet returnset = new LocalBlockVectorSet();
for (final Vector v : vset) {
final double x = v.getX(), y = v.getY(), z = v.getZ();
if (!(vset.contains(new Vector(x + 1, y, z))
@ -3144,7 +3139,8 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
return returnset;
}
private Set<Vector> getHollowed(final Set<Vector> vset) {
public Set<Vector> getHollowed(final Set<Vector> vset) {
//TODO Optimize - avoid vector creation
final Set returnset = new LocalBlockVectorSet();
for (final Vector v : vset) {
final double x = v.getX(), y = v.getY(), z = v.getZ();
@ -3159,7 +3155,8 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
return returnset;
}
private void recurseHollow(final Region region, final BlockVector origin, final Set<BlockVector> outside) {
public void recurseHollow(final Region region, final BlockVector origin, final Set<BlockVector> outside) {
//TODO Optimize - avoid vector creation
final ArrayDeque<BlockVector> queue = new ArrayDeque<BlockVector>();
queue.addLast(origin);
@ -3283,6 +3280,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
}
public boolean regenerate(final Region region, final BaseBiome biome, final Long seed) {
//TODO Optimize - avoid Vector2D creation (make mutable)
final FaweQueue queue = this.getQueue();
queue.setChangeTask(null);
final FaweChangeSet fcs = (FaweChangeSet) this.getChangeSet();

View File

@ -41,6 +41,7 @@ import com.boydti.fawe.object.brush.ShatterBrush;
import com.boydti.fawe.object.brush.SplatterBrush;
import com.boydti.fawe.object.brush.SplineBrush;
import com.boydti.fawe.object.brush.StencilBrush;
import com.boydti.fawe.object.brush.SurfaceSpline;
import com.boydti.fawe.object.brush.TargetMode;
import com.boydti.fawe.object.brush.heightmap.ScalableHeightMap;
import com.boydti.fawe.object.brush.scroll.ScrollClipboard;
@ -407,6 +408,26 @@ public class BrushCommands {
player.print(BBC.getPrefix() + BBC.BRUSH_SPLINE.f(radius));
}
// final double tension, final double bias, final double continuity, final double quality
@Command(
aliases = { "sspl", "sspline", "surfacespline" },
usage = "<pattern> [size] [tension] [bias] [continuity] [quality]",
desc = "Draws a spline on the surface",
help = "Chooses the surface spline brush",
min = 0,
max = 2
)
@CommandPermissions("worldedit.brush.surfacespline") // 0, 0, 0, 10, 0,
public void surfaceSpline(Player player, LocalSession session, Pattern fill, @Optional("0") double radius, @Optional("0") double tension, @Optional("0") double bias, @Optional("0") double continuity, @Optional("10") double quality) throws WorldEditException {
worldEdit.checkMaxBrushRadius(radius);
BrushTool tool = session.getBrushTool(player);
tool.setFill(fill);
tool.setSize(radius);
tool.setBrush(new SurfaceSpline(tension, bias, continuity, quality), "worldedit.brush.spline", player);
player.print(BBC.getPrefix() + BBC.BRUSH_SPLINE.f(radius));
}
@Command(
aliases = { "sphere", "s" },
usage = "<pattern> [radius]",

View File

@ -148,7 +148,6 @@ public class HashTagPatternParser extends FaweParser<Pattern> {
try {
ClipboardHolder[] clipboards = ClipboardFormat.SCHEMATIC.loadAllFromInput(context.getActor(), context.requireWorld().getWorldData(), location, true);
if (clipboards == null) {
System.out.println("NULL!");
throw new InputParseException("#fullcopy:<source>");
}
boolean random = split2.length == 3 && split2[2].equalsIgnoreCase("true");