////////////////////////////////////////////////////////////////////////////
// gingerbread.cpp - minimal D3D application drawing gingerbread fractal
//
#include "stdafx.h"

// width and height of frame buffer
const UINT FRAME_WIDTH = 320;
const UINT FRAME_HEIGHT = 240;

// device window and interface
HWND g_window = 0;
IDirect3DDevice8 *g_device = 0;

// device presentation parameters
D3DPRESENT_PARAMETERS g_presentation = {
    // width, height, format, number of back buffers
    FRAME_WIDTH, FRAME_HEIGHT, D3DFMT_X8R8G8B8, 1,
    // multisample type, swap effect, device window, windowed
    D3DMULTISAMPLE_NONE, D3DSWAPEFFECT_COPY, g_window, TRUE,
    // auto depth/stencil surface, depth/stencil surface format
    TRUE, D3DFMT_D16
};

const DWORD VERTEX_FVF = D3DFVF_XYZRHW | D3DFVF_DIFFUSE;
struct vertex_s
{
    float m_x, m_y, m_z, m_recip_w;
    DWORD m_diffuse;

    void set_xy(const float x, const float y, const D3DCOLOR diffuse)
    {
        m_x = FRAME_WIDTH*x;
        m_y = FRAME_HEIGHT*y;
        m_z = 0.5f;
        m_recip_w = 2.0f;
        m_diffuse = diffuse;
    }
};

// draw a frame containing the scene
void
frame_draw()
{
    if (g_device)
    {
        const unsigned NUM_POINTS = 20000;
        std::vector<vertex_s> points(NUM_POINTS);

        static float x0 = -0.1f;
        static float y0 = 0.0f;
        int n = points.size();
        float x = x0;
        float y = y0;
        float min_x = x0;
        float max_x = x0;
        float min_y = y0;
        float max_y = y0;
        for (int i = 0; i < NUM_POINTS; i++)
        {
            // store current
            points[i].m_x = x;
            points[i].m_y = y;

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

            // adjust bounding box
            if (x < min_x) min_x = x;
            if (x > max_x) max_x = x;
            if (y < min_y) min_y = y;
            if (y > max_y) max_y = y;
        }
        // remember current as initial for next draw
        x0 = x;
        y0 = y;

        // normalize to [0,1] and set colors
        const float scale = 1.0f /
            (max_x-min_x > max_y-min_y ? max_x-min_x : max_y-min_y);
        for (i = 0; i < NUM_POINTS; i++)
        {
            const float x = (points[i].m_x - min_x)*scale;
            const float y = (points[i].m_y - min_y)*scale;
            points[i].set_xy(x, 1.f - y,
                             D3DCOLOR_COLORVALUE(1.f-x, x, y, 1.f));
        }

        // draw the scene and clear the frame buffer
        g_device->BeginScene();
        g_device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
                        0, 1.0f, 0);

        // draw them in batches < 64K vertices for really large point counts
        g_device->SetVertexShader(VERTEX_FVF);
        const UINT BATCH_COUNT = 0xFF00;
        for (i = 0; i < NUM_POINTS; i += BATCH_COUNT)
        {
            const UINT count = i + BATCH_COUNT > NUM_POINTS ? (NUM_POINTS - i) : BATCH_COUNT;
            g_device->DrawPrimitiveUP(D3DPT_POINTLIST, count,
                                      &points[i], sizeof(vertex_s));
        }

        g_device->EndScene();

        // present the scene & handle lost devices
        HRESULT hr = g_device->Present(NULL, NULL, NULL, NULL);
        while (D3DERR_DEVICELOST == hr)
        {
            do
            {
                ::Sleep(1000);
                hr = g_device->TestCooperativeLevel();
            }
            while (hr != D3DERR_DEVICENOTRESET);

            if (FAILED(g_device->Reset(&g_presentation)))
            {
                hr = D3DERR_DEVICELOST;
            }
        }

        // clear Win32 dirty region
        ::ValidateRect(g_window, NULL);
    }
}

// basic windows message procedure
LRESULT CALLBACK
frame_window_proc(HWND window, UINT msg, WPARAM wp, LPARAM lp)
{
    LRESULT result = 0;

    if (WM_SIZING == msg || WM_PAINT == msg)
    {
        frame_draw();
    }
    else
    {
        result = ::DefWindowProc(window, msg, wp, lp);
    }
    return result;
}

int __stdcall
_tWinMain(HINSTANCE instance, HINSTANCE, LPTSTR, int)
{
    const TCHAR *const APP_NAME = _T("Ginger Bread Man Fractal");

    // Register the window class for the main window.
    {
        const WNDCLASS wc = {
            0, frame_window_proc, 0, 0, instance,
            NULL, LoadCursor(NULL, IDC_CROSS),
            static_cast<HBRUSH>(GetStockObject(BLACK_BRUSH)),
            NULL, APP_NAME
        };
        if (!::RegisterClass(&wc))
        {
            return 1;
        }
    }

    // Create the main window.
    g_window = ::CreateWindow(APP_NAME, APP_NAME, WS_OVERLAPPEDWINDOW,
                              CW_USEDEFAULT, CW_USEDEFAULT,
                              FRAME_WIDTH, FRAME_HEIGHT, GetDesktopWindow(),
                              NULL, instance, NULL);
    if (!g_window)
    {
        return 1;
    }
    g_presentation.hDeviceWindow = g_window;

    // Get IDirect3D8 interface, then create a device
    {
        IDirect3D8 *d3d = ::Direct3DCreate8(D3D_SDK_VERSION);
        if (!d3d)
        {
            ::DestroyWindow(g_window);
            return 1;
        }
        {
            D3DDISPLAYMODE dm;
            HRESULT hr = d3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &dm);
            if (FAILED(hr))
            {
                ::DestroyWindow(g_window);
                return 1;
            }
            g_presentation.BackBufferFormat = dm.Format;
        }
        HRESULT hr = d3d->
            CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_window,
                         D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                         &g_presentation, &g_device);
        d3d->Release();
        if (FAILED(hr))
        {
            ::DestroyWindow(g_window);
            return 1;
        }
    }

    // display the window
    ::ShowWindow(g_window, SW_SHOWDEFAULT);
    ::UpdateWindow(g_window);

    // pump messages
    {
        MSG msg;
        while (0 < ::GetMessage(&msg, g_window, 0, 0))
        {
            ::TranslateMessage(&msg);
            ::DispatchMessage(&msg);
        }
    }

    // clean up resources
    g_device->Release();
    ::DestroyWindow(g_window);

    return 0;
}
