diff --git a/AGENTS.md b/AGENTS.md index 851cb19..9f8b5a7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,10 +31,10 @@ engine/ (Rust → WASM) web/ (TypeScript + Vite) ## Welt-Modell -- **Große Welt**: 1024×768 Pixel (4×3 Chunks à 256×256) -- Nur Zellen im Sichtfeld + 80px Margin werden simuliert +- **Große Welt**: 4096×3072 Pixel (16×12 Chunks à 256×256, tiled 4×4 Szenen) +- Nur Zellen im Sichtfeld + 200px Margin werden simuliert - Rest eingefroren — keine CPU-Kosten für unsichtbare Bereiche -- Grid wächst auf 1024×768 = 786K Zellen (~3 MB Speicher) +- Grid wächst auf 4096×3072 = 12.5M Zellen (~48 MB Speicher) ### Datenstruktur Cell (pro Pixel) @@ -93,6 +93,8 @@ Properties pro Material: `MaterialProps` in `engine/src/material.rs`: - Wasser/Eis in Nachbarschaft → Feuer erlischt sofort - Erzeugt Funken (14) und Rauch (15) beim Brennen - **Flammen-Züngeln**: 1/5 Chance pro Zelle, Funken-Partikel nach oben zu feuern (4–12 Frames Lebensdauer) +- **Liquid-Momentum**: FLAG_MOMENTUM auf fallendem/sloshendem Wasser, waves klettern an Schalenwänden hoch +- **Flammen-Züngeln**: 1/5 Chance pro Zelle, Funken-Partikel nach oben zu feuern (4–12 Frames Lebensdauer) **Ephemere Partikel** (Funken + Rauch): - `update_ephemeral`: dekrementiert Health, bei 0 → Luft @@ -105,16 +107,16 @@ Properties pro Material: `MaterialProps` in `engine/src/material.rs`: Reihenfolge in `physics::update(cam_x, cam_y, rw, rh, zoom)`: -Nur Zellen innerhalb **Kamera-Sichtfeld + 80px Margin** werden simuliert. +Nur Zellen innerhalb **Kamera-Sichtfeld + 200px Margin** werden simuliert. Rest der Welt ist eingefroren (keine CPU-Kosten). -1. **update_fire** — Feuer-Update: Brennstoff-Verbrauch, Ausbreitung, Funken/Rauch-Emission +1. **update_fire** — Feuer-Update: Brennstoff-Verbrauch, Ausbreitung, Funken/Rauch-Emission, Flammen-Züngeln 2. **Haupt-Loop** (bottom→top, alternierende Spalten, nur aktive Region): - Heat-Transfer über Moore-Nachbarn - Material-Reaktionen (Water+Lava, Säure+Holz, ...) - Phasen-Transformation (Temp-basiert) - Bewegung: Powders ↓, Liquids ↓↔, Solids ↓ (kein Gas!) -3. **update_gases** (top→bottom, nur jedes 2. Frame, nur aktive Region): Gas steigt 1px/Frame +3. **update_gases** (top→bottom, nur jedes 2. Frame, nur aktive Region): Gas + Flammen-Funken steigen max 1px/Frame 4. **update_ephemeral** (nur aktive Region): Spark/Smoke Health dekrementieren, bei 0 → Luft ## Level-Format @@ -149,10 +151,11 @@ Jedes Material bekommt deterministische Pixel-Variation basierend auf Grid-Koord **Light-Propagation** in `render_buffer.rs`: 1. Emissions-Pass: Jedes Pixel mit `light_emit > 0` strahlt Licht -2. Flood-Fill (max 12 Passes, early-termination): 8-Richtungs-Propagation - - Falloff: 2 (kardinal) / 3 (diagonal) + `light_block` (gecapped bei 200) +2. Flood-Fill (max `16/zoom` Passes, early-termination): 8-Richtungs-Propagation + - Falloff: `(2|3)/zoom` (kardinal/diagonal) + `light_block` (gecapped bei 200) - Opaque Pixel blocken Weitergabe, werden aber selbst beleuchtet 3. Abwechselnde Scan-Richtung für gleichmäßige Verteilung +4. Zoom-skaliert: Passes + Falloff passen sich an Kamera-Zoom an **Lichtquellen**: Feuer (255), Lava (200), Funken (180) diff --git a/engine/src/grid.rs b/engine/src/grid.rs index 5847cf7..a63bbdb 100644 --- a/engine/src/grid.rs +++ b/engine/src/grid.rs @@ -9,6 +9,7 @@ pub struct Grid { pub const FLAG_UPDATED: u8 = 0b0000_0001; pub const FLAG_PLAYER: u8 = 0b0000_0010; +pub const FLAG_MOMENTUM: u8 = 0b0000_0100; impl Grid { pub fn new(width: u32, height: u32) -> Self { diff --git a/engine/src/main.rs b/engine/src/main.rs index 68e8d89..5b5ffbe 100644 --- a/engine/src/main.rs +++ b/engine/src/main.rs @@ -8,26 +8,93 @@ use physics::Physics; use render_buffer::RenderBuffer; fn main() { - let width = 1024u32; - let height = 768u32; + let width = 4096u32; + let height = 3072u32; let rw = 320u32; let rh = 180u32; let mut grid = Grid::new(width, height); let mut physics = Physics::new(); let mut rb = RenderBuffer::new(rw, rh); - grid.fill_rect(440, 720, 144, 48, 1); - grid.fill_rect(460, 700, 104, 20, 2); - grid.fill_rect(470, 600, 84, 12, 5); - grid.fill_circle(490, 560, 20, 3); - grid.fill_rect(430, 650, 50, 30, 4); - grid.fill_rect(540, 650, 50, 30, 6); - grid.fill_rect(500, 640, 10, 18, 11); - grid.fill_rect(480, 630, 64, 24, 5); - grid.fill_rect(480, 618, 64, 12, 9); + for ty in 0..4 { + for tx in 0..4 { + let ox = tx * 1024; + let oy = ty * 768; + grid.fill_rect(ox, oy + 740, 1024, 28, 1); + grid.fill_rect(ox + 20, oy + 680, 80, 60, 1); + grid.fill_rect(ox + 30, oy + 640, 60, 40, 1); + grid.fill_rect(ox + 40, oy + 610, 40, 30, 1); + grid.fill_circle(ox + 60, oy + 600, 15, 2); + grid.fill_rect(ox + 50, oy + 660, 20, 20, 2); + grid.fill_rect(ox, oy + 720, 120, 20, 1); + grid.fill_rect(ox + 85, oy + 705, 35, 15, 1); + grid.fill_rect(ox + 5, oy + 705, 60, 15, 1); + grid.fill_circle(ox + 60, oy + 712, 12, 6); + grid.fill_circle(ox + 70, oy + 708, 6, 6); + grid.fill_rect(ox + 60, oy + 695, 20, 10, 2); + grid.fill_rect(ox + 350, oy + 680, 100, 60, 1); + grid.fill_rect(ox + 365, oy + 640, 70, 40, 1); + grid.fill_rect(ox + 380, oy + 610, 40, 30, 1); + grid.fill_rect(ox + 390, oy + 660, 20, 20, 10); + grid.fill_rect(ox + 370, oy + 700, 60, 40, 5); + grid.fill_circle(ox + 400, oy + 690, 8, 9); + grid.fill_rect(ox + 395, oy + 675, 10, 5, 9); + grid.fill_rect(ox + 750, oy + 640, 50, 100, 1); + grid.fill_rect(ox + 760, oy + 610, 30, 30, 1); + grid.fill_rect(ox + 770, oy + 590, 10, 20, 1); + grid.fill_circle(ox + 775, oy + 580, 8, 9); + grid.fill_rect(ox + 760, oy + 650, 30, 40, 5); + grid.fill_rect(ox + 180, oy + 420, 100, 20, 1); + grid.fill_rect(ox + 190, oy + 400, 80, 20, 2); + grid.fill_circle(ox + 230, oy + 390, 15, 3); + grid.fill_rect(ox + 200, oy + 380, 30, 20, 5); + grid.fill_circle(ox + 215, oy + 385, 6, 9); + grid.fill_rect(ox + 240, oy + 410, 10, 10, 10); + grid.fill_rect(ox + 620, oy + 350, 80, 18, 1); + grid.fill_rect(ox + 630, oy + 335, 60, 15, 2); + grid.fill_circle(ox + 660, oy + 325, 12, 3); + grid.fill_circle(ox + 640, oy + 340, 8, 11); + grid.fill_rect(ox, oy + 700, 100, 40, 4); + grid.fill_rect(ox + 10, oy + 680, 80, 20, 4); + grid.fill_rect(ox + 20, oy + 660, 60, 20, 4); + grid.fill_rect(ox + 880, oy + 700, 80, 40, 6); + grid.fill_rect(ox + 890, oy + 680, 60, 20, 6); + grid.fill_rect(ox + 900, oy + 660, 40, 20, 6); + grid.fill_circle(ox + 420, oy + 280, 30, 11); + grid.fill_circle(ox + 450, oy + 270, 20, 11); + grid.fill_circle(ox + 390, oy + 290, 18, 11); + grid.fill_rect(ox + 420, oy + 300, 20, 15, 11); + grid.fill_rect(ox + 100, oy + 720, 180, 20, 3); + grid.fill_circle(ox + 120, oy + 710, 25, 3); + grid.fill_circle(ox + 180, oy + 708, 30, 3); + grid.fill_circle(ox + 160, oy + 700, 18, 3); + grid.fill_circle(ox + 220, oy + 715, 20, 3); + grid.fill_circle(ox + 850, oy + 580, 22, 12); + grid.fill_circle(ox + 870, oy + 590, 14, 12); + grid.fill_rect(ox + 840, oy + 600, 40, 20, 12); + grid.fill_rect(ox + 470, oy + 660, 280, 10, 5); + grid.fill_rect(ox + 490, oy + 650, 40, 10, 5); + grid.fill_rect(ox + 710, oy + 650, 40, 10, 5); + grid.fill_rect(ox + 520, oy + 720, 180, 20, 1); + grid.fill_rect(ox + 540, oy + 710, 140, 10, 1); + grid.fill_circle(ox + 600, oy + 718, 15, 6); + grid.fill_circle(ox + 620, oy + 716, 10, 6); + grid.fill_rect(ox + 100, oy + 730, 40, 10, 13); + grid.fill_circle(ox + 120, oy + 732, 8, 13); + grid.fill_rect(ox + 280, oy + 640, 40, 40, 10); + grid.fill_rect(ox + 290, oy + 650, 20, 30, 1); + grid.fill_circle(ox + 300, oy + 670, 5, 9); + grid.fill_circle(ox + 550, oy + 520, 20, 2); + grid.fill_circle(ox + 570, oy + 530, 14, 2); + grid.fill_circle(ox + 200, oy + 520, 30, 1); + grid.fill_circle(ox + 220, oy + 500, 20, 1); + grid.fill_rect(ox + 300, oy + 580, 12, 20, 4); + grid.fill_rect(ox + 700, oy + 500, 10, 30, 4); + } + } - let cam_x = 512i32; - let cam_y = 600i32; + let cam_x = 400i32; + let cam_y = 630i32; let zoom = 2.0f32; let mut frame = 0; diff --git a/engine/src/physics.rs b/engine/src/physics.rs index 08a0061..eb4a2bd 100644 --- a/engine/src/physics.rs +++ b/engine/src/physics.rs @@ -415,21 +415,56 @@ impl Physics { if can_fall { let was_liquid = bprops.is_liquid; grid.swap(x, y, x, y + 1); + let mi = grid.index(x, y + 1); + grid.flags[mi] |= crate::grid::FLAG_MOMENTUM; if was_liquid { - let pdir = self.rand_dir(); - for dist in 1..=4i32 { - let sx = x as i32 + pdir * dist; - if !grid.in_bounds(sx, y as i32 + 1) { break; } - let sxu = sx as u32; - if grid.get(sxu, y + 1) == 0 { - grid.swap(x, y + 1, sxu, y + 1); - break; + let idx_displaced = grid.index(x, y); + grid.flags[idx_displaced] |= crate::grid::FLAG_MOMENTUM; + if self.rand() % 10 < 7 { + if y > 0 && grid.get(x, y - 1) == 0 { + let ai = grid.index(x, y - 1); + grid.flags[ai] |= crate::grid::FLAG_MOMENTUM; + grid.swap(x, y, x, y - 1); + } + } else { + let pdir = self.rand_dir(); + for dist in 3..=8i32 { + let sx = x as i32 + pdir * dist; + if !grid.in_bounds(sx, y as i32 + 1) { break; } + let sxu = sx as u32; + if grid.get(sxu, y + 1) == 0 { + let si = grid.index(sxu, y + 1); + grid.flags[si] |= crate::grid::FLAG_MOMENTUM; + grid.swap(x, y + 1, sxu, y + 1); + break; + } } } } return; } + if grid.flags[grid.index(x, y)] & crate::grid::FLAG_MOMENTUM != 0 { + if y > 0 { + if grid.get(x, y - 1) == 0 { + let ai = grid.index(x, y - 1); + grid.flags[ai] |= crate::grid::FLAG_MOMENTUM; + grid.swap(x, y, x, y - 1); + return; + } + let dir = self.rand_dir(); + for &d in &[dir, -dir] { + let dx = x as i32 + d; + if grid.in_bounds(dx, y as i32 - 1) && grid.get(dx as u32, y - 1) == 0 { + grid.swap(x, y, dx as u32, y - 1); + return; + } + } + } + let mi = grid.index(x, y); + grid.flags[mi] &= !crate::grid::FLAG_MOMENTUM; + } + let dir = self.rand_dir(); for &d in &[dir, -dir] { let dx = x as i32 + d; @@ -458,6 +493,28 @@ impl Physics { } } } + + let has_momentum = grid.flags[grid.index(x, y)] & crate::grid::FLAG_MOMENTUM != 0; + if has_momentum && below != 0 && !bprops.is_liquid && !bprops.is_gas { + if y > 1 { + for &d in &[self.rand_dir(), -self.rand_dir()] { + let dx = x as i32 + d; + if grid.in_bounds(dx, y as i32 - 2) && grid.get(dx as u32, y - 1) == 0 && grid.get(dx as u32, y - 2) == 0 { + grid.swap(x, y, dx as u32, y - 2); + return; + } + } + } + if y > 0 { + for &d in &[self.rand_dir(), -self.rand_dir()] { + let dx = x as i32 + d; + if grid.in_bounds(dx, y as i32 - 1) && grid.get(dx as u32, y - 1) == 0 { + grid.swap(x, y, dx as u32, y - 1); + return; + } + } + } + } } fn update_solid(&mut self, grid: &mut Grid, x: u32, y: u32, _mat: u8) { diff --git a/engine/src/render_buffer.rs b/engine/src/render_buffer.rs index f1db1d4..b11e479 100644 --- a/engine/src/render_buffer.rs +++ b/engine/src/render_buffer.rs @@ -7,6 +7,7 @@ pub struct RenderBuffer { pub height: u32, light: Vec, block: Vec, + zoom: f32, } impl RenderBuffer { @@ -19,6 +20,7 @@ impl RenderBuffer { height, light: vec![0; lsize], block: vec![0; lsize], + zoom: 1.0, } } @@ -27,6 +29,8 @@ impl RenderBuffer { let rh = self.height as i32; let total = (rw * rh) as usize; + self.zoom = zoom; + for i in 0..total { self.light[i] = 0; self.block[i] = 0; @@ -49,7 +53,8 @@ impl RenderBuffer { } } - for _pass in 0..12 { + let max_passes = (16.0 / zoom).max(6.0).min(24.0) as u32; + for _pass in 0..max_passes { let dir = _pass & 1; let mut changed = false; if dir == 0 { @@ -157,7 +162,8 @@ impl RenderBuffer { let ni = (ny * rw + nx) as usize; let nlight = self.light[ni] as u32; if nlight <= 1 { continue; } - let falloff = (if nx == px || ny == py { 2 } else { 3 }) + blk; + let base_falloff = if nx == px || ny == py { 2.0 } else { 3.0 }; + let falloff = (base_falloff / self.zoom) as u32 + blk; if nlight > falloff { let incoming = nlight - falloff; if incoming > best { diff --git a/web/src/camera.ts b/web/src/camera.ts index f2d8d9b..c135e8b 100644 --- a/web/src/camera.ts +++ b/web/src/camera.ts @@ -12,4 +12,13 @@ export class Camera { setZoom(z: number) { this.targetZoom = Math.max(0.1, Math.min(10, z)); } + + zoomAt(z: number, worldX: number, worldY: number) { + if (this.zoom <= 0) return; + const oldZoom = this.zoom; + this.setZoom(z); + const newZoom = this.targetZoom; + this.x = worldX - (worldX - this.x) * (oldZoom / newZoom); + this.y = worldY - (worldY - this.y) * (oldZoom / newZoom); + } } diff --git a/web/src/main.ts b/web/src/main.ts index 5e22514..c37bc8e 100644 --- a/web/src/main.ts +++ b/web/src/main.ts @@ -3,8 +3,8 @@ import { Renderer } from "./renderer"; import { Input } from "./input"; import { Camera } from "./camera"; -const GRID_WIDTH = 1024; -const GRID_HEIGHT = 768; +const GRID_WIDTH = 4096; +const GRID_HEIGHT = 3072; const PAINT_MATERIALS = [M.SAND, M.WATER, M.LAVA, M.WOOD, M.FIRE, M.ICE, M.OIL, M.ACID, M.STONE, M.DIRT, M.GLASS]; @@ -19,30 +19,93 @@ async function main() { const engine = await createEngine(GRID_WIDTH, GRID_HEIGHT); - engine.fillRect(0, 740, 1024, 28, M.STONE); - engine.fillRect(440, 720, 144, 20, M.WOOD); - engine.fillRect(460, 700, 104, 20, M.DIRT); - engine.fillCircle(490, 560, 25, M.SAND); - engine.fillCircle(520, 550, 20, M.SAND); - engine.fillRect(430, 650, 40, 30, M.WATER); - engine.fillRect(550, 650, 40, 25, M.LAVA); - engine.fillRect(505, 640, 10, 16, M.ICE); - engine.fillCircle(620, 580, 20, M.OIL); - engine.fillRect(480, 620, 64, 30, M.WOOD); - engine.fillRect(480, 600, 64, 20, M.FIRE); - engine.fillRect(300, 720, 40, 20, M.GLASS); - engine.fillCircle(200, 680, 18, M.SAND); - engine.fillRect(100, 700, 30, 40, M.WOOD); - engine.fillRect(750, 680, 60, 60, M.STONE); - engine.fillCircle(780, 660, 15, M.LAVA); - engine.fillRect(700, 720, 30, 20, M.ICE); + // === EPIC WORLD: tiled 16 copies of the scene === + function buildScene(ox: number, oy: number) { + engine.fillRect(ox, oy + 740, 1024, 28, M.STONE); + engine.fillRect(ox + 20, oy + 680, 80, 60, M.STONE); + engine.fillRect(ox + 30, oy + 640, 60, 40, M.STONE); + engine.fillRect(ox + 40, oy + 610, 40, 30, M.STONE); + engine.fillCircle(ox + 60, oy + 600, 15, M.DIRT); + engine.fillRect(ox + 50, oy + 660, 20, 20, M.DIRT); + engine.fillRect(ox, oy + 720, 120, 20, M.STONE); + engine.fillRect(ox + 85, oy + 705, 35, 15, M.STONE); + engine.fillRect(ox + 5, oy + 705, 60, 15, M.STONE); + engine.fillCircle(ox + 60, oy + 712, 12, M.LAVA); + engine.fillCircle(ox + 70, oy + 708, 6, M.LAVA); + engine.fillRect(ox + 60, oy + 695, 20, 10, M.DIRT); + engine.fillRect(ox + 350, oy + 680, 100, 60, M.STONE); + engine.fillRect(ox + 365, oy + 640, 70, 40, M.STONE); + engine.fillRect(ox + 380, oy + 610, 40, 30, M.STONE); + engine.fillRect(ox + 390, oy + 660, 20, 20, M.GLASS); + engine.fillRect(ox + 370, oy + 700, 60, 40, M.WOOD); + engine.fillCircle(ox + 400, oy + 690, 8, M.FIRE); + engine.fillRect(ox + 395, oy + 675, 10, 5, M.FIRE); + engine.fillRect(ox + 750, oy + 640, 50, 100, M.STONE); + engine.fillRect(ox + 760, oy + 610, 30, 30, M.STONE); + engine.fillRect(ox + 770, oy + 590, 10, 20, M.STONE); + engine.fillCircle(ox + 775, oy + 580, 8, M.FIRE); + engine.fillRect(ox + 760, oy + 650, 30, 40, M.WOOD); + engine.fillRect(ox + 180, oy + 420, 100, 20, M.STONE); + engine.fillRect(ox + 190, oy + 400, 80, 20, M.DIRT); + engine.fillCircle(ox + 230, oy + 390, 15, M.SAND); + engine.fillRect(ox + 200, oy + 380, 30, 20, M.WOOD); + engine.fillCircle(ox + 215, oy + 385, 6, M.FIRE); + engine.fillRect(ox + 240, oy + 410, 10, 10, M.GLASS); + engine.fillRect(ox + 620, oy + 350, 80, 18, M.STONE); + engine.fillRect(ox + 630, oy + 335, 60, 15, M.DIRT); + engine.fillCircle(ox + 660, oy + 325, 12, M.SAND); + engine.fillCircle(ox + 640, oy + 340, 8, M.ICE); + engine.fillRect(ox, oy + 700, 100, 40, M.WATER); + engine.fillRect(ox + 10, oy + 680, 80, 20, M.WATER); + engine.fillRect(ox + 20, oy + 660, 60, 20, M.WATER); + engine.fillRect(ox + 880, oy + 700, 80, 40, M.LAVA); + engine.fillRect(ox + 890, oy + 680, 60, 20, M.LAVA); + engine.fillRect(ox + 900, oy + 660, 40, 20, M.LAVA); + engine.fillCircle(ox + 420, oy + 280, 30, M.ICE); + engine.fillCircle(ox + 450, oy + 270, 20, M.ICE); + engine.fillCircle(ox + 390, oy + 290, 18, M.ICE); + engine.fillRect(ox + 420, oy + 300, 20, 15, M.ICE); + engine.fillRect(ox + 100, oy + 720, 180, 20, M.SAND); + engine.fillCircle(ox + 120, oy + 710, 25, M.SAND); + engine.fillCircle(ox + 180, oy + 708, 30, M.SAND); + engine.fillCircle(ox + 160, oy + 700, 18, M.SAND); + engine.fillCircle(ox + 220, oy + 715, 20, M.SAND); + engine.fillCircle(ox + 850, oy + 580, 22, M.OIL); + engine.fillCircle(ox + 870, oy + 590, 14, M.OIL); + engine.fillRect(ox + 840, oy + 600, 40, 20, M.OIL); + engine.fillRect(ox + 470, oy + 660, 280, 10, M.WOOD); + engine.fillRect(ox + 490, oy + 650, 40, 10, M.WOOD); + engine.fillRect(ox + 710, oy + 650, 40, 10, M.WOOD); + engine.fillRect(ox + 520, oy + 720, 180, 20, M.STONE); + engine.fillRect(ox + 540, oy + 710, 140, 10, M.STONE); + engine.fillCircle(ox + 600, oy + 718, 15, M.LAVA); + engine.fillCircle(ox + 620, oy + 716, 10, M.LAVA); + engine.fillRect(ox + 100, oy + 730, 40, 10, M.ACID); + engine.fillCircle(ox + 120, oy + 732, 8, M.ACID); + engine.fillRect(ox + 280, oy + 640, 40, 40, M.GLASS); + engine.fillRect(ox + 290, oy + 650, 20, 30, M.STONE); + engine.fillCircle(ox + 300, oy + 670, 5, M.FIRE); + engine.fillCircle(ox + 550, oy + 520, 20, M.DIRT); + engine.fillCircle(ox + 570, oy + 530, 14, M.DIRT); + engine.fillCircle(ox + 200, oy + 520, 30, M.STONE); + engine.fillCircle(ox + 220, oy + 500, 20, M.STONE); + engine.fillRect(ox + 300, oy + 580, 12, 20, M.WATER); + engine.fillRect(ox + 700, oy + 500, 10, 30, M.WATER); + } + + for (let ty = 0; ty < 4; ty++) { + for (let tx = 0; tx < 4; tx++) { + buildScene(tx * 1024, ty * 768); + } + } + + // === Spawn point: center on the castle === + camera.x = 400; + camera.y = 630; const rw = engine.renderWidth(); const rh = engine.renderHeight(); - camera.x = 512; - camera.y = 600; - function resize() { const dpr = window.devicePixelRatio || 1; renderer.resize(window.innerWidth, window.innerHeight, dpr); @@ -58,6 +121,13 @@ async function main() { return { x: Math.floor(mx), y: Math.floor(my) }; } + function mouseWorld() { + return { + x: (input.mouseX / window.innerWidth - 0.5) * rw / camera.zoom + camera.x, + y: (input.mouseY / window.innerHeight - 0.5) * rh / camera.zoom + camera.y, + }; + } + const panel = document.getElementById("panel")!; function buildPanel() { panel.innerHTML = ""; @@ -122,7 +192,7 @@ async function main() { if (input.isDown("Equal")) camera.setZoom(camera.zoom + 2 * dt); if (input.isDown("Minus")) camera.setZoom(camera.zoom - 2 * dt); if (input.wheel !== 0) { - camera.setZoom(camera.zoom + input.wheel * 0.5); + camera.setZoom(camera.zoom + input.wheel * 0.2); input.wheel = 0; }