]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/GeometryUtils.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.scenegraph / src / org / simantics / scenegraph / utils / GeometryUtils.java
diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/GeometryUtils.java b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/GeometryUtils.java
new file mode 100644 (file)
index 0000000..e329c47
--- /dev/null
@@ -0,0 +1,511 @@
+/*******************************************************************************\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