Various minor

Stencil/Color brush can now be used on any surface, not just the top
Optimize adjacency and radius mask
This commit is contained in:
Jesse Boyd 2017-03-11 00:27:39 +11:00
parent 0434755333
commit 5bbc381dd0
No known key found for this signature in database
GPG Key ID: 59F1DE6293AF6E1F
8 changed files with 194 additions and 41 deletions

View File

@ -119,7 +119,6 @@ public class FawePrimitiveBinding extends BindingHelper {
try { try {
return Double.parseDouble(input); return Double.parseDouble(input);
} catch (NumberFormatException e1) { } catch (NumberFormatException e1) {
System.out.println("NUMBER FORMAT EXCEPTION " + e1);
try { try {
Expression expression = Expression.compile(input); Expression expression = Expression.compile(input);
return expression.evaluate(); return expression.evaluate();

View File

@ -1,56 +1,117 @@
package com.boydti.fawe.object.brush; package com.boydti.fawe.object.brush;
import com.boydti.fawe.object.PseudoRandom; import com.boydti.fawe.object.PseudoRandom;
import com.boydti.fawe.object.mask.AdjacentAnyMask;
import com.boydti.fawe.object.mask.RadiusMask;
import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.function.mask.ExistingBlockMask; import com.sk89q.worldedit.function.RegionFunction;
import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.mask.SolidBlockMask;
import com.sk89q.worldedit.function.operation.Operations;
import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.function.visitor.RecursiveVisitor;
import java.io.InputStream; import java.io.InputStream;
import java.util.Arrays;
public class StencilBrush extends HeightBrush { public class StencilBrush extends HeightBrush {
private final boolean onlyWhite; private final boolean onlyWhite;
private final int depth;
public StencilBrush(InputStream stream, int depth, int rotation, double yscale, boolean onlyWhite, Clipboard clipboard) { public StencilBrush(InputStream stream, int rotation, double yscale, boolean onlyWhite, Clipboard clipboard) {
super(stream, rotation, yscale, clipboard); super(stream, rotation, yscale, clipboard);
this.onlyWhite = onlyWhite; this.onlyWhite = onlyWhite;
this.depth = depth;
} }
@Override @Override
public void build(EditSession editSession, Vector position, Pattern pattern, double sizeDouble) throws MaxChangedBlocksException { public void build(EditSession editSession, Vector position, Pattern pattern, double sizeDouble) throws MaxChangedBlocksException {
final int cx = position.getBlockX();
final int cy = position.getBlockY();
final int cz = position.getBlockZ();
int size = (int) sizeDouble; int size = (int) sizeDouble;
Mask mask = new ExistingBlockMask(editSession);
int maxY = editSession.getMaxY(); int maxY = editSession.getMaxY();
double scale = (yscale / sizeDouble) * (maxY + 1); double scale = (yscale / sizeDouble) * (maxY + 1);
heightMap.setSize(size); heightMap.setSize(size);
int cutoff = onlyWhite ? maxY : 0; int cutoff = onlyWhite ? maxY : 0;
for (int x = -size; x <= size; x++) { final AdjacentAnyMask adjacent = new AdjacentAnyMask(editSession, Arrays.asList(new BaseBlock(0)));
int xx = position.getBlockX() + x; final SolidBlockMask solid = new SolidBlockMask(editSession);
for (int z = -size; z <= size; z++) { final RadiusMask radius = new RadiusMask(0, size);
double raise; RecursiveVisitor visitor = new RecursiveVisitor(new Mask() {
switch (rotation) { @Override
default:raise = heightMap.getHeight(x, z); break; public boolean test(Vector vector) {
case 1: raise = heightMap.getHeight(z, x); break; if (solid.test(vector) && radius.test(vector)) {
case 2: raise = heightMap.getHeight(-x, -z); break; Vector dir = adjacent.direction(vector);
case 3: raise = heightMap.getHeight(-z, -x);break; if (dir != null) {
} int dx = vector.getBlockX() - cx;
int val = (int) Math.ceil(raise * scale); int dy = vector.getBlockY() - cy;
if (val <= cutoff) { int dz = vector.getBlockZ() - cz;
continue; if (dy != 0) {
} if (dir.getBlockX() != 0) {
if (val >= 255 || PseudoRandom.random.random(maxY) < val) { dx += dir.getBlockX() * dy;
int zz = position.getBlockZ() + z; } else if (dir.getBlockZ() != 0) {
int y = editSession.getNearestSurfaceTerrainBlock(xx, zz, position.getBlockY(), 0, maxY); dz += dir.getBlockZ() * dy;
for (int i = 0; i < depth; i++) { }
editSession.setBlock(xx, y - i, zz, pattern); }
double raise = heightMap.getHeight(dx, dz);
int val = (int) Math.ceil(raise * scale);
if (val <= cutoff) {
return true;
}
if (val >= 255 || PseudoRandom.random.random(maxY) < val) {
editSession.setBlock(vector.getBlockX(), vector.getBlockY(), vector.getBlockZ(), pattern);
}
return true;
} }
} }
return false;
} }
} }, new RegionFunction() {
@Override
public boolean apply(Vector vector) throws WorldEditException {
return true;
}
}, Integer.MAX_VALUE, editSession);
visitor.setDirections(Arrays.asList(visitor.DIAGONAL_DIRECTIONS));
visitor.visit(position);
Operations.completeBlindly(visitor);
// Mask mask = new ExistingBlockMask(editSession);
// int maxY = editSession.getMaxY();
// double scale = (yscale / sizeDouble) * (maxY + 1);
// heightMap.setSize(size);
// int cutoff = onlyWhite ? maxY : 0;
//
// for (int x = -size; x <= size; x++) {
// int xx = position.getBlockX() + x;
// for (int z = -size; z <= size; z++) {
// double raise;
// switch (rotation) {
// default:raise = heightMap.getHeight(x, z); break;
// case 1: raise = heightMap.getHeight(z, x); break;
// case 2: raise = heightMap.getHeight(-x, -z); break;
// case 3: raise = heightMap.getHeight(-z, -x);break;
// }
// int val = (int) Math.ceil(raise * scale);
// if (val <= cutoff) {
// continue;
// }
// if (val >= 255 || PseudoRandom.random.random(maxY) < val) {
// int zz = position.getBlockZ() + z;
// int y = editSession.getNearestSurfaceTerrainBlock(xx, zz, position.getBlockY(), 0, maxY);
// for (int i = 0; i < depth; i++) {
// editSession.setBlock(xx, y - i, zz, pattern);
// }
// }
// }
// }
}
private void apply(double val) {
} }
} }

View File

@ -0,0 +1,62 @@
package com.boydti.fawe.object.mask;
import com.sk89q.worldedit.MutableBlockVector;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.function.mask.BlockMask;
import java.util.Collection;
/**
* Just an optimized version of the Adjacent Mask for single adjacency
*/
public class AdjacentAnyMask extends BlockMask {
public AdjacentAnyMask(Extent extent, Collection<BaseBlock> blocks) {
super(extent, blocks);
}
@Override
public boolean test(Vector v) {
int x = v.getBlockX();
int y = v.getBlockY();
int z = v.getBlockZ();
v.mutY(y + 1);
if (super.test(v)) { v.mutY(y); return true; }
v.mutY(y - 1);
if (super.test(v)) { v.mutY(y); return true; }
v.mutY(y);
v.mutX(x + 1);
if (super.test(v)) { v.mutX(x); return true; }
v.mutX(x - 1);
if (super.test(v)) { v.mutX(x); return true; }
v.mutX(x);
v.mutZ(z + 1);
if (super.test(v)) { v.mutZ(z); return true; }
v.mutZ(z - 1);
if (super.test(v)) { v.mutZ(z); return true; }
v.mutZ(z);
return false;
}
public Vector direction(Vector v) {
int x = v.getBlockX();
int y = v.getBlockY();
int z = v.getBlockZ();
v.mutY(y + 1);
if (super.test(v)) { v.mutY(y); return MutableBlockVector.get(0, 1, 0); }
v.mutY(y - 1);
if (super.test(v)) { v.mutY(y); return MutableBlockVector.get(0, -1, 0); }
v.mutY(y);
v.mutX(x + 1);
if (super.test(v)) { v.mutX(x); return MutableBlockVector.get(1, 0, 0); }
v.mutX(x - 1);
if (super.test(v)) { v.mutX(x); return MutableBlockVector.get(-1, 0, 0); }
v.mutX(x);
v.mutZ(z + 1);
if (super.test(v)) { v.mutZ(z); return MutableBlockVector.get(0, 0, 1); }
v.mutZ(z - 1);
if (super.test(v)) { v.mutZ(z); return MutableBlockVector.get(0, 0, - 1); }
v.mutZ(z);
return null;
}
}

View File

@ -1,5 +1,6 @@
package com.boydti.fawe.object.mask; package com.boydti.fawe.object.mask;
import com.sk89q.worldedit.MutableBlockVector;
import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.mask.Mask2D; import com.sk89q.worldedit.function.mask.Mask2D;
@ -24,19 +25,19 @@ public class RadiusMask implements Mask, ResettableMask{
@Override @Override
public boolean test(Vector to) { public boolean test(Vector to) {
if (pos == null) { if (pos == null) {
pos = new Vector(to); pos = new MutableBlockVector(to);
} }
int dx = Math.abs((int) (pos.getX() - to.getX())); int dx = pos.getBlockX() - to.getBlockX();
int dy = Math.abs((int) (pos.getY() - to.getY()));
int dz = Math.abs((int) (pos.getZ() - to.getZ()));
int d = dx * dx; int d = dx * dx;
if (d < minSqr || d > maxSqr) { if (d > maxSqr) {
return false; return false;
} }
int dz = pos.getBlockZ() - to.getBlockZ();
d += dz * dz; d += dz * dz;
if (d < minSqr || d > maxSqr) { if (d > maxSqr) {
return false; return false;
} }
int dy = pos.getBlockY() - to.getBlockY();
d += dy * dy; d += dy * dy;
if (d < minSqr || d > maxSqr) { if (d < minSqr || d > maxSqr) {
return false; return false;

View File

@ -542,16 +542,16 @@ public class BrushCommands {
max = -1 max = -1
) )
@CommandPermissions("worldedit.brush.stencil") @CommandPermissions("worldedit.brush.stencil")
public void stencilBrush(Player player, LocalSession session, Pattern fill, @Optional("5") double radius, @Optional("1") double depth, @Optional("") final String filename, @Optional("0") final int rotation, @Optional("1") final double yscale, @Switch('w') boolean onlyWhite) throws WorldEditException { public void stencilBrush(Player player, EditSession editSession, LocalSession session, Pattern fill, @Optional("5") double radius, @Optional("") final String filename, @Optional("0") final int rotation, @Optional("1") final double yscale, @Switch('w') boolean onlyWhite) throws WorldEditException {
worldEdit.checkMaxBrushRadius(radius); worldEdit.checkMaxBrushRadius(radius);
BrushTool tool = session.getBrushTool(player); BrushTool tool = session.getBrushTool(player);
InputStream stream = getHeightmapStream(filename); InputStream stream = getHeightmapStream(filename);
tool.setFill(fill); tool.setFill(fill);
tool.setSize(radius); tool.setSize(radius);
try { try {
tool.setBrush(new StencilBrush(stream, (int) depth, rotation, yscale, onlyWhite, filename.equalsIgnoreCase("#clipboard") ? session.getClipboard().getClipboard() : null), "worldedit.brush.height", player); tool.setBrush(new StencilBrush(stream, rotation, yscale, onlyWhite, filename.equalsIgnoreCase("#clipboard") ? session.getClipboard().getClipboard() : null), "worldedit.brush.height", player);
} catch (EmptyClipboardException ignore) { } catch (EmptyClipboardException ignore) {
tool.setBrush(new StencilBrush(stream, (int) depth, rotation, yscale, onlyWhite, null), "worldedit.brush.height", player); tool.setBrush(new StencilBrush(stream, rotation, yscale, onlyWhite, null), "worldedit.brush.height", player);
} }
player.print(BBC.getPrefix() + BBC.BRUSH_STENCIL.f(radius)); player.print(BBC.getPrefix() + BBC.BRUSH_STENCIL.f(radius));

View File

@ -241,7 +241,7 @@ public class DefaultMaskParser extends FaweParser<Mask> {
return new WallMask(extent, Arrays.asList(new BaseBlock(0)), 1, 8); return new WallMask(extent, Arrays.asList(new BaseBlock(0)), 1, 8);
case "#surface": case "#surface":
masks.add(new ExistingBlockMask(extent)); masks.add(new ExistingBlockMask(extent));
return new AdjacentMask(extent, Arrays.asList(new BaseBlock(0)), 1, 8); return new AdjacentAnyMask(extent, Arrays.asList(new BaseBlock(0)));
default: default:
throw new SuggestInputParseException(input, HASHTAG_MASKS); throw new SuggestInputParseException(input, HASHTAG_MASKS);
} }
@ -297,6 +297,9 @@ public class DefaultMaskParser extends FaweParser<Mask> {
} }
} }
if (firstChar == '~') { if (firstChar == '~') {
if (requiredMax >= 8 && requiredMin == 1) {
return new AdjacentAnyMask(extent, worldEdit.getBlockFactory().parseFromListInput(split[0], tempContext));
}
return new AdjacentMask(extent, worldEdit.getBlockFactory().parseFromListInput(split[0], tempContext), requiredMin, requiredMax); return new AdjacentMask(extent, worldEdit.getBlockFactory().parseFromListInput(split[0], tempContext), requiredMin, requiredMax);
} else { } else {
return new WallMask(extent, worldEdit.getBlockFactory().parseFromListInput(input.substring(1), tempContext), requiredMin, requiredMax); return new WallMask(extent, worldEdit.getBlockFactory().parseFromListInput(input.substring(1), tempContext), requiredMin, requiredMax);

View File

@ -45,7 +45,9 @@ public interface Mask extends com.sk89q.worldedit.masks.Mask {
* @return a 2D mask version or {@code null} if this mask can't be 2D * @return a 2D mask version or {@code null} if this mask can't be 2D
*/ */
@Nullable @Nullable
Mask2D toMask2D(); default Mask2D toMask2D() {
return null;
}
default void prepare(LocalSession session, LocalPlayer player, Vector target) {} default void prepare(LocalSession session, LocalPlayer player, Vector target) {}

View File

@ -16,11 +16,14 @@ import com.sk89q.worldedit.function.operation.RunContext;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List; import java.util.List;
public abstract class BreadthFirstSearch implements Operation { public abstract class BreadthFirstSearch implements Operation {
public static Vector[] DEFAULT_DIRECTIONS = new Vector[6]; public static final Vector[] DEFAULT_DIRECTIONS = new Vector[6];
public static final Vector[] DIAGONAL_DIRECTIONS;
static { static {
DEFAULT_DIRECTIONS[0] = (new MutableBlockVector(0, -1, 0)); DEFAULT_DIRECTIONS[0] = (new MutableBlockVector(0, -1, 0));
DEFAULT_DIRECTIONS[1] = (new MutableBlockVector(0, 1, 0)); DEFAULT_DIRECTIONS[1] = (new MutableBlockVector(0, 1, 0));
@ -28,10 +31,30 @@ public abstract class BreadthFirstSearch implements Operation {
DEFAULT_DIRECTIONS[3] = (new MutableBlockVector(1, 0, 0)); DEFAULT_DIRECTIONS[3] = (new MutableBlockVector(1, 0, 0));
DEFAULT_DIRECTIONS[4] = (new MutableBlockVector(0, 0, -1)); DEFAULT_DIRECTIONS[4] = (new MutableBlockVector(0, 0, -1));
DEFAULT_DIRECTIONS[5] = (new MutableBlockVector(0, 0, 1)); DEFAULT_DIRECTIONS[5] = (new MutableBlockVector(0, 0, 1));
List<MutableBlockVector> list = new ArrayList<>();
for (int x = -1; x <= 1; x++) {
for (int y = -1; y <= 1; y++) {
for (int z = -1; z <= 1; z++) {
if (x != 0 || y != 0 || z != 0) {
MutableBlockVector pos = new MutableBlockVector(x, y, z);
if (!list.contains(pos)) {
list.add(pos);
}
}
}
}
}
Collections.sort(list, new Comparator<Vector>() {
@Override
public int compare(Vector o1, Vector o2) {
return (int) Math.signum(o1.lengthSq() - o2.lengthSq());
}
});
DIAGONAL_DIRECTIONS = list.toArray(new Vector[list.size()]);
} }
private final RegionFunction function; private final RegionFunction function;
private final List<Vector> directions = new ArrayList<>(); private List<Vector> directions = new ArrayList<>();
private BlockVectorSet visited; private BlockVectorSet visited;
private final MappedFaweQueue mFaweQueue; private final MappedFaweQueue mFaweQueue;
private BlockVectorSet queue; private BlockVectorSet queue;
@ -62,6 +85,10 @@ public abstract class BreadthFirstSearch implements Operation {
return this.directions; return this.directions;
} }
public void setDirections(List<Vector> directions) {
this.directions = directions;
}
private IntegerTrio[] getIntDirections() { private IntegerTrio[] getIntDirections() {
IntegerTrio[] array = new IntegerTrio[directions.size()]; IntegerTrio[] array = new IntegerTrio[directions.size()];
for (int i = 0; i < array.length; i++) { for (int i = 0; i < array.length; i++) {
@ -91,8 +118,6 @@ public abstract class BreadthFirstSearch implements Operation {
return visited.contains(pos); return visited.contains(pos);
} }
@Override @Override
public Operation resume(RunContext run) throws WorldEditException { public Operation resume(RunContext run) throws WorldEditException {
MutableBlockVector mutable = new MutableBlockVector(); MutableBlockVector mutable = new MutableBlockVector();