Browse Source

Math: implement cubic Bezier <-> cubic Hermite conversion.

pull/267/head
Vladimír Vondruš 8 years ago
parent
commit
6c8a2a46b8
  1. 4
      doc/changelog.dox
  2. 14
      doc/snippets/MagnumMath.cpp
  3. 39
      src/Magnum/Math/Bezier.h
  4. 43
      src/Magnum/Math/CubicHermite.h
  5. 15
      src/Magnum/Math/Test/BezierTest.cpp
  6. 41
      src/Magnum/Math/Test/CubicHermiteTest.cpp

4
doc/changelog.dox

@ -47,7 +47,9 @@ See also:
@subsubsection changelog-latest-new-math Math library @subsubsection changelog-latest-new-math Math library
- New @ref Math::CubicHermite class for cubic Hermite spline interpolation - New @ref Math::CubicHermite class for cubic Hermite spline interpolation,
convertible to and from cubic Bézier curves using
@ref Math::Bezier::fromCubicHermite() and @ref Math::CubicHermite::fromBezier()
- Added @ref Math::Intersection::rangeFrustum(), - Added @ref Math::Intersection::rangeFrustum(),
@ref Math::Intersection::aabbFrustum(), @ref Math::Intersection::aabbFrustum(),
@ref Math::Intersection::sphereFrustum(), @ref Math::Intersection::sphereFrustum(),

14
doc/snippets/MagnumMath.cpp

@ -25,6 +25,8 @@
#include "Magnum/Magnum.h" #include "Magnum/Magnum.h"
#include "Magnum/Math/Color.h" #include "Magnum/Math/Color.h"
#include "Magnum/Math/Bezier.h"
#include "Magnum/Math/CubicHermite.h"
#include "Magnum/Math/DualComplex.h" #include "Magnum/Math/DualComplex.h"
#include "Magnum/Math/DualQuaternion.h" #include "Magnum/Math/DualQuaternion.h"
#include "Magnum/Math/Half.h" #include "Magnum/Math/Half.h"
@ -839,6 +841,18 @@ Color4 a = 0x33b27fcc_srgbaf; // {0.0331048f, 0.445201f, 0.212231f, 0.8f}
static_cast<void>(a); static_cast<void>(a);
} }
{
/* [CubicHermite-fromBezier] */
CubicBezier2D segment;
auto startPoint = CubicHermite2D::fromBezier(
{Vector2{}, Vector2{}, Vector2{}, segment[3]}, segment);
auto endPoint = CubicHermite2D::fromBezier(segment,
{segment[0], Vector2{}, Vector2{}, Vector2{}});
/* [CubicHermite-fromBezier] */
static_cast<void>(startPoint);
static_cast<void>(endPoint);
}
{ {
/* [Half-usage] */ /* [Half-usage] */
using namespace Math::Literals; using namespace Math::Literals;

39
src/Magnum/Math/Bezier.h

@ -46,8 +46,12 @@ namespace Implementation {
@tparam dimensions Dimensions of control points @tparam dimensions Dimensions of control points
@tparam T Underlying data type @tparam T Underlying data type
Implementation of M-order N-dimensional Represents a M-order N-dimensional
[Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve). [Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve) segment.
Cubic Bézier curves are fully interchangeable with cubic Hermite splines, use
@ref fromCubicHermite() and @ref CubicHermite::fromBezier() for the conversion.
@see @ref QuadraticBezier, @ref CubicBezier, @ref QuadraticBezier2D, @see @ref QuadraticBezier, @ref CubicBezier, @ref QuadraticBezier2D,
@ref QuadraticBezier3D, @ref CubicBezier2D, @ref CubicBezier3D @ref QuadraticBezier3D, @ref CubicBezier2D, @ref CubicBezier3D
*/ */
@ -64,6 +68,37 @@ template<UnsignedInt order, UnsignedInt dimensions, class T> class Bezier {
Dimensions = dimensions /**< Dimensions of control points */ Dimensions = dimensions /**< Dimensions of control points */
}; };
/**
* @brief Create cubic Hermite spline point from adjacent Bézier curve segments
*
* Given two cubic Hermite spline points defined by points
* @f$ \boldsymbol{p}_i @f$, in-tangents @f$ \boldsymbol{m}_i @f$ and
* out-tangents @f$ \boldsymbol{n}_i @f$, the corresponding cubic
* Bezier curve segment with points @f$ \boldsymbol{c}_0 @f$,
* @f$ \boldsymbol{c}_1 @f$, @f$ \boldsymbol{c}_2 @f$ and
* @f$ \boldsymbol{c}_3 @f$ is defined as: @f[
* \begin{array}{rcl}
* \boldsymbol{c}_0 & = & \boldsymbol{p}_a \\
* \boldsymbol{c}_1 & = & \frac{1}{3} \boldsymbol{n}_a - \boldsymbol{p}_a \\
* \boldsymbol{c}_2 & = & \boldsymbol{p}_b - \frac{1}{3} \boldsymbol{m}_b \\
* \boldsymbol{c}_3 & = & \boldsymbol{p}_b
* \end{array}
* @f]
*
* Enabled only on @ref CubicBezier for @ref CubicHermite with vector
* underlying types. See @ref CubicHermite::fromBezier() for the
* inverse operation.
*/
template<class VectorType> static
#ifndef DOXYGEN_GENERATING_OUTPUT
typename std::enable_if<std::is_base_of<Vector<dimensions, T>, VectorType>::value && order == 3, Bezier<order, dimensions, T>>::type
#else
Bezier<order, dimensions, T>
#endif
fromCubicHermite(const CubicHermite<VectorType>& a, const CubicHermite<VectorType>& b) {
return {a.point(), a.outTangent()/T(3) - a.point(), b.point() - b.inTangent()/T(3), b.point()};
}
/** /**
* @brief Default constructor * @brief Default constructor
* *

43
src/Magnum/Math/CubicHermite.h

@ -46,6 +46,9 @@ animation keyframe representation. The structure assumes the in/out tangents
to be in their final form, i.e. already normalized by length of their adjacent to be in their final form, i.e. already normalized by length of their adjacent
segments. segments.
Cubic Hermite splines are fully interchangeable with cubic Bézier curves, use
@ref fromBezier() and @ref Bezier::fromCubicHermite() for the conversion.
@see @ref CubicHermite2D, @ref CubicHermite3D, @see @ref CubicHermite2D, @ref CubicHermite3D,
@ref Magnum::CubicHermite2D, @ref Magnum::CubicHermite2Dd, @ref Magnum::CubicHermite2D, @ref Magnum::CubicHermite2Dd,
@ref Magnum::CubicHermite3D, @ref Magnum::CubicHermite3Dd, @ref Magnum::CubicHermite3D, @ref Magnum::CubicHermite3Dd,
@ -56,6 +59,46 @@ template<class T> class CubicHermite {
public: public:
typedef T Type; /**< @brief Underlying data type */ typedef T Type; /**< @brief Underlying data type */
/**
* @brief Create cubic Hermite spline point from adjacent Bézier curve segments
*
* Given two adjacent cubic Bézier curve segments defined by points
* @f$ \boldsymbol{a}_i @f$ and @f$ \boldsymbol{b}_i @f$,
* @f$ i \in \{ 0, 1, 2, 3 \} @f$, the corresponding cubic Hermite
* spline point @f$ \boldsymbol{p} @f$, in-tangent @f$ \boldsymbol{m} @f$
* and out-tangent @f$ \boldsymbol{n} @f$ is defined as: @f[
* \begin{array}{rcl}
* \boldsymbol{m} & = & 3 (\boldsymbol{a}_3 - \boldsymbol{a}_2)
* = 3 (\boldsymbol{b}_0 - \boldsymbol{a}_2) \\
* \boldsymbol{p} & = & \boldsymbol{a}_3 = \boldsymbol{b}_0 \\
* \boldsymbol{n} & = & 3 (\boldsymbol{b}_1 - \boldsymbol{a}_3)
* = 3 (\boldsymbol{b}_1 - \boldsymbol{b}_0)
* \end{array}
* @f]
*
* Expects that the two segments are adjacent (i.e., the endpoint of
* first segment is the start point of the second). If you need to
* create a cubic Hermite spline point that's at the beginning or at
* the end of a curve, simply pass a dummy Bézier segment that
* satisfies this constraint as the other parameter:
*
* @snippet MagnumMath.cpp CubicHermite-fromBezier
*
* Enabled only on vector underlying types. See
* @ref Bezier::fromCubicHermite() for the inverse operation.
*/
template<UnsignedInt dimensions, class U> static
#ifdef DOXYGEN_GENERATING_OUTPUT
CubicHermite<T>
#else
typename std::enable_if<std::is_base_of<Vector<dimensions, U>, T>::value, CubicHermite<T>>::type
#endif
fromBezier(const CubicBezier<dimensions, U>& a, const CubicBezier<dimensions, U>& b) {
return CORRADE_CONSTEXPR_ASSERT(a[3] == b[0],
"Math::CubicHermite::fromBezier(): segments are not adjacent"),
CubicHermite<T>{3*(a[3] - a[2]), a[3], 3*(b[1] - a[3])};
}
/** /**
* @brief Default constructor * @brief Default constructor
* *

15
src/Magnum/Math/Test/BezierTest.cpp

@ -29,6 +29,7 @@
#include <Corrade/Utility/Configuration.h> #include <Corrade/Utility/Configuration.h>
#include "Magnum/Math/Bezier.h" #include "Magnum/Math/Bezier.h"
#include "Magnum/Math/CubicHermite.h"
#include "Magnum/Math/Vector2.h" #include "Magnum/Math/Vector2.h"
#include "Magnum/Math/Functions.h" #include "Magnum/Math/Functions.h"
@ -60,6 +61,7 @@ typedef Math::Bezier<1, 2, Float> LinearBezier2D;
typedef Math::QuadraticBezier2D<Float> QuadraticBezier2D; typedef Math::QuadraticBezier2D<Float> QuadraticBezier2D;
typedef Math::QuadraticBezier2D<Double> QuadraticBezier2Dd; typedef Math::QuadraticBezier2D<Double> QuadraticBezier2Dd;
typedef Math::CubicBezier2D<Float> CubicBezier2D; typedef Math::CubicBezier2D<Float> CubicBezier2D;
typedef Math::CubicHermite2D<Float> CubicHermite2D;
struct BezierTest: Corrade::TestSuite::Tester { struct BezierTest: Corrade::TestSuite::Tester {
explicit BezierTest(); explicit BezierTest();
@ -68,6 +70,7 @@ struct BezierTest: Corrade::TestSuite::Tester {
void constructDefault(); void constructDefault();
void constructNoInit(); void constructNoInit();
void constructConversion(); void constructConversion();
void constructFromCubicHermite();
void constructCopy(); void constructCopy();
void convert(); void convert();
@ -91,6 +94,7 @@ BezierTest::BezierTest() {
&BezierTest::constructDefault, &BezierTest::constructDefault,
&BezierTest::constructNoInit, &BezierTest::constructNoInit,
&BezierTest::constructConversion, &BezierTest::constructConversion,
&BezierTest::constructFromCubicHermite,
&BezierTest::constructCopy, &BezierTest::constructCopy,
&BezierTest::convert, &BezierTest::convert,
@ -160,6 +164,16 @@ void BezierTest::constructConversion() {
CORRADE_VERIFY((std::is_nothrow_constructible<QuadraticBezier2D, QuadraticBezier2Dd>::value)); CORRADE_VERIFY((std::is_nothrow_constructible<QuadraticBezier2D, QuadraticBezier2Dd>::value));
} }
void BezierTest::constructFromCubicHermite() {
/* See CubicHermiterTest::constructFromBezier() for the inverse. Expected
value the same as in valueCubic() to test also interpolation with it. */
CubicHermite2D a{{}, Vector2{0.0f, 0.0f}, Vector2{30.0f, 45.0f}};
CubicHermite2D b{Vector2{-45, -72}, Vector2{5.0f, -20.0f}, {}};
auto bezier = CubicBezier2D::fromCubicHermite(a, b);
CORRADE_COMPARE(bezier, (CubicBezier2D{Vector2{0.0f, 0.0f}, Vector2{10.0f, 15.0f}, Vector2{20.0f, 4.0f}, Vector2{5.0f, -20.0f}}));
}
void BezierTest::constructCopy() { void BezierTest::constructCopy() {
constexpr QuadraticBezier2D a{Vector2{0.5f, 1.0f}, Vector2{1.1f, 0.3f}, Vector2{0.1f, 1.2f}}; constexpr QuadraticBezier2D a{Vector2{0.5f, 1.0f}, Vector2{1.1f, 0.3f}, Vector2{0.1f, 1.2f}};
constexpr QuadraticBezier2D b{a}; constexpr QuadraticBezier2D b{a};
@ -237,6 +251,7 @@ void BezierTest::valueQuadratic() {
void BezierTest::valueCubic() { void BezierTest::valueCubic() {
CubicBezier2D bezier{Vector2{0.0f, 0.0f}, Vector2{10.0f, 15.0f}, Vector2{20.0f, 4.0f}, Vector2{5.0f, -20.0f}}; CubicBezier2D bezier{Vector2{0.0f, 0.0f}, Vector2{10.0f, 15.0f}, Vector2{20.0f, 4.0f}, Vector2{5.0f, -20.0f}};
/* Values should be exactly the same as in CubicHermiterTest::splerpVectorFromBezier() */
CORRADE_COMPARE(bezier.value(0.0f), (Vector2{0.0f, 0.0f})); CORRADE_COMPARE(bezier.value(0.0f), (Vector2{0.0f, 0.0f}));
CORRADE_COMPARE(bezier.value(0.2f), (Vector2{5.8f, 5.984f})); CORRADE_COMPARE(bezier.value(0.2f), (Vector2{5.8f, 5.984f}));
CORRADE_COMPARE(bezier.value(0.5f), (Vector2{11.875f, 4.625f})); CORRADE_COMPARE(bezier.value(0.5f), (Vector2{11.875f, 4.625f}));

41
src/Magnum/Math/Test/CubicHermiteTest.cpp

@ -26,6 +26,7 @@
#include <sstream> #include <sstream>
#include <Corrade/TestSuite/Tester.h> #include <Corrade/TestSuite/Tester.h>
#include "Magnum/Math/Bezier.h"
#include "Magnum/Math/CubicHermite.h" #include "Magnum/Math/CubicHermite.h"
#include "Magnum/Math/Functions.h" #include "Magnum/Math/Functions.h"
#include "Magnum/Math/Vector2.h" #include "Magnum/Math/Vector2.h"
@ -65,6 +66,8 @@ struct CubicHermiteTest: Corrade::TestSuite::Tester {
void constructConversionComplex(); void constructConversionComplex();
void constructConversionQuaternion(); void constructConversionQuaternion();
void constructFromBezier();
void constructCopyScalar(); void constructCopyScalar();
void constructCopyVector(); void constructCopyVector();
void constructCopyComplex(); void constructCopyComplex();
@ -94,6 +97,7 @@ struct CubicHermiteTest: Corrade::TestSuite::Tester {
void splerpScalar(); void splerpScalar();
void splerpVector(); void splerpVector();
void splerpVectorFromBezier();
void splerpComplex(); void splerpComplex();
void splerpComplexNotNormalized(); void splerpComplexNotNormalized();
void splerpQuaternion(); void splerpQuaternion();
@ -136,6 +140,8 @@ CubicHermiteTest::CubicHermiteTest() {
&CubicHermiteTest::constructConversionComplex, &CubicHermiteTest::constructConversionComplex,
&CubicHermiteTest::constructConversionQuaternion, &CubicHermiteTest::constructConversionQuaternion,
&CubicHermiteTest::constructFromBezier,
&CubicHermiteTest::constructCopyScalar, &CubicHermiteTest::constructCopyScalar,
&CubicHermiteTest::constructCopyVector, &CubicHermiteTest::constructCopyVector,
&CubicHermiteTest::constructCopyComplex, &CubicHermiteTest::constructCopyComplex,
@ -165,6 +171,7 @@ CubicHermiteTest::CubicHermiteTest() {
&CubicHermiteTest::splerpScalar, &CubicHermiteTest::splerpScalar,
&CubicHermiteTest::splerpVector, &CubicHermiteTest::splerpVector,
&CubicHermiteTest::splerpVectorFromBezier,
&CubicHermiteTest::splerpComplex, &CubicHermiteTest::splerpComplex,
&CubicHermiteTest::splerpComplexNotNormalized, &CubicHermiteTest::splerpComplexNotNormalized,
&CubicHermiteTest::splerpQuaternion, &CubicHermiteTest::splerpQuaternion,
@ -179,6 +186,7 @@ CubicHermiteTest::CubicHermiteTest() {
typedef Math::Vector2<Float> Vector2; typedef Math::Vector2<Float> Vector2;
typedef Math::Complex<Float> Complex; typedef Math::Complex<Float> Complex;
typedef Math::Quaternion<Float> Quaternion; typedef Math::Quaternion<Float> Quaternion;
typedef Math::CubicBezier2D<Float> CubicBezier2D;
typedef Math::CubicHermite1D<Float> CubicHermite1D; typedef Math::CubicHermite1D<Float> CubicHermite1D;
typedef Math::CubicHermite2D<Float> CubicHermite2D; typedef Math::CubicHermite2D<Float> CubicHermite2D;
typedef Math::CubicHermiteComplex<Float> CubicHermiteComplex; typedef Math::CubicHermiteComplex<Float> CubicHermiteComplex;
@ -499,6 +507,20 @@ void CubicHermiteTest::constructConversionQuaternion() {
CORRADE_VERIFY((std::is_nothrow_constructible<CubicHermiteQuaternion, CubicHermiteQuaternioni>::value)); CORRADE_VERIFY((std::is_nothrow_constructible<CubicHermiteQuaternion, CubicHermiteQuaternioni>::value));
} }
void CubicHermiteTest::constructFromBezier() {
/* Taken from BezierTest::valueCubic() -- we're testing the same values
also in splerpVectorFromBezier(). See
BezierTest::constructFromCubicHermite() for the inverse. */
CubicBezier2D bezier{Vector2{0.0f, 0.0f}, Vector2{10.0f, 15.0f}, Vector2{20.0f, 4.0f}, Vector2{5.0f, -20.0f}};
auto a = CubicHermite2D::fromBezier({Vector2{}, Vector2{}, Vector2{}, bezier[0]}, bezier);
auto b = CubicHermite2D::fromBezier(bezier, {bezier[3], Vector2{}, Vector2{}, Vector2{}});
CORRADE_COMPARE(a.point(), bezier[0]);
CORRADE_COMPARE(a.outTangent(), (Vector2{30.0f, 45.0f}));
CORRADE_COMPARE(b.inTangent(), (Vector2{-45, -72}));
CORRADE_COMPARE(b.point(), bezier[3]);
}
void CubicHermiteTest::constructCopyScalar() { void CubicHermiteTest::constructCopyScalar() {
constexpr CubicHermite1D a{2.0f, -2.0f, -0.5f}; constexpr CubicHermite1D a{2.0f, -2.0f, -0.5f};
constexpr CubicHermite1D b{a}; constexpr CubicHermite1D b{a};
@ -815,6 +837,25 @@ void CubicHermiteTest::splerpVector() {
CORRADE_COMPARE(Math::splerp(a, b, 0.8f), (Vector2{-2.152f, 0.9576f})); CORRADE_COMPARE(Math::splerp(a, b, 0.8f), (Vector2{-2.152f, 0.9576f}));
} }
void CubicHermiteTest::splerpVectorFromBezier() {
/* Taken from BezierTest::valueCubic() */
CubicBezier2D bezier{Vector2{0.0f, 0.0f}, Vector2{10.0f, 15.0f}, Vector2{20.0f, 4.0f}, Vector2{5.0f, -20.0f}};
auto a = CubicHermite2D::fromBezier({Vector2{}, Vector2{}, Vector2{}, bezier[0]}, bezier);
auto b = CubicHermite2D::fromBezier(bezier, {bezier[3], Vector2{}, Vector2{}, Vector2{}});
CORRADE_COMPARE(bezier.value(0.0f), (Vector2{0.0f, 0.0f}));
CORRADE_COMPARE(Math::splerp(a, b, 0.0f), (Vector2{0.0f, 0.0f}));
CORRADE_COMPARE(bezier.value(0.2f), (Vector2{5.8f, 5.984f}));
CORRADE_COMPARE(Math::splerp(a, b, 0.2f), (Vector2{5.8f, 5.984f}));
CORRADE_COMPARE(bezier.value(0.5f), (Vector2{11.875f, 4.625f}));
CORRADE_COMPARE(Math::splerp(a, b, 0.5f), (Vector2{11.875f, 4.625f}));
CORRADE_COMPARE(bezier.value(1.0f), (Vector2{5.0f, -20.0f}));
CORRADE_COMPARE(Math::splerp(a, b, 1.0f), (Vector2{5.0f, -20.0f}));
}
void CubicHermiteTest::splerpComplex() { void CubicHermiteTest::splerpComplex() {
CubicHermiteComplex a{{2.0f, 1.5f}, {0.999445f, 0.0333148f}, {-1.0f, 0.0f}}; CubicHermiteComplex a{{2.0f, 1.5f}, {0.999445f, 0.0333148f}, {-1.0f, 0.0f}};
CubicHermiteComplex b{{5.0f, 0.3f}, {-0.876216f, 0.481919f}, {1.5f, 0.3f}}; CubicHermiteComplex b{{5.0f, 0.3f}, {-0.876216f, 0.481919f}, {1.5f, 0.3f}};

Loading…
Cancel
Save