--- /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.g2d.utils;\r
+\r
+import java.awt.BasicStroke;\r
+import java.awt.Color;\r
+import java.awt.Polygon;\r
+import java.awt.Shape;\r
+import java.awt.Stroke;\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.Area;\r
+import java.awt.geom.Ellipse2D;\r
+import java.awt.geom.Path2D;\r
+import java.awt.geom.PathIterator;\r
+import java.awt.geom.Point2D;\r
+import java.awt.geom.Rectangle2D;\r
+import java.util.List;\r
+\r
+import org.simantics.scenegraph.utils.TransformedRectangle;\r
+\r
+/**\r
+ * Geometry related Utilities\r
+ *\r
+ * @see PathUtils2 Path related utilities\r
+ * \r
+ * @author Toni Kalajainen\r
+ * @author Tuukka Lehtonen\r
+ */\r
+public final class GeometryUtils {\r
+\r
+ //public static final double EPSILON = 1e-7;\r
+\r
+ public static final BasicStroke BASIC_STROKE = new BasicStroke();\r
+\r
+ public static BasicStroke scaleStroke(Stroke stroke, float factor)\r
+ {\r
+ BasicStroke s = (BasicStroke) stroke;\r
+ float[] dash = s.getDashArray();\r
+ if (dash!=null) {\r
+ assert(factor!=0);\r
+ dash = scaleArray(factor, dash, new float[dash.length]);\r
+ }\r
+ if (dash==null)\r
+ return new BasicStroke(\r
+ s.getLineWidth() * factor,\r
+ s.getEndCap(),\r
+ s.getLineJoin(),\r
+ s.getMiterLimit()\r
+ );\r
+ return new BasicStroke(\r
+ s.getLineWidth() * factor,\r
+ s.getEndCap(),\r
+ s.getLineJoin(),\r
+ s.getMiterLimit(),\r
+ dash,\r
+ s.getDashPhase() * factor\r
+ );\r
+ }\r
+\r
+\r
+\r
+ /**\r
+ * Scales every element in array\r
+ * @param array\r
+ * @param factor\r
+ * @return new scaled array\r
+ */\r
+ public static float[] scaleArray(float factor, float [] array, float [] targetArray)\r
+ {\r
+ assert(array!=null);\r
+ if (targetArray==null)\r
+ targetArray = new float[array.length];\r
+ for (int i=0; i<array.length; i++)\r
+ {\r
+ targetArray[i] = array[i] * factor;\r
+ }\r
+ return targetArray;\r
+ }\r
+\r
+ /**\r
+ * Get x/y scale of a transform\r
+ * @param at\r
+ * @param pt\r
+ * @return\r
+ */\r
+ public static Point2D getScaleXY(AffineTransform at, Point2D pt)\r
+ {\r
+ double m00 = at.getScaleX();\r
+ double m11 = at.getScaleY();\r
+ double m10 = at.getShearY();\r
+ double m01 = at.getShearX();\r
+ // Project unit vector to canvas\r
+ double sx = Math.sqrt( m00*m00+m10*m10 );\r
+ double sy = Math.sqrt( m01*m01+m11*m11 );\r
+ if (pt==null) pt = new Point2D.Double();\r
+ pt.setLocation(sx, sy);\r
+ return pt;\r
+ }\r
+\r
+ /**\r
+ * Get scale of a transform\r
+ * @param at\r
+ * @param pt\r
+ * @return\r
+ */\r
+ /*\r
+ public static double getScale(AffineTransform at)\r
+ {\r
+ double m00 = at.getScaleX();\r
+ double m11 = at.getScaleY();\r
+ double m10 = at.getShearY();\r
+ double m01 = at.getShearX();\r
+ // Project unit vector to canvas\r
+ double sx = Math.sqrt( m00*m00+m10*m10 );\r
+ double sy = Math.sqrt( m01*m01+m11*m11 );\r
+ return Math.sqrt(sx*sx+sy*sy);\r
+ } */\r
+\r
+ /*\r
+ public static double getScale(AffineTransform at)\r
+ {\r
+ double m00 = at.getScaleX();\r
+ double m11 = at.getScaleY();\r
+ double m10 = at.getShearY();\r
+ double m01 = at.getShearX();\r
+\r
+ double a = m00 + m11;\r
+ double b = 4.0 * m10*m01 + (m00 - m11) * (m00 - m11);\r
+ if(b <= 0.0)\r
+ return 0.5 * Math.sqrt(a*a + b);\r
+ else\r
+ return 0.5 * (Math.abs(a) + Math.sqrt(b));\r
+ }*/\r
+\r
+ public static double getScale(AffineTransform at)\r
+ {\r
+ double m00 = at.getScaleX();\r
+ double m11 = at.getScaleY();\r
+ double m10 = at.getShearY();\r
+ double m01 = at.getShearX();\r
+\r
+ return Math.sqrt(Math.abs(m00*m11 - m10*m01));\r
+ }\r
+\r
+ /**\r
+ * Computes the greatest absolute value of the eigenvalues\r
+ * of the matrix.\r
+ */\r
+ public static double getMaxScale(AffineTransform at)\r
+ {\r
+ double m00 = at.getScaleX();\r
+ double m11 = at.getScaleY();\r
+ double m10 = at.getShearY();\r
+ double m01 = at.getShearX();\r
+\r
+ /*\r
+ * If a and b are the eigenvalues of the matrix,\r
+ * then\r
+ * trace = a + b\r
+ * determinant = a*b\r
+ */\r
+ double trace = m00 + m11;\r
+ double determinant = m00*m11 - m10*m01;\r
+\r
+ double dd = trace*trace*0.25 - determinant;\r
+ if(dd >= 0.0) {\r
+ /*\r
+ * trace/2 +- sqrt(trace^2 / 4 - determinant)\r
+ * = (a+b)/2 +- sqrt((a+b)^2 / 4 - a b)\r
+ * = (a+b)/2 +- sqrt(a^2 / 4 + a b / 2 + b^2 / 4 - a b)\r
+ * = (a+b)/2 +- sqrt(a^2 / 4 - a b / 2 + b^2 / 4)\r
+ * = (a+b)/2 +- (a-b)/2\r
+ * = a or b\r
+ * \r
+ * Thus the formula below calculates the greatest\r
+ * absolute value of the eigenvalues max(abs(a), abs(b))\r
+ */\r
+ return Math.abs(trace*0.5) + Math.sqrt(dd);\r
+ }\r
+ else {\r
+ /*\r
+ * If dd < 0, then the eigenvalues a and b are not real.\r
+ * Because both trace and determinant are real, a and b\r
+ * have form:\r
+ * a = x + i y\r
+ * b = x - i y\r
+ * \r
+ * Then\r
+ * sqrt(determinant)\r
+ * = sqrt(a b)\r
+ * = sqrt(x^2 + y^2),\r
+ * which is the absolute value of the eigenvalues.\r
+ */\r
+ return Math.sqrt(determinant);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Intersect test of two shapes\r
+ * @param s1\r
+ * @param s2\r
+ * @return\r
+ */\r
+ public static boolean intersects(Shape s1, Shape s2)\r
+ {\r
+ if (s1==s2) return true;\r
+ if (s1.equals(s2)) return true;\r
+ if (s1 instanceof Rectangle2D)\r
+ return s2.intersects((Rectangle2D) s1);\r
+ if (s2 instanceof Rectangle2D)\r
+ return s1.intersects((Rectangle2D) s2);\r
+ if (s1 instanceof TransformedRectangle)\r
+ return ((TransformedRectangle)s1).intersects(s2);\r
+ if (s2 instanceof TransformedRectangle)\r
+ return ((TransformedRectangle)s2).intersects(s1);\r
+\r
+ // VERY SLOW IMPLEMENTATION\r
+ // Convert shapes to areas and intersect them\r
+ Area a1 = new Area(s1);\r
+ Area a2 = new Area(s2);\r
+ a1.intersect(a2);\r
+ return !a1.isEmpty();\r
+ }\r
+\r
+ /**\r
+ * Contains test of two shapes\r
+ * @param s1\r
+ * @param s2\r
+ * @return <code>true</code> if s1 contains s2, else <code>false</code>\r
+ */\r
+ public static boolean contains(Shape s1, Shape s2)\r
+ {\r
+ if (s1==s2) return true;\r
+ if (s1.equals(s2)) return true;\r
+ if (s2 instanceof Rectangle2D)\r
+ return s1.contains((Rectangle2D) s2);\r
+ if (s1 instanceof Rectangle2D)\r
+ return contains((Rectangle2D)s1, s2);\r
+ if (s1 instanceof TransformedRectangle)\r
+ return ((TransformedRectangle)s1).contains(s2);\r
+\r
+ // VERY SLOW IMPLEMENTATION\r
+ // Convert shapes to areas and intersect them\r
+ Area a1 = new Area(s1);\r
+ Area a2 = new Area(s2);\r
+ a2.subtract(a1);\r
+ return a2.isEmpty();\r
+ }\r
+\r
+ /**\r
+ * Tests if rectangle contains a shape\r
+ * @param r rectangle\r
+ * @param s shape to be tested\r
+ * @return <code>true</code> if r contains s, else <code>false</code>\r
+ */\r
+ public static boolean contains(Rectangle2D r, Shape s)\r
+ {\r
+ // Rectangle contains a shape if\r
+ // all points of the shape are contained in r\r
+ PathIterator pi = s.getPathIterator(null, Double.MAX_VALUE);\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 (!r.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
+ /**\r
+ * Creates new transformed shape\r
+ * @param s shape to be transformed\r
+ * @param t transform\r
+ * @return new transformed shape\r
+ */\r
+ public static Shape transformShape(Shape s, AffineTransform t)\r
+ {\r
+ if (t.isIdentity()) return s;\r
+ if (s instanceof Rectangle2D) {\r
+ Rectangle2D rect = (Rectangle2D) s;\r
+ int type = t.getType();\r
+ if (type == AffineTransform.TYPE_IDENTITY) {\r
+ Rectangle2D r = new Rectangle2D.Double();\r
+ r.setFrame(rect);\r
+ return r;\r
+ }\r
+ if ((type & (AffineTransform.TYPE_GENERAL_ROTATION|AffineTransform.TYPE_GENERAL_TRANSFORM) ) != 0)\r
+ return new TransformedRectangle(rect, t);\r
+ return org.simantics.scenegraph.utils.GeometryUtils.transformRectangle(t, rect);\r
+ }\r
+ if (s instanceof TransformedRectangle)\r
+ {\r
+ TransformedRectangle tr = (TransformedRectangle) s;\r
+ TransformedRectangle result = new TransformedRectangle(tr);\r
+ result.concatenate(t);\r
+ return result;\r
+ }\r
+\r
+ //return t.createTransformedShape(s);\r
+ //return new Area(s).createTransformedArea(t);\r
+ Area result = new Area(s);\r
+ result.transform(t);\r
+ return result;\r
+ }\r
+\r
+ /**\r
+ * Get compass direction. 0 = north, clockwise\r
+ * @param pt\r
+ * @return\r
+ */\r
+ public static double getCompassDirection(Point2D pt) {\r
+ return getCompassDirection(pt.getX(), pt.getY());\r
+ }\r
+\r
+ /**\r
+ * Get compass direction for the vector p1→p2. 0 = north, clockwise\r
+ * \r
+ * @param p1 the start of the vector\r
+ * @param p2 the end of the vector\r
+ * @return compass direction\r
+ */\r
+ public static double getCompassDirection(Point2D p1, Point2D p2) {\r
+ double dx = p2.getX() - p1.getX();\r
+ double dy = p2.getY() - p1.getY();\r
+ return getCompassDirection(dx, dy);\r
+ }\r
+\r
+ /**\r
+ * Get compass direction. 0 = north, clockwise\r
+ * @param pt\r
+ * @return\r
+ */\r
+ public static double getCompassDirection(double x, double y) {\r
+ double rad = Math.atan2(y, x);\r
+ double deg = rad*180.0 / Math.PI + 90.0;\r
+ if (deg<0) deg = 360+deg;\r
+ return deg;\r
+ }\r
+\r
+ /**\r
+ * Converts compass direction to unit vector\r
+ * @param deg compass direction\r
+ * @param uv\r
+ * @return\r
+ */\r
+ public static Point2D toUnitVector(double deg, Point2D uv)\r
+ {\r
+ if (uv==null) uv = new Point2D.Double();\r
+ double x=0, y=0;\r
+ if (deg==0) {\r
+ y=-1;\r
+ } else if (deg==90) {\r
+ x=1;\r
+ } else if (deg==180) {\r
+ y=1;\r
+ } else if (deg==270) {\r
+ x=-1;\r
+ } else {\r
+ double rad = (deg-90)*Math.PI/180.0;\r
+ y = Math.sin(rad);\r
+ x = Math.cos(rad);\r
+ }\r
+ uv.setLocation(x, y);\r
+ return uv;\r
+ }\r
+\r
+ /**\r
+ * Convert compass direction to radian presentation (0=right, CCW)\r
+ * @param deg\r
+ * @return radians\r
+ */\r
+ public static double compassToRad(double deg) {\r
+ double rad = (deg-90)*Math.PI/180.0;\r
+ return rad;\r
+\r
+ }\r
+\r
+ /**\r
+ * Interpolate between two colors\r
+ * @param c1\r
+ * @param c2\r
+ * @param phase 0..1 (0=c1, 1=c2)\r
+ * @return\r
+ */\r
+ public static Color interpolate(Color c1, Color c2, double phase)\r
+ {\r
+ float r = (c1.getRed()/255.0f)*(1-(float)phase) + (c2.getRed()/255.0f)*((float)phase);\r
+ float g = (c1.getGreen()/255.0f)*(1-(float)phase) + (c2.getGreen()/255.0f)*((float)phase);\r
+ float b = (c1.getBlue()/255.0f)*(1-(float)phase) + (c2.getBlue()/255.0f)*((float)phase);\r
+ float a = (c1.getAlpha()/255.0f)*(1-(float)phase) + (c2.getAlpha()/255.0f)*((float)phase);\r
+ return new Color(r, g, b, a);\r
+ }\r
+\r
+ public static Path2D buildPath(List<Point2D> positions)\r
+ {\r
+ Path2D result = new Path2D.Double();\r
+ if (positions.size()==0) return result;\r
+ Point2D pos = positions.get(0);\r
+ result.moveTo(pos.getX(), pos.getY());\r
+ for (int i=1; i<positions.size(); i++)\r
+ {\r
+ pos = positions.get(i);\r
+ result.lineTo(pos.getX(), pos.getY());\r
+ }\r
+ return result;\r
+ }\r
+\r
+ /**\r
+ * Get path positions\r
+ * \r
+ * @param path path\r
+ * @param positions positions\r
+ */\r
+ public static void getPoints(Path2D path, List<Point2D> positions)\r
+ {\r
+ PathIterator pi = path.getPathIterator(null);\r
+ double mat[] = new double[6];\r
+ while (!pi.isDone()) {\r
+ pi.currentSegment(mat);\r
+ positions.add(new Point2D.Double(mat[0], mat[1]));\r
+ pi.next();\r
+ }\r
+ }\r
+\r
+ // Tests intersects and contains\r
+ public static void main(String[] args) {\r
+/*\r
+ System.out.println(toUnitVector(getCompassDirection(0, -1), null));\r
+ System.out.println(toUnitVector(getCompassDirection(1, -1), null));\r
+ System.out.println(toUnitVector(getCompassDirection(1, 0), null));\r
+ System.out.println(toUnitVector(getCompassDirection(1, 1), null));\r
+ System.out.println(toUnitVector(getCompassDirection(0, 1), null));\r
+ System.out.println(toUnitVector(getCompassDirection(-1, 1), null));\r
+ System.out.println(toUnitVector(getCompassDirection(-1, 0), null));\r
+ System.out.println(toUnitVector(getCompassDirection(-1,-1), null));\r
+\r
+ System.out.println(getCompassDirection(0, -1));\r
+ System.out.println(getCompassDirection(1, -1));\r
+ System.out.println(getCompassDirection(1, 0));\r
+ System.out.println(getCompassDirection(1, 1));\r
+ System.out.println(getCompassDirection(0, 1));\r
+ System.out.println(getCompassDirection(-1, 1));\r
+ System.out.println(getCompassDirection(-1, 0));\r
+ System.out.println(getCompassDirection(-1,-1));\r
+\r
+ System.out.println(getCompassDirection(new Point2D.Double(0, 0), new Point2D.Double(0, -1)));\r
+ System.out.println(getCompassDirection(new Point2D.Double(0, 0), new Point2D.Double(1, 0)));\r
+ System.out.println(getCompassDirection(new Point2D.Double(0, 0), new Point2D.Double(0, 1)));\r
+ System.out.println(getCompassDirection(new Point2D.Double(0, 0), new Point2D.Double(-1, 0)));\r
+ */\r
+ Shape s1 = new Polygon(new int[]{10,10,0}, new int[]{0,10,10}, 3);\r
+ Shape s2 = new Ellipse2D.Double(0,0,5,5);\r
+ Shape s3 = new Ellipse2D.Double(8,8,4,4);\r
+ Shape s4 = new Rectangle2D.Double(-5, 3, 20, 2);\r
+ Shape s5 = new Rectangle2D.Double(-100, -100, 200, 200);\r
+ Shape s6 = new Ellipse2D.Double(-100, -100, 200, 200);\r
+\r
+ assert(!intersects(s1, s2) );\r
+ assert( intersects(s1, s3) );\r
+ assert( intersects(s1, s4) );\r
+ assert( intersects(s1, s5) );\r
+\r
+ assert(!intersects(s2, s1) );\r
+ assert(!intersects(s2, s3) );\r
+ assert( intersects(s2, s4) );\r
+ assert( intersects(s2, s5) );\r
+\r
+ assert( intersects(s3, s1) );\r
+ assert(!intersects(s3, s2) );\r
+ assert(!intersects(s3, s4) );\r
+ assert( intersects(s3, s5) );\r
+\r
+ assert( intersects(s4, s1) );\r
+ assert( intersects(s4, s2) );\r
+ assert(!intersects(s4, s3) );\r
+ assert( intersects(s4, s5) );\r
+\r
+ assert( intersects(s5, s1) );\r
+ assert( intersects(s5, s2) );\r
+ assert( intersects(s5, s3) );\r
+ assert( intersects(s5, s4) );\r
+\r
+ assert(!contains(s1, s2) );\r
+ assert(!contains(s1, s3) );\r
+ assert(!contains(s1, s4) );\r
+ assert(!contains(s1, s5) );\r
+\r
+ assert(!contains(s2, s1) );\r
+ assert(!contains(s2, s3) );\r
+ assert(!contains(s2, s4) );\r
+ assert(!contains(s2, s5) );\r
+\r
+ assert(!contains(s3, s1) );\r
+ assert(!contains(s3, s2) );\r
+ assert(!contains(s3, s4) );\r
+ assert(!contains(s3, s5) );\r
+\r
+ assert(!contains(s4, s1) );\r
+ assert(!contains(s4, s2) );\r
+ assert(!contains(s4, s3) );\r
+ assert(!contains(s4, s5) );\r
+\r
+ assert( contains(s5, s1) );\r
+ assert( contains(s5, s2) );\r
+ assert( contains(s5, s3) );\r
+ assert( contains(s5, s4) );\r
+\r
+ assert( contains(s6, s1) );\r
+ assert( contains(s6, s2) );\r
+ assert( contains(s6, s3) );\r
+ assert( contains(s6, s4) );\r
+\r
+ }\r
+\r
+}\r