#include "G3D/platform.h" #include "G3D/Spline.h" namespace G3D { float SplineBase::getFinalInterval() const { if (extrapolationMode != SplineExtrapolationMode::CYCLIC) { return 0; } else if (finalInterval <= 0) { int N = time.size(); if (N >= 2) { return (time[1] - time[0] + time[N - 1] - time[N - 2]) * 0.5f; } else { return 1.0f; } } else { return finalInterval; } } Matrix4 SplineBase::computeBasis() { // The standard Catmull-Rom spline basis (e.g., Watt & Watt p108) // is for [u^3 u^2 u^1 u^0] * B * [p[0] p[1] p[2] p[3]]^T. // We need a basis formed for: // // U * C * [2*p'[1] p[1] p[2] 2*p'[2]]^T // // U * C * [p2-p0 p1 p2 p3-p1]^T // // To make this transformation, compute the differences of columns in C: // For [p0 p1 p2 p3] Matrix4 basis = Matrix4( -1, 3, -3, 1, 2, -5, 4, -1, -1, 0, 1, 0, 0, 2, 0, 0) * 0.5f; // For [-p0 p1 p2 p3]^T basis.setColumn(0, -basis.column(0)); // For [-p0 p1 p2 p3-p1]^T basis.setColumn(1, basis.column(1) + basis.column(3)); // For [p2-p0 p1 p2 p3-p1]^T basis.setColumn(2, basis.column(2) - basis.column(0)); return basis; } float SplineBase::duration() const { if (time.size() == 0) { return 0; } else { return time.last() - time[0] + getFinalInterval(); } } void SplineBase::computeIndexInBounds(float s, int& i, float& u) const { int N = time.size(); float t0 = time[0]; float tn = time[N - 1]; i = iFloor((N - 1) * (s - t0) / (tn - t0)); // Inclusive bounds for binary search int hi = N - 1; int lo = 0; while ((time[i] > s) || (time[i + 1] <= s)) { if (time[i] > s) { // too big hi = i - 1; } else if (time[i + 1] <= s) { // too small lo = i + 1; } i = (hi + lo) / 2; } // Having exited the above loop, i must be correct, so compute u. u = (s - time[i]) / (time[i + 1] - time[i]); } void SplineBase::computeIndex(float s, int& i, float& u) const { int N = time.size(); debugAssertM(N > 0, "No control points"); float t0 = time[0]; float tn = time[N - 1]; if (N < 2) { // No control points to work with i = 0; u = 0.0; } else if (extrapolationMode == SplineExtrapolationMode::CYCLIC) { float fi = getFinalInterval(); // Cyclic spline if ((s < t0) || (s >= tn + fi)) { // Cyclic, off the bottom or top // Compute offset and reduce to the in-bounds case float d = duration(); // Number of times we wrapped around the cyclic array int wraps = iFloor((s - t0) / d); debugAssert(s - d * wraps >= t0); debugAssert(s - d * wraps < tn + getFinalInterval()); computeIndex(s - d * wraps, i, u); i += wraps * N; } else if (s >= tn) { debugAssert(s < tn + fi); // Cyclic, off the top but before the end of the last interval i = N - 1; u = (s - tn) / fi; } else { // Cyclic, in bounds computeIndexInBounds(s, i, u); } } else { // Non-cyclic if (s < t0) { // Non-cyclic, off the bottom. Assume points are spaced // following the first time interval. float dt = time[1] - t0; float x = (s - t0) / dt; i = iFloor(x); u = x - i; } else if (s >= tn) { // Non-cyclic, off the top. Assume points are spaced following // the last time interval. float dt = tn - time[N - 2]; float x = N - 1 + (s - tn) / dt; i = iFloor(x); u = x - i; } else { // In bounds, non-cyclic. Assume a regular // distribution (which gives O(1) for uniform spacing) // and then binary search to handle the general case // efficiently. computeIndexInBounds(s, i, u); } // if in bounds } // extrapolation Mode } }