Skip to content

Commit 9604ed5

Browse files
Merge pull request #4 from TimWillebrands/add-unionfind-room-collision-mode
2 parents aed5efd + b4f35de commit 9604ed5

16 files changed

Lines changed: 1079 additions & 160 deletions

Cargo.lock

Lines changed: 164 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "bresenham-lighting-engine"
3-
version = "0.2.6"
3+
version = "0.2.7"
44
edition = "2021"
55
description = "A CPU-based 2D lighting engine using Bresenham's line algorithm with fast native collision detection"
66
license = "ISC"
@@ -26,6 +26,9 @@ features = [
2626
"console",
2727
]
2828

29+
js-sys = "0.3.69"
30+
2931
[dev-dependencies]
32+
wasm-bindgen-test = "0.3.42"
3033
image = "0.24"
3134
imageproc = "0.23"
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Hybrid Room-Based and Pixel-Based Collision Detection
2+
3+
- Status: accepted
4+
- Date: 2025-08-03
5+
6+
Technical Story: We need to support lighting and collision in environments with distinct rooms. The existing pixel-based collision system is inefficient for this, and a more structured approach is required to enhance performance and enable new features.
7+
8+
## Context and Problem Statement
9+
10+
As outlined in [ADR-0004](0004-rust-native-collision-detection.md), we have a performant rust-native collision system. However, it is based on either a simple tile grid or a pixel bitmap. The pixel-based approach, while precise, is inefficient for large, room-based maps, as it requires checking every point along a ray. It also introduces an issue of "thickness" where walls between adjacent tiles are multiple pixels wide, making it difficult to model seamless room boundaries.
11+
12+
We have a `UnionFind` implementation in TypeScript (`MapGrid.ts`) that is perfectly suited for identifying contiguous areas (rooms) from a tilemap and calculating their exact edge loops. This logic is critical for performance and functionality but currently resides outside our core Rust engine.
13+
14+
How can we integrate this room-identification logic into the core engine to create a high-performance, hybrid collision system that supports both room boundaries and fine-grained pixel obstacles?
15+
16+
## Decision Drivers
17+
18+
- **Performance**: Must be significantly faster than a pure pixel-based approach for room-based maps.
19+
- **Accuracy**: Must model room boundaries precisely without pixel "thickness".
20+
- **Flexibility**: Must allow for a hybrid model where room-based collision (broad-phase) is combined with pixel-based collision (narrow-phase) for dynamic objects within rooms.
21+
- **Code Cohesion**: Critical collision and map logic should be consolidated into the Rust core, not split between Rust and TypeScript.
22+
23+
## Considered Options
24+
25+
1. **Pure Pixel-Based Collision**: Continue with the existing system. This is simple but fails to meet the performance and accuracy requirements for room-based environments.
26+
2. **Port `UnionFind` for Room Detection Only**: Port the `UnionFind` logic to Rust to define room boundaries, but keep it separate from the primary collision system. This would lead to a complex and disjointed architecture.
27+
3. **Hybrid `UnionFind` + Pixel Collision System**: Port the `UnionFind` logic to Rust and integrate it as a "broad-phase" collision layer. A ray is first checked against the room boundaries. If it does not cross a boundary, the existing pixel-based system is then used for "narrow-phase" checks against objects within that room.
28+
29+
## Decision Outcome
30+
31+
Chosen option: **"Hybrid `UnionFind` + Pixel Collision System"**, because it provides the best of both worlds.
32+
33+
This approach leverages the `UnionFind` data structure to efficiently determine room membership and boundaries from a simple tilemap. This acts as a highly optimized broad-phase check. For a ray cast, the engine first determines if the ray crosses a hard wall between rooms. If it doesn't, it can then perform the more expensive pixel-based checks for dynamic or detailed objects inside the room. This hierarchical strategy dramatically reduces the number of pixels that need to be checked, leading to a major performance improvement.
34+
35+
### Positive Consequences
36+
37+
- **Massive Performance Gain**: Avoids brute-force pixel checks for entire scenes, focusing computation where it is needed.
38+
- **Architectural Soundness**: Creates a clean, hierarchical collision system (broad-phase and narrow-phase).
39+
- **Enables New Features**: Allows for logic like "light up the entire room" or room-specific effects.
40+
- **Consolidates Core Logic**: Moves the `UnionFind` implementation into the Rust engine, improving maintainability.
41+
42+
### Negative Consequences
43+
44+
- **Increased Complexity**: The collision system now has two layers to manage and maintain.
45+
- **Data Dependency**: Requires a tilemap representation for the room layout in addition to the pixel map for obstacles.
46+
47+
## Links
48+
49+
- Supersedes parts of [ADR-0004](0004-rust-native-collision-detection.md) by introducing a more advanced, hybrid collision strategy.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Unify Collision Detection Around Hybrid Pixel + Room System
2+
3+
- Status: accepted
4+
- Date: 2025-01-08
5+
6+
## Context and Problem Statement
7+
8+
After implementing the hybrid collision system (ADR-0005), we now have four collision modes: Tile, Pixel, Auto, and Hybrid. This creates unnecessary complexity and confusion:
9+
10+
- **Tile mode**: Uses the block_map system but provides no clear advantage over hybrid mode
11+
- **Pixel mode**: Works well for freeform drawing but lacks room-based optimizations
12+
- **Auto mode**: Adds complexity without clear benefits
13+
- **Hybrid mode**: Provides the best of both worlds - room-based broad-phase + pixel-based narrow-phase
14+
15+
The hybrid system is flexible enough to handle all use cases:
16+
- When no rooms are configured, it behaves like pure pixel collision
17+
- When rooms are configured, it provides performance benefits through broad-phase collision
18+
19+
## Decision
20+
21+
Remove all collision modes except the hybrid system and make it the default and only collision detection method.
22+
23+
### Positive Consequences
24+
25+
- **Simplified API**: No more collision mode selection confusion
26+
- **Unified Codebase**: Single collision detection path reduces maintenance burden
27+
- **Better Performance**: All users benefit from the optimized hybrid approach
28+
- **Clearer Intent**: Room-based collision is explicit via map configuration, not mode selection
29+
30+
### Negative Consequences
31+
32+
- **Breaking Change**: Existing code using specific modes will need updates
33+
- **Slightly Higher Memory Usage**: UnionFind structures are always allocated (minimal impact)
34+
35+
## Implementation
36+
37+
1. Remove `CollisionMode` enum and related switching logic
38+
2. Always use `HybridCollisionMap` as the collision detector
39+
3. Remove collision mode configuration from WASM API
40+
4. Update documentation to reflect unified approach
41+
5. Simplify initialization code
42+
43+
## Links
44+
45+
- Supersedes [ADR-0005](0005-hybrid-room-pixel-collision.md) by making hybrid the only option
46+
- Related to [ADR-0004](0004-rust-native-collision-detection.md) performance goals

examples/web-demo/src/components/ControlPanel.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { html } from 'https://esm.sh/htm/preact';
22

3-
export default function ControlPanel({ lightConfig, onLightConfigChange }) {
3+
export default function ControlPanel({ lightConfig, onLightConfigChange, roomsConfigured, onCreateRoomLayout }) {
44
const { x, y, radius } = lightConfig;
55

66
const handleInputChange = (key) => (e) => {
@@ -58,6 +58,15 @@ export default function ControlPanel({ lightConfig, onLightConfigChange }) {
5858
onInput=${handleInputChange('radius')}
5959
/>
6060
</div>
61+
<div class="control-group">
62+
<label for="roomLayout">
63+
Room Layout
64+
</label>
65+
<button type="button" onclick=${onCreateRoomLayout} disabled=${roomsConfigured}>
66+
${roomsConfigured ? 'Rooms Configured' : 'Setup Room Layout'}
67+
</button>
68+
${roomsConfigured && html`<small>Room-based collision optimization active</small>`}
69+
</div>
6170
</form>
6271
`;
6372
}

examples/web-demo/src/components/Instructions.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ export default function Instructions() {
2525
<strong>💡 Pro Tip:</strong> Try creating complex shapes and
2626
watch how the CPU-based ray casting creates realistic
2727
lighting and shadows without any GPU acceleration!
28+
The engine uses a unified collision system that optimizes
29+
performance with room-based broad-phase collision detection.
2830
</div>
2931
</div>
3032
`;

examples/web-demo/src/components/LightingDemo.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ export default function LightingDemo({ lighting, initTime }) {
99
<${ControlPanel}
1010
lightConfig=${lighting.lightConfig}
1111
onLightConfigChange=${lighting.updateLightConfig}
12+
roomsConfigured=${lighting.roomsConfigured}
13+
onCreateRoomLayout=${lighting.createSimpleRoomLayout}
1214
/>
1315
1416
<${CanvasContainer}

0 commit comments

Comments
 (0)