bitshifter / glam-rs

A simple and fast linear algebra library for games and graphics
Apache License 2.0
1.48k stars 151 forks source link

Add methods for swing-twist decomposition to `Quat` #536

Open cactusdualcore opened 1 month ago

cactusdualcore commented 1 month ago

I ran into a problem where I wanted to decompose a rotation into a part which rotates around a specified axis and perpendicular to it. This can be done using the swing-twist decomposition of quaternions.

I now implemented an alternative solution to my problem, but I think this library would still profit from this feature.

I implemented a crude function to do this for me based on this stackoverflow question.

/// Decompose the rotation on to 2 parts.
///
/// 1. Twist - rotation around the "direction" vector
/// 2. Swing - rotation around axis that is perpendicular to "direction" vector
///
/// The rotation can be composed back by
/// `rotation = swing * twist`.
/// Order matters!
///
/// has singularity in case of swing_rotation close to 180 degrees rotation.
/// if the input quaternion is of non-unit length, the outputs are non-unit as well
/// otherwise, outputs are both unit
fn swing_twist_decomposition(rotation: Quat, axis: Dir3) -> Option<(Quat, Quat)> {
    let rotation_axis = rotation.xyz();
    let projection = rotation_axis.project_onto(*axis);

    let twist = {
        let maybe_flipped_twist = Quat::from_vec4(projection.extend(rotation.w));
        if rotation_axis.dot(projection) < 0.0 {
            -maybe_flipped_twist
        } else {
            maybe_flipped_twist
        }
    };

    if twist.length_squared() != 0.0 {
        let swing = rotation * twist.conjugate();
        Some((twist.normalize(), swing))
    } else {
        None
    }
}

The above code is fully written by me, but the mechanism was adapted from a StackOverflow answer. I have no clues about licensing here. If I am legally allowed, I'd like to give up any rights to the code.

There seem to be many resources on this topic online. I have neither thoroughly tested nor benchmarked the above snipped and I assume it requires more polish. If desired, I could try putting together a PR for a full implementation?

cactusdualcore commented 1 month ago

There's a proposal for this for Godot too! https://github.com/godotengine/godot-proposals/issues/8906

johannesvollmer commented 1 month ago

I too was looking for this but couldn't find it in glam. This use case is valuable and general enough to be included. +1

Edit (Context): I'm trying to do something in bevy, which uses Quat to store rotation, and I want to find out the current rotation along a particular axis.