; ============================================================
; Retro Tool PBI Header
; Datei: Formats/module_format_forma.pbi
; SPDX-License-Identifier: MIT
; Copyright (c) 2026 Jörg Burbach, joerg-burbach.de
; Lizenz: Siehe Formats/LICENSE_MIT.txt
; Version: FormA-26
;
; Funktion:
;   FormA/FMA1 memory-only spatial codec for compact Int32-mm geometry.
;
;   Changelog:
;   2.2 - Production hardening without bitstream changes: reserved core bytes,
;         complete scene subheaders and triangle index ranges are validated.
;   2.1 - Delta-varint coordinate loops advance one linear axis pointer
;         instead of multiplying the vertex index per value. No bitstream
;         change.
;   2.0 - Incompatible 24-byte core header. Triangle and PackBits flags
;         are derived from counts/methods; scene offset and size are
;         derived from the two packed stream lengths and file size.
;         Removes 8 bytes and all duplicated header state.
;   1.3 - Byteplane geometry encode/decode advances source/destination
;         positions linearly in the fixed 32-bit coordinate/index loops.
;         No bitstream change.
;   1.2 - MWReserve uses ReAllocateMemory() instead of manual
;         AllocateMemory+CopyMemory+FreeMemory growth. No bitstream
;         change; keeps the writer path in line with LeXA's 1.3 cleanup.
;   1.1 - Fourth coordinate candidate coordMethod=3 (order-2 delta-
;         varint, linear extrapolation from the last two values) beside
;         raw/PackBits/order-1-delta. Encoder picks the smallest per
;         file. -9.4% on smooth grid/terrain meshes, -1.4% on a sphere,
;         0% on scattered random points (no regression).
;
; Unterstuetzte Formate:
;   XYZ Int32 vertices and optional triangle index triples.
;
; Dateiformat:
;   FMA1 header plus compact delta coordinate and index streams.
;
; DLL-Faehigkeit:
;   Ja; standalone decoder/encoder, no file/UI/image/sound dependencies.
;
; Formatstatus:
;   lesen=ja | schreiben=ja | memory=ja | optimiert=ja | vollstaendig=FormA-26
; ============================================================

CompilerIf Not Defined(Common_Compression_PackBits, #PB_Module)
  XIncludeFile "../module_common_compression_packbits.pbi"
CompilerEndIf

CompilerIf Not Defined(WORM0_Media, #PB_Module)
  XIncludeFile "module_format_rfxl.pbi"
CompilerEndIf

CompilerIf Not Defined(Retro3D, #PB_Module)
  XIncludeFile "../3D + Rendering/module_3d_scene.pbi"
CompilerEndIf

CompilerIf Not Defined(WORM0_FormA, #PB_Module)

DeclareModule WORM0_FormA
  EnableExplicit

  #FMA1_VERSION = 26
  #FMA1_HEADER_SIZE = 24
  #FMA1_FLAG_TRIANGLES = 1
  #FMA1_FLAG_COORD_PACKBITS = 2
  #FMA1_FLAG_INDEX_PACKBITS = 4
  #FMA1_FLAG_SCENE = 8
  #FMA1_METHOD_BYTEPLANE_RAW = 0
  #FMA1_METHOD_BYTEPLANE_PACKBITS = 1
  #FMA1_METHOD_DELTA_VARINT = 2
  #FMA1_METHOD_DELTA2_VARINT = 3
  #FMA1_MAX_VERTICES = 4194304
  #FMA1_MAX_FACES = 4194304

  Structure FMA1Info
    valid.i
    version.i
    flags.i
    vertexCount.i
    faceCount.i
    coordBytes.i
    indexBytes.i
    coordPackedBytes.i
    indexPackedBytes.i
    coordMethod.i
    indexMethod.i
    sceneBytes.i
  EndStructure

  Declare.i DetectMemory(*Memory, MemorySize.i)
  Declare.i InspectMemory(*Memory, MemorySize.i, *Info.FMA1Info)
  Declare.i EncodeXYZI32Memory(*XYZ, VertexCount.i, *Indices = 0, FaceCount.i = 0, *OutSize.Integer = #Null)
  Declare.i DecodeXYZI32Memory(*Memory, MemorySize.i, *OutVertexCount.Integer = #Null, *OutFaceCount.Integer = #Null, *OutIndexMemory.Integer = #Null, *OutIndexBytes.Integer = #Null)
  Declare.s GetSpatial_FormAInfo()

  Declare.i EncodeSceneMemory(*Scene.Retro3D::Scene, *OutSize.Integer = #Null)
  Declare.i DecodeSceneMemory(*Memory, MemorySize.i, *Scene.Retro3D::Scene)
EndDeclareModule

Module WORM0_FormA
  EnableExplicit

  #FMA1_FACE_ATTR_NORMALS = 1
  #FMA1_FACE_ATTR_UVS = 2
  #FMA1_FACE_ATTR_MATERIALS = 4
  #FMA1_FACE_ATTR_COMMON_NORMALS = 8
  #FMA1_FACE_ATTR_COMMON_UVS = 16

  Structure MemWriter
    *memory
    size.i
    capacity.i
  EndStructure

  Declare.i MWVarI32(*mw.MemWriter, Value.i)
  Declare.i ReadVarI32Next(*Memory, Size.i, *Pos.Integer, *Out.Integer)

  Procedure.s GetSpatial_FormAInfo()
    ProcedureReturn "Module: FormA-26/FMA1; File: module_format_forma.pbi; memory-only Retro3D scene codec; core=axis/index 7-bit delta varint or byteplanes + PackBits; textures=RFXL-26"
  EndProcedure

  Procedure.i RangeOK(Offset.q, Bytes.q, Size.q)
    If Offset < 0 Or Bytes < 0 Or Offset > Size : ProcedureReturn #False : EndIf
    If Bytes > Size - Offset : ProcedureReturn #False : EndIf
    ProcedureReturn #True
  EndProcedure

  Procedure.i ReadU32LE(*Memory, Offset.i)
    ProcedureReturn (PeekA(*Memory + Offset) & $FF) | ((PeekA(*Memory + Offset + 1) & $FF) << 8) | ((PeekA(*Memory + Offset + 2) & $FF) << 16) | ((PeekA(*Memory + Offset + 3) & $FF) << 24)
  EndProcedure

  Procedure WriteU32LE(*Memory, Offset.i, Value.i)
    PokeA(*Memory + Offset, Value & $FF)
    PokeA(*Memory + Offset + 1, (Value >> 8) & $FF)
    PokeA(*Memory + Offset + 2, (Value >> 16) & $FF)
    PokeA(*Memory + Offset + 3, (Value >> 24) & $FF)
  EndProcedure

  Procedure.i MWReserve(*mw.MemWriter, AddBytes.i)
    Protected newCapacity.i, *newMemory
    If *mw = 0 Or AddBytes < 0 : ProcedureReturn #False : EndIf
    If *mw\size + AddBytes <= *mw\capacity : ProcedureReturn #True : EndIf
    newCapacity = *mw\capacity
    If newCapacity < 1024 : newCapacity = 1024 : EndIf
    While newCapacity < *mw\size + AddBytes
      newCapacity * 2
    Wend
    If *mw\memory
      *newMemory = ReAllocateMemory(*mw\memory, newCapacity)
    Else
      *newMemory = AllocateMemory(newCapacity)
    EndIf
    If *newMemory = 0 : ProcedureReturn #False : EndIf
    *mw\memory = *newMemory
    *mw\capacity = newCapacity
    ProcedureReturn #True
  EndProcedure

  Procedure.i MWData(*mw.MemWriter, *Data, Bytes.i)
    If Bytes < 0 Or (*Data = 0 And Bytes > 0) : ProcedureReturn #False : EndIf
    If MWReserve(*mw, Bytes) = #False : ProcedureReturn #False : EndIf
    If Bytes > 0 : CopyMemory(*Data, *mw\memory + *mw\size, Bytes) : EndIf
    *mw\size + Bytes
    ProcedureReturn #True
  EndProcedure

  Procedure.i MWByte(*mw.MemWriter, Value.i)
    If MWReserve(*mw, 1) = #False : ProcedureReturn #False : EndIf
    PokeA(*mw\memory + *mw\size, Value & $FF)
    *mw\size + 1
    ProcedureReturn #True
  EndProcedure

  Procedure.i MWU32(*mw.MemWriter, Value.i)
    If MWReserve(*mw, 4) = #False : ProcedureReturn #False : EndIf
    WriteU32LE(*mw\memory, *mw\size, Value)
    *mw\size + 4
    ProcedureReturn #True
  EndProcedure

  Procedure.i MWFloat(*mw.MemWriter, Value.f)
    If MWReserve(*mw, 4) = #False : ProcedureReturn #False : EndIf
    PokeF(*mw\memory + *mw\size, Value)
    *mw\size + 4
    ProcedureReturn #True
  EndProcedure

  Procedure.i MWString(*mw.MemWriter, Text.s)
    Protected bytes.i = StringByteLength(Text, #PB_UTF8), *utf8
    If MWVarI32(*mw, bytes) = #False : ProcedureReturn #False : EndIf
    If bytes <= 0 : ProcedureReturn #True : EndIf
    *utf8 = UTF8(Text)
    If *utf8 = 0 : ProcedureReturn #False : EndIf
    If MWData(*mw, *utf8, bytes) = #False
      FreeMemory(*utf8)
      ProcedureReturn #False
    EndIf
    FreeMemory(*utf8)
    ProcedureReturn #True
  EndProcedure

  Procedure.i MWMagic4(*mw.MemWriter, A.i, B.i, C.i, D.i)
    ProcedureReturn Bool(MWByte(*mw, A) And MWByte(*mw, B) And MWByte(*mw, C) And MWByte(*mw, D))
  EndProcedure

  Procedure.i DetectMemory(*Memory, MemorySize.i)
    If *Memory = 0 Or MemorySize < #FMA1_HEADER_SIZE : ProcedureReturn #False : EndIf
    If PeekS(*Memory, 4, #PB_Ascii) <> "FMA1" : ProcedureReturn #False : EndIf
    ProcedureReturn Bool((PeekA(*Memory + 4) & $FF) = #FMA1_VERSION)
  EndProcedure

  Procedure.i InspectMemory(*Memory, MemorySize.i, *Info.FMA1Info)
    Protected vertexCount.i, faceCount.i, coordBytes.i, indexBytes.i, flags.i, coordMethod.i, indexMethod.i, sceneOff.i, sceneBytes.i
    Protected sceneRaw.i, sceneMethod.i, scenePacked.i
    If *Info : FillMemory(*Info, SizeOf(FMA1Info), 0) : EndIf
    If DetectMemory(*Memory, MemorySize) = #False : ProcedureReturn #False : EndIf

    coordMethod = PeekA(*Memory + 5) & $FF
    indexMethod = PeekA(*Memory + 6) & $FF
    If (PeekA(*Memory + 7) & $FF) <> 0 : ProcedureReturn #False : EndIf
    If coordMethod < #FMA1_METHOD_BYTEPLANE_RAW Or coordMethod > #FMA1_METHOD_DELTA2_VARINT Or indexMethod < #FMA1_METHOD_BYTEPLANE_RAW Or indexMethod > #FMA1_METHOD_DELTA_VARINT
      ProcedureReturn #False
    EndIf
    vertexCount = ReadU32LE(*Memory, 8)
    faceCount = ReadU32LE(*Memory, 12)
    coordBytes = ReadU32LE(*Memory, 16)
    indexBytes = ReadU32LE(*Memory, 20)
    If vertexCount <= 0 Or vertexCount > #FMA1_MAX_VERTICES Or faceCount < 0 Or faceCount > #FMA1_MAX_FACES
      ProcedureReturn #False
    EndIf
    If coordBytes <= 0 Or coordBytes > vertexCount * 12 Or indexBytes < 0 Or indexBytes > faceCount * 12
      ProcedureReturn #False
    EndIf
    If faceCount = 0 And indexBytes <> 0
      ProcedureReturn #False
    EndIf
    sceneOff = #FMA1_HEADER_SIZE + coordBytes + indexBytes
    If RangeOK(#FMA1_HEADER_SIZE, coordBytes + indexBytes, MemorySize) = #False
      ProcedureReturn #False
    EndIf
    sceneBytes = MemorySize - sceneOff
    flags = Bool(faceCount > 0) * #FMA1_FLAG_TRIANGLES
    If coordMethod = #FMA1_METHOD_BYTEPLANE_PACKBITS : flags | #FMA1_FLAG_COORD_PACKBITS : EndIf
    If indexMethod = #FMA1_METHOD_BYTEPLANE_PACKBITS : flags | #FMA1_FLAG_INDEX_PACKBITS : EndIf
    If sceneBytes > 0
      flags | #FMA1_FLAG_SCENE
      If sceneBytes < 20 Or PeekS(*Memory + sceneOff, 4, #PB_Ascii) <> "FMAS" Or ReadU32LE(*Memory, sceneOff + 4) <> 1
        ProcedureReturn #False
      EndIf
      sceneRaw = ReadU32LE(*Memory, sceneOff + 8)
      sceneMethod = ReadU32LE(*Memory, sceneOff + 12)
      scenePacked = ReadU32LE(*Memory, sceneOff + 16)
      If sceneRaw <= 0 Or scenePacked <= 0 Or sceneMethod < #FMA1_METHOD_BYTEPLANE_RAW Or sceneMethod > #FMA1_METHOD_BYTEPLANE_PACKBITS Or scenePacked <> sceneBytes - 20
        ProcedureReturn #False
      EndIf
    EndIf

    If *Info
      *Info\valid = #True
      *Info\version = PeekA(*Memory + 4) & $FF
      *Info\flags = flags
      *Info\vertexCount = vertexCount
      *Info\faceCount = faceCount
      *Info\coordBytes = vertexCount * 12
      *Info\indexBytes = faceCount * 12
      *Info\coordPackedBytes = coordBytes
      *Info\indexPackedBytes = indexBytes
      *Info\coordMethod = coordMethod
      *Info\indexMethod = indexMethod
      *Info\sceneBytes = sceneBytes
    EndIf
    ProcedureReturn #True
  EndProcedure

  Procedure EncodeAxisFixed1BytePlane(*XYZ, VertexCount.i, Axis.i, *Dst)
    Protected i.i, pos.i, v.i, prev.i, delta.i
    Protected plane1.i = VertexCount, plane2.i = VertexCount * 2, plane3.i = VertexCount * 3
    pos = Axis * 4
    For i = 0 To VertexCount - 1
      v = PeekL(*XYZ + pos)
      If i = 0
        delta = v
      Else
        delta = v - prev
      EndIf
      prev = v
      PokeA(*Dst + i, delta & $FF)
      PokeA(*Dst + plane1 + i, (delta >> 8) & $FF)
      PokeA(*Dst + plane2 + i, (delta >> 16) & $FF)
      PokeA(*Dst + plane3 + i, (delta >> 24) & $FF)
      pos + 12
    Next
  EndProcedure

  Procedure DecodeAxisFixed1BytePlane(*Src, VertexCount.i, Axis.i, *XYZ)
    Protected i.i, pos.i, value.i, delta.i
    Protected plane1.i = VertexCount, plane2.i = VertexCount * 2, plane3.i = VertexCount * 3
    pos = Axis * 4
    For i = 0 To VertexCount - 1
      delta = (PeekA(*Src + i) & $FF) | ((PeekA(*Src + plane1 + i) & $FF) << 8) | ((PeekA(*Src + plane2 + i) & $FF) << 16) | ((PeekA(*Src + plane3 + i) & $FF) << 24)
      value + delta
      PokeL(*XYZ + pos, value)
      pos + 12
    Next
  EndProcedure

  Procedure EncodeI32DeltaBytePlane(*Values, Count.i, *Dst)
    Protected i.i, pos.i, v.i, prev.i, delta.i
    Protected plane1.i = Count, plane2.i = Count * 2, plane3.i = Count * 3
    For i = 0 To Count - 1
      v = PeekL(*Values + pos)
      If i = 0
        delta = v
      Else
        delta = v - prev
      EndIf
      prev = v
      PokeA(*Dst + i, delta & $FF)
      PokeA(*Dst + plane1 + i, (delta >> 8) & $FF)
      PokeA(*Dst + plane2 + i, (delta >> 16) & $FF)
      PokeA(*Dst + plane3 + i, (delta >> 24) & $FF)
      pos + 4
    Next
  EndProcedure

  Procedure DecodeI32DeltaBytePlane(*Src, Count.i, *Values)
    Protected i.i, pos.i, value.i, delta.i
    Protected plane1.i = Count, plane2.i = Count * 2, plane3.i = Count * 3
    For i = 0 To Count - 1
      delta = (PeekA(*Src + i) & $FF) | ((PeekA(*Src + plane1 + i) & $FF) << 8) | ((PeekA(*Src + plane2 + i) & $FF) << 16) | ((PeekA(*Src + plane3 + i) & $FF) << 24)
      value + delta
      PokeL(*Values + pos, value)
      pos + 4
    Next
  EndProcedure

  Procedure.q ZigZag32(Value.i)
    ProcedureReturn (Value << 1) ! (Value >> 31)
  EndProcedure

  Procedure.i UnZigZag32(Value.q)
    If Value & 1
      ProcedureReturn -((Value >> 1) + 1)
    EndIf
    ProcedureReturn Value >> 1
  EndProcedure

  Procedure.i VarI32Reserve(*Dst, *Pos.Integer, Capacity.i, AddBytes.i)
    ProcedureReturn Bool(*Pos\i + AddBytes <= Capacity)
  EndProcedure

  Procedure.i VarI32WriteDelta(*Dst, *Pos.Integer, Capacity.i, Delta.i)
    Protected z.q = ZigZag32(Delta)
    Repeat
      If VarI32Reserve(*Dst, *Pos, Capacity, 1) = #False : ProcedureReturn #False : EndIf
      If z >= 128
        PokeA(*Dst + *Pos\i, (z & $7F) | $80)
      Else
        PokeA(*Dst + *Pos\i, z & $7F)
      EndIf
      *Pos\i + 1
      z >> 7
    Until z = 0
    ProcedureReturn #True
  EndProcedure

  Procedure.i VarI32ReadDelta(*Src, Size.i, *Pos.Integer, *Out.Integer)
    Protected shift.i, b.i, z.q
    Repeat
      If *Pos\i >= Size Or shift > 28 : ProcedureReturn #False : EndIf
      b = PeekA(*Src + *Pos\i) & $FF
      *Pos\i + 1
      z | ((b & $7F) << shift)
      shift + 7
    Until (b & $80) = 0
    *Out\i = UnZigZag32(z)
    ProcedureReturn #True
  EndProcedure

  Procedure.i MWVarI32(*mw.MemWriter, Value.i)
    Protected pos.Integer
    Dim bytes.b(4)
    pos\i = 0
    If VarI32WriteDelta(@bytes(0), @pos, 5, Value) = #False : ProcedureReturn #False : EndIf
    ProcedureReturn MWData(*mw, @bytes(0), pos\i)
  EndProcedure

  Procedure.i ReadVarI32Next(*Memory, Size.i, *Pos.Integer, *Out.Integer)
    ProcedureReturn VarI32ReadDelta(*Memory, Size, *Pos, *Out)
  EndProcedure

  Procedure.s FMA1ReadString(*Memory, Size.i, *Pos.Integer)
    Protected bytes.Integer, out.s
    If ReadVarI32Next(*Memory, Size, *Pos, @bytes) = #False Or bytes\i < 0
      *Pos\i = -1
      ProcedureReturn ""
    EndIf
    If *Pos\i + bytes\i > Size : *Pos\i = -1 : ProcedureReturn "" : EndIf
    If bytes\i = 0
      out = ""
    Else
      out = PeekS(*Memory + *Pos\i, bytes\i, #PB_UTF8 | #PB_ByteLength)
    EndIf
    *Pos\i + bytes\i
    ProcedureReturn out
  EndProcedure

  Procedure.i EncodeAxisVarI32(*XYZ, VertexCount.i, Axis.i, *Dst, *Pos.Integer, Capacity.i)
    Protected i.i, v.i, prev.i, delta.i, xyzPos.i = Axis * 4
    For i = 0 To VertexCount - 1
      v = PeekL(*XYZ + xyzPos)
      xyzPos + 12
      If i = 0 : delta = v : Else : delta = v - prev : EndIf
      prev = v
      If VarI32WriteDelta(*Dst, *Pos, Capacity, delta) = #False : ProcedureReturn #False : EndIf
    Next
    ProcedureReturn #True
  EndProcedure

  Procedure.i DecodeAxisVarI32(*Src, Size.i, *Pos.Integer, VertexCount.i, Axis.i, *XYZ)
    Protected i.i, value.i, xyzPos.i = Axis * 4, delta.Integer
    For i = 0 To VertexCount - 1
      If VarI32ReadDelta(*Src, Size, *Pos, @delta) = #False : ProcedureReturn #False : EndIf
      value + delta\i
      PokeL(*XYZ + xyzPos, value)
      xyzPos + 12
    Next
    ProcedureReturn #True
  EndProcedure

  Procedure.i EncodeXYZVarI32(*XYZ, VertexCount.i, *OutSize.Integer)
    Protected capacity.i = VertexCount * 15, pos.Integer, *out
    If *OutSize : *OutSize\i = 0 : EndIf
    *out = AllocateMemory(capacity)
    If *out = 0 : ProcedureReturn 0 : EndIf
    If EncodeAxisVarI32(*XYZ, VertexCount, 0, *out, @pos, capacity) = #False Or EncodeAxisVarI32(*XYZ, VertexCount, 1, *out, @pos, capacity) = #False Or EncodeAxisVarI32(*XYZ, VertexCount, 2, *out, @pos, capacity) = #False
      FreeMemory(*out)
      ProcedureReturn 0
    EndIf
    If *OutSize : *OutSize\i = pos\i : EndIf
    ProcedureReturn *out
  EndProcedure

  Procedure.i DecodeXYZVarI32(*Src, Size.i, VertexCount.i, *XYZ)
    Protected pos.Integer
    If DecodeAxisVarI32(*Src, Size, @pos, VertexCount, 0, *XYZ) = #False : ProcedureReturn #False : EndIf
    If DecodeAxisVarI32(*Src, Size, @pos, VertexCount, 1, *XYZ) = #False : ProcedureReturn #False : EndIf
    If DecodeAxisVarI32(*Src, Size, @pos, VertexCount, 2, *XYZ) = #False : ProcedureReturn #False : EndIf
    ProcedureReturn Bool(pos\i = Size)
  EndProcedure

  ; Second-order (linear-extrapolation) delta varint: predicts each vertex
  ; from the previous two on the same axis (p = 2*prev1 - prev2, same
  ; predictor RAU's order-2 fixed predictor uses) instead of just carrying
  ; the previous value forward. Wins when consecutive vertices lie roughly
  ; on a line/arc - typical for grid, terrain or parametric (sphere/tube)
  ; meshes exported in scan order; a pure order-1 delta already handles
  ; constant-velocity runs, order-2 additionally handles constant-curvature
  ; runs. Falls back to the plain order-1 residual for the first two
  ; vertices, where no second history sample exists yet.
  Procedure.i EncodeAxisVarI32Order2(*XYZ, VertexCount.i, Axis.i, *Dst, *Pos.Integer, Capacity.i)
    Protected i.i, v.i, p1.i, p2.i, pred.i, res.i, xyzPos.i = Axis * 4
    For i = 0 To VertexCount - 1
      v = PeekL(*XYZ + xyzPos)
      xyzPos + 12
      Select i
        Case 0 : res = v
        Case 1 : res = v - p1
        Default
          pred = 2 * p1 - p2
          res = v - pred
      EndSelect
      If VarI32WriteDelta(*Dst, *Pos, Capacity, res) = #False : ProcedureReturn #False : EndIf
      p2 = p1 : p1 = v
    Next
    ProcedureReturn #True
  EndProcedure

  Procedure.i DecodeAxisVarI32Order2(*Src, Size.i, *Pos.Integer, VertexCount.i, Axis.i, *XYZ)
    Protected i.i, v.i, p1.i, p2.i, pred.i, xyzPos.i = Axis * 4, res.Integer
    For i = 0 To VertexCount - 1
      If VarI32ReadDelta(*Src, Size, *Pos, @res) = #False : ProcedureReturn #False : EndIf
      Select i
        Case 0 : v = res\i
        Case 1 : v = p1 + res\i
        Default
          pred = 2 * p1 - p2
          v = pred + res\i
      EndSelect
      PokeL(*XYZ + xyzPos, v)
      xyzPos + 12
      p2 = p1 : p1 = v
    Next
    ProcedureReturn #True
  EndProcedure

  Procedure.i EncodeXYZVarI32Order2(*XYZ, VertexCount.i, *OutSize.Integer)
    Protected capacity.i = VertexCount * 15, pos.Integer, *out
    If *OutSize : *OutSize\i = 0 : EndIf
    *out = AllocateMemory(capacity)
    If *out = 0 : ProcedureReturn 0 : EndIf
    If EncodeAxisVarI32Order2(*XYZ, VertexCount, 0, *out, @pos, capacity) = #False Or EncodeAxisVarI32Order2(*XYZ, VertexCount, 1, *out, @pos, capacity) = #False Or EncodeAxisVarI32Order2(*XYZ, VertexCount, 2, *out, @pos, capacity) = #False
      FreeMemory(*out)
      ProcedureReturn 0
    EndIf
    If *OutSize : *OutSize\i = pos\i : EndIf
    ProcedureReturn *out
  EndProcedure

  Procedure.i DecodeXYZVarI32Order2(*Src, Size.i, VertexCount.i, *XYZ)
    Protected pos.Integer
    If DecodeAxisVarI32Order2(*Src, Size, @pos, VertexCount, 0, *XYZ) = #False : ProcedureReturn #False : EndIf
    If DecodeAxisVarI32Order2(*Src, Size, @pos, VertexCount, 1, *XYZ) = #False : ProcedureReturn #False : EndIf
    If DecodeAxisVarI32Order2(*Src, Size, @pos, VertexCount, 2, *XYZ) = #False : ProcedureReturn #False : EndIf
    ProcedureReturn Bool(pos\i = Size)
  EndProcedure

  Procedure.i EncodeI32VarI32(*Values, Count.i, *OutSize.Integer)
    Protected capacity.i = Count * 5, pos.Integer, *out, i.i, v.i, prev.i, delta.i, valuePos.i
    If *OutSize : *OutSize\i = 0 : EndIf
    *out = AllocateMemory(capacity)
    If *out = 0 : ProcedureReturn 0 : EndIf
    For i = 0 To Count - 1
      v = PeekL(*Values + valuePos)
      valuePos + 4
      If i = 0 : delta = v : Else : delta = v - prev : EndIf
      prev = v
      If VarI32WriteDelta(*out, @pos, capacity, delta) = #False
        FreeMemory(*out)
        ProcedureReturn 0
      EndIf
    Next
    If *OutSize : *OutSize\i = pos\i : EndIf
    ProcedureReturn *out
  EndProcedure

  Procedure.i DecodeI32VarI32(*Src, Size.i, Count.i, *Values)
    Protected pos.Integer, i.i, value.i, valuePos.i, delta.Integer
    For i = 0 To Count - 1
      If VarI32ReadDelta(*Src, Size, @pos, @delta) = #False : ProcedureReturn #False : EndIf
      value + delta\i
      PokeL(*Values + valuePos, value)
      valuePos + 4
    Next
    ProcedureReturn Bool(pos\i = Size)
  EndProcedure

  Procedure.i PackStream(*Raw, RawBytes.i, *OutSize.Integer, *OutMethod.Integer)
    Protected maxBytes.i, packedBytes.i, *packed, *out
    If *OutSize : *OutSize\i = 0 : EndIf
    If *OutMethod : *OutMethod\i = #FMA1_METHOD_BYTEPLANE_RAW : EndIf
    If *Raw = 0 Or RawBytes <= 0 : ProcedureReturn 0 : EndIf
    maxBytes = RawBytes + RawBytes / 128 + 256
    *packed = AllocateMemory(maxBytes)
    If *packed
      packedBytes = Common_Compression_PackBits::Compress(*Raw, RawBytes, *packed, maxBytes)
      If packedBytes > 0 And packedBytes < RawBytes
        *out = AllocateMemory(packedBytes)
        If *out = 0 : FreeMemory(*packed) : ProcedureReturn 0 : EndIf
        CopyMemory(*packed, *out, packedBytes)
        FreeMemory(*packed)
        If *OutSize : *OutSize\i = packedBytes : EndIf
        If *OutMethod : *OutMethod\i = #FMA1_METHOD_BYTEPLANE_PACKBITS : EndIf
        ProcedureReturn *out
      EndIf
      FreeMemory(*packed)
    EndIf
    *out = AllocateMemory(RawBytes)
    If *out = 0 : ProcedureReturn 0 : EndIf
    CopyMemory(*Raw, *out, RawBytes)
    If *OutSize : *OutSize\i = RawBytes : EndIf
    ProcedureReturn *out
  EndProcedure

  Procedure.i UnpackStream(*Packed, PackedBytes.i, RawBytes.i, Method.i)
    Protected *out, decoded.i
    If *Packed = 0 Or PackedBytes <= 0 Or RawBytes <= 0 : ProcedureReturn 0 : EndIf
    *out = AllocateMemory(RawBytes)
    If *out = 0 : ProcedureReturn 0 : EndIf
    If Method = #FMA1_METHOD_BYTEPLANE_PACKBITS
      decoded = Common_Compression_PackBits::Decompress(*Packed, PackedBytes, *out, RawBytes)
      If decoded <> RawBytes
        FreeMemory(*out)
        ProcedureReturn 0
      EndIf
    ElseIf Method = #FMA1_METHOD_BYTEPLANE_RAW
      If PackedBytes <> RawBytes
        FreeMemory(*out)
        ProcedureReturn 0
      EndIf
      CopyMemory(*Packed, *out, RawBytes)
    Else
      FreeMemory(*out)
      ProcedureReturn 0
    EndIf
    ProcedureReturn *out
  EndProcedure

  Procedure.i EncodeXYZI32Memory(*XYZ, VertexCount.i, *Indices = 0, FaceCount.i = 0, *OutSize.Integer = #Null)
    Protected coordBytes.i, indexBytes.i, total.i, *out, coordOff.i, indexOff.i, flags.i
    Protected *coordRaw, *indexRaw, *coordPayload, *indexPayload, *candidate
    Protected coordPayloadBytes.Integer, indexPayloadBytes.Integer, coordMethod.Integer, indexMethod.Integer, candidateBytes.Integer
    Protected i.i, index.i
    If *OutSize : *OutSize\i = 0 : EndIf
    If *XYZ = 0 Or VertexCount <= 0 Or VertexCount > #FMA1_MAX_VERTICES Or FaceCount < 0 Or FaceCount > #FMA1_MAX_FACES : ProcedureReturn 0 : EndIf
    If FaceCount > 0 And *Indices = 0 : ProcedureReturn 0 : EndIf
    For i = 0 To FaceCount * 3 - 1
      index = PeekL(*Indices + i * 4)
      If index < 0 Or index >= VertexCount : ProcedureReturn 0 : EndIf
    Next

    coordBytes = VertexCount * 12
    indexBytes = FaceCount * 12
    *coordRaw = AllocateMemory(coordBytes)
    If *coordRaw = 0 : ProcedureReturn 0 : EndIf
    EncodeAxisFixed1BytePlane(*XYZ, VertexCount, 0, *coordRaw)
    EncodeAxisFixed1BytePlane(*XYZ, VertexCount, 1, *coordRaw + VertexCount * 4)
    EncodeAxisFixed1BytePlane(*XYZ, VertexCount, 2, *coordRaw + VertexCount * 8)
    *coordPayload = PackStream(*coordRaw, coordBytes, @coordPayloadBytes, @coordMethod)
    FreeMemory(*coordRaw)
    If *coordPayload = 0 : ProcedureReturn 0 : EndIf
    *candidate = EncodeXYZVarI32(*XYZ, VertexCount, @candidateBytes)
    If *candidate
      If candidateBytes\i < coordPayloadBytes\i
        FreeMemory(*coordPayload)
        *coordPayload = *candidate
        coordPayloadBytes\i = candidateBytes\i
        coordMethod\i = #FMA1_METHOD_DELTA_VARINT
      Else
        FreeMemory(*candidate)
      EndIf
    EndIf
    *candidate = EncodeXYZVarI32Order2(*XYZ, VertexCount, @candidateBytes)
    If *candidate
      If candidateBytes\i < coordPayloadBytes\i
        FreeMemory(*coordPayload)
        *coordPayload = *candidate
        coordPayloadBytes\i = candidateBytes\i
        coordMethod\i = #FMA1_METHOD_DELTA2_VARINT
      Else
        FreeMemory(*candidate)
      EndIf
    EndIf

    If indexBytes > 0
      *indexRaw = AllocateMemory(indexBytes)
      If *indexRaw = 0 : FreeMemory(*coordPayload) : ProcedureReturn 0 : EndIf
      EncodeI32DeltaBytePlane(*Indices, FaceCount * 3, *indexRaw)
      *indexPayload = PackStream(*indexRaw, indexBytes, @indexPayloadBytes, @indexMethod)
      FreeMemory(*indexRaw)
      If *indexPayload = 0 : FreeMemory(*coordPayload) : ProcedureReturn 0 : EndIf
      *candidate = EncodeI32VarI32(*Indices, FaceCount * 3, @candidateBytes)
      If *candidate
        If candidateBytes\i < indexPayloadBytes\i
          FreeMemory(*indexPayload)
          *indexPayload = *candidate
          indexPayloadBytes\i = candidateBytes\i
          indexMethod\i = #FMA1_METHOD_DELTA_VARINT
        Else
          FreeMemory(*candidate)
        EndIf
      EndIf
    EndIf

    flags = Bool(FaceCount > 0) * #FMA1_FLAG_TRIANGLES
    If coordMethod\i = #FMA1_METHOD_BYTEPLANE_PACKBITS : flags | #FMA1_FLAG_COORD_PACKBITS : EndIf
    If indexMethod\i = #FMA1_METHOD_BYTEPLANE_PACKBITS : flags | #FMA1_FLAG_INDEX_PACKBITS : EndIf
    total = #FMA1_HEADER_SIZE + coordPayloadBytes\i + indexPayloadBytes\i
    *out = AllocateMemory(total)
    If *out = 0
      FreeMemory(*coordPayload)
      If *indexPayload : FreeMemory(*indexPayload) : EndIf
      ProcedureReturn 0
    EndIf

    PokeS(*out, "FMA1", 4, #PB_Ascii)
    PokeA(*out + 4, #FMA1_VERSION)
    PokeA(*out + 5, coordMethod\i)
    PokeA(*out + 6, indexMethod\i)
    WriteU32LE(*out, 8, VertexCount)
    WriteU32LE(*out, 12, FaceCount)
    WriteU32LE(*out, 16, coordPayloadBytes\i)
    WriteU32LE(*out, 20, indexPayloadBytes\i)

    coordOff = #FMA1_HEADER_SIZE
    CopyMemory(*coordPayload, *out + coordOff, coordPayloadBytes\i)
    FreeMemory(*coordPayload)

    If indexPayloadBytes\i > 0
      indexOff = coordOff + coordPayloadBytes\i
      CopyMemory(*indexPayload, *out + indexOff, indexPayloadBytes\i)
      FreeMemory(*indexPayload)
    EndIf
    If *OutSize : *OutSize\i = total : EndIf
    ProcedureReturn *out
  EndProcedure

  Procedure.i DecodeXYZI32Memory(*Memory, MemorySize.i, *OutVertexCount.Integer = #Null, *OutFaceCount.Integer = #Null, *OutIndexMemory.Integer = #Null, *OutIndexBytes.Integer = #Null)
    Protected info.FMA1Info, *xyz, *indices, *coordRaw, *indexRaw, coordOff.i, indexOff.i, i.i, index.i
    If *OutVertexCount : *OutVertexCount\i = 0 : EndIf
    If *OutFaceCount : *OutFaceCount\i = 0 : EndIf
    If *OutIndexMemory : *OutIndexMemory\i = 0 : EndIf
    If *OutIndexBytes : *OutIndexBytes\i = 0 : EndIf
    If InspectMemory(*Memory, MemorySize, @info) = #False : ProcedureReturn 0 : EndIf

    *xyz = AllocateMemory(info\coordBytes)
    If *xyz = 0 : ProcedureReturn 0 : EndIf
    coordOff = #FMA1_HEADER_SIZE
    If info\coordMethod = #FMA1_METHOD_DELTA_VARINT
      If DecodeXYZVarI32(*Memory + coordOff, info\coordPackedBytes, info\vertexCount, *xyz) = #False
        FreeMemory(*xyz)
        ProcedureReturn 0
      EndIf
    ElseIf info\coordMethod = #FMA1_METHOD_DELTA2_VARINT
      If DecodeXYZVarI32Order2(*Memory + coordOff, info\coordPackedBytes, info\vertexCount, *xyz) = #False
        FreeMemory(*xyz)
        ProcedureReturn 0
      EndIf
    Else
      *coordRaw = UnpackStream(*Memory + coordOff, info\coordPackedBytes, info\coordBytes, info\coordMethod)
      If *coordRaw = 0 : FreeMemory(*xyz) : ProcedureReturn 0 : EndIf
      DecodeAxisFixed1BytePlane(*coordRaw, info\vertexCount, 0, *xyz)
      DecodeAxisFixed1BytePlane(*coordRaw + info\vertexCount * 4, info\vertexCount, 1, *xyz)
      DecodeAxisFixed1BytePlane(*coordRaw + info\vertexCount * 8, info\vertexCount, 2, *xyz)
      FreeMemory(*coordRaw)
    EndIf

    If info\indexBytes > 0 And *OutIndexMemory
      indexOff = coordOff + info\coordPackedBytes
      *indices = AllocateMemory(info\indexBytes)
      If *indices = 0 : FreeMemory(*xyz) : ProcedureReturn 0 : EndIf
      If info\indexMethod = #FMA1_METHOD_DELTA_VARINT
        If DecodeI32VarI32(*Memory + indexOff, info\indexPackedBytes, info\faceCount * 3, *indices) = #False
          FreeMemory(*indices)
          FreeMemory(*xyz)
          ProcedureReturn 0
        EndIf
      Else
        *indexRaw = UnpackStream(*Memory + indexOff, info\indexPackedBytes, info\indexBytes, info\indexMethod)
        If *indexRaw = 0 : FreeMemory(*indices) : FreeMemory(*xyz) : ProcedureReturn 0 : EndIf
        DecodeI32DeltaBytePlane(*indexRaw, info\faceCount * 3, *indices)
        FreeMemory(*indexRaw)
      EndIf
      For i = 0 To info\faceCount * 3 - 1
        index = PeekL(*indices + i * 4)
        If index < 0 Or index >= info\vertexCount
          FreeMemory(*indices)
          FreeMemory(*xyz)
          ProcedureReturn 0
        EndIf
      Next
      *OutIndexMemory\i = *indices
      If *OutIndexBytes : *OutIndexBytes\i = info\indexBytes : EndIf
    EndIf
    If *OutVertexCount : *OutVertexCount\i = info\vertexCount : EndIf
    If *OutFaceCount : *OutFaceCount\i = info\faceCount : EndIf
    ProcedureReturn *xyz
  EndProcedure

    Procedure.i MWVec3(*mw.MemWriter, *v.Retro3D::Vec3)
      ProcedureReturn Bool(MWFloat(*mw, *v\x) And MWFloat(*mw, *v\y) And MWFloat(*mw, *v\z))
    EndProcedure

    Procedure.i MWVec2(*mw.MemWriter, *v.Retro3D::Vec2)
      ProcedureReturn Bool(MWFloat(*mw, *v\u) And MWFloat(*mw, *v\v))
    EndProcedure

    Procedure.i ReadVec3(*Memory, Size.i, *Pos.Integer, *v.Retro3D::Vec3)
      If *Pos\i + 12 > Size : ProcedureReturn #False : EndIf
      *v\x = PeekF(*Memory + *Pos\i) : *Pos\i + 4
      *v\y = PeekF(*Memory + *Pos\i) : *Pos\i + 4
      *v\z = PeekF(*Memory + *Pos\i) : *Pos\i + 4
      ProcedureReturn #True
    EndProcedure

    Procedure.i ReadVec2(*Memory, Size.i, *Pos.Integer, *v.Retro3D::Vec2)
      If *Pos\i + 8 > Size : ProcedureReturn #False : EndIf
      *v\u = PeekF(*Memory + *Pos\i) : *Pos\i + 4
      *v\v = PeekF(*Memory + *Pos\i) : *Pos\i + 4
      ProcedureReturn #True
    EndProcedure

    Procedure.i BuildSceneRaw(*Scene.Retro3D::Scene, *OutSize.Integer)
      Protected mw.MemWriter, materials.i, textures.i, lights.i, meshes.i, regions.i, textureBytes.Integer, *texture
      Protected faceAttrFlags.i, commonMaterial.i, commonN1.i, commonN2.i, commonN3.i, commonUV1.i, commonUV2.i, commonUV3.i, firstFace.i
      If *OutSize : *OutSize\i = 0 : EndIf
      If *Scene = 0 : ProcedureReturn 0 : EndIf
      materials = ListSize(*Scene\materials()) : textures = ListSize(*Scene\textures()) : lights = ListSize(*Scene\lights()) : meshes = ListSize(*Scene\meshes()) : regions = ListSize(*Scene\spatialRegions())
      If MWMagic4(@mw, 'S', 'C', 'N', '2') = #False : Goto fail : EndIf
      If MWString(@mw, *Scene\name) = #False Or MWU32(@mw, *Scene\width) = #False Or MWU32(@mw, *Scene\height) = #False : Goto fail : EndIf
      If MWVec3(@mw, @*Scene\camera\origin) = #False Or MWVec3(@mw, @*Scene\camera\target) = #False Or MWFloat(@mw, *Scene\camera\screenDistance) = #False Or MWFloat(@mw, *Scene\camera\nearPlane) = #False : Goto fail : EndIf
      If MWVec3(@mw, @*Scene\light\direction) = #False Or MWVec3(@mw, @*Scene\light\position) = #False : Goto fail : EndIf
      If MWU32(@mw, *Scene\light\hasPosition) = #False Or MWFloat(@mw, *Scene\light\intensity) = #False Or MWU32(@mw, *Scene\light\color) = #False Or MWU32(@mw, *Scene\light\isSpotlight) = #False Or MWFloat(@mw, *Scene\light\radius) = #False Or MWFloat(@mw, *Scene\light\falloff) = #False Or MWFloat(@mw, *Scene\light\tightness) = #False : Goto fail : EndIf
      If MWFloat(@mw, *Scene\ambientLight) = #False Or MWU32(@mw, *Scene\ambientColor) = #False Or MWU32(@mw, *Scene\maxRecursionDepth) = #False Or MWU32(@mw, *Scene\backgroundMode) = #False Or MWU32(@mw, *Scene\backgroundColor1) = #False Or MWU32(@mw, *Scene\backgroundColor2) = #False Or MWU32(@mw, *Scene\previewBackfaceCulling) = #False Or MWU32(@mw, *Scene\preferredRenderMode) = #False : Goto fail : EndIf
      If MWU32(@mw, materials) = #False Or MWU32(@mw, textures) = #False Or MWU32(@mw, lights) = #False Or MWU32(@mw, meshes) = #False Or MWU32(@mw, regions) = #False : Goto fail : EndIf
      ForEach *Scene\materials()
        If MWString(@mw, *Scene\materials()\name) = #False Or MWU32(@mw, *Scene\materials()\paletteIndex) = #False Or MWFloat(@mw, *Scene\materials()\ambient) = #False Or MWFloat(@mw, *Scene\materials()\diffuse) = #False Or MWFloat(@mw, *Scene\materials()\specular) = #False Or MWFloat(@mw, *Scene\materials()\shininess) = #False Or MWFloat(@mw, *Scene\materials()\reflective) = #False Or MWFloat(@mw, *Scene\materials()\transparency) = #False Or MWFloat(@mw, *Scene\materials()\refractiveIndex) = #False Or MWFloat(@mw, *Scene\materials()\emission) = #False : Goto fail : EndIf
        If MWU32(@mw, *Scene\materials()\textureType) = #False Or MWString(@mw, *Scene\materials()\texturePath) = #False Or MWU32(@mw, *Scene\materials()\textureSlot) = #False Or MWFloat(@mw, *Scene\materials()\textureScale) = #False Or MWFloat(@mw, *Scene\materials()\textureStrength) = #False Or MWU32(@mw, *Scene\materials()\color) = #False Or MWU32(@mw, *Scene\materials()\color2) = #False Or MWFloat(@mw, *Scene\materials()\colorMix) = #False Or MWU32(@mw, *Scene\materials()\colorModel) = #False : Goto fail : EndIf
      Next
      ForEach *Scene\textures()
        *texture = 0 : textureBytes\i = 0
        If *Scene\textures()\pixels And *Scene\textures()\width > 0 And *Scene\textures()\height > 0 And *Scene\textures()\format = Retro3D::#TextureMemory_RGBA32
          *texture = WORM0_Media::EncodeRFXLFromRGBA32(*Scene\textures()\pixels, *Scene\textures()\width, *Scene\textures()\height, @textureBytes, WORM0_Media::#rp_image_plugin_rfxl, WORM0_Media::#Quality_Lossless, *Scene\textures()\pitch, 4)
        EndIf
        If MWString(@mw, *Scene\textures()\name) = #False Or MWU32(@mw, *Scene\textures()\width) = #False Or MWU32(@mw, *Scene\textures()\height) = #False Or MWU32(@mw, *Scene\textures()\format) = #False Or MWU32(@mw, *Scene\textures()\wrapMode) = #False Or MWU32(@mw, *Scene\textures()\filterMode) = #False Or MWU32(@mw, *Scene\textures()\alphaThreshold) = #False Or MWU32(@mw, textureBytes\i) = #False : If *texture : FreeMemory(*texture) : EndIf : Goto fail : EndIf
        If textureBytes\i > 0
          If MWData(@mw, *texture, textureBytes\i) = #False : FreeMemory(*texture) : Goto fail : EndIf
          FreeMemory(*texture)
        EndIf
      Next
      ForEach *Scene\lights()
        If MWVec3(@mw, @*Scene\lights()\direction) = #False Or MWVec3(@mw, @*Scene\lights()\position) = #False : Goto fail : EndIf
        If MWU32(@mw, *Scene\lights()\hasPosition) = #False Or MWFloat(@mw, *Scene\lights()\intensity) = #False Or MWU32(@mw, *Scene\lights()\color) = #False Or MWU32(@mw, *Scene\lights()\isSpotlight) = #False Or MWFloat(@mw, *Scene\lights()\radius) = #False Or MWFloat(@mw, *Scene\lights()\falloff) = #False Or MWFloat(@mw, *Scene\lights()\tightness) = #False : Goto fail : EndIf
      Next
      ForEach *Scene\meshes()
        If MWString(@mw, *Scene\meshes()\name) = #False Or MWU32(@mw, ListSize(*Scene\meshes()\vertices())) = #False Or MWU32(@mw, ListSize(*Scene\meshes()\normals())) = #False Or MWU32(@mw, ListSize(*Scene\meshes()\uvs())) = #False Or MWU32(@mw, ListSize(*Scene\meshes()\faces())) = #False : Goto fail : EndIf
        faceAttrFlags = 0 : commonMaterial = -1 : commonN1 = -1 : commonN2 = -1 : commonN3 = -1 : commonUV1 = -1 : commonUV2 = -1 : commonUV3 = -1 : firstFace = #True
        ForEach *Scene\meshes()\faces()
          If firstFace
            commonMaterial = *Scene\meshes()\faces()\materialIndex
            commonN1 = *Scene\meshes()\faces()\n1 : commonN2 = *Scene\meshes()\faces()\n2 : commonN3 = *Scene\meshes()\faces()\n3
            commonUV1 = *Scene\meshes()\faces()\uv1 : commonUV2 = *Scene\meshes()\faces()\uv2 : commonUV3 = *Scene\meshes()\faces()\uv3
            firstFace = #False
          Else
            If *Scene\meshes()\faces()\materialIndex <> commonMaterial : faceAttrFlags | #FMA1_FACE_ATTR_MATERIALS : EndIf
            If *Scene\meshes()\faces()\n1 <> commonN1 Or *Scene\meshes()\faces()\n2 <> commonN2 Or *Scene\meshes()\faces()\n3 <> commonN3 : faceAttrFlags | #FMA1_FACE_ATTR_NORMALS : EndIf
            If *Scene\meshes()\faces()\uv1 <> commonUV1 Or *Scene\meshes()\faces()\uv2 <> commonUV2 Or *Scene\meshes()\faces()\uv3 <> commonUV3 : faceAttrFlags | #FMA1_FACE_ATTR_UVS : EndIf
          EndIf
        Next
        If (faceAttrFlags & #FMA1_FACE_ATTR_NORMALS) = 0 And (commonN1 <> -1 Or commonN2 <> -1 Or commonN3 <> -1) : faceAttrFlags | #FMA1_FACE_ATTR_COMMON_NORMALS : EndIf
        If (faceAttrFlags & #FMA1_FACE_ATTR_UVS) = 0 And (commonUV1 <> -1 Or commonUV2 <> -1 Or commonUV3 <> -1) : faceAttrFlags | #FMA1_FACE_ATTR_COMMON_UVS : EndIf
        If MWVarI32(@mw, faceAttrFlags) = #False Or MWVarI32(@mw, commonMaterial) = #False : Goto fail : EndIf
        If faceAttrFlags & #FMA1_FACE_ATTR_COMMON_NORMALS
          If MWVarI32(@mw, commonN1) = #False Or MWVarI32(@mw, commonN2) = #False Or MWVarI32(@mw, commonN3) = #False : Goto fail : EndIf
        EndIf
        If faceAttrFlags & #FMA1_FACE_ATTR_COMMON_UVS
          If MWVarI32(@mw, commonUV1) = #False Or MWVarI32(@mw, commonUV2) = #False Or MWVarI32(@mw, commonUV3) = #False : Goto fail : EndIf
        EndIf
        ForEach *Scene\meshes()\normals() : If MWVec3(@mw, @*Scene\meshes()\normals()) = #False : Goto fail : EndIf : Next
        ForEach *Scene\meshes()\uvs() : If MWVec2(@mw, @*Scene\meshes()\uvs()) = #False : Goto fail : EndIf : Next
        ForEach *Scene\meshes()\faces()
          If faceAttrFlags & #FMA1_FACE_ATTR_NORMALS
            If MWVarI32(@mw, *Scene\meshes()\faces()\n1) = #False Or MWVarI32(@mw, *Scene\meshes()\faces()\n2) = #False Or MWVarI32(@mw, *Scene\meshes()\faces()\n3) = #False : Goto fail : EndIf
          EndIf
          If faceAttrFlags & #FMA1_FACE_ATTR_UVS
            If MWVarI32(@mw, *Scene\meshes()\faces()\uv1) = #False Or MWVarI32(@mw, *Scene\meshes()\faces()\uv2) = #False Or MWVarI32(@mw, *Scene\meshes()\faces()\uv3) = #False : Goto fail : EndIf
          EndIf
          If faceAttrFlags & #FMA1_FACE_ATTR_MATERIALS
            If MWVarI32(@mw, *Scene\meshes()\faces()\materialIndex) = #False : Goto fail : EndIf
          EndIf
        Next
      Next
      ForEach *Scene\spatialRegions()
        If MWString(@mw, *Scene\spatialRegions()\name) = #False Or MWU32(@mw, *Scene\spatialRegions()\kind) = #False Or MWU32(@mw, *Scene\spatialRegions()\typeID) = #False Or MWString(@mw, *Scene\spatialRegions()\role) = #False Or MWString(@mw, *Scene\spatialRegions()\description) = #False Or MWString(@mw, *Scene\spatialRegions()\actionHint) = #False Or MWU32(@mw, *Scene\spatialRegions()\interactable) = #False : Goto fail : EndIf
        If MWVec3(@mw, @*Scene\spatialRegions()\boundsMin) = #False Or MWVec3(@mw, @*Scene\spatialRegions()\boundsMax) = #False : Goto fail : EndIf
        If MWU32(@mw, *Scene\spatialRegions()\firstMesh) = #False Or MWU32(@mw, *Scene\spatialRegions()\meshCount) = #False Or MWU32(@mw, *Scene\spatialRegions()\proxyMesh) = #False Or MWU32(@mw, *Scene\spatialRegions()\parentRegion) = #False Or MWU32(@mw, *Scene\spatialRegions()\visible) = #False : Goto fail : EndIf
      Next
      If *OutSize : *OutSize\i = mw\size : EndIf
      ProcedureReturn mw\memory
fail:
      If mw\memory : FreeMemory(mw\memory) : EndIf
      ProcedureReturn 0
    EndProcedure

    Procedure.i BuildSceneBlock(*Scene.Retro3D::Scene, *OutSize.Integer)
      Protected *raw, rawSize.Integer, *packed, packedSize.Integer, method.Integer, mw.MemWriter
      If *OutSize : *OutSize\i = 0 : EndIf
      *raw = BuildSceneRaw(*Scene, @rawSize)
      If *raw = 0 : ProcedureReturn 0 : EndIf
      *packed = PackStream(*raw, rawSize\i, @packedSize, @method)
      FreeMemory(*raw)
      If *packed = 0 : ProcedureReturn 0 : EndIf
      If MWMagic4(@mw, 'F', 'M', 'A', 'S') = #False Or MWU32(@mw, 1) = #False Or MWU32(@mw, rawSize\i) = #False Or MWU32(@mw, method\i) = #False Or MWU32(@mw, packedSize\i) = #False Or MWData(@mw, *packed, packedSize\i) = #False
        FreeMemory(*packed)
        If mw\memory : FreeMemory(mw\memory) : EndIf
        ProcedureReturn 0
      EndIf
      FreeMemory(*packed)
      If *OutSize : *OutSize\i = mw\size : EndIf
      ProcedureReturn mw\memory
    EndProcedure

    Procedure.i ReadU32Next(*Memory, Size.i, *Pos.Integer, *Out.Integer)
      If *Pos\i + 4 > Size : ProcedureReturn #False : EndIf
      *Out\i = ReadU32LE(*Memory, *Pos\i)
      *Pos\i + 4
      ProcedureReturn #True
    EndProcedure

    Procedure.i ReadFloatNext(*Memory, Size.i, *Pos.Integer, *Out.Float)
      If *Pos\i + 4 > Size : ProcedureReturn #False : EndIf
      *Out\f = PeekF(*Memory + *Pos\i)
      *Pos\i + 4
      ProcedureReturn #True
    EndProcedure

    Procedure.i DecodeSceneRaw(*Raw, RawSize.i, *Scene.Retro3D::Scene, *XYZ, TotalVertices.i, *Indices, TotalFaces.i)
      Protected pos.Integer, width.Integer, height.Integer, materials.i, textures.i, lights.i, meshes.i, regions.i, i.i, j.i
      Protected vCount.Integer, normalCount.Integer, uvCount.Integer, faceCount.Integer, wrapMode.Integer, filterMode.Integer, alphaThreshold.Integer
      Protected tmpI.Integer, tmpF.Float, textureBytes.Integer, *rgba, rgbaSize.Integer, texW.Integer, texH.Integer, textureFormat.Integer
      Protected name.s, vertexBase.i, faceBase.i, idx0.i, idx1.i, idx2.i
      Protected commonN1.i, commonN2.i, commonN3.i, commonUV1.i, commonUV2.i, commonUV3.i
      Protected faceAttrFlags.Integer, commonMaterial.Integer
      If *Raw = 0 Or RawSize < 4 Or *Scene = 0 Or *XYZ = 0 Or TotalVertices <= 0 : ProcedureReturn #False : EndIf
      If PeekS(*Raw, 4, #PB_Ascii) <> "SCN1" And PeekS(*Raw, 4, #PB_Ascii) <> "SCN2" : ProcedureReturn #False : EndIf
      pos\i = 4
      name = FMA1ReadString(*Raw, RawSize, @pos) : If pos\i < 0 : ProcedureReturn #False : EndIf
      If ReadU32Next(*Raw, RawSize, @pos, @width) = #False Or ReadU32Next(*Raw, RawSize, @pos, @height) = #False : ProcedureReturn #False : EndIf
      Retro3D::InitScene(*Scene, width\i, height\i)
      *Scene\name = name
      If ReadVec3(*Raw, RawSize, @pos, @*Scene\camera\origin) = #False Or ReadVec3(*Raw, RawSize, @pos, @*Scene\camera\target) = #False : ProcedureReturn #False : EndIf
      If ReadFloatNext(*Raw, RawSize, @pos, @tmpF) = #False : ProcedureReturn #False : EndIf : *Scene\camera\screenDistance = tmpF\f
      If ReadFloatNext(*Raw, RawSize, @pos, @tmpF) = #False : ProcedureReturn #False : EndIf : *Scene\camera\nearPlane = tmpF\f
      If ReadVec3(*Raw, RawSize, @pos, @*Scene\light\direction) = #False Or ReadVec3(*Raw, RawSize, @pos, @*Scene\light\position) = #False : ProcedureReturn #False : EndIf
      If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\light\hasPosition = tmpI\i
      If ReadFloatNext(*Raw, RawSize, @pos, @tmpF) = #False : ProcedureReturn #False : EndIf : *Scene\light\intensity = tmpF\f
      If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\light\color = tmpI\i
      If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\light\isSpotlight = tmpI\i
      If ReadFloatNext(*Raw, RawSize, @pos, @tmpF) = #False : ProcedureReturn #False : EndIf : *Scene\light\radius = tmpF\f
      If ReadFloatNext(*Raw, RawSize, @pos, @tmpF) = #False : ProcedureReturn #False : EndIf : *Scene\light\falloff = tmpF\f
      If ReadFloatNext(*Raw, RawSize, @pos, @tmpF) = #False : ProcedureReturn #False : EndIf : *Scene\light\tightness = tmpF\f
      If ReadFloatNext(*Raw, RawSize, @pos, @tmpF) = #False : ProcedureReturn #False : EndIf : *Scene\ambientLight = tmpF\f
      If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\ambientColor = tmpI\i
      If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\maxRecursionDepth = tmpI\i
      If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\backgroundMode = tmpI\i
      If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\backgroundColor1 = tmpI\i
      If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\backgroundColor2 = tmpI\i
      If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\previewBackfaceCulling = tmpI\i
      If PeekS(*Raw, 4, #PB_Ascii) = "SCN2"
        If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\preferredRenderMode = tmpI\i
      EndIf
      If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : materials = tmpI\i
      If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : textures = tmpI\i
      If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : lights = tmpI\i
      If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : meshes = tmpI\i
      If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : regions = tmpI\i
      ClearList(*Scene\materials())
      For i = 0 To materials - 1
        AddElement(*Scene\materials())
        *Scene\materials()\name = FMA1ReadString(*Raw, RawSize, @pos) : If pos\i < 0 : ProcedureReturn #False : EndIf
        If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\materials()\paletteIndex = tmpI\i
        If ReadFloatNext(*Raw, RawSize, @pos, @tmpF) = #False : ProcedureReturn #False : EndIf : *Scene\materials()\ambient = tmpF\f
        If ReadFloatNext(*Raw, RawSize, @pos, @tmpF) = #False : ProcedureReturn #False : EndIf : *Scene\materials()\diffuse = tmpF\f
        If ReadFloatNext(*Raw, RawSize, @pos, @tmpF) = #False : ProcedureReturn #False : EndIf : *Scene\materials()\specular = tmpF\f
        If ReadFloatNext(*Raw, RawSize, @pos, @tmpF) = #False : ProcedureReturn #False : EndIf : *Scene\materials()\shininess = tmpF\f
        If ReadFloatNext(*Raw, RawSize, @pos, @tmpF) = #False : ProcedureReturn #False : EndIf : *Scene\materials()\reflective = tmpF\f
        If ReadFloatNext(*Raw, RawSize, @pos, @tmpF) = #False : ProcedureReturn #False : EndIf : *Scene\materials()\transparency = tmpF\f
        If ReadFloatNext(*Raw, RawSize, @pos, @tmpF) = #False : ProcedureReturn #False : EndIf : *Scene\materials()\refractiveIndex = tmpF\f
        If ReadFloatNext(*Raw, RawSize, @pos, @tmpF) = #False : ProcedureReturn #False : EndIf : *Scene\materials()\emission = tmpF\f
        If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\materials()\textureType = tmpI\i
        *Scene\materials()\texturePath = FMA1ReadString(*Raw, RawSize, @pos) : If pos\i < 0 : ProcedureReturn #False : EndIf
        If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\materials()\textureSlot = tmpI\i
        If ReadFloatNext(*Raw, RawSize, @pos, @tmpF) = #False : ProcedureReturn #False : EndIf : *Scene\materials()\textureScale = tmpF\f
        If ReadFloatNext(*Raw, RawSize, @pos, @tmpF) = #False : ProcedureReturn #False : EndIf : *Scene\materials()\textureStrength = tmpF\f
        If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\materials()\color = tmpI\i
        If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\materials()\color2 = tmpI\i
        If ReadFloatNext(*Raw, RawSize, @pos, @tmpF) = #False : ProcedureReturn #False : EndIf : *Scene\materials()\colorMix = tmpF\f
        If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\materials()\colorModel = tmpI\i
      Next
      For i = 0 To textures - 1
        name = FMA1ReadString(*Raw, RawSize, @pos) : If pos\i < 0 : ProcedureReturn #False : EndIf
        If ReadU32Next(*Raw, RawSize, @pos, @texW) = #False Or ReadU32Next(*Raw, RawSize, @pos, @texH) = #False : ProcedureReturn #False : EndIf
        If ReadU32Next(*Raw, RawSize, @pos, @textureFormat) = #False : ProcedureReturn #False : EndIf
        If ReadU32Next(*Raw, RawSize, @pos, @wrapMode) = #False : ProcedureReturn #False : EndIf
        If ReadU32Next(*Raw, RawSize, @pos, @filterMode) = #False : ProcedureReturn #False : EndIf
        If ReadU32Next(*Raw, RawSize, @pos, @alphaThreshold) = #False : ProcedureReturn #False : EndIf
        If ReadU32Next(*Raw, RawSize, @pos, @textureBytes) = #False Or pos\i + textureBytes\i > RawSize : ProcedureReturn #False : EndIf
        If textureBytes\i > 0
          *rgba = WORM0_Media::DecodeRFXLToRGBA32(*Raw + pos\i, textureBytes\i, @texW, @texH, @rgbaSize)
          If *rgba = 0
            ProcedureReturn #False
          EndIf
          If Retro3D::AddTextureMemory(*Scene, name, *rgba, rgbaSize\i, texW\i, texH\i, texW\i * 4) >= 0
            LastElement(*Scene\textures())
            *Scene\textures()\wrapMode = wrapMode\i
            *Scene\textures()\filterMode = filterMode\i
            *Scene\textures()\alphaThreshold = alphaThreshold\i
            *Scene\textures()\ownsPixels = #True
          Else
            FreeMemory(*rgba)
          EndIf
        Else
          AddElement(*Scene\textures())
          *Scene\textures()\name = name
          *Scene\textures()\width = texW\i
          *Scene\textures()\height = texH\i
          *Scene\textures()\format = textureFormat\i
          *Scene\textures()\wrapMode = wrapMode\i
          *Scene\textures()\filterMode = filterMode\i
          *Scene\textures()\alphaThreshold = alphaThreshold\i
        EndIf
        pos\i + textureBytes\i
      Next
      ClearList(*Scene\lights())
      For i = 0 To lights - 1
        AddElement(*Scene\lights())
        If ReadVec3(*Raw, RawSize, @pos, @*Scene\lights()\direction) = #False Or ReadVec3(*Raw, RawSize, @pos, @*Scene\lights()\position) = #False : ProcedureReturn #False : EndIf
        If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\lights()\hasPosition = tmpI\i
        If ReadFloatNext(*Raw, RawSize, @pos, @tmpF) = #False : ProcedureReturn #False : EndIf : *Scene\lights()\intensity = tmpF\f
        If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\lights()\color = tmpI\i
        If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\lights()\isSpotlight = tmpI\i
        If ReadFloatNext(*Raw, RawSize, @pos, @tmpF) = #False : ProcedureReturn #False : EndIf : *Scene\lights()\radius = tmpF\f
        If ReadFloatNext(*Raw, RawSize, @pos, @tmpF) = #False : ProcedureReturn #False : EndIf : *Scene\lights()\falloff = tmpF\f
        If ReadFloatNext(*Raw, RawSize, @pos, @tmpF) = #False : ProcedureReturn #False : EndIf : *Scene\lights()\tightness = tmpF\f
      Next
      ClearList(*Scene\meshes())
      For i = 0 To meshes - 1
        name = FMA1ReadString(*Raw, RawSize, @pos) : If pos\i < 0 : ProcedureReturn #False : EndIf
        Retro3D::AddMesh(*Scene, name)
        If ReadU32Next(*Raw, RawSize, @pos, @vCount) = #False : ProcedureReturn #False : EndIf
        If ReadU32Next(*Raw, RawSize, @pos, @normalCount) = #False : ProcedureReturn #False : EndIf
        If ReadU32Next(*Raw, RawSize, @pos, @uvCount) = #False : ProcedureReturn #False : EndIf
        If ReadU32Next(*Raw, RawSize, @pos, @faceCount) = #False : ProcedureReturn #False : EndIf
        If ReadVarI32Next(*Raw, RawSize, @pos, @faceAttrFlags) = #False Or ReadVarI32Next(*Raw, RawSize, @pos, @commonMaterial) = #False : ProcedureReturn #False : EndIf
        If faceAttrFlags\i & ~(#FMA1_FACE_ATTR_NORMALS | #FMA1_FACE_ATTR_UVS | #FMA1_FACE_ATTR_MATERIALS | #FMA1_FACE_ATTR_COMMON_NORMALS | #FMA1_FACE_ATTR_COMMON_UVS) : ProcedureReturn #False : EndIf
        If (faceAttrFlags\i & #FMA1_FACE_ATTR_NORMALS) And (faceAttrFlags\i & #FMA1_FACE_ATTR_COMMON_NORMALS) : ProcedureReturn #False : EndIf
        If (faceAttrFlags\i & #FMA1_FACE_ATTR_UVS) And (faceAttrFlags\i & #FMA1_FACE_ATTR_COMMON_UVS) : ProcedureReturn #False : EndIf
        commonN1 = -1 : commonN2 = -1 : commonN3 = -1 : commonUV1 = -1 : commonUV2 = -1 : commonUV3 = -1
        If faceAttrFlags\i & #FMA1_FACE_ATTR_COMMON_NORMALS
          If ReadVarI32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : commonN1 = tmpI\i
          If ReadVarI32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : commonN2 = tmpI\i
          If ReadVarI32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : commonN3 = tmpI\i
        EndIf
        If faceAttrFlags\i & #FMA1_FACE_ATTR_COMMON_UVS
          If ReadVarI32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : commonUV1 = tmpI\i
          If ReadVarI32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : commonUV2 = tmpI\i
          If ReadVarI32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : commonUV3 = tmpI\i
        EndIf
        If vertexBase + vCount\i > TotalVertices Or faceBase + faceCount\i > TotalFaces : ProcedureReturn #False : EndIf
        For j = 0 To vCount\i - 1
          AddElement(*Scene\meshes()\vertices())
          *Scene\meshes()\vertices()\x = PeekL(*XYZ + (vertexBase + j) * 12 + 0) / 1000.0
          *Scene\meshes()\vertices()\y = PeekL(*XYZ + (vertexBase + j) * 12 + 4) / 1000.0
          *Scene\meshes()\vertices()\z = PeekL(*XYZ + (vertexBase + j) * 12 + 8) / 1000.0
        Next
        For j = 1 To normalCount\i : AddElement(*Scene\meshes()\normals()) : If ReadVec3(*Raw, RawSize, @pos, @*Scene\meshes()\normals()) = #False : ProcedureReturn #False : EndIf : Next
        For j = 1 To uvCount\i : AddElement(*Scene\meshes()\uvs()) : If ReadVec2(*Raw, RawSize, @pos, @*Scene\meshes()\uvs()) = #False : ProcedureReturn #False : EndIf : Next
        For j = 1 To faceCount\i
          AddElement(*Scene\meshes()\faces())
          If *Indices
            idx0 = PeekL(*Indices + (faceBase * 3 + 0) * 4)
            idx1 = PeekL(*Indices + (faceBase * 3 + 1) * 4)
            idx2 = PeekL(*Indices + (faceBase * 3 + 2) * 4)
          Else
            idx0 = 0 : idx1 = 0 : idx2 = 0
          EndIf
          *Scene\meshes()\faces()\v1 = idx0 - vertexBase
          *Scene\meshes()\faces()\v2 = idx1 - vertexBase
          *Scene\meshes()\faces()\v3 = idx2 - vertexBase
          *Scene\meshes()\faces()\n1 = -1
          *Scene\meshes()\faces()\n2 = -1
          *Scene\meshes()\faces()\n3 = -1
          *Scene\meshes()\faces()\uv1 = -1
          *Scene\meshes()\faces()\uv2 = -1
          *Scene\meshes()\faces()\uv3 = -1
          *Scene\meshes()\faces()\materialIndex = commonMaterial\i
          If faceAttrFlags\i & #FMA1_FACE_ATTR_COMMON_NORMALS
            *Scene\meshes()\faces()\n1 = commonN1
            *Scene\meshes()\faces()\n2 = commonN2
            *Scene\meshes()\faces()\n3 = commonN3
          EndIf
          If faceAttrFlags\i & #FMA1_FACE_ATTR_COMMON_UVS
            *Scene\meshes()\faces()\uv1 = commonUV1
            *Scene\meshes()\faces()\uv2 = commonUV2
            *Scene\meshes()\faces()\uv3 = commonUV3
          EndIf
          If faceAttrFlags\i & #FMA1_FACE_ATTR_NORMALS
            If ReadVarI32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\meshes()\faces()\n1 = tmpI\i
            If ReadVarI32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\meshes()\faces()\n2 = tmpI\i
            If ReadVarI32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\meshes()\faces()\n3 = tmpI\i
          EndIf
          If faceAttrFlags\i & #FMA1_FACE_ATTR_UVS
            If ReadVarI32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\meshes()\faces()\uv1 = tmpI\i
            If ReadVarI32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\meshes()\faces()\uv2 = tmpI\i
            If ReadVarI32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\meshes()\faces()\uv3 = tmpI\i
          EndIf
          If faceAttrFlags\i & #FMA1_FACE_ATTR_MATERIALS
            If ReadVarI32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\meshes()\faces()\materialIndex = tmpI\i
          EndIf
          faceBase + 1
        Next
        vertexBase + vCount\i
      Next
      For i = 0 To regions - 1
        AddElement(*Scene\spatialRegions())
        *Scene\spatialRegions()\name = FMA1ReadString(*Raw, RawSize, @pos) : If pos\i < 0 : ProcedureReturn #False : EndIf
        If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\spatialRegions()\kind = tmpI\i
        If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\spatialRegions()\typeID = tmpI\i
        *Scene\spatialRegions()\role = FMA1ReadString(*Raw, RawSize, @pos) : If pos\i < 0 : ProcedureReturn #False : EndIf
        *Scene\spatialRegions()\description = FMA1ReadString(*Raw, RawSize, @pos) : If pos\i < 0 : ProcedureReturn #False : EndIf
        *Scene\spatialRegions()\actionHint = FMA1ReadString(*Raw, RawSize, @pos) : If pos\i < 0 : ProcedureReturn #False : EndIf
        If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\spatialRegions()\interactable = tmpI\i
        If ReadVec3(*Raw, RawSize, @pos, @*Scene\spatialRegions()\boundsMin) = #False Or ReadVec3(*Raw, RawSize, @pos, @*Scene\spatialRegions()\boundsMax) = #False : ProcedureReturn #False : EndIf
        If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\spatialRegions()\firstMesh = tmpI\i
        If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\spatialRegions()\meshCount = tmpI\i
        If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\spatialRegions()\proxyMesh = tmpI\i
        If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\spatialRegions()\parentRegion = tmpI\i
        If ReadU32Next(*Raw, RawSize, @pos, @tmpI) = #False : ProcedureReturn #False : EndIf : *Scene\spatialRegions()\visible = tmpI\i
      Next
      ProcedureReturn Bool(pos\i = RawSize And vertexBase = TotalVertices And faceBase = TotalFaces)
    EndProcedure

    Procedure.i EncodeSceneMemory(*Scene.Retro3D::Scene, *OutSize.Integer = #Null)
      Protected vertices.i = Retro3D::VertexCount(*Scene), faces.i = Retro3D::FaceCount(*Scene)
      Protected *xyz, *idx, vi.i, fi.i, mi.i, scale.d = 1000.0, *out, outSize.Integer, *sceneBlock, sceneBytes.Integer, oldSize.i, newSize.i, *newOut
      If *OutSize : *OutSize\i = 0 : EndIf
      If *Scene = 0 Or vertices <= 0 : ProcedureReturn 0 : EndIf
      *xyz = AllocateMemory(vertices * 12)
      If *xyz = 0 : ProcedureReturn 0 : EndIf
      If faces > 0
        *idx = AllocateMemory(faces * 12)
        If *idx = 0 : FreeMemory(*xyz) : ProcedureReturn 0 : EndIf
      EndIf
      ForEach *Scene\meshes()
        ForEach *Scene\meshes()\vertices()
          PokeL(*xyz + (vi * 3 + 0) * 4, Round(*Scene\meshes()\vertices()\x * scale, #PB_Round_Nearest))
          PokeL(*xyz + (vi * 3 + 1) * 4, Round(*Scene\meshes()\vertices()\y * scale, #PB_Round_Nearest))
          PokeL(*xyz + (vi * 3 + 2) * 4, Round(*Scene\meshes()\vertices()\z * scale, #PB_Round_Nearest))
          vi + 1
        Next
        ForEach *Scene\meshes()\faces()
          PokeL(*idx + (fi * 3 + 0) * 4, *Scene\meshes()\faces()\v1 + mi)
          PokeL(*idx + (fi * 3 + 1) * 4, *Scene\meshes()\faces()\v2 + mi)
          PokeL(*idx + (fi * 3 + 2) * 4, *Scene\meshes()\faces()\v3 + mi)
          fi + 1
        Next
        mi = vi
      Next
      *out = EncodeXYZI32Memory(*xyz, vertices, *idx, faces, @outSize)
      FreeMemory(*xyz)
      If *idx : FreeMemory(*idx) : EndIf
      If *out = 0 : ProcedureReturn 0 : EndIf
      oldSize = outSize\i
      *sceneBlock = BuildSceneBlock(*Scene, @sceneBytes)
      If *sceneBlock = 0 : FreeMemory(*out) : ProcedureReturn 0 : EndIf
      newSize = oldSize + sceneBytes\i
      *newOut = ReAllocateMemory(*out, newSize)
      If *newOut = 0
        FreeMemory(*out)
        FreeMemory(*sceneBlock)
        ProcedureReturn 0
      EndIf
      *out = *newOut
      CopyMemory(*sceneBlock, *out + oldSize, sceneBytes\i)
      FreeMemory(*sceneBlock)
      If *OutSize : *OutSize\i = newSize : EndIf
      ProcedureReturn *out
    EndProcedure

    Procedure.i DecodeSceneMemory(*Memory, MemorySize.i, *Scene.Retro3D::Scene)
      Protected info.FMA1Info, sceneOff.i, sceneBytes.i, rawSize.i, method.i, packedSize.i, *raw, *xyz, vertexCount.Integer, faceCount.Integer, indexPtr.Integer, indexBytes.Integer
      If *Scene = 0 : ProcedureReturn #False : EndIf
      If InspectMemory(*Memory, MemorySize, @info) = #False Or (info\flags & #FMA1_FLAG_SCENE) = 0
        ProcedureReturn #False
      EndIf
      sceneOff = #FMA1_HEADER_SIZE + info\coordPackedBytes + info\indexPackedBytes
      sceneBytes = MemorySize - sceneOff
      If sceneBytes < 20 Or PeekS(*Memory + sceneOff, 4, #PB_Ascii) <> "FMAS" Or ReadU32LE(*Memory, sceneOff + 4) <> 1
        ProcedureReturn #False
      EndIf
      rawSize = ReadU32LE(*Memory, sceneOff + 8)
      method = ReadU32LE(*Memory, sceneOff + 12)
      packedSize = ReadU32LE(*Memory, sceneOff + 16)
      If rawSize <= 0 Or packedSize <= 0 Or 20 + packedSize <> sceneBytes
        ProcedureReturn #False
      EndIf
      *raw = UnpackStream(*Memory + sceneOff + 20, packedSize, rawSize, method)
      If *raw = 0 : ProcedureReturn #False : EndIf
      *xyz = DecodeXYZI32Memory(*Memory, MemorySize, @vertexCount, @faceCount, @indexPtr, @indexBytes)
      If *xyz = 0
        FreeMemory(*raw)
        ProcedureReturn #False
      EndIf
      If DecodeSceneRaw(*raw, rawSize, *Scene, *xyz, vertexCount\i, indexPtr\i, faceCount\i) = #False
        If indexPtr\i : FreeMemory(indexPtr\i) : EndIf
        FreeMemory(*xyz)
        FreeMemory(*raw)
        ProcedureReturn #False
      EndIf
      If indexPtr\i : FreeMemory(indexPtr\i) : EndIf
      FreeMemory(*xyz)
      FreeMemory(*raw)
      ProcedureReturn #True
    EndProcedure
EndModule

CompilerEndIf

CompilerIf #PB_Compiler_IsMainFile
  XIncludeFile "test_lexa_forma_worm0_hardening.pb"
CompilerEndIf
