; DOS or bootable sierpinski triangle hack
;
; Author: John Tsiombikas <nuclear@member.fsf.org>
; License: public domain or CC0
;
; Build for DOS: nasm -o sierp.com -f bin sierp.asm
; Build to boot: nasm -o sierp.img -f bin -DBOOTSECT sierp.asm
;       (then dd if=sierp.img of=/dev/<usbstick> bs=512)

	bits 16
%ifdef BOOTSECT
	org 7c00h
%else
	org 100h
%endif

NUM_POINTS equ 8192
SHADOW_OFFS equ 5

%macro SETPAL 4
	push word %4
	push word %3
	push word %2
	push word %1
	call setpal
	add sp, 8
%endmacro

start:
%ifdef BOOTSECT
	xor ax, ax
	mov ds, ax
	mov es, ax
	mov ss, ax
	jmp 00:.setcs
.setcs:
	mov sp, 0
%endif
	; call video bios (10h) to set video mode (call 00h), to mode
	; 13h (320x200 8bpp).
	mov ax, 13h	; call number in ah (0), call argument in al (13h)
	int 10h		; call video bios

	; "allocate" the next segment for the framebuffer
	mov ax, es
	add ax, 1000h
	mov es, ax

	; setup palette for the effect
	; - bg color (80h)
	; - shadow color (81h)
	; - fractal color (82h)
	SETPAL 80h, 40, 40, 80
	SETPAL 81h, 16, 16, 32
	SETPAL 82h, 96, 192, 96

.mainloop:
	call animate

	; clear the framebuffer
	mov eax, 80808080h	; bg color
	mov ecx, 16000		; framebuffer size in 32bit units (320x200/4 = 16000)
	xor di, di		; start from offset 0
	rep stosd		; repeat (while ecx != 0) the "store string dword" instr.

	; draw shadow
	push word SHADOW_OFFS	; offset for the shadow
	push word 81h		; shadow color
	call drawsierp
	; draw fractal
	mov bp, sp
	mov word [bp + 2], 0	; reset offset
	mov word [bp], 82h	; fractal color
	call drawsierp
	add sp, 4		; clean up arguments from the stack

	; copy framebuffer to video ram
	push ds
	push es
	; point ds:si (source) to es:0
	mov ax, es
	mov ds, ax
	xor si, si
	; point es:di (dest) to a000:0
	mov ax, 0a000h
	mov es, ax
	xor di, di
	mov ecx, 16000
	call wait_vblank
	rep movsd
	pop es
	pop ds

%ifdef BOOTSECT
	jmp .mainloop
%else
	in al, 60h	; read pending scancode from the keyboard port (if any)
	dec al		; ESC is 1, so decrement ...
	jnz .mainloop	; ... and loop as long as the result was not 0

	; switch back to text mode (mode 3)
	mov ax, 3
	int 10h
	; return to dos
	ret
%endif

	; VBLANK is bit 3 of the input status #1 register (port 3dah).
	; It's 0 while we're in the visible area, and 1 if we're in the
	; vertical blank period.
wait_vblank:
	mov dx, 3dah
	; in case we're already in vblank, wait until we get out of it
.inblank:
	in al, dx
	and al, 8
	jnz .inblank	; loop while vblank bit is 1 (in vblank)
	; wait until vblank starts
.notblank:
	in al, dx
	and al, 8
	jz .notblank	; loop while vblank bit is 0 (visible area)
	ret

	; draw sierpinski triangle
drawsierp:
	mov bp, sp
	; start from one of the vertices
	mov ax, [sierp_verts]
	mov bx, [sierp_verts + 2]
	mov [sierp_pt], ax
	mov [sierp_pt + 2], bx
	; number of iterations in cx
	mov cx, NUM_POINTS
.loop:	; pick a vertex at random and move half-way there
	call rand
	mov bx, 3
	xor dx, dx
	div bx	; dx is now rand % 3
	; put the vertex address in ebx
	mov edi, sierp_verts
	movzx ebx, dx
	lea ebx, [ebx * 4 + edi]
	; add to sierp_pt and divide by 2 to move half-way there
	mov ax, [bx]
	add ax, [sierp_pt]
	shr ax, 1
	mov [sierp_pt], ax	; store the resulting X back to [sierp_pt]
	mov di, ax		; save X coordinate in di
	mov ax, [bx + 2]
	add ax, [sierp_pt + 2]
	shr ax, 1
	mov [sierp_pt + 2], ax	; store the reuslting Y back to [sierp_pt + 2]
	add ax, [bp + 4]	; add offset
	mov bx, ax
	shl ax, 8
	shl bx, 6
	add bx, ax		; bx = Y * 320
	add bx, [bp + 4]	; add offset
	mov al, [bp + 2]	; color to set
	mov byte [es:bx + di], al

	dec cx
	jnz .loop
	ret

	align 2
sierp_verts:
	dw 160, 40
	dw 240, 160
	dw 80, 160
sierp_pt dw 0, 0
sierp_vel:
	dw 1, 1
	dw -1, 1
	dw -1, -1

animate:
	mov cx, 3
	mov di, sierp_verts
	mov si, sierp_vel
.loop:
	mov ax, [di]		; grab vertex X
	add ax, [si]		; add velocity X
	jl .xout
	cmp ax, 320-SHADOW_OFFS
	jge .xout
	jmp .skip_xflip
.xout:
	sub ax, [si]		; revert to previous X
	neg word [si]		; negate velocity X
.skip_xflip:
	mov [di], ax		; update vertex X

	; to do the same for Y increment edi and esi by 2
	add di, 2
	add si, 2
	mov ax, [di]		; grab vertex Y
	add ax, [si]		; add velocity Y
	jl .yout
	cmp ax, 200-SHADOW_OFFS
	jge .yout
	jmp .skip_yflip
.yout:
	sub ax, [si]		; revert to previous Y
	neg word [si]		; negate velocity Y
.skip_yflip:
	mov [di], ax		; update vertex Y

	add di, 2
	add si, 2
	dec cx
	jnz .loop
	ret

	; set palette entry (idx, r, g, b)
setpal:
	mov bp, sp
	mov dx, 3c8h		; DAC index port
	mov al, [bp + 2]
	out dx, al
	inc dx			; DAC data port
	mov al, [bp + 4]
	shr al, 2
	out dx, al
	mov al, [bp + 6]
	shr al, 2
	out dx, al
	mov al, [bp + 8]
	shr al, 2
	out dx, al
	ret

	; random number generator
rand:
	mov eax, [randval]
	mul dword [randmul]
	add eax, 12345
	and eax, 0x7fffffff
	mov [randval], eax
	shr eax, 16
	ret

	align 4
randval dd 0
randmul dd 1103515245

%ifdef BOOTSECT
	times 510-($-$$) db 0
	db 0x55,0xaa
%endif
; vi:ft=nasm: