'Open GL wrapper, alpha release 1.0, JohnK, lots of help from FreeBasic Developers, and of course SGI 


$IFNDEF __GL_inc
    $INCLUDE <\GL\gl.inc>
    $INCLUDE <GL\glu.inc>
$ENDIF

$IFNDEF __glwgl_inc
    declare function wglCreateContext lib "opengl32" alias "wglCreateContext" (BYVAL dw1 as long) as long
    declare sub wglDeleteContext lib "opengl32" alias "wglDeleteContext"(BYVAL dw1 as long)
    declare sub wglMakeCurrent lib "opengl32" alias "wglMakeCurrent"(BYVAL dw1 as long, BYVAL dw2 as long)
$ENDIF


$IFNDEF __WIN32API
' pixel builder 
type PIXELFORMATDESCRIPTOR
     nSize           as word
     nVersion        as word
     dwFlags         as dword
     iPixelType      As Byte
     cColorBits      As Byte
     cRedBits        As Byte
     cRedShift       As Byte
     cGreenBits      As Byte
     cGreenShift     As Byte
     cBlueBits       As Byte
     cBlueShift      As Byte
     cAlphaBits      As Byte
     cAlphaShift     As Byte
     cAccumBits      As Byte
     cAccumRedBits   As Byte
     cAccumGreenBits As Byte
     cAccumBlueBits  As Byte
     cAccumAlphaBits As Byte
     cDepthBits      As Byte
     cStencilBits    As Byte
     cAuxBuffers     As Byte
     iLayerType      As Byte
     bReserved       As Byte
     dwLayerMask     as dword
     dwVisibleMask   as dword
     dwDamageMask    as dword
end type


' import gdi32.dll 
declare function ChoosePixelFormat lib "gdi32" alias  "ChoosePixelFormat" (BYVAL dw1 as long, dw2 as PIXELFORMATDESCRIPTOR) as long
Declare Function SetPixelFormat Lib "gdi32" Alias "SetPixelFormat" (ByVal hDC As Long, ByVal n As Long, pcPixelFormatDescriptor As PIXELFORMATDESCRIPTOR) As Long
Declare Function SwapBuffers Lib "gdi32" Alias "SwapBuffers" (ByVal hDC As Long) As Long


  $IFNDEF __RQINC2					'rapidQ2 includes 
    ' import user32.dll 
    declare function GetDC lib "user32" alias "GetDC" (BYVAL dw1 as long) as long
    declare sub ReleaseDC lib "user32" alias "ReleaseDC" (BYVAL dw1 as long, BYVAL dw2 as long)
  $ENDIF   'rapidQ2.inc 
$ENDIF   'windows inc 

$DEFINE PFD_MAIN_PLANE      0
$DEFINE PFD_TYPE_RGBA       0
$DEFINE PFD_DOUBLEBUFFER    1
$DEFINE PFD_DRAW_TO_WINDOW  4
$DEFINE PFD_SUPPORT_OPENGL  &H20
$DEFINE PFD_DRAW_TO_BITMAP  &H00000008
$DEFINE PFD_SUPPORT_GDI     &H00000010
$DEFINE PFD_GENERIC_ACCELERATED     &H00001000
$DEFINE PFD_SUPPORT_DIRECTDRAW      &H00002000


'===========  The main object now... =================== 

DIM QGLinternalBMP AS QBITMAPEX

TYPE QGL EXTENDS QOBJECT
  PRIVATE:
	OrigWidth 		AS INTEGER
	OrigHeight		AS INTEGER
    ParentHandle    AS LONG
    FPSTimer        AS QTIMER       'periodic update of Frames/sec 
    FrameCount      AS DWORD        'this keeps track of Frames for FPS 

  PUBLIC:
    RenderToBitMap  AS INTEGER      PROPERTY SET Set_RenderToBitmap
    Bitmap          AS QBITMAP      'for rendering to a bmp 
    Width           AS INTEGER
    Height          AS INTEGER
    Front           AS GLdouble     'front and back clipping planes 
    Back            AS GLdouble
	MipMap 			AS INTEGER
	FullScreen 		AS INTEGER  PROPERTY SET Set_FullScreen
	hDC 			AS LONG			'handle to device context (DC) in the parent window 
    hBMP            AS LONG         'handle to BMP (device context handle) 
	hglRC			AS LONG			'OpenGL specific -- rendering context (like a handle to DC) 
    hGLRC_bmp          AS LONG         'OpenGL rendering context to bitmap 
                                    'a rendering context is like a windows DC fix for OpenGL 
    bpp             AS INTEGER      'bits per pixel of screen 
    DepthBits       AS INTEGER
    StencilBits     AS INTEGER      'set the depth, stencil, and accum bit resolutions 
    AccumBits       AS INTEGER      ' 
    FrameRate       AS SINGLE
    FOV             AS DOUBLE       'field of view 
    AspectRatio     AS DOUBLE
    FogEnabled      AS INTEGER      PROPERTY SET Set_FogEnabled
    FogMode         AS INTEGER      PROPERTY SET Set_FogMode

'************************************************************************** 
' Set OpenGL viewport using width and height of GL form  -- our drawing boundries 
'************************************************************************** 

SUB ReSize(width AS INTEGER, height AS INTEGER)        'adjusts the viewport onResize 
    IF height > 0 THEN 
        QGL.Height = height            'Prevent Divide By Zero error 
        IF width > 0 THEN 
            QGL.width = width
            glViewport(0,0, width, height)   'viewport doesn't have to be whole form 
        END IF
    END IF
'  *********   Calculate The Aspect Ratio Of The Window  **************** 
	' The parameters are (view angle, aspect ration of the width to the height,closest camera distance before clipping 
	' FOV deg in y-direction /Aspect Ratio =FOV in x-direction/ dist to Near clipping plane/ dist to far clipping Plane 

	glMatrixMode GL_PROJECTION                     '' Select The Projection Matrix 
	glLoadIdentity                                 '' Reset The Projection Matrix 
	gluPerspective(QGL.FOV, (QGL.AspectRatio * width/height), QGL.Front, QGL.Back)
    glMatrixMode (GL_MODELVIEW)
END SUB




'************************************************************************************************** 
'initalize the interface to windows:   
'   Run this at the Form.OnShow event and pass the Qform that will be drawn 
'**************************************************************************************************** 
'  

FUNCTION Init(MainForm AS QFORM) AS INTEGER
    DIM pf 			AS PIXELFORMATDESCRIPTOR
	DIM Hresult		AS LONG

    RESULT = 0                              'returns TRUE on suceed 
    QGL.ParentHandle = MainForm.Handle      'store this for closing 
    IF QGL.Width = 0 THEN QGL.Width = MainForm.ClientWidth
    IF QGL.Height = 0 THEN QGL.Height = MainForm.ClientHeight
	QGL.hDC = GetDC(MainForm.Handle)		'global DC,don't free this hdc until the end of our program 
    pf.nSize = SIZEOF(pf)
    pf.nVersion = 1
    pf.dwFlags = PFD_DRAW_TO_WINDOW OR _
                 PFD_SUPPORT_OPENGL OR _
                 PFD_DOUBLEBUFFER OR _              'don't add PFD_USE_GDI 
                 PFD_GENERIC_ACCELERATED            'last flag probably won't work 
    pf.dwLayerMask = PFD_MAIN_PLANE
    pf.iPixelType = PFD_TYPE_RGBA					'RGB and Alpha pixel type 
	'color bitplanes in each color buffer. For RGBA pixel = size of the color buffer excluding the alpha 
    IF QGL.bpp < 32 THEN pf.cColorBits = 24 ELSE  pf.cColorBits = QGL.bpp
    pf.cDepthBits = QGL.DepthBits           'Depthbits is ignored for RGBA, but we do it anyway		 
    pf.cAccumBits = QGL.AccumBits           'No special bitplanes needed 
    pf.cStencilBits = QGL.stencilBits
    pf.iLayerType = PFD_MAIN_PLANE

    Hresult = ChoosePixelFormat(QGL.hDC, pf)
    IF Hresult = 0 THEN	EXIT FUNCTION               'pixel format failed 
    IF SetPixelFormat(QGL.hDC, Hresult, pf) = 0 THEN EXIT FUNCTION
    QGL.hglRC = wglCreateContext(QGL.hDC)			'create a rendering context from our hdc 
    wglMakeCurrent (QGL.hDC, QGL.hglRC)				'make it the one drawn on 
    RESULT = QGL.hDC
END FUNCTION



PROPERTY SET Set_RenderToBitmap(TheState AS INTEGER)
    DIM pf 			AS PIXELFORMATDESCRIPTOR
	DIM Hresult		AS LONG

    IF TheState = False THEN
    	IF QGL.hGLRC_bmp THEN 
  		    wglMakeCurrent (QGL.hDC, QGL.hglRC)
    		wglDeleteContext QGL.hGLRC_bmp
            QGL.Bitmap.Width = 0        'deallocate mem! 
            QGL.Bitmap.Height = 0
        END IF
    ELSE
        IF QGL.Bitmap.Width = 0 THEN 
            QGL.Bitmap.Width = QGL.Width
            QGL.Bitmap.Height = QGL.Height
            IF QGL.bpp = 32 THEN 
                QGL.Bitmap.PixelFormat = pf32Bit 
            ELSE 
                QGL.bpp = 24
                QGL.Bitmap.PixelFormat = pf24Bit        'only support 24 bit or higher 
            END IF
        END IF
        pf.nSize = SIZEOF(pf):              pf.nVersion = 1
        pf.dwFlags = PFD_SUPPORT_OPENGL OR _
                     PFD_SUPPORT_GDI OR _
                     PFD_DRAW_TO_BITMAP OR _
                     PFD_GENERIC_ACCELERATED            'last flag probably won't work 
        pf.iPixelType = PFD_TYPE_RGBA					'RGB and Alpha pixel type 
    	'color bitplanes in each color buffer. For RGBA pixel = size of the color buffer excluding the alpha 
        pf.cColorBits = QGL.bpp
        pf.cDepthBits = QGL.DepthBits           'Depthbits is ignored for RGBA, but we do it anyway		 
        pf.cAccumBits = QGL.AccumBits           'No special bitplanes needed 
        pf.cStencilBits = QGL.stencilBits
        pf.iLayerType = PFD_MAIN_PLANE
        pf.dwLayerMask = PFD_MAIN_PLANE
    
        QGL.hBMP = QGL.Bitmap.handle
        Hresult = ChoosePixelFormat(QGL.hBMP, pf)
        IF (Hresult = 0) THEN
            Showmessage "Render to Bitmap Failed"        'pixel format failed 
        ELSE
            IF SetPixelFormat(QGL.hBMP, Hresult, pf) <> 0 THEN
                QGL.hGLRC_bmp = wglCreateContext(QGL.hBMP)		'create a rendering context from bitmap handle 
                wglMakeCurrent (QGL.hBMP, QGL.hGLRC_bmp)	     'set up a DIB context for openGL screen 
            END IF
        END IF
    END IF
    QGL.RenderToBitmap = TheState
END PROPERTY



'************************************************************************** 
'   Properly exit from the program and release device context (DC) 
'   only at program end. 
'************************************************************************** 

SUB Close()       'must run at end to free resources 
    IF QGL.FullScreen THEN
    	QGL.bpp = Screen.GetPixelDepth
    	Screen.SetResolution(QGL.OrigWidth, QGL.OrigHeight, QGL.bpp, 0)
    END IF

	IF QGL.hGLRC_bmp THEN 
		wglDeleteContext QGL.hGLRC_bmp
    END IF

	IF QGL.hglRC THEN 
		wglMakeCurrent 0, 0
		wglDeleteContext QGL.hglRC
        IF QGL.hDC THEN ReleaseDC QGL.ParentHandle, QGL.hDC
    END IF
END SUB


SUB Flip
    SwapBuffers GL.hDC                   ' flip the screen 
    QGL.FrameCount++
    IF QGL.FrameCount > 32767 THEN QGL.FrameCount = 1
END SUB


PROPERTY SET Set_FullScreen(GoFull AS INTEGER)
    IF QGL.Width > 319 AND QGL.Height > 239 THEN
	IF GoFull = True THEN
    	QGL.bpp = Screen.GetPixelDepth
       	IF Screen.SetResolution(QGL.Width, QGL.Height, QGL.bpp, 0) <> DISP_CHANGE_SUCCESSFUL THEN    'failed, menus, statusbar... 
          SELECT CASE QGL.Width
               CASE IS < 641
                   Screen.SetResolution(640, 480, QGL.bpp, 0)
               CASE < 801
                   Screen.SetResolution(800, 600, QGL.bpp, 0)
               CASE ELSE
                   Screen.SetResolution(1024, 768, QGL.bpp, 0)
           END SELECT
        END IF
    ELSE
    	QGL.bpp = Screen.GetPixelDepth
    	Screen.SetResolution(QGL.OrigWidth, QGL.OrigHeight, QGL.bpp, 0)
	END IF
   	QGL.FullScreen = GoFull
    END IF
END PROPERTY




PRIVATE:
FUNCTION GL_GenerateTexture() AS LONG
	DIM tex AS GLuint
    DIM ClrBits AS GLint
	DIM format AS GLenum
    DIM NotBMP  As INTEGER

    RESULT = 0 
	'texture ok size?, powers of 2? 
	IF(QGLinternalBMP.Width < 2) OR (QGLinternalBMP.Height < 2) THEN EXIT FUNCTION
'	IF(QGLinternalBMP.Width AND (QGLinternalBMP.Width-1)) OR _ 
'      (QGLinternalBMP.Height AND (QGLinternalBMP.Height-1)) THEN EXIT FUNCTION 

	'generate and bind the texture, openGl keeps track 
	glGenTextures 1, tex
	glBindTexture GL_TEXTURE_2D, tex
	'' Swap R and B so we can use the GL_RGBA texture format? 
    SELECT CASE QGLinternalBMP.PixelFormat 
        CASE pf32Bit
            ClrBits = 4
            format = GL_BGRA            'typical GL_RGBA  but we are in windows 
        CASE pf24bit
            ClrBits = 3
            format = GL_BGR             'typical GL_RGB but we are in windows 
        CASE pf16bit
            QGLinternalBMP.PixelFormat = pf24bit
            ClrBits = 3
            format = GL_BGR             'typical GL_RGB but we are in windows 
        CASE pf8bit                     'only 24 bit supported... 
'            ClrBits = 1 
'            format = GL_COLOR_INDEX 
            QGLinternalBMP.PixelFormat = pf24bit
            ClrBits = 3
            format = GL_BGR             'typical GL_RGB but we are in windows 
    END SELECT

	IF QGL.MipMap THEN
'     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT) 
'     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT) 

		gluBuild2DMipmaps(GL_TEXTURE_2D, ClrBits,_
				QGLinternalBMP.Width, QGLinternalBMP.Height,_
				format,GL_UNSIGNED_BYTE,_
				QGLinternalBMP.Pointer)

    	glTexParameteri GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, QGL.MipMap
    	glTexParameteri GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR     'choose better option 
    ELSE
        glTexImage2D(GL_TEXTURE_2D, 0, ClrBits,_
				QGLinternalBMP.Width, QGLinternalBMP.Height,_
				0, format, GL_UNSIGNED_BYTE,_
				QGLinternalBMP.Pointer)
        glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST      'GL_LINEAR is smoother but slower 
        glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST
    END IF

	RESULT = tex
END FUNCTION


PUBLIC:

FUNCTION CreateTextureFromFile(ImgFileName as string) AS LONG
    RESULT = 0 
	IF FILEEXISTS(ImgFileName) = False THEN	EXIT FUNCTION
    IF INSTR(UCASE$(ImgFileName), ".JPG") OR _
       INSTR(UCASE$(ImgFileName), ".TGA") OR _
       INSTR(UCASE$(ImgFileName), ".GIF") OR _
       INSTR(UCASE$(ImgFileName), ".PCX") AND NViewLibPresent THEN
        	QGLinternalBMP.LoadOtherImage(ImgFileName, 0,"")
    ELSE
        	QGLinternalBMP.LoadFromFile(ImgFileName)
    END IF
    RESULT = QGL.GL_GenerateTexture
END FUNCTION


FUNCTION CreateTextureFromBitMap(ImgBMP as QBITMAP) AS LONG
    RESULT = 0 
	IF ImgBMP.Width > 1 AND ImgBMP.Height > 1 THEN 
        QGLinternalBMP.Width = ImgBMP.Width
        QGLinternalBMP.Height = ImgBMP.Height
        QGLinternalBMP.PixelFormat = ImgBMP.PixelFormat
        QGLinternalBMP.Draw(0, 0, ImgBMP.BMP)
        RESULT = QGL.GL_GenerateTexture
    END IF
END FUNCTION



'*********  Fog property, affects whole scene  ******************* 
PROPERTY SET Set_FogEnabled(theState AS INTEGER)
    IF theState THEN glEnable(GL_FOG) ELSE glDisable(GL_FOG)
    QGL.FogEnabled = theState
END PROPERTY

PROPERTY SET Set_FogMode(theMode AS INTEGER)
 	glFogi GL_FOG_MODE, theMode                       ' Mode = GL_LINEAR, GL_EXP, GL_EXP2 
    QGL.FogMode = theMode
END PROPERTY

SUB FogColor(R#, G#, B#)
    DEFSNG fogColor(0 to 3) = {R#, G#,B#, 1.0}         '' Fog Color, alpha always 1? 
 	glFogfv GL_FOG_COLOR, fogColor(0)                  '' Set Fog Color 
END SUB

SUB SetFogParams (Start#, End#, Density#) 
 	glFogf GL_FOG_DENSITY, Density#                    '' How Dense Will The Fog Be 
 	glHint GL_FOG_HINT, GL_DONT_CARE                   '' Fog Hint Value 
 	glFogf GL_FOG_START, Start#                        '' Fog Start Depth 
 	glFogf GL_FOG_END, End#                            '' Fog End Depth 
END SUB




PRIVATE:

SUB FPSTimerOver        'this keeps track of Frames/sec 
    QGL.FrameRate = QGL.FrameCount  '/QGL.FPSTimer.Interval, which is 1 sec 
    QGL.FrameCount = 0
END SUB


CONSTRUCTOR
    hDC         = 0
    hglRC       = 0
    hBMP        = 0
    hGLRC_bmp   = 0
	FullScreen = False
	OrigWidth = Screen.Width
	OrigHeight = Screen.Height
    bpp         = 24
    DepthBits   = 8
    StencilBits = 16
    AccumBits   = 0
    Front       = 0.1
    Back        = 500.0
    FOV         = 60.0
    AspectRatio = 1.0       'equal width/height ratio in scaling 
    FPSTimer.Enabled = True
    FPSTimer.OnTimer = QGL.FPSTimerOver
    FPSTimer.Interval = 1000
    FogEnabled  = False
    FogMode     = GL_EXP
END CONSTRUCTOR
			 
END TYPE



'************************************************************** 
'************************************************************** 
'           MD2 (quake) animations 
'************************************************************** 
'************************************************************** 


TYPE MD2Header                               ' file Header data 
   ident            AS INTEGER
   version          AS INTEGER
   skinwidth        AS INTEGER                  ' Width of skin texture (pixels) 
   skinheight       AS INTEGER                  ' Height of skin texture (pixels) 
   framesize        AS INTEGER
    ' Number of different components 
   num_skins        AS INTEGER                   ' Skins 
   num_xyz          AS INTEGER                   ' Vertices (x, y, z) 
   num_st           AS INTEGER                   ' Texture coordinates (s, t) 
   num_tris         AS INTEGER                   ' Triangles 
   num_glcmds       AS INTEGER                   ' GL Commands (which we aren't going to use) 
   num_frames       AS INTEGER                   ' Frames (transform matrices) 
    ' File offset of different components 
   ofs_skins        AS INTEGER                   ' Skins 
   ofs_st           AS INTEGER                   ' Texture coordinates (s,t) 
   ofs_tris         AS INTEGER                   ' Triangles 
   ofs_frames       AS INTEGER                   ' Frames 
   ofs_glcmds       AS INTEGER                   ' GL Commands (which we aren't going to use) 
   ofs_end          AS INTEGER                   ' End of file 
END TYPE


' 'These values are set at the max allowed MD2 specs, or dynamically allocate these after loading 
$DEFINE MD2_MAX_VERTICES 1024   '2048 
$DEFINE MD2_MAX_TRIANGLES 2048  '4096 
$DEFINE MD2_MAX_FRAMES 256      '512 
$DEFINE MD2_MAX_TEXT_COORD 1024 '2048 
$DEFINE MD2_MAX_ANIMATIONS 1024


TYPE QMD2model EXTENDS QMEMORYSTREAM
    TheModel                                     AS MD2Header
    vertex(MD2_MAX_FRAMES, MD2_MAX_VERTICES, 2)  AS BYTE         'each byte is rescaled by Frame 
    normal(MD2_MAX_FRAMES, MD2_MAX_VERTICES)     AS BYTE        'index into table of normal  
    TexCoord(MD2_MAX_TEXT_COORD, 1)              AS SINGLE      'actual values in SHORT 
    indexXYZ(MD2_MAX_TRIANGLES, 2)               AS SHORT       'index to which vertex 
    indexST(MD2_MAX_TRIANGLES, 2)                AS SHORT       'index for texture coord 
    Frame_Scale(MD2_MAX_FRAMES, 2)               AS SINGLE
    Frame_Translate(MD2_MAX_FRAMES, 2)           AS SINGLE
    Frame_Name(MD2_MAX_FRAMES)                   AS STRING * 16
    skins(32)                                    AS GLint       'texture ID's for OpenGL bindTexture 
    'individual render frame specific 
    StartFrame                                   AS INTEGER
    CurrentFrame                                 AS INTEGER     'for updating animation 
    MaxFrame                                     AS INTEGER     'of single frames 
    'animation list specific 
    AnimationFrame(MD2_MAX_ANIMATIONS)           AS GLint       'array of Displaylist handles 
    MaxAnimationFrame                            AS INTEGER     'highest display List index 
    KeyFrame(MD2_MAX_FRAMES)                     AS SHORT       'special locations in animation  
    KeyFrameIndex                                AS SHORT
    'fluffy stuff 
    Position                                     AS QD3DVECTOR  'set our 3D position 
    Rotation                                     As QD3DVECTOR  'set the rotation angle in degs for each axis 
    Scale                                        As QD3DVECTOR  'set scaling for each axis 
    hDC                                          AS LONG        'set handle for device context for auto flipping 

    'frames per second counters 
    FPSTimer             AS QTIMER                                'do background timing 
    FPS                  AS SINGLE  PROPERTY SET MD2_Set_FPS      'animation can be too fast 
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 
' Functions 
'''''''''''''''''''''' 
' LoadMD2 model, Input:  a MD2 Header structure, this allows multiple 3D objects 

SUB LoadMD2File (TheFileName AS STRING)
    DIM theFileDir$         AS STRING
    DIM TheFile             AS QFILESTREAM
    DIM i                   AS INTEGER
    DIM j                   AS INTEGER
    DIM TempStr             AS STRING
    DIM OpenDialog 		    AS QOPENDIALOG		'open file dialog 

    IF TheFileName = "" THEN
	openDialog.Caption = "select a Quake model "
    openDialog.filter = "*.MD2 (quake)|*.MD2"
        IF openDialog.execute THEN
    		theFileDir$ = OpenDialog.InitialDir	
    		theFileName = UCASE$(OpenDialog.FileName)
    	END IF
    END IF

    ' Open file, if it exists then load it up 
    IF FILEEXISTS(theFileDir$ + theFileName) THEN
        TheFile.Open(theFileDir$ + theFileName, fmOpenRead)
    ELSE
        ShowMessage "Failed to open " + TheFileName
        EXIT SUB            'be sure to compile with the latest fixed libs 
    END IF

    TheFile.ReadUDT(This.TheModel)

WITH THIS
    '------------------------------------------------------------- 
    'load the 'skins', which are texures from bitmap files 
    '------------------------------------------------------------- 
'    REDIM SMD2_skins(TheModel.num_skins)           'crashes 
    IF .TheModel.num_skins > 0 THEN
    TheFile.Seek(soFromBeginning, .TheModel.ofs_skins)   'position in the file with offset in .TheModel.ofs_skins 
         for i = 0 to .TheModel.num_skins - 1                    ' Load all texture files 
             TempStr = TheFile.ReadStr(64)                      ' Read texture name 
             TempStr = RTRIM$(REPLACESUBSTR$(TempStr, "/","\"))       'condition it 
             TempStr = LEFT$(TempStr,INSTR(TempStr, CHR$(0))-1)
            IF FileExists(theFileDir$ + tempStr) = False THEN
                theFileDir$ = ""                                'not in directory 
                IF FileExists(tempStr) = False THEN
                    tempStr = LEFT$(TheFileName, INSTR(TheFileName, ".MD2")-1) + ".bmp"  'next shot 
                IF FileExists(tempStr) = False THEN
                    tempStr = LEFT$(tempStr, INSTR(tempStr, ".bmp")-1) + ".jpg"  'last shot 
                END IF
                END IF
            END IF
            This.skins(i) = GL.CreateTextureFromFile(theFileDir$ + tempStr)  'Load texture 
            if This.skins(i) = 0 then
             ShowMessage "Failed to load texture: " + theFileDir$ + tempStr
'                 end 
             end if
         next i
     ELSE
         tempStr = LEFT$(TheFileName, INSTR(TheFileName, ".MD2")-1) + ".bmp"  'next shot 
         This.skins(0) =  GL.CreateTextureFromFile(tempStr)
     END IF

    '------------------------------------------------------------- 
    'get Texture coordinates (s, t) 
    '------------------------------------------------------------- 
    IF .TheModel.num_st > MD2_MAX_TEXT_COORD THEN         Showmessage "Err: Too many tex coords"
    TheFile.Seek(soFromBeginning, .TheModel.ofs_st)      ' Seek to texture coordinates 
    for i = 0 to .TheModel.num_st - 1
        This.TexCoord(i, 0) = (TheFile.ReadNum(num_WORD))/(1.0* QGLinternalBMP.Width)
        This.TexCoord(i, 1) = (TheFile.ReadNum(num_WORD))/(-1.0 * QGLinternalBMP.Height)
    next i

    '------------------------------------------------------------- 
    'get model triangles, indexes to all vertices 
    '------------------------------------------------------------- 
    IF .TheModel.num_tris > MD2_MAX_TRIANGLES THEN Showmessage "Err: Too many triangles"
    TheFile.Seek( soFromBeginning, .TheModel.ofs_tris)           ' Seek to triangles 
    for i = 0 to .TheModel.num_tris - 1          ' Load triangles 
        TheFile.LoadArray(This.indexXYZ(i, 0), 3)
        TheFile.LoadArray(This.indexST(i, 0), 3)
    next i

    '------------------------------------------------------------- 
    'get model frames, which are basically 4x4 matrices and all their vertices & normals 
    '------------------------------------------------------------- 
    IF .TheModel.num_frames > MD2_MAX_FRAMES THEN 
        Showmessage "Err: Too many frames"
        .MaxFrame = MD2_MAX_FRAMES
    ELSE
        .MaxFrame = .TheModel.num_frames
    END IF

    IF .TheModel.num_xyz > MD2_MAX_VERTICES THEN Showmessage "Err: Too many verts"
    TempStr = ""
    TheFile.Seek( soFromBeginning, .TheModel.ofs_frames)         ' Seek to frames 
    for i = 0 to .TheModel.num_frames - 1                        ' Load each animation frame 
        TheFile.LoadArray(This.Frame_Scale(i,0), 3)
        TheFile.LoadArray(This.Frame_Translate(i,0), 3)
        .Frame_Name(i)= TheFile.ReadStr(16)             'a char string with terminating 0's 
        .Frame_Name(i)= LTRIM$(RTRIM$((LEFT$(This.Frame_Name(i), INSTR(This.Frame_Name(i), CHR$(0))-1))))    'trim off zero chars 

        IF TempStr <> LEFT$(This.Frame_Name(i), 3) THEN
            TempStr = LEFT$(This.Frame_Name(i),3)
'        IF TempStr <> This.Frame_Name(i) - STR$(VAL(This.Frame_Name(i))) THEN 
'            TempStr = This.Frame_Name(i) - STR$(VAL(This.Frame_Name(i))) 
            .KeyFrameIndex++
            .KeyFrame(.KeyFrameIndex) = i                   'keep track of unique frames by name 
        END IF

        FOR j = 0 to .TheModel.num_xyz - 1                   ' Read vertices 
            TheFile.LoadArray(This.vertex(i,j,0) ,3)         'get 3D coord of vertices 
            TheFile.LoadArray(This.normal(i,j) ,  1)         'an indexes to normal table 
        NEXT j
    next i
    TheFile.Close
END WITH

END SUB


PRIVATE:

SUB DoTransformations
    WITH THIS
    IF (.Scale.x <> 1.0) OR (.Scale.y <> 1.0) OR (.Scale.z <> 1.0) THEN
        glScalef(This.Scale.x, This.Scale.y, This.Scale.z)
    END IF

    IF (.Position.x <> 0.0) OR (.Position.y <> 0.0) OR (.Position.z <> 0.0) THEN
        glTranslatef (This.Position.x, This.Position.y, This.Position.z)
    END IF

    IF (.Rotation.x <> 0.0) OR (.Rotation.y <> 0.0) OR (.Rotation.z <> 0.0) THEN
        glRotatef (This.Rotation.x, 1, 0, 0)
        glRotatef (This.Rotation.y, 0, 1, 0)
        glRotatef (This.Rotation.z, 0, 0, 1)
    END IF
    END WITH
END SUB


PUBLIC:

SUB SetPosition (X AS SINGLE, Y AS SINGLE, Z AS SINGLE)        'like Qd3dFrame 
    This.Position.x = x
    This.Position.y = y
    This.Position.z = z
'    This.DoTransformations 
END SUB


SUB SetRotation (X AS SINGLE, Y AS SINGLE, Z AS SINGLE)
    This.Rotation.x = x
    This.Rotation.y = y
    This.Rotation.z = z
'    This.DoTransformations 
END SUB


SUB SetScale (X AS SINGLE, Y AS SINGLE, Z AS SINGLE)        'like Qd3dFrame 
    This.Scale.x = x
    This.Scale.y = y
    This.Scale.z = z
'    This.DoTransformations 
END SUB



'''''''''''''''''' 
    ' Draw an interpolated MD2 frame 
    ' Expects: 
    '   theModel        = pointer to model 
    '   theFrameNum     = index of first frame 
    '   theFrameNum2    = index of the second frame 
    '   theFrameFactor = 0 to draw the first frame, 1 to draw the second frame, or a value between 0 and 1 
    '   theSkin         = skin to use 

SUB DrawMD2 (theFrameNum AS INTEGER, theFrameNum2 AS INTEGER, theFrameFactor AS SINGLE, theSkin AS INTEGER)
    DIM i as integer, i2 as integer, i3 as integer
    DIM tempVec(2)      AS SINGLE
    DIM PartFrame       AS SINGLE

    This.DoTransformations
    glBindTexture (GL_TEXTURE_2D, This.skins(theSkin))
    ' Apply model scaling 
     glMatrixMode (GL_MODELVIEW)
     glPushMatrix 

     glRotatef( -90, 1, 0, 0 )
     glRotatef( -90, 0, 0, 1 )
     PartFrame = 1.0 - theFrameFactor           'get the remaining portion of frame 
    'linear interpolate position between frames 
     tempVec(0) = This.Frame_translate(TheFrameNum, 0) * PartFrame + This.Frame_translate(TheFrameNum2, 0) * theFrameFactor
     tempVec(1) = This.Frame_translate(TheFrameNum, 1) * PartFrame + This.Frame_translate(TheFrameNum2, 1) * theFrameFactor
     tempVec(2) = This.Frame_translate(TheFrameNum, 2) * PartFrame + This.Frame_translate(TheFrameNum2, 2) * theFrameFactor
     glTranslatef (tempVec (0), tempVec (1), tempVec (2))

     'linear interpolate scale between frames 
     tempVec(0) = This.Frame_scale(TheFrameNum, 0) * PartFrame + This.Frame_scale(TheFrameNum2, 0) * theFrameFactor
     tempVec(1) = This.Frame_scale(TheFrameNum, 1) * PartFrame + This.Frame_scale(TheFrameNum2, 1) * theFrameFactor
     tempVec(2) = This.Frame_scale(TheFrameNum, 2) * PartFrame + This.Frame_scale(TheFrameNum2, 2) * theFrameFactor
     glScalef (tempVec (0), tempVec (1), tempVec (2))

    ' Render each triangle 
     glBegin (GL_TRIANGLES)
        for i = 0 to This.TheModel.num_tris - 1
            for i2 = 0 to 2
'                glNormal3fv (This.Frames(TheFrameNum).verts (This.tris(i).index_xyz (i2)).normal * (1 - theFrameFactor) + This.Frames(TheFrameNum2).verts (This.tris(i).index_xyz (i2)).normal * theFrameFactor) 
                glTexCoord2fv(This.TexCoord(This.indexST(i,i2),0))
                tempVec(0) = This.Vertex(theFrameNum2, This.indexXYZ(i, i2), 0) * theFrameFactor + _
                              This.Vertex(theFrameNum, This.indexXYZ(i, i2), 0) * PartFrame
                tempVec(1) = This.Vertex(theFrameNum2, This.indexXYZ(i, i2), 1) * theFrameFactor + _
                              This.Vertex(theFrameNum, This.indexXYZ(i, i2), 1) * PartFrame
                tempVec(2) = This.Vertex(theFrameNum2, This.indexXYZ(i, i2), 2) * theFrameFactor + _
                              This.Vertex(theFrameNum, This.indexXYZ(i, i2), 2) * PartFrame
                glVertex3fv(tempVec(0))
            next i2
        next i
    glEnd
    glPopMatrix                         ' Restore modelview matrix 
    glMatrixMode (GL_MODELVIEW)
END SUB



FUNCTION CreateAnimationSet(FirstFrame AS INTEGER, LastFrame AS INTEGER, FrameFactor AS SINGLE) AS LONG
    DIM i as integer, NumFrame as integer
    DIM FrameStep as single

    WITH THIS
    .StartFrame = .MaxAnimationFrame    'start from last animation index 
    NumFrame = FirstFrame               'set our Frame counter 
    FrameStep = FrameFactor             'and the portion to next frame 

    DO
        .AnimationFrame(.MaxAnimationFrame) = glGenLists(1)    'allow seperate lists by start index 
        glNewList( .AnimationFrame(.MaxAnimationFrame), GL_COMPILE)
        This.DrawMD2 (NumFrame, NumFrame + 1, FrameStep, This.skins(1))
        glEndList
        .MaxAnimationFrame++                                    'next list index 
        IF .MaxAnimationFrame > MD2_MAX_ANIMATIONS THEN EXIT DO 'out of room 
        FrameStep = FrameStep + FrameFactor                     'next part of animation 
        IF FrameStep > 1.0 THEN FrameStep = 1.0: NumFrame++     'if finished to next frame, increment 
    LOOP UNTIL (NumFrame >= LastFrame)
    RESULT = .StartFrame                                      'return start number of animation 
    END WITH
END FUNCTION



SUB Animate
    static i as integer

    IF i <> This.CurrentFrame THEN
        This.DoTransformations
        glCallList(This.AnimationFrame(This.CurrentFrame))
        i = This.CurrentFrame
        IF This.hDC <> 0 THEN SwapBuffers This.hDC
    END IF
END SUB


SUB MD2_Set_FPS(TheFramesPerSecond AS SINGLE)
    IF TheFramesPerSecond > 0.0 THEN 
        This.FPS = TheFramesPerSecond
        This.FPSTimer.Interval = INT(1000/This.FPS)     'timer units in ms 
        IF This.FPSTimer.Enabled = 0 THEN This.FPSTimer.Enabled = 1
    ELSE
        This.FPSTimer.Enabled = 0
    END IF
END SUB


SUB FPSTimerOver
    This.CurrentFrame++
    if This.CurrentFrame => This.MaxAnimationFrame then This.CurrentFrame = This.StartFrame
END SUB


SUB New()
    WITH THIS
    .hDC = 0
    .FPSTimer.Enabled = 0
    .FPSTimer.OnTimer = This.FPSTimerOver
    .StartFrame = 0
    .CurrentFrame = 0
    .MaxFrame= 0
    .MaxAnimationFrame = 0
    .KeyFrameIndex = 0
    .Position.x = 0.0
    .Position.y = 0.0
    .Position.z = 0.0
    .Rotation.x = 0.0
    .Rotation.y = 0.0
    .Rotation.z = 0.0
    .Scale.x = 1.0
    .Scale.y = 1.0
    .Scale.z = 1.0
    END WITH
END SUB


SUB Clear
    DIM i AS INTEGER

    WITH THIS
    IF .MaxAnimationFrame > 0 THEN glDeleteLists(.AnimationFrame(0), .MaxAnimationFrame + 1)
      .New
      MEMSET(VARPTR(This.vertex(0,0,0)), 0, (MD2_MAX_FRAMES * MD2_MAX_VERTICES * 2))
      MEMSET(VARPTR(This.normal(0,0)), 0, (MD2_MAX_FRAMES * MD2_MAX_VERTICES))
      MEMSET(VARPTR(This.TexCoord(0,0)), 0, (MD2_MAX_TEXT_COORD * 2 * SIZEOF(SINGLE)))
      MEMSET(VARPTR(This.indexXYZ(0, 0)), 0, (MD2_MAX_TRIANGLES* 3 * SIZEOF(SHORT)))
      MEMSET(VARPTR(This.indexST(0 ,0)), 0, (MD2_MAX_TRIANGLES* 3 * SIZEOF(SHORT)))
      MEMSET(VARPTR(This.Frame_Scale(0, 0)), 0, (MD2_MAX_FRAMES* 3 * SIZEOF(SINGLE)))
      MEMSET(VARPTR(This.Frame_Translate(0, 0)), 0, (MD2_MAX_FRAMES* 3 * SIZEOF(SINGLE)))
      MEMSET(VARPTR(This.Frame_Name(0)), 0,(MD2_MAX_FRAMES * 16))
      MEMSET(VARPTR(This.skins(0)), 0, (32 * SIZEOF(GLint)))
    END WITH
END SUB



SUB CalcMD2Normals
'     DIM i As INTEGER, i2 As INTEGER, i3 As INTEGER 
'DIM tempNormal(2)   AS SINGLE 
'DIM tempTriangle(2,2) AS SINGLE 

'     ' Note: The MD2 file format stores normals as indexed lookups into some normal table. 
'     ' I cannot find this table, so I will simply recalculate them. 
'  
'     ' Calculate for all frames 
'     for i3 = 0 to theModel.num_frames - 1 
'         for i = 0 to theModel.num_tris - 1 
'             for i2 = 0 to 2 
'                 tempTriangle (i2) = theFrame.verts (Stris(i).index_xyz (i2)).v 
'             next 
'  
'             ' Calculate normal as cross product between edges 0-1 and 0-2 
'             tempNormal = Normalize (-CrossProduct (tempTriangle (1) - tempTriangle (0), tempTriangle (2) - tempTriangle (0))) 
'  
'             ' Add new normal to normals at ALL vertices of the triangle. 
'             ' This will have an averaging effect over vertices that are shared between multiple triangles 
'             for i2 = 0 to 2 
'                 theFrame.verts (Stris(i).index_xyz (i2)).normal = theFrame.verts (Stris(i).index_xyz (i2)).normal + tempNormal 
'             next 
'         next 
'  
'         ' Normalise all normals 
'         for i = 0 to theModel.num_xyz - 1 
'             theFrame.verts (i).normal = Normalize (theFrame.verts (i).normal) 
'         next 
'     next 
END SUB


CONSTRUCTOR
    New
END CONSTRUCTOR

END TYPE