; 4ge's Xmas 94 Intro fractalzoom source ; ====================================== ; Copyright (C) 1994/5 Samuel Marshall. All rights reserved. ; Text and program code by Samuel Marshall, a.k.a. CuteElf / 4ge. ; Contact me at the following email address: ; Samuel.Marshall@durham.ac.uk ; and if you don't get a response, probably it's not during termtime, and ; I am at home - in which case, I check email here only about once a month ; (if that). You will get a reply eventually. ; My WWW homepage is ; http://www.dur.ac.uk/~d405ua/ ; Enjoy the program... hope it helps you. Don't expect a wonderfully- ; optimised piece of code, because 1) the zoom routine wasn't really time- ; critical, and 2) this is the first time I ever wrote a fractal program, ; in my entire life, and 3) I didn't know how to do fixed-point numbers ; properly when I wrote this ;) ; General Principles for a real-time fractalzoom using this method ; ================================================================ ; ; First, we calculate a fractal at twice the size, each way (i.e. 4 times ; the area) as the screen display area. ; ; We then display that fractal, zoomed-out to half size each way, so that ; it will exactly fill the screen display area. ; ; Then, we calculate another fractal twice the size of the display area, ; but this one is calculated "zoomed in" so that this fractal is a more ; detailed view of one-quarter of the area of the fractal just calculated. ; ; While we are calculating this, which takes a few seconds, we gradually ; zoom in the fractal we already have - using standard bitmap-zoom ; techniques - until eventually by the time the new fractal is finished, ; the old fractal will be showing at 1:1 size, and will can then be ; seamlessly replaced on the display by the new fractal at 1:2. Then ; repeat. ; ; Note: the fractal being DISPLAYED, i.e. the one that's already been ; calculated, can be zoomed to any point at all within the region, but ; this decision must be known in advance so that the next fractal is ; calculated from the right point. That's why you can't change the direction ; "realtime", only every so often. ; I would include references here but I worked the method out myself with ; no help from anything or anyone, so there aren't any... (oh, by the ; way, I assume this is the standard method everyone else uses too, it's ; nothing special or anything, just that I reinvented the wheel one more ; time ;) ; More details are included in the individual function descriptions. .MODEL tiny .386 .CODE .STARTUP jmp start Getch: mov ax,0 int 16h ret ; You can probably change height without messing things up, but ; changing the width will I think need some work. FRACWIDTH equ 256 FRACHEIGHT equ 128 ; Fractal parameter frac dd 3 ; Memory enlargebufferseg dw 0 ; segment address of the 64*32 enlarge buf. textureseg dw 0 ; the segment address of texture to display. fractalseg dw 0 ; the segment address of texture to create ;==================================================================== ;ZOOMTEXTURE - display picture at given zoom fraction ;-------------------------------------------------------------------- ; This is pretty obvious; just a standard, fixed-point bitmap ; display routine. The only extra bit you might notice is that ; it doubles each pixel, so as to get a larger screen display without ; taking too long to calculate the fractals. ; Data_______________________________________________________________ lfrac dw 0 hfrac dw 0 ; texture pixels per real pixel hfracb db 0 ; same but a byte ystart db 0 xstart db 0 ; where to start, in texturemap oldbx dw 0 oldss dw 0 yfracpos dw 0 ; fractional y-position stopat dw 0 ; where to stop in screen ram ; Source_____________________________________________________________ ZoomTexture: push ax push bx push cx push dx push si push di mov di,screenstart ; start position on display memory mov stopat,di add stopat,FRACHEIGHT*320 ; stop position on display memory mov bh,ystart mov bl,xstart ; start position on texture memory mov ax,hfrac mov hfracb,al mov dx,lfrac mov ax,ss mov oldss,ax mov ax,textureseg mov ss,ax ZoomYLoop: mov si,FRACWIDTH/2 mov oldbx,bx mov cx,0 ZoomXLoop: mov al,ss:[bx] mov ah,al mov es:[di],ax mov es:[di+320],ax add di,2 add cx,lfrac adc bx,hfrac dec si jnz ZoomXLoop mov bx,oldbx add yfracpos,dx adc bh,hfracb add di,640-FRACWIDTH cmp di,stopat jb ZoomYLoop mov ax,oldss mov ss,ax ; Check for keypress mov ah,1 int 16h jz zt_afterkeyhit jmp dfs_keyhit zt_afterkeyhit: pop di pop si pop dx pop cx pop bx pop ax ret ;==================================================================== ;CALCULATE - work out one pixel of Mandelbrot fractal ;-------------------------------------------------------------------- ; The fractal formula used, with coordinates x,y, is: ; A = B = 0. Colour=starting colour. ; ; new A = a squared - b squared - x ; new B = 2 * a * b - y ; ; If A squared + B squared > some number frac, then stop. ; If been round loop more than colour limit (64) times, then stop too. ; ; Otherwise, work out next A and B, and increment the colour we're going ; to use for the pixel. ; ; When we get out of the loop, that colour value is the one to draw at ; this pixel. ; Note: ; The fixed point sections of this are pretty crap; there are proper ways ; to do fixed point (I think...) so for god's sake don't copy those ; bits for your own fixed point code. ; Data_______________________________________________________________ colour db 0 ; colour to plot the pixel esign db 0 ; esign of multiplication result la dd 0 ha dd 0 lb dd 0 hb dd 0 ; a,b from a+bi. Low and high words thereof. lasq dd 0 hasq dd 0 lbsq dd 0 hbsq dd 0 ; a squared, b squared, low and high words. lnewa dd 0 hnewa dd 0 ; temporary storage for `new' veresion of `a'. lx dd 0 hx dd 0 ly dd 0 hy dd 0 ; co-oredinates in complex plane of this point ; Source_____________________________________________________________ Calculate: push eax push ebx push ecx push edx push esi push edi ; 0) Fix up the x,y data to specific point ; mov lx,2621 ; mov hx,1 ; mov ly,11107 ; mov hy,1 ; 1) setup colour mov colour,32 ; 2) clear the a,b and squared variables mov la,0 mov ha,0 mov lb,0 mov hb,0 mov lasq,0 mov hasq,0 mov lbsq,0 mov hbsq,0 CalcLoop: ; 3) increment colour inc colour ; 4) set up a-squared asquared: mov eax,lasq mov edx,hasq minusbsquared: ; 5) subtract b-squared sub eax,lbsq sbb edx,hbsq minusx: ; 6) subtract x and store result in new-a sub eax,lx sbb edx,hx mov lnewa,eax mov hnewa,edx ; 7) multiply a and b atimesb: ; a. setup variables mov ebx,la mov ecx,ha mov esi,lb mov edi,hb fixesigns: ; b. sort out esigns to be poesitive mov esign,0 cmp ecx,0 jge ecxok_1 xor ecx,0ffffffffh xor ebx,0ffffffffh add ebx,1 adc ecx,0 inc esign ecxok_1: cmp edi,0 jge ediok_1 xor edi,0ffffffffh xor esi,0ffffffffh add esi,1 adc edi,0 inc esign ediok_1: multiply: ; c. multiply the two numbers mov hb,0 mov eax,ebx mov edx,esi mul edx mov lb,edx mov eax,ebx mov edx,edi mul edx add lb,eax adc hb,edx mov eax,ecx mov edx,esi mul edx add lb,eax adc hb,edx mov eax,ecx mov edx,edi mul edx add hb,eax fixresultesign: ; d. fix the esign of the result test esign,1 jz esignok_1 xor hb,0ffffffffh xor lb,0ffffffffh add lx,1 adc hb,0 esignok_1: doublenumber: ; 8) Add this to itself, (with carry) mov eax,lb mov edx,hb add eax,eax adc edx,edx subtracty: ; 9) Subtract y and store in b sub eax,ly sbb edx,hy mov lb,eax mov hb,edx ; 10) Update a from new-a : combined with ; 11) Square a and store in a-squared ; a. setup variable starttosquare: mov ebx,lnewa mov ecx,hnewa mov la,ebx mov ha,ecx fixesign: ; b. sort out esign to be poesitive cmp ecx,0 jge ecxok_2 xor ecx,0ffffffffh xor ebx,0ffffffffh add ebx,1 adc ecx,0 ecxok_2: squareit: ; c. square data mov hasq,0 mov eax,ebx mul eax mov lasq,edx mov eax,ebx mul ecx add lasq,eax adc hasq,edx add lasq,eax adc hasq,edx mov eax,ecx mul eax add hasq,eax sametosquareb: ; 12) Square b and store in b-squared ; a. setup variable mov ebx,lb mov ecx,hb ; b. sort out esign to be poesitive cmp ecx,0 jge ecxok_3 xor ecx,0ffffffffh xor ebx,0ffffffffh add ebx,1 adc ecx,0 ecxok_3: ; c. square data mov hbsq,0 mov eax,ebx mul eax mov lbsq,edx mov eax,ebx mul ecx add lbsq,eax adc hbsq,edx add lbsq,eax adc hbsq,edx mov eax,ecx mul eax add hbsq,eax asquaredaddbsquared: ; 13) Setup a-squared mov eax,lasq mov edx,hasq ; 14) Add b-squared add eax,lbsq adc edx,hbsq ; 15) Compare with *n*, stop if > cmp edx,frac jg CalcFinish ; 16) If colour > *c*, stop cmp colour,63 jg CalcFinish jmp CalcLoop CalcFinish: ; 17) Return pixel colour pop edi pop esi pop edx pop ecx pop ebx pop eax mov al,colour ret ;==================================================================== ;FRACTAL - loop round to draw a 256x256 fractal using Calculate ;-------------------------------------------------------------------- ; Basically, this just uses fixed-point to go through all the ; x and y coordinates corresponding to SCREEN x and y. ; If you're confused about the "normaltime", "othertime", etc crap ; in the zooming-in section, well, I *think* this is because the ; Y-position sometimes needs to start at a half-pixel (i.e. "othertime") ; but usually ("normaltime") starts on a whole pixel. ; Note, it would be possible to speed this up by 1/4, simply by re-using ; the relevant pixels from the fractal calculated last: see this diagram ; We're zooming in to top left corner. ; Original fractal Fractal needs calculating next ; ; abcd.... a?b?c?d? ; efgh.... ???????? ; ijkl.... e?f?g?h? ; mnop.... ???????? ; ........ i?j?k?l? ; ........ ???????? ; ........ m?n?o?p? ; ........ ???????? ; ; where "." has been calculated on original fractal, but will not be used ; for the new one, and ? represents what actually needs to be calculated ; in the new one. (a,b,c,... could be copied from the old one). ; ; Actually, this routine doesn't copy over a,b,c,..., they are recalculated. ; Data_______________________________________________________________ lxcentre dd 0 hxcentre dd 0 lycentre dd 0 hycentre dd 0 ; co-ordinates of the window's centre, not used here ; (they are used in the main loop...) lxs dd 0 hxs dd 0 lys dd 0 hys dd 0 ; co-oredinates of the window's top left corner lxi dd 0 hxi dd 0 lyi dd 0 hyi dd 0 ; amount to increment fractal parameter per pixel ycount dw 0 xcount dw 0 ; loop counters nodraw db 1 ; whether or not to draw the last one ydirection db 1 xdirection db 1 ; direction of the zoom (0 = left/up, 1=centre, 2=rt/down) newxdirection db 1 newydirection db 1 screenstart dw 0 ; screen edisplay offset ; Source_____________________________________________________________ Fractal: push eax push ebx push ecx push edx push esi push edi ; 1) Initialise x and y parameters of fractal to xstart,ystart mov eax,lxs mov edx,hxs mov lx,eax mov hx,edx mov eax,lys mov edx,hys mov ly,eax mov hy,edx ; 1.5) Initialise zoom parameters mov lfrac,1024*63 mov hfrac,1 mov ystart,0 mov xstart,0 ; 2) Set up screen pointer push es mov ax,fractalseg mov es,ax mov edi,0 mov ycount,FRACHEIGHT FracYLoop: mov xcount,FRACWIDTH FracXLoop: ; 3) Calculate pixel call Calculate ; 4) Draw pixel mov es:[edi],al ; 5) Add X increment to X mov eax,lxi mov edx,hxi add lx,eax adc hx,edx ; 6) Increment screen poesition inc edi ; 7) If count >127, stop dec xcount jnz FracXLoop ; 8) Set X to xstart mov eax,lxs mov edx,hxs mov lx,eax mov hx,edx ; 10) Add Y increment to Y mov eax,lxi mov edx,hxi add ly,eax adc hy,edx ; 10.5) Draw zoomed last fractal if count%2==0 mov ax,ycount and al,1 jnz notthistime push es mov ax,0a000h mov es,ax ; Update zoom position mov yfracpos,0 mov al,xdirection add xstart,al cmp ydirection,0 je normaltime cmp ydirection,2 jne ydirection1 inc ystart jmp normaltime ydirection1: test ycount,2 jz othertime inc ystart jmp normaltime othertime: mov yfracpos,8000h normaltime: cmp nodraw,0 jne dontdraw call ZoomTexture dontdraw: pop es sub lfrac,1024 notthistime: ; 11) If count>127, stop dec ycount jnz FracYLoop pop es pop edi pop esi pop edx pop ecx pop ebx pop eax ret ;==================================================================== ; SWITCHTEXTURES - switch around the two buffers ;-------------------------------------------------------------------- ; This just changes the two buffers when one has been finished; so ; that the new fractal becomes the one that gets drawn to screen, and ; the old fractal will get written over by the one newly being ; calculated. ; This is a separate function, because if you decide to implement ; the 25% saving described above, you'll need to copy over those ; re-cycled pixels at some point, and this is a good time to do that. ; Source_____________________________________________________________ SwitchTextures: push ax push bx mov ax,fractalseg mov bx,textureseg mov fractalseg,bx mov textureseg,ax pop bx pop ax ret ;==================================================================== ;DOBACKGROUND - the background for lo-res part of demo ;-------------------------------------------------------------------- ; Just draws the swirly background things, very simple. DoBackground: push ax push bx push cx push di push es mov ax,0a000h mov es,ax mov bx,0 ; bh=y, bl=x mov di,0 mov cx,64000 mov al,0 rep stosb mov di,0 db_loop: mov al,bh mul bl and al,0fh add al,16 mov es:[di],al inc di inc bx cmp bl,0 jne notnextline add di,320-256 notnextline: cmp bx,256*193 jb db_loop mov si,0 mov di,256 push ds push es pop ds mov dx,192 copy_loop: mov cx,32 rep movsw add si,320-64 add di,320-64 dec dx jnz copy_loop pop ds ;mov di,256*193 ;mov cx,8*320 ;mov al,0 ;rep stosb pop es pop di pop cx pop bx pop ax ret ;==================================================================== ;DRAWSQUARE - fills a square, for showing which way things are going ;-------------------------------------------------------------------- ; I won't bother explaining this, you can all manage to draw a square ; by now... DrawSquare: push ax push dx push si push di push es mov si,ax mov ax,0a000h mov es,ax ; bh=starty, ax(now si)=startx ; dx=width and height ; cl=colour push dx mov di,bx shr di,8 mov ax,320 mul di add ax,si mov di,ax pop dx mov al,cl mov si,dx ds_yloop: mov cx,dx rep stosb add di,320 sub di,dx dec si jnz ds_yloop pop es pop di pop si pop dx pop ax ret ;==================================================================== ;DRAWDIRECTIONSQUARES - draw the motion direction indicators ;-------------------------------------------------------------------- ; this is pretty trivial too. DrawDirectionSquares: push ax push bx push cx push dx push si push di ; Clear all squares mov bh,0 ; was 16 mov ax,0 ; was 16 mov cl,0 mov dx,32 mov si,3 dds_yloop: mov di,3 dds_xloop: cmp di,2 jne dds_drawit cmp si,2 je dds_skipit dds_drawit: call DrawSquare dds_skipit: add ax,9*16 dec di jnz dds_xloop mov ax,0 add bh,5*16 dec si jnz dds_yloop ; Draw chosen square cmp newydirection,1 jne drawchosen cmp newxdirection,1 je afterchosen drawchosen: mov ah,0 mov al,newydirection imul ax,5*16 mov bh,al mov eax,0 mov al,newxdirection mov si,9*16 mul si mov dx,32 ; square side length mov cl,1 call DrawSquare add bh,4 add ax,4 sub dx,8 mov cl,0 call DrawSquare ; clear the inside afterchosen: ; Draw actual (current) square cmp ydirection,1 jne drawcurrent cmp xdirection,1 je aftercurrent drawcurrent: mov ah,0 mov al,ydirection imul ax,5*16 add ax,0 mov bh,al mov ah,0 mov al,xdirection mov si,9*16 mul si add bh,4 add ax,4 ; current square start now in bx. mov dx,24 ; square side length mov cl,2 call DrawSquare aftercurrent: pop di pop si pop dx pop cx pop bx pop ax ret ;==================================================================== ;DOFRACTALSECTION - the controllable fractals part of the demo ;-------------------------------------------------------------------- ; hopefully what with the background you have already read, this ; routine is self-explanatory. DoFractalSection: ; Now setup fractal parameters ; Start at preplanned position mov hxcentre,000000000h mov lxcentre,04afadfffh mov hycentre,0fffffffeh mov lycentre,08f71bfffh ; and increment by 1/64 per pixel mov hxi,0 mov lxi,1024*65536 mov hyi,0 mov lyi,1024*65536 mov screenstart,33*320+32 dfs_fracloop: ; Calculate new hxcentre etc depending on xdirection,ydirection ; (new position = FW/4*(xdirection+1),FH/4*(ydirection+1) on the display) ; xcentre=xcentre+fw/4*(xdirection-1)*xi cmp xdirection,1 je xchangedone mov edx,FRACWIDTH/2 mov eax,lxi mul edx cmp xdirection,2 jne xchangeminus add lxcentre,eax adc hxcentre,edx jmp xchangedone xchangeminus: sub lxcentre,eax sbb hxcentre,edx xchangedone: ; Same for Y: cmp ydirection,1 je ychangedone mov edx,FRACHEIGHT/2 mov eax,lyi mul edx cmp ydirection,2 jne ychangeminus add lycentre,eax adc hycentre,edx jmp ychangedone ychangeminus: sub lycentre,eax sbb hycentre,edx ychangedone: ; Calculate start hxs,lxs hys,lys to keep hxcentre in middle (at 128,128) ; xs=xcentre-128*xi ys=ycentre-128*yi mov eax,lxi mov edx,FRACWIDTH/2 mul edx mov ebx,eax ; bx is l(xi*128) mov ecx,edx ; cx is h(xi*128) xor ebx,0ffffffffh xor ecx,0ffffffffh inc ecx ; cx:bx now negative'd add ebx,lxcentre adc ecx,hxcentre mov lxs,ebx mov hxs,ecx ; Same for Y: mov eax,lyi mov edx,FRACHEIGHT/2 mul edx mov ebx,eax ; bx is l(xi*128) mov ecx,edx ; cx is h(xi*128) xor ebx,0ffffffffh xor ecx,0ffffffffh inc ecx ; cx:bx now negative'd add ebx,lycentre adc ecx,hycentre mov lys,ebx mov hys,ecx ; Calculate next fractal while we zoom the last one call Fractal ; Switch the texture buffers, including copying 1/4 of the pixels call SwitchTextures cmp nodraw,1 jne alreadydrawing mov nodraw,0 call DoBackground mov cx,0 alreadydrawing: ; Double magnification mov eax,lxi mov edx,hxi shr eax,1 shr edx,1 mov lxi,eax mov hxi,edx mov lyi,eax mov hyi,edx ; Update direction mov al,newxdirection mov xdirection,al mov al,newydirection mov ydirection,al call DrawDirectionSquares jmp dfs_fracloop dfs_keyhit: call Getch cmp al,'7' jne not7 mov newxdirection,0 mov newydirection,0 not7: cmp al,'4' jne not4 mov newxdirection,0 mov newydirection,1 not4: cmp al,'1' jne not1 mov newxdirection,0 mov newydirection,2 not1: cmp al,'8' jne not8 mov newxdirection,1 mov newydirection,0 not8: cmp al,'5' jne not5 mov newxdirection,1 mov newydirection,1 not5: cmp al,'2' jne not2 mov newxdirection,1 mov newydirection,2 not2: cmp al,'9' jne not9 mov newxdirection,2 mov newydirection,0 not9: cmp al,'6' jne not6 mov newxdirection,2 mov newydirection,1 not6: cmp al,'3' jne not3 mov newxdirection,2 mov newydirection,2 not3: cmp al,27 je breakout call DrawDirectionSquares donethecentrechange: jmp zt_afterkeyhit ;==================================================================== ;MAIN SECTION & MISC ;-------------------------------------------------------------------- Init: push ax push bx ; 1) Setup ES segment mov ax,0a000h mov es,ax ; 2) Allocate RAM ; This is a COM program, so we just set the segments to spare ; space in memory. (well, hopefully spare space :) push cs pop ax add ax,4096 ; textureseg is 64k above our segment mov textureseg,ax add ax,4096 ; and fractalseg is 128k above mov fractalseg,ax sub ax,4096 sub ax,2048 ; enlargebuffer is 1/2 way through our segment. mov enlargebufferseg,ax ; 3) Do graphics mode mov ax,0013h int 10h pop bx pop ax ret Shutdown: push ax mov ax,0003h int 10h pop ax ret message db 'Thanks for watching the modified version of 4ge',39,'S XMaS 94 iNTRo.' db 13,10,13,10 db 'Get 4ge-xmas.zip from ftp.cdrom.com for the full version, Tseng gfx only.',13,10,13,10,'$' Start: call Init call DoFractalSection breakout: call Shutdown mov ah,09h mov dx,offset message int 21h int 20h END