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