3DOMS.INC

Assembly source for MS-DOS 3D engine, 3305 lines. Raw file

;       LKS 3-Dimensional Object Management System (3DOMS) version 0.15
;       3DOMS.INC - <c> LKS   Created: 22/Dec/1994   Now: 12/Jan/1995.



;----------------------------- Documentation --------------------------------;

comment |


  The logon and logoff routines must be called at the start and end of the
host program respectively.  Everything else is done through INT 3Dh, with the
function number in AH.  If a function can't do what it was asked, it returns
with the carry flag set and an error code in AX, and nothing in the scene has
been changed.


;-------- Structure.

  Point <- Face <- Shape <- FORM <- Object <- Object ... <- UNIVERSE

  Everything lives on a heap of 32-byte cells, one structure to a cell, and
structures are passed around as the segments of their cells.

  All co-ordinates are double words with 8 bits after the point, so 100h is
1.0, and they must be between -32768.0 and 32768.0.  Angles are bytes, with
256 to a full turn.

POINTS

  Points are managed behind the scenes.  A point shared between many faces
has all its calculations done once per form, not once per face.  When a face
is created, each of its corners is looked up in its form's list of points,
and a new point is only made if it isn't there already.

FACES

  Faces are the most basic pieces of a 3-dimensional thing. To avoid any
programming difficulties on my part and to make rendering as fast as possible,
all faces are triangular.  If higher polygons are required, they must be made
from more than one face.
  Seen from the front, the corners of a face go anticlockwise.  Faces seen
from the back are not drawn.

SHAPES

  Shapes are the basic building blocks of complicated objects.  A shape must
be totally convex otherwise rendering will be inaccurate.  If you need to
make a convex object, it must consist of more than one shape.  For example,
if you want to make a plane, you may have each wing as one shape, the body as
another and the tail as another.
  Center co-ordinates are first used in shapes.  These are used to tell 3DOMS
which order to put shapes on the screen if they overlap.  It starts by drawing
the one with a center furthest from the viewer and works forward.  The
programmer has to specify the center co-ordinates themselves - they should be
centralised and 'inside' the shape if possible.

FORMS

  Shapes and faces are constants - if you want to have more than one plane
flying around, as long as all the planes look the same, they can all use the
same shapes and faces.  A form is basically a descriptor for a linked list of
shapes, and it keeps the list of points its faces use.  When you create a new
object, you give it a form, which will be used when the object is drawn.  A
form can't be deleted while any object is using it.

OBJECTS

  Collections of shapes of a particular form.  All the planes would be
objects.
  Objects can also hold other objects, which move and turn with them.  A
plane may be an object with the body as its form, holding some wheel and flap
objects, as the wheels and flaps are not fixed in position on the plane.
  An object's own form and the objects inside it are drawn starting with the
one with a center furthest from the viewer, the same as shapes.  The form
goes by its own center, and each object by its center.  Objects can be put
inside each other 8 deep.
  Things are only ever sorted against the others in the same object.  An
object is drawn all in one go, with everything inside it, at the place its
own center puts it among the things next to it.  So parts that can come in
front of each other should be in the same object.

CENTERS

  Two centers are the same distance from the viewer anywhere on the plane
halfway between them, so which side of that plane the viewer is on decides
which is drawn first.  If two shapes or objects touch along a flat side, put
their centers the same distance either side of it, and they will always be
drawn the right way round.  Centers can be moved with Function 12h.

UNIVERSE

  There is one universe, made when you log on.  It holds the objects at the
top of the scene, and it has a position and angles like an object, which
place the whole scene in front of the camera.

CAMERA

  The camera is at the origin, looking along the z axis, with x to the right
and y down the screen.  Nothing is moved in front of it for you: give the
universe, or an object, a position that puts things where they can be seen.
At a distance of 128, one unit across is one pixel on the screen.
  A point can't be drawn if it is less than 1.0 in front of the camera, or if
it is more than 16 times as far out to the side, up or down as it is in front.
Faces with a corner like that are left out.

TEXTURES

  When I have perfected the basic rendering, I will work on texturing methods
including bitmapping, RGB colours (using dithering and an orthogonal palette),
and reflectiveness and surface normal smooth shading (for fast computers
only!).  Other graphical effects which I hope to include to add realism to
scenes are light source shading and distance fading.



;-------- Logon_3DOMS

  Inputs: BX - Smallest heap that will do, in cells (at least 16).
	  DX - Largest heap wanted, in cells (at most 7FFFh).

  Returns: Carry clear - Success
	     AX - Segment of heap, which is also the universe.
	     CX - Size of heap in cells.
	   Carry set - Error
	     AX - Error code.
	     CX - Most cells DOS could give, if the error is ERROR_HEAPOUT.

  Allocates the heap, makes the universe and points INT 3Dh at 3DOMS.  This is
a normal near call, not an interrupt function.



;-------- Logoff_3DOMS

  Inputs: (none)

  Returns: Carry set if 3DOMS wasn't logged on.

  Frees the heap and puts the old INT 3Dh vector back.



;-------- Function 00h - Get Version And Run-Time Info

  Inputs: AH - 00h

  Returns: AH - Major version number.
	   AL - Minor version number.
	   BX - Segment of heap.
	   CX - Size of heap in 32-byte cells.
	   DX - Number of 32-byte cells free.

  This function returns information about the status of LKS 3DOMS.



;-------- Function 10h - Move

  Inputs: AH - 10h
	  DS:SI - Pointer to position and angles:
	    3 double word co-ordinates, 3 rotation bytes and one rotation
	    order byte - 16 bytes in all.
	  ES - Segment of object or universe.

  Returns: Carry clear - Success
	   Carry set - Error
	     AX - Error code.

  Changes the position and angles of an object, or of the universe.



;-------- Function 11h - Change Object Form

  Inputs: AH - 11h
	  BX - Segment of new form, or 0 to take the form away.
	  ES - Segment of object.

  Returns: Carry clear - Success
	     AX - Segment of old form, or 0 if it had none.
	   Carry set - Error
	     AX - Error code.

  Changes the form an object is drawn with.



;-------- Function 12h - Change Center

  Inputs: AH - 12h
	  DS:SI - Pointer to new center:
	    3 double word co-ordinates.
	  ES - Segment of object, form or shape.

  Returns: Carry clear - Success
	   Carry set - Error
	     AX - Error code.

  Moves the center that an object, form or shape is sorted by.



;-------- Function 18h - Delete Object

  Inputs: AH - 18h
	  ES - Segment of object.

  Returns: Carry clear - Success
	     AX - Segment of the object or universe it was in.
	   Carry set - Error
	     AX - Error code.

  Takes an object out of the scene and frees its cells.  It mustn't have any
objects inside it.



;-------- Function 19h - Delete Form

  Inputs: AH - 19h
	  ES - Segment of form.

  Returns: Carry clear - Success
	   Carry set - Error
	     AX - Error code.

  Frees a form and all its shapes, faces and points.  No object may be using
it.



;-------- Function 1Bh - Create Object

  Inputs: AH - 1Bh
	  DS:SI - Pointer to object data:
	    3 double word co-ordinates for center of object.
	  ES - Segment of the object or universe to put it in.

  Returns: Carry clear - Success
	     AX - Segment of created object.
	   Carry set - Error
	     AX - Error code.

  Creates a new object, at the origin of the one it's in and not turned, and
with no form.



;-------- Function 1Ch - Create Form

  Inputs: AH - 1Ch
	  DS:SI - Pointer to form data:
	    3 double word co-ordinates for center of form.

  Returns: Carry clear - Success
	     AX - Segment of created form.
	   Carry set - Error
	     AX - Error code.

  Creates a new, empty form.



;-------- Function 1Dh - Create Shape

  Inputs: AH - 1Dh
	  DS:SI - Pointer to shape data:
	    3 double word co-ordinates for center of shape.
	  ES - Segment of parent form.

  Returns: Carry clear - Success
	     AX - Segment of created shape.
	   Carry set - Error
	     AX - Error code.

  Creates a new shape in the form specified.



;-------- Function 1Eh - Create Face

  Inputs: AH - 1Eh
	  AL - Colour (palette index).
	  DS:SI - Pointer to face data:
	    9 double word co-ordinates, the x, y and z of each corner - 36
	    bytes in all.
	  ES - Segment of parent shape.

  Returns: Carry clear - Success
	     AX - Segment of created face.
	   Carry set - Error
	     AX - Error code.

  Creates a new face in the shape specified, sharing any corners that are
already points in the form.



;-------- Function 20h - Render

  Inputs: AH - 20h
	  DS - Segment of universe.
	  ES - Segment of display buffer (this may be 0A000h or any other
	       64k buffer which will be overlayed with a 320x200 bitmap).

  Returns: Carry clear - Success
	   Carry set - Error
	     AX - Error code.

  Draws the whole scene in a 320x200x256 bitmap.  It is usually desirable to
draw the screen into a buffer or a hidden screen, then display it so that the
user cannot see the polygons being drawn.  The buffer isn't cleared first.



;-------- Function 2Ch - Triangle

  Inputs: AH - 2Ch
	  AL - Palette index.
	  BX, CX - x and y of first corner.
	  SI, BP - x and y of second corner.
	  DX, DI - x and y of third corner.
	  ES - Segment of output bitmap.

  Returns: Carry set if ES overlaps the heap or 3DOMS.

  Draws a filled triangle.  Anything off the screen is left out.



;-------- Function 2Dh - Line

  Inputs: AH - 2Dh
	  AL - Palette index.
	  BX, CX - x and y of start.
	  SI, BP - x and y of end.
	  ES - Segment of output bitmap.

  Returns: Carry set if either end is off the screen, or ES overlaps the
	   heap or 3DOMS.

  Draws a line, including both ends.

|



int3D_first	label byte



;----------------------------- Constants ------------------------------------;



;-------- Errors.

ERROR_HEAPOUT		equ -1	;  Not enough free cells, or not enough memory.
ERROR_BADFACE		equ -2	;  A segment isn't the structure that was
ERROR_BADSHAPE		equ -3	; wanted.
ERROR_BADFORM		equ -4
ERROR_BADOBJECT		equ -5
ERROR_BADPOINT		equ -6
ERROR_BADUNIVERSE	equ -7

ERROR_BADPARENT		equ -8	;  The parent isn't an object or the universe.

ERROR_TOOMANY		equ -16	;  The parent already holds as many as it can.
ERROR_TOODEEP		equ -17	;  Objects can't be put inside each other
				; any deeper.
ERROR_HASCHILDREN	equ -18	;  The object still has objects in it.
ERROR_INUSE		equ -19	;  The form is still used by an object.

ERROR_BADLIST		equ -32	;  A linked list doesn't match its count.

ERROR_BADBUFFER		equ -48	;  The data at DS:SI runs off the end of the
				; segment.
ERROR_BADOUTPUT		equ -49	;  The output buffer overlaps the heap or
				; 3DOMS.
ERROR_BADCOORD		equ -50	;  A co-ordinate is out of range.
ERROR_BADORDER		equ -51	;  Not one of the six rotation orders.
ERROR_OFFSCREEN		equ -52	;  A line doesn't fit on the screen.

ERROR_BADSIZE		equ -64	;  The heap sizes asked for don't make sense.
ERROR_LOGGEDON		equ -65	;  3DOMS is already logged on.
ERROR_NOTLOGGEDON	equ -66	;  3DOMS isn't logged on.

ERROR_BADFUNCTION	equ -99	;  There's no function with that number.



;-------- Rotation Order constants.

RORDER_XYZ	equ 00000000b
RORDER_XZY	equ 00000100b
RORDER_YXZ	equ 00000001b
RORDER_YZX	equ 00000101b
RORDER_ZXY	equ 00000010b
RORDER_ZYX	equ 00000110b



;-------- Magics.

MAGIC_FREE      equ 00h
MAGIC_POINT	equ 'P'
MAGIC_FACE      equ 'F'
MAGIC_SHAPE     equ 'S'
MAGIC_FORM      equ 'M'
MAGIC_OBJECT    equ 'O'
MAGIC_UNIVERSE	equ 'U'
MAGIC_TAKEN	equ 0FFh	;  Taken, but not filled in yet.



;-------- Common offsets.

;  Offsets which are common to all structures.  All structures take up 32
; bytes (2 paragraphs) - one cell.

_MAGIC		equ 0000h	;  (B) Magic - indicates type of structure.
_NUM		equ 0001h	;  (B) Number of child structures.
_PTR		equ 0002h	;  (W) Segment of first child in linked list.
_NEXT		equ 0004h	;  (W) Segment of next structure in list, or 0.
_PARENT		equ 0006h	;  (W) Segment of parent structure.
_CENTRE		equ 0008h	;  (W) Segment of center point.



;-------- Points.

P_MAGIC		equ 0000h	;  (B) Magic byte.
P_OFF		equ 0001h	;  (B) 1 if it couldn't be put on the screen.
P_NEXT		equ 0004h	;  (W) Segment of next point in form, or 0.
P_PARENT	equ 0006h	;  (W) Segment of form, shape or object.
P_X		equ 000Ch	;  (D) Co-ordinates.
P_Y		equ 0010h
P_Z		equ 0014h
P_SX		equ 0018h	;  (W) Where it went on the screen.
P_SY		equ 001Ah



;-------- Faces.

F_MAGIC		equ 0000h	;  (B) Magic byte.
F_NEXT		equ 0004h	;  (W) Segment of next face, or 0 if none.
F_PARENT	equ 0006h	;  (W) Segment of parent shape.
F_COL		equ 0008h	;  (B) Colour of face (palette index).
F_P1		equ 000Ah	;  (W) Segments of corner points.
F_P2		equ 000Ch
F_P3		equ 000Eh



;-------- Shapes.

S_MAGIC		equ 0000h	;  (B) Magic byte.
S_NUM		equ 0001h	;  (B) Number of faces.
S_PTR		equ 0002h	;  (W) Segment of first face in linked list.
S_NEXT		equ 0004h	;  (W) Segment of next shape, or 0 if none.
S_PARENT	equ 0006h	;  (W) Segment of parent form.
S_CENTRE	equ 0008h	;  (W) Segment of center point.



;-------- Forms.

M_MAGIC		equ 0000h	;  (B) Magic byte.
M_NUM		equ 0001h	;  (B) Number of shapes in form.
M_PTR		equ 0002h	;  (W) Segment of first shape in linked list.
M_CENTRE	equ 0008h	;  (W) Segment of center point.
M_NUMPTS	equ 000Ah	;  (B) Number of points in form.
M_PTS		equ 000Bh	;  (W) Segment of first point in linked list.
M_USERS		equ 000Dh	;  (W) Number of objects which use this form.



;-------- Objects.

O_MAGIC		equ 0000h	;  (B) Magic byte.
O_NUM		equ 0001h	;  (B) Number of objects inside it.
O_PTR		equ 0002h	;  (W) Segment of first object in linked list.
O_NEXT		equ 0004h	;  (W) Segment of next object, or 0 if none.
O_PARENT	equ 0006h	;  (W) Segment of object or universe it's in.
O_CENTRE	equ 0008h	;  (W) Segment of center point.
O_FORM		equ 000Ah	;  (W) Segment of form, or 0 if none.
O_LEVEL		equ 000Ch	;  (B) How deep it is - 1 if in the universe.
O_X		equ 0010h	;  (D) Transformations.
O_Y		equ 0014h
O_Z		equ 0018h
O_RX		equ 001Ch	;  (B) Rotation about the axes.
O_RY		equ 001Dh
O_RZ		equ 001Eh
O_RORDER	equ 001Fh	;  (B) Order of rotations.



;-------- Universe.

;  The universe is laid out like an object, with no parent, center or form,
; and a level of 0.

U_MAGIC		equ 0000h	;  (B) Magic byte.
U_NUM		equ 0001h	;  (B) Number of objects in it.
U_PTR		equ 0002h	;  (W) Segment of first object in linked list.
U_X		equ 0010h	;  (D) Transformations.
U_Y		equ 0014h
U_Z		equ 0018h
U_RX		equ 001Ch	;  (B) Rotation about the axes.
U_RY		equ 001Dh
U_RZ		equ 001Eh
U_RORDER	equ 001Fh	;  (B) Order of rotations.



;-------- Limits.

MAX_CHILDREN	equ 07Fh	;  Most objects inside an object.
MAX_DEPTH	equ 8		;  Deepest objects can be inside each other.
MAX_SHAPES	equ 07Fh	;  Most shapes in a form.
MAX_FACES	equ 07Fh	;  Most faces in a shape.
MAX_POINTS	equ 0FFh	;  Most points in a form.
MAX_COORD	equ 007FFFFFh	;  Biggest co-ordinate, just under 32768.0.

MIN_CELLS	equ 16		;  Smallest heap.
MAX_CELLS	equ 7FFFh	;  Biggest heap.



;-------- Camera.

SCR2EYE		equ 00008000h	;  Screen to eye distance, 128.0.
NEAR_Z		equ 00000100h	;  Nearest a point can be to be drawn, 1.0.
VIEW_CX		equ 160		;  Center of the screen.
VIEW_CY		equ 100



;-------- Rendering.

;  The transformation stack has one level for the universe and each object
; being drawn, and one more for an object whose center is being measured.

TSTACK_LEVELS	equ MAX_DEPTH+2

;  Each entry in the draw list is the distance squared (a quad word) and the
; segment of what to draw.  The longest the list can get is all the objects
; and forms in every object down to the deepest, and the shapes of one form.

SORT_LO		equ 0000h	;  (D) Distance squared, low double word.
SORT_HI		equ 0004h	;  (D) Distance squared, high double word.
SORT_SEG	equ 0008h	;  (W) Segment, or 0 when it's been drawn.
SORT_SIZE	equ 10
SORT_ENTRIES	equ (MAX_DEPTH+1)*(MAX_CHILDREN+1)+MAX_SHAPES

STACK_SIZE	equ 2048	;  Stack used while rendering.



;----------------------------- 3DOMS Data Area ------------------------------;



_3doms_loggedon		db 0	;  1 when logged on.

  ;  Storage for old INT 3Dh vector.
_3doms_int3Dseg         dw 0000h
_3doms_int3Doff         dw 0000h

  ;  Requested min and max size of heap on call to logon routine.
_3doms_minheap          dw 0000h
_3doms_maxheap          dw 0000h

  ;  Heap descriptors.
_3doms_heapseg          dw 0000h        ;  Segment of allocated memory.
_3doms_heapsize         dw 0000h        ;  Size in 32-byte cells of heap.
_3doms_heapfree         dw 0000h        ;  Number of free 32-byte cells.
_3doms_heapend		dw 0000h	;  First segment after the heap.

  ;  Segment of universe.
_3doms_universe		dw 0000h

  ;  Co-ordinates read in by the create functions.
_3doms_xyz		dd 9 dup (0)

  ;  Rendering.
_3doms_callss		dw 0000h	;  Caller's stack.
_3doms_callsp		dw 0000h
_3doms_failsp		dw 0000h	;  Stack to go back to on an error.
_3doms_tdepth		dw 0000h	;  Levels on the transformation stack.
_3doms_sortptr		dw 0000h	;  End of the draw list.

  ;  Triangle being drawn.
_tri_x1			dw 0000h
_tri_y1			dw 0000h
_tri_x2			dw 0000h
_tri_y2			dw 0000h
_tri_x3			dw 0000h
_tri_y3			dw 0000h
_tri_col		db 00h
_tri_y			dw 0000h	;  Row being drawn.
_tri_yend		dw 0000h	;  Last row to draw.

  ;  Line being drawn.
_line_len		dw 0000h



;----------------------------- Lookup Tables --------------------------------;



  ;  Sine and cosine table - 256 double word entries for each, but the tables
  ; overlap quite substantially.
_3doms_sin	dd 000000000h, 000000006h, 00000000Ch, 000000012h  ;  0 deg
		dd 000000019h, 00000001Fh, 000000025h, 00000002Bh
		dd 000000032h, 000000038h, 00000003Eh, 000000044h
		dd 00000004Ah, 000000050h, 000000056h, 00000005Ch
		dd 000000062h, 000000067h, 00000006Dh, 000000073h
		dd 000000078h, 00000007Eh, 000000083h, 000000089h
		dd 00000008Eh, 000000093h, 000000098h, 00000009Dh
		dd 0000000A2h, 0000000A7h, 0000000ACh, 0000000B0h
		dd 0000000B5h, 0000000B9h, 0000000BDh, 0000000C2h
		dd 0000000C6h, 0000000CAh, 0000000CDh, 0000000D1h
		dd 0000000D5h, 0000000D8h, 0000000DBh, 0000000DFh
		dd 0000000E2h, 0000000E4h, 0000000E7h, 0000000EAh
		dd 0000000ECh, 0000000EFh, 0000000F1h, 0000000F3h
		dd 0000000F5h, 0000000F7h, 0000000F8h, 0000000FAh
		dd 0000000FBh, 0000000FCh, 0000000FDh, 0000000FEh
		dd 0000000FFh, 0000000FFh, 0000000FFh, 000000100h
_3doms_cos	dd 000000100h, 000000100h, 0000000FFh, 0000000FFh  ;  90 deg
		dd 0000000FFh, 0000000FEh, 0000000FDh, 0000000FCh
		dd 0000000FBh, 0000000FAh, 0000000F8h, 0000000F7h
		dd 0000000F5h, 0000000F3h, 0000000F1h, 0000000EFh
		dd 0000000ECh, 0000000EAh, 0000000E7h, 0000000E4h
		dd 0000000E2h, 0000000DFh, 0000000DBh, 0000000D8h
		dd 0000000D5h, 0000000D1h, 0000000CDh, 0000000CAh
		dd 0000000C6h, 0000000C2h, 0000000BDh, 0000000B9h
		dd 0000000B5h, 0000000B0h, 0000000ACh, 0000000A7h
		dd 0000000A2h, 00000009Dh, 000000098h, 000000093h
		dd 00000008Eh, 000000089h, 000000083h, 00000007Eh
		dd 000000078h, 000000073h, 00000006Dh, 000000067h
		dd 000000062h, 00000005Ch, 000000056h, 000000050h
		dd 00000004Ah, 000000044h, 00000003Eh, 000000038h
		dd 000000032h, 00000002Bh, 000000025h, 00000001Fh
		dd 000000019h, 000000012h, 00000000Ch, 000000006h
		dd 000000000h, 0FFFFFFFAh, 0FFFFFFF4h, 0FFFFFFEEh  ;  180 deg
		dd 0FFFFFFE7h, 0FFFFFFE1h, 0FFFFFFDBh, 0FFFFFFD5h
		dd 0FFFFFFCEh, 0FFFFFFC8h, 0FFFFFFC2h, 0FFFFFFBCh
		dd 0FFFFFFB6h, 0FFFFFFB0h, 0FFFFFFAAh, 0FFFFFFA4h
		dd 0FFFFFF9Eh, 0FFFFFF99h, 0FFFFFF93h, 0FFFFFF8Dh
		dd 0FFFFFF88h, 0FFFFFF82h, 0FFFFFF7Dh, 0FFFFFF77h
		dd 0FFFFFF72h, 0FFFFFF6Dh, 0FFFFFF68h, 0FFFFFF63h
		dd 0FFFFFF5Eh, 0FFFFFF59h, 0FFFFFF54h, 0FFFFFF50h
		dd 0FFFFFF4Bh, 0FFFFFF47h, 0FFFFFF43h, 0FFFFFF3Eh
		dd 0FFFFFF3Ah, 0FFFFFF36h, 0FFFFFF33h, 0FFFFFF2Fh
		dd 0FFFFFF2Bh, 0FFFFFF28h, 0FFFFFF25h, 0FFFFFF21h
		dd 0FFFFFF1Eh, 0FFFFFF1Ch, 0FFFFFF19h, 0FFFFFF16h
		dd 0FFFFFF14h, 0FFFFFF11h, 0FFFFFF0Fh, 0FFFFFF0Dh
		dd 0FFFFFF0Bh, 0FFFFFF09h, 0FFFFFF08h, 0FFFFFF06h
		dd 0FFFFFF05h, 0FFFFFF04h, 0FFFFFF03h, 0FFFFFF02h
		dd 0FFFFFF01h, 0FFFFFF01h, 0FFFFFF01h, 0FFFFFF00h
		dd 0FFFFFF00h, 0FFFFFF00h, 0FFFFFF01h, 0FFFFFF01h  ;  270 deg
		dd 0FFFFFF01h, 0FFFFFF02h, 0FFFFFF03h, 0FFFFFF04h
		dd 0FFFFFF05h, 0FFFFFF06h, 0FFFFFF08h, 0FFFFFF09h
		dd 0FFFFFF0Bh, 0FFFFFF0Dh, 0FFFFFF0Fh, 0FFFFFF11h
		dd 0FFFFFF14h, 0FFFFFF16h, 0FFFFFF19h, 0FFFFFF1Ch
		dd 0FFFFFF1Eh, 0FFFFFF21h, 0FFFFFF25h, 0FFFFFF28h
		dd 0FFFFFF2Bh, 0FFFFFF2Fh, 0FFFFFF33h, 0FFFFFF36h
		dd 0FFFFFF3Ah, 0FFFFFF3Eh, 0FFFFFF43h, 0FFFFFF47h
		dd 0FFFFFF4Bh, 0FFFFFF50h, 0FFFFFF54h, 0FFFFFF59h
		dd 0FFFFFF5Eh, 0FFFFFF63h, 0FFFFFF68h, 0FFFFFF6Dh
		dd 0FFFFFF72h, 0FFFFFF77h, 0FFFFFF7Dh, 0FFFFFF82h
		dd 0FFFFFF88h, 0FFFFFF8Dh, 0FFFFFF93h, 0FFFFFF99h
		dd 0FFFFFF9Eh, 0FFFFFFA4h, 0FFFFFFAAh, 0FFFFFFB0h
		dd 0FFFFFFB6h, 0FFFFFFBCh, 0FFFFFFC2h, 0FFFFFFC8h
		dd 0FFFFFFCEh, 0FFFFFFD5h, 0FFFFFFDBh, 0FFFFFFE1h
		dd 0FFFFFFE7h, 0FFFFFFEEh, 0FFFFFFF4h, 0FFFFFFFAh
		dd 000000000h, 000000006h, 00000000Ch, 000000012h  ;  360 deg
		dd 000000019h, 00000001Fh, 000000025h, 00000002Bh
		dd 000000032h, 000000038h, 00000003Eh, 000000044h
		dd 00000004Ah, 000000050h, 000000056h, 00000005Ch
		dd 000000062h, 000000067h, 00000006Dh, 000000073h
		dd 000000078h, 00000007Eh, 000000083h, 000000089h
		dd 00000008Eh, 000000093h, 000000098h, 00000009Dh
		dd 0000000A2h, 0000000A7h, 0000000ACh, 0000000B0h
		dd 0000000B5h, 0000000B9h, 0000000BDh, 0000000C2h
		dd 0000000C6h, 0000000CAh, 0000000CDh, 0000000D1h
		dd 0000000D5h, 0000000D8h, 0000000DBh, 0000000DFh
		dd 0000000E2h, 0000000E4h, 0000000E7h, 0000000EAh
		dd 0000000ECh, 0000000EFh, 0000000F1h, 0000000F3h
		dd 0000000F5h, 0000000F7h, 0000000F8h, 0000000FAh
		dd 0000000FBh, 0000000FCh, 0000000FDh, 0000000FEh
		dd 0000000FFh, 0000000FFh, 0000000FFh, 000000100h



;----------------------------- Initialisation Code --------------------------;



;-------- Logon_3DOMS

;  Sets up the heap, the universe and the interrupt vector.

;  Inputs:
;       BX - Min cells to allocate.
;       DX - Max cells to allocate.

;  Returns:
;       Carry clear:
;         AX - Segment of heap.
;         CX - Size of heap in cells.
;       Carry set:
;         AX - Error code.
;         CX - Cells available, if the error is ERROR_HEAPOUT.

logon_3doms:

    ;  Only log on once.
  cmp _3doms_loggedon, 0
  je logon_3doms_notyet
  mov ax, ERROR_LOGGEDON
  stc
  ret
logon_3doms_notyet:

    ;  Make sure the sizes make sense.
  cmp bx, MIN_CELLS
  jb logon_3doms_badsize
  cmp dx, MAX_CELLS
  ja logon_3doms_badsize
  cmp bx, dx
  jbe logon_3doms_sizeok
logon_3doms_badsize:
  mov ax, ERROR_BADSIZE
  stc
  ret
logon_3doms_sizeok:

  push bx
  push dx
  push di
  push ds
  push es

    ;  Allocate memory.
    ;  Save parameters.
  mov _3doms_minheap, bx
  mov _3doms_maxheap, dx
    ;  First try the maximum size requested.  A cell is two paragraphs.
  mov ah, 48h
  mov bx, dx
  shl bx, 1
  int 21h
    ;  Was that allocation successful?
  jnc logon_3doms_memok
    ;  If not that much memory free, BX says how much there is.  See if the
    ; minimum can be allocated.
  shr bx, 1
  cmp bx, _3doms_minheap
  jb logon_3doms_nomem
    ;  Allocate all that can be allocated.
  shl bx, 1
  mov ah, 48h
  int 21h
  jnc logon_3doms_memok
  shr bx, 1
logon_3doms_nomem:
    ;  There is not enough memory to satisfy the minimum memory requirements.
  mov cx, bx
  mov ax, ERROR_HEAPOUT
  pop es
  pop ds
  pop di
  pop dx
  pop bx
  stc
  ret

logon_3doms_memok:
  mov _3doms_heapseg, ax
  mov _3doms_universe, ax
  add ax, bx
  mov _3doms_heapend, ax
  shr bx, 1
  mov _3doms_heapsize, bx
  mov _3doms_heapfree, bx

    ;  Set all cells on heap to free.
  mov ax, _3doms_heapseg
  mov cx, bx
logon_3doms_freeheap:
  mov ds, ax
  mov byte ptr ds: [_MAGIC], MAGIC_FREE
  add ax, 2
  loop logon_3doms_freeheap

    ;  Set up universe structure in the first cell, at the origin and not
    ; turned.
  mov es, _3doms_heapseg
  xor di, di
  xor ax, ax
  mov cx, 16
  cld
  rep stosw
  mov byte ptr es: [U_MAGIC], MAGIC_UNIVERSE
  dec _3doms_heapfree

    ;  Change interrupt.
    ;  Get old interrupt vector.
  mov ax, 353Dh
  int 21h
  mov _3doms_int3Dseg, es
  mov _3doms_int3Doff, bx

    ;  Set interrupt vector.
  mov ax, 253Dh
  push cs
  pop ds
  mov dx, offset int3D_intvect
  int 21h

  mov _3doms_loggedon, 1

  pop es
  pop ds
  pop di
  pop dx
  pop bx
  mov ax, _3doms_heapseg
  mov cx, _3doms_heapsize
  clc
  ret



;-------- Logoff_3DOMS

;  Frees the heap and restores interrupt vector.

;  Returns:
;	Carry set if not logged on.

logoff_3doms:
  cmp _3doms_loggedon, 0
  jne logoff_3doms_on
  mov ax, ERROR_NOTLOGGEDON
  stc
  ret
logoff_3doms_on:

  push dx
  push ds
  push es

    ;  Free allocated memory.
  mov ah, 49h
  mov es, _3doms_heapseg
  int 21h

    ;  Reset interrupt vector.
  mov ax, 253Dh
  mov dx, _3doms_int3Doff
  mov ds, _3doms_int3Dseg
  int 21h

  mov _3doms_loggedon, 0

  pop es
  pop ds
  pop dx
  clc
  ret



;----------------------------- The 3DOMS Interrupt --------------------------;



;  3DOMS Identifier - look for to see if 3DOMS TSR loaded.

db '3DOMSv', 00h, 15h



;  The 3DOMS interrupt handler.

int3D_intvect:
  sti

  cmp ah, 00h
  je int3D_info

    ;  Every other function keeps all the registers apart from AX.
  push ebx
  push ecx
  push edx
  push esi
  push edi
  push ebp
  push ds
  push es
  cld
  call int3D_function
  pop es
  pop ds
  pop ebp
  pop edi
  pop esi
  pop edx
  pop ecx
  pop ebx
  jc int3D_error
  jmp int3D_end



    ;  Set carry flag on stack and IRET.
int3D_error:
  push bp
  mov bp, sp
  or word ptr ss: [bp+6], 0001h
  pop bp
  iret



    ;  Clear carry flag on stack and IRET.
int3D_end:
  push bp
  mov bp, sp
  and word ptr ss: [bp+6], 0FFFEh
  pop bp
  iret



;-------- Branch to the required function.

;  Each function returns with carry clear, or with carry set and an error code
; in AX.

int3D_function:
  cmp ah, 10h
  je int3D_move
  cmp ah, 11h
  je int3D_formobject
  cmp ah, 12h
  je int3D_centre

  cmp ah, 18h
  je int3D_delobject
  cmp ah, 19h
  je int3D_delform

  cmp ah, 1Bh
  je int3D_newobject
  cmp ah, 1Ch
  je int3D_newform
  cmp ah, 1Dh
  je int3D_newshape
  cmp ah, 1Eh
  je int3D_newface

  cmp ah, 20h
  je int3D_render

  cmp ah, 2Ch
  je int3D_triangle
  cmp ah, 2Dh
  je int3D_line

  mov ax, ERROR_BADFUNCTION
  stc
  ret



;----------------------------- The 3DOMS Functions --------------------------;



;-------- Function 00h - Info.

;  Inputs:
;       AH - 00h

;  Returns:
;       AH - Major version number.
;       AL - Minor version number.
;       BX - Segment of heap.
;       CX - Size of heap in 32-byte cells.
;       DX - Number of free 32-byte cells in heap.

int3D_info:
  mov ax, 0015h         ;  Version 0.15 (00.15).
  mov bx, _3doms_heapseg
  mov cx, _3doms_heapsize
  mov dx, _3doms_heapfree
  jmp int3D_end



;-------- Function 10h - Move an object or the universe.

;  Inputs:
;       AH - 10h
;       DS:SI - Position and angles, 16 bytes.
;       ES - Object or universe.

int3D_move:

    ;  Make sure it's an object or the universe.
  mov ax, es
  call int3D_checkholder
  jnc int3D_move_ok
  mov ax, ERROR_BADOBJECT
  stc
  ret
int3D_move_ok:

    ;  Check the data: all 16 bytes have to be there, the position has to be
    ; in range and the rotation order has to be one of the six.
  mov cx, 16
  call int3D_checkbuffer
  jnc int3D_move_bufferok
  mov ax, ERROR_BADBUFFER
  stc
  ret
int3D_move_bufferok:
  mov cx, 3
  call int3D_readxyz
  jnc int3D_move_coordok
  ret
int3D_move_coordok:
  mov al, ds: [si+15]
  test al, 11111000b
  jnz int3D_move_badorder
  and al, 00000011b
  cmp al, 00000011b
  jne int3D_move_orderok
int3D_move_badorder:
  mov ax, ERROR_BADORDER
  stc
  ret
int3D_move_orderok:

    ;  Copy the position and angles.
  mov di, O_X
  mov cx, 8
  rep movsw
  clc
  ret



;-------- Function 11h - Change the form of an object.

;  Inputs:
;       AH - 11h
;       BX - New form (or 0000h to take the form away).
;       ES - Object.

;  Returns:
;       AX - Old form, or 0.

int3D_formobject:

    ;  Make sure object is valid.
  mov ax, es
  mov dl, MAGIC_OBJECT
  call int3D_checkcell
  jnc int3D_formobject_objectok
  mov ax, ERROR_BADOBJECT
  stc
  ret
int3D_formobject_objectok:

    ;  Check new form is valid.
  or bx, bx
  jz int3D_formobject_newok
  mov ax, bx
  mov dl, MAGIC_FORM
  call int3D_checkcell
  jnc int3D_formobject_newok
  mov ax, ERROR_BADFORM
  stc
  ret
int3D_formobject_newok:

    ;  The old form has one less object using it.
  mov ax, es: [O_FORM]
  call int3D_unuseform

    ;  And the new one has one more.
  or bx, bx
  jz int3D_formobject_nonew
  mov ds, bx
  inc word ptr ds: [M_USERS]
int3D_formobject_nonew:

  mov es: [O_FORM], bx
  clc
  ret



;-------- Function 12h - Change the center of an object, form or shape.

;  Inputs:
;       AH - 12h
;       DS:SI - New center, 3 double words.
;       ES - Object, form or shape.

int3D_centre:

    ;  Make sure it's something with a center.
  mov ax, es
  mov dl, MAGIC_OBJECT
  call int3D_checkcell
  jnc int3D_centre_ok
  mov dl, MAGIC_FORM
  call int3D_checkcell
  jnc int3D_centre_ok
  mov dl, MAGIC_SHAPE
  call int3D_checkcell
  jnc int3D_centre_ok
  mov ax, ERROR_BADOBJECT
  stc
  ret
int3D_centre_ok:

    ;  Read the new center.
  mov cx, 3
  call int3D_readxyz
  jnc int3D_centre_coordok
  ret
int3D_centre_coordok:

    ;  Objects, forms and shapes all keep their center point in the same
    ; place.
  mov ax, es: [_CENTRE]
  mov dl, MAGIC_POINT
  call int3D_checkcell
  jnc int3D_centre_pointok
  mov ax, ERROR_BADPOINT
  stc
  ret
int3D_centre_pointok:
  mov ds, ax
  mov eax, _3doms_xyz
  mov ds: [P_X], eax
  mov eax, _3doms_xyz+4
  mov ds: [P_Y], eax
  mov eax, _3doms_xyz+8
  mov ds: [P_Z], eax
  clc
  ret



;-------- Function 18h - Delete an object.

;  Inputs:
;       AH - 18h
;       ES - Object.

;  Returns:
;       AX - Object or universe it was in.

int3D_delobject:

    ;  Make sure object is valid, and empty.
  mov ax, es
  mov dl, MAGIC_OBJECT
  call int3D_checkcell
  jnc int3D_delobject_objectok
  mov ax, ERROR_BADOBJECT
  stc
  ret
int3D_delobject_objectok:
  cmp byte ptr es: [O_NUM], 0
  je int3D_delobject_empty
  mov ax, ERROR_HASCHILDREN
  stc
  ret
int3D_delobject_empty:

    ;  Check its parent.
  mov ax, es: [O_PARENT]
  call int3D_checkholder
  jnc int3D_delobject_parentok
  mov ax, ERROR_BADPARENT
  stc
  ret
int3D_delobject_parentok:
  mov ds, ax

    ;  Find it in its parent's list, keeping the one before it in DI.
  xor cx, cx
  mov cl, ds: [O_NUM]
  mov bx, es
  xor di, di
  mov ax, ds: [O_PTR]
int3D_delobject_find:
  jcxz int3D_delobject_badlist
  cmp ax, bx
  je int3D_delobject_found
  mov dl, MAGIC_OBJECT
  call int3D_checkcell
  jc int3D_delobject_badlist
  mov di, ax
  push ds
  mov ds, ax
  mov ax, ds: [O_NEXT]
  pop ds
  dec cx
  jmp int3D_delobject_find
int3D_delobject_badlist:
  mov ax, ERROR_BADLIST
  stc
  ret
int3D_delobject_found:

    ;  Take it out of the list.
  mov ax, es: [O_NEXT]
  or di, di
  jnz int3D_delobject_notfirst
  mov ds: [O_PTR], ax
  jmp int3D_delobject_unlinked
int3D_delobject_notfirst:
  push ds
  mov ds, di
  mov ds: [O_NEXT], ax
  pop ds
int3D_delobject_unlinked:
  dec byte ptr ds: [O_NUM]

    ;  Its form has one less object using it.
  mov ax, es: [O_FORM]
  call int3D_unuseform

    ;  Free its center and the object.
  mov ax, es: [O_CENTRE]
  mov dl, MAGIC_POINT
  call int3D_checkcell
  jc int3D_delobject_nocentre
  call int3D_freecell
int3D_delobject_nocentre:
  mov ax, es
  call int3D_freecell

  mov ax, ds
  clc
  ret



;-------- Function 19h - Delete a form.

;  Inputs:
;       AH - 19h
;       ES - Form.

int3D_delform:

    ;  Check if form is valid, and not used.
  mov ax, es
  mov dl, MAGIC_FORM
  call int3D_checkcell
  jnc int3D_delform_formok
  mov ax, ERROR_BADFORM
  stc
  ret
int3D_delform_formok:
  cmp word ptr es: [M_USERS], 0
  je int3D_delform_unused
  mov ax, ERROR_INUSE
  stc
  ret
int3D_delform_unused:

    ;  Go through everything in it once to check it, so that nothing is freed
    ; unless all of it can be, then again to free it.
  xor bp, bp
  call int3D_walkform
  jc int3D_delform_end
  mov bp, 1
  call int3D_walkform

    ;  Free its center and the form.
  mov ax, es: [M_CENTRE]
  call int3D_freecell
  mov ax, es
  call int3D_freecell
  clc
int3D_delform_end:
  ret



;-------- Function 1Bh - Create an object.

;  Inputs:
;       AH - 1Bh
;       DS:SI - Center, 3 double words.
;       ES - Object or universe to put it in.

;  Returns:
;       AX - Object.

int3D_newobject:

    ;  Make sure parent structure is valid, has room for another object and
    ; isn't too deep.
  mov ax, es
  call int3D_checkholder
  jnc int3D_newobject_parentok
  mov ax, ERROR_BADPARENT
  stc
  ret
int3D_newobject_parentok:
  cmp byte ptr es: [O_NUM], MAX_CHILDREN
  jb int3D_newobject_numok
  mov ax, ERROR_TOOMANY
  stc
  ret
int3D_newobject_numok:
  cmp byte ptr es: [O_LEVEL], MAX_DEPTH
  jb int3D_newobject_levelok
  mov ax, ERROR_TOODEEP
  stc
  ret
int3D_newobject_levelok:

    ;  Read the center.
  mov cx, 3
  call int3D_readxyz
  jnc int3D_newobject_coordok
  ret
int3D_newobject_coordok:

    ;  Make sure there are cells for the center and the object.
  cmp _3doms_heapfree, 2
  jae int3D_newobject_memok
  mov ax, ERROR_HEAPOUT
  stc
  ret
int3D_newobject_memok:

    ;  Make the center and the object.
  mov si, offset _3doms_xyz
  call int3D_newpoint
  mov bx, ax
  call int3D_takecell
  mov ds, ax
  mov byte ptr ds: [O_MAGIC], MAGIC_OBJECT
  mov ds: [O_CENTRE], bx
  mov dl, es: [O_LEVEL]
  inc dl
  mov ds: [O_LEVEL], dl

    ;  Put it at the front of its parent's list.
  mov ds: [O_PARENT], es
  mov dx, es: [O_PTR]
  mov ds: [O_NEXT], dx
  mov es: [O_PTR], ax
  inc byte ptr es: [O_NUM]

    ;  The center belongs to the object.
  mov ds, bx
  mov ds: [P_PARENT], ax
  clc
  ret



;-------- Function 1Ch - Create a form.

;  Inputs:
;       AH - 1Ch
;       DS:SI - Center, 3 double words.

;  Returns:
;       AX - Form.

int3D_newform:

    ;  Read the center.
  mov cx, 3
  call int3D_readxyz
  jnc int3D_newform_coordok
  ret
int3D_newform_coordok:

    ;  Make sure there are cells for the center and the form.
  cmp _3doms_heapfree, 2
  jae int3D_newform_memok
  mov ax, ERROR_HEAPOUT
  stc
  ret
int3D_newform_memok:

    ;  Make the center and the form.
  mov si, offset _3doms_xyz
  call int3D_newpoint
  mov bx, ax
  call int3D_takecell
  mov ds, ax
  mov byte ptr ds: [M_MAGIC], MAGIC_FORM
  mov ds: [M_CENTRE], bx

    ;  The center belongs to the form.
  mov ds, bx
  mov ds: [P_PARENT], ax
  clc
  ret



;-------- Function 1Dh - Create a shape.

;  Inputs:
;       AH - 1Dh
;       DS:SI - Center, 3 double words.
;       ES - Form.

;  Returns:
;       AX - Shape.

int3D_newshape:

    ;  Make sure parent form is valid and has room for another shape.
  mov ax, es
  mov dl, MAGIC_FORM
  call int3D_checkcell
  jnc int3D_newshape_formok
  mov ax, ERROR_BADFORM
  stc
  ret
int3D_newshape_formok:
  cmp byte ptr es: [M_NUM], MAX_SHAPES
  jb int3D_newshape_numok
  mov ax, ERROR_TOOMANY
  stc
  ret
int3D_newshape_numok:

    ;  Read the center.
  mov cx, 3
  call int3D_readxyz
  jnc int3D_newshape_coordok
  ret
int3D_newshape_coordok:

    ;  Make sure there are cells for the center and the shape.
  cmp _3doms_heapfree, 2
  jae int3D_newshape_memok
  mov ax, ERROR_HEAPOUT
  stc
  ret
int3D_newshape_memok:

    ;  Make the center and the shape.
  mov si, offset _3doms_xyz
  call int3D_newpoint
  mov bx, ax
  call int3D_takecell
  mov ds, ax
  mov byte ptr ds: [S_MAGIC], MAGIC_SHAPE
  mov ds: [S_CENTRE], bx

    ;  Put it at the front of the form's list.
  mov ds: [S_PARENT], es
  mov dx, es: [M_PTR]
  mov ds: [S_NEXT], dx
  mov es: [M_PTR], ax
  inc byte ptr es: [M_NUM]

    ;  The center belongs to the shape.
  mov ds, bx
  mov ds: [P_PARENT], ax
  clc
  ret



;-------- Function 1Eh - Create a face.

;  Inputs:
;       AH - 1Eh
;       AL - Colour.
;       DS:SI - Corners, 9 double words.
;       ES - Shape.

;  Returns:
;       AX - Face.

int3D_newface:
  mov bl, al

    ;  Make sure parent shape is valid and has room for another face.
  mov ax, es
  mov dl, MAGIC_SHAPE
  call int3D_checkcell
  jnc int3D_newface_shapeok
  mov ax, ERROR_BADSHAPE
  stc
  ret
int3D_newface_shapeok:
  cmp byte ptr es: [S_NUM], MAX_FACES
  jb int3D_newface_numok
  mov ax, ERROR_TOOMANY
  stc
  ret
int3D_newface_numok:

    ;  The form the shape is in has to have room for three more points.
  mov ax, es: [S_PARENT]
  mov dl, MAGIC_FORM
  call int3D_checkcell
  jnc int3D_newface_formok
  mov ax, ERROR_BADFORM
  stc
  ret
int3D_newface_formok:
  mov bp, ax
  push ds
  mov ds, ax
  cmp byte ptr ds: [M_NUMPTS], MAX_POINTS-2
  pop ds
  jb int3D_newface_pointsok
  mov ax, ERROR_TOOMANY
  stc
  ret
int3D_newface_pointsok:

    ;  Read the corners.
  mov cx, 9
  call int3D_readxyz
  jnc int3D_newface_coordok
  ret
int3D_newface_coordok:

    ;  Make sure there are cells for the face and three new points.
  cmp _3doms_heapfree, 4
  jae int3D_newface_memok
  mov ax, ERROR_HEAPOUT
  stc
  ret
int3D_newface_memok:

    ;  Make the face.
  call int3D_takecell
  mov di, ax
  mov ds, ax
  mov byte ptr ds: [F_MAGIC], MAGIC_FACE
  mov ds: [F_COL], bl

    ;  Find its corners among the form's points, or add them.
  push es
  mov es, bp
  mov si, offset _3doms_xyz
  call int3D_findpoint
  mov ds: [F_P1], ax
  add si, 12
  call int3D_findpoint
  mov ds: [F_P2], ax
  add si, 12
  call int3D_findpoint
  mov ds: [F_P3], ax
  pop es

    ;  Put it at the front of the shape's list.
  mov ds: [F_PARENT], es
  mov ax, es: [S_PTR]
  mov ds: [F_NEXT], ax
  mov es: [S_PTR], di
  inc byte ptr es: [S_NUM]

  mov ax, di
  clc
  ret



;-------- Function 20h - Render.

;  Inputs:
;	AH - 20h
;	DS - Universe.
;	ES - Output Segment.

int3D_render:

    ;  Check universe and output buffer.
  mov ax, ds
  cmp ax, _3doms_universe
  jne int3D_render_baduniverse
  cmp byte ptr ds: [U_MAGIC], MAGIC_UNIVERSE
  je int3D_render_universeok
int3D_render_baduniverse:
  mov ax, ERROR_BADUNIVERSE
  stc
  ret
int3D_render_universeok:
  call int3D_checkoutput
  jnc int3D_render_outputok
  mov ax, ERROR_BADOUTPUT
  stc
  ret
int3D_render_outputok:

    ;  Render on 3DOMS's own stack, so there is always room.
  mov _3doms_callss, ss
  mov _3doms_callsp, sp
  mov ax, cs
  mov ss, ax
  mov sp, offset _3doms_stacktop

  mov _3doms_tdepth, 0
  mov _3doms_sortptr, offset _3doms_sorttab
  call int3D_drawstart

    ;  Back to the caller's stack, keeping AX and the carry flag.
  mov ss, _3doms_callss
  mov sp, _3doms_callsp
  ret



;  Start drawing at the universe.  If anything goes wrong while drawing,
; int3D_drawfail comes straight back here, however deep it is.

int3D_drawstart:
  mov _3doms_failsp, sp
  call int3D_drawobject
  clc
  ret

int3D_drawfail:
  mov sp, _3doms_failsp
  stc
  ret



;-------- Function 2Ch - Triangle.

;  Inputs:
;	AH - 2Ch
;	AL - Colour.
;	BX, CX - First corner.
;	SI, BP - Second corner.
;	DX, DI - Third corner.
;	ES - Output segment.

int3D_triangle:
  call int3D_checkoutput
  jnc int3D_triangle_outputok
  mov ax, ERROR_BADOUTPUT
  stc
  ret
int3D_triangle_outputok:
  mov _tri_x1, bx
  mov _tri_y1, cx
  mov _tri_x2, si
  mov _tri_y2, bp
  mov _tri_x3, dx
  mov _tri_y3, di
  call int3D_tri
  clc
  ret



;-------- Function 2Dh - Line.

;  Inputs:
;	AH - 2Dh
;	AL - Colour.
;	BX, CX - Start.
;	SI, BP - End.
;	ES - Output segment.

int3D_line:
  call int3D_checkoutput
  jnc int3D_line_outputok
  mov ax, ERROR_BADOUTPUT
  stc
  ret
int3D_line_outputok:

    ;  Both ends have to be on the screen.  Unsigned compares catch the
    ; negative ones as well.
  cmp bx, 319
  ja int3D_line_off
  cmp si, 319
  ja int3D_line_off
  cmp cx, 199
  ja int3D_line_off
  cmp bp, 199
  jbe int3D_line_on
int3D_line_off:
  mov ax, ERROR_OFFSCREEN
  stc
  ret
int3D_line_on:
  mov _tri_col, al

    ;  Offset of the start in the buffer.
  mov ax, 320
  mul cx
  add ax, bx
  mov di, ax

    ;  How far it goes, and which way, along x and y.
  mov ax, si
  sub ax, bx
  mov bx, 1
  jge int3D_line_right
  neg ax
  mov bx, -1
int3D_line_right:
  mov dx, bp
  sub dx, cx
  mov cx, 320
  jge int3D_line_down
  neg dx
  mov cx, -320
int3D_line_down:

    ;  Step along whichever is longer, and the other way when the error
    ; term runs out.
  cmp ax, dx
  jae int3D_line_major
  xchg ax, dx
  xchg bx, cx
int3D_line_major:
  mov _line_len, ax
  mov si, ax
  shr si, 1
  mov bp, ax
  inc bp
  mov al, _tri_col
int3D_line_loop:
  mov es: [di], al
  add di, bx
  sub si, dx
  jge int3D_line_noadj
  add si, _line_len
  add di, cx
int3D_line_noadj:
  dec bp
  jnz int3D_line_loop
  clc
  ret



;----------------------------- Structure Routines ---------------------------;



;-------- Routine: Check cell.

;  Makes sure a segment is a cell on the heap holding the structure wanted.

;  Inputs:
;	AX - Segment.
;	DL - Magic wanted.

;  Returns:
;	Carry clear if it is, carry set if not.

int3D_checkcell:
  push ax
  push ds
  cmp ax, _3doms_heapseg
  jb int3D_checkcell_bad
  cmp ax, _3doms_heapend
  jae int3D_checkcell_bad
  mov ds, ax
  sub ax, _3doms_heapseg
  test al, 01h
  jnz int3D_checkcell_bad
  cmp ds: [_MAGIC], dl
  jne int3D_checkcell_bad
  pop ds
  pop ax
  clc
  ret
int3D_checkcell_bad:
  pop ds
  pop ax
  stc
  ret



;-------- Routine: Check holder.

;  Makes sure a segment is an object or the universe - something that can
; hold objects.

;  Inputs:
;	AX - Segment.

;  Returns:
;	Carry clear if it is, carry set if not.

int3D_checkholder:
  push dx
  mov dl, MAGIC_OBJECT
  call int3D_checkcell
  jnc int3D_checkholder_end
  mov dl, MAGIC_UNIVERSE
  call int3D_checkcell
int3D_checkholder_end:
  pop dx
  ret



;-------- Routine: Check buffer.

;  Makes sure CX bytes at SI fit before the end of the segment.

;  Returns:
;	Carry set if they don't.

int3D_checkbuffer:
  push ax
  mov ax, cx
  dec ax
  add ax, si
  pop ax
  ret



;-------- Routine: Check output.

;  Makes sure the 64000-byte buffer at ES doesn't overlap the heap or 3DOMS.

;  Returns:
;	Carry set if it does.

int3D_checkoutput:
  push eax
  push ebx
  push ecx
  push edx

    ;  First and last paragraphs of the buffer.
  xor eax, eax
  mov ax, es
  mov edx, eax
  add edx, 0F9Fh

    ;  The heap.
  xor ebx, ebx
  mov bx, _3doms_heapseg
  xor ecx, ecx
  mov cx, _3doms_heapend
  dec ecx
  call int3D_overlap
  jc int3D_checkoutput_end

    ;  3DOMS.
  xor ebx, ebx
  mov bx, cs
  xor ecx, ecx
  mov cx, offset int3D_last
  add ecx, 15
  shr ecx, 4
  add ecx, ebx
  push ecx
  xor ecx, ecx
  mov cx, offset int3D_first
  shr ecx, 4
  add ebx, ecx
  pop ecx
  call int3D_overlap

int3D_checkoutput_end:
  pop edx
  pop ecx
  pop ebx
  pop eax
  ret

;  Carry set if paragraphs EAX to EDX overlap EBX to ECX.

int3D_overlap:
  cmp edx, ebx
  jb int3D_overlap_no
  cmp eax, ecx
  ja int3D_overlap_no
  stc
  ret
int3D_overlap_no:
  clc
  ret



;-------- Routine: Read co-ordinates.

;  Reads CX double word co-ordinates from DS:SI into _3doms_xyz, making sure
; they are all there and in range.

;  Returns:
;	Carry set and AX - Error code if they aren't.

int3D_readxyz:
  push cx
  push si
  push di

  push cx
  shl cx, 2
  call int3D_checkbuffer
  pop cx
  jc int3D_readxyz_badbuffer

  mov di, offset _3doms_xyz
int3D_readxyz_loop:
  lodsd
  cmp eax, MAX_COORD
  jg int3D_readxyz_badcoord
  cmp eax, -MAX_COORD
  jl int3D_readxyz_badcoord
  mov cs: [di], eax
  add di, 4
  loop int3D_readxyz_loop

  pop di
  pop si
  pop cx
  clc
  ret

int3D_readxyz_badbuffer:
  mov ax, ERROR_BADBUFFER
  jmp int3D_readxyz_fail
int3D_readxyz_badcoord:
  mov ax, ERROR_BADCOORD
int3D_readxyz_fail:
  pop di
  pop si
  pop cx
  stc
  ret



;-------- Routine: Take cell.

;  Takes the first free cell on the heap and clears it.  Callers have already
; made sure there are enough free cells.

;  Returns:
;	AX - Segment of cell.

int3D_takecell:
  push cx
  push di
  push es

  mov ax, _3doms_heapseg
  mov cx, _3doms_heapsize
int3D_takecell_loop:
  mov es, ax
  cmp byte ptr es: [_MAGIC], MAGIC_FREE
  je int3D_takecell_found
  add ax, 2
  loop int3D_takecell_loop

int3D_takecell_found:
  push ax
  xor di, di
  xor ax, ax
  mov cx, 16
  rep stosw
  pop ax
  mov byte ptr es: [_MAGIC], MAGIC_TAKEN
  dec _3doms_heapfree

  pop es
  pop di
  pop cx
  ret



;-------- Routine: Unuse form.

;  Counts one less object using a form.

;  Inputs:
;	AX - Form, or 0.

int3D_unuseform:
  push dx
  push ds
  mov dl, MAGIC_FORM
  call int3D_checkcell
  jc int3D_unuseform_end
  mov ds, ax
  cmp word ptr ds: [M_USERS], 0
  je int3D_unuseform_end
  dec word ptr ds: [M_USERS]
int3D_unuseform_end:
  pop ds
  pop dx
  ret



;-------- Routine: Free cell.

;  Inputs:
;	AX - Segment of cell.

int3D_freecell:
  push ds
  mov ds, ax
  mov byte ptr ds: [_MAGIC], MAGIC_FREE
  inc _3doms_heapfree
  pop ds
  ret



;-------- Routine: New point.

;  Inputs:
;	CS:SI - x, y and z.

;  Returns:
;	AX - Segment of point.

int3D_newpoint:
  push edx
  push ds
  call int3D_takecell
  mov ds, ax
  mov byte ptr ds: [P_MAGIC], MAGIC_POINT
  mov byte ptr ds: [P_OFF], 1
  mov edx, cs: [si]
  mov ds: [P_X], edx
  mov edx, cs: [si+4]
  mov ds: [P_Y], edx
  mov edx, cs: [si+8]
  mov ds: [P_Z], edx
  pop ds
  pop edx
  ret



;-------- Routine: Find point.

;  Finds the point in a form at the co-ordinates given, or adds a new one.

;  Inputs:
;	CS:SI - x, y and z.
;	ES - Form.

;  Returns:
;	AX - Segment of point.

int3D_findpoint:
  push cx
  push edx
  push ds

  xor cx, cx
  mov cl, es: [M_NUMPTS]
  mov ax, es: [M_PTS]
  jcxz int3D_findpoint_new
int3D_findpoint_loop:
  mov ds, ax
  mov edx, cs: [si]
  cmp edx, ds: [P_X]
  jne int3D_findpoint_next
  mov edx, cs: [si+4]
  cmp edx, ds: [P_Y]
  jne int3D_findpoint_next
  mov edx, cs: [si+8]
  cmp edx, ds: [P_Z]
  je int3D_findpoint_end
int3D_findpoint_next:
  mov ax, ds: [P_NEXT]
  loop int3D_findpoint_loop

    ;  It isn't there, so make it and put it at the front of the form's list.
int3D_findpoint_new:
  call int3D_newpoint
  mov ds, ax
  mov ds: [P_PARENT], es
  mov dx, es: [M_PTS]
  mov ds: [P_NEXT], dx
  mov es: [M_PTS], ax
  inc byte ptr es: [M_NUMPTS]

int3D_findpoint_end:
  pop ds
  pop edx
  pop cx
  ret



;-------- Routine: Walk form.

;  Goes through everything in a form, checking every structure in it and that
; every list matches its count.  If BP is 1, it frees them as well.  The
; form's own cell and center are left alone.

;  Inputs:
;	BP - 0 to check, 1 to check and free.
;	ES - Form.

;  Returns:
;	Carry set and AX - ERROR_BADLIST if anything is wrong.

int3D_walkform:
  push bx
  push cx
  push dx
  push ds

    ;  Its center.
  mov ax, es: [M_CENTRE]
  mov dl, MAGIC_POINT
  call int3D_checkcell
  jc int3D_walkform_bad

    ;  Its shapes.
  xor cx, cx
  mov cl, es: [M_NUM]
  mov ax, es: [M_PTR]
int3D_walkform_shape:
  jcxz int3D_walkform_shapesdone
  mov dl, MAGIC_SHAPE
  call int3D_checkcell
  jc int3D_walkform_bad
  mov ds, ax

    ;  The shape's faces.
  push cx
  xor cx, cx
  mov cl, ds: [S_NUM]
  mov ax, ds: [S_PTR]
int3D_walkform_face:
  jcxz int3D_walkform_facesdone
  mov dl, MAGIC_FACE
  call int3D_checkcell
  jc int3D_walkform_badface
  push ds
  mov ds, ax
  mov bx, ds: [F_NEXT]
  pop ds
  or bp, bp
  jz int3D_walkform_keepface
  call int3D_freecell
int3D_walkform_keepface:
  mov ax, bx
  dec cx
  jmp int3D_walkform_face
int3D_walkform_facesdone:
  pop cx
  or ax, ax
  jnz int3D_walkform_bad

    ;  The shape's center, then the shape.
  mov ax, ds: [S_CENTRE]
  mov dl, MAGIC_POINT
  call int3D_checkcell
  jc int3D_walkform_bad
  or bp, bp
  jz int3D_walkform_keepcentre
  call int3D_freecell
int3D_walkform_keepcentre:
  mov bx, ds: [S_NEXT]
  or bp, bp
  jz int3D_walkform_keepshape
  mov ax, ds
  call int3D_freecell
int3D_walkform_keepshape:
  mov ax, bx
  dec cx
  jmp int3D_walkform_shape
int3D_walkform_shapesdone:
  or ax, ax
  jnz int3D_walkform_bad

    ;  Its points.
  xor cx, cx
  mov cl, es: [M_NUMPTS]
  mov ax, es: [M_PTS]
int3D_walkform_point:
  jcxz int3D_walkform_pointsdone
  mov dl, MAGIC_POINT
  call int3D_checkcell
  jc int3D_walkform_bad
  push ds
  mov ds, ax
  mov bx, ds: [P_NEXT]
  pop ds
  or bp, bp
  jz int3D_walkform_keeppoint
  call int3D_freecell
int3D_walkform_keeppoint:
  mov ax, bx
  dec cx
  jmp int3D_walkform_point
int3D_walkform_pointsdone:
  or ax, ax
  jnz int3D_walkform_bad

  pop ds
  pop dx
  pop cx
  pop bx
  clc
  ret

int3D_walkform_badface:
  pop cx
int3D_walkform_bad:
  mov ax, ERROR_BADLIST
  pop ds
  pop dx
  pop cx
  pop bx
  stc
  ret



;----------------------------- Rendering Routines ---------------------------;



;-------- Routine: Draw object.

;  Draws an object, or the universe, and everything in it.  Its own form and
; the objects in it are put in a list by how far their centers are from the
; camera, and drawn starting with the furthest.

;  Inputs:
;	DS - Object or universe.
;	ES - Output segment.

int3D_drawobject:
  call int3D_pushtransform
  push _3doms_sortptr

    ;  Its own form goes in the list by the distance of the form's center.
    ; The universe has no form.
  cmp byte ptr ds: [_MAGIC], MAGIC_OBJECT
  jne int3D_drawobject_children
  mov ax, ds: [O_FORM]
  or ax, ax
  jz int3D_drawobject_children
  mov dl, MAGIC_FORM
  call int3D_checkcell
  jc int3D_drawobject_badform
  mov bx, ax
  push ds
  mov ds, ax
  mov ax, ds: [M_CENTRE]
  pop ds
  call int3D_measure

    ;  So does each object in it, by its center with its own position and
    ; angles as well.
int3D_drawobject_children:
  xor cx, cx
  mov cl, ds: [O_NUM]
  mov ax, ds: [O_PTR]
int3D_drawobject_child:
  jcxz int3D_drawobject_listed
  mov dl, MAGIC_OBJECT
  call int3D_checkcell
  jc int3D_drawobject_badobject
  push ds
  mov ds, ax
  mov bx, ax
  call int3D_pushtransform
  mov ax, ds: [O_CENTRE]
  call int3D_measure
  dec _3doms_tdepth
  mov ax, ds: [O_NEXT]
  pop ds
  dec cx
  jmp int3D_drawobject_child
int3D_drawobject_listed:
  or ax, ax
  jnz int3D_drawobject_badlist

    ;  Draw them, furthest first.
int3D_drawobject_draw:
  mov bp, sp
  mov si, ss: [bp]
  call int3D_takefurthest
  or ax, ax
  jz int3D_drawobject_done
  push ds
  mov ds, ax
  cmp byte ptr ds: [_MAGIC], MAGIC_OBJECT
  jne int3D_drawobject_form
  call int3D_drawobject
  jmp int3D_drawobject_drawn
int3D_drawobject_form:
  call int3D_drawform
int3D_drawobject_drawn:
  pop ds
  jmp int3D_drawobject_draw

int3D_drawobject_done:
  pop _3doms_sortptr
  dec _3doms_tdepth
  ret

int3D_drawobject_badform:
  mov ax, ERROR_BADFORM
  jmp int3D_drawfail
int3D_drawobject_badobject:
  mov ax, ERROR_BADOBJECT
  jmp int3D_drawfail
int3D_drawobject_badlist:
  mov ax, ERROR_BADLIST
  jmp int3D_drawfail



;-------- Routine: Draw form.

;  Works out where every point in the form goes on the screen, then puts the
; shapes in a list by how far their centers are from the camera and draws
; them, starting with the furthest.

;  Inputs:
;	DS - Form.
;	ES - Output segment.

int3D_drawform:

    ;  Every point, once.
  xor cx, cx
  mov cl, ds: [M_NUMPTS]
  mov ax, ds: [M_PTS]
int3D_drawform_point:
  jcxz int3D_drawform_pointsdone
  mov dl, MAGIC_POINT
  call int3D_checkcell
  jc int3D_drawform_badpoint
  push ds
  mov ds, ax
  mov esi, ds: [P_X]
  mov edi, ds: [P_Y]
  mov ebp, ds: [P_Z]
  call int3D_transform
  call int3D_project
  mov ax, ds: [P_NEXT]
  pop ds
  dec cx
  jmp int3D_drawform_point
int3D_drawform_pointsdone:
  or ax, ax
  jnz int3D_drawform_badlist

    ;  The shapes go in the list by the distance of their centers.
  push _3doms_sortptr
  xor cx, cx
  mov cl, ds: [M_NUM]
  mov ax, ds: [M_PTR]
int3D_drawform_shape:
  jcxz int3D_drawform_listed
  mov dl, MAGIC_SHAPE
  call int3D_checkcell
  jc int3D_drawform_badshape
  push ds
  mov ds, ax
  mov bx, ax
  mov ax, ds: [S_CENTRE]
  call int3D_measure
  mov ax, ds: [S_NEXT]
  pop ds
  dec cx
  jmp int3D_drawform_shape
int3D_drawform_listed:
  or ax, ax
  jnz int3D_drawform_badlist

    ;  Draw them, furthest first.
int3D_drawform_draw:
  mov bp, sp
  mov si, ss: [bp]
  call int3D_takefurthest
  or ax, ax
  jz int3D_drawform_done
  push ds
  mov ds, ax
  call int3D_drawshape
  pop ds
  jmp int3D_drawform_draw

int3D_drawform_done:
  pop _3doms_sortptr
  ret

int3D_drawform_badpoint:
  mov ax, ERROR_BADPOINT
  jmp int3D_drawfail
int3D_drawform_badshape:
  mov ax, ERROR_BADSHAPE
  jmp int3D_drawfail
int3D_drawform_badlist:
  mov ax, ERROR_BADLIST
  jmp int3D_drawfail



;-------- Routine: Draw shape.

;  Draws every face in a shape.  A shape is convex, so leaving out the faces
; that face away is enough, and the rest can be drawn in any order.

;  Inputs:
;	DS - Shape.
;	ES - Output segment.

int3D_drawshape:
  xor cx, cx
  mov cl, ds: [S_NUM]
  mov ax, ds: [S_PTR]
int3D_drawshape_face:
  jcxz int3D_drawshape_done
  mov dl, MAGIC_FACE
  call int3D_checkcell
  jc int3D_drawshape_badface
  push ds
  mov ds, ax
  call int3D_drawface
  mov ax, ds: [F_NEXT]
  pop ds
  dec cx
  jmp int3D_drawshape_face
int3D_drawshape_done:
  or ax, ax
  jnz int3D_drawshape_badlist
  ret

int3D_drawshape_badface:
  mov ax, ERROR_BADFACE
  jmp int3D_drawfail
int3D_drawshape_badlist:
  mov ax, ERROR_BADLIST
  jmp int3D_drawfail



;-------- Routine: Draw face.

;  Inputs:
;	DS - Face.
;	ES - Output segment.

int3D_drawface:
  push cx

    ;  Get where its corners went on the screen.  If any of them couldn't be
    ; put on the screen, the face is left out.
  mov ax, ds: [F_P1]
  mov di, offset _tri_x1
  call int3D_getcorner
  jc int3D_drawface_nodraw
  mov ax, ds: [F_P2]
  mov di, offset _tri_x2
  call int3D_getcorner
  jc int3D_drawface_nodraw
  mov ax, ds: [F_P3]
  mov di, offset _tri_x3
  call int3D_getcorner
  jc int3D_drawface_nodraw

    ;  Calculate cross vector product - if it isn't -ive then the face is
    ; facing away, or edge on - don't draw it.
  movsx eax, _tri_x2
  movsx ecx, _tri_x1
  sub eax, ecx
  movsx ecx, _tri_y3
  movsx edx, _tri_y2
  sub ecx, edx
  imul eax, ecx
  movsx ecx, _tri_x3
  movsx edx, _tri_x2
  sub ecx, edx
  movsx edx, _tri_y2
  movsx esi, _tri_y1
  sub edx, esi
  imul ecx, edx
  sub eax, ecx
  jge int3D_drawface_nodraw

    ;  Draw face.
  mov al, ds: [F_COL]
  call int3D_tri

int3D_drawface_nodraw:
  pop cx
  ret



;-------- Routine: Get corner.

;  Inputs:
;	AX - Point.
;	CS:DI - Where to put its x and y on the screen.

;  Returns:
;	Carry set if it couldn't be put on the screen.

int3D_getcorner:
  mov dl, MAGIC_POINT
  call int3D_checkcell
  jnc int3D_getcorner_ok
  mov ax, ERROR_BADPOINT
  jmp int3D_drawfail
int3D_getcorner_ok:
  push ds
  mov ds, ax
  cmp byte ptr ds: [P_OFF], 0
  jne int3D_getcorner_off
  mov ax, ds: [P_SX]
  mov cs: [di], ax
  mov ax, ds: [P_SY]
  mov cs: [di+2], ax
  pop ds
  clc
  ret
int3D_getcorner_off:
  pop ds
  stc
  ret



;-------- Routine: Measure.

;  Works out how far a center is from the camera and adds it to the draw list.

;  Inputs:
;	AX - Center point.
;	BX - What to draw.

int3D_measure:
  mov dl, MAGIC_POINT
  call int3D_checkcell
  jnc int3D_measure_ok
  mov ax, ERROR_BADPOINT
  jmp int3D_drawfail
int3D_measure_ok:
  push ds
  mov ds, ax
  mov esi, ds: [P_X]
  mov edi, ds: [P_Y]
  mov ebp, ds: [P_Z]
  pop ds
  call int3D_transform
  call int3D_addentry
  ret



;-------- Routine: Add entry.

;  Adds something to the end of the draw list, with the distance squared of
; a point from the camera.

;  Inputs:
;	ESI, EDI, EBP - The point, after transforming.
;	BX - What to draw.

int3D_addentry:
  push eax
  push ecx
  push edx
  push di

    ;  Distance squared = x*x + y*y + z*z, into ECX:ESI.
  mov eax, esi
  imul eax
  mov esi, eax
  mov ecx, edx
  mov eax, edi
  imul eax
  add esi, eax
  adc ecx, edx
  mov eax, ebp
  imul eax
  add esi, eax
  adc ecx, edx

  mov di, _3doms_sortptr
  cmp di, offset _3doms_sortend - SORT_SIZE
  jbe int3D_addentry_room
  mov ax, ERROR_TOOMANY
  jmp int3D_drawfail
int3D_addentry_room:
  mov cs: [di+SORT_LO], esi
  mov cs: [di+SORT_HI], ecx
  mov cs: [di+SORT_SEG], bx
  add di, SORT_SIZE
  mov _3doms_sortptr, di

  pop di
  pop edx
  pop ecx
  pop eax
  ret



;-------- Routine: Take furthest.

;  Finds the furthest entry from SI to the end of the draw list that hasn't
; been drawn yet, and marks it drawn.

;  Inputs:
;	SI - Start of list.

;  Returns:
;	AX - What to draw, or 0 if everything has been drawn.

int3D_takefurthest:
  push ebx
  push ecx
  push edx
  push si
  push di

  xor ebx, ebx
  xor ecx, ecx
  xor di, di
int3D_takefurthest_loop:
  cmp si, _3doms_sortptr
  jae int3D_takefurthest_end
  cmp word ptr cs: [si+SORT_SEG], 0
  je int3D_takefurthest_next
  or di, di
  jz int3D_takefurthest_take
  mov edx, cs: [si+SORT_HI]
  cmp edx, ecx
  ja int3D_takefurthest_take
  jb int3D_takefurthest_next
  mov edx, cs: [si+SORT_LO]
  cmp edx, ebx
  jbe int3D_takefurthest_next
int3D_takefurthest_take:
  mov ebx, cs: [si+SORT_LO]
  mov ecx, cs: [si+SORT_HI]
  mov di, si
int3D_takefurthest_next:
  add si, SORT_SIZE
  jmp int3D_takefurthest_loop

int3D_takefurthest_end:
  xor ax, ax
  or di, di
  jz int3D_takefurthest_none
  mov ax, cs: [di+SORT_SEG]
  mov word ptr cs: [di+SORT_SEG], 0
int3D_takefurthest_none:
  pop di
  pop si
  pop edx
  pop ecx
  pop ebx
  ret



;-------- Routine: Push transform.

;  Puts the position and angles of an object, or the universe, on the
; transformation stack.

;  Inputs:
;	DS - Object or universe.

int3D_pushtransform:
  push bx
  push cx
  push si
  push di
  push es

  mov bx, _3doms_tdepth
  cmp bx, TSTACK_LEVELS
  jb int3D_pushtransform_room
  mov ax, ERROR_TOODEEP
  jmp int3D_drawfail
int3D_pushtransform_room:
  shl bx, 4
  push cs
  pop es
  mov si, O_X
  lea di, _3doms_tstack[bx]
  mov cx, 8
  rep movsw
  inc _3doms_tdepth

  pop es
  pop di
  pop si
  pop cx
  pop bx
  ret



;-------- Routine: Transform.

;  Takes a point through every level on the transformation stack, from the
; top down: at each level it's rotated by the angles, in the rotation order,
; and then moved by the position.

;  Inputs:
;	ESI, EDI, EBP - x, y and z.

;  Returns:
;	ESI, EDI, EBP - x, y and z as the camera sees them.

int3D_transform:
  push bx
  push cx

  mov cx, _3doms_tdepth
  mov bx, cx
  shl bx, 4
  jcxz int3D_transform_end
int3D_transform_level:
  sub bx, 16
  call int3D_rotate
  add esi, dword ptr _3doms_tstack[bx]
  add edi, dword ptr _3doms_tstack[bx+4]
  add ebp, dword ptr _3doms_tstack[bx+8]
  loop int3D_transform_level

int3D_transform_end:
  pop cx
  pop bx
  ret



;-------- Rotate

;  Inputs:
;	_3doms_tstack[BX] - Level, with the angles and rotation order at 12.
;	ESI, EDI, EBP - x, y and z to be rotated.

int3D_rotate:

  test byte ptr _3doms_tstack[bx+15], 00000001b
  jnz int3D_rotate_y__
  test byte ptr _3doms_tstack[bx+15], 00000010b
  jnz int3D_rotate_z__

int3D_rotate_x__:
  mov al, byte ptr _3doms_tstack[bx+12]
  call int3D_rotate_x
  test byte ptr _3doms_tstack[bx+15], 00000100b
  jz int3D_rotate__yz
int3D_rotate__zy:
  mov al, byte ptr _3doms_tstack[bx+14]
  call int3D_rotate_z
  mov al, byte ptr _3doms_tstack[bx+13]
  call int3D_rotate_y
  ret
int3D_rotate__yz:
  mov al, byte ptr _3doms_tstack[bx+13]
  call int3D_rotate_y
  mov al, byte ptr _3doms_tstack[bx+14]
  call int3D_rotate_z
  ret

int3D_rotate_y__:
  mov al, byte ptr _3doms_tstack[bx+13]
  call int3D_rotate_y
  test byte ptr _3doms_tstack[bx+15], 00000100b
  jz int3D_rotate__xz
int3D_rotate__zx:
  mov al, byte ptr _3doms_tstack[bx+14]
  call int3D_rotate_z
  mov al, byte ptr _3doms_tstack[bx+12]
  call int3D_rotate_x
  ret
int3D_rotate__xz:
  mov al, byte ptr _3doms_tstack[bx+12]
  call int3D_rotate_x
  mov al, byte ptr _3doms_tstack[bx+14]
  call int3D_rotate_z
  ret

int3D_rotate_z__:
  mov al, byte ptr _3doms_tstack[bx+14]
  call int3D_rotate_z
  test byte ptr _3doms_tstack[bx+15], 00000100b
  jz int3D_rotate__xy
int3D_rotate__yx:
  mov al, byte ptr _3doms_tstack[bx+13]
  call int3D_rotate_y
  mov al, byte ptr _3doms_tstack[bx+12]
  call int3D_rotate_x
  ret
int3D_rotate__xy:
  mov al, byte ptr _3doms_tstack[bx+12]
  call int3D_rotate_x
  mov al, byte ptr _3doms_tstack[bx+13]
  call int3D_rotate_y
  ret



;-------- Rotate_x

;  Inputs:
;	AL - Angle.
;	EDI, EBP - y and z, to be rotated about the x axis.

int3D_rotate_x:
  or al, al
  jz int3D_rotate_x_end
  push bx
  push ecx
  push edx
    ;  Calculate offset in sine table.
  xor bx, bx
  mov bl, al
  shl bx, 2

    ;  NY = Y*cos(AL)-Z*sin(AL)
  mov ecx, edi
  mov eax, _3doms_cos[bx]
  imul ecx
  mov al, dl
  ror eax, 8
  mov edi, eax
  mov eax, _3doms_sin[bx]
  imul ebp
  mov al, dl
  ror eax, 8
  sub edi, eax
    ;  NZ = Y*sin(AL)+Z*cos(AL)
  mov eax, _3doms_cos[bx]
  imul ebp
  mov al, dl
  ror eax, 8
  mov ebp, eax
  mov eax, _3doms_sin[bx]
  imul ecx
  mov al, dl
  ror eax, 8
  add ebp, eax

  pop edx
  pop ecx
  pop bx
int3D_rotate_x_end:
  ret



;-------- Rotate_y

;  Inputs:
;	AL - Angle.
;	ESI, EBP - x and z, to be rotated about the y axis.

int3D_rotate_y:
  or al, al
  jz int3D_rotate_y_end
  push bx
  push ecx
  push edx
    ;  Calculate offset in sine table.
  xor bx, bx
  mov bl, al
  shl bx, 2

    ;  NX = X*cos(AL)-Z*sin(AL)
  mov ecx, esi
  mov eax, _3doms_cos[bx]
  imul ecx
  mov al, dl
  ror eax, 8
  mov esi, eax
  mov eax, _3doms_sin[bx]
  imul ebp
  mov al, dl
  ror eax, 8
  sub esi, eax
    ;  NZ = X*sin(AL)+Z*cos(AL)
  mov eax, _3doms_cos[bx]
  imul ebp
  mov al, dl
  ror eax, 8
  mov ebp, eax
  mov eax, _3doms_sin[bx]
  imul ecx
  mov al, dl
  ror eax, 8
  add ebp, eax

  pop edx
  pop ecx
  pop bx
int3D_rotate_y_end:
  ret



;-------- Rotate_z

;  Inputs:
;	AL - Angle.
;	ESI, EDI - x and y, to be rotated about the z axis.

int3D_rotate_z:
  or al, al
  jz int3D_rotate_z_end
  push bx
  push ecx
  push edx
    ;  Calculate offset in sine table.
  xor bx, bx
  mov bl, al
  shl bx, 2

    ;  NX = X*cos(AL)-Y*sin(AL)
  mov ecx, esi
  mov eax, _3doms_cos[bx]
  imul ecx
  mov al, dl
  ror eax, 8
  mov esi, eax
  mov eax, _3doms_sin[bx]
  imul edi
  mov al, dl
  ror eax, 8
  sub esi, eax
    ;  NY = X*sin(AL)+Y*cos(AL)
  mov eax, _3doms_cos[bx]
  imul edi
  mov al, dl
  ror eax, 8
  mov edi, eax
  mov eax, _3doms_sin[bx]
  imul ecx
  mov al, dl
  ror eax, 8
  add edi, eax

  pop edx
  pop ecx
  pop bx
int3D_rotate_z_end:
  ret



;-------- Project

;  Works out where a point goes on the screen.

;  Inputs:
;	ESI, EDI, EBP - x, y and z as the camera sees them.
;	DS - Point, to put the screen co-ordinates in.

int3D_project:
  push eax
  push ecx
  push edx

    ;  It can't be put on the screen until it passes these tests.
  mov byte ptr ds: [P_OFF], 1

    ;  It has to be at least NEAR_Z in front of the camera.
  cmp ebp, NEAR_Z
  jl int3D_project_end

    ;  And no more than 16 times as far to the side, or up or down, as it is
    ; in front, so the divides can't overflow.
  mov eax, esi
  cdq
  xor eax, edx
  sub eax, edx
  shr eax, 4
  cmp eax, ebp
  ja int3D_project_end
  mov eax, edi
  cdq
  xor eax, edx
  sub eax, edx
  shr eax, 4
  cmp eax, ebp
  ja int3D_project_end

    ;  SX = (scr2eye*X) / Z, from the center of the screen.
  mov ecx, SCR2EYE
  mov eax, esi
  imul ecx
  idiv ebp
  sar eax, 8
  add ax, VIEW_CX
  mov ds: [P_SX], ax
    ;  SY = (scr2eye*Y) / Z.
  mov eax, edi
  imul ecx
  idiv ebp
  sar eax, 8
  add ax, VIEW_CY
  mov ds: [P_SY], ax

  mov byte ptr ds: [P_OFF], 0

int3D_project_end:
  pop edx
  pop ecx
  pop eax
  ret



;----------------------------- Graphics Routines ----------------------------;



;-------- Triangle.

;  Fills a triangle, leaving out any of it that's off the screen.

;  Inputs:
;	AL - Colour.
;	_tri_x1 to _tri_y3 - Corners.
;	ES - Output segment.

int3D_tri:
  push eax
  push ebx
  push ecx
  push edx
  push esi
  push di

  mov _tri_col, al

    ;  Sort the co-ordinates into order - lowest y-value first.
    ;  Swap (X1,Y1) and (X2,Y2) if necessary.
  mov ax, _tri_y1
  cmp ax, _tri_y2
  jle int3D_tri_y1ok
  xchg ax, _tri_y2
  mov _tri_y1, ax
  mov ax, _tri_x1
  xchg ax, _tri_x2
  mov _tri_x1, ax
int3D_tri_y1ok:
    ;  Swap (X2,Y2) and (X3,Y3) if necessary.
  mov ax, _tri_y2
  cmp ax, _tri_y3
  jle int3D_tri_y2ok
  xchg ax, _tri_y3
  mov _tri_y2, ax
  mov ax, _tri_x2
  xchg ax, _tri_x3
  mov _tri_x2, ax
int3D_tri_y2ok:
    ;  Swap (X1,Y1) and (X2,Y2) again if necessary.
  mov ax, _tri_y1
  cmp ax, _tri_y2
  jle int3D_tri_y1ok2
  xchg ax, _tri_y2
  mov _tri_y1, ax
  mov ax, _tri_x1
  xchg ax, _tri_x2
  mov _tri_x1, ax
int3D_tri_y1ok2:

    ;  Only the rows that are on the screen are drawn.
  mov ax, _tri_y1
  cmp ax, 0
  jge int3D_tri_topok
  xor ax, ax
int3D_tri_topok:
  mov _tri_y, ax
  mov ax, _tri_y3
  cmp ax, 199
  jle int3D_tri_bottomok
  mov ax, 199
int3D_tri_bottomok:
  mov _tri_yend, ax

int3D_tri_row:
  mov ax, _tri_y
  cmp ax, _tri_yend
  jg int3D_tri_end

    ;  If all three corners are on one row, the row goes from the leftmost
    ; to the rightmost.
  mov ax, _tri_y3
  cmp ax, _tri_y1
  jne int3D_tri_notflat
  movsx ebx, _tri_x1
  mov edx, ebx
  movsx eax, _tri_x2
  cmp eax, ebx
  jge int3D_tri_flat1
  mov ebx, eax
int3D_tri_flat1:
  cmp eax, edx
  jle int3D_tri_flat2
  mov edx, eax
int3D_tri_flat2:
  movsx eax, _tri_x3
  cmp eax, ebx
  jge int3D_tri_flat3
  mov ebx, eax
int3D_tri_flat3:
  cmp eax, edx
  jle int3D_tri_span
  mov edx, eax
  jmp int3D_tri_span

int3D_tri_notflat:
    ;  Where the long edge, from the first corner to the last, crosses this
    ; row: X1 + (X3-X1)*(Y-Y1)/(Y3-Y1).
  movsx eax, _tri_x3
  movsx ecx, _tri_x1
  sub eax, ecx
  movsx ecx, _tri_y
  movsx esi, _tri_y1
  sub ecx, esi
  imul ecx
  movsx ecx, _tri_y3
  sub ecx, esi
  idiv ecx
  movsx ecx, _tri_x1
  add eax, ecx
  mov ebx, eax

    ;  Where the short edges cross it: the first corner to the middle one
    ; above the middle corner, and the middle one to the last from there on.
  mov ax, _tri_y
  cmp ax, _tri_y2
  jge int3D_tri_lower
  movsx eax, _tri_x2
  movsx ecx, _tri_x1
  sub eax, ecx
  movsx ecx, _tri_y
  movsx esi, _tri_y1
  sub ecx, esi
  imul ecx
  movsx ecx, _tri_y2
  sub ecx, esi
  idiv ecx
  movsx ecx, _tri_x1
  add eax, ecx
  mov edx, eax
  jmp int3D_tri_span
int3D_tri_lower:
  mov ax, _tri_y3
  cmp ax, _tri_y2
  jne int3D_tri_lowerslope
  movsx edx, _tri_x2
  jmp int3D_tri_span
int3D_tri_lowerslope:
  movsx eax, _tri_x3
  movsx ecx, _tri_x2
  sub eax, ecx
  movsx ecx, _tri_y
  movsx esi, _tri_y2
  sub ecx, esi
  imul ecx
  movsx ecx, _tri_y3
  sub ecx, esi
  idiv ecx
  movsx ecx, _tri_x2
  add eax, ecx
  mov edx, eax

int3D_tri_span:
    ;  EBX and EDX are the ends of the row.  Put them in order, and clip them
    ; to the screen.
  cmp ebx, edx
  jle int3D_tri_ordered
  xchg ebx, edx
int3D_tri_ordered:
  cmp edx, 0
  jl int3D_tri_next
  cmp ebx, 319
  jg int3D_tri_next
  cmp ebx, 0
  jge int3D_tri_leftok
  xor ebx, ebx
int3D_tri_leftok:
  cmp edx, 319
  jle int3D_tri_rightok
  mov edx, 319
int3D_tri_rightok:

    ;  Horiz line.
  movzx eax, _tri_y
  imul eax, eax, 320
  add eax, ebx
  mov di, ax
  mov ecx, edx
  sub ecx, ebx
  inc ecx
  mov al, _tri_col
  call int3D_hline

int3D_tri_next:
  inc _tri_y
  jmp int3D_tri_row

int3D_tri_end:
  pop di
  pop esi
  pop edx
  pop ecx
  pop ebx
  pop eax
  ret



;-------- Horizontal line.

;  Inputs:
;	AL - Colour.
;	CX - Number of pixels.
;	ES:DI - Where it starts.

int3D_hline:
  jcxz int3D_hline_end

;  rep stosb		;  Slow but safe while testing rendering functions.
;  ret

  shr cx, 1
  jnc int3D_hline_nobyte
  stosb
int3D_hline_nobyte:
  jcxz int3D_hline_end
  mov ah, al
  shr cx, 1
  jnc int3D_hline_noword
  stosw
int3D_hline_noword:
  jcxz int3D_hline_end
  push ax
  rol eax, 16
  pop ax
  rep stosd
int3D_hline_end:
  ret



;----------------------------- Work Areas -----------------------------------;



  ;  Transformation stack: 16 bytes a level, the same as an object's
  ; position and angles.
_3doms_tstack		db TSTACK_LEVELS*16 dup (0)

  ;  Draw list.
_3doms_sorttab		db SORT_ENTRIES*SORT_SIZE dup (0)
_3doms_sortend		label byte

  ;  Stack used while rendering.
_3doms_stack		db STACK_SIZE dup (0)
_3doms_stacktop		label word



int3D_last	label byte



;--------  End of code.