*.b3d Lade-/Speicherroutinen

Übersicht BlitzMax, BlitzMax NG Codearchiv & Module

Neue Antwort erstellen

Noobody

Betreff: *.b3d Lade-/Speicherroutinen

BeitragMi, Apr 25, 2012 22:07
Antworten mit Zitat
Benutzer-Profile anzeigen
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]
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
'Unrecognized chunk - skip it
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
'Unrecognized chunk - skip it
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]
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)

'Modell verändern oder was auch immer

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.
Man is the best computer we can put aboard a spacecraft ... and the only one that can be mass produced with unskilled labor. -- Wernher von Braun

Neue Antwort erstellen


Übersicht BlitzMax, BlitzMax NG Codearchiv & Module

Gehe zu:

Powered by phpBB © 2001 - 2006, phpBB Group