Weil es mich interessiert hat, habe ich gestern Abend kurz kleine Lade- und Speicherroutinen für das *.b3d Modellformat geschrieben. Die Klassenstruktur orientiert sich stark an der B3D-Spezifikation. Wo möglich, betreibt die Laderoutine auch Validierung der zu ladenden Datei (um einen Absturz bei korrupten oder falschen Dateien zu vermeiden).
Code: BlitzMax: [AUSKLAPPEN] [EINKLAPPEN] SuperStrict
Function ReadCString:String(Stream:TStream) Local Buffer:Byte[1024] Local Index:Int Repeat Buffer[Index] = Stream.ReadByte() If Not Buffer[Index] Then Exit Index :+ 1 If Index = Buffer.Length Then Buffer = Buffer[.. Buffer.Length*2] Forever Return String.FromBytes(Varptr Buffer[0], Index) End Function
Function WriteCString:String(Stream:TStream, Source:String) Stream.WriteString(Source) Stream.WriteByte(0) End Function
Type TB3DLoaderException Field Msg:String Method ToString:String() Return Msg End Method Method Create:TB3DLoaderException(Msg:String) Self.Msg = Msg Return Self End Method End Type
Type TB3DModel Const CHUNK_BB3D:Int = $44334242 Const CHUNK_TEXS:Int = $53584554 Const CHUNK_BRUS:Int = $53555242 Const CHUNK_VRTS:Int = $53545256 Const CHUNK_TRIS:Int = $53495254 Const CHUNK_MESH:Int = $4853454D Const CHUNK_BONE:Int = $454E4F42 Const CHUNK_KEYS:Int = $5359454B Const CHUNK_ANIM:Int = $4D494E41 Const CHUNK_NODE:Int = $45444F4E Field Root:TBB3DChunk Method Load:TB3DModel(Stream:TStream) While Not Stream.Eof() Local Id :Int = Stream.ReadInt() Local Size:Int = Stream.ReadInt() Select Id Case CHUNK_BB3D If Root Then Throw New TB3DLoaderException.Create("Duplicate BB3D chunk") Root = New TBB3DChunk.Load(Stream, Size) Default Stream.SkipBytes(Size) End Select Wend Return Self End Method Method Save(Stream:TStream) If Root Then Local OutSize:Int = Root.CalcSize() + 8 Root.Save(Stream) If Stream.Pos() <> OutSize Then .. Throw New TB3DLoaderException.Create("Export size mismatch") EndIf End Method End Type
Type TBB3DChunk Const MAX_SUPPORTED_VERSION:Int = 100 Field Size:Int Field Version:Int Field Textures:TTextureChunk[] Field Brushes :TBrushChunk[] Field RootNode:TNodeChunk Method Load:TBB3DChunk(Stream:TStream, Size:Int) Local EndPos:Int = Stream.Pos() + Size Version = Stream.ReadInt() If Version > MAX_SUPPORTED_VERSION Then .. Throw New TB3DLoaderException.Create("Unsupported file version") While Stream.Pos() < EndPos Local Id:Int = Stream.ReadInt() Local S :Int = Stream.ReadInt() Select Id Case TB3DModel.CHUNK_TEXS If Textures Then Throw New TB3DLoaderException.Create("Duplicate textures chunk") Textures = TTexturesChunk.Load(Stream, S) Case TB3DModel.CHUNK_BRUS If Brushes Then Throw New TB3DLoaderException.Create("Duplicate brushes chunk") Brushes = TBrushesChunk.Load(Stream, S) Case TB3DModel.CHUNK_NODE If RootNode Then Throw New TB3DLoaderException.Create("Duplicate root node chunk") RootNode = New TNodeChunk.Load(Stream, S) Default Stream.SkipBytes(S) End Select Wend If Stream.Pos() > EndPos Then .. Throw New TB3DLoaderException.Create("Corrupt BB3D chunk") Return Self End Method Method Save(Stream:TStream) Stream.WriteInt(TB3DModel.CHUNK_BB3D) Stream.WriteInt(Size) Stream.WriteInt(Version) TTexturesChunk.Save(Stream) TBrushesChunk.Save(Stream) If RootNode Then RootNode.Save(Stream) End Method Method CalcSize:Int() Size = 4 Size :+ TTexturesChunk.CalcSize() Size :+ TBrushesChunk.CalcSize() If Textures Then Size :+ 8 If Brushes Then Size :+ 8 If RootNode Then Size :+ RootNode.CalcSize() + 8 Return Size End Method End Type
Type TTexturesChunk Global Size:Int Global Textures:TTextureChunk[] Function Load:TTextureChunk[](Stream:TStream, Size:Int) Local TextureList:TList = New TList Local EndPos:Int = Stream.Pos() + Size While Stream.Pos() < EndPos TextureList.AddLast(New TTextureChunk.Load(Stream)) Wend If Stream.Pos() > EndPos Then .. Throw New TB3DLoaderException.Create("Corrupt textures chunk") Textures = New TTextureChunk[TextureList.Count()] Local Index:Int = 0 For Local Tex:TTextureChunk = EachIn TextureList Textures[Index] = Tex Index :+ 1 Next Return Textures End Function Function CalcSize:Int() Size = 0 For Local Tex:TTextureChunk = EachIn Textures Size :+ Tex.CalcSize() Next Return Size End Function Function Save(Stream:TStream) If Textures Then Stream.WriteInt(TB3DModel.CHUNK_TEXS) Stream.WriteInt(Size) For Local Tex:TTextureChunk = EachIn Textures Tex.Save(Stream) Next EndIf End Function End Type
Type TBrushesChunk Global Size:Int Global Brushes:TBrushChunk[] Global TextureCount:Int Function Load:TBrushChunk[](Stream:TStream, Size:Int) Local EndPos:Int = Stream.Pos() + Size Local BrushList:TList = New TList TextureCount = Stream.ReadInt() While Stream.Pos() < EndPos BrushList.AddLast(New TBrushChunk.Load(Stream, TextureCount)) Wend If Stream.Pos() > EndPos Then .. Throw New TB3DLoaderException.Create("Corrupt brush chunk") Brushes = New TBrushChunk[BrushList.Count()] Local Index:Int = 0 For Local Brush:TBrushChunk = EachIn BrushList Brushes[Index] = Brush Index :+ 1 Next Return Brushes End Function Function CalcSize:Int() If Brushes Then Size = 4 For Local Brush:TBrushChunk = EachIn Brushes Size :+ Brush.CalcSize() Next Else Size = 0 EndIf Return Size End Function Function Save(Stream:TStream) If Brushes Then Stream.WriteInt(TB3DModel.CHUNK_BRUS) Stream.WriteInt(Size) Stream.WriteInt(TextureCount) For Local Brush:TBrushChunk = EachIn Brushes Brush.Save(Stream) Next EndIf End Function End Type
Type TTextureChunk Field Size:Int Field FileName :String Field Flags :Int Field BlendMode:Int Field XPos :Float, YPos :Float Field XScale :Float, YScale:Float Field Rotation :Float Method Load:TTextureChunk(Stream:TStream) FileName = ReadCString(Stream) Flags = Stream.ReadInt() BlendMode = stream.ReadInt() XPos = Stream.ReadFloat() YPos = Stream.ReadFloat() XScale = Stream.ReadFloat() YScale = Stream.ReadFloat() Rotation = Stream.ReadFloat() Return Self End Method Method Save(Stream:TStream) WriteCString(Stream, FileName) Stream.WriteInt(Flags) Stream.WriteInt(BlendMode) Stream.WriteFloat(XPos) Stream.WriteFloat(YPos) Stream.WriteFloat(XScale) Stream.WriteFloat(YScale) Stream.WriteFloat(Rotation) End Method Method CalcSize:Int() Size = FileName.Length + 1 + 7*4 Return Size End Method End Type
Type TBrushChunk Field Size:Int Field Name :String Field R :Float Field G :Float Field B :Float Field A :Float Field Specular :Float Field BlendMode:Int Field Effects :Int Field Textures :Int[] Method Load:TBrushChunk(Stream:TStream, TextureCount:Int) Name = ReadCString(Stream) R = Stream.ReadFloat() G = Stream.ReadFloat() B = Stream.ReadFloat() A = Stream.ReadFloat() Specular = Stream.ReadFloat() BlendMode = Stream.ReadInt() Effects = Stream.ReadInt() Textures = New Int[TextureCount] For Local I:Int = 0 Until TextureCount Textures[I] = Stream.ReadInt() Next Return Self End Method Method Save(Stream:TStream) WriteCString(Stream, Name) Stream.WriteFloat(R) Stream.WriteFloat(G) Stream.WriteFloat(B) Stream.WriteFloat(A) Stream.WriteFloat(Specular) Stream.WriteInt(BlendMode) Stream.WriteInt(Effects) Stream.WriteBytes(Varptr Textures[0], Textures.Length*4) End Method Method CalcSize:Int() Size = Name.Length + 1 + 7*4 + Textures.Length*4 Return Size End Method End Type
Type TVerticesChunk Field Size:Int Field Flags:Int Field SetCount:Int Field SetSize:Int Field Vertices:TVertexChunk[] Method Load:TVerticesChunk(Stream:TStream, Size:Int) Flags = Stream.ReadInt() SetCount = Stream.ReadInt() SetSize = Stream.ReadInt() Local VertexCount:Int = (Size - 3*4)/CalcVertexSize() Vertices = New TVertexChunk[VertexCount] For Local I:Int = 0 Until VertexCount Vertices[I] = New TVertexChunk.Load(Stream, Self) Next Return Self End Method Method Save(Stream:TStream) Stream.WriteInt(TB3DModel.CHUNK_VRTS) Stream.WriteInt(Size) Stream.WriteInt(Flags) Stream.WriteInt(SetCount) Stream.WriteInt(SetSize) For Local Vertex:TVertexChunk = EachIn Vertices Vertex.Save(Stream) Next End Method Method CalcSize:Int() Size = 3*4 + Vertices.Length*CalcVertexSize() Return Size End Method Method CalcVertexSize:Int() Local VertexSize:Int = 4*3 If Flags & 1 Then VertexSize :+ 3*4 If Flags & 2 Then VertexSize :+ 4*4 VertexSize :+ SetCount*SetSize*4 Return VertexSize End Method End Type
Type TVertexChunk Field Size:Int Field Parent:TVerticesChunk Field X:Float, Y:Float, Z:Float Field NX:Float, NY:Float, NZ:Float Field R:Float, G:Float, B:Float, A:Float Field TexCoords:Float[,] Method Load:TVertexChunk(Stream:TStream, Parent:TVerticesChunk) Self.Parent = Parent X = Stream.ReadFloat() Y = Stream.ReadFloat() Z = Stream.ReadFloat() If Parent.Flags & 1 Then NX = Stream.ReadFloat() NY = Stream.ReadFloat() NZ = Stream.ReadFloat() EndIf If Parent.Flags & 2 Then R = Stream.ReadFloat() G = Stream.ReadFloat() B = Stream.ReadFloat() A = Stream.ReadFloat() EndIf TexCoords = New Float[Parent.SetCount, Parent.SetSize] For Local I:Int = 0 Until Parent.SetCount For Local T:Int = 0 Until Parent.SetSize TexCoords[I, T] = Stream.ReadFloat() Next Next Return Self End Method Method Save(Stream:TStream) Stream.WriteFloat(X) Stream.WriteFloat(Y) Stream.WriteFloat(Z) If Parent.Flags & 1 Then Stream.WriteFloat(NX) Stream.WriteFloat(NY) Stream.WriteFloat(NZ) EndIf If Parent.Flags & 2 Then Stream.WriteFloat(R) Stream.WriteFloat(G) Stream.WriteFloat(B) Stream.WriteFloat(A) EndIf For Local I:Int = 0 Until Parent.SetCount For Local T:Int = 0 Until Parent.SetSize Stream.WriteFloat(TexCoords[I, T]) Next Next End Method Method CalcSize:Int() Size = Parent.CalcVertexSize() Return Size End Method End Type
Type TTriangleChunk Field Size:Int Field Brush:Int Field Indices:Int[] Method Load:TTriangleChunk(Stream:TStream, Size:Int) Brush = Stream.ReadInt() Size :- 4 Indices = New Int[Size/4] Stream.ReadBytes(Varptr Indices[0], Size) Return Self End Method Method Save(Stream:TStream) Stream.WriteInt(TB3DModel.CHUNK_TRIS) Stream.WriteInt(Size) Stream.WriteInt(Brush) Stream.WriteBytes(Varptr Indices[0], Indices.Length*4) End Method Method CalcSize:Int() Size = 4 + Indices.Length*4 Return Size End Method End Type
Type TMeshChunk Field Size:Int Field Brush:Int Field Vertices:TVerticesChunk Field Triangles:TList Method Load:TMeshChunk(Stream:TStream, Size:Int) Local EndPos:Int = Stream.Pos() + Size Brush = Stream.ReadInt() Triangles = New TList While Stream.Pos() < EndPos Local Id:Int = Stream.ReadInt() Local S :Int = Stream.ReadInt() Select Id Case TB3DModel.CHUNK_VRTS If Vertices Then Throw New TB3DLoaderException.Create("Duplicate vertices chunk") Vertices = New TVerticesChunk.Load(Stream, S) Case TB3DModel.CHUNK_TRIS Triangles.AddLast(New TTriangleChunk.Load(Stream, S)) Default Stream.SkipBytes(S) End Select Wend If Stream.Pos() > EndPos Then .. Throw New TB3DLoaderException.Create("Corrupt mesh chunk") Return Self End Method Method Save(Stream:TStream) Stream.WriteInt(TB3DModel.CHUNK_MESH) Stream.WriteInt(Size) Stream.WriteInt(Brush) If Vertices Then Vertices.Save(Stream) For Local Triangle:TTriangleChunk = EachIn Triangles Triangle.Save(Stream) Next End Method Method CalcSize:Int() Size = 4 If Vertices Then Size :+ Vertices.CalcSize() + 8 For Local Triangle:TTriangleChunk = EachIn Triangles Size :+ Triangle.CalcSize() + 8 Next Return Size End Method End Type
Type TBoneChunk Field Size:Int Field Indices:Int [] Field Weights:Float[] Method Load:TBoneChunk(Stream:TStream, Size:Int) Local Length:Int = Size/8 If Not Length Then Return Self Indices = New Int [Length] Weights = New Float[Length] Stream.ReadBytes(Varptr Indices[0], Length*4) Stream.ReadBytes(Varptr Weights[0], Length*4) Return Self End Method Method Save(Stream:TStream) Stream.WriteInt(TB3DModel.CHUNK_BONE) Stream.WriteInt(Size) If (Not Indices) Or (Not Weights) Then Return Stream.WriteBytes(Varptr Indices[0], Indices.Length*4) Stream.WriteBytes(Varptr Weights[0], Indices.Length*4) End Method Method CalcSize:Int() Size = Indices.Length*4 + Weights.Length*4 Return Size End Method End Type
Type TKeysChunk Field Size:Int Field Flags:Int Field Keys:TKeyChunk[] Method Load:TKeysChunk(Stream:TStream, Size:Int) Flags = Stream.ReadInt() Local KeyCount:Int = (Size - 4)/CalcKeySize() Keys = New TKeyChunk[KeyCount] For Local I:Int = 0 Until KeyCount Keys[I] = New TKeyChunk.Load(Stream, Self) Next Return Self End Method Method Save(Stream:TStream) Stream.WriteInt(TB3DModel.CHUNK_KEYS) Stream.WriteInt(Size) Stream.WriteInt(Flags) For Local Key:TKeyChunk = EachIn Keys Key.Save(Stream) Next End Method Method CalcSize:Int() Size = CalcKeySize()*Keys.Length + 4 Return Size End Method Method CalcKeySize:Int() Local KeySize:Int = 4 If Flags & 1 Then KeySize :+ 3*4 If Flags & 2 Then KeySize :+ 3*4 If Flags & 4 Then KeySize :+ 4*4 Return KeySize End Method End Type
Type TKeyChunk Field Size:Int Field Parent:TKeysChunk Field Frame :Int Field XPos :Float, YPos :Float, ZPos :Float Field XScale:Float, YScale:Float, ZScale:Float Field XRot :Float, YRot :Float, ZRot :Float, WRot:Float Method Load:TKeyChunk(Stream:TStream, Parent:TKeysChunk) Self.Parent = Parent Frame = Stream.ReadInt() If Parent.Flags & 1 Then XPos = Stream.ReadFloat() YPos = Stream.ReadFloat() ZPos = Stream.ReadFloat() EndIf If Parent.Flags & 2 Then XScale = Stream.ReadFloat() YScale = Stream.ReadFloat() ZScale = Stream.ReadFloat() EndIf If Parent.Flags & 4 Then WRot = Stream.ReadFloat() XRot = Stream.ReadFloat() YRot = Stream.ReadFloat() ZRot = Stream.ReadFloat() EndIf Return Self End Method Method Save(Stream:TStream) Stream.WriteInt(Frame) If Parent.Flags & 1 Then Stream.WriteFloat(XPos) Stream.WriteFloat(YPos) Stream.WriteFloat(ZPos) EndIf If Parent.Flags & 2 Then Stream.WriteFloat(XScale) Stream.WriteFloat(YScale) Stream.WriteFloat(ZScale) EndIf If Parent.Flags & 4 Then Stream.WriteFloat(WRot) Stream.WriteFloat(XRot) Stream.WriteFloat(YRot) Stream.WriteFloat(ZRot) EndIf End Method Method CalcSize:Int() Size = Parent.CalcKeySize() Return Size End Method End Type
Type TAnimChunk Field Size:Int Field Flags:Int Field Frames:Int Field FPS:Float Method Load:TAnimChunk(Stream:TStream) Flags = Stream.ReadInt() Frames = Stream.ReadInt() FPS = Stream.ReadFloat() Return Self End Method Method Save(Stream:TStream) Stream.WriteInt(TB3DModel.CHUNK_ANIM) Stream.WriteInt(Size) Stream.WriteInt(Flags) Stream.WriteInt(Frames) Stream.WriteFloat(FPS) End Method Method CalcSize:Int() Size = 3*4 Return Size End Method End Type
Type TNodeChunk Field Size:Int Field Name:String Field XPos :Float, YPos :Float, ZPos :Float Field XScale:Float, YScale:Float, ZScale:Float Field XRot :Float, YRot :Float, ZRot :Float, WRot:Float Field NodeKind:Int Field Mesh:TMeshChunk Field Bone:TBoneChunk Field AnimKeys:TList Field Children:TList Field Anim:TAnimChunk Method Load:TNodeChunk(Stream:TStream, Size:Int) Local EndPos:Int = Stream.Pos() + Size Name = ReadCString(Stream) XPos = Stream.ReadFloat() YPos = Stream.ReadFloat() ZPos = Stream.ReadFloat() XScale = Stream.ReadFloat() YScale = Stream.ReadFloat() ZScale = Stream.ReadFloat() WRot = Stream.ReadFloat() XRot = Stream.ReadFloat() YRot = Stream.ReadFloat() ZRot = Stream.ReadFloat() NodeKind = Stream.ReadInt() Local NodeSize:Int = Stream.ReadInt() Select NodeKind Case TB3DModel.CHUNK_MESH Mesh = New TMeshChunk.Load(Stream, NodeSize) Case TB3DModel.CHUNK_BONE Bone = New TBoneChunk.Load(Stream, NodeSize) Default Stream.SkipBytes(NodeSize) End Select Children = New TList AnimKeys = New TList While Stream.Pos() < EndPos Local Id:Int = Stream.ReadInt() Local S :Int = Stream.ReadInt() Select Id Case TB3DModel.CHUNK_ANIM If Anim Then Throw New TB3DLoaderException.Create("Duplicate anim chunk") Anim = New TAnimChunk.Load(Stream) Case TB3DModel.CHUNK_KEYS AnimKeys.AddLast(New TKeysChunk.Load(Stream, S)) Case TB3DModel.CHUNK_NODE Children.AddLast(New TNodeChunk.Load(Stream, S)) Default Stream.SkipBytes(S) End Select Wend Return Self End Method Method Save(Stream:TStream) Stream.WriteInt(TB3DModel.CHUNK_NODE) Stream.WriteInt(Size) WriteCString(Stream, Name) Stream.WriteFloat(XPos) Stream.WriteFloat(YPos) Stream.WriteFloat(ZPos) Stream.WriteFloat(XScale) Stream.WriteFloat(YScale) Stream.WriteFloat(ZScale) Stream.WriteFloat(WRot) Stream.WriteFloat(XRot) Stream.WriteFloat(YRot) Stream.WriteFloat(ZRot) Select NodeKind Case TB3DModel.CHUNK_MESH Mesh.Save(Stream) Case TB3DModel.CHUNK_BONE Bone.Save(Stream) End Select For Local Key:TKeysChunk = EachIn AnimKeys Key.Save(Stream) Next If Anim Then Anim.Save(Stream) For Local Node:TNodeChunk = EachIn Children Node.Save(Stream) Next End Method Method CalcSize:Int() Size = Name.Length + 1 + 10*4 Select NodeKind Case TB3DModel.CHUNK_MESH Size :+ Mesh.CalcSize() + 8 Case TB3DModel.CHUNK_BONE Size :+ Bone.CalcSize() + 8 End Select For Local Key:TKeysChunk = EachIn AnimKeys Size :+ Key.CalcSize() + 8 Next For Local Node:TNodeChunk = EachIn Children Size :+ Node.CalcSize() + 8 Next If Anim Then Size :+ Anim.CalcSize() + 8 Return Size End Method End Type
Anwendungsbeispiel: BlitzMax: [AUSKLAPPEN] [EINKLAPPEN] SuperStrict
Local Path:String = "MeinTollesModell.b3d" Local Instream:TStream = LittleEndianStream(ReadFile(Path)) Local Outstream:TStream = LittleEndianStream(WriteFile("Speichertest.b3d"))
Local Model:TB3DModel = New TB3DModel.Load(Instream)
Model.Save(Outstream)
Wichtig: Der Stream, der den Speicher-/Laderoutinen übergeben wird, muss ein LittleEndianStream sein.
Da man einen Stream und nicht direkt einen Pfad übergibt, kann man auch andere Streams als Filestreams benutzen - nützlich, wenn man Modelle direkt aus dem Speicher oder sogar dem Web laden will.
|