123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312 |
- // David Eberly, Geometric Tools, Redmond WA 98052
- // Copyright (c) 1998-2020
- // Distributed under the Boost Software License, Version 1.0.
- // https://www.boost.org/LICENSE_1_0.txt
- // https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt
- // Version: 4.0.2019.08.13
- #pragma once
- #include <Mathematics/Integration.h>
- #include <Mathematics/RootsBisection.h>
- #include <Mathematics/Vector.h>
- namespace WwiseGTE
- {
- template <int N, typename Real>
- class ParametricCurve
- {
- protected:
- // Abstract base class for a parameterized curve X(t), where t is the
- // parameter in [tmin,tmax] and X is an N-tuple position. The first
- // constructor is for single-segment curves. The second constructor is
- // for multiple-segment curves. The times must be strictly increasing.
- ParametricCurve(Real tmin, Real tmax)
- :
- mTime(2),
- mSegmentLength(1),
- mAccumulatedLength(1),
- mRombergOrder(DEFAULT_ROMBERG_ORDER),
- mMaxBisections(DEFAULT_MAX_BISECTIONS),
- mConstructed(false)
- {
- mTime[0] = tmin;
- mTime[1] = tmax;
- mSegmentLength[0] = (Real)0;
- mAccumulatedLength[0] = (Real)0;
- }
- ParametricCurve(int numSegments, Real const* times)
- :
- mTime(numSegments + 1),
- mSegmentLength(numSegments),
- mAccumulatedLength(numSegments),
- mRombergOrder(DEFAULT_ROMBERG_ORDER),
- mMaxBisections(DEFAULT_MAX_BISECTIONS),
- mConstructed(false)
- {
- std::copy(times, times + numSegments + 1, mTime.begin());
- mSegmentLength[0] = (Real)0;
- mAccumulatedLength[0] = (Real)0;
- }
- public:
- virtual ~ParametricCurve()
- {
- }
- // To validate construction, create an object as shown:
- // DerivedClassCurve<N, Real> curve(parameters);
- // if (!curve) { <constructor failed, handle accordingly>; }
- inline operator bool() const
- {
- return mConstructed;
- }
- // Member access.
- inline Real GetTMin() const
- {
- return mTime.front();
- }
- inline Real GetTMax() const
- {
- return mTime.back();
- }
- inline int GetNumSegments() const
- {
- return static_cast<int>(mSegmentLength.size());
- }
- inline Real const* GetTimes() const
- {
- return &mTime[0];
- }
- // This function applies only when the first constructor is used (two
- // times rather than a sequence of three or more times).
- void SetTimeInterval(Real tmin, Real tmax)
- {
- if (mTime.size() == 2)
- {
- mTime[0] = tmin;
- mTime[1] = tmax;
- }
- }
- // Parameters used in GetLength(...), GetTotalLength() and
- // GetTime(...).
- // The default value is 8.
- inline void SetRombergOrder(int order)
- {
- mRombergOrder = std::max(order, 1);
- }
- // The default value is 1024.
- inline void SetMaxBisections(unsigned int maxBisections)
- {
- mMaxBisections = std::max(maxBisections, 1u);
- }
- // Evaluation of the curve. The function supports derivative
- // calculation through order 3; that is, order <= 3 is required. If
- // you want/ only the position, pass in order of 0. If you want the
- // position and first derivative, pass in order of 1, and so on. The
- // output array 'jet' must have enough storage to support the maximum
- // order. The values are ordered as: position, first derivative,
- // second derivative, third derivative.
- enum { SUP_ORDER = 4 };
- virtual void Evaluate(Real t, unsigned int order, Vector<N, Real>* jet) const = 0;
- void Evaluate(Real t, unsigned int order, Real* values) const
- {
- Evaluate(t, order, reinterpret_cast<Vector<N, Real>*>(values));
- }
- // Differential geometric quantities.
- Vector<N, Real> GetPosition(Real t) const
- {
- Vector<N, Real> position;
- Evaluate(t, 0, &position);
- return position;
- }
- Vector<N, Real> GetTangent(Real t) const
- {
- std::array<Vector<N, Real>, 2> jet; // (position, tangent)
- Evaluate(t, 1, jet.data());
- Normalize(jet[1]);
- return jet[1];
- }
- Real GetSpeed(Real t) const
- {
- std::array<Vector<N, Real>, 2> jet; // (position, tangent)
- Evaluate(t, 1, jet.data());
- return Length(jet[1]);
- }
- Real GetLength(Real t0, Real t1) const
- {
- std::function<Real(Real)> speed = [this](Real t)
- {
- return GetSpeed(t);
- };
- if (mSegmentLength[0] == (Real)0)
- {
- // Lazy initialization of lengths of segments.
- int const numSegments = static_cast<int>(mSegmentLength.size());
- Real accumulated = (Real)0;
- for (int i = 0; i < numSegments; ++i)
- {
- mSegmentLength[i] = Integration<Real>::Romberg(mRombergOrder,
- mTime[i], mTime[i + 1], speed);
- accumulated += mSegmentLength[i];
- mAccumulatedLength[i] = accumulated;
- }
- }
- t0 = std::max(t0, GetTMin());
- t1 = std::min(t1, GetTMax());
- auto iter0 = std::lower_bound(mTime.begin(), mTime.end(), t0);
- int index0 = static_cast<int>(iter0 - mTime.begin());
- auto iter1 = std::lower_bound(mTime.begin(), mTime.end(), t1);
- int index1 = static_cast<int>(iter1 - mTime.begin());
- Real length;
- if (index0 < index1)
- {
- length = (Real)0;
- if (t0 < *iter0)
- {
- length += Integration<Real>::Romberg(mRombergOrder, t0,
- mTime[index0], speed);
- }
- int isup;
- if (t1 < *iter1)
- {
- length += Integration<Real>::Romberg(mRombergOrder,
- mTime[index1 - 1], t1, speed);
- isup = index1 - 1;
- }
- else
- {
- isup = index1;
- }
- for (int i = index0; i < isup; ++i)
- {
- length += mSegmentLength[i];
- }
- }
- else
- {
- length = Integration<Real>::Romberg(mRombergOrder, t0, t1, speed);
- }
- return length;
- }
- Real GetTotalLength() const
- {
- if (mAccumulatedLength.back() == (Real)0)
- {
- // Lazy evaluation of the accumulated length array.
- return GetLength(mTime.front(), mTime.back());
- }
- return mAccumulatedLength.back();
- }
- // Inverse mapping of s = Length(t) given by t = Length^{-1}(s). The
- // inverse length function generally cannot be written in closed form,
- // in which case it is not directly computable. Instead, we can
- // specify s and estimate the root t for F(t) = Length(t) - s. The
- // derivative is F'(t) = Speed(t) >= 0, so F(t) is nondecreasing. To
- // be robust, we use bisection to locate the root, although it is
- // possible to use a hybrid of Newton's method and bisection. For
- // details, see the document
- // https://www.geometrictools.com/Documentation/MovingAlongCurveSpecifiedSpeed.pdf
- Real GetTime(Real length) const
- {
- if (length > (Real)0)
- {
- if (length < GetTotalLength())
- {
- std::function<Real(Real)> F = [this, &length](Real t)
- {
- return Integration<Real>::Romberg(mRombergOrder,
- mTime.front(), t, [this](Real z) { return GetSpeed(z); })
- - length;
- };
- // We know that F(tmin) < 0 and F(tmax) > 0, which allows us to
- // use bisection. Rather than bisect the entire interval, let's
- // narrow it down with a reasonable initial guess.
- Real ratio = length / GetTotalLength();
- Real omratio = (Real)1 - ratio;
- Real tmid = omratio * mTime.front() + ratio * mTime.back();
- Real fmid = F(tmid);
- if (fmid > (Real)0)
- {
- RootsBisection<Real>::Find(F, mTime.front(), tmid, (Real)-1,
- (Real)1, mMaxBisections, tmid);
- }
- else if (fmid < (Real)0)
- {
- RootsBisection<Real>::Find(F, tmid, mTime.back(), (Real)-1,
- (Real)1, mMaxBisections, tmid);
- }
- return tmid;
- }
- else
- {
- return mTime.back();
- }
- }
- else
- {
- return mTime.front();
- }
- }
- // Compute a subset of curve points according to the specified attribute.
- // The input 'numPoints' must be two or larger.
- void SubdivideByTime(int numPoints, Vector<N, Real>* points) const
- {
- Real delta = (mTime.back() - mTime.front()) / (Real)(numPoints - 1);
- for (int i = 0; i < numPoints; ++i)
- {
- Real t = mTime.front() + delta * i;
- points[i] = GetPosition(t);
- }
- }
- void SubdivideByLength(int numPoints, Vector<N, Real>* points) const
- {
- Real delta = GetTotalLength() / (Real)(numPoints - 1);
- for (int i = 0; i < numPoints; ++i)
- {
- Real length = delta * i;
- Real t = GetTime(length);
- points[i] = GetPosition(t);
- }
- }
- protected:
- enum
- {
- DEFAULT_ROMBERG_ORDER = 8,
- DEFAULT_MAX_BISECTIONS = 1024
- };
- std::vector<Real> mTime;
- mutable std::vector<Real> mSegmentLength;
- mutable std::vector<Real> mAccumulatedLength;
- int mRombergOrder;
- unsigned int mMaxBisections;
- bool mConstructed;
- };
- }
|