1use glam::{Mat4, Quat, Vec3};
2use serde::{Deserialize, Serialize};
3
4use crate::camera::Camera;
5use crate::ecs::entity::EntityAllocator;
6use crate::ecs::{ComponentStore, Entity};
7use crate::renderer::{Model, Sprite};
8use crate::scripting::ScriptList;
9use crate::transform::Transformable;
10
11#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
16pub struct Transform {
17 pub position: Vec3,
18 pub rotation: Quat,
19 pub scale: Vec3,
20}
21
22impl Default for Transform {
23 fn default() -> Self {
24 Self { position: Vec3::ZERO, rotation: Quat::IDENTITY, scale: Vec3::ONE }
25 }
26}
27
28impl Transform {
29 pub fn to_mat4(&self) -> Mat4 {
30 Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.position)
31 }
32}
33
34#[derive(Clone)]
35pub struct Name(pub String);
36
37#[derive(Clone)]
40pub enum Renderable {
41 Sprite(Sprite),
42 Model(Model),
43}
44
45impl Renderable {
46 pub fn sync_transform(&mut self, transform: &Transform) {
49 let matrix = transform.to_mat4();
50 match self {
51 Renderable::Sprite(sprite) => {
52 sprite.set_transform(matrix);
53 }
54 Renderable::Model(model) => {
55 model.set_transform(matrix);
56 }
57 }
58 }
59
60 pub fn bounding_radius(&self) -> f32 {
62 match self {
63 Renderable::Sprite(sprite) => 0.5 * sprite.width.hypot(sprite.height),
64 Renderable::Model(model) => model.bounding_radius,
65 }
66 }
67
68 pub fn bounding_center(&self) -> Vec3 {
70 match self {
71 Renderable::Sprite(sprite) => Vec3::new(sprite.width * 0.5, sprite.height * 0.5, 0.0),
72 Renderable::Model(model) => model.bounding_center,
73 }
74 }
75}
76
77#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
84pub struct CameraComponent {
85 pub fov: f32,
87 pub znear: f32,
88 pub zfar: f32,
89 pub active: bool,
90}
91
92impl Default for CameraComponent {
93 fn default() -> Self {
94 Self { fov: 45.0, znear: 0.1, zfar: 100.0, active: false }
95 }
96}
97
98impl CameraComponent {
99 pub fn derive_camera(&self, transform: &Transform, aspect: f32) -> Camera {
105 let forward = transform.rotation * Vec3::NEG_Z;
106 let (yaw, pitch) = Camera::yaw_pitch_from_forward(forward);
107 Camera {
108 position: transform.position,
109 yaw,
110 pitch,
111 aspect,
112 fov: self.fov,
113 znear: self.znear,
114 zfar: self.zfar,
115 }
116 }
117}
118
119#[derive(Clone)]
128pub struct World {
129 allocator: EntityAllocator,
130 pub transforms: ComponentStore<Transform>,
131 pub names: ComponentStore<Name>,
132 pub renderables: ComponentStore<Renderable>,
133 pub scripts: ComponentStore<ScriptList>,
134 pub cameras: ComponentStore<CameraComponent>,
135 pub persistent: ComponentStore<()>,
142 pub camera: Camera,
143}
144
145impl World {
146 pub fn new(camera: Camera) -> Self {
147 Self {
148 allocator: EntityAllocator::new(),
149 transforms: ComponentStore::new(),
150 names: ComponentStore::new(),
151 renderables: ComponentStore::new(),
152 scripts: ComponentStore::new(),
153 cameras: ComponentStore::new(),
154 persistent: ComponentStore::new(),
155 camera,
156 }
157 }
158
159 pub fn spawn_empty(&mut self, name: impl Into<String>, transform: Transform) -> Entity {
160 let entity = self.allocator.spawn();
161 self.names.insert(entity, Name(name.into()));
162 self.transforms.insert(entity, transform);
163 entity
164 }
165
166 pub fn set_renderable(&mut self, entity: Entity, renderable: Renderable) {
167 self.renderables.insert(entity, renderable);
168 }
169
170 pub fn clear_renderable(&mut self, entity: Entity) {
171 self.renderables.remove(entity);
172 }
173
174 pub fn set_camera(&mut self, entity: Entity, component: CameraComponent) {
175 self.cameras.insert(entity, component);
176 }
177
178 pub fn clear_camera(&mut self, entity: Entity) {
179 self.cameras.remove(entity);
180 }
181
182 pub fn set_persistent(&mut self, entity: Entity, persistent: bool) {
184 if persistent {
185 self.persistent.insert(entity, ());
186 } else {
187 self.persistent.remove(entity);
188 }
189 }
190
191 pub fn is_persistent(&self, entity: Entity) -> bool {
192 self.persistent.get(entity).is_some()
193 }
194
195 pub fn despawn(&mut self, entity: Entity) {
196 self.allocator.despawn(entity);
197 self.names.remove(entity);
198 self.transforms.remove(entity);
199 self.renderables.remove(entity);
200 self.scripts.remove(entity);
201 self.cameras.remove(entity);
202 self.persistent.remove(entity);
203 }
204
205 pub fn is_alive(&self, entity: Entity) -> bool {
206 self.allocator.is_alive(entity)
207 }
208
209 pub fn iter_entities(&self) -> impl Iterator<Item = Entity> + '_ {
212 self.transforms.iter().map(|(entity, _)| entity)
213 }
214
215 pub fn despawn_non_persistent(&mut self) {
220 let doomed: Vec<Entity> = self.iter_entities().filter(|&e| !self.is_persistent(e)).collect();
221 for entity in doomed {
222 self.despawn(entity);
223 }
224 }
225
226 pub fn sync_transforms(&mut self) {
229 for (entity, renderable) in self.renderables.iter_mut() {
230 if let Some(transform) = self.transforms.get(entity) {
231 renderable.sync_transform(transform);
232 }
233 }
234 }
235
236 pub fn active_camera(&self, aspect: f32) -> Option<Camera> {
244 let (entity, component) = self.cameras.iter().find(|(_, c)| c.active)?;
245 let transform = self.transforms.get(entity)?;
246 Some(component.derive_camera(transform, aspect))
247 }
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253 use crate::scripting::{ScriptAttachment, ScriptList};
254 use std::path::PathBuf;
255
256 fn test_camera() -> Camera {
257 Camera { position: Vec3::ZERO, yaw: 0.0, pitch: 0.0, aspect: 1.0, fov: 45.0, znear: 0.1, zfar: 100.0 }
258 }
259
260 #[test]
261 fn despawn_non_persistent_keeps_persistent_entities_alive() {
262 let mut world = World::new(test_camera());
263 let persistent = world.spawn_empty("Persistent", Transform::default());
264 let transient = world.spawn_empty("Transient", Transform::default());
265 world.set_persistent(persistent, true);
266
267 world.despawn_non_persistent();
268
269 assert!(world.is_alive(persistent));
270 assert!(!world.is_alive(transient));
271 }
272
273 #[test]
274 fn despawn_clears_the_persistent_flag() {
275 let mut world = World::new(test_camera());
276 let entity = world.spawn_empty("Persistent", Transform::default());
277 world.set_persistent(entity, true);
278
279 world.despawn(entity);
280
281 assert!(!world.is_persistent(entity));
282 }
283
284 #[test]
285 fn despawn_clears_attached_scripts() {
286 let mut world = World::new(test_camera());
287 let entity = world.spawn_empty("Scripted", Transform::default());
288 world.scripts.insert(
289 entity,
290 ScriptList(vec![ScriptAttachment { path: PathBuf::from("scripts/move.rhai"), enabled: true }]),
291 );
292
293 world.despawn(entity);
294
295 assert!(world.scripts.get(entity).is_none());
296 }
297
298 #[test]
299 fn despawn_clears_attached_camera() {
300 let mut world = World::new(test_camera());
301 let entity = world.spawn_empty("Cam", Transform::default());
302 world.set_camera(entity, CameraComponent::default());
303
304 world.despawn(entity);
305
306 assert!(world.cameras.get(entity).is_none());
307 }
308
309 #[test]
310 fn derive_camera_looks_down_local_negative_z() {
311 let component = CameraComponent::default();
312 let camera = component.derive_camera(&Transform::default(), 1.0);
313
314 assert!(camera.forward().abs_diff_eq(Vec3::NEG_Z, 1e-5));
315 }
316
317 #[test]
320 fn derive_camera_respects_rotation() {
321 let component = CameraComponent::default();
322 let transform = Transform { rotation: Quat::from_rotation_y(90.0_f32.to_radians()), ..Transform::default() };
323
324 let camera = component.derive_camera(&transform, 1.0);
325
326 assert!(camera.forward().abs_diff_eq(Vec3::NEG_X, 1e-4));
327 }
328
329 #[test]
330 fn active_camera_returns_none_when_nothing_is_marked_active() {
331 let mut world = World::new(test_camera());
332 let entity = world.spawn_empty("Cam", Transform::default());
333 world.set_camera(entity, CameraComponent { active: false, ..CameraComponent::default() });
334
335 assert!(world.active_camera(1.0).is_none());
336 }
337
338 #[test]
339 fn active_camera_derives_from_the_marked_entity() {
340 let mut world = World::new(test_camera());
341 let position = Vec3::new(1.0, 2.0, 3.0);
342 let entity = world.spawn_empty("Cam", Transform { position, ..Transform::default() });
343 world.set_camera(entity, CameraComponent { fov: 60.0, znear: 0.5, zfar: 50.0, active: true });
344
345 let camera = world.active_camera(1.7).expect("an active camera entity exists");
346
347 assert_eq!(camera.position, position);
348 assert_eq!(camera.fov, 60.0);
349 assert_eq!(camera.znear, 0.5);
350 assert_eq!(camera.zfar, 50.0);
351 assert_eq!(camera.aspect, 1.7);
352 }
353
354 #[test]
364 fn clone_is_independent_of_the_original() {
365 let mut world = World::new(test_camera());
366 let entity = world.spawn_empty("Original", Transform { position: Vec3::new(1.0, 2.0, 3.0), ..Transform::default() });
367 world.scripts.insert(
368 entity,
369 ScriptList(vec![ScriptAttachment { path: PathBuf::from("scripts/move.rhai"), enabled: true }]),
370 );
371
372 let mut clone = world.clone();
373
374 clone.transforms.get_mut(entity).unwrap().position = Vec3::ZERO;
377 clone.names.get_mut(entity).unwrap().0 = "Renamed".to_string();
378 clone.scripts.get_mut(entity).unwrap().0[0].enabled = false;
379 let new_entity = clone.spawn_empty("SpawnedDuringPlay", Transform::default());
380
381 assert_eq!(world.transforms.get(entity).unwrap().position, Vec3::new(1.0, 2.0, 3.0));
382 assert_eq!(world.names.get(entity).unwrap().0, "Original");
383 assert!(world.scripts.get(entity).unwrap().0[0].enabled);
384 assert!(!world.is_alive(new_entity), "an entity spawned only on the clone shouldn't exist on the original");
385 }
386}