/* * @(#)Area.java 1.21 06/02/24 * * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */ package sec.sun.awt.geom; import armyc2.c2sd.graphics2d.*; import sec.geo.ShapeObject; /** * An <code>Area</code> object stores and manipulates a resolution-independent * description of an enclosed area of 2-dimensional space. <code>Area</code> * objects can be transformed and can perform various Constructive Area Geometry * (CAG) operations when combined with other <code>Area</code> objects. The CAG * operations include area {@link #add addition}, {@link #subtract subtraction}, * {@link #intersect intersection}, and {@link #exclusiveOr exclusive or}. See * the linked method documentation for examples of the various operations. * <p> * The <code>Area</code> class implements the <code>Shape</code> interface and * provides full support for all of its hit-testing and path iteration * facilities, but an <code>Area</code> is more specific than a generalized path * in a number of ways: * <ul> * <li>Only closed paths and sub-paths are stored. <code>Area</code> objects * constructed from unclosed paths are implicitly closed during construction as * if those paths had been filled by the <code>Graphics2D.fill</code> method. * <li>The interiors of the individual stored sub-paths are all non-empty and * non-overlapping. Paths are decomposed during construction into separate * component non-overlapping parts, empty pieces of the path are discarded, and * then these non-empty and non-overlapping properties are maintained through * all subsequent CAG operations. Outlines of different component sub-paths may * touch each other, as long as they do not cross so that their enclosed areas * overlap. * <li>The geometry of the path describing the outline of the <code>Area</code> * resembles the path from which it was constructed only in that it describes * the same enclosed 2-dimensional area, but may use entirely different types * and ordering of the path segments to do so. * </ul> * Interesting issues which are not always obvious when using the * <code>Area</code> include: * <ul> * <li>Creating an <code>Area</code> from an unclosed (open) <code>Shape</code> * results in a closed outline in the <code>Area</code> object. * <li>Creating an <code>Area</code> from a <code>Shape</code> which encloses no * area (even when "closed") produces an empty <code>Area</code>. A common * example of this issue is that producing an <code>Area</code> from a line will * be empty since the line encloses no area. An empty <code>Area</code> will * iterate no geometry in its <code>PathIterator</code> objects. * <li>A self-intersecting <code>Shape</code> may be split into two (or more) * sub-paths each enclosing one of the non-intersecting portions of the original * path. * <li>An <code>Area</code> may take more path segments to describe the same * geometry even when the original outline is simple and obvious. The analysis * that the <code>Area</code> class must perform on the path may not reflect the * same concepts of "simple and obvious" as a human being perceives. * </ul> * * @since 1.2 */ public class Area /* implements Shape, Cloneable */ { private static Vector EmptyCurves = new Vector(); private static final boolean normalizeGeoPoints = true; private Vector curves; /** * Default constructor which creates an empty area. * * @since 1.2 */ public Area() { curves = EmptyCurves; } /** * The <code>Area</code> class creates an area geometry from the specified * {@link Shape} object. The geometry is explicitly closed, if the * <code>Shape</code> is not already closed. The fill rule (even-odd or * winding) specified by the geometry of the <code>Shape</code> is used to * determine the resulting enclosed area. * * @param s the <code>Shape</code> from which the area is constructed * @throws NullPointerException if <code>s</code> is null * @since 1.2 */ public Area(ShapeObject s) { // if (s instanceof Area) { // curves = ((Area) s).curves; // } // else // { curves = pathToCurves(s.getPathIterator(null)); // } } private static Vector pathToCurves(PathIterator pi) { Vector curves = new Vector(); int windingRule = pi.getWindingRule(); // coords array is big enough for holding: // coordinates returned from currentSegment (6) // OR // two subdivided quadratic curves (2+4+4=10) // AND // 0-1 horizontal splitting parameters // OR // 2 parametric equation derivative coefficients // OR // three subdivided cubic curves (2+6+6+6=20) // AND // 0-2 horizontal splitting parameters // OR // 3 parametric equation derivative coefficients double coords[] = new double[23]; double movx = 0, movy = 0; double curx = 0, cury = 0; double newx, newy; while (!pi.isDone()) { switch (pi.currentSegment(coords)) { case PathIterator.SEG_MOVETO: if (normalizeGeoPoints == true) { if (movx > 0) { movx -= 360; } if (curx > 0) { curx -= 360; } } Curve.insertLine(curves, curx, cury, movx, movy); curx = movx = coords[0]; cury = movy = coords[1]; if (normalizeGeoPoints == true) { if (movx > 0) { movx -= 360; } } Curve.insertMove(curves, movx, movy); break; case PathIterator.SEG_LINETO: newx = coords[0]; newy = coords[1]; if (normalizeGeoPoints == true) { if (newx > 0) { newx -= 360; } if (curx > 0) { curx -= 360; } } Curve.insertLine(curves, curx, cury, newx, newy); curx = newx; cury = newy; break; case PathIterator.SEG_QUADTO: newx = coords[2]; newy = coords[3]; if (normalizeGeoPoints == true) { if (curx > 0) { curx -= 360; } } Curve.insertQuad(curves, curx, cury, coords); curx = newx; cury = newy; break; case PathIterator.SEG_CUBICTO: newx = coords[4]; newy = coords[5]; if (normalizeGeoPoints == true) { if (curx > 0) { curx -= 360; } } Curve.insertCubic(curves, curx, cury, coords); curx = newx; cury = newy; break; case PathIterator.SEG_CLOSE: if (normalizeGeoPoints == true) { if (movx > 0) { movx -= 360; } if (curx > 0) { curx -= 360; } } Curve.insertLine(curves, curx, cury, movx, movy); curx = movx; cury = movy; break; } pi.next(); } if (normalizeGeoPoints == true) { if (movx > 0) { movx -= 360; } if (curx > 0) { curx -= 360; } } Curve.insertLine(curves, curx, cury, movx, movy); AreaOp2 operator2 = null; if (windingRule == PathIterator.WIND_EVEN_ODD) { operator2 = new AreaOp2(AreaOp2.EOWINDOP); } else { operator2 = new AreaOp2(AreaOp2.NZWINDOP); } return operator2.calculate(curves, EmptyCurves); } /** * Adds the shape of the specified <code>Area</code> to the shape of this * <code>Area</code>. The resulting shape of this <code>Area</code> will * include the union of both shapes, or all areas that were contained in * either this or the specified <code>Area</code>. * <pre> * // Example: * Area a1 = new Area([triangle 0,0 => 8,0 => 0,8]); * Area a2 = new Area([triangle 0,0 => 8,0 => 8,8]); * a1.add(a2); * * a1(before) + a2 = a1(after) * * ################ ################ ################ * ############## ############## ################ * ############ ############ ################ * ########## ########## ################ * ######## ######## ################ * ###### ###### ###### ###### * #### #### #### #### * ## ## ## ## * </pre> * * @param rhs the <code>Area</code> to be added to the current shape * @throws NullPointerException if <code>rhs</code> is null * @since 1.2 */ public void add(Area rhs) { //curves = new AreaOp.AddOp().calculate(this.curves, rhs.curves); //curves = new AddOp().calculate(this.curves, rhs.curves); curves = new SomeOp(SomeOp.ADDOP).calculate(this.curves, rhs.curves); invalidateBounds(); } /** * Subtracts the shape of the specified <code>Area</code> from the shape of * this <code>Area</code>. The resulting shape of this <code>Area</code> * will include areas that were contained only in this <code>Area</code> and * not in the specified <code>Area</code>. * <pre> * // Example: * Area a1 = new Area([triangle 0,0 => 8,0 => 0,8]); * Area a2 = new Area([triangle 0,0 => 8,0 => 8,8]); * a1.subtract(a2); * * a1(before) - a2 = a1(after) * * ################ ################ * ############## ############## ## * ############ ############ #### * ########## ########## ###### * ######## ######## ######## * ###### ###### ###### * #### #### #### * ## ## ## * </pre> * * @param rhs the <code>Area</code> to be subtracted from the current shape * @throws NullPointerException if <code>rhs</code> is null * @since 1.2 */ public void subtract(Area rhs) { //curves = new AreaOp.SubOp().calculate(this.curves, rhs.curves); curves = new SomeOp(SomeOp.SUBOP).calculate(this.curves, rhs.curves); invalidateBounds(); } /** * Sets the shape of this <code>Area</code> to the intersection of its * current shape and the shape of the specified <code>Area</code>. The * resulting shape of this <code>Area</code> will include only areas that * were contained in both this <code>Area</code> and also in the specified * <code>Area</code>. * <pre> * // Example: * Area a1 = new Area([triangle 0,0 => 8,0 => 0,8]); * Area a2 = new Area([triangle 0,0 => 8,0 => 8,8]); * a1.intersect(a2); * * a1(before) intersect a2 = a1(after) * * ################ ################ ################ * ############## ############## ############ * ############ ############ ######## * ########## ########## #### * ######## ######## * ###### ###### * #### #### * ## ## * </pre> * * @param rhs the <code>Area</code> to be intersected with this * <code>Area</code> * @throws NullPointerException if <code>rhs</code> is null * @since 1.2 */ public void intersect(Area rhs) { //curves = new AreaOp.IntOp().calculate(this.curves, rhs.curves); curves = new SomeOp(SomeOp.INTOP).calculate(this.curves, rhs.curves); invalidateBounds(); } /** * Sets the shape of this <code>Area</code> to be the combined area of its * current shape and the shape of the specified <code>Area</code>, minus * their intersection. The resulting shape of this <code>Area</code> will * include only areas that were contained in either this <code>Area</code> * or in the specified <code>Area</code>, but not in both. * <pre> * // Example: * Area a1 = new Area([triangle 0,0 => 8,0 => 0,8]); * Area a2 = new Area([triangle 0,0 => 8,0 => 8,8]); * a1.exclusiveOr(a2); * * a1(before) xor a2 = a1(after) * * ################ ################ * ############## ############## ## ## * ############ ############ #### #### * ########## ########## ###### ###### * ######## ######## ################ * ###### ###### ###### ###### * #### #### #### #### * ## ## ## ## * </pre> * * @param rhs the <code>Area</code> to be exclusive ORed with this * <code>Area</code>. * @throws NullPointerException if <code>rhs</code> is null * @since 1.2 */ public void exclusiveOr(Area rhs) { //curves = new AreaOp.XorOp().calculate(this.curves, rhs.curves); curves = new SomeOp(SomeOp.XOROP).calculate(this.curves, rhs.curves); invalidateBounds(); } /** * Removes all of the geometry from this <code>Area</code> and restores it * to an empty area. * * @since 1.2 */ public void reset() { curves = new Vector(); invalidateBounds(); } /** * Tests whether this <code>Area</code> object encloses any area. * * @return <code>true</code> if this <code>Area</code> object represents an * empty area; <code>false</code> otherwise. * @since 1.2 */ public boolean isEmpty() { //return (curves.size() == 0); return (curves.isEmpty()); } /** * Tests whether this <code>Area</code> consists entirely of straight edged * polygonal geometry. * * @return <code>true</code> if the geometry of this <code>Area</code> * consists entirely of line segments; <code>false</code> otherwise. * @since 1.2 */ public boolean isPolygonal() { Enumeration enum_ = curves.elements(); while (enum_.hasMoreElements()) { if (((CurveObject) enum_.nextElement()).getOrder() > 1) { return false; } } return true; } /** * Tests whether this <code>Area</code> is rectangular in shape. * * @return <code>true</code> if the geometry of this <code>Area</code> is * rectangular in shape; <code>false</code> otherwise. * @since 1.2 */ public boolean isRectangular() { int size = curves.size(); if (size == 0) { return true; } if (size > 3) { return false; } CurveObject c1 = (CurveObject) curves.get(1); CurveObject c2 = (CurveObject) curves.get(2); if (c1.getOrder() != 1 || c2.getOrder() != 1) { return false; } if (c1.getXTop() != c1.getXBot() || c2.getXTop() != c2.getXBot()) { return false; } if (c1.getYTop() != c2.getYTop() || c1.getYBot() != c2.getYBot()) { // One might be able to prove that this is impossible... return false; } return true; } /** * Tests whether this <code>Area</code> is comprised of a single closed * subpath. This method returns <code>true</code> if the path contains 0 or * 1 subpaths, or <code>false</code> if the path contains more than 1 * subpath. The subpaths are counted by the number of * {@link PathIterator#SEG_MOVETO SEG_MOVETO} segments that appear in the * path. * * @return <code>true</code> if the <code>Area</code> is comprised of a * single basic geometry; <code>false</code> otherwise. * @since 1.2 */ public boolean isSingular() { if (curves.size() < 3) { return true; } Enumeration enum_ = curves.elements(); enum_.nextElement(); // First Order0 "moveto" while (enum_.hasMoreElements()) { if (((CurveObject) enum_.nextElement()).getOrder() == 0) { return false; } } return true; } private Rectangle2D cachedBounds; private void invalidateBounds() { cachedBounds = null; } /** * Tests whether the geometries of the two <code>Area</code> objects are * equal. This method will return false if the argument is null. * * @param other the <code>Area</code> to be compared to this * <code>Area</code> * @return <code>true</code> if the two geometries are equal; * <code>false</code> otherwise. * @since 1.2 */ public boolean equals(Area other) { // REMIND: A *much* simpler operation should be possible... // Should be able to do a curve-wise comparison since all Areas // should evaluate their curves in the same top-down order. if (other == this) { return true; } if (other == null) { return false; } //Vector c = new AreaOp.XorOp().calculate(this.curves, other.curves); Vector c = new SomeOp(SomeOp.XOROP).calculate(this.curves, other.curves); return c.isEmpty(); } /** * Creates a {@link PathIterator} for the outline of this <code>Area</code> * object. This <code>Area</code> object is unchanged. * * @param at an optional <code>AffineTransform</code> to be applied to the * coordinates as they are returned in the iteration, or <code>null</code> * if untransformed coordinates are desired * @return the <code>PathIterator</code> object that returns the geometry of * the outline of this <code>Area</code>, one segment at a time. * @since 1.2 */ public AreaIterator getPathIterator(AffineTransform at) { //did return PathIterator return new AreaIterator(curves, at); } }