/// <summary> /// Converts length parameter [0..TotalLength] to time parameter [0..1] /// </summary> /// <param name="length">Distance parameter to convert into time parameter</param> /// <param name="iterations">Number of iterations used in internal calculations, default is 32</param> /// <param name="tolerance">Small positive number, e.g. 1e-5f</param> public float LengthToTime(float length, int iterations, float tolerance) { // Update length information if necessary if (_data.Count < 2) { return(0f); } RecalcSegmentsLength(); int segmentCount = SegmentCount; int lastSegment = segmentCount - 1; // Check out of bounds length if (length <= 0f) { return(0f); } if (length >= _data[lastSegment].AccumulatedLength) { return(1f); } //TODO: use binary search int segmentIndex; for (segmentIndex = 0; segmentIndex < segmentCount; ++segmentIndex) { if (length < _data[segmentIndex].AccumulatedLength) { break; } } ItemData segment = _data[segmentIndex]; float segmentStartTime = (float)segmentIndex; // Uniform parametrization with unit length time per segment float len0 = segmentIndex == 0 ? length : (length - _data[segmentIndex - 1].AccumulatedLength); float len1 = _data[segmentIndex].Length; // If L(t) is the length function for t in [tmin,tmax], the derivative is // L'(t) = |x'(t)| >= 0 (the magnitude of speed). Therefore, L(t) is a // nondecreasing function (and it is assumed that x'(t) is zero only at // isolated points; that is, no degenerate curves allowed). The second // derivative is L"(t). If L"(t) >= 0 for all t, L(t) is a convex // function and Newton's method for root finding is guaranteed to // converge. However, L"(t) can be negative, which can lead to Newton // iterates outside the domain [tmin,tmax]. The algorithm here avoids // this problem by using a hybrid of Newton's method and bisection. // Initial guess for Newton's method is dt0. float dt1 = 1f; float dt0 = dt1 * len0 / len1; // Initial root-bounding interval for bisection. float lower = 0f; float upper = dt1; for (int i = 0; i < iterations; ++i) { float difference = segment.EvalLength(0, dt0) - len0; if (UnityEngine.Mathf.Abs(difference) <= tolerance) { // |L(mTimes[key]+dt0)-length| is close enough to zero, report // mTimes[key]+dt0 as the time at which 'length' is attained. return((segmentStartTime + dt0) / segmentCount); } // Generate a candidate for Newton's method. float dt0Candidate = dt0 - difference / segment.EvalSpeed(dt0); // Update the root-bounding interval and test for containment of the candidate. if (difference > 0f) { upper = dt0; if (dt0Candidate <= lower) { // Candidate is outside the root-bounding interval. Use bisection instead. dt0 = 0.5f * (upper + lower); } else { // There is no need to compare to 'upper' because the tangent line has // positive slope, guaranteeing that the t-axis intercept is smaller than 'upper'. dt0 = dt0Candidate; } } else { lower = dt0; if (dt0Candidate >= upper) { // Candidate is outside the root-bounding interval. Use bisection instead. dt0 = 0.5f * (upper + lower); } else { // There is no need to compare to 'lower' because the tangent line has // positive slope, guaranteeing that the t-axis intercept is larger than 'lower'. dt0 = dt0Candidate; } } } // A root was not found according to the specified number of iterations // and tolerance. You might want to increase iterations or tolerance or // integration accuracy. However, in this application it is likely that // the time values are oscillating, due to the limited numerical // precision of 32-bit floats. It is safe to use the last computed time. return((segmentStartTime + dt0) / segmentCount); }