]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/TransformedRectangle.java
G2DParentNode handles "undefined" child bounds separately
[simantics/platform.git] / bundles / org.simantics.scenegraph / src / org / simantics / scenegraph / utils / TransformedRectangle.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.scenegraph.utils;
13
14 import java.awt.Rectangle;
15 import java.awt.Shape;
16 import java.awt.geom.AffineTransform;
17 import java.awt.geom.Ellipse2D;
18 import java.awt.geom.NoninvertibleTransformException;
19 import java.awt.geom.PathIterator;
20 import java.awt.geom.Point2D;
21 import java.awt.geom.Rectangle2D;
22 import java.io.Serializable;
23
24 /**
25  * A rectangle that is transformed.
26  * 
27  * @see {@link GeometryUtils} Geometry-related tests for Shapes
28  * @author Toni Kalajainen
29  */
30 public class TransformedRectangle implements Shape, Serializable {
31
32     private static final long serialVersionUID = 5160199078645323706L;
33
34     Rectangle2D rect = new Rectangle2D.Double();
35
36     // T(rect) -> Canvas
37     AffineTransform transform = new AffineTransform();
38     // T(canvas) -> Rect
39     AffineTransform inv = new AffineTransform();
40     boolean isIdentity = true;
41
42     /**
43      * Cache
44      */
45     transient Rectangle2D bounds = null;
46
47     public TransformedRectangle() {}
48
49     public TransformedRectangle(Rectangle2D rect) {
50         if (rect!=null)
51             setUntransformedRectangle(rect);
52     }
53
54     public TransformedRectangle(Rectangle2D rect, AffineTransform transform) {
55         if (rect!=null)
56             setUntransformedRectangle(rect);
57         if (transform!=null)
58             setTransform(transform);
59     }
60
61     public TransformedRectangle(TransformedRectangle src) {
62         setUntransformedRectangle(src.rect);
63         setTransform(src.transform);
64     }
65
66     public Rectangle2D getUntransformedRectangle() {
67         return rect;
68     }
69     public void setUntransformedRectangle(Rectangle2D rect) {
70         this.rect.setFrame(rect);
71         bounds = null;
72     }
73     public AffineTransform getTransform() {
74         return transform;
75     }
76     /**
77      * Set the transform of the rectangle.
78      * Transform transforms rectangle coordinates to external coordinates.
79      * 
80      *  T = Rect -> Canvas
81      * 
82      * @param transform
83      */
84     public void setTransform(AffineTransform transform) {
85         this.transform.setTransform(transform);
86         bounds = null;
87         isIdentity = transform.isIdentity();
88         try {
89             inv = transform.createInverse();
90         } catch (NoninvertibleTransformException e) {
91             throw new RuntimeException(e);
92         }
93     }
94
95     public void concatenate(AffineTransform transform) {
96         this.transform.concatenate(transform);
97         bounds = null;
98         try {
99             inv = transform.createInverse();
100         } catch (NoninvertibleTransformException e) {
101             throw new RuntimeException(e);
102         }
103     }
104
105     @Override
106     public boolean contains(Point2D p) {
107         if (isIdentity)
108             return rect.contains(p);
109
110         Point2D newP = inv.transform(p, new Point2D.Double());
111         return rect.contains(newP);
112     }
113     @Override
114     public boolean contains(double x, double y) {
115         if (isIdentity)
116             return rect.contains(x, y);
117
118         Point2D p = new Point2D.Double(x, y);
119         inv.transform(p, p);
120         return rect.contains(p);
121     }
122     @Override
123     public boolean contains(Rectangle2D r) {
124         if (isIdentity)
125             return rect.contains(r);
126
127         Point2D p1 = new Point2D.Double(r.getMinX(), r.getMinY());
128         Point2D p2 = new Point2D.Double(r.getMaxX(), r.getMinY());
129         Point2D p3 = new Point2D.Double(r.getMaxX(), r.getMaxY());
130         Point2D p4 = new Point2D.Double(r.getMinX(), r.getMaxY());
131
132         inv.transform(p1, p1);
133         inv.transform(p2, p2);
134         inv.transform(p3, p3);
135         inv.transform(p4, p4);
136
137         return rect.contains(p1) && rect.contains(p2) && rect.contains(p3) && rect.contains(p4);
138     }
139     public boolean contains(TransformedRectangle r) {
140         if (isIdentity)
141             return r.intersects(rect);
142
143         // Convert points of r to rect
144         Point2D p1 = new Point2D.Double(r.rect.getMinX(), r.rect.getMinY());
145         Point2D p2 = new Point2D.Double(r.rect.getMaxX(), r.rect.getMinY());
146         Point2D p3 = new Point2D.Double(r.rect.getMaxX(), r.rect.getMaxY());
147         Point2D p4 = new Point2D.Double(r.rect.getMinX(), r.rect.getMaxY());
148
149         r.transform.transform(p1, p1);
150         r.transform.transform(p2, p2);
151         r.transform.transform(p3, p3);
152         r.transform.transform(p4, p4);
153
154         inv.transform(p1, p1);
155         inv.transform(p2, p2);
156         inv.transform(p3, p3);
157         inv.transform(p4, p4);
158
159         return rect.contains(p1) && rect.contains(p2) && rect.contains(p3) && rect.contains(p4);
160     }
161     /**
162      * Tests if the rectangle completely contains the specified shape.
163      * 
164      * NOTE This test simplifies curves to straight edges.
165      * 
166      * @param s
167      * @return
168      */
169     public boolean contains(Shape s) {
170         return contains(s, Double.MAX_VALUE);
171     }
172     public boolean contains(Shape s, double flatness) {
173         if (s instanceof Rectangle2D)
174             return contains((Rectangle2D)s);
175         if (s instanceof TransformedRectangle)
176             return contains( (TransformedRectangle) s);
177
178         // rectangle contains s if all points of s are in side rect
179         PathIterator pi = s.getPathIterator(inv, flatness);
180         double coords[] = new double[6];
181         while (!pi.isDone()) {
182             int type = pi.currentSegment(coords);
183             if (type == PathIterator.SEG_MOVETO || type == PathIterator.SEG_LINETO)
184             {
185                 if (!rect.contains(coords[0], coords[1]))
186                     return false;
187             }
188             assert(type != PathIterator.SEG_CUBICTO && type != PathIterator.SEG_QUADTO);
189             pi.next();
190         }
191         return true;
192     }
193
194     @Override
195     public boolean contains(double x, double y, double w, double h) {
196         if (isIdentity)
197             return rect.contains(x, y, w, h);
198
199         return contains(new Rectangle2D.Double(x, y, w, h));
200     }
201
202     /**
203      * 
204      * NOTE This test simplifies curves to straight edges.
205      * 
206      * @param s
207      * @return
208      */
209     public boolean intersects(Shape s) {
210         return intersects(s, Double.MAX_VALUE);
211     }
212
213     public boolean intersects(Shape s, double flatness) {
214         if (s instanceof Rectangle2D)
215             return intersects((Rectangle2D)s);
216         if (s instanceof TransformedRectangle)
217             return intersects( (TransformedRectangle) s);
218
219         PathIterator pi = s.getPathIterator(inv, flatness);
220         double pos[] = new double[2];
221         double coords[] = new double[6];
222         while (!pi.isDone()) {
223             int type = pi.currentSegment(coords);
224             if (type == PathIterator.SEG_MOVETO)
225             {
226                 pos[0] = coords[0];
227                 pos[1] = coords[1];
228             }
229             else
230                 if (type == PathIterator.SEG_LINETO)
231                 {
232
233                 }
234             assert(type != PathIterator.SEG_CUBICTO && type != PathIterator.SEG_QUADTO);
235             pi.next();
236         }
237         return true;
238     }
239
240     public boolean intersects(TransformedRectangle r) {
241         if (isIdentity)
242             return r.intersects(rect);
243
244         // Convert points of r to rect
245         Point2D p1 = new Point2D.Double(rect.getMinX(), rect.getMinY());
246         Point2D p2 = new Point2D.Double(rect.getMaxX(), rect.getMinY());
247         Point2D p3 = new Point2D.Double(rect.getMaxX(), rect.getMaxY());
248         Point2D p4 = new Point2D.Double(rect.getMinX(), rect.getMaxY());
249
250         AffineTransform at = new AffineTransform(transform);
251         at.concatenate(r.inv);
252
253         at.transform(p1, p1);
254         at.transform(p2, p2);
255         at.transform(p3, p3);
256         at.transform(p4, p4);
257
258         if (r.rect.contains(p1) && r.rect.contains(p2) && r.rect.contains(p3) && r.rect.contains(p4))
259             return true;
260
261         if (r.rect.intersectsLine(p1.getX(), p1.getY(), p2.getX(), p2.getY()) ||
262                 r.rect.intersectsLine(p2.getX(), p2.getY(), p3.getX(), p3.getY()) ||
263                 r.rect.intersectsLine(p3.getX(), p3.getY(), p4.getX(), p4.getY()) ||
264                 r.rect.intersectsLine(p4.getX(), p4.getY(), p1.getX(), p1.getY()))
265             return true;
266
267         p1 = new Point2D.Double(r.rect.getMinX(), r.rect.getMinY());
268         p2 = new Point2D.Double(r.rect.getMaxX(), r.rect.getMinY());
269         p3 = new Point2D.Double(r.rect.getMaxX(), r.rect.getMaxY());
270         p4 = new Point2D.Double(r.rect.getMinX(), r.rect.getMaxY());
271
272         at = new AffineTransform(r.transform);
273         at.concatenate(inv);
274
275         at.transform(p1, p1);
276         at.transform(p2, p2);
277         at.transform(p3, p3);
278         at.transform(p4, p4);
279
280         if (rect.contains(p1) && rect.contains(p2) && rect.contains(p3) && rect.contains(p4))
281             return true;
282
283
284         return false;
285     }
286     @Override
287     public boolean intersects(Rectangle2D r) {
288         if (isIdentity)
289             return rect.intersects(r);
290
291         Point2D p1 = new Point2D.Double(r.getMinX(), r.getMinY());
292         Point2D p2 = new Point2D.Double(r.getMaxX(), r.getMinY());
293         Point2D p3 = new Point2D.Double(r.getMaxX(), r.getMaxY());
294         Point2D p4 = new Point2D.Double(r.getMinX(), r.getMaxY());
295
296         inv.transform(p1, p1);
297         inv.transform(p2, p2);
298         inv.transform(p3, p3);
299         inv.transform(p4, p4);
300
301         if (rect.contains(p1) || rect.contains(p2) || rect.contains(p3) || rect.contains(p4))
302             return true;
303
304         if (rect.intersectsLine(p1.getX(), p1.getY(), p2.getX(), p2.getY()) ||
305                 rect.intersectsLine(p2.getX(), p2.getY(), p3.getX(), p3.getY()) ||
306                 rect.intersectsLine(p3.getX(), p3.getY(), p4.getX(), p4.getY()) ||
307                 rect.intersectsLine(p4.getX(), p4.getY(), p1.getX(), p1.getY()))
308             return true;
309
310         p1 = new Point2D.Double(rect.getMinX(), rect.getMinY());
311         p2 = new Point2D.Double(rect.getMaxX(), rect.getMinY());
312         p3 = new Point2D.Double(rect.getMaxX(), rect.getMaxY());
313         p4 = new Point2D.Double(rect.getMinX(), rect.getMaxY());
314
315         transform.transform(p1, p1);
316         transform.transform(p2, p2);
317         transform.transform(p3, p3);
318         transform.transform(p4, p4);
319
320         if (r.contains(p1) || r.contains(p2) || r.contains(p3) || r.contains(p4))
321             return true;
322
323         return false;
324     }
325     @Override
326     public boolean intersects(double x, double y, double w, double h) {
327         if (isIdentity)
328             return rect.intersects(x, y, w, h);
329
330         return intersects(new Rectangle2D.Double(x, y, w, h));
331     }
332
333     @Override
334     public Rectangle getBounds() {
335         if (isIdentity)
336             return rect.getBounds();
337         Rectangle2D b = getOrCreateBounds();
338         return new Rectangle(
339                 (int) Math.floor(b.getMinX()),
340                 (int) Math.floor(b.getMinY()),
341                 (int) Math.ceil(b.getWidth()),
342                 (int) Math.ceil(b.getHeight())
343         );
344     }
345     @Override
346     public Rectangle2D getBounds2D() {
347         if (isIdentity) return rect;
348         return getOrCreateBounds();
349     }
350     @Override
351     public PathIterator getPathIterator(AffineTransform at) {
352         if (isIdentity)
353             return rect.getPathIterator(at);
354         if (at == null || at.isIdentity())
355             return rect.getPathIterator(transform);
356         // Concatenate both iterators
357         // IS THIS ORDER CORRECT?! UNTESTED
358         AffineTransform con = new AffineTransform(transform);
359         con.preConcatenate(at);
360         return rect.getPathIterator(con);
361     }
362     @Override
363     public PathIterator getPathIterator(AffineTransform at, double flatness) {
364         if (isIdentity)
365             return rect.getPathIterator(at, flatness);
366         if (at == null || at.isIdentity())
367             return rect.getPathIterator(transform, flatness);
368         // Concatenate both iterators
369         AffineTransform con = new AffineTransform(transform);
370         con.concatenate(at);
371         return rect.getPathIterator(con, flatness);
372     }
373
374
375     Rectangle2D getOrCreateBounds()
376     {
377         if (bounds==null)
378             bounds = transformRectangle(transform, rect);
379         return bounds;
380     }
381
382     static Rectangle2D transformRectangle(AffineTransform transform, Rectangle2D rect) {
383         double x0 = rect.getMinX();
384         double y0 = rect.getMinY();
385         double x1 = rect.getMaxX();
386         double y1 = rect.getMaxY();
387         double m00 = transform.getScaleX();
388         double m10 = transform.getShearY();
389         double m01 = transform.getShearX();
390         double m11 = transform.getScaleY();
391         double X0, Y0, X1, Y1;
392         if(m00 > 0.0) {
393             X0 = m00 * x0;
394             X1 = m00 * x1;
395         }
396         else {
397             X1 = m00 * x0;
398             X0 = m00 * x1;
399         }
400         if(m01 > 0.0) {
401             X0 += m01 * y0;
402             X1 += m01 * y1;
403         }
404         else {
405             X1 += m01 * y0;
406             X0 += m01 * y1;
407         }
408         if(m10 > 0.0) {
409             Y0 = m10 * x0;
410             Y1 = m10 * x1;
411         }
412         else {
413             Y1 = m10 * x0;
414             Y0 = m10 * x1;
415         }
416         if(m11 > 0.0) {
417             Y0 += m11 * y0;
418             Y1 += m11 * y1;
419         }
420         else {
421             Y1 += m11 * y0;
422             Y0 += m11 * y1;
423         }
424         return new Rectangle2D.Double(X0+transform.getTranslateX(),
425                 Y0+transform.getTranslateY(), X1-X0, Y1-Y0);
426     }
427
428     @Override
429     public String toString() {
430         Point2D p1 = new Point2D.Double(rect.getMinX(), rect.getMinY());
431         Point2D p2 = new Point2D.Double(rect.getMaxX(), rect.getMinY());
432         Point2D p3 = new Point2D.Double(rect.getMaxX(), rect.getMaxY());
433         Point2D p4 = new Point2D.Double(rect.getMinX(), rect.getMaxY());
434
435         transform.transform(p1, p1);
436         transform.transform(p2, p2);
437         transform.transform(p3, p3);
438         transform.transform(p4, p4);
439
440         StringBuilder sb = new StringBuilder();
441         sb.append(p1+"\n");
442         sb.append(p2+"\n");
443         sb.append(p3+"\n");
444         sb.append(p4);
445
446         return sb.toString();
447     }
448
449     // Test this class
450     // Run with VM arg: -ea
451     public static void main(String[] args) {
452         Rectangle2D rect = new Rectangle2D.Double(0, 0, 10, 10);
453         AffineTransform at = new AffineTransform();
454         at.setToRotation(Math.PI/4);
455         TransformedRectangle tr = new TransformedRectangle(rect, at);
456
457         Rectangle2D t1 = new Rectangle2D.Double(5, 5, 5, 5);
458         Rectangle2D t2 = new Rectangle2D.Double(-2, 4, 3, 2);
459         Rectangle2D t3 = new Rectangle2D.Double(9, 9, 5, 5);
460         Rectangle2D t4 = new Rectangle2D.Double(-100, -100, 200, 200);
461
462         // Contains test
463         assert(!tr.contains(t1));
464         assert( tr.contains(t2));
465         assert(!tr.contains(t3));
466         assert(!tr.contains(t4));
467
468         // Intersects test
469         assert( tr.intersects(t1));
470         assert( tr.intersects(t2));
471         assert(!tr.intersects(t3));
472         assert( tr.intersects(t4));
473
474         Ellipse2D e = new Ellipse2D.Double(-5, 0, 10, 10);
475         assert( tr.intersects(e) );
476         assert(!tr.contains(e) );
477
478         TransformedRectangle tr1 = new TransformedRectangle(t4, at);
479         TransformedRectangle tr2 = new TransformedRectangle(new Rectangle2D.Double(3, 3, 2, 2), at);
480         TransformedRectangle tr3 = new TransformedRectangle(new Rectangle2D.Double(-20, 3, 40, 3), at);
481         TransformedRectangle tr4 = new TransformedRectangle(new Rectangle2D.Double(8, -6, 4, 8), at);
482         TransformedRectangle tr5 = new TransformedRectangle(new Rectangle2D.Double(2, 12, 7, 7), at);
483
484         assert(!tr.contains(tr1) );
485         assert( tr.contains(tr2) );
486         assert(!tr.contains(tr3) );
487         assert(!tr.contains(tr4) );
488         assert(!tr.contains(tr5) );
489
490         assert( tr1.contains(tr) );
491         assert(!tr2.contains(tr) );
492         assert(!tr3.contains(tr) );
493         assert(!tr4.contains(tr) );
494         assert(!tr5.contains(tr) );
495
496         assert( tr.intersects(tr1) );
497         assert( tr.intersects(tr2) );
498         assert( tr.intersects(tr3) );
499         assert( tr.intersects(tr4) );
500         assert(!tr.intersects(tr5) );
501
502         assert(false);
503     }
504
505 }