View Javadoc
1   package org.djutils.draw.curve;
2   
3   import java.util.Arrays;
4   
5   import org.djutils.draw.line.PolyLine3d;
6   import org.djutils.draw.point.Point3d;
7   import org.djutils.exceptions.Throw;
8   
9   /**
10   * Continuous definition of a Bézier curve in 3d. This class is simply a helper class for (and a super of)
11   * {@code BezierCubic3d}, which uses this class to determine curvature, offset lines, etc.
12   * <p>
13   * Copyright (c) 2023-2025 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
14   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
15   * </p>
16   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
17   * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
18   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
19   * @see <a href="https://pomax.github.io/bezierinfo/">B&eacute;zier info</a>
20   */
21  public class Bezier3d implements Curve3d
22  {
23      /** The x-coordinates of the points of this B&eacute;zier. */
24      private final double[] x;
25  
26      /** The y-coordinates of the points of this B&eacute;zier. */
27      private final double[] y;
28  
29      /** The z-coordinates of the points of this B&eacute;zier. */
30      private final double[] z;
31  
32      /**
33       * Create a B&eacute;zier curve of any order.
34       * @param points Point2d... shape points that define the B&eacute;zier curve
35       * @throws NullPointerException when <code>points</code> is <code>null</code>
36       * @throws IllegalArgumentException when <code>points</code> has &lt; 2 elements
37       */
38      public Bezier3d(final Point3d... points)
39      {
40          Throw.when(points.length < 2, IllegalArgumentException.class, "minimum number of points is 2");
41          this.x = new double[points.length];
42          this.y = new double[points.length];
43          this.z = new double[points.length];
44          int index = 0;
45          for (Point3d point : points)
46          {
47              Throw.whenNull(point, "One of the points is null");
48              this.x[index] = point.x;
49              this.y[index] = point.y;
50              this.z[index] = point.z;
51              index++;
52          }
53      }
54  
55      /**
56       * Create a B&eacute;zier curve of any order.
57       * @param x the x-coordinates of the points that define the B&eacute;zier curve
58       * @param y the y-coordinates of the points that define the B&eacute;zier curve
59       * @param z the z-coordinates of the points that define the B&eacute;zier curve
60       * @throws NullPointerException when <code>x</code>, <code>y</code>, or <code>z</code> is <code>null</code>
61       * @throws IllegalArgumentException when the length of the <code>x</code> array is not equal to the length of the
62       *             <code>y</code> array, or not equal to the length of the <code>z</code> array, or less than <code>2</code>
63       */
64      public Bezier3d(final double[] x, final double[] y, final double[] z)
65      {
66          this(true, x, y, z);
67      }
68  
69      /**
70       * Construct a B&eacute;zier curve of any order, optionally checking the lengths of the provided arrays.
71       * @param checkLengths if <code>true</code>; check the lengths of the <code>x</code> and <code>y</code> arrays; if
72       *            <code>false</code>; do not check those lengths
73       * @param x the x-coordinates of the points that define the B&eacute;zier curve
74       * @param y the y-coordinates of the points that define the B&eacute;zier curve
75       * @param z the z-coordinates of the points that define the B&eacute;zier curve
76       * @throws NullPointerException when <code>x</code>, <code>y</code>, or <code>z</code> is <code>null</code>
77       * @throws IllegalArgumentException when the length of the <code>x</code> array is not equal to the length of the
78       *             <code>y</code> array, or not equal to the length of the <code>z</code> array, or less than <code>2</code> and
79       *             <code>checkLengths</code> is <code>true</code>
80       */
81      private Bezier3d(final boolean checkLengths, final double[] x, final double[] y, final double[] z)
82      {
83          if (checkLengths)
84          {
85              Throw.when(x.length < 1, IllegalArgumentException.class, "minimum number of points is 1");
86              Throw.when(x.length != y.length || x.length != z.length, IllegalArgumentException.class,
87                      "length of x-array must be same as length of y-array and length of z-array");
88          }
89          this.x = Arrays.copyOf(x, x.length);
90          this.y = Arrays.copyOf(y, y.length);
91          this.z = Arrays.copyOf(z, z.length);
92      }
93  
94      /**
95       * Returns the derivative for a B&eacute;zier, which is a B&eacute;zier of 1 order lower.
96       * @return derivative B&eacute;zier
97       * @throws IllegalStateException when the order of <code>this Bezier2d</code> is <code>1</code>
98       */
99      public Bezier3d derivative()
100     {
101         return new Bezier3d(false, Bezier.derivative(this.x), Bezier.derivative(this.y), Bezier.derivative(this.z));
102     }
103 
104     /**
105      * Return the number of points (or x-y pairs) that this B&eacute;zier curve is based on.
106      * @return the number of points (or x-y pairs) that this B&eacute;zier curve is based on
107      */
108     public int size()
109     {
110         return this.x.length;
111     }
112 
113     /**
114      * Returns the estimated path length of this B&eacute;zier curve using the method of numerical approach of Legendre-Gauss,
115      * which is quite accurate.
116      * @return estimated length.
117      */
118     public double length()
119     {
120         double len = 0.0;
121         Bezier3d derivativeBezier = derivative();
122         for (int i = 0; i < Bezier.T.length; i++)
123         {
124             double t = 0.5 * Bezier.T[i] + 0.5;
125             Point3d p = derivativeBezier.getPoint(t);
126             len += Bezier.C[i] * Math.sqrt(p.x * p.x + p.y * p.y + p.z * p.z);
127         }
128         len *= 0.5;
129         return len;
130     }
131 
132     /**
133      * Retrieve the x-coordinate of the i'th point of this B&eacute;zier curve.
134      * @param i the index
135      * @return the x-coordinate of the i'th point of this B&eacute;zier curve
136      * @throws IndexOutOfBoundsException when <code>i &lt; 0</code>, or <code>i &ge; size()</code>
137      */
138     public double getX(final int i)
139     {
140         return this.x[i];
141     }
142 
143     /**
144      * Retrieve the y-coordinate of the i'th point of this B&eacute;zier curve.
145      * @param i the index
146      * @return the y-coordinate of the i'th point of this B&eacute;zier curve
147      * @throws IndexOutOfBoundsException when <code>i &lt; 0</code>, or <code>i &ge; size()</code>
148      */
149     public double getY(final int i)
150     {
151         return this.y[i];
152     }
153 
154     /**
155      * Retrieve the z-coordinate of the i'th point of this B&eacute;zier curve.
156      * @param i the index
157      * @throws IndexOutOfBoundsException when <code>i &lt; 0</code>, or <code>i &ge; size()</code>
158      * @return the z-coordinate of the i'th point of this B&eacute;zier curve
159      */
160     public double getZ(final int i)
161     {
162         return this.z[i];
163     }
164 
165     @Override
166     public Point3d getPoint(final double t)
167     {
168         return new Point3d(Bezier.Bn(t, this.x), Bezier.Bn(t, this.y), Bezier.Bn(t, this.z));
169     }
170 
171     /** Cache the result of the getLength method. */
172     private double cachedLength = -1;
173 
174     @Override
175     public double getLength()
176     {
177         if (this.cachedLength < 0)
178         {
179             this.cachedLength = length();
180         }
181         return this.cachedLength;
182     }
183 
184     @Override
185     public PolyLine3d toPolyLine(final Flattener3d flattener)
186     {
187         return flattener.flatten(this);
188     }
189 
190     @Override
191     public String toString()
192     {
193         return "Bezier3d [x=" + Arrays.toString(this.x) + ", y=" + Arrays.toString(this.y) + ", z=" + Arrays.toString(this.z)
194                 + "]";
195     }
196 
197     @Override
198     public int hashCode()
199     {
200         final int prime = 31;
201         int result = 1;
202         result = prime * result + Arrays.hashCode(this.x);
203         result = prime * result + Arrays.hashCode(this.y);
204         result = prime * result + Arrays.hashCode(this.z);
205         return result;
206     }
207 
208     @Override
209     @SuppressWarnings("checkstyle:needbraces")
210     public boolean equals(final Object obj)
211     {
212         if (this == obj)
213             return true;
214         if (obj == null)
215             return false;
216         if (getClass() != obj.getClass())
217             return false;
218         Bezier3d other = (Bezier3d) obj;
219         return Arrays.equals(this.x, other.x) && Arrays.equals(this.y, other.y) && Arrays.equals(this.z, other.z);
220     }
221 
222 }