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
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.scenegraph.utils;
\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
25 * A rectangle that is transformed.
\r
27 * @see {@link GeometryUtils} Geometry-related tests for Shapes
\r
28 * @author Toni Kalajainen
\r
30 public class TransformedRectangle implements Shape, Serializable {
\r
32 private static final long serialVersionUID = 5160199078645323706L;
\r
34 Rectangle2D rect = new Rectangle2D.Double();
\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
45 transient Rectangle2D bounds = null;
\r
47 public TransformedRectangle() {}
\r
49 public TransformedRectangle(Rectangle2D rect) {
\r
51 setUntransformedRectangle(rect);
\r
54 public TransformedRectangle(Rectangle2D rect, AffineTransform transform) {
\r
56 setUntransformedRectangle(rect);
\r
57 if (transform!=null)
\r
58 setTransform(transform);
\r
61 public TransformedRectangle(TransformedRectangle src) {
\r
62 setUntransformedRectangle(src.rect);
\r
63 setTransform(src.transform);
\r
66 public Rectangle2D getUntransformedRectangle() {
\r
69 public void setUntransformedRectangle(Rectangle2D rect) {
\r
70 this.rect.setFrame(rect);
\r
73 public AffineTransform getTransform() {
\r
77 * Set the transform of the rectangle.
\r
78 * Transform transforms rectangle coordinates to external coordinates.
\r
80 * T = Rect -> Canvas
\r
84 public void setTransform(AffineTransform transform) {
\r
85 this.transform.setTransform(transform);
\r
87 isIdentity = transform.isIdentity();
\r
89 inv = transform.createInverse();
\r
90 } catch (NoninvertibleTransformException e) {
\r
91 throw new RuntimeException(e);
\r
95 public void concatenate(AffineTransform transform) {
\r
96 this.transform.concatenate(transform);
\r
99 inv = transform.createInverse();
\r
100 } catch (NoninvertibleTransformException e) {
\r
101 throw new RuntimeException(e);
\r
106 public boolean contains(Point2D p) {
\r
108 return rect.contains(p);
\r
110 Point2D newP = inv.transform(p, new Point2D.Double());
\r
111 return rect.contains(newP);
\r
114 public boolean contains(double x, double y) {
\r
116 return rect.contains(x, y);
\r
118 Point2D p = new Point2D.Double(x, y);
\r
119 inv.transform(p, p);
\r
120 return rect.contains(p);
\r
123 public boolean contains(Rectangle2D r) {
\r
125 return rect.contains(r);
\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
132 inv.transform(p1, p1);
\r
133 inv.transform(p2, p2);
\r
134 inv.transform(p3, p3);
\r
135 inv.transform(p4, p4);
\r
137 return rect.contains(p1) && rect.contains(p2) && rect.contains(p3) && rect.contains(p4);
\r
139 public boolean contains(TransformedRectangle r) {
\r
141 return r.intersects(rect);
\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
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
154 inv.transform(p1, p1);
\r
155 inv.transform(p2, p2);
\r
156 inv.transform(p3, p3);
\r
157 inv.transform(p4, p4);
\r
159 return rect.contains(p1) && rect.contains(p2) && rect.contains(p3) && rect.contains(p4);
\r
162 * Tests if the rectangle completely contains the specified shape.
\r
164 * NOTE This test simplifies curves to straight edges.
\r
169 public boolean contains(Shape s) {
\r
170 return contains(s, Double.MAX_VALUE);
\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
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
185 if (!rect.contains(coords[0], coords[1]))
\r
188 assert(type != PathIterator.SEG_CUBICTO && type != PathIterator.SEG_QUADTO);
\r
195 public boolean contains(double x, double y, double w, double h) {
\r
197 return rect.contains(x, y, w, h);
\r
199 return contains(new Rectangle2D.Double(x, y, w, h));
\r
204 * NOTE This test simplifies curves to straight edges.
\r
209 public boolean intersects(Shape s) {
\r
210 return intersects(s, Double.MAX_VALUE);
\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
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
226 pos[0] = coords[0];
\r
227 pos[1] = coords[1];
\r
230 if (type == PathIterator.SEG_LINETO)
\r
234 assert(type != PathIterator.SEG_CUBICTO && type != PathIterator.SEG_QUADTO);
\r
240 public boolean intersects(TransformedRectangle r) {
\r
242 return r.intersects(rect);
\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
250 AffineTransform at = new AffineTransform(transform);
\r
251 at.concatenate(r.inv);
\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
258 if (r.rect.contains(p1) && r.rect.contains(p2) && r.rect.contains(p3) && r.rect.contains(p4))
\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
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
272 at = new AffineTransform(r.transform);
\r
273 at.concatenate(inv);
\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
280 if (rect.contains(p1) && rect.contains(p2) && rect.contains(p3) && rect.contains(p4))
\r
287 public boolean intersects(Rectangle2D r) {
\r
289 return rect.intersects(r);
\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
296 inv.transform(p1, p1);
\r
297 inv.transform(p2, p2);
\r
298 inv.transform(p3, p3);
\r
299 inv.transform(p4, p4);
\r
301 if (rect.contains(p1) || rect.contains(p2) || rect.contains(p3) || rect.contains(p4))
\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
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
315 transform.transform(p1, p1);
\r
316 transform.transform(p2, p2);
\r
317 transform.transform(p3, p3);
\r
318 transform.transform(p4, p4);
\r
320 if (r.contains(p1) || r.contains(p2) || r.contains(p3) || r.contains(p4))
\r
326 public boolean intersects(double x, double y, double w, double h) {
\r
328 return rect.intersects(x, y, w, h);
\r
330 return intersects(new Rectangle2D.Double(x, y, w, h));
\r
334 public Rectangle getBounds() {
\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
346 public Rectangle2D getBounds2D() {
\r
347 if (isIdentity) return rect;
\r
348 return getOrCreateBounds();
\r
351 public PathIterator getPathIterator(AffineTransform at) {
\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
363 public PathIterator getPathIterator(AffineTransform at, double flatness) {
\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
375 Rectangle2D getOrCreateBounds()
\r
378 bounds = transformRectangle(transform, rect);
\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
424 return new Rectangle2D.Double(X0+transform.getTranslateX(),
\r
425 Y0+transform.getTranslateY(), X1-X0, Y1-Y0);
\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
435 transform.transform(p1, p1);
\r
436 transform.transform(p2, p2);
\r
437 transform.transform(p3, p3);
\r
438 transform.transform(p4, p4);
\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
446 return sb.toString();
\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
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
463 assert(!tr.contains(t1));
\r
464 assert( tr.contains(t2));
\r
465 assert(!tr.contains(t3));
\r
466 assert(!tr.contains(t4));
\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
474 Ellipse2D e = new Ellipse2D.Double(-5, 0, 10, 10);
\r
475 assert( tr.intersects(e) );
\r
476 assert(!tr.contains(e) );
\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
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
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
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