]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/GeometryUtils.java
Merge commit 'd186091'
[simantics/platform.git] / bundles / org.simantics.scenegraph / src / org / simantics / scenegraph / utils / GeometryUtils.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.scenegraph.utils;\r
13 \r
14 import java.awt.BasicStroke;\r
15 import java.awt.Stroke;\r
16 import java.awt.geom.AffineTransform;\r
17 import java.awt.geom.Point2D;\r
18 import java.awt.geom.Rectangle2D;\r
19 \r
20 import org.simantics.utils.page.MarginUtils.Margins;\r
21 \r
22 /**\r
23  * Basic utilities for geometric calculations and testing.\r
24  * \r
25  * @author J-P Laine\r
26  * @author Tuukka Lehtonen\r
27  */\r
28 public final class GeometryUtils {\r
29 \r
30     /**\r
31      * @param p the point of distance measure\r
32      * @param p1 other end of line\r
33      * @param p2 other end of line\r
34      * @return distance of p from the line defined by p1 and p2\r
35      */\r
36     public static double distanceFromLine(Point2D p, Point2D p1, Point2D p2) {\r
37         return distanceFromLine(p1.getX(), p1.getY(), p2.getX(), p2.getY(), p.getX(), p.getY());\r
38     }\r
39 \r
40     /**\r
41      * @param x1 line segment end 1 x\r
42      * @param y1 line segment end 1 y\r
43      * @param x2 line segment end 2 x\r
44      * @param y2 line segment end 2 y\r
45      * @param px point x\r
46      * @param py point y\r
47      * @return\r
48      */\r
49     public static double distanceFromLine(double x1, double y1, double x2, double y2, double px, double py) {\r
50         // Adjust vectors relative to x1,y1\r
51         // x2,y2 becomes relative vector from x1,y1 to end of segment\r
52         x2 -= x1;\r
53         y2 -= y1;\r
54         // px,py becomes relative vector from x1,y1 to test point\r
55         px -= x1;\r
56         py -= y1;\r
57         double dotprod = px * x2 + py * y2;\r
58         // dotprod is the length of the px,py vector\r
59         // projected on the x1,y1=>x2,y2 vector times the\r
60         // length of the x1,y1=>x2,y2 vector\r
61         double lineLenSq = x2 * x2 + y2 * y2;\r
62         double lineLen = Math.sqrt(lineLenSq);\r
63         double projLen = dotprod / lineLen;\r
64 \r
65         // Check whether the projection of (px,py) is outside the specified line.\r
66         if (projLen < 0) {\r
67             return Math.sqrt(px * px + py * py);\r
68         } else if (projLen > lineLen) {\r
69             double dx = px - x2;\r
70             double dy = py - y2;\r
71             return Math.sqrt(dx * dx + dy * dy);\r
72         }\r
73         return Math.sqrt(px * px + py * py - projLen * projLen);\r
74     }\r
75 \r
76     /**\r
77      * @param x1 line segment end 1 x\r
78      * @param y1 line segment end 1 y\r
79      * @param x2 line segment end 2 x\r
80      * @param y2 line segment end 2 y\r
81      * @param px point x\r
82      * @param py point y\r
83      * @return\r
84      */\r
85     public static Point2D intersectionToLine(double x1, double y1, double x2, double y2, double px, double py) {\r
86         double xx2 = x2;\r
87         double yy2 = y2;\r
88         // Adjust vectors relative to x1,y1\r
89         // x2,y2 becomes relative vector from x1,y1 to end of segment\r
90         x2 -= x1;\r
91         y2 -= y1;\r
92         // px,py becomes relative vector from x1,y1 to test point\r
93         px -= x1;\r
94         py -= y1;\r
95         double dotprod = px * x2 + py * y2;\r
96         // dotprod is the length of the px,py vector\r
97         // projected on the x1,y1=>x2,y2 vector times the\r
98         // length of the x1,y1=>x2,y2 vector\r
99         double lineLenSq = x2 * x2 + y2 * y2;\r
100         double lineLen = Math.sqrt(lineLenSq);\r
101         if (lineLen <= Double.MIN_VALUE)\r
102             return new Point2D.Double(x1, y1);\r
103 \r
104         double projLen = dotprod / lineLen;\r
105 \r
106         // Check whether the projection of (px,py) is outside the specified line.\r
107         if (projLen < 0) {\r
108             return new Point2D.Double(x1, y1);\r
109         } else if (projLen > lineLen) {\r
110             return new Point2D.Double(xx2, yy2);\r
111         }\r
112         return new Point2D.Double(x1 + x2/lineLen*projLen, y1 + y2/lineLen*projLen);\r
113     }\r
114 \r
115     /**\r
116      * Expands margins to a rectangle\r
117      * @param rect\r
118      * @param top\r
119      * @param bottom\r
120      * @param left\r
121      * @param right\r
122      */\r
123     public static Rectangle2D expandRectangle(Rectangle2D rect, double top, double bottom, double left, double right)\r
124     {\r
125         if (rect==null) throw new IllegalArgumentException("null arg");\r
126         rect.setRect(\r
127                 rect.getX() - left,\r
128                 rect.getY() - top,\r
129                 rect.getWidth() + left + right,\r
130                 rect.getHeight() + top + bottom);\r
131         return rect;\r
132     }\r
133 \r
134     public static Rectangle2D expandRectangle(Rectangle2D rect, double horizontalExpand, double verticalExpand) {\r
135         return expandRectangle(rect, verticalExpand, verticalExpand, horizontalExpand, horizontalExpand);\r
136     }\r
137 \r
138     public static Rectangle2D expandRectangle(Rectangle2D rect, double evenExpand) {\r
139         return expandRectangle(rect, evenExpand, evenExpand, evenExpand, evenExpand);\r
140     }\r
141 \r
142     public static BasicStroke scaleStroke(Stroke stroke, float factor)\r
143     {\r
144         return scaleAndOffsetStroke(stroke, factor, 0.0f);\r
145     }\r
146 \r
147     public static BasicStroke offsetStroke(Stroke stroke, float offset)\r
148     {\r
149         BasicStroke s = (BasicStroke) stroke;\r
150         float[] dash = s.getDashArray();\r
151         if (dash == null)\r
152             return s;\r
153 \r
154         return new BasicStroke(\r
155                 s.getLineWidth(),\r
156                 s.getEndCap(),\r
157                 s.getLineJoin(),\r
158                 s.getMiterLimit(),\r
159                 dash,\r
160                 s.getDashPhase() + offset\r
161         );\r
162     }\r
163 \r
164     public static BasicStroke scaleAndOffsetStroke(Stroke stroke, float factor, float offset)\r
165     {\r
166         BasicStroke s = (BasicStroke) stroke;\r
167         float[] dash = s.getDashArray();\r
168         if (dash!=null) {\r
169             assert(factor!=0);\r
170             dash = scaleArray(factor, dash, new float[dash.length]);\r
171         }\r
172         if (dash==null)\r
173             return new BasicStroke(\r
174                     s.getLineWidth() * factor,\r
175                     s.getEndCap(),\r
176                     s.getLineJoin(),\r
177                     s.getMiterLimit()\r
178             );\r
179         return new BasicStroke(\r
180                 s.getLineWidth() * factor,\r
181                 s.getEndCap(),\r
182                 s.getLineJoin(),\r
183                 s.getMiterLimit(),\r
184                 dash,\r
185                 s.getDashPhase() * factor + offset\r
186         );\r
187     }\r
188 \r
189     public static BasicStroke scaleStrokeWidth(Stroke stroke, float factor)\r
190     {\r
191         BasicStroke s = (BasicStroke) stroke;\r
192         return new BasicStroke(\r
193                 s.getLineWidth() * factor,\r
194                 s.getEndCap(),\r
195                 s.getLineJoin(),\r
196                 s.getMiterLimit(),\r
197                 s.getDashArray(),\r
198                 s.getDashPhase()\r
199         );\r
200     }\r
201 \r
202     public static BasicStroke scaleAndOffsetStrokeWidth(Stroke stroke, float factor, float widthOffset)\r
203     {\r
204         BasicStroke s = (BasicStroke) stroke;\r
205         return new BasicStroke(\r
206                 s.getLineWidth() * factor + widthOffset,\r
207                 s.getEndCap(),\r
208                 s.getLineJoin(),\r
209                 s.getMiterLimit(),\r
210                 s.getDashArray(),\r
211                 s.getDashPhase()\r
212         );\r
213     }\r
214 \r
215     /**\r
216      * Scales every element in array\r
217      * @param array\r
218      * @param factor\r
219      * @return new scaled array\r
220      */\r
221     public static float[] scaleArray(float factor, float [] array, float [] targetArray)\r
222     {\r
223         assert(array!=null);\r
224         if (targetArray==null)\r
225             targetArray = new float[array.length];\r
226         for (int i=0; i<array.length; i++)\r
227         {\r
228             targetArray[i] = array[i] * factor;\r
229         }\r
230         return targetArray;\r
231     }\r
232 \r
233     public static double getScale(AffineTransform at)\r
234     {\r
235         double m00 = at.getScaleX();\r
236         double m11 = at.getScaleY();\r
237         double m10 = at.getShearY();\r
238         double m01 = at.getShearX();\r
239 \r
240         return Math.sqrt(Math.abs(m00*m11 - m10*m01));\r
241     }\r
242 \r
243     public static Point2D getScale2D(AffineTransform at)\r
244     {\r
245         double m00 = at.getScaleX();\r
246         double m11 = at.getScaleY();\r
247         double m10 = at.getShearY();\r
248         double m01 = at.getShearX();\r
249         double sx = Math.sqrt(m00 * m00 + m10 * m10);\r
250         double sy = Math.sqrt(m01 * m01 + m11 * m11);\r
251         return new Point2D.Double(sx, sy);\r
252     }\r
253 \r
254     /**\r
255      * Computes the greatest absolute value of the eigenvalues\r
256      * of the matrix.\r
257      */\r
258     public static double getMaxScale(AffineTransform at)\r
259     {\r
260         double m00 = at.getScaleX();\r
261         double m11 = at.getScaleY();\r
262         double m10 = at.getShearY();\r
263         double m01 = at.getShearX();\r
264 \r
265         /*\r
266          * If a and b are the eigenvalues of the matrix,\r
267          * then\r
268          *   trace       = a + b\r
269          *   determinant = a*b\r
270          */\r
271         double trace = m00 + m11;\r
272         double determinant = m00*m11 - m10*m01;\r
273 \r
274         double dd = trace*trace*0.25 - determinant;\r
275         if(dd >= 0.0) {\r
276             /*\r
277              * trace/2 +- sqrt(trace^2 / 4 - determinant)\r
278              * = (a+b)/2 +- sqrt((a+b)^2 / 4 - a b)\r
279              * = (a+b)/2 +- sqrt(a^2 / 4 + a b / 2 + b^2 / 4 - a b)\r
280              * = (a+b)/2 +- sqrt(a^2 / 4 - a b / 2 + b^2 / 4)\r
281              * = (a+b)/2 +- (a-b)/2\r
282              * = a or b\r
283              * \r
284              * Thus the formula below calculates the greatest\r
285              * absolute value of the eigenvalues max(abs(a), abs(b))\r
286              */\r
287             return Math.abs(trace*0.5) + Math.sqrt(dd);\r
288         }\r
289         else {\r
290             /*\r
291              * If dd < 0, then the eigenvalues a and b are not real.\r
292              * Because both trace and determinant are real, a and b\r
293              * have form:\r
294              *   a = x + i y\r
295              *   b = x - i y\r
296              * \r
297              * Then\r
298              *    sqrt(determinant)\r
299              *    = sqrt(a b)\r
300              *    = sqrt(x^2 + y^2),\r
301              * which is the absolute value of the eigenvalues.\r
302              */\r
303             return Math.sqrt(determinant);\r
304         }\r
305     }\r
306 \r
307     /**\r
308      * Get a transform that makes canvas area fully visible.\r
309      * @param controlArea\r
310      * @param diagramArea\r
311      * @param margins margins\r
312      * @return transform\r
313      */\r
314     public static AffineTransform fitArea(Rectangle2D controlArea, Rectangle2D diagramArea)\r
315     {\r
316         double controlAspectRatio = controlArea.getWidth() / controlArea.getHeight();\r
317         double canvasAspectRatio  = diagramArea.getWidth() / diagramArea.getHeight();\r
318         // Control is really wide => center canvas horizontally, match vertically\r
319         double scale = 1.0;\r
320         double tx = 0.0;\r
321         double ty = 0.0;\r
322         if (controlAspectRatio>canvasAspectRatio)\r
323         {\r
324             scale = controlArea.getHeight() / diagramArea.getHeight();\r
325             tx = ( controlArea.getWidth() - diagramArea.getWidth() * scale ) / 2;\r
326         } else\r
327             // Control is really tall => center canvas vertically, match horizontally\r
328         {\r
329             scale = controlArea.getWidth() / diagramArea.getWidth();\r
330             ty = ( controlArea.getHeight() - diagramArea.getHeight() * scale ) / 2;\r
331         }\r
332         AffineTransform at = new AffineTransform();\r
333         at.translate(tx, ty);\r
334         at.translate(controlArea.getMinX(), controlArea.getMinY());\r
335         at.scale(scale, scale);\r
336         at.translate(-diagramArea.getMinX(), -diagramArea.getMinY());\r
337         //System.out.println("FIT TRANSFORM: " + at);\r
338         return at;\r
339     }\r
340 \r
341     /**\r
342      * Rotates rectangle. Note, general rotation is not supported.\r
343      * @param transform\r
344      * @param rect\r
345      * @return transformed rectangle\r
346      */\r
347     public static Rectangle2D transformRectangle(AffineTransform transform, Rectangle2D rect) {\r
348         int type = transform.getType();\r
349         if (type == AffineTransform.TYPE_IDENTITY)\r
350             return new Rectangle2D.Double(rect.getMinX(), rect.getMinY(), rect.getWidth(), rect.getHeight());\r
351         if ((type & (AffineTransform.TYPE_GENERAL_ROTATION|AffineTransform.TYPE_GENERAL_TRANSFORM)) == 0) {\r
352             double x0 = rect.getMinX();\r
353             double y0 = rect.getMinY();\r
354             double x1 = rect.getMaxX();\r
355             double y1 = rect.getMaxY();\r
356             double m00 = transform.getScaleX();\r
357             double m10 = transform.getShearY();\r
358             double m01 = transform.getShearX();\r
359             double m11 = transform.getScaleY();\r
360             double X0, Y0, X1, Y1;\r
361             if(m00 > 0.0) {\r
362                 X0 = m00 * x0;\r
363                 X1 = m00 * x1;\r
364             }\r
365             else {\r
366                 X1 = m00 * x0;\r
367                 X0 = m00 * x1;\r
368             }\r
369             if(m01 > 0.0) {\r
370                 X0 += m01 * y0;\r
371                 X1 += m01 * y1;\r
372             }\r
373             else {\r
374                 X1 += m01 * y0;\r
375                 X0 += m01 * y1;\r
376             }\r
377             if(m10 > 0.0) {\r
378                 Y0 = m10 * x0;\r
379                 Y1 = m10 * x1;\r
380             }\r
381             else {\r
382                 Y1 = m10 * x0;\r
383                 Y0 = m10 * x1;\r
384             }\r
385             if(m11 > 0.0) {\r
386                 Y0 += m11 * y0;\r
387                 Y1 += m11 * y1;\r
388             }\r
389             else {\r
390                 Y1 += m11 * y0;\r
391                 Y0 += m11 * y1;\r
392             }\r
393             return new Rectangle2D.Double(X0+transform.getTranslateX(),\r
394                     Y0+transform.getTranslateY(), X1-X0, Y1-Y0);\r
395         }\r
396     \r
397         // Generic but much slower.\r
398         return transform.createTransformedShape(rect).getBounds2D();\r
399     }\r
400 \r
401     public static Rectangle2D transformRectangleInv(AffineTransform transform, Rectangle2D rect) {\r
402         assert( (transform.getType() & (AffineTransform.TYPE_GENERAL_ROTATION|AffineTransform.TYPE_GENERAL_TRANSFORM)) == 0);\r
403         double[] mx = new double[6];\r
404         transform.getMatrix(mx);\r
405         double x0 = rect.getMinX() - mx[4];\r
406         double y0 = rect.getMinY() - mx[5];\r
407         double x1 = rect.getMaxX() - mx[4];\r
408         double y1 = rect.getMaxY() - mx[5];\r
409         double det = mx[0]*mx[3] - mx[1]*mx[2];\r
410         double m00 = mx[3] / det;\r
411         double m10 = -mx[1] / det;\r
412         double m01 = -mx[2] / det;\r
413         double m11 = mx[0] / det;\r
414         double X0, Y0, X1, Y1;\r
415         if(m00 > 0.0) {\r
416             X0 = m00 * x0;\r
417             X1 = m00 * x1;\r
418         }\r
419         else {\r
420             X1 = m00 * x0;\r
421             X0 = m00 * x1;\r
422         }\r
423         if(m01 > 0.0) {\r
424             X0 += m01 * y0;\r
425             X1 += m01 * y1;\r
426         }\r
427         else {\r
428             X1 += m01 * y0;\r
429             X0 += m01 * y1;\r
430         }\r
431         if(m10 > 0.0) {\r
432             Y0 = m10 * x0;\r
433             Y1 = m10 * x1;\r
434         }\r
435         else {\r
436             Y1 = m10 * x0;\r
437             Y0 = m10 * x1;\r
438         }\r
439         if(m11 > 0.0) {\r
440             Y0 += m11 * y0;\r
441             Y1 += m11 * y1;\r
442         }\r
443         else {\r
444             Y1 += m11 * y0;\r
445             Y0 += m11 * y1;\r
446         }\r
447         return new Rectangle2D.Double(X0, Y0, X1-X0, Y1-Y0);\r
448     }\r
449 \r
450     /**\r
451      * @param points\r
452      * @return millimeters\r
453      */\r
454     public static float pointToMillimeter(float points) {\r
455         return points * 25.4f / 72.0f;\r
456     }\r
457 \r
458     /**\r
459      * @param points\r
460      * @return millimeters\r
461      */\r
462     public static double pointToMillimeter(double points) {\r
463         return points * 25.4 / 72.0;\r
464     }\r
465 \r
466     /**\r
467      * Get a transform that makes canvas area fully visible.\r
468      * @param controlArea\r
469      * @param diagramArea\r
470      * @param margins margins\r
471      * @return transform\r
472      */\r
473     public static AffineTransform fitArea(Rectangle2D controlArea, Rectangle2D diagramArea, Margins margins)\r
474     {\r
475         diagramArea = expandRectangle(diagramArea,\r
476                 margins.top.diagramAbsolute,\r
477                 margins.bottom.diagramAbsolute,\r
478                 margins.left.diagramAbsolute,\r
479                 margins.right.diagramAbsolute);\r
480         controlArea = expandRectangle(controlArea,\r
481                 -margins.top.controlAbsolute    - margins.top.controlRelative * controlArea.getHeight(),\r
482                 -margins.bottom.controlAbsolute - margins.bottom.controlRelative * controlArea.getHeight(),\r
483                 -margins.left.controlAbsolute   - margins.left.controlRelative * controlArea.getWidth(),\r
484                 -margins.right.controlAbsolute  - margins.right.controlRelative * controlArea.getWidth());\r
485 \r
486         double controlAspectRatio = controlArea.getWidth() / controlArea.getHeight();\r
487         double canvasAspectRatio  = diagramArea.getWidth() / diagramArea.getHeight();\r
488         // Control is really wide => center canvas horizontally, match vertically\r
489         double scale = 1.0;\r
490         double tx = 0.0;\r
491         double ty = 0.0;\r
492         if (controlAspectRatio>canvasAspectRatio)\r
493         {\r
494             scale = controlArea.getHeight() / diagramArea.getHeight();\r
495             tx = ( controlArea.getWidth() - diagramArea.getWidth() * scale ) / 2;\r
496         } else\r
497             // Control is really tall => center canvas vertically, match horizontally\r
498         {\r
499             scale = controlArea.getWidth() / diagramArea.getWidth();\r
500             ty = ( controlArea.getHeight() - diagramArea.getHeight() * scale ) / 2;\r
501         }\r
502         AffineTransform at = new AffineTransform();\r
503         at.translate(tx, ty);\r
504         at.translate(controlArea.getMinX(), controlArea.getMinY());\r
505         at.scale(scale, scale);\r
506         at.translate(-diagramArea.getMinX(), -diagramArea.getMinY());\r
507         //System.out.println("FIT TRANSFORM: " + at);\r
508         return at;\r
509     }\r
510     \r
511 }\r