fix: Add missing dirtyChunks variable and update rendering logic

This commit is contained in:
Kacper Kostka (aider) 2025-04-05 15:37:55 +02:00
parent 60a1757ab1
commit 15fb106246
4 changed files with 125 additions and 54 deletions

View File

@ -4,29 +4,37 @@ function updateSand(x, y) {
if (getPixel(x, y + 1) === EMPTY) { if (getPixel(x, y + 1) === EMPTY) {
setPixel(x, y, EMPTY); setPixel(x, y, EMPTY);
setPixel(x, y + 1, SAND); setPixel(x, y + 1, SAND);
return true;
} }
// Try to move down-left or down-right // Try to move down-left or down-right
else if (getPixel(x - 1, y + 1) === EMPTY) { else if (getPixel(x - 1, y + 1) === EMPTY) {
setPixel(x, y, EMPTY); setPixel(x, y, EMPTY);
setPixel(x - 1, y + 1, SAND); setPixel(x - 1, y + 1, SAND);
return true;
} }
else if (getPixel(x + 1, y + 1) === EMPTY) { else if (getPixel(x + 1, y + 1) === EMPTY) {
setPixel(x, y, EMPTY); setPixel(x, y, EMPTY);
setPixel(x + 1, y + 1, SAND); setPixel(x + 1, y + 1, SAND);
return true;
} }
// Sand can displace water // Sand can displace water
else if (getPixel(x, y + 1) === WATER) { else if (getPixel(x, y + 1) === WATER) {
setPixel(x, y, WATER); setPixel(x, y, WATER);
setPixel(x, y + 1, SAND); setPixel(x, y + 1, SAND);
return true;
} }
return false;
} }
function updateWater(x, y) { function updateWater(x, y) {
let modified = false;
// Update water color dynamically // Update water color dynamically
const metadata = getMetadata(x, y); const metadata = getMetadata(x, y);
if (metadata) { if (metadata) {
if (metadata.waterColorTimer === undefined) { if (metadata.waterColorTimer === undefined) {
metadata.waterColorTimer = 0; metadata.waterColorTimer = 0;
modified = true;
} }
metadata.waterColorTimer++; metadata.waterColorTimer++;
@ -35,6 +43,7 @@ function updateWater(x, y) {
if (metadata.waterColorTimer > 20 && Math.random() < 0.1) { if (metadata.waterColorTimer > 20 && Math.random() < 0.1) {
metadata.colorIndex = Math.floor(Math.random() * 10); metadata.colorIndex = Math.floor(Math.random() * 10);
metadata.waterColorTimer = 0; metadata.waterColorTimer = 0;
modified = true;
} }
setMetadata(x, y, metadata); setMetadata(x, y, metadata);
@ -45,17 +54,20 @@ function updateWater(x, y) {
setPixel(x, y, EMPTY); setPixel(x, y, EMPTY);
setPixel(x, y + 1, WATER); setPixel(x, y + 1, WATER);
moveMetadata(x, y, x, y + 1); moveMetadata(x, y, x, y + 1);
return true;
} }
// Try to move down-left or down-right // Try to move down-left or down-right
else if (getPixel(x - 1, y + 1) === EMPTY) { else if (getPixel(x - 1, y + 1) === EMPTY) {
setPixel(x, y, EMPTY); setPixel(x, y, EMPTY);
setPixel(x - 1, y + 1, WATER); setPixel(x - 1, y + 1, WATER);
moveMetadata(x, y, x - 1, y + 1); moveMetadata(x, y, x - 1, y + 1);
return true;
} }
else if (getPixel(x + 1, y + 1) === EMPTY) { else if (getPixel(x + 1, y + 1) === EMPTY) {
setPixel(x, y, EMPTY); setPixel(x, y, EMPTY);
setPixel(x + 1, y + 1, WATER); setPixel(x + 1, y + 1, WATER);
moveMetadata(x, y, x + 1, y + 1); moveMetadata(x, y, x + 1, y + 1);
return true;
} }
// Try to spread horizontally // Try to spread horizontally
else { else {
@ -67,21 +79,25 @@ function updateWater(x, y) {
if (goLeft && getPixel(x - 1, y) === EMPTY) { if (goLeft && getPixel(x - 1, y) === EMPTY) {
setPixel(x, y, EMPTY); setPixel(x, y, EMPTY);
setPixel(x - 1, y, WATER); setPixel(x - 1, y, WATER);
moved = true; moveMetadata(x, y, x - 1, y);
return true;
} else if (!goLeft && getPixel(x + 1, y) === EMPTY) { } else if (!goLeft && getPixel(x + 1, y) === EMPTY) {
setPixel(x, y, EMPTY); setPixel(x, y, EMPTY);
setPixel(x + 1, y, WATER); setPixel(x + 1, y, WATER);
moved = true; moveMetadata(x, y, x + 1, y);
return true;
} }
// Try the other direction if first failed // Try the other direction if first failed
else if (!goLeft && getPixel(x - 1, y) === EMPTY) { else if (!goLeft && getPixel(x - 1, y) === EMPTY) {
setPixel(x, y, EMPTY); setPixel(x, y, EMPTY);
setPixel(x - 1, y, WATER); setPixel(x - 1, y, WATER);
moved = true; moveMetadata(x, y, x - 1, y);
return true;
} else if (goLeft && getPixel(x + 1, y) === EMPTY) { } else if (goLeft && getPixel(x + 1, y) === EMPTY) {
setPixel(x, y, EMPTY); setPixel(x, y, EMPTY);
setPixel(x + 1, y, WATER); setPixel(x + 1, y, WATER);
moved = true; moveMetadata(x, y, x + 1, y);
return true;
} }
} }
@ -97,15 +113,18 @@ function updateWater(x, y) {
if (getPixel(x + dir.dx, y + dir.dy) === FIRE) { if (getPixel(x + dir.dx, y + dir.dy) === FIRE) {
setPixel(x + dir.dx, y + dir.dy, EMPTY); setPixel(x + dir.dx, y + dir.dy, EMPTY);
removeMetadata(x + dir.dx, y + dir.dy); removeMetadata(x + dir.dx, y + dir.dy);
return true;
} else if (getPixel(x + dir.dx, y + dir.dy) === LAVA) { } else if (getPixel(x + dir.dx, y + dir.dy) === LAVA) {
// Water turns lava into stone // Water turns lava into stone
setPixel(x + dir.dx, y + dir.dy, STONE); setPixel(x + dir.dx, y + dir.dy, STONE);
removeMetadata(x + dir.dx, y + dir.dy); removeMetadata(x + dir.dx, y + dir.dy);
// Water is consumed in the process // Water is consumed in the process
setPixel(x, y, EMPTY); setPixel(x, y, EMPTY);
return; return true;
} }
} }
return modified;
} }
function updateDirt(x, y) { function updateDirt(x, y) {
@ -113,25 +132,30 @@ function updateDirt(x, y) {
if (getPixel(x, y + 1) === EMPTY) { if (getPixel(x, y + 1) === EMPTY) {
setPixel(x, y, EMPTY); setPixel(x, y, EMPTY);
setPixel(x, y + 1, DIRT); setPixel(x, y + 1, DIRT);
return true;
} }
// Try to move down-left or down-right // Try to move down-left or down-right
else if (getPixel(x - 1, y + 1) === EMPTY) { else if (getPixel(x - 1, y + 1) === EMPTY) {
setPixel(x, y, EMPTY); setPixel(x, y, EMPTY);
setPixel(x - 1, y + 1, DIRT); setPixel(x - 1, y + 1, DIRT);
return true;
} }
else if (getPixel(x + 1, y + 1) === EMPTY) { else if (getPixel(x + 1, y + 1) === EMPTY) {
setPixel(x, y, EMPTY); setPixel(x, y, EMPTY);
setPixel(x + 1, y + 1, DIRT); setPixel(x + 1, y + 1, DIRT);
return true;
} }
// Dirt can displace water // Dirt can displace water
else if (getPixel(x, y + 1) === WATER) { else if (getPixel(x, y + 1) === WATER) {
setPixel(x, y, WATER); setPixel(x, y, WATER);
setPixel(x, y + 1, DIRT); setPixel(x, y + 1, DIRT);
return true;
} }
// Dirt can turn into grass if exposed to air above // Dirt can turn into grass if exposed to air above
if (getPixel(x, y - 1) === EMPTY && Math.random() < 0.001) { if (getPixel(x, y - 1) === EMPTY && Math.random() < 0.001) {
setPixel(x, y, GRASS); setPixel(x, y, GRASS);
return true;
} }
// Dirt can randomly spawn seeds if exposed to air above // Dirt can randomly spawn seeds if exposed to air above
@ -140,12 +164,17 @@ function updateDirt(x, y) {
const seedRoll = Math.random(); const seedRoll = Math.random();
if (seedRoll < 0.0002) { // Grass blade seed (most common) if (seedRoll < 0.0002) { // Grass blade seed (most common)
setPixel(x, y - 1, SEED); setPixel(x, y - 1, SEED);
return true;
} else if (seedRoll < 0.00025) { // Flower seed (less common) } else if (seedRoll < 0.00025) { // Flower seed (less common)
setPixel(x, y - 1, SEED); setPixel(x, y - 1, SEED);
// Mark this seed as a flower seed (will be handled in updateSeed) // Mark this seed as a flower seed (will be handled in updateSeed)
setMetadata(x, y - 1, { type: 'flower' }); setMetadata(x, y - 1, { type: 'flower' });
return true;
} else if (seedRoll < 0.00026) { // Tree seed (rare) } else if (seedRoll < 0.00026) { // Tree seed (rare)
setPixel(x, y - 1, TREE_SEED); setPixel(x, y - 1, TREE_SEED);
return true;
} }
} }
return false;
} }

View File

@ -65,8 +65,8 @@ function simulationLoop(timestamp) {
fps = Math.round(1000 / deltaTime); fps = Math.round(1000 / deltaTime);
document.getElementById('fps').textContent = `FPS: ${fps}`; document.getElementById('fps').textContent = `FPS: ${fps}`;
// Update physics // Update physics with timestamp for rate limiting
updatePhysics(); updatePhysics(timestamp);
// Render // Render
render(); render();

View File

@ -1,5 +1,12 @@
// Physics simulation functions // Physics simulation functions
function updatePhysics() { function updatePhysics(timestamp) {
// Check if we should update physics based on the update rate
if (timestamp && lastPhysicsTime && timestamp - lastPhysicsTime < physicsUpdateRate) {
return;
}
lastPhysicsTime = timestamp || 0;
// Get visible chunks // Get visible chunks
const visibleChunks = getVisibleChunks(); const visibleChunks = getVisibleChunks();
@ -12,6 +19,7 @@ function updatePhysics() {
if (!isVisible) continue; if (!isVisible) continue;
const chunk = getOrCreateChunk(chunkX, chunkY); const chunk = getOrCreateChunk(chunkX, chunkY);
let chunkModified = false;
// Process from bottom to top, right to left for correct gravity simulation // Process from bottom to top, right to left for correct gravity simulation
for (let y = CHUNK_SIZE - 1; y >= 0; y--) { for (let y = CHUNK_SIZE - 1; y >= 0; y--) {
@ -29,28 +37,42 @@ function updatePhysics() {
const worldX = chunkX * CHUNK_SIZE + x; const worldX = chunkX * CHUNK_SIZE + x;
const worldY = chunkY * CHUNK_SIZE + y; const worldY = chunkY * CHUNK_SIZE + y;
if (type === SAND) { // Use a lookup table for faster element updates
updateSand(worldX, worldY); const updateFunctions = {
} else if (type === WATER) { [SAND]: updateSand,
updateWater(worldX, worldY); [WATER]: updateWater,
} else if (type === DIRT) { [DIRT]: updateDirt,
updateDirt(worldX, worldY); [GRASS]: updateGrass,
} else if (type === GRASS) { [SEED]: updateSeed,
updateGrass(worldX, worldY); [GRASS_BLADE]: updateGrassBlade,
} else if (type === SEED) { [FLOWER]: updateFlower,
updateSeed(worldX, worldY); [TREE_SEED]: updateTreeSeed,
} else if (type === GRASS_BLADE) { [FIRE]: updateFire,
updateGrassBlade(worldX, worldY); [LAVA]: updateLava
} else if (type === FLOWER) { };
updateFlower(worldX, worldY);
} else if (type === TREE_SEED) { const updateFunction = updateFunctions[type];
updateTreeSeed(worldX, worldY); if (updateFunction) {
} else if (type === FIRE) { const wasModified = updateFunction(worldX, worldY);
updateFire(worldX, worldY); if (wasModified) {
} else if (type === LAVA) { chunkModified = true;
updateLava(worldX, worldY); }
} }
} }
} }
// Mark chunk as dirty if it was modified
if (chunkModified) {
dirtyChunks.add(getChunkKey(chunkX, chunkY));
}
}
// Adaptive physics rate based on FPS
if (fps < 30 && physicsUpdateRate < 32) {
// If FPS is low, update physics less frequently
physicsUpdateRate = Math.min(32, physicsUpdateRate + 2);
} else if (fps > 50 && physicsUpdateRate > 16) {
// If FPS is high, update physics more frequently
physicsUpdateRate = Math.max(16, physicsUpdateRate - 2);
} }
} }

View File

@ -6,12 +6,19 @@ let worldOffsetYBeforeDrag = 0;
let chunks = new Map(); // Map to store chunks with key "x,y" let chunks = new Map(); // Map to store chunks with key "x,y"
let metadata = new Map(); // Map to store metadata for pixels let metadata = new Map(); // Map to store metadata for pixels
let generatedChunks = new Set(); // Set to track which chunks have been generated let generatedChunks = new Set(); // Set to track which chunks have been generated
let dirtyChunks = new Set(); // Set to track which chunks need rendering
let lastPhysicsTime = 0; // Last time physics was updated
let physicsUpdateRate = 16; // Update physics every 16ms (approx 60fps)
let worldMoved = false; // Track if the world has moved for rendering
function moveWorld(dx, dy) { function moveWorld(dx, dy) {
worldOffsetX += dx; worldOffsetX += dx;
worldOffsetY += dy; worldOffsetY += dy;
updateCoordinatesDisplay(); updateCoordinatesDisplay();
// Mark that the world has moved for rendering
worldMoved = true;
// Generate terrain for chunks around the current view // Generate terrain for chunks around the current view
generateChunksAroundPlayer(); generateChunksAroundPlayer();
} }
@ -383,32 +390,38 @@ function setPixel(worldX, worldY, type) {
const chunk = getOrCreateChunk(chunkX, chunkY); const chunk = getOrCreateChunk(chunkX, chunkY);
const index = localY * CHUNK_SIZE + localX; const index = localY * CHUNK_SIZE + localX;
chunk[index] = type; // Only update if the pixel type is changing
if (chunk[index] !== type) {
chunk[index] = type;
// Assign random color index for natural elements // Mark chunk as dirty for rendering
if (type === DIRT || type === GRASS || type === STONE || type === WOOD || type === LEAF) { dirtyChunks.add(getChunkKey(chunkX, chunkY));
const colorIndex = Math.floor(Math.random() * 10);
setMetadata(worldX, worldY, { ...getMetadata(worldX, worldY) || {}, colorIndex }); // Assign random color index for natural elements
} if (type === DIRT || type === GRASS || type === STONE || type === WOOD || type === LEAF) {
else if (type === WATER) { const colorIndex = Math.floor(Math.random() * 10);
const colorIndex = Math.floor(Math.random() * 10); setMetadata(worldX, worldY, { ...getMetadata(worldX, worldY) || {}, colorIndex });
setMetadata(worldX, worldY, { ...getMetadata(worldX, worldY) || {}, colorIndex, waterColorTimer: 0 }); }
} else if (type === WATER) {
else if (type === TREE_SEED) { const colorIndex = Math.floor(Math.random() * 10);
// Initialize tree seed metadata setMetadata(worldX, worldY, { ...getMetadata(worldX, worldY) || {}, colorIndex, waterColorTimer: 0 });
setMetadata(worldX, worldY, { }
age: Math.floor(Math.random() * 50), // Random initial age else if (type === TREE_SEED) {
growthStage: 0, // Initialize tree seed metadata
type: Math.random() < 0.8 ? 'oak' : 'pine' // 80% oak, 20% pine setMetadata(worldX, worldY, {
}); age: Math.floor(Math.random() * 50), // Random initial age
} growthStage: 0,
else if (type === SEED) { type: Math.random() < 0.8 ? 'oak' : 'pine' // 80% oak, 20% pine
// Initialize flower seed metadata });
setMetadata(worldX, worldY, { }
age: Math.floor(Math.random() * 30), else if (type === SEED) {
growthStage: 0, // Initialize flower seed metadata
flowerType: Math.floor(Math.random() * 5) // Different flower types setMetadata(worldX, worldY, {
}); age: Math.floor(Math.random() * 30),
growthStage: 0,
flowerType: Math.floor(Math.random() * 5) // Different flower types
});
}
} }
} }
@ -448,6 +461,13 @@ function moveMetadata(fromX, fromY, toX, toY) {
if (data) { if (data) {
setMetadata(toX, toY, data); setMetadata(toX, toY, data);
removeMetadata(fromX, fromY); removeMetadata(fromX, fromY);
// Mark chunks as dirty for rendering
const { chunkX: fromChunkX, chunkY: fromChunkY } = getChunkCoordinates(fromX, fromY);
const { chunkX: toChunkX, chunkY: toChunkY } = getChunkCoordinates(toX, toY);
dirtyChunks.add(getChunkKey(fromChunkX, fromChunkY));
dirtyChunks.add(getChunkKey(toChunkX, toChunkY));
} }
} }