]> gerrit.simantics Code Review - simantics/platform.git/blob - 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
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
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.g2d.utils;\r
13 \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
18 \r
19 /**\r
20  * @author Tuukka Lehtonen\r
21  */\r
22 public final class ViewBoxUtils {\r
23 \r
24     /**\r
25      * A default viewbox generated with Stetson-Harrison method for a last\r
26      * fallback value if nothing else is available.\r
27      */\r
28     private static final Rectangle2D defaultViewBox = new Rectangle2D.Double(-10, -10, 20, 20);\r
29     \r
30     public static Rectangle2D getDefaultViewBox() {\r
31         Rectangle2D r = new Rectangle2D.Double();\r
32         r.setFrame(defaultViewBox);\r
33         return r;\r
34     }\r
35     \r
36     /**\r
37      * @param x\r
38      * @param y\r
39      * @param distanceFromPlane (0, +inf)\r
40      * @param fieldOfView (0, 180) deg\r
41      * @param aspectRatio (0, +inf), view-width / view-height\r
42      * @return\r
43      */\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
49 //        \r
50 //        Rectangle2D r = new Rectangle2D.Double(x - w, y - h, w * 2, h * 2);\r
51 //        return r;\r
52 //    }\r
53 \r
54     /**\r
55      * Get the view transformation of the specified canvas as an\r
56      * AffineTransform.\r
57      * \r
58      */\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
62 \r
63         AffineTransform tr = new AffineTransform();\r
64         tr.scale(sx, sy);\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
70         \r
71         return tr;\r
72     }\r
73     \r
74     /**\r
75      * Get the view transformation of the specified canvas as an\r
76      * AffineTransform.\r
77      * \r
78      */\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
82 \r
83         AffineTransform tr = new AffineTransform();\r
84         tr.translate(\r
85                 (-usableControlSize.getX()+controlSize.getX())/2, \r
86                 (-usableControlSize.getX()+controlSize.getX())/2);                \r
87         tr.scale(sx, sy);\r
88         tr.translate(-viewBox.getMinX(), -viewBox.getMinY());\r
89         \r
90         return tr;\r
91     }    \r
92     /**\r
93      * Interpolates a 2D position with respect to a rectangle and two normalized\r
94      * interpolation factors.\r
95      * \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
99      * @return\r
100      */\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
105     }\r
106     \r
107     /**\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
112      * \r
113      * @param canvasSize\r
114      * @param tr\r
115      * @param uniform\r
116      * @return\r
117      */\r
118     public static Rectangle2D transformToViewBox(Point2D canvasSize, AffineTransform tr, boolean uniform) {\r
119         double sx;\r
120         double sy;\r
121         \r
122         if (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
126         } else {\r
127             sx = 1.0 / tr.getScaleX();\r
128             sy = 1.0 / tr.getScaleY();\r
129         }\r
130         \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
135 \r
136         return new Rectangle2D.Double(startX, startY, endX - startX, endY - startY);\r
137     }\r
138 \r
139     /**\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
142      * \r
143      * @param r the rectangle to inflate\r
144      * @param scale the inflation scale factor\r
145      */\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
154     }\r
155     \r
156     /**\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
160      * \r
161      * @param r the rectangle to inflate\r
162      * @param scale the inflation scale factor\r
163      * @return an inflated rectangle\r
164      */\r
165     public static Rectangle2D inflated(Rectangle2D r, double scale) {\r
166         Rectangle2D r2 = new Rectangle2D.Double();\r
167         r2.setFrame(r);\r
168         inflate(r2, scale);\r
169         return r2;\r
170     }\r
171 \r
172     /**\r
173      * Uniformly fits the specified view box to size of the specified canvas.\r
174      * \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
180      * \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
183      */\r
184     public static void uniformFitToCanvas(Point2D canvasSize, Rectangle2D viewBox) {\r
185         double cx = viewBox.getCenterX();\r
186         double cy = viewBox.getCenterY();\r
187         \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
194         \r
195         if (sw < sh) {\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
198             \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
201             \r
202             double dh = h * sw;\r
203             double fh = h * ch / dh;\r
204             \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
210             \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
213             \r
214             double dw = w * sh;\r
215             double fw = w * cw / dw;\r
216             \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
219         }\r
220     }\r
221 \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
224     }\r
225 \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
229         return r2;\r
230     }\r
231 \r
232     \r
233     /**\r
234      * @param canvasSize\r
235      * @param px\r
236      * @param py\r
237      * @param sx\r
238      * @param sy\r
239      * @return\r
240      */\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
243     }\r
244  \r
245     /**\r
246      * @param c\r
247      * @param px\r
248      * @param py\r
249      * @param sx\r
250      * @param sy\r
251      * @param uniform\r
252      * @return\r
253      */\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
257         }\r
258         \r
259         AffineTransform view = getViewTransform(viewBox, canvasSize);\r
260         \r
261         AffineTransform at = AffineTransform.getTranslateInstance(px, py);\r
262         at.scale(sx, sy);\r
263         at.translate(-px, -py);\r
264         at.concatenate(view);\r
265         \r
266         Rectangle2D box = transformToViewBox(canvasSize, at, uniform);\r
267         return box;\r
268     }\r
269     \r
270     \r
271     \r
272     \r
273     \r
274     \r
275 \r
276     public enum Align {Begin, Center, End, Fill};\r
277     \r
278     /**\r
279      * Creates tranform matrix, that converts coordinates from \r
280      * uniform viewport to a 100x100 square. Aspect ratio remains the same.\r
281      *  \r
282      * @param viewport\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
286      */\r
287     public static AffineTransform viewportToSquare(\r
288             Rectangle2D viewport, \r
289             Align horiz, \r
290             Align vert) {\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
297         if (wide) {\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
304         } else {\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
311         }\r
312         \r
313         AffineTransform at = new AffineTransform();\r
314         at.scale(s, s);\r
315         at.translate(tx, ty);\r
316         return at;\r
317     }\r
318 \r
319     /**\r
320      * Creates tranform matrix, that converts coordinates from \r
321      * 100x100 square to a uniform viewport. Aspect ratio remains the same.\r
322      *  \r
323      * @param viewport\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
327      */\r
328     public static AffineTransform squareToViewport(\r
329             Rectangle2D viewport, \r
330             Align horiz, \r
331             Align vert) {\r
332         AffineTransform at = viewportToSquare(viewport, horiz, vert);\r
333         try {\r
334             return at.createInverse();\r
335         } catch (NoninvertibleTransformException e) {\r
336             throw new RuntimeException(e);\r
337         }         \r
338     }\r
339     \r
340     \r
341 }\r