; A win32asm graphics app, which generates an image of Pascal's Triangle by a method best 
; know as The Chaos Game.

; I fully indorse any plagiarism of this code.
; Eóin O'Callaghan, eoinoc@iol.ie

.586
.MODEL FLAT,STDCALL
OPTION SCOPED
OPTION CASEMAP:NONE

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc

includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib

WM_CASE MACRO reg,msgs		; The Svins MACRO for Window Message processing
	irp msg,<msgs>
		cmp reg,WM_&msg
		je j&msg
	endm
endm

.data
ClassName db "Win32",0
AppName  db "Order in Chaos",0

.DATA?
wc WNDCLASSEX {?}
msg MSG {?}
rect RECT {?}
gWnd dd ?					; Global variable with window handle
gIst dd ?					; Global variable holds Instance

bDc dd ?					; Back Buffer DC
pBb dd ?					; Pointer to Back Buffer Bitmap Bits
hBm dd ?					; Back Buffer Bitmap Handle

TriPoint POINT {?, ?}, {?, ?}, {?, ?}	; Will hold 3 corners of triangle

.CODE
ALIGN 4
WinProc Proc uses ebx edi esi hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
LOCAL ps:PAINTSTRUCT
	mov eax,uMsg
	WM_CASE eax,<PAINT,SIZE,DESTROY>			; MACRO will process these three messages
	invoke DefWindowProc,hWnd,uMsg,wParam,lParam; Otherwise message is passed to default proc
	ret

	jSIZE:
	invoke GetClientRect,hWnd,addr rect			; Store Window dimensions in rect struct
	call InitDoubleBuffer						; Initalsie Back Buffer
	call RenderFractal							; Render Fractal

	xor eax,eax
	ret

	jPAINT:
	invoke BeginPaint,hWnd,addr ps
		invoke BitBlt,ps.hdc,0,0,rect.right,rect.bottom,\	; Blt Backbuffer to window DC
		              bDc,0,0,SRCCOPY
	invoke EndPaint,hWnd,addr ps
	xor eax,eax
	ret

	jDESTROY:
	invoke DeleteDC,bDc							; Delete Buffer DC
	invoke DeleteObject,hBm						; Delete Buffer Bitmap
	invoke PostQuitMessage,NULL					; Quit App
	ret
WinProc ENDP

ALIGN 4
InitDoubleBuffer Proc
LOCAL bih:BITMAPINFOHEADER
	invoke DeleteDC,bDc							; Delete Old DC
	invoke DeleteObject,hBm						; Delete Old Bitmap

	; In order to set up a back bufer you need to create a DC. You then need to fill out a 
	; BITMAPINFOHEADER adn use CreateDIBSection to create a DIB Section. You can then select
	; the DIB Section into the Back Buffers DC and from that you can BitBlt it.
	
	invoke CreateCompatibleDC,0					; Create the Backbuffer DC
	mov bDc,eax
	
	lea edx,bih										; edx point to BITMAPINFOHEADER memory
	mov [edx].BITMAPINFOHEADER.biSize,sizeof(BITMAPINFOHEADER)
	mov eax,rect.right
	mov (BITMAPINFOHEADER ptr [edx]).biWidth, eax	; Set width to that of main window
	mov eax,rect.bottom
	mov [edx].BITMAPINFOHEADER.biHeight,eax			; And match Heights
	mov [edx].BITMAPINFOHEADER.biPlanes,1
	mov [edx].BITMAPINFOHEADER.biBitCount,32		; Set 32bit colors, ie dwords
	mov [edx].BITMAPINFOHEADER.biCompression,BI_RGB ; Pixel storage format
	mov [edx].BITMAPINFOHEADER.biSizeImage,0
	mov [edx].BITMAPINFOHEADER.biXPelsPerMeter,0
	mov [edx].BITMAPINFOHEADER.biYPelsPerMeter,0
	mov [edx].BITMAPINFOHEADER.biClrUsed,0
	mov [edx].BITMAPINFOHEADER.biClrImportant,0

	invoke CreateDIBSection,bDc,edx,DIB_RGB_COLORS,addr pBb,0,0 ; Here pBb will be set with
	mov hBm,eax													; a pointer to bitmap bits
	invoke SelectObject,bDc,eax
ret
InitDoubleBuffer EndP

Rnd3Bit Proc		; This procedure generates 3 random bits. A slight change can be made to 
.data				; alter this value up to at least 20bits. 
	RndInit dd 0A2F59C2Eh
.code
	mov edx,RndInit
	mov eax,3;  <- Here's where it's set to 3 bits
rl: rol edx,1
	jnc rs
	xor edx,0ah
rs: dec eax
	jne rl
    mov eax,edx
    rcr	edx,1
    mov RndInit,edx
ret
Rnd3Bit EndP

RenderFractal Proc	
; This procdure renders the image of the fractal, Pascals Triangle. Perhaps the best example 
; of perfect order out of absolutly random chaos.
; The fractal is generated by starting at any point, then for each iteration of the loop you 
; pick one of the three points at random and move to a position half way between current point
; and the chosen corner. You then plot a pixel at that location.

; The method of selecting the postion half way is optimised so I'll explain it. The obvious
; method is to get the distance from the current point to the corner, half that value and add 
; it to the current point. So that equation for x would be:
; CurX + (CornerX - CurX)/2
; Those who like maths may sopt that this can be shortened to:
; (2CurX + CornerX - CurX)/2 => (CurX + CornerX)/2
; An identical equation works for the y coords.

; Note that in a DIB Section the origion (0,0) is the bottom left corner. Not the top left 
; as in most windows GDI functions.

	mov eax,rect.right		; eax = window width.
	shr eax,1				; eax = width/2.
	mov TriPoint[0].x,eax	; x coord of first point = eax = witdh/2.
	lea eax,[eax*2][-5]     ; eax = (eax*2)-5 = width-5.
	mov TriPoint[8].x,eax   ; x coord of 2nd point = eax, i.e. 5 pixels from right side.
	mov TriPoint[16].x,5	; x coord of 3rd point = 5, i.e. 5 pixels from left side.
	mov eax,rect.bottom		; eax = height.
	sub eax,5				; eax = height-5.
	mov TriPoint[0].y,eax	; y coord of 1st point = eax = height-5. i.e. 5 from top.
	mov TriPoint[8].y,5		; y coord of 2nd point 5 from bottom.
	mov TriPoint[16].y,5 	; y coord of 3rd point 5 from bottom.

	mov ebx,TriPoint[0].x	; ebx will hold current x throughout loop, initalise to point 1.
	mov ecx,TriPoint[0].y	; ebx will hold current y throughout loop. 

	mov edi,pBb				; edi points to bitmap bits.
	mov esi,100000			; esi acts as loop counter. Note higher esi = better image & slower

	rLp:call Rnd3Bit		; Get random 3 bit number.
		and eax,3			; convert to num between 0-3.
		jz rLp				; if 0 then get new number. (Only 3 points, not 4)
		
		add ebx,TriPoint[eax*8][-8].x	; CurX = CurX + CornerX
		shr ebx,1						; CurX = (CurX + CornerX)/2
		add ecx,TriPoint[eax*8][-8].y	; CurY = CurY + CornerY
		shr ecx,1						; CurY = (CurY + CornerY)/2

		mov eax,rect.right
		xor edx,edx
		mul ecx							; eax = CurY * width
		add eax,ebx						; eax = eax + Cur X = (CurY * width) + CurX

		mov dword ptr [edi][eax*4],0FFFFFFh	; Set Pixel to White
	dec esi					; decrement counter
	jnz rLp					; repeat loop if unfinished
ret
RenderFractal EndP

ALIGN 4
Start:
lea edi,wc						
xor eax,eax						
mov ecx,(sizeof WNDCLASSEX)/4	
rep stosd						; Zero wc WNDCLASSEX Struct

invoke GetModuleHandle,eax
mov gIst,eax
mov wc.hInstance,eax
mov wc.cbSize, sizeof WNDCLASSEX
mov wc.style,CS_OWNDC or CS_HREDRAW or CS_VREDRAW
mov wc.lpszClassName,offset ClassName
mov wc.lpfnWndProc,offset WinProc
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
mov wc.hbrBackground,COLOR_WINDOW+1
invoke RegisterClassEx,ADDR wc

xor eax,eax
invoke CreateWindowEx,0,ADDR ClassName,ADDR AppName,WS_OVERLAPPEDWINDOW,\
					  10,10,300,200,eax,eax,gIst,eax
mov gWnd,eax					; Store handle in ase its needed later.

push eax
push SW_SHOWNORMAL
push eax
call ShowWindow
call UpdateWindow

ml:	xor eax,eax
	lea edi,msg
	invoke GetMessage,edi,eax,eax,eax
	test eax,eax
	jz qm
		push edi
		push edi
		call TranslateMessage
		call DispatchMessage
jmp ml
	ALIGN 4
qm:	mov eax,msg.wParam
	invoke ExitProcess,eax
END Start