/*

  "Introduction to 3D Programming" Tutorial series
  Volume #2 - For DemoNews 116
  Topic: 2D and 3D Rotations

  Sample code for Borland C 3.1 or above (might work with lesser versions,
  but it's untested below 3.1 so I can't say for sure).

  Make sure at least 286 instructions are enabled in your compiler options.

*/

#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
#include <math.h>
#include <dos.h>
#include <alloc.h>

#define PI 3.1415926535897932385

/* "vector" structure - holds a 3D cartesian point, and its projection. */
typedef struct vector
{
int x;
int y;
int z;
int scrx;
int scry;
} vector;

/* Box object, used for the last example as well as this one. */

vector Box[8] =  {
		 { 20, 40, 30, 0,0},
		 { 20, 40,-30, 0,0},
		 { 20,-40, 30, 0,0},
		 { 20,-40,-30, 0,0},
		 {-20, 40, 30, 0,0},
		 {-20, 40,-30, 0,0},
		 {-20,-40, 30, 0,0},
		 {-20,-40,-30, 0,0}
		 };

vector RotatedBox[8];

/*

videomode, putpixel, clrscr, copyscr, and wfvr functions for graphics display.
(wfvr is short for Wait For Vertical Retrace)

(Yes, these are not optimized, but that's not the point of
 this example program :-)

 (Can you sense that I'm cut-and-pasting from volume 1? :)

*/

void videomode(unsigned int mode)
{
  asm {
	mov ax, mode;
	int 10h;
      }
}

void putpixel(unsigned int x, unsigned int y, char color, char far *vscr)
{
  int dseg, dofs;
  dseg = FP_SEG(vscr);
  dofs = FP_OFF(vscr);
  asm {
	mov es, dseg;
	mov di, dofs;
	mov ax, y;
	shl ax, 6;
	mov bx, ax;
	shl ax, 2;
	add ax, bx;
	add ax, x;
	add di, ax;
	mov al, color;
	mov [es:di], al;
      }
}

void clrscreen(char far *screen)
{
  int dseg, dofs;
  dseg = FP_SEG(screen);
  dofs = FP_OFF(screen);
  asm {
	mov es, dseg;
	mov di, dofs;
	xor ax, ax;
	mov cx, 32000;
	cld;
	rep stosw;
      }
}

void copyscr(char far *destscreen, char far *srcscreen)
{
  int dseg, dofs, sseg, sofs;
  dseg = FP_SEG(destscreen);
  dofs = FP_OFF(destscreen);
  sseg = FP_SEG(srcscreen);
  sofs = FP_OFF(srcscreen);
  asm {
	push ds;
	mov  ds, sseg;
	mov  es, dseg;
	mov  si, sofs;
	mov  di, dofs;
	mov  cx, 32000;
	cld;
	rep  movsw;
	pop  ds;
      }
}


void wfvr(void)
{
  while ((inp(0x03DA) & 8) == 0);
  while ((inp(0x03DA) & 8) != 0);
}

/* Function Declarations for RotatePoint, RotateBox, and the trig functions */

void RotatePoint(vector point, vector *dest, char rx, char ry, char rz);
void RotateBox(char angle_x, char angle_y, char angle_z);
float sin256(char angle);
float cos256(char angle);

/* Declarations for PointProject and DrawRotatedBox, from Volume 1 */

void PointProject(int distance, vector point, vector *dest, vector center);
void DrawRotatedBox(int cenx, int ceny, int cenz, char color);

/* Screen pointers for the drawing functions */

char far *physicalscreen;
char far *virtualscreen;

/*
Main program - Draws a standard box like in volume 1, waits for keypress,
	       then rotates by each axis between keypresses, and finally
	       by all axes.

	       The drawing is to the virtual screen, allocated in the
	       beginning.  copyscr() copies the virtual screen to the
	       physical screen, during the vertical retrace.

*/

void main(void)
{
  char angle;

  videomode(0x03);

  physicalscreen = MK_FP(0xA000,0x0000);
  virtualscreen = (char far *)farmalloc(64000);
  if (virtualscreen == NULL)
  {
    printf("\nSorry, not enough memory. :(\n");
    exit(1);
  }

  printf("\nIntro to 3D Programming - Volume #2, 2D and 3D rotations.\n");
  printf("By Chris Hargrove (Kiwidog - Terraformer/Hornet)\n");
  printf("From DemoNews issue #116, 2/19/96\n");
  printf("This example takes the box we had from Volume #1, and performs\n");
  printf("basic rotations on it.\n\n");
  printf("The first rotation will be about the X axis, the second about\n");
  printf("the Y axis, and the third about the Z axis.  The final rotation\n");
  printf("will rotate about all three axes, at the same time.  Just press\n");
  printf("a key during any rotation to move to the next one.\n\n");
  printf("BTW, no this example is not optimized. ");
  printf("'Tis not meant to be. :) \n\n");
  printf("Hit a key to begin...\n");

  getch();

  videomode(0x13);

  /* X axis rotation */
  while (!kbhit())
  {
    RotateBox(angle++,0,0);
    DrawRotatedBox(0,0,0,15);
    wfvr();
    copyscr(physicalscreen, virtualscreen);
    clrscreen(virtualscreen);
  }
  getch();

  /* Y axis rotation */
  while (!kbhit())
  {
    RotateBox(0,angle++,0);
    DrawRotatedBox(0,0,0,15);
    wfvr();
    copyscr(physicalscreen, virtualscreen);
    clrscreen(virtualscreen);
  }
  getch();

  /* Z axis rotation */
  while (!kbhit())
  {
    RotateBox(0,0,angle++);
    DrawRotatedBox(0,0,0,15);
    wfvr();
    copyscr(physicalscreen, virtualscreen);
    clrscreen(virtualscreen);
  }
  getch();

  /* All 3 axes :) */
  while (!kbhit())
  {
    RotateBox(angle,angle,angle++);
    DrawRotatedBox(0,0,0,15);
    wfvr();
    copyscr(physicalscreen, virtualscreen);
    clrscreen(virtualscreen);
  }
  getch();

  videomode(0x03);
}

/* sin256 and cos256 functions.  These return the sine and cosine of angles
   in a 256-degree system (just like a regular 360-degree system, only
   256 is easier to deal with for 3D coding).  So where in a conventional
   system, 90 degrees is straight up, in this system 64 degrees is straight
   up, etc.  The sin and cos functions in C deal with radians, and these
   functions just convert from radians to the 256-degree system. */

/* Note: This is an insanely unoptimized way of doing Trig functions.  Two
   very effective optimizations you can do are
	1) Make a look-up table of all 256 sin/cos values, so actually doing
	   a sin() or cos() function isn't necessary (this is a major speed
	   gain).  I would have done it here, but I didn't want to confuse
	   you any more than I already am :)
	2) Replace the floating point values with "fixed point" integer
	   values.  This makes it easy to do 3D functions in straight
	   assembler.
   */

float sin256(char angle)
{
  return sin(angle*PI/128);
}

float cos256(char angle)
{
  return cos(angle*PI/128);
}


/*

void RotatePoint(vector point, vector *dest, char rx, char ry, char rz)

This rotates the vector point in 3D by the three "theta" angles you give
in rx, ry, and rz, and puts the final rotated point in *dest.  The angles
are in the 256-degree system mentioned above.

These formulas are based on the article text, so you can see how they're
applied here. :)

*/

void RotatePoint(vector point, vector *dest, char rx, char ry, char rz)
{
  vector temp;

  /* Since we don't want to destroy the original point (if we do, the
     rounding error over time will mess it up beyond repair), we'll keep
     it intact and use the dest point for all our operations. */

  dest->x = point.x;
  dest->y = point.y;
  dest->z = point.z;

  /* X axis rotation, by 256-degree angle rx */

  temp.y = dest->y*cos256(rx) - dest->z*sin256(rx);
  temp.z = dest->z*cos256(rx) + dest->y*sin256(rx);
  dest->y = temp.y;
  dest->z = temp.z;

  /* Y axis rotation, by 256-degree angle ry */

  temp.z = dest->z*cos256(ry) - dest->x*sin256(ry);
  temp.x = dest->x*cos256(ry) + dest->z*sin256(ry);
  dest->z = temp.z;
  dest->x = temp.x;

  /* Z axis rotation, by 256-degree angle rz */

  temp.x = dest->x*cos256(rz) - dest->y*sin256(rz);
  temp.y = dest->y*cos256(rz) + dest->x*sin256(rz);
  dest->x = temp.x;
  dest->y = temp.y;

  /* We're done! :) */

}

/*

void RotateBox(char angle_x, char angle_y, char angle_z)

This just does RotatePoint on the 8 points of the Box vector array, and
puts the resulting box in RotatedBox.  RotatedBox is what's used in the
PointProject/DrawRotatedBox combo, which is the stuff we learned in the
first volume. :)

*/

void RotateBox(char angle_x, char angle_y, char angle_z)
{
  int count;

  for (count=0; count<8; count++)
  {
    RotatePoint(Box[count], &RotatedBox[count], angle_x, angle_y, angle_z);
  }
}


/*

void PointProject(int distance, vector point, vector *dest, vector center)

Same function as in volume #1, so you should be familiar with it by now...

*/


void PointProject(int distance, vector point, vector *dest, vector center)
{
  dest->scrx = (256*(point.x+center.x) / (distance-(point.z+center.z))) + 160;
  dest->scry = 100 - (256*(point.y+center.y) / (distance-(point.z+center.z)));
}

/*

void DrawRotatedBox(int cenx, int ceny, int cenz, char color)

Same as in volume #1, except it draws the RotatedBox structure, which is
created after doing the RotateBox() function.  The putpixel was also modified
to draw to any destination screen, so in this example I used a virtual screen
to reduce flicker.  The DrawRotatedBox will draw to the virtualscreen address
and the copyscr() function will move it to the actual screen after the
retrace.

*/

void DrawRotatedBox(int cenx, int ceny, int cenz, char color)
{
  int count;
  vector BoxCenter;

  BoxCenter.x = cenx;
  BoxCenter.y = ceny;
  BoxCenter.z = cenz;

/* sorry for the bad formatting below, I just wanted to keep it all on
   individual lines so everything was clear. :)   */

  for (count=0; count<8; count++)
  {
PointProject(256, RotatedBox[count], &RotatedBox[count], BoxCenter);
putpixel(RotatedBox[count].scrx, RotatedBox[count].scry, color,virtualscreen);
  }
}



