sandsim/js/elements/fire.js
Kacper Kostka (aider) 60a1757ab1 feat: Optimize rendering and physics with dirty chunk tracking and adaptive update rates
This commit introduces several performance optimizations:
- Implement chunk-based dirty rendering
- Add adaptive physics update rates
- Return modification status from element update functions
- Reduce unnecessary rendering and physics calculations
- Track world movement for efficient re-rendering

The key changes include:
1. Adding `dirtyChunks` and `worldMoved` tracking
2. Modifying element update functions to return modification status
3. Implementing adaptive physics update rates based on FPS
4. Skipping rendering for unchanged chunks
5. Reducing computational overhead in physics and rendering loops

These optimizations should significantly improve the simulation's performance, especially with large numbers of elements.
2025-04-05 15:35:19 +02:00

189 lines
6.2 KiB
JavaScript

// Fire and lava element behaviors
let fireUpdateCounter = 0;
function updateFire(x, y) {
let modified = false;
const metadata = getMetadata(x, y);
if (!metadata) {
// Initialize metadata if it doesn't exist
setMetadata(x, y, {
lifetime: 100 + Math.floor(Math.random() * 100),
colorIndex: Math.floor(Math.random() * FIRE_COLORS.length)
});
return true;
}
// Decrease lifetime
metadata.lifetime--;
modified = true;
// Randomly change color for flickering effect
if (Math.random() < 0.2) {
metadata.colorIndex = Math.floor(Math.random() * FIRE_COLORS.length);
modified = true;
}
// Update metadata
setMetadata(x, y, metadata);
// Fire rises upward occasionally
if (Math.random() < 0.3 && getPixel(x, y - 1) === EMPTY) {
setPixel(x, y, EMPTY);
setPixel(x, y - 1, FIRE);
moveMetadata(x, y, x, y - 1);
return true;
}
// Fire can also move slightly to the sides
if (Math.random() < 0.1) {
const direction = Math.random() > 0.5 ? 1 : -1;
if (getPixel(x + direction, y - 1) === EMPTY) {
setPixel(x, y, EMPTY);
setPixel(x + direction, y - 1, FIRE);
moveMetadata(x, y, x + direction, y - 1);
return true;
}
}
// Fire spreads to nearby flammable materials (less frequently to reduce performance impact)
if (fireUpdateCounter % 3 === 0 && Math.random() < 0.3) {
const directions = [
{dx: -1, dy: 0}, {dx: 1, dy: 0},
{dx: 0, dy: -1}, {dx: 0, dy: 1},
{dx: -1, dy: -1}, {dx: 1, dy: -1},
{dx: -1, dy: 1}, {dx: 1, dy: 1}
];
const dir = directions[Math.floor(Math.random() * directions.length)];
const nearbyType = getPixel(x + dir.dx, y + dir.dy);
if (FLAMMABLE_MATERIALS.includes(nearbyType)) {
setPixel(x + dir.dx, y + dir.dy, FIRE);
setMetadata(x + dir.dx, y + dir.dy, {
lifetime: 100 + Math.floor(Math.random() * 100),
colorIndex: Math.floor(Math.random() * FIRE_COLORS.length)
});
modified = true;
}
}
// Fire burns out after its lifetime
if (metadata.lifetime <= 0) {
setPixel(x, y, EMPTY);
removeMetadata(x, y);
return true;
}
return modified;
}
function updateLava(x, y) {
let modified = false;
const metadata = getMetadata(x, y);
if (!metadata) {
// Initialize metadata if it doesn't exist
setMetadata(x, y, {
colorIndex: Math.floor(Math.random() * LAVA_COLORS.length)
});
modified = true;
} else {
// Randomly change color for flowing effect
if (Math.random() < 0.1) {
metadata.colorIndex = Math.floor(Math.random() * LAVA_COLORS.length);
setMetadata(x, y, metadata);
modified = true;
}
}
// Lava moves slower than water
if (Math.random() < 0.7) {
// Try to move down
if (getPixel(x, y + 1) === EMPTY) {
setPixel(x, y, EMPTY);
setPixel(x, y + 1, LAVA);
moveMetadata(x, y, x, y + 1);
return true;
}
// Try to move down-left or down-right
else if (getPixel(x - 1, y + 1) === EMPTY) {
setPixel(x, y, EMPTY);
setPixel(x - 1, y + 1, LAVA);
moveMetadata(x, y, x - 1, y + 1);
return true;
}
else if (getPixel(x + 1, y + 1) === EMPTY) {
setPixel(x, y, EMPTY);
setPixel(x + 1, y + 1, LAVA);
moveMetadata(x, y, x + 1, y + 1);
return true;
}
// Try to spread horizontally (slower than water)
else if (Math.random() < 0.3) {
// Randomly choose direction first
const goLeft = Math.random() > 0.5;
if (goLeft && getPixel(x - 1, y) === EMPTY) {
setPixel(x, y, EMPTY);
setPixel(x - 1, y, LAVA);
moveMetadata(x, y, x - 1, y);
return true;
} else if (!goLeft && getPixel(x + 1, y) === EMPTY) {
setPixel(x, y, EMPTY);
setPixel(x + 1, y, LAVA);
moveMetadata(x, y, x + 1, y);
return true;
}
// Try the other direction if first failed
else if (!goLeft && getPixel(x - 1, y) === EMPTY) {
setPixel(x, y, EMPTY);
setPixel(x - 1, y, LAVA);
moveMetadata(x, y, x - 1, y);
return true;
} else if (goLeft && getPixel(x + 1, y) === EMPTY) {
setPixel(x, y, EMPTY);
setPixel(x + 1, y, LAVA);
moveMetadata(x, y, x + 1, y);
return true;
}
}
}
// Lava sets nearby flammable materials on fire
const directions = [
{dx: -1, dy: 0}, {dx: 1, dy: 0},
{dx: 0, dy: -1}, {dx: 0, dy: 1},
{dx: -1, dy: -1}, {dx: 1, dy: -1},
{dx: -1, dy: 1}, {dx: 1, dy: 1}
];
for (const dir of directions) {
const nearbyType = getPixel(x + dir.dx, y + dir.dy);
// Set flammable materials on fire
if (FLAMMABLE_MATERIALS.includes(nearbyType)) {
setPixel(x + dir.dx, y + dir.dy, FIRE);
setMetadata(x + dir.dx, y + dir.dy, {
lifetime: 100 + Math.floor(Math.random() * 100),
colorIndex: Math.floor(Math.random() * FIRE_COLORS.length)
});
modified = true;
}
// Lava can melt sand into glass (stone)
else if (nearbyType === SAND && Math.random() < 0.05) {
setPixel(x + dir.dx, y + dir.dy, STONE);
modified = true;
}
// Lava can burn dirt
else if (nearbyType === DIRT && Math.random() < 0.02) {
setPixel(x + dir.dx, y + dir.dy, EMPTY);
modified = true;
}
}
return modified;
}