|
| 1 | +/* |
| 2 | + This class represents a cubic Bézier curve. |
| 3 | +
|
| 4 | + getPointAtParameter() method works the same as bezierPoint(). |
| 5 | +
|
| 6 | + Points returned from this method are closer to each other |
| 7 | + at places where the curve bends and farther apart where the |
| 8 | + curve runs straight. |
| 9 | +
|
| 10 | + On the orther hand, getPointAtFraction() and getPointAtLength() |
| 11 | + return points at fixed distances. This is useful in many scenarios: |
| 12 | + you may want to move an object along the curve at some speed |
| 13 | + or you may want to draw dashed Bézier curves. |
| 14 | +*/ |
| 15 | + |
| 16 | + |
| 17 | +class BezierCurve { |
| 18 | + |
| 19 | + private final int SEGMENT_COUNT = 100; |
| 20 | + |
| 21 | + private PVector v0, v1, v2, v3; |
| 22 | + |
| 23 | + private float arcLengths[] = new float[SEGMENT_COUNT + 1]; // there are n segments between n+1 points |
| 24 | + |
| 25 | + private float curveLength; |
| 26 | + |
| 27 | + |
| 28 | + BezierCurve(PVector a, PVector b, PVector c, PVector d) { |
| 29 | + v0 = a.get(); // curve begins here |
| 30 | + v1 = b.get(); |
| 31 | + v2 = c.get(); |
| 32 | + v3 = d.get(); // curve ends here |
| 33 | + |
| 34 | + // The idea here is to make a handy look up table, which contains |
| 35 | + // parameter values with their arc lengths along the curve. Later, |
| 36 | + // when we want a point at some arc length, we can go through our |
| 37 | + // table, pick the place where the point is going to be located and |
| 38 | + // interpolate the value of parameter from two surrounding parameters |
| 39 | + // in our table. |
| 40 | + |
| 41 | + // we will keep current length along the curve here |
| 42 | + float arcLength = 0; |
| 43 | + |
| 44 | + PVector prev = new PVector(); |
| 45 | + prev.set(v0); |
| 46 | + |
| 47 | + // i goes from 0 to SEGMENT_COUNT |
| 48 | + for (int i = 0; i <= SEGMENT_COUNT; i++) { |
| 49 | + |
| 50 | + // map index from range (0, SEGMENT_COUNT) to parameter in range (0.0, 1.0) |
| 51 | + float t = (float) i / SEGMENT_COUNT; |
| 52 | + |
| 53 | + // get point on the curve at this parameter value |
| 54 | + PVector point = pointAtParameter(t); |
| 55 | + |
| 56 | + // get distance from previous point |
| 57 | + float distanceFromPrev = PVector.dist(prev, point); |
| 58 | + |
| 59 | + // add arc length of last segment to total length |
| 60 | + arcLength += distanceFromPrev; |
| 61 | + |
| 62 | + // save current arc length to the look up table |
| 63 | + arcLengths[i] = arcLength; |
| 64 | + |
| 65 | + // keep this point to compute length of next segment |
| 66 | + prev.set(point); |
| 67 | + } |
| 68 | + |
| 69 | + // Here we have sum of all segment lengths, which should be |
| 70 | + // very close to the actual length of the curve. The more |
| 71 | + // segments we use, the more accurate it becomes. |
| 72 | + curveLength = arcLength; |
| 73 | + } |
| 74 | + |
| 75 | + |
| 76 | + // Returns the length of this curve |
| 77 | + float length() { |
| 78 | + return curveLength; |
| 79 | + } |
| 80 | + |
| 81 | + |
| 82 | + // Returns a point along the curve at a specified parameter value. |
| 83 | + PVector pointAtParameter(float t) { |
| 84 | + PVector result = new PVector(); |
| 85 | + result.x = bezierPoint(v0.x, v1.x, v2.x, v3.x, t); |
| 86 | + result.y = bezierPoint(v0.y, v1.y, v2.y, v3.y, t); |
| 87 | + result.z = bezierPoint(v0.z, v1.z, v2.z, v3.z, t); |
| 88 | + return result; |
| 89 | + } |
| 90 | + |
| 91 | + |
| 92 | + |
| 93 | + // Returns a point at a fraction of curve's length. |
| 94 | + // Example: pointAtFraction(0.25) returns point at one quarter of curve's length. |
| 95 | + PVector pointAtFraction(float r) { |
| 96 | + float wantedLength = curveLength * r; |
| 97 | + return pointAtLength(wantedLength); |
| 98 | + } |
| 99 | + |
| 100 | + |
| 101 | + // Returns a point at a specified arc length along the curve. |
| 102 | + PVector pointAtLength(float wantedLength) { |
| 103 | + wantedLength = constrain(wantedLength, 0.0, curveLength); |
| 104 | + |
| 105 | + // look up the length in our look up table |
| 106 | + int index = java.util.Arrays.binarySearch(arcLengths, wantedLength); |
| 107 | + |
| 108 | + float mappedIndex; |
| 109 | + |
| 110 | + if (index < 0) { |
| 111 | + // if the index is negative, exact length is not in the table, |
| 112 | + // but it tells us where it should be in the table |
| 113 | + // see http://docs.oracle.com/javase/7/docs/api/java/util/Arrays.html#binarySearch(float[], float) |
| 114 | + |
| 115 | + // interpolate two surrounding indexes |
| 116 | + int nextIndex = -(index + 1); |
| 117 | + int prevIndex = nextIndex - 1; |
| 118 | + float prevLength = arcLengths[prevIndex]; |
| 119 | + float nextLength = arcLengths[nextIndex]; |
| 120 | + mappedIndex = map(wantedLength, prevLength, nextLength, prevIndex, nextIndex); |
| 121 | + |
| 122 | + } else { |
| 123 | + // wanted length is in the table, we know the index right away |
| 124 | + mappedIndex = index; |
| 125 | + } |
| 126 | + |
| 127 | + // map index from range (0, SEGMENT_COUNT) to parameter in range (0.0, 1.0) |
| 128 | + float parameter = mappedIndex / SEGMENT_COUNT; |
| 129 | + |
| 130 | + return pointAtParameter(parameter); |
| 131 | + } |
| 132 | + |
| 133 | + |
| 134 | + // Returns an array of equidistant point on the curve |
| 135 | + PVector[] equidistantPoints(int howMany) { |
| 136 | + |
| 137 | + PVector[] resultPoints = new PVector[howMany]; |
| 138 | + |
| 139 | + // we already know the beginning and the end of the curve |
| 140 | + resultPoints[0] = v0.get(); |
| 141 | + resultPoints[howMany - 1] = v3.get(); |
| 142 | + |
| 143 | + int arcLengthIndex = 1; |
| 144 | + for (int i = 1; i < howMany - 1; i++) { |
| 145 | + |
| 146 | + // compute wanted arc length |
| 147 | + float fraction = (float) i / (howMany - 1); |
| 148 | + float wantedLength = fraction * curveLength; |
| 149 | + |
| 150 | + // move through the look up table until we find greater length |
| 151 | + while (wantedLength > arcLengths[arcLengthIndex] && arcLengthIndex < arcLengths.length) { |
| 152 | + arcLengthIndex++; |
| 153 | + } |
| 154 | + |
| 155 | + // interpolate two surrounding indexes |
| 156 | + int nextIndex = arcLengthIndex; |
| 157 | + int prevIndex = arcLengthIndex - 1; |
| 158 | + float prevLength = arcLengths[prevIndex]; |
| 159 | + float nextLength = arcLengths[nextIndex]; |
| 160 | + float mappedIndex = map(wantedLength, prevLength, nextLength, prevIndex, nextIndex); |
| 161 | + |
| 162 | + // map index from range (0, SEGMENT_COUNT) to parameter in range (0.0, 1.0) |
| 163 | + float parameter = mappedIndex / SEGMENT_COUNT; |
| 164 | + |
| 165 | + resultPoints[i] = pointAtParameter(parameter); |
| 166 | + } |
| 167 | + |
| 168 | + return resultPoints; |
| 169 | + } |
| 170 | + |
| 171 | + |
| 172 | + // Returns an array of points on the curve. |
| 173 | + PVector[] points(int howMany) { |
| 174 | + |
| 175 | + PVector[] resultPoints = new PVector[howMany]; |
| 176 | + |
| 177 | + // we already know the first and the last point of the curve |
| 178 | + resultPoints[0] = v0.get(); |
| 179 | + resultPoints[howMany - 1] = v3.get(); |
| 180 | + |
| 181 | + for (int i = 1; i < howMany - 1; i++) { |
| 182 | + |
| 183 | + // map index to parameter in range (0.0, 1.0) |
| 184 | + float parameter = (float) i / (howMany - 1); |
| 185 | + |
| 186 | + resultPoints[i] = pointAtParameter(parameter); |
| 187 | + } |
| 188 | + |
| 189 | + return resultPoints; |
| 190 | + } |
| 191 | + |
| 192 | +} |
0 commit comments