khora_data/render/
world.rs1use khora_core::{
22 asset::{AssetHandle, AssetUUID, Material},
23 math::{affine_transform::AffineTransform, Vec3},
24 renderer::{
25 api::scene::{GpuMaterial, GpuMesh},
26 light::LightType,
27 },
28};
29
30#[derive(Clone)]
32pub struct ExtractedMesh {
33 pub transform: AffineTransform,
35 pub cpu_mesh_uuid: AssetUUID,
37 pub gpu_mesh: AssetHandle<GpuMesh>,
39 pub material: Option<AssetHandle<Box<dyn Material>>>,
45 pub gpu_material: Option<AssetHandle<GpuMaterial>>,
50}
51
52#[derive(Debug, Clone)]
54pub struct ExtractedLight {
55 pub light_type: LightType,
57 pub position: Vec3,
59 pub direction: Vec3,
61 pub shadow_view_proj: khora_core::math::Mat4,
63 pub shadow_atlas_index: Option<i32>,
65}
66
67#[derive(Debug, Clone)]
69pub struct ExtractedView {
70 pub view_proj: khora_core::math::Mat4,
72 pub position: Vec3,
74}
75
76#[derive(Default, Clone)]
81pub struct RenderWorld {
82 pub meshes: Vec<ExtractedMesh>,
84 pub lights: Vec<ExtractedLight>,
86 pub views: Vec<ExtractedView>,
88}
89
90impl RenderWorld {
91 pub fn new() -> Self {
93 Self::default()
94 }
95
96 pub fn clear(&mut self) {
98 self.meshes.clear();
99 self.lights.clear();
100 self.views.clear();
101 }
102
103 pub fn directional_light_count(&self) -> usize {
105 self.lights
106 .iter()
107 .filter(|l| matches!(l.light_type, LightType::Directional(_)))
108 .count()
109 }
110
111 pub fn point_light_count(&self) -> usize {
113 self.lights
114 .iter()
115 .filter(|l| matches!(l.light_type, LightType::Point(_)))
116 .count()
117 }
118
119 pub fn spot_light_count(&self) -> usize {
121 self.lights
122 .iter()
123 .filter(|l| matches!(l.light_type, LightType::Spot(_)))
124 .count()
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131 use khora_core::renderer::light::{DirectionalLight, PointLight, SpotLight};
132
133 #[test]
134 fn render_world_default_is_empty() {
135 let world = RenderWorld::default();
136 assert!(world.meshes.is_empty());
137 assert!(world.lights.is_empty());
138 assert!(world.views.is_empty());
139 }
140
141 #[test]
142 fn render_world_clear_drains_collections() {
143 let mut world = RenderWorld::new();
144 world.lights.push(ExtractedLight {
145 light_type: LightType::Directional(DirectionalLight::default()),
146 position: Vec3::ZERO,
147 direction: Vec3::new(0.0, -1.0, 0.0),
148 shadow_view_proj: khora_core::math::Mat4::IDENTITY,
149 shadow_atlas_index: None,
150 });
151 assert_eq!(world.lights.len(), 1);
152
153 world.clear();
154 assert!(world.lights.is_empty());
155 assert!(world.meshes.is_empty());
156 }
157
158 #[test]
159 fn light_count_methods_filter_by_type() {
160 let mut world = RenderWorld::new();
161 world.lights.push(ExtractedLight {
162 light_type: LightType::Directional(DirectionalLight::default()),
163 position: Vec3::ZERO,
164 direction: Vec3::new(0.0, -1.0, 0.0),
165 shadow_view_proj: khora_core::math::Mat4::IDENTITY,
166 shadow_atlas_index: None,
167 });
168 world.lights.push(ExtractedLight {
169 light_type: LightType::Point(PointLight::default()),
170 position: Vec3::new(1.0, 2.0, 3.0),
171 direction: Vec3::ZERO,
172 shadow_view_proj: khora_core::math::Mat4::IDENTITY,
173 shadow_atlas_index: None,
174 });
175 world.lights.push(ExtractedLight {
176 light_type: LightType::Point(PointLight::default()),
177 position: Vec3::new(-1.0, 2.0, 3.0),
178 direction: Vec3::ZERO,
179 shadow_view_proj: khora_core::math::Mat4::IDENTITY,
180 shadow_atlas_index: None,
181 });
182 world.lights.push(ExtractedLight {
183 light_type: LightType::Spot(SpotLight::default()),
184 position: Vec3::ZERO,
185 direction: Vec3::new(0.0, -1.0, 0.0),
186 shadow_view_proj: khora_core::math::Mat4::IDENTITY,
187 shadow_atlas_index: None,
188 });
189
190 assert_eq!(world.directional_light_count(), 1);
191 assert_eq!(world.point_light_count(), 2);
192 assert_eq!(world.spot_light_count(), 1);
193 }
194}