#pragma region CPL License /* Nuclex Native Framework Copyright (C) 2002-2015 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #pragma endregion // CPL License #ifndef NUCLEX_GEOMETRY_LINES_INTERPOLATORS_CUBICINTERPOLATOR_H #define NUCLEX_GEOMETRY_LINES_INTERPOLATORS_CUBICINTERPOLATOR_H #include "Nuclex/Geometry/Config.h" #include "Nuclex/Geometry/Point2.h" #include namespace Nuclex { namespace Geometry { namespace Lines { namespace Interpolators { // ------------------------------------------------------------------------------------------- // #if 0 /// Example implementation of the TVertexTraits type struct ExampleVertexTraits { /// Type that is used to measure distance between vertices public: typedef float TDistance; /// Type that is used to specify the interpolation interval public: typedef float TScalar; /// A vertex with a value of zero public: static const float ZeroVertex = 0.0f; /// Measures the distance between two vertices /// First vertex for the distance calculation /// Second vertex for the distance calculation /// The distance between the two vertices public: static TDistance DistanceBetween(float first, float second) { return std::abs(second - first), } /// Rounds a scalar / interval value to the closest integer /// Scalar / interval value that will be rounded /// The closest integer to the specified scalar / interval value public: static std::size_t RoundScalarToInteger(float t) { return static_cast(t + 0.5f); } /// Truncates a scalar / interval value to the next lower integer /// Scalar / interval value that will be truncated /// The next lower integer to the specified scalar / interval value public: static std::size_t TruncateScalarToInteger(float t) { return static_cast(t); } /// Sums a vertex value with another vertex /// Vertex to which another vertex wlll be added /// Other vertex that will be added to the vertex public: static void Add(float &base, float amount) { base += amount; } /// Subtracts another vertex from a vertex /// Point from which another vertex wlll be subtracted /// Other vertex that will be substracted from the vertex public: static void Subtract(float &base, float amount) { base -= amount; } /// Scales a vertex by a factor /// Vertex that will be scaled by a factor /// Factor the Vertex will be scaled by public: static void Scale(float &base, float factor) { base *= factor; } }; #endif // ------------------------------------------------------------------------------------------- // /// Interpolator that generates a smooth path along vertices /// Type of the interpolated values /// Additional information about the vertex type template struct CubicInterpolator { /// /// Performs cubic interpolation between the vertices at the specified time index /// /// Type of the iterator used to find vertices /// Iterator pointing to the first vertex /// Iterator pointing one past the last vertex /// Interpolation time index, equivalent to vertex index /// The cubic-interpolated position at the specified time index public: template static TVertex InterpolateByTime( TVertexIterator first, TVertexIterator onePastLast, typename TVertexTraits::TScalar t ) { // Empty vertex list? Return default value if(onePastLast == first) { return TVertexTraits::ZeroVertex; } // Asking for a time before the first vertex? Clamp to first vertex if(t < 0) { return *first; } // Determine the control points. If the interpolation point touches // the beginning or end, duplicate the outermost points to ease in + out TVertex controls[4]; { // Truncate to obtain left vertex and right vertex index std::size_t leftVertexIndex = TVertexTraits::TruncateScalarToInteger(t); std::size_t rightVertexIndex = leftVertexIndex + 1; // Obtain the final two vertices (or clamp if right vertex is past the end) std::size_t count = onePastLast - first; if(rightVertexIndex > count - 1) { return *(onePastLast - 1); } else if(rightVertexIndex < count - 1) { controls[2] = *(first + rightVertexIndex); controls[3] = *(first + (rightVertexIndex + 1)); } else { controls[2] = *(first + rightVertexIndex); controls[3] = controls[2]; } // Obtain the first two vertices if(leftVertexIndex == 0) { controls[0] = *first; controls[1] = controls[0]; } else { controls[0] = *(first + (leftVertexIndex - 1)); controls[1] = *(first + leftVertexIndex); } t -= leftVertexIndex; } // At this point we have 4 neighboring vertices we can interpolate between return interpolateCubic(controls, t); } /// /// Performs cubic interpolation between the vertices at the specified distance /// /// Type of the iterator used to find vertices /// Iterator pointing to the first vertex /// Iterator pointing one past the last vertex /// Distance along the path to interpolate /// The cubic-interpolated position at the specified distance public: template static TVertex InterpolateByDistance( TVertexIterator first, TVertexIterator onePastLast, typename TVertexTraits::TDistance distance ) { // Empty vertex list? Return default value if(onePastLast == first) { return TVertexTraits::ZeroVertex; } // If the distance is zero or less, return the starting point if(distance < 0) { return *first; } // Determine the control points. If the interpolation point touches // the beginning or end, duplicate the outermost points to ease in + out TVertex controls[4]; { // Initialize beginning with duplicated start point (for ease in + out) controls[0] = *first; // If there is only one point, it is the clamped interpolation result ++first; if(first == onePastLast) { return controls[0]; } else { // Two points or more, perform cubic interpolation controls[1] = controls[0]; controls[2] = *first; ++first; while (first != onePastLast) { controls[3] = *first; typename TVertexTraits::TDistance segmentLength = measure(controls); if(distance < segmentLength) { return interpolateCubic(controls, distance / segmentLength); } distance -= segmentLength; // Shift all control points one to the left so we can append the new one controls[0] = controls[1]; controls[1] = controls[2]; controls[2] = controls[3]; ++first; } } // See if the distance is within the final segment with the last point duplicated // (for ease in + out) controls[3] = controls[2]; typename TVertexTraits::TDistance segmentLength = measure(controls); if(distance < segmentLength) { return interpolateCubic(controls, distance / segmentLength); } } // Specified distance was beyond the end of the path, return the lastmost // vertex we processed (thereby clamping to the end of the path) return controls[3]; } /// Estimates the length of a path when cubic-interpolated /// Type of the iterator used to find vertices /// Iterator pointing to the first vertex /// Iterator pointing one past the last vertex /// The length of the path public: template static typename TVertexTraits::TDistance EstimateLength( TVertexIterator first, TVertexIterator onePastLast ) { // Empty vertex list? The path has a length of 0 if(onePastLast == first) { return 0; } // Perform the measurement by summing the lengths of the individual path segments typename TVertexTraits::TDistance distance = 0; { TVertex controls[4]; // Initialize beginning with duplicated start point (for ease in + out) controls[0] = *first; // If there is only one point, the length is 0 ++first; if(first == onePastLast) { return 0; } else { // Two points or more, measure intermediate segments controls[1] = controls[0]; controls[2] = *first; ++first; while(first != onePastLast) { controls[3] = *first; distance += measure(controls); // Shift all control points one to the left so we can append the new one controls[0] = controls[1]; controls[1] = controls[2]; controls[2] = controls[3]; ++first; } } // Measure the final segment with the last point duplicated (for ease in + out) controls[3] = controls[2]; distance += measure(controls); } return distance; } /// Measures the length of the specified curve /// Control points for the interpolaton curve /// The approximate length of the curve /// All contents of the array will be destroyed private: static typename TVertexTraits::TDistance measure(TVertex controls[4]) { const std::size_t Resolution = 100; // Resolution per iterative measurement typename TVertexTraits::TDistance distance = 0; // Sample the curve in small increments to obtain an approximation of // its overall length. TVertex previous = controls[1]; for(std::size_t index = 1; index <= Resolution; ++index) { typename TVertexTraits::TScalar t = ( static_cast(index) / static_cast(Resolution) ); TVertex temp[4] = { controls[0], controls[1], controls[2], controls[3] }; TVertex current = interpolateCubic(temp, t); distance += TVertexTraits::DistanceBetween(previous, current); previous = current; } return distance; } /// Performs cubic interpolation over the specified vertices /// Control points for the interpolaton curve /// Interpolation time between the second and third vertex /// The interpolation vertex /// All contents of the array will be destroyed private: static TVertex interpolateCubic( TVertex controls[4], typename TVertexTraits::TScalar t ) { // Formula: // // TVertex a0 = c3 - c2 - c0 + c1; // TVertex a1 = c0 - c1 - a0; // TVertex a2 = c2 - c0; // TVertex a3 = c1; // // TScalar t2 = t * t; // return (a0*t*t2 + a1*t2 + a2*t + a3); // // Calculate the intermediate terms (in-place) // // 3 - 2 - 0 + 1 // 2 - 0 // 0 - 1 - (3) // 1 { TVertexTraits::Subtract(controls[3], controls[2]); TVertexTraits::Subtract(controls[3], controls[0]); TVertexTraits::Add(controls[3], controls[1]); TVertexTraits::Subtract(controls[2], controls[0]); TVertexTraits::Subtract(controls[0], controls[1]); TVertexTraits::Subtract(controls[0], controls[3]); } // Scale and sum them as demanded by the formula { typename TVertexTraits::TScalar t2 = t * t; TVertexTraits::Scale(controls[2], t); // Scale by T TVertexTraits::Scale(controls[0], t2); // Scale by T*T TVertexTraits::Scale(controls[3], t2 * t); // Scale by T*T*T TVertexTraits::Add(controls[0], controls[1]); TVertexTraits::Add(controls[0], controls[2]); TVertexTraits::Add(controls[0], controls[3]); } return controls[0]; } }; // ------------------------------------------------------------------------------------------- // }}}} // namespace Nuclex::Geometry::Lines::Interpolators #endif // NUCLEX_GEOMETRY_LINES_INTERPOLATORS_CUBICINTERPOLATOR_H