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