#COMPILE EXE "3DObj.exe"

#INCLUDE "Win32API.inc"
#INCLUDE "GL.inc"
#INCLUDE "GLU.inc"
#LINK "mobj.sll"

DECLARE FUNCTION zTrace LIB "zTrace.DLL" ALIAS "zTrace" (zMessage AS ASCIIZ) AS LONG

'//-----------------------------------------------------------------------------
'// Constants.
'//-----------------------------------------------------------------------------

MACRO CAMERA_FOVY  = 60.0
MACRO CAMERA_ZFAR  = 10.0
MACRO CAMERA_ZNEAR = 0.01

MACRO MOUSE_ORBIT_SPEED = 0.30     '// 0 = SLOWEST, 1 = FASTEST
MACRO MOUSE_DOLLY_SPEED = 0.02     '// same as above...but much more sensitive
MACRO MOUSE_TRACK_SPEED = 0.005    '// same as above...but much more sensitive

%CAMERA_NONE  = 0
%CAMERA_TRACK = 1
%CAMERA_DOLLY = 2
%CAMERA_ORBIT = 3

'//-----------------------------------------------------------------------------
'// Type definitions.
'//-----------------------------------------------------------------------------

'typedef std::map<std::string, GLuint> ModelTextures

'//-----------------------------------------------------------------------------
'// Globals.
'//-----------------------------------------------------------------------------

GLOBAL g_hWnd, g_hDC, g_hRC AS DWORD
GLOBAL g_hInstance, g_windowWidth, g_windowHeight, g_isFullScreen AS LONG

GLOBAL gr_heading, gr_pitch AS SINGLE
GLOBAL gr_cameraPos(), gr_targetPos()  AS SINGLE
'ModelTextures       g_modelTextures


FUNCTION WINMAIN (BYVAL hInstance     AS LONG, _
                  BYVAL hPrevInstance AS LONG, _
                  BYVAL lpCmdLine     AS ASCIIZ PTR, _
                  BYVAL nShowCmd      AS LONG) AS LONG

    LOCAL Msg           AS tagMsg
    LOCAL wcl           AS WndClassEx
    LOCAL szClass       AS ASCIIZ * 16
    LOCAL IsInitialized AS LONG

    szClass = "GLWindowClass"
    g_hInstance = hInstance

    wcl.cbSize = SIZEOF(wcl)
    IsInitialized = GetClassInfoEx(hInstance, szClass, wcl)
    IF IsInitialized   = 0 THEN
       wcl.style = %CS_OWNDC OR %CS_HREDRAW OR %CS_VREDRAW
       wcl.lpfnWndProc = CODEPTR(WndProc)
       wcl.cbClsExtra = 0
       wcl.cbWndExtra = 0
       wcl.hInstance = g_hInstance
       wcl.hIcon = %NULL
       wcl.hCursor = LoadCursor(%NULL, BYVAL %IDC_ARROW)
       wcl.hbrBackground = 16
       wcl.lpszMenuName = %NULL
       wcl.lpszClassName = VARPTR(szClass)
       wcl.hIconSm = 0
       IF RegisterClassEx(wcl) THEN IsInitialized = -1
    END IF

    IF NOT IsInitialized THEN FUNCTION = 0: EXIT FUNCTION

    '// Create a window that is centered on the desktop. It's exactly 1/4 the
    '// size of the desktop. Don't allow it to be resized.

    LOCAL wndExStyle AS DWORD: wndExStyle = %WS_EX_OVERLAPPEDWINDOW
    LOCAL wndStyle AS DWORD
    wndStyle = %WS_OVERLAPPED OR %WS_CAPTION OR %WS_SYSMENU OR %WS_MINIMIZEBOX OR %WS_CLIPCHILDREN OR %WS_CLIPSIBLINGS

    g_hWnd = CreateWindowEx(wndExStyle, szClass, "", wndStyle, 0, 0, 0, 0, 0, 0, g_hInstance, BYVAL %NULL)

    IF g_hWnd THEN
        LOCAL nScreenWidth, nScreenHeight, nHalfScreenWidth, nHalfScreenHeight, nLeft, nTop AS LONG
        nScreenWidth = GetSystemMetrics(%SM_CXSCREEN)
        nScreenHeight = GetSystemMetrics(%SM_CYSCREEN)
        nHalfScreenWidth = nScreenWidth \ 2
        nHalfScreenHeight = nScreenHeight \ 2
        nLeft = (nScreenWidth - nHalfScreenWidth) \ 2
        nTop = (nScreenHeight - nHalfScreenHeight) \ 2
        LOCAL rc AS RECT

        SetRect(rc, nLeft, nTop, nLeft + nHalfScreenWidth, nTop + nHalfScreenHeight)
        AdjustWindowRectEx(rc, wndStyle, %FALSE, wndExStyle)
        MoveWindow(g_hWnd, rc.nLeft, rc.nTop, rc.right - rc.nLeft, rc.bottom - rc.nTop, %TRUE)

        GetClientRect(g_hWnd, rc)
        g_windowWidth = rc.right - rc.nLeft
        g_windowHeight = rc.bottom - rc.nTop

        InitGL()

        ShowWindow(g_hWnd, nShowCmd)
        UpdateWindow(g_hWnd)

        SetTimer(g_hWnd, 1, 0, 0) ' 66 FPS
        WHILE GetMessage(Msg, 0, 0, 0)
              TranslateMessage(Msg)
              DispatchMessage(Msg)
        WEND
        KillTimer(g_hWnd, 1)


        Cleanup()
        UnregisterClass(szClass, g_hInstance)
    END IF

    FUNCTION = msg.wParam

END FUNCTION

FUNCTION WndProc(BYVAL hWnd AS DWORD, BYVAL msg AS DWORD, BYVAL wParam AS LONG, BYVAL lParam AS LONG) AS LONG

    SELECT CASE LONG (msg)

    CASE %WM_TIMER
         DrawFrame()
         FUNCTION = 0: EXIT FUNCTION

    CASE %WM_COMMAND:
        FUNCTION = 0: EXIT FUNCTION

    CASE %WM_DESTROY:
        PostQuitMessage(0)
        FUNCTION = 0: EXIT FUNCTION

    CASE %WM_SIZE:
        g_windowWidth = LO(WORD, lParam)
        g_windowHeight = HI(WORD, lParam)

    CASE ELSE
        ProcessMouseInput(hWnd, msg, wParam, lParam)

    END SELECT

    FUNCTION = DefWindowProc(hWnd, msg, wParam, lParam)
END FUNCTION

SUB Cleanup()

    Mobj_destroy()

    IF (g_hDC) THEN
        IF (g_hRC) THEN
            wglMakeCurrent(g_hDC, 0)
            wglDeleteContext(g_hRC)
            g_hRC = 0
        END IF
        ReleaseDC(g_hWnd, g_hDC)
        g_hDC = 0
    END IF
END SUB

SUB DrawFrame()
    glViewport(0, 0, g_windowWidth, g_windowHeight)
    glClearColor(0.3, 0.5, 0.9, 0.0)
    glClear(%GL_COLOR_BUFFER_BIT OR %GL_DEPTH_BUFFER_BIT)

    glMatrixMode(%GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(CAMERA_FOVY, g_windowWidth / g_windowHeight, CAMERA_ZNEAR, CAMERA_ZFAR)

    glMatrixMode(%GL_MODELVIEW)
    glLoadIdentity()
    gluLookAt(gr_cameraPos(0), gr_cameraPos(1), gr_cameraPos(2), gr_targetPos(0), gr_targetPos(1), gr_targetPos(2), _
              0.0, 1.0, 0.0)

    glRotatef(gr_pitch, 1.0, 0.0, 0.0)
    glRotatef(gr_heading, 0.0, 1.0, 0.0)

    DrawModelUsingFixedFuncPipeline()

    SwapBuffers(g_hDC)

END SUB

SUB InitGL()

    g_hDC = GetDC(g_hWnd)
    IF g_hDC = 0 THEN MsgBox "GetDC() failed.": EXIT SUB

    LOCAL pf, msaaSamples AS LONG
    LOCAL pfd AS PIXELFORMATDESCRIPTOR

    pfd.nSize              = SIZEOF(pfd)
    pfd.nVersion           = 1
    pfd.dwFlags            = %PFD_DRAW_TO_WINDOW OR %PFD_SUPPORT_OPENGL OR %PFD_DOUBLEBUFFER
    pfd.iPixelType         = 0 ' PFD_TYPE_RGBA
    pfd.cColorBits         = 32
    pfd.cAlphaBits         = 1
    pfd.cDepthBits         = 16
    pfd.iLayerType         = 0 ' %PFD_MAIN_PLANE

    ChooseBestMultiSampleAntiAliasingPixelFormat(pf, msaaSamples)
    
    IF pf = 0 THEN pf = ChoosePixelFormat(g_hDC, pfd)

    IF SetPixelFormat(g_hDC, pf, pfd) THEN
       g_hRC = wglCreateContext(g_hDC): IF g_hRC = 0 THEN MsgBox " wglCreateContext error.": EXIT SUB
       IF wglMakeCurrent(g_hDC, g_hRC) = 0 THEN MsgBox "wglMakeCurrent() failed.": EXIT SUB

       glEnable(%GL_TEXTURE_2D)
       glEnable(%GL_DEPTH_TEST)
       glEnable(%GL_CULL_FACE)
       glEnable(%GL_LIGHTING)
       glEnable(%GL_LIGHT0)

       LoadModel(EXE.Path$ + "venusm.obj")
       'LoadModel(EXE.Path$ +"altair.obj")
       'LoadModel(EXE.Path$ +"chariot.obj")
       'LoadModel(EXE.Path$ +"dragon1.obj")

       ResetCamera()
    END IF

END SUB

SUB LoadModel(pszFilename AS ASCIIZ)

    '// Import the OBJ file and normalize to unit length.

    SetCursor(LoadCursor(%NULL, BYVAL %IDC_WAIT))

    IF Mobj_import(pszFilename, 0) THEN
       Mobj_normalize(1.0, %TRUE)
    END IF

    SetCursor(LoadCursor(%NULL, BYVAL %IDC_ARROW))

END SUB

SUB ProcessMouseInput(BYVAL hWnd AS DWORD, BYVAL msg AS DWORD, BYVAL wParam AS LONG, BYVAL lParam AS LONG)

    '// Use the left mouse button to track the camera.
    '// Use the middle mouse button to dolly the camera.
    '// Use the right mouse button to orbit the camera.

    STATIC CameraMode, mouseButtonsDown AS LONG
    STATIC ptMousePrev AS POINTAPI
    STATIC ptMouseCurrent AS POINTAPI
    STATIC rx, ry AS SINGLE

    SELECT CASE LONG msg

    CASE %WM_LBUTTONDOWN:
        cameraMode = %CAMERA_ORBIT
        INCR mouseButtonsDown
        SetCapture(hWnd)
        ptMousePrev.x = LO(INTEGER, lParam)
        ptMousePrev.y = HI(INTEGER, lParam)
        ClientToScreen(hWnd, ptMousePrev)

    CASE %WM_RBUTTONDOWN:
        cameraMode = %CAMERA_TRACK
        INCR mouseButtonsDown
        SetCapture(hWnd)
        ptMousePrev.x = LO(INTEGER, lParam)
        ptMousePrev.y = HI(INTEGER, lParam)
        ClientToScreen(hWnd, ptMousePrev)

    CASE %WM_MBUTTONDOWN:
        cameraMode = %CAMERA_DOLLY
        INCR mouseButtonsDown
        SetCapture(hWnd)
        ptMousePrev.x = LO(INTEGER, lParam)
        ptMousePrev.y = HI(INTEGER, lParam)
        ClientToScreen(hWnd, ptMousePrev)

    CASE %WM_MOUSEMOVE:
        ptMouseCurrent.x = LO(INTEGER, lParam)
        ptMouseCurrent.y = HI(INTEGER, lParam)
        ClientToScreen(hWnd, ptMouseCurrent)

        SELECT CASE LONG cameraMode
        CASE %CAMERA_TRACK:
            rx = (ptMouseCurrent.x - ptMousePrev.x) * MOUSE_TRACK_SPEED
            ry = (ptMouseCurrent.y - ptMousePrev.y) * MOUSE_TRACK_SPEED

            gr_cameraPos(0) -= rx
            gr_cameraPos(1) += ry

            gr_targetPos(0) -= rx
            gr_targetPos(1) += ry

        CASE %CAMERA_DOLLY:
            ry = (ptMouseCurrent.y - ptMousePrev.y) * MOUSE_DOLLY_SPEED

            gr_cameraPos(2) -= ry

            IF (gr_cameraPos(2) < Mobj_getRadius() + CAMERA_ZNEAR) THEN gr_cameraPos(2) = Mobj_getRadius() + CAMERA_ZNEAR
            IF (gr_cameraPos(2) > CAMERA_ZFAR - Mobj_getRadius()) THEN gr_cameraPos(2) = CAMERA_ZFAR - Mobj_getRadius()

        CASE %CAMERA_ORBIT:
            rx = (ptMouseCurrent.x - ptMousePrev.x) * MOUSE_ORBIT_SPEED
            ry = (ptMouseCurrent.y - ptMousePrev.y) * MOUSE_ORBIT_SPEED

            gr_heading += rx
            gr_pitch += ry

            IF gr_pitch > 90.0 THEN gr_pitch = 90.0
            IF gr_pitch < -90.0 THEN gr_pitch = -90.0

        END SELECT

        ptMousePrev.x = ptMouseCurrent.x
        ptMousePrev.y = ptMouseCurrent.y

    CASE %WM_LBUTTONUP, %WM_RBUTTONUP, %WM_MBUTTONUP:
        DECR mouseButtonsDown
        IF (mouseButtonsDown < 1) THEN
            mouseButtonsDown = 0
            cameraMode = %CAMERA_NONE
            ReleaseCapture()
        ELSE
            IF (wParam AND %MK_LBUTTON) THEN
               cameraMode = %CAMERA_TRACK
            ELSEIF (wParam AND %MK_RBUTTON) THEN
               cameraMode = %CAMERA_ORBIT
            ELSEIF (wParam AND %MK_MBUTTON) THEN
               cameraMode = %CAMERA_DOLLY
            END IF
        END IF

    CASE ELSE

    END SELECT

END SUB

SUB ResetCamera()

    REDIM gr_targetPos(2), gr_cameraPos(2) AS SINGLE

    Mobj_getCenter(gr_targetPos(0), gr_targetPos(1), gr_targetPos(2))

    gr_cameraPos(0) = gr_targetPos(0)
    gr_cameraPos(1) = gr_targetPos(1)
    gr_cameraPos(2) = gr_targetPos(2) + Mobj_getRadius() + CAMERA_ZNEAR + 0.4

    gr_pitch = 0.0
    gr_heading = 0.0

END SUB

DECLARE SUB glEnableClientState LIB "OPENGL32.DLL" ALIAS "glEnableClientState" (BYVAL array AS DWORD)
DECLARE SUB My_glVertexPointer LIB "OPENGL32.DLL" ALIAS "glVertexPointer" (BYVAL size AS LONG, BYVAL type AS DWORD, BYVAL stride AS LONG, BYVAL DWORD)
DECLARE SUB My_glTexCoordPointer LIB "OPENGL32.DLL" ALIAS "glTexCoordPointer" (BYVAL size AS LONG, BYVAL type AS DWORD, BYVAL stride AS LONG, BYVAL DWORD)
DECLARE SUB My_glNormalPointer LIB "OPENGL32.DLL" ALIAS "glNormalPointer" (BYVAL type AS DWORD, BYVAL stride AS LONG, BYVAL DWORD)
DECLARE SUB glDisableClientState LIB "OPENGL32.DLL" ALIAS "glDisableClientState" (BYVAL array AS DWORD)
DECLARE SUB My_glDrawElements LIB "OPENGL32.DLL" ALIAS "glDrawElements" (BYVAL mode aS DWORD, BYVAL count AS LONG, BYVAL type AS DWORD, BYVAL DWORD)

TYPE Material
    ambient(0 TO 3)    AS SINGLE
    diffuse(0 TO 3)    AS SINGLE
    specular(0 TO 3)   AS SINGLE
    shininess          AS SINGLE '// (0 = min shininess, 1 = max shininess)
    alpha              AS SINGLE '// (0 = fully transparent, 1 = fully opaque)

    szMapName          AS ASCIIZ * %MAX_PATH
    szColorMapFilename AS ASCIIZ * %MAX_PATH
    szBumpMapFilename  AS ASCIIZ * %MAX_PATH
END TYPE

TYPE VertexT
    position(0 TO 2)   AS SINGLE
    texCoord(0 TO 1)   AS SINGLE
    normal(0 TO 2)     AS SINGLE
    tangent(0 TO 3)    AS SINGLE
    bitangent(0 TO 2)  AS SINGLE
END TYPE

TYPE Mesh
    startIndex         AS LONG
    triangleCount      AS LONG
    pMaterial          AS Material PTR
END TYPE

SUB DrawModelUsingFixedFuncPipeline()

    LOCAL pMesh AS Mesh PTR
    LOCAL pMaterial AS Material PTR
    LOCAL pVertices AS VertexT PTR
    LOCAL nI, nCount, nVertexSize AS LONG
    LOCAL nPtr AS DWORD

    nVertexSize = Mobj_getVertexSize()
    FOR nI = 0 TO Mobj_getNumberOfMeshes() - 1

        pMesh = Mobj_getMesh(nI)
        pMaterial = @pMesh.pMaterial
        pVertices = Mobj_getVertexBuffer()

        glMaterialfv(%GL_FRONT_AND_BACK, %GL_AMBIENT, @pMaterial.ambient(0))
        glMaterialfv(%GL_FRONT_AND_BACK, %GL_DIFFUSE, @pMaterial.diffuse(0))
        glMaterialfv(%GL_FRONT_AND_BACK, %GL_SPECULAR, @pMaterial.specular(0))
        glMaterialf(%GL_FRONT_AND_BACK, %GL_SHININESS, @pMaterial.shininess * 128.0)


        IF Mobj_hasPositions() THEN
            glEnableClientState(%GL_VERTEX_ARRAY)
            My_glVertexPointer(3, %GL_FLOAT, nVertexSize, Mobj_getVertexBufferPosition())
        END IF


        IF Mobj_hasNormals() THEN
            glEnableClientState(%GL_NORMAL_ARRAY)
            My_glNormalPointer(%GL_FLOAT, nVertexSize, Mobj_getVertexBufferNormal())
        END IF


        My_glDrawElements(%GL_TRIANGLES, @pMesh.triangleCount * 3, %GL_UNSIGNED_INT, Mobj_getIndexBuffer() + @pMesh.startIndex)

        IF Mobj_hasNormals() THEN glDisableClientState(%GL_NORMAL_ARRAY)

        IF Mobj_hasPositions() THEN glDisableClientState(%GL_VERTEX_ARRAY)
    NEXT

END SUB
