/* "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 #include #include #include #include #include #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); } }