v0.3: Large world 4096x3072 + momentum waves + zoom-scaled lighting
- World expanded to 4096x3072 (16x area, tiled 4x4 copies) - Active region margin increased to 200px - Liquid momentum system (FLAG_MOMENTUM) for wave sloshing - Zoom-at-cursor support (camera.zoomAt) - Smoother zoom steps (0.2 per scroll tick) - Light propagation scaled by zoom (passes & falloff) - AGENTS.md updated with all current features
This commit is contained in:
@@ -31,10 +31,10 @@ engine/ (Rust → WASM) web/ (TypeScript + Vite)
|
|||||||
|
|
||||||
## Welt-Modell
|
## Welt-Modell
|
||||||
|
|
||||||
- **Große Welt**: 1024×768 Pixel (4×3 Chunks à 256×256)
|
- **Große Welt**: 4096×3072 Pixel (16×12 Chunks à 256×256, tiled 4×4 Szenen)
|
||||||
- Nur Zellen im Sichtfeld + 80px Margin werden simuliert
|
- Nur Zellen im Sichtfeld + 200px Margin werden simuliert
|
||||||
- Rest eingefroren — keine CPU-Kosten für unsichtbare Bereiche
|
- 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)
|
### Datenstruktur Cell (pro Pixel)
|
||||||
|
|
||||||
@@ -93,6 +93,8 @@ Properties pro Material: `MaterialProps` in `engine/src/material.rs`:
|
|||||||
- Wasser/Eis in Nachbarschaft → Feuer erlischt sofort
|
- Wasser/Eis in Nachbarschaft → Feuer erlischt sofort
|
||||||
- Erzeugt Funken (14) und Rauch (15) beim Brennen
|
- 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)
|
- **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):
|
**Ephemere Partikel** (Funken + Rauch):
|
||||||
- `update_ephemeral`: dekrementiert Health, bei 0 → Luft
|
- `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)`:
|
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).
|
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):
|
2. **Haupt-Loop** (bottom→top, alternierende Spalten, nur aktive Region):
|
||||||
- Heat-Transfer über Moore-Nachbarn
|
- Heat-Transfer über Moore-Nachbarn
|
||||||
- Material-Reaktionen (Water+Lava, Säure+Holz, ...)
|
- Material-Reaktionen (Water+Lava, Säure+Holz, ...)
|
||||||
- Phasen-Transformation (Temp-basiert)
|
- Phasen-Transformation (Temp-basiert)
|
||||||
- Bewegung: Powders ↓, Liquids ↓↔, Solids ↓ (kein Gas!)
|
- 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
|
4. **update_ephemeral** (nur aktive Region): Spark/Smoke Health dekrementieren, bei 0 → Luft
|
||||||
|
|
||||||
## Level-Format
|
## Level-Format
|
||||||
@@ -149,10 +151,11 @@ Jedes Material bekommt deterministische Pixel-Variation basierend auf Grid-Koord
|
|||||||
|
|
||||||
**Light-Propagation** in `render_buffer.rs`:
|
**Light-Propagation** in `render_buffer.rs`:
|
||||||
1. Emissions-Pass: Jedes Pixel mit `light_emit > 0` strahlt Licht
|
1. Emissions-Pass: Jedes Pixel mit `light_emit > 0` strahlt Licht
|
||||||
2. Flood-Fill (max 12 Passes, early-termination): 8-Richtungs-Propagation
|
2. Flood-Fill (max `16/zoom` Passes, early-termination): 8-Richtungs-Propagation
|
||||||
- Falloff: 2 (kardinal) / 3 (diagonal) + `light_block` (gecapped bei 200)
|
- Falloff: `(2|3)/zoom` (kardinal/diagonal) + `light_block` (gecapped bei 200)
|
||||||
- Opaque Pixel blocken Weitergabe, werden aber selbst beleuchtet
|
- Opaque Pixel blocken Weitergabe, werden aber selbst beleuchtet
|
||||||
3. Abwechselnde Scan-Richtung für gleichmäßige Verteilung
|
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)
|
**Lichtquellen**: Feuer (255), Lava (200), Funken (180)
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ pub struct Grid {
|
|||||||
|
|
||||||
pub const FLAG_UPDATED: u8 = 0b0000_0001;
|
pub const FLAG_UPDATED: u8 = 0b0000_0001;
|
||||||
pub const FLAG_PLAYER: u8 = 0b0000_0010;
|
pub const FLAG_PLAYER: u8 = 0b0000_0010;
|
||||||
|
pub const FLAG_MOMENTUM: u8 = 0b0000_0100;
|
||||||
|
|
||||||
impl Grid {
|
impl Grid {
|
||||||
pub fn new(width: u32, height: u32) -> Self {
|
pub fn new(width: u32, height: u32) -> Self {
|
||||||
|
|||||||
+80
-13
@@ -8,26 +8,93 @@ use physics::Physics;
|
|||||||
use render_buffer::RenderBuffer;
|
use render_buffer::RenderBuffer;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let width = 1024u32;
|
let width = 4096u32;
|
||||||
let height = 768u32;
|
let height = 3072u32;
|
||||||
let rw = 320u32;
|
let rw = 320u32;
|
||||||
let rh = 180u32;
|
let rh = 180u32;
|
||||||
let mut grid = Grid::new(width, height);
|
let mut grid = Grid::new(width, height);
|
||||||
let mut physics = Physics::new();
|
let mut physics = Physics::new();
|
||||||
let mut rb = RenderBuffer::new(rw, rh);
|
let mut rb = RenderBuffer::new(rw, rh);
|
||||||
|
|
||||||
grid.fill_rect(440, 720, 144, 48, 1);
|
for ty in 0..4 {
|
||||||
grid.fill_rect(460, 700, 104, 20, 2);
|
for tx in 0..4 {
|
||||||
grid.fill_rect(470, 600, 84, 12, 5);
|
let ox = tx * 1024;
|
||||||
grid.fill_circle(490, 560, 20, 3);
|
let oy = ty * 768;
|
||||||
grid.fill_rect(430, 650, 50, 30, 4);
|
grid.fill_rect(ox, oy + 740, 1024, 28, 1);
|
||||||
grid.fill_rect(540, 650, 50, 30, 6);
|
grid.fill_rect(ox + 20, oy + 680, 80, 60, 1);
|
||||||
grid.fill_rect(500, 640, 10, 18, 11);
|
grid.fill_rect(ox + 30, oy + 640, 60, 40, 1);
|
||||||
grid.fill_rect(480, 630, 64, 24, 5);
|
grid.fill_rect(ox + 40, oy + 610, 40, 30, 1);
|
||||||
grid.fill_rect(480, 618, 64, 12, 9);
|
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_x = 400i32;
|
||||||
let cam_y = 600i32;
|
let cam_y = 630i32;
|
||||||
let zoom = 2.0f32;
|
let zoom = 2.0f32;
|
||||||
|
|
||||||
let mut frame = 0;
|
let mut frame = 0;
|
||||||
|
|||||||
+58
-1
@@ -415,21 +415,56 @@ impl Physics {
|
|||||||
if can_fall {
|
if can_fall {
|
||||||
let was_liquid = bprops.is_liquid;
|
let was_liquid = bprops.is_liquid;
|
||||||
grid.swap(x, y, x, y + 1);
|
grid.swap(x, y, x, y + 1);
|
||||||
|
let mi = grid.index(x, y + 1);
|
||||||
|
grid.flags[mi] |= crate::grid::FLAG_MOMENTUM;
|
||||||
if was_liquid {
|
if was_liquid {
|
||||||
|
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();
|
let pdir = self.rand_dir();
|
||||||
for dist in 1..=4i32 {
|
for dist in 3..=8i32 {
|
||||||
let sx = x as i32 + pdir * dist;
|
let sx = x as i32 + pdir * dist;
|
||||||
if !grid.in_bounds(sx, y as i32 + 1) { break; }
|
if !grid.in_bounds(sx, y as i32 + 1) { break; }
|
||||||
let sxu = sx as u32;
|
let sxu = sx as u32;
|
||||||
if grid.get(sxu, y + 1) == 0 {
|
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);
|
grid.swap(x, y + 1, sxu, y + 1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return;
|
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();
|
let dir = self.rand_dir();
|
||||||
for &d in &[dir, -dir] {
|
for &d in &[dir, -dir] {
|
||||||
let dx = x as i32 + d;
|
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) {
|
fn update_solid(&mut self, grid: &mut Grid, x: u32, y: u32, _mat: u8) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ pub struct RenderBuffer {
|
|||||||
pub height: u32,
|
pub height: u32,
|
||||||
light: Vec<u8>,
|
light: Vec<u8>,
|
||||||
block: Vec<u8>,
|
block: Vec<u8>,
|
||||||
|
zoom: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderBuffer {
|
impl RenderBuffer {
|
||||||
@@ -19,6 +20,7 @@ impl RenderBuffer {
|
|||||||
height,
|
height,
|
||||||
light: vec![0; lsize],
|
light: vec![0; lsize],
|
||||||
block: vec![0; lsize],
|
block: vec![0; lsize],
|
||||||
|
zoom: 1.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,6 +29,8 @@ impl RenderBuffer {
|
|||||||
let rh = self.height as i32;
|
let rh = self.height as i32;
|
||||||
let total = (rw * rh) as usize;
|
let total = (rw * rh) as usize;
|
||||||
|
|
||||||
|
self.zoom = zoom;
|
||||||
|
|
||||||
for i in 0..total {
|
for i in 0..total {
|
||||||
self.light[i] = 0;
|
self.light[i] = 0;
|
||||||
self.block[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 dir = _pass & 1;
|
||||||
let mut changed = false;
|
let mut changed = false;
|
||||||
if dir == 0 {
|
if dir == 0 {
|
||||||
@@ -157,7 +162,8 @@ impl RenderBuffer {
|
|||||||
let ni = (ny * rw + nx) as usize;
|
let ni = (ny * rw + nx) as usize;
|
||||||
let nlight = self.light[ni] as u32;
|
let nlight = self.light[ni] as u32;
|
||||||
if nlight <= 1 { continue; }
|
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 {
|
if nlight > falloff {
|
||||||
let incoming = nlight - falloff;
|
let incoming = nlight - falloff;
|
||||||
if incoming > best {
|
if incoming > best {
|
||||||
|
|||||||
@@ -12,4 +12,13 @@ export class Camera {
|
|||||||
setZoom(z: number) {
|
setZoom(z: number) {
|
||||||
this.targetZoom = Math.max(0.1, Math.min(10, z));
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+93
-23
@@ -3,8 +3,8 @@ import { Renderer } from "./renderer";
|
|||||||
import { Input } from "./input";
|
import { Input } from "./input";
|
||||||
import { Camera } from "./camera";
|
import { Camera } from "./camera";
|
||||||
|
|
||||||
const GRID_WIDTH = 1024;
|
const GRID_WIDTH = 4096;
|
||||||
const GRID_HEIGHT = 768;
|
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];
|
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);
|
const engine = await createEngine(GRID_WIDTH, GRID_HEIGHT);
|
||||||
|
|
||||||
engine.fillRect(0, 740, 1024, 28, M.STONE);
|
// === EPIC WORLD: tiled 16 copies of the scene ===
|
||||||
engine.fillRect(440, 720, 144, 20, M.WOOD);
|
function buildScene(ox: number, oy: number) {
|
||||||
engine.fillRect(460, 700, 104, 20, M.DIRT);
|
engine.fillRect(ox, oy + 740, 1024, 28, M.STONE);
|
||||||
engine.fillCircle(490, 560, 25, M.SAND);
|
engine.fillRect(ox + 20, oy + 680, 80, 60, M.STONE);
|
||||||
engine.fillCircle(520, 550, 20, M.SAND);
|
engine.fillRect(ox + 30, oy + 640, 60, 40, M.STONE);
|
||||||
engine.fillRect(430, 650, 40, 30, M.WATER);
|
engine.fillRect(ox + 40, oy + 610, 40, 30, M.STONE);
|
||||||
engine.fillRect(550, 650, 40, 25, M.LAVA);
|
engine.fillCircle(ox + 60, oy + 600, 15, M.DIRT);
|
||||||
engine.fillRect(505, 640, 10, 16, M.ICE);
|
engine.fillRect(ox + 50, oy + 660, 20, 20, M.DIRT);
|
||||||
engine.fillCircle(620, 580, 20, M.OIL);
|
engine.fillRect(ox, oy + 720, 120, 20, M.STONE);
|
||||||
engine.fillRect(480, 620, 64, 30, M.WOOD);
|
engine.fillRect(ox + 85, oy + 705, 35, 15, M.STONE);
|
||||||
engine.fillRect(480, 600, 64, 20, M.FIRE);
|
engine.fillRect(ox + 5, oy + 705, 60, 15, M.STONE);
|
||||||
engine.fillRect(300, 720, 40, 20, M.GLASS);
|
engine.fillCircle(ox + 60, oy + 712, 12, M.LAVA);
|
||||||
engine.fillCircle(200, 680, 18, M.SAND);
|
engine.fillCircle(ox + 70, oy + 708, 6, M.LAVA);
|
||||||
engine.fillRect(100, 700, 30, 40, M.WOOD);
|
engine.fillRect(ox + 60, oy + 695, 20, 10, M.DIRT);
|
||||||
engine.fillRect(750, 680, 60, 60, M.STONE);
|
engine.fillRect(ox + 350, oy + 680, 100, 60, M.STONE);
|
||||||
engine.fillCircle(780, 660, 15, M.LAVA);
|
engine.fillRect(ox + 365, oy + 640, 70, 40, M.STONE);
|
||||||
engine.fillRect(700, 720, 30, 20, M.ICE);
|
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 rw = engine.renderWidth();
|
||||||
const rh = engine.renderHeight();
|
const rh = engine.renderHeight();
|
||||||
|
|
||||||
camera.x = 512;
|
|
||||||
camera.y = 600;
|
|
||||||
|
|
||||||
function resize() {
|
function resize() {
|
||||||
const dpr = window.devicePixelRatio || 1;
|
const dpr = window.devicePixelRatio || 1;
|
||||||
renderer.resize(window.innerWidth, window.innerHeight, dpr);
|
renderer.resize(window.innerWidth, window.innerHeight, dpr);
|
||||||
@@ -58,6 +121,13 @@ async function main() {
|
|||||||
return { x: Math.floor(mx), y: Math.floor(my) };
|
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")!;
|
const panel = document.getElementById("panel")!;
|
||||||
function buildPanel() {
|
function buildPanel() {
|
||||||
panel.innerHTML = "";
|
panel.innerHTML = "";
|
||||||
@@ -122,7 +192,7 @@ async function main() {
|
|||||||
if (input.isDown("Equal")) camera.setZoom(camera.zoom + 2 * dt);
|
if (input.isDown("Equal")) camera.setZoom(camera.zoom + 2 * dt);
|
||||||
if (input.isDown("Minus")) camera.setZoom(camera.zoom - 2 * dt);
|
if (input.isDown("Minus")) camera.setZoom(camera.zoom - 2 * dt);
|
||||||
if (input.wheel !== 0) {
|
if (input.wheel !== 0) {
|
||||||
camera.setZoom(camera.zoom + input.wheel * 0.5);
|
camera.setZoom(camera.zoom + input.wheel * 0.2);
|
||||||
input.wheel = 0;
|
input.wheel = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user