Rotate a bone in hierarhy and then rotate it back


#1

hello,

I have a skeleton for which I made a script to rotate some of its bones. I’ve bind it to ms button and by the ‘rightclick’ on it I want to rotate bones to *(-1)

so I’ve a made a function out of this script. and have two case on single button ‘pressed’ and ‘rightclick’

surprisingly ‘righclick’ doesn’t correctly rotate the bones back but with slight deflection

how to rotate bones and then rotate them back into same position?

the method I use is:

bnNames = #($j_shoulder_ri, $j_shoulder_le)
for o in bnNames do o.transform = (rotateXmatrix -20) * o.transform

#2

you probably have to rotate them in ‘parent’ coordinate system …
the simplest way is:

in coordsys parent rotate <bones> (eulerangles <x> <y> <z>)

#3

thanks. but I already setup all the angles. how can I use this data with ‘coordsys parent’?


#4

btw I have local rotations for bones, like X=-20, Y=5, Z=-35
is it possible to translate this (?):

bnNames = #($j_shoulder_ri, $j_shoulder_le)
for o in bnNames do o.transform = (rotateXmatrix -20) * o.transform
for o in bnNames do o.transform = (rotateYmatrix 5) * o.transform
for o in bnNames do o.transform = (rotateZmatrix -35) * o.transform

into this:

bnNames = #($j_shoulder_ri, $j_shoulder_le)
in coordsys parent rotate bnNames (eulerangles -20 5 -35)

when I do it straight the result is not correct.


#5
obj.transform = (rotateXmatrix -20) * obj.transform
obj.transform = (rotateYmatrix 5) * obj.transform
obj.transform = (rotateZmatrix -35) * obj.transform

is the same as:

obj.transform = (rotateXmatrix -20)  *  (rotateYmatrix 5)  *  (rotateZmatrix -35)  * obj.transform
-- or
obj.transform = (eulerAngles -20 5 -35) as matrix3 * obj.transform
-- or
rotate obj (eulerAngles -20 5 -35)

#6

in case of rotate it has to be

in coordsys world about pivot rotate obj (eulerAngles -20 5 -35)

but for any hierarchical system you should use transformations in the parent coordinate system.
only root of whole hierarchy might be transformed in world


#7

thanks for the tips, denis! that’s usefull

however - as I mentioned - I already rotated bones (in their local coord). the number of bones is huge, it also been done not for only a single skeleton.

you gave a good advice on to rotate them parentally, but how to convert these data I have for ‘local’ rorations into ‘parent’?


#8

you have your data not probably in local… you rotate now as i can see in absolute coordinate space


#9

what makes you think that?
how I rorate: make coord. system Local then choose bones and rotate


#10

my bad… yes, you rotate in local. and it’s OK if you want to offset rotation from some previous value.
but in animation usually we need to set a value (not offset). if so we need to do it parent coordinate system .

the question is - do you want to offset or set the rotation?


#11

Well I have a character in pose that doesn’t fit my needs. I simply rotate some bones localy.
I can do it straight with the code I posted. But what I additionally what is to have possibility to rotate bones into initial pose.

the animations (that I already have) will be added later and - in my opinion - they should take any pose I do.

yes, I want offset! and thing is I already have those offset values but can’t use it cirrectly for the back rotations


#12

ok… let’s do it for offset.
if you do euler rotations in order X, Y, Z to return it back you have to it in reversed order -Z, -Y, -X

if you apply rotation matrices (rotateXmatrix X) * (rotateYmatrix Y) * (rotateZmatrix Z) then you have to apply inverse matrices in reversed order inverse (rotateZmatrix Z) * inverse (rotateYmatrix Y) * inverse (rotateXmatrix X)

but in you apply a final rotation matrix TM then you just have to multiply the result with inverse TM

using matrices:

rot_tm = (eulerangles 30 40 50) as matrix3

-- rotate
$.transform = rot_tm * $.transform
-- rotate back
$.transform = (inverse rot_tm) * $.transform

understanding of eulers:

qt = eulerToQuat (eulerangles 30 40 50) order:1 --XYZ
-- (quat -0.0808047 -0.402198 -0.303372 0.860042)

inverse qt
-- (quat 0.0808047 0.402199 0.303372 0.860042)

inv_qt = eulerToQuat (eulerangles -50 -40 -30) order:6 --ZYX
-- (quat 0.0808047 0.402198 0.303372 0.860042)

#13

thanks! seems like this is something I’m looking for.

only this code:

bnNames = #($j_shoulder_ri, $j_shoulder_le)
rot_tm = (eulerangles -20 5 25) as matrix3
for i=1 to bnNames1.count do (bnNames[i].transform = rot_tm * bnNames[i].transform)

doesn’t perform the same transformations as mine version:

bnNames = #($j_shoulder_ri, $j_shoulder_le)
for o in bnNames do o.transform = (rotateXmatrix -20) * o.transform
for o in bnNames do o.transform = (rotateYmatrix 5) * o.transform
for o in bnNames do o.transform = (rotateZmatrix 25) * o.transform

p.s.:
also maybe you help me to optimize this monster:

bnNames1 = #($j_shoulder_ri, $j_shoulder_le)
	rot_tm1 = (eulerangles -20 5 25) as matrix3

bnNames2 = #($j_elbow_ri, $j_elbow_bulge_ri, $j_elbow_le, $j_elbow_bulge_le)
	rot_tm2 = (eulerangles 0 0 20) as matrix3

bnNames3 = #($j_wrist_ri, $j_wrist_le)
	rot_tm3 = (eulerangles 10 -8 -5) as matrix3

bnNames4 = #($j_hip_ri, $j_hip_le)
	rot_tm4 = (eulerangles -7 -3 0) as matrix3

bnNames5 = #($j_ankle_ri, $j_ankle_le)
	rot_tm5 = (eulerangles 3 0 0) as matrix3

bnNames6 = #($j_wrist_ri, $j_wrist_le)
	rot_tm6 = (eulerangles 20 20 10) as matrix3

bnNames7 = #($j_thumb_ri_1, $j_thumb_le_1)
	rot_tm7 = (eulerangles -10 -10 0) as matrix3

bnNames8 = #($j_thumb_ri_2, $j_thumb_le_2)
	rot_tm8 = (eulerangles 0 0 -20) as matrix3

bnNames9 = #($j_thumb_ri_3, $j_pinky_ri_2, $j_index_ri_2, $j_thumb_le_3, $j_pinky_le_2, $j_index_le_2)
	rot_tm9 = (eulerangles 0 0 -15) as matrix3

bnNames10 = #($j_pinky_ri_1, $j_pinky_le_1)
	rot_tm10 = (eulerangles 0 -10 -15) as matrix3

bnNames11 = #($j_mid_ri_3, $j_mid_le_3, $j_ring_ri_3, $j_ring_le_3)
	rot_tm11 = (eulerangles 0 0 -35) as matrix3

bnNames12 = #($j_mid_ri_1, $j_mid_le_1)
	rot_tm12 = (eulerangles 0 -7 0) as matrix3

bnNames13 = #($j_pinky_ri_3, $j_pinky_le_3)
	rot_tm13 = (eulerangles 0 0 -25) as matrix3

bnNames14 = #($j_index_ri_3, $j_index_le_3)
	rot_tm14 = (eulerangles 0 0 -25) as matrix3

bnNames15 = #($j_mid_ri_2, $j_ring_ri_2, $j_mid_le_2, $j_ring_le_2)
	rot_tm15 = (eulerangles 0 0 -30) as matrix3

on PPose pressed do (
for i=1 to bnNames1.count do (bnNames1[i].transform = rot_tm1 * bnNames1[i].transform)
for i=1 to bnNames2.count do (bnNames2[i].transform = rot_tm2 * bnNames2[i].transform)
for i=1 to bnNames3.count do (bnNames3[i].transform = rot_tm3 * bnNames3[i].transform)
for i=1 to bnNames4.count do (bnNames4[i].transform = rot_tm4 * bnNames4[i].transform)
for i=1 to bnNames5.count do (bnNames5[i].transform = rot_tm5 * bnNames5[i].transform)
for i=1 to bnNames6.count do (bnNames6[i].transform = rot_tm6 * bnNames6[i].transform)
for i=1 to bnNames7.count do (bnNames7[i].transform = rot_tm7 * bnNames7[i].transform)
for i=1 to bnNames8.count do (bnNames8[i].transform = rot_tm8 * bnNames8[i].transform)
for i=1 to bnNames9.count do (bnNames9[i].transform = rot_tm9 * bnNames9[i].transform)
for i=1 to bnNames10.count do (bnNames10[i].transform = rot_tm10 * bnNames10[i].transform)
for i=1 to bnNames11.count do (bnNames11[i].transform = rot_tm11 * bnNames11[i].transform)
for i=1 to bnNames12.count do (bnNames12[i].transform = rot_tm12 * bnNames12[i].transform)
for i=1 to bnNames13.count do (bnNames13[i].transform = rot_tm13 * bnNames13[i].transform)
for i=1 to bnNames14.count do (bnNames14[i].transform = rot_tm14 * bnNames14[i].transform)
for i=1 to bnNames15.count do (bnNames15[i].transform = rot_tm15 * bnNames15[i].transform)
)

on PPose rightclick do (
for i=1 to bnNames1.count do (bnNames1[i].transform = (inverse rot_tm1) * bnNames1[i].transform)
for i=1 to bnNames2.count do (bnNames2[i].transform = (inverse rot_tm2) * bnNames2[i].transform)
for i=1 to bnNames3.count do (bnNames3[i].transform = (inverse rot_tm3) * bnNames3[i].transform)
for i=1 to bnNames4.count do (bnNames4[i].transform = (inverse rot_tm4) * bnNames4[i].transform)
for i=1 to bnNames5.count do (bnNames5[i].transform = (inverse rot_tm5) * bnNames5[i].transform)
for i=1 to bnNames6.count do (bnNames6[i].transform = (inverse rot_tm6) * bnNames6[i].transform)
for i=1 to bnNames7.count do (bnNames7[i].transform = (inverse rot_tm7) * bnNames7[i].transform)
for i=1 to bnNames8.count do (bnNames8[i].transform = (inverse rot_tm8) * bnNames8[i].transform)
for i=1 to bnNames9.count do (bnNames9[i].transform = (inverse rot_tm9) * bnNames9[i].transform)
for i=1 to bnNames10.count do (bnNames10[i].transform = (inverse rot_tm10) * bnNames10[i].transform)
for i=1 to bnNames11.count do (bnNames11[i].transform = (inverse rot_tm11) * bnNames11[i].transform)
for i=1 to bnNames12.count do (bnNames12[i].transform = (inverse rot_tm12) * bnNames12[i].transform)
for i=1 to bnNames13.count do (bnNames13[i].transform = (inverse rot_tm13) * bnNames13[i].transform)
for i=1 to bnNames14.count do (bnNames14[i].transform = (inverse rot_tm14) * bnNames14[i].transform)
for i=1 to bnNames15.count do (bnNames15[i].transform = (inverse rot_tm15) * bnNames15[i].transform)
)

#14

optimization…
first of all we have to understand that optimization equals organization.

Shorter code doesn’t mean better!
We must organize our code in such a way as not to repeat a part of the code if it is changed, and to make it simple and clear in the case of an extension.

this is how i do my tools, and how i learn my students:

global TOSYK_CharacterRig_Setup
(
	struct CharacterRig_Struct
	(
		InitBone = 
		(
			struct InitBone 
			(
			private
				tm, 
			public
				name, 
				origin = [0,0,0],
				node, 
				fn getNode = getnodebyname name,
				fn setup = if isvalidnode node do node.transform = tm * node.transform,
				fn backup = if isvalidnode node do node.transform = inverse tm * node.transform,
			
				fn update = 
				(
					node = getnodebyname name 
					tm = (eulerangles origin[1] origin[2] origin[3]) as matrix3
				),
				on create do
				(
					update()
				)
			)
		),
		
		BoneGroup = 
		(
			struct BoneGroup (name, bones)
		),
	private
		shoulder_origin = [-20,5,25],
		elbow_origin = [0,0,20],
	public
		bone_groups = 
		#(
			BoneGroup name:#shoulder bones:
			#(
				InitBone name:#j_shoulder_ri origin:shoulder_origin,
				InitBone name:#j_shoulder_lo origin:shoulder_origin
			),
			BoneGroup name:#elbow bones:
			#(
				InitBone name:#j_elbow_ri origin:elbow_origin,
				InitBone name:#j_elbow_bulge_ri origin:elbow_origin,
				InitBone name:#j_elbow_le origin:elbow_origin,
				InitBone name:#j_elbow_bulge_le origin:elbow_origin
			)
			-- all other bone groups ...
		),
		
		mapped fn update_bone bone = bone.update(),
		mapped fn setup_bone bone = bone.setup(),
		mapped fn backup_bone bone = bone.backup(),
		
		fn update = 
		(
			for bg in bone_groups do update_bone bg.bones
		),
		fn setup = 
		(
			for bg in bone_groups do setup_bone bg.bones
		),
		fn backup = 
		(
			for bg in bone_groups do backup_bone bg.bones
		),
		
		dialog = 
		(
			rollout dialog "TOSYK Rig" width:200
			(
				local owner = if owner != undefined do owner
				
				group "Edit: " 
				(
					button pose_all_bt "Pose All Bones" width:182 align:#left offset:[-4,0] tooltip:"Setup All Bones\n   RC\t- Backup"
				)
				button update_all_bt "Update Rig Data" width:182 align:#left offset:[-4,4] tooltip:"Update Rig Data"
				
				on pose_all_bt pressed do undo "Setup" on 
				(
					if isstruct owner do owner.setup()
				)
				on pose_all_bt rightclick do undo "Beckup" on 
				(
					if isstruct owner do owner.backup()
				)
				
				fn makeColorByName name hash:1 = 
				(
					maxops.colorById (getHashValue name hash) &c
					c
				)
				group "Debug: " 
				(
					button create_test_bt "Create Test" width:182 align:#left offset:[-4,0] tooltip:"Create Test"
				)
				on create_test_bt pressed do undo "Create Test" on if isstruct owner do 
				(
					delete objects
					for bg in owner.bone_groups do
					(
						col = makeColorByName (bg.name as string)
						for node in bg.bones do
						(
							point name:node.name axis:on pos:(random [100,100,100] -[100,100,100]) wirecolor:col
						)
					)
					owner.update()
				)
				
				on dialog close do
				(
				)
				on dialog open do
				(
				)
			)
		),
		fn destroy_dialog = 
		(
			try (destroydialog ::TOSYK_CharacterRig_Setup.dialog) catch()
		),
		fn create_dialog =
		(
			destroy_dialog()
			createdialog dialog
 		),
		on create do
		(
			destroy_dialog()
			update()
			dialog.owner = this
		)
	)
	global _cs = TOSYK_CharacterRig_Setup = CharacterRig_Struct()
	ok
)

/*
_cs.create_dialog()
*/

The DEBUG part is really not needed for your final code (tool). But it’s easier to make a simple scene for debugging complex solutions. (in my case, I had nothing to debug the code, so I do a test that covers the all needs to implement the idea). You can remove or comment this part of code

Ask questions if don’t understand any of my code solutions. Of course if you want to learn instead of just use it…


#15

It’s not about How to Make a Character Rig. It’s only about how to organize code for a tool that will grow and improve


#16

but if you are interested, i can give some recommendations how to do a rig as well…


#17

thanks denis, really appreciate for you help!

unfortunately my knowledges are way too far from the understanding such a complex solution — right now I’m somewhere between the parsing of files and copy/paste others work into scripts of mine

it would take too much time to understand this before I can use it though I wanted a simple solution for my current needs.

it’s a matter of fact I love to organize things. but what’s more important at the moment it’s efficient using of time

speaking about the rig I must say I always wanted to add automatic rig-system and a way to use any animation on a skeleton with a different bone names but that would be offtopic I suppose