//-----------------------------------------------------------------------------
// File: rt_D3DXSprite.cpp
//
// Desc: Simple ID3DXSprite sample.  Draws two kinds of sprite displays,
// either a dynamically rotating collection of sprites, or tiling the
// window with sprites.
//
// Copyright (c) 1997-2000 Microsoft Corporation. All rights reserved.
// Portions Copyright (C) 2001 Rich Thomson.  All rights reserved.
//-----------------------------------------------------------------------------

// C++ standard includes
#include <cmath>
#include <iomanip>
#include <sstream>
#include <vector>

// Win32 includes
#define STRICT
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <commdlg.h>
#include <tchar.h>

// ATL includes
#include <atlbase.h>
#include <atlconv.h>

// DX8 includes
#include <d3dx8.h>

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

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

//-----------------------------------------------------------------------------
const UINT MIN_NUM_SPRITES = 1;
const UINT HOME_NUM_SPRITES = 10;
const UINT END_NUM_SPRITES = 100;

const UINT SPRITE_LINE_SIZE = 1;
const UINT SPRITE_PAGE_SIZE = 10;

const DWORD HIMETRIC_INCH = 2540;

//-----------------------------------------------------------------------------
// tstring
// tostringstream
//
// 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 containers (list, vector, map, etc.),
// iterators and algorithms.
//
typedef std::basic_string<TCHAR> tstring;
typedef std::basic_ostringstream<TCHAR> tostringstream;

//-----------------------------------------------------------------------------
// 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, LPCTSTR expr)
{
    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("): ") << expr
            << _T("\n    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.  Double-clicking on the message in the
        // debug window in Visual Studio will bring you to the line
        // where the failure was passed to THR().
        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_, _T(#hr_))

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

//-----------------------------------------------------------------------------
// enable_menu
//
// Enables a menu item to reflect the value of a bool.
//
inline void
enable_menu(HMENU menu, UINT item, bool value)
{
    ::EnableMenuItem(menu, item, value ? MF_ENABLED : MF_GRAYED);
}

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

//-----------------------------------------------------------------------------
// 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;
}

//-----------------------------------------------------------------------------
// s_transform
//
// Holds the transformation state for one of the sprites.  All the sprites
// use the same texture, so that information isn't stored here, but in the
// application object.
//
struct s_transform
{
    D3DXVECTOR2 m_scale;
    D3DXVECTOR2 m_center;
    D3DXVECTOR2 m_translate;
    float m_rotate;

    // order of transformations:
    // translate to center of rotation
    // scale
    // rotate
    // translate
    s_transform()
        : m_scale(1, 1),
        m_center(0, 0),
        m_translate(0, 0),
        m_rotate(0)
    {
    }

    s_transform(float sx, float sy,
                float cx, float cy, float rot,
                float tx, float ty)
                : m_scale(sx, sy),
                m_center(cx, cy),
                m_rotate(rot),
                m_translate(tx, ty)
    {
    }
};

//-----------------------------------------------------------------------------
// 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
{
public:
    LRESULT MsgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
    CMyD3DApplication();

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

private:
    CD3DFont *m_font;
    D3DCOLOR m_text_fg;
    CComPtr<ID3DXSprite> m_sprite;
    CComPtr<IDirect3DTexture8> m_texture;
    D3DSURFACE_DESC m_texture_desc;
    tstring m_texture_file;
    std::vector<s_transform> m_transforms;
    DWORD m_flags;
    UINT m_tick;
    UINT m_image_width;
    UINT m_image_height;
    UINT m_num_sprites;
    tstring m_stats;
    enum e_sprite_style
    {
        SS_DYNAMIC = 1,
        SS_TILED = 2
    };
    DWORD m_sprite_style;
	DWORD m_mirror_flags;
    enum e_mirror_flags
    {
        MIRROR_X = 1,
        MIRROR_Y = 2
    };
    D3DCOLOR m_background;
    D3DCOLOR m_transparency;
    D3DCOLOR m_colorkey;

    void create_texture();
    void render_sprites(UINT start, UINT stop);
    void inc_sprites(int delta);
    s_transform next_transform() const;
    void set_stats();

    void text(float x, float y, LPCTSTR str)
    {
        THR(m_font->DrawText(x, y, m_text_fg, const_cast<LPTSTR>(str)));
    }
    void text(float x, float y, const tostringstream &stream)
    {
        text(x, y, stream.str().c_str());
    }
    LRESULT on_command(HWND window, WPARAM wp, LPARAM lp, bool &handled);
    LRESULT on_key_down(HWND window, WPARAM wp, LPARAM lp, bool &handled);
    LRESULT on_file_open_texture(HWND window, WPARAM wp, LPARAM lp, bool &handled);
};

//-----------------------------------------------------------------------------
// 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;
};

//-----------------------------------------------------------------------------
// Name: CMyD3DApplication()
// Desc: Application constructor. Sets attributes for the app.
//-----------------------------------------------------------------------------
CMyD3DApplication::CMyD3DApplication()
    : m_font(new CD3DFont(_T("Arial"), 12, D3DFONT_BOLD)),
    m_text_fg(D3DCOLOR_XRGB(0, 230, 0)),
    m_sprite(0),
    m_texture(0),
    m_texture_file(_T("dx5_logo.bmp")),
    m_transforms(),
    m_flags(0),
    m_tick(0),
    m_image_width(0),
    m_image_height(0),
    m_num_sprites(HOME_NUM_SPRITES),
    m_stats(_T("")),
    m_sprite_style(SS_TILED | SS_DYNAMIC),
    m_mirror_flags(0),
    m_background(D3DCOLOR_XRGB(0, 0, 0)),
    m_transparency(~0),
    m_colorkey(D3DCOLOR_XRGB(0, 0, 0))
{
    set_stats();
    m_transforms.reserve(10);
    ::ZeroMemory(&m_texture_desc, sizeof(m_texture_desc));
    m_strWindowTitle    = _T("rt_D3DXSprite");
    m_bUseDepthBuffer   = FALSE;
}

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

//-----------------------------------------------------------------------------
// CMyD3DApplication::next_transform
//
// Generate the next transform in the sequence.
//
s_transform
CMyD3DApplication::next_transform() const
{
    // stash off the image, texture and screen dimensions as floats
    const float iw = m_image_width;
    const float ih = m_image_height;
    const float tw = m_texture_desc.Width;
    const float th = m_texture_desc.Height;
    const float sw = m_d3dsdBackBuffer.Width;
    const float sh = m_d3dsdBackBuffer.Height;

    // derive aspect-ratio preserving scale constant.
    float aspect = 1.0f;
    if (sw/sh > iw/ih)
    {
        // screen is wider aspect ratio than texture
        aspect = sh/ih;
    }
    else
    {
        aspect = sw/iw;
    }

    // The order of transformations for Draw() is
    //      first scale,
    //      then translate to center of rotation,
    //      then rotate,
    //      then translate to final position
    //
    // Here we make the entire sprite spin around its center, scale it
    // to fit the relative aspect ratio of the window, and translate it
    // to locate it at the center of the window.
    const float freq = 2.0f*D3DX_PI/100.0f;
    const float angle = D3DX_PI*0.75f*::sinf(freq*float(m_tick));
    s_transform next(aspect*iw/tw, aspect*ih/th,
                     0.5f*iw*aspect, 0.5f*ih*aspect, angle,
                     0.5f*sw - 0.5f*iw*aspect, 0.5f*sh - 0.5f*ih*aspect);

    return next;
}

//-----------------------------------------------------------------------------
// Name: FrameMove()
// Desc: Called once per frame, the call is the entry point for animating
//       the scene.
//-----------------------------------------------------------------------------
HRESULT CMyD3DApplication::FrameMove()
{
    if (SS_DYNAMIC & m_sprite_style)
    {
        const s_transform next = next_transform();
        if (m_transforms.size() < m_num_sprites)
        {
            // append transformations until we reach max count...
            m_transforms.push_back(next);
        }
        else
        {
            // then overwrite in circular buffer style
            m_transforms[m_tick % m_num_sprites] = next;
        }
        m_tick++;
    }

    return S_OK;
}

//-----------------------------------------------------------------------------
// CMyD3DApplication::render_sprites
//
// Render each of the sprites from start through stop, non-inclusive.
//
void
CMyD3DApplication::render_sprites(UINT start, UINT stop)
{
    // translate to center of frame
    // rotate
    // scale to size of frame

    // order of transformations:
    // translate to center of rotation
    // scale
    // rotate
    // translate
    for (UINT i = start; i < stop; i++)
    {
        THR(m_sprite->Draw(m_texture, NULL,
                &m_transforms[i].m_scale, &m_transforms[i].m_center,
                m_transforms[i].m_rotate, &m_transforms[i].m_translate,
                m_transparency));
    }
}

//-----------------------------------------------------------------------------
// 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
    THR(m_pd3dDevice->Clear(0L, NULL, D3DCLEAR_TARGET,
                            m_background, 1.0f, 0L));

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

    // render the sprites, either the entire buffer, or two chunks
    THR(m_sprite->Begin());

    if (m_sprite_style & SS_TILED)
    {
        // tile the screen
        const UINT ntw = m_d3dsdBackBuffer.Width/m_image_width + 1;
        const UINT nth = m_d3dsdBackBuffer.Height/m_image_height + 1;
        const float sx = float(m_image_width)/float(m_texture_desc.Width);
        const float sy = float(m_image_height)/float(m_texture_desc.Height);
        for (UINT iy = 0; iy < nth; iy++)
        {
            float my = 1.0f;
            UINT oy = 0.0f;
            if ((m_mirror_flags & MIRROR_Y) && (iy & 1))
            {
                my = -1.0f;
                oy = 1;
            }
            for (UINT ix = 0; ix < ntw; ix++)
            {
                float mx = 1.0f;
                UINT ox = 0;
                if ((m_mirror_flags & MIRROR_X) && (ix & 1))
                {
                    mx = -1.0f;
                    ox = 1;
                }
                const s_transform xform(mx*sx, my*sy, 0, 0, 0,
                    (ix+ox)*m_image_width, (iy+oy)*m_image_height);
                THR(m_sprite->Draw(m_texture, NULL,
                    &xform.m_scale, &xform.m_center,
                    xform.m_rotate, &xform.m_translate,
                    m_transparency));
            }
        }
    }
    if (m_sprite_style & SS_DYNAMIC)
    {
        const UINT split = m_tick % m_num_sprites;
        const UINT size = m_transforms.size();
        if (split == m_transforms.size())
        {
            render_sprites(0, m_transforms.size());
        }
        else
        {
            render_sprites(split, m_transforms.size());
            render_sprites(0, split);
        }
    }

    THR(m_sprite->End());

    // Show frame rate
    text(2,  0, m_strFrameStats);
    text(2, 20, m_strDeviceStats);
    if (SS_DYNAMIC & m_sprite_style)
    {
        text(2, 40, m_stats.c_str());
    }

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

    return S_OK;
}

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

    return S_OK;
}

//-----------------------------------------------------------------------------
// CMyD3DApplication::create_texture
//
// Create the texture and gather immage dimensions from the current 
// filename.
// 
void
CMyD3DApplication::create_texture()
{
    m_texture = 0;

    TCHAR path[MAX_PATH] = { 0 };
    THR(DXUtil_FindMediaFile(path, const_cast<LPTSTR>(m_texture_file.c_str())));

    D3DXIMAGE_INFO info = { 0 };
    THR(D3DXCreateTextureFromFileEx(m_pd3dDevice, path,
            256, 256, D3DX_DEFAULT,
            0, D3DFMT_UNKNOWN, D3DPOOL_MANAGED,
            D3DX_FILTER_TRIANGLE | D3DX_FILTER_MIRROR,
            D3DX_FILTER_TRIANGLE | D3DX_FILTER_MIRROR,
            m_colorkey, &info, NULL, &m_texture));
    m_image_width = info.Width;
    m_image_height = info.Height;
    THR(m_texture->GetLevelDesc(0, &m_texture_desc));

    m_transforms.clear();
    m_tick = 0;
}

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

    THR(D3DXCreateSprite(m_pd3dDevice, &m_sprite));
    create_texture();

    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_font->InvalidateDeviceObjects();
    m_sprite = 0;
    m_texture = 0;

    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_font->DeleteDeviceObjects();
    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()
{
    delete m_font;
    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)
{
    bool handled = false;
    LRESULT res = 0;

#define HANDLE_MSG(msg_, fn_) \
    case msg_: res = fn_(window, wp, lp, handled); break

    switch (msg)
    {
    HANDLE_MSG(WM_COMMAND, on_command);
    HANDLE_MSG(WM_KEYDOWN, on_key_down);
    }
#undef HANDLE_MSG

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

//-----------------------------------------------------------------------------
// on_command
//
// WM_COMMAND handler
//
LRESULT
CMyD3DApplication::on_command(HWND window, WPARAM wp, LPARAM lp, bool &handled)
{
    const UINT control = LOWORD(wp);
    const HMENU menu = ::GetMenu(window);
    const HMENU mirror = ::GetSubMenu(menu, 2);

    switch (control)
    {
    case IDM_FILE_OPEN_TEXTURE:
        {
            pauser block(*this);
            handled = true;
            return on_file_open_texture(window, wp, lp, handled);
        }
        break;

    case IDM_SS_DYNAMIC:
        m_sprite_style ^= SS_DYNAMIC;
        check_menu(menu, IDM_SS_DYNAMIC, (m_sprite_style & SS_DYNAMIC) != 0);
        handled = true;
        break;

    case IDM_SS_TILED:
        m_sprite_style ^= SS_TILED;
        {
            const bool tiled = (m_sprite_style & SS_TILED) != 0;
            check_menu(menu, IDM_SS_TILED, tiled);
            enable_menu(menu, IDM_MIRROR_X, tiled);
            enable_menu(menu, IDM_MIRROR_Y, tiled);
        }
        handled = true;
        break;

    case IDM_MIRROR_X:
        m_mirror_flags ^= MIRROR_X;
        check_menu(menu, control, (m_mirror_flags & MIRROR_X) != 0);
        handled = true;
        break;

    case IDM_MIRROR_Y:
        m_mirror_flags ^= MIRROR_Y;
        check_menu(menu, control, (m_mirror_flags & MIRROR_Y) != 0);
        handled = true;
        break;

    case IDM_COLOR_BACKGROUND:
        m_background = choose_color(window, m_background, true);
        handled = true;
        break;

    case IDM_COLOR_TRANSPARENCY:
        m_transparency = choose_color(window, m_transparency, true);
        handled = true;
        break;

    case IDM_COLOR_COLORKEY:
        m_colorkey = choose_color(window, m_colorkey, true);
        create_texture();
        handled = true;
        break;
    }

    return 0;
}

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

//-----------------------------------------------------------------------------
// CMyD3DApplication::on_key_down
//
// Handle key messages by adjusting the number of points computed and
// displayed per frame.
//
LRESULT
CMyD3DApplication::on_key_down(HWND window, WPARAM wp, LPARAM lp, bool &handled)
{
    if (m_sprite_style & SS_DYNAMIC)
    {
        switch (wp)
        {
        // adjust segments up or down and rebuild mesh
        case VK_UP:     inc_sprites(+SPRITE_LINE_SIZE); handled = true; break;
        case VK_DOWN:   inc_sprites(-SPRITE_LINE_SIZE); handled = true; break;
        case VK_PRIOR:  inc_sprites(+SPRITE_PAGE_SIZE); handled = true; break;
        case VK_NEXT:   inc_sprites(-SPRITE_PAGE_SIZE); handled = true; break;

        // set segments to specific value and rebuild mesh
        case VK_HOME:
            m_num_sprites = HOME_NUM_SPRITES;
            set_stats();
            handled = true;
            break;

        case VK_END:
            m_num_sprites = END_NUM_SPRITES;
            set_stats();
            handled = true;
            break;
        }
    }

    return 0;
}

void
CMyD3DApplication::set_stats()
{
    tostringstream buff;
    buff << m_num_sprites << _T(" sprites.");
    m_stats = buff.str();
}

#undef min
#undef max
template <typename T>
T min(const T &v1, const T &v2)
{
    return v1 < v2 ? v1 : v2;
}
template <typename T>
T max(const T &v1, const T &v2)
{
    return v1 < v2 ? v2 : v1;
}

//-----------------------------------------------------------------------------
// CMyD3DApplication::inc_sprites
//
// Adjust the number of sprites drawn, keeping within bounds and adjusting
// storage array.
//
void
CMyD3DApplication::inc_sprites(int delta)
{
    const int new_num =
        max(int(MIN_NUM_SPRITES), min(int(END_NUM_SPRITES), int(m_num_sprites) + delta));
    const UINT split = m_tick % m_num_sprites;

    if (new_num > m_num_sprites)
    {
        for (UINT i = m_num_sprites; i < new_num; i++)
        {
            m_transforms.insert(&m_transforms[split], next_transform());
            m_tick++;
        }
        m_num_sprites = new_num;
    }
    else if (new_num < m_num_sprites)
    {
        UINT count = m_num_sprites - new_num;
        ATLASSERT(MIN_NUM_SPRITES + count <= m_transforms.size());
        if (split + count < m_transforms.size())
        {
            m_transforms.erase(&m_transforms[split],
                               &m_transforms[split + count]);
        }
        else
        {
            count -= m_transforms.size() - split;
            m_transforms.erase(&m_transforms[split], m_transforms.end());
            m_transforms.erase(&m_transforms[0], &m_transforms[count]);
        }
        m_tick = split + new_num - m_num_sprites;
        m_num_sprites = new_num;
    }
    set_stats();
}

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