ZX Spectrum Raytracer

This is the example output and source code for the 2nd iteration of the ZX Spectrum Raytracer, with better handling of attribute clashes.

   1 BRIGHT 1: CLS

   5 LET ROX = 0
   6 LET ROY = 0
   7 LET ROZ = 0
   8 LET TMIN = 0
   9 LET TMAX = 10000

  10 FOR X = 0 TO 255 STEP 8
  20   FOR Y = 0 TO 175 STEP 8

  30      DIM C(64)
  31      LET CI = 1
  32      DIM A(8)

 120     REM --- For each 8x8 block, collect the pixel colors and their counts ---
 125     FOR U = X TO X+7
 126       FOR V = Y TO Y+7

 130         LET RDX = (U - 128) / 256
 131         LET RDY = (V - 88) / 256
 132         LET RDZ = 1

 140         GO SUB 1000
 141         LET C(CI) = COL
 142         LET CI = CI + 1
 143         LET A(COL+1) = A(COL+1) + 1

 160       NEXT V
 161     NEXT U

 199     REM --- Find the most and second most frequent colors in this 8x8 block ---
 201     LET MFC = 0
 202     FOR C = 1 TO 8
 203       IF A(C) > MFC THEN LET MFC = A(C): LET MFI = C
 204     NEXT C
 205     LET FCOL = MFI - 1

 207     LET II = MFI: LET MFC = 0: LET MFI = 0
 208     FOR C = 1 TO 8
 209       IF C <> II AND A(C) > MFC THEN LET MFC = A(C): LET MFI = C
 210     NEXT C
 211     LET SCOL = MFI - 1

 259     REM --- If there's only one color, paint the whole block --
 260     IF SCOL <> -1 THEN GO TO 300
 270     POKE 22528 + X/8 + 32*(21-Y/8), 64 + FCOL * 8
 280     GO TO 500

 300     REM --- Otherwise set the PAPER to the most frequent color, and draw everything else in the second most frequent color --
 301     LET CI = 1
 310     FOR U = X TO X+7
 311       FOR V = Y TO Y+7

 320         IF C(CI) <> FCOL THEN PLOT INK SCOL; PAPER FCOL; U, V
 321         LET CI = CI + 1

 350       NEXT V
 351     NEXT U

 500   NEXT Y
 505   GO SUB 3000: PRINT AT 0, 0; TIME
 510 NEXT X

 520 STOP

1000 REM ===== TraceRay =====
1001 REM Params: (ROX, ROY, ROZ): ray origin; (RDX, RDY, RDZ): ray direction; (TMIN, TMAX): wanted ranges of t
1002 REM Returns: COL: pixel color

1010 LET COL = -1: LET MINT = 0

1100 RESTORE 9000
1101 READ NS
1102 FOR S = 1 TO NS

1110    READ SCX, SCY, SCZ, SRAD, SCOL

1200    LET COX = ROX - SCX
1201    LET COY = ROY - SCY
1202    LET COZ = ROZ - SCZ

1210    LET EQA = RDX*RDX + RDY*RDY + RDZ*RDZ
1211    LET EQB = 2*(RDX*COX + RDY*COY + RDZ*COZ)
1212    LET EQC = (COX*COX + COY*COY + COZ*COZ) - SRAD*SRAD

1220    LET DISC = EQB*EQB - 4*EQA*EQC
1230    IF DISC < 0 THEN GO TO 1500

1240    LET T1 = (-EQB + SQR(DISC)) / 2*EQA
1241    LET T2 = (-EQB - SQR(DISC)) / 2*EQA

1250    IF T1 >= TMIN AND T1 <= TMAX AND (T1 < MINT OR COL = -1) THEN LET COL = SCOL: LET MINT = T1
1300    IF T2 >= TMIN AND T2 <= TMAX AND (T2 < MINT OR COL = -1) THEN LET COL = SCOL: LET MINT = T2

1500 NEXT S

1999 IF COL = -1 THEN LET COL = 0
2000 RETURN

3000 REM ===== Get timestamp in seconds =====
3001 LET TIME = (65536*PEEK 23674 + 256*PEEK 23673 + PEEK 23672) / 50
3002 RETURN

8998 REM ===== Sphere data =====
8999 REM Sphere count, followed by (SCX, SCY, SCZ, SRAD, COLOR)
9000 DATA 4
9001 DATA  0, -1, 4, 1, 2
9002 DATA  2,  0, 4, 1, 1
9003 DATA -2,  0, 4, 1, 4
9004 DATA 0, -5001, 0, 5000, 6