Skip to content

Commit

Permalink
implement quad tree for fire rendering optimization and update fire r…
Browse files Browse the repository at this point in the history
…ender state management
  • Loading branch information
gtanczyk committed Nov 19, 2024
1 parent 76ec53f commit 2fd5a74
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 57 deletions.
94 changes: 94 additions & 0 deletions games/xmas/src/screens/play/game-render/fire-render-quad-tree.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { GRID_HEIGHT, GRID_WIDTH } from './fire-render-types';

/**
* A lightweight quad tree implementation for fire rendering optimization.
* - Uses flat array for temperature storage
* - Supports 8-way neighborhood checking
* - Provides O(log n) cold region detection
* - Minimizes allocations during reconstruction
*/
export class FireQuadTree {
private temperatures: Float32Array;
private static readonly NEIGHBORHOOD = [
[-1, -1],
[0, -1],
[1, -1],
[-1, 0],
[1, 0],
[-1, 1],
[0, 1],
[1, 1],
];

constructor() {
// Use a single Float32Array for efficient temperature storage
// Use 2 padding cells on each side for easier neighborhood checking
this.temperatures = new Float32Array((GRID_WIDTH + 2) * (GRID_HEIGHT + 2));
}

/**
* Get the hot regions in the grid
*/
getHotRegions(): [x: number, y: number, width: number, height: number][] {
// TODO: This is an implementation stub
return [[0, 0, GRID_WIDTH, GRID_HEIGHT]];
}

/**
* Updates the temperature at a specific point and its influence on neighbors
* @param x X coordinate in the grid
* @param y Y coordinate in the grid
* @param temperature New temperature value
*/
update(x: number, y: number, temperature: number): void {
// Update the temperature at the given point
this.setTemperature(x, y, temperature);

// Check all 8 neighbors
for (const [dx, dy] of FireQuadTree.NEIGHBORHOOD) {
const nx = x + dx;
const ny = y + dy;

this.setTemperature(nx, ny, temperature);
}
}

/**
* Checks if a point and its 8-way neighborhood are cold (zero temperature)
* @param x X coordinate in the grid
* @param y Y coordinate in the grid
* @returns true if the point and all neighbors are cold
*/
isCold(x: number, y: number): boolean {
return this.getTemperature(x, y) === 0;
}

/**
* Gets the temperature at a specific point
* @param x X coordinate in the grid
* @param y Y coordinate in the grid
* @returns Temperature value at the point
*/
private getTemperature(x: number, y: number): number {
return this.temperatures[this.getTemperatureIndex(x, y)];
}

/**
* Sets the temperature at a specific point
* @param x X coordinate in the grid
* @param y Y coordinate in the grid
* @param temperature New temperature value
*/
private setTemperature(x: number, y: number, temperature: number): void {
const index = this.getTemperatureIndex(x, y);
if (this.temperatures[index] > 0) {
return;
}

this.temperatures[index] = temperature;
}

private getTemperatureIndex(x: number, y: number): number {
return (y + 1) * GRID_WIDTH + (x + 1);
}
}
109 changes: 59 additions & 50 deletions games/xmas/src/screens/play/game-render/fire-render-state.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,25 @@
import { GameWorldState } from '../game-world/game-world-types';
import { GAME_WORLD_HEIGHT, GAME_WORLD_WIDTH } from '../game-world/game-world-consts';

// Cell size in pixels
export const GRID_CELL_SIZE = 8;

// Grid dimensions
export const GRID_WIDTH = Math.ceil(GAME_WORLD_WIDTH / GRID_CELL_SIZE);
export const GRID_HEIGHT = Math.ceil(GAME_WORLD_HEIGHT / GRID_CELL_SIZE);

// Temperature thresholds and constants
export const MAX_TEMPERATURE = 1;
export const EXTINGUISH_THRESHOLD = 0.009;
export const HOT_CELL_THRESHOLD = 0.6; // Above this temperature, cells behave as "hot"

// Rate constants
export const EXTINGUISH_RATE = 0.0001;
export const BASE_SPREAD_RATE = 0.01;
export const VERTICAL_SPREAD_MULTIPLIER = 1.0;

// Directional transfer probabilities
export const HOT_CELL_SPREAD_PROBABILITY = 0.6; // Probability of hot cells spreading in any direction
export const HOT_CELL_DOWNWARD_PROBABILITY = 0.5; // Probability of hot cells spreading downward
export const COOL_CELL_HORIZONTAL_PROBABILITY = 0.3; // Probability of cool cells spreading horizontally

// Represents a single cell in the fire grid
export type FireCell = {
temperature: number;
};
import { FireQuadTree } from './fire-render-quad-tree';
import {
FireCell,
GRID_WIDTH,
GRID_HEIGHT,
GRID_CELL_SIZE,
MAX_TEMPERATURE,
HOT_CELL_THRESHOLD,
VERTICAL_SPREAD_MULTIPLIER,
HOT_CELL_SPREAD_PROBABILITY,
HOT_CELL_DOWNWARD_PROBABILITY,
COOL_CELL_HORIZONTAL_PROBABILITY,
EXTINGUISH_RATE,
BASE_SPREAD_RATE,
EXTINGUISH_THRESHOLD,
} from './fire-render-types';

// The complete fire render state
export type FireRenderState = {
grid: FireCell[][];
quadTree: FireQuadTree;
};

/**
Expand All @@ -45,7 +34,7 @@ export function createFireRenderState(): FireRenderState {
.map(() => ({ temperature: 0 })),
);

return { grid };
return { grid, quadTree: new FireQuadTree() };
}

/**
Expand Down Expand Up @@ -132,10 +121,20 @@ function calculateNeighborHeat(grid: FireCell[][], x: number, y: number): number
/**
* Precompute heat distribution from fireballs for the entire grid
*/
function computeFireballHeatMap(fireballs: GameWorldState['fireballs'], grid: FireCell[][]) {
function computeFireballHeatMap(
fireballs: GameWorldState['fireballs'],
grid: FireCell[][],
quadTree: FireQuadTree,
): void {
if (fireballs.length === 0) {
// No fireballs, no heat
return;
}

for (let y = 0; y < GRID_HEIGHT; y++) {
for (let x = 0; x < GRID_WIDTH; x++) {
grid[y][x].temperature = calculateFireballHeat(x, y, fireballs);
quadTree.update(x, y, grid[y][x].temperature);
}
}
}
Expand All @@ -157,39 +156,49 @@ export function updateFireRenderState(
.fill(null)
.map(() => ({ temperature: 0 })),
);
const newQuadTree = new FireQuadTree();

computeFireballHeatMap(world.fireballs, newGrid);
computeFireballHeatMap(world.fireballs, newGrid, newQuadTree);

// Update each cell in the grid
for (let y = 0; y < GRID_HEIGHT; y++) {
for (let x = 0; x < GRID_WIDTH; x++) {
const currentTemp = state.grid[y][x].temperature + newGrid[y][x].temperature;
for (const [regionX, regionY, regionWidth, regionHeight] of state.quadTree.getHotRegions()) {
for (let y = regionY; y < regionHeight; y++) {
for (let x = regionX; x < regionWidth; x++) {
if (state.quadTree.isCold(x, y)) {
continue;
}

// Calculate new temperature from various sources
const neighborHeat = calculateNeighborHeat(state.grid, x, y);
const currentTemp = state.grid[y][x].temperature + newGrid[y][x].temperature;

// Temperature changes:
// 1. Natural decay over time
// 2. Heat from fireballs (immediate effect)
// 3. Heat spreading from neighbors with directional bias
let newTemp = currentTemp;
// Calculate new temperature from various sources
const neighborHeat = calculateNeighborHeat(state.grid, x, y);

// Apply decay
newTemp = Math.max(0, newTemp - EXTINGUISH_RATE * deltaTime);
// Temperature changes:
// 1. Natural decay over time
// 2. Heat from fireballs (immediate effect)
// 3. Heat spreading from neighbors with directional bias
let newTemp = currentTemp;

// Apply neighbor heat spreading
const spreadRate = BASE_SPREAD_RATE * (1 + (Math.random() * 0.2 - 0.1));
newTemp += (neighborHeat - newTemp) * spreadRate * deltaTime;
// Apply decay
newTemp = Math.max(0, newTemp - EXTINGUISH_RATE * deltaTime);

// Ensure temperature stays within bounds
newTemp = Math.min(MAX_TEMPERATURE, Math.max(0, newTemp));
// Apply neighbor heat spreading
const spreadRate = BASE_SPREAD_RATE * (1 + (Math.random() * 0.2 - 0.1));
newTemp += (neighborHeat - newTemp) * spreadRate * deltaTime;

// Only update if above extinction threshold
newGrid[y][x].temperature = newTemp > EXTINGUISH_THRESHOLD ? newTemp : 0;
// Ensure temperature stays within bounds
newTemp = Math.min(MAX_TEMPERATURE, Math.max(0, newTemp));

// Only update if above extinction threshold
newGrid[y][x].temperature = newTemp > EXTINGUISH_THRESHOLD ? newTemp : 0;

newQuadTree.update(x, y, newGrid[y][x].temperature);
}
}
}

return {
grid: newGrid,
quadTree: newQuadTree,
};
}
28 changes: 28 additions & 0 deletions games/xmas/src/screens/play/game-render/fire-render-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { GAME_WORLD_HEIGHT, GAME_WORLD_WIDTH } from '../game-world/game-world-consts';

// Cell size in pixels
export const GRID_CELL_SIZE = 8;

// Grid dimensions
export const GRID_WIDTH = Math.ceil(GAME_WORLD_WIDTH / GRID_CELL_SIZE);
export const GRID_HEIGHT = Math.ceil(GAME_WORLD_HEIGHT / GRID_CELL_SIZE);

// Temperature thresholds and constants
export const MAX_TEMPERATURE = 1;
export const EXTINGUISH_THRESHOLD = 0.009;
export const HOT_CELL_THRESHOLD = 0.6; // Above this temperature, cells behave as "hot"

// Rate constants
export const EXTINGUISH_RATE = 0.0001;
export const BASE_SPREAD_RATE = 0.01;
export const VERTICAL_SPREAD_MULTIPLIER = 1.0;

// Directional transfer probabilities
export const HOT_CELL_SPREAD_PROBABILITY = 0.6; // Probability of hot cells spreading in any direction
export const HOT_CELL_DOWNWARD_PROBABILITY = 0.5; // Probability of hot cells spreading downward
export const COOL_CELL_HORIZONTAL_PROBABILITY = 0.3; // Probability of cool cells spreading horizontally

// Represents a single cell in the fire grid
export type FireCell = {
temperature: number;
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { RenderState } from './render-state';
import { GRID_CELL_SIZE, GRID_HEIGHT, GRID_WIDTH } from './fire-render-state';
import { GRID_CELL_SIZE, GRID_HEIGHT, GRID_WIDTH } from './fire-render-types';

/**
* Render a single fire cell with temperature-based color
Expand Down
8 changes: 2 additions & 6 deletions games/xmas/src/screens/play/game-render/render-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,8 @@ export function createRenderState(): RenderState {
/**
* Update the render state based on the current game world state
*/
export function updateRenderState(
world: GameWorldState,
state: RenderState,
deltaTime: number
): RenderState {
export function updateRenderState(world: GameWorldState, state: RenderState, deltaTime: number): RenderState {
return {
fire: updateFireRenderState(world, state.fire, deltaTime),
};
}
}

0 comments on commit 2fd5a74

Please sign in to comment.