From 8fba3d253db76f1c37db70aa2d9b0167b96f623f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Del=20Pino?= <stephane.delpino44@gmail.com> Date: Thu, 7 Oct 2021 18:53:23 +0200 Subject: [PATCH] Add affine transformation (R^d -> R^d) --- src/geometry/AffineTransformation.hpp | 78 ++++++++++++++++++++++++ tests/CMakeLists.txt | 1 + tests/test_AffineTransformation.cpp | 88 +++++++++++++++++++++++++++ 3 files changed, 167 insertions(+) create mode 100644 src/geometry/AffineTransformation.hpp create mode 100644 tests/test_AffineTransformation.cpp diff --git a/src/geometry/AffineTransformation.hpp b/src/geometry/AffineTransformation.hpp new file mode 100644 index 000000000..e7179ef5d --- /dev/null +++ b/src/geometry/AffineTransformation.hpp @@ -0,0 +1,78 @@ +#ifndef AFFINE_TRANSFORMATION_HPP +#define AFFINE_TRANSFORMATION_HPP + +#include <algebra/TinyMatrix.hpp> +#include <algebra/TinyVector.hpp> + +#include <array> + +template <size_t Dimension> +class AffineTransformation +{ + private: + constexpr static size_t NumberOfPoints = Dimension + 1; + + TinyMatrix<Dimension> m_jacobian; + TinyVector<Dimension> m_shift; + double m_jacobian_determinant; + + public: + PUGS_INLINE + TinyVector<Dimension> + operator()(const TinyVector<Dimension>& x) const + { + return m_jacobian * x + m_shift; + } + + double + jacobianDeterminant() const + { + return m_jacobian_determinant; + } + + PUGS_INLINE + AffineTransformation(const std::array<TinyVector<Dimension>, NumberOfPoints>& image_nodes) + { + static_assert(Dimension >= 1 and Dimension <= 3, "invalid dimension"); + if constexpr (Dimension == 1) { + const TinyVector<Dimension>& a = image_nodes[0]; + const TinyVector<Dimension>& b = image_nodes[1]; + + m_jacobian(0, 0) = 0.5 * (b[0] - a[0]); + + m_shift[0] = 0.5 * (a[0] + b[0]); + } else if constexpr (Dimension == 2) { + const TinyVector<Dimension>& a = image_nodes[0]; + const TinyVector<Dimension>& b = image_nodes[1]; + const TinyVector<Dimension>& c = image_nodes[2]; + + for (size_t i = 0; i < Dimension; ++i) { + m_jacobian(i, 0) = 0.5 * (b[i] - a[i]); + m_jacobian(i, 1) = 0.5 * (c[i] - a[i]); + + m_shift[i] = 0.5 * (b[i] + c[i]); + } + } else { + static_assert(Dimension == 3); + + const TinyVector<Dimension>& a = image_nodes[0]; + const TinyVector<Dimension>& b = image_nodes[1]; + const TinyVector<Dimension>& c = image_nodes[2]; + const TinyVector<Dimension>& d = image_nodes[3]; + + for (size_t i = 0; i < Dimension; ++i) { + m_jacobian(i, 0) = 0.5 * (b[i] - a[i]); + m_jacobian(i, 1) = 0.5 * (c[i] - a[i]); + m_jacobian(i, 2) = 0.5 * (d[i] - a[i]); + + m_shift[i] = 0.5 * (b[i] + c[i] + d[i] - a[i]); + } + } + + m_jacobian_determinant = det(m_jacobian); + } + + ~AffineTransformation() = default; +}; + +#endif // AFFINE_TRANSFORMATION_HPP diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b5d57ecad..5d0fa65b1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -9,6 +9,7 @@ add_executable (unit_tests test_AffectationProcessor.cpp test_AffectationToStringProcessor.cpp test_AffectationToTupleProcessor.cpp + test_AffineTransformation.cpp test_Array.cpp test_ArraySubscriptProcessor.cpp test_ASTBuilder.cpp diff --git a/tests/test_AffineTransformation.cpp b/tests/test_AffineTransformation.cpp new file mode 100644 index 000000000..cce135741 --- /dev/null +++ b/tests/test_AffineTransformation.cpp @@ -0,0 +1,88 @@ +#include <catch2/catch_approx.hpp> +#include <catch2/catch_test_macros.hpp> + +#include <geometry/AffineTransformation.hpp> + +// clazy:excludeall=non-pod-global-static + +TEST_CASE("AffineTransformation", "[geometry]") +{ + SECTION("1D") + { + using R1 = TinyVector<1>; + + const R1 a = 0.3; + const R1 b = 2.7; + + const std::array points = {a, b}; + + const AffineTransformation<1> t(points); + + REQUIRE(t(-1)[0] == Catch::Approx(0.3)); + REQUIRE(t(0)[0] == Catch::Approx(1.5)); + REQUIRE(t(1)[0] == Catch::Approx(2.7)); + + REQUIRE(t.jacobianDeterminant() == Catch::Approx(1.2)); + } + + SECTION("2D") + { + using R2 = TinyVector<2>; + + const R2 a = {1, 2}; + const R2 b = {3, 1}; + const R2 c = {2, 5}; + + const std::array points = {a, b, c}; + const AffineTransformation<2> t(points); + + REQUIRE(t({-1, -1})[0] == Catch::Approx(1)); + REQUIRE(t({-1, -1})[1] == Catch::Approx(2)); + + REQUIRE(t({+1, -1})[0] == Catch::Approx(3)); + REQUIRE(t({+1, -1})[1] == Catch::Approx(1)); + + REQUIRE(t({-1, +1})[0] == Catch::Approx(2)); + REQUIRE(t({-1, +1})[1] == Catch::Approx(5)); + + REQUIRE(t({-1. / 3, -1. / 3})[0] == Catch::Approx(2)); + REQUIRE(t({-1. / 3, -1. / 3})[1] == Catch::Approx(8. / 3)); + + REQUIRE(t.jacobianDeterminant() == Catch::Approx(7. / 4)); + } + + SECTION("3D") + { + using R3 = TinyVector<3>; + + const R3 a = {1, 2, 1}; + const R3 b = {3, 1, 3}; + const R3 c = {2, 5, 2}; + const R3 d = {2, 3, 4}; + + const std::array points = {a, b, c, d}; + const AffineTransformation<3> t(points); + + REQUIRE(t({-1, -1, -1})[0] == Catch::Approx(1)); + REQUIRE(t({-1, -1, -1})[1] == Catch::Approx(2)); + REQUIRE(t({-1, -1, -1})[2] == Catch::Approx(1)); + + REQUIRE(t({+1, -1, -1})[0] == Catch::Approx(3)); + REQUIRE(t({+1, -1, -1})[1] == Catch::Approx(1)); + REQUIRE(t({+1, -1, -1})[2] == Catch::Approx(3)); + + REQUIRE(t({-1, +1, -1})[0] == Catch::Approx(2)); + REQUIRE(t({-1, +1, -1})[1] == Catch::Approx(5)); + REQUIRE(t({-1, +1, -1})[2] == Catch::Approx(2)); + + REQUIRE(t({-1, -1, +1})[0] == Catch::Approx(2)); + REQUIRE(t({-1, -1, +1})[1] == Catch::Approx(3)); + REQUIRE(t({-1, -1, +1})[2] == Catch::Approx(4)); + + REQUIRE(t({-0.5, -0.5, -0.5})[0] == Catch::Approx(2)); + REQUIRE(t({-0.5, -0.5, -0.5})[1] == Catch::Approx(11. / 4)); + REQUIRE(t({-0.5, -0.5, -0.5})[2] == Catch::Approx(2.5)); + + REQUIRE(t.jacobianDeterminant() == Catch::Approx(7. / 4)); + } +} -- GitLab