diff options
Diffstat (limited to 'libjava/classpath/java/awt/geom')
32 files changed, 15895 insertions, 0 deletions
diff --git a/libjava/classpath/java/awt/geom/AffineTransform.java b/libjava/classpath/java/awt/geom/AffineTransform.java new file mode 100644 index 000000000..42590efce --- /dev/null +++ b/libjava/classpath/java/awt/geom/AffineTransform.java @@ -0,0 +1,1489 @@ +/* AffineTransform.java -- transform coordinates between two 2-D spaces + Copyright (C) 2000, 2001, 2002, 2004 Free Software Foundation + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.awt.geom; + +import java.awt.Shape; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; + +/** + * This class represents an affine transformation between two coordinate + * spaces in 2 dimensions. Such a transform preserves the "straightness" + * and "parallelness" of lines. The transform is built from a sequence of + * translations, scales, flips, rotations, and shears. + * + * <p>The transformation can be represented using matrix math on a 3x3 array. + * Given (x,y), the transformation (x',y') can be found by: + * <pre> + * [ x'] [ m00 m01 m02 ] [ x ] [ m00*x + m01*y + m02 ] + * [ y'] = [ m10 m11 m12 ] [ y ] = [ m10*x + m11*y + m12 ] + * [ 1 ] [ 0 0 1 ] [ 1 ] [ 1 ] + * </pre> + * The bottom row of the matrix is constant, so a transform can be uniquely + * represented (as in {@link #toString()}) by + * "[[m00, m01, m02], [m10, m11, m12]]". + * + * @author Tom Tromey (tromey@cygnus.com) + * @author Eric Blake (ebb9@email.byu.edu) + * @since 1.2 + * @status partially updated to 1.4, still has some problems + */ +public class AffineTransform implements Cloneable, Serializable +{ + /** + * Compatible with JDK 1.2+. + */ + private static final long serialVersionUID = 1330973210523860834L; + + /** + * The transformation is the identity (x' = x, y' = y). All other transforms + * have either a combination of the appropriate transform flag bits for + * their type, or the type GENERAL_TRANSFORM. + * + * @see #TYPE_TRANSLATION + * @see #TYPE_UNIFORM_SCALE + * @see #TYPE_GENERAL_SCALE + * @see #TYPE_FLIP + * @see #TYPE_QUADRANT_ROTATION + * @see #TYPE_GENERAL_ROTATION + * @see #TYPE_GENERAL_TRANSFORM + * @see #getType() + */ + public static final int TYPE_IDENTITY = 0; + + /** + * The transformation includes a translation - shifting in the x or y + * direction without changing length or angles. + * + * @see #TYPE_IDENTITY + * @see #TYPE_UNIFORM_SCALE + * @see #TYPE_GENERAL_SCALE + * @see #TYPE_FLIP + * @see #TYPE_QUADRANT_ROTATION + * @see #TYPE_GENERAL_ROTATION + * @see #TYPE_GENERAL_TRANSFORM + * @see #getType() + */ + public static final int TYPE_TRANSLATION = 1; + + /** + * The transformation includes a uniform scale - length is scaled in both + * the x and y directions by the same amount, without affecting angles. + * This is mutually exclusive with TYPE_GENERAL_SCALE. + * + * @see #TYPE_IDENTITY + * @see #TYPE_TRANSLATION + * @see #TYPE_GENERAL_SCALE + * @see #TYPE_FLIP + * @see #TYPE_QUADRANT_ROTATION + * @see #TYPE_GENERAL_ROTATION + * @see #TYPE_GENERAL_TRANSFORM + * @see #TYPE_MASK_SCALE + * @see #getType() + */ + public static final int TYPE_UNIFORM_SCALE = 2; + + /** + * The transformation includes a general scale - length is scaled in either + * or both the x and y directions, but by different amounts; without + * affecting angles. This is mutually exclusive with TYPE_UNIFORM_SCALE. + * + * @see #TYPE_IDENTITY + * @see #TYPE_TRANSLATION + * @see #TYPE_UNIFORM_SCALE + * @see #TYPE_FLIP + * @see #TYPE_QUADRANT_ROTATION + * @see #TYPE_GENERAL_ROTATION + * @see #TYPE_GENERAL_TRANSFORM + * @see #TYPE_MASK_SCALE + * @see #getType() + */ + public static final int TYPE_GENERAL_SCALE = 4; + + /** + * This constant checks if either variety of scale transform is performed. + * + * @see #TYPE_UNIFORM_SCALE + * @see #TYPE_GENERAL_SCALE + */ + public static final int TYPE_MASK_SCALE = 6; + + /** + * The transformation includes a flip about an axis, swapping between + * right-handed and left-handed coordinate systems. In a right-handed + * system, the positive x-axis rotates counter-clockwise to the positive + * y-axis; in a left-handed system it rotates clockwise. + * + * @see #TYPE_IDENTITY + * @see #TYPE_TRANSLATION + * @see #TYPE_UNIFORM_SCALE + * @see #TYPE_GENERAL_SCALE + * @see #TYPE_QUADRANT_ROTATION + * @see #TYPE_GENERAL_ROTATION + * @see #TYPE_GENERAL_TRANSFORM + * @see #getType() + */ + public static final int TYPE_FLIP = 64; + + /** + * The transformation includes a rotation of a multiple of 90 degrees (PI/2 + * radians). Angles are rotated, but length is preserved. This is mutually + * exclusive with TYPE_GENERAL_ROTATION. + * + * @see #TYPE_IDENTITY + * @see #TYPE_TRANSLATION + * @see #TYPE_UNIFORM_SCALE + * @see #TYPE_GENERAL_SCALE + * @see #TYPE_FLIP + * @see #TYPE_GENERAL_ROTATION + * @see #TYPE_GENERAL_TRANSFORM + * @see #TYPE_MASK_ROTATION + * @see #getType() + */ + public static final int TYPE_QUADRANT_ROTATION = 8; + + /** + * The transformation includes a rotation by an arbitrary angle. Angles are + * rotated, but length is preserved. This is mutually exclusive with + * TYPE_QUADRANT_ROTATION. + * + * @see #TYPE_IDENTITY + * @see #TYPE_TRANSLATION + * @see #TYPE_UNIFORM_SCALE + * @see #TYPE_GENERAL_SCALE + * @see #TYPE_FLIP + * @see #TYPE_QUADRANT_ROTATION + * @see #TYPE_GENERAL_TRANSFORM + * @see #TYPE_MASK_ROTATION + * @see #getType() + */ + public static final int TYPE_GENERAL_ROTATION = 16; + + /** + * This constant checks if either variety of rotation is performed. + * + * @see #TYPE_QUADRANT_ROTATION + * @see #TYPE_GENERAL_ROTATION + */ + public static final int TYPE_MASK_ROTATION = 24; + + /** + * The transformation is an arbitrary conversion of coordinates which + * could not be decomposed into the other TYPEs. + * + * @see #TYPE_IDENTITY + * @see #TYPE_TRANSLATION + * @see #TYPE_UNIFORM_SCALE + * @see #TYPE_GENERAL_SCALE + * @see #TYPE_FLIP + * @see #TYPE_QUADRANT_ROTATION + * @see #TYPE_GENERAL_ROTATION + * @see #getType() + */ + public static final int TYPE_GENERAL_TRANSFORM = 32; + + /** + * The X coordinate scaling element of the transform matrix. + * + * @serial matrix[0,0] + */ + private double m00; + + /** + * The Y coordinate shearing element of the transform matrix. + * + * @serial matrix[1,0] + */ + private double m10; + + /** + * The X coordinate shearing element of the transform matrix. + * + * @serial matrix[0,1] + */ + private double m01; + + /** + * The Y coordinate scaling element of the transform matrix. + * + * @serial matrix[1,1] + */ + private double m11; + + /** + * The X coordinate translation element of the transform matrix. + * + * @serial matrix[0,2] + */ + private double m02; + + /** + * The Y coordinate translation element of the transform matrix. + * + * @serial matrix[1,2] + */ + private double m12; + + /** The type of this transform. */ + private transient int type; + + /** + * Construct a new identity transform: + * <pre> + * [ 1 0 0 ] + * [ 0 1 0 ] + * [ 0 0 1 ] + * </pre> + */ + public AffineTransform() + { + m00 = m11 = 1; + } + + /** + * Create a new transform which copies the given one. + * + * @param tx the transform to copy + * @throws NullPointerException if tx is null + */ + public AffineTransform(AffineTransform tx) + { + setTransform(tx); + } + + /** + * Construct a transform with the given matrix entries: + * <pre> + * [ m00 m01 m02 ] + * [ m10 m11 m12 ] + * [ 0 0 1 ] + * </pre> + * + * @param m00 the x scaling component + * @param m10 the y shearing component + * @param m01 the x shearing component + * @param m11 the y scaling component + * @param m02 the x translation component + * @param m12 the y translation component + */ + public AffineTransform(float m00, float m10, + float m01, float m11, + float m02, float m12) + { + this.m00 = m00; + this.m10 = m10; + this.m01 = m01; + this.m11 = m11; + this.m02 = m02; + this.m12 = m12; + updateType(); + } + + /** + * Construct a transform from a sequence of float entries. The array must + * have at least 4 entries, which has a translation factor of 0; or 6 + * entries, for specifying all parameters: + * <pre> + * [ f[0] f[2] (f[4]) ] + * [ f[1] f[3] (f[5]) ] + * [ 0 0 1 ] + * </pre> + * + * @param f the matrix to copy from, with at least 4 (6) entries + * @throws NullPointerException if f is null + * @throws ArrayIndexOutOfBoundsException if f is too small + */ + public AffineTransform(float[] f) + { + m00 = f[0]; + m10 = f[1]; + m01 = f[2]; + m11 = f[3]; + if (f.length >= 6) + { + m02 = f[4]; + m12 = f[5]; + } + updateType(); + } + + /** + * Construct a transform with the given matrix entries: + * <pre> + * [ m00 m01 m02 ] + * [ m10 m11 m12 ] + * [ 0 0 1 ] + * </pre> + * + * @param m00 the x scaling component + * @param m10 the y shearing component + * @param m01 the x shearing component + * @param m11 the y scaling component + * @param m02 the x translation component + * @param m12 the y translation component + */ + public AffineTransform(double m00, double m10, double m01, + double m11, double m02, double m12) + { + this.m00 = m00; + this.m10 = m10; + this.m01 = m01; + this.m11 = m11; + this.m02 = m02; + this.m12 = m12; + updateType(); + } + + /** + * Construct a transform from a sequence of double entries. The array must + * have at least 4 entries, which has a translation factor of 0; or 6 + * entries, for specifying all parameters: + * <pre> + * [ d[0] d[2] (d[4]) ] + * [ d[1] d[3] (d[5]) ] + * [ 0 0 1 ] + * </pre> + * + * @param d the matrix to copy from, with at least 4 (6) entries + * @throws NullPointerException if d is null + * @throws ArrayIndexOutOfBoundsException if d is too small + */ + public AffineTransform(double[] d) + { + m00 = d[0]; + m10 = d[1]; + m01 = d[2]; + m11 = d[3]; + if (d.length >= 6) + { + m02 = d[4]; + m12 = d[5]; + } + updateType(); + } + + /** + * Returns a translation transform: + * <pre> + * [ 1 0 tx ] + * [ 0 1 ty ] + * [ 0 0 1 ] + * </pre> + * + * @param tx the x translation distance + * @param ty the y translation distance + * @return the translating transform + */ + public static AffineTransform getTranslateInstance(double tx, double ty) + { + AffineTransform t = new AffineTransform(); + t.m02 = tx; + t.m12 = ty; + t.type = (tx == 0 && ty == 0) ? TYPE_UNIFORM_SCALE : TYPE_TRANSLATION; + return t; + } + + /** + * Returns a rotation transform. A positive angle (in radians) rotates + * the positive x-axis to the positive y-axis: + * <pre> + * [ cos(theta) -sin(theta) 0 ] + * [ sin(theta) cos(theta) 0 ] + * [ 0 0 1 ] + * </pre> + * + * @param theta the rotation angle + * @return the rotating transform + */ + public static AffineTransform getRotateInstance(double theta) + { + AffineTransform t = new AffineTransform(); + t.setToRotation(theta); + return t; + } + + /** + * Returns a rotation transform about a point. A positive angle (in radians) + * rotates the positive x-axis to the positive y-axis. This is the same + * as calling: + * <pre> + * AffineTransform tx = new AffineTransform(); + * tx.setToTranslation(x, y); + * tx.rotate(theta); + * tx.translate(-x, -y); + * </pre> + * + * <p>The resulting matrix is: + * <pre> + * [ cos(theta) -sin(theta) x-x*cos+y*sin ] + * [ sin(theta) cos(theta) y-x*sin-y*cos ] + * [ 0 0 1 ] + * </pre> + * + * @param theta the rotation angle + * @param x the x coordinate of the pivot point + * @param y the y coordinate of the pivot point + * @return the rotating transform + */ + public static AffineTransform getRotateInstance(double theta, + double x, double y) + { + AffineTransform t = new AffineTransform(); + t.setToTranslation(x, y); + t.rotate(theta); + t.translate(-x, -y); + return t; + } + + /** + * Returns a scaling transform: + * <pre> + * [ sx 0 0 ] + * [ 0 sy 0 ] + * [ 0 0 1 ] + * </pre> + * + * @param sx the x scaling factor + * @param sy the y scaling factor + * @return the scaling transform + */ + public static AffineTransform getScaleInstance(double sx, double sy) + { + AffineTransform t = new AffineTransform(); + t.setToScale(sx, sy); + return t; + } + + /** + * Returns a shearing transform (points are shifted in the x direction based + * on a factor of their y coordinate, and in the y direction as a factor of + * their x coordinate): + * <pre> + * [ 1 shx 0 ] + * [ shy 1 0 ] + * [ 0 0 1 ] + * </pre> + * + * @param shx the x shearing factor + * @param shy the y shearing factor + * @return the shearing transform + */ + public static AffineTransform getShearInstance(double shx, double shy) + { + AffineTransform t = new AffineTransform(); + t.setToShear(shx, shy); + return t; + } + + /** + * Returns the type of this transform. The result is always valid, although + * it may not be the simplest interpretation (in other words, there are + * sequences of transforms which reduce to something simpler, which this + * does not always detect). The result is either TYPE_GENERAL_TRANSFORM, + * or a bit-wise combination of TYPE_TRANSLATION, the mutually exclusive + * TYPE_*_ROTATIONs, and the mutually exclusive TYPE_*_SCALEs. + * + * @return The type. + * + * @see #TYPE_IDENTITY + * @see #TYPE_TRANSLATION + * @see #TYPE_UNIFORM_SCALE + * @see #TYPE_GENERAL_SCALE + * @see #TYPE_QUADRANT_ROTATION + * @see #TYPE_GENERAL_ROTATION + * @see #TYPE_GENERAL_TRANSFORM + */ + public int getType() + { + return type; + } + + /** + * Return the determinant of this transform matrix. If the determinant is + * non-zero, the transform is invertible; otherwise operations which require + * an inverse throw a NoninvertibleTransformException. A result very near + * zero, due to rounding errors, may indicate that inversion results do not + * carry enough precision to be meaningful. + * + * <p>If this is a uniform scale transformation, the determinant also + * represents the squared value of the scale. Otherwise, it carries little + * additional meaning. The determinant is calculated as: + * <pre> + * | m00 m01 m02 | + * | m10 m11 m12 | = m00 * m11 - m01 * m10 + * | 0 0 1 | + * </pre> + * + * @return the determinant + * @see #createInverse() + */ + public double getDeterminant() + { + return m00 * m11 - m01 * m10; + } + + /** + * Return the matrix of values used in this transform. If the matrix has + * fewer than 6 entries, only the scale and shear factors are returned; + * otherwise the translation factors are copied as well. The resulting + * values are: + * <pre> + * [ d[0] d[2] (d[4]) ] + * [ d[1] d[3] (d[5]) ] + * [ 0 0 1 ] + * </pre> + * + * @param d the matrix to store the results into; with 4 (6) entries + * @throws NullPointerException if d is null + * @throws ArrayIndexOutOfBoundsException if d is too small + */ + public void getMatrix(double[] d) + { + d[0] = m00; + d[1] = m10; + d[2] = m01; + d[3] = m11; + if (d.length >= 6) + { + d[4] = m02; + d[5] = m12; + } + } + + /** + * Returns the X coordinate scaling factor of the matrix. + * + * @return m00 + * @see #getMatrix(double[]) + */ + public double getScaleX() + { + return m00; + } + + /** + * Returns the Y coordinate scaling factor of the matrix. + * + * @return m11 + * @see #getMatrix(double[]) + */ + public double getScaleY() + { + return m11; + } + + /** + * Returns the X coordinate shearing factor of the matrix. + * + * @return m01 + * @see #getMatrix(double[]) + */ + public double getShearX() + { + return m01; + } + + /** + * Returns the Y coordinate shearing factor of the matrix. + * + * @return m10 + * @see #getMatrix(double[]) + */ + public double getShearY() + { + return m10; + } + + /** + * Returns the X coordinate translation factor of the matrix. + * + * @return m02 + * @see #getMatrix(double[]) + */ + public double getTranslateX() + { + return m02; + } + + /** + * Returns the Y coordinate translation factor of the matrix. + * + * @return m12 + * @see #getMatrix(double[]) + */ + public double getTranslateY() + { + return m12; + } + + /** + * Concatenate a translation onto this transform. This is equivalent, but + * more efficient than + * <code>concatenate(AffineTransform.getTranslateInstance(tx, ty))</code>. + * + * @param tx the x translation distance + * @param ty the y translation distance + * @see #getTranslateInstance(double, double) + * @see #concatenate(AffineTransform) + */ + public void translate(double tx, double ty) + { + m02 += tx * m00 + ty * m01; + m12 += tx * m10 + ty * m11; + updateType(); + } + + /** + * Concatenate a rotation onto this transform. This is equivalent, but + * more efficient than + * <code>concatenate(AffineTransform.getRotateInstance(theta))</code>. + * + * @param theta the rotation angle + * @see #getRotateInstance(double) + * @see #concatenate(AffineTransform) + */ + public void rotate(double theta) + { + double c = Math.cos(theta); + double s = Math.sin(theta); + double n00 = m00 * c + m01 * s; + double n01 = m00 * -s + m01 * c; + double n10 = m10 * c + m11 * s; + double n11 = m10 * -s + m11 * c; + m00 = n00; + m01 = n01; + m10 = n10; + m11 = n11; + updateType(); + } + + /** + * Concatenate a rotation about a point onto this transform. This is + * equivalent, but more efficient than + * <code>concatenate(AffineTransform.getRotateInstance(theta, x, y))</code>. + * + * @param theta the rotation angle + * @param x the x coordinate of the pivot point + * @param y the y coordinate of the pivot point + * @see #getRotateInstance(double, double, double) + * @see #concatenate(AffineTransform) + */ + public void rotate(double theta, double x, double y) + { + translate(x, y); + rotate(theta); + translate(-x, -y); + } + + /** + * Concatenate a scale onto this transform. This is equivalent, but more + * efficient than + * <code>concatenate(AffineTransform.getScaleInstance(sx, sy))</code>. + * + * @param sx the x scaling factor + * @param sy the y scaling factor + * @see #getScaleInstance(double, double) + * @see #concatenate(AffineTransform) + */ + public void scale(double sx, double sy) + { + m00 *= sx; + m01 *= sy; + m10 *= sx; + m11 *= sy; + updateType(); + } + + /** + * Concatenate a shearing onto this transform. This is equivalent, but more + * efficient than + * <code>concatenate(AffineTransform.getShearInstance(sx, sy))</code>. + * + * @param shx the x shearing factor + * @param shy the y shearing factor + * @see #getShearInstance(double, double) + * @see #concatenate(AffineTransform) + */ + public void shear(double shx, double shy) + { + double n00 = m00 + (shy * m01); + double n01 = m01 + (shx * m00); + double n10 = m10 + (shy * m11); + double n11 = m11 + (shx * m10); + m00 = n00; + m01 = n01; + m10 = n10; + m11 = n11; + updateType(); + } + + /** + * Reset this transform to the identity (no transformation): + * <pre> + * [ 1 0 0 ] + * [ 0 1 0 ] + * [ 0 0 1 ] + * </pre> + */ + public void setToIdentity() + { + m00 = m11 = 1; + m01 = m02 = m10 = m12 = 0; + type = TYPE_IDENTITY; + } + + /** + * Set this transform to a translation: + * <pre> + * [ 1 0 tx ] + * [ 0 1 ty ] + * [ 0 0 1 ] + * </pre> + * + * @param tx the x translation distance + * @param ty the y translation distance + */ + public void setToTranslation(double tx, double ty) + { + m00 = m11 = 1; + m01 = m10 = 0; + m02 = tx; + m12 = ty; + type = (tx == 0 && ty == 0) ? TYPE_UNIFORM_SCALE : TYPE_TRANSLATION; + } + + /** + * Set this transform to a rotation. A positive angle (in radians) rotates + * the positive x-axis to the positive y-axis: + * <pre> + * [ cos(theta) -sin(theta) 0 ] + * [ sin(theta) cos(theta) 0 ] + * [ 0 0 1 ] + * </pre> + * + * @param theta the rotation angle + */ + public void setToRotation(double theta) + { + double c = Math.cos(theta); + double s = Math.sin(theta); + m00 = c; + m01 = -s; + m02 = 0; + m10 = s; + m11 = c; + m12 = 0; + type = (c == 1 ? TYPE_IDENTITY + : c == 0 || c == -1 ? TYPE_QUADRANT_ROTATION + : TYPE_GENERAL_ROTATION); + } + + /** + * Set this transform to a rotation about a point. A positive angle (in + * radians) rotates the positive x-axis to the positive y-axis. This is the + * same as calling: + * <pre> + * tx.setToTranslation(x, y); + * tx.rotate(theta); + * tx.translate(-x, -y); + * </pre> + * + * <p>The resulting matrix is: + * <pre> + * [ cos(theta) -sin(theta) x-x*cos+y*sin ] + * [ sin(theta) cos(theta) y-x*sin-y*cos ] + * [ 0 0 1 ] + * </pre> + * + * @param theta the rotation angle + * @param x the x coordinate of the pivot point + * @param y the y coordinate of the pivot point + */ + public void setToRotation(double theta, double x, double y) + { + double c = Math.cos(theta); + double s = Math.sin(theta); + m00 = c; + m01 = -s; + m02 = x - x * c + y * s; + m10 = s; + m11 = c; + m12 = y - x * s - y * c; + updateType(); + } + + /** + * Set this transform to a scale: + * <pre> + * [ sx 0 0 ] + * [ 0 sy 0 ] + * [ 0 0 1 ] + * </pre> + * + * @param sx the x scaling factor + * @param sy the y scaling factor + */ + public void setToScale(double sx, double sy) + { + m00 = sx; + m01 = m02 = m10 = m12 = 0; + m11 = sy; + type = (sx != sy ? TYPE_GENERAL_SCALE + : sx == 1 ? TYPE_IDENTITY : TYPE_UNIFORM_SCALE); + } + + /** + * Set this transform to a shear (points are shifted in the x direction based + * on a factor of their y coordinate, and in the y direction as a factor of + * their x coordinate): + * <pre> + * [ 1 shx 0 ] + * [ shy 1 0 ] + * [ 0 0 1 ] + * </pre> + * + * @param shx the x shearing factor + * @param shy the y shearing factor + */ + public void setToShear(double shx, double shy) + { + m00 = m11 = 1; + m01 = shx; + m10 = shy; + m02 = m12 = 0; + updateType(); + } + + /** + * Set this transform to a copy of the given one. + * + * @param tx the transform to copy + * @throws NullPointerException if tx is null + */ + public void setTransform(AffineTransform tx) + { + m00 = tx.m00; + m01 = tx.m01; + m02 = tx.m02; + m10 = tx.m10; + m11 = tx.m11; + m12 = tx.m12; + type = tx.type; + } + + /** + * Set this transform to the given values: + * <pre> + * [ m00 m01 m02 ] + * [ m10 m11 m12 ] + * [ 0 0 1 ] + * </pre> + * + * @param m00 the x scaling component + * @param m10 the y shearing component + * @param m01 the x shearing component + * @param m11 the y scaling component + * @param m02 the x translation component + * @param m12 the y translation component + */ + public void setTransform(double m00, double m10, double m01, + double m11, double m02, double m12) + { + this.m00 = m00; + this.m10 = m10; + this.m01 = m01; + this.m11 = m11; + this.m02 = m02; + this.m12 = m12; + updateType(); + } + + /** + * Set this transform to the result of performing the original version of + * this followed by tx. This is commonly used when chaining transformations + * from one space to another. In matrix form: + * <pre> + * [ this ] = [ this ] x [ tx ] + * </pre> + * + * @param tx the transform to concatenate + * @throws NullPointerException if tx is null + * @see #preConcatenate(AffineTransform) + */ + public void concatenate(AffineTransform tx) + { + double n00 = m00 * tx.m00 + m01 * tx.m10; + double n01 = m00 * tx.m01 + m01 * tx.m11; + double n02 = m00 * tx.m02 + m01 * tx.m12 + m02; + double n10 = m10 * tx.m00 + m11 * tx.m10; + double n11 = m10 * tx.m01 + m11 * tx.m11; + double n12 = m10 * tx.m02 + m11 * tx.m12 + m12; + m00 = n00; + m01 = n01; + m02 = n02; + m10 = n10; + m11 = n11; + m12 = n12; + updateType(); + } + + /** + * Set this transform to the result of performing tx followed by the + * original version of this. This is less common than normal concatenation, + * but can still be used to chain transformations from one space to another. + * In matrix form: + * <pre> + * [ this ] = [ tx ] x [ this ] + * </pre> + * + * @param tx the transform to concatenate + * @throws NullPointerException if tx is null + * @see #concatenate(AffineTransform) + */ + public void preConcatenate(AffineTransform tx) + { + double n00 = tx.m00 * m00 + tx.m01 * m10; + double n01 = tx.m00 * m01 + tx.m01 * m11; + double n02 = tx.m00 * m02 + tx.m01 * m12 + tx.m02; + double n10 = tx.m10 * m00 + tx.m11 * m10; + double n11 = tx.m10 * m01 + tx.m11 * m11; + double n12 = tx.m10 * m02 + tx.m11 * m12 + tx.m12; + m00 = n00; + m01 = n01; + m02 = n02; + m10 = n10; + m11 = n11; + m12 = n12; + updateType(); + } + + /** + * Returns a transform, which if concatenated to this one, will result in + * the identity transform. This is useful for undoing transformations, but + * is only possible if the original transform has an inverse (ie. does not + * map multiple points to the same line or point). A transform exists only + * if getDeterminant() has a non-zero value. + * + * The inverse is calculated as: + * + * <pre> + * + * Let A be the matrix for which we want to find the inverse: + * + * A = [ m00 m01 m02 ] + * [ m10 m11 m12 ] + * [ 0 0 1 ] + * + * + * 1 + * inverse (A) = --- x adjoint(A) + * det + * + * + * + * = 1 [ m11 -m01 m01*m12-m02*m11 ] + * --- x [ -m10 m00 -m00*m12+m10*m02 ] + * det [ 0 0 m00*m11-m10*m01 ] + * + * + * + * = [ m11/det -m01/det m01*m12-m02*m11/det ] + * [ -m10/det m00/det -m00*m12+m10*m02/det ] + * [ 0 0 1 ] + * + * + * </pre> + * + * + * + * @return a new inverse transform + * @throws NoninvertibleTransformException if inversion is not possible + * @see #getDeterminant() + */ + public AffineTransform createInverse() + throws NoninvertibleTransformException + { + double det = getDeterminant(); + if (det == 0) + throw new NoninvertibleTransformException("can't invert transform"); + + double im00 = m11 / det; + double im10 = -m10 / det; + double im01 = -m01 / det; + double im11 = m00 / det; + double im02 = (m01 * m12 - m02 * m11) / det; + double im12 = (-m00 * m12 + m10 * m02) / det; + + return new AffineTransform (im00, im10, im01, im11, im02, im12); + } + + /** + * Perform this transformation on the given source point, and store the + * result in the destination (creating it if necessary). It is safe for + * src and dst to be the same. + * + * @param src the source point + * @param dst the destination, or null + * @return the transformation of src, in dst if it was non-null + * @throws NullPointerException if src is null + */ + public Point2D transform(Point2D src, Point2D dst) + { + if (dst == null) + dst = new Point2D.Double(); + double x = src.getX(); + double y = src.getY(); + double nx = m00 * x + m01 * y + m02; + double ny = m10 * x + m11 * y + m12; + dst.setLocation(nx, ny); + return dst; + } + + /** + * Perform this transformation on an array of points, storing the results + * in another (possibly same) array. This will not create a destination + * array, but will create points for the null entries of the destination. + * The transformation is done sequentially. While having a single source + * and destination point be the same is safe, you should be aware that + * duplicate references to the same point in the source, and having the + * source overlap the destination, may result in your source points changing + * from a previous transform before it is their turn to be evaluated. + * + * @param src the array of source points + * @param srcOff the starting offset into src + * @param dst the array of destination points (may have null entries) + * @param dstOff the starting offset into dst + * @param num the number of points to transform + * @throws NullPointerException if src or dst is null, or src has null + * entries + * @throws ArrayIndexOutOfBoundsException if array bounds are exceeded + * @throws ArrayStoreException if new points are incompatible with dst + */ + public void transform(Point2D[] src, int srcOff, + Point2D[] dst, int dstOff, int num) + { + while (--num >= 0) + dst[dstOff] = transform(src[srcOff++], dst[dstOff++]); + } + + /** + * Perform this transformation on an array of points, in (x,y) pairs, + * storing the results in another (possibly same) array. This will not + * create a destination array. All sources are copied before the + * transformation, so that no result will overwrite a point that has not yet + * been evaluated. + * + * @param srcPts the array of source points + * @param srcOff the starting offset into src + * @param dstPts the array of destination points + * @param dstOff the starting offset into dst + * @param num the number of points to transform + * @throws NullPointerException if src or dst is null + * @throws ArrayIndexOutOfBoundsException if array bounds are exceeded + */ + public void transform(float[] srcPts, int srcOff, + float[] dstPts, int dstOff, int num) + { + if (srcPts == dstPts && dstOff > srcOff + && num > 1 && srcOff + 2 * num > dstOff) + { + float[] f = new float[2 * num]; + System.arraycopy(srcPts, srcOff, f, 0, 2 * num); + srcPts = f; + } + while (--num >= 0) + { + float x = srcPts[srcOff++]; + float y = srcPts[srcOff++]; + dstPts[dstOff++] = (float) (m00 * x + m01 * y + m02); + dstPts[dstOff++] = (float) (m10 * x + m11 * y + m12); + } + } + + /** + * Perform this transformation on an array of points, in (x,y) pairs, + * storing the results in another (possibly same) array. This will not + * create a destination array. All sources are copied before the + * transformation, so that no result will overwrite a point that has not yet + * been evaluated. + * + * @param srcPts the array of source points + * @param srcOff the starting offset into src + * @param dstPts the array of destination points + * @param dstOff the starting offset into dst + * @param num the number of points to transform + * @throws NullPointerException if src or dst is null + * @throws ArrayIndexOutOfBoundsException if array bounds are exceeded + */ + public void transform(double[] srcPts, int srcOff, + double[] dstPts, int dstOff, int num) + { + if (srcPts == dstPts && dstOff > srcOff + && num > 1 && srcOff + 2 * num > dstOff) + { + double[] d = new double[2 * num]; + System.arraycopy(srcPts, srcOff, d, 0, 2 * num); + srcPts = d; + } + while (--num >= 0) + { + double x = srcPts[srcOff++]; + double y = srcPts[srcOff++]; + dstPts[dstOff++] = m00 * x + m01 * y + m02; + dstPts[dstOff++] = m10 * x + m11 * y + m12; + } + } + + /** + * Perform this transformation on an array of points, in (x,y) pairs, + * storing the results in another array. This will not create a destination + * array. + * + * @param srcPts the array of source points + * @param srcOff the starting offset into src + * @param dstPts the array of destination points + * @param dstOff the starting offset into dst + * @param num the number of points to transform + * @throws NullPointerException if src or dst is null + * @throws ArrayIndexOutOfBoundsException if array bounds are exceeded + */ + public void transform(float[] srcPts, int srcOff, + double[] dstPts, int dstOff, int num) + { + while (--num >= 0) + { + float x = srcPts[srcOff++]; + float y = srcPts[srcOff++]; + dstPts[dstOff++] = m00 * x + m01 * y + m02; + dstPts[dstOff++] = m10 * x + m11 * y + m12; + } + } + + /** + * Perform this transformation on an array of points, in (x,y) pairs, + * storing the results in another array. This will not create a destination + * array. + * + * @param srcPts the array of source points + * @param srcOff the starting offset into src + * @param dstPts the array of destination points + * @param dstOff the starting offset into dst + * @param num the number of points to transform + * @throws NullPointerException if src or dst is null + * @throws ArrayIndexOutOfBoundsException if array bounds are exceeded + */ + public void transform(double[] srcPts, int srcOff, + float[] dstPts, int dstOff, int num) + { + while (--num >= 0) + { + double x = srcPts[srcOff++]; + double y = srcPts[srcOff++]; + dstPts[dstOff++] = (float) (m00 * x + m01 * y + m02); + dstPts[dstOff++] = (float) (m10 * x + m11 * y + m12); + } + } + + /** + * Perform the inverse of this transformation on the given source point, + * and store the result in the destination (creating it if necessary). It + * is safe for src and dst to be the same. + * + * @param src the source point + * @param dst the destination, or null + * @return the inverse transformation of src, in dst if it was non-null + * @throws NullPointerException if src is null + * @throws NoninvertibleTransformException if the inverse does not exist + * @see #getDeterminant() + */ + public Point2D inverseTransform(Point2D src, Point2D dst) + throws NoninvertibleTransformException + { + return createInverse().transform(src, dst); + } + + /** + * Perform the inverse of this transformation on an array of points, in + * (x,y) pairs, storing the results in another (possibly same) array. This + * will not create a destination array. All sources are copied before the + * transformation, so that no result will overwrite a point that has not yet + * been evaluated. + * + * @param srcPts the array of source points + * @param srcOff the starting offset into src + * @param dstPts the array of destination points + * @param dstOff the starting offset into dst + * @param num the number of points to transform + * @throws NullPointerException if src or dst is null + * @throws ArrayIndexOutOfBoundsException if array bounds are exceeded + * @throws NoninvertibleTransformException if the inverse does not exist + * @see #getDeterminant() + */ + public void inverseTransform(double[] srcPts, int srcOff, + double[] dstPts, int dstOff, int num) + throws NoninvertibleTransformException + { + createInverse().transform(srcPts, srcOff, dstPts, dstOff, num); + } + + /** + * Perform this transformation, less any translation, on the given source + * point, and store the result in the destination (creating it if + * necessary). It is safe for src and dst to be the same. The reduced + * transform is equivalent to: + * <pre> + * [ x' ] = [ m00 m01 ] [ x ] = [ m00 * x + m01 * y ] + * [ y' ] [ m10 m11 ] [ y ] = [ m10 * x + m11 * y ] + * </pre> + * + * @param src the source point + * @param dst the destination, or null + * @return the delta transformation of src, in dst if it was non-null + * @throws NullPointerException if src is null + */ + public Point2D deltaTransform(Point2D src, Point2D dst) + { + if (dst == null) + dst = new Point2D.Double(); + double x = src.getX(); + double y = src.getY(); + double nx = m00 * x + m01 * y; + double ny = m10 * x + m11 * y; + dst.setLocation(nx, ny); + return dst; + } + + /** + * Perform this transformation, less any translation, on an array of points, + * in (x,y) pairs, storing the results in another (possibly same) array. + * This will not create a destination array. All sources are copied before + * the transformation, so that no result will overwrite a point that has + * not yet been evaluated. The reduced transform is equivalent to: + * <pre> + * [ x' ] = [ m00 m01 ] [ x ] = [ m00 * x + m01 * y ] + * [ y' ] [ m10 m11 ] [ y ] = [ m10 * x + m11 * y ] + * </pre> + * + * @param srcPts the array of source points + * @param srcOff the starting offset into src + * @param dstPts the array of destination points + * @param dstOff the starting offset into dst + * @param num the number of points to transform + * @throws NullPointerException if src or dst is null + * @throws ArrayIndexOutOfBoundsException if array bounds are exceeded + */ + public void deltaTransform(double[] srcPts, int srcOff, + double[] dstPts, int dstOff, + int num) + { + if (srcPts == dstPts && dstOff > srcOff + && num > 1 && srcOff + 2 * num > dstOff) + { + double[] d = new double[2 * num]; + System.arraycopy(srcPts, srcOff, d, 0, 2 * num); + srcPts = d; + } + while (--num >= 0) + { + double x = srcPts[srcOff++]; + double y = srcPts[srcOff++]; + dstPts[dstOff++] = m00 * x + m01 * y; + dstPts[dstOff++] = m10 * x + m11 * y; + } + } + + /** + * Return a new Shape, based on the given one, where the path of the shape + * has been transformed by this transform. Notice that this uses GeneralPath, + * which only stores points in float precision. + * + * @param src the shape source to transform + * @return the shape, transformed by this, <code>null</code> if src is + * <code>null</code>. + * @see GeneralPath#transform(AffineTransform) + */ + public Shape createTransformedShape(Shape src) + { + if(src == null) + return null; + GeneralPath p = new GeneralPath(src); + p.transform(this); + return p; + } + + /** + * Returns a string representation of the transform, in the format: + * <code>"AffineTransform[[" + m00 + ", " + m01 + ", " + m02 + "], [" + * + m10 + ", " + m11 + ", " + m12 + "]]"</code>. + * + * @return the string representation + */ + public String toString() + { + return "AffineTransform[[" + m00 + ", " + m01 + ", " + m02 + "], [" + + m10 + ", " + m11 + ", " + m12 + "]]"; + } + + /** + * Tests if this transformation is the identity: + * <pre> + * [ 1 0 0 ] + * [ 0 1 0 ] + * [ 0 0 1 ] + * </pre> + * + * @return true if this is the identity transform + */ + public boolean isIdentity() + { + // Rather than rely on type, check explicitly. + return (m00 == 1 && m01 == 0 && m02 == 0 + && m10 == 0 && m11 == 1 && m12 == 0); + } + + /** + * Create a new transform of the same run-time type, with the same + * transforming properties as this one. + * + * @return the clone + */ + public Object clone() + { + try + { + return super.clone(); + } + catch (CloneNotSupportedException e) + { + throw (Error) new InternalError().initCause(e); // Impossible + } + } + + /** + * Return the hashcode for this transformation. The formula is not + * documented, but appears to be the same as: + * <pre> + * long l = Double.doubleToLongBits(getScaleX()); + * l = l * 31 + Double.doubleToLongBits(getShearX()); + * l = l * 31 + Double.doubleToLongBits(getTranslateX()); + * l = l * 31 + Double.doubleToLongBits(getShearY()); + * l = l * 31 + Double.doubleToLongBits(getScaleY()); + * l = l * 31 + Double.doubleToLongBits(getTranslateY()); + * return (int) ((l >> 32) ^ l); + * </pre> + * + * @return the hashcode + */ + public int hashCode() + { + long l = Double.doubleToLongBits(m00); + l = l * 31 + Double.doubleToLongBits(m01); + l = l * 31 + Double.doubleToLongBits(m02); + l = l * 31 + Double.doubleToLongBits(m10); + l = l * 31 + Double.doubleToLongBits(m11); + l = l * 31 + Double.doubleToLongBits(m12); + return (int) ((l >> 32) ^ l); + } + + /** + * Compares two transforms for equality. This returns true if they have the + * same matrix values. + * + * @param obj the transform to compare + * @return true if it is equal + */ + public boolean equals(Object obj) + { + if (! (obj instanceof AffineTransform)) + return false; + AffineTransform t = (AffineTransform) obj; + return (m00 == t.m00 && m01 == t.m01 && m02 == t.m02 + && m10 == t.m10 && m11 == t.m11 && m12 == t.m12); + } + + /** + * Helper to decode the type from the matrix. This is not guaranteed + * to find the optimal type, but at least it will be valid. + */ + private void updateType() + { + double det = getDeterminant(); + if (det == 0) + { + type = TYPE_GENERAL_TRANSFORM; + return; + } + // Scale (includes rotation by PI) or translation. + if (m01 == 0 && m10 == 0) + { + if (m00 == m11) + type = m00 == 1 ? TYPE_IDENTITY : TYPE_UNIFORM_SCALE; + else + type = TYPE_GENERAL_SCALE; + if (m02 != 0 || m12 != 0) + type |= TYPE_TRANSLATION; + } + // Rotation. + else if (m00 == m11 && m01 == -m10) + { + type = m00 == 0 ? TYPE_QUADRANT_ROTATION : TYPE_GENERAL_ROTATION; + if (det != 1) + type |= TYPE_UNIFORM_SCALE; + if (m02 != 0 || m12 != 0) + type |= TYPE_TRANSLATION; + } + else + type = TYPE_GENERAL_TRANSFORM; + } + + /** + * Reads a transform from an object stream. + * + * @param s the stream to read from + * @throws ClassNotFoundException if there is a problem deserializing + * @throws IOException if there is a problem deserializing + */ + private void readObject(ObjectInputStream s) + throws ClassNotFoundException, IOException + { + s.defaultReadObject(); + updateType(); + } +} // class AffineTransform diff --git a/libjava/classpath/java/awt/geom/Arc2D.java b/libjava/classpath/java/awt/geom/Arc2D.java new file mode 100644 index 000000000..928c5cfc8 --- /dev/null +++ b/libjava/classpath/java/awt/geom/Arc2D.java @@ -0,0 +1,1413 @@ +/* Arc2D.java -- represents an arc in 2-D space + Copyright (C) 2002, 2003, 2004 Free Software Foundation + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package java.awt.geom; + +import java.util.NoSuchElementException; + + +/** + * This class represents all arcs (segments of an ellipse in 2-D space). The + * arcs are defined by starting angle and extent (arc length) in degrees, as + * opposed to radians (like the rest of Java), and can be open, chorded, or + * wedge shaped. The angles are skewed according to the ellipse, so that 45 + * degrees always points to the upper right corner (positive x, negative y) + * of the bounding rectangle. A positive extent draws a counterclockwise arc, + * and while the angle can be any value, the path iterator only traverses the + * first 360 degrees. Storage is up to the subclasses. + * + * @author Eric Blake (ebb9@email.byu.edu) + * @author Sven de Marothy (sven@physto.se) + * @since 1.2 + */ +public abstract class Arc2D extends RectangularShape +{ + /** + * An open arc, with no segment connecting the endpoints. This type of + * arc still contains the same points as a chorded version. + */ + public static final int OPEN = 0; + + /** + * A closed arc with a single segment connecting the endpoints (a chord). + */ + public static final int CHORD = 1; + + /** + * A closed arc with two segments, one from each endpoint, meeting at the + * center of the ellipse. + */ + public static final int PIE = 2; + + /** The closure type of this arc. This is package-private to avoid an + * accessor method. */ + int type; + + /** + * Create a new arc, with the specified closure type. + * + * @param type one of {@link #OPEN}, {@link #CHORD}, or {@link #PIE}. + * @throws IllegalArgumentException if type is invalid + */ + protected Arc2D(int type) + { + if (type < OPEN || type > PIE) + throw new IllegalArgumentException(); + this.type = type; + } + + /** + * Get the starting angle of the arc in degrees. + * + * @return the starting angle + * @see #setAngleStart(double) + */ + public abstract double getAngleStart(); + + /** + * Get the extent angle of the arc in degrees. + * + * @return the extent angle + * @see #setAngleExtent(double) + */ + public abstract double getAngleExtent(); + + /** + * Return the closure type of the arc. + * + * @return the closure type + * @see #OPEN + * @see #CHORD + * @see #PIE + * @see #setArcType(int) + */ + public int getArcType() + { + return type; + } + + /** + * Returns the starting point of the arc. + * + * @return the start point + */ + public Point2D getStartPoint() + { + double angle = Math.toRadians(getAngleStart()); + double rx = getWidth() / 2; + double ry = getHeight() / 2; + double x = getX() + rx + rx * Math.cos(angle); + double y = getY() + ry - ry * Math.sin(angle); + return new Point2D.Double(x, y); + } + + /** + * Returns the ending point of the arc. + * + * @return the end point + */ + public Point2D getEndPoint() + { + double angle = Math.toRadians(getAngleStart() + getAngleExtent()); + double rx = getWidth() / 2; + double ry = getHeight() / 2; + double x = getX() + rx + rx * Math.cos(angle); + double y = getY() + ry - ry * Math.sin(angle); + return new Point2D.Double(x, y); + } + + /** + * Set the parameters of the arc. The angles are in degrees, and a positive + * extent sweeps counterclockwise (from the positive x-axis to the negative + * y-axis). + * + * @param x the new x coordinate of the upper left of the bounding box + * @param y the new y coordinate of the upper left of the bounding box + * @param w the new width of the bounding box + * @param h the new height of the bounding box + * @param start the start angle, in degrees + * @param extent the arc extent, in degrees + * @param type one of {@link #OPEN}, {@link #CHORD}, or {@link #PIE} + * @throws IllegalArgumentException if type is invalid + */ + public abstract void setArc(double x, double y, double w, double h, + double start, double extent, int type); + + /** + * Set the parameters of the arc. The angles are in degrees, and a positive + * extent sweeps counterclockwise (from the positive x-axis to the negative + * y-axis). + * + * @param p the upper left point of the bounding box + * @param d the dimensions of the bounding box + * @param start the start angle, in degrees + * @param extent the arc extent, in degrees + * @param type one of {@link #OPEN}, {@link #CHORD}, or {@link #PIE} + * @throws IllegalArgumentException if type is invalid + * @throws NullPointerException if p or d is null + */ + public void setArc(Point2D p, Dimension2D d, double start, double extent, + int type) + { + setArc(p.getX(), p.getY(), d.getWidth(), d.getHeight(), start, extent, type); + } + + /** + * Set the parameters of the arc. The angles are in degrees, and a positive + * extent sweeps counterclockwise (from the positive x-axis to the negative + * y-axis). + * + * @param r the new bounding box + * @param start the start angle, in degrees + * @param extent the arc extent, in degrees + * @param type one of {@link #OPEN}, {@link #CHORD}, or {@link #PIE} + * @throws IllegalArgumentException if type is invalid + * @throws NullPointerException if r is null + */ + public void setArc(Rectangle2D r, double start, double extent, int type) + { + setArc(r.getX(), r.getY(), r.getWidth(), r.getHeight(), start, extent, type); + } + + /** + * Set the parameters of the arc from the given one. + * + * @param a the arc to copy + * @throws NullPointerException if a is null + */ + public void setArc(Arc2D a) + { + setArc(a.getX(), a.getY(), a.getWidth(), a.getHeight(), a.getAngleStart(), + a.getAngleExtent(), a.getArcType()); + } + + /** + * Set the parameters of the arc. The angles are in degrees, and a positive + * extent sweeps counterclockwise (from the positive x-axis to the negative + * y-axis). This controls the center point and radius, so the arc will be + * circular. + * + * @param x the x coordinate of the center of the circle + * @param y the y coordinate of the center of the circle + * @param r the radius of the circle + * @param start the start angle, in degrees + * @param extent the arc extent, in degrees + * @param type one of {@link #OPEN}, {@link #CHORD}, or {@link #PIE} + * @throws IllegalArgumentException if type is invalid + */ + public void setArcByCenter(double x, double y, double r, double start, + double extent, int type) + { + setArc(x - r, y - r, r + r, r + r, start, extent, type); + } + + /** + * Sets the parameters of the arc by finding the tangents of two lines, and + * using the specified radius. The arc will be circular, will begin on the + * tangent point of the line extending from p1 to p2, and will end on the + * tangent point of the line extending from p2 to p3. + * + * XXX What happens if the points are colinear, or the radius negative? + * + * @param p1 the first point + * @param p2 the tangent line intersection point + * @param p3 the third point + * @param r the radius of the arc + * @throws NullPointerException if any point is null + */ + public void setArcByTangent(Point2D p1, Point2D p2, Point2D p3, double r) + { + if ((p2.getX() - p1.getX()) * (p3.getY() - p1.getY()) + - (p3.getX() - p1.getX()) * (p2.getY() - p1.getY()) > 0) + { + Point2D p = p3; + p3 = p1; + p1 = p; + } + + // normalized tangent vectors + double dx1 = (p1.getX() - p2.getX()) / p1.distance(p2); + double dy1 = (p1.getY() - p2.getY()) / p1.distance(p2); + double dx2 = (p2.getX() - p3.getX()) / p3.distance(p2); + double dy2 = (p2.getY() - p3.getY()) / p3.distance(p2); + double theta1 = Math.atan2(dx1, dy1); + double theta2 = Math.atan2(dx2, dy2); + + double dx = r * Math.cos(theta2) - r * Math.cos(theta1); + double dy = -r * Math.sin(theta2) + r * Math.sin(theta1); + + if (theta1 < 0) + theta1 += 2 * Math.PI; + if (theta2 < 0) + theta2 += 2 * Math.PI; + if (theta2 < theta1) + theta2 += 2 * Math.PI; + + // Vectors of the lines, not normalized, note we change + // the direction of line 2. + dx1 = p1.getX() - p2.getX(); + dy1 = p1.getY() - p2.getY(); + dx2 = p3.getX() - p2.getX(); + dy2 = p3.getY() - p2.getY(); + + // Calculate the tangent point to the second line + double t2 = -(dx1 * dy - dy1 * dx) / (dx2 * dy1 - dx1 * dy2); + double x2 = t2 * (p3.getX() - p2.getX()) + p2.getX(); + double y2 = t2 * (p3.getY() - p2.getY()) + p2.getY(); + + // calculate the center point + double x = x2 - r * Math.cos(theta2); + double y = y2 + r * Math.sin(theta2); + + setArc(x - r, y - r, 2 * r, 2 * r, Math.toDegrees(theta1), + Math.toDegrees(theta2 - theta1), getArcType()); + } + + /** + * Set the start, in degrees. + * + * @param start the new start angle + * @see #getAngleStart() + */ + public abstract void setAngleStart(double start); + + /** + * Set the extent, in degrees. + * + * @param extent the new extent angle + * @see #getAngleExtent() + */ + public abstract void setAngleExtent(double extent); + + /** + * Sets the starting angle to the angle of the given point relative to + * the center of the arc. The extent remains constant; in other words, + * this rotates the arc. + * + * @param p the new start point + * @throws NullPointerException if p is null + * @see #getStartPoint() + * @see #getAngleStart() + */ + public void setAngleStart(Point2D p) + { + // Normalize. + double x = p.getX() - (getX() + getWidth() / 2); + double y = p.getY() - (getY() + getHeight() / 2); + setAngleStart(Math.toDegrees(Math.atan2(-y, x))); + } + + /** + * Sets the starting and extent angles to those of the given points + * relative to the center of the arc. The arc will be non-empty, and will + * extend counterclockwise. + * + * @param x1 the first x coordinate + * @param y1 the first y coordinate + * @param x2 the second x coordinate + * @param y2 the second y coordinate + * @see #setAngleStart(Point2D) + */ + public void setAngles(double x1, double y1, double x2, double y2) + { + // Normalize the points. + double mx = getX(); + double my = getY(); + double mw = getWidth(); + double mh = getHeight(); + x1 = x1 - (mx + mw / 2); + y1 = y1 - (my + mh / 2); + x2 = x2 - (mx + mw / 2); + y2 = y2 - (my + mh / 2); + double start = Math.toDegrees(Math.atan2(-y1, x1)); + double extent = Math.toDegrees(Math.atan2(-y2, x2)) - start; + if (extent < 0) + extent += 360; + setAngleStart(start); + setAngleExtent(extent); + } + + /** + * Sets the starting and extent angles to those of the given points + * relative to the center of the arc. The arc will be non-empty, and will + * extend counterclockwise. + * + * @param p1 the first point + * @param p2 the second point + * @throws NullPointerException if either point is null + * @see #setAngleStart(Point2D) + */ + public void setAngles(Point2D p1, Point2D p2) + { + setAngles(p1.getX(), p1.getY(), p2.getX(), p2.getY()); + } + + /** + * Set the closure type of this arc. + * + * @param type one of {@link #OPEN}, {@link #CHORD}, or {@link #PIE} + * @throws IllegalArgumentException if type is invalid + * @see #getArcType() + */ + public void setArcType(int type) + { + if (type < OPEN || type > PIE) + throw new IllegalArgumentException(); + this.type = type; + } + + /** + * Sets the location and bounds of the ellipse of which this arc is a part. + * + * @param x the new x coordinate + * @param y the new y coordinate + * @param w the new width + * @param h the new height + * @see #getFrame() + */ + public void setFrame(double x, double y, double w, double h) + { + setArc(x, y, w, h, getAngleStart(), getAngleExtent(), type); + } + + /** + * Gets the bounds of the arc. This is much tighter than + * <code>getBounds</code>, as it takes into consideration the start and + * end angles, and the center point of a pie wedge, rather than just the + * overall ellipse. + * + * @return the bounds of the arc + * @see #getBounds() + */ + public Rectangle2D getBounds2D() + { + double extent = getAngleExtent(); + if (Math.abs(extent) >= 360) + return makeBounds(getX(), getY(), getWidth(), getHeight()); + + // Find the minimal bounding box. This determined by its extrema, + // which are the center, the endpoints of the arc, and any local + // maximum contained by the arc. + double rX = getWidth() / 2; + double rY = getHeight() / 2; + double centerX = getX() + rX; + double centerY = getY() + rY; + + Point2D p1 = getStartPoint(); + Rectangle2D result = makeBounds(p1.getX(), p1.getY(), 0, 0); + result.add(getEndPoint()); + + if (type == PIE) + result.add(centerX, centerY); + if (containsAngle(0)) + result.add(centerX + rX, centerY); + if (containsAngle(90)) + result.add(centerX, centerY - rY); + if (containsAngle(180)) + result.add(centerX - rX, centerY); + if (containsAngle(270)) + result.add(centerX, centerY + rY); + + return result; + } + + /** + * Construct a bounding box in a precision appropriate for the subclass. + * + * @param x the x coordinate + * @param y the y coordinate + * @param w the width + * @param h the height + * @return the rectangle for use in getBounds2D + */ + protected abstract Rectangle2D makeBounds(double x, double y, double w, + double h); + + /** + * Tests if the given angle, in degrees, is included in the arc. + * All angles are normalized to be between 0 and 360 degrees. + * + * @param a the angle to test + * @return true if it is contained + */ + public boolean containsAngle(double a) + { + double start = getAngleStart(); + double extent = getAngleExtent(); + double end = start + extent; + + if (extent == 0) + return false; + + if (extent >= 360 || extent <= -360) + return true; + + if (extent < 0) + { + end = start; + start += extent; + } + + start %= 360; + while (start < 0) + start += 360; + + end %= 360; + while (end < start) + end += 360; + + a %= 360; + while (a < start) + a += 360; + + return a >= start && a < end; // starting angle included, ending angle not + } + + /** + * Determines if the arc contains the given point. If the bounding box + * is empty, then this will return false. + * + * The area considered 'inside' an arc of type OPEN is the same as the + * area inside an equivalent filled CHORD-type arc. The area considered + * 'inside' a CHORD-type arc is the same as the filled area. + * + * @param x the x coordinate to test + * @param y the y coordinate to test + * @return true if the point is inside the arc + */ + public boolean contains(double x, double y) + { + double w = getWidth(); + double h = getHeight(); + double extent = getAngleExtent(); + if (w <= 0 || h <= 0 || extent == 0) + return false; + + double mx = getX() + w / 2; + double my = getY() + h / 2; + double dx = (x - mx) * 2 / w; + double dy = (y - my) * 2 / h; + if ((dx * dx + dy * dy) >= 1.0) + return false; + + double angle = Math.toDegrees(Math.atan2(-dy, dx)); + if (getArcType() == PIE) + return containsAngle(angle); + + double a1 = Math.toRadians(getAngleStart()); + double a2 = Math.toRadians(getAngleStart() + extent); + double x1 = mx + getWidth() * Math.cos(a1) / 2; + double y1 = my - getHeight() * Math.sin(a1) / 2; + double x2 = mx + getWidth() * Math.cos(a2) / 2; + double y2 = my - getHeight() * Math.sin(a2) / 2; + double sgn = ((x2 - x1) * (my - y1) - (mx - x1) * (y2 - y1)) * ((x2 - x1) * (y + - y1) - (x - x1) * (y2 - y1)); + + if (Math.abs(extent) > 180) + { + if (containsAngle(angle)) + return true; + return sgn > 0; + } + else + { + if (! containsAngle(angle)) + return false; + return sgn < 0; + } + } + + /** + * Tests if a given rectangle intersects the area of the arc. + * + * For a definition of the 'inside' area, see the contains() method. + * @see #contains(double, double) + * + * @param x the x coordinate of the rectangle + * @param y the y coordinate of the rectangle + * @param w the width of the rectangle + * @param h the height of the rectangle + * @return true if the two shapes share common points + */ + public boolean intersects(double x, double y, double w, double h) + { + double extent = getAngleExtent(); + if (extent == 0) + return false; + + if (contains(x, y) || contains(x, y + h) || contains(x + w, y) + || contains(x + w, y + h)) + return true; + + Rectangle2D rect = new Rectangle2D.Double(x, y, w, h); + + double a = getWidth() / 2.0; + double b = getHeight() / 2.0; + + double mx = getX() + a; + double my = getY() + b; + double x1 = mx + a * Math.cos(Math.toRadians(getAngleStart())); + double y1 = my - b * Math.sin(Math.toRadians(getAngleStart())); + double x2 = mx + a * Math.cos(Math.toRadians(getAngleStart() + extent)); + double y2 = my - b * Math.sin(Math.toRadians(getAngleStart() + extent)); + + if (getArcType() != CHORD) + { + // check intersections against the pie radii + if (rect.intersectsLine(mx, my, x1, y1)) + return true; + if (rect.intersectsLine(mx, my, x2, y2)) + return true; + } + else// check the chord + if (rect.intersectsLine(x1, y1, x2, y2)) + return true; + + // Check the Arc segment against the four edges + double dx; + + // Check the Arc segment against the four edges + double dy; + dy = y - my; + dx = a * Math.sqrt(1 - ((dy * dy) / (b * b))); + if (! java.lang.Double.isNaN(dx)) + { + if (mx + dx >= x && mx + dx <= x + w + && containsAngle(Math.toDegrees(Math.atan2(-dy, dx)))) + return true; + if (mx - dx >= x && mx - dx <= x + w + && containsAngle(Math.toDegrees(Math.atan2(-dy, -dx)))) + return true; + } + dy = (y + h) - my; + dx = a * Math.sqrt(1 - ((dy * dy) / (b * b))); + if (! java.lang.Double.isNaN(dx)) + { + if (mx + dx >= x && mx + dx <= x + w + && containsAngle(Math.toDegrees(Math.atan2(-dy, dx)))) + return true; + if (mx - dx >= x && mx - dx <= x + w + && containsAngle(Math.toDegrees(Math.atan2(-dy, -dx)))) + return true; + } + dx = x - mx; + dy = b * Math.sqrt(1 - ((dx * dx) / (a * a))); + if (! java.lang.Double.isNaN(dy)) + { + if (my + dy >= y && my + dy <= y + h + && containsAngle(Math.toDegrees(Math.atan2(-dy, dx)))) + return true; + if (my - dy >= y && my - dy <= y + h + && containsAngle(Math.toDegrees(Math.atan2(dy, dx)))) + return true; + } + + dx = (x + w) - mx; + dy = b * Math.sqrt(1 - ((dx * dx) / (a * a))); + if (! java.lang.Double.isNaN(dy)) + { + if (my + dy >= y && my + dy <= y + h + && containsAngle(Math.toDegrees(Math.atan2(-dy, dx)))) + return true; + if (my - dy >= y && my - dy <= y + h + && containsAngle(Math.toDegrees(Math.atan2(dy, dx)))) + return true; + } + + // Check whether the arc is contained within the box + if (rect.contains(mx, my)) + return true; + + return false; + } + + /** + * Tests if a given rectangle is contained in the area of the arc. + * + * @param x the x coordinate of the rectangle + * @param y the y coordinate of the rectangle + * @param w the width of the rectangle + * @param h the height of the rectangle + * @return true if the arc contains the rectangle + */ + public boolean contains(double x, double y, double w, double h) + { + double extent = getAngleExtent(); + if (extent == 0) + return false; + + if (! (contains(x, y) && contains(x, y + h) && contains(x + w, y) + && contains(x + w, y + h))) + return false; + + Rectangle2D rect = new Rectangle2D.Double(x, y, w, h); + + double a = getWidth() / 2.0; + double b = getHeight() / 2.0; + + double mx = getX() + a; + double my = getY() + b; + double x1 = mx + a * Math.cos(Math.toRadians(getAngleStart())); + double y1 = my - b * Math.sin(Math.toRadians(getAngleStart())); + double x2 = mx + a * Math.cos(Math.toRadians(getAngleStart() + extent)); + double y2 = my - b * Math.sin(Math.toRadians(getAngleStart() + extent)); + if (getArcType() != CHORD) + { + // check intersections against the pie radii + if (rect.intersectsLine(mx, my, x1, y1)) + return false; + + if (rect.intersectsLine(mx, my, x2, y2)) + return false; + } + else if (rect.intersectsLine(x1, y1, x2, y2)) + return false; + return true; + } + + /** + * Tests if a given rectangle is contained in the area of the arc. + * + * @param r the rectangle + * @return true if the arc contains the rectangle + */ + public boolean contains(Rectangle2D r) + { + return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + /** + * Returns an iterator over this arc, with an optional transformation. + * This iterator is threadsafe, so future modifications to the arc do not + * affect the iteration. + * + * @param at the transformation, or null + * @return a path iterator + */ + public PathIterator getPathIterator(AffineTransform at) + { + return new ArcIterator(this, at); + } + + /** + * This class is used to iterate over an arc. Since ellipses are a subclass + * of arcs, this is used by Ellipse2D as well. + * + * @author Eric Blake (ebb9@email.byu.edu) + */ + static final class ArcIterator implements PathIterator + { + /** The current iteration. */ + private int current; + + /** The last iteration. */ + private final int limit; + + /** The optional transformation. */ + private final AffineTransform xform; + + /** The x coordinate of the bounding box. */ + private final double x; + + /** The y coordinate of the bounding box. */ + private final double y; + + /** The width of the bounding box. */ + private final double w; + + /** The height of the bounding box. */ + private final double h; + + /** The start angle, in radians (not degrees). */ + private final double start; + + /** The extent angle, in radians (not degrees). */ + private final double extent; + + /** The arc closure type. */ + private final int type; + + /** + * Construct a new iterator over an arc. + * + * @param a the arc + * @param xform the transform + */ + public ArcIterator(Arc2D a, AffineTransform xform) + { + this.xform = xform; + x = a.getX(); + y = a.getY(); + w = a.getWidth(); + h = a.getHeight(); + double start = Math.toRadians(a.getAngleStart()); + double extent = Math.toRadians(a.getAngleExtent()); + + this.start = start; + this.extent = extent; + + type = a.type; + if (w < 0 || h < 0) + limit = -1; + else if (extent == 0) + limit = type; + else if (Math.abs(extent) <= Math.PI / 2.0) + limit = type + 1; + else if (Math.abs(extent) <= Math.PI) + limit = type + 2; + else if (Math.abs(extent) <= 3.0 * (Math.PI / 2.0)) + limit = type + 3; + else + limit = type + 4; + } + + /** + * Construct a new iterator over an ellipse. + * + * @param e the ellipse + * @param xform the transform + */ + public ArcIterator(Ellipse2D e, AffineTransform xform) + { + this.xform = xform; + x = e.getX(); + y = e.getY(); + w = e.getWidth(); + h = e.getHeight(); + start = 0; + extent = 2 * Math.PI; + type = CHORD; + limit = (w < 0 || h < 0) ? -1 : 5; + } + + /** + * Return the winding rule. + * + * @return {@link PathIterator#WIND_NON_ZERO} + */ + public int getWindingRule() + { + return WIND_NON_ZERO; + } + + /** + * Test if the iteration is complete. + * + * @return true if more segments exist + */ + public boolean isDone() + { + return current > limit; + } + + /** + * Advance the iterator. + */ + public void next() + { + current++; + } + + /** + * Put the current segment into the array, and return the segment type. + * + * @param coords an array of 6 elements + * @return the segment type + * @throws NullPointerException if coords is null + * @throws ArrayIndexOutOfBoundsException if coords is too small + */ + public int currentSegment(float[] coords) + { + double[] double_coords = new double[6]; + int code = currentSegment(double_coords); + for (int i = 0; i < 6; ++i) + coords[i] = (float) double_coords[i]; + return code; + } + + /** + * Put the current segment into the array, and return the segment type. + * + * @param coords an array of 6 elements + * @return the segment type + * @throws NullPointerException if coords is null + * @throws ArrayIndexOutOfBoundsException if coords is too small + */ + public int currentSegment(double[] coords) + { + double rx = w / 2; + double ry = h / 2; + double xmid = x + rx; + double ymid = y + ry; + + if (current > limit) + throw new NoSuchElementException("arc iterator out of bounds"); + + if (current == 0) + { + coords[0] = xmid + rx * Math.cos(start); + coords[1] = ymid - ry * Math.sin(start); + if (xform != null) + xform.transform(coords, 0, coords, 0, 1); + return SEG_MOVETO; + } + + if (type != OPEN && current == limit) + return SEG_CLOSE; + + if ((current == limit - 1) && (type == PIE)) + { + coords[0] = xmid; + coords[1] = ymid; + if (xform != null) + xform.transform(coords, 0, coords, 0, 1); + return SEG_LINETO; + } + + // note that this produces a cubic approximation of the arc segment, + // not a true ellipsoid. there's no ellipsoid path segment code, + // unfortunately. the cubic approximation looks about right, though. + double kappa = (Math.sqrt(2.0) - 1.0) * (4.0 / 3.0); + double quad = (Math.PI / 2.0); + + double curr_begin; + double curr_extent; + if (extent > 0) + { + curr_begin = start + (current - 1) * quad; + curr_extent = Math.min((start + extent) - curr_begin, quad); + } + else + { + curr_begin = start - (current - 1) * quad; + curr_extent = Math.max((start + extent) - curr_begin, -quad); + } + + double portion_of_a_quadrant = Math.abs(curr_extent / quad); + + double x0 = xmid + rx * Math.cos(curr_begin); + double y0 = ymid - ry * Math.sin(curr_begin); + + double x1 = xmid + rx * Math.cos(curr_begin + curr_extent); + double y1 = ymid - ry * Math.sin(curr_begin + curr_extent); + + AffineTransform trans = new AffineTransform(); + double[] cvec = new double[2]; + double len = kappa * portion_of_a_quadrant; + double angle = curr_begin; + + // in a hypothetical "first quadrant" setting, our first control + // vector would be sticking up, from [1,0] to [1,kappa]. + // + // let us recall however that in java2d, y coords are upside down + // from what one would consider "normal" first quadrant rules, so we + // will *subtract* the y value of this control vector from our first + // point. + cvec[0] = 0; + if (extent > 0) + cvec[1] = len; + else + cvec[1] = -len; + + trans.scale(rx, ry); + trans.rotate(angle); + trans.transform(cvec, 0, cvec, 0, 1); + coords[0] = x0 + cvec[0]; + coords[1] = y0 - cvec[1]; + + // control vector #2 would, ideally, be sticking out and to the + // right, in a first quadrant arc segment. again, subtraction of y. + cvec[0] = 0; + if (extent > 0) + cvec[1] = -len; + else + cvec[1] = len; + + trans.rotate(curr_extent); + trans.transform(cvec, 0, cvec, 0, 1); + coords[2] = x1 + cvec[0]; + coords[3] = y1 - cvec[1]; + + // end point + coords[4] = x1; + coords[5] = y1; + + if (xform != null) + xform.transform(coords, 0, coords, 0, 3); + + return SEG_CUBICTO; + } + } // class ArcIterator + + /** + * This class implements an arc in double precision. + * + * @author Eric Blake (ebb9@email.byu.edu) + * @since 1.2 + */ + public static class Double extends Arc2D + { + /** The x coordinate of the box bounding the ellipse of this arc. */ + public double x; + + /** The y coordinate of the box bounding the ellipse of this arc. */ + public double y; + + /** The width of the box bounding the ellipse of this arc. */ + public double width; + + /** The height of the box bounding the ellipse of this arc. */ + public double height; + + /** The start angle of this arc, in degrees. */ + public double start; + + /** The extent angle of this arc, in degrees. */ + public double extent; + + /** + * Create a new, open arc at (0,0) with 0 extent. + */ + public Double() + { + super(OPEN); + } + + /** + * Create a new arc of the given type at (0,0) with 0 extent. + * + * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} + * @throws IllegalArgumentException if type is invalid + */ + public Double(int type) + { + super(type); + } + + /** + * Create a new arc with the given dimensions. + * + * @param x the x coordinate + * @param y the y coordinate + * @param w the width + * @param h the height + * @param start the start angle, in degrees + * @param extent the extent, in degrees + * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} + * @throws IllegalArgumentException if type is invalid + */ + public Double(double x, double y, double w, double h, double start, + double extent, int type) + { + super(type); + this.x = x; + this.y = y; + width = w; + height = h; + this.start = start; + this.extent = extent; + } + + /** + * Create a new arc with the given dimensions. + * + * @param r the bounding box + * @param start the start angle, in degrees + * @param extent the extent, in degrees + * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} + * @throws IllegalArgumentException if type is invalid + * @throws NullPointerException if r is null + */ + public Double(Rectangle2D r, double start, double extent, int type) + { + super(type); + x = r.getX(); + y = r.getY(); + width = r.getWidth(); + height = r.getHeight(); + this.start = start; + this.extent = extent; + } + + /** + * Return the x coordinate of the bounding box. + * + * @return the value of x + */ + public double getX() + { + return x; + } + + /** + * Return the y coordinate of the bounding box. + * + * @return the value of y + */ + public double getY() + { + return y; + } + + /** + * Return the width of the bounding box. + * + * @return the value of width + */ + public double getWidth() + { + return width; + } + + /** + * Return the height of the bounding box. + * + * @return the value of height + */ + public double getHeight() + { + return height; + } + + /** + * Return the start angle of the arc, in degrees. + * + * @return the value of start + */ + public double getAngleStart() + { + return start; + } + + /** + * Return the extent of the arc, in degrees. + * + * @return the value of extent + */ + public double getAngleExtent() + { + return extent; + } + + /** + * Tests if the arc contains points. + * + * @return true if the arc has no interior + */ + public boolean isEmpty() + { + return width <= 0 || height <= 0; + } + + /** + * Sets the arc to the given dimensions. + * + * @param x the x coordinate + * @param y the y coordinate + * @param w the width + * @param h the height + * @param start the start angle, in degrees + * @param extent the extent, in degrees + * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} + * @throws IllegalArgumentException if type is invalid + */ + public void setArc(double x, double y, double w, double h, double start, + double extent, int type) + { + this.x = x; + this.y = y; + width = w; + height = h; + this.start = start; + this.extent = extent; + setArcType(type); + } + + /** + * Sets the start angle of the arc. + * + * @param start the new start angle + */ + public void setAngleStart(double start) + { + this.start = start; + } + + /** + * Sets the extent angle of the arc. + * + * @param extent the new extent angle + */ + public void setAngleExtent(double extent) + { + this.extent = extent; + } + + /** + * Creates a tight bounding box given dimensions that more precise than + * the bounding box of the ellipse. + * + * @param x the x coordinate + * @param y the y coordinate + * @param w the width + * @param h the height + */ + protected Rectangle2D makeBounds(double x, double y, double w, double h) + { + return new Rectangle2D.Double(x, y, w, h); + } + } // class Double + + /** + * This class implements an arc in float precision. + * + * @author Eric Blake (ebb9@email.byu.edu) + * @since 1.2 + */ + public static class Float extends Arc2D + { + /** The x coordinate of the box bounding the ellipse of this arc. */ + public float x; + + /** The y coordinate of the box bounding the ellipse of this arc. */ + public float y; + + /** The width of the box bounding the ellipse of this arc. */ + public float width; + + /** The height of the box bounding the ellipse of this arc. */ + public float height; + + /** The start angle of this arc, in degrees. */ + public float start; + + /** The extent angle of this arc, in degrees. */ + public float extent; + + /** + * Create a new, open arc at (0,0) with 0 extent. + */ + public Float() + { + super(OPEN); + } + + /** + * Create a new arc of the given type at (0,0) with 0 extent. + * + * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} + * @throws IllegalArgumentException if type is invalid + */ + public Float(int type) + { + super(type); + } + + /** + * Create a new arc with the given dimensions. + * + * @param x the x coordinate + * @param y the y coordinate + * @param w the width + * @param h the height + * @param start the start angle, in degrees + * @param extent the extent, in degrees + * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} + * @throws IllegalArgumentException if type is invalid + */ + public Float(float x, float y, float w, float h, float start, + float extent, int type) + { + super(type); + this.x = x; + this.y = y; + width = w; + height = h; + this.start = start; + this.extent = extent; + } + + /** + * Create a new arc with the given dimensions. + * + * @param r the bounding box + * @param start the start angle, in degrees + * @param extent the extent, in degrees + * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} + * @throws IllegalArgumentException if type is invalid + * @throws NullPointerException if r is null + */ + public Float(Rectangle2D r, float start, float extent, int type) + { + super(type); + x = (float) r.getX(); + y = (float) r.getY(); + width = (float) r.getWidth(); + height = (float) r.getHeight(); + this.start = start; + this.extent = extent; + } + + /** + * Return the x coordinate of the bounding box. + * + * @return the value of x + */ + public double getX() + { + return x; + } + + /** + * Return the y coordinate of the bounding box. + * + * @return the value of y + */ + public double getY() + { + return y; + } + + /** + * Return the width of the bounding box. + * + * @return the value of width + */ + public double getWidth() + { + return width; + } + + /** + * Return the height of the bounding box. + * + * @return the value of height + */ + public double getHeight() + { + return height; + } + + /** + * Return the start angle of the arc, in degrees. + * + * @return the value of start + */ + public double getAngleStart() + { + return start; + } + + /** + * Return the extent of the arc, in degrees. + * + * @return the value of extent + */ + public double getAngleExtent() + { + return extent; + } + + /** + * Tests if the arc contains points. + * + * @return true if the arc has no interior + */ + public boolean isEmpty() + { + return width <= 0 || height <= 0; + } + + /** + * Sets the arc to the given dimensions. + * + * @param x the x coordinate + * @param y the y coordinate + * @param w the width + * @param h the height + * @param start the start angle, in degrees + * @param extent the extent, in degrees + * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} + * @throws IllegalArgumentException if type is invalid + */ + public void setArc(double x, double y, double w, double h, double start, + double extent, int type) + { + this.x = (float) x; + this.y = (float) y; + width = (float) w; + height = (float) h; + this.start = (float) start; + this.extent = (float) extent; + setArcType(type); + } + + /** + * Sets the start angle of the arc. + * + * @param start the new start angle + */ + public void setAngleStart(double start) + { + this.start = (float) start; + } + + /** + * Sets the extent angle of the arc. + * + * @param extent the new extent angle + */ + public void setAngleExtent(double extent) + { + this.extent = (float) extent; + } + + /** + * Creates a tight bounding box given dimensions that more precise than + * the bounding box of the ellipse. + * + * @param x the x coordinate + * @param y the y coordinate + * @param w the width + * @param h the height + */ + protected Rectangle2D makeBounds(double x, double y, double w, double h) + { + return new Rectangle2D.Float((float) x, (float) y, (float) w, (float) h); + } + } // class Float +} // class Arc2D diff --git a/libjava/classpath/java/awt/geom/Area.java b/libjava/classpath/java/awt/geom/Area.java new file mode 100644 index 000000000..a1eaf63f3 --- /dev/null +++ b/libjava/classpath/java/awt/geom/Area.java @@ -0,0 +1,3312 @@ +/* Area.java -- represents a shape built by constructive area geometry + Copyright (C) 2002, 2004 Free Software Foundation + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package java.awt.geom; + +import java.awt.Rectangle; +import java.awt.Shape; +import java.util.Vector; + + +/** + * The Area class represents any area for the purpose of + * Constructive Area Geometry (CAG) manipulations. CAG manipulations + * work as an area-wise form of boolean logic, where the basic operations are: + * <P><li>Add (in boolean algebra: A <B>or</B> B)<BR> + * <li>Subtract (in boolean algebra: A <B>and</B> (<B>not</B> B) )<BR> + * <li>Intersect (in boolean algebra: A <B>and</B> B)<BR> + * <li>Exclusive Or <BR> + * <img src="doc-files/Area-1.png" width="342" height="302" + * alt="Illustration of CAG operations" /><BR> + * Above is an illustration of the CAG operations on two ring shapes.<P> + * + * The contains and intersects() methods are also more accurate than the + * specification of #Shape requires.<P> + * + * Please note that constructing an Area can be slow + * (Self-intersection resolving is proportional to the square of + * the number of segments).<P> + * @see #add(Area) + * @see #subtract(Area) + * @see #intersect(Area) + * @see #exclusiveOr(Area) + * + * @author Sven de Marothy (sven@physto.se) + * + * @since 1.2 + * @status Works, but could be faster and more reliable. + */ +public class Area implements Shape, Cloneable +{ + /** + * General numerical precision + */ + private static final double EPSILON = 1E-11; + + /** + * recursive subdivision epsilon - (see getRecursionDepth) + */ + private static final double RS_EPSILON = 1E-13; + + /** + * Snap distance - points within this distance are considered equal + */ + private static final double PE_EPSILON = 1E-11; + + /** + * Segment vectors containing solid areas and holes + * This is package-private to avoid an accessor method. + */ + Vector solids; + + /** + * Segment vectors containing solid areas and holes + * This is package-private to avoid an accessor method. + */ + Vector holes; + + /** + * Vector (temporary) storing curve-curve intersections + */ + private Vector cc_intersections; + + /** + * Winding rule WIND_NON_ZERO used, after construction, + * this is irrelevant. + */ + private int windingRule; + + /** + * Constructs an empty Area + */ + public Area() + { + solids = new Vector(); + holes = new Vector(); + } + + /** + * Constructs an Area from any given Shape. <P> + * + * If the Shape is self-intersecting, the created Area will consist + * of non-self-intersecting subpaths, and any inner paths which + * are found redundant in accordance with the Shape's winding rule + * will not be included. + * + * @param s the shape (<code>null</code> not permitted). + * + * @throws NullPointerException if <code>s</code> is <code>null</code>. + */ + public Area(Shape s) + { + this(); + + Vector p = makeSegment(s); + + // empty path + if (p == null) + return; + + // delete empty paths + for (int i = 0; i < p.size(); i++) + if (((Segment) p.elementAt(i)).getSignedArea() == 0.0) + p.remove(i--); + + /* + * Resolve self intersecting paths into non-intersecting + * solids and holes. + * Algorithm is as follows: + * 1: Create nodes at all self intersections + * 2: Put all segments into a list + * 3: Grab a segment, follow it, change direction at each node, + * removing segments from the list in the process + * 4: Repeat (3) until no segments remain in the list + * 5: Remove redundant paths and sort into solids and holes + */ + Vector paths = new Vector(); + Segment v; + + for (int i = 0; i < p.size(); i++) + { + Segment path = (Segment) p.elementAt(i); + createNodesSelf(path); + } + + if (p.size() > 1) + { + for (int i = 0; i < p.size() - 1; i++) + for (int j = i + 1; j < p.size(); j++) + { + Segment path1 = (Segment) p.elementAt(i); + Segment path2 = (Segment) p.elementAt(j); + createNodes(path1, path2); + } + } + + // we have intersecting points. + Vector segments = new Vector(); + + for (int i = 0; i < p.size(); i++) + { + Segment path = v = (Segment) p.elementAt(i); + do + { + segments.add(v); + v = v.next; + } + while (v != path); + } + + paths = weilerAtherton(segments); + deleteRedundantPaths(paths); + } + + /** + * Performs an add (union) operation on this area with another Area.<BR> + * @param area - the area to be unioned with this one + */ + public void add(Area area) + { + if (equals(area)) + return; + if (area.isEmpty()) + return; + + Area B = (Area) area.clone(); + + Vector pathA = new Vector(); + Vector pathB = new Vector(); + pathA.addAll(solids); + pathA.addAll(holes); + pathB.addAll(B.solids); + pathB.addAll(B.holes); + + int nNodes = 0; + + for (int i = 0; i < pathA.size(); i++) + { + Segment a = (Segment) pathA.elementAt(i); + for (int j = 0; j < pathB.size(); j++) + { + Segment b = (Segment) pathB.elementAt(j); + nNodes += createNodes(a, b); + } + } + + Vector paths = new Vector(); + Segment v; + + // we have intersecting points. + Vector segments = new Vector(); + + // In a union operation, we keep all + // segments of A oustide B and all B outside A + for (int i = 0; i < pathA.size(); i++) + { + v = (Segment) pathA.elementAt(i); + Segment path = v; + do + { + if (v.isSegmentOutside(area)) + segments.add(v); + v = v.next; + } + while (v != path); + } + + for (int i = 0; i < pathB.size(); i++) + { + v = (Segment) pathB.elementAt(i); + Segment path = v; + do + { + if (v.isSegmentOutside(this)) + segments.add(v); + v = v.next; + } + while (v != path); + } + + paths = weilerAtherton(segments); + deleteRedundantPaths(paths); + } + + /** + * Performs a subtraction operation on this Area.<BR> + * @param area the area to be subtracted from this area. + * @throws NullPointerException if <code>area</code> is <code>null</code>. + */ + public void subtract(Area area) + { + if (isEmpty() || area.isEmpty()) + return; + + if (equals(area)) + { + reset(); + return; + } + + Vector pathA = new Vector(); + Area B = (Area) area.clone(); + pathA.addAll(solids); + pathA.addAll(holes); + + // reverse the directions of B paths. + setDirection(B.holes, true); + setDirection(B.solids, false); + + Vector pathB = new Vector(); + pathB.addAll(B.solids); + pathB.addAll(B.holes); + + int nNodes = 0; + + // create nodes + for (int i = 0; i < pathA.size(); i++) + { + Segment a = (Segment) pathA.elementAt(i); + for (int j = 0; j < pathB.size(); j++) + { + Segment b = (Segment) pathB.elementAt(j); + nNodes += createNodes(a, b); + } + } + + Vector paths = new Vector(); + + // we have intersecting points. + Vector segments = new Vector(); + + // In a subtraction operation, we keep all + // segments of A oustide B and all B within A + // We outsideness-test only one segment in each path + // and the segments before and after any node + for (int i = 0; i < pathA.size(); i++) + { + Segment v = (Segment) pathA.elementAt(i); + Segment path = v; + if (v.isSegmentOutside(area) && v.node == null) + segments.add(v); + boolean node = false; + do + { + if ((v.node != null || node)) + { + node = (v.node != null); + if (v.isSegmentOutside(area)) + segments.add(v); + } + v = v.next; + } + while (v != path); + } + + for (int i = 0; i < pathB.size(); i++) + { + Segment v = (Segment) pathB.elementAt(i); + Segment path = v; + if (! v.isSegmentOutside(this) && v.node == null) + segments.add(v); + v = v.next; + boolean node = false; + do + { + if ((v.node != null || node)) + { + node = (v.node != null); + if (! v.isSegmentOutside(this)) + segments.add(v); + } + v = v.next; + } + while (v != path); + } + + paths = weilerAtherton(segments); + deleteRedundantPaths(paths); + } + + /** + * Performs an intersection operation on this Area.<BR> + * @param area - the area to be intersected with this area. + * @throws NullPointerException if <code>area</code> is <code>null</code>. + */ + public void intersect(Area area) + { + if (isEmpty() || area.isEmpty()) + { + reset(); + return; + } + if (equals(area)) + return; + + Vector pathA = new Vector(); + Area B = (Area) area.clone(); + pathA.addAll(solids); + pathA.addAll(holes); + + Vector pathB = new Vector(); + pathB.addAll(B.solids); + pathB.addAll(B.holes); + + int nNodes = 0; + + // create nodes + for (int i = 0; i < pathA.size(); i++) + { + Segment a = (Segment) pathA.elementAt(i); + for (int j = 0; j < pathB.size(); j++) + { + Segment b = (Segment) pathB.elementAt(j); + nNodes += createNodes(a, b); + } + } + + Vector paths = new Vector(); + + // we have intersecting points. + Vector segments = new Vector(); + + // In an intersection operation, we keep all + // segments of A within B and all B within A + // (The rest must be redundant) + // We outsideness-test only one segment in each path + // and the segments before and after any node + for (int i = 0; i < pathA.size(); i++) + { + Segment v = (Segment) pathA.elementAt(i); + Segment path = v; + if (! v.isSegmentOutside(area) && v.node == null) + segments.add(v); + boolean node = false; + do + { + if ((v.node != null || node)) + { + node = (v.node != null); + if (! v.isSegmentOutside(area)) + segments.add(v); + } + v = v.next; + } + while (v != path); + } + + for (int i = 0; i < pathB.size(); i++) + { + Segment v = (Segment) pathB.elementAt(i); + Segment path = v; + if (! v.isSegmentOutside(this) && v.node == null) + segments.add(v); + v = v.next; + boolean node = false; + do + { + if ((v.node != null || node)) + { + node = (v.node != null); + if (! v.isSegmentOutside(this)) + segments.add(v); + } + v = v.next; + } + while (v != path); + } + + paths = weilerAtherton(segments); + deleteRedundantPaths(paths); + } + + /** + * Performs an exclusive-or operation on this Area.<BR> + * @param area - the area to be XORed with this area. + * @throws NullPointerException if <code>area</code> is <code>null</code>. + */ + public void exclusiveOr(Area area) + { + if (area.isEmpty()) + return; + + if (isEmpty()) + { + Area B = (Area) area.clone(); + solids = B.solids; + holes = B.holes; + return; + } + if (equals(area)) + { + reset(); + return; + } + + Vector pathA = new Vector(); + + Area B = (Area) area.clone(); + Vector pathB = new Vector(); + pathA.addAll(solids); + pathA.addAll(holes); + + // reverse the directions of B paths. + setDirection(B.holes, true); + setDirection(B.solids, false); + pathB.addAll(B.solids); + pathB.addAll(B.holes); + + int nNodes = 0; + + for (int i = 0; i < pathA.size(); i++) + { + Segment a = (Segment) pathA.elementAt(i); + for (int j = 0; j < pathB.size(); j++) + { + Segment b = (Segment) pathB.elementAt(j); + nNodes += createNodes(a, b); + } + } + + Vector paths = new Vector(); + Segment v; + + // we have intersecting points. + Vector segments = new Vector(); + + // In an XOR operation, we operate on all segments + for (int i = 0; i < pathA.size(); i++) + { + v = (Segment) pathA.elementAt(i); + Segment path = v; + do + { + segments.add(v); + v = v.next; + } + while (v != path); + } + + for (int i = 0; i < pathB.size(); i++) + { + v = (Segment) pathB.elementAt(i); + Segment path = v; + do + { + segments.add(v); + v = v.next; + } + while (v != path); + } + + paths = weilerAtherton(segments); + deleteRedundantPaths(paths); + } + + /** + * Clears the Area object, creating an empty area. + */ + public void reset() + { + solids = new Vector(); + holes = new Vector(); + } + + /** + * Returns whether this area encloses any area. + * @return true if the object encloses any area. + */ + public boolean isEmpty() + { + if (solids.size() == 0) + return true; + + double totalArea = 0; + for (int i = 0; i < solids.size(); i++) + totalArea += Math.abs(((Segment) solids.elementAt(i)).getSignedArea()); + for (int i = 0; i < holes.size(); i++) + totalArea -= Math.abs(((Segment) holes.elementAt(i)).getSignedArea()); + if (totalArea <= EPSILON) + return true; + + return false; + } + + /** + * Determines whether the Area consists entirely of line segments + * @return true if the Area lines-only, false otherwise + */ + public boolean isPolygonal() + { + for (int i = 0; i < holes.size(); i++) + if (! ((Segment) holes.elementAt(i)).isPolygonal()) + return false; + for (int i = 0; i < solids.size(); i++) + if (! ((Segment) solids.elementAt(i)).isPolygonal()) + return false; + return true; + } + + /** + * Determines if the Area is rectangular.<P> + * + * This is strictly qualified. An area is considered rectangular if:<BR> + * <li>It consists of a single polygonal path.<BR> + * <li>It is oriented parallel/perpendicular to the xy axis<BR> + * <li>It must be exactly rectangular, i.e. small errors induced by + * transformations may cause a false result, although the area is + * visibly rectangular.<P> + * @return true if the above criteria are met, false otherwise + */ + public boolean isRectangular() + { + if (isEmpty()) + return true; + + if (holes.size() != 0 || solids.size() != 1) + return false; + + Segment path = (Segment) solids.elementAt(0); + if (! path.isPolygonal()) + return false; + + int nCorners = 0; + Segment s = path; + do + { + Segment s2 = s.next; + double d1 = (s.P2.getX() - s.P1.getX())*(s2.P2.getX() - s2.P1.getX())/ + ((s.P1.distance(s.P2)) * (s2.P1.distance(s2.P2))); + double d2 = (s.P2.getY() - s.P1.getY())*(s2.P2.getY() - s2.P1.getY())/ + ((s.P1.distance(s.P2)) * (s2.P1.distance(s2.P2))); + double dotproduct = d1 + d2; + + // For some reason, only rectangles on the XY axis count. + if (d1 != 0 && d2 != 0) + return false; + + if (Math.abs(dotproduct) == 0) // 90 degree angle + nCorners++; + else if ((Math.abs(1.0 - dotproduct) > 0)) // 0 degree angle? + return false; // if not, return false + + s = s.next; + } + while (s != path); + + return nCorners == 4; + } + + /** + * Returns whether the Area consists of more than one simple + * (non self-intersecting) subpath. + * + * @return true if the Area consists of none or one simple subpath, + * false otherwise. + */ + public boolean isSingular() + { + return (holes.size() == 0 && solids.size() <= 1); + } + + /** + * Returns the bounding box of the Area.<P> Unlike the CubicCurve2D and + * QuadraticCurve2D classes, this method will return the tightest possible + * bounding box, evaluating the extreme points of each curved segment.<P> + * @return the bounding box + */ + public Rectangle2D getBounds2D() + { + if (solids.size() == 0) + return new Rectangle2D.Double(0.0, 0.0, 0.0, 0.0); + + double xmin; + double xmax; + double ymin; + double ymax; + xmin = xmax = ((Segment) solids.elementAt(0)).P1.getX(); + ymin = ymax = ((Segment) solids.elementAt(0)).P1.getY(); + + for (int path = 0; path < solids.size(); path++) + { + Rectangle2D r = ((Segment) solids.elementAt(path)).getPathBounds(); + xmin = Math.min(r.getMinX(), xmin); + ymin = Math.min(r.getMinY(), ymin); + xmax = Math.max(r.getMaxX(), xmax); + ymax = Math.max(r.getMaxY(), ymax); + } + + return (new Rectangle2D.Double(xmin, ymin, (xmax - xmin), (ymax - ymin))); + } + + /** + * Returns the bounds of this object in Rectangle format. + * Please note that this may lead to loss of precision. + * + * @return The bounds. + * @see #getBounds2D() + */ + public Rectangle getBounds() + { + return getBounds2D().getBounds(); + } + + /** + * Create a new area of the same run-time type with the same contents as + * this one. + * + * @return the clone + */ + public Object clone() + { + try + { + Area clone = new Area(); + for (int i = 0; i < solids.size(); i++) + clone.solids.add(((Segment) solids.elementAt(i)).cloneSegmentList()); + for (int i = 0; i < holes.size(); i++) + clone.holes.add(((Segment) holes.elementAt(i)).cloneSegmentList()); + return clone; + } + catch (CloneNotSupportedException e) + { + throw (Error) new InternalError().initCause(e); // Impossible + } + } + + /** + * Compares two Areas. + * + * @param area the area to compare against this area (<code>null</code> + * permitted). + * @return <code>true</code> if the areas are equal, and <code>false</code> + * otherwise. + */ + public boolean equals(Area area) + { + if (area == null) + return false; + + if (! getBounds2D().equals(area.getBounds2D())) + return false; + + if (solids.size() != area.solids.size() + || holes.size() != area.holes.size()) + return false; + + Vector pathA = new Vector(); + pathA.addAll(solids); + pathA.addAll(holes); + Vector pathB = new Vector(); + pathB.addAll(area.solids); + pathB.addAll(area.holes); + + int nPaths = pathA.size(); + boolean[][] match = new boolean[2][nPaths]; + + for (int i = 0; i < nPaths; i++) + { + for (int j = 0; j < nPaths; j++) + { + Segment p1 = (Segment) pathA.elementAt(i); + Segment p2 = (Segment) pathB.elementAt(j); + if (! match[0][i] && ! match[1][j]) + if (p1.pathEquals(p2)) + match[0][i] = match[1][j] = true; + } + } + + boolean result = true; + for (int i = 0; i < nPaths; i++) + result = result && match[0][i] && match[1][i]; + return result; + } + + /** + * Transforms this area by the AffineTransform at. + * + * @param at the transform. + */ + public void transform(AffineTransform at) + { + for (int i = 0; i < solids.size(); i++) + ((Segment) solids.elementAt(i)).transformSegmentList(at); + for (int i = 0; i < holes.size(); i++) + ((Segment) holes.elementAt(i)).transformSegmentList(at); + + // Note that the orientation is not invariant under inversion + if ((at.getType() & AffineTransform.TYPE_FLIP) != 0) + { + setDirection(holes, false); + setDirection(solids, true); + } + } + + /** + * Returns a new Area equal to this one, transformed + * by the AffineTransform at. + * @param at the transform. + * @return the transformed area + * @throws NullPointerException if <code>at</code> is <code>null</code>. + */ + public Area createTransformedArea(AffineTransform at) + { + Area a = (Area) clone(); + a.transform(at); + return a; + } + + /** + * Determines if the point (x,y) is contained within this Area. + * + * @param x the x-coordinate of the point. + * @param y the y-coordinate of the point. + * @return true if the point is contained, false otherwise. + */ + public boolean contains(double x, double y) + { + int n = 0; + for (int i = 0; i < solids.size(); i++) + if (((Segment) solids.elementAt(i)).contains(x, y)) + n++; + + for (int i = 0; i < holes.size(); i++) + if (((Segment) holes.elementAt(i)).contains(x, y)) + n--; + + return (n != 0); + } + + /** + * Determines if the Point2D p is contained within this Area. + * + * @param p the point. + * @return <code>true</code> if the point is contained, <code>false</code> + * otherwise. + * @throws NullPointerException if <code>p</code> is <code>null</code>. + */ + public boolean contains(Point2D p) + { + return contains(p.getX(), p.getY()); + } + + /** + * Determines if the rectangle specified by (x,y) as the upper-left + * and with width w and height h is completely contained within this Area, + * returns false otherwise.<P> + * + * This method should always produce the correct results, unlike for other + * classes in geom. + * + * @param x the x-coordinate of the rectangle. + * @param y the y-coordinate of the rectangle. + * @param w the width of the the rectangle. + * @param h the height of the rectangle. + * @return <code>true</code> if the rectangle is considered contained + */ + public boolean contains(double x, double y, double w, double h) + { + LineSegment[] l = new LineSegment[4]; + l[0] = new LineSegment(x, y, x + w, y); + l[1] = new LineSegment(x, y + h, x + w, y + h); + l[2] = new LineSegment(x, y, x, y + h); + l[3] = new LineSegment(x + w, y, x + w, y + h); + + // Since every segment in the area must a contour + // between inside/outside segments, ANY intersection + // will mean the rectangle is not entirely contained. + for (int i = 0; i < 4; i++) + { + for (int path = 0; path < solids.size(); path++) + { + Segment v; + Segment start; + start = v = (Segment) solids.elementAt(path); + do + { + if (l[i].hasIntersections(v)) + return false; + v = v.next; + } + while (v != start); + } + for (int path = 0; path < holes.size(); path++) + { + Segment v; + Segment start; + start = v = (Segment) holes.elementAt(path); + do + { + if (l[i].hasIntersections(v)) + return false; + v = v.next; + } + while (v != start); + } + } + + // Is any point inside? + if (! contains(x, y)) + return false; + + // Final hoop: Is the rectangle non-intersecting and inside, + // but encloses a hole? + Rectangle2D r = new Rectangle2D.Double(x, y, w, h); + for (int path = 0; path < holes.size(); path++) + if (! ((Segment) holes.elementAt(path)).isSegmentOutside(r)) + return false; + + return true; + } + + /** + * Determines if the Rectangle2D specified by r is completely contained + * within this Area, returns false otherwise.<P> + * + * This method should always produce the correct results, unlike for other + * classes in geom. + * + * @param r the rectangle. + * @return <code>true</code> if the rectangle is considered contained + * + * @throws NullPointerException if <code>r</code> is <code>null</code>. + */ + public boolean contains(Rectangle2D r) + { + return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + /** + * Determines if the rectangle specified by (x,y) as the upper-left + * and with width w and height h intersects any part of this Area. + * + * @param x the x-coordinate for the rectangle. + * @param y the y-coordinate for the rectangle. + * @param w the width of the rectangle. + * @param h the height of the rectangle. + * @return <code>true</code> if the rectangle intersects the area, + * <code>false</code> otherwise. + */ + public boolean intersects(double x, double y, double w, double h) + { + if (solids.size() == 0) + return false; + + LineSegment[] l = new LineSegment[4]; + l[0] = new LineSegment(x, y, x + w, y); + l[1] = new LineSegment(x, y + h, x + w, y + h); + l[2] = new LineSegment(x, y, x, y + h); + l[3] = new LineSegment(x + w, y, x + w, y + h); + + // Return true on any intersection + for (int i = 0; i < 4; i++) + { + for (int path = 0; path < solids.size(); path++) + { + Segment v; + Segment start; + start = v = (Segment) solids.elementAt(path); + do + { + if (l[i].hasIntersections(v)) + return true; + v = v.next; + } + while (v != start); + } + for (int path = 0; path < holes.size(); path++) + { + Segment v; + Segment start; + start = v = (Segment) holes.elementAt(path); + do + { + if (l[i].hasIntersections(v)) + return true; + v = v.next; + } + while (v != start); + } + } + + // Non-intersecting, Is any point inside? + if (contains(x + w * 0.5, y + h * 0.5)) + return true; + + // What if the rectangle encloses the whole shape? + Point2D p = ((Segment) solids.elementAt(0)).getMidPoint(); + if ((new Rectangle2D.Double(x, y, w, h)).contains(p)) + return true; + return false; + } + + /** + * Determines if the Rectangle2D specified by r intersects any + * part of this Area. + * @param r the rectangle to test intersection with (<code>null</code> + * not permitted). + * @return <code>true</code> if the rectangle intersects the area, + * <code>false</code> otherwise. + * @throws NullPointerException if <code>r</code> is <code>null</code>. + */ + public boolean intersects(Rectangle2D r) + { + return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + /** + * Returns a PathIterator object defining the contour of this Area, + * transformed by at. + * + * @param at the transform. + * @return A path iterator. + */ + public PathIterator getPathIterator(AffineTransform at) + { + return (new AreaIterator(at)); + } + + /** + * Returns a flattened PathIterator object defining the contour of this + * Area, transformed by at and with a defined flatness. + * + * @param at the transform. + * @param flatness the flatness. + * @return A path iterator. + */ + public PathIterator getPathIterator(AffineTransform at, double flatness) + { + return new FlatteningPathIterator(getPathIterator(at), flatness); + } + + //--------------------------------------------------------------------- + // Non-public methods and classes + + /** + * Private pathiterator object. + */ + private class AreaIterator implements PathIterator + { + private Vector segments; + private int index; + private AffineTransform at; + + // Simple compound type for segments + class IteratorSegment + { + int type; + double[] coords; + + IteratorSegment() + { + coords = new double[6]; + } + } + + /** + * The contructor here does most of the work, + * creates a vector of IteratorSegments, which can + * readily be returned + */ + public AreaIterator(AffineTransform at) + { + this.at = at; + index = 0; + segments = new Vector(); + Vector allpaths = new Vector(); + allpaths.addAll(solids); + allpaths.addAll(holes); + + for (int i = 0; i < allpaths.size(); i++) + { + Segment v = (Segment) allpaths.elementAt(i); + Segment start = v; + + IteratorSegment is = new IteratorSegment(); + is.type = SEG_MOVETO; + is.coords[0] = start.P1.getX(); + is.coords[1] = start.P1.getY(); + segments.add(is); + + do + { + is = new IteratorSegment(); + is.type = v.pathIteratorFormat(is.coords); + segments.add(is); + v = v.next; + } + while (v != start); + + is = new IteratorSegment(); + is.type = SEG_CLOSE; + segments.add(is); + } + } + + public int currentSegment(double[] coords) + { + IteratorSegment s = (IteratorSegment) segments.elementAt(index); + if (at != null) + at.transform(s.coords, 0, coords, 0, 3); + else + for (int i = 0; i < 6; i++) + coords[i] = s.coords[i]; + return (s.type); + } + + public int currentSegment(float[] coords) + { + IteratorSegment s = (IteratorSegment) segments.elementAt(index); + double[] d = new double[6]; + if (at != null) + { + at.transform(s.coords, 0, d, 0, 3); + for (int i = 0; i < 6; i++) + coords[i] = (float) d[i]; + } + else + for (int i = 0; i < 6; i++) + coords[i] = (float) s.coords[i]; + return (s.type); + } + + // Note that the winding rule should not matter here, + // EVEN_ODD is chosen because it renders faster. + public int getWindingRule() + { + return (PathIterator.WIND_EVEN_ODD); + } + + public boolean isDone() + { + return (index >= segments.size()); + } + + public void next() + { + index++; + } + } + + /** + * Performs the fundamental task of the Weiler-Atherton algorithm, + * traverse a list of segments, for each segment: + * Follow it, removing segments from the list and switching paths + * at each node. Do so until the starting segment is reached. + * + * Returns a Vector of the resulting paths. + */ + private Vector weilerAtherton(Vector segments) + { + Vector paths = new Vector(); + while (segments.size() > 0) + { + // Iterate over the path + Segment start = (Segment) segments.elementAt(0); + Segment s = start; + do + { + segments.remove(s); + if (s.node != null) + { // switch over + s.next = s.node; + s.node = null; + } + s = s.next; // continue + } + while (s != start); + + paths.add(start); + } + return paths; + } + + /** + * A small wrapper class to store intersection points + */ + private class Intersection + { + Point2D p; // the 2D point of intersection + double ta; // the parametric value on a + double tb; // the parametric value on b + Segment seg; // segment placeholder for node setting + + public Intersection(Point2D p, double ta, double tb) + { + this.p = p; + this.ta = ta; + this.tb = tb; + } + } + + /** + * Returns the recursion depth necessary to approximate the + * curve by line segments within the error RS_EPSILON. + * + * This is done with Wang's formula: + * L0 = max{0<=i<=N-2}(|xi - 2xi+1 + xi+2|,|yi - 2yi+1 + yi+2|) + * r0 = log4(sqrt(2)*N*(N-1)*L0/8e) + * Where e is the maximum distance error (RS_EPSILON) + */ + private int getRecursionDepth(CubicSegment curve) + { + double x0 = curve.P1.getX(); + double y0 = curve.P1.getY(); + + double x1 = curve.cp1.getX(); + double y1 = curve.cp1.getY(); + + double x2 = curve.cp2.getX(); + double y2 = curve.cp2.getY(); + + double x3 = curve.P2.getX(); + double y3 = curve.P2.getY(); + + double L0 = Math.max(Math.max(Math.abs(x0 - 2 * x1 + x2), + Math.abs(x1 - 2 * x2 + x3)), + Math.max(Math.abs(y0 - 2 * y1 + y2), + Math.abs(y1 - 2 * y2 + y3))); + + double f = Math.sqrt(2) * 6.0 * L0 / (8.0 * RS_EPSILON); + + int r0 = (int) Math.ceil(Math.log(f) / Math.log(4.0)); + return (r0); + } + + /** + * Performs recursive subdivision: + * @param c1 - curve 1 + * @param c2 - curve 2 + * @param depth1 - recursion depth of curve 1 + * @param depth2 - recursion depth of curve 2 + * @param t1 - global parametric value of the first curve's starting point + * @param t2 - global parametric value of the second curve's starting point + * @param w1 - global parametric length of curve 1 + * @param w2 - global parametric length of curve 2 + * + * The final four parameters are for keeping track of the parametric + * value of the curve. For a full curve t = 0, w = 1, w is halved with + * each subdivision. + */ + private void recursiveSubdivide(CubicCurve2D c1, CubicCurve2D c2, + int depth1, int depth2, double t1, + double t2, double w1, double w2) + { + boolean flat1 = depth1 <= 0; + boolean flat2 = depth2 <= 0; + + if (flat1 && flat2) + { + double xlk = c1.getP2().getX() - c1.getP1().getX(); + double ylk = c1.getP2().getY() - c1.getP1().getY(); + + double xnm = c2.getP2().getX() - c2.getP1().getX(); + double ynm = c2.getP2().getY() - c2.getP1().getY(); + + double xmk = c2.getP1().getX() - c1.getP1().getX(); + double ymk = c2.getP1().getY() - c1.getP1().getY(); + double det = xnm * ylk - ynm * xlk; + + if (det + 1.0 == 1.0) + return; + + double detinv = 1.0 / det; + double s = (xnm * ymk - ynm * xmk) * detinv; + double t = (xlk * ymk - ylk * xmk) * detinv; + if ((s < 0.0) || (s > 1.0) || (t < 0.0) || (t > 1.0)) + return; + + double[] temp = new double[2]; + temp[0] = t1 + s * w1; + temp[1] = t2 + t * w1; + cc_intersections.add(temp); + return; + } + + CubicCurve2D.Double c11 = new CubicCurve2D.Double(); + CubicCurve2D.Double c12 = new CubicCurve2D.Double(); + CubicCurve2D.Double c21 = new CubicCurve2D.Double(); + CubicCurve2D.Double c22 = new CubicCurve2D.Double(); + + if (! flat1 && ! flat2) + { + depth1--; + depth2--; + w1 = w1 * 0.5; + w2 = w2 * 0.5; + c1.subdivide(c11, c12); + c2.subdivide(c21, c22); + if (c11.getBounds2D().intersects(c21.getBounds2D())) + recursiveSubdivide(c11, c21, depth1, depth2, t1, t2, w1, w2); + if (c11.getBounds2D().intersects(c22.getBounds2D())) + recursiveSubdivide(c11, c22, depth1, depth2, t1, t2 + w2, w1, w2); + if (c12.getBounds2D().intersects(c21.getBounds2D())) + recursiveSubdivide(c12, c21, depth1, depth2, t1 + w1, t2, w1, w2); + if (c12.getBounds2D().intersects(c22.getBounds2D())) + recursiveSubdivide(c12, c22, depth1, depth2, t1 + w1, t2 + w2, w1, w2); + return; + } + + if (! flat1) + { + depth1--; + c1.subdivide(c11, c12); + w1 = w1 * 0.5; + if (c11.getBounds2D().intersects(c2.getBounds2D())) + recursiveSubdivide(c11, c2, depth1, depth2, t1, t2, w1, w2); + if (c12.getBounds2D().intersects(c2.getBounds2D())) + recursiveSubdivide(c12, c2, depth1, depth2, t1 + w1, t2, w1, w2); + return; + } + + depth2--; + c2.subdivide(c21, c22); + w2 = w2 * 0.5; + if (c1.getBounds2D().intersects(c21.getBounds2D())) + recursiveSubdivide(c1, c21, depth1, depth2, t1, t2, w1, w2); + if (c1.getBounds2D().intersects(c22.getBounds2D())) + recursiveSubdivide(c1, c22, depth1, depth2, t1, t2 + w2, w1, w2); + } + + /** + * Returns a set of interesections between two Cubic segments + * Or null if no intersections were found. + * + * The method used to find the intersection is recursive midpoint + * subdivision. Outline description: + * + * 1) Check if the bounding boxes of the curves intersect, + * 2) If so, divide the curves in the middle and test the bounding + * boxes again, + * 3) Repeat until a maximum recursion depth has been reached, where + * the intersecting curves can be approximated by line segments. + * + * This is a reasonably accurate method, although the recursion depth + * is typically around 20, the bounding-box tests allow for significant + * pruning of the subdivision tree. + * + * This is package-private to avoid an accessor method. + */ + Intersection[] cubicCubicIntersect(CubicSegment curve1, CubicSegment curve2) + { + Rectangle2D r1 = curve1.getBounds(); + Rectangle2D r2 = curve2.getBounds(); + + if (! r1.intersects(r2)) + return null; + + cc_intersections = new Vector(); + recursiveSubdivide(curve1.getCubicCurve2D(), curve2.getCubicCurve2D(), + getRecursionDepth(curve1), getRecursionDepth(curve2), + 0.0, 0.0, 1.0, 1.0); + + if (cc_intersections.size() == 0) + return null; + + Intersection[] results = new Intersection[cc_intersections.size()]; + for (int i = 0; i < cc_intersections.size(); i++) + { + double[] temp = (double[]) cc_intersections.elementAt(i); + results[i] = new Intersection(curve1.evaluatePoint(temp[0]), temp[0], + temp[1]); + } + cc_intersections = null; + return (results); + } + + /** + * Returns the intersections between a line and a quadratic bezier + * Or null if no intersections are found1 + * This is done through combining the line's equation with the + * parametric form of the Bezier and solving the resulting quadratic. + * This is package-private to avoid an accessor method. + */ + Intersection[] lineQuadIntersect(LineSegment l, QuadSegment c) + { + double[] y = new double[3]; + double[] x = new double[3]; + double[] r = new double[3]; + int nRoots; + double x0 = c.P1.getX(); + double y0 = c.P1.getY(); + double x1 = c.cp.getX(); + double y1 = c.cp.getY(); + double x2 = c.P2.getX(); + double y2 = c.P2.getY(); + + double lx0 = l.P1.getX(); + double ly0 = l.P1.getY(); + double lx1 = l.P2.getX(); + double ly1 = l.P2.getY(); + double dx = lx1 - lx0; + double dy = ly1 - ly0; + + // form r(t) = y(t) - x(t) for the bezier + y[0] = y0; + y[1] = 2 * (y1 - y0); + y[2] = (y2 - 2 * y1 + y0); + + x[0] = x0; + x[1] = 2 * (x1 - x0); + x[2] = (x2 - 2 * x1 + x0); + + // a point, not a line + if (dy == 0 && dx == 0) + return null; + + // line on y axis + if (dx == 0 || (dy / dx) > 1.0) + { + double k = dx / dy; + x[0] -= lx0; + y[0] -= ly0; + y[0] *= k; + y[1] *= k; + y[2] *= k; + } + else + { + double k = dy / dx; + x[0] -= lx0; + y[0] -= ly0; + x[0] *= k; + x[1] *= k; + x[2] *= k; + } + + for (int i = 0; i < 3; i++) + r[i] = y[i] - x[i]; + + if ((nRoots = QuadCurve2D.solveQuadratic(r)) > 0) + { + Intersection[] temp = new Intersection[nRoots]; + int intersections = 0; + for (int i = 0; i < nRoots; i++) + { + double t = r[i]; + if (t >= 0.0 && t <= 1.0) + { + Point2D p = c.evaluatePoint(t); + + // if the line is on an axis, snap the point to that axis. + if (dx == 0) + p.setLocation(lx0, p.getY()); + if (dy == 0) + p.setLocation(p.getX(), ly0); + + if (p.getX() <= Math.max(lx0, lx1) + && p.getX() >= Math.min(lx0, lx1) + && p.getY() <= Math.max(ly0, ly1) + && p.getY() >= Math.min(ly0, ly1)) + { + double lineparameter = p.distance(l.P1) / l.P2.distance(l.P1); + temp[i] = new Intersection(p, lineparameter, t); + intersections++; + } + } + else + temp[i] = null; + } + if (intersections == 0) + return null; + + Intersection[] rValues = new Intersection[intersections]; + + for (int i = 0; i < nRoots; i++) + if (temp[i] != null) + rValues[--intersections] = temp[i]; + return (rValues); + } + return null; + } + + /** + * Returns the intersections between a line and a cubic segment + * This is done through combining the line's equation with the + * parametric form of the Bezier and solving the resulting quadratic. + * This is package-private to avoid an accessor method. + */ + Intersection[] lineCubicIntersect(LineSegment l, CubicSegment c) + { + double[] y = new double[4]; + double[] x = new double[4]; + double[] r = new double[4]; + int nRoots; + double x0 = c.P1.getX(); + double y0 = c.P1.getY(); + double x1 = c.cp1.getX(); + double y1 = c.cp1.getY(); + double x2 = c.cp2.getX(); + double y2 = c.cp2.getY(); + double x3 = c.P2.getX(); + double y3 = c.P2.getY(); + + double lx0 = l.P1.getX(); + double ly0 = l.P1.getY(); + double lx1 = l.P2.getX(); + double ly1 = l.P2.getY(); + double dx = lx1 - lx0; + double dy = ly1 - ly0; + + // form r(t) = y(t) - x(t) for the bezier + y[0] = y0; + y[1] = 3 * (y1 - y0); + y[2] = 3 * (y2 + y0 - 2 * y1); + y[3] = y3 - 3 * y2 + 3 * y1 - y0; + + x[0] = x0; + x[1] = 3 * (x1 - x0); + x[2] = 3 * (x2 + x0 - 2 * x1); + x[3] = x3 - 3 * x2 + 3 * x1 - x0; + + // a point, not a line + if (dy == 0 && dx == 0) + return null; + + // line on y axis + if (dx == 0 || (dy / dx) > 1.0) + { + double k = dx / dy; + x[0] -= lx0; + y[0] -= ly0; + y[0] *= k; + y[1] *= k; + y[2] *= k; + y[3] *= k; + } + else + { + double k = dy / dx; + x[0] -= lx0; + y[0] -= ly0; + x[0] *= k; + x[1] *= k; + x[2] *= k; + x[3] *= k; + } + for (int i = 0; i < 4; i++) + r[i] = y[i] - x[i]; + + if ((nRoots = CubicCurve2D.solveCubic(r)) > 0) + { + Intersection[] temp = new Intersection[nRoots]; + int intersections = 0; + for (int i = 0; i < nRoots; i++) + { + double t = r[i]; + if (t >= 0.0 && t <= 1.0) + { + // if the line is on an axis, snap the point to that axis. + Point2D p = c.evaluatePoint(t); + if (dx == 0) + p.setLocation(lx0, p.getY()); + if (dy == 0) + p.setLocation(p.getX(), ly0); + + if (p.getX() <= Math.max(lx0, lx1) + && p.getX() >= Math.min(lx0, lx1) + && p.getY() <= Math.max(ly0, ly1) + && p.getY() >= Math.min(ly0, ly1)) + { + double lineparameter = p.distance(l.P1) / l.P2.distance(l.P1); + temp[i] = new Intersection(p, lineparameter, t); + intersections++; + } + } + else + temp[i] = null; + } + + if (intersections == 0) + return null; + + Intersection[] rValues = new Intersection[intersections]; + for (int i = 0; i < nRoots; i++) + if (temp[i] != null) + rValues[--intersections] = temp[i]; + return (rValues); + } + return null; + } + + /** + * Returns the intersection between two lines, or null if there is no + * intersection. + * This is package-private to avoid an accessor method. + */ + Intersection linesIntersect(LineSegment a, LineSegment b) + { + Point2D P1 = a.P1; + Point2D P2 = a.P2; + Point2D P3 = b.P1; + Point2D P4 = b.P2; + + if (! Line2D.linesIntersect(P1.getX(), P1.getY(), P2.getX(), P2.getY(), + P3.getX(), P3.getY(), P4.getX(), P4.getY())) + return null; + + double x1 = P1.getX(); + double y1 = P1.getY(); + double rx = P2.getX() - x1; + double ry = P2.getY() - y1; + + double x2 = P3.getX(); + double y2 = P3.getY(); + double sx = P4.getX() - x2; + double sy = P4.getY() - y2; + + double determinant = sx * ry - sy * rx; + double nom = (sx * (y2 - y1) + sy * (x1 - x2)); + + // Parallel lines don't intersect. At least we pretend they don't. + if (Math.abs(determinant) < EPSILON) + return null; + + nom = nom / determinant; + + if (nom == 0.0) + return null; + if (nom == 1.0) + return null; + + Point2D p = new Point2D.Double(x1 + nom * rx, y1 + nom * ry); + + return new Intersection(p, p.distance(P1) / P1.distance(P2), + p.distance(P3) / P3.distance(P4)); + } + + /** + * Determines if two points are equal, within an error margin + * 'snap distance' + * This is package-private to avoid an accessor method. + */ + boolean pointEquals(Point2D a, Point2D b) + { + return (a.equals(b) || a.distance(b) < PE_EPSILON); + } + + /** + * Helper method + * Turns a shape into a Vector of Segments + */ + private Vector makeSegment(Shape s) + { + Vector paths = new Vector(); + PathIterator pi = s.getPathIterator(null); + double[] coords = new double[6]; + Segment subpath = null; + Segment current = null; + double cx; + double cy; + double subpathx; + double subpathy; + cx = cy = subpathx = subpathy = 0.0; + + this.windingRule = pi.getWindingRule(); + + while (! pi.isDone()) + { + Segment v; + switch (pi.currentSegment(coords)) + { + case PathIterator.SEG_MOVETO: + if (subpath != null) + { // close existing open path + current.next = new LineSegment(cx, cy, subpathx, subpathy); + current = current.next; + current.next = subpath; + } + subpath = null; + subpathx = cx = coords[0]; + subpathy = cy = coords[1]; + break; + + // replace 'close' with a line-to. + case PathIterator.SEG_CLOSE: + if (subpath != null && (subpathx != cx || subpathy != cy)) + { + current.next = new LineSegment(cx, cy, subpathx, subpathy); + current = current.next; + current.next = subpath; + cx = subpathx; + cy = subpathy; + subpath = null; + } + else if (subpath != null) + { + current.next = subpath; + subpath = null; + } + break; + case PathIterator.SEG_LINETO: + if (cx != coords[0] || cy != coords[1]) + { + v = new LineSegment(cx, cy, coords[0], coords[1]); + if (subpath == null) + { + subpath = current = v; + paths.add(subpath); + } + else + { + current.next = v; + current = current.next; + } + cx = coords[0]; + cy = coords[1]; + } + break; + case PathIterator.SEG_QUADTO: + v = new QuadSegment(cx, cy, coords[0], coords[1], coords[2], + coords[3]); + if (subpath == null) + { + subpath = current = v; + paths.add(subpath); + } + else + { + current.next = v; + current = current.next; + } + cx = coords[2]; + cy = coords[3]; + break; + case PathIterator.SEG_CUBICTO: + v = new CubicSegment(cx, cy, coords[0], coords[1], coords[2], + coords[3], coords[4], coords[5]); + if (subpath == null) + { + subpath = current = v; + paths.add(subpath); + } + else + { + current.next = v; + current = current.next; + } + + // check if the cubic is self-intersecting + double[] lpts = ((CubicSegment) v).getLoop(); + if (lpts != null) + { + // if it is, break off the loop into its own path. + v.subdivideInsert(lpts[0]); + v.next.subdivideInsert((lpts[1] - lpts[0]) / (1.0 - lpts[0])); + + CubicSegment loop = (CubicSegment) v.next; + v.next = loop.next; + loop.next = loop; + + v.P2 = v.next.P1 = loop.P2 = loop.P1; // snap points + paths.add(loop); + current = v.next; + } + + cx = coords[4]; + cy = coords[5]; + break; + } + pi.next(); + } + + if (subpath != null) + { // close any open path + if (subpathx != cx || subpathy != cy) + { + current.next = new LineSegment(cx, cy, subpathx, subpathy); + current = current.next; + current.next = subpath; + } + else + current.next = subpath; + } + + if (paths.size() == 0) + return (null); + + return (paths); + } + + /** + * Find the intersections of two separate closed paths, + * A and B, split the segments at the intersection points, + * and create nodes pointing from one to the other + */ + private int createNodes(Segment A, Segment B) + { + int nNodes = 0; + + Segment a = A; + Segment b = B; + + do + { + do + { + nNodes += a.splitIntersections(b); + b = b.next; + } + while (b != B); + + a = a.next; // move to the next segment + } + while (a != A); // until one wrap. + + return (nNodes); + } + + /** + * Find the intersections of a path with itself. + * Splits the segments at the intersection points, + * and create nodes pointing from one to the other. + */ + private int createNodesSelf(Segment A) + { + int nNodes = 0; + Segment a = A; + + if (A.next == A) + return 0; + + do + { + Segment b = a.next; + do + { + if (b != a) // necessary + nNodes += a.splitIntersections(b); + b = b.next; + } + while (b != A); + a = a.next; // move to the next segment + } + while (a != A); // until one wrap. + + return (nNodes); + } + + /** + * Deletes paths which are redundant from a list, (i.e. solid areas within + * solid areas) Clears any nodes. Sorts the remaining paths into solids + * and holes, sets their orientation and sets the solids and holes lists. + */ + private void deleteRedundantPaths(Vector paths) + { + int npaths = paths.size(); + + int[][] contains = new int[npaths][npaths]; + int[][] windingNumbers = new int[npaths][2]; + int neg; + Rectangle2D[] bb = new Rectangle2D[npaths]; // path bounding boxes + + neg = ((windingRule == PathIterator.WIND_NON_ZERO) ? -1 : 1); + + for (int i = 0; i < npaths; i++) + bb[i] = ((Segment) paths.elementAt(i)).getPathBounds(); + + // Find which path contains which, assign winding numbers + for (int i = 0; i < npaths; i++) + { + Segment pathA = (Segment) paths.elementAt(i); + pathA.nullNodes(); // remove any now-redundant nodes, in case. + int windingA = pathA.hasClockwiseOrientation() ? 1 : neg; + + for (int j = 0; j < npaths; j++) + if (i != j) + { + Segment pathB = (Segment) paths.elementAt(j); + + // A contains B + if (bb[i].intersects(bb[j])) + { + Segment s = pathB.next; + while (s.P1.getY() == s.P2.getY() && s != pathB) + s = s.next; + Point2D p = s.getMidPoint(); + if (pathA.contains(p.getX(), p.getY())) + contains[i][j] = windingA; + } + else + // A does not contain B + contains[i][j] = 0; + } + else + contains[i][j] = windingA; // i == j + } + + for (int i = 0; i < npaths; i++) + { + windingNumbers[i][0] = 0; + for (int j = 0; j < npaths; j++) + windingNumbers[i][0] += contains[j][i]; + windingNumbers[i][1] = contains[i][i]; + } + + Vector solids = new Vector(); + Vector holes = new Vector(); + + if (windingRule == PathIterator.WIND_NON_ZERO) + { + for (int i = 0; i < npaths; i++) + { + if (windingNumbers[i][0] == 0) + holes.add(paths.elementAt(i)); + else if (windingNumbers[i][0] - windingNumbers[i][1] == 0 + && Math.abs(windingNumbers[i][0]) == 1) + solids.add(paths.elementAt(i)); + } + } + else + { + windingRule = PathIterator.WIND_NON_ZERO; + for (int i = 0; i < npaths; i++) + { + if ((windingNumbers[i][0] & 1) == 0) + holes.add(paths.elementAt(i)); + else if ((windingNumbers[i][0] & 1) == 1) + solids.add(paths.elementAt(i)); + } + } + + setDirection(holes, false); + setDirection(solids, true); + this.holes = holes; + this.solids = solids; + } + + /** + * Sets the winding direction of a Vector of paths + * @param clockwise gives the direction, + * true = clockwise, false = counter-clockwise + */ + private void setDirection(Vector paths, boolean clockwise) + { + Segment v; + for (int i = 0; i < paths.size(); i++) + { + v = (Segment) paths.elementAt(i); + if (clockwise != v.hasClockwiseOrientation()) + v.reverseAll(); + } + } + + /** + * Class representing a linked-list of vertices forming a closed polygon, + * convex or concave, without holes. + */ + private abstract class Segment implements Cloneable + { + // segment type, PathIterator segment types are used. + Point2D P1; + Point2D P2; + Segment next; + Segment node; + + Segment() + { + P1 = P2 = null; + node = next = null; + } + + /** + * Reverses the direction of a single segment + */ + abstract void reverseCoords(); + + /** + * Returns the segment's midpoint + */ + abstract Point2D getMidPoint(); + + /** + * Returns the bounding box of this segment + */ + abstract Rectangle2D getBounds(); + + /** + * Transforms a single segment + */ + abstract void transform(AffineTransform at); + + /** + * Returns the PathIterator type of a segment + */ + abstract int getType(); + + /** + */ + abstract int splitIntersections(Segment b); + + /** + * Returns the PathIterator coords of a segment + */ + abstract int pathIteratorFormat(double[] coords); + + /** + * Returns the number of intersections on the positive X axis, + * with the origin at (x,y), used for contains()-testing + * + * (Although that could be done by the line-intersect methods, + * a dedicated method is better to guarantee consitent handling + * of endpoint-special-cases) + */ + abstract int rayCrossing(double x, double y); + + /** + * Subdivides the segment at parametric value t, inserting + * the new segment into the linked list after this, + * such that this becomes [0,t] and this.next becomes [t,1] + */ + abstract void subdivideInsert(double t); + + /** + * Returns twice the area of a curve, relative the P1-P2 line + * Used for area calculations. + */ + abstract double curveArea(); + + /** + * Compare two segments. + */ + abstract boolean equals(Segment b); + + /** + * Determines if this path of segments contains the point (x,y) + */ + boolean contains(double x, double y) + { + Segment v = this; + int crossings = 0; + do + { + int n = v.rayCrossing(x, y); + crossings += n; + v = v.next; + } + while (v != this); + return ((crossings & 1) == 1); + } + + /** + * Nulls all nodes of the path. Clean up any 'hairs'. + */ + void nullNodes() + { + Segment v = this; + do + { + v.node = null; + v = v.next; + } + while (v != this); + } + + /** + * Transforms each segment in the closed path + */ + void transformSegmentList(AffineTransform at) + { + Segment v = this; + do + { + v.transform(at); + v = v.next; + } + while (v != this); + } + + /** + * Determines the winding direction of the path + * By the sign of the area. + */ + boolean hasClockwiseOrientation() + { + return (getSignedArea() > 0.0); + } + + /** + * Returns the bounds of this path + */ + public Rectangle2D getPathBounds() + { + double xmin; + double xmax; + double ymin; + double ymax; + xmin = xmax = P1.getX(); + ymin = ymax = P1.getY(); + + Segment v = this; + do + { + Rectangle2D r = v.getBounds(); + xmin = Math.min(r.getMinX(), xmin); + ymin = Math.min(r.getMinY(), ymin); + xmax = Math.max(r.getMaxX(), xmax); + ymax = Math.max(r.getMaxY(), ymax); + v = v.next; + } + while (v != this); + + return (new Rectangle2D.Double(xmin, ymin, (xmax - xmin), (ymax - ymin))); + } + + /** + * Calculates twice the signed area of the path; + */ + double getSignedArea() + { + Segment s; + double area = 0.0; + + s = this; + do + { + area += s.curveArea(); + + area += s.P1.getX() * s.next.P1.getY() + - s.P1.getY() * s.next.P1.getX(); + s = s.next; + } + while (s != this); + + return area; + } + + /** + * Reverses the orientation of the whole polygon + */ + void reverseAll() + { + reverseCoords(); + Segment v = next; + Segment former = this; + while (v != this) + { + v.reverseCoords(); + Segment vnext = v.next; + v.next = former; + former = v; + v = vnext; + } + next = former; + } + + /** + * Inserts a Segment after this one + */ + void insert(Segment v) + { + Segment n = next; + next = v; + v.next = n; + } + + /** + * Returns if this segment path is polygonal + */ + boolean isPolygonal() + { + Segment v = this; + do + { + if (! (v instanceof LineSegment)) + return false; + v = v.next; + } + while (v != this); + return true; + } + + /** + * Clones this path + */ + Segment cloneSegmentList() throws CloneNotSupportedException + { + Vector list = new Vector(); + Segment v = next; + + while (v != this) + { + list.add(v); + v = v.next; + } + + Segment clone = (Segment) this.clone(); + v = clone; + for (int i = 0; i < list.size(); i++) + { + clone.next = (Segment) ((Segment) list.elementAt(i)).clone(); + clone = clone.next; + } + clone.next = v; + return v; + } + + /** + * Creates a node between this segment and segment b + * at the given intersection + * @return the number of nodes created (0 or 1) + */ + int createNode(Segment b, Intersection i) + { + Point2D p = i.p; + if ((pointEquals(P1, p) || pointEquals(P2, p)) + && (pointEquals(b.P1, p) || pointEquals(b.P2, p))) + return 0; + + subdivideInsert(i.ta); + b.subdivideInsert(i.tb); + + // snap points + b.P2 = b.next.P1 = P2 = next.P1 = i.p; + + node = b.next; + b.node = next; + return 1; + } + + /** + * Creates multiple nodes from a list of intersections, + * This must be done in the order of ascending parameters, + * and the parameters must be recalculated in accordance + * with each split. + * @return the number of nodes created + */ + protected int createNodes(Segment b, Intersection[] x) + { + Vector v = new Vector(); + for (int i = 0; i < x.length; i++) + { + Point2D p = x[i].p; + if (! ((pointEquals(P1, p) || pointEquals(P2, p)) + && (pointEquals(b.P1, p) || pointEquals(b.P2, p)))) + v.add(x[i]); + } + + int nNodes = v.size(); + Intersection[] A = new Intersection[nNodes]; + Intersection[] B = new Intersection[nNodes]; + for (int i = 0; i < nNodes; i++) + A[i] = B[i] = (Intersection) v.elementAt(i); + + // Create two lists sorted by the parameter + // Bubble sort, OK I suppose, since the number of intersections + // cannot be larger than 9 (cubic-cubic worst case) anyway + for (int i = 0; i < nNodes - 1; i++) + { + for (int j = i + 1; j < nNodes; j++) + { + if (A[i].ta > A[j].ta) + { + Intersection swap = A[i]; + A[i] = A[j]; + A[j] = swap; + } + if (B[i].tb > B[j].tb) + { + Intersection swap = B[i]; + B[i] = B[j]; + B[j] = swap; + } + } + } + // subdivide a + Segment s = this; + for (int i = 0; i < nNodes; i++) + { + s.subdivideInsert(A[i].ta); + + // renormalize the parameters + for (int j = i + 1; j < nNodes; j++) + A[j].ta = (A[j].ta - A[i].ta) / (1.0 - A[i].ta); + + A[i].seg = s; + s = s.next; + } + + // subdivide b, set nodes + s = b; + for (int i = 0; i < nNodes; i++) + { + s.subdivideInsert(B[i].tb); + + for (int j = i + 1; j < nNodes; j++) + B[j].tb = (B[j].tb - B[i].tb) / (1.0 - B[i].tb); + + // set nodes + B[i].seg.node = s.next; // node a -> b + s.node = B[i].seg.next; // node b -> a + + // snap points + B[i].seg.P2 = B[i].seg.next.P1 = s.P2 = s.next.P1 = B[i].p; + s = s.next; + } + return nNodes; + } + + /** + * Determines if two paths are equal. + * Colinear line segments are ignored in the comparison. + */ + boolean pathEquals(Segment B) + { + if (! getPathBounds().equals(B.getPathBounds())) + return false; + + Segment startA = getTopLeft(); + Segment startB = B.getTopLeft(); + Segment a = startA; + Segment b = startB; + do + { + if (! a.equals(b)) + return false; + + if (a instanceof LineSegment) + a = ((LineSegment) a).lastCoLinear(); + if (b instanceof LineSegment) + b = ((LineSegment) b).lastCoLinear(); + + a = a.next; + b = b.next; + } + while (a != startA && b != startB); + return true; + } + + /** + * Return the segment with the top-leftmost first point + */ + Segment getTopLeft() + { + Segment v = this; + Segment tl = this; + do + { + if (v.P1.getY() < tl.P1.getY()) + tl = v; + else if (v.P1.getY() == tl.P1.getY()) + { + if (v.P1.getX() < tl.P1.getX()) + tl = v; + } + v = v.next; + } + while (v != this); + return tl; + } + + /** + * Returns if the path has a segment outside a shape + */ + boolean isSegmentOutside(Shape shape) + { + return ! shape.contains(getMidPoint()); + } + } // class Segment + + private class LineSegment extends Segment + { + public LineSegment(double x1, double y1, double x2, double y2) + { + super(); + P1 = new Point2D.Double(x1, y1); + P2 = new Point2D.Double(x2, y2); + } + + public LineSegment(Point2D p1, Point2D p2) + { + super(); + P1 = (Point2D) p1.clone(); + P2 = (Point2D) p2.clone(); + } + + /** + * Clones this segment + */ + public Object clone() + { + return new LineSegment(P1, P2); + } + + /** + * Transforms the segment + */ + void transform(AffineTransform at) + { + P1 = at.transform(P1, null); + P2 = at.transform(P2, null); + } + + /** + * Swap start and end points + */ + void reverseCoords() + { + Point2D p = P1; + P1 = P2; + P2 = p; + } + + /** + * Returns the segment's midpoint + */ + Point2D getMidPoint() + { + return (new Point2D.Double(0.5 * (P1.getX() + P2.getX()), + 0.5 * (P1.getY() + P2.getY()))); + } + + /** + * Returns twice the area of a curve, relative the P1-P2 line + * Obviously, a line does not enclose any area besides the line + */ + double curveArea() + { + return 0; + } + + /** + * Returns the PathIterator type of a segment + */ + int getType() + { + return (PathIterator.SEG_LINETO); + } + + /** + * Subdivides the segment at parametric value t, inserting + * the new segment into the linked list after this, + * such that this becomes [0,t] and this.next becomes [t,1] + */ + void subdivideInsert(double t) + { + Point2D p = new Point2D.Double((P2.getX() - P1.getX()) * t + P1.getX(), + (P2.getY() - P1.getY()) * t + P1.getY()); + insert(new LineSegment(p, P2)); + P2 = p; + next.node = node; + node = null; + } + + /** + * Determines if two line segments are strictly colinear + */ + boolean isCoLinear(LineSegment b) + { + double x1 = P1.getX(); + double y1 = P1.getY(); + double x2 = P2.getX(); + double y2 = P2.getY(); + double x3 = b.P1.getX(); + double y3 = b.P1.getY(); + double x4 = b.P2.getX(); + double y4 = b.P2.getY(); + + if ((y1 - y3) * (x4 - x3) - (x1 - x3) * (y4 - y3) != 0.0) + return false; + + return ((x2 - x1) * (y4 - y3) - (y2 - y1) * (x4 - x3) == 0.0); + } + + /** + * Return the last segment colinear with this one. + * Used in comparing paths. + */ + Segment lastCoLinear() + { + Segment prev = this; + Segment v = next; + + while (v instanceof LineSegment) + { + if (isCoLinear((LineSegment) v)) + { + prev = v; + v = v.next; + } + else + return prev; + } + return prev; + } + + /** + * Compare two segments. + * We must take into account that the lines may be broken into colinear + * subsegments and ignore them. + */ + boolean equals(Segment b) + { + if (! (b instanceof LineSegment)) + return false; + Point2D p1 = P1; + Point2D p3 = b.P1; + + if (! p1.equals(p3)) + return false; + + Point2D p2 = lastCoLinear().P2; + Point2D p4 = ((LineSegment) b).lastCoLinear().P2; + return (p2.equals(p4)); + } + + /** + * Returns a line segment + */ + int pathIteratorFormat(double[] coords) + { + coords[0] = P2.getX(); + coords[1] = P2.getY(); + return (PathIterator.SEG_LINETO); + } + + /** + * Returns if the line has intersections. + */ + boolean hasIntersections(Segment b) + { + if (b instanceof LineSegment) + return (linesIntersect(this, (LineSegment) b) != null); + + if (b instanceof QuadSegment) + return (lineQuadIntersect(this, (QuadSegment) b) != null); + + if (b instanceof CubicSegment) + return (lineCubicIntersect(this, (CubicSegment) b) != null); + + return false; + } + + /** + * Splits intersections into nodes, + * This one handles line-line, line-quadratic, line-cubic + */ + int splitIntersections(Segment b) + { + if (b instanceof LineSegment) + { + Intersection i = linesIntersect(this, (LineSegment) b); + + if (i == null) + return 0; + + return createNode(b, i); + } + + Intersection[] x = null; + + if (b instanceof QuadSegment) + x = lineQuadIntersect(this, (QuadSegment) b); + + if (b instanceof CubicSegment) + x = lineCubicIntersect(this, (CubicSegment) b); + + if (x == null) + return 0; + + if (x.length == 1) + return createNode(b, (Intersection) x[0]); + + return createNodes(b, x); + } + + /** + * Returns the bounding box of this segment + */ + Rectangle2D getBounds() + { + return (new Rectangle2D.Double(Math.min(P1.getX(), P2.getX()), + Math.min(P1.getY(), P2.getY()), + Math.abs(P1.getX() - P2.getX()), + Math.abs(P1.getY() - P2.getY()))); + } + + /** + * Returns the number of intersections on the positive X axis, + * with the origin at (x,y), used for contains()-testing + */ + int rayCrossing(double x, double y) + { + double x0 = P1.getX() - x; + double y0 = P1.getY() - y; + double x1 = P2.getX() - x; + double y1 = P2.getY() - y; + + if (y0 * y1 > 0) + return 0; + + if (x0 < 0 && x1 < 0) + return 0; + + if (y0 == 0.0) + y0 -= EPSILON; + + if (y1 == 0.0) + y1 -= EPSILON; + + if (Line2D.linesIntersect(x0, y0, x1, y1, + EPSILON, 0.0, Double.MAX_VALUE, 0.0)) + return 1; + return 0; + } + } // class LineSegment + + /** + * Quadratic Bezier curve segment + * + * Note: Most peers don't support quadratics directly, so it might make + * sense to represent them as cubics internally and just be done with it. + * I think we should be peer-agnostic, however, and stay faithful to the + * input geometry types as far as possible. + */ + private class QuadSegment extends Segment + { + Point2D cp; // control point + + /** + * Constructor, takes the coordinates of the start, control, + * and end point, respectively. + */ + QuadSegment(double x1, double y1, double cx, double cy, double x2, + double y2) + { + super(); + P1 = new Point2D.Double(x1, y1); + P2 = new Point2D.Double(x2, y2); + cp = new Point2D.Double(cx, cy); + } + + /** + * Clones this segment + */ + public Object clone() + { + return new QuadSegment(P1.getX(), P1.getY(), cp.getX(), cp.getY(), + P2.getX(), P2.getY()); + } + + /** + * Returns twice the area of a curve, relative the P1-P2 line + * + * The area formula can be derived by using Green's formula in the + * plane on the parametric form of the bezier. + */ + double curveArea() + { + double x0 = P1.getX(); + double y0 = P1.getY(); + double x1 = cp.getX(); + double y1 = cp.getY(); + double x2 = P2.getX(); + double y2 = P2.getY(); + + double P = (y2 - 2 * y1 + y0); + double Q = 2 * (y1 - y0); + + double A = (x2 - 2 * x1 + x0); + double B = 2 * (x1 - x0); + + double area = (B * P - A * Q) / 3.0; + return (area); + } + + /** + * Compare two segments. + */ + boolean equals(Segment b) + { + if (! (b instanceof QuadSegment)) + return false; + + return (P1.equals(b.P1) && cp.equals(((QuadSegment) b).cp) + && P2.equals(b.P2)); + } + + /** + * Returns a Point2D corresponding to the parametric value t + * of the curve + */ + Point2D evaluatePoint(double t) + { + double x0 = P1.getX(); + double y0 = P1.getY(); + double x1 = cp.getX(); + double y1 = cp.getY(); + double x2 = P2.getX(); + double y2 = P2.getY(); + + return new Point2D.Double(t * t * (x2 - 2 * x1 + x0) + 2 * t * (x1 - x0) + + x0, + t * t * (y2 - 2 * y1 + y0) + 2 * t * (y1 - y0) + + y0); + } + + /** + * Returns the bounding box of this segment + */ + Rectangle2D getBounds() + { + double x0 = P1.getX(); + double y0 = P1.getY(); + double x1 = cp.getX(); + double y1 = cp.getY(); + double x2 = P2.getX(); + double y2 = P2.getY(); + double r0; + double r1; + + double xmax = Math.max(x0, x2); + double ymax = Math.max(y0, y2); + double xmin = Math.min(x0, x2); + double ymin = Math.min(y0, y2); + + r0 = 2 * (y1 - y0); + r1 = 2 * (y2 - 2 * y1 + y0); + if (r1 != 0.0) + { + double t = -r0 / r1; + if (t > 0.0 && t < 1.0) + { + double y = evaluatePoint(t).getY(); + ymax = Math.max(y, ymax); + ymin = Math.min(y, ymin); + } + } + r0 = 2 * (x1 - x0); + r1 = 2 * (x2 - 2 * x1 + x0); + if (r1 != 0.0) + { + double t = -r0 / r1; + if (t > 0.0 && t < 1.0) + { + double x = evaluatePoint(t).getY(); + xmax = Math.max(x, xmax); + xmin = Math.min(x, xmin); + } + } + + return (new Rectangle2D.Double(xmin, ymin, xmax - xmin, ymax - ymin)); + } + + /** + * Returns a cubic segment corresponding to this curve + */ + CubicSegment getCubicSegment() + { + double x1 = P1.getX() + 2.0 * (cp.getX() - P1.getX()) / 3.0; + double y1 = P1.getY() + 2.0 * (cp.getY() - P1.getY()) / 3.0; + double x2 = cp.getX() + (P2.getX() - cp.getX()) / 3.0; + double y2 = cp.getY() + (P2.getY() - cp.getY()) / 3.0; + + return new CubicSegment(P1.getX(), P1.getY(), x1, y1, x2, y2, P2.getX(), + P2.getY()); + } + + /** + * Returns the segment's midpoint + */ + Point2D getMidPoint() + { + return evaluatePoint(0.5); + } + + /** + * Returns the PathIterator type of a segment + */ + int getType() + { + return (PathIterator.SEG_QUADTO); + } + + /** + * Returns the PathIterator coords of a segment + */ + int pathIteratorFormat(double[] coords) + { + coords[0] = cp.getX(); + coords[1] = cp.getY(); + coords[2] = P2.getX(); + coords[3] = P2.getY(); + return (PathIterator.SEG_QUADTO); + } + + /** + * Returns the number of intersections on the positive X axis, + * with the origin at (x,y), used for contains()-testing + */ + int rayCrossing(double x, double y) + { + double x0 = P1.getX() - x; + double y0 = P1.getY() - y; + double x1 = cp.getX() - x; + double y1 = cp.getY() - y; + double x2 = P2.getX() - x; + double y2 = P2.getY() - y; + double[] r = new double[3]; + int nRoots; + int nCrossings = 0; + + /* check if curve may intersect X+ axis. */ + if ((x0 > 0.0 || x1 > 0.0 || x2 > 0.0) && (y0 * y1 <= 0 || y1 * y2 <= 0)) + { + if (y0 == 0.0) + y0 -= EPSILON; + if (y2 == 0.0) + y2 -= EPSILON; + + r[0] = y0; + r[1] = 2 * (y1 - y0); + r[2] = (y2 - 2 * y1 + y0); + + nRoots = QuadCurve2D.solveQuadratic(r); + for (int i = 0; i < nRoots; i++) + if (r[i] > 0.0f && r[i] < 1.0f) + { + double t = r[i]; + if (t * t * (x2 - 2 * x1 + x0) + 2 * t * (x1 - x0) + x0 > 0.0) + nCrossings++; + } + } + return nCrossings; + } + + /** + * Swap start and end points + */ + void reverseCoords() + { + Point2D temp = P1; + P1 = P2; + P2 = temp; + } + + /** + * Splits intersections into nodes, + * This one handles quadratic-quadratic only, + * Quadratic-line is passed on to the LineSegment class, + * Quadratic-cubic is passed on to the CubicSegment class + */ + int splitIntersections(Segment b) + { + if (b instanceof LineSegment) + return (b.splitIntersections(this)); + + if (b instanceof CubicSegment) + return (b.splitIntersections(this)); + + if (b instanceof QuadSegment) + { + // Use the cubic-cubic intersection routine for quads as well, + // Since a quadratic can be exactly described as a cubic, this + // should not be a problem; + // The recursion depth will be the same in any case. + Intersection[] x = cubicCubicIntersect(getCubicSegment(), + ((QuadSegment) b) + .getCubicSegment()); + if (x == null) + return 0; + + if (x.length == 1) + return createNode(b, (Intersection) x[0]); + + return createNodes(b, x); + } + return 0; + } + + /** + * Subdivides the segment at parametric value t, inserting + * the new segment into the linked list after this, + * such that this becomes [0,t] and this.next becomes [t,1] + */ + void subdivideInsert(double t) + { + double x0 = P1.getX(); + double y0 = P1.getY(); + double x1 = cp.getX(); + double y1 = cp.getY(); + double x2 = P2.getX(); + double y2 = P2.getY(); + + double p10x = x0 + t * (x1 - x0); + double p10y = y0 + t * (y1 - y0); + double p11x = x1 + t * (x2 - x1); + double p11y = y1 + t * (y2 - y1); + double p20x = p10x + t * (p11x - p10x); + double p20y = p10y + t * (p11y - p10y); + + insert(new QuadSegment(p20x, p20y, p11x, p11y, x2, y2)); + P2 = next.P1; + cp.setLocation(p10x, p10y); + + next.node = node; + node = null; + } + + /** + * Transforms the segment + */ + void transform(AffineTransform at) + { + P1 = at.transform(P1, null); + P2 = at.transform(P2, null); + cp = at.transform(cp, null); + } + } // class QuadSegment + + /** + * Cubic Bezier curve segment + */ + private class CubicSegment extends Segment + { + Point2D cp1; // control points + Point2D cp2; // control points + + /** + * Constructor - takes coordinates of the starting point, + * first control point, second control point and end point, + * respecively. + */ + public CubicSegment(double x1, double y1, double c1x, double c1y, + double c2x, double c2y, double x2, double y2) + { + super(); + P1 = new Point2D.Double(x1, y1); + P2 = new Point2D.Double(x2, y2); + cp1 = new Point2D.Double(c1x, c1y); + cp2 = new Point2D.Double(c2x, c2y); + } + + /** + * Clones this segment + */ + public Object clone() + { + return new CubicSegment(P1.getX(), P1.getY(), cp1.getX(), cp1.getY(), + cp2.getX(), cp2.getY(), P2.getX(), P2.getY()); + } + + /** + * Returns twice the area of a curve, relative the P1-P2 line + * + * The area formula can be derived by using Green's formula in the + * plane on the parametric form of the bezier. + */ + double curveArea() + { + double x0 = P1.getX(); + double y0 = P1.getY(); + double x1 = cp1.getX(); + double y1 = cp1.getY(); + double x2 = cp2.getX(); + double y2 = cp2.getY(); + double x3 = P2.getX(); + double y3 = P2.getY(); + + double P = y3 - 3 * y2 + 3 * y1 - y0; + double Q = 3 * (y2 + y0 - 2 * y1); + double R = 3 * (y1 - y0); + + double A = x3 - 3 * x2 + 3 * x1 - x0; + double B = 3 * (x2 + x0 - 2 * x1); + double C = 3 * (x1 - x0); + + double area = (B * P - A * Q) / 5.0 + (C * P - A * R) / 2.0 + + (C * Q - B * R) / 3.0; + + return (area); + } + + /** + * Compare two segments. + */ + boolean equals(Segment b) + { + if (! (b instanceof CubicSegment)) + return false; + + return (P1.equals(b.P1) && cp1.equals(((CubicSegment) b).cp1) + && cp2.equals(((CubicSegment) b).cp2) && P2.equals(b.P2)); + } + + /** + * Returns a Point2D corresponding to the parametric value t + * of the curve + */ + Point2D evaluatePoint(double t) + { + double x0 = P1.getX(); + double y0 = P1.getY(); + double x1 = cp1.getX(); + double y1 = cp1.getY(); + double x2 = cp2.getX(); + double y2 = cp2.getY(); + double x3 = P2.getX(); + double y3 = P2.getY(); + + return new Point2D.Double(-(t * t * t) * (x0 - 3 * x1 + 3 * x2 - x3) + + 3 * t * t * (x0 - 2 * x1 + x2) + + 3 * t * (x1 - x0) + x0, + -(t * t * t) * (y0 - 3 * y1 + 3 * y2 - y3) + + 3 * t * t * (y0 - 2 * y1 + y2) + + 3 * t * (y1 - y0) + y0); + } + + /** + * Returns the bounding box of this segment + */ + Rectangle2D getBounds() + { + double x0 = P1.getX(); + double y0 = P1.getY(); + double x1 = cp1.getX(); + double y1 = cp1.getY(); + double x2 = cp2.getX(); + double y2 = cp2.getY(); + double x3 = P2.getX(); + double y3 = P2.getY(); + double[] r = new double[3]; + + double xmax = Math.max(x0, x3); + double ymax = Math.max(y0, y3); + double xmin = Math.min(x0, x3); + double ymin = Math.min(y0, y3); + + r[0] = 3 * (y1 - y0); + r[1] = 6.0 * (y2 + y0 - 2 * y1); + r[2] = 3.0 * (y3 - 3 * y2 + 3 * y1 - y0); + + int n = QuadCurve2D.solveQuadratic(r); + for (int i = 0; i < n; i++) + { + double t = r[i]; + if (t > 0 && t < 1.0) + { + double y = evaluatePoint(t).getY(); + ymax = Math.max(y, ymax); + ymin = Math.min(y, ymin); + } + } + + r[0] = 3 * (x1 - x0); + r[1] = 6.0 * (x2 + x0 - 2 * x1); + r[2] = 3.0 * (x3 - 3 * x2 + 3 * x1 - x0); + n = QuadCurve2D.solveQuadratic(r); + for (int i = 0; i < n; i++) + { + double t = r[i]; + if (t > 0 && t < 1.0) + { + double x = evaluatePoint(t).getX(); + xmax = Math.max(x, xmax); + xmin = Math.min(x, xmin); + } + } + return (new Rectangle2D.Double(xmin, ymin, (xmax - xmin), (ymax - ymin))); + } + + /** + * Returns a CubicCurve2D object corresponding to this segment. + */ + CubicCurve2D getCubicCurve2D() + { + return new CubicCurve2D.Double(P1.getX(), P1.getY(), cp1.getX(), + cp1.getY(), cp2.getX(), cp2.getY(), + P2.getX(), P2.getY()); + } + + /** + * Returns the parametric points of self-intersection if the cubic + * is self-intersecting, null otherwise. + */ + double[] getLoop() + { + double x0 = P1.getX(); + double y0 = P1.getY(); + double x1 = cp1.getX(); + double y1 = cp1.getY(); + double x2 = cp2.getX(); + double y2 = cp2.getY(); + double x3 = P2.getX(); + double y3 = P2.getY(); + double[] r = new double[4]; + double k; + double R; + double T; + double A; + double B; + double[] results = new double[2]; + + R = x3 - 3 * x2 + 3 * x1 - x0; + T = y3 - 3 * y2 + 3 * y1 - y0; + + // A qudratic + if (R == 0.0 && T == 0.0) + return null; + + // true cubic + if (R != 0.0 && T != 0.0) + { + A = 3 * (x2 + x0 - 2 * x1) / R; + B = 3 * (x1 - x0) / R; + + double P = 3 * (y2 + y0 - 2 * y1) / T; + double Q = 3 * (y1 - y0) / T; + + if (A == P || Q == B) + return null; + + k = (Q - B) / (A - P); + } + else + { + if (R == 0.0) + { + // quadratic in x + k = -(3 * (x1 - x0)) / (3 * (x2 + x0 - 2 * x1)); + A = 3 * (y2 + y0 - 2 * y1) / T; + B = 3 * (y1 - y0) / T; + } + else + { + // quadratic in y + k = -(3 * (y1 - y0)) / (3 * (y2 + y0 - 2 * y1)); + A = 3 * (x2 + x0 - 2 * x1) / R; + B = 3 * (x1 - x0) / R; + } + } + + r[0] = -k * k * k - A * k * k - B * k; + r[1] = 3 * k * k + 2 * k * A + 2 * B; + r[2] = -3 * k; + r[3] = 2; + + int n = CubicCurve2D.solveCubic(r); + if (n != 3) + return null; + + // sort r + double t; + for (int i = 0; i < 2; i++) + for (int j = i + 1; j < 3; j++) + if (r[j] < r[i]) + { + t = r[i]; + r[i] = r[j]; + r[j] = t; + } + + if (Math.abs(r[0] + r[2] - k) < 1E-13) + if (r[0] >= 0.0 && r[0] <= 1.0 && r[2] >= 0.0 && r[2] <= 1.0) + if (evaluatePoint(r[0]).distance(evaluatePoint(r[2])) < PE_EPSILON * 10) + { // we snap the points anyway + results[0] = r[0]; + results[1] = r[2]; + return (results); + } + return null; + } + + /** + * Returns the segment's midpoint + */ + Point2D getMidPoint() + { + return evaluatePoint(0.5); + } + + /** + * Returns the PathIterator type of a segment + */ + int getType() + { + return (PathIterator.SEG_CUBICTO); + } + + /** + * Returns the PathIterator coords of a segment + */ + int pathIteratorFormat(double[] coords) + { + coords[0] = cp1.getX(); + coords[1] = cp1.getY(); + coords[2] = cp2.getX(); + coords[3] = cp2.getY(); + coords[4] = P2.getX(); + coords[5] = P2.getY(); + return (PathIterator.SEG_CUBICTO); + } + + /** + * Returns the number of intersections on the positive X axis, + * with the origin at (x,y), used for contains()-testing + */ + int rayCrossing(double x, double y) + { + double x0 = P1.getX() - x; + double y0 = P1.getY() - y; + double x1 = cp1.getX() - x; + double y1 = cp1.getY() - y; + double x2 = cp2.getX() - x; + double y2 = cp2.getY() - y; + double x3 = P2.getX() - x; + double y3 = P2.getY() - y; + double[] r = new double[4]; + int nRoots; + int nCrossings = 0; + + /* check if curve may intersect X+ axis. */ + if ((x0 > 0.0 || x1 > 0.0 || x2 > 0.0 || x3 > 0.0) + && (y0 * y1 <= 0 || y1 * y2 <= 0 || y2 * y3 <= 0)) + { + if (y0 == 0.0) + y0 -= EPSILON; + if (y3 == 0.0) + y3 -= EPSILON; + + r[0] = y0; + r[1] = 3 * (y1 - y0); + r[2] = 3 * (y2 + y0 - 2 * y1); + r[3] = y3 - 3 * y2 + 3 * y1 - y0; + + if ((nRoots = CubicCurve2D.solveCubic(r)) > 0) + for (int i = 0; i < nRoots; i++) + { + if (r[i] > 0.0 && r[i] < 1.0) + { + double t = r[i]; + if (-(t * t * t) * (x0 - 3 * x1 + 3 * x2 - x3) + + 3 * t * t * (x0 - 2 * x1 + x2) + 3 * t * (x1 - x0) + + x0 > 0.0) + nCrossings++; + } + } + } + return nCrossings; + } + + /** + * Swap start and end points + */ + void reverseCoords() + { + Point2D p = P1; + P1 = P2; + P2 = p; + p = cp1; // swap control points + cp1 = cp2; + cp2 = p; + } + + /** + * Splits intersections into nodes, + * This one handles cubic-cubic and cubic-quadratic intersections + */ + int splitIntersections(Segment b) + { + if (b instanceof LineSegment) + return (b.splitIntersections(this)); + + Intersection[] x = null; + + if (b instanceof QuadSegment) + x = cubicCubicIntersect(this, ((QuadSegment) b).getCubicSegment()); + + if (b instanceof CubicSegment) + x = cubicCubicIntersect(this, (CubicSegment) b); + + if (x == null) + return 0; + + if (x.length == 1) + return createNode(b, x[0]); + + return createNodes(b, x); + } + + /** + * Subdivides the segment at parametric value t, inserting + * the new segment into the linked list after this, + * such that this becomes [0,t] and this.next becomes [t,1] + */ + void subdivideInsert(double t) + { + CubicSegment s = (CubicSegment) clone(); + double p1x = (s.cp1.getX() - s.P1.getX()) * t + s.P1.getX(); + double p1y = (s.cp1.getY() - s.P1.getY()) * t + s.P1.getY(); + + double px = (s.cp2.getX() - s.cp1.getX()) * t + s.cp1.getX(); + double py = (s.cp2.getY() - s.cp1.getY()) * t + s.cp1.getY(); + + s.cp2.setLocation((s.P2.getX() - s.cp2.getX()) * t + s.cp2.getX(), + (s.P2.getY() - s.cp2.getY()) * t + s.cp2.getY()); + + s.cp1.setLocation((s.cp2.getX() - px) * t + px, + (s.cp2.getY() - py) * t + py); + + double p2x = (px - p1x) * t + p1x; + double p2y = (py - p1y) * t + p1y; + + double p3x = (s.cp1.getX() - p2x) * t + p2x; + double p3y = (s.cp1.getY() - p2y) * t + p2y; + s.P1.setLocation(p3x, p3y); + + // insert new curve + insert(s); + + // set this curve + cp1.setLocation(p1x, p1y); + cp2.setLocation(p2x, p2y); + P2 = s.P1; + next.node = node; + node = null; + } + + /** + * Transforms the segment + */ + void transform(AffineTransform at) + { + P1 = at.transform(P1, null); + P2 = at.transform(P2, null); + cp1 = at.transform(cp1, null); + cp2 = at.transform(cp2, null); + } + } // class CubicSegment +} // class Area diff --git a/libjava/classpath/java/awt/geom/CubicCurve2D.java b/libjava/classpath/java/awt/geom/CubicCurve2D.java new file mode 100644 index 000000000..5cb11fe77 --- /dev/null +++ b/libjava/classpath/java/awt/geom/CubicCurve2D.java @@ -0,0 +1,1724 @@ +/* CubicCurve2D.java -- represents a parameterized cubic curve in 2-D space + Copyright (C) 2002, 2003, 2004 Free Software Foundation + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package java.awt.geom; + +import java.awt.Rectangle; +import java.awt.Shape; +import java.util.NoSuchElementException; + + +/** + * A two-dimensional curve that is parameterized with a cubic + * function. + * + * <p><img src="doc-files/CubicCurve2D-1.png" width="350" height="180" + * alt="A drawing of a CubicCurve2D" /> + * + * @author Eric Blake (ebb9@email.byu.edu) + * @author Graydon Hoare (graydon@redhat.com) + * @author Sascha Brawer (brawer@dandelis.ch) + * @author Sven de Marothy (sven@physto.se) + * + * @since 1.2 + */ +public abstract class CubicCurve2D implements Shape, Cloneable +{ + private static final double BIG_VALUE = java.lang.Double.MAX_VALUE / 10.0; + private static final double EPSILON = 1E-10; + + /** + * Constructs a new CubicCurve2D. Typical users will want to + * construct instances of a subclass, such as {@link + * CubicCurve2D.Float} or {@link CubicCurve2D.Double}. + */ + protected CubicCurve2D() + { + } + + /** + * Returns the <i>x</i> coordinate of the curve’s start + * point. + */ + public abstract double getX1(); + + /** + * Returns the <i>y</i> coordinate of the curve’s start + * point. + */ + public abstract double getY1(); + + /** + * Returns the curve’s start point. + */ + public abstract Point2D getP1(); + + /** + * Returns the <i>x</i> coordinate of the curve’s first + * control point. + */ + public abstract double getCtrlX1(); + + /** + * Returns the <i>y</i> coordinate of the curve’s first + * control point. + */ + public abstract double getCtrlY1(); + + /** + * Returns the curve’s first control point. + */ + public abstract Point2D getCtrlP1(); + + /** + * Returns the <i>x</i> coordinate of the curve’s second + * control point. + */ + public abstract double getCtrlX2(); + + /** + * Returns the <i>y</i> coordinate of the curve’s second + * control point. + */ + public abstract double getCtrlY2(); + + /** + * Returns the curve’s second control point. + */ + public abstract Point2D getCtrlP2(); + + /** + * Returns the <i>x</i> coordinate of the curve’s end + * point. + */ + public abstract double getX2(); + + /** + * Returns the <i>y</i> coordinate of the curve’s end + * point. + */ + public abstract double getY2(); + + /** + * Returns the curve’s end point. + */ + public abstract Point2D getP2(); + + /** + * Changes the curve geometry, separately specifying each coordinate + * value. + * + * <p><img src="doc-files/CubicCurve2D-1.png" width="350" height="180" + * alt="A drawing of a CubicCurve2D" /> + * + * @param x1 the <i>x</i> coordinate of the curve’s new start + * point. + * + * @param y1 the <i>y</i> coordinate of the curve’s new start + * point. + * + * @param cx1 the <i>x</i> coordinate of the curve’s new + * first control point. + * + * @param cy1 the <i>y</i> coordinate of the curve’s new + * first control point. + * + * @param cx2 the <i>x</i> coordinate of the curve’s new + * second control point. + * + * @param cy2 the <i>y</i> coordinate of the curve’s new + * second control point. + * + * @param x2 the <i>x</i> coordinate of the curve’s new end + * point. + * + * @param y2 the <i>y</i> coordinate of the curve’s new end + * point. + */ + public abstract void setCurve(double x1, double y1, double cx1, double cy1, + double cx2, double cy2, double x2, double y2); + + /** + * Changes the curve geometry, specifying coordinate values in an + * array. + * + * @param coords an array containing the new coordinate values. The + * <i>x</i> coordinate of the new start point is located at + * <code>coords[offset]</code>, its <i>y</i> coordinate at + * <code>coords[offset + 1]</code>. The <i>x</i> coordinate of the + * new first control point is located at <code>coords[offset + + * 2]</code>, its <i>y</i> coordinate at <code>coords[offset + + * 3]</code>. The <i>x</i> coordinate of the new second control + * point is located at <code>coords[offset + 4]</code>, its <i>y</i> + * coordinate at <code>coords[offset + 5]</code>. The <i>x</i> + * coordinate of the new end point is located at <code>coords[offset + * + 6]</code>, its <i>y</i> coordinate at <code>coords[offset + + * 7]</code>. + * + * @param offset the offset of the first coordinate value in + * <code>coords</code>. + */ + public void setCurve(double[] coords, int offset) + { + setCurve(coords[offset++], coords[offset++], coords[offset++], + coords[offset++], coords[offset++], coords[offset++], + coords[offset++], coords[offset++]); + } + + /** + * Changes the curve geometry, specifying coordinate values in + * separate Point objects. + * + * <p><img src="doc-files/CubicCurve2D-1.png" width="350" height="180" + * alt="A drawing of a CubicCurve2D" /> + * + * <p>The curve does not keep any reference to the passed point + * objects. Therefore, a later change to <code>p1</code>, + * <code>c1</code>, <code>c2</code> or <code>p2</code> will not + * affect the curve geometry. + * + * @param p1 the new start point. + * @param c1 the new first control point. + * @param c2 the new second control point. + * @param p2 the new end point. + */ + public void setCurve(Point2D p1, Point2D c1, Point2D c2, Point2D p2) + { + setCurve(p1.getX(), p1.getY(), c1.getX(), c1.getY(), c2.getX(), c2.getY(), + p2.getX(), p2.getY()); + } + + /** + * Changes the curve geometry, specifying coordinate values in an + * array of Point objects. + * + * <p><img src="doc-files/CubicCurve2D-1.png" width="350" height="180" + * alt="A drawing of a CubicCurve2D" /> + * + * <p>The curve does not keep references to the passed point + * objects. Therefore, a later change to the <code>pts</code> array + * or any of its elements will not affect the curve geometry. + * + * @param pts an array containing the points. The new start point + * is located at <code>pts[offset]</code>, the new first control + * point at <code>pts[offset + 1]</code>, the new second control + * point at <code>pts[offset + 2]</code>, and the new end point + * at <code>pts[offset + 3]</code>. + * + * @param offset the offset of the start point in <code>pts</code>. + */ + public void setCurve(Point2D[] pts, int offset) + { + setCurve(pts[offset].getX(), pts[offset++].getY(), pts[offset].getX(), + pts[offset++].getY(), pts[offset].getX(), pts[offset++].getY(), + pts[offset].getX(), pts[offset++].getY()); + } + + /** + * Changes the curve geometry to that of another curve. + * + * @param c the curve whose coordinates will be copied. + */ + public void setCurve(CubicCurve2D c) + { + setCurve(c.getX1(), c.getY1(), c.getCtrlX1(), c.getCtrlY1(), + c.getCtrlX2(), c.getCtrlY2(), c.getX2(), c.getY2()); + } + + /** + * Calculates the squared flatness of a cubic curve, directly + * specifying each coordinate value. The flatness is the maximal + * distance of a control point to the line between start and end + * point. + * + * <p><img src="doc-files/CubicCurve2D-4.png" width="350" height="180" + * alt="A drawing that illustrates the flatness" /> + * + * <p>In the above drawing, the straight line connecting start point + * P1 and end point P2 is depicted in gray. In comparison to C1, + * control point C2 is father away from the gray line. Therefore, + * the result will be the square of the distance between C2 and the + * gray line, i.e. the squared length of the red line. + * + * @param x1 the <i>x</i> coordinate of the start point P1. + * @param y1 the <i>y</i> coordinate of the start point P1. + * @param cx1 the <i>x</i> coordinate of the first control point C1. + * @param cy1 the <i>y</i> coordinate of the first control point C1. + * @param cx2 the <i>x</i> coordinate of the second control point C2. + * @param cy2 the <i>y</i> coordinate of the second control point C2. + * @param x2 the <i>x</i> coordinate of the end point P2. + * @param y2 the <i>y</i> coordinate of the end point P2. + */ + public static double getFlatnessSq(double x1, double y1, double cx1, + double cy1, double cx2, double cy2, + double x2, double y2) + { + return Math.max(Line2D.ptSegDistSq(x1, y1, x2, y2, cx1, cy1), + Line2D.ptSegDistSq(x1, y1, x2, y2, cx2, cy2)); + } + + /** + * Calculates the flatness of a cubic curve, directly specifying + * each coordinate value. The flatness is the maximal distance of a + * control point to the line between start and end point. + * + * <p><img src="doc-files/CubicCurve2D-4.png" width="350" height="180" + * alt="A drawing that illustrates the flatness" /> + * + * <p>In the above drawing, the straight line connecting start point + * P1 and end point P2 is depicted in gray. In comparison to C1, + * control point C2 is father away from the gray line. Therefore, + * the result will be the distance between C2 and the gray line, + * i.e. the length of the red line. + * + * @param x1 the <i>x</i> coordinate of the start point P1. + * @param y1 the <i>y</i> coordinate of the start point P1. + * @param cx1 the <i>x</i> coordinate of the first control point C1. + * @param cy1 the <i>y</i> coordinate of the first control point C1. + * @param cx2 the <i>x</i> coordinate of the second control point C2. + * @param cy2 the <i>y</i> coordinate of the second control point C2. + * @param x2 the <i>x</i> coordinate of the end point P2. + * @param y2 the <i>y</i> coordinate of the end point P2. + */ + public static double getFlatness(double x1, double y1, double cx1, + double cy1, double cx2, double cy2, + double x2, double y2) + { + return Math.sqrt(getFlatnessSq(x1, y1, cx1, cy1, cx2, cy2, x2, y2)); + } + + /** + * Calculates the squared flatness of a cubic curve, specifying the + * coordinate values in an array. The flatness is the maximal + * distance of a control point to the line between start and end + * point. + * + * <p><img src="doc-files/CubicCurve2D-4.png" width="350" height="180" + * alt="A drawing that illustrates the flatness" /> + * + * <p>In the above drawing, the straight line connecting start point + * P1 and end point P2 is depicted in gray. In comparison to C1, + * control point C2 is father away from the gray line. Therefore, + * the result will be the square of the distance between C2 and the + * gray line, i.e. the squared length of the red line. + * + * @param coords an array containing the coordinate values. The + * <i>x</i> coordinate of the start point P1 is located at + * <code>coords[offset]</code>, its <i>y</i> coordinate at + * <code>coords[offset + 1]</code>. The <i>x</i> coordinate of the + * first control point C1 is located at <code>coords[offset + + * 2]</code>, its <i>y</i> coordinate at <code>coords[offset + + * 3]</code>. The <i>x</i> coordinate of the second control point C2 + * is located at <code>coords[offset + 4]</code>, its <i>y</i> + * coordinate at <code>coords[offset + 5]</code>. The <i>x</i> + * coordinate of the end point P2 is located at <code>coords[offset + * + 6]</code>, its <i>y</i> coordinate at <code>coords[offset + + * 7]</code>. + * + * @param offset the offset of the first coordinate value in + * <code>coords</code>. + */ + public static double getFlatnessSq(double[] coords, int offset) + { + return getFlatnessSq(coords[offset++], coords[offset++], coords[offset++], + coords[offset++], coords[offset++], coords[offset++], + coords[offset++], coords[offset++]); + } + + /** + * Calculates the flatness of a cubic curve, specifying the + * coordinate values in an array. The flatness is the maximal + * distance of a control point to the line between start and end + * point. + * + * <p><img src="doc-files/CubicCurve2D-4.png" width="350" height="180" + * alt="A drawing that illustrates the flatness" /> + * + * <p>In the above drawing, the straight line connecting start point + * P1 and end point P2 is depicted in gray. In comparison to C1, + * control point C2 is father away from the gray line. Therefore, + * the result will be the distance between C2 and the gray line, + * i.e. the length of the red line. + * + * @param coords an array containing the coordinate values. The + * <i>x</i> coordinate of the start point P1 is located at + * <code>coords[offset]</code>, its <i>y</i> coordinate at + * <code>coords[offset + 1]</code>. The <i>x</i> coordinate of the + * first control point C1 is located at <code>coords[offset + + * 2]</code>, its <i>y</i> coordinate at <code>coords[offset + + * 3]</code>. The <i>x</i> coordinate of the second control point C2 + * is located at <code>coords[offset + 4]</code>, its <i>y</i> + * coordinate at <code>coords[offset + 5]</code>. The <i>x</i> + * coordinate of the end point P2 is located at <code>coords[offset + * + 6]</code>, its <i>y</i> coordinate at <code>coords[offset + + * 7]</code>. + * + * @param offset the offset of the first coordinate value in + * <code>coords</code>. + */ + public static double getFlatness(double[] coords, int offset) + { + return Math.sqrt(getFlatnessSq(coords[offset++], coords[offset++], + coords[offset++], coords[offset++], + coords[offset++], coords[offset++], + coords[offset++], coords[offset++])); + } + + /** + * Calculates the squared flatness of this curve. The flatness is + * the maximal distance of a control point to the line between start + * and end point. + * + * <p><img src="doc-files/CubicCurve2D-4.png" width="350" height="180" + * alt="A drawing that illustrates the flatness" /> + * + * <p>In the above drawing, the straight line connecting start point + * P1 and end point P2 is depicted in gray. In comparison to C1, + * control point C2 is father away from the gray line. Therefore, + * the result will be the square of the distance between C2 and the + * gray line, i.e. the squared length of the red line. + */ + public double getFlatnessSq() + { + return getFlatnessSq(getX1(), getY1(), getCtrlX1(), getCtrlY1(), + getCtrlX2(), getCtrlY2(), getX2(), getY2()); + } + + /** + * Calculates the flatness of this curve. The flatness is the + * maximal distance of a control point to the line between start and + * end point. + * + * <p><img src="doc-files/CubicCurve2D-4.png" width="350" height="180" + * alt="A drawing that illustrates the flatness" /> + * + * <p>In the above drawing, the straight line connecting start point + * P1 and end point P2 is depicted in gray. In comparison to C1, + * control point C2 is father away from the gray line. Therefore, + * the result will be the distance between C2 and the gray line, + * i.e. the length of the red line. + */ + public double getFlatness() + { + return Math.sqrt(getFlatnessSq(getX1(), getY1(), getCtrlX1(), getCtrlY1(), + getCtrlX2(), getCtrlY2(), getX2(), getY2())); + } + + /** + * Subdivides this curve into two halves. + * + * <p><img src="doc-files/CubicCurve2D-3.png" width="700" + * height="180" alt="A drawing that illustrates the effects of + * subdividing a CubicCurve2D" /> + * + * @param left a curve whose geometry will be set to the left half + * of this curve, or <code>null</code> if the caller is not + * interested in the left half. + * + * @param right a curve whose geometry will be set to the right half + * of this curve, or <code>null</code> if the caller is not + * interested in the right half. + */ + public void subdivide(CubicCurve2D left, CubicCurve2D right) + { + // Use empty slots at end to share single array. + double[] d = new double[] + { + getX1(), getY1(), getCtrlX1(), getCtrlY1(), getCtrlX2(), + getCtrlY2(), getX2(), getY2(), 0, 0, 0, 0, 0, 0 + }; + subdivide(d, 0, d, 0, d, 6); + if (left != null) + left.setCurve(d, 0); + if (right != null) + right.setCurve(d, 6); + } + + /** + * Subdivides a cubic curve into two halves. + * + * <p><img src="doc-files/CubicCurve2D-3.png" width="700" + * height="180" alt="A drawing that illustrates the effects of + * subdividing a CubicCurve2D" /> + * + * @param src the curve to be subdivided. + * + * @param left a curve whose geometry will be set to the left half + * of <code>src</code>, or <code>null</code> if the caller is not + * interested in the left half. + * + * @param right a curve whose geometry will be set to the right half + * of <code>src</code>, or <code>null</code> if the caller is not + * interested in the right half. + */ + public static void subdivide(CubicCurve2D src, CubicCurve2D left, + CubicCurve2D right) + { + src.subdivide(left, right); + } + + /** + * Subdivides a cubic curve into two halves, passing all coordinates + * in an array. + * + * <p><img src="doc-files/CubicCurve2D-3.png" width="700" + * height="180" alt="A drawing that illustrates the effects of + * subdividing a CubicCurve2D" /> + * + * <p>The left end point and the right start point will always be + * identical. Memory-concious programmers thus may want to pass the + * same array for both <code>left</code> and <code>right</code>, and + * set <code>rightOff</code> to <code>leftOff + 6</code>. + * + * @param src an array containing the coordinates of the curve to be + * subdivided. The <i>x</i> coordinate of the start point P1 is + * located at <code>src[srcOff]</code>, its <i>y</i> at + * <code>src[srcOff + 1]</code>. The <i>x</i> coordinate of the + * first control point C1 is located at <code>src[srcOff + + * 2]</code>, its <i>y</i> at <code>src[srcOff + 3]</code>. The + * <i>x</i> coordinate of the second control point C2 is located at + * <code>src[srcOff + 4]</code>, its <i>y</i> at <code>src[srcOff + + * 5]</code>. The <i>x</i> coordinate of the end point is located at + * <code>src[srcOff + 6]</code>, its <i>y</i> at <code>src[srcOff + + * 7]</code>. + * + * @param srcOff an offset into <code>src</code>, specifying + * the index of the start point’s <i>x</i> coordinate. + * + * @param left an array that will receive the coordinates of the + * left half of <code>src</code>. It is acceptable to pass + * <code>src</code>. A caller who is not interested in the left half + * can pass <code>null</code>. + * + * @param leftOff an offset into <code>left</code>, specifying the + * index where the start point’s <i>x</i> coordinate will be + * stored. + * + * @param right an array that will receive the coordinates of the + * right half of <code>src</code>. It is acceptable to pass + * <code>src</code> or <code>left</code>. A caller who is not + * interested in the right half can pass <code>null</code>. + * + * @param rightOff an offset into <code>right</code>, specifying the + * index where the start point’s <i>x</i> coordinate will be + * stored. + */ + public static void subdivide(double[] src, int srcOff, double[] left, + int leftOff, double[] right, int rightOff) + { + // To understand this code, please have a look at the image + // "CubicCurve2D-3.png" in the sub-directory "doc-files". + double src_C1_x; + double src_C1_y; + double src_C2_x; + double src_C2_y; + double left_P1_x; + double left_P1_y; + double left_C1_x; + double left_C1_y; + double left_C2_x; + double left_C2_y; + double right_C1_x; + double right_C1_y; + double right_C2_x; + double right_C2_y; + double right_P2_x; + double right_P2_y; + double Mid_x; // Mid = left.P2 = right.P1 + double Mid_y; // Mid = left.P2 = right.P1 + + left_P1_x = src[srcOff]; + left_P1_y = src[srcOff + 1]; + src_C1_x = src[srcOff + 2]; + src_C1_y = src[srcOff + 3]; + src_C2_x = src[srcOff + 4]; + src_C2_y = src[srcOff + 5]; + right_P2_x = src[srcOff + 6]; + right_P2_y = src[srcOff + 7]; + + left_C1_x = (left_P1_x + src_C1_x) / 2; + left_C1_y = (left_P1_y + src_C1_y) / 2; + right_C2_x = (right_P2_x + src_C2_x) / 2; + right_C2_y = (right_P2_y + src_C2_y) / 2; + Mid_x = (src_C1_x + src_C2_x) / 2; + Mid_y = (src_C1_y + src_C2_y) / 2; + left_C2_x = (left_C1_x + Mid_x) / 2; + left_C2_y = (left_C1_y + Mid_y) / 2; + right_C1_x = (Mid_x + right_C2_x) / 2; + right_C1_y = (Mid_y + right_C2_y) / 2; + Mid_x = (left_C2_x + right_C1_x) / 2; + Mid_y = (left_C2_y + right_C1_y) / 2; + + if (left != null) + { + left[leftOff] = left_P1_x; + left[leftOff + 1] = left_P1_y; + left[leftOff + 2] = left_C1_x; + left[leftOff + 3] = left_C1_y; + left[leftOff + 4] = left_C2_x; + left[leftOff + 5] = left_C2_y; + left[leftOff + 6] = Mid_x; + left[leftOff + 7] = Mid_y; + } + + if (right != null) + { + right[rightOff] = Mid_x; + right[rightOff + 1] = Mid_y; + right[rightOff + 2] = right_C1_x; + right[rightOff + 3] = right_C1_y; + right[rightOff + 4] = right_C2_x; + right[rightOff + 5] = right_C2_y; + right[rightOff + 6] = right_P2_x; + right[rightOff + 7] = right_P2_y; + } + } + + /** + * Finds the non-complex roots of a cubic equation, placing the + * results into the same array as the equation coefficients. The + * following equation is being solved: + * + * <blockquote><code>eqn[3]</code> · <i>x</i><sup>3</sup> + * + <code>eqn[2]</code> · <i>x</i><sup>2</sup> + * + <code>eqn[1]</code> · <i>x</i> + * + <code>eqn[0]</code> + * = 0 + * </blockquote> + * + * <p>For some background about solving cubic equations, see the + * article <a + * href="http://planetmath.org/encyclopedia/CubicFormula.html" + * >“Cubic Formula”</a> in <a + * href="http://planetmath.org/" >PlanetMath</a>. For an extensive + * library of numerical algorithms written in the C programming + * language, see the <a href= "http://www.gnu.org/software/gsl/">GNU + * Scientific Library</a>, from which this implementation was + * adapted. + * + * @param eqn an array with the coefficients of the equation. When + * this procedure has returned, <code>eqn</code> will contain the + * non-complex solutions of the equation, in no particular order. + * + * @return the number of non-complex solutions. A result of 0 + * indicates that the equation has no non-complex solutions. A + * result of -1 indicates that the equation is constant (i.e., + * always or never zero). + * + * @see #solveCubic(double[], double[]) + * @see QuadCurve2D#solveQuadratic(double[],double[]) + * + * @author Brian Gough (bjg@network-theory.com) + * (original C implementation in the <a href= + * "http://www.gnu.org/software/gsl/">GNU Scientific Library</a>) + * + * @author Sascha Brawer (brawer@dandelis.ch) + * (adaptation to Java) + */ + public static int solveCubic(double[] eqn) + { + return solveCubic(eqn, eqn); + } + + /** + * Finds the non-complex roots of a cubic equation. The following + * equation is being solved: + * + * <blockquote><code>eqn[3]</code> · <i>x</i><sup>3</sup> + * + <code>eqn[2]</code> · <i>x</i><sup>2</sup> + * + <code>eqn[1]</code> · <i>x</i> + * + <code>eqn[0]</code> + * = 0 + * </blockquote> + * + * <p>For some background about solving cubic equations, see the + * article <a + * href="http://planetmath.org/encyclopedia/CubicFormula.html" + * >“Cubic Formula”</a> in <a + * href="http://planetmath.org/" >PlanetMath</a>. For an extensive + * library of numerical algorithms written in the C programming + * language, see the <a href= "http://www.gnu.org/software/gsl/">GNU + * Scientific Library</a>, from which this implementation was + * adapted. + * + * @see QuadCurve2D#solveQuadratic(double[],double[]) + * + * @param eqn an array with the coefficients of the equation. + * + * @param res an array into which the non-complex roots will be + * stored. The results may be in an arbitrary order. It is safe to + * pass the same array object reference for both <code>eqn</code> + * and <code>res</code>. + * + * @return the number of non-complex solutions. A result of 0 + * indicates that the equation has no non-complex solutions. A + * result of -1 indicates that the equation is constant (i.e., + * always or never zero). + * + * @author Brian Gough (bjg@network-theory.com) + * (original C implementation in the <a href= + * "http://www.gnu.org/software/gsl/">GNU Scientific Library</a>) + * + * @author Sascha Brawer (brawer@dandelis.ch) + * (adaptation to Java) + */ + public static int solveCubic(double[] eqn, double[] res) + { + // Adapted from poly/solve_cubic.c in the GNU Scientific Library + // (GSL), revision 1.7 of 2003-07-26. For the original source, see + // http://www.gnu.org/software/gsl/ + // + // Brian Gough, the author of that code, has granted the + // permission to use it in GNU Classpath under the GNU Classpath + // license, and has assigned the copyright to the Free Software + // Foundation. + // + // The Java implementation is very similar to the GSL code, but + // not a strict one-to-one copy. For example, GSL would sort the + // result. + + double a; + double b; + double c; + double q; + double r; + double Q; + double R; + double c3; + double Q3; + double R2; + double CR2; + double CQ3; + + // If the cubic coefficient is zero, we have a quadratic equation. + c3 = eqn[3]; + if (c3 == 0) + return QuadCurve2D.solveQuadratic(eqn, res); + + // Divide the equation by the cubic coefficient. + c = eqn[0] / c3; + b = eqn[1] / c3; + a = eqn[2] / c3; + + // We now need to solve x^3 + ax^2 + bx + c = 0. + q = a * a - 3 * b; + r = 2 * a * a * a - 9 * a * b + 27 * c; + + Q = q / 9; + R = r / 54; + + Q3 = Q * Q * Q; + R2 = R * R; + + CR2 = 729 * r * r; + CQ3 = 2916 * q * q * q; + + if (R == 0 && Q == 0) + { + // The GNU Scientific Library would return three identical + // solutions in this case. + res[0] = -a / 3; + return 1; + } + + if (CR2 == CQ3) + { + /* this test is actually R2 == Q3, written in a form suitable + for exact computation with integers */ + /* Due to finite precision some double roots may be missed, and + considered to be a pair of complex roots z = x +/- epsilon i + close to the real axis. */ + double sqrtQ = Math.sqrt(Q); + + if (R > 0) + { + res[0] = -2 * sqrtQ - a / 3; + res[1] = sqrtQ - a / 3; + } + else + { + res[0] = -sqrtQ - a / 3; + res[1] = 2 * sqrtQ - a / 3; + } + return 2; + } + + if (CR2 < CQ3) /* equivalent to R2 < Q3 */ + { + double sqrtQ = Math.sqrt(Q); + double sqrtQ3 = sqrtQ * sqrtQ * sqrtQ; + double theta = Math.acos(R / sqrtQ3); + double norm = -2 * sqrtQ; + res[0] = norm * Math.cos(theta / 3) - a / 3; + res[1] = norm * Math.cos((theta + 2.0 * Math.PI) / 3) - a / 3; + res[2] = norm * Math.cos((theta - 2.0 * Math.PI) / 3) - a / 3; + + // The GNU Scientific Library sorts the results. We don't. + return 3; + } + + double sgnR = (R >= 0 ? 1 : -1); + double A = -sgnR * Math.pow(Math.abs(R) + Math.sqrt(R2 - Q3), 1.0 / 3.0); + double B = Q / A; + res[0] = A + B - a / 3; + return 1; + } + + /** + * Determines whether a position lies inside the area bounded + * by the curve and the straight line connecting its end points. + * + * <p><img src="doc-files/CubicCurve2D-5.png" width="350" height="180" + * alt="A drawing of the area spanned by the curve" /> + * + * <p>The above drawing illustrates in which area points are + * considered “inside” a CubicCurve2D. + */ + public boolean contains(double x, double y) + { + if (! getBounds2D().contains(x, y)) + return false; + + return ((getAxisIntersections(x, y, true, BIG_VALUE) & 1) != 0); + } + + /** + * Determines whether a point lies inside the area bounded + * by the curve and the straight line connecting its end points. + * + * <p><img src="doc-files/CubicCurve2D-5.png" width="350" height="180" + * alt="A drawing of the area spanned by the curve" /> + * + * <p>The above drawing illustrates in which area points are + * considered “inside” a CubicCurve2D. + */ + public boolean contains(Point2D p) + { + return contains(p.getX(), p.getY()); + } + + /** + * Determines whether any part of a rectangle is inside the area bounded + * by the curve and the straight line connecting its end points. + * + * <p><img src="doc-files/CubicCurve2D-5.png" width="350" height="180" + * alt="A drawing of the area spanned by the curve" /> + * + * <p>The above drawing illustrates in which area points are + * considered “inside” in a CubicCurve2D. + * @see #contains(double, double) + */ + public boolean intersects(double x, double y, double w, double h) + { + if (! getBounds2D().contains(x, y, w, h)) + return false; + + /* Does any edge intersect? */ + if (getAxisIntersections(x, y, true, w) != 0 /* top */ + || getAxisIntersections(x, y + h, true, w) != 0 /* bottom */ + || getAxisIntersections(x + w, y, false, h) != 0 /* right */ + || getAxisIntersections(x, y, false, h) != 0) /* left */ + return true; + + /* No intersections, is any point inside? */ + if ((getAxisIntersections(x, y, true, BIG_VALUE) & 1) != 0) + return true; + + return false; + } + + /** + * Determines whether any part of a Rectangle2D is inside the area bounded + * by the curve and the straight line connecting its end points. + * @see #intersects(double, double, double, double) + */ + public boolean intersects(Rectangle2D r) + { + return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + /** + * Determine whether a rectangle is entirely inside the area that is bounded + * by the curve and the straight line connecting its end points. + * + * <p><img src="doc-files/CubicCurve2D-5.png" width="350" height="180" + * alt="A drawing of the area spanned by the curve" /> + * + * <p>The above drawing illustrates in which area points are + * considered “inside” a CubicCurve2D. + * @see #contains(double, double) + */ + public boolean contains(double x, double y, double w, double h) + { + if (! getBounds2D().intersects(x, y, w, h)) + return false; + + /* Does any edge intersect? */ + if (getAxisIntersections(x, y, true, w) != 0 /* top */ + || getAxisIntersections(x, y + h, true, w) != 0 /* bottom */ + || getAxisIntersections(x + w, y, false, h) != 0 /* right */ + || getAxisIntersections(x, y, false, h) != 0) /* left */ + return false; + + /* No intersections, is any point inside? */ + if ((getAxisIntersections(x, y, true, BIG_VALUE) & 1) != 0) + return true; + + return false; + } + + /** + * Determine whether a Rectangle2D is entirely inside the area that is + * bounded by the curve and the straight line connecting its end points. + * + * <p><img src="doc-files/CubicCurve2D-5.png" width="350" height="180" + * alt="A drawing of the area spanned by the curve" /> + * + * <p>The above drawing illustrates in which area points are + * considered “inside” a CubicCurve2D. + * @see #contains(double, double) + */ + public boolean contains(Rectangle2D r) + { + return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + /** + * Determines the smallest rectangle that encloses the + * curve’s start, end and control points. + */ + public Rectangle getBounds() + { + return getBounds2D().getBounds(); + } + + public PathIterator getPathIterator(final AffineTransform at) + { + return new PathIterator() + { + /** Current coordinate. */ + private int current = 0; + + public int getWindingRule() + { + return WIND_NON_ZERO; + } + + public boolean isDone() + { + return current >= 2; + } + + public void next() + { + current++; + } + + public int currentSegment(float[] coords) + { + int result; + switch (current) + { + case 0: + coords[0] = (float) getX1(); + coords[1] = (float) getY1(); + result = SEG_MOVETO; + break; + case 1: + coords[0] = (float) getCtrlX1(); + coords[1] = (float) getCtrlY1(); + coords[2] = (float) getCtrlX2(); + coords[3] = (float) getCtrlY2(); + coords[4] = (float) getX2(); + coords[5] = (float) getY2(); + result = SEG_CUBICTO; + break; + default: + throw new NoSuchElementException("cubic iterator out of bounds"); + } + if (at != null) + at.transform(coords, 0, coords, 0, 3); + return result; + } + + public int currentSegment(double[] coords) + { + int result; + switch (current) + { + case 0: + coords[0] = getX1(); + coords[1] = getY1(); + result = SEG_MOVETO; + break; + case 1: + coords[0] = getCtrlX1(); + coords[1] = getCtrlY1(); + coords[2] = getCtrlX2(); + coords[3] = getCtrlY2(); + coords[4] = getX2(); + coords[5] = getY2(); + result = SEG_CUBICTO; + break; + default: + throw new NoSuchElementException("cubic iterator out of bounds"); + } + if (at != null) + at.transform(coords, 0, coords, 0, 3); + return result; + } + }; + } + + public PathIterator getPathIterator(AffineTransform at, double flatness) + { + return new FlatteningPathIterator(getPathIterator(at), flatness); + } + + /** + * Create a new curve with the same contents as this one. + * + * @return the clone. + */ + public Object clone() + { + try + { + return super.clone(); + } + catch (CloneNotSupportedException e) + { + throw (Error) new InternalError().initCause(e); // Impossible + } + } + + /** + * Helper method used by contains() and intersects() methods, that + * returns the number of curve/line intersections on a given axis + * extending from a certain point. + * + * @param x x coordinate of the origin point + * @param y y coordinate of the origin point + * @param useYaxis axis used, if true the positive Y axis is used, + * false uses the positive X axis. + * + * This is an implementation of the line-crossings algorithm, + * Detailed in an article on Eric Haines' page: + * http://www.acm.org/tog/editors/erich/ptinpoly/ + * + * A special-case not adressed in this code is self-intersections + * of the curve, e.g. if the axis intersects the self-itersection, + * the degenerate roots of the polynomial will erroneously count as + * a single intersection of the curve, and not two. + */ + private int getAxisIntersections(double x, double y, boolean useYaxis, + double distance) + { + int nCrossings = 0; + double a0; + double a1; + double a2; + double a3; + double b0; + double b1; + double b2; + double b3; + double[] r = new double[4]; + int nRoots; + + a0 = a3 = 0.0; + + if (useYaxis) + { + a0 = getY1() - y; + a1 = getCtrlY1() - y; + a2 = getCtrlY2() - y; + a3 = getY2() - y; + b0 = getX1() - x; + b1 = getCtrlX1() - x; + b2 = getCtrlX2() - x; + b3 = getX2() - x; + } + else + { + a0 = getX1() - x; + a1 = getCtrlX1() - x; + a2 = getCtrlX2() - x; + a3 = getX2() - x; + b0 = getY1() - y; + b1 = getCtrlY1() - y; + b2 = getCtrlY2() - y; + b3 = getY2() - y; + } + + /* If the axis intersects a start/endpoint, shift it up by some small + amount to guarantee the line is 'inside' + If this is not done, bad behaviour may result for points on that axis.*/ + if (a0 == 0.0 || a3 == 0.0) + { + double small = getFlatness() * EPSILON; + if (a0 == 0.0) + a0 -= small; + if (a3 == 0.0) + a3 -= small; + } + + if (useYaxis) + { + if (Line2D.linesIntersect(b0, a0, b3, a3, EPSILON, 0.0, distance, 0.0)) + nCrossings++; + } + else + { + if (Line2D.linesIntersect(a0, b0, a3, b3, 0.0, EPSILON, 0.0, distance)) + nCrossings++; + } + + r[0] = a0; + r[1] = 3 * (a1 - a0); + r[2] = 3 * (a2 + a0 - 2 * a1); + r[3] = a3 - 3 * a2 + 3 * a1 - a0; + + if ((nRoots = solveCubic(r)) != 0) + for (int i = 0; i < nRoots; i++) + { + double t = r[i]; + if (t >= 0.0 && t <= 1.0) + { + double crossing = -(t * t * t) * (b0 - 3 * b1 + 3 * b2 - b3) + + 3 * t * t * (b0 - 2 * b1 + b2) + + 3 * t * (b1 - b0) + b0; + if (crossing > 0.0 && crossing <= distance) + nCrossings++; + } + } + + return (nCrossings); + } + + /** + * A two-dimensional curve that is parameterized with a cubic + * function and stores coordinate values in double-precision + * floating-point format. + * + * @see CubicCurve2D.Float + * + * @author Eric Blake (ebb9@email.byu.edu) + * @author Sascha Brawer (brawer@dandelis.ch) + */ + public static class Double extends CubicCurve2D + { + /** + * The <i>x</i> coordinate of the curve’s start point. + */ + public double x1; + + /** + * The <i>y</i> coordinate of the curve’s start point. + */ + public double y1; + + /** + * The <i>x</i> coordinate of the curve’s first control point. + */ + public double ctrlx1; + + /** + * The <i>y</i> coordinate of the curve’s first control point. + */ + public double ctrly1; + + /** + * The <i>x</i> coordinate of the curve’s second control point. + */ + public double ctrlx2; + + /** + * The <i>y</i> coordinate of the curve’s second control point. + */ + public double ctrly2; + + /** + * The <i>x</i> coordinate of the curve’s end point. + */ + public double x2; + + /** + * The <i>y</i> coordinate of the curve’s end point. + */ + public double y2; + + /** + * Constructs a new CubicCurve2D that stores its coordinate values + * in double-precision floating-point format. All points are + * initially at position (0, 0). + */ + public Double() + { + } + + /** + * Constructs a new CubicCurve2D that stores its coordinate values + * in double-precision floating-point format, specifying the + * initial position of each point. + * + * <p><img src="doc-files/CubicCurve2D-1.png" width="350" height="180" + * alt="A drawing of a CubicCurve2D" /> + * + * @param x1 the <i>x</i> coordinate of the curve’s start + * point. + * + * @param y1 the <i>y</i> coordinate of the curve’s start + * point. + * + * @param cx1 the <i>x</i> coordinate of the curve’s first + * control point. + * + * @param cy1 the <i>y</i> coordinate of the curve’s first + * control point. + * + * @param cx2 the <i>x</i> coordinate of the curve’s second + * control point. + * + * @param cy2 the <i>y</i> coordinate of the curve’s second + * control point. + * + * @param x2 the <i>x</i> coordinate of the curve’s end + * point. + * + * @param y2 the <i>y</i> coordinate of the curve’s end + * point. + */ + public Double(double x1, double y1, double cx1, double cy1, double cx2, + double cy2, double x2, double y2) + { + this.x1 = x1; + this.y1 = y1; + ctrlx1 = cx1; + ctrly1 = cy1; + ctrlx2 = cx2; + ctrly2 = cy2; + this.x2 = x2; + this.y2 = y2; + } + + /** + * Returns the <i>x</i> coordinate of the curve’s start + * point. + */ + public double getX1() + { + return x1; + } + + /** + * Returns the <i>y</i> coordinate of the curve’s start + * point. + */ + public double getY1() + { + return y1; + } + + /** + * Returns the curve’s start point. + */ + public Point2D getP1() + { + return new Point2D.Double(x1, y1); + } + + /** + * Returns the <i>x</i> coordinate of the curve’s first + * control point. + */ + public double getCtrlX1() + { + return ctrlx1; + } + + /** + * Returns the <i>y</i> coordinate of the curve’s first + * control point. + */ + public double getCtrlY1() + { + return ctrly1; + } + + /** + * Returns the curve’s first control point. + */ + public Point2D getCtrlP1() + { + return new Point2D.Double(ctrlx1, ctrly1); + } + + /** + * Returns the <i>x</i> coordinate of the curve’s second + * control point. + */ + public double getCtrlX2() + { + return ctrlx2; + } + + /** + * Returns the <i>y</i> coordinate of the curve’s second + * control point. + */ + public double getCtrlY2() + { + return ctrly2; + } + + /** + * Returns the curve’s second control point. + */ + public Point2D getCtrlP2() + { + return new Point2D.Double(ctrlx2, ctrly2); + } + + /** + * Returns the <i>x</i> coordinate of the curve’s end + * point. + */ + public double getX2() + { + return x2; + } + + /** + * Returns the <i>y</i> coordinate of the curve’s end + * point. + */ + public double getY2() + { + return y2; + } + + /** + * Returns the curve’s end point. + */ + public Point2D getP2() + { + return new Point2D.Double(x2, y2); + } + + /** + * Changes the curve geometry, separately specifying each coordinate + * value. + * + * <p><img src="doc-files/CubicCurve2D-1.png" width="350" height="180" + * alt="A drawing of a CubicCurve2D" /> + * + * @param x1 the <i>x</i> coordinate of the curve’s new start + * point. + * + * @param y1 the <i>y</i> coordinate of the curve’s new start + * point. + * + * @param cx1 the <i>x</i> coordinate of the curve’s new + * first control point. + * + * @param cy1 the <i>y</i> coordinate of the curve’s new + * first control point. + * + * @param cx2 the <i>x</i> coordinate of the curve’s new + * second control point. + * + * @param cy2 the <i>y</i> coordinate of the curve’s new + * second control point. + * + * @param x2 the <i>x</i> coordinate of the curve’s new end + * point. + * + * @param y2 the <i>y</i> coordinate of the curve’s new end + * point. + */ + public void setCurve(double x1, double y1, double cx1, double cy1, + double cx2, double cy2, double x2, double y2) + { + this.x1 = x1; + this.y1 = y1; + ctrlx1 = cx1; + ctrly1 = cy1; + ctrlx2 = cx2; + ctrly2 = cy2; + this.x2 = x2; + this.y2 = y2; + } + + /** + * Determines the smallest rectangle that encloses the + * curve’s start, end and control points. As the + * illustration below shows, the invisible control points may cause + * the bounds to be much larger than the area that is actually + * covered by the curve. + * + * <p><img src="doc-files/CubicCurve2D-2.png" width="350" height="180" + * alt="An illustration of the bounds of a CubicCurve2D" /> + */ + public Rectangle2D getBounds2D() + { + double nx1 = Math.min(Math.min(x1, ctrlx1), Math.min(ctrlx2, x2)); + double ny1 = Math.min(Math.min(y1, ctrly1), Math.min(ctrly2, y2)); + double nx2 = Math.max(Math.max(x1, ctrlx1), Math.max(ctrlx2, x2)); + double ny2 = Math.max(Math.max(y1, ctrly1), Math.max(ctrly2, y2)); + return new Rectangle2D.Double(nx1, ny1, nx2 - nx1, ny2 - ny1); + } + } + + /** + * A two-dimensional curve that is parameterized with a cubic + * function and stores coordinate values in single-precision + * floating-point format. + * + * @see CubicCurve2D.Float + * + * @author Eric Blake (ebb9@email.byu.edu) + * @author Sascha Brawer (brawer@dandelis.ch) + */ + public static class Float extends CubicCurve2D + { + /** + * The <i>x</i> coordinate of the curve’s start point. + */ + public float x1; + + /** + * The <i>y</i> coordinate of the curve’s start point. + */ + public float y1; + + /** + * The <i>x</i> coordinate of the curve’s first control point. + */ + public float ctrlx1; + + /** + * The <i>y</i> coordinate of the curve’s first control point. + */ + public float ctrly1; + + /** + * The <i>x</i> coordinate of the curve’s second control point. + */ + public float ctrlx2; + + /** + * The <i>y</i> coordinate of the curve’s second control point. + */ + public float ctrly2; + + /** + * The <i>x</i> coordinate of the curve’s end point. + */ + public float x2; + + /** + * The <i>y</i> coordinate of the curve’s end point. + */ + public float y2; + + /** + * Constructs a new CubicCurve2D that stores its coordinate values + * in single-precision floating-point format. All points are + * initially at position (0, 0). + */ + public Float() + { + } + + /** + * Constructs a new CubicCurve2D that stores its coordinate values + * in single-precision floating-point format, specifying the + * initial position of each point. + * + * <p><img src="doc-files/CubicCurve2D-1.png" width="350" height="180" + * alt="A drawing of a CubicCurve2D" /> + * + * @param x1 the <i>x</i> coordinate of the curve’s start + * point. + * + * @param y1 the <i>y</i> coordinate of the curve’s start + * point. + * + * @param cx1 the <i>x</i> coordinate of the curve’s first + * control point. + * + * @param cy1 the <i>y</i> coordinate of the curve’s first + * control point. + * + * @param cx2 the <i>x</i> coordinate of the curve’s second + * control point. + * + * @param cy2 the <i>y</i> coordinate of the curve’s second + * control point. + * + * @param x2 the <i>x</i> coordinate of the curve’s end + * point. + * + * @param y2 the <i>y</i> coordinate of the curve’s end + * point. + */ + public Float(float x1, float y1, float cx1, float cy1, float cx2, + float cy2, float x2, float y2) + { + this.x1 = x1; + this.y1 = y1; + ctrlx1 = cx1; + ctrly1 = cy1; + ctrlx2 = cx2; + ctrly2 = cy2; + this.x2 = x2; + this.y2 = y2; + } + + /** + * Returns the <i>x</i> coordinate of the curve’s start + * point. + */ + public double getX1() + { + return x1; + } + + /** + * Returns the <i>y</i> coordinate of the curve’s start + * point. + */ + public double getY1() + { + return y1; + } + + /** + * Returns the curve’s start point. + */ + public Point2D getP1() + { + return new Point2D.Float(x1, y1); + } + + /** + * Returns the <i>x</i> coordinate of the curve’s first + * control point. + */ + public double getCtrlX1() + { + return ctrlx1; + } + + /** + * Returns the <i>y</i> coordinate of the curve’s first + * control point. + */ + public double getCtrlY1() + { + return ctrly1; + } + + /** + * Returns the curve’s first control point. + */ + public Point2D getCtrlP1() + { + return new Point2D.Float(ctrlx1, ctrly1); + } + + /** + * Returns the <i>s</i> coordinate of the curve’s second + * control point. + */ + public double getCtrlX2() + { + return ctrlx2; + } + + /** + * Returns the <i>y</i> coordinate of the curve’s second + * control point. + */ + public double getCtrlY2() + { + return ctrly2; + } + + /** + * Returns the curve’s second control point. + */ + public Point2D getCtrlP2() + { + return new Point2D.Float(ctrlx2, ctrly2); + } + + /** + * Returns the <i>x</i> coordinate of the curve’s end + * point. + */ + public double getX2() + { + return x2; + } + + /** + * Returns the <i>y</i> coordinate of the curve’s end + * point. + */ + public double getY2() + { + return y2; + } + + /** + * Returns the curve’s end point. + */ + public Point2D getP2() + { + return new Point2D.Float(x2, y2); + } + + /** + * Changes the curve geometry, separately specifying each coordinate + * value as a double-precision floating-point number. + * + * <p><img src="doc-files/CubicCurve2D-1.png" width="350" height="180" + * alt="A drawing of a CubicCurve2D" /> + * + * @param x1 the <i>x</i> coordinate of the curve’s new start + * point. + * + * @param y1 the <i>y</i> coordinate of the curve’s new start + * point. + * + * @param cx1 the <i>x</i> coordinate of the curve’s new + * first control point. + * + * @param cy1 the <i>y</i> coordinate of the curve’s new + * first control point. + * + * @param cx2 the <i>x</i> coordinate of the curve’s new + * second control point. + * + * @param cy2 the <i>y</i> coordinate of the curve’s new + * second control point. + * + * @param x2 the <i>x</i> coordinate of the curve’s new end + * point. + * + * @param y2 the <i>y</i> coordinate of the curve’s new end + * point. + */ + public void setCurve(double x1, double y1, double cx1, double cy1, + double cx2, double cy2, double x2, double y2) + { + this.x1 = (float) x1; + this.y1 = (float) y1; + ctrlx1 = (float) cx1; + ctrly1 = (float) cy1; + ctrlx2 = (float) cx2; + ctrly2 = (float) cy2; + this.x2 = (float) x2; + this.y2 = (float) y2; + } + + /** + * Changes the curve geometry, separately specifying each coordinate + * value as a single-precision floating-point number. + * + * <p><img src="doc-files/CubicCurve2D-1.png" width="350" height="180" + * alt="A drawing of a CubicCurve2D" /> + * + * @param x1 the <i>x</i> coordinate of the curve’s new start + * point. + * + * @param y1 the <i>y</i> coordinate of the curve’s new start + * point. + * + * @param cx1 the <i>x</i> coordinate of the curve’s new + * first control point. + * + * @param cy1 the <i>y</i> coordinate of the curve’s new + * first control point. + * + * @param cx2 the <i>x</i> coordinate of the curve’s new + * second control point. + * + * @param cy2 the <i>y</i> coordinate of the curve’s new + * second control point. + * + * @param x2 the <i>x</i> coordinate of the curve’s new end + * point. + * + * @param y2 the <i>y</i> coordinate of the curve’s new end + * point. + */ + public void setCurve(float x1, float y1, float cx1, float cy1, float cx2, + float cy2, float x2, float y2) + { + this.x1 = x1; + this.y1 = y1; + ctrlx1 = cx1; + ctrly1 = cy1; + ctrlx2 = cx2; + ctrly2 = cy2; + this.x2 = x2; + this.y2 = y2; + } + + /** + * Determines the smallest rectangle that encloses the + * curve’s start, end and control points. As the + * illustration below shows, the invisible control points may cause + * the bounds to be much larger than the area that is actually + * covered by the curve. + * + * <p><img src="doc-files/CubicCurve2D-2.png" width="350" height="180" + * alt="An illustration of the bounds of a CubicCurve2D" /> + */ + public Rectangle2D getBounds2D() + { + float nx1 = Math.min(Math.min(x1, ctrlx1), Math.min(ctrlx2, x2)); + float ny1 = Math.min(Math.min(y1, ctrly1), Math.min(ctrly2, y2)); + float nx2 = Math.max(Math.max(x1, ctrlx1), Math.max(ctrlx2, x2)); + float ny2 = Math.max(Math.max(y1, ctrly1), Math.max(ctrly2, y2)); + return new Rectangle2D.Float(nx1, ny1, nx2 - nx1, ny2 - ny1); + } + } +} diff --git a/libjava/classpath/java/awt/geom/Dimension2D.java b/libjava/classpath/java/awt/geom/Dimension2D.java new file mode 100644 index 000000000..6b5ce8830 --- /dev/null +++ b/libjava/classpath/java/awt/geom/Dimension2D.java @@ -0,0 +1,118 @@ +/* Dimension2D.java -- abstraction of a dimension + Copyright (C) 1999, 2000, 2002 Free Software Foundation + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.awt.geom; + +/** + * This stores a dimension in 2-dimensional space - a width (along the x-axis) + * and height (along the y-axis). The storage is left to subclasses. + * + * @author Per Bothner (bothner@cygnus.com) + * @author Eric Blake (ebb9@email.byu.edu) + * @since 1.2 + * @status updated to 1.4 + */ +public abstract class Dimension2D implements Cloneable +{ + /** + * The default constructor. + */ + protected Dimension2D() + { + } + + /** + * Get the width of this dimension. A negative result, while legal, is + * undefined in meaning. + * + * @return the width + */ + public abstract double getWidth(); + + /** + * Get the height of this dimension. A negative result, while legal, is + * undefined in meaning. + * + * @return the height + */ + public abstract double getHeight(); + + /** + * Set the size of this dimension to the requested values. Loss of precision + * may occur. + * + * @param w the new width + * @param h the new height + */ + public abstract void setSize(double w, double h); + + /** + * Set the size of this dimension to the requested value. Loss of precision + * may occur. + * + * @param d the dimension containing the new values + * + * @throws NullPointerException if d is null + */ + public void setSize(Dimension2D d) + { + setSize(d.getWidth(), d.getHeight()); + } + + /** + * Create a new dimension of the same run-time type with the same contents + * as this one. + * + * @return the clone + * + * @exception OutOfMemoryError If there is not enough memory available. + * + * @since 1.2 + */ + public Object clone() + { + try + { + return super.clone(); + } + catch (CloneNotSupportedException e) + { + throw (Error) new InternalError().initCause(e); // Impossible + } + } +} // class Dimension2D diff --git a/libjava/classpath/java/awt/geom/Ellipse2D.java b/libjava/classpath/java/awt/geom/Ellipse2D.java new file mode 100644 index 000000000..3bbf2f010 --- /dev/null +++ b/libjava/classpath/java/awt/geom/Ellipse2D.java @@ -0,0 +1,413 @@ +/* Ellipse2D.java -- represents an ellipse in 2-D space + Copyright (C) 2000, 2002, 2004 Free Software Foundation + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package java.awt.geom; + + +/** + * Ellipse2D is the shape of an ellipse. + * <BR> + * <img src="doc-files/Ellipse-1.png" width="347" height="221" + * alt="A drawing of an ellipse" /><BR> + * The ellipse is defined by it's bounding box (shown in red), + * and is defined by the implicit curve:<BR> + * <blockquote>(<i>x</i>/<i>a</i>)<sup>2</sup> + + * (<i>y</i>/<i>b</i>)<sup>2</sup> = 1<BR><BR></blockquote> + * + * @author Tom Tromey (tromey@cygnus.com) + * @author Eric Blake (ebb9@email.byu.edu) + * + * @since 1.2 + */ +public abstract class Ellipse2D extends RectangularShape +{ + /** + * Ellipse2D is defined as abstract. + * Implementing classes are Ellipse2D.Float and Ellipse2D.Double. + */ + protected Ellipse2D() + { + } + + /** + * Determines if a point is contained within the ellipse. <P> + * @param x - x coordinate of the point. + * @param y - y coordinate of the point. + * @return true if the point is within the ellipse, false otherwise. + */ + public boolean contains(double x, double y) + { + double rx = getWidth() / 2; + double ry = getHeight() / 2; + double tx = (x - (getX() + rx)) / rx; + double ty = (y - (getY() + ry)) / ry; + return tx * tx + ty * ty < 1.0; + } + + /** + * Determines if a rectangle is completely contained within the + * ellipse. <P> + * @param x - x coordinate of the upper-left corner of the rectangle + * @param y - y coordinate of the upper-left corner of the rectangle + * @param w - width of the rectangle + * @param h - height of the rectangle + * @return true if the rectangle is completely contained, false otherwise. + */ + public boolean contains(double x, double y, double w, double h) + { + double x2 = x + w; + double y2 = y + h; + return (contains(x, y) && contains(x, y2) && contains(x2, y) + && contains(x2, y2)); + } + + /** + * Returns a PathIterator object corresponding to the ellipse.<P> + * + * Note: An ellipse cannot be represented exactly in PathIterator + * segments, the outline is thefore approximated with cubic + * Bezier segments. + * + * @param at an optional transform. + * @return A path iterator. + */ + public PathIterator getPathIterator(AffineTransform at) + { + // An ellipse is just a complete arc. + return new Arc2D.ArcIterator(this, at); + } + + /** + * Determines if a rectangle intersects any part of the ellipse.<P> + * @param x - x coordinate of the upper-left corner of the rectangle + * @param y - y coordinate of the upper-left corner of the rectangle + * @param w - width of the rectangle + * @param h - height of the rectangle + * @return true if the rectangle intersects the ellipse, false otherwise. + */ + public boolean intersects(double x, double y, double w, double h) + { + Rectangle2D r = new Rectangle2D.Double(x, y, w, h); + if (! r.intersects(getX(), getY(), getWidth(), getHeight())) + return false; + + if (contains(x, y) || contains(x, y + h) || contains(x + w, y) + || contains(x + w, y + h)) + return true; + + Line2D l1 = new Line2D.Double(getX(), getY() + (getHeight() / 2), + getX() + getWidth(), + getY() + (getHeight() / 2)); + Line2D l2 = new Line2D.Double(getX() + (getWidth() / 2), getY(), + getX() + (getWidth() / 2), + getY() + getHeight()); + + if (l1.intersects(r) || l2.intersects(r)) + return true; + + return false; + } + + /** + * An {@link Ellipse2D} that stores its coordinates using <code>double</code> + * primitives. + */ + public static class Double extends Ellipse2D + { + /** + * The height of the ellipse. + */ + public double height; + + /** + * The width of the ellipse. + */ + public double width; + + /** + * The upper-left x coordinate of the bounding-box + */ + public double x; + + /** + * The upper-left y coordinate of the bounding-box + */ + public double y; + + /** + * Creates a new Ellipse2D with an upper-left coordinate of (0,0) + * and a zero size. + */ + public Double() + { + } + + /** + * Creates a new Ellipse2D within a given rectangle + * using double-precision coordinates.<P> + * @param x - x coordinate of the upper-left of the bounding rectangle + * @param y - y coordinate of the upper-left of the bounding rectangle + * @param w - width of the ellipse + * @param h - height of the ellipse + */ + public Double(double x, double y, double w, double h) + { + this.x = x; + this.y = y; + height = h; + width = w; + } + + /** + * Returns the bounding-box of the ellipse. + * @return The bounding box. + */ + public Rectangle2D getBounds2D() + { + return new Rectangle2D.Double(x, y, width, height); + } + + /** + * Returns the height of the ellipse. + * @return The height of the ellipse. + */ + public double getHeight() + { + return height; + } + + /** + * Returns the width of the ellipse. + * @return The width of the ellipse. + */ + public double getWidth() + { + return width; + } + + /** + * Returns x coordinate of the upper-left corner of + * the ellipse's bounding-box. + * @return The x coordinate. + */ + public double getX() + { + return x; + } + + /** + * Returns y coordinate of the upper-left corner of + * the ellipse's bounding-box. + * @return The y coordinate. + */ + public double getY() + { + return y; + } + + /** + * Returns <code>true</code> if the ellipse encloses no area, and + * <code>false</code> otherwise. + * + * @return A boolean. + */ + public boolean isEmpty() + { + return height <= 0 || width <= 0; + } + + /** + * Sets the geometry of the ellipse's bounding box.<P> + * + * @param x - x coordinate of the upper-left of the bounding rectangle + * @param y - y coordinate of the upper-left of the bounding rectangle + * @param w - width of the ellipse + * @param h - height of the ellipse + */ + public void setFrame(double x, double y, double w, double h) + { + this.x = x; + this.y = y; + height = h; + width = w; + } + } // class Double + + /** + * An {@link Ellipse2D} that stores its coordinates using <code>float</code> + * primitives. + */ + public static class Float extends Ellipse2D + { + /** + * The height of the ellipse. + */ + public float height; + + /** + * The width of the ellipse. + */ + public float width; + + /** + * The upper-left x coordinate of the bounding-box + */ + public float x; + + /** + * The upper-left y coordinate of the bounding-box + */ + public float y; + + /** + * Creates a new Ellipse2D with an upper-left coordinate of (0,0) + * and a zero size. + */ + public Float() + { + } + + /** + * Creates a new Ellipse2D within a given rectangle + * using floating-point precision.<P> + * @param x - x coordinate of the upper-left of the bounding rectangle + * @param y - y coordinate of the upper-left of the bounding rectangle + * @param w - width of the ellipse + * @param h - height of the ellipse + * + */ + public Float(float x, float y, float w, float h) + { + this.x = x; + this.y = y; + this.height = h; + this.width = w; + } + + /** + * Returns the bounding-box of the ellipse. + * @return The bounding box. + */ + public Rectangle2D getBounds2D() + { + return new Rectangle2D.Float(x, y, width, height); + } + + /** + * Returns the height of the ellipse. + * @return The height of the ellipse. + */ + public double getHeight() + { + return height; + } + + /** + * Returns the width of the ellipse. + * @return The width of the ellipse. + */ + public double getWidth() + { + return width; + } + + /** + * Returns x coordinate of the upper-left corner of + * the ellipse's bounding-box. + * @return The x coordinate. + */ + public double getX() + { + return x; + } + + /** + * Returns y coordinate of the upper-left corner of + * the ellipse's bounding-box. + * @return The y coordinate. + */ + public double getY() + { + return y; + } + + /** + * Returns <code>true</code> if the ellipse encloses no area, and + * <code>false</code> otherwise. + * + * @return A boolean. + */ + public boolean isEmpty() + { + return height <= 0 || width <= 0; + } + + /** + * Sets the geometry of the ellipse's bounding box.<P> + * + * @param x - x coordinate of the upper-left of the bounding rectangle + * @param y - y coordinate of the upper-left of the bounding rectangle + * @param w - width of the ellipse + * @param h - height of the ellipse + */ + public void setFrame(float x, float y, float w, float h) + { + this.x = x; + this.y = y; + height = h; + width = w; + } + + /** + * Sets the geometry of the ellipse's bounding box. + * + * Note: This leads to a loss of precision.<P> + * + * @param x - x coordinate of the upper-left of the bounding rectangle + * @param y - y coordinate of the upper-left of the bounding rectangle + * @param w - width of the ellipse + * @param h - height of the ellipse + */ + public void setFrame(double x, double y, double w, double h) + { + this.x = (float) x; + this.y = (float) y; + height = (float) h; + width = (float) w; + } + } // class Float +} // class Ellipse2D diff --git a/libjava/classpath/java/awt/geom/FlatteningPathIterator.java b/libjava/classpath/java/awt/geom/FlatteningPathIterator.java new file mode 100644 index 000000000..629936bf7 --- /dev/null +++ b/libjava/classpath/java/awt/geom/FlatteningPathIterator.java @@ -0,0 +1,579 @@ +/* FlatteningPathIterator.java -- Approximates curves by straight lines + Copyright (C) 2003 Free Software Foundation + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.awt.geom; + +import java.util.NoSuchElementException; + + +/** + * A PathIterator for approximating curved path segments by sequences + * of straight lines. Instances of this class will only return + * segments of type {@link PathIterator#SEG_MOVETO}, {@link + * PathIterator#SEG_LINETO}, and {@link PathIterator#SEG_CLOSE}. + * + * <p>The accuracy of the approximation is determined by two + * parameters: + * + * <ul><li>The <i>flatness</i> is a threshold value for deciding when + * a curved segment is consided flat enough for being approximated by + * a single straight line. Flatness is defined as the maximal distance + * of a curve control point to the straight line that connects the + * curve start and end. A lower flatness threshold means a closer + * approximation. See {@link QuadCurve2D#getFlatness()} and {@link + * CubicCurve2D#getFlatness()} for drawings which illustrate the + * meaning of flatness.</li> + * + * <li>The <i>recursion limit</i> imposes an upper bound for how often + * a curved segment gets subdivided. A limit of <i>n</i> means that + * for each individual quadratic and cubic Bézier spline + * segment, at most 2<sup><small><i>n</i></small></sup> {@link + * PathIterator#SEG_LINETO} segments will be created.</li></ul> + * + * <p><b>Memory Efficiency:</b> The memory consumption grows linearly + * with the recursion limit. Neither the <i>flatness</i> parameter nor + * the number of segments in the flattened path will affect the memory + * consumption. + * + * <p><b>Thread Safety:</b> Multiple threads can safely work on + * separate instances of this class. However, multiple threads should + * not concurrently access the same instance, as no synchronization is + * performed. + * + * @see <a href="doc-files/FlatteningPathIterator-1.html" + * >Implementation Note</a> + * + * @author Sascha Brawer (brawer@dandelis.ch) + * + * @since 1.2 + */ +public class FlatteningPathIterator + implements PathIterator +{ + /** + * The PathIterator whose curved segments are being approximated. + */ + private final PathIterator srcIter; + + + /** + * The square of the flatness threshold value, which determines when + * a curve segment is considered flat enough that no further + * subdivision is needed. + * + * <p>Calculating flatness actually produces the squared flatness + * value. To avoid the relatively expensive calculation of a square + * root for each curve segment, we perform all flatness comparisons + * on squared values. + * + * @see QuadCurve2D#getFlatnessSq() + * @see CubicCurve2D#getFlatnessSq() + */ + private final double flatnessSq; + + + /** + * The maximal number of subdivions that are performed to + * approximate a quadratic or cubic curve segment. + */ + private final int recursionLimit; + + + /** + * A stack for holding the coordinates of subdivided segments. + * + * @see <a href="doc-files/FlatteningPathIterator-1.html" + * >Implementation Note</a> + */ + private double[] stack; + + + /** + * The current stack size. + * + * @see <a href="doc-files/FlatteningPathIterator-1.html" + * >Implementation Note</a> + */ + private int stackSize; + + + /** + * The number of recursions that were performed to arrive at + * a segment on the stack. + * + * @see <a href="doc-files/FlatteningPathIterator-1.html" + * >Implementation Note</a> + */ + private int[] recLevel; + + + + private final double[] scratch = new double[6]; + + + /** + * The segment type of the last segment that was returned by + * the source iterator. + */ + private int srcSegType; + + + /** + * The current <i>x</i> position of the source iterator. + */ + private double srcPosX; + + + /** + * The current <i>y</i> position of the source iterator. + */ + private double srcPosY; + + + /** + * A flag that indicates when this path iterator has finished its + * iteration over path segments. + */ + private boolean done; + + + /** + * Constructs a new PathIterator for approximating an input + * PathIterator with straight lines. The approximation works by + * recursive subdivisons, until the specified flatness threshold is + * not exceeded. + * + * <p>There will not be more than 10 nested recursion steps, which + * means that a single <code>SEG_QUADTO</code> or + * <code>SEG_CUBICTO</code> segment is approximated by at most + * 2<sup><small>10</small></sup> = 1024 straight lines. + */ + public FlatteningPathIterator(PathIterator src, double flatness) + { + this(src, flatness, 10); + } + + + /** + * Constructs a new PathIterator for approximating an input + * PathIterator with straight lines. The approximation works by + * recursive subdivisons, until the specified flatness threshold is + * not exceeded. Additionally, the number of recursions is also + * bound by the specified recursion limit. + */ + public FlatteningPathIterator(PathIterator src, double flatness, + int limit) + { + if (flatness < 0 || limit < 0) + throw new IllegalArgumentException(); + + srcIter = src; + flatnessSq = flatness * flatness; + recursionLimit = limit; + fetchSegment(); + } + + + /** + * Returns the maximally acceptable flatness. + * + * @see QuadCurve2D#getFlatness() + * @see CubicCurve2D#getFlatness() + */ + public double getFlatness() + { + return Math.sqrt(flatnessSq); + } + + + /** + * Returns the maximum number of recursive curve subdivisions. + */ + public int getRecursionLimit() + { + return recursionLimit; + } + + + // Documentation will be copied from PathIterator. + public int getWindingRule() + { + return srcIter.getWindingRule(); + } + + + // Documentation will be copied from PathIterator. + public boolean isDone() + { + return done; + } + + + // Documentation will be copied from PathIterator. + public void next() + { + if (stackSize > 0) + { + --stackSize; + if (stackSize > 0) + { + switch (srcSegType) + { + case PathIterator.SEG_QUADTO: + subdivideQuadratic(); + return; + + case PathIterator.SEG_CUBICTO: + subdivideCubic(); + return; + + default: + throw new IllegalStateException(); + } + } + } + + srcIter.next(); + fetchSegment(); + } + + + // Documentation will be copied from PathIterator. + public int currentSegment(double[] coords) + { + if (done) + throw new NoSuchElementException(); + + switch (srcSegType) + { + case PathIterator.SEG_CLOSE: + return srcSegType; + + case PathIterator.SEG_MOVETO: + case PathIterator.SEG_LINETO: + coords[0] = srcPosX; + coords[1] = srcPosY; + return srcSegType; + + case PathIterator.SEG_QUADTO: + if (stackSize == 0) + { + coords[0] = srcPosX; + coords[1] = srcPosY; + } + else + { + int sp = stack.length - 4 * stackSize; + coords[0] = stack[sp + 2]; + coords[1] = stack[sp + 3]; + } + return PathIterator.SEG_LINETO; + + case PathIterator.SEG_CUBICTO: + if (stackSize == 0) + { + coords[0] = srcPosX; + coords[1] = srcPosY; + } + else + { + int sp = stack.length - 6 * stackSize; + coords[0] = stack[sp + 4]; + coords[1] = stack[sp + 5]; + } + return PathIterator.SEG_LINETO; + } + + throw new IllegalStateException(); + } + + + // Documentation will be copied from PathIterator. + public int currentSegment(float[] coords) + { + if (done) + throw new NoSuchElementException(); + + switch (srcSegType) + { + case PathIterator.SEG_CLOSE: + return srcSegType; + + case PathIterator.SEG_MOVETO: + case PathIterator.SEG_LINETO: + coords[0] = (float) srcPosX; + coords[1] = (float) srcPosY; + return srcSegType; + + case PathIterator.SEG_QUADTO: + if (stackSize == 0) + { + coords[0] = (float) srcPosX; + coords[1] = (float) srcPosY; + } + else + { + int sp = stack.length - 4 * stackSize; + coords[0] = (float) stack[sp + 2]; + coords[1] = (float) stack[sp + 3]; + } + return PathIterator.SEG_LINETO; + + case PathIterator.SEG_CUBICTO: + if (stackSize == 0) + { + coords[0] = (float) srcPosX; + coords[1] = (float) srcPosY; + } + else + { + int sp = stack.length - 6 * stackSize; + coords[0] = (float) stack[sp + 4]; + coords[1] = (float) stack[sp + 5]; + } + return PathIterator.SEG_LINETO; + } + + throw new IllegalStateException(); + } + + + /** + * Fetches the next segment from the source iterator. + */ + private void fetchSegment() + { + int sp; + + if (srcIter.isDone()) + { + done = true; + return; + } + + srcSegType = srcIter.currentSegment(scratch); + + switch (srcSegType) + { + case PathIterator.SEG_CLOSE: + return; + + case PathIterator.SEG_MOVETO: + case PathIterator.SEG_LINETO: + srcPosX = scratch[0]; + srcPosY = scratch[1]; + return; + + case PathIterator.SEG_QUADTO: + if (recursionLimit == 0) + { + srcPosX = scratch[2]; + srcPosY = scratch[3]; + stackSize = 0; + return; + } + sp = 4 * recursionLimit; + stackSize = 1; + if (stack == null) + { + stack = new double[sp + /* 4 + 2 */ 6]; + recLevel = new int[recursionLimit + 1]; + } + recLevel[0] = 0; + stack[sp] = srcPosX; // P1.x + stack[sp + 1] = srcPosY; // P1.y + stack[sp + 2] = scratch[0]; // C.x + stack[sp + 3] = scratch[1]; // C.y + srcPosX = stack[sp + 4] = scratch[2]; // P2.x + srcPosY = stack[sp + 5] = scratch[3]; // P2.y + subdivideQuadratic(); + break; + + case PathIterator.SEG_CUBICTO: + if (recursionLimit == 0) + { + srcPosX = scratch[4]; + srcPosY = scratch[5]; + stackSize = 0; + return; + } + sp = 6 * recursionLimit; + stackSize = 1; + if ((stack == null) || (stack.length < sp + 8)) + { + stack = new double[sp + /* 6 + 2 */ 8]; + recLevel = new int[recursionLimit + 1]; + } + recLevel[0] = 0; + stack[sp] = srcPosX; // P1.x + stack[sp + 1] = srcPosY; // P1.y + stack[sp + 2] = scratch[0]; // C1.x + stack[sp + 3] = scratch[1]; // C1.y + stack[sp + 4] = scratch[2]; // C2.x + stack[sp + 5] = scratch[3]; // C2.y + srcPosX = stack[sp + 6] = scratch[4]; // P2.x + srcPosY = stack[sp + 7] = scratch[5]; // P2.y + subdivideCubic(); + return; + } + } + + + /** + * Repeatedly subdivides the quadratic curve segment that is on top + * of the stack. The iteration terminates when the recursion limit + * has been reached, or when the resulting segment is flat enough. + */ + private void subdivideQuadratic() + { + int sp; + int level; + + sp = stack.length - 4 * stackSize - 2; + level = recLevel[stackSize - 1]; + while ((level < recursionLimit) + && (QuadCurve2D.getFlatnessSq(stack, sp) >= flatnessSq)) + { + recLevel[stackSize] = recLevel[stackSize - 1] = ++level; + QuadCurve2D.subdivide(stack, sp, stack, sp - 4, stack, sp); + ++stackSize; + sp -= 4; + } + } + + + /** + * Repeatedly subdivides the cubic curve segment that is on top + * of the stack. The iteration terminates when the recursion limit + * has been reached, or when the resulting segment is flat enough. + */ + private void subdivideCubic() + { + int sp; + int level; + + sp = stack.length - 6 * stackSize - 2; + level = recLevel[stackSize - 1]; + while ((level < recursionLimit) + && (CubicCurve2D.getFlatnessSq(stack, sp) >= flatnessSq)) + { + recLevel[stackSize] = recLevel[stackSize - 1] = ++level; + + CubicCurve2D.subdivide(stack, sp, stack, sp - 6, stack, sp); + ++stackSize; + sp -= 6; + } + } + + + /* These routines were useful for debugging. Since they would + * just bloat the implementation, they are commented out. + * + * + + private static String segToString(int segType, double[] d, int offset) + { + String s; + + switch (segType) + { + case PathIterator.SEG_CLOSE: + return "SEG_CLOSE"; + + case PathIterator.SEG_MOVETO: + return "SEG_MOVETO (" + d[offset] + ", " + d[offset + 1] + ")"; + + case PathIterator.SEG_LINETO: + return "SEG_LINETO (" + d[offset] + ", " + d[offset + 1] + ")"; + + case PathIterator.SEG_QUADTO: + return "SEG_QUADTO (" + d[offset] + ", " + d[offset + 1] + + ") (" + d[offset + 2] + ", " + d[offset + 3] + ")"; + + case PathIterator.SEG_CUBICTO: + return "SEG_CUBICTO (" + d[offset] + ", " + d[offset + 1] + + ") (" + d[offset + 2] + ", " + d[offset + 3] + + ") (" + d[offset + 4] + ", " + d[offset + 5] + ")"; + } + + throw new IllegalStateException(); + } + + + private void dumpQuadraticStack(String msg) + { + int sp = stack.length - 4 * stackSize - 2; + int i = 0; + System.err.print(" " + msg + ":"); + while (sp < stack.length) + { + System.err.print(" (" + stack[sp] + ", " + stack[sp+1] + ")"); + if (i < recLevel.length) + System.out.print("/" + recLevel[i++]); + if (sp + 3 < stack.length) + System.err.print(" [" + stack[sp+2] + ", " + stack[sp+3] + "]"); + sp += 4; + } + System.err.println(); + } + + + private void dumpCubicStack(String msg) + { + int sp = stack.length - 6 * stackSize - 2; + int i = 0; + System.err.print(" " + msg + ":"); + while (sp < stack.length) + { + System.err.print(" (" + stack[sp] + ", " + stack[sp+1] + ")"); + if (i < recLevel.length) + System.out.print("/" + recLevel[i++]); + if (sp + 3 < stack.length) + { + System.err.print(" [" + stack[sp+2] + ", " + stack[sp+3] + "]"); + System.err.print(" [" + stack[sp+4] + ", " + stack[sp+5] + "]"); + } + sp += 6; + } + System.err.println(); + } + + * + * + */ +} diff --git a/libjava/classpath/java/awt/geom/GeneralPath.java b/libjava/classpath/java/awt/geom/GeneralPath.java new file mode 100644 index 000000000..99f1905e2 --- /dev/null +++ b/libjava/classpath/java/awt/geom/GeneralPath.java @@ -0,0 +1,992 @@ +/* GeneralPath.java -- represents a shape built from subpaths + Copyright (C) 2002, 2003, 2004, 2006 Free Software Foundation + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.awt.geom; + +import java.awt.Rectangle; +import java.awt.Shape; + + +/** + * A general geometric path, consisting of any number of subpaths + * constructed out of straight lines and cubic or quadratic Bezier + * curves. + * + * <p>The inside of the curve is defined for drawing purposes by a winding + * rule. Either the WIND_EVEN_ODD or WIND_NON_ZERO winding rule can be chosen. + * + * <p><img src="doc-files/GeneralPath-1.png" width="300" height="210" + * alt="A drawing of a GeneralPath" /> + * <p>The EVEN_ODD winding rule defines a point as inside a path if: + * A ray from the point towards infinity in an arbitrary direction + * intersects the path an odd number of times. Points <b>A</b> and + * <b>C</b> in the image are considered to be outside the path. + * (both intersect twice) + * Point <b>B</b> intersects once, and is inside. + * + * <p>The NON_ZERO winding rule defines a point as inside a path if: + * The path intersects the ray in an equal number of opposite directions. + * Point <b>A</b> in the image is outside (one intersection in the + * ’up’ + * direction, one in the ’down’ direction) Point <b>B</b> in + * the image is inside (one intersection ’down’) + * Point <b>C</b> in the image is inside (two intersections in the + * ’down’ direction) + * + * @see Line2D + * @see CubicCurve2D + * @see QuadCurve2D + * + * @author Sascha Brawer (brawer@dandelis.ch) + * @author Sven de Marothy (sven@physto.se) + * + * @since 1.2 + */ +public final class GeneralPath implements Shape, Cloneable +{ + /** Same constant as {@link PathIterator#WIND_EVEN_ODD}. */ + public static final int WIND_EVEN_ODD = PathIterator.WIND_EVEN_ODD; + + /** Same constant as {@link PathIterator#WIND_NON_ZERO}. */ + public static final int WIND_NON_ZERO = PathIterator.WIND_NON_ZERO; + + /** Initial size if not specified. */ + private static final int INIT_SIZE = 10; + + /** A big number, but not so big it can't survive a few float operations */ + private static final double BIG_VALUE = Double.MAX_VALUE / 10.0; + + /** The winding rule. + * This is package-private to avoid an accessor method. + */ + int rule; + + /** + * The path type in points. Note that xpoints[index] and ypoints[index] maps + * to types[index]; the control points of quad and cubic paths map as + * well but are ignored. + * This is package-private to avoid an accessor method. + */ + byte[] types; + + /** + * The list of all points seen. Since you can only append floats, it makes + * sense for these to be float[]. I have no idea why Sun didn't choose to + * allow a general path of double precision points. + * Note: Storing x and y coords seperately makes for a slower transforms, + * But it speeds up and simplifies box-intersection checking a lot. + * These are package-private to avoid accessor methods. + */ + float[] xpoints; + float[] ypoints; + + /** The index of the most recent moveto point, or null. */ + private int subpath = -1; + + /** The next available index into points. + * This is package-private to avoid an accessor method. + */ + int index; + + /** + * Constructs a GeneralPath with the default (NON_ZERO) + * winding rule and initial capacity (20). + */ + public GeneralPath() + { + this(WIND_NON_ZERO, INIT_SIZE); + } + + /** + * Constructs a GeneralPath with a specific winding rule + * and the default initial capacity (20). + * @param rule the winding rule ({@link #WIND_NON_ZERO} or + * {@link #WIND_EVEN_ODD}) + * + * @throws IllegalArgumentException if <code>rule</code> is not one of the + * listed values. + */ + public GeneralPath(int rule) + { + this(rule, INIT_SIZE); + } + + /** + * Constructs a GeneralPath with a specific winding rule + * and the initial capacity. The initial capacity should be + * the approximate number of path segments to be used. + * @param rule the winding rule ({@link #WIND_NON_ZERO} or + * {@link #WIND_EVEN_ODD}) + * @param capacity the inital capacity, in path segments + * + * @throws IllegalArgumentException if <code>rule</code> is not one of the + * listed values. + */ + public GeneralPath(int rule, int capacity) + { + if (rule != WIND_EVEN_ODD && rule != WIND_NON_ZERO) + throw new IllegalArgumentException(); + this.rule = rule; + if (capacity < INIT_SIZE) + capacity = INIT_SIZE; + types = new byte[capacity]; + xpoints = new float[capacity]; + ypoints = new float[capacity]; + } + + /** + * Constructs a GeneralPath from an arbitrary shape object. + * The Shapes PathIterator path and winding rule will be used. + * + * @param s the shape (<code>null</code> not permitted). + * + * @throws NullPointerException if <code>shape</code> is <code>null</code>. + */ + public GeneralPath(Shape s) + { + types = new byte[INIT_SIZE]; + xpoints = new float[INIT_SIZE]; + ypoints = new float[INIT_SIZE]; + PathIterator pi = s.getPathIterator(null); + setWindingRule(pi.getWindingRule()); + append(pi, false); + } + + /** + * Adds a new point to a path. + * + * @param x the x-coordinate. + * @param y the y-coordinate. + */ + public void moveTo(float x, float y) + { + subpath = index; + ensureSize(index + 1); + types[index] = PathIterator.SEG_MOVETO; + xpoints[index] = x; + ypoints[index++] = y; + } + + /** + * Appends a straight line to the current path. + * @param x x coordinate of the line endpoint. + * @param y y coordinate of the line endpoint. + */ + public void lineTo(float x, float y) + { + ensureSize(index + 1); + types[index] = PathIterator.SEG_LINETO; + xpoints[index] = x; + ypoints[index++] = y; + } + + /** + * Appends a quadratic Bezier curve to the current path. + * @param x1 x coordinate of the control point + * @param y1 y coordinate of the control point + * @param x2 x coordinate of the curve endpoint. + * @param y2 y coordinate of the curve endpoint. + */ + public void quadTo(float x1, float y1, float x2, float y2) + { + ensureSize(index + 2); + types[index] = PathIterator.SEG_QUADTO; + xpoints[index] = x1; + ypoints[index++] = y1; + xpoints[index] = x2; + ypoints[index++] = y2; + } + + /** + * Appends a cubic Bezier curve to the current path. + * @param x1 x coordinate of the first control point + * @param y1 y coordinate of the first control point + * @param x2 x coordinate of the second control point + * @param y2 y coordinate of the second control point + * @param x3 x coordinate of the curve endpoint. + * @param y3 y coordinate of the curve endpoint. + */ + public void curveTo(float x1, float y1, float x2, float y2, float x3, + float y3) + { + ensureSize(index + 3); + types[index] = PathIterator.SEG_CUBICTO; + xpoints[index] = x1; + ypoints[index++] = y1; + xpoints[index] = x2; + ypoints[index++] = y2; + xpoints[index] = x3; + ypoints[index++] = y3; + } + + /** + * Closes the current subpath by drawing a line + * back to the point of the last moveTo, unless the path is already closed. + */ + public void closePath() + { + if (index >= 1 && types[index - 1] == PathIterator.SEG_CLOSE) + return; + ensureSize(index + 1); + types[index] = PathIterator.SEG_CLOSE; + xpoints[index] = xpoints[subpath]; + ypoints[index++] = ypoints[subpath]; + } + + /** + * Appends the segments of a Shape to the path. If <code>connect</code> is + * true, the new path segments are connected to the existing one with a line. + * The winding rule of the Shape is ignored. + * + * @param s the shape (<code>null</code> not permitted). + * @param connect whether to connect the new shape to the existing path. + * + * @throws NullPointerException if <code>s</code> is <code>null</code>. + */ + public void append(Shape s, boolean connect) + { + append(s.getPathIterator(null), connect); + } + + /** + * Appends the segments of a PathIterator to this GeneralPath. + * Optionally, the initial {@link PathIterator#SEG_MOVETO} segment + * of the appended path is changed into a {@link + * PathIterator#SEG_LINETO} segment. + * + * @param iter the PathIterator specifying which segments shall be + * appended (<code>null</code> not permitted). + * + * @param connect <code>true</code> for substituting the initial + * {@link PathIterator#SEG_MOVETO} segment by a {@link + * PathIterator#SEG_LINETO}, or <code>false</code> for not + * performing any substitution. If this GeneralPath is currently + * empty, <code>connect</code> is assumed to be <code>false</code>, + * thus leaving the initial {@link PathIterator#SEG_MOVETO} + * unchanged. + */ + public void append(PathIterator iter, boolean connect) + { + // A bad implementation of this method had caused Classpath bug #6076. + float[] f = new float[6]; + while (! iter.isDone()) + { + switch (iter.currentSegment(f)) + { + case PathIterator.SEG_MOVETO: + if (! connect || (index == 0)) + { + moveTo(f[0], f[1]); + break; + } + if ((index >= 1) && (types[index - 1] == PathIterator.SEG_CLOSE) + && (f[0] == xpoints[index - 1]) + && (f[1] == ypoints[index - 1])) + break; + + // Fall through. + case PathIterator.SEG_LINETO: + lineTo(f[0], f[1]); + break; + case PathIterator.SEG_QUADTO: + quadTo(f[0], f[1], f[2], f[3]); + break; + case PathIterator.SEG_CUBICTO: + curveTo(f[0], f[1], f[2], f[3], f[4], f[5]); + break; + case PathIterator.SEG_CLOSE: + closePath(); + break; + } + + connect = false; + iter.next(); + } + } + + /** + * Returns the path’s current winding rule. + * + * @return {@link #WIND_EVEN_ODD} or {@link #WIND_NON_ZERO}. + */ + public int getWindingRule() + { + return rule; + } + + /** + * Sets the path’s winding rule, which controls which areas are + * considered ’inside’ or ’outside’ the path + * on drawing. Valid rules are WIND_EVEN_ODD for an even-odd winding rule, + * or WIND_NON_ZERO for a non-zero winding rule. + * + * @param rule the rule ({@link #WIND_EVEN_ODD} or {@link #WIND_NON_ZERO}). + */ + public void setWindingRule(int rule) + { + if (rule != WIND_EVEN_ODD && rule != WIND_NON_ZERO) + throw new IllegalArgumentException(); + this.rule = rule; + } + + /** + * Returns the current appending point of the path. + * + * @return The point. + */ + public Point2D getCurrentPoint() + { + if (subpath < 0) + return null; + return new Point2D.Float(xpoints[index - 1], ypoints[index - 1]); + } + + /** + * Resets the path. All points and segments are destroyed. + */ + public void reset() + { + subpath = -1; + index = 0; + } + + /** + * Applies a transform to the path. + * + * @param xform the transform (<code>null</code> not permitted). + */ + public void transform(AffineTransform xform) + { + double nx; + double ny; + double[] m = new double[6]; + xform.getMatrix(m); + for (int i = 0; i < index; i++) + { + nx = m[0] * xpoints[i] + m[2] * ypoints[i] + m[4]; + ny = m[1] * xpoints[i] + m[3] * ypoints[i] + m[5]; + xpoints[i] = (float) nx; + ypoints[i] = (float) ny; + } + } + + /** + * Creates a transformed version of the path. + * @param xform the transform to apply + * @return a new transformed GeneralPath + */ + public Shape createTransformedShape(AffineTransform xform) + { + GeneralPath p = new GeneralPath(this); + p.transform(xform); + return p; + } + + /** + * Returns the path’s bounding box. + */ + public Rectangle getBounds() + { + return getBounds2D().getBounds(); + } + + /** + * Returns the path’s bounding box, in <code>float</code> precision + */ + public Rectangle2D getBounds2D() + { + float x1; + float y1; + float x2; + float y2; + + if (index > 0) + { + x1 = x2 = xpoints[0]; + y1 = y2 = ypoints[0]; + } + else + x1 = x2 = y1 = y2 = 0.0f; + + for (int i = 0; i < index; i++) + { + x1 = Math.min(xpoints[i], x1); + y1 = Math.min(ypoints[i], y1); + x2 = Math.max(xpoints[i], x2); + y2 = Math.max(ypoints[i], y2); + } + return (new Rectangle2D.Float(x1, y1, x2 - x1, y2 - y1)); + } + + /** + * Evaluates if a point is within the GeneralPath, + * The NON_ZERO winding rule is used, regardless of the + * set winding rule. + * @param x x coordinate of the point to evaluate + * @param y y coordinate of the point to evaluate + * @return true if the point is within the path, false otherwise + */ + public boolean contains(double x, double y) + { + return (getWindingNumber(x, y) != 0); + } + + /** + * Evaluates if a Point2D is within the GeneralPath, + * The NON_ZERO winding rule is used, regardless of the + * set winding rule. + * @param p The Point2D to evaluate + * @return true if the point is within the path, false otherwise + */ + public boolean contains(Point2D p) + { + return contains(p.getX(), p.getY()); + } + + /** + * Evaluates if a rectangle is completely contained within the path. + * This method will return false in the cases when the box + * intersects an inner segment of the path. + * (i.e.: The method is accurate for the EVEN_ODD winding rule) + */ + public boolean contains(double x, double y, double w, double h) + { + if (! getBounds2D().intersects(x, y, w, h)) + return false; + + /* Does any edge intersect? */ + if (getAxisIntersections(x, y, false, w) != 0 /* top */ + || getAxisIntersections(x, y + h, false, w) != 0 /* bottom */ + || getAxisIntersections(x + w, y, true, h) != 0 /* right */ + || getAxisIntersections(x, y, true, h) != 0) /* left */ + return false; + + /* No intersections, is any point inside? */ + if (getWindingNumber(x, y) != 0) + return true; + + return false; + } + + /** + * Evaluates if a rectangle is completely contained within the path. + * This method will return false in the cases when the box + * intersects an inner segment of the path. + * (i.e.: The method is accurate for the EVEN_ODD winding rule) + * @param r the rectangle + * @return <code>true</code> if the rectangle is completely contained + * within the path, <code>false</code> otherwise + */ + public boolean contains(Rectangle2D r) + { + return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + /** + * Evaluates if a rectangle intersects the path. + * @param x x coordinate of the rectangle + * @param y y coordinate of the rectangle + * @param w width of the rectangle + * @param h height of the rectangle + * @return <code>true</code> if the rectangle intersects the path, + * <code>false</code> otherwise + */ + public boolean intersects(double x, double y, double w, double h) + { + /* Does any edge intersect? */ + if (getAxisIntersections(x, y, false, w) != 0 /* top */ + || getAxisIntersections(x, y + h, false, w) != 0 /* bottom */ + || getAxisIntersections(x + w, y, true, h) != 0 /* right */ + || getAxisIntersections(x, y, true, h) != 0) /* left */ + return true; + + /* No intersections, is any point inside? */ + if (getWindingNumber(x, y) != 0) + return true; + + return false; + } + + /** + * Evaluates if a Rectangle2D intersects the path. + * @param r The rectangle + * @return <code>true</code> if the rectangle intersects the path, + * <code>false</code> otherwise + */ + public boolean intersects(Rectangle2D r) + { + return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + /** + * A PathIterator that iterates over the segments of a GeneralPath. + * + * @author Sascha Brawer (brawer@dandelis.ch) + */ + private static class GeneralPathIterator implements PathIterator + { + /** + * The number of coordinate values for each segment type. + */ + private static final int[] NUM_COORDS = { + /* 0: SEG_MOVETO */ 1, + /* 1: SEG_LINETO */ 1, + /* 2: SEG_QUADTO */ 2, + /* 3: SEG_CUBICTO */ 3, + /* 4: SEG_CLOSE */ 0}; + + /** + * The GeneralPath whose segments are being iterated. + * This is package-private to avoid an accessor method. + */ + final GeneralPath path; + + /** + * The affine transformation used to transform coordinates. + */ + private final AffineTransform transform; + + /** + * The current position of the iterator. + */ + private int pos; + + /** + * Constructs a new iterator for enumerating the segments of a + * GeneralPath. + * + * @param path the path to enumerate + * @param transform an affine transformation for projecting the returned + * points, or <code>null</code> to return the original points + * without any mapping. + */ + GeneralPathIterator(GeneralPath path, AffineTransform transform) + { + this.path = path; + this.transform = transform; + } + + /** + * Returns the current winding rule of the GeneralPath. + */ + public int getWindingRule() + { + return path.rule; + } + + /** + * Determines whether the iterator has reached the last segment in + * the path. + */ + public boolean isDone() + { + return pos >= path.index; + } + + /** + * Advances the iterator position by one segment. + */ + public void next() + { + int seg; + + /* + * Increment pos by the number of coordinate pairs. + */ + seg = path.types[pos]; + if (seg == SEG_CLOSE) + pos++; + else + pos += NUM_COORDS[seg]; + } + + /** + * Returns the current segment in float coordinates. + */ + public int currentSegment(float[] coords) + { + int seg; + int numCoords; + + seg = path.types[pos]; + numCoords = NUM_COORDS[seg]; + if (numCoords > 0) + { + for (int i = 0; i < numCoords; i++) + { + coords[i << 1] = path.xpoints[pos + i]; + coords[(i << 1) + 1] = path.ypoints[pos + i]; + } + + if (transform != null) + transform.transform( /* src */ + coords, /* srcOffset */ + 0, /* dest */ coords, /* destOffset */ + 0, /* numPoints */ numCoords); + } + return seg; + } + + /** + * Returns the current segment in double coordinates. + */ + public int currentSegment(double[] coords) + { + int seg; + int numCoords; + + seg = path.types[pos]; + numCoords = NUM_COORDS[seg]; + if (numCoords > 0) + { + for (int i = 0; i < numCoords; i++) + { + coords[i << 1] = (double) path.xpoints[pos + i]; + coords[(i << 1) + 1] = (double) path.ypoints[pos + i]; + } + if (transform != null) + transform.transform( /* src */ + coords, /* srcOffset */ + 0, /* dest */ coords, /* destOffset */ + 0, /* numPoints */ numCoords); + } + return seg; + } + } + + /** + * Creates a PathIterator for iterating along the segments of the path. + * + * @param at an affine transformation for projecting the returned + * points, or <code>null</code> to let the created iterator return + * the original points without any mapping. + */ + public PathIterator getPathIterator(AffineTransform at) + { + return new GeneralPathIterator(this, at); + } + + /** + * Creates a new FlatteningPathIterator for the path + */ + public PathIterator getPathIterator(AffineTransform at, double flatness) + { + return new FlatteningPathIterator(getPathIterator(at), flatness); + } + + /** + * Creates a new shape of the same run-time type with the same contents + * as this one. + * + * @return the clone + * + * @exception OutOfMemoryError If there is not enough memory available. + * + * @since 1.2 + */ + public Object clone() + { + // This class is final; no need to use super.clone(). + return new GeneralPath(this); + } + + /** + * Helper method - ensure the size of the data arrays, + * otherwise, reallocate new ones twice the size + * + * @param size the minimum array size. + */ + private void ensureSize(int size) + { + if (subpath < 0) + throw new IllegalPathStateException("need initial moveto"); + if (size <= xpoints.length) + return; + byte[] b = new byte[types.length << 1]; + System.arraycopy(types, 0, b, 0, index); + types = b; + float[] f = new float[xpoints.length << 1]; + System.arraycopy(xpoints, 0, f, 0, index); + xpoints = f; + f = new float[ypoints.length << 1]; + System.arraycopy(ypoints, 0, f, 0, index); + ypoints = f; + } + + /** + * Helper method - Get the total number of intersections from (x,y) along + * a given axis, within a given distance. + */ + private int getAxisIntersections(double x, double y, boolean useYaxis, + double distance) + { + return (evaluateCrossings(x, y, false, useYaxis, distance)); + } + + /** + * Helper method - returns the winding number of a point. + */ + private int getWindingNumber(double x, double y) + { + /* Evaluate the crossings from x,y to infinity on the y axis (arbitrary + choice). Note that we don't actually use Double.INFINITY, since that's + slower, and may cause problems. */ + return (evaluateCrossings(x, y, true, true, BIG_VALUE)); + } + + /** + * Helper method - evaluates the number of intersections on an axis from + * the point (x,y) to the point (x,y+distance) or (x+distance,y). + * @param x x coordinate. + * @param y y coordinate. + * @param neg True if opposite-directed intersections should cancel, + * false to sum all intersections. + * @param useYaxis Use the Y axis, false uses the X axis. + * @param distance Interval from (x,y) on the selected axis to find + * intersections. + */ + private int evaluateCrossings(double x, double y, boolean neg, + boolean useYaxis, double distance) + { + float cx = 0.0f; + float cy = 0.0f; + float firstx = 0.0f; + float firsty = 0.0f; + + int negative = (neg) ? -1 : 1; + double x0; + double x1; + double x2; + double x3; + double y0; + double y1; + double y2; + double y3; + double[] r = new double[4]; + int nRoots; + double epsilon = 0.0; + int pos = 0; + int windingNumber = 0; + boolean pathStarted = false; + + if (index == 0) + return (0); + if (useYaxis) + { + float[] swap1; + swap1 = ypoints; + ypoints = xpoints; + xpoints = swap1; + double swap2; + swap2 = y; + y = x; + x = swap2; + } + + /* Get a value which is hopefully small but not insignificant relative + the path. */ + epsilon = ypoints[0] * 1E-7; + + if(epsilon == 0) + epsilon = 1E-7; + + pos = 0; + while (pos < index) + { + switch (types[pos]) + { + case PathIterator.SEG_MOVETO: + if (pathStarted) // close old path + { + x0 = cx; + y0 = cy; + x1 = firstx; + y1 = firsty; + + if (y0 == 0.0) + y0 -= epsilon; + if (y1 == 0.0) + y1 -= epsilon; + if (Line2D.linesIntersect(x0, y0, x1, y1, + epsilon, 0.0, distance, 0.0)) + windingNumber += (y1 < y0) ? 1 : negative; + + cx = firstx; + cy = firsty; + } + cx = firstx = xpoints[pos] - (float) x; + cy = firsty = ypoints[pos++] - (float) y; + pathStarted = true; + break; + case PathIterator.SEG_CLOSE: + x0 = cx; + y0 = cy; + x1 = firstx; + y1 = firsty; + + if (y0 == 0.0) + y0 -= epsilon; + if (y1 == 0.0) + y1 -= epsilon; + if (Line2D.linesIntersect(x0, y0, x1, y1, + epsilon, 0.0, distance, 0.0)) + windingNumber += (y1 < y0) ? 1 : negative; + + cx = firstx; + cy = firsty; + pos++; + pathStarted = false; + break; + case PathIterator.SEG_LINETO: + x0 = cx; + y0 = cy; + x1 = xpoints[pos] - (float) x; + y1 = ypoints[pos++] - (float) y; + + if (y0 == 0.0) + y0 -= epsilon; + if (y1 == 0.0) + y1 -= epsilon; + if (Line2D.linesIntersect(x0, y0, x1, y1, + epsilon, 0.0, distance, 0.0)) + windingNumber += (y1 < y0) ? 1 : negative; + + cx = xpoints[pos - 1] - (float) x; + cy = ypoints[pos - 1] - (float) y; + break; + case PathIterator.SEG_QUADTO: + x0 = cx; + y0 = cy; + x1 = xpoints[pos] - x; + y1 = ypoints[pos++] - y; + x2 = xpoints[pos] - x; + y2 = ypoints[pos++] - y; + + /* check if curve may intersect X+ axis. */ + if ((x0 > 0.0 || x1 > 0.0 || x2 > 0.0) + && (y0 * y1 <= 0 || y1 * y2 <= 0)) + { + if (y0 == 0.0) + y0 -= epsilon; + if (y2 == 0.0) + y2 -= epsilon; + + r[0] = y0; + r[1] = 2 * (y1 - y0); + r[2] = (y2 - 2 * y1 + y0); + + /* degenerate roots (=tangent points) do not + contribute to the winding number. */ + if ((nRoots = QuadCurve2D.solveQuadratic(r)) == 2) + for (int i = 0; i < nRoots; i++) + { + float t = (float) r[i]; + if (t > 0.0f && t < 1.0f) + { + double crossing = t * t * (x2 - 2 * x1 + x0) + + 2 * t * (x1 - x0) + x0; + if (crossing >= 0.0 && crossing <= distance) + windingNumber += (2 * t * (y2 - 2 * y1 + y0) + + 2 * (y1 - y0) < 0) ? 1 : negative; + } + } + } + + cx = xpoints[pos - 1] - (float) x; + cy = ypoints[pos - 1] - (float) y; + break; + case PathIterator.SEG_CUBICTO: + x0 = cx; + y0 = cy; + x1 = xpoints[pos] - x; + y1 = ypoints[pos++] - y; + x2 = xpoints[pos] - x; + y2 = ypoints[pos++] - y; + x3 = xpoints[pos] - x; + y3 = ypoints[pos++] - y; + + /* check if curve may intersect X+ axis. */ + if ((x0 > 0.0 || x1 > 0.0 || x2 > 0.0 || x3 > 0.0) + && (y0 * y1 <= 0 || y1 * y2 <= 0 || y2 * y3 <= 0)) + { + if (y0 == 0.0) + y0 -= epsilon; + if (y3 == 0.0) + y3 -= epsilon; + + r[0] = y0; + r[1] = 3 * (y1 - y0); + r[2] = 3 * (y2 + y0 - 2 * y1); + r[3] = y3 - 3 * y2 + 3 * y1 - y0; + + if ((nRoots = CubicCurve2D.solveCubic(r)) != 0) + for (int i = 0; i < nRoots; i++) + { + float t = (float) r[i]; + if (t > 0.0 && t < 1.0) + { + double crossing = -(t * t * t) * (x0 - 3 * x1 + + 3 * x2 - x3) + + 3 * t * t * (x0 - 2 * x1 + x2) + + 3 * t * (x1 - x0) + x0; + if (crossing >= 0 && crossing <= distance) + windingNumber += (3 * t * t * (y3 + 3 * y1 + - 3 * y2 - y0) + + 6 * t * (y0 - 2 * y1 + y2) + + 3 * (y1 - y0) < 0) ? 1 : negative; + } + } + } + + cx = xpoints[pos - 1] - (float) x; + cy = ypoints[pos - 1] - (float) y; + break; + } + } + + // swap coordinates back + if (useYaxis) + { + float[] swap; + swap = ypoints; + ypoints = xpoints; + xpoints = swap; + } + return (windingNumber); + } +} // class GeneralPath diff --git a/libjava/classpath/java/awt/geom/IllegalPathStateException.java b/libjava/classpath/java/awt/geom/IllegalPathStateException.java new file mode 100644 index 000000000..4d190c748 --- /dev/null +++ b/libjava/classpath/java/awt/geom/IllegalPathStateException.java @@ -0,0 +1,71 @@ +/* IllegalPathStateException.java -- an operation was in an illegal path state + Copyright (C) 2000, 2002 Free Software Foundation + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package java.awt.geom; + +/** + * Thrown when an operation on a path is in an illegal state, such as appending + * a segment to a <code>GeneralPath</code> without an initial moveto. + * + * @author Tom Tromey (tromey@cygnus.com) + * @see GeneralPath + * @status updated to 1.4 + */ +public class IllegalPathStateException extends RuntimeException +{ + /** + * Compatible with JDK 1.2+. + */ + private static final long serialVersionUID = -5158084205220481094L; + + /** + * Create an exception with no message. + */ + public IllegalPathStateException() + { + } + + /** + * Create an exception with a message. + * + * @param msg the message + */ + public IllegalPathStateException(String msg) + { + super(msg); + } +} diff --git a/libjava/classpath/java/awt/geom/Line2D.java b/libjava/classpath/java/awt/geom/Line2D.java new file mode 100644 index 000000000..c92aab004 --- /dev/null +++ b/libjava/classpath/java/awt/geom/Line2D.java @@ -0,0 +1,1182 @@ +/* Line2D.java -- represents a line in 2-D space, plus operations on a line + Copyright (C) 2000, 2001, 2002 Free Software Foundation + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package java.awt.geom; + +import java.awt.Rectangle; +import java.awt.Shape; +import java.util.NoSuchElementException; + +/** + * Represents a directed line bewteen two points in (x,y) Cartesian space. + * Remember, on-screen graphics have increasing x from left-to-right, and + * increasing y from top-to-bottom. The storage is left to subclasses. + * + * @author Tom Tromey (tromey@cygnus.com) + * @author Eric Blake (ebb9@email.byu.edu) + * @author David Gilbert + * @since 1.2 + * @status updated to 1.4 + */ +public abstract class Line2D implements Shape, Cloneable +{ + /** + * The default constructor. + */ + protected Line2D() + { + } + + /** + * Return the x coordinate of the first point. + * + * @return the starting x coordinate + */ + public abstract double getX1(); + + /** + * Return the y coordinate of the first point. + * + * @return the starting y coordinate + */ + public abstract double getY1(); + + /** + * Return the first point. + * + * @return the starting point + */ + public abstract Point2D getP1(); + + /** + * Return the x coordinate of the second point. + * + * @return the ending x coordinate + */ + public abstract double getX2(); + + /** + * Return the y coordinate of the second point. + * + * @return the ending y coordinate + */ + public abstract double getY2(); + + /** + * Return the second point. + * + * @return the ending point + */ + public abstract Point2D getP2(); + + /** + * Set the coordinates of the line to the given coordinates. Loss of + * precision may occur due to rounding issues. + * + * @param x1 the first x coordinate + * @param y1 the first y coordinate + * @param x2 the second x coordinate + * @param y2 the second y coordinate + */ + public abstract void setLine(double x1, double y1, double x2, double y2); + + /** + * Set the coordinates to the given points. + * + * @param p1 the first point + * @param p2 the second point + * @throws NullPointerException if either point is null + */ + public void setLine(Point2D p1, Point2D p2) + { + setLine(p1.getX(), p1.getY(), p2.getX(), p2.getY()); + } + + /** + * Set the coordinates to those of the given line. + * + * @param l the line to copy + * @throws NullPointerException if l is null + */ + public void setLine(Line2D l) + { + setLine(l.getX1(), l.getY1(), l.getX2(), l.getY2()); + } + + /** + * Computes the relative rotation direction needed to pivot the line about + * the first point in order to have the second point colinear with point p. + * Because of floating point rounding, don't expect this to be a perfect + * measure of colinearity. The answer is 1 if the line has a shorter rotation + * in the direction of the positive X axis to the negative Y axis + * (counter-clockwise in the default Java coordinate system), or -1 if the + * shortest rotation is in the opposite direction (clockwise). If p + * is already colinear, the return value is -1 if it lies beyond the first + * point, 0 if it lies in the segment, or 1 if it lies beyond the second + * point. If the first and second point are coincident, this returns 0. + * + * @param x1 the first x coordinate + * @param y1 the first y coordinate + * @param x2 the second x coordinate + * @param y2 the second y coordinate + * @param px the reference x coordinate + * @param py the reference y coordinate + * @return the relative rotation direction + */ + public static int relativeCCW(double x1, double y1, double x2, double y2, + double px, double py) + { + if ((x1 == x2 && y1 == y2) + || (x1 == px && y1 == py)) + return 0; // Coincident points. + // Translate to the origin. + x2 -= x1; + y2 -= y1; + px -= x1; + py -= y1; + double slope2 = y2 / x2; + double slopep = py / px; + if (slope2 == slopep || (x2 == 0 && px == 0)) + return y2 > 0 // Colinear. + ? (py < 0 ? -1 : py > y2 ? 1 : 0) + : (py > 0 ? -1 : py < y2 ? 1 : 0); + if (x2 >= 0 && slope2 >= 0) + return px >= 0 // Quadrant 1. + ? (slope2 > slopep ? 1 : -1) + : (slope2 < slopep ? 1 : -1); + if (y2 > 0) + return px < 0 // Quadrant 2. + ? (slope2 > slopep ? 1 : -1) + : (slope2 < slopep ? 1 : -1); + if (slope2 >= 0.0) + return px >= 0 // Quadrant 3. + ? (slope2 < slopep ? 1 : -1) + : (slope2 > slopep ? 1 : -1); + return px < 0 // Quadrant 4. + ? (slope2 < slopep ? 1 : -1) + : (slope2 > slopep ? 1 : -1); + } + + /** + * Computes the relative rotation direction needed to pivot this line about + * the first point in order to have the second point colinear with point p. + * Because of floating point rounding, don't expect this to be a perfect + * measure of colinearity. The answer is 1 if the line has a shorter rotation + * in the direction of the positive X axis to the negative Y axis + * (counter-clockwise in the default Java coordinate system), or -1 if the + * shortest rotation is in the opposite direction (clockwise). If p + * is already colinear, the return value is -1 if it lies beyond the first + * point, 0 if it lies in the segment, or 1 if it lies beyond the second + * point. If the first and second point are coincident, this returns 0. + * + * @param px the reference x coordinate + * @param py the reference y coordinate + * @return the relative rotation direction + * @see #relativeCCW(double, double, double, double, double, double) + */ + public int relativeCCW(double px, double py) + { + return relativeCCW(getX1(), getY1(), getX2(), getY2(), px, py); + } + + /** + * Computes the relative rotation direction needed to pivot this line about + * the first point in order to have the second point colinear with point p. + * Because of floating point rounding, don't expect this to be a perfect + * measure of colinearity. The answer is 1 if the line has a shorter rotation + * in the direction of the positive X axis to the negative Y axis + * (counter-clockwise in the default Java coordinate system), or -1 if the + * shortest rotation is in the opposite direction (clockwise). If p + * is already colinear, the return value is -1 if it lies beyond the first + * point, 0 if it lies in the segment, or 1 if it lies beyond the second + * point. If the first and second point are coincident, this returns 0. + * + * @param p the reference point + * @return the relative rotation direction + * @throws NullPointerException if p is null + * @see #relativeCCW(double, double, double, double, double, double) + */ + public int relativeCCW(Point2D p) + { + return relativeCCW(getX1(), getY1(), getX2(), getY2(), p.getX(), p.getY()); + } + + /** + * Computes twice the (signed) area of the triangle defined by the three + * points. This method is used for intersection testing. + * + * @param x1 the x-coordinate of the first point. + * @param y1 the y-coordinate of the first point. + * @param x2 the x-coordinate of the second point. + * @param y2 the y-coordinate of the second point. + * @param x3 the x-coordinate of the third point. + * @param y3 the y-coordinate of the third point. + * + * @return Twice the area. + */ + private static double area2(double x1, double y1, + double x2, double y2, + double x3, double y3) + { + return (x2 - x1) * (y3 - y1) - (x3 - x1) * (y2 - y1); + } + + /** + * Returns <code>true</code> if (x3, y3) lies between (x1, y1) and (x2, y2), + * and false otherwise, This test assumes that the three points are + * collinear, and is used for intersection testing. + * + * @param x1 the x-coordinate of the first point. + * @param y1 the y-coordinate of the first point. + * @param x2 the x-coordinate of the second point. + * @param y2 the y-coordinate of the second point. + * @param x3 the x-coordinate of the third point. + * @param y3 the y-coordinate of the third point. + * + * @return A boolean. + */ + private static boolean between(double x1, double y1, + double x2, double y2, + double x3, double y3) + { + if (x1 != x2) { + return (x1 <= x3 && x3 <= x2) || (x1 >= x3 && x3 >= x2); + } + else { + return (y1 <= y3 && y3 <= y2) || (y1 >= y3 && y3 >= y2); + } + } + + /** + * Test if the line segment (x1,y1)->(x2,y2) intersects the line segment + * (x3,y3)->(x4,y4). + * + * @param x1 the first x coordinate of the first segment + * @param y1 the first y coordinate of the first segment + * @param x2 the second x coordinate of the first segment + * @param y2 the second y coordinate of the first segment + * @param x3 the first x coordinate of the second segment + * @param y3 the first y coordinate of the second segment + * @param x4 the second x coordinate of the second segment + * @param y4 the second y coordinate of the second segment + * @return true if the segments intersect + */ + public static boolean linesIntersect(double x1, double y1, + double x2, double y2, + double x3, double y3, + double x4, double y4) + { + double a1, a2, a3, a4; + + // deal with special cases + if ((a1 = area2(x1, y1, x2, y2, x3, y3)) == 0.0) + { + // check if p3 is between p1 and p2 OR + // p4 is collinear also AND either between p1 and p2 OR at opposite ends + if (between(x1, y1, x2, y2, x3, y3)) + { + return true; + } + else + { + if (area2(x1, y1, x2, y2, x4, y4) == 0.0) + { + return between(x3, y3, x4, y4, x1, y1) + || between (x3, y3, x4, y4, x2, y2); + } + else { + return false; + } + } + } + else if ((a2 = area2(x1, y1, x2, y2, x4, y4)) == 0.0) + { + // check if p4 is between p1 and p2 (we already know p3 is not + // collinear) + return between(x1, y1, x2, y2, x4, y4); + } + + if ((a3 = area2(x3, y3, x4, y4, x1, y1)) == 0.0) { + // check if p1 is between p3 and p4 OR + // p2 is collinear also AND either between p1 and p2 OR at opposite ends + if (between(x3, y3, x4, y4, x1, y1)) { + return true; + } + else { + if (area2(x3, y3, x4, y4, x2, y2) == 0.0) { + return between(x1, y1, x2, y2, x3, y3) + || between (x1, y1, x2, y2, x4, y4); + } + else { + return false; + } + } + } + else if ((a4 = area2(x3, y3, x4, y4, x2, y2)) == 0.0) { + // check if p2 is between p3 and p4 (we already know p1 is not + // collinear) + return between(x3, y3, x4, y4, x2, y2); + } + else { // test for regular intersection + return ((a1 > 0.0) ^ (a2 > 0.0)) && ((a3 > 0.0) ^ (a4 > 0.0)); + } + } + + /** + * Test if this line intersects the line given by (x1,y1)->(x2,y2). + * + * @param x1 the first x coordinate of the other segment + * @param y1 the first y coordinate of the other segment + * @param x2 the second x coordinate of the other segment + * @param y2 the second y coordinate of the other segment + * @return true if the segments intersect + * @see #linesIntersect(double, double, double, double, + * double, double, double, double) + */ + public boolean intersectsLine(double x1, double y1, double x2, double y2) + { + return linesIntersect(getX1(), getY1(), getX2(), getY2(), + x1, y1, x2, y2); + } + + /** + * Test if this line intersects the given line. + * + * @param l the other segment + * @return true if the segments intersect + * @throws NullPointerException if l is null + * @see #linesIntersect(double, double, double, double, + * double, double, double, double) + */ + public boolean intersectsLine(Line2D l) + { + return linesIntersect(getX1(), getY1(), getX2(), getY2(), + l.getX1(), l.getY1(), l.getX2(), l.getY2()); + } + + /** + * Measures the square of the shortest distance from the reference point + * to a point on the line segment. If the point is on the segment, the + * result will be 0. + * + * @param x1 the first x coordinate of the segment + * @param y1 the first y coordinate of the segment + * @param x2 the second x coordinate of the segment + * @param y2 the second y coordinate of the segment + * @param px the x coordinate of the point + * @param py the y coordinate of the point + * @return the square of the distance from the point to the segment + * @see #ptSegDist(double, double, double, double, double, double) + * @see #ptLineDistSq(double, double, double, double, double, double) + */ + public static double ptSegDistSq(double x1, double y1, double x2, double y2, + double px, double py) + { + double pd2 = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2); + + double x, y; + if (pd2 == 0) + { + // Points are coincident. + x = x1; + y = y2; + } + else + { + double u = ((px - x1) * (x2 - x1) + (py - y1) * (y2 - y1)) / pd2; + + if (u < 0) + { + // "Off the end" + x = x1; + y = y1; + } + else if (u > 1.0) + { + x = x2; + y = y2; + } + else + { + x = x1 + u * (x2 - x1); + y = y1 + u * (y2 - y1); + } + } + + return (x - px) * (x - px) + (y - py) * (y - py); + } + + /** + * Measures the shortest distance from the reference point to a point on + * the line segment. If the point is on the segment, the result will be 0. + * + * @param x1 the first x coordinate of the segment + * @param y1 the first y coordinate of the segment + * @param x2 the second x coordinate of the segment + * @param y2 the second y coordinate of the segment + * @param px the x coordinate of the point + * @param py the y coordinate of the point + * @return the distance from the point to the segment + * @see #ptSegDistSq(double, double, double, double, double, double) + * @see #ptLineDist(double, double, double, double, double, double) + */ + public static double ptSegDist(double x1, double y1, double x2, double y2, + double px, double py) + { + return Math.sqrt(ptSegDistSq(x1, y1, x2, y2, px, py)); + } + + /** + * Measures the square of the shortest distance from the reference point + * to a point on this line segment. If the point is on the segment, the + * result will be 0. + * + * @param px the x coordinate of the point + * @param py the y coordinate of the point + * @return the square of the distance from the point to the segment + * @see #ptSegDistSq(double, double, double, double, double, double) + */ + public double ptSegDistSq(double px, double py) + { + return ptSegDistSq(getX1(), getY1(), getX2(), getY2(), px, py); + } + + /** + * Measures the square of the shortest distance from the reference point + * to a point on this line segment. If the point is on the segment, the + * result will be 0. + * + * @param p the point + * @return the square of the distance from the point to the segment + * @throws NullPointerException if p is null + * @see #ptSegDistSq(double, double, double, double, double, double) + */ + public double ptSegDistSq(Point2D p) + { + return ptSegDistSq(getX1(), getY1(), getX2(), getY2(), p.getX(), p.getY()); + } + + /** + * Measures the shortest distance from the reference point to a point on + * this line segment. If the point is on the segment, the result will be 0. + * + * @param px the x coordinate of the point + * @param py the y coordinate of the point + * @return the distance from the point to the segment + * @see #ptSegDist(double, double, double, double, double, double) + */ + public double ptSegDist(double px, double py) + { + return ptSegDist(getX1(), getY1(), getX2(), getY2(), px, py); + } + + /** + * Measures the shortest distance from the reference point to a point on + * this line segment. If the point is on the segment, the result will be 0. + * + * @param p the point + * @return the distance from the point to the segment + * @throws NullPointerException if p is null + * @see #ptSegDist(double, double, double, double, double, double) + */ + public double ptSegDist(Point2D p) + { + return ptSegDist(getX1(), getY1(), getX2(), getY2(), p.getX(), p.getY()); + } + + /** + * Measures the square of the shortest distance from the reference point + * to a point on the infinite line extended from the segment. If the point + * is on the segment, the result will be 0. If the segment is length 0, + * the distance is to the common endpoint. + * + * @param x1 the first x coordinate of the segment + * @param y1 the first y coordinate of the segment + * @param x2 the second x coordinate of the segment + * @param y2 the second y coordinate of the segment + * @param px the x coordinate of the point + * @param py the y coordinate of the point + * @return the square of the distance from the point to the extended line + * @see #ptLineDist(double, double, double, double, double, double) + * @see #ptSegDistSq(double, double, double, double, double, double) + */ + public static double ptLineDistSq(double x1, double y1, double x2, double y2, + double px, double py) + { + double pd2 = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2); + + double x, y; + if (pd2 == 0) + { + // Points are coincident. + x = x1; + y = y2; + } + else + { + double u = ((px - x1) * (x2 - x1) + (py - y1) * (y2 - y1)) / pd2; + x = x1 + u * (x2 - x1); + y = y1 + u * (y2 - y1); + } + + return (x - px) * (x - px) + (y - py) * (y - py); + } + + /** + * Measures the shortest distance from the reference point to a point on + * the infinite line extended from the segment. If the point is on the + * segment, the result will be 0. If the segment is length 0, the distance + * is to the common endpoint. + * + * @param x1 the first x coordinate of the segment + * @param y1 the first y coordinate of the segment + * @param x2 the second x coordinate of the segment + * @param y2 the second y coordinate of the segment + * @param px the x coordinate of the point + * @param py the y coordinate of the point + * @return the distance from the point to the extended line + * @see #ptLineDistSq(double, double, double, double, double, double) + * @see #ptSegDist(double, double, double, double, double, double) + */ + public static double ptLineDist(double x1, double y1, + double x2, double y2, + double px, double py) + { + return Math.sqrt(ptLineDistSq(x1, y1, x2, y2, px, py)); + } + + /** + * Measures the square of the shortest distance from the reference point + * to a point on the infinite line extended from this segment. If the point + * is on the segment, the result will be 0. If the segment is length 0, + * the distance is to the common endpoint. + * + * @param px the x coordinate of the point + * @param py the y coordinate of the point + * @return the square of the distance from the point to the extended line + * @see #ptLineDistSq(double, double, double, double, double, double) + */ + public double ptLineDistSq(double px, double py) + { + return ptLineDistSq(getX1(), getY1(), getX2(), getY2(), px, py); + } + + /** + * Measures the square of the shortest distance from the reference point + * to a point on the infinite line extended from this segment. If the point + * is on the segment, the result will be 0. If the segment is length 0, + * the distance is to the common endpoint. + * + * @param p the point + * @return the square of the distance from the point to the extended line + * @throws NullPointerException if p is null + * @see #ptLineDistSq(double, double, double, double, double, double) + */ + public double ptLineDistSq(Point2D p) + { + return ptLineDistSq(getX1(), getY1(), getX2(), getY2(), + p.getX(), p.getY()); + } + + /** + * Measures the shortest distance from the reference point to a point on + * the infinite line extended from this segment. If the point is on the + * segment, the result will be 0. If the segment is length 0, the distance + * is to the common endpoint. + * + * @param px the x coordinate of the point + * @param py the y coordinate of the point + * @return the distance from the point to the extended line + * @see #ptLineDist(double, double, double, double, double, double) + */ + public double ptLineDist(double px, double py) + { + return ptLineDist(getX1(), getY1(), getX2(), getY2(), px, py); + } + + /** + * Measures the shortest distance from the reference point to a point on + * the infinite line extended from this segment. If the point is on the + * segment, the result will be 0. If the segment is length 0, the distance + * is to the common endpoint. + * + * @param p the point + * @return the distance from the point to the extended line + * @throws NullPointerException if p is null + * @see #ptLineDist(double, double, double, double, double, double) + */ + public double ptLineDist(Point2D p) + { + return ptLineDist(getX1(), getY1(), getX2(), getY2(), p.getX(), p.getY()); + } + + /** + * Test if a point is contained inside the line. Since a line has no area, + * this returns false. + * + * @param x the x coordinate + * @param y the y coordinate + * @return false; the line does not contain points + */ + public boolean contains(double x, double y) + { + return false; + } + + /** + * Test if a point is contained inside the line. Since a line has no area, + * this returns false. + * + * @param p the point + * @return false; the line does not contain points + */ + public boolean contains(Point2D p) + { + return false; + } + + /** + * Tests if this line intersects the interior of the specified rectangle. + * + * @param x the x coordinate of the rectangle + * @param y the y coordinate of the rectangle + * @param w the width of the rectangle + * @param h the height of the rectangle + * @return true if the line intersects the rectangle + */ + public boolean intersects(double x, double y, double w, double h) + { + if (w <= 0 || h <= 0) + return false; + double x1 = getX1(); + double y1 = getY1(); + double x2 = getX2(); + double y2 = getY2(); + + if (x1 >= x && x1 <= x + w && y1 >= y && y1 <= y + h) + return true; + if (x2 >= x && x2 <= x + w && y2 >= y && y2 <= y + h) + return true; + + double x3 = x + w; + double y3 = y + h; + + return (linesIntersect(x1, y1, x2, y2, x, y, x, y3) + || linesIntersect(x1, y1, x2, y2, x, y3, x3, y3) + || linesIntersect(x1, y1, x2, y2, x3, y3, x3, y) + || linesIntersect(x1, y1, x2, y2, x3, y, x, y)); + } + + /** + * Tests if this line intersects the interior of the specified rectangle. + * + * @param r the rectangle + * @return true if the line intersects the rectangle + * @throws NullPointerException if r is null + */ + public boolean intersects(Rectangle2D r) + { + return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + /** + * Tests if the line contains a rectangle. Since lines have no area, this + * always returns false. + * + * @param x the x coordinate of the rectangle + * @param y the y coordinate of the rectangle + * @param w the width of the rectangle + * @param h the height of the rectangle + * @return false; the line does not contain points + */ + public boolean contains(double x, double y, double w, double h) + { + return false; + } + + /** + * Tests if the line contains a rectangle. Since lines have no area, this + * always returns false. + * + * @param r the rectangle + * @return false; the line does not contain points + */ + public boolean contains(Rectangle2D r) + { + return false; + } + + /** + * Gets a bounding box (not necessarily minimal) for this line. + * + * @return the integer bounding box + * @see #getBounds2D() + */ + public Rectangle getBounds() + { + return getBounds2D().getBounds(); + } + + /** + * Return a path iterator, possibly applying a transform on the result. This + * iterator is not threadsafe. + * + * @param at the transform, or null + * @return a new path iterator + */ + public PathIterator getPathIterator(final AffineTransform at) + { + return new PathIterator() + { + /** Current coordinate. */ + private int current = 0; + + public int getWindingRule() + { + return WIND_NON_ZERO; + } + + public boolean isDone() + { + return current >= 2; + } + + public void next() + { + current++; + } + + public int currentSegment(float[] coords) + { + int result; + switch (current) + { + case 0: + coords[0] = (float) getX1(); + coords[1] = (float) getY1(); + result = SEG_MOVETO; + break; + case 1: + coords[0] = (float) getX2(); + coords[1] = (float) getY2(); + result = SEG_LINETO; + break; + default: + throw new NoSuchElementException("line iterator out of bounds"); + } + if (at != null) + at.transform(coords, 0, coords, 0, 1); + return result; + } + + public int currentSegment(double[] coords) + { + int result; + switch (current) + { + case 0: + coords[0] = getX1(); + coords[1] = getY1(); + result = SEG_MOVETO; + break; + case 1: + coords[0] = getX2(); + coords[1] = getY2(); + result = SEG_LINETO; + break; + default: + throw new NoSuchElementException("line iterator out of bounds"); + } + if (at != null) + at.transform(coords, 0, coords, 0, 1); + return result; + } + }; + } + + /** + * Return a flat path iterator, possibly applying a transform on the result. + * This iterator is not threadsafe. + * + * @param at the transform, or null + * @param flatness ignored, since lines are already flat + * @return a new path iterator + * @see #getPathIterator(AffineTransform) + */ + public PathIterator getPathIterator(AffineTransform at, double flatness) + { + return getPathIterator(at); + } + + /** + * Create a new line of the same run-time type with the same contents as + * this one. + * + * @return the clone + * + * @exception OutOfMemoryError If there is not enough memory available. + * + * @since 1.2 + */ + public Object clone() + { + try + { + return super.clone(); + } + catch (CloneNotSupportedException e) + { + throw (Error) new InternalError().initCause(e); // Impossible + } + } + + /** + * This class defines a point in <code>double</code> precision. + * + * @author Eric Blake (ebb9@email.byu.edu) + * @since 1.2 + * @status updated to 1.4 + */ + public static class Double extends Line2D + { + /** The x coordinate of the first point. */ + public double x1; + + /** The y coordinate of the first point. */ + public double y1; + + /** The x coordinate of the second point. */ + public double x2; + + /** The y coordinate of the second point. */ + public double y2; + + /** + * Construct the line segment (0,0)->(0,0). + */ + public Double() + { + } + + /** + * Construct the line segment with the specified points. + * + * @param x1 the x coordinate of the first point + * @param y1 the y coordinate of the first point + * @param x2 the x coordinate of the second point + * @param y2 the y coordinate of the second point + */ + public Double(double x1, double y1, double x2, double y2) + { + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + } + + /** + * Construct the line segment with the specified points. + * + * @param p1 the first point + * @param p2 the second point + * @throws NullPointerException if either point is null + */ + public Double(Point2D p1, Point2D p2) + { + x1 = p1.getX(); + y1 = p1.getY(); + x2 = p2.getX(); + y2 = p2.getY(); + } + + /** + * Return the x coordinate of the first point. + * + * @return the value of x1 + */ + public double getX1() + { + return x1; + } + + /** + * Return the y coordinate of the first point. + * + * @return the value of y1 + */ + public double getY1() + { + return y1; + } + + /** + * Return the first point. + * + * @return the point (x1,y1) + */ + public Point2D getP1() + { + return new Point2D.Double(x1, y1); + } + + /** + * Return the x coordinate of the second point. + * + * @return the value of x2 + */ + public double getX2() + { + return x2; + } + + /** + * Return the y coordinate of the second point. + * + * @return the value of y2 + */ + public double getY2() + { + return y2; + } + + /** + * Return the second point. + * + * @return the point (x2,y2) + */ + public Point2D getP2() + { + return new Point2D.Double(x2, y2); + } + + /** + * Set this line to the given points. + * + * @param x1 the new x coordinate of the first point + * @param y1 the new y coordinate of the first point + * @param x2 the new x coordinate of the second point + * @param y2 the new y coordinate of the second point + */ + public void setLine(double x1, double y1, double x2, double y2) + { + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + } + + /** + * Return the exact bounds of this line segment. + * + * @return the bounding box + */ + public Rectangle2D getBounds2D() + { + double x = Math.min(x1, x2); + double y = Math.min(y1, y2); + double w = Math.abs(x1 - x2); + double h = Math.abs(y1 - y2); + return new Rectangle2D.Double(x, y, w, h); + } + } // class Double + + /** + * This class defines a point in <code>float</code> precision. + * + * @author Eric Blake (ebb9@email.byu.edu) + * @since 1.2 + * @status updated to 1.4 + */ + public static class Float extends Line2D + { + /** The x coordinate of the first point. */ + public float x1; + + /** The y coordinate of the first point. */ + public float y1; + + /** The x coordinate of the second point. */ + public float x2; + + /** The y coordinate of the second point. */ + public float y2; + + /** + * Construct the line segment (0,0)->(0,0). + */ + public Float() + { + } + + /** + * Construct the line segment with the specified points. + * + * @param x1 the x coordinate of the first point + * @param y1 the y coordinate of the first point + * @param x2 the x coordinate of the second point + * @param y2 the y coordinate of the second point + */ + public Float(float x1, float y1, float x2, float y2) + { + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + } + + /** + * Construct the line segment with the specified points. + * + * @param p1 the first point + * @param p2 the second point + * @throws NullPointerException if either point is null + */ + public Float(Point2D p1, Point2D p2) + { + x1 = (float) p1.getX(); + y1 = (float) p1.getY(); + x2 = (float) p2.getX(); + y2 = (float) p2.getY(); + } + + /** + * Return the x coordinate of the first point. + * + * @return the value of x1 + */ + public double getX1() + { + return x1; + } + + /** + * Return the y coordinate of the first point. + * + * @return the value of y1 + */ + public double getY1() + { + return y1; + } + + /** + * Return the first point. + * + * @return the point (x1,y1) + */ + public Point2D getP1() + { + return new Point2D.Float(x1, y1); + } + + /** + * Return the x coordinate of the second point. + * + * @return the value of x2 + */ + public double getX2() + { + return x2; + } + + /** + * Return the y coordinate of the second point. + * + * @return the value of y2 + */ + public double getY2() + { + return y2; + } + + /** + * Return the second point. + * + * @return the point (x2,y2) + */ + public Point2D getP2() + { + return new Point2D.Float(x2, y2); + } + + /** + * Set this line to the given points. + * + * @param x1 the new x coordinate of the first point + * @param y1 the new y coordinate of the first point + * @param x2 the new x coordinate of the second point + * @param y2 the new y coordinate of the second point + */ + public void setLine(double x1, double y1, double x2, double y2) + { + this.x1 = (float) x1; + this.y1 = (float) y1; + this.x2 = (float) x2; + this.y2 = (float) y2; + } + + /** + * Set this line to the given points. + * + * @param x1 the new x coordinate of the first point + * @param y1 the new y coordinate of the first point + * @param x2 the new x coordinate of the second point + * @param y2 the new y coordinate of the second point + */ + public void setLine(float x1, float y1, float x2, float y2) + { + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + } + + /** + * Return the exact bounds of this line segment. + * + * @return the bounding box + */ + public Rectangle2D getBounds2D() + { + float x = Math.min(x1, x2); + float y = Math.min(y1, y2); + float w = Math.abs(x1 - x2); + float h = Math.abs(y1 - y2); + return new Rectangle2D.Float(x, y, w, h); + } + } // class Float +} // class Line2D diff --git a/libjava/classpath/java/awt/geom/NoninvertibleTransformException.java b/libjava/classpath/java/awt/geom/NoninvertibleTransformException.java new file mode 100644 index 000000000..7995a52eb --- /dev/null +++ b/libjava/classpath/java/awt/geom/NoninvertibleTransformException.java @@ -0,0 +1,65 @@ +/* NoninvertibleTransformException.java -- a transform can't be inverted + Copyright (C) 2000, 2002 Free Software Foundation + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package java.awt.geom; + +/** + * Thrown if an operation requires an inverse of an + * <code>AffineTransform</code>, but the transform is in a non-invertible + * state. + * + * @author Tom Tromey (tromey@cygnus.com) + * @see AffineTransform + * @status updated to 1.4 + */ +public class NoninvertibleTransformException extends Exception +{ + /** + * Compatible with JDK 1.2+. + */ + private static final long serialVersionUID = 6137225240503990466L; + + /** + * Create an exception with a message. + * + * @param s the message + */ + public NoninvertibleTransformException(String s) + { + super(s); + } +} diff --git a/libjava/classpath/java/awt/geom/PathIterator.java b/libjava/classpath/java/awt/geom/PathIterator.java new file mode 100644 index 000000000..2cd08b9b4 --- /dev/null +++ b/libjava/classpath/java/awt/geom/PathIterator.java @@ -0,0 +1,189 @@ +/* PathIterator.java -- describes a shape by iterating over its vertices + Copyright (C) 2000, 2002, 2003 Free Software Foundation + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package java.awt.geom; + +/** + * This interface provides a directed path over the boundary of a shape. The + * path can contain 1st through 3rd order Bezier curves (lines, and quadratic + * and cubic splines). A shape can have multiple disjoint paths via the + * MOVETO directive, and can close a circular path back to the previos + * MOVETO via the CLOSE directive. + * + * @author Tom Tromey (tromey@cygnus.com) + * @author Eric Blake (ebb9@email.byu.edu) + * @see java.awt.Shape + * @see java.awt.Stroke + * @see FlatteningPathIterator + * @since 1.2 + * @status updated to 1.4 + */ +public interface PathIterator +{ + /** + * The even-odd winding mode: a point is internal to the shape if a ray + * from the point to infinity (in any direction) crosses an odd number of + * segments. + */ + int WIND_EVEN_ODD = 0; + + /** + * The non-zero winding mode: a point is internal to the shape if a ray + * from the point to infinity (in any direction) crosses a different number + * of segments headed clockwise than those headed counterclockwise. + */ + int WIND_NON_ZERO = 1; + + /** + * Starts a new subpath. There is no segment from the previous vertex. + */ + int SEG_MOVETO = 0; + + /** + * The current segment is a line. + */ + int SEG_LINETO = 1; + + /** + * The current segment is a quadratic parametric curve. It is interpolated + * as t varies from 0 to 1 over the current point (CP), first control point + * (P1), and final interpolated control point (P2): + * <pre> + * P(t) = B(2,0)*CP + B(2,1)*P1 + B(2,2)*P2 + * 0 <= t <= 1 + * B(n,m) = mth coefficient of nth degree Bernstein polynomial + * = C(n,m) * t^(m) * (1 - t)^(n-m) + * C(n,m) = Combinations of n things, taken m at a time + * = n! / (m! * (n-m)!) + * </pre> + */ + int SEG_QUADTO = 2; + + /** + * The current segment is a cubic parametric curve (more commonly known as + * a Bezier curve). It is interpolated as t varies from 0 to 1 over the + * current point (CP), first control point (P1), the second control point + * (P2), and final interpolated control point (P3): + * <pre> + * P(t) = B(3,0)*CP + B(3,1)*P1 + B(3,2)*P2 + B(3,3)*P3 + * 0 <= t <= 1 + * B(n,m) = mth coefficient of nth degree Bernstein polynomial + * = C(n,m) * t^(m) * (1 - t)^(n-m) + * C(n,m) = Combinations of n things, taken m at a time + * = n! / (m! * (n-m)!) + * </pre> + */ + int SEG_CUBICTO = 3; + + /** + * The current segment closes a loop by an implicit line to the previous + * SEG_MOVETO coordinate. + */ + int SEG_CLOSE = 4; + + /** + * Returns the winding rule to determine which points are inside this path. + * + * @return the winding rule + * @see #WIND_EVEN_ODD + * @see #WIND_NON_ZERO + */ + int getWindingRule(); + + /** + * Tests if the iterator is exhausted. If this returns true, currentSegment + * and next may throw a NoSuchElementException (although this is not + * required). + * + * @return true if the iteration is complete + */ + boolean isDone(); + + /** + * Advance to the next segment in the iteration. It is not specified what + * this does if called when isDone() returns true. + * + * @throws java.util.NoSuchElementException optional when isDone() is true + */ + void next(); + + /** + * Returns the coordinates of the next point(s), as well as the type of + * line segment. The input array must be at least a float[6], to accomodate + * up to three (x,y) point pairs (although if you know the iterator is + * flat, you can probably get by with a float[2]). If the returned type is + * SEG_MOVETO or SEG_LINETO, the first point in the array is modified; if + * the returned type is SEG_QUADTO, the first two points are modified; if + * the returned type is SEG_CUBICTO, all three points are modified; and if + * the returned type is SEG_CLOSE, the array is untouched. + * + * @param coords the array to place the point coordinates in + * @return the segment type + * @throws NullPointerException if coords is null + * @throws ArrayIndexOutOfBoundsException if coords is too small + * @throws java.util.NoSuchElementException optional when isDone() is true + * @see #SEG_MOVETO + * @see #SEG_LINETO + * @see #SEG_QUADTO + * @see #SEG_CUBICTO + * @see #SEG_CLOSE + */ + int currentSegment(float[] coords); + + /** + * Returns the coordinates of the next point(s), as well as the type of + * line segment. The input array must be at least a double[6], to accomodate + * up to three (x,y) point pairs (although if you know the iterator is + * flat, you can probably get by with a double[2]). If the returned type is + * SEG_MOVETO or SEG_LINETO, the first point in the array is modified; if + * the returned type is SEG_QUADTO, the first two points are modified; if + * the returned type is SEG_CUBICTO, all three points are modified; and if + * the returned type is SEG_CLOSE, the array is untouched. + * + * @param coords the array to place the point coordinates in + * @return the segment type + * @throws NullPointerException if coords is null + * @throws ArrayIndexOutOfBoundsException if coords is too small + * @throws java.util.NoSuchElementException optional when isDone() is true + * @see #SEG_MOVETO + * @see #SEG_LINETO + * @see #SEG_QUADTO + * @see #SEG_CUBICTO + * @see #SEG_CLOSE + */ + int currentSegment(double[] coords); +} // interface PathIterator diff --git a/libjava/classpath/java/awt/geom/Point2D.java b/libjava/classpath/java/awt/geom/Point2D.java new file mode 100644 index 000000000..a2689abf8 --- /dev/null +++ b/libjava/classpath/java/awt/geom/Point2D.java @@ -0,0 +1,396 @@ +/* Point2D.java -- generic point in 2-D space + Copyright (C) 1999, 2000, 2002, 2004, 2006, Free Software Foundation + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.awt.geom; + +/** + * This class implements a generic point in 2D Cartesian space. The storage + * representation is left up to the subclass. Point includes two useful + * nested classes, for float and double storage respectively. + * + * @author Per Bothner (bothner@cygnus.com) + * @author Eric Blake (ebb9@email.byu.edu) + * @since 1.2 + * @status updated to 1.4 + */ +public abstract class Point2D implements Cloneable +{ + /** + * The default constructor. + * + * @see java.awt.Point + * @see Point2D.Float + * @see Point2D.Double + */ + protected Point2D() + { + } + + /** + * Get the X coordinate, in double precision. + * + * @return the x coordinate + */ + public abstract double getX(); + + /** + * Get the Y coordinate, in double precision. + * + * @return the y coordinate + */ + public abstract double getY(); + + /** + * Set the location of this point to the new coordinates. There may be a + * loss of precision. + * + * @param x the new x coordinate + * @param y the new y coordinate + */ + public abstract void setLocation(double x, double y); + + /** + * Set the location of this point to the new coordinates. There may be a + * loss of precision. + * + * @param p the point to copy + * @throws NullPointerException if p is null + */ + public void setLocation(Point2D p) + { + setLocation(p.getX(), p.getY()); + } + + /** + * Return the square of the distance between two points. + * + * @param x1 the x coordinate of point 1 + * @param y1 the y coordinate of point 1 + * @param x2 the x coordinate of point 2 + * @param y2 the y coordinate of point 2 + * @return (x2 - x1)^2 + (y2 - y1)^2 + */ + public static double distanceSq(double x1, double y1, double x2, double y2) + { + x2 -= x1; + y2 -= y1; + return x2 * x2 + y2 * y2; + } + + /** + * Return the distance between two points. + * + * @param x1 the x coordinate of point 1 + * @param y1 the y coordinate of point 1 + * @param x2 the x coordinate of point 2 + * @param y2 the y coordinate of point 2 + * @return the distance from (x1,y1) to (x2,y2) + */ + public static double distance(double x1, double y1, double x2, double y2) + { + return Math.sqrt(distanceSq(x1, y1, x2, y2)); + } + + /** + * Return the square of the distance from this point to the given one. + * + * @param x the x coordinate of the other point + * @param y the y coordinate of the other point + * @return the square of the distance + */ + public double distanceSq(double x, double y) + { + return distanceSq(getX(), getY(), x, y); + } + + /** + * Return the square of the distance from this point to the given one. + * + * @param p the other point + * @return the square of the distance + * @throws NullPointerException if p is null + */ + public double distanceSq(Point2D p) + { + return distanceSq(getX(), getY(), p.getX(), p.getY()); + } + + /** + * Return the distance from this point to the given one. + * + * @param x the x coordinate of the other point + * @param y the y coordinate of the other point + * @return the distance + */ + public double distance(double x, double y) + { + return distance(getX(), getY(), x, y); + } + + /** + * Return the distance from this point to the given one. + * + * @param p the other point + * @return the distance + * @throws NullPointerException if p is null + */ + public double distance(Point2D p) + { + return distance(getX(), getY(), p.getX(), p.getY()); + } + + /** + * Create a new point of the same run-time type with the same contents as + * this one. + * + * @return the clone + */ + public Object clone() + { + try + { + return super.clone(); + } + catch (CloneNotSupportedException e) + { + throw (Error) new InternalError().initCause(e); // Impossible + } + } + + /** + * Return the hashcode for this point. The formula is not documented, but + * appears to be the same as: + * <pre> + * long l = Double.doubleToLongBits(getY()); + * l = l * 31 ^ Double.doubleToLongBits(getX()); + * return (int) ((l >> 32) ^ l); + * </pre> + * + * @return the hashcode + */ + public int hashCode() + { + // Talk about a fun time reverse engineering this one! + long l = java.lang.Double.doubleToLongBits(getY()); + l = l * 31 ^ java.lang.Double.doubleToLongBits(getX()); + return (int) ((l >> 32) ^ l); + } + + /** + * Compares two points for equality. This returns true if they have the + * same coordinates. + * + * @param o the point to compare + * @return true if it is equal + */ + public boolean equals(Object o) + { + if (! (o instanceof Point2D)) + return false; + Point2D p = (Point2D) o; + return getX() == p.getX() && getY() == p.getY(); + } + + /** + * This class defines a point in <code>double</code> precision. + * + * @author Eric Blake (ebb9@email.byu.edu) + * @since 1.2 + * @status updated to 1.4 + */ + public static class Double extends Point2D + { + /** The X coordinate. */ + public double x; + + /** The Y coordinate. */ + public double y; + + /** + * Create a new point at (0,0). + */ + public Double() + { + } + + /** + * Create a new point at (x,y). + * + * @param x the x coordinate + * @param y the y coordinate + */ + public Double(double x, double y) + { + this.x = x; + this.y = y; + } + + /** + * Return the x coordinate. + * + * @return the x coordinate + */ + public double getX() + { + return x; + } + + /** + * Return the y coordinate. + * + * @return the y coordinate + */ + public double getY() + { + return y; + } + + /** + * Sets the location of this point. + * + * @param x the new x coordinate + * @param y the new y coordinate + */ + public void setLocation(double x, double y) + { + this.x = x; + this.y = y; + } + + /** + * Returns a string representation of this object. The format is: + * <code>"Point2D.Double[" + x + ", " + y + ']'</code>. + * + * @return a string representation of this object + */ + public String toString() + { + return "Point2D.Double[" + x + ", " + y + ']'; + } + } // class Double + + /** + * This class defines a point in <code>float</code> precision. + * + * @author Eric Blake (ebb9@email.byu.edu) + * @since 1.2 + * @status updated to 1.4 + */ + public static class Float extends Point2D + { + /** The X coordinate. */ + public float x; + + /** The Y coordinate. */ + public float y; + + /** + * Create a new point at (0,0). + */ + public Float() + { + } + + /** + * Create a new point at (x,y). + * + * @param x the x coordinate + * @param y the y coordinate + */ + public Float(float x, float y) + { + this.x = x; + this.y = y; + } + + /** + * Return the x coordinate. + * + * @return the x coordinate + */ + public double getX() + { + return x; + } + + /** + * Return the y coordinate. + * + * @return the y coordinate + */ + public double getY() + { + return y; + } + + /** + * Sets the location of this point. + * + * @param x the new x coordinate + * @param y the new y coordinate + */ + public void setLocation(double x, double y) + { + this.x = (float) x; + this.y = (float) y; + } + + /** + * Sets the location of this point. + * + * @param x the new x coordinate + * @param y the new y coordinate + */ + public void setLocation(float x, float y) + { + this.x = x; + this.y = y; + } + + /** + * Returns a string representation of this object. The format is: + * <code>"Point2D.Float[" + x + ", " + y + ']'</code>. + * + * @return a string representation of this object + */ + public String toString() + { + return "Point2D.Float[" + x + ", " + y + ']'; + } + } // class Float +} // class Point2D diff --git a/libjava/classpath/java/awt/geom/QuadCurve2D.java b/libjava/classpath/java/awt/geom/QuadCurve2D.java new file mode 100644 index 000000000..62c829d30 --- /dev/null +++ b/libjava/classpath/java/awt/geom/QuadCurve2D.java @@ -0,0 +1,1467 @@ +/* QuadCurve2D.java -- represents a parameterized quadratic curve in 2-D space + Copyright (C) 2002, 2003, 2004 Free Software Foundation + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package java.awt.geom; + +import java.awt.Rectangle; +import java.awt.Shape; +import java.util.NoSuchElementException; + +/** + * A two-dimensional curve that is parameterized with a quadratic + * function. + * + * <p><img src="doc-files/QuadCurve2D-1.png" width="350" height="180" + * alt="A drawing of a QuadCurve2D" /> + * + * @author Eric Blake (ebb9@email.byu.edu) + * @author Graydon Hoare (graydon@redhat.com) + * @author Sascha Brawer (brawer@dandelis.ch) + * @author Sven de Marothy (sven@physto.se) + * + * @since 1.2 + */ +public abstract class QuadCurve2D implements Shape, Cloneable +{ + private static final double BIG_VALUE = java.lang.Double.MAX_VALUE / 10.0; + private static final double EPSILON = 1E-10; + + /** + * Constructs a new QuadCurve2D. Typical users will want to + * construct instances of a subclass, such as {@link + * QuadCurve2D.Float} or {@link QuadCurve2D.Double}. + */ + protected QuadCurve2D() + { + } + + /** + * Returns the <i>x</i> coordinate of the curve’s start + * point. + */ + public abstract double getX1(); + + /** + * Returns the <i>y</i> coordinate of the curve’s start + * point. + */ + public abstract double getY1(); + + /** + * Returns the curve’s start point. + */ + public abstract Point2D getP1(); + + /** + * Returns the <i>x</i> coordinate of the curve’s control + * point. + */ + public abstract double getCtrlX(); + + /** + * Returns the <i>y</i> coordinate of the curve’s control + * point. + */ + public abstract double getCtrlY(); + + /** + * Returns the curve’s control point. + */ + public abstract Point2D getCtrlPt(); + + /** + * Returns the <i>x</i> coordinate of the curve’s end + * point. + */ + public abstract double getX2(); + + /** + * Returns the <i>y</i> coordinate of the curve’s end + * point. + */ + public abstract double getY2(); + + /** + * Returns the curve’s end point. + */ + public abstract Point2D getP2(); + + /** + * Changes the curve geometry, separately specifying each coordinate + * value. + * + * @param x1 the <i>x</i> coordinate of the curve’s new start + * point. + * + * @param y1 the <i>y</i> coordinate of the curve’s new start + * point. + * + * @param cx the <i>x</i> coordinate of the curve’s new + * control point. + * + * @param cy the <i>y</i> coordinate of the curve’s new + * control point. + * + * @param x2 the <i>x</i> coordinate of the curve’s new end + * point. + * + * @param y2 the <i>y</i> coordinate of the curve’s new end + * point. + */ + public abstract void setCurve(double x1, double y1, double cx, double cy, + double x2, double y2); + + /** + * Changes the curve geometry, passing coordinate values in an + * array. + * + * @param coords an array containing the new coordinate values. The + * <i>x</i> coordinate of the new start point is located at + * <code>coords[offset]</code>, its <i>y</i> coordinate at + * <code>coords[offset + 1]</code>. The <i>x</i> coordinate of the + * new control point is located at <code>coords[offset + 2]</code>, + * its <i>y</i> coordinate at <code>coords[offset + 3]</code>. The + * <i>x</i> coordinate of the new end point is located at + * <code>coords[offset + 4]</code>, its <i>y</i> coordinate at + * <code>coords[offset + 5]</code>. + * + * @param offset the offset of the first coordinate value in + * <code>coords</code>. + */ + public void setCurve(double[] coords, int offset) + { + setCurve(coords[offset++], coords[offset++], coords[offset++], + coords[offset++], coords[offset++], coords[offset++]); + } + + /** + * Changes the curve geometry, specifying coordinate values in + * separate Point objects. + * + * <p><img src="doc-files/QuadCurve2D-1.png" width="350" height="180" + * alt="A drawing of a QuadCurve2D" /> + * + * <p>The curve does not keep any reference to the passed point + * objects. Therefore, a later change to <code>p1</code>, + * <code>c</code> <code>p2</code> will not affect the curve + * geometry. + * + * @param p1 the new start point. + * @param c the new control point. + * @param p2 the new end point. + */ + public void setCurve(Point2D p1, Point2D c, Point2D p2) + { + setCurve(p1.getX(), p1.getY(), c.getX(), c.getY(), p2.getX(), p2.getY()); + } + + /** + * Changes the curve geometry, specifying coordinate values in an + * array of Point objects. + * + * <p><img src="doc-files/QuadCurve2D-1.png" width="350" height="180" + * alt="A drawing of a QuadCurve2D" /> + * + * <p>The curve does not keep references to the passed point + * objects. Therefore, a later change to the <code>pts</code> array + * or any of its elements will not affect the curve geometry. + * + * @param pts an array containing the points. The new start point + * is located at <code>pts[offset]</code>, the new control + * point at <code>pts[offset + 1]</code>, and the new end point + * at <code>pts[offset + 2]</code>. + * + * @param offset the offset of the start point in <code>pts</code>. + */ + public void setCurve(Point2D[] pts, int offset) + { + setCurve(pts[offset].getX(), pts[offset].getY(), pts[offset + 1].getX(), + pts[offset + 1].getY(), pts[offset + 2].getX(), + pts[offset + 2].getY()); + } + + /** + * Changes the geometry of the curve to that of another curve. + * + * @param c the curve whose coordinates will be copied. + */ + public void setCurve(QuadCurve2D c) + { + setCurve(c.getX1(), c.getY1(), c.getCtrlX(), c.getCtrlY(), c.getX2(), + c.getY2()); + } + + /** + * Calculates the squared flatness of a quadratic curve, directly + * specifying each coordinate value. The flatness is the distance of + * the control point to the line between start and end point. + * + * <p><img src="doc-files/QuadCurve2D-4.png" width="350" height="180" + * alt="A drawing that illustrates the flatness" /> + * + * <p>In the above drawing, the straight line connecting start point + * P1 and end point P2 is depicted in gray. The result will be the + * the square of the distance between C and the gray line, i.e. + * the squared length of the red line. + * + * @param x1 the <i>x</i> coordinate of the start point P1. + * @param y1 the <i>y</i> coordinate of the start point P1. + * @param cx the <i>x</i> coordinate of the control point C. + * @param cy the <i>y</i> coordinate of the control point C. + * @param x2 the <i>x</i> coordinate of the end point P2. + * @param y2 the <i>y</i> coordinate of the end point P2. + */ + public static double getFlatnessSq(double x1, double y1, double cx, + double cy, double x2, double y2) + { + return Line2D.ptSegDistSq(x1, y1, x2, y2, cx, cy); + } + + /** + * Calculates the flatness of a quadratic curve, directly specifying + * each coordinate value. The flatness is the distance of the + * control point to the line between start and end point. + * + * <p><img src="doc-files/QuadCurve2D-4.png" width="350" height="180" + * alt="A drawing that illustrates the flatness" /> + * + * <p>In the above drawing, the straight line connecting start point + * P1 and end point P2 is depicted in gray. The result will be the + * the distance between C and the gray line, i.e. the length of + * the red line. + * + * @param x1 the <i>x</i> coordinate of the start point P1. + * @param y1 the <i>y</i> coordinate of the start point P1. + * @param cx the <i>x</i> coordinate of the control point C. + * @param cy the <i>y</i> coordinate of the control point C. + * @param x2 the <i>x</i> coordinate of the end point P2. + * @param y2 the <i>y</i> coordinate of the end point P2. + */ + public static double getFlatness(double x1, double y1, double cx, double cy, + double x2, double y2) + { + return Line2D.ptSegDist(x1, y1, x2, y2, cx, cy); + } + + /** + * Calculates the squared flatness of a quadratic curve, specifying + * the coordinate values in an array. The flatness is the distance + * of the control point to the line between start and end point. + * + * <p><img src="doc-files/QuadCurve2D-4.png" width="350" height="180" + * alt="A drawing that illustrates the flatness" /> + * + * <p>In the above drawing, the straight line connecting start point + * P1 and end point P2 is depicted in gray. The result will be the + * the square of the distance between C and the gray line, i.e. + * the squared length of the red line. + * + * @param coords an array containing the coordinate values. The + * <i>x</i> coordinate of the start point P1 is located at + * <code>coords[offset]</code>, its <i>y</i> coordinate at + * <code>coords[offset + 1]</code>. The <i>x</i> coordinate of the + * control point C is located at <code>coords[offset + 2]</code>, + * its <i>y</i> coordinate at <code>coords[offset + 3]</code>. The + * <i>x</i> coordinate of the end point P2 is located at + * <code>coords[offset + 4]</code>, its <i>y</i> coordinate at + * <code>coords[offset + 5]</code>. + * + * @param offset the offset of the first coordinate value in + * <code>coords</code>. + */ + public static double getFlatnessSq(double[] coords, int offset) + { + return Line2D.ptSegDistSq(coords[offset], coords[offset + 1], + coords[offset + 4], coords[offset + 5], + coords[offset + 2], coords[offset + 3]); + } + + /** + * Calculates the flatness of a quadratic curve, specifying the + * coordinate values in an array. The flatness is the distance of + * the control point to the line between start and end point. + * + * <p><img src="doc-files/QuadCurve2D-4.png" width="350" height="180" + * alt="A drawing that illustrates the flatness" /> + * + * <p>In the above drawing, the straight line connecting start point + * P1 and end point P2 is depicted in gray. The result will be the + * the the distance between C and the gray line, i.e. the length of + * the red line. + * + * @param coords an array containing the coordinate values. The + * <i>x</i> coordinate of the start point P1 is located at + * <code>coords[offset]</code>, its <i>y</i> coordinate at + * <code>coords[offset + 1]</code>. The <i>x</i> coordinate of the + * control point C is located at <code>coords[offset + 2]</code>, + * its <i>y</i> coordinate at <code>coords[offset + 3]</code>. The + * <i>x</i> coordinate of the end point P2 is located at + * <code>coords[offset + 4]</code>, its <i>y</i> coordinate at + * <code>coords[offset + 5]</code>. + * + * @param offset the offset of the first coordinate value in + * <code>coords</code>. + */ + public static double getFlatness(double[] coords, int offset) + { + return Line2D.ptSegDist(coords[offset], coords[offset + 1], + coords[offset + 4], coords[offset + 5], + coords[offset + 2], coords[offset + 3]); + } + + /** + * Calculates the squared flatness of this curve. The flatness is + * the distance of the control point to the line between start and + * end point. + * + * <p><img src="doc-files/QuadCurve2D-4.png" width="350" height="180" + * alt="A drawing that illustrates the flatness" /> + * + * <p>In the above drawing, the straight line connecting start point + * P1 and end point P2 is depicted in gray. The result will be the + * the square of the distance between C and the gray line, i.e. the + * squared length of the red line. + */ + public double getFlatnessSq() + { + return Line2D.ptSegDistSq(getX1(), getY1(), getX2(), getY2(), getCtrlX(), + getCtrlY()); + } + + /** + * Calculates the flatness of this curve. The flatness is the + * distance of the control point to the line between start and end + * point. + * + * <p><img src="doc-files/QuadCurve2D-4.png" width="350" height="180" + * alt="A drawing that illustrates the flatness" /> + * + * <p>In the above drawing, the straight line connecting start point + * P1 and end point P2 is depicted in gray. The result will be the + * the distance between C and the gray line, i.e. the length of the + * red line. + */ + public double getFlatness() + { + return Line2D.ptSegDist(getX1(), getY1(), getX2(), getY2(), getCtrlX(), + getCtrlY()); + } + + /** + * Subdivides this curve into two halves. + * + * <p><img src="doc-files/QuadCurve2D-3.png" width="700" + * height="180" alt="A drawing that illustrates the effects of + * subdividing a QuadCurve2D" /> + * + * @param left a curve whose geometry will be set to the left half + * of this curve, or <code>null</code> if the caller is not + * interested in the left half. + * + * @param right a curve whose geometry will be set to the right half + * of this curve, or <code>null</code> if the caller is not + * interested in the right half. + */ + public void subdivide(QuadCurve2D left, QuadCurve2D right) + { + // Use empty slots at end to share single array. + double[] d = new double[] + { + getX1(), getY1(), getCtrlX(), getCtrlY(), getX2(), getY2(), + 0, 0, 0, 0 + }; + subdivide(d, 0, d, 0, d, 4); + if (left != null) + left.setCurve(d, 0); + if (right != null) + right.setCurve(d, 4); + } + + /** + * Subdivides a quadratic curve into two halves. + * + * <p><img src="doc-files/QuadCurve2D-3.png" width="700" + * height="180" alt="A drawing that illustrates the effects of + * subdividing a QuadCurve2D" /> + * + * @param src the curve to be subdivided. + * + * @param left a curve whose geometry will be set to the left half + * of <code>src</code>, or <code>null</code> if the caller is not + * interested in the left half. + * + * @param right a curve whose geometry will be set to the right half + * of <code>src</code>, or <code>null</code> if the caller is not + * interested in the right half. + */ + public static void subdivide(QuadCurve2D src, QuadCurve2D left, + QuadCurve2D right) + { + src.subdivide(left, right); + } + + /** + * Subdivides a quadratic curve into two halves, passing all + * coordinates in an array. + * + * <p><img src="doc-files/QuadCurve2D-3.png" width="700" + * height="180" alt="A drawing that illustrates the effects of + * subdividing a QuadCurve2D" /> + * + * <p>The left end point and the right start point will always be + * identical. Memory-concious programmers thus may want to pass the + * same array for both <code>left</code> and <code>right</code>, and + * set <code>rightOff</code> to <code>leftOff + 4</code>. + * + * @param src an array containing the coordinates of the curve to be + * subdivided. The <i>x</i> coordinate of the start point is + * located at <code>src[srcOff]</code>, its <i>y</i> at + * <code>src[srcOff + 1]</code>. The <i>x</i> coordinate of the + * control point is located at <code>src[srcOff + 2]</code>, its + * <i>y</i> at <code>src[srcOff + 3]</code>. The <i>x</i> + * coordinate of the end point is located at <code>src[srcOff + + * 4]</code>, its <i>y</i> at <code>src[srcOff + 5]</code>. + * + * @param srcOff an offset into <code>src</code>, specifying + * the index of the start point’s <i>x</i> coordinate. + * + * @param left an array that will receive the coordinates of the + * left half of <code>src</code>. It is acceptable to pass + * <code>src</code>. A caller who is not interested in the left half + * can pass <code>null</code>. + * + * @param leftOff an offset into <code>left</code>, specifying the + * index where the start point’s <i>x</i> coordinate will be + * stored. + * + * @param right an array that will receive the coordinates of the + * right half of <code>src</code>. It is acceptable to pass + * <code>src</code> or <code>left</code>. A caller who is not + * interested in the right half can pass <code>null</code>. + * + * @param rightOff an offset into <code>right</code>, specifying the + * index where the start point’s <i>x</i> coordinate will be + * stored. + */ + public static void subdivide(double[] src, int srcOff, double[] left, + int leftOff, double[] right, int rightOff) + { + double x1; + double y1; + double xc; + double yc; + double x2; + double y2; + + x1 = src[srcOff]; + y1 = src[srcOff + 1]; + xc = src[srcOff + 2]; + yc = src[srcOff + 3]; + x2 = src[srcOff + 4]; + y2 = src[srcOff + 5]; + + if (left != null) + { + left[leftOff] = x1; + left[leftOff + 1] = y1; + } + + if (right != null) + { + right[rightOff + 4] = x2; + right[rightOff + 5] = y2; + } + + x1 = (x1 + xc) / 2; + x2 = (xc + x2) / 2; + xc = (x1 + x2) / 2; + y1 = (y1 + yc) / 2; + y2 = (y2 + yc) / 2; + yc = (y1 + y2) / 2; + + if (left != null) + { + left[leftOff + 2] = x1; + left[leftOff + 3] = y1; + left[leftOff + 4] = xc; + left[leftOff + 5] = yc; + } + + if (right != null) + { + right[rightOff] = xc; + right[rightOff + 1] = yc; + right[rightOff + 2] = x2; + right[rightOff + 3] = y2; + } + } + + /** + * Finds the non-complex roots of a quadratic equation, placing the + * results into the same array as the equation coefficients. The + * following equation is being solved: + * + * <blockquote><code>eqn[2]</code> · <i>x</i><sup>2</sup> + * + <code>eqn[1]</code> · <i>x</i> + * + <code>eqn[0]</code> + * = 0 + * </blockquote> + * + * <p>For some background about solving quadratic equations, see the + * article <a href= + * "http://planetmath.org/encyclopedia/QuadraticFormula.html" + * >“Quadratic Formula”</a> in <a href= + * "http://planetmath.org/">PlanetMath</a>. For an extensive library + * of numerical algorithms written in the C programming language, + * see the <a href="http://www.gnu.org/software/gsl/">GNU Scientific + * Library</a>. + * + * @see #solveQuadratic(double[], double[]) + * @see CubicCurve2D#solveCubic(double[], double[]) + * + * @param eqn an array with the coefficients of the equation. When + * this procedure has returned, <code>eqn</code> will contain the + * non-complex solutions of the equation, in no particular order. + * + * @return the number of non-complex solutions. A result of 0 + * indicates that the equation has no non-complex solutions. A + * result of -1 indicates that the equation is constant (i.e., + * always or never zero). + * + * @author Brian Gough (bjg@network-theory.com) + * (original C implementation in the <a href= + * "http://www.gnu.org/software/gsl/">GNU Scientific Library</a>) + * + * @author Sascha Brawer (brawer@dandelis.ch) + * (adaptation to Java) + */ + public static int solveQuadratic(double[] eqn) + { + return solveQuadratic(eqn, eqn); + } + + /** + * Finds the non-complex roots of a quadratic equation. The + * following equation is being solved: + * + * <blockquote><code>eqn[2]</code> · <i>x</i><sup>2</sup> + * + <code>eqn[1]</code> · <i>x</i> + * + <code>eqn[0]</code> + * = 0 + * </blockquote> + * + * <p>For some background about solving quadratic equations, see the + * article <a href= + * "http://planetmath.org/encyclopedia/QuadraticFormula.html" + * >“Quadratic Formula”</a> in <a href= + * "http://planetmath.org/">PlanetMath</a>. For an extensive library + * of numerical algorithms written in the C programming language, + * see the <a href="http://www.gnu.org/software/gsl/">GNU Scientific + * Library</a>. + * + * @see CubicCurve2D#solveCubic(double[],double[]) + * + * @param eqn an array with the coefficients of the equation. + * + * @param res an array into which the non-complex roots will be + * stored. The results may be in an arbitrary order. It is safe to + * pass the same array object reference for both <code>eqn</code> + * and <code>res</code>. + * + * @return the number of non-complex solutions. A result of 0 + * indicates that the equation has no non-complex solutions. A + * result of -1 indicates that the equation is constant (i.e., + * always or never zero). + * + * @author Brian Gough (bjg@network-theory.com) + * (original C implementation in the <a href= + * "http://www.gnu.org/software/gsl/">GNU Scientific Library</a>) + * + * @author Sascha Brawer (brawer@dandelis.ch) + * (adaptation to Java) + */ + public static int solveQuadratic(double[] eqn, double[] res) + { + // Taken from poly/solve_quadratic.c in the GNU Scientific Library + // (GSL), cvs revision 1.7 of 2003-07-26. For the original source, + // see http://www.gnu.org/software/gsl/ + // + // Brian Gough, the author of that code, has granted the + // permission to use it in GNU Classpath under the GNU Classpath + // license, and has assigned the copyright to the Free Software + // Foundation. + // + // The Java implementation is very similar to the GSL code, but + // not a strict one-to-one copy. For example, GSL would sort the + // result. + double a; + double b; + double c; + double disc; + + c = eqn[0]; + b = eqn[1]; + a = eqn[2]; + + // Check for linear or constant functions. This is not done by the + // GNU Scientific Library. Without this special check, we + // wouldn't return -1 for constant functions, and 2 instead of 1 + // for linear functions. + if (a == 0) + { + if (b == 0) + return -1; + + res[0] = -c / b; + return 1; + } + + disc = b * b - 4 * a * c; + + if (disc < 0) + return 0; + + if (disc == 0) + { + // The GNU Scientific Library returns two identical results here. + // We just return one. + res[0] = -0.5 * b / a; + return 1; + } + + // disc > 0 + if (b == 0) + { + double r; + + r = Math.abs(0.5 * Math.sqrt(disc) / a); + res[0] = -r; + res[1] = r; + } + else + { + double sgnb; + double temp; + + sgnb = (b > 0 ? 1 : -1); + temp = -0.5 * (b + sgnb * Math.sqrt(disc)); + + // The GNU Scientific Library sorts the result here. We don't. + res[0] = temp / a; + res[1] = c / temp; + } + return 2; + } + + /** + * Determines whether a point is inside the area bounded + * by the curve and the straight line connecting its end points. + * + * <p><img src="doc-files/QuadCurve2D-5.png" width="350" height="180" + * alt="A drawing of the area spanned by the curve" /> + * + * <p>The above drawing illustrates in which area points are + * considered “inside” a QuadCurve2D. + */ + public boolean contains(double x, double y) + { + if (! getBounds2D().contains(x, y)) + return false; + + return ((getAxisIntersections(x, y, true, BIG_VALUE) & 1) != 0); + } + + /** + * Determines whether a point is inside the area bounded + * by the curve and the straight line connecting its end points. + * + * <p><img src="doc-files/QuadCurve2D-5.png" width="350" height="180" + * alt="A drawing of the area spanned by the curve" /> + * + * <p>The above drawing illustrates in which area points are + * considered “inside” a QuadCurve2D. + */ + public boolean contains(Point2D p) + { + return contains(p.getX(), p.getY()); + } + + /** + * Determines whether any part of a rectangle is inside the area bounded + * by the curve and the straight line connecting its end points. + * + * <p><img src="doc-files/QuadCurve2D-5.png" width="350" height="180" + * alt="A drawing of the area spanned by the curve" /> + * + * <p>The above drawing illustrates in which area points are + * considered “inside” in a CubicCurve2D. + */ + public boolean intersects(double x, double y, double w, double h) + { + if (! getBounds2D().contains(x, y, w, h)) + return false; + + /* Does any edge intersect? */ + if (getAxisIntersections(x, y, true, w) != 0 /* top */ + || getAxisIntersections(x, y + h, true, w) != 0 /* bottom */ + || getAxisIntersections(x + w, y, false, h) != 0 /* right */ + || getAxisIntersections(x, y, false, h) != 0) /* left */ + return true; + + /* No intersections, is any point inside? */ + if ((getAxisIntersections(x, y, true, BIG_VALUE) & 1) != 0) + return true; + + return false; + } + + /** + * Determines whether any part of a Rectangle2D is inside the area bounded + * by the curve and the straight line connecting its end points. + * @see #intersects(double, double, double, double) + */ + public boolean intersects(Rectangle2D r) + { + return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + /** + * Determines whether a rectangle is entirely inside the area bounded + * by the curve and the straight line connecting its end points. + * + * <p><img src="doc-files/QuadCurve2D-5.png" width="350" height="180" + * alt="A drawing of the area spanned by the curve" /> + * + * <p>The above drawing illustrates in which area points are + * considered “inside” a QuadCurve2D. + * @see #contains(double, double) + */ + public boolean contains(double x, double y, double w, double h) + { + if (! getBounds2D().intersects(x, y, w, h)) + return false; + + /* Does any edge intersect? */ + if (getAxisIntersections(x, y, true, w) != 0 /* top */ + || getAxisIntersections(x, y + h, true, w) != 0 /* bottom */ + || getAxisIntersections(x + w, y, false, h) != 0 /* right */ + || getAxisIntersections(x, y, false, h) != 0) /* left */ + return false; + + /* No intersections, is any point inside? */ + if ((getAxisIntersections(x, y, true, BIG_VALUE) & 1) != 0) + return true; + + return false; + } + + /** + * Determines whether a Rectangle2D is entirely inside the area that is + * bounded by the curve and the straight line connecting its end points. + * @see #contains(double, double, double, double) + */ + public boolean contains(Rectangle2D r) + { + return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + /** + * Determines the smallest rectangle that encloses the + * curve’s start, end and control point. As the illustration + * below shows, the invisible control point may cause the bounds to + * be much larger than the area that is actually covered by the + * curve. + * + * <p><img src="doc-files/QuadCurve2D-2.png" width="350" height="180" + * alt="An illustration of the bounds of a QuadCurve2D" /> + */ + public Rectangle getBounds() + { + return getBounds2D().getBounds(); + } + + public PathIterator getPathIterator(final AffineTransform at) + { + return new PathIterator() + { + /** Current coordinate. */ + private int current = 0; + + public int getWindingRule() + { + return WIND_NON_ZERO; + } + + public boolean isDone() + { + return current >= 2; + } + + public void next() + { + current++; + } + + public int currentSegment(float[] coords) + { + int result; + switch (current) + { + case 0: + coords[0] = (float) getX1(); + coords[1] = (float) getY1(); + result = SEG_MOVETO; + break; + case 1: + coords[0] = (float) getCtrlX(); + coords[1] = (float) getCtrlY(); + coords[2] = (float) getX2(); + coords[3] = (float) getY2(); + result = SEG_QUADTO; + break; + default: + throw new NoSuchElementException("quad iterator out of bounds"); + } + if (at != null) + at.transform(coords, 0, coords, 0, 2); + return result; + } + + public int currentSegment(double[] coords) + { + int result; + switch (current) + { + case 0: + coords[0] = getX1(); + coords[1] = getY1(); + result = SEG_MOVETO; + break; + case 1: + coords[0] = getCtrlX(); + coords[1] = getCtrlY(); + coords[2] = getX2(); + coords[3] = getY2(); + result = SEG_QUADTO; + break; + default: + throw new NoSuchElementException("quad iterator out of bounds"); + } + if (at != null) + at.transform(coords, 0, coords, 0, 2); + return result; + } + }; + } + + public PathIterator getPathIterator(AffineTransform at, double flatness) + { + return new FlatteningPathIterator(getPathIterator(at), flatness); + } + + /** + * Creates a new curve with the same contents as this one. + * + * @return the clone. + */ + public Object clone() + { + try + { + return super.clone(); + } + catch (CloneNotSupportedException e) + { + throw (Error) new InternalError().initCause(e); // Impossible + } + } + + /** + * Helper method used by contains() and intersects() methods + * Return the number of curve/line intersections on a given axis + * extending from a certain point. useYaxis is true for using the Y axis, + * @param x x coordinate of the origin point + * @param y y coordinate of the origin point + * @param useYaxis axis to follow, if true the positive Y axis is used, + * false uses the positive X axis. + * + * This is an implementation of the line-crossings algorithm, + * Detailed in an article on Eric Haines' page: + * http://www.acm.org/tog/editors/erich/ptinpoly/ + */ + private int getAxisIntersections(double x, double y, boolean useYaxis, + double distance) + { + int nCrossings = 0; + double a0; + double a1; + double a2; + double b0; + double b1; + double b2; + double[] r = new double[3]; + int nRoots; + + a0 = a2 = 0.0; + + if (useYaxis) + { + a0 = getY1() - y; + a1 = getCtrlY() - y; + a2 = getY2() - y; + b0 = getX1() - x; + b1 = getCtrlX() - x; + b2 = getX2() - x; + } + else + { + a0 = getX1() - x; + a1 = getCtrlX() - x; + a2 = getX2() - x; + b0 = getY1() - y; + b1 = getCtrlY() - y; + b2 = getY2() - y; + } + + /* If the axis intersects a start/endpoint, shift it up by some small + amount to guarantee the line is 'inside' + If this is not done,bad behaviour may result for points on that axis. */ + if (a0 == 0.0 || a2 == 0.0) + { + double small = getFlatness() * EPSILON; + if (a0 == 0.0) + a0 -= small; + + if (a2 == 0.0) + a2 -= small; + } + + r[0] = a0; + r[1] = 2 * (a1 - a0); + r[2] = (a2 - 2 * a1 + a0); + + nRoots = solveQuadratic(r); + for (int i = 0; i < nRoots; i++) + { + double t = r[i]; + if (t >= 0.0 && t <= 1.0) + { + double crossing = t * t * (b2 - 2 * b1 + b0) + 2 * t * (b1 - b0) + + b0; + /* single root is always doubly degenerate in quads */ + if (crossing > 0 && crossing < distance) + nCrossings += (nRoots == 1) ? 2 : 1; + } + } + + if (useYaxis) + { + if (Line2D.linesIntersect(b0, a0, b2, a2, EPSILON, 0.0, distance, 0.0)) + nCrossings++; + } + else + { + if (Line2D.linesIntersect(a0, b0, a2, b2, 0.0, EPSILON, 0.0, distance)) + nCrossings++; + } + + return (nCrossings); + } + + /** + * A two-dimensional curve that is parameterized with a quadratic + * function and stores coordinate values in double-precision + * floating-point format. + * + * @see QuadCurve2D.Float + * + * @author Eric Blake (ebb9@email.byu.edu) + * @author Sascha Brawer (brawer@dandelis.ch) + */ + public static class Double extends QuadCurve2D + { + /** + * The <i>x</i> coordinate of the curve’s start point. + */ + public double x1; + + /** + * The <i>y</i> coordinate of the curve’s start point. + */ + public double y1; + + /** + * The <i>x</i> coordinate of the curve’s control point. + */ + public double ctrlx; + + /** + * The <i>y</i> coordinate of the curve’s control point. + */ + public double ctrly; + + /** + * The <i>x</i> coordinate of the curve’s end point. + */ + public double x2; + + /** + * The <i>y</i> coordinate of the curve’s end point. + */ + public double y2; + + /** + * Constructs a new QuadCurve2D that stores its coordinate values + * in double-precision floating-point format. All points are + * initially at position (0, 0). + */ + public Double() + { + } + + /** + * Constructs a new QuadCurve2D that stores its coordinate values + * in double-precision floating-point format, specifying the + * initial position of each point. + * + * @param x1 the <i>x</i> coordinate of the curve’s start + * point. + * + * @param y1 the <i>y</i> coordinate of the curve’s start + * point. + * + * @param cx the <i>x</i> coordinate of the curve’s control + * point. + * + * @param cy the <i>y</i> coordinate of the curve’s control + * point. + * + * @param x2 the <i>x</i> coordinate of the curve’s end + * point. + * + * @param y2 the <i>y</i> coordinate of the curve’s end + * point. + */ + public Double(double x1, double y1, double cx, double cy, double x2, + double y2) + { + this.x1 = x1; + this.y1 = y1; + ctrlx = cx; + ctrly = cy; + this.x2 = x2; + this.y2 = y2; + } + + /** + * Returns the <i>x</i> coordinate of the curve’s start + * point. + */ + public double getX1() + { + return x1; + } + + /** + * Returns the <i>y</i> coordinate of the curve’s start + * point. + */ + public double getY1() + { + return y1; + } + + /** + * Returns the curve’s start point. + */ + public Point2D getP1() + { + return new Point2D.Double(x1, y1); + } + + /** + * Returns the <i>x</i> coordinate of the curve’s control + * point. + */ + public double getCtrlX() + { + return ctrlx; + } + + /** + * Returns the <i>y</i> coordinate of the curve’s control + * point. + */ + public double getCtrlY() + { + return ctrly; + } + + /** + * Returns the curve’s control point. + */ + public Point2D getCtrlPt() + { + return new Point2D.Double(ctrlx, ctrly); + } + + /** + * Returns the <i>x</i> coordinate of the curve’s end + * point. + */ + public double getX2() + { + return x2; + } + + /** + * Returns the <i>y</i> coordinate of the curve’s end + * point. + */ + public double getY2() + { + return y2; + } + + /** + * Returns the curve’s end point. + */ + public Point2D getP2() + { + return new Point2D.Double(x2, y2); + } + + /** + * Changes the geometry of the curve. + * + * @param x1 the <i>x</i> coordinate of the curve’s new + * start point. + * + * @param y1 the <i>y</i> coordinate of the curve’s new + * start point. + * + * @param cx the <i>x</i> coordinate of the curve’s new + * control point. + * + * @param cy the <i>y</i> coordinate of the curve’s new + * control point. + * + * @param x2 the <i>x</i> coordinate of the curve’s new + * end point. + * + * @param y2 the <i>y</i> coordinate of the curve’s new + * end point. + */ + public void setCurve(double x1, double y1, double cx, double cy, + double x2, double y2) + { + this.x1 = x1; + this.y1 = y1; + ctrlx = cx; + ctrly = cy; + this.x2 = x2; + this.y2 = y2; + } + + /** + * Determines the smallest rectangle that encloses the + * curve’s start, end and control point. As the + * illustration below shows, the invisible control point may cause + * the bounds to be much larger than the area that is actually + * covered by the curve. + * + * <p><img src="doc-files/QuadCurve2D-2.png" width="350" height="180" + * alt="An illustration of the bounds of a QuadCurve2D" /> + */ + public Rectangle2D getBounds2D() + { + double nx1 = Math.min(Math.min(x1, ctrlx), x2); + double ny1 = Math.min(Math.min(y1, ctrly), y2); + double nx2 = Math.max(Math.max(x1, ctrlx), x2); + double ny2 = Math.max(Math.max(y1, ctrly), y2); + return new Rectangle2D.Double(nx1, ny1, nx2 - nx1, ny2 - ny1); + } + } + + /** + * A two-dimensional curve that is parameterized with a quadratic + * function and stores coordinate values in single-precision + * floating-point format. + * + * @see QuadCurve2D.Double + * + * @author Eric Blake (ebb9@email.byu.edu) + * @author Sascha Brawer (brawer@dandelis.ch) + */ + public static class Float extends QuadCurve2D + { + /** + * The <i>x</i> coordinate of the curve’s start point. + */ + public float x1; + + /** + * The <i>y</i> coordinate of the curve’s start point. + */ + public float y1; + + /** + * The <i>x</i> coordinate of the curve’s control point. + */ + public float ctrlx; + + /** + * The <i>y</i> coordinate of the curve’s control point. + */ + public float ctrly; + + /** + * The <i>x</i> coordinate of the curve’s end point. + */ + public float x2; + + /** + * The <i>y</i> coordinate of the curve’s end point. + */ + public float y2; + + /** + * Constructs a new QuadCurve2D that stores its coordinate values + * in single-precision floating-point format. All points are + * initially at position (0, 0). + */ + public Float() + { + } + + /** + * Constructs a new QuadCurve2D that stores its coordinate values + * in single-precision floating-point format, specifying the + * initial position of each point. + * + * @param x1 the <i>x</i> coordinate of the curve’s start + * point. + * + * @param y1 the <i>y</i> coordinate of the curve’s start + * point. + * + * @param cx the <i>x</i> coordinate of the curve’s control + * point. + * + * @param cy the <i>y</i> coordinate of the curve’s control + * point. + * + * @param x2 the <i>x</i> coordinate of the curve’s end + * point. + * + * @param y2 the <i>y</i> coordinate of the curve’s end + * point. + */ + public Float(float x1, float y1, float cx, float cy, float x2, float y2) + { + this.x1 = x1; + this.y1 = y1; + ctrlx = cx; + ctrly = cy; + this.x2 = x2; + this.y2 = y2; + } + + /** + * Returns the <i>x</i> coordinate of the curve’s start + * point. + */ + public double getX1() + { + return x1; + } + + /** + * Returns the <i>y</i> coordinate of the curve’s start + * point. + */ + public double getY1() + { + return y1; + } + + /** + * Returns the curve’s start point. + */ + public Point2D getP1() + { + return new Point2D.Float(x1, y1); + } + + /** + * Returns the <i>x</i> coordinate of the curve’s control + * point. + */ + public double getCtrlX() + { + return ctrlx; + } + + /** + * Returns the <i>y</i> coordinate of the curve’s control + * point. + */ + public double getCtrlY() + { + return ctrly; + } + + /** + * Returns the curve’s control point. + */ + public Point2D getCtrlPt() + { + return new Point2D.Float(ctrlx, ctrly); + } + + /** + * Returns the <i>x</i> coordinate of the curve’s end + * point. + */ + public double getX2() + { + return x2; + } + + /** + * Returns the <i>y</i> coordinate of the curve’s end + * point. + */ + public double getY2() + { + return y2; + } + + /** + * Returns the curve’s end point. + */ + public Point2D getP2() + { + return new Point2D.Float(x2, y2); + } + + /** + * Changes the geometry of the curve, specifying coordinate values + * as double-precision floating-point numbers. + * + * @param x1 the <i>x</i> coordinate of the curve’s new + * start point. + * + * @param y1 the <i>y</i> coordinate of the curve’s new + * start point. + * + * @param cx the <i>x</i> coordinate of the curve’s new + * control point. + * + * @param cy the <i>y</i> coordinate of the curve’s new + * control point. + * + * @param x2 the <i>x</i> coordinate of the curve’s new + * end point. + * + * @param y2 the <i>y</i> coordinate of the curve’s new + * end point. + */ + public void setCurve(double x1, double y1, double cx, double cy, + double x2, double y2) + { + this.x1 = (float) x1; + this.y1 = (float) y1; + ctrlx = (float) cx; + ctrly = (float) cy; + this.x2 = (float) x2; + this.y2 = (float) y2; + } + + /** + * Changes the geometry of the curve, specifying coordinate values + * as single-precision floating-point numbers. + * + * @param x1 the <i>x</i> coordinate of the curve’s new + * start point. + * + * @param y1 the <i>y</i> coordinate of the curve’s new + * start point. + * + * @param cx the <i>x</i> coordinate of the curve’s new + * control point. + * + * @param cy the <i>y</i> coordinate of the curve’s new + * control point. + * + * @param x2 the <i>x</i> coordinate of the curve’s new + * end point. + * + * @param y2 the <i>y</i> coordinate of the curve’s new + * end point. + */ + public void setCurve(float x1, float y1, float cx, float cy, float x2, + float y2) + { + this.x1 = x1; + this.y1 = y1; + ctrlx = cx; + ctrly = cy; + this.x2 = x2; + this.y2 = y2; + } + + /** + * Determines the smallest rectangle that encloses the + * curve’s start, end and control point. As the + * illustration below shows, the invisible control point may cause + * the bounds to be much larger than the area that is actually + * covered by the curve. + * + * <p><img src="doc-files/QuadCurve2D-2.png" width="350" height="180" + * alt="An illustration of the bounds of a QuadCurve2D" /> + */ + public Rectangle2D getBounds2D() + { + float nx1 = Math.min(Math.min(x1, ctrlx), x2); + float ny1 = Math.min(Math.min(y1, ctrly), y2); + float nx2 = Math.max(Math.max(x1, ctrlx), x2); + float ny2 = Math.max(Math.max(y1, ctrly), y2); + return new Rectangle2D.Float(nx1, ny1, nx2 - nx1, ny2 - ny1); + } + } +} diff --git a/libjava/classpath/java/awt/geom/Rectangle2D.java b/libjava/classpath/java/awt/geom/Rectangle2D.java new file mode 100644 index 000000000..6a255f953 --- /dev/null +++ b/libjava/classpath/java/awt/geom/Rectangle2D.java @@ -0,0 +1,992 @@ +/* Rectangle2D.java -- generic rectangles in 2-D space + Copyright (C) 2000, 2001, 2002, 2004 Free Software Foundation + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.awt.geom; + +import java.util.NoSuchElementException; + +/** + * This class describes a rectangle by a point (x,y) and dimension (w x h). + * The actual storage is left up to subclasses. + * + * <p>It is valid for a rectangle to have negative width or height; but it + * is considered to have no area or internal points. Therefore, the behavior + * in methods like <code>contains</code> or <code>intersects</code> is + * undefined unless the rectangle has positive width and height. + * + * @author Tom Tromey (tromey@cygnus.com) + * @author Eric Blake (ebb9@email.byu.edu) + * @since 1.2 + * @status updated to 1.4 + */ +public abstract class Rectangle2D extends RectangularShape +{ + /** + * The point lies left of the rectangle (p.x < r.x). + * + * @see #outcode(double, double) + */ + public static final int OUT_LEFT = 1; + + /** + * The point lies above the rectangle (p.y < r.y). + * + * @see #outcode(double, double) + */ + public static final int OUT_TOP = 2; + + /** + * The point lies right of the rectangle (p.x > r.maxX). + * + * @see #outcode(double, double) + */ + public static final int OUT_RIGHT = 4; + + /** + * The point lies below of the rectangle (p.y > r.maxY). + * + * @see #outcode(double, double) + */ + public static final int OUT_BOTTOM = 8; + + /** + * Default constructor. + */ + protected Rectangle2D() + { + } + + /** + * Set the bounding box of this rectangle. + * + * @param x the new X coordinate + * @param y the new Y coordinate + * @param w the new width + * @param h the new height + */ + public abstract void setRect(double x, double y, double w, double h); + + /** + * Set the bounding box of this rectangle from the given one. + * + * @param r rectangle to copy + * @throws NullPointerException if r is null + */ + public void setRect(Rectangle2D r) + { + setRect(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + /** + * Tests if the specified line intersects the interior of this rectangle. + * + * @param x1 the first x coordinate of line segment + * @param y1 the first y coordinate of line segment + * @param x2 the second x coordinate of line segment + * @param y2 the second y coordinate of line segment + * @return true if the line intersects the rectangle + */ + public boolean intersectsLine(double x1, double y1, double x2, double y2) + { + double x = getX(); + double y = getY(); + double w = getWidth(); + double h = getHeight(); + if (w <= 0 || h <= 0) + return false; + + if (x1 >= x && x1 <= x + w && y1 >= y && y1 <= y + h) + return true; + if (x2 >= x && x2 <= x + w && y2 >= y && y2 <= y + h) + return true; + + double x3 = x + w; + double y3 = y + h; + + return (Line2D.linesIntersect(x1, y1, x2, y2, x, y, x, y3) + || Line2D.linesIntersect(x1, y1, x2, y2, x, y3, x3, y3) + || Line2D.linesIntersect(x1, y1, x2, y2, x3, y3, x3, y) + || Line2D.linesIntersect(x1, y1, x2, y2, x3, y, x, y)); + } + + /** + * Tests if the specified line intersects the interior of this rectangle. + * + * @param l the line segment + * @return true if the line intersects the rectangle + * @throws NullPointerException if l is null + */ + public boolean intersectsLine(Line2D l) + { + return intersectsLine(l.getX1(), l.getY1(), l.getX2(), l.getY2()); + } + + /** + * Determine where the point lies with respect to this rectangle. The + * result will be the binary OR of the appropriate bit masks. + * + * @param x the x coordinate to check + * @param y the y coordinate to check + * @return the binary OR of the result + * @see #OUT_LEFT + * @see #OUT_TOP + * @see #OUT_RIGHT + * @see #OUT_BOTTOM + */ + public abstract int outcode(double x, double y); + + /** + * Determine where the point lies with respect to this rectangle. The + * result will be the binary OR of the appropriate bit masks. + * + * @param p the point to check + * @return the binary OR of the result + * @throws NullPointerException if p is null + * @see #OUT_LEFT + * @see #OUT_TOP + * @see #OUT_RIGHT + * @see #OUT_BOTTOM + */ + public int outcode(Point2D p) + { + return outcode(p.getX(), p.getY()); + } + + /** + * Set the bounding box of this rectangle. + * + * @param x the new X coordinate + * @param y the new Y coordinate + * @param w the new width + * @param h the new height + */ + public void setFrame(double x, double y, double w, double h) + { + setRect(x, y, w, h); + } + + /** + * Returns the bounds of this rectangle. A pretty useless method, as this + * is already a rectangle. + * + * @return a copy of this rectangle + */ + public Rectangle2D getBounds2D() + { + return (Rectangle2D) clone(); + } + + /** + * Test if the given point is contained in the rectangle. + * + * @param x the x coordinate of the point + * @param y the y coordinate of the point + * @return true if (x,y) is in the rectangle + */ + public boolean contains(double x, double y) + { + double mx = getX(); + double my = getY(); + double w = getWidth(); + double h = getHeight(); + return w > 0 && h > 0 && x >= mx && x < mx + w && y >= my && y < my + h; + } + + /** + * Tests if the given rectangle intersects this one. In other words, test if + * the two rectangles share at least one internal point. + * + * @param x the x coordinate of the other rectangle + * @param y the y coordinate of the other rectangle + * @param w the width of the other rectangle + * @param h the height of the other rectangle + * @return true if the rectangles intersect + */ + public boolean intersects(double x, double y, double w, double h) + { + double mx = getX(); + double my = getY(); + double mw = getWidth(); + double mh = getHeight(); + return w > 0 && h > 0 && mw > 0 && mh > 0 + && x < mx + mw && x + w > mx && y < my + mh && y + h > my; + } + + /** + * Tests if this rectangle contains the given one. In other words, test if + * this rectangle contains all points in the given one. + * + * @param x the x coordinate of the other rectangle + * @param y the y coordinate of the other rectangle + * @param w the width of the other rectangle + * @param h the height of the other rectangle + * @return true if this rectangle contains the other + */ + public boolean contains(double x, double y, double w, double h) + { + double mx = getX(); + double my = getY(); + double mw = getWidth(); + double mh = getHeight(); + return w > 0 && h > 0 && mw > 0 && mh > 0 + && x >= mx && x + w <= mx + mw && y >= my && y + h <= my + mh; + } + + /** + * Return a new rectangle which is the intersection of this and the given + * one. The result will be empty if there is no intersection. + * + * @param r the rectangle to be intersected + * @return the intersection + * @throws NullPointerException if r is null + */ + public abstract Rectangle2D createIntersection(Rectangle2D r); + + /** + * Intersects a pair of rectangles, and places the result in the + * destination; this can be used to avoid object creation. This method + * even works when the destination is also a source, although you stand + * to lose the original data. + * + * @param src1 the first source + * @param src2 the second source + * @param dest the destination for the intersection + * @throws NullPointerException if any rectangle is null + */ + public static void intersect(Rectangle2D src1, Rectangle2D src2, + Rectangle2D dest) + { + double x = Math.max(src1.getX(), src2.getX()); + double y = Math.max(src1.getY(), src2.getY()); + double maxx = Math.min(src1.getMaxX(), src2.getMaxX()); + double maxy = Math.min(src1.getMaxY(), src2.getMaxY()); + dest.setRect(x, y, maxx - x, maxy - y); + } + + /** + * Return a new rectangle which is the union of this and the given one. + * + * @param r the rectangle to be merged + * @return the union + * @throws NullPointerException if r is null + */ + public abstract Rectangle2D createUnion(Rectangle2D r); + + /** + * Joins a pair of rectangles, and places the result in the destination; + * this can be used to avoid object creation. This method even works when + * the destination is also a source, although you stand to lose the + * original data. + * + * @param src1 the first source + * @param src2 the second source + * @param dest the destination for the union + * @throws NullPointerException if any rectangle is null + */ + public static void union(Rectangle2D src1, Rectangle2D src2, + Rectangle2D dest) + { + double x = Math.min(src1.getX(), src2.getX()); + double y = Math.min(src1.getY(), src2.getY()); + double maxx = Math.max(src1.getMaxX(), src2.getMaxX()); + double maxy = Math.max(src1.getMaxY(), src2.getMaxY()); + dest.setRect(x, y, maxx - x, maxy - y); + } + + /** + * Modifies this rectangle so that it represents the smallest rectangle + * that contains both the existing rectangle and the specified point. + * However, if the point falls on one of the two borders which are not + * inside the rectangle, a subsequent call to <code>contains</code> may + * return false. + * + * @param newx the X coordinate of the point to add to this rectangle + * @param newy the Y coordinate of the point to add to this rectangle + */ + public void add(double newx, double newy) + { + double minx = Math.min(getX(), newx); + double maxx = Math.max(getMaxX(), newx); + double miny = Math.min(getY(), newy); + double maxy = Math.max(getMaxY(), newy); + setRect(minx, miny, maxx - minx, maxy - miny); + } + + /** + * Modifies this rectangle so that it represents the smallest rectangle + * that contains both the existing rectangle and the specified point. + * However, if the point falls on one of the two borders which are not + * inside the rectangle, a subsequent call to <code>contains</code> may + * return false. + * + * @param p the point to add to this rectangle + * @throws NullPointerException if p is null + */ + public void add(Point2D p) + { + add(p.getX(), p.getY()); + } + + /** + * Modifies this rectangle so that it represents the smallest rectangle + * that contains both the existing rectangle and the specified rectangle. + * + * @param r the rectangle to add to this rectangle + * @throws NullPointerException if r is null + * @see #union(Rectangle2D, Rectangle2D, Rectangle2D) + */ + public void add(Rectangle2D r) + { + union(this, r, this); + } + + /** + * Return an iterator along the shape boundary. If the optional transform + * is provided, the iterator is transformed accordingly. Each call returns + * a new object, independent from others in use. This iterator is thread + * safe; modifications to the rectangle do not affect the results of this + * path instance. + * + * @param at an optional transform to apply to the iterator + * @return a new iterator over the boundary + * @since 1.2 + */ + public PathIterator getPathIterator(final AffineTransform at) + { + final double minx = getX(); + final double miny = getY(); + final double maxx = minx + getWidth(); + final double maxy = miny + getHeight(); + return new PathIterator() + { + /** Current coordinate. */ + private int current = (maxx <= minx && maxy <= miny) ? 6 : 0; + + public int getWindingRule() + { + // A test program showed that Sun J2SE 1.3.1 and 1.4.1_01 + // return WIND_NON_ZERO paths. While this does not really + // make any difference for rectangles (because they are not + // self-intersecting), it seems appropriate to behave + // identically. + + return WIND_NON_ZERO; + } + + public boolean isDone() + { + return current > 5; + } + + public void next() + { + current++; + } + + public int currentSegment(float[] coords) + { + switch (current) + { + case 1: + coords[0] = (float) maxx; + coords[1] = (float) miny; + break; + case 2: + coords[0] = (float) maxx; + coords[1] = (float) maxy; + break; + case 3: + coords[0] = (float) minx; + coords[1] = (float) maxy; + break; + case 0: + case 4: + coords[0] = (float) minx; + coords[1] = (float) miny; + break; + case 5: + return SEG_CLOSE; + default: + throw new NoSuchElementException("rect iterator out of bounds"); + } + if (at != null) + at.transform(coords, 0, coords, 0, 1); + return current == 0 ? SEG_MOVETO : SEG_LINETO; + } + + public int currentSegment(double[] coords) + { + switch (current) + { + case 1: + coords[0] = maxx; + coords[1] = miny; + break; + case 2: + coords[0] = maxx; + coords[1] = maxy; + break; + case 3: + coords[0] = minx; + coords[1] = maxy; + break; + case 0: + case 4: + coords[0] = minx; + coords[1] = miny; + break; + case 5: + return SEG_CLOSE; + default: + throw new NoSuchElementException("rect iterator out of bounds"); + } + if (at != null) + at.transform(coords, 0, coords, 0, 1); + return current == 0 ? SEG_MOVETO : SEG_LINETO; + } + }; + } + + /** + * Return an iterator along the shape boundary. If the optional transform + * is provided, the iterator is transformed accordingly. Each call returns + * a new object, independent from others in use. This iterator is thread + * safe; modifications to the rectangle do not affect the results of this + * path instance. As the rectangle is already flat, the flatness parameter + * is ignored. + * + * @param at an optional transform to apply to the iterator + * @param flatness the maximum distance for deviation from the real boundary + * @return a new iterator over the boundary + * @since 1.2 + */ + public PathIterator getPathIterator(AffineTransform at, double flatness) + { + return getPathIterator(at); + } + + /** + * Return the hashcode for this rectangle. The formula is not documented, but + * appears to be the same as: + * <pre> + * long l = Double.doubleToLongBits(getX()) + * + 37 * Double.doubleToLongBits(getY()) + * + 43 * Double.doubleToLongBits(getWidth()) + * + 47 * Double.doubleToLongBits(getHeight()); + * return (int) ((l >> 32) ^ l); + * </pre> + * + * @return the hashcode + */ + public int hashCode() + { + // Talk about a fun time reverse engineering this one! + long l = java.lang.Double.doubleToLongBits(getX()) + + 37 * java.lang.Double.doubleToLongBits(getY()) + + 43 * java.lang.Double.doubleToLongBits(getWidth()) + + 47 * java.lang.Double.doubleToLongBits(getHeight()); + return (int) ((l >> 32) ^ l); + } + + /** + * Tests this rectangle for equality against the specified object. This + * will be true if an only if the specified object is an instance of + * Rectangle2D with the same coordinates and dimensions. + * + * @param obj the object to test against for equality + * @return true if the specified object is equal to this one + */ + public boolean equals(Object obj) + { + if (! (obj instanceof Rectangle2D)) + return false; + Rectangle2D r = (Rectangle2D) obj; + return r.getX() == getX() && r.getY() == getY() + && r.getWidth() == getWidth() && r.getHeight() == getHeight(); + } + + /** + * This class defines a rectangle in <code>double</code> precision. + * + * @author Eric Blake (ebb9@email.byu.edu) + * @since 1.2 + * @status updated to 1.4 + */ + public static class Double extends Rectangle2D + { + /** The x coordinate of the lower left corner. */ + public double x; + + /** The y coordinate of the lower left corner. */ + public double y; + + /** The width of the rectangle. */ + public double width; + + /** The height of the rectangle. */ + public double height; + + /** + * Create a rectangle at (0,0) with width 0 and height 0. + */ + public Double() + { + } + + /** + * Create a rectangle with the given values. + * + * @param x the x coordinate + * @param y the y coordinate + * @param w the width + * @param h the height + */ + public Double(double x, double y, double w, double h) + { + this.x = x; + this.y = y; + width = w; + height = h; + } + + /** + * Return the X coordinate. + * + * @return the value of x + */ + public double getX() + { + return x; + } + + /** + * Return the Y coordinate. + * + * @return the value of y + */ + public double getY() + { + return y; + } + + /** + * Return the width. + * + * @return the value of width + */ + public double getWidth() + { + return width; + } + + /** + * Return the height. + * + * @return the value of height + */ + public double getHeight() + { + return height; + } + + /** + * Test if the rectangle is empty. + * + * @return true if width or height is not positive + */ + public boolean isEmpty() + { + return width <= 0 || height <= 0; + } + + /** + * Set the contents of this rectangle to those specified. + * + * @param x the x coordinate + * @param y the y coordinate + * @param w the width + * @param h the height + */ + public void setRect(double x, double y, double w, double h) + { + this.x = x; + this.y = y; + width = w; + height = h; + } + + /** + * Set the contents of this rectangle to those specified. + * + * @param r the rectangle to copy + * @throws NullPointerException if r is null + */ + public void setRect(Rectangle2D r) + { + x = r.getX(); + y = r.getY(); + width = r.getWidth(); + height = r.getHeight(); + } + + /** + * Determine where the point lies with respect to this rectangle. The + * result will be the binary OR of the appropriate bit masks. + * + * @param x the x coordinate to check + * @param y the y coordinate to check + * @return the binary OR of the result + * @see #OUT_LEFT + * @see #OUT_TOP + * @see #OUT_RIGHT + * @see #OUT_BOTTOM + * @since 1.2 + */ + public int outcode(double x, double y) + { + int result = 0; + if (width <= 0) + result |= OUT_LEFT | OUT_RIGHT; + else if (x < this.x) + result |= OUT_LEFT; + else if (x > this.x + width) + result |= OUT_RIGHT; + if (height <= 0) + result |= OUT_BOTTOM | OUT_TOP; + else if (y < this.y) // Remember that +y heads top-to-bottom. + result |= OUT_TOP; + else if (y > this.y + height) + result |= OUT_BOTTOM; + return result; + } + + /** + * Returns the bounds of this rectangle. A pretty useless method, as this + * is already a rectangle. + * + * @return a copy of this rectangle + */ + public Rectangle2D getBounds2D() + { + return new Double(x, y, width, height); + } + + /** + * Return a new rectangle which is the intersection of this and the given + * one. The result will be empty if there is no intersection. + * + * @param r the rectangle to be intersected + * @return the intersection + * @throws NullPointerException if r is null + */ + public Rectangle2D createIntersection(Rectangle2D r) + { + Double res = new Double(); + intersect(this, r, res); + return res; + } + + /** + * Return a new rectangle which is the union of this and the given one. + * + * @param r the rectangle to be merged + * @return the union + * @throws NullPointerException if r is null + */ + public Rectangle2D createUnion(Rectangle2D r) + { + Double res = new Double(); + union(this, r, res); + return res; + } + + /** + * Returns a string representation of this rectangle. This is in the form + * <code>getClass().getName() + "[x=" + x + ",y=" + y + ",w=" + width + * + ",h=" + height + ']'</code>. + * + * @return a string representation of this rectangle + */ + public String toString() + { + return getClass().getName() + "[x=" + x + ",y=" + y + ",w=" + width + + ",h=" + height + ']'; + } + } + + /** + * This class defines a rectangle in <code>float</code> precision. + * + * @author Eric Blake (ebb9@email.byu.edu) + * @since 1.2 + * @status updated to 1.4 + */ + public static class Float extends Rectangle2D + { + /** The x coordinate of the lower left corner. */ + public float x; + + /** The y coordinate of the lower left corner. */ + public float y; + + /** The width of the rectangle. */ + public float width; + + /** The height of the rectangle. */ + public float height; + + /** + * Create a rectangle at (0,0) with width 0 and height 0. + */ + public Float() + { + } + + /** + * Create a rectangle with the given values. + * + * @param x the x coordinate + * @param y the y coordinate + * @param w the width + * @param h the height + */ + public Float(float x, float y, float w, float h) + { + this.x = x; + this.y = y; + width = w; + height = h; + } + + /** + * Create a rectangle with the given values. + * + * @param x the x coordinate + * @param y the y coordinate + * @param w the width + * @param h the height + */ + Float(double x, double y, double w, double h) + { + this.x = (float) x; + this.y = (float) y; + width = (float) w; + height = (float) h; + } + + /** + * Return the X coordinate. + * + * @return the value of x + */ + public double getX() + { + return x; + } + + /** + * Return the Y coordinate. + * + * @return the value of y + */ + public double getY() + { + return y; + } + + /** + * Return the width. + * + * @return the value of width + */ + public double getWidth() + { + return width; + } + + /** + * Return the height. + * + * @return the value of height + */ + public double getHeight() + { + return height; + } + + /** + * Test if the rectangle is empty. + * + * @return true if width or height is not positive + */ + public boolean isEmpty() + { + return width <= 0 || height <= 0; + } + + /** + * Set the contents of this rectangle to those specified. + * + * @param x the x coordinate + * @param y the y coordinate + * @param w the width + * @param h the height + */ + public void setRect(float x, float y, float w, float h) + { + this.x = x; + this.y = y; + width = w; + height = h; + } + + /** + * Set the contents of this rectangle to those specified. + * + * @param x the x coordinate + * @param y the y coordinate + * @param w the width + * @param h the height + */ + public void setRect(double x, double y, double w, double h) + { + this.x = (float) x; + this.y = (float) y; + width = (float) w; + height = (float) h; + } + + /** + * Set the contents of this rectangle to those specified. + * + * @param r the rectangle to copy + * @throws NullPointerException if r is null + */ + public void setRect(Rectangle2D r) + { + x = (float) r.getX(); + y = (float) r.getY(); + width = (float) r.getWidth(); + height = (float) r.getHeight(); + } + + /** + * Determine where the point lies with respect to this rectangle. The + * result will be the binary OR of the appropriate bit masks. + * + * @param x the x coordinate to check + * @param y the y coordinate to check + * @return the binary OR of the result + * @see #OUT_LEFT + * @see #OUT_TOP + * @see #OUT_RIGHT + * @see #OUT_BOTTOM + * @since 1.2 + */ + public int outcode(double x, double y) + { + int result = 0; + if (width <= 0) + result |= OUT_LEFT | OUT_RIGHT; + else if (x < this.x) + result |= OUT_LEFT; + else if (x > this.x + width) + result |= OUT_RIGHT; + if (height <= 0) + result |= OUT_BOTTOM | OUT_TOP; + else if (y < this.y) // Remember that +y heads top-to-bottom. + result |= OUT_TOP; + else if (y > this.y + height) + result |= OUT_BOTTOM; + return result; + } + + /** + * Returns the bounds of this rectangle. A pretty useless method, as this + * is already a rectangle. + * + * @return a copy of this rectangle + */ + public Rectangle2D getBounds2D() + { + return new Float(x, y, width, height); + } + + /** + * Return a new rectangle which is the intersection of this and the given + * one. The result will be empty if there is no intersection. + * + * @param r the rectangle to be intersected + * @return the intersection + * @throws NullPointerException if r is null + */ + public Rectangle2D createIntersection(Rectangle2D r) + { + Float res = new Float(); + intersect(this, r, res); + return res; + } + + /** + * Return a new rectangle which is the union of this and the given one. + * + * @param r the rectangle to be merged + * @return the union + * @throws NullPointerException if r is null + */ + public Rectangle2D createUnion(Rectangle2D r) + { + Float res = new Float(); + union(this, r, res); + return res; + } + + /** + * Returns a string representation of this rectangle. This is in the form + * <code>getClass().getName() + "[x=" + x + ",y=" + y + ",w=" + width + * + ",h=" + height + ']'</code>. + * + * @return a string representation of this rectangle + */ + public String toString() + { + return getClass().getName() + "[x=" + x + ",y=" + y + ",w=" + width + + ",h=" + height + ']'; + } + } +} diff --git a/libjava/classpath/java/awt/geom/RectangularShape.java b/libjava/classpath/java/awt/geom/RectangularShape.java new file mode 100644 index 000000000..68bc451cc --- /dev/null +++ b/libjava/classpath/java/awt/geom/RectangularShape.java @@ -0,0 +1,382 @@ +/* RectangularShape.java -- a rectangular frame for several generic shapes + Copyright (C) 2000, 2002 Free Software Foundation + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.awt.geom; + +import java.awt.Rectangle; +import java.awt.Shape; + +/** + * This class provides a generic framework, and several helper methods, for + * subclasses which represent geometric objects inside a rectangular frame. + * This does not specify any geometry except for the bounding box. + * + * @author Tom Tromey (tromey@cygnus.com) + * @author Eric Blake (ebb9@email.byu.edu) + * @since 1.2 + * @see Arc2D + * @see Ellipse2D + * @see Rectangle2D + * @see RoundRectangle2D + * @status updated to 1.4 + */ +public abstract class RectangularShape implements Shape, Cloneable +{ + /** + * Default constructor. + */ + protected RectangularShape() + { + } + + /** + * Get the x coordinate of the upper-left corner of the framing rectangle. + * + * @return the x coordinate + */ + public abstract double getX(); + + /** + * Get the y coordinate of the upper-left corner of the framing rectangle. + * + * @return the y coordinate + */ + public abstract double getY(); + + /** + * Get the width of the framing rectangle. + * + * @return the width + */ + public abstract double getWidth(); + + /** + * Get the height of the framing rectangle. + * + * @return the height + */ + public abstract double getHeight(); + + /** + * Get the minimum x coordinate in the frame. This is misnamed, or else + * Sun has a bug, because the implementation returns getX() even when + * getWidth() is negative. + * + * @return the minimum x coordinate + */ + public double getMinX() + { + return getX(); + } + + /** + * Get the minimum y coordinate in the frame. This is misnamed, or else + * Sun has a bug, because the implementation returns getY() even when + * getHeight() is negative. + * + * @return the minimum y coordinate + */ + public double getMinY() + { + return getY(); + } + + /** + * Get the maximum x coordinate in the frame. This is misnamed, or else + * Sun has a bug, because the implementation returns getX()+getWidth() even + * when getWidth() is negative. + * + * @return the maximum x coordinate + */ + public double getMaxX() + { + return getX() + getWidth(); + } + + /** + * Get the maximum y coordinate in the frame. This is misnamed, or else + * Sun has a bug, because the implementation returns getY()+getHeight() even + * when getHeight() is negative. + * + * @return the maximum y coordinate + */ + public double getMaxY() + { + return getY() + getHeight(); + } + + /** + * Return the x coordinate of the center point of the framing rectangle. + * + * @return the central x coordinate + */ + public double getCenterX() + { + return getX() + getWidth() / 2; + } + + /** + * Return the y coordinate of the center point of the framing rectangle. + * + * @return the central y coordinate + */ + public double getCenterY() + { + return getY() + getHeight() / 2; + } + + /** + * Return the frame around this object. Note that this may be a looser + * bounding box than getBounds2D. + * + * @return the frame, in double precision + * @see #setFrame(double, double, double, double) + */ + public Rectangle2D getFrame() + { + return new Rectangle2D.Double(getX(), getY(), getWidth(), getHeight()); + } + + /** + * Test if the shape is empty, meaning that no points are inside it. + * + * @return true if the shape is empty + */ + public abstract boolean isEmpty(); + + /** + * Set the framing rectangle of this shape to the given coordinate and size. + * + * @param x the new x coordinate + * @param y the new y coordinate + * @param w the new width + * @param h the new height + * @see #getFrame() + */ + public abstract void setFrame(double x, double y, double w, double h); + + /** + * Set the framing rectangle of this shape to the given coordinate and size. + * + * @param p the new point + * @param d the new dimension + * @throws NullPointerException if p or d is null + * @see #getFrame() + */ + public void setFrame(Point2D p, Dimension2D d) + { + setFrame(p.getX(), p.getY(), d.getWidth(), d.getHeight()); + } + + /** + * Set the framing rectangle of this shape to the given rectangle. + * + * @param r the new framing rectangle + * @throws NullPointerException if r is null + * @see #getFrame() + */ + public void setFrame(Rectangle2D r) + { + setFrame(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + /** + * Set the framing rectangle of this shape using two points on a diagonal. + * The area will be positive. + * + * @param x1 the first x coordinate + * @param y1 the first y coordinate + * @param x2 the second x coordinate + * @param y2 the second y coordinate + */ + public void setFrameFromDiagonal(double x1, double y1, double x2, double y2) + { + if (x1 > x2) + { + double t = x2; + x2 = x1; + x1 = t; + } + if (y1 > y2) + { + double t = y2; + y2 = y1; + y1 = t; + } + setFrame(x1, y1, x2 - x1, y2 - y1); + } + + /** + * Set the framing rectangle of this shape using two points on a diagonal. + * The area will be positive. + * + * @param p1 the first point + * @param p2 the second point + * @throws NullPointerException if either point is null + */ + public void setFrameFromDiagonal(Point2D p1, Point2D p2) + { + setFrameFromDiagonal(p1.getX(), p1.getY(), p2.getX(), p2.getY()); + } + + /** + * Set the framing rectangle of this shape using the center of the frame, + * and one of the four corners. The area will be positive. + * + * @param centerX the x coordinate at the center + * @param centerY the y coordinate at the center + * @param cornerX the x coordinate at a corner + * @param cornerY the y coordinate at a corner + */ + public void setFrameFromCenter(double centerX, double centerY, + double cornerX, double cornerY) + { + double halfw = Math.abs(cornerX - centerX); + double halfh = Math.abs(cornerY - centerY); + setFrame(centerX - halfw, centerY - halfh, halfw + halfw, halfh + halfh); + } + + /** + * Set the framing rectangle of this shape using the center of the frame, + * and one of the four corners. The area will be positive. + * + * @param center the center point + * @param corner a corner point + * @throws NullPointerException if either point is null + */ + public void setFrameFromCenter(Point2D center, Point2D corner) + { + setFrameFromCenter(center.getX(), center.getY(), + corner.getX(), corner.getY()); + } + + /** + * Tests if a point is inside the boundary of the shape. + * + * @param p the point to test + * @return true if the point is inside the shape + * @throws NullPointerException if p is null + * @see #contains(double, double) + */ + public boolean contains(Point2D p) + { + return contains(p.getX(), p.getY()); + } + + /** + * Tests if a rectangle and this shape share common internal points. + * + * @param r the rectangle to test + * @return true if the rectangle intersects this shpae + * @throws NullPointerException if r is null + * @see #intersects(double, double, double, double) + */ + public boolean intersects(Rectangle2D r) + { + return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + /** + * Tests if the shape completely contains the given rectangle. + * + * @param r the rectangle to test + * @return true if r is contained in this shape + * @throws NullPointerException if r is null + * @see #contains(double, double, double, double) + */ + public boolean contains(Rectangle2D r) + { + return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + /** + * Returns a bounding box for this shape, in integer format. Notice that you + * may get a tighter bound with getBounds2D. + * + * @return a bounding box + */ + public Rectangle getBounds() + { + double x = getX(); + double y = getY(); + double maxx = Math.ceil(x + getWidth()); + double maxy = Math.ceil(y + getHeight()); + x = Math.floor(x); + y = Math.floor(y); + return new Rectangle((int) x, (int) y, (int) (maxx - x), (int) (maxy - y)); + } + + /** + * Return an iterator along the shape boundary. If the optional transform + * is provided, the iterator is transformed accordingly. The path is + * flattened until all segments differ from the curve by at most the value + * of the flatness parameter, within the limits of the default interpolation + * recursion limit of 1024 segments between actual points. Each call + * returns a new object, independent from others in use. The result is + * threadsafe if and only if the iterator returned by + * {@link #getPathIterator(AffineTransform)} is as well. + * + * @param at an optional transform to apply to the iterator + * @param flatness the desired flatness + * @return a new iterator over the boundary + * @throws IllegalArgumentException if flatness is invalid + * @since 1.2 + */ + public PathIterator getPathIterator(AffineTransform at, double flatness) + { + return new FlatteningPathIterator(getPathIterator(at), flatness); + } + + /** + * Create a new shape of the same run-time type with the same contents as + * this one. + * + * @return the clone + */ + public Object clone() + { + try + { + return super.clone(); + } + catch (CloneNotSupportedException e) + { + throw (Error) new InternalError().initCause(e); // Impossible + } + } +} // class RectangularShape diff --git a/libjava/classpath/java/awt/geom/RoundRectangle2D.java b/libjava/classpath/java/awt/geom/RoundRectangle2D.java new file mode 100644 index 000000000..19a7b4237 --- /dev/null +++ b/libjava/classpath/java/awt/geom/RoundRectangle2D.java @@ -0,0 +1,584 @@ +/* RoundRectangle2D.java -- represents a rectangle with rounded corners + Copyright (C) 2000, 2002, 2003, 2004, 2006, Free Software Foundation + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package java.awt.geom; + + + +/** This class implements a rectangle with rounded corners. + * @author Tom Tromey (tromey@cygnus.com) + * @date December 3, 2000 + */ +public abstract class RoundRectangle2D extends RectangularShape +{ + /** + * Return the arc height of this round rectangle. The arc height and width + * control the roundness of the corners of the rectangle. + * + * @return The arc height. + * + * @see #getArcWidth() + */ + public abstract double getArcHeight(); + + /** + * Return the arc width of this round rectangle. The arc width and height + * control the roundness of the corners of the rectangle. + * + * @return The arc width. + * + * @see #getArcHeight() + */ + public abstract double getArcWidth(); + + /** + * Set the values of this round rectangle. + * + * @param x The x coordinate + * @param y The y coordinate + * @param w The width + * @param h The height + * @param arcWidth The arc width + * @param arcHeight The arc height + */ + public abstract void setRoundRect(double x, double y, double w, double h, + double arcWidth, double arcHeight); + + /** + * Create a RoundRectangle2D. This is protected because this class + * is abstract and cannot be instantiated. + */ + protected RoundRectangle2D() + { + } + + /** + * Return true if this object contains the specified point. + * @param x The x coordinate + * @param y The y coordinate + */ + public boolean contains(double x, double y) + { + double mx = getX(); + double mw = getWidth(); + if (x < mx || x >= mx + mw) + return false; + double my = getY(); + double mh = getHeight(); + if (y < my || y >= my + mh) + return false; + + // Now check to see if the point is in range of an arc. + double dy = Math.min(Math.abs(my - y), Math.abs(my + mh - y)); + double dx = Math.min(Math.abs(mx - x), Math.abs(mx + mw - x)); + + // The arc dimensions are that of the corresponding ellipse + // thus a 90 degree segment is half of that. + double aw = getArcWidth() / 2.0; + double ah = getArcHeight() / 2.0; + if (dx > aw || dy > ah) + return true; + + // At this point DX represents the distance from the nearest edge + // of the rectangle. But we want to transform it to represent the + // scaled distance from the center of the ellipse that forms the + // arc. Hence this code: + dy = (ah - dy) / ah; + dx = (aw - dx) / aw; + + return dx * dx + dy * dy <= 1.0; + } + + /** + * Return true if this object contains the specified rectangle + * @param x The x coordinate + * @param y The y coordinate + * @param w The width + * @param h The height + */ + public boolean contains(double x, double y, double w, double h) + { + // We have to check all four points here (for ordinary rectangles + // we can just check opposing corners). + return (contains(x, y) && contains(x, y + h) && contains(x + w, y + h) + && contains(x + w, y)); + } + + /** + * Return a new path iterator which iterates over this rectangle. + * + * @param at An affine transform to apply to the object + */ + public PathIterator getPathIterator(final AffineTransform at) + { + double arcW = Math.min(getArcWidth(), getWidth()); + double arcH = Math.min(getArcHeight(), getHeight()); + + // check for special cases... + if (arcW <= 0 || arcH <= 0) + { + Rectangle2D r = new Rectangle2D.Double(getX(), getY(), getWidth(), + getHeight()); + return r.getPathIterator(at); + } + else if (arcW >= getWidth() && arcH >= getHeight()) + { + Ellipse2D e = new Ellipse2D.Double(getX(), getY(), getWidth(), + getHeight()); + return e.getPathIterator(at); + } + + // otherwise return the standard case... + return new PathIterator() + { + double x = getX(); + double y = getY(); + double w = getWidth(); + double h = getHeight(); + double arcW = Math.min(getArcWidth(), w); + double arcH = Math.min(getArcHeight(), h); + Arc2D.Double arc = new Arc2D.Double(); + PathIterator corner; + int step = -1; + + public int currentSegment(double[] coords) + { + if (corner != null) // steps 1, 3, 5 and 7 + { + int r = corner.currentSegment(coords); + if (r == SEG_MOVETO) + r = SEG_LINETO; + return r; + } + if (step == -1) + { + // move to the start position + coords[0] = x + w - arcW / 2; + coords[1] = y; + } + else if (step == 0) + { + // top line + coords[0] = x + arcW / 2; + coords[1] = y; + } + else if (step == 2) + { + // left line + coords[0] = x; + coords[1] = y + h - arcH / 2; + } + else if (step == 4) + { + // bottom line + coords[0] = x + w - arcW / 2; + coords[1] = y + h; + } + else if (step == 6) + { + // right line + coords[0] = x + w; + coords[1] = y + arcH / 2; + } + if (at != null) + at.transform(coords, 0, coords, 0, 1); + return step == -1 ? SEG_MOVETO : SEG_LINETO; + } + + public int currentSegment(float[] coords) { + if (corner != null) // steps 1, 3, 5 and 7 + { + int r = corner.currentSegment(coords); + if (r == SEG_MOVETO) + r = SEG_LINETO; + return r; + } + if (step == -1) + { + // move to the start position + coords[0] = (float) (x + w - arcW / 2); + coords[1] = (float) y; + } + else if (step == 0) + { + // top line + coords[0] = (float) (x + arcW / 2); + coords[1] = (float) y; + } + else if (step == 2) + { + // left line + coords[0] = (float) x; + coords[1] = (float) (y + h - arcH / 2); + } + else if (step == 4) + { + // bottom line + coords[0] = (float) (x + w - arcW / 2); + coords[1] = (float) (y + h); + } + else if (step == 6) + { + // right line + coords[0] = (float) (x + w); + coords[1] = (float) (y + arcH / 2); + } + if (at != null) + at.transform(coords, 0, coords, 0, 1); + return step == -1 ? SEG_MOVETO : SEG_LINETO; + } + + public int getWindingRule() { + return WIND_NON_ZERO; + } + + public boolean isDone() { + return step >= 8; + } + + public void next() + { + if (corner != null) + { + corner.next(); + if (corner.isDone()) + { + corner = null; + step++; + } + } + else + { + step++; + if (step == 1) + { + // create top left corner + arc.setArc(x, y, arcW, arcH, 90, 90, Arc2D.OPEN); + corner = arc.getPathIterator(at); + } + else if (step == 3) + { + // create bottom left corner + arc.setArc(x, y + h - arcH, arcW, arcH, 180, 90, + Arc2D.OPEN); + corner = arc.getPathIterator(at); + } + else if (step == 5) + { + // create bottom right corner + arc.setArc(x + w - arcW, y + h - arcH, arcW, arcH, 270, 90, + Arc2D.OPEN); + corner = arc.getPathIterator(at); + } + else if (step == 7) + { + // create top right corner + arc.setArc(x + w - arcW, y, arcW, arcH, 0, 90, Arc2D.OPEN); + corner = arc.getPathIterator(at); + } + } + } + }; + } + + /** + * Return true if the given rectangle intersects this shape. + * @param x The x coordinate + * @param y The y coordinate + * @param w The width + * @param h The height + */ + public boolean intersects(double x, double y, double w, double h) + { + // Check if any corner is within the rectangle + return (contains(x, y) || contains(x, y + h) || contains(x + w, y + h) + || contains(x + w, y)); + } + + /** + * Set the boundary of this round rectangle. + * @param x The x coordinate + * @param y The y coordinate + * @param w The width + * @param h The height + */ + public void setFrame(double x, double y, double w, double h) + { + // This is a bit lame. + setRoundRect(x, y, w, h, getArcWidth(), getArcHeight()); + } + + /** + * Set the values of this round rectangle to be the same as those + * of the argument. + * @param rr The round rectangle to copy + */ + public void setRoundRect(RoundRectangle2D rr) + { + setRoundRect(rr.getX(), rr.getY(), rr.getWidth(), rr.getHeight(), + rr.getArcWidth(), rr.getArcHeight()); + } + + /** + * A subclass of RoundRectangle which keeps its parameters as + * doubles. + */ + public static class Double extends RoundRectangle2D + { + /** The height of the corner arc. */ + public double archeight; + + /** The width of the corner arc. */ + public double arcwidth; + + /** The x coordinate of this object. */ + public double x; + + /** The y coordinate of this object. */ + public double y; + + /** The width of this object. */ + public double width; + + /** The height of this object. */ + public double height; + + /** + * Construct a new instance, with all parameters set to 0. + */ + public Double() + { + } + + /** + * Construct a new instance with the given arguments. + * @param x The x coordinate + * @param y The y coordinate + * @param w The width + * @param h The height + * @param arcWidth The arc width + * @param arcHeight The arc height + */ + public Double(double x, double y, double w, double h, double arcWidth, + double arcHeight) + { + this.x = x; + this.y = y; + this.width = w; + this.height = h; + this.arcwidth = arcWidth; + this.archeight = arcHeight; + } + + public double getArcHeight() + { + return archeight; + } + + public double getArcWidth() + { + return arcwidth; + } + + public Rectangle2D getBounds2D() + { + return new Rectangle2D.Double(x, y, width, height); + } + + public double getX() + { + return x; + } + + public double getY() + { + return y; + } + + public double getWidth() + { + return width; + } + + public double getHeight() + { + return height; + } + + public boolean isEmpty() + { + return width <= 0 || height <= 0; + } + + public void setRoundRect(double x, double y, double w, double h, + double arcWidth, double arcHeight) + { + this.x = x; + this.y = y; + this.width = w; + this.height = h; + this.arcwidth = arcWidth; + this.archeight = arcHeight; + } + } // class Double + + /** + * A subclass of RoundRectangle which keeps its parameters as + * floats. + */ + public static class Float extends RoundRectangle2D + { + /** The height of the corner arc. */ + public float archeight; + + /** The width of the corner arc. */ + public float arcwidth; + + /** The x coordinate of this object. */ + public float x; + + /** The y coordinate of this object. */ + public float y; + + /** The width of this object. */ + public float width; + + /** The height of this object. */ + public float height; + + /** + * Construct a new instance, with all parameters set to 0. + */ + public Float() + { + } + + /** + * Construct a new instance with the given arguments. + * @param x The x coordinate + * @param y The y coordinate + * @param w The width + * @param h The height + * @param arcWidth The arc width + * @param arcHeight The arc height + */ + public Float(float x, float y, float w, float h, float arcWidth, + float arcHeight) + { + this.x = x; + this.y = y; + this.width = w; + this.height = h; + this.arcwidth = arcWidth; + this.archeight = arcHeight; + } + + public double getArcHeight() + { + return archeight; + } + + public double getArcWidth() + { + return arcwidth; + } + + public Rectangle2D getBounds2D() + { + return new Rectangle2D.Float(x, y, width, height); + } + + public double getX() + { + return x; + } + + public double getY() + { + return y; + } + + public double getWidth() + { + return width; + } + + public double getHeight() + { + return height; + } + + public boolean isEmpty() + { + return width <= 0 || height <= 0; + } + + /** + * Sets the dimensions for this rounded rectangle. + * + * @param x the x-coordinate of the top left corner. + * @param y the y-coordinate of the top left corner. + * @param w the width of the rectangle. + * @param h the height of the rectangle. + * @param arcWidth the arc width. + * @param arcHeight the arc height. + * + * @see #setRoundRect(double, double, double, double, double, double) + */ + public void setRoundRect(float x, float y, float w, float h, + float arcWidth, float arcHeight) + { + this.x = x; + this.y = y; + this.width = w; + this.height = h; + this.arcwidth = arcWidth; + this.archeight = arcHeight; + } + + public void setRoundRect(double x, double y, double w, double h, + double arcWidth, double arcHeight) + { + this.x = (float) x; + this.y = (float) y; + this.width = (float) w; + this.height = (float) h; + this.arcwidth = (float) arcWidth; + this.archeight = (float) arcHeight; + } + } // class Float +} // class RoundRectangle2D diff --git a/libjava/classpath/java/awt/geom/doc-files/Area-1.png b/libjava/classpath/java/awt/geom/doc-files/Area-1.png Binary files differnew file mode 100644 index 000000000..44650f2d8 --- /dev/null +++ b/libjava/classpath/java/awt/geom/doc-files/Area-1.png diff --git a/libjava/classpath/java/awt/geom/doc-files/CubicCurve2D-1.png b/libjava/classpath/java/awt/geom/doc-files/CubicCurve2D-1.png Binary files differnew file mode 100644 index 000000000..1784509be --- /dev/null +++ b/libjava/classpath/java/awt/geom/doc-files/CubicCurve2D-1.png diff --git a/libjava/classpath/java/awt/geom/doc-files/CubicCurve2D-2.png b/libjava/classpath/java/awt/geom/doc-files/CubicCurve2D-2.png Binary files differnew file mode 100644 index 000000000..1ddae9fc8 --- /dev/null +++ b/libjava/classpath/java/awt/geom/doc-files/CubicCurve2D-2.png diff --git a/libjava/classpath/java/awt/geom/doc-files/CubicCurve2D-3.png b/libjava/classpath/java/awt/geom/doc-files/CubicCurve2D-3.png Binary files differnew file mode 100644 index 000000000..b200dad37 --- /dev/null +++ b/libjava/classpath/java/awt/geom/doc-files/CubicCurve2D-3.png diff --git a/libjava/classpath/java/awt/geom/doc-files/CubicCurve2D-4.png b/libjava/classpath/java/awt/geom/doc-files/CubicCurve2D-4.png Binary files differnew file mode 100644 index 000000000..e57ffdc5c --- /dev/null +++ b/libjava/classpath/java/awt/geom/doc-files/CubicCurve2D-4.png diff --git a/libjava/classpath/java/awt/geom/doc-files/CubicCurve2D-5.png b/libjava/classpath/java/awt/geom/doc-files/CubicCurve2D-5.png Binary files differnew file mode 100644 index 000000000..701ab138f --- /dev/null +++ b/libjava/classpath/java/awt/geom/doc-files/CubicCurve2D-5.png diff --git a/libjava/classpath/java/awt/geom/doc-files/Ellipse-1.png b/libjava/classpath/java/awt/geom/doc-files/Ellipse-1.png Binary files differnew file mode 100644 index 000000000..8317db661 --- /dev/null +++ b/libjava/classpath/java/awt/geom/doc-files/Ellipse-1.png diff --git a/libjava/classpath/java/awt/geom/doc-files/FlatteningPathIterator-1.html b/libjava/classpath/java/awt/geom/doc-files/FlatteningPathIterator-1.html new file mode 100644 index 000000000..5a52d693e --- /dev/null +++ b/libjava/classpath/java/awt/geom/doc-files/FlatteningPathIterator-1.html @@ -0,0 +1,481 @@ +<?xml version="1.0" encoding="US-ASCII"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head> + <title>The GNU Implementation of java.awt.geom.FlatteningPathIterator</title> + <meta name="author" content="Sascha Brawer" /> + <style type="text/css"><!-- + td { white-space: nowrap; } + li { margin: 2mm 0; } + --></style> +</head> +<body> + +<h1>The GNU Implementation of FlatteningPathIterator</h1> + +<p><i><a href="http://www.dandelis.ch/people/brawer/">Sascha +Brawer</a>, November 2003</i></p> + +<p>This document describes the GNU implementation of the class +<code>java.awt.geom.FlatteningPathIterator</code>. It does +<em>not</em> describe how a programmer should use this class; please +refer to the generated API documentation for this purpose. Instead, it +is intended for maintenance programmers who want to understand the +implementation, for example because they want to extend the class or +fix a bug.</p> + + +<h2>Data Structures</h2> + +<p>The algorithm uses a stack. Its allocation is delayed to the time +when the source path iterator actually returns the first curved +segment (either <code>SEG_QUADTO</code> or <code>SEG_CUBICTO</code>). +If the input path does not contain any curved segments, the value of +the <code>stack</code> variable stays <code>null</code>. In this quite +common case, the memory consumption is minimal.</p> + +<dl><dt><code>stack</code></dt><dd>The variable <code>stack</code> is +a <code>double</code> array that holds the start, control and end +points of individual sub-segments.</dd> + +<dt><code>recLevel</code></dt><dd>The variable <code>recLevel</code> +holds how many recursive sub-divisions were needed to calculate a +segment. The original curve has recursion level 0. For each +sub-division, the corresponding recursion level is increased by +one.</dd> + +<dt><code>stackSize</code></dt><dd>Finally, the variable +<code>stackSize</code> indicates how many sub-segments are stored on +the stack.</dd></dl> + +<h2>Algorithm</h2> + +<p>The implementation separately processes each segment that the +base iterator returns.</p> + +<p>In the case of <code>SEG_CLOSE</code>, +<code>SEG_MOVETO</code> and <code>SEG_LINETO</code> segments, the +implementation simply hands the segment to the consumer, without actually +doing anything.</p> + +<p>Any <code>SEG_QUADTO</code> and <code>SEG_CUBICTO</code> segments +need to be flattened. Flattening is performed with a fixed-sized +stack, holding the coordinates of subdivided segments. When the base +iterator returns a <code>SEG_QUADTO</code> and +<code>SEG_CUBICTO</code> segments, it is recursively flattened as +follows:</p> + +<ol><li>Intialization: Allocate memory for the stack (unless a +sufficiently large stack has been allocated previously). Push the +original quadratic or cubic curve onto the stack. Mark that segment as +having a <code>recLevel</code> of zero.</li> + +<li>If the stack is empty, flattening the segment is complete, +and the next segment is fetched from the base iterator.</li> + +<li>If the stack is not empty, pop a curve segment from the +stack. + + <ul><li>If its <code>recLevel</code> exceeds the recursion limit, + hand the current segment to the consumer.</li> + + <li>Calculate the squared flatness of the segment. If it smaller + than <code>flatnessSq</code>, hand the current segment to the + consumer.</li> + + <li>Otherwise, split the segment in two halves. Push the right + half onto the stack. Then, push the left half onto the stack. + Continue with step two.</li></ul></li> +</ol> + +<p>The implementation is slightly complicated by the fact that +consumers <em>pull</em> the flattened segments from the +<code>FlatteningPathIterator</code>. This means that we actually +cannot “hand the curent segment over to the consumer.” +But the algorithm is easier to understand if one assumes a +<em>push</em> paradigm.</p> + + +<h2>Example</h2> + +<p>The following example shows how a +<code>FlatteningPathIterator</code> processes a +<code>SEG_QUADTO</code> segment. It is (arbitrarily) assumed that the +recursion limit was set to 2.</p> + +<blockquote> +<table border="1" cellspacing="0" cellpadding="8"> + <tr align="center" valign="baseline"> + <th></th><th>A</th><th>B</th><th>C</th> + <th>D</th><th>E</th><th>F</th><th>G</th><th>H</th> + </tr> + <tr align="center" valign="baseline"> + <th><code>stack[0]</code></th> + <td>—</td> + <td>—</td> + <td><i>S<sub>ll</sub>.x</i></td> + <td>—</td> + <td>—</td> + <td>—</td> + <td>—</td> + <td>—</td> + </tr> + <tr align="center" valign="baseline"> + <th><code>stack[1]</code></th> + <td>—</td> + <td>—</td> + <td><i>S<sub>ll</sub>.y</i></td> + <td>—</td> + <td>—</td> + <td>—</td> + <td>—</td> + <td>—</td> + </tr> + <tr align="center" valign="baseline"> + <th><code>stack[2]</code></th> + <td>—</td> + <td>—</td> + <td><i>C<sub>ll</sub>.x</i></td> + <td>—</td> + <td>—</td> + <td>—</td> + <td>—</td> + <td>—</td> + </tr> + <tr align="center" valign="baseline"> + <th><code>stack[3]</code></th> + <td>—</td> + <td>—</td> + <td><i>C<sub>ll</sub>.y</i></td> + <td>—</td> + <td>—</td> + <td>—</td> + <td>—</td> + <td>—</td> + </tr> + <tr align="center" valign="baseline"> + <th><code>stack[4]</code></th> + <td>—</td> + <td><i>S<sub>l</sub>.x</i></td> + <td><i>E<sub>ll</sub>.x</i> + = <i>S<sub>lr</sub>.x</i></td> + <td><i>S<sub>lr</sub>.x</i></td> + <td>—</td> + <td><i>S<sub>rl</sub>.x</i></td> + <td>—</td> + <td>—</td> + </tr> + <tr align="center" valign="baseline"> + <th><code>stack[5]</code></th> + <td>—</td> + <td><i>S<sub>l</sub>.y</i></td> + <td><i>E<sub>ll</sub>.x</i> + = <i>S<sub>lr</sub>.y</i></td> + <td><i>S<sub>lr</sub>.y</i></td> + <td>—</td> + <td><i>S<sub>rl</sub>.y</i></td> + <td>—</td> + <td>—</td> + </tr> + <tr align="center" valign="baseline"> + <th><code>stack[6]</code></th> + <td>—</td> + <td><i>C<sub>l</sub>.x</i></td> + <td><i>C<sub>lr</sub>.x</i></td> + <td><i>C<sub>lr</sub>.x</i></td> + <td>—</td> + <td><i>C<sub>rl</sub>.x</i></td> + <td>—</td> + <td>—</td> + </tr> + <tr align="center" valign="baseline"> + <th><code>stack[7]</code></th> + <td>—</td> + <td><i>C<sub>l</sub>.y</i></td> + <td><i>C<sub>lr</sub>.y</i></td> + <td><i>C<sub>lr</sub>.y</i></td> + <td>—</td> + <td><i>C<sub>rl</sub>.y</i></td> + <td>—</td> + <td>—</td> + </tr> + <tr align="center" valign="baseline"> + <th><code>stack[8]</code></th> + <td><i>S.x</i></td> + <td><i>E<sub>l</sub>.x</i> + = <i>S<sub>r</sub>.x</i></td> + <td><i>E<sub>lr</sub>.x</i> + = <i>S<sub>r</sub>.x</i></td> + <td><i>E<sub>lr</sub>.x</i> + = <i>S<sub>r</sub>.x</i></td> + <td><i>S<sub>r</sub>.x</i></td> + <td><i>E<sub>rl</sub>.x</i> + = <i>S<sub>rr</sub>.x</i></td> + <td><i>S<sub>rr</sub>.x</i></td> + <td>—</td> + </tr> + <tr align="center" valign="baseline"> + <th><code>stack[9]</code></th> + <td><i>S.y</i></td> + <td><i>E<sub>l</sub>.y</i> + = <i>S<sub>r</sub>.y</i></td> + <td><i>E<sub>lr</sub>.y</i> + = <i>S<sub>r</sub>.y</i></td> + <td><i>E<sub>lr</sub>.y</i> + = <i>S<sub>r</sub>.y</i></td> + <td><i>S<sub>r</sub>.y</i></td> + <td><i>E<sub>rl</sub>.y</i> + = <i>S<sub>rr</sub>.y</i></td> + <td><i>S<sub>rr</sub>.y</i></td> + <td>—</td> + </tr> + <tr align="center" valign="baseline"> + <th><code>stack[10]</code></th> + <td><i>C.x</i></td> + <td><i>C<sub>r</sub>.x</i></td> + <td><i>C<sub>r</sub>.x</i></td> + <td><i>C<sub>r</sub>.x</i></td> + <td><i>C<sub>r</sub>.x</i></td> + <td><i>C<sub>rr</sub>.x</i></td> + <td><i>C<sub>rr</sub>.x</i></td> + <td>—</td> + </tr> + <tr align="center" valign="baseline"> + <th><code>stack[11]</code></th> + <td><i>C.y</i></td> + <td><i>C<sub>r</sub>.y</i></td> + <td><i>C<sub>r</sub>.y</i></td> + <td><i>C<sub>r</sub>.y</i></td> + <td><i>C<sub>r</sub>.y</i></td> + <td><i>C<sub>rr</sub>.y</i></td> + <td><i>C<sub>rr</sub>.y</i></td> + <td>—</td> + </tr> + <tr align="center" valign="baseline"> + <th><code>stack[12]</code></th> + <td><i>E.x</i></td> + <td><i>E<sub>r</sub>.x</i></td> + <td><i>E<sub>r</sub>.x</i></td> + <td><i>E<sub>r</sub>.x</i></td> + <td><i>E<sub>r</sub>.x</i></td> + <td><i>E<sub>rr</sub>.x</i></td> + <td><i>E<sub>rr</sub>.x</i></td> + <td>—</td> + </tr> + <tr align="center" valign="baseline"> + <th><code>stack[13]</code></th> + <td><i>E.y</i></td> + <td><i>E<sub>r</sub>.y</i></td> + <td><i>E<sub>r</sub>.y</i></td> + <td><i>E<sub>r</sub>.y</i></td> + <td><i>E<sub>r</sub>.y</i></td> + <td><i>E<sub>rr</sub>.y</i></td> + <td><i>E<sub>rr</sub>.x</i></td> + <td>—</td> + </tr> + <tr align="center" valign="baseline"> + <th><code>stackSize</code></th> + <td>1</td> + <td>2</td> + <td>3</td> + <td>2</td> + <td>1</td> + <td>2</td> + <td>1</td> + <td>0</td> + </tr> + <tr align="center" valign="baseline"> + <th><code>recLevel[2]</code></th> + <td>—</td> + <td>—</td> + <td>2</td> + <td>—</td> + <td>—</td> + <td>—</td> + <td>—</td> + <td>—</td> + </tr> + <tr align="center" valign="baseline"> + <th><code>recLevel[1]</code></th> + <td>—</td> + <td>1</td> + <td>2</td> + <td>2</td> + <td>—</td> + <td>2</td> + <td>—</td> + <td>—</td> + </tr> + <tr align="center" valign="baseline"> + <th><code>recLevel[0]</code></th> + <td>0</td> + <td>1</td> + <td>1</td> + <td>1</td> + <td>1</td> + <td>2</td> + <td>2</td> + <td>—</td> + </tr> + </table> +</blockquote> + +<ol> + +<li>The data structures are initialized as follows. + +<ul><li>The segment’s end point <i>E</i>, control point +<i>C</i>, and start point <i>S</i> are pushed onto the stack.</li> + + <li>Currently, the curve in the stack would be approximated by one + single straight line segment (<i>S</i> – <i>E</i>). + Therefore, <code>stackSize</code> is set to 1.</li> + + <li>This single straight line segment is approximating the original + curve, which can be seen as the result of zero recursive + splits. Therefore, <code>recLevel[0]</code> is set to + zero.</li></ul> + +Column A shows the state after the initialization step.</li> + +<li>The algorithm proceeds by taking the topmost curve segment +(<i>S</i> – <i>C</i> – <i>E</i>) from the stack. + + <ul><li>The recursion level of this segment (stored in + <code>recLevel[0]</code>) is zero, which is smaller than + the limit 2.</li> + + <li>The method <code>java.awt.geom.QuadCurve2D.getFlatnessSq</code> + is called to calculate the squared flatness.</li> + + <li>For the sake of argument, we assume that the squared flatness is + exceeding the threshold stored in <code>flatnessSq</code>. Thus, the + curve segment <i>S</i> – <i>C</i> – <i>E</i> gets + subdivided into a left and a right half, namely + <i>S<sub>l</sub></i> – <i>C<sub>l</sub></i> – + <i>E<sub>l</sub></i> and <i>S<sub>r</sub></i> – + <i>C<sub>r</sub></i> – <i>E<sub>r</sub></i>. Both halves are + pushed onto the stack, so the left half is now on top. + + <br /> <br />The left half starts at the same point + as the original curve, so <i>S<sub>l</sub></i> has the same + coordinates as <i>S</i>. Similarly, the end point of the right + half and of the original curve are identical + (<i>E<sub>r</sub></i> = <i>E</i>). More interestingly, the left + half ends where the right half starts. Because + <i>E<sub>l</sub></i> = <i>S<sub>r</sub></i>, their coordinates need + to be stored only once, which amounts to saving 16 bytes (two + <code>double</code> values) for each iteration.</li></ul> + +Column B shows the state after the first iteration.</li> + +<li>Again, the topmost curve segment (<i>S<sub>l</sub></i> +– <i>C<sub>l</sub></i> – <i>E<sub>l</sub></i>) is +taken from the stack. + + <ul><li>The recursion level of this segment (stored in + <code>recLevel[1]</code>) is 1, which is smaller than + the limit 2.</li> + + <li>The method <code>java.awt.geom.QuadCurve2D.getFlatnessSq</code> + is called to calculate the squared flatness.</li> + + <li>Assuming that the segment is still not considered + flat enough, it gets subdivided into a left + (<i>S<sub>ll</sub></i> – <i>C<sub>ll</sub></i> – + <i>E<sub>ll</sub></i>) and a right (<i>S<sub>lr</sub></i> + – <i>C<sub>lr</sub></i> – <i>E<sub>lr</sub></i>) + half.</li></ul> + +Column C shows the state after the second iteration.</li> + +<li>The topmost curve segment (<i>S<sub>ll</sub></i> – +<i>C<sub>ll</sub></i> – <i>E<sub>ll</sub></i>) is popped from +the stack. + + <ul><li>The recursion level of this segment (stored in + <code>recLevel[2]</code>) is 2, which is <em>not</em> smaller than + the limit 2. Therefore, a <code>SEG_LINETO</code> (from + <i>S<sub>ll</sub></i> to <i>E<sub>ll</sub></i>) is passed to the + consumer.</li></ul> + + The new state is shown in column D.</li> + + +<li>The topmost curve segment (<i>S<sub>lr</sub></i> – +<i>C<sub>lr</sub></i> – <i>E<sub>lr</sub></i>) is popped from +the stack. + + <ul><li>The recursion level of this segment (stored in + <code>recLevel[1]</code>) is 2, which is <em>not</em> smaller than + the limit 2. Therefore, a <code>SEG_LINETO</code> (from + <i>S<sub>lr</sub></i> to <i>E<sub>lr</sub></i>) is passed to the + consumer.</li></ul> + + The new state is shown in column E.</li> + +<li>The algorithm proceeds by taking the topmost curve segment +(<i>S<sub>r</sub></i> – <i>C<sub>r</sub></i> – +<i>E<sub>r</sub></i>) from the stack. + + <ul><li>The recursion level of this segment (stored in + <code>recLevel[0]</code>) is 1, which is smaller than + the limit 2.</li> + + <li>The method <code>java.awt.geom.QuadCurve2D.getFlatnessSq</code> + is called to calculate the squared flatness.</li> + + <li>For the sake of argument, we again assume that the squared + flatness is exceeding the threshold stored in + <code>flatnessSq</code>. Thus, the curve segment + (<i>S<sub>r</sub></i> – <i>C<sub>r</sub></i> – + <i>E<sub>r</sub></i>) is subdivided into a left and a right half, + namely + <i>S<sub>rl</sub></i> – <i>C<sub>rl</sub></i> – + <i>E<sub>rl</sub></i> and <i>S<sub>rr</sub></i> – + <i>C<sub>rr</sub></i> – <i>E<sub>rr</sub></i>. Both halves + are pushed onto the stack.</li></ul> + + The new state is shown in column F.</li> + +<li>The topmost curve segment (<i>S<sub>rl</sub></i> – +<i>C<sub>rl</sub></i> – <i>E<sub>rl</sub></i>) is popped from +the stack. + + <ul><li>The recursion level of this segment (stored in + <code>recLevel[2]</code>) is 2, which is <em>not</em> smaller than + the limit 2. Therefore, a <code>SEG_LINETO</code> (from + <i>S<sub>rl</sub></i> to <i>E<sub>rl</sub></i>) is passed to the + consumer.</li></ul> + + The new state is shown in column G.</li> + +<li>The topmost curve segment (<i>S<sub>rr</sub></i> – +<i>C<sub>rr</sub></i> – <i>E<sub>rr</sub></i>) is popped from +the stack. + + <ul><li>The recursion level of this segment (stored in + <code>recLevel[2]</code>) is 2, which is <em>not</em> smaller than + the limit 2. Therefore, a <code>SEG_LINETO</code> (from + <i>S<sub>rr</sub></i> to <i>E<sub>rr</sub></i>) is passed to the + consumer.</li></ul> + + The new state is shown in column H.</li> + +<li>The stack is now empty. The FlatteningPathIterator will fetch the +next segment from the base iterator, and process it.</li> + +</ol> + +<p>In order to split the most recently pushed segment, the +<code>subdivideQuadratic()</code> method passes <code>stack</code> +directly to +<code>QuadCurve2D.subdivide(double[],int,double[],int,double[],int)</code>. +Because the stack grows towards the beginning of the array, no data +needs to be copied around: <code>subdivide</code> will directly store +the result into the stack, which will have the contents shown to the +right.</p> + +</body> +</html> diff --git a/libjava/classpath/java/awt/geom/doc-files/GeneralPath-1.png b/libjava/classpath/java/awt/geom/doc-files/GeneralPath-1.png Binary files differnew file mode 100644 index 000000000..d1d75d575 --- /dev/null +++ b/libjava/classpath/java/awt/geom/doc-files/GeneralPath-1.png diff --git a/libjava/classpath/java/awt/geom/doc-files/QuadCurve2D-1.png b/libjava/classpath/java/awt/geom/doc-files/QuadCurve2D-1.png Binary files differnew file mode 100644 index 000000000..7c2ec0ea9 --- /dev/null +++ b/libjava/classpath/java/awt/geom/doc-files/QuadCurve2D-1.png diff --git a/libjava/classpath/java/awt/geom/doc-files/QuadCurve2D-2.png b/libjava/classpath/java/awt/geom/doc-files/QuadCurve2D-2.png Binary files differnew file mode 100644 index 000000000..496180c44 --- /dev/null +++ b/libjava/classpath/java/awt/geom/doc-files/QuadCurve2D-2.png diff --git a/libjava/classpath/java/awt/geom/doc-files/QuadCurve2D-3.png b/libjava/classpath/java/awt/geom/doc-files/QuadCurve2D-3.png Binary files differnew file mode 100644 index 000000000..a7557ba7b --- /dev/null +++ b/libjava/classpath/java/awt/geom/doc-files/QuadCurve2D-3.png diff --git a/libjava/classpath/java/awt/geom/doc-files/QuadCurve2D-4.png b/libjava/classpath/java/awt/geom/doc-files/QuadCurve2D-4.png Binary files differnew file mode 100644 index 000000000..835c0643b --- /dev/null +++ b/libjava/classpath/java/awt/geom/doc-files/QuadCurve2D-4.png diff --git a/libjava/classpath/java/awt/geom/doc-files/QuadCurve2D-5.png b/libjava/classpath/java/awt/geom/doc-files/QuadCurve2D-5.png Binary files differnew file mode 100644 index 000000000..72110cd5a --- /dev/null +++ b/libjava/classpath/java/awt/geom/doc-files/QuadCurve2D-5.png diff --git a/libjava/classpath/java/awt/geom/package.html b/libjava/classpath/java/awt/geom/package.html new file mode 100644 index 000000000..c8ee8272f --- /dev/null +++ b/libjava/classpath/java/awt/geom/package.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> +<!-- package.html - describes classes in java.awt.geom package. + Copyright (C) 2002 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. --> + +<html> +<head><title>GNU Classpath - java.awt.geom</title></head> + +<body> +<p>Classes to represent 2D objects and different path transformations.</p> + +</body> +</html> |