Code archives/3D Graphics - Mesh/UpdateNormalsAngle

This code has been declared by its author to be Public Domain code.

Download source code

UpdateNormalsAngle by BIG BUG2008
This is an enhanced UpdateNormals function allowing to set an angle, to which two faces are smoothed.

The regular UpdateNormals just smoothes everything, even when a clear edge is desired, so especially mechanical models aren't that well-shaded.
There is already a code for flat shading in the archives, but with that one curved parts are flattened too, so it's not a solution for a complex mesh with angular and curved parts.
Using the UpdateNormalsAngle function it is now possible to set the normals for a smooth shading on curved parts and a hard shading on angular parts.
The parameter "Angle" is compared with the delta angle between two vertex normals. So the maximum value is 180°, which means the normals are exactly opposite. In this case the function works like the regular UpdateNormals, every face is smoothed.
With the default of 89° square edges stay, obtuse ones get smoothed.

Just try this example to get a feeling how to use this function:
http://www.mein-murks.de/quellcode/UpdateNormals.zip
;by Robert Hierl / www.mein-murks.de / 30.12.2007
Type tVertexVector

	Field vertex
	Field x#
	Field y#
	Field z#
	
End Type


Type tVertexTree

	Field x#
	Field y#
	Field z#
	Field vertices.tVertexVector[50]
	Field octree.tVertexTree[7]

End Type



Function UpdateNormalsAngle( pMesh, pAngle# = 89 )
;This function is used much like the regular UpdateNormals in B3D. Unlike the original function, 
;it doesn't just smooth all edges, but provides an option to set an angle, to which two faces are smoothed.
;So it's a very easy way to improve shading on a mesh, without setting up each normal manually.
;Author Robert Hierl / www.mein-murks.de

	Local surf, numSurface
	Local vert, numVertex
	Local vertexTree.tVertexTree
	
	Local triNormal.tVertexVector
	
	
	Local v1.tVertexVector, v2.tVertexVector, v3.tVertexVector

	;handle special ones
	If pAngle# =  0   Then UpdateNormalsFlat( pMesh ) : Return
	;regular UpdateNormals works only, when mesh was modified, so line is commented for this example
	;If pAngle# >= 180 Then UpdateNormals( pMesh ) : Return


	For numSurface = 1 To CountSurfaces( pMesh )

		surf = GetSurface( pMesh, numSurface )

		Delete Each tVertexTree
		vertexTree = New tVertexTree

		;gather all possible vertex coordinates with their triangle normal		
		For numTriangle = 0 To CountTriangles( surf ) - 1

			v1 = GetVertexVector(surf, TriangleVertex( surf, numTriangle, 0 ))
			v2 = GetVertexVector(surf, TriangleVertex( surf, numTriangle, 1 ))
			v3 = GetVertexVector(surf, TriangleVertex( surf, numTriangle, 2 ))	
			
			;calculate triangle normal
			triNormal = GetTriangleNormal(surf, v1\vertex, v2\vertex, v3\vertex)
			
			;add each vertex with calculated normal
			AddVertex2Tree(vertexTree, v1, triNormal)
			AddVertex2Tree(vertexTree, v2, triNormal)
			AddVertex2Tree(vertexTree, v3, triNormal)
				
			;clean up
			Delete triNormal
			Delete v1
			Delete v2
			Delete v3
		
		Next

		;calculate and set new vertex normals			
		For vertexTree = Each tVertexTree	
		
			SetNormalsMulti(vertexTree, surf, pAngle#)

		Next

		;clean up
		Delete Each tVertexTree
		Delete Each tVertexVector

	Next

End Function


Function UpdateNormalsFlat(mesh)
;This simple function is used to disable smoothing on a mesh. It is faster than UpdateNormalsAngle with value 0.

	Local surf, numSurface, numTriangle
	Local v1, v2, v3
	Local triNormal.tVertexVector

	For numSurface = 1 To CountSurfaces(mesh)

		surf = GetSurface(mesh, numSurface)
		
		For numTriangle = 0 To CountTriangles( surf ) - 1
		
			;calculate normal for each triangle
        	v1   = TriangleVertex(surf, numTriangle, 0)
        	v2   = TriangleVertex(surf, numTriangle, 1)
        	v3   = TriangleVertex(surf, numTriangle, 2) 

			triNormal = GetTriangleNormal(surf, v1, v2, v3)
		
			;set normals for vertex
			VertexNormal surf, v1, triNormal\x, triNormal\y, triNormal\z
 
			;when using EntityFX 4, only the first vertex normal is relevant, in this case just comment the following two lines
			VertexNormal surf, v2, triNormal\x, triNormal\y, triNormal\z
			VertexNormal surf, v3, triNormal\x, triNormal\y, triNormal\z
			
		Next
     Next

End Function



Function SetNormalsMulti(vertexTree.tVertexTree, surf, pAngle#)
	;calculate new normals
	
	Local ax#, ay#, az#
	Local lx#, ly#, lz#
	Local nx#, ny#, nz#
	Local factor#, merged
	Local diffAngle#, vertex.tVertexVector
	
	Local i, l


		Repeat
		
			merged = False

			For i = 0 To 50	
	
				If vertexTree\vertices[i] = Null Then Exit
				
				vertexcount = 0
				nx# = 0
				ny# = 0
				nz# = 0		
	
				For l = 0 To 50
				
					If vertexTree\vertices[l] = Null Then Exit	
									
					diffAngle# = VectorAngle#(vertexTree\vertices[l], vertexTree\vertices[i])
					
					If diffAngle# <= pAngle# Then
					
						If diffAngle# > 0 Then merged = True;						

						vertex.tVertexVector = vertexTree\vertices[l]
						vertexcount = vertexcount + 1
						nx# = nx# + vertex\x
						ny# = ny# + vertex\y
						nz# = nz# + vertex\z
						
					EndIf
				
				Next
			
				nx# = nx# / vertexcount
				ny# = ny# / vertexcount
				nz# = nz# / vertexcount

				
				;normalize result
				factor# = Sqr((nx# * nx#)+(ny# * ny#)+(nz# * nz#))   
				nx# = nx# / factor#
				ny# = ny# / factor#
				nz# = nz# / factor#
				
				
				VertexNormal surf, vertexTree\vertices[i]\vertex, nx#, ny#, nz#
			Next


			If Not merged Then Exit
						
			For i = 0 To 50	
	
				If vertexTree\vertices[i] = Null Then Exit	
				
				vertex = vertexTree\vertices[i]
								
				vertex\x = VertexNX(surf, vertex\vertex)
				vertex\y = VertexNY(surf, vertex\vertex)
				vertex\z = VertexNZ(surf, vertex\vertex)
				
			Next							
			

		Forever


End Function


Function GetVertexVector.tVertexVector(pSurface, pVertex)
	;this one provides the coordinate of a given vertex as vector
	Return VertexVector(VertexX#(pSurface, pVertex), VertexY#(pSurface, pVertex), VertexZ#(pSurface, pVertex), pVertex)

End Function 


Function AddVertex2Tree( pNode.tVertexTree, pVertex.tVertexVector, pNormal.tVertexVector)
	;adds a vertex to our octree
	Local i, treePosition

	;if our coordinate matches, we just add the given vertex normal to the list
	If pNode\x = pVertex\x And pNode\y = pVertex\y And pNode\z = pVertex\z Then
	
		For i = 0 To 50
			If pNode\vertices[i] = Null Then 
			   pNode\vertices[i] = VertexVector(pNormal\x#, pNormal\y#, pNormal\z#, pVertex\vertex)
			   Return
			EndIf		
		Next
		
	Else

		If pNode\x >= pVertex\x Then treePosition = treePosition Or 1
		If pNode\y >= pVertex\y Then treePosition = treePosition Or 2
		If pNode\z >= pVertex\z Then treePosition = treePosition Or 4	

		If pNode\octree[treePosition] = Null Then
		
			pNode\octree[treePosition] 		= New tVertexTree
			pNode\octree[treePosition]\x# 	= pVertex\x#
	   		pNode\octree[treePosition]\y# 	= pVertex\y#
			pNode\octree[treePosition]\z# 	= pVertex\z#
			pNode\octree[treePosition]\vertices[0] 	= VertexVector(pNormal\x#, pNormal\y#, pNormal\z#, pVertex\vertex)
	
		Else
	
			AddVertex2Tree( pNode\octree[treePosition], pVertex, pNormal)	
	
		EndIf

	EndIf

End Function




Function GetTriangleNormal.tVertexVector(pSurface, v1, v2, v3)
	;return normal of given triangle as vector
	Local factor#

	;v1 to v2 as vector      
	Local lx# = VertexX#(pSurface,v1) - VertexX#(pSurface,v2)
	Local ly# = VertexY#(pSurface,v1) - VertexY#(pSurface,v2)
	Local lz# = VertexZ#(pSurface,v1) - VertexZ#(pSurface,v2)

	;v1 to v3 as vector  
	Local ax# = VertexX#(pSurface,v1) - VertexX#(pSurface,v3)
	Local ay# = VertexY#(pSurface,v1) - VertexY#(pSurface,v3)
	Local az# = VertexZ#(pSurface,v1) - VertexZ#(pSurface,v3)

	;cross product of these two vectors
	Local nx# = (ly# * az#)-(lz# * ay#)
	Local ny# = (lz# * ax#)-(lx# * az#)
	Local nz# = (lx# * ay#)-(ly# * ax#)
		
	;normalize result ( set vector length to 1 )
	factor# = Sqr((nx# * nx#)+(ny# * ny#)+(nz# * nz#))   
	nx# = nx# / factor#
	ny# = ny# / factor#
	nz# = nz# / factor#

	Return VertexVector(nx#, ny#, nz#) 

End Function


Function VertexVector.tVertexVector(x#, y#, z#, pVertex = -1)
	;creates a VertexVector type, storing coordinates and related mesh vertex

	Local Vector.tVertexVector
	
	Vector  	  = New tVertexVector
	Vector\x#	  = x#
	Vector\y#	  = y#
	Vector\z#	  = z#
	Vector\vertex = pVertex

	Return Vector

End Function


Function VectorAngle#(v1.tVertexVector,v2.tVertexVector)
	;returns angle between two normalized vectors
	;dot product is converted to integer and back to avoid some weird float issues
	;(as a matter of fact I don't know what the problem is exactly, maybe rounding differences, maybe NaN)
	Local dot = ((v1\X * v2\X) + ( v1\Y * v2\Y) + (v1\Z*v2\Z)) * 10000
	Return ACos#( dot / 10000.0 )	
			
End Function

Comments

None.

Code Archives Forum