########################################### # # Chipenstein 3D # # A work-in-progress experiment # in creating a raycast 2.5d shooter. # Uses PWM techniques to simulate extra # colors and must run at 1000 cycles/frame. # Epilepsy warning! # ########################################### # The largest a map for this approach could be is 16x16. # Technically we don't have to surround all sides with # walls due to wraparound, but if there is a ray path # around the map without hitting a wall our raycast # routine will get stuck in an infinite loop: : map-data 0xFF 0x01 0xFF 0x01 0xFF 0x01 0xFF 0x01 0xFF 0x01 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0xFF 0xFF 0xFF 0x01 0xFF 0x00 0x00 0x00 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0xFF 0xFF 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x01 0x01 0x00 0xFF 0x01 0x00 0x00 0x00 0xFF 0x00 0xFF 0xFF 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x01 0x01 0xFF 0x01 0xFF 0x01 0xFF 0x01 0xFF 0x01 0xFF : collision-test # take whole x/y in vc/vd, return map color in v0 i := map-data vF := 0b111 vc &= vF i += vc # + (x % 8) vd &= vF v0 <<= vd v0 <<= v0 v0 <<= v0 i += v0 # + (y % 8)*8 load v0 # the current map color ; : player-position 0 0 0 0 # scratchpad : save-position v0 := va v1 := vb v2 := vc v3 := vd i := player-position save v3 ; : restore-position i := player-position load v3 va := v0 vb := v1 vc := v2 vd := v3 ; ########################################### # # Raycasting # ########################################### # This is a table of the deltas and magnitudes for a fixed-point representation # of a cosine function. 64 entries correspond to ~5.6 degrees each. # deltas are interleaved with magnitudes- 0 for positive and 1 for negative. # deltas are normalized to slightly over half a step to make the distance calculations # come up with a maximum distance of roughly 15. Generated like so: # # for(int x = 0; x < 64; x++) { # double s = Math.cos(Math.PI*2/64*x); # System.out.format("0x%02X 0x%02X ", (int)(140 * Math.abs(s)), (s>=0) ?0 : 1); # if (x % 8 == 7) { System.out.println(); } # } : cosine-table 0x8C 0x00 0x8B 0x00 0x89 0x00 0x85 0x00 0x81 0x00 0x7B 0x00 0x74 0x00 0x6C 0x00 0x62 0x00 0x58 0x00 0x4D 0x00 0x41 0x00 0x35 0x00 0x28 0x00 0x1B 0x00 0x0D 0x00 0x00 0x00 0x0D 0x01 0x1B 0x01 0x28 0x01 0x35 0x01 0x41 0x01 0x4D 0x01 0x58 0x01 0x62 0x01 0x6C 0x01 0x74 0x01 0x7B 0x01 0x81 0x01 0x85 0x01 0x89 0x01 0x8B 0x01 0x8C 0x01 0x8B 0x01 0x89 0x01 0x85 0x01 0x81 0x01 0x7B 0x01 0x74 0x01 0x6C 0x01 0x62 0x01 0x58 0x01 0x4D 0x01 0x41 0x01 0x35 0x01 0x28 0x01 0x1B 0x01 0x0D 0x01 0x00 0x01 0x0D 0x00 0x1B 0x00 0x28 0x00 0x35 0x00 0x41 0x00 0x4D 0x00 0x58 0x00 0x62 0x00 0x6C 0x00 0x74 0x00 0x7B 0x00 0x81 0x00 0x85 0x00 0x89 0x00 0x8B 0x00 : get-cosine # takes angle in v0, # returns delta, magnitude in v0, v1 vf := 0b111111 v0 &= vf # mod 64 v0 <<= v0 i := cosine-table i += v0 load v1 ; : get-angles # takes angle in v3, # unpacks x/y delta/mag into v4-v7 # cos(a) = cosine-table[(a % 64) << 1] v0 := v3 get-cosine v4 := v0 # x delta v5 := v1 # x magnitude # sin(a) = cosine-table[(a+16 % 64) << 1] v0 := v3 v0 += 16 get-cosine v6 := v0 # y delta v7 := v1 # y magnitude ; : delta-step vf := 0 if v5 == 0 then va += v4 # positive x delta if vf == 1 then vc += 1 # carry in vf := 0 if v5 == 1 then va -= v4 # negative x delta if vf == 0 then vc += -1 # borrow out vf := 0 if v7 == 0 then vb += v6 # positive y delta if vf == 1 then vd += 1 # carry in vf := 0 if v7 == 1 then vb -= v6 # negative y delta if vf == 0 then vd += -1 # borrow out ; : heights # {height, color} 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 : raycast save-position #v0-v1 are available as scratch. v8 := 0 # loop index * 2 v3 := v9 # scan angle v3 += -8 loop get-angles v2 := 16 # height+1 loop delta-step v2 += -1 # count down height collision-test # returns result in v0 while v2 != 0 if v0 == 0 then again # save height and color in heights table: i := heights i += v8 v1 := v0 v0 := v2 save v1 vf := v3 restore-position v3 := vf v8 += 2 v3 += 1 # 16 slices * 5.625 = 90 degree FoV if v8 != 32 then again ; ########################################### # # Rendering # ########################################### # this 45-byte table allows me to construct all the necessary vertical # strips using only immediate i and a height offset: : top 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 : btm 0xF0 0xF0 0xF0 0xF0 0xF0 0xF0 0xF0 0xF0 0xF0 0xF0 0xF0 0xF0 0xF0 0xF0 0xF0 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 : sync # take advantage of the fact that vf # is always a free register if we aren't # doing any operations that can carry: loop vf := delay if vf != 0 then again vf := 1 delay := vf ; : draw-heights # registers for draw loop: # v0 is used to store the current height # v1 stores the 'color' bitmask v2 := 0 # heightmap index v3 := 1 # upward strip Y v4 := 16 # down strip Y v5 := 0 # strip X (avoid shifting v1 in loop) v6 := 15 # constant sync clear loop # fetch height of column i := heights i += v2 load v1 # apply PWM duty cycle for 'color' v1 &= ve if v1 == 0 then v0 := 0 # draw upward strip i := top i += v0 v0 =- v6 sprite v5 v3 15 # draw downward strip i := btm i += v0 sprite v5 v4 15 # draw 16 columns v2 += 2 v5 += 4 if v2 != 32 then again ; ########################################### # # Main Loop # ########################################### : gun 0x10 0x18 0x24 0x24 0x24 0x24 0x5A 0xC2 0xE7 0x7F 0x3E 0x21 0x41 0x41 : spin-left v9 += -1 raycast ; : spin-right v9 += 1 raycast ; : walk-forward v3 := v9 get-angles delta-step raycast ; : walk-backward v3 := v9 get-angles vf := 1 v5 ^= vf v7 ^= vf delta-step raycast ; : main # global state: v9 := 30 # player angle va := 128 # x position (fractional) vb := 128 # y position (fractional) vc := 5 # x position (whole) vd := 5 # y position (whole) ve := 0 # rolling frame counter raycast loop draw-heights i := gun v0 := 38 v1 := 18 sprite v0 v1 14 ve += 1 v0 := 7 if v0 key then spin-left v0 := 9 if v0 key then spin-right v0 := 5 if v0 key then walk-forward v0 := 8 if v0 key then walk-backward again