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);
}
}
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) {
return mask.test(pos);
}

View File

@ -2,134 +2,102 @@ package com.boydti.fawe.object.brush;
import com.boydti.fawe.object.PseudoRandom;
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.MaxChangedBlocksException;
import com.sk89q.worldedit.MutableBlockVector;
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.Masks;
import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.function.visitor.BreadthFirstSearch;
import com.sk89q.worldedit.regions.CuboidRegion;
public class ShatterBrush implements Brush {
private final int count;
public class ShatterBrush extends ScatterBrush {
private final MutableBlockVector mutable = new MutableBlockVector();
public ShatterBrush(int count) {
this.count = count;
super(count, 1);
}
@Override
public void build(EditSession editSession, final Vector position, Pattern pattern, 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;
}
public void apply(final EditSession editSession, final LocalBlockVectorSet placed, final Vector position, Pattern p, double size) throws MaxChangedBlocksException {
}
@Override
public void finish(EditSession editSession, LocalBlockVectorSet placed, final Vector position, Pattern pattern, double size) {
int radius2 = (int) (size * size);
// Keep track of where we've visited
LocalBlockVectorSet tmp = new LocalBlockVectorSet();
// Individual frontier for each point
LocalBlockVectorSet[] frontiers = new LocalBlockVectorSet[placed.size()];
// Keep track of where each frontier has visited
LocalBlockVectorSet[] frontiersVisited = new LocalBlockVectorSet[placed.size()];
// Initiate the frontier with the starting points
int i = 0;
for (Vector pos : placed) {
LocalBlockVectorSet set = new LocalBlockVectorSet();
set.add(pos);
frontiers[i] = set;
frontiersVisited[i] = set.clone();
i++;
}
// Ideally we'd calculate all the bisecting planes, but that's complex to program
// With this algorithm compute time depends on the number of blocks rather than the number of points
// - Expand from each point (block by block) until there is a collision
{
// Keep track of where we've visited
LocalBlockVectorSet visited = queue;
LocalBlockVectorSet tmp = new LocalBlockVectorSet();
// Individual frontier for each point
LocalBlockVectorSet[] frontiers = new LocalBlockVectorSet[queue.size()];
// Keep track of where each frontier has visited
LocalBlockVectorSet[] frontiersVisited = new LocalBlockVectorSet[queue.size()];
// Initiate the frontier with the starting points
int i = 0;
for (Vector pos : queue) {
LocalBlockVectorSet set = new LocalBlockVectorSet();
set.add(pos);
frontiers[i] = set;
frontiersVisited[i] = set.clone();
i++;
}
// Mask
Mask mask = editSession.getMask();
if (mask == null) {
mask = Masks.alwaysTrue();
}
final Mask finalMask = mask;
// Expand
boolean notEmpty = true;
while (notEmpty) {
notEmpty = false;
for (i = 0; i < frontiers.length; i++) {
LocalBlockVectorSet frontier = frontiers[i];
notEmpty |= !frontier.isEmpty();
final LocalBlockVectorSet frontierVisited = frontiersVisited[i];
// This is a temporary set with the next blocks the frontier will visit
final LocalBlockVectorSet finalTmp = tmp;
frontier.forEach(new LocalBlockVectorSet.BlockVectorSetVisitor() {
@Override
public void run(int x, int y, int z, int index) {
if (PseudoRandom.random.random(2) == 0) {
finalTmp.add(x, y, z);
return;
}
for (Vector direction : BreadthFirstSearch.DEFAULT_DIRECTIONS) {
int x2 = x + direction.getBlockX();
int y2 = y + direction.getBlockY();
int z2 = z + direction.getBlockZ();
// Check boundary
int dx = position.getBlockX() - x2;
int dy = position.getBlockY() - y2;
int dz = position.getBlockZ() - z2;
int dSqr = (dx * dx) + (dy * dy) + (dz * dz);
if (dSqr <= radius2) {
if (finalMask.test(MutableBlockVector.get(x2, y2, z2))) {
// (collision) If it's visited and part of another frontier, set the block
if (!visited.add(x2, y2, z2)) {
if (!frontierVisited.contains(x2, y2, z2)) {
editSession.setBlock(x2, y2, z2, pattern);
}
} else {
// Hasn't visited and not a collision = add it
finalTmp.add(x2, y2, z2);
frontierVisited.add(x2, y2, z2);
// Mask
Mask mask = editSession.getMask();
if (mask == null) {
mask = Masks.alwaysTrue();
}
final Mask finalMask = mask;
final SurfaceMask surfaceTest = new SurfaceMask(editSession);
// Expand
boolean notEmpty = true;
while (notEmpty) {
notEmpty = false;
for (i = 0; i < frontiers.length; i++) {
LocalBlockVectorSet frontier = frontiers[i];
notEmpty |= !frontier.isEmpty();
final LocalBlockVectorSet frontierVisited = frontiersVisited[i];
// This is a temporary set with the next blocks the frontier will visit
final LocalBlockVectorSet finalTmp = tmp;
frontier.forEach(new LocalBlockVectorSet.BlockVectorSetVisitor() {
@Override
public void run(int x, int y, int z, int index) {
if (PseudoRandom.random.random(2) == 0) {
finalTmp.add(x, y, z);
return;
}
for (int i = 0; i < BreadthFirstSearch.DIAGONAL_DIRECTIONS.length; i++) {
Vector direction = BreadthFirstSearch.DIAGONAL_DIRECTIONS[i];
int x2 = x + direction.getBlockX();
int y2 = y + direction.getBlockY();
int z2 = z + direction.getBlockZ();
// Check boundary
int dx = position.getBlockX() - x2;
int dy = position.getBlockY() - y2;
int dz = position.getBlockZ() - z2;
int dSqr = (dx * dx) + (dy * dy) + (dz * dz);
if (dSqr <= radius2) {
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
if (!placed.add(x2, y2, z2)) {
if (!frontierVisited.contains(x2, y2, z2)) {
editSession.setBlock(x2, y2, z2, pattern);
}
} else {
// Hasn't visited and not a collision = add it
finalTmp.add(x2, y2, z2);
frontierVisited.add(x2, y2, z2);
}
}
}
}
});
// Swap the frontier with the temporary set
frontiers[i] = tmp;
tmp = frontier;
tmp.clear();
}
}
});
// Swap the frontier with the temporary set
frontiers[i] = tmp;
tmp = frontier;
tmp.clear();
}
}
}
}
}

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