scirs2-spatial 0.4.0

Spatial algorithms module for SciRS2 (scirs2-spatial)
Documentation
use ndarray::array;
use scirs2_spatial::transform::{RigidTransform, Rotation, RotationSpline, Slerp};
use scirs2_spatial::SpatialResult;
use std::f64::consts::PI;

#[allow(dead_code)]
fn main() -> SpatialResult<()> {
    println!("Spatial Transformations Examples");
    println!("==============================\n");

    // Demonstrate Rotation
    rotation_examples()?;

    // Demonstrate RigidTransform
    rigid_transform_examples()?;

    // Demonstrate Slerp
    slerp_examples()?;

    // Demonstrate RotationSpline
    rotation_spline_examples()?;

    Ok(())
}

#[allow(dead_code)]
fn rotation_examples() -> SpatialResult<()> {
    println!("Rotation Examples");
    println!("-----------------");

    // Create rotations using different representations
    println!("Creating rotations using different representations:");

    // 1. From quaternion
    let quat = array![
        std::f64::consts::FRAC_1_SQRT_2,
        std::f64::consts::FRAC_1_SQRT_2,
        0.0,
        0.0
    ]; // 90-degree rotation around X
    let rot_quat = Rotation::from_quat(&quat.view())?;
    println!("  1. From quaternion [std::f64::consts::FRAC_1_SQRT_2, std::f64::consts::FRAC_1_SQRT_2, 0.0, 0.0] (90° around X)");

    // 2. From Euler angles
    let euler = array![0.0, 0.0, PI / 2.0]; // 90-degree rotation around Z
    let rot_euler = Rotation::from_euler(&euler.view(), "xyz")?;
    println!("  2. From Euler angles [0, 0, π/2] (90° around Z)");

    // 3. From rotation matrix
    let matrix = array![[0.0, -1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]]; // 90-degree rotation around Z
    let _rot_matrix = Rotation::from_matrix(&matrix.view())?;
    println!("  3. From rotation matrix (90° around Z)");

    // 4. From axis-angle
    let rotvec = array![0.0, 0.0, PI / 2.0]; // 90-degree rotation around Z
    let _rot_rotvec = Rotation::from_rotvec(&rotvec.view())?;
    println!("  4. From rotation vector [0, 0, π/2] (90° around Z)");

    // Apply rotations to a test point
    println!("\nApplying rotations to point [1, 0, 0]:");
    let point = array![1.0, 0.0, 0.0];

    let rotated_quat = rot_quat.apply(&point.view()).unwrap();
    println!(
        "  X-rotation: [{:.4}, {:.4}, {:.4}]",
        rotated_quat[0], rotated_quat[1], rotated_quat[2]
    );

    let rotated_euler = rot_euler.apply(&point.view()).unwrap();
    println!(
        "  Z-rotation: [{:.4}, {:.4}, {:.4}]",
        rotated_euler[0], rotated_euler[1], rotated_euler[2]
    );

    // Convert between representations
    println!("\nConverting between representations:");

    // Quaternion of the Z-rotation
    let quat_z = rot_euler.as_quat();
    println!(
        "  Quaternion of Z-rotation: [{:.4}, {:.4}, {:.4}, {:.4}]",
        quat_z[0], quat_z[1], quat_z[2], quat_z[3]
    );

    // Euler angles of the X-rotation (xyz convention)
    let euler_x = rot_quat.as_euler("xyz")?;
    println!(
        "  Euler angles of X-rotation: [{:.4}, {:.4}, {:.4}]",
        euler_x[0], euler_x[1], euler_x[2]
    );

    // Rotation vector of the Z-rotation
    let rotvec_z = rot_euler.as_rotvec();
    println!(
        "  Rotation vector of Z-rotation: [{:.4}, {:.4}, {:.4}]",
        rotvec_z[0], rotvec_z[1], rotvec_z[2]
    );

    // Compose rotations
    println!("\nComposing rotations:");
    let composed = rot_quat.compose(&rot_euler);
    let composed_point = composed.apply(&point.view()).unwrap();
    println!("  X-rotation followed by Z-rotation applied to [1, 0, 0]:");
    println!(
        "  Result: [{:.4}, {:.4}, {:.4}]",
        composed_point[0], composed_point[1], composed_point[2]
    );

    // Inverse rotation
    println!("\nInverse rotation:");
    let rot_inv = rot_euler.inv();
    let point_rotated = rot_euler.apply(&point.view()).unwrap();
    let point_back = rot_inv.apply(&point_rotated.view()).unwrap();
    println!(
        "  Original point: [{:.4}, {:.4}, {:.4}]",
        point[0], point[1], point[2]
    );
    println!(
        "  Rotated by Z-rotation: [{:.4}, {:.4}, {:.4}]",
        point_rotated[0], point_rotated[1], point_rotated[2]
    );
    println!(
        "  Then by inverse Z-rotation: [{:.4}, {:.4}, {:.4}]",
        point_back[0], point_back[1], point_back[2]
    );

    println!();
    Ok(())
}

#[allow(dead_code)]
fn rigid_transform_examples() -> SpatialResult<()> {
    println!("RigidTransform Examples");
    println!("----------------------");

    // Create a rigid transform from rotation and translation
    // Create an array first, then pass its view
    let euler_angles = array![0.0, 0.0, PI / 2.0];
    let rotation = Rotation::from_euler(&euler_angles.view(), "xyz")?;
    let translation = array![1.0, 2.0, 3.0];

    let transform =
        RigidTransform::from_rotation_and_translation(rotation.clone(), &translation.view())?;
    println!("Created a rigid transform with:");
    println!("  - 90° rotation around Z");
    println!("  - Translation [1, 2, 3]");

    // Create a rigid transform from a 4x4 matrix
    let matrix = array![
        [0.0, -1.0, 0.0, 1.0],
        [1.0, 0.0, 0.0, 2.0],
        [0.0, 0.0, 1.0, 3.0],
        [0.0, 0.0, 0.0, 1.0]
    ];
    let _transform_from_matrix = RigidTransform::from_matrix(&matrix.view())?;
    println!("\nCreated an equivalent rigid transform from 4x4 matrix");

    // Apply the transform to points
    let point1 = array![1.0, 0.0, 0.0];
    let point2 = array![0.0, 0.0, 0.0];

    let transformed1 = transform.apply(&point1.view()).unwrap();
    let transformed2 = transform.apply(&point2.view()).unwrap();

    println!("\nApplying the transform to points:");
    println!(
        "  [1, 0, 0] -> [{:.4}, {:.4}, {:.4}]",
        transformed1[0], transformed1[1], transformed1[2]
    );
    println!(
        "  [0, 0, 0] -> [{:.4}, {:.4}, {:.4}]",
        transformed2[0], transformed2[1], transformed2[2]
    );

    // Inverse transform
    let inverse = transform.inv().unwrap();
    println!("\nApplying the inverse transform:");

    let back1 = inverse.apply(&transformed1.view()).unwrap();
    println!(
        "  [{:.4}, {:.4}, {:.4}] -> [{:.4}, {:.4}, {:.4}]",
        transformed1[0], transformed1[1], transformed1[2], back1[0], back1[1], back1[2]
    );

    // Composition of transforms
    let transform2 = RigidTransform::from_rotation_and_translation(
        {
            let angles = array![PI / 2.0, 0.0, 0.0];
            Rotation::from_euler(&angles.view(), "xyz")?
        },
        &array![0.0, 0.0, 1.0].view(),
    )?;

    let composed = transform.compose(&transform2).unwrap();

    println!("\nComposing transforms:");
    println!("  Transform 1: 90° rotation around Z + translation [1, 2, 3]");
    println!("  Transform 2: 90° rotation around X + translation [0, 0, 1]");

    let composed_applied = composed.apply(&point1.view()).unwrap();
    println!(
        "  [1, 0, 0] -> [{:.4}, {:.4}, {:.4}]",
        composed_applied[0], composed_applied[1], composed_applied[2]
    );

    println!();
    Ok(())
}

#[allow(dead_code)]
fn slerp_examples() -> SpatialResult<()> {
    println!("Slerp (Spherical Linear Interpolation) Examples");
    println!("---------------------------------------------");

    // Create two rotations to interpolate between
    let rot1 = Rotation::identity(); // No rotation
    let euler_angles2 = array![0.0, 0.0, PI];
    let rot2 = Rotation::from_euler(&euler_angles2.view(), "xyz")?; // 180° around Z

    // Create a Slerp interpolator
    let slerp = Slerp::new(rot1, rot2)?;
    println!("Created a Slerp interpolator between:");
    println!("  - Identity rotation");
    println!("  - 180° rotation around Z");

    // Sample at different interpolation parameters
    println!("\nSampling at different interpolation parameters:");
    let test_point = array![1.0, 0.0, 0.0];

    let t_values = [0.0, 0.25, 0.5, 0.75, 1.0];
    for &t in &t_values {
        let rot_t = slerp.interpolate(t);
        let rotated = rot_t.apply(&test_point.view()).unwrap();

        println!(
            "  t = {:.2}: [{:.4}, {:.4}, {:.4}]",
            t, rotated[0], rotated[1], rotated[2]
        );
    }

    // Times for constant angular velocity
    println!("\nGenerating times for constant angular velocity:");
    let times = slerp.times(5);
    println!(
        "  Times: [{:.2}, {:.2}, {:.2}, {:.2}, {:.2}]",
        times[0], times[1], times[2], times[3], times[4]
    );

    println!();
    Ok(())
}

#[allow(dead_code)]
fn rotation_spline_examples() -> SpatialResult<()> {
    println!("RotationSpline Examples");
    println!("---------------------");

    // Create a sequence of rotations
    let rotations = vec![
        Rotation::identity(),
        {
            let angles = array![0.0, 0.0, PI / 2.0];
            Rotation::from_euler(&angles.view(), "xyz")?
        },
        {
            let angles = array![PI / 2.0, 0.0, PI / 2.0];
            Rotation::from_euler(&angles.view(), "xyz")?
        },
        {
            let angles = array![PI / 2.0, PI / 2.0, PI / 2.0];
            Rotation::from_euler(&angles.view(), "xyz")?
        },
    ];

    // Define times at which these rotations occur
    let times = vec![0.0, 1.0, 2.0, 3.0];

    // Create a rotation spline using default Slerp interpolation
    let mut spline = RotationSpline::new(&rotations, &times)?;
    println!("Created a rotation spline with 4 key rotations at times [0, 1, 2, 3]");
    println!(
        "Default interpolation type: {}",
        spline.interpolation_type()
    );

    // Sample the spline at various times using Slerp interpolation
    println!("\nSampling the spline at various times (Slerp interpolation):");
    let test_point = array![1.0, 0.0, 0.0];

    let sample_times = [0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0];
    for &t in &sample_times {
        let rot_t = spline.interpolate(t);
        let rotated = rot_t.apply(&test_point.view()).unwrap();

        println!(
            "  t = {:.1}: [{:.4}, {:.4}, {:.4}]",
            t, rotated[0], rotated[1], rotated[2]
        );
    }

    // Generate evenly spaced samples
    println!("\nGenerating 7 evenly spaced samples (Slerp):");
    let (sample_times, sample_rotations) = spline.sample(7);

    for i in 0..7 {
        let rotated = sample_rotations[i].apply(&test_point.view()).unwrap();
        println!(
            "  t = {:.2}: [{:.4}, {:.4}, {:.4}]",
            sample_times[i], rotated[0], rotated[1], rotated[2]
        );
    }

    // Switch to cubic interpolation
    println!("\nSwitching to cubic interpolation");
    spline.set_interpolation_type("cubic")?;
    println!("New interpolation type: {}", spline.interpolation_type());

    // Sample the spline with cubic interpolation
    println!("\nSampling the spline with cubic interpolation:");
    for &t in &sample_times {
        let rot_t = spline.interpolate(t);
        let rotated = rot_t.apply(&test_point.view()).unwrap();

        println!(
            "  t = {:.1}: [{:.4}, {:.4}, {:.4}]",
            t, rotated[0], rotated[1], rotated[2]
        );
    }

    // Calculate angular velocities
    println!("\nCalculating angular velocities at sample times:");
    for &t in &sample_times {
        let velocity = spline.angular_velocity(t).unwrap();

        println!(
            "  t = {:.1}: [{:.4}, {:.4}, {:.4}] rad/s",
            t, velocity[0], velocity[1], velocity[2]
        );
    }

    // Calculate angular accelerations
    println!("\nCalculating angular accelerations at sample times:");
    for &t in &sample_times {
        let acceleration = spline.angular_acceleration(t);

        println!(
            "  t = {:.1}: [{:.4}, {:.4}, {:.4}] rad/s²",
            t, acceleration[0], acceleration[1], acceleration[2]
        );
    }

    // Create a more complex spline for animation
    println!("\nCreating a complex spline for animation:");
    let animation_rotations = vec![
        Rotation::identity(),
        {
            let angles = array![0.0, PI / 4.0, 0.0];
            Rotation::from_euler(&angles.view(), "xyz")?
        },
        {
            let angles = array![PI / 4.0, PI / 4.0, 0.0];
            Rotation::from_euler(&angles.view(), "xyz")?
        },
        {
            let angles = array![PI / 4.0, 0.0, PI / 4.0];
            Rotation::from_euler(&angles.view(), "xyz")?
        },
        {
            let angles = array![0.0, 0.0, 0.0];
            Rotation::from_euler(&angles.view(), "xyz")?
        },
    ];

    let animation_times = vec![0.0, 1.0, 2.0, 3.0, 4.0];
    let mut animation_spline = RotationSpline::new(&animation_rotations, &animation_times)?;

    // Use cubic interpolation for smooth animation
    animation_spline.set_interpolation_type("cubic")?;

    println!("Creating a smooth animation with 9 frames:");
    let (anim_times, anim_rotations) = animation_spline.sample(9);

    for i in 0..9 {
        let rotated = anim_rotations[i].apply(&test_point.view()).unwrap();
        let velocity = animation_spline.angular_velocity(anim_times[i]).unwrap();

        println!(
            "  Frame {}: position [{:.4}, {:.4}, {:.4}], velocity magnitude: {:.4} rad/s",
            i,
            rotated[0],
            rotated[1],
            rotated[2],
            (velocity[0] * velocity[0] + velocity[1] * velocity[1] + velocity[2] * velocity[2])
                .sqrt()
        );
    }

    println!();
    Ok(())
}