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ézier info</a> 20 */ 21 public class Bezier3d implements Curve3d 22 { 23 /** The x-coordinates of the points of this Bézier. */ 24 private final double[] x; 25 26 /** The y-coordinates of the points of this Bézier. */ 27 private final double[] y; 28 29 /** The z-coordinates of the points of this Bézier. */ 30 private final double[] z; 31 32 /** 33 * Create a Bézier curve of any order. 34 * @param points Point2d... shape points that define the Bézier curve 35 * @throws NullPointerException when <code>points</code> is <code>null</code> 36 * @throws IllegalArgumentException when <code>points</code> has < 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ézier curve of any order. 57 * @param x the x-coordinates of the points that define the Bézier curve 58 * @param y the y-coordinates of the points that define the Bézier curve 59 * @param z the z-coordinates of the points that define the Bé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é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ézier curve 74 * @param y the y-coordinates of the points that define the Bézier curve 75 * @param z the z-coordinates of the points that define the Bé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ézier, which is a Bézier of 1 order lower. 96 * @return derivative Bé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ézier curve is based on. 106 * @return the number of points (or x-y pairs) that this Bé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é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ézier curve. 134 * @param i the index 135 * @return the x-coordinate of the i'th point of this Bézier curve 136 * @throws IndexOutOfBoundsException when <code>i < 0</code>, or <code>i ≥ 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ézier curve. 145 * @param i the index 146 * @return the y-coordinate of the i'th point of this Bézier curve 147 * @throws IndexOutOfBoundsException when <code>i < 0</code>, or <code>i ≥ 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ézier curve. 156 * @param i the index 157 * @throws IndexOutOfBoundsException when <code>i < 0</code>, or <code>i ≥ size()</code> 158 * @return the z-coordinate of the i'th point of this Bé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 }