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