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}