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);
}
}