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 grin ). 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.


Extensive Multiplayer tutorial:
http://mesetts.com/index.php?page=201