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;
+ }
+}
diff --git a/core/src/main/java/com/boydti/fawe/object/pattern/ExpressionPattern.java b/core/src/main/java/com/boydti/fawe/object/pattern/ExpressionPattern.java
new file mode 100644
index 00000000..72dddf02
--- /dev/null
+++ b/core/src/main/java/com/boydti/fawe/object/pattern/ExpressionPattern.java
@@ -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.
+ *
+ * Expressions are evaluated as {@code true} if they return a value
+ * greater than {@code 0}.
+ */
+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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/boydti/fawe/object/pattern/SolidRandomOffsetPattern.java b/core/src/main/java/com/boydti/fawe/object/pattern/SolidRandomOffsetPattern.java
new file mode 100644
index 00000000..71f03dbc
--- /dev/null
+++ b/core/src/main/java/com/boydti/fawe/object/pattern/SolidRandomOffsetPattern.java
@@ -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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/boydti/fawe/object/pattern/SurfaceRandomOffsetPattern.java b/core/src/main/java/com/boydti/fawe/object/pattern/SurfaceRandomOffsetPattern.java
new file mode 100644
index 00000000..8769720b
--- /dev/null
+++ b/core/src/main/java/com/boydti/fawe/object/pattern/SurfaceRandomOffsetPattern.java
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/boydti/fawe/util/ShapeInterpolator.java b/core/src/main/java/com/boydti/fawe/util/ShapeInterpolator.java
new file mode 100644
index 00000000..1e69c31b
--- /dev/null
+++ b/core/src/main/java/com/boydti/fawe/util/ShapeInterpolator.java
@@ -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;
+
+/**
+ * Original source
+ * 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.
+ *
+ * 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 savedPathEndPoints = new Vector();
+ 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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/sk89q/worldedit/EditSession.java b/core/src/main/java/com/sk89q/worldedit/EditSession.java
index 46b53ef1..e3a3c3bc 100644
--- a/core/src/main/java/com/sk89q/worldedit/EditSession.java
+++ b/core/src/main/java/com/sk89q/worldedit/EditSession.java
@@ -45,6 +45,7 @@ import com.boydti.fawe.object.extent.FastWorldEditExtent;
import com.boydti.fawe.object.extent.FaweRegionExtent;
import com.boydti.fawe.object.extent.NullExtent;
import com.boydti.fawe.object.extent.ProcessedWEExtent;
+import com.boydti.fawe.object.extent.TransformExtent;
import com.boydti.fawe.object.mask.ResettableMask;
import com.boydti.fawe.object.progress.DefaultProgressTracker;
import com.boydti.fawe.util.ExtentTraverser;
@@ -572,6 +573,21 @@ public class EditSession extends AbstractWorld implements HasFaweQueue {
return maskingExtent != null ? maskingExtent.get().getMask() : null;
}
+ public void addTransform(TransformExtent transform) {
+ if (transform == null) {
+ ExtentTraverser 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.
*
diff --git a/core/src/main/java/com/sk89q/worldedit/LocalSession.java b/core/src/main/java/com/sk89q/worldedit/LocalSession.java
index d4ca251d..f675334d 100644
--- a/core/src/main/java/com/sk89q/worldedit/LocalSession.java
+++ b/core/src/main/java/com/sk89q/worldedit/LocalSession.java
@@ -29,6 +29,7 @@ import com.boydti.fawe.object.brush.DoubleActionBrushTool;
import com.boydti.fawe.object.changeset.DiskStorageHistory;
import com.boydti.fawe.object.changeset.FaweChangeSet;
import com.boydti.fawe.object.clipboard.DiskOptimizedClipboard;
+import com.boydti.fawe.object.extent.TransformExtent;
import com.boydti.fawe.util.EditSessionBuilder;
import com.boydti.fawe.util.MainUtil;
import com.boydti.fawe.wrappers.WorldWrapper;
@@ -138,6 +139,7 @@ public class LocalSession {
private transient int cuiVersion = -1;
private transient boolean fastMode = false;
private transient Mask mask;
+ private TransformExtent transform = null;
private transient TimeZone timezone = TimeZone.getDefault();
private transient World currentWorld;
@@ -1203,7 +1205,12 @@ public class LocalSession {
getBlockChangeLimit(), blockBag, player);
editSession.setFastMode(fastMode);
Request.request().setEditSession(editSession);
- editSession.setMask(mask);
+ if (mask != null) {
+ editSession.setMask(mask);
+ }
+ if (transform != null) {
+ editSession.addTransform(transform);
+ }
return editSession;
}
@@ -1254,6 +1261,14 @@ public class LocalSession {
setMask(mask != null ? Masks.wrap(mask) : null);
}
+ public TransformExtent getTransform() {
+ return transform;
+ }
+
+ public void setTransform(TransformExtent transform) {
+ this.transform = transform;
+ }
+
public static Class> inject() {
return LocalSession.class;
}
diff --git a/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java b/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java
index cdfb943c..26a7bebf 100644
--- a/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java
+++ b/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java
@@ -390,7 +390,7 @@ public class BrushCommands {
public void command(Player player, LocalSession session, EditSession editSession, @Optional("5") double radius, CommandContext args) throws WorldEditException {
BrushTool tool = session.getBrushTool(player.getItemInHand());
String cmd = args.getJoinedStrings(1);
- tool.setBrush(new CommandBrush(player, cmd, radius), "worldedit.brush.copy");
+ tool.setBrush(new CommandBrush(player, tool, cmd, radius), "worldedit.brush.copy");
BBC.BRUSH_COMMAND.send(player, cmd);
}
diff --git a/core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java b/core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java
index 476b589f..13892e51 100644
--- a/core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java
+++ b/core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java
@@ -300,8 +300,8 @@ public class ClipboardCommands {
if (selectPasted) {
Vector clipboardOffset = clipboard.getRegion().getMinimumPoint().subtract(clipboard.getOrigin());
- Vector realTo = to.add(holder.getTransform().apply(clipboardOffset));
- Vector max = realTo.add(holder.getTransform().apply(region.getMaximumPoint().subtract(region.getMinimumPoint())));
+ Vector realTo = to.add(new Vector(holder.getTransform().apply(clipboardOffset)));
+ Vector max = realTo.add(new Vector(holder.getTransform().apply(region.getMaximumPoint().subtract(region.getMinimumPoint()))));
RegionSelector selector = new CuboidRegionSelector(player.getWorld(), realTo, max);
session.setRegionSelector(player.getWorld(), selector);
selector.learnChanges();
diff --git a/core/src/main/java/com/sk89q/worldedit/command/FlattenedClipboardTransform.java b/core/src/main/java/com/sk89q/worldedit/command/FlattenedClipboardTransform.java
index 1f52bda4..d557ed88 100644
--- a/core/src/main/java/com/sk89q/worldedit/command/FlattenedClipboardTransform.java
+++ b/core/src/main/java/com/sk89q/worldedit/command/FlattenedClipboardTransform.java
@@ -91,7 +91,7 @@ public class FlattenedClipboardTransform {
maximum.setZ(minimum.getZ()) };
for (int i = 0; i < corners.length; i++) {
- corners[i] = transformAround.apply(corners[i]);
+ corners[i] = transformAround.apply(new Vector(corners[i]));
}
Vector newMinimum = corners[0];
diff --git a/core/src/main/java/com/sk89q/worldedit/command/GeneralCommands.java b/core/src/main/java/com/sk89q/worldedit/command/GeneralCommands.java
index 5cbe39c9..cb86bafb 100644
--- a/core/src/main/java/com/sk89q/worldedit/command/GeneralCommands.java
+++ b/core/src/main/java/com/sk89q/worldedit/command/GeneralCommands.java
@@ -1,6 +1,8 @@
package com.sk89q.worldedit.command;
import com.boydti.fawe.config.BBC;
+import com.boydti.fawe.object.extent.DefaultTransformParser;
+import com.boydti.fawe.object.extent.TransformExtent;
import com.sk89q.minecraft.util.commands.Command;
import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandPermissions;
@@ -20,6 +22,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
public class GeneralCommands {
private final WorldEdit worldEdit;
+ private final DefaultTransformParser transformParser;
/**
* Create a new instance.
@@ -29,6 +32,7 @@ public class GeneralCommands {
public GeneralCommands(WorldEdit worldEdit) {
checkNotNull(worldEdit);
this.worldEdit = worldEdit;
+ transformParser = new DefaultTransformParser(worldEdit);
}
@Command(
@@ -102,7 +106,7 @@ public class GeneralCommands {
public void gmask(Player player, LocalSession session, EditSession editSession, @Optional CommandContext context) throws WorldEditException {
if (context == null || context.argsLength() == 0) {
session.setMask((Mask) null);
- BBC.BRUSH_MASK_DISABLED.send(player);
+ BBC.MASK_DISABLED.send(player);
} else {
ParserContext parserContext = new ParserContext();
parserContext.setActor(player);
@@ -111,7 +115,31 @@ public class GeneralCommands {
parserContext.setExtent(editSession);
Mask mask = worldEdit.getMaskFactory().parseFromInput(context.getJoinedStrings(0), parserContext);
session.setMask(mask);
- BBC.BRUSH_MASK.send(player);
+ BBC.MASK.send(player);
+ }
+ }
+
+ @Command(
+ aliases = { "/gtransform", "gtransform" },
+ usage = "[transform]",
+ desc = "Set the global transform",
+ min = 0,
+ max = -1
+ )
+ @CommandPermissions("worldedit.global-trasnform")
+ public void gtransform(Player player, LocalSession session, EditSession editSession, @Optional CommandContext context) throws WorldEditException {
+ if (context == null || context.argsLength() == 0) {
+ session.setTransform(null);
+ BBC.TRANSFORM_DISABLED.send(player);
+ } else {
+ ParserContext parserContext = new ParserContext();
+ parserContext.setActor(player);
+ parserContext.setWorld(player.getWorld());
+ parserContext.setSession(session);
+ parserContext.setExtent(editSession);
+ TransformExtent transform = transformParser.parseFromInput(context.getJoinedStrings(0), parserContext);
+ session.setTransform(transform);
+ BBC.TRANSFORM.send(player);
}
}
diff --git a/core/src/main/java/com/sk89q/worldedit/command/ToolUtilCommands.java b/core/src/main/java/com/sk89q/worldedit/command/ToolUtilCommands.java
index c6071386..e56507da 100644
--- a/core/src/main/java/com/sk89q/worldedit/command/ToolUtilCommands.java
+++ b/core/src/main/java/com/sk89q/worldedit/command/ToolUtilCommands.java
@@ -2,6 +2,8 @@ package com.sk89q.worldedit.command;
import com.boydti.fawe.config.BBC;
import com.boydti.fawe.object.brush.DoubleActionBrushTool;
+import com.boydti.fawe.object.extent.DefaultTransformParser;
+import com.boydti.fawe.object.extent.TransformExtent;
import com.sk89q.minecraft.util.commands.Command;
import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandPermissions;
@@ -22,9 +24,11 @@ import com.sk89q.worldedit.util.command.parametric.Optional;
*/
public class ToolUtilCommands {
private final WorldEdit we;
+ private final DefaultTransformParser transformParser;
public ToolUtilCommands(WorldEdit we) {
this.we = we;
+ this.transformParser = new DefaultTransformParser(we);
}
@Command(
@@ -95,6 +99,42 @@ public class ToolUtilCommands {
}
}
+ @Command(
+ aliases = { "transform" },
+ usage = "[transform]",
+ desc = "Set the brush transform",
+ min = 0,
+ max = -1
+ )
+ @CommandPermissions("worldedit.brush.options.transform")
+ public void transform(Player player, LocalSession session, EditSession editSession, @Optional CommandContext context) throws WorldEditException {
+ Tool tool = session.getTool(player.getItemInHand());
+ if (tool == null) {
+ return;
+ }
+ if (context == null || context.argsLength() == 0) {
+ if (tool instanceof BrushTool) {
+ ((BrushTool) tool).setTransform(null);
+ } else if (tool instanceof DoubleActionBrushTool) {
+ ((DoubleActionBrushTool) tool).setTransform(null);
+ }
+ BBC.BRUSH_TRANSFORM_DISABLED.send(player);
+ } else {
+ ParserContext parserContext = new ParserContext();
+ parserContext.setActor(player);
+ parserContext.setWorld(player.getWorld());
+ parserContext.setSession(session);
+ parserContext.setExtent(editSession);
+ TransformExtent transform = transformParser.parseFromInput(context.getJoinedStrings(0), parserContext);
+ if (tool instanceof BrushTool) {
+ ((BrushTool) tool).setTransform(transform);
+ } else if (tool instanceof DoubleActionBrushTool) {
+ ((DoubleActionBrushTool) tool).setTransform(transform);
+ }
+ BBC.BRUSH_TRANSFORM.send(player);
+ }
+ }
+
@Command(
aliases = { "mat", "material" },
usage = "[pattern]",
@@ -104,7 +144,12 @@ public class ToolUtilCommands {
)
@CommandPermissions("worldedit.brush.options.material")
public void material(Player player, LocalSession session, EditSession editSession, Pattern pattern) throws WorldEditException {
- session.getBrushTool(player.getItemInHand()).setFill(pattern);
+ Tool tool = session.getTool(player.getItemInHand());
+ if (tool instanceof BrushTool) {
+ ((BrushTool) tool).setMask(null);
+ } else if (tool instanceof DoubleActionBrushTool) {
+ ((DoubleActionBrushTool) tool).setFill(pattern);
+ }
BBC.BRUSH_MATERIAL.send(player);
}
@@ -118,7 +163,12 @@ public class ToolUtilCommands {
@CommandPermissions("worldedit.brush.options.range")
public void range(Player player, LocalSession session, EditSession editSession, CommandContext args) throws WorldEditException {
int range = args.getInteger(0);
- session.getBrushTool(player.getItemInHand()).setRange(range);
+ Tool tool = session.getTool(player.getItemInHand());
+ if (tool instanceof BrushTool) {
+ ((BrushTool) tool).setMask(null);
+ } else if (tool instanceof DoubleActionBrushTool) {
+ ((DoubleActionBrushTool) tool).setRange(range);
+ }
BBC.BRUSH_RANGE.send(player);
}
@@ -135,7 +185,12 @@ public class ToolUtilCommands {
int radius = args.getInteger(0);
we.checkMaxBrushRadius(radius);
- session.getBrushTool(player.getItemInHand()).setSize(radius);
+ Tool tool = session.getTool(player.getItemInHand());
+ if (tool instanceof BrushTool) {
+ ((BrushTool) tool).setMask(null);
+ } else if (tool instanceof DoubleActionBrushTool) {
+ ((DoubleActionBrushTool) tool).setSize(radius);
+ }
BBC.BRUSH_SIZE.send(player);
}
diff --git a/core/src/main/java/com/sk89q/worldedit/command/tool/BrushTool.java b/core/src/main/java/com/sk89q/worldedit/command/tool/BrushTool.java
new file mode 100644
index 00000000..6e61142f
--- /dev/null
+++ b/core/src/main/java/com/sk89q/worldedit/command/tool/BrushTool.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/sk89q/worldedit/extension/factory/DefaultMaskParser.java b/core/src/main/java/com/sk89q/worldedit/extension/factory/DefaultMaskParser.java
index 21b8c781..3d279327 100644
--- a/core/src/main/java/com/sk89q/worldedit/extension/factory/DefaultMaskParser.java
+++ b/core/src/main/java/com/sk89q/worldedit/extension/factory/DefaultMaskParser.java
@@ -7,12 +7,15 @@ import com.boydti.fawe.object.mask.DataMask;
import com.boydti.fawe.object.mask.IdDataMask;
import com.boydti.fawe.object.mask.IdMask;
import com.boydti.fawe.object.mask.RadiusMask;
+import com.boydti.fawe.object.mask.WallMask;
import com.boydti.fawe.object.mask.XAxisMask;
import com.boydti.fawe.object.mask.YAxisMask;
import com.boydti.fawe.object.mask.ZAxisMask;
+import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.IncompleteRegionException;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEdit;
+import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.extension.input.InputParseException;
import com.sk89q.worldedit.extension.input.NoMatchException;
import com.sk89q.worldedit.extension.input.ParserContext;
@@ -102,6 +105,12 @@ public class DefaultMaskParser extends InputParser {
final char firstChar = component.charAt(0);
switch (firstChar) {
case '#':
+ int colon = component.indexOf(':');
+ if (colon != -1) {
+ String rest = component.substring(colon + 1);
+ component = component.substring(0, colon);
+ masks.add(getBlockMaskComponent(masks, rest, context));
+ }
switch (component.toLowerCase()) {
case "#existing":
return new ExistingBlockMask(extent);
@@ -131,6 +140,13 @@ public class DefaultMaskParser extends InputParser {
return new DataMask(extent);
case "#iddata":
return new IdDataMask(extent);
+ case "#wall":
+ masks.add(new ExistingBlockMask(extent));
+ BlockMask matchAir = new BlockMask(extent, EditSession.nullBlock);
+ return new WallMask(extent, Arrays.asList(new BaseBlock(0)), 1, 8);
+ case "#surface":
+ masks.add(new ExistingBlockMask(extent));
+ return new AdjacentMask(extent, Arrays.asList(new BaseBlock(0)), 1, 8);
default:
throw new NoMatchException("Unrecognized mask '" + component + "'");
}
@@ -141,10 +157,10 @@ public class DefaultMaskParser extends InputParser {
throw new InputParseException("Unknown angle '" + component + "' (not in form `/#,#`)");
}
try {
- int y1 = Integer.parseInt(split[0]);
- int y2 = Integer.parseInt(split[1]);
+ int y1 = (int) Math.abs(Expression.compile(split[0]).evaluate());
+ int y2 = (int) Math.abs(Expression.compile(split[1]).evaluate());
return new AngleMask(extent, y1, y2);
- } catch (NumberFormatException e) {
+ } catch (NumberFormatException | ExpressionException e) {
throw new InputParseException("Unknown angle '" + component + "' (not in form `/#,#`)");
}
}
@@ -154,13 +170,14 @@ public class DefaultMaskParser extends InputParser {
throw new InputParseException("Unknown range '" + component + "' (not in form `{#,#`)");
}
try {
- int y1 = Integer.parseInt(split[0]);
- int y2 = Integer.parseInt(split[1]);
+ int y1 = (int) Math.abs(Expression.compile(split[0]).evaluate());
+ int y2 = (int) Math.abs(Expression.compile(split[1]).evaluate());
return new RadiusMask(y1, y2);
- } catch (NumberFormatException e) {
+ } catch (NumberFormatException | ExpressionException e) {
throw new InputParseException("Unknown range '" + component + "' (not in form `{#,#`)");
}
}
+ case '|':
case '~': {
String[] split = component.substring(1).split("=");
ParserContext tempContext = new ParserContext(context);
@@ -171,13 +188,17 @@ public class DefaultMaskParser extends InputParser {
int requiredMax = 8;
if (split.length == 2) {
String[] split2 = split[1].split(",");
- requiredMin = Integer.parseInt(split2[0]);
+ requiredMin = (int) Math.abs(Expression.compile(split2[0]).evaluate());
if (split2.length == 2) {
- requiredMax = Integer.parseInt(split2[1]);
+ requiredMax = (int) Math.abs(Expression.compile(split2[1]).evaluate());
}
}
- return new AdjacentMask(extent, worldEdit.getBlockFactory().parseFromListInput(component.substring(1), tempContext), requiredMin, requiredMax);
- } catch (NumberFormatException e) {
+ if (firstChar == '~') {
+ return new AdjacentMask(extent, worldEdit.getBlockFactory().parseFromListInput(component.substring(1), tempContext), requiredMin, requiredMax);
+ } else {
+ return new WallMask(extent, worldEdit.getBlockFactory().parseFromListInput(component.substring(1), tempContext), requiredMin, requiredMax);
+ }
+ } catch (NumberFormatException | ExpressionException e) {
throw new InputParseException("Unknown adjacent mask '" + component + "' (not in form `~[=count]`)");
}
}
@@ -208,9 +229,12 @@ public class DefaultMaskParser extends InputParser {
return Masks.asMask(new BiomeMask2D(context.requireExtent(), biomes));
case '%':
- int i = Integer.parseInt(component.substring(1));
- return new NoiseFilter(new RandomNoise(), ((double) i) / 100);
-
+ try {
+ double i = Math.abs(Expression.compile(component.substring(1)).evaluate());
+ return new NoiseFilter(new RandomNoise(), (i) / 100);
+ } catch (NumberFormatException | ExpressionException e) {
+ throw new InputParseException("Unknown percentage '" + component.substring(1) + "'");
+ }
case '=':
try {
Expression exp = Expression.compile(component.substring(1), "x", "y", "z");
diff --git a/core/src/main/java/com/sk89q/worldedit/extension/factory/HashTagPatternParser.java b/core/src/main/java/com/sk89q/worldedit/extension/factory/HashTagPatternParser.java
index 668bcc9b..b4c6092e 100644
--- a/core/src/main/java/com/sk89q/worldedit/extension/factory/HashTagPatternParser.java
+++ b/core/src/main/java/com/sk89q/worldedit/extension/factory/HashTagPatternParser.java
@@ -1,6 +1,7 @@
package com.sk89q.worldedit.extension.factory;
import com.boydti.fawe.object.pattern.ExistingPattern;
+import com.boydti.fawe.object.pattern.ExpressionPattern;
import com.boydti.fawe.object.pattern.Linear3DBlockPattern;
import com.boydti.fawe.object.pattern.LinearBlockPattern;
import com.boydti.fawe.object.pattern.MaskedPattern;
@@ -11,9 +12,14 @@ import com.boydti.fawe.object.pattern.OffsetPattern;
import com.boydti.fawe.object.pattern.PatternExtent;
import com.boydti.fawe.object.pattern.RandomOffsetPattern;
import com.boydti.fawe.object.pattern.RelativePattern;
+import com.boydti.fawe.object.pattern.SolidRandomOffsetPattern;
+import com.boydti.fawe.object.pattern.SurfaceRandomOffsetPattern;
+import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.EmptyClipboardException;
import com.sk89q.worldedit.LocalSession;
+import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEdit;
+import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.extension.input.InputParseException;
import com.sk89q.worldedit.extension.input.ParserContext;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
@@ -25,6 +31,7 @@ import com.sk89q.worldedit.function.pattern.RandomPattern;
import com.sk89q.worldedit.internal.expression.Expression;
import com.sk89q.worldedit.internal.expression.ExpressionException;
import com.sk89q.worldedit.internal.registry.InputParser;
+import com.sk89q.worldedit.regions.shape.WorldEditExpressionEnvironment;
import com.sk89q.worldedit.session.ClipboardHolder;
import com.sk89q.worldedit.session.request.Request;
import java.util.ArrayList;
@@ -137,6 +144,30 @@ public class HashTagPatternParser extends InputParser {
} catch (NumberFormatException | ExpressionException e) {
throw new InputParseException("The correct format is #offset::::");
}
+ 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::::");
+ }
+ }
+ 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::::");
+ }
+ }
case "#randomoffset":
case "#spread": {
try {
@@ -176,6 +207,21 @@ public class HashTagPatternParser extends InputParser {
}
throw new InputParseException("Invalid, see: https://github.com/boy0001/FastAsyncWorldedit/wiki/WorldEdit-and-FAWE-patterns");
}
+ case '=': {
+ try {
+ Expression exp = Expression.compile(input.substring(1), "x", "y", "z");
+ EditSession editSession = Request.request().getEditSession();
+ if (editSession == null) {
+ editSession = context.requireSession().createEditSession((Player) context.getActor());
+ }
+ WorldEditExpressionEnvironment env = new WorldEditExpressionEnvironment(
+ editSession, Vector.ONE, Vector.ZERO);
+ exp.setEnvironment(env);
+ return new ExpressionPattern(exp);
+ } catch (ExpressionException e) {
+ throw new InputParseException("Invalid expression: " + e.getMessage());
+ }
+ }
default:
List items = split(input, ',');
if (items.size() == 1) {
@@ -183,23 +229,27 @@ public class HashTagPatternParser extends InputParser {
}
BlockFactory blockRegistry = worldEdit.getBlockFactory();
RandomPattern randomPattern = new RandomPattern();
- for (String token : items) {
- Pattern pattern;
- double chance;
- // Parse special percentage syntax
- if (token.matches("[0-9]+(\\.[0-9]*)?%.*")) {
- String[] p = token.split("%");
- if (p.length < 2) {
- throw new InputParseException("Missing the pattern after the % symbol for '" + input + "'");
+ try {
+ for (String token : items) {
+ Pattern pattern;
+ double chance;
+ // Parse special percentage syntax
+ if (token.matches("[0-9]+(\\.[0-9]*)?%.*")) {
+ String[] p = token.split("%");
+ if (p.length < 2) {
+ throw new InputParseException("Missing the pattern after the % symbol for '" + input + "'");
+ } else {
+ chance = Expression.compile(p[0]).evaluate();
+ pattern = parseFromInput(p[1], context);
+ }
} else {
- chance = Double.parseDouble(p[0]);
- pattern = parseFromInput(p[1], context);
+ chance = 1;
+ pattern = parseFromInput(token, context);
}
- } else {
- chance = 1;
- pattern = parseFromInput(token, context);
+ randomPattern.add(pattern, chance);
}
- randomPattern.add(pattern, chance);
+ } catch (NumberFormatException | ExpressionException e) {
+ throw new InputParseException("Invalid, see: https://github.com/boy0001/FastAsyncWorldedit/wiki/WorldEdit-and-FAWE-patterns");
}
return randomPattern;
diff --git a/core/src/main/java/com/sk89q/worldedit/extent/AbstractDelegateExtent.java b/core/src/main/java/com/sk89q/worldedit/extent/AbstractDelegateExtent.java
index ac3c8940..e046a834 100644
--- a/core/src/main/java/com/sk89q/worldedit/extent/AbstractDelegateExtent.java
+++ b/core/src/main/java/com/sk89q/worldedit/extent/AbstractDelegateExtent.java
@@ -27,7 +27,6 @@ import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.entity.Entity;
import com.sk89q.worldedit.function.operation.Operation;
import com.sk89q.worldedit.function.operation.OperationQueue;
-import com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.world.biome.BaseBiome;
@@ -86,7 +85,7 @@ public abstract class AbstractDelegateExtent implements Extent {
mutable.x = x;
mutable.y = y;
mutable.z = z;
- return extent.setBlock(mutable, block);
+ return setBlock(mutable, block);
}
@Override
diff --git a/core/src/main/java/com/sk89q/worldedit/extent/transform/BlockTransformExtent.java b/core/src/main/java/com/sk89q/worldedit/extent/transform/BlockTransformExtent.java
index e0eceb85..6b57f6e0 100644
--- a/core/src/main/java/com/sk89q/worldedit/extent/transform/BlockTransformExtent.java
+++ b/core/src/main/java/com/sk89q/worldedit/extent/transform/BlockTransformExtent.java
@@ -1,24 +1,6 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q
- * 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 .
- */
-
package com.sk89q.worldedit.extent.transform;
+import com.boydti.fawe.FaweCache;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.blocks.BaseBlock;
@@ -28,9 +10,9 @@ import com.sk89q.worldedit.math.transform.Transform;
import com.sk89q.worldedit.world.registry.BlockRegistry;
import com.sk89q.worldedit.world.registry.State;
import com.sk89q.worldedit.world.registry.StateValue;
-
-import javax.annotation.Nullable;
import java.util.Map;
+import javax.annotation.Nullable;
+
import static com.google.common.base.Preconditions.checkNotNull;
@@ -44,6 +26,8 @@ public class BlockTransformExtent extends AbstractDelegateExtent {
private final Transform transform;
private final BlockRegistry blockRegistry;
+ private final BaseBlock[] BLOCK_TRANSFORM;
+ private final BaseBlock[] BLOCK_TRANSFORM_INVERSE;
/**
* Create a new instance.
@@ -57,6 +41,16 @@ public class BlockTransformExtent extends AbstractDelegateExtent {
checkNotNull(blockRegistry);
this.transform = transform;
this.blockRegistry = blockRegistry;
+ BLOCK_TRANSFORM = new BaseBlock[FaweCache.CACHE_BLOCK.length];
+ BLOCK_TRANSFORM_INVERSE = new BaseBlock[FaweCache.CACHE_BLOCK.length];
+ Transform inverse = transform.inverse();
+ for (int i = 0; i < BLOCK_TRANSFORM.length; i++) {
+ BaseBlock block = FaweCache.CACHE_BLOCK[i];
+ if (block != null) {
+ BLOCK_TRANSFORM[i] = transform(new BaseBlock(block), transform, blockRegistry);
+ BLOCK_TRANSFORM_INVERSE[i] = transform(new BaseBlock(block), inverse, blockRegistry);
+ }
+ }
}
/**
@@ -68,32 +62,28 @@ public class BlockTransformExtent extends AbstractDelegateExtent {
return transform;
}
- /**
- * Transform a block without making a copy.
- *
- * @param block the block
- * @param reverse true to transform in the opposite direction
- * @return the same block
- */
- private BaseBlock transformBlock(BaseBlock block, boolean reverse) {
- return transform(block, reverse ? transform.inverse() : transform, blockRegistry);
- }
-
@Override
public BaseBlock getBlock(Vector position) {
- return transformBlock(super.getBlock(position), false);
+ return transformFast(super.getBlock(position));
}
@Override
public BaseBlock getLazyBlock(Vector position) {
- return transformBlock(super.getLazyBlock(position), false);
+ return transformFast(super.getLazyBlock(position));
}
@Override
public boolean setBlock(Vector location, BaseBlock block) throws WorldEditException {
- return super.setBlock(location, transformBlock(block, true));
+ return super.setBlock(location, transformFastInverse(block));
}
+ private final BaseBlock transformFast(BaseBlock block) {
+ return BLOCK_TRANSFORM[FaweCache.getCombined(block)];
+ }
+
+ private final BaseBlock transformFastInverse(BaseBlock block) {
+ return BLOCK_TRANSFORM_INVERSE[FaweCache.getCombined(block)];
+ }
/**
* Transform the given block using the given transform.
@@ -157,7 +147,7 @@ public class BlockTransformExtent extends AbstractDelegateExtent {
*/
@Nullable
private static StateValue getNewStateValue(State state, Transform transform, Vector oldDirection) {
- Vector newDirection = transform.apply(oldDirection).subtract(transform.apply(Vector.ZERO)).normalize();
+ Vector newDirection = new Vector(transform.apply(oldDirection)).subtract(transform.apply(Vector.ZERO)).normalize();
StateValue newValue = null;
double closest = -2;
boolean found = false;
diff --git a/core/src/main/java/com/sk89q/worldedit/function/entity/ExtentEntityCopy.java b/core/src/main/java/com/sk89q/worldedit/function/entity/ExtentEntityCopy.java
index cc8e1828..248775b9 100644
--- a/core/src/main/java/com/sk89q/worldedit/function/entity/ExtentEntityCopy.java
+++ b/core/src/main/java/com/sk89q/worldedit/function/entity/ExtentEntityCopy.java
@@ -27,7 +27,6 @@ import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.entity.Entity;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.function.EntityFunction;
-import com.sk89q.worldedit.function.operation.ForwardExtentCopy;
import com.sk89q.worldedit.internal.helper.MCDirections;
import com.sk89q.worldedit.math.transform.Transform;
import com.sk89q.worldedit.util.Direction;
@@ -99,7 +98,7 @@ public class ExtentEntityCopy implements EntityFunction {
newDirection = transform.isIdentity() ?
entity.getLocation().getDirection()
- : transform.apply(location.getDirection()).subtract(transform.apply(Vector.ZERO)).normalize();
+ : new Vector(transform.apply(location.getDirection())).subtract(transform.apply(Vector.ZERO)).normalize();
newLocation = new Location(destination, newPosition.add(to.round().add(0.5, 0.5, 0.5)), newDirection);
// Some entities store their position data in NBT
@@ -148,7 +147,7 @@ public class ExtentEntityCopy implements EntityFunction {
Direction direction = MCDirections.fromHanging(d);
if (direction != null) {
- Vector vector = transform.apply(direction.toVector()).subtract(transform.apply(Vector.ZERO)).normalize();
+ Vector vector = new Vector(transform.apply(direction.toVector())).subtract(transform.apply(Vector.ZERO)).normalize();
Direction newDirection = Direction.findClosest(vector, Flag.CARDINAL);
builder.putByte("Direction", (byte) MCDirections.toHanging(newDirection));
diff --git a/core/src/main/java/com/sk89q/worldedit/function/mask/BlockMask.java b/core/src/main/java/com/sk89q/worldedit/function/mask/BlockMask.java
index be9ef577..61b0a8ae 100644
--- a/core/src/main/java/com/sk89q/worldedit/function/mask/BlockMask.java
+++ b/core/src/main/java/com/sk89q/worldedit/function/mask/BlockMask.java
@@ -1,6 +1,7 @@
package com.sk89q.worldedit.function.mask;
import com.boydti.fawe.FaweCache;
+import com.boydti.fawe.util.StringMan;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.extent.Extent;
@@ -110,6 +111,11 @@ public class BlockMask extends AbstractExtentMask {
return computedLegacyList;
}
+ @Override
+ public String toString() {
+ return StringMan.getString(getBlocks());
+ }
+
public boolean test(int blockId) {
return blockIds[blockId];
}
diff --git a/core/src/main/java/com/sk89q/worldedit/math/transform/AffineTransform.java b/core/src/main/java/com/sk89q/worldedit/math/transform/AffineTransform.java
new file mode 100644
index 00000000..26d6d508
--- /dev/null
+++ b/core/src/main/java/com/sk89q/worldedit/math/transform/AffineTransform.java
@@ -0,0 +1,307 @@
+package com.sk89q.worldedit.math.transform;
+
+import com.sk89q.worldedit.Vector;
+import com.sk89q.worldedit.math.MathUtils;
+
+/**
+ * An affine transform.
+ *
+ * This class is from the
+ * JavaGeom project,
+ * which is licensed under LGPL v2.1.
+ */
+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;
+ }
+}
\ No newline at end of file