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