; ID: 1627
; Author: Noel - Banned
; Date: 2006-02-25 05:10:27
; Title: BSP Parser
; Description: Parses Q3 BSP files

Strict

Import Brl.Blitz
Import Brl.Stream
Import Brl.Bank
Import Brl.BankStream
'Import Cower.Math3D
Import "resource.bmx"

Private

Const MAGIC_BSP% = $50534249

Const BSP_ENTITIES% = 0
Const BSP_TEXTURES% = 1
Const BSP_PLANES% = 2
Const BSP_NODES% = 3
Const BSP_LEAVES% = 4
Const BSP_LEAFFACES% = 5
Const BSP_LEAFBRUSHES% = 6
Const BSP_MODELS% = 7
Const BSP_BRUSHES% = 8
Const BSP_BRUSHSIDES% = 9
Const BSP_VERTICES% = 10
Const BSP_MESHVERTS% = 11
Const BSP_EFFECTS% = 12
Const BSP_FACES% = 12
Const BSP_LIGHTMAPS% = 14
Const BSP_LIGHTVOLS% = 15
Const BSP_VIS% = 16

Type bsplump
    Field offset%
    Field length%
End Type

Public

Type bspdata
    Field entities:bspentity[]
    Field textures:bsptexture[]
    Field planes:bspplane[]
    Field nodes:bspnode[]
    Field leaves:bspleaf[]
    Field leaffaces%[]
    Field leafbrushes%[]
    Field models:bspmodel[]
    Field brushes:bspbrush[]
    Field brushsides:bspbrushside[]
    Field vertices:bspvertex[]
    Field meshverts%[]
    Field effects:bspeffect[]
    Field faces:bspface[]
    Field lightmaps:TBank[]
    Field lightvols:bsplightvol[]
    Field vis:bspvis
End Type

Function LoadBSP:bspdata( url:Object )
    Rem
    Local res:TBank = GetResource( StripExt(n)+".bsp" )
    If res = Null Then
        Debuglog "BSP file does not exist"
        Return
    EndIf
    
    If res.PeekInt( 0 ) <> MAGIC_BSP Or res.PeekInt( 4 ) <> $2E Then
        Debuglog "Resource is not a valid BSP, version: "+res.PeekInt( 4 )+"  magic: "+res.PeekInt( 0 )
        Return
    EndIf
    
    Local s:TStream = ReadStream( res )
    s.Seek( 8 )
    EndRem  ' code from indigo, won't work unless you've got the engine
    Local s:TStream = OpenStream( url, True, False )
    If s.ReadInt( ) <> MAGIC_BSP Then
        Debuglog "Resource is not a valid BSP - magic int didn't match"
        s.Close( )
        Return Null
    EndIf
    
    If s.ReadInt( ) <> $2E Then
        Debuglog "BSP version is not supported"
        s.Close( )
        Return Null
    EndIf
    
    s.Seek( 8 ) ' Just in case
    Local lumps:bsplump[17]
    For Local i:Int = 0 To 16
        lumps[i] = New bsplump
        s.ReadBytes( Varptr lumps[i].offset, 8 )
        Debuglog "  lump: "+i+"  offset: "+lumps[i].offset+"  length: "+lumps[i].length
    Next
    
    Local bsp:bspdata = New bspdata
    bsp.entities = BSP_ParseEntities( s, lumps[BSP_ENTITIES] )
    bsp.textures = BSP_ParseTextures( s, lumps[BSP_TEXTURES] )
    bsp.planes = BSP_ParsePlanes( s, lumps[BSP_PLANES] )
    bsp.nodes = BSP_ParseNodes( s, lumps[BSP_NODES] )
    bsp.leaves = BSP_ParseLeaves( s, lumps[BSP_LEAVES] )
    bsp.leaffaces = BSP_ParseLeafFaces( s, lumps[BSP_LEAFFACES] )
    bsp.leafbrushes = BSP_ParseLeafBrushes( s, lumps[BSP_LEAFBRUSHES] )
    bsp.models = BSP_ParseModels( s, lumps[BSP_MODELS] )
    bsp.brushes = BSP_ParseBrushes( s, lumps[BSP_BRUSHES] )
    bsp.brushsides = BSP_ParseBrushSides( s, lumps[BSP_BRUSHSIDES] )
    bsp.vertices = BSP_ParseVertices( s, lumps[BSP_VERTICES] )
    bsp.meshverts = BSP_ParseMeshVertices( s, lumps[BSP_MESHVERTS] )
    bsp.effects = BSP_ParseEffects( s, lumps[BSP_EFFECTS] )
    bsp.faces = BSP_ParseFaces( s, lumps[BSP_FACES] )
    bsp.lightmaps = BSP_ParseLightmaps( s, lumps[BSP_LIGHTMAPS] )
    bsp.lightvols = BSP_ParseLightVols( s, lumps[BSP_LIGHTVOLS] )
    bsp.vis = BSP_ParseVisData( s, lumps[BSP_VIS] )
    
    s.Close( )
    Return bsp
End Function

'-------------- entities
Type bspkey
    Field name$
    Field value$
End Type

Type bspentity
    Field keys:TList = New TList
    
    Method GetKey$( name$, _default$="" )
        For Local i:bspkey = EachIn keys
            If i.name = name Then Return i.value
        Next
        Local i:bspkey = New bspkey
        keys.AddLast( i )
        i.name = name
        i.value = _default
        Return _default
    End Method
End Type

Private

Function BSP_ParseEntities:bspentity[]( s:TStream, lump:bsplump )
    s.Seek( lump.offset )
    Local entityString$ = s.ReadString( lump.Length )
    Local entities:TList = New TList
    Local c$, state%, val$, nam$, ent:bspentity
    For Local i:Int = 0 To entityString.Length-1
        c = Chr(entityString[i])
        
        Select c
            Case "{"
                Debuglog "new entity opened"
                ent = New bspentity
                entities.AddLast( ent )
            Case "}"
                ent = Null
            Case "~q"
                If state < 3 Then
                    state :+ 1
                ElseIf state = 3 Then
                    Local k:bspkey = New bspkey
                    k.name = nam
                    k.value = val
                    ent.keys.AddLast( k )
                    Debuglog "  adding bspkey to entity, name: "+nam+"  value: "+val
                    k = Null
                    nam = ""
                    val = ""
                    state = 0
                EndIf
            Default
                Select state
                    Case 1
                        nam :+ c
                    Case 3
                        val :+ c
                End Select
        End Select
    Next
    Return bspentity[](entities.ToArray( ))
End Function


'------------- textures
Public
Type bsptexture
    Field name$
    Field flags%
    Field content%
End Type

Private
Function BSP_ParseTextures:bsptexture[]( s:TStream, lump:bsplump )
    s.Seek( lump.offset )
    Local textures:bsptexture[lump.length/72]
    
    For Local i:Int = 0 To textures.Length-1
        Local name$ = s.ReadString( 64 ).Trim( )
        Local flags% = s.ReadInt( )
        Local content% = s.ReadInt( )
        Local tex:bsptexture = New bsptexture
        tex.name = name
        tex.flags = flags
        tex.content = content
        textures[i] = tex
        Debuglog "  texture name: "+name+"  flags: "+flags+"  content: "+content
    Next
    Return textures
End Function


'------------------- planes
Public
Type bspplane
    Field x#,y#,z#,d#
End Type

Private
Function BSP_ParsePlanes:bspplane[]( s:TStream, lump:bsplump )
    s.Seek( lump.offset )
    Local planes:bspplane[lump.length/12]
    For Local i:Int = 0 To planes.Length-1
        planes[i] = New bspplane
        s.ReadBytes( Varptr planes[i].x, 12 )
        Debuglog "  plane: "+planes[i].x+", "+planes[i].y+", "+planes[i].z+", "+planes[i].d
    Next
    Return planes
End Function


'------------- nodes
Public
Type bspnode
    Field planei%
    Field childa%
    Field childb%
    Field minx%
    Field miny%
    Field minz%
    Field maxx%
    Field maxy%
    Field maxz%
End Type

Private
Function BSP_ParseNodes:bspnode[]( s:TStream, lump:bsplump )
    s.Seek( lump.offset )
    Local nodes:bspnode[lump.length/36]
    For Local i:Int = 0 To nodes.Length-1
        nodes[i] = New bspnode
        s.ReadBytes( Varptr nodes[i].planei, 36 )
    Next
    Return nodes
End Function


'------------- leaves
Public
Type bspleaf
    Field cluster%
    Field area%
    Field minx%, miny%, minz%
    Field maxx%, maxy%, maxz%
    Field face%, faces%
    Field brush%, brushes%
End Type

Private
Function BSP_ParseLeaves:bspleaf[]( s:TStream, lump:bsplump )
    s.Seek( lump.offset )
    Local leaves:bspleaf[lump.length/Sizeof(bspleaf)]
    For Local i:Int = 0 To leaves.Length-1
        leaves[i] = New bspleaf
        s.ReadBytes( Varptr leaves[i].cluster, Sizeof(bspleaf) )
    Next
    Return leaves
End Function


'---------------- leaf faces
Private
Function BSP_ParseLeafFaces%[]( s:TStream, lump:bsplump )
    Local arr%[lump.Length/4]
    s.Seek( lump.offset )
    s.ReadBytes( arr, lump.length )
    Return arr
End Function


'--------------- leaf brushes
Private
Function BSP_ParseLeafBrushes%[]( s:TStream, lump:bsplump )
    Local arr%[lump.Length/4]
    s.Seek( lump.offset )
    s.ReadBytes( arr, lump.length )
    Return arr
End Function


'--------------- models
Public
Type bspmodel
    Field minx#, miny#, minz#
    Field maxx#, maxy#, maxz#
    Field face%, faces%
    Field brush%, brushes%
End Type

Private
Function BSP_ParseModels:bspmodel[]( s:TStream, lump:bsplump )
    s.Seek( lump.offset )
    Local models:bspmodel[lump.length/Sizeof(bspmodel)]
    For Local i:Int = 0 To models.Length-1
        models[i] = New bspmodel
        s.ReadBytes( Varptr models[i].minx, Sizeof(bspmodel) )
    Next
    Return models
End Function


'---------------- brushes
Public
Type bspbrush
    Field side%, sides%
    Field texture%
End Type

Private
Function BSP_ParseBrushes:bspbrush[]( s:TStream, lump:bsplump )
    s.Seek( lump.offset )
    Local brushes:bspbrush[lump.length/Sizeof(bspbrush)]
    For Local i:Int = 0 To brushes.Length-1
        brushes[i] = New bspbrush
        s.ReadBytes( Varptr brushes[i].side, Sizeof(bspbrush) )
    Next
    Return brushes
End Function


'--------------- brush sides
Public
Type bspbrushside
    Field planei%
    Field texture%
End Type

Private
Function BSP_ParseBrushSides:bspbrushside[]( s:TStream, lump:bsplump )
    s.Seek( lump.offset )
    Local sides:bspbrushside[lump.length/Sizeof(bspbrushside)]
    For Local i:Int = 0 To sides.Length-1
        sides[i] = New bspbrushside
        s.ReadBytes( Varptr sides[i].planei, Sizeof(bspbrushside) )
    Next
    Return sides
End Function


'-------------- vertices
Public
Type bspvertex
    Field x#,y#,z#
    Field su#,sv#
    Field lu#,lv#
    Field nx#, ny#, nz#
    Field r@, g@, b@, a@
End Type

Private
Function BSP_ParseVertices:bspvertex[]( s:TStream, lump:bsplump )
    s.Seek( lump.offset )
    Local vertices:bspvertex[lump.length/Sizeof(bspvertex)]
    For Local i:Int = 0 To vertices.Length-1
        vertices[i] = New bspvertex
        s.ReadBytes( Varptr vertices[i].x, Sizeof(bspvertex) )
    Next
    Return vertices
End Function


'---------------- meshverts
Private
Function BSP_ParseMeshVertices%[]( s:TStream, lump:bsplump )
    Local arr%[lump.Length/4]
    s.Seek( lump.offset )
    s.ReadBytes( arr, lump.length )
    Return arr
End Function


'---------------- effects
Public
Type bspeffect
    Field name$
    Field brush%
    Field unknown%
End Type

Private
Function BSP_ParseEffects:bspeffect[]( s:TStream, lump:bsplump )
    s.Seek( lump.offset )
    Local effects:bspeffect[lump.length/Sizeof(bspeffect)]
    For Local i:Int = 0 To effects.Length-1
        effects[i] = New bspeffect
        effects[i].name = s.ReadString(64).Trim( )
        s.ReadBytes( Varptr effects[i].brush, 8 )
    Next
    Return effects
End Function


'------------------ faces
Public
Type bspface
    Field texture%
    Field effect%
    Field _type%
    Field vertex%
    Field vertices%
    Field meshvert%
    Field meshverts%
    Field lightmap%
    Field lmx%, lmy%
    Field lmw%, lmh%
    Field lmox#, lmoy#, lmoz#
    Field lmsx#, lmsy#, lmsz#
    Field lmtx#, lmty#, lmtz#
    Field nx#, ny#, nz#
    Field pw%, ph%
End Type

Private
Function BSP_ParseFaces:bspface[]( s:TStream, lump:bsplump )
    s.Seek( lump.offset )
    Local faces:bspface[lump.length/Sizeof(bspface)]
    For Local i:Int = 0 To faces.Length-1
        faces[i] = New bspface
        s.ReadBytes( Varptr faces[i].texture, Sizeof(bspface) )
    Next
    Return faces
End Function


'----------------- lightmaps
Private
Function BSP_ParseLightmaps:TBank[]( s:TStream, lump:bsplump )
    s.Seek( lump.offset )
    Local lm:TBank[lump.Length/49152]
    For Local i:Int = 0 To lm.Length - 1
        lm[i] = TBank.Create( 49152 )
        s.ReadBytes( lm[i].Buf( ), 49152 )
    Next
    Return lm
End Function


'----------------- light vols
Public
Type bsplightvol
    Field ar@, ag@, ab@
    Field dr@, dg@, db@
    Field du@, dv@
End Type

Private
Function BSP_ParseLightVols:bsplightvol[]( s:TStream, lump:bsplump )
    s.Seek( lump.offset )
    Local lv:bsplightvol[lump.length/8]
    For Local i:Int = 0 To lv.Length-1
        lv[i] = New bsplightvol
        s.ReadBytes( Varptr lv[i].ar, 8 )
    Next
    Return lv
End Function


'----------------- vis
Public
Type bspvis
    Field n_vecs%
    Field sz_vecs%
    Field vecs@ Ptr
    
    Method Delete( )
        If vecs Then MemFree( vecs )
    End Method
End Type

Private
Function BSP_ParseVisData:bspvis( s:TStream, lump:bsplump )
    s.Seek( lump.offset )
    Local vis:bspvis = New bspvis
    vis.n_vecs = s.ReadInt( )
    vis.sz_vecs = s.ReadInt( )
    Debuglog "  vis size: "+(vis.n_vecs*vis.sz_vecs)
    If vis.n_vecs > 0 And vis.sz_vecs > 0 Then
        vis.vecs = MemAlloc( vis.n_vecs*vis.sz_vecs )
        s.ReadBytes( vis.vecs, vis.n_vecs*vis.sz_vecs )
    EndIf
    Return vis
End Function
