More geometrical progress! Not compiling right now because of lack of cubic and
quad curves.
This commit is contained in:
@@ -33,6 +33,6 @@ public abstract class AbstractDimension implements IDimension
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString () {
|
public String toString () {
|
||||||
return Geometry.dimenToString(getWidth(), getHeight());
|
return Dimensions.dimenToString(getWidth(), getHeight());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
package pythagoras.f;
|
package pythagoras.f;
|
||||||
|
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides most of the implementation of {@link ILine}, obtaining only the start and end points
|
* Provides most of the implementation of {@link ILine}, obtaining only the start and end points
|
||||||
* from the derived class.
|
* from the derived class.
|
||||||
@@ -45,42 +47,42 @@ public abstract class AbstractLine implements ILine
|
|||||||
|
|
||||||
@Override // from interface ILine
|
@Override // from interface ILine
|
||||||
public float pointLineDistSq (float px, float py) {
|
public float pointLineDistSq (float px, float py) {
|
||||||
return Geometry.pointLineDistSq(px, py, getX1(), getY1(), getX2(), getY2());
|
return Lines.pointLineDistSq(px, py, getX1(), getY1(), getX2(), getY2());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override // from interface ILine
|
@Override // from interface ILine
|
||||||
public float pointLineDistSq (IPoint p) {
|
public float pointLineDistSq (IPoint p) {
|
||||||
return Geometry.pointLineDistSq(p.getX(), p.getY(), getX1(), getY1(), getX2(), getY2());
|
return Lines.pointLineDistSq(p.getX(), p.getY(), getX1(), getY1(), getX2(), getY2());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override // from interface ILine
|
@Override // from interface ILine
|
||||||
public float pointLineDist (float px, float py) {
|
public float pointLineDist (float px, float py) {
|
||||||
return Geometry.pointLineDist(px, py, getX1(), getY1(), getX2(), getY2());
|
return Lines.pointLineDist(px, py, getX1(), getY1(), getX2(), getY2());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override // from interface ILine
|
@Override // from interface ILine
|
||||||
public float pointLineDist (IPoint p) {
|
public float pointLineDist (IPoint p) {
|
||||||
return Geometry.pointLineDist(p.getX(), p.getY(), getX1(), getY1(), getX2(), getY2());
|
return Lines.pointLineDist(p.getX(), p.getY(), getX1(), getY1(), getX2(), getY2());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override // from interface ILine
|
@Override // from interface ILine
|
||||||
public float pointSegDistSq (float px, float py) {
|
public float pointSegDistSq (float px, float py) {
|
||||||
return Geometry.pointSegDistSq(px, py, getX1(), getY1(), getX2(), getY2());
|
return Lines.pointSegDistSq(px, py, getX1(), getY1(), getX2(), getY2());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override // from interface ILine
|
@Override // from interface ILine
|
||||||
public float pointSegDistSq (IPoint p) {
|
public float pointSegDistSq (IPoint p) {
|
||||||
return Geometry.pointSegDistSq(p.getX(), p.getY(), getX1(), getY1(), getX2(), getY2());
|
return Lines.pointSegDistSq(p.getX(), p.getY(), getX1(), getY1(), getX2(), getY2());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override // from interface ILine
|
@Override // from interface ILine
|
||||||
public float pointSegDist (float px, float py) {
|
public float pointSegDist (float px, float py) {
|
||||||
return Geometry.pointSegDist(px, py, getX1(), getY1(), getX2(), getY2());
|
return Lines.pointSegDist(px, py, getX1(), getY1(), getX2(), getY2());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override // from interface ILine
|
@Override // from interface ILine
|
||||||
public float pointSegDist (IPoint p) {
|
public float pointSegDist (IPoint p) {
|
||||||
return Geometry.pointSegDist(p.getX(), p.getY(), getX1(), getY1(), getX2(), getY2());
|
return Lines.pointSegDist(p.getX(), p.getY(), getX1(), getY1(), getX2(), getY2());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override // from interface ILine
|
@Override // from interface ILine
|
||||||
@@ -115,7 +117,7 @@ public abstract class AbstractLine implements ILine
|
|||||||
|
|
||||||
@Override // from interface IShape
|
@Override // from interface IShape
|
||||||
public boolean intersects (float rx, float ry, float rw, float rh) {
|
public boolean intersects (float rx, float ry, float rw, float rh) {
|
||||||
return Geometry.lineIntersectsRect(getX1(), getY1(), getX2(), getY2(), rx, ry, rw, rh);
|
return Lines.lineIntersectsRect(getX1(), getY1(), getX2(), getY2(), rx, ry, rw, rh);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override // from interface IShape
|
@Override // from interface IShape
|
||||||
@@ -167,4 +169,62 @@ public abstract class AbstractLine implements ILine
|
|||||||
}
|
}
|
||||||
return new Rectangle(rx, ry, rw, rh);
|
return new Rectangle(rx, ry, rw, rh);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override // from interface IShape
|
||||||
|
public PathIterator getPathIterator (AffineTransform at) {
|
||||||
|
return new Iterator(this, at);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // from interface IShape
|
||||||
|
public PathIterator getPathIterator (AffineTransform at, float flatness) {
|
||||||
|
return new Iterator(this, at);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** An iterator over an {@link ILine}. */
|
||||||
|
protected static class Iterator implements PathIterator
|
||||||
|
{
|
||||||
|
private float x1, y1, x2, y2;
|
||||||
|
private AffineTransform t;
|
||||||
|
private int index;
|
||||||
|
|
||||||
|
Iterator (ILine l, AffineTransform at) {
|
||||||
|
this.x1 = l.getX1();
|
||||||
|
this.y1 = l.getY1();
|
||||||
|
this.x2 = l.getX2();
|
||||||
|
this.y2 = l.getY2();
|
||||||
|
this.t = at;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int getWindingRule () {
|
||||||
|
return WIND_NON_ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public boolean isDone () {
|
||||||
|
return index > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void next () {
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int currentSegment (float[] coords) {
|
||||||
|
if (isDone()) {
|
||||||
|
throw new NoSuchElementException("Iterator out of bounds");
|
||||||
|
}
|
||||||
|
int type;
|
||||||
|
if (index == 0) {
|
||||||
|
type = SEG_MOVETO;
|
||||||
|
coords[0] = x1;
|
||||||
|
coords[1] = y1;
|
||||||
|
} else {
|
||||||
|
type = SEG_LINETO;
|
||||||
|
coords[0] = x2;
|
||||||
|
coords[1] = y2;
|
||||||
|
}
|
||||||
|
if (t != null) {
|
||||||
|
t.transform(coords, 0, coords, 0, 1);
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,22 +11,22 @@ public abstract class AbstractPoint implements IPoint
|
|||||||
{
|
{
|
||||||
@Override // from interface IPoint
|
@Override // from interface IPoint
|
||||||
public float distanceSq (float px, float py) {
|
public float distanceSq (float px, float py) {
|
||||||
return Geometry.distanceSq(getX(), getY(), px, py);
|
return Points.distanceSq(getX(), getY(), px, py);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override // from interface IPoint
|
@Override // from interface IPoint
|
||||||
public float distanceSq (IPoint p) {
|
public float distanceSq (IPoint p) {
|
||||||
return Geometry.distanceSq(getX(), getY(), p.getX(), p.getY());
|
return Points.distanceSq(getX(), getY(), p.getX(), p.getY());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override // from interface IPoint
|
@Override // from interface IPoint
|
||||||
public float distance (float px, float py) {
|
public float distance (float px, float py) {
|
||||||
return Geometry.distance(getX(), getY(), px, py);
|
return Points.distance(getX(), getY(), px, py);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override // from interface IPoint
|
@Override // from interface IPoint
|
||||||
public float distance (IPoint p) {
|
public float distance (IPoint p) {
|
||||||
return Geometry.distance(getX(), getY(), p.getX(), p.getY());
|
return Points.distance(getX(), getY(), p.getX(), p.getY());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override // from interface IPoint
|
@Override // from interface IPoint
|
||||||
@@ -53,6 +53,6 @@ public abstract class AbstractPoint implements IPoint
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString () {
|
public String toString () {
|
||||||
return Geometry.pointToString(getX(), getY());
|
return Points.pointToString(getX(), getY());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
package pythagoras.f;
|
package pythagoras.f;
|
||||||
|
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides most of the implementation of {@link IRectangle}, obtaining only the location and
|
* Provides most of the implementation of {@link IRectangle}, obtaining only the location and
|
||||||
* dimensions from the derived class.
|
* dimensions from the derived class.
|
||||||
@@ -66,7 +68,7 @@ public abstract class AbstractRectangle extends RectangularShape implements IRec
|
|||||||
|
|
||||||
@Override // from interface IRectangle
|
@Override // from interface IRectangle
|
||||||
public boolean intersectsLine (float x1, float y1, float x2, float y2) {
|
public boolean intersectsLine (float x1, float y1, float x2, float y2) {
|
||||||
return Geometry.lineIntersectsRect(x1, y1, x2, y2, getX(), getY(), getWidth(), getHeight());
|
return Lines.lineIntersectsRect(x1, y1, x2, y2, getX(), getY(), getWidth(), getHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override // from interface IRectangle
|
@Override // from interface IRectangle
|
||||||
@@ -135,6 +137,16 @@ public abstract class AbstractRectangle extends RectangularShape implements IRec
|
|||||||
return (rx + rw > x1) && (rx < x2) && (ry + rh > y1) && (ry < y2);
|
return (rx + rw > x1) && (rx < x2) && (ry + rh > y1) && (ry < y2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override // from interface IShape
|
||||||
|
public PathIterator getPathIterator (AffineTransform t) {
|
||||||
|
return new Iterator(this, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // from interface IShape
|
||||||
|
public PathIterator getPathIterator (AffineTransform t, float flatness) {
|
||||||
|
return new Iterator(this, t);
|
||||||
|
}
|
||||||
|
|
||||||
@Override // from RectangularShape
|
@Override // from RectangularShape
|
||||||
public IRectangle frame () {
|
public IRectangle frame () {
|
||||||
return this;
|
return this;
|
||||||
@@ -166,7 +178,79 @@ public abstract class AbstractRectangle extends RectangularShape implements IRec
|
|||||||
|
|
||||||
@Override // from Object
|
@Override // from Object
|
||||||
public String toString () {
|
public String toString () {
|
||||||
return Geometry.dimenToString(getWidth(), getHeight()) +
|
return Dimensions.dimenToString(getWidth(), getHeight()) +
|
||||||
Geometry.pointToString(getX(), getY());
|
Points.pointToString(getX(), getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** An iterator over an {@link IRectangle}. */
|
||||||
|
protected static class Iterator implements PathIterator
|
||||||
|
{
|
||||||
|
private float x, y, width, height;
|
||||||
|
private AffineTransform t;
|
||||||
|
|
||||||
|
/** The current segmenet index. */
|
||||||
|
private int index;
|
||||||
|
|
||||||
|
Iterator (IRectangle r, AffineTransform at) {
|
||||||
|
this.x = r.getX();
|
||||||
|
this.y = r.getY();
|
||||||
|
this.width = r.getWidth();
|
||||||
|
this.height = r.getHeight();
|
||||||
|
this.t = at;
|
||||||
|
if (width < 0f || height < 0f) {
|
||||||
|
index = 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int getWindingRule () {
|
||||||
|
return WIND_NON_ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public boolean isDone () {
|
||||||
|
return index > 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void next () {
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int currentSegment (float[] coords) {
|
||||||
|
if (isDone()) {
|
||||||
|
throw new NoSuchElementException("Iterator out of bounds");
|
||||||
|
}
|
||||||
|
if (index == 5) {
|
||||||
|
return SEG_CLOSE;
|
||||||
|
}
|
||||||
|
int type;
|
||||||
|
if (index == 0) {
|
||||||
|
type = SEG_MOVETO;
|
||||||
|
coords[0] = x;
|
||||||
|
coords[1] = y;
|
||||||
|
} else {
|
||||||
|
type = SEG_LINETO;
|
||||||
|
switch (index) {
|
||||||
|
case 1:
|
||||||
|
coords[0] = x + width;
|
||||||
|
coords[1] = y;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
coords[0] = x + width;
|
||||||
|
coords[1] = y + height;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
coords[0] = x;
|
||||||
|
coords[1] = y + height;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
coords[0] = x;
|
||||||
|
coords[1] = y;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (t != null) {
|
||||||
|
t.transform(coords, 0, coords, 0, 1);
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,483 @@
|
|||||||
|
//
|
||||||
|
// Pythagoras - a collection of geometry classes
|
||||||
|
// http://github.com/samskivert/pythagoras
|
||||||
|
|
||||||
|
package pythagoras.f;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a 2D affine transform, which performs a linear mapping that preserves the
|
||||||
|
* straightness and parallelness of lines.
|
||||||
|
*
|
||||||
|
* @see http://download.oracle.com/javase/6/docs/api/java/awt/geom/AffineTransform.html
|
||||||
|
*/
|
||||||
|
public class AffineTransform implements Cloneable, Serializable
|
||||||
|
{
|
||||||
|
public static final int TYPE_IDENTITY = 0;
|
||||||
|
public static final int TYPE_TRANSLATION = 1;
|
||||||
|
public static final int TYPE_UNIFORM_SCALE = 2;
|
||||||
|
public static final int TYPE_GENERAL_SCALE = 4;
|
||||||
|
public static final int TYPE_QUADRANT_ROTATION = 8;
|
||||||
|
public static final int TYPE_GENERAL_ROTATION = 16;
|
||||||
|
public static final int TYPE_GENERAL_TRANSFORM = 32;
|
||||||
|
public static final int TYPE_FLIP = 64;
|
||||||
|
public static final int TYPE_MASK_SCALE = TYPE_UNIFORM_SCALE | TYPE_GENERAL_SCALE;
|
||||||
|
public static final int TYPE_MASK_ROTATION = TYPE_QUADRANT_ROTATION | TYPE_GENERAL_ROTATION;
|
||||||
|
|
||||||
|
public AffineTransform () {
|
||||||
|
this.type = TYPE_IDENTITY;
|
||||||
|
this.m00 = this.m11 = 1f;
|
||||||
|
this.m10 = this.m01 = this.m02 = this.m12 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AffineTransform (AffineTransform t) {
|
||||||
|
this.type = t.type;
|
||||||
|
this.m00 = t.m00;
|
||||||
|
this.m10 = t.m10;
|
||||||
|
this.m01 = t.m01;
|
||||||
|
this.m11 = t.m11;
|
||||||
|
this.m02 = t.m02;
|
||||||
|
this.m12 = t.m12;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AffineTransform (float m00, float m10, float m01, float m11, float m02, float m12) {
|
||||||
|
this.type = TYPE_UNKNOWN;
|
||||||
|
this.m00 = m00;
|
||||||
|
this.m10 = m10;
|
||||||
|
this.m01 = m01;
|
||||||
|
this.m11 = m11;
|
||||||
|
this.m02 = m02;
|
||||||
|
this.m12 = m12;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AffineTransform (float[] matrix) {
|
||||||
|
this.type = TYPE_UNKNOWN;
|
||||||
|
m00 = matrix[0];
|
||||||
|
m10 = matrix[1];
|
||||||
|
m01 = matrix[2];
|
||||||
|
m11 = matrix[3];
|
||||||
|
if (matrix.length > 4) {
|
||||||
|
m02 = matrix[4];
|
||||||
|
m12 = matrix[5];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Method returns type of affine transformation.
|
||||||
|
*
|
||||||
|
* Transform matrix is m00 m01 m02 m10 m11 m12
|
||||||
|
*
|
||||||
|
* According analytic geometry new basis vectors are (m00, m01) and (m10,
|
||||||
|
* m11), translation vector is (m02, m12). Original basis vectors are (1, 0)
|
||||||
|
* and (0, 1). Type transformations classification: TYPE_IDENTITY - new
|
||||||
|
* basis equals original one and zero translation TYPE_TRANSLATION -
|
||||||
|
* translation vector isn't zero TYPE_UNIFORM_SCALE - vectors length of new
|
||||||
|
* basis equals TYPE_GENERAL_SCALE - vectors length of new basis doesn't
|
||||||
|
* equal TYPE_FLIP - new basis vector orientation differ from original one
|
||||||
|
* TYPE_QUADRANT_ROTATION - new basis is rotated by 90, 180, 270, or 360
|
||||||
|
* degrees TYPE_GENERAL_ROTATION - new basis is rotated by arbitrary angle
|
||||||
|
* TYPE_GENERAL_TRANSFORM - transformation can't be inversed
|
||||||
|
*/
|
||||||
|
public int getType () {
|
||||||
|
if (type != TYPE_UNKNOWN) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
int type = 0;
|
||||||
|
|
||||||
|
if (m00 * m01 + m10 * m11 != 0) {
|
||||||
|
type |= TYPE_GENERAL_TRANSFORM;
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m02 != 0 || m12 != 0) {
|
||||||
|
type |= TYPE_TRANSLATION;
|
||||||
|
} else if (m00 == 1f && m11 == 1f && m01 == 0 && m10 == 0) {
|
||||||
|
type = TYPE_IDENTITY;
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m00 * m11 - m01 * m10 < 0) {
|
||||||
|
type |= TYPE_FLIP;
|
||||||
|
}
|
||||||
|
|
||||||
|
float dx = m00 * m00 + m10 * m10;
|
||||||
|
float dy = m01 * m01 + m11 * m11;
|
||||||
|
if (dx != dy) {
|
||||||
|
type |= TYPE_GENERAL_SCALE;
|
||||||
|
} else if (dx != 1f) {
|
||||||
|
type |= TYPE_UNIFORM_SCALE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((m00 == 0 && m11 == 0) || (m10 == 0 && m01 == 0 && (m00 < 0 || m11 < 0))) {
|
||||||
|
type |= TYPE_QUADRANT_ROTATION;
|
||||||
|
} else if (m01 != 0 || m10 != 0) {
|
||||||
|
type |= TYPE_GENERAL_ROTATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getScaleX () {
|
||||||
|
return m00;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getScaleY () {
|
||||||
|
return m11;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getShearX () {
|
||||||
|
return m01;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getShearY () {
|
||||||
|
return m10;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getTranslateX () {
|
||||||
|
return m02;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getTranslateY () {
|
||||||
|
return m12;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isIdentity () {
|
||||||
|
return getType() == TYPE_IDENTITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getMatrix (float[] matrix) {
|
||||||
|
matrix[0] = m00;
|
||||||
|
matrix[1] = m10;
|
||||||
|
matrix[2] = m01;
|
||||||
|
matrix[3] = m11;
|
||||||
|
if (matrix.length > 4) {
|
||||||
|
matrix[4] = m02;
|
||||||
|
matrix[5] = m12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getDeterminant () {
|
||||||
|
return m00 * m11 - m01 * m10;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTransform (float m00, float m10, float m01, float m11, float m02, float m12) {
|
||||||
|
this.type = TYPE_UNKNOWN;
|
||||||
|
this.m00 = m00;
|
||||||
|
this.m10 = m10;
|
||||||
|
this.m01 = m01;
|
||||||
|
this.m11 = m11;
|
||||||
|
this.m02 = m02;
|
||||||
|
this.m12 = m12;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTransform (AffineTransform t) {
|
||||||
|
type = t.type;
|
||||||
|
setTransform(t.m00, t.m10, t.m01, t.m11, t.m02, t.m12);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToIdentity () {
|
||||||
|
type = TYPE_IDENTITY;
|
||||||
|
m00 = m11 = 1f;
|
||||||
|
m10 = m01 = m02 = m12 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToTranslation (float mx, float my) {
|
||||||
|
m00 = m11 = 1f;
|
||||||
|
m01 = m10 = 0;
|
||||||
|
m02 = mx;
|
||||||
|
m12 = my;
|
||||||
|
if (mx == 0 && my == 0) {
|
||||||
|
type = TYPE_IDENTITY;
|
||||||
|
} else {
|
||||||
|
type = TYPE_TRANSLATION;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToScale (float scx, float scy) {
|
||||||
|
m00 = scx;
|
||||||
|
m11 = scy;
|
||||||
|
m10 = m01 = m02 = m12 = 0;
|
||||||
|
if (scx != 1f || scy != 1f) {
|
||||||
|
type = TYPE_UNKNOWN;
|
||||||
|
} else {
|
||||||
|
type = TYPE_IDENTITY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToShear (float shx, float shy) {
|
||||||
|
m00 = m11 = 1f;
|
||||||
|
m02 = m12 = 0;
|
||||||
|
m01 = shx;
|
||||||
|
m10 = shy;
|
||||||
|
if (shx != 0 || shy != 0) {
|
||||||
|
type = TYPE_UNKNOWN;
|
||||||
|
} else {
|
||||||
|
type = TYPE_IDENTITY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToRotation (float angle) {
|
||||||
|
float sin = (float)Math.sin(angle);
|
||||||
|
float cos = (float)Math.cos(angle);
|
||||||
|
if (Math.abs(cos) < ZERO) {
|
||||||
|
cos = 0;
|
||||||
|
sin = sin > 0 ? 1f : -1f;
|
||||||
|
} else if (Math.abs(sin) < ZERO) {
|
||||||
|
sin = 0;
|
||||||
|
cos = cos > 0 ? 1f : -1f;
|
||||||
|
}
|
||||||
|
m00 = m11 = cos;
|
||||||
|
m01 = -sin;
|
||||||
|
m10 = sin;
|
||||||
|
m02 = m12 = 0;
|
||||||
|
type = TYPE_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToRotation (float angle, float px, float py) {
|
||||||
|
setToRotation(angle);
|
||||||
|
m02 = px * (1f - m00) + py * m10;
|
||||||
|
m12 = py * (1f - m00) - px * m10;
|
||||||
|
type = TYPE_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AffineTransform getTranslateInstance (float mx, float my) {
|
||||||
|
AffineTransform t = new AffineTransform();
|
||||||
|
t.setToTranslation(mx, my);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AffineTransform getScaleInstance (float scx, float scY) {
|
||||||
|
AffineTransform t = new AffineTransform();
|
||||||
|
t.setToScale(scx, scY);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AffineTransform getShearInstance (float shx, float shy) {
|
||||||
|
AffineTransform m = new AffineTransform();
|
||||||
|
m.setToShear(shx, shy);
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AffineTransform getRotateInstance (float angle) {
|
||||||
|
AffineTransform t = new AffineTransform();
|
||||||
|
t.setToRotation(angle);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AffineTransform getRotateInstance (float angle, float x, float y) {
|
||||||
|
AffineTransform t = new AffineTransform();
|
||||||
|
t.setToRotation(angle, x, y);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void translate (float mx, float my) {
|
||||||
|
concatenate(AffineTransform.getTranslateInstance(mx, my));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void scale (float scx, float scy) {
|
||||||
|
concatenate(AffineTransform.getScaleInstance(scx, scy));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void shear (float shx, float shy) {
|
||||||
|
concatenate(AffineTransform.getShearInstance(shx, shy));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void rotate (float angle) {
|
||||||
|
concatenate(AffineTransform.getRotateInstance(angle));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void rotate (float angle, float px, float py) {
|
||||||
|
concatenate(AffineTransform.getRotateInstance(angle, px, py));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multiply matrix of two AffineTransform objects
|
||||||
|
*
|
||||||
|
* @param t1
|
||||||
|
* - the AffineTransform object is a multiplicand
|
||||||
|
* @param t2
|
||||||
|
* - the AffineTransform object is a multiplier
|
||||||
|
* @return an AffineTransform object that is a result of t1 multiplied by
|
||||||
|
* matrix t2.
|
||||||
|
*/
|
||||||
|
AffineTransform multiply (AffineTransform t1, AffineTransform t2) {
|
||||||
|
return new AffineTransform(t1.m00 * t2.m00 + t1.m10 * t2.m01, // m00
|
||||||
|
t1.m00 * t2.m10 + t1.m10 * t2.m11, // m01
|
||||||
|
t1.m01 * t2.m00 + t1.m11 * t2.m01, // m10
|
||||||
|
t1.m01 * t2.m10 + t1.m11 * t2.m11, // m11
|
||||||
|
t1.m02 * t2.m00 + t1.m12 * t2.m01 + t2.m02, // m02
|
||||||
|
t1.m02 * t2.m10 + t1.m12 * t2.m11 + t2.m12);// m12
|
||||||
|
}
|
||||||
|
|
||||||
|
public void concatenate (AffineTransform t) {
|
||||||
|
setTransform(multiply(t, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void preConcatenate (AffineTransform t) {
|
||||||
|
setTransform(multiply(this, t));
|
||||||
|
}
|
||||||
|
|
||||||
|
public AffineTransform createInverse () throws NoninvertibleTransformException {
|
||||||
|
float det = getDeterminant();
|
||||||
|
if (Math.abs(det) < ZERO) {
|
||||||
|
throw new NoninvertibleTransformException("Determinant is zero");
|
||||||
|
}
|
||||||
|
return new AffineTransform(m11 / det, // m00
|
||||||
|
-m10 / det, // m10
|
||||||
|
-m01 / det, // m01
|
||||||
|
m00 / det, // m11
|
||||||
|
(m01 * m12 - m11 * m02) / det, // m02
|
||||||
|
(m10 * m02 - m00 * m12) / det // m12
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Point transform (IPoint src, Point dst) {
|
||||||
|
if (dst == null) {
|
||||||
|
dst = new Point();
|
||||||
|
}
|
||||||
|
|
||||||
|
float x = src.getX(), y = src.getY();
|
||||||
|
dst.setLocation(x * m00 + y * m01 + m02, x * m10 + y * m11 + m12);
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void transform (IPoint[] src, int srcOff, Point[] dst, int dstOff, int length) {
|
||||||
|
while (--length >= 0) {
|
||||||
|
IPoint srcPoint = src[srcOff++];
|
||||||
|
float x = srcPoint.getX();
|
||||||
|
float y = srcPoint.getY();
|
||||||
|
Point dstPoint = dst[dstOff];
|
||||||
|
if (dstPoint == null) {
|
||||||
|
dstPoint = new Point();
|
||||||
|
}
|
||||||
|
dstPoint.setLocation(x * m00 + y * m01 + m02, x * m10 + y * m11 + m12);
|
||||||
|
dst[dstOff++] = dstPoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void transform (float[] src, int srcOff, float[] dst, int dstOff, int length) {
|
||||||
|
int step = 2;
|
||||||
|
if (src == dst && srcOff < dstOff && dstOff < srcOff + length * 2) {
|
||||||
|
srcOff = srcOff + length * 2 - 2;
|
||||||
|
dstOff = dstOff + length * 2 - 2;
|
||||||
|
step = -2;
|
||||||
|
}
|
||||||
|
while (--length >= 0) {
|
||||||
|
float x = src[srcOff + 0];
|
||||||
|
float y = src[srcOff + 1];
|
||||||
|
dst[dstOff + 0] = (x * m00 + y * m01 + m02);
|
||||||
|
dst[dstOff + 1] = (x * m10 + y * m11 + m12);
|
||||||
|
srcOff += step;
|
||||||
|
dstOff += step;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Point deltaTransform (IPoint src, Point dst) {
|
||||||
|
if (dst == null) {
|
||||||
|
dst = new Point();
|
||||||
|
}
|
||||||
|
float x = src.getX(), y = src.getY();
|
||||||
|
dst.setLocation(x * m00 + y * m01, x * m10 + y * m11);
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deltaTransform (float[] src, int srcOff, float[] dst, int dstOff, int length) {
|
||||||
|
while (--length >= 0) {
|
||||||
|
float x = src[srcOff++], y = src[srcOff++];
|
||||||
|
dst[dstOff++] = x * m00 + y * m01;
|
||||||
|
dst[dstOff++] = x * m10 + y * m11;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Point inverseTransform (IPoint src, Point dst) throws NoninvertibleTransformException {
|
||||||
|
float det = getDeterminant();
|
||||||
|
if (Math.abs(det) < ZERO) {
|
||||||
|
throw new NoninvertibleTransformException("Determinant is zero");
|
||||||
|
}
|
||||||
|
if (dst == null) {
|
||||||
|
dst = new Point();
|
||||||
|
}
|
||||||
|
float x = src.getX() - m02, y = src.getY() - m12;
|
||||||
|
dst.setLocation((x * m11 - y * m01) / det, (y * m00 - x * m10) / det);
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void inverseTransform (float[] src, int srcOff, float[] dst, int dstOff, int length)
|
||||||
|
throws NoninvertibleTransformException {
|
||||||
|
float det = getDeterminant();
|
||||||
|
if (Math.abs(det) < ZERO) {
|
||||||
|
throw new NoninvertibleTransformException("Determinant is zero");
|
||||||
|
}
|
||||||
|
while (--length >= 0) {
|
||||||
|
float x = src[srcOff++] - m02, y = src[srcOff++] - m12;
|
||||||
|
dst[dstOff++] = (x * m11 - y * m01) / det;
|
||||||
|
dst[dstOff++] = (y * m00 - x * m10) / det;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IShape createTransformedShape (IShape src) {
|
||||||
|
if (src == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (src instanceof Path) {
|
||||||
|
return ((Path)src).createTransformedShape(this);
|
||||||
|
}
|
||||||
|
PathIterator path = src.getPathIterator(this);
|
||||||
|
Path dst = new Path(path.getWindingRule());
|
||||||
|
dst.append(path, false);
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString () {
|
||||||
|
return getClass().getName() +
|
||||||
|
"[[" + m00 + ", " + m01 + ", " + m02 + "], [" + m10 + ", " + m11 + ", " + m12 + "]]";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AffineTransform clone () {
|
||||||
|
try {
|
||||||
|
return (AffineTransform)super.clone();
|
||||||
|
} catch (CloneNotSupportedException e) {
|
||||||
|
throw new InternalError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode () {
|
||||||
|
return Float.floatToIntBits(m00) ^ Float.floatToIntBits(m01) ^ Float.floatToIntBits(m02) ^
|
||||||
|
Float.floatToIntBits(m10) ^ Float.floatToIntBits(m11) ^ Float.floatToIntBits(m12);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals (Object obj) {
|
||||||
|
if (obj == this) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj instanceof AffineTransform) {
|
||||||
|
AffineTransform t = (AffineTransform)obj;
|
||||||
|
return m00 == t.m00 && m01 == t.m01 && m02 == t.m02 &&
|
||||||
|
m10 == t.m10 && m11 == t.m11 && m12 == t.m12;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the values of transformation matrix
|
||||||
|
private float m00;
|
||||||
|
private float m10;
|
||||||
|
private float m01;
|
||||||
|
private float m11;
|
||||||
|
private float m02;
|
||||||
|
private float m12;
|
||||||
|
|
||||||
|
/** The transformation {@code type}. */
|
||||||
|
private transient int type;
|
||||||
|
|
||||||
|
/** An initial type value. */
|
||||||
|
private static final int TYPE_UNKNOWN = -1;
|
||||||
|
|
||||||
|
/** The min value equivalent to zero. An absolute value < ZERO is considered to be zero. */
|
||||||
|
private static final float ZERO = 1E-10f;
|
||||||
|
}
|
||||||
@@ -0,0 +1,864 @@
|
|||||||
|
//
|
||||||
|
// Pythagoras - a collection of geometry classes
|
||||||
|
// http://github.com/samskivert/pythagoras
|
||||||
|
|
||||||
|
package pythagoras.f;
|
||||||
|
|
||||||
|
class Crossing
|
||||||
|
{
|
||||||
|
/** Return value indicating that a crossing was found. */
|
||||||
|
public static final int CROSSING = 255;
|
||||||
|
|
||||||
|
/** Return value indicating the crossing result is unknown. */
|
||||||
|
public static final int UNKNOWN = 254;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Solves quadratic equation
|
||||||
|
*
|
||||||
|
* @param eqn the coefficients of the equation
|
||||||
|
* @param res the roots of the equation
|
||||||
|
* @return a number of roots
|
||||||
|
*/
|
||||||
|
public static int solveQuad (float eqn[], float res[]) {
|
||||||
|
float a = eqn[2];
|
||||||
|
float b = eqn[1];
|
||||||
|
float c = eqn[0];
|
||||||
|
int rc = 0;
|
||||||
|
if (a == 0f) {
|
||||||
|
if (b == 0f) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
res[rc++] = -c / b;
|
||||||
|
} else {
|
||||||
|
float d = b * b - 4f * a * c;
|
||||||
|
// d < 0f
|
||||||
|
if (d < 0f) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
d = (float)Math.sqrt(d);
|
||||||
|
res[rc++] = (-b + d) / (a * 2f);
|
||||||
|
// d != 0f
|
||||||
|
if (d != 0f) {
|
||||||
|
res[rc++] = (-b - d) / (a * 2f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fixRoots(res, rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Solves cubic equation
|
||||||
|
*
|
||||||
|
* @param eqn the coefficients of the equation
|
||||||
|
* @param res the roots of the equation
|
||||||
|
* @return a number of roots
|
||||||
|
*/
|
||||||
|
public static int solveCubic (float eqn[], float res[]) {
|
||||||
|
float d = eqn[3];
|
||||||
|
if (d == 0) {
|
||||||
|
return solveQuad(eqn, res);
|
||||||
|
}
|
||||||
|
float a = eqn[2] / d;
|
||||||
|
float b = eqn[1] / d;
|
||||||
|
float c = eqn[0] / d;
|
||||||
|
int rc = 0;
|
||||||
|
|
||||||
|
float Q = (a * a - 3f * b) / 9f;
|
||||||
|
float R = (2f * a * a * a - 9f * a * b + 27f * c) / 54f;
|
||||||
|
float Q3 = Q * Q * Q;
|
||||||
|
float R2 = R * R;
|
||||||
|
float n = -a / 3f;
|
||||||
|
|
||||||
|
if (R2 < Q3) {
|
||||||
|
float t = (float)Math.acos(R / Math.sqrt(Q3)) / 3f;
|
||||||
|
float p = 2f * (float)Math.PI / 3f;
|
||||||
|
float m = -2f * (float)Math.sqrt(Q);
|
||||||
|
res[rc++] = m * (float)Math.cos(t) + n;
|
||||||
|
res[rc++] = m * (float)Math.cos(t + p) + n;
|
||||||
|
res[rc++] = m * (float)Math.cos(t - p) + n;
|
||||||
|
} else {
|
||||||
|
// Debug.println("R2 >= Q3 (" + R2 + "/" + Q3 + ")");
|
||||||
|
float A = (float)Math.pow(Math.abs(R) + Math.sqrt(R2 - Q3), 1f / 3f);
|
||||||
|
if (R > 0f) {
|
||||||
|
A = -A;
|
||||||
|
}
|
||||||
|
// if (A == 0f) {
|
||||||
|
if (-ROOT_DELTA < A && A < ROOT_DELTA) {
|
||||||
|
res[rc++] = n;
|
||||||
|
} else {
|
||||||
|
float B = Q / A;
|
||||||
|
res[rc++] = A + B + n;
|
||||||
|
// if (R2 == Q3) {
|
||||||
|
float delta = R2 - Q3;
|
||||||
|
if (-ROOT_DELTA < delta && delta < ROOT_DELTA) {
|
||||||
|
res[rc++] = -(A + B) / 2f + n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return fixRoots(res, rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Excludes double roots. Roots are double if they lies enough close with each other.
|
||||||
|
*
|
||||||
|
* @param res the roots
|
||||||
|
* @param rc the roots count
|
||||||
|
* @return new roots count
|
||||||
|
*/
|
||||||
|
protected static int fixRoots (float res[], int rc) {
|
||||||
|
int tc = 0;
|
||||||
|
for (int i = 0; i < rc; i++) {
|
||||||
|
out: {
|
||||||
|
for (int j = i + 1; j < rc; j++) {
|
||||||
|
if (isZero(res[i] - res[j])) {
|
||||||
|
break out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res[tc++] = res[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* QuadCurve class provides basic functionality to find curve crossing and calculating bounds
|
||||||
|
*/
|
||||||
|
public static class QuadCurve
|
||||||
|
{
|
||||||
|
float ax, ay, bx, by;
|
||||||
|
float Ax, Ay, Bx, By;
|
||||||
|
|
||||||
|
public QuadCurve (float x1, float y1, float cx, float cy, float x2, float y2) {
|
||||||
|
ax = x2 - x1;
|
||||||
|
ay = y2 - y1;
|
||||||
|
bx = cx - x1;
|
||||||
|
by = cy - y1;
|
||||||
|
|
||||||
|
Bx = bx + bx; // Bx = 2f * bx
|
||||||
|
Ax = ax - Bx; // Ax = ax - 2f * bx
|
||||||
|
|
||||||
|
By = by + by; // By = 2f * by
|
||||||
|
Ay = ay - By; // Ay = ay - 2f * by
|
||||||
|
}
|
||||||
|
|
||||||
|
public int cross (float res[], int rc, float py1, float py2) {
|
||||||
|
int cross = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < rc; i++) {
|
||||||
|
float t = res[i];
|
||||||
|
|
||||||
|
// CURVE-OUTSIDE
|
||||||
|
if (t < -DELTA || t > 1 + DELTA) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// CURVE-START
|
||||||
|
if (t < DELTA) {
|
||||||
|
if (py1 < 0f && (bx != 0f ? bx : ax - bx) < 0f) {
|
||||||
|
cross--;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// CURVE-END
|
||||||
|
if (t > 1 - DELTA) {
|
||||||
|
if (py1 < ay && (ax != bx ? ax - bx : bx) > 0f) {
|
||||||
|
cross++;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// CURVE-INSIDE
|
||||||
|
float ry = t * (t * Ay + By);
|
||||||
|
// ry = t * t * Ay + t * By
|
||||||
|
if (ry > py2) {
|
||||||
|
float rxt = t * Ax + bx;
|
||||||
|
// rxt = 2f * t * Ax + Bx = 2f * t * Ax + 2f * bx
|
||||||
|
if (rxt > -DELTA && rxt < DELTA) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cross += rxt > 0f ? 1 : -1;
|
||||||
|
}
|
||||||
|
} // for
|
||||||
|
|
||||||
|
return cross;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int solvePoint (float res[], float px) {
|
||||||
|
float eqn[] = { -px, Bx, Ax };
|
||||||
|
return solveQuad(eqn, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int solveExtreme (float res[]) {
|
||||||
|
int rc = 0;
|
||||||
|
if (Ax != 0f) {
|
||||||
|
res[rc++] = -Bx / (Ax + Ax);
|
||||||
|
}
|
||||||
|
if (Ay != 0f) {
|
||||||
|
res[rc++] = -By / (Ay + Ay);
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int addBound (float bound[], int bc, float res[], int rc, float minX, float maxX,
|
||||||
|
boolean changeId, int id) {
|
||||||
|
for (int i = 0; i < rc; i++) {
|
||||||
|
float t = res[i];
|
||||||
|
if (t > -DELTA && t < 1 + DELTA) {
|
||||||
|
float rx = t * (t * Ax + Bx);
|
||||||
|
if (minX <= rx && rx <= maxX) {
|
||||||
|
bound[bc++] = t;
|
||||||
|
bound[bc++] = rx;
|
||||||
|
bound[bc++] = t * (t * Ay + By);
|
||||||
|
bound[bc++] = id;
|
||||||
|
if (changeId) {
|
||||||
|
id++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CubicCurve class provides basic functionality to find curve crossing and calculating bounds
|
||||||
|
*/
|
||||||
|
public static class CubicCurve
|
||||||
|
{
|
||||||
|
float ax, ay, bx, by, cx, cy;
|
||||||
|
float Ax, Ay, Bx, By, Cx, Cy;
|
||||||
|
float Ax3, Bx2;
|
||||||
|
|
||||||
|
public CubicCurve (float x1, float y1, float cx1, float cy1, float cx2, float cy2,
|
||||||
|
float x2, float y2) {
|
||||||
|
ax = x2 - x1;
|
||||||
|
ay = y2 - y1;
|
||||||
|
bx = cx1 - x1;
|
||||||
|
by = cy1 - y1;
|
||||||
|
cx = cx2 - x1;
|
||||||
|
cy = cy2 - y1;
|
||||||
|
|
||||||
|
Cx = bx + bx + bx; // Cx = 3f * bx
|
||||||
|
Bx = cx + cx + cx - Cx - Cx; // Bx = 3f * cx - 6f * bx
|
||||||
|
Ax = ax - Bx - Cx; // Ax = ax - 3f * cx + 3f * bx
|
||||||
|
|
||||||
|
Cy = by + by + by; // Cy = 3f * by
|
||||||
|
By = cy + cy + cy - Cy - Cy; // By = 3f * cy - 6f * by
|
||||||
|
Ay = ay - By - Cy; // Ay = ay - 3f * cy + 3f * by
|
||||||
|
|
||||||
|
Ax3 = Ax + Ax + Ax;
|
||||||
|
Bx2 = Bx + Bx;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int cross (float res[], int rc, float py1, float py2) {
|
||||||
|
int cross = 0;
|
||||||
|
for (int i = 0; i < rc; i++) {
|
||||||
|
float t = res[i];
|
||||||
|
|
||||||
|
// CURVE-OUTSIDE
|
||||||
|
if (t < -DELTA || t > 1 + DELTA) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// CURVE-START
|
||||||
|
if (t < DELTA) {
|
||||||
|
if (py1 < 0f && (bx != 0f ? bx : (cx != bx ? cx - bx : ax - cx)) < 0f) {
|
||||||
|
cross--;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// CURVE-END
|
||||||
|
if (t > 1 - DELTA) {
|
||||||
|
if (py1 < ay && (ax != cx ? ax - cx : (cx != bx ? cx - bx : bx)) > 0f) {
|
||||||
|
cross++;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// CURVE-INSIDE
|
||||||
|
float ry = t * (t * (t * Ay + By) + Cy);
|
||||||
|
// ry = t * t * t * Ay + t * t * By + t * Cy
|
||||||
|
if (ry > py2) {
|
||||||
|
float rxt = t * (t * Ax3 + Bx2) + Cx;
|
||||||
|
// rxt = 3f * t * t * Ax + 2f * t * Bx + Cx
|
||||||
|
if (rxt > -DELTA && rxt < DELTA) {
|
||||||
|
rxt = t * (Ax3 + Ax3) + Bx2;
|
||||||
|
// rxt = 6f * t * Ax + 2f * Bx
|
||||||
|
if (rxt < -DELTA || rxt > DELTA) {
|
||||||
|
// Inflection point
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
rxt = ax;
|
||||||
|
}
|
||||||
|
cross += rxt > 0f ? 1 : -1;
|
||||||
|
}
|
||||||
|
} // for
|
||||||
|
|
||||||
|
return cross;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int solvePoint (float res[], float px) {
|
||||||
|
float eqn[] = { -px, Cx, Bx, Ax };
|
||||||
|
return solveCubic(eqn, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int solveExtremeX (float res[]) {
|
||||||
|
float eqn[] = { Cx, Bx2, Ax3 };
|
||||||
|
return solveQuad(eqn, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int solveExtremeY (float res[]) {
|
||||||
|
float eqn[] = { Cy, By + By, Ay + Ay + Ay };
|
||||||
|
return solveQuad(eqn, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int addBound (float bound[], int bc, float res[], int rc, float minX, float maxX,
|
||||||
|
boolean changeId, int id) {
|
||||||
|
for (int i = 0; i < rc; i++) {
|
||||||
|
float t = res[i];
|
||||||
|
if (t > -DELTA && t < 1 + DELTA) {
|
||||||
|
float rx = t * (t * (t * Ax + Bx) + Cx);
|
||||||
|
if (minX <= rx && rx <= maxX) {
|
||||||
|
bound[bc++] = t;
|
||||||
|
bound[bc++] = rx;
|
||||||
|
bound[bc++] = t * (t * (t * Ay + By) + Cy);
|
||||||
|
bound[bc++] = id;
|
||||||
|
if (changeId) {
|
||||||
|
id++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns how many times ray from point (x,y) cross line.
|
||||||
|
*/
|
||||||
|
public static int crossLine (float x1, float y1, float x2, float y2, float x, float y) {
|
||||||
|
// LEFT/RIGHT/UP/EMPTY
|
||||||
|
if ((x < x1 && x < x2) || (x > x1 && x > x2) || (y > y1 && y > y2) || (x1 == x2)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DOWN
|
||||||
|
if (y < y1 && y < y2) {
|
||||||
|
} else {
|
||||||
|
// INSIDE
|
||||||
|
if ((y2 - y1) * (x - x1) / (x2 - x1) <= y - y1) {
|
||||||
|
// INSIDE-UP
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// START
|
||||||
|
if (x == x1) {
|
||||||
|
return x1 < x2 ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// END
|
||||||
|
if (x == x2) {
|
||||||
|
return x1 < x2 ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// INSIDE-DOWN
|
||||||
|
return x1 < x2 ? 1 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns how many times ray from point (x,y) cross quard curve
|
||||||
|
*/
|
||||||
|
public static int crossQuad (float x1, float y1, float cx, float cy, float x2, float y2,
|
||||||
|
float x, float y) {
|
||||||
|
// LEFT/RIGHT/UP/EMPTY
|
||||||
|
if ((x < x1 && x < cx && x < x2) || (x > x1 && x > cx && x > x2)
|
||||||
|
|| (y > y1 && y > cy && y > y2) || (x1 == cx && cx == x2)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DOWN
|
||||||
|
if (y < y1 && y < cy && y < y2 && x != x1 && x != x2) {
|
||||||
|
if (x1 < x2) {
|
||||||
|
return x1 < x && x < x2 ? 1 : 0;
|
||||||
|
}
|
||||||
|
return x2 < x && x < x1 ? -1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// INSIDE
|
||||||
|
QuadCurve c = new QuadCurve(x1, y1, cx, cy, x2, y2);
|
||||||
|
float px = x - x1, py = y - y1;
|
||||||
|
float[] res = new float[3];
|
||||||
|
int rc = c.solvePoint(res, px);
|
||||||
|
return c.cross(res, rc, py, py);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns how many times ray from point (x,y) cross cubic curve
|
||||||
|
*/
|
||||||
|
public static int crossCubic (float x1, float y1, float cx1, float cy1, float cx2,
|
||||||
|
float cy2, float x2, float y2, float x, float y) {
|
||||||
|
// LEFT/RIGHT/UP/EMPTY
|
||||||
|
if ((x < x1 && x < cx1 && x < cx2 && x < x2) || (x > x1 && x > cx1 && x > cx2 && x > x2)
|
||||||
|
|| (y > y1 && y > cy1 && y > cy2 && y > y2)
|
||||||
|
|| (x1 == cx1 && cx1 == cx2 && cx2 == x2)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DOWN
|
||||||
|
if (y < y1 && y < cy1 && y < cy2 && y < y2 && x != x1 && x != x2) {
|
||||||
|
if (x1 < x2) {
|
||||||
|
return x1 < x && x < x2 ? 1 : 0;
|
||||||
|
}
|
||||||
|
return x2 < x && x < x1 ? -1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// INSIDE
|
||||||
|
CubicCurve c = new CubicCurve(x1, y1, cx1, cy1, cx2, cy2, x2, y2);
|
||||||
|
float px = x - x1, py = y - y1;
|
||||||
|
float[] res = new float[3];
|
||||||
|
int rc = c.solvePoint(res, px);
|
||||||
|
return c.cross(res, rc, py, py);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns how many times ray from point (x,y) cross path
|
||||||
|
*/
|
||||||
|
public static int crossPath (PathIterator p, float x, float y) {
|
||||||
|
int cross = 0;
|
||||||
|
float mx, my, cx, cy;
|
||||||
|
mx = my = cx = cy = 0f;
|
||||||
|
float[] coords = new float[6];
|
||||||
|
|
||||||
|
while (!p.isDone()) {
|
||||||
|
switch (p.currentSegment(coords)) {
|
||||||
|
case PathIterator.SEG_MOVETO:
|
||||||
|
if (cx != mx || cy != my) {
|
||||||
|
cross += crossLine(cx, cy, mx, my, x, y);
|
||||||
|
}
|
||||||
|
mx = cx = coords[0];
|
||||||
|
my = cy = coords[1];
|
||||||
|
break;
|
||||||
|
case PathIterator.SEG_LINETO:
|
||||||
|
cross += crossLine(cx, cy, cx = coords[0], cy = coords[1], x, y);
|
||||||
|
break;
|
||||||
|
case PathIterator.SEG_QUADTO:
|
||||||
|
cross += crossQuad(cx, cy, coords[0], coords[1], cx = coords[2], cy = coords[3], x,
|
||||||
|
y);
|
||||||
|
break;
|
||||||
|
case PathIterator.SEG_CUBICTO:
|
||||||
|
cross += crossCubic(cx, cy, coords[0], coords[1], coords[2], coords[3],
|
||||||
|
cx = coords[4], cy = coords[5], x, y);
|
||||||
|
break;
|
||||||
|
case PathIterator.SEG_CLOSE:
|
||||||
|
if (cy != my || cx != mx) {
|
||||||
|
cross += crossLine(cx, cy, cx = mx, cy = my, x, y);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// checks if the point (x,y) is the vertex of shape with PathIterator p
|
||||||
|
if (x == cx && y == cy) {
|
||||||
|
cross = 0;
|
||||||
|
cy = my;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
p.next();
|
||||||
|
}
|
||||||
|
if (cy != my) {
|
||||||
|
cross += crossLine(cx, cy, mx, my, x, y);
|
||||||
|
}
|
||||||
|
return cross;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns how many times a ray from point (x,y) crosses a shape.
|
||||||
|
*/
|
||||||
|
public static int crossShape (IShape s, float x, float y) {
|
||||||
|
if (!s.bounds().contains(x, y)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return crossPath(s.getPathIterator(null), x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if value is close enough to zero.
|
||||||
|
*/
|
||||||
|
public static boolean isZero (float val) {
|
||||||
|
return -DELTA < val && val < DELTA;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns how many times rectangle stripe cross line or the are intersect
|
||||||
|
*/
|
||||||
|
public static int intersectLine (float x1, float y1, float x2, float y2, float rx1,
|
||||||
|
float ry1, float rx2, float ry2) {
|
||||||
|
// LEFT/RIGHT/UP
|
||||||
|
if ((rx2 < x1 && rx2 < x2) || (rx1 > x1 && rx1 > x2) || (ry1 > y1 && ry1 > y2)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DOWN
|
||||||
|
if (ry2 < y1 && ry2 < y2) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// INSIDE
|
||||||
|
if (x1 == x2) {
|
||||||
|
return CROSSING;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build bound
|
||||||
|
float bx1, bx2;
|
||||||
|
if (x1 < x2) {
|
||||||
|
bx1 = x1 < rx1 ? rx1 : x1;
|
||||||
|
bx2 = x2 < rx2 ? x2 : rx2;
|
||||||
|
} else {
|
||||||
|
bx1 = x2 < rx1 ? rx1 : x2;
|
||||||
|
bx2 = x1 < rx2 ? x1 : rx2;
|
||||||
|
}
|
||||||
|
float k = (y2 - y1) / (x2 - x1);
|
||||||
|
float by1 = k * (bx1 - x1) + y1;
|
||||||
|
float by2 = k * (bx2 - x1) + y1;
|
||||||
|
|
||||||
|
// BOUND-UP
|
||||||
|
if (by1 < ry1 && by2 < ry1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// BOUND-DOWN
|
||||||
|
if (by1 > ry2 && by2 > ry2) {
|
||||||
|
} else {
|
||||||
|
return CROSSING;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EMPTY
|
||||||
|
if (x1 == x2) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CURVE-START
|
||||||
|
if (rx1 == x1) {
|
||||||
|
return x1 < x2 ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CURVE-END
|
||||||
|
if (rx1 == x2) {
|
||||||
|
return x1 < x2 ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x1 < x2) {
|
||||||
|
return x1 < rx1 && rx1 < x2 ? 1 : 0;
|
||||||
|
}
|
||||||
|
return x2 < rx1 && rx1 < x1 ? -1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns how many times rectangle stripe cross quad curve or the are
|
||||||
|
* intersect
|
||||||
|
*/
|
||||||
|
public static int intersectQuad (float x1, float y1, float cx, float cy, float x2,
|
||||||
|
float y2, float rx1, float ry1, float rx2, float ry2) {
|
||||||
|
// LEFT/RIGHT/UP ------------------------------------------------------
|
||||||
|
if ((rx2 < x1 && rx2 < cx && rx2 < x2) || (rx1 > x1 && rx1 > cx && rx1 > x2) ||
|
||||||
|
(ry1 > y1 && ry1 > cy && ry1 > y2)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DOWN ---------------------------------------------------------------
|
||||||
|
if (ry2 < y1 && ry2 < cy && ry2 < y2 && rx1 != x1 && rx1 != x2) {
|
||||||
|
if (x1 < x2) {
|
||||||
|
return x1 < rx1 && rx1 < x2 ? 1 : 0;
|
||||||
|
}
|
||||||
|
return x2 < rx1 && rx1 < x1 ? -1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// INSIDE -------------------------------------------------------------
|
||||||
|
QuadCurve c = new QuadCurve(x1, y1, cx, cy, x2, y2);
|
||||||
|
float px1 = rx1 - x1;
|
||||||
|
float py1 = ry1 - y1;
|
||||||
|
float px2 = rx2 - x1;
|
||||||
|
float py2 = ry2 - y1;
|
||||||
|
|
||||||
|
float res1[] = new float[3];
|
||||||
|
float res2[] = new float[3];
|
||||||
|
int rc1 = c.solvePoint(res1, px1);
|
||||||
|
int rc2 = c.solvePoint(res2, px2);
|
||||||
|
|
||||||
|
// INSIDE-LEFT/RIGHT
|
||||||
|
if (rc1 == 0 && rc2 == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build bound --------------------------------------------------------
|
||||||
|
float minX = px1 - DELTA;
|
||||||
|
float maxX = px2 + DELTA;
|
||||||
|
float bound[] = new float[28];
|
||||||
|
int bc = 0;
|
||||||
|
// Add roots
|
||||||
|
bc = c.addBound(bound, bc, res1, rc1, minX, maxX, false, 0);
|
||||||
|
bc = c.addBound(bound, bc, res2, rc2, minX, maxX, false, 1);
|
||||||
|
// Add extremal points
|
||||||
|
rc2 = c.solveExtreme(res2);
|
||||||
|
bc = c.addBound(bound, bc, res2, rc2, minX, maxX, true, 2);
|
||||||
|
// Add start and end
|
||||||
|
if (rx1 < x1 && x1 < rx2) {
|
||||||
|
bound[bc++] = 0f;
|
||||||
|
bound[bc++] = 0f;
|
||||||
|
bound[bc++] = 0f;
|
||||||
|
bound[bc++] = 4;
|
||||||
|
}
|
||||||
|
if (rx1 < x2 && x2 < rx2) {
|
||||||
|
bound[bc++] = 1f;
|
||||||
|
bound[bc++] = c.ax;
|
||||||
|
bound[bc++] = c.ay;
|
||||||
|
bound[bc++] = 5;
|
||||||
|
}
|
||||||
|
// End build bound ----------------------------------------------------
|
||||||
|
|
||||||
|
int cross = crossBound(bound, bc, py1, py2);
|
||||||
|
if (cross != UNKNOWN) {
|
||||||
|
return cross;
|
||||||
|
}
|
||||||
|
return c.cross(res1, rc1, py1, py2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns how many times rectangle stripe cross cubic curve or the are
|
||||||
|
* intersect
|
||||||
|
*/
|
||||||
|
public static int intersectCubic (float x1, float y1, float cx1, float cy1,
|
||||||
|
float cx2, float cy2, float x2, float y2,
|
||||||
|
float rx1, float ry1, float rx2, float ry2) {
|
||||||
|
// LEFT/RIGHT/UP
|
||||||
|
if ((rx2 < x1 && rx2 < cx1 && rx2 < cx2 && rx2 < x2)
|
||||||
|
|| (rx1 > x1 && rx1 > cx1 && rx1 > cx2 && rx1 > x2)
|
||||||
|
|| (ry1 > y1 && ry1 > cy1 && ry1 > cy2 && ry1 > y2)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DOWN
|
||||||
|
if (ry2 < y1 && ry2 < cy1 && ry2 < cy2 && ry2 < y2 && rx1 != x1 && rx1 != x2) {
|
||||||
|
if (x1 < x2) {
|
||||||
|
return x1 < rx1 && rx1 < x2 ? 1 : 0;
|
||||||
|
}
|
||||||
|
return x2 < rx1 && rx1 < x1 ? -1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// INSIDE
|
||||||
|
CubicCurve c = new CubicCurve(x1, y1, cx1, cy1, cx2, cy2, x2, y2);
|
||||||
|
float px1 = rx1 - x1;
|
||||||
|
float py1 = ry1 - y1;
|
||||||
|
float px2 = rx2 - x1;
|
||||||
|
float py2 = ry2 - y1;
|
||||||
|
|
||||||
|
float res1[] = new float[3];
|
||||||
|
float res2[] = new float[3];
|
||||||
|
int rc1 = c.solvePoint(res1, px1);
|
||||||
|
int rc2 = c.solvePoint(res2, px2);
|
||||||
|
|
||||||
|
// LEFT/RIGHT
|
||||||
|
if (rc1 == 0 && rc2 == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
float minX = px1 - DELTA;
|
||||||
|
float maxX = px2 + DELTA;
|
||||||
|
|
||||||
|
// Build bound --------------------------------------------------------
|
||||||
|
float bound[] = new float[40];
|
||||||
|
int bc = 0;
|
||||||
|
// Add roots
|
||||||
|
bc = c.addBound(bound, bc, res1, rc1, minX, maxX, false, 0);
|
||||||
|
bc = c.addBound(bound, bc, res2, rc2, minX, maxX, false, 1);
|
||||||
|
// Add extremal points
|
||||||
|
rc2 = c.solveExtremeX(res2);
|
||||||
|
bc = c.addBound(bound, bc, res2, rc2, minX, maxX, true, 2);
|
||||||
|
rc2 = c.solveExtremeY(res2);
|
||||||
|
bc = c.addBound(bound, bc, res2, rc2, minX, maxX, true, 4);
|
||||||
|
// Add start and end
|
||||||
|
if (rx1 < x1 && x1 < rx2) {
|
||||||
|
bound[bc++] = 0f;
|
||||||
|
bound[bc++] = 0f;
|
||||||
|
bound[bc++] = 0f;
|
||||||
|
bound[bc++] = 6;
|
||||||
|
}
|
||||||
|
if (rx1 < x2 && x2 < rx2) {
|
||||||
|
bound[bc++] = 1f;
|
||||||
|
bound[bc++] = c.ax;
|
||||||
|
bound[bc++] = c.ay;
|
||||||
|
bound[bc++] = 7;
|
||||||
|
}
|
||||||
|
// End build bound ----------------------------------------------------
|
||||||
|
|
||||||
|
int cross = crossBound(bound, bc, py1, py2);
|
||||||
|
if (cross != UNKNOWN) {
|
||||||
|
return cross;
|
||||||
|
}
|
||||||
|
return c.cross(res1, rc1, py1, py2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns how many times rectangle stripe cross path or the are intersect
|
||||||
|
*/
|
||||||
|
public static int intersectPath (PathIterator p, float x, float y, float w, float h) {
|
||||||
|
int cross = 0;
|
||||||
|
int count;
|
||||||
|
float mx, my, cx, cy;
|
||||||
|
mx = my = cx = cy = 0f;
|
||||||
|
float coords[] = new float[6];
|
||||||
|
|
||||||
|
float rx1 = x;
|
||||||
|
float ry1 = y;
|
||||||
|
float rx2 = x + w;
|
||||||
|
float ry2 = y + h;
|
||||||
|
|
||||||
|
while (!p.isDone()) {
|
||||||
|
count = 0;
|
||||||
|
switch (p.currentSegment(coords)) {
|
||||||
|
case PathIterator.SEG_MOVETO:
|
||||||
|
if (cx != mx || cy != my) {
|
||||||
|
count = intersectLine(cx, cy, mx, my, rx1, ry1, rx2, ry2);
|
||||||
|
}
|
||||||
|
mx = cx = coords[0];
|
||||||
|
my = cy = coords[1];
|
||||||
|
break;
|
||||||
|
case PathIterator.SEG_LINETO:
|
||||||
|
count = intersectLine(cx, cy, cx = coords[0], cy = coords[1], rx1, ry1, rx2, ry2);
|
||||||
|
break;
|
||||||
|
case PathIterator.SEG_QUADTO:
|
||||||
|
count = intersectQuad(cx, cy, coords[0], coords[1], cx = coords[2], cy = coords[3],
|
||||||
|
rx1, ry1, rx2, ry2);
|
||||||
|
break;
|
||||||
|
case PathIterator.SEG_CUBICTO:
|
||||||
|
count = intersectCubic(cx, cy, coords[0], coords[1], coords[2], coords[3],
|
||||||
|
cx = coords[4], cy = coords[5], rx1, ry1, rx2, ry2);
|
||||||
|
break;
|
||||||
|
case PathIterator.SEG_CLOSE:
|
||||||
|
if (cy != my || cx != mx) {
|
||||||
|
count = intersectLine(cx, cy, mx, my, rx1, ry1, rx2, ry2);
|
||||||
|
}
|
||||||
|
cx = mx;
|
||||||
|
cy = my;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (count == CROSSING) {
|
||||||
|
return CROSSING;
|
||||||
|
}
|
||||||
|
cross += count;
|
||||||
|
p.next();
|
||||||
|
}
|
||||||
|
if (cy != my) {
|
||||||
|
count = intersectLine(cx, cy, mx, my, rx1, ry1, rx2, ry2);
|
||||||
|
if (count == CROSSING) {
|
||||||
|
return CROSSING;
|
||||||
|
}
|
||||||
|
cross += count;
|
||||||
|
}
|
||||||
|
return cross;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns how many times rectangle stripe cross shape or the are intersect
|
||||||
|
*/
|
||||||
|
public static int intersectShape (IShape s, float x, float y, float w, float h) {
|
||||||
|
if (!s.bounds().intersects(x, y, w, h)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return intersectPath(s.getPathIterator(null), x, y, w, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if cross count correspond inside location for non zero path
|
||||||
|
* rule
|
||||||
|
*/
|
||||||
|
public static boolean isInsideNonZero (int cross) {
|
||||||
|
return cross != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if cross count correspond inside location for even-odd path
|
||||||
|
* rule
|
||||||
|
*/
|
||||||
|
public static boolean isInsideEvenOdd (int cross) {
|
||||||
|
return (cross & 1) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorts a bound array.
|
||||||
|
*/
|
||||||
|
protected static void sortBound (float[] bound, int bc) {
|
||||||
|
for (int i = 0; i < bc - 4; i += 4) {
|
||||||
|
int k = i;
|
||||||
|
for (int j = i + 4; j < bc; j += 4) {
|
||||||
|
if (bound[k] > bound[j]) {
|
||||||
|
k = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (k != i) {
|
||||||
|
float tmp = bound[i];
|
||||||
|
bound[i] = bound[k];
|
||||||
|
bound[k] = tmp;
|
||||||
|
tmp = bound[i + 1];
|
||||||
|
bound[i + 1] = bound[k + 1];
|
||||||
|
bound[k + 1] = tmp;
|
||||||
|
tmp = bound[i + 2];
|
||||||
|
bound[i + 2] = bound[k + 2];
|
||||||
|
bound[k + 2] = tmp;
|
||||||
|
tmp = bound[i + 3];
|
||||||
|
bound[i + 3] = bound[k + 3];
|
||||||
|
bound[k + 3] = tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether bounds intersect a rectangle or not.
|
||||||
|
*/
|
||||||
|
protected static int crossBound (float bound[], int bc, float py1, float py2) {
|
||||||
|
// LEFT/RIGHT
|
||||||
|
if (bc == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check Y coordinate
|
||||||
|
int up = 0;
|
||||||
|
int down = 0;
|
||||||
|
for (int i = 2; i < bc; i += 4) {
|
||||||
|
if (bound[i] < py1) {
|
||||||
|
up++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (bound[i] > py2) {
|
||||||
|
down++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return CROSSING;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UP
|
||||||
|
if (down == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (up != 0) {
|
||||||
|
// bc >= 2
|
||||||
|
sortBound(bound, bc);
|
||||||
|
boolean sign = bound[2] > py2;
|
||||||
|
for (int i = 6; i < bc; i += 4) {
|
||||||
|
boolean sign2 = bound[i] > py2;
|
||||||
|
if (sign != sign2 && bound[i + 1] != bound[i - 3]) {
|
||||||
|
return CROSSING;
|
||||||
|
}
|
||||||
|
sign = sign2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allowable tolerance for bounds comparison
|
||||||
|
*/
|
||||||
|
protected static final float DELTA = 1E-5f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If roots have distance less then <code>ROOT_DELTA</code> they are double
|
||||||
|
*/
|
||||||
|
protected static final float ROOT_DELTA = 1E-10f;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
//
|
||||||
|
// $Id$
|
||||||
|
|
||||||
|
package pythagoras.f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dimension-related utility methods.
|
||||||
|
*/
|
||||||
|
public class Dimensions
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns a string describing the supplied dimension, of the form <code>widthxheight</code>.
|
||||||
|
*/
|
||||||
|
public static String dimenToString (float width, float height) {
|
||||||
|
return width + "x" + height;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,257 @@
|
|||||||
|
//
|
||||||
|
// Pythagoras - a collection of geometry classes
|
||||||
|
// http://github.com/samskivert/pythagoras
|
||||||
|
|
||||||
|
package pythagoras.f;
|
||||||
|
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
|
public class FlatteningPathIterator implements PathIterator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The default points buffer size
|
||||||
|
*/
|
||||||
|
private static final int BUFFER_SIZE = 16;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default curve subdivision limit
|
||||||
|
*/
|
||||||
|
private static final int BUFFER_LIMIT = 16;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The points buffer capacity
|
||||||
|
*/
|
||||||
|
private static final int BUFFER_CAPACITY = 16;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of current segment to be flat
|
||||||
|
*/
|
||||||
|
int bufType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The curve subdivision limit
|
||||||
|
*/
|
||||||
|
int bufLimit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current points buffer size
|
||||||
|
*/
|
||||||
|
int bufSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The inner cursor position in points buffer
|
||||||
|
*/
|
||||||
|
int bufIndex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current subdivision count
|
||||||
|
*/
|
||||||
|
int bufSubdiv;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The points buffer
|
||||||
|
*/
|
||||||
|
float buf[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The indicator of empty points buffer
|
||||||
|
*/
|
||||||
|
boolean bufEmpty = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The source PathIterator
|
||||||
|
*/
|
||||||
|
PathIterator p;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The flatness of new path
|
||||||
|
*/
|
||||||
|
float flatness;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The square of flatness
|
||||||
|
*/
|
||||||
|
float flatness2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The x coordinate of previous path segment
|
||||||
|
*/
|
||||||
|
float px;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The y coordinate of previous path segment
|
||||||
|
*/
|
||||||
|
float py;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The tamporary buffer for getting points from PathIterator
|
||||||
|
*/
|
||||||
|
float coords[] = new float[6];
|
||||||
|
|
||||||
|
public FlatteningPathIterator (PathIterator path, float flatness) {
|
||||||
|
this(path, flatness, BUFFER_LIMIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FlatteningPathIterator (PathIterator path, float flatness, int limit) {
|
||||||
|
if (flatness < 0) {
|
||||||
|
throw new IllegalArgumentException("Flatness is less then zero");
|
||||||
|
}
|
||||||
|
if (limit < 0) {
|
||||||
|
throw new IllegalArgumentException("Limit is less then zero");
|
||||||
|
}
|
||||||
|
if (path == null) {
|
||||||
|
throw new NullPointerException("Path is null");
|
||||||
|
}
|
||||||
|
this.p = path;
|
||||||
|
this.flatness = flatness;
|
||||||
|
this.flatness2 = flatness * flatness;
|
||||||
|
this.bufLimit = limit;
|
||||||
|
this.bufSize = Math.min(bufLimit, BUFFER_SIZE);
|
||||||
|
this.buf = new float[bufSize];
|
||||||
|
this.bufIndex = bufSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getFlatness () {
|
||||||
|
return flatness;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRecursionLimit () {
|
||||||
|
return bufLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWindingRule () {
|
||||||
|
return p.getWindingRule();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDone () {
|
||||||
|
return bufEmpty && p.isDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates flat path points for current segment of the source shape.
|
||||||
|
*
|
||||||
|
* Line segment is flat by itself. Flatness of quad and cubic curves
|
||||||
|
* evaluated by getFlatnessSq() method. Curves subdivided until current
|
||||||
|
* flatness is bigger than user defined and subdivision limit isn't
|
||||||
|
* exhausted. Single source segment translated to series of buffer points.
|
||||||
|
* The less flatness the bigger serries. Every currentSegment() call extract
|
||||||
|
* one point from the buffer. When series completed evaluate() takes next
|
||||||
|
* source shape segment.
|
||||||
|
*/
|
||||||
|
void evaluate () {
|
||||||
|
if (bufEmpty) {
|
||||||
|
bufType = p.currentSegment(coords);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (bufType) {
|
||||||
|
case SEG_MOVETO:
|
||||||
|
case SEG_LINETO:
|
||||||
|
px = coords[0];
|
||||||
|
py = coords[1];
|
||||||
|
break;
|
||||||
|
case SEG_QUADTO:
|
||||||
|
if (bufEmpty) {
|
||||||
|
bufIndex -= 6;
|
||||||
|
buf[bufIndex + 0] = px;
|
||||||
|
buf[bufIndex + 1] = py;
|
||||||
|
System.arraycopy(coords, 0, buf, bufIndex + 2, 4);
|
||||||
|
bufSubdiv = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (bufSubdiv < bufLimit) {
|
||||||
|
if (QuadCurve2D.getFlatnessSq(buf, bufIndex) < flatness2) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Realloc buffer
|
||||||
|
if (bufIndex <= 4) {
|
||||||
|
float tmp[] = new float[bufSize + BUFFER_CAPACITY];
|
||||||
|
System.arraycopy(buf, bufIndex, tmp, bufIndex + BUFFER_CAPACITY, bufSize
|
||||||
|
- bufIndex);
|
||||||
|
buf = tmp;
|
||||||
|
bufSize += BUFFER_CAPACITY;
|
||||||
|
bufIndex += BUFFER_CAPACITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
QuadCurve2D.subdivide(buf, bufIndex, buf, bufIndex - 4, buf, bufIndex);
|
||||||
|
|
||||||
|
bufIndex -= 4;
|
||||||
|
bufSubdiv++;
|
||||||
|
}
|
||||||
|
|
||||||
|
bufIndex += 4;
|
||||||
|
px = buf[bufIndex];
|
||||||
|
py = buf[bufIndex + 1];
|
||||||
|
|
||||||
|
bufEmpty = (bufIndex == bufSize - 2);
|
||||||
|
if (bufEmpty) {
|
||||||
|
bufIndex = bufSize;
|
||||||
|
bufType = SEG_LINETO;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SEG_CUBICTO:
|
||||||
|
if (bufEmpty) {
|
||||||
|
bufIndex -= 8;
|
||||||
|
buf[bufIndex + 0] = px;
|
||||||
|
buf[bufIndex + 1] = py;
|
||||||
|
System.arraycopy(coords, 0, buf, bufIndex + 2, 6);
|
||||||
|
bufSubdiv = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (bufSubdiv < bufLimit) {
|
||||||
|
if (CubicCurve2D.getFlatnessSq(buf, bufIndex) < flatness2) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Realloc buffer
|
||||||
|
if (bufIndex <= 6) {
|
||||||
|
float tmp[] = new float[bufSize + BUFFER_CAPACITY];
|
||||||
|
System.arraycopy(buf, bufIndex, tmp, bufIndex + BUFFER_CAPACITY, bufSize
|
||||||
|
- bufIndex);
|
||||||
|
buf = tmp;
|
||||||
|
bufSize += BUFFER_CAPACITY;
|
||||||
|
bufIndex += BUFFER_CAPACITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
CubicCurve2D.subdivide(buf, bufIndex, buf, bufIndex - 6, buf, bufIndex);
|
||||||
|
|
||||||
|
bufIndex -= 6;
|
||||||
|
bufSubdiv++;
|
||||||
|
}
|
||||||
|
|
||||||
|
bufIndex += 6;
|
||||||
|
px = buf[bufIndex];
|
||||||
|
py = buf[bufIndex + 1];
|
||||||
|
|
||||||
|
bufEmpty = (bufIndex == bufSize - 2);
|
||||||
|
if (bufEmpty) {
|
||||||
|
bufIndex = bufSize;
|
||||||
|
bufType = SEG_LINETO;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void next () {
|
||||||
|
if (bufEmpty) {
|
||||||
|
p.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int currentSegment (float[] coords) {
|
||||||
|
if (isDone()) {
|
||||||
|
throw new NoSuchElementException("Iterator out of bounds");
|
||||||
|
}
|
||||||
|
evaluate();
|
||||||
|
int type = bufType;
|
||||||
|
if (type != SEG_CLOSE) {
|
||||||
|
coords[0] = px;
|
||||||
|
coords[1] = py;
|
||||||
|
if (type != SEG_MOVETO) {
|
||||||
|
type = SEG_LINETO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -56,20 +56,20 @@ public interface IShape
|
|||||||
*/
|
*/
|
||||||
Rectangle getBounds ();
|
Rectangle getBounds ();
|
||||||
|
|
||||||
// /**
|
/**
|
||||||
// * Returns an iterator over the path described by this shape.
|
* Returns an iterator over the path described by this shape.
|
||||||
// *
|
*
|
||||||
// * @param at if supplied, the points in the path are transformed using this.
|
* @param at if supplied, the points in the path are transformed using this.
|
||||||
// */
|
*/
|
||||||
// PathIterator getPathIterator (AffineTransform at);
|
PathIterator getPathIterator (AffineTransform at);
|
||||||
|
|
||||||
// /**
|
/**
|
||||||
// * Returns an iterator over the path described by this shape.
|
* Returns an iterator over the path described by this shape.
|
||||||
// *
|
*
|
||||||
// * @param at if supplied, the points in the path are transformed using this.
|
* @param at if supplied, the points in the path are transformed using this.
|
||||||
// * @param flatness when approximating curved segments with lines, this controls the maximum
|
* @param flatness when approximating curved segments with lines, this controls the maximum
|
||||||
// * distance the lines are allowed to deviate from the approximated curve, thus a higher
|
* distance the lines are allowed to deviate from the approximated curve, thus a higher
|
||||||
// * flatness value generally allows for a path with fewer segments.
|
* flatness value generally allows for a path with fewer segments.
|
||||||
// */
|
*/
|
||||||
// PathIterator getPathIterator (AffineTransform at, float flatness);
|
PathIterator getPathIterator (AffineTransform at, float flatness);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
//
|
||||||
|
// Pythagoras - a collection of geometry classes
|
||||||
|
// http://github.com/samskivert/pythagoras
|
||||||
|
|
||||||
|
package pythagoras.f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception thrown if an operation is performed on a {@link Path} that is in an illegal state
|
||||||
|
* with respect to the particular operation being performed. For example, appending a segment to a
|
||||||
|
* path without an initial moveto.
|
||||||
|
*/
|
||||||
|
public class IllegalPathStateException extends RuntimeException
|
||||||
|
{
|
||||||
|
public IllegalPathStateException () {
|
||||||
|
}
|
||||||
|
|
||||||
|
public IllegalPathStateException (String s) {
|
||||||
|
super(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,9 +10,16 @@ import java.io.Serializable;
|
|||||||
*/
|
*/
|
||||||
public class Line extends AbstractLine implements Serializable
|
public class Line extends AbstractLine implements Serializable
|
||||||
{
|
{
|
||||||
|
/** The x-coordinate of the start of this line segment. */
|
||||||
public float x1;
|
public float x1;
|
||||||
|
|
||||||
|
/** The y-coordinate of the start of this line segment. */
|
||||||
public float y1;
|
public float y1;
|
||||||
|
|
||||||
|
/** The x-coordinate of the end of this line segment. */
|
||||||
public float x2;
|
public float x2;
|
||||||
|
|
||||||
|
/** The y-coordinate of the end of this line segment. */
|
||||||
public float y2;
|
public float y2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,30 +1,13 @@
|
|||||||
//
|
//
|
||||||
// Pythagoras - a collection of geometry classes
|
// $Id$
|
||||||
// http://github.com/samskivert/pythagoras
|
|
||||||
|
|
||||||
package pythagoras.f;
|
package pythagoras.f;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains geometry routines that don't fit more nicely into specialized classes.
|
* Line-related utility methods.
|
||||||
*/
|
*/
|
||||||
public class Geometry
|
public class Lines
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Returns the squared Euclidian distance between the specified two points.
|
|
||||||
*/
|
|
||||||
public static float distanceSq (float x1, float y1, float x2, float y2) {
|
|
||||||
x2 -= x1;
|
|
||||||
y2 -= y1;
|
|
||||||
return x2 * x2 + y2 * y2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the Euclidian distance between the specified two points.
|
|
||||||
*/
|
|
||||||
public static float distance (float x1, float y1, float x2, float y2) {
|
|
||||||
return (float)Math.sqrt(distanceSq(x1, y1, x2, y2));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the specified two line segments intersect.
|
* Returns true if the specified two line segments intersect.
|
||||||
*/
|
*/
|
||||||
@@ -137,24 +120,4 @@ public class Geometry
|
|||||||
public static float pointSegDist (float px, float py, float x1, float y1, float x2, float y2) {
|
public static float pointSegDist (float px, float py, float x1, float y1, float x2, float y2) {
|
||||||
return (float)Math.sqrt(pointSegDistSq(px, py, x1, y1, x2, y2));
|
return (float)Math.sqrt(pointSegDistSq(px, py, x1, y1, x2, y2));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a string describing the supplied point, of the form <code>+x+y</code>,
|
|
||||||
* <code>+x-y</code>, <code>-x-y</code>, etc.
|
|
||||||
*/
|
|
||||||
public static String pointToString (float x, float y) {
|
|
||||||
StringBuilder buf = new StringBuilder();
|
|
||||||
if (x >= 0) buf.append("+");
|
|
||||||
buf.append(x);
|
|
||||||
if (y >= 0) buf.append("+");
|
|
||||||
buf.append(y);
|
|
||||||
return buf.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a string describing the supplied dimension, of the form <code>widthxheight</code>.
|
|
||||||
*/
|
|
||||||
public static String dimenToString (float width, float height) {
|
|
||||||
return width + "x" + height;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
//
|
||||||
|
// Pythagoras - a collection of geometry classes
|
||||||
|
// http://github.com/samskivert/pythagoras
|
||||||
|
|
||||||
|
package pythagoras.f;
|
||||||
|
|
||||||
|
public class NoninvertibleTransformException extends java.lang.Exception
|
||||||
|
{
|
||||||
|
public NoninvertibleTransformException (String s) {
|
||||||
|
super(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,378 @@
|
|||||||
|
//
|
||||||
|
// Pythagoras - a collection of geometry classes
|
||||||
|
// http://github.com/samskivert/pythagoras
|
||||||
|
|
||||||
|
package pythagoras.f;
|
||||||
|
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a path constructed from lines and curves and which can contain subpaths.
|
||||||
|
*/
|
||||||
|
public final class Path implements IShape, Cloneable
|
||||||
|
{
|
||||||
|
/** Specifies the even/odd rule for determining the interior of a path. */
|
||||||
|
public static final int WIND_EVEN_ODD = PathIterator.WIND_EVEN_ODD;
|
||||||
|
|
||||||
|
/** Specifies the non-zero rule for determining the interior of a path. */
|
||||||
|
public static final int WIND_NON_ZERO = PathIterator.WIND_NON_ZERO;
|
||||||
|
|
||||||
|
public Path () {
|
||||||
|
this(WIND_NON_ZERO, BUFFER_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path (int rule) {
|
||||||
|
this(rule, BUFFER_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path (int rule, int initialCapacity) {
|
||||||
|
setWindingRule(rule);
|
||||||
|
types = new byte[initialCapacity];
|
||||||
|
points = new float[initialCapacity * 2];
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path (IShape shape) {
|
||||||
|
this(WIND_NON_ZERO, BUFFER_SIZE);
|
||||||
|
PathIterator p = shape.getPathIterator(null);
|
||||||
|
setWindingRule(p.getWindingRule());
|
||||||
|
append(p, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWindingRule (int rule) {
|
||||||
|
if (rule != WIND_EVEN_ODD && rule != WIND_NON_ZERO) {
|
||||||
|
throw new IllegalArgumentException("Invalid winding rule value");
|
||||||
|
}
|
||||||
|
this.rule = rule;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWindingRule () {
|
||||||
|
return rule;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void moveTo (float x, float y) {
|
||||||
|
if (typeSize > 0 && types[typeSize - 1] == PathIterator.SEG_MOVETO) {
|
||||||
|
points[pointSize - 2] = x;
|
||||||
|
points[pointSize - 1] = y;
|
||||||
|
} else {
|
||||||
|
checkBuf(2, false);
|
||||||
|
types[typeSize++] = PathIterator.SEG_MOVETO;
|
||||||
|
points[pointSize++] = x;
|
||||||
|
points[pointSize++] = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void lineTo (float x, float y) {
|
||||||
|
checkBuf(2, true);
|
||||||
|
types[typeSize++] = PathIterator.SEG_LINETO;
|
||||||
|
points[pointSize++] = x;
|
||||||
|
points[pointSize++] = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void quadTo (float x1, float y1, float x2, float y2) {
|
||||||
|
checkBuf(4, true);
|
||||||
|
types[typeSize++] = PathIterator.SEG_QUADTO;
|
||||||
|
points[pointSize++] = x1;
|
||||||
|
points[pointSize++] = y1;
|
||||||
|
points[pointSize++] = x2;
|
||||||
|
points[pointSize++] = y2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void curveTo (float x1, float y1, float x2, float y2, float x3, float y3) {
|
||||||
|
checkBuf(6, true);
|
||||||
|
types[typeSize++] = PathIterator.SEG_CUBICTO;
|
||||||
|
points[pointSize++] = x1;
|
||||||
|
points[pointSize++] = y1;
|
||||||
|
points[pointSize++] = x2;
|
||||||
|
points[pointSize++] = y2;
|
||||||
|
points[pointSize++] = x3;
|
||||||
|
points[pointSize++] = y3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void closePath () {
|
||||||
|
if (typeSize == 0 || types[typeSize - 1] != PathIterator.SEG_CLOSE) {
|
||||||
|
checkBuf(0, true);
|
||||||
|
types[typeSize++] = PathIterator.SEG_CLOSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void append (IShape shape, boolean connect) {
|
||||||
|
PathIterator p = shape.getPathIterator(null);
|
||||||
|
append(p, connect);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void append (PathIterator path, boolean connect) {
|
||||||
|
while (!path.isDone()) {
|
||||||
|
float coords[] = new float[6];
|
||||||
|
switch (path.currentSegment(coords)) {
|
||||||
|
case PathIterator.SEG_MOVETO:
|
||||||
|
if (!connect || typeSize == 0) {
|
||||||
|
moveTo(coords[0], coords[1]);
|
||||||
|
} else if (types[typeSize - 1] != PathIterator.SEG_CLOSE &&
|
||||||
|
points[pointSize - 2] == coords[0] &&
|
||||||
|
points[pointSize - 1] == coords[1]) {
|
||||||
|
// we're already here
|
||||||
|
} else {
|
||||||
|
lineTo(coords[0], coords[1]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PathIterator.SEG_LINETO:
|
||||||
|
lineTo(coords[0], coords[1]);
|
||||||
|
break;
|
||||||
|
case PathIterator.SEG_QUADTO:
|
||||||
|
quadTo(coords[0], coords[1], coords[2], coords[3]);
|
||||||
|
break;
|
||||||
|
case PathIterator.SEG_CUBICTO:
|
||||||
|
curveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]);
|
||||||
|
break;
|
||||||
|
case PathIterator.SEG_CLOSE:
|
||||||
|
closePath();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
path.next();
|
||||||
|
connect = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Point getCurrentPoint () {
|
||||||
|
if (typeSize == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int j = pointSize - 2;
|
||||||
|
if (types[typeSize - 1] == PathIterator.SEG_CLOSE) {
|
||||||
|
for (int i = typeSize - 2; i > 0; i--) {
|
||||||
|
int type = types[i];
|
||||||
|
if (type == PathIterator.SEG_MOVETO) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
j -= pointShift[type];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Point(points[j], points[j + 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset () {
|
||||||
|
typeSize = 0;
|
||||||
|
pointSize = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void transform (AffineTransform t) {
|
||||||
|
t.transform(points, 0, points, 0, pointSize / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IShape createTransformedShape (AffineTransform t) {
|
||||||
|
Path p = clone();
|
||||||
|
if (t != null) {
|
||||||
|
p.transform(t);
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* <em>Note:</em> this method violates the contract in that the bounds returned will
|
||||||
|
* <em>not</em> reflect subsequent changes to this path. Doing so is prohibitively expensive.
|
||||||
|
*/
|
||||||
|
@Override public IRectangle bounds () {
|
||||||
|
return getBounds();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // from interface IShape
|
||||||
|
public Rectangle getBounds () {
|
||||||
|
float rx1, ry1, rx2, ry2;
|
||||||
|
if (pointSize == 0) {
|
||||||
|
rx1 = ry1 = rx2 = ry2 = 0f;
|
||||||
|
} else {
|
||||||
|
int i = pointSize - 1;
|
||||||
|
ry1 = ry2 = points[i--];
|
||||||
|
rx1 = rx2 = points[i--];
|
||||||
|
while (i > 0) {
|
||||||
|
float y = points[i--];
|
||||||
|
float x = points[i--];
|
||||||
|
if (x < rx1) {
|
||||||
|
rx1 = x;
|
||||||
|
} else if (x > rx2) {
|
||||||
|
rx2 = x;
|
||||||
|
}
|
||||||
|
if (y < ry1) {
|
||||||
|
ry1 = y;
|
||||||
|
} else if (y > ry2) {
|
||||||
|
ry2 = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Rectangle(rx1, ry1, rx2 - rx1, ry2 - ry1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // from interface IShape
|
||||||
|
public boolean isEmpty () {
|
||||||
|
// TODO: will this be insanely difficult to do correctly?
|
||||||
|
return getBounds().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // from interface IShape
|
||||||
|
public boolean contains (float px, float py) {
|
||||||
|
return isInside(Crossing.crossShape(this, px, py));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // from interface IShape
|
||||||
|
public boolean contains (float rx, float ry, float rw, float rh) {
|
||||||
|
int cross = Crossing.intersectShape(this, rx, ry, rw, rh);
|
||||||
|
return cross != Crossing.CROSSING && isInside(cross);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // from interface IShape
|
||||||
|
public boolean intersects (float rx, float ry, float rw, float rh) {
|
||||||
|
int cross = Crossing.intersectShape(this, rx, ry, rw, rh);
|
||||||
|
return cross == Crossing.CROSSING || isInside(cross);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // from interface IShape
|
||||||
|
public boolean contains (IPoint p) {
|
||||||
|
return contains(p.getX(), p.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // from interface IShape
|
||||||
|
public boolean contains (IRectangle r) {
|
||||||
|
return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // from interface IShape
|
||||||
|
public boolean intersects (IRectangle r) {
|
||||||
|
return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // from interface IShape
|
||||||
|
public PathIterator getPathIterator (AffineTransform t) {
|
||||||
|
return new Iterator(this, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // from interface IShape
|
||||||
|
public PathIterator getPathIterator (AffineTransform t, float flatness) {
|
||||||
|
return new FlatteningPathIterator(getPathIterator(t), flatness);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Path clone () {
|
||||||
|
try {
|
||||||
|
Path p = (Path)super.clone();
|
||||||
|
p.types = types.clone();
|
||||||
|
p.points = points.clone();
|
||||||
|
return p;
|
||||||
|
} catch (CloneNotSupportedException e) {
|
||||||
|
throw new InternalError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks points and types buffer size to add pointCount points. If necessary realloc buffers
|
||||||
|
* to enlarge size.
|
||||||
|
*
|
||||||
|
* @param pointCount the point count to be added in buffer
|
||||||
|
*/
|
||||||
|
protected void checkBuf (int pointCount, boolean checkMove) {
|
||||||
|
if (checkMove && typeSize == 0) {
|
||||||
|
throw new IllegalPathStateException("First segment must be a SEG_MOVETO");
|
||||||
|
}
|
||||||
|
if (typeSize == types.length) {
|
||||||
|
byte tmp[] = new byte[typeSize + BUFFER_CAPACITY];
|
||||||
|
System.arraycopy(types, 0, tmp, 0, typeSize);
|
||||||
|
types = tmp;
|
||||||
|
}
|
||||||
|
if (pointSize + pointCount > points.length) {
|
||||||
|
float tmp[] = new float[pointSize + Math.max(BUFFER_CAPACITY * 2, pointCount)];
|
||||||
|
System.arraycopy(points, 0, tmp, 0, pointSize);
|
||||||
|
points = tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks cross count according to path rule to define is it point inside shape or not.
|
||||||
|
*
|
||||||
|
* @param cross the point cross count.
|
||||||
|
* @return true if point is inside path, or false otherwise.
|
||||||
|
*/
|
||||||
|
protected boolean isInside (int cross) {
|
||||||
|
return (rule == WIND_NON_ZERO) ? Crossing.isInsideNonZero(cross) :
|
||||||
|
Crossing.isInsideEvenOdd(cross);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** An iterator over a {@link Path}. */
|
||||||
|
protected static class Iterator implements PathIterator
|
||||||
|
{
|
||||||
|
/** The current cursor position in types buffer. */
|
||||||
|
private int typeIndex;
|
||||||
|
|
||||||
|
/** The current cursor position in points buffer. */
|
||||||
|
private int pointIndex;
|
||||||
|
|
||||||
|
/** The source Path object. */
|
||||||
|
private Path p;
|
||||||
|
|
||||||
|
/** The path iterator transformation. */
|
||||||
|
private AffineTransform t;
|
||||||
|
|
||||||
|
Iterator (Path path) {
|
||||||
|
this(path, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator (Path path, AffineTransform at) {
|
||||||
|
this.p = path;
|
||||||
|
this.t = at;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int getWindingRule () {
|
||||||
|
return p.getWindingRule();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public boolean isDone () {
|
||||||
|
return typeIndex >= p.typeSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void next () {
|
||||||
|
typeIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int currentSegment (float[] coords) {
|
||||||
|
if (isDone()) {
|
||||||
|
throw new NoSuchElementException("Iterator out of bounds");
|
||||||
|
}
|
||||||
|
int type = p.types[typeIndex];
|
||||||
|
int count = Path.pointShift[type];
|
||||||
|
System.arraycopy(p.points, pointIndex, coords, 0, count);
|
||||||
|
if (t != null) {
|
||||||
|
t.transform(coords, 0, coords, 0, count / 2);
|
||||||
|
}
|
||||||
|
pointIndex += count;
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The point's types buffer. */
|
||||||
|
protected byte[] types;
|
||||||
|
|
||||||
|
/** The points buffer. */
|
||||||
|
protected float[] points;
|
||||||
|
|
||||||
|
/** The point's type buffer size. */
|
||||||
|
protected int typeSize;
|
||||||
|
|
||||||
|
/** The points buffer size. */
|
||||||
|
protected int pointSize;
|
||||||
|
|
||||||
|
/* The path rule. */
|
||||||
|
protected int rule;
|
||||||
|
|
||||||
|
/** The space required in points buffer for different segmenet types. */
|
||||||
|
protected static int pointShift[] = { 2, // MOVETO
|
||||||
|
2, // LINETO
|
||||||
|
4, // QUADTO
|
||||||
|
6, // CUBICTO
|
||||||
|
0 }; // CLOSE
|
||||||
|
|
||||||
|
/** The default initial buffer size. */
|
||||||
|
protected static final int BUFFER_SIZE = 10;
|
||||||
|
|
||||||
|
/** The amount by which to expand the buffer capacity. */
|
||||||
|
protected static final int BUFFER_CAPACITY = 10;
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
//
|
||||||
|
// Pythagoras - a collection of geometry classes
|
||||||
|
// http://github.com/samskivert/pythagoras
|
||||||
|
|
||||||
|
package pythagoras.f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to return the boundary of a {@link IShape}, one segment at a time.
|
||||||
|
*/
|
||||||
|
public interface PathIterator
|
||||||
|
{
|
||||||
|
/** Specifies the even/odd rule for determining the interior of a path. */
|
||||||
|
int WIND_EVEN_ODD = 0;
|
||||||
|
|
||||||
|
/** Specifies the non-zero rule for determining the interior of a path. */
|
||||||
|
int WIND_NON_ZERO = 1;
|
||||||
|
|
||||||
|
/** Indicates the starting location for a new subpath. */
|
||||||
|
int SEG_MOVETO = 0;
|
||||||
|
|
||||||
|
/** Indicates the end point of a line to be drawn from the most recently specified point. */
|
||||||
|
int SEG_LINETO = 1;
|
||||||
|
|
||||||
|
/** Indicates a pair of points that specify a quadratic parametric curve to be drawn from the
|
||||||
|
* most recently specified point. */
|
||||||
|
int SEG_QUADTO = 2;
|
||||||
|
|
||||||
|
/** Indicates a pair of points that specify a cubic parametric curve to be drawn from the most
|
||||||
|
* recently specified point. */
|
||||||
|
int SEG_CUBICTO = 3;
|
||||||
|
|
||||||
|
/** Indicates that the preceding subpath should be closed by appending a line segment back to
|
||||||
|
* the point corresponding to the most recent {@link #SEG_MOVETO}. */
|
||||||
|
int SEG_CLOSE = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the winding rule used to determine the interior of this path.
|
||||||
|
*/
|
||||||
|
int getWindingRule ();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this path has no additional segments.
|
||||||
|
*/
|
||||||
|
boolean isDone ();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advances this path to the next segment.
|
||||||
|
*/
|
||||||
|
void next ();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the coordinates and type of the current path segment. The number of points stored in
|
||||||
|
* {@code coords} differs by path segment type: 0 - {@link #SEG_CLOSE}, 1 - {@link
|
||||||
|
* #SEG_MOVETO}, {@link #SEG_LINETO}, 2 - {@link #SEG_QUADTO}, 3 - {@link #SEG_CUBICTO}.
|
||||||
|
*
|
||||||
|
* @param coords a buffer into which the current coordinates will be copied. It must be of
|
||||||
|
* length 6. Each point is stored as a pair of x,y coordinates.
|
||||||
|
* @return the path segment type, e.g. {@link #SEG_MOVETO}.
|
||||||
|
*/
|
||||||
|
int currentSegment (float[] coords);
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
//
|
||||||
|
// $Id$
|
||||||
|
|
||||||
|
package pythagoras.f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Point-related utility methods.
|
||||||
|
*/
|
||||||
|
public class Points
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns the squared Euclidian distance between the specified two points.
|
||||||
|
*/
|
||||||
|
public static float distanceSq (float x1, float y1, float x2, float y2) {
|
||||||
|
x2 -= x1;
|
||||||
|
y2 -= y1;
|
||||||
|
return x2 * x2 + y2 * y2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Euclidian distance between the specified two points.
|
||||||
|
*/
|
||||||
|
public static float distance (float x1, float y1, float x2, float y2) {
|
||||||
|
return (float)Math.sqrt(distanceSq(x1, y1, x2, y2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string describing the supplied point, of the form <code>+x+y</code>,
|
||||||
|
* <code>+x-y</code>, <code>-x-y</code>, etc.
|
||||||
|
*/
|
||||||
|
public static String pointToString (float x, float y) {
|
||||||
|
StringBuilder buf = new StringBuilder();
|
||||||
|
if (x >= 0) buf.append("+");
|
||||||
|
buf.append(x);
|
||||||
|
if (y >= 0) buf.append("+");
|
||||||
|
buf.append(y);
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user