Notifications
Clear all

[Closed] Need help determining vertex normals

I’m trying to write a little export tool for a game framework me and my friend are building. So far so good. Everything works except for determining the normals. I’ve Googled around and searched these forums but couldn’t find an answer to my question. To be honest, this is my first Maxscript attempt. Comments on my code are also welcome.


--Export Animated Lego Mesh v0.3.0

macroscript ExportAnimatedLegoMesh buttontext:"Export *.alm" tooltip:"Export selected as Animated Lego Mesh" category:"Alles Kan Beter" (
	
	file = undefined
	legoMesh = undefined
	mountHead = $MountHead
	start = undefined
	end = undefined

	function SerializeMesh = (
		
		numberOfFaces = polyop.getnumfaces LegoMesh
		
		for face = 1 to numberOfFaces do (
			
			format "<Face>
" to:file
			
			verticesFace = polyop.getfaceverts legoMesh face
			faceNormal = polyop.getfacenormal legoMesh face
			faceMap = polyop.getmapface legoMesh 1 face
			numberOfVerticesFace = verticesFace.count
			
			for vertex = 1 to numberOfVerticesFace do (
				
				position = polyop.getvert legoMesh verticesFace[vertex]
				uv = polyop.getmapvert legoMesh 1 faceMap[vertex]
				
				format "<VertexPNT px=\"%\" py=\"%\" pz=\"%\" nx=\"%\" ny=\"%\" nz=\"%\" u=\"%\" v=\"%\"/>
" position.x position.z position.y faceNormal.x faceNormal.z faceNormal.y uv.x (1 - uv.y) to:file
			)
			
			format "</Face>
" to:file
		)
	)

	function SerializeAnimation = (
		
		startFrame = slidertime
		
		for frame = start to end do (
			
			format "<Frame>
" to:file
			slidertime = frame
			SerializeMesh()
			
			if mountHead != undefined then (
				
				format "<MountHead>
" to:file
				
				position = mountHead.position
				rot = mountHead.rotation as eulerangles
				
				format "<VertexPR px=\"%\" py=\"%\" pz=\"%\" rx=\"%\" ry=\"%\" rz=\"%\"/>
" position.x position.z position.y rot.x rot.y rot.z to:file
				format "</MountHead>
" to:file
			)
			
			format "</Frame>
" to:file
		)
		
		slidertime = startFrame
	)

	rollout TimelineRangePicker "Timeline Range Picker" (
		
		spinner	snrStart	"Start:"	type:#integer	range:[-1000, 1000, 0]
		spinner	snrEnd	"End:"	type:#integer	range:[-1000, 1000, 100]
		button	btnOk	"Ok"
		
		on btnOk pressed do (
			
			start = snrStart.value
			end = snrEnd.value
			
			if end - start >= 0 then (
				
				format "<?xml version=\"1.0\"?>
" to:file
				format "<AnimatedLegoMesh>
" to:file
				SerializeAnimation()
				format "</AnimatedLegoMesh>" to:file
				close file
				destroydialog TimelineRangePicker
				messagebox "Export complete." title:"Info"
			) else (
				
				messagebox "No frames to export." title:"Error"
			)
		)
	)

	if selection.count == 1 then (
		
		legoMesh = selection[1]
		
		if classof legoMesh == editable_poly or classof legoMesh == polymeshobject then (
			
			filePath = getsavefilename caption:"Save as" types:"Animated Lego mesh (*.alm)|*.alm"
			
			if filePath != undefined then (
				
				file = createfile filePath
				
				if file != undefined then (
					
					createdialog TimelineRangePicker
				) else (
					
					messagebox "Unable to create file." title:"Error"
				)
			) else (
				
				messagebox "Unable to create file." title:"Error"
			)
		) else (
			
			messagebox "Incompatible data type." title:"Error"
		)
	) else (
		
		if selection.count < 1 then (
			
			messagebox "Nothing to export." title:"Error"
		) else (
			
			messagebox "Can not export more then one object at a time." title:"Error"
		)
	)
)

Thanks in advance.

3 Replies

Hi Nicolas and welcome,
I guess the simplest way to get vert normals is to average normals from faces sharing them:

(
    local oSphere = convertToPoly(Sphere())
    local iVert = 26 -- just choose a vert
    polyOp.setVertSelection oSphere iVert -- just to have a visual clue, unneeded
    
    local baVertFaces = polyOp.getFacesUsingVert oSphere iVert
    local p3VertNormal = [0,0,0]

    -- Note: It's better to collect all face normals before computing vert normals. See following point 3.

    for iFace in baVertFaces do
        p3VertNormal += polyOp.getFaceNormal oSphere iFace

    p3VertNormal = normalize(p3VertNormal)
    print p3VertNormal
)

There are other ways, that weight vert normals about face angles, dimensions and so on.
Take a look at 3ds Max 2009 SDK Help under: “Computing Face and Vertex Normals”.

I got some hints by reading your code. Some of them are just a question of taste, other could improve your code.

  1. Unless you need ordered indexes, use BitArrays rather than Arrays, it’s much faster.
verticesFace = #() -- Array
verticesFace = polyOp.getFaceVerts legoMesh face

-- use: polyOp.getVertsUsingFace <Poly poly> <int face>
verticesFace = #{} -- BitArray
verticesFace = polyOp.getVertsUsingFace legoMesh face
  1. Do not take an array only to get the number of its elements if there are straight methods:
verticesFace = polyOp.getFaceVerts legoMesh face
numberOfVerticesFace = verticesFace.count
for vertex = 1 to numberOfVerticesFace do ( ... )

-- use: polyOp.getFaceDeg <Poly poly> <int face>
numberOfVerticesFace = polyOp.getFaceDeg legoMesh face
for vertex = 1 to numberOfVerticesFace do ( ... )
  1. Do not get position for the same vert more than once, polyOp.getVert is a quite heavy task. Store all verts position in a Point3 Array then access it. Same goes for uvMapVerts
for vertex = 1 to numberOfVerticesFace do (
    position = polyop.getvert legoMesh verticesFace[vertex]
    ...
)

-- becomes:
local iNumVerts = polyOp.getNumVerts LegoMesh

local ap3VertsPos = #() -- Array
ap3VertsPos[iNumVerts] = [0,0,0] -- size initialization

for iVert = 1 to iNumVerts do (
    ap3VertsPos[iVert] = polyOp.getVert LegoMesh iVert
)
-- or:
ap3VertsPos = for iVert = 1 to iNumVerts collect (polyOp.getVert LegoMesh iVert)

-- assuming verticesFace is an Array storing ordered list of verts in a face
for vertex = 1 to numberOfVerticesFace do (
    position = ap3VertsPos[verticesFace[vertex]]
    ...
)
  1. Do not query the count of an array to iterate over its elements. Use for…in…do, it’s faster.
-- previous loop becomes:
for vertex in verticesFace do (
    position = ap3VertsPos[vertex]
    ...
)
-- works with Arrays and BitArrays
  1. Avoid declaring static object names into your code without a check.
mountHead = $MountHead

-- becomes:
if (selection[1].name == "MountHead") then ( mountHead = selection[1] )

It’s better to have a pickButton to point to scene geometry, maybe with a filter function.

-- to filter object with that name, not particularly smart anyway, but you can set it as you like
function filterMesh = (selection[1].name == "MountHead")

pickButton pbtGetMesh "getMesh" message:"Pick the Mesh to Export" filter:filterMesh autoDisplay:true

-- and later in the code:
if (mountHead != undefined) then ( ... )
-- becomes:
if (pbtGetMesh.object != undefined) then ( ... )
  1. If there are long operations giving feedback only at the end of processing consider using a ProgressBar to show the computer is not frozen.

  2. Not required since variables declared inside a MacroScript are local to it, but it nicer to specify it with a “local”.

Putting all this buzzing code chat aside, nice MaxScript start!

  • Enrico

Thank you SyncViewS for the very informative reply. Much appreciated. I haven’t been able to test your solution for the exact same reason it took me a while to answer your post. I’m waiting for my computer to be repaired. I’ll let you know how it turns out. Thanks again.

Splendid maxscripting tips you got there. These would be a good addition to the “How to Make It Better?” category in the maxscript helpfile. Thank you SyncViewS!