Skip to content

Commit

Permalink
Math: implement cubic Bezier <-> cubic Hermite conversion.
Browse files Browse the repository at this point in the history
  • Loading branch information
mosra committed Aug 31, 2018
1 parent c3d093b commit 6c8a2a4
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 3 deletions.
4 changes: 3 additions & 1 deletion doc/changelog.dox
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ See also:

@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(),
@ref Math::Intersection::aabbFrustum(),
@ref Math::Intersection::sphereFrustum(),
Expand Down
14 changes: 14 additions & 0 deletions doc/snippets/MagnumMath.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@

#include "Magnum/Magnum.h"
#include "Magnum/Math/Color.h"
#include "Magnum/Math/Bezier.h"
#include "Magnum/Math/CubicHermite.h"
#include "Magnum/Math/DualComplex.h"
#include "Magnum/Math/DualQuaternion.h"
#include "Magnum/Math/Half.h"
Expand Down Expand Up @@ -839,6 +841,18 @@ Color4 a = 0x33b27fcc_srgbaf; // {0.0331048f, 0.445201f, 0.212231f, 0.8f}
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] */
using namespace Math::Literals;
Expand Down
39 changes: 37 additions & 2 deletions src/Magnum/Math/Bezier.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,12 @@ namespace Implementation {
@tparam dimensions Dimensions of control points
@tparam T Underlying data type
Implementation of M-order N-dimensional
[Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve).
Represents a M-order N-dimensional
[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,
@ref QuadraticBezier3D, @ref CubicBezier2D, @ref CubicBezier3D
*/
Expand All @@ -64,6 +68,37 @@ template<UnsignedInt order, UnsignedInt dimensions, class T> class Bezier {
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
*
Expand Down
43 changes: 43 additions & 0 deletions src/Magnum/Math/CubicHermite.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
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,
@ref Magnum::CubicHermite2D, @ref Magnum::CubicHermite2Dd,
@ref Magnum::CubicHermite3D, @ref Magnum::CubicHermite3Dd,
Expand All @@ -56,6 +59,46 @@ template<class T> class CubicHermite {
public:
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
*
Expand Down
15 changes: 15 additions & 0 deletions src/Magnum/Math/Test/BezierTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <Corrade/Utility/Configuration.h>

#include "Magnum/Math/Bezier.h"
#include "Magnum/Math/CubicHermite.h"
#include "Magnum/Math/Vector2.h"
#include "Magnum/Math/Functions.h"

Expand Down Expand Up @@ -60,6 +61,7 @@ typedef Math::Bezier<1, 2, Float> LinearBezier2D;
typedef Math::QuadraticBezier2D<Float> QuadraticBezier2D;
typedef Math::QuadraticBezier2D<Double> QuadraticBezier2Dd;
typedef Math::CubicBezier2D<Float> CubicBezier2D;
typedef Math::CubicHermite2D<Float> CubicHermite2D;

struct BezierTest: Corrade::TestSuite::Tester {
explicit BezierTest();
Expand All @@ -68,6 +70,7 @@ struct BezierTest: Corrade::TestSuite::Tester {
void constructDefault();
void constructNoInit();
void constructConversion();
void constructFromCubicHermite();
void constructCopy();
void convert();

Expand All @@ -91,6 +94,7 @@ BezierTest::BezierTest() {
&BezierTest::constructDefault,
&BezierTest::constructNoInit,
&BezierTest::constructConversion,
&BezierTest::constructFromCubicHermite,
&BezierTest::constructCopy,
&BezierTest::convert,

Expand Down Expand Up @@ -160,6 +164,16 @@ void BezierTest::constructConversion() {
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() {
constexpr QuadraticBezier2D a{Vector2{0.5f, 1.0f}, Vector2{1.1f, 0.3f}, Vector2{0.1f, 1.2f}};
constexpr QuadraticBezier2D b{a};
Expand Down Expand Up @@ -237,6 +251,7 @@ void BezierTest::valueQuadratic() {
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}};

/* 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.2f), (Vector2{5.8f, 5.984f}));
CORRADE_COMPARE(bezier.value(0.5f), (Vector2{11.875f, 4.625f}));
Expand Down
41 changes: 41 additions & 0 deletions src/Magnum/Math/Test/CubicHermiteTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <sstream>
#include <Corrade/TestSuite/Tester.h>

#include "Magnum/Math/Bezier.h"
#include "Magnum/Math/CubicHermite.h"
#include "Magnum/Math/Functions.h"
#include "Magnum/Math/Vector2.h"
Expand Down Expand Up @@ -65,6 +66,8 @@ struct CubicHermiteTest: Corrade::TestSuite::Tester {
void constructConversionComplex();
void constructConversionQuaternion();

void constructFromBezier();

void constructCopyScalar();
void constructCopyVector();
void constructCopyComplex();
Expand Down Expand Up @@ -94,6 +97,7 @@ struct CubicHermiteTest: Corrade::TestSuite::Tester {

void splerpScalar();
void splerpVector();
void splerpVectorFromBezier();
void splerpComplex();
void splerpComplexNotNormalized();
void splerpQuaternion();
Expand Down Expand Up @@ -136,6 +140,8 @@ CubicHermiteTest::CubicHermiteTest() {
&CubicHermiteTest::constructConversionComplex,
&CubicHermiteTest::constructConversionQuaternion,

&CubicHermiteTest::constructFromBezier,

&CubicHermiteTest::constructCopyScalar,
&CubicHermiteTest::constructCopyVector,
&CubicHermiteTest::constructCopyComplex,
Expand Down Expand Up @@ -165,6 +171,7 @@ CubicHermiteTest::CubicHermiteTest() {

&CubicHermiteTest::splerpScalar,
&CubicHermiteTest::splerpVector,
&CubicHermiteTest::splerpVectorFromBezier,
&CubicHermiteTest::splerpComplex,
&CubicHermiteTest::splerpComplexNotNormalized,
&CubicHermiteTest::splerpQuaternion,
Expand All @@ -179,6 +186,7 @@ CubicHermiteTest::CubicHermiteTest() {
typedef Math::Vector2<Float> Vector2;
typedef Math::Complex<Float> Complex;
typedef Math::Quaternion<Float> Quaternion;
typedef Math::CubicBezier2D<Float> CubicBezier2D;
typedef Math::CubicHermite1D<Float> CubicHermite1D;
typedef Math::CubicHermite2D<Float> CubicHermite2D;
typedef Math::CubicHermiteComplex<Float> CubicHermiteComplex;
Expand Down Expand Up @@ -499,6 +507,20 @@ void CubicHermiteTest::constructConversionQuaternion() {
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() {
constexpr CubicHermite1D a{2.0f, -2.0f, -0.5f};
constexpr CubicHermite1D b{a};
Expand Down Expand Up @@ -815,6 +837,25 @@ void CubicHermiteTest::splerpVector() {
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() {
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}};
Expand Down

0 comments on commit 6c8a2a4

Please sign in to comment.