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