diff --git a/assets/animations/hall/jackbox-v1.bin b/assets/animations/hall/jackbox-v1.bin index 51baaa0..d521666 100644 Binary files a/assets/animations/hall/jackbox-v1.bin and b/assets/animations/hall/jackbox-v1.bin differ diff --git a/bin/poker.exe b/bin/poker.exe index 8cd30e4..33daddb 100644 Binary files a/bin/poker.exe and b/bin/poker.exe differ diff --git a/build/AssemblerCasinoLauncher.cs b/build/AssemblerCasinoLauncher.cs index 44eea94..635046f 100644 --- a/build/AssemblerCasinoLauncher.cs +++ b/build/AssemblerCasinoLauncher.cs @@ -8,7 +8,7 @@ namespace AssemblerCasinoLauncher { static class Program { - const string ExpectedPokerSha256 = "7488391B71FCEB1C2A2F68C06445CC1421A650793205498BA19E787C9E184297"; + const string ExpectedPokerSha256 = "07D474A0223390E7AC4F4F779818D77CC2DAE5123F4BDB0F72DD975473267310"; [STAThread] static void Main() diff --git a/build/graphics/poker.exe b/build/graphics/poker.exe index 8cd30e4..33daddb 100644 Binary files a/build/graphics/poker.exe and b/build/graphics/poker.exe differ diff --git a/dist/Assembler Casino.exe b/dist/Assembler Casino.exe index f19b049..33520b2 100644 Binary files a/dist/Assembler Casino.exe and b/dist/Assembler Casino.exe differ diff --git a/docs/previews_minigames/jukebox_3d_angle.png b/docs/previews_minigames/jukebox_3d_angle.png index 752bb07..1c34bb1 100644 Binary files a/docs/previews_minigames/jukebox_3d_angle.png and b/docs/previews_minigames/jukebox_3d_angle.png differ diff --git a/docs/previews_minigames/jukebox_3d_close.png b/docs/previews_minigames/jukebox_3d_close.png index 94e5897..ba20513 100644 Binary files a/docs/previews_minigames/jukebox_3d_close.png and b/docs/previews_minigames/jukebox_3d_close.png differ diff --git a/docs/previews_minigames/jukebox_3d_front.png b/docs/previews_minigames/jukebox_3d_front.png index 542ebbe..e26fbcd 100644 Binary files a/docs/previews_minigames/jukebox_3d_front.png and b/docs/previews_minigames/jukebox_3d_front.png differ diff --git a/docs/previews_minigames/jukebox_3d_side.png b/docs/previews_minigames/jukebox_3d_side.png index 6830d9e..22682a8 100644 Binary files a/docs/previews_minigames/jukebox_3d_side.png and b/docs/previews_minigames/jukebox_3d_side.png differ diff --git a/poker.exe b/poker.exe index 8cd30e4..33daddb 100644 Binary files a/poker.exe and b/poker.exe differ diff --git a/src_graphics/jackbox.inc b/src_graphics/jackbox.inc index 70e1731..75a9926 100644 --- a/src_graphics/jackbox.inc +++ b/src_graphics/jackbox.inc @@ -52,60 +52,56 @@ jackbox_surface_missing: 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 - +DrawJackboxCabinet3D PROC uses ebx esi edi wx:DWORD, wy:DWORD + local x0:DWORD, x1:DWORD, y0:DWORD, y1:DWORD + ; Solid rectangular cabinet against East wall beside billiards: + ; Depth along X: 0.55m (x0 = 30.40m, x1 = 30.95m against wall at 31.0m) + ; Width along Y: 0.85m (y0 = 24.575m, y1 = 25.425m, centered at Y = 25.0m) + ; Height Z: 1.85m + mov x0, FP_1*304/10 + mov x1, FP_1*3095/100 + mov y0, 25*FP_1 - FP_1*425/1000 + mov y1, 25*FP_1 + FP_1*425/1000 + + ; 1. Back Face (against East wall: X = x1) + invoke DrawCityVertical3D, 0, x1, y0, y1, 0, FP_1*37/20, MATERIAL_JACKBOX_BACK + ; 2. North Side (Y = y0) + invoke DrawCityVertical3D, 1, y0, x0, x1, 0, FP_1*37/20, MATERIAL_JACKBOX_SIDE + ; 3. South Side (Y = y1) + invoke DrawCityVertical3D, 1, y1, x0, x1, 0, FP_1*37/20, MATERIAL_JACKBOX_SIDE + ; 4. Front Face (facing West towards billiards: X = x0) + invoke DrawCityVertical3D, 0, x0, y0, y1, 0, FP_1*37/20, MATERIAL_JACKBOX_FRONT + ; 5. Top Lid (horizontal cap at z = 1.85m) + invoke DrawCityHorizontal3D, x0, x1, y0, y1, FP_1*37/20, 110 + ret +DrawJackboxCabinet3D ENDP + +RenderJackboxCabinet3D PROC + invoke DrawJackboxCabinet3D, JACKBOX_X, JACKBOX_Y + ret +RenderJackboxCabinet3D ENDP + +JackboxCanWalk3D PROC px:DWORD, py:DWORD + mov eax, px + cmp eax, FP_1*303/10 + jb jackbox_walk_clear + cmp eax, 31*FP_1+9830 + ja jackbox_walk_clear + mov eax, py + sub eax, 25*FP_1 + test eax, eax + jns @F + neg eax +@@: + cmp eax, FP_1*55/100 + ja jackbox_walk_clear + xor eax, eax + ret +jackbox_walk_clear: + mov eax, 1 + ret +JackboxCanWalk3D ENDP + PlayJukeboxTrack3D PROC uses ebx trackIndex:DWORD invoke mciSendStringA, OFFSET jboxStopCmd, 0, 0, 0 invoke mciSendStringA, OFFSET jboxCloseCmd, 0, 0, 0 diff --git a/src_graphics/jackbox_data.inc b/src_graphics/jackbox_data.inc index b9bae27..6eae614 100644 --- a/src_graphics/jackbox_data.inc +++ b/src_graphics/jackbox_data.inc @@ -5,8 +5,8 @@ MODE_JACKBOX equ 28 INTERACT_JACKBOX equ 48 -JACKBOX_X equ 6*FP_1 -JACKBOX_Y equ 20*FP_1 +JACKBOX_X equ FP_1*306/10 +JACKBOX_Y equ 25*FP_1 JACKBOX_TRACK_COUNT equ 10 MATERIAL_JACKBOX_FRONT equ 161 diff --git a/tools/generators/pack_jackbox.py b/tools/generators/pack_jackbox.py index baf1cd8..0c877a0 100644 --- a/tools/generators/pack_jackbox.py +++ b/tools/generators/pack_jackbox.py @@ -1,63 +1,124 @@ """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). +Processes high-resolution source images into a solid rectangular cabinet ('прямоугольный шкаф'): +- Front: Arched jukebox art framed in rich dark mahogany wood casing (solid 256x512 BGRA). +- Side: Cropped rectangular wooden cabinet side panel with chrome accents (solid 256x512 BGRA). +- Back: Matching dark walnut cabinet rear panel with ventilation slots (solid 256x512 BGRA). +- UI: 640x480 BGRA track selector screen. +Eliminates all grey studio backgrounds, transparent gaps, and seam holes. """ import math from pathlib import Path import struct import cv2 import numpy as np -from PIL import Image ROOT = Path(__file__).resolve().parents[2] SOURCE = ROOT / "assets/sprites/jackbox" OUT = ROOT / "assets/animations/hall" +# Deep rich dark walnut / mahogany wood color (BGR) +WOOD_COLOR = np.array([16, 26, 48], dtype=np.uint8) -def clean_cutout(img_bgr: np.ndarray) -> tuple[np.ndarray, np.ndarray]: - """Segment studio grey background using connected components from image borders.""" + +def make_solid_front(img_bgr: np.ndarray) -> np.ndarray: + """Frame the arched jukebox front into a solid rectangular wooden cabinet face.""" 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])) + # Detect studio grey background + diff = np.maximum( + np.abs(img_bgr[:, :, 0].astype(int) - img_bgr[:, :, 1].astype(int)), + np.maximum(np.abs(img_bgr[:, :, 1].astype(int) - img_bgr[:, :, 2].astype(int)), + np.abs(img_bgr[:, :, 0].astype(int) - img_bgr[:, :, 2].astype(int))) ) - brightness = np.mean(arr, axis=2) - is_bg_cand = (color_diff < 10) & (brightness >= 18) & (brightness <= 65) + bright = np.mean(img_bgr, axis=2) + is_bg = (diff < 10) & (bright >= 18) & (bright <= 65) - # Seed from all 4 borders + # Seed from 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] + seed_mask[0, :] = is_bg[0, :] + seed_mask[-1, :] = is_bg[-1, :] + seed_mask[:, 0] = is_bg[:, 0] + seed_mask[:, -1] = is_bg[:, -1] - num_labels, labels = cv2.connectedComponents(is_bg_cand.astype(np.uint8)) + num_labels, labels = cv2.connectedComponents(is_bg.astype(np.uint8)) border_labels = np.unique(labels[seed_mask == 1]) border_labels = border_labels[border_labels != 0] + is_real_bg = np.isin(labels, border_labels) - is_bg = np.isin(labels, border_labels) - clean_fg = (~is_bg).astype(np.uint8) + # Replace studio grey background with dark mahogany wood casing + front_solid = img_bgr.copy() + mask = cv2.GaussianBlur(is_real_bg.astype(np.float32), (5, 5), 1.0) + for c in range(3): + front_solid[:, :, c] = np.clip( + front_solid[:, :, c] * (1.0 - mask) + WOOD_COLOR[c] * mask, + 0, 255 + ).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) + # Resize to 256x512 BGRA, 100% opaque (no transparency gaps!) + resized = cv2.resize(front_solid, (256, 512), interpolation=cv2.INTER_LANCZOS4) + bgra = cv2.cvtColor(resized, cv2.COLOR_BGR2BGRA) + bgra[:, :, 3] = 255 + return bgra - # 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 make_solid_side(img_bgr: np.ndarray) -> np.ndarray: + """Crop side image to a solid rectangular cabinet side panel.""" + # Crop to actual rectangular cabinet profile: + # y: 90..2910 (removes studio top margin and floor shadow) + # x: 120..1345 (from front edge to back vertical edge) + side_crop = img_bgr[90:2910, 120:1345] + h, w = side_crop.shape[:2] + + # Detect studio grey in the top corner outside the curve + diff = np.maximum( + np.abs(side_crop[:, :, 0].astype(int) - side_crop[:, :, 1].astype(int)), + np.maximum(np.abs(side_crop[:, :, 1].astype(int) - side_crop[:, :, 2].astype(int)), + np.abs(side_crop[:, :, 0].astype(int) - side_crop[:, :, 2].astype(int))) + ) + bright = np.mean(side_crop, axis=2) + is_bg = (diff < 10) & (bright >= 18) & (bright <= 65) + + seed_mask = np.zeros((h, w), dtype=np.uint8) + seed_mask[0, :] = is_bg[0, :] + seed_mask[-1, :] = is_bg[-1, :] + seed_mask[:, 0] = is_bg[:, 0] + seed_mask[:, -1] = is_bg[:, -1] + + num_labels, labels = cv2.connectedComponents(is_bg.astype(np.uint8)) + border_labels = np.unique(labels[seed_mask == 1]) + border_labels = border_labels[border_labels != 0] + is_real_bg = np.isin(labels, border_labels) + + # Fill corner with dark mahogany cabinet wood + side_solid = side_crop.copy() + mask = cv2.GaussianBlur(is_real_bg.astype(np.float32), (5, 5), 1.0) + for c in range(3): + side_solid[:, :, c] = np.clip( + side_solid[:, :, c] * (1.0 - mask) + WOOD_COLOR[c] * mask, + 0, 255 + ).astype(np.uint8) + + # Left of texture (x=0) is FRONT edge; right of texture (x=max) is BACK edge. + # Matches along_x from x0 (front) to x1 (back against wall). + resized = cv2.resize(side_solid, (256, 512), interpolation=cv2.INTER_LANCZOS4) + bgra = cv2.cvtColor(resized, cv2.COLOR_BGR2BGRA) + bgra[:, :, 3] = 255 + return bgra + + +def make_solid_back() -> np.ndarray: + """Generate a solid dark walnut cabinet rear panel with ventilation slots.""" + rear = np.zeros((512, 256, 4), dtype=np.uint8) + for y in range(512): + grain = int(math.sin(y * 0.2) * 4 + math.sin(y * 0.05) * 6) + rear[y, :, 0] = np.clip(int(WOOD_COLOR[0]) + grain, 0, 255) # B + rear[y, :, 1] = np.clip(int(WOOD_COLOR[1]) + grain + 1, 0, 255) # G + rear[y, :, 2] = np.clip(int(WOOD_COLOR[2]) + grain + 3, 0, 255) # R + rear[y, :, 3] = 255 # Fully opaque + for y in range(120, 240, 12): + rear[y:y+3, 32:224, :3] = 10 # Dark ventilation slots + return rear def pack_bgra(path: Path, frames: list[np.ndarray], w: int, h: int): @@ -78,31 +139,18 @@ def main(): assert side_path.exists(), f"Missing {side_path}" assert ui_path.exists(), f"Missing {ui_path}" - print("Extracting and segmenting Jukebox textures...") + print("Building solid rectangular Jukebox cabinet textures...") - # 1. Front Texture + # 1. Front Texture: solid rectangular face 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) + front_tex = make_solid_front(front_bgr) - # 2. Side Texture: clean, crop margins, flip horizontally so back is x=0 and front is x=255 + # 2. Side Texture: solid rectangular side panel 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) + side_tex = make_solid_side(side_bgr) - # 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) + # 3. Rear Texture: solid dark walnut panel + rear_tex = make_solid_back() # 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) @@ -113,7 +161,7 @@ def main(): 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.") + print("Solid Jukebox asset packaging complete.") if __name__ == "__main__": diff --git a/tools/tests/check_jackbox.py b/tools/tests/check_jackbox.py index 14c209d..fe2622c 100644 --- a/tools/tests/check_jackbox.py +++ b/tools/tests/check_jackbox.py @@ -63,8 +63,8 @@ def test_code_structure(): # Check constants assert "MODE_JACKBOX equ 28" in data_inc 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 "JACKBOX_X equ FP_1*306/10" in data_inc + assert "JACKBOX_Y equ 25*FP_1" in data_inc assert "MATERIAL_JACKBOX_BACK equ 163" in data_inc # Check procs diff --git a/tools/tests/test_jukebox_render_all_angles.py b/tools/tests/test_jukebox_render_all_angles.py index 4e89d4e..e6364e3 100644 --- a/tools/tests/test_jukebox_render_all_angles.py +++ b/tools/tests/test_jukebox_render_all_angles.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- """ -Visual simulation of the 3D Jukebox cabinet in the Casino Hall. +Visual simulation of the solid rectangular 3D Jukebox cabinet beside billiards. Loads assets/animations/hall/jackbox-v1.bin directly (3 frames: Front, Side, Back). -Renders pixel-perfect 640x480 views matching MASM32 DrawCityVertical3D with alpha transparency. +Renders pixel-perfect 640x480 views matching MASM32 DrawCityVertical3D and DrawCityHorizontal3D. """ import math import struct @@ -56,7 +56,7 @@ def get_tex_pixel(slot, tx, ty): # 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}' +assert magic == b'ANM1' and (jw, jh, jframes) == (256, 512, 3) jpixels = jackbox_raw[20:] def get_jackbox_pixel(frame, tx, ty): @@ -67,7 +67,7 @@ def get_jackbox_pixel(frame, tx, ty): 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): +def render_scene(cam_x, cam_y, cam_angle, out_path): playerX = int(cam_x * FP_1) playerY = int(cam_y * FP_1) playerAngle = cam_angle % 512 @@ -181,7 +181,7 @@ def render_scene(cam_x, cam_y, cam_angle, jb_x, jb_y, out_path): 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): + def draw_vert(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: @@ -218,30 +218,57 @@ def render_scene(cam_x, cam_y, cam_angle, jb_x, jb_y, out_path): 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. Pool table geometry: X in [26.0, 28.0], Y in [24.0, 26.0], Z in [0, 0.85m] + def draw_pool_side(axis, coord, lo, hi, z0, z1): + 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 < lo or along >= hi: + continue + top = (HALL_STANDING_EYE - z1) * SCREEN_H // depth + HALF_H + bottom = (HALL_STANDING_EYE - z0) * SCREEN_H // depth + HALF_H + r_start = max(0, top) + r_end = min(SCREEN_H, bottom) + shade = max(40, min(255, 255 - (depth >> 12))) + for row in range(r_start, r_end): + if depth < depthBuffer[row * SCREEN_W + col]: + depthBuffer[row * SCREEN_W + col] = depth + screen_pixels[row * SCREEN_W + col] = ((50 * shade) >> 8, (25 * shade) >> 8, (15 * shade) >> 8) - # 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) + draw_pool_side(1, int(24.0*FP_1), int(26.0*FP_1), int(28.0*FP_1), 0, int(0.85*FP_1)) + draw_pool_side(1, int(26.0*FP_1), int(26.0*FP_1), int(28.0*FP_1), 0, int(0.85*FP_1)) + draw_pool_side(0, int(26.0*FP_1), int(24.0*FP_1), int(26.0*FP_1), 0, int(0.85*FP_1)) + draw_pool_side(0, int(28.0*FP_1), int(24.0*FP_1), int(26.0*FP_1), 0, int(0.85*FP_1)) + + # 2. Solid Rectangular Jukebox Cabinet (at East wall, next to billiards table!) + # Depth: X in [30.40m .. 30.95m] (back against East wall at 31.0m) + # Width: Y in [24.575m .. 25.425m] (centered at Y = 25.0m) + # Height: Z in [0 .. 1.85m] + x0 = int(30.40 * FP_1) + x1 = int(30.95 * FP_1) + y0 = int(24.575 * FP_1) + y1 = int(25.425 * FP_1) + zHigh = int(1.85 * FP_1) + + # Back Face (against wall: X = x1) - Frame 2 + draw_vert(0, x1, y0, y1, 0, zHigh, 2) + # North Side (Y = y0) - Frame 1 + draw_vert(1, y0, x0, x1, 0, zHigh, 1) + # South Side (Y = y1) - Frame 1 + draw_vert(1, y1, x0, x1, 0, zHigh, 1) + # Front Face (facing West towards billiards table: X = x0) - Frame 0 + draw_vert(0, x0, y0, y1, 0, zHigh, 0) img = Image.new('RGB', (SCREEN_W, SCREEN_H)) img.putdata(screen_pixels) @@ -249,13 +276,12 @@ def render_scene(cam_x, cam_y, cam_angle, jb_x, jb_y, 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') + # 1. Front View: Standing near billiards at (26.0, 25.0) looking East (angle=0) past table at Jukebox + render_scene(26.0, 25.0, 0, DOCS_DIR / 'jukebox_3d_front.png') + # 2. Angle View: Standing at (27.5, 22.5) looking South-East (angle=480) at pool table and Jukebox + render_scene(27.5, 22.5, 480, DOCS_DIR / 'jukebox_3d_angle.png') + # 3. Close-up View: Standing at (29.2, 25.0) looking East (angle=0) right at Jukebox + render_scene(29.2, 25.0, 0, DOCS_DIR / 'jukebox_3d_close.png') + # 4. Side View: Standing at (30.0, 27.5) looking North (angle=384) at South side of Jukebox + render_scene(30.0, 27.5, 384, DOCS_DIR / 'jukebox_3d_side.png') print('All Jukebox 3D preview frames rendered to docs/previews_minigames/!')