.obj loading code

BlitzMax Modules Forums/MiniB3D/.obj loading code

The forum is locked. These are the original blitzbasic.com forums, kept so they can still be read. No new topics, replies or edits can be made here. To join in, head over to the Blitz3D+ forums.

This adds .mtl loading and support for relative vertex numbers and other weird stuff. It has been able to handle everything I have thrown at it:

CreateMeshLoader(LoadMeshObj,"obj")

Function LoadMeshObj:TEntity(path$)
	
	Local stream:TStream
	Local position:TBank=New TBank
	Local normal:TBank=New TBank
	Local texcoords:TBank=New TBank
	Local line:String[]
	Local indices:String[]
	Local sumindices:Int
	Local x#,y#,z#,s$,a,b,c,na,nb,nc
	Local nx#,ny#,nz#,tu#,tv#,v,m#
	Local currentsmoothgroup
	Local face:TObjFace,vert:TObjVert,face2:TObjFace
	Local n,avg#,n2
	Local currentgroup
	Local currentmaterialfile$
	Local materiallist:TObjMTL[]
	Local groupmaterial:TObjMTL[1024]
	
	stream=ReadFile(path)
	If Not stream Return

	Local mesh:TMesh=CreateMesh()
	
	While Not stream.Eof()
		
		s$=Trim(stream.ReadLine())
		If s="" Continue
		If Left(s,1)="#" Continue
		
		line=s.split("")
		If line.length=0 Continue
		
		Select Lower(line[0])
			Case "mtllib"
				If line.length>1
					materiallist=ParseMTLLib(ExtractDir(path)+"/"+line[1])
				EndIf
			
			Case "usemtl"
				currentgroup:+1
				If line.length>1
					For Local objmtl:TObjMTL=EachIn materiallist
						If objmtl.name.tolower()=line[1].tolower()
							groupmaterial[currentgroup]=objmtl
							Exit
						EndIf
					Next
				EndIf
				
				
			Case "v"
				If line.length<3 Return
				AppendFloat position,Float(line[1])
				AppendFloat position,Float(line[2])
				AppendFloat position,Float(line[3])
				
			Case "vt"
				If line.length<2 Return
				AppendFloat texcoords,Float(line[1])
				AppendFloat texcoords,-Float(line[2])
			
			Case "vn"
				If line.length<3 Return
				nx=Float(line[1])
				ny=Float(line[2])
				nz=Float(line[3])
				m=Sqr(nx*nx+ny*ny+nz*nz)
				nx:/m
				ny:/m
				nz:/m
				AppendFloat normal,nx
				AppendFloat normal,ny
				AppendFloat normal,nz
			
			Case "s"
				If line.length>1
					currentsmoothgroup=Int(line[1])
				EndIf
				
			Case "g"
				currentgroup:+1
							
			Case "f"
				Local indice[line.length-1]
				
				Local verts:TObjVert[indice.length]
				face=New TObjFace
				face.verts=verts
				face.group=currentgroup
				face.smoothgroup=currentsmoothgroup
				
				For n=0 To indice.length-1
					indices=line[n+1].split("/")
					tu=0
					tv=0
					nx=0
					ny=0
					nz=0
					
					vert=New TObjVert
					
					v=Int(indices[0])-1
					If v<0 v=position.size()/12+v+1
					
					x#=position.PeekFloat(v*12+0)
					y#=position.PeekFloat(v*12+4)
					z#=position.PeekFloat(v*12+8)
					If indices.length>1
						v=Int(indices[1])-1
						If v<0 v=texcoords.size()/8+v+1
						tu#=texcoords.PeekFloat(v*8+0)
						tv#=texcoords.PeekFloat(v*8+4)
						If indices.length>2
							v=Int(indices[2])-1
							If v<0 v=normal.size()/12+v+1
							nx#=normal.PeekFloat(v*12+0)
							ny#=normal.PeekFloat(v*12+4)
							nz#=normal.PeekFloat(v*12+8)
							vert.normalsdefined=True
						EndIf
					EndIf
					
					vert.x=x
					vert.y=y
					vert.z=z
					vert.nx=nx
					vert.ny=ny
					vert.nz=nz
					vert.u=tu
					vert.v=tv
					face.verts[n]=vert
				Next
				
				'For Local n=2 To indice.length-1
				'	surf.addtriangle indice[n],indice[0],indice[n-1]
				'	sumindices:+3
				'Next
				
		EndSelect
	Wend
	
	'Calculate face normals
	Local norm:TVec3
	For face=EachIn TObjFace.list
		If face.verts.length<3 Continue
		'TriangleNormal(face.verts[0].x,face.verts[0].y,face.verts[0].z,face.verts[1].x,face.verts[1].y,face.verts[1].z,face.verts[2].x,face.verts[2].y,face.verts[2].z)
		
		norm=TPlane.FromTriangle(vec3(face.verts[0].x,face.verts[0].y,face.verts[0].z),vec3(face.verts[1].x,face.verts[1].y,face.verts[1].z),vec3(face.verts[2].x,face.verts[2].y,face.verts[2].z)).normal()
		
		face.nx=-norm.x
		face.ny=-norm.y
		face.nz=-norm.z
		For n=0 To face.verts.length-1
			If Not face.verts[n].normalsdefined ' use hard edges for verts with undefined normal
				face.verts[n].nx=face.nx
				face.verts[n].ny=face.ny
				face.verts[n].nz=face.nz
			EndIf
		Next
	Next
	
	For face=EachIn TObjFace.list
		If face.smoothgroup
			For n=0 To face.verts.length-1
				nx#=face.nx
				ny#=face.ny
				nz#=face.nz
				avg#=1.0
				For face2=EachIn TObjFace.list' this part is highly inefficient, but don't give a fuck
					If face=face2 Continue
					If face.group<>face2.group Continue
					If face.smoothgroup<>face2.smoothgroup Continue
					For n2=0 To face2.verts.length-1
						If Abs(face.verts[n].x-face2.verts[n2].x)>0.01 Continue
						If Abs(face.verts[n].y-face2.verts[n2].y)>0.01 Continue
						If Abs(face.verts[n].z-face2.verts[n2].z)>0.01 Continue
						nx:+face2.nx
						ny:+face2.ny
						nz:+face2.nz
						avg:+1
						Exit
						'n2=face2.verts.length-1
					Next
				Next
				face.verts[n].nx=nx/avg
				face.verts[n].ny=ny/avg
				face.verts[n].nz=nz/avg
			Next
		EndIf
	Next
	
	Local badvertices=0
	
	'Add triangles
	Local submesh:TSubmesh
	For Local g=1 To currentgroup
		submesh=Null
		For face=EachIn TObjFace.list
			If face.group<>g Continue
			'Local s$=""
			If Not submesh
				submesh=CreateSubmesh(mesh)
				If groupmaterial[ g ]
					submesh.paint groupmaterial[ g ].material
					'Notify groupmaterial[ g ].material.name
				EndIf
			EndIf
			For n=0 To face.verts.length-1
				face.verts[n].index=submesh.surface.FindVertex(face.verts[n].x,face.verts[n].y,face.verts[n].z,face.verts[n].nx,face.verts[n].ny,face.verts[n].nz,face.verts[n].u,face.verts[n].v)
				's:+n+", "
				If n>1
					a=face.verts[n].index
					b=face.verts[n-1].index
					c=face.verts[0].index
					
					If (VertsMatchU(submesh.surface,a,b) And VertsMatchU(submesh.surface,a,c) And VertsMatchU(submesh.surface,b,c))=True Or (VertsMatchV(submesh.surface,a,b) And VertsMatchV(submesh.surface,a,c) And VertsMatchV(submesh.surface,b,c))=True
					'	Print "WARNING: Possible mesh error (identical texcoords in triangle)"
						badvertices:+1
					EndIf
					
					If a=b Or b=c Or a=c
						mesh.destroy()
						Return
					'	Notify "MESH ERROR"
					EndIf
					
					submesh.surface.addtriangle a,c,b
					sumindices:+3
				EndIf
			Next
		Next
		If submesh
			'If submesh.surface.counttriangles()>1024 submesh.surface.drawinstanced=0
		EndIf
	Next
	
	mesh.update()
	mesh.lock()
	
	TObjFace.list=New TList
	
	'Print badvertices+" bad triangles."
	
	Return mesh
	
	Function VertsMatchU%(surf:TSurface,a,b,e#=0.000001)
		If Abs(surf.vertexu(a)-surf.vertexu(b))>e Return False
		Return True
	EndFunction
	
	Function VertsMatchV%(surf:TSurface,a,b,e#=0.000001)
		If Abs(surf.vertexv(a)-surf.vertexv(b))>e Return False
		Return True
	EndFunction

	Function AppendFloat(bank:TBank,value#)
		Local size
		size=bank.size()
		bank.resize size+4
		bank.PokeFloat size,value
	EndFunction

EndFunction

Private

Type TObjVert
	Field x#,y#,z#
	Field nx#,ny#,nz#
	Field u#,v#
	Field index
	Field normalsdefined
EndType

Type TObjFace
	Global list:TList=New TList
	
	Method New()
		list.addfirst(Self)
	EndMethod
	
	Field nx#,ny#,nz#
	Field verts:TObjVert[]
	Field smoothgroup
	Field group
EndType

Type TObjMTL
	Field name:String
	'Field Brush:TBrush
	Field Texture:TTexture
	Field material:TMaterial
EndType

Function ParseMTLLib:TObjMTL[](Path:String)
	Local matStream:TStream = ReadStream(Path) 
	Local dir:String = ExtractDir(Path) + "/"
	If dir = "/" Then dir = ""
	If Not matStream Then Return Null
	Local MatLib:TObjMtl[0]
	Local CMI:Int = -1
	While Not Eof(matStream)
		Local Line:String = ReadLine(MatStream) 
		If Line[0..7] = "newmtl " Then
			MatLib = MatLib[..Matlib.Length + 1]
			CMI = MatLib.Length-1
			MatLib[CMI] = New TObjMtl
			MatLib[CMI].Name = Line[7..].Trim() 
			'MatLib[CMI].Brush = CreateBrush() 
			'BrushFX MatLib[CMI].Brush,4+16
			'DebugLog("Matname : " + Matlib[CMI].Name)
		EndIf
		'Colours
		If Line[0..3] = "Kd " Then
			Local Data:String = Line[3..].Trim()+" "
			Local F:Float[3]
			For Local I:Int = 0 To 2
				'Print "Before : " + Data
				Local FL:Int = Data.Find(" ")
				If I < 2 Then
					f[I] = Float(Data[..FL])
				Else
					f[i] = Float(Data) 
				EndIf
				Data = Data[FL+1..]
				'Print "After : " + Data
			Next
			'BrushColor(MatLib[CMI].Brush , F[0] * 255 , F[1] * 255 , F[2] * 255) 
			'DebugLog("MatColor : " +  (F[0] * 255) +","+(F[1] * 255)+","+(F[2] * 255))
		EndIf
		
		If Line[0..2] = "d " Then
			'BrushAlpha(MatLib[CMI].Brush , Float(Line[2..]))
			'DebugLog("MatAlpha : " + Float(Line[2..]) ) 
		EndIf
		
		If Line[0..3] = "Tr " Then
			'BrushAlpha(MatLib[CMI].Brush , Float(Line[2..])) 
			'DebugLog("MatAlpha : " + Float(Line[2..]) ) 
		EndIf 
		
		If Line[0..7] = "map_Kd " Then
			'MatLib[CMI].Texture = LoadTexture(dir+Line[7..].Trim())
			'Notify dir+Line[7..].Trim()
			'MatLib[CMI].Material = LoadMaterial(dir+Line[7..].Trim())
			'If Not MatLib[CMI].Material MatLib[CMI].Material = LoadMaterial(Line[7..].Trim())
			'If Not MatLib[CMI].Material 
			MatLib[CMI].Material = LoadMaterial( StripAll( Line[7..].Trim()) )
			'If MatLib[CMI].Texture <> Null BrushTexture(MatLib[CMI].Brush , MatLib[CMI].Texture) 
			'DebugLog("MatTexture : " + Line[7..].Trim() ) 
		EndIf
	Wend
	
	Return MatLib
End Function

Public