/// LSU EE 7700-1 (Sp 2009), Graphics Processors
//
 /// CPU-Only Demo 1: One Triangle

 // $Id:$

 /// Purpose
//
//   Routine render_one_triangle demonstrates the most basic 3D
//   techniques: how to project a triangle specified in object space
//   onto a frame buffer.

 /// To compile and run:
//
//     make
//     demo-1-one-triangle

 /// More Information
//
//   File coord.h on coordinate and matrix objects and operations.


#include <stdio.h>
#include <strings.h>
#include <stdlib.h>

#include "frame_buffer.h"
#include "coord.h"

inline float
slope(pCoor& a, pCoor& b)
{
  return ( b.x - a.x ) / fabs( b.y - a.y );
}

void
render_one_triangle(pFrame_Buffer &frame_buffer)
{
  // This routine will be called automatically each time the frame
  // buffer needs to be painted.

  ///
  /// Determined by Hardware
  ///

  const int win_width = frame_buffer.get_width();
  // const int win_height = frame_buffer.get_height(); // Uncomment if needed.

  // Address of start of frame buffer.
  //
  int32_t* const f_buffer = frame_buffer.get_buffer();

  ///
  /// Determined by Application
  ///

  // Coordinates of triangle to render, in object space.
  //
  // ( Just one triangle. )
  //
  pCoor c0(10,9,-3);
  pCoor c1(5,10,-3);
  pCoor c2(6,17,-6);

  ///
  /// Determined by Application
  ///

  // Location of viewer, transformation to eye coordinates.
  //
  // Transform object coordinates to eye space: viewer is at (0,0,0)
  // looking in -z direction with +y straight up and +x pointing to
  // right.

  pMatrix_Translate center_eye(-6,-10,0);


  // Perspective projection, transformation to clip coordinates.
  //
  // Transform eye coordinates to clip coordinates: each visible
  // coordinate in [-1,1].

  pMatrix_Frustum frustum(12,20,1,20);

  // Transform to window coordinates.
  //
  // Transform device coordinates (homogenized clip coordinates) to
  // window coordinates: x in [0,win_width) and y in [0,win_height).

  pMatrix_Translate center_window(1,1,0);
  pMatrix_Scale scale(win_width/2);

  pMatrix transform = scale * center_window * frustum * center_eye;

  //
  // Apply transformation to transform triangle coordinates from
  // object space to clip space.
  //
  pCoor c0w = transform * c0;  c0w.homogenize();
  pCoor c1w = transform * c1;  c1w.homogenize();
  pCoor c2w = transform * c2;  c2w.homogenize();


  // For convenience and performance, convert y coordinates to integers.
  //
  const int c0y = (int)c0w.y;
  const int c1y = (int)c1w.y;
  const int c2y = (int)c2w.y;

  // Enforce y ordering: c0w.y < c1w.y < c2w.y
  //
  if ( c0y >= c1y || c1y >= c2y )
    {fprintf(stderr,"Ordering violation."); exit(1);}

  ///
  /// Rasterization
  ///

  float x_02 = c0w.x;   // Call this the left (side of triangle) position.
  float x_012 = c0w.x;  // Call this the right position.
  float dx_02 = slope(c0w,c2w);
  float dx_012 = slope(c0w,c1w);
  // Note: if direction -1 then what is called "left" is really right, etc.
  const int direction = dx_02 < dx_012 ? 1 : -1;

  // Position of first row in frame buffer to write.
  //
  int fb_line_idx = c0y * win_width;

  // Outer loop proceeds from bottom to top.
  //
  for ( int y = c0y;  y < c2y;  y++ )
    {
      // Inner loop proceeds from "left" to "right"
      //
      for ( int x = (int)x_02; x != (int)x_012; x += direction )
        {
          // Write a green pixel.
          f_buffer[fb_line_idx + x] = 0xff00;
        }

      // Change slope if point c1 reached.
      //
      if ( y == c1y ) dx_012 = slope(c1w,c2w);

      // Move down by one pixel.
      //
      fb_line_idx += win_width;  // Advance frame buffer pointer to next row.
      x_02 += dx_02;             // Advance "left" x position.
      x_012 += dx_012;           // Advance "right" x position.
    }
}

int
main(int argc, char **argv)
{
  // Instantiate frame buffer simulation object.
  //
  pFrame_Buffer frame_buffer(argc,argv);

  // Tell frame buffer simulator to call render_one_triangle.
  //
  frame_buffer.show(render_one_triangle);

  return 0;
}