--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.scenegraph.utils;\r
+\r
+import java.awt.Rectangle;\r
+import java.awt.Shape;\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.Ellipse2D;\r
+import java.awt.geom.NoninvertibleTransformException;\r
+import java.awt.geom.PathIterator;\r
+import java.awt.geom.Point2D;\r
+import java.awt.geom.Rectangle2D;\r
+import java.io.Serializable;\r
+\r
+/**\r
+ * A rectangle that is transformed.\r
+ * \r
+ * @see {@link GeometryUtils} Geometry-related tests for Shapes\r
+ * @author Toni Kalajainen\r
+ */\r
+public class TransformedRectangle implements Shape, Serializable {\r
+\r
+ private static final long serialVersionUID = 5160199078645323706L;\r
+\r
+ Rectangle2D rect = new Rectangle2D.Double();\r
+\r
+ // T(rect) -> Canvas\r
+ AffineTransform transform = new AffineTransform();\r
+ // T(canvas) -> Rect\r
+ AffineTransform inv = new AffineTransform();\r
+ boolean isIdentity = true;\r
+\r
+ /**\r
+ * Cache\r
+ */\r
+ transient Rectangle2D bounds = null;\r
+\r
+ public TransformedRectangle() {}\r
+\r
+ public TransformedRectangle(Rectangle2D rect) {\r
+ if (rect!=null)\r
+ setUntransformedRectangle(rect);\r
+ }\r
+\r
+ public TransformedRectangle(Rectangle2D rect, AffineTransform transform) {\r
+ if (rect!=null)\r
+ setUntransformedRectangle(rect);\r
+ if (transform!=null)\r
+ setTransform(transform);\r
+ }\r
+\r
+ public TransformedRectangle(TransformedRectangle src) {\r
+ setUntransformedRectangle(src.rect);\r
+ setTransform(src.transform);\r
+ }\r
+\r
+ public Rectangle2D getUntransformedRectangle() {\r
+ return rect;\r
+ }\r
+ public void setUntransformedRectangle(Rectangle2D rect) {\r
+ this.rect.setFrame(rect);\r
+ bounds = null;\r
+ }\r
+ public AffineTransform getTransform() {\r
+ return transform;\r
+ }\r
+ /**\r
+ * Set the transform of the rectangle.\r
+ * Transform transforms rectangle coordinates to external coordinates.\r
+ * \r
+ * T = Rect -> Canvas\r
+ * \r
+ * @param transform\r
+ */\r
+ public void setTransform(AffineTransform transform) {\r
+ this.transform.setTransform(transform);\r
+ bounds = null;\r
+ isIdentity = transform.isIdentity();\r
+ try {\r
+ inv = transform.createInverse();\r
+ } catch (NoninvertibleTransformException e) {\r
+ throw new RuntimeException(e);\r
+ }\r
+ }\r
+\r
+ public void concatenate(AffineTransform transform) {\r
+ this.transform.concatenate(transform);\r
+ bounds = null;\r
+ try {\r
+ inv = transform.createInverse();\r
+ } catch (NoninvertibleTransformException e) {\r
+ throw new RuntimeException(e);\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public boolean contains(Point2D p) {\r
+ if (isIdentity)\r
+ return rect.contains(p);\r
+\r
+ Point2D newP = inv.transform(p, new Point2D.Double());\r
+ return rect.contains(newP);\r
+ }\r
+ @Override\r
+ public boolean contains(double x, double y) {\r
+ if (isIdentity)\r
+ return rect.contains(x, y);\r
+\r
+ Point2D p = new Point2D.Double(x, y);\r
+ inv.transform(p, p);\r
+ return rect.contains(p);\r
+ }\r
+ @Override\r
+ public boolean contains(Rectangle2D r) {\r
+ if (isIdentity)\r
+ return rect.contains(r);\r
+\r
+ Point2D p1 = new Point2D.Double(r.getMinX(), r.getMinY());\r
+ Point2D p2 = new Point2D.Double(r.getMaxX(), r.getMinY());\r
+ Point2D p3 = new Point2D.Double(r.getMaxX(), r.getMaxY());\r
+ Point2D p4 = new Point2D.Double(r.getMinX(), r.getMaxY());\r
+\r
+ inv.transform(p1, p1);\r
+ inv.transform(p2, p2);\r
+ inv.transform(p3, p3);\r
+ inv.transform(p4, p4);\r
+\r
+ return rect.contains(p1) && rect.contains(p2) && rect.contains(p3) && rect.contains(p4);\r
+ }\r
+ public boolean contains(TransformedRectangle r) {\r
+ if (isIdentity)\r
+ return r.intersects(rect);\r
+\r
+ // Convert points of r to rect\r
+ Point2D p1 = new Point2D.Double(r.rect.getMinX(), r.rect.getMinY());\r
+ Point2D p2 = new Point2D.Double(r.rect.getMaxX(), r.rect.getMinY());\r
+ Point2D p3 = new Point2D.Double(r.rect.getMaxX(), r.rect.getMaxY());\r
+ Point2D p4 = new Point2D.Double(r.rect.getMinX(), r.rect.getMaxY());\r
+\r
+ r.transform.transform(p1, p1);\r
+ r.transform.transform(p2, p2);\r
+ r.transform.transform(p3, p3);\r
+ r.transform.transform(p4, p4);\r
+\r
+ inv.transform(p1, p1);\r
+ inv.transform(p2, p2);\r
+ inv.transform(p3, p3);\r
+ inv.transform(p4, p4);\r
+\r
+ return rect.contains(p1) && rect.contains(p2) && rect.contains(p3) && rect.contains(p4);\r
+ }\r
+ /**\r
+ * Tests if the rectangle completely contains the specified shape.\r
+ * \r
+ * NOTE This test simplifies curves to straight edges.\r
+ * \r
+ * @param s\r
+ * @return\r
+ */\r
+ public boolean contains(Shape s) {\r
+ return contains(s, Double.MAX_VALUE);\r
+ }\r
+ public boolean contains(Shape s, double flatness) {\r
+ if (s instanceof Rectangle2D)\r
+ return contains((Rectangle2D)s);\r
+ if (s instanceof TransformedRectangle)\r
+ return contains( (TransformedRectangle) s);\r
+\r
+ // rectangle contains s if all points of s are in side rect\r
+ PathIterator pi = s.getPathIterator(inv, flatness);\r
+ double coords[] = new double[6];\r
+ while (!pi.isDone()) {\r
+ int type = pi.currentSegment(coords);\r
+ if (type == PathIterator.SEG_MOVETO || type == PathIterator.SEG_LINETO)\r
+ {\r
+ if (!rect.contains(coords[0], coords[1]))\r
+ return false;\r
+ }\r
+ assert(type != PathIterator.SEG_CUBICTO && type != PathIterator.SEG_QUADTO);\r
+ pi.next();\r
+ }\r
+ return true;\r
+ }\r
+\r
+ @Override\r
+ public boolean contains(double x, double y, double w, double h) {\r
+ if (isIdentity)\r
+ return rect.contains(x, y, w, h);\r
+\r
+ return contains(new Rectangle2D.Double(x, y, w, h));\r
+ }\r
+\r
+ /**\r
+ * \r
+ * NOTE This test simplifies curves to straight edges.\r
+ * \r
+ * @param s\r
+ * @return\r
+ */\r
+ public boolean intersects(Shape s) {\r
+ return intersects(s, Double.MAX_VALUE);\r
+ }\r
+\r
+ public boolean intersects(Shape s, double flatness) {\r
+ if (s instanceof Rectangle2D)\r
+ return intersects((Rectangle2D)s);\r
+ if (s instanceof TransformedRectangle)\r
+ return intersects( (TransformedRectangle) s);\r
+\r
+ PathIterator pi = s.getPathIterator(inv, flatness);\r
+ double pos[] = new double[2];\r
+ double coords[] = new double[6];\r
+ while (!pi.isDone()) {\r
+ int type = pi.currentSegment(coords);\r
+ if (type == PathIterator.SEG_MOVETO)\r
+ {\r
+ pos[0] = coords[0];\r
+ pos[1] = coords[1];\r
+ }\r
+ else\r
+ if (type == PathIterator.SEG_LINETO)\r
+ {\r
+\r
+ }\r
+ assert(type != PathIterator.SEG_CUBICTO && type != PathIterator.SEG_QUADTO);\r
+ pi.next();\r
+ }\r
+ return true;\r
+ }\r
+\r
+ public boolean intersects(TransformedRectangle r) {\r
+ if (isIdentity)\r
+ return r.intersects(rect);\r
+\r
+ // Convert points of r to rect\r
+ Point2D p1 = new Point2D.Double(rect.getMinX(), rect.getMinY());\r
+ Point2D p2 = new Point2D.Double(rect.getMaxX(), rect.getMinY());\r
+ Point2D p3 = new Point2D.Double(rect.getMaxX(), rect.getMaxY());\r
+ Point2D p4 = new Point2D.Double(rect.getMinX(), rect.getMaxY());\r
+\r
+ AffineTransform at = new AffineTransform(transform);\r
+ at.concatenate(r.inv);\r
+\r
+ at.transform(p1, p1);\r
+ at.transform(p2, p2);\r
+ at.transform(p3, p3);\r
+ at.transform(p4, p4);\r
+\r
+ if (r.rect.contains(p1) && r.rect.contains(p2) && r.rect.contains(p3) && r.rect.contains(p4))\r
+ return true;\r
+\r
+ if (r.rect.intersectsLine(p1.getX(), p1.getY(), p2.getX(), p2.getY()) ||\r
+ r.rect.intersectsLine(p2.getX(), p2.getY(), p3.getX(), p3.getY()) ||\r
+ r.rect.intersectsLine(p3.getX(), p3.getY(), p4.getX(), p4.getY()) ||\r
+ r.rect.intersectsLine(p4.getX(), p4.getY(), p1.getX(), p1.getY()))\r
+ return true;\r
+\r
+ p1 = new Point2D.Double(r.rect.getMinX(), r.rect.getMinY());\r
+ p2 = new Point2D.Double(r.rect.getMaxX(), r.rect.getMinY());\r
+ p3 = new Point2D.Double(r.rect.getMaxX(), r.rect.getMaxY());\r
+ p4 = new Point2D.Double(r.rect.getMinX(), r.rect.getMaxY());\r
+\r
+ at = new AffineTransform(r.transform);\r
+ at.concatenate(inv);\r
+\r
+ at.transform(p1, p1);\r
+ at.transform(p2, p2);\r
+ at.transform(p3, p3);\r
+ at.transform(p4, p4);\r
+\r
+ if (rect.contains(p1) && rect.contains(p2) && rect.contains(p3) && rect.contains(p4))\r
+ return true;\r
+\r
+\r
+ return false;\r
+ }\r
+ @Override\r
+ public boolean intersects(Rectangle2D r) {\r
+ if (isIdentity)\r
+ return rect.intersects(r);\r
+\r
+ Point2D p1 = new Point2D.Double(r.getMinX(), r.getMinY());\r
+ Point2D p2 = new Point2D.Double(r.getMaxX(), r.getMinY());\r
+ Point2D p3 = new Point2D.Double(r.getMaxX(), r.getMaxY());\r
+ Point2D p4 = new Point2D.Double(r.getMinX(), r.getMaxY());\r
+\r
+ inv.transform(p1, p1);\r
+ inv.transform(p2, p2);\r
+ inv.transform(p3, p3);\r
+ inv.transform(p4, p4);\r
+\r
+ if (rect.contains(p1) || rect.contains(p2) || rect.contains(p3) || rect.contains(p4))\r
+ return true;\r
+\r
+ if (rect.intersectsLine(p1.getX(), p1.getY(), p2.getX(), p2.getY()) ||\r
+ rect.intersectsLine(p2.getX(), p2.getY(), p3.getX(), p3.getY()) ||\r
+ rect.intersectsLine(p3.getX(), p3.getY(), p4.getX(), p4.getY()) ||\r
+ rect.intersectsLine(p4.getX(), p4.getY(), p1.getX(), p1.getY()))\r
+ return true;\r
+\r
+ p1 = new Point2D.Double(rect.getMinX(), rect.getMinY());\r
+ p2 = new Point2D.Double(rect.getMaxX(), rect.getMinY());\r
+ p3 = new Point2D.Double(rect.getMaxX(), rect.getMaxY());\r
+ p4 = new Point2D.Double(rect.getMinX(), rect.getMaxY());\r
+\r
+ transform.transform(p1, p1);\r
+ transform.transform(p2, p2);\r
+ transform.transform(p3, p3);\r
+ transform.transform(p4, p4);\r
+\r
+ if (r.contains(p1) || r.contains(p2) || r.contains(p3) || r.contains(p4))\r
+ return true;\r
+\r
+ return false;\r
+ }\r
+ @Override\r
+ public boolean intersects(double x, double y, double w, double h) {\r
+ if (isIdentity)\r
+ return rect.intersects(x, y, w, h);\r
+\r
+ return intersects(new Rectangle2D.Double(x, y, w, h));\r
+ }\r
+\r
+ @Override\r
+ public Rectangle getBounds() {\r
+ if (isIdentity)\r
+ return rect.getBounds();\r
+ Rectangle2D b = getOrCreateBounds();\r
+ return new Rectangle(\r
+ (int) Math.floor(b.getMinX()),\r
+ (int) Math.floor(b.getMinY()),\r
+ (int) Math.ceil(b.getWidth()),\r
+ (int) Math.ceil(b.getHeight())\r
+ );\r
+ }\r
+ @Override\r
+ public Rectangle2D getBounds2D() {\r
+ if (isIdentity) return rect;\r
+ return getOrCreateBounds();\r
+ }\r
+ @Override\r
+ public PathIterator getPathIterator(AffineTransform at) {\r
+ if (isIdentity)\r
+ return rect.getPathIterator(at);\r
+ if (at == null || at.isIdentity())\r
+ return rect.getPathIterator(transform);\r
+ // Concatenate both iterators\r
+ // IS THIS ORDER CORRECT?! UNTESTED\r
+ AffineTransform con = new AffineTransform(transform);\r
+ con.preConcatenate(at);\r
+ return rect.getPathIterator(con);\r
+ }\r
+ @Override\r
+ public PathIterator getPathIterator(AffineTransform at, double flatness) {\r
+ if (isIdentity)\r
+ return rect.getPathIterator(at, flatness);\r
+ if (at == null || at.isIdentity())\r
+ return rect.getPathIterator(transform, flatness);\r
+ // Concatenate both iterators\r
+ AffineTransform con = new AffineTransform(transform);\r
+ con.concatenate(at);\r
+ return rect.getPathIterator(con, flatness);\r
+ }\r
+\r
+\r
+ Rectangle2D getOrCreateBounds()\r
+ {\r
+ if (bounds==null)\r
+ bounds = transformRectangle(transform, rect);\r
+ return bounds;\r
+ }\r
+\r
+ static Rectangle2D transformRectangle(AffineTransform transform, Rectangle2D rect) {\r
+ double x0 = rect.getMinX();\r
+ double y0 = rect.getMinY();\r
+ double x1 = rect.getMaxX();\r
+ double y1 = rect.getMaxY();\r
+ double m00 = transform.getScaleX();\r
+ double m10 = transform.getShearY();\r
+ double m01 = transform.getShearX();\r
+ double m11 = transform.getScaleY();\r
+ double X0, Y0, X1, Y1;\r
+ if(m00 > 0.0) {\r
+ X0 = m00 * x0;\r
+ X1 = m00 * x1;\r
+ }\r
+ else {\r
+ X1 = m00 * x0;\r
+ X0 = m00 * x1;\r
+ }\r
+ if(m01 > 0.0) {\r
+ X0 += m01 * y0;\r
+ X1 += m01 * y1;\r
+ }\r
+ else {\r
+ X1 += m01 * y0;\r
+ X0 += m01 * y1;\r
+ }\r
+ if(m10 > 0.0) {\r
+ Y0 = m10 * x0;\r
+ Y1 = m10 * x1;\r
+ }\r
+ else {\r
+ Y1 = m10 * x0;\r
+ Y0 = m10 * x1;\r
+ }\r
+ if(m11 > 0.0) {\r
+ Y0 += m11 * y0;\r
+ Y1 += m11 * y1;\r
+ }\r
+ else {\r
+ Y1 += m11 * y0;\r
+ Y0 += m11 * y1;\r
+ }\r
+ return new Rectangle2D.Double(X0+transform.getTranslateX(),\r
+ Y0+transform.getTranslateY(), X1-X0, Y1-Y0);\r
+ }\r
+\r
+ @Override\r
+ public String toString() {\r
+ Point2D p1 = new Point2D.Double(rect.getMinX(), rect.getMinY());\r
+ Point2D p2 = new Point2D.Double(rect.getMaxX(), rect.getMinY());\r
+ Point2D p3 = new Point2D.Double(rect.getMaxX(), rect.getMaxY());\r
+ Point2D p4 = new Point2D.Double(rect.getMinX(), rect.getMaxY());\r
+\r
+ transform.transform(p1, p1);\r
+ transform.transform(p2, p2);\r
+ transform.transform(p3, p3);\r
+ transform.transform(p4, p4);\r
+\r
+ StringBuilder sb = new StringBuilder();\r
+ sb.append(p1+"\n");\r
+ sb.append(p2+"\n");\r
+ sb.append(p3+"\n");\r
+ sb.append(p4);\r
+\r
+ return sb.toString();\r
+ }\r
+\r
+ // Test this class\r
+ // Run with VM arg: -ea\r
+ public static void main(String[] args) {\r
+ Rectangle2D rect = new Rectangle2D.Double(0, 0, 10, 10);\r
+ AffineTransform at = new AffineTransform();\r
+ at.setToRotation(Math.PI/4);\r
+ TransformedRectangle tr = new TransformedRectangle(rect, at);\r
+\r
+ Rectangle2D t1 = new Rectangle2D.Double(5, 5, 5, 5);\r
+ Rectangle2D t2 = new Rectangle2D.Double(-2, 4, 3, 2);\r
+ Rectangle2D t3 = new Rectangle2D.Double(9, 9, 5, 5);\r
+ Rectangle2D t4 = new Rectangle2D.Double(-100, -100, 200, 200);\r
+\r
+ // Contains test\r
+ assert(!tr.contains(t1));\r
+ assert( tr.contains(t2));\r
+ assert(!tr.contains(t3));\r
+ assert(!tr.contains(t4));\r
+\r
+ // Intersects test\r
+ assert( tr.intersects(t1));\r
+ assert( tr.intersects(t2));\r
+ assert(!tr.intersects(t3));\r
+ assert( tr.intersects(t4));\r
+\r
+ Ellipse2D e = new Ellipse2D.Double(-5, 0, 10, 10);\r
+ assert( tr.intersects(e) );\r
+ assert(!tr.contains(e) );\r
+\r
+ TransformedRectangle tr1 = new TransformedRectangle(t4, at);\r
+ TransformedRectangle tr2 = new TransformedRectangle(new Rectangle2D.Double(3, 3, 2, 2), at);\r
+ TransformedRectangle tr3 = new TransformedRectangle(new Rectangle2D.Double(-20, 3, 40, 3), at);\r
+ TransformedRectangle tr4 = new TransformedRectangle(new Rectangle2D.Double(8, -6, 4, 8), at);\r
+ TransformedRectangle tr5 = new TransformedRectangle(new Rectangle2D.Double(2, 12, 7, 7), at);\r
+\r
+ assert(!tr.contains(tr1) );\r
+ assert( tr.contains(tr2) );\r
+ assert(!tr.contains(tr3) );\r
+ assert(!tr.contains(tr4) );\r
+ assert(!tr.contains(tr5) );\r
+\r
+ assert( tr1.contains(tr) );\r
+ assert(!tr2.contains(tr) );\r
+ assert(!tr3.contains(tr) );\r
+ assert(!tr4.contains(tr) );\r
+ assert(!tr5.contains(tr) );\r
+\r
+ assert( tr.intersects(tr1) );\r
+ assert( tr.intersects(tr2) );\r
+ assert( tr.intersects(tr3) );\r
+ assert( tr.intersects(tr4) );\r
+ assert(!tr.intersects(tr5) );\r
+\r
+ assert(false);\r
+ }\r
+\r
+}\r