1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
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
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.scenegraph.utils;
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;
20 import org.simantics.utils.page.MarginUtils.Margins;
23 * Basic utilities for geometric calculations and testing.
26 * @author Tuukka Lehtonen
28 public final class GeometryUtils {
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
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());
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
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
54 // px,py becomes relative vector from x1,y1 to test point
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;
65 // Check whether the projection of (px,py) is outside the specified line.
67 return Math.sqrt(px * px + py * py);
68 } else if (projLen > lineLen) {
71 return Math.sqrt(dx * dx + dy * dy);
73 return Math.sqrt(px * px + py * py - projLen * projLen);
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
85 public static Point2D intersectionToLine(double x1, double y1, double x2, double y2, double px, double py) {
88 // Adjust vectors relative to x1,y1
89 // x2,y2 becomes relative vector from x1,y1 to end of segment
92 // px,py becomes relative vector from x1,y1 to test point
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);
104 double projLen = dotprod / lineLen;
106 // Check whether the projection of (px,py) is outside the specified line.
108 return new Point2D.Double(x1, y1);
109 } else if (projLen > lineLen) {
110 return new Point2D.Double(xx2, yy2);
112 return new Point2D.Double(x1 + x2/lineLen*projLen, y1 + y2/lineLen*projLen);
116 * Expands margins to a rectangle
123 public static Rectangle2D expandRectangle(Rectangle2D rect, double top, double bottom, double left, double right)
125 if (rect==null) throw new IllegalArgumentException("null arg");
129 rect.getWidth() + left + right,
130 rect.getHeight() + top + bottom);
134 public static Rectangle2D expandRectangle(Rectangle2D rect, double horizontalExpand, double verticalExpand) {
135 return expandRectangle(rect, verticalExpand, verticalExpand, horizontalExpand, horizontalExpand);
138 public static Rectangle2D expandRectangle(Rectangle2D rect, double evenExpand) {
139 return expandRectangle(rect, evenExpand, evenExpand, evenExpand, evenExpand);
142 public static BasicStroke scaleStroke(Stroke stroke, float factor)
144 return scaleAndOffsetStroke(stroke, factor, 0.0f);
147 public static BasicStroke offsetStroke(Stroke stroke, float offset)
149 BasicStroke s = (BasicStroke) stroke;
150 float[] dash = s.getDashArray();
154 return new BasicStroke(
160 s.getDashPhase() + offset
164 public static BasicStroke scaleAndOffsetStroke(Stroke stroke, float factor, float offset)
166 BasicStroke s = (BasicStroke) stroke;
167 float[] dash = s.getDashArray();
170 dash = scaleArray(factor, dash, new float[dash.length]);
173 return new BasicStroke(
174 s.getLineWidth() * factor,
179 return new BasicStroke(
180 s.getLineWidth() * factor,
185 s.getDashPhase() * factor + offset
189 public static BasicStroke scaleStrokeWidth(Stroke stroke, float factor)
191 BasicStroke s = (BasicStroke) stroke;
192 return new BasicStroke(
193 s.getLineWidth() * factor,
202 public static BasicStroke scaleAndOffsetStrokeWidth(Stroke stroke, float factor, float widthOffset)
204 BasicStroke s = (BasicStroke) stroke;
205 return new BasicStroke(
206 s.getLineWidth() * factor + widthOffset,
216 * Scales every element in array
219 * @return new scaled array
221 public static float[] scaleArray(float factor, float [] array, float [] targetArray)
224 if (targetArray==null)
225 targetArray = new float[array.length];
226 for (int i=0; i<array.length; i++)
228 targetArray[i] = array[i] * factor;
233 public static double getScale(AffineTransform at)
235 double m00 = at.getScaleX();
236 double m11 = at.getScaleY();
237 double m10 = at.getShearY();
238 double m01 = at.getShearX();
240 return Math.sqrt(Math.abs(m00*m11 - m10*m01));
243 public static Point2D getScale2D(AffineTransform at)
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);
255 * Computes the greatest absolute value of the eigenvalues
258 public static double getMaxScale(AffineTransform at)
260 double m00 = at.getScaleX();
261 double m11 = at.getScaleY();
262 double m10 = at.getShearY();
263 double m01 = at.getShearX();
266 * If a and b are the eigenvalues of the matrix,
271 double trace = m00 + m11;
272 double determinant = m00*m11 - m10*m01;
274 double dd = trace*trace*0.25 - determinant;
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
284 * Thus the formula below calculates the greatest
285 * absolute value of the eigenvalues max(abs(a), abs(b))
287 return Math.abs(trace*0.5) + Math.sqrt(dd);
291 * If dd < 0, then the eigenvalues a and b are not real.
292 * Because both trace and determinant are real, a and b
301 * which is the absolute value of the eigenvalues.
303 return Math.sqrt(determinant);
308 * Get a transform that makes canvas area fully visible.
311 * @param margins margins
314 public static AffineTransform fitArea(Rectangle2D controlArea, Rectangle2D diagramArea)
316 double controlAspectRatio = controlArea.getWidth() / controlArea.getHeight();
317 double canvasAspectRatio = diagramArea.getWidth() / diagramArea.getHeight();
318 // Control is really wide => center canvas horizontally, match vertically
322 if (controlAspectRatio>canvasAspectRatio)
324 scale = controlArea.getHeight() / diagramArea.getHeight();
325 tx = ( controlArea.getWidth() - diagramArea.getWidth() * scale ) / 2;
327 // Control is really tall => center canvas vertically, match horizontally
329 scale = controlArea.getWidth() / diagramArea.getWidth();
330 ty = ( controlArea.getHeight() - diagramArea.getHeight() * scale ) / 2;
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);
342 * Rotates rectangle. Note, general rotation is not supported.
345 * @return transformed rectangle
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;
393 return new Rectangle2D.Double(X0+transform.getTranslateX(),
394 Y0+transform.getTranslateY(), X1-X0, Y1-Y0);
397 // Generic but much slower.
398 return transform.createTransformedShape(rect).getBounds2D();
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;
447 return new Rectangle2D.Double(X0, Y0, X1-X0, Y1-Y0);
452 * @return millimeters
454 public static float pointToMillimeter(float points) {
455 return points * 25.4f / 72.0f;
460 * @return millimeters
462 public static double pointToMillimeter(double points) {
463 return points * 25.4 / 72.0;
467 * Get a transform that makes canvas area fully visible.
470 * @param margins margins
473 public static AffineTransform fitArea(Rectangle2D controlArea, Rectangle2D diagramArea, Margins margins)
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());
486 double controlAspectRatio = controlArea.getWidth() / controlArea.getHeight();
487 double canvasAspectRatio = diagramArea.getWidth() / diagramArea.getHeight();
488 // Control is really wide => center canvas horizontally, match vertically
492 if (controlAspectRatio>canvasAspectRatio)
494 scale = controlArea.getHeight() / diagramArea.getHeight();
495 tx = ( controlArea.getWidth() - diagramArea.getWidth() * scale ) / 2;
497 // Control is really tall => center canvas vertically, match horizontally
499 scale = controlArea.getWidth() / diagramArea.getWidth();
500 ty = ( controlArea.getHeight() - diagramArea.getHeight() * scale ) / 2;
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);