Initial commit: AtomicEngine v0.2

- Rust/WASM physics engine (grid, material, physics, render_buffer)
- 16 material types (stone, sand, water, wood, fire, lava, glass, ice, oil, acid, steam, spark, smoke + player placeholder)
- Fire system (fuel-based, flammability-scaled consumption, spread, smoke/sparks)
- Lighting system (emission, flood-fill propagation, glow, reflection)
- Per-material pixel texturing (wood grain, dirt specks, stone noise, flame variation)
- WebGL2 renderer with camera (WASD move, mouse wheel zoom)
- Native x86 debug binary (identical dimensions, FPS counter)
- Large world 1024x768 with active-region simulation (camera + 200px margin)
- UI material palette + FPS display
This commit is contained in:
2026-07-09 11:13:39 +02:00
commit 1bce9da0c6
21 changed files with 3261 additions and 0 deletions
+153
View File
@@ -0,0 +1,153 @@
import { createEngine, M, MATERIAL_NAMES, MATERIAL_COLORS } from "./engine";
import { Renderer } from "./renderer";
import { Input } from "./input";
import { Camera } from "./camera";
const GRID_WIDTH = 1024;
const GRID_HEIGHT = 768;
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];
async function main() {
const canvas = document.getElementById("canvas") as HTMLCanvasElement;
const renderer = new Renderer(canvas);
const input = new Input();
const camera = new Camera();
camera.x = GRID_WIDTH / 2;
camera.y = GRID_HEIGHT / 2;
camera.zoom = 3;
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);
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);
}
window.addEventListener("resize", resize);
resize();
let paintMat: number = M.SAND;
function screenToGrid(sx: number, sy: number) {
const mx = (sx / window.innerWidth - 0.5) * rw / camera.zoom + camera.x;
const my = (sy / window.innerHeight - 0.5) * rh / camera.zoom + camera.y;
return { x: Math.floor(mx), y: Math.floor(my) };
}
const panel = document.getElementById("panel")!;
function buildPanel() {
panel.innerHTML = "";
PAINT_MATERIALS.forEach((mat, idx) => {
const div = document.createElement("div");
div.className = "mat" + (mat === paintMat ? " active" : "");
div.dataset.mat = String(mat);
div.innerHTML = `<div class="swatch" style="background:${MATERIAL_COLORS[mat]}"></div><div class="label"><span class="key">${idx}</span> ${MATERIAL_NAMES[mat]}</div>`;
div.addEventListener("click", () => selectMat(mat));
panel.appendChild(div);
});
}
function selectMat(mat: number) {
paintMat = mat;
document.querySelectorAll(".mat").forEach((el) => {
el.classList.toggle("active", (el as HTMLElement).dataset.mat === String(mat));
});
}
buildPanel();
window.addEventListener("keydown", (e) => {
const idx = parseInt(e.key);
if (idx >= 0 && idx < PAINT_MATERIALS.length) {
selectMat(PAINT_MATERIALS[idx]);
}
});
let lastTime = performance.now();
let simAccum = 0;
const simStep = 1 / 60;
let fpsFrames = 0;
let fpsLast = performance.now();
const fpsEl = document.getElementById("fps")!;
function loop(now: number) {
const dt = Math.min((now - lastTime) / 1000, 0.1);
lastTime = now;
fpsFrames++;
if (now - fpsLast >= 500) {
const fps = Math.round(fpsFrames / ((now - fpsLast) / 1000));
fpsEl.textContent = `${fps} FPS`;
fpsFrames = 0;
fpsLast = now;
}
simAccum += dt;
while (simAccum >= simStep) {
simAccum -= simStep;
engine.simulate(
Math.round(camera.x),
Math.round(camera.y),
camera.zoom,
);
}
const speed = 200;
if (input.isDown("KeyA") || input.isDown("ArrowLeft")) camera.x -= speed * dt;
if (input.isDown("KeyD") || input.isDown("ArrowRight")) camera.x += speed * dt;
if (input.isDown("KeyW") || input.isDown("ArrowUp")) camera.y -= speed * dt;
if (input.isDown("KeyS") || input.isDown("ArrowDown")) camera.y += speed * dt;
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);
input.wheel = 0;
}
if (input.mouseDown) {
const g = screenToGrid(input.mouseX, input.mouseY);
engine.fillCircle(g.x, g.y, 6, paintMat);
}
camera.update(dt);
engine.updateRender(
Math.round(camera.x),
Math.round(camera.y),
camera.zoom,
);
const ptr = engine.renderBufferPtr();
const len = engine.renderBufferLen();
const buf = new Uint8Array(engine.memory.buffer, ptr, len).slice(0);
renderer.uploadTexture(buf, rw, rh);
renderer.render();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
}
main();