home *** CD-ROM | disk | FTP | other *** search
- \ Copyright 1989 NerveWare
- \ No portion of this code may used for commercial purposes,
- \ nor may any executable version of this code be disributed for
- \ commercial purposes without the author's express written permission.
- \ This code is shareware, all rights reserved.
- \ Nick Didkovsky 1/25/89
-
- \ MandEdge3.JF
-
- \ Main File
- \ written in JForth v2.0
-
- \ This approach to generating the Mandelbrot set relies on the fact that
- \ the set is connected.
- \ The connectedness of the Mandelbrot set guarantees
- \ that an area surrounded by Mandelbrot pixels will be filled
- \ with Mandelbrot pixels.
- \ This program uses edge detection on the set whenever it is
- \ encountered: surrounds the set, marks the interior, and thereby
- \ protects the algorithm (and your valuable time) from having to
- \ calculate costly interior points!
- \ None of these interior points will be calculated.
- \ The amount of time this saves is tremendous,
- \ as it is exactly these points that would take the most computation time.
-
-
- \ A requested region is first padded with a one-pixel wide frame on top,
- \ bottom, left, and right, to prevent edge detection from running off the
- \ screen.
-
- \ Full 640x400 screen stored in two 32k tables. One table to bit-mark a pixel
- \ whether it was calculated yet or not, and one table to bit-mark a pixel
- \ whether it is in the mandelbrot set or not.
-
- \ MOD: added "screen" menu strip. ND 2/16/89
- \ MOD: checking for new-screen? flag to change resolutions ND 2/16/89
- \ MOD: fixed bug in singleton? check if point already calc'ed ND 2/17/89
- \ MOD: logto ram:MandelbrotSession for fun ND 2/17/89
- \ MOD: slight speedup, including MandelBitMasks.ASM ND 2/18/89
- \ MOD: ONLY marking edge and interior points! ND 2/18/89
- \ MOD: Checking for events in PadRegion also ND 2/20/89
- \ MOD: includes small-buffered iff-save code 4/6/89
- \ MOD: adding final text credits 4/6/89
- \ MOD: using Fast.Draw ( x y color -- ) to draw lines for color runs 6/10/89
- \ MOD: only checking ev.getclass once every 8 inner loops 6/10/89
- \ MOD: using color lookup table 6/10/89
- \ MOD: changed 8 MOD to 7 AND in markinterior 6/13/89
- \ MOD: eliminated num-skipped in markinterior 6/13/89
- \ MOD: using MandelBitMasks2.ASM now 6/13/89
- \ MOD: Code body of SetNewNeighborStart, NextNeighbor, SetXYcurrent moved
- \ into FindEdge directly - saves a few jsr's 6/13/89
-
- include? mandelbrot.ev.loop MandelEvMenus.JF
- include? mark.point MandelBitMasks2.ASM
- include? $logto ju:logto
- include? report.mandel.time MandelMeasure.jf
- include? byetext byetext.jf
- include? fast.draw FastLine.jf
- include? build.lookup ColorLookup.jf
-
- anew task-mandeledge
-
-
- decimal
-
-
- \ ************* FIND A MANDELBROT EDGE STARTING AT A GIVEN PIXEL **********
- \ The 8-neighbors of pixel P are coded as shown below:
- \
- \ 5 6 7
- \ 4 P 0
- \ 3 2 1
- \
- \ Given a mandelbrot set edge pixel P, we find the next edge pixel
- \ by checking its 8-neighbors for mandelbrot set membership.
- \ Start at neighbor 0 and continue
- \ clockwise until the first 8-neighbor is found to be in the set.
- \ Jump to this new pixel Q ... search for the next edge pixel by starting
- \ with 5+NeighborNumber modulo 8. That is, if Q was discovered at 2,
- \ jump to Q and search for the next at Q's 8-neighbor#7
- \ We're sort of rolling around the edge with a clockwise spin.
-
- variable CurrentNeighbor
- variable Xstart
- variable Ystart
- variable Xneighbor
- variable Yneighbor
- variable Xcurrent
- variable Ycurrent
- variable Ymax \ these three used to limit interior point marking
- variable Xmax
- variable Xmin
- variable last-iteration
- variable inside-flag
-
- \ MOD: moved body of this code into FindEdge 6/13/89
- \ : NEXTNEIGHBOR ( -- )
- \ CurrentNeighbor @ 1+ 7 and CurrentNeighbor !
- \ ;
-
- \ MOD: moved body of this code into FindEdge 6/13/89
- \ : SETNEWNEIGHBORSTART ( -- )
- \ CurrentNeighbor @ 5 + 7 and CurrentNeighbor !
- \ ;
-
- : PN ( -- )
- Xneighbor @ . space Yneighbor @ . cr
- ;
-
- : SETXYNEIGHBOR ( CurrentNeighbor -- )
- case 0 of Xcurrent @ 1+ Xneighbor ! Ycurrent @ Yneighbor ! endof
- 1 of Xcurrent @ 1+ Xneighbor ! Ycurrent @ 1+ Yneighbor ! endof
- 2 of Xcurrent @ Xneighbor ! Ycurrent @ 1+ Yneighbor ! endof
- 3 of Xcurrent @ 1- Xneighbor ! Ycurrent @ 1+ Yneighbor ! endof
- 4 of Xcurrent @ 1- Xneighbor ! Ycurrent @ Yneighbor ! endof
- 5 of Xcurrent @ 1- Xneighbor ! Ycurrent @ 1- Yneighbor ! endof
- 6 of Xcurrent @ Xneighbor ! Ycurrent @ 1- Yneighbor ! endof
- 7 of Xcurrent @ 1+ Xneighbor ! Ycurrent @ 1- Yneighbor ! endof
- endcase
- ;
-
- \ MOD: moved body of this code into FindEdge 6/13/89
- \ Set the current edge pixel, and update Ymax if necessary
- \ : SetXYcurrent ( -- )
- \ Xneighbor @ dup dup Xcurrent ! Xmax @ max Xmax ! Xmin @ min Xmin !
- \ Yneighbor @ dup Ycurrent ! Ymax @ max Ymax !
- \ ;
-
- \ 64000 calls to 0 0 SETABCONST take 10.56 sec. 256000 calls take 42.00 sec
- \ ASM version below takes: 7.22 sec. and 29.08 sec
- \ : SETABCONST ( x y -- )
- \ ygap @ * bcorner @ swap - bconst !
- \ xgap @ * acorner @ + aconst !
- \ ;
-
- ASM SetABconst ( x y -- )
- \ bconst first
- callcfa ygap ( -- x y ^ygap )
- move.l $0(org,tos.l),tos ( -- x y ygap )
- move.l (dsp)+,d0 ( -- x ygap)
- muls.l d0,tos ( -- x y*ygap )
- callcfa bcorner ( -- x y*ygap ^bcorner )
- move.l $0(org,tos.l),tos ( -- x y*ygap bcorner )
- move.l (dsp)+,d0 ( -- x bcorner , y*ygap in d0 )
- neg.l d0 ( -- x bcorner , -y*ygap in d0 )
- add.l d0,tos ( -- x bcorner-y*ygap )
- callcfa bconst ( -- x bcorner-y*ygap ^bconst )
- move.l (dsp)+,$0(org,tos.l) ( -- x ^bconst )
- move.l (dsp)+,tos ( -- x )
- \ aconst now
- callcfa xgap ( -- x ^xgap)
- move.l $0(org,tos.l),tos ( -- x xgap)
- move.l (dsp)+,d0 ( -- xgap)
- muls.l d0,tos ( -- x*xgap)
- callcfa acorner ( -- x*xgap ^acorner)
- move.l $0(org,tos.l),tos ( -- x*xgap acorner)
- add.l (dsp)+,tos ( -- x*xgap+acorner)
- callcfa aconst ( -- x*xgap+acorner ^aconst)
- move.l (dsp)+,$0(org,tos.l) ( -- ^aconst)
- move.l (dsp)+,tos ( -- )
- end-code
-
- \ returns true if a pixel has no NEW mandelbrot 8-neighbors
-
- : SINGLETON? ( x y -- true | false, sets CurrentNeighbor if false )
- 2dup 0 FastWritePixel
- 2dup mark.mandelbrot.point
- 2dup SetABconst
- dup Ystart ! Ycurrent !
- dup Xstart ! Xcurrent !
- true ( -- true)
- 8 0 do
- i SetXYneighbor
- Xneighbor @ Yneighbor @ 2dup get.point
- IF-NOT
- 2dup 2dup SetABconst
- quick.guts ( -- true x y x y iter)
- dup>r color-lookup + c@ FastWritePixel
- r> mandelmax = IF mark.mandelbrot.point
- i CurrentNeighbor !
- drop false leave
- ELSE mark.non.point
- THEN
- ELSE 2drop
- THEN ( if neighbor not already marked)
- loop
- \ blacken.last.xy
- ;
-
- \ CurrentNeighbor value set by a call to singleton? that returned false.
-
- : FINDEDGE ( -- )
- BEGIN
- CurrentNeighbor @ SetXYneighbor
- Xneighbor @ Yneighbor @ get.point ( -- false | mask )
- IF-NOT ( if not marked, do quick.guts)
- Xneighbor @ Yneighbor @ 2dup SetABconst ( -- x y)
- quick.guts ( -- x y iter)
- >r 2dup r@ ( -- x y x y iter) ( -R- iter)
- color-lookup + c@ FastWritePixel ( -- x y)
- r@ mark.point r> MandelMax = ( -- mandelflag )
- ELSE ( else check if it's mandelbrot point)
- Xneighbor @ Yneighbor @ get.mandelbrot.point ( -- mandelflag )
- THEN
- IF \ if it is a mandelbrot point
- ( SetXYcurrent)
- Xneighbor @ dup dup Xcurrent !
- Xmax @ max Xmax !
- Xmin @ min Xmin !
- Yneighbor @ dup Ycurrent !
- Ymax @ max Ymax !
- ( SetNewNeighborStart)
- CurrentNeighbor @ 5 + 7 and CurrentNeighbor !
- ELSE \ otherwise repeat with the next neighbor
- ( NextNeighbor)
- CurrentNeighbor @ 1+ 7 and CurrentNeighbor !
- THEN
- \ arbitrarily check mouse event every so often, not always
- CurrentNeighbor @ 0= IF
- gr-curwindow @ ev.getclass ?dup
- IF handle.mandelbrot.event
- THEN
- THEN
- restart? @ quit? @ OR new-values? @ OR new-screen? @ OR ( exit mouse signal)
- Xcurrent @ Xstart @ = Ycurrent @ Ystart @ = AND ( exit from edge finished)
- OR
- \ how's that for a disgusting sequence of exit conditions?
- UNTIL
- \ blacken.last.xy
- ;
-
- \ *************************** IsoMandelPlot **********************************
- \ Plot region, call FindEdge whenever a non-singleton mandelbrot pixel
- \ is discovered.
-
- variable NumSkipped
-
- \ counts from xstart through MandelPixels on that row. Used to
- \ increment +loop in IsoMandelPlot
- : COUNTNUMSKIPPED ( -- )
- Xstart @ Xcurrent !
- BEGIN
- Xcurrent @ Ystart @ get.mandelbrot.point
- WHILE
- 1 Xcurrent +!
- REPEAT
- Xcurrent @ Xstart @ - 1+ NumSkipped !
- ;
-
-
- \ Starts at Yth row to Ymax'th row
- \ marks all pixels between mandelbrot set boundaries.
-
- \ variable interior-skipped - no longer used -
-
- \ old code: 19.63 sec for full lores mandelbrot screen (pure black)
- \ new code: 17.43 sec for full lores mandelbrot screen
- \ with MandelBitMasks3.ASM: 16.06 sec
-
- : MARKINTERIOR ( y -- )
- wait.pointer
- >r Xmin @ r@ Xmax @ Ymax @ gr.highlight
- Ymax @ r@ do
- Xmax @ 1+ Xmin @ do
- i j get.mandelbrot.point
- IF
- 0 ( -- 0, init interior-skipped counter)
- BEGIN
- dup i + j mark.non.point ( -- int-skip)
- 1+ ( -- int-skip+1)
- dup i + j
- 2dup get.point ( -- int-skip x y marked-flag)
-
- \ ************** OLD CODE START ****************
- \ 0 interior-skipped !
- \ BEGIN
- \ i interior-skipped @ + j MARK.NON.POINT
- \ 1 interior-skipped +!
- \ i interior-skipped @ + j ( check next pixel)
- \ 2dup get.point ( -- x y marked-flag)
- \ ************ OLD CODE END *****************
-
- \ next IF-THEN-ELSE sets up UNTIL's exit flag
- IF get.mandelbrot.point not ( -- flag )
- ELSE 2drop false ( -- false)
- THEN
- \ escape when a marked point is found that is not a mandelbrot point
- UNTIL ( -- int-skipped)
-
- \ interior-skipped @ ( leave skipped amount on stack for +loop)
-
- ELSE 1 ( leave 1 on stack for +loop)
- THEN
- +loop
- loop
- Xmin @ r> Xmax @ Ymax @ gr.dehighlight
- crosshairs.pointer
- ;
-
- \ This routine plots a pixel-width border around region. Pixels are marked
- \ as being non-mandelbrot points regardless of whether they are or not,
- \ so as to stop future edge detection from running off the screen.
- \ MOD: added checking for mouse events. This routine looks disgusting now.
-
- : PADREGION.HANDLE.LINE ( x y iter -- )
- dup>r last-iteration @ = IF-NOT \ if new iteration value
- 2dup ( -- x y x y) ( -r- iter)
- last-iteration @ mandelmax =
- IF-NOT
- last-iteration @ color-lookup + c@
- ( -- x y x y colornum) ( -r- iter)
- fast.draw ( -- x y) ( -r- iter)
- ELSE 2drop
- THEN
- fast.move ( -- ) ( -r- iter)
- r> last-iteration !
- ELSE 2drop rdrop ( -- ) ( -r-)
- THEN
- ;
-
- : PADREGION ( -- )
-
- \ TOP ROW LEFT TO RIGHT
- 0 0 fast.move 0 0 mark.non.point
- bcorner @ bconst !
- acorner @ aconst !
- quick.guts last-iteration !
- mandel-width @ 1 do
- i xgap @ * acorner @ + aconst !
- i 0 quick.guts ( -- x y iter )
- padregion.handle.line
- i 0 mark.non.point
- gr-curwindow @ ev.getclass ?dup
- IF handle.mandelbrot.event
- THEN
- restart? @ quit? @ or new-values? @ or new-screen? @ or
- IF leave
- THEN
- loop
- last-iteration @ mandelmax = IF-NOT
- mandel-width @ 0 last-iteration @ color-lookup + c@
- fast.draw
- THEN
-
- \ RIGHT COLUMN TOP TO BOTTOM
- mandel-width @ 1- 0 2dup fast.move mark.non.point
- acorner @ mandel-width @ 1- xgap @ * + aconst !
- bcorner @ bconst !
- quick.guts last-iteration !
- mandel-height @ ( 1- ) 1 do
- bcorner @ i ygap @ * - bconst !
- mandel-width @ 1- i quick.guts ( -- x y iter )
- padregion.handle.line
- mandel-width @ 1- i mark.non.point
- gr-curwindow @ ev.getclass ?dup
- IF handle.mandelbrot.event
- THEN
- restart? @ quit? @ or new-values? @ or new-screen? @ or
- IF leave
- THEN
- loop
-
- last-iteration @ mandelmax = IF-NOT
- mandel-width @ 1- mandel-height @
- last-iteration @ color-lookup + c@
- fast.draw
- THEN
-
- \ BOTTOM ROW RIGHT TO LEFT
- mandel-width @ 1- mandel-height @ 1- 2dup fast.move mark.non.point
- bcorner @ mandel-height @ 1- ygap @ * - bconst !
- acorner @ mandel-width @ 1- xgap @ * + aconst !
- quick.guts last-iteration !
- 0 mandel-width @ 2 - -do
- i xgap @ * acorner @ + aconst !
- i mandel-height @ 1- quick.guts
- padregion.handle.line
- i mandel-height @ 1- mark.non.point
- gr-curwindow @ ev.getclass ?dup
- IF handle.mandelbrot.event
- THEN
- restart? @ quit? @ or new-values? @ or new-screen? @ or
- IF leave
- THEN
- 1 -loop
-
- last-iteration @ mandelmax = IF-NOT
- 0 mandel-height @ 1-
- last-iteration @ color-lookup + c@
- fast.draw
- THEN
-
- \ LEFT COLUMN BOTTOM TO TOP
- 0 mandel-height @ 1- 2dup fast.move mark.non.point
- acorner @ aconst !
- bcorner @ mandel-height @ 1- ygap @ * - bconst !
- quick.guts last-iteration !
- 0 mandel-height @ 2 - -do
- bcorner @ i ygap @ * - bconst !
- acorner @ aconst !
- 0 i quick.guts
- padregion.handle.line
- 0 i mark.non.point
- gr-curwindow @ ev.getclass ?dup
- IF handle.mandelbrot.event
- THEN
- restart? @ quit? @ or new-values? @ or new-screen? @ or
- IF leave
- THEN
- 1 -loop
-
- last-iteration @ mandelmax = IF-NOT
- 0 0
- last-iteration @ color-lookup + c@
- fast.draw
- THEN
- ;
-
- \ close old screen, open new screen, attach menus, used when changing resolution
- : CLOSE.AND.OPEN ( -- )
- close.mandelscreen
- open.mandelscreen
- show-title? on
- gr-curwindow @ Mandelbrot-Menu-1 SetMenuStrip()
- crosshairs.pointer
- build.lookup
- ;
-
- \ IsoMandelPlot plots the entire region, vigilant for mandelbrot pixels,
- \ in which case it will branch off and surround the set by edge detecting.
- \ Then it marks all interior points as though already calculated.
-
- \ MOD: Accumulates runs of the same iteration, plotting LINES of same color
- \ instead of pixel-by-pixel POINTS (very fast - saves 2 mins in 640x400 res).
-
- : ISOMANDELPLOT ( -- )
-
- \ Initialize a new region
- set.mandel.start.time
- show-title? @ IF toggle.title THEN ( no title bar when calc new region)
- gr.clear
- clear.tables
- PadRegion
-
- \ Y loop starts
- mandel-height @ 1- 1 DO
- 1 i fast.move
- 1 i SetABconst
- 1 i quick.guts dup last-iteration ! color-lookup + c@ FastWritePixel
- \ blacken.last.xy
- 1 i last-iteration @ mark.point
- -1 inside-flag !
- \ X loop starts
- mandel-width @ 1- 2 DO
- 1 NumSkipped ! \ +loop default, may be changed by CountNumSkipped
- i j get.point
- IF-NOT ( if not already calculated ... )
- \ if scan was inside precalced region, reset starting pos
- inside-flag @ IF i j fast.move THEN
- 0 inside-flag !
- i j 2dup SetABconst
- quick.guts ( -- x y iter)
- dup>r ( -- x y iter) ( -r- iter)
-
- \ Handle line
- last-iteration @ = ( -- x y ) ( -r- iter)
- IF-NOT \ if new iteration value
- 2dup ( -- x y x y) ( -r- iter)
- last-iteration @ color-lookup + c@
- ( -- x y x y colornum) ( -r- iter)
- fast.draw ( -- x y) ( -r- iter)
- fast.move ( -- ) ( -r- iter)
- r@ last-iteration !
- ELSE 2drop ( -- ) ( -r- iter)
- THEN \ if-not same iteration value
-
- \ Check for beginning of mandelbrot edge
- r> mandelmax =
- IF
- i j Singleton?
- IF-NOT
- j Ymax !
- i dup Xmax ! Xmin !
- FindEdge
- restart? @ quit? @ OR
- new-values? @ OR new-screen? @ OR
- IF leave THEN
- j MarkInterior
- CountNumSkipped
- \ Jump the pen ahead if just skipped over more than one pixel
- NumSkipped @ 1 >
- \ 1+ ???
- IF i ( 1+) NumSkipped @ + j fast.move
- THEN
-
- THEN \ if-not singleton?
- THEN \ if mandelmax
- ELSE \ else if already calculated
- inside-flag @ IF-NOT \ if we were just accumulating...
- i 1- j
- last-iteration @ color-lookup + c@
- ( -- x y colornum)
- fast.draw ( -- )
- THEN \ ...then draw line before going inside
- -1 inside-flag !
- THEN \ if-not already calculated
-
- \ Check for mouse event once every 8 inner loops
- i 7 and 0=
- IF
- gr-curwindow @ ev.getclass ?dup
- IF handle.mandelbrot.event
- THEN
- THEN
-
- \ check for exit
- restart? @ quit? @ or new-values? @ or new-screen? @ or
- IF leave
- THEN
- NumSkipped @ +loop
-
- \ Finish row with a line
- \ mandel-width @ 2 - i 2dup get.mandelbrot.point IF-NOT
- inside-flag @
- IF-NOT
- mandel-width @ 2 - i
- last-iteration @ color-lookup + c@
- fast.draw ( -- )
- \ ELSE 2drop
- THEN
- 0 inside-flag !
- restart? @ quit? @ OR new-values? @ OR new-screen? @ OR IF leave THEN
- loop
- new-values? @ IF calc.new.values THEN
- new-screen? @ IF close.and.open THEN
- new-values? @ new-screen? @ OR IF new-screen? off new-values? off recurse
- THEN ( harmless tail recursion)
- ;
-
-
-
- \ allocate resources and open screen
-
- : INIT.AND.OPEN ( -- )
- set.320x200
- init.file.next
- init.tables
- load.bm.to.screen
- init.link.menus
- gr-curwindow @ Mandelbrot-Menu-1 SetMenuStrip()
- restart.mandel
- restart? off
- new-screen? off
- build.lookup
- ;
-
- DEFER ..OLDABORT
-
-
- : RESET.OLD.ABORT ( -- )
- what's ..oldabort is abort
- ;
-
- : NEWABORT ( -- )
- ." Mandelbrot ABORTing!!!" cr
- \ these routines check whether what they free was in fact allocated
- close.mandelscreen
- free.mandelbitmap
- free.tables
- iff-fileid @ ?dup IF fclose THEN
- ." A log of your session is in RAM:MandelbrotSession" cr
- logend
- reset.old.abort
- ABORT
- ;
-
- : SET.NEW.ABORT
- what's abort is ..oldabort
- 'c newabort is abort
- ;
-
- : MANDELBROT ( -- )
- set.new.abort
- mouse.ptr.init
- \ init.last.xy
- init.and.open
- crosshairs.pointer
- " ram:MandelbrotSession" $logto
- begin
- mandelbrot.ev.loop
- new-values? @ IF calc.new.values new-values? off
- IsoMandelPlot
- quit? @ restart? @ or IF-NOT report.mandel.time cr
- THEN
- THEN
- new-screen? @ IF new-screen? off
- close.and.open
- IsoMandelPlot
- quit? @ restart? @ or IF-NOT report.mandel.time cr
- THEN
- THEN
- restart? @ if Starting.Image restart? off then
- new-values? @ restart? @ and not quit? @ and
- until
- byetext
- cr ." Don't forget to rename any IFF images you've saved!" cr
- logend
- ." A log of your session is in RAM:MandelSession" cr
- restore.color.table
- normal.pointer
- cleanup
- free.tables
- free.mouse.data
- reset.old.abort
- ;
-
- cr cr ." type Mandelbrot to enter super quick edge-detectin' generator!" cr cr
-
-
-