#include <windows.h>
#include <mmsystem.h>
#include <math.h>
#include "Pattern.h"



//--- DATA -------------------------------------------------------------------//

HINSTANCE hMainInstance;
HWND hMainWindow = NULL;

char szAppName[40] = "Lewpen's pattern generator";

int mouse_x = 0, mouse_y = 0;
int mouse_xstart = 0, mouse_ystart = 0;

bool mouse_drag = false;

int width = 480;
int height = 360;

int smwidth, smheight;

char *buf;



//---- Raytrace Data Types ---------------------------------------------------//

typedef struct
{
  float ax, ay, az;
  float x, y, z;
  float rad;
  int col;
}
RTSPHERE;



typedef struct
{
  RTSPHERE *obj;

  bool shadow;

  float t;
  float x, y, z;
  float nx, ny, nz;
  float reflx, refly, reflz;

  int col;
}
RTPIXEL;



typedef struct
{
  float ox, oy, oz;
  float dx, dy, dz;
}
RTRAY;



//---- RAYTRACE DATA ---------------------------------------------------------//

RTSPHERE spheres[64];
int numspheres = 0;

RTRAY raystack[4];

RTPIXEL *rtpixel;

float light_x = 1.0, light_y = 5.0, light_z = 2.0-6.0;

float rx = 0, ry = 0;



//---- Raytrace functions ----------------------------------------------------//

void sphere(float x, float y, float z, float rad, int col)
{
  spheres[numspheres].ax = x;
  spheres[numspheres].ay = y;
  spheres[numspheres].az = z;
  
  spheres[numspheres].x = x;
  spheres[numspheres].y = y;
  spheres[numspheres].z = z;

  spheres[numspheres].rad = rad;
  spheres[numspheres].col = col;

  numspheres ++;
}



//---- vect_unit

void vect_unit(float *v)
{
  float d = v[0]*v[0] + v[1]*v[1] + v[2]*v[2];

  if(d > 0)
  {
    d = 1.0 / sqrt(d);
    v[0] *= d;
    v[1] *= d;
    v[2] *= d;
  }
}



//---- rt_intersect_sphere

float rt_intersect_sphere(RTRAY *ray, RTSPHERE *sphere, RTPIXEL *pix)
{
  float x = sphere->x;
  float y = sphere->y;
  float z = sphere->z;
  float r = sphere->rad;

  float a, b, c, d, t1, t2;

  a = ray->dx*ray->dx + ray->dy*ray->dy + ray->dz*ray->dz;
  b = 2*(ray->dx*(ray->ox-x) + ray->dy*(ray->oy-y) + ray->dz*(ray->oz-z));
  c = (ray->ox-x)*(ray->ox-x) + (ray->oy-y)*(ray->oy-y) + (ray->oz-z)*(ray->oz-z) - r*r;
  d = b*b-4*a*c;

  if(d > 0)
  {
    d = sqrt(d);

    t2 = (-b + d) / (2*a);
    t1 = (-b - d) / (2*a);

    if(t1 <= 0)
    {
      d = t2;
    }
    else if(t2 <= 0)
    {
      d = t1;
    }
    else
    {
      d = t1 < t2 ? t1 : t2;
    }

    if(d > 0)
    {
      pix->t = d;

      pix->x = ray->ox + pix->t*ray->dx;
      pix->y = ray->oy + pix->t*ray->dy;
      pix->z = ray->oz + pix->t*ray->dz;

      a = 1.0/r;
      pix->nx = a*(pix->x - x);
      pix->ny = a*(pix->y - y);
      pix->nz = a*(pix->z - z);

      a = - (ray->dx*pix->nx + ray->dy*pix->ny + ray->dz*pix->nz);
      pix->reflx = ray->dx + 2*pix->nx*a;
      pix->refly = ray->dy + 2*pix->ny*a;
      pix->reflz = ray->dz + 2*pix->nz*a;

      return d;
    }
  }

  return -1;
}



//---- rt_shadow

bool rt_shadow(RTPIXEL *pix)
{
  //  Calculate shadow

  RTRAY shadow_ray;

  shadow_ray.ox = pix->x;
  shadow_ray.oy = pix->y;
  shadow_ray.oz = pix->z;

  shadow_ray.dx = light_x - pix->x;
  shadow_ray.dy = light_y - pix->y;
  shadow_ray.dz = light_z - pix->z;

  //  Test against objects

  RTPIXEL pix2;

  int i;

  for(i=0; i<numspheres; i++)
  {
    if( rt_intersect_sphere(&shadow_ray, &spheres[i], &pix2) > 0.0001 ) return true;
  }

  return false;
}



//---- rt_intersect

void rt_intersect(RTRAY *ray, RTPIXEL *pix)
{
  int i, foundi = -1;
  float dd, d = 9999999;

  for(i=0; i<numspheres; i++)
  {
    dd = rt_intersect_sphere(ray, &spheres[i], pix);
    if(dd > 0 && dd < d) { d = dd; foundi = i; }
  }

  if(foundi < 0)
  {
    pix->obj = NULL;
    pix->col = 0;
  }
  else
  {
    pix->obj = &spheres[foundi];
    pix->shadow = rt_shadow(pix);
    pix->col = pix->obj->col;

    if(pix->shadow)
    {
      pix->col = (pix->col >> 1) & 0x7F7F7F7F;
    }
  }
}



//---- rect

void rect(int x, int y, int w, int h, int col)
{
  char r = col>>16;
  char g = col>>8;
  char b = col;

  int i, j;

  for(j=y; j<y+h; j++) for(i=x; i<x+w; i++)
  {
    buf[ (j*width+i)*3 + 0 ] = b;
    buf[ (j*width+i)*3 + 1 ] = g;
    buf[ (j*width+i)*3 + 2 ] = r;
  }
}



//---- Display ---------------------------------------------------------------//

void Display(HDC dc, char *buf, int w, int h)
{
  BITMAPINFO bmi;

  bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
  bmi.bmiHeader.biWidth = w;
  bmi.bmiHeader.biHeight = -h;
  bmi.bmiHeader.biPlanes = 1;
  bmi.bmiHeader.biBitCount = 24;
  bmi.bmiHeader.biCompression = BI_RGB;
  bmi.bmiHeader.biSizeImage = 0;
  bmi.bmiHeader.biXPelsPerMeter = 1000;
  bmi.bmiHeader.biYPelsPerMeter = 1000;
  bmi.bmiHeader.biClrUsed = 0;
  bmi.bmiHeader.biClrImportant = 0;

  StretchDIBits(
    dc,
    0, 0, w, h,
    0, 0, w, h,
    (LPVOID)buf,
    (LPBITMAPINFO)&bmi,
    DIB_RGB_COLORS, SRCCOPY
  );

}



//---- Startup ---------------------------------------------------------------//

void Startup()
{
  //- Allocate buffer

  width = (width+7) & -8;
  height = (height+7) & -8;

  buf = (char*)malloc(width*height*3);

  smwidth = (width>>3) + 1;
  smheight = (height>>3) + 1;

  rtpixel = (RTPIXEL*)malloc(smwidth*smheight*sizeof(RTPIXEL));

  //- Set up scene

  int texOrange = 0xFFBB00;
  int texPurple = 0xBB00FF;
  int texWhite = 0xFFFFFF;

  sphere( 1.0, -1.0, 6.0-6.0, 1.0, texOrange);
  sphere( 1.0,  3.0, 6.0-6.0, 1.5, texPurple);
  sphere(-1.0,  2.0, 5.0-6.0, 1.2, texOrange);

  sphere(-3.0,  1.0, 6.0-6.0, 1.5, texWhite);
  sphere(-3.0, -1.0, 6.0-6.0, 1.5, texPurple);

  sphere( 4.0, 3.5, 4.0-6.0, 0.7, texWhite);

  sphere( 3.0, -1.5,  2.0-6.0, 0.7, texWhite);
  sphere( 4.0, -1.5,  5.0-6.0, 0.7, texPurple);
  sphere( 5.0, -1.5,  8.0-6.0, 0.7, texWhite);
  sphere( 6.0, -1.5, 11.0-6.0, 0.7, texPurple);
  sphere( 7.0, -1.5, 14.0-6.0, 0.7, texWhite);
  sphere( 8.0, -1.5, 17.0-6.0, 0.7, texPurple);

  sphere( 0, -1001.5, 0, 1000, 0x666666);
}



//---- Redraw ----------------------------------------------------------------//

void Redraw(HDC dc)
{
  int i, j;

  //- Position spheres

  float tx, ty, tz, s, c;

  for(i=0; i<numspheres; i++)
  {
    s = sin(3.14159*ry);
    c = cos(3.14159*ry);

    tx = spheres[i].ax * c + spheres[i].az * s;
    tz = spheres[i].az * c - spheres[i].ax * s;
    ty = spheres[i].ay;

    s = sin(3.14159*rx);
    c = cos(3.14159*rx);

    spheres[i].x = tx;
    spheres[i].y = ty * c + tz * s;
    spheres[i].z = tz * c - ty * s;
  }

  //- Do coarse intersections (8x8)

  raystack[0].ox = 0;
  raystack[0].oy = 0;
  raystack[0].oz = -6.0;
  raystack[0].dz = 1;

  float y1 = 1.0;
  float dy = -16.0/height;

  float x1 = -1.0*width/height;
  float dx = 16.0/height;

  int o = 0;

  for(j=0; j<smheight; j ++)
  {
    for(i=0; i<smwidth; i ++)
    {
      raystack[0].dx = x1 + i*dx;
      raystack[0].dy = y1 + j*dy;

      //  Intersect sphere

      rt_intersect(raystack, &rtpixel[o]);
      o++;
    }
  }

  //- Render

  int x, y;
  int col, col00, col01, col10, col11;
  RTSPHERE *obj, *obj00, *obj01, *obj10, *obj11;

  bool shad00, shad01, shad10, shad11;

  RTPIXEL pix00, pix01, pix10, pix11;

  o = 0;

  y1 = 1.0;
  dy = -2.0/height;

  x1 = -1.0*width/height;
  dx = 2.0/height;

  for(j=0; j<height; j+=8)
  {
    for(i=0; i<width; i+=8)
    {
      //  Check if we need to subdivide

      obj01 = rtpixel[o+1].obj;
      obj10 = rtpixel[o+smwidth].obj;
      obj11 = rtpixel[o+smwidth+1].obj;

      shad01 = rtpixel[o+1].obj;
      shad10 = rtpixel[o+smwidth].obj;
      shad11 = rtpixel[o+smwidth+1].obj;

      bool subdiv = false;

      if(!(obj00 == obj01 && obj00 == obj10 && obj00 == obj11)) subdiv = true;
      if(!(shad00 == shad01 && shad00 == shad10 && shad00 == shad11)) subdiv = true;

      if(!subdiv)
      {
        rect(i, j, 8, 8, rtpixel[o].col);
      }
      else
      {
        raystack[0].dx = x1 + (i+4)*dx;
        raystack[0].dy = y1 + j*dy;
        rt_intersect(raystack, &pix01);

        raystack[0].dx = x1 + i*dx;
        raystack[0].dy = y1 + (j+4)*dy;
        rt_intersect(raystack, &pix10);

        raystack[0].dx = x1 + (i+4)*dx;
        raystack[0].dy = y1 + (j+4)*dy;
        rt_intersect(raystack, &pix11);

        rect(i, j, 4, 4, rtpixel[o].col);
        rect(i+4, j, 4, 4, pix01.col);
        rect(i, j+4, 4, 4, pix10.col);
        rect(i+4, j+4, 4, 4, pix11.col);
      }

      o++;
    }
    o++;
  }

  //- Display

  static int frame = 0;
  frame += 0x822711;

  for(j=0; j<8; j++) for(i=0; i<8; i++)
  {
    buf[(j*width+i)*3 + 0] = i+j+frame;
  }

  Display(dc, buf, width, height);
}



//---- WinProc ---------------------------------------------------------------//

LRESULT CALLBACK WinProc(HWND hwnd, UINT uiMsg, UINT wParam, LONG lParam)
{
  HDC dc;
  PAINTSTRUCT ps;

  switch(uiMsg) {

  case WM_CREATE:
    Startup();
    SetTimer(hwnd, 1, 10, NULL);
    break;

  case WM_TIMER:
    dc = GetDC(hwnd);
    Redraw(dc);
    ReleaseDC(hwnd, dc);
    return 0L;

  case WM_PAINT:
    dc = BeginPaint(hwnd, &ps);
    Redraw(dc);
    EndPaint(hwnd, &ps);
    return 0L;

  case WM_LBUTTONDOWN:
    mouse_xstart = HIWORD(lParam);
    mouse_ystart = LOWORD(lParam);
    mouse_drag = true;
    SetCapture(hwnd);
    return 0L;

  case WM_MOUSEMOVE:
    mouse_x = HIWORD(lParam);
    mouse_y = LOWORD(lParam);

    if(mouse_drag)
    {
      ry -= (mouse_y-mouse_ystart) * 0.004;
      rx += (mouse_x-mouse_xstart) * 0.004;
      mouse_xstart = mouse_x;
      mouse_ystart = mouse_y;
    }
    return 0L;

  case WM_LBUTTONUP:
    mouse_drag = false;
    ReleaseCapture();
    return 0L;

/*
  case WM_NCHITTEST:
    return HTCAPTION;
*/

  case WM_DESTROY:
    KillTimer(hwnd, 1);
    PostQuitMessage(0);
    return 0L;

  }

  return DefWindowProc(hwnd, uiMsg, wParam, lParam);
}




//---- WinMain ---------------------------------------------------------------//

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
  WNDCLASS wndclass;
  char cSwitch;
  MSG msg;
  RECT rc;

  hMainInstance = hInstance;

  if(hPrevInstance) return 1;

  //- Create window class

  wndclass.style         = CS_HREDRAW | CS_VREDRAW;
  wndclass.lpfnWndProc   = WinProc;
  wndclass.cbClsExtra    = 0;
  wndclass.cbWndExtra    = 0;
  wndclass.hInstance     = hInstance;
  wndclass.hIcon         = NULL;
  wndclass.hCursor       = LoadCursor(NULL, IDC_ARROW);
  wndclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
  wndclass.lpszMenuName  = NULL;
  wndclass.lpszClassName = szAppName;

  if(!RegisterClass(&wndclass)) return 1;

  //- Create main window

  GetWindowRect(GetDesktopWindow(), &rc);

  hMainWindow = CreateWindowEx(
    0,
    szAppName,
    szAppName,
    WS_POPUP,
    (rc.right-width)/2,
    (rc.bottom-height)/2,
    width,
    height,
    NULL,
    NULL,
    hInstance,
    NULL
  );

  if(!hMainWindow) return 1;

  ShowWindow(hMainWindow, SW_SHOW);

  //- Message loop

  while(TRUE) {
    while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
      if (msg.message == WM_QUIT) return TRUE;
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
    HDC dc = GetDC(hMainWindow);
    Redraw(dc);
    ReleaseDC(hMainWindow, dc);
  }

  return 0;
}



//---- END OF FILE -----------------------------------------------------------//




