Fixes #490 (shatter brush)

This commit is contained in:
Jesse Boyd 2017-04-20 21:02:28 +10:00
parent bc2879df99
commit b7e3eff59b
No known key found for this signature in database
GPG Key ID: 59F1DE6293AF6E1F
3 changed files with 102 additions and 108 deletions

View File

@ -88,8 +88,11 @@ public class ScatterBrush implements Brush {
apply(editSession, placed, pos, pattern, size); apply(editSession, placed, pos, pattern, size);
} }
} }
finish(editSession, placed, position, pattern, size);
} }
public void finish(EditSession editSession, LocalBlockVectorSet placed, Vector pos, Pattern pattern, double size) {}
public boolean canApply(EditSession editSession, Vector pos) { public boolean canApply(EditSession editSession, Vector pos) {
return mask.test(pos); return mask.test(pos);
} }

View File

@ -2,73 +2,39 @@ package com.boydti.fawe.object.brush;
import com.boydti.fawe.object.PseudoRandom; import com.boydti.fawe.object.PseudoRandom;
import com.boydti.fawe.object.collection.LocalBlockVectorSet; import com.boydti.fawe.object.collection.LocalBlockVectorSet;
import com.sk89q.worldedit.BlockVector; import com.boydti.fawe.object.mask.SurfaceMask;
import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.MutableBlockVector; import com.sk89q.worldedit.MutableBlockVector;
import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.command.tool.brush.Brush;
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.pattern.Pattern; import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.function.visitor.BreadthFirstSearch; import com.sk89q.worldedit.function.visitor.BreadthFirstSearch;
import com.sk89q.worldedit.regions.CuboidRegion;
public class ShatterBrush implements Brush { public class ShatterBrush extends ScatterBrush {
private final int count; private final MutableBlockVector mutable = new MutableBlockVector();
public ShatterBrush(int count) { public ShatterBrush(int count) {
this.count = count; super(count, 1);
} }
@Override @Override
public void build(EditSession editSession, final Vector position, Pattern pattern, double size) throws MaxChangedBlocksException { public void apply(final EditSession editSession, final LocalBlockVectorSet placed, final Vector position, Pattern p, double size) throws MaxChangedBlocksException {
// We'll want this to be somewhat circular, so the cuboid needs to fit.
int r2Radius = (int) Math.ceil(size * Math.sqrt(2));
int radius2 = (int) (Math.ceil(r2Radius * r2Radius));
Vector bot = new MutableBlockVector(position.subtract(size, size, size));
Vector top = new MutableBlockVector(position.add(size, size, size));
CuboidRegion region = new CuboidRegion(bot, top);
// We'll want to use a fast random
PseudoRandom random = new PseudoRandom();
// We don't need double precision, so use a BlockVector
BlockVector min = region.getMinimumPoint().toBlockVector();
BlockVector max = region.getMaximumPoint().toBlockVector();
// Let's keep it inside the brush radius
int dx = max.getBlockX() - min.getBlockX() + 1;
int dy = max.getBlockY() - min.getBlockY() + 1;
int dz = max.getBlockZ() - min.getBlockZ() + 1;
// We'll store the points in a set
LocalBlockVectorSet queue = new LocalBlockVectorSet();
// User could select a single block and try to create 10 points = infinite loop
// To avoid being stuck in an infinite loop, let's stop after 5 collisions
int maxFails = 5;
for (int added = 0; added < count;) {
int x = (int) (random.nextDouble() * dx) + min.getBlockX();
int z = (int) (random.nextDouble() * dz) + min.getBlockZ();
int y = editSession.getHighestTerrainBlock(x, z, 0, 255);
// Check the adjacent blocks efficiently (loops over the adjacent blocks, or the set; whichever is faster)
if (!queue.containsRadius(x, y, z, 1)) {
added++;
queue.add(x, y, z);
} else if (maxFails-- <= 0) {
break;
} }
}
// Ideally we'd calculate all the bisecting planes, but that's complex to program @Override
// With this algorithm compute time depends on the number of blocks rather than the number of points public void finish(EditSession editSession, LocalBlockVectorSet placed, final Vector position, Pattern pattern, double size) {
// - Expand from each point (block by block) until there is a collision int radius2 = (int) (size * size);
{
// Keep track of where we've visited // Keep track of where we've visited
LocalBlockVectorSet visited = queue;
LocalBlockVectorSet tmp = new LocalBlockVectorSet(); LocalBlockVectorSet tmp = new LocalBlockVectorSet();
// Individual frontier for each point // Individual frontier for each point
LocalBlockVectorSet[] frontiers = new LocalBlockVectorSet[queue.size()]; LocalBlockVectorSet[] frontiers = new LocalBlockVectorSet[placed.size()];
// Keep track of where each frontier has visited // Keep track of where each frontier has visited
LocalBlockVectorSet[] frontiersVisited = new LocalBlockVectorSet[queue.size()]; LocalBlockVectorSet[] frontiersVisited = new LocalBlockVectorSet[placed.size()];
// Initiate the frontier with the starting points // Initiate the frontier with the starting points
int i = 0; int i = 0;
for (Vector pos : queue) { for (Vector pos : placed) {
LocalBlockVectorSet set = new LocalBlockVectorSet(); LocalBlockVectorSet set = new LocalBlockVectorSet();
set.add(pos); set.add(pos);
frontiers[i] = set; frontiers[i] = set;
@ -81,6 +47,7 @@ public class ShatterBrush implements Brush {
mask = Masks.alwaysTrue(); mask = Masks.alwaysTrue();
} }
final Mask finalMask = mask; final Mask finalMask = mask;
final SurfaceMask surfaceTest = new SurfaceMask(editSession);
// Expand // Expand
boolean notEmpty = true; boolean notEmpty = true;
while (notEmpty) { while (notEmpty) {
@ -98,7 +65,8 @@ public class ShatterBrush implements Brush {
finalTmp.add(x, y, z); finalTmp.add(x, y, z);
return; return;
} }
for (Vector direction : BreadthFirstSearch.DEFAULT_DIRECTIONS) { for (int i = 0; i < BreadthFirstSearch.DIAGONAL_DIRECTIONS.length; i++) {
Vector direction = BreadthFirstSearch.DIAGONAL_DIRECTIONS[i];
int x2 = x + direction.getBlockX(); int x2 = x + direction.getBlockX();
int y2 = y + direction.getBlockY(); int y2 = y + direction.getBlockY();
int z2 = z + direction.getBlockZ(); int z2 = z + direction.getBlockZ();
@ -108,9 +76,10 @@ public class ShatterBrush implements Brush {
int dz = position.getBlockZ() - z2; int dz = position.getBlockZ() - z2;
int dSqr = (dx * dx) + (dy * dy) + (dz * dz); int dSqr = (dx * dx) + (dy * dy) + (dz * dz);
if (dSqr <= radius2) { if (dSqr <= radius2) {
if (finalMask.test(MutableBlockVector.get(x2, y2, z2))) { MutableBlockVector v = mutable.setComponents(x2, y2, z2);
if (surfaceTest.test(v) && finalMask.test(v)) {
// (collision) If it's visited and part of another frontier, set the block // (collision) If it's visited and part of another frontier, set the block
if (!visited.add(x2, y2, z2)) { if (!placed.add(x2, y2, z2)) {
if (!frontierVisited.contains(x2, y2, z2)) { if (!frontierVisited.contains(x2, y2, z2)) {
editSession.setBlock(x2, y2, z2, pattern); editSession.setBlock(x2, y2, z2, pattern);
} }
@ -131,5 +100,4 @@ public class ShatterBrush implements Brush {
} }
} }
} }
}
} }

View File

@ -0,0 +1,23 @@
package com.boydti.fawe.object.mask;
import com.boydti.fawe.FaweCache;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.extent.Extent;
public class SurfaceMask extends AdjacentAnyMask {
public SurfaceMask(Extent extent) {
super(extent);
for (int id = 0; id < 256; id++) {
if (FaweCache.canPassThrough(id, 0)) {
add(new BaseBlock(id, -1));
}
}
}
@Override
public boolean test(Vector v) {
BaseBlock block = getExtent().getBlock(v);
return !test(block.getId()) && super.test(v);
}
}