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::*;
use approx::{AbsDiffEq, UlpsEq};
use num_traits::float::FloatCore;

pub fn test_xyz<T: HasXYZ + Approx>(x: T::Scalar, y: T::Scalar, z: T::Scalar) {
    let v0 = T::new_3d(x, y, z);
    assert_eq!(v0.x(), x);
    assert_eq!(v0.y(), y);
    assert_eq!(v0.z(), z);

    let mult = 6.0.into();
    let mut v1 = v0;
    *v1.x_mut() = x * mult;
    *v1.y_mut() = y * mult;
    *v1.z_mut() = z * mult;
    assert_eq!(v1.x(), x * mult);
    assert_eq!(v1.y(), y * mult);
    assert_eq!(v1.z(), z * mult);

    v1.set_x(x * mult);
    v1.set_y(y * mult);
    v1.set_z(z * mult);
    assert_eq!(v1.x(), x * mult);
    assert_eq!(v1.y(), y * mult);
    assert_eq!(v1.z(), z * mult);

    let v0 = T::new_3d(T::Scalar::ONE, T::Scalar::TWO, T::Scalar::THREE);
    let v1 = T::new_3d(T::Scalar::ONE, T::Scalar::TWO, T::Scalar::THREE);
    assert!(v0.is_ulps_eq(
        v1,
        T::Scalar::default_epsilon(),
        T::Scalar::default_max_ulps()
    ));
    assert!(v0.is_abs_diff_eq(v1, T::Scalar::default_epsilon()));
}

pub fn test_gxyz<T: GenericVector3>(x: T::Scalar, y: T::Scalar, z: T::Scalar) {
    let v0 = T::new_3d(x, y, z);
    assert_eq!(v0.x(), x);
    assert_eq!(v0.y(), y);
    assert_eq!(v0.z(), z);
    let v1 = v0.to_2d();
    assert_eq!(v1.x(), x);
    assert_eq!(v1.y(), y);

    let mult = 6.0.into();
    let mut v1 = v0;
    v1.set_x(x * mult);
    v1.set_y(y * mult);
    v1.set_z(z * mult);
    assert_eq!(v1.x(), x * mult);
    assert_eq!(v1.y(), y * mult);
    assert_eq!(v1.z(), z * mult);

    let center = (v0 + v1) / T::Scalar::TWO;
    assert!(center.x().ulps_eq(
        &((x + x * 6.0.into()) / 2.0.into()),
        T::Scalar::default_epsilon(),
        T::Scalar::default_max_ulps()
    ));
    assert!(center.y().ulps_eq(
        &((y + y * 6.0.into()) / 2.0.into()),
        T::Scalar::default_epsilon(),
        T::Scalar::default_max_ulps()
    ));
    assert!(center.z().ulps_eq(
        &((z + z * 6.0.into()) / 2.0.into()),
        T::Scalar::default_epsilon(),
        T::Scalar::default_max_ulps()
    ));

    let x = v0.x();
    let y = v0.y();
    let z = v0.z();

    // Test min()
    let min_value = T::new_3d(1.0.into(), 2.0.into(), 3.0.into());
    let max_value = T::new_3d(8.0.into(), 9.0.into(), 10.0.into());
    let min_vec = v0.min(min_value);
    assert!(min_vec.x().ulps_eq(
        &x.min(min_value.x()),
        T::Scalar::default_epsilon(),
        T::Scalar::default_max_ulps()
    ));
    assert!(min_vec.y().ulps_eq(
        &y.min(min_value.y()),
        T::Scalar::default_epsilon(),
        T::Scalar::default_max_ulps()
    ));
    assert!(min_vec.z().ulps_eq(
        &z.min(min_value.z()),
        T::Scalar::default_epsilon(),
        T::Scalar::default_max_ulps()
    ));

    // Test max()
    let max_vec = v0.max(max_value);
    assert!(max_vec.x().ulps_eq(
        &x.max(max_value.x()),
        T::Scalar::default_epsilon(),
        T::Scalar::default_max_ulps()
    ));
    assert!(max_vec.y().ulps_eq(
        &y.max(max_value.y()),
        T::Scalar::default_epsilon(),
        T::Scalar::default_max_ulps()
    ));
    assert!(max_vec.z().ulps_eq(
        &z.max(max_value.z()),
        T::Scalar::default_epsilon(),
        T::Scalar::default_max_ulps()
    ));

    // Test clamp()
    let clamped_vec = v0.clamp(min_value, max_value);
    assert!(clamped_vec.x().ulps_eq(
        &x.clamp(min_value.x(), max_value.x()),
        T::Scalar::default_epsilon(),
        T::Scalar::default_max_ulps()
    ));
    assert!(clamped_vec.y().ulps_eq(
        &y.clamp(min_value.y(), max_value.y()),
        T::Scalar::default_epsilon(),
        T::Scalar::default_max_ulps()
    ));
    assert!(clamped_vec.z().ulps_eq(
        &z.clamp(min_value.z(), max_value.z()),
        T::Scalar::default_epsilon(),
        T::Scalar::default_max_ulps()
    ));

    let mut v0 = T::new_3d(x, y, z);
    let five = 5.0.into();
    v0[0] *= five;
    v0[1] *= five;
    v0[2] *= five;

    assert_eq!(v0[0], x * five);
    assert_eq!(v0[1], y * five);
    assert_eq!(v0[2], z * five);
}

pub fn test_generic_xyz<T: GenericVector3>(
    x: T::Scalar,
    y: T::Scalar,
    z: T::Scalar,
    _w: T::Scalar, // an extra scalar value for testing
    epsilon: T::Scalar,
) {
    let v0 = T::new_3d(x, y, z);
    assert_eq!(v0.x(), x);
    assert_eq!(v0.y(), y);
    assert_eq!(v0.z(), z);

    let mut v1 = v0;
    let mult: T::Scalar = 6.0.into();
    v1.set_x(v1.x() * mult);
    v1.set_y(v1.y() * mult);
    v1.set_z(v1.z() * mult);
    assert_eq!(v1.x(), x * mult);
    assert_eq!(v1.y(), y * mult);
    assert_eq!(v1.z(), z * mult);

    let v2 = (v0 * mult).to_2d();
    assert_eq!(v2.x(), x * mult);
    assert_eq!(v2.y(), y * mult);

    assert!(!v0.is_ulps_eq(
        v1,
        T::Scalar::default_epsilon(),
        T::Scalar::default_max_ulps()
    ));
    assert!(!v0.is_abs_diff_eq(v1, T::Scalar::default_epsilon()));

    // Test magnitude and magnitude_sq
    let magnitude = v0.magnitude();
    let magnitude_sq = v0.magnitude_sq();
    assert!(
        (magnitude * magnitude - magnitude_sq).abs() < epsilon,
        "{} != {}",
        magnitude * magnitude,
        magnitude_sq
    );

    // Test dot product
    let dot = v0.dot(v1);
    assert_eq!(dot, (x * x * mult + y * y * mult + z * z * mult));

    // Test cross product
    let cross_product = v0.cross(v1);
    // Depending on values, cross product may vary, but in this case v0 and v1 are collinear
    assert_eq!(
        cross_product,
        T::new_3d(T::Scalar::ZERO, T::Scalar::ZERO, T::Scalar::ZERO)
    );

    // Test distance and distance_sq
    let distance = v0.distance(v1);
    let distance_sq = v0.distance_sq(v1);
    assert!(
        (distance * distance - distance_sq).abs() < epsilon,
        "{} != {}",
        distance * distance,
        distance_sq
    );

    // Test normalize
    let normalized = v0.normalize();
    assert!(
        (normalized.magnitude() - T::Scalar::ONE) < epsilon,
        "{} != {}",
        normalized.magnitude(),
        T::Scalar::ONE
    );

    // Test try_normalize
    if let Some(v) = v0.try_normalize(T::Scalar::EPSILON) {
        assert!(
            (v.magnitude() - T::Scalar::ONE) < epsilon,
            "{} != {}",
            v.magnitude(),
            T::Scalar::from(1.0)
        )
    };
    let v0 = T::new_3d(T::Scalar::ZERO, T::Scalar::ZERO, T::Scalar::ZERO);
    assert!(v0.try_normalize(T::Scalar::EPSILON).is_none());
    assert!(v0.is_ulps_eq(
        v0,
        T::Scalar::default_epsilon(),
        T::Scalar::default_max_ulps()
    ));
    assert!(v0.is_abs_diff_eq(v0, T::Scalar::default_epsilon()));
    let v0 = T::splat(T::Scalar::THREE);
    assert!(v0.is_ulps_eq(
        T::new(T::Scalar::THREE, T::Scalar::THREE, T::Scalar::THREE),
        T::Scalar::default_epsilon(),
        T::Scalar::default_max_ulps()
    ));

    let _7: T::Scalar = 7.0.into();
    let v0 = T::new_2d(T::Scalar::nan(), _7);
    assert!(!v0.is_finite());
    let v0 = T::new_3d(_7, T::Scalar::nan(), _7);
    assert!(!v0.is_finite());
    let v0 = T::new_3d(_7, _7, T::Scalar::nan());
    assert!(!v0.is_finite());

    let v0 = T::new_2d(T::Scalar::infinity(), _7);
    assert!(!v0.is_finite());
    let v0 = T::new_3d(_7, T::Scalar::infinity(), _7);
    assert!(!v0.is_finite());
    let v0 = T::new_3d(_7, _7, T::Scalar::infinity());
    assert!(!v0.is_finite());
}

pub fn test_simd_conversion<T: SimdUpgradable>() {
    let vec3 = T::new_3d(1.0.into(), 2.0.into(), 3.0.into());
    let vec3a = vec3.to_simd();
    assert_eq!(vec3a.x(), vec3.x());
    assert_eq!(vec3a.y(), vec3.y());
    assert_eq!(vec3a.z(), vec3.z());
    let vec3_round_trip = T::from_simd(vec3a);
    assert_eq!(vec3_round_trip.x(), vec3.x());
    assert_eq!(vec3_round_trip.y(), vec3.y());
    assert_eq!(vec3_round_trip.z(), vec3.z());
}