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