Notifications
Clear all

[Closed] The Challenge >>> Biped Figure Mode

Yes, I know that this site is dying as a professional resource. But according to the old tradition, I want to offer one more challenge.

Imagine I have a CheckButton “Biped In Figure Mode” in the MXS dialog (simple Rollout).

This button should always be ON if any Biped system is in figure mode, and OFF otherwise.

Obviously, we need to track events … but which ones? what is the safest way to keep track of all related events and callbacks?

How do we set up a minimal but sufficient kind of callbacks to track this task?

try(destroydialog FigureModeCheck) catch()
rollout FigureModeCheck "Figure Mode Check" width:191
(
	checkbutton figure_mode_cb "Figure Mode" width:180 align:#center	

	on FigureModeCheck open do
	(
		b = for obj in objects where iskindof obj.controller Vertical_Horizontal_Turn do exit with obj
		if isvalidnode b do figure_mode_cb.state = b.controller.figureMode 
	)
)
createdialog FigureModeCheck

this is a good test to check how well you know the event mechanism in MAX

19 Replies

Not very elegant, but it kinda works

try(destroydialog FigureModeCheck) catch()
rollout FigureModeCheck "Figure Mode Check" width:191
(
	checkbutton figure_mode_cb "Figure Mode" width:180 align:#center
	
	local bipeds = #()
	
	fn UpdateCheckbox =
	(		
		local state = false
		bipeds = for n in bipeds where isValidNode n collect ( state = state or n.controller.figureMode; n )
		if state != figure_mode_cb.checked do figure_mode_cb.checked = state
	)
	
	fn OnEvent ev nodes =
	(
		local complete = false
		for handle in nodes while not complete where isKindOf (getAnimByHandle handle) Biped_Object do ( complete = true; UpdateCheckbox() )
	)
	
	fn OnAdded ev nodes =
	(
		local found = false
		
		for n in nodes while not found do
		(
			local new_node = getAnimByHandle n
			
			if isKindOf new_node Biped_Object do
			(
				found = true
				for n in getClassInstances Vertical_Horizontal_Turn do appendIfUnique bipeds n.rootnode
				UpdateCheckbox()
			)				
		)		
	)
	
	fn OnDeleted ev nodes =
	(
		local complete = false
		for n in bipeds while not complete where not isValidNode n do ( complete = true; UpdateCheckbox() )		
	)
	
	local handler = NodeEventCallback enabled:true controllerOtherEvent:OnEvent	added:OnAdded deleted:OnDeleted
	
	on FigureModeCheck open do
	(
		for n in getClassInstances Vertical_Horizontal_Turn do appendIfUnique bipeds n.rootnode

		UpdateCheckbox()
	)
	
	on FigureModeCheck close do
	(
		handler = undefined; gc light:true
	)
)
createdialog FigureModeCheck

Not too bad, neither too good. There must be a way to minimize the calls even more.

try(destroyDialog ::RO_FIGUREMODECHECK) catch()
rollout RO_FIGUREMODECHECK "Figure Mode Check" width:200
(
	checkbutton figure_mode_cb "Figure Mode" width:180 height:32
	
	local calls = 0
	local instances = #()
	local nodesCallback = undefined
	
	fn UpdateUI =
	(
		mode = false
		for ctrl in instances do mode = mode or ctrl.figureMode
		figure_mode_cb.state = mode
		format "mode:%\tcalls:%\n" mode (calls+=1)
	)
	
	fn UpdateInstances event node =
	(
		deleteAllChangeHandlers id:#ID_0X712292CE
		instances = getClassInstances Vertical_Horizontal_Turn
		
		when parameters instances changes id:#ID_0X712292CE handleAt:#redrawViews do UpdateUI()
		UpdateUI()
	)
	
	on RO_FIGUREMODECHECK open do
	(
		UpdateInstances 0 0
		nodesCallback = NodeEventCallback added:UpdateInstances deleted:UpdateInstances
	)
	
	on RO_FIGUREMODECHECK close do
	(
		deleteAllChangeHandlers id:#ID_0X712292CE
		nodesCallback = undefined
		gc light:on
	)
)
createDialog ::RO_FIGUREMODECHECK

damn
So much less code and it is way simpler. I didn’t know the fact that you can use when construct with collections.

we’re on the same track … use NodeEventCallback to track scene changes and when context to track Figure mode change.

now let’s get down to optimization.

getClassInstances is slow method. To find a specified class instances we enumerate ALL animatables. It’s faster to find Bip roots than all Vertical_Horizontal_Turn controllers


fn getBipBodies  = (for obj in geometry where iskindof (c = obj.controller) Vertical_Horizontal_Turn collect c)

(
	t0 = timestamp()
	h0 = heapfree

	for k=1 to 1000 do
	(
--getClassInstances Vertical_Horizontal_Turn
--getbipbodies()
	)

	format "time:% heap:%\n" (timestamp() - t0) (h0 - heapfree)
)

if we call getClassInstances just once it always wins, even in situations when there’re thousands of nodes present in a scene. I don’t have a real big project to test if it is still a true in general.

Timings
calls | nodes | getBipBodies | getClassInstances
  1 |     1 |      0 |      1
  3 |     1 |      0 |      0
  5 |     1 |      0 |      1
 10 |     1 |      0 |      2

  1 |    10 |      1 |      0
  3 |    10 |      0 |      1
  5 |    10 |      0 |      1
 10 |    10 |      0 |      3

  1 |   100 |      2 |      1
  3 |   100 |      1 |      2
  5 |   100 |      2 |      4
 10 |   100 |      2 |      8

  1 |  1000 |     21 |      8
  3 |  1000 |     11 |     23
  5 |  1000 |     18 |     38
 10 |  1000 |     33 |     75

  1 |  5000 |    103 |     38
  3 |  5000 |     60 |    113
  5 |  5000 |     99 |    191
 10 |  5000 |    197 |    379

  1 |  6000 |    124 |     46
  3 |  6000 |     71 |    139
  5 |  6000 |    119 |    230
 10 |  6000 |    236 |    458

  1 |  7000 |    149 |     55
  3 |  7000 |     83 |    163
  5 |  7000 |    138 |    275
 10 |  7000 |    278 |    544

  1 |  8000 |    165 |     64
  3 |  8000 |     94 |    186
  5 |  8000 |    156 |    307
 10 |  8000 |    313 |    615

  1 |  9000 |    188 |     71
  3 |  9000 |    105 |    213
  5 |  9000 |    178 |    354
 10 |  9000 |    357 |    703

  1 | 10000 |    213 |     81
  3 | 10000 |    119 |    237
  5 | 10000 |    198 |    385
 10 | 10000 |    388 |    778
(
	fn getBipBodies  = (for obj in geometry where iskindof (c = obj.controller) Vertical_Horizontal_Turn collect c)
	format "calls | nodes | getBipBodies | getClassInstances\n" 
	for i in #( 1, 10, 100, 1000, 5000, 6000, 7000, 8000, 9000, 10000 ) do
	(		
		with undo off
		( 
			delete objects; gc() 
			for j = 1 to i do Teapot()
		)

		for max_iterations in #( 1, 3, 5, 10 ) do
		(
			result = #(max_iterations,i,0,0)
			(
				t0 = timestamp()

				for k = 1 to max_iterations do
				(
					getbipbodies()
				)

				result[3] = timestamp() - t0
			)

			(
				t0 = timestamp()

				for k = 1 to max_iterations do
				(
					getClassInstances Vertical_Horizontal_Turn
				)

				result[4] = timestamp() - t0
			)

			format "%\n" ((dotNetClass "system.string").format "{0,3} | {1,5} | {2,6} | {3,6}" (dotNet.ValueToDotNetObject result (dotnetclass "system.object[]")))
		)
		format "\n" 
	)
	
	
)

check this snippet:

delete objects 
for k=1 to 1000 do point()

fn getpoints = (for obj in objects where iskindof obj point collect obj)

(
	t0 = timestamp()
	h0 = heapfree

	for k=1 to 1000 do
	(
		getclassinstances point
		--getpoints()
	)

	format "time:% heap:%\n" (timestamp() - t0) (h0 - heapfree)
)

sometimes the number of nodes in a scene is not very important. more dramatic is a number of cross-dependencies (it includes controllers, attributes, modifiers, materials, … all reference targets (anims))

For a scene with 13000 bipeds, 10000 additional objects and 10000 materials I get these results:

fn getBipBodies = (for obj in geometry where iskindof (c = obj.controller) Vertical_Horizontal_Turn collect c)

time:169 heap:6980544L

getClassInstances Vertical_Horizontal_Turn processAllAnimatables:true

time:56 heap:33592L

Hmm…

But what numbers do you have with my snippet above?

getclassinstances point

time:3205 heap:232120L

getpoints()

time:745 heap:96120L

But, for this example (which is more “realistic”), I get these numbers:

I mean more “realisctic” because in my code (post 3), getclassinstances() is called when an object is created/deleted only, so it won’t be called too frequently, not 1000 times in less than a second for sure.

(
	with undo off
	(
		delete objects 
		for k=1 to 10000 do point()
	)
	
	gc()

	fn getpoints = (for obj in objects where iskindof obj point collect obj)
	
	t0 = timestamp()
	h0 = heapfree
	
	--getclassinstances point processAllAnimatables:true
	--getpoints()
	
	format "time:% heap:%\n" (timestamp() - t0) (h0 - heapfree)
)
getclassinstances point processAllAnimatables:true

time:50 heap:1368672L

getpoints()

time:49 heap:1368536L

Page 1 / 2