.b3d Animations
BlitzMax Forums/BlitzMax Programming/.b3d Animations
| ||
| I'm doing a b3d loader for t2 and so far it loads static meshes perfectly but I'm about to do animations. I can understand and load the data contained within the b3d, the series of keys and bones for each node but I'm not sure how to interpret this data. Do I - for each vertex asscoiated with each bone - Transform each vert assigned to this bone through the bone's current rotation and postion. Or do I first offset the position based on the node's current position. I.e, NewX = Node.X-VertexX FinX = Node.LocalMatrix.Transform( NewX ) FinX = Node.X + FinX Or simply FinX = Node.LocalMatrix.Transform( VertexX ) It appears wierd to me because each node in a animated b3d has it's own relative position yet the mesh renderes fine with the vertex's default positions. Implying mark did something to the naked data to get it to be represented accurately by the file it's self. Confusion. Much. confusion. |
| ||
| is this some kind of forward compatability allowing offsets that most modelers dont allow for? seems strange If you've parsed out index and tri vertex info I wouldnt mind implementing b3d in my ode editor |
| ||
| I'm not sure tbh. This is my first time implementing a skeleton based animation system so maybe it's the norm. Where's mark when you need him. [quote]If you've parsed out index and tri vertex info I wouldnt mind implementing b3d in my ode editor[/quote[ Here's the loader as is. Replace the calls to t2(I.e entity/vertex creation) with your own but it works fine for static entities.
Type TLoaderB3D Extends TLoader
Method New()
Name = "Blitz3D Mesh Loader Plugin"
Author = "Antony Wells"
Ver = "V1.0"
Ext = "b3d"
End Method
Function Create:TLoaderB3D()
Return New tloaderb3d
End Function
Field MediaPath:String
Field Root:TEntity
Field Stream:TStream
Field MatList:TList,TexList:TList
Method Load:TEntity(file:String)
MediaPath = ExtractDir(file)
Print "Media Path:"+MediaPath
If mediapath="<bad_dir>" mediapath=""
Root = Null
Stream = ReadFile( File )
If Stream = Null Throw "Unable to open B3D"
MatList = CreateList()
TexList = CreateList()
Local Header:bChunk = Self.ReadChunk()
If Header = Null Throw "Unable to read b3d header"
If Header.Name<>"BB3D"
MainLog.Post "Not a valid b3d file "+File
CloseFile stream
Return Null
Else
MainLog.Post(file+" is a valid b3d file.")
EndIf
Local B3d_Version = ReadInt( Stream )
If B3d_Version<>1 Throw "Trying to load b3d model version "+b3d_Version+" in v1.0 loader"
ParseChunk( Header )
CloseStream Stream
Return Root
End Method
Method ReadChunk:bChunk()
Local Out:bChunk = New bChunk
For Local J=1 To 4
Out.Name=Out.Name+Chr( ReadByte(Stream) )
Next
out.siz = ReadInt( Stream )
out.begin = StreamPos( Stream )
out.fin = out.begin+out.siz
Return out
End Method
Method ParseChunk( From:BChunk )
Local Cur:bChunk
Local Mat:TMaterial
Local Textures:Int
Local NewEnt:Int
If StreamPos(Stream) => StreamSize(Stream) Return
Repeat
Cur = ReadChunk()
Select cur.name
Case "BRUS" 'Material In Vivid Terms
Print "Brush Reached."
Local Textures = ReadInt( Stream )
MainLog.Post("Textures Per Brush:"+Textures)
While StreamPos( Stream ) < Cur.Fin
Local Mat:TMaterial = TMaterial.create()
Local NullString:String = ReadText()
Mat.Diffuse( ReadFloat( Stream ),ReadFloat( Stream ),ReadFloat( Stream ),ReadFloat( Stream ) )
Mat.Shininess( ReadFloat( Stream ) )
Mat.Name = NullString
MainLog.Post "Material Called:"+NullString,0
Local Blend:Int = ReadInt( Stream )
Select Blend
Case 1
Mat.Blend( Blend_Normal )
Case 2
Mat.Blend( Blend_Add )
Case 3
Mat.Blend( Blend_ADD )
Default
Throw "Unkonw blend mode>"+Blend
End Select
Local Fx:Int = ReadInt( Stream )
If (fx & 1) = 1
Mat.Ambient(255,255,255)
Mat.Specular(255,255,255)
Mat.Color(255,255,255)
EndIf
If (fx & 2) = 2
Mat.ColorMode( 0)
Else
Mat.ColorMode( 1)
End If
If (fx & 4) =4
Mat.Shade( Shade_Flat )
End If
If (fx & 16) =16
Mat.Cull( Cull_Off )
Else
Mat.Cull( Cull_Off )
End If
' Main_Error.invoke "Has "+Textures+" Textures"
For Local J:Int = 1 To Textures
Local TextureNum = ReadInt( Stream )
If TextureNum = -1
' Main_Error.invoke "Texture Slot "+J+" Empty"
Else
Local ThisTexture:TTexture =TTexture ( TexList.ValueAtIndex( TextureNum ) )
' Mat.AddTexture( ThisTexture )
mat.AddTexture( ThisTexture)
EndIf
Next
ListAddLast MatList,Mat
Wend
Case "TEXS"
Print "Texs"
Local Texture:TTexture
While StreamPos( Stream ) < Cur.Fin
Local File:String = ReadText()
Local Flags = ReadInt(Stream)
Local Blend = ReadInt(Stream)
If file<>""
If (Flags & 64) = 64
MainLog.Post "Sphere map ignored, not yet supported."
'Texture = New TSphereMap
Else
Texture = New TTexture
End If
mainlog.post "Loading Texture : File:"+mediaPath+"/"+file
'Texture.Load ( MediaPath+"/"+File )
Texture = TTexture.Load( MediaPath+"/"+file )
If texture = Null
texture=TTexture.load( MediaPath+"/"+StripAll(file)+"."+ExtractExt(file) )
If texture = Null
texture = ttexture.load(file)
If texture=Null
texture = ttexture.load(StripAll(file)+"."+ExtractExt(file) )
If texture=Null
MainLog.Post("Could not resolve texture name "+file,False)
EndIf
EndIf
EndIf
EndIf
' Texture.ResPath = MediaPath+"\"
Texture.Position( ReadFloat( Stream ),ReadFloat( Stream ),0)
Texture.Scale( ReadFloat( Stream),ReadFloat( Stream ),0 )
Texture.Rotate( 0,0,ReadFloat( Stream ) )
If (Flags & 65537) = 65537
Texture.CoordSet = 1
MainLog.Post "Using Second Coord set",0
End If
mainLog.post "Tex blend>"+blend+" flag>"+flags
Texture.ColorScale=1
Select Blend
Case 0
Texture.Blend( Texture_Normal )
Case 1
Texture.Blend( Texture_Normal )
Case 2
Texture.Blend( Texture_Modulated )
Case 3
Texture.Blend( Texture_Add )
Case 4
Texture.Blend( Texture_Dot3 )
Case 5
Texture.Blend( Texture_Modulated )
Texture.ColorScale =2
Case 6
Texture.Blend( Texture_Modulated )
Texture.ColorScale= 4
Case 7
Texture.Blend( Texture_Dot3Alpha )
End Select
ListAddLast TexList,Texture
EndIf
Wend
Case "NODE"
Print "Node."
Local W:Float,X:Float,Y:Float,Z:Float
Local Name:String = ReadText()
MainLog.Post "Entity Named>"+Name,0
Local Entity:TEntity = New TEntity
'Entity.Name = Name
'main_error.invoke "Node>"+Entity.Name
'Entity.IgnorePipeline=True
Entity.Position( ReadFloat( Stream ),ReadFloat( Stream ),ReadFloat( Stream ) ,True )
Entity.setScale( ReadFloat( Stream ),ReadFloat( Stream ),ReadFloat( Stream ) )
aEnt = Entity
' Main_Error.invoke "X:"+entity.x+" Y:"+entity.y+" Z:"+entity.z,0
If root = Null
Root = Entity
' main_error.invoke "Was Root"
Else
If pStack[ pCount ] = Null
' "Child entity without parent in b3d loader"
EndIf
Entity.SetParent( pStack[ pCount ] )
End If
W = ReadFloat( Stream )
X = ReadFloat( Stream )
Y = ReadFloat( Stream )
Z = ReadFloat( Stream )
Entity.Quat = New Quaternion
Entity.Quat.Set( X,Y,Z,W )
Entity.RotateFromQuat()
pCount:+1
pStack[ pCount ] = Entity
NewEnt = True
ParseChunk( Cur )
Case "MESH"
aBrush = ReadInt( Stream )
Print "Mesh reached."
'main_Error.invoke "Mesh BrushID:"+aBrush
ParseChunk( Cur )
Case "VRTS"
Print "Reading Verts"
Local Flags:Int = ReadInt( Stream )
Local Sets:Int = ReadInt( Stream )
Local SetSize:Int = ReadInt( Stream )
Local VertAt
Local TmpCoord:Float[3]
TmpSurf:TSurface = TSurface.Create()
aEnt.vSurf = tmpSurf
Local VertCIs
While StreamPos( Stream ) < Cur.Fin
VertAt = TmpSurf.AddVertex( ReadFloat(Stream),ReadFloat(Stream),ReadFloat(Stream) )
VertCIs:+1
If ( Flags & 1 ) = 1
TmpSurf.VertexNormal( VertAt,ReadFloat( Stream),ReadFloat( stream),-ReadFloat(Stream) )
End If
If ( Flags & 2 ) = 2
TmpSurf.VertexColor( VertAt, ReadFloat( Stream),ReadFloat(Stream),ReadFloat(Stream),ReadFloat(Stream) )
End If
For Local J = 0 To Sets-1
For Local k=0 To SetSize-1
TmpCoord[k] = ReadFloat( Stream )
Next
TmpSurf.VertexCoords(J,VertAt,TmpCoord[0],TmpCoord[1],TmpCoord[2] )
Next
Wend
Case "TRIS"
Print "Reading Tris."
Local TMat:TMaterial
Local MatId:Int = ReadInt( Stream )
Local Mat:TMaterial,Surf:TSurface
'Main_Error.Invoke "Loading Surface",0
If MatId = -1
' Mat:TMaterial = New TMaterial
Else
mainLog.post "MatId>"+MatId
TMat = TMaterial( MatList.ValueAtIndex( MatId ) )
mainlog.post "Surface material:"+TMat.Name
End If
Local triCIs:Int
If tmpSurf = Null
Throw "No surface previously defined in b3d loader"
EndIf
aSurf = TSurface.create()
If aSurf = Null
Throw "Surface clone failed in b3d loader"
EndIf
aEnt.AddSurface( aSurf )
aSurf.verts = tmpSurf.verts
aSurf.tris = tmpSurf.tris
aSurf.norms = tmpSurf.norms
aSurf.cols = tmpSurf.cols
asurf.coords = tmpSurf.coords
aSurf.coordsets =tmpsurf.coordsets
aSurf.VertC = tmpsurf.vertc
If tMat<>Null
aSurf.SetMaterial( TMat )
mainLog.post "USing Material>"+TMat.Name
EndIf
While StreamPos( Stream )< Cur.Fin
aSurf.AddTriangle( ReadInt( Stream ),ReadInt( Stream ),ReadInt( Stream ) )
Wend
' main_error.invoke "Read "+tricIs+" tris",0
Case "ANIM"
Local flags = ReadInt( stream )
Local frames = ReadInt( stream )
Local fps# = ReadFloat(stream)
Print "Anim chunk. Flags:"+Flags+" Frames:"+Frames+" Fps:"+Fps
Case "BONE"
aent.bone = New bone
While StreamPos(Stream)<Cur.fin
Int VertId = ReadInt()
Int VertWeight = ReadFloat()
Wend
Case "KEYS"
Local flag = ReadInt(stream)
While StreamPos(stream)<Cur.fin
Local frame:Int = ReadInt(Stream)
Local x#,y#,z#
Local rw#,rx#,ry#,rz#
Local sx#,sy#,sz#
If flag & 1
x
EndIf
Wend
End Select
' Print Cur.NAme
If NewEnt
pCount:-1
NewEnt=False
EndIf
SeekStream Stream,Cur.Fin
Until StreamPos( Stream)=>From.Fin
End Method
Field pStack:TEntity[10000],pCount:Int
Field aEnt:TEntity,aBrush
Field TmpSurf:TSurface
Field aSurf:TSurface
Method ReadText:String()
Local C:Byte,Text:String
Repeat
C = ReadByte( Stream )
If C = 0
Return Text
EndIf
Text:+Chr( C )
Forever
Return ""
End Method
End Type
|
| ||
| thanks for that! I'll have to modify it a bit to store vertex info in doubles for ode but I've put your code in my snippets folder for later use! |
| ||
| No problem. You might wanna use floats instead of doubles when using gl btw. I went doubles mad at first but I found it really slowed things down on my gpu. Not sure if modern cards are optimized for that kinda percision though. Back to my problem, i now have the animator code complete, and although the actual pivots(entities) joints are animating correctly(I added a box to each joint to visually see it) the actual mesh deformation is kind of whacky. It works fine frame 1. Perfectly looks like psonic's freaky zombie should but as soon as I go up the frames it starts to deform badly. Please if you're reading mark let me know where i'm going wrong. You're probably the only one with enough knowledge of the b3d format here. Here's the code that creates the matrix to deform each bone by. Method UpdateAnim() For Local s:tentity = EachIn subs s.calculateWorldMatrix(Null) Next TEntity.TmpSurf = RenSurf TEntity.Bas = VSurf For Local s:TEntity = EachIn subs s.TFormBones() Next End Method Global TmpSurf:TSurface Global Bas:TSurface Method CalculateWorldMatrix(Parent:TMatrix) Local Tmp:TMatrix = LocalRot.CreateCopy() Tmp.Multiply( LocalTrans ) If Parent<>Null Tmp.Multiply( Parent ) EndIf World = Tmp.CreateCopy() For Local s:Tentity = EachIn subs s.calculateWorldMatrix( tmp ) Next End Method I multiply each entities rotation and translation matrices by it's parent (Exactly in the same order as the entity is visually drawn) Then I call the deformer here which is supposed to transform the base surfaces vert into the visual surface based on each bone. IT does not work, is an understatement. Method TFormBones() Local os:TSurface = TEntity.Bas Local rs:TSurface = TEntity.TmpSurf If Bone = Null Return EndIf For Local j=0 Until bone.vc Local Vert = bone.vertid[ j ] World.GetPosition() Local ox#,oy#,oz# ox = world.x() oy = world.y() oz = world.z() Local vx#,vy#,vz# vx = os.vertexX( vert ) vy = os.vertexy( vert ) vz = os.vertexz( vert ) Local nx#,ny#,nz# nx = vx - ox ny = vy - oy nz = vz - oz world.tformvector( nx,ny,nz ) nx = world.tformx ny = world.tformy nz = world.tformz vx = ox + nx vy = oy + ny vz = oz + nz rs.moveVertex( vert,vx,vy,vz ) Next For Local t:Tentity = EachIn subs t.tformbones() Next End Method Here's the code that computers the rotation postion based on the current anim time. IT slerps the rotation quat beween the left most and right most keyframes, and interpolates(I think that's the term) between the left and right anim frames position. Scale is not yet implemeted because the model i'm using has no scale keys so it makes no difference.
Type TFrame
Field Rot:Quaternion
Field PosX#,posY#,posZ#
Field scalX#,scaly#,scalz#
Field Time#
Function MakeFrame:TFrame( L:TFrame,R:TFrame,Scal# )
Local out:TFrame = New tframe
out.rot = New quaternion
L.Rot.Slerp( out.rot,R.rot,scal )
'out.rot = l.rot
Local xd#,yd#,zd#
xd = r.posx-l.posx
yd = r.posy-l.posy
zd = r.posz-l.posz
out.posx = l.posx+xd*scal
out.posy = l.posy+yd*scal
out.posz = l.posz+zd*scal
Return out
End Function
End Type
Type TAnim
Field Frame:TFrame[3000],frames
Method FindLeft:TFrame( time# )
For Local j=time To 1 Step -1
If frame[j]<>Null
Return frame[j]
End If
Next
End Method
Method FindRight:TFrame( time#,Ignore:TFrame)
For Local j=time To frames
If frame[j]<>Null And frame[j]<>ignore
Return frame[j]
EndIf
Next
End Method
End Type
Method SetAnimTime( time# )
AnimTime = time
Print "SetAnimTime Called."
If animC <>Null
time = Time Mod AnimC.frames
If time<1 time =1
If time>animc.frames
mainlog.post("Mod failed.",True)
EndIf
For Local s:tentity = EachIn subs
s.SetFramer( time )
Next
Else
MainLog.Post("Entity has no aasscoiated animation tracks.",True)
EndIf
End Method
Method SetFramer(Time#)
If NoAnim = True Return
If animC<>Null
mainlog.post("Cannot directly animate top-tier entity.",True)
End If
Local LeftFrame:TFrame = Anim.FindLeft( Time )
Local RightFrame:TFrame = Anim.FindRight( Time,LeftFrame )
If leftFrame = RightFrame
cframe.rot = leftFrame.rot
cframe.posx = leftframe.posx
cframe.posy = leftframe.posy
cframe.posz = leftframe.posz
Else
Local dis# = RightFrame.time-LeftFrame.time
Local ltime# = time-leftframe.time
Local scal# = ltime/dis
cframe = TFrame.MakeFrame( LeftFrame,RightFrame,Scal )
cframe.rot.toAngles()
EndIf
localrot.rotate( cframe.rot.pitch,cframe.rot.yaw,cframe.rot.roll )
localtrans.position( cframe.posx,cframe.posy,cframe.posz )
For Local s:tentity = EachIn subs
s.setframer( time )
Next
End Method
|