Add transforms

This commit is contained in:
Jesse Boyd 2016-09-30 06:12:08 +10:00
parent 7deeb51ca7
commit 0251c193c1
31 changed files with 2181 additions and 108 deletions

View File

@ -169,29 +169,31 @@ public class FaweBukkit implements IFawe, Listener {
}
try {
return plugin.getQueue(world);
} catch (Throwable ignore) {}
// Disable incompatible settings
Settings.QUEUE.PARALLEL_THREADS = 1; // BukkitAPI placer is too slow to parallel thread at the chunk level
Settings.HISTORY.COMBINE_STAGES = false; // Performing a chunk copy (if possible) wouldn't be faster using the BukkitAPI
if (hasNMS) {
debug("====== NO NMS BLOCK PLACER FOUND ======");
debug("FAWE couldn't find a fast block placer");
debug("Bukkit version: " + Bukkit.getVersion());
debug("NMS label: " + plugin.getClass().getSimpleName().split("_")[1]);
debug("Fallback placer: " + BukkitQueue_All.class);
debug("=======================================");
debug("Download the version of FAWE for your platform");
debug(" - http://ci.athion.net/job/FastAsyncWorldEdit/lastSuccessfulBuild/artifact/target");
debug("=======================================");
TaskManager.IMP.laterAsync(new Runnable() {
@Override
public void run() {
MainUtil.sendAdmin("&cNo NMS placer found, see console!");
}
}, 1);
hasNMS = false;
} catch (Throwable ignore) {
// Disable incompatible settings
Settings.QUEUE.PARALLEL_THREADS = 1; // BukkitAPI placer is too slow to parallel thread at the chunk level
Settings.HISTORY.COMBINE_STAGES = false; // Performing a chunk copy (if possible) wouldn't be faster using the BukkitAPI
if (hasNMS) {
ignore.printStackTrace();
debug("====== NO NMS BLOCK PLACER FOUND ======");
debug("FAWE couldn't find a fast block placer");
debug("Bukkit version: " + Bukkit.getVersion());
debug("NMS label: " + plugin.getClass().getSimpleName().split("_")[1]);
debug("Fallback placer: " + BukkitQueue_All.class);
debug("=======================================");
debug("Download the version of FAWE for your platform");
debug(" - http://ci.athion.net/job/FastAsyncWorldEdit/lastSuccessfulBuild/artifact/target");
debug("=======================================");
TaskManager.IMP.laterAsync(new Runnable() {
@Override
public void run() {
MainUtil.sendAdmin("&cNo NMS placer found, see console!");
}
}, 1);
hasNMS = false;
}
return new BukkitQueue_All(world);
}
return new BukkitQueue_All(world);
}
/**

View File

@ -37,6 +37,7 @@ import com.sk89q.worldedit.command.ToolCommands;
import com.sk89q.worldedit.command.ToolUtilCommands;
import com.sk89q.worldedit.command.composition.SelectionCommand;
import com.sk89q.worldedit.command.tool.AreaPickaxe;
import com.sk89q.worldedit.command.tool.BrushTool;
import com.sk89q.worldedit.command.tool.LongRangeBuildTool;
import com.sk89q.worldedit.command.tool.RecursivePickaxe;
import com.sk89q.worldedit.command.tool.brush.GravityBrush;
@ -74,6 +75,7 @@ import com.sk89q.worldedit.function.visitor.RegionVisitor;
import com.sk89q.worldedit.history.change.EntityCreate;
import com.sk89q.worldedit.history.change.EntityRemove;
import com.sk89q.worldedit.math.interpolation.KochanekBartelsInterpolation;
import com.sk89q.worldedit.math.transform.AffineTransform;
import com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.regions.selector.CuboidRegionSelector;
import com.sk89q.worldedit.session.SessionManager;
@ -356,11 +358,12 @@ public class Fawe {
SchematicReader.inject();
SchematicWriter.inject();
ClipboardFormat.inject();
// Brushes
// Brushes/Tools
GravityBrush.inject(); // Fix for instant placement assumption
LongRangeBuildTool.inject();
AreaPickaxe.inject(); // Fixes
RecursivePickaxe.inject(); // Fixes
BrushTool.inject(); // Add transform
// Selectors
CuboidRegionSelector.inject(); // Translations
// Visitors
@ -412,6 +415,7 @@ public class Fawe {
NBTOutputStream.inject(); // New methods
// Math
KochanekBartelsInterpolation.inject(); // Optimizations
AffineTransform.inject(); // Optimizations
try {
CommandManager.inject(); // Async commands
PlatformManager.inject(); // Async brushes / tools

View File

@ -63,14 +63,14 @@ public class FaweCache {
* Immutable BaseBlock cache
* [ combined ] => block
*/
public final static BaseBlock[] CACHE_BLOCK = new BaseBlock[Short.MAX_VALUE];
public final static BaseBlock[] CACHE_BLOCK = new BaseBlock[Character.MAX_VALUE + 1];
/**
* Faster than java random (since it just needs to look random)
*/
public final static PseudoRandom RANDOM = new PseudoRandom();
public final static Color[] CACHE_COLOR = new Color[Short.MAX_VALUE];
public final static Color[] CACHE_COLOR = new Color[Character.MAX_VALUE + 1];
/**
* Get the cached BaseBlock object for an id/data<br>
@ -143,7 +143,7 @@ public class FaweCache {
CACHE_DATA[i] = (byte) k;
}
for (int i = 0; i < Short.MAX_VALUE; i++) {
for (int i = 0; i < Character.MAX_VALUE; i++) {
int id = i >> 4;
int data = i & 0xf;
CACHE_BLOCK[i] = new BaseBlock(id, data) {

View File

@ -47,7 +47,10 @@ public enum BBC {
GENERATING_LINK_FAILED("&cFailed to generate download link!", "Web"),
DOWNLOAD_LINK("%s", "Web"),
MASK_DISABLED("Global mask disabled", "WorldEdit.General"),
MASK("Global mask set", "WorldEdit.General"),
TRANSFORM_DISABLED("Global transform disabled", "WorldEdit.General"),
TRANSFORM("Global transform set", "WorldEdit.General"),
COMMAND_COPY("%s0 blocks were copied", "WorldEdit.Copy"),
COMMAND_CUT("%s0 blocks were cut", "WorldEdit.Cut"),
@ -104,6 +107,8 @@ public enum BBC {
BRUSH_RANGE("Brush size set", "WorldEdit.Brush"),
BRUSH_MASK_DISABLED("Brush mask disabled", "WorldEdit.Brush"),
BRUSH_MASK("Brush mask set", "WorldEdit.Brush"),
BRUSH_TRANSFORM_DISABLED("Brush transform disabled", "WorldEdit.Brush"),
BRUSH_TRANSFORM("Brush transform set", "WorldEdit.Brush"),
BRUSH_MATERIAL("Brush material set", "WorldEdit.Brush"),
ROLLBACK_ELEMENT("Undoing %s0", "WorldEdit.Rollback"),

View File

@ -8,6 +8,7 @@ import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldVectorFace;
import com.sk89q.worldedit.command.tool.BrushTool;
import com.sk89q.worldedit.command.tool.brush.Brush;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.event.platform.CommandEvent;
@ -20,11 +21,13 @@ public class CommandBrush implements Brush {
private final String command;
private final Player player;
private final int radius;
private final BrushTool tool;
public CommandBrush(Player player, String command, double radius) {
public CommandBrush(Player player, BrushTool tool, String command, double radius) {
this.player = player;
this.command = command;
this.radius = (int) radius;
this.tool = tool;
}
@Override
@ -51,4 +54,4 @@ public class CommandBrush implements Brush {
CommandManager.getInstance().handleCommand(event);
}
}
}
}

View File

@ -1,5 +1,6 @@
package com.boydti.fawe.object.brush;
import com.boydti.fawe.object.extent.TransformExtent;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.LocalConfiguration;
import com.sk89q.worldedit.LocalSession;
@ -29,6 +30,7 @@ public class DoubleActionBrushTool implements DoubleActionTraceTool {
protected static int MAX_RANGE = 500;
protected int range = -1;
private Mask mask = null;
private TransformExtent transform = null;
private DoubleActionBrush brush = null;
@Nullable
private Pattern material;
@ -50,6 +52,14 @@ public class DoubleActionBrushTool implements DoubleActionTraceTool {
return player.hasPermission(permission);
}
public TransformExtent getTransform() {
return transform;
}
public void setTransform(TransformExtent transform) {
this.transform = transform;
}
/**
* Get the filter.
*
@ -168,7 +178,9 @@ public class DoubleActionBrushTool implements DoubleActionTraceTool {
editSession.setMask(newMask);
}
}
if (transform != null) {
editSession.addTransform(transform);
}
try {
brush.build(action, editSession, target, material, size);
} catch (MaxChangedBlocksException e) {

View File

@ -0,0 +1,138 @@
package com.boydti.fawe.object.extent;
import com.boydti.fawe.FaweCache;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.Vector2D;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.extent.transform.BlockTransformExtent;
import com.sk89q.worldedit.math.transform.AffineTransform;
import com.sk89q.worldedit.math.transform.Transform;
import com.sk89q.worldedit.world.biome.BaseBiome;
import com.sk89q.worldedit.world.registry.BlockRegistry;
public class AffineTransformExtent extends TransformExtent {
private final Vector mutable = new Vector();
private final BlockRegistry registry;
private int maxy;
private AffineTransform affine;
private BaseBlock[] BLOCK_TRANSFORM;
private BaseBlock[] BLOCK_TRANSFORM_INVERSE;
private Vector min;
public AffineTransformExtent(Extent parent, BlockRegistry registry) {
super(parent);
this.maxy = parent.getMaximumPoint().getBlockY();
this.affine = new AffineTransform();
this.registry = registry;
}
private void cache() {
BLOCK_TRANSFORM = new BaseBlock[FaweCache.CACHE_BLOCK.length];
BLOCK_TRANSFORM_INVERSE = new BaseBlock[FaweCache.CACHE_BLOCK.length];
Transform inverse = affine.inverse();
for (int i = 0; i < BLOCK_TRANSFORM.length; i++) {
BaseBlock block = FaweCache.CACHE_BLOCK[i];
if (block != null) {
BLOCK_TRANSFORM[i] = BlockTransformExtent.transform(new BaseBlock(block), affine, registry);
BLOCK_TRANSFORM_INVERSE[i] = BlockTransformExtent.transform(new BaseBlock(block), inverse, registry);
}
}
}
@Override
public TransformExtent setExtent(Extent extent) {
min = null;
maxy = extent.getMaximumPoint().getBlockY();
return super.setExtent(extent);
}
public AffineTransform getAffine() {
return affine;
}
public void setAffine(AffineTransform affine) {
this.affine = affine;
cache();
}
private Vector getPos(Vector pos) {
if (min == null) {
min = new Vector(pos);
}
mutable.x = (pos.x - min.x);
mutable.y = (pos.y - min.y);
mutable.z = (pos.z - min.z);
Vector tmp = affine.apply(mutable);
tmp.x += min.x;
tmp.y += min.y;
tmp.z += min.z;
return tmp;
}
private Vector getPos(int x, int y, int z) {
if (min == null) {
min = new Vector(x, y, z);
}
mutable.x = (x - min.x);
mutable.y = (y - min.y);
mutable.z = (z - min.z);
Vector tmp = affine.apply(mutable);
tmp.x += min.x;
tmp.y += min.y;
tmp.z += min.z;
return tmp;
}
private final BaseBlock transformFast(BaseBlock block) {
return BLOCK_TRANSFORM[FaweCache.getCombined(block)];
}
private final BaseBlock transformFastInverse(BaseBlock block) {
return BLOCK_TRANSFORM_INVERSE[FaweCache.getCombined(block)];
}
@Override
public BaseBlock getLazyBlock(int x, int y, int z) {
return transformFast(super.getLazyBlock(getPos(x, y, z)));
}
@Override
public BaseBlock getLazyBlock(Vector position) {
return transformFast(super.getLazyBlock(getPos(position)));
}
@Override
public BaseBlock getBlock(Vector position) {
return transformFast(super.getBlock(getPos(position)));
}
@Override
public BaseBiome getBiome(Vector2D position) {
mutable.x = position.getBlockX();
mutable.z = position.getBlockZ();
mutable.y = 0;
return super.getBiome(getPos(mutable).toVector2D());
}
@Override
public boolean setBlock(int x, int y, int z, BaseBlock block) throws WorldEditException {
return super.setBlock(getPos(x, y, z), transformFastInverse(block));
}
@Override
public boolean setBlock(Vector location, BaseBlock block) throws WorldEditException {
return super.setBlock(getPos(location), transformFastInverse(block));
}
@Override
public boolean setBiome(Vector2D position, BaseBiome biome) {
mutable.x = position.getBlockX();
mutable.z = position.getBlockZ();
mutable.y = 0;
return super.setBiome(getPos(mutable).toVector2D(), biome);
}
}

View File

@ -0,0 +1,125 @@
package com.boydti.fawe.object.extent;
import com.boydti.fawe.object.mask.CustomMask;
import com.boydti.fawe.util.ExtentTraverser;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.extension.factory.DefaultMaskParser;
import com.sk89q.worldedit.extension.input.InputParseException;
import com.sk89q.worldedit.extension.input.NoMatchException;
import com.sk89q.worldedit.extension.input.ParserContext;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.extent.NullExtent;
import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.internal.expression.Expression;
import com.sk89q.worldedit.internal.expression.ExpressionException;
import com.sk89q.worldedit.internal.registry.InputParser;
import com.sk89q.worldedit.math.transform.AffineTransform;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Parses mask input strings.
*/
public class DefaultTransformParser extends InputParser<TransformExtent> {
public DefaultTransformParser(WorldEdit worldEdit) {
super(worldEdit);
}
private static CustomMask[] customMasks = new CustomMask[0];
public void addMask(CustomMask mask) {
checkNotNull(mask);
List<CustomMask> list = new ArrayList<>(Arrays.asList(customMasks));
list.add(mask);
customMasks = list.toArray(new CustomMask[list.size()]);
}
public List<CustomMask> getCustomMasks() {
return Arrays.asList(customMasks);
}
@Override
public TransformExtent parseFromInput(String input, ParserContext context) throws InputParseException {
Extent extent = new NullExtent();
for (String component : input.split(" ")) {
if (component.isEmpty()) {
continue;
}
extent = getTansformComponent(extent, component, context);
}
if (extent instanceof TransformExtent) {
return (TransformExtent) extent;
}
return null;
}
private TransformExtent getTansformComponent(Extent parent, String component, ParserContext context) throws InputParseException {
final char firstChar = component.charAt(0);
switch (firstChar) {
case '#':
int colon = component.indexOf(':');
if (colon != -1) {
String rest = component.substring(colon + 1);
switch (component.substring(0, colon).toLowerCase()) {
case "#pattern": {
Pattern pattern = worldEdit.getPatternFactory().parseFromInput(rest, context);
return new PatternTransform(parent, pattern);
}
case "#scale": {
try {
String[] split2 = component.split(":");
double x = Math.abs(Expression.compile(split2[1]).evaluate());
double y = Math.abs(Expression.compile(split2[2]).evaluate());
double z = Math.abs(Expression.compile(split2[3]).evaluate());
rest = rest.substring(Math.min(rest.length(), split2[1].length() + split2[2].length() + split2[3].length() + 3));
if (!rest.isEmpty()) {
parent = parseFromInput(rest, context);
}
return new ScaleTransform(parent, x, y, z);
} catch (NumberFormatException | ExpressionException e) {
throw new InputParseException("The correct format is #scale:<dx>:<dy>:<dz>");
}
}
case "#rotate": {
try {
String[] split2 = component.split(":");
double x = (Expression.compile(split2[1]).evaluate());
double y = (Expression.compile(split2[2]).evaluate());
double z = (Expression.compile(split2[3]).evaluate());
rest = rest.substring(Math.min(rest.length(), split2[1].length() + split2[2].length() + split2[3].length() + 3));
if (!rest.isEmpty()) {
parent = parseFromInput(rest, context);
}
ExtentTraverser traverser = new ExtentTraverser(parent).find(AffineTransformExtent.class);
AffineTransformExtent affine = (AffineTransformExtent) (traverser != null ? traverser.get() : null);
if (affine == null) {
parent = affine = new AffineTransformExtent(parent, context.requireWorld().getWorldData().getBlockRegistry());
}
AffineTransform transform = affine.getAffine();
transform = transform.rotateY(x);
transform = transform.rotateX(y);
transform = transform.rotateZ(z);
affine.setAffine(transform);
return (TransformExtent) parent;
} catch (NumberFormatException | ExpressionException e) {
throw new InputParseException("The correct format is #scale:<dx>:<dy>:<dz>");
}
}
default:
throw new NoMatchException("Unrecognized transform '" + component + "'");
}
}
default:
throw new NoMatchException("Unrecognized transform '" + component + "'");
}
}
public static Class<?> inject() {
return DefaultMaskParser.class;
}
}

View File

@ -0,0 +1,21 @@
package com.boydti.fawe.object.extent;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.function.pattern.Pattern;
public class PatternTransform extends TransformExtent {
private final Pattern pattern;
public PatternTransform(Extent parent, Pattern pattern) {
super(parent);
this.pattern = pattern;
}
@Override
public boolean setBlock(Vector location, BaseBlock block) throws WorldEditException {
return super.setBlock(location, pattern.apply(location));
}
}

View File

@ -0,0 +1,121 @@
package com.boydti.fawe.object.extent;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.Vector2D;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.entity.Entity;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.world.biome.BaseBiome;
import javax.annotation.Nullable;
public class ScaleTransform extends TransformExtent {
private final Vector mutable = new Vector();
private final double dx,dy,dz;
private int maxy;
private Vector min;
public ScaleTransform(Extent parent, double dx, double dy, double dz) {
super(parent);
this.dx = dx;
this.dy = dy;
this.dz = dz;
this.maxy = parent.getMaximumPoint().getBlockY();
}
@Override
public TransformExtent setExtent(Extent extent) {
min = null;
maxy = extent.getMaximumPoint().getBlockY();
return super.setExtent(extent);
}
private Vector getPos(Vector pos) {
if (min == null) {
min = new Vector(pos);
}
mutable.x = min.x + (pos.x - min.x) * dx;
mutable.y = min.y + (pos.y - min.y) * dy;
mutable.z = min.z + (pos.z - min.z) * dz;
return mutable;
}
private Vector getPos(int x, int y, int z) {
if (min == null) {
min = new Vector(x, y, z);
}
mutable.x = min.x + (x - min.x) * dx;
mutable.y = min.y + (y - min.y) * dy;
mutable.z = min.z + (z - min.z) * dz;
return mutable;
}
@Override
public boolean setBlock(Vector location, BaseBlock block) throws WorldEditException {
boolean result = false;
Vector pos = getPos(location);
double sx = pos.x;
double sy = pos.y;
double sz = pos.z;
double ex = sx + dx;
double ey = Math.min(maxy, sy + dy);
double ez = sz + dz;
for (pos.y = sy; pos.y < ey; pos.y++) {
for (pos.z = sz; pos.z < ez; pos.z++) {
for (pos.x = sx; pos.x < ex; pos.x++) {
result |= super.setBlock(pos, block);
}
}
}
return result;
}
@Override
public boolean setBiome(Vector2D position, BaseBiome biome) {
boolean result = false;
Vector pos = getPos(position.getBlockX(), 0, position.getBlockZ());
double sx = pos.x;
double sz = pos.z;
double ex = pos.x + dx;
double ez = pos.z + dz;
for (pos.z = sz; pos.z < ez; pos.z++) {
for (pos.x = sx; pos.x < ex; pos.x++) {
result |= super.setBiome(pos.toVector2D(), biome);
}
}
return result;
}
@Override
public boolean setBlock(int x1, int y1, int z1, BaseBlock block) throws WorldEditException {
boolean result = false;
Vector pos = getPos(x1, y1, z1);
double sx = pos.x;
double sy = pos.y;
double sz = pos.z;
double ex = pos.x + dx;
double ey = Math.min(maxy, sy + dy);
double ez = pos.z + dz;
for (pos.y = sy; pos.y < ey; pos.y++) {
for (pos.z = sz; pos.z < ez; pos.z++) {
for (pos.x = sx; pos.x < ex; pos.x++) {
result |= super.setBlock(pos, block);
}
}
}
return result;
}
@Nullable
@Override
public Entity createEntity(Location location, BaseEntity entity) {
Location newLoc = new Location(location.getExtent(), getPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), location.getYaw(), location.getPitch());
return super.createEntity(newLoc, entity);
}
}

View File

@ -0,0 +1,24 @@
package com.boydti.fawe.object.extent;
import com.boydti.fawe.util.ExtentTraverser;
import com.sk89q.worldedit.extent.AbstractDelegateExtent;
import com.sk89q.worldedit.extent.Extent;
import static com.google.common.base.Preconditions.checkNotNull;
public class TransformExtent extends AbstractDelegateExtent {
public TransformExtent(Extent parent) {
super(parent);
}
public TransformExtent setExtent(Extent extent) {
checkNotNull(extent);
if (getExtent() instanceof TransformExtent) {
((TransformExtent) getExtent()).setExtent(extent);
} else {
new ExtentTraverser(this).setNext(extent);
}
return this;
}
}

View File

@ -0,0 +1,36 @@
package com.boydti.fawe.object.mask;
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;
public class WallMask extends BlockMask {
private final int min, max;
public WallMask(Extent extent, Collection<BaseBlock> blocks, int requiredMin, int requiredMax) {
super(extent, blocks);
this.min = requiredMin;
this.max = requiredMax;
}
@Override
public boolean test(Vector v) {
int count = 0;
double x = v.x;
double y = v.y;
double z = v.z;
v.x = x + 1;
if (super.test(v) && ++count == min && max >= 8) { v.x = x; return true; }
v.x = x - 1;
if (super.test(v) && ++count == min && max >= 8) { v.x = x; return true; }
v.x = x;
v.z = z + 1;
if (super.test(v) && ++count == min && max >= 8) { v.z = z; return true; }
v.z = z - 1;
if (super.test(v) && ++count == min && max >= 8) { v.z = z; return true; }
v.z = z;
return count >= min && count <= max;
}
}

View File

@ -0,0 +1,59 @@
package com.boydti.fawe.object.pattern;
import com.boydti.fawe.FaweCache;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.function.pattern.AbstractPattern;
import com.sk89q.worldedit.internal.expression.Expression;
import com.sk89q.worldedit.internal.expression.ExpressionException;
import com.sk89q.worldedit.internal.expression.runtime.EvaluationException;
import com.sk89q.worldedit.regions.shape.WorldEditExpressionEnvironment;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* A mask that evaluates an expression.
*
* <p>Expressions are evaluated as {@code true} if they return a value
* greater than {@code 0}.</p>
*/
public class ExpressionPattern extends AbstractPattern {
private final Expression expression;
/**
* Create a new instance.
*
* @param expression the expression
* @throws ExpressionException thrown if there is an error with the expression
*/
public ExpressionPattern(String expression) throws ExpressionException {
checkNotNull(expression);
this.expression = Expression.compile(expression, "x", "y", "z");
}
/**
* Create a new instance.
*
* @param expression the expression
*/
public ExpressionPattern(Expression expression) {
checkNotNull(expression);
this.expression = expression;
}
@Override
public BaseBlock apply(Vector vector) {
try {
if (expression.getEnvironment() instanceof WorldEditExpressionEnvironment) {
((WorldEditExpressionEnvironment) expression.getEnvironment()).setCurrentBlock(vector);
}
double combined = expression.evaluate(vector.getX(), vector.getY(), vector.getZ());
return FaweCache.CACHE_BLOCK[(char) combined];
} catch (EvaluationException e) {
return EditSession.nullBlock;
}
}
}

View File

@ -0,0 +1,48 @@
package com.boydti.fawe.object.pattern;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.object.PseudoRandom;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.blocks.BlockType;
import com.sk89q.worldedit.function.pattern.AbstractPattern;
import com.sk89q.worldedit.function.pattern.Pattern;
public class SolidRandomOffsetPattern extends AbstractPattern {
private final PseudoRandom r = new PseudoRandom();
private final int dx, dy, dz, dx2, dy2, dz2;
private final Pattern pattern;
private final Vector mutable = new Vector();
boolean[] solid;
public SolidRandomOffsetPattern(Pattern pattern, int dx, int dy, int dz) {
this.pattern = pattern;
this.dx = dx;
this.dy = dy;
this.dz = dz;
this.dx2 = dx * 2 + 1;
this.dy2 = dy * 2 + 1;
this.dz2 = dz * 2 + 1;
solid = new boolean[Character.MAX_VALUE + 1];
for (int id = 0; id < 4096; id++) {
for (int data = 0; data < 16; data++) {
if (!BlockType.canPassThrough(id, data)) {
solid[FaweCache.getCombined(id, data)] = true;
}
}
}
}
@Override
public BaseBlock apply(Vector position) {
mutable.x = position.x + r.nextInt(dx2) - dx;
mutable.y = position.y + r.nextInt(dy2) - dy;
mutable.z = position.z + r.nextInt(dz2) - dz;
BaseBlock block = pattern.apply(mutable);
if (solid[FaweCache.getCombined(block)]) {
return block;
} else {
return pattern.apply(position);
}
}
}

View File

@ -0,0 +1,50 @@
package com.boydti.fawe.object.pattern;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.object.PseudoRandom;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.blocks.BlockType;
import com.sk89q.worldedit.function.pattern.AbstractPattern;
import com.sk89q.worldedit.function.pattern.Pattern;
public class SurfaceRandomOffsetPattern extends AbstractPattern {
private final PseudoRandom r = new PseudoRandom();
private final int dx, dy, dz, dx2, dy2, dz2;
private final Pattern pattern;
private final Vector mutable = new Vector();
boolean[] solid;
public SurfaceRandomOffsetPattern(Pattern pattern, int dx, int dy, int dz) {
this.pattern = pattern;
this.dx = dx;
this.dy = dy;
this.dz = dz;
this.dx2 = dx * 2 + 1;
this.dy2 = dy * 2 + 1;
this.dz2 = dz * 2 + 1;
solid = new boolean[Character.MAX_VALUE + 1];
for (int id = 0; id < 4096; id++) {
for (int data = 0; data < 16; data++) {
if (!BlockType.canPassThrough(id, data)) {
solid[FaweCache.getCombined(id, data)] = true;
}
}
}
}
@Override
public BaseBlock apply(Vector position) {
mutable.x = position.x + r.nextInt(dx2) - dx;
mutable.y = position.y + r.nextInt(dy2) - dy;
mutable.z = position.z + r.nextInt(dz2) - dz;
BaseBlock block = pattern.apply(mutable);
if (solid[FaweCache.getCombined(block)]) {
mutable.y++;
if (!solid[FaweCache.getCombined(pattern.apply(mutable))]) {
return block;
}
}
return pattern.apply(position);
}
}

View File

@ -0,0 +1,735 @@
package com.boydti.fawe.util;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.FlatteningPathIterator;
import java.awt.geom.IllegalPathStateException;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Vector;
/**
* <a href="https://github.com/Sciss/ShapeInterpolator/blob/master/src/main/java/de/sciss/shapeint/ShapeInterpolator.java">Original source</a><br>
* An interpolator for {@link Shape} objects.
* This class can be used to morph between the geometries
* of two relatively arbitrary shapes with the only restrictions being
* that the two different numbers of sub-paths or two shapes with
* disparate winding rules may not blend together in a pleasing
* manner.
* The ShapeEvaluator will do the best job it can if the shapes do
* not match in winding rule or number of sub-paths, but the geometry
* of the shapes may need to be adjusted by other means to make the
* shapes more like each other for best aesthetic effect.
* <p>
* Note that the process of comparing two geometries and finding similar
* structures between them to blend for the morphing operation can be
* expensive.
* Instances of this class will properly perform the necessary
* geometric analysis of their arguments on every method call and attempt
* to cache the information so that they can operate more quickly if called
* multiple times in a row on the same pair of {@code Shape} objects.
* As a result attempting to mutate a {@code Shape} object that is stored
* in one of their keyframes may not have any effect if the associated
* interpolator has already cached the geometry.
* Also, it is advisable to use different instances of {@code ShapeEvaluator}
* for every pair of keyframes being morphed so that the cached information
* can be reused as much as possible.
*/
public class ShapeInterpolator {
private Shape savedV0;
private Shape savedV1;
private Geometry geom0;
private Geometry geom1;
public static Shape apply(Shape v0, Shape v1, float fraction) {
return apply(v0, v1, fraction, false);
}
public static Shape apply(Shape v0, Shape v1, float fraction, boolean unionBounds) {
final ShapeInterpolator instance = new ShapeInterpolator();
return instance.evaluate(v0, v1, fraction, unionBounds);
}
/** Creates an interpolated shape from tight bounds. */
public Shape evaluate(Shape v0, Shape v1, float fraction) {
return evaluate(v0, v1, fraction, false);
}
/** Creates an interpolated shape.
*
* @param v0 the first shape
* @param v1 the second shape
* @param fraction the fraction from zero (just first shape) to one (just second shape)
* @param unionBounds if `true`, the shape reports bounds which are the union of
* the bounds of both shapes, if `false` it reports "tight" bounds
* using the actual interpolated path.
*/
public Shape evaluate(Shape v0, Shape v1, float fraction, boolean unionBounds) {
if (savedV0 != v0 || savedV1 != v1) {
if (savedV0 == v1 && savedV1 == v0) {
// Just swap the geometries
final Geometry tmp = geom0;
geom0 = geom1;
geom1 = tmp;
} else {
recalculate(v0, v1);
}
savedV0 = v0;
savedV1 = v1;
}
return getShape(fraction, unionBounds);
}
private void recalculate(Shape v0, Shape v1) {
geom0 = new Geometry(v0);
geom1 = new Geometry(v1);
final float[] tVals0 = geom0.getTVals();
final float[] tVals1 = geom1.getTVals();
final float[] masterTVals = mergeTVals(tVals0, tVals1);
geom0.setTVals(masterTVals);
geom1.setTVals(masterTVals);
}
private Shape getShape(float fraction, boolean unionBounds) {
return new MorphedShape(geom0, geom1, fraction, unionBounds);
}
private static float[] mergeTVals(float[] tVals0, float[] tVals1) {
final int count = sortTVals(tVals0, tVals1, null);
final float[] newTVals = new float[count];
sortTVals(tVals0, tVals1, newTVals);
return newTVals;
}
private static int sortTVals(float[] tVals0,
float[] tVals1,
float[] newTVals) {
int i0 = 0;
int i1 = 0;
int numTVals = 0;
while (i0 < tVals0.length && i1 < tVals1.length) {
final float t0 = tVals0[i0];
final float t1 = tVals1[i1];
if (t0 <= t1) {
if (newTVals != null) {
newTVals[numTVals] = t0;
}
i0++;
}
if (t1 <= t0) {
if (newTVals != null) {
newTVals[numTVals] = t1;
}
i1++;
}
numTVals++;
}
return numTVals;
}
private static float interp(float v0, float v1, float t) {
return (v0 + ((v1 - v0) * t));
}
private static class Geometry {
static final float THIRD = (1f / 3f);
static final float MIN_LEN = 0.001f;
final int windingRule;
float[] bezierCoordinates;
int numCoordinates;
float[] myTVals;
public Geometry(Shape s) {
// Multiple of 6 plus 2 more for initial move-to
bezierCoordinates = new float[20];
final PathIterator pi = s.getPathIterator(null);
windingRule = pi.getWindingRule();
if (pi.isDone()) {
// We will have 1 segment and it will be all zeros
// It will have 8 coordinates (2 for move-to, 6 for cubic)
numCoordinates = 8;
}
final float[] coordinates = new float[6];
int type = pi.currentSegment(coordinates);
pi.next();
if (type != PathIterator.SEG_MOVETO) {
throw new IllegalPathStateException("missing initial move-to");
}
float curX, curY, movX, movY;
bezierCoordinates[0] = curX = movX = coordinates[0];
bezierCoordinates[1] = curY = movY = coordinates[1];
float newX, newY;
final Vector<Point2D.Float> savedPathEndPoints = new Vector<Point2D.Float>();
numCoordinates = 2;
while (!pi.isDone()) {
switch (pi.currentSegment(coordinates)) {
case PathIterator.SEG_MOVETO:
if (curX != movX || curY != movY) {
appendLineTo(curX, curY, movX, movY);
curX = movX;
curY = movY;
}
newX = coordinates[0];
newY = coordinates[1];
if (curX != newX || curY != newY) {
savedPathEndPoints.add(new Point2D.Float(movX, movY));
appendLineTo(curX, curY, newX, newY);
curX = movX = newX;
curY = movY = newY;
}
break;
case PathIterator.SEG_CLOSE:
if (curX != movX || curY != movY) {
appendLineTo(curX, curY, movX, movY);
curX = movX;
curY = movY;
}
break;
case PathIterator.SEG_LINETO:
newX = coordinates[0];
newY = coordinates[1];
appendLineTo(curX, curY, newX, newY);
curX = newX;
curY = newY;
break;
case PathIterator.SEG_QUADTO:
final float ctrlX = coordinates[0];
final float ctrlY = coordinates[1];
newX = coordinates[2];
newY = coordinates[3];
appendQuadTo(curX, curY, ctrlX, ctrlY, newX, newY);
curX = newX;
curY = newY;
break;
case PathIterator.SEG_CUBICTO:
newX = coordinates[4];
newY = coordinates[5];
appendCubicTo(
coordinates[0], coordinates[1],
coordinates[2], coordinates[3],
newX, newY);
curX = newX;
curY = newY;
break;
}
pi.next();
}
// Add closing segment if either:
// - we only have initial move-to - expand it to an empty cubic
// - or we are not back to the starting point
if ((numCoordinates < 8) || curX != movX || curY != movY) {
appendLineTo(curX, curY, movX, movY);
curX = movX;
curY = movY;
}
// Now retrace our way back through all of the connecting
// inter-sub-path segments
for (int i = savedPathEndPoints.size()-1; i >= 0; i--) {
final Point2D.Float p = savedPathEndPoints.get(i);
newX = p.x;
newY = p.y;
if (curX != newX || curY != newY) {
appendLineTo(curX, curY, newX, newY);
curX = newX;
curY = newY;
}
}
// Now find the segment endpoint with the smallest Y coordinate
int minPt = 0;
float minX = bezierCoordinates[0];
float minY = bezierCoordinates[1];
for (int ci = 6; ci < numCoordinates; ci += 6) {
float x = bezierCoordinates[ci];
float y = bezierCoordinates[ci + 1];
if (y < minY || (y == minY && x < minX)) {
minPt = ci;
minX = x;
minY = y;
}
}
// If the smallest Y coordinate is not the first coordinate,
// rotate the points so that it is...
if (minPt > 0) {
// Keep in mind that first 2 coordinates == last 2 coordinates
final float[] newCoordinates = new float[numCoordinates];
// Copy all coordinates from minPt to the end of the
// array to the beginning of the new array
System.arraycopy(bezierCoordinates, minPt,
newCoordinates, 0,
numCoordinates - minPt);
// Now we do not want to copy 0,1 as they are duplicates
// of the last 2 coordinates which we just copied. So
// we start the source copy at index 2, but we still
// copy a full minPt coordinates which copies the two
// coordinates that were at minPt to the last two elements
// of the array, thus ensuring that thew new array starts
// and ends with the same pair of coordinates...
System.arraycopy(bezierCoordinates, 2,
newCoordinates, numCoordinates - minPt,
minPt);
bezierCoordinates = newCoordinates;
}
/* Clockwise enforcement:
* - This technique is based on the formula for calculating
* the area of a Polygon. The standard formula is:
* Area(Poly) = 1/2 * sum(x[i]*y[i+1] - x[i+1]y[i])
* - The returned area is negative if the polygon is
* "mostly clockwise" and positive if the polygon is
* "mostly counter-clockwise".
* - One failure mode of the Area calculation is if the
* Polygon is self-intersecting. This is due to the
* fact that the areas on each side of the self-intersection
* are bounded by segments which have opposite winding
* direction. Thus, those areas will have opposite signs
* on the accumulation of their area summations and end
* up canceling each other out partially.
* - This failure mode of the algorithm in determining the
* exact magnitude of the area is not actually a big problem
* for our needs here since we are only using the sign of
* the resulting area to figure out the overall winding
* direction of the path. If self-intersections cause
* different parts of the path to disagree as to the
* local winding direction, that is no matter as we just
* wait for the final answer to tell us which winding
* direction had greater representation. If the final
* result is zero then the path was equal parts clockwise
* and counter-clockwise and we do not care about which
* way we order it as either way will require half of the
* path to unwind and re-wind itself.
*/
float area = 0;
// Note that first and last points are the same so we
// do not need to process coordinates[0,1] against coordinates[n-2,n-1]
curX = bezierCoordinates[0];
curY = bezierCoordinates[1];
for (int i = 2; i < numCoordinates; i += 2) {
newX = bezierCoordinates[i];
newY = bezierCoordinates[i + 1];
area += curX * newY - newX * curY;
curX = newX;
curY = newY;
}
if (area < 0) {
/* The area is negative so the shape was clockwise
* in a Euclidean sense. But, our screen coordinate
* systems have the origin in the upper left so they
* are flipped. Thus, this path "looks" ccw on the
* screen so we are flipping it to "look" clockwise.
* Note that the first and last points are the same
* so we do not need to swap them.
* (Not that it matters whether the paths end up cw
* or ccw in the end as long as all of them are the
* same, but above we called this section "Clockwise
* Enforcement", so we do not want to be liars. ;-)
*/
// Note that [0,1] do not need to be swapped with [n-2,n-1]
// So first pair to swap is [2,3] and [n-4,n-3]
int i = 2;
int j = numCoordinates - 4;
while (i < j) {
curX = bezierCoordinates[i];
curY = bezierCoordinates[i + 1];
bezierCoordinates[i] = bezierCoordinates[j];
bezierCoordinates[i + 1] = bezierCoordinates[j + 1];
bezierCoordinates[j] = curX;
bezierCoordinates[j + 1] = curY;
i += 2;
j -= 2;
}
}
}
private void appendLineTo(float x0, float y0,
float x1, float y1) {
appendCubicTo(// A third of the way from xy0 to xy1:
interp(x0, x1, THIRD),
interp(y0, y1, THIRD),
// A third of the way from xy1 back to xy0:
interp(x1, x0, THIRD),
interp(y1, y0, THIRD),
x1, y1);
}
private void appendQuadTo(float x0, float y0,
float ctrlX, float ctrlY,
float x1, float y1) {
appendCubicTo(// A third of the way from ctrl X/Y back to xy0:
interp(ctrlX, x0, THIRD),
interp(ctrlY, y0, THIRD),
// A third of the way from ctrl X/Y to xy1:
interp(ctrlX, x1, THIRD),
interp(ctrlY, y1, THIRD),
x1, y1);
}
private void appendCubicTo(float ctrlX1, float ctrlY1,
float ctrlX2, float ctrlY2,
float x1, float y1) {
if (numCoordinates + 6 > bezierCoordinates.length) {
// Keep array size to a multiple of 6 plus 2
int newsize = (numCoordinates - 2) * 2 + 2;
final float[] newCoordinates = new float[newsize];
System.arraycopy(bezierCoordinates, 0, newCoordinates, 0, numCoordinates);
bezierCoordinates = newCoordinates;
}
bezierCoordinates[numCoordinates++] = ctrlX1;
bezierCoordinates[numCoordinates++] = ctrlY1;
bezierCoordinates[numCoordinates++] = ctrlX2;
bezierCoordinates[numCoordinates++] = ctrlY2;
bezierCoordinates[numCoordinates++] = x1;
bezierCoordinates[numCoordinates++] = y1;
}
public int getWindingRule() {
return windingRule;
}
public int getNumCoordinates() {
return numCoordinates;
}
public float getCoordinate(int i) {
return bezierCoordinates[i];
}
public float[] getTVals() {
if (myTVals != null) {
return myTVals;
}
// assert(numCoordinates >= 8);
// assert(((numCoordinates - 2) % 6) == 0);
final float[] tVals = new float[(numCoordinates - 2) / 6 + 1];
// First calculate total "length" of path
// Length of each segment is averaged between
// the length between the endpoints (a lower bound for a cubic)
// and the length of the control polygon (an upper bound)
float segX = bezierCoordinates[0];
float segY = bezierCoordinates[1];
float tLen = 0;
int ci = 2;
int ti = 0;
while (ci < numCoordinates) {
float prevX, prevY, newX, newY;
prevX = segX;
prevY = segY;
newX = bezierCoordinates[ci++];
newY = bezierCoordinates[ci++];
prevX -= newX;
prevY -= newY;
float len = (float) Math.sqrt(prevX * prevX + prevY * prevY);
prevX = newX;
prevY = newY;
newX = bezierCoordinates[ci++];
newY = bezierCoordinates[ci++];
prevX -= newX;
prevY -= newY;
len += (float) Math.sqrt(prevX * prevX + prevY * prevY);
prevX = newX;
prevY = newY;
newX = bezierCoordinates[ci++];
newY = bezierCoordinates[ci++];
prevX -= newX;
prevY -= newY;
len += (float) Math.sqrt(prevX * prevX + prevY * prevY);
// len is now the total length of the control polygon
segX -= newX;
segY -= newY;
len += (float) Math.sqrt(segX * segX + segY * segY);
// len is now sum of linear length and control polygon length
len /= 2;
// len is now average of the two lengths
/* If the result is zero length then we will have problems
* below trying to do the math and bookkeeping to split
* the segment or pair it against the segments in the
* other shape. Since these lengths are just estimates
* to map the segments of the two shapes onto corresponding
* segments of "approximately the same length", we will
* simply modify the length of this segment to be at least
* a minimum value and it will simply grow from zero or
* near zero length to a non-trivial size as it morphs.
*/
if (len < MIN_LEN) {
len = MIN_LEN;
}
tLen += len;
tVals[ti++] = tLen;
segX = newX;
segY = newY;
}
// Now set tVals for each segment to its proportional
// part of the length
float prevT = tVals[0];
tVals[0] = 0;
for (ti = 1; ti < tVals.length - 1; ti++) {
final float nextT = tVals[ti];
tVals[ti] = prevT / tLen;
prevT = nextT;
}
tVals[ti] = 1;
return (myTVals = tVals);
}
public void setTVals(float[] newTVals) {
final float[] oldCoordinates = bezierCoordinates;
final float[] newCoordinates = new float[2 + (newTVals.length - 1) * 6];
final float[] oldTVals = getTVals();
int oldCi = 0;
float x0, xc0, xc1, x1;
float y0, yc0, yc1, y1;
x0 = xc0 = xc1 = x1 = oldCoordinates[oldCi++];
y0 = yc0 = yc1 = y1 = oldCoordinates[oldCi++];
int newCi = 0;
newCoordinates[newCi++] = x0;
newCoordinates[newCi++] = y0;
float t0 = 0;
float t1 = 0;
int oldTi = 1;
int newTi = 1;
while (newTi < newTVals.length) {
if (t0 >= t1) {
x0 = x1;
y0 = y1;
xc0 = oldCoordinates[oldCi++];
yc0 = oldCoordinates[oldCi++];
xc1 = oldCoordinates[oldCi++];
yc1 = oldCoordinates[oldCi++];
x1 = oldCoordinates[oldCi++];
y1 = oldCoordinates[oldCi++];
t1 = oldTVals [oldTi++];
}
float nt = newTVals[newTi++];
// assert(nt > t0);
if (nt < t1) {
// Make nt proportional to [t0 => t1] range
float relT = (nt - t0) / (t1 - t0);
newCoordinates[newCi++] = x0 = interp(x0, xc0, relT);
newCoordinates[newCi++] = y0 = interp(y0, yc0, relT);
xc0 = interp(xc0, xc1, relT);
yc0 = interp(yc0, yc1, relT);
xc1 = interp(xc1, x1 , relT);
yc1 = interp(yc1, y1 , relT);
newCoordinates[newCi++] = x0 = interp(x0, xc0, relT);
newCoordinates[newCi++] = y0 = interp(y0, yc0, relT);
xc0 = interp(xc0, xc1, relT);
yc0 = interp(yc0, yc1, relT);
newCoordinates[newCi++] = x0 = interp(x0, xc0, relT);
newCoordinates[newCi++] = y0 = interp(y0, yc0, relT);
} else {
newCoordinates[newCi++] = xc0;
newCoordinates[newCi++] = yc0;
newCoordinates[newCi++] = xc1;
newCoordinates[newCi++] = yc1;
newCoordinates[newCi++] = x1;
newCoordinates[newCi++] = y1;
}
t0 = nt;
}
bezierCoordinates = newCoordinates;
numCoordinates = newCoordinates.length;
myTVals = newTVals;
}
}
private static class MorphedShape implements Shape {
final Geometry geom0;
final Geometry geom1;
final float t;
final boolean unionBounds;
MorphedShape(Geometry geom0, Geometry geom1, float t, boolean unionBounds) {
this.geom0 = geom0;
this.geom1 = geom1;
this.t = t;
this.unionBounds= unionBounds;
}
public Rectangle getBounds() {
return getBounds2D().getBounds();
}
public Rectangle2D getBounds2D() {
final int n = geom0.getNumCoordinates();
float xMin, yMin, xMax, yMax;
if (unionBounds) {
xMin = xMax = geom0.getCoordinate(0);
yMin = yMax = geom0.getCoordinate(1);
for (int i = 2; i < n; i += 2) {
final float x = geom0.getCoordinate(i );
final float y = geom0.getCoordinate(i+1);
if (xMin > x) {
xMin = x;
}
if (yMin > y) {
yMin = y;
}
if (xMax < x) {
xMax = x;
}
if (yMax < y) {
yMax = y;
}
}
final int m = geom1.getNumCoordinates();
for (int i = 0; i < m; i += 2) {
final float x = geom1.getCoordinate(i );
final float y = geom1.getCoordinate(i+1);
if (xMin > x) {
xMin = x;
}
if (yMin > y) {
yMin = y;
}
if (xMax < x) {
xMax = x;
}
if (yMax < y) {
yMax = y;
}
}
} else {
xMin = xMax = interp(geom0.getCoordinate(0), geom1.getCoordinate(0), t);
yMin = yMax = interp(geom0.getCoordinate(1), geom1.getCoordinate(1), t);
for (int i = 2; i < n; i += 2) {
final float x = interp(geom0.getCoordinate(i ), geom1.getCoordinate(i ), t);
final float y = interp(geom0.getCoordinate(i+1), geom1.getCoordinate(i+1), t);
if (xMin > x) {
xMin = x;
}
if (yMin > y) {
yMin = y;
}
if (xMax < x) {
xMax = x;
}
if (yMax < y) {
yMax = y;
}
}
}
return new Rectangle2D.Float(xMin, yMin, xMax - xMin, yMax - yMin);
}
public boolean contains(double x, double y) {
return Path2D.contains(getPathIterator(null), x, y);
}
public boolean contains(Point2D p) {
return Path2D.contains(getPathIterator(null), p);
}
public boolean intersects(double x, double y, double w, double h) {
return Path2D.intersects(getPathIterator(null), x, y, w, h);
}
public boolean intersects(Rectangle2D r) {
return Path2D.intersects(getPathIterator(null), r);
}
public boolean contains(double x, double y, double width, double height) {
return Path2D.contains(getPathIterator(null), x, y, width, height);
}
public boolean contains(Rectangle2D r) {
return Path2D.contains(getPathIterator(null), r);
}
public PathIterator getPathIterator(AffineTransform at) {
return new Iterator(at, geom0, geom1, t);
}
public PathIterator getPathIterator(AffineTransform at, double flatness) {
return new FlatteningPathIterator(getPathIterator(at), flatness);
}
}
private static class Iterator implements PathIterator {
AffineTransform at;
Geometry g0;
Geometry g1;
float t;
int cIndex;
public Iterator(AffineTransform at,
Geometry g0, Geometry g1,
float t) {
this.at = at;
this.g0 = g0;
this.g1 = g1;
this.t = t;
}
/**
* @{inheritDoc}
*/
public int getWindingRule() {
return (t < 0.5 ? g0.getWindingRule() : g1.getWindingRule());
}
/**
* @{inheritDoc}
*/
public boolean isDone() {
return (cIndex > g0.getNumCoordinates());
}
/**
* @{inheritDoc}
*/
public void next() {
if (cIndex == 0) {
cIndex = 2;
} else {
cIndex += 6;
}
}
/**
* @{inheritDoc}
*/
public int currentSegment(float[] coordinates) {
int type;
int n;
if (cIndex == 0) {
type = SEG_MOVETO;
n = 2;
} else if (cIndex >= g0.getNumCoordinates()) {
type = SEG_CLOSE;
n = 0;
} else {
type = SEG_CUBICTO;
n = 6;
}
if (n > 0) {
for (int i = 0; i < n; i++) {
coordinates[i] = interp(
g0.getCoordinate(cIndex + i),
g1.getCoordinate(cIndex + i),
t);
}
if (at != null) {
at.transform(coordinates, 0, coordinates, 0, n / 2);
}
}
return type;
}
public int currentSegment(double[] coordinates) {
final float[] temp = new float[6];
final int res = currentSegment(temp);
for (int i = 0; i < 6; i++) {
coordinates[i] = temp[i];
}
return res;
}
}
}

View File

@ -45,6 +45,7 @@ import com.boydti.fawe.object.extent.FastWorldEditExtent;
import com.boydti.fawe.object.extent.FaweRegionExtent;
import com.boydti.fawe.object.extent.NullExtent;
import com.boydti.fawe.object.extent.ProcessedWEExtent;
import com.boydti.fawe.object.extent.TransformExtent;
import com.boydti.fawe.object.mask.ResettableMask;
import com.boydti.fawe.object.progress.DefaultProgressTracker;
import com.boydti.fawe.util.ExtentTraverser;
@ -572,6 +573,21 @@ public class EditSession extends AbstractWorld implements HasFaweQueue {
return maskingExtent != null ? maskingExtent.get().getMask() : null;
}
public void addTransform(TransformExtent transform) {
if (transform == null) {
ExtentTraverser<AbstractDelegateExtent> traverser = new ExtentTraverser(this.extent).find(TransformExtent.class);
AbstractDelegateExtent next = extent;
while (traverser != null && traverser.get() instanceof TransformExtent) {
traverser = traverser.next();
next = traverser.get();
}
this.extent = next;
return;
} else {
this.extent = transform.setExtent(extent);
}
}
/**
* Set a mask.
*

View File

@ -29,6 +29,7 @@ import com.boydti.fawe.object.brush.DoubleActionBrushTool;
import com.boydti.fawe.object.changeset.DiskStorageHistory;
import com.boydti.fawe.object.changeset.FaweChangeSet;
import com.boydti.fawe.object.clipboard.DiskOptimizedClipboard;
import com.boydti.fawe.object.extent.TransformExtent;
import com.boydti.fawe.util.EditSessionBuilder;
import com.boydti.fawe.util.MainUtil;
import com.boydti.fawe.wrappers.WorldWrapper;
@ -138,6 +139,7 @@ public class LocalSession {
private transient int cuiVersion = -1;
private transient boolean fastMode = false;
private transient Mask mask;
private TransformExtent transform = null;
private transient TimeZone timezone = TimeZone.getDefault();
private transient World currentWorld;
@ -1203,7 +1205,12 @@ public class LocalSession {
getBlockChangeLimit(), blockBag, player);
editSession.setFastMode(fastMode);
Request.request().setEditSession(editSession);
editSession.setMask(mask);
if (mask != null) {
editSession.setMask(mask);
}
if (transform != null) {
editSession.addTransform(transform);
}
return editSession;
}
@ -1254,6 +1261,14 @@ public class LocalSession {
setMask(mask != null ? Masks.wrap(mask) : null);
}
public TransformExtent getTransform() {
return transform;
}
public void setTransform(TransformExtent transform) {
this.transform = transform;
}
public static Class<?> inject() {
return LocalSession.class;
}

View File

@ -390,7 +390,7 @@ public class BrushCommands {
public void command(Player player, LocalSession session, EditSession editSession, @Optional("5") double radius, CommandContext args) throws WorldEditException {
BrushTool tool = session.getBrushTool(player.getItemInHand());
String cmd = args.getJoinedStrings(1);
tool.setBrush(new CommandBrush(player, cmd, radius), "worldedit.brush.copy");
tool.setBrush(new CommandBrush(player, tool, cmd, radius), "worldedit.brush.copy");
BBC.BRUSH_COMMAND.send(player, cmd);
}

View File

@ -300,8 +300,8 @@ public class ClipboardCommands {
if (selectPasted) {
Vector clipboardOffset = clipboard.getRegion().getMinimumPoint().subtract(clipboard.getOrigin());
Vector realTo = to.add(holder.getTransform().apply(clipboardOffset));
Vector max = realTo.add(holder.getTransform().apply(region.getMaximumPoint().subtract(region.getMinimumPoint())));
Vector realTo = to.add(new Vector(holder.getTransform().apply(clipboardOffset)));
Vector max = realTo.add(new Vector(holder.getTransform().apply(region.getMaximumPoint().subtract(region.getMinimumPoint()))));
RegionSelector selector = new CuboidRegionSelector(player.getWorld(), realTo, max);
session.setRegionSelector(player.getWorld(), selector);
selector.learnChanges();

View File

@ -91,7 +91,7 @@ public class FlattenedClipboardTransform {
maximum.setZ(minimum.getZ()) };
for (int i = 0; i < corners.length; i++) {
corners[i] = transformAround.apply(corners[i]);
corners[i] = transformAround.apply(new Vector(corners[i]));
}
Vector newMinimum = corners[0];

View File

@ -1,6 +1,8 @@
package com.sk89q.worldedit.command;
import com.boydti.fawe.config.BBC;
import com.boydti.fawe.object.extent.DefaultTransformParser;
import com.boydti.fawe.object.extent.TransformExtent;
import com.sk89q.minecraft.util.commands.Command;
import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandPermissions;
@ -20,6 +22,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
public class GeneralCommands {
private final WorldEdit worldEdit;
private final DefaultTransformParser transformParser;
/**
* Create a new instance.
@ -29,6 +32,7 @@ public class GeneralCommands {
public GeneralCommands(WorldEdit worldEdit) {
checkNotNull(worldEdit);
this.worldEdit = worldEdit;
transformParser = new DefaultTransformParser(worldEdit);
}
@Command(
@ -102,7 +106,7 @@ public class GeneralCommands {
public void gmask(Player player, LocalSession session, EditSession editSession, @Optional CommandContext context) throws WorldEditException {
if (context == null || context.argsLength() == 0) {
session.setMask((Mask) null);
BBC.BRUSH_MASK_DISABLED.send(player);
BBC.MASK_DISABLED.send(player);
} else {
ParserContext parserContext = new ParserContext();
parserContext.setActor(player);
@ -111,7 +115,31 @@ public class GeneralCommands {
parserContext.setExtent(editSession);
Mask mask = worldEdit.getMaskFactory().parseFromInput(context.getJoinedStrings(0), parserContext);
session.setMask(mask);
BBC.BRUSH_MASK.send(player);
BBC.MASK.send(player);
}
}
@Command(
aliases = { "/gtransform", "gtransform" },
usage = "[transform]",
desc = "Set the global transform",
min = 0,
max = -1
)
@CommandPermissions("worldedit.global-trasnform")
public void gtransform(Player player, LocalSession session, EditSession editSession, @Optional CommandContext context) throws WorldEditException {
if (context == null || context.argsLength() == 0) {
session.setTransform(null);
BBC.TRANSFORM_DISABLED.send(player);
} else {
ParserContext parserContext = new ParserContext();
parserContext.setActor(player);
parserContext.setWorld(player.getWorld());
parserContext.setSession(session);
parserContext.setExtent(editSession);
TransformExtent transform = transformParser.parseFromInput(context.getJoinedStrings(0), parserContext);
session.setTransform(transform);
BBC.TRANSFORM.send(player);
}
}

View File

@ -2,6 +2,8 @@ package com.sk89q.worldedit.command;
import com.boydti.fawe.config.BBC;
import com.boydti.fawe.object.brush.DoubleActionBrushTool;
import com.boydti.fawe.object.extent.DefaultTransformParser;
import com.boydti.fawe.object.extent.TransformExtent;
import com.sk89q.minecraft.util.commands.Command;
import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandPermissions;
@ -22,9 +24,11 @@ import com.sk89q.worldedit.util.command.parametric.Optional;
*/
public class ToolUtilCommands {
private final WorldEdit we;
private final DefaultTransformParser transformParser;
public ToolUtilCommands(WorldEdit we) {
this.we = we;
this.transformParser = new DefaultTransformParser(we);
}
@Command(
@ -95,6 +99,42 @@ public class ToolUtilCommands {
}
}
@Command(
aliases = { "transform" },
usage = "[transform]",
desc = "Set the brush transform",
min = 0,
max = -1
)
@CommandPermissions("worldedit.brush.options.transform")
public void transform(Player player, LocalSession session, EditSession editSession, @Optional CommandContext context) throws WorldEditException {
Tool tool = session.getTool(player.getItemInHand());
if (tool == null) {
return;
}
if (context == null || context.argsLength() == 0) {
if (tool instanceof BrushTool) {
((BrushTool) tool).setTransform(null);
} else if (tool instanceof DoubleActionBrushTool) {
((DoubleActionBrushTool) tool).setTransform(null);
}
BBC.BRUSH_TRANSFORM_DISABLED.send(player);
} else {
ParserContext parserContext = new ParserContext();
parserContext.setActor(player);
parserContext.setWorld(player.getWorld());
parserContext.setSession(session);
parserContext.setExtent(editSession);
TransformExtent transform = transformParser.parseFromInput(context.getJoinedStrings(0), parserContext);
if (tool instanceof BrushTool) {
((BrushTool) tool).setTransform(transform);
} else if (tool instanceof DoubleActionBrushTool) {
((DoubleActionBrushTool) tool).setTransform(transform);
}
BBC.BRUSH_TRANSFORM.send(player);
}
}
@Command(
aliases = { "mat", "material" },
usage = "[pattern]",
@ -104,7 +144,12 @@ public class ToolUtilCommands {
)
@CommandPermissions("worldedit.brush.options.material")
public void material(Player player, LocalSession session, EditSession editSession, Pattern pattern) throws WorldEditException {
session.getBrushTool(player.getItemInHand()).setFill(pattern);
Tool tool = session.getTool(player.getItemInHand());
if (tool instanceof BrushTool) {
((BrushTool) tool).setMask(null);
} else if (tool instanceof DoubleActionBrushTool) {
((DoubleActionBrushTool) tool).setFill(pattern);
}
BBC.BRUSH_MATERIAL.send(player);
}
@ -118,7 +163,12 @@ public class ToolUtilCommands {
@CommandPermissions("worldedit.brush.options.range")
public void range(Player player, LocalSession session, EditSession editSession, CommandContext args) throws WorldEditException {
int range = args.getInteger(0);
session.getBrushTool(player.getItemInHand()).setRange(range);
Tool tool = session.getTool(player.getItemInHand());
if (tool instanceof BrushTool) {
((BrushTool) tool).setMask(null);
} else if (tool instanceof DoubleActionBrushTool) {
((DoubleActionBrushTool) tool).setRange(range);
}
BBC.BRUSH_RANGE.send(player);
}
@ -135,7 +185,12 @@ public class ToolUtilCommands {
int radius = args.getInteger(0);
we.checkMaxBrushRadius(radius);
session.getBrushTool(player.getItemInHand()).setSize(radius);
Tool tool = session.getTool(player.getItemInHand());
if (tool instanceof BrushTool) {
((BrushTool) tool).setMask(null);
} else if (tool instanceof DoubleActionBrushTool) {
((DoubleActionBrushTool) tool).setSize(radius);
}
BBC.BRUSH_SIZE.send(player);
}

View File

@ -0,0 +1,201 @@
package com.sk89q.worldedit.command.tool;
import com.boydti.fawe.object.extent.TransformExtent;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.LocalConfiguration;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.WorldVector;
import com.sk89q.worldedit.command.tool.brush.Brush;
import com.sk89q.worldedit.command.tool.brush.SphereBrush;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.extension.platform.Platform;
import com.sk89q.worldedit.extent.inventory.BlockBag;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.mask.MaskIntersection;
import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.session.request.Request;
import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Builds a shape at the place being looked at.
*/
public class BrushTool implements TraceTool {
protected static int MAX_RANGE = 500;
protected int range = -1;
private Mask mask = null;
private TransformExtent transform = null;
private Brush brush = new SphereBrush();
@Nullable
private Pattern material;
private double size = 1;
private String permission;
/**
* Construct the tool.
*
* @param permission the permission to check before use is allowed
*/
public BrushTool(String permission) {
checkNotNull(permission);
this.permission = permission;
}
@Override
public boolean canUse(Actor player) {
return player.hasPermission(permission);
}
public TransformExtent getTransform() {
return transform;
}
public void setTransform(TransformExtent transform) {
this.transform = transform;
}
/**
* Get the filter.
*
* @return the filter
*/
public Mask getMask() {
return mask;
}
/**
* Set the block filter used for identifying blocks to replace.
*
* @param filter the filter to set
*/
public void setMask(Mask filter) {
this.mask = filter;
}
/**
* Set the brush.
*
* @param brush tbe brush
* @param permission the permission
*/
public void setBrush(Brush brush, String permission) {
this.brush = brush;
this.permission = permission;
}
/**
* Get the current brush.
*
* @return the current brush
*/
public Brush getBrush() {
return brush;
}
/**
* Set the material.
*
* @param material the material
*/
public void setFill(@Nullable Pattern material) {
this.material = material;
}
/**
* Get the material.
*
* @return the material
*/
@Nullable public Pattern getMaterial() {
return material;
}
/**
* Get the set brush size.
*
* @return a radius
*/
public double getSize() {
return size;
}
/**
* Set the set brush size.
*
* @param radius a radius
*/
public void setSize(double radius) {
this.size = radius;
}
/**
* Get the set brush range.
*
* @return the range of the brush in blocks
*/
public int getRange() {
return (range < 0) ? MAX_RANGE : Math.min(range, MAX_RANGE);
}
/**
* Set the set brush range.
*
* @param range the range of the brush in blocks
*/
public void setRange(int range) {
this.range = range;
}
@Override
public boolean actPrimary(Platform server, LocalConfiguration config, Player player, LocalSession session) {
WorldVector target = null;
target = player.getBlockTrace(getRange(), true);
if (target == null) {
player.printError("No block in sight!");
return true;
}
BlockBag bag = session.getBlockBag(player);
EditSession editSession = session.createEditSession(player);
Request.request().setEditSession(editSession);
if (mask != null) {
Mask existingMask = editSession.getMask();
if (existingMask == null) {
editSession.setMask(mask);
} else if (existingMask instanceof MaskIntersection) {
((MaskIntersection) existingMask).add(mask);
} else {
MaskIntersection newMask = new MaskIntersection(existingMask);
newMask.add(mask);
editSession.setMask(newMask);
}
}
if (transform != null) {
editSession.addTransform(transform);
}
try {
brush.build(editSession, target, material, size);
} catch (MaxChangedBlocksException e) {
player.printError("Max blocks change limit reached.");
} finally {
if (bag != null) {
bag.flushChanges();
}
session.remember(editSession);
}
return true;
}
public static Class<?> inject() {
return BrushTool.class;
}
}

View File

@ -7,12 +7,15 @@ import com.boydti.fawe.object.mask.DataMask;
import com.boydti.fawe.object.mask.IdDataMask;
import com.boydti.fawe.object.mask.IdMask;
import com.boydti.fawe.object.mask.RadiusMask;
import com.boydti.fawe.object.mask.WallMask;
import com.boydti.fawe.object.mask.XAxisMask;
import com.boydti.fawe.object.mask.YAxisMask;
import com.boydti.fawe.object.mask.ZAxisMask;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.IncompleteRegionException;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.extension.input.InputParseException;
import com.sk89q.worldedit.extension.input.NoMatchException;
import com.sk89q.worldedit.extension.input.ParserContext;
@ -102,6 +105,12 @@ public class DefaultMaskParser extends InputParser<Mask> {
final char firstChar = component.charAt(0);
switch (firstChar) {
case '#':
int colon = component.indexOf(':');
if (colon != -1) {
String rest = component.substring(colon + 1);
component = component.substring(0, colon);
masks.add(getBlockMaskComponent(masks, rest, context));
}
switch (component.toLowerCase()) {
case "#existing":
return new ExistingBlockMask(extent);
@ -131,6 +140,13 @@ public class DefaultMaskParser extends InputParser<Mask> {
return new DataMask(extent);
case "#iddata":
return new IdDataMask(extent);
case "#wall":
masks.add(new ExistingBlockMask(extent));
BlockMask matchAir = new BlockMask(extent, EditSession.nullBlock);
return new WallMask(extent, Arrays.asList(new BaseBlock(0)), 1, 8);
case "#surface":
masks.add(new ExistingBlockMask(extent));
return new AdjacentMask(extent, Arrays.asList(new BaseBlock(0)), 1, 8);
default:
throw new NoMatchException("Unrecognized mask '" + component + "'");
}
@ -141,10 +157,10 @@ public class DefaultMaskParser extends InputParser<Mask> {
throw new InputParseException("Unknown angle '" + component + "' (not in form `/#,#`)");
}
try {
int y1 = Integer.parseInt(split[0]);
int y2 = Integer.parseInt(split[1]);
int y1 = (int) Math.abs(Expression.compile(split[0]).evaluate());
int y2 = (int) Math.abs(Expression.compile(split[1]).evaluate());
return new AngleMask(extent, y1, y2);
} catch (NumberFormatException e) {
} catch (NumberFormatException | ExpressionException e) {
throw new InputParseException("Unknown angle '" + component + "' (not in form `/#,#`)");
}
}
@ -154,13 +170,14 @@ public class DefaultMaskParser extends InputParser<Mask> {
throw new InputParseException("Unknown range '" + component + "' (not in form `{#,#`)");
}
try {
int y1 = Integer.parseInt(split[0]);
int y2 = Integer.parseInt(split[1]);
int y1 = (int) Math.abs(Expression.compile(split[0]).evaluate());
int y2 = (int) Math.abs(Expression.compile(split[1]).evaluate());
return new RadiusMask(y1, y2);
} catch (NumberFormatException e) {
} catch (NumberFormatException | ExpressionException e) {
throw new InputParseException("Unknown range '" + component + "' (not in form `{#,#`)");
}
}
case '|':
case '~': {
String[] split = component.substring(1).split("=");
ParserContext tempContext = new ParserContext(context);
@ -171,13 +188,17 @@ public class DefaultMaskParser extends InputParser<Mask> {
int requiredMax = 8;
if (split.length == 2) {
String[] split2 = split[1].split(",");
requiredMin = Integer.parseInt(split2[0]);
requiredMin = (int) Math.abs(Expression.compile(split2[0]).evaluate());
if (split2.length == 2) {
requiredMax = Integer.parseInt(split2[1]);
requiredMax = (int) Math.abs(Expression.compile(split2[1]).evaluate());
}
}
return new AdjacentMask(extent, worldEdit.getBlockFactory().parseFromListInput(component.substring(1), tempContext), requiredMin, requiredMax);
} catch (NumberFormatException e) {
if (firstChar == '~') {
return new AdjacentMask(extent, worldEdit.getBlockFactory().parseFromListInput(component.substring(1), tempContext), requiredMin, requiredMax);
} else {
return new WallMask(extent, worldEdit.getBlockFactory().parseFromListInput(component.substring(1), tempContext), requiredMin, requiredMax);
}
} catch (NumberFormatException | ExpressionException e) {
throw new InputParseException("Unknown adjacent mask '" + component + "' (not in form `~<ids>[=count]`)");
}
}
@ -208,9 +229,12 @@ public class DefaultMaskParser extends InputParser<Mask> {
return Masks.asMask(new BiomeMask2D(context.requireExtent(), biomes));
case '%':
int i = Integer.parseInt(component.substring(1));
return new NoiseFilter(new RandomNoise(), ((double) i) / 100);
try {
double i = Math.abs(Expression.compile(component.substring(1)).evaluate());
return new NoiseFilter(new RandomNoise(), (i) / 100);
} catch (NumberFormatException | ExpressionException e) {
throw new InputParseException("Unknown percentage '" + component.substring(1) + "'");
}
case '=':
try {
Expression exp = Expression.compile(component.substring(1), "x", "y", "z");

View File

@ -1,6 +1,7 @@
package com.sk89q.worldedit.extension.factory;
import com.boydti.fawe.object.pattern.ExistingPattern;
import com.boydti.fawe.object.pattern.ExpressionPattern;
import com.boydti.fawe.object.pattern.Linear3DBlockPattern;
import com.boydti.fawe.object.pattern.LinearBlockPattern;
import com.boydti.fawe.object.pattern.MaskedPattern;
@ -11,9 +12,14 @@ import com.boydti.fawe.object.pattern.OffsetPattern;
import com.boydti.fawe.object.pattern.PatternExtent;
import com.boydti.fawe.object.pattern.RandomOffsetPattern;
import com.boydti.fawe.object.pattern.RelativePattern;
import com.boydti.fawe.object.pattern.SolidRandomOffsetPattern;
import com.boydti.fawe.object.pattern.SurfaceRandomOffsetPattern;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.EmptyClipboardException;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.extension.input.InputParseException;
import com.sk89q.worldedit.extension.input.ParserContext;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
@ -25,6 +31,7 @@ import com.sk89q.worldedit.function.pattern.RandomPattern;
import com.sk89q.worldedit.internal.expression.Expression;
import com.sk89q.worldedit.internal.expression.ExpressionException;
import com.sk89q.worldedit.internal.registry.InputParser;
import com.sk89q.worldedit.regions.shape.WorldEditExpressionEnvironment;
import com.sk89q.worldedit.session.ClipboardHolder;
import com.sk89q.worldedit.session.request.Request;
import java.util.ArrayList;
@ -137,6 +144,30 @@ public class HashTagPatternParser extends InputParser<Pattern> {
} catch (NumberFormatException | ExpressionException e) {
throw new InputParseException("The correct format is #offset:<dx>:<dy>:<dz>:<pattern>");
}
case "#surfacespread": {
try {
int x = (int) Math.abs(Expression.compile(split2[1]).evaluate());
int y = (int) Math.abs(Expression.compile(split2[2]).evaluate());
int z = (int) Math.abs(Expression.compile(split2[3]).evaluate());
rest = rest.substring(split2[1].length() + split2[2].length() + split2[3].length() + 3);
Pattern pattern = parseFromInput(rest, context);
return new SurfaceRandomOffsetPattern(pattern, x, y, z);
} catch (NumberFormatException | ExpressionException e) {
throw new InputParseException("The correct format is #spread:<dx>:<dy>:<dz>:<pattern>");
}
}
case "#solidspread": {
try {
int x = (int) Math.abs(Expression.compile(split2[1]).evaluate());
int y = (int) Math.abs(Expression.compile(split2[2]).evaluate());
int z = (int) Math.abs(Expression.compile(split2[3]).evaluate());
rest = rest.substring(split2[1].length() + split2[2].length() + split2[3].length() + 3);
Pattern pattern = parseFromInput(rest, context);
return new SolidRandomOffsetPattern(pattern, x, y, z);
} catch (NumberFormatException | ExpressionException e) {
throw new InputParseException("The correct format is #spread:<dx>:<dy>:<dz>:<pattern>");
}
}
case "#randomoffset":
case "#spread": {
try {
@ -176,6 +207,21 @@ public class HashTagPatternParser extends InputParser<Pattern> {
}
throw new InputParseException("Invalid, see: https://github.com/boy0001/FastAsyncWorldedit/wiki/WorldEdit-and-FAWE-patterns");
}
case '=': {
try {
Expression exp = Expression.compile(input.substring(1), "x", "y", "z");
EditSession editSession = Request.request().getEditSession();
if (editSession == null) {
editSession = context.requireSession().createEditSession((Player) context.getActor());
}
WorldEditExpressionEnvironment env = new WorldEditExpressionEnvironment(
editSession, Vector.ONE, Vector.ZERO);
exp.setEnvironment(env);
return new ExpressionPattern(exp);
} catch (ExpressionException e) {
throw new InputParseException("Invalid expression: " + e.getMessage());
}
}
default:
List<String> items = split(input, ',');
if (items.size() == 1) {
@ -183,23 +229,27 @@ public class HashTagPatternParser extends InputParser<Pattern> {
}
BlockFactory blockRegistry = worldEdit.getBlockFactory();
RandomPattern randomPattern = new RandomPattern();
for (String token : items) {
Pattern pattern;
double chance;
// Parse special percentage syntax
if (token.matches("[0-9]+(\\.[0-9]*)?%.*")) {
String[] p = token.split("%");
if (p.length < 2) {
throw new InputParseException("Missing the pattern after the % symbol for '" + input + "'");
try {
for (String token : items) {
Pattern pattern;
double chance;
// Parse special percentage syntax
if (token.matches("[0-9]+(\\.[0-9]*)?%.*")) {
String[] p = token.split("%");
if (p.length < 2) {
throw new InputParseException("Missing the pattern after the % symbol for '" + input + "'");
} else {
chance = Expression.compile(p[0]).evaluate();
pattern = parseFromInput(p[1], context);
}
} else {
chance = Double.parseDouble(p[0]);
pattern = parseFromInput(p[1], context);
chance = 1;
pattern = parseFromInput(token, context);
}
} else {
chance = 1;
pattern = parseFromInput(token, context);
randomPattern.add(pattern, chance);
}
randomPattern.add(pattern, chance);
} catch (NumberFormatException | ExpressionException e) {
throw new InputParseException("Invalid, see: https://github.com/boy0001/FastAsyncWorldedit/wiki/WorldEdit-and-FAWE-patterns");
}
return randomPattern;

View File

@ -27,7 +27,6 @@ import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.entity.Entity;
import com.sk89q.worldedit.function.operation.Operation;
import com.sk89q.worldedit.function.operation.OperationQueue;
import com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.world.biome.BaseBiome;
@ -86,7 +85,7 @@ public abstract class AbstractDelegateExtent implements Extent {
mutable.x = x;
mutable.y = y;
mutable.z = z;
return extent.setBlock(mutable, block);
return setBlock(mutable, block);
}
@Override

View File

@ -1,24 +1,6 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.extent.transform;
import com.boydti.fawe.FaweCache;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.blocks.BaseBlock;
@ -28,9 +10,9 @@ import com.sk89q.worldedit.math.transform.Transform;
import com.sk89q.worldedit.world.registry.BlockRegistry;
import com.sk89q.worldedit.world.registry.State;
import com.sk89q.worldedit.world.registry.StateValue;
import javax.annotation.Nullable;
import java.util.Map;
import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkNotNull;
@ -44,6 +26,8 @@ public class BlockTransformExtent extends AbstractDelegateExtent {
private final Transform transform;
private final BlockRegistry blockRegistry;
private final BaseBlock[] BLOCK_TRANSFORM;
private final BaseBlock[] BLOCK_TRANSFORM_INVERSE;
/**
* Create a new instance.
@ -57,6 +41,16 @@ public class BlockTransformExtent extends AbstractDelegateExtent {
checkNotNull(blockRegistry);
this.transform = transform;
this.blockRegistry = blockRegistry;
BLOCK_TRANSFORM = new BaseBlock[FaweCache.CACHE_BLOCK.length];
BLOCK_TRANSFORM_INVERSE = new BaseBlock[FaweCache.CACHE_BLOCK.length];
Transform inverse = transform.inverse();
for (int i = 0; i < BLOCK_TRANSFORM.length; i++) {
BaseBlock block = FaweCache.CACHE_BLOCK[i];
if (block != null) {
BLOCK_TRANSFORM[i] = transform(new BaseBlock(block), transform, blockRegistry);
BLOCK_TRANSFORM_INVERSE[i] = transform(new BaseBlock(block), inverse, blockRegistry);
}
}
}
/**
@ -68,32 +62,28 @@ public class BlockTransformExtent extends AbstractDelegateExtent {
return transform;
}
/**
* Transform a block without making a copy.
*
* @param block the block
* @param reverse true to transform in the opposite direction
* @return the same block
*/
private BaseBlock transformBlock(BaseBlock block, boolean reverse) {
return transform(block, reverse ? transform.inverse() : transform, blockRegistry);
}
@Override
public BaseBlock getBlock(Vector position) {
return transformBlock(super.getBlock(position), false);
return transformFast(super.getBlock(position));
}
@Override
public BaseBlock getLazyBlock(Vector position) {
return transformBlock(super.getLazyBlock(position), false);
return transformFast(super.getLazyBlock(position));
}
@Override
public boolean setBlock(Vector location, BaseBlock block) throws WorldEditException {
return super.setBlock(location, transformBlock(block, true));
return super.setBlock(location, transformFastInverse(block));
}
private final BaseBlock transformFast(BaseBlock block) {
return BLOCK_TRANSFORM[FaweCache.getCombined(block)];
}
private final BaseBlock transformFastInverse(BaseBlock block) {
return BLOCK_TRANSFORM_INVERSE[FaweCache.getCombined(block)];
}
/**
* Transform the given block using the given transform.
@ -157,7 +147,7 @@ public class BlockTransformExtent extends AbstractDelegateExtent {
*/
@Nullable
private static StateValue getNewStateValue(State state, Transform transform, Vector oldDirection) {
Vector newDirection = transform.apply(oldDirection).subtract(transform.apply(Vector.ZERO)).normalize();
Vector newDirection = new Vector(transform.apply(oldDirection)).subtract(transform.apply(Vector.ZERO)).normalize();
StateValue newValue = null;
double closest = -2;
boolean found = false;

View File

@ -27,7 +27,6 @@ import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.entity.Entity;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.function.EntityFunction;
import com.sk89q.worldedit.function.operation.ForwardExtentCopy;
import com.sk89q.worldedit.internal.helper.MCDirections;
import com.sk89q.worldedit.math.transform.Transform;
import com.sk89q.worldedit.util.Direction;
@ -99,7 +98,7 @@ public class ExtentEntityCopy implements EntityFunction {
newDirection = transform.isIdentity() ?
entity.getLocation().getDirection()
: transform.apply(location.getDirection()).subtract(transform.apply(Vector.ZERO)).normalize();
: new Vector(transform.apply(location.getDirection())).subtract(transform.apply(Vector.ZERO)).normalize();
newLocation = new Location(destination, newPosition.add(to.round().add(0.5, 0.5, 0.5)), newDirection);
// Some entities store their position data in NBT
@ -148,7 +147,7 @@ public class ExtentEntityCopy implements EntityFunction {
Direction direction = MCDirections.fromHanging(d);
if (direction != null) {
Vector vector = transform.apply(direction.toVector()).subtract(transform.apply(Vector.ZERO)).normalize();
Vector vector = new Vector(transform.apply(direction.toVector())).subtract(transform.apply(Vector.ZERO)).normalize();
Direction newDirection = Direction.findClosest(vector, Flag.CARDINAL);
builder.putByte("Direction", (byte) MCDirections.toHanging(newDirection));

View File

@ -1,6 +1,7 @@
package com.sk89q.worldedit.function.mask;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.util.StringMan;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.extent.Extent;
@ -110,6 +111,11 @@ public class BlockMask extends AbstractExtentMask {
return computedLegacyList;
}
@Override
public String toString() {
return StringMan.getString(getBlocks());
}
public boolean test(int blockId) {
return blockIds[blockId];
}

View File

@ -0,0 +1,307 @@
package com.sk89q.worldedit.math.transform;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.math.MathUtils;
/**
* An affine transform.
*
* <p>This class is from the
* <a href="http://geom-java.sourceforge.net/index.html">JavaGeom project</a>,
* which is licensed under LGPL v2.1.</p>
*/
public class AffineTransform implements Transform {
private Vector mutable = new Vector();
/**
* coefficients for x coordinate.
*/
private double m00, m01, m02, m03;
/**
* coefficients for y coordinate.
*/
private double m10, m11, m12, m13;
/**
* coefficients for z coordinate.
*/
private double m20, m21, m22, m23;
// ===================================================================
// constructors
/**
* Creates a new affine transform3D set to identity
*/
public AffineTransform() {
// init to identity matrix
m00 = m11 = m22 = 1;
m01 = m02 = m03 = 0;
m10 = m12 = m13 = 0;
m20 = m21 = m23 = 0;
}
public AffineTransform(double[] coefs) {
if (coefs.length == 9) {
m00 = coefs[0];
m01 = coefs[1];
m02 = coefs[2];
m10 = coefs[3];
m11 = coefs[4];
m12 = coefs[5];
m20 = coefs[6];
m21 = coefs[7];
m22 = coefs[8];
} else if (coefs.length == 12) {
m00 = coefs[0];
m01 = coefs[1];
m02 = coefs[2];
m03 = coefs[3];
m10 = coefs[4];
m11 = coefs[5];
m12 = coefs[6];
m13 = coefs[7];
m20 = coefs[8];
m21 = coefs[9];
m22 = coefs[10];
m23 = coefs[11];
} else {
throw new IllegalArgumentException(
"Input array must have 9 or 12 elements");
}
}
public AffineTransform(double xx, double yx, double zx, double tx,
double xy, double yy, double zy, double ty, double xz, double yz,
double zz, double tz) {
m00 = xx;
m01 = yx;
m02 = zx;
m03 = tx;
m10 = xy;
m11 = yy;
m12 = zy;
m13 = ty;
m20 = xz;
m21 = yz;
m22 = zz;
m23 = tz;
}
// ===================================================================
// accessors
@Override
public boolean isIdentity() {
if (m00 != 1)
return false;
if (m11 != 1)
return false;
if (m22 != 1)
return false;
if (m01 != 0)
return false;
if (m02 != 0)
return false;
if (m03 != 0)
return false;
if (m10 != 0)
return false;
if (m12 != 0)
return false;
if (m13 != 0)
return false;
if (m20 != 0)
return false;
if (m21 != 0)
return false;
if (m23 != 0)
return false;
return true;
}
/**
* Returns the affine coefficients of the transform. Result is an array of
* 12 double.
*/
public double[] coefficients() {
return new double[]{m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23};
}
/**
* Computes the determinant of this transform. Can be zero.
*
* @return the determinant of the transform.
*/
private double determinant() {
return m00 * (m11 * m22 - m12 * m21) - m01 * (m10 * m22 - m20 * m12)
+ m02 * (m10 * m21 - m20 * m11);
}
/**
* Computes the inverse affine transform.
*/
@Override
public AffineTransform inverse() {
double det = this.determinant();
return new AffineTransform(
(m11 * m22 - m21 * m12) / det,
(m21 * m01 - m01 * m22) / det,
(m01 * m12 - m11 * m02) / det,
(m01 * (m22 * m13 - m12 * m23) + m02 * (m11 * m23 - m21 * m13)
- m03 * (m11 * m22 - m21 * m12)) / det,
(m20 * m12 - m10 * m22) / det,
(m00 * m22 - m20 * m02) / det,
(m10 * m02 - m00 * m12) / det,
(m00 * (m12 * m23 - m22 * m13) - m02 * (m10 * m23 - m20 * m13)
+ m03 * (m10 * m22 - m20 * m12)) / det,
(m10 * m21 - m20 * m11) / det,
(m20 * m01 - m00 * m21) / det,
(m00 * m11 - m10 * m01) / det,
(m00 * (m21 * m13 - m11 * m23) + m01 * (m10 * m23 - m20 * m13)
- m03 * (m10 * m21 - m20 * m11)) / det);
}
// ===================================================================
// general methods
/**
* Returns the affine transform created by applying first the affine
* transform given by {@code that}, then this affine transform.
*
* @param that the transform to apply first
* @return the composition this * that
*/
public AffineTransform concatenate(AffineTransform that) {
double n00 = m00 * that.m00 + m01 * that.m10 + m02 * that.m20;
double n01 = m00 * that.m01 + m01 * that.m11 + m02 * that.m21;
double n02 = m00 * that.m02 + m01 * that.m12 + m02 * that.m22;
double n03 = m00 * that.m03 + m01 * that.m13 + m02 * that.m23 + m03;
double n10 = m10 * that.m00 + m11 * that.m10 + m12 * that.m20;
double n11 = m10 * that.m01 + m11 * that.m11 + m12 * that.m21;
double n12 = m10 * that.m02 + m11 * that.m12 + m12 * that.m22;
double n13 = m10 * that.m03 + m11 * that.m13 + m12 * that.m23 + m13;
double n20 = m20 * that.m00 + m21 * that.m10 + m22 * that.m20;
double n21 = m20 * that.m01 + m21 * that.m11 + m22 * that.m21;
double n22 = m20 * that.m02 + m21 * that.m12 + m22 * that.m22;
double n23 = m20 * that.m03 + m21 * that.m13 + m22 * that.m23 + m23;
return new AffineTransform(
n00, n01, n02, n03,
n10, n11, n12, n13,
n20, n21, n22, n23);
}
/**
* Return the affine transform created by applying first this affine
* transform, then the affine transform given by {@code that}.
*
* @param that the transform to apply in a second step
* @return the composition that * this
*/
public AffineTransform preConcatenate(AffineTransform that) {
double n00 = that.m00 * m00 + that.m01 * m10 + that.m02 * m20;
double n01 = that.m00 * m01 + that.m01 * m11 + that.m02 * m21;
double n02 = that.m00 * m02 + that.m01 * m12 + that.m02 * m22;
double n03 = that.m00 * m03 + that.m01 * m13 + that.m02 * m23 + that.m03;
double n10 = that.m10 * m00 + that.m11 * m10 + that.m12 * m20;
double n11 = that.m10 * m01 + that.m11 * m11 + that.m12 * m21;
double n12 = that.m10 * m02 + that.m11 * m12 + that.m12 * m22;
double n13 = that.m10 * m03 + that.m11 * m13 + that.m12 * m23 + that.m13;
double n20 = that.m20 * m00 + that.m21 * m10 + that.m22 * m20;
double n21 = that.m20 * m01 + that.m21 * m11 + that.m22 * m21;
double n22 = that.m20 * m02 + that.m21 * m12 + that.m22 * m22;
double n23 = that.m20 * m03 + that.m21 * m13 + that.m22 * m23 + that.m23;
return new AffineTransform(
n00, n01, n02, n03,
n10, n11, n12, n13,
n20, n21, n22, n23);
}
public AffineTransform translate(Vector vec) {
return translate(vec.getX(), vec.getY(), vec.getZ());
}
public AffineTransform translate(double x, double y, double z) {
return concatenate(new AffineTransform(1, 0, 0, x, 0, 1, 0, y, 0, 0, 1, z));
}
public AffineTransform rotateX(double theta) {
double cot = MathUtils.dCos(theta);
double sit = MathUtils.dSin(theta);
return concatenate(
new AffineTransform(
1, 0, 0, 0,
0, cot, -sit, 0,
0, sit, cot, 0));
}
public AffineTransform rotateY(double theta) {
double cot = MathUtils.dCos(theta);
double sit = MathUtils.dSin(theta);
return concatenate(
new AffineTransform(
cot, 0, sit, 0,
0, 1, 0, 0,
-sit, 0, cot, 0));
}
public AffineTransform rotateZ(double theta) {
double cot = MathUtils.dCos(theta);
double sit = MathUtils.dSin(theta);
return concatenate(
new AffineTransform(
cot, -sit, 0, 0,
sit, cot, 0, 0,
0, 0, 1, 0));
}
public AffineTransform scale(double s) {
return scale(s, s, s);
}
public AffineTransform scale(double sx, double sy, double sz) {
return concatenate(new AffineTransform(sx, 0, 0, 0, 0, sy, 0, 0, 0, 0, sz, 0));
}
public AffineTransform scale(Vector vec) {
return scale(vec.getX(), vec.getY(), vec.getZ());
}
@Override
public Vector apply(Vector vector) {
// vector.getX() * m00 + vector.getY() * m01 + vector.getZ() * m02 + m03
// vector.getX() * m10 + vector.getY() * m11 + vector.getZ() * m12 + m13
// vector.getX() * m20 + vector.getY() * m21 + vector.getZ() * m22 + m23
mutable.x = vector.getX() * m00 + vector.getY() * m01 + vector.getZ() * m02 + m03;
mutable.y = vector.getX() * m10 + vector.getY() * m11 + vector.getZ() * m12 + m13;
mutable.z = vector.getX() * m20 + vector.getY() * m21 + vector.getZ() * m22 + m23;
return new Vector(
vector.getX() * m00 + vector.getY() * m01 + vector.getZ() * m02 + m03,
vector.getX() * m10 + vector.getY() * m11 + vector.getZ() * m12 + m13,
vector.getX() * m20 + vector.getY() * m21 + vector.getZ() * m22 + m23);
}
public AffineTransform combine(AffineTransform other) {
return concatenate(other);
}
@Override
public Transform combine(Transform other) {
if (other instanceof AffineTransform) {
return concatenate((AffineTransform) other);
} else {
return new CombinedTransform(this, other);
}
}
@Override
public String toString() {
return String.format("Affine[%g %g %g %g, %g %g %g %g, %g %g %g %g]}", m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23);
}
public static Class<?> inject() {
return AffineTransform.class;
}
}