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