fix(jukebox): remove studio grey background, fix 3D texture orientation and cabinet rendering
This commit is contained in:
parent
82f3aa9437
commit
0a85e271e9
17 changed files with 696 additions and 342 deletions
Binary file not shown.
Binary file not shown.
BIN
bin/poker.exe
BIN
bin/poker.exe
Binary file not shown.
|
|
@ -8,7 +8,7 @@ namespace AssemblerCasinoLauncher
|
|||
{
|
||||
static class Program
|
||||
{
|
||||
const string ExpectedPokerSha256 = "B1D4D027ACE9BDF6B34C4621191E5DAB855D44E384F45F9B0D0E8EC528DF2B28";
|
||||
const string ExpectedPokerSha256 = "7488391B71FCEB1C2A2F68C06445CC1421A650793205498BA19E787C9E184297";
|
||||
|
||||
[STAThread]
|
||||
static void Main()
|
||||
|
|
|
|||
Binary file not shown.
BIN
dist/Assembler Casino.exe
vendored
BIN
dist/Assembler Casino.exe
vendored
Binary file not shown.
BIN
docs/previews_minigames/jukebox_3d_angle.png
Normal file
BIN
docs/previews_minigames/jukebox_3d_angle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
BIN
docs/previews_minigames/jukebox_3d_close.png
Normal file
BIN
docs/previews_minigames/jukebox_3d_close.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 198 KiB |
BIN
docs/previews_minigames/jukebox_3d_front.png
Normal file
BIN
docs/previews_minigames/jukebox_3d_front.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 241 KiB |
BIN
docs/previews_minigames/jukebox_3d_side.png
Normal file
BIN
docs/previews_minigames/jukebox_3d_side.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
BIN
poker.exe
BIN
poker.exe
Binary file not shown.
|
|
@ -7,7 +7,7 @@ ResolveCitySurface3D PROC material:DWORD
|
|||
mov eax,material
|
||||
cmp eax,161
|
||||
jb @F
|
||||
cmp eax,162
|
||||
cmp eax,163
|
||||
ja @F
|
||||
invoke ResolveJackboxSurface3D,material
|
||||
ret
|
||||
|
|
|
|||
|
|
@ -1,323 +1,335 @@
|
|||
; ==============================================================================
|
||||
; JACKBOX / JUKEBOX IMPLEMENTATION - MASM32 / CP1251
|
||||
; ==============================================================================
|
||||
|
||||
.code
|
||||
|
||||
LoadJackboxArt3D PROC
|
||||
invoke LoadAnimPack3D, OFFSET jackboxWorldArt3D, OFFSET jackboxWorldPath3D
|
||||
invoke ValidateCityPack3D, OFFSET jackboxWorldArt3D, 256, 512, 2
|
||||
invoke LoadAnimPack3D, OFFSET jackboxScreenArt3D, OFFSET jackboxScreenPath3D
|
||||
invoke ValidateCityPack3D, OFFSET jackboxScreenArt3D, 640, 480, 1
|
||||
ret
|
||||
LoadJackboxArt3D ENDP
|
||||
|
||||
FreeJackboxArt3D PROC
|
||||
invoke FreeAnimPack3D, OFFSET jackboxWorldArt3D
|
||||
invoke FreeAnimPack3D, OFFSET jackboxScreenArt3D
|
||||
ret
|
||||
FreeJackboxArt3D ENDP
|
||||
|
||||
ResolveJackboxSurface3D PROC material:DWORD
|
||||
cmp jackboxWorldArt3D.pixels, 0
|
||||
je jackbox_surface_missing
|
||||
mov eax, material
|
||||
cmp eax, MATERIAL_JACKBOX_FRONT
|
||||
je @F
|
||||
cmp eax, MATERIAL_JACKBOX_SIDE
|
||||
jne jackbox_surface_missing
|
||||
; Frame 1: side (offset 256*512*4 = 524288)
|
||||
mov eax, jackboxWorldArt3D.pixels
|
||||
add eax, 524288
|
||||
mov ecx, 256
|
||||
mov edx, 512
|
||||
ret
|
||||
@@:
|
||||
; Frame 0: front
|
||||
mov eax, jackboxWorldArt3D.pixels
|
||||
mov ecx, 256
|
||||
mov edx, 512
|
||||
ret
|
||||
jackbox_surface_missing:
|
||||
xor eax, eax
|
||||
ret
|
||||
ResolveJackboxSurface3D ENDP
|
||||
|
||||
DrawJackboxCabinet3D PROC uses ebx esi edi wx:DWORD, wy:DWORD
|
||||
local x0:DWORD, x1:DWORD, y0:DWORD, y1:DWORD
|
||||
; Footprint: width 0.7m, depth 0.45m, height 1.85m
|
||||
mov eax, wx
|
||||
sub eax, FP_1*7/20
|
||||
mov x0, eax
|
||||
add eax, FP_1*7/10
|
||||
mov x1, eax
|
||||
mov eax, wy
|
||||
sub eax, FP_1*9/40
|
||||
mov y0, eax
|
||||
add eax, FP_1*9/20
|
||||
mov y1, eax
|
||||
invoke DrawCityBoxSides3D, x0, x1, y0, y1, 0, FP_1*37/20, MATERIAL_JACKBOX_SIDE
|
||||
invoke DrawCityHorizontal3D, x0, x1, y0, y1, FP_1*37/20, 110
|
||||
mov eax, y1
|
||||
add eax, FP_1/500
|
||||
invoke DrawCityVertical3D, 1, eax, x0, x1, 0, FP_1*37/20, MATERIAL_JACKBOX_FRONT
|
||||
ret
|
||||
DrawJackboxCabinet3D ENDP
|
||||
|
||||
RenderJackboxCabinet3D PROC
|
||||
invoke DrawJackboxCabinet3D, JACKBOX_X, JACKBOX_Y
|
||||
ret
|
||||
RenderJackboxCabinet3D ENDP
|
||||
|
||||
JackboxCanWalk3D PROC px:DWORD, py:DWORD
|
||||
mov eax, py
|
||||
sub eax, JACKBOX_Y
|
||||
test eax, eax
|
||||
jns @F
|
||||
neg eax
|
||||
@@:
|
||||
cmp eax, FP_1*7/20
|
||||
ja jackbox_walk_clear
|
||||
mov eax, px
|
||||
sub eax, JACKBOX_X
|
||||
test eax, eax
|
||||
jns @F
|
||||
neg eax
|
||||
@@:
|
||||
cmp eax, FP_HALF
|
||||
jbe jackbox_walk_blocked
|
||||
jackbox_walk_clear:
|
||||
mov eax, 1
|
||||
ret
|
||||
jackbox_walk_blocked:
|
||||
xor eax, eax
|
||||
ret
|
||||
JackboxCanWalk3D ENDP
|
||||
|
||||
PlayJukeboxTrack3D PROC uses ebx trackIndex:DWORD
|
||||
invoke mciSendStringA, OFFSET jboxStopCmd, 0, 0, 0
|
||||
invoke mciSendStringA, OFFSET jboxCloseCmd, 0, 0, 0
|
||||
mov jboxIsPlaying, 0
|
||||
|
||||
mov ebx, trackIndex
|
||||
cmp ebx, 9
|
||||
jae jbox_play_done
|
||||
cmp ebx, JACKBOX_TRACK_COUNT
|
||||
jae jbox_play_done
|
||||
|
||||
mov eax, jboxOpenCommands[ebx*4]
|
||||
test eax, eax
|
||||
jz jbox_play_done
|
||||
invoke mciSendStringA, eax, 0, 0, 0
|
||||
invoke mciSendStringA, OFFSET jboxPlayCmd, 0, 0, 0
|
||||
mov jboxCurrentTrack, ebx
|
||||
mov jboxIsPlaying, 1
|
||||
jbox_play_done:
|
||||
ret
|
||||
PlayJukeboxTrack3D ENDP
|
||||
|
||||
StopJukeboxTrack3D PROC
|
||||
invoke mciSendStringA, OFFSET jboxStopCmd, 0, 0, 0
|
||||
invoke mciSendStringA, OFFSET jboxCloseCmd, 0, 0, 0
|
||||
mov jboxIsPlaying, 0
|
||||
mov jboxCurrentTrack, -1
|
||||
ret
|
||||
StopJukeboxTrack3D ENDP
|
||||
|
||||
BeginJackbox3D PROC
|
||||
mov engineMode, MODE_JACKBOX
|
||||
call ClearKeys3D
|
||||
mov mouseClick3D, 0
|
||||
ret
|
||||
BeginJackbox3D ENDP
|
||||
|
||||
HandleJackbox3D PROC uses ebx esi
|
||||
; 1. Mouse interactions
|
||||
cmp mouseClick3D, 0
|
||||
je jbox_keys
|
||||
|
||||
; Check Exit button in top right: x in [228..312], y in [8..26]
|
||||
cmp mouseY3D, 8
|
||||
jb @F
|
||||
cmp mouseY3D, 26
|
||||
ja @F
|
||||
cmp mouseX3D, 228
|
||||
jae jbox_exit
|
||||
@@:
|
||||
; Check track row click: y in [54..186]
|
||||
cmp mouseY3D, 54
|
||||
jb jbox_keys
|
||||
cmp mouseY3D, 186
|
||||
ja jbox_keys
|
||||
cmp mouseX3D, 16
|
||||
jb jbox_keys
|
||||
cmp mouseX3D, 304
|
||||
ja jbox_keys
|
||||
mov eax, mouseY3D
|
||||
sub eax, 54
|
||||
xor edx, edx
|
||||
mov ebx, 13
|
||||
div ebx
|
||||
cmp eax, JACKBOX_TRACK_COUNT
|
||||
jae jbox_keys
|
||||
mov jboxSelectedTrack, eax
|
||||
invoke PlayJukeboxTrack3D, eax
|
||||
jmp jbox_handled
|
||||
|
||||
jbox_keys:
|
||||
; Exit keys: ESC (1Bh) or 'E' (45h)
|
||||
invoke Pressed3D, 1Bh
|
||||
test eax, eax
|
||||
jnz jbox_exit
|
||||
invoke Pressed3D, 45h
|
||||
test eax, eax
|
||||
jnz jbox_exit
|
||||
|
||||
; Navigation: UP (26h) / DOWN (28h)
|
||||
invoke Pressed3D, 26h
|
||||
test eax, eax
|
||||
jz @F
|
||||
dec jboxSelectedTrack
|
||||
jns jbox_handled
|
||||
mov jboxSelectedTrack, JACKBOX_TRACK_COUNT - 1
|
||||
jmp jbox_handled
|
||||
@@:
|
||||
invoke Pressed3D, 28h
|
||||
test eax, eax
|
||||
jz @F
|
||||
inc jboxSelectedTrack
|
||||
cmp jboxSelectedTrack, JACKBOX_TRACK_COUNT
|
||||
jb jbox_handled
|
||||
mov jboxSelectedTrack, 0
|
||||
jmp jbox_handled
|
||||
@@:
|
||||
; Play selection: ENTER (0Dh) or SPACE (20h)
|
||||
invoke Pressed3D, 0Dh
|
||||
test eax, eax
|
||||
jnz jbox_play_selected
|
||||
invoke Pressed3D, 20h
|
||||
test eax, eax
|
||||
jnz jbox_play_selected
|
||||
|
||||
; Stop key: 'S' (53h)
|
||||
invoke Pressed3D, 53h
|
||||
test eax, eax
|
||||
jz @F
|
||||
call StopJukeboxTrack3D
|
||||
jmp jbox_handled
|
||||
@@:
|
||||
; Random key: 'R' (52h)
|
||||
invoke Pressed3D, 52h
|
||||
test eax, eax
|
||||
jz @F
|
||||
invoke RandomRange3D, 9
|
||||
mov jboxSelectedTrack, eax
|
||||
invoke PlayJukeboxTrack3D, eax
|
||||
jmp jbox_handled
|
||||
@@:
|
||||
; Direct digit keys '1'..'9', '0'
|
||||
mov ebx, 31h ; '1'
|
||||
jbox_digit_loop:
|
||||
invoke Pressed3D, ebx
|
||||
test eax, eax
|
||||
jz jbox_digit_next
|
||||
mov eax, ebx
|
||||
sub eax, 31h
|
||||
mov jboxSelectedTrack, eax
|
||||
invoke PlayJukeboxTrack3D, eax
|
||||
jmp jbox_handled
|
||||
jbox_digit_next:
|
||||
inc ebx
|
||||
cmp ebx, 39h ; '9'
|
||||
jbe jbox_digit_loop
|
||||
|
||||
; '0' key for mute/stop
|
||||
invoke Pressed3D, 30h
|
||||
test eax, eax
|
||||
jz jbox_handled
|
||||
mov jboxSelectedTrack, 9
|
||||
invoke PlayJukeboxTrack3D, 9
|
||||
jmp jbox_handled
|
||||
|
||||
jbox_play_selected:
|
||||
invoke PlayJukeboxTrack3D, jboxSelectedTrack
|
||||
jmp jbox_handled
|
||||
|
||||
jbox_exit:
|
||||
mov engineMode, MODE_WALK
|
||||
call ClearKeys3D
|
||||
|
||||
jbox_handled:
|
||||
mov mouseClick3D, 0
|
||||
ret
|
||||
HandleJackbox3D ENDP
|
||||
|
||||
RenderJackbox3D PROC uses ebx esi edi
|
||||
local rowY:DWORD, color:DWORD
|
||||
|
||||
; 1. Draw base background
|
||||
invoke DrawBox, 0, 0, 320, 240, 000B1815h
|
||||
|
||||
; 2. Overlay screen artwork if available
|
||||
cmp jackboxScreenArt3D.pixels, 0
|
||||
je @F
|
||||
invoke DrawAnimSprite3D, OFFSET jackboxScreenArt3D, 0, 0, 0, 640, 480
|
||||
@@:
|
||||
; 3. Title & Exit Button
|
||||
invoke DrawString8x8, 52, 12, OFFSET jboxTitle, 00E7C479h
|
||||
invoke DrawBoxBorder, 228, 8, 84, 16, 001A2522h, 00C8AD75h, 1
|
||||
invoke DrawString8x8, 234, 12, OFFSET jboxClose, 00F2E6C6h
|
||||
|
||||
; 4. Now Playing Status Banner
|
||||
cmp jboxIsPlaying, 1
|
||||
jne jbox_render_stopped
|
||||
cmp jboxCurrentTrack, -1
|
||||
je jbox_render_stopped
|
||||
cmp jboxCurrentTrack, 9
|
||||
jae jbox_render_stopped
|
||||
|
||||
invoke DrawString8x8, 20, 34, OFFSET jboxStatusPlaying, 008CD8AFh
|
||||
mov eax, jboxCurrentTrack
|
||||
mov edx, jboxTrackNames[eax*4]
|
||||
invoke DrawString8x8, 140, 34, edx, 00F0E6C3h
|
||||
jmp jbox_render_divider
|
||||
|
||||
jbox_render_stopped:
|
||||
invoke DrawString8x8, 20, 34, OFFSET jboxStatusStopped, 0090B19Fh
|
||||
|
||||
jbox_render_divider:
|
||||
invoke DrawBox, 20, 47, 280, 1, 00495B4Ah
|
||||
|
||||
; 5. Track Selection List (10 rows, step Y = 13)
|
||||
mov rowY, 55
|
||||
xor esi, esi
|
||||
jbox_render_row:
|
||||
mov color, 0090B19Fh ; Default muted
|
||||
cmp esi, jboxSelectedTrack
|
||||
jne @F
|
||||
mov color, 00F2E6C6h ; Bright cream for selection
|
||||
invoke DrawString8x8, 14, rowY, OFFSET jboxCursor, 00E7C479h
|
||||
@@:
|
||||
mov edx, jboxTrackNames[esi*4]
|
||||
invoke DrawString8x8, 28, rowY, edx, color
|
||||
|
||||
; Mark playing track
|
||||
cmp esi, jboxCurrentTrack
|
||||
jne jbox_row_next
|
||||
cmp jboxIsPlaying, 1
|
||||
jne jbox_row_next
|
||||
invoke DrawString8x8, 252, rowY, OFFSET jboxPlayingTag, 008CD8AFh
|
||||
|
||||
jbox_row_next:
|
||||
add rowY, 13
|
||||
inc esi
|
||||
cmp esi, JACKBOX_TRACK_COUNT
|
||||
jb jbox_render_row
|
||||
|
||||
; 6. Footer Controls Help
|
||||
invoke DrawBox, 20, 192, 280, 1, 00314538h
|
||||
invoke DrawString8x8, 20, 200, OFFSET jboxHelp1, 00C1A982h
|
||||
invoke DrawString8x8, 20, 214, OFFSET jboxHelp2, 0090B19Fh
|
||||
|
||||
ret
|
||||
RenderJackbox3D ENDP
|
||||
; ==============================================================================
|
||||
; JACKBOX / JUKEBOX IMPLEMENTATION - MASM32 / CP1251
|
||||
; ==============================================================================
|
||||
|
||||
.code
|
||||
|
||||
LoadJackboxArt3D PROC
|
||||
invoke LoadAnimPack3D, OFFSET jackboxWorldArt3D, OFFSET jackboxWorldPath3D
|
||||
invoke ValidateCityPack3D, OFFSET jackboxWorldArt3D, 256, 512, 3
|
||||
invoke LoadAnimPack3D, OFFSET jackboxScreenArt3D, OFFSET jackboxScreenPath3D
|
||||
invoke ValidateCityPack3D, OFFSET jackboxScreenArt3D, 640, 480, 1
|
||||
ret
|
||||
LoadJackboxArt3D ENDP
|
||||
|
||||
FreeJackboxArt3D PROC
|
||||
invoke FreeAnimPack3D, OFFSET jackboxWorldArt3D
|
||||
invoke FreeAnimPack3D, OFFSET jackboxScreenArt3D
|
||||
ret
|
||||
FreeJackboxArt3D ENDP
|
||||
|
||||
ResolveJackboxSurface3D PROC material:DWORD
|
||||
cmp jackboxWorldArt3D.pixels, 0
|
||||
je jackbox_surface_missing
|
||||
mov eax, material
|
||||
cmp eax, MATERIAL_JACKBOX_FRONT
|
||||
je jackbox_surface_front
|
||||
cmp eax, MATERIAL_JACKBOX_SIDE
|
||||
je jackbox_surface_side
|
||||
cmp eax, MATERIAL_JACKBOX_BACK
|
||||
jne jackbox_surface_missing
|
||||
; Frame 2: rear panel (offset 256*512*4*2 = 1048576)
|
||||
mov eax, jackboxWorldArt3D.pixels
|
||||
add eax, 1048576
|
||||
mov ecx, 256
|
||||
mov edx, 512
|
||||
ret
|
||||
jackbox_surface_side:
|
||||
; Frame 1: side profile (offset 256*512*4 = 524288)
|
||||
mov eax, jackboxWorldArt3D.pixels
|
||||
add eax, 524288
|
||||
mov ecx, 256
|
||||
mov edx, 512
|
||||
ret
|
||||
jackbox_surface_front:
|
||||
; Frame 0: front face
|
||||
mov eax, jackboxWorldArt3D.pixels
|
||||
mov ecx, 256
|
||||
mov edx, 512
|
||||
ret
|
||||
jackbox_surface_missing:
|
||||
xor eax, eax
|
||||
ret
|
||||
ResolveJackboxSurface3D ENDP
|
||||
|
||||
DrawJackboxCabinet3D PROC uses ebx esi edi wx:DWORD, wy:DWORD
|
||||
local x0:DWORD, x1:DWORD, y0:DWORD, y1:DWORD
|
||||
; Footprint: width 0.75m, depth 0.55m, height 1.85m
|
||||
mov eax, wx
|
||||
sub eax, FP_1*15/40
|
||||
mov x0, eax
|
||||
add eax, FP_1*15/20
|
||||
mov x1, eax
|
||||
mov eax, wy
|
||||
sub eax, FP_1*11/40
|
||||
mov y0, eax
|
||||
add eax, FP_1*11/20
|
||||
mov y1, eax
|
||||
|
||||
; Rear: North face (Y = y0)
|
||||
invoke DrawCityVertical3D, 1, y0, x0, x1, 0, FP_1*37/20, MATERIAL_JACKBOX_BACK
|
||||
; Sides: West face (X = x0) and East face (X = x1)
|
||||
invoke DrawCityVertical3D, 0, x0, y0, y1, 0, FP_1*37/20, MATERIAL_JACKBOX_SIDE
|
||||
invoke DrawCityVertical3D, 0, x1, y0, y1, 0, FP_1*37/20, MATERIAL_JACKBOX_SIDE
|
||||
; Front: South face (Y = y1)
|
||||
invoke DrawCityVertical3D, 1, y1, x0, x1, 0, FP_1*37/20, MATERIAL_JACKBOX_FRONT
|
||||
ret
|
||||
DrawJackboxCabinet3D ENDP
|
||||
|
||||
RenderJackboxCabinet3D PROC
|
||||
invoke DrawJackboxCabinet3D, JACKBOX_X, JACKBOX_Y
|
||||
ret
|
||||
RenderJackboxCabinet3D ENDP
|
||||
|
||||
JackboxCanWalk3D PROC px:DWORD, py:DWORD
|
||||
mov eax, py
|
||||
sub eax, JACKBOX_Y
|
||||
test eax, eax
|
||||
jns @F
|
||||
neg eax
|
||||
@@:
|
||||
cmp eax, FP_1*11/20
|
||||
ja jackbox_walk_clear
|
||||
mov eax, px
|
||||
sub eax, JACKBOX_X
|
||||
test eax, eax
|
||||
jns @F
|
||||
neg eax
|
||||
@@:
|
||||
cmp eax, FP_1*15/20
|
||||
jbe jackbox_walk_blocked
|
||||
jackbox_walk_clear:
|
||||
mov eax, 1
|
||||
ret
|
||||
jackbox_walk_blocked:
|
||||
xor eax, eax
|
||||
ret
|
||||
JackboxCanWalk3D ENDP
|
||||
|
||||
PlayJukeboxTrack3D PROC uses ebx trackIndex:DWORD
|
||||
invoke mciSendStringA, OFFSET jboxStopCmd, 0, 0, 0
|
||||
invoke mciSendStringA, OFFSET jboxCloseCmd, 0, 0, 0
|
||||
mov jboxIsPlaying, 0
|
||||
|
||||
mov ebx, trackIndex
|
||||
cmp ebx, 9
|
||||
jae jbox_play_done
|
||||
cmp ebx, JACKBOX_TRACK_COUNT
|
||||
jae jbox_play_done
|
||||
|
||||
mov eax, jboxOpenCommands[ebx*4]
|
||||
test eax, eax
|
||||
jz jbox_play_done
|
||||
invoke mciSendStringA, eax, 0, 0, 0
|
||||
invoke mciSendStringA, OFFSET jboxPlayCmd, 0, 0, 0
|
||||
mov jboxCurrentTrack, ebx
|
||||
mov jboxIsPlaying, 1
|
||||
jbox_play_done:
|
||||
ret
|
||||
PlayJukeboxTrack3D ENDP
|
||||
|
||||
StopJukeboxTrack3D PROC
|
||||
invoke mciSendStringA, OFFSET jboxStopCmd, 0, 0, 0
|
||||
invoke mciSendStringA, OFFSET jboxCloseCmd, 0, 0, 0
|
||||
mov jboxIsPlaying, 0
|
||||
mov jboxCurrentTrack, -1
|
||||
ret
|
||||
StopJukeboxTrack3D ENDP
|
||||
|
||||
BeginJackbox3D PROC
|
||||
mov engineMode, MODE_JACKBOX
|
||||
call ClearKeys3D
|
||||
mov mouseClick3D, 0
|
||||
ret
|
||||
BeginJackbox3D ENDP
|
||||
|
||||
HandleJackbox3D PROC uses ebx esi
|
||||
; 1. Mouse interactions
|
||||
cmp mouseClick3D, 0
|
||||
je jbox_keys
|
||||
|
||||
; Check Exit button in top right: x in [228..312], y in [8..26]
|
||||
cmp mouseY3D, 8
|
||||
jb @F
|
||||
cmp mouseY3D, 26
|
||||
ja @F
|
||||
cmp mouseX3D, 228
|
||||
jae jbox_exit
|
||||
@@:
|
||||
; Check track row click: y in [54..186]
|
||||
cmp mouseY3D, 54
|
||||
jb jbox_keys
|
||||
cmp mouseY3D, 186
|
||||
ja jbox_keys
|
||||
cmp mouseX3D, 16
|
||||
jb jbox_keys
|
||||
cmp mouseX3D, 304
|
||||
ja jbox_keys
|
||||
mov eax, mouseY3D
|
||||
sub eax, 54
|
||||
xor edx, edx
|
||||
mov ebx, 13
|
||||
div ebx
|
||||
cmp eax, JACKBOX_TRACK_COUNT
|
||||
jae jbox_keys
|
||||
mov jboxSelectedTrack, eax
|
||||
invoke PlayJukeboxTrack3D, eax
|
||||
jmp jbox_handled
|
||||
|
||||
jbox_keys:
|
||||
; Exit keys: ESC (1Bh) or 'E' (45h)
|
||||
invoke Pressed3D, 1Bh
|
||||
test eax, eax
|
||||
jnz jbox_exit
|
||||
invoke Pressed3D, 45h
|
||||
test eax, eax
|
||||
jnz jbox_exit
|
||||
|
||||
; Navigation: UP (26h) / DOWN (28h)
|
||||
invoke Pressed3D, 26h
|
||||
test eax, eax
|
||||
jz @F
|
||||
dec jboxSelectedTrack
|
||||
jns jbox_handled
|
||||
mov jboxSelectedTrack, JACKBOX_TRACK_COUNT - 1
|
||||
jmp jbox_handled
|
||||
@@:
|
||||
invoke Pressed3D, 28h
|
||||
test eax, eax
|
||||
jz @F
|
||||
inc jboxSelectedTrack
|
||||
cmp jboxSelectedTrack, JACKBOX_TRACK_COUNT
|
||||
jb jbox_handled
|
||||
mov jboxSelectedTrack, 0
|
||||
jmp jbox_handled
|
||||
@@:
|
||||
; Play selection: ENTER (0Dh) or SPACE (20h)
|
||||
invoke Pressed3D, 0Dh
|
||||
test eax, eax
|
||||
jnz jbox_play_selected
|
||||
invoke Pressed3D, 20h
|
||||
test eax, eax
|
||||
jnz jbox_play_selected
|
||||
|
||||
; Stop key: 'S' (53h)
|
||||
invoke Pressed3D, 53h
|
||||
test eax, eax
|
||||
jz @F
|
||||
call StopJukeboxTrack3D
|
||||
jmp jbox_handled
|
||||
@@:
|
||||
; Random key: 'R' (52h)
|
||||
invoke Pressed3D, 52h
|
||||
test eax, eax
|
||||
jz @F
|
||||
invoke RandomRange3D, 9
|
||||
mov jboxSelectedTrack, eax
|
||||
invoke PlayJukeboxTrack3D, eax
|
||||
jmp jbox_handled
|
||||
@@:
|
||||
; Direct digit keys '1'..'9', '0'
|
||||
mov ebx, 31h ; '1'
|
||||
jbox_digit_loop:
|
||||
invoke Pressed3D, ebx
|
||||
test eax, eax
|
||||
jz jbox_digit_next
|
||||
mov eax, ebx
|
||||
sub eax, 31h
|
||||
mov jboxSelectedTrack, eax
|
||||
invoke PlayJukeboxTrack3D, eax
|
||||
jmp jbox_handled
|
||||
jbox_digit_next:
|
||||
inc ebx
|
||||
cmp ebx, 39h ; '9'
|
||||
jbe jbox_digit_loop
|
||||
|
||||
; '0' key for mute/stop
|
||||
invoke Pressed3D, 30h
|
||||
test eax, eax
|
||||
jz jbox_handled
|
||||
mov jboxSelectedTrack, 9
|
||||
invoke PlayJukeboxTrack3D, 9
|
||||
jmp jbox_handled
|
||||
|
||||
jbox_play_selected:
|
||||
invoke PlayJukeboxTrack3D, jboxSelectedTrack
|
||||
jmp jbox_handled
|
||||
|
||||
jbox_exit:
|
||||
mov engineMode, MODE_WALK
|
||||
call ClearKeys3D
|
||||
|
||||
jbox_handled:
|
||||
mov mouseClick3D, 0
|
||||
ret
|
||||
HandleJackbox3D ENDP
|
||||
|
||||
RenderJackbox3D PROC uses ebx esi edi
|
||||
local rowY:DWORD, color:DWORD
|
||||
|
||||
; 1. Draw base background
|
||||
invoke DrawBox, 0, 0, 320, 240, 000B1815h
|
||||
|
||||
; 2. Overlay screen artwork if available
|
||||
cmp jackboxScreenArt3D.pixels, 0
|
||||
je @F
|
||||
invoke DrawAnimSprite3D, OFFSET jackboxScreenArt3D, 0, 0, 0, 640, 480
|
||||
@@:
|
||||
; 3. Title & Exit Button
|
||||
invoke DrawString8x8, 52, 12, OFFSET jboxTitle, 00E7C479h
|
||||
invoke DrawBoxBorder, 228, 8, 84, 16, 001A2522h, 00C8AD75h, 1
|
||||
invoke DrawString8x8, 234, 12, OFFSET jboxClose, 00F2E6C6h
|
||||
|
||||
; 4. Now Playing Status Banner
|
||||
cmp jboxIsPlaying, 1
|
||||
jne jbox_render_stopped
|
||||
cmp jboxCurrentTrack, -1
|
||||
je jbox_render_stopped
|
||||
cmp jboxCurrentTrack, 9
|
||||
jae jbox_render_stopped
|
||||
|
||||
invoke DrawString8x8, 20, 34, OFFSET jboxStatusPlaying, 008CD8AFh
|
||||
mov eax, jboxCurrentTrack
|
||||
mov edx, jboxTrackNames[eax*4]
|
||||
invoke DrawString8x8, 140, 34, edx, 00F0E6C3h
|
||||
jmp jbox_render_divider
|
||||
|
||||
jbox_render_stopped:
|
||||
invoke DrawString8x8, 20, 34, OFFSET jboxStatusStopped, 0090B19Fh
|
||||
|
||||
jbox_render_divider:
|
||||
invoke DrawBox, 20, 47, 280, 1, 00495B4Ah
|
||||
|
||||
; 5. Track Selection List (10 rows, step Y = 13)
|
||||
mov rowY, 55
|
||||
xor esi, esi
|
||||
jbox_render_row:
|
||||
mov color, 0090B19Fh ; Default muted
|
||||
cmp esi, jboxSelectedTrack
|
||||
jne @F
|
||||
mov color, 00F2E6C6h ; Bright cream for selection
|
||||
invoke DrawString8x8, 14, rowY, OFFSET jboxCursor, 00E7C479h
|
||||
@@:
|
||||
mov edx, jboxTrackNames[esi*4]
|
||||
invoke DrawString8x8, 28, rowY, edx, color
|
||||
|
||||
; Mark playing track
|
||||
cmp esi, jboxCurrentTrack
|
||||
jne jbox_row_next
|
||||
cmp jboxIsPlaying, 1
|
||||
jne jbox_row_next
|
||||
invoke DrawString8x8, 252, rowY, OFFSET jboxPlayingTag, 008CD8AFh
|
||||
|
||||
jbox_row_next:
|
||||
add rowY, 13
|
||||
inc esi
|
||||
cmp esi, JACKBOX_TRACK_COUNT
|
||||
jb jbox_render_row
|
||||
|
||||
; 6. Footer Controls Help
|
||||
invoke DrawBox, 20, 192, 280, 1, 00314538h
|
||||
invoke DrawString8x8, 20, 200, OFFSET jboxHelp1, 00C1A982h
|
||||
invoke DrawString8x8, 20, 214, OFFSET jboxHelp2, 0090B19Fh
|
||||
|
||||
ret
|
||||
RenderJackbox3D ENDP
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ JACKBOX_Y equ 20*FP_1
|
|||
JACKBOX_TRACK_COUNT equ 10
|
||||
|
||||
MATERIAL_JACKBOX_FRONT equ 161
|
||||
MATERIAL_JACKBOX_SIDE equ 162
|
||||
MATERIAL_JACKBOX_SIDE equ 162
|
||||
MATERIAL_JACKBOX_BACK equ 163
|
||||
|
||||
LoadJackboxArt3D PROTO
|
||||
FreeJackboxArt3D PROTO
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
"""Offline packaging of Jackbox / Jukebox art for the MASM32 assembly renderer."""
|
||||
"""Offline packaging of Jackbox / Jukebox art for the MASM32 assembly renderer.
|
||||
|
||||
Processes high-resolution source images, removes studio grey background with clean
|
||||
alpha masking and anti-aliasing, aligns 3D cabinet surfaces (Front, Side, Back),
|
||||
and outputs binary packs (ANM1).
|
||||
"""
|
||||
import math
|
||||
from pathlib import Path
|
||||
import struct
|
||||
import cv2
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
|
|
@ -8,11 +16,56 @@ SOURCE = ROOT / "assets/sprites/jackbox"
|
|||
OUT = ROOT / "assets/animations/hall"
|
||||
|
||||
|
||||
def pack(path: Path, frames: list):
|
||||
w, h = frames[0].size
|
||||
raw = b"".join(image.tobytes("raw", "BGRA") for image in frames)
|
||||
def clean_cutout(img_bgr: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
|
||||
"""Segment studio grey background using connected components from image borders."""
|
||||
h, w = img_bgr.shape[:2]
|
||||
arr = img_bgr.astype(np.float32)
|
||||
|
||||
# Detect neutral studio grey pixels: low saturation and mid-low brightness
|
||||
color_diff = np.maximum(
|
||||
np.abs(arr[:, :, 0] - arr[:, :, 1]),
|
||||
np.maximum(np.abs(arr[:, :, 1] - arr[:, :, 2]), np.abs(arr[:, :, 0] - arr[:, :, 2]))
|
||||
)
|
||||
brightness = np.mean(arr, axis=2)
|
||||
is_bg_cand = (color_diff < 10) & (brightness >= 18) & (brightness <= 65)
|
||||
|
||||
# Seed from all 4 borders
|
||||
seed_mask = np.zeros((h, w), dtype=np.uint8)
|
||||
seed_mask[0, :] = is_bg_cand[0, :]
|
||||
seed_mask[-1, :] = is_bg_cand[-1, :]
|
||||
seed_mask[:, 0] = is_bg_cand[:, 0]
|
||||
seed_mask[:, -1] = is_bg_cand[:, -1]
|
||||
|
||||
num_labels, labels = cv2.connectedComponents(is_bg_cand.astype(np.uint8))
|
||||
border_labels = np.unique(labels[seed_mask == 1])
|
||||
border_labels = border_labels[border_labels != 0]
|
||||
|
||||
is_bg = np.isin(labels, border_labels)
|
||||
clean_fg = (~is_bg).astype(np.uint8)
|
||||
|
||||
# Fill internal holes in foreground if any
|
||||
num_fg, fg_labels = cv2.connectedComponents(clean_fg)
|
||||
fg_sizes = np.bincount(fg_labels.flatten())
|
||||
fg_sizes[0] = 0
|
||||
main_fg_label = np.argmax(fg_sizes)
|
||||
clean_fg = (fg_labels == main_fg_label).astype(np.uint8)
|
||||
|
||||
# Anti-aliased alpha falloff via distance transform
|
||||
dist_in = cv2.distanceTransform(clean_fg, cv2.DIST_L2, 3)
|
||||
dist_out = cv2.distanceTransform(1 - clean_fg, cv2.DIST_L2, 3)
|
||||
alpha = np.clip((dist_in - dist_out + 0.5) * 255.0, 0, 255).astype(np.uint8)
|
||||
|
||||
bgra = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2BGRA)
|
||||
bgra[:, :, 3] = alpha
|
||||
return bgra, clean_fg
|
||||
|
||||
|
||||
def pack_bgra(path: Path, frames: list[np.ndarray], w: int, h: int):
|
||||
"""Pack an array of (H, W, 4) BGRA numpy uint8 images into an ANM1 binary pack."""
|
||||
raw = b"".join(f.tobytes() for f in frames)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_bytes(struct.pack("<4s4I", b"ANM1", w, h, len(frames), len(raw)) + raw)
|
||||
header = struct.pack("<4s4I", b"ANM1", w, h, len(frames), len(raw))
|
||||
path.write_bytes(header + raw)
|
||||
print(f"Packed {len(frames)} frame(s) ({w}x{h}) into {path.name}: {len(raw)} bytes")
|
||||
|
||||
|
||||
|
|
@ -25,16 +78,42 @@ def main():
|
|||
assert side_path.exists(), f"Missing {side_path}"
|
||||
assert ui_path.exists(), f"Missing {ui_path}"
|
||||
|
||||
# 1. World Cabinet Pack: 2 frames (256x512 BGRA)
|
||||
front_img = Image.open(front_path).convert("RGBA").resize((256, 512), Image.Resampling.LANCZOS)
|
||||
side_img = Image.open(side_path).convert("RGBA").resize((256, 512), Image.Resampling.LANCZOS)
|
||||
pack(OUT / "jackbox-v1.bin", [front_img, side_img])
|
||||
print("Extracting and segmenting Jukebox textures...")
|
||||
|
||||
# 2. Screen UI Pack: 1 frame (640x480 BGRA)
|
||||
ui_img = Image.open(ui_path).convert("RGBA").resize((640, 480), Image.Resampling.LANCZOS)
|
||||
pack(OUT / "jackbox-ui-v1.bin", [ui_img])
|
||||
# 1. Front Texture
|
||||
front_bgr = cv2.imread(str(front_path))
|
||||
front_bgra, front_fg = clean_cutout(front_bgr)
|
||||
front_tex = cv2.resize(front_bgra, (256, 512), interpolation=cv2.INTER_LANCZOS4)
|
||||
|
||||
print("Jackbox asset packaging complete.")
|
||||
# 2. Side Texture: clean, crop margins, flip horizontally so back is x=0 and front is x=255
|
||||
side_bgr = cv2.imread(str(side_path))
|
||||
side_bgra, side_fg = clean_cutout(side_bgr)
|
||||
# Crop to actual jukebox silhouette (remove studio floor and top empty margins)
|
||||
side_cropped = side_bgra[80:2912, 0:1350]
|
||||
side_flipped = cv2.flip(side_cropped, 1)
|
||||
side_tex = cv2.resize(side_flipped, (256, 512), interpolation=cv2.INTER_LANCZOS4)
|
||||
|
||||
# 3. Rear Texture: authentic dark walnut cabinet panel with ventilation slots and arch top
|
||||
rear_tex = np.zeros((512, 256, 4), dtype=np.uint8)
|
||||
for y in range(512):
|
||||
grain = int(math.sin(y * 0.2) * 5 + math.sin(y * 0.05) * 8)
|
||||
rear_tex[y, :, 0] = np.clip(20 + grain, 0, 255) # B
|
||||
rear_tex[y, :, 1] = np.clip(28 + grain + 2, 0, 255) # G
|
||||
rear_tex[y, :, 2] = np.clip(45 + grain + 5, 0, 255) # R
|
||||
for y in range(120, 220, 10):
|
||||
rear_tex[y:y+3, 40:216, :3] = 12 # Dark ventilation slots
|
||||
rear_tex[:, :, 3] = cv2.resize(front_bgra[:, :, 3], (256, 512), interpolation=cv2.INTER_LANCZOS4)
|
||||
|
||||
# Pack 3D world frames: Frame 0 = Front, Frame 1 = Side, Frame 2 = Rear
|
||||
pack_bgra(OUT / "jackbox-v1.bin", [front_tex, side_tex, rear_tex], 256, 512)
|
||||
|
||||
# 4. Screen UI Pack: 640x480 BGRA
|
||||
ui_bgr = cv2.imread(str(ui_path))
|
||||
ui_bgra = cv2.cvtColor(ui_bgr, cv2.COLOR_BGR2BGRA)
|
||||
ui_tex = cv2.resize(ui_bgra, (640, 480), interpolation=cv2.INTER_LANCZOS4)
|
||||
pack_bgra(OUT / "jackbox-ui-v1.bin", [ui_tex], 640, 480)
|
||||
|
||||
print("Jukebox asset packaging complete.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -14,12 +14,12 @@ def test_packed_assets():
|
|||
assert world_bin.is_file(), f"Missing {world_bin}"
|
||||
assert ui_bin.is_file(), f"Missing {ui_bin}"
|
||||
|
||||
# Check world bin header: ANM1, 256, 512, 2 frames
|
||||
# Check world bin header: ANM1, 256, 512, 3 frames
|
||||
wdata = world_bin.read_bytes()
|
||||
magic, w, h, frames, raw_len = struct.unpack("<4s4I", wdata[:20])
|
||||
assert magic == b"ANM1", f"Bad magic: {magic}"
|
||||
assert (w, h, frames) == (256, 512, 2), f"Bad dimensions: {w}x{h}, {frames} frames"
|
||||
assert len(wdata) == 20 + raw_len == 20 + 256 * 512 * 4 * 2
|
||||
assert (w, h, frames) == (256, 512, 3), f"Bad dimensions: {w}x{h}, {frames} frames"
|
||||
assert len(wdata) == 20 + raw_len == 20 + 256 * 512 * 4 * 3
|
||||
print(f"[OK] jackbox-v1.bin verified: {frames} frames ({w}x{h}), {raw_len} bytes")
|
||||
|
||||
# Check UI bin header: ANM1, 640, 480, 1 frame
|
||||
|
|
@ -65,6 +65,7 @@ def test_code_structure():
|
|||
assert "INTERACT_JACKBOX equ 48" in data_inc
|
||||
assert "JACKBOX_X equ 6*FP_1" in data_inc
|
||||
assert "JACKBOX_Y equ 20*FP_1" in data_inc
|
||||
assert "MATERIAL_JACKBOX_BACK equ 163" in data_inc
|
||||
|
||||
# Check procs
|
||||
procs = [
|
||||
|
|
|
|||
261
tools/tests/test_jukebox_render_all_angles.py
Normal file
261
tools/tests/test_jukebox_render_all_angles.py
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Visual simulation of the 3D Jukebox cabinet in the Casino Hall.
|
||||
Loads assets/animations/hall/jackbox-v1.bin directly (3 frames: Front, Side, Back).
|
||||
Renders pixel-perfect 640x480 views matching MASM32 DrawCityVertical3D with alpha transparency.
|
||||
"""
|
||||
import math
|
||||
import struct
|
||||
import re
|
||||
from pathlib import Path
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
DOCS_DIR = REPO_ROOT / 'docs' / 'previews_minigames'
|
||||
DOCS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
FP_1 = 65536
|
||||
SCREEN_W = 640
|
||||
SCREEN_H = 480
|
||||
HALF_W = 320
|
||||
HALF_H = 240
|
||||
HALL_STANDING_EYE = FP_1 * 21 // 20
|
||||
|
||||
# 1. Load map
|
||||
with open(REPO_ROOT / 'casino3d_data.inc', 'r', encoding='latin1') as f:
|
||||
text = f.read()
|
||||
|
||||
lines = text.splitlines()
|
||||
map_lines = []
|
||||
capturing = False
|
||||
for l in lines:
|
||||
if 'casinoMap db' in l:
|
||||
capturing = True
|
||||
if capturing:
|
||||
map_lines.append(l)
|
||||
if len(map_lines) == 32:
|
||||
break
|
||||
|
||||
casinoMap = []
|
||||
for l in map_lines:
|
||||
vals = [int(x.strip()) for x in re.findall(r'\d+', l)]
|
||||
casinoMap.extend(vals)
|
||||
|
||||
# 2. Load HD textures
|
||||
tex_raw = open(REPO_ROOT / 'assets/textures/casino3d-hd.bin', 'rb').read()
|
||||
slot_size = 128 * 128 * 4
|
||||
|
||||
def get_tex_pixel(slot, tx, ty):
|
||||
idx = slot * slot_size + (ty * 128 + tx) * 4
|
||||
if idx + 4 > len(tex_raw):
|
||||
return (50, 50, 50, 255)
|
||||
b, g, r, a = tex_raw[idx:idx+4]
|
||||
return (r, g, b, a)
|
||||
|
||||
# 3. Load Jackbox Art pack (ANM1)
|
||||
jackbox_raw = open(REPO_ROOT / 'assets/animations/hall/jackbox-v1.bin', 'rb').read()
|
||||
magic, jw, jh, jframes, jlen = struct.unpack('<4s4I', jackbox_raw[:20])
|
||||
assert magic == b'ANM1' and (jw, jh, jframes) == (256, 512, 3), f'Invalid pack: {magic}, {jw}x{jh}, {jframes}'
|
||||
jpixels = jackbox_raw[20:]
|
||||
|
||||
def get_jackbox_pixel(frame, tx, ty):
|
||||
idx = frame * (jw * jh * 4) + (ty * jw + tx) * 4
|
||||
b, g, r, a = jpixels[idx:idx+4]
|
||||
return (r, g, b, a)
|
||||
|
||||
tableCos = [round(math.cos(i * 2 * math.pi / 512) * FP_1) for i in range(512)]
|
||||
tableSin = [round(math.sin(i * 2 * math.pi / 512) * FP_1) for i in range(512)]
|
||||
|
||||
def render_scene(cam_x, cam_y, cam_angle, jb_x, jb_y, out_path):
|
||||
playerX = int(cam_x * FP_1)
|
||||
playerY = int(cam_y * FP_1)
|
||||
playerAngle = cam_angle % 512
|
||||
|
||||
playerDirX = tableCos[playerAngle]
|
||||
playerDirY = tableSin[playerAngle]
|
||||
playerPlaneX = -66 * playerDirY // 100
|
||||
playerPlaneY = 66 * playerDirX // 100
|
||||
|
||||
screen_pixels = [(20, 20, 25)] * (SCREEN_W * SCREEN_H)
|
||||
zBuffer = [0x7FFFFFFF] * SCREEN_W
|
||||
depthBuffer = [0x7FFFFFFF] * (SCREEN_W * SCREEN_H)
|
||||
|
||||
# Floor & Ceiling
|
||||
for y in range(HALF_H, SCREEN_H):
|
||||
pDist = (HALF_H * FP_1) // (y - HALF_H + 1)
|
||||
shade = max(35, min(255, 255 - (pDist >> 13)))
|
||||
cShade = max(20, shade - 25)
|
||||
|
||||
fStepX = (playerPlaneX * 2 * pDist // FP_1) // SCREEN_W
|
||||
fStepY = (playerPlaneY * 2 * pDist // FP_1) // SCREEN_W
|
||||
|
||||
fX = playerX + ((playerDirX - playerPlaneX) * pDist // FP_1)
|
||||
fY = playerY + ((playerDirY - playerPlaneY) * pDist // FP_1)
|
||||
|
||||
for x in range(SCREEN_W):
|
||||
tx = (fX >> 9) & 127
|
||||
ty = (fY >> 9) & 127
|
||||
r, g, b, _ = get_tex_pixel(0, tx, ty)
|
||||
screen_pixels[y * SCREEN_W + x] = ((r * shade) >> 8, (g * shade) >> 8, (b * shade) >> 8)
|
||||
|
||||
cx = (fX >> 10) & 127
|
||||
cy = (fY >> 10) & 127
|
||||
cr, cg, cb, _ = get_tex_pixel(5, cx, cy)
|
||||
screen_pixels[(SCREEN_H - 1 - y) * SCREEN_W + x] = ((cr * cShade) >> 8, (cg * cShade) >> 8, (cb * cShade) >> 8)
|
||||
|
||||
fX += fStepX
|
||||
fY += fStepY
|
||||
|
||||
cityRayX = [0] * SCREEN_W
|
||||
cityRayY = [0] * SCREEN_W
|
||||
for col in range(SCREEN_W):
|
||||
camX = (2 * col - SCREEN_W) * FP_1 // SCREEN_W
|
||||
cityRayX[col] = playerDirX + (playerPlaneX * camX // FP_1)
|
||||
cityRayY[col] = playerDirY + (playerPlaneY * camX // FP_1)
|
||||
|
||||
# Walls
|
||||
for col in range(SCREEN_W):
|
||||
rayDirX = cityRayX[col]
|
||||
rayDirY = cityRayY[col]
|
||||
mapX = playerX >> 16
|
||||
mapY = playerY >> 16
|
||||
deltaDistX = (1 << 32) // abs(rayDirX) if rayDirX != 0 else 0x7FFFFFFF
|
||||
deltaDistY = (1 << 32) // abs(rayDirY) if rayDirY != 0 else 0x7FFFFFFF
|
||||
|
||||
if rayDirX < 0:
|
||||
stepX = -1
|
||||
sideDistX = ((playerX - (mapX << 16)) * deltaDistX) >> 16
|
||||
else:
|
||||
stepX = 1
|
||||
sideDistX = (((mapX + 1 << 16) - playerX) * deltaDistX) >> 16
|
||||
|
||||
if rayDirY < 0:
|
||||
stepY = -1
|
||||
sideDistY = ((playerY - (mapY << 16)) * deltaDistY) >> 16
|
||||
else:
|
||||
stepY = 1
|
||||
sideDistY = (((mapY + 1 << 16) - playerY) * deltaDistY) >> 16
|
||||
|
||||
side = 0
|
||||
hit = 0
|
||||
steps = 0
|
||||
while hit == 0 and steps < 64:
|
||||
steps += 1
|
||||
if sideDistX < sideDistY:
|
||||
sideDistX += deltaDistX
|
||||
mapX += stepX
|
||||
side = 0
|
||||
else:
|
||||
sideDistY += deltaDistY
|
||||
mapY += stepY
|
||||
side = 1
|
||||
if 0 <= mapX < 32 and 0 <= mapY < 32:
|
||||
tile = casinoMap[mapY * 32 + mapX]
|
||||
if tile != 0:
|
||||
hit = tile
|
||||
else:
|
||||
hit = 1
|
||||
|
||||
perpWallDist = sideDistX - deltaDistX if side == 0 else sideDistY - deltaDistY
|
||||
perpWallDist = max(2048, perpWallDist)
|
||||
zBuffer[col] = perpWallDist
|
||||
|
||||
lineH = (SCREEN_H * FP_1) // perpWallDist
|
||||
drawStart = max(0, HALF_H - lineH // 2)
|
||||
drawEnd = min(SCREEN_H - 1, HALF_H + lineH // 2)
|
||||
|
||||
wallHit = playerY + ((perpWallDist * rayDirY) >> 16) if side == 0 else playerX + ((perpWallDist * rayDirX) >> 16)
|
||||
texX = ((wallHit & 0xFFFF) * 128) >> 16
|
||||
if (side == 0 and rayDirX > 0) or (side == 1 and rayDirY < 0):
|
||||
texX = 127 - texX
|
||||
|
||||
shade = max(40, min(255, 255 - (perpWallDist >> 12)))
|
||||
if side == 1:
|
||||
shade = (shade * 180) >> 8
|
||||
|
||||
texId = hit if hit != 0 else 1
|
||||
for y in range(drawStart, drawEnd):
|
||||
texY = (((y * 2 - SCREEN_H + lineH) * 128) // (lineH * 2)) & 127
|
||||
r, g, b, _ = get_tex_pixel(texId, texX, texY)
|
||||
screen_pixels[y * SCREEN_W + col] = ((r * shade) >> 8, (g * shade) >> 8, (b * shade) >> 8)
|
||||
depthBuffer[y * SCREEN_W + col] = perpWallDist
|
||||
|
||||
def draw_vertical(axis, coord, loBound, hiBound, zLow, zHigh, frame_idx):
|
||||
for col in range(SCREEN_W):
|
||||
ray_dir = cityRayX[col] if axis == 0 else cityRayY[col]
|
||||
if abs(ray_dir) < 256:
|
||||
continue
|
||||
orig = playerX if axis == 0 else playerY
|
||||
num = (coord - orig)
|
||||
depth = (num << 16) // ray_dir
|
||||
if depth <= 4096 or depth >= zBuffer[col]:
|
||||
continue
|
||||
|
||||
along = playerY + ((depth * cityRayY[col]) >> 16) if axis == 0 else playerX + ((depth * cityRayX[col]) >> 16)
|
||||
if along < loBound or along >= hiBound:
|
||||
continue
|
||||
|
||||
srcX = (along - loBound) * jw // (hiBound - loBound)
|
||||
if srcX < 0 or srcX >= jw:
|
||||
continue
|
||||
|
||||
top = (HALL_STANDING_EYE - zHigh) * SCREEN_H // depth + HALF_H
|
||||
bottom = (HALL_STANDING_EYE - zLow) * SCREEN_H // depth + HALF_H
|
||||
span = bottom - top
|
||||
if span <= 0:
|
||||
continue
|
||||
|
||||
stepY = (jh << 16) // span
|
||||
r_start = max(0, top)
|
||||
r_end = min(SCREEN_H, bottom)
|
||||
curY = (r_start - top) * stepY
|
||||
|
||||
shade = max(40, min(255, 255 - (depth >> 12)))
|
||||
for row in range(r_start, r_end):
|
||||
srcY = (curY >> 16)
|
||||
curY += stepY
|
||||
if srcY < 0 or srcY >= jh:
|
||||
continue
|
||||
r, g, b, a = get_jackbox_pixel(frame_idx, srcX, srcY)
|
||||
# Alpha testing for materials >= 104 (cmp eax, 80000000h; jb city_vertical_next_pixel)
|
||||
if a < 128:
|
||||
continue
|
||||
if depth < depthBuffer[row * SCREEN_W + col]:
|
||||
depthBuffer[row * SCREEN_W + col] = depth
|
||||
screen_pixels[row * SCREEN_W + col] = ((r * shade) >> 8, (g * shade) >> 8, (b * shade) >> 8)
|
||||
|
||||
# Footprint: width 0.75m, depth 0.55m, height 1.85m
|
||||
wx = int(jb_x * FP_1)
|
||||
wy = int(jb_y * FP_1)
|
||||
x0 = wx - FP_1 * 15 // 40
|
||||
x1 = wx + FP_1 * 15 // 40
|
||||
y0 = wy - FP_1 * 11 // 40
|
||||
y1 = wy + FP_1 * 11 // 40
|
||||
zHigh = FP_1 * 37 // 20
|
||||
|
||||
# 1. Rear Face (Y = y0) - Frame 2
|
||||
draw_vertical(1, y0, x0, x1, 0, zHigh, 2)
|
||||
# 2. Left Side (X = x0) - Frame 1
|
||||
draw_vertical(0, x0, y0, y1, 0, zHigh, 1)
|
||||
# 3. Right Side (X = x1) - Frame 1
|
||||
draw_vertical(0, x1, y0, y1, 0, zHigh, 1)
|
||||
# 4. Front Face (Y = y1) - Frame 0
|
||||
draw_vertical(1, y1, x0, x1, 0, zHigh, 0)
|
||||
|
||||
img = Image.new('RGB', (SCREEN_W, SCREEN_H))
|
||||
img.putdata(screen_pixels)
|
||||
img.save(str(out_path))
|
||||
print(f'Rendered: {out_path.name}')
|
||||
|
||||
if __name__ == '__main__':
|
||||
jb_x, jb_y = 6.0, 20.0
|
||||
# 1. Front View (from South looking North directly at Front)
|
||||
render_scene(6.0, 22.2, 384, jb_x, jb_y, DOCS_DIR / 'jukebox_3d_front.png')
|
||||
# 2. 45-degree angle View (from South-East looking North-West)
|
||||
render_scene(7.2, 21.8, 416, jb_x, jb_y, DOCS_DIR / 'jukebox_3d_angle.png')
|
||||
# 3. Side View (from East looking West)
|
||||
render_scene(8.0, 20.0, 128, jb_x, jb_y, DOCS_DIR / 'jukebox_3d_side.png')
|
||||
# 4. Close-up Front View (standing right at the jukebox)
|
||||
render_scene(6.0, 21.1, 384, jb_x, jb_y, DOCS_DIR / 'jukebox_3d_close.png')
|
||||
print('All Jukebox 3D preview frames rendered to docs/previews_minigames/!')
|
||||
Loading…
Add table
Reference in a new issue