PDA

View Full Version : viewport.activeViewport problem in callback


lo
06-28-2011, 11:54 AM
I am trying to write a small set of functions that link all viewports to the same rotation.
It is working, except that whenever any other window gets focus, max hangs for several minutes. I have narrowed it down to the viewport.activeViewport being set inside the callback timer function. Here is the code, with the offending line marked in orange:

global linkViewports
struct linkViewportsStr
(
callbackString = "linkViewports.startLinkTimer()",
linkTimer,
lastTM = matrix3 0,

fn areMatricesIdentical a b = a[1]==b[1] and a[2]==b[2] and a[3]==b[3] and a[4]==b[4],

fn setCallbacks state =
(
format "Settings callbacks to %\n" state
if state then callbacks.addScript #viewportChange callbackString id:#linkViewports
else callbacks.removeScripts id:#linkViewports
),

fn doSync =
(
format "Entered doSync fn\n"
local masterView = viewport.activeViewport --check which viewport is active (the master)
local masterTm = viewport.GetTM() --get current viewport transform (for rotation part)
if not areMatricesIdentical lastTM masterTM do --if the new transform is different than the old transform
(
setCallbacks off --we must disable the callback temporarily, otherwise we will get stuck in an infinite loop!
format "Syncing viewports\n"
lastTM = masterTM
with redraw off --disable redrawing
(
for v = 1 to viewport.numViews do if v!=masterView do --for each viewport except the master
(
if viewport.getType index:v != #view_persp_user do continue --ignore orthographic views and cameras
format "Settings viewport no. %\n" v
viewport.activeViewport = v
local slaveTm = viewport.GetTM() --get the slave viewport transform (for position part)
viewport.setTM (matrix3 masterTm.row1 masterTm.row2 masterTm.row3 slaveTM.row4) -- set the transform
)
viewport.activeViewport = masterView --return the view to the one we started from
)
setCallbacks on --reenable the callbacks
)
),

fn linkTimerHandler sender args =
(
if mouse.buttonStates.numberSet == 0 do --if we have stopped moving (no mouse buttons are pressed)
(
format "Stopping linkTimer\n"
sender.stop() --stop the timer
linkViewports.doSync() --perform the synchronization
)
),

fn startLinkTimer =
(
if not linkViewports.linkTimer.enabled and mouse.buttonStates.numberSet == 0 do
(
format "Starting linkTimer\n"
linkviewports.linkTimer.start() --start the timer
)
),

fn initializeLinkStruct =
(
callbacks.removeScripts id:#linkViewports
linkTimer = dotNetObject "System.Timers.Timer" 500 --create a timer with 500ms interval
dotNet.addEventHandler linkTimer "Elapsed" linkTimerHandler --when the timer ticks, run the link callback
),

_init = initializeLinkStruct() --a variable that executes the initializeLinkStruct function when the struct instance is created
)

linkViewports = linkViewportsStr() --create struct instance
linkViewports.setCallbacks on --turn link on

I am on max2009 64.

denisT
06-28-2011, 04:04 PM
why do you use timer? i would use redraw views callback.

lo
06-28-2011, 04:10 PM
As usual you are right.
Without the timer it works fine, even with #viewportChange callback.
I added the timer so it wouldn't redraw all the time, but I can just add the mousebuttons check in the callback function and it's ok.

Maybe the problem was that the timer callback runs on a different thread than the 3dsmax UI thread.

Thanks for the help.

lo
06-28-2011, 04:27 PM
Here is the working (and shorter) code if anyone is interested:

global linkViewports
struct linkViewportsStr
(
callbackString = "linkViewports.doSync()",
lastTM = matrix3 0, --a variable to store the last transform

fn areMatricesEqual a b = a[1]==b[1] and a[2]==b[2] and a[3]==b[3] and a[4]==b[4],

fn setLinkActive state =
(
callbacks.removeScripts id:#linkViewports
if state do callbacks.addScript #viewportChange callbackString id:#linkViewports
),

fn doSync =
(
if mouse.buttonStates.numberSet == 0 and viewport.getType() == #view_persp_user do --if the mouse is not pressed and this is a perspective viewport
(
local masterView = viewport.activeViewport --check which viewport is active (the master)
local masterTm = viewport.GetTM() --get current viewport transform (for rotation part)
if not areMatricesEqual lastTM masterTM do --if the new transform is different than the old transform
(
local offset = masterTm.row4-lastTm.row4 --offset in position since last update
lastTM = masterTM --save new transform for next update
setLinkActive off --before changing the viewports, we must disable the callback temporarily, otherwise we will get stuck in an infinite loop!
with redraw off --disable redrawing
(
viewport.activeViewport = 1
for v = 1 to viewport.numViews do if v!=masterView and viewport.getType index:v == #view_persp_user do --for each perspective viewport except the master
(
viewport.activeViewport = v
local slaveTm = viewport.GetTM() --get the slave viewport transform (for position part)
viewport.setTM (matrix3 masterTm.row1 masterTm.row2 masterTm.row3 (slaveTm.row4+offset)) -- set the transform
)
viewport.activeViewport = masterView --return the view to the one we started from
)
setLinkActive on --reenable the callbacks
)
)
)
)

linkViewports = linkViewportsStr() --create struct instance

linkViewports.setLinkActive on --turn link on

denisT
06-28-2011, 04:44 PM
As usual you are right.
Without the timer it works fine, even with #viewportChange callback.
I added the timer so it wouldn't redraw all the time, but I can just add the mousebuttons check in the callback function and it's ok.

Maybe the problem was that the timer callback runs on a different thread than the 3dsmax UI thread.

Thanks for the help.

timer has to work. i use a timer for some viewport things. but not is in this case.

lo
06-28-2011, 06:21 PM
the only command which causes the problem is setting viewport.activeViewport property.
For example, viewport.GetTM and viewport.setTM do not cause the problem.

denisT
06-28-2011, 06:55 PM
the only command which causes the problem is setting viewport.activeViewport property.
For example, viewport.GetTM and viewport.setTM do not cause the problem.

i like the method with timer ;)

unRegisterRedrawViewsCallback syncViews

global ViewHwnd = for w in (windows.getChildrenHWND #max) where w[4] == "ViewPanel" do exit with w[1]
WM_SETREDRAW = 0x000B

global DirtyViewport
global ViewTransform = matrix3 1
global LastView = undefined

fn dirtyViewportDraw = (DirtyViewport = on)
fn syncViews s e = if viewport.numViews > 1 and DirtyViewport and (viewport.getType()) == #view_persp_user do
(
s.Stop()
atm = viewport.GetTM()
ctm = ViewTransform
av = viewport.activeViewport
if LastView == av and ((atm[1] != ctm[1]) or (atm[2] != ctm[2]) or (atm[3] != ctm[3])) do
(
windows.sendmessage ViewHwnd WM_SETREDRAW 0 1

for v=1 to viewport.numViews where v != av and (viewport.getType index:v) == #view_persp_user do
(
viewport.activeViewport = v
vtm = viewport.GetTM()
viewport.setTM (translate (rotate (scalematrix vtm.scale) atm.rotation) vtm.pos)
)
viewport.activeViewport = av
windows.sendmessage ViewHwnd WM_SETREDRAW 1 1
)
LastView = av

ViewTransform = atm
DirtyViewport = off
s.Start()
)
registerRedrawViewsCallback dirtyViewportDraw

try
(
dotnet.removeAllEventHandlers tmr
tmr.dispose()
)
catch()

tmr = dotnetobject "Timer"
tmr.Stop()
tmr.Interval = 10
dotnet.addEventHandler tmr "Tick" syncViews
DirtyViewport = on
tmr.Start()

lo
06-28-2011, 09:14 PM
very nice!

You get extra points for not using a c# assembly ;)

denisT
06-28-2011, 09:23 PM
very nice!

You get extra points for not using a c# assembly ;)

i wanted... but... put a lid on my desire...

denisT
06-28-2011, 09:51 PM
Here is the working (and shorter) code if anyone is interested:

...

do you sync views scale on purpose?

lo
06-29-2011, 08:07 AM
do you sync views scale on purpose?

Honsetly, I haven't given it much thought. Is there ever a naturally occurring case in which the perspective viewport TM scale part is not [1,1,1]?

denisT
06-29-2011, 09:50 AM
Honsetly, I haven't given it much thought. Is there ever a naturally occurring case in which the perspective viewport TM scale part is not [1,1,1]?
scale might be any. matrix's [1] ([2],[3]) is direction*scale. in my sample i don't sync scale.

lo
06-29-2011, 10:52 AM
Yes, I noticed that. But I'm trying to understand the logic of why it's important. Can you give an example in which (getViewTM()).scale!=[1,1,1] (other that setting it so by script)?

denisT
06-29-2011, 11:08 AM
Yes, I noticed that. But I'm trying to understand the logic of why it's important. Can you give an example in which (getViewTM()).scale!=[1,1,1] (other that setting it so by script)?
that's easy. just try to change a view by wheeling mouse middle button

lo
06-29-2011, 11:19 AM
that's easy. just try to change a view by wheeling mouse middle button

That is what I tried but the scale stays [1,1,1] for me... only the position changes.

denisT
06-29-2011, 11:30 AM
That is what I tried but the scale stays [1,1,1] for me... only the position changes.
that means i am going off my head.

denisT
06-29-2011, 11:41 AM
never mind ... it's paranoia... when i see matrix3 i always split it on translation-rotation-scale

lo
06-30-2011, 03:42 PM
I've been breaking my head how to have the viewports maintain their initial position offset. Imagine two viewports looking at similar objects in two different positions. When one orbits around the object, the other should as well.
for some reason, I can not achieve this. I feel like I am making a very obvious mistake but can not find it.

Here is my current code, I've implemented Denis's redraw-disable messages.
global linkViewports
struct linkViewportsStr
(
callbackString = "linkViewports.doSync()",

ViewHwnd = (for w in (windows.getChildrenHWND #max) where w[4] == "ViewPanel" collect w[1])[1],
WM_SETREDRAW = 0x000B,

lastTM,
lastView,
lastFOV,
lastFD,

fn areMatricesDifferent a b = a[1]!=b[1] or a[2]!=b[2] or a[3]!=b[3] or a[4]!=b[4],

fn registerCallbacks state =
(
callbacks.removeScripts #viewportChange id:#linkViewports
if state do callbacks.addScript #viewportChange callbackString id:#linkViewports
),

fn setLinkActive state =
(
if state then
(
lastTM = getViewTm()
lastView = viewport.activeViewport
lastFOV = viewport.GetFOV()
lastFD = viewport.GetFocalDistance()
)
registerCallbacks state
),

fn doSync =
(
if viewport.getType() == #view_persp_user do
(
local masterView = viewport.activeViewport
local masterTm = getviewtm()
local masterFOV = viewport.GetFOV()
local masterFD = viewport.GetFocalDistance()
if lastView==masterView and (lastFOV!=masterFOV or areMatricesDifferent lastTM masterTM) do
(
registerCallbacks off
windows.sendmessage ViewHwnd WM_SETREDRAW 0 1
for v = 1 to viewport.numViews do if v!=masterView and viewport.getType index:v == #view_persp_user do
(
viewport.activeViewport = v
local slaveTm = inverse (getViewTm())
local slaveOffset = (slaveTM.pos* masterTM)
viewport.setTM (translate masterTM slaveOffset)

--local fPoint = [0,0,-masterFD] * slaveTM
--viewport.setFOV masterFOV
--viewport.setFocalDistance (distance fPoint slaveTM.pos)
)
viewport.activeViewport = masterView
windows.sendmessage ViewHwnd WM_SETREDRAW 1 1
registerCallbacks on
)
lastView=masterView
lastTM=masterTm
lastFOV=masterFOV
lastFD = masterFD
)
),

fn init =
(
setLinkActive off
windows.sendmessage ViewHwnd WM_SETREDRAW 1 1 --for testing
),

_init = init()
)

linkViewports = linkViewportsStr()


--test scene
(
delete objects
teapot pos:[100,0,0] radius:25 wirecolor:red
teapot pos:[-100,0,0] radius:25 wirecolor:blue
viewport.resetAllViews()
viewport.setLayout #layout_2v
viewport.activeviewport = 1
viewport.setType #view_persp_user
viewport.setFov 45.0
viewport.setTM (matrix3 [1,0,0] [0,0,-1] [0,1,0] [97.4478,-22.0418,-103.726])
viewport.setFocalDistance 103.18
viewport.activeviewport = 2
viewport.setType #view_persp_user
viewport.setFov 45.0
viewport.setTM (matrix3 [1,0,0] [0,0,-1] [0,1,0] [-102.32,-22.0418,-107.547])
viewport.setFocalDistance 103.18
)
linkViewports.setLinkActive on

denisT
06-30-2011, 06:08 PM
I've been breaking my head how to have the viewports maintain their initial position offset. Imagine two viewports looking at similar objects in two different positions. When one orbits around the object, the other should as well.
...

look at viewport.rotate method. it seems only chance for you to get what you want.

lo
06-30-2011, 06:20 PM
tried that, but even when specifying correct center point, results come out wrong for some reason...

denisT
06-30-2011, 06:33 PM
tried that, but even when specifying correct center point, results come out wrong for some reason...
don't specify a center. viewport.rotate works in current orbit mode. but you have to know that scene is in an orbit mode.

lo
07-01-2011, 12:34 PM
the orbit thing didn't work as well, but I found my original problem. I was unaware that (getViewTM()) does not get a copy of the view transform, but gives a reference to it in place!
So calling translate on it modified the source viewport as well!
The solution of course, is to use a copy of the matrix to manipulate.

global linkViewports
struct linkViewportsStr
(
callbackString = "linkViewports.doSync()",

ViewHwnd = (for w in (windows.getChildrenHWND #max) where w[4] == "ViewPanel" collect w[1])[1],
WM_SETREDRAW = 0x000B,

lastTM,
lastView,
lastFOV,

fn areMatricesDifferent a b =
(
(local inv = (a*inverse b)).rotation != quat 0 or (for i = 1 to 3 where abs ((inv.pos)[i]) > 0.001 collect ok).count>0
),

fn registerCallbacks state =
(
callbacks.removeScripts #viewportChange id:#linkViewports
if state do callbacks.addScript #viewportChange callbackString id:#linkViewports
),

fn setLinkActive state =
(
if state then
(
lastTM = getViewTm()
lastView = viewport.activeViewport
lastFOV = viewport.GetFOV()
)
registerCallbacks state
),

fn doSync =
(
if viewport.getType() == #view_persp_user do
(
local masterView = viewport.activeViewport
local masterTm = getViewTm()
local masterFOV = viewport.GetFOV()
if numviews == viewport.numViews and lastView==masterView and (masterFOV!=lastFOV or areMatricesDifferent lastTM masterTM) do
(
print 1
registerCallbacks off
windows.sendmessage ViewHwnd WM_SETREDRAW 0 1
for v = 1 to viewport.numViews do if v!=masterView and viewport.getType index:v == #view_persp_user do
(
viewport.activeViewport = v
viewport.setFOV masterFOV
viewport.setTM (pretranslate (copy masterTM) ((getViewTm()) * inverse lastTM).pos)
)
viewport.activeViewport = masterView
windows.sendmessage ViewHwnd WM_SETREDRAW 1 1
registerCallbacks on
)
lastView=masterView
lastTM=masterTm
lastFOV=masterFOV
)
),

fn init =
(
setLinkActive off
windows.sendmessage ViewHwnd WM_SETREDRAW 1 1 --for testing
),

_init = init()
)

linkViewports = linkViewportsStr()


--test scene
(
delete objects
teapot pos:[100,0,0] radius:25 wirecolor:red
teapot pos:[-100,0,0] radius:25 wirecolor:blue
viewport.resetAllViews()
viewport.setLayout #layout_2v
viewport.activeviewport = 1
viewport.setType #view_persp_user
viewport.setFov 45.0
viewport.setTM (matrix3 [1,0,0] [0,0,-1] [0,1,0] [100,-25,-100])
viewport.setFocalDistance 103.18
viewport.activeviewport = 2
viewport.setType #view_persp_user
viewport.setFov 45.0
viewport.setTM (matrix3 [1,0,0] [0,0,-1] [0,1,0] [-100,-25,-100])
viewport.setFocalDistance 103.18
clearListener()
)
linkViewports.setLinkActive on

CGTalk Moderation
07-01-2011, 12:34 PM
This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.