Notifications
Clear all

[Closed] How to make my function faster – get peer vertex normals

Hi
I wrote function that returns normals for each model’s vertex.
It works fine, but it is so slow. Execution for standad teapot takes 7 to 10 seconds…
I have read tips in max help and also tried trick with disabling command panel, but it no give results.
Maybe I do something totaly wrong? Other parts of my script (geometry exporter) doing much more but takes less time (under one second).

There is the script. I wrote testing interface, so you can copy and run it. Result shape in viewport will be flat shaded, but in render should looks identical as source object (normals returned by my function)

-- get peer-vertex normals using normals source object
   -- not exacly working...
   -- by Miran - Miran4@op.pl
   -- you can use on GNU license terms
   
   
   -- return average point3 of point3 array
   function avg3 point3array =
   (
   	local average = point3 0.0 0.0 0.0
   
   	if( point3array.count > 0 ) then
   	(
   		for i=1 to point3array.count do
   		(
   				average += point3array[i]
   		)
   
   		average /= point3array.count
   	)
   
   	return average
   )
   
   -- returns array of face indices which using given vertice's indice
   function getFacesByVert object vert = 
   (
   	local face
   	local faces = #()
   
   	for i=1 to object.numfaces do
   	(
   		face = (getFace object i)
   
   		for j=1 to 3 do
   		(
   			if( face[j] == vert ) then
   			(
   				append faces i
   				exit
   			)
   		)	
   	)
   
   	return faces
   )
   
   --
   function getVertNormalsObj vert_obj normal_obj =
   (
   	print "### getVertNormalsObj started ###"
   	local tt=timeStamp(), t=tt
   
   	local normalMod, localNormalMod = true
   
   	-- use already existing normals modifier
   	for i=1 to normal_obj.modifiers.count do
   	(
   		if( (classOf normal_obj.modifiers[i]) == editNormals ) then
   		(
   			normalMod = normal_obj.modifiers[i]
   			localNormalMod = false
   			exit
   		)
   	)
   
   	-- create new modifier
   	if( localNormalMod ) then
   	(
   		normalMod = editNormals()
   		addModifier normal_obj (normalMod)
   	)
   		
   	-- for correct working of modifier
   	select #( normal_obj )
   	max modify mode
   
   	print( " #1 time: " + (((timeStamp()-t)/1000.0) as string) + "s" )
   	t=timeStamp()
   
   	local VertNormals = #()
   	local faces_vert, vert_normals, face, face_normals
   
   	for i=1 to vert_obj.numverts do -- this is most time-wasting loop
   	(
   		-- get faces builded on this vert
   		faces_vert = getFacesByVert vert_obj i
   
   		vert_normals = #()
   		for j=1 to faces_vert.count do
   		(
   			-- get this vert corner index in the face
   			face = getFace vert_obj faces_vert[j]
   
   			for k=1 to 3 do
   			(		
   				if( face[k] == i ) then
   				(
   					-- get this face's corner normal
   					append vert_normals (normalMod.GetNormal (normalMod.GetNormalID faces_vert[j] k))
   					exit
   				)
   			)
   		)
   
   		append VertNormals (avg3 vert_normals)
   	)
   
   	if( localNormalMod ) then
   		deleteModifier normal_obj normalMod -- remove local created modifier
   
   	print( "TOTAL time: " + (((timeStamp()-tt)/1000.0) as string) + "s" )
   
   	return VertNormals
   )
   
   -- testing interface
   rollout VertNormals "VertNormals" 
   (
   	button getBtn "Get peer-vertex normals"
   
   	on getBtn pressed do
   	(
   		undo off
   		(
   			disableSceneRedraw()
   
   			if( (selection.count > 0) and ((classOf selection[1]) == Editable_mesh) ) then
   			(
   				meshop.autoedge selection[1] selection[1].Edges 0 -- make all faces triangles
   				
   				local broken_mesh = copy selection[1]
   				broken_mesh.name = "broken"
   				meshop.breakVerts broken_mesh broken_mesh.verts -- break all verts
   				
   				local vert_normals = getVertNormalsObj broken_mesh selection[1] -- tested function here
   				for i=1 to broken_mesh.numverts do
   				( -- apply calculated peer-vertex normals (visible when render)
   					setNormal broken_mesh i vert_normals[i]
   				)
   
   				move broken_mesh (point3 (1.5*(broken_mesh.max.x-broken_mesh.min.x)) 0.0 0.0) -- move next to original object
   				select #( selection[1], broken_mesh ) -- select source and result
   				max zoomext sel all -- center viewport on selected
   			)
   			else
   				messageBox "No editable mesh selected!"
   
   			enableSceneRedraw()
   		)
   	)
   )
   
   -- create the rollout window and add the rollout
   if VertNormalsFloater != undefined do closerolloutfloater VertNormalsFloater
   	
   VertNormalsFloater = newRolloutFloater "VertNormals Floater" 200 100
   addRollout VertNormals VertNormalsFloater
   
5 Replies

when you break all vertices in the mesh you can simply get current vertex normal just using getnormal function.

Then I will get normals of flat shaded objects. I want get same normals like in original object.
Break all verts is only example for debuging. My exporter is optimizing the mesh, and breaks only these vertices which need it.

I rewrite the code. Now it no using modifier. But I reduced execute time only from 23 seconds to 17 (tested on teapot with 10 segments).
The most hevy think is part ended by #S 6 print text (17 sec). #S 4 also isn’t preety fast (4 sec).

-- get peer-vertex normals using normals source object
 -- by Miran - Miran4@op.pl
 
 -- return average point3 of point3 array
 function avg3 point3array =
 (
 	local average = point3 0 0 0
 
 	if( point3array.count > 0 ) then
 	(
 		for i=1 to point3array.count do
 			average += point3array[i]
 
 		average /= point3array.count
 	)
 
 	return average
 )
 
 function getVertNormalsObj vert_obj normal_obj =
 (
 	local t=timeStamp()
 
 	-- working on snaps avoid errors and speed up mesh operations
 	local vert_mesh
 	if( (classOf vert_obj) == mesh or (classOf vert_obj) == trimesh ) then
 		vert_mesh = vert_obj
 	else
 		vert_mesh = snapshotAsMesh vert_obj
 
 	if( (classOf normal_obj) == mesh or (classOf normal_obj) == trimesh ) then
 		normal_mesh = normal_obj
 	else
 		normal_mesh = snapshotAsMesh normal_obj
 
 	print( "#S 1: " + ( (((timeStamp()-t)/1000.0)) as string ) ); t=timeStamp()
 
 -- collect array of coresponding faces
 	local vert_mesh_centers = (for i=1 to vert_mesh.numfaces collect (meshOp.getFaceCenter vert_mesh i))
 	local normal_mesh_centers = (for i=1 to normal_mesh.numfaces collect (meshOp.getFaceCenter normal_mesh i))
 	local normal_mesh_centers_idx = (for i=1 to normal_mesh.numfaces collect i)
 
 	print( "#S 2: " + ( (((timeStamp()-t)/1000.0)) as string ) ); t=timeStamp()
 
 	mirror_face = #(); mirror_face.count = vert_mesh.numfaces
 	for i=1 to vert_mesh.numfaces do
 	(
 		for j=1 to normal_mesh_centers.count do
 		(
 			if( (distance vert_mesh_centers[i] normal_mesh_centers[j]) < 0.000005 ) then
 			(
 				mirror_face[i] = normal_mesh_centers_idx[j]
 
 				-- remove mathed face from search
 				deleteItem normal_mesh_centers_idx j
 				deleteItem normal_mesh_centers j
 				exit
 			)
 		)
 	)
 
 	print( "#S 3: " + ( (((timeStamp()-t)/1000.0)) as string ) ); t=timeStamp()
 
 	-- free memory
 	vert_mesh_centers = undefined
 	normal_mesh_centers = undefined
 	normal_mesh_centers_idx = undefined
 	gc light:true
 
 	print( "#S 4: " + ( (((timeStamp()-t)/1000.0)) as string ) ); t=timeStamp()
 
 -- get normals
 	
 	local normals = (for i=1 to vert_mesh.numverts collect #())
 	local vert_face, normal_face, faceNormals, vert_v, normal_v, vert, verts_left
 
 	print( "#S 5: " + ( (((timeStamp()-t)/1000.0)) as string ) ); t=timeStamp()
 
 	for i=1 to vert_mesh.numfaces do
 	(
 		if( mirror_face[i] != undeined ) then
 		(
 			vert_face = getFace vert_mesh i
 			normal_face = getFace normal_mesh mirror_face[i]
 			faceNormals = meshOp.getFaceRNormals normal_mesh mirror_face[i]
 
 			verts_left = #(1,2,3) -- array of yet unmatched face's corners
 			for j=1 to 3 do -- three face corners
 			(
 				if( verts_left.count != 1 ) then  -- 2 verts already mathed, so last one has to be correct
 					vert = getVert vert_mesh vert_face[j] -- store pos of corner vertice
 
 				for k=1 to verts_left.count do
 				(
 					if( (verts_left.count == 1) or ((distance vert (getVert normal_mesh normal_face[verts_left[k]])) <= 0.000005) ) then
 					(
 						--if( (findItem normals[vert_face[j]] faceNormals[verts_left[k]]) == 0 ) then -- this normal isn't already added to this vert (disable to get face count average/enable to get different normals count average, check which result is better for you)
 							append normals[vert_face[j]] faceNormals[verts_left[k]]
 
 						if( verts_left.count != 1 ) then -- don't do for last one
 						(
 							deleteItem verts_left k -- matched
 							exit
 						)
 					)
 				)
 			)
 		)	
 	)
 
 	print( "#S 6: " + ( (((timeStamp()-t)/1000.0)) as string ) ); t=timeStamp()
 
 -- average results to get single normal peer vert
 	local avg_normals = #()
 	avg_normals.count = vert_mesh.numverts
 
 	for i=1 to vert_mesh.numverts do
 	(
 		if( normals[i].count > 1 ) then
 			avg_normals[i] = (avg3 normals[i])
 		else
 		if( normals[i].count == 1 ) then
 			avg_normals[i] = normals[i][1]
 		else
 			avg_normals[i] = [0,0,0]
 	)
 
 	print( "#S 7: " + ( (((timeStamp()-t)/1000.0)) as string ) ); t=timeStamp()
 
 	return avg_normals
 )
 
 -- testing interface
 rollout VertNormals_roll "VertNormals" 
 (
 	button getBtn "Get peer-vertex normals"
 
 	on getBtn pressed do
 	(
 		undo off
 		(
 			--disableSceneRedraw()
 
 			if( (selection.count > 0) and ((classOf selection[1]) == Editable_mesh) ) then
 			(
 				meshop.autoedge selection[1] selection[1].Edges 0 -- make all faces triangles
 				
 				local broken_mesh = copy selection[1]
 				broken_mesh.name = "broken"
 				meshop.breakVerts broken_mesh broken_mesh.verts -- break all verts
 				
 				local t = timeStamp()
 				local vert_normals = getVertNormalsObj broken_mesh selection[1] -- tested function here
 				print( "NEW GET NORMALS TIME: " + ( (((timeStamp()-t)/1000.0)) as string ) )
 
 				for i=1 to broken_mesh.numverts do
 				( -- apply calculated peer-vertex normals (visible when render)
 					setNormal broken_mesh i vert_normals[i]
 				)
 
 				move broken_mesh (point3 (1.5*(broken_mesh.max.x-broken_mesh.min.x)) 0.0 0.0) -- move next to original object
 				select #( selection[1], broken_mesh ) -- select source and result
 				max zoomext sel all -- center viewport on selected
 			)
 			else
 				messageBox "No mesh selected!"
 
 			--enableSceneRedraw()
 		)
 	)
 )
 
 -- create the rollout window and add the rollout
 if VertNormalsFloater != undefined do closerolloutfloater VertNormalsFloater
 	
 VertNormalsFloater = newRolloutFloater "VertNormals Floater" 200 100
 addRollout VertNormals_roll VertNormalsFloater

Maye is there better way to match coresponding object’s faces and corners.

there is a topic in maxscript help named “how to make it faster” try to use those tips.
Try first the “do not use return, break, exit or continue”

[color=white][/color]
cheers

I didn’t know that “exit” function works so slow.
After exit replace it executes in less than one second (before 17 seconds).
Thanks!