package icy.type.geom; import java.awt.Point; import java.awt.Rectangle; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Line2D; import java.awt.geom.Path2D; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; /* * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Modified by Stephane Dallongeville */ /** * This class has the same behavior than {@link Polygon2D}, except that * the figure is not closed. */ public class Polyline2D implements Shape, Cloneable { private static final double ASSUME_ZERO = 0.00001d; /** * The total number of points. The value of <code>npoints</code> represents the number of points in this * <code>Polyline2D</code>. */ public int npoints; /** * The array of <i>x</i> coordinates. The value of {@link #npoints} is equal to the * number of points in this <code>Polyline2D</code>. */ public double[] xpoints; /** * The array of <i>y</i> coordinates. The value of {@link #npoints} is equal to the * number of points in this <code>Polyline2D</code>. */ public double[] ypoints; /** * Bounds of the Polyline2D. * * @see #getBounds() */ protected Rectangle2D bounds; protected Path2D.Double path; /** * Creates an empty Polyline2D. */ public Polyline2D() { super(); reset(); } /** * Constructs and initializes a <code>Polyline2D</code> from the specified * parameters. * * @param xpoints * an array of <i>x</i> coordinates * @param ypoints * an array of <i>y</i> coordinates * @param npoints * the total number of points in the <code>Polyline2D</code> * @exception NegativeArraySizeException * if the value of <code>npoints</code> is negative. * @exception IndexOutOfBoundsException * if <code>npoints</code> is * greater than the length of <code>xpoints</code> or the length of <code>ypoints</code>. * @exception NullPointerException * if <code>xpoints</code> or <code>ypoints</code> is <code>null</code>. */ public Polyline2D(double[] xpoints, double[] ypoints, int npoints) { super(); if (npoints > xpoints.length || npoints > ypoints.length) throw new IndexOutOfBoundsException("npoints > xpoints.length || npoints > ypoints.length"); this.npoints = npoints; this.xpoints = new double[npoints]; this.ypoints = new double[npoints]; System.arraycopy(xpoints, 0, this.xpoints, 0, npoints); System.arraycopy(ypoints, 0, this.ypoints, 0, npoints); calculatePath(); } /** * Constructs and initializes a <code>Polyline2D</code> from the specified * parameters. * * @param xpoints * an array of <i>x</i> coordinates * @param ypoints * an array of <i>y</i> coordinates * @param npoints * the total number of points in the <code>Polyline2D</code> * @exception NegativeArraySizeException * if the value of <code>npoints</code> is negative. * @exception IndexOutOfBoundsException * if <code>npoints</code> is * greater than the length of <code>xpoints</code> or the length of <code>ypoints</code>. * @exception NullPointerException * if <code>xpoints</code> or <code>ypoints</code> is <code>null</code>. */ public Polyline2D(int[] xpoints, int[] ypoints, int npoints) { super(); if (npoints > xpoints.length || npoints > ypoints.length) throw new IndexOutOfBoundsException("npoints > xpoints.length || npoints > ypoints.length"); this.npoints = npoints; this.xpoints = new double[npoints]; this.ypoints = new double[npoints]; for (int i = 0; i < npoints; i++) { this.xpoints[i] = xpoints[i]; this.ypoints[i] = ypoints[i]; } calculatePath(); } public Polyline2D(Line2D line) { super(); npoints = 2; xpoints = new double[2]; ypoints = new double[2]; xpoints[0] = line.getX1(); xpoints[1] = line.getX2(); ypoints[0] = line.getY1(); ypoints[1] = line.getY2(); calculatePath(); } /** * Resets this <code>Polyline2D</code> object to an empty polygon. * The coordinate arrays and the data in them are left untouched * but the number of points is reset to zero to mark the old * vertex data as invalid and to start accumulating new vertex * data at the beginning. * All internally-cached data relating to the old vertices * are discarded. * Note that since the coordinate arrays from before the reset * are reused, creating a new empty <code>Polyline2D</code> might * be more memory efficient than resetting the current one if * the number of vertices in the new polyline data is significantly * smaller than the number of vertices in the data from before the * reset. */ public void reset() { npoints = 0; xpoints = new double[0]; ypoints = new double[0]; bounds = new Rectangle2D.Double(); path = null; } @Override public Object clone() { Polyline2D pol = new Polyline2D(); for (int i = 0; i < npoints; i++) pol.addPoint(xpoints[i], ypoints[i]); return pol; } public void calculatePath() { path = new Path2D.Double(); path.moveTo(xpoints[0], ypoints[0]); for (int i = 1; i < npoints; i++) path.lineTo(xpoints[i], ypoints[i]); bounds = path.getBounds2D(); } protected void updatePath(double x, double y) { if (path == null) { path = new Path2D.Double(Path2D.WIND_EVEN_ODD); path.moveTo(x, y); bounds = new Rectangle2D.Double(x, y, 0, 0); } else { path.lineTo(x, y); double _xmax = bounds.getMaxX(); double _ymax = bounds.getMaxY(); double _xmin = bounds.getMinX(); double _ymin = bounds.getMinY(); if (x < _xmin) _xmin = x; else if (x > _xmax) _xmax = x; if (y < _ymin) _ymin = y; else if (y > _ymax) _ymax = y; bounds = new Rectangle2D.Double(_xmin, _ymin, _xmax - _xmin, _ymax - _ymin); } } public void addPoint(Point2D p) { addPoint(p.getX(), p.getY()); } /** * Appends the specified coordinates to this <code>Polyline2D</code>. * <p> * If an operation that calculates the bounding box of this <code>Polyline2D</code> has already been performed, such * as <code>getBounds</code> or <code>contains</code>, then this method updates the bounding box. * * @param x * the specified x coordinate * @param y * the specified y coordinate * @see java.awt.Polygon#getBounds * @see java.awt.Polygon#contains(double,double) */ public void addPoint(double x, double y) { if (npoints == xpoints.length) { double[] tmp; tmp = new double[(npoints * 2) + 1]; System.arraycopy(xpoints, 0, tmp, 0, npoints); xpoints = tmp; tmp = new double[(npoints * 2) + 1]; System.arraycopy(ypoints, 0, tmp, 0, npoints); ypoints = tmp; } xpoints[npoints] = x; ypoints[npoints] = y; npoints++; updatePath(x, y); } /** * Returns the high precision bounding box of the {@link Shape}. * * @return a {@link Rectangle2D} that precisely * bounds the <code>Shape</code>. */ @Override public Rectangle2D getBounds2D() { return (Rectangle2D) bounds.clone(); } /** * Gets the bounding box of this <code>Polyline2D</code>. * The bounding box is the smallest {@link Rectangle} whose * sides are parallel to the x and y axes of the * coordinate space, and can completely contain the <code>Polyline2D</code>. * * @return a <code>Rectangle</code> that defines the bounds of this <code>Polyline2D</code>. */ @Override public Rectangle getBounds() { if (bounds == null) return new Rectangle(); return bounds.getBounds(); } /** * Determines whether the specified {@link Point} is inside this <code>Polyline2D</code>. * This method is required to implement the Shape interface, * but in the case of Line2D objects it always returns false since a line contains no area. */ public boolean contains(Point p) { return false; } /** * Determines if the specified coordinates are inside this <code>Polyline2D</code>. * This method is required to implement the Shape interface, * but in the case of Line2D objects it always returns false since a line contains no area. */ @Override public boolean contains(double x, double y) { return false; } /** * Determines whether the specified coordinates are inside this <code>Polyline2D</code>. * This method is required to implement the Shape interface, * but in the case of Line2D objects it always returns false since a line contains no area. */ public boolean contains(int x, int y) { return false; } /** * Tests if a specified {@link Point2D} is inside the boundary of this <code>Polyline2D</code>. * This method is required to implement the Shape interface, * but in the case of Line2D objects it always returns false since a line contains no area. */ @Override public boolean contains(Point2D p) { return false; } /** * Tests if the interior of this <code>Polygon</code> intersects the * interior of a specified set of rectangular coordinates. * * @param x * the x coordinate of the specified rectangular * shape's top-left corner * @param y * the y coordinate of the specified rectangular * shape's top-left corner * @param w * the width of the specified rectangular shape * @param h * the height of the specified rectangular shape * @return <code>true</code> if the interior of this <code>Polygon</code> and the interior of the * specified set of rectangular * coordinates intersect each other; <code>false</code> otherwise. */ @Override public boolean intersects(double x, double y, double w, double h) { if ((path == null) || !bounds.intersects(x, y, w, h)) return false; return path.intersects(x, y, w, h); } /** * Tests if the interior of this <code>Polygon</code> intersects the * interior of a specified <code>Rectangle2D</code>. * * @param r * a specified <code>Rectangle2D</code> * @return <code>true</code> if this <code>Polygon</code> and the * interior of the specified <code>Rectangle2D</code> intersect each other; <code>false</code> otherwise. */ @Override public boolean intersects(Rectangle2D r) { return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight()); } /** * Tests if the interior of this <code>Polyline2D</code> entirely * contains the specified set of rectangular coordinates. * This method is required to implement the Shape interface, * but in the case of Line2D objects it always returns false since a line contains no area. */ @Override public boolean contains(double x, double y, double w, double h) { return false; } /** * Tests if the interior of this <code>Polyline2D</code> entirely * contains the specified <code>Rectangle2D</code>. * This method is required to implement the Shape interface, * but in the case of Line2D objects it always returns false since a line contains no area. */ @Override public boolean contains(Rectangle2D r) { return false; } /* * get the associated {@link Polygon2D}. * This method take care that may be the last point can * be equal to the first. In that case it must not be included in the Polygon, * as polygons declare their first point only once. */ public Polygon2D getPolygon2D() { Polygon2D pol = new Polygon2D(); for (int i = 0; i < npoints - 1; i++) pol.addPoint(xpoints[i], ypoints[i]); Point2D.Double p0 = new Point2D.Double(xpoints[0], ypoints[0]); Point2D.Double p1 = new Point2D.Double(xpoints[npoints - 1], ypoints[npoints - 1]); if (p0.distance(p1) > ASSUME_ZERO) pol.addPoint(xpoints[npoints - 1], ypoints[npoints - 1]); return pol; } /** * Returns an iterator object that iterates along the boundary of this <code>Polygon</code> and provides access to * the geometry * of the outline of this <code>Polygon</code>. An optional {@link AffineTransform} can be specified so that the * coordinates * returned in the iteration are transformed accordingly. * * @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 a {@link PathIterator} object that provides access to the * geometry of this <code>Polygon</code>. */ @Override public PathIterator getPathIterator(AffineTransform at) { if (path == null) return new Path2D.Double().getPathIterator(at); return path.getPathIterator(at); } /** * Returns an iterator object that iterates along the boundary of * the <code>Shape</code> and provides access to the geometry of the * outline of the <code>Shape</code>. Only SEG_MOVETO and SEG_LINETO, point types * are returned by the iterator. * Since polylines are already flat, the <code>flatness</code> parameter * is ignored. * * @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 * @param flatness * the maximum amount that the control points * for a given curve can vary from colinear before a subdivided * curve is replaced by a straight line connecting the * endpoints. Since polygons are already flat the <code>flatness</code> parameter is ignored. * @return a <code>PathIterator</code> object that provides access to the <code>Shape</code> object's geometry. */ @Override public PathIterator getPathIterator(AffineTransform at, double flatness) { return getPathIterator(at); } }