gigatron/rom/Apps/Mandelbrot/Mandelbrot_v2.gcl
2025-01-28 19:17:01 +03:00

361 lines
9.3 KiB
Plaintext

{-----------------------------------------------------------------------+
| |
| Mandelbrot fractal |
| |
+-----------------------------------------------------------------------}
gcl0x
{
Plot the Mandelbrot set
- 160x120 pixels and 64 colors [updated 160x118 pixels!]
- Faithful translation of mandelbrot.c pre-study
- Use 16-bit vCPU math as 7-bit fixed point arithmetic (1.00 -> 128)
- Implement multiplication in interpreter
- Show settable clock, with blinking colon as long as unset
Ideas for after ROMv1:
XXX Random color offset for each new rendering
XXX Lookup table for intensity mapping
XXX Refactor pixel order from what to do with it
XXX Double vertical resolution by exploiting vertical symmetry
}
{ Slow unsigned multiply shift.
Arguments are 3+7 fixed point numbers.
Need to set sysFn to SYS_LSRW1_48 before calling this }
[def
B 2<< B=
0 C=
1 bit=
[do
bit A& [if<>0 B] C+ 48!! C=
bit 1<< bit= \vACH, 2^ if<>0loop]
>A, $fe& [if<>0 B] C+ C=
ret
] SlowMulShift7=
{ Compute a table of squares for -2<x<2
Hide the table in the top four lines of the screen }
\videoTop_v5 ptr= 8 ptr.
$800 SquareTable=
\SYS_LSRW1_48 \sysFn:
$1ff [do i= A= B= 1<< SquareTable+ ptr= SlowMulShift7! ptr: i 1- if>=0loop]
{ Multiply shift with fast mode
Need to set sysFn to SYS_LSRW1_48 before calling this }
[def
{Extract sign and absolute values}
0 sign=
A- [if>0 A= <sign++]
0 B- [if>0 B= <sign++]
{Check range}
A B| \vACH, $fe&
[if<>0
push SlowMulShift7! pop
else
A 1<< SquareTable+ deek C= {C now contains A^2}
A B- [if<0 B A-] 1<< SquareTable+ deek A= {A now contains (A-B)^2}
B 1<< SquareTable+ deek C+ A- 48!! C= {C now contains (A^2+B^2-(A-B)^2)/2}
]
{Apply sign to return value}
sign 1& [if=0 C ret] 0 C- ret
] MulShift7=
{ Square-shift with fast mode }
[def
0 A- [if>0 A=]
>A, $fe& [if<>0 push A B= SlowMulShift7! pop ret ]
A 1<< SquareTable+ deek C= ret
] SqrShift7=
{-----------------------------------------------------------------------+
|} $0300 \vLR: [ret]{ RAM page 3 |
+-----------------------------------------------------------------------}
$0300:
[def {CalcSet}
push
$C00 Pen= {Start of video}
116 Height= 160 Width= len=
LastPixel= {Anything well-defined}
Step 1<< Y0+ Y0=
0 DY=
Step DX=
[do
{Draw white pixel while busy here}
63 Pen.
{Update clock}
UpdateClock!
{Reset sysFn to right shift}
\SYS_LSRW1_48 \sysFn:
LastPixel [if=0
{Check if we are inside one of the main bulbs for
a quick bailout (Wikipedia)
(x+1)^2 + y^2 < 1/16}
Y0 A= SqrShift7! YY=
X0 128+ A= SqrShift7! YY+ 8- [if<0 0
else
{q*(q + x - 1/4) < 1/4*y^2, where q = (x - 1/4)^2 + y^2}
X0 32- A= SqrShift7! YY+ {q}
A= X0+ 32- B= MulShift7! tmp=
tmp+ tmp= tmp+ tmp= {*4} YY- [if<0 0
else
{Otherwise run the escape time algorithm}
CalcPixel!
]
]
else
{No shortcut}
CalcPixel!
]
LastPixel= Pen. {Plot pixel}
len 1- [if<=0
{Turn right}
DY tmp= DX DY= 0 tmp- DX=
{Length of next segment, either horizontal or vertical}
DX [if<>0 Width 1- Width= else Height 1- Height=]
]
{Break when reaching a zero-length segment}
len= if>0
{Step in the fractal plane}
X0 DX+ X0=
Y0 DY+ Y0=
{Matching step in video frame}
DX [if<0 Pen 1- Pen=]
DX [if>0 Pen 1+ Pen=]
DY [if<0 -$100 Pen+ Pen=]
DY [if>0 $100 Pen+ Pen=]
loop]
{60 \soundTimer. {For debugging}}
GrayOut!
pop ret
] CalcSet=
{-----------------------------------------------------------------------+
|}\vLR>++ [ret]{ RAM page 4 |
+-----------------------------------------------------------------------}
$0400:
[def {GrayOut}
push
$C00 Pen=
116 Height= 160 Width= len=
1 DX= 0 DY=
[do
\SYS_LSRW1_48 \sysFn:
0 Level=
32 [do
i=
Pen, 63& i- [if>=0 Pen. i 21& [if<>0 1 else 2] Level+ Level=]
i 48!! {ShiftRight}
if>0loop]
{Level in 0..9}
Pen. {Paint it black}
Level [if<>0
{Level in 1..9}
{Ordered dithering}
Pen>, 1& i= i+ i= {2 * bit 0 of y}
Pen 1& i+ i= {+ bit 0 of x}
[def 0# 2# {Bayer matrix (Wikipedia)}
3# 1#]
i+ peek Level+ 1- Level=
{Level in 0..11}
{Map intensity level to actual gray color (multiple of 1+4+16)}
[do Level 3- if>=0 Level= Pen, 21+ Pen. loop]
]
{Advance to next pixel}
len 1- [if<=0
{Turn right}
DY tmp= DX DY= 0 tmp- DX=
{Length of next segment, either horizontal or vertical}
DX [if<>0 Width 1- Width= else Height 1- Height=]
]
{Break when reaching a zero-length segment}
len= if>0
{Matching step in video frame}
DX [if<0 Pen 1- Pen=]
DX [if>0 Pen 1+ Pen=]
DY [if<0 -$100 Pen+ Pen=]
DY [if>0 $100 Pen+ Pen=]
{Update clock}
UpdateClock!
loop]
pop ret
] GrayOut=
$201a Separator= {High: separator character, Low: XOR when blinking}
{-----------------------------------------------------------------------+
|}\vLR>++ [ret]{ RAM page 5 |
+-----------------------------------------------------------------------}
$0500:
{Update 24-hour clock}
[def
\frameCount,
LastFrame- 255& Elapsed=
60- [if<0
\buttonState, 128& [if<>0 ret]
15 {Yellow}
else
63 {White}]
Color=
{1 second has elapsed, or button A was pressed}
push
Clock0 Elapsed+ Clock0=
LastFrame Elapsed+ LastFrame=
-3599 Clock0+ [if>=0
Clock0=
1 AddMinutes!
]
{Change separator between on/off ($3a/$20)}
Separator>, Separator^ Separator>.
{Check if user is adjusting the time}
\buttonState, 119^ {A+Up} [if=0 Clock0= +1 else
12^ {A+Down} [if=0 Clock0= -1 else
6^ {A+Left} [if=0 Clock0= -60 else
3^ {A+Right} [if=0 Clock0= +60 else 0]]]]
[if<>0
AddMinutes! {Adjust minutes/hours clock}
0 Clock0= {Reset seconds/subseconds clock}
$3a00 Separator= {Stop blinking once adjusted}
]
\buttonState, 15| \buttonState. {Reset arrow presses, but not the others}
{Convert to HH:MM:SS and draw on screen}
$445c Pos=
Clock1 Value= 600 NextDigit! {10-hour digit}
60 NextDigit! { 1-hour digit}
Separator>, PrintChar! {Colon or space}
10 NextDigit! {10-minute digit}
1 NextDigit! { 1-minute digit}
pop ret
] UpdateClock=
[def {PrintChar}
32- i= \font32up fontData= {All of '0'-'9' and ':' are in the first page}
i 2<< i+ {Multiply by 5}
fontData+ fontData= {Add to page address to reach bitmap data}
$800 Pos+ q= {Where to stop the inner drawing loop}
{Draw 5 vertical slices: 5 using font data}
0 \sysArgs0. {Black}
Color \sysArgs1.
Pos \sysArgs4:
6+ Pos=
\SYS_VDrawBits_134 \sysFn:
$fb i= [do
fontData 0? fontData<++ \sysArgs2. 134!!
\sysArgs4<++
i<++ i if<>0loop]
ret
] PrintChar=
{-----------------------------------------------------------------------+
|}\vLR>++ [ret]{ RAM page 6 |
+-----------------------------------------------------------------------}
$0600:
[def {NextDigit -- Value Radix}
push
Radix=
$30 Digit=
Value Radix- [if>=0
[do Value= Digit<++ Radix- if>=0loop]
]
Digit PrintChar!
pop ret
] NextDigit=
[def {AddMinutes}
Clock1+ Clock1=
[if<0 1440 Clock1+ Clock1=]
-1440 Clock1+ [if>=0 Clock1=]
ret
] AddMinutes=
{ Calculate color for (X0,Y0) using the escape time algorithm }
[def {CalcPixel}
push
0 X= XX= Y= YY= i=
[do
i 1+ i= 64^ if<>0 {Break after completing 63 iterations}
X Y- A= SqrShift7! Y= {Y=(X-Y)^2}
XX YY+ Y- Y0+ Y= {Y=X2+Y2-(X-Y)^2+Y0}
XX YY- X0+ X= {X =X2-Y2+X0}
{Calculate squares}
{X}A= SqrShift7! XX=
Y A= SqrShift7! YY=
-$200 XX+ YY+
if<=0loop {else} i {Also break when X^2 + Y^2 > 4}
]
pop ret
] CalcPixel=
{ Main }
0 Clock0= Clock1= {Frames and minutes}
Digit=
\frameCount, LastFrame=
[do
-180 Y0= -320 X0= 3 Step= CalcSet! {Global 1127.446 seconds} {#$b4 #$80}
-148 Y0= -98 X0= 1 Step= CalcSet! {Zoom1 2125.648 seconds}
-60 Y0= -256 X0= 1 Step= CalcSet! {Zoom2 2050.341 seconds}
-320 Y0= -360 X0= 9 Step= CalcSet! {Wide 356.482 seconds}
0 Y0= -100 X0= 1 Step= CalcSet! {Zoom3 2337.633 seconds}
-60 Y0= 0 X0= 1 Step= CalcSet! {Zoom4 1409.569 seconds}
loop]
{ Benchmark settings:
+ $1c10 p= {Start of video}
+ 96 Height= 128 Width= len=
+ i 1+ i= 27^ if<>0 {Break after completing 26 iterations}
+ -144 Y0= -256 X0= 3 Step= CalcSet! {Global 736.586 seconds}
}
{-----------------------------------------------------------------------+
| End |
+-----------------------------------------------------------------------}