diff --git a/include/geode/geometry/frame_transform.hpp b/include/geode/geometry/frame_transform.hpp new file mode 100644 index 000000000..2293767fc --- /dev/null +++ b/include/geode/geometry/frame_transform.hpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2019 - 2025 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +#pragma once + +#include + +#include + +namespace geode +{ + FORWARD_DECLARATION_DIMENSION_CLASS( Frame ); + FORWARD_DECLARATION_DIMENSION_CLASS( Point ); + FORWARD_DECLARATION_DIMENSION_CLASS( Vector ); +} // namespace geode + +namespace geode +{ + template < index_t dimension > + class FrameTransform + { + public: + FrameTransform( + const Frame< dimension >& from, const Frame< dimension >& to ); + virtual ~FrameTransform(); + + [[nodiscard]] virtual local_index_t direction( + local_index_t index ) const; + + [[nodiscard]] virtual signed_index_t orientation( + local_index_t index ) const; + + [[nodiscard]] virtual Frame< dimension > apply( + const Frame< dimension >& frame ) const; + + [[nodiscard]] virtual Vector< dimension > apply( + const Vector< dimension >& vector ) const; + + [[nodiscard]] virtual Point< dimension > apply( + const Point< dimension >& point ) const; + + private: + IMPLEMENTATION_MEMBER( impl_ ); + }; + ALIAS_2D_AND_3D( FrameTransform ); +} // namespace geode \ No newline at end of file diff --git a/include/geode/geometry/normal_frame_transform.hpp b/include/geode/geometry/normal_frame_transform.hpp new file mode 100644 index 000000000..21dbcfa84 --- /dev/null +++ b/include/geode/geometry/normal_frame_transform.hpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2019 - 2025 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +#pragma once + +#include + +#include +#include + +namespace geode +{ + FORWARD_DECLARATION_DIMENSION_CLASS( Frame ); + FORWARD_DECLARATION_DIMENSION_CLASS( Point ); + FORWARD_DECLARATION_DIMENSION_CLASS( Vector ); +} // namespace geode + +namespace geode +{ + template < index_t dimension > + class NormalFrameTransform : public FrameTransform< dimension > + { + public: + NormalFrameTransform( + const Frame< dimension >& from, const Frame< dimension >& to ); + ~NormalFrameTransform(); + + [[nodiscard]] Frame< dimension > apply( + const Frame< dimension >& frame ) const final; + + [[nodiscard]] Vector< dimension > apply( + const Vector< dimension >& vector ) const final; + + [[nodiscard]] Point< dimension > apply( + const Point< dimension >& point ) const final; + + private: + IMPLEMENTATION_MEMBER( impl_ ); + }; + ALIAS_2D_AND_3D( NormalFrameTransform ); +} // namespace geode \ No newline at end of file diff --git a/src/geode/geometry/CMakeLists.txt b/src/geode/geometry/CMakeLists.txt index 954bdb19e..d37660496 100644 --- a/src/geode/geometry/CMakeLists.txt +++ b/src/geode/geometry/CMakeLists.txt @@ -42,11 +42,13 @@ add_geode_library( "distance.cpp" "dynamic_nn_search.cpp" "frame.cpp" + "frame_transform.cpp" "information.cpp" "intersection.cpp" "intersection_detection.cpp" "mensuration.cpp" "nn_search.cpp" + "normal_frame_transform.cpp" "perpendicular.cpp" "points_sort.cpp" "position.cpp" @@ -77,11 +79,13 @@ add_geode_library( "distance.hpp" "dynamic_nn_search.hpp" "frame.hpp" + "frame_transform.hpp" "information.hpp" "intersection.hpp" "intersection_detection.hpp" "mensuration.hpp" "nn_search.hpp" + "normal_frame_transform.hpp" "perpendicular.hpp" "point.hpp" "points_sort.hpp" diff --git a/src/geode/geometry/frame_transform.cpp b/src/geode/geometry/frame_transform.cpp new file mode 100644 index 000000000..835ddd29f --- /dev/null +++ b/src/geode/geometry/frame_transform.cpp @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2019 - 2025 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include + +#include +#include + +#include +#include + +namespace geode +{ + template < index_t dimension > + class FrameTransform< dimension >::Impl + { + public: + Impl( const Frame< dimension >& from, const Frame< dimension >& to ) + { + for( const auto d_from : LRange{ dimension } ) + { + const auto from_vector = from.direction( d_from ).normalize(); + double max_dot{ 0 }; + for( const auto d_to : LRange{ dimension } ) + { + const auto to_vector = to.direction( d_to ).normalize(); + const auto dot = from_vector.dot( to_vector ); + const auto abs_dot = std::fabs( dot ); + if( abs_dot > max_dot ) + { + directions_[d_from] = d_to; + max_dot = abs_dot; + orientations_[d_from] = dot > 0 ? 1 : -1; + } + } + } + } + + local_index_t direction( local_index_t index ) const + { + return directions_[index]; + } + + signed_index_t orientation( local_index_t index ) const + { + return orientations_[index]; + } + + Frame< dimension > apply( const Frame< dimension >& frame ) const + { + Frame< dimension > result; + for( const auto d : LRange{ dimension } ) + { + result.set_direction( + direction( d ), frame.direction( d ) * orientation( d ) ); + } + return result; + } + + Point< dimension > apply( const Point< dimension >& point ) const + { + Point< dimension > result; + for( const auto d : LRange{ dimension } ) + { + result.set_value( + d, orientations_[d] * point.value( directions_[d] ) ); + } + return result; + } + + Vector< dimension > apply( const Vector< dimension >& vector ) const + { + Vector< dimension > result; + for( const auto d : LRange{ dimension } ) + { + result.set_value( + d, orientations_[d] * vector.value( directions_[d] ) ); + } + return result; + } + + private: + std::array< local_index_t, dimension > directions_; + std::array< signed_index_t, dimension > orientations_; + }; + + template < index_t dimension > + FrameTransform< dimension >::FrameTransform( + const Frame< dimension >& from, const Frame< dimension >& to ) + : impl_{ from, to } + { + } + + template < index_t dimension > + FrameTransform< dimension >::~FrameTransform() = default; + + template < index_t dimension > + local_index_t FrameTransform< dimension >::direction( + local_index_t index ) const + { + return impl_->direction( index ); + } + + template < index_t dimension > + signed_index_t FrameTransform< dimension >::orientation( + local_index_t index ) const + { + return impl_->orientation( index ); + } + + template < index_t dimension > + Frame< dimension > FrameTransform< dimension >::apply( + const Frame< dimension >& frame ) const + { + return impl_->apply( frame ); + } + + template < index_t dimension > + Vector< dimension > FrameTransform< dimension >::apply( + const Vector< dimension >& vector ) const + { + return impl_->apply( vector ); + } + + template < index_t dimension > + Point< dimension > FrameTransform< dimension >::apply( + const Point< dimension >& point ) const + { + return impl_->apply( point ); + } + template opengeode_geometry_api class FrameTransform< 2 >; + template opengeode_geometry_api class FrameTransform< 3 >; +} // namespace geode diff --git a/src/geode/geometry/normal_frame_transform.cpp b/src/geode/geometry/normal_frame_transform.cpp new file mode 100644 index 000000000..c3dec0cfd --- /dev/null +++ b/src/geode/geometry/normal_frame_transform.cpp @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2019 - 2025 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include +#include + +#include +#include +#include + +namespace geode +{ + template < index_t dimension > + class NormalFrameTransform< dimension >::Impl + { + public: + Impl( const NormalFrameTransform< dimension >& normal_frame_transform, + const Frame< dimension >& from, + const Frame< dimension >& to ) + { + for( const auto d_from : LRange{ dimension } ) + { + directions_[d_from] = + normal_frame_transform.direction( d_from ); + orientations_[d_from] = + normal_frame_transform.orientation( d_from ); + magnitudes_[d_from] = + to.direction( normal_frame_transform.direction( d_from ) ) + .length() + / from.direction( d_from ).length(); + } + } + + Frame< dimension > apply( const Frame< dimension >& frame ) const + { + Frame< dimension > result; + for( const auto d : LRange{ dimension } ) + { + result.set_direction( directions_[d], + frame.direction( d ) * orientations_[d] * magnitudes_[d] ); + } + return result; + } + + Point< dimension > apply( const Point< dimension >& point ) const + { + Point< dimension > result; + for( const auto d : LRange{ dimension } ) + { + result.set_value( d, orientations_[d] + * point.value( directions_[d] ) + * magnitudes_[d] ); + } + return result; + } + + Vector< dimension > apply( const Vector< dimension >& vector ) const + { + Vector< dimension > result; + for( const auto d : LRange{ dimension } ) + { + result.set_value( d, orientations_[d] + * vector.value( directions_[d] ) + * magnitudes_[d] ); + } + return result; + } + + private: + std::array< local_index_t, dimension > directions_; + std::array< signed_index_t, dimension > orientations_; + std::array< double, dimension > magnitudes_; + }; + + template < index_t dimension > + NormalFrameTransform< dimension >::NormalFrameTransform( + const Frame< dimension >& from, const Frame< dimension >& to ) + : FrameTransform< dimension >( from, to ), impl_{ *this, from, to } + { + } + + template < index_t dimension > + NormalFrameTransform< dimension >::~NormalFrameTransform() = default; + + template < index_t dimension > + Frame< dimension > NormalFrameTransform< dimension >::apply( + const Frame< dimension >& frame ) const + { + return impl_->apply( frame ); + } + + template < index_t dimension > + Vector< dimension > NormalFrameTransform< dimension >::apply( + const Vector< dimension >& vector ) const + { + return impl_->apply( vector ); + } + + template < index_t dimension > + Point< dimension > NormalFrameTransform< dimension >::apply( + const Point< dimension >& point ) const + { + return impl_->apply( point ); + } + template opengeode_geometry_api class NormalFrameTransform< 2 >; + template opengeode_geometry_api class NormalFrameTransform< 3 >; +} // namespace geode diff --git a/tests/geometry/CMakeLists.txt b/tests/geometry/CMakeLists.txt index c62766c0e..ca4fd469b 100644 --- a/tests/geometry/CMakeLists.txt +++ b/tests/geometry/CMakeLists.txt @@ -92,6 +92,14 @@ add_geode_test( ${PROJECT_NAME}::basic ${PROJECT_NAME}::geometry ) + +add_geode_test( + SOURCE "test-normal-frame-transform.cpp" + DEPENDENCIES + ${PROJECT_NAME}::basic + ${PROJECT_NAME}::geometry +) + add_geode_test( SOURCE "test-perpendicular.cpp" DEPENDENCIES @@ -158,3 +166,4 @@ add_geode_test( ${PROJECT_NAME}::basic ${PROJECT_NAME}::geometry ) + diff --git a/tests/geometry/test-normal-frame-transform.cpp b/tests/geometry/test-normal-frame-transform.cpp new file mode 100644 index 000000000..8ae51fc84 --- /dev/null +++ b/tests/geometry/test-normal-frame-transform.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019 - 2025 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include +#include + +#include +#include +#include + +#include + +void test_normal_frame_transform() +{ + geode::Frame2D from{ { geode::Vector< 2 >{ { 1, 0 } }, + geode::Vector2D{ { 0, 1 } } } }; + geode::Frame2D to{ { geode::Vector2D{ { 1, 0 } }, + geode::Vector2D{ { 0, 0.1 } } } }; + geode::NormalFrameTransform2D frame_transform{ from, to }; + geode::Vector2D test_vector{ { 1, 1 } }; + const auto result = frame_transform.apply( test_vector ); + const auto correct_result = geode::Vector2D{ { 1, 0.1 } }; + OPENGEODE_EXCEPTION( result == correct_result, + "[Test] Wrong result for normal frame transform" ); + geode::Logger::info( "TEST SUCCESS" ); +} + +void test() +{ + test_normal_frame_transform(); +} + +OPENGEODE_TEST( "normal-frame-transform" ) \ No newline at end of file