Code archives/3D Graphics - Mesh/Vertex animation
This code has been declared by its author to be Public Domain code.
Download source code
| |||||
EDIT 2: If anyone's still watching this - DLL version is available upon request that is (at least on my system) as fast as B3D animation (but not faster). Not sure how useful this actually is... This basically uses native Blitz commands to play MD2-style vertex morph animations (loaded from custom "MDX" files, but with a little imagination you could easily make it read real MD2s). I had hoped it would provide all the advantages of Blitz3D meshes while retaining the speed of MD2 animation. Sadly that doesn't seem to be the case - since each mesh needs to be a separate surface, most of the same speed hit from using B3Ds is still there. Even so it is usable for smaller numbers of characters. Apart from the speed, it is worth pointing out that since this moves the vertices every frame with VertexCoords, it is possible to cast shadows onto meshes animated in this way. Animations also don't suffer from the limitations of MD2, although if you've converted them from an MD2 file this is obviously irrelevant. EDIT: If ported to miniB3D this system is in fact significantly faster than B3D animation. See the comments for a couple of short programs to convert MD2 and B3D files to MDX format. This demo was designed with the dragon.md2 mesh from the dragon demo in Samples\mak. You can convert it with the MD2 to MDX converter. | |||||
Const MAXFRAMES=100 Const MAXVERTS=1000 Const MAXSEQS=10 Global appheight=768 Global appwidth=1024 Global appdepth=32 AppTitle "Vertex morph animation";,"Are you sure you want to quit?" Type MDX Field mesh,surf,verts ;Mesh, surface and texture handles; Total no. vertices Field tex,texname$ ;Texture handle, and filename. Add more slots if using more than one texture Field cframe#,frames ;Current frame, total no. frames Field fr.TFrame[MAXFRAMES] ;All frames Field cseq,seqs ;Current anim sequence, total no. sequences Field seq.sequence[MAXSEQS] ;Animation sequences Field speed#,mode,dir ;Animation speed, mode (0=stop,1=loop,2=ping-pong,3=one-shot), direction (mode 2) Field tlen,tt ;Transition length and time (for tweening between sequences) End Type Type TFrame Field vx#[MAXVERTS],vy#[MAXVERTS],vz#[MAXVERTS] ;Position Field nx#[MAXVERTS],ny#[MAXVERTS],nz#[MAXVERTS] ;Normal End Type Type sequence Field start ;First frame Field finish ;Last frame Field speed# ;Default play speed End Type SC_FPS=60 ;Desired framerate rtime=Floor(1000.0/SC_FPS) limited=True Graphics3D appwidth,appheight,appdepth,6 SetBuffer BackBuffer() centrecam=CreatePivot() PositionEntity centrecam,0,15,0 camera=CreateCamera(centrecam) PositionEntity camera,0,20,-50,1 sun=CreateLight() PositionEntity sun,-100,400,0 PointEntity sun,centrecam ground=CreateMesh() parquet=CreateSurface(ground) v1=AddVertex(parquet,-125,0,150):v2=AddVertex(parquet,125,0,150):v3=AddVertex(parquet,125,0,-100) AddTriangle(parquet,v1,v2,v3):v2=AddVertex(parquet,-125,0,-100):AddTriangle(parquet,v1,v3,v2) EntityColor ground,0,0,255 block=CreateCube():ScaleMesh block,20,5,20 dragon.MDX=LoadMDX("dragon.mdx"):d=dragon\mesh:PositionEntity d,0,17,0:ScaleMDXMesh(dragon,0.5,0.5,0.5) dragon\texname="dragon.bmp":dragon\tex=LoadTexture(dragon\texname):EntityTexture d,dragon\tex While Not KeyDown(1) ctime=MilliSecs() MoveEntity camera,0,KeyDown(200)-KeyDown(208),KeyDown(30)-KeyDown(44) TurnEntity centrecam,0,KeyDown(203)-KeyDown(205),0 MoveEntity centrecam,0,(KeyDown(31)-KeyDown(45))*0.2,0 PointEntity camera,centrecam If KeyHit(57) Then AnimateMDX(dragon.MDX,1,0.1,0,0) If KeyHit(28) Then AnimateMDX(dragon.MDX,1,0.1,0,20) ;Tween! If KeyHit(38) Then limited=Not limited ;Turn off frame limit If MilliSecs()-render_time=>1000 Then fps=frames:frames=0:render_time=MilliSecs():Else frames=frames+1 UpdateMDX UpdateWorld RenderWorld Text 0,30,"FPS: "+fps Text 0,60,"Current frame: "+dragon\cframe n=rtime-(MilliSecs()-ctime) ;Free spare CPU time Delay n-(limited+1) Flip limited Wend End Function LoadMDX.MDX(fname$) ;Load an MDX file meshbank=CreateBank(FileSize(fname)) filein=ReadFile(fname) ReadBytes(meshbank,filein,0,BankSize(meshbank)) CloseFile filein scale#=PeekFloat(meshbank,0) sfac#=PeekFloat(meshbank,4) ent.MDX=New MDX ent\mesh=CreateMesh():s=CreateSurface(ent\mesh):ent\surf=s ent\frames=PeekShort(meshbank,8) ;Number of frames fsize=PeekInt(meshbank,10) ;Size of a frame in bytes ent\verts=PeekShort(meshbank,14) ;No. verts ent\seqs=PeekShort(meshbank,28) ;No. anim sequences For v=0 To ent\verts-1 ;Store the UVs, don't bother with other data yet vu#=PeekShort(meshbank,34+v*4)/65535.0 vv#=PeekShort(meshbank,36+v*4)/65535.0 AddVertex(s,0,0,0,vu,vv) Next np=PeekInt(meshbank,16) ts=PeekShort(meshbank,np) ;No. tris For t=0 To ts-1 ;Triangle info v0=PeekShort(meshbank,np+2+t*6) v1=PeekShort(meshbank,np+4+t*6) v2=PeekShort(meshbank,np+6+t*6) AddTriangle(s,v0,v1,v2) Next np=PeekInt(meshbank,20):nlen=0 ;Texture data offset texnum=PeekByte(meshbank,np) ;Number of textures For t=0 To texnum-1 pos=PeekInt(meshbank,np+1+t*4) namelen=PeekShort(meshbank,pos) ent\texname="" ;Add more texture slots if intending to use more than one texture For v=0 To namelen-1 ent\texname=ent\texname+Chr(PeekByte(meshbank,pos+2+v)) Next If FileType(ent\texname)=1 Then ent\tex=LoadTexture(ent\texname):EntityTexture ent\mesh,ent\tex Next np=PeekInt(meshbank,24) For f=0 To ent\frames-1 ;Frame data ent\fr[f]=New TFrame For v=0 To ent\verts-1 pos=np+(f*ent\verts+v)*9 ent\fr[f]\vx[v]=PeekSShort(meshbank,pos)/sfac ent\fr[f]\vy[v]=PeekSShort(meshbank,pos+2)/sfac ent\fr[f]\vz[v]=PeekSShort(meshbank,pos+4)/sfac ent\fr[f]\nx[v]=PeekSByte(meshbank,pos+6)/127.0 ent\fr[f]\ny[v]=PeekSByte(meshbank,pos+7)/127.0 ent\fr[f]\nz[v]=PeekSByte(meshbank,pos+8)/127.0 Next Next ent\fr[MAXFRAMES]=New TFrame ;Temporary frame (for transition tweening) np=PeekInt(meshbank,30) For sq=0 To ent\seqs-1 ent\seq[sq]=New sequence ent\seq[sq]\start=PeekShort(meshbank,np+sq*8) ent\seq[sq]\finish=PeekShort(meshbank,np+sq*8+2) ent\seq[sq]\speed=PeekFloat(meshbank,np+sq*8+4) Next For v=0 To ent\verts-1 ;Set up the mesh at frame 0 (entirely optional) VertexCoords s,v,ent\fr[0]\vx[v],ent\fr[0]\vy[v],ent\fr[0]\vz[v] VertexNormal s,v,ent\fr[0]\nx[v],ent\fr[0]\ny[v],ent\fr[0]\nz[v] Next FreeBank meshbank Return ent End Function Function SaveMDX(ent.MDX,fname$) ;Save an MDX to file after changing it meshbank=CreateBank(34) numframes=ent\frames SetMDXFrame(ent,0) scale#=MaxRadius(ent\mesh):sfac#=32765.0/scale ;Prevent rounding errors that might result in changing sign PokeFloat meshbank,0,scale:PokeFloat meshbank,4,sfac s=ent\surf ;Only one surface for now vs=ent\verts PokeShort meshbank,8,numframes ;Number of frames PokeInt meshbank,10,vs*9 ;Size of a frame in bytes PokeShort meshbank,28,ent\seqs ;Number of anim sequences (only one, no way to get sequence data from an b3d. Change it later if necessary) PokeShort meshbank,14,vs:ResizeBank meshbank,34+(vs*4)+2 For v=0 To vs-1 ;Store the UVs, don't bother with other data yet PokeShort meshbank,34+v*4,VertexU(s,v)*65535 PokeShort meshbank,36+v*4,VertexV(s,v)*65535 Next ts=CountTriangles(s):np=BankSize(meshbank) PokeShort meshbank,np-2,ts:ResizeBank meshbank,np+ts*6 PokeInt meshbank,16,np-2 ;Offset for triangle data For t=0 To ts-1 ;Triangle data PokeShort meshbank,np+t*6,TriangleVertex(s,t,0) PokeShort meshbank,np+2+t*6,TriangleVertex(s,t,1) PokeShort meshbank,np+4+t*6,TriangleVertex(s,t,2) Next FreeEntity mesh pos=BankSize(meshbank):PokeInt meshbank,20,pos ;Offset for texture name texnum=1 ;Need to make a couple of changes to the system to manage more textures ResizeBank meshbank,pos+1+texnum*4 PokeByte(meshbank,pos,texnum) For t=0 To texnum-1 np=BankSize(meshbank):PokeInt meshbank,pos+1+t*4,np ResizeBank meshbank,np+2+Len(ent\texname):PokeShort(meshbank,np,Len(ent\texname)) For v=1 To Len(ent\texname) PokeByte(meshbank,np+1+v,Asc(Mid(ent\texname,v,1))) Next Next np=BankSize(meshbank):PokeInt meshbank,24,np ;Offset for frame data ResizeBank meshbank,np+ent\frames*vs*9 For f=0 To numframes-1 ;Frame data For v=0 To vs-1 pos=np+(f*vs+v)*9 PokeSShort meshbank,pos,ent\fr[f]\vx[v]*sfac PokeSShort meshbank,pos+2,ent\fr[f]\vy[v]*sfac PokeSShort meshbank,pos+4,ent\fr[f]\vz[v]*sfac PokeSByte meshbank,pos+6,ent\fr[f]\nx[v]*127 PokeSByte meshbank,pos+7,ent\fr[f]\ny[v]*127 PokeSByte meshbank,pos+8,ent\fr[f]\nz[v]*127 Next Next np=BankSize(meshbank):PokeInt meshbank,30,np ;Offset for sequence data ResizeBank meshbank,np+8*ent\seqs For s=0 To ent\seqs PokeShort meshbank,np+8*ent\seqs,ent\seq[s]\start:PokeShort meshbank,np+2+8*ent\seqs,ent\seq[s]\finish PokeFloat meshbank,np+4+8*ent\seqs,ent\seq[s]\speed ;Call it one long animseq because I don't know if/how B3D stores those Next fileout=WriteFile(mdxfile) WriteBytes(meshbank,fileout,0,BankSize(meshbank)) CloseFile fileout FreeBank meshbank End Function Function AnimateMDX(ent.MDX,mode=1,speed#=1,seq=0,tlen=0) ;Animate an MDX ent\mode=mode ent\speed=speed ent\cseq=seq ent\dir=Sgn(ent\speed) If ent\dir=1 ent\cframe=ent\seq[seq]\start Else ent\cframe=ent\seq[seq]\finish EndIf ent\tlen=tlen If tlen For v=0 To ent\verts-1 ent\fr[MAXFRAMES]\vx[v]=VertexX(ent\surf,v):ent\fr[MAXFRAMES]\vy[v]=VertexY(ent\surf,v):ent\fr[MAXFRAMES]\vz[v]=VertexZ(ent\surf,v) ent\fr[MAXFRAMES]\nx[v]=VertexNX(ent\surf,v):ent\fr[MAXFRAMES]\ny[v]=VertexNY(ent\surf,v):ent\fr[MAXFRAMES]\nz[v]=VertexNZ(ent\surf,v) Next ent\tt=0 EndIf End Function Function MDXAnimTime#(ent.MDX) ;Return the current point of the animation as a float, like MD2AnimTime If ent\tlen Then Return -1 Return ent\cframe End Function Function MDXAnimLength(ent.MDX,seq=-1) ;Total number of frames held in the MDX, or in the specified sequence If seq<0 Or seq>ent\seqs-1 Return ent\frames Else Return ent\seq[seq]\finish-ent\seq[seq]\start EndIf End Function Function MDXAnimating(ent.MDX) ;Return true if MDX is currently animating If ent\mode>0 Then Return True:Else Return False End Function Function SetMDXFrame(ent.MDX,frame#) ;Manually set animation to a specific point (stops animation) ent\mode=0 ent\cframe=frame For v=0 To ent\verts VertexCoords ent\surf,v,ent\fr[frame]\vx[v],ent\fr[frame]\vy[v],ent\fr[frame]\vz[v] VertexNormal ent\surf,v,ent\fr[frame]\nx[v],ent\fr[frame]\ny[v],ent\fr[frame]\nz[v] Next End Function Function AddMDXSeq(ent.MDX,fframe,lframe,speed#=1) ;Define an MDX sequence by first and last frames ent\seq[ent\seqs]\start=fframe ent\seq[ent\seqs]\finish=lframe ent\seq[ent\seqs]\speed=speed ent\seqs=ent\seqs+1 End Function Function GetMDXSeq(ent.MDX) ;Return which sequence the MDX is currently playing Return ent\cseq End Function Function UpdateMDX(updatespeed#=1.0) ;Use this instead of/in addition to UpdateWorld, as that obviously doesn't control MDX animation For ent.MDX=Each MDX animspeed#=ent\seq[ent\cseq]\speed*ent\speed*updatespeed If ent\mode If Not ent\tlen If ent\mode=2 ;Ping-pong If animspeed<0 Then animspeed=-animspeed:ent\dir=-ent\dir ;Just reverse the direction, simpler ent\cframe=ent\cframe+animspeed*ent\dir If ent\cframe>=ent\seq[ent\cseq]\finish-1 Then ent\dir=-1:ent\cframe=ent\seq[ent\cseq]\finish-1 If ent\cframe<=ent\seq[ent\cseq]\start Then ent\dir=1:ent\cframe=ent\seq[ent\cseq]\start If ent\dir=1 frame1=Floor(ent\cframe):frame2=frame1+1:frametween#=ent\cframe-frame1 Else frame1=Ceil(ent\cframe):frame2=frame1-1:frametween#=frame1-ent\cframe EndIf Else ;Linear ent\cframe=ent\cframe+animspeed If ent\dir=1 ;Going forwards If ent\cframe>=ent\seq[ent\cseq]\finish Then ent\cframe=ent\seq[ent\cseq]\start:If ent\mode=3 Then ent\mode=0 frame1=Floor(ent\cframe) If frame1<ent\seq[ent\cseq]\finish-1 Then frame2=frame1+1:Else frame2=ent\seq[ent\cseq]\start frametween#=ent\cframe-frame1 Else ;Going backwards If ent\cframe<ent\seq[ent\cseq]\start ent\cframe=ent\seq[ent\cseq]\finish-(ent\seq[ent\cseq]\start-ent\cframe) If ent\mode=3 Then ent\mode=0 EndIf frame2=Floor(ent\cframe) If frame2<ent\seq[ent\cseq]\finish-1 Then frame1=frame2+1:Else frame1=ent\seq[ent\cseq]\start frametween#=1-(ent\cframe-frame2) EndIf EndIf Else ;Tween to next sequence frame1=MAXFRAMES frame2=ent\seq[ent\cseq]\start ent\tt=ent\tt+1 frametween#=Float(ent\tt)/ent\tlen If ent\tt>=ent\tlen Then ent\tlen=0:ent\tt=0 EndIf f1.TFrame=ent\fr[frame1]:f2.TFrame=ent\fr[frame2] ;When repeatedly accessing type fields, keep the path simple - faster For v=0 To ent\verts-1 vx#=(f1\vx[v]*(1-frametween))+(f2\vx[v]*frametween) vy#=(f1\vy[v]*(1-frametween))+(f2\vy[v]*frametween) vz#=(f1\vz[v]*(1-frametween))+(f2\vz[v]*frametween) nx#=(f1\nx[v]*(1-frametween))+(f2\nx[v]*frametween) ny#=(f1\ny[v]*(1-frametween))+(f2\ny[v]*frametween) nz#=(f1\nz[v]*(1-frametween))+(f2\nz[v]*frametween) VertexCoords ent\surf,v,vx,vy,vz VertexNormal ent\surf,v,nx,ny,nz Next EndIf Next End Function Function ScaleMDXMesh(ent.MDX,xs#,ys#,zs#) ;ScaleMesh won't work, because the VertexCoords change every frame. Slow! For f=0 To ent\frames-1 For v=0 To ent\verts-1 ent\fr[f]\vx[v]=ent\fr[f]\vx[v]*xs ent\fr[f]\vy[v]=ent\fr[f]\vy[v]*ys ent\fr[f]\vz[v]=ent\fr[f]\vz[v]*zs Next Next frame=Floor(ent\cframe) For v=0 To ent\verts VertexCoords ent\surf,v,ent\fr[frame]\vx[v],ent\fr[frame]\vy[v],ent\fr[frame]\vz[v] VertexNormal ent\surf,v,ent\fr[frame]\nx[v],ent\fr[frame]\ny[v],ent\fr[frame]\nz[v] Next End Function Function CopyMDX.MDX(ent.MDX,newtex=False) ent2.MDX=New MDX ent2\mesh=CopyMesh(ent\mesh) ;Have to use CopyMesh as CopyEntity would use old mesh data ent2\surf=GetSurface(ent2\mesh,1):ent2\verts=CountVertices(ent2\surf) ;Change this if multisurface or multimesh MDX desired ent2\texname=ent\texname If newtex=True Then ent2\tex=LoadTexture(ent2\texname):Else ent2\tex=ent\tex ;Can share the texture. Remember not to free it in that case! EntityTexture ent2\mesh,ent2\tex ent2\frames=ent\frames For f=0 To ent2\frames-1 ent2\fr[f]=New TFrame For v=0 To ent2\verts-1 ent2\fr[f]\vx[v]=ent\fr[f]\vx[v] ent2\fr[f]\vy[v]=ent\fr[f]\vy[v] ent2\fr[f]\vz[v]=ent\fr[f]\vz[v] Next Next ent2\cseq=ent\cseq:ent2\seqs=ent\seqs For s=0 To ent2\seqs-1 ent2\seq[s]=New sequence ent2\seq[s]\start=ent\seq[s]\start ent2\seq[s]\finish=ent\seq[s]\finish ent2\seq[s]\speed=ent\seq[s]\speed Next ent2\cframe=ent2\seq[ent2\cseq]\start Return ent2.MDX End Function Function FreeMDX(ent.MDX,freetex=True) If freetex And ent\tex<>0 Then FreeTexture ent\tex For s=0 To ent\seqs-1 Delete ent\seq[s] Next For f=0 To ent\frames-1 Delete ent\fr[f] Next FreeEntity ent\mesh Delete ent End Function Function PeekSShort(bank,offset) ;Return a signed short, range -32767,32767 s=PeekShort(bank,offset) If s>32767 Then Return s-65535:Else Return s End Function Function PeekSByte(bank,offset) ;Return a signed byte, range -127,127 b=PeekByte(bank,offset) If b>127 Then Return b-255:Else Return b End Function Function PokeSShort(bank,offset,value) ;Store a signed Short If value<0 Then value=value+65535 PokeShort bank,offset,value End Function Function PokeSByte(bank,offset,value) ;Store a signed Byte If value<0 Then value=value+255 PokeByte bank,offset,value End Function Function MaxRadius#(body) ;Get the largest radius of a mesh (ie. distance of furthest vertex) Local r#,cs%,s%,ver% ;Obviously only works on single meshes For cs=1 To CountSurfaces(body) s=GetSurface(body,cs) For ver=0 To CountVertices(s)-1 dx#=VertexX(s,ver) dy#=VertexY(s,ver) dz#=VertexZ(s,ver) dd#=Sqr(dx*dx+dy*dy+dz*dz) If r<dd Then r=dd Next Next Return r# End Function |
Comments
| ||
Use this program to convert an existing MD2 file to the MDX format used by this system: And this one converts B3D files to MDX: I'm well aware that building a whole static mesh anew for each frame is a thoroughly stupid way to get the frame data, but it works, and I don't expect anyone to want to use these in real time! With a little tweaking you could use a version of this to convert B3D to MD2, as well, which I imagine would be infinitely more useful. Anyway, I hope this is helpful to someone! |
| ||
You might gain a little speed by saving the mesh back out as a B3D with one bone per vertex, and animation on each bone (debatable, but worth a try). |
Code Archives Forum