'// MD3 Model Loader v.01 - 07/07/2020
'// By John Onyon a.k.a Unseen Machine

REM $include:'gdk_gl\gdk_gl.bi' '// GDK_GL type and constant declerations

DIM SHARED InitGL AS _BYTE, AllowGL AS _BYTE '// Generic OpenGL control variables.
DIM SHARED Debug AS _BYTE '// Debug mode control

'// Set up screen \\

SCREEN _NEWIMAGE(800, 600, 32) '// Create screen
SLEEP 1 '// Wait for the screen to be initialised
_DISPLAY '// Turn off auto-display
'_GLRENDER _ONLY '// Only allow GL rendering
_DISPLAYORDER _GLRENDER , _SOFTWARE '// For debugging purposes gonna use this mode.
_FPS 30 '// Limit frames per second


'// Model loading - Texture must be loaded in SUB _GL \\
'// .MD3 Models are often made from multiple models and then stiched together!! \\
'// You should load Lower, Upper and then head \\
DIM SHARED Model(2) AS MODEL '// Create the model - Shared so we dont have to pass it to the _GL sub

Lower$ = "3d programs\md3\lara\Lower.md3" '// Model file name
Upper$ = "3d programs\md3\lara\Upper.md3" '// Model file name
Head$ = "3d programs\md3\lara\Head.md3" '// Model file name


MD3_Load Model(0), Lower$ '// Load the model into GDK_GL type MODEL
MD3_Load Model(1), Upper$ '// Load the model into GDK_GL type MODEL
MD3_Load Model(2), Head$ '// Load the model into GDK_GL type MODEL
Model(2).Scale = 3.4

DIM SHARED ModelFrame(2) AS INTEGER

'// xyz position and rotations will be provided from the tags \\

'// Flags \\

InitGL = True
AllowGL = True
Debug = True


'// Debug variables \\
DIM KB(1) AS KeyBoardState, Mouse(1) AS MouseState
DIM SHARED Spin AS _BYTE

'/////// Main Loop \\\\\\\

DO
 _LIMIT 30 '// Tether loops to fps

 IF Debug = True AND InitGL = False THEN

  '// Get input \\
  GDK_Keyboard_GetState KB(0)
  GDK_Mouse_GetState Mouse(0)

  '// Do Logic \\
  IF KB(0).SPACE AND NOT KB(1).SPACE THEN
   IF Spin THEN Spin = False ELSE Spin = True
  ELSEIF KB(0).Up AND NOT KB(1).Up THEN
   IF CrntMdl& < 2 THEN CrntMdl& = CrntMdl& + 1 ELSE CrntMdl& = 0
  ELSEIF KB(0).Down AND NOT KB(1).Down THEN
   IF CrntMdl& > 0 THEN CrntMdl& = CrntMdl& - 1 ELSE CrntMdl& = 2
  ELSEIF KB(0).Left AND NOT KB(1).Left THEN
   IF Auto& = False THEN
    IF ModelFrame(CrntMdl&) > 0 THEN
     ModelFrame(CrntMdl&) = ModelFrame(CrntMdl&) - 1
    ELSE
     ModelFrame(CrntMdl&) = Model(CrntMdl&).NumFrames - 1
    END IF
   END IF
  ELSEIF KB(0).Right AND NOT KB(1).Right THEN
   IF Auto& = False THEN
    IF ModelFrame(CrntMdl&) < Model(CrntMdl&).NumFrames - 1 THEN
     ModelFrame(CrntMdl&) = ModelFrame(CrntMdl&) + 1
    ELSE
     ModelFrame(CrntMdl&) = 0
    END IF
   END IF
  ELSEIF KB(0).A AND NOT KB(1).A THEN
   IF Auto& THEN Auto& = False ELSE Auto& = True
  END IF


  '// Auto animation
  IF Auto& THEN
   FOR i& = 0 TO 2
    IF ModelFrame(i&) < Model(i&).NumFrames - 1 THEN
     ModelFrame(i&) = ModelFrame(i&) + 1
    ELSE
     ModelFrame(i&) = 0
    END IF
   NEXT
  END IF


  '// Backup input handlers for comaprisons \\
  KB(1) = KB(0)
  Mouse(1) = Mouse(0)

  '// Display info \\
  LOCATE 30, 1: PRINT "Next/Prev Model = (-/+)"
  PRINT "Next/Prev Frame = (Up/Down)"
  PRINT "Stop/Start Spin = (Space)"
  PRINT "Quit = (Esc)"

  LOCATE 1, 1: PRINT "Model Info "
  PRINT "----------------"

  PRINT "Lower Body"
  PRINT "Num Frames : "; Model(0).NumFrames
  PRINT "Current Frame : "; ModelFrame(0)

  PRINT
  PRINT "Upper Body"
  PRINT "Num Frames : "; Model(1).NumFrames
  PRINT "Current Frame : "; ModelFrame(1)

  PRINT
  PRINT "Head"
  PRINT "Num Frames : "; Model(2).NumFrames
  PRINT "Current Frame : "; ModelFrame(2)

  SELECT CASE CrntMdl&

   CASE 0
    CrntMdl$ = "Lower Body"
   CASE 1
    CrntMdl$ = "Upper Body"
   CASE 2
    CrntMdl$ = "Head"

  END SELECT

  LOCATE 28, 1: PRINT "Current Model : "; CrntMdl$
  PRINT "---------------------"

 END IF

 _DELAY (.005) '// Reduce CPU load
LOOP UNTIL INKEY$ = CHR$(27) '// Loop until user presses esc key

'////////////////////////////////// SUBS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

'// This sub draws multipart models all connected by their tags
SUB MD3_Model_Draw_Complete (Model() AS MODEL, NumModels&)

 '// It's important to store your models in the right order - Legs, torso, head and finally weapon

 '// Load the model header data
 DIM TmpMDL(NumModels& - 1) AS MD3
 FOR MDLCnt& = 0 TO NumMdl& - 1
  _MEMGET Model(MDLCnt&).HeaderPntr, Model(MDLCnt&).HeaderPntr.OFFSET, TmpMDL(MDLCnt&)
 NEXT


 FOR MDLCnt& = 0 TO NumModels& - 1


 NEXT


END SUB




'// This sub uses the tags in the models to position and rotate objects to be attached (i.e head to body, gun to hand, etc...)
SUB MD3_Orientate_Using_Tag (MDLToAttach AS MODEL, Frame1&, MDLBase AS MODEL, Frame2&)

 DIM TmpMDL(1) AS MD3
 _MEMGET MDLToAttach.HeaderPntr, MDLToAttach.HeaderPntr.OFFSET, TmpMDL(0) '// get header info
 _MEMGET MDLBase.HeaderPntr, MDLBase.HeaderPntr.OFFSET, TmpMDL(1) '// get header info

 '// Load the tags data for each model

 DIM MDL_Tags1((TmpMDL(0).NumTags * TmpMDL(0).NumFrames) - 1) AS MD3_Tag
 _MEMGET MDLToAttach.DataPntr, MDLToAttach.DataPntr.OFFSET + MDLToAttach.TagsOffset, MDL_Tags1()

 DIM MDL_Tags2((TmpMDL(1).NumTags * TmpMDL(1).NumFrames) - 1) AS MD3_Tag
 _MEMGET MDLBase.DataPntr, MDLBase.DataPntr.OFFSET + MDLBase.TagsOffset, MDL_Tags2()

 '// check each model for a matching reference and apply the rotation and translation
 Tag1& = Frame1& * TmpMDL(0).NumTags
 FOR i& = Tag1& TO Tag1& + TmpMDL(0).NumTags - 1
  Tag2& = Frame2& * TmpMDL(1).NumTags
  FOR j& = Tag2& TO Tag2& + TmpMDL(1).NumTags - 1
   IF MDL_Tags2(j&).Name = MDL_Tags1(i&).Name THEN
    DIM Move AS MD3_Vector
    Move.X = (MDL_Tags2(j&).Origin.X + MDL_Tags1(i&).Origin.X) * MDLToAttach.Scale
    Move.Y = (MDL_Tags2(j&).Origin.Y + MDL_Tags1(i&).Origin.Y) * MDLToAttach.Scale
    Move.Z = (MDL_Tags2(j&).Origin.Z + MDL_Tags1(i&).Origin.Z) * MDLToAttach.Scale
    _glTranslatef Move.X, Move.Y, Move.Z
    EXIT SUB
   END IF
  NEXT
 NEXT


END SUB


'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

SUB MD3_Load (Model AS MODEL, File$) '// MDL to be changed to type MODEL when converted to GDK_GL_MODEL_LOAD

 DIM TmpMDL AS MD3 '// stores the model header info

 FFile& = FREEFILE
 OPEN File$ FOR BINARY AS #FFile&

 GET #FFile&, 1, TmpMDL '// get header info

 Model.NumFrames = TmpMDL.NumFrames
 Model.ID = MD3
 Model.Scale = 1 '// Default scale value

 '// DIM containers
 DIM MDL_Frame(TmpMDL.NumFrames - 1) AS MD3_Frame
 DIM MDL_Surface(TmpMDL.NumSurfaces - 1) AS MD3_Surface

 '// Load frame data
 BytePos& = TmpMDL.FrameOffset + 1
 FOR i& = 0 TO TmpMDL.NumFrames - 1
  GET #FFile&, BytePos&, MDL_Frame(i&)
  BytePos& = BytePos& + LEN(MDL_Frame(0))
 NEXT

 '// Load surface (mesh) data
 BytePos& = TmpMDL.SurfaceOffset + 1
 FOR i& = 0 TO TmpMDL.NumSurfaces - 1
  GET #FFile&, BytePos&, MDL_Surface(i&)
  NumTris& = NumTris& + MDL_Surface(i&).Triangles '// Count how many triangles in total
  BytePos& = BytePos& + MDL_Surface(i&).EndOffset '// Skip to next surface
 NEXT

 '// Load triangle data - each surface has it's own set of triangles!!!
 '// These are not actual coordinates, rather indexes into the Vertex array.
 DIM MDL_Tris(NumTris& - 1) AS MD3_Triangle '// Triangle index array
 BytePos& = TmpMDL.SurfaceOffset + MDL_Surface(0).TriangleOffset + 1
 FOR i& = 0 TO TmpMDL.NumSurfaces - 1
  FOR j& = 0 TO MDL_Surface(i&).Triangles - 1
   GET #FFile&, BytePos&, MDL_Tris(Tcnt&)
   BytePos& = BytePos& + LEN(MDL_Tris(0))
   Tcnt& = Tcnt& + 1
  NEXT
 NEXT

 '// Load vertex data
 FOR i& = 0 TO TmpMDL.NumSurfaces - 1
  TotalVerts& = TotalVerts& + (MDL_Surface(i&).Frames * MDL_Surface(i&).Verts) '// Calculate total array size needed
 NEXT
 DIM MDL_Verts(TotalVerts& - 1) AS MD3_Vertex '// Vertex array (due to scaling these values need dividing by 64)
 BytePos& = TmpMDL.SurfaceOffset + MDL_Surface(0).VertOffset + 1
 FOR i& = 0 TO TotalVerts& - 1
  GET #FFile&, BytePos&, MDL_Verts(i&)
  BytePos& = BytePos& + LEN(MDL_Verts(0))
 NEXT

 '// Load texture coordinates
 DIM MDL_TexCoord(TotalVerts& - 1) AS MD3_TexCoord
 BytePos& = TmpMDL.SurfaceOffset + MDL_Surface(0).TextureOffset + 1
 FOR j& = 0 TO TmpMDL.NumSurfaces - 1
  FOR i& = 0 TO MDL_Surface(j&).Verts - 1
   GET #FFile&, BytePos&, MDL_TexCoord(TXCnt&)
   BytePos& = BytePos& + LEN(MDL_TexCoord(0))
   TXCnt& = TXCnt& + 1
  NEXT
 NEXT

 '// Load TAG infomation \\
 DIM MDL_Tags((TmpMDL.NumTags * TmpMDL.NumFrames) - 1) AS MD3_Tag
 BytePos& = TmpMDL.TagsOffset + 1
 FOR i& = 0 TO (TmpMDL.NumTags * TmpMDL.NumFrames) - 1
  GET #FFile&, BytePos&, MDL_Tags(i&)
  BytePos& = BytePos& + LEN(MDL_Tags(0))
 NEXT

 CLOSE FFile& '// All data should be loaded so close the model file.

 '// NOW COMES THE FUN BIT!!! We love _MEM Functions !!! \\

 Model.HeaderPntr = _MEMNEW(LEN(TmpMDL)) '// allocate memory
 _MEMPUT Model.HeaderPntr, Model.HeaderPntr.OFFSET, TmpMDL '// put header data into memory

 FrameSize& = (LEN(MDL_Frame(0)) * TmpMDL.NumFrames)
 SurfaceSize& = (LEN(MDL_Surface(0)) * TmpMDL.NumSurfaces)
 TrisSize& = (LEN(MDL_Tris(0)) * NumTris&)
 VertsSize& = LEN(MDL_Verts(0)) * TotalVerts&
 TexCoordSize& = LEN(MDL_TexCoord(0)) * TotalVerts&
 TagsSize& = (TmpMDL.NumTags * TmpMDL.NumFrames) * LEN(MDL_Tags(0))
 TotalSize& = FrameSize& + TrisSize& + SurfaceSize& + TexCoordSize& + VertsSize& + TagsSize& '// Calculate size of required memory block

 Model.DataPntr = _MEMNEW(TotalSize&) '// Allocate memory
 Model.TagsOffset = TotalSize& - TagsSize& '// store the tags offset for use later on
 MemOffset& = 0
 _MEMPUT Model.DataPntr, Model.DataPntr.OFFSET + MemOffset&, MDL_Frame()
 MemOffset& = FrameSize&
 _MEMPUT Model.DataPntr, Model.DataPntr.OFFSET + MemOffset&, MDL_Surface()
 MemOffset& = MemOffset& + SurfaceSize&
 _MEMPUT Model.DataPntr, Model.DataPntr.OFFSET + MemOffset&, MDL_Tris()
 MemOffset& = MemOffset& + TrisSize&
 _MEMPUT Model.DataPntr, Model.DataPntr.OFFSET + MemOffset&, MDL_Verts()
 MemOffset& = MemOffset& + VertsSize&
 _MEMPUT Model.DataPntr, Model.DataPntr.OFFSET + MemOffset&, MDL_TexCoord()
 MemOffset& = MemOffset& + TexCoordSize&
 _MEMPUT Model.DataPntr, Model.DataPntr.OFFSET + MemOffset&, MDL_Tags()

END SUB

'//////////////////////////////////

SUB MD3_DRAW (Model AS MODEL, Frame&)

 DIM TmpMDL AS MD3 '// stores the model header info

 _MEMGET Model.HeaderPntr, Model.HeaderPntr.OFFSET, TmpMDL '// get header info

 '// DIM containers
 DIM MDL_Frame(TmpMDL.NumFrames - 1) AS MD3_Frame
 DIM MDL_Surface(TmpMDL.NumSurfaces - 1) AS MD3_Surface

 '// Load frame data
 _MEMGET Model.DataPntr, Model.DataPntr.OFFSET + BytePos&, MDL_Frame()
 BytePos& = (LEN(MDL_Frame(0)) * TmpMDL.NumFrames)

 '// Load surface (mesh) data
 _MEMGET Model.DataPntr, Model.DataPntr.OFFSET + BytePos&, MDL_Surface()
 BytePos& = BytePos& + (LEN(MDL_Surface(i&)) * TmpMDL.NumSurfaces)

 '// Count how many triangles in total
 FOR i& = 0 TO TmpMDL.NumSurfaces - 1
  NumTris& = NumTris& + MDL_Surface(i&).Triangles
 NEXT

 '// Load triangle data
 DIM MDL_Tris(NumTris& - 1) AS MD3_Triangle '// Triangle index array
 _MEMGET Model.DataPntr, Model.DataPntr.OFFSET + BytePos&, MDL_Tris()
 BytePos& = BytePos& + (LEN(MDL_Tris(0)) * NumTris&)

 '// Load vertex data
 FOR i& = 0 TO TmpMDL.NumSurfaces - 1
  TotalVerts& = TotalVerts& + (MDL_Surface(i&).Frames * MDL_Surface(i&).Verts) '// Calculate total array size needed
 NEXT
 DIM MDL_Verts(TotalVerts& - 1) AS MD3_Vertex '// Vertex array (due to scaling these values need dividing by 64)
 _MEMGET Model.DataPntr, Model.DataPntr.OFFSET + BytePos&, MDL_Verts()
 BytePos& = BytePos& + (LEN(MDL_Verts(0)) * TotalVerts&)

 '// Load texture coordinates
 DIM MDL_TexCoord(TotalVerts& - 1) AS MD3_TexCoord
 _MEMGET Model.DataPntr, Model.DataPntr.OFFSET + BytePos&, MDL_TexCoord()

 DIM VB_Vert(8, NumTris& - 1) AS SINGLE '// Vertex buffer
 DIM TC(5, NumTris& - 1) AS SINGLE '// Texture coordinate array

 AnimFrame& = Frame& * MDL_Surface(0).Verts

 FOR j& = 0 TO TmpMDL.NumSurfaces - 1
  FOR i& = 0 TO MDL_Surface(j&).Triangles - 1
   VB_Vert(0, i&) = (MDL_Verts(AnimFrame& + MDL_Tris(i&).Vert1).X / 64) * Model.Scale
   VB_Vert(1, i&) = (MDL_Verts(AnimFrame& + MDL_Tris(i&).Vert1).Y / 64) * Model.Scale
   VB_Vert(2, i&) = (MDL_Verts(AnimFrame& + MDL_Tris(i&).Vert1).Z / 64) * Model.Scale
   VB_Vert(3, i&) = (MDL_Verts(AnimFrame& + MDL_Tris(i&).Vert2).X / 64) * Model.Scale
   VB_Vert(4, i&) = (MDL_Verts(AnimFrame& + MDL_Tris(i&).Vert2).Y / 64) * Model.Scale
   VB_Vert(5, i&) = (MDL_Verts(AnimFrame& + MDL_Tris(i&).Vert2).Z / 64) * Model.Scale
   VB_Vert(6, i&) = (MDL_Verts(AnimFrame& + MDL_Tris(i&).Vert3).X / 64) * Model.Scale
   VB_Vert(7, i&) = (MDL_Verts(AnimFrame& + MDL_Tris(i&).Vert3).Y / 64) * Model.Scale
   VB_Vert(8, i&) = (MDL_Verts(AnimFrame& + MDL_Tris(i&).Vert3).Z / 64) * Model.Scale

   TC(0, i&) = MDL_TexCoord(MDL_Tris(i&).Vert1).X
   TC(1, i&) = MDL_TexCoord(MDL_Tris(i&).Vert1).Y
   TC(2, i&) = MDL_TexCoord(MDL_Tris(i&).Vert2).X
   TC(3, i&) = MDL_TexCoord(MDL_Tris(i&).Vert2).Y
   TC(4, i&) = MDL_TexCoord(MDL_Tris(i&).Vert3).X
   TC(5, i&) = MDL_TexCoord(MDL_Tris(i&).Vert3).Y
  NEXT
 NEXT


 '// All data stored into memory - lets render !! \\
 _glEnableClientState _GL_VERTEX_ARRAY
 _glEnableClientState _GL_TEXTURE_COORD_ARRAY
 '_glEnableClientState _GL_NORMAL_ARRAY
 _glBindTexture _GL_TEXTURE_2D, Model.Skin
 _glVertexPointer 3, _GL_FLOAT, 0, _OFFSET(VB_Vert())
 _glTexCoordPointer 2, _GL_FLOAT, 0, _OFFSET(TC())
 '_glNormalPointer _GL_FLOAT, 0, _OFFSET(Norm())
 _glDrawArrays _GL_TRIANGLES, 0, NumTris& * 3
 _glDisableClientState _GL_VERTEX_ARRAY
 _glDisableClientState _GL_TEXTURE_COORD_ARRAY
 '_glDisableClientState _GL_NORMAL_ARRAy

END SUB

'//////////////////////////////////

REM $INCLUDE:'GDK_GL\GDK_GL.bm' '// GDK_GL Library
REM $INCLUDE:'UnseenGDK.bm'  '// GDK Library (Only used here for input handling)

'//////////////////////////////////

SUB _GL

 IF AllowGL = True THEN

  '// Bog standard Open_GL Setup \\
  _glClearDepth 1
  _glClearColor 0, 0, 0, 1
  _glEnable _GL_DEPTH_TEST
  _glEnable _GL_TEXTURE_2D
  _glMatrixMode _GL_PROJECTION
  _glLoadIdentity
  _gluPerspective 90, 800 / 600, 1, 1000
  _glMatrixMode _GL_MODELVIEW
  _glLoadIdentity
  _glViewport 0, 0, 800, 600

  IF InitGL THEN '// Load things here

   Model(0).Skin = GDK_GL_LOAD_TEXTURE("3d programs\md3\lara\lara.png") '// Model texture file
   Model(1).Skin = Model(0).Skin
   Model(2).Skin = GDK_GL_LOAD_TEXTURE("3d programs\md3\lara\head.png") '// Model texture file


   InitGL = False '// Change flag.

   STATIC SpinVal& '// Holds spin rotation value

  ELSE '// in render loop

   GDK_GL_CLS '// Reset the open gl screen (effectively CLS)

   '// CAMERA \\
   _glTranslatef 0, -40, -105
   _glRotatef 90, 1, 0, 0 '// X axis rotation
   _glRotatef 180, 0, 1, 0 '// y axis rotation

   '// RENDER \\
   _glPushMatrix
   _glRotatef SpinVal&, 0, 0, 1 '// z axis rotation

   'MD3_DRAW Model(0), ModelFrame(0) '// Call model render sub  '// Lower body \\
   'MD3_Orientate_Using_Tag Model(1), ModelFrame(1), Model(0), ModelFrame(0) '// Apply predefined tranlsation and rotation
   'MD3_DRAW Model(1), ModelFrame(1) '// Call model render sub '// Upper body
   'MD3_Orientate_Using_Tag Model(2), ModelFrame(2), Model(1), ModelFrame(1) '// Apply predefined tranlsation and rotation
   'MD3_DRAW Model(2), ModelFrame(2) '// Call model render sub  '// Head

   MD3_Orientate_Using_Tag Model(2), ModelFrame(2), Model(1), ModelFrame(1) '// Apply predefined tranlsation and rotation
   MD3_DRAW Model(2), ModelFrame(2) '// Call model render sub  '// Head
   MD3_Orientate_Using_Tag Model(1), ModelFrame(1), Model(0), ModelFrame(0) '// Apply predefined tranlsation and rotation
   MD3_DRAW Model(1), ModelFrame(1) '// Call model render sub '// Upper body
   MD3_DRAW Model(0), ModelFrame(0) '// Call model render sub  '// Lower body \\
   _glPopMatrix


   IF Spin THEN IF SpinVal& < 360 THEN SpinVal& = SpinVal& + 1 ELSE SpinVal& = 0

   _DISPLAY '// Update the display

  END IF
 END IF
END SUB


