//-----------------------------------------------------------------------------
// File: rt_D3DXSphere.cpp
//
// Desc: Create spheres, cylinders, tori, boxes (rectangular parallelpipeds),
// and teapots.  Sample specific stuff is at the beginning and end of the
// file.  Use the following keys to control the sample:
//
// Up Arrow     +1 segment
// Down Arrow   -1 segment
// Page Up      +5 segments
// Page Down    -5 segments
// Home         use 3 segments
// End          use 25 segments
// F2           Change Device
// Alt+ENTER    Toggle exclusive/windowed mode
// ESC          Quit
//
// Copyright (c) 1997-2000 Microsoft Corporation. All rights reserved.
// Portions Copyright (c) 2001 Rich Thomson.  All rights reserved.
//-----------------------------------------------------------------------------
#define STRICT
#include <windows.h>
#include <commdlg.h>
#include <math.h>
#include <tchar.h>
#include <d3dx8.h>
#include "D3DApp.h"
#include "D3DFont.h"
#include "D3DUtil.h"
#include "DXUtil.h"
#include "resource.h"
#include <atlbase.h> // CComPtr<> smart pointers
#include <vector>    // std::vector<T> dynamic one-dimensional arrays
#include <sstream>   // std::basic_xxx<TCHAR> ANSI/UNICODE strings
#include <iomanip>   // setw, hex -- iostream manipulators

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// expression evaluating to the number of elements in a fixed-size array,
// usually an array initialized with an aggregate initializer.
#define NUM_OF(ary_) (sizeof(ary_)/sizeof(ary_[0]))

//-----------------------------------------------------------------------------
// sanity check on all calls specific to this sample
inline HRESULT
check_hr(LPCTSTR file, unsigned line, HRESULT hr)
{
    if (FAILED(hr))
    {
        using namespace std;
        basic_stringstream<TCHAR> buff;
        TCHAR error_text[256] = { 0 };
        ::D3DXGetErrorString(hr, error_text, NUM_OF(error_text));
        buff << file << _T(":") << line << _T(" 0x") << hex << setw(8)
            << hr << _T(" = ") << error_text << endl;
        ::OutputDebugString(buff.str().c_str());
        ATLASSERT(false);
    }
    return hr;
}

// this is similar to the THR() in my RT utility library, only this
// flavor doesn't throw() an exception, it prints to the debug stream
// and ATLASSERT()s false.
#define THR(hr_) check_hr(_T(__FILE__), __LINE__, hr_)

//-----------------------------------------------------------------------------
enum shape_t
{
    SHAPE_BOX,
    SHAPE_CYLINDER,
    // yep, that's right, you noticed we have no menu item hooked up to
    // call D3DXCreatePolygon.  When I tried it, I didn't see the polygon,
    // but it could have rendered edge on or outside the view volume.
    // Rather than show a confusing blank screen, we don't hook a menu
    // item up to this, but the code is there.
    SHAPE_POLYGON,
    SHAPE_SPHERE,
    SHAPE_TEAPOT,
    SHAPE_TORUS
};

//-----------------------------------------------------------------------------
// Ugh.  I'm sick of typing reinterpret_cast<>() to get good C++ style casts
// -and- the convenience of D3DX.  D3DX, even with casts it requires, does
// quite a bit of useful things.  Use it until you outgrow its capabilities.
//
// However, they have chosen BYTE pointers for untyped data return values.
// In my opinion, the proper choice would have been void pointers, obviating
// the need for these casts.  Fortunately, template classes can ease the
// pain without compromising type safety -- or at least as much type safety
// you can have with this approach for passing data in a library.
//
// This template class makes an ID3DXBuffer's data pointer look like a T *,
// not a BYTE *.
//
// 'p' is the interface pointer public member in CComPtr<> (see <atlbase.h>)
//
template <typename T>
class typed_dxbuffer : public CComPtr<ID3DXBuffer>
{
public:
    typed_dxbuffer() : CComPtr<ID3DXBuffer>() {}
    typed_dxbuffer(UINT count) : CComPtr<ID3DXBuffer>()
    {
        THR(::D3DXCreateBuffer(count*sizeof(T), &*this));
    }
    operator const T *() const
    {
        ATLASSERT(p);
        return reinterpret_cast<T *>(p->GetBufferPointer());
    }
    operator T *()
    {
        ATLASSERT(p);
        return reinterpret_cast<T *>(p->GetBufferPointer());
    }
};

//-----------------------------------------------------------------------------
// set device state for rendering
void
set_state(IDirect3DDevice8 *device)
{
    struct
    {
        D3DRENDERSTATETYPE m_state;
        DWORD m_value;
    } states[] =
    {
        // Initialize a bunch of render states just to be paranoid
        // (you -did- test your code out on refrast as well as HAL, right?)
        // refrast caught me with my renderstates down!
        //
        // You can use the renderstate pipeline diagram at
        // <http://www.xmission.com/~legalize/book/preview/> to
        // visualize the relevant renderstates for the rendering you
        // want.
        D3DRS_CLIPPLANEENABLE, 0L,
        D3DRS_CULLMODE, D3DCULL_CCW,
        D3DRS_FILLMODE, D3DFILL_SOLID,
        D3DRS_SHADEMODE, D3DSHADE_GOURAUD,
        D3DRS_CLIPPING, TRUE,
        D3DRS_LIGHTING, TRUE,
        D3DRS_LOCALVIEWER, FALSE,
        D3DRS_AMBIENT, D3DCOLOR_ARGB(0, 0x55, 0x55, 0x55),
        D3DRS_SPECULARENABLE, FALSE,
        D3DRS_ALPHATESTENABLE, FALSE,
        D3DRS_ZENABLE, D3DZB_TRUE,
        D3DRS_ZWRITEENABLE, TRUE,
        D3DRS_ZFUNC, D3DCMP_LESS,
        D3DRS_ZBIAS, 0,
        D3DRS_STENCILENABLE, FALSE,
        D3DRS_ALPHABLENDENABLE, FALSE,
        D3DRS_DITHERENABLE, TRUE,
        D3DRS_NORMALIZENORMALS, TRUE,
        D3DRS_COLORVERTEX, FALSE,
        D3DRS_AMBIENTMATERIALSOURCE, D3DMCS_MATERIAL,
        D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_MATERIAL,
        D3DRS_SPECULARMATERIALSOURCE, D3DMCS_MATERIAL,
        D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_MATERIAL
    };
    for (UINT i = 0; i < NUM_OF(states); i++)
    {
        THR(device->SetRenderState(states[i].m_state, states[i].m_value));
    }

    // Set up a material
    D3DMATERIAL8 material;
    ::D3DUtil_InitMaterial(material, 0.5f, 0.8f, 1.0f); // see d3dutil.cpp
    THR(device->SetMaterial(&material));

    // Set up the light
    D3DLIGHT8 light;
    ::D3DUtil_InitLight(light, D3DLIGHT_DIRECTIONAL, 0.0f, -1.0f, 1.0f); // see d3dutil.cpp
    THR(device->SetLight(0, &light));
    THR(device->LightEnable(0, TRUE));

    // we'll use an identity matrix for our world transform, since we've
    // constructed the calls in the shape factories to keep the model inside
    // the cube [-1,1] on the three principal axes.
    D3DXMATRIX ident(1.f, 0.f, 0.f, 0.f,
                     0.f, 1.f, 0.f, 0.f,
                     0.f, 0.f, 1.f, 0.f,
                     0.f, 0.f, 0.f, 1.f);
    THR(device->SetTransform(D3DTS_WORLD, &ident));

    // we'll put the eye off to the corner of this cube and make it
    // look at the origin
    D3DXMATRIX view;
    D3DXVECTOR3 eye(1, 1, -1);
    eye /= 2.0f*::D3DXVec3Length(&eye);
    D3DXVECTOR3 at(0, 0, 0);
    D3DXVECTOR3 up(0, 1, 0);
    ::D3DXMatrixLookAtLH(&view, &eye, &at, &up);
    THR(device->SetTransform(D3DTS_VIEW, &view));

    // we'll use an orghographic projection spanning [-1,1] on the three
    // principal axes
    D3DXMATRIX proj;
    ::D3DXMatrixOrthoLH(&proj, 2.0f, 2.0f, -1.0f, 1.0f);
    THR(device->SetTransform(D3DTS_PROJECTION, &proj));
}

//-----------------------------------------------------------------------------
// optimize a mesh in place
void
optimize(ID3DXMesh *mesh, const DWORD *adj)
{
    std::vector<DWORD> adj_out(mesh->GetNumFaces()*3);
    std::vector<DWORD> face_remap(mesh->GetNumFaces());
    CComPtr<ID3DXBuffer> vertex_remap;
    THR(mesh->OptimizeInplace(D3DXMESHOPT_ATTRSORT, adj,
                              &adj_out[0], &face_remap[0], &vertex_remap));
}

//-----------------------------------------------------------------------------
// shape factories: box, cylinder, polygon, sphere, teapot, torus
// the factory returns an ID3DXMesh interface pointer and an adjacency
// ID3DXBuffer interface wrapped in a smart pointer.
typedef void factory_proc(IDirect3DDevice8 *device, UINT segments,
                          ID3DXMesh **mesh, typed_dxbuffer<DWORD> &adj);
factory_proc create_box, create_cylinder, create_polygon,
             create_sphere, create_teapot, create_torus;

//-----------------------------------------------------------------------------
void create_box(IDirect3DDevice8 *device, UINT segments,
                ID3DXMesh **box, typed_dxbuffer<DWORD> &adj)
{
    THR(::D3DXCreateBox(device, 0.75f, 0.25f + 0.02f*segments, 0.75f,
                        box, &adj));
}
//-----------------------------------------------------------------------------
void create_cylinder(IDirect3DDevice8 *device, UINT segments,
                     ID3DXMesh **cylinder, typed_dxbuffer<DWORD> &adj)
{
    THR(::D3DXCreateCylinder(device, 0.75f, 0.75f, 0.75f, segments, segments,
                             cylinder, &adj));
}
//-----------------------------------------------------------------------------
void create_polygon(IDirect3DDevice8 *device, UINT segments,
                    ID3DXMesh **polygon, typed_dxbuffer<DWORD> &adj)
{
    THR(::D3DXCreatePolygon(device, 0.75f, segments, polygon, &adj));
}
//-----------------------------------------------------------------------------
void create_sphere(IDirect3DDevice8 *device, UINT segments,
                   ID3DXMesh **sphere, typed_dxbuffer<DWORD> &adj)
{
    THR(::D3DXCreateSphere(device, 0.75f, segments, segments, sphere, &adj));
}
//-----------------------------------------------------------------------------
void create_teapot(IDirect3DDevice8 *device, UINT segments,
                   ID3DXMesh **mesh, typed_dxbuffer<DWORD> &adj)
{
    THR(::D3DXCreateTeapot(device, mesh, &adj));
}
//-----------------------------------------------------------------------------
void create_torus(IDirect3DDevice8 *device, UINT segments,
                  ID3DXMesh **torus, typed_dxbuffer<DWORD> &adj)
{
    THR(::D3DXCreateTorus(device, 0.25f, 0.75f, segments, segments,
                          torus, &adj));
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// Name: class CMyD3DApplication
// Desc: Application class. The base class (CD3DApplication) provides the 
//       generic functionality needed in all Direct3D samples. CMyD3DApplication 
//       adds functionality specific to this sample program.
//-----------------------------------------------------------------------------
class CMyD3DApplication : public CD3DApplication
{
    // these are defined at the end of the file
    void create_shape();
    void inc_segments(int amount);
    void make_seg_stats();

    // shape data:
    // - enumerant identifying current shape type,
    // - mesh smart pointer for holding the shape geometry
    // - number of 'segments' for the shape (the teapot can't be adjusted and
    //   the box doesn't really change in polygon count, only its size)
    // - a string for segment reports
    //
    shape_t m_which_shape;
    CComPtr<ID3DXMesh> m_shape;
    UINT m_segments;
    std::basic_string<TCHAR> m_seg_stats;

    CD3DFont*     m_pStatsFont;

protected:
    HRESULT OneTimeSceneInit();
    HRESULT InitDeviceObjects();
    HRESULT RestoreDeviceObjects();
    HRESULT InvalidateDeviceObjects();
    HRESULT DeleteDeviceObjects();
    HRESULT Render();
    HRESULT FrameMove();
    HRESULT FinalCleanup();

public:
    LRESULT MsgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam );
    CMyD3DApplication();
};

//-----------------------------------------------------------------------------
// Name: WinMain()
// Desc: Entry point to the program. Initializes everything, and goes into a
//       message-processing loop. Idle time is used to render the scene.
//-----------------------------------------------------------------------------
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR, INT )
{
    CMyD3DApplication d3dApp;

    if( FAILED( d3dApp.Create( hInst ) ) )
        return 0;

    return d3dApp.Run();
}

//-----------------------------------------------------------------------------
// Name: CMyD3DApplication()
// Desc: Application constructor. Sets attributes for the app.
//-----------------------------------------------------------------------------
CMyD3DApplication::CMyD3DApplication()
    : CD3DApplication(),
    // initial shape corresponds to the checked item in the menu resource
    m_which_shape(SHAPE_SPHERE),
    m_shape(),
    m_segments(10),
    m_seg_stats(16, TCHAR(0)),
    m_pStatsFont(new CD3DFont(_T("Arial"), 12, D3DFONT_BOLD))
{
    m_strWindowTitle    = _T("rt_D3DXSphere");
    m_bUseDepthBuffer   = TRUE;
    make_seg_stats();
}


//-----------------------------------------------------------------------------
// Name: OneTimeSceneInit()
// Desc: Called during initial app startup, this function performs all the
//       permanent initialization.
//-----------------------------------------------------------------------------
HRESULT CMyD3DApplication::OneTimeSceneInit()
{
    return S_OK;
}

//-----------------------------------------------------------------------------
// Name: FrameMove()
// Desc: Called once per frame, the call is the entry point for animating
//       the scene.
//-----------------------------------------------------------------------------
HRESULT CMyD3DApplication::FrameMove()
{
    return S_OK;
}

//-----------------------------------------------------------------------------
// Name: Render()
// Desc: Called once per frame, the call is the entry point for 3d
//       rendering. This function sets up render states, clears the
//       viewport, and renders the scene.
//-----------------------------------------------------------------------------
HRESULT CMyD3DApplication::Render()
{
    // Clear the viewport
    m_pd3dDevice->Clear(0L, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
                        D3DCOLOR_ARGB(0, 0, 0, 0), 1.0f, 0L);

    // Begin the scene 
    m_pd3dDevice->BeginScene();

    m_shape->DrawSubset(0);

    // Show frame rate
    const D3DCOLOR fg = D3DCOLOR_XRGB(0, 230, 0);
    m_pStatsFont->DrawText( 2,  0, fg, m_strFrameStats );
    m_pStatsFont->DrawText( 2, 20, fg, m_strDeviceStats );
    THR(m_pStatsFont->DrawText( 2, 40, fg,
        const_cast<LPTSTR>(m_seg_stats.c_str())));

    // End the scene.
    m_pd3dDevice->EndScene();

    return S_OK;
}

//-----------------------------------------------------------------------------
// Name: InitDeviceObjects()
// Desc: Initialize scene objects.
//-----------------------------------------------------------------------------
HRESULT CMyD3DApplication::InitDeviceObjects()
{
    // Restore the fonts
    m_pStatsFont->InitDeviceObjects(m_pd3dDevice );

    // D3DX objects are lost when a device is lost, so we always recreate
    create_shape();
    set_state(m_pd3dDevice);

    return S_OK;
}

//-----------------------------------------------------------------------------
// Name: RestoreDeviceObjects()
// Desc: Initialize scene objects.
//-----------------------------------------------------------------------------
HRESULT CMyD3DApplication::RestoreDeviceObjects()
{
    // Restore the fonts
    m_pStatsFont->RestoreDeviceObjects();
    
    set_state(m_pd3dDevice);

    return S_OK;
}

//-----------------------------------------------------------------------------
// Name: InvalidateDeviceObjects()
// Desc: Called when the app is exiting, or the device is being changed,
//       this function deletes any device dependent objects.
//-----------------------------------------------------------------------------
HRESULT CMyD3DApplication::InvalidateDeviceObjects()
{
    m_pStatsFont->InvalidateDeviceObjects();
    return S_OK;
}

//-----------------------------------------------------------------------------
// Name: DeleteDeviceObjects()
// Desc: Called when the app is exiting, or the device is being changed,
//       this function deletes any device dependent objects.
//-----------------------------------------------------------------------------
HRESULT CMyD3DApplication::DeleteDeviceObjects()
{
    m_pStatsFont->DeleteDeviceObjects();
    m_shape = 0;
    return S_OK;
}

//-----------------------------------------------------------------------------
// Name: FinalCleanup()
// Desc: Called before the app exits, this function gives the app the chance
//       to cleanup after itself.
//-----------------------------------------------------------------------------
HRESULT CMyD3DApplication::FinalCleanup()
{
    SAFE_DELETE( m_pStatsFont );
    m_shape = 0;
    return S_OK;
}

//-----------------------------------------------------------------------------
// Name: MsgProc()
// Desc: Message proc function to handle key and menu input
//-----------------------------------------------------------------------------
LRESULT
CMyD3DApplication::MsgProc(HWND window, UINT msg, WPARAM wp, LPARAM lp)
{
    if (WM_COMMAND == msg)
    {
        UINT ctl_id = LOWORD(wp);
        // the menu item IDs are manually chosen to lay in a range
        // spanning the range of the shape_t enum
        if (IDM_BOX <= ctl_id && IDM_TORUS >= ctl_id)
        {
            UINT choice = ctl_id - IDM_BOX;
            HMENU menu = ::GetMenu(window);
            ::CheckMenuItem(menu, IDM_BOX + m_which_shape, MF_UNCHECKED);
            m_which_shape = shape_t(choice);
            ::CheckMenuItem(menu, ctl_id, MF_CHECKED);
            create_shape();
        }
    }
    else if (WM_KEYDOWN == msg)
    {
        switch (wp)
        {
        // adjust segments up or down and rebuild mesh
        case VK_UP:     inc_segments(+1);  break;
        case VK_DOWN:   inc_segments(-1);  break;
        case VK_PRIOR:  inc_segments(+5);  break;
        case VK_NEXT:   inc_segments(-5);  break;

        // set segments to specific value and rebuild mesh
        case VK_HOME:   m_segments = 3;  inc_segments(0); break;
        case VK_END:    m_segments = 25; inc_segments(0); break;
        }
    }

    return CD3DApplication::MsgProc(window, msg, wp, lp);
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// increase/decrease number of segments for models,
// constrained to a minimum of 3
void
CMyD3DApplication::inc_segments(int amount)
{
    if (amount >= 0 || -amount < m_segments-2)
    {
        m_segments += amount;
    }
    create_shape();
    make_seg_stats();
}

//-----------------------------------------------------------------------------
// make a string from the number of segments for the status display
void
CMyD3DApplication::make_seg_stats()
{
    std::basic_stringstream<TCHAR> buff;
    buff << m_segments << _T(" segments");
    m_seg_stats = buff.str();
}

//-----------------------------------------------------------------------------
// create the mesh corresponding to the enum m_which_shape
void
CMyD3DApplication::create_shape()
{
    factory_proc *factories[] =
    {
        create_box,
        create_cylinder,
        create_polygon,
        create_sphere,
        create_teapot,
        create_torus
    };
    if (m_which_shape < NUM_OF(factories))
    {
        // release old shape, if any
        m_shape = 0;

        // make new shape
        typed_dxbuffer<DWORD> adj;
        factories[m_which_shape](m_pd3dDevice, m_segments, &m_shape, adj);

        // optimize the mesh in place
        optimize(m_shape, adj);
    }
}