This program renders a 3D wire-frame surface of revolution by rotating a user-supplied 2D profile curve around an axis. The user inputs up to 16 X/Y coordinate pairs defining the profile; the program then computes eight rotational copies of each point using a fixed 45° rotation matrix (PI/4 increments), storing results in three 16×8 arrays X, Y, and Z. A cabinet-projection formula (Y − 0.5×Z) is used to project the 3D mesh onto the screen, with PLOT and DRAW calls building the quadrilateral mesh faces. Lines 600–850 use POKE 26715/26718/26720 and RANDOMIZE USR 26715 to invoke a machine-code routine while an additional slow-rotation animation loop at lines 910–1030 applies incremental PI/20 rotations before cycling through display frames.
Program Analysis
Program Structure
The program is divided into four broad phases:
- Initialisation (lines 1–10): Sets border, paper and ink colours and defines the loop variable
l=1(the letter L, used as a numeric constant throughout). - Profile input & drawing (lines 500–560): Accepts up to 16 X/Y coordinate pairs from the user, immediately PLOTting and DRAWing the 2D profile on-screen.
- Surface-of-revolution construction & display (lines 600–850): Computes eight rotated copies of the profile using a 45° rotation matrix, then renders the mesh with cabinet projection via RANDOMIZE USR machine code.
- Slow-rotation animation loop (lines 910–1030): Applies successive PI/20 rotations to the base profile row and redraws, looping until a key is pressed, then restarts with
RUN.
Data Structures
Three 16×8 arrays — X, Y, Z — store world coordinates for up to 16 profile points across 8 rotation slices. The first column (index 1) holds the original profile; subsequent columns are filled by the rotation loop at line 620.
Rotation Mathematics
A surface of revolution is generated by rotating the profile in the X–Z plane. At line 500, scalar variables SIN and COS are pre-computed for 45° (PI/4), giving exactly 8 slices around the full 360°. The rotation recurrence at line 620 is:
X(A,B) = X(A,B-1)*COS - Z(A,B-1)*SINZ(A,B) = Z(A,B-1)*COS + X(A,B-1)*SINY(A,B) = Y(A,B-1)(unchanged — Y is the axis of revolution)
Note that the variable names SIN and COS shadow the built-in functions of the same name; this is legal because the parser accepts them as numeric variables in an assignment context.
Projection
A cabinet oblique projection is used. The screen coordinate is computed as 128 + X(A,B) horizontally and 30 + Y(A,B) - 0.5*Z(A,B) vertically, halving the Z depth contribution to give a reasonable sense of perspective without a full perspective divide.
Mesh Rendering
Lines 710–740 iterate over all interior slice pairs (B=1 to 7) and profile segments (A=1 to C-2), drawing two edges per quad: one along the rotation direction and one diagonally. Lines 810–850 close the surface by connecting the last slice back to the first, completing the revolution. Each quad is rendered as two DRAW calls rather than four, relying on the previous PLOT to supply the starting point.
Machine Code Usage
Lines 600–603 and 1000–1030 POKE values into addresses 26715–26720 before calling RANDOMIZE USR 26715. This is a self-modifying machine-code stub technique common in Spectrum BASIC:
| Address | Value POKEd | Likely purpose |
|---|---|---|
| 26715 | 33 / 17 | LD HL or LD DE opcode |
| 26718 | 17 / 33 | Paired register load opcode |
| 26720 | 130 + 24*Q | Screen third address byte, selecting display area |
The value 130 + 24*Q for Q=0..4 produces 130, 154, 178, 202, 226 — stepping through the high bytes of the Spectrum’s display file (0x4000–0x5AFF) to target successive screen thirds or attribute rows. The swapped opcodes between the draw phase and the animation phase at line 1000 suggest the routine is repurposed to copy or scroll screen data in the reverse direction during the animation.
Animation Loop
Lines 910–930 apply a smaller PI/20 rotation (9°) to the base profile row only, then the outer loop Q (0 to 4) repeats the machine-code display call. After five frames, the program checks INKEY$="" at line 1030; if no key is held, it attempts GO TO 1010 — a non-existent line — which falls through to the next available line (1020), effectively creating a continuous loop. A keypress breaks out to RUN at line 1040, reinitialising everything.
Notable Idioms and Techniques
las constant 1: The variablel(letter L) is set to 1 at line 10 and used pervasively as an array index, saving one byte per occurrence over the literal1in the tokenised file.- Variable named
SIN/COS: Scalar variables with the same names as built-in functions are assigned at line 500, demonstrating that BASIC variable names can legally overlap keyword names when used in numeric context. - Input termination: Entering X > 255 at line 540 causes an immediate
GO TO 590… but line 590 does not exist; execution falls to line 600, neatly skipping the rest of the input loop. - PAUSE 5 + INKEY$ polling: Line 1030 uses
PAUSE 5between frames for timing, then testsINKEY$to detect user interruption.
Potential Bugs and Anomalies
- The loop at line 610 iterates
B=2 TO 8, filling columns 2–8 of the arrays. However, the render loop at line 710 usesB=l TO 7(i.e. 1 to 7), referencing column 1 (the original profile) as a valid starting slice, which is correct, but column 8 is never rendered — the last rendered pair is columns 7 and 8. - At line 540, the variable
C(upper-case) is used for input, but the condition and subsequent lines reference bothCandc(lower-case used only in the FOR statement). On the Spectrum, variable names are case-sensitive in display but the tokeniser treats them identically, so this is not a runtime error. - The animation at lines 910–930 modifies only row 1 of the X/Z arrays; rows 2–8 are not recomputed before the next display call, so the animated rotation is applied only to the stored base profile, not a geometrically correct re-rotation of the full mesh.
Content
Source Code
1 REM @!▘ GO SUB VAL <>
10 LET l=1: BORDER 0: PAPER 0: INK 7: CLS
500 DIM X(16,8): DIM Y(16,8): DIM Z(16,8): LET SIN=SIN (PI/4): LET COS=COS (PI/4)
510 INPUT "X";X(l,l),"Y";Y(l,l): LET Z(l,l)=0
520 PLOT X(l,l)+128,Y(l,l)+30
540 FOR c=2 TO 16: INPUT "X";X(C,l): IF X(C,l)>255 THEN GO TO 590
541 INPUT ,"Y";Y(C,l): LET Z(C,l)=0
550 DRAW X(C,l)-X(C-l,l),Y(C,l)-Y(C-l,l)
560 PLOT X(C,l)+128,Y(C,l)+30: NEXT C
600 POKE 26715,33: POKE 26718,17
603 FOR Q=0 TO 4: POKE 26720,130+24*Q
610 FOR B=2 TO 8: FOR A=l TO C-l
620 LET X(A,B)=X(A,B-l)*COS-Z(A,B-l)*SIN: LET Z(A,B)=Z(A,B-l)*COS+X(A,B-l)*SIN: LET Y(A,B)=Y(A,B-l)
630 NEXT A: NEXT B: CLS
710 FOR B=l TO 7: FOR A=l TO C-2
720 PLOT 128+X(A,B),30+Y(A,B)-.5*Z(A,B)
730 DRAW (X(A,B+l))-(X(A,B)),(Y(A,B+l)-.5*Z(A,B+l))-(Y(A,B)-.5*Z(A,B))
740 DRAW (X(A+l,B+l))-(X(A,B+l)),(Y(A+l,B+l)-.5*Z(A+l,B+l))-(Y(A,B+l)-.5*Z(A,B+l)): NEXT A: NEXT B
810 FOR A=l TO C-2: PLOT 128+X(A,B),30+Y(A,B)-.5*Z(A,B)
820 DRAW (X(A,l))-(X(A,B)),(Y(A,l)-.5*Z(A,l))-(Y(A,B)-.5*Z(A,B))
830 DRAW (X(A+l,l))-(X(A,l)),(Y(A+l,l)-.5*Z(A+l,l))-(Y(A,l)-.5*Z(A,l))
850 NEXT A: RANDOMIZE USR 26715
910 FOR A=l TO C-l: LET XN=X(A,l)*COS (PI/20)-Z(A,l)*SIN (PI/20)
920 LET ZN=Z(A,l)*COS (PI/20)+X(A,l)*SIN (PI/20)
930 LET X(A,l)=XN: LET Z(A,l)=ZN: NEXT A: NEXT Q
1000 POKE 26715,17: POKE 26718,33: BEEP .2,30
1020 FOR Q=0 TO 4: POKE 26720,130+24*Q
1030 RANDOMIZE USR 26715: PAUSE 5: NEXT Q: IF INKEY$="" THEN GO TO 1010
1040 RUN
9998 SAVE "Goblet" LINE 1
9999 VERIFY ""
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
