//-----------------------------------------------------------------------------
// File: rt_GingerBread.cpp
//
// ginger bread man fractal in DX8 sample framework form.  The rich
// texture of a 2D chaotic orbit is visualized by this sample.  The orbit
// is computed by a repetition of the formulas:
//
//      x' = 1 - y + |x|
//      y' = x
//
//      |x| denotes the absolute value of x.
//
// For values of (x,y) in the plane starting with (-0.1, 0) this produces
// a chaotic orbit in the plane.  (See "Science of Fractal Images",
// Heinz-Otto Peitgen and Dietmar Saupe, eds., Springer-Verlag, 1988, 
// pg. 149.)
//
// The orbit is visualized using an orthographic projection, adjusted to
// keep the viewport aspect ratio 1:1 regardless of the back buffer aspect
// ratio.
//
// Copyright (c) 1997-2000 Microsoft Corporation. All rights reserved.
// Portions (C) 2001 Rich Thomson. All rights reserved.
//-----------------------------------------------------------------------------

// C++ standard includes
#include <math.h>               // standard math routines
#include <sstream>              // std::basic_string{,stream}<>
#include <iomanip>              // std::hex, std::setw, etc. manipulators
#include <utility>              // std::pair

// Win32 includes
#define STRICT
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <commdlg.h>            // OpenFileDialog
#include <tchar.h>              // TCHAR, LPCTSTR, _tcscpy, etc.

// ATL includes
#include <atlbase.h>            // CComPtr<>, ATLASSERT()

// DX8 includes
#include <d3dx8.h>

// D3DFrame includes
#include "D3DApp.h"
#include "D3DFont.h"
#include "D3DUtil.h"
#include "DXUtil.h"

// application includes
#include "resource.h"
#include "colorsel.h"

//-----------------------------------------------------------------------------
// C++ standard string library classes in the spirit of <tchar.h>
// An extended implementation of this idea for general Win32 C++ programming
// is in my RT utility library in the include file <rt/tstring.h>.
//
// Fortunately we can get what we need from the C++ standard library
// with a couple of judicious typedefs.
//
// See "The C++ Standard Library", Nicolai Josuttis, 1999 for an
// excellent reference to the C++ Standard Library, including strings,
// streams and all the STL classes.
typedef std::basic_string<TCHAR> tstring;
typedef std::basic_ostringstream<TCHAR> tostringstream;

//-----------------------------------------------------------------------------
const int INITIAL_NUM_POINTS = 10000; // number of points initially displayed
const int HOME_NUM_POINTS = 300;      // VK_HOME number of points
const int END_NUM_POINTS = 300000;    // VK_END number of points
const int POINT_LINE_SIZE = 100;      // VK_UP/VK_DOWN inc/dec amount
const int POINT_PAGE_SIZE = 1000;     // VK_NEXT/VK_PRIOR inc/dec amount
const int MIN_NUM_POINTS = 100;
const int MAX_NUM_POINTS = 1280*1024; // 1 point per pixel at 1280x1024!
const float SQRT_2 = sqrtf(2.f);       // just do this once  

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

//-----------------------------------------------------------------------------
// check_hr
//
// 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;
        tostringstream 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());

        // Put a breakpoint on this ATLASSERT and you'll hit your
        // breakpoint every time your program encounters an unexpected
        // failed HRESULT.
        ATLASSERT(false);
    }
    return hr;
}

//-----------------------------------------------------------------------------
// THR
//
// 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_)

//-----------------------------------------------------------------------------
// s_vertex
//
// each point in the ginger bread fractal has a position and a diffuse color.
// Without a diffuse color in the vertex structure, we would have to use
// Direct3D lighting to get a color on the untextured points.
//
struct s_vertex
{
    float m_x, m_y, m_z;
    D3DCOLOR m_diffuse;
    static const DWORD FVF;

    void set(float x, float y, D3DCOLOR diffuse)
    {
        m_x = x;
        m_y = y;
        m_z = 0.5f;                 // arbitrary Z value in [0,1]
        m_diffuse = diffuse;
    }
};
const DWORD s_vertex::FVF = D3DFVF_XYZ | D3DFVF_DIFFUSE;

//-----------------------------------------------------------------------------
// vb_lock<Vertex>
//
// A vertex buffer lock helper class: locks the vertex buffer using an
// construction-is-resource-acquisition idiom.  When the destructor is
// called, the lock is released.  Use like so:
//
//      extern IDirect3DVertexBuffer8 *get_vertex_buffer();
//      extern UINT get_num_vertices();
//      extern void do_vertex(Vertex &v);
//
//      void
//      some_top_level_function()
//      {
//          // ...
//          IDirect3DVertexBuffer8 *vb = get_vertex_buffer();
//          UINT num_vertices = get_num_vertices(vb);
//          {
//              vb_lock<MyVertex> vertices(vb)
//              for (UINT i = 0; i < num_vertices; i++)
//              {
//                  do_something_with_vertex(vertices.vertices[i]);
//              }
//          } // lock released here
//      }
//
template <typename Vertex>
class vb_lock
{
public:
    // lock vertex buffer in c'tor
    vb_lock(IDirect3DVertexBuffer8 *vb, DWORD flags = 0,
            UINT offset = 0, UINT size = 0)
            : m_vb(vb)
    {
        THR(m_vb->Lock(offset, size,
                       reinterpret_cast<BYTE **>(&m_data), flags));
    }
    // unlock vertex buffer in d'tor
    ~vb_lock()
    {
        const HRESULT hr = m_vb->Unlock();
        // destructors shouldn't throw(), but Unlock() should only fail
        // if our lock succeeded.  Our object can only be fully constructed
        // if the lock succeeded, so this is only here to be thorough in
        // our error checkings.
        ATLASSERT(SUCCEEDED(hr));
    }

    // accessors to typed vertex data acquired in c'tor
    const Vertex *vertices() const { return m_data; }
    Vertex *vertices() { return m_data; }

private:
    CComPtr<IDirect3DVertexBuffer8> m_vb;
    Vertex *m_data;
};

//-----------------------------------------------------------------------------
// s_enum_value<Enum>
// s_rs
// s_tss
//
// struct for storing a render state and its value
//
template <typename Enum>
struct s_enum_value
{
    Enum m_state;
    DWORD m_value;
};
typedef s_enum_value<D3DRENDERSTATETYPE> s_rs;
typedef s_enum_value<D3DTEXTURESTAGESTATETYPE> s_tss;

//-----------------------------------------------------------------------------
// clamp
//
// Clamps a float in the [0,1] range.
//
inline void
clamp(float &val)
{
    if (val < 0)
    {
        val = 0;
    }
    if (val > 1)
    {
        val = 1;
    }
}

//-----------------------------------------------------------------------------
// check_menu
//
// Makes a menu item's check mark reflect the value of a bool.
//
inline void
check_menu(HMENU menu, UINT item, bool value)
{
    ::CheckMenuItem(menu, item, value ? MF_CHECKED : MF_UNCHECKED);
}

//-----------------------------------------------------------------------------
// accum_minmax
//
// Given a floating point value, accumulate it into a min/max pair.
//
inline void
accum_minmax(float val, float &min, float &max)
{
    if (val < min)
    {
        min = val;
    }
    else if (val > max)
    {
        max = val;
    }
}

//-----------------------------------------------------------------------------
// choose_color
//
// Invoke the rt_choose_color dialog from "colorsel.h", but handles
// premultiplied alpha so that the dialog always thinks its editing
// in the [0,255] range.
//
D3DCOLOR
choose_color(HWND owner, D3DCOLOR current, bool transparent)
{
    if (transparent)
    {
        const float normalize = 1/255.f;
        float scale = 1.f/D3DColor_Alpha(current);
        D3DXCOLOR nonassoc(D3DColor_Red(current)*scale,
                           D3DColor_Green(current)*scale,
                           D3DColor_Blue(current)*scale,
                           D3DColor_Alpha(current)*normalize);
        clamp(nonassoc.a);
        clamp(nonassoc.r);
        clamp(nonassoc.g);
        clamp(nonassoc.b);
        D3DCOLOR tmp = rt_choose_color(owner, D3DCOLOR(nonassoc));
        nonassoc.a = D3DColor_Alpha(tmp)*normalize;
        scale = normalize*nonassoc.a;
        nonassoc.r = D3DColor_Red(tmp)*scale;
        nonassoc.g = D3DColor_Green(tmp)*scale;
        nonassoc.b = D3DColor_Blue(tmp)*scale;
        clamp(nonassoc.a);
        clamp(nonassoc.r);
        clamp(nonassoc.g);
        clamp(nonassoc.b);
        current = D3DCOLOR(nonassoc);
    }
    else
    {
        // always opaque, doesn't need scaling since its already full-scale
        current |= D3DCOLOR_ARGB(255, 0, 0, 0);
        current = rt_choose_color(owner, current);
        current |= D3DCOLOR_ARGB(255, 0, 0, 0);
    }

    return current;
}

//-----------------------------------------------------------------------------
// CMyD3DApplication
//
// 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
{
    enum e_setups
    {
        S_DIFFUSE,
        S_TEXTURE,
        S_BBOX,
    };

    bool m_can_texture;         // true: device can do required texturing
    bool m_textured;            // true: texture the points
    bool m_clear;               // true: clear render target before drawing
    bool m_show_stats;          // true: show statistics
    CD3DFont *m_pStatsFont;
    bool m_show_help;           // true: display keyboard help
    CD3DFont *m_help_font;
    bool m_bounding_box;        // true: draw red bounding box around points
    D3DCOLOR m_box_fg;

    bool m_rotate_view;         // true: rotate view up vector around Z axis
    float m_view_angle;         // angle to rotate view up vector from +Y axis
    float m_x0, m_y0;           // initial point of chaotic iteration
    float m_min_x, m_min_y;     // bounding box of points computed into the VB
    float m_max_x, m_max_y;
    UINT m_iterations;          // hundreds of points computed so far
    UINT m_num_points;          // number of points to compute each frame
    UINT m_num_vb_points;       // number of points the VB can hold
    CComPtr<IDirect3DVertexBuffer8> m_vb;
    tstring m_point_stats;      // statistics string
    D3DCOLOR m_fg;              // colors: points, background, text
    D3DCOLOR m_bg;
    D3DCOLOR m_text_fg;
    tstring m_texture_file;     // image file for textured points
    CComPtr<IDirect3DTexture8> m_texture;

    void create_texture(LPCTSTR file = NULL);
    void create_vb(int new_points);
    void create_points();
    void update_stats();
    void inc_points(int amount);
    void set_state(e_setups setup) const;
    void show_help(UINT x, UINT y) const;

#define HANDLER(name_) void name_(HWND window, WPARAM wp, LPARAM lp)
    HANDLER(on_command);
    HANDLER(on_key_down);
    HANDLER(on_texture_file);
#undef HANDLER

protected:
    HRESULT ConfirmDevice(D3DCAPS8*,DWORD,D3DFORMAT);
    HRESULT OneTimeSceneInit();
    HRESULT InitDeviceObjects();
    HRESULT RestoreDeviceObjects();
    HRESULT InvalidateDeviceObjects();
    HRESULT DeleteDeviceObjects();
    HRESULT Render();
    HRESULT FrameMove();
    HRESULT FinalCleanup();

public:
    LRESULT MsgProc(HWND window, UINT msg, WPARAM wp, LPARAM lp);
    CMyD3DApplication();
};

//-----------------------------------------------------------------------------
// pauser
//
// Pauses a CD3DApplication in it's c'tor, resumes in its d'tor
//
class pauser
{
public:
    pauser(CMyD3DApplication &app) : m_app(app)
    {
        m_app.Pause(true);
    }
    ~pauser()
    {
        m_app.Pause(false);
    }
private:
    CMyD3DApplication &m_app;
};

//-----------------------------------------------------------------------------
// WinMain()
// 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();
}

//-----------------------------------------------------------------------------
// CMyD3DApplication::CMyD3DApplication()
//
// Application constructor. Sets attributes for the app.
//
CMyD3DApplication::CMyD3DApplication()
    :  m_can_texture(false),
    m_clear(true),
    m_show_stats(true),
    m_pStatsFont(new CD3DFont(_T("Arial"), 10, D3DFONT_BOLD)),
    m_show_help(false),
    m_help_font(new CD3DFont(_T("Arial"), 10, 0)),
    m_bounding_box(false),
    m_box_fg(D3DCOLOR_XRGB(255, 255, 255)),
    m_rotate_view(false),
    m_view_angle(0.0f),
    m_x0(-0.1f), m_y0(0.0f),
    m_min_x(m_x0), m_min_y(m_y0),
    m_max_x(m_x0), m_max_y(m_y0),
    m_iterations(0),
    m_num_vb_points(INITIAL_NUM_POINTS),
    m_num_points(INITIAL_NUM_POINTS),
    m_vb(),
    m_point_stats(),
    m_bg(D3DCOLOR_XRGB(0, 0, 0)),
    m_fg(D3DCOLOR_ARGB(125, 125, 0, 0)),
    m_text_fg(D3DCOLOR_XRGB(0, 255, 128)),
    m_texture_file(_T("mandel_waves.png")),
    m_texture(),
    m_textured(false)
{
    m_strWindowTitle    = _T("rt_GingerBread");
    m_bUseDepthBuffer   = FALSE;
    update_stats();
}

//-----------------------------------------------------------------------------
// CMyD3DApplication::ConfirmDevice
//
// Reject devices that don't support required blending and texture states.
//
HRESULT
CMyD3DApplication::
ConfirmDevice(D3DCAPS8 *caps, DWORD behavior, D3DFORMAT format)
{
    if ((caps->SrcBlendCaps & D3DPBLENDCAPS_ONE) &&
        (caps->DestBlendCaps & D3DPBLENDCAPS_INVSRCALPHA))
    {
        return S_OK;
    }

    return E_FAIL;
}

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

//-----------------------------------------------------------------------------
// CMyD3DApplication::FrameMove()
//
// Animate the view if enabled.
// Create new points for this frame.
//
HRESULT CMyD3DApplication::FrameMove()
{
    if (m_rotate_view)
    {
        m_view_angle += min(0.25f*D3DX_PI/m_fFPS, D3DX_PI/32);
        if (m_view_angle > 2*D3DX_PI)
        {
            m_view_angle = fmodf(m_view_angle, 2*D3DX_PI);
        }
    }
    create_points();
    return S_OK;
}

//-----------------------------------------------------------------------------
// CMyD3DApplication::Render()
//
// 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()
{
    // draw the scene and clear the frame buffer
    THR(m_pd3dDevice->BeginScene());
    if (m_clear)
    {
        THR(m_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET, m_bg, 1.0f, 0L));
    }

    set_state(m_textured ? S_TEXTURE : S_DIFFUSE);

    // now draw the points...
    // draw them in batches < device max for really large point counts
    const UINT BATCH_COUNT = m_d3dCaps.MaxPrimitiveCount;
    for (UINT i = 0; i < m_num_points; i += BATCH_COUNT)
    {
        const UINT count = ((i + BATCH_COUNT) > m_num_points) ?
            (m_num_points - i) : BATCH_COUNT;
        THR(m_pd3dDevice->DrawPrimitive(D3DPT_POINTLIST, i, count));
    }
    if (m_bounding_box)
    {
        set_state(S_BBOX);
        struct s_vertex box[] =
        {
            m_min_x, m_min_y, 0.5f, m_box_fg,
            m_max_x, m_min_y, 0.5f, m_box_fg,
            m_max_x, m_max_y, 0.5f, m_box_fg,
            m_min_x, m_max_y, 0.5f, m_box_fg,
            m_min_x, m_min_y, 0.5f, m_box_fg
        };
        THR(m_pd3dDevice->DrawPrimitiveUP(D3DPT_LINESTRIP, 4,
            box, sizeof(box[0])));
    }

    // 
    // Show statistics
    if (m_show_stats)
    {
        m_pStatsFont->DrawText(2,  0, m_text_fg, m_strFrameStats);
        m_pStatsFont->DrawText(2, 20, m_text_fg, m_strDeviceStats);
        m_pStatsFont->DrawText(2, 40, m_text_fg,
            const_cast<LPTSTR>(m_point_stats.c_str()));
    
        if (m_show_help)
        {
            show_help(2, 60);
        }
        else
        {
            m_pStatsFont->DrawText(2, 60, m_text_fg, _T("Press F1 for help."));
        }
    }
    else if (m_show_help)
    {
        show_help(2, 0);
    }

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

    return S_OK;
}

//-----------------------------------------------------------------------------
// CMyD3DApplication::show_help
//
// Draws the help string
//
void
CMyD3DApplication::show_help(UINT x, UINT y) const
{
    THR(m_help_font->DrawText(x, y, m_text_fg, _T("Keyboard controls:")));
    THR(m_help_font->DrawText(x, y+20, m_text_fg,
        _T("Toggle Animate View\n")
        _T("Reset View\n")
        _T("Toggle Bounding Box\n")
        _T("Toggle Statistics\n")
        _T("Toggle Clear Target\n")
        _T("Toggle Textured\n")
        _T("\n")
        _T("Toggle Help\n")
        _T("Change device\n")
        _T("Start/Stop\n")
        _T("Single Step\n")
        _T("Full Screen\n")
        _T("Exit")));
    THR(m_help_font->DrawText(x+140, y+20, m_text_fg,
        _T("A\n")
        _T("R\n")
        _T("B\n")
        _T("S\n")
        _T("C\n")
        _T("T\n")
        _T("\n")
        _T("F1, H\n")
        _T("F2\n")
        _T("ENTER\n")
        _T("SPACE\n")
        _T("Alt+ENTER\n")
        _T("Esc")));
}

//-----------------------------------------------------------------------------
// CMyD3DApplication::create_vb
//
// Creates the vertex buffer for the requested number of points.  If we ran
// out of video memory while trying to create the new vertex buffer, the old
// vertex buffer is retained.
//
void
CMyD3DApplication::create_vb(int new_points)
{
    CComPtr<IDirect3DVertexBuffer8> new_vb;
    const HRESULT hr = m_pd3dDevice->
        CreateVertexBuffer(new_points*sizeof(s_vertex),
            D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC,
            s_vertex::FVF,
            D3DPOOL_DEFAULT,
            &new_vb);
    if (SUCCEEDED(hr))
    {
        m_num_vb_points = new_points;
        m_vb = new_vb;
    }
    // the requested VB could be too big for available memory
    else if (D3DERR_OUTOFVIDEOMEMORY != hr)
    {
        THR(hr);
    }
}

//-----------------------------------------------------------------------------
// CMyD3DApplication::create_texture
//
// Creates the texture map from the given file.  If that was successful, it
// becomes the current texture used on the next frame draw.
//
void
CMyD3DApplication::create_texture(LPCTSTR file)
{
    CComPtr<IDirect3DTexture8> new_texture;
    if (SUCCEEDED(::D3DUtil_CreateTexture(m_pd3dDevice,
            const_cast<LPTSTR>(file ? file : m_texture_file.c_str()),
            &new_texture, D3DFMT_A8R8G8B8)))
    {
        m_texture = new_texture;
        if (file)
        {
            m_texture_file = file;
        }
    }
}

//-----------------------------------------------------------------------------
// CMyD3DApplication::InitDeviceObjects
//
// Initialize scene objects.  Figure out if we can texture the way we want,
// if not then disable that rendering option.
//
#define VALIDATE_SETUP(setup_) \
    set_state(setup_); \
    { \
        DWORD passes = 0; \
        const HRESULT hr = m_pd3dDevice->ValidateDevice(&passes); \
        if (FAILED(hr)) \
        { \
            return hr; \
        } \
    } \
    0
HRESULT
CMyD3DApplication::InitDeviceObjects()
{
    // Restore the fonts
    m_pStatsFont->InitDeviceObjects( m_pd3dDevice );
    m_help_font->InitDeviceObjects(m_pd3dDevice);

    // create a vertex buffer to hold our points
    create_vb(INITIAL_NUM_POINTS);

    // validate our desired rendering states
    {
        const bool textured = m_textured;
        m_textured = false;
        VALIDATE_SETUP(S_DIFFUSE);
        VALIDATE_SETUP(S_BBOX);
        m_textured = textured;
    }

    // rather than reject devices without this cap, we simply disable
    // texturing and allow rendering with the diffuse color on this device.
    m_can_texture =
        bool((m_d3dCaps.TextureFilterCaps & D3DPTFILTERCAPS_MINFLINEAR) &&
             (m_d3dCaps.TextureFilterCaps & D3DPTFILTERCAPS_MAGFLINEAR) &&
             (m_d3dCaps.TextureFilterCaps & D3DPTFILTERCAPS_MIPFLINEAR) &&
             (m_d3dCaps.TextureOpCaps & D3DTEXOPCAPS_SELECTARG2) &&
             (m_d3dCaps.TextureOpCaps & D3DTEXOPCAPS_MODULATE) &&
             (m_d3dCaps.TextureAddressCaps & D3DPTADDRESSCAPS_CLAMP) &&
             (m_d3dCaps.VertexProcessingCaps & D3DVTXPCAPS_TEXGEN));
    if (m_can_texture)
    {
        create_texture();
        set_state(S_TEXTURE);
        {
            DWORD passes = 0;
            const HRESULT hr = m_pd3dDevice->ValidateDevice(&passes);
            if (FAILED(hr))
            {
                m_can_texture = false;
            }
        }
    }
    if (!m_can_texture)
    {
        m_textured = false;
        m_texture = 0;
    }

    // Render() won't clear per frame, so do it here to init the new device
    if (!m_clear)
    {
        THR(m_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET, m_bg, 1.0f, 0L));
    }

    return S_OK;
}
#undef VALIDATE_SETUP

//-----------------------------------------------------------------------------
// RestoreDeviceObjects()
//
// Initialize scene objects.
//
HRESULT CMyD3DApplication::RestoreDeviceObjects()
{
    // Restore the fonts
    m_pStatsFont->RestoreDeviceObjects();
    m_help_font->RestoreDeviceObjects();

    if (!m_vb)
    {
        create_vb(INITIAL_NUM_POINTS);
    }
    if (m_textured && !m_texture)
    {
        create_texture();
    }
    create_points();

    return S_OK;
}

//-----------------------------------------------------------------------------
// InvalidateDeviceObjects()
//
// 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();
    m_help_font->InvalidateDeviceObjects();

    // first unselect out of the device
    THR(m_pd3dDevice->SetTexture(0, NULL));
    THR(m_pd3dDevice->SetStreamSource(0, 0, 0));

    // now release
    m_vb = 0;
    m_texture = 0;

    return S_OK;
}

//-----------------------------------------------------------------------------
// DeleteDeviceObjects()
//
// 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_help_font->DeleteDeviceObjects();
    return S_OK;
}

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

//-----------------------------------------------------------------------------
// CMyD3DApplication::on_texture_file
//
// Get a texture filename from the user and try to open it as the current
// texture.
//
void
CMyD3DApplication::on_texture_file(HWND window, WPARAM wp, LPARAM lp)
{
    TCHAR file[MAX_PATH] = { 0 };
    OPENFILENAME ofn =
    {
        sizeof(ofn), 0, 0,
            _T("All images\0")
                _T("*.bmp;*.jpg;*.png;*.pbm;*.pgm;*.ppm;*.pnm;*.tga;*.tif\0")
            _T("All files (*.*)\0*.*\0")
            _T("Bitmap images (*.bmp)\0*.bmp\0")
            _T("JPEG images (*.jpg)\0*.jpg\0")
            _T("PNG images (*.png)\0*.png\0")
            _T("Portable bitmap (*.pbm)\0*.pbm\0")
            _T("Portable graymap (*.pgm)\0*.pgm\0")
            _T("Portable pixmap (*.ppm)\0*.ppm\0")
            _T("Portable anymap (*.pnm)\0*.pnm\0")
            _T("Targa images(*.tga)\0*.tga\0")
            _T("TIFF images (*.tif)\0*.tif\0")
            _T("\0"),
        0,  0, 0,
        file, MAX_PATH, 0,  0, 0, 0, OFN_FILEMUSTEXIST
    };
    if (::GetOpenFileName(&ofn))
    {
        create_texture(file);
    }
}

//-----------------------------------------------------------------------------
// CMyD3DApplication::on_command
//
// Handle WM_COMMAND parsing: mostly togglilng menu items and their
// associated bools that control rendering options.  Pause the timer
// for actions that result in a dialog being opened.
//
void
CMyD3DApplication::on_command(HWND window, WPARAM wp, LPARAM lp)
{
    const HMENU menu = ::GetMenu(window);
    const UINT control = LOWORD(wp);
    switch (control)
    {
#define TOGGLE(id_, state_) \
    case id_: \
        state_ = !state_; \
        check_menu(menu, id_, state_); \
        break
    TOGGLE(IDM_ANIMATE_VIEW, m_rotate_view);
    TOGGLE(IDM_BOUNDING_BOX, m_bounding_box);
    TOGGLE(IDM_SHOW_STATS, m_show_stats);
    TOGGLE(IDM_CLEAR_BACK, m_clear);
    TOGGLE(IDM_SHOW_HELP, m_show_help);
#undef TOGGLE

#define MODIFY_COLOR(id_, state_, transparent_) \
    case id_: \
        { \
            pauser block(*this); \
            state_ = choose_color(window, state_, transparent_); \
        } \
        break
    MODIFY_COLOR(IDM_EDIT_DIFFUSE_COLOR, m_fg, true);
    MODIFY_COLOR(IDM_EDIT_BBOX_COLOR, m_box_fg, false);
    MODIFY_COLOR(IDM_EDIT_BG_COLOR, m_bg, false);
    MODIFY_COLOR(IDM_EDIT_TEXT_COLOR, m_text_fg, false);
#undef MODIFY_COLOR

    case IDM_RESET_VIEW:
        m_view_angle = 0.0f;
        break;

    case IDM_TEXTURED:
        m_textured = m_can_texture && !m_textured;
        check_menu(menu, IDM_TEXTURED, m_textured);
        break;

    case IDM_TEXTURE_FILE:
        {
            pauser block(*this);
            on_texture_file(window, wp, lp);
        }
        break;
    }
}

//-----------------------------------------------------------------------------
// CMyD3DApplication::on_key_down
//
// Handle key messages by adjusting the number of points computed and
// displayed per frame.
//
void
CMyD3DApplication::on_key_down(HWND window, WPARAM wp, LPARAM lp)
{
    switch (wp)
    {
    // adjust segments up or down and rebuild mesh
    case VK_UP:     inc_points(+POINT_LINE_SIZE);  break;
    case VK_DOWN:   inc_points(-POINT_LINE_SIZE);  break;
    case VK_PRIOR:  inc_points(+POINT_PAGE_SIZE);  break;
    case VK_NEXT:   inc_points(-POINT_PAGE_SIZE);  break;

    // set segments to specific value and rebuild mesh
    case VK_HOME:   m_num_points = HOME_NUM_POINTS; inc_points(0); break;
    case VK_END:    m_num_points = END_NUM_POINTS;  inc_points(0); break;
    }
}

//-----------------------------------------------------------------------------
// CMyD3DApplication::MsgProc()
//
// Message proc function to handle key and menu input
//
LRESULT
CMyD3DApplication::MsgProc(HWND window, UINT msg, WPARAM wp, LPARAM lp)
{
    if (WM_COMMAND == msg)
    {
        on_command(window, wp, lp);
    }
    else if (WM_KEYDOWN == msg)
    {
        on_key_down(window, wp, lp);
    }

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

//-----------------------------------------------------------------------------
// CMyD3DApplication::inc_points
//
// Adjust the number of computed points per frame.  If the new number is
// more than our VB can hold, recreate the VB.
//
void
CMyD3DApplication::inc_points(int amount)
{
    const int new_points =
        max(MIN_NUM_POINTS, min(MAX_NUM_POINTS, int(m_num_points) + amount));
    if (new_points > m_num_vb_points)
    {
        create_vb(new_points);
    }
    else
    {
        m_num_points = new_points;
    }

    create_points();
}

//-----------------------------------------------------------------------------
// CMyD3DApplication::create_points
//
// Create the next m_num_points iterations of the chaotic map, storing the
// resulting points into the vertex buffer.
//
void
CMyD3DApplication::create_points()
{
    // first compute the new points for this frame.
    // The computation is a repetition of the formulas:
    //
    //      x' = 1 - y + |x|
    //      y' = x
    //
    //      |x| denotes the absolute value of x.
    //
    // For values of (x,y) in the plane starting with (-0.1, 0) this
    // produces a chaotic orbit in the plane.  (See "Science of Fractal
    // Images", Heinz-Otto Peitgen and Dietmar Saupe, eds., pg. 149.)
    //
    // This routine crunches through m_num_points iterations of this
    // process, computing a 2D vertex for each point and storing it
    // in the locked vertex buffer.
    float x = m_x0;
    float y = m_y0;
    float min_x = x;
    float max_x = x;
    float min_y = y;
    float max_y = y;

    {
        vb_lock<s_vertex> vertices(m_vb, D3DLOCK_DISCARD);
        s_vertex *points = vertices.vertices();
        for (UINT i = 0; i < m_num_points; i++)
        {
            // store current
            points[i].set(x, y, m_fg);

            // compute next
            const float next_x = 1.0f - y + (x < 0.0f ? -x : x);
            y = x;
            x = next_x;

            // adjust bounding box
            accum_minmax(x, min_x, max_x);
            accum_minmax(y, min_y, max_y);
        }

        // forces lock to be released, holding it as little as possible
    }

    // remember current as initial for next draw
    m_x0 = x;
    m_y0 = y;
    accum_minmax(min_x, m_min_x, m_max_x);
    accum_minmax(max_x, m_min_x, m_max_x);
    accum_minmax(min_y, m_min_y, m_max_y);
    accum_minmax(max_y, m_min_y, m_max_y);

    m_iterations += m_num_points/100;

    update_stats();
}

//-----------------------------------------------------------------------------
// CMyD3DApplication::update_stats
//
// Update the string reporting statistics about this sample.
//
void
CMyD3DApplication::update_stats()
{
    tostringstream buff;
    buff << std::fixed << std::setprecision(1) << float(m_iterations/10.f)
        << _T(" Kiters, ") << m_num_points << _T(" pts.");
    if (m_textured)
    {
        const tstring::size_type pos = m_texture_file.rfind(_T('\\'));
        buff << _T(", \"")
            << ((tstring::npos != pos) ? 
                m_texture_file.substr(pos+1) : m_texture_file)
            << _T("\"");
    }
    buff << std::ends;
    m_point_stats = buff.str();
}

//-----------------------------------------------------------------------------
// set_states
//
// Calls SetTextureStageState on an array of state/value pairs.
//
void
set_states(IDirect3DDevice8 *device, UINT stage,
           const s_tss *states, UINT num_states)
{
    for (UINT i = 0; i < num_states; i++)
    {
        THR(device->SetTextureStageState(stage,
                states[i].m_state, states[i].m_value));
    }
}

//-----------------------------------------------------------------------------
// set_states
//
// Calls SetRenderState() on an array of state/value pairs.
//
void
set_states(IDirect3DDevice8 *device, const s_rs *states, UINT num_states)
{
    for (UINT i = 0; i < num_states; i++)
    {
        THR(device->SetRenderState(states[i].m_state, states[i].m_value));
    }
}

//-----------------------------------------------------------------------------
// CMyD3DApplication::set_state
//
// Initializes the device state based on the desired rendering setup:
//  S_BBOX:     bounding box
//  S_DIFFUSE:  points with diffuse color
//  S_TEXTURE:  points with texture modulated by diffuse alpha
//
#define RS(ary_) set_states(m_pd3dDevice, ary_, NUM_OF(ary_))
#define TSS(ary_) set_states(m_pd3dDevice, 0, ary_, NUM_OF(ary_))
void
CMyD3DApplication::set_state(e_setups setup) const
{
    const s_tss disable_texture[] =
    {
        D3DTSS_ALPHAOP, D3DTOP_DISABLE,
        D3DTSS_COLOROP, D3DTOP_DISABLE
    };
    if (S_BBOX == setup)
    {
        // disable texturing
        TSS(disable_texture);
        THR(m_pd3dDevice->SetTexture(0, NULL));
        THR(m_pd3dDevice->SetVertexShader(s_vertex::FVF));
    }
    else if (S_DIFFUSE == setup || S_TEXTURE == setup)
    {
        const s_rs r_states[] =
        {
            D3DRS_LIGHTING, FALSE,
            D3DRS_COLORVERTEX, TRUE,
            D3DRS_SPECULARENABLE, FALSE,
            D3DRS_TEXTUREFACTOR,
                D3DCOLOR_ARGB(D3DColor_Alpha(m_fg), D3DColor_Alpha(m_fg),
                              D3DColor_Alpha(m_fg), D3DColor_Alpha(m_fg)),
            D3DRS_ALPHATESTENABLE, FALSE,
            D3DRS_ZENABLE, D3DZB_FALSE,
            D3DRS_DITHERENABLE, TRUE,
            D3DRS_ALPHABLENDENABLE, TRUE,
            D3DRS_SRCBLEND, D3DBLEND_ONE,
            D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA,
            D3DRS_BLENDOP, D3DBLENDOP_ADD,
            D3DRS_WRAP0, D3DWRAPCOORD_0 | D3DWRAPCOORD_1
        };
        RS(r_states);
        THR(m_pd3dDevice->SetVertexShader(s_vertex::FVF));
        THR(m_pd3dDevice->SetStreamSource(0, m_vb, sizeof(s_vertex)));

        if (S_DIFFUSE == setup)
        {
            TSS(disable_texture);
            THR(m_pd3dDevice->SetTexture(0, NULL));
        }
        else
        {
            const s_tss ts_states[] =
            {
                D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION,
                D3DTSS_COLORARG1, D3DTA_TEXTURE,
                D3DTSS_COLOROP, D3DTOP_MODULATE,
                D3DTSS_COLORARG2, D3DTA_TFACTOR,
                D3DTSS_ALPHAARG1, D3DTA_TEXTURE,
                D3DTSS_ALPHAOP, D3DTOP_SELECTARG2,
                D3DTSS_ALPHAARG2, D3DTA_TFACTOR,
                D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2,
                D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP,
                D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP,
                D3DTSS_MINFILTER, D3DTEXF_LINEAR,
                D3DTSS_MAGFILTER, D3DTEXF_LINEAR,
                D3DTSS_MIPFILTER, D3DTEXF_NONE
            };
            TSS(ts_states);
            ATLASSERT(m_texture);
            THR(m_pd3dDevice->SetTexture(0, m_texture));
            D3DXMATRIX texform(2, 0, 0, 0,
                               0, -2, 0, 0,
                               0, 0, 2, 0,
                               0.5f, 0.5f, 0, 2);
            THR(m_pd3dDevice->SetTransform(D3DTS_TEXTURE0, &texform));
        }

        // build a world matrix that fits all of the model into [-1,1]x[-1,1]
        D3DXMATRIX trans;
        const float width = m_max_x - m_min_x;
        const float height = m_max_y - m_min_y;
        ::D3DXMatrixTranslation(&trans,
            -(m_min_x + width*0.5f), -(m_min_y + height*0.5f), 0.f);
        D3DXMATRIX scale;
        const float factor = width > height ? width : height;
        ::D3DXMatrixScaling(&scale,
            1.f/(SQRT_2*factor), 1.f/(SQRT_2*factor), 1.f);
        D3DXMATRIX world = trans*scale;
        THR(m_pd3dDevice->SetTransform(D3DTS_WORLD, &world));

        // rotate the view around the Z axis by the current viewing angle
        D3DXMATRIX view;
        D3DXVECTOR3 eye(0, 0, -0.2f);
        D3DXVECTOR3 at(0, 0, 0.5f);
        D3DXVECTOR3 view_up(sinf(m_view_angle), cosf(m_view_angle), 0.f);
        ::D3DXMatrixLookAtLH(&view, &eye, &at, &view_up);
        THR(m_pd3dDevice->SetTransform(D3DTS_VIEW, &view));

        // use an orthographic projection that preserves aspect ratio
        D3DXMATRIX proj;
        const float ratio =
            float(m_d3dsdBackBuffer.Width)/m_d3dsdBackBuffer.Height;
        if (ratio > 1.0f)
        {
            ::D3DXMatrixOrthoLH(&proj, ratio, 1.0f, 0.f, 1.f);
        }
        else
        {
            ::D3DXMatrixOrthoLH(&proj, 1.f, 1.f/ratio, 0.f, 1.f);
        }
        THR(m_pd3dDevice->SetTransform(D3DTS_PROJECTION, &proj));
    }
}
#undef RS
#undef TSS
