Exemplo n.º 1
0
        /// <inheritdoc/>
        public override float GetLength(float start, float end, int maxNumberOfIterations, float tolerance)
        {
            int numberOfKeys = Count;

            if (numberOfKeys == 0)
            {
                return(0);
            }
            if (numberOfKeys == 1 && PreLoop != CurveLoopType.Linear && PostLoop != CurveLoopType.Linear)
            {
                return(0);
            }

            // TODO: Maybe a piecewise computation would be faster when the curve has non-continuous parts.
            // Difficulty: start and end are not on spline ends and can be outside.
            // For non-continuous curves the result can have an error larger than tolerance.
            return(CurveHelper.GetLength(this, start, end, 5, maxNumberOfIterations, tolerance));
        }
Exemplo n.º 2
0
 /// <inheritdoc/>
 public void Flatten(ICollection <Vector2F> points, int maxNumberOfIterations, float tolerance)
 {
     CurveHelper.Flatten(this, points, maxNumberOfIterations, tolerance);
 }
Exemplo n.º 3
0
 /// <inheritdoc/>
 public float GetLength(float start, float end, int maxNumberOfIterations, float tolerance)
 {
     return(CurveHelper.GetLength(this, start, end, 2, maxNumberOfIterations, tolerance));
 }
Exemplo n.º 4
0
        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));
        }
Exemplo n.º 5
0
        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);
            }
        }
Exemplo n.º 6
0
        /// <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);
            }
        }