sketchpunk / FunWithWebGL2

Fun with WebGL 2.0 Youtube Tutorial Series
666 stars 141 forks source link

Regarding lesson 123 on generating t-pose #26

Closed mmmovania closed 2 months ago

mmmovania commented 2 months ago

First, I would like to thank you for sharing your immense knowledge in the area of skeleton retargeting. I am trying to do it it Three.js for my own mixamo model that i just downloaded. The model is in A-pose with the left limb X local axes flipped to back as shown below. image

When I try to apply your align_dir function, it gives the following result where the arms are slighly higher. The same goes for the legs and my avatar looks more in a ballet dancer pose :P. image

Please share if you know how to fix this issue?

mmmovania commented 2 months ago

Some more updates: I notice that there is issue is also there in vegetta model. So it seems that my code is wrong in some part.

function align_dir( Lc, Rc, arm, up )
{
    let wp  = new THREE.Quaternion(),               // World Parent
        wc  = new THREE.Quaternion(),               // World Child
        lc  = new THREE.Quaternion(),               // Local Child
        lft = new THREE.Vector3(),
        fwd = new THREE.Vector3(),
        bb  = arm.bones,
        i   = Lc[0],
        d, b;

    //App.node.getWorldRot( bb[ i ], wp, false );   // Get Chain's parent world space rotation
    bb[i].getWorldQuaternion( wp );

    for( i=0; i < Lc.length; i++ )
    {
        b = bb[ Lc[i] ];            // Get Bone we're working on

        //wc.from_mul( wp, b.local.rot );   // Get Child's WS Rotation
        //wc.multiplyQuaternions(wp, b.quaternion );
        b.getWorldQuaternion( wc )

        // Define new rot axis with up pointing in the direction 
        // we need it to point While trying to keep the bone's 
        // forward direction in the same general direction
        //fwd.copy( Vec3.FORWARD ).transform_quat( wc );        // Bone's Forward Direction
        fwd.copy(Vec3_FORWARD).applyQuaternion(wc);

        //lft.from_cross( up, fwd ).normalize();                // Calc Left Direction
        lft.crossVectors(up, fwd).normalize(); 

        //fwd.from_cross( lft, up ).normalize();                // Realign Forward
        fwd.crossVectors(lft, up).normalize();

        //wc.from_axis( lft, up, fwd );                     // Final Aligned World Space Rotation
        wc = from_axis(lft, up, fwd);

        //lc.from_mul( wp.invert(), wc );                       // Convert to bone's Local Space
        lc.multiplyQuaternions(wp.invert(), wc);

        //d = Quat.dot( lc, b.local.rot );                  // Check for inverted Axis that can cause issues.
        d = lc.dot(b.quaternion);

        //if( d < 0 ) lc.negate();
        if( d < 0 ) 
            lc = negate(lc);

        //b.setRot( lc );                                       // Save Local to Bone
        b.quaternion.copy(lc);

        //bb[ Rc[i] ].Node.setRot( lc.mirror_x() );         // Mirror and save to other bone
        bb[ Rc[i] ].quaternion.copy(mirror_x( lc ) );

        //wp.copy( wc );                                        // World child becomes World Parent for next bone.
        wp.copy( wc.clone() ); 

    } 

    bb[i].getWorldQuaternion( wp ); 
    console.log(wp);
}

So it seems something is wrong in my code.

mmmovania commented 2 months ago

OK finally I got it. The issue was the following call before the loop bb[i].getWorldQuaternion( wp ); This is supposed to return the rotation of the first bone passed to align_dir function in world space. But compaing the results with the original code, this produces wrong output. So then I manually wrote my own function getWorldRot based on the framework of this tutorial and it produces correct output. Here are the final functions and outputs. It works fine for all the models I tested this on.

function getWorldRot( e, out = null, incChild=true ){
    out = out || new THREE.Quaternion();

    if( !e.parent ) 
        return out.copy( e.quaternion );

    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // Get the heirarchy nodes
    let n       = e,
        tree    = [ ];

    if( incChild ) tree.push( n ); // Incase we do not what to add the requested entity to the world transform.

    while( n.parent != null ){
        tree.push( n.parent );
        n = n.parent;
    }

    // Nothing
    if( tree.length == 0 ) return out.reset();

    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    let i = tree.length - 1;
    out.copy( tree[ i ].quaternion );                       // Copy in the Root Parent

    for( i--; i > -1; i-- )
        out.multiply( tree[ i ].quaternion );   // Add Up All Transforms from root to child.

    return out;
}

function align_dir( Lc, Rc, skeleton, up )
{
    let wp  = new THREE.Quaternion(),               // World Parent
        wc  = new THREE.Quaternion(),               // World Child
        lc  = new THREE.Quaternion(),               // Local Child
        lft = new THREE.Vector3(),
        fwd = new THREE.Vector3(),
        bb  = skeleton.bones,
        i   = Lc[0],
        d, b;

    //App.node.getWorldRot( bb[ i ], wp, false );   // Get Chain's parent world space rotation
    getWorldRot(bb[i], wp, false);

    for( i=0; i < Lc.length; i++ )
    {
        b = bb[ Lc[i] ];            // Get Bone we're working on

        //wc.from_mul( wp, b.local.rot );   // Get Child's WS Rotation
        wc.multiplyQuaternions(wp, b.quaternion );

        // Define new rot axis with up pointing in the direction 
        // we need it to point While trying to keep the bone's 
        // forward direction in the same general direction
        //fwd.copy( Vec3.FORWARD ).transform_quat( wc );        // Bone's Forward Direction
        fwd.copy(Vec3_FORWARD).applyQuaternion(wc );

        //lft.from_cross( up, fwd ).normalize();                // Calc Left Direction
        lft.crossVectors(up, fwd).normalize(); 

        //fwd.from_cross( lft, up ).normalize();                // Realign Forward
        fwd.crossVectors(lft, up).normalize();

        //wc.from_axis( lft, up, fwd );                     // Final Aligned World Space Rotation
        wc = from_axis(lft, up, fwd);

        //lc.from_mul( wp.invert(), wc );                       // Convert to bone's Local Space
        lc.multiplyQuaternions(wp.invert(), wc );

        //d = Quat.dot( lc, b.local.rot );                  // Check for inverted Axis that can cause issues.
        d = lc.dot(b.quaternion);

        //if( d < 0 ) lc.negate();
        if( d < 0 ) 
            lc = negate(lc);

        //b.setRot( lc );                                       // Save Local to Bone
        b.quaternion.copy(lc);

        //bb[ Rc[i] ].Node.setRot( lc.mirror_x() );         // Mirror and save to other bone
        bb[ Rc[i] ].quaternion.copy(mirror_x( lc ) );

        wp.copy( wc );                                      // World child becomes World Parent for next bone.

    }   

    //arm.isModified = true;
}

image