--- /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.BasicStroke;\r
+import java.awt.Stroke;\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.Point2D;\r
+import java.awt.geom.Rectangle2D;\r
+\r
+import org.simantics.utils.page.MarginUtils.Margins;\r
+\r
+/**\r
+ * Basic utilities for geometric calculations and testing.\r
+ * \r
+ * @author J-P Laine\r
+ * @author Tuukka Lehtonen\r
+ */\r
+public final class GeometryUtils {\r
+\r
+ /**\r
+ * @param p the point of distance measure\r
+ * @param p1 other end of line\r
+ * @param p2 other end of line\r
+ * @return distance of p from the line defined by p1 and p2\r
+ */\r
+ public static double distanceFromLine(Point2D p, Point2D p1, Point2D p2) {\r
+ return distanceFromLine(p1.getX(), p1.getY(), p2.getX(), p2.getY(), p.getX(), p.getY());\r
+ }\r
+\r
+ /**\r
+ * @param x1 line segment end 1 x\r
+ * @param y1 line segment end 1 y\r
+ * @param x2 line segment end 2 x\r
+ * @param y2 line segment end 2 y\r
+ * @param px point x\r
+ * @param py point y\r
+ * @return\r
+ */\r
+ public static double distanceFromLine(double x1, double y1, double x2, double y2, double px, double py) {\r
+ // Adjust vectors relative to x1,y1\r
+ // x2,y2 becomes relative vector from x1,y1 to end of segment\r
+ x2 -= x1;\r
+ y2 -= y1;\r
+ // px,py becomes relative vector from x1,y1 to test point\r
+ px -= x1;\r
+ py -= y1;\r
+ double dotprod = px * x2 + py * y2;\r
+ // dotprod is the length of the px,py vector\r
+ // projected on the x1,y1=>x2,y2 vector times the\r
+ // length of the x1,y1=>x2,y2 vector\r
+ double lineLenSq = x2 * x2 + y2 * y2;\r
+ double lineLen = Math.sqrt(lineLenSq);\r
+ double projLen = dotprod / lineLen;\r
+\r
+ // Check whether the projection of (px,py) is outside the specified line.\r
+ if (projLen < 0) {\r
+ return Math.sqrt(px * px + py * py);\r
+ } else if (projLen > lineLen) {\r
+ double dx = px - x2;\r
+ double dy = py - y2;\r
+ return Math.sqrt(dx * dx + dy * dy);\r
+ }\r
+ return Math.sqrt(px * px + py * py - projLen * projLen);\r
+ }\r
+\r
+ /**\r
+ * @param x1 line segment end 1 x\r
+ * @param y1 line segment end 1 y\r
+ * @param x2 line segment end 2 x\r
+ * @param y2 line segment end 2 y\r
+ * @param px point x\r
+ * @param py point y\r
+ * @return\r
+ */\r
+ public static Point2D intersectionToLine(double x1, double y1, double x2, double y2, double px, double py) {\r
+ double xx2 = x2;\r
+ double yy2 = y2;\r
+ // Adjust vectors relative to x1,y1\r
+ // x2,y2 becomes relative vector from x1,y1 to end of segment\r
+ x2 -= x1;\r
+ y2 -= y1;\r
+ // px,py becomes relative vector from x1,y1 to test point\r
+ px -= x1;\r
+ py -= y1;\r
+ double dotprod = px * x2 + py * y2;\r
+ // dotprod is the length of the px,py vector\r
+ // projected on the x1,y1=>x2,y2 vector times the\r
+ // length of the x1,y1=>x2,y2 vector\r
+ double lineLenSq = x2 * x2 + y2 * y2;\r
+ double lineLen = Math.sqrt(lineLenSq);\r
+ if (lineLen <= Double.MIN_VALUE)\r
+ return new Point2D.Double(x1, y1);\r
+\r
+ double projLen = dotprod / lineLen;\r
+\r
+ // Check whether the projection of (px,py) is outside the specified line.\r
+ if (projLen < 0) {\r
+ return new Point2D.Double(x1, y1);\r
+ } else if (projLen > lineLen) {\r
+ return new Point2D.Double(xx2, yy2);\r
+ }\r
+ return new Point2D.Double(x1 + x2/lineLen*projLen, y1 + y2/lineLen*projLen);\r
+ }\r
+\r
+ /**\r
+ * Expands margins to a rectangle\r
+ * @param rect\r
+ * @param top\r
+ * @param bottom\r
+ * @param left\r
+ * @param right\r
+ */\r
+ public static Rectangle2D expandRectangle(Rectangle2D rect, double top, double bottom, double left, double right)\r
+ {\r
+ if (rect==null) throw new IllegalArgumentException("null arg");\r
+ rect.setRect(\r
+ rect.getX() - left,\r
+ rect.getY() - top,\r
+ rect.getWidth() + left + right,\r
+ rect.getHeight() + top + bottom);\r
+ return rect;\r
+ }\r
+\r
+ public static Rectangle2D expandRectangle(Rectangle2D rect, double horizontalExpand, double verticalExpand) {\r
+ return expandRectangle(rect, verticalExpand, verticalExpand, horizontalExpand, horizontalExpand);\r
+ }\r
+\r
+ public static Rectangle2D expandRectangle(Rectangle2D rect, double evenExpand) {\r
+ return expandRectangle(rect, evenExpand, evenExpand, evenExpand, evenExpand);\r
+ }\r
+\r
+ public static BasicStroke scaleStroke(Stroke stroke, float factor)\r
+ {\r
+ return scaleAndOffsetStroke(stroke, factor, 0.0f);\r
+ }\r
+\r
+ public static BasicStroke offsetStroke(Stroke stroke, float offset)\r
+ {\r
+ BasicStroke s = (BasicStroke) stroke;\r
+ float[] dash = s.getDashArray();\r
+ if (dash == null)\r
+ return s;\r
+\r
+ return new BasicStroke(\r
+ s.getLineWidth(),\r
+ s.getEndCap(),\r
+ s.getLineJoin(),\r
+ s.getMiterLimit(),\r
+ dash,\r
+ s.getDashPhase() + offset\r
+ );\r
+ }\r
+\r
+ public static BasicStroke scaleAndOffsetStroke(Stroke stroke, float factor, float offset)\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 + offset\r
+ );\r
+ }\r
+\r
+ public static BasicStroke scaleStrokeWidth(Stroke stroke, float factor)\r
+ {\r
+ BasicStroke s = (BasicStroke) stroke;\r
+ return new BasicStroke(\r
+ s.getLineWidth() * factor,\r
+ s.getEndCap(),\r
+ s.getLineJoin(),\r
+ s.getMiterLimit(),\r
+ s.getDashArray(),\r
+ s.getDashPhase()\r
+ );\r
+ }\r
+\r
+ public static BasicStroke scaleAndOffsetStrokeWidth(Stroke stroke, float factor, float widthOffset)\r
+ {\r
+ BasicStroke s = (BasicStroke) stroke;\r
+ return new BasicStroke(\r
+ s.getLineWidth() * factor + widthOffset,\r
+ s.getEndCap(),\r
+ s.getLineJoin(),\r
+ s.getMiterLimit(),\r
+ s.getDashArray(),\r
+ s.getDashPhase()\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
+ 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
+ public static Point2D getScale2D(AffineTransform at)\r
+ {\r
+ double m00 = at.getScaleX();\r
+ double m11 = at.getScaleY();\r
+ double m10 = at.getShearY();\r
+ double m01 = at.getShearX();\r
+ double sx = Math.sqrt(m00 * m00 + m10 * m10);\r
+ double sy = Math.sqrt(m01 * m01 + m11 * m11);\r
+ return new Point2D.Double(sx, sy);\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
+ * Get a transform that makes canvas area fully visible.\r
+ * @param controlArea\r
+ * @param diagramArea\r
+ * @param margins margins\r
+ * @return transform\r
+ */\r
+ public static AffineTransform fitArea(Rectangle2D controlArea, Rectangle2D diagramArea)\r
+ {\r
+ double controlAspectRatio = controlArea.getWidth() / controlArea.getHeight();\r
+ double canvasAspectRatio = diagramArea.getWidth() / diagramArea.getHeight();\r
+ // Control is really wide => center canvas horizontally, match vertically\r
+ double scale = 1.0;\r
+ double tx = 0.0;\r
+ double ty = 0.0;\r
+ if (controlAspectRatio>canvasAspectRatio)\r
+ {\r
+ scale = controlArea.getHeight() / diagramArea.getHeight();\r
+ tx = ( controlArea.getWidth() - diagramArea.getWidth() * scale ) / 2;\r
+ } else\r
+ // Control is really tall => center canvas vertically, match horizontally\r
+ {\r
+ scale = controlArea.getWidth() / diagramArea.getWidth();\r
+ ty = ( controlArea.getHeight() - diagramArea.getHeight() * scale ) / 2;\r
+ }\r
+ AffineTransform at = new AffineTransform();\r
+ at.translate(tx, ty);\r
+ at.translate(controlArea.getMinX(), controlArea.getMinY());\r
+ at.scale(scale, scale);\r
+ at.translate(-diagramArea.getMinX(), -diagramArea.getMinY());\r
+ //System.out.println("FIT TRANSFORM: " + at);\r
+ return at;\r
+ }\r
+\r
+ /**\r
+ * Rotates rectangle. Note, general rotation is not supported.\r
+ * @param transform\r
+ * @param rect\r
+ * @return transformed rectangle\r
+ */\r
+ public static Rectangle2D transformRectangle(AffineTransform transform, Rectangle2D rect) {\r
+ int type = transform.getType();\r
+ if (type == AffineTransform.TYPE_IDENTITY)\r
+ return new Rectangle2D.Double(rect.getMinX(), rect.getMinY(), rect.getWidth(), rect.getHeight());\r
+ if ((type & (AffineTransform.TYPE_GENERAL_ROTATION|AffineTransform.TYPE_GENERAL_TRANSFORM)) == 0) {\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
+ // Generic but much slower.\r
+ return transform.createTransformedShape(rect).getBounds2D();\r
+ }\r
+\r
+ public static Rectangle2D transformRectangleInv(AffineTransform transform, Rectangle2D rect) {\r
+ assert( (transform.getType() & (AffineTransform.TYPE_GENERAL_ROTATION|AffineTransform.TYPE_GENERAL_TRANSFORM)) == 0);\r
+ double[] mx = new double[6];\r
+ transform.getMatrix(mx);\r
+ double x0 = rect.getMinX() - mx[4];\r
+ double y0 = rect.getMinY() - mx[5];\r
+ double x1 = rect.getMaxX() - mx[4];\r
+ double y1 = rect.getMaxY() - mx[5];\r
+ double det = mx[0]*mx[3] - mx[1]*mx[2];\r
+ double m00 = mx[3] / det;\r
+ double m10 = -mx[1] / det;\r
+ double m01 = -mx[2] / det;\r
+ double m11 = mx[0] / det;\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, Y0, X1-X0, Y1-Y0);\r
+ }\r
+\r
+ /**\r
+ * @param points\r
+ * @return millimeters\r
+ */\r
+ public static float pointToMillimeter(float points) {\r
+ return points * 25.4f / 72.0f;\r
+ }\r
+\r
+ /**\r
+ * @param points\r
+ * @return millimeters\r
+ */\r
+ public static double pointToMillimeter(double points) {\r
+ return points * 25.4 / 72.0;\r
+ }\r
+\r
+ /**\r
+ * Get a transform that makes canvas area fully visible.\r
+ * @param controlArea\r
+ * @param diagramArea\r
+ * @param margins margins\r
+ * @return transform\r
+ */\r
+ public static AffineTransform fitArea(Rectangle2D controlArea, Rectangle2D diagramArea, Margins margins)\r
+ {\r
+ diagramArea = expandRectangle(diagramArea,\r
+ margins.top.diagramAbsolute,\r
+ margins.bottom.diagramAbsolute,\r
+ margins.left.diagramAbsolute,\r
+ margins.right.diagramAbsolute);\r
+ controlArea = expandRectangle(controlArea,\r
+ -margins.top.controlAbsolute - margins.top.controlRelative * controlArea.getHeight(),\r
+ -margins.bottom.controlAbsolute - margins.bottom.controlRelative * controlArea.getHeight(),\r
+ -margins.left.controlAbsolute - margins.left.controlRelative * controlArea.getWidth(),\r
+ -margins.right.controlAbsolute - margins.right.controlRelative * controlArea.getWidth());\r
+\r
+ double controlAspectRatio = controlArea.getWidth() / controlArea.getHeight();\r
+ double canvasAspectRatio = diagramArea.getWidth() / diagramArea.getHeight();\r
+ // Control is really wide => center canvas horizontally, match vertically\r
+ double scale = 1.0;\r
+ double tx = 0.0;\r
+ double ty = 0.0;\r
+ if (controlAspectRatio>canvasAspectRatio)\r
+ {\r
+ scale = controlArea.getHeight() / diagramArea.getHeight();\r
+ tx = ( controlArea.getWidth() - diagramArea.getWidth() * scale ) / 2;\r
+ } else\r
+ // Control is really tall => center canvas vertically, match horizontally\r
+ {\r
+ scale = controlArea.getWidth() / diagramArea.getWidth();\r
+ ty = ( controlArea.getHeight() - diagramArea.getHeight() * scale ) / 2;\r
+ }\r
+ AffineTransform at = new AffineTransform();\r
+ at.translate(tx, ty);\r
+ at.translate(controlArea.getMinX(), controlArea.getMinY());\r
+ at.scale(scale, scale);\r
+ at.translate(-diagramArea.getMinX(), -diagramArea.getMinY());\r
+ //System.out.println("FIT TRANSFORM: " + at);\r
+ return at;\r
+ }\r
+ \r
+}\r