| 1 | // tSpline.cpp  |
| 2 | //  |
| 3 | // Implementations for splines and paths with various degrees of smoothness. A 'path', or 'spline', is arbitrarily long  |
| 4 | // and may be composed of smaller path sections called 'curves'. For example, a Bezier path is made from multiple  |
| 5 | // Bezier curves. Implementations for piecewise-linear paths, Hermite/Bezier curves and paths, and Nonuniform  |
| 6 | // Nonrational Cubic-Basis splines can be found in this file.  |
| 7 | //  |
| 8 | // Regarding naming, the word 'spline' refers to any path that is composed of piecewise parts. Strictly speaking one  |
| 9 | // could call a composite of multiple Bezier curves a 'Bezier Spline' but it is not a common term. In this file the  |
| 10 | // word 'path' is used for a composite of Bezier curves or a composite of line segments, and we reserve the word spline  |
| 11 | // for paths composed of multiple cubic polynomial pieces.  |
| 12 | //  |
| 13 | // Copyright (c) 2006, 2017, 2020 Tristan Grimmer.  |
| 14 | // Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby  |
| 15 | // granted, provided that the above copyright notice and this permission notice appear in all copies.  |
| 16 | //  |
| 17 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL  |
| 18 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,  |
| 19 | // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN  |
| 20 | // AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR  |
| 21 | // PERFORMANCE OF THIS SOFTWARE.  |
| 22 |   |
| 23 | #include <Foundation/tStandard.h>  |
| 24 | #include "Math/tSpline.h"  |
| 25 |   |
| 26 |   |
| 27 | void tMath::tPiecewiseLinearPath::SetPoints(const tVector3* points, int numPoints)  |
| 28 | {  |
| 29 | tAssert(numPoints > 1);  |
| 30 | Clear();  |
| 31 | NumPoints = numPoints;  |
| 32 | Points = new tVector3[numPoints];  |
| 33 | tStd::tMemcpy(Points, points, numPoints*sizeof(tVector3));  |
| 34 | PercentagePoints = new float[numPoints];  |
| 35 |   |
| 36 | // Fill in the percentage points array with distances.  |
| 37 | PercentagePoints[0] = 0.0f;  |
| 38 | TotalLength = 0.0f;  |
| 39 | for (int p = 0; p < NumPoints-1; p++)  |
| 40 | {  |
| 41 | TotalLength += tDistBetween(Points[p], Points[p+1]);  |
| 42 | PercentagePoints[p+1] = TotalLength;  |
| 43 | }  |
| 44 |   |
| 45 | // Now modify the percentage points array so it represents percentages.  |
| 46 | PercentagePoints[NumPoints-1] = 1.0f;  |
| 47 | for (int p = 1; p < NumPoints-1; p++)  |
| 48 | {  |
| 49 | if (TotalLength)  |
| 50 | PercentagePoints[p] /= TotalLength;  |
| 51 | else  |
| 52 | PercentagePoints[p] = 0.0f;  |
| 53 | }  |
| 54 | }  |
| 55 |   |
| 56 |   |
| 57 | tMath::tVector3 tMath::tPiecewiseLinearPath::GetPoint(float t) const  |
| 58 | {  |
| 59 | if (!IsValid())  |
| 60 | return tVector3(0.0f, 0.0f, 0.0f);  |
| 61 |   |
| 62 | // We need the slow truncating cast here.  |
| 63 | int firstIndex = int(t);  |
| 64 | int lastIndex = firstIndex + 1;  |
| 65 | if (t < 0.0f)  |
| 66 | {  |
| 67 | firstIndex = 0;  |
| 68 | lastIndex = 1;  |
| 69 | }  |
| 70 | else if (t > float(NumPoints - 1))  |
| 71 | {  |
| 72 | firstIndex = NumPoints - 2;  |
| 73 | lastIndex = NumPoints - 1;  |
| 74 | }  |
| 75 |   |
| 76 | t -= float(firstIndex);  |
| 77 | tVector3& first = Points[firstIndex];  |
| 78 | tVector3& last = Points[lastIndex];  |
| 79 |   |
| 80 | float x = tLisc(t, first.x, last.x);  |
| 81 | float y = tLisc(t, first.y, last.y);  |
| 82 | float z = tLisc(t, first.z, last.z);  |
| 83 |   |
| 84 | return tVector3(x, y, z);  |
| 85 | }  |
| 86 |   |
| 87 |   |
| 88 | tMath::tVector3 tMath::tPiecewiseLinearPath::GetPercentPoint(float t) const  |
| 89 | {  |
| 90 | if (!IsValid())  |
| 91 | return tVector3::zero;  |
| 92 |   |
| 93 | tiSaturate(t);  |
| 94 | tVector3* a = 0;  |
| 95 | tVector3* b = 0;  |
| 96 | float pa = 0.0f;  |
| 97 | float pb = 0.0f;  |
| 98 |   |
| 99 | for (int p = 0; p < NumPoints-1; p++)  |
| 100 | {  |
| 101 | pa = PercentagePoints[p];  |
| 102 | pb = PercentagePoints[p+1];  |
| 103 | if ((t >= pa) && (t <= pb))  |
| 104 | {  |
| 105 | a = Points + p;  |
| 106 | b = Points + p + 1;  |
| 107 | break;  |
| 108 | }  |
| 109 | }  |
| 110 | tAssert(a && b);  |
| 111 |   |
| 112 | t = (t-pa) / (pb-pa);  |
| 113 | float x = tLisc(t, a->x, b->x);  |
| 114 | float y = tLisc(t, a->y, b->y);  |
| 115 | float z = tLisc(t, a->z, b->z);  |
| 116 |   |
| 117 | return tVector3(x, y, z);  |
| 118 | }  |
| 119 |   |
| 120 |   |
| 121 | void tMath::tBezierCurve::GetPoint(tVector3& point, float t) const  |
| 122 | {  |
| 123 | tAssert((t >= 0.0f) && (t <= 1.0f));  |
| 124 | float c = 1.0f - t;  |
| 125 |   |
| 126 | // The Bernstein polynomials.  |
| 127 | float bb0 = c*c*c;  |
| 128 | float bb1 = 3*t*c*c;  |
| 129 | float bb2 = 3*t*t*c;  |
| 130 | float bb3 = t*t*t;  |
| 131 |   |
| 132 | point = ControlVerts[0]*bb0 + ControlVerts[1]*bb1 + ControlVerts[2]*bb2 + ControlVerts[3]*bb3;  |
| 133 | }  |
| 134 |   |
| 135 |   |
| 136 | void tMath::tBezierCurve::GetTangent(tVector3& v, float t) const  |
| 137 | {  |
| 138 | // See: http://bimixual.org/AnimationLibrary/beziertangents.html  |
| 139 | tAssert((t >= 0.0f) && (t <= 1.0f));  |
| 140 |   |
| 141 | const tVector3 q0 = ControlVerts[0] + ((ControlVerts[1] - ControlVerts[0]) * t);  |
| 142 | const tVector3 q1 = ControlVerts[1] + ((ControlVerts[2] - ControlVerts[1]) * t);  |
| 143 | const tVector3 q2 = ControlVerts[2] + ((ControlVerts[3] - ControlVerts[2]) * t);  |
| 144 |   |
| 145 | const tVector3 r0 = q0 + ((q1 - q0) * t);  |
| 146 | const tVector3 r1 = q1 + ((q2 - q1) * t);  |
| 147 |   |
| 148 | v = r1 - r0;  |
| 149 | }  |
| 150 |   |
| 151 |   |
| 152 | float tMath::tBezierCurve::GetClosestParamRec(const tVector3& p, tComponents components, float start, float end, float threshold) const  |
| 153 | {  |
| 154 | float mid = (start + end)/2.0f;  |
| 155 |   |
| 156 | // Base case for recursion.  |
| 157 | if ((end - start) < threshold)  |
| 158 | return mid;  |
| 159 |   |
| 160 | // The two halves have param range [start, mid] and [mid, end]. We decide which one to use by using a midpoint param  |
| 161 | // calculation for each section.  |
| 162 | float paramA = (start+mid) / 2.0f;  |
| 163 | float paramB = (mid+end) / 2.0f;  |
| 164 |   |
| 165 | tVector3 pos(p);  |
| 166 | pos.Zero(~components);  |
| 167 |   |
| 168 | tVector3 posA;  |
| 169 | GetPoint(posA, paramA);  |
| 170 | posA.Zero(~components);  |
| 171 |   |
| 172 | tVector3 posB;  |
| 173 | GetPoint(posB, paramB);  |
| 174 | posB.Zero(~components);  |
| 175 |   |
| 176 | float distASq = (posA - pos).LengthSq();  |
| 177 | float distBSq = (posB - pos).LengthSq();  |
| 178 |   |
| 179 | if (distASq < distBSq)  |
| 180 | end = mid;  |
| 181 | else  |
| 182 | start = mid;  |
| 183 |   |
| 184 | // The (tail) recursive call.  |
| 185 | return GetClosestParamRec(p, components, start, end, threshold);  |
| 186 | }  |
| 187 |   |
| 188 |   |
| 189 | tMath::tComponents tMath::tBezierPath::tFastSectionState::CompareComponents = tMath::tComponent_All;  |
| 190 | tMath::tVector3 tMath::tBezierPath::tFastSectionState::ComparePos = tVector3::zero;  |
| 191 | const tMath::tVector3* tMath::tBezierPath::tFastSectionState::CompareControlVerts = nullptr;  |
| 192 |   |
| 193 |   |
| 194 | tMath::tBezierPath::tBezierPath(const tBezierPath& src) :  |
| 195 | Mode(src.Mode),  |
| 196 | Type(src.Type),  |
| 197 | NumCurveSegments(src.NumCurveSegments),  |
| 198 | NumControlVerts(src.NumControlVerts)  |
| 199 | {  |
| 200 | if (!src.IsValid())  |
| 201 | {  |
| 202 | ControlVerts = nullptr;  |
| 203 | return;  |
| 204 | }  |
| 205 |   |
| 206 | switch (Mode)  |
| 207 | {  |
| 208 | case tMode::InternalCVs:  |
| 209 | ControlVerts = new tVector3[NumControlVerts];  |
| 210 | tStd::tMemcpy(ControlVerts, src.ControlVerts, sizeof(tVector3) * NumControlVerts);  |
| 211 | break;  |
| 212 |   |
| 213 | case tMode::ExternalCVs:  |
| 214 | ControlVerts = src.ControlVerts;  |
| 215 | break;  |
| 216 | }  |
| 217 | }  |
| 218 |   |
| 219 |   |
| 220 | void tMath::tBezierPath::Clear()  |
| 221 | {  |
| 222 | if (Mode == tMode::InternalCVs)  |
| 223 | delete[] ControlVerts;  |
| 224 | ControlVerts = 0;  |
| 225 |   |
| 226 | Mode = tMode::InternalCVs;  |
| 227 | Type = tType::Open;  |
| 228 | NumCurveSegments = 0;  |
| 229 | NumControlVerts = 0;  |
| 230 | }  |
| 231 |   |
| 232 |   |
| 233 | float tMath::tBezierPath::ComputeApproxParamPerCoordinateUnit(tComponents components) const  |
| 234 | {  |
| 235 | if (!IsValid())  |
| 236 | return 0.0f;  |
| 237 |   |
| 238 | // For a closed path this still works if you consider the last point as separate from the first. That is, a closed  |
| 239 | // path is just like an open except the last interpolated point happens to match the first.  |
| 240 | int numInterpolatedPoints = NumCurveSegments + 1;  |
| 241 | if (numInterpolatedPoints < 2)  |
| 242 | return 0.0f;  |
| 243 |   |
| 244 | float totalDist = 0.0f;  |
| 245 | for (int n = 1; n < numInterpolatedPoints; n++)  |
| 246 | {  |
| 247 | tVector3 a = ControlVerts[(n-1)*3];  |
| 248 | tVector3 b = ControlVerts[n*3];  |
| 249 | a.Zero(~components);  |
| 250 | b.Zero(~components);  |
| 251 | totalDist += tDistBetween(a, b);  |
| 252 | }  |
| 253 |   |
| 254 | if (totalDist == 0.0f)  |
| 255 | return 0.0f;  |
| 256 |   |
| 257 | return float(NumCurveSegments) / totalDist;  |
| 258 | }  |
| 259 |   |
| 260 |   |
| 261 | void tMath::tBezierPath::InterpolatePoints(const tVector3* knots, int numKnots, tType type)  |
| 262 | {  |
| 263 | tAssert(numKnots >= 2);  |
| 264 | Clear();  |
| 265 |   |
| 266 | Mode = tMode::InternalCVs;  |
| 267 | Type = type;  |
| 268 | switch (Type)  |
| 269 | {  |
| 270 | case tType::Open:  |
| 271 | {  |
| 272 | NumCurveSegments = numKnots - 1;  |
| 273 | NumControlVerts = 3*numKnots - 2;  |
| 274 | ControlVerts = new tVector3[NumControlVerts];  |
| 275 |   |
| 276 | // Place the interpolated CVs.  |
| 277 | for (int n = 0; n < numKnots; n++)  |
| 278 | ControlVerts[n*3] = knots[n];  |
| 279 |   |
| 280 | // Place the first and last non-interploated CVs.  |
| 281 | tVector3 initialPoint = (knots[1] - knots[0]) * 0.25f;  |
| 282 |   |
| 283 | // Interp 1/4 away along first segment.  |
| 284 | ControlVerts[1] = knots[0] + initialPoint;  |
| 285 | tVector3 finalPoint = (knots[numKnots-2] - knots[numKnots-1]) * 0.25f;  |
| 286 |   |
| 287 | // Interp 1/4 backward along last segment.  |
| 288 | ControlVerts[NumControlVerts-2] = knots[numKnots-1] + finalPoint;  |
| 289 |   |
| 290 | // Now we'll do all the interior non-interpolated CVs.  |
| 291 | for (int k = 1; k < NumCurveSegments; k++)  |
| 292 | {  |
| 293 | tVector3 a = knots[k-1] - knots[k];  |
| 294 | tVector3 b = knots[k+1] - knots[k];  |
| 295 |   |
| 296 | float aLen = a.Length();  |
| 297 | float bLen = b.Length();  |
| 298 |   |
| 299 | if (aLen && bLen)  |
| 300 | {  |
| 301 | float abLen = (aLen + bLen) / 8.0f;  |
| 302 | tVector3 ab = (b/bLen) - (a/aLen);  |
| 303 | ab.Normalize();  |
| 304 | ab *= abLen;  |
| 305 |   |
| 306 | ControlVerts[k*3 - 1] = knots[k] - ab;  |
| 307 | ControlVerts[k*3 + 1] = knots[k] + ab;  |
| 308 | }  |
| 309 | else  |
| 310 | {  |
| 311 | ControlVerts[k*3 - 1] = knots[k];  |
| 312 | ControlVerts[k*3 + 1] = knots[k];  |
| 313 | }  |
| 314 | }  |
| 315 | break;  |
| 316 | }  |
| 317 |   |
| 318 | case tType::Closed:  |
| 319 | {  |
| 320 | NumCurveSegments = numKnots;  |
| 321 |   |
| 322 | // We duplicate the first point at the end so we have contiguous memory to look of the curve value. That's  |
| 323 | // what the +1 is for.  |
| 324 | NumControlVerts = 3*numKnots + 1;  |
| 325 | ControlVerts = new tVector3[NumControlVerts];  |
| 326 |   |
| 327 | // First lets place the interpolated CVs and duplicate the first into the last CV slot.  |
| 328 | for (int n = 0; n < numKnots; n++)  |
| 329 | ControlVerts[n*3] = knots[n];  |
| 330 |   |
| 331 | ControlVerts[NumControlVerts-1] = knots[0];  |
| 332 |   |
| 333 | // Now we'll do all the interior non-interpolated CVs. We go to k=NumCurveSegments which will compute the  |
| 334 | // two CVs around the zeroeth knot (points[0]).  |
| 335 | for (int k = 1; k <= NumCurveSegments; k++)  |
| 336 | {  |
| 337 | int modkm1 = k-1;  |
| 338 | int modkp1 = (k+1) % NumCurveSegments;  |
| 339 | int modk = k % NumCurveSegments;  |
| 340 |   |
| 341 | tVector3 a = knots[modkm1] - knots[modk];  |
| 342 | tVector3 b = knots[modkp1] - knots[modk];  |
| 343 |   |
| 344 | float aLen = a.Length();  |
| 345 | float bLen = b.Length();  |
| 346 |   |
| 347 | int mod3km1 = 3*k - 1;  |
| 348 |   |
| 349 | // Need the -1 so the end point is a duplicated start point.  |
| 350 | int mod3kp1 = (3*k + 1) % (NumControlVerts-1);  |
| 351 | if (aLen && bLen)  |
| 352 | {  |
| 353 | float abLen = (aLen + bLen) / 8.0f;  |
| 354 | tVector3 ab = (b/bLen) - (a/aLen);  |
| 355 | ab.Normalize();  |
| 356 | ab *= abLen;  |
| 357 |   |
| 358 | ControlVerts[mod3km1] = knots[modk] - ab;  |
| 359 | ControlVerts[mod3kp1] = knots[modk] + ab;  |
| 360 | }  |
| 361 | else  |
| 362 | {  |
| 363 | ControlVerts[mod3km1] = knots[modk];  |
| 364 | ControlVerts[mod3kp1] = knots[modk];  |
| 365 | }  |
| 366 | }  |
| 367 | break;  |
| 368 | }  |
| 369 | }  |
| 370 | }  |
| 371 |   |
| 372 |   |
| 373 | void tMath::tBezierPath::SetControlVerts(const tVector3* cvs, int numCVs, tMode mode, tType type)  |
| 374 | {  |
| 375 | tAssert(cvs);  |
| 376 | tAssert( ((type == tType::Open) && (numCVs >= 4)) || ((type == tType::Closed) && (numCVs >= 7)) );  |
| 377 | tAssert(((numCVs-1) % 3) == 0);  |
| 378 |   |
| 379 | Clear();  |
| 380 | Mode = mode;  |
| 381 | Type = type;  |
| 382 |   |
| 383 | NumControlVerts = numCVs;  |
| 384 | NumCurveSegments = ((numCVs - 1) / 3);  |
| 385 | switch (Mode)  |
| 386 | {  |
| 387 | case tMode::InternalCVs:  |
| 388 | ControlVerts = new tVector3[NumControlVerts];  |
| 389 | tStd::tMemcpy(ControlVerts, cvs, sizeof(tVector3) * NumControlVerts);  |
| 390 | break;  |
| 391 |   |
| 392 | case tMode::ExternalCVs:  |
| 393 | // Since the mode is external we know they won't be deleted so it's safe to cast the const away.  |
| 394 | ControlVerts = (tVector3*)cvs;  |
| 395 | break;  |
| 396 | }  |
| 397 | }  |
| 398 |   |
| 399 |   |
| 400 | void tMath::tBezierPath::GetPoint(tVector3& point, float t) const  |
| 401 | {  |
| 402 | if (!IsValid())  |
| 403 | return;  |
| 404 |   |
| 405 | // Only closed paths accept t values out of range.  |
| 406 | if (Type == tType::Closed)  |
| 407 | {  |
| 408 | while (t < 0.0f)  |
| 409 | t += float(NumCurveSegments);  |
| 410 |   |
| 411 | while (t > float(NumCurveSegments))  |
| 412 | t -= float(NumCurveSegments);  |
| 413 | }  |
| 414 | else  |
| 415 | {  |
| 416 | tiClamp(t, 0.0f, float(NumCurveSegments));  |
| 417 | }  |
| 418 |   |
| 419 | // Segment 0 is for t E [0, 1). The last segment is for t E [NumCurveSegments-1, NumCurveSegments].  |
| 420 | // The following 'if' statement deals with the final inclusive bracket on the last segment. The cast must truncate.  |
| 421 | int segment = int(t);  |
| 422 | if (segment >= NumCurveSegments)  |
| 423 | segment = NumCurveSegments - 1;  |
| 424 |   |
| 425 | tBezierCurve bc( &ControlVerts[3*segment] );  |
| 426 | bc.GetPoint(point, t - float(segment));  |
| 427 | }  |
| 428 |   |
| 429 |   |
| 430 | void tMath::tBezierPath::GetTangent(tVector3& v, float t) const  |
| 431 | {  |
| 432 | if (!IsValid())  |
| 433 | return;  |
| 434 |   |
| 435 | // Only closed paths accept t values out of range.  |
| 436 | if (Type == tType::Closed)  |
| 437 | {  |
| 438 | while (t < 0.0f)  |
| 439 | t += float(NumCurveSegments);  |
| 440 |   |
| 441 | while (t > float(NumCurveSegments))  |
| 442 | t -= float(NumCurveSegments);  |
| 443 | }  |
| 444 | else  |
| 445 | {  |
| 446 | tiClamp(t, 0.0f, float(NumCurveSegments));  |
| 447 | }  |
| 448 |   |
| 449 | // Segment 0 is for t E [0, 1). The last segment is for t E [NumCurveSegments-1, NumCurveSegments].  |
| 450 | // The following 'if' statement deals with the final inclusive bracket on the last segment. The cast must truncate.  |
| 451 | int segment = int(t);  |
| 452 | if (segment >= NumCurveSegments)  |
| 453 | segment = NumCurveSegments - 1;  |
| 454 |   |
| 455 | tBezierCurve bc( &ControlVerts[3*segment] );  |
| 456 | bc.GetTangent(v, t - float(segment));  |
| 457 | }  |
| 458 |   |
| 459 |   |
| 460 | bool tMath::tBezierPath::tFastSectionState::CompareSections(const tSectionInfo& a, const tSectionInfo& b)  |
| 461 | {  |
| 462 | // My attempt at a stable section sorting function. Note that the versions that used all 4 points individually  |
| 463 | // fail to be stable because they are dependent on the particular sections being compared.  |
| 464 | tVector3 approxAvgA = (CompareControlVerts[a.StartIndex] + CompareControlVerts[a.StartIndex + 3]) / 2.0f;  |
| 465 | tVector3 approxAvgB = (CompareControlVerts[b.StartIndex] + CompareControlVerts[b.StartIndex + 3]) / 2.0f;  |
| 466 |   |
| 467 | approxAvgA.Zero(~CompareComponents);  |
| 468 | approxAvgB.Zero(~CompareComponents);  |
| 469 |   |
| 470 | float distSqA = tDistBetweenSq(ComparePos, approxAvgA);  |
| 471 | float distSqB = tDistBetweenSq(ComparePos, approxAvgB);  |
| 472 | return distSqA < distSqB;  |
| 473 | }  |
| 474 |   |
| 475 |   |
| 476 | void tMath::tBezierPath::tFastSectionState::Set(const tVector3& pos, tComponents components, const tVector3* cvs, int numCVs) const  |
| 477 | {  |
| 478 | Clear();  |
| 479 | Components = components;  |
| 480 | for (int p = 0; p < numCVs - 1; p += 3)  |
| 481 | Sections.Append(new tSectionInfo(p));  |
| 482 |   |
| 483 | // @todo There's gotta be a more modern way to do this. Maybe with a closure.  |
| 484 | CompareComponents = Components;  |
| 485 | ComparePos = pos;  |
| 486 | CompareControlVerts = cvs;  |
| 487 | Sections.Sort(CompareSections, tListSortAlgorithm::Merge);  |
| 488 | }  |
| 489 |   |
| 490 |   |
| 491 | void tMath::tBezierPath::tFastSectionState::Update(const tVector3& pos, const tVector3* cvs) const  |
| 492 | {  |
| 493 | CompareComponents = Components;  |
| 494 | ComparePos = pos;  |
| 495 | CompareControlVerts = cvs;  |
| 496 | Sections.Sort(CompareSections, tListSortAlgorithm::Bubble);  |
| 497 | }  |
| 498 |   |
| 499 |   |
| 500 | float tMath::tBezierPath::GetClosestParam(const tVector3& p, tComponents components, float paramThreshold, const tBezierPath::tFastSectionState& optObj) const  |
| 501 | {  |
| 502 | // Can we use the supplied FastSectionState if it has the correct components, number of sections, and CVs. If  |
| 503 | // anything is wrong, we simply clear the optimization object for next time.  |
| 504 | if (((optObj.Sections.GetNumItems() * 3) + 1) != NumControlVerts)  |
| 505 | optObj.Clear();  |
| 506 |   |
| 507 | if (optObj.Components != components)  |
| 508 | optObj.Clear();  |
| 509 |   |
| 510 | if (!optObj.Sections.GetNumItems())  |
| 511 | optObj.Set(p, components, ControlVerts, NumControlVerts);  |
| 512 | else  |
| 513 | optObj.Update(p, ControlVerts);  |
| 514 |   |
| 515 | // We try the first 3 sections. In most cases the closest will be in the first but due to the nature of the sort  |
| 516 | // function, it is prudent to check one on either side.  |
| 517 | tFastSectionState::tSectionInfo* section = optObj.Sections.First();  |
| 518 | float minDistSq = MaxFloat;  |
| 519 | float closestParam = 0.0f;  |
| 520 | tVector3 pos(p);  |
| 521 | pos.Zero(~components);  |
| 522 |   |
| 523 | for (int s = 0; (s < 3) && section; s++, section = section->Next())  |
| 524 | {  |
| 525 | tBezierCurve curve(ControlVerts + section->StartIndex);  |
| 526 | float curveClosestParam = curve.GetClosestParam(pos, components, paramThreshold);  |
| 527 |   |
| 528 | tVector3 curvePos;  |
| 529 | curve.GetPoint(curvePos, curveClosestParam);  |
| 530 | curvePos.Zero(~components);  |
| 531 | float distSq = (curvePos - pos).LengthSq();  |
| 532 | if (distSq < minDistSq)  |
| 533 | {  |
| 534 | minDistSq = distSq;  |
| 535 | float startParam = float(section->StartIndex) / 3.0f;  |
| 536 | closestParam = startParam + curveClosestParam;  |
| 537 | }  |
| 538 | }  |
| 539 |   |
| 540 | return closestParam;  |
| 541 | }  |
| 542 |   |
| 543 |   |
| 544 | tMath::tNNBSpline::tNNBSpline(int* knotValueSequence, tVector3* controlPoints, int numControlPoints, float paramRange) :  |
| 545 | KVS(knotValueSequence)  |
| 546 | {  |
| 547 | tAssert(!"tNNBSpline not implemented." );  |
| 548 | }  |
| 549 |   |
| 550 |   |
| 551 | tMath::tNNBSpline::~tNNBSpline()  |
| 552 | {  |
| 553 | }  |
| 554 |   |
| 555 |   |
| 556 | tMath::tVector3 tMath::tNNBSpline::Q(int i, float t)  |
| 557 | {  |
| 558 | tVector3 t1 = CPs[i-3] * B4(i-3, t);  |
| 559 | tVector3 t2 = CPs[i-2] * B4(i-2, t);  |
| 560 | tVector3 t3 = CPs[i-1] * B4(i-1, t);  |
| 561 | tVector3 t4 = CPs[i ] * B4(i , t);  |
| 562 |   |
| 563 | return t1 + t2 + t3 + t4;  |
| 564 | }  |
| 565 |   |
| 566 |   |
| 567 | float tMath::tNNBSpline::B1(int i, float t)  |
| 568 | {  |
| 569 | if ( (KVS[i] <= t) && (t < KVS[i+1]) )  |
| 570 | return 1.0f;  |
| 571 | else  |
| 572 | return 0.0f;  |
| 573 | }  |
| 574 |   |
| 575 |   |
| 576 | float tMath::tNNBSpline::B2(int i, float t)  |
| 577 | {  |
| 578 | int ti0 = KVS[i];  |
| 579 | int ti1 = KVS[i+1];  |
| 580 | int ti2 = KVS[i+2];  |
| 581 |   |
| 582 | int denom1 = ti1-ti0;  |
| 583 | int denom2 = ti2-ti1;  |
| 584 |   |
| 585 | float term1 = 0;  |
| 586 | if (denom1)  |
| 587 | term1 = B1(i,t) * (t-ti0)/float(denom1);  |
| 588 |   |
| 589 | float term2 = 0;  |
| 590 | if (denom2)  |
| 591 | term2 = B1(i+1, t) * (ti2-t)/float(denom2);  |
| 592 |   |
| 593 | return term1 + term2;  |
| 594 | }  |
| 595 |   |
| 596 |   |
| 597 | float tMath::tNNBSpline::B3(int i, float t)  |
| 598 | {  |
| 599 | int ti0 = KVS[i];  |
| 600 | int ti1 = KVS[i+1];  |
| 601 | int ti2 = KVS[i+2];  |
| 602 | int ti3 = KVS[i+3];  |
| 603 |   |
| 604 | int denom1 = ti2-ti0;  |
| 605 | int denom2 = ti3-ti1;  |
| 606 |   |
| 607 | float term1 = 0;  |
| 608 | if (denom1)  |
| 609 | term1 = B2(i,t) * (t-ti0)/float(denom1);  |
| 610 |   |
| 611 | float term2 = 0;  |
| 612 | if (denom2)  |
| 613 | term2 = B2(i+1, t) * (ti3-t)/float(denom2);  |
| 614 |   |
| 615 | return term1 + term2;  |
| 616 | }  |
| 617 |   |
| 618 |   |
| 619 | float tMath::tNNBSpline::B4(int i, float t)  |
| 620 | {  |
| 621 | int ti0 = KVS[i];  |
| 622 | int ti1 = KVS[i+1];  |
| 623 | int ti3 = KVS[i+2];  |
| 624 | int ti4 = KVS[i+3];  |
| 625 |   |
| 626 | int denom1 = ti3-ti0;  |
| 627 | int denom2 = ti4-ti1;  |
| 628 |   |
| 629 | float term1 = 0;  |
| 630 | if (denom1)  |
| 631 | term1 = B3(i,t) * (t-ti0)/float(denom1);  |
| 632 |   |
| 633 | float term2 = 0;  |
| 634 | if (denom2)  |
| 635 | term2 = B3(i+1, t) * (ti4-t)/float(denom2);  |
| 636 |   |
| 637 | return term1 + term2;  |
| 638 | }  |
| 639 | |