/******************************************************************************* * Copyright (c) 2007, 2010 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.g2d.utils; import java.awt.Shape; import java.awt.geom.CubicCurve2D; import java.awt.geom.Line2D; import java.awt.geom.Path2D; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.awt.geom.QuadCurve2D; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; /** * Path Utils. *

* A line segment (linear, quadratic or cubic bezier) is described * with a double array. The length of the array describes its degree (4,6,8). * The first 2 elements define start point and last 2 the end point. * Points in the middle are bezier control points. * * @See {@link GeometryUtils} for more geometry related utilities * @author Toni Kalajainen */ public class PathUtils2 { /** * Get tangent of an bezier * @param lineSegment bezier of n degrees * @param degree 1..3 * @param t 0..1 * @return unit vector */ public static Point2D getLineTangent(Shape line, double t, Point2D pt) { double x=0, y=0; if (line instanceof Line2D) { Line2D l = (Line2D) line; x = l.getX1() - l.getX2(); y = l.getY1() - l.getY2(); } else if (line instanceof QuadCurve2D) { QuadCurve2D l = (QuadCurve2D) line; double p0x = l.getX1(); double p0y = l.getY1(); double p1x = l.getCtrlX(); double p1y = l.getCtrlY(); double p2x = l.getX2(); double p2y = l.getY2(); x = 2*t*(p0x - 2*p1x + p2x) + 2*(-p0x + p1x); y = 2*t*(p0y - 2*p1y + p2y) + 2*(-p0y + p1y); } else if (line instanceof CubicCurve2D) { CubicCurve2D l = (CubicCurve2D) line; double p0x = l.getX1(); double p0y = l.getY1(); double p1x = l.getCtrlX1(); double p1y = l.getCtrlY1(); double p2x = l.getCtrlX2(); double p2y = l.getCtrlY2(); double p3x = l.getX2(); double p3y = l.getY2(); x = 3*(1-t)*(1-t)*(p1x-p2x) + 3*(p2x-p1x)*2*t*(1-t) + 3*(p3x-p0x)*t*t; y = 3*(1-t)*(1-t)*(p1y-p0y) + 3*(p2y-p1y)*2*t*(1-t) + 3*(p3y-p2y)*t*t; } else throw new IllegalArgumentException(); return new Point2D.Double(x, y); } /** * * @param lineSegment * @param t 0..1 * @return */ public static Point2D getLinePos(Shape line, double t, Point2D pt) { assert(line!=null); double x=0, y=0; if (pt==null) pt = new Point2D.Double(); if (line instanceof Line2D) { Line2D l = (Line2D) line; double p0x = l.getX1(); double p0y = l.getY1(); double p1x = l.getX2(); double p1y = l.getY2(); x = p0x*(1-t) + t*p1x; y = p0y*(1-t) + t*p1y; } else if (line instanceof QuadCurve2D) { QuadCurve2D l = (QuadCurve2D) line; double p0x = l.getX1(); double p0y = l.getY1(); double p1x = l.getCtrlX(); double p1y = l.getCtrlY(); double p2x = l.getX2(); double p2y = l.getY2(); double c2x = p0x-2*p1x+p2x; double c2y = p0y-2*p1y+p2y; double c1x = -2*p0x+2*p1x; double c1y = -2*p0y+2*p1y; double c0x = p0x; double c0y = p0y; x = t*t*c2x+t*c1x+c0x; y = t*t*c2y+t*c1y+c0y; } else if (line instanceof CubicCurve2D) { CubicCurve2D l = (CubicCurve2D) line; double p0x = l.getX1(); double p0y = l.getY1(); double p1x = l.getCtrlX1(); double p1y = l.getCtrlY1(); double p2x = l.getCtrlX2(); double p2y = l.getCtrlY2(); double p3x = l.getX2(); double p3y = l.getY2(); 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; 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; } else throw new IllegalArgumentException(); pt.setLocation(x, y); return pt; } public static double getLineLength(Shape line) { if (line instanceof Line2D) { Line2D l = (Line2D) line; double dx = l.getX2() - l.getX1(); double dy = l.getY2() - l.getY1(); return Math.sqrt(dx*dx+dy*dy); } // Quick'n'dirty approximation // TODO Replace with accurate value double result = 0; Point2D prevPos = getLinePos(line, 0.0, null); for (int i=0; i<10; i++) { double t = (double)(i+1)/10; Point2D pos = getLinePos(line, t, null); result += pos.distance(prevPos); prevPos.setLocation(pos); } return result; } public static int getLineDegree(Shape line) { if (line instanceof Line2D) return 1; if (line instanceof QuadCurve2D) return 2; if (line instanceof CubicCurve2D) return 3; throw new IllegalArgumentException(line+" is not a shape"); } public static double[] toArray(Shape line) { if (line instanceof Line2D) { Line2D l = (Line2D) line; return new double[] {l.getX1(), l.getY1(), l.getX2(), l.getY2()}; } if (line instanceof QuadCurve2D) { QuadCurve2D l = (QuadCurve2D) line; return new double[] {l.getX1(), l.getY1(), l.getCtrlX(), l.getCtrlY(), l.getX2(), l.getY2()}; } if (line instanceof CubicCurve2D) { CubicCurve2D l = (CubicCurve2D) line; return new double[] {l.getX1(), l.getY1(), l.getCtrlX1(), l.getCtrlY1(), l.getCtrlX2(), l.getCtrlY2(), l.getX2(), l.getY2()}; } throw new IllegalArgumentException(line+" is not a shape"); } public static Shape toShape(double dada[]) { if (dada.length==4) return new Line2D.Double(dada[0], dada[1], dada[2], dada[3]); if (dada.length==6) return new QuadCurve2D.Double(dada[0], dada[1], dada[2], dada[3], dada[4], dada[5]); if (dada.length==8) return new CubicCurve2D.Double(dada[0], dada[1], dada[2], dada[3], dada[4], dada[5], dada[6], dada[7]); throw new IllegalArgumentException(); } public static void interpolatePaths(List l1, List l2, double t, Collection result) { if (l1.size()>l2.size()) { List l_ = l1; l1 = l2; l2 = l_; t = 1-t; } int div = l2.size() / l1.size(); int mod = l2.size() % l1.size(); int rightIndex = 0; for (int i=0; i l1 = new ArrayList(); ArrayList l2 = new ArrayList(); toShapes(path1, l1); toShapes(path2, l2); ArrayList result = new ArrayList(); interpolatePaths(l1, l2, t, result); Path2D p = new Path2D.Double(); appedToPath(p, result); return p; } public static Path2D interpolatePaths(Path2D path1, Path2D path2, double t) { return interpolatePaths(path1.getPathIterator(null), path2.getPathIterator(null), t); } public static Shape interpolateLine(Shape l1, Shape l2, double t) { assert(t>=0 && t<=1); if (t==0) return l1; if (t==1) return l2; double[] a1 = toArray(l1); double[] a2 = toArray(l2); double[] res = PathUtils.interpolateLineSegment(a1, a2, t); return toShape(res); } public static void toShapes(PathIterator pi, Collection result) { Iterator i = toShapeIterator(pi); while (i.hasNext()) { Shape segment = i.next(); result.add(segment); } } /** * Returns an iterator that splits path into line shapes * @param pi path iterator * @return line segment iterator */ public static Iterator toShapeIterator(final PathIterator pi) { return new PathIteratorToShapeIterator(pi); } private static class PathIteratorToShapeIterator implements Iterator { final PathIterator pi; double lineTo[] = new double[6]; double startPos[] = new double[2]; double from[] = new double[2]; int degree = 0; PathIteratorToShapeIterator(PathIterator pi) { this.pi = pi; while(!pi.isDone()) { int type = pi.currentSegment(lineTo); pi.next(); if (type == PathIterator.SEG_MOVETO) { startPos[0] = from[0] = lineTo[0]; startPos[1] = from[1] = lineTo[1]; } if (type == PathIterator.SEG_CLOSE) { type = PathIterator.SEG_LINETO; lineTo[0] = startPos[0]; lineTo[1] = startPos[1]; } if (type>=PathIterator.SEG_LINETO && type<=PathIterator.SEG_CUBICTO) { degree = type; // from == xx break; } } } @Override public boolean hasNext() { return degree>0; } @Override public Shape next() { if (degree==0) return null; Shape res = null; if (degree==1) res = new Line2D.Double(from[0], from[1], lineTo[0], lineTo[1]); else if (degree==2) res = new QuadCurve2D.Double(from[0], from[1], lineTo[0], lineTo[1], lineTo[2], lineTo[3]); else if (degree==3) res = new CubicCurve2D.Double(from[0], from[1], lineTo[0], lineTo[1], lineTo[2], lineTo[3], lineTo[4], lineTo[5]); else throw new IllegalArgumentException(); // traverse path iterator until end or until next segment is known degree = 0; from[0] = lineTo[0]; from[1] = lineTo[1]; while(!pi.isDone()) { int type = pi.currentSegment(lineTo); pi.next(); if (type == PathIterator.SEG_MOVETO) { startPos[0] = from[0] = lineTo[0]; startPos[1] = from[1] = lineTo[1]; } if (type == PathIterator.SEG_CLOSE) { type = PathIterator.SEG_LINETO; lineTo[0] = startPos[0]; lineTo[1] = startPos[1]; } if (type>=PathIterator.SEG_LINETO && type<=PathIterator.SEG_CUBICTO) { degree = type; break; } } return res; } @Override public void remove() { throw new UnsupportedOperationException(); } } public static Shape subdiv_takeLeft(Shape line, double t) { if (t==1) return line; if (line instanceof Line2D) { Line2D l = (Line2D) line; double p0x = l.getX1(); double p0y = l.getY1(); double p1x = l.getX2(); double p1y = l.getY2(); double p1x_ = p0x*(1-t) + p1x*t; double p1y_ = p0y*(1-t) + p1y*t; return new Line2D.Double( p0x, p0y, p1x_, p1y_ ); } if (line instanceof QuadCurve2D) { QuadCurve2D l = (QuadCurve2D) line; double p0x = l.getX1(); double p0y = l.getY1(); double p1x = l.getCtrlX(); double p1y = l.getCtrlY(); double p2x = l.getX2(); double p2y = l.getY2(); double p1x_ = p0x*(1-t) + p1x*t; double p1y_ = p0y*(1-t) + p1y*t; double q0x = p0x*(1-t) + p1x*t; double q0y = p0y*(1-t) + p1y*t; double q1x = p1x*(1-t) + p2x*t; double q1y = p1y*(1-t) + p2y*t; double p2x_ = q0x*(1-t) + q1x*t; double p2y_ = q0y*(1-t) + q1y*t; return new QuadCurve2D.Double( p0x, p0y, p1x_, p1y_, p2x_, p2y_ ); } if (line instanceof CubicCurve2D) { CubicCurve2D l = (CubicCurve2D) line; double p0x = l.getX1(); double p0y = l.getY1(); double p1x = l.getCtrlX1(); double p1y = l.getCtrlY1(); double p2x = l.getCtrlX2(); double p2y = l.getCtrlY2(); double p3x = l.getX2(); double p3y = l.getY2(); double p1x_ = p0x*(1-t) + p1x*t; double p1y_ = p0y*(1-t) + p1y*t; double q0x = p0x*(1-t) + p1x*t; double q0y = p0y*(1-t) + p1y*t; double q1x = p1x*(1-t) + p2x*t; double q1y = p1y*(1-t) + p2y*t; double p2x_ = q0x*(1-t) + q1x*t; double p2y_ = q0y*(1-t) + q1y*t; double q2x = p2x*(1-t) + p3x*t; double q2y = p2y*(1-t) + p3y*t; double r0x = q0x*(1-t) + q1x*t; double r0y = q0y*(1-t) + q1y*t; double r1x = q1x*(1-t) + q2x*t; double r1y = q1y*(1-t) + q2y*t; double p3x_ = r0x*(1-t) + r1x*t; double p3y_ = r0y*(1-t) + r1y*t; return new CubicCurve2D.Double(p0x, p0y, p1x_, p1y_, p2x_, p2y_, p3x_, p3y_); } throw new IllegalArgumentException(); } public static Shape subdiv_takeRight(Shape line, double t) { if (t==0) return line; if (line instanceof Line2D) { Line2D l = (Line2D) line; double p0x = l.getX1(); double p0y = l.getY1(); double p1x = l.getX2(); double p1y = l.getY2(); double p0x_ = p0x*(1-t) + p1x*t; double p0y_ = p0y*(1-t) + p1y*t; return new Line2D.Double( p0x_, p0y_, p1x, p1y ); } if (line instanceof QuadCurve2D) { QuadCurve2D l = (QuadCurve2D) line; double p0x = l.getX1(); double p0y = l.getY1(); double p1x = l.getCtrlX(); double p1y = l.getCtrlY(); double p2x = l.getX2(); double p2y = l.getY2(); double q0x = p0x*(1-t) + p1x*t; double q0y = p0y*(1-t) + p1y*t; double q1x = p1x*(1-t) + p2x*t; double q1y = p1y*(1-t) + p2y*t; double p2x_ = q0x*(1-t) + q1x*t; double p2y_ = q0y*(1-t) + q1y*t; return new QuadCurve2D.Double( p2x_, p2y_, q1x, q1y, p2x, p2y ); } if (line instanceof CubicCurve2D) { CubicCurve2D l = (CubicCurve2D) line; double p0x = l.getX1(); double p0y = l.getY1(); double p1x = l.getCtrlX1(); double p1y = l.getCtrlY1(); double p2x = l.getCtrlX2(); double p2y = l.getCtrlY2(); double p3x = l.getX2(); double p3y = l.getY2(); double q0x = p0x*(1-t) + p1x*t; double q0y = p0y*(1-t) + p1y*t; double q1x = p1x*(1-t) + p2x*t; double q1y = p1y*(1-t) + p2y*t; double q2x = p2x*(1-t) + p3x*t; double q2y = p2y*(1-t) + p3y*t; double r0x = q0x*(1-t) + q1x*t; double r0y = q0y*(1-t) + q1y*t; double r1x = q1x*(1-t) + q2x*t; double r1y = q1y*(1-t) + q2y*t; double p3x_ = r0x*(1-t) + r1x*t; double p3y_ = r0y*(1-t) + r1y*t; return new CubicCurve2D.Double( p3x_, p3y_, r1x, r1y, q2x, q2y, p3x, p3y ); } return null; } /** * Crops line segment into a smaller line segment * @param line line segment * @param t0 begin t * @param t1 end t * @return cropped line segment */ public static Shape subdiv(Shape line, double t0, double t1) { if (t0==0 && t1==1) return line; Shape temp = subdiv_takeLeft(line, t1); return subdiv_takeRight(temp, t0/t1); } public static void appedToPath(Path2D p, Shape ... shapes) { for (Shape s : shapes) { Point2D cur = p.getCurrentPoint(); if (s instanceof Line2D) { Line2D l = (Line2D) s; if (cur.getX()!=l.getX1() || cur.getY()!=l.getY1()) p.moveTo(l.getX1(), l.getY1()); p.lineTo(l.getX2(), l.getY2()); } else if (s instanceof QuadCurve2D) { QuadCurve2D l = (QuadCurve2D) s; if (cur.getX()!=l.getX1() || cur.getY()!=l.getY1()) p.moveTo(l.getX1(), l.getY1()); p.quadTo(l.getCtrlX(), l.getCtrlY(), l.getX2(), l.getY2()); } else if (s instanceof CubicCurve2D) { CubicCurve2D l = (CubicCurve2D) s; if (cur.getX()!=l.getX1() || cur.getY()!=l.getY1()) p.moveTo(l.getX1(), l.getY1()); p.curveTo(l.getCtrlX1(), l.getCtrlY1(), l.getCtrlX2(), l.getCtrlY2(), l.getX2(), l.getY2()); } throw new IllegalArgumentException(); } } public static void appedToPath(Path2D p, Collection shapes) { for (Shape s : shapes) { Point2D cur = p.getCurrentPoint(); if (s instanceof Line2D) { Line2D l = (Line2D) s; if (cur==null || cur.getX()!=l.getX1() || cur.getY()!=l.getY1()) p.moveTo(l.getX1(), l.getY1()); p.lineTo(l.getX2(), l.getY2()); } else if (s instanceof QuadCurve2D) { QuadCurve2D l = (QuadCurve2D) s; if (cur==null || cur.getX()!=l.getX1() || cur.getY()!=l.getY1()) p.moveTo(l.getX1(), l.getY1()); p.quadTo(l.getCtrlX(), l.getCtrlY(), l.getX2(), l.getY2()); } else if (s instanceof CubicCurve2D) { CubicCurve2D l = (CubicCurve2D) s; if (cur==null || cur.getX()!=l.getX1() || cur.getY()!=l.getY1()) p.moveTo(l.getX1(), l.getY1()); p.curveTo(l.getCtrlX1(), l.getCtrlY1(), l.getCtrlX2(), l.getCtrlY2(), l.getX2(), l.getY2()); } else throw new IllegalArgumentException(); } } public static void main(String[] args) { } }