From ff929c04e94588f046382901ada6b5842dfc3bc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 20 Feb 2013 18:20:01 +0100 Subject: [PATCH] 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. --- src/CMakeLists.txt | 1 + src/Math/Angle.cpp | 29 ++++ src/Math/Angle.h | 248 +++++++++++++++++++++++++++++++++++ src/Math/CMakeLists.txt | 1 + src/Math/Math.h | 2 + src/Math/Test/AngleTest.cpp | 115 ++++++++++++++++ src/Math/Test/CMakeLists.txt | 1 + 7 files changed, 397 insertions(+) create mode 100644 src/Math/Angle.cpp create mode 100644 src/Math/Angle.h create mode 100644 src/Math/Test/AngleTest.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1fa36c4e0..788f83301 100644 --- a/src/CMakeLists.txt +++ b/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 diff --git a/src/Math/Angle.cpp b/src/Math/Angle.cpp new file mode 100644 index 000000000..73f128ac1 --- /dev/null +++ b/src/Math/Angle.cpp @@ -0,0 +1,29 @@ +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + 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&); +template Corrade::Utility::Debug operator<<(Corrade::Utility::Debug, const Deg&); +#ifndef MAGNUM_TARGET_GLES +template Corrade::Utility::Debug operator<<(Corrade::Utility::Debug, const Rad&); +template Corrade::Utility::Debug operator<<(Corrade::Utility::Debug, const Deg&); +#endif +#endif + +}} diff --git a/src/Math/Angle.h b/src/Math/Angle.h new file mode 100644 index 000000000..d579c1b39 --- /dev/null +++ b/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š + + 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 + +#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 +auto radians = 1.047_rad; // type is Rad +@endcode + +Or explicitly convert unitless value (such as output from some function) to +either degrees or radians: +@code +double foo(); + +Deg degrees(35.0f); +Rad 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 angle(); + +Deg 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 angle); +float a = sine(60.0_degf); // the same as sine(1.047_radf) +Deg 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 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(angleInDegrees)); // explicitly specifying unit + +//std::sin(angleInDegrees); // compilation error +std::sin(float(Rad(angleInDegrees)); // required explicit conversion hints + // to user that this case needs special + // attention (i.e., conversion to radians) +@endcode +*/ +template class Deg: public Unit { + public: + /** @brief Default constructor */ + inline constexpr /*implicit*/ Deg() {} + + /** @brief Explicit constructor from unitless type */ + inline constexpr explicit Deg(T value): Unit(value) {} + + /** @brief Copy constructor */ + inline constexpr /*implicit*/ Deg(Unit value): Unit(value) {} + + /** @brief Construct from another underlying type */ + template inline constexpr explicit Deg(Unit value): Unit(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 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 operator "" _deg(long double value) { return Deg(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 operator "" _degf(long double value) { return Deg(value); } +#endif + +/** +@brief Angle in radians + +See Deg for more information. +*/ +template class Rad: public Unit { + public: + /** @brief Default constructor */ + inline constexpr /*implicit*/ Rad() {} + + /** @brief Construct from unitless type */ + inline constexpr explicit Rad(T value): Unit(value) {} + + /** @brief Copy constructor */ + inline constexpr /*implicit*/ Rad(Unit value): Unit(value) {} + + /** @brief Construct from another underlying type */ + template inline constexpr explicit Rad(Unit value): Unit(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 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 operator "" _rad(long double value) { return Rad(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 operator "" _radf(long double value) { return Rad(value); } +#endif + +template inline constexpr Deg::Deg(Unit value): Unit(T(180)*T(value)/Math::Constants::pi()) {} +template inline constexpr Rad::Rad(Unit value): Unit(T(value)*Math::Constants::pi()/T(180)) {} + +/** @debugoperator{Magnum::Math::Rad} */ +template Corrade::Utility::Debug operator<<(Corrade::Utility::Debug debug, const Rad& 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 Corrade::Utility::Debug operator<<(Corrade::Utility::Debug debug, const Deg& 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&); +extern template Corrade::Utility::Debug MAGNUM_EXPORT operator<<(Corrade::Utility::Debug, const Deg&); +#ifndef MAGNUM_TARGET_GLES +extern template Corrade::Utility::Debug MAGNUM_EXPORT operator<<(Corrade::Utility::Debug, const Rad&); +extern template Corrade::Utility::Debug MAGNUM_EXPORT operator<<(Corrade::Utility::Debug, const Deg&); +#endif +#endif + +}} + +#endif diff --git a/src/Math/CMakeLists.txt b/src/Math/CMakeLists.txt index 813349648..e6d909a0c 100644 --- a/src/Math/CMakeLists.txt +++ b/src/Math/CMakeLists.txt @@ -1,4 +1,5 @@ set(MagnumMath_HEADERS + Angle.h BoolVector.h Complex.h Constants.h diff --git a/src/Math/Math.h b/src/Math/Math.h index 88557f8d1..31feb02b3 100644 --- a/src/Math/Math.h +++ b/src/Math/Math.h @@ -42,6 +42,8 @@ template class Quaternion; template class RectangularMatrix; template class, class> class Unit; +template class Deg; +template class Rad; template class Vector; template class Vector2; diff --git a/src/Math/Test/AngleTest.cpp b/src/Math/Test/AngleTest.cpp new file mode 100644 index 000000000..dae6cef93 --- /dev/null +++ b/src/Math/Test/AngleTest.cpp @@ -0,0 +1,115 @@ +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + 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 +#include + +#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 Deg; +typedef Math::Rad Rad; +typedef Math::Deg Degd; +typedef Math::Rad 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::value)); + CORRADE_VERIFY((std::is_same::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::value)); + CORRADE_VERIFY((std::is_same::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) diff --git a/src/Math/Test/CMakeLists.txt b/src/Math/Test/CMakeLists.txt index e6bee9a10..c76ea26fb 100644 --- a/src/Math/Test/CMakeLists.txt +++ b/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)