--- title: "3D Fractals" id: 50658 type: "computer_media" slug: "3d-fractals" url: "http://localhost/computer_media/3d-fractals/" markdown_url: "http://localhost/computer_media/3d-fractals.md" published_at: "2023-06-25T08:49:28+00:00" modified_at: "2026-03-30T21:40:52+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2019/02/20230809-041332.jpg" excerpt: "A fractal terrain generator builds a randomized 3D landscape through iterative midpoint displacement, then renders it as a projected wireframe with sea-level clipping." category: - name: "Archived Media" slug: "archived-media" taxonomy: "category" url: "http://localhost/category/archived-media/" post_tag: - name: "Downloadable" slug: "downloadable" taxonomy: "post_tag" url: "http://localhost/tag/downloadable/" - name: "TS 2068" slug: "ts2068" taxonomy: "post_tag" url: "http://localhost/tag/ts2068/" model: - name: "Timex/Sinclair 2068" slug: "ts-2068" taxonomy: "model" url: "http://localhost/model/ts-2068/" indiv: - name: "Ted Knyszek" slug: "ted-knyszek" taxonomy: "indiv" url: "http://localhost/indiv/ted-knyszek/" genre: - name: "Demo" slug: "demo" taxonomy: "genre" url: "http://localhost/type/demo/" - name: "Graphics" slug: "graphics" taxonomy: "genre" url: "http://localhost/type/graphics/" media_type: "Program" programmers: - name: "Ted Knyszek" slug: "ted-knyszek" taxonomy: "indiv" url: "http://localhost/indiv/ted-knyszek/" download_url: "https://archive.org/download/timex-sinclair-software-archive/3D%20Fractals%20%28198x%29%28Knyszek%2C%20Ted%29%28TS2068%29%28US%29%28Program%29.zip" mediadate: "198x" images: - url: "http://localhost/wp-content/uploads/2023/06/3d-fractals-2.png" - url: "http://localhost/wp-content/uploads/2023/06/3d-fractals-1.png" article_media: - id: 10534 title: "3D Fractals" type: "article" url: "http://localhost/article/3d-fractals/" - id: 44276 title: "3D Fractals" type: "article" url: "http://localhost/article/3d-fractals-2/" media_type_tags: "Demo, Graphics" --- This program generates and renders a fractal terrain landscape using a midpoint displacement algorithm, converted from an Apple II original to run on the TS-2068. The user specifies a detail level from 1 to 6, which controls the recursion depth; the terrain heights are stored in a 65×33 array `D()` and built up iteratively across X, Y, and diagonal passes. Three rotation subroutines apply horizontal (azimuth) and vertical (elevation) transformations using precomputed sine and cosine values before projecting to screen coordinates. The display loop renders the surface as three sets of isometric line strips using `PLOT`/`DRAW`, with a sea-level clipping routine that interpolates the shoreline crossing point when a line segment transitions between above and below zero height. Scaling factors `XS`, `YS`, and `ZS` are each set to 0.04, and the screen projection applies a fixed perspective correction in the `XP`/`YP` calculation at line 1030. *** ## Program Analysis ### Program Structure The program is organized into clearly delineated phases: initialization, terrain generation, and display. Control flows from the input and setup block (lines 15–52), through the iterative fractal subdivision loop (lines 60–120), then jumps to the display routines starting at line 640. A final `GO TO 1130` at line 750 targets a non-existent line, terminating execution after display — a recognized termination idiom. Line 1150 contains a `STOP` that is never reached. ### Terrain Generation — Midpoint Displacement The fractal surface is built in a 65×33 array `D()` (line 20), sized for up to 6 levels: `DS = 2^LE + 1`, giving a maximum of 65 columns. For each level `N` (line 60), the roughness scale `L = 10000 / 1.8^N` decreases geometrically, producing finer detail at each pass. Three subroutines perform the midpoint displacement: - **Lines 150–200**: interpolates heights along the X direction. - **Lines 220–270**: interpolates heights along the Y direction. - **Lines 290–350**: interpolates heights along the diagonal. Each subroutine computes a new height as the average of two neighbors plus a random offset: `(D1+D2)/2 + (RND-0.5)*L/2`. The step size `SK = IB*2` and interval `IB = MX/2^N` halve each level, refining the mesh. ### Array Access with Symmetry Folding The read subroutine (lines 370–400) and write subroutine (lines 420–450) apply a symmetry fold when `J > MY`: the indices are remapped as `BY = MX+1-J`, `BX = MX-I`. This exploits the triangular structure of the terrain grid, effectively mirroring the second half of the array rather than storing it separately. Array indices are offset by +1 throughout to avoid the zero-based boundary (`D(BX+1, BY+1)`). ### 3D Projection Pipeline Before plotting, each point passes through a multi-stage transformation pipeline: 1. **Sea-level clipping** (line 470, subroutine): detects sign changes in `ZZ` and interpolates the shoreline crossing using parameter `W3 = ZZ/(ZZ-Z2)`. 2. **Scaling** (line 950): multiplies `XX`, `YY`, `ZZ` by `XS=YS=ZS=0.04`. 3. **Horizontal rotation** (lines 770–800): applies azimuth rotation using precomputed `RC`, `RS` (cosine/sine of `PI/6`). 4. **Vertical rotation** (lines 860–890): applies elevation rotation using `VC`, `VS` (cosine/sine of `-PI/5`). 5. **Screen projection** (lines 1030–1050): maps to pixel coordinates with `XP = XP*0.55+5` and `YP = 175-(24-0.7*YP)`, then draws with `PLOT`/`DRAW`. ### Sea-Level and Flag Logic The sea-level subroutine (lines 470–620) tracks whether the current segment is above or below zero height using a flag variable `F1`. `F1=1` (set by line 1070) suppresses drawing; `F1=0` (set by line 1090) re-enables it. The sentinel value `XO=-999` signals a pen-up state (start of a new strip), analogous to a “move” vs. “draw” distinction. The clipping interpolation correctly handles both transitions: entering water (line 560) and exiting water (line 590). ### Display Loops Three nested loops at lines 660–740 sweep the terrain in three directions to draw isometric wireframe strips: - Lines 660–680: strips along diagonal slices with `J` from 0 to `I`. - Lines 690–710: strips along rows with `I` from `J` to `MX`. - Lines 720–740: strips along the diagonal grain using `G` and `H`. Each strip resets `XO=-999` at the start to lift the pen. The isometric world coordinates use `XX = I/MX*10000 - YY/2` to apply a horizontal shear consistent with a 30° isometric projection. ### Notable Anomalies - The REM comment at line 210 reads “HEIGHTS ALONG X” but actually governs the Y-direction pass (lines 220–270); line 140 correctly labels the X pass. - Subroutine at line 1110 is an empty stub (just `RETURN`), presumably a placeholder for screen setup such as clearing or mode switching. - Lines 760 and 850 each contain a bare `REM` with no text, used as spacers between subroutines. - The bounds check at line 1045 (`Y8>174 OR Y8<0 OR YP>174 OR YP<0`) guards against off-screen `PLOT`/`DRAW` calls, preventing errors for extreme height values. ## Source Code ``` 5 REM Fractals 6 REM T. A. Knyszek 7 REM T-S Horizons n17 p19 10 REM Converted from Apple II to TS-2068 by Ted Knyszek 15 BORDER 1: PAPER 5: INK 0: PRINT : CLS 20 DIM D(65,33) 30 INPUT "NUMBER OF LEVELS (1-6)";LE 40 LET DS=2^LE+1 50 LET MX=DS-1: LET MY=MX/2: LET RH=PI/6: LET VT=-PI/5 51 LET RC=COS (RH): LET RS=SIN (RH) 52 LET VC=COS (VT): LET VS=SIN (VT) 60 FOR N=1 TO LE: LET L=10000/1.8^N 70 PRINT #1;AT 0,0;"WORKING ON LEVEL ";N 80 LET IB=MX/2^N: LET SK=IB*2 90 GO SUB 150: REM ASSIGN HEIGHTS ALONG X IN ARRAY 100 GO SUB 220: REM ASSIGN HEIGHTS ALONG Y IN ARRAY 110 GO SUB 290: REM ASSIGN HEIGHTS ALONG DIAG IN ARRAY 120 NEXT N 130 GO TO 640 140 REM HEIGHTS ALONG X 150 FOR T=0 TO MX-1 STEP SK 160 FOR K=IB+T TO MX STEP SK 170 LET I=K-IB: LET J=T: GO SUB 370: LET D1=D: LET I=K+IB: GO SUB 370: LET D2=D 180 LET D=(D1+D2)/2+(RND-.5)*L/2: LET I=K: LET J=T: GO SUB 420 190 NEXT K 200 NEXT T: RETURN 210 REM HEIGHTS ALONG X 220 FOR K=MX TO 1 STEP -SK 230 FOR T=IB TO K STEP SK 240 LET I=K: LET J=T+IB: GO SUB 370: LET D1=D: LET J=T-IB: GO SUB 370: LET D2=D 250 LET D=(D1+D2)/2+(RND-.5)*L/2: LET I=K: LET J=T: GO SUB 420 260 NEXT T 270 NEXT K: RETURN 280 REM HEIGHTS ALONG DIAG 290 FOR K=0 TO MX-1 STEP SK 300 FOR T=IB TO MX-K STEP SK 310 LET I=K+T-IB: LET J=T-IB: GO SUB 370: LET D1=D 320 LET I=K+T+IB: LET J=T+IB: GO SUB 370: LET D2=D 330 LET I=K+T: LET J=T: LET D=(D1+D2)/2+(RND-.5)*L/2: GO SUB 420 340 NEXT T 350 NEXT K: RETURN 360 REM RETURN DATA FROM ARRAY 370 IF J>MY THEN GO TO 390 380 LET BY=J: LET BX=I: GO TO 400 390 LET BY=MX+1-J: LET BX=MX-I 400 LET D=D(BX+1,BY+1): RETURN 410 REM PUT DATA IN ARRAY 420 IF J>MY THEN GO TO 440 430 LET BY=J: LET BX=I: GO TO 450 440 LET BY=MX+1-J: LET BX=MX-I 450 LET D(BX+1,BY+1)=D: RETURN 460 REM PUT IN SEA LEVEL HERE 470 IF XO<>-999 THEN GO TO 500 480 IF ZZ<0 THEN GO SUB 1070: LET Z2=ZZ: LET ZZ=0: GO TO 620 490 GO SUB 1090: GO TO 610 500 IF Z2>0 AND ZZ>0 THEN GO TO 610 510 IF Z2<0 AND ZZ<0 THEN LET Z2=ZZ: LET ZZ=0: GO TO 620 520 LET W3=ZZ/(ZZ-Z2): LET X3=(X2-XX)*W3+XX: LET Y3=(Y2-YY)*W3+YY: LET Z3=0 530 LET ZT=ZZ: LET YT=YY: LET XT=XX 540 IF ZZ>0 THEN GO TO 590 550 REM GOING INTO WATER 560 LET ZZ=Z3: LET YY=Y3: LET XX=X3: GO SUB 950 570 GO SUB 1070: LET ZZ=0: LET YY=YT: LET XX=XT: LET Z2=ZT: GO TO 620 580 REM COMING OUT OF WATER 590 LET ZZ=Z3: LET YY=Y3: LET XX=X3: GO SUB 950 600 GO SUB 1090: LET ZZ=ZT: LET YY=YT: LET XX=XT 610 LET Z2=ZZ 620 LET X2=XX: LET Y2=YY: RETURN 630 REM DISPLAY HERE 640 GO SUB 1110: REM SET UP PLOTTING DEVICE ON SCREEN 650 LET XS=.04: LET YS=.04: LET ZS=.04: REM SCALING FACTORS 660 FOR I=0 TO MX: LET XO=-999: FOR J=0 TO I 670 GO SUB 370: LET ZZ=D: LET YY=J/MX*10000: LET XX=I/MX*10000-YY/2 680 GO SUB 940: NEXT J: NEXT I 690 FOR J=0 TO MX: LET XO=-999: FOR I=J TO MX 700 GO SUB 370: LET ZZ=D: LET YY=J/MX*10000: LET XX=I/MX*10000-YY/2 710 GO SUB 940: NEXT I: NEXT J 720 FOR G=0 TO MX: LET XO=-999: FOR H=0 TO MX-G 730 LET I=G+H: LET J=H: GO SUB 370: LET ZZ=D: LET YY=J/MX*10000 740 LET XX=I/MX*10000-YY/2: GO SUB 940: NEXT H: NEXT G 750 GO TO 1130: REM 760 REM 770 LET OX=XX 780 LET XX=XX*RC-YY*RS 790 LET YY=OX*RS+YY*RC 800 RETURN 850 REM 860 LET OX=XX 870 LET XX=VC*XX-VS*ZZ 880 LET ZZ=VS*OX+VC*ZZ 890 RETURN 930 REM MOVE OR PLOT 940 GO SUB 470 950 LET XX=XX*XS: LET YY=YY*YS: LET ZZ=ZZ*ZS 960 GO SUB 770 970 GO SUB 860 990 LET XP=INT (YY)+1: LET YP=INT (ZZ) 1000 GO SUB 1030 1010 RETURN 1020 REM 1030 LET XP=XP*0.55+5: LET YP=175-(24-0.7*YP) 1040 IF XO=-999 OR F1=1 THEN LET X8=XP: LET Y8=YP: LET XO=XP 1045 IF Y8>174 OR Y8<0 OR YP>174 OR YP<0 THEN RETURN 1050 PLOT X8,Y8: DRAW (XP-X8),(YP-Y8): LET X8=XP: LET Y8=YP: RETURN 1060 REM 1070 LET F1=1: RETURN 1080 REM 1090 LET F1=0: RETURN 1110 RETURN 1150 STOP ```