fixing axisangle and vector

This commit is contained in:
Hartmut Seichter 2024-07-12 21:05:56 +02:00
parent 3989c0f68e
commit 2919c47e99
7 changed files with 283 additions and 197 deletions

View file

@ -58,5 +58,4 @@ target_include_directories(
target_link_libraries(pwcore)
# add_subdirectory(tests)
add_subdirectory(tests)

View file

@ -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 <pw/core/vector.hpp>
#include <pw/core/matrix.hpp>
namespace pw {
template <typename T>
struct axisangle_ {
template <typename T> struct axisangle {
using value_type = T;
using axis_type = vector3_<T>; // todo - should default to UP
using axis_type = vector3<T>;
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<T, 4, 4>& 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<T, 4, 4> {
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<T,4,4>::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<float>;
using axisangled = axisangle<double>;
using axisangle = axisangle_<real_t> ;
using axisanglef = axisangle_<float> ;
using axisangled = axisangle_<double> ;
}
} // namespace pw
#endif

View file

@ -23,6 +23,7 @@
#ifndef PW_CORE_SERIALIZE_HPP
#define PW_CORE_SERIALIZE_HPP
#include "vector.hpp"
#include <pw/core/matrix.hpp>
@ -33,19 +34,25 @@ namespace pw {
struct serialize {
template <size_t R,size_t C,typename T>
inline static std::string matrix(const matrix_<R,C,T>& m) {
template <typename T,auto N>
constexpr static std::string to_string(const vector<T, N>& v) {
std::stringstream ss;
for(const auto& e : v) ss << e << ' ';
return ss.str();
}
template <typename T,auto R,auto C>
constexpr static std::string to_string(const matrix<T,R,C>& 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();
}
};
}

View file

@ -25,14 +25,32 @@
#include <pw/core/globals.hpp>
#include <cmath>
namespace pw {
template <typename Scalar, unsigned int size> struct vector final {
template <typename T, auto N>
concept Vector2 = (N == 2);
using value_type = Scalar;
static constexpr unsigned int coefficients{size};
template <typename T, auto N>
concept Vector3 = (N == 3);
Scalar v_[size]{};
template <typename T, auto N>
concept Vector4 = (N == 4);
template <typename Scalar, unsigned int N> 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<decltype(self)>(self).v_;
@ -45,20 +63,20 @@ template <typename Scalar, unsigned int size> struct vector final {
static constexpr auto basis(const auto& d) noexcept {
return [&d]<std::size_t... Ss>(std::index_sequence<Ss...>) {
return vector{(d == Ss) ? Scalar(1) : Scalar(0)...};
}(std::make_index_sequence<size>{});
}(std::make_index_sequence<N>{});
}
template <typename... Args>
static constexpr auto
make(Args&&... values) noexcept -> vector<Scalar, sizeof...(Args)> {
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::size_t... Is>(std::index_sequence<Is...>) {
return vector{value + Scalar(Is * 0)...};
}(std::make_index_sequence<size>{});
return vector{value + Scalar{Is * 0}...};
}(std::make_index_sequence<N>{});
}
template <typename... Args>
@ -69,22 +87,29 @@ template <typename Scalar, unsigned int size> struct vector final {
constexpr auto minor(std::unsigned_integral auto d0) const noexcept {
return [this, &d0]<std::size_t... Ss>(std::index_sequence<Ss...>) {
return vector<Scalar, size - 1>{
return vector<Scalar, N - 1>{
(*this).v_[(Ss < d0) ? Ss : Ss + 1]...};
}(std::make_index_sequence<size - 1>{});
}(std::make_index_sequence<N - 1>{});
}
static constexpr auto sequence(Scalar factor = Scalar{1},
Scalar offset = Scalar{0}) noexcept {
return [&]<std::size_t... Ss>(std::index_sequence<Ss...>) {
return vector<Scalar, size>{{Scalar{Ss} * factor + offset...}};
}(std::make_index_sequence<size>{});
return vector<Scalar, N>{{Scalar{Ss} * factor + offset...}};
}(std::make_index_sequence<N>{});
}
static constexpr auto lerp(const vector& A, const vector& B,const Scalar& factor) -> vector
{
return [&]<std::size_t... Ss>(std::index_sequence<Ss...>) {
return vector<Scalar, N>{{A[Ss] + factor * (B[Ss] - A[Ss])...}};
}(std::make_index_sequence<N>{});
}
constexpr Scalar dot(const auto& other) const {
return [this, &other]<std::size_t... Ss>(std::index_sequence<Ss...>) {
return (... + (other[Ss] * (*this)[Ss]));
}(std::make_index_sequence<size>{});
}(std::make_index_sequence<N>{});
}
constexpr auto squared_norm() const noexcept { return dot(*this); }
@ -96,7 +121,7 @@ template <typename Scalar, unsigned int size> struct vector final {
constexpr vector operator*(const Scalar& v) const noexcept {
return [this, &v]<std::size_t... Ss>(std::index_sequence<Ss...>) {
return vector{{(*this)[Ss] * v...}};
}(std::make_index_sequence<size>{});
}(std::make_index_sequence<N>{});
}
constexpr vector operator/(const Scalar& v) const noexcept {
@ -106,19 +131,19 @@ template <typename Scalar, unsigned int size> struct vector final {
constexpr vector operator+(const vector& v) const noexcept {
return [this, &v]<std::size_t... Ss>(std::index_sequence<Ss...>) {
return vector{{(*this)[Ss] + v[Ss]...}};
}(std::make_index_sequence<size>{});
}(std::make_index_sequence<N>{});
}
constexpr vector operator-(const vector& v) const noexcept {
return [this, &v]<std::size_t... Ss>(std::index_sequence<Ss...>) {
return vector{{(*this)[Ss] - v[Ss]...}};
}(std::make_index_sequence<size>{});
}(std::make_index_sequence<N>{});
}
constexpr vector& operator*=(const Scalar& v) noexcept {
[this, &v]<std::size_t... Ss>(std::index_sequence<Ss...>) {
(((*this)[Ss] *= v), ...);
}(std::make_index_sequence<size>{});
}(std::make_index_sequence<N>{});
return *this;
}
@ -129,7 +154,7 @@ template <typename Scalar, unsigned int size> struct vector final {
constexpr vector& operator+=(const Scalar& v) noexcept {
[this, &v]<std::size_t... Ss>(std::index_sequence<Ss...>) {
(((*this)[Ss] += v), ...);
}(std::make_index_sequence<size>{});
}(std::make_index_sequence<N>{});
return *this;
}
@ -137,78 +162,120 @@ template <typename Scalar, unsigned int size> struct vector final {
return operator+=(-v);
}
constexpr auto project() const noexcept -> vector<Scalar, size - 1> {
constexpr auto project() const noexcept -> vector<Scalar, N - 1> {
return [this]<std::size_t... Ss>(std::index_sequence<Ss...>) {
return vector<Scalar, size - 1>{
{(*this)[Ss] / (*this)[size - 1]...}};
}(std::make_index_sequence<size - 1>{});
return vector<Scalar, N - 1>{{(*this)[Ss] / (*this)[N - 1]...}};
}(std::make_index_sequence<N - 1>{});
}
constexpr auto
unproject(auto&& w) const noexcept -> vector<Scalar, size + 1> {
unproject(const Scalar& w) const noexcept -> vector<Scalar, N + 1> {
return [this, &w]<std::size_t... Ss>(std::index_sequence<Ss...>) {
return vector<Scalar, size + 1>{{(Ss < size) ? (*this)[Ss] : w...}};
}(std::make_index_sequence<size + 1>{});
return vector<Scalar, N + 1>{{(Ss < N) ? (*this)[Ss] : w...}};
}(std::make_index_sequence<N + 1>{});
}
template <typename ScalarCast>
constexpr auto cast() const noexcept {
return [this]<std::size_t... Ss>(std::index_sequence<Ss...>) {
return vector{ ScalarCast{(*this)[Ss]}...};
}(std::make_index_sequence<size>{});
constexpr vector cross(const auto& rhs) const noexcept
requires(Vector3<Scalar, N>)
{
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<Scalar, N> || Vector3<Scalar, N> || Vector4<Scalar, N>)
{
return std::forward<decltype(self)>(self).v_[0];
}
auto&& y(this auto&& self)
requires(Vector2<Scalar, N> || Vector3<Scalar, N> || Vector4<Scalar, N>)
{
return std::forward<decltype(self)>(self).v_[1];
}
auto&& z(this auto&& self)
requires(Vector3<Scalar, N> || Vector4<Scalar, N>)
{
return std::forward<decltype(self)>(self).v_[2];
}
auto&& w(this auto&& self)
requires(Vector4<Scalar, N>)
{
return std::forward<decltype(self)>(self).v_[3];
}
static constexpr vector right() noexcept
requires(Vector3<Scalar, N>)
{
return vector{1, 0, 0};
};
static constexpr vector up() noexcept
requires(Vector3<Scalar, N>)
{
return vector{0, 1, 0};
};
static constexpr vector forward() noexcept
requires(Vector3<Scalar, N>)
{
return vector{0, 0, -1};
};
static constexpr vector x_axis() noexcept
requires(Vector2<Scalar, N> || Vector3<Scalar, N> || Vector4<Scalar, N>)
{
return vector::basis(0);
};
static constexpr vector y_axis() noexcept
requires(Vector2<Scalar, N> || Vector3<Scalar, N> || Vector4<Scalar, N>)
{
return vector::basis(1);
};
static constexpr vector z_axis() noexcept
requires(Vector3<Scalar, N> || Vector4<Scalar, N>)
{
return vector::basis(2);
};
static constexpr vector w_axis() noexcept
requires(Vector4<Scalar, N>)
{
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 <class... U, class CT = std::common_type_t<U...>>
vector(U...) -> vector<CT, sizeof...(U)>;
template <class T, class... U, class CT = std::common_type_t<T, U...>>
vector(T, U...) -> vector<CT, 1 + sizeof...(U)>;
template <typename Scalar> struct vector2 : vector<Scalar, 2> {
//
// 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 <typename Scalar> struct vector3 : vector<Scalar, 3> {
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 <typename Scalar> struct vector4 : vector<Scalar, 4> {
using base_type = vector<Scalar, 4>;
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 <typename Scalar> using vector2 = vector<Scalar, 2>;
template <typename Scalar> using vector3 = vector<Scalar, 3>;
template <typename Scalar> using vector4 = vector<Scalar, 4>;
using vector2f = vector2<float>;
using vector2d = vector2<double>;
@ -216,6 +283,11 @@ using vector2i = vector2<int>;
using vector3f = vector3<float>;
using vector3d = vector3<double>;
using vector3i = vector3<int>;
using vector4f = vector4<float>;
using vector4d = vector4<double>;
using vector4i = vector4<int>;
} // namespace pw

View file

@ -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)

View file

@ -1,53 +1,46 @@
#include <pw/core/axisangle.hpp>
#include <pw/core/quaternion.hpp>
#include <pw/core/serialize.hpp>
#include <iostream>
int main(int argc,char **argv) {
auto main() -> int {
pw::axisangle_<float> aa = pw::axisangle_<float>();
// 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<float>();
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<float, 4, 4>{};
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;
}

View file

@ -1,51 +1,65 @@
#include <pw/core/vector.hpp>
#include <pw/core/serialize.hpp>
#include <pw/core/vector.hpp>
#include <iostream>
#include <print>
int main(int ,char **) {
auto main() -> int {
pw::vector2_<float> v2_A = { 3.2, 1.2 };
pw::vector2_<float> 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_<float> 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<float>::all(1.4);
auto v4_sq = pw::vector4<float>::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;
}