Notifications
Clear all

[Closed] when transform changes

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?

27 Replies
1 Reply
(@denist)
Joined: 11 months ago

Posts: 0

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.

 eek

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…

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.

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!

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.

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 "%
	%, %
" _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.

1 Reply
(@norman3d)
Joined: 11 months ago

Posts: 0

First of all thanks denis once more for your help!

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.

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
)

what does the unreal actually need? what do you send to the unreal? as I know it should be a string…

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

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.

Page 1 / 3