/*
  (c) 1990 S.Hawtin.
  Permission is granted to copy this file provided
   1) It is not used for commercial gain
   2) This notice is included in all copies
   3) Altered copies are marked as such

  No liability is accepted for the contents of the file.

    render.c	within		NorthC render
*/

/*
 Quick hack to play with transforms for three dimensional objects
*/

#include <stdio.h>
#include <stddef.h>
#include <math.h>

#include "3d.h"

/* Controlling variables */
int solid_fill = -1;
double rot1 = 0.120;
double rot2 = 0.011;

/* The position of the lights, they are fixed relative to the viewer */

#define LCOUNT 5

long lights[LCOUNT][3] =
   {
    { 278, 356, 634},
    {  500,-1000,-250},
    {-2000,    0, 1000},
    {0,0,0},
    {0,0,0},
    };

char object_name[32] = "objects/boat.dat";

/* The object is made of an array of triangles */

Triangle **object;
int obj_count;

Vertex **vertices;
int    vertex_count;

/* Here are the triangles we must draw */
Triangle **draw_list;
int draw_count;

/* Angle object is being viewed from */
double view[2] =
   {0.3,4.0};

/* Transformation matrix in 1024th */

static int scale_factor = 1024;

long transform[4][4] =
   {{ -512, -737, -737,    0},
    {  870, -431, -256,    0},
    {    0,  512, -870,    0},
    {    0,    0,    0, 1024}
    };

trans_vertex(vtex)
    Vertex *vtex;
   {/* Transform a single vertex */
    register long temp;
    if(vtex->is_done==0)
       {/* This vertex has not yet been done, do it */
        temp = (transform[0][0] * vtex->logical.x +
                transform[0][1] * vtex->logical.y +
                transform[0][2] * vtex->logical.z)/(1024*SCALE);
	vtex->screen.x = (int)temp;
        temp = (transform[1][0] * vtex->logical.x +
                transform[1][1] * vtex->logical.y +
                transform[1][2] * vtex->logical.z)/(1024*SCALE);
	vtex->screen.y = (int)temp;
        temp = (transform[2][0] * vtex->logical.x +
                transform[2][1] * vtex->logical.y +
                transform[2][2] * vtex->logical.z)/(1024*SCALE);
	vtex->screen.z = (int)temp;
	vtex->is_done = -1;
        }
    }

int
illuminate(vect)
    Vector *vect;
   {/* Work out the ammount of illumination from the normal to the
       surface */
    long tx,ty,tz;
    long i,factor,add;
    long sum = 0;
    tx = (transform[0][0] * vect->x + transform[0][1] * vect->y +
                          transform[0][2] * vect->z)/1024;
    ty = (transform[1][0] * vect->x + transform[1][1] * vect->y +
                          transform[1][2] * vect->z)/1024;
    tz = (transform[2][0] * vect->x + transform[2][1] * vect->y +
                          transform[2][2] * vect->z)/1024;
    for(i=0;i<LCOUNT;i++)
       {/* Sum the illumination from each light */
        add = (tx * lights[i][0] + ty * lights[i][1] + tz * lights[i][2]);
        if(add>0)
	    sum += add;
        }
    sum = sum/(LCOUNT*1024);
    if(sum>1023)
        return(1023);
      else
        return((int)sum);
    }

int
cmpz(tri1,tri2)
    Triangle **tri1;
    Triangle **tri2;
   {/* Compare the z values of the triangle centres */
#ifndef TEST_LIGHTS
    if(tri1[0]->centre.screen.z > tri2[0]->centre.screen.z)
        return(1);
      else if(tri1[0]->centre.screen.z == tri2[0]->centre.screen.z)
#else
    if(tri1[0]->centre.logical.z > tri2[0]->centre.logical.z)
        return(1);
      else if(tri1[0]->centre.logical.z == tri2[0]->centre.logical.z)
#endif
        return(0);
      else
        return(-1);
    }

display_object()
   {/* Display the object, first work out all the points */
    int i;
    draw_count = 0;
    for(i=0;i<obj_count;i++)
       {/* Work out the real points for the whole object */
#ifndef TEST_LIGHTS
        if((transform[2][0] * object[i]->normal.x +
            transform[2][1] * object[i]->normal.y +
            transform[2][2] * object[i]->normal.z)>0)
#else
        if((object[i]->normal.z)>0)
#endif
           {trans_vertex(object[i]->v1);
            trans_vertex(object[i]->v2);
            trans_vertex(object[i]->v3);
            trans_vertex(&(object[i]->centre));
            /* Work out the brightness of the lights */
            object[i]->shade = illuminate(&(object[i]->normal));
            draw_list[draw_count++] = object[i];
            }
        }
    qsort(draw_list,draw_count,sizeof(Triangle *),cmpz);
    clear_screen();
    for(i=0;i<draw_count;i++)
        draw_tri((int)((draw_list[i]->shade)/64),
                 (int)(draw_list[i]->v1->screen.x),
                 (int)(draw_list[i]->v1->screen.y),
                 (int)(draw_list[i]->v2->screen.x),
                 (int)(draw_list[i]->v2->screen.y),
                 (int)(draw_list[i]->v3->screen.x),
                 (int)(draw_list[i]->v3->screen.y));
    }

/* Read the object description, */

init_triangle(tri)
    Triangle *tri;
   {long nx,ny,nz,length;
    /* Initialise the triangle once the corners are set up */
    tri->centre.logical.x = (tri->v1->logical.x + tri->v2->logical.x + 
                             tri->v3->logical.x)/3;
    tri->centre.logical.y = (tri->v1->logical.y + tri->v2->logical.y + 
                             tri->v3->logical.y)/3;
    tri->centre.logical.z = (tri->v1->logical.z + tri->v2->logical.z + 
                             tri->v3->logical.z)/3;

    nx=(tri->v1->logical.y * (tri->v3->logical.z - tri->v2->logical.z)
        +  tri->v2->logical.y * (tri->v1->logical.z - tri->v3->logical.z)
        +  tri->v3->logical.y * (tri->v2->logical.z - tri->v1->logical.z));
    ny=(tri->v1->logical.z * (tri->v3->logical.x - tri->v2->logical.x)
        +  tri->v2->logical.z * (tri->v1->logical.x - tri->v3->logical.x)
        +  tri->v3->logical.z * (tri->v2->logical.x - tri->v1->logical.x));
    nz=(tri->v1->logical.x * (tri->v3->logical.y - tri->v2->logical.y)
        +  tri->v2->logical.x * (tri->v1->logical.y - tri->v3->logical.y)
        +  tri->v3->logical.x * (tri->v2->logical.y - tri->v1->logical.y));
    /* Now make the vector 1024 units long */
    length = (long)sqrt((double)(nx*nx+ny*ny+nz*nz));
    tri->normal.x = (nx*1024)/length;
    tri->normal.y = (ny*1024)/length;
    tri->normal.z = (nz*1024)/length;
    }

read_file(fptr)
    FILE *fptr;
   {/* First a count of the vertex list */
    Vertex   *vdata;
    Triangle *tdata;
    int i,n1,n2,n3;
    char next;

    fscanf(fptr,"%d",&vertex_count);
    if(vertex_count<=0)
        printf("No vertices in object\n");
    vertices = (Vertex **)calloc(sizeof(Vertex *),vertex_count);
    vdata    = (Vertex *)calloc(sizeof(Vertex),vertex_count);
    if(vertices==NULL || vdata==NULL)
       {printf("Failed to malloc space\n");
        exit(EXIT_FAILURE);
        }
    for(i=0;i<vertex_count;i++)
       {/* Set up the vertex stuff */
        vertices[i] = vdata;
        fscanf(fptr,"%d %d %d",&(vdata->logical.x),
                          &(vdata->logical.y),&vdata->logical.z);
        next = fgetc(fptr);
        while(next!='\n')
            next = fgetc(fptr);
        vdata++;
        }
    fscanf(fptr,"%d",&obj_count);
    if(obj_count<=0)
        printf("No triangles in object\n");
    object  = (Triangle **)calloc(sizeof(Triangle *),obj_count);
    draw_list = (Triangle **)calloc(sizeof(Triangle *),obj_count);
    tdata   = (Triangle *)calloc(sizeof(Triangle),obj_count);
    if(object==NULL || draw_list==NULL || tdata==NULL)
       {printf("Failed to malloc space\n");
        exit(EXIT_FAILURE);
        }
    for(i=0;i<obj_count;i++)
       {/* Set up the triangle stuff */
        object[i] = tdata;
        fscanf(fptr,"%d %d %d",&n1,&n2,&n3);
        tdata->v1 = vertices[n1];
        tdata->v2 = vertices[n2];
        tdata->v3 = vertices[n3];
        next = fgetc(fptr);
        while(next!='\n')
            next = fgetc(fptr);
        init_triangle(tdata);
        tdata++;
        }
    }

/* Transform the view into a transform */

trans_view()
   {/* Constructthe transformation matrix */
    int i;
    double sinth,costh,sinph,cosph;

    sinth = sin(view[0]);
    costh = cos(view[0]);
    sinph = sin(view[1]);
    cosph = cos(view[1]);

    transform[0][0] = (long)(-sinth*scale_factor);	/* -sin(th) */
    transform[2][0] = (long)(costh*scale_factor);	/* cos(th)  */
    transform[1][0] = (long)0;				/* 0 */

    transform[0][1] = (long)(-costh*cosph*scale_factor);/* -cos(th)cos(ph) */
    transform[2][1] = (long)(-sinth*cosph*scale_factor);/* -sin(th)cos(ph) */
    transform[1][1] = (long)(sinph*scale_factor);	/* sin(ph) */

    transform[0][2] = (long)(-costh*sinph*scale_factor);/* -cos(th)sin(ph) */
    transform[2][2] = (long)(-sinth*sinph*scale_factor);/* -sin(th)sin(ph) */
    transform[1][2] = (long)(-cosph*scale_factor);	/* -cos(ph) */

    /* Note that we havent done any of the transforms yet */
    for(i=0;i<obj_count;i++)
       {object[i]->centre.is_done = 0;
        }
    for(i=0;i<vertex_count;i++)
       {vertices[i]->is_done = 0;
        }
    }

/* SHOW_FACES is set when creating objects */
/* #define SHOW_FACES */

main(argc,argv)
    int argc;
    char **argv;
   {
    FILE *fptr;
    int i;

    /* Read the object file */
    if(argc>=2)
       {strcpy(object_name,argv[argc-1]);
        }
      else
        scale_factor = 1500;
    fptr = fopen(object_name,"r");
    if(fptr==NULL)
       {printf("Cannot open %s\n",object_name);
        exit(EXIT_FAILURE);
        }
    read_file(fptr);
    /* Set SHOW_FACES to get a print out of the centres and normals of
       all the faces, very useful when defining objects */
#ifdef SHOW_FACES
    for(i=0;i<obj_count;i++)
       {printf("Face %d %5d,%5d,%5d\n",i,object[i]->normal.x,
		 object[i]->normal.y,object[i]->normal.z);
        printf("  cent  %5d,%5d,%5d\n",object[i]->centre.logical.x,
		 object[i]->centre.logical.y,object[i]->centre.logical.z);
        }
#endif /* SHOW_FACES */
    /* Set up the screen */
    init_screen();
    /* Keep displaying forever, the code calls exit() when its done */
    while(-1)
       {
    	trans_view();
	display_object();
	view[0] += rot1;
	view[1] += rot2;
	}
    }
