

#include <windows.h>
#include <mmsystem.h>
#include <math.h>
#include "rt.h"



//---- DATA ------------------------------------------------------------------//

//---- Render buffer

int *buf;
int w, h;

float bx1, by1, bx2, by2;
float dx, dy, dx8, dy8;



//---- Scene

RT_SPHERE *sphere;
int numspheres = 0;

float light_x = 1.0, light_y = 5.0, light_z = 2.0;



//---- Render data

int sw, sh;

RT_PIXEL *spixel1, *spixel2;
RT_PIXEL *subdiv;

RT_SPHERE *shadow_list = NULL;



//---- Debug data

int shadow_calc_count = 0;
int shadow_calc_pixels = 0;



//---- CODE ------------------------------------------------------------------//

//---- rt_startup

void rt_startup(int *buffer, int width, int height)
{
  //- Render buffer

  buf = buffer;
  w = width;
  h = height;

  bx1 = -1.0*w/h;
  bx2 =  1.0*w/h;
  dx = 2.0/h;
  dx8 = dx * 8.0;

  by1 =  1.0;
  by2 = -1.0;
  dy = -2.0/h;
  dy8 = dy * 8.0;

  //- Allocate scene

  sphere = new RT_SPHERE[64];

  //- Allocate render data

  sw = ((w & -8) >> 3) + 1;
  sh = ((h & -8) >> 3) + 1;

  spixel1 = new RT_PIXEL[sw*4];
  spixel2 = new RT_PIXEL[sw*4];

  subdiv = new RT_PIXEL[64];
}



//---- rt_shutdown

void rt_shutdown()
{
  delete[] sphere;

  delete[] spixel1;
  delete[] spixel2;
  delete[] subdiv;
}



//---- rt_addSphere

void rt_addSphere(float x, float y, float z, float rad, int col)
{
  sphere[numspheres].ax = x;
  sphere[numspheres].ay = y;
  sphere[numspheres].az = z;
  
  sphere[numspheres].x = x;
  sphere[numspheres].y = y;
  sphere[numspheres].z = z;

  sphere[numspheres].rad = rad;
  sphere[numspheres].col = col;

  sphere[numspheres].shadow_list = shadow_list;
  shadow_list = &sphere[numspheres];

  numspheres ++;
}




//------------------------------------------------------



//---- rt_intersect_sphere

float rt_intersect_sphere(RT_RAY *ray, RT_SPHERE *sphere, RT_PIXEL *pix)
{
  float x = sphere->x;
  float y = sphere->y;
  float z = sphere->z;
  float r = sphere->rad;

  sphere->a = ray->dx*ray->dx + ray->dy*ray->dy + ray->dz*ray->dz;
  sphere->b = 2*(ray->dx*(ray->ox-x) + ray->dy*(ray->oy-y) + ray->dz*(ray->oz-z));
  sphere->c = (ray->ox-x)*(ray->ox-x) + (ray->oy-y)*(ray->oy-y) + (ray->oz-z)*(ray->oz-z) - r*r;
  sphere->d = sphere->b*sphere->b - 4*sphere->a*sphere->c;

  if(sphere->d > 0)
  {
    float t1, t2;

    sphere->d = sqrt(sphere->d);

    t2 = (-sphere->b + sphere->d) / (2*sphere->a);
    t1 = (-sphere->b - sphere->d) / (2*sphere->a);

    if(t1 <= 0)
    {
      sphere->d = t2;
    }
    else if(t2 <= 0)
    {
      sphere->d = t1;
    }
    else
    {
      sphere->d = t1 < t2 ? t1 : t2;
    }

    return sphere->d;
  }

  return -1;
}



//---- rt_shadow_sphere

bool rt_shadow_sphere(RT_RAY *ray, RT_SPHERE *sphere)
{
  float x = sphere->x;
  float y = sphere->y;
  float z = sphere->z;
  float r = sphere->rad;

  sphere->a = ray->dx*ray->dx + ray->dy*ray->dy + ray->dz*ray->dz;
  sphere->b = 2*(ray->dx*(ray->ox-x) + ray->dy*(ray->oy-y) + ray->dz*(ray->oz-z));
  sphere->c = (ray->ox-x)*(ray->ox-x) + (ray->oy-y)*(ray->oy-y) + (ray->oz-z)*(ray->oz-z) - r*r;
  sphere->d = sphere->b*sphere->b - 4*sphere->a*sphere->c;

  if(sphere->d > 0)
  {
  	if(sphere->b > 0) sphere->b *= sphere->b;
	               else sphere->b *= -sphere->b;

    return sphere->d > sphere->b;
  }

  return false;
}



//---- rt_pix_sphere

void rt_pix_sphere(RT_RAY *ray, RT_SPHERE *sphere, RT_PIXEL *pix)
{
  float x = sphere->x;
  float y = sphere->y;
  float z = sphere->z;
  float r = sphere->rad;

  if(sphere->d > 0)
  {
    pix->t = sphere->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;

    float 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;
  }
}



//---- rt_shadow

bool rt_shadow(RT_PIXEL *pix)
{
  //  Calculate shadow ray

  RT_RAY 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;

  //  Check object normal

  float d = pix->nx * shadow_ray.dx + pix->ny * shadow_ray.dy + pix->nz * shadow_ray.dz;

  if(d < 0) return true;

  //  Test against objects

  RT_PIXEL pix2;

  int i;

  RT_SPHERE *ls = NULL, *s = shadow_list;

  shadow_calc_pixels ++;

  for(i=0; i<numspheres; i++)
  {
    if(s != pix->sphere)
    {
      shadow_calc_count ++;

  	  if( rt_shadow_sphere(&shadow_ray, s) )
      {
	      if(ls)
        {
          ls->shadow_list = s->shadow_list;
	        s->shadow_list = shadow_list;
	        shadow_list = s;
        }
  	    return true;
      }
    }

  	ls = s;
	  s = s->shadow_list;
  }

  return false;
}



//--------------------------------------------------------------------------




//---- rt_intersect

void rt_intersect(RT_RAY *ray, RT_PIXEL *pix)
{
  //- Find which sphere this ray intersects

  int i, foundi = -1;
  float dd, d = 9999999;

  for(i=0; i<numspheres; i++)
  {
  	dd = rt_intersect_sphere(ray, &sphere[i], pix);
    if(dd > 0.0001 && dd < d) { d = dd; foundi = i; }
  }

  //- If it doesn't intersect any of them then return blank pixel

  if(foundi < 0)
  {
    pix->sphere = NULL;
    pix->col = 0;
    pix->shadow = false;
  }

  //- If it does then get more info

  else
  {
    rt_pix_sphere(ray, &sphere[foundi], pix);

    pix->sphere = &sphere[foundi];
    pix->shadow = rt_shadow(pix);
    pix->col = pix->sphere->col;

    if(pix->shadow)
    {
      pix->col = ((pix->col >> 1) & 0x7F7F7F7F);
    }
  }

}



//---- rt_comparePixels

bool rt_comparePixels(RT_PIXEL *pix1, RT_PIXEL *pix2)
{
  if(pix1->sphere != pix2->sphere) return false;
  if(pix1->shadow != pix2->shadow) return false;

  return true;
}



//---- rt_spix

void rt_spix(RT_PIXEL *spix, int x, int y)
{
  RT_RAY ray;

  ray.ox = 0;
  ray.oy = 0;
  ray.oz = 0;

  ray.dx = bx1 + x*dx;
  ray.dy = by1 + y*dy;
  ray.dz = 1;

  rt_intersect(&ray, spix);
}



//---- rt_spixLine

void rt_spixLine(RT_PIXEL *spix, int j)
{
  RT_RAY ray;

  ray.ox = 0;
  ray.oy = 0;
  ray.oz = 0;

  ray.dy = by1 + j*dy8;
  ray.dz = 1;

  //- Coarse line

  int i;

  for(i=0; i<sw; i++)
  {
    ray.dx = bx1 + i*dx8;
  	rt_intersect(&ray, &spix[i*2]);

    if(i > 0)
    {
      if( rt_comparePixels(&spix[i*2-2], &spix[i*2]) )
      {
        spix[i*2-1].sphere = spix[i*2].sphere;
        spix[i*2-1].shadow = spix[i*2].shadow;
        spix[i*2-1].col = spix[i*2].col;
      }
      else
      {
        ray.dx -= 4.0*dx;
  	    rt_intersect(&ray, &spix[i*2-1]);
      }
    }
  }

}



//---- rt_spixTween

void rt_spixTween(RT_PIXEL *spix3, RT_PIXEL *spix1, RT_PIXEL *spix2, int j)
{
  RT_RAY ray;

  ray.ox = 0;
  ray.oy = 0;
  ray.oz = 0;

  ray.dy = by1 + (j+0.5f)*dy8;
  ray.dz = 1;

  //- Coarse line (4-pixel)

  int i;

  for(i=0; i<sw*2; i++)
  {
    if( rt_comparePixels(&spix1[i], &spix2[i]) )
    {
      spix3[i].sphere = spix1[i].sphere;
      spix3[i].shadow = spix1[i].shadow;
      spix3[i].col = spix1[i].col;
    }
    else
    {
      ray.dx = bx1 + (i*0.5f)*dx8;
    	rt_intersect(&ray, &spix3[i]);
    }

  }

}



//---- rt_render

void rt_render(float rx, float ry)
{
  int i, j, k, kk, x, y, u, v;

  //- Transform spheres

  float tx, ty, tz, s, c;

  for(i=0; i<numspheres; i++)
  {
    s = sin(3.14159*ry);
    c = cos(3.14159*ry);

    tx = sphere[i].ax * c + sphere[i].az * s;
    tz = sphere[i].az * c - sphere[i].ax * s;
    ty = sphere[i].ay;

    s = sin(3.14159*rx);
    c = cos(3.14159*rx);

    sphere[i].x = tx;
    sphere[i].y = ty * c + tz * s;
    sphere[i].z = 6 + tz * c - ty * s;
  }

  //- Reset debug data

  shadow_calc_count = 0;
  shadow_calc_pixels = 0;

  //- Render

  int bigoffset = 0;
  int offset = 0;

  RT_PIXEL *spix1 = spixel1;
  RT_PIXEL *spix2 = spixel2;
  RT_PIXEL *tpix;

  int col;
  bool solid;

  //  Render first line (coarse)

  rt_spixLine(spix1, 0);

  for(j=0; j<sh-1; j++)
  {
    //  Render next line (coarse)
    
    rt_spixLine(spix2, j+1);
    rt_spixTween(&spix1[sw*2], spix1, spix2, j);

    //  TEMP: Use spix1 and spix2 to render line (coarse)

    for(k=0, kk=0; k<sw*4; k+=sw*2, kk++)
    {
      for(i=0; i<sw*2-2; i++)
	    {
	      offset = bigoffset + i*4;

	      //  If all corners the same then render rectangle

        if(kk == 0)
        {
          solid = rt_comparePixels(&spix1[i], &spix1[i+1]) &&
                  rt_comparePixels(&spix1[i], &spix1[i+k]) &&
                  rt_comparePixels(&spix1[i], &spix1[i+k+1]);
        }
        else
        {
          solid = rt_comparePixels(&spix1[i+k], &spix1[i+k+1]) &&
                  rt_comparePixels(&spix1[i+k], &spix2[i]) &&
                  rt_comparePixels(&spix1[i+k], &spix2[i+1]);
        }

	      if(solid)
	      {
          col = spix1[i+k].col;

          for(v=0; v<4; v++)
          {
		        for(u=0; u<4; u++) buf[ offset++ ] = col;
		        offset += w-4;
          }
	      }

	      //  Otherwise subdivide

	      else
	      {
          for(v=0; v<4; v++)
          {
		        for(u=0; u<4; u++)
            {
    			    rt_spix(subdiv, i*4+u, j*8+kk*4+v);
		    	    buf[ offset++ ] = subdiv->col;
            }
		        offset += w-4;
          }
        }

	    }

      bigoffset += w*4;
    }

	  //  Move on to next line

    tpix = spix1;
    spix1 = spix2;
	  spix2 = tpix;
  }

  return;
}


