CatmullRomSpline.java

00001 package edu.stanford.hci.r3.util.geometry;
00002 
00003 import java.awt.Shape;
00004 import java.awt.geom.*;
00005 import java.util.ArrayList;
00006 import java.util.List;
00007 
00020 public class CatmullRomSpline {
00021 
00022         private List<Vector2D> backwardTangents = new ArrayList<Vector2D>();
00023 
00024         private List<Point2D> controlPoints = new ArrayList<Point2D>();
00025 
00026         private List<Vector2D> forwardTangents = new ArrayList<Vector2D>();
00027 
00028         private GeneralPath linePath = new GeneralPath();
00029 
00030         private GeneralPath path = new GeneralPath();
00031 
00032         private List<Point2D> pathPoints = new ArrayList<Point2D>();
00033 
00034         // .3 is pretty good :)
00035         private double tension = .3;
00036 
00037         public List<Vector2D> getBackwardTangents() {
00038                 return backwardTangents;
00039         }
00040 
00041         public List<Point2D> getControlPoints() {
00042                 return controlPoints;
00043         }
00044 
00048         public List<Vector2D> getForwardTangents() {
00049                 return forwardTangents;
00050         }
00051 
00055         public Shape getLineShape() {
00056                 return linePath;
00057         }
00058 
00062         public List<Point2D> getPathPoints() {
00063                 return pathPoints;
00064         }
00065 
00069         public Shape getShape() {
00070                 return path;
00071         }
00072 
00078         private void makeConnectedLines() {
00079                 for (int i = 0; i < pathPoints.size() - 1; i++) {
00080                         Point2D p0 = pathPoints.get(i);
00081                         Point2D p1 = pathPoints.get(i + 1);
00082 
00083                         linePath.append(new Line2D.Double(p0, p1), false);
00084 
00085                         p0 = p1;
00086                 }
00087         }
00088 
00092         private void makePiecewiseBezier() {
00093                 path.reset();
00094 
00095                 // for each bezier segment
00096                 // pull one point
00097                 // pull two control points
00098                 // pull next point
00099 
00100                 Point2D p1;
00101                 Point2D c1;
00102                 Point2D c2;
00103                 Point2D p2;
00104                 int j = 0; // control points index
00105                 for (int i = 0; i < pathPoints.size() - 1; i++) {
00106                         p1 = pathPoints.get(i);
00107                         c1 = controlPoints.get(j++); // get j, and increment it
00108                         c2 = controlPoints.get(j++);
00109                         p2 = pathPoints.get(i + 1);
00110 
00111                         path.append(//
00112                                         new CubicCurve2D.Double(//
00113                                                         p1.getX(), p1.getY(),//
00114                                                         c1.getX(), c1.getY(),//
00115                                                         c2.getX(), c2.getY(),//
00116                                                         p2.getX(), p2.getY()), //
00117                                         false);
00118                 }
00119         }
00120 
00125         public void setPoints(double[] x, double[] y) {
00126                 List<Point2D> points = new ArrayList<Point2D>();
00127                 for (int i = 0; i < x.length; i++) {
00128                         points.add(new Point2D.Double(x[i], y[i]));
00129                 }
00130                 setPoints(points);
00131         }
00132 
00139         public void setPoints(List<Point2D> points) {
00140                 path.reset();
00141                 pathPoints.clear();
00142 
00143                 pathPoints.addAll(points);
00144 
00145                 // if a single point, draw a dot
00146                 if (points.size() == 1) {
00147                         final Point2D point = points.get(0);
00148                         path.append(new Line2D.Double(point, point), false);
00149                         return;
00150                 }
00151 
00152                 // if two points, draw a line segment
00153                 if (points.size() == 2) {
00154                         final Point2D point1 = points.get(0);
00155                         final Point2D point2 = points.get(1);
00156                         path.append(new Line2D.Double(point1, point2), false);
00157                         return;
00158                 }
00159 
00160                 // if three points or more, do the piecewise business...
00161                 // construct piecewise cubic bezier curves that generate the catmull-rom
00162 
00163                 // tack on an extra last point, to enable the calculation of the last tangent
00164                 Point2D pSecondToLast = points.get(points.size() - 2);
00165                 Point2D pLast = points.get(points.size() - 1);
00166                 points.add(Vector2D.add(pLast, Vector2D.subtract(pLast, pSecondToLast)));
00167 
00168                 // p0 is the previous point
00169                 Point2D p0 = Vector2D.add(points.get(0), Vector2D.subtract(points.get(0), points.get(1)));
00170                 // p1 is the current point
00171                 Point2D p1 = points.get(0);
00172                 // p2 is the next point
00173                 Point2D p2;
00174 
00175                 // add tangents and control points
00176                 for (int i = 0; i < points.size() - 1; i++) {
00177                         p2 = points.get(i + 1);
00178 
00179                         // === Backwards ===
00180                         // add a backward tangent for each point, equal to the point before minus the point
00181                         // after
00182                         final Vector2D bkwd = Vector2D.getScaled(tension * Vector2D.subtract(p0, p1).magnitude(),
00183                                         Vector2D.subtract(p0, p2));
00184                         backwardTangents.add(bkwd);
00185 
00186                         // make a backward control point by adding it to the current point
00187                         controlPoints.add(Vector2D.add(p1, bkwd));
00188 
00189                         // === Forwards ===
00190                         // add a tangent for each point, that is equal to the point after minus the point before
00191                         // length is equal to half the distance to the next point
00192                         final Vector2D fwd = Vector2D.getScaled(tension * Vector2D.subtract(p2, p1).magnitude(), Vector2D
00193                                         .subtract(p2, p0));
00194                         forwardTangents.add(fwd);
00195 
00196                         // make a forward control point by adding it to the current point
00197                         controlPoints.add(Vector2D.add(p1, fwd));
00198 
00199                         // shift the points over
00200                         p0 = p1;
00201                         p1 = p2;
00202                 }
00203 
00204                 // delete first and last control points (as they are useless)
00205                 controlPoints.remove(0);
00206                 controlPoints.remove(controlPoints.size() - 1);
00207 
00208                 // don't remove tangents
00209 
00210                 // line segments
00211                 makeConnectedLines();
00212 
00213                 // general path, piecewise cubic bezier
00214                 makePiecewiseBezier();
00215         }
00216 }

Generated on Sat Apr 14 18:21:33 2007 for R3 Paper Toolkit by  doxygen 1.4.7