/*****************************************************************************
*
*                                    render.c
*
*   from DKBTrace (c) 1990  David Buck
*
*  This module implements the main raytracing loop.
*
* This software is freely distributable. The source and/or object code may be
* copied or uploaded to communications services so long as this notice remains
* at the top of each file.  If any changes are made to the program, you must
* clearly indicate in the documentation and in the programs startup message
* who it was who made the changes. The documentation should also describe what
* those changes were. This software may not be included in whole or in
* part into any commercial package without the express written consent of the
* author.  It may, however, be included in other public domain or freely
* distributed software so long as the proper credit for the software is given.
*
* This software is provided as is without any guarantees or warranty. Although
* the author has attempted to find and correct any bugs in the software, he
* is not responsible for any damage caused by the use of the software.  The
* author is under no obligation to provide service, corrections, or upgrades
* to this package.
*
* Despite all the legal stuff above, if you do find bugs, I would like to hear
* about them.  Also, if you have any comments or questions, you may contact me
* at the following address:
*
*     David Buck
*     22C Sonnet Cres.
*     Nepean Ontario
*     Canada, K2H 8W7
*
*  I can also be reached on the following bulleton boards:
*
*     ATX              (613) 526-4141
*     OMX              (613) 731-3419
*     Mystic           (613) 731-0088 or (613) 731-6698
*
*  Fidonet:   1:163/109.9
*  Internet:  David_Buck@Carleton.CA
*
*  IBM Port by Aaron A. Collins. Aaron may be reached on the following BBS'es:
*
*     Lattice BBS                      (708) 916-1200
*     The Information Exchange BBS     (708) 945-5575
*     Stillwaters BBS                  (708) 403-2826
*
*****************************************************************************/


#include "frame.h"
#include "vector.h"
#include "dkbproto.h"

extern FILE *bfp;
extern char Output_File_Name[30];
extern int File_Buffer_Size;
extern unsigned long Options;
extern int Quality;
volatile int Stop_Flag;
extern int First_Line, Last_Line;
extern long Number_Of_Pixels, Number_Of_Rays, Number_Of_Pixels_Supersampled;

extern short *hashTable;
extern unsigned short crctab[256];
#define rand3d(a,b) crctab[(int)(hashTable[(int)(hashTable[(int)((a)&0xfff)]^(b))&0xfff])&0xff]

FRAME Frame;
RAY *VP_Ray;
int Trace_Level;

#define MAX_TRACE_LEVEL 5

void Create_Ray (ray, width, height, x, y)
   RAY *ray;
   int width, height;
   DBL x, y;
   {
   register DBL X_Scalar, Y_Scalar;
   VECTOR Temp_Vect_1, Temp_Vect_2;

   /* Convert the X Coordinate to be a DBL from 0.0 to 1.0 */
   X_Scalar = (x - (DBL) width / 2.0) / (DBL) width;

   /* Convert the Y Coordinate to be a DBL from 0.0 to 1.0 */
   Y_Scalar = (( (DBL)(Frame.Screen_Height - 1) - y) -
	      (DBL) height / 2.0) / (DBL) height;

   VScale (Temp_Vect_1, Frame.View_Point.Up, Y_Scalar);
   VScale (Temp_Vect_2, Frame.View_Point.Right, X_Scalar);
   VAdd (ray->Direction, Temp_Vect_1, Temp_Vect_2);
   VAdd (ray->Direction, ray->Direction, Frame.View_Point.Direction);
   VNormalize (ray->Direction, ray->Direction);
   Initialize_Ray_Containers (ray);
   ray->Quadric_Constants_Cached = FALSE;
   }

void Write_Line (Line, y)
   COLOUR *Line;
   int y;
   {
   char Red, Green, Blue, temp[2];
   register int x;

   if (!(Options & TARGA))	/* Write out line in DKB/QRT RAW format */
      {
      temp[0] = y % 256;
      putc (temp[0], bfp);
      temp[0] = y / 256;
      putc (temp[0], bfp);

      for (x = 0 ; x < Frame.Screen_Width ; x++) {
         Red = (char) floor (Line[x].Red * 255.0);
         putc (Red, bfp);
         }

      for (x = 0 ; x < Frame.Screen_Width ; x++) {
         Green = (char) floor (Line[x].Green * 255.0);
         putc (Green, bfp);
         }

      for (x = 0 ; x < Frame.Screen_Width ; x++) {
         Blue = (char) floor (Line[x].Blue * 255.0);
         putc (Blue, bfp);
         }
      }
   else       /* write out TrueVision Targa 24 format file */
      {
      for (x = 0; x < Frame.Screen_Width; x++) {
         putc((char) floor (Line[x].Blue * 255.0), bfp);
         putc((char) floor (Line[x].Green * 255.0), bfp);
         putc((char) floor (Line[x].Red * 255.0), bfp);
         }
      }

   if (File_Buffer_Size == 0) {
      fflush(bfp);				       /* close and reopen file for */
      bfp = freopen(Output_File_Name, "ab", bfp); /* integrity in case we crash*/
      }
   }

void Supersample (result, x, y, Width, Height)
   COLOUR *result;
   int x, y, Width, Height;
   {
   COLOUR colour;
   register DBL dx, dy, Jitter_X, Jitter_Y;

   dx = (DBL) x;
   dy = (DBL) y;

   Number_Of_Pixels_Supersampled++;

   Make_Colour (result, 0.0, 0.0, 0.0);

   Jitter_X = (rand3d(x, y) & 0x7FFF) / 32768.0 * 0.33333333 - 0.16666666;
   Jitter_Y = (rand3d(x, y) & 0x7FFF) / 32768.0 * 0.33333333 - 0.16666666;
   Create_Ray (VP_Ray, Frame.Screen_Width, Frame.Screen_Height,
	         dx + Jitter_X, dy + Jitter_Y);

   Trace_Level = 0;
   Trace (VP_Ray, &colour);
   Clip_Colour (&colour, &colour);
   Scale_Colour (&colour, &colour, 0.11111111);
   Add_Colour (result, result, &colour);

   Jitter_X = (rand3d(x, y) & 0x7FFF) / 32768.0 * 0.33333333 - 0.16666666;
   Jitter_Y = (rand3d(x, y) & 0x7FFF) / 32768.0 * 0.33333333 - 0.16666666;
   Create_Ray (VP_Ray, Width, Height, dx + Jitter_X - 0.3333333,
                                      dy + Jitter_Y - 0.3333333);
   Trace_Level = 0;
   Trace (VP_Ray, &colour);
   Clip_Colour (&colour, &colour);
   Scale_Colour (&colour, &colour, 0.11111111);
   Add_Colour (result, result, &colour);

   Jitter_X = (rand3d(x, y) & 0x7FFF) / 32768.0 * 0.33333333 - 0.16666666;
   Jitter_Y = (rand3d(x, y) & 0x7FFF) / 32768.0 * 0.33333333 - 0.16666666;
   Create_Ray (VP_Ray, Width, Height, dx + Jitter_X - 0.3333333,
                                      dy + Jitter_Y);
   Trace_Level = 0;
   Trace (VP_Ray, &colour);
   Clip_Colour (&colour, &colour);
   Scale_Colour (&colour, &colour, 0.11111111);
   Add_Colour (result, result, &colour);

   Jitter_X = (rand3d(x, y) & 0x7FFF) / 32768.0 * 0.33333333 - 0.16666666;
   Jitter_Y = (rand3d(x, y) & 0x7FFF) / 32768.0 * 0.33333333 - 0.16666666;
   Create_Ray (VP_Ray, Width, Height, dx + Jitter_X - 0.3333333,
                                      dy + Jitter_Y + 0.3333333);
   Trace_Level = 0;
   Trace (VP_Ray, &colour);
   Clip_Colour (&colour, &colour);
   Scale_Colour (&colour, &colour, 0.11111111);
   Add_Colour (result, result, &colour);

   Jitter_X = (rand3d(x, y) & 0x7FFF) / 32768.0 * 0.33333333 - 0.16666666;
   Jitter_Y = (rand3d(x, y) & 0x7FFF) / 32768.0 * 0.33333333 - 0.16666666;
   Create_Ray (VP_Ray, Width, Height, dx + Jitter_X,
                                      dy + Jitter_Y - 0.3333333);
   Trace_Level = 0;
   Trace (VP_Ray, &colour);
   Clip_Colour (&colour, &colour);
   Scale_Colour (&colour, &colour, 0.11111111);
   Add_Colour (result, result, &colour);

   Jitter_X = (rand3d(x, y) & 0x7FFF) / 32768.0 * 0.33333333 - 0.16666666;
   Jitter_Y = (rand3d(x, y) & 0x7FFF) / 32768.0 * 0.33333333 - 0.16666666;
   Create_Ray (VP_Ray, Width, Height, dx + Jitter_X,
                                      dy + Jitter_Y + 0.3333333);
   Trace_Level = 0;
   Trace (VP_Ray, &colour);
   Clip_Colour (&colour, &colour);
   Scale_Colour (&colour, &colour, 0.11111111);
   Add_Colour (result, result, &colour);

   Jitter_X = (rand3d(x, y) & 0x7FFF) / 32768.0 * 0.33333333 - 0.16666666;
   Jitter_Y = (rand3d(x, y) & 0x7FFF) / 32768.0 * 0.33333333 - 0.16666666;
   Create_Ray (VP_Ray, Width, Height, dx + Jitter_X + 0.3333333,
                                      dy + Jitter_Y - 0.3333333);
   Trace_Level = 0;
   Trace (VP_Ray, &colour);
   Clip_Colour (&colour, &colour);
   Scale_Colour (&colour, &colour, 0.11111111);
   Add_Colour (result, result, &colour);

   Jitter_X = (rand3d(x, y) & 0x7FFF) / 32768.0 * 0.33333333 - 0.16666666;
   Jitter_Y = (rand3d(x, y) & 0x7FFF) / 32768.0 * 0.33333333 - 0.16666666;
   Create_Ray (VP_Ray, Width, Height, dx + Jitter_X + 0.3333333,
                                      dy + Jitter_Y);
   Trace_Level = 0;
   Trace (VP_Ray, &colour);
   Clip_Colour (&colour, &colour);
   Scale_Colour (&colour, &colour, 0.11111111);
   Add_Colour (result, result, &colour);

   Jitter_X = (rand3d(x, y) & 0x7FFF) / 32768.0 * 0.33333333 - 0.16666666;
   Jitter_Y = (rand3d(x, y) & 0x7FFF) / 32768.0 * 0.33333333 - 0.16666666;
   Create_Ray (VP_Ray, Width, Height, dx + Jitter_X + 0.3333333,
                                      dy + Jitter_Y + 0.3333333);
   Trace_Level = 0;
   Trace (VP_Ray, &colour);
   Clip_Colour (&colour, &colour);
   Scale_Colour (&colour, &colour, 0.11111111);
   Add_Colour (result, result, &colour);
   }

void Start_Tracing ()
   {
   COLOUR Colour;
   RAY Ray;
   register int x, y, i, SuperSampleCount;
   char Red, Green, Blue, Antialias_Center_Flag;
   COLOUR *Previous_Line, *Current_Line, *Temp_Colour_Ptr;
   char *Previous_Line_Antialiased_Flags, *Current_Line_Antialiased_Flags,
        *Temp_Char_Ptr;


   if (Options & VERBOSE) {
       printf("Tracing...\n");
       fflush (stdout);
       }

   VP_Ray = &Ray;

   Previous_Line = (COLOUR *)   malloc (sizeof (COLOUR)*(Frame.Screen_Width + 1));
   Current_Line = (COLOUR *) malloc (sizeof (COLOUR)*(Frame.Screen_Width + 1));

   for (i = 0 ; i <= Frame.Screen_Width ; i++) {
      Previous_Line[i].Red = 0.0;
      Previous_Line[i].Green = 0.0;
      Previous_Line[i].Blue = 0.0;

      Current_Line[i].Red = 0.0;
      Current_Line[i].Green = 0.0;
      Current_Line[i].Blue = 0.0;
      }

   if (Options & ANTIALIAS) {
      Previous_Line_Antialiased_Flags =
          (char *) malloc (sizeof (char)*(Frame.Screen_Width + 1));
      Current_Line_Antialiased_Flags =
          (char *)  malloc (sizeof (char)*(Frame.Screen_Width + 1));

      for (i = 0 ; i <= Frame.Screen_Width ; i++) {
         Previous_Line_Antialiased_Flags[i] = 0;
         Current_Line_Antialiased_Flags[i] = 0;
         }
      }

   Ray.Initial = Frame.View_Point.Location;

   for (y = (Options & ANTIALIAS)?First_Line-1:First_Line ; y<Last_Line ; y++) {

      TEST_ABORT

      if (Options & VERBOSE)
         printf ("Line %3d", y);

      if (Options & ANTIALIAS)
         SuperSampleCount = 0;

      for (x = 0 ; x < Frame.Screen_Width ; x++) {
         Number_Of_Pixels++;
         if (Stop_Flag) {
            close_all();
            exit(0);
            }

         Create_Ray (VP_Ray, Frame.Screen_Width, Frame.Screen_Height, (DBL) x, (DBL) y);
         Trace_Level = 0;
         Trace (&Ray, &Colour);
         Clip_Colour (&Colour, &Colour);

         Current_Line[x] = Colour;

         if (Options & ANTIALIAS) {
            Antialias_Center_Flag = 0;
            Current_Line_Antialiased_Flags[x] = 0;

            if (x != 0) {
               if (Colour_Distance (&Current_Line[x-1], &Current_Line[x])
                   >= Frame.Antialias_Threshold) {
                  Antialias_Center_Flag = 1;
                  if (!(Current_Line_Antialiased_Flags[x-1])) {
                     Supersample (&Current_Line[x-1], 
                                  x-1, y, Frame.Screen_Width, Frame.Screen_Height);
                     Current_Line_Antialiased_Flags[x-1] = 1;
                     SuperSampleCount++;
                     }
                  }
               }

            if (y != First_Line-1) {
               if (Colour_Distance (&Previous_Line[x], &Current_Line[x])
                    >= Frame.Antialias_Threshold) {
                  Antialias_Center_Flag = 1;
                  if (!(Previous_Line_Antialiased_Flags[x])) {
                     Supersample (&Previous_Line[x],
                                  x, y-1, Frame.Screen_Width, Frame.Screen_Height);
                     Previous_Line_Antialiased_Flags[x] = 1;
                     SuperSampleCount++;
                     }
                  }
               }

            if (Antialias_Center_Flag) {
               Supersample (&Current_Line[x],
                            x, y, Frame.Screen_Width, Frame.Screen_Height);
               Current_Line_Antialiased_Flags[x] = 1;
               Colour = Current_Line[x];
               SuperSampleCount++;
               }
            }

         if (y != First_Line-1) {
            Red = (char) (Colour.Red * 255.0);
            Green = (char) (Colour.Green * 255.0);
            Blue = (char) (Colour.Blue * 255.0);

            if (Options & DISPLAY)
               display_plot (x, y, Red, Green, Blue);
            }
         }

      if ((Options & DISKWRITE) || (Options & TARGA))
         if (y > First_Line) {
            Write_Line (Previous_Line, y-1);
         }

      if (Options & VERBOSE)
         if (Options & ANTIALIAS)
            printf (" supersampled %d times\n", SuperSampleCount);
         else
            putchar ('\n');

      Temp_Colour_Ptr = Previous_Line;
      Previous_Line = Current_Line;
      Current_Line = Temp_Colour_Ptr;

      Temp_Char_Ptr = Previous_Line_Antialiased_Flags;
      Previous_Line_Antialiased_Flags = Current_Line_Antialiased_Flags;
      Current_Line_Antialiased_Flags = Temp_Char_Ptr;
      }

   if ((Options & DISKWRITE) || (Options & TARGA)) {
      Write_Line (Previous_Line, Last_Line - 1);
      }
   }

void Trace (Ray, Colour)
   RAY *Ray;
   COLOUR *Colour;
   {
   COLOUR Temp_Colour, Surface_Colour;
   OBJECT *Object;
   VECTOR Surface_Normal;
   INTERSECTION *Local_Intersection, *New_Intersection;
   register int Intersection_Found;
   DBL Normal_Direction;

   Number_Of_Rays++;
   if (Frame.Fog_Distance == 0.0) {
      Make_Colour (&Temp_Colour, 0.0, 0.0, 0.0);
      }
   else
      Temp_Colour = Frame.Fog_Colour;

   Intersection_Found = FALSE;
   Local_Intersection = NULL;

   if (Trace_Level > MAX_TRACE_LEVEL)
      return;

    /* What objects does this ray intersect? */
   for (Object = Frame.Objects ; 
        Object != NULL ;
        Object = Object -> Next_Object) {

      if (New_Intersection = Intersection (Object, Ray)) {
         if (Intersection_Found) {
            if (Local_Intersection -> Depth > New_Intersection -> Depth) {
               free (Local_Intersection);
               Local_Intersection = New_Intersection;
               }
            else
               free (New_Intersection);
            }
         else
            Local_Intersection = New_Intersection;

         Intersection_Found = TRUE;
         }
      }

   if (Intersection_Found) {
      Make_Colour (&Temp_Colour, 0.0, 0.0, 0.0);
      Make_Colour (&Surface_Colour, 0.0, 0.0, 0.0);

      if (Quality > 5)
         Colour_At (&Surface_Colour, Local_Intersection->Object,
                    &Local_Intersection -> Point);
      else
         Surface_Colour = Local_Intersection->Object->Object_Colour;


      if (Quality <= 1)
         Temp_Colour = Surface_Colour;
      else {
         Normal (&Surface_Normal, (OBJECT *) Local_Intersection -> Shape,
                 &Local_Intersection -> Point);
         VDot (Normal_Direction, Surface_Normal, Ray->Direction);
         if (Normal_Direction > 0.0) {
            VScale (Surface_Normal, Surface_Normal, -1.0);
            }

         Ambient (Local_Intersection -> Object, &Local_Intersection -> Point,
                    &Surface_Colour, &Temp_Colour);
         Diffuse (Local_Intersection -> Object, &Local_Intersection -> Point, Ray,
                    &Surface_Normal, &Surface_Colour, &Temp_Colour);

         if (Quality > 7) {
            Transmit (Local_Intersection -> Object, &Local_Intersection -> Point, Ray,
                      &Surface_Normal, &Surface_Colour, &Temp_Colour);
            Reflect (Local_Intersection -> Object, &Local_Intersection -> Point, Ray,
                     &Surface_Normal, &Temp_Colour);
            Refract (Local_Intersection -> Object, &Local_Intersection -> Point, Ray,
                     &Surface_Normal, &Temp_Colour);

            if (Frame.Fog_Distance != 0.0)
               Fog (Local_Intersection->Depth, &Frame.Fog_Colour, Frame.Fog_Distance,
                    &Temp_Colour);
            }
         }
      free (Local_Intersection);
      }

   *Colour = Temp_Colour;
   }

