mirror of
https://github.com/boy0001/FastAsyncWorldedit.git
synced 2024-11-28 21:56:33 +01:00
Add transforms
This commit is contained in:
parent
7deeb51ca7
commit
0251c193c1
@ -169,11 +169,12 @@ public class FaweBukkit implements IFawe, Listener {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return plugin.getQueue(world);
|
return plugin.getQueue(world);
|
||||||
} catch (Throwable ignore) {}
|
} catch (Throwable ignore) {
|
||||||
// Disable incompatible settings
|
// Disable incompatible settings
|
||||||
Settings.QUEUE.PARALLEL_THREADS = 1; // BukkitAPI placer is too slow to parallel thread at the chunk level
|
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
|
Settings.HISTORY.COMBINE_STAGES = false; // Performing a chunk copy (if possible) wouldn't be faster using the BukkitAPI
|
||||||
if (hasNMS) {
|
if (hasNMS) {
|
||||||
|
ignore.printStackTrace();
|
||||||
debug("====== NO NMS BLOCK PLACER FOUND ======");
|
debug("====== NO NMS BLOCK PLACER FOUND ======");
|
||||||
debug("FAWE couldn't find a fast block placer");
|
debug("FAWE couldn't find a fast block placer");
|
||||||
debug("Bukkit version: " + Bukkit.getVersion());
|
debug("Bukkit version: " + Bukkit.getVersion());
|
||||||
@ -193,6 +194,7 @@ public class FaweBukkit implements IFawe, Listener {
|
|||||||
}
|
}
|
||||||
return new BukkitQueue_All(world);
|
return new BukkitQueue_All(world);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The FaweQueue is a core part of block placement<br>
|
* The FaweQueue is a core part of block placement<br>
|
||||||
|
@ -37,6 +37,7 @@ import com.sk89q.worldedit.command.ToolCommands;
|
|||||||
import com.sk89q.worldedit.command.ToolUtilCommands;
|
import com.sk89q.worldedit.command.ToolUtilCommands;
|
||||||
import com.sk89q.worldedit.command.composition.SelectionCommand;
|
import com.sk89q.worldedit.command.composition.SelectionCommand;
|
||||||
import com.sk89q.worldedit.command.tool.AreaPickaxe;
|
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.LongRangeBuildTool;
|
||||||
import com.sk89q.worldedit.command.tool.RecursivePickaxe;
|
import com.sk89q.worldedit.command.tool.RecursivePickaxe;
|
||||||
import com.sk89q.worldedit.command.tool.brush.GravityBrush;
|
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.EntityCreate;
|
||||||
import com.sk89q.worldedit.history.change.EntityRemove;
|
import com.sk89q.worldedit.history.change.EntityRemove;
|
||||||
import com.sk89q.worldedit.math.interpolation.KochanekBartelsInterpolation;
|
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.CuboidRegion;
|
||||||
import com.sk89q.worldedit.regions.selector.CuboidRegionSelector;
|
import com.sk89q.worldedit.regions.selector.CuboidRegionSelector;
|
||||||
import com.sk89q.worldedit.session.SessionManager;
|
import com.sk89q.worldedit.session.SessionManager;
|
||||||
@ -356,11 +358,12 @@ public class Fawe {
|
|||||||
SchematicReader.inject();
|
SchematicReader.inject();
|
||||||
SchematicWriter.inject();
|
SchematicWriter.inject();
|
||||||
ClipboardFormat.inject();
|
ClipboardFormat.inject();
|
||||||
// Brushes
|
// Brushes/Tools
|
||||||
GravityBrush.inject(); // Fix for instant placement assumption
|
GravityBrush.inject(); // Fix for instant placement assumption
|
||||||
LongRangeBuildTool.inject();
|
LongRangeBuildTool.inject();
|
||||||
AreaPickaxe.inject(); // Fixes
|
AreaPickaxe.inject(); // Fixes
|
||||||
RecursivePickaxe.inject(); // Fixes
|
RecursivePickaxe.inject(); // Fixes
|
||||||
|
BrushTool.inject(); // Add transform
|
||||||
// Selectors
|
// Selectors
|
||||||
CuboidRegionSelector.inject(); // Translations
|
CuboidRegionSelector.inject(); // Translations
|
||||||
// Visitors
|
// Visitors
|
||||||
@ -412,6 +415,7 @@ public class Fawe {
|
|||||||
NBTOutputStream.inject(); // New methods
|
NBTOutputStream.inject(); // New methods
|
||||||
// Math
|
// Math
|
||||||
KochanekBartelsInterpolation.inject(); // Optimizations
|
KochanekBartelsInterpolation.inject(); // Optimizations
|
||||||
|
AffineTransform.inject(); // Optimizations
|
||||||
try {
|
try {
|
||||||
CommandManager.inject(); // Async commands
|
CommandManager.inject(); // Async commands
|
||||||
PlatformManager.inject(); // Async brushes / tools
|
PlatformManager.inject(); // Async brushes / tools
|
||||||
|
@ -63,14 +63,14 @@ public class FaweCache {
|
|||||||
* Immutable BaseBlock cache
|
* Immutable BaseBlock cache
|
||||||
* [ combined ] => block
|
* [ 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)
|
* Faster than java random (since it just needs to look random)
|
||||||
*/
|
*/
|
||||||
public final static PseudoRandom RANDOM = new PseudoRandom();
|
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>
|
* Get the cached BaseBlock object for an id/data<br>
|
||||||
@ -143,7 +143,7 @@ public class FaweCache {
|
|||||||
CACHE_DATA[i] = (byte) k;
|
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 id = i >> 4;
|
||||||
int data = i & 0xf;
|
int data = i & 0xf;
|
||||||
CACHE_BLOCK[i] = new BaseBlock(id, data) {
|
CACHE_BLOCK[i] = new BaseBlock(id, data) {
|
||||||
|
@ -47,7 +47,10 @@ public enum BBC {
|
|||||||
GENERATING_LINK_FAILED("&cFailed to generate download link!", "Web"),
|
GENERATING_LINK_FAILED("&cFailed to generate download link!", "Web"),
|
||||||
DOWNLOAD_LINK("%s", "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_COPY("%s0 blocks were copied", "WorldEdit.Copy"),
|
||||||
COMMAND_CUT("%s0 blocks were cut", "WorldEdit.Cut"),
|
COMMAND_CUT("%s0 blocks were cut", "WorldEdit.Cut"),
|
||||||
@ -104,6 +107,8 @@ public enum BBC {
|
|||||||
BRUSH_RANGE("Brush size set", "WorldEdit.Brush"),
|
BRUSH_RANGE("Brush size set", "WorldEdit.Brush"),
|
||||||
BRUSH_MASK_DISABLED("Brush mask disabled", "WorldEdit.Brush"),
|
BRUSH_MASK_DISABLED("Brush mask disabled", "WorldEdit.Brush"),
|
||||||
BRUSH_MASK("Brush mask set", "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"),
|
BRUSH_MATERIAL("Brush material set", "WorldEdit.Brush"),
|
||||||
|
|
||||||
ROLLBACK_ELEMENT("Undoing %s0", "WorldEdit.Rollback"),
|
ROLLBACK_ELEMENT("Undoing %s0", "WorldEdit.Rollback"),
|
||||||
|
@ -8,6 +8,7 @@ import com.sk89q.worldedit.EditSession;
|
|||||||
import com.sk89q.worldedit.MaxChangedBlocksException;
|
import com.sk89q.worldedit.MaxChangedBlocksException;
|
||||||
import com.sk89q.worldedit.Vector;
|
import com.sk89q.worldedit.Vector;
|
||||||
import com.sk89q.worldedit.WorldVectorFace;
|
import com.sk89q.worldedit.WorldVectorFace;
|
||||||
|
import com.sk89q.worldedit.command.tool.BrushTool;
|
||||||
import com.sk89q.worldedit.command.tool.brush.Brush;
|
import com.sk89q.worldedit.command.tool.brush.Brush;
|
||||||
import com.sk89q.worldedit.entity.Player;
|
import com.sk89q.worldedit.entity.Player;
|
||||||
import com.sk89q.worldedit.event.platform.CommandEvent;
|
import com.sk89q.worldedit.event.platform.CommandEvent;
|
||||||
@ -20,11 +21,13 @@ public class CommandBrush implements Brush {
|
|||||||
private final String command;
|
private final String command;
|
||||||
private final Player player;
|
private final Player player;
|
||||||
private final int radius;
|
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.player = player;
|
||||||
this.command = command;
|
this.command = command;
|
||||||
this.radius = (int) radius;
|
this.radius = (int) radius;
|
||||||
|
this.tool = tool;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.boydti.fawe.object.brush;
|
package com.boydti.fawe.object.brush;
|
||||||
|
|
||||||
|
import com.boydti.fawe.object.extent.TransformExtent;
|
||||||
import com.sk89q.worldedit.EditSession;
|
import com.sk89q.worldedit.EditSession;
|
||||||
import com.sk89q.worldedit.LocalConfiguration;
|
import com.sk89q.worldedit.LocalConfiguration;
|
||||||
import com.sk89q.worldedit.LocalSession;
|
import com.sk89q.worldedit.LocalSession;
|
||||||
@ -29,6 +30,7 @@ public class DoubleActionBrushTool implements DoubleActionTraceTool {
|
|||||||
protected static int MAX_RANGE = 500;
|
protected static int MAX_RANGE = 500;
|
||||||
protected int range = -1;
|
protected int range = -1;
|
||||||
private Mask mask = null;
|
private Mask mask = null;
|
||||||
|
private TransformExtent transform = null;
|
||||||
private DoubleActionBrush brush = null;
|
private DoubleActionBrush brush = null;
|
||||||
@Nullable
|
@Nullable
|
||||||
private Pattern material;
|
private Pattern material;
|
||||||
@ -50,6 +52,14 @@ public class DoubleActionBrushTool implements DoubleActionTraceTool {
|
|||||||
return player.hasPermission(permission);
|
return player.hasPermission(permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TransformExtent getTransform() {
|
||||||
|
return transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTransform(TransformExtent transform) {
|
||||||
|
this.transform = transform;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the filter.
|
* Get the filter.
|
||||||
*
|
*
|
||||||
@ -168,7 +178,9 @@ public class DoubleActionBrushTool implements DoubleActionTraceTool {
|
|||||||
editSession.setMask(newMask);
|
editSession.setMask(newMask);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (transform != null) {
|
||||||
|
editSession.addTransform(transform);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
brush.build(action, editSession, target, material, size);
|
brush.build(action, editSession, target, material, size);
|
||||||
} catch (MaxChangedBlocksException e) {
|
} catch (MaxChangedBlocksException e) {
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
36
core/src/main/java/com/boydti/fawe/object/mask/WallMask.java
Normal file
36
core/src/main/java/com/boydti/fawe/object/mask/WallMask.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
735
core/src/main/java/com/boydti/fawe/util/ShapeInterpolator.java
Normal file
735
core/src/main/java/com/boydti/fawe/util/ShapeInterpolator.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -45,6 +45,7 @@ import com.boydti.fawe.object.extent.FastWorldEditExtent;
|
|||||||
import com.boydti.fawe.object.extent.FaweRegionExtent;
|
import com.boydti.fawe.object.extent.FaweRegionExtent;
|
||||||
import com.boydti.fawe.object.extent.NullExtent;
|
import com.boydti.fawe.object.extent.NullExtent;
|
||||||
import com.boydti.fawe.object.extent.ProcessedWEExtent;
|
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.mask.ResettableMask;
|
||||||
import com.boydti.fawe.object.progress.DefaultProgressTracker;
|
import com.boydti.fawe.object.progress.DefaultProgressTracker;
|
||||||
import com.boydti.fawe.util.ExtentTraverser;
|
import com.boydti.fawe.util.ExtentTraverser;
|
||||||
@ -572,6 +573,21 @@ public class EditSession extends AbstractWorld implements HasFaweQueue {
|
|||||||
return maskingExtent != null ? maskingExtent.get().getMask() : null;
|
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.
|
* Set a mask.
|
||||||
*
|
*
|
||||||
|
@ -29,6 +29,7 @@ import com.boydti.fawe.object.brush.DoubleActionBrushTool;
|
|||||||
import com.boydti.fawe.object.changeset.DiskStorageHistory;
|
import com.boydti.fawe.object.changeset.DiskStorageHistory;
|
||||||
import com.boydti.fawe.object.changeset.FaweChangeSet;
|
import com.boydti.fawe.object.changeset.FaweChangeSet;
|
||||||
import com.boydti.fawe.object.clipboard.DiskOptimizedClipboard;
|
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.EditSessionBuilder;
|
||||||
import com.boydti.fawe.util.MainUtil;
|
import com.boydti.fawe.util.MainUtil;
|
||||||
import com.boydti.fawe.wrappers.WorldWrapper;
|
import com.boydti.fawe.wrappers.WorldWrapper;
|
||||||
@ -138,6 +139,7 @@ public class LocalSession {
|
|||||||
private transient int cuiVersion = -1;
|
private transient int cuiVersion = -1;
|
||||||
private transient boolean fastMode = false;
|
private transient boolean fastMode = false;
|
||||||
private transient Mask mask;
|
private transient Mask mask;
|
||||||
|
private TransformExtent transform = null;
|
||||||
private transient TimeZone timezone = TimeZone.getDefault();
|
private transient TimeZone timezone = TimeZone.getDefault();
|
||||||
|
|
||||||
private transient World currentWorld;
|
private transient World currentWorld;
|
||||||
@ -1203,7 +1205,12 @@ public class LocalSession {
|
|||||||
getBlockChangeLimit(), blockBag, player);
|
getBlockChangeLimit(), blockBag, player);
|
||||||
editSession.setFastMode(fastMode);
|
editSession.setFastMode(fastMode);
|
||||||
Request.request().setEditSession(editSession);
|
Request.request().setEditSession(editSession);
|
||||||
|
if (mask != null) {
|
||||||
editSession.setMask(mask);
|
editSession.setMask(mask);
|
||||||
|
}
|
||||||
|
if (transform != null) {
|
||||||
|
editSession.addTransform(transform);
|
||||||
|
}
|
||||||
|
|
||||||
return editSession;
|
return editSession;
|
||||||
}
|
}
|
||||||
@ -1254,6 +1261,14 @@ public class LocalSession {
|
|||||||
setMask(mask != null ? Masks.wrap(mask) : null);
|
setMask(mask != null ? Masks.wrap(mask) : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TransformExtent getTransform() {
|
||||||
|
return transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTransform(TransformExtent transform) {
|
||||||
|
this.transform = transform;
|
||||||
|
}
|
||||||
|
|
||||||
public static Class<?> inject() {
|
public static Class<?> inject() {
|
||||||
return LocalSession.class;
|
return LocalSession.class;
|
||||||
}
|
}
|
||||||
|
@ -390,7 +390,7 @@ public class BrushCommands {
|
|||||||
public void command(Player player, LocalSession session, EditSession editSession, @Optional("5") double radius, CommandContext args) throws WorldEditException {
|
public void command(Player player, LocalSession session, EditSession editSession, @Optional("5") double radius, CommandContext args) throws WorldEditException {
|
||||||
BrushTool tool = session.getBrushTool(player.getItemInHand());
|
BrushTool tool = session.getBrushTool(player.getItemInHand());
|
||||||
String cmd = args.getJoinedStrings(1);
|
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);
|
BBC.BRUSH_COMMAND.send(player, cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,8 +300,8 @@ public class ClipboardCommands {
|
|||||||
|
|
||||||
if (selectPasted) {
|
if (selectPasted) {
|
||||||
Vector clipboardOffset = clipboard.getRegion().getMinimumPoint().subtract(clipboard.getOrigin());
|
Vector clipboardOffset = clipboard.getRegion().getMinimumPoint().subtract(clipboard.getOrigin());
|
||||||
Vector realTo = to.add(holder.getTransform().apply(clipboardOffset));
|
Vector realTo = to.add(new Vector(holder.getTransform().apply(clipboardOffset)));
|
||||||
Vector max = realTo.add(holder.getTransform().apply(region.getMaximumPoint().subtract(region.getMinimumPoint())));
|
Vector max = realTo.add(new Vector(holder.getTransform().apply(region.getMaximumPoint().subtract(region.getMinimumPoint()))));
|
||||||
RegionSelector selector = new CuboidRegionSelector(player.getWorld(), realTo, max);
|
RegionSelector selector = new CuboidRegionSelector(player.getWorld(), realTo, max);
|
||||||
session.setRegionSelector(player.getWorld(), selector);
|
session.setRegionSelector(player.getWorld(), selector);
|
||||||
selector.learnChanges();
|
selector.learnChanges();
|
||||||
|
@ -91,7 +91,7 @@ public class FlattenedClipboardTransform {
|
|||||||
maximum.setZ(minimum.getZ()) };
|
maximum.setZ(minimum.getZ()) };
|
||||||
|
|
||||||
for (int i = 0; i < corners.length; i++) {
|
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];
|
Vector newMinimum = corners[0];
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package com.sk89q.worldedit.command;
|
package com.sk89q.worldedit.command;
|
||||||
|
|
||||||
import com.boydti.fawe.config.BBC;
|
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.Command;
|
||||||
import com.sk89q.minecraft.util.commands.CommandContext;
|
import com.sk89q.minecraft.util.commands.CommandContext;
|
||||||
import com.sk89q.minecraft.util.commands.CommandPermissions;
|
import com.sk89q.minecraft.util.commands.CommandPermissions;
|
||||||
@ -20,6 +22,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||||||
public class GeneralCommands {
|
public class GeneralCommands {
|
||||||
|
|
||||||
private final WorldEdit worldEdit;
|
private final WorldEdit worldEdit;
|
||||||
|
private final DefaultTransformParser transformParser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new instance.
|
* Create a new instance.
|
||||||
@ -29,6 +32,7 @@ public class GeneralCommands {
|
|||||||
public GeneralCommands(WorldEdit worldEdit) {
|
public GeneralCommands(WorldEdit worldEdit) {
|
||||||
checkNotNull(worldEdit);
|
checkNotNull(worldEdit);
|
||||||
this.worldEdit = worldEdit;
|
this.worldEdit = worldEdit;
|
||||||
|
transformParser = new DefaultTransformParser(worldEdit);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Command(
|
@Command(
|
||||||
@ -102,7 +106,7 @@ public class GeneralCommands {
|
|||||||
public void gmask(Player player, LocalSession session, EditSession editSession, @Optional CommandContext context) throws WorldEditException {
|
public void gmask(Player player, LocalSession session, EditSession editSession, @Optional CommandContext context) throws WorldEditException {
|
||||||
if (context == null || context.argsLength() == 0) {
|
if (context == null || context.argsLength() == 0) {
|
||||||
session.setMask((Mask) null);
|
session.setMask((Mask) null);
|
||||||
BBC.BRUSH_MASK_DISABLED.send(player);
|
BBC.MASK_DISABLED.send(player);
|
||||||
} else {
|
} else {
|
||||||
ParserContext parserContext = new ParserContext();
|
ParserContext parserContext = new ParserContext();
|
||||||
parserContext.setActor(player);
|
parserContext.setActor(player);
|
||||||
@ -111,7 +115,31 @@ public class GeneralCommands {
|
|||||||
parserContext.setExtent(editSession);
|
parserContext.setExtent(editSession);
|
||||||
Mask mask = worldEdit.getMaskFactory().parseFromInput(context.getJoinedStrings(0), parserContext);
|
Mask mask = worldEdit.getMaskFactory().parseFromInput(context.getJoinedStrings(0), parserContext);
|
||||||
session.setMask(mask);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,8 @@ package com.sk89q.worldedit.command;
|
|||||||
|
|
||||||
import com.boydti.fawe.config.BBC;
|
import com.boydti.fawe.config.BBC;
|
||||||
import com.boydti.fawe.object.brush.DoubleActionBrushTool;
|
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.Command;
|
||||||
import com.sk89q.minecraft.util.commands.CommandContext;
|
import com.sk89q.minecraft.util.commands.CommandContext;
|
||||||
import com.sk89q.minecraft.util.commands.CommandPermissions;
|
import com.sk89q.minecraft.util.commands.CommandPermissions;
|
||||||
@ -22,9 +24,11 @@ import com.sk89q.worldedit.util.command.parametric.Optional;
|
|||||||
*/
|
*/
|
||||||
public class ToolUtilCommands {
|
public class ToolUtilCommands {
|
||||||
private final WorldEdit we;
|
private final WorldEdit we;
|
||||||
|
private final DefaultTransformParser transformParser;
|
||||||
|
|
||||||
public ToolUtilCommands(WorldEdit we) {
|
public ToolUtilCommands(WorldEdit we) {
|
||||||
this.we = we;
|
this.we = we;
|
||||||
|
this.transformParser = new DefaultTransformParser(we);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Command(
|
@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(
|
@Command(
|
||||||
aliases = { "mat", "material" },
|
aliases = { "mat", "material" },
|
||||||
usage = "[pattern]",
|
usage = "[pattern]",
|
||||||
@ -104,7 +144,12 @@ public class ToolUtilCommands {
|
|||||||
)
|
)
|
||||||
@CommandPermissions("worldedit.brush.options.material")
|
@CommandPermissions("worldedit.brush.options.material")
|
||||||
public void material(Player player, LocalSession session, EditSession editSession, Pattern pattern) throws WorldEditException {
|
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);
|
BBC.BRUSH_MATERIAL.send(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,7 +163,12 @@ public class ToolUtilCommands {
|
|||||||
@CommandPermissions("worldedit.brush.options.range")
|
@CommandPermissions("worldedit.brush.options.range")
|
||||||
public void range(Player player, LocalSession session, EditSession editSession, CommandContext args) throws WorldEditException {
|
public void range(Player player, LocalSession session, EditSession editSession, CommandContext args) throws WorldEditException {
|
||||||
int range = args.getInteger(0);
|
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);
|
BBC.BRUSH_RANGE.send(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,7 +185,12 @@ public class ToolUtilCommands {
|
|||||||
int radius = args.getInteger(0);
|
int radius = args.getInteger(0);
|
||||||
we.checkMaxBrushRadius(radius);
|
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);
|
BBC.BRUSH_SIZE.send(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -7,12 +7,15 @@ import com.boydti.fawe.object.mask.DataMask;
|
|||||||
import com.boydti.fawe.object.mask.IdDataMask;
|
import com.boydti.fawe.object.mask.IdDataMask;
|
||||||
import com.boydti.fawe.object.mask.IdMask;
|
import com.boydti.fawe.object.mask.IdMask;
|
||||||
import com.boydti.fawe.object.mask.RadiusMask;
|
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.XAxisMask;
|
||||||
import com.boydti.fawe.object.mask.YAxisMask;
|
import com.boydti.fawe.object.mask.YAxisMask;
|
||||||
import com.boydti.fawe.object.mask.ZAxisMask;
|
import com.boydti.fawe.object.mask.ZAxisMask;
|
||||||
|
import com.sk89q.worldedit.EditSession;
|
||||||
import com.sk89q.worldedit.IncompleteRegionException;
|
import com.sk89q.worldedit.IncompleteRegionException;
|
||||||
import com.sk89q.worldedit.Vector;
|
import com.sk89q.worldedit.Vector;
|
||||||
import com.sk89q.worldedit.WorldEdit;
|
import com.sk89q.worldedit.WorldEdit;
|
||||||
|
import com.sk89q.worldedit.blocks.BaseBlock;
|
||||||
import com.sk89q.worldedit.extension.input.InputParseException;
|
import com.sk89q.worldedit.extension.input.InputParseException;
|
||||||
import com.sk89q.worldedit.extension.input.NoMatchException;
|
import com.sk89q.worldedit.extension.input.NoMatchException;
|
||||||
import com.sk89q.worldedit.extension.input.ParserContext;
|
import com.sk89q.worldedit.extension.input.ParserContext;
|
||||||
@ -102,6 +105,12 @@ public class DefaultMaskParser extends InputParser<Mask> {
|
|||||||
final char firstChar = component.charAt(0);
|
final char firstChar = component.charAt(0);
|
||||||
switch (firstChar) {
|
switch (firstChar) {
|
||||||
case '#':
|
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()) {
|
switch (component.toLowerCase()) {
|
||||||
case "#existing":
|
case "#existing":
|
||||||
return new ExistingBlockMask(extent);
|
return new ExistingBlockMask(extent);
|
||||||
@ -131,6 +140,13 @@ public class DefaultMaskParser extends InputParser<Mask> {
|
|||||||
return new DataMask(extent);
|
return new DataMask(extent);
|
||||||
case "#iddata":
|
case "#iddata":
|
||||||
return new IdDataMask(extent);
|
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:
|
default:
|
||||||
throw new NoMatchException("Unrecognized mask '" + component + "'");
|
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 `/#,#`)");
|
throw new InputParseException("Unknown angle '" + component + "' (not in form `/#,#`)");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
int y1 = Integer.parseInt(split[0]);
|
int y1 = (int) Math.abs(Expression.compile(split[0]).evaluate());
|
||||||
int y2 = Integer.parseInt(split[1]);
|
int y2 = (int) Math.abs(Expression.compile(split[1]).evaluate());
|
||||||
return new AngleMask(extent, y1, y2);
|
return new AngleMask(extent, y1, y2);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException | ExpressionException e) {
|
||||||
throw new InputParseException("Unknown angle '" + component + "' (not in form `/#,#`)");
|
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 `{#,#`)");
|
throw new InputParseException("Unknown range '" + component + "' (not in form `{#,#`)");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
int y1 = Integer.parseInt(split[0]);
|
int y1 = (int) Math.abs(Expression.compile(split[0]).evaluate());
|
||||||
int y2 = Integer.parseInt(split[1]);
|
int y2 = (int) Math.abs(Expression.compile(split[1]).evaluate());
|
||||||
return new RadiusMask(y1, y2);
|
return new RadiusMask(y1, y2);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException | ExpressionException e) {
|
||||||
throw new InputParseException("Unknown range '" + component + "' (not in form `{#,#`)");
|
throw new InputParseException("Unknown range '" + component + "' (not in form `{#,#`)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case '|':
|
||||||
case '~': {
|
case '~': {
|
||||||
String[] split = component.substring(1).split("=");
|
String[] split = component.substring(1).split("=");
|
||||||
ParserContext tempContext = new ParserContext(context);
|
ParserContext tempContext = new ParserContext(context);
|
||||||
@ -171,13 +188,17 @@ public class DefaultMaskParser extends InputParser<Mask> {
|
|||||||
int requiredMax = 8;
|
int requiredMax = 8;
|
||||||
if (split.length == 2) {
|
if (split.length == 2) {
|
||||||
String[] split2 = split[1].split(",");
|
String[] split2 = split[1].split(",");
|
||||||
requiredMin = Integer.parseInt(split2[0]);
|
requiredMin = (int) Math.abs(Expression.compile(split2[0]).evaluate());
|
||||||
if (split2.length == 2) {
|
if (split2.length == 2) {
|
||||||
requiredMax = Integer.parseInt(split2[1]);
|
requiredMax = (int) Math.abs(Expression.compile(split2[1]).evaluate());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (firstChar == '~') {
|
||||||
return new AdjacentMask(extent, worldEdit.getBlockFactory().parseFromListInput(component.substring(1), tempContext), requiredMin, requiredMax);
|
return new AdjacentMask(extent, worldEdit.getBlockFactory().parseFromListInput(component.substring(1), tempContext), requiredMin, requiredMax);
|
||||||
} catch (NumberFormatException e) {
|
} 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]`)");
|
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));
|
return Masks.asMask(new BiomeMask2D(context.requireExtent(), biomes));
|
||||||
|
|
||||||
case '%':
|
case '%':
|
||||||
int i = Integer.parseInt(component.substring(1));
|
try {
|
||||||
return new NoiseFilter(new RandomNoise(), ((double) i) / 100);
|
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 '=':
|
case '=':
|
||||||
try {
|
try {
|
||||||
Expression exp = Expression.compile(component.substring(1), "x", "y", "z");
|
Expression exp = Expression.compile(component.substring(1), "x", "y", "z");
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.sk89q.worldedit.extension.factory;
|
package com.sk89q.worldedit.extension.factory;
|
||||||
|
|
||||||
import com.boydti.fawe.object.pattern.ExistingPattern;
|
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.Linear3DBlockPattern;
|
||||||
import com.boydti.fawe.object.pattern.LinearBlockPattern;
|
import com.boydti.fawe.object.pattern.LinearBlockPattern;
|
||||||
import com.boydti.fawe.object.pattern.MaskedPattern;
|
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.PatternExtent;
|
||||||
import com.boydti.fawe.object.pattern.RandomOffsetPattern;
|
import com.boydti.fawe.object.pattern.RandomOffsetPattern;
|
||||||
import com.boydti.fawe.object.pattern.RelativePattern;
|
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.EmptyClipboardException;
|
||||||
import com.sk89q.worldedit.LocalSession;
|
import com.sk89q.worldedit.LocalSession;
|
||||||
|
import com.sk89q.worldedit.Vector;
|
||||||
import com.sk89q.worldedit.WorldEdit;
|
import com.sk89q.worldedit.WorldEdit;
|
||||||
|
import com.sk89q.worldedit.entity.Player;
|
||||||
import com.sk89q.worldedit.extension.input.InputParseException;
|
import com.sk89q.worldedit.extension.input.InputParseException;
|
||||||
import com.sk89q.worldedit.extension.input.ParserContext;
|
import com.sk89q.worldedit.extension.input.ParserContext;
|
||||||
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
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.Expression;
|
||||||
import com.sk89q.worldedit.internal.expression.ExpressionException;
|
import com.sk89q.worldedit.internal.expression.ExpressionException;
|
||||||
import com.sk89q.worldedit.internal.registry.InputParser;
|
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.ClipboardHolder;
|
||||||
import com.sk89q.worldedit.session.request.Request;
|
import com.sk89q.worldedit.session.request.Request;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -137,6 +144,30 @@ public class HashTagPatternParser extends InputParser<Pattern> {
|
|||||||
} catch (NumberFormatException | ExpressionException e) {
|
} catch (NumberFormatException | ExpressionException e) {
|
||||||
throw new InputParseException("The correct format is #offset:<dx>:<dy>:<dz>:<pattern>");
|
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 "#randomoffset":
|
||||||
case "#spread": {
|
case "#spread": {
|
||||||
try {
|
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");
|
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:
|
default:
|
||||||
List<String> items = split(input, ',');
|
List<String> items = split(input, ',');
|
||||||
if (items.size() == 1) {
|
if (items.size() == 1) {
|
||||||
@ -183,6 +229,7 @@ public class HashTagPatternParser extends InputParser<Pattern> {
|
|||||||
}
|
}
|
||||||
BlockFactory blockRegistry = worldEdit.getBlockFactory();
|
BlockFactory blockRegistry = worldEdit.getBlockFactory();
|
||||||
RandomPattern randomPattern = new RandomPattern();
|
RandomPattern randomPattern = new RandomPattern();
|
||||||
|
try {
|
||||||
for (String token : items) {
|
for (String token : items) {
|
||||||
Pattern pattern;
|
Pattern pattern;
|
||||||
double chance;
|
double chance;
|
||||||
@ -192,7 +239,7 @@ public class HashTagPatternParser extends InputParser<Pattern> {
|
|||||||
if (p.length < 2) {
|
if (p.length < 2) {
|
||||||
throw new InputParseException("Missing the pattern after the % symbol for '" + input + "'");
|
throw new InputParseException("Missing the pattern after the % symbol for '" + input + "'");
|
||||||
} else {
|
} else {
|
||||||
chance = Double.parseDouble(p[0]);
|
chance = Expression.compile(p[0]).evaluate();
|
||||||
pattern = parseFromInput(p[1], context);
|
pattern = parseFromInput(p[1], context);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -201,6 +248,9 @@ public class HashTagPatternParser extends InputParser<Pattern> {
|
|||||||
}
|
}
|
||||||
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;
|
return randomPattern;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,6 @@ import com.sk89q.worldedit.entity.BaseEntity;
|
|||||||
import com.sk89q.worldedit.entity.Entity;
|
import com.sk89q.worldedit.entity.Entity;
|
||||||
import com.sk89q.worldedit.function.operation.Operation;
|
import com.sk89q.worldedit.function.operation.Operation;
|
||||||
import com.sk89q.worldedit.function.operation.OperationQueue;
|
import com.sk89q.worldedit.function.operation.OperationQueue;
|
||||||
import com.sk89q.worldedit.regions.CuboidRegion;
|
|
||||||
import com.sk89q.worldedit.regions.Region;
|
import com.sk89q.worldedit.regions.Region;
|
||||||
import com.sk89q.worldedit.util.Location;
|
import com.sk89q.worldedit.util.Location;
|
||||||
import com.sk89q.worldedit.world.biome.BaseBiome;
|
import com.sk89q.worldedit.world.biome.BaseBiome;
|
||||||
@ -86,7 +85,7 @@ public abstract class AbstractDelegateExtent implements Extent {
|
|||||||
mutable.x = x;
|
mutable.x = x;
|
||||||
mutable.y = y;
|
mutable.y = y;
|
||||||
mutable.z = z;
|
mutable.z = z;
|
||||||
return extent.setBlock(mutable, block);
|
return setBlock(mutable, block);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -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;
|
package com.sk89q.worldedit.extent.transform;
|
||||||
|
|
||||||
|
import com.boydti.fawe.FaweCache;
|
||||||
import com.sk89q.worldedit.Vector;
|
import com.sk89q.worldedit.Vector;
|
||||||
import com.sk89q.worldedit.WorldEditException;
|
import com.sk89q.worldedit.WorldEditException;
|
||||||
import com.sk89q.worldedit.blocks.BaseBlock;
|
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.BlockRegistry;
|
||||||
import com.sk89q.worldedit.world.registry.State;
|
import com.sk89q.worldedit.world.registry.State;
|
||||||
import com.sk89q.worldedit.world.registry.StateValue;
|
import com.sk89q.worldedit.world.registry.StateValue;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
@ -44,6 +26,8 @@ public class BlockTransformExtent extends AbstractDelegateExtent {
|
|||||||
|
|
||||||
private final Transform transform;
|
private final Transform transform;
|
||||||
private final BlockRegistry blockRegistry;
|
private final BlockRegistry blockRegistry;
|
||||||
|
private final BaseBlock[] BLOCK_TRANSFORM;
|
||||||
|
private final BaseBlock[] BLOCK_TRANSFORM_INVERSE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new instance.
|
* Create a new instance.
|
||||||
@ -57,6 +41,16 @@ public class BlockTransformExtent extends AbstractDelegateExtent {
|
|||||||
checkNotNull(blockRegistry);
|
checkNotNull(blockRegistry);
|
||||||
this.transform = transform;
|
this.transform = transform;
|
||||||
this.blockRegistry = blockRegistry;
|
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;
|
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
|
@Override
|
||||||
public BaseBlock getBlock(Vector position) {
|
public BaseBlock getBlock(Vector position) {
|
||||||
return transformBlock(super.getBlock(position), false);
|
return transformFast(super.getBlock(position));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BaseBlock getLazyBlock(Vector position) {
|
public BaseBlock getLazyBlock(Vector position) {
|
||||||
return transformBlock(super.getLazyBlock(position), false);
|
return transformFast(super.getLazyBlock(position));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean setBlock(Vector location, BaseBlock block) throws WorldEditException {
|
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.
|
* Transform the given block using the given transform.
|
||||||
@ -157,7 +147,7 @@ public class BlockTransformExtent extends AbstractDelegateExtent {
|
|||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private static StateValue getNewStateValue(State state, Transform transform, Vector oldDirection) {
|
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;
|
StateValue newValue = null;
|
||||||
double closest = -2;
|
double closest = -2;
|
||||||
boolean found = false;
|
boolean found = false;
|
||||||
|
@ -27,7 +27,6 @@ import com.sk89q.worldedit.entity.BaseEntity;
|
|||||||
import com.sk89q.worldedit.entity.Entity;
|
import com.sk89q.worldedit.entity.Entity;
|
||||||
import com.sk89q.worldedit.extent.Extent;
|
import com.sk89q.worldedit.extent.Extent;
|
||||||
import com.sk89q.worldedit.function.EntityFunction;
|
import com.sk89q.worldedit.function.EntityFunction;
|
||||||
import com.sk89q.worldedit.function.operation.ForwardExtentCopy;
|
|
||||||
import com.sk89q.worldedit.internal.helper.MCDirections;
|
import com.sk89q.worldedit.internal.helper.MCDirections;
|
||||||
import com.sk89q.worldedit.math.transform.Transform;
|
import com.sk89q.worldedit.math.transform.Transform;
|
||||||
import com.sk89q.worldedit.util.Direction;
|
import com.sk89q.worldedit.util.Direction;
|
||||||
@ -99,7 +98,7 @@ public class ExtentEntityCopy implements EntityFunction {
|
|||||||
|
|
||||||
newDirection = transform.isIdentity() ?
|
newDirection = transform.isIdentity() ?
|
||||||
entity.getLocation().getDirection()
|
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);
|
newLocation = new Location(destination, newPosition.add(to.round().add(0.5, 0.5, 0.5)), newDirection);
|
||||||
|
|
||||||
// Some entities store their position data in NBT
|
// Some entities store their position data in NBT
|
||||||
@ -148,7 +147,7 @@ public class ExtentEntityCopy implements EntityFunction {
|
|||||||
Direction direction = MCDirections.fromHanging(d);
|
Direction direction = MCDirections.fromHanging(d);
|
||||||
|
|
||||||
if (direction != null) {
|
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);
|
Direction newDirection = Direction.findClosest(vector, Flag.CARDINAL);
|
||||||
|
|
||||||
builder.putByte("Direction", (byte) MCDirections.toHanging(newDirection));
|
builder.putByte("Direction", (byte) MCDirections.toHanging(newDirection));
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.sk89q.worldedit.function.mask;
|
package com.sk89q.worldedit.function.mask;
|
||||||
|
|
||||||
import com.boydti.fawe.FaweCache;
|
import com.boydti.fawe.FaweCache;
|
||||||
|
import com.boydti.fawe.util.StringMan;
|
||||||
import com.sk89q.worldedit.Vector;
|
import com.sk89q.worldedit.Vector;
|
||||||
import com.sk89q.worldedit.blocks.BaseBlock;
|
import com.sk89q.worldedit.blocks.BaseBlock;
|
||||||
import com.sk89q.worldedit.extent.Extent;
|
import com.sk89q.worldedit.extent.Extent;
|
||||||
@ -110,6 +111,11 @@ public class BlockMask extends AbstractExtentMask {
|
|||||||
return computedLegacyList;
|
return computedLegacyList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return StringMan.getString(getBlocks());
|
||||||
|
}
|
||||||
|
|
||||||
public boolean test(int blockId) {
|
public boolean test(int blockId) {
|
||||||
return blockIds[blockId];
|
return blockIds[blockId];
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user