/// <summary> /// Fits a cubic spline to the list of control points. /// </summary> /// <param name="transferPoints">Control points to fit a cubic spline to.</param> public void Calculate(List <T> controlPoints) { Debug.Assert(controlPoints.Count >= 3, "A cubic spline cannot be calculated with less " + "than three points. Otherwise it is just a straight line (with two points)."); // We need to solve the equation taken from: http://mathworld.wolfram.com/CubicSpline.html, // // [2 1 0] [D[0]] [3(y[1] - y[0]) ] // |1 4 1 | |D[1]| |3(y[2] - y[0]) | // | 1 4 1 | | . | = | . | // | ..... | | . | | . | // | 1 4 1| | . | |3(y[n] - y[n-2])| // [0 1 2] [D[n]] [3(y[n] - y[n-1])] // // where n is the number of cubics and D[i] are the derivatives at the control points. // This linear system of equations happens to be tridiagonal and therefore can be solved // using the Thomas algorithm (aka TDMA), which is a simplified form of Gaussian elimination // that can obtain the solution in O(n) instead of O(n^3) required by Gaussian elimination. // The Thomas algorithm was taken from: http://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm. int numCubics = controlPoints.Count - 1; T t = new T(); // Construct the coefficients a,b,c and the right-hand-side vector d. T[,] matrixCoef = new T[numCubics + 1, 4]; matrixCoef[0, 0] = t.Zero; matrixCoef[0, 1] = t.Two; matrixCoef[0, 2] = t.One; matrixCoef[0, 3] = t.Mult(t.Sub(controlPoints[1], controlPoints[0]), 3f); for (int i = 1; i < numCubics; ++i) { matrixCoef[i, 0] = t.One; matrixCoef[i, 1] = t.Four; matrixCoef[i, 2] = t.One; matrixCoef[i, 3] = t.Mult(t.Sub(controlPoints[i + 1], controlPoints[i - 1]), 3f); } matrixCoef[numCubics, 0] = t.One; matrixCoef[numCubics, 1] = t.Two; matrixCoef[numCubics, 2] = t.Zero; matrixCoef[numCubics, 3] = t.Mult(t.Sub(controlPoints[numCubics], controlPoints[numCubics - 1]), 3f); // Modify the coefficients (Thomas algorithm). matrixCoef[0, 2] = t.Div(matrixCoef[0, 2], matrixCoef[0, 1]); matrixCoef[0, 3] = t.Div(matrixCoef[0, 3], matrixCoef[0, 1]); for (int i = 1; i < numCubics; ++i) { T val = t.Sub(matrixCoef[i, 1], t.Mult(matrixCoef[i - 1, 2], matrixCoef[i, 0])); matrixCoef[i, 2] = t.Div(matrixCoef[i, 2], val); matrixCoef[i, 3] = t.Div(t.Sub(matrixCoef[i, 3], t.Mult(matrixCoef[i - 1, 3], matrixCoef[i, 0])), val); } // Back substitute to solve for the derivaties (Thomas algorithm). T[] derivatives = new T[numCubics + 1]; derivatives[numCubics - 1] = matrixCoef[numCubics - 1, 3]; for (int i = numCubics - 2; i >= 0; --i) { derivatives[i] = t.Sub(matrixCoef[i, 3], t.Mult(matrixCoef[i, 2], derivatives[i + 1])); } // Use the derivatives to solve for the coefficients of the cubics. cubics = new Cubic <T> [numCubics]; for (int i = 0; i < numCubics; ++i) { T a = controlPoints[i]; T b = derivatives[i]; T c = t.Sub(t.Sub(t.Mult(t.Sub(controlPoints[i + 1], controlPoints[i]), 3f), t.Mult(derivatives[i], 2f)), derivatives[i + 1]); T d = t.Add(t.Add(t.Mult(t.Sub(controlPoints[i], controlPoints[i + 1]), 2f), derivatives[i]), derivatives[i + 1]); cubics[i] = new Cubic <T>(a, b, c, d); } }