{$X+}
program TP_EX2;

{

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

  Sample code for Turbo Pascal 7.0 or above (might work with lesser versions,
  probably at least 6.0, but it's untested so I can't guarantee anything).

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

}

uses crt;


{ "vector" structure - holds a 3D cartesian point, and its projection. }

type
    vector = record
             x : integer;
             y : integer;
             z : integer;
             scrx : integer;
             scry : integer;
             end;

{virtual screen to reduce flicker.}

    virtualscreen_ray = array[0..63999] of byte;
    virtualscreen_ptr = ^virtualscreen_ray;

{ Box object, used for the last example as well as this one. }
var
   Box : array[0..7] of vector;  {The values are initialized later}

   RotatedBox : array[0..7] of vector;

   virtualscreen : virtualscreen_ptr;
   virseg, physeg : word; {segment addresses for screens}

{
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? :)

}

procedure videomode(mode : word); assembler;
asm
   mov ax, mode
   int 10h
end;

procedure putpixel(x, y : word; color : byte; vseg : word); assembler;
asm
   mov es, vseg
   mov ax, y
   shl ax, 6
   mov bx, ax
   shl ax, 2
   add ax, bx
   add ax, x
   mov di, ax
   mov al, color
   mov [es:di], al
end;

procedure clrscr(screenseg : word); assembler;
asm
   mov es, screenseg
   xor di, di
   xor ax, ax
   mov cx, 32000
   cld
   rep stosw
end;

procedure copyscr(destseg, srcseg : word); assembler;
asm
   push ds
   push si  {I've had problems if I don't push SI, so it's a precaution.}
   mov ds, srcseg
   mov es, destseg
   xor si, si
   xor di, di
   mov cx, 32000
   cld
   rep movsw
   pop si
   pop ds
end;

procedure wfvr; assembler;
asm
   mov dx, 3dah
   @waitr:
   in al, dx
   test al, 8
   jz @waitr
   @retrr:
   in al, dx
   test al, 8
   jnz @retrr
end;

{  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 :)  But this is probably the FIRST
           thing you want to do in your own 3D system.
	2) Replace the floating point values with "fixed point" integer
	   values.  This makes it easy to do 3D functions in straight
	   assembler.
}

function sin256(angle : byte) : real;
begin
  sin256 := sin(angle*PI/128);
end;

function cos256(angle : byte) : real;
begin
  cos256 := cos(angle*PI/128);
end;



{
procedure RotatePoint(point : vector; var dest : vector; rx, ry, rz : byte);

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. :)

}

procedure RotatePoint(point : vector; var dest : vector; rx, ry, rz : byte);
var temp1, temp2 : real;
begin

  {  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 }

  temp1 := dest.y*cos256(rx) - dest.z*sin256(rx);
  temp2 := dest.z*cos256(rx) + dest.y*sin256(rx);
  dest.y := trunc(temp1);
  dest.z := trunc(temp2);

  { Y axis rotation, by 256-degree angle ry }

  temp1 := dest.z*cos256(ry) - dest.x*sin256(ry);
  temp2 := dest.x*cos256(ry) + dest.z*sin256(ry);
  dest.z := trunc(temp1);
  dest.x := trunc(temp2);

  { Z axis rotation, by 256-degree angle rz }

  temp1 := dest.x*cos256(rz) - dest.y*sin256(rz);
  temp2 := dest.y*cos256(rz) + dest.x*sin256(rz);
  dest.x := trunc(temp1);
  dest.y := trunc(temp2);

  { We're done! :) }

end;

{
procedure RotateBox(angle_x, angle_y, angle_z : byte);

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. :)
}

procedure RotateBox(angle_x, angle_y, angle_z : byte);
var count : byte;
begin

  for count := 0 to 7 do
  begin
    RotatePoint(Box[count], RotatedBox[count], angle_x, angle_y, angle_z);
  end;
end;

{
procedure PointProject(distance : integer; point : vector,
                       var dest : vector; center : vector);

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

}

procedure PointProject(distance : integer; point : vector;
                       var dest : vector; center : vector);
begin
     dest.scrx := (256*(point.x+center.x)
                  div (distance-(point.z+center.z)))+160;

     dest.scry := 100-(256*(point.y+center.y)
                  div (distance-(point.z+center.z)));
end;

{
procedure DrawRotatedBox(cenx, ceny, cenz : integer; color : byte);

Same as in volume #1, except it draws the RotatedBox structure, which is
created after doing the RotateBox procedure.  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 procedure will move it to the actual screen after the
retrace.
}

procedure DrawRotatedBox(cenx, ceny, cenz : integer; color : byte);
var
   count : integer;
   BoxCenter : vector;
begin
     BoxCenter.x := cenx;
     BoxCenter.y := ceny;
     BoxCenter.z := cenz;

     for count := 0 to 7 do
     begin
     PointProject(256, RotatedBox[count], RotatedBox[count], BoxCenter);
     putpixel(RotatedBox[count].scrx, RotatedBox[count].scry, color, virseg);
     end;
end;


{
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.
}

var
   angle : byte;

begin

  {set up virtual screen and physical screen addresses}
  getmem(virtualscreen, 64000);
  virseg := seg(virtualscreen^);
  physeg := $A000;

  Box[0].x := 20;   Box[0].y := 40;   Box[0].z := 30;
  Box[1].x := 20;   Box[1].y := 40;   Box[1].z := -30;
  Box[2].x := 20;   Box[2].y := -40;  Box[2].z := 30;
  Box[3].x := 20;   Box[3].y := -40;  Box[3].z := -30;
  Box[4].x := -20;  Box[4].y := 40;   Box[4].z := 30;
  Box[5].x := -20;  Box[5].y := 40;   Box[5].z := -30;
  Box[6].x := -20;  Box[6].y := -40;  Box[6].z := 30;
  Box[7].x := -20;  Box[7].y := -40;  Box[7].z := -30;

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

  videomode($13);

  repeat
        RotateBox(angle, 0, 0);
        inc(angle);
        DrawRotatedBox(0,0,0,15);
        wfvr;
        copyscr(physeg, virseg);
        clrscr(virseg);
  until keypressed;
  readkey;

  repeat
        RotateBox(0, angle, 0);
        inc(angle);
        DrawRotatedBox(0,0,0,15);
        wfvr;
        copyscr(physeg, virseg);
        clrscr(virseg);
  until keypressed;
  readkey;

  repeat
        RotateBox(0, 0, angle);
        inc(angle);
        DrawRotatedBox(0,0,0,15);
        wfvr;
        copyscr(physeg, virseg);
        clrscr(virseg);
  until keypressed;
  readkey;

  repeat
        RotateBox(angle, angle, angle);
        inc(angle);
        DrawRotatedBox(0,0,0,15);
        wfvr;
        copyscr(physeg, virseg);
        clrscr(virseg);
  until keypressed;
  readkey;

  videomode(3);
  freemem(virtualscreen, 64000);
end.

