diff --git a/.DS_Store b/.DS_Store index a72a57488..ebf3cd0ce 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/assets/.DS_Store b/assets/.DS_Store index 7d52abd95..e65ce88ab 100644 Binary files a/assets/.DS_Store and b/assets/.DS_Store differ diff --git a/assets/DEMO_FAZA1/Environment/stream_body.png b/assets/DEMO_FAZA1/Environment/stream_body.png deleted file mode 100644 index e4e2b7699..000000000 Binary files a/assets/DEMO_FAZA1/Environment/stream_body.png and /dev/null differ diff --git a/assets/DEMO_FAZA1/Environment/stream_final_v6.png b/assets/DEMO_FAZA1/Environment/stream_final_v6.png new file mode 100644 index 000000000..2baabd1af Binary files /dev/null and b/assets/DEMO_FAZA1/Environment/stream_final_v6.png differ diff --git a/assets/DEMO_FAZA1/Environment/stream_final_v7.png b/assets/DEMO_FAZA1/Environment/stream_final_v7.png new file mode 100644 index 000000000..cfd0bcfd9 Binary files /dev/null and b/assets/DEMO_FAZA1/Environment/stream_final_v7.png differ diff --git a/assets/DEMO_FAZA1/Environment/stream_head.png b/assets/DEMO_FAZA1/Environment/stream_head.png deleted file mode 100644 index 000987e18..000000000 Binary files a/assets/DEMO_FAZA1/Environment/stream_head.png and /dev/null differ diff --git a/assets/DEMO_FAZA1/Environment/stream_pipe.png b/assets/DEMO_FAZA1/Environment/stream_pipe.png deleted file mode 100644 index b2a3cf142..000000000 Binary files a/assets/DEMO_FAZA1/Environment/stream_pipe.png and /dev/null differ diff --git a/assets/DEMO_FAZA1/Environment/stream_v5_graded.png b/assets/DEMO_FAZA1/Environment/stream_v5_graded.png new file mode 100644 index 000000000..392ce700c Binary files /dev/null and b/assets/DEMO_FAZA1/Environment/stream_v5_graded.png differ diff --git a/assets/DEMO_FAZA1/Environment/stream_water.png b/assets/DEMO_FAZA1/Environment/stream_water.png deleted file mode 100644 index fa44ce3c0..000000000 Binary files a/assets/DEMO_FAZA1/Environment/stream_water.png and /dev/null differ diff --git a/assets/DEMO_FAZA1/Environment/stream_winding.png b/assets/DEMO_FAZA1/Environment/stream_winding.png deleted file mode 100644 index cd7d1b1d6..000000000 Binary files a/assets/DEMO_FAZA1/Environment/stream_winding.png and /dev/null differ diff --git a/assets/references/potok_voda_cevi.png b/assets/references/potok_voda_cevi.png new file mode 100644 index 000000000..2baabd1af Binary files /dev/null and b/assets/references/potok_voda_cevi.png differ diff --git a/scripts/main.js b/scripts/main.cjs similarity index 94% rename from scripts/main.js rename to scripts/main.cjs index a2da24363..325ba22d5 100644 --- a/scripts/main.js +++ b/scripts/main.cjs @@ -19,7 +19,8 @@ function createWindow() { title: 'Mrtva Dolina - Death Valley' }); - mainWindow.loadFile('index.html'); + // Fix path to index.html (it's one level up from scripts/) + mainWindow.loadFile(path.join(__dirname, '../index.html')); mainWindow.webContents.openDevTools(); mainWindow.on('closed', () => { diff --git a/scripts/slice_stream.py b/scripts/slice_stream.py index 51208014c..70742fde8 100644 --- a/scripts/slice_stream.py +++ b/scripts/slice_stream.py @@ -1,108 +1,107 @@ + + + + + import cv2 import numpy as np import os -def slice_stream_assets(): - src_path = '/Users/davidkotnik/.gemini/antigravity/brain/07019d04-a214-43ab-9565-86f4e8f17e5b/uploaded_media_1769607894587.jpg' - - print(f"Loading {src_path}") - img = cv2.imread(src_path) - if img is None: return +# Paths +input_path = '/Users/davidkotnik/.gemini/antigravity/brain/8233d64e-0c17-43b1-b8b5-fbc41754e56b/uploaded_media_1769679216460.jpg' +output_dir = '/Users/davidkotnik/repos/novafarma/assets/DEMO_FAZA1/Environment/' + +# Ensure output dir exists +if not os.path.exists(output_dir): + os.makedirs(output_dir) + +# Read image +img = cv2.imread(input_path, cv2.IMREAD_COLOR) +if img is None: + print("Error loading input image") + exit(1) + +h, w = img.shape[:2] +print(f"Image loaded: {w}x{h}") + +# Convert to BGRA +img_rgba = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA) +hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) + +# --- 1. REMOVE BACKGROUND (Checkerboard/White) --- +# Strategy: The background is bright (White/Light Gray). The content (Mud, Pipe, Debris) is darker. +# Threshold on Value (V) channel. +# Let's verify histogram or min/max. +# Or just a hard threshold. +# Mud is dark. Pipe is dark gray. +# Debris is dark. +# Checkerboard is > 200 usually. + +thresh_val = 190 +# Create mask where Value > 190 -> Background +_, mask_bg_bright = cv2.threshold(hsv[:, :, 2], thresh_val, 255, cv2.THRESH_BINARY) + +# Also check for Low Saturation for the checkerboard? +# Checkerboard is grayscale (S near 0). +# Pipe is also grayscale. +# But Pipe is Darker. +# So Brightness threshold should handle it. + +# Refine mask: +# Remove small holes in background mask? +# Use morphological open +kernel = np.ones((3,3), np.uint8) +mask_bg_bright = cv2.morphologyEx(mask_bg_bright, cv2.MORPH_OPEN, kernel) + +# --- 2. REMOVE GREEN TRIANGLE --- +# Green range +lower_green = np.array([35, 40, 40]) +upper_green = np.array([90, 255, 255]) +mask_green = cv2.inRange(hsv, lower_green, upper_green) + +# Combine Background and Green masks +# We want to remove (set alpha 0) where mask is BG or Green. +mask_remove = cv2.bitwise_or(mask_bg_bright, mask_green) + +# Set alpha +# Start with opaque (255) +img_rgba[:, :, 3] = 255 +# Set 0 where removal mask is active +img_rgba[mask_remove > 0, 3] = 0 + +# Smooth edges slightly +# cv2.GaussianBlur on alpha channel? +# Or erode the alpha slightly to remove fringes. +# Let's erode the opaque region. +alpha = img_rgba[:, :, 3] +alpha = cv2.erode(alpha, kernel, iterations=1) +img_rgba[:, :, 3] = alpha + + +# --- 3. CROP AND SAVE --- +# Crop to content +coords = cv2.findNonZero(img_rgba[:, :, 3]) +if coords is not None: + x, y, w_content, h_content = cv2.boundingRect(coords) + + # Padding + padding = 2 + x = max(0, x - padding) + y = max(0, y - padding) + w_content = min(w - x, w_content + 2*padding) + h_content = min(h - y, h_content + 2*padding) + + cropped_main = img_rgba[y:y+h_content, x:x+w_content] +else: + cropped_main = img_rgba # Fallback + +# Save FINAL asset v7 +output_path = os.path.join(output_dir, 'stream_final_v7.png') +cv2.imwrite(output_path, cropped_main) +print(f"Saved V7 asset to {output_path}") + + - # 1. CLEAN BACKGROUND (GrabCut) - mask = np.zeros(img.shape[:2], np.uint8) - bgdModel = np.zeros((1,65),np.float64) - fgdModel = np.zeros((1,65),np.float64) - h, w = img.shape[:2] - cv2.grabCut(img, mask, (10, 10, w-20, h-20), bgdModel, fgdModel, 5, cv2.GC_INIT_WITH_RECT) - mask_final = np.where((mask==2)|(mask==0),0,1).astype('uint8') - - # Apply Alpha - b, g, r = cv2.split(img) - alpha = mask_final * 255 - img_rgba = cv2.merge([b, g, r, alpha]) - - # 2. CREATE 'HEAD' (Pipe Only) - # The pipe is roughly the top 40%? Let's crop visually based on the image structure. - # The pipe is top-right. - # Let's say we keep the top half as the "Start". - # And we take a middle slice as the "Segment". - - # Finding the pipe: - # Based on the image, the pipe drain is at the top. - # Let's crop `head` from y=0 to y=0.45*h - cut_y = int(h * 0.45) - - head_img = img_rgba[0:cut_y, :] - - # Crop transparent borders from head - coords = cv2.findNonZero(head_img[:,:,3]) # Alpha - if coords is not None: - x, y, cw, ch = cv2.boundingRect(coords) - head_img = head_img[y:y+ch, x:x+cw] - - # 3. CREATE 'SEGMENT' (Water Channel) - # We take a slice from the middle-bottom. - # Crop from y=0.45*h to y=0.85*h (skip very bottom tip?) - # Actually, let's take a nice chunk that can be tiled. - # The channel is diagonal. Tiling diagonal is hard without overlap. - # Let's just crop the rest of the stream as one big piece for now. - - body_img = img_rgba[cut_y:, :] - - # Crop transparent borders from body - coords = cv2.findNonZero(body_img[:,:,3]) - if coords is not None: - x, y, cw, ch = cv2.boundingRect(coords) - body_img = body_img[y:y+ch, x:x+cw] - - # 4. SOFTEN EDGES (To fix floating walls) - # Applied to both Head and Body. - # We want to fade out the BOTTOM edge of the mask, so the "wall" blends into the grass. - - def soften_bottom_edge(image): - h, w = image.shape[:2] - # Create a gradient mask for the bottom 20 pixels - grad_h = 30 - if h < grad_h: return image # Too small - - # We modify the alpha channel - alpha = image[:,:,3] - - # We need to detect where the "bottom" of the object is. - # Since it's diagonal, it's tricky. - # Simple hack: Erode the alpha slightly to sharpen the cut, then blur it? - # Or just blur the edges? - - # Let's try blurring the alpha channel to soften the hard cut against the grass. - # Only on the edges. - # Get edge mask - edges = cv2.Canny(alpha, 100, 200) - # Dilate edges to get a rim - rim = cv2.dilate(edges, np.ones((5,5),np.uint8)) - - # Blur alpha - blurred_alpha = cv2.GaussianBlur(alpha, (7,7), 0) - - # Apply blurred alpha where rim is - # image[:,:,3] = np.where(rim>0, blurred_alpha, alpha) - - # Actually, let's just do a global soft outline. - image[:,:,3] = blurred_alpha - return image - # head_img = soften_bottom_edge(head_img) - # body_img = soften_bottom_edge(body_img) - # (Skipping blur for now, plain cut is cleaner if geometry is right) - # Save - base_dir = '/Users/davidkotnik/repos/novafarma/assets/DEMO_FAZA1/Environment' - if not os.path.exists(base_dir): os.makedirs(base_dir, exist_ok=True) - - cv2.imwrite(os.path.join(base_dir, 'stream_head.png'), head_img) - cv2.imwrite(os.path.join(base_dir, 'stream_body.png'), body_img) - - print("Sliced stream into head and body.") -if __name__ == "__main__": - slice_stream_assets() diff --git a/src/scenes/GrassScene_Clean.js b/src/scenes/GrassScene_Clean.js index e9d303d98..97fe8d08d 100644 --- a/src/scenes/GrassScene_Clean.js +++ b/src/scenes/GrassScene_Clean.js @@ -10,8 +10,9 @@ export default class GrassSceneClean extends Phaser.Scene { // 1. Podlaga (Foundation) this.load.image('ground_base', 'DEMO_FAZA1/Ground/tla_trava_tekstura.png'); - // 2. Vodni kanali (Water) - this.load.image('stream_water', 'DEMO_FAZA1/Environment/stream_water.png'); + // 2. Vodni kanali (Water) - NEW CLEAN ASSET V7 (Aggressive Clean) + this.load.image('stream_final_v7', 'DEMO_FAZA1/Environment/stream_final_v7.png'); + // Removed extensions for now // 3. Foliage this.load.image('grass_dense', 'DEMO_FAZA1/Vegetation/grass_cluster_dense.png'); @@ -26,22 +27,13 @@ export default class GrassSceneClean extends Phaser.Scene { frameHeight: 256 }); // Loading as 'kai' to keep existing references working, but now it has frames. - // 5. UI Assets (Loaded from DEMO_FAZA1/UI) - this.load.image('ui_health_bar', 'DEMO_FAZA1/UI/health_bar.png'); - this.load.image('ui_weather_widget', 'DEMO_FAZA1/UI/weather_widget.png'); - this.load.image('ui_minimap', 'DEMO_FAZA1/UI/minimap_frame.png'); - this.load.image('ui_hotbar', 'DEMO_FAZA1/UI/hotbar_background.png'); - this.load.image('ui_action_btn', 'DEMO_FAZA1/UI/action_btn.png'); + // 5. UI Assets (Loaded in UIScene now to prevent duplicates) + // REMOVED // 6. Camp Assets this.load.image('campfire', 'DEMO_FAZA1/Environment/taborni_ogenj.png'); this.load.image('tent', 'DEMO_FAZA1/Environment/sotor.png'); this.load.image('sleeping_bag', 'DEMO_FAZA1/Items/spalna_vreca.png'); - - // 7. NEW: Sliced Stream Assets - this.load.image('stream_head', 'DEMO_FAZA1/Environment/stream_head.png'); - this.load.image('stream_body', 'DEMO_FAZA1/Environment/stream_body.png'); - } create() { @@ -52,27 +44,42 @@ export default class GrassSceneClean extends Phaser.Scene { this.cameras.main.setBounds(0, 0, WORLD_W, WORLD_H); this.cameras.main.setBackgroundColor('#1a1a1a'); + // --- ZOOM CONTROL --- + this.cameras.main.setZoom(1.5); // Default start zoom + + this.input.on('wheel', (pointer, gameObjects, deltaX, deltaY, deltaZ) => { + // Zoom In/Out based on wheel delta + // deltaY > 0 means scroll down (zoom out), deltaY < 0 means scroll up (zoom in) + const zoomFactor = -0.001; + const newZoom = this.cameras.main.zoom + deltaY * zoomFactor; + + // Clamp zoom to reasonable limits (e.g., 0.5x to 3x) + this.cameras.main.setZoom(Phaser.Math.Clamp(newZoom, 0.5, 3.0)); + }); + // --- 1. PODLAGA (The Foundation) --- // Level 0, Locked to Z = -100 this.ground = this.add.tileSprite(WORLD_W / 2, WORLD_H / 2, WORLD_W, WORLD_H, 'ground_base'); this.ground.setTileScale(1, 1); this.ground.setDepth(-100); - // --- 2. STREAM (Single Pipe Channel) --- - // Placed at Z=0 (Ground Level) - const startX = WORLD_W / 2 + 50; - const startY = WORLD_H / 2 + 100; + // --- 2. STREAM SYSTEM (Head + Extensions) --- + // Center the whole construction + const startX = WORLD_W / 2; + const startY = WORLD_H / 2 - 300; // Start higher up to leave room for extensions - this.stream = this.physics.add.staticImage(startX, startY, 'stream_muddy'); - this.stream.setOrigin(0.5, 0.9); - this.stream.setDepth(0); - this.stream.setScale(1.0); + // Main Head (Pipe + Splash) + // Showing V7 Asset (Aggressive Clean + Transparent Green) + this.stream = this.physics.add.staticImage(startX, startY, 'stream_final_v7'); + this.stream.setOrigin(0.5, 0.5); + this.stream.setDepth(-50); - // Physics Body - this.stream.body.setSize(this.stream.width * 0.9, this.stream.height * 0.4); - this.stream.body.setOffset(this.stream.width * 0.05, this.stream.height * 0.5); + // Physics Body for Main + this.stream.body.setSize(this.stream.width * 0.8, this.stream.height * 0.2); + this.stream.body.setOffset(this.stream.width * 0.1, this.stream.height * 0.4); - // Collider added later after Kai creation + // Extensions removed for reset + // for (let i = 1; i <= 3; i++) { ... } // Collider added later after Kai creation @@ -95,7 +102,7 @@ export default class GrassSceneClean extends Phaser.Scene { this.kai.body.setOffset(256 / 2 - 25, 256 - 40); // Pivot offset based on 256 frame // Collider Stream <-> Kai - this.physics.add.collider(this.kai, this.stream); + // this.physics.add.collider(this.kai, this.stream); // --- ANIMATIONS --- // 0-3: Down, 4-7: Left, 8-11: Right, 12-15: Up @@ -128,42 +135,55 @@ export default class GrassSceneClean extends Phaser.Scene { this.kai.play('walk-down'); this.kai.stop(); - // --- 7. CAMP SETUP (Oživitev) --- - this.campGroup = this.add.group(); - - // Tent (Behind Kai usually) - let tent = this.add.image(WORLD_W / 2 - 100, WORLD_H / 2 - 80, 'tent'); - tent.setOrigin(0.5, 0.9); // Bottom anchor - tent.setScale(1.2); // Big enough for Kai - this.campGroup.add(tent); - - // Campfire (In front) - let fire = this.add.image(WORLD_W / 2 + 50, WORLD_H / 2 + 50, 'campfire'); - fire.setOrigin(0.5, 0.8); - fire.setScale(0.8); - this.campGroup.add(fire); - - // Tweens for Fire (Little pulse) - this.tweens.add({ - targets: fire, - scaleX: 0.85, - scaleY: 0.85, - duration: 500, - yoyo: true, - repeat: -1 - }); - - // Sleeping Bag - let bag = this.add.image(WORLD_W / 2 - 80, WORLD_H / 2 + 20, 'sleeping_bag'); - bag.setOrigin(0.5, 0.5); // Flat on ground? - bag.setScale(0.8); - this.campGroup.add(bag); - - // Camera + // Camera setup logic this.cameras.main.startFollow(this.kai, true, 0.1, 0.1); - this.cameras.main.setZoom(1.5); this.cursors = this.input.keyboard.createCursorKeys(); + + + // Collider added later after Kai creation + + // Collider added later after Kai creation + + // --- 3. FOLIAGE (Trava - Šopi) --- + // Removed as requested + + // --- 4. ITEMS (Seno) --- + // Removed as requested + + + + // Collider Stream <-> Kai + // this.physics.add.collider(this.kai, this.stream); + + // --- ANIMATIONS --- + // 0-3: Down, 4-7: Left, 8-11: Right, 12-15: Up + // This is a duplicate animation creation block, removing it. + // this.anims.create({ + // key: 'walk-down', + // frames: this.anims.generateFrameNumbers('kai', { start: 0, end: 3 }), + // frameRate: 8, + // repeat: -1 + // }); + // this.anims.create({ + // key: 'walk-left', + // frames: this.anims.generateFrameNumbers('kai', { start: 4, end: 7 }), + // frameRate: 8, + // repeat: -1 + // }); + // this.anims.create({ + // key: 'walk-right', + // frames: this.anims.generateFrameNumbers('kai', { start: 8, end: 11 }), + // frameRate: 8, + // repeat: -1 + // }); + // this.anims.create({ + // key: 'walk-up', + // frames: this.anims.generateFrameNumbers('kai', { start: 12, end: 15 }), + // frameRate: 8, + // repeat: -1 + // }); + // Launch UI Scene if (!this.scene.get('UIScene').scene.settings.active) { this.scene.launch('UIScene'); @@ -210,10 +230,5 @@ export default class GrassSceneClean extends Phaser.Scene { // --- Z-SORTING SYSTEM --- // Player this.kai.setDepth(this.kai.y); - - // Camp Sorting - this.campGroup.children.each(item => { - item.setDepth(item.y); - }); } }