Has anyone written a B3D loader for Blitzmax

Miscellaneous Forums/General Discussion/Has anyone written a B3D loader for Blitzmax

Just wondering as it's something that would be really useful for a lot of people wanting to play with 3D in Bmax.

So far I've been trying to help people on the art side with Bmax, and the B3D loaders have always been the stumbling block, particularly transposing the coordinates from D3D to GL so that a B3D file will load correctly.

The resources that are there are all ancient and don't work, including the samples that come with Bmax.

Any help would be handy.

there was one writting to work with the irrlicht wrapper somewhere in the bmax forums but I cant seem to find it.

Halo seems to have made the jump - perhaps he can help?

3DWS is OpenGL and he exports something which can be loaded as a B3d into Blitz.

IPete2.

Yes i wrote one, didnt finish animation though >_>. Sorry theres pretty much no comments, as this was really just me messing around designing a 3d engine, its pretty hacking'ly done to... But it should give you a basic idea of the structure of the file.

Type TB3DLoader Extends TModelLoader
	
	Field MeshFileURL:String,	MeshStream:TStream
	Field CompleteMesh:TMesh
	Field ChunkName:String, ChunkStart:Int, ChunkLength:Int
	Field LastSurface:TSurface, LastBrush:TBrush, LastTexture:TTexture, LastBrushID:Int
	Field BrushList:Tlist = CreateList(), TextureList:Tlist = CreateList()
	
	Method New()
	
		Header = "BB3D"
	
	End Method

	Method Load(Mesh:TMesh,Stream:TStream,URL:String = "")
	
		' Seutp variables
		CompleteMesh = Mesh
		MeshFileURL	 = URL
		MeshStream	 = Stream
	
		' Recursivly read chunks
		While Not Eof(MeshStream)
			ReadChunk()
		Wend
	
		' Remove references
		CompleteMesh = Null
		MeshStream	 = Null
		ClearList(BrushList)
		ClearList(TextureList)
				
	End Method

	Method ReadChunk()

		' Read header 
		ChunkName       = ReadString(MeshStream,4)
		ChunkLength	 	= ReadInt(MeshStream)
		ChunkStart  	= StreamPos(MeshStream)
	
		' Check what type of chunk it is
		Select ChunkName
		
			Case "BB3D"
				Local Version:Int = ReadInt(MeshStream)
				If Version / 100 > 1 / 100 Then Throw "Unsupported version ("+Version+") of b3d file"
		
			Case "NODE"
				Local Name:String = ReadNTString(MeshStream)
				
				CompleteMesh.Position[0] = ReadFloat(MeshStream)
				CompleteMesh.Position[1] = ReadFloat(MeshStream)
				CompleteMesh.Position[2] = ReadFloat(MeshStream)
				
				CompleteMesh.Scale[0]	 = ReadFloat(MeshStream)
				CompleteMesh.Scale[1]	 = ReadFloat(MeshStream)
				CompleteMesh.Scale[2]	 = ReadFloat(MeshStream)

				Local WRot:Float = ReadFloat(MeshStream)
				CompleteMesh.Rotation[0] = ReadFloat(MeshStream)
				CompleteMesh.Rotation[1] = ReadFloat(MeshStream)
				CompleteMesh.Rotation[1] = ReadFloat(MeshStream)
				
			Case "TEXS"
				While Not EOC()

					LastTexture = New TTexture
					
					Local FileURL:String 	 	= ReadNTString(MeshStream)
					Local Flags:Int			= ReadInt(MeshStream)
					LastTexture.Blend		 	= ReadInt(MeshStream)
					LastTexture.Position[0]	= ReadFloat(MeshStream)
					LastTexture.Position[1]	= ReadFloat(MeshStream)
					LastTexture.Scale[0]	 	= ReadFloat(MeshStream)
					LastTexture.Scale[1]	 	= ReadFloat(MeshStream)
					LastTexture.Rotation	 	= ReadFloat(MeshStream)
									
					If FileType(FileURL) = 1 
						LastTexture.Load(FileURL)
					ElseIf FileType(ExtractDir(MeshFileURL)+""+StripDir(FileURL)) = 1
						LastTexture.Load(ExtractDir(MeshFileURL)+""+StripDir(FileURL))
					EndIf	
					
					ListAddLast(TextureList,LastTexture)					

				Wend

			Case "BRUS"		
				Local TexCount:Int = ReadInt(MeshStream)
					
				While Not EOC()
		
					LastBrush = New TBrush
					
					LastBrush.Name 			 = ReadNTString(MeshStream)
					LastBrush.Color[0] 		 = ReadFloat(MeshStream)
					LastBrush.Color[1] 		 = ReadFloat(MeshStream)
					LastBrush.Color[2] 		 = ReadFloat(MeshStream)
					LastBrush.Color[3] 		 = ReadFloat(MeshStream)
					
					LastBrush.Shininess		 = ReadFloat(MeshStream)
					LastBrush.Blend			 = ReadInt(MeshStream)
					LastBrush.FX			 	 = ReadInt(MeshStream)
					
					For Local Tex:Int = 1 To TexCount
						Local ID:Int 		 = ReadInt(MeshStream) 
						If ID <> -1 Then LastBrush.Texture = TTexture(TextureList.ValueAtIndex(ID))
					Next
									
					ListAddLast(BrushList,LastBrush)		

				Wend
																		
			Case "MESH"
				LastBrushID = ReadInt(meshStream) 
				If LastBrushID <> -1 Then TBrush(BrushList.ValueAtIndex(LastBrushID)).ApplyToMesh(CompleteMesh)
				
			Case "TRIS"
				LastSurface = CompleteMesh.AddSurface(0)			
				LastBrushID = ReadInt(meshStream)
				If LastBrushID <> -1 Then TBrush(BrushList.ValueAtIndex(LastBrushID)).ApplyToSurface(LastSurface)
				
				Local V0:Int, V1:Int, V2:Int						
				Local VSize:Int = 0
				Local ParseStart:Int = StreamPos(MeshStream)
				
				While Not EOC()
					ReadInt(MeshStream) ; ReadInt(MeshStream) ; ReadInt(MeshStream)
					VSize:+1
				Wend
				
				SeekStream(MeshStream,ParseStart)
				LastSurface.ResizePolygonList(LastSurface.PolyList.Length + VSize + 1)
								
				Local TChanger:Int = True
				While Not EOC()
				
					V0 = ReadInt(MeshStream)
					V1 = ReadInt(MeshStream)
					V2 = ReadInt(MeshStream)
					LastSurface.AddTriangle(CompleteMesh.GetVertex(V0),CompleteMesh.GetVertex(V1),CompleteMesh.GetVertex(V2))
				
				Wend
				
			Case "VRTS"
				Local Flags:Int = ReadInt(MeshStream)
				Local TexCoordSet:Int = ReadInt(MeshStream)
				Local TexCoordCount:Int = ReadInt(MeshStream)
				Local VX:Float, VY:Float, VZ:Float, VNX:Float, VNY:Float, VNZ:Float
				Local VR:Float = 255, VG:Float = 255, VB:Float = 255, VA:Float = 1
				Local VSize:Int = 0
				Local ParseStart:Int = StreamPos(MeshStream)
				
				While Not EOC()
					ReadFloat(MeshStream) ; ReadFloat(MeshStream) ; ReadFloat(MeshStream)
					If Flags & 1 Then ReadFloat(MeshStream) ; ReadFloat(MeshStream) ; ReadFloat(MeshStream)					
					If Flags & 2 Then ReadFloat(MeshStream) ; ReadFloat(MeshStream) ; ReadFloat(MeshStream)				
					For Local Set:Int = 1 To TexCoordSet
						For Local Size:Int = 1 To TexCoordCount
							ReadFloat(MeshStream)
						Next
					Next
					VSize:+1
				Wend
				
				SeekStream(MeshStream,ParseStart)
				CompleteMesh.ResizeVertexList(CompleteMesh.VertexList.Length + VSize + 1)
				
				While Not EOC()
				
					Local Vertex:TVertex = CompleteMesh.AddVertex(0,0,0)
					
					Vertex.Position[0] = ReadFloat(MeshStream)
					Vertex.Position[1] = ReadFloat(MeshStream)
					Vertex.Position[2] = ReadFloat(MeshStream)
					
					If Flags & 1
						Vertex.Normal[0] = ReadFloat(MeshStream)
						Vertex.Normal[1] = ReadFloat(MeshStream)
						Vertex.Normal[2] = ReadFloat(MeshStream)					
					EndIf
					
					If Flags & 2
						Vertex.Color[0] = ReadFloat(MeshStream)
						Vertex.Color[1] = ReadFloat(MeshStream)
						Vertex.Color[2] = ReadFloat(MeshStream)	
						Vertex.Color[3] = ReadFloat(MeshStream)					
					EndIf

					For Local Set:Int = 1 To TexCoordSet
						For Local Size:Int = 1 To TexCoordCount
							Local Temp:Float = ReadFloat(MeshStream)
							If Set = 1
								If Size = 1
									Vertex.TextureCoords[0] = Temp
								ElseIf Size = 2
									Vertex.TextureCoords[1] = Temp
								EndIf
							EndIf
						Next
					Next

				Wend
							
			Default
				NextChunk()
		
		End Select

	End Method
	
	Method NextChunk(Offset:Int = 0)
	
		SeekStream(MeshStream,ChunkStart + ChunkLength + Offset)
	
	End Method
	
	Method EOC:Int()
	
		If StreamPos(MeshStream) >= ChunkStart + ChunkLength Then Return True
		Return False
	
	End Method

End Type


Hmm, now this is interesting- I see techniques i've yet to use in Max. Useful!


Thanks for sharing this.

Helios, how did you go about coverting the quat obtained from the node chunk into euler angles?
Also when rendering did you treat each rotation as the location rotation of the node, or the absolute rotation?

We've got it working, similar to yours but children entities are rotating at odd angles. like 90 degrees off. Although position appears okay...

From Helios code above:
Local WRot:Float = ReadFloat(MeshStream)
CompleteMesh.Rotation[0] = ReadFloat(MeshStream)
CompleteMesh.Rotation[1] = ReadFloat(MeshStream)
CompleteMesh.Rotation[1] = ReadFloat(MeshStream)

I'm not familiar with Bmax or the B3D format but that last line looks bugged to me. Shouldn't the index be 2, not 1?

Indeed you are correct Big10P. Was about to say there was a part of the quat missing, but then i noticed the first line including the Readfloat.

Forgive me- I dont have max here to play at the moment like I usually do when trying these things out :D

Yeh that is a bug, sorry, as i've said this was me just messing around doing my first 3d engine in opengl ever (never done any proper opengl before, except some small perspective things for max2D), so you cant expect much.


Helios, how did you go about coverting the quat obtained from the node chunk into euler angles?
Also when rendering did you treat each rotation as the location rotation of the node, or the absolute rotation?


Lol, i hate to admit my ignorance, but i have no idea, i just loaded it based on the model format i found on on the 'Specs and utils' link at the top (and from looking through other peoples examples), and it seems to work >_>.

*shrug* everyones goter start somewhere.

heh cool code, I think my problem is actually transposing the coordinates from B3D's DX7 which is left handed I believe, to OpenGL which is Right handed.

At the moment a bunch of objects rotated into a 3D axis shape have their rotations all messed up in bmax, and a single static one comes out flipped.

I'll capture some screenies in a bit to illustrate the problem were having.

Here's the rendering code as is, this shows how the matrix are multiplied onto the gl matrix stack.

Method RenderEntity(Entity:TPivot)
		
		Local Rot:TMatrix = LocalRot.CreateCopy()
		Rot.Invert() 'Invert Camera Matrix
		Rot.LoadGL() 'Load Camera Matrix Onto Stack.
		LocalTrans.MultiGL() 'Multiply Camera Position onto stack
		RenderEntities( Entity )
		
	End Method
	
	Method RenderEntities(Entity:TPivot)
		GlPushMatrix() 'Push matrix stack.
		Entity.LocalTrans.MultiGL() Multiply stack with entities local position
		
		Local sm:TMatrix = tmatrix.create()
		sm.scale( entity.xscale,entity.yscale,entity.zscale )
		sm.MultiGL() 'Scale Matrix stack by scale matrix.
		Entity.LocalRot.MultiGL() 'Multiply gl stack by entity rotatin
			
		Local Ent:TEntity = TEntity( Entity )
		If ent<>Null
			ent.render()
		End If
		For Local sub:TPivot = EachIn Entity.Subs
			RenderEntities( sub )
		Next
		glPopMatrix()
		
		
	End Method
	



And here's the code that converts the quat described in the b3d file to a matrix.
This was converted from mark's own ms3d b3d saver.
Method ToMatrix:TMatrix()
		Local q:Float[4]
		q[0] = w
		q[1] = x
		q[2] = y
		q[3] = z
		Local xx#=q[1]*q[1]
		Local yy#=q[2]*q[2]
		Local zz#=q[3]*q[3];
		Local xy#=q[1]*q[2]
		Local xz#=q[1]*q[3]
		Local yz#=q[2]*q[3];
		Local wx#=q[0]*q[1]
		Local wy#=q[0]*q[2]
		Local wz#=q[0]*q[3];
		Local mat:Float[16]
		
		Local tmat:TMatrix = tmatrix.create()
				
		mat[0]=1-2*(yy+zz)
		mat[1]=  2*(xy-wz)
		mat[2]=  2*(xz+wy);
		mat[3]=  2*(xy+wz)
		mat[4]=1-2*(xx+zz)
		mat[5]=  2*(yz-wx);
		mat[6]=  2*(xz-wy)
		mat[7]=  2*(yz+wx)
		mat[8]=1-2*(xx+yy);
		
		Local ind=0
		For Local j=0 To 2
		For Local k=0 To 2
			tmat.setelement( k,j,mat[ind] )
			ind:+1
		Next
		Next
	
		Return tmat
	
	End Method



I started a new topic about coordinate systems, since that is where the real problem lay.

http://www.blitzbasic.com/Community/posts.php?topic=56261

Looks like the issue isn't the loader, but rather coordinate systems. B3D being Left handed D3D and Bmax Bine Right Handed GL

So far we have gotten the geometry to load ok but back to front kind of as expected given the circumstances.

Here's the original as it looks in Blitz3d (the axis model was made Z up so you will have to ignore that):




Here's the result in Bmax when all the parts are combined into a single object.



In this case its an obvious horizontal flip to make the single object work.

The real problem is rotations, the pic below shows what happens when the axis is made up of several meshes rotated to make the original object. The rotations are completely out of whack.




(I got some hints from jeremy who overcame the problem in torque, not sure I can paste it here but will if he will let me)

If anyone has been here before, the solution would be really handy. If we can figure it out we will post our B3D loader for free for anyone that wants it.

PS. I saw that halo used to have a dll that did the conversion, but the link he posted is old and no longer works.