Browse Source

Math: added lerp() and slerp() for complex numbers.

It's a straight copy of the code for quaternions -- it could probably be
simplified a bit, but I don't have the necessary brain cells at the
moment. I tried the following but failed:

    retun Complex::rotation(acos(cosAngle)*t)*normalizedA;
pull/267/head
Vladimír Vondruš 8 years ago
parent
commit
c2c6b9f22b
  1. 3
      doc/changelog.dox
  2. 47
      src/Magnum/Math/Complex.h
  3. 3
      src/Magnum/Math/Functions.h
  4. 12
      src/Magnum/Math/Quaternion.h
  5. 61
      src/Magnum/Math/Test/ComplexTest.cpp
  6. 26
      src/Magnum/Math/Test/QuaternionTest.cpp

3
doc/changelog.dox

@ -66,6 +66,9 @@ See also:
@ref Math::Color4::fromSrgb(UnsignedInt, T), @ref Math::Color3::toSrgbInt(), @ref Math::Color4::fromSrgb(UnsignedInt, T), @ref Math::Color3::toSrgbInt(),
and @ref Math::Color4::toSrgbAlphaInt() for easier conversion of packed and @ref Math::Color4::toSrgbAlphaInt() for easier conversion of packed
24-/32-bit sRGB colors to and from @ref Math::Color3 / @ref Math::Color4 24-/32-bit sRGB colors to and from @ref Math::Color3 / @ref Math::Color4
- Added @ref Math::lerp(const Complex<T>&, const Complex<T>&, T) and
@ref Math::slerp(const Complex<T>&, const Complex<T>&, T) for feature
parity with @ref Math::Quaternion
- Added @ref Math::Range2D::x(), @ref Math::Range3D::x(), - Added @ref Math::Range2D::x(), @ref Math::Range3D::x(),
@ref Math::Range2D::y(), @ref Math::Range3D::y(), @ref Math::Range3D::z() @ref Math::Range2D::y(), @ref Math::Range3D::y(), @ref Math::Range3D::z()
and @ref Math::Range3D::y() for slicing ranges into lower dimensions and @ref Math::Range3D::y() for slicing ranges into lower dimensions

47
src/Magnum/Math/Complex.h

@ -456,6 +456,53 @@ template<class T> inline Complex<T> operator/(T scalar, const Complex<T>& comple
return {scalar/complex.real(), scalar/complex.imaginary()}; return {scalar/complex.real(), scalar/complex.imaginary()};
} }
/** @relatesalso Complex
@brief Linear interpolation of two complex numbers
@param normalizedA First complex number
@param normalizedB Second complex number
@param t Interpolation phase (from range @f$ [0; 1] @f$)
Expects that both complex numbers are normalized. @f[
c_{LERP} = \frac{(1 - t) c_A + t c_B}{|(1 - t) c_A + t c_B|}
@f]
@see @ref Complex::isNormalized(), @ref slerp(const Complex<T>&, const Complex<T>&, T),
@ref lerp(const Quaternion<T>&, const Quaternion<T>&, T),
@ref lerp(const T&, const T&, U)
*/
template<class T> inline Complex<T> lerp(const Complex<T>& normalizedA, const Complex<T>& normalizedB, T t) {
CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(),
"Math::lerp(): complex numbers must be normalized", {});
return ((T(1) - t)*normalizedA + t*normalizedB).normalized();
}
/** @relatesalso Complex
@brief Spherical linear interpolation of two complex numbers
@param normalizedA First complex number
@param normalizedB Second complex number
@param t Interpolation phase (from range @f$ [0; 1] @f$)
Expects that both complex numbers are normalized. If the complex numbers are
the same, returns the first argument. @f[
c_{SLERP} = \frac{sin((1 - t) \theta) c_A + sin(t \theta) c_B}{sin \theta}
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
\theta = acos \left( \frac{c_A \cdot c_B}{|c_A| \cdot |c_B|} \right) = acos(c_A \cdot c_B)
@f]
@see @ref Complex::isNormalized(), @ref lerp(const Complex<T>&, const Complex<T>&, T),
@ref slerp(const Quaternion<T>&, const Quaternion<T>&, T)
*/
template<class T> inline Complex<T> slerp(const Complex<T>& normalizedA, const Complex<T>& normalizedB, T t) {
CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(),
"Math::slerp(): complex numbers must be normalized", {});
const T cosAngle = dot(normalizedA, normalizedB);
/* Avoid division by zero */
if(std::abs(cosAngle) >= T(1)) return Complex<T>{normalizedA};
/** @todo couldn't this be done somewhat simpler? */
const T a = std::acos(cosAngle);
return (std::sin((T(1) - t)*a)*normalizedA + std::sin(t*a)*normalizedB)/std::sin(a);
}
/** @debugoperator{Complex} */ /** @debugoperator{Complex} */
template<class T> Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug& debug, const Complex<T>& value) { template<class T> Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug& debug, const Complex<T>& value) {
return debug << "Complex(" << Corrade::Utility::Debug::nospace return debug << "Complex(" << Corrade::Utility::Debug::nospace

3
src/Magnum/Math/Functions.h

@ -560,7 +560,8 @@ The interpolation for vectors is done as in following, similarly for scalars: @f
@f] @f]
See @ref select() for constant interpolation using the same API. See @ref select() for constant interpolation using the same API.
@see @ref lerpInverted(), @ref lerp(const Quaternion<T>&, const Quaternion<T>&, T) @see @ref lerpInverted(), @ref lerp(const Complex<T>&, const Complex<T>&, T),
@ref lerp(const Quaternion<T>&, const Quaternion<T>&, T)
@m_keyword{mix(),GLSL mix(),} @m_keyword{mix(),GLSL mix(),}
*/ */
template<class T, class U> inline template<class T, class U> inline

12
src/Magnum/Math/Quaternion.h

@ -87,8 +87,10 @@ template<class T> inline Rad<T> angle(const Quaternion<T>& normalizedA, const Qu
Expects that both quaternions are normalized. @f[ Expects that both quaternions are normalized. @f[
q_{LERP} = \frac{(1 - t) q_A + t q_B}{|(1 - t) q_A + t q_B|} q_{LERP} = \frac{(1 - t) q_A + t q_B}{|(1 - t) q_A + t q_B|}
@f] @f]
@see @ref Quaternion::isNormalized(), @ref slerp(const Quaternion<T>&, const Quaternion<T>&, T), @see @ref Quaternion::isNormalized(),
@ref lerp(const T&, const T&, U), @ref sclerp() @ref slerp(const Quaternion<T>&, const Quaternion<T>&, T), @ref sclerp(),
@ref lerp(const T&, const T&, U),
@ref lerp(const Complex<T>&, const Complex<T>&, T)
*/ */
template<class T> inline Quaternion<T> lerp(const Quaternion<T>& normalizedA, const Quaternion<T>& normalizedB, T t) { template<class T> inline Quaternion<T> lerp(const Quaternion<T>& normalizedA, const Quaternion<T>& normalizedB, T t) {
CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(), CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(),
@ -105,12 +107,12 @@ template<class T> inline Quaternion<T> lerp(const Quaternion<T>& normalizedA, co
Expects that both quaternions are normalized. If the quaternions are the same Expects that both quaternions are normalized. If the quaternions are the same
or one is a negation of the other, returns the first argument. @f[ or one is a negation of the other, returns the first argument. @f[
q_{SLERP} = \frac{sin((1 - t) \theta) q_A + sin(t \theta) q_B}{sin \theta} q_{SLERP} = \frac{sin((1 - t) \theta) q_A + sin(t \theta) q_B}{sin \theta}
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
\theta = acos \left( \frac{q_A \cdot q_B}{|q_A| \cdot |q_B|} \right) = acos(q_A \cdot q_B) \theta = acos \left( \frac{q_A \cdot q_B}{|q_A| \cdot |q_B|} \right) = acos(q_A \cdot q_B)
@f] @f]
@see @ref Quaternion::isNormalized(), @ref lerp(const Quaternion<T>&, const Quaternion<T>&, T), @see @ref Quaternion::isNormalized(), @ref lerp(const Quaternion<T>&, const Quaternion<T>&, T),
@ref sclerp() @ref slerp(const Complex<T>&, const Complex<T>&, T), @ref sclerp()
*/ */
template<class T> inline Quaternion<T> slerp(const Quaternion<T>& normalizedA, const Quaternion<T>& normalizedB, T t) { template<class T> inline Quaternion<T> slerp(const Quaternion<T>& normalizedA, const Quaternion<T>& normalizedB, T t) {
CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(), CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(),
"Math::slerp(): quaternions must be normalized", {}); "Math::slerp(): quaternions must be normalized", {});

61
src/Magnum/Math/Test/ComplexTest.cpp

@ -86,6 +86,10 @@ struct ComplexTest: Corrade::TestSuite::Tester {
void angle(); void angle();
void rotation(); void rotation();
void matrix(); void matrix();
void lerp();
void lerpNotNormalized();
void slerp();
void slerpNotNormalized();
void transformVector(); void transformVector();
void debug(); void debug();
@ -128,6 +132,10 @@ ComplexTest::ComplexTest() {
&ComplexTest::angle, &ComplexTest::angle,
&ComplexTest::rotation, &ComplexTest::rotation,
&ComplexTest::matrix, &ComplexTest::matrix,
&ComplexTest::lerp,
&ComplexTest::lerpNotNormalized,
&ComplexTest::slerp,
&ComplexTest::slerpNotNormalized,
&ComplexTest::transformVector, &ComplexTest::transformVector,
&ComplexTest::debug, &ComplexTest::debug,
@ -141,6 +149,8 @@ typedef Math::Vector2<Float> Vector2;
typedef Math::Matrix3<Float> Matrix3; typedef Math::Matrix3<Float> Matrix3;
typedef Math::Matrix2x2<Float> Matrix2x2; typedef Math::Matrix2x2<Float> Matrix2x2;
using namespace Math::Literals;
void ComplexTest::construct() { void ComplexTest::construct() {
constexpr Complex a = {0.5f, -3.7f}; constexpr Complex a = {0.5f, -3.7f};
CORRADE_COMPARE(a, Complex(0.5f, -3.7f)); CORRADE_COMPARE(a, Complex(0.5f, -3.7f));
@ -411,6 +421,57 @@ void ComplexTest::matrix() {
CORRADE_COMPARE(b, a); CORRADE_COMPARE(b, a);
} }
void ComplexTest::lerp() {
/* Results should be consistent with QuaternionTest::lerp2D() (but not
equivalent, probably because quaternions double cover and complex
numbers not) */
Complex a = Complex::rotation(15.0_degf);
Complex b = Complex::rotation(57.0_degf);
Complex lerp = Math::lerp(a, b, 0.35f);
CORRADE_VERIFY(lerp.isNormalized());
CORRADE_COMPARE(lerp.angle(), 29.4308_degf); /* almost but not quite 29.7 */
CORRADE_COMPARE(lerp, (Complex{0.87095f, 0.491372f}));
}
void ComplexTest::lerpNotNormalized() {
std::ostringstream out;
Error redirectError{&out};
Complex a;
Math::lerp(a*3.0f, a, 0.35f);
Math::lerp(a, a*-3.0f, 0.35f);
CORRADE_COMPARE(out.str(),
"Math::lerp(): complex numbers must be normalized\n"
"Math::lerp(): complex numbers must be normalized\n");
}
void ComplexTest::slerp() {
/* Result angle should be equivalent to QuaternionTest::slerp2D() */
Complex a = Complex::rotation(15.0_degf);
Complex b = Complex::rotation(57.0_degf);
Complex slerp = Math::slerp(a, b, 0.35f);
CORRADE_VERIFY(slerp.isNormalized());
CORRADE_COMPARE(slerp.angle(), 29.7_degf); /* 15 + (57-15)*0.35 */
CORRADE_COMPARE(slerp, (Complex{0.868632f, 0.495459f}));
/* Avoid division by zero */
CORRADE_COMPARE(Math::slerp(a, a, 0.25f), a);
}
void ComplexTest::slerpNotNormalized() {
std::ostringstream out;
Error redirectError{&out};
Complex a;
Math::slerp(a*3.0f, a, 0.35f);
Math::slerp(a, a*-3.0f, 0.35f);
CORRADE_COMPARE(out.str(),
"Math::slerp(): complex numbers must be normalized\n"
"Math::slerp(): complex numbers must be normalized\n");
}
void ComplexTest::transformVector() { void ComplexTest::transformVector() {
Complex a = Complex::rotation(Deg(23.0f)); Complex a = Complex::rotation(Deg(23.0f));
Matrix3 m = Matrix3::rotation(Deg(23.0f)); Matrix3 m = Matrix3::rotation(Deg(23.0f));

26
src/Magnum/Math/Test/QuaternionTest.cpp

@ -88,8 +88,10 @@ struct QuaternionTest: Corrade::TestSuite::Tester {
void angle(); void angle();
void matrix(); void matrix();
void lerp(); void lerp();
void lerp2D();
void lerpNotNormalized(); void lerpNotNormalized();
void slerp(); void slerp();
void slerp2D();
void slerpNotNormalized(); void slerpNotNormalized();
void transformVector(); void transformVector();
void transformVectorNormalized(); void transformVectorNormalized();
@ -145,8 +147,10 @@ QuaternionTest::QuaternionTest() {
&QuaternionTest::angle, &QuaternionTest::angle,
&QuaternionTest::matrix, &QuaternionTest::matrix,
&QuaternionTest::lerp, &QuaternionTest::lerp,
&QuaternionTest::lerp2D,
&QuaternionTest::lerpNotNormalized, &QuaternionTest::lerpNotNormalized,
&QuaternionTest::slerp, &QuaternionTest::slerp,
&QuaternionTest::slerp2D,
&QuaternionTest::slerpNotNormalized, &QuaternionTest::slerpNotNormalized,
&QuaternionTest::transformVector, &QuaternionTest::transformVector,
&QuaternionTest::transformVectorNormalized, &QuaternionTest::transformVectorNormalized,
@ -469,6 +473,17 @@ void QuaternionTest::lerp() {
CORRADE_COMPARE(lerp, Quaternion({0.119127f, 0.049134f, 0.049134f}, 0.990445f)); CORRADE_COMPARE(lerp, Quaternion({0.119127f, 0.049134f, 0.049134f}, 0.990445f));
} }
void QuaternionTest::lerp2D() {
/* Results should be consistent with ComplexTest::lerp() */
Quaternion a = Quaternion::rotation(15.0_degf, Vector3::zAxis());
Quaternion b = Quaternion::rotation(57.0_degf, Vector3::zAxis());
Quaternion lerp = Math::lerp(a, b, 0.35f);
CORRADE_VERIFY(lerp.isNormalized());
CORRADE_COMPARE(lerp.angle(), 29.6351_degf); /* almost but not quite 29.7 */
CORRADE_COMPARE(lerp, (Quaternion{{0.0f, 0.0f, 0.255742f}, 0.966745f}));
}
void QuaternionTest::lerpNotNormalized() { void QuaternionTest::lerpNotNormalized() {
std::ostringstream out; std::ostringstream out;
Error redirectError{&out}; Error redirectError{&out};
@ -494,6 +509,17 @@ void QuaternionTest::slerp() {
CORRADE_COMPARE(Math::slerp(a, -a, 0.42f), a); CORRADE_COMPARE(Math::slerp(a, -a, 0.42f), a);
} }
void QuaternionTest::slerp2D() {
/* Result angle should be equivalent to ComplexTest::slerp() */
Quaternion a = Quaternion::rotation(15.0_degf, Vector3::zAxis());
Quaternion b = Quaternion::rotation(57.0_degf, Vector3::zAxis());
Quaternion slerp = Math::slerp(a, b, 0.35f);
CORRADE_VERIFY(slerp.isNormalized());
CORRADE_COMPARE(slerp.angle(), 29.7_degf); /* 15 + (57-15)*0.35 */
CORRADE_COMPARE(slerp, (Quaternion{{0.0f, 0.0f, 0.256289f}, 0.9666f}));
}
void QuaternionTest::slerpNotNormalized() { void QuaternionTest::slerpNotNormalized() {
std::ostringstream out; std::ostringstream out;
Error redirectError{&out}; Error redirectError{&out};

Loading…
Cancel
Save