Graf3D
Volume Number: | | 4
|
Issue Number: | | 6
|
Column Tag: | | Forth Forum
|
Graf3D Library Access
By Jörg Langowski, MacTutor Editorial Board
Graf3D and Mach2 - accessing external libraries
The subject of this month has been initiated by one of our readers, Paul Thomas, who in a desperate mood sent me both a paper letter and GEnie mail, because he needed to know whether it would be possible at all to access the Graf3D routines from Mach2.
A little background for those who werent with the Mac (or with us, for that matter) from the beginning: Graf3D is a set of routines on top of QuickDraw, which enable to create a 3-d GrafPort with all necessary support to do 3-dimensional perspective drawing. The routines arent explained in Inside Macintosh, but in several other different documents, one of them being the MPW Pascal manual (Appendix J).
Graf3D was also part of the Lisa Workshop, and one of the first Macintosh demo programs, Boxes, used those routines. The Boxes program - many of you might have seen it - draws at random fifteen rectangular boxes on a plane and displays them in a 3-d perspective view. The program is written originally in Lisa Pascal, and also contained on the TML Pascal disk as an example. Listing 4 shows the original program.
Glancing through the code, you might notice that this example certainly wouldnt please the User Interface Thought Police today. Using the whole screen as a GrafPort without regard to anything else on there is a definite no-no in the days of Multifinder, and if you run the example under Multifinder, you wont find your windows back on the screen afterwards because they are all covered by little boxes. Nevertheless, it is a nice example for the use of Graf3D, and well show you this month how to translate it into Mach2 (Well also correct the garbled screen problem under Multifinder).
First and most difficult question: How can we access routines from Forth that are clearly labeled (Not in ROM) in the documentation? This remark tells us, of course, that they are part of some object library, no source code available and inaccessible to the Forth user. Just like the printing manager was before it was implemented in the Mach2 and MacForth systems.
There may be other libraries to come, also not in ROM, and waiting for updates takes a long time. So Ill give you two general strategies to get access to object library code from Forth, making you independent of those updates.
The first scheme I thought up was simply to write a short Pascal program that calls all the routines at least once - so that they will be loaded -, compile that program, create a linker map if possible, and pass the final application through Nosy. That will get you a more or less annotated assembly listing from which you can extract the source code of the not in ROM routines that you are interested in.
Well, that works for you and me on our respective desks, but we certainly cannot publish reverse-engineered Apple source code in MacTutor, at least if we want to stay in business. Also, it is a lot of work (i.e. translating to a different assembler dialect, writing the glue code, etc.), and all in all qualifies as a master example of a dirty hack.
There is an easier way to go, and much more general, too: write some sort of a linker for Pascal library code. We write an assembly main program (Listing 2), which is just a table of JMP instructions to the routines that we want to call, and let the MPW linker do its job (listing 3).
The assembly/link script will create a new resource, gr3D, which contains the jump instructions at the beginning, followed by the linked Graf3D code. All we have to do now is to provide a block of memory in our Forth code where this resource can be copied to.
In order to assign Forth words to the JMP instructions, we simply make a table of CREATEs, which will allocate 4 bytes to each name (the length of a JMP d(PC) instruction), and create a dictionary entry so that we know the routines address in the jump table. After the block of CREATEs, we allocate sufficient memory so that the gr3D resource can be loaded (see listing 1).
We write a word, Init3D, that gets the gr3D resource and copies it into the table, starting with the address of the first Graf3d word, gInitGrf3D. Now the Graf3D routines are all set up for use by Mach2. As simple as that.
Well, not quite. We still need to write glue code to move the parameters from the Forth stack (A6) to the Pascal stack (A7). That glue code is rather simple and is just the same as is used for calling toolbox ROM routines. Like the traps, the Graf3D code preserves all registers that are vital to Mach2 (A2-A7, D4-D7). Like in the case of traps, we have to make A7 point to the lowest stack (EXG D4,A7) in order to avoid overwriting of Mach2 space by the intermediate Quickdraw record that is put between the stack and the heap. The glue code for the different Graf3D routines is given at the beginning of listing 1.
The Boxes examples should be self-explanatory when you compare it with the Pascal code; in fact, it might help some of you Pascal programmers and casual Forth readers appreciate that there is really no big difference between Forth and Pascal code...
I should point out some more things. First, the access words for fields in a record, .x, .y, .pt1, and so on, defined at the beginning of the Forth code. Second, the word restore.screen, which cleans up the display after the example has run; very important when running under Multifinder. It basically does a PaintBehind(FrontWindow, grayRgn), the same mechanism by which many screen savers restore a blacked-out screen. Third, we define a terminal task that runs the Boxes example; that way, we can simply check for ?terminal after each drawing loop to see whether were done. Again, we have to make sure (like always in Mach2 multitasking) that the correct GrafPort is set by calling myPort3D SetPort3D before each passage through the loop. This is because any PAUSE-containing word, such as ?terminal, may change the active GrafPort.
For using the example, you will first have to create the Graf3Dglue resource file with the gr3D resource in it, then move that resource into the MACH.RSRC file before starting your Mach2 system. In case you dont have access to MPW, the source code disk contains the Graf3Dglue and MACH.RSRC files. The complete program, of course, is also on the disk.
Thats it for this month; next month I plan to discuss various projects of object-oriented extensions to Mach2 that have recently appeared on the networks. Some of you might also appreciate that NEON hasnt died altogether, but that there is at least one person trying to extend it in a very interesting way.
Listing 1: The Boxes example in Mach2
\ Graf3d / Mach2 glue code
\ An example for calling MPW routines from Forth
\ J. Langowski April 1988
\ __________________________________________
Only Forth Also Mac Also Assembler
\ some general definitions first
16 CONSTANT portRect
GLOBAL
CODE SCALE
MOVE.L (A6)+,D0
BMI.S @1
MOVE.L (A6),D1
ASL.L D0,D1
MOVE.L D1,(A6)
RTS
@1 MOVE.L (A6),D1
NEG.L D0
ASR.L D0,D1
MOVE.L D1,(A6)
RTS
END-CODE
global
CODE white
MOVE.L (A5),-(A6)
SUBQ.L #8,(A6)
RTS
END-CODE MACH
global
CODE black
MOVE.L (A5),-(A6)
SUBI.L #16,(A6)
RTS
END-CODE MACH
global
CODE gray
MOVE.L (A5),-(A6)
SUBI.L #24,(A6)
RTS
END-CODE MACH
: 4ASCII
0
4 0 DO
8 SCALE 0 WORD 1+ C@ +
LOOP
;
4ASCII gr3D CONSTANT gr3D \ resource ID
\ Graf3D jump table
CREATE gInitGrf3D
CREATE gOpen3DPort
CREATE gSetPort3D
CREATE gGetPort3D
CREATE gMoveTo2D
CREATE gMoveTo3D
CREATE gLineTo2D
CREATE gLineTo3D
CREATE gMove2D
CREATE gMove3D
CREATE gLine2D
CREATE gLine3D
CREATE gViewPort
CREATE gLookAt
CREATE gViewAngle
CREATE gIdentity
CREATE gScale
CREATE gTranslate
CREATE gPitch
CREATE gYaw
CREATE gRoll
CREATE gSkew
CREATE gTransform
CREATE gClip3D
CREATE gSetPt3D
CREATE gSetPt2D
\ The glue code is 2636 bytes long, so we allocate
\ sufficient additional buffer space to put it into
2600 ALLOT
: Init3D \ gets Graf3D code from gr3D=1 resource
\ and copies it into buffer
gr3D 1 call GetResource
dup @ swap call SizeRsrc
[] gInitGrf3D swap cmove
;
CODE InitGrf3d ( globalPtr -- )
EXG D4,A7
MOVE.L (A6)+,-(A7)
JSR gInitGrf3d
EXG D4,A7
RTS
END-CODE
CODE Open3DPort ( port -- )
EXG D4,A7
MOVE.L (A6)+,-(A7)
JSR gOpen3DPort
EXG D4,A7
RTS
END-CODE
CODE SetPort3d ( port -- )
EXG D4,A7
MOVE.L (A6)+,-(A7)
JSR gSetPort3d
EXG D4,A7
RTS
END-CODE
CODE GetPort3d ( VAR port -- )
EXG D4,A7
MOVE.L (A6)+,-(A7)
JSR gGetPort3d
EXG D4,A7
RTS
END-CODE
CODE MoveTo2d ( x y -- )
EXG D4,A7
MOVE.L 4(A6),-(A7)
MOVE.L (A6),-(A7)
ADDA.W #8,A6
JSR gMoveTo2d
EXG D4,A7
RTS
END-CODE
CODE MoveTo3d ( x y z -- )
EXG D4,A7
MOVE.L 8(A6),-(A7)
MOVE.L 4(A6),-(A7)
MOVE.L (A6),-(A7)
ADDA.W #12,A6
JSR gMoveTo3d
EXG D4,A7
RTS
END-CODE
CODE LineTo2d ( x y -- )
EXG D4,A7
MOVE.L 4(A6),-(A7)
MOVE.L (A6),-(A7)
ADDA.W #8,A6
JSR gLineTo2d
EXG D4,A7
RTS
END-CODE
CODE LineTo3d ( x y z -- )
EXG D4,A7
MOVE.L 8(A6),-(A7)
MOVE.L 4(A6),-(A7)
MOVE.L (A6),-(A7)
ADDA.W #12,A6
JSR gLineTo3d
EXG D4,A7
RTS
END-CODE
CODE Move2d ( dx dy -- )
EXG D4,A7
MOVE.L 4(A6),-(A7)
MOVE.L (A6),-(A7)
ADDA.W #8,A6
JSR gMove2d
EXG D4,A7
RTS
END-CODE
CODE Move3d ( dx dy dz -- )
EXG D4,A7
MOVE.L 8(A6),-(A7)
MOVE.L 4(A6),-(A7)
MOVE.L (A6),-(A7)
ADDA.W #12,A6
JSR gMove3d
EXG D4,A7
RTS
END-CODE
CODE Line2d ( x y -- )
EXG D4,A7
MOVE.L 4(A6),-(A7)
MOVE.L (A6),-(A7)
ADDA.W #8,A6
JSR gLine2d
EXG D4,A7
RTS
END-CODE
CODE Line3d ( x y z -- )
EXG D4,A7
MOVE.L 8(A6),-(A7)
MOVE.L 4(A6),-(A7)
MOVE.L (A6),-(A7)
ADDA.W #12,A6
JSR gLine3d
EXG D4,A7
RTS
END-CODE
CODE ViewPort ( r -- )
EXG D4,A7
MOVE.L (A6)+,-(A7)
JSR gViewPort
EXG D4,A7
RTS
END-CODE
CODE LookAt ( left top right bottom -- )
EXG D4,A7
MOVE.L 12(A6),-(A7)
MOVE.L 8(A6),-(A7)
MOVE.L 4(A6),-(A7)
MOVE.L (A6),-(A7)
ADDA.W #16,A6
JSR gLookAt
EXG D4,A7
RTS
END-CODE
CODE ViewAngle ( angle -- )
EXG D4,A7
MOVE.L (A6)+,-(A7)
JSR gViewAngle
EXG D4,A7
RTS
END-CODE
CODE Identity
EXG D4,A7
JSR gIdentity
EXG D4,A7
RTS
END-CODE
CODE Scal ( xfactor yfactor zfactor -- )
EXG D4,A7
MOVE.L 8(A6),-(A7)
MOVE.L 4(A6),-(A7)
MOVE.L (A6),-(A7)
ADDA.W #12,A6
JSR gScale
EXG D4,A7
RTS
END-CODE
CODE Translate ( dx dy dz -- )
EXG D4,A7
MOVE.L 8(A6),-(A7)
MOVE.L 4(A6),-(A7)
MOVE.L (A6),-(A7)
ADDA.W #12,A6
JSR gTranslate
EXG D4,A7
RTS
END-CODE
CODE Pitch( xangle -- )
EXG D4,A7
MOVE.L (A6)+,-(A7)
JSR gPitch
EXG D4,A7
RTS
END-CODE
CODE Yaw( yangle -- )
EXG D4,A7
MOVE.L (A6)+,-(A7)
JSR gYaw
EXG D4,A7
RTS
END-CODE
CODE Rol( zangle -- )
EXG D4,A7
MOVE.L (A6)+,-(A7)
JSR gRoll
EXG D4,A7
RTS
END-CODE
CODE Skew ( zangle -- )
EXG D4,A7
MOVE.L (A6)+,-(A7)
JSR gSkew
EXG D4,A7
RTS
END-CODE
CODE Transform ( src dst -- )
EXG D4,A7
MOVE.L 4(A6),-(A7)
MOVE.L (A6),-(A7)
ADDA.W #8,A6
JSR gTransform
EXG D4,A7
RTS
END-CODE
CODE Clip3D ( src1 src2 dst1 dst2 -- flag )
EXG D4,A7
CLR.W -(A7)
MOVE.L 12(A6),-(A7)
MOVE.L 8(A6),-(A7)
MOVE.L 4(A6),-(A7)
MOVE.L (A6),-(A7)
ADDA.W #16,A6
JSR gClip3D
MOVE.W (A7)+,D0
EXT.L D0
MOVE.L D0,-(A6)
EXG D4,A7
RTS
END-CODE
CODE SetPt3d( pt3D x y z -- )
EXG D4,A7
MOVE.L 12(A6),-(A7)
MOVE.L 8(A6),-(A7)
MOVE.L 4(A6),-(A7)
MOVE.L (A6),-(A7)
ADDA.W #16,A6
JSR gSetPt3D
EXG D4,A7
RTS
END-CODE
CODE SetPt2d( pt2D x y -- )
EXG D4,A7
MOVE.L 8(A6),-(A7)
MOVE.L 4(A6),-(A7)
MOVE.L (A6),-(A7)
ADDA.W #12,A6
JSR gSetPt2D
EXG D4,A7
RTS
END-CODE
\ Translation of Boxes.pas example into Forth follows
\ _________________________________________
: .x ; mach
: .y 4 + ; mach
: .z 8 + ; mach
: .pt1 ; mach
: .pt2 12 + ; mach
15 CONSTANT BoxCount
Variable MyPort 104 vallot
\ grafPort is 108 bytes long
Variable MyPort3D 150 vallot
\ graf3DPort is 154 bytes long
Variable boxArray 24 BoxCount * vallot
\ BoxCount * 2* point3D @ 3 long words
Variable nboxes \ # of boxes made
Variable MyBox 20 vallot \ 24 bytes for 2* point3d
Variable p1 8 vallot \ point3d
Variable p2 8 vallot \ point3d
Variable myRect 4 vallot \ Rect
Variable testRect 4 vallot \ Rect
: DrawBrick { pt1 pt2 | tempRgn -- }
call NewRgn -> tempRgn
call OpenRgn
pt1 .x @ pt1 .y @ pt1 .z @ MoveTo3D
pt1 .x @ pt1 .y @ pt2 .z @ LineTo3D
pt2 .x @ pt1 .y @ pt2 .z @ LineTo3D
pt2 .x @ pt1 .y @ pt1 .z @ LineTo3D
pt1 .x @ pt1 .y @ pt1 .z @ LineTo3D
tempRgn call CloseRgn
tempRgn white call FillRgn
call OpenRgn
pt1 .x @ pt1 .y @ pt2 .z @ MoveTo3D
pt1 .x @ pt2 .y @ pt2 .z @ LineTo3D
pt2 .x @ pt2 .y @ pt2 .z @ LineTo3D
pt2 .x @ pt1 .y @ pt2 .z @ LineTo3D
pt1 .x @ pt1 .y @ pt2 .z @ LineTo3D
tempRgn call CloseRgn
tempRgn gray call FillRgn
call OpenRgn
pt2 .x @ pt1 .y @ pt1 .z @ MoveTo3D
pt2 .x @ pt1 .y @ pt2 .z @ LineTo3D
pt2 .x @ pt2 .y @ pt2 .z @ LineTo3D
pt2 .x @ pt2 .y @ pt1 .z @ LineTo3D
pt2 .x @ pt1 .y @ pt1 .z @ LineTo3D
tempRgn call CloseRgn
tempRgn black call FillRgn
white call penpat
pt2 .x @ pt2 .y @ pt2 .z @ MoveTo3D
pt2 .x @ pt2 .y @ pt1 .z @ LineTo3D
pt2 .x @ pt1 .y @ pt1 .z @ LineTo3D
call pennormal
tempRgn call DisposRgn
;
: hi -16 scale ;
: chkBox { | box -- }
1 ( flag )
nBoxes @ 0 DO
boxArray i 24 * + -> box
testRect
box .pt1 .x @ hi
box .pt1 .y @ hi
box .pt2 .x @ hi
box .pt2 .y @ hi call SetRect
testRect -1 -1 call InSetRect
myRect testRect testRect call SectRect
IF drop 0 leave THEN
LOOP
;
: MakeBox { | p1x p1y p1z p2x p2y p2z box ii -- }
call random 70 mod 15 - 1 call FixRatio -> p1x
call random 70 mod 10 - 1 call FixRatio -> p1y
0 -> p1z
call random 30 mod abs 10 + 1 call FixRatio p1x +
-> p2x
call random 45 mod abs 10 + 1 call FixRatio p1y +
-> p2y
call random 30 mod abs 10 + 1 call FixRatio p1z +
-> p2z
myRect p1x hi p1y hi p2x hi p2y hi call SetRect
chkBox IF
p1x myBox .pt1 .x !
p1y myBox .pt1 .y !
p1z myBox .pt1 .z !
p2x myBox .pt2 .x !
p2y myBox .pt2 .y !
p2z myBox .pt2 .z !
0 -> ii
myBox boxArray nBoxes @ 24 * + 24 cmove
BEGIN
myBox .pt1 .y @ boxArray ii + .pt2 .y @ >
myBox .pt2 .y @ boxArray ii + .pt1 .y @ > and
myBox .pt1 .x @ boxArray ii + .pt2 .x @ <
myBox .pt2 .x @ boxArray ii + .pt1 .x @ < and or
WHILE
24 +> ii
REPEAT
ii 24 / -> ii
ii 1+ nBoxes @ DO
boxArray i 1- 24 * +
boxArray i 24 * +
24 cmove
-1 +loop
myBox boxArray ii 24 * + 24 cmove
1 nBoxes +!
THEN
;
: drawGrid
11 -10 DO
i 10 * 1 call FixRatio -100 1 call FixRatio 0
MoveTo3D
i 10 * 1 call FixRatio 100 1 call FixRatio 0
LineTo3D
LOOP
11 -10 DO
-100 1 call FixRatio i 10 * 1 call FixRatio 0
MoveTo3D
100 1 call FixRatio i 10 * 1 call FixRatio 0
LineTo3D
LOOP
;
: restore.screen
call drawmenubar
call frontwindow
grayrgn @ call paintbehind
call showcursor
;
: main
init3d
call hidecursor
myPort call OpenPort
myPort3D Open3DPort
myPort portRect + ViewPort
-100 1 call FixRatio 75 1 call FixRatio
100 1 call FixRatio -75 1 call FixRatio
LookAt
30 1 call FixRatio ViewAngle
Identity
20 1 call FixRatio Rol
70 1 call FixRatio Pitch
BEGIN
myPort3D SetPort3D
0 nBoxes !
BEGIN makeBox nBoxes @ boxCount = UNTIL
white call penPat
black call backPat
myPort portRect + call EraseRect
drawGrid
0 nBoxes @ 1- DO
boxArray i 24 * +
dup .pt1 swap .pt2 DrawBrick
-1 +LOOP
?terminal UNTIL
restore.screen
bye
;
NEW.WINDOW Boxes
Boxes Boxes TITLE
0 0 20 20 Boxes BOUNDS
Plain Visible NoCloseBox NoGrowBox Boxes ITEMS
600 5000 terminal Box
: go.box activate main ;
: start
Boxes add
Boxes Box build
Box go.Box
;
.( To create a turkey application ) cr
.( type TURNKEY START BOXES )
Listing 2: MPW Assembly glue code for accessing the Graf3D Pascal library
TITLE Graf3D glue code for Mach2
BLANKS OFF
PRINT OFF
INCLUDE Traps.a
INCLUDE ToolEqu.a
INCLUDE QuickEqu.a
INCLUDE SysEqu.a
INCLUDE Graf3DEqu.a
PRINT ON
GrafGlueMAIN
JMP InitGrf3D
JMP Open3DPort
JMP SetPort3D
JMP GetPort3D
JMP MoveTo2D
JMP MoveTo3D
JMP LineTo2D
JMP LineTo3D
JMP Move2D
JMP Move3D
JMP Line2D
JMP Line3D
JMP ViewPort
JMP LookAt
JMP ViewAngle
JMP Identity
JMP Scale
JMP Translate
JMP Pitch
JMP Yaw
JMP Roll
JMP Skew
JMP Transform
JMP Clip3D
JMP SetPt3D
JMP SetPt2D
END
Listing 3: MPW assembly / link script for generating the gr3D resource
(to be put into the MACH.RSRC file)
Asm Graf3dglue.a -l
Link Graf3dglue.a.o
{Libraries}Interface.o
-rt gr3D=1 -o Graf3dglue -l > Graf3dglue.map
Listing 4: The original Boxes example (from TML Pascal)
PROGRAM Boxes;
{ Boxes demonstrates the use of the Three Dimensional
Quickdraw package Graf3D.
Pascal source: Boxes.Pas
Resources: *none*
This program is from Apples Lisa Software Supplement,
modified for TML Systems Pascal compiler.
}
USES MacIntf, FixMath, Graf3D;
CONST boxCount = 15;
keyOrMouse = 10;
TYPE Box3D =
RECORD
pt1: Point3D;
pt2: Point3D;
END;
VARmyPort: GrafPort;
myPort3D: Port3D;
boxArray: ARRAY [0..boxCount] OF Box3D;
nBoxes: INTEGER;
i: INTEGER;
dummy: EventRecord;
PROCEDURE DrawBrick(pt1, pt2: Point3D);
{ draws a 3D brick with shaded faces.
only shades correctly in one direction.
}
VARtempRgn: RgnHandle;
BEGIN
tempRgn := NewRgn;
OpenRgn;
MoveTo3D(pt1.X, pt1.Y, pt1.Z); { front face, y=y1 }
LineTo3D(pt1.X, pt1.Y, pt2.Z);
LineTo3D(pt2.X, pt1.Y, pt2.Z);
LineTo3D(pt2.X, pt1.Y, pt1.Z);
LineTo3D(pt1.X, pt1.Y, pt1.Z);
CloseRgn(tempRgn);
FillRgn(tempRgn, white);
OpenRgn;
MoveTo3D(pt1.X, pt1.Y, pt2.Z); { top face, z=z2 }
LineTo3D(pt1.X, pt2.Y, pt2.Z);
LineTo3D(pt2.X, pt2.Y, pt2.Z);
LineTo3D(pt2.X, pt1.Y, pt2.Z);
LineTo3D(pt1.X, pt1.Y, pt2.Z);
CloseRgn(tempRgn);
FillRgn(tempRgn, gray);
OpenRgn;
MoveTo3D(pt2.X, pt1.Y, pt1.Z); { right face, x=x2 }
LineTo3D(pt2.X, pt1.Y, pt2.Z);
LineTo3D(pt2.X, pt2.Y, pt2.Z);
LineTo3D(pt2.X, pt2.Y, pt1.Z);
LineTo3D(pt2.X, pt1.Y, pt1.Z);
CloseRgn(tempRgn);
FillRgn(tempRgn, black);
PenPat(white);
MoveTo3D(pt2.X, pt2.Y, pt2.Z); { outline right }
LineTo3D(pt2.X, pt2.Y, pt1.Z);
LineTo3D(pt2.X, pt1.Y, pt1.Z);
PenNormal;
DisposeRgn(tempRgn);
END;
PROCEDURE MakeBox;
LABEL 1;
VARmyBox: Box3D;
i, j, h, v: INTEGER;
p1, p2: Point3D;
myRect: Rect;
testRect: Rect;
BEGIN
p1.x := FixRatio((Random mod 70 - 15), 1);
p1.y := FixRatio((Random mod 70 - 10), 1);
p1.z := 0;
p2.x := p1.x + FixRatio((10 + ABS(Random) MOD 30), 1);
p2.y := p1.y + FixRatio((10 + ABS(Random) MOD 45), 1);
p2.z := p1.z + FixRatio((10 + ABS(Random) MOD 35), 1);
{ reject box if it intersects one already in list }
SetRect(myRect, HiWord(p1.x), HiWord(p1.y), HiWord(p2.x), HiWord(p2.y));
FOR i := 0 TO nBoxes - 1 DO BEGIN
WITH boxArray[i] DO
SetRect(testRect,
HiWord(pt1.x),
HiWord(pt1.y),
HiWord(pt2.x),
HiWord(pt2.y));
InSetRect(testRect, -1, -1);
IF SectRect(myRect, testRect, testRect) THEN goto 1
END;
myBox.pt1 := p1;
myBox.pt2 := p2;
{ calc midpoint of box and its distance from the eye }
i := 0;
boxArray[nBoxes].pt1 := myBox.pt1; { sentinel }
boxArray[nBoxes].pt2 := myBox.pt2;
WHILE
((myBox.pt1.y > boxArray[i].pt2.y) AND (myBox.pt2.y > boxArray[i].pt1.y))
OR
((myBox.pt1.x < boxArray[i].pt2.x) AND (myBox.pt2.x < boxArray[i].pt1.x))
DO
inc(i); { insert in order of dist }
FOR j := nBoxes DOWNTO i + 1 DO boxArray[j] := boxArray[j - 1];
boxArray[i] := myBox;
inc(nBoxes);
1:
END;
BEGIN { main program }
FlushEvents(everyEvent, 0);
InitGraf(@thePort);
InitCursor;
HideCursor;
OpenPort(@myPort);
Open3DPort(@myPort3D);
{ put the image in this rect }
ViewPort(myPort.portRect);
{ aim the camera into 3D space }
LookAt(FixRatio(-100, 1),
FixRatio(75, 1),
FixRatio(100, 1),
FixRatio(-75, 1));
{ choose lens focal length }
ViewAngle(FixRatio(30, 1));
Identity;
Roll(FixRatio(20, 1));
Pitch(FixRatio(70, 1)); { roll and pitch the plane }
REPEAT
nBoxes := 0;
REPEAT
MakeBox
UNTIL nBoxes=boxCount;
PenPat(white);
BackPat(black);
EraseRect(myPort.portRect);
FOR i := -10 TO 10 DO BEGIN
MoveTo3D(FixRatio(i*10, 1), FixRatio(-100, 1), 0);
LineTo3D(FixRatio(i*10, 1), FixRatio(100, 1), 0);
END;
FOR i := -10 TO 10 DO BEGIN
MoveTo3D(FixRatio(-100, 1), FixRatio(i*10, 1), 0);
LineTo3D(FixRatio(100, 1), FixRatio(i*10, 1), 0);
END;
FOR i := nBoxes-1 DOWNTO 0 DO
DrawBrick(boxArray[i].pt1,boxArray[i].pt2);
UNTIL OSEventAvail(keyOrMouse, dummy);
END.