khora_data/ecs/systems/
physics_world_writeback.rs1use std::sync::{Arc, Mutex};
25
26use khora_core::ecs::entity::EntityId;
27use khora_core::lane::OutputDeck;
28use khora_core::physics::{CharacterControllerOptions, PhysicsProvider};
29use khora_core::Runtime;
30
31use crate::ecs::{
32 Collider, CollisionEvents, DataSystemRegistration, KinematicCharacterController, RigidBody,
33 TickPhase, Transform, World,
34};
35use crate::flow::PhysicsStepResult;
36
37fn physics_world_writeback(world: &mut World, runtime: &Runtime, deck: &mut OutputDeck) {
38 if !deck.contains::<PhysicsStepResult>() {
43 return;
44 }
45 let _step = deck.take::<PhysicsStepResult>();
46
47 let Some(provider_arc) = runtime
48 .backends
49 .get::<Arc<Mutex<Box<dyn PhysicsProvider>>>>()
50 .cloned()
51 else {
52 return;
53 };
54 let guard = match provider_arc.lock() {
55 Ok(g) => g,
56 Err(e) => {
57 log::error!("physics_world_writeback: provider mutex poisoned: {}", e);
58 return;
59 }
60 };
61 let provider = guard.as_ref();
62
63 sync_from_provider(world, provider);
64 resolve_characters(world, provider);
65 dispatch_collision_events(world, provider);
66}
67
68fn sync_from_provider(world: &mut World, provider: &dyn PhysicsProvider) {
70 for (transform, rb) in world.query_mut::<(&mut Transform, &mut RigidBody)>() {
71 if let Some(handle) = rb.handle {
72 let (pos, rot) = provider.get_body_transform(handle);
73 transform.translation = pos;
74 transform.rotation = rot;
75 }
76 }
77}
78
79fn resolve_characters(world: &mut World, provider: &dyn PhysicsProvider) {
81 let mut results = Vec::new();
82 {
83 let query = world.query_mut::<(EntityId, &mut KinematicCharacterController, &Collider)>();
84 for (id, kcc, collider) in query {
85 if let Some(h) = collider.handle {
86 let options = CharacterControllerOptions {
87 autostep_height: kcc.autostep_height,
88 autostep_min_width: kcc.autostep_min_width,
89 autostep_enabled: kcc.autostep_enabled,
90 max_slope_climb_angle: kcc.max_slope_climb_angle,
91 min_slope_slide_angle: kcc.min_slope_slide_angle,
92 offset: kcc.offset,
93 };
94 let (m, g) = provider.move_character(h, kcc.desired_translation, &options);
95 results.push((id, m, g));
96 }
97 }
98 }
99
100 for (id, m, g) in results {
101 if let Some(kcc) = world.get_mut::<KinematicCharacterController>(id) {
102 kcc.is_grounded = g;
103 kcc.desired_translation = khora_core::math::Vec3::ZERO;
104 }
105 if let Some(transform) = world.get_mut::<Transform>(id) {
106 transform.translation = transform.translation + m;
107 }
108 }
109}
110
111fn dispatch_collision_events(world: &mut World, provider: &dyn PhysicsProvider) {
114 let events = provider.get_collision_events();
115 for (_, buffer) in world.query_mut::<(EntityId, &mut CollisionEvents)>() {
116 if events.is_empty() {
117 buffer.events.clear();
118 } else {
119 buffer.events = events.clone();
120 }
121 }
122}
123
124inventory::submit! {
125 DataSystemRegistration {
126 name: "physics_world_writeback",
127 phase: TickPhase::Maintenance,
128 run: physics_world_writeback,
129 order_hint: 0,
130 runs_after: &[],
131 }
132}