[comp.graphics] LONG 3d

js9b+@andrew.cmu.edu (Jon C. Slenk) (11/19/88)

Here follows a somewhat long description of what I have gleaned since my
request for information on 3d graphics. Please let me know of necessary
corrections / alterations / additions which would be useful.

Thanx for all your help,

Sincerely
Jon Slenk / js9b CMU <andrew.cmu.edu>
Three Dimensional Modelling on Computers. Esp. Mac (T pascal 1.0).

        On the Macintosh, one must realize that the graphics are not mapped ont
o a Cartesian coordinate system. Thus, one should describe a transformation procedure to alter the coordinates of Cartesian space (x pos is right, y pos is up,
and z pos is into the screen if considering 3d) to Macintosh space. The following procedure does just that.

        Procedure ToCartesian (VAR x,:Integer);

        begin
               x:=-x;
        end;

        Then, all one has to do is to set up everything in Cartesian coordinate
s, and call the above procedure whenever plotting is necessary.

        Rotating an object in three dimensional space can be accomplished using
 the trig functions sine and cosine. To rotate around a specific axis, one alters the coordinates of the point's other axes, ie to rotate around the z axis, one does not have to alter the z coordinate value, but the x and y values. A suitable group of procedures is listed below, and assumes that the z axis extends off
into the screen, and the x and y axes are standart Cartesian.

        Procedure RotateX (angle:Real; VAR x,y,z:Integer);

        {or just y and z, as x won't be changed}
               Var
                      ytemp:Integer;

               begin
                      ytemp:=round(y*cos(angle)+z*sin(angle));
                      z:=round(-y*sin(angle)+z*cos(angle));
                      y:=tempy;

                      {the purpose of tempy is to make sure the equation for
z is not corrupted with an incorrect y value.}

               end;
        Procedure RotateY (angle:Real; VAR x,y,z:Integer);

        {or just x and z, as y won't be changed}

               Var
                      xtemp:Integer;

               begin
                      xtemp:=round(x*cos(angle)-z*sin(angle));
                      z:=round(x*sin(angle)+z*cos(angle));
                      x:=tempx;
                      {the purpose of tempx is to make sure the equation for
z is not corrupted with an incorrect x value.}

               end;

        Procedure RotateZ (angle:Real; VAR x,y,z:Integer);

        {or just x and y, as z won't be changed}

               Var
                      xtemp:Integer;
               begin
                      xtemp:=round(x*cos(angle)+y*sin(angle));
                      y:=round(-x*sin(angle)+y*cos(angle));
                      x:=tempx;

                      {the purpose of tempx is to make sure the equation for
y is not corrupted with an incorrect x value.}

               end;

        These are the fundamental rotations. They can be concatenated with anot
her procedure which takes the points in a shape description and passes them one
at a time to be altered.

        The program should be approached in the following way. When setting up
the shape tables, the arrays or records of points defining a shape, one should do so around the origin of Cartesian space. These shape tables will form a database of basic shaped able to be drawn onto the screen.

        Type
               CubeType=Record
                      x:Array [1..8] of Integer;
                      y:Array [1..8] of Integer;
                      z:Array [1..8] of Integer;

        later on in the program...

               Var
                      BaseCube:CubeType

               begin
                      BaseCube.x[1]:=1;
                      BaseCube.y[1]:=1;
                      BaseCube.z[1]:=-1;

                      etc. using the points
                      (1,1,1) (1,1,-1) (-1,1,-1) (-1,1,1)
                      (-1,-1,1) (1,-1,1) (1,-1,-1) (-1,-1,-1)

        Once this set of shapes has been defined, we can make copies of them fo
r use in the program. We would not wish to corrupt our original data in any way, as this would force us to have a shape table for every occurance of any shape.
A better way is to set up variables of the types mentioned above:

        Var
               BaseCube,CubeOne:CubeType;

        begin
               {BaseCube definition perhaps read from disk file}
               CubeOne:=BaseCube;
        end;

        When we create this data, we must take into consideration the edges. We
 do not connect every point to every other point, nor do we connect them in order (necessaraly). Thus, when we draw the object, we much tell the computer which
points to join. I do this by creating a standard procedure for the type in question. Thus, the following might be appropriate.

        Procedure DrawCube (CubeData:CubeType);
        Var
               thecount:Integer;

        begin
               moveto (CubeData.x[1],CubeData.y[1],CubeData.z[1]);
               for thecount:=2 to 8 do
                      lineto (CubeData.x[thecount],CubeData.y[thecount],CubeD
ata.z[thecount]);
               moveto (CubeData.x[1],CubeData.y[1],CubeData.z[1]);
               lineto (CubeData.x[6],CubeData.y[6],CubeData.z[6]);

               etc, connecting 1&6, 2&7, 3&8

        Admittedly, this is cumbersome and in no way elegant. What I have done
is to use the moveto command to position myself at the first point of the cube.
Then I use the lineto command to draw from one point to the next. The way the database inside BaseCube is set up, we do not create a diagonal moving from point
4 to point 5, rather we travel down to the second or sub square of the cube. Once we have connected the points in order, we have to go back and join points 1&6, 2&7 and 3&8 to create the cube wire frame image.
        Note that the commands moveto and lineto contain the all-important conv
ersion from three dimentional coordinates to the two screen coordinates. They either move the pen to the point (x,y,z) units away/offset from the present point, or draw a line between the current point and (x,y,z). Lets work them out below.
        Procedure moveto (x,y,z:Integer);

        Var
               screenx,screeny:Integer;

        begin
               screenx:=((x/(z+focallength))*focallength);
               screeny:=((y/(z+focallength))*focallength);
               setpen (screenx,screeny);
        end;
        It is important to note that the Macintosh uses the QuickDraw routines
MoveTo and LineTo in its 2d drawings. Thus, one would have to change the names of the procedures mentioned above, and would alter setpen to the MoveTo command.
        The idea is to convert using similar triangles. We use the equations ab
ove to find x and y, which are then plotted directly onto the screen. The focal
length determines how far back the observer is from the screen. I will attempt a text diagram below.

Fig. 1

                 --|
               --  |
             --    |
           --      |
         --|       |
       --  |       |
     --    |       |
   --      |       |
 --        |       |
--------------------

        The upper right hand point or vertex of the large triangle represents t
he point in three dimentional space. The upper right hand point or vertex of the small triangle represents the point to be plotted on the screen. It is determined by drawing a line from the eyepoint to the 3d point in question. The eyepoint or eye origin is the left hand point of the two triangles. The origin of the entire system is at the bottom right hand point of the smaller triangle. Thus, the eyepoint is in negative z space. One can see from the diagram that as we shorten the focal length (dist betwee




n the screen and the screen), we allow a projection to see more of the world. This is a sort of wide angle appearance, as more of the lines drawn between the 3d points and the eye point intersect the screen, where they can be drawn.
        Since the triangles are similar, we can calculate the height of the sma
ller triangle from the known height of the taller one. We take the height of the larger triangle (y) and put it over the base of that triangle (z+abs(eyez)). This gives us the equation

    y            scrny
--------    :    ------
z+|eyez|         |eyez|

and from this we can calculate

scrny = y/z+abs(eyez) * |eyez|.

The same sort of method is used to calculate the screen x coordinate. (NB: |x| denotes absolute value of x above)
        Well, I think that everything of importance has been covered.

----

Notes and questions:

Does the program have to convert to eyecoordinates first?

Can we create a procedure set which will join given points according to edge data (and would it be useful / quick)? I believe it would be relativly easy (use a
counter and then pass the relevant end points to the line routine).
What is missing / invalid in the above?

Thanks,
Jon Slenk / js9b CMU.

stergios@athsys.uucp (Stergios Marinopoulos) (11/22/88)

In  article  <kXVEuU588k-0AnjWdT@andrew.cmu.edu>  js9b+@andrew.cmu.edu
(Jon C. Slenk) writes:  

>On the  Macintosh, one must realize  that the graphics  are not mapped
>onto  a Cartesian  coordinate  system.   Thus,  one  should describe a
>transformation procedure to  alter the  coordinates of Cartesian space
>(x pos is  right, y  pos  is up,  and  z  pos is  into the  screen  if
>considering 3d) to Macintosh space.


I think the right hand rule gives z positive as out of the screen