Notifications
Clear all

[Closed] Speeding a script controller as much as possible

All right guys, I need your help. I’ve been delaying this post for long now trying to solve the problems on my own but I progress too slow and hence at this rate I’m going to give up. Something I don’t want to. And before I forget, I’m using Max 2012 x64.

I don’t know about the code of my helper plugin. Never coded a scripted plugin before and it took me some time to figure out how not to create the mesh all the time. It was slow as hell before, now feels ok for me. But still, I think the line “meshop.autoEdge effectorMesh #All 0.1” could be replaced with something faster. Although maybe it’s not needed since the mesh is created only at specific cases. Either way any feedback is very welcome. Here it is:

plugin helper falloff_gizmo
name:"Falloff Gizmo"
category: "Eugenio's Helpers"
classID: #(0xa71e203, 0x4ee2cb80)
extends:dummy
replaceUI:true
(
	local effectorMesh, mVerts
	
	parameters gizmoP rollout:gizmoR (Segments	type:#integer		ui:ui_segs		default:30		animatable:false)

	parameters paramsP rollout:paramsR
	(
		size			type:#float			ui:ui_size		default:10.0	animatable:true
		'falloff'		type:#float			ui:ui_falloff		default:0.0		animatable:true
		
		inside			type:#float		ui:ui_inside1		animatable:true		default:0.0
		outside		type:#float		ui:ui_outside1		animatable:true		default:1.0
	)
	
	fn setEffectorSize =
	(
		for i=1 to (j = mVerts.count) do
		(
			ns = normalize mverts[i]*size
			nf = ns*(1-('falloff'/100))
			
			setVert effectorMesh i ns
			setVert effectorMesh (j+i) nf
		)
	)

	fn createMesh =
	(
		effectorMesh = triMesh()

		v1 = for i=1 to Segments collect [cos (i*(360.0/Segments))*size, sin (i*(360.0/Segments))*size, 0]
		
		f1 = for i=1 to v1.count-2 collect [1,i+1,i+2]
		
		setMesh effectorMesh verts:v1 faces:f1
		
		meshop.autoEdge effectorMesh #All 0.1
		
		m2 = copy effectorMesh.mesh
		rotate m2 (eulerAngles 90 0 0)
		
		m3 = copy effectorMesh.mesh
		rotate m3 (eulerAngles 0 90 00)
		
		meshop.attach effectorMesh m2
		meshop.attach effectorMesh m3
		
		nv = effectorMesh.numVerts
		mVerts = for v=1 to nv collect getVert effectorMesh v
		
		meshop.attach effectorMesh (copy effectorMesh)
	)
	
	on getDisplayMesh do
	(
		if effectorMesh == undefined then
			createMesh()
		else
			setEffectorSize()
		
		effectorMesh.mesh
	)	
	
	rollout gizmoR "Gizmo Display"
	(
		group "Gizmo Resolution"
		(
			spinner ui_segs "Segments:" type:#integer	range: [4,100,30]
		)
		on ui_segs changed val do createMesh()
	)
	
	rollout paramsR "Falloff Gizmo"
	(
		group "Parameters"
		(
			spinner ui_size "Size:" range:[0,1e9,0]
			spinner ui_falloff "Falloff:" range:[0,100,0]
		)
		group "Ranges"
		(
			spinner ui_inside1 "In:" range:[-1e9,1e9,0]	 	align:#left		width:59
			spinner ui_outside1 "Out:" range:[-1e9,1e9,0]	align:#right		width:59	offset:[0,-20]
		)
	)
	
	tool create
	( 
        on mousePoint click do case click of 
		(
			1:
			(
				nodeTM.translation = gridPoint
				size=0
			)
		)
		
		on mouseMove click do case click of
		(
			2: 
			(
				size = length gridDist
				createMesh()
			)
			3: 'falloff' = if (d = length gridDist) > 100 then 100 else if d < 0 then 0 else d
			4: #stop
		)
    ) 
)

Ok, so let’s get to the questions:

1: As my main concern here is speed, THIS topic was really interesting. Unfortunately soon the talk got too “deep” and I could not follow it anymore. However, as shown in there, script controllers seem to be a bottleneck speed and memory wise. But I don’t know how to achieve the same I’m doing without them. I was planning to make some functions (like different falloff modes maybe) inside the falloff object and call them from the script controllers, but I can’t do that with expression or wire. Does weak references help speeding things up here?

Well, I don’t know if it’s possible through maxscript only, but I wanted to be able to have a workable feedback (8-10fps?!) with at least 1k objects. Am I asking too much from a scripted solution?

Test scene (must run the scripted plugin above first!):

(
	delete objects
	
	fn setScript effector obj =
	(
		ss = float_script()
		ss.addConstant "inside" effector.baseobject[#inside]
		ss.addConstant "outside" effector.baseobject[#outside]
		ss.addNode "obj" obj
		ss.addNode "effector" effector
		ss.addTarget "size" effector.baseObject[#size]
		ss.addTarget "falloff" effector.baseObject[#falloff]
		
		script = "" as stringStream
		format "rIn = size - (size * (falloff/100))
dist = distance effector.pos obj.pos
x = (dist-rIn)/(size - rIn)
scale = (1-x)*inside + x*outside
scl = if dist <= size then if dist >= rIn then scale else inside else outside" to:script
		ss.script = script as string
		
		ss
	)
	
	sp = sphere radius:10 pos:[80,0,0] wirecolor:brown
	obj = falloff_gizmo size:100 falloff:60 inside:10 outside:2 wirecolor:orange
	sp.radius.controller = setScript obj sp
	
	gc()
	t1 = timestamp()
	mem = heapfree
	count = 10000
	for i=1 to count do sp.radius
	
	format "Time: %   Mem: %
" (timestamp() - t1) (mem - heapfree)
	
)

2: I’m doing this tool to control arbitrary parameters, essentially float controllers. I want to have dynamic UI controls so the user can add/remove new ranges to link to different things. The approach I’m experimenting with is through custom attributes and redefining it as I add/remove stuff. But I don’t like the UI blinking it causes. Is it possible to avoid that? Is there a more correct/elegant way of doing this?

3: How to access the node itself from inside the scripted plugin? I tried using “this” with no luck. Can’t find anything in maxscript help. This is a problem for me since I’m adding the custom attribute to the node by using the “$” which is fine if the user explicitly creates the object. But if I try to create it via script it of course fails.

4: As the affected objects hold a script controller, deleting the effector will make a mess. I was thinking in storing the affected controllers in the effector node. So I can revert them before deleting the effector. Does the #maxObjectTab support controller values? The best way for dealing with this is with a #nodePreDelete callback, right?

Forgive me if some questions are too noob, but my coding skills are waaaaayyyy outdated. So I’m completely opened to any tip, suggestion or anything to improve.

Regards,
Eugenio

1 Reply

Have you tried using the on <param> set <arg> do () and on <param> get <arg> do ( <arg> ) handlers within your param blocks? This would dispense entirely with the need for any external controllers as the paramblock parameters would have their own sub-anim tracks (assuming you have set them to animatable:true).

Edit: A quick hack of your code yeilds this… Link your sphere object to the helper using the pick button on the UI then the UI properties will change the radius of the sphere:

plugin helper falloff_gizmo
name:"Falloff Gizmo"
category: "Eugenio's Helpers"
classID: #(0xa71e203, 0x4ee2cb80)
extends:dummy
replaceUI:true
(
	local effectorMesh, mVerts
	
	parameters gizmoP rollout:gizmoR (Segments	type:#integer		ui:ui_segs		default:30		animatable:false)

	fn getEffectorSize =
	(
		if this.self != undefined and this.theNode != undefined do
		(
			rIn = this.size - (this.size * (this.falloff/100.0))
			dist = distance (getNodeByName this.self).pos this.theNode.pos
			x = (dist-rIn)/(this.size - rIn)
			scale = (1-x)*this.inside + x*this.outside
			scl = if dist <= this.size then if dist >= rIn then scale else this.inside else this.outside
		)
	)
	
	parameters paramsP rollout:paramsR
	(
		size		type:#float		ui:ui_size		default:10.0		animatable:true
		falloff		type:#float		ui:ui_falloff		default:0.0			animatable:true
		self		type:#string												animatable:false
		theNode type:#node	ui:theNode 								animatable:false
		inside		type:#float		ui:ui_inside1	default:0.0			animatable:true
		outside	type:#float		ui:ui_outside1	default:1.0			animatable:true
		
		on size set val do
		(
			if theNode != undefined do theNode.radius = getEffectorSize()
		)
		on falloff set val do
		(
			if theNode != undefined do theNode.radius = getEffectorSize()
		)
		on inside set val do
		(
			if theNode != undefined do theNode.radius = getEffectorSize()
		)
		on outside set val do
		(
			if theNode != undefined do theNode.radius = getEffectorSize()
		)
	)
	
	fn setEffectorSize =
	(
		for i=1 to (j = mVerts.count) do
		(
			ns = normalize mverts[i]*size
			nf = ns*(1-(this.falloff/100.0))
			
			setVert effectorMesh i ns
			setVert effectorMesh (j+i) nf
		)
	)

	fn createMesh =
	(
		effectorMesh = triMesh()

		v1 = for i=1 to Segments collect [cos (i*(360.0/Segments))*size, sin (i*(360.0/Segments))*size, 0]
		
		f1 = for i=1 to v1.count-2 collect [1,i+1,i+2]
		
		setMesh effectorMesh verts:v1 faces:f1
		
		meshop.autoEdge effectorMesh #All 0.1
		
		m2 = copy effectorMesh.mesh
		rotate m2 (eulerAngles 90 0 0)
		
		m3 = copy effectorMesh.mesh
		rotate m3 (eulerAngles 0 90 00)
		
		meshop.attach effectorMesh m2
		meshop.attach effectorMesh m3
		
		nv = effectorMesh.numVerts
		mVerts = for v=1 to nv collect getVert effectorMesh v
		
		meshop.attach effectorMesh (copy effectorMesh)
	)
	
	on getDisplayMesh do
	(
		if effectorMesh == undefined then
			createMesh()
		else
			setEffectorSize()
		
		effectorMesh.mesh
	)	
	
	rollout gizmoR "Gizmo Display"
	(
		group "Gizmo Resolution"
		(
			spinner ui_segs "Segments:" type:#integer	range: [4,100,30]
		)
		on ui_segs changed val do createMesh()
	)
	
	rollout paramsR "Falloff Gizmo"
	(
		pickButton theNode "Pick Node"
		group "Parameters"
		(
			spinner ui_size "Size:" range:[0,1e9,0]
			spinner ui_falloff "Falloff:" range:[0,100,0]
		)
		group "Ranges"
		(
			spinner ui_inside1 "In:" range:[-1e9,1e9,0]	 	align:#left		width:59
			spinner ui_outside1 "Out:" range:[-1e9,1e9,0]	align:#right		width:59	offset:[0,-20]
		)
		
		on theNode picked obj do
		(
			if obj != undefined do theNode.text = obj.name
		)
		on paramsR open do
		(
			if this.theNode != undefined then theNode.text = this.theNode.name
		)
	)
	
	tool create
	( 
        on mousePoint click do case click of 
		(
			1:
			(
				nodeTM.translation = gridPoint
				size=0
			)
		)
		
		on mouseMove click do case click of
		(
			2: 
			(
				size = length gridDist
				createMesh()
			)
			3: this.falloff = if (d = length gridDist) > 100 then 100 else if d < 0 then 0 else d
			4: #stop
		)
    ) 
	on attachedToNode n do
	(
		this.self = n.name
	)
)

if you want the sphere to update when the helper is moved then you can use the when construct to add a change handler, something like this:

when transform $'Falloff Gizmo001' changes id:#fallOffGizmos do $'Falloff Gizmo001'.size = $'Falloff Gizmo001'.size

You can add as many handlers as you need to the same id, then when you’re done you can remove them with the delete change handler functions (check the reference for details).

Hope that helps!