/*******************************************************************************
* 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) {
}
}