Add fuzzy region selection (with mask support)

`//sel fuzzy` or `//sel fuzzy=<mask>`
- All connected blocks matching the mask (default is same id/data) will
be added.
- Left click to select the region
- Right click to add to the existing region
This commit is contained in:
Jesse Boyd 2016-12-12 20:48:09 +11:00
parent f0a36ba1fa
commit e7d97d030f
No known key found for this signature in database
GPG Key ID: 59F1DE6293AF6E1F
9 changed files with 314 additions and 168 deletions

View File

@ -149,6 +149,8 @@ public enum BBC {
VISITOR_ENTITY("%s0 entities affected", "WorldEdit.Visitor"),
VISITOR_FLAT("%s0 columns affected", "WorldEdit.Visitor"),
SELECTOR_FUZZY_POS1("Region set and expanded from %s0 %s1.", "WorldEdit.Selector"),
SELECTOR_FUZZY_POS2("Added expansion of %s0 %s1.", "WorldEdit.Selector"),
SELECTOR_CUBOID_POS1("pos1 set to %s0 %s1.", "WorldEdit.Selector"),
SELECTOR_CUBOID_POS2("pos2 set to %s0 %s1.", "WorldEdit.Selector"),
SELECTOR_INVALID_COORDINATES("Invalid coordinates %s0", "WorldEdit.Selector"),

View File

@ -4,7 +4,6 @@ import com.boydti.fawe.Fawe;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.config.Settings;
import com.boydti.fawe.jnbt.NBTStreamer;
import com.boydti.fawe.jnbt.SchematicStreamer;
import com.boydti.fawe.object.IntegerTrio;
import com.boydti.fawe.object.RunnableVal2;
import com.boydti.fawe.object.io.BufferedRandomAccessFile;
@ -12,7 +11,6 @@ import com.boydti.fawe.util.MainUtil;
import com.boydti.fawe.util.ReflectionUtils;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.IntTag;
import com.sk89q.jnbt.NBTInputStream;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.BlockVector;
import com.sk89q.worldedit.EditSession;
@ -23,10 +21,8 @@ import com.sk89q.worldedit.entity.Entity;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
import com.sk89q.worldedit.regions.CuboidRegion;
import java.io.BufferedInputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
@ -34,7 +30,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.zip.GZIPInputStream;
/**
* A clipboard with disk backed storage. (lower memory + loads on crash)
@ -426,17 +421,6 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
}
}
public static void main(String[] args) throws IOException{
long start = System.currentTimeMillis();
File file = new File("C:/Users/Jesse/Desktop/OTHER/mc/plugins/WorldEdit/schematics/50mil.schematic");
BufferedInputStream in = new BufferedInputStream(new GZIPInputStream(new BufferedInputStream(new FileInputStream(file))));
NBTInputStream nbtin = new NBTInputStream(in);
Settings.CLIPBOARD.USE_DISK = true;
SchematicStreamer streamer = new SchematicStreamer(nbtin, UUID.randomUUID());
streamer.getClipboard();
System.out.println(System.currentTimeMillis() - start);
}
@Override
public void setData(int i, int data) {
try {

View File

@ -1,55 +1,92 @@
package com.boydti.fawe.object.regions;
import com.boydti.fawe.util.EditSessionBuilder;
import com.boydti.fawe.util.MathMan;
import com.sk89q.worldedit.BlockVector;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.function.RegionFunction;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.operation.Operations;
import com.sk89q.worldedit.function.visitor.RecursiveVisitor;
import com.sk89q.worldedit.regions.AbstractRegion;
import com.sk89q.worldedit.regions.RegionOperationException;
import com.sk89q.worldedit.world.World;
import java.util.BitSet;
import java.util.Iterator;
public class MagicRegion extends AbstractRegion {
public class FuzzyRegion extends AbstractRegion {
private final Mask mask;
private BitSet set = new BitSet();
private boolean populated;
private int minX, minY, minZ, maxX, maxY, maxZ;
private int offsetX, offsetY, offsetZ;
private Extent extent;
{
minX = minY = minZ = Integer.MAX_VALUE;
maxX = maxY = maxZ = Integer.MIN_VALUE;
}
public MagicRegion(World world) {
public FuzzyRegion(World world, Extent editSession, Mask mask) {
super(world);
this.extent = editSession;
this.mask = mask;
}
public Mask getMask() {
return mask;
}
private static int pair(int x, int y, int z) {
byte b1 = (byte) y;
byte b2 = (byte) (x);
byte b3 = (byte) (z);
int x16 = (x >> 8) & 0xF;
int z16 = (z >> 8) & 0xF;
byte b4 = MathMan.pair16(x16, z16);
return (b1 & 0xFF)
int x16 = (x >> 8) & 0x7;
int z16 = (z >> 8) & 0x7;
byte b4 = MathMan.pair8(x16, z16);
return ((b1 & 0xFF)
+ ((b2 & 0xFF) << 8)
+ ((b3 & 0xFF) << 16)
+ ((b4 & 0xFF) << 24)
+ ((b4 & 0x7F) << 24))
;
}
public void select(int x, int y, int z) {
EditSession editSession = new EditSessionBuilder(getWorld())
.limitUnlimited()
.changeSetNull()
.fastmode(true)
.allowedRegionsEverywhere()
.checkMemory(false)
.autoQueue(false)
.build();
@Override
public int getArea() {
return set.length();
}
public void select(int x, int y, int z) {
RecursiveVisitor visitor = new RecursiveVisitor(mask, new RegionFunction() {
@Override
public boolean apply(Vector position) throws WorldEditException {
return true;
}
}) {
@Override
public boolean isVisited(Node node) {
return contains(node.getX(), node.getY(), node.getZ());
}
@Override
public void addVisited(Node node, int depth) {
try {
set(node.getX(), node.getY(), node.getZ());
} catch (RegionOperationException e) {
throw new RuntimeException(e);
}
}
@Override
public void cleanVisited(int layer) {
// Do nothing
}
};
visitor.visit(new Vector(x, y, z));
Operations.completeBlindly(visitor);
}
@Override
@ -67,13 +104,13 @@ public class MagicRegion extends AbstractRegion {
@Override
public BlockVector next() {
int b1 = index & 0xFF;
int b2 = (index >> 8) & 0xFF;
int b3 = (index >> 16) & 0xFF;
byte b4 = (byte) ((index >> 24) & 0xFF);
pos.x = offsetX + (((b2 & 0xFF) + ((MathMan.unpair16x(b4)) << 8)) << 20) >> 20;
int b1 = ((byte) index) & 0xFF;
int b2 = ((byte) (index >> 8)) & 0xFF;
int b3 = ((byte)(index >> 16)) & 0xFF;
byte b4 = (byte) (index >> 24);
pos.x = offsetX + (((b2 + ((MathMan.unpair8x(b4)) << 8)) << 21) >> 21);
pos.y = offsetY + b1;
pos.z = offsetZ + (((b3) + ((MathMan.unpair16y(b4)) << 8)) << 20) >> 20;
pos.z = offsetZ + (((b3 + ((MathMan.unpair8y(b4)) << 8)) << 21) >> 21);
return pos;
}
@ -84,13 +121,20 @@ public class MagicRegion extends AbstractRegion {
};
}
public void set(int x, int y, int z) throws RegionOperationException{
x -= offsetX;
y -= offsetY;
z -= offsetZ;
if (populated) {
x -= offsetX;
y -= offsetY;
z -= offsetZ;
} else {
offsetX = x;
offsetZ = z;
x = 0;
z = 0;
populated = true;
}
set.set(pair(x, y, z), true);
if (x >= 2048 || x <= -2048 || z >= 2048 || z <= -2048) {
if (x >= 1024 || x <= -1024 || z >= 1024 || z <= -1024) {
throw new RegionOperationException("Selection is too large!");
}
if (x > maxX) {
@ -114,7 +158,11 @@ public class MagicRegion extends AbstractRegion {
}
public boolean contains(int x, int y, int z) {
return set.get(pair(x - offsetX, y - offsetY, z - offsetZ));
try {
return set.get(pair(x - offsetX, y - offsetY, z - offsetZ));
} catch (IndexOutOfBoundsException e) {
throw new RuntimeException(e);
}
}
@Override
@ -154,4 +202,8 @@ public class MagicRegion extends AbstractRegion {
maxY += change.getBlockY();
maxZ += change.getBlockZ();
}
public void setExtent(EditSession extent) {
this.extent = extent;
}
}

View File

@ -0,0 +1,161 @@
package com.boydti.fawe.object.regions.selector;
import com.boydti.fawe.config.BBC;
import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.regions.FuzzyRegion;
import com.boydti.fawe.util.EditSessionBuilder;
import com.boydti.fawe.util.ExtentTraverser;
import com.boydti.fawe.util.MaskTraverser;
import com.sk89q.worldedit.BlockVector;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.IncompleteRegionException;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.extent.AbstractDelegateExtent;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.regions.RegionSelector;
import com.sk89q.worldedit.regions.selector.limit.SelectorLimits;
import com.sk89q.worldedit.world.World;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
public class FuzzyRegionSelector extends AbstractDelegateExtent implements RegionSelector {
private final Player player;
private FuzzyRegion region;
private ArrayList<Vector> positions;
public FuzzyRegionSelector(Player player, @Nullable World world, Mask mask) {
super(new EditSessionBuilder(world)
.player(FawePlayer.wrap(player))
.changeSetNull()
.checkMemory(false)
.autoQueue(false)
.build());
this.player = player;
this.region = new FuzzyRegion(world, getExtent(), mask);
this.positions = new ArrayList<>();
new MaskTraverser(mask).reset(getExtent());
}
@Nullable
@Override
public World getWorld() {
return this.region.getWorld();
}
@Override
public void setWorld(@Nullable World world) {
EditSession extent = new EditSessionBuilder(world)
.player(FawePlayer.wrap(player))
.changeSetNull()
.checkMemory(false)
.autoQueue(false)
.build();
new ExtentTraverser(this).setNext(extent);
this.region.setWorld(world);
this.region.setExtent(extent);
new MaskTraverser(getMask()).reset(extent);
}
public Mask getMask() {
return region.getMask();
}
@Override
public boolean selectPrimary(Vector position, SelectorLimits limits) {
positions.clear();
positions.add(position);
Extent extent = getExtent();
if (extent instanceof EditSession) {
((EditSession) extent).resetLimit();
}
new MaskTraverser(getMask()).reset(extent);
this.region = new FuzzyRegion(getWorld(), getExtent(), getMask());
this.region.select((int) position.x, (int) position.y, (int) position.z);
return true;
}
@Override
public boolean selectSecondary(Vector position, SelectorLimits limits) {
this.positions.add(position);
new MaskTraverser(getMask()).reset(getExtent());
this.region.select((int) position.x, (int) position.y, (int) position.z);
return true;
}
@Override
public void explainPrimarySelection(Actor actor, LocalSession session, Vector position) {
int size = this.region.getArea();
BBC.SELECTOR_FUZZY_POS1.send(player, position, "(" + region.getArea() + ")");
}
@Override
public void explainSecondarySelection(Actor actor, LocalSession session, Vector position) {
int size = this.region.getArea();
BBC.SELECTOR_FUZZY_POS2.send(player, position, "(" + region.getArea() + ")");
}
@Override
public void explainRegionAdjust(Actor actor, LocalSession session) {
}
@Override
public BlockVector getPrimaryPosition() throws IncompleteRegionException {
if (positions.isEmpty()) {
throw new IncompleteRegionException();
}
return new BlockVector(positions.get(0));
}
@Override
public Region getRegion() throws IncompleteRegionException {
return region;
}
@Override
public Region getIncompleteRegion() {
return region;
}
@Override
public boolean isDefined() {
return true;
}
@Override
public int getArea() {
return region.getArea();
}
@Override
public void learnChanges() {
}
@Override
public void clear() {
positions.clear();
this.region = new FuzzyRegion(getWorld(), getExtent(), getMask());
}
@Override
public String getTypeName() {
return "fuzzy";
}
@Override
public List<String> getInformationLines() {
final List<String> lines = new ArrayList<String>();
for (int i = 0; i < positions.size(); i++) {
lines.add("Position " + i + ": " + positions.get(i));
}
return lines;
}
}

View File

@ -1,104 +0,0 @@
package com.boydti.fawe.object.regions.selector;
import com.boydti.fawe.object.regions.MagicRegion;
import com.sk89q.worldedit.BlockVector;
import com.sk89q.worldedit.IncompleteRegionException;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.regions.RegionSelector;
import com.sk89q.worldedit.regions.selector.limit.SelectorLimits;
import com.sk89q.worldedit.world.World;
import java.util.List;
import javax.annotation.Nullable;
public class MagicRegionSelector implements RegionSelector {
private MagicRegion region;
public MagicRegionSelector(@Nullable World world, Vector pos) {
this.region = new MagicRegion(world);
}
@Nullable
@Override
public World getWorld() {
return this.region.getWorld();
}
@Override
public void setWorld(@Nullable World world) {
this.region.setWorld(world);
}
@Override
public boolean selectPrimary(Vector position, SelectorLimits limits) {
}
@Override
public boolean selectSecondary(Vector position, SelectorLimits limits) {
}
@Override
public void explainPrimarySelection(Actor actor, LocalSession session, Vector position) {
}
@Override
public void explainSecondarySelection(Actor actor, LocalSession session, Vector position) {
}
@Override
public void explainRegionAdjust(Actor actor, LocalSession session) {
}
@Override
public BlockVector getPrimaryPosition() throws IncompleteRegionException {
return null;
}
@Override
public Region getRegion() throws IncompleteRegionException {
return null;
}
@Override
public Region getIncompleteRegion() {
return null;
}
@Override
public boolean isDefined() {
return false;
}
@Override
public int getArea() {
return 0;
}
@Override
public void learnChanges() {
}
@Override
public void clear() {
}
@Override
public String getTypeName() {
return null;
}
@Override
public List<String> getInformationLines() {
return null;
}
}

View File

@ -92,6 +92,18 @@ public class MathMan {
return (byte) ((value >> 4) & 0xF);
}
public static final byte pair8(int x, int y) {
return (byte) (x + (y << 3));
}
public static byte unpair8x(byte value) {
return (byte) (value & 0x7);
}
public static byte unpair8y(byte value) {
return (byte) ((value >> 3) & 0x7F);
}
public static final int lossyFastDivide(int a, int b) {
return (a*((1<<16)/b))>>16;
}

View File

@ -360,6 +360,10 @@ public class EditSession extends AbstractWorld implements HasFaweQueue {
return originalLimit;
}
public void resetLimit() {
this.limit = this.originalLimit.copy();
}
/**
* Returns a new limit representing how much of this edit's limit has been used so far
* @return

View File

@ -20,6 +20,8 @@
package com.sk89q.worldedit.command;
import com.boydti.fawe.config.BBC;
import com.boydti.fawe.object.mask.IdMask;
import com.boydti.fawe.object.regions.selector.FuzzyRegionSelector;
import com.google.common.base.Optional;
import com.sk89q.minecraft.util.commands.Command;
import com.sk89q.minecraft.util.commands.CommandContext;
@ -35,8 +37,10 @@ import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.blocks.BlockType;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.extension.input.ParserContext;
import com.sk89q.worldedit.extension.platform.permission.ActorSelectorLimits;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.regions.RegionOperationException;
import com.sk89q.worldedit.regions.RegionSelector;
@ -56,11 +60,11 @@ import com.sk89q.worldedit.util.formatting.StyledFragment;
import com.sk89q.worldedit.util.formatting.component.CommandListBox;
import com.sk89q.worldedit.world.World;
import com.sk89q.worldedit.world.storage.ChunkStore;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import static com.sk89q.minecraft.util.commands.Logging.LogMode.POSITION;
import static com.sk89q.minecraft.util.commands.Logging.LogMode.REGION;
@ -748,6 +752,20 @@ public class SelectionCommands {
if (limit.isPresent()) {
player.print(limit.get() + " points maximum.");
}
} else if (typeName.startsWith("fuzzy") || typeName.startsWith("magic")) {
Mask mask;
if (typeName.length() > 6) {
ParserContext parserContext = new ParserContext();
parserContext.setActor(player);
parserContext.setWorld(player.getWorld());
parserContext.setSession(session);
parserContext.setExtent(editSession);
mask = we.getMaskFactory().parseFromInput(typeName.substring(6),parserContext);
} else {
mask = new IdMask(editSession);
}
selector = new FuzzyRegionSelector(player, editSession, mask);
player.print("Fuzzy selector: Left click to select all contingent blocks, right click to add");
} else {
CommandListBox box = new CommandListBox("Selection modes");
StyledFragment contents = box.getContents();
@ -761,6 +779,7 @@ public class SelectionCommands {
box.appendCommand("sphere", "Select a sphere");
box.appendCommand("cyl", "Select a cylinder");
box.appendCommand("convex", "Select a convex polyhedral");
box.appendCommand("fuzzy[=<mask>]", "Select all connected blocks");
player.printRaw(ColorCodeBuilder.asColorCodes(box));
return;

View File

@ -58,10 +58,38 @@ public abstract class BreadthFirstSearch implements Operation {
public void visit(final Vector pos) {
Node node = new Node((int) pos.x, (int) pos.y, (int) pos.z);
if (!this.visited.containsKey(node)) {
if (!isVisited(node)) {
isVisitable(pos, pos); // Ignore this, just to initialize mask on this point
visited.put(node, 0);
queue.add(node);
addVisited(node, 0);
}
}
public void addVisited(Node node, int depth) {
visited.put(node, depth);
}
public boolean isVisited(Node node) {
return visited.containsKey(node);
}
public void cleanVisited(int layer) {
if (layer == Integer.MAX_VALUE) {
visited.clear();
return;
}
int size = visited.size();
if (size > 16384) {
Iterator<Map.Entry<Node, Integer>> iter = visited.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<Node, Integer> entry = iter.next();
Integer val = entry.getValue();
if (val < layer) {
iter.remove();
} else {
break;
}
}
}
}
@ -76,7 +104,7 @@ public abstract class BreadthFirstSearch implements Operation {
for (int layer = 0; !queue.isEmpty() && layer <= maxDepth; layer++) {
int size = queue.size();
if (layer == maxDepth) {
visited.clear();
cleanVisited(Integer.MAX_VALUE);
for (Node current : queue) {
mutable.x = current.getX();
mutable.y = current.getY();
@ -99,27 +127,15 @@ public abstract class BreadthFirstSearch implements Operation {
mutable2.z = from.getZ() + direction.z;
if (isVisitable(mutable, mutable2)) {
adjacent = new Node(mutable2.getBlockX(), mutable2.getBlockY(), mutable2.getBlockZ());
if (!visited.containsKey(adjacent)) {
visited.put(adjacent, layer);
if (!isVisited(adjacent)) {
addVisited(adjacent, layer);
queue.add(adjacent);
}
}
}
}
int lastLayer = layer - 1;
size = visited.size();
if (shouldTrim || (shouldTrim = size > 16384)) {
Iterator<Map.Entry<Node, Integer>> iter = visited.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<Node, Integer> entry = iter.next();
Integer val = entry.getValue();
if (val < lastLayer) {
iter.remove();
} else {
break;
}
}
}
cleanVisited(lastLayer);
}
return null;
}