diff --git a/ApplicationLibCode/Application/Tools/CMakeLists_files.cmake b/ApplicationLibCode/Application/Tools/CMakeLists_files.cmake index d2c09146235..2fc8f9cf845 100644 --- a/ApplicationLibCode/Application/Tools/CMakeLists_files.cmake +++ b/ApplicationLibCode/Application/Tools/CMakeLists_files.cmake @@ -56,6 +56,8 @@ set(SOURCE_GROUP_HEADER_FILES ${CMAKE_CURRENT_LIST_DIR}/RiaProjectBackupTools.h ${CMAKE_CURRENT_LIST_DIR}/RiaQuantityInfoTools.h ${CMAKE_CURRENT_LIST_DIR}/RiaHashTools.h + ${CMAKE_CURRENT_LIST_DIR}/RiaAngleUtils.h + ${CMAKE_CURRENT_LIST_DIR}/RiaAngleUtils.inl ) set(SOURCE_GROUP_SOURCE_FILES @@ -108,6 +110,7 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RiaFileLogger.cpp ${CMAKE_CURRENT_LIST_DIR}/RiaProjectBackupTools.cpp ${CMAKE_CURRENT_LIST_DIR}/RiaQuantityInfoTools.cpp + ${CMAKE_CURRENT_LIST_DIR}/RiaAngleUtils.cpp ) list(APPEND CODE_SOURCE_FILES ${SOURCE_GROUP_SOURCE_FILES}) diff --git a/ApplicationLibCode/Application/Tools/RiaAngleUtils.cpp b/ApplicationLibCode/Application/Tools/RiaAngleUtils.cpp new file mode 100644 index 00000000000..f5e8d8641dc --- /dev/null +++ b/ApplicationLibCode/Application/Tools/RiaAngleUtils.cpp @@ -0,0 +1,69 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2025 Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight 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 General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RiaAngleUtils.h" + +#include + +namespace Ria +{ + +// Explicit instantiation of commonly used types +template class Degrees; +template class Degrees; +template class Radians; +template class Radians; + +// Explicit instantiation of coordinate conversion functions +template cvf::Vector3 CoordinateConverter::cylindricalToCartesian( float, const Radians&, float ); +template cvf::Vector3 CoordinateConverter::cylindricalToCartesian( double, const Radians&, double ); +template cvf::Vector3 CoordinateConverter::cylindricalToCartesian( float, const Degrees&, float ); +template cvf::Vector3 CoordinateConverter::cylindricalToCartesian( double, const Degrees&, double ); +template cvf::Vector3 CoordinateConverter::cylindricalToCartesian( const CylindricalCoordinate& ); +template cvf::Vector3 CoordinateConverter::cylindricalToCartesian( const CylindricalCoordinate& ); +template CylindricalCoordinate CoordinateConverter::cartesianToCylindrical( const cvf::Vector3& ); +template CylindricalCoordinate CoordinateConverter::cartesianToCylindrical( const cvf::Vector3& ); +template CylindricalCoordinate CoordinateConverter::cartesianToCylindrical( float, float, float ); +template CylindricalCoordinate CoordinateConverter::cartesianToCylindrical( double, double, double ); + +// Explicit instantiation of trigonometric functions +template float sin( const Degrees& ); +template double sin( const Degrees& ); +template float sin( const Radians& ); +template double sin( const Radians& ); + +template float cos( const Degrees& ); +template double cos( const Degrees& ); +template float cos( const Radians& ); +template double cos( const Radians& ); + +template float tan( const Degrees& ); +template double tan( const Degrees& ); +template float tan( const Radians& ); +template double tan( const Radians& ); + +template Radians asin( float ); +template Radians asin( double ); +template Radians acos( float ); +template Radians acos( double ); +template Radians atan( float ); +template Radians atan( double ); +template Radians atan2( float, float ); +template Radians atan2( double, double ); + +} // namespace Ria diff --git a/ApplicationLibCode/Application/Tools/RiaAngleUtils.h b/ApplicationLibCode/Application/Tools/RiaAngleUtils.h new file mode 100644 index 00000000000..f4d2ac22116 --- /dev/null +++ b/ApplicationLibCode/Application/Tools/RiaAngleUtils.h @@ -0,0 +1,360 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2025 Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight 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 General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "cvfBase.h" +#include "cvfMath.h" +#include "cvfVector3.h" + +#include + +namespace Ria +{ + +//================================================================================================== +// +// Type-safe angle representation classes +// +//================================================================================================== + +template +class Radians; + +template +class Degrees +{ +public: + constexpr Degrees() + : m_value( T{} ) + { + } + constexpr explicit Degrees( T value ) + : m_value( value ) + { + } + constexpr Degrees( const Degrees& other ) + : m_value( other.m_value ) + { + } + constexpr Degrees( const Radians& radians ); + + constexpr T value() const { return m_value; } + constexpr operator T() const { return m_value; } + + constexpr Degrees& operator=( const Degrees& other ) + { + m_value = other.m_value; + return *this; + } + constexpr Degrees& operator=( const Radians& radians ); + + constexpr Degrees operator+( const Degrees& other ) const { return Degrees( m_value + other.m_value ); } + constexpr Degrees operator-( const Degrees& other ) const { return Degrees( m_value - other.m_value ); } + constexpr Degrees operator*( T scalar ) const { return Degrees( m_value * scalar ); } + constexpr Degrees operator/( T scalar ) const { return Degrees( m_value / scalar ); } + constexpr Degrees operator-() const { return Degrees( -m_value ); } + + constexpr Degrees& operator+=( const Degrees& other ) + { + m_value += other.m_value; + return *this; + } + constexpr Degrees& operator-=( const Degrees& other ) + { + m_value -= other.m_value; + return *this; + } + constexpr Degrees& operator*=( T scalar ) + { + m_value *= scalar; + return *this; + } + constexpr Degrees& operator/=( T scalar ) + { + m_value /= scalar; + return *this; + } + + constexpr bool operator==( const Degrees& other ) const { return m_value == other.m_value; } + constexpr bool operator!=( const Degrees& other ) const { return m_value != other.m_value; } + constexpr bool operator<( const Degrees& other ) const { return m_value < other.m_value; } + constexpr bool operator<=( const Degrees& other ) const { return m_value <= other.m_value; } + constexpr bool operator>( const Degrees& other ) const { return m_value > other.m_value; } + constexpr bool operator>=( const Degrees& other ) const { return m_value >= other.m_value; } + + Degrees normalized() const; + +private: + T m_value; +}; + +template +class Radians +{ +public: + constexpr Radians() + : m_value( T{} ) + { + } + constexpr explicit Radians( T value ) + : m_value( value ) + { + } + constexpr Radians( const Radians& other ) + : m_value( other.m_value ) + { + } + constexpr Radians( const Degrees& degrees ); + + constexpr T value() const { return m_value; } + constexpr operator T() const { return m_value; } + + constexpr Radians& operator=( const Radians& other ) + { + m_value = other.m_value; + return *this; + } + constexpr Radians& operator=( const Degrees& degrees ); + + constexpr Radians operator+( const Radians& other ) const { return Radians( m_value + other.m_value ); } + constexpr Radians operator-( const Radians& other ) const { return Radians( m_value - other.m_value ); } + constexpr Radians operator*( T scalar ) const { return Radians( m_value * scalar ); } + constexpr Radians operator/( T scalar ) const { return Radians( m_value / scalar ); } + constexpr Radians operator-() const { return Radians( -m_value ); } + + constexpr Radians& operator+=( const Radians& other ) + { + m_value += other.m_value; + return *this; + } + constexpr Radians& operator-=( const Radians& other ) + { + m_value -= other.m_value; + return *this; + } + constexpr Radians& operator*=( T scalar ) + { + m_value *= scalar; + return *this; + } + constexpr Radians& operator/=( T scalar ) + { + m_value /= scalar; + return *this; + } + + constexpr bool operator==( const Radians& other ) const { return m_value == other.m_value; } + constexpr bool operator!=( const Radians& other ) const { return m_value != other.m_value; } + constexpr bool operator<( const Radians& other ) const { return m_value < other.m_value; } + constexpr bool operator<=( const Radians& other ) const { return m_value <= other.m_value; } + constexpr bool operator>( const Radians& other ) const { return m_value > other.m_value; } + constexpr bool operator>=( const Radians& other ) const { return m_value >= other.m_value; } + + Radians normalized() const; + +private: + T m_value; +}; + +// Cross-conversions between Degrees and Radians +template +constexpr Degrees::Degrees( const Radians& radians ) + : m_value( cvf::Math::toDegrees( radians.value() ) ) +{ +} + +template +constexpr Degrees& Degrees::operator=( const Radians& radians ) +{ + m_value = cvf::Math::toDegrees( radians.value() ); + return *this; +} + +template +constexpr Radians::Radians( const Degrees& degrees ) + : m_value( cvf::Math::toRadians( degrees.value() ) ) +{ +} + +template +constexpr Radians& Radians::operator=( const Degrees& degrees ) +{ + m_value = cvf::Math::toRadians( degrees.value() ); + return *this; +} + +// Scalar multiplication (scalar * angle) +template +constexpr Degrees operator*( T scalar, const Degrees& degrees ) +{ + return degrees * scalar; +} + +template +constexpr Radians operator*( T scalar, const Radians& radians ) +{ + return radians * scalar; +} + +// Type aliases +using Degreesf = Degrees; +using Degreesd = Degrees; +using Radiansf = Radians; +using Radiansd = Radians; + +//================================================================================================== +// +// Trigonometric functions for typed angles +// +//================================================================================================== + +template +T sin( const Degrees& angle ) +{ + return cvf::Math::sin( cvf::Math::toRadians( angle.value() ) ); +} + +template +T sin( const Radians& angle ) +{ + return cvf::Math::sin( angle.value() ); +} + +template +T cos( const Degrees& angle ) +{ + return cvf::Math::cos( cvf::Math::toRadians( angle.value() ) ); +} + +template +T cos( const Radians& angle ) +{ + return cvf::Math::cos( angle.value() ); +} + +template +T tan( const Degrees& angle ) +{ + return cvf::Math::tan( cvf::Math::toRadians( angle.value() ) ); +} + +template +T tan( const Radians& angle ) +{ + return cvf::Math::tan( angle.value() ); +} + +template +Radians asin( T value ) +{ + return Radians( cvf::Math::asin( value ) ); +} + +template +Radians acos( T value ) +{ + return Radians( cvf::Math::acos( value ) ); +} + +template +Radians atan( T value ) +{ + return Radians( cvf::Math::atan( value ) ); +} + +template +Radians atan2( T y, T x ) +{ + return Radians( std::atan2( y, x ) ); +} + +//================================================================================================== +// +// Coordinate system conversion utilities +// +//================================================================================================== + +struct CylindricalCoordinate +{ + template + CylindricalCoordinate( T radius, const Radians& angle, T height ) + : radius( radius ) + , angle( angle.value() ) + , height( height ) + { + } + + template + CylindricalCoordinate( T radius, const Degrees& angle, T height ) + : radius( radius ) + , angle( cvf::Math::toRadians( angle.value() ) ) + , height( height ) + { + } + + double radius; + double angle; // In radians + double height; +}; + +class CoordinateConverter +{ +public: + template + static cvf::Vector3 cylindricalToCartesian( T radius, const Radians& angle, T height ); + + template + static cvf::Vector3 cylindricalToCartesian( T radius, const Degrees& angle, T height ); + + template + static cvf::Vector3 cylindricalToCartesian( const CylindricalCoordinate& coord ); + + template + static CylindricalCoordinate cartesianToCylindrical( const cvf::Vector3& cartesian ); + + template + static CylindricalCoordinate cartesianToCylindrical( T x, T y, T z ); +}; + +//================================================================================================== +// +// Convenience functions for creating angle literals +// +//================================================================================================== + +constexpr Degreesd operator"" _deg( long double value ) +{ + return Degreesd( static_cast( value ) ); +} +constexpr Degreesf operator"" _degf( long double value ) +{ + return Degreesf( static_cast( value ) ); +} +constexpr Radiansd operator"" _rad( long double value ) +{ + return Radiansd( static_cast( value ) ); +} +constexpr Radiansf operator"" _radf( long double value ) +{ + return Radiansf( static_cast( value ) ); +} + +} // namespace Ria + +#include "RiaAngleUtils.inl" diff --git a/ApplicationLibCode/Application/Tools/RiaAngleUtils.inl b/ApplicationLibCode/Application/Tools/RiaAngleUtils.inl new file mode 100644 index 00000000000..0f9ce268ba6 --- /dev/null +++ b/ApplicationLibCode/Application/Tools/RiaAngleUtils.inl @@ -0,0 +1,100 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2025 Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight 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 General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include +#include + +namespace Ria +{ + +//================================================================================================== +// Degrees normalization +//================================================================================================== +template +inline Degrees Degrees::normalized() const +{ + T normalized = cvf::Math::fmod( m_value, T( 360 ) ); + if ( normalized < T( 0 ) ) + { + normalized += T( 360 ); + } + return Degrees( normalized ); +} + +//================================================================================================== +// Radians normalization +//================================================================================================== +template +inline Radians Radians::normalized() const +{ + T twoPi = T( 2 ) * ( std::is_same_v ? cvf::PI_F : cvf::PI_D ); + T normalized = cvf::Math::fmod( m_value, twoPi ); + if ( normalized < T( 0 ) ) + { + normalized += twoPi; + } + return Radians( normalized ); +} + +//================================================================================================== +// Coordinate conversion implementations +//================================================================================================== + +template +inline cvf::Vector3 CoordinateConverter::cylindricalToCartesian( T radius, const Radians& angle, T height ) +{ + T x = radius * cvf::Math::cos( angle.value() ); + T y = radius * cvf::Math::sin( angle.value() ); + return cvf::Vector3( x, y, height ); +} + +template +inline cvf::Vector3 CoordinateConverter::cylindricalToCartesian( T radius, const Degrees& angle, T height ) +{ + T angleRad = cvf::Math::toRadians( angle.value() ); + T x = radius * cvf::Math::cos( angleRad ); + T y = radius * cvf::Math::sin( angleRad ); + return cvf::Vector3( x, y, height ); +} + +template +inline cvf::Vector3 CoordinateConverter::cylindricalToCartesian( const CylindricalCoordinate& coord ) +{ + T x = static_cast( coord.radius * cvf::Math::cos( coord.angle ) ); + T y = static_cast( coord.radius * cvf::Math::sin( coord.angle ) ); + T z = static_cast( coord.height ); + return cvf::Vector3( x, y, z ); +} + +template +inline CylindricalCoordinate CoordinateConverter::cartesianToCylindrical( const cvf::Vector3& cartesian ) +{ + return cartesianToCylindrical( cartesian.x(), cartesian.y(), cartesian.z() ); +} + +template +inline CylindricalCoordinate CoordinateConverter::cartesianToCylindrical( T x, T y, T z ) +{ + double radius = cvf::Math::sqrt( static_cast( x * x + y * y ) ); + double angle = std::atan2( static_cast( y ), static_cast( x ) ); + double height = static_cast( z ); + + return CylindricalCoordinate( radius, Radiansd( angle ), height ); +} + +} // namespace Ria diff --git a/ApplicationLibCode/UnitTests/CMakeLists.txt b/ApplicationLibCode/UnitTests/CMakeLists.txt index c790ab1fa1b..03a8286b49b 100644 --- a/ApplicationLibCode/UnitTests/CMakeLists.txt +++ b/ApplicationLibCode/UnitTests/CMakeLists.txt @@ -113,6 +113,7 @@ set(SOURCE_UNITTEST_FILES ${CMAKE_CURRENT_LIST_DIR}/RimTools-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RifSurfio-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RifRmsWellPathReader-Test.cpp + ${CMAKE_CURRENT_LIST_DIR}/RiaAngleUtils-Test.cpp ) if(RESINSIGHT_ENABLE_GRPC) diff --git a/ApplicationLibCode/UnitTests/RiaAngleUtils-Test.cpp b/ApplicationLibCode/UnitTests/RiaAngleUtils-Test.cpp new file mode 100644 index 00000000000..9f5a973916b --- /dev/null +++ b/ApplicationLibCode/UnitTests/RiaAngleUtils-Test.cpp @@ -0,0 +1,206 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2025 Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight 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 General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "gtest/gtest.h" + +#include "RiaAngleUtils.h" + +using namespace Ria; + +TEST( RiaAngleUtilsTest, DegreesBasicOperations ) +{ + Degreesd d1( 90.0 ); + Degreesd d2( 45.0 ); + + EXPECT_DOUBLE_EQ( 90.0, d1.value() ); + EXPECT_DOUBLE_EQ( 45.0, d2.value() ); + + auto sum = d1 + d2; + EXPECT_DOUBLE_EQ( 135.0, sum.value() ); + + auto diff = d1 - d2; + EXPECT_DOUBLE_EQ( 45.0, diff.value() ); + + auto scaled = d1 * 2.0; + EXPECT_DOUBLE_EQ( 180.0, scaled.value() ); + + auto divided = d1 / 2.0; + EXPECT_DOUBLE_EQ( 45.0, divided.value() ); +} + +TEST( RiaAngleUtilsTest, RadiansBasicOperations ) +{ + Radiansd r1( cvf::PI_D / 2.0 ); // 90 degrees + Radiansd r2( cvf::PI_D / 4.0 ); // 45 degrees + + EXPECT_DOUBLE_EQ( cvf::PI_D / 2.0, r1.value() ); + EXPECT_DOUBLE_EQ( cvf::PI_D / 4.0, r2.value() ); + + auto sum = r1 + r2; + EXPECT_DOUBLE_EQ( 3.0 * cvf::PI_D / 4.0, sum.value() ); + + auto diff = r1 - r2; + EXPECT_DOUBLE_EQ( cvf::PI_D / 4.0, diff.value() ); + + auto scaled = r1 * 2.0; + EXPECT_DOUBLE_EQ( cvf::PI_D, scaled.value() ); + + auto divided = r1 / 2.0; + EXPECT_DOUBLE_EQ( cvf::PI_D / 4.0, divided.value() ); +} + +TEST( RiaAngleUtilsTest, DegreesRadiansConversion ) +{ + Degreesd degrees( 180.0 ); + Radiansd radians( degrees ); + double diff1 = cvf::Math::abs( cvf::PI_D - radians.value() ); + EXPECT_LT( diff1, 1e-10 ); + + Radiansd piRadians( cvf::PI_D ); + Degreesd fromRadians( piRadians ); + double diff2 = cvf::Math::abs( 180.0 - fromRadians.value() ); + EXPECT_LT( diff2, 1e-10 ); + + Degreesd deg90( 90.0 ); + Radiansd rad90( deg90 ); + double diff3 = cvf::Math::abs( cvf::PI_D / 2.0 - rad90.value() ); + EXPECT_LT( diff3, 1e-10 ); + + Degreesd original( 45.0 ); + Radiansd converted( original ); + Degreesd backToOriginal( converted ); + double diff4 = cvf::Math::abs( 45.0 - backToOriginal.value() ); + EXPECT_LT( diff4, 1e-10 ); +} + +TEST( RiaAngleUtilsTest, TrigonometricFunctions ) +{ + Degreesd deg90( 90.0 ); + double sin90 = sin( deg90 ); + double cos90 = cos( deg90 ); + EXPECT_LT( cvf::Math::abs( 1.0 - sin90 ), 1e-10 ); + EXPECT_LT( cvf::Math::abs( 0.0 - cos90 ), 1e-10 ); + + Degreesd deg45( 45.0 ); + double sin45 = sin( deg45 ); + double cos45 = cos( deg45 ); + double tan45 = tan( deg45 ); + EXPECT_LT( cvf::Math::abs( cvf::SQRT1_2_D - sin45 ), 1e-10 ); + EXPECT_LT( cvf::Math::abs( cvf::SQRT1_2_D - cos45 ), 1e-10 ); + EXPECT_LT( cvf::Math::abs( 1.0 - tan45 ), 1e-10 ); + + Radiansd radPiOver2( cvf::PI_D / 2.0 ); + double sinPi2 = sin( radPiOver2 ); + double cosPi2 = cos( radPiOver2 ); + EXPECT_LT( cvf::Math::abs( 1.0 - sinPi2 ), 1e-10 ); + EXPECT_LT( cvf::Math::abs( 0.0 - cosPi2 ), 1e-10 ); +} + +TEST( RiaAngleUtilsTest, CylindricalToCartesianConversion ) +{ + double radius = 5.0; + Radiansd angle( cvf::PI_D / 4.0 ); // 45 degrees + double height = 10.0; + + auto cartesian = CoordinateConverter::cylindricalToCartesian( radius, angle, height ); + + double expectedX = radius * cvf::SQRT1_2_D; + double expectedY = radius * cvf::SQRT1_2_D; + + EXPECT_LT( cvf::Math::abs( expectedX - cartesian.x() ), 1e-10 ); + EXPECT_LT( cvf::Math::abs( expectedY - cartesian.y() ), 1e-10 ); + EXPECT_LT( cvf::Math::abs( height - cartesian.z() ), 1e-10 ); + + Degreesd angleDeg( 45.0 ); + auto cartesianFromDeg = CoordinateConverter::cylindricalToCartesian( radius, angleDeg, height ); + + EXPECT_LT( cvf::Math::abs( cartesian.x() - cartesianFromDeg.x() ), 1e-10 ); + EXPECT_LT( cvf::Math::abs( cartesian.y() - cartesianFromDeg.y() ), 1e-10 ); + EXPECT_LT( cvf::Math::abs( cartesian.z() - cartesianFromDeg.z() ), 1e-10 ); +} + +TEST( RiaAngleUtilsTest, CartesianToCylindricalConversion ) +{ + cvf::Vec3d cartesian( 3.0, 4.0, 5.0 ); + + auto cylindrical = CoordinateConverter::cartesianToCylindrical( cartesian ); + + EXPECT_LT( cvf::Math::abs( 5.0 - cylindrical.radius ), 1e-10 ); // sqrt(3^2 + 4^2) = 5 + EXPECT_LT( cvf::Math::abs( std::atan2( 4.0, 3.0 ) - cylindrical.angle ), 1e-10 ); + EXPECT_LT( cvf::Math::abs( 5.0 - cylindrical.height ), 1e-10 ); + + cvf::Vec3d backToCartesian = CoordinateConverter::cylindricalToCartesian( cylindrical ); + + EXPECT_LT( cvf::Math::abs( cartesian.x() - backToCartesian.x() ), 1e-10 ); + EXPECT_LT( cvf::Math::abs( cartesian.y() - backToCartesian.y() ), 1e-10 ); + EXPECT_LT( cvf::Math::abs( cartesian.z() - backToCartesian.z() ), 1e-10 ); +} + +TEST( RiaAngleUtilsTest, ComparisonOperators ) +{ + Degreesd deg45( 45.0 ); + Degreesd deg90( 90.0 ); + Degreesd deg45_2( 45.0 ); + + EXPECT_TRUE( deg45 == deg45_2 ); + EXPECT_FALSE( deg45 == deg90 ); + EXPECT_TRUE( deg45 != deg90 ); + EXPECT_FALSE( deg45 != deg45_2 ); + + EXPECT_TRUE( deg45 < deg90 ); + EXPECT_FALSE( deg90 < deg45 ); + EXPECT_TRUE( deg45 <= deg90 ); + EXPECT_TRUE( deg45 <= deg45_2 ); + + EXPECT_TRUE( deg90 > deg45 ); + EXPECT_FALSE( deg45 > deg90 ); + EXPECT_TRUE( deg90 >= deg45 ); + EXPECT_TRUE( deg45 >= deg45_2 ); +} + +TEST( RiaAngleUtilsTest, FloatPrecision ) +{ + Degreesf degf( 90.0f ); + Radiansf radf( degf ); + + float expectedRad = cvf::PI_F / 2.0f; + EXPECT_LT( cvf::Math::abs( expectedRad - radf.value() ), 1e-6f ); + + float sinResult = sin( degf ); + EXPECT_LT( cvf::Math::abs( 1.0f - sinResult ), 1e-6f ); + + cvf::Vec3f cartesianf = CoordinateConverter::cylindricalToCartesian( 5.0f, degf, 10.0f ); + EXPECT_LT( cvf::Math::abs( 0.0f - cartesianf.x() ), 1e-6f ); + EXPECT_LT( cvf::Math::abs( 5.0f - cartesianf.y() ), 1e-6f ); + EXPECT_LT( cvf::Math::abs( 10.0f - cartesianf.z() ), 1e-6f ); +} + +TEST( RiaAngleUtilsTest, AngleLiterals ) +{ + auto deg90 = 90.0_deg; + EXPECT_LT( cvf::Math::abs( 90.0 - deg90.value() ), 1e-10 ); + + auto degFloat = 45.0_degf; + EXPECT_LT( cvf::Math::abs( 45.0f - degFloat.value() ), 1e-6f ); + + auto radPi = 3.14159265_rad; + EXPECT_LT( cvf::Math::abs( 3.14159265 - radPi.value() ), 1e-8 ); + + auto radFloat = 1.57079633_radf; + EXPECT_LT( cvf::Math::abs( 1.57079633f - radFloat.value() ), 1e-6f ); +}