Skip to content

Instantly share code, notes, and snippets.

@svnt
Created May 8, 2018 17:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save svnt/64b2d8e5eee84e9494523a18380a14d5 to your computer and use it in GitHub Desktop.
Save svnt/64b2d8e5eee84e9494523a18380a14d5 to your computer and use it in GitHub Desktop.
Water FX, tutorial and C64 ASM code
Water
---------------------------------------------------------------------------
Introduction
Water is a really nice effect, one of the better tricks around. Its also
pretty simple to code, should take no more than an afternoons work to get a
good water routine.
---------------------------------------------------------------------------
Basics
First thing you'll need is 2 buffers, for the water. This needs to be an
array of ints, same size as destination buffer. Arrange these in a
2-element array, to ease the flipping. Clear these to zero, and you're
ready to start.
---------------------------------------------------------------------------
Calculate The New Water
Calculating the new water is pretty simple. You'll need a loop that execute
from 1 to height-1, then 1 to width - 1. At each element for the water,
you'll need to sum the North, South, East, West points from the current
water, and divide by 2 Not 4, 2. Then subtract the new water for [y][x]
from this. This is your basic smooth function. Now you need to add the
'harmonic motion' to it. Take the new-water[y][x], shift it right x places
(x could be 4) and subtract it from the new-water[y][x]. Pseudo-code for
this might look like:
for y := 1 to height - 1
for x := 1 to width - 1
new-water[y][x] = ((old-water[y-1][x] +
old-water[y+1][x] +
old-water[y][x-1] +
old-water[y][x+1]) / 2) -
new-water[y][x])
new-water[y][x] -= new-water[y][x] shr x
end
end
Not too hard to make into working code. Because there are 2 pages, and we
are flipping between them, the references to what seem like uncalculated
water ( - new-water[y][x]) are ok, because they come around and feed back.
This is all it takes to calculate the water! You may also want a part where
you set the 1 pixel wide frame around the water to 0, just to be safe.
---------------------------------------------------------------------------
Paint The Water
Water is also pretty straightforward to paint. In fact some of the
techniques here, you will see later on in the bump-mapping. Back to the
task in hand. We will again need a loop for every pixel on the screen. What
we need to do is calculate a kind-of normal at each pixel. This is simply:
offsetx = water[y][x] - water[y+1][x]
offsety = water[y][x] - water[y][x+1]
Which measures changes in X and Y. 'Colour' is then calculated by 128 -
offsetx. Clip colour to the 0..255 range. Divide both offsetx and offsety
by 8, and add them to x and y. Now for the tricky bit. You'll need a
background image (or perhaps you can do without ...). You need to light the
background image by the water. This is done by:
offsetx /= 3;
offsety /= 8;
indexu = offsetx + x;
indexv = offsety + y;
MulTable[backdrop[indexv*256+indexu]*256+colour];
MulTable is another handy lookup table. It simply takes the value of
(row*col) >> 8.
Pseudo code for this would be :
For y := 1 to height - 1
For x := 1 to width - 1
offsetx = water[y][x] - water[y+1][x]
offsety = water[y][x] - water[y][x+1]
colour = 128 - offsetx
trim colour to 0..255
divide offsetx and offsety by 8
add offsetx to x giving indexu
add offsety to y giving indexv
Plot (backdrop*colour) >> 8, lookup in table
End
End
What we are effectively doing here is applying fake lighting to the water,
then mixing the colours. There are plenty of variations on calculating the
normals. Plenty of room for exploration there.
---------------------------------------------------------------------------
The Water Loop
Note it makes a difference what order you do calculations in. Its pretty
simple though. You need to:
1. Draw to the water
2. Paint it
3. Calculate new water
4. Page flip the water
If you stick to that, you can't go wrong. If you really want to be smart,
you'll use the texturemap lighting info on this page to do make logos and
so on ripple. I've even seen it combined with bump-mapping. Water is a very
rewarding effect, well worth coding.
Tom Hammersley,tomh@globalnet.co.uk
;---------------------------------------------
;------------------------------------------------
;--------------------------------------------------
; "lame 2D water"
;
;
; (c) by tecM0/Plush/+H
;
; 02.09.2003 02:18 faster version buffer=screen
; 25.07.2003 03:41 enuff for today.
; 16.04.2006 18:00 get rid of the damn copy routine
;--------------------------------------------------
!to "water.prg"
;memmap--------------------------------------------
;
;$ 0123456789abcdef
;
;0 ................
;1 c............... code
;2 ................
;3 ................
;4v1111222233334444 4 "screens" 0/4-screenbuffer
;5vffffffff........ 1 font 8/c-waterbuffer
;6v................
;7v................
;8 ................
;9 ................
;a ................
;b ................
;c ................
;d ................
;e ................
;f ................
;
; v vicbank
;
;
;0page---------------------------------------------
arg = $6a ;floating point acc2 & rnd seed
seed = $8b ;used by rnd#generator 32 bit
;not complete!
;--------------------------------------------------
*=$0800 ;basicstart
!byte $00,$0b,$08,$00,$00,$9e
!convtab pet
!text "4096"
;*=$1000
;!bin "../../../#incoming/the fourth .prg",,2
;*=$5000
;!bin "../../#/data/bumpfont4x4mul.bin",,2
*= $1000
jsr rndinit ;initalize radom number genarator
lda #$0a ;set vicbase to $4000
sta $dd00
lda #$09
sta $d020
sta $d021
sta 646
jsr $e544
lda #0
sta $d020
lda #0
sta $d021
lda #$06
sta $d022
lda #$0e
sta $d023
;lda $d011
;ora #%00000000
;sta $d011
lda #%00011000 ;set multicolormode
sta $d016
lda #%00010101 ;set font to $5000
sta $d018
;----------
ldx #0 ;clear all...
cccc
lda #$00
sta $5000,x
sta $5100,x
sta $5200,x
sta $5300,x
sta $5400,x
sta $5500,x
sta $5600,x
sta $5700,x
sta $4000,x
sta $4100,x
sta $4200,x
sta $4300,x
sta $4400,x
sta $4500,x
sta $4600,x
sta $4700,x
txa
sta $4400,x
dex
bne cccc
;----------
jsr createfont ;dither a font
;----------
lda #$ef ;wait for spacekey
cmp $dc01
bne *-3
;----------
;--------------------------------------------------eof
;.........
;...***...
;...***...
;...***...
;.*******.
;..*****..
;...***...
;....*....
;.........
;--------------------------------------------------
;----- paragraph @the bull starts here....init irq@ -----
;--------------------------------------------------
bull
lda #$00 ;clear all 4 screens/buffers
ldx #$00
clears sta $4000,x ;scr1
sta $4100,x
sta $4200,x
sta $4300,x
sta $4400,x ;scr2
sta $4500,x
sta $4600,x
sta $4700,x
sta $4800,x ;buf1
sta $4900,x
sta $4a00,x
sta $4b00,x
sta $4c00,x ;buf2
sta $4d00,x
sta $4e00,x
sta $4f00,x
dex
bne clears
;lda #$00
;jsr $1000
jsr install
jsr main
jsr deinstall
;jmp *
;jmp bull
jmp $1000
;--------------------------------------------------eof
;--------------------------------------------------
;----- paragraph @the main...outside irq_stuff@ -----
;--------------------------------------------------
!zone main
;-------
main
lda fxstate ;...time for a new fx?
beq main ;no if 0...and back...
;yes...start run if 1
;-------
lda fxtoggl ;toggle toggl (2buffering.)
eor #$01
sta fxtoggl
jsr plot4025 ;plot new 'drop'
jsr plot4025 ;plot new 'drop'
jsr water
dec fxstate ;set fxstate to 0
lda $dc01 ;space pressed?
cmp #$ef
beq end
jmp main ;no, do it again...
end rts
fxstate !byte $00 ;fx state: 01->running 00->ready
fxtoggl !byte $00 ;0/1 toggles every fx (2buffer...)
;--------------------------------------------------eof
;--------------------------------------------------
;----- paragraph @sub: calculate water loops @ -----
;--------------------------------------------------
;mx = 1(or 2,3,4)
;for y = 1 to height - 1
; for x = 1 to width - 1
; new-water[y][x] = ((old-water[y-1][x] +
; old-water[y+1][x] +
; old-water[y][x-1] +
; old-water[y][x+1]) / 2) -
; new-water[y][x])
; new-water[y][x] = new-water[y][x] - (new-water[y][x] shr mx)
; end
;end
;
;
!zone water
water
;$4800/$4c00
lda #$00 ;nn00
sta $50
lda fxtoggl ;0->48 1->4c
asl
asl
ora #%01001000
sta $51
;$4828/$4c28
lda #$28 ;nn28
sta $52
lda fxtoggl ;0->48 1->4c
asl
asl
ora #%01001000
sta $53
;$4850/$4c50
lda #$50 ;nn50
sta $54
lda fxtoggl ;0->48 1->4c
asl
asl
ora #%01001000
sta $55
;$4c28/$4828
lda #$28 ;nn28
sta $56
lda fxtoggl ;0->4c 1->48
eor #%00000001
asl
asl
ora #%01001000
sta $57
; + 50
;+ + 52 -> 56
; + 54
ldx #23 ;y-max
wlop2
ldy #39 ;x_max
wlop1
!set m=0
!do {
lda ($52),y ;get1
dey
clc
adc ($50),y ;add2
adc ($54),y ;add3
dey
adc ($52),y ;add4
lsr
iny
sec
sbc ($56),y
sta ($56),y
lda ($56),y
lsr
lsr
lsr
lsr
;lsr
;lsr
sta $ff
lda ($56),y
sec
sbc $ff
bpl *+4
lda #$00
sta ($56),y
!set m=m+1
} until m=39
lda $50
clc
adc #$28
sta $50
bcc *+4
inc $51
clc
lda $56
adc #$28
sta $56
lda $52
clc
adc #$28
sta $52
bcc *+6
inc $53
inc $57
clc
lda $54
adc #$28
sta $54
bcc *+4
inc $55
dex
beq elop
jmp wlop2
elop
rts
;--------------------------------------------------eof
;--------------------------------------------------
;----- paragraph @irqtable: irqs defined here@ -----
;--------------------------------------------------
irqtable
; low high rasterline
!byte <irq1,>irq1,$00,$00
;!byte <irq2,>irq2,$80,$00
;!byte <irq3,>irq3,$88,$00
nirq !byte 0
nirqmax = ((nirq-irqtable)/4)-1
;--------------------------------------------------
;----- paragraph @the irq routines@ -----
;--------------------------------------------------
!zone irqroutines
!subzone irq1 {
irq1
jsr nosharp
;------- fx_ready
lda fxstate
bne nofx
inc fxstate
lda fxtoggl ;prepare screenpart
eor #1
ora #%00000010
asl
asl
asl
asl
ora #%00000101 ;add (low) charpart
sta $d018 ;set new screen
nofx
;--------
jmp nextirq
lda $d020
sta oldBGcol1+1
lda fxtoggl
asl
asl
sta $d020
;sta $d021
ldx #$c2
dex
bne *-1
;jsr $1003
oldBGcol1 lda #$00
;sta $d020
sta $d020
jmp nextirq
}
;-------------------
!subzone irq2{
irq2
jsr sharp
lda #$04
sta $d020
sta $d021
ldx #$01
dex
bne *-1
lda #$00
sta $d020
sta $d021
jmp nextirq
}
;-------------------
!subzone irq3{
irq3
jsr nosharp
lda #$05
sta $d020
sta $d021
ldx #$01
dex
bne *-1
lda #$00
sta $d020
sta $d021
jmp nextirq
}
;-------------------
;--------------------------------------------------eof
;----- paragraph @sub: irq_core@ -----
;---------------------------------------
;--------
;-------- - -- -
;-------- - - - - -
;-------- - -- ---
;-------- - - - --
;--------
;---------------------------------------
;------------------------ multi_ir(a)q's
;---------------------------------------
;--------------------------------------
!zone irqcore
;------- install
install
sei lda #$00
sta nirq
lda #$35
sta $01
lda #<nmi
ldx #>nmi
sta $fffa
sta $fffc
stx $fffb
stx $fffd
lda irqtable
ldx irqtable+1
sta $fffe
stx $ffff
lda irqtable+2
sta $d012
lda $d011
and #$7f
ora irqtable+3
sta $d011
sta $dc0d ; disable 1
sta $dd0d ; disable 2
lda #$01
sta $d01a
inc $d019
cli
rts
;------- deinstall
deinstall
sei
lda #$37
sta $01
lda #$81
sta $dc0d
lda #$00
sta $d01a
lda #$1b
sta $d011
inc $d019
lda #$37
sta $01
cli
rts
;------- sharp: called from irq
sharp
inc $d012
inc $d019
sta sa+1
lda #<sharpirq
sta $fffe
lda #>sharpirq
sta $ffff
cli
nop
nop
nop
nop
nop
nop
nop
nop
nop
; <- nosharp starts here
nop ; one extra nop
jmp *-10
;--- nosharp: called instead of sharp by code that
; doesn't require timing
nosharp
sta sa+1 ; restore a
stx sx+1 ; restore x
rts
;------- sharpirq: irq generated by sharp
sharpirq
pla ; remove sharpirq
pla ; from stack
pla ;
stx sx+1
ldx #5
dex
bne *-1
nop
nop
lda $d012
cmp $d012
beq *+2
rts ; return to caller of sharp
;------- nmi: handle nmi irq's (restore button)
nmi
rti ; disable restore button
;pla ; jump back to i.e. tass
;pla
;pla
;jsr deinstall
;ldx #$ff
;txs
;jmp $9000 ;back2tass
;------- nextirq: end current irq and setup next one
nextirq
ldx nirq
txa
asl
asl
tax
lda irqtable,x
sta $fffe
lda irqtable+1,x
sta $ffff
lda irqtable+2,x
sta $d012
lda $d011
and #$7f
ora irqtable+3,x
sta $d011
ldx nirq
inx
cpx #nirqmax+1
bne *+4
ldx #0
stx nirq
inc $d019
sa lda #0 ;restore a
sx ldx #0 ;restore x
rti ;return from irq
;--------------------------------------------------eof
;--------------------------------------------------
;----- paragraph @sub:plot40x25@ -----
;--------------------------------------------------
!zone plot4026
plot4025
jsr rnd
lda seed+2
and #%00011111
adc #3
tay
lda seed+3
and #%0001111
clc
adc #3
tax
lda seed+3
and #%00011111
sta pl4025chr+1
jsr doplot4025
lda seed+3
and #%00011111
lsr
sta pl4025chr+1
inx
jsr doplot4025
dex
dex
jsr doplot4025
inx
iny
jsr doplot4025
dey
dey
jsr doplot4025
lda seed+3
and #%00011111
lsr
lsr
lsr
sta pl4025chr+1
dex
jsr doplot4025
iny
iny
jsr doplot4025
inx
inx
jsr doplot4025
dey
dey
jsr doplot4025
rts
;----------
doplot4025
lda lop4025,x
sta $fe
sta $fc
lda fxtoggl ;0->48 1->4c
asl
asl
ora #%01001000
clc
adc hip4025,x
sta $fd
sta $ff
pl4025chr lda #$00
;lda ($fe),y
;ora bmp4025,x
sta ($fc),y
sta ($fe),y
rts
hip4025 !byte $00,$00,$00,$00,$00,$00
!byte $00,$01,$01,$01,$01,$01
!byte $01,$02,$02,$02,$02,$02
!byte $02,$02,$03,$03,$03,$03
!byte $03
lop4025 !byte $00,$28,$50,$78,$a0,$c8
!byte $f0,$18,$40,$68,$90,$b8
!byte $e0,$08,$30,$58,$80,$a8
!byte $d0,$f8,$20,$48,$70,$98
!byte $c0
bmp4025
;--------------------------------------------------eof
;--------------------------------------------------
;----- Paragraph @sub: 32bitRandomizer@ -----
;--------------------------------------------------
!zone 32bit_randomize
!macro add .mactar {
lda $6a+.mactar
adc $8b+.mactar
sta $6a+.mactar
}
!macro rot .target {
lda $8b+.target
rol
sta $6a+.target
}
; initalize the seed with 'random' data
rndinit
adc $d012
sta seed+3
eor $dc04
sta seed+2
sbc $dc05
sta seed+1
eor #23
sta seed+0
rts
; random value held in seed (32 bits),
; arg used as temp.
; preserves x, y
; returns msb in a.
rnd lda seed+4
asl
sta arg+4
+rot 3
+rot 2
+rot 1
sec
rol arg+4
rol arg+3
rol arg+2
rol arg+1
clc
+add 4
pha
+add 3
pha
+add 2
+add 1
clc
lda arg+2
adc seed+4
sta seed+2
lda arg+1
adc seed+3
sta seed+1
pla
sta seed+3
pla
sta seed+4
lda seed+1 ;most signif byte
rts
;--------------------------------------------------eof
;--------------------------------------------------
;----- Paragraph @sub:create (dithered) font@ -----
;--------------------------------------------------
!zone ditherchar
createfont
lda #$08
sta $50
lda #$50
sta $51
lda #$01
sta ditlop+1
;screenscrambler ;)
;ditlop
;didilo
; jsr rnd
; lda seed+3
; and #%00000110
; tay
; lda seed+1
; sta ($50),y
;
; lda seed+2
; lsr
; asl
; adc $50
; sta $50
;
; sta $50
; bcc *+4
; inc $51
; lda $51
; cmp #$58
; bne ditlop
ditlop ldx #$00
inc ditlop+1
inc ditlop+1
inc ditlop+1
inc ditlop+1
didilo stx xpuf1+1
jsr rnd
lda seed+1
and #%00000111
tax
lda seed+3
and #%00000110 ;doublescanOff ;)
;and #%00000111
tay
lda ($50),y
ora dbmp,x
sta ($50),y
xpuf1 ldx #$00
dex
bne didilo
lda $50
clc
adc #$08
sta $50
bcc *+4
inc $51
lda $51
cmp #$51
bne ditlop
rts
dbmp !byte 1,2,4,8,16,32,64,128 ;lookup table for charDither
;---------------------------------------eof
;--------------------------------------------------
;------------------------------------------------
;---------------------------------------------
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment