Notifications
Clear all

[Closed] Offset transform an axis with a matrix

yeah I’ve found some resources and managed to get it to work right without offset, the problem comes when I have a Hold position and want to offset it, I get Gimbal Lock and I don’t really know how to get rid of it

this is not a gimble-lock issue. it’s a euler-to-matrix conversion. we have to do it in different order for a type of component (x,y,z) changed:

fn makeRotMatrix x y z order:1 invert:off = 
(
	tm = Matrix3 1
	case order of
	(
		1:
		(
			prerotateX tm x 
			prerotateY tm y 
			prerotateZ tm z 
		)
		2:
		(
			prerotateX tm x
			prerotateZ tm z 
			prerotateY tm y 
		)
		3:
		(
			prerotateY tm y 
			prerotateZ tm z 
			prerotateX tm x 
		)
	)
	if invert then inverse tm else tm
)

try (destroydialog Transformer) catch()

with redraw off
(
	delete objects
	d = dummy name:#target pos:(random -[10,10,10] [10,10,10]) dir:(random -[1,1,1] [1,1,1]) isselected:on
)

rollout Transformer "Transformer by denisT" width:200
(
	local originTM = Matrix3 1
	
	group "Position: "
	(
		slider pos_x "X" width:190 range:[-100,100,0]
		slider pos_y "Y" width:190 range:[-100,100,0]
		slider pos_z "Z" width:190 range:[-100,100,0]
	)
	group "Position: "
	(
		slider rot_x "X" width:190 range:[-180,180,0]
		slider rot_y "Y" width:190 range:[-180,180,0]
		slider rot_z "Z" width:190 range:[-180,180,0]
	)
	
	button hold_tm_bt "Hold Transform" width:190 
	button reset_tm_bt "Zero Offset" width:190 
	
	fn transformNode node: order:1 = 
	(
		if node == unsupplied do node = selection[1]
		if isvalidnode node do
		(
			rotTM = makeRotMatrix rot_x.value rot_y.value rot_z.value order:order
			
			tm = rotTM * originTM
			tm.pos = [pos_x.value, pos_y.value, pos_z.value] * originTM 
			
			node.transform = tm
		)
	)
	
	on pos_x changed val do transformNode order:3
	on pos_y changed val do transformNode order:2
	on pos_z changed val do transformNode order:1

	on rot_x changed val do transformNode order:3 --#yzx
	on rot_y changed val do transformNode order:2 --#xzy
	on rot_z changed val do transformNode order:1 --#xyz
		
	on hold_tm_bt pressed do if isvalidnode (node = selection[1]) do
	(
		originTM = node.transform
		rot_x.value = rot_y.value = rot_z.value = pos_x.value = pos_y.value = pos_z.value = 0
	)
	on reset_tm_bt pressed do if isvalidnode (node = selection[1]) do
	(
		rot_x.value = rot_y.value = rot_z.value = pos_x.value = pos_y.value = pos_z.value = 0
		transformNode()
	)
	
	on Transformer open do hold_tm_bt.pressed()
)
createdialog Transformer 




the axis we modify has to be set last

Hey Denis, thanks so much for the help.
I tried something similar with controllers, changing the order of axis on the fly and I had the same problem that I have with the script, when the order changes and there is a previous transform the object snaps to a different location (you can see it if you move x then y then z then x again and it snaps).
there must be a way to do offset rotations cleanly.
maybe it has to be via quaternions because of the nature of euler.
or maybe counter rotate the snap rotation of the proposed solution could work, I’ll have to see if it always snaps the same way but that may work.

Update:
the counter rotation is not so straight forward

here is another solution for Local transform. It applies relative transform (offset) every type you change an axis value:

try (destroydialog Transformer) catch()

with redraw off
(
	delete objects
	d = dummy name:#target pos:(random -[10,10,10] [10,10,10]) dir:(random -[1,1,1] [1,1,1]) isselected:on
)

rollout Transformer "Transformer by denisT" width:200
(
	local pos_offset = [0,0,0]
	local rot_offset = [0,0,0]
	
	group "Position: "
	(
		slider pos_x "X" width:190 range:[-100,100,0]
		slider pos_y "Y" width:190 range:[-100,100,0]
		slider pos_z "Z" width:190 range:[-100,100,0]
	)
	group "Position: "
	(
		slider rot_x "X" width:190 range:[-180,180,0]
		slider rot_y "Y" width:190 range:[-180,180,0]
		slider rot_z "Z" width:190 range:[-180,180,0]
	)
	
	button hold_tm_bt "Hold Transform" width:190 
	button reset_tm_bt "Zero Offset" width:190 
	
	fn transformNode node: pos: rot: = 
	(
		if node == unsupplied do node = selection[1]
		if isvalidnode node do
		(
			if pos == unsupplied do pos = [pos_x.value, pos_y.value, pos_z.value]
			if rot == unsupplied do rot = [rot_x.value, rot_y.value, rot_z.value]			
			
			tm = node.transform
			dr = rot - rot_offset
			prerotatex tm dr.x
			prerotatey tm dr.y
			prerotatez tm dr.z
			
			dp = pos - pos_offset
			pretranslate tm dp
			
			node.transform = tm
			
			pos_offset = pos
			rot_offset = rot
		)
	)
	
	on pos_x changed val do transformNode()
	on pos_y changed val do transformNode()
	on pos_z changed val do transformNode()

	on rot_x changed val do transformNode()
	on rot_y changed val do transformNode()
	on rot_z changed val do transformNode()
		
	on hold_tm_bt pressed do if isvalidnode (node = selection[1]) do
	(
		pos_offset = [0,0,0]
		rot_offset = [0,0,0]
		rot_x.value = rot_y.value = rot_z.value = pos_x.value = pos_y.value = pos_z.value = 0
	)
	on reset_tm_bt pressed do if isvalidnode (node = selection[1]) do
	(
		rot_x.value = rot_y.value = rot_z.value = pos_x.value = pos_y.value = pos_z.value = 0
		transformNode()
	)
	
	on Transformer open do hold_tm_bt.pressed()
)
createdialog Transformer


 

Hey Denis, that seems to work, tonight I’m going to sit down and check what did the trick.
Thanks so much, I really appreciate it.

Denis thanks for this info about world and local transform.
I recently came across this interesting software akeytsu. It use very cool SPINNER manipulator as main transform tool.
Inspired by this I create a lot simpler GUI which can be used as “akeytsu” spinner but for MAX.
But …transform fn’s that you showed are harder to setup here, in this tool then max rollout. :banghead:


(
-- LMB + Drag on "ANIMO" title to move position of form ei. tool
-- MMB on "ANIMO" title to rollup]rolldown form ei. tool
-- RMB on "ANIMO" title to close form ei. tool
	
-- Red Green and Blue buttons represent the virtual sliders for XYZ axis
-- "M" checkbutton activate MOVE mode
-- "R" checkbutton activate ROTATE mode
-- "S" checkbutton activate SCALE mode
-- "V" checkbutton activate VIEW coordsys mode
-- "L" checkbutton activate LOCAL coordsys mode
-- "P" checkbutton activate PARENT coordsys mode
-- "P" button - select parent node of current selection
-- "C" button - select child node of current selection
-- "P" button - select whole hierarchy of current selection
	
if ::animoForm != undefined do try(animoForm.close())catch()
global animo = 
(
	struct aStruct 
	(
		movePos, objsTM = #(), animoArr = #(#move,#rotate,#scale,#view,#local,#parent), trans = animoArr[1], coord_sys = animoArr[4], threshold = 10	
	)
	animo = aStruct()
)
fn defColor r g b = ((dotNetClass "System.Drawing.Color").FromArgb r g b)
fn defPoint x y = (dotNetObject "System.Drawing.Point" x y)
fn defSize x y = (dotNetObject "System.Drawing.Size" x y)
fn defRect x y w h = (dotNetObject "System.Drawing.Rectangle" x y w h)
fn defFontB fName fSize = (dotNetObject "System.Drawing.Font" fName fSize ((dotNetClass "System.Drawing.FontStyle").bold));
C1 = (defColor 0 0 0); C2 = (defColor 60 60 60); C3 = (defColor 89 90 91); C4 = (defColor 220 220 220); C5 = (defColor 40 40 40); 
C6 = (defColor 170 21 40); C7 = (defColor 154 191 73); C8 = (defColor 34 83 120) ; F1 = (defFontB "Verdana" 6.5) ; F2 = (defFontB "Verdana" 6.0) 
CRS = (dotNetClass "Cursors").Hand ; MRG20 = (dotNetObject "padding" 2 2 0 0)

/*/////////////////////////////// GUI FN's ///////////////////////////////////////*/
	fn defForm dnForm w: h: x: y: = --main form
	(
		dnForm.ShowInTaskbar = false ; dnForm.Text = "" ; dnForm.ClientSize = defSize w h
		dnForm.StartPosition = dnForm.StartPosition.Manual ; dnForm.DesktopLocation = defPoint x y
		dnForm.FormBorderStyle = dnForm.FormBorderStyle.None ; dnForm.BackColor = C1
		dnForm.AutoSizeMode = dnForm.AutoSizeMode.GrowAndShrink ; dnForm.AutoSize = on
	)
	fn defTitleLbl bnrLbl w: h: txt: = --baner title
	(
		bnrLbl.Size = defSize w h ; bnrLbl.Location = defPoint 0 0
		bnrLbl.BackColor = C1 ; bnrLbl.ForeColor = C4 ; bnrLbl.Cursor = CRS
		bnrLbl.Font = F1 ; bnrLbl.TextAlign = bnrLbl.TextAlign.MiddleCenter ; bnrLbl.Text = txt
	)
	fn defMainFPnl mFPnl w: h: = -- main container
	(
		mFPnl.Size = defSize w h ; mFPnl.Location = defPoint 1 16
		mFPnl.BackColor = C1 ; mFPnl.AutoSizeMode = mFPnl.AutoSizeMode.GrowAndShrink
	)
	fn defLblPnl lblPnl w: h: txt: = -- label panel
	(
		lblPnl.Size = defSize w h ; lblPnl.Margin= MRG20
		lblPnl.BackColor = C2 ; lblPnl.ForeColor = C4
		lblPnl.Font = F2 ; lblPnl.TextAlign = lblPnl.TextAlign.MiddleLeft
		if txt != unsupplied do lblPnl.Text = txt		
	)
	fn defBtn btn pnl w: h: x: y: col:C3 txt: title: tag:0 = --buttons
	(
		if x != unsupplied and y != unsupplied do btn.Location = defPoint x y
		if txt != unsupplied do btn.text = txt ; if title != unsupplied do btn.name = title
		btn.Size = defSize w h ; btn.Margin = MRG20 ; btn.ForeColor = C4 ; btn.font = F2
		btn.FlatStyle = btn.FlatStyle.Flat ; btn.FlatAppearance.BorderSize = 0 ; btn.tag = tag
		btn.BackColor = btn.FlatAppearance.BorderColor = btn.FlatAppearance.MouseDownBackColor = col
		pnl.Controls.add btn
	)
	fn defCBtn cbtn pnl w: h: x: y: txt: check:off = --checkbuttons and checkboxses
	(
		cbtn.Size = defSize w h ; cbtn.Location = defPoint x y ; cbtn.text = txt
		cbtn.FlatStyle = cbtn.FlatStyle.Flat ; cbtn.TextAlign.MiddleCenter ; cbtn.checked = check
		cbtn.Appearance = cbtn.Appearance.Button ; cbtn.FlatAppearance.BorderSize = 0
		cbtn.FlatAppearance.CheckedBackColor = cbtn.FlatAppearance.MouseDownBackColor = C5
		cbtn.FlatAppearance.BorderColor = cbtn.BackColor = C3 ; cbtn.ForeColor = C4
		pnl.Controls.add cbtn
	)
	fn maxHW = 
	(
		nw = DotNetObject "NativeWindow"
		nw.AssignHandle (DotNetObject "System.IntPtr" (Windows.GetMaxHWND())) 
		nw
	)
	mapped fn setLifetimeControl control = dotnet.setLifetimeControl control #dotnet

/*/////////////////////////////// EVENT FN'S ///////////////////////////////////////*/
	fn getFormCtrls dnCtrl ctrls:#() =
	(
		if dnCtrl.Parent == undefined do append ctrls dnCtrl
		for i in 0 to dnCtrl.Controls.count-1 do 
		(
			ctrl = dnCtrl.Controls.Item[i] ; append ctrls ctrl
			if ctrl.HasChildren and ((ctrl.gettype()).name) != "NumericUpDown" do (getFormCtrls ctrl ctrls:ctrls)
		) ; ctrls
	)
	fn animoTITLEMD s e =
	(
		animo.movePos = [(mouse.screenpos.x - animoForm.DesktopLocation.x), (mouse.screenpos.y - animoForm.DesktopLocation.y)]
		case e.Button of (
			(e.Button.Left): s.Cursor = (dotNetClass "Cursors").NoMove2D
			(e.Button.Middle): s.Cursor = (dotNetClass "Cursors").HSplit
		)
	)
	fn animoTITLEMM s e =
	(
		if e.Button == e.Button.Left and animo.movePos != undefined do
		(
			animoForm.DesktopLocation = defPoint (mouse.screenpos.x - animo.movePos.x) (mouse.screenpos.y - animo.movePos.y)
		)
	)
	fn animoTITLEMU s e =
	(
		animo.movePos = undefined ; s.Cursor = CRS
		case e.Button of (
			(e.Button.Middle): 
			(
				format "H %
" animoForm.controls.item[1].Height
				if animoForm.controls.item[1].Height != 0 then (animoForm.controls.item[1].Height = 0) else (animoForm.controls.item[1].Height = 124)
				--if animoForm.Height != 16 then (animoForm.Height = 16) else (animoForm.Height = 143)
			)
			(e.Button.Right): (
				(try(animoForm.Dispose() ; animoForm.close() ; animoForm = undefined)catch())
				gc light:true ; clearListener()
			)
		)
	)
	fn setUpMainToolbar ctrl = 
	(
		setCoordCenter #local
		for i = 0 to 5 do 
		(
			if i == 0 and ctrl.Controls.Item[i].checked do max move
			if i == 1 and ctrl.Controls.Item[i].checked do max rotate
			if i == 2 and ctrl.Controls.Item[i].checked do toolMode.uniformScale()
			if i == 3 and ctrl.Controls.Item[i].checked do setRefCoordSys #hybrid
			if i == 4 and ctrl.Controls.Item[i].checked do setRefCoordSys #local
			if i == 5 and ctrl.Controls.Item[i].checked do setRefCoordSys #parent
		)
	)
	fn selectParent s e = if (object = getCurrentSelection()).count > 0 do
	(
		for o in object where o.parent != undefined do select o.parent
	)
	fn selectChild s e = if (object = getCurrentSelection()).count > 0 do
	(
		for o in object where o.children.count > 0 do select o.children[1]
	)
	fn selectHierarchy s e = if selection.count > 0 do
	(
		object = getCurrentSelection() ; node = #()
		for o in object where o.children.count > 0 do join node o
		select (makeUniqueArray node)
	)
	fn mrsvlpsMDown s e = (s.tag = s.checked) -- move - rotate - scale - view - local - parent buttons MouseUp fn
	fn mrsMUp s e = -- move - rotate - scale buttons MouseUp fn
	(
		if s.tag then s.checked = s.tag else
		(
			for i in 0 to 2 where i != (s.Parent.controls.IndexOf s) do s.Parent.controls.item[i].checked = off
			setUpMainToolbar s.parent
		)
		for i = 0 to 2 where s.Parent.controls.item[i].checked do animo.trans = animo.animoArr[i+1]
	)
	fn vlpMUp s e = -- view - local - parent buttons MouseUp fn
	(
		if s.tag then s.checked = s.tag else
		(
			for i in 3 to 5 where i != (s.Parent.controls.IndexOf s) do s.Parent.controls.item[i].checked = off
			setUpMainToolbar s.parent
		)
		for i = 3 to 5 where s.Parent.controls.item[i].checked do animo.coord_sys = animo.animoArr[i+1]
	)
	fn btnMDown s e = if selection.count > 0 do -- red X - green Y - blue Z MouseDown fn
	(
		if e.Button == e.Button.Left do
		( 
			animo.threshold = if keyboard.controlPressed then 1 else if keyboard.shiftPressed then 20 else 10
			s.tag -= e.x
			-- CODE
		)
	)
	fn btnMMove s e = if selection.count > 0 do -- red X - green Y - blue Z MouseMove fn
	(
		if e.Button == e.Button.Left do
		(
			-- CODE
		)
	)
	fn btnMUp s e = if selection.count > 0 do -- red X - green Y - blue Z MouseUp fn
	(
		-- CODE
	)
/*/////////////////////////////// CONTROLS ///////////////////////////////////////*/
	animoForm = dotNetObject "Form" ; defForm animoForm w:60 h:124 x:50 y:110
	animoTITLE = dotNetObject "Label" ; defTitleLbl animoTITLE w:56 h:16 txt:"ANIMO"
	animoMFPnl = dotNetObject "FlowLayoutPanel" ; defMainFPnl animoMFPnl w:58 h:124
	animoPnl = dotNetObject "Label" ; defLblPnl animoPnl w:56 h:56 txt:"" ; animoForm.tag = animoPnl
	animoMove = dotNetObject "Checkbox" ; defCBtn animoMove animoPnl w:16 h:16 x:2 y:2 txt:"M" check:on
	animoRotate = dotNetObject "Checkbox" ; defCBtn animoRotate animoPnl w:16 h:16 x:20 y:2 txt:"R"
	animoScale = dotNetObject "Checkbox" ; defCBtn animoScale animoPnl w:16 h:16 x:38 y:2 txt:"S"
	animoView = dotNetObject "Checkbox" ; defCBtn animoView animoPnl w:16 h:16 x:2 y:20 txt:"V" check:on
	animoLocal = dotNetObject "Checkbox" ; defCBtn animoLocal animoPnl w:16 h:16 x:20 y:20 txt:"L"
	animoParent = dotNetObject "Checkbox" ; defCBtn animoParent animoPnl w:16 h:16 x:38 y:20 txt:"P"
	animoSelParent = dotNetObject "Button" ; defBtn animoSelParent animoPnl w:16 h:16 x:2 y:38 txt:"P"
	animoSelChild = dotNetObject "Button" ; defBtn animoSelChild animoPnl w:16 h:16 x:20 y:38 txt:"C"
	animoSelHierarchy = dotNetObject "Button" ; defBtn animoSelHierarchy animoPnl w:16 h:16 x:38 y:38 txt:"H"
	animoX = dotNetObject "Button" ; defBtn animoX animoMFPnl w:56 h:20 col:C6 title:"X"
	animoY = dotNetObject "Button" ; defBtn animoY animoMFPnl w:56 h:20 col:C7 title:"Y"
	animoZ = dotNetObject "Button" ; defBtn animoZ animoMFPnl w:56 h:20 col:C8 title:"Z"
	animoMFPnl.Controls.addRange #(animoPnl)
	animoForm.Controls.addRange #(animoTITLE, animoMFPnl)

	dotNet.addEventHandler animoTITLE "MouseDown" animoTITLEMD
	dotNet.addEventHandler animoTITLE "MouseMove" animoTITLEMM
	dotNet.addEventHandler animoTITLE "MouseUp" animoTITLEMU
	dotNet.addEventHandler animoMove "MouseDown" mrsvlpsMDown
	dotNet.addEventHandler animoMove "MouseUp" mrsMUp
	dotNet.addEventHandler animoRotate "MouseDown" mrsvlpsMDown
	dotNet.addEventHandler animoRotate "MouseUp" mrsMUp
	dotNet.addEventHandler animoScale "MouseDown" mrsvlpsMDown
	dotNet.addEventHandler animoScale "MouseUp" mrsMUp
	dotNet.addEventHandler animoView "MouseDown" mrsvlpsMDown
	dotNet.addEventHandler animoView "MouseUp" vlpMUp
	dotNet.addEventHandler animoLocal "MouseDown" mrsvlpsMDown
	dotNet.addEventHandler animoLocal "MouseUp" vlpMUp
	dotNet.addEventHandler animoParent "MouseDown" mrsvlpsMDown
	dotNet.addEventHandler animoParent "MouseUp" vlpMUp
	dotNet.addEventHandler animoSelParent "Click" selectParent
	dotNet.addEventHandler animoSelChild "Click" selectChild
	dotNet.addEventHandler animoSelHierarchy "Click" selectHierarchy
	dotNet.addEventHandler animoX "MouseDown" btnMDown
	dotNet.addEventHandler animoX "MouseMove" btnMMove
	dotNet.addEventHandler animoX "MouseUp" btnMUp
	dotNet.addEventHandler animoY "MouseDown" btnMDown
	dotNet.addEventHandler animoY "MouseMove" btnMMove
	dotNet.addEventHandler animoY "MouseUp" btnMUp
	dotNet.addEventHandler animoZ "MouseDown" btnMDown
	dotNet.addEventHandler animoZ "MouseMove" btnMMove
	dotNet.addEventHandler animoZ "MouseUp" btnMUp

	dotNet.setLifetimeControl animoForm #dotnet	
	
	handle = maxHW()
	setLifetimeControl (getFormCtrls animoForm)
	animoForm.Show(handle) ; handle.ReleaseHandle()
ok	
)

the last code i’ve posted it’s exactly what you need for relative transform.
the only thing you need to do is to convert screen-distance (pixels) to transform units (move, rotate, scale)

i’m absolutely sure that i posted complete solution for that on this forum. at least in my archive i have four controls with the similar functionality

1 Reply
(@gazybara)
Joined: 10 months ago

Posts: 0

I know i know I use it as example But that example as I remember covers only translation x y z.
I find it http://forums.cgsociety.org/showpost.php?p=7235836&postcount=15
I can’t find better solution to store and restore “e.x” value on mouse down, move and up events.
For this example is it good idea to use struct rather than tag property?

my users fall into two categories – one loves, another doesn’t love – this kind of control.

i’ve decided to leave them with the built-in transform gizmo. (the same story for Maya).

1 Reply
(@gazybara)
Joined: 10 months ago

Posts: 0

I agree. That’s why every 3D package use it. Default tm-gizmo in max is the best solution for sure without competition.Everything else can be only a temporary solution.

Page 2 / 2