1 /*******************************************************************************
\r
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
\r
3 * in Industry THTH ry.
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.g2d.utils;
\r
14 import java.awt.geom.AffineTransform;
\r
15 import java.awt.geom.NoninvertibleTransformException;
\r
16 import java.awt.geom.Point2D;
\r
17 import java.awt.geom.Rectangle2D;
\r
20 * @author Tuukka Lehtonen
\r
22 public final class ViewBoxUtils {
\r
25 * A default viewbox generated with Stetson-Harrison method for a last
\r
26 * fallback value if nothing else is available.
\r
28 private static final Rectangle2D defaultViewBox = new Rectangle2D.Double(-10, -10, 20, 20);
\r
30 public static Rectangle2D getDefaultViewBox() {
\r
31 Rectangle2D r = new Rectangle2D.Double();
\r
32 r.setFrame(defaultViewBox);
\r
39 * @param distanceFromPlane (0, +inf)
\r
40 * @param fieldOfView (0, 180) deg
\r
41 * @param aspectRatio (0, +inf), view-width / view-height
\r
44 // public static Rectangle2D toViewBox(double x, double y, double distanceFromPlane, double fieldOfView, double aspectRatio) {
\r
45 // // w/2 = d * tan(fov)
\r
46 // // h = w / aspectRatio
\r
47 // double w = distanceFromPlane * Math.tan(fieldOfView * 0.5);
\r
48 // double h = w / aspectRatio;
\r
50 // Rectangle2D r = new Rectangle2D.Double(x - w, y - h, w * 2, h * 2);
\r
55 * Get the view transformation of the specified canvas as an
\r
59 public static AffineTransform getViewTransform(Rectangle2D viewBox, Point2D canvasSize) {
\r
60 double sx = canvasSize.getX() / viewBox.getWidth();
\r
61 double sy = canvasSize.getY() / viewBox.getHeight();
\r
63 AffineTransform tr = new AffineTransform();
\r
65 // System.out.println("getViewTransform:");
\r
66 // System.out.println(" scale: (" + sx + ", " + sy + ")");
\r
67 // System.out.println(" after scale: " + tr);
\r
68 tr.translate(-viewBox.getMinX(), -viewBox.getMinY());
\r
69 // System.out.println(" after translation: " + tr);
\r
75 * Get the view transformation of the specified canvas as an
\r
79 public static AffineTransform getViewTransform(Rectangle2D viewBox, Point2D controlSize, Point2D usableControlSize) {
\r
80 double sx = usableControlSize.getX() / viewBox.getWidth();
\r
81 double sy = usableControlSize.getY() / viewBox.getHeight();
\r
83 AffineTransform tr = new AffineTransform();
\r
85 (-usableControlSize.getX()+controlSize.getX())/2,
\r
86 (-usableControlSize.getX()+controlSize.getX())/2);
\r
88 tr.translate(-viewBox.getMinX(), -viewBox.getMinY());
\r
93 * Interpolates a 2D position with respect to a rectangle and two normalized
\r
94 * interpolation factors.
\r
96 * @param r the rectangle inside which to interpolate a coordinate
\r
97 * @param nx x interpolation value in [0,1]
\r
98 * @param ny y interpolation value in [0,1]
\r
101 public static Point2D interpolate(Rectangle2D r, double nx, double ny) {
\r
102 return new Point2D.Double(
\r
103 r.getMinX() + r.getWidth() * nx,
\r
104 r.getMinY() + r.getHeight() * ny);
\r
108 * Converts the specified AffineTransform into a view box rectangle with
\r
109 * respect to the size of the specified canvas instance. The client can
\r
110 * specify whether the scaling should be uniform or non-uniform. In the
\r
111 * uniform case the X scale factor is used for both dimensions.
\r
113 * @param canvasSize
\r
118 public static Rectangle2D transformToViewBox(Point2D canvasSize, AffineTransform tr, boolean uniform) {
\r
123 // if (tr.getScaleX() != tr.getScaleY())
\r
124 // System.out.println("WARNING: scale not uniform: " + tr.getScaleX() + " vs. " + tr.getScaleY());
\r
125 sx = sy = 1.0 / tr.getScaleX();
\r
127 sx = 1.0 / tr.getScaleX();
\r
128 sy = 1.0 / tr.getScaleY();
\r
131 double startX = -tr.getTranslateX() * sx;
\r
132 double startY = -tr.getTranslateY() * sy;
\r
133 double endX = startX + sx * canvasSize.getX();
\r
134 double endY = startY + sy * canvasSize.getY();
\r
136 return new Rectangle2D.Double(startX, startY, endX - startX, endY - startY);
\r
140 * Uniformly inflates the specified rectangle by a scaling factor around its
\r
141 * the center of the box. Modifies the specified rectangle instance itself.
\r
143 * @param r the rectangle to inflate
\r
144 * @param scale the inflation scale factor
\r
146 public static void inflate(Rectangle2D r, double scale) {
\r
147 double cx = r.getCenterX();
\r
148 double cy = r.getCenterY();
\r
149 double nw = r.getWidth() * scale;
\r
150 double nh = r.getHeight() * scale;
\r
151 double nw2 = nw * 0.5;
\r
152 double nh2 = nh * 0.5;
\r
153 r.setFrame(cx - nw2, cy - nh2, nw, nh);
\r
157 * Uniformly inflates the specified rectangle by a scaling factor around its
\r
158 * the center of the box. Returns the inflated rectangle as a new instance.
\r
159 * The original rectangle is not modified.
\r
161 * @param r the rectangle to inflate
\r
162 * @param scale the inflation scale factor
\r
163 * @return an inflated rectangle
\r
165 public static Rectangle2D inflated(Rectangle2D r, double scale) {
\r
166 Rectangle2D r2 = new Rectangle2D.Double();
\r
168 inflate(r2, scale);
\r
173 * Uniformly fits the specified view box to size of the specified canvas.
\r
175 * This means that the viewbox if the viewbox coordinates are not a perfect
\r
176 * multiple of the canvas size (i.e. viewBox-size * s = canvas-size), the
\r
177 * view box is always scaled in either the horizontal or the vertical
\r
178 * dimension. Here the scaled dimension is chosen to always inflate the view
\r
179 * box, never deflate it.
\r
181 * @param canvasSize the size of the canvas to fit the view box to
\r
182 * @param viewBox the view box to fit
\r
184 public static void uniformFitToCanvas(Point2D canvasSize, Rectangle2D viewBox) {
\r
185 double cx = viewBox.getCenterX();
\r
186 double cy = viewBox.getCenterY();
\r
188 double cw = (double) canvasSize.getX();
\r
189 double ch = (double) canvasSize.getY();
\r
190 double w = viewBox.getWidth();
\r
191 double h = viewBox.getHeight();
\r
192 double sw = cw / w;
\r
193 double sh = ch / h;
\r
196 // The specified viewbox fits the canvas in width but in height there
\r
197 // is extra space which the viewbox needs to fill!
\r
199 // height of requested viewbox in device coordinates = h' = h * sw
\r
200 // height of viewbox that fits the canvas height = h * ch / h'
\r
202 double dh = h * sw;
\r
203 double fh = h * ch / dh;
\r
205 viewBox.setFrameFromCenter(cx, cy, cx + w * 0.5, cy + fh * 0.5);
\r
206 // System.out.println("sw < sh: " + dh + ", " + fh + ": " + viewBox);
\r
207 } else if (sw > sh) {
\r
208 // The specified viewbox fits the canvas in height but in width there
\r
209 // is extra space which the viewbox needs to fill!
\r
211 // width of requested viewbox in device coordinates = dw = w * sh
\r
212 // width of viewbox that fits the canvas height = fw = w * cw / w'
\r
214 double dw = w * sh;
\r
215 double fw = w * cw / dw;
\r
217 viewBox.setFrameFromCenter(cx, cy, cx + fw * 0.5, cy + h * 0.5);
\r
218 // System.out.println("sw > sh: " + dw + ", " + fw + ": " + viewBox);
\r
222 public static void move(Rectangle2D r, double tx, double ty) {
\r
223 r.setFrame(r.getMinX() + tx, r.getMinY() + ty, r.getWidth(), r.getHeight());
\r
226 public static Rectangle2D moved(Rectangle2D r, double tx, double ty) {
\r
227 Rectangle2D r2 = new Rectangle2D.Double();
\r
228 r2.setFrame(r.getMinX() + tx, r.getMinY() + ty, r.getWidth(), r.getHeight());
\r
234 * @param canvasSize
\r
241 public static Rectangle2D uniformZoomedViewBox(Rectangle2D viewBox, Point2D canvasSize, int px, int py, double sx, double sy) {
\r
242 return zoomedViewBox(viewBox, canvasSize, px, py, sx, sy, true);
\r
254 public static Rectangle2D zoomedViewBox(Rectangle2D viewBox, Point2D canvasSize, int px, int py, double sx, double sy, boolean uniform) {
\r
255 if (sx <= 0 || sy <= 0) {
\r
256 throw new IllegalArgumentException("invalid scaling: " + sx + ", " + sy);
\r
259 AffineTransform view = getViewTransform(viewBox, canvasSize);
\r
261 AffineTransform at = AffineTransform.getTranslateInstance(px, py);
\r
263 at.translate(-px, -py);
\r
264 at.concatenate(view);
\r
266 Rectangle2D box = transformToViewBox(canvasSize, at, uniform);
\r
276 public enum Align {Begin, Center, End, Fill};
\r
279 * Creates tranform matrix, that converts coordinates from
\r
280 * uniform viewport to a 100x100 square. Aspect ratio remains the same.
\r
283 * @param horiz Horizontal alignment of 100x100 square
\r
284 * @param vert Vertical alignment of 100x100 square
\r
285 * @return transform (that can be concatenated)
\r
287 public static AffineTransform viewportToSquare(
\r
288 Rectangle2D viewport,
\r
291 boolean wide = viewport.getWidth() > viewport.getHeight();
\r
292 double shorterEdge = wide ? viewport.getHeight() : viewport.getWidth();
\r
293 double longerEdge = !wide ? viewport.getHeight() : viewport.getWidth();
\r
294 double tx = -viewport.getX();
\r
295 double ty = -viewport.getY();
\r
296 double s = 100 / shorterEdge;
\r
298 if (horiz == Align.Center)
\r
299 tx += (longerEdge - shorterEdge) / 2;
\r
300 else if (horiz == Align.End)
\r
301 tx += longerEdge - shorterEdge;
\r
302 else if (horiz == Align.Fill)
\r
303 s = 100 / viewport.getWidth();
\r
305 if (vert == Align.Center)
\r
306 ty += (longerEdge - shorterEdge) / 2;
\r
307 else if (vert == Align.End)
\r
308 ty += longerEdge - shorterEdge;
\r
309 else if (vert == Align.Fill)
\r
310 s = 100 / viewport.getHeight();
\r
313 AffineTransform at = new AffineTransform();
\r
315 at.translate(tx, ty);
\r
320 * Creates tranform matrix, that converts coordinates from
\r
321 * 100x100 square to a uniform viewport. Aspect ratio remains the same.
\r
324 * @param horiz Horizontal alignment of 100x100 square
\r
325 * @param vert Vertical alignment of 100x100 square
\r
326 * @return transform (that can be concatenated)
\r
328 public static AffineTransform squareToViewport(
\r
329 Rectangle2D viewport,
\r
332 AffineTransform at = viewportToSquare(viewport, horiz, vert);
\r
334 return at.createInverse();
\r
335 } catch (NoninvertibleTransformException e) {
\r
336 throw new RuntimeException(e);
\r