View Javadoc
1   package org.opentrafficsim.core.geometry;
2   
3   import nl.tudelft.simulation.language.d3.DirectedPoint;
4   
5   /**
6    * Generation of B&eacute;zier curves. <br>
7    * The class implements the cubic(...) method to generate a cubic B&eacute;zier curve using the following formula: B(t) = (1 -
8    * t)<sup>3</sup>P<sub>0</sub> + 3t(1 - t)<sup>2</sup> P<sub>1</sub> + 3t<sup>2</sup> (1 - t) P<sub>2</sub> + t<sup>3</sup>
9    * P<sub>3</sub> where P<sub>0</sub> and P<sub>3</sub> are the end points, and P<sub>1</sub> and P<sub>2</sub> the control
10   * points. <br>
11   * For a smooth movement, one of the standard implementations if the cubic(...) function offered is the case where P<sub>1</sub>
12   * is positioned halfway between P<sub>0</sub> and P<sub>3</sub> starting from P<sub>0</sub> in the direction of P<sub>3</sub>,
13   * and P<sub>2</sub> is positioned halfway between P<sub>3</sub> and P<sub>0</sub> starting from P<sub>3</sub> in the direction
14   * of P<sub>0</sub>.<br>
15   * Finally, an n-point generalization of the B&eacute;zier curve is implemented with the bezier(...) function.
16   * <p>
17   * Copyright (c) 2013-2015 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
18   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
19   * </p>
20   * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
21   * initial version Nov 14, 2015 <br>
22   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
23   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
24   */
25  public final class Bezier
26  {
27      /** The default number of points to use to construct a B&eacute;zier curve. */
28      private static final int DEFAULT_NUM_POINTS = 64;
29  
30      /** Cached factorial values. */
31      private static long[] fact = new long[]{1L, 1L, 2L, 6L, 24L, 120L, 720L, 5040L, 40320L, 362880L, 3628800L,
32          39916800L, 479001600L, 6227020800L, 87178291200L, 1307674368000L, 20922789888000L, 355687428096000L,
33          6402373705728000L, 121645100408832000L, 2432902008176640000L};
34  
35      /** Utility class. */
36      private Bezier()
37      {
38          // do not instantiate
39      }
40  
41      /**
42       * Construct a cubic B&eacute;zier curve from start to end with two control points.
43       * @param numPoints the number of points for the B&eacute;zier curve
44       * @param start the start point of the B&eacute;zier curve
45       * @param control1 the first control point
46       * @param control2 the second control point
47       * @param end the end point of the B&eacute;zier curve
48       * @return a cubic B&eacute;zier curve between start and end, with the two provided control points
49       * @throws OTSGeometryException in case the number of points is less than 2 or the B&eacute;zier curve could not be
50       *             constructed
51       */
52      public static OTSLine3D cubic(final int numPoints, final OTSPoint3D start, final OTSPoint3D control1,
53          final OTSPoint3D control2, final OTSPoint3D end) throws OTSGeometryException
54      {
55          OTSPoint3D[] points = new OTSPoint3D[numPoints];
56          for (int n = 0; n < numPoints; n++)
57          {
58              double t = n / (numPoints - 1.0);
59              double x = B3(t, start.x, control1.x, control2.x, end.x);
60              double y = B3(t, start.y, control1.y, control2.y, end.y);
61              double z = B3(t, start.z, control1.z, control2.z, end.z);
62              points[n] = new OTSPoint3D(x, y, z);
63          }
64          return new OTSLine3D(points);
65      }
66  
67      /**
68       * Construct a cubic B&eacute;zier curve from start to end with two generated control points at half the distance between
69       * start and end. The z-value is interpolated in a linear way.
70       * @param numPoints the number of points for the B&eacute;zier curve
71       * @param start the directed start point of the B&eacute;zier curve
72       * @param end the directed end point of the B&eacute;zier curve
73       * @return a cubic B&eacute;zier curve between start and end, with the two provided control points
74       * @throws OTSGeometryException in case the number of points is less than 2 or the B&eacute;zier curve could not be
75       *             constructed
76       */
77      public static OTSLine3D cubic(final int numPoints, final DirectedPoint start, final DirectedPoint end)
78          throws OTSGeometryException
79      {
80          double distance2 =
81              Math.sqrt((end.x - start.x) * (end.x - start.x) + (end.y - start.y) * (end.y - start.y)) / 2.0;
82          OTSPoint3D control1 =
83              new OTSPoint3D(start.x + distance2 * Math.cos(start.getRotZ()), start.y + distance2
84                  * Math.sin(start.getRotZ()), start.z);
85          OTSPoint3D control2 =
86              new OTSPoint3D(end.x - distance2 * Math.cos(end.getRotZ()), end.y - distance2 * Math.sin(end.getRotZ()),
87                  end.z);
88          // return cubic(numPoints, new OTSPoint3D(start), control1, control2, new OTSPoint3D(end));
89          return bezier(numPoints, new OTSPoint3D(start), control1, control2, new OTSPoint3D(end));
90      }
91  
92      /**
93       * Construct a cubic B&eacute;zier curve from start to end with two generated control points at half the distance between
94       * start and end. The z-value is interpolated in a linear way.
95       * @param start the directed start point of the B&eacute;zier curve
96       * @param end the directed end point of the B&eacute;zier curve
97       * @return a cubic B&eacute;zier curve between start and end, with the two provided control points
98       * @throws OTSGeometryException in case the number of points is less than 2 or the B&eacute;zier curve could not be
99       *             constructed
100      */
101     public static OTSLine3D cubic(final DirectedPoint start, final DirectedPoint end) throws OTSGeometryException
102     {
103         return cubic(DEFAULT_NUM_POINTS, start, end);
104     }
105 
106     /**
107      * Calculate the cubic B&eacute;zier point with B(t) = (1 - t)<sup>3</sup>P<sub>0</sub> + 3t(1 - t)<sup>2</sup>
108      * P<sub>1</sub> + 3t<sup>2</sup> (1 - t) P<sub>2</sub> + t<sup>3</sup> P<sub>3</sub>.
109      * @param t the fraction
110      * @param p0 the first point of the curve
111      * @param p1 the first control point
112      * @param p2 the second control point
113      * @param p3 the end point of the curve
114      * @return the cubic bezier value B(t)
115      */
116     @SuppressWarnings("checkstyle:methodname")
117     private static double B3(final double t, final double p0, final double p1, final double p2, final double p3)
118     {
119         double t2 = t * t;
120         double t3 = t2 * t;
121         double m = (1.0 - t);
122         double m2 = m * m;
123         double m3 = m2 * m;
124         return m3 * p0 + 3.0 * t * m2 * p1 + 3.0 * t2 * m * p2 + t3 * p3;
125     }
126 
127     /**
128      * Construct a B&eacute;zier curve of degree n.
129      * @param numPoints the number of points for the B&eacute;zier curve to be constructed
130      * @param points the points of the curve, where the first and last are begin and end point, and the intermediate ones are
131      *            control points. There should be at least two points.
132      * @return the B&eacute;zier value B(t) of degree n, where n is the number of points in the array
133      * @throws OTSGeometryException in case the number of points is less than 2 or the B&eacute;zier curve could not be
134      *             constructed
135      */
136     public static OTSLine3D bezier(final int numPoints, final OTSPoint3D... points) throws OTSGeometryException
137     {
138         OTSPoint3D[] result = new OTSPoint3D[numPoints];
139         double[] px = new double[points.length];
140         double[] py = new double[points.length];
141         double[] pz = new double[points.length];
142         for (int i = 0; i < points.length; i++)
143         {
144             px[i] = points[i].x;
145             py[i] = points[i].y;
146             pz[i] = points[i].z;
147         }
148         for (int n = 0; n < numPoints; n++)
149         {
150             double t = n / (numPoints - 1.0);
151             double x = Bn(t, px);
152             double y = Bn(t, py);
153             double z = Bn(t, pz);
154             result[n] = new OTSPoint3D(x, y, z);
155         }
156         return new OTSLine3D(result);
157     }
158 
159     /**
160      * Construct a B&eacute;zier curve of degree n.
161      * @param points the points of the curve, where the first and last are begin and end point, and the intermediate ones are
162      *            control points. There should be at least two points.
163      * @return the B&eacute;zier value B(t) of degree n, where n is the number of points in the array
164      * @throws OTSGeometryException in case the number of points is less than 2 or the B&eacute;zier curve could not be
165      *             constructed
166      */
167     public static OTSLine3D bezier(final OTSPoint3D... points) throws OTSGeometryException
168     {
169         return bezier(DEFAULT_NUM_POINTS, points);
170     }
171 
172     /**
173      * Calculate the B&eacute;zier point of degree n, with B(t) = Sum(i = 0..n) [C(n, i) * (1 - t)<sup>n-i</sup> t<sup>i</sup>
174      * P<sub>i</sub>], where C(n, k) is the binomial coefficient defined by n! / ( k! (n-k)! ), ! being the factorial operator.
175      * @param t the fraction
176      * @param p the points of the curve, where the first and last are begin and end point, and the intermediate ones are control
177      *            points
178      * @return the B&eacute;zier value B(t) of degree n, where n is the number of points in the array
179      */
180     @SuppressWarnings("checkstyle:methodname")
181     private static double Bn(final double t, final double... p)
182     {
183         double b = 0.0;
184         double m = (1.0 - t);
185         int n = p.length - 1;
186         double fn = factorial(n);
187         for (int i = 0; i <= n; i++)
188         {
189             double c = fn / (factorial(i) * (factorial(n - i)));
190             b += c * Math.pow(m, n - i) * Math.pow(t, i) * p[i];
191         }
192         return b;
193     }
194 
195     /**
196      * Calculate factorial(k), which is k * (k-1) * (k-2) * ... * 1. For factorials up to 20, a lookup table is used.
197      * @param k the parameter
198      * @return factorial(k)
199      */
200     private static double factorial(final int k)
201     {
202         if (k < fact.length)
203         {
204             return fact[k];
205         }
206         double f = 1;
207         for (int i = 2; i <= k; i++)
208         {
209             f = f * i;
210         }
211         return f;
212     }
213 
214     /**
215      * @param args args
216      * @throws OTSGeometryException ne
217      */
218     public static void main(final String[] args) throws OTSGeometryException
219     {
220         // DirectedPoint s = new DirectedPoint(0, 0, 0, 0, 0, -Math.PI/2.0);
221         // DirectedPoint e = new DirectedPoint(10, 10, 20, 0, 0, Math.PI);
222         // OTSLine3D b1 = Bezier.cubic(s, e);
223         // for (OTSPoint3D p : b1.getPoints())
224         // {
225         // System.out.println(p.x + "\t" + p.y + "\t" + p.z);
226         // }
227 
228         OTSPoint3D s = new OTSPoint3D(0, 0, 0);
229         OTSPoint3D s1 = new OTSPoint3D(10, 0, 0);
230         OTSPoint3D m1 = new OTSPoint3D(25, 5, 0);
231         OTSPoint3D m2 = new OTSPoint3D(-15, 5, 0);
232         OTSPoint3D e0 = new OTSPoint3D(0, 10, 20);
233         OTSPoint3D e = new OTSPoint3D(10, 10, 20);
234         OTSLine3D b1 = Bezier.bezier(s, s1, m1, m2, e0, e);
235         for (OTSPoint3D p : b1.getPoints())
236         {
237             System.out.println(p.x + "\t" + p.y + "\t" + p.z);
238         }
239 
240     }
241 }