PDA

View Full Version : when transform changes


Norman3D
06-10-2011, 01:15 AM
Hey guys!

So I was looking for a quite specific callback mechanism. The "when transform changes" construct is pretty close but not exactly what I need.
The construct allows me to run code "while" objects are being transformed. (It is quite a lot of code that I need to run, I need to basically put the transform properties in arrays of every object in the selection. So this causes quite a lot of lag when you move the objects around.)

I would rather prefer a callback mechanism that let's me run code only after it has stopped moving. I'm pretty sure there is nothing like that in 3dsMax, I might be wrong though.

So I suspect I might have to live with "when transform changes" and combine it with a timer perhaps? Not sure how... maybe I can use the construct to notify me that the objects are being transformed, and maybe a timer that checks how long ago the last transformation was done, and if it was 500ms ago... it runs the code! I still wouldn't know how to "connect" the timer with the construct so that the timer "knows" how long ago the the construct ran. arhgh Does this make any sense? :shrug:

Do you guys have any ideas?

eek
06-10-2011, 02:04 AM
Use a dotNetTimer and event:

http://forums.cgsociety.org/showthread.php?f=98&t=974489&highlight=dotnet+event
http://forums.cgsociety.org/showthread.php?f=98&t=945895&highlight=dotnet+timer

You could do something like when the transform of the selection changes a count get appended. If the count doesnt change for 40ms or so append the array. You could set a flag to false as well to only append the array once - then when you transform the objects again the flag gets set to true.

roughly..

denisT
06-10-2011, 05:29 AM
So I was looking for a quite specific callback mechanism. The "when transform changes" construct is pretty close but not exactly what I need.
The construct allows me to run code "while" objects are being transformed. (It is quite a lot of code that I need to run, I need to basically put the transform properties in arrays of every object in the selection. So this causes quite a lot of lag when you move the objects around.)


as i understood your problem, the way of getting transforms of many objects is too slow for your needs. is it correct?
but to collect transforms of 10,000 objects takes less than 0.1s... is it slow? it's less that screen redraw time.
i'm not sure that you use the when construct the right way.

Norman3D
06-10-2011, 12:25 PM
Ok, so I'm just going to explain what it is that I'm working on...
I'm working on a little tool that allows artists to place objects in 3dsMax and then move them to Unreal Engine 3.
The reason for this is that Unreal 3 does not have many placement tools. So in 3dsMax you could for example use reactor to let a couple of boxes fall, whereas in Unreal you would have to place each one of them manually. So right now I have the "useful" part working. You click on a button and ta-da! You have your assets placed in Unreal.

But I'm working on another feature, just for fun. I don't really think it has practical use. But I'd like to see how fast I can get it to work. I want to try to achieve 1:1 sync between the two programs.

Watch this: http://www.youtube.com/watch?v=nnfdj5VVVjs

(Notice how it lags in 3dsMax when you move only 30 objects, this is because of my when construct)

So there is no real mystery as to how I pass the objects to Unreal. You can select one of the objects in Unreal, Crtl-C and paste it in the script. The contents of the clipboard have properties about the object, such as name, location, rotation, and scale.

So this is how I have it setup:
The when transform changes construct adds the location, rotation, scale to global arrays.
Then there is a dotnetTimer set to 500ms that checks those variables and creates the new clipboard text and finally sends a Crtl-V to the Unreal window.

While I'm writing this, I realize I might be doing a bit too much in the when construct...

when transform selection changes id:#UDK_TRANSFORM_TRACKER do
(
global theUDK_LOCATIONS = #()
global theUDK_ROTATIONS = #()
for n in theOBJECTselection do
(
theNewLocation = ("Location=(X=" + (n.pos[2] as string) + "," + "Y=" + (n.pos[1] as string) + "," + "Z=" + (n.pos[3] as string) + ")")
pitch = ((n.rotation.controller[1].value * 182) as integer) as string
yaw = ((((n.rotation.controller[3].value * -1) * 182) as integer) as string)
roll = ((n.rotation.controller[2].value * 182) as integer) as string
theNewRotation = ("Rotation=(Pitch=" + pitch + "," + "Yaw=" + yaw + "," + "Roll=" + roll + ")")
append theUDK_LOCATIONS theNewLocation
append theUDK_ROTATIONS theNewRotation
)
)

I'm already preparing the lines that need to be added to the final output text that the Timer takes care of. Do you think I should get rid of that part, and just add the raw data to the array and let the Timer take care of creating the final string?

So what I would need is to either get the when construct to be faster! Or think of an alternative callback mechanism, that notifies me when objects have stopped moving.

(Btw, offtopic: I also realized that the SetClipboardText took 5 seconds compared to the dotnet alternative that only takes 0.003 seconds! What the hell? :surprised )

Edit: To clarify: "theOBJECTselection" is also a global array set by a dialog. The user has to select first which objects in the scene will be synced, so that's what the array contains.

Pjanssen
06-10-2011, 01:20 PM
You could start/reset the timer in the when clause, and do all the performance-heavy stuff when the timer ticks. That way the timer would only tick after there have been no transformations for the specified timer delay.
You might have to take a bit of care to make sure nothing funky happens if you quickly change and transform the selection though.

And in general: do performance tests on your code (http://forums.cgsociety.org/showthread.php?f=98&t=981125) and on parts of it. Add traces to see how often blocks are being executed. Find the bottleneck and focus on optimizing that :)

Norman3D
06-10-2011, 01:26 PM
You could start/reset the timer in the when clause, and do all the performance-heavy stuff when the timer ticks. That way the timer would only tick after there have been no transformations for the specified timer delay.
You might have to take a bit of care to make sure nothing funky happens if you quickly change and transform the selection though.

And in general: do performance tests on your code (http://forums.cgsociety.org/showthread.php?f=98&t=981125) and on parts of it. Add traces to see how often blocks are being executed. Find the bottleneck and focus on optimizing that :)

aha! That makes sense! What about stopping the when construct at some point in the Timer? Then wait until it has finished running the heavy stuff and start the when construct again. I'll give this a try... Thanks!

Pjanssen
06-10-2011, 01:30 PM
Yes you could do more 'fixed' periodic updates too. With either approach, I'd say don't generate or process data in the when clause unless you will actually use it.

denisT
06-10-2011, 03:44 PM
when construct is not an issue in your case. it can't cause the delay. the delay is caused by the way how you transfer data from MAX to UNREAL.
check the when using sample:

deleteAllChangeHandlers id:#udk_transform_tracker
csb = dotnetclass "System.Text.StringBuilder"

srt_obj = "NodeName={0}"
srt_pos = "Location=(X={1},Y={0},Z={2})"
srt_rot = "Rotation=(Pitch={0},Yaw={2},Roll={1})"

global _udk_nodenames = #()
global _udk_locations = #()
global _udk_rotations = #()

fn _udk_GetTransform node =
(
sb = dotnetobject csb
sb.AppendFormat srt_obj node.name
append _udk_nodenames (sb.ToString())

sb = dotnetobject csb
sb.AppendFormat srt_pos node.pos[1] node.pos[2] node.pos[3]
append _udk_locations (sb.ToString())

sb = dotnetobject csb
sb.AppendFormat srt_rot (node.rotation.controller[1].value*182) (-node.rotation.controller[3].value*182) (node.rotation.controller[2].value*182)
append _udk_rotations (sb.ToString())
)

when transform objects changes id:#udk_transform_tracker handleAt:#redrawViews node do _udk_GetTransform node


it's fast enough

if you want to check transform data history:

fn _udk_GetNodeInfo index: =
(
if index == unsupplied do index = _udk_nodenames.count
if index > 0 and index <= _udk_nodenames.count do
format "%\n\t%, %\n" _udk_nodenames[index] _udk_locations[index] _udk_rotations[index]
)


You might meet a problem very soon using string arrays. With you method max memory leaks very quick. I would make some c# DLL and send data (obj name + transform (float arrays)) to the .net object. The .net object would transfer data to UNREAL. Technically it might be in another thread. so you can collect of transforms and send data to unreal asynchronously.

Norman3D
06-10-2011, 07:34 PM
First of all thanks denis once more for your help!

when construct is not an issue in your case. it can't cause the delay. the delay is caused by the way how you transfer data from MAX to UNREAL.

I did a test where I'm not transferring the data to Unreal, just running the when construct passing the transform values to the arrays and it was lagging just as much! So I don't think it has anything to do with the transferring.

Regarding your code...
What is the difference between using 3dsMax arrays and "dotnetclass "System.Text.StringBuilder"". Is this StringBuilder still an array? Is it faster because I'm only working with dotnet instead of working with 3dsMax arrays?


About transferring data to Unreal, since you mentioned this as being the bottleneck... I did a quick test and it takes around 0.29 seconds to transfer. Of those 0.29 seconds, it takes 0.25 to send the keystrokes. I think it takes that much time, because I'm actually sending a "delete" key before pasting the output. And it takes around 0.25 seconds to delete the selection in Unreal. There is not much I can do about that, though :/
It would be awesome if I could actually connect 3dsMax with Unreal, so that I could have access to the objects placed in the scene, and move them, instead of deleting and placing them again.

You might meet a problem very soon using string arrays. With you method max memory leaks very quick. I would make some c# DLL and send data (obj name + transform (float arrays)) to the .net object. The .net object would transfer data to UNREAL. Technically it might be in another thread. so you can collect of transforms and send data to unreal asynchronously.

Hmmm... could you give me an estimate as to how much 3dsMax can handle? I'm not planning on transferring 10000 objects. So if the leaking would occur around that number it wouldn't be a problem. But it does happen, haha I'm afraid I'm going to have to learn to create DLLs.

Another question. How do I properly convert an array to a string?
Would this be the fastest way? Probably not...

theArray = #("test1", "test2")
TheString = ""
for n in theArray do
(
theString = theString + "\n" + n
)

denisT
06-10-2011, 07:43 PM
what does the unreal actually need? what do you send to the unreal? as I know it should be a string...

Norman3D
06-10-2011, 07:47 PM
yes exactly. Here is an example:

Begin Map
Begin Level
Begin Actor Class=StaticMeshActor Name=StaticMeshActor_4143 Archetype=StaticMeshActor'Engine.Default__StaticMeshActor'
Begin Object Class=StaticMeshComponent Name=StaticMeshComponent0 ObjName=StaticMeshComponent_560 Archetype=StaticMeshComponent'Engine.Default__StaticMeshActor:StaticMeshComponent0'
StaticMesh=StaticMesh'LT_Buildings2.SM.Mesh.S_LT_Buildings_SM_BunkerSupA2'
OverriddenLightMapRes=32
ReplacementPrimitive=None
PreviewEnvironmentShadowing=51
bAllowApproximateOcclusion=True
bForceDirectLightMap=True
bUsePrecomputedShadows=True
BlockNonZeroExtent=False
BlockRigidBody=False
LightingChannels=(bInitialized=True,Static=True)
Name="StaticMeshComponent_560"
ObjectArchetype=StaticMeshComponent'Engine.Default__StaticMeshActor:StaticMeshComponent0'
CustomProperties
End Object
StaticMeshComponent=StaticMeshComponent'StaticMeshComponent_560'
Components(0)=StaticMeshComponent'StaticMeshComponent_560'
Location=(X=464.000000,Y=1632.000000,Z=-672.000000)
Rotation=(Pitch=-4096,Yaw=0,Roll=0)
DrawScale=2.000000
CollisionType=COLLIDE_BlockWeapons
BlockRigidBody=False
CreationTime=12.641472
Tag="StaticMeshActor"
CollisionComponent=StaticMeshComponent'StaticMeshComponent_560'
Name="StaticMeshActor_4143"
ObjectArchetype=StaticMeshActor'Engine.Default__StaticMeshActor'
End Actor
End Level
Begin Surface
End Surface
End Map

Pjanssen
06-10-2011, 07:59 PM
What is the difference between using 3dsMax arrays and "dotnetclass "System.Text.StringBuilder"". Is this StringBuilder still an array? Is it faster because I'm only working with dotnet instead of working with 3dsMax arrays?The StringBuilder is a class designed specifically for performing string operations (concatenating for example). Underneath it uses a char array. It is probably faster than using a normal String object when performing many operations on a string. And probably faster than using maxscript arrays too. But as with everything, it really depends on the situation, and the only proof you can get is by benchmarking it. For example, .NET calls might end up costing more than they gain.

Hmmm... could you give me an estimate as to how much 3dsMax can handle? I'm not planning on transferring 10000 objects. So if the leaking would occur around that number it wouldn't be a problem. But it does happen, haha I'm afraid I'm going to have to learn to create DLLs.I think there can be a short answer to that: memory leaking is always bad. :p

denisT
06-10-2011, 08:49 PM
yes exactly. Here is an example:

Begin Map
...
End Map
does it mean you send a file of this format?

Norman3D
06-11-2011, 12:19 AM
does it mean you send a file of this format?
This wouldn't be really a file. It's just text in the clipboard that is being pasted on the Unreal 3 Viewport. It contains all the relevant data. It references an asset in the asset library and all sorts of different properties. Unreal then knows what to do with the pasted text.
So you can basically select an object in a Unreal scene copy and paste it in notepad. And do any kind of changes to it, (not to the geometry obviously), but you can change values such as the transformation values.
This is what I'm basically doing with the script. I really don't think that the creation of the text file is the bottleneck. (Besides the possible memory leaking you guys mentioned.) But in terms of speed, it only took around 0.05 seconds to create the output text (30 objects).

My main concern was the "when construct" that is definitely slowing things down. But as Pjanssen suggested, I could simply start and stop the timer in the when construct. And then let the timer do the parsing and creation of the output text when it gets enough time. In other words, when the objects have stopped moving.

Norman3D
06-12-2011, 01:26 AM
Ok quick update! Pjanssen's idea about stopping and starting the timer in the when construct worked very well! It is exactly what I was looking for.

However it was still lagging a bit. I did a little test where I printed the objects position in the when construct and I realized that a position is always printed twice. So I "optimized" the when construct a bit.

global tick = 1

when transform selection changes id:#UDK_TRANSFORM_TRACKER do
(
if tick == 1 then
(
tick = 2
)
else
(
UDK_theTimer.stop()
UDK_theTimer.start()
tick = 1
)
)

This seems to do the trick and the lag is now completly gone. Oh, and I'm already noticing how the memory is leaking... :banghead:

denisT
06-12-2011, 01:47 AM
[QUOTE=Norman3D

This seems to do the trick and the lag is now completly gone. Oh, and I'm already noticing how the memory is leaking... :banghead:[/QUOTE]

i'm telling you again... when construct is not an issue. i know the sdk code. when construct is fast. it's faster than any general callback.

Norman3D
06-12-2011, 02:28 AM
i'm telling you again... when construct is not an issue. i know the sdk code. when construct is fast. it's faster than any general callback.

Ok, so then there must be something wrong with the Timer. All I'm doing in the Timer is stopping and starting it. Could this be enough to cause the lagging?


How can this...
when transform selection changes id:#UDK_TRANSFORM_TRACKER do
(
UDK_theTimer.stop()
UDK_theTimer.start()
)cause any lagging? :shrug:
When I move one object there is no lag at all. If I move 300 I get a lot of lag. It can't be the function that the Timer runs, because it doesn't have time to actually run it, right?

EDIT: ok, strange. There must be something with the Timer slowing things down. This runs super fast:


deleteAllChangeHandlers id:#UDK_TRANSFORM_TRACKER

con = dotNetClass "system.windows.Forms.Control"
UDK_theTimer = dotNetObject "System.Windows.Forms.Timer"

fn fn_timer =
(
UDK_theTimer.stop()
print "start function"
)

dotnet.addEventHandler UDK_theTimer "tick" fn_timer
UDK_theTimer.interval = 200


when transform selection changes id:#UDK_TRANSFORM_TRACKER do
(
UDK_theTimer.stop()
UDK_theTimer.start()
)

But I don't see why the function of the timer is relevant! It should not be a problem. It will essentially run after the transformation has stopped.

Edit 2: I just tested the code above again, and it's not running fast anymore. What is going on? :/

Pjanssen
06-12-2011, 08:55 AM
This may not be causing the issue, but try using the System.Timers.Timer instead of Windows.Forms.Timer:
http://msdn.microsoft.com/en-us/library/0tcs6ww8.aspx
http://msdn.microsoft.com/en-us/magazine/cc164015.aspx

Norman3D
06-12-2011, 01:18 PM
No luck. :/ Still lagging...

UDK_theTimer = dotNetObject "System.Timers.Timer"

fn fn_timer =
(
UDK_theTimer.stop()
print "start function"
)

dotnet.addEventHandler UDK_theTimer "Elapsed" fn_timer
UDK_theTimer.interval = 200


when transform selection changes id:#UDK_TRANSFORM_TRACKER do
(
UDK_theTimer.stop()
UDK_theTimer.start()
)

Pjanssen
06-12-2011, 02:26 PM
Next idea for optimization, cache the start/stop function calls. I'd be surprised if this would solve the problem as well, but its worth a try.

lo
06-12-2011, 02:32 PM
This might sound like a stupid question, but you are running
deleteAllChangeHandlers id:#UDK_TRANSFORM_TRACKERbetween each test, right?

Otherwise the when callbacks multiply and slow things down.

Your code isn't slow for me with several hundred objects unless I run it a few times and don't delete the callbacks.

Norman3D
06-12-2011, 02:35 PM
This might sound like a stupid question, but you are running
deleteAllChangeHandlers id:#UDK_TRANSFORM_TRACKERbetween each test, right?

Otherwise the when callbacks multiply and slow things down.

Your code isn't slow for me with several hundred objects unless I run it a few times and don't delete the callbacks.

yes, yes I'm making sure to delete the change handlers every time. I'm beginning to suspect there is something wrong with my machine. Even when I start 3dsMax run the script once and attempt to move 300 objects it's lagging.

lo
06-12-2011, 02:47 PM
Even when I start 3dsMax run the script once and attempt to move 300 objects it's lagging.

Strange, works very fast for me. What max version are you on?

Norman3D
06-12-2011, 02:49 PM
Strange, works very fast for me. What max version are you on?

3dsMax 2011 - 64bit, Windows 7 here. I'm going to try on different versions.

lo
06-12-2011, 02:53 PM
Tried it here on 2009/64 and 2011/64 and both were fast.

Perhaps check if there are other callbacks running on your machine using callbacks.show()

Norman3D
06-12-2011, 03:05 PM
Tried it here on 2009/64 and 2011/64 and both were fast.

Perhaps check if there are other callbacks running on your machine using callbacks.show()

Nope, it's not the case.
I just tested on 4 different 3dsMax versions.

-3dsMax 2009 - 64bit, the code runs fast, no lag.
-3dsMax 2010 - 64bit, the code runs fast, no lag.
-3dsMax 2011 - 64bit, lag!
-3dsMax 2012 - 64bit, lag!

Except 2011, all the others are clean installs, no scripts installed at all.

Norman3D
06-12-2011, 03:16 PM
lo, since the when construct works on the current selection, did you make sure you had 300 objects selected when you ran the script?

Here are the steps to reproduce the issue, perhaps others can try it.

1. Create 300 boxes.
2. Select all of them.
3. Run this script
deleteAllChangeHandlers id:#UDK_TRANSFORM_TRACKER
UDK_theTimer = dotNetObject "System.Timers.Timer"

fn fn_timer =
(
UDK_theTimer.stop()
print "start function"
)

dotnet.addEventHandler UDK_theTimer "Elapsed" fn_timer
UDK_theTimer.interval = 200


when transform selection changes id:#UDK_TRANSFORM_TRACKER do
(
UDK_theTimer.stop()
UDK_theTimer.start()
)
4. Move all 300 boxes.
5. Lag or no lag?

lo
06-12-2011, 03:34 PM
Yup those were the conditions I tested. Tested on another two machines, still can't reproduce the lag.

CGTalk Moderation
06-12-2011, 03: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.