X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.scenegraph%2Fsrc%2Forg%2Fsimantics%2Fscenegraph%2Futils%2FTransformedRectangle.java;fp=bundles%2Forg.simantics.scenegraph%2Fsrc%2Forg%2Fsimantics%2Fscenegraph%2Futils%2FTransformedRectangle.java;h=e729fa4d5b7b1f6f44e426819e67c8ff591ce323;hp=0000000000000000000000000000000000000000;hb=969bd23cab98a79ca9101af33334000879fb60c5;hpb=866dba5cd5a3929bbeae85991796acb212338a08 diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/TransformedRectangle.java b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/TransformedRectangle.java new file mode 100644 index 000000000..e729fa4d5 --- /dev/null +++ b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/TransformedRectangle.java @@ -0,0 +1,505 @@ +/******************************************************************************* + * 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); + } + +}