Skip to main content

mittens_engine/engine/ecs/system/
light_system.rs

1use crate::engine::ecs::component::DirectionalLightComponent;
2use crate::engine::ecs::component::PointLightComponent;
3use crate::engine::ecs::component::{AmbientLightComponent, ColorComponent};
4use crate::engine::ecs::system::System;
5use crate::engine::ecs::system::TransformSystem;
6use crate::engine::ecs::{ComponentId, World};
7use crate::engine::graphics::VisualWorld;
8use crate::engine::user_input::InputState;
9
10/// ECS lighting system.
11///
12/// Keeps `VisualWorld`'s point-light list in sync with ECS.
13#[derive(Debug, Default)]
14pub struct LightSystem;
15
16impl LightSystem {
17    pub fn new() -> Self {
18        Self
19    }
20
21    pub fn register_light(
22        &mut self,
23        world: &mut World,
24        visuals: &mut VisualWorld,
25        component: ComponentId,
26    ) {
27        let position_ws =
28            TransformSystem::world_position(world, component).unwrap_or([0.0, 0.0, 0.0]);
29
30        if let Some(light) = world.get_component_by_id_as::<PointLightComponent>(component) {
31            visuals.upsert_point_light(
32                component,
33                crate::engine::graphics::visual_world::VisualPointLight {
34                    light_type: 1,
35                    position_ws,
36                    intensity: light.intensity,
37                    distance: light.distance,
38                    color: light.color,
39                },
40            );
41            return;
42        }
43
44        if let Some(light) = world.get_component_by_id_as::<DirectionalLightComponent>(component) {
45            let color = world
46                .children_of(component)
47                .iter()
48                .find_map(|&ch| {
49                    world
50                        .get_component_by_id_as::<ColorComponent>(ch)
51                        .map(|c| [c.rgba[0], c.rgba[1], c.rgba[2]])
52                })
53                .unwrap_or(light.color);
54            // Direction is encoded in the node's world position.
55            visuals.upsert_point_light(
56                component,
57                crate::engine::graphics::visual_world::VisualPointLight {
58                    light_type: 2,
59                    position_ws,
60                    intensity: light.intensity,
61                    distance: 0.0,
62                    color,
63                },
64            );
65        }
66    }
67
68    pub fn register_ambient_light(
69        &mut self,
70        world: &mut World,
71        visuals: &mut VisualWorld,
72        component: ComponentId,
73    ) {
74        let Some(ambient) = world.get_component_by_id_as::<AmbientLightComponent>(component) else {
75            return;
76        };
77
78        // Color driven by an immediate child ColorComponent; falls back to ambient.rgb.
79        let rgb = world
80            .children_of(component)
81            .iter()
82            .find_map(|&ch| {
83                world
84                    .get_component_by_id_as::<ColorComponent>(ch)
85                    .map(|c| [c.rgba[0], c.rgba[1], c.rgba[2]])
86            })
87            .unwrap_or(ambient.rgb);
88
89        // Global state: last registered wins.
90        visuals.set_ambient_light(rgb);
91    }
92
93    /// Called when a TransformComponent changes.
94    ///
95    /// Updates all descendant point lights' positions in `VisualWorld`.
96    pub fn transform_changed(
97        &mut self,
98        world: &mut World,
99        visuals: &mut VisualWorld,
100        component: ComponentId,
101    ) {
102        let mut _visited_nodes = 0usize;
103        let mut _updated_lights = 0usize;
104
105        let mut stack = vec![component];
106        while let Some(node) = stack.pop() {
107            _visited_nodes += 1;
108            for &child in world.children_of(node) {
109                stack.push(child);
110
111                let position_ws =
112                    TransformSystem::world_position(world, child).unwrap_or([0.0, 0.0, 0.0]);
113
114                if let Some(light) = world.get_component_by_id_as::<PointLightComponent>(child) {
115                    _updated_lights += 1;
116                    visuals.upsert_point_light(
117                        child,
118                        crate::engine::graphics::visual_world::VisualPointLight {
119                            light_type: 1,
120                            position_ws,
121                            intensity: light.intensity,
122                            distance: light.distance,
123                            color: light.color,
124                        },
125                    );
126                    continue;
127                }
128
129                if let Some(light) =
130                    world.get_component_by_id_as::<DirectionalLightComponent>(child)
131                {
132                    _updated_lights += 1;
133                    let color = world
134                        .children_of(child)
135                        .iter()
136                        .find_map(|&ch| {
137                            world
138                                .get_component_by_id_as::<ColorComponent>(ch)
139                                .map(|c| [c.rgba[0], c.rgba[1], c.rgba[2]])
140                        })
141                        .unwrap_or(light.color);
142                    visuals.upsert_point_light(
143                        child,
144                        crate::engine::graphics::visual_world::VisualPointLight {
145                            light_type: 2,
146                            position_ws,
147                            intensity: light.intensity,
148                            distance: 0.0,
149                            color,
150                        },
151                    );
152                }
153            }
154        }
155    }
156}
157
158impl System for LightSystem {
159    fn tick(
160        &mut self,
161        _world: &mut World,
162        _visuals: &mut VisualWorld,
163        _input: &InputState,
164        _dt_sec: f32,
165    ) {
166        // No-op for now.
167    }
168}