public override Vector2F GetTangent(float parameter) { int numberOfKeys = Count; if (numberOfKeys == 0) { return(new Vector2F()); } CurveKey2F firstKey = Items[0]; CurveKey2F lastKey = Items[numberOfKeys - 1]; float curveStart = firstKey.Parameter; float curveEnd = lastKey.Parameter; if (PreLoop == CurveLoopType.Constant && parameter < curveStart || PostLoop == CurveLoopType.Constant && parameter > curveEnd) { return(Vector2F.UnitX); } // Correct parameter. float loopedParameter = LoopParameter(parameter); ICurve <float, float> xSpline, ySpline; // Note: Tangents from splines are relative to the spline parameter range [0,1]. We have // to divide by the spline length measured in the curve parameter unit. // Handle CurveLoopType.Linear: // If parameter is outside: Use spline tangent. Exceptions are Bézier and Hermite // were the exact tangent is given in the outer keys. if (loopedParameter < curveStart) { Debug.Assert(PreLoop == CurveLoopType.Linear); Vector2F tangent; switch (firstKey.Interpolation) { case SplineInterpolation.Bezier: tangent = (firstKey.Point - firstKey.TangentIn) * 3; break; case SplineInterpolation.Hermite: tangent = firstKey.TangentIn; break; case SplineInterpolation.StepLeft: case SplineInterpolation.StepCentered: case SplineInterpolation.StepRight: tangent = Vector2F.UnitX; break; default: if (numberOfKeys > 1) { GetSplines(0, out xSpline, out ySpline); tangent = new Vector2F(xSpline.GetTangent(0), ySpline.GetTangent(0)); ((IRecyclable)xSpline).Recycle(); ((IRecyclable)ySpline).Recycle(); } else { tangent = Vector2F.UnitX; } break; } float splineLength = (numberOfKeys > 1) ? Items[1].Parameter - curveStart : 0; return((splineLength > 0) ? tangent / splineLength : tangent); } else if (loopedParameter > curveEnd) { Debug.Assert(PostLoop == CurveLoopType.Linear); Vector2F tangent; switch (lastKey.Interpolation) { case SplineInterpolation.Bezier: tangent = (lastKey.TangentOut - lastKey.Point) * 3; break; case SplineInterpolation.Hermite: tangent = lastKey.TangentOut; break; case SplineInterpolation.StepLeft: case SplineInterpolation.StepCentered: case SplineInterpolation.StepRight: tangent = Vector2F.UnitX; break; default: if (numberOfKeys > 1) { GetSplines(numberOfKeys - 2, out xSpline, out ySpline); tangent = new Vector2F(xSpline.GetTangent(1), ySpline.GetTangent(1)); ((IRecyclable)xSpline).Recycle(); ((IRecyclable)ySpline).Recycle(); } else { tangent = Vector2F.UnitX; } break; } float splineLength = (numberOfKeys > 1) ? curveEnd - Items[numberOfKeys - 2].Parameter : 0; return((splineLength > 0) ? tangent / splineLength : tangent); } else { if (numberOfKeys == 1) { // Degenerate curves with 1 key. // Note: The following tangents are not scaled because we do not have enough information. if (PostLoop == CurveLoopType.Linear) { // Pick outgoing tangent. CurveKey2F p = firstKey; if (p.Interpolation == SplineInterpolation.Bezier) { return((p.TangentOut - p.Point) * 3); } if (p.Interpolation == SplineInterpolation.Hermite) { return(p.TangentOut); } } else if (PreLoop == CurveLoopType.Linear) { // Pick incoming tangent. CurveKey2F p = firstKey; if (p.Interpolation == SplineInterpolation.Bezier) { return((p.Point - p.TangentIn) * 3); } if (p.Interpolation == SplineInterpolation.Hermite) { return(p.TangentIn); } } return(Vector2F.UnitX); } int index = GetKeyIndex(loopedParameter); if (index == numberOfKeys - 1) { index--; // For the last key we take the previous spline. } Debug.Assert(0 <= index && index < numberOfKeys - 1); CurveKey2F p2 = Items[index]; CurveKey2F p3 = Items[index + 1]; // Special case: Step interpolation. if (p2.Interpolation == SplineInterpolation.StepLeft || p2.Interpolation == SplineInterpolation.StepCentered || p2.Interpolation == SplineInterpolation.StepRight) { return(Vector2F.UnitX); } float splineStart = p2.Parameter; float splineEnd = p3.Parameter; float splineLength = splineEnd - splineStart; // Get splines for this segment. GetSplines(index, out xSpline, out ySpline); // Find correct parameter. float relativeParameter = 0; if (!Numeric.IsZero(splineLength)) { if (Items[index].Interpolation == SplineInterpolation.Bezier || Items[index].Interpolation == SplineInterpolation.BSpline || Items[index].Interpolation == SplineInterpolation.CatmullRom || Items[index].Interpolation == SplineInterpolation.Hermite) { // x spline is not linearly proportional. Need root finding. relativeParameter = CurveHelper.GetParameter(xSpline, loopedParameter, 20); if (Numeric.IsNaN(relativeParameter) && Items[index].Interpolation == SplineInterpolation.BSpline) { // Search neighbor splines. BSpline do not normally go exactly from p2 to p3. // Try left neighbor spline first. if (index - 1 >= 0 && Items[index - 1].Interpolation == SplineInterpolation.BSpline) { ((IRecyclable)xSpline).Recycle(); ((IRecyclable)ySpline).Recycle(); GetSplines(index - 1, out xSpline, out ySpline); relativeParameter = CurveHelper.GetParameter(xSpline, loopedParameter, 20); } // If we didn't get a solution search the right neighbor. if (Numeric.IsNaN(relativeParameter) && index + 1 < numberOfKeys - 1 && Items[index + 1].Interpolation == SplineInterpolation.BSpline) { ((IRecyclable)xSpline).Recycle(); ((IRecyclable)ySpline).Recycle(); GetSplines(index + 1, out xSpline, out ySpline); relativeParameter = CurveHelper.GetParameter(xSpline, loopedParameter, 20); } } Debug.Assert( Items[index].Interpolation == SplineInterpolation.BSpline || // BSplines do sometimes not include the boundary points, but for the rest we expect a solution. Numeric.AreEqual( xSpline.GetPoint(relativeParameter), loopedParameter, Math.Max(Math.Abs(splineLength) * Numeric.EpsilonF * 10, Numeric.EpsilonF))); } else { relativeParameter = (loopedParameter - splineStart) / splineLength; } } float tangentX = xSpline.GetTangent(relativeParameter); float tangentY = ySpline.GetTangent(relativeParameter); if (IsInMirroredOscillation(parameter)) { tangentY = -tangentY; // Mirrored direction. } ((IRecyclable)xSpline).Recycle(); ((IRecyclable)ySpline).Recycle(); return(new Vector2F(tangentX, tangentY) / splineLength); } }
public override Vector2F GetPoint(float parameter) { int numberOfKeys = Count; if (numberOfKeys == 0) { return(new Vector2F(float.NaN, float.NaN)); } float interpolatedX = parameter; // The x result must be equal to the parameter. CurveKey2F firstKey = Items[0]; CurveKey2F lastKey = Items[numberOfKeys - 1]; float curveStart = firstKey.Parameter; float curveEnd = lastKey.Parameter; // Correct parameter. float loopedParameter = LoopParameter(parameter); ICurve <float, float> xSpline, ySpline; #region ----- Handle Linear Pre- and PostLoops ----- // If parameter is outside: Use spline tangent. Exceptions are Bézier and Hermite // were the exact tangent is given in the outer keys. if (loopedParameter < curveStart) { Debug.Assert(PreLoop == CurveLoopType.Linear); // Get tangent. Vector2F tangent; switch (firstKey.Interpolation) { case SplineInterpolation.Bezier: tangent = (firstKey.Point - firstKey.TangentIn) * 3; break; case SplineInterpolation.Hermite: tangent = firstKey.TangentIn; break; default: { if (numberOfKeys == 1) { return(new Vector2F(parameter, firstKey.Point.Y)); } GetSplines(0, out xSpline, out ySpline); tangent = new Vector2F(xSpline.GetTangent(0), ySpline.GetTangent(0)); ((IRecyclable)xSpline).Recycle(); ((IRecyclable)ySpline).Recycle(); break; } } float k = 0; if (Numeric.IsZero(tangent.X) == false) { k = tangent.Y / tangent.X; } return(new Vector2F(interpolatedX, firstKey.Point.Y + k * (loopedParameter - curveStart))); } else if (loopedParameter > curveEnd) { Debug.Assert(PostLoop == CurveLoopType.Linear); // Get tangent. Vector2F tangent; switch (lastKey.Interpolation) { case SplineInterpolation.Bezier: tangent = (lastKey.TangentOut - lastKey.Point) * 3; break; case SplineInterpolation.Hermite: tangent = lastKey.TangentOut; break; default: { if (numberOfKeys == 1) { return(new Vector2F(parameter, firstKey.Point.Y)); } GetSplines(numberOfKeys - 2, out xSpline, out ySpline); tangent = new Vector2F(xSpline.GetTangent(1), ySpline.GetTangent(1)); ((IRecyclable)xSpline).Recycle(); ((IRecyclable)ySpline).Recycle(); break; } } float k = 0; if (Numeric.IsZero(tangent.X) == false) { k = tangent.Y / tangent.X; } return(new Vector2F(interpolatedX, lastKey.Point.Y + k * (loopedParameter - curveEnd))); } #endregion // Special case: Only 1 point. if (numberOfKeys == 1) { return(new Vector2F(parameter, firstKey.Point.Y)); } float cycleOffset = GetCycleOffset(parameter); // Special case: Parameter = parameter of last key. if (loopedParameter == lastKey.Parameter && Items[numberOfKeys - 2].Interpolation != SplineInterpolation.BSpline) // BSplines need special handling because they do not go through key points. { return(new Vector2F(parameter, lastKey.Point.Y + cycleOffset)); } // Get near keys. int index = GetKeyIndex(loopedParameter); // If the looped parameter == parameter of last key, we want to use the previous key. if (index == numberOfKeys - 1) { index--; } Debug.Assert(0 <= index && index < numberOfKeys - 1); CurveKey2F p2 = Items[index]; CurveKey2F p3 = Items[index + 1]; float splineStart = p2.Parameter; float splineEnd = p3.Parameter; float splineLength = (splineEnd - splineStart); // Get splines for this segment. GetSplines(index, out xSpline, out ySpline); // Find correct parameter. float relativeParameter = 0; if (!Numeric.IsZero(splineLength)) { if (Items[index].Interpolation == SplineInterpolation.Bezier || Items[index].Interpolation == SplineInterpolation.BSpline || Items[index].Interpolation == SplineInterpolation.CatmullRom || Items[index].Interpolation == SplineInterpolation.Hermite) { // x spline is not linearly proportional. Need root finding. relativeParameter = CurveHelper.GetParameter(xSpline, loopedParameter, 20); if (Numeric.IsNaN(relativeParameter) && Items[index].Interpolation == SplineInterpolation.BSpline) { // Search neighbor splines. BSpline do not normally go exactly from p2 to p3. // Try left neighbor spline first. if (index - 1 >= 0 && Items[index - 1].Interpolation == SplineInterpolation.BSpline) { ((IRecyclable)xSpline).Recycle(); ((IRecyclable)ySpline).Recycle(); GetSplines(index - 1, out xSpline, out ySpline); relativeParameter = CurveHelper.GetParameter(xSpline, loopedParameter, 20); } // If we didn't get a solution search the right neighbor. if (Numeric.IsNaN(relativeParameter) && index + 1 < numberOfKeys - 1 && Items[index + 1].Interpolation == SplineInterpolation.BSpline) { ((IRecyclable)xSpline).Recycle(); ((IRecyclable)ySpline).Recycle(); GetSplines(index + 1, out xSpline, out ySpline); relativeParameter = CurveHelper.GetParameter(xSpline, loopedParameter, 20); } } Debug.Assert( Items[index].Interpolation == SplineInterpolation.BSpline || // BSplines do sometimes not include the boundary points, but for the rest we expect a solution. Numeric.AreEqual(xSpline.GetPoint(relativeParameter), loopedParameter, Math.Max(Math.Abs(splineLength) * Numeric.EpsilonF * 10, Numeric.EpsilonF))); } else { relativeParameter = (loopedParameter - splineStart) / splineLength; } } // Find y value for parameter. float interpolatedY = ySpline.GetPoint(relativeParameter); ((IRecyclable)xSpline).Recycle(); ((IRecyclable)ySpline).Recycle(); return(new Vector2F(interpolatedX, interpolatedY + cycleOffset)); }
/// <summary> /// Gets the curve parameter for the given curve length (for length-parameterized splines). /// </summary> /// <param name="length">The length.</param> /// <param name="maxNumberOfIterations"> /// The maximum number of iterations which are taken to compute the length. /// </param> /// <param name="tolerance"> /// The tolerance value. This method will return an approximation of the precise parameter. The /// absolute error will be less than this tolerance. /// </param> /// <returns>The parameter at which the curve has the given length.</returns> /// <exception cref="ArgumentOutOfRangeException"> /// <paramref name="tolerance"/> is negative or 0. /// </exception> /// <remarks> /// <para> /// This method assumes that the spline is length-parameterized; This means: The parameter of a /// key is equal to the length of the spline at this key. If this is not the case, /// <see cref="ParameterizeByLength"/> can be called to correct the key parameters /// automatically. /// </para> /// <para> /// Normally, the spline curve parameter is not linearly proportional to the length. Therefore, /// if the point at a given curve length is required, this method can be used to compute the /// curve parameter which will return the point for the given distance. The result of this /// method can be used in <see cref="GetPoint"/>. /// </para> /// <para> /// This method uses an iterative algorithm. The iterations end when the /// <paramref name="maxNumberOfIterations"/> were performed, or when the /// <paramref name="tolerance"/> criterion is met - whichever comes first. /// </para> /// </remarks> public float GetParameterFromLength(float length, int maxNumberOfIterations, float tolerance) { if (tolerance <= 0) { throw new ArgumentOutOfRangeException("tolerance", "The tolerance must be greater than zero."); } int numberOfKeys = Count; if (numberOfKeys == 0) { return(float.NaN); } // Handle CurveLoopType.Linear: float curveStart = Items[0].Parameter; float curveEnd = Items[numberOfKeys - 1].Parameter; float curveLength = curveEnd - curveStart; if (length < curveStart && PreLoop == CurveLoopType.Linear) { var tangent = GetTangent(curveStart); return(curveStart - (curveStart - length) / tangent.Length); } if (length > curveEnd && PostLoop == CurveLoopType.Linear) { var tangent = GetTangent(curveEnd); return(curveEnd + (length - curveEnd) / tangent.Length); } if (Numeric.IsZero(curveLength)) { return(curveStart); } // Correct parameter. float loopedLength = LoopParameter(length); // Find key index for the parameter. int index = GetKeyIndex(loopedLength); if (index == numberOfKeys - 1) { index--; // For the last key we take the previous spline. } // Get spline. var spline = GetSpline(index); // Get relative length on the segment from length on whole curve. float splineStart = Items[index].Parameter; float splineEnd = Items[index + 1].Parameter; float splineLength = (splineEnd - splineStart); loopedLength = loopedLength - splineStart; Debug.Assert(0 <= loopedLength && loopedLength <= splineLength); float result = CurveHelper.GetParameter(spline, loopedLength, splineLength, maxNumberOfIterations, tolerance); float localParameter = Items[index].Parameter + result * splineLength; ((IRecyclable)spline).Recycle(); spline = null; // Handle looping: We return a parameter that is outside if the original length parameter is outside. // Otherwise the returned parameter would correspond to the correct parameter on the path, but // the total distance from the first key would wrong because some "loops" are missing. if (length < curveStart && PreLoop != CurveLoopType.Constant) { int numberOfPeriods = (int)((length - curveEnd) / curveLength); if (PreLoop == CurveLoopType.Oscillate && numberOfPeriods % 2 == -1) { return(curveStart + curveStart - localParameter + curveLength * (numberOfPeriods + 1)); // odd = mirrored } else { return(localParameter + numberOfPeriods * curveLength); } } else if (length > curveEnd && PostLoop != CurveLoopType.Constant) { int numberOfPeriods = (int)((length - curveStart) / curveLength); if (PostLoop == CurveLoopType.Oscillate && numberOfPeriods % 2 == 1) { return(curveEnd + curveEnd - localParameter + curveLength * (numberOfPeriods - 1)); // odd = mirrored } else { return(localParameter + numberOfPeriods * curveLength); } } else { return(localParameter); } }