/* * $Id$ * This file is a part of the Arakhne Foundation Classes, http://www.arakhne.org/afc * * Copyright (c) 2000-2012 Stephane GALLAND. * Copyright (c) 2005-10, Multiagent Team, Laboratoire Systemes et Transports, * Universite de Technologie de Belfort-Montbeliard. * Copyright (c) 2013-2016 The original authors, and other authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.arakhne.afc.ui.vector; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.arakhne.afc.math.MathConstants; import org.arakhne.afc.math.MathUtil; import org.arakhne.afc.math.continous.object2d.Path2f; import org.arakhne.afc.math.continous.object2d.PathElement2f; import org.arakhne.afc.math.continous.object2d.Point2f; import org.arakhne.afc.math.continous.object2d.Vector2f; import org.arakhne.afc.math.generic.Point2D; import org.arakhne.afc.math.generic.Vector2D; import org.eclipse.xtext.xbase.lib.Pair; /** Vector-oriented utilities for splines. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @deprecated see JavaFX API */ @Deprecated public class PathUtil { /** The ratio used to compute the length of the tangent vectors * at the control points of a spline. The ratio is in * {@code (0;1]} and represents the ratio of the length of * the vector between two control points. */ public static final float SPLINE_TANGENT_SIZE_FACTOR = .25f ; private static void computeBezierCtrlPoints(Point2D pts0, Point2D pts1, Point2D pts2, Point2D ctrl1, Point2D ctrl2) { float v0x = (pts0.getX() - pts1.getX()); float v0y = (pts0.getY() - pts1.getY()); float v1x = (pts2.getX() - pts1.getX()); float v1y = (pts2.getY() - pts1.getY()); float tangentx = (pts2.getX() - pts0.getX()); float tangenty = (pts2.getY() - pts0.getY()); float length = (float)Math.sqrt(tangentx*tangentx + tangenty*tangenty); if (length==0f) { // Assume left-handed coordinate system, then invert the perpendicular vector to obtain the tangent tangentx = v0y; tangenty = -v0x; length = (float)Math.sqrt(tangentx*tangentx + tangenty*tangenty); if (length==0f) { ctrl1.set(pts0); ctrl2.set(pts0); return ; } tangentx /= length; tangenty /= length; } else { tangentx /= length; tangenty /= length; } float length1 = (float)Math.sqrt(v0x*v0x + v0y*v0y) * SPLINE_TANGENT_SIZE_FACTOR; float length2 = (float)Math.sqrt(v1x*v1x + v1y*v1y) * SPLINE_TANGENT_SIZE_FACTOR; ctrl1.set(pts1.getX() - tangentx * length1, pts1.getY() - tangenty * length1); ctrl2.set(pts1.getX() + tangentx * length2, pts1.getY() + tangenty * length2); } /** Create a cubic spline which is passing at the specified * control points. * * @param path is the path to build. * @param startTangent is set with the tangent to the first point. * @param endTangent is set with the tangent to the last point. * @param controlPath is, if not <code>null</code>, set with the path of * all the specified control points of the splines and the intermeditate * control points. * @param segmentPath is, if not <code>null</code>, set with the path * the specified control points of the splines. * @param controlPoints are the control point at which the spline must pass. */ public static void createCubicSpline(Path2f path, Vector2D startTangent, Vector2D endTangent, Path2f controlPath, Path2f segmentPath, List<? extends Point2D> controlPoints) { assert(path!=null); if (controlPoints.size()>2) { Point2D pts0, pts1, pts2; Point2D ctrl1, ctrl2; // Compute the control points pts0 = controlPoints.get(0); pts1 = controlPoints.get(1); Point2D pts00 = pts0; Point2D[] ctrls = new Point2D[(controlPoints.size()-1)*2]; for(int i=2, j=1; i<controlPoints.size(); ++i, j+=2) { pts2 = controlPoints.get(i); ctrl1 = new Point2f(); ctrl2 = new Point2f(); computeBezierCtrlPoints(pts0, pts1, pts2, ctrl1, ctrl2); assert(ctrls[j]==null); ctrls[j] = ctrl1; assert(ctrls[j+1]==null); ctrls[j+1] = ctrl2; pts0 = pts1; pts1 = pts2; } { float vx = (ctrls[1].getX() - pts00.getX()); float vy = (ctrls[1].getY() - pts00.getY()); float length = (float)Math.sqrt(vx*vx + vy*vy); ctrls[0] = new Point2f( pts00.getX() + vx*SPLINE_TANGENT_SIZE_FACTOR, pts00.getY() + vy*SPLINE_TANGENT_SIZE_FACTOR); if (startTangent!=null) startTangent.set(vx/length, vy/length); } { pts0 = controlPoints.get(controlPoints.size()-1); pts1 = ctrls[ctrls.length-2]; float vx = (pts1.getX() - pts0.getX()); float vy = (pts1.getY() - pts0.getY()); float length = (float)Math.sqrt(vx*vx + vy*vy); ctrls[ctrls.length-1] = new Point2f( pts0.getX() + vx*SPLINE_TANGENT_SIZE_FACTOR, pts0.getY() + vy*SPLINE_TANGENT_SIZE_FACTOR); if (endTangent!=null) endTangent.set(vx/length, vy/length); } // Draw the spline pts0 = controlPoints.get(0); path.moveTo(pts0.getX(), pts0.getY()); if (controlPath!=null) { controlPath.moveTo(pts0.getX(), pts0.getY()); } if (segmentPath!=null) { segmentPath.moveTo(pts0.getX(), pts0.getY()); } for(int i=1, j=0; i<controlPoints.size() && j<ctrls.length; ++i, j+=2) { pts0 = controlPoints.get(i); path.curveTo( ctrls[j].getX(), ctrls[j].getY(), ctrls[j+1].getX(), ctrls[j+1].getY(), pts0.getX(), pts0.getY()); if (controlPath!=null) { controlPath.lineTo(ctrls[j].getX(), ctrls[j].getY()); controlPath.lineTo(ctrls[j+1].getX(), ctrls[j+1].getY()); controlPath.lineTo(pts0.getX(), pts0.getY()); } if (segmentPath!=null) { segmentPath.lineTo(pts0.getX(), pts0.getY()); } } } else if (controlPoints.size()==2) { Point2D pts0, pts1; pts0 = controlPoints.get(0); pts1 = controlPoints.get(1); path.moveTo(pts0.getX(), pts0.getY()); path.lineTo(pts1.getX(), pts1.getY()); startTangent.sub(pts1, pts0); startTangent.normalize(); endTangent.sub(pts0, pts1); endTangent.normalize(); } } private static Vector2D computeTangent(Point2D p1, Point2D p2, Point2D p3) { double x = ((p3.getX() - p1.getX()) * .5) + p1.getX() - p2.getX(); double y = ((p3.getY() - p1.getY()) * .5) + p1.getY() - p2.getY(); Vector2D v = new Vector2f((float)x, (float)y); v.normalize(); v.perpendicularize(); return v; } /** Create a quadratic spline which is passing at the specified * control points. * * @param path is the path to build. * @param startTangent is set with the tangent to the first point. * @param endTangent is set with the tangent to the last point. * @param controlPath is, if not <code>null</code>, set with the path of * all the specified control points of the splines and the intermeditate * control points. * @param segmentPath is, if not <code>null</code>, set with the path * the specified control points of the splines. * @param controlPoints are the control point at which the spline must pass. */ public static void createQuadraticSpline(Path2f path, Vector2D startTangent, Vector2D endTangent, Path2f controlPath, Path2f segmentPath, List<? extends Point2D> controlPoints) { if (controlPoints.size()>2) { Point2D pts0, pts1, pts2; // Compute the control points pts0 = controlPoints.get(0); pts1 = controlPoints.get(1); Vector2D[] tangents = new Vector2D[controlPoints.size()-2]; Point2D[] ctrls = new Point2D[controlPoints.size()-1]; for(int i=2, j=0; i<controlPoints.size(); ++i, ++j) { pts2 = controlPoints.get(i); tangents[i-2] = computeTangent(pts0, pts1, pts2); if (i>2) { ctrls[j] = MathUtil.computeLineIntersection(pts0, tangents[i-3], pts1, tangents[i-2]); } pts0 = pts1; pts1 = pts2; } { pts0 = controlPoints.get(0); pts1 = controlPoints.get(1); Vector2D v = new Vector2f(); v.sub(pts1, pts0); v.scale(.5f); Point2D c = new Point2f( pts0.getX()+v.getX(), pts0.getY()+v.getY()); v.perpendicularize(); ctrls[0] = MathUtil.computeLineIntersection(c, v, pts1, tangents[0]); startTangent.sub(ctrls[0], pts0); startTangent.normalize(); } { pts0 = controlPoints.get(controlPoints.size()-1); pts1 = controlPoints.get(controlPoints.size()-2); Vector2D v = new Vector2f(); v.sub(pts1, pts0); v.scale(.5f); Point2D c = new Point2f( pts0.getX()+v.getX(), pts0.getY()+v.getY()); v.perpendicularize(); ctrls[ctrls.length-1] = MathUtil.computeLineIntersection(c, v, pts1, tangents[tangents.length-1]); endTangent.sub(ctrls[ctrls.length-1], pts0); endTangent.normalize(); } // Draw the spline pts0 = controlPoints.get(0); path.moveTo(pts0.getX(), pts0.getY()); if (controlPath!=null) { controlPath.moveTo(pts0.getX(), pts0.getY()); } if (segmentPath!=null) { segmentPath.moveTo(pts0.getX(), pts0.getY()); } for(int i=1, j=0; i<controlPoints.size() && j<ctrls.length; ++i, ++j) { pts0 = controlPoints.get(i); path.quadTo(ctrls[j].getX(), ctrls[j].getY(), pts0.getX(), pts0.getY()); if (controlPath!=null) { controlPath.lineTo(ctrls[j].getX(), ctrls[j].getY()); controlPath.lineTo(pts0.getX(), pts0.getY()); } if (segmentPath!=null) { segmentPath.lineTo(pts0.getX(), pts0.getY()); } } } else if (controlPoints.size()==2) { Point2D pts0, pts1; pts0 = controlPoints.get(0); pts1 = controlPoints.get(1); path.moveTo(pts0.getX(), pts0.getY()); path.lineTo(pts1.getX(), pts1.getY()); startTangent.sub(pts1, pts0); startTangent.normalize(); endTangent.sub(pts0, pts1); endTangent.normalize(); } } /** Create the polyline that is passing at the specified control points. * * @param path is the path to fill. * @param startTangent is, if not <code>null</code>, set to the tangent at the first point. * @param endTangent is, if not <code>null</code>, set to the tangent at the last point. * @param controlPoints are the control points to pass at. */ public static void createSegments(Path2f path, Vector2D startTangent, Vector2D endTangent, List<? extends Point2D> controlPoints) { Point2D pts = controlPoints.get(0); Point2D ppts = pts; path.moveTo(pts.getX(), pts.getY()); for(int i=1; i<controlPoints.size(); ++i) { pts = controlPoints.get(i); if (i==1 && startTangent!=null) { startTangent.sub(pts, ppts); startTangent.normalize(); } if (i==controlPoints.size()-1 && endTangent!=null) { endTangent.sub(ppts, pts); endTangent.normalize(); } path.lineTo(pts.getX(), pts.getY()); ppts = pts; } } /** Compute the length of the segments along the specified path. * The size of the array <var>lengths</var> must be the same as * the size of the list <var>controlPoints</var>. * * @param path is the path to traverse. * @param lengths is the array to fill with the lengths. * @param controlPoints are the control points between the segments. */ public static void computeSegmentLengths(Path2f path, float[] lengths, List<? extends Point2D> controlPoints) { assert(lengths.length==controlPoints.size()); Iterator<PathElement2f> pathIterator = path.getPathIterator((float) MathConstants.SPLINE_APPROXIMATION_RATIO); int index = 0 ; PathElement2f pathElement; // Checks all segments while (pathIterator.hasNext()) { pathElement = pathIterator.next(); if ( ( index < 0 ) || ( index >= controlPoints.size() ) ) { index = -1 ; } else if( controlPoints.get(index+1).equals( new Point2f( pathElement.toX, pathElement.toY ) ) ) { index ++ ; if (index<lengths.length) lengths[index] = 0; } switch( pathElement.type ) { case MOVE_TO: break; case LINE_TO: lengths[index] += MathUtil.distancePointToPoint( pathElement.fromX, pathElement.fromY, pathElement.toX, pathElement.toY); break; case CLOSE: lengths[index] += MathUtil.distancePointToPoint( pathElement.fromX, pathElement.fromY, pathElement.toX, pathElement.toY); break; case CURVE_TO: case QUAD_TO: default: throw new IllegalStateException(); } } } /** Compute the point along the specified path and which * is located at the specified position. * * @param path is the path. * @param factor is the position factor, between 0 and 1. * @return the point; never <code>null</code> */ public static Point2D interpolate(Path2f path, float factor) { return interpolate(path, factor, null); } /** Compute the point along the specified path and which * is located at the specified position. * * @param path is the path. * @param factor is the position factor, between 0 and 1. * @param normal will contain the normal at the specified point. * @return the point; never <code>null</code> */ public static Point2D interpolate(Path2f path, float factor, Vector2D normal) { Iterator<PathElement2f> iterator = path.getPathIterator((float) MathConstants.SPLINE_APPROXIMATION_RATIO); PathElement2f pathElement; Point2D firstPoint = null; Point2D lastPoint = null; float length = 0; List<Pair<Float,Point2D>> points = new ArrayList<>(); float d; while (iterator.hasNext()) { pathElement = iterator.next(); switch(pathElement.type) { case MOVE_TO: lastPoint = new Point2f(pathElement.toX, pathElement.toY); if (firstPoint==null) firstPoint = lastPoint; points.add(new Pair<>(length, lastPoint)); break; case LINE_TO: case CLOSE: d = MathUtil.distanceSquaredPointToPoint( pathElement.fromX, pathElement.fromY, pathElement.toX, pathElement.toY); length += d; lastPoint = new Point2f(pathElement.toX, pathElement.toY); points.add(new Pair<>(length, lastPoint)); break; case CURVE_TO: case QUAD_TO: default: throw new IllegalStateException(); } } float distance = factor * length; Pair<Float,Point2D> pair; int l = 0; int r = points.size()-1; int c; while (l<r) { c = (l+r)/2; pair = points.get(c); if (distance==pair.getKey()) { return pair.getValue(); } if (distance<pair.getKey()) { r = c-1; } else { l = c+1; } } if (l<=0) return firstPoint; if (l>=points.size()) return lastPoint; pair = points.get(l-1); distance -= pair.getKey(); lastPoint = points.get(l).getValue(); distance /= pair.getValue().distanceSquared(lastPoint); if (normal!=null) { normal.set(lastPoint.getX(), lastPoint.getY()); normal.sub(pair.getValue().getX(), pair.getValue().getY()); normal.perpendicularize(); normal.normalize(); } org.arakhne.afc.math.generic.Point2D p = MathUtil.interpolate( pair.getValue().getX(), pair.getValue().getY(), lastPoint.getX(), lastPoint.getY(), distance); return new Point2f(p.getX(), p.getY()); } }