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.scenegraph.utils;
\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
20 import org.simantics.utils.page.MarginUtils.Margins;
\r
23 * Basic utilities for geometric calculations and testing.
\r
26 * @author Tuukka Lehtonen
\r
28 public final class GeometryUtils {
\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
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
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
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
54 // px,py becomes relative vector from x1,y1 to test point
\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
65 // Check whether the projection of (px,py) is outside the specified line.
\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
73 return Math.sqrt(px * px + py * py - projLen * projLen);
\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
85 public static Point2D intersectionToLine(double x1, double y1, double x2, double y2, double px, double py) {
\r
88 // Adjust vectors relative to x1,y1
\r
89 // x2,y2 becomes relative vector from x1,y1 to end of segment
\r
92 // px,py becomes relative vector from x1,y1 to test point
\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
104 double projLen = dotprod / lineLen;
\r
106 // Check whether the projection of (px,py) is outside the specified line.
\r
108 return new Point2D.Double(x1, y1);
\r
109 } else if (projLen > lineLen) {
\r
110 return new Point2D.Double(xx2, yy2);
\r
112 return new Point2D.Double(x1 + x2/lineLen*projLen, y1 + y2/lineLen*projLen);
\r
116 * Expands margins to a rectangle
\r
123 public static Rectangle2D expandRectangle(Rectangle2D rect, double top, double bottom, double left, double right)
\r
125 if (rect==null) throw new IllegalArgumentException("null arg");
\r
127 rect.getX() - left,
\r
129 rect.getWidth() + left + right,
\r
130 rect.getHeight() + top + bottom);
\r
134 public static Rectangle2D expandRectangle(Rectangle2D rect, double horizontalExpand, double verticalExpand) {
\r
135 return expandRectangle(rect, verticalExpand, verticalExpand, horizontalExpand, horizontalExpand);
\r
138 public static Rectangle2D expandRectangle(Rectangle2D rect, double evenExpand) {
\r
139 return expandRectangle(rect, evenExpand, evenExpand, evenExpand, evenExpand);
\r
142 public static BasicStroke scaleStroke(Stroke stroke, float factor)
\r
144 return scaleAndOffsetStroke(stroke, factor, 0.0f);
\r
147 public static BasicStroke offsetStroke(Stroke stroke, float offset)
\r
149 BasicStroke s = (BasicStroke) stroke;
\r
150 float[] dash = s.getDashArray();
\r
154 return new BasicStroke(
\r
160 s.getDashPhase() + offset
\r
164 public static BasicStroke scaleAndOffsetStroke(Stroke stroke, float factor, float offset)
\r
166 BasicStroke s = (BasicStroke) stroke;
\r
167 float[] dash = s.getDashArray();
\r
170 dash = scaleArray(factor, dash, new float[dash.length]);
\r
173 return new BasicStroke(
\r
174 s.getLineWidth() * factor,
\r
179 return new BasicStroke(
\r
180 s.getLineWidth() * factor,
\r
185 s.getDashPhase() * factor + offset
\r
189 public static BasicStroke scaleStrokeWidth(Stroke stroke, float factor)
\r
191 BasicStroke s = (BasicStroke) stroke;
\r
192 return new BasicStroke(
\r
193 s.getLineWidth() * factor,
\r
202 public static BasicStroke scaleAndOffsetStrokeWidth(Stroke stroke, float factor, float widthOffset)
\r
204 BasicStroke s = (BasicStroke) stroke;
\r
205 return new BasicStroke(
\r
206 s.getLineWidth() * factor + widthOffset,
\r
216 * Scales every element in array
\r
219 * @return new scaled array
\r
221 public static float[] scaleArray(float factor, float [] array, float [] targetArray)
\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
228 targetArray[i] = array[i] * factor;
\r
230 return targetArray;
\r
233 public static double getScale(AffineTransform at)
\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
240 return Math.sqrt(Math.abs(m00*m11 - m10*m01));
\r
243 public static Point2D getScale2D(AffineTransform at)
\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
255 * Computes the greatest absolute value of the eigenvalues
\r
258 public static double getMaxScale(AffineTransform at)
\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
266 * If a and b are the eigenvalues of the matrix,
\r
269 * determinant = a*b
\r
271 double trace = m00 + m11;
\r
272 double determinant = m00*m11 - m10*m01;
\r
274 double dd = trace*trace*0.25 - determinant;
\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
284 * Thus the formula below calculates the greatest
\r
285 * absolute value of the eigenvalues max(abs(a), abs(b))
\r
287 return Math.abs(trace*0.5) + Math.sqrt(dd);
\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
298 * sqrt(determinant)
\r
300 * = sqrt(x^2 + y^2),
\r
301 * which is the absolute value of the eigenvalues.
\r
303 return Math.sqrt(determinant);
\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
314 public static AffineTransform fitArea(Rectangle2D controlArea, Rectangle2D diagramArea)
\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
322 if (controlAspectRatio>canvasAspectRatio)
\r
324 scale = controlArea.getHeight() / diagramArea.getHeight();
\r
325 tx = ( controlArea.getWidth() - diagramArea.getWidth() * scale ) / 2;
\r
327 // Control is really tall => center canvas vertically, match horizontally
\r
329 scale = controlArea.getWidth() / diagramArea.getWidth();
\r
330 ty = ( controlArea.getHeight() - diagramArea.getHeight() * scale ) / 2;
\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
342 * Rotates rectangle. Note, general rotation is not supported.
\r
345 * @return transformed rectangle
\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
393 return new Rectangle2D.Double(X0+transform.getTranslateX(),
\r
394 Y0+transform.getTranslateY(), X1-X0, Y1-Y0);
\r
397 // Generic but much slower.
\r
398 return transform.createTransformedShape(rect).getBounds2D();
\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
447 return new Rectangle2D.Double(X0, Y0, X1-X0, Y1-Y0);
\r
452 * @return millimeters
\r
454 public static float pointToMillimeter(float points) {
\r
455 return points * 25.4f / 72.0f;
\r
460 * @return millimeters
\r
462 public static double pointToMillimeter(double points) {
\r
463 return points * 25.4 / 72.0;
\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
473 public static AffineTransform fitArea(Rectangle2D controlArea, Rectangle2D diagramArea, Margins margins)
\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
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
492 if (controlAspectRatio>canvasAspectRatio)
\r
494 scale = controlArea.getHeight() / diagramArea.getHeight();
\r
495 tx = ( controlArea.getWidth() - diagramArea.getWidth() * scale ) / 2;
\r
497 // Control is really tall => center canvas vertically, match horizontally
\r
499 scale = controlArea.getWidth() / diagramArea.getWidth();
\r
500 ty = ( controlArea.getHeight() - diagramArea.getHeight() * scale ) / 2;
\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