Anyone Have a B3d Loader?

Blitz3D Forums/Blitz3D Programming/Anyone Have a B3d Loader?

I don't need it to support animation, just to load a static mesh, with brushes, textures, multitexturing, hierarchy, etc.

The only one I can find in the code archives, by Halo, is not declared PD, and doesn't load the first example b3d I threw at it anyway.

I'm currently reading through the docs and trying to write my own, but I don't think it's gonna be long before I start crying uncontrollably ( around the time I get to quaternions, I suspect )

maybe a stupid question, but why do you need one? If you load a mesh usig LoadMesh, I think you have access to all aspects of the model, no?

It's a fair question. I'm using a whole bunch of textures which I do not have the right to distribute except in an encrypted form. Since Blitz is not going to get load-from-bank I either have to parse the B3d files and textures myself, or I have to go elsewhere for the next game.

I figured parsing the b3d and textures would be slightly easier than learning a whole new engine or whatever. ( I'm having my doubts now.. lol )

Use Molebox!

Use Molebox!


And risk incompatibility on a consumer's PC!

And risk incompatibility on a consumer's PC!

We risk it with everything we write. If it's a requirement that the textures must be encrypted, then I don't see much of an alternative.

If it's a requirement that the textures must be encrypted, then I don't see much of an alternative.
Except write your own loader that decrypts then loads them. Afterall, you can create images and you can write pixels to them. you can create models and give them vertices, tris, and texturing. I'm not sure about anims though :/. The main problem is sound - there aren't many ways other than using an external dll.

Except write your own loader that decrypts then loads them. Afterall, you can create images and you can write pixels to them. you can create models and give them vertices, tris, and texturing.


I'm currently reading through the docs and trying to write my own, but I don't think it's gonna be long before I start crying uncontrollably ( around the time I get to quaternions, I suspect )


It doesn't sound like that's really an option!

Besides, if I recall correctly, Mike Boeh did some testing on his huge stack of 3D cards, and there are some that don't like writing to textures - so for all that extra work, you've still added incompatability.

mm. ouch. I wonder how blitz can load textures if it can't write to them... ah well. I might take a shot at it.

[edit] decided I might as well, and mine'll be PD and optimized

If the quat stuff is freaking you out just use this. Create a quat, put data in from the file, convert to a rotation, and orient the mesh. No need to know how it works :D

http://www.dscho.co.uk/blitz/tutorials/quaternions.shtml
; Types
Type Rotation
	Field pitch#, yaw#, roll#
End Type

Type Quat
	Field w#, x#, y#, z#
End Type

; Change these constants if you notice slips in accuracy
Const QuatToEulerAccuracy# = 0.001

; convert a Quat to a Rotation
Function QuatToEuler(out.Rotation, src.Quat)
	Local sint#, cost#, sinv#, cosv#, sinf#, cosf#
	Local cost_temp#

	sint = (2 * src\w * src\y) - (2 * src\x * src\z)
	cost_temp = 1.0 - (sint * sint)

	If Abs(cost_temp) > QuatToEulerAccuracy
		cost = Sqr(cost_temp)
	Else
		cost = 0
	EndIf

	If Abs(cost) > QuatToEulerAccuracy
		sinv = ((2 * src\y * src\z) + (2 * src\w * src\x)) / cost
		cosv = (1 - (2 * src\x * src\x) - (2 * src\y * src\y)) / cost
		sinf = ((2 * src\x * src\y) + (2 * src\w * src\z)) / cost
		cosf = (1 - (2 * src\y * src\y) - (2 * src\z * src\z)) / cost
	Else
		sinv = (2 * src\w * src\x) - (2 * src\y * src\z)
		cosv = 1 - (2 * src\x * src\x) - (2 * src\z * src\z)
		sinf = 0
		cosf = 1
	EndIf

	; Generate the output rotation
	out\roll = -ATan2(sinv, cosv) ;  inverted due to change in handedness of coordinate system
	out\pitch = ATan2(sint, cost)
	out\yaw = ATan2(sinf, cosf)
End Function


Also, if you're still trying to write yours here's my crazy optimized way of identifying a ChunkID if your interested:

chunkID=PeekInt(bank,i)
Select chunkID
 Case 1398293844 ;TEXS
 Case 1398100546 ;BRUS
 Case 1162104654 ;NODE
 Case 1213416781 ;MESH
 Case 1398035030 ;VRTS
 Case 1397314132 ;TRIS
 Case 1296649793 ;ANIM
 Case 1162760002 ;BONE
 Case 1398359371 ;KEYS
End Select


And here's my read c string function:
Function ReadString$(Bank)
	Repeat
		ascii=PeekByte(Bank,i)
		If ascii=0 Then Exit Else out$=out$+Chr$(i)
		i=i+1
	Forever
	i=i+1
	Return out$
End Function
Assumes a global i which represents the position to be read (it also intcrements this up to the byte after the end of the string -ready for more reading.

Why not use TeraBit's DataPak?
It does the same as molebox (you can stick your data on the end of an exe), is free and works on all pc's. You can encrypt your data too.

This was my first attempt at one for vivid, based off's mark's dump-file.

It works on static and anims, you just have to convert the parts that feed that data into banks into the equivilent b3d func(I.e instead of poking verts into bank cverts, acctually add verts to a b3d mesh.

Sorry, it's a bit messy, but bear in mind I was butchering up someone elses code as a prototype ;) (Hence it being pd too)



Function glLoadB3dfile(file$,uvSets=2,parent=0);internal function
	fS=ReadFile(file)
	texc=0
	name$=file
	lent.ent=Null
	l=Len(name)
	found=False
	For j=l To 1 Step -1
		Select Mid(name,j,1)
			Case "/",""
				name=Mid(name,1,j)
				found=True
		End Select
		If found Exit
	Next
	glStackEC=-1
	glStackSC=-1
;1142;-
	If Not fs glerror("b3d "+file+" not found")
	b3dSetFile( fs )
	If b3dreadchunk$()<>"BB3D"
		CloseFile fs
	 	Return
	EndIf
	If b3dreadInt()/100>0 
		CloseFile fs
		Return
	EndIf
	top=glReadb3dChunks("",name)
	Delete Each b3dnode

If lent=Null Stop
	Return Handle(lent)
End Function

Function glerror(err

Function glReadb3dChunks(tab$="",localDir$="",uvSets=2,isAnim=False) ;INTERNAL FUNCTION
	tt$=tab$
	tab$=tab$+"  "
	hLevel=hLevel(tab)
	While b3dChunkSize()
		chunk$=b3dReadChunk$()
		;tt$=Replace(tt$," ","/")
		DebugLog "Level:"+hLevel+" Chunk:"+chunk$+" (size="+b3dChunkSize()+")"
		Select chunk$
		Case "ANIM"
			flags=b3dReadInt()
			DebugLog "Animation Found:"
			n_frames=b3dReadInt()
		;	DebugLog "Frames:"+n_frames
			fps=b3dReadFloat()
		;	DebugLog "Fps:"+fps
			lent\animFrames=n_frames
			lent\animFPS=fps
			;lent\root=New limb
			lent\isAnim=True
			;DebugLog tab$+flags+" "+n_frames+" "+fps
		Case "KEYS"
			flags=b3dReadInt()
			sz=4
			sk=0
			If flags And 1 sz=sz+12
			If flags And 2 sz=sz+12
			If flags And 4 sz=sz+16
			n_keys=b3dChunkSize()/sz
			If n_keys*sz=b3dChunkSize() 
			
				DebugLog tab$+n_keys+" keys"
				;Read all keys in chunk
				While b3dChunkSize()
					frame=b3dReadInt()
					isPos=False
					isScale=False
					isRot=False
					x#=0
					y#=0
					z#=0
					msx#=0
					msy#=0
					msz#=0
					rw#=0
					rx#=0
					ry#=0
					rz#=0
					If flags And 1
						x#=b3dReadFloat()
						y#=b3dReadFloat()
						z#=b3dReadFloat()
						isPos=True
					EndIf
					If flags And 2
						msx#=b3dReadFloat()
						msy#=b3dReadFloat()
						msz#=b3dReadFloat()
						isScale=True
					EndIf
					If flags And 4
						rw#=b3dReadFloat()
						rx#=b3dReadFloat()
						ry#=b3dReadFloat()
						rz#=b3dReadFloat()
						isRot=True
					EndIf
					
.boneStuff	
	qtt.quat=quat(rw,rx,ry,rz)
	If ry>0 And sk=0
		DebugLog "Limb>"+nlimb\name+" Has active frame"
		sk=True
	EndIf
	
;	quatToEuler(tRot[10],qtt)
;	trot[10]\pitch=trot[10]\pitch+180
;	eulerToQuat(qtt,trot[10]) ;this 180 conversation is needed. Saves having to do it every frame.
					kf.frame=newFrame(frame,qtt,x,y,z,msx,msy,msz,ispos,isscale,isrot)
					;If qtt\y>0 DebugLog nlimb\name+" Active quat"
					nlimb\frame[nlimb\fc]=kf
					nlimb\fc=nlimb\fc+1
				Wend
				If sk=False nlimb\fc=0
				
			Else
				RuntimeError "Illegal number of animation keys"
			EndIf
		Case "TEXS"
			While b3dChunkSize()
				name$=b3dReadString$()
				;first check direct link
				texIn=glLoadTexture(name$,glMipMap+glCompress)
				If Not texIn
					l=Len(name)
					found=False
					For j=l To 1 Step -1
						Select Mid(name,j,1)
						Case "/",""
							name=Mid(name,j+1)
							found=True
						End Select
						If found Exit
					Next
					texIn=glLoadTexture(localdir+name$,glMipMap+glCompress)
				EndIf
				
				If texIn=0 
					texIn=glLoadTexture("vivid\failsafe.jpg",glMipMap+glCompressed)	
					;	glerror("Texture "+localdir+name+" not found")
				EndIf
				
				rTex.gText=Object.gtext(texIn)
				If rTex=Null RuntimeError "Critical texture error. Please report"
				rtex\file=name
				texA[texC]=rTex
				texC=texC+1
				flags=b3dReadInt()
				If flags And 65536
					glTextureUvSet(texIn,1)
				EndIf
				
				blend=b3dReadInt()
				x_pos#=b3dReadFloat()
				y_pos#=b3dReadFloat()
				x_scl#=b3dReadFloat()
				y_scl#=b3dReadFloat()
				rot#=b3dReadFloat()
				DebugLog tab$+name$
			Wend
		Case "BRUS"
			n_texs=b3dReadInt()
			If n_texs=0 Exit
			;read all brushes in chunk...
		;	brush=glBrush()
			bc=0
			While b3dChunkSize()
				
				brush=glBrush()
				brushA[brushC]=brush
				brushC=brushC+1	
				name$=b3dReadString$()
				red#=b3dReadFloat()
				grn#=b3dReadFloat()
				blu#=b3dReadFloat()
				alp#=b3dReadFloat()
				glBrushColor(brush,red,grn,blu,alp)
				shi#=b3dReadFloat()
				blend=b3dReadInt()
				fx=b3dReadInt()
			
				For k=0 To n_texs-1
					tex_id=b3dReadInt()
					;DebugLog "=====Brush>"+texID>"+tex_id
					If tex_id>-1
						glBrushTexture(brush,Handle(texA[tex_id]))
					EndIf
				
				Next
				
				;DebugLog tab$+"Brush>"+name$+" texs:"+n_texs
			Wend
		Case "VRTS"
			flags=b3dReadInt()
			tc_sets=b3dReadInt()
			tc_size=b3dReadInt()
			sz=12+tc_sets*tc_size*4
			If flags And 1 Then sz=sz+12
			If flags And 2 Then sz=sz+16
			n_verts=b3dChunkSize()/sz
			lvc=n_verts
			If n_verts*sz=b3dChunkSize() 
				;DebugLog tab$+n_verts+" vertices"
				cVerts=CreateBank((n_verts+16)*12)
				If isAnim
					anVerts=CreateBank((n_verts+16)*12)
				Else
				
				EndIf
				;read all verts in chunk
				vo=0
				cNorm=CreateBank((n_verts+16)*12)
				cCol=CreateBank((n_verts+16)*16)
				co=0
				uvSize=tc_size
				For cc=0 To tc_sets
					lUV[cc]=CreateBank(((12)*tc_sets)*(n_verts+16))
				Next
				uof=0
				While b3dChunkSize()
					;DebugLog "Vert >"+vert
					vert=vert+1
					PokeFloat cVerts,vo,b3dReadFloat()
					PokeFloat cVerts,vo+4,b3dReadFloat()
					PokeFloat cVerts,vo+8,b3dReadFloat()
							
					
					If flags And 1
						PokeFloat cNorm,vo,b3dReadFloat()
						PokeFloat cNorm,vo+8,b3dReadFloat()
						PokeFloat cNorm,vo+12,b3dReadFloat()
					EndIf
					If flags And 2
						PokeFloat cCol,co,b3dReadFloat()
						PokeFloat cCol,co+4,b3dReadFloat()
						PokeFloat cCol,co+8,b3dReadFloat()
						PokeFloat cCol,co+12,b3dReadFloat()
						co=co+16
					EndIf
					tc_size2=12
					;read tex coords...
					For j=1 To tc_sets;*tc_size
						If tc_size2>0
							inU#= b3dReadFloat()
							PokeFloat luv[j-1],uof,inu
						EndIf
						If tc_size2>1
							inV#= b3dreadfloat()
							PokeFloat luv[j-1],uof+4,inv
						EndIf
						If tc_Size2>2
						;	inQ#=b3dreadfloat()
						;	PokeFloat luv[j-1],uof+8,inq
						EndIf
						
						If tc_size2>3
							;inW#=b3dreadfloat()
						;	PokeFloat luv[j-1],uof+12,inW
						EndIf
											
					Next
					uof=uof+tc_size2
					vo=vo+12
				Wend
				tmpVerts=cVerts
				tmpCol=cCol
				tmpNorm=cNorm
			
			Else
				DebugLog tab$+"***** Illegal number of vertices *****"
			EndIf
			
		Case "TRIS" ;surface
		;	If isAnim Exit;Skip as we're not using tri banks to establish surfaces.
			brush_id=b3dReadInt()
			sz=12
			n_tris=b3dChunkSize()/sz
			If n_tris<4 Exit ;skip surface
			sdat.sdat=New sdat
			sdat\cverts=tmpverts
			sdat\ccol=tmpcol
			sdat\cnorm=tmpnorm
			sdat\vc=lvc
			sdat\uvsets=2
			For j=0 To 1
				sdat\cuv[j]=luv[j]
			Next
			
			sdat\ctris=CreateBank((n_tris+1000)*12)
			lent\sdat[lent\sc]=sdat
			lent\sc=lent\sc+1
			sdat\tc=n_tris*2
			; "NEW SURFACE!!!!!!"
			If n_tris<1 
			Else
			DebugLog n_tris+"<        tris"
			EndIf
			
			glLockSurface(Handle(sdat)) ;not as fast as direct, but is future proof.
			If n_tris*sz=b3dChunkSize()
				;DebugLog tab$+n_tris+" triangles"
				;read all tris in chunk
				While b3dChunkSize()
					v0=b3dReadInt()
					v1=b3dReadInt()
					v2=b3dReadInt()
					glTriangle(v0,v1,v2)
				Wend
			Else
				DebugLog tab$+"***** Illegal number of triangles *****"
			EndIf
			If brush_id>0 And brush_id<brushC
				glPaintBrushSurface(brushA[brush_id])
				;DebugLog "BRUSH TO SURFACE!"
			EndIf
		Case "MESH"
		
			brush_id=b3dReadInt()
			;DebugLog tab$+"brush="+brush_id
		;	If b3dNC=0 RuntimeError "Fatal .b3d loader error. Please report."
			;b3dNS[b3dNC-1]\brushId=brush_id
			If lent=Null
			ent.ent=New ent
			lent=ent
			rent=Handle(lent)
			EndIf
			If ent=Null Stop
			;1142;Default values
			ent\r=1
			ent\g=1
			DebugLog "NEW mesh1'
			ent\b=1
			If isAnim
				ent\isAnim=True
			;	ent\root =New limb
			If nlimb=Null
				RuntimeError "No prior root node for animated .b3d" 
			Else
				ent\root=nlimb
				DebugLog "Root node name is>"+ent\root\name
				plimb(hLevel)=nlimb
				DebugLog "Set it to hlevel>"+hLevel
			EndIf
			
				ent\name="Root" ;Internal engine root. Not accesible.
			Else
			;Stop
			EndIf
			
			ent\vpiv=CreatePivot()
			ent\fx=glFx()
			ent\a=1
			ent\hLevel=hLevel
			ent\eType=glemesh
			eStack[eStackC]=ent
			eStackC=eStackC+1
		Case "BONE"
			sz=8
			n_weights=b3dChunkSize()/sz
			If n_weights*sz=b3dChunkSize()
				
				;DebugLog tab$+n_weights+" weights"
			;	If n_weights>0  DebugLog "YEAH!"				
				;read all weights
				verts=CreateBank(n_weights*4)
				weights=CreateBank(n_weights*4)
				vo=0
				nlimb\lverts=verts
				nlimb\vc=n_weights
				nlimb\lweights=verts
				ldc=1
				If sco=1 ldc=1
				sco=1
				
				DebugLog "Checking hlevel>"+Str(hLevel-ldc)
				
				
				While b3dChunkSize()
					vertex_id=b3dReadInt()
					weight#=b3dReadFloat()
					PokeInt verts,vo,vertex_id
					PokeFloat weights,vo,weight
					vo=vo+4
					;DebugLog "OH YEAH!!"
				Wend
			Else
				DebugLog tab$+"***** Illegal number of bone weights *****"
			EndIf
		Case "NODE" ;New Entity
			;Increase entity stack
			;glStackEC=glStackEC+1
			
			
			;b3dNS[b3dNC]=New b3dNode
			
			
			name$=b3dReadString$()
			;DebugLog "-=-=-=-==-Node >"+b3dns[b3dNC]\name
			
			x#=b3dReadFloat()
			y#=b3dReadFloat()
			z#=b3dReadFloat()
		;	DebugLog "X>"+x+" y>"+y+" z>"+z
			nLimb.limb =glLimb(name$) ;create limb.
			nlimb\x=x
			nlimb\y=y
			nlimb\z=z
			
			If isAnim
			If lent<>Null
				If cVerts=0	cVerts=lent\sdat[0]\cVerts
			EndIf
			EndIf
			
			nlimb\cverts=cverts
	
			DebugLog nlimb\name+"<Name of limb"
			
.node
			nlimb\xs#=b3dReadFloat()
			nlimb\ys#=b3dReadFloat()
			nlimb\zs#=b3dReadFloat()
			nlimb\hLevel=hLevel
			nlimb\anVerts=anVerts
			setQuat(nlimb\quat,b3dreadfloat(),b3dreadfloat(),b3dreadfloat(),b3dreadfloat())
			quatToEuler(nlimb\rot,nlimb\quat) 
			
			fillRotation(nlimb\rot,nlimb\rot\pitch+180,nlimb\rot\yaw,nlimb\rot\roll)
			eulertoQuat(nlimb\quat,nlimb\rot)
			;nlimb\rw#=b3dReadFloat()
			;DebugLog "-0-0-0-0-0-0-0"
			;plimb(hLevel)=nlimb
		;	If hLevel>0
				;If plimb(hlevel-1)=Null ;no previous limb. assume root limb.
				;	If lent<>Null 
				;	Stop
				;		If lent\root=Null
				;			lent\root=nlimb
				;			pLimb(hlevel)=nlimb
				;			End
				;			DebugLog "Made >"+lent\root\name+" The root!"
				;		Else
						;Stop
				;		EndIf
				;	EndIf
				;Else
			
		;	If lent\root=Null
		;					lent\root=nlimb
		;					pLimb(hlevel)=nlimb
		;		;			End
		;					DebugLog "Made >"+lent\root\name+" The root!"
		;				Else
							
		;			plimb(hlevel)=nlimb
		;			plimb(hlevel-1)\child[plimb(hlevel-1)\lc]=nlimb
		;			plimb(hlevel-1)\lc=plimb(hlevel-1)\lc+1
		;				EndIf
		
		;		EndIf
		;		DebugLog "!!!!!!!!!!!!!!!!!!!!!!!!!!!"
		;	EndIf
			ldc=1
			If isAnim
			If plimb(hLevel-ldc)=Null
					;Stop
				Else
					plimb(hLevel-ldc)\child[plimb(hLevel-ldc)\lc]=nlimb
					plimb(hLevel-ldc)\lc=plimb(hLevel-ldc)\lc+1
					If nlimb=Null Stop
					plimb(hLevel)=nlimb
					DebugLog "Connected "+plimb(hLevel-ldc)\name+" to "+nlimb\name
				EndIf
			EndIf
			DebugLog tab$+"name="+name$
		End Select
		vt=glReadb3dChunks( tab$,localDir,uvsets,isAnim)	;dump any subchunks
		If vt<>0 rent=vt
		b3dExitChunk()		;exit this chunk
		
	Wend
Return rent
End Function



Thanks Bot and Ant. Plenty of code to get me going there.

Big&: Because DataPak plasters the textures all over the harddrive before putting them in the recycle bin. It would contravene the license agreement. No Blitz-made packer can do anything else, unless it limits itself to static meshes and textures as sounds and animated models cannot be created from scratch within Blitz.

Michael: Because Molebox seems to have problems with recent PC's and writing to textures should only affect older PC's that aren't going to run the game anyway. Whenever there's a molebox'ed demo floating around, there are always a handful of people shouting "MAV", and even if it's not Molebox's fault but rather user error, I've still got to live with it.


EDIT: At least if/when I get a loader working, I can make a test and see how it works. Then I have some kind of first hand evidence to compare to the results of Molebox'ed demos.

How about running the textures through some sort of compression algorithm, turning the compressed data into Data statements, then rebuilding the textures internally at runtime (uncompress data, CreateTexture/WritePixel)?

There are a couple of compression algorithms in the Code Archives that could probably be modified to write to Data statements and read into bank and/or texture.

Yeah James. I was thinking about doing something along those lines, but when I use LoadAnimMesh() to load a level I create in Gile[s] with lots of entities and textures and surfaces and whatnot, it loads textures using LoadTexture, so I need to be able to parse the b3d myself in order to use my own texture load/create commands.

I've managed to fix Halo's code in the code archives so that it actually parses the b3d correctly now. It was written so that it would only read one texture per chunk, only one brush per chunk, and it ignored vertex colours and vertex normals, which sent the whole thing screwy because it didn't bypass them either.

Now I just have to make it build the mesh correctly. Then I'll rewrite it myself from scratch if and when I have it working. No point rewriting until I've got it working with Halo's basic structure. Ant's code has been very useful in showing me where Halo's code was lacking, so thanks for that. Hopefully Bot Builder's code will help me build the meshes to match the parsed data.

Ok, I think I'm making progress. I can't see any obvious differences between the two, but there might be something I'm missing.

Here's what my Gile[s] exported alley.b3d looks like when loaded with LoadMesh()




Here's what the same alley.b3d looks like when loaded with my loader :



That seems an odd place to put a static camera?

There are no cameras in the scene. It's not from my game, I just happened to steal an alley from the game to test with, since Gile[s] is where all my level meshes will be coming from.

I just used this code to get two identical positions and angles so that I could reasonably compare the results to see if anything is wrong.

Graphics3D 640,480
SetBuffer BackBuffer()

camera=CreateCamera()
CameraViewport camera,0,0,GraphicsWidth(),GraphicsHeight()

alley=Loadb3d("d:\my games\b3d loader\alley\alley.b3d",0,"d:\my games\b3d loader\alley")

hierarchy_tree(alley)

PositionEntity alley,0,-10,0


Repeat
	;RotateEntity camera,0,135,0
	TurnEntity camera,0,1,0
	UpdateWorld
	RenderWorld
	Flip
Until KeyHit(1)>0


Hierarchy tree is JFK's code from the code archives to ensure the mesh has the same hierarchy as the original too.

I've tried overlaying the images in Photoshop and I can't see a pixel difference, so I guess it's working :D