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.g2d.utils;
\r
14 import java.awt.BasicStroke;
\r
15 import java.awt.Color;
\r
16 import java.awt.Polygon;
\r
17 import java.awt.Shape;
\r
18 import java.awt.Stroke;
\r
19 import java.awt.geom.AffineTransform;
\r
20 import java.awt.geom.Area;
\r
21 import java.awt.geom.Ellipse2D;
\r
22 import java.awt.geom.Path2D;
\r
23 import java.awt.geom.PathIterator;
\r
24 import java.awt.geom.Point2D;
\r
25 import java.awt.geom.Rectangle2D;
\r
26 import java.util.List;
\r
28 import org.simantics.scenegraph.utils.TransformedRectangle;
\r
31 * Geometry related Utilities
\r
33 * @see PathUtils2 Path related utilities
\r
35 * @author Toni Kalajainen
\r
36 * @author Tuukka Lehtonen
\r
38 public final class GeometryUtils {
\r
40 //public static final double EPSILON = 1e-7;
\r
42 public static final BasicStroke BASIC_STROKE = new BasicStroke();
\r
44 public static BasicStroke scaleStroke(Stroke stroke, float factor)
\r
46 BasicStroke s = (BasicStroke) stroke;
\r
47 float[] dash = s.getDashArray();
\r
50 dash = scaleArray(factor, dash, new float[dash.length]);
\r
53 return new BasicStroke(
\r
54 s.getLineWidth() * factor,
\r
59 return new BasicStroke(
\r
60 s.getLineWidth() * factor,
\r
65 s.getDashPhase() * factor
\r
72 * Scales every element in array
\r
75 * @return new scaled array
\r
77 public static float[] scaleArray(float factor, float [] array, float [] targetArray)
\r
79 assert(array!=null);
\r
80 if (targetArray==null)
\r
81 targetArray = new float[array.length];
\r
82 for (int i=0; i<array.length; i++)
\r
84 targetArray[i] = array[i] * factor;
\r
90 * Get x/y scale of a transform
\r
95 public static Point2D getScaleXY(AffineTransform at, Point2D pt)
\r
97 double m00 = at.getScaleX();
\r
98 double m11 = at.getScaleY();
\r
99 double m10 = at.getShearY();
\r
100 double m01 = at.getShearX();
\r
101 // Project unit vector to canvas
\r
102 double sx = Math.sqrt( m00*m00+m10*m10 );
\r
103 double sy = Math.sqrt( m01*m01+m11*m11 );
\r
104 if (pt==null) pt = new Point2D.Double();
\r
105 pt.setLocation(sx, sy);
\r
110 * Get scale of a transform
\r
116 public static double getScale(AffineTransform at)
\r
118 double m00 = at.getScaleX();
\r
119 double m11 = at.getScaleY();
\r
120 double m10 = at.getShearY();
\r
121 double m01 = at.getShearX();
\r
122 // Project unit vector to canvas
\r
123 double sx = Math.sqrt( m00*m00+m10*m10 );
\r
124 double sy = Math.sqrt( m01*m01+m11*m11 );
\r
125 return Math.sqrt(sx*sx+sy*sy);
\r
129 public static double getScale(AffineTransform at)
\r
131 double m00 = at.getScaleX();
\r
132 double m11 = at.getScaleY();
\r
133 double m10 = at.getShearY();
\r
134 double m01 = at.getShearX();
\r
136 double a = m00 + m11;
\r
137 double b = 4.0 * m10*m01 + (m00 - m11) * (m00 - m11);
\r
139 return 0.5 * Math.sqrt(a*a + b);
\r
141 return 0.5 * (Math.abs(a) + Math.sqrt(b));
\r
144 public static double getScale(AffineTransform at)
\r
146 double m00 = at.getScaleX();
\r
147 double m11 = at.getScaleY();
\r
148 double m10 = at.getShearY();
\r
149 double m01 = at.getShearX();
\r
151 return Math.sqrt(Math.abs(m00*m11 - m10*m01));
\r
155 * Computes the greatest absolute value of the eigenvalues
\r
158 public static double getMaxScale(AffineTransform at)
\r
160 double m00 = at.getScaleX();
\r
161 double m11 = at.getScaleY();
\r
162 double m10 = at.getShearY();
\r
163 double m01 = at.getShearX();
\r
166 * If a and b are the eigenvalues of the matrix,
\r
169 * determinant = a*b
\r
171 double trace = m00 + m11;
\r
172 double determinant = m00*m11 - m10*m01;
\r
174 double dd = trace*trace*0.25 - determinant;
\r
177 * trace/2 +- sqrt(trace^2 / 4 - determinant)
\r
178 * = (a+b)/2 +- sqrt((a+b)^2 / 4 - a b)
\r
179 * = (a+b)/2 +- sqrt(a^2 / 4 + a b / 2 + b^2 / 4 - a b)
\r
180 * = (a+b)/2 +- sqrt(a^2 / 4 - a b / 2 + b^2 / 4)
\r
181 * = (a+b)/2 +- (a-b)/2
\r
184 * Thus the formula below calculates the greatest
\r
185 * absolute value of the eigenvalues max(abs(a), abs(b))
\r
187 return Math.abs(trace*0.5) + Math.sqrt(dd);
\r
191 * If dd < 0, then the eigenvalues a and b are not real.
\r
192 * Because both trace and determinant are real, a and b
\r
198 * sqrt(determinant)
\r
200 * = sqrt(x^2 + y^2),
\r
201 * which is the absolute value of the eigenvalues.
\r
203 return Math.sqrt(determinant);
\r
208 * Intersect test of two shapes
\r
213 public static boolean intersects(Shape s1, Shape s2)
\r
215 if (s1==s2) return true;
\r
216 if (s1.equals(s2)) return true;
\r
217 if (s1 instanceof Rectangle2D)
\r
218 return s2.intersects((Rectangle2D) s1);
\r
219 if (s2 instanceof Rectangle2D)
\r
220 return s1.intersects((Rectangle2D) s2);
\r
221 if (s1 instanceof TransformedRectangle)
\r
222 return ((TransformedRectangle)s1).intersects(s2);
\r
223 if (s2 instanceof TransformedRectangle)
\r
224 return ((TransformedRectangle)s2).intersects(s1);
\r
226 // VERY SLOW IMPLEMENTATION
\r
227 // Convert shapes to areas and intersect them
\r
228 Area a1 = new Area(s1);
\r
229 Area a2 = new Area(s2);
\r
231 return !a1.isEmpty();
\r
235 * Contains test of two shapes
\r
238 * @return <code>true</code> if s1 contains s2, else <code>false</code>
\r
240 public static boolean contains(Shape s1, Shape s2)
\r
242 if (s1==s2) return true;
\r
243 if (s1.equals(s2)) return true;
\r
244 if (s2 instanceof Rectangle2D)
\r
245 return s1.contains((Rectangle2D) s2);
\r
246 if (s1 instanceof Rectangle2D)
\r
247 return contains((Rectangle2D)s1, s2);
\r
248 if (s1 instanceof TransformedRectangle)
\r
249 return ((TransformedRectangle)s1).contains(s2);
\r
251 // VERY SLOW IMPLEMENTATION
\r
252 // Convert shapes to areas and intersect them
\r
253 Area a1 = new Area(s1);
\r
254 Area a2 = new Area(s2);
\r
256 return a2.isEmpty();
\r
260 * Tests if rectangle contains a shape
\r
261 * @param r rectangle
\r
262 * @param s shape to be tested
\r
263 * @return <code>true</code> if r contains s, else <code>false</code>
\r
265 public static boolean contains(Rectangle2D r, Shape s)
\r
267 // Rectangle contains a shape if
\r
268 // all points of the shape are contained in r
\r
269 PathIterator pi = s.getPathIterator(null, Double.MAX_VALUE);
\r
270 double coords[] = new double[6];
\r
271 while (!pi.isDone()) {
\r
272 int type = pi.currentSegment(coords);
\r
273 if (type == PathIterator.SEG_MOVETO || type == PathIterator.SEG_LINETO)
\r
275 if (!r.contains(coords[0], coords[1]))
\r
278 assert(type != PathIterator.SEG_CUBICTO && type != PathIterator.SEG_QUADTO);
\r
285 * Creates new transformed shape
\r
286 * @param s shape to be transformed
\r
287 * @param t transform
\r
288 * @return new transformed shape
\r
290 public static Shape transformShape(Shape s, AffineTransform t)
\r
292 if (t.isIdentity()) return s;
\r
293 if (s instanceof Rectangle2D) {
\r
294 Rectangle2D rect = (Rectangle2D) s;
\r
295 int type = t.getType();
\r
296 if (type == AffineTransform.TYPE_IDENTITY) {
\r
297 Rectangle2D r = new Rectangle2D.Double();
\r
301 if ((type & (AffineTransform.TYPE_GENERAL_ROTATION|AffineTransform.TYPE_GENERAL_TRANSFORM) ) != 0)
\r
302 return new TransformedRectangle(rect, t);
\r
303 return org.simantics.scenegraph.utils.GeometryUtils.transformRectangle(t, rect);
\r
305 if (s instanceof TransformedRectangle)
\r
307 TransformedRectangle tr = (TransformedRectangle) s;
\r
308 TransformedRectangle result = new TransformedRectangle(tr);
\r
309 result.concatenate(t);
\r
313 //return t.createTransformedShape(s);
\r
314 //return new Area(s).createTransformedArea(t);
\r
315 Area result = new Area(s);
\r
316 result.transform(t);
\r
321 * Get compass direction. 0 = north, clockwise
\r
325 public static double getCompassDirection(Point2D pt) {
\r
326 return getCompassDirection(pt.getX(), pt.getY());
\r
330 * Get compass direction for the vector p1→p2. 0 = north, clockwise
\r
332 * @param p1 the start of the vector
\r
333 * @param p2 the end of the vector
\r
334 * @return compass direction
\r
336 public static double getCompassDirection(Point2D p1, Point2D p2) {
\r
337 double dx = p2.getX() - p1.getX();
\r
338 double dy = p2.getY() - p1.getY();
\r
339 return getCompassDirection(dx, dy);
\r
343 * Get compass direction. 0 = north, clockwise
\r
347 public static double getCompassDirection(double x, double y) {
\r
348 double rad = Math.atan2(y, x);
\r
349 double deg = rad*180.0 / Math.PI + 90.0;
\r
350 if (deg<0) deg = 360+deg;
\r
355 * Converts compass direction to unit vector
\r
356 * @param deg compass direction
\r
360 public static Point2D toUnitVector(double deg, Point2D uv)
\r
362 if (uv==null) uv = new Point2D.Double();
\r
366 } else if (deg==90) {
\r
368 } else if (deg==180) {
\r
370 } else if (deg==270) {
\r
373 double rad = (deg-90)*Math.PI/180.0;
\r
377 uv.setLocation(x, y);
\r
382 * Convert compass direction to radian presentation (0=right, CCW)
\r
386 public static double compassToRad(double deg) {
\r
387 double rad = (deg-90)*Math.PI/180.0;
\r
393 * Interpolate between two colors
\r
396 * @param phase 0..1 (0=c1, 1=c2)
\r
399 public static Color interpolate(Color c1, Color c2, double phase)
\r
401 float r = (c1.getRed()/255.0f)*(1-(float)phase) + (c2.getRed()/255.0f)*((float)phase);
\r
402 float g = (c1.getGreen()/255.0f)*(1-(float)phase) + (c2.getGreen()/255.0f)*((float)phase);
\r
403 float b = (c1.getBlue()/255.0f)*(1-(float)phase) + (c2.getBlue()/255.0f)*((float)phase);
\r
404 float a = (c1.getAlpha()/255.0f)*(1-(float)phase) + (c2.getAlpha()/255.0f)*((float)phase);
\r
405 return new Color(r, g, b, a);
\r
408 public static Path2D buildPath(List<Point2D> positions)
\r
410 Path2D result = new Path2D.Double();
\r
411 if (positions.size()==0) return result;
\r
412 Point2D pos = positions.get(0);
\r
413 result.moveTo(pos.getX(), pos.getY());
\r
414 for (int i=1; i<positions.size(); i++)
\r
416 pos = positions.get(i);
\r
417 result.lineTo(pos.getX(), pos.getY());
\r
423 * Get path positions
\r
426 * @param positions positions
\r
428 public static void getPoints(Path2D path, List<Point2D> positions)
\r
430 PathIterator pi = path.getPathIterator(null);
\r
431 double mat[] = new double[6];
\r
432 while (!pi.isDone()) {
\r
433 pi.currentSegment(mat);
\r
434 positions.add(new Point2D.Double(mat[0], mat[1]));
\r
439 // Tests intersects and contains
\r
440 public static void main(String[] args) {
\r
442 System.out.println(toUnitVector(getCompassDirection(0, -1), null));
\r
443 System.out.println(toUnitVector(getCompassDirection(1, -1), null));
\r
444 System.out.println(toUnitVector(getCompassDirection(1, 0), null));
\r
445 System.out.println(toUnitVector(getCompassDirection(1, 1), null));
\r
446 System.out.println(toUnitVector(getCompassDirection(0, 1), null));
\r
447 System.out.println(toUnitVector(getCompassDirection(-1, 1), null));
\r
448 System.out.println(toUnitVector(getCompassDirection(-1, 0), null));
\r
449 System.out.println(toUnitVector(getCompassDirection(-1,-1), null));
\r
451 System.out.println(getCompassDirection(0, -1));
\r
452 System.out.println(getCompassDirection(1, -1));
\r
453 System.out.println(getCompassDirection(1, 0));
\r
454 System.out.println(getCompassDirection(1, 1));
\r
455 System.out.println(getCompassDirection(0, 1));
\r
456 System.out.println(getCompassDirection(-1, 1));
\r
457 System.out.println(getCompassDirection(-1, 0));
\r
458 System.out.println(getCompassDirection(-1,-1));
\r
460 System.out.println(getCompassDirection(new Point2D.Double(0, 0), new Point2D.Double(0, -1)));
\r
461 System.out.println(getCompassDirection(new Point2D.Double(0, 0), new Point2D.Double(1, 0)));
\r
462 System.out.println(getCompassDirection(new Point2D.Double(0, 0), new Point2D.Double(0, 1)));
\r
463 System.out.println(getCompassDirection(new Point2D.Double(0, 0), new Point2D.Double(-1, 0)));
\r
465 Shape s1 = new Polygon(new int[]{10,10,0}, new int[]{0,10,10}, 3);
\r
466 Shape s2 = new Ellipse2D.Double(0,0,5,5);
\r
467 Shape s3 = new Ellipse2D.Double(8,8,4,4);
\r
468 Shape s4 = new Rectangle2D.Double(-5, 3, 20, 2);
\r
469 Shape s5 = new Rectangle2D.Double(-100, -100, 200, 200);
\r
470 Shape s6 = new Ellipse2D.Double(-100, -100, 200, 200);
\r
472 assert(!intersects(s1, s2) );
\r
473 assert( intersects(s1, s3) );
\r
474 assert( intersects(s1, s4) );
\r
475 assert( intersects(s1, s5) );
\r
477 assert(!intersects(s2, s1) );
\r
478 assert(!intersects(s2, s3) );
\r
479 assert( intersects(s2, s4) );
\r
480 assert( intersects(s2, s5) );
\r
482 assert( intersects(s3, s1) );
\r
483 assert(!intersects(s3, s2) );
\r
484 assert(!intersects(s3, s4) );
\r
485 assert( intersects(s3, s5) );
\r
487 assert( intersects(s4, s1) );
\r
488 assert( intersects(s4, s2) );
\r
489 assert(!intersects(s4, s3) );
\r
490 assert( intersects(s4, s5) );
\r
492 assert( intersects(s5, s1) );
\r
493 assert( intersects(s5, s2) );
\r
494 assert( intersects(s5, s3) );
\r
495 assert( intersects(s5, s4) );
\r
497 assert(!contains(s1, s2) );
\r
498 assert(!contains(s1, s3) );
\r
499 assert(!contains(s1, s4) );
\r
500 assert(!contains(s1, s5) );
\r
502 assert(!contains(s2, s1) );
\r
503 assert(!contains(s2, s3) );
\r
504 assert(!contains(s2, s4) );
\r
505 assert(!contains(s2, s5) );
\r
507 assert(!contains(s3, s1) );
\r
508 assert(!contains(s3, s2) );
\r
509 assert(!contains(s3, s4) );
\r
510 assert(!contains(s3, s5) );
\r
512 assert(!contains(s4, s1) );
\r
513 assert(!contains(s4, s2) );
\r
514 assert(!contains(s4, s3) );
\r
515 assert(!contains(s4, s5) );
\r
517 assert( contains(s5, s1) );
\r
518 assert( contains(s5, s2) );
\r
519 assert( contains(s5, s3) );
\r
520 assert( contains(s5, s4) );
\r
522 assert( contains(s6, s1) );
\r
523 assert( contains(s6, s2) );
\r
524 assert( contains(s6, s3) );
\r
525 assert( contains(s6, s4) );
\r