//-----------------------------------------------------------------------------
// RGL.cpp
//-----------------------------------------------------------------------------
//
// Robert's Graphics Library for Direct3D 8
//
// Copyright (c) 2000 Robert Rose.  All Rights Reserved Worldwide.
// rose@engr.orst.edu | robert_w_rose@hp.com
//
// Summary:
//
// This framework allows you to quickly build a simple Direct3D application
// with only a basic understanding of Windows programming, and almost zero
// knowledge of how DirectX works.  RGL is modeled after OpenGL, so it
// should come fairly naturally for you OpenGL programmers ;-)
//
// Features (or limitations):
//
// * RGL handles for you many of the mundane object drawing routines.
// * Your app is in charge of handling windows events.
// * RGL can hinder hardware Texture and Lighting (TnL).
// * RGL is fullscreen only.
// * RGL does not support texture mapping.
// * RGL uses a right-handed coordinate system.
//
// To use RGL in your application:
//
//	0. Install the DirectX 8 SDK
//	1. Copy the files rgl.cpp and rgl.h into your project directory
//	2. Include the libraries d3dx8.lib and d3d8.lib into your project
//
// Operation:
//
//	1. Make a window.  This is your app's responsibility.
//	2. Call RGLInit() and pass is the window handle.
//	3. Call RGLBeginRender() to begin drawing a frame.
//	4. Perform your drawing. (Call RGL drawing functions).
//	5. Call RGLEndRender() when you're done drawing the frame.
//	... repeat 3-5
//	6. Call RGLShutdown() and then close you're app.
//


#include <d3dx8.h>
#include "rgl.h"

LPDIRECT3D8             g_pD3D       = NULL;
LPDIRECT3DDEVICE8       g_pd3dDevice = NULL;

//-----------------------------------------------------------------------------
// RGLNewVertexBuffer
//-----------------------------------------------------------------------------
// Creates a new Direct3D Vertex Buffer of size numVerts.
// Remember to call RGLDisposeVertexBuffer() when you're done.

HRESULT RGLNewVertexBuffer(LPDIRECT3DVERTEXBUFFER8 *theBuffer, const RGLVertex *vertices,
						   long numVerts)
{
	// allocate the buffer
	if( FAILED( g_pd3dDevice->CreateVertexBuffer( numVerts*sizeof(RGLVertex),
                                                  0, D3DFVF_RGLVertex,
                                                  D3DPOOL_DEFAULT, theBuffer)))
    {
        return E_FAIL;
    }

    // fill in the buffer
    RGLVertex* pVertices;
    if( FAILED( (*theBuffer)->Lock( 0, sizeof(RGLVertex) * numVerts, (BYTE**)&pVertices, 0 )))
        return E_FAIL;
    memcpy( pVertices, vertices, sizeof(RGLVertex) * numVerts);
    (*theBuffer)->Unlock();


    return S_OK;
}

//-----------------------------------------------------------------------------
// RGLDisposeVertexBuffer
//-----------------------------------------------------------------------------

HRESULT RGLDisposeVertexBuffer(LPDIRECT3DVERTEXBUFFER8 *theBuffer)
{
	if( *theBuffer != NULL )        
        return (*theBuffer)->Release();
	return S_OK;
}

//-----------------------------------------------------------------------------
// RGLDrawLine
//-----------------------------------------------------------------------------
// Draws a single straight line.
// Takes a pointer to a structure of two RGLVertex's that specify the
// ends of the line.

HRESULT RGLDrawLine(const RGLVertex theVerts[2])
{
	LPDIRECT3DVERTEXBUFFER8 theBuffer = NULL;
	HRESULT hr;

	if(hr = RGLNewVertexBuffer(&theBuffer, theVerts, 2) != S_OK)
		return hr;

	if(theBuffer == NULL)
		return E_FAIL;

    g_pd3dDevice->SetStreamSource( 0, theBuffer, sizeof(RGLVertex) );
    g_pd3dDevice->SetVertexShader( D3DFVF_RGLVertex );
    g_pd3dDevice->DrawPrimitive( D3DPT_LINELIST, 0, 1 );

    return RGLDisposeVertexBuffer(&theBuffer);
}

//-----------------------------------------------------------------------------
// RGLDrawFacetN
//-----------------------------------------------------------------------------
// Draws a single N-point facet.
// Points are to be specified as a triangle strip.
// 'n' is the number of vertices.

HRESULT RGLDrawFacetN(const RGLVertex theVerts[], long n)
{
	LPDIRECT3DVERTEXBUFFER8 theBuffer = NULL;
	HRESULT hr;

	if(hr = RGLNewVertexBuffer(&theBuffer, theVerts, n) != S_OK)
		return hr;

	if(theBuffer == NULL)
		return E_FAIL;

    g_pd3dDevice->SetStreamSource( 0, theBuffer, sizeof(RGLVertex) );
    g_pd3dDevice->SetVertexShader( D3DFVF_RGLVertex );
    g_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP , 0, n - 2);

    return RGLDisposeVertexBuffer(&theBuffer);
}

//-----------------------------------------------------------------------------
// RGLLoadIdentity
//-----------------------------------------------------------------------------

HRESULT RGLLoadIdentity()
{
	D3DXMATRIX matIdent;
	D3DXMatrixIdentity(&matIdent);
	return g_pd3dDevice->SetTransform( D3DTS_WORLD, &matIdent );
}

//-----------------------------------------------------------------------------
// RGLRotate
//-----------------------------------------------------------------------------

HRESULT RGLRotate(float angle, float dx, float dy, float dz)
{
	D3DXMATRIX matRot;
	D3DXMatrixRotationAxis(&matRot, &D3DXVECTOR3(dx, dy, dz), angle);
	return g_pd3dDevice->MultiplyTransform( D3DTS_WORLD, &matRot );
}

//-----------------------------------------------------------------------------
// RGLTranslate
//-----------------------------------------------------------------------------

HRESULT RGLTranslate(float dx, float dy, float dz)
{
	D3DXMATRIX matTran;
	D3DXMatrixTranslation(&matTran, dx, dy, dz);
	return g_pd3dDevice->MultiplyTransform( D3DTS_WORLD, &matTran );
}

//-----------------------------------------------------------------------------
// RGLScale
//-----------------------------------------------------------------------------

HRESULT RGLScale(float dx, float dy, float dz)
{
	D3DXMATRIX matScale;
	D3DXMatrixScaling(&matScale, dx, dy, dz);
	return g_pd3dDevice->MultiplyTransform( D3DTS_WORLD, &matScale );
}

//-----------------------------------------------------------------------------
// RGLLookAt
//-----------------------------------------------------------------------------

HRESULT RGLLookAt(float eyex, float eyey, float eyez, float vx, float vy, float vz,
			float upx, float upy, float upz)
{
	D3DXMATRIX matView;
    D3DXMatrixLookAtRH( &matView, &D3DXVECTOR3( eyex, eyey, eyez ),
                                  &D3DXVECTOR3( vx, vy, vz ),
                                  &D3DXVECTOR3( upx, upy, upz ) );
    return g_pd3dDevice->SetTransform( D3DTS_VIEW, &matView );
}

//-----------------------------------------------------------------------------
// RGLPerspective
//-----------------------------------------------------------------------------

HRESULT RGLPerspective(float width, float height, float nearz, float farz)
{
    D3DXMATRIX matProj;
    D3DXMatrixPerspectiveRH( &matProj, width, height, nearz, farz );
    return g_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );
}

//-----------------------------------------------------------------------------
// RGLBeginRender
//-----------------------------------------------------------------------------

void RGLBeginRender()
{
    if( NULL == g_pd3dDevice )
        return;

    // Clear the backbuffer
    g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0,0,0), 1.0f, 0 );
    
    // Begin the scene
    g_pd3dDevice->BeginScene();
}

//-----------------------------------------------------------------------------
// RGLEndRender
//-----------------------------------------------------------------------------

void RGLEndRender()
{
    // End the scene
    g_pd3dDevice->EndScene();
    
    // Present the backbuffer contents to the display
    g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
}

//-----------------------------------------------------------------------------
// RGLInit
//-----------------------------------------------------------------------------

HRESULT RGLInit(HWND hWnd, int width, int height)
{
    if( NULL == ( g_pD3D = Direct3DCreate8( D3D_SDK_VERSION ) ) )
        return E_FAIL;

    D3DDISPLAYMODE d3ddm;
    if( FAILED( g_pD3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &d3ddm ) ) )
        return E_FAIL;

    D3DPRESENT_PARAMETERS d3dpp; 
    ZeroMemory( &d3dpp, sizeof(d3dpp) );
    d3dpp.Windowed = TRUE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.BackBufferFormat = d3ddm.Format;
    d3dpp.EnableAutoDepthStencil = TRUE;
    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

    if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
                                      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                                      &d3dpp, &g_pd3dDevice ) ) )
    {
        return E_FAIL;
    }

		// Assume the user doesn't want any lighting, atleast for now.
    g_pd3dDevice->SetRenderState( D3DRS_LIGHTING, FALSE );

		// Turn on the zbuffer
    g_pd3dDevice->SetRenderState( D3DRS_ZENABLE, TRUE );

		// Turn off backface culling (idiot proofing)
    RGLSetCullMode(D3DCULL_NONE);
    return S_OK;
}

//-----------------------------------------------------------------------------
// RGLShutdown
//-----------------------------------------------------------------------------

void RGLShutdown()
{
    if( g_pd3dDevice != NULL) 
        g_pd3dDevice->Release();

    if( g_pD3D != NULL)
        g_pD3D->Release();
}

//-----------------------------------------------------------------------------
// RGLSetCullMode
//-----------------------------------------------------------------------------
// Sets the backface culling mode for rendering.  Valid modes are:
//	D3DCULL_NONE - for no backface culling (always displays objects - default)
//  D3DCULL_CCW  - counter-clockwise culling
//	D3DCULL_CW   - clockwise culling

HRESULT RGLSetCullMode(D3DCULL mode)
{
	return g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, mode);
}

//-----------------------------------------------------------------------------
// RGLGetDevice
//-----------------------------------------------------------------------------
// There are ocassions where the user may want to get at the Direct3D
// drawing device and tweak it directly, (after all, this frame work
// is pretty limited), so here's where we give them access. 

LPDIRECT3DDEVICE8 RGLGetDevice()
{
	return g_pd3dDevice;
}
