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