From 2919c47e993e629d696b4303c67ffa5a6a2c3ee6 Mon Sep 17 00:00:00 2001 From: Hartmut Seichter Date: Fri, 12 Jul 2024 21:05:56 +0200 Subject: [PATCH] fixing axisangle and vector --- src/core/CMakeLists.txt | 3 +- src/core/include/pw/core/axisangle.hpp | 107 +++++------ src/core/include/pw/core/serialize.hpp | 21 ++- src/core/include/pw/core/vector.hpp | 226 +++++++++++++++-------- src/core/tests/CMakeLists.txt | 4 +- src/core/tests/pwcore_test_axisangle.cpp | 49 +++-- src/core/tests/pwcore_test_vector.cpp | 70 ++++--- 7 files changed, 283 insertions(+), 197 deletions(-) diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index bfa5ad8..13aef4c 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -58,5 +58,4 @@ target_include_directories( target_link_libraries(pwcore) -# add_subdirectory(tests) - +add_subdirectory(tests) diff --git a/src/core/include/pw/core/axisangle.hpp b/src/core/include/pw/core/axisangle.hpp index cda1b3b..719cbb9 100644 --- a/src/core/include/pw/core/axisangle.hpp +++ b/src/core/include/pw/core/axisangle.hpp @@ -8,8 +8,8 @@ * 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 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, @@ -24,83 +24,84 @@ #define PW_CORE_AXISANGLE_HPP #include +#include namespace pw { -template -struct axisangle_ { +template struct axisangle { using value_type = T; - using axis_type = vector3_; // todo - should default to UP + using axis_type = vector3; - axis_type axis{}; + axis_type axis = axis_type::basis(2); T angle{}; - static axisangle_ from_matrix(const matrix_<4,4,T>& m) - { - using std::acos; - using std::sqrt; + constexpr static auto from_matrix(const matrix& m) noexcept -> axisangle { + using std::acos; + using std::sqrt; - axisangle_ aa_res; - aa_res.angle = acos((m(0,0) + m(1,1) + m(2,2) - 1) / T(2)); - const T m2112 = m(2,1) - m(1,2); - const T m0220 = m(0,2) - m(2,0); - const T m1001 = m(1,0) - m(0,1); - // no singularity check here ... - const T mrot_denom = sqrt( m2112 * m2112 + m0220 * m0220 + m1001 * m1001 ); + // axisangle aa_res; + // aa_res.angle = acos((m[0][0]) + m[1][1] + m[2][2] - 1) / T{2}); + const T m2112 = m[2][1] - m[1][2]; + const T m0220 = m[0][2] - m[2][0]; + const T m1001 = m[1][0] - m[0][1]; - aa_res.axis.x() = m2112 / mrot_denom; - aa_res.axis.y() = m0220 / mrot_denom; - aa_res.axis.z() = m1001 / mrot_denom; + // no singularity check here ... + const T mrot_denom = + sqrt(m2112 * m2112 + m0220 * m0220 + m1001 * m1001); - return aa_res; - } + // aa_res.axis.x() = m2112 / mrot_denom; + // aa_res.axis.y() = m0220 / mrot_denom; + // aa_res.axis.z() = m1001 / mrot_denom; - matrix_<4,4,T> to_matrix() const - { + return { + .axis = axis_type{ + m2112 / mrot_denom, + m0220 / mrot_denom, + m1001 / mrot_denom + }, + .angle = acos( (m[0][0] + m[1][1] + m[2][2] - 1) / T{2} ) + }; + } + + constexpr auto to_matrix() const noexcept -> matrix { using std::cos; using std::sin; // result - matrix_<4,4,T> rot_mat; rot_mat.set_identity(); - axis_type axis_n = axis.normalized(); // always normalize + auto rot_mat { matrix::identity() }; + auto axis_n = axis.normalized(); // always normalize - const T cos_a = cos(angle); - const T sin_a = sin(angle); - const T cos_1_a = T(1) - cos_a; + const T cos_a = cos(angle); + const T sin_a = sin(angle); + const T cos_1_a = T(1) - cos_a; - rot_mat(0,0) = cos_a + axis_n.x() * axis_n.x() * cos_1_a; - rot_mat(1,1) = cos_a + axis_n.y() * axis_n.y() * cos_1_a; - rot_mat(2,2) = cos_a + axis_n.z() * axis_n.z() * cos_1_a; + rot_mat[0][0] = cos_a + axis_n.x() * axis_n.x() * cos_1_a; + rot_mat[1][1] = cos_a + axis_n.y() * axis_n.y() * cos_1_a; + rot_mat[2][2] = cos_a + axis_n.z() * axis_n.z() * cos_1_a; - T v1 = axis_n.x() * axis_n.y() * cos_1_a; - T v2 = axis_n.z() * sin_a; - rot_mat(1,0) = v1 + v2; - rot_mat(0,1) = v1 - v2; + T v1 = axis_n.x() * axis_n.y() * cos_1_a; + T v2 = axis_n.z() * sin_a; + rot_mat[1][0] = v1 + v2; + rot_mat[0][1] = v1 - v2; - v1 = axis_n.x() * axis_n.z() * cos_1_a; - v2 = axis_n.y() * sin_a; - rot_mat(2,0) = v1 - v2; - rot_mat(0,2) = v1 + v2; + v1 = axis_n.x() * axis_n.z() * cos_1_a; + v2 = axis_n.y() * sin_a; + rot_mat[2][0] = v1 - v2; + rot_mat[0][2] = v1 + v2; - v1 = axis_n.y() * axis_n.z() * cos_1_a; - v2 = axis_n.x() * sin_a; - rot_mat(2,1) = v1 + v2; - rot_mat(1,2) = v1 - v2; + v1 = axis_n.y() * axis_n.z() * cos_1_a; + v2 = axis_n.x() * sin_a; + rot_mat[2][1] = v1 + v2; + rot_mat[1][2] = v1 - v2; return rot_mat; } }; +using axisanglef = axisangle; +using axisangled = axisangle; -using axisangle = axisangle_ ; -using axisanglef = axisangle_ ; -using axisangled = axisangle_ ; - -} - - - - +} // namespace pw #endif diff --git a/src/core/include/pw/core/serialize.hpp b/src/core/include/pw/core/serialize.hpp index f9b10ec..ee8c851 100644 --- a/src/core/include/pw/core/serialize.hpp +++ b/src/core/include/pw/core/serialize.hpp @@ -23,6 +23,7 @@ #ifndef PW_CORE_SERIALIZE_HPP #define PW_CORE_SERIALIZE_HPP +#include "vector.hpp" #include @@ -33,19 +34,25 @@ namespace pw { struct serialize { - template - inline static std::string matrix(const matrix_& m) { + template + constexpr static std::string to_string(const vector& v) { + std::stringstream ss; + for(const auto& e : v) ss << e << ' '; + return ss.str(); + } + + template + constexpr static std::string to_string(const matrix& m) { std::stringstream ss; - for (int r = 0; r < m.rows;r++) { - for (int c = 0; c < m.cols;c++) { - ss << m(r,c) << " "; - } - ss << std::endl; + for (int r = 0; r < R;r++) { + ss << to_string(m[r]) << '\n'; } return ss.str(); } + + }; } diff --git a/src/core/include/pw/core/vector.hpp b/src/core/include/pw/core/vector.hpp index de03827..4a1099e 100644 --- a/src/core/include/pw/core/vector.hpp +++ b/src/core/include/pw/core/vector.hpp @@ -25,14 +25,32 @@ #include +#include + namespace pw { -template struct vector final { +template +concept Vector2 = (N == 2); - using value_type = Scalar; - static constexpr unsigned int coefficients{size}; +template +concept Vector3 = (N == 3); - Scalar v_[size]{}; +template +concept Vector4 = (N == 4); + +template struct vector final { + + using value_type = Scalar; + using size_type = decltype(N); + using difference_type = std::ptrdiff_t; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + + static constexpr size_type coefficients{N}; + + Scalar v_[N]{}; auto&& data(this auto&& self) { return std::forward(self).v_; @@ -45,20 +63,20 @@ template struct vector final { static constexpr auto basis(const auto& d) noexcept { return [&d](std::index_sequence) { return vector{(d == Ss) ? Scalar(1) : Scalar(0)...}; - }(std::make_index_sequence{}); + }(std::make_index_sequence{}); } template static constexpr auto make(Args&&... values) noexcept -> vector { - static_assert(sizeof...(Args) == size, "incorrect number of arguments"); + static_assert(sizeof...(Args) == N, "incorrect number of arguments"); return {{Scalar(values)...}}; } static constexpr auto all(Scalar value) noexcept -> vector { return [&value](std::index_sequence) { - return vector{value + Scalar(Is * 0)...}; - }(std::make_index_sequence{}); + return vector{value + Scalar{Is * 0}...}; + }(std::make_index_sequence{}); } template @@ -69,22 +87,29 @@ template struct vector final { constexpr auto minor(std::unsigned_integral auto d0) const noexcept { return [this, &d0](std::index_sequence) { - return vector{ + return vector{ (*this).v_[(Ss < d0) ? Ss : Ss + 1]...}; - }(std::make_index_sequence{}); + }(std::make_index_sequence{}); } static constexpr auto sequence(Scalar factor = Scalar{1}, Scalar offset = Scalar{0}) noexcept { return [&](std::index_sequence) { - return vector{{Scalar{Ss} * factor + offset...}}; - }(std::make_index_sequence{}); + return vector{{Scalar{Ss} * factor + offset...}}; + }(std::make_index_sequence{}); + } + + static constexpr auto lerp(const vector& A, const vector& B,const Scalar& factor) -> vector + { + return [&](std::index_sequence) { + return vector{{A[Ss] + factor * (B[Ss] - A[Ss])...}}; + }(std::make_index_sequence{}); } constexpr Scalar dot(const auto& other) const { return [this, &other](std::index_sequence) { return (... + (other[Ss] * (*this)[Ss])); - }(std::make_index_sequence{}); + }(std::make_index_sequence{}); } constexpr auto squared_norm() const noexcept { return dot(*this); } @@ -96,7 +121,7 @@ template struct vector final { constexpr vector operator*(const Scalar& v) const noexcept { return [this, &v](std::index_sequence) { return vector{{(*this)[Ss] * v...}}; - }(std::make_index_sequence{}); + }(std::make_index_sequence{}); } constexpr vector operator/(const Scalar& v) const noexcept { @@ -106,19 +131,19 @@ template struct vector final { constexpr vector operator+(const vector& v) const noexcept { return [this, &v](std::index_sequence) { return vector{{(*this)[Ss] + v[Ss]...}}; - }(std::make_index_sequence{}); + }(std::make_index_sequence{}); } constexpr vector operator-(const vector& v) const noexcept { return [this, &v](std::index_sequence) { return vector{{(*this)[Ss] - v[Ss]...}}; - }(std::make_index_sequence{}); + }(std::make_index_sequence{}); } constexpr vector& operator*=(const Scalar& v) noexcept { [this, &v](std::index_sequence) { (((*this)[Ss] *= v), ...); - }(std::make_index_sequence{}); + }(std::make_index_sequence{}); return *this; } @@ -129,7 +154,7 @@ template struct vector final { constexpr vector& operator+=(const Scalar& v) noexcept { [this, &v](std::index_sequence) { (((*this)[Ss] += v), ...); - }(std::make_index_sequence{}); + }(std::make_index_sequence{}); return *this; } @@ -137,78 +162,120 @@ template struct vector final { return operator+=(-v); } - constexpr auto project() const noexcept -> vector { + constexpr auto project() const noexcept -> vector { return [this](std::index_sequence) { - return vector{ - {(*this)[Ss] / (*this)[size - 1]...}}; - }(std::make_index_sequence{}); + return vector{{(*this)[Ss] / (*this)[N - 1]...}}; + }(std::make_index_sequence{}); } constexpr auto - unproject(auto&& w) const noexcept -> vector { + unproject(const Scalar& w) const noexcept -> vector { return [this, &w](std::index_sequence) { - return vector{{(Ss < size) ? (*this)[Ss] : w...}}; - }(std::make_index_sequence{}); + return vector{{(Ss < N) ? (*this)[Ss] : w...}}; + }(std::make_index_sequence{}); } - template - constexpr auto cast() const noexcept { - return [this](std::index_sequence) { - return vector{ ScalarCast{(*this)[Ss]}...}; - }(std::make_index_sequence{}); + constexpr vector cross(const auto& rhs) const noexcept + requires(Vector3) + { + return {(*this)[1] * rhs[2] - rhs[1] * (*this)[2], + (*this)[2] * rhs[0] - rhs[2] * (*this)[0], + (*this)[0] * rhs[1] - rhs[0] * (*this)[1]}; + } + + constexpr vector normalized() const noexcept { + return (*this) / norm(); + } + + auto&& x(this auto&& self) + requires(Vector2 || Vector3 || Vector4) + { + return std::forward(self).v_[0]; + } + + auto&& y(this auto&& self) + requires(Vector2 || Vector3 || Vector4) + { + return std::forward(self).v_[1]; + } + + auto&& z(this auto&& self) + requires(Vector3 || Vector4) + { + return std::forward(self).v_[2]; + } + + auto&& w(this auto&& self) + requires(Vector4) + { + return std::forward(self).v_[3]; + } + + static constexpr vector right() noexcept + requires(Vector3) + { + return vector{1, 0, 0}; + }; + + static constexpr vector up() noexcept + requires(Vector3) + { + return vector{0, 1, 0}; + }; + + static constexpr vector forward() noexcept + requires(Vector3) + { + return vector{0, 0, -1}; + }; + + static constexpr vector x_axis() noexcept + requires(Vector2 || Vector3 || Vector4) + { + return vector::basis(0); + }; + + static constexpr vector y_axis() noexcept + requires(Vector2 || Vector3 || Vector4) + { + return vector::basis(1); + }; + + static constexpr vector z_axis() noexcept + requires(Vector3 || Vector4) + { + return vector::basis(2); + }; + + static constexpr vector w_axis() noexcept + requires(Vector4) + { + return vector::basis(3); + }; + + // + // Iterators + // + constexpr const_pointer begin() const { + return &v_[0]; + } + + constexpr const_pointer end() const { + return &v_[N]; } }; // deduction guide for vector -template > -vector(U...) -> vector; +template > +vector(T, U...) -> vector; -template struct vector2 : vector { +// +// Vector Aliases +// - constexpr const Scalar& x() const { return (*this)[0]; } - constexpr Scalar& x() { return (*this)[0]; } - - constexpr const Scalar& y() const { return (*this)[1]; } - constexpr Scalar& y() { return (*this)[1]; } -}; - -template struct vector3 : vector { - - constexpr static vector3 cross(const vector3& lhs, const vector3& rhs) { - return {lhs[1] * rhs[2] - rhs[1] * lhs[2], - lhs[2] * rhs[0] - rhs[2] * lhs[0], - lhs[0] * rhs[1] - rhs[0] * lhs[1]}; - } - - constexpr static auto forward() { return {0, 0, -1}; } - constexpr static auto backward() { return {0, 0, +1}; } - constexpr static auto up() { return {0, +1, 0}; } - constexpr static auto down() { return {0, -1, 0}; } - constexpr static auto right() { return {+1, 0, 0}; } - constexpr static auto left() { return {-1, 0, 0}; } - constexpr static auto x_axis() { return {0, 0, +1}; } - constexpr static auto y_axis() { return {0, 0, +1}; } - constexpr static auto z_axis() { return {0, 0, +1}; } -}; - -template struct vector4 : vector { - - using base_type = vector; - - constexpr const Scalar& x() const { return (*this)[0]; } - constexpr Scalar& x() { return (*this)[0]; } - - constexpr const Scalar& y() const { return (*this)[1]; } - constexpr Scalar& y() { return (*this)[1]; } - - constexpr const Scalar& z() const { return (*this)[2]; } - constexpr Scalar& z() { return (*this)[2]; } - - constexpr const Scalar& w() const { return (*this)[3]; } - constexpr Scalar& w() { return (*this)[3]; } - - constexpr auto xyz() const { return base_type::swizzle(0, 1, 2); } -}; +template using vector2 = vector; +template using vector3 = vector; +template using vector4 = vector; using vector2f = vector2; using vector2d = vector2; @@ -216,6 +283,11 @@ using vector2i = vector2; using vector3f = vector3; using vector3d = vector3; +using vector3i = vector3; + +using vector4f = vector4; +using vector4d = vector4; +using vector4i = vector4; } // namespace pw diff --git a/src/core/tests/CMakeLists.txt b/src/core/tests/CMakeLists.txt index 600d6b3..e50423a 100644 --- a/src/core/tests/CMakeLists.txt +++ b/src/core/tests/CMakeLists.txt @@ -7,9 +7,9 @@ macro(make_test arg1) endmacro() -make_test(pwcore_test_matrix) +# make_test(pwcore_test_matrix) make_test(pwcore_test_vector) -# make_test(pwcore_test_axisangle) +make_test(pwcore_test_axisangle) # make_test(pwcore_test_quaternion) # make_test(pwcore_test_transform_tools) # make_test(pwcore_test_mesh) diff --git a/src/core/tests/pwcore_test_axisangle.cpp b/src/core/tests/pwcore_test_axisangle.cpp index 34b32c0..e9850a5 100644 --- a/src/core/tests/pwcore_test_axisangle.cpp +++ b/src/core/tests/pwcore_test_axisangle.cpp @@ -1,53 +1,46 @@ #include -#include #include #include -int main(int argc,char **argv) { +auto main() -> int { - pw::axisangle_ aa = pw::axisangle_(); - -// pw::quaternionf qf = pw::quaternionf::from_axisangle(aa); -// std::cout << "aa as quaternion as matrix = " << pw::serialize::matrix(qf.to_matrix()) << std::endl; -// std::cout << "aa.matrix() = " << pw::serialize::matrix(qf.to_matrix()) << std::endl; + auto aa = pw::axisangle(); std::cout << "x-axis" << std::endl; - aa.axis = pw::vector3::x_axis(); + aa.axis = pw::vector3f::x_axis(); aa.angle = pw::deg_to_rad(45.f); - std::cout << "aa.matrix() = " << std::endl << pw::serialize::matrix(aa.to_matrix()) << std::endl; - + std::cout << "aa.matrix() = " << std::endl + << pw::serialize::to_string(aa.to_matrix()) << std::endl; std::cout << "y-axis" << std::endl; - aa.axis = pw::vector3::y_axis(); + aa.axis = pw::vector3f::y_axis(); aa.angle = pw::deg_to_rad(45.f); - std::cout << "aa.matrix() = " << std::endl << pw::serialize::matrix(aa.to_matrix()) << std::endl; + std::cout << "aa.matrix() = " << std::endl + << pw::serialize::to_string(aa.to_matrix()) << std::endl; + std::cout << "z-axis" << std::endl; + aa.axis = pw::vector3f::z_axis(); + aa.angle = pw::deg_to_rad(45.f); - std::cout << "z-axis" << std::endl; + std::cout << "aa.matrix() = " << std::endl + << pw::serialize::to_string(aa.to_matrix()) << std::endl; - aa.axis = pw::vector3::z_axis(); - aa.angle = pw::deg_to_rad(45.f); + std::cout << "from matrix" << std::endl; - std::cout << "aa.matrix() = " << std::endl << pw::serialize::matrix(aa.to_matrix()) << std::endl; - - - std::cout << "from matrix" << std::endl; - - pw::matrix4x4f mrot; mrot.zero(); - mrot(0,0) = 1; - mrot(2,1) = 1; - mrot(1,2) = -1; - - pw::axisanglef aa_fm = pw::axisangle::from_matrix(mrot); - - std::cout << pw::serialize::matrix(aa_fm.axis.transposed()) << " " << pw::rad_to_deg(aa_fm.angle) << "deg" << std::endl; + auto mrot = pw::matrix{}; + mrot[0][0] = 1; + mrot[2][1] = 1; + mrot[1][2] = -1; + auto aa_fm = pw::axisanglef::from_matrix(mrot); + std::cout << pw::serialize::to_string(aa_fm.axis) << " " + << pw::rad_to_deg(aa_fm.angle) << "deg" << std::endl; return 0; } diff --git a/src/core/tests/pwcore_test_vector.cpp b/src/core/tests/pwcore_test_vector.cpp index e2e291a..84d0810 100644 --- a/src/core/tests/pwcore_test_vector.cpp +++ b/src/core/tests/pwcore_test_vector.cpp @@ -1,51 +1,65 @@ -#include #include +#include -#include +#include -int main(int ,char **) { +auto main() -> int { - pw::vector2_ v2_A = { 3.2, 1.2 }; - pw::vector2_ v2_B = { 3.2, 1.2 }; + auto v2_A = pw::vector{3.2, 1.2}; + auto v2_B = pw::vector{6.4, 2.4}; - auto AB_lerp = pw::vector2f::lerp(v2_A,v2_B,0.5); + auto AB_lerp = decltype(v2_A)::lerp(v2_A, v2_B, 0.5); - pw::vector4_ v4; - pw::vector3f v = pw::vector3f::backward(); + std::print("lerp A:({}) B:({}) t:({}) -> {}\n", + pw::serialize::to_string(v2_A), pw::serialize::to_string(v2_B), + 0.5, pw::serialize::to_string(AB_lerp)); - v4.fill(1.5); + auto v4_14 = pw::vector4::all(1.4); + auto v4_sq = pw::vector4::sequence(1.4); + auto v3_fw = pw::vector3f::forward(); - std::cout << "v4 = " << pw::serialize::matrix(v4) << std::endl; + std::print("all(1.4) -> {}\n", pw::serialize::to_string(v4_14)); + std::print("sequence(1.4) -> {}\n", pw::serialize::to_string(v4_sq)); + std::print("forward() -> {}\n", pw::serialize::to_string(v3_fw)); -// std::cout << "rows() : " << v4.rows() << std::endl; -// std::cout << "cols() : " << v4.cols() << std::endl; - std::cout << "ptr() : " << v4.ptr() << std::endl; - std::cout << "ptr()[0] : " << v4.ptr()[0] << std::endl; - std::cout << "(0,0) : " << v4(0,0) << std::endl; + auto v3_sw_1 = v4_sq.swizzle(0, 1); // xy + auto v3_sw_2 = v4_sq.swizzle(1, 0); // yx + auto v3_sw_3 = v4_sq.swizzle(0, 2); // xz - auto v3 = v4.xyz(); + std::print("swizzle(0,1) aka xy -> {}\n", + pw::serialize::to_string(v3_sw_1)); + std::print("swizzle(1,0) aka yx -> {}\n", + pw::serialize::to_string(v3_sw_2)); + std::print("swizzle(0,2) aka xz -> {}\n", + pw::serialize::to_string(v3_sw_3)); - auto v3_p = v4.project(); + auto v3_up_1 = v3_fw.unproject(1); + auto v3_pj_1 = v3_up_1.project(); - auto v3_h = v.homogenous(); + std::print("unproject(1) -> {}\n", pw::serialize::to_string(v3_up_1)); + std::print("project() -> {}\n", pw::serialize::to_string(v3_pj_1)); -// auto v3_lerp = vector4f:: + auto v3_nlz = v3_up_1.normalized(); + std::print("normalized() -> {}\n", pw::serialize::to_string(v3_nlz)); - std::cout << "v3 = " << pw::serialize::matrix(v3) << std::endl; - - std::cout << "v3.normalized() = " << pw::serialize::matrix(v3.normalized()) << std::endl; - - auto e1 = pw::vector3 { 2.0, 0.0, 0.0 }; - auto e2 = pw::vector3 { 0.0, 0.0, 2.0 }; + auto e1 = pw::vector{2.0, 0.0, 0.0}; + auto e2 = pw::vector{0.0, 0.0, 2.0}; auto e2_e1 = e1 - e2; - auto n_e1_e2 = pw::vector3::cross(e1,e2); + std::print("{} - {} -> {}\n", pw::serialize::to_string(e1), + pw::serialize::to_string(e2), pw::serialize::to_string(e2_e1)); - std::cout << "e1xe2 " << pw::serialize::matrix(n_e1_e2) << std::endl; + auto e1_cross_e2 = e1.cross(e2); + std::print("{} x {} -> {}\n", pw::serialize::to_string(e1), + pw::serialize::to_string(e2), + pw::serialize::to_string(e1_cross_e2)); - std::cout << "e1xe2 " << pw::serialize::matrix(e2_e1) << std::endl; + auto e1_dot_e2 = e1.dot(e2); + + std::print("{} * {} -> {}\n", pw::serialize::to_string(e1), + pw::serialize::to_string(e2), e1_dot_e2); return 0; }