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.Shape;
\r
15 import java.awt.geom.CubicCurve2D;
\r
16 import java.awt.geom.Line2D;
\r
17 import java.awt.geom.Path2D;
\r
18 import java.awt.geom.PathIterator;
\r
19 import java.awt.geom.Point2D;
\r
20 import java.awt.geom.QuadCurve2D;
\r
21 import java.util.ArrayList;
\r
22 import java.util.Collection;
\r
23 import java.util.Iterator;
\r
24 import java.util.List;
\r
29 * A line segment (linear, quadratic or cubic bezier) is described
\r
30 * with a double array. The length of the array describes its degree (4,6,8).
\r
31 * The first 2 elements define start point and last 2 the end point.
\r
32 * Points in the middle are bezier control points.
\r
34 * @See {@link GeometryUtils} for more geometry related utilities
\r
35 * @author Toni Kalajainen
\r
37 public class PathUtils2 {
\r
40 * Get tangent of an bezier
\r
41 * @param lineSegment bezier of n degrees
\r
42 * @param degree 1..3
\r
44 * @return unit vector
\r
46 public static Point2D getLineTangent(Shape line, double t, Point2D pt)
\r
49 if (line instanceof Line2D)
\r
51 Line2D l = (Line2D) line;
\r
52 x = l.getX1() - l.getX2();
\r
53 y = l.getY1() - l.getY2();
\r
54 } else if (line instanceof QuadCurve2D) {
\r
55 QuadCurve2D l = (QuadCurve2D) line;
\r
56 double p0x = l.getX1();
\r
57 double p0y = l.getY1();
\r
58 double p1x = l.getCtrlX();
\r
59 double p1y = l.getCtrlY();
\r
60 double p2x = l.getX2();
\r
61 double p2y = l.getY2();
\r
62 x = 2*t*(p0x - 2*p1x + p2x) + 2*(-p0x + p1x);
\r
63 y = 2*t*(p0y - 2*p1y + p2y) + 2*(-p0y + p1y);
\r
65 } else if (line instanceof CubicCurve2D) {
\r
66 CubicCurve2D l = (CubicCurve2D) line;
\r
67 double p0x = l.getX1();
\r
68 double p0y = l.getY1();
\r
69 double p1x = l.getCtrlX1();
\r
70 double p1y = l.getCtrlY1();
\r
71 double p2x = l.getCtrlX2();
\r
72 double p2y = l.getCtrlY2();
\r
73 double p3x = l.getX2();
\r
74 double p3y = l.getY2();
\r
75 x = 3*(1-t)*(1-t)*(p1x-p2x) + 3*(p2x-p1x)*2*t*(1-t) + 3*(p3x-p0x)*t*t;
\r
76 y = 3*(1-t)*(1-t)*(p1y-p0y) + 3*(p2y-p1y)*2*t*(1-t) + 3*(p3y-p2y)*t*t;
\r
77 } else throw new IllegalArgumentException();
\r
79 return new Point2D.Double(x, y);
\r
84 * @param lineSegment
\r
88 public static Point2D getLinePos(Shape line, double t, Point2D pt)
\r
92 if (pt==null) pt = new Point2D.Double();
\r
94 if (line instanceof Line2D) {
\r
95 Line2D l = (Line2D) line;
\r
96 double p0x = l.getX1();
\r
97 double p0y = l.getY1();
\r
98 double p1x = l.getX2();
\r
99 double p1y = l.getY2();
\r
101 x = p0x*(1-t) + t*p1x;
\r
102 y = p0y*(1-t) + t*p1y;
\r
104 if (line instanceof QuadCurve2D) {
\r
105 QuadCurve2D l = (QuadCurve2D) line;
\r
106 double p0x = l.getX1();
\r
107 double p0y = l.getY1();
\r
108 double p1x = l.getCtrlX();
\r
109 double p1y = l.getCtrlY();
\r
110 double p2x = l.getX2();
\r
111 double p2y = l.getY2();
\r
113 double c2x = p0x-2*p1x+p2x;
\r
114 double c2y = p0y-2*p1y+p2y;
\r
116 double c1x = -2*p0x+2*p1x;
\r
117 double c1y = -2*p0y+2*p1y;
\r
122 x = t*t*c2x+t*c1x+c0x;
\r
123 y = t*t*c2y+t*c1y+c0y;
\r
124 } else if (line instanceof CubicCurve2D) {
\r
125 CubicCurve2D l = (CubicCurve2D) line;
\r
126 double p0x = l.getX1();
\r
127 double p0y = l.getY1();
\r
128 double p1x = l.getCtrlX1();
\r
129 double p1y = l.getCtrlY1();
\r
130 double p2x = l.getCtrlX2();
\r
131 double p2y = l.getCtrlY2();
\r
132 double p3x = l.getX2();
\r
133 double p3y = l.getY2();
\r
135 x = (1-t)*(1-t)*(1-t)*p0x + 3*t*(1-t)*(1-t)*p1x + 3*t*t*(1-t)*p2x + t*t*t*p3x;
\r
136 y = (1-t)*(1-t)*(1-t)*p0y + 3*t*(1-t)*(1-t)*p1y + 3*t*t*(1-t)*p2y + t*t*t*p3y;
\r
137 } else throw new IllegalArgumentException();
\r
138 pt.setLocation(x, y);
\r
143 public static double getLineLength(Shape line)
\r
145 if (line instanceof Line2D) {
\r
146 Line2D l = (Line2D) line;
\r
147 double dx = l.getX2() - l.getX1();
\r
148 double dy = l.getY2() - l.getY1();
\r
149 return Math.sqrt(dx*dx+dy*dy);
\r
152 // Quick'n'dirty approximation
\r
153 // TODO Replace with accurate value
\r
155 Point2D prevPos = getLinePos(line, 0.0, null);
\r
156 for (int i=0; i<10; i++)
\r
158 double t = (double)(i+1)/10;
\r
159 Point2D pos = getLinePos(line, t, null);
\r
160 result += pos.distance(prevPos);
\r
161 prevPos.setLocation(pos);
\r
166 public static int getLineDegree(Shape line)
\r
168 if (line instanceof Line2D) return 1;
\r
169 if (line instanceof QuadCurve2D) return 2;
\r
170 if (line instanceof CubicCurve2D) return 3;
\r
171 throw new IllegalArgumentException(line+" is not a shape");
\r
175 public static double[] toArray(Shape line)
\r
177 if (line instanceof Line2D) {
\r
178 Line2D l = (Line2D) line;
\r
179 return new double[] {l.getX1(), l.getY1(), l.getX2(), l.getY2()};
\r
181 if (line instanceof QuadCurve2D) {
\r
182 QuadCurve2D l = (QuadCurve2D) line;
\r
183 return new double[] {l.getX1(), l.getY1(), l.getCtrlX(), l.getCtrlY(), l.getX2(), l.getY2()};
\r
185 if (line instanceof CubicCurve2D) {
\r
186 CubicCurve2D l = (CubicCurve2D) line;
\r
187 return new double[] {l.getX1(), l.getY1(), l.getCtrlX1(), l.getCtrlY1(), l.getCtrlX2(), l.getCtrlY2(), l.getX2(), l.getY2()};
\r
189 throw new IllegalArgumentException(line+" is not a shape");
\r
192 public static Shape toShape(double dada[])
\r
194 if (dada.length==4)
\r
195 return new Line2D.Double(dada[0], dada[1], dada[2], dada[3]);
\r
196 if (dada.length==6)
\r
197 return new QuadCurve2D.Double(dada[0], dada[1], dada[2], dada[3], dada[4], dada[5]);
\r
198 if (dada.length==8)
\r
199 return new CubicCurve2D.Double(dada[0], dada[1], dada[2], dada[3], dada[4], dada[5], dada[6], dada[7]);
\r
200 throw new IllegalArgumentException();
\r
203 public static void interpolatePaths(List<Shape> l1, List<Shape> l2, double t, Collection<Shape> result)
\r
205 if (l1.size()>l2.size()) {
\r
206 List<Shape> l_ = l1;
\r
212 int div = l2.size() / l1.size();
\r
213 int mod = l2.size() % l1.size();
\r
215 int rightIndex = 0;
\r
216 for (int i=0; i<l1.size(); i++) {
\r
217 int rightCounterParts = i<l1.size()-1 ? (i<mod?div+1:div) : div;
\r
219 Shape leftShape = l1.get(leftIndex);
\r
220 for (int j=0; j<rightCounterParts; j++)
\r
222 Shape rightShape = l2.get(rightIndex++);
\r
223 double inv = 1/(double)rightCounterParts;
\r
225 double t1 = (j+1)*inv;
\r
226 Shape leftShapePart = subdiv(leftShape, t0, t1);
\r
227 Shape intpShape = interpolateLine(leftShapePart, rightShape, t);
\r
228 result.add( intpShape );
\r
234 * Interpolate two paths
\r
237 * @param t phase 0..1, 0==path1, 1==path2
\r
240 public static Path2D interpolatePaths(PathIterator path1, PathIterator path2, double t)
\r
242 ArrayList<Shape> l1 = new ArrayList<Shape>();
\r
243 ArrayList<Shape> l2 = new ArrayList<Shape>();
\r
244 toShapes(path1, l1);
\r
245 toShapes(path2, l2);
\r
246 ArrayList<Shape> result = new ArrayList<Shape>();
\r
247 interpolatePaths(l1, l2, t, result);
\r
248 Path2D p = new Path2D.Double();
\r
249 appedToPath(p, result);
\r
253 public static Path2D interpolatePaths(Path2D path1, Path2D path2, double t)
\r
255 return interpolatePaths(path1.getPathIterator(null), path2.getPathIterator(null), t);
\r
258 public static Shape interpolateLine(Shape l1, Shape l2, double t)
\r
260 assert(t>=0 && t<=1);
\r
261 if (t==0) return l1;
\r
262 if (t==1) return l2;
\r
264 double[] a1 = toArray(l1);
\r
265 double[] a2 = toArray(l2);
\r
266 double[] res = PathUtils.interpolateLineSegment(a1, a2, t);
\r
267 return toShape(res);
\r
270 public static void toShapes(PathIterator pi, Collection<Shape> result)
\r
272 Iterator<Shape> i = toShapeIterator(pi);
\r
273 while (i.hasNext()) {
\r
274 Shape segment = i.next();
\r
275 result.add(segment);
\r
280 * Returns an iterator that splits path into line shapes
\r
281 * @param pi path iterator
\r
282 * @return line segment iterator
\r
284 public static Iterator<Shape> toShapeIterator(final PathIterator pi)
\r
286 return new PathIteratorToShapeIterator(pi);
\r
289 private static class PathIteratorToShapeIterator implements Iterator<Shape>
\r
291 final PathIterator pi;
\r
292 double lineTo[] = new double[6];
\r
293 double startPos[] = new double[2];
\r
294 double from[] = new double[2];
\r
296 PathIteratorToShapeIterator(PathIterator pi) {
\r
298 while(!pi.isDone()) {
\r
299 int type = pi.currentSegment(lineTo);
\r
301 if (type == PathIterator.SEG_MOVETO) {
\r
302 startPos[0] = from[0] = lineTo[0];
\r
303 startPos[1] = from[1] = lineTo[1];
\r
305 if (type == PathIterator.SEG_CLOSE) {
\r
306 type = PathIterator.SEG_LINETO;
\r
307 lineTo[0] = startPos[0];
\r
308 lineTo[1] = startPos[1];
\r
310 if (type>=PathIterator.SEG_LINETO && type<=PathIterator.SEG_CUBICTO)
\r
319 public boolean hasNext() {
\r
323 public Shape next() {
\r
324 if (degree==0) return null;
\r
327 res = new Line2D.Double(from[0], from[1], lineTo[0], lineTo[1]);
\r
328 else if (degree==2)
\r
329 res = new QuadCurve2D.Double(from[0], from[1], lineTo[0], lineTo[1], lineTo[2], lineTo[3]);
\r
330 else if (degree==3)
\r
331 res = new CubicCurve2D.Double(from[0], from[1], lineTo[0], lineTo[1], lineTo[2], lineTo[3], lineTo[4], lineTo[5]);
\r
332 else throw new IllegalArgumentException();
\r
333 // traverse path iterator until end or until next segment is known
\r
335 from[0] = lineTo[0];
\r
336 from[1] = lineTo[1];
\r
337 while(!pi.isDone()) {
\r
338 int type = pi.currentSegment(lineTo);
\r
340 if (type == PathIterator.SEG_MOVETO) {
\r
341 startPos[0] = from[0] = lineTo[0];
\r
342 startPos[1] = from[1] = lineTo[1];
\r
344 if (type == PathIterator.SEG_CLOSE) {
\r
345 type = PathIterator.SEG_LINETO;
\r
346 lineTo[0] = startPos[0];
\r
347 lineTo[1] = startPos[1];
\r
349 if (type>=PathIterator.SEG_LINETO && type<=PathIterator.SEG_CUBICTO)
\r
358 public void remove() {
\r
359 throw new UnsupportedOperationException();
\r
366 public static Shape subdiv_takeLeft(Shape line, double t)
\r
368 if (t==1) return line;
\r
369 if (line instanceof Line2D) {
\r
370 Line2D l = (Line2D) line;
\r
371 double p0x = l.getX1();
\r
372 double p0y = l.getY1();
\r
373 double p1x = l.getX2();
\r
374 double p1y = l.getY2();
\r
375 double p1x_ = p0x*(1-t) + p1x*t;
\r
376 double p1y_ = p0y*(1-t) + p1y*t;
\r
378 return new Line2D.Double( p0x, p0y, p1x_, p1y_ );
\r
381 if (line instanceof QuadCurve2D) {
\r
382 QuadCurve2D l = (QuadCurve2D) line;
\r
383 double p0x = l.getX1();
\r
384 double p0y = l.getY1();
\r
385 double p1x = l.getCtrlX();
\r
386 double p1y = l.getCtrlY();
\r
387 double p2x = l.getX2();
\r
388 double p2y = l.getY2();
\r
389 double p1x_ = p0x*(1-t) + p1x*t;
\r
390 double p1y_ = p0y*(1-t) + p1y*t;
\r
392 double q0x = p0x*(1-t) + p1x*t;
\r
393 double q0y = p0y*(1-t) + p1y*t;
\r
395 double q1x = p1x*(1-t) + p2x*t;
\r
396 double q1y = p1y*(1-t) + p2y*t;
\r
398 double p2x_ = q0x*(1-t) + q1x*t;
\r
399 double p2y_ = q0y*(1-t) + q1y*t;
\r
401 return new QuadCurve2D.Double( p0x, p0y, p1x_, p1y_, p2x_, p2y_ );
\r
404 if (line instanceof CubicCurve2D) {
\r
405 CubicCurve2D l = (CubicCurve2D) line;
\r
406 double p0x = l.getX1();
\r
407 double p0y = l.getY1();
\r
408 double p1x = l.getCtrlX1();
\r
409 double p1y = l.getCtrlY1();
\r
410 double p2x = l.getCtrlX2();
\r
411 double p2y = l.getCtrlY2();
\r
412 double p3x = l.getX2();
\r
413 double p3y = l.getY2();
\r
414 double p1x_ = p0x*(1-t) + p1x*t;
\r
415 double p1y_ = p0y*(1-t) + p1y*t;
\r
417 double q0x = p0x*(1-t) + p1x*t;
\r
418 double q0y = p0y*(1-t) + p1y*t;
\r
420 double q1x = p1x*(1-t) + p2x*t;
\r
421 double q1y = p1y*(1-t) + p2y*t;
\r
423 double p2x_ = q0x*(1-t) + q1x*t;
\r
424 double p2y_ = q0y*(1-t) + q1y*t;
\r
426 double q2x = p2x*(1-t) + p3x*t;
\r
427 double q2y = p2y*(1-t) + p3y*t;
\r
429 double r0x = q0x*(1-t) + q1x*t;
\r
430 double r0y = q0y*(1-t) + q1y*t;
\r
432 double r1x = q1x*(1-t) + q2x*t;
\r
433 double r1y = q1y*(1-t) + q2y*t;
\r
435 double p3x_ = r0x*(1-t) + r1x*t;
\r
436 double p3y_ = r0y*(1-t) + r1y*t;
\r
438 return new CubicCurve2D.Double(p0x, p0y, p1x_, p1y_, p2x_, p2y_, p3x_, p3y_);
\r
441 throw new IllegalArgumentException();
\r
444 public static Shape subdiv_takeRight(Shape line, double t)
\r
446 if (t==0) return line;
\r
447 if (line instanceof Line2D) {
\r
448 Line2D l = (Line2D) line;
\r
449 double p0x = l.getX1();
\r
450 double p0y = l.getY1();
\r
451 double p1x = l.getX2();
\r
452 double p1y = l.getY2();
\r
453 double p0x_ = p0x*(1-t) + p1x*t;
\r
454 double p0y_ = p0y*(1-t) + p1y*t;
\r
456 return new Line2D.Double( p0x_, p0y_, p1x, p1y );
\r
459 if (line instanceof QuadCurve2D) {
\r
460 QuadCurve2D l = (QuadCurve2D) line;
\r
461 double p0x = l.getX1();
\r
462 double p0y = l.getY1();
\r
463 double p1x = l.getCtrlX();
\r
464 double p1y = l.getCtrlY();
\r
465 double p2x = l.getX2();
\r
466 double p2y = l.getY2();
\r
468 double q0x = p0x*(1-t) + p1x*t;
\r
469 double q0y = p0y*(1-t) + p1y*t;
\r
471 double q1x = p1x*(1-t) + p2x*t;
\r
472 double q1y = p1y*(1-t) + p2y*t;
\r
474 double p2x_ = q0x*(1-t) + q1x*t;
\r
475 double p2y_ = q0y*(1-t) + q1y*t;
\r
477 return new QuadCurve2D.Double( p2x_, p2y_, q1x, q1y, p2x, p2y );
\r
481 if (line instanceof CubicCurve2D) {
\r
482 CubicCurve2D l = (CubicCurve2D) line;
\r
484 double p0x = l.getX1();
\r
485 double p0y = l.getY1();
\r
486 double p1x = l.getCtrlX1();
\r
487 double p1y = l.getCtrlY1();
\r
488 double p2x = l.getCtrlX2();
\r
489 double p2y = l.getCtrlY2();
\r
490 double p3x = l.getX2();
\r
491 double p3y = l.getY2();
\r
493 double q0x = p0x*(1-t) + p1x*t;
\r
494 double q0y = p0y*(1-t) + p1y*t;
\r
496 double q1x = p1x*(1-t) + p2x*t;
\r
497 double q1y = p1y*(1-t) + p2y*t;
\r
499 double q2x = p2x*(1-t) + p3x*t;
\r
500 double q2y = p2y*(1-t) + p3y*t;
\r
502 double r0x = q0x*(1-t) + q1x*t;
\r
503 double r0y = q0y*(1-t) + q1y*t;
\r
505 double r1x = q1x*(1-t) + q2x*t;
\r
506 double r1y = q1y*(1-t) + q2y*t;
\r
508 double p3x_ = r0x*(1-t) + r1x*t;
\r
509 double p3y_ = r0y*(1-t) + r1y*t;
\r
511 return new CubicCurve2D.Double( p3x_, p3y_, r1x, r1y, q2x, q2y, p3x, p3y );
\r
520 * Crops line segment into a smaller line segment
\r
521 * @param line line segment
\r
522 * @param t0 begin t
\r
524 * @return cropped line segment
\r
526 public static Shape subdiv(Shape line, double t0, double t1)
\r
528 if (t0==0 && t1==1) return line;
\r
529 Shape temp = subdiv_takeLeft(line, t1);
\r
530 return subdiv_takeRight(temp, t0/t1);
\r
533 public static void appedToPath(Path2D p, Shape ... shapes)
\r
535 for (Shape s : shapes)
\r
537 Point2D cur = p.getCurrentPoint();
\r
538 if (s instanceof Line2D) {
\r
539 Line2D l = (Line2D) s;
\r
540 if (cur.getX()!=l.getX1() || cur.getY()!=l.getY1())
\r
541 p.moveTo(l.getX1(), l.getY1());
\r
542 p.lineTo(l.getX2(), l.getY2());
\r
543 } else if (s instanceof QuadCurve2D) {
\r
544 QuadCurve2D l = (QuadCurve2D) s;
\r
545 if (cur.getX()!=l.getX1() || cur.getY()!=l.getY1())
\r
546 p.moveTo(l.getX1(), l.getY1());
\r
547 p.quadTo(l.getCtrlX(), l.getCtrlY(), l.getX2(), l.getY2());
\r
548 } else if (s instanceof CubicCurve2D) {
\r
549 CubicCurve2D l = (CubicCurve2D) s;
\r
550 if (cur.getX()!=l.getX1() || cur.getY()!=l.getY1())
\r
551 p.moveTo(l.getX1(), l.getY1());
\r
552 p.curveTo(l.getCtrlX1(), l.getCtrlY1(), l.getCtrlX2(), l.getCtrlY2(), l.getX2(), l.getY2());
\r
553 } throw new IllegalArgumentException();
\r
556 public static void appedToPath(Path2D p, Collection<Shape> shapes)
\r
558 for (Shape s : shapes)
\r
560 Point2D cur = p.getCurrentPoint();
\r
561 if (s instanceof Line2D) {
\r
562 Line2D l = (Line2D) s;
\r
563 if (cur==null || cur.getX()!=l.getX1() || cur.getY()!=l.getY1())
\r
564 p.moveTo(l.getX1(), l.getY1());
\r
565 p.lineTo(l.getX2(), l.getY2());
\r
566 } else if (s instanceof QuadCurve2D) {
\r
567 QuadCurve2D l = (QuadCurve2D) s;
\r
568 if (cur==null || cur.getX()!=l.getX1() || cur.getY()!=l.getY1())
\r
569 p.moveTo(l.getX1(), l.getY1());
\r
570 p.quadTo(l.getCtrlX(), l.getCtrlY(), l.getX2(), l.getY2());
\r
571 } else if (s instanceof CubicCurve2D) {
\r
572 CubicCurve2D l = (CubicCurve2D) s;
\r
573 if (cur==null || cur.getX()!=l.getX1() || cur.getY()!=l.getY1())
\r
574 p.moveTo(l.getX1(), l.getY1());
\r
575 p.curveTo(l.getCtrlX1(), l.getCtrlY1(), l.getCtrlX2(), l.getCtrlY2(), l.getX2(), l.getY2());
\r
576 } else throw new IllegalArgumentException();
\r
581 public static void main(String[] args) {
\r