Skip to content

Commit 293ddb4

Browse files
Merge pull request #1 from TimWillebrands/configurable-colors
2 parents 68b4f04 + d934e52 commit 293ddb4

6 files changed

Lines changed: 152 additions & 31 deletions

docs/decisions/0001-bresenham-based-lighting-engine.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
# Use Bresenham Line Algorithms for CPU-Based Lighting Engine
22

33
- Status: accepted
4-
- Deciders: Development Team
5-
- Date: 2024-12-19
64

75
Technical Story: Need for a portable, GPU-independent lighting engine that can work across multiple platforms and devices.
86

docs/decisions/0002-use-rust-for-core-engine.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
# Use Rust for Core Engine Instead of C3
22

33
- Status: accepted
4-
- Deciders: Development Team
5-
- Date: 2024-12-19
64

75
Technical Story: The project needs a robust, modern, and maintainable language for its core logic to ensure long-term viability and developer productivity.
86

docs/decisions/0003-avoid-async-for-simplicity.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
# Avoid Async Rust for Core Engine Simplicity
22

33
- Status: accepted
4-
- Deciders: Development Team
5-
- Date: 2024-12-19
64

75
Technical Story: As we adopt Rust, we need to decide on a concurrency model that fits the project's goals of simplicity and performance for a CPU-bound workload.
86

docs/decisions/0004-rust-native-collision-detection.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
# Rust-Native Collision Detection for Ray Casting Performance
22

3-
- Status: proposed
4-
- Deciders: [Performance Analysis and Architecture Review]
5-
- Date: 2025-07-27
3+
- Status: accepted
64

75
Technical Story: Performance bottleneck identified in ray casting where JavaScript-based collision detection causes ~250ms per light update due to expensive WASM bridge calls.
86

src/lib.rs

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
//! - **Deterministic**: Same inputs always produce identical outputs
1919
//! - **Portable**: Works on any platform with a CPU
2020
//! - **Minimalistic**: Small codebase with no heavy dependencies
21+
//! - **Configurable Colors**: Support for rainbow, solid, and custom HSV colors
2122
//!
2223
//! # Architecture
2324
//!
@@ -37,8 +38,14 @@
3738
//! // Initialize the engine
3839
//! init();
3940
//!
40-
//! // Add a light at position (100, 50) with radius 30
41-
//! const lightCanvas = put(1, 30, 100, 50);
41+
//! // Add a rainbow light at position (100, 50) with radius 30
42+
//! const rainbowLight = put(1, 30, 100, 50);
43+
//!
44+
//! // Add a red light at position (200, 50) with radius 25
45+
//! const redLight = put_solid_color(2, 25, 200, 50, 0);
46+
//!
47+
//! // Add a desaturated blue light with custom color
48+
//! const customLight = put_custom_color(3, 20, 150, 100, 170, 128);
4249
//!
4350
//! // Set up some obstacles
4451
//! set_tile(5, 3, 1);
@@ -53,8 +60,14 @@
5360
//! // Initialize the lighting system
5461
//! lighting::init();
5562
//!
56-
//! // Create a light source
63+
//! // Create a rainbow light source (default)
5764
//! let canvas_ptr = lighting::update_or_add_light(1, 30, 100, 50);
65+
//!
66+
//! // Create a solid color light source
67+
//! let red_light = lighting::update_or_add_light_with_solid_color(2, 25, 200, 50, 0);
68+
//!
69+
//! // Create a custom HSV color light source
70+
//! let custom_light = lighting::update_or_add_light_with_custom_color(3, 20, 150, 100, 170, 128);
5871
//! ```
5972
//!
6073
//! # Performance Characteristics
@@ -144,6 +157,7 @@ pub fn start() {
144157
///
145158
/// This is the primary interface for managing lights in the scene.
146159
/// Each light is identified by a unique ID and can be updated independently.
160+
/// Uses the default rainbow color mode for backward compatibility.
147161
///
148162
/// # Arguments
149163
/// * `id` - Unique identifier for this light (0-255)
@@ -174,7 +188,7 @@ pub fn start() {
174188
/// # Example Usage (JavaScript)
175189
///
176190
/// ```javascript
177-
/// // Create a red light with radius 50 at position (200, 100)
191+
/// // Create a rainbow light with radius 50 at position (200, 100)
178192
/// const lightCanvas = put(0, 50, 200, 100);
179193
///
180194
/// // Later, move the same light to a new position
@@ -185,6 +199,59 @@ pub fn put(id: u8, r: i16, x: i16, y: i16) -> *const lighting::Color {
185199
lighting::update_or_add_light(id, r, x, y)
186200
}
187201

202+
/// Updates an existing light or creates a new one with a solid color.
203+
///
204+
/// # Arguments
205+
/// * `id` - Unique identifier for this light (0-255)
206+
/// * `r` - Light radius/range in world units
207+
/// * `x` - World X coordinate of the light center
208+
/// * `y` - World Y coordinate of the light center
209+
/// * `hue` - Color hue (0-255, representing 0-360°)
210+
///
211+
/// # Returns
212+
/// A pointer to the light's rendered canvas data (RGBA pixel array).
213+
///
214+
/// # Example Usage (JavaScript)
215+
///
216+
/// ```javascript
217+
/// // Create a red light (hue=0) with radius 50 at position (200, 100)
218+
/// const lightCanvas = put_solid_color(0, 50, 200, 100, 0);
219+
///
220+
/// // Create a green light (hue=85) with radius 30 at position (150, 200)
221+
/// const greenLight = put_solid_color(1, 30, 150, 200, 85);
222+
/// ```
223+
#[wasm_bindgen]
224+
pub fn put_solid_color(id: u8, r: i16, x: i16, y: i16, hue: u8) -> *const lighting::Color {
225+
lighting::update_or_add_light_with_solid_color(id, r, x, y, hue)
226+
}
227+
228+
/// Updates an existing light or creates a new one with custom HSV color.
229+
///
230+
/// # Arguments
231+
/// * `id` - Unique identifier for this light (0-255)
232+
/// * `r` - Light radius/range in world units
233+
/// * `x` - World X coordinate of the light center
234+
/// * `y` - World Y coordinate of the light center
235+
/// * `hue` - Color hue (0-255, representing 0-360°)
236+
/// * `saturation` - Color saturation (0-255, 0=grayscale, 255=full color)
237+
///
238+
/// # Returns
239+
/// A pointer to the light's rendered canvas data (RGBA pixel array).
240+
///
241+
/// # Example Usage (JavaScript)
242+
///
243+
/// ```javascript
244+
/// // Create a desaturated blue light
245+
/// const lightCanvas = put_custom_color(0, 50, 200, 100, 170, 128);
246+
///
247+
/// // Create a bright cyan light
248+
/// const cyanLight = put_custom_color(1, 30, 150, 200, 128, 255);
249+
/// ```
250+
#[wasm_bindgen]
251+
pub fn put_custom_color(id: u8, r: i16, x: i16, y: i16, hue: u8, saturation: u8) -> *const lighting::Color {
252+
lighting::update_or_add_light_with_custom_color(id, r, x, y, hue, saturation)
253+
}
254+
188255
/// Returns a pointer to the world's tile data array.
189256
///
190257
/// The tile array represents the high-level structure of the world,

src/lighting.rs

Lines changed: 80 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,14 @@ use std::sync::RwLock;
2323

2424
use crate::{arctan, ray};
2525

26-
26+
/// Color mode configuration for light sources
27+
#[derive(Clone, Debug, PartialEq)]
28+
pub enum ColorMode {
29+
/// Solid color using specified hue (0-255)
30+
Solid(u8),
31+
/// Custom HSV color with specified hue and saturation
32+
Custom { hue: u8, saturation: u8 },
33+
}
2734

2835
/// Maximum distance for light ray casting
2936
#[cfg(all(test, not(target_arch = "wasm32")))]
@@ -101,6 +108,8 @@ struct Light {
101108
pos: PtI,
102109
/// Radius/range of the light (maximum distance it can illuminate)
103110
r: i16,
111+
/// Color mode configuration for this light (None = default rainbow effect)
112+
color_mode: Option<ColorMode>,
104113
/// Rendered pixel data for this light (RGBA format)
105114
canvas: Vec<Color>,
106115
/// Canvas dimensions (width and height)
@@ -116,15 +125,17 @@ impl Light {
116125
/// # Arguments
117126
/// * `pos` - World coordinates (x, y) where the light is positioned
118127
/// * `r` - Maximum distance the light can illuminate
128+
/// * `color_mode` - Color configuration for this light (None for default rainbow)
119129
///
120130
/// # Returns
121131
/// A new Light instance with cleared canvas and unblocked angles
122-
fn new(pos: PtI, r: i16) -> Self {
132+
fn new(pos: PtI, r: i16, color_mode: Option<ColorMode>) -> Self {
123133
let canvas_size = (r * 2 + 1) as usize;
124134
let canvas_pixels = canvas_size * canvas_size;
125135
Light {
126136
pos,
127137
r,
138+
color_mode,
128139
canvas: vec![Color::default(); canvas_pixels],
129140
canvas_size,
130141
blocked_angles: [255; ANGLES], // 255 = not blocked
@@ -219,7 +230,7 @@ impl Light {
219230
///
220231
/// # Arguments
221232
/// * `cell` - Local coordinates relative to the light center
222-
/// * `angle` - The angle of the ray (used for hue calculation)
233+
/// * `angle` - The angle of the ray (used for hue calculation when in rainbow mode)
223234
/// * `distance` - Distance from light source (used for brightness falloff)
224235
fn render_light_pixel(&mut self, cell: PtI, angle: usize, distance: u8) {
225236
// Transform local coordinates to canvas coordinates
@@ -240,10 +251,23 @@ impl Light {
240251

241252
// Ensure we don't write outside the canvas bounds
242253
if cell_idx < self.canvas.len() {
243-
// Use angle for hue, full saturation, and distance-based brightness
244-
// Scale angle to full hue range (0-255) for proper color distribution
245-
let scaled_hue = (angle * 255) / (ANGLES - 1);
246-
self.canvas[cell_idx] = hsv2rgb(scaled_hue as u8, 255, falloff as u8);
254+
let color = match &self.color_mode {
255+
// Default rainbow effect - hue varies by angle, full saturation
256+
None => {
257+
let scaled_hue = (angle * 255) / (ANGLES - 1);
258+
hsv2rgb(scaled_hue as u8, 255, falloff as u8)
259+
}
260+
// Solid color - fixed hue, full saturation
261+
Some(ColorMode::Solid(hue)) => {
262+
hsv2rgb(*hue, 255, falloff as u8)
263+
}
264+
// Custom color - specified hue and saturation
265+
Some(ColorMode::Custom { hue, saturation }) => {
266+
hsv2rgb(*hue, *saturation, falloff as u8)
267+
}
268+
};
269+
270+
self.canvas[cell_idx] = color;
247271
}
248272
}
249273
}
@@ -286,46 +310,62 @@ fn hsv2rgb(h: u8, s: u8, v: u8) -> Color {
286310
}
287311
}
288312

289-
/// Updates an existing light or creates a new one with the specified parameters
290-
///
291-
/// This is the main entry point for the lighting system from WASM. It manages
292-
/// the light storage and triggers recalculation when light properties change.
313+
/// Updates an existing light or creates a new one with a solid color
293314
///
294315
/// # Arguments
295316
/// * `id` - Unique identifier for the light (0-255)
296317
/// * `r` - Light radius/range (clamped to MAX_DIST)
297318
/// * `x` - World X coordinate
298319
/// * `y` - World Y coordinate
320+
/// * `hue` - Color hue (0-255, representing 0-360°)
299321
///
300322
/// # Returns
301323
/// Pointer to the light's canvas data for rendering, or null pointer on error
324+
pub fn update_or_add_light_with_solid_color(id: u8, r: i16, x: i16, y: i16, hue: u8) -> *const Color {
325+
update_light_with_color_mode(id, r, x, y, Some(ColorMode::Solid(hue)))
326+
}
327+
328+
/// Updates an existing light or creates a new one with custom HSV color
302329
///
303-
/// # Thread Safety
304-
/// This function is thread-safe thanks to the RwLock protecting the light map.
305-
/// Multiple lights can be updated concurrently from different threads.
306-
pub fn update_or_add_light(id: u8, r: i16, x: i16, y: i16) -> *const Color {
330+
/// # Arguments
331+
/// * `id` - Unique identifier for the light (0-255)
332+
/// * `r` - Light radius/range (clamped to MAX_DIST)
333+
/// * `x` - World X coordinate
334+
/// * `y` - World Y coordinate
335+
/// * `hue` - Color hue (0-255, representing 0-360°)
336+
/// * `saturation` - Color saturation (0-255, 0=grayscale, 255=full color)
337+
///
338+
/// # Returns
339+
/// Pointer to the light's canvas data for rendering, or null pointer on error
340+
pub fn update_or_add_light_with_custom_color(id: u8, r: i16, x: i16, y: i16, hue: u8, saturation: u8) -> *const Color {
341+
update_light_with_color_mode(id, r, x, y, Some(ColorMode::Custom { hue, saturation }))
342+
}
343+
344+
/// Internal helper function to update lights with any color mode
345+
fn update_light_with_color_mode(id: u8, r: i16, x: i16, y: i16, color_mode: Option<ColorMode>) -> *const Color {
307346
// Clamp radius to maximum supported distance
308347
let clamped_r = r.min(MAX_DIST as i16).max(1);
309348

310349
// Attempt to get write access to the light map
311350
if let Ok(mut light_map) = LIGHT_MAP.write() {
312351
// Check if we need to create a new light or update existing
313352
let needs_new_light = if let Some(existing_light) = light_map.get(&id) {
314-
existing_light.r != clamped_r
353+
existing_light.r != clamped_r || existing_light.color_mode != color_mode
315354
} else {
316355
true
317356
};
318357

319358
if needs_new_light {
320-
// Create new light with correct radius
321-
let new_light = Light::new((x, y), clamped_r);
359+
// Create new light with correct radius and color mode
360+
let new_light = Light::new((x, y), clamped_r, color_mode.clone());
322361
light_map.insert(id, new_light);
323362
}
324363

325364
// Get the light and update its properties
326365
if let Some(light) = light_map.get_mut(&id) {
327366
light.pos = (x, y);
328367
light.r = clamped_r;
368+
light.color_mode = color_mode;
329369
light.update()
330370
} else {
331371
std::ptr::null()
@@ -336,6 +376,28 @@ pub fn update_or_add_light(id: u8, r: i16, x: i16, y: i16) -> *const Color {
336376
}
337377
}
338378

379+
/// Updates an existing light or creates a new one with the specified parameters
380+
///
381+
/// This function maintains backward compatibility by using the default rainbow color mode.
382+
/// For custom colors, use `update_or_add_light_with_solid_color` or
383+
/// `update_or_add_light_with_custom_color`.
384+
///
385+
/// # Arguments
386+
/// * `id` - Unique identifier for the light (0-255)
387+
/// * `r` - Light radius/range (clamped to MAX_DIST)
388+
/// * `x` - World X coordinate
389+
/// * `y` - World Y coordinate
390+
///
391+
/// # Returns
392+
/// Pointer to the light's canvas data for rendering, or null pointer on error
393+
///
394+
/// # Thread Safety
395+
/// This function is thread-safe thanks to the RwLock protecting the light map.
396+
/// Multiple lights can be updated concurrently from different threads.
397+
pub fn update_or_add_light(id: u8, r: i16, x: i16, y: i16) -> *const Color {
398+
update_light_with_color_mode(id, r, x, y, None)
399+
}
400+
339401
/// Initializes the lighting system
340402
///
341403
/// This function must be called before any lighting calculations can be performed.

0 commit comments

Comments
 (0)