-/*******************************************************************************\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);
+ }
+ }
+
+
+}