001package icy.type.geom;
002
003import java.awt.Point;
004import java.awt.Rectangle;
005import java.awt.Shape;
006import java.awt.geom.AffineTransform;
007import java.awt.geom.Line2D;
008import java.awt.geom.Path2D;
009import java.awt.geom.PathIterator;
010import java.awt.geom.Point2D;
011import java.awt.geom.Rectangle2D;
012
013/*
014 * 
015 * Licensed to the Apache Software Foundation (ASF) under one or more
016 * contributor license agreements. See the NOTICE file distributed with
017 * this work for additional information regarding copyright ownership.
018 * The ASF licenses this file to You under the Apache License, Version 2.0
019 * (the "License"); you may not use this file except in compliance with
020 * the License. You may obtain a copy of the License at
021 * 
022 * http://www.apache.org/licenses/LICENSE-2.0
023 * 
024 * Unless required by applicable law or agreed to in writing, software
025 * distributed under the License is distributed on an "AS IS" BASIS,
026 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
027 * See the License for the specific language governing permissions and
028 * limitations under the License.
029 * 
030 * Modified by Stephane Dallongeville
031 */
032
033/**
034 * This class has the same behavior than {@link Polygon2D}, except that
035 * the figure is not closed.
036 */
037public class Polyline2D implements Shape, Cloneable
038{
039    private static final double ASSUME_ZERO = 0.00001d;
040
041    /**
042     * The total number of points. The value of <code>npoints</code> represents the number of points in this
043     * <code>Polyline2D</code>.
044     */
045    public int npoints;
046
047    /**
048     * The array of <i>x</i> coordinates. The value of {@link #npoints} is equal to the
049     * number of points in this <code>Polyline2D</code>.
050     */
051    public double[] xpoints;
052
053    /**
054     * The array of <i>y</i> coordinates. The value of {@link #npoints} is equal to the
055     * number of points in this <code>Polyline2D</code>.
056     */
057    public double[] ypoints;
058
059    /**
060     * Bounds of the Polyline2D.
061     * 
062     * @see #getBounds()
063     */
064    protected Rectangle2D bounds;
065
066    protected Path2D.Double path;
067
068    /**
069     * Creates an empty Polyline2D.
070     */
071    public Polyline2D()
072    {
073        super();
074
075        reset();
076    }
077
078    /**
079     * Constructs and initializes a <code>Polyline2D</code> from the specified
080     * parameters.
081     * 
082     * @param xpoints
083     *        an array of <i>x</i> coordinates
084     * @param ypoints
085     *        an array of <i>y</i> coordinates
086     * @param npoints
087     *        the total number of points in the <code>Polyline2D</code>
088     * @exception NegativeArraySizeException
089     *            if the value of <code>npoints</code> is negative.
090     * @exception IndexOutOfBoundsException
091     *            if <code>npoints</code> is
092     *            greater than the length of <code>xpoints</code> or the length of <code>ypoints</code>.
093     * @exception NullPointerException
094     *            if <code>xpoints</code> or <code>ypoints</code> is <code>null</code>.
095     */
096    public Polyline2D(double[] xpoints, double[] ypoints, int npoints)
097    {
098        super();
099
100        if (npoints > xpoints.length || npoints > ypoints.length)
101            throw new IndexOutOfBoundsException("npoints > xpoints.length || npoints > ypoints.length");
102
103        this.npoints = npoints;
104        this.xpoints = new double[npoints];
105        this.ypoints = new double[npoints];
106
107        System.arraycopy(xpoints, 0, this.xpoints, 0, npoints);
108        System.arraycopy(ypoints, 0, this.ypoints, 0, npoints);
109
110        calculatePath();
111    }
112
113    /**
114     * Constructs and initializes a <code>Polyline2D</code> from the specified
115     * parameters.
116     * 
117     * @param xpoints
118     *        an array of <i>x</i> coordinates
119     * @param ypoints
120     *        an array of <i>y</i> coordinates
121     * @param npoints
122     *        the total number of points in the <code>Polyline2D</code>
123     * @exception NegativeArraySizeException
124     *            if the value of <code>npoints</code> is negative.
125     * @exception IndexOutOfBoundsException
126     *            if <code>npoints</code> is
127     *            greater than the length of <code>xpoints</code> or the length of <code>ypoints</code>.
128     * @exception NullPointerException
129     *            if <code>xpoints</code> or <code>ypoints</code> is <code>null</code>.
130     */
131    public Polyline2D(int[] xpoints, int[] ypoints, int npoints)
132    {
133        super();
134
135        if (npoints > xpoints.length || npoints > ypoints.length)
136            throw new IndexOutOfBoundsException("npoints > xpoints.length || npoints > ypoints.length");
137
138        this.npoints = npoints;
139        this.xpoints = new double[npoints];
140        this.ypoints = new double[npoints];
141
142        for (int i = 0; i < npoints; i++)
143        {
144            this.xpoints[i] = xpoints[i];
145            this.ypoints[i] = ypoints[i];
146        }
147
148        calculatePath();
149    }
150
151    public Polyline2D(Line2D line)
152    {
153        super();
154
155        npoints = 2;
156        xpoints = new double[2];
157        ypoints = new double[2];
158
159        xpoints[0] = line.getX1();
160        xpoints[1] = line.getX2();
161        ypoints[0] = line.getY1();
162        ypoints[1] = line.getY2();
163
164        calculatePath();
165    }
166
167    /**
168     * Resets this <code>Polyline2D</code> object to an empty polygon.
169     * The coordinate arrays and the data in them are left untouched
170     * but the number of points is reset to zero to mark the old
171     * vertex data as invalid and to start accumulating new vertex
172     * data at the beginning.
173     * All internally-cached data relating to the old vertices
174     * are discarded.
175     * Note that since the coordinate arrays from before the reset
176     * are reused, creating a new empty <code>Polyline2D</code> might
177     * be more memory efficient than resetting the current one if
178     * the number of vertices in the new polyline data is significantly
179     * smaller than the number of vertices in the data from before the
180     * reset.
181     */
182    public void reset()
183    {
184        npoints = 0;
185        xpoints = new double[0];
186        ypoints = new double[0];
187        bounds = new Rectangle2D.Double();
188        path = null;
189    }
190
191    @Override
192    public Object clone()
193    {
194        Polyline2D pol = new Polyline2D();
195
196        for (int i = 0; i < npoints; i++)
197            pol.addPoint(xpoints[i], ypoints[i]);
198
199        return pol;
200    }
201
202    public void calculatePath()
203    {
204        path = new Path2D.Double();
205
206        path.moveTo(xpoints[0], ypoints[0]);
207        for (int i = 1; i < npoints; i++)
208            path.lineTo(xpoints[i], ypoints[i]);
209
210        bounds = path.getBounds2D();
211    }
212
213    protected void updatePath(double x, double y)
214    {
215        if (path == null)
216        {
217            path = new Path2D.Double(Path2D.WIND_EVEN_ODD);
218            path.moveTo(x, y);
219            bounds = new Rectangle2D.Double(x, y, 0, 0);
220        }
221        else
222        {
223            path.lineTo(x, y);
224
225            double _xmax = bounds.getMaxX();
226            double _ymax = bounds.getMaxY();
227            double _xmin = bounds.getMinX();
228            double _ymin = bounds.getMinY();
229
230            if (x < _xmin)
231                _xmin = x;
232            else if (x > _xmax)
233                _xmax = x;
234            if (y < _ymin)
235                _ymin = y;
236            else if (y > _ymax)
237                _ymax = y;
238
239            bounds = new Rectangle2D.Double(_xmin, _ymin, _xmax - _xmin, _ymax - _ymin);
240        }
241    }
242
243    public void addPoint(Point2D p)
244    {
245        addPoint(p.getX(), p.getY());
246    }
247
248    /**
249     * Appends the specified coordinates to this <code>Polyline2D</code>.
250     * <p>
251     * If an operation that calculates the bounding box of this <code>Polyline2D</code> has already been performed, such
252     * as <code>getBounds</code> or <code>contains</code>, then this method updates the bounding box.
253     * 
254     * @param x
255     *        the specified x coordinate
256     * @param y
257     *        the specified y coordinate
258     * @see java.awt.Polygon#getBounds
259     * @see java.awt.Polygon#contains(double,double)
260     */
261    public void addPoint(double x, double y)
262    {
263        if (npoints == xpoints.length)
264        {
265            double[] tmp;
266
267            tmp = new double[(npoints * 2) + 1];
268            System.arraycopy(xpoints, 0, tmp, 0, npoints);
269            xpoints = tmp;
270
271            tmp = new double[(npoints * 2) + 1];
272            System.arraycopy(ypoints, 0, tmp, 0, npoints);
273            ypoints = tmp;
274        }
275
276        xpoints[npoints] = x;
277        ypoints[npoints] = y;
278        npoints++;
279
280        updatePath(x, y);
281    }
282
283    /**
284     * Returns the high precision bounding box of the {@link Shape}.
285     * 
286     * @return a {@link Rectangle2D} that precisely
287     *         bounds the <code>Shape</code>.
288     */
289    @Override
290    public Rectangle2D getBounds2D()
291    {
292        return (Rectangle2D) bounds.clone();
293    }
294
295    /**
296     * Gets the bounding box of this <code>Polyline2D</code>.
297     * The bounding box is the smallest {@link Rectangle} whose
298     * sides are parallel to the x and y axes of the
299     * coordinate space, and can completely contain the <code>Polyline2D</code>.
300     * 
301     * @return a <code>Rectangle</code> that defines the bounds of this <code>Polyline2D</code>.
302     */
303    @Override
304    public Rectangle getBounds()
305    {
306        if (bounds == null)
307            return new Rectangle();
308
309        return bounds.getBounds();
310    }
311
312    /**
313     * Determines whether the specified {@link Point} is inside this <code>Polyline2D</code>.
314     * This method is required to implement the Shape interface,
315     * but in the case of Line2D objects it always returns false since a line contains no area.
316     */
317    public boolean contains(Point p)
318    {
319        return false;
320    }
321
322    /**
323     * Determines if the specified coordinates are inside this <code>Polyline2D</code>.
324     * This method is required to implement the Shape interface,
325     * but in the case of Line2D objects it always returns false since a line contains no area.
326     */
327    @Override
328    public boolean contains(double x, double y)
329    {
330        return false;
331    }
332
333    /**
334     * Determines whether the specified coordinates are inside this <code>Polyline2D</code>.
335     * This method is required to implement the Shape interface,
336     * but in the case of Line2D objects it always returns false since a line contains no area.
337     */
338    public boolean contains(int x, int y)
339    {
340        return false;
341    }
342
343    /**
344     * Tests if a specified {@link Point2D} is inside the boundary of this <code>Polyline2D</code>.
345     * This method is required to implement the Shape interface,
346     * but in the case of Line2D objects it always returns false since a line contains no area.
347     */
348    @Override
349    public boolean contains(Point2D p)
350    {
351        return false;
352    }
353
354    /**
355     * Tests if the interior of this <code>Polygon</code> intersects the
356     * interior of a specified set of rectangular coordinates.
357     * 
358     * @param x
359     *        the x coordinate of the specified rectangular
360     *        shape's top-left corner
361     * @param y
362     *        the y coordinate of the specified rectangular
363     *        shape's top-left corner
364     * @param w
365     *        the width of the specified rectangular shape
366     * @param h
367     *        the height of the specified rectangular shape
368     * @return <code>true</code> if the interior of this <code>Polygon</code> and the interior of the
369     *         specified set of rectangular
370     *         coordinates intersect each other; <code>false</code> otherwise.
371     */
372    @Override
373    public boolean intersects(double x, double y, double w, double h)
374    {
375        if ((path == null) || !bounds.intersects(x, y, w, h))
376            return false;
377
378        return path.intersects(x, y, w, h);
379    }
380
381    /**
382     * Tests if the interior of this <code>Polygon</code> intersects the
383     * interior of a specified <code>Rectangle2D</code>.
384     * 
385     * @param r
386     *        a specified <code>Rectangle2D</code>
387     * @return <code>true</code> if this <code>Polygon</code> and the
388     *         interior of the specified <code>Rectangle2D</code> intersect each other; <code>false</code> otherwise.
389     */
390    @Override
391    public boolean intersects(Rectangle2D r)
392    {
393        return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight());
394    }
395
396    /**
397     * Tests if the interior of this <code>Polyline2D</code> entirely
398     * contains the specified set of rectangular coordinates.
399     * This method is required to implement the Shape interface,
400     * but in the case of Line2D objects it always returns false since a line contains no area.
401     */
402    @Override
403    public boolean contains(double x, double y, double w, double h)
404    {
405        return false;
406    }
407
408    /**
409     * Tests if the interior of this <code>Polyline2D</code> entirely
410     * contains the specified <code>Rectangle2D</code>.
411     * This method is required to implement the Shape interface,
412     * but in the case of Line2D objects it always returns false since a line contains no area.
413     */
414    @Override
415    public boolean contains(Rectangle2D r)
416    {
417        return false;
418    }
419
420    /*
421     * get the associated {@link Polygon2D}.
422     * This method take care that may be the last point can
423     * be equal to the first. In that case it must not be included in the Polygon,
424     * as polygons declare their first point only once.
425     */
426    public Polygon2D getPolygon2D()
427    {
428        Polygon2D pol = new Polygon2D();
429
430        for (int i = 0; i < npoints - 1; i++)
431            pol.addPoint(xpoints[i], ypoints[i]);
432
433        Point2D.Double p0 = new Point2D.Double(xpoints[0], ypoints[0]);
434        Point2D.Double p1 = new Point2D.Double(xpoints[npoints - 1], ypoints[npoints - 1]);
435
436        if (p0.distance(p1) > ASSUME_ZERO)
437            pol.addPoint(xpoints[npoints - 1], ypoints[npoints - 1]);
438
439        return pol;
440    }
441
442    /**
443     * Returns an iterator object that iterates along the boundary of this <code>Polygon</code> and provides access to
444     * the geometry
445     * of the outline of this <code>Polygon</code>. An optional {@link AffineTransform} can be specified so that the
446     * coordinates
447     * returned in the iteration are transformed accordingly.
448     * 
449     * @param at
450     *        an optional <code>AffineTransform</code> to be applied to the
451     *        coordinates as they are returned in the iteration, or <code>null</code> if untransformed coordinates are
452     *        desired
453     * @return a {@link PathIterator} object that provides access to the
454     *         geometry of this <code>Polygon</code>.
455     */
456    @Override
457    public PathIterator getPathIterator(AffineTransform at)
458    {
459        if (path == null)
460            return new Path2D.Double().getPathIterator(at);
461
462        return path.getPathIterator(at);
463    }
464
465    /**
466     * Returns an iterator object that iterates along the boundary of
467     * the <code>Shape</code> and provides access to the geometry of the
468     * outline of the <code>Shape</code>. Only SEG_MOVETO and SEG_LINETO, point types
469     * are returned by the iterator.
470     * Since polylines are already flat, the <code>flatness</code> parameter
471     * is ignored.
472     * 
473     * @param at
474     *        an optional <code>AffineTransform</code> to be applied to the
475     *        coordinates as they are returned in the iteration, or <code>null</code> if untransformed coordinates are
476     *        desired
477     * @param flatness
478     *        the maximum amount that the control points
479     *        for a given curve can vary from colinear before a subdivided
480     *        curve is replaced by a straight line connecting the
481     *        endpoints. Since polygons are already flat the <code>flatness</code> parameter is ignored.
482     * @return a <code>PathIterator</code> object that provides access to the <code>Shape</code> object's geometry.
483     */
484    @Override
485    public PathIterator getPathIterator(AffineTransform at, double flatness)
486    {
487        return getPathIterator(at);
488    }
489}