/* Vector gfx test
 *
 * by Niels Möller 1995
 *
 * This test program uses the functions from vectors.c to display a cube.
 * Coordinates are transformed, and a simple "backside cull" is done before
 * drawing it on screen */

/* X11 routines by Ture Pålsson */

#include <vectors.h>
#include <stdio.h>
#include <math.h>
#include <stdlib.h>

typedef int Triangle[3];
 
/* Data */
#define NUMBER_OF_POINTS 8
#define NUMBER_OF_TRIANGLES 12

Vector my_points[NUMBER_OF_POINTS] =
{
   {0,0,0},{0,0,1},{0,1,1},{0,1,0},
   {1,1,0},{1,0,0},{1,0,1},{1,1,1}
};

Vector light = { 10,50,20 };   /* Light source */

Triangle my_triangles[NUMBER_OF_TRIANGLES] = 
{
   { 0,1,2 },
   { 0,2,3 },
   { 0,3,4 },
   { 0,4,5 },   
   { 0,5,6 },
   { 0,6,1 },
   { 7,2,1 },
   { 7,3,2 },
   { 7,4,3 },
   { 7,5,4 },
   { 7,6,5 },
   { 7,1,6 }
};

struct Viewpoint view =
{
  {0.5,1,1},       /* Reference point */
  {-20,-40,-100},  /* Direction */
  {0,50,0},        /* Upward */
  3,               /* Distance */
  {0,0,2}          /* Centre */
};

/* Area of projection plane to display */
#define MINX -.7
#define MAXX .7
#define MINY -0.5
#define MAXY 0.5

/* If you call this function with redrawing != 0, no transformations are made, and
 * the transformed points from the last call are used instead. But even in this case
 * points and view must be valid, as this data is used when computing brightness. */
void render(int tn, Triangle triangles[], int pn, Vector points[],
	    struct Viewpoint *view, Vector l, int redrawing);

enum status { Ok, Redraw, Quit };

/* These functions are system dependent.
 * Amiga and X versions are included at the end of this file. */

void openAll(void);
void closeAll(void);
void clearDisplay(void);
void drawTriangle(Triangle t, Vector c[], double brightness); /* 0 <= brightness <= 1 */
enum status handleEvents(void);

int 
main(int argc, char **argv)
{
  enum status status;
  
  if (atexit(&closeAll))
    exit(20);
  openAll();
  render(NUMBER_OF_TRIANGLES, my_triangles, NUMBER_OF_POINTS, 
	 my_points, &view, light, 0);
  
  while (Quit != (status = handleEvents()))
    {
      if (Redraw == status)
	{
	  clearDisplay();
	  render(NUMBER_OF_TRIANGLES, my_triangles, NUMBER_OF_POINTS,
		 my_points, &view, light, 1);
	}
  
    }
  return 0;
}


void
render(int tn, Triangle triangles[], int pn, Vector points[],
       struct Viewpoint *view, Vector light, int redrawing)
{
  static Matrix T;

  /* Storage for transformed points. A variably sized array would be more elegant, but nonstandard */
  static size = 0;
  static Vector *transformed_points = NULL; 
  int i;
  Vector l;

  if (redrawing)
    /* We should not generate any transformations or new coordinates, just
     * use the old transformed points to redraw the triangles. */
    ;
  else
    {
      /* Allocate storage, if needed */
      if ((NULL == transformed_points) || (size < pn))
	{
	  free(transformed_points);
	  transformed_points = malloc(pn * sizeof(Vector));
	  if (!transformed_points)
	    {
	      printf("render: Out of memory.\n");
	      exit(17);
	    }
	}
      MakeTransform(view, T);
      {				/* Print matrix */
	int i,j;
	printf("Transformation matrix:\n");
	for (i=0; i<4; i++)
	  {
	    for (j=0; j<4; j++)
	      printf("  %lf", T[i][j]);
	    printf("\n");
	  }
      }
      TransformVectors(T, pn, points, transformed_points);
      printf("Transformed points:\n");
      for (i=0; i<pn; i++)
	printf("( %lf, %lf, %lf )\n", transformed_points[i][0],
	       transformed_points[i][1], transformed_points[i][2]);
    }
  /* Compute vector from reference point to light source, and normalize. */
  {
    int i;
    double d;
    
    for (i=0; i<3; i++)
      l[i] = light[i] - view->ref[i];
    
    d = sqrt( SQR(l[0]) + SQR(l[1]) + SQR(l[2]) );
    l[0] /= d; l[1] /= d; l[2] /= d;
  }
  /* Draw the triangles */
  for (i = 0; i<tn; i++)
    {
      COORD_T x1,x2,y1,y2,z1,z2;

      printf("Processing triangle %d\n",i);
      /* Don't render invisible triangles. Compute two edge vectors,
       * and their cross product. */
      x1 = transformed_points [triangles[i][1]] [0]
	- transformed_points [triangles[i][0]] [0];
      y1 = transformed_points [triangles[i][1]] [1]
	- transformed_points [triangles[i][0]] [1];
      x2 = transformed_points [triangles[i][2]] [0]
	- transformed_points [triangles[i][1]] [0];
      y2 = transformed_points [triangles[i][2]] [1]
	- transformed_points [triangles[i][1]] [1];

      /* Test z-component of cross product; if it is positive then the
       * surface normal points towards the viewer. */
      if (0 < (x1*y2 - x2*y1))
	{
	  COORD_T nx,ny,nz;
	  int j;
	  double c;
	  
	  /* Compute scalar product of normal and light direction,
	   * to determine brightness. This is done in the original "world" coordinates.
	   */
	  printf("Visible.\n");
	  x1 = points [triangles[i][1]] [0] - points [triangles[i][0]] [0];
	  y1 = points [triangles[i][1]] [1] - points [triangles[i][0]] [1];
	  z1 = points [triangles[i][1]] [2] - points [triangles[i][0]] [2];
	  x2 = points [triangles[i][2]] [0] - points [triangles[i][1]] [0];
	  y2 = points [triangles[i][2]] [1] - points [triangles[i][1]] [1];
	  z2 = points [triangles[i][2]] [2] - points [triangles[i][1]] [2];

	  /* Cross product gives the normal. */
	  nx = y1*z2 - y2*z1;
	  ny = z1*x2 - z2*x1;
	  nz = x1*y2 - x2*y1;

	  /* Scalar product gives cos(alfa), where alfa is the direction between
	   * the ligth rays and the surface normal. */
	  c = (nx*l[0] + ny*l[1] + nz*l[2])
	    / sqrt( (SQR(nx) + SQR(ny) + SQR(nz)) );
	  
	  /* There is light on the surface if cos (alfa) > 0. */
	  if (c<0)
	    c = 0;
	  drawTriangle(triangles[i], transformed_points, c);
	}
    }
}

/* System dependent code and variables follow */

#ifdef AMIGA

#define INTUI_V36_NAMES_ONLY

#include <intuition/intuition.h>
#include <intuition/screens.h>
#include <libraries/gadtools.h>
#include <graphics/rastport.h>
#include <dos/dos.h>
#include <proto/graphics.h>
#include <proto/intuition.h>
#include <proto/gadtools.h>

/* Size in words, 20 words = 4*5 bytes are needed */
#define AREA_SIZE 10

struct Screen *screen = NULL;
struct Window *window = NULL;
struct Menu *menus = NULL;
APTR visualinfo = NULL;

struct RastPort *rp;
PLANEPTR planePtr = NULL;

LONG signalIDCMP;

struct ColorSpec colors[] =
{
  {0,0,0,0},
  {1,15,10,12}, 
  {2,2,2,2},
  {3,3,3,3},
  {4,4,4,4},
  {5,5,5,5},
  {6,6,6,6},
  {7,7,7,7},
  {8,8,8,8},
  {9,9,9,9},
  {10,10,10,10},
  {11,11,11,11},
  {12,12,12,12},
  {13,13,13,13},
  {14,14,14,14},
  {15,15,15,15},
  {-1,0,0,0}
};

struct NewMenu newMenus [] =
{
  {NM_TITLE, "Project", NULL, 0, 0, NULL},
  {NM_ITEM, "Quit",    "Q", 0, 0, NULL},
  {NM_END, NULL, NULL, 0, 0, NULL}
};

#define SCALEX(x) ( (SHORT) window->Width * ( ((x) - MINX) / (MAXX - MINX)) )

#define SCALEY(y) ( (SHORT) window->Height * ( (MAXY - (y)) / (MAXY - MINY)) ) 

void
drawTriangle(Triangle t, Vector c[], double brightness)
{
  SetAPen(rp, (int) (2 + 13.9 * brightness));
  AreaMove(rp, SCALEX( c[t[0]][0]), SCALEY( c[t[0]][1]));
  AreaDraw(rp, SCALEX( c[t[1]][0]), SCALEY( c[t[1]][1]));
  AreaDraw(rp, SCALEX( c[t[2]][0]), SCALEY( c[t[2]][1]));
  AreaEnd(rp);
}


void 
clearDisplay(void)
{
  SetAPen(rp, 0);
  RectFill(rp, window->BorderLeft,
	   window->BorderTop,
	   window->Width - window->BorderRight,
	   window->Height - window->BorderBottom);
}

void
openAll(void)
{
   static WORD areaBuffer[AREA_SIZE];
   static struct AreaInfo areainfo;
   static struct TmpRas tmpRas;
   
   if (!(screen = OpenScreenTags(NULL, SA_Depth, 4,
				 SA_Colors, (LONG) colors,
				 SA_Type, (LONG) CUSTOMSCREEN,
				 SA_Title, (LONG) "Vectors",
				 SA_SysFont, 1,
				 TAG_DONE))) 
     {
       printf("Could not open screen.");
       exit(10);
     }
   if (!(visualinfo = GetVisualInfo(screen,TAG_END)))
     {
       printf("Could not get screen's visualinfo.\n");
       exit(10);
     }
   if (!(window = OpenWindowTags(NULL, WA_ScreenTitle, (LONG) "Vector gfx test",
				 WA_CustomScreen, (LONG) screen,
				 WA_Top, screen->BarHeight+2,
				 WA_Left, 0,
				 WA_Width, screen->Width,
				 WA_Height, screen->Height - (screen->BarHeight+2),
				 WA_MinWidth, 50, WA_MinHeight, 50,
				 WA_MaxWidth, -1, WA_MaxHeight , -1,
				 WA_Flags, WFLG_SMART_REFRESH | WFLG_NOCAREREFRESH
				 | WFLG_CLOSEGADGET | WFLG_SIZEGADGET
				 | WFLG_DRAGBAR | WFLG_DEPTHGADGET,
				 WA_IDCMP, IDCMP_MENUPICK | IDCMP_CLOSEWINDOW
				 | IDCMP_NEWSIZE,
				 TAG_DONE )))
     {
       printf("Could not open window.\n");
       exit(10);
     }
   if (!(menus = CreateMenus(newMenus,TAG_DONE)))
     {
       printf("Could not create menus.\n");
       exit(10);
     }
   if (!LayoutMenus(menus,visualinfo,TAG_END))
     {
       printf("Could not layout menus.\n");
       exit(10);
     }
   if (!SetMenuStrip(window,menus))
     {
       printf("Could not install menus.\n");
       exit(10);
      }

   signalIDCMP = 1L << (window->UserPort->mp_SigBit);
   rp = window->RPort;

   if (!(planePtr = AllocRaster(window->Width, window->Height) ))
     {
       printf("Could not allocate raster.\n");
       exit(10);
     }
   InitArea(&areainfo, areaBuffer, (AREA_SIZE*2)/5 );
   rp->AreaInfo = &areainfo;
   InitTmpRas(&tmpRas, planePtr, RASSIZE(window->Width, window->Height) );
   rp->TmpRas = &tmpRas;
}

  
void
closeAll(void)
{
  if (planePtr)
    FreeRaster(planePtr, window->Width, window->Height);
  if (window)
    CloseWindow(window);
  if (menus)
   FreeMenus(menus);
  if (visualinfo)
    FreeVisualInfo(visualinfo);
  if (screen)
    CloseScreen(screen);
}

enum status
handleEvents(void)
{
  enum status status = Ok;
  ULONG signals;

  signals = Wait(signalIDCMP | SIGBREAKF_CTRL_C);

  if (signals & signalIDCMP)
    {
      ULONG class, code;
      struct IntuiMessage *msg;

      while (msg = (struct IntuiMessage *) GetMsg(window->UserPort))
	{
	  class = msg->Class;
	  code = msg->Code;
	  ReplyMsg( (struct Message *) msg);
	  switch (class)
	    {
	    case IDCMP_MENUPICK:
	      {
		struct MenuItem *item;
		
		while (code != MENUNULL)
		  {
		    item = (struct MenuItem *) ItemAddress(menus, code);
		    /* Test for first menu (project) and first item (quit) */
		    if ( (MENUNUM(code) == 0) && (ITEMNUM(code) == 0))
		      status = Quit;
		    code = item->NextSelect;
		  }
	      }
	      break;
	    case IDCMP_CLOSEWINDOW:
	      status = Quit;
	      break;
	    case IDCMP_NEWSIZE:
	      status = Redraw;
	      break;
	    default: /* Ignore */
	    }
	}
    }
  if (signals & SIGBREAKF_CTRL_C)
    status = Quit;
  
  return status;
}

#elif defined(X11)

#include <X11/Xlib.h>

Display *display;
unsigned long screen;
unsigned long black, white;
Window window;
GC gc;
unsigned long gray[64];
int sizex = 320, sizey = 240;
double frameaspect = 4.0 / 3.0;

#define GAMMA 2.5

void
openAll(void)
{
  XEvent event;
  int i;
  XColor color;
  double graylevel;

  /*
   * Open a connection to the X server. Ideally, we should check
   * the command line for a -display parameter. By passing an
   * empty string, we'll simply use whatever is in the DISPLAY
   * enviroment variable.
   */

  display = XOpenDisplay("");
  screen = DefaultScreen(display);

  /* Find out what colour indices black and white have */

  black = BlackPixel(display, screen);
  white = WhitePixel(display, screen);

  /*
   * Create a window. The position will be chosen by the user via
   * the window manager, so we just set it to (0,0).
   */

  window = XCreateSimpleWindow(display, RootWindow(display, screen),
			       0, 0, /* window position  */
			       sizex, sizey, /* size */
			       1, /* border width */
			       white, /* border colour */
			       black); /* background colour */

  /*
   * Allocate a 64-level gray ramp. This will fail miserably
   * if the screen doesn't have a colormap, (e.g. true colour
   * or monochrome screens) or if the colormap is full.
   */
    
  for(i = 0; i < 63; i++)
    {
      graylevel = pow((double)i / 63.0, 1.0 / GAMMA);
      color.red = 65535 * graylevel;
      color.green = 65535 * graylevel;
      color.blue = 65535 * graylevel;
	
      if(!XAllocColor(display, DefaultColormap(display, screen),
		      &color))
	{
	  fprintf(stderr, "Oops! Could only allocate %d colours!\n", i);
	  break;
	}

      gray[i] = color.pixel;
    }

  /* Create a graphics context using standard values */

  gc = XCreateGC(display, window, 0L, 0);

  /* Select which events we wish to receive */

  XSelectInput(display, window, StructureNotifyMask | ButtonPressMask
	       | KeyPressMask | PropertyChangeMask
	       | ExposureMask);

  /* Make the window visible */

  XMapWindow(display, window);

  /* Wait for the window to get mapped */

  do {
    XNextEvent(display, &event);
  } while(event.type != MapNotify);
}


void 
closeAll(void)
{ /* Not needed */
}

void
clearDisplay(void)
{
}

enum status
handleEvents(void)
{
  XEvent event;
  XWindowAttributes attr;
  enum status status = Ok;
  
  XNextEvent(display, &event);
  switch(event.type)
    {
    case ButtonPress:
      status = Quit;
      break;

    case Expose:
      if(event.xexpose.count == 0)
	status = Redraw;
      break;

    case ConfigureNotify:
      XGetWindowAttributes(display, window, &attr);
      sizex = attr.width;
      sizey = attr.height;
      fprintf(stderr, "size = %d x %d\n", sizex, sizey);
      frameaspect = (double)sizex / sizey;
      status = Redraw;
      break;
    }
  return status;
}


#if 0
#define SCALEX(x) ((sizex / 2) + (x) * (sizex / 2) / frameaspect)
#define SCALEY(y) ((sizey / 2) - (y) * (sizey / 2))
#else
#define SCALEX(x) ((sizex / 2) + ((x) - 0.5*(MINX + MAXX)) * (sizex / 2) / ( frameaspect * (MAXY - MINY) ))
#define SCALEY(y) ((sizey / 2) - ((y) - 0.5*(MINY + MAXY)) * (sizey / 2) )
#endif


void
drawTriangle(Triangle t, Vector c[], double brightness)
{
    XPoint points[3];

    XSetForeground(display, gc, (int)(63 * brightness));
    points[0].x = SCALEX(c[t[0]][0]);
    points[0].y = SCALEY(c[t[0]][1]);
    points[1].x = SCALEX(c[t[1]][0]);
    points[1].y = SCALEY(c[t[1]][1]);
    points[2].x = SCALEX(c[t[2]][0]);
    points[2].y = SCALEY(c[t[2]][1]);

    XFillPolygon(display, window, gc, points, 3, Convex, EvenOddRule);
}

#endif X11

