/* :ts=8 */	/* Yes some of us still use vi ! */
/************************************************************************/
/*                                                                      */
/* This file contains code which is called from tesselate.c to render 	*/
/* the current object.							*/
/*                                                                      */
/************************************************************************/

#include <math.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <stdlib.h>

	/* SIPP include files: */
#include "sipp.h"
#include "shaders.h"

#include "general.h"
#include "globals.h"
#include "intui.h"
#include "spl_gfx.h"
#include "tesselate.h"
#include "render.h"

static Strauss_desc	Strauss_Shader_Data;
static double 		Min_Value;
static double 		Max_Value;
static double		View_Distance;
static SObject		*SIPP_Object 		= NULL;

static int    		Current_Pen 		= -1;

static Byte_T 		*Error_Diffusion_Table 	= NULL;

static
void Error_Diffusion_Free()
/************************************************************************/
/*									*/
/* Free work space used by the Floyd-Steinberg algorithm.		*/
/*									*/
/************************************************************************/
{
    int i;
   
    if (Error_Diffusion_Table == NULL) return;

    free(Error_Diffusion_Table);
    Error_Diffusion_Table = NULL; 

} /* Error_Diffusion_Free */

static
void Error_Diffusion_Init()
/************************************************************************/
/*									*/
/* Allocate and initialize workspace used by the Floyd-Steinberg 	*/
/* algorithm.								*/
/*									*/
/************************************************************************/
{
    int i;

    Error_Diffusion_Free();
    
    Error_Diffusion_Table = (Byte_T *) 
			malloc(Screen_Width * sizeof(Byte_T)+1);
    if (Error_Diffusion_Table == NULL) {

	Display_Error_Message("Couldn't allocate memory");
	CloseStuff();
	exit(2);

    } /* if */

    for (i = 0; i < Screen_Width; i++) Error_Diffusion_Table[i] = 0;

} /* Error_Diffusion_Init */


static
short Error_Diffusion_Correct(int X, short Value)
/************************************************************************/
/*									*/
/* Compute and return actual pen id to be used to plot a color at column*/
/* X with the value 'Value' (0 - 255).					*/
/* We use a slightly modified Floyd-Steinberg error diffusion algoritm	*/
/* to do this.								*/
/*									*/
/* The modification is that the below-right term is ignored.		*/
/*									*/
/************************************************************************/
{
    short Pen, E, E_Right, E_BelowLeft, E_Below;

    Value  += Error_Diffusion_Table[X]; /* Correct for earlier error */
    Pen = (Value + 8) >> 4;
    E      = Value - (Pen << 4);	/* Error */
   
    E_Right     = ((7 * E) + 8) >> 4;
    E_BelowLeft = ((3 * E) + 8) >> 4;
    E_Below     =  E - E_Right - E_BelowLeft;

    Error_Diffusion_Table[X+1] += E_Right; 	/* right 	*/
    Error_Diffusion_Table[X-1] += E_BelowLeft;	/* Below left 	*/
    Error_Diffusion_Table[X]    = E_Below; 	/* Below 	*/

    return(Pen);

} /* Error_Diffusion_Correct */

static
void Set_Color_Table_Gray(Boolean_T On)
/************************************************************************/
/*									*/
/* If On is TRUE, then set the colortable to a grayscale, otherwise	*/
/* restore the colortable.						*/
/*									*/
/************************************************************************/
{
    int i;

    if (On) {

	for (i = 0; i < 16; i++)
 	    SetRGB4(Windows[Id_Active_Window].ViewPort,  i, i, i, i); 

    } else {

	for (i = 0; i < 16 && ScreenColors[i].ColorIndex >= 0; i++)
 	    SetRGB4(Windows[Id_Active_Window].ViewPort,  
					ScreenColors[i].ColorIndex,
					ScreenColors[i].Red,
					ScreenColors[i].Green,
					ScreenColors[i].Blue);
    }
} /* Set_Color_Table_Gray */

static
void Draw_Line(char *Data, 		/* Not used 	*/
	      int Col1, int Row1, 	/* Start pos	*/
	      int Col2, int Row2)	/* End pos	*/
/************************************************************************/
/*									*/
/* Draw a line from (Col1, Row1) to (Col2, Row2).			*/
/*									*/
/************************************************************************/
{
    short	X1, Y1, X2, Y2;

    if (Current_Pen < 0) {
  	Current_Pen = 1;
        SetAPen(Windows[Id_Active_Window].RastPort, Current_Pen);
    }

    X1 = Views[V_P].X_Min + (Col1 << 1);
    Y1 = Views[V_P].Y_Min + Row1;
    X2 = Views[V_P].X_Min + (Col2 << 1);
    Y2 = Views[V_P].Y_Min + Row2;

    Move(Windows[Id_Active_Window].RastPort, X1, Y1);
    Draw(Windows[Id_Active_Window].RastPort, X2, Y2);

} /* Draw_Line */

static
void Draw_Pixel(char *Data, 		/* Not used	*/
		int Col, int Row, 	/* Position	*/
		unsigned char R,	/* Red		*/
		unsigned char G,	/* Green	*/
		unsigned char B)	/* Blue		*/
/************************************************************************/
/*									*/
/* Draw a pixel on position (Col, Row) with the color given by R, G, B.	*/
/*									*/
/* Simplifications:							*/
/*									*/
/* We know that we're working with a monochrome picture, so we only 	*/
/* look at R (red).							*/
/*									*/
/* We disregard aspect ratio, but plot 2 horizontal pixels each time	*/
/* (corresponding to an aspect ration of 0.91 in PAL).			*/
/*									*/
/* Actual pen is chosen based on a slightly modified Floyd - Steinberg	*/
/* error diffusion algoritm.						*/
/*									*/
/************************************************************************/
{
    short	X, Y;
    int		Pen;
    static int	Old_Row = -1;

    if (Old_Row != Row) {
        sprintf(Error_Msg, "Rendering row %d", Row);
    	Display_Status(Error_Msg);
	Old_Row = Row;
    }

    if (R == 0) {

		/* Trick to speed up for black color:			*/
		/* Ignore error correction for this pixel, and	return	*/
		/* immediately, as we know that all pixels are black by	*/
		/* default.						*/

        Error_Diffusion_Table[Col]    = 0; 
        return;		/* Not entirely correct, but faster */

    } else Pen = Error_Diffusion_Correct(Col, R);

		/* We have already set full screen to black, so there's	*/
		/* no need to actually plot the black pixels.		*/
    if (Pen == 0) return;	

    if (Pen != Current_Pen) {
        SetAPen(Windows[Id_Active_Window].RastPort, Pen);
        Current_Pen = Pen;
    }

	/* Plot 2 pixels for each column.				*/
    X = Views[V_P].X_Min + (Col << 1);
    Y = Views[V_P].Y_Min + Row;

    WritePixel(Windows[Id_Active_Window].RastPort, X,   Y);
    WritePixel(Windows[Id_Active_Window].RastPort, X+1, Y);


} /* Draw_Pixel */

static
void Create_SIPP_Polygon(Vector_T Point0, Vector_T Point1, 
		   	 Vector_T Point2, Vector_T Point3)
/************************************************************************/
/*									*/
/* Add the square given by the four points to polygon stack.		*/
/*									*/
/************************************************************************/
{
    Boolean_T 	Face1_Ok, Face2_Ok;
    int		i;

    Face1_Ok = Face_Is_Ok(Point0, Point1, Point2);
    Face2_Ok = Face_Is_Ok(Point1, Point2, Point3);

    if (Face1_Ok) {

	vertex_push(Point0[0], Point0[1], Point0[2]);
	vertex_push(Point1[0], Point1[1], Point1[2]);
	vertex_push(Point2[0], Point2[1], Point2[2]);
        polygon_push();

	for (i = 0; i < 3; i++) { 
	    if (Point0[i] < Min_Value) Min_Value = Point0[i];
	    if (Point0[i] > Max_Value) Max_Value = Point0[i];
	    if (Point1[i] < Min_Value) Min_Value = Point1[i];
	    if (Point1[i] > Max_Value) Max_Value = Point1[i];
	    if (Point2[i] < Min_Value) Min_Value = Point2[i];
	    if (Point2[i] > Max_Value) Max_Value = Point2[i];
  	} /* for */

    } /* if */

    if (Face2_Ok) {

	vertex_push(Point2[0], Point2[1], Point2[2]);
	vertex_push(Point1[0], Point1[1], Point1[2]);
	vertex_push(Point3[0], Point3[1], Point3[2]);
        polygon_push();

	for (i = 0; i < 3; i++) { 
	    if (Point1[i] < Min_Value) Min_Value = Point1[i];
	    if (Point1[i] > Max_Value) Max_Value = Point1[i];
	    if (Point2[i] < Min_Value) Min_Value = Point2[i];
	    if (Point2[i] > Max_Value) Max_Value = Point2[i];
	    if (Point3[i] < Min_Value) Min_Value = Point3[i];
	    if (Point3[i] > Max_Value) Max_Value = Point3[i];
  	} /* for */

    } /* if */

} /* Create_SIPP_Polygon */


Boolean_T Render_Setup()
/************************************************************************/
/*									*/
/* Tesselate the current object, and add faces to SIPP.			*/
/* This can take quite some time...					*/
/*									*/
/************************************************************************/
{
    Tesselate_Info_T	TI;
    Boolean_T		Error;
    Surface		*SIPP_Surface;
    static  Boolean_T	First_Time = TRUE;

    Render_Cleanup();

    Min_Value = 999999.9;
    Max_Value = -999999.9;

    TI.Patch_Begin         = NULL;
    TI.Patch_Generate_Face = Create_SIPP_Polygon;
    TI.Patch_End   	   = NULL;

    if (First_Time) {

	First_Time = FALSE;

        sipp_init();

    } /* if */

    Error = Tesselate_Object(&TI);

    if (Error) return(Error);

    Strauss_Shader_Data.ambient 	= 0.0;	/* 0 - 1 */
    Strauss_Shader_Data.smoothness 	= 0.5;
    Strauss_Shader_Data.metalness 	= 0.5;
    Strauss_Shader_Data.color.red 	= 1.0;
    Strauss_Shader_Data.color.grn 	= 1.0;
    Strauss_Shader_Data.color.blu 	= 1.0;
    Strauss_Shader_Data.opacity.red 	= 1.0;
    Strauss_Shader_Data.opacity.grn 	= 1.0;
    Strauss_Shader_Data.opacity.blu 	= 1.0;

    Display_Status("Creating surface");
    SIPP_Surface = surface_create(&Strauss_Shader_Data, strauss_shader);

    Display_Status("Creating object");
    SIPP_Object = object_create();
    object_add_surface(SIPP_Object, SIPP_Surface);
 
    object_add_subobj(sipp_world, SIPP_Object);

    Min_Value = ABS(Min_Value);
    Max_Value = ABS(Max_Value);

    View_Distance  = (Min_Value > Max_Value) ? Min_Value : Max_Value;
    View_Distance  = View_Distance * 3.0;

    Display_Status("Render setup done");

    return(FALSE);

} /* Render_Setup */

void Render_Cleanup()
/************************************************************************/
/*									*/
/* Free memory etc. used in rendering.					*/
/*									*/
/************************************************************************/
{
    if (SIPP_Object == NULL) return;

    object_sub_subobj(sipp_world, SIPP_Object);
    object_delete(SIPP_Object);

    SIPP_Object = NULL;

} /* Render_Cleanup */

Boolean_T Render_Object()
/************************************************************************/
/*									*/
/* render the current object.						*/
/*									*/
/* Render_Setup should be called before this routine.			*/
/* Render_Cleanup should be called later to release memory etc.		*/
/*									*/
/************************************************************************/
{
    Vector_T		P, RP;
    Boolean_T		Error;
    Lightsource		*SIPP_Lightsource;
    int			Width, Height;
    int			Mode;

    if (SIPP_Object == NULL) return(TRUE);

    Set_Pointer(TRUE);

    Error_Diffusion_Init();

    switch (Rendering_Mode) {

    case RM_Phong	: Mode = PHONG; 	break;
    case RM_Gouraud	: Mode = GOURAUD; 	break;
    case RM_Flat	: Mode = FLAT;		break;
    case RM_Line	: Mode = LINE;  	break;
    default		: Mode = LINE;		break;

    } /* switch */

    P[0] = 0.0; 		
    P[1] = -View_Distance;
    P[2] = 0.0;

    Compute_Inverse_Rotation_Matrix(M_InvRotation, Rotation_Vector);
    Matrix_MultV(RP, M_InvRotation, P);

    sipp_show_backfaces(!Backface_Culling);

    camera_params(sipp_camera, 
		RP[0], RP[1], RP[2],
		0.0, 0.0, 0.0,		/* Look at	*/
		0.0, 0.0, 1.0,		/* Up vector   	*/
		0.4);			/* Focal factor	*/

    SIPP_Lightsource = 	lightsource_create(
			2.0*RP[0], 2.0*RP[1], 2.0*RP[2], /* Position 	*/
			1.0, 1.0, 1.0, 		/* Color (RGB)	*/
			LIGHT_DIRECTION);


    Display_Status("Rendering image");
    Current_Pen = -1;
    Width  = Views[Id_Active_Window].X_Max - 
	     Views[Id_Active_Window].X_Min + 1;
    Height = Views[Id_Active_Window].Y_Max - 
             Views[Id_Active_Window].Y_Min + 1;

    Width  = Width / 2;

    if (Mode != LINE) {

	Set_Color_Table_Gray(TRUE);

        Clear_All(What_P);

        render_image_func(
		Width,			/* Width	*/
		Height,			/* Height	*/
		Draw_Pixel,		/* Function	*/
		0,			/* Data 	*/
		Mode,			/* Mode		*/
		1);			/* Oversampling	*/

        Display_Status("Rendering Done. Press spacebar");

	while (Wait_For_Key() != ' ') ; 

	Set_Color_Table_Gray(FALSE);
        Redraw_Mask |= What_All;

    } else {

        Clear_All(What_P);

        render_image_func(
		Width,			/* Width	*/
		Height,			/* Height	*/
		Draw_Line,		/* Function	*/
		0,			/* Data 	*/
		LINE,			/* Mode		*/
		1);			/* Oversampling	*/

        Display_Status("Rendering Done. Press spacebar");

	while (Wait_For_Key() != ' ') ; 

        Redraw_Mask |= What_All;

    } /* if .. else .. */

    Display_Status(NULL);

    light_destruct(SIPP_Lightsource);

    Error_Diffusion_Free();

    Set_Pointer(FALSE);

    return(FALSE);

} /* Render_Object */

Boolean_T Render_Tesselate_Object()
/************************************************************************/
/*									*/
/* Tesselate the current object, and render it.				*/
/*									*/
/************************************************************************/
{
    Boolean_T		Error;

    Set_Pointer(TRUE);

    if (Render_Setup()) return(TRUE);

    Error = Render_Object();

    return(Error);

} /* Render_Tesselate_Object */
