1use crate::palette::WHITE;
5use crate::runner::MATERIAL_PREFIX;
6use nightshade::prelude::nalgebra_glm::Mat4;
7use nightshade::prelude::*;
8use serde::{Deserialize, Serialize};
9
10pub use nightshade::prelude::despawn_recursive_immediate as despawn;
11pub use nightshade::prelude::spawn_cone_at as spawn_cone;
12pub use nightshade::prelude::spawn_cube_at as spawn_cube;
13pub use nightshade::prelude::spawn_cylinder_at as spawn_cylinder;
14pub use nightshade::prelude::spawn_plane_at as spawn_plane;
15pub use nightshade::prelude::spawn_sphere_at as spawn_sphere;
16pub use nightshade::prelude::spawn_torus_at as spawn_torus;
17
18#[derive(
20 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize, enum2schema::Schema,
21)]
22pub enum Shape {
23 #[default]
24 Cube,
25 Sphere,
26 Cylinder,
27 Cone,
28 Torus,
29 Plane,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize, enum2schema::Schema)]
39pub enum Body {
40 #[default]
41 None,
42 Static,
43 Dynamic {
44 mass: f32,
45 },
46}
47
48pub struct Object {
60 pub shape: Shape,
61 pub position: Vec3,
62 pub scale: Vec3,
63 pub color: [f32; 4],
64 pub body: Body,
65}
66
67impl Default for Object {
68 fn default() -> Self {
69 Self {
70 shape: Shape::Cube,
71 position: Vec3::zeros(),
72 scale: Vec3::new(1.0, 1.0, 1.0),
73 color: WHITE,
74 body: Body::None,
75 }
76 }
77}
78
79pub fn spawn_object(world: &mut World, object: Object) -> Entity {
81 let entity = spawn_mesh_at(
82 world,
83 mesh_name(object.shape),
84 object.position,
85 object.scale,
86 );
87 crate::appearance::set_color(world, entity, object.color);
88 match object.body {
89 Body::None => {}
90 #[cfg(feature = "physics")]
91 Body::Static => {
92 let collider = static_collider(world, object.shape, object.scale)
93 .with_friction(0.8)
94 .with_restitution(0.1);
95 attach_body(
96 world,
97 entity,
98 RigidBodyComponent::new_static().with_translation(
99 object.position.x,
100 object.position.y,
101 object.position.z,
102 ),
103 collider,
104 false,
105 );
106 }
107 #[cfg(feature = "physics")]
108 Body::Dynamic { mass } => {
109 let collider = dynamic_collider(world, object.shape, object.scale)
110 .with_friction(0.7)
111 .with_restitution(0.2);
112 attach_body(
113 world,
114 entity,
115 RigidBodyComponent::new_dynamic()
116 .with_translation(object.position.x, object.position.y, object.position.z)
117 .with_mass(mass),
118 collider,
119 true,
120 );
121 }
122 #[cfg(not(feature = "physics"))]
123 Body::Static | Body::Dynamic { .. } => {}
124 }
125 entity
126}
127
128#[cfg(feature = "physics")]
134pub fn spawn_capsule_body(
135 world: &mut World,
136 position: Vec3,
137 half_height: f32,
138 radius: f32,
139 mass: f32,
140 color: [f32; 4],
141) -> Entity {
142 let scale = Vec3::new(radius * 2.0, (half_height + radius) * 2.0, radius * 2.0);
143 let entity = spawn_mesh_at(world, mesh_name(Shape::Cylinder), position, scale);
144 crate::appearance::set_color(world, entity, color);
145 let collider = ColliderComponent::new_capsule(half_height, radius)
146 .with_friction(0.7)
147 .with_restitution(0.2);
148 attach_body(
149 world,
150 entity,
151 RigidBodyComponent::new_dynamic()
152 .with_translation(position.x, position.y, position.z)
153 .with_mass(mass),
154 collider,
155 true,
156 );
157 entity
158}
159
160#[cfg(feature = "physics")]
166pub fn spawn_custom_mesh_collider(
167 world: &mut World,
168 name: &str,
169 vertices: &[([f32; 3], [f32; 3], [f32; 2])],
170 indices: &[u32],
171 position: Vec3,
172) -> Entity {
173 let entity = crate::mesh::spawn_custom_mesh(world, name, vertices, indices, position);
174 let positions: Vec<[f32; 3]> = vertices.iter().map(|(point, _, _)| *point).collect();
175 let triangles: Vec<[u32; 3]> = indices
176 .chunks_exact(3)
177 .map(|chunk| [chunk[0], chunk[1], chunk[2]])
178 .collect();
179 let collider = ColliderComponent {
180 shape: nightshade::plugins::physics::components::ColliderShape::TriMesh {
181 vertices: positions,
182 indices: triangles,
183 },
184 ..Default::default()
185 };
186 attach_body(
187 world,
188 entity,
189 RigidBodyComponent::new_static().with_translation(position.x, position.y, position.z),
190 collider,
191 false,
192 );
193 entity
194}
195
196pub fn spawn_objects(world: &mut World, object: Object, positions: &[Vec3]) -> Vec<Entity> {
200 let mut entities = Vec::with_capacity(positions.len());
201 let mut shared_material: Option<String> = None;
202 #[cfg(feature = "physics")]
203 let mut collider_template: Option<ColliderComponent> = None;
204
205 for &position in positions {
206 let entity = spawn_mesh_at(world, mesh_name(object.shape), position, object.scale);
207 match shared_material.as_deref() {
208 None => {
209 crate::appearance::set_color(world, entity, object.color);
210 shared_material = world
211 .get::<nightshade::ecs::material::components::MaterialRef>(entity)
212 .map(|material_ref| material_ref.name.clone());
213 }
214 Some(name) => {
215 let name = name.to_string();
216 adopt_shared_material(world, entity, &name);
217 }
218 }
219
220 #[cfg(feature = "physics")]
221 match object.body {
222 Body::None => {}
223 Body::Static => {
224 let collider = collider_template
225 .get_or_insert_with(|| {
226 static_collider(world, object.shape, object.scale)
227 .with_friction(0.8)
228 .with_restitution(0.1)
229 })
230 .clone();
231 attach_body(
232 world,
233 entity,
234 RigidBodyComponent::new_static()
235 .with_translation(position.x, position.y, position.z),
236 collider,
237 false,
238 );
239 }
240 Body::Dynamic { mass } => {
241 let collider = collider_template
242 .get_or_insert_with(|| {
243 dynamic_collider(world, object.shape, object.scale)
244 .with_friction(0.7)
245 .with_restitution(0.2)
246 })
247 .clone();
248 attach_body(
249 world,
250 entity,
251 RigidBodyComponent::new_dynamic()
252 .with_translation(position.x, position.y, position.z)
253 .with_mass(mass),
254 collider,
255 true,
256 );
257 }
258 }
259
260 entities.push(entity);
261 }
262 entities
263}
264
265pub fn spawn_instanced(
270 world: &mut World,
271 shape: Shape,
272 transforms: Vec<InstanceTransform>,
273 color: [f32; 4],
274) -> Entity {
275 let shape_mesh_name = mesh_name(shape);
276 ensure_primitive_mesh(world, shape_mesh_name);
277 let fallback = format!(
278 "api::material::instanced::{:.4}_{:.4}_{:.4}_{:.4}",
279 color[0], color[1], color[2], color[3]
280 );
281 let material_name = nightshade::ecs::material::resources::material_registry_find_or_insert(
282 &mut world
283 .res_mut::<nightshade::ecs::asset_state::AssetState>()
284 .material_registry,
285 fallback,
286 Material {
287 base_color: color,
288 ..Default::default()
289 },
290 );
291 spawn_instanced_mesh_with_material(world, shape_mesh_name, transforms, &material_name)
292}
293
294pub fn spawn_instanced_with_material(
300 world: &mut World,
301 shape: Shape,
302 transforms: Vec<InstanceTransform>,
303 material: &str,
304) -> Entity {
305 let shape_mesh_name = mesh_name(shape);
306 ensure_primitive_mesh(world, shape_mesh_name);
307 spawn_instanced_mesh_with_material(world, shape_mesh_name, transforms, material)
308}
309
310pub fn set_instances(world: &mut World, batch: Entity, transforms: Vec<InstanceTransform>) {
314 if let Some(instanced) =
315 world.get_mut::<nightshade::ecs::mesh::components::InstancedMesh>(batch)
316 {
317 instanced.set_instances(transforms);
318 }
319 world
320 .res_mut::<nightshade::render::mesh_state::MeshRenderState>()
321 .mark_instanced_meshes_changed();
322}
323
324fn adopt_shared_material(world: &mut World, entity: Entity, name: &str) {
325 let previous = world
326 .get::<nightshade::ecs::material::components::MaterialRef>(entity)
327 .map(|material_ref| material_ref.name.clone());
328 if let Some(previous_name) = previous
329 && let Some((index, _)) = registry_lookup_index(
330 &world
331 .res::<nightshade::ecs::asset_state::AssetState>()
332 .material_registry
333 .registry,
334 &previous_name,
335 )
336 {
337 registry_remove_reference(
338 &mut world
339 .res_mut::<nightshade::ecs::asset_state::AssetState>()
340 .material_registry
341 .registry,
342 index,
343 );
344 }
345 if let Some((index, _)) = registry_lookup_index(
346 &world
347 .res::<nightshade::ecs::asset_state::AssetState>()
348 .material_registry
349 .registry,
350 name,
351 ) {
352 registry_add_reference(
353 &mut world
354 .res_mut::<nightshade::ecs::asset_state::AssetState>()
355 .material_registry
356 .registry,
357 index,
358 );
359 }
360 world.set(entity, MaterialRef::new(name.to_string()));
361 world
362 .res_mut::<nightshade::render::mesh_state::MeshRenderState>()
363 .mark_entity_added(render_entity(entity));
364}
365
366pub(crate) fn ensure_primitive_mesh(world: &mut World, mesh_name: &str) {
367 use nightshade::render::procedural_meshes::{
368 create_cone_mesh, create_cube_mesh, create_cylinder_mesh, create_plane_mesh,
369 create_sphere_mesh, create_torus_mesh,
370 };
371 if !world
372 .res::<nightshade::ecs::asset_state::AssetState>()
373 .mesh_cache
374 .registry
375 .name_to_index
376 .contains_key(mesh_name)
377 {
378 let mesh = match mesh_name {
379 "Cube" => create_cube_mesh(),
380 "Sphere" => create_sphere_mesh(1.0, 16),
381 "Plane" => create_plane_mesh(2.0),
382 "Torus" => create_torus_mesh(1.0, 0.3, 32, 16),
383 "Cylinder" => create_cylinder_mesh(0.5, 1.0, 16),
384 _ => create_cone_mesh(0.5, 1.0, 16),
385 };
386 mesh_cache_insert(
387 &mut world
388 .res_mut::<nightshade::ecs::asset_state::AssetState>()
389 .mesh_cache,
390 mesh_name.to_string(),
391 mesh,
392 );
393 }
394 if let Some((index, _)) = registry_lookup_index(
395 &world
396 .res::<nightshade::ecs::asset_state::AssetState>()
397 .mesh_cache
398 .registry,
399 mesh_name,
400 ) {
401 registry_add_reference(
402 &mut world
403 .res_mut::<nightshade::ecs::asset_state::AssetState>()
404 .mesh_cache
405 .registry,
406 index,
407 );
408 }
409}
410
411pub fn spawn_cloth_sheet(world: &mut World, position: Vec3, width: f32, height: f32) -> Entity {
416 spawn_cloth(
417 world,
418 Cloth {
419 width,
420 height,
421 ..Default::default()
422 },
423 position,
424 "Cloth".to_string(),
425 )
426}
427
428pub fn set_visible(world: &mut World, entity: Entity, visible: bool) {
430 if let Some(visibility) = world.get_mut::<nightshade::ecs::primitives::Visibility>(entity) {
431 visibility.visible = visible;
432 }
433}
434
435pub fn spawn_floor(world: &mut World, half_extent: f32) -> Entity {
439 let entity = spawn_mesh_at(
440 world,
441 "Plane",
442 Vec3::zeros(),
443 Vec3::new(half_extent, 1.0, half_extent),
444 );
445 #[cfg(feature = "physics")]
446 attach_body(
447 world,
448 entity,
449 RigidBodyComponent::new_static().with_translation(0.0, -0.05, 0.0),
450 ColliderComponent::new_cuboid(half_extent, 0.05, half_extent)
451 .with_friction(0.8)
452 .with_restitution(0.1),
453 false,
454 );
455 entity
456}
457
458pub fn spawn_model(world: &mut World, glb_bytes: &[u8], position: Vec3) -> Entity {
461 let mut result =
462 import_gltf_from_bytes(glb_bytes).expect("failed to import the glb model bytes");
463 nightshade::ecs::loading::queue_gltf_load(world, &mut result);
464 let prefab = &result.prefabs[0];
465 nightshade::ecs::prefab::commands::spawn_prefab_with_skins(
466 world,
467 prefab,
468 &result.animations,
469 &result.skins,
470 position,
471 )
472}
473
474pub fn play_animation(world: &mut World, entity: Entity, clip_index: usize) {
476 if let Some(player) =
477 world.get_mut::<nightshade::ecs::animation::components::AnimationPlayer>(entity)
478 {
479 player.play(clip_index);
480 }
481}
482
483pub fn set_animation_looping(world: &mut World, entity: Entity, looping: bool) {
485 if let Some(player) =
486 world.get_mut::<nightshade::ecs::animation::components::AnimationPlayer>(entity)
487 {
488 player.looping = looping;
489 }
490}
491
492pub fn play_animation_named(world: &mut World, entity: Entity, clip_name: &str) -> bool {
495 if let Some(player) =
496 world.get_mut::<nightshade::ecs::animation::components::AnimationPlayer>(entity)
497 && let Some(index) = player.clips.iter().position(|clip| clip.name == clip_name)
498 {
499 player.play(index);
500 return true;
501 }
502 false
503}
504
505pub fn set_animation_speed(world: &mut World, entity: Entity, speed: f32) {
508 if let Some(player) =
509 world.get_mut::<nightshade::ecs::animation::components::AnimationPlayer>(entity)
510 {
511 player.speed = speed;
512 }
513}
514
515pub fn blend_to_animation(world: &mut World, entity: Entity, clip_index: usize, seconds: f32) {
518 if let Some(player) =
519 world.get_mut::<nightshade::ecs::animation::components::AnimationPlayer>(entity)
520 {
521 player.blend_to(clip_index, seconds);
522 }
523}
524
525pub fn blend_to_animation_named(
528 world: &mut World,
529 entity: Entity,
530 clip_name: &str,
531 seconds: f32,
532) -> bool {
533 if let Some(player) =
534 world.get_mut::<nightshade::ecs::animation::components::AnimationPlayer>(entity)
535 && let Some(index) = player.clips.iter().position(|clip| clip.name == clip_name)
536 {
537 player.blend_to(index, seconds);
538 return true;
539 }
540 false
541}
542
543pub fn pause_animation(world: &mut World, entity: Entity) {
545 if let Some(player) =
546 world.get_mut::<nightshade::ecs::animation::components::AnimationPlayer>(entity)
547 {
548 player.pause();
549 }
550}
551
552pub fn resume_animation(world: &mut World, entity: Entity) {
554 if let Some(player) =
555 world.get_mut::<nightshade::ecs::animation::components::AnimationPlayer>(entity)
556 {
557 player.resume();
558 }
559}
560
561pub fn stop_animation(world: &mut World, entity: Entity) {
563 if let Some(player) =
564 world.get_mut::<nightshade::ecs::animation::components::AnimationPlayer>(entity)
565 {
566 player.stop();
567 }
568}
569
570pub fn animation_clips(world: &World, entity: Entity) -> Vec<String> {
572 world
573 .get::<nightshade::ecs::animation::components::AnimationPlayer>(entity)
574 .map(|player| player.clips.iter().map(|clip| clip.name.clone()).collect())
575 .unwrap_or_default()
576}
577
578pub fn add_animation_event(
584 world: &mut World,
585 entity: Entity,
586 clip_index: usize,
587 time: f32,
588 name: &str,
589) -> bool {
590 if let Some(player) =
591 world.get_mut::<nightshade::ecs::animation::components::AnimationPlayer>(entity)
592 && let Some(clip) = player.clips.get_mut(clip_index)
593 {
594 clip.events
595 .push(nightshade::ecs::animation::components::AnimationEvent {
596 time,
597 name: name.to_string(),
598 });
599 return true;
600 }
601 false
602}
603
604pub fn add_animation_event_named(
607 world: &mut World,
608 entity: Entity,
609 clip_name: &str,
610 time: f32,
611 name: &str,
612) -> bool {
613 if let Some(player) =
614 world.get_mut::<nightshade::ecs::animation::components::AnimationPlayer>(entity)
615 && let Some(clip) = player.clips.iter_mut().find(|clip| clip.name == clip_name)
616 {
617 clip.events
618 .push(nightshade::ecs::animation::components::AnimationEvent {
619 time,
620 name: name.to_string(),
621 });
622 return true;
623 }
624 false
625}
626
627pub fn add_animation_layer(
632 world: &mut World,
633 entity: Entity,
634 clip_index: usize,
635 weight: f32,
636) -> Option<usize> {
637 let player =
638 world.get_mut::<nightshade::ecs::animation::components::AnimationPlayer>(entity)?;
639 let index = player.layers.len();
640 player.layers.push(
641 nightshade::ecs::animation::components::AnimationLayer::new(clip_index).with_weight(weight),
642 );
643 Some(index)
644}
645
646pub fn add_animation_layer_masked(
650 world: &mut World,
651 entity: Entity,
652 clip_index: usize,
653 weight: f32,
654 bones: &[&str],
655) -> Option<usize> {
656 let player =
657 world.get_mut::<nightshade::ecs::animation::components::AnimationPlayer>(entity)?;
658 let index = player.layers.len();
659 let mask = bones.iter().map(|bone| bone.to_string()).collect();
660 player.layers.push(
661 nightshade::ecs::animation::components::AnimationLayer::new(clip_index)
662 .with_weight(weight)
663 .with_mask(mask),
664 );
665 Some(index)
666}
667
668pub fn set_animation_layer_weight(
671 world: &mut World,
672 entity: Entity,
673 layer_index: usize,
674 weight: f32,
675) {
676 if let Some(player) =
677 world.get_mut::<nightshade::ecs::animation::components::AnimationPlayer>(entity)
678 && let Some(layer) = player.layers.get_mut(layer_index)
679 {
680 layer.weight = weight;
681 }
682}
683
684pub fn clear_animation_layers(world: &mut World, entity: Entity) {
686 if let Some(player) =
687 world.get_mut::<nightshade::ecs::animation::components::AnimationPlayer>(entity)
688 {
689 player.layers.clear();
690 }
691}
692
693pub fn name_entity(world: &mut World, name: &str, entity: Entity) {
697 world
698 .res_mut::<nightshade::ecs::entity_registry::EntityRegistry>()
699 .names
700 .insert(name.to_string(), entity);
701}
702
703pub fn spawn_group(world: &mut World, position: Vec3) -> Entity {
706 let entity = spawn_entities(world, NAME | LOCAL_TRANSFORM | GLOBAL_TRANSFORM, 1)[0];
707 world.set(entity, Name("Group".to_string()));
708 world.set(
709 entity,
710 LocalTransform {
711 translation: position,
712 ..Default::default()
713 },
714 );
715 entity
716}
717
718pub fn set_parent(world: &mut World, child: Entity, parent: Option<Entity>) {
721 let child_world = crate::placement::world_matrix(world, child);
722 let parent_world = parent
723 .map(|parent_entity| crate::placement::world_matrix(world, parent_entity))
724 .unwrap_or_else(Mat4::identity);
725 let local = nalgebra_glm::inverse(&parent_world) * child_world;
726
727 let translation = nalgebra_glm::vec3(local[(0, 3)], local[(1, 3)], local[(2, 3)]);
728 let basis_x = nalgebra_glm::vec3(local[(0, 0)], local[(1, 0)], local[(2, 0)]);
729 let basis_y = nalgebra_glm::vec3(local[(0, 1)], local[(1, 1)], local[(2, 1)]);
730 let basis_z = nalgebra_glm::vec3(local[(0, 2)], local[(1, 2)], local[(2, 2)]);
731 let scale = nalgebra_glm::vec3(
732 basis_x.magnitude(),
733 basis_y.magnitude(),
734 basis_z.magnitude(),
735 );
736 let rotation_matrix = nalgebra_glm::Mat3::from_columns(&[
737 basis_x / scale.x.max(f32::EPSILON),
738 basis_y / scale.y.max(f32::EPSILON),
739 basis_z / scale.z.max(f32::EPSILON),
740 ]);
741 let rotation = nalgebra_glm::mat3_to_quat(&rotation_matrix);
742
743 if parent.is_some() {
744 world.ecs.worlds[CORE].add_components(child, PARENT);
745 }
746 update_parent(world, child, parent);
747 world.set(
748 child,
749 LocalTransform {
750 translation,
751 rotation,
752 scale,
753 },
754 );
755}
756
757#[cfg(feature = "picking")]
758pub(crate) fn is_reserved(world: &World, entity: Entity) -> bool {
759 world
760 .get::<nightshade::ecs::primitives::Name>(entity)
761 .is_some_and(|name| name.0.starts_with(crate::runner::RESERVED_PREFIX))
762}
763
764pub(crate) fn api_material_name(entity: Entity) -> String {
765 format!("{MATERIAL_PREFIX}{}", entity.id)
766}
767
768fn mesh_name(shape: Shape) -> &'static str {
769 match shape {
770 Shape::Cube => "Cube",
771 Shape::Sphere => "Sphere",
772 Shape::Cylinder => "Cylinder",
773 Shape::Cone => "Cone",
774 Shape::Torus => "Torus",
775 Shape::Plane => "Plane",
776 }
777}
778
779#[cfg(feature = "physics")]
780fn dynamic_collider(world: &World, shape: Shape, scale: Vec3) -> ColliderComponent {
781 match shape {
782 Shape::Cube => ColliderComponent::new_cuboid(scale.x * 0.5, scale.y * 0.5, scale.z * 0.5),
783 Shape::Sphere => ColliderComponent::new_ball(scale.x),
784 Shape::Cylinder => ColliderComponent::new_cylinder(scale.y * 0.5, scale.x * 0.5),
785 Shape::Cone => ColliderComponent::new_cone(scale.y * 0.5, scale.x * 0.5),
786 Shape::Torus => ColliderComponent {
787 shape: ColliderShape::ConvexMesh {
788 vertices: scaled_mesh_points(world, "Torus", scale),
789 },
790 ..Default::default()
791 },
792 Shape::Plane => ColliderComponent::new_cuboid(scale.x, 0.05, scale.z),
793 }
794}
795
796#[cfg(feature = "physics")]
797fn static_collider(world: &World, shape: Shape, scale: Vec3) -> ColliderComponent {
798 match shape {
799 Shape::Torus | Shape::Plane => {
800 let shape_mesh_name = mesh_name(shape);
801 ColliderComponent {
802 shape: ColliderShape::TriMesh {
803 vertices: scaled_mesh_points(world, shape_mesh_name, scale),
804 indices: mesh_triangles(world, shape_mesh_name),
805 },
806 ..Default::default()
807 }
808 }
809 _ => dynamic_collider(world, shape, scale),
810 }
811}
812
813#[cfg(feature = "physics")]
814fn scaled_mesh_points(world: &World, mesh_name: &str, scale: Vec3) -> Vec<[f32; 3]> {
815 registry_entry_by_name(
816 &world
817 .res::<nightshade::ecs::asset_state::AssetState>()
818 .mesh_cache
819 .registry,
820 mesh_name,
821 )
822 .map(|mesh| {
823 mesh.vertices
824 .iter()
825 .map(|vertex| {
826 [
827 vertex.position[0] * scale.x,
828 vertex.position[1] * scale.y,
829 vertex.position[2] * scale.z,
830 ]
831 })
832 .collect()
833 })
834 .unwrap_or_default()
835}
836
837#[cfg(feature = "physics")]
838fn mesh_triangles(world: &World, mesh_name: &str) -> Vec<[u32; 3]> {
839 registry_entry_by_name(
840 &world
841 .res::<nightshade::ecs::asset_state::AssetState>()
842 .mesh_cache
843 .registry,
844 mesh_name,
845 )
846 .map(|mesh| {
847 mesh.indices
848 .chunks_exact(3)
849 .map(|triangle| [triangle[0], triangle[1], triangle[2]])
850 .collect()
851 })
852 .unwrap_or_default()
853}
854
855#[cfg(feature = "physics")]
856fn attach_body(
857 world: &mut World,
858 entity: Entity,
859 body: RigidBodyComponent,
860 collider: ColliderComponent,
861 dynamic: bool,
862) {
863 let mut flags = RIGID_BODY | COLLIDER;
864 if dynamic {
865 flags |= COLLISION_LISTENER | PHYSICS_INTERPOLATION;
866 }
867 world.ecs.worlds[CORE].add_components(entity, flags);
868 world.set(entity, body);
869 world.set(entity, collider);
870 if dynamic {
871 reset_physics_interpolation(world, entity);
872 if let Some(interpolation) =
873 world.get_mut::<nightshade::plugins::physics::components::PhysicsInterpolation>(entity)
874 {
875 interpolation.enabled = true;
876 }
877 }
878}