Apple II Saturn Reborn

A few books from my small library

Growing up, I had a love for computers the first time we got them in our 6th grade class. The Apple II. I was so amazed by this device that could output graphics and sound, and those could be used in games or applications or whatever, from programs entered entirely by the user. Oregon Trail was one of the class favorites, something very minimal in regards to a game, but it was more interactive than anything else we had at the time.

Even though I desparately wanted an Apple computer, I knew there was no way my parents could afford one. So in middle school, I would spend lunch hours and time after school in the computer lab, using the computer books and magazines from either the school or the local library as references and punching in the programs contained within, key by key, before I had taken any typing class.

One of the programs was from the "Graphics for the Apple" book (pictured above). It was just called "Saturn", and it would draw a shaded model of Saturn with rings. I thought this was just one of the coolest things ever - an actual 3D shaded model on an Apple! At the time, most every game was 2D, and for good reason - this personal computer was one of the first, and was of course, not powerful at all. I do remember a 3D wireframe game that came out later, Stellar 7, which I really enjoyed and thought was another amazing achievment, actual 3D gameplay in real time.

At the time, I had no idea how the program worked. Sure, the book explained how the code worked, but I had no idea how any of these sines and cosines and matrix transformations worked - I wouldn't be introduced to trigonometry until my junior year of high school. For the most part I would just key in the programs, line by line, making sure to copy exactly what was written. After debugging and getting the program to run, the only thing that was rather disappointing was how long it took to run. If I made the size of Saturn and the rings large enough to fill the 140 by 80 pixel screen, it took just forever to run. We're talking like a pixel or two every second. Even back then, I knew that BASIC programs were much slower than ones made in Assembler, but I wasn't prepared for it to be quite so slow.

Decades later, being experienced with computers and programming, I ran across the "Graphics For The IBM PC" book online, recognizing the pixelated yellow picture of Saturn on the cover, and bought it for nostalgic reasons. I wouldn't find the Apple version until later, which I of course picked up as well, seeing as that's the original book I had, and also, I had over the years picked up some old Apple II / IIc / IIe computers that I could still use to program if I wanted.

I had always kicked around the idea of making the original program work on modern hardware, but the idea was usually supplanted with another project that was more interesting. I finally got around to dusting off the books and try to run it in a web browser using the canvas element. I thought it was going to be more difficult than it was. The hardest part was recreating some of the logic in the program: within a few IF/THEN statements were some GOTO and GOSUB statements that go to specific line numbers of the program - programming constructs that aren't used in modern languages anymore, and for good reason. However, back in the day, they were the only way to accomplish complicated logic branching using the limited BASIC instruction set.

I made a few changes to the code. One was the smoothing factor of the shading, I turned that up, since I felt it was way too blocky. Interestingly, the cover of the books shows the shading as more smooth; I have a feeling they changed the code making the shading to be blockier in order for it to run faster, since it was already dreadfully slow. There are shading and arc routines and such in the canvas toolset, but I didn't want to recreate it using those, I wanted the same pixel-by-pixel code. So I simply use the canvas fillRect() method to make small rectangles for the pixels. The original code did the rings and shading by using for-loops and plotting out lines, which ends up creating some interesting moiré patterns, which I wanted to preserve. The code would also ask for inputs. In this version, I just created sliders. I also included sliders for the light vector direction, which wasn't part of the original code. I increased the space between the rings and Saturn a bit. Also, 140x80 pixels is a bit small, so I bumped it up to 640x400. Still using the original Apple's amazing white/cyan/magenta hi-res color pallete, however.

10 REM ----------------------------------------------------------------------------- SATURN
11 REM ------------------------------------------------ COPYRIGHT 1983 BY KERN PUBLICATIONS
12 HGR
20 PRINT "SATURN"
22 PRINT "DRAWS SATURN IN COLOR"
30 GET A$
41 DIM C(3,3)
42 NP = 4: DIM X (NP), Y (NP), Z (NP)
1000 REM ----------------------------------------------------------------------------- DRAW
1005 XO = 140:YO = 80:ZO = 0
1009 INPUT "RADIUS OF SATURN (RS)";RS
1011 INPUT "RING RADIUS (RO) ";RR
1012 INPUT "RING X ANGLE (RX)";RX
1013 INPUT "RING Y ANGLE (RY)";RY
1014 INPUT "RING Z ANGLE (RZ)";RZ
1015 A = 3.14159 / 180
1016 RX = RX * A:RY = RY * A:RZ = RZ * A
1018 HGR
1019 GOSUB 9500: REM --FILL PLANET WITH COLOR
1020 LX = 1: REM --ESTABLISH LIGHT DIRECTION
1030 LY = 1
1040 LZ = 1
1050 QL = SQR (LX * LX + LY * LY + LZ * LZ)
1060 LX = LX / QL
1070 LY = LY / QL
1080 LZ = LZ / QL
5000 DP = 4 * 3.14159 / 180: REM --SHADE SPHERE
5008 P1 = DP / 2
5009 P2 = 3.14159 - DP / 2
5010 FOR PH = P1 TO P2 STEP DP
5018 A1 = 3.14159 / 2 - DP / 2
5019 A2 = - A1
5020 FOR AL = A1 TO A2 STEP - DP
5029 A = DP / 2
5030 X(1) = RS * COS (AL + A) * COS (PH + A)
5031 X(2) = RS * COS (AL + A) * COS (PH - A)
5032 X(3) = RS * COS (AL - A) * COS (PH - A)
5033 X(4) = RS * COS (AL - A) * COS (PH + A)
5040 Y(1) = - RS * SIN (AL + A)
5041 Y(2) = Y(1)
5042 Y(3) = - RS * SIN (AL - A)
5043 Y(4) = Y(3)
5050 Z(1) = - RS * COS (AL + A) * SIN (PH + A)
5051 Z(2) = - RS * COS (AL + A) * SIN (PH - A)
5052 Z(3) = - RS * COS (AL - A) * SIN (PH - A)
5053 Z(4) = - RS * COS (AL - A) * SIN (PH + A)
5806 AA = X(4) - X(1)
5807 BB = Y(4) - Y(1)
5808 CC = 2(4) - Z(1)
5810 Q1 = SQR (AA * AA + BB BB + CC * CC)
5815 AA = AA / Q1
5816 BB = BB / Q1
5817 CC = CC / Q1
5820 DD = X(2) - X(1)
5821 EE = Y(2) - Y(1)
5822 FF = Z(2) - Z(1)
5825 Q2 = SQR (DD * DD + EE * EE + FF * FF)
5827 DD = DD / Q2
5828 EE = EE / Q2
5829 FF = FF / Q2
5830 NX = BB * FF - CC * EE
5831 NY = CC * DD - AA * FF
5832 NZ = AA * EE - BB * DD
5833 H = NX * LX + NY * LY + NZ * LZ
5835 IF H> = 0 THEN CN = 0
5840 IF H < 0 THEN CN = 3
5845 H = ABS (H)
5860 IF H> = 0 AND H < .1 THEN N = 4
5865 IF H > = .1 AND H < .3 THEN N = 3
5870 IF H = .3 AND H < .5 THEN N = 3
5875 IF H = .5 AND H < .7 THEN N = 2
5880 IF H > = .7 AND H < .9000001 THEN N = 2
5885 IF H>.9000001 THEN N = 1
5893 IF N > 4 GOTO 5933
5895 FOR II = 0 TO Q1 STEP N
5900 FOR JJ = 0 TO Q2 STEP N
5905 XS = X(1) + II * AA + JJ * DD
5910 YS = Y(1) + II * BB + JJ * EE
5915 ZS = Z(1) + II * CC + JJ * FF
5920 HCOLOR= CN: HPLOT 1.12 * XS + XO, YS + YO
5925 NEXT JJ
5930 NEXT II
5933 NEXT AL
5934 NEXT PH
5935 REM ----------------------------------------------------------------------- DRAW RINGS
6000 C(1,1) = COS (RY) * COS (RZ): REM --SET UP TRANSFORMATION MATRIX
6010 C(1,2) = COS (RY) # SIN (RZ)
6020 C(1,3) = - SIN (RY)
6030 C(2,1) = - COS (RX) * SIN (RZ) + SIN (RX) * SIN (RY) * COS (RZ)
6040 C(2,2) = COS (RX) * COS (RZ) + SIN (RX) * SIN (RY) * SIN (RZ)
6050 C(2,3) = SIN (RX) * COS (RY)
6060 C(3,1) = SIN (RX) * SIN (RZ) + COS (RX) * SIN (RY) * COS (RZ)
6070 C(3,2) = - SIN (RX) * COS (RZ) + COS (RX) * SIN (RY) * SIN (RZ)
6080 C(3,3) = COS (RX) * COS (RY)
6082 DP = 1 * 3.14159 / 180
6083 PM = 360 # 3.14159 / 180
6084 FOR R = RS + 12 TO RR STEP 1
6085 FOR PH = 0 TO PM STEP DP: REM --TRAVERSE RINGS
6095 X = R * COS (PH)
6096 Z = -R * SIN (PH)
6097 Y = 0
6100 AA = X:BB = Y:CC = Z: REM --TRANSFORM COORDINATES
6110 X = AA * C(1,1) + BB * C(2,1) + CC * C(3,1)
6120 Y = AA * C(1,2) + BB * C(2,2) + CC * C(3,2)
6125 Z = AA * C(1,3) + BB * C(2,3) + CC * C(3,3)
6129 IF ABS (Y) > = RS GOTO 6140
6130 XX = SQR (RS * RS - Y * Y)
6131 IF ABS (X) > XX GOTO 6140
6133 IF Z > O GOTO 6149
6140 HCOLOR= 2: HPLOT 1.12 * X + XO, Y + YO: REM --MAGENTA
6149 NEXT PH
6150 NEXT R
6160 STOP
9500 REM -------------------------------------------------------------- FILL SPHERICAL AREA
9510 YS = YO - RS
9520 YE = YO + RS
9530 FOR Y = YS TO YE
9540 YY = Y - YO
9550 XX = SQR (RS * RS - YY * YY)
9551 XX = 1.12 * XX
9560 XS = XO - XX
9570 XE = XO + XX
9580 HCOLOR= 7: HPLOT XS,Y TO XE,Y: REM --WHITE
9590 NEXT Y
9610 RETURN