vector-traits 0.6.2

Rust traits for 2D and 3D vector types.
Documentation
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) 2023, 2025 lacklustr@protonmail.com https://github.com/eadf

// This file is part of vector-traits.

use crate::prelude::*;
pub fn test_affine2d_v1<V>()
where
    V: GenericVector2 + From<V>,
{
    let _0 = 0.0.into();
    let _1 = 1.0.into();
    let _2 = 2.0.into();
    let half = 0.5.into();

    // Construct identity matrix
    let identity = V::Affine::identity();
    let pointi = V::new_2d(_1, _2);
    let pointo = V::new_2d(_1, _2);
    let transformed_point = identity.transform_point2(pointi);
    assert_eq!(transformed_point, pointo);

    let vec = V::new_2d(_2 * _2, _2 * half);
    let transformed_vec = identity.transform_vector2(vec);
    assert_eq!(transformed_vec, V::new_2d(vec.x(), vec.y()));

    // Build matrix from column-major array (column-wise)
    let arr = [
        _1, _0, _0, // column 1
        _0, _1, _0, // column 2
        _2, _2, _1, // column 3: this encodes translation (2,2)
    ];
    let trans_mat = V::Affine::from_cols_array(&arr);
    let translated = trans_mat.transform_point2(pointi);
    let expected = V::new_2d(pointi.x() + _2, pointi.y() + _2);
    assert_eq!(translated, expected);

    // Vector transformation should *ignore* translation
    let transformed_vec = trans_mat.transform_vector2(vec);
    assert_eq!(transformed_vec, vec);
}

pub fn test_affine3d_v1<V>(_backend: &str)
where
    V: GenericVector3 + From<V>,
{
    let _1 = 1.0.into();
    let _0 = 0.0.into();
    let _2 = 2.0.into();
    let half = 0.5.into();

    // Construct identity matrix
    let identity = V::Affine::identity();
    let point = V::new_2d(_1, _2);
    let transformed_point = identity.transform_point3(point);
    assert_eq!(transformed_point, point);

    let vec = V::new_2d(_2 * _2, _2 * half);
    let transformed_vec = identity.transform_vector3(vec);
    assert_eq!(transformed_vec, vec);

    // Build matrix from column-major array (column-wise)
    let arr = [
        _1, _0, _0, _0, // column 1
        _0, _1, _0, _0, // column 2
        _0, _0, _1, _0, // column 2
        _2, _2, _0, _1, // column 3:
    ];
    let trans_mat = V::Affine::from_cols_array(&arr);
    let translated = trans_mat.transform_point3(point);
    let expected = V::new_2d(point.x() + _2, point.y() + _2);
    assert_eq!(translated, expected);

    // Vector transformation should *ignore* translation
    let transformed_vec = trans_mat.transform_vector3(vec);
    assert_eq!(transformed_vec, vec);

    let safe_inv = trans_mat.try_inverse();
    assert!(safe_inv.is_some());

    test_affine3d_rotations::<V>();
}

pub fn test_affine2d_v2<V: GenericVector2>() {
    let _1 = 1.0.into();
    let _0 = 0.0.into();
    let _2 = 2.0.into();
    let half = 0.5.into();

    // Construct identity matrix
    let identity = V::Affine::identity();
    let point = V::new_2d(_1, _2);
    let transformed_point = identity.transform_point2(point);
    assert_eq!(transformed_point, point);

    let vec = V::new_2d(_2 * _2, _2 * half);
    let transformed_vec = identity.transform_vector2(vec);
    assert_eq!(transformed_vec, vec);

    // Build matrix from column-major array
    let arr = [
        _1, _0, _0, // column 1
        _0, _1, _0, // column 2
        _2, _2, _1, // column 3
    ];
    let trans_mat = V::Affine::from_cols_array(&arr);
    let translated = trans_mat.transform_point2(point);
    let expected = V::new_2d(point.x() + _2, point.y() + _2);
    assert_eq!(translated, expected);

    // Vector transformation should ignore translation
    let transformed_vec = trans_mat.transform_vector2(vec);
    assert_eq!(transformed_vec, vec);

    let safe_inv = trans_mat.try_inverse();
    assert!(safe_inv.is_some());

    // Scale + translate
    let scale_arr = [_2, _0, _0, _0, half, _0, _0, _0, _1];
    let scale_mat = V::Affine::from_cols_array(&scale_arr);
    let scaled = scale_mat.transform_point2(point);
    let expected_scaled = V::new_2d(_1 * _2, _2 * half);
    assert_eq!(scaled, expected_scaled);

    test_affine2d_inverse::<V>();
}

pub fn test_affine2d_inverse<V>()
where
    V: GenericVector2 + From<V>,
{
    let _1 = 1.0.into();
    let _0 = 0.0.into();
    let _2 = 2.0.into();
    let half = 0.5.into();

    // Build a transform with translation and scaling
    let arr = [
        _2, _0, _0, // Scale X by 2
        _0, half, _0, // Scale Y by 0.5
        _2, _2, _1, // Translate by (2,2)
    ];
    let transform = V::Affine::from_cols_array(&arr);

    // Get the inverse
    let inverse = transform
        .try_inverse()
        .expect("Transform should be invertible");

    // Test round-trip transformation
    let original_point = V::new_2d(_1, _2);
    let transformed = transform.transform_point2(original_point);
    let round_trip = inverse.transform_point2(transformed);

    // Should get back to the original point (within floating point precision)
    assert_eq!(round_trip, original_point);

    // Test that inverse.inverse() equals the original transform
    if let Some(inv_inv) = inverse.try_inverse() {
        // Test by applying both to a point
        let test_point = V::new_2d(half, _2 + half);
        let t1 = transform.transform_point2(test_point);
        let t2 = inv_inv.transform_point2(test_point);

        // Results should be equal
        assert_eq!(t1, t2);
    }

    // Test inverse properties for vectors too
    let original_vector = V::new_2d(_2, half);
    let transformed_vector = transform.transform_vector2(original_vector);
    let round_trip_vector = inverse.transform_vector2(transformed_vector);

    // Should get back to the original vector
    assert_eq!(round_trip_vector, original_vector);
}

pub fn test_affine3d_rotations<V: GenericVector3>() {
    let _1: V::Scalar = 1.0.into();
    let _0: V::Scalar = 0.0.into();

    // Create a 90-degree rotation around the Z-axis
    // This is a simple rotation where:
    // - X axis becomes Y axis (0,1,0)
    // - Y axis becomes -X axis (-1,0,0)
    // - Z remains Z (0,0,1)
    let rot_z_90 = [
        _0, _1, _0, _0, // First column - new X axis
        -_1, _0, _0, _0, // Second column - new Y axis
        _0, _0, _1, _0, // Third column - new Z axis
        _0, _0, _0, _1, // Translation (none)
    ];

    let rot_matrix = V::Affine::from_cols_array(&rot_z_90);

    // Test rotation of unit vectors
    let unit_x = V::new_3d(_1, _0, _0);
    let unit_y = V::new_3d(_0, _1, _0);
    let unit_z = V::new_3d(_0, _0, _1);

    // Rotating X should give us Y
    let rotated_x = rot_matrix.transform_vector3(unit_x);
    assert_eq!(rotated_x, V::new_3d(_0, _1, _0));

    // Rotating Y should give us -X
    let rotated_y = rot_matrix.transform_vector3(unit_y);
    assert_eq!(rotated_y, V::new_3d(-_1, _0, _0));

    // Rotating Z should keep it as Z
    let rotated_z = rot_matrix.transform_vector3(unit_z);
    assert_eq!(rotated_z, V::new_3d(_0, _0, _1));

    // Verify that applying rotation 4 times gets us back to the original position
    let point = V::new_3d(_1, _1, _0);
    let rot1 = rot_matrix.transform_point3(point);
    let rot2 = rot_matrix.transform_point3(rot1);
    let rot3 = rot_matrix.transform_point3(rot2);
    let rot4 = rot_matrix.transform_point3(rot3);

    // Should be back to the original point after 4 rotations of 90 degrees
    assert_eq!(rot4, point);
}

pub fn test_affine3d_scale_and_translation<V: GenericVector3>() {
    let _1: V::Scalar = 1.0.into();
    let _0: V::Scalar = 0.0.into();
    let _2: V::Scalar = 2.0.into();
    let _3: V::Scalar = 3.0.into();
    let _4: V::Scalar = 4.0.into();

    // Test scaling
    let scale_vec = V::new_3d(_2, _3, _1); // Scale X by 2, Y by 3, Z by 1
    let scale_matrix = V::Affine::from_scale(scale_vec);

    // Test scaling of unit vectors
    let unit_x = V::new_3d(_1, _0, _0);
    let unit_y = V::new_3d(_0, _1, _0);
    let unit_z = V::new_3d(_0, _0, _1);

    // Scaling X should give us (2,0,0)
    let scaled_x = scale_matrix.transform_vector3(unit_x);
    assert_eq!(scaled_x, V::new_3d(_2, _0, _0));

    // Scaling Y should give us (0,3,0)
    let scaled_y = scale_matrix.transform_vector3(unit_y);
    assert_eq!(scaled_y, V::new_3d(_0, _3, _0));

    // Scaling Z should keep it as (0,0,1)
    let scaled_z = scale_matrix.transform_vector3(unit_z);
    assert_eq!(scaled_z, V::new_3d(_0, _0, _1));

    // Test translation
    let translation_vec = V::new_3d(_2, _3, _1); // Translate X by 2, Y by 3, Z by 1
    let translation_matrix = V::Affine::from_translation(translation_vec);

    // Test translation of origin point
    let origin = V::new_3d(_0, _0, _0);
    let translated_origin = translation_matrix.transform_point3(origin);
    assert_eq!(translated_origin, translation_vec);

    // Test translation of another point
    let point = V::new_3d(_1, _1, _1);
    let translated_point = translation_matrix.transform_point3(point);
    assert_eq!(translated_point, V::new_3d(_3, _4, _2));

    // Test that translation doesn't affect vectors (only points)
    let vector = V::new_3d(_1, _1, _1);
    let translated_vector = translation_matrix.transform_vector3(vector);
    assert_eq!(translated_vector, vector);
}

pub fn test_affine3d_plane_xy<V: GenericVector3>() {
    let _0: V::Scalar = 0.0.into();
    let _1: V::Scalar = 1.0.into();
    let _2: V::Scalar = 2.0.into();

    let plane_matrix = V::Affine::from_plane_to_xy(Plane::XY);
    let plane_matrix_inverse = plane_matrix.try_inverse().unwrap();

    let a = V::new_3d(_1, _2, _0);
    let a_rot = plane_matrix.transform_point3(a);
    let a_rot_2d = a_rot.to_2d();
    let a_rot_2d_2 = Plane::XY.point_to_2d(a);
    assert_eq!(a_rot_2d, a_rot_2d_2);
    let a_rot_rot = plane_matrix_inverse.transform_point3(a_rot);

    assert_eq!(a_rot, V::new_3d(_1, _2, _0));
    assert_eq!(a, a_rot_rot);
}

pub fn test_affine3d_plane_xz<V: GenericVector3>() {
    let _0: V::Scalar = 0.0.into();
    let _1: V::Scalar = 1.0.into();
    let _2: V::Scalar = 2.0.into();

    let plane_matrix = V::Affine::from_plane_to_xy(Plane::XZ);
    let plane_matrix_inverse = plane_matrix.try_inverse().unwrap();

    let a = V::new_3d(_1, _0, _2);
    let a_rot = plane_matrix.transform_point3(a);
    let a_rot_2d = a_rot.to_2d();
    let a_rot_2d_2 = Plane::XZ.point_to_2d(a);
    assert_eq!(a_rot_2d, a_rot_2d_2);

    let a_rot_rot = plane_matrix_inverse.transform_point3(a_rot);

    assert_eq!(a_rot, V::new_3d(_1, _2, _0));
    assert_eq!(a, a_rot_rot);
}

pub fn test_affine3d_plane_yz<V: GenericVector3>() {
    let _0: V::Scalar = 0.0.into();
    let _1: V::Scalar = 1.0.into();
    let _2: V::Scalar = 2.0.into();

    let plane_matrix = V::Affine::from_plane_to_xy(Plane::YZ);

    let a = V::new_3d(_0, _1, _2);
    let a_rot = plane_matrix.transform_point3(a);
    let a_rot_2d = a_rot.to_2d();
    let a_rot_2d_2 = Plane::YZ.point_to_2d(a);
    assert_eq!(a_rot_2d, a_rot_2d_2);

    let plane_matrix_inverse = plane_matrix.try_inverse().unwrap();

    let a_rot_rot = plane_matrix_inverse.transform_point3(a_rot);

    assert_eq!(a_rot, V::new_3d(_2, _1, _0));
    assert_eq!(a, a_rot_rot);
}