]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.g2d/src/org/simantics/g2d/utils/ViewBoxUtils.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / utils / ViewBoxUtils.java
diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/utils/ViewBoxUtils.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/utils/ViewBoxUtils.java
new file mode 100644 (file)
index 0000000..48e0392
--- /dev/null
@@ -0,0 +1,341 @@
+/*******************************************************************************\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.geom.AffineTransform;\r
+import java.awt.geom.NoninvertibleTransformException;\r
+import java.awt.geom.Point2D;\r
+import java.awt.geom.Rectangle2D;\r
+\r
+/**\r
+ * @author Tuukka Lehtonen\r
+ */\r
+public final class ViewBoxUtils {\r
+\r
+    /**\r
+     * A default viewbox generated with Stetson-Harrison method for a last\r
+     * fallback value if nothing else is available.\r
+     */\r
+    private static final Rectangle2D defaultViewBox = new Rectangle2D.Double(-10, -10, 20, 20);\r
+    \r
+    public static Rectangle2D getDefaultViewBox() {\r
+        Rectangle2D r = new Rectangle2D.Double();\r
+        r.setFrame(defaultViewBox);\r
+        return r;\r
+    }\r
+    \r
+    /**\r
+     * @param x\r
+     * @param y\r
+     * @param distanceFromPlane (0, +inf)\r
+     * @param fieldOfView (0, 180) deg\r
+     * @param aspectRatio (0, +inf), view-width / view-height\r
+     * @return\r
+     */\r
+//    public static Rectangle2D toViewBox(double x, double y, double distanceFromPlane, double fieldOfView, double aspectRatio) {\r
+//        // w/2 = d * tan(fov)\r
+//        // h = w / aspectRatio\r
+//        double w = distanceFromPlane * Math.tan(fieldOfView * 0.5);\r
+//        double h = w / aspectRatio;\r
+//        \r
+//        Rectangle2D r = new Rectangle2D.Double(x - w, y - h, w * 2, h * 2);\r
+//        return r;\r
+//    }\r
+\r
+    /**\r
+     * Get the view transformation of the specified canvas as an\r
+     * AffineTransform.\r
+     * \r
+     */\r
+    public static AffineTransform getViewTransform(Rectangle2D viewBox, Point2D canvasSize) {\r
+        double sx = canvasSize.getX() / viewBox.getWidth();\r
+        double sy = canvasSize.getY() / viewBox.getHeight();\r
+\r
+        AffineTransform tr = new AffineTransform();\r
+        tr.scale(sx, sy);\r
+//        System.out.println("getViewTransform:");\r
+//        System.out.println("  scale: (" + sx + ", " + sy + ")");\r
+//        System.out.println("  after scale: " + tr);\r
+        tr.translate(-viewBox.getMinX(), -viewBox.getMinY());\r
+//        System.out.println("  after translation: " + tr); \r
+        \r
+        return tr;\r
+    }\r
+    \r
+    /**\r
+     * Get the view transformation of the specified canvas as an\r
+     * AffineTransform.\r
+     * \r
+     */\r
+    public static AffineTransform getViewTransform(Rectangle2D viewBox, Point2D controlSize, Point2D usableControlSize) {\r
+        double sx = usableControlSize.getX() / viewBox.getWidth();\r
+        double sy = usableControlSize.getY() / viewBox.getHeight();\r
+\r
+        AffineTransform tr = new AffineTransform();\r
+        tr.translate(\r
+                (-usableControlSize.getX()+controlSize.getX())/2, \r
+                (-usableControlSize.getX()+controlSize.getX())/2);                \r
+        tr.scale(sx, sy);\r
+        tr.translate(-viewBox.getMinX(), -viewBox.getMinY());\r
+        \r
+        return tr;\r
+    }    \r
+    /**\r
+     * Interpolates a 2D position with respect to a rectangle and two normalized\r
+     * interpolation factors.\r
+     * \r
+     * @param r the rectangle inside which to interpolate a coordinate\r
+     * @param nx x interpolation value in [0,1]\r
+     * @param ny y interpolation value in [0,1]\r
+     * @return\r
+     */\r
+    public static Point2D interpolate(Rectangle2D r, double nx, double ny) {\r
+        return new Point2D.Double(\r
+                r.getMinX() + r.getWidth() * nx,\r
+                r.getMinY() + r.getHeight() * ny);\r
+    }\r
+    \r
+    /**\r
+     * Converts the specified AffineTransform into a view box rectangle with\r
+     * respect to the size of the specified canvas instance. The client can\r
+     * specify whether the scaling should be uniform or non-uniform. In the\r
+     * uniform case the X scale factor is used for both dimensions.\r
+     * \r
+     * @param canvasSize\r
+     * @param tr\r
+     * @param uniform\r
+     * @return\r
+     */\r
+    public static Rectangle2D transformToViewBox(Point2D canvasSize, AffineTransform tr, boolean uniform) {\r
+        double sx;\r
+        double sy;\r
+        \r
+        if (uniform) {\r
+//          if (tr.getScaleX() != tr.getScaleY())\r
+//          System.out.println("WARNING: scale not uniform: " + tr.getScaleX() + " vs. " + tr.getScaleY());\r
+            sx = sy = 1.0 / tr.getScaleX();\r
+        } else {\r
+            sx = 1.0 / tr.getScaleX();\r
+            sy = 1.0 / tr.getScaleY();\r
+        }\r
+        \r
+        double startX = -tr.getTranslateX() * sx;\r
+        double startY = -tr.getTranslateY() * sy;\r
+        double endX = startX + sx * canvasSize.getX();\r
+        double endY = startY + sy * canvasSize.getY();\r
+\r
+        return new Rectangle2D.Double(startX, startY, endX - startX, endY - startY);\r
+    }\r
+\r
+    /**\r
+     * Uniformly inflates the specified rectangle by a scaling factor around its\r
+     * the center of the box. Modifies the specified rectangle instance itself.\r
+     * \r
+     * @param r the rectangle to inflate\r
+     * @param scale the inflation scale factor\r
+     */\r
+    public static void inflate(Rectangle2D r, double scale) {\r
+        double cx = r.getCenterX();\r
+        double cy = r.getCenterY();\r
+        double nw = r.getWidth() * scale;\r
+        double nh = r.getHeight() * scale;\r
+        double nw2 = nw * 0.5;\r
+        double nh2 = nh * 0.5;\r
+        r.setFrame(cx - nw2, cy - nh2, nw, nh);\r
+    }\r
+    \r
+    /**\r
+     * Uniformly inflates the specified rectangle by a scaling factor around its\r
+     * the center of the box. Returns the inflated rectangle as a new instance.\r
+     * The original rectangle is not modified.\r
+     * \r
+     * @param r the rectangle to inflate\r
+     * @param scale the inflation scale factor\r
+     * @return an inflated rectangle\r
+     */\r
+    public static Rectangle2D inflated(Rectangle2D r, double scale) {\r
+        Rectangle2D r2 = new Rectangle2D.Double();\r
+        r2.setFrame(r);\r
+        inflate(r2, scale);\r
+        return r2;\r
+    }\r
+\r
+    /**\r
+     * Uniformly fits the specified view box to size of the specified canvas.\r
+     * \r
+     * This means that the viewbox if the viewbox coordinates are not a perfect\r
+     * multiple of the canvas size (i.e. viewBox-size * s = canvas-size), the\r
+     * view box is always scaled in either the horizontal or the vertical\r
+     * dimension. Here the scaled dimension is chosen to always inflate the view\r
+     * box, never deflate it.\r
+     * \r
+     * @param canvasSize the size of the canvas to fit the view box to\r
+     * @param viewBox the view box to fit\r
+     */\r
+    public static void uniformFitToCanvas(Point2D canvasSize, Rectangle2D viewBox) {\r
+        double cx = viewBox.getCenterX();\r
+        double cy = viewBox.getCenterY();\r
+        \r
+        double cw = (double) canvasSize.getX();\r
+        double ch = (double) canvasSize.getY();\r
+        double w = viewBox.getWidth();\r
+        double h = viewBox.getHeight();\r
+        double sw = cw / w;\r
+        double sh = ch / h;\r
+        \r
+        if (sw < sh) {\r
+            // The specified viewbox fits the canvas in width but in height there\r
+            // is extra space which the viewbox needs to fill!\r
+            \r
+            // height of requested viewbox in device coordinates = h' = h * sw\r
+            // height of viewbox that fits the canvas height = h * ch / h'\r
+            \r
+            double dh = h * sw;\r
+            double fh = h * ch / dh;\r
+            \r
+            viewBox.setFrameFromCenter(cx, cy, cx + w * 0.5, cy + fh * 0.5);\r
+//            System.out.println("sw < sh: " + dh + ", " + fh + ": " + viewBox);\r
+        } else if (sw > sh) {\r
+            // The specified viewbox fits the canvas in height but in width there\r
+            // is extra space which the viewbox needs to fill!\r
+            \r
+            // width of requested viewbox in device coordinates = dw = w * sh\r
+            // width of viewbox that fits the canvas height = fw = w * cw / w'\r
+            \r
+            double dw = w * sh;\r
+            double fw = w * cw / dw;\r
+            \r
+            viewBox.setFrameFromCenter(cx, cy, cx + fw * 0.5, cy + h * 0.5);\r
+//            System.out.println("sw > sh: " + dw + ", " + fw + ": " + viewBox);\r
+        }\r
+    }\r
+\r
+    public static void move(Rectangle2D r, double tx, double ty) {\r
+        r.setFrame(r.getMinX() + tx, r.getMinY() + ty, r.getWidth(), r.getHeight());\r
+    }\r
+\r
+    public static Rectangle2D moved(Rectangle2D r, double tx, double ty) {\r
+        Rectangle2D r2 = new Rectangle2D.Double();\r
+        r2.setFrame(r.getMinX() + tx, r.getMinY() + ty, r.getWidth(), r.getHeight());\r
+        return r2;\r
+    }\r
+\r
+    \r
+    /**\r
+     * @param canvasSize\r
+     * @param px\r
+     * @param py\r
+     * @param sx\r
+     * @param sy\r
+     * @return\r
+     */\r
+    public static Rectangle2D uniformZoomedViewBox(Rectangle2D viewBox, Point2D canvasSize, int px, int py, double sx, double sy) {\r
+        return zoomedViewBox(viewBox, canvasSize, px, py, sx, sy, true);\r
+    }\r
\r
+    /**\r
+     * @param c\r
+     * @param px\r
+     * @param py\r
+     * @param sx\r
+     * @param sy\r
+     * @param uniform\r
+     * @return\r
+     */\r
+    public static Rectangle2D zoomedViewBox(Rectangle2D viewBox, Point2D canvasSize, int px, int py, double sx, double sy, boolean uniform) {\r
+        if (sx <= 0 || sy <= 0) {\r
+            throw new IllegalArgumentException("invalid scaling: " + sx + ", " + sy);\r
+        }\r
+        \r
+        AffineTransform view = getViewTransform(viewBox, canvasSize);\r
+        \r
+        AffineTransform at = AffineTransform.getTranslateInstance(px, py);\r
+        at.scale(sx, sy);\r
+        at.translate(-px, -py);\r
+        at.concatenate(view);\r
+        \r
+        Rectangle2D box = transformToViewBox(canvasSize, at, uniform);\r
+        return box;\r
+    }\r
+    \r
+    \r
+    \r
+    \r
+    \r
+    \r
+\r
+    public enum Align {Begin, Center, End, Fill};\r
+    \r
+    /**\r
+     * Creates tranform matrix, that converts coordinates from \r
+     * uniform viewport to a 100x100 square. Aspect ratio remains the same.\r
+     *  \r
+     * @param viewport\r
+     * @param horiz Horizontal alignment of 100x100 square\r
+     * @param vert Vertical alignment of 100x100 square\r
+     * @return transform (that can be concatenated)\r
+     */\r
+    public static AffineTransform viewportToSquare(\r
+            Rectangle2D viewport, \r
+            Align horiz, \r
+            Align vert) {\r
+        boolean wide = viewport.getWidth() > viewport.getHeight();\r
+        double shorterEdge = wide ? viewport.getHeight() : viewport.getWidth();\r
+        double longerEdge = !wide ? viewport.getHeight() : viewport.getWidth();\r
+        double tx = -viewport.getX(); \r
+        double ty = -viewport.getY();\r
+        double s = 100 / shorterEdge;\r
+        if (wide) {\r
+            if (horiz == Align.Center)\r
+                tx += (longerEdge - shorterEdge) / 2;\r
+            else if (horiz == Align.End)\r
+                tx +=  longerEdge - shorterEdge;            \r
+            else if (horiz == Align.Fill)            \r
+                s = 100 / viewport.getWidth();            \r
+        } else {\r
+            if (vert == Align.Center)\r
+                ty += (longerEdge - shorterEdge) / 2;\r
+            else if (vert == Align.End)\r
+                ty +=  longerEdge - shorterEdge;            \r
+            else if (vert == Align.Fill)            \r
+                s = 100 / viewport.getHeight();            \r
+        }\r
+        \r
+        AffineTransform at = new AffineTransform();\r
+        at.scale(s, s);\r
+        at.translate(tx, ty);\r
+        return at;\r
+    }\r
+\r
+    /**\r
+     * Creates tranform matrix, that converts coordinates from \r
+     * 100x100 square to a uniform viewport. Aspect ratio remains the same.\r
+     *  \r
+     * @param viewport\r
+     * @param horiz Horizontal alignment of 100x100 square\r
+     * @param vert Vertical alignment of 100x100 square\r
+     * @return transform (that can be concatenated)\r
+     */\r
+    public static AffineTransform squareToViewport(\r
+            Rectangle2D viewport, \r
+            Align horiz, \r
+            Align vert) {\r
+        AffineTransform at = viewportToSquare(viewport, horiz, vert);\r
+        try {\r
+            return at.createInverse();\r
+        } catch (NoninvertibleTransformException e) {\r
+            throw new RuntimeException(e);\r
+        }         \r
+    }\r
+    \r
+    \r
+}\r