[BITS 16]
[ORG 0x7c00]

; Color 0x3f is a lovely pink.
; Color 0xa8 is a fresh green.

;;; Initialization

shl ebx,16
imul ebx

; hash 1
;mul bx      ; ax = ax * bx
;mov dx,5    ; dx = 5
;div dx      ; To get dx = ((ax * bx) % 5). ax = (ax * bx) / 5
;            ; We should back up ax and restore it, but this
;            ; only affects periodicity in the long term.
;imul bx, ax ; bx = bx * bx
;imul bx, dx ; bx = bx * bx * ((ax * bx) % 5)
;
;xor ax, bx  ; ax = ((ax * bx) / 5) xor (bx * bx * ((ax * bx) % 5))
;; hash 2
;mov bx, ax  ; bx = ax
;add ax, 3   ; ax = ax + 3
;mul bx      ; ax = ax * (ax + 3)
;mov bx, 719 ; bx = 719
;div bx      ; dx = ax * (ax + 3) % 719


; Switch to 320x200x256 vga mode
mov ax, 0x0013
int 10h

; front buffer
; Computer's y axis goes down, but our's goes up.
; So when we set pixels, we do (y * -320) + x,
; with y starting at 1.
; But the screen area is 320*200*1 = 64000 bytes long,
; and the segment size is 2^16     = 65536 bytes long.
; Thus our first (bottom) line's start is shifted 1536
; bytes past the end of the screen area. So we substract
; this value from the segment base.
; And since the segment base is measured in multiple of
; 16, we substract only 1536 / 16 = 96
; To avoid counting y starting at 1, one could add a
; 'inc di' in the setpixel procedure (see below)
push 0xa000 - 96
pop fs

;;; Define color palette
;xor bx, bx      ; color number
;xor cx, cx      ; green, blue
;xor dh, dh      ; red
;mov ax, 0x1007  ; get color index from color number
;int 10h
;mov bl,bh
;xor bh,bh
;mov

; Convert to greyscale
;mov bx, 0
;mov cx, 0xff
;mov ax, 0x101b
;int 10h


;; soil gradient (earth-plants-snow)
;; hsv from #b1bbbe to #362a16

;; green gradient :
;; 255 : 210 210 210 -> 48  255 103
;; 64  : 52  52  52  -> 12  63  25
;; diff :               40  11  27

define_color_palette:
	push 0xa000 - 96
	pop es
	xor bx,bx
	xor di,di
	.setcolor:
;		; red
;		mov ax, bx
;		imul ax, -40
;		sar ax,6     ; /64
;		add ax,52
;			stosb
;		; green
;		mov ax, bx
;		imul ax, 11
;		sar ax,6     ; /64
;		add ax,52
;			stosb
;		; blue
;		mov ax, bx
;		imul ax, -27
;		sar ax,6     ; /64
;		add ax,52
;			stosb
		
		;;; Black & white
		mov ax, bx
		shr ax, 2
		neg al
		stosb
		stosb
		stosb
		dec bl
	jnz .setcolor
	
	mov ax, 1012h
	xor bx,bx
	mov cx,0xff
	xor dx,dx
	int 10h

;;; Show palette
;	xor ax,ax
;	xor di,di
;	xor cx,cx
;	dec cx
;	rep stosb
;	
;	xor al,al
;	inc al
;	xor di,di
;	.setpixel:
;		stosb
;		inc al
;	jnz .setpixel
;hlt

;;; Main program

;xor si, si  ; camera y position
;xor di, di  ; camera x position
mainloop:
	; render frame...
	call gen_terrain
	
	; From this separator to next one, the code checks if a
	; character is available, if not goes back to mainloop.
	; If there's something to read, it clears the keyboard
	; buffer by effectively reading the character
	; It then increments or decrements either
	;    dx (vertical position) or
	;    bx (horizontal position)
	; depending on the arrow key pressed (up/down/left/right)
	; 
	; THE WHOLE STUFF IS ONLY 29 BYTES OF MACHINE CODE
	; 
	; =======================================================
	
	; Check for next key
	mov ah, 0x01
	int 16h
	jz mainloop
		; Manage pressed key and jump back to mainloop
		xor ax,ax
		int 16h   ; clear keyboard buffer
		
		; We create an opcode ex nihilo, store it to
		; some memory place and jump to it.
		; 
		; inc / dec opcodes look like this :
		; 
		;   0100 drrr
		; 
		; Where d = 1 when decrementing, 0 otherwise
		; Where rrr is a register. Possible values are:
		; 
		;   000 ax    100 sp
		;   001 cx    101 bp
		;   010 dx    110 si
		;   011 bx    111 di
		; 
		; We choose 0100 d110 (inc/dec si) for y movement,
		; and       0100 d111 (inc/dec di) for x movement
		
		mov al,ah
		
		and al,0x01             ; bit 1: switch from dx to bx when it's a horiz key.
		
		and ah,0x12             ; zero = positive move, nonzero = negative move
		jz .positive
			or al,0x08          ; in opcode, set 'd' bit
		.positive:
		
		or al, 0x46             ; in opcode, set 'inc/dec' bit (0x40), and 'si/di' bits (0x06)
		
		; insert generated opcode just before mainloop. since we never go before
		; mainloop, it's just dead code now, so we don't care about overwriting it.
		; Then jump to it. if you don't put an instruction (nop, jmp, ...) between
		; the mov and the place where you stored the opcode, it's not gonna work.
		
		mov [mainloop - 1],al
		jmp short mainloop - 1
		
	; =======================================================



;;; Generate terrain
gen_terrain:
	pusha
	xor bh, bh
	.start_gen_terrain:
	
	; ax screen y         ;; y for get_z
	; bx bh:tree(1) vs terrain(0) bl : color
	; cx screen x
	; dx cur_y
	; sp --
	; bp traceto          ;; x for get_z
	; si camera_y
	; di camera_x
	
	mov cx, 320                     ; screen x = 320
	.gen_column:
		mov ax, 0x0100              ; screen y = 200; sy += 256        ;;; DEBUG, should be 200+0x0100
		xor dx, dx                  ; dx = cur_y (current y max)
		.gen_pixel:
			fild word [_1024]       ; st0 = 1024
			fldpi                   ; st0 = pi                           st1=1024
			fdiv st0, st1           ; st0 = pi / 1024                    st1=1024
			
			mov [temp], ax
			fild word [temp]        ; st0 = sy                           st1=pi/1024     st2=1024
			fmul st0, st1           ; st0 = fsy = sy * (pi / 1024)       st1=pi/1024     st2=1024
			
			;;; TODO : documentation claims fptan will fail
			;;; if st7 isn't empty, plus we have to pop out
			;;; st0 after. So it's simpler to do sin/cos.
			;;; fptan               ; st0 = tan(fsy)                     st1=pi/1024     st2=1024
			fsincos                 ; st0 = cos(fsy), st1 = sin(fsy)                     st2=pi/1024     st3=1024
			;;; TODO : screen is upside-down. we could use fdivrp and then adjust y_start
			fdivp st1, st0          ; st0 = tan(fsy) = sin/cos           st1=pi/1024     st2=1024
			fmul st0, st2           ; st0 = mradius = 1024 * tan(fsy)
			
			mov [temp], cx
			fild word [temp]        ; st0 = sx                           st1=mradius     st2=pi*1024     st3=1024
			fmul st0, st2           ; st0 = fsx = sx * (pi / 1024)       st1=mradius     st2=pi*1024     st3=1024
			fsincos                 ; st0 = cos(fsx), st1 = sin(fsx)                     st2=mradius     st3=pi*1024     st4=1024
			
			fmul st0, st2           ; st0 = cos(fsx) * mradius           st1=sin(fsx)    st2=mradius     st3=pi*1024     st4=1024
			fistp word [map_y]      ; st0 = sin(fsx)                     st1=mradius     st2=pi*1024     st3=1024
			
			fmul st0, st1           ; st0 = sin(fsx) * mradius           st1=mradius     st2=pi*1024     st3=1024
			fistp word [map_x]      ; st0 = mradius                      st1=pi*1024     st2=1024
			
			call get_z
			
			fild word [map_z]       ; st0 = mz*2                         st1=mradius     st2=pi*1024     st3=1024
			fsubr st0, st3          ; st0 = remaining_mz = 1024 - mz*2   st1=mradius     st2=pi*1024     st3=1024
			fld st1                 ; st0 = mradius                      st1=rem_mz      st2=mradius     st3=pi*1024     st4=1024
			fpatan                  ; st0 = atan(st1/st0)                st1=mradius     st2=pi*1024     st3=1024
			
			;;; TODO : comment fild, and use directly fmul ?
			fild word [_255]        ; st0 = 255                          st1=atan(...)   st2=mradius     st3=pi*1024     st4=1024
			fmul st0, st1           ; st0 = remaining_sy = 255 * atan()  st1=atan(...)   st2=mradius     st3=pi*1024     st4=1024
			
			fistp word [temp]       ; st0 = atan(...)                    st1=mradius     st2=pi*1024     st3=1024
			mov bp, [temp]          ; bp = remaining_sy
			;;; TODO: could use dl, but might overflow
			neg bp                  ; bp = - remaining_sy
			add bp, 128             ; bp = traceto = 128 - remaining_sy
			
			mov bl, [map_z]         ; bl = color
			
			test bh,bh
			jnz .dotrees
				.drawline:
					cmp dx, bp          ; compare cur_y (dx) and traceto (bp)
				jg .endline
					call setpixel       ; set a pixel
					inc dx              ; cur_y++
					jmp .drawline       ; next pixel of line
				.endline:
			.notree:
		inc al                      ; y--                           ;;; DEBUG should be dec al
		cmp al, 200                                                 ;;; DEBUG should not be there
		jne .gen_pixel              ; go up (down?) one pixel       ;;; DEBUG should be jnz
;	dec cx                          ; x--
;	jnz .gen_column                 ; previous column
	loop .gen_column
	
	test bh,bh
	jnz .end_gen_terrain
;		pusha
		inc bh
		jmp .start_gen_terrain
	.end_gen_terrain:
	popa
ret

			.dotrees:
				cmp bl, 194
				jne .notree
					pusha
						; st0 is size factor parameter.
						mov si, dx
						mov di, cx
						call tree
					popa
				jmp .notree



;;; Heightmap function. 69 freakin' bytes.
; Since it is called only once, we shouldn't need to make it a
; separate function (we save the "call" and the "ret")

; get_z([map_x], [map_y]) -> [map_z]
get_z:
	pusha
		mov ax, [map_y]
		shl si, 6               ;;; TODO : modify keyboard inc/dec hack to add/sub 64 instead.
		add ax, si              ; my += camy
		mov dx, [map_x]
		shl di, 6
		add dx, di              ; mx += camx
		
		xor bp, bp              ; bp = z_tot = 0
		mov cx, 8               ; cx (cl) = detail level
		
		.detail_level:
			push dx                 ; push dx = x
			push ax                 ; push ax = y
			
			;; Todo? : initialize at 1111...b and shr mask,1 at each detail level
			mov bx, 1               ; bx = 1
			shl bx, cl              ; bx = 1 << detail
			dec bx                  ; bx = mask = (1 << detail) -1
			
			mov di, dx              ; di = x
			mov si, ax              ; si = y
			and di, bx              ; di = xm = x & mask
			and si, bx              ; si = ym = y & mask
			
			shr dx, cl              ; dx = xd = x >> detail
			shr ax, cl              ; ax = yd = y >> detail
			
			; Convention : z[x][y] where for y and x,
			;   0 means the real value
			;   1 means the real value minus one (one step to the left)
			
			; z00
			call random             ; ax = z00 = random (ax = yd, dx = xd)
			
			; z01
			; TODO : push si and get it back when inverting again
			neg si                  ; si = - ym
			add si, bx              ; si = mask - ym
			dec ax                  ; ax = yd - 1
			call random             ; ax = z01 = random (yd-1, xd)
			
			; z11
			neg di                  ; di = - xm
			add di, bx              ; di = mask - xm
			dec dx                  ; dx = xd - 1
			call random             ; ax = z01 = random (yd-1, xd-1)
			
			; z10
			neg si                  ; si = - ym
			add si, bx              ; si = mask - ym
			inc ax                  ; ax = yd
			call random             ; ax = z01 = random (yd, xd-1)
			
			pop ax                  ; ax = y
			pop dx                  ; dx = x
			inc ax                  ; blurs artifacts. TODO : we can do without
			inc dx                  ; blurs artifacts. TODO : we can do without
			
		loop .detail_level
		
		mov ax, bp
		shr ax, 9                   ; max_detail + 1 it seems (???)
;		xor ah, ah                  ; shorter than shr ax, 8
		
		;;; partially compute remaining_mz :
		shl ax,1
		mov [map_z], ax
	popa
	ret

; Deterministic pseudo-random number generator
; random(ax,dx)
;   returns random by adding it to bp (part of the get_z code creeped in here)
random:
	push dx
	push ax
	push cx
		xchg ax, dx      ; DEBUG
		mov cx, 3
		.random_loop:
			add dx, ax
;			push dx     ; dx is overwritten by mul dx, so we should save it
				mul dx  ; for better quality, but running the routine three
;			pop dx      ; times instead of two seems to be enough actually.
			xor dx, ax
			add al, 211 ; should be ax, but we save a byte being inaccurate
			shr dx,1
		loop .random_loop ; do it three times
		;shr ax,1       ; we should do this to avoid artefacts, but it seems ok without
		
	pop cx
		mul di                  ; ax = zXX * xm       dx = upper bits
		shrd ax, dx, cl         ; ax = (zXX * xm * ym) >> detail
		
		mul si                  ; ax = zXX * xm * ym  dx = upper bits
		shrd ax, dx, cl         ; ax = (zXX * xm * ym) >> detail
		shrd ax, dx, 9          ; ax = (zXX * xm * ym) >> detail
		
		add bp, ax              ; bp = z_tot += (zXX ...) >> ...
	pop ax
	pop dx
	ret



tree:
; ax al=length ah=old_length
; bx bl=color  bh=branch number
; cx x
; dx y
; sp --
; bp
; si screen y
; di screen x
; st0 : size factor (parameter)
	
	; each bit in bh indicates if we should turn clockwise
	; or ccw at the end of each branch. So we rotate it each
	; time to test the next bit.
;	pusha           ;;; done by caller
;	mov bh, 0xf0    nice tree too.
	mov bh, 0xff
	
	.drawbranch:
		; Store 100 in al and 128 in ah
		mov ax, 100 + 128*256 ; length=al=100, old_length=ah=128
		
		; Use pi instead of 0 to rotate everything by 180°,
		; so y points up. x shall be inversed too, but we
		; don't care about x symmetry
		fldpi                  ; angle=0  st1=sizefactor
		fldz                   ; x=0      st2=size
		fldz                   ; y=0      st3=size

		.drawline:
			.drawpixel:
				; compute new y
				fld st2             ; Copy angle                                          st4=size
				fsincos             ; Compute x_shift (st1) and y_shift (st0); pop angle  st5=size
				fmul st0, st5                                                            ;st5=size
				faddp st2           ; y += y_shift; pop y_shift                           st4=size
				fmul st0, st4                                                            ;st4=size
				faddp st2           ; x += x_shift; pop x_shift                          ;st3
				
				; store y and x
				fist word [temp]    ; Store y in temp. word or dword, who cares ?        ;st3
				mov dx, [temp]      ; dx = y
				neg dx
				add dx, si
				fld st1             ; get x                                              ;st4
				fistp word [temp]   ; Store x in temp (and pop it to hell)               ;st3
				mov cx, [temp]      ; cx = x
				add cx, di
;				sub cx, 100         ;;; might be deleted
				
				; call setpixel(cx,dx)
				call setpixel
				
				dec al
			jnz .drawpixel
			
			
			fld1                ; Load 1 in order to add/substract it to angle           ;st4
			rol bh,1            ; bit 1 goes into carry,
			jc .ccw             ; carry tells us if we should turn cw/ccw
				fchs            ; Clockwise                                              ;st4
			.ccw:               ; Counterclockwise
			faddp st3           ; angle += 1; pop 1                                      ;st3
			
			shr ah,1            ; old_length /= 2
			mov al,ah           ; length = old_length
			
		jnz .drawline           ; draw next line
		
		dec bh                  ;;; next branch configuration (cw / ccw)
		fld st3                 ;;; re-load size factor
	jnz .drawbranch
;	popa                        ;;; done by caller
	ret



; Setpixel at x=cx,y=dx,color=bl. (Changes di when without push/pop)
setpixel:
	pusha
	; Compute pixel position : y*-320 + x
	; We must invert y because computer
	; graphics' y axis points downwards
	xor  dh, dh
	mov  di, dx
	imul di, -320
	add  di, cx
	
	; Set pixel
	mov byte [fs : di], bl
	popa
	ret

_1024:
dw 1024
;_512:  ;;; debug
;dw 512
_255:               ;;; TODO : do we need this one ???
dw 255
_testval:
dw 30



times 510-($-$$) db 0

map_x: ; store map_z here
map_y equ map_x + 2
map_z equ map_x + 4

dw 0xaa55

temp: ; store temp here
