The previous rotation gets cleared when rotating around another axis

Started by ani.tumanyan, March 22, 2013, 08:29:29 PM

Previous topic - Next topic

ani.tumanyan

Hi raft,

I'm using JPCT-AE to display my ninja object on an Android phone. What I'm trying to do it to rotate different joints of the ninja. Because of the human structure people can rotate their neck around different axis (twist, bend, flexion and extension). But because all the rotations are with the same joint, I'm getting a weird problem. Once I have twisted the neck by some angle, and I'm trying to add some blending rotation the neck gets to it's original position and everything starts from scratch. I'm not sure what I'm doing wrong here, as I don't have a lot of experience in this field. Here is the section of performing rotation:


private void rotateJoint(SkeletonPose pose, int jointIndex, SimpleVector bindPoseDirection, float angle, final float targetStrength) {
   
        final int parentIndex = pose.getSkeleton().getJoint(jointIndex).getParentIndex();

        final Matrix jointInverseBindPose = pose.getSkeleton().getJoint(jointIndex).getInverseBindPose();
        final Matrix jointBindPose = jointInverseBindPose.invert();
       
        // Get a vector representing forward direction in neck space, use inverse to take from world -> neck space.
        SimpleVector forwardDirection = new SimpleVector(bindPoseDirection);
        forwardDirection.rotate(jointInverseBindPose);

        // Calculate a rotation to go from one direction to the other and set that rotation on a blank transform.
        Quaternion quat = new Quaternion();       
        quat.fromAngleAxis(angle, forwardDirection);
        quat.slerp(Quaternion.IDENTITY, quat, targetStrength);

        final Matrix subGlobal = quat.getRotationMatrix();
       
        subGlobal.matMul(jointBindPose);
        subGlobal.matMul(pose.getSkeleton().getJoint(parentIndex).getInverseBindPose());
       
        // set that as the neck's transform
        pose.getLocal(jointIndex).setTo(subGlobal);
    }





Ani

raft

yes, that's expected. every time you call that method for a specific bone, you overwrite previous values for that bone. those modifications are not cumulative.

this is the part which actually modifies that specific bone's transformation
pose.getLocal(jointIndex).setTo(subGlobal);

ani.tumanyan

Thanks raft!! But is it somehow feasible to combine the rotation modifications ?

raft

it depends on the application. if you are asking for performance, it's negligible. calculating and setting some transformation on a bone has almost no impact, expensive part is applying bone transformations to mesh.

ani.tumanyan

Now I'm really confused :(. I know that my questions might be very basic but please bear with me. In your first reply you told me that those modifications are not cumulative. What I really want to know is how to add the previous position of the neck joint to the rotation matrix before doing this action pose.getLocal(jointIndex).setTo(subGlobal);, so instead of resetting the joint to it's original position it will continue to rotate around ANOTHER axis.

raft

well just store that rotation (whatever it is) in a field of class and apply that cumulatively.

for example:

    Matrix rotation = new Matrix();
   
    private void targetJoint(SkeletonPose pose, int jointIndex, SimpleVector bindPoseDirection,
    SimpleVector targetPos, final float targetStrength) {
   
        // previous part is same in sample         

        if (jointIndex == 13) { // apply to only neck
        SimpleVector rotationAxis = new SimpleVector(0, 1, 0);
        rotation.rotateAxis(rotationAxis, 0.01f);
        quat.rotate(rotation);
        }
        final Matrix subGlobal = quat.getRotationMatrix();
       
        // now remove the global/world transform of the neck's parent bone, leaving us with just the local transform of
        // neck + rotation.
        subGlobal.matMul(jointBindPose);
        subGlobal.matMul(pose.getSkeleton().getJoint(parentIndex).getInverseBindPose());

        // set that as the neck's transform
        pose.getLocal(jointIndex).setTo(subGlobal);
    }


this actually turns the neck cumulatively, but since we used a bogus rotation axis, the results are also bogus

ani.tumanyan

Thanks raft!!! It saved me lots of time and effort.

This is eventually what I ended up doing, in case someone else would be wondering:

private void rotateJoint(SkeletonPose pose, Matrix rotation, int jointIndex, SimpleVector bindPoseDirection, float angle, final float targetStrength) {
    final int parentIndex = pose.getSkeleton().getJoint(jointIndex).getParentIndex();

        // neckBindGlobalTransform is the neck bone -> model space transform. essentially, it is the world transform of
        // the neck bone in bind pose.
        final Matrix jointInverseBindPose = pose.getSkeleton().getJoint(jointIndex).getInverseBindPose();
        final Matrix jointBindPose = jointInverseBindPose.invert();
       
        // Get a vector representing forward direction in neck space, use inverse to take from world -> neck space.
        SimpleVector forwardDirection = new SimpleVector(bindPoseDirection);
        forwardDirection.rotate(jointInverseBindPose);

        // Calculate a rotation to go from one direction to the other and set that rotation on a blank transform.
        Quaternion quat = new Quaternion();
    rotation.rotateAxis(bindPoseDirection, angle);
    quat.rotate(rotation);

        final Matrix subGlobal = quat.getRotationMatrix();
       
        // now remove the global/world transform of the neck's parent bone, leaving us with just the local transform of
        // neck + rotation.
        subGlobal.matMul(jointBindPose);
        subGlobal.matMul(pose.getSkeleton().getJoint(parentIndex).getInverseBindPose());

        // set that as the neck's transform
        pose.getLocal(jointIndex).setTo(subGlobal);
    }