View Javadoc
1   package org.opentrafficsim.core.geometry;
2   
3   import java.awt.geom.Line2D;
4   
5   import org.djutils.exceptions.Throw;
6   
7   /**
8    * Generation of B&eacute;zier curves. <br>
9    * The class implements the cubic(...) method to generate a cubic B&eacute;zier curve using the following formula: B(t) = (1 -
10   * 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>
11   * 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
12   * points. <br>
13   * For a smooth movement, one of the standard implementations if the cubic(...) function offered is the case where P<sub>1</sub>
14   * 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>,
15   * 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
16   * of P<sub>0</sub>.<br>
17   * Finally, an n-point generalization of the B&eacute;zier curve is implemented with the bezier(...) function.
18   * <p>
19   * Copyright (c) 2013-2022 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
20   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
21   * </p>
22   * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
23   * initial version Nov 14, 2015 <br>
24   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
25   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
26   */
27  public final class Bezier
28  {
29      /** The default number of points to use to construct a B&eacute;zier curve. */
30      private static final int DEFAULT_NUM_POINTS = 64;
31  
32      /** Cached factorial values. */
33      private static long[] fact = new long[] {1L, 1L, 2L, 6L, 24L, 120L, 720L, 5040L, 40320L, 362880L, 3628800L, 39916800L,
34              479001600L, 6227020800L, 87178291200L, 1307674368000L, 20922789888000L, 355687428096000L, 6402373705728000L,
35              121645100408832000L, 2432902008176640000L};
36  
37      /** Utility class. */
38      private Bezier()
39      {
40          // do not instantiate
41      }
42  
43      /**
44       * Construct a cubic B&eacute;zier curve from start to end with two control points.
45       * @param numPoints int; the number of points for the B&eacute;zier curve
46       * @param start OTSPoint3D; the start point of the B&eacute;zier curve
47       * @param control1 OTSPoint3D; the first control point
48       * @param control2 OTSPoint3D; the second control point
49       * @param end OTSPoint3D; the end point of the B&eacute;zier curve
50       * @return a cubic B&eacute;zier curve between start and end, with the two provided control points
51       * @throws OTSGeometryException in case the number of points is less than 2 or the B&eacute;zier curve could not be
52       *             constructed
53       */
54      public static OTSLine3D cubic(final int numPoints, final OTSPoint3D start, final OTSPoint3D control1,
55              final OTSPoint3D control2, final OTSPoint3D end) throws OTSGeometryException
56      {
57          Throw.when(numPoints < 2, OTSGeometryException.class, "Number of points too small (got %d; minimum value is 2)",
58                  numPoints);
59          OTSPoint3D[] points = new OTSPoint3D[numPoints];
60          for (int n = 0; n < numPoints; n++)
61          {
62              double t = n / (numPoints - 1.0);
63              double x = B3(t, start.x, control1.x, control2.x, end.x);
64              double y = B3(t, start.y, control1.y, control2.y, end.y);
65              double z = B3(t, start.z, control1.z, control2.z, end.z);
66              points[n] = new OTSPoint3D(x, y, z);
67          }
68          return new OTSLine3D(points);
69      }
70  
71      /**
72       * Construct a cubic B&eacute;zier curve from start to end with two generated control points at half the distance between
73       * start and end. The z-value is interpolated in a linear way.
74       * @param numPoints int; the number of points for the B&eacute;zier curve
75       * @param start DirectedPoint; the directed start point of the B&eacute;zier curve
76       * @param end DirectedPoint; the directed end point of the B&eacute;zier curve
77       * @return a cubic B&eacute;zier curve between start and end, with the two provided control points
78       * @throws OTSGeometryException in case the number of points is less than 2 or the B&eacute;zier curve could not be
79       *             constructed
80       */
81      public static OTSLine3D cubic(final int numPoints, final DirectedPoint start, final DirectedPoint end)
82              throws OTSGeometryException
83      {
84          return cubic(numPoints, start, end, 1.0);
85      }
86  
87      /**
88       * Construct a cubic B&eacute;zier curve from start to end with two generated control points at half the distance between
89       * start and end. The z-value is interpolated in a linear way.
90       * @param numPoints int; the number of points for the B&eacute;zier curve
91       * @param start DirectedPoint; the directed start point of the B&eacute;zier curve
92       * @param end DirectedPoint; the directed end point of the B&eacute;zier curve
93       * @param shape shape factor; 1 = control points at half the distance between start and end, &gt; 1 results in a pointier
94       *            shape, &lt; 1 results in a flatter shape, value should be above 0
95       * @return a cubic B&eacute;zier curve between start and end, with the two determined control points
96       * @throws OTSGeometryException in case the number of points is less than 2 or the B&eacute;zier curve could not be
97       *             constructed
98       */
99      public static OTSLine3D cubic(final int numPoints, final DirectedPoint start, final DirectedPoint end, final double shape)
100             throws OTSGeometryException
101     {
102         return cubic(numPoints, start, end, shape, false);
103     }
104 
105     /**
106      * Construct a cubic B&eacute;zier curve from start to end with two generated control points at half the distance between
107      * start and end. The z-value is interpolated in a linear way.
108      * @param numPoints int; the number of points for the B&eacute;zier curve
109      * @param start DirectedPoint; the directed start point of the B&eacute;zier curve
110      * @param end DirectedPoint; the directed end point of the B&eacute;zier curve
111      * @param shape shape factor; 1 = control points at half the distance between start and end, &gt; 1 results in a pointier
112      *            shape, &lt; 1 results in a flatter shape, value should be above 0
113      * @param weighted boolean; control point distance relates to distance to projected point on extended line from other end
114      * @return a cubic B&eacute;zier curve between start and end, with the two determined control points
115      * @throws OTSGeometryException in case the number of points is less than 2 or the B&eacute;zier curve could not be
116      *             constructed
117      */
118     public static OTSLine3D cubic(final int numPoints, final DirectedPoint start, final DirectedPoint end, final double shape,
119             final boolean weighted) throws OTSGeometryException
120     {
121         OTSPoint3D control1;
122         OTSPoint3D control2;
123 
124         if (weighted)
125         {
126             // each control point is 'w' * the distance between the end-points away from the respective end point
127             // 'w' is a weight given by the distance from the end point to the extended line of the other end point
128             double distance = shape * Math.sqrt((end.x - start.x) * (end.x - start.x) + (end.y - start.y) * (end.y - start.y));
129             double cosEnd = Math.cos(end.getRotZ());
130             double sinEnd = Math.sin(end.getRotZ());
131             double dStart = Line2D.ptLineDist(end.x, end.y, end.x + cosEnd, end.y + sinEnd, start.x, start.y);
132             double cosStart = Math.cos(start.getRotZ());
133             double sinStart = Math.sin(start.getRotZ());
134             double dEnd = Line2D.ptLineDist(start.x, start.y, start.x + cosStart, start.y + sinStart, end.x, end.y);
135             double wStart = dStart / (dStart + dEnd);
136             double wEnd = dEnd / (dStart + dEnd);
137             double wStartDistance = wStart * distance;
138             double wEndDistance = wEnd * distance;
139             control1 = new OTSPoint3D(start.x + wStartDistance * cosStart, start.y + wStartDistance * sinStart);
140             // - (minus) as the angle is where the line leaves, i.e. from shape point to end
141             control2 = new OTSPoint3D(end.x - wEndDistance * cosEnd, end.y - wEndDistance * sinEnd);
142         }
143         else
144         {
145             // each control point is half the distance between the end-points away from the respective end point
146             double distance2 =
147                     shape * Math.sqrt((end.x - start.x) * (end.x - start.x) + (end.y - start.y) * (end.y - start.y)) / 2.0;
148             control1 = new OTSPoint3D(start.x + distance2 * Math.cos(start.getRotZ()),
149                     start.y + distance2 * Math.sin(start.getRotZ()), start.z);
150             control2 = new OTSPoint3D(end.x - distance2 * Math.cos(end.getRotZ()), end.y - distance2 * Math.sin(end.getRotZ()),
151                     end.z);
152         }
153 
154         // Limit control points to not intersect with the other (infinite) line
155         OTSPoint3D s = new OTSPoint3D(start);
156         OTSPoint3D e = new OTSPoint3D(end);
157 
158         // return cubic(numPoints, new OTSPoint3D(start), control1, control2, new OTSPoint3D(end));
159         return bezier(numPoints, s, control1, control2, e);
160     }
161 
162     /**
163      * Construct a cubic B&eacute;zier curve from start to end with two generated control points at half the distance between
164      * start and end. The z-value is interpolated in a linear way.
165      * @param start DirectedPoint; the directed start point of the B&eacute;zier curve
166      * @param end DirectedPoint; the directed end point of the B&eacute;zier curve
167      * @return a cubic B&eacute;zier curve between start and end, with the two provided control points
168      * @throws OTSGeometryException in case the number of points is less than 2 or the B&eacute;zier curve could not be
169      *             constructed
170      */
171     public static OTSLine3D cubic(final DirectedPoint start, final DirectedPoint end) throws OTSGeometryException
172     {
173         return cubic(DEFAULT_NUM_POINTS, start, end);
174     }
175 
176     /**
177      * 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>
178      * P<sub>1</sub> + 3t<sup>2</sup> (1 - t) P<sub>2</sub> + t<sup>3</sup> P<sub>3</sub>.
179      * @param t double; the fraction
180      * @param p0 double; the first point of the curve
181      * @param p1 double; the first control point
182      * @param p2 double; the second control point
183      * @param p3 double; the end point of the curve
184      * @return the cubic bezier value B(t)
185      */
186     @SuppressWarnings("checkstyle:methodname")
187     private static double B3(final double t, final double p0, final double p1, final double p2, final double p3)
188     {
189         double t2 = t * t;
190         double t3 = t2 * t;
191         double m = (1.0 - t);
192         double m2 = m * m;
193         double m3 = m2 * m;
194         return m3 * p0 + 3.0 * t * m2 * p1 + 3.0 * t2 * m * p2 + t3 * p3;
195     }
196 
197     /**
198      * Construct a B&eacute;zier curve of degree n.
199      * @param numPoints int; the number of points for the B&eacute;zier curve to be constructed
200      * @param points OTSPoint3D...; the points of the curve, where the first and last are begin and end point, and the
201      *            intermediate ones are control points. There should be at least two points.
202      * @return the B&eacute;zier value B(t) of degree n, where n is the number of points in the array
203      * @throws OTSGeometryException in case the number of points is less than 2 or the B&eacute;zier curve could not be
204      *             constructed
205      */
206     public static OTSLine3D bezier(final int numPoints, final OTSPoint3D... points) throws OTSGeometryException
207     {
208         OTSPoint3D[] result = new OTSPoint3D[numPoints];
209         double[] px = new double[points.length];
210         double[] py = new double[points.length];
211         double[] pz = new double[points.length];
212         for (int i = 0; i < points.length; i++)
213         {
214             px[i] = points[i].x;
215             py[i] = points[i].y;
216             pz[i] = points[i].z;
217         }
218         for (int n = 0; n < numPoints; n++)
219         {
220             double t = n / (numPoints - 1.0);
221             double x = Bn(t, px);
222             double y = Bn(t, py);
223             double z = Bn(t, pz);
224             result[n] = new OTSPoint3D(x, y, z);
225         }
226         return new OTSLine3D(result);
227     }
228 
229     /**
230      * Construct a B&eacute;zier curve of degree n.
231      * @param points OTSPoint3D...; the points of the curve, where the first and last are begin and end point, and the
232      *            intermediate ones are control points. There should be at least two points.
233      * @return the B&eacute;zier value B(t) of degree n, where n is the number of points in the array
234      * @throws OTSGeometryException in case the number of points is less than 2 or the B&eacute;zier curve could not be
235      *             constructed
236      */
237     public static OTSLine3D bezier(final OTSPoint3D... points) throws OTSGeometryException
238     {
239         return bezier(DEFAULT_NUM_POINTS, points);
240     }
241 
242     /**
243      * 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>
244      * P<sub>i</sub>], where C(n, k) is the binomial coefficient defined by n! / ( k! (n-k)! ), ! being the factorial operator.
245      * @param t double; the fraction
246      * @param p double...; the points of the curve, where the first and last are begin and end point, and the intermediate ones
247      *            are control points
248      * @return the B&eacute;zier value B(t) of degree n, where n is the number of points in the array
249      */
250     @SuppressWarnings("checkstyle:methodname")
251     private static double Bn(final double t, final double... p)
252     {
253         double b = 0.0;
254         double m = (1.0 - t);
255         int n = p.length - 1;
256         double fn = factorial(n);
257         for (int i = 0; i <= n; i++)
258         {
259             double c = fn / (factorial(i) * (factorial(n - i)));
260             b += c * Math.pow(m, n - i) * Math.pow(t, i) * p[i];
261         }
262         return b;
263     }
264 
265     /**
266      * Calculate factorial(k), which is k * (k-1) * (k-2) * ... * 1. For factorials up to 20, a lookup table is used.
267      * @param k int; the parameter
268      * @return factorial(k)
269      */
270     private static double factorial(final int k)
271     {
272         if (k < fact.length)
273         {
274             return fact[k];
275         }
276         double f = 1;
277         for (int i = 2; i <= k; i++)
278         {
279             f = f * i;
280         }
281         return f;
282     }
283 
284 }