Posted By: EpsiloN
Posing - Bone rotation with Gizmos similar to 3D Studio Max - 07/30/15 08:22
Here's a script to rotate bone joints with gizmos (like in 3D Studio Max for example).
Before anything, you should know this...
All bones rotate with local angles, which means, if you rotate the shoulder, the elbow points in a different direction, but its local orientation remains the same! There is a function to get the bone orientation, but it returns the absolute orientation, not the local one!
By using local_pan/tilt/roll skills and manipulating them each time we rotate a bone, we can keep track of the local angles of a joint.
As a side note, its not implemented yet, but we could use a temp vector to store the local bone orientation before the wait(1) in the while loop of the gizmo. Then, when the loop begins again, we can check if this local angle changed and update the bone local angle too. This way we can set a bone's local orientation to a certain value outside of the gizmo function (for example, writing rShoulderGizmo.local_pan = 45, which will set the gizmo and bone local orientation to 45 degrees.) Just bonerotate if the old (before wait(1)) angles are different than the next iteration...
In the main function, create an entity with puppet_act function, start the gizmoController() function and start propagate_rotation() which will update the gizmo orientation and local angles down the chain for every joint.
The puppet_act function initializes the gizmos, meaning, it saves all joint positions, all bone handles and 'bias' between actual bone orientation and local bone orientation. It is not currently used, but you can use it to have the real bone orientation (where the bone is pointing).
In there we create the gizmos at the specific joint position, we save the parent entity of the gizmo (puppet_act) and we save the bone handle that the gizmo controls. We also save the parent gizmo for that given gizmo, in order to orient it correctly.
Function get_gizmo_parent() returns the handle of the parent gizmo if the current gizmo has a parent gizmo, otherwise 0.
Function propagate_rotation() orients all gizmos to the real local angles of the bones, by adding the parent gizmos local angles up the chain. There is some problem with this, but I just noticed it, so its not fixed. When you rotate a gizmo, its child gizmos will rotate at a strange axis not following the mouse any more...
Function torsoRotator() is the gizmo function (it was originally for the torso, so its not renamed yet to a more general name ). It just follows its joint x coordinates each frame and controls its FLAG2 to be ignored if another gizmo is being controlled right now...
Function gizmoController() is the function that controls the gizmos when you press the left mouse button.
If you hold the mouse button over the gizmo it traces to find the new point (where the mouse is) and calculates an axis perpendicular to new point and the original point (where you first clicked). It then rotates around that axis by the angle between the new and original points...
If you go outside the gizmo the function builds a vector with magnitude = the distance to the last gizmo controlled (which is somewhere around the gizmo...) and then traces a vector from the gizmo's origin (x/y/z) to the mouse vector that was build. Then, it scales that new vector with magnitude gizmo.max_x (which is the radius of the gizmo if c_setminmax was called) and this last vector is the new position on the visible edge of the gizmo...
Here's the code:
Function main() :
File def.c :
File gizmo.c :
File puppet.c :
My next goal is to implement an animation system with interpolation between key frames (saved from all local gizmo angles), but first I have to fix the rotation problem with the propagation function.
When I fix it, if I dont forget, I will post the update.
Before anything, you should know this...
All bones rotate with local angles, which means, if you rotate the shoulder, the elbow points in a different direction, but its local orientation remains the same! There is a function to get the bone orientation, but it returns the absolute orientation, not the local one!
By using local_pan/tilt/roll skills and manipulating them each time we rotate a bone, we can keep track of the local angles of a joint.
As a side note, its not implemented yet, but we could use a temp vector to store the local bone orientation before the wait(1) in the while loop of the gizmo. Then, when the loop begins again, we can check if this local angle changed and update the bone local angle too. This way we can set a bone's local orientation to a certain value outside of the gizmo function (for example, writing rShoulderGizmo.local_pan = 45, which will set the gizmo and bone local orientation to 45 degrees.) Just bonerotate if the old (before wait(1)) angles are different than the next iteration...
In the main function, create an entity with puppet_act function, start the gizmoController() function and start propagate_rotation() which will update the gizmo orientation and local angles down the chain for every joint.
The puppet_act function initializes the gizmos, meaning, it saves all joint positions, all bone handles and 'bias' between actual bone orientation and local bone orientation. It is not currently used, but you can use it to have the real bone orientation (where the bone is pointing).
In there we create the gizmos at the specific joint position, we save the parent entity of the gizmo (puppet_act) and we save the bone handle that the gizmo controls. We also save the parent gizmo for that given gizmo, in order to orient it correctly.
Function get_gizmo_parent() returns the handle of the parent gizmo if the current gizmo has a parent gizmo, otherwise 0.
Function propagate_rotation() orients all gizmos to the real local angles of the bones, by adding the parent gizmos local angles up the chain. There is some problem with this, but I just noticed it, so its not fixed. When you rotate a gizmo, its child gizmos will rotate at a strange axis not following the mouse any more...
Function torsoRotator() is the gizmo function (it was originally for the torso, so its not renamed yet to a more general name ). It just follows its joint x coordinates each frame and controls its FLAG2 to be ignored if another gizmo is being controlled right now...
Function gizmoController() is the function that controls the gizmos when you press the left mouse button.
If you hold the mouse button over the gizmo it traces to find the new point (where the mouse is) and calculates an axis perpendicular to new point and the original point (where you first clicked). It then rotates around that axis by the angle between the new and original points...
If you go outside the gizmo the function builds a vector with magnitude = the distance to the last gizmo controlled (which is somewhere around the gizmo...) and then traces a vector from the gizmo's origin (x/y/z) to the mouse vector that was build. Then, it scales that new vector with magnitude gizmo.max_x (which is the radius of the gizmo if c_setminmax was called) and this last vector is the new position on the visible edge of the gizmo...
Here's the code:
Function main() :
Code:
... puppet = ent_create( "HumanCasualRigged.mdl" , NULLVECTOR , puppet_act ); gizmoController(); mouse_mode = 2; mouse_map = arrow_bmp; while(1) { if(mouse_right == 1) { if(mouse_mode == 2) { mouse_mode = 0; } } else { mouse_mode = 2; } vec_set( mouse_pos.x , mouse_cursor ); propagate_rotation(); wait(1); } ...
File def.c :
Code:
// ------------------------- // --- PUPPET AND GIZMOS --- // ------------------------- ENTITY* puppet; ENTITY* gizmo; ENTITY* neckGizmoX; ENTITY* chestGizmoX; // Right arm ENTITY* rsGizmoX; ENTITY* reGizmoX; ENTITY* rwGizmoX; // Left arm ENTITY* lsGizmoX; ENTITY* leGizmoX; ENTITY* lwGizmoX; // Right leg ENTITY* rhGizmoX; ENTITY* rkGizmoX; ENTITY* raGizmoX; // Left leg ENTITY* lhGizmoX; ENTITY* lkGizmoX; ENTITY* laGizmoX; // Objects definitions #define gizmoObject 1 // Gizmo properties #define object_type skill4 #define parent_entity skill5 #define bone_handle skill6 #define local_pan skill7 #define local_tilt skil8 #define local_roll skil9 #define parent_gizmo skill40
File gizmo.c :
Code:
ENTITY* currentGizmoControlled; function get_gizmo_parent( ENTITY* childEnt ) { me = childEnt; you = ent_next( NULL ); while( you ) { if( handle( you ) == my.parent_gizmo ) { return you; } you = ent_next( you ); } return 0; } function propagate_rotation() { ENTITY* parentGizmo; ENTITY* currentGizmo; currentGizmo = ent_next( NULL ); while( currentGizmo ) { if( currentGizmo.object_type == gizmoObject ) { vec_set( currentGizmo.pan , NULLVECTOR ); parentGizmo = get_gizmo_parent( currentGizmo ); while( parentGizmo ) { ang_rotate( currentGizmo.pan , parentGizmo.local_pan ); parentGizmo = get_gizmo_parent( parentGizmo); } } ang_rotate( currentGizmo.pan , currentGizmo.local_pan ); currentGizmo = ent_next( currentGizmo ); } } function torsoRotator() { my.object_type = gizmoObject; wait(1); my.alpha = 30; set( my , TRANSLUCENT ); set( my , UNLIT ); my.ambient = 100; set( my , POLYGON ); vec_set( my.scale_x , vector( 0.33 , 0.33 , 0.33 ) ); wait(1); c_setminmax( me ); ENTITY* parentEnt; while(1) { if( mouse_right == 1 ) { set( my , INVISIBLE ); } else { reset( my , INVISIBLE ); } parentEnt = ptr_for_handle( my.parent_entity ); if( parentEnt != NULL ) { vec_for_bone( my.x , parentEnt , (long)my.bone_handle ); } if( currentGizmoControlled != NULL ) { if( currentGizmoControlled != me ) { set( my , FLAG2 ); } else { reset( my , FLAG2 ); } } else { reset( my , FLAG2 ); } wait(1); } } function gizmoController() { VECTOR vPos, vTarget; ENTITY* entParent; VECTOR vDir1; VECTOR vDir2; ANGLE angOld; ANGLE localAngOld; VECTOR vAxis; ANGLE angRot; while(1) { if(mouse_left == 1) { vec_set( vPos, camera.x ); vec_set( vTarget, mouse_dir3d ); vec_scale( vTarget, 1000 ); vec_add( vTarget, vPos ); c_trace( vPos, vTarget, IGNORE_FLAG2 ); if ( HIT_TARGET ) { currentGizmoControlled = you; vec_diff ( vDir1, hit.x, you.x ); vec_normalize ( vDir1, 1 ); vec_set ( angOld, you.pan ); vec_set ( localAngOld , you.local_pan ); you.alpha = 90; while ( mouse_left ) { vec_set ( vPos, camera.x ); vec_set ( vTarget, mouse_dir3d ); vec_scale ( vTarget, 1000 ); vec_add ( vTarget, vPos ); c_trace ( vPos, vTarget, IGNORE_FLAG2 ); if ( HIT_TARGET ) { if ( you == currentGizmoControlled ) { draw_point3d ( hit.x, COLOR_WHITE, 100, 1 ); vec_diff ( vDir2, hit.x, you.x ); vec_normalize ( vDir2, 1 ); vec_cross ( vAxis , vDir1 , vDir2 ); ang_for_axis( angRot, vAxis, acosv ( vec_dot ( vDir1 , vDir2 ) ) ); vec_set ( you.pan, angOld ); ang_add( you.pan, angRot ); vec_set ( you.local_pan , localAngOld ); ang_add( you.local_pan , angRot ); entParent = ptr_for_handle( you.parent_entity ); if( entParent != NULL ) { ent_bonereset( entParent , (long)you.bone_handle ); ent_bonerotate( entParent , (long)you.bone_handle , you.local_pan ); } } else { vec_set ( vPos, camera.x ); vec_set ( vTarget, mouse_dir3d ); vec_scale ( vTarget, vec_dist( camera.x , currentGizmoControlled.x ) ); vec_add ( vTarget, vPos ); vec_set( vDir2 , vTarget ); vec_sub( vDir2 , currentGizmoControlled.x ); vec_normalize( vDir2 , currentGizmoControlled.max_x ); vec_add( vDir2 , currentGizmoControlled.x ); draw_point3d ( vDir2 , COLOR_RED , 100, 1 ); vec_diff ( vDir2, vDir2 , currentGizmoControlled.x ); vec_normalize ( vDir2, 1 ); vec_cross ( vAxis , vDir1 , vDir2 ); ang_for_axis( angRot, vAxis, acosv ( vec_dot ( vDir1 , vDir2 ) ) ); vec_set ( currentGizmoControlled.pan, angOld ); ang_add( currentGizmoControlled.pan, angRot ); vec_set ( currentGizmoControlled.local_pan , localAngOld ); ang_add( currentGizmoControlled.local_pan , angRot ); entParent = ptr_for_handle( currentGizmoControlled.parent_entity ); if( entParent != NULL ) { ent_bonereset( entParent , (long)currentGizmoControlled.bone_handle ); ent_bonerotate( entParent , (long)currentGizmoControlled.bone_handle , currentGizmoControlled.local_pan ); } } } else { vec_set ( vPos, camera.x ); vec_set ( vTarget, mouse_dir3d ); vec_scale ( vTarget, vec_dist( camera.x , currentGizmoControlled.x ) ); vec_add ( vTarget, vPos ); vec_set( vDir2 , vTarget ); vec_sub( vDir2 , currentGizmoControlled.x ); vec_normalize( vDir2 , currentGizmoControlled.max_x ); vec_add( vDir2 , currentGizmoControlled.x ); draw_point3d ( vDir2 , COLOR_RED , 100, 1 ); vec_diff ( vDir2, vDir2 , currentGizmoControlled.x ); vec_normalize ( vDir2, 1 ); vec_cross ( vAxis , vDir1 , vDir2 ); ang_for_axis( angRot, vAxis, acosv ( vec_dot ( vDir1 , vDir2 ) ) ); vec_set ( currentGizmoControlled.pan, angOld ); ang_add( currentGizmoControlled.pan, angRot ); vec_set ( currentGizmoControlled.local_pan , localAngOld ); ang_add( currentGizmoControlled.local_pan , angRot ); entParent = ptr_for_handle( currentGizmoControlled.parent_entity ); if( entParent != NULL ) { ent_bonereset( entParent , (long)currentGizmoControlled.bone_handle ); ent_bonerotate( entParent , (long)currentGizmoControlled.bone_handle , currentGizmoControlled.local_pan ); } } DEBUG_VAR ( currentGizmoControlled.pan, 10 ); DEBUG_VAR ( currentGizmoControlled.tilt, 40 ); DEBUG_VAR ( currentGizmoControlled.roll, 70 ); wait(1); } currentGizmoControlled.alpha = 30; currentGizmoControlled = NULL; } } else { vec_set( vPos, camera.x ); vec_set( vTarget, mouse_dir3d ); vec_scale( vTarget, 1000 ); vec_add( vTarget, vPos ); c_trace( vPos, vTarget, IGNORE_FLAG2 ); if ( HIT_TARGET ) { if( currentGizmoControlled != NULL ) { currentGizmoControlled.alpha = 30; } currentGizmoControlled = you; you.alpha = 90; } else { if( currentGizmoControlled != NULL ) { currentGizmoControlled.alpha = 30; currentGizmoControlled = NULL; } } } wait(1); } }
File puppet.c :
Code:
STRING* boneName_str = ""; function puppet_act() { set( my , UNTOUCHABLE ); my.flags2 |= UNTOUCHABLE; my.alpha = 50; set( my , TRANSLUCENT ); // ------------------------- // -- GET JOINT POSITIONS -- // ------------------------- // // *** Right body *** // . Upper VECTOR* rShoulder[3]; vec_for_bone( rShoulder.x , my , "RShoulder" ); VECTOR* rElbow[3]; vec_for_bone( rElbow.x , my , "RElbow" ); VECTOR* rWrist[3]; vec_for_bone( rWrist.x , my , "RWrist" ); VECTOR* rMidFingerBase[3]; vec_for_bone( rMidFingerBase.x , my , "RMidFinger" ); // . Lower VECTOR* rHip[3]; vec_for_bone( rHip.x , my , "RHip" ); VECTOR* rKnee[3]; vec_for_bone( rKnee.x , my , "RKnee" ); VECTOR* rAnkle[3]; vec_for_bone( rAnkle.x , my , "RAnkle" ); VECTOR* rFoot[3]; vec_for_bone( rFoot.x , my , "RFoot" ); // *** Left Body *** // . Upper VECTOR* lShoulder[3]; vec_for_bone( lShoulder.x , my , "LShoulder" ); VECTOR* lElbow[3]; vec_for_bone( lElbow.x , my , "LElbow" ); VECTOR* lWrist[3]; vec_for_bone( lWrist.x , my , "LWrist" ); VECTOR* lMidFingerBase[3]; vec_for_bone( lMidFingerBase.x , my , "LMidFinger" ); // . Lower VECTOR* lHip[3]; vec_for_bone( lHip.x , my , "LHip" ); VECTOR* lKnee[3]; vec_for_bone( lKnee.x , my , "LKnee" ); VECTOR* lAnkle[3]; vec_for_bone( lAnkle.x , my , "LAnkle" ); VECTOR* lFoot[3]; vec_for_bone( lFoot.x , my , "LFoot" ); // * Middle Body VECTOR* Neck[3]; vec_for_bone( Neck.x , my , "Neck" ); VECTOR* SkullBase[3]; vec_for_bone( SkullBase.x , my , "SkullBase" ); VECTOR* Chest[3]; vec_for_bone( Chest.x , my , "Chest" ); VECTOR* Base[3]; vec_for_bone( Base.x , my , "Base" ); // ---------------------------- // -- GET JOINT ORIENTATIONS -- // ---------------------------- VECTOR* tempBiasVec[3]; // *** Right Upper Body *** // rShoulder ANGLE* rShoulderBias[3]; vec_set( tempBiasVec.x , rElbow.x ); vec_sub( tempBiasVec.x , rShoulder.x ); vec_to_angle( rShoulderBias.pan , tempBiasVec.x ); // rElbow ANGLE* rElbowBias[3]; vec_set( tempBiasVec.x , rWrist.x ); vec_sub( tempBiasVec.x , rElbow.x ); vec_to_angle( rElbowBias.pan , tempBiasVec.x ); // rWrist ANGLE* rWristBias[3]; vec_set( tempBiasVec.x , rMidFingerBase.x ); vec_sub( tempBiasVec.x , rWrist.x ); vec_to_angle( rWristBias.pan , tempBiasVec.x ); // *** Left Upper Body *** // lShoulder ANGLE* lShoulderBias[3]; vec_set( tempBiasVec.x , lElbow.x ); vec_sub( tempBiasVec.x , lShoulder.x ); vec_to_angle( lShoulderBias.pan , tempBiasVec.x ); // lElbow ANGLE* lElbowBias[3]; vec_set( tempBiasVec.x , lWrist.x ); vec_sub( tempBiasVec.x , lElbow.x ); vec_to_angle( lElbowBias.pan , tempBiasVec.x ); // lWrist ANGLE* lWristBias[3]; vec_set( tempBiasVec.x , lMidFingerBase.x ); vec_sub( tempBiasVec.x , lWrist.x ); vec_to_angle( lWristBias.pan , tempBiasVec.x ); // *** Lower Body *** // Right side ANGLE* rHipBias[3]; vec_set( tempBiasVec.x , rKnee.x ); vec_sub( tempBiasVec.x , rHip.x ); vec_to_angle( rHipBias.pan , tempBiasVec.x ); ANGLE* rKneeBias[3]; vec_set( tempBiasVec.x , rAnkle.x ); vec_sub( tempBiasVec.x , rKnee.x ); vec_to_angle( rKneeBias.pan , tempBiasVec.x ); ANGLE* rAnkleBias[3]; vec_set( tempBiasVec.x , rFoot.x ); vec_sub( tempBiasVec.x , rAnkle.x ); vec_to_angle( rAnkleBias.pan , tempBiasVec.x ); // Left Side ANGLE* lHipBias[3]; vec_set( tempBiasVec.x , lKnee.x ); vec_sub( tempBiasVec.x , lHip.x ); vec_to_angle( lHipBias.pan , tempBiasVec.x ); ANGLE* lKneeBias[3]; vec_set( tempBiasVec.x , lAnkle.x ); vec_sub( tempBiasVec.x , lKnee.x ); vec_to_angle( lKneeBias.pan , tempBiasVec.x ); ANGLE* lAnkleBias[3]; vec_set( tempBiasVec.x , lFoot.x ); vec_sub( tempBiasVec.x , lAnkle.x ); vec_to_angle( lAnkleBias.pan , tempBiasVec.x ); // *** Middle Body *** // Neck ANGLE* NeckBias[3]; vec_set( tempBiasVec.x , SkullBase.x ); vec_sub( tempBiasVec.x , Neck.x ); vec_to_angle( NeckBias.pan , tempBiasVec.x ); // Chest ANGLE* ChestBias[3]; vec_set( tempBiasVec.x , Chest.x ); vec_sub( tempBiasVec.x , Base.x ); vec_to_angle( ChestBias.pan , tempBiasVec.x ); // ------------------ // -- BONE HANDLES -- // ------------------ // // *** Upper Body *** // . Right side var rShoulderHandle = 0; var rElbowHandle = 0; var rWristHandle = 0; var rMidFingerBaseHandle = 0; // . Left Side var lShoulderHandle = 0; var lElbowHandle = 0; var lWristHandle = 0; var lMidFingerBaseHandle = 0; // *** Middle Body *** var neckHandle = 0; var chestHandle = 0; // *** Lower Body *** // . Right Side var rHipHandle = 0; var rKneeHandle = 0; var rAnkleHandle = 0; var rFootHandle = 0; // . Left Side var lHipHandle = 0; var lKneeHandle = 0; var lAnkleHandle = 0; var lFootHandle = 0; var boneHandle; var i = 0; for(i = 0; i < ent_bones( my ); i++ ) { boneHandle = ent_bonehandle( my , boneName_str , i ); // Right Upper if(str_cmpi(boneName_str,"RElbow") == 1) { rElbowHandle = boneHandle; } if(str_cmpi(boneName_str,"RShoulder") == 1) { rShoulderHandle = boneHandle; } if(str_cmpi(boneName_str,"RWrist") == 1) { rWristHandle = boneHandle; } if(str_cmpi(boneName_str,"RMidFinger") == 1) { rMidFingerBaseHandle = boneHandle; } // Left Upper if(str_cmpi(boneName_str,"LElbow") == 1) { lElbowHandle = boneHandle; } if(str_cmpi(boneName_str,"LShoulder") == 1) { lShoulderHandle = boneHandle; } if(str_cmpi(boneName_str,"LWrist") == 1) { lWristHandle = boneHandle; } if(str_cmpi(boneName_str,"LMidFinger") == 1) { lMidFingerBaseHandle = boneHandle; } // Middle Body if(str_cmpi(boneName_str,"Neck") == 1) { neckHandle = boneHandle; } if(str_cmpi(boneName_str,"Chest") == 1) { chestHandle = boneHandle; } // Lower Body // Right Side if(str_cmpi(boneName_str,"RHip") == 1) { rHipHandle = boneHandle; } if(str_cmpi(boneName_str,"RKnee") == 1) { rKneeHandle = boneHandle; } if(str_cmpi(boneName_str,"RAnkle") == 1) { rAnkleHandle = boneHandle; } if(str_cmpi(boneName_str,"RFoot") == 1) { rFootHandle = boneHandle; } // Left Side if(str_cmpi(boneName_str,"LHip") == 1) { lHipHandle = boneHandle; } if(str_cmpi(boneName_str,"LKnee") == 1) { lKneeHandle = boneHandle; } if(str_cmpi(boneName_str,"LAnkle") == 1) { lAnkleHandle = boneHandle; } if(str_cmpi(boneName_str,"LFoot") == 1) { lFootHandle = boneHandle; } } chestGizmoX = ent_create( "Sphere.mdl" , Chest.x , torsoRotator ); chestGizmoX.parent_entity = handle(me); chestGizmoX.bone_handle = chestHandle; neckGizmoX = ent_create( "Sphere.mdl" , Neck.x , torsoRotator ); neckGizmoX.parent_entity = handle(me); neckGizmoX.parent_gizmo = handle(chestGizmoX); neckGizmoX.bone_handle = neckHandle; rsGizmoX = ent_create( "Sphere.mdl" , rShoulder.x , torsoRotator ); rsGizmoX.parent_entity = handle(me); rsGizmoX.parent_gizmo = handle(neckGizmoX); rsGizmoX.bone_handle = rShoulderHandle; reGizmoX = ent_create( "Sphere.mdl" , rElbow.x , torsoRotator ); reGizmoX.parent_entity = handle(me); reGizmoX.parent_gizmo = handle(rsGizmoX); reGizmoX.bone_handle = rElbowHandle; rwGizmoX = ent_create( "Sphere.mdl" , rWrist.x , torsoRotator ); rwGizmoX.parent_entity = handle(me); rwGizmoX.parent_gizmo = handle(reGizmoX); rwGizmoX.bone_handle = rWristHandle; while(1) { if( mouse_right == 1 ) { reset( my , TRANSLUCENT ); my.alpha = 100; } else { set( my , TRANSLUCENT ); my.alpha = 30; } wait(1); } }
My next goal is to implement an animation system with interpolation between key frames (saved from all local gizmo angles), but first I have to fix the rotation problem with the propagation function.
When I fix it, if I dont forget, I will post the update.