1use crate::core::{Bundle, Entity, EntityName, World};
14use crate::math::{Quat, Vec3};
15use gizmo_physics_core::Transform;
16use crate::renderer::components::{
17 Camera, DirectionalLight, LightRole, Material, Mesh, MeshRenderer, PointLight, SpotLight,
18};
19
20#[derive(Debug, Clone, Copy, PartialEq)]
26pub struct DirectionalLightBundle {
27 pub rotation: Quat,
28 pub color: Vec3,
29 pub intensity: f32,
30 pub role: LightRole,
31}
32
33impl Default for DirectionalLightBundle {
34 fn default() -> Self {
35 Self {
36 rotation: Quat::from_rotation_x(-std::f32::consts::PI / 4.0),
37 color: Vec3::new(1.0, 1.0, 1.0),
38 intensity: 3.0,
39 role: LightRole::Sun,
40 }
41 }
42}
43
44impl Bundle for DirectionalLightBundle {
45 fn get_infos() -> Vec<gizmo_core::archetype::ComponentInfo> { vec![] }
46 unsafe fn write_to_archetype(self, _arch: &mut gizmo_core::archetype::Archetype, _row: usize, _tick: u32) {}
47 fn apply(self, world: &mut World, entity: Entity) {
48 world.add_component(
49 entity,
50 Transform::new(Vec3::ZERO).with_rotation(self.rotation),
51 );
52 world.add_component(entity, gizmo_physics_core::components::GlobalTransform::default());
53 world.add_component(
54 entity,
55 DirectionalLight::new(self.color, self.intensity, self.role),
56 );
57 }
58}
59
60#[derive(Debug, Clone, Copy, PartialEq)]
66pub struct PointLightBundle {
67 pub position: Vec3,
68 pub color: Vec3,
69 pub intensity: f32,
70 pub radius: f32,
71}
72
73impl Default for PointLightBundle {
74 fn default() -> Self {
75 Self {
76 position: Vec3::ZERO,
77 color: Vec3::new(1.0, 1.0, 1.0),
78 intensity: 5.0,
79 radius: 20.0,
80 }
81 }
82}
83
84impl Bundle for PointLightBundle {
85 fn get_infos() -> Vec<gizmo_core::archetype::ComponentInfo> { vec![] }
86 unsafe fn write_to_archetype(self, _arch: &mut gizmo_core::archetype::Archetype, _row: usize, _tick: u32) {}
87 fn apply(self, world: &mut World, entity: Entity) {
88 world.add_component(entity, Transform::new(self.position));
89 world.add_component(entity, gizmo_physics_core::components::GlobalTransform::default());
90 world.add_component(
91 entity,
92 PointLight::new(self.color, self.intensity, self.radius),
93 );
94 }
95}
96
97#[derive(Debug, Clone, Copy, PartialEq)]
103pub struct SpotLightBundle {
104 pub position: Vec3,
105 pub rotation: Quat,
106 pub color: Vec3,
107 pub intensity: f32,
108 pub radius: f32,
109 pub inner_angle: f32,
110 pub outer_angle: f32,
111}
112
113impl Default for SpotLightBundle {
114 fn default() -> Self {
115 Self {
116 position: Vec3::ZERO,
117 rotation: Quat::IDENTITY,
118 color: Vec3::new(1.0, 1.0, 1.0),
119 intensity: 10.0,
120 radius: 30.0,
121 inner_angle: 0.4,
122 outer_angle: 0.6,
123 }
124 }
125}
126
127impl Bundle for SpotLightBundle {
128 fn get_infos() -> Vec<gizmo_core::archetype::ComponentInfo> { vec![] }
129 unsafe fn write_to_archetype(self, _arch: &mut gizmo_core::archetype::Archetype, _row: usize, _tick: u32) {}
130 fn apply(self, world: &mut World, entity: Entity) {
131 world.add_component(
132 entity,
133 Transform::new(self.position).with_rotation(self.rotation),
134 );
135 world.add_component(entity, gizmo_physics_core::components::GlobalTransform::default());
136 world.add_component(
137 entity,
138 SpotLight::new(
139 self.color,
140 self.intensity,
141 self.radius,
142 self.inner_angle,
143 self.outer_angle,
144 ),
145 );
146 }
147}
148
149#[derive(Debug, Clone, Copy, PartialEq)]
155pub struct CameraBundle {
156 pub position: Vec3,
157 pub fov: f32,
158 pub near: f32,
159 pub far: f32,
160 pub yaw: f32,
161 pub pitch: f32,
162 pub exposure: f32,
163 pub primary: bool,
164}
165
166impl Default for CameraBundle {
167 fn default() -> Self {
168 Self {
169 position: Vec3::new(0.0, 5.0, 10.0),
170 fov: std::f32::consts::FRAC_PI_3,
171 near: 0.1,
172 far: 1500.0,
173 yaw: 0.0,
174 pitch: 0.0,
175 exposure: 1.0,
176 primary: true,
177 }
178 }
179}
180
181impl Bundle for CameraBundle {
182 fn get_infos() -> Vec<gizmo_core::archetype::ComponentInfo> { vec![] }
183 unsafe fn write_to_archetype(self, _arch: &mut gizmo_core::archetype::Archetype, _row: usize, _tick: u32) {}
184 fn apply(self, world: &mut World, entity: Entity) {
185 world.add_component(entity, Transform::new(self.position));
186 world.add_component(entity, gizmo_physics_core::components::GlobalTransform::default());
187 let mut cam = Camera::new(
188 self.fov,
189 self.near,
190 self.far,
191 self.yaw,
192 self.pitch,
193 self.primary,
194 );
195 cam.exposure = self.exposure;
196 world.add_component(entity, cam);
197 }
198}
199
200pub struct MeshBundle {
214 pub position: Vec3,
215 pub rotation: Quat,
216 pub scale: Vec3,
217 pub mesh: crate::core::asset::Handle<Mesh>,
218 pub material: crate::core::asset::Handle<Material>,
219 pub name: Option<String>,
220}
221
222impl MeshBundle {
223 pub fn new(
225 mesh: crate::core::asset::Handle<Mesh>,
226 material: crate::core::asset::Handle<Material>,
227 ) -> Self {
228 Self {
229 position: Vec3::ZERO,
230 rotation: Quat::IDENTITY,
231 scale: Vec3::ONE,
232 mesh,
233 material,
234 name: None,
235 }
236 }
237
238 pub fn at(mut self, position: Vec3) -> Self {
240 self.position = position;
241 self
242 }
243
244 pub fn with_rotation(mut self, rotation: Quat) -> Self {
246 self.rotation = rotation;
247 self
248 }
249
250 pub fn with_scale(mut self, scale: Vec3) -> Self {
252 self.scale = scale;
253 self
254 }
255
256 pub fn with_name(mut self, name: &str) -> Self {
258 self.name = Some(name.to_string());
259 self
260 }
261}
262
263impl Bundle for MeshBundle {
264 fn get_infos() -> Vec<gizmo_core::archetype::ComponentInfo> { vec![] }
265 unsafe fn write_to_archetype(self, _arch: &mut gizmo_core::archetype::Archetype, _row: usize, _tick: u32) {}
266 fn apply(self, world: &mut World, entity: Entity) {
267 world.add_component(
268 entity,
269 Transform::new(self.position)
270 .with_rotation(self.rotation)
271 .with_scale(self.scale),
272 );
273 world.add_component(entity, gizmo_physics_core::components::GlobalTransform::default());
274 world.add_component(entity, self.mesh);
275 world.add_component(entity, self.material);
276 world.add_component(entity, MeshRenderer::new());
277 if let Some(name) = self.name {
278 world.add_component(entity, EntityName(name));
279 }
280 }
281}
282
283use gizmo_physics_core::Collider;
288use gizmo_physics_rigid::components::{RigidBody, Velocity};
289
290#[derive(Debug, Clone, PartialEq, Default)]
293pub struct RigidBodyBundle {
294 pub rigid_body: RigidBody,
295 pub velocity: Velocity,
296 pub collider: Collider,
297}
298
299
300impl RigidBodyBundle {
301 pub fn dynamic(mass: f32) -> Self {
302 Self {
303 rigid_body: RigidBody::new(mass, true),
304 ..Default::default()
305 }
306 }
307
308 pub fn static_body() -> Self {
309 Self {
310 rigid_body: RigidBody::new_static(),
311 ..Default::default()
312 }
313 }
314
315 pub fn kinematic() -> Self {
319 Self {
320 rigid_body: RigidBody::new_kinematic(),
321 ..Default::default()
322 }
323 }
324
325 pub fn with_collider(mut self, collider: Collider) -> Self {
326 self.collider = collider;
327 self
328 }
329
330 pub fn with_velocity(mut self, linear: Vec3) -> Self {
332 self.velocity = Velocity::new(linear);
333 self
334 }
335
336 pub fn with_ccd(mut self) -> Self {
341 self.rigid_body.ccd_enabled = true;
342 self
343 }
344
345 pub fn with_air_drag(mut self, cd: f32, area: f32) -> Self {
349 self.rigid_body = self.rigid_body.with_air_drag(cd, area);
350 self
351 }
352
353 pub fn with_restitution(mut self, restitution: f32) -> Self {
356 self.collider = self.collider.with_restitution(restitution);
357 self
358 }
359
360 pub fn with_friction(mut self, friction: f32) -> Self {
362 self.collider = self.collider.with_friction(friction);
363 self
364 }
365
366 pub fn with_damping(mut self, linear: f32, angular: f32) -> Self {
369 self.rigid_body = self.rigid_body.with_damping(linear, angular);
370 self
371 }
372
373 pub fn with_gravity(mut self, enabled: bool) -> Self {
375 self.rigid_body = self.rigid_body.with_gravity(enabled);
376 self
377 }
378
379 pub fn with_center_of_mass(mut self, com: Vec3) -> Self {
381 self.rigid_body = self.rigid_body.with_center_of_mass(com);
382 self
383 }
384
385 pub fn lock_rotation(mut self) -> Self {
387 self.rigid_body = self.rigid_body.lock_rotation();
388 self
389 }
390
391 pub fn with_angular_velocity(mut self, angular: Vec3) -> Self {
393 self.velocity.angular = angular;
394 self
395 }
396}
397
398impl Bundle for RigidBodyBundle {
399 fn get_infos() -> Vec<gizmo_core::archetype::ComponentInfo> {
400 <(RigidBody, Velocity, Collider)>::get_infos()
401 }
402
403 unsafe fn write_to_archetype(self, arch: &mut gizmo_core::archetype::Archetype, row: usize, tick: u32) {
404 let mut rb = self.rigid_body;
405 rb.update_inertia_from_collider(&self.collider);
406 (rb, self.velocity, self.collider).write_to_archetype(arch, row, tick)
407 }
408
409 fn apply(self, world: &mut World, entity: Entity) {
410 let mut rb = self.rigid_body;
411 rb.update_inertia_from_collider(&self.collider);
416 (rb, self.velocity, self.collider).apply(world, entity)
417 }
418}
419
420#[cfg(test)]
421mod tests {
422 use super::*;
423
424 #[test]
425 fn ergonomic_collider_and_bundle_builders() {
426 let c = Collider::sphere(0.5).with_restitution(0.9).with_friction(0.3);
428 assert_eq!(c.material.restitution, 0.9);
429 assert_eq!(c.material.static_friction, 0.3);
430 assert_eq!(c.material.dynamic_friction, 0.3);
431
432 let b = RigidBodyBundle::dynamic(2.0)
434 .with_collider(Collider::sphere(0.5))
435 .with_air_drag(0.47, 0.8)
436 .with_restitution(0.85);
437 assert_eq!(b.rigid_body.drag_coefficient, 0.47);
438 assert_eq!(b.rigid_body.drag_area, 0.8);
439 assert_eq!(b.collider.material.restitution, 0.85);
440
441 let b2 = RigidBodyBundle::dynamic(1.0)
443 .with_damping(0.1, 0.2)
444 .with_gravity(false)
445 .lock_rotation()
446 .with_center_of_mass(Vec3::new(0.0, 0.5, 0.0))
447 .with_angular_velocity(Vec3::new(0.0, 3.0, 0.0));
448 assert_eq!(b2.rigid_body.linear_damping, 0.1);
449 assert!(!b2.rigid_body.use_gravity);
450 assert!(b2.rigid_body.lock_rotation_x);
451 assert_eq!(b2.rigid_body.center_of_mass, Vec3::new(0.0, 0.5, 0.0));
452 assert_eq!(b2.velocity.angular, Vec3::new(0.0, 3.0, 0.0));
453 }
454
455 #[test]
456 fn rigid_body_bundle_derives_inertia_from_collider() {
457 let mut world = World::new();
459 let e = world
460 .spawn_bundle(RigidBodyBundle::dynamic(2.0).with_collider(Collider::sphere(0.5)));
461
462 let rbs = world.borrow::<RigidBody>();
463 let rb = rbs.get(e.id()).expect("rigid body spawned");
464
465 assert!(
467 (rb.local_inertia - Vec3::splat(0.2)).length() < 1e-6,
468 "bundle must derive sphere inertia from the collider, got {:?}",
469 rb.local_inertia
470 );
471 assert!(
473 (rb.local_inertia - Vec3::splat(1.0)).length() > 1e-3,
474 "inertia must be derived, not left at the default"
475 );
476 }
477
478 #[test]
479 fn rigid_body_bundle_static_body_inertia_derivation_is_noop() {
480 let mut world = World::new();
482 let e = world
483 .spawn_bundle(RigidBodyBundle::static_body().with_collider(Collider::sphere(0.5)));
484 assert!(world.borrow::<RigidBody>().get(e.id()).is_some());
485 }
486}