.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 |