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