Arc3D User's Manual (first draft) Arc3D v1.0 ALPHA Copyright (C) 1991 by Andy McFadden _______________________________________________________________________________ Contents ======== 0. Installation & Legal Stuff 1. Introduction 2. Defining and Using 3D Shapes 3. Entities: Function and Purpose 4. Arc3D Function List 5. Creating a Game 6. System Internals 7. Final Notes A. Arc3D Direct Page Map _______________________________________________________________________________ 0. Installation & Legal Stuff ============================= ***** System Requirements - APW or Orca programming environment (which implies a //gs with 1MB RAM) - APW/Orca assembler or APW C (compatibility with Orca/C and Pascal unknown) ***** Installation To install Arc3D on your system, unpack the ShrinkIt archive. Execute the "Arc3D.Inst" script, which will: - Move "arc3dlib" to 2/ - Move "shconv" to 6/ - Move "view" to 6/ It is strongly recommended that you keep a copy of the original ShrinkIt archive for backup purposes. In addition, future releases of Arc3D may or may not include all of the files contained herein; instead, they may contain only the files that have been updated. Please keep this in mind when installing Arc3D. Consult the file "Release.Notes" for information about this release of Arc3D. ***** Distribution and Library License Arc3D may be distributed freely provided that all files not found in the "sample" and "shape" subdirectories are included. All files which are included, regardless of what directory they are in, must be the unmodified original versions as distributed by me. Put another way, you can drop the "sample" and "shape" directories to save space, and you can add your own files if you like, but you can't change any of the files. Legal status of programs which incorporate this library: - Public Domain or Freeware programs which say "Uses Arc3D" (or something appropriate) may be distributed without prior approval from me. If you don't make money from your program, then all you have to do is say that you're using my code. - Shareware or Commercial programs (anything you get money for) require a written agreement. Hey, I'd like to get something for my efforts... I started completely from scratch (had barely tried the APW assembler before). I probably won't ask anything for shareware, but I'm going to require a written agreement just the same. My address is in the "Final Notes" section. ***** Credit Due The line drawing routine was adapated from code written by the FTA for the Modulae demo. While I've certainly changed it enough to avoid any copyright issues, I feel that they deserve credit for the speed of the line drawing. ***** Disclaimers & Warnings (READ THIS) Andy McFadden shall have no liability for loss or damage caused, or allegedly caused, directly or indirectly, by this software. Some states do not allow the exclusion or limitation of implied warranties for incidental or consequential damages, so this limitation may not apply to you. Arc3D, in general, does very little error checking (particularly this release). This was done to get the maximum possible speed. However, it is very susceptible to crashes caused by bad data from programs which use it, and could potentially cause damage to disk files or other writable storage media (it hasn't yet, but you never know). When you include my library in your programs, you are responsible for whatever happens when you run them. You have been warned. % unsetenv PARANOID Realistically, a crash within Arc3D is probably no more dangerous than a crash within any other //gs program. However, since it does write to the $e1 bank, the possibility of overwriting something important (like the OA-Reset vector) may be slightly higher than for other programs. _______________________________________________________________________________ 1. Introduction =============== Arc3D is a collection of routines stored in a linkable library. The interface is accessible from C or assembly; Pascal is a maybe. Besides the ability to display multiple 3D shapes simultaneously, it is possible to move or rotate the viewer (so it is possible to create games like Stellar 7 or Elite, both of which were given heavy consideration during the planning stages). Basic features: - Full-color wireframe 3D rendering - Optional backface removal - Movable "eye" - Optional proportion scaling - Variety of visibility tests for quick elimination of offscreen objects - 2D clipping window - Very fast - Shapes defined with a shape definition language - 32-bit object space (x,y,z coords can vary from -2^31 to 2^31) - Rotate in 256 different positions (turn in increments of 1.41 degrees) - Interface with C or assembly as a linkable APW library The Arc3D library provides everything necessary to draw 3D shapes (which is the hard part when writing an arcade game of this type). Several examples are provided: o QuickView - a bare-bones demo. Loads a shape and displays it. There are two functionally identical versions: one in C, one in assembly. o Not Modulae - an imitation of the wireframe section of the Modulae demo. Demonstrates simple shape manipulation, zoom in/out, rotation, and a very fast way to refresh the screen. o Not Stellar 7 - a demo with the flavor of an old Apple II tank game (recently released in an updated form for the IBM PC). Demonstrates multiple simultaneous object management, and includes some useful subroutines. o View - actually the source code for the shape viewing utility. Demonstrates a broad range of entity manipulation commands, and provides a way to view shapes that you are developing. Throughout this document, I use the labels and symbols for C programs. If you want to access Arc3D from assembly, you'll have to make the appropriate translations in your head (better yet, print out "arc3d.aii" and look at the definitions). 95% of the code I've written that uses Arc3D is in C... then again, 100% of Arc3D itself is in assembly. Weird. _______________________________________________________________________________ 2. Defining and Using 3D Shapes =============================== Without shapes, a 3D graphics system is worthless. Arc3D comes with a shape definition language; a textual description of the shape is compiled (using "shconv") into a binary shape file (for details on the format of the shape files, see the "FileShape_t" struct in "arc3d.h"). Several sample shapes are provided in the "shape" subdirectory. The format of the shape description files ("=.shp") looks like: header section list of points list of faces list of decorations The exact details can be found in "shapedef.doc", but you'll get the idea after you look at a couple of shapes. The header contains an identification string (so "shconv" can distinguish shape def files from other text files), a version number (for future updates to the shape definition language), and the name of the shape, which is kept in memory and will be available to your programs. The points section is a list of points, in three dimensional coordinates. You must give each point a unique integer ID, which can be any number between 1 and 32767. They don't have to be consecutive or in order; they are used later to reference points when you are specifying faces. They are merely temporary labels ("shconv" isn't sophisticated enough to accept character labels). Suppose you want to make a cube, centered around the origin. You would create a new file, called "cube.shp", and add the header (which looks something like: ATMTDS <1> Cube It has 8 points, so you might enter: POINTS 1 -32 32 32 2 -32 -32 32 3 32 -32 32 4 32 32 32 5 -32 32 -32 6 -32 -32 -32 7 32 -32 -32 8 32 32 -32 endpoints which would give you a cube with 64 units on a side. One "unit" is roughly one pixel when the object is at close range. The maximum value for the three coordinates is +/- 16384. [ ALHPA: keep it closer to 8192... there are some problems with the visibility checks for coords that large. ] Now you have to specify how the points are joined together to form faces. You must specify one face for each face of the object, so for a cube you need six: FACES 1 -1 1 2 3 4 2 -1 8 7 6 5 3 -1 2 1 5 6 4 -1 3 7 8 4 5 -1 1 4 8 5 6 -1 2 6 7 3 endfaces For each, you need another unique ID (unique for the faces; you can re-use numbers that you used for point IDs). These IDs will be referenced by decorations (discussed later). The next number is a color value for that face; in this case "-1" was specified for all, which means that the shape color will be whatever the current color is when the shape is drawn. The order in which points are specified is the order in which they will be plotted. For face #1, Arc3D will draw a line between 1 and 2, 2 and 3, 3 and 4, and then 4 and 1 (the last and first points are always connected). If you intend to use backface removal, the order is very important, because each face is drawn or omitted based on whether the points are plotted in clockwise or ccw order. If you are looking at a top view of your object like this: positive Z negative X zero positive X negative Z (positive Y comes out of the page) then you want to specify the points in counter-clockwise order. Sound complicated? It's hard to get it right the first time. The easiest way to go about it is to make sure that you're listing the points in the same order for each face, and then run the "view" program in backface mode ('b' toggles) to see if you got it right. If not, go back and reverse the order of all the face point lists and try again. The quick and dirty backface algorithm used only works for *convex* shapes, like cubes, spheres, asteroids, etc. (note all objects in Elite are convex). Shapes like tanks are generally concave, and will not come out right. The reason for this is explained later. Keep this in mind if you intend to use BFR shapes in a game. Sometimes a face may blink on and off when the viewing angle becomes very slight. This is due to round-off errors in the computations; this can usually be alleviated by making the first angle (i.e., the angle formed by the first three points specified) the largest. Large numbers suffer less from the inaccuracies of the integer math. Arc3D has a special feature known as "decorations." The standard BFR algorithm has a problem with concave objects, because it either draws a face or it doesn't; if two faces overlap, you can "see through" them. Consider looking down on a tank: you would be able to see the top of the gun turret, and the top of the tank body, even though the tank body should be partially obscured. Decorations allow you to use a limited class of concave shapes, by using a face OTHER THAN the decoration's to determine visibility. You still can't do the tank, but you can put tail fins on missiles, or laser turrets on the front of space ships. Decorations are special lines which are attached to a given face. If the face (or faces) they are attached to are drawn, then they are drawn (note that Arc3D doesn't have to perform expensive BFR checks on them... the faces they are attached to will be computed in any event). Suppose you wanted to put tail fins on a missile. A given tail fin is visible, in general, when the missile's rear end is visible or the sides adjacent to the tail fin are visible. Thus, we want to specify that if ANY of the faces is/are visible, the decoration should be drawn. If NONE of those are visible (say, we're looking at a side profile of the other side of the shape), then the decoration should not be drawn. This sort of thing was used extensively in Elite (although I'm not sure if this is exactly how they went about it). Decorations are not simple to specify. It's a good idea to use comments (lines beginning with '#') to explain them. Here's a somewhat meaningless example, showing two different decorations: DECORATIONS # face 1 & 2, mode = 0 (AND), color=6 (Red), points 9,10 { 1 2 } 0 6 9 10 # face 1, mode = 0 (AND), color=2 (Yellow), points 11,12 { 1 } 0 2 11 12 enddecorations (Note that the decorations section is optional.) The possible modes are: 0 - AND - draw decoration if all faces are visible 1 - NAND - draw if not all of the faces are visible 2 - OR - draw if one or more are visible 3 - NOR - draw if none of the specified faces are visible It is important to keep in mind that the last and first points are NOT connected. If you want to draw a square, you have to specify FIVE points, e.g. 1 2 3 4 1. You can specify a color for each decoration, or "-1" to use whatever the current color is when the shape is drawn. There is a special feature to speed processing: if you put a '*' in the POINTS section like this: ... 2 -32 -32 32 3 32 -32 32 * 4 32 32 32 ... then point #4 (and any other points specified after the '*') will NOT be calculated unless they are needed. This is only useful for decorations; all other points will *always* be needed, because points need to be calculated during the back-face determination phase. Since the back-face removal for decorations is performed based on the status of other faces, there is no need to compute the points for decorations which are inivisible. The '*' marks all points defined after it as "compute on demand only." This feature is entirely optional, and could actually degrade performance due to increased overhead. Note that it affects points that are physically specified after it in the file; the point IDs are irrelevant. (If you don't understand any of what I just said, forget about it.) See "cobra3.shp" and "krait.shp" for examples of decorations on BFRable shapes (both of these appear in the Not Modulae demo). It's easier to understand after you look at the examples. The end of your shape file should have the word "end". Everything past that point will be ignored. Once your shape description is complete, you have to compile the shape with "shconv": shconv myshape.shp myshape.3d "shconv" will print some information, and list any errors it finds. If all is well, then it will place a binary image of the shape in "myshape.3d". This shape can then be viewed with "view": view myshape.3d Attempting to view a ".shp" file will not work. Please consult "shapedef.doc" for a shape description file specification. Note that the use of the shape definition language is NOT necessary, but it's a whole lot more convenient than entering points in binary. The source code for the "shconv" program is an abomination and hence was not included. There are two different shape structures, one for shape files and one for the shape in memory. At present, they are very much alike. The FileShape will likely change before the next release (some form of RLE compression will probably be applied to reduce the amount of file space used), but the portions of Shapes that you need to use will likely change very little. Here's a quick overview of what's in a shape. You do not have to (and generally speaking should NEVER) change any of these. They are all set by the LoadShape() function, either from the binary shape file or by performing computations on the data. Arc3D does not modify any of these after LoadShape() completes. char shape_name[NAMELEN+1]; Null-terminated ASCII name (in C string format). int shape_size; Size in bytes of entire shape. int point_count; Number of points in shape. int mpoint_count; All points past this one are decorations. int face_count; Number of faces on the shape. int decor_count; Number of decoration faces. int max_radius; Maximum possible radius of the shape. Used to determine if the shape is visible or not. Computed at runtime. It is very, very important that this value is not disturbed. long max_radius_sq; The above value, squared. Rather than compute this value every time, it's computed once and stored here. int edge_count; Number of edges in the edge_list. int outpr_count; Number of edges in out_proto. int moutpr_count; The out_proto equivalent of mpoint_count (see above). char unused[26]; Makes header 64 bytes long. Point_t points[MAXPOINTS]; List of points (1K bytes). unsigned int face_offset[MAXFACES]; Face list offset table (128 bytes). Decoration_t decoration[MAXDECOR]; The decorations (2K). Edge_t edge_list[MAXEDGES]; List of edges (computed; 2K). OutProto_t out_proto[MAXEDGES]; output_list prototype (computed once; 512 bytes). unsigned int face_data[DATASIZE]; Point lists for faces (8K). _______________________________________________________________________________ 3. Entities: Function and Purpose ================================= The basic unit of activity in Arc3D is the "entity." There is exactly one entity for every "thing" on the screen; the entity specifies position, rotation, and much more (covered in section 5). To object-oriented people, an entity is an instance of a 3D shape. Each entity has one shape, but several different entities can use the same shape. This is useful if you want to have, say, 20 cubes, because each entity can be as small as 64 bytes, while shapes are closer to 12000. Entities are manipulated with a variety of Arc3D functions. The most important is "NewEntity3D()", which creates a new entity for a given shape. [ ALPHA: at present, that's all you CAN do. Unfortunately, each call to NewEntity3D() involves a call to the NewHandle toolbox routine, so it's wise not to call it too often. The next major release of Arc3D will have more manipulation commands, and will manage a pool of memory to reduce overhead, so that entities can be created and destroyed frequently without an adverse impact on performance. Be patient, it'll happen. ] Each entity has three 4-byte parameters specifying position, and three 2-byte parameters specifying rotation. The position values (xpos, ypos, zpos) are pretty much self-explanatory; they range from 2^30 to -2^30. The rotation values (xrot, yrot, zrot) require a little explanation. [ ALPHA: only position values in the range +/- 32767 are accepted at this stage. If any point on a shape falls outside this area, the shape will not be drawn. ] Picture the object in 3-D space, with the point 0,0,0 at the center of the screen, the positive Z-axis pointed away from the viewer, the positive y-axis pointing upward, and the positive x-axis pointing to the right (just like a cartesian coordinate system, with Z going into the page). Each of the orientation values goes from 0-255, with 0 being forward orientation, 127 pointed 180 degrees back, and other values falling in between (about 1.141 degrees per value). The rotation about the Z-axis is applied first (increasing zrot is a counter-clockwise rotation). Next the Y rotation is applied; this is a flat spin (increasing yrot is counter-clockwise as you look down on the object from a point above your monitor). Finally the X rotation is applied; this causes the shape to "roll back" (increasing xrot is ccw from a point to the right of your monitor). For applications like Stellar 7 or Battlezone, only the Y rotation is used. For Arctic Fox, where you can drive up hills, you need to be able to both spin in place and angle uphill. However, this requires that the X rotation be peformed before the Y rotation: Suppose you want a side profile of a tank driving up a 45 degree incline. If you do a 90deg Y-rot followed by a 45deg X-rot, you will have a profile of a tank rolling over onto it's side. What we wanted to do is perform the 45deg X-rot so that it's angled upward, and then swivel it 90deg to the left or right. The lesson here is that the rotations are performed about axes that are fixed to the screen, NOT the shape. The xy_flag handles this; if TRUE (1), then X is performed before Y. Games like Elite are more complicated. The entities can be pointed in any direction, which makes things rather complicated. Special routines are provided which help calculate new values of X, Y, and Z: changes in orientation are either a roll (change in Z), or "climbing" or "diving". The latter require changes in both X and Y rotation, the magnitude of each depending on how the ship is positioned. [ ALPHA: "will be" provided. The code in Not Stellar 7 demonstrates the two-dimensional case; 3D is more complicated and difficult to do quickly. I'll try to get some sample code working soon. ] Here's a list of entity parameters and what they do: (note that "int"s are 16 bits and "long"s are 32. Pointers are also 32 bits. int e_id; Entity ID#, assigned by NewEntity3D(). Not used at present. long xpos, ypos, zpos; Location in space of the center of the object (assuming the center is at 0,0,0). int xrot, yrot, zrot; Rotation about those axes. From 0-255. IMPORTANT: do not specify a rotation value of 256 or more! Bad things will happen. Yes, I could check for this, but that would slow your games down. int xy_flag; Controls the order in which the rotations are applied. If FALSE, the order is Z-Y-X. If TRUE (the default), the order is Z-X-Y. int bfr_flag; Determines whether or not backface removal is to be performed on this entity. The default is FALSE (no BFR). Shape_t *shape_p; A pointer to the shape that will be drawn for this entity. Initially set by NewEntity3D(). I would advise against changing it, although at the moment you can get away with it. The next few sections are used by Compute3D(), and should not be altered by your programs (they are described for reference only): int decor_flag[2]; TRUE if decorations should be drawn. This is affected by the BFR flag, the decoration threshold, and some other factors. int visible[2]; Visibility flag; 0=undefined, 1=visible, $100=invisible, $101=partially visibile. A completely visible entity is drawn without line clipping; a partially visibile entity is drawn with clipping. If you change the "max_radius" parameter in the entity's shape, the visible status will be incorrect, and you WILL crash your system. int output_count[2]; The number of points in each output list. long viewer_dist; Distance from the viewer, squared. Used in some visibility calculations. char unused[20]; Offset to put the "output_list" arrays at 64 bytes The following fields are only present in "extended" entities: Output_t output_list_0[MAXEDGES]; Output_t output_list_1[MAXEDGES]; Two output lists, so that you can draw/erase from one list while computing a new frame in the other. The lists are 1K each. [ ALPHA: only "extended" entities are supported. You can go ahead and try to create a "standard" entity, and you won't have any problems (other than that it takes over 2K of memory instead of 64 bytes). ] One entity may be chosen as a view entity. It defines the position from which everything is viewed. For most arcade games, you want the entity representing the player's spaceship, tank, or whatever to be the view entity. However, you can make ANY entity the view entity (which allows you to some interesting things, like switching between the cockpit views of several different spaceships without incurring any performance penalty). You can also choose not to have a view entity, in which case the viewer is defined as being at (0,0,0), with rotation (0/0/0). The details of how to set the view entity are detailed below. Since you no longer have to call FormViewingTran3D(), and certain computations are simplified, deactivating the viewer can boost performance in certain applications. _______________________________________________________________________________ 4. Arc3D Function List ====================== This is the complete list of Arc3D functions, grouped by category. I show C function prototypes here; an explanation of how to issue equivalent assembly calls is provided at the end. Probably easiest to look at "sample/qv/qva.aii" for a working example. To include Arc3D in your programs, you can either include "2/arc3dlib" explicitly, or just let the linker find it. ***** General Housekeeping int StartUp3D(int MemID, int flags) This function MUST be called before ANY Arc3D function is used, or you are almost guaranteed to have a system crash. It initializes the Arc3D system, creates direct space, etc. To function properly, the following tools should be started up before calling: Tool Locator (of course) Memory Manager Allocate direct page & multiplication tables Misc Tools For other tools to use QuickDraw Uses _GrafOn and _GrafOff. Integer Math Hex -> decimal conversions for error messages Text Tools Outputs error messages See the "QuickView" program for examples in both assembly and APW C. Note that all of the tools are in ROM for both ROM 01 and ROM 03 machines. The value of "flags" determines which arithmetic routines to use. At present, the options are: SF_SLOWDIV iterative divide SF_FASTDIV table based divide [ ALPHA: not implemented ] SF_SLOWMUL iterative multiply [ ALPHA: not implemented ] SF_FASTMUL table based multiply (+128K) As you can see, the faster routines require large sections of memory. These sections are automatically allocated by Arc3D, using the memory ID supplied by your program. The flags should be ORed together. The default is SF_SLOWDIV | SF_FASTMUL. void ShutDown3D(void) Turns off the graphics screen, and cleans up. Should be called BEFORE the tools are shut down. Note that ShutDown3D() does NOT release any memory. I would rather let your program issue a single DisposeAll than have Arc3D inadvertently free up memory that your program was going to use. ***** Operations on Shapes Shape_t *LoadShape3D(char *filename) Loads the shape named by "filename" into memory. "filename" should be a C-format string, i.e. terminated with a 0 and no length byte. This returns a pointer to the shape. If an error occurs, an error message will be printed, and the routine will return a NULL pointer (four zero bytes). It is prudent to check the return value of this routine. ***** Operations on Entities Entity_t *NewEntity3D(Shape_t *shape_p, int flags) Creates a new entity. The "shape_p" field of the entity is set equal to "shape_p", and the rest of the fields are initialized. The "flags" field can be used to specify the following: ENTITY_STD create a standard entity ENTITY_XTND create an extended entity (page flip buffers, etc) ***** Parameter changes void SetClipRgn3D(int lx, int ty, int rx, int by) Sets the clipping region to the coordinates specified (left X, top Y, right X, bottom Y). Specifying (0, 0, 319, 199) would allow plotting on the entire screen (points ARE drawn on the boundary itself). DO NOT specify values off of the screen. Points plotted off of the screen will overwrite parts of memory that are better left alone. Note that Arc3D expects to draw in 320 mode. There is no provision at this time for drawing in 640 mode. void SetForeColor3D(int color) Sets the foreground color used by the Arc3D line drawing routines. "color" should be the pixel value (0-15) desired. void SetBackColor3D(int color) Sets the background color. This isn't used at present, but will be in a future release. The default is 0. void SetGrBank3D(int bank) Sets the bank # of the graphics screen. Does NOT error check the value. In general, it should be either $e1 (main SHR drawing area; the default), or $01 (for shadowed drawing). However, it is my philosophy that you should be allowed to draw in whatever bank you please, so feel free to use whatever you want. void SetCurEntity3D(Entity_t *entity_p) Sets the Current Entity. This is the entity that will be computed, drawn, or erased by Compute3D(), Draw3D(), and Erase3D() respectively. void SetCenter3D(int center_x, int center_y) Set where the center of the screen (logical 0,0) is. Defaults to the actual center of the screen, (159,99). Change this if the center of animation is not in the exact center of the screen (for example, Modulae used a window which is above center). void SetScale3D(int scale_x, int scale_y) Set which scale table to use. Note that this is NOT a scale factor; it is the number of a lookup table in memory which is used to translate logical coordinates to scaled logical coordinates. Possible values are: SCALE_1TO1 1:1, default value for Y SCALE_123TO1 1.23:1, default value for X SCALE_163TO1 1.63:1, use this for Y and 2:1 for X SCALE_2TO1 2:1, double scale SCALE_246TO1 2.46:1, use for X with 2:1 for Y SCALE_USR0 user scale table 0 SCALE_USR1 user scale table 1 The default is (1TO1, 123TO1), which multiplies all X values by 1.23. This makes square look square (it compensates for the non-squareness of the //gs screen). It also makes the animation jerk a bit, because every fourth coordinate is unused. The two user scale tables are provided for you to insert your own scale factors. There is no requirement that they be linear; you could easily create a "fish eye lens" effect, or a "bifocal zoom lens." [ ALPHA: user tables exist, but they're sort of confused right now. If you have a burning desire to use them, contact me. ] void SetThresh3D(int deco_thresh, int draw_thresh) Set the Decoration Threshold and the Draw Threshold. The thresholds work like this: if the apparent size is less than "x" pixels, don't perform a certain action. So if you set deco_thresh to 5 and draw_thresh to 2, Arc3D will not draw (or compute) any decorations once the shape is smaller than 5 pixels across, and will not draw the shape at all once it is smaller than 2 pixels. There is a slight amount of overhead involved in computing the apparent size; this can be avoided by setting both thresholds to zero. Three divisions must be performed if either or both is used. void SetLongFlags3D(int flags) This determines what Arc3D will do when a shape is either more than about 32000 units away, or is smaller than draw_thresh. The options are: LF_INVIS if too far or too small, don't draw anything LF_FAR_LONG if too far, use long-range routines LF_SMALL_DRAW if shape too small, draw it anyway LF_SMALL_22 if shape too small, draw 2x2 square (a la Elite) The default is LF_INVIS. You should pick one option for FAR and one for SMALL, and OR them together. [ ALPHA: the long-range routines aren't written. Keep all your shapes within 32000 or so, or they'll disappear no matter what you pick. ] void SetVisFlags(int flags) This determines what visibility checks will be performed. If you disable a visibility check, you MUST be certain that it doesn't exceed the range of legal values that are monitored by that check. For example, if you disable VF_COORDS, and your entity wanders more than 32767 units from the viewer, bizarre things will begin to happen because the routines were convinced that such a thing could never happen. If VF_COORDS is activated, it will switch to the long-range routines. Possible values are (OR them together): VF_NONE all flags off VF_COORDS If the center of the entity is within max_radius of 32767, the entity is either made invisible or drawn with the long-range routines (depending on the value set by LongFlags3D()). This routine is very inexpensive. VF_DIST Like VF_COORDS, only it checks the distance of the shape. Can be used to make shapes disappear at a certain distance. More expensive (requires three 32 bit multiplications), but it avoids some of the problems of VF_COORDS (blinking objects). VF_NEGZ This cuts entities that are entirely behind the viewer. Very inexpensive to compute. VF_XYDIST Determines if an entity is too far to the left or right of the viewer to be seen. The name "XYDIST" is slightly misleading; it uses the (x,y) coordinates AFTER the viewing transformation. Anyway, it involves two divides, but they are required for the threshold checks anyway (so if you're using one of the thresholds, you might as well add this, unless you're certain that your entities will always be visible). If an entity is determined to be inivisible, Compute3D() will return without further computation, and Draw3D() will likewise return without doing anything. ***** Main Routines void FormViewingTran3D(void) Forms the Viewing Transformation Matrix based on the position and rotation of the view entity. You need to call this routine before calling Compute3D() if you have altered the view entity in any way. It would probably be more convenient for you if I did this automatically, or maybe compared the previous values of the view entity with the current ones, but that takes time. Far easier for you to increment a flag when you make a change, and then branch around a call if the flag is zero. If this is too much of a hassle, just FormViewingTran() every time. It's relatively expensive, but only has to be called once for each frame of animation (NOT once for every entity!) void Compute3D(void) Determines if the Current Entity is visible, and if so, computes the two-dimensional coordinates of all the entity's points, and optionally determines which faces are invisible. This is the heart, soul, and most expensive component of Arc3D(). If the entity is a "standard" entity, the computations are stored in a global output region. If it is an "extended" entity, then the output is placed within the enitity itself. The Swap3D() call switches which output list is used. [ ALPHA: Only "extended" entities are supported. But if you just ignore that fact, they behave just like "standard" entities, except that the output is stored within the entity instead of in a global area. ] void Draw3D(void) Draws the Current Entity on the screen. void Erase3D(void) Like Draw3D(), but with an important difference. In a shape, you can specify a color for a face or decoration, or you can enter (-1) to use whatever the current color is (as set by SetForeColor3D()). For Erase3D(), ONLY the current color is used. Thus, the entire shape can be erased in black. If you desire the specified colors to remain on screen, just call Draw3D() with the current color set equal to the background (which can make for some interesting effects). void Swap3D(void) Switches the output list used. Note that this is a GLOBAL change, not local to the entity [ this may change ]. See the "view" program for an example of output list flipping. ***** Miscellaneous Routines void DrawLine3D(int x0, int y0, int x1, int y1) Draws a line in the current bank, from (x0,y0) to (x1,y1). Uses a very fast algorithm; does not use or know about QuickDraw. This routine does NOT observe the clipping window; it should be called only when you are certain that both coordinates are within (0,0) to (319,199). void ClipDrawLine3D(int x0, int y0, int x1, int y1) Like DrawLine3D(), but first clips the line to fit within the current clipping window (set by SetClipRgn3D()). Point_t CalcPoint3D(Point_t *src_p, Point_t *dst_p) Translates a single point from 3D coordinates to 2D. This is the actual routine used by Arc3D. Place the 3D point to be converted into a Point pointed to be src_p, and point dst_p at a Point which will receive the computed value. ***** Internal Routines A few very useful routines used by Arc3D are available to you. They are: int ICos(int rot) int ISin(int rot) Given a rotation value (0-255), returns its cosine or sine * 256. For example, ICos(0) returns 256, and ISin(191) returns -256. See "Not Stellar 7" for an example. ***** Using Arc3D from Assembly Language: In general, you should push arguments from right to left (i.e. backwards), and push the high word of pointer arguments before the low word. For example, to call CalcPoint3D(), you might use: pea dest_point+2 pea dest_point pea source_point+2 pea source_point jsl CalcPoint3D() ... To call LoadShape3D(), you might use: lda #^name pha lda #|name pha jsl LoadShape3D ply ply Don't forget to pop all of the arguments off of the stack. Return values are in the accumulator (for ints), or in the X register (high) and accumulator (low) for long or pointer results. See "sample/qv/qva.aii" for working examples. Since procedure call overhead reduces game speed, most of the calls that you will make during the execution of a game have zero or one arguments. For example, CurEntity3D() sets the entity to be used by Compute3D(), Draw3D(), and Erase3D(). _______________________________________________________________________________ 5. Creating a Game ================== Every game must declare the following: short *ourDirp; Viewer_t viewer; From assembly, that looks like: ourDirp ENTRY ds 4 viewer ENTRY ds 4 (If you use the supplied "arc3d.aii" startup file, then you won't have to worry about the size of "viewer" changing. If not, make sure you check the size of "viewer" in future releases.) This was done to support direct variable access from APW C (see the "arc3d.h" header file). Basically, they had to be declared in the APW C "~globals" segment for them to be easily accessible from C programs. Sorry for the inconvenience. At any rate, "ourDirp" is a pointer to the direct page used by Arc3D. If you want to directly alter Arc3D variables (as opposed to using SetXxx3D() calls), you can reference them through ourDirp. "viewer" is a structure holding information about the viewer. At present, the only field is "view_entity_p", which points to the view entity. To set the viewer entity, use "viewer.view_entity_p = entity_p" from C, or lda entity_p ldy #oVwEntity sta >viewer,y lda entity_p+2 ldy #oVwEntity+2 sta >viewer,y If you set it equal to NULL (four zero bytes), then the viewer is defined at position (0,0,0) with rotations (0/0/0), and a number of computations will be skipped. This is useful for demos like "Not Modulae", because the viewer never changes. A note on the viewer: To turn a shape to the left, you increment the Y-rotation. To make the viewer look left, you decrement the Y-rotation. This apparent contradiction is easily explained when you think of the other entities as being points on a large shape, and the viewing direction as being fixed. Twisting the "viewer entity" to the left brings "entity points" into view from the right side... thus turning left gives the appearance of turning right. This is why the view routines in Not Stellar 7 store 256 - actor[].yrot into the view entity. Generally, all you have to do in each phase is erase the screen, and call Compute3D() and Draw3D() for every active entity (assuming shadowing; if not, you should call Compute3D() on all, Erase3D() them all, call Swap3D(), and then Draw3D() all). The Arc3D routines will automatically omit any entities that are out of range of vision of the viewer, so long as the visibility flags are set appropriately (with SetVisFlags3D()). What you are responsible for is the motion of the entities, collision detection, and creation/destruction of entities. I hope to write an "entity manager" at some point, which will maintain a list of active entities, and will perform object motion and collision detection with a single call. While having these as separate libraries may degrade performance slightly, I think that the flexibility gained from being able to access all of the Arc3D routines separately and independently is worth it. [ ALPHA: I really need to write more here. I'm hoping that, at this point, a good hard look at "Not Modulae" and "Not Stellar 7" will fill in any gaps that are missing from this documentation. If you have any questions, please let me know and I'll stick a Q&A section in here, or write a few paragraphs explaining anything that doesn't make sense. Bear with me, this is a first draft! ] _______________________________________________________________________________ 6. System Internals =================== There are several data structures accessible to the user. Generally you will only have to deal with entities Most others are either for internal use or can be ignored after initialization. These structures include: Point_t Holds 4-coordinate point values (x, y, z, w). Matrix_t Holds a 4x4 matrix. Edge_t A special structure which holds a modified form of the shape. Output_t A list of lines to output. Decoration_t Holds definitions of decorations. FileShape_t Shape data from a binary shape file. Shape_t Shape data. Entity_t Entity definition. [ ALPHA: need more info here. When I get more time, I'll explain in more detail how things are done. Not terribly important, but useful. ] It should be noted that the Shape structure holds the shape in two different formats. First, the point/face hierarchy, where every face has a list of points. Since this is slow to traverse, and really only necessary for performing backface removal, a second structure called an "output prototype" is used. This is simply a list of pairs of points to be drawn. The code using the output prototype is considerably faster than that which must traverse all the faces, and has some other advantages as well. [ ALPHA: need to discuss line duplication problems ] _______________________________________________________________________________ 7. Final Notes ============== The ALPHA release contains most of the features to be found in the final version. Here's a summary of what needs to be done: - Handling of distant shapes. At present, if an entity is at a position where one of it's coordinates is different from the viewer's by more than about 32700, it will not be drawn. Future versions of Arc3D will allow the option of switching to a different set of routines which are slower but can handle larger numbers (the alternative is just not drawing the entity). - Need more/better entity management routines (FreeEntity(), CopyEntity(), etc). Possible future enhancements: - An "Entity Manager", which would automatically move and keep track of entities for you. If I can define a generalized "computer intelligence" interface (the EM would call user intelligence routines to move the entities that the computer controls, then move the entities appropriately; all the main program would have to do is loop and handle special events). If anyone would like to tackle this, let me know. Who I am & how to reach me: Andy McFadden Internet: fadden@uts.amdahl.com 1822 Amelia Way #8 fadden@cory.berkeley.edu Santa Clara, CA 95050 _______________________________________________________________________________ A. Arc3D Direct Page ==================== Arc3D uses a single page-aligned direct page for computations. Values from $00-5f are used for long-term parameter storage (detailed below). $60-6f is used by the arithmetic routines, $70-7f is used for debugging, and $80-ff is used as temporary space by the individual routines. Each entry is two bytes. I strongly recommend that you do not change these directly, as their definition may change from version to version. They are provided here for your personal edification. CX0, CX1, CY0, CY1 Unclipped coordinates. These are passed to ClipDrawLine3D(). CLPX0, CLPX1, CLPY0, CLPY1 Clipped coordinates. These are passed to DrawLine3D(). PEN_COLOR (SetPenColor3D) Current pen color; 0-15. GR_BANK (SetGrBank3D) Graphics bank; usually either $0101 or $e1e1. It's stored this way so that a PHA/PLB/PLB combination can be used. DRAW_MODE Set to 0 for erase, 1 for draw. Both Draw3D() and Erase3D() execute the same body of code; DRAW_MODE determines whether or not the color value will change from PEN_COLOR or not. CUR_ENTITY (SetCurEntity3D; 4 bytes) A pointer to the current entity. ARG0, ARG1, ARG2, ARG3 Generic arguments; used internally. PTR0, PTR1, PTR2 (4 bytes) Generic pointers; used internally. C_BANK Unused. CENTER_X, CENTER_Y (SetCenter3D) Where the center of the shape should be drawn (vanishing point). SCALE_X, SCALE_Y (SetScale3D) Scale table index for X and Y axes. FILL_MODE Unused. DECO_THRESH, DRAW_THRESH (SetThresh3D) Threshold values for decorations and shape drawing. DLX, DRX, DTY, DBY (SetClipRgn3D) Clipping window M_RESULT (4 bytes) Result of multiplication; used internally. M_OVERFLOW The multiplication routine is a little sloppy; sometimes it writes here. VIS_FLAGS (SetVisFlags3D) Visibility flags. DRAW_WHICH (Swap3D) Which output list to use (0/1). LONG_FLAGS (SetLongFlags3D) What to do if the entity is at long range. BACK_COLOR (SetBackColor3D) The current background color (0-15). Not used. dNEXT Where the next definition goes. NO_MORE Where the last definition will go. _______________________________________________________________________________