Browse Source

Math: type-safe implementation for degrees and radians.

Next few commits will add requirement for "strongly typed" angles in all
function parameters, e.g.:

    Matrix3::rotation(24.0_degf);
    Math::sin(1.047_radf);

The purpose is to make angle entering less error-prone, e.g. not passing
degrees when radians should be etc.
pull/7/head
Vladimír Vondruš 13 years ago
parent
commit
ff929c04e9
  1. 1
      src/CMakeLists.txt
  2. 29
      src/Math/Angle.cpp
  3. 248
      src/Math/Angle.h
  4. 1
      src/Math/CMakeLists.txt
  5. 2
      src/Math/Math.h
  6. 115
      src/Math/Test/AngleTest.cpp
  7. 1
      src/Math/Test/CMakeLists.txt

1
src/CMakeLists.txt

@ -98,6 +98,7 @@ add_library(MagnumObjects OBJECT ${Magnum_SRCS})
# Files shared between main library and math unit test library
set(MagnumMath_SRCS
Math/Angle.cpp
Math/Complex.cpp
Math/DualQuaternion.cpp
Math/Functions.cpp

29
src/Math/Angle.cpp

@ -0,0 +1,29 @@
/*
Copyright © 2010, 2011, 2012 Vladimír Vondruš <mosra@centrum.cz>
This file is part of Magnum.
Magnum is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License version 3
only, as published by the Free Software Foundation.
Magnum 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
GNU Lesser General Public License version 3 for more details.
*/
#include "Angle.h"
namespace Magnum { namespace Math {
#ifndef DOXYGEN_GENERATING_OUTPUT
template Corrade::Utility::Debug operator<<(Corrade::Utility::Debug, const Rad<float>&);
template Corrade::Utility::Debug operator<<(Corrade::Utility::Debug, const Deg<float>&);
#ifndef MAGNUM_TARGET_GLES
template Corrade::Utility::Debug operator<<(Corrade::Utility::Debug, const Rad<double>&);
template Corrade::Utility::Debug operator<<(Corrade::Utility::Debug, const Deg<double>&);
#endif
#endif
}}

248
src/Math/Angle.h

@ -0,0 +1,248 @@
#ifndef Magnum_Math_Angle_h
#define Magnum_Math_Angle_h
/*
Copyright © 2010, 2011, 2012 Vladimír Vondruš <mosra@centrum.cz>
This file is part of Magnum.
Magnum is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License version 3
only, as published by the Free Software Foundation.
Magnum 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
GNU Lesser General Public License version 3 for more details.
*/
/** @file
* @brief Class Magnum::Math::Deg, Magnum::Math::Rad and related operators.
*/
#include <Utility/Debug.h>
#include "Math/Constants.h"
#include "Math/Math.h"
#include "Math/Unit.h"
#include "magnumVisibility.h"
namespace Magnum { namespace Math {
/**
@brief Angle in degrees
Along with Rad provides convenience classes to make angle specification and
conversion less error-prone.
@section Rad-usage Usage
You can enter the value either by using literal:
@code
auto degrees = 60.0_degf; // type is Deg<float>
auto radians = 1.047_rad; // type is Rad<double>
@endcode
Or explicitly convert unitless value (such as output from some function) to
either degrees or radians:
@code
double foo();
Deg<float> degrees(35.0f);
Rad<double> radians(foo());
//degrees = 60.0f; // error, no implicit conversion
@endcode
The classes support all arithmetic operations, such as addition, subtraction
or multiplication/division by unitless number:
@code
auto a = 60.0_degf + 17.35_degf;
auto b = -a + 23.0_degf*4;
//auto c = 60.0_degf*45.0_degf; // error, undefined resulting unit
@endcode
It is also possible to compare angles with all comparison operators, but
comparison of degrees and radians is not possible without explicit conversion
to common type:
@code
Rad<float> angle();
Deg<float> x = angle(); // convert to degrees for easier comparison
if(x < 30.0_degf) foo();
//if(x > 1.57_radf) bar(); // error, both need to be of the same type
@endcode
It is possible to seamlessly convert between degrees and radians and explicitly
convert the value back to underlying type:
@code
float sine(Rad<float> angle);
float a = sine(60.0_degf); // the same as sine(1.047_radf)
Deg<double> b = 1.047_rad; // the same as 60.0_deg
float d = double(b); // 60.0
//float e = b; // error, no implicit conversion
@endcode
@section Rad-conversions Requirement of explicit conversion
The requirement of explicit conversions from and to unitless types helps to
reduce unit-based errors. Consider following example with implicit conversions
allowed:
@code
float std::sin(float angle);
float sine(Rad<float> angle);
float a = 60.0f; // degrees
sine(a); // silent error, sine() expected radians
auto b = 60.0_degf; // degrees
std::sin(b); // silent error, std::sin() expected radians
@endcode
These silent errors are easily avoided by requiring explicit conversions:
@code
//sine(angleInDegrees); // compilation error
sine(Deg<float>(angleInDegrees)); // explicitly specifying unit
//std::sin(angleInDegrees); // compilation error
std::sin(float(Rad<float>(angleInDegrees)); // required explicit conversion hints
// to user that this case needs special
// attention (i.e., conversion to radians)
@endcode
*/
template<class T> class Deg: public Unit<Deg, T> {
public:
/** @brief Default constructor */
inline constexpr /*implicit*/ Deg() {}
/** @brief Explicit constructor from unitless type */
inline constexpr explicit Deg(T value): Unit<Deg, T>(value) {}
/** @brief Copy constructor */
inline constexpr /*implicit*/ Deg(Unit<Deg, T> value): Unit<Deg, T>(value) {}
/** @brief Construct from another underlying type */
template<class U> inline constexpr explicit Deg(Unit<Deg, U> value): Unit<Deg, T>(value) {}
/**
* @brief Construct degrees from radians
*
* Performs conversion from radians to degrees, i.e.:
* @f[
* deg = 180 \frac {rad} \pi
* @f]
*/
inline constexpr /*implicit*/ Deg(Unit<Rad, T> value);
};
#ifndef CORRADE_GCC46_COMPATIBILITY
/** @relates Deg
@brief Double-precision degree value literal
Example usage:
@code
double cosine = Math::cos(60.0_deg); // cosine = 0.5
double cosine = Math::cos(1.047_rad); // cosine = 0.5
@endcode
@see operator""_degf(), operator""_rad()
@note Not available on GCC < 4.7. Use Deg::Deg(T) instead.
*/
inline constexpr Deg<double> operator "" _deg(long double value) { return Deg<double>(value); }
/** @relates Deg
@brief Single-precision degree value literal
Example usage:
@code
float tangent = Math::tan(60.0_degf); // tangent = 1.732f
float tangent = Math::tan(1.047_radf); // tangent = 1.732f
@endcode
@see operator""_deg(), operator""_radf()
@note Not available on GCC < 4.7. Use Deg::Deg(T) instead.
*/
inline constexpr Deg<float> operator "" _degf(long double value) { return Deg<float>(value); }
#endif
/**
@brief Angle in radians
See Deg for more information.
*/
template<class T> class Rad: public Unit<Rad, T> {
public:
/** @brief Default constructor */
inline constexpr /*implicit*/ Rad() {}
/** @brief Construct from unitless type */
inline constexpr explicit Rad(T value): Unit<Rad, T>(value) {}
/** @brief Copy constructor */
inline constexpr /*implicit*/ Rad(Unit<Rad, T> value): Unit<Rad, T>(value) {}
/** @brief Construct from another underlying type */
template<class U> inline constexpr explicit Rad(Unit<Rad, U> value): Unit<Rad, T>(value) {}
/**
* @brief Construct radians from degrees
*
* Performs conversion from degrees to radians, i.e.:
* @f[
* rad = deg \frac \pi 180
* @f]
*/
inline constexpr /*implicit*/ Rad(Unit<Deg, T> value);
};
#ifndef CORRADE_GCC46_COMPATIBILITY
/** @relates Rad
@brief Double-precision radian value literal
See operator""_rad() for more information.
@see operator""_radf(), operator""_deg()
@note Not available on GCC < 4.7. Use Rad::Rad(T) instead.
*/
inline constexpr Rad<double> operator "" _rad(long double value) { return Rad<double>(value); }
/** @relates Rad
@brief Single-precision radian value literal
See operator""_degf() for more information.
@see operator""_rad(), operator""_degf()
@note Not available on GCC < 4.7. Use Rad::Rad(T) instead.
*/
inline constexpr Rad<float> operator "" _radf(long double value) { return Rad<float>(value); }
#endif
template<class T> inline constexpr Deg<T>::Deg(Unit<Rad, T> value): Unit<Deg, T>(T(180)*T(value)/Math::Constants<T>::pi()) {}
template<class T> inline constexpr Rad<T>::Rad(Unit<Deg, T> value): Unit<Rad, T>(T(value)*Math::Constants<T>::pi()/T(180)) {}
/** @debugoperator{Magnum::Math::Rad} */
template<class T> Corrade::Utility::Debug operator<<(Corrade::Utility::Debug debug, const Rad<T>& value) {
debug << "Rad(";
debug.setFlag(Corrade::Utility::Debug::SpaceAfterEachValue, false);
debug << T(value) << ")";
debug.setFlag(Corrade::Utility::Debug::SpaceAfterEachValue, true);
return debug;
}
/** @debugoperator{Magnum::Math::Deg} */
template<class T> Corrade::Utility::Debug operator<<(Corrade::Utility::Debug debug, const Deg<T>& value) {
debug << "Deg(";
debug.setFlag(Corrade::Utility::Debug::SpaceAfterEachValue, false);
debug << T(value) << ")";
debug.setFlag(Corrade::Utility::Debug::SpaceAfterEachValue, true);
return debug;
}
/* Explicit instantiation for commonly used types */
#ifndef DOXYGEN_GENERATING_OUTPUT
extern template Corrade::Utility::Debug MAGNUM_EXPORT operator<<(Corrade::Utility::Debug, const Rad<float>&);
extern template Corrade::Utility::Debug MAGNUM_EXPORT operator<<(Corrade::Utility::Debug, const Deg<float>&);
#ifndef MAGNUM_TARGET_GLES
extern template Corrade::Utility::Debug MAGNUM_EXPORT operator<<(Corrade::Utility::Debug, const Rad<double>&);
extern template Corrade::Utility::Debug MAGNUM_EXPORT operator<<(Corrade::Utility::Debug, const Deg<double>&);
#endif
#endif
}}
#endif

1
src/Math/CMakeLists.txt

@ -1,4 +1,5 @@
set(MagnumMath_HEADERS
Angle.h
BoolVector.h
Complex.h
Constants.h

2
src/Math/Math.h

@ -42,6 +42,8 @@ template<class> class Quaternion;
template<std::size_t, std::size_t, class> class RectangularMatrix;
template<template<class> class, class> class Unit;
template<class> class Deg;
template<class> class Rad;
template<std::size_t, class> class Vector;
template<class> class Vector2;

115
src/Math/Test/AngleTest.cpp

@ -0,0 +1,115 @@
/*
Copyright © 2010, 2011, 2012 Vladimír Vondruš <mosra@centrum.cz>
This file is part of Magnum.
Magnum is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License version 3
only, as published by the Free Software Foundation.
Magnum 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
GNU Lesser General Public License version 3 for more details.
*/
#include <sstream>
#include <TestSuite/Tester.h>
#include "Math/Angle.h"
namespace Magnum { namespace Math { namespace Test {
class AngleTest: public Corrade::TestSuite::Tester {
public:
explicit AngleTest();
void construct();
void literals();
void conversion();
void debugDeg();
void debugRad();
};
typedef Math::Deg<float> Deg;
typedef Math::Rad<float> Rad;
typedef Math::Deg<double> Degd;
typedef Math::Rad<double> Radd;
AngleTest::AngleTest() {
addTests(&AngleTest::construct,
&AngleTest::literals,
&AngleTest::conversion,
&AngleTest::debugDeg,
&AngleTest::debugRad);
}
void AngleTest::construct() {
/* Default constructor */
constexpr Degd a;
constexpr Deg m;
CORRADE_COMPARE(double(a), 0.0f);
CORRADE_COMPARE(float(m), 0.0f);
/* Value constructor */
constexpr Deg b(25.0);
constexpr Radd n(3.14);
CORRADE_COMPARE(float(b), 25.0);
CORRADE_COMPARE(double(n), 3.14);
/* Copy constructor */
constexpr Deg c(b);
constexpr Radd o(n);
CORRADE_COMPARE(c, b);
CORRADE_COMPARE(o, n);
/* Conversion operator */
constexpr Degd d(b);
constexpr Rad p(n);
CORRADE_COMPARE(double(d), 25.0);
CORRADE_COMPARE(float(p), 3.14f);
}
void AngleTest::literals() {
constexpr auto a = 25.0_deg;
constexpr auto b = 25.0_degf;
CORRADE_VERIFY((std::is_same<decltype(a), const Degd>::value));
CORRADE_VERIFY((std::is_same<decltype(b), const Deg>::value));
CORRADE_COMPARE(double(a), 25.0);
CORRADE_COMPARE(float(b), 25.0f);
constexpr auto m = 3.14_rad;
constexpr auto n = 3.14_radf;
CORRADE_VERIFY((std::is_same<decltype(m), const Radd>::value));
CORRADE_VERIFY((std::is_same<decltype(n), const Rad>::value));
CORRADE_COMPARE(double(m), 3.14);
CORRADE_COMPARE(float(n), 3.14f);
}
void AngleTest::conversion() {
constexpr Deg a(Rad(1.57079633f));
CORRADE_COMPARE(float(a), 90.0f);
constexpr Rad b(Deg(90.0));
CORRADE_COMPARE(float(b), 1.57079633f);
}
void AngleTest::debugDeg() {
std::ostringstream o;
Debug(&o) << Deg(90.0);
CORRADE_COMPARE(o.str(), "Deg(90)\n");
}
void AngleTest::debugRad() {
std::ostringstream o;
Debug(&o) << Rad(1.5708);
CORRADE_COMPARE(o.str(), "Rad(1.5708)\n");
}
}}}
CORRADE_TEST_MAIN(Magnum::Math::Test::AngleTest)

1
src/Math/Test/CMakeLists.txt

@ -17,6 +17,7 @@ corrade_add_test(MathMatrix4Test Matrix4Test.cpp LIBRARIES MagnumMathTestLib)
corrade_add_test(MathSwizzleTest SwizzleTest.cpp LIBRARIES MagnumMathTestLib)
corrade_add_test(MathUnitTest UnitTest.cpp)
corrade_add_test(MathAngleTest AngleTest.cpp LIBRARIES MagnumMathTestLib)
corrade_add_test(MathComplexTest ComplexTest.cpp LIBRARIES MagnumMathTestLib)
corrade_add_test(MathDualTest DualTest.cpp)

Loading…
Cancel
Save