View Javadoc

1   /* File Box2D.java 
2    *
3    * Project : Java Geometry Library
4    *
5    * ===========================================
6    * 
7    * This library is free software; you can redistribute it and/or modify it 
8    * under the terms of the GNU Lesser General Public License as published by
9    * the Free Software Foundation, either version 2.1 of the License, or (at
10   * your option) any later version.
11   *
12   * This library is distributed in the hope that it will be useful, but 
13   * WITHOUT ANY WARRANTY, without even the implied warranty of MERCHANTABILITY
14   * or FITNESS FOR A PARTICULAR PURPOSE.
15   *
16   * See the GNU Lesser General Public License for more details.
17   *
18   * You should have received a copy of the GNU Lesser General Public License
19   * along with this library. if not, write to :
20   * The Free Software Foundation, Inc., 59 Temple Place, Suite 330,
21   * Boston, MA 02111-1307, USA.
22   * 
23   * Created on 05 mar. 2007
24   */
25  
26  // package
27  
28  package math.geom2d;
29  
30  // Imports
31  import java.awt.Graphics2D;
32  import java.util.ArrayList;
33  import java.util.Collection;
34  
35  import math.geom2d.domain.Boundary2D;
36  import math.geom2d.domain.BoundaryPolyCurve2D;
37  import math.geom2d.domain.BoundarySet2D;
38  import math.geom2d.line.AbstractLine2D;
39  import math.geom2d.line.LineArc2D;
40  import math.geom2d.line.LineSegment2D;
41  import math.geom2d.line.LinearShape2D;
42  import math.geom2d.line.StraightLine2D;
43  import math.geom2d.polygon.HRectangle2D;
44  import math.geom2d.polygon.LinearRing2D;
45  
46  /**
47   * This class defines bounds of a shape. It stores limits in each direction:
48   * <code>x</code> and <code>y</code>. It also provides methods for clipping
49   * others shapes, depending on their type.
50   */
51  public class Box2D implements Cloneable {
52  
53      // ===================================================================
54      // class variables
55  
56      private double xmin = 0;
57      private double xmax = 0;
58      private double ymin = 0;
59      private double ymax = 0;
60  
61      // ===================================================================
62      // constructors
63  
64      /** Empty constructor (size and position zero) */
65      public Box2D() {
66          this(0, 0, 0, 0);
67      }
68  
69      /**
70       * Main constructor, given bounds for x coord, then bounds for y coord. A
71       * check is performed to ensure first bound is lower than second bound.
72       * Consider creating a new box using static factory instead (0.8.1)
73       */
74      public Box2D(double x0, double x1, double y0, double y1) {
75          xmin = Math.min(x0, x1);
76          xmax = Math.max(x0, x1);
77          ymin = Math.min(y0, y1);
78          ymax = Math.max(y0, y1);
79      }
80  
81      /** Constructor from awt, to allow easy construction from existing apps. */
82      public Box2D(java.awt.geom.Rectangle2D rect) {
83          this(rect.getX(), rect.getX()+rect.getWidth(), rect.getY(), rect.getY()
84                  +rect.getHeight());
85      }
86  
87      /**
88       * Constructor from 2 points, giving extreme coordinates of the box.
89       * Consider creating a new box using static factory instead (0.8.1)
90       */
91      public Box2D(Point2D p1, Point2D p2) {
92          this(p1.getX(), p2.getX(), p1.getY(), p2.getY());
93      }
94  
95      /** Constructor from a point, a width and an height */
96      public Box2D(Point2D point, double w, double h) {
97          this(point.getX(), point.getX()+w, point.getY(), point.getY()+h);
98      }
99  
100     // ===================================================================
101     // Static factory
102     
103     
104     /**
105      * @since 0.8.1
106      */
107     public final static Box2D create(double xmin, double xmax, double ymin, double ymax) {
108     	return new Box2D(xmin, xmax, ymin, ymax);
109     }
110 
111     /**
112      * @since 0.8.1
113      */
114     public final static Box2D create(Point2D p1, Point2D p2) {
115     	return new Box2D(p1, p2);
116     }
117 
118     // ===================================================================
119     // accessors to Box2D fields
120 
121     public double getMinX() {
122         return xmin;
123     }
124 
125     public double getMinY() {
126         return ymin;
127     }
128 
129     public double getMaxX() {
130         return xmax;
131     }
132 
133     public double getMaxY() {
134         return ymax;
135     }
136 
137     public double getWidth() {
138         return xmax-xmin;
139     }
140 
141     public double getHeight() {
142         return ymax-ymin;
143     }
144 
145     /** Returns true if all bounds are finite. */
146     public boolean isBounded() {
147         if (Double.isInfinite(xmin))
148             return false;
149         if (Double.isInfinite(ymin))
150             return false;
151         if (Double.isInfinite(xmax))
152             return false;
153         if (Double.isInfinite(ymax))
154             return false;
155         return true;
156     }
157 
158     // ===================================================================
159     // tests of inclusion
160 
161     public boolean contains(java.awt.geom.Point2D point) {
162         double x = point.getX();
163         double y = point.getY();
164         if (x<xmin)
165             return false;
166         if (y<ymin)
167             return false;
168         if (x>xmax)
169             return false;
170         if (y>ymax)
171             return false;
172         return true;
173     }
174 
175     public boolean contains(double x, double y) {
176         if (x<xmin)
177             return false;
178         if (y<ymin)
179             return false;
180         if (x>xmax)
181             return false;
182         if (y>ymax)
183             return false;
184         return true;
185     }
186 
187     /**
188      * Test if the specified Shape is totally contained in this Box2D. Note that
189      * the test is performed on the bounding box of the shape, then for rotated
190      * rectangles, this method can return false with a shape totally contained
191      * in the rectangle. The problem does not exist for horizontal rectangle,
192      * since edges of rectangle and bounding box are parallel.
193      */
194     public boolean containsBounds(Shape2D shape) {
195         if (!shape.isBounded())
196             return false;
197         for (Point2D point : shape.getBoundingBox().getVertices())
198             if (!contains(point))
199                 return false;
200 
201         return true;
202     }
203 
204     // ===================================================================
205     // information on the boundary
206 
207     /**
208      * Returns a set of straight of lines defining half-planes, that all contain
209      * the box. If the box is bounded, the number of straight lines is 4,
210      * otherwise it can be less.
211      * 
212      * @return a set of straight lines
213      */
214     public Collection<StraightLine2D> getClippingLines() {
215         ArrayList<StraightLine2D> lines = new ArrayList<StraightLine2D>(4);
216 
217         if (!(Double.isInfinite(ymin)||Double.isNaN(ymin)))
218             lines.add(new StraightLine2D(0, ymin, 1, 0));
219         if (!(Double.isInfinite(xmax)||Double.isNaN(xmax)))
220             lines.add(new StraightLine2D(xmax, 0, 0, 1));
221         if (!(Double.isInfinite(ymax)||Double.isNaN(ymax)))
222             lines.add(new StraightLine2D(0, ymax, -1, 0));
223         if (!(Double.isInfinite(xmin)||Double.isNaN(xmin)))
224             lines.add(new StraightLine2D(xmin, 0, 0, -1));
225         return lines;
226     }
227 
228     /**
229      * Returns the set of linear shapes that constitutes the boundary of this
230      * box.
231      */
232     public Collection<LinearShape2D> getEdges() {
233         ArrayList<LinearShape2D> edges = new ArrayList<LinearShape2D>(4);
234 
235         if (isBounded()) {
236             edges.add(new LineSegment2D(xmin, ymin, xmax, ymin));
237             edges.add(new LineSegment2D(xmax, ymin, xmax, ymax));
238             edges.add(new LineSegment2D(xmax, ymax, xmin, ymax));
239             edges.add(new LineSegment2D(xmin, ymax, xmin, ymin));
240             return edges;
241         }
242 
243         if (!Double.isInfinite(ymin)) {
244             if (Double.isInfinite(xmin)&&Double.isInfinite(xmax))
245                 edges.add(new StraightLine2D(0, ymin, 1, 0));
246             else if (!Double.isInfinite(xmin)&&!Double.isInfinite(xmax))
247                 edges.add(new LineSegment2D(xmin, ymin, xmax, ymin));
248             else
249                 edges.add(new LineArc2D(0, ymin, 1, 0, xmin, xmax));
250         }
251 
252         if (!Double.isInfinite(xmax)) {
253             if (Double.isInfinite(ymin)&&Double.isInfinite(ymax))
254                 edges.add(new StraightLine2D(xmax, 0, 0, 1));
255             else if (!Double.isInfinite(ymin)&&!Double.isInfinite(ymax))
256                 edges.add(new LineSegment2D(xmax, ymin, xmax, ymax));
257             else
258                 edges.add(new LineArc2D(xmax, 0, 0, 1, ymin, ymax));
259         }
260 
261         if (!Double.isInfinite(ymax)) {
262             if (Double.isInfinite(xmin)&&Double.isInfinite(xmax))
263                 edges.add(new StraightLine2D(0, ymax, 1, 0));
264             else if (!Double.isInfinite(xmin)&&!Double.isInfinite(xmax))
265                 edges.add(new LineSegment2D(xmax, ymax, xmin, ymax));
266             else
267                 edges.add(new LineArc2D(0, ymin, 1, 0, xmin, xmax)
268                         .getReverseCurve());
269         }
270 
271         if (!Double.isInfinite(xmin)) {
272             if (Double.isInfinite(ymin)&&Double.isInfinite(ymax))
273                 edges.add(new StraightLine2D(xmin, 0, 0, -1));
274             else if (!Double.isInfinite(ymin)&&!Double.isInfinite(ymax))
275                 edges.add(new LineSegment2D(xmin, ymax, xmin, ymin));
276             else
277                 edges.add(new LineArc2D(xmin, 0, 0, 1, ymin, ymax)
278                         .getReverseCurve());
279         }
280 
281         return edges;
282     }
283 
284     /**
285      * Returns the boundary of this box. The boundary can be bounded, in the
286      * case of a bounded box. It is unbounded if at least one bound of the box
287      * is infinite. If both x bounds or both y-bounds are infinite, the
288      * boundary is constituted from 2 straight lines.
289      * @return the box boundary
290      */
291     public Boundary2D getBoundary() {
292 
293         // First case of totally bounded box
294         if (isBounded()) {
295             Point2D pts[] = new Point2D[4];
296             pts[0] = new Point2D(xmin, ymin);
297             pts[1] = new Point2D(xmax, ymin);
298             pts[2] = new Point2D(xmax, ymax);
299             pts[3] = new Point2D(xmin, ymax);
300             return new LinearRing2D(pts);
301         }
302 
303         // extract boolean info on "boundedness" in each direction
304         boolean bx0 = !Double.isInfinite(xmin);
305         boolean bx1 = !Double.isInfinite(xmax);
306         boolean by0 = !Double.isInfinite(ymin);
307         boolean by1 = !Double.isInfinite(ymax);
308 
309         // case of boxes unbounded in both x directions
310         if (!bx0&&!bx1) {
311             if (!by0&&!by1)
312                 return new BoundarySet2D<StraightLine2D>();
313             if (by0&&!by1)
314                 return new StraightLine2D(0, ymin, 1, 0);
315             if (!by0&&by1)
316                 return new StraightLine2D(0, ymax, -1, 0);
317             return new BoundarySet2D<StraightLine2D>(new StraightLine2D[] {
318                     new StraightLine2D(0, ymin, 1, 0),
319                     new StraightLine2D(0, ymax, -1, 0) });
320         }
321 
322         // case of boxes unbounded in both y directions
323         if (!by0&&!by1) {
324             if (!bx0&&!bx1)
325                 return new BoundarySet2D<StraightLine2D>();
326             if (bx0&&!bx1)
327                 return new StraightLine2D(xmin, 0, 0, -1);
328             if (!bx0&&bx1)
329                 return new StraightLine2D(xmax, 0, 0, 1);
330             return new BoundarySet2D<StraightLine2D>(new StraightLine2D[] {
331                     new StraightLine2D(xmin, 0, 0, -1),
332                     new StraightLine2D(xmax, 0, 0, 1) });
333         }
334 
335         // "corner boxes"
336 
337         if (bx0&&by0) // lower left corner
338             return new BoundaryPolyCurve2D<LineArc2D>(
339                     new LineArc2D[] {
340                             new LineArc2D(xmin, ymin, 0, -1,
341                                     Double.NEGATIVE_INFINITY, 0),
342                             new LineArc2D(xmin, ymin, 1, 0, 0,
343                                     Double.POSITIVE_INFINITY) });
344 
345         if (bx1&&by0) // lower right corner
346             return new BoundaryPolyCurve2D<LineArc2D>(
347                     new LineArc2D[] {
348                             new LineArc2D(xmax, ymin, 1, 0,
349                                     Double.NEGATIVE_INFINITY, 0),
350                             new LineArc2D(xmax, ymin, 0, 1, 0,
351                                     Double.POSITIVE_INFINITY) });
352 
353         if (bx1&&by1) // upper right corner
354             return new BoundaryPolyCurve2D<LineArc2D>(
355                     new LineArc2D[] {
356                             new LineArc2D(xmax, ymax, 0, 1,
357                                     Double.NEGATIVE_INFINITY, 0),
358                             new LineArc2D(xmax, ymax, -1, 0, 0,
359                                     Double.POSITIVE_INFINITY) });
360 
361         if (bx0&&by1) // upper left corner
362             return new BoundaryPolyCurve2D<LineArc2D>(new LineArc2D[] {
363                     new LineArc2D(xmin, ymax, -1, 0, Double.NEGATIVE_INFINITY,
364                             0),
365                     new LineArc2D(xmin, ymax, 0, -1, 0,
366                             Double.POSITIVE_INFINITY) });
367 
368         // Remains only 4 cases: boxes unbounded in only one direction
369 
370         if (bx0)
371             return new BoundaryPolyCurve2D<AbstractLine2D>(
372                     new AbstractLine2D[] {
373                             new LineArc2D(xmin, ymax, -1, 0,
374                                     Double.NEGATIVE_INFINITY, 0),
375                             new LineSegment2D(xmin, ymax, xmin, ymin),
376                             new LineArc2D(xmin, ymin, 1, 0, 0,
377                                     Double.POSITIVE_INFINITY) });
378 
379         if (bx1)
380             return new BoundaryPolyCurve2D<AbstractLine2D>(
381                     new AbstractLine2D[] {
382                             new LineArc2D(xmax, ymin, 1, 0,
383                                     Double.NEGATIVE_INFINITY, 0),
384                             new LineSegment2D(xmax, ymin, xmax, ymax),
385                             new LineArc2D(xmax, ymax, -1, 0, 0,
386                                     Double.POSITIVE_INFINITY) });
387 
388         if (by0)
389             return new BoundaryPolyCurve2D<AbstractLine2D>(
390                     new AbstractLine2D[] {
391                             new LineArc2D(xmin, ymin, 0, -1,
392                                     Double.NEGATIVE_INFINITY, 0),
393                             new LineSegment2D(xmin, ymin, xmax, ymin),
394                             new LineArc2D(xmax, ymin, 0, 1, 0,
395                                     Double.POSITIVE_INFINITY) });
396 
397         if (by1)
398             return new BoundaryPolyCurve2D<AbstractLine2D>(
399                     new AbstractLine2D[] {
400                             new LineArc2D(xmax, ymax, 0, 1,
401                                     Double.NEGATIVE_INFINITY, 0),
402                             new LineSegment2D(xmax, ymax, xmin, ymax),
403                             new LineArc2D(xmin, ymax, 0, -1, 0,
404                                     Double.POSITIVE_INFINITY) });
405 
406         return null;
407     }
408 
409     public Collection<Point2D> getVertices() {
410         ArrayList<Point2D> points = new ArrayList<Point2D>(4);
411         boolean bx0 = !(Double.isInfinite(xmin)||Double.isNaN(xmin));
412         boolean bx1 = !(Double.isInfinite(xmax)||Double.isNaN(xmax));
413         boolean by0 = !(Double.isInfinite(ymin)||Double.isNaN(ymin));
414         boolean by1 = !(Double.isInfinite(ymax)||Double.isNaN(ymax));
415         if (bx0&&by0)
416             points.add(new Point2D(xmin, ymin));
417         if (bx1&&by0)
418             points.add(new Point2D(xmax, ymin));
419         if (bx0&&by1)
420             points.add(new Point2D(xmin, ymax));
421         if (bx1&&by1)
422             points.add(new Point2D(xmax, ymax));
423         return points;
424     }
425 
426     /** Returns the number of vertices of the box. */
427     public int getVertexNumber() {
428         return this.getVertices().size();
429     }
430 
431     // ===================================================================
432     // combination of box with other boxes
433 
434     /**
435      * Returns the Box2D which contains both this box and the specified box.
436      * 
437      * @param box the bounding box to include
438      * @return a new Box2D
439      */
440     public Box2D union(Box2D box) {
441         double xmin = Math.min(this.xmin, box.xmin);
442         double xmax = Math.max(this.xmax, box.xmax);
443         double ymin = Math.min(this.ymin, box.ymin);
444         double ymax = Math.max(this.ymax, box.ymax);
445         return new Box2D(xmin, xmax, ymin, ymax);
446     }
447 
448     /**
449      * Returns the Box2D which is contained both by this box and by the
450      * specified box.
451      * 
452      * @param box the bounding box to include
453      * @return a new Box2D
454      */
455     public Box2D intersection(Box2D box) {
456         double xmin = Math.max(this.xmin, box.xmin);
457         double xmax = Math.min(this.xmax, box.xmax);
458         double ymin = Math.max(this.ymin, box.ymin);
459         double ymax = Math.min(this.ymax, box.ymax);
460         return new Box2D(xmin, xmax, ymin, ymax);
461     }
462 
463     /**
464      * Changes the bounds of this box to also include bounds of the argument.
465      * 
466      * @param box the bounding box to include
467      * @return this
468      */
469     public Box2D merge(Box2D box) {
470         this.xmin = Math.min(this.xmin, box.xmin);
471         this.xmax = Math.max(this.xmax, box.xmax);
472         this.ymin = Math.min(this.ymin, box.ymin);
473         this.ymax = Math.max(this.ymax, box.ymax);
474         return this;
475     }
476 
477     /**
478      * Clip this bounding box such that after clipping, it is totally contained
479      * in the given box.
480      * 
481      * @return this
482      */
483     public Box2D clip(Box2D box) {
484         this.xmin = Math.max(this.xmin, box.xmin);
485         this.xmax = Math.min(this.xmax, box.xmax);
486         this.ymin = Math.max(this.ymin, box.ymin);
487         this.ymax = Math.min(this.ymax, box.ymax);
488         return this;
489     }
490 
491     /**
492      * Return the new domain created by an affine transform of this box.
493      */
494     public Box2D transform(AffineTransform2D trans) {
495         if (this.isBounded()) {
496             // Extract the 4 vertices, transform them, and compute
497             // the new bounding box.
498             Collection<Point2D> points = this.getVertices();
499             double xmin = Double.POSITIVE_INFINITY;
500             double xmax = Double.NEGATIVE_INFINITY;
501             double ymin = Double.POSITIVE_INFINITY;
502             double ymax = Double.NEGATIVE_INFINITY;
503             for (Point2D point : points) {
504                 point = point.transform(trans);
505                 xmin = Math.min(xmin, point.getX());
506                 ymin = Math.min(ymin, point.getY());
507                 xmax = Math.max(xmax, point.getX());
508                 ymax = Math.max(ymax, point.getY());
509             }
510             return new Box2D(xmin, xmax, ymin, ymax);
511         }
512 
513         // TODO: implement a more precise method
514         double xmin = Double.NEGATIVE_INFINITY;
515         double xmax = Double.POSITIVE_INFINITY;
516         double ymin = Double.NEGATIVE_INFINITY;
517         double ymax = Double.POSITIVE_INFINITY;
518 
519         return new Box2D(xmin, xmax, ymin, ymax);
520     }
521 
522     // ===================================================================
523     // conversion methods
524 
525     /**
526      * convert to AWT rectangle.
527      * 
528      * @return an instance of java.awt.geom.Rectangle2D
529      */
530     public java.awt.Rectangle getAsAWTRectangle() {
531         int xr = (int) Math.floor(this.xmin);
532         int yr = (int) Math.floor(this.ymin);
533         int wr = (int) Math.ceil(this.xmax-xr);
534         int hr = (int) Math.ceil(this.ymax-yr);
535         return new java.awt.Rectangle(xr, yr, wr, hr);
536     }
537 
538     /**
539      * convert to AWT Rectangle2D. Result is an instance of HRectangle, which
540      * extends java.awt.geom.Rectangle2D.Double.
541      * 
542      * @return an instance of java.awt.geom.Rectangle2D
543      */
544     public java.awt.geom.Rectangle2D getAsAWTRectangle2D() {
545         return new HRectangle2D(xmin, ymin, xmax-xmin, ymax-ymin);
546     }
547 
548     /**
549      * Converts to a rectangle. Result is an instance of HRectangle, which
550      * extends java.awt.geom.Rectangle2D.Double.
551      * 
552      * @return an instance of HRectangle2D
553      */
554     public HRectangle2D getAsRectangle() {
555         return new HRectangle2D(xmin, ymin, xmax-xmin, ymax-ymin);
556     }
557 
558     public void draw(Graphics2D g2) {
559         if (!isBounded())
560             throw new UnboundedShapeException();
561         this.getBoundary().draw(g2);
562     }
563 
564     public void fill(Graphics2D g2) {
565         if (!isBounded())
566             throw new UnboundedShapeException();
567         this.getBoundary().fill(g2);
568     }
569 
570     public Box2D getBoundingBox() {
571         return new Box2D(this.getMinX(), this.getMaxX(), this.getMinY(), this
572                 .getMaxY());
573     }
574 
575     // ===================================================================
576     // methods from Object interface
577 
578     @Override
579     public String toString() {
580         return new String("Box2D("+xmin+","+xmax+","+ymin+","+ymax+")");
581     }
582 
583     /**
584      * Test if boxes are the same. two boxes are the same if the have the same
585      * bounds.
586      */
587     @Override
588     public boolean equals(Object obj) {
589         // check class, and cast type
590         if (!(obj instanceof Box2D))
591             return false;
592         Box2D box = (Box2D) obj;
593 
594         if (Math.abs(box.xmin-this.xmin)>Shape2D.ACCURACY)
595             return false;
596         if (Math.abs(box.ymin-this.ymin)>Shape2D.ACCURACY)
597             return false;
598         if (Math.abs(box.xmax-this.xmax)>Shape2D.ACCURACY)
599             return false;
600         if (Math.abs(box.ymax-this.ymax)>Shape2D.ACCURACY)
601             return false;
602 
603         return true;
604     }
605     
606     @Override
607     public Box2D clone() {
608         return new Box2D(xmin, xmax, ymin, ymax);
609     }
610 }