1use crate::math::Vec3;
2use gizmo_physics_core::{Collider, ColliderShape, Transform, components::GpuPhysicsLink};
3use gizmo_physics_rigid::components::RigidBody;
4#[cfg(feature = "render")]
5use crate::renderer::Renderer;
6
7#[cfg(feature = "render")]
8pub fn physics_debug_system(world: &crate::core::World) {
9 if let Some(mut gizmos) = world.get_resource_mut::<crate::renderer::Gizmos>() {
10 fn draw_collider(
11 trans: &Transform,
12 col: &Collider,
13 color: [f32; 4],
14 gizmos: &mut crate::renderer::Gizmos,
15 ) {
16 match &col.shape {
17 gizmo_physics_core::ColliderShape::Box(b) => {
18 let h = b.half_extents;
19 let r = trans.rotation;
20 let p = trans.position;
21 let p0 = p + r.mul_vec3(Vec3::new(-h.x, -h.y, -h.z));
22 let p1 = p + r.mul_vec3(Vec3::new(h.x, -h.y, -h.z));
23 let p2 = p + r.mul_vec3(Vec3::new(h.x, h.y, -h.z));
24 let p3 = p + r.mul_vec3(Vec3::new(-h.x, h.y, -h.z));
25 let p4 = p + r.mul_vec3(Vec3::new(-h.x, -h.y, h.z));
26 let p5 = p + r.mul_vec3(Vec3::new(h.x, -h.y, h.z));
27 let p6 = p + r.mul_vec3(Vec3::new(h.x, h.y, h.z));
28 let p7 = p + r.mul_vec3(Vec3::new(-h.x, h.y, h.z));
29
30 gizmos.draw_line(p0, p1, color);
31 gizmos.draw_line(p1, p2, color);
32 gizmos.draw_line(p2, p3, color);
33 gizmos.draw_line(p3, p0, color);
34 gizmos.draw_line(p4, p5, color);
35 gizmos.draw_line(p5, p6, color);
36 gizmos.draw_line(p6, p7, color);
37 gizmos.draw_line(p7, p4, color);
38 gizmos.draw_line(p0, p4, color);
39 gizmos.draw_line(p1, p5, color);
40 gizmos.draw_line(p2, p6, color);
41 gizmos.draw_line(p3, p7, color);
42 }
43 gizmo_physics_core::ColliderShape::Sphere(s) => {
44 let r = s.radius;
45 let min = trans.position - Vec3::new(r, r, r);
46 let max = trans.position + Vec3::new(r, r, r);
47 gizmos.draw_box(min, max, color);
48 }
49 gizmo_physics_core::ColliderShape::ConvexHull(ch) => {
50 let r = trans.rotation;
51 let p = trans.position;
52 for face in ch.faces.iter() {
53 let p0 = p + r.mul_vec3(ch.vertices[face[0] as usize]);
54 let p1 = p + r.mul_vec3(ch.vertices[face[1] as usize]);
55 let p2 = p + r.mul_vec3(ch.vertices[face[2] as usize]);
56 gizmos.draw_line(p0, p1, color);
57 gizmos.draw_line(p1, p2, color);
58 gizmos.draw_line(p2, p0, color);
59 }
60 }
61 gizmo_physics_core::ColliderShape::Compound(shapes) => {
62 for (local_t, sub_shape) in shapes {
63 let world_pos = trans.position + trans.rotation.mul_vec3(local_t.position);
64 let world_rot = trans.rotation * local_t.rotation;
65 let sub_trans = gizmo_physics_core::Transform {
66 position: world_pos,
67 rotation: world_rot,
68 scale: trans.scale,
69 ..*trans
70 };
71 let temp_col = gizmo_physics_core::Collider::from_shape(*sub_shape.clone());
72 draw_collider(&sub_trans, &temp_col, color, gizmos);
73 }
74 }
75 gizmo_physics_core::ColliderShape::Plane(p) => {
76 let n = p.normal.normalize();
78 let up = if n.y.abs() < 0.99 {
79 Vec3::new(0.0, 1.0, 0.0)
80 } else {
81 Vec3::new(1.0, 0.0, 0.0)
82 };
83 let t1 = n.cross(up).normalize();
84 let t2 = n.cross(t1).normalize();
85 let signed = n.dot(trans.position) - p.distance;
87 let center = trans.position - n * signed;
88 let half = 40.0_f32;
89 let step = 4.0_f32;
90 let n_lines = (half * 2.0 / step) as i32;
91 for i in 0..=n_lines {
92 let o = -half + i as f32 * step;
93 gizmos.draw_line(center + t1 * o - t2 * half, center + t1 * o + t2 * half, color);
94 gizmos.draw_line(center + t2 * o - t1 * half, center + t2 * o + t1 * half, color);
95 }
96 }
97 _ => {
98 let min = trans.position - Vec3::new(1.0, 1.0, 1.0);
99 let max = trans.position + Vec3::new(1.0, 1.0, 1.0);
100 gizmos.draw_box(min, max, color);
101 }
102 }
103 }
104
105 if let Some(q) = world.query::<(
106 &gizmo_physics_core::Transform,
107 &gizmo_physics_core::Collider,
108 &gizmo_physics_rigid::components::RigidBody,
109 )>() {
110 for (_, (trans, col, rb)) in q.iter() {
111 let color = if rb.is_static() {
112 [0.5, 0.5, 0.5, 1.0]
113 } else if rb.is_sleeping {
114 [0.9, 0.1, 0.1, 1.0]
115 } else {
116 [0.1, 0.9, 0.1, 1.0]
117 };
118 draw_collider(trans, col, color, &mut gizmos);
119 }
120 }
121
122 if let Some(q) = world.query::<(
123 &gizmo_physics_core::Transform,
124 &gizmo_physics_core::Collider,
125 crate::core::query::Without<gizmo_physics_rigid::components::RigidBody>,
126 )>() {
127 for (_, (trans, col, _)) in q.iter() {
128 draw_collider(trans, col, [0.5, 0.5, 0.5, 1.0], &mut gizmos);
129 }
130 }
131
132 let draw_oriented_box = |trans: &gizmo_physics_core::Transform,
134 offset: gizmo_math::Vec3,
135 h: gizmo_math::Vec3,
136 color: [f32; 4],
137 gizmos: &mut crate::renderer::Gizmos| {
138 let p = trans.position + trans.rotation.mul_vec3(offset);
139 let r = trans.rotation;
140 let p0 = p + r.mul_vec3(Vec3::new(-h.x, -h.y, -h.z));
141 let p1 = p + r.mul_vec3(Vec3::new(h.x, -h.y, -h.z));
142 let p2 = p + r.mul_vec3(Vec3::new(h.x, h.y, -h.z));
143 let p3 = p + r.mul_vec3(Vec3::new(-h.x, h.y, -h.z));
144 let p4 = p + r.mul_vec3(Vec3::new(-h.x, -h.y, h.z));
145 let p5 = p + r.mul_vec3(Vec3::new(h.x, -h.y, h.z));
146 let p6 = p + r.mul_vec3(Vec3::new(h.x, h.y, h.z));
147 let p7 = p + r.mul_vec3(Vec3::new(-h.x, h.y, h.z));
148
149 gizmos.draw_line(p0, p1, color);
150 gizmos.draw_line(p1, p2, color);
151 gizmos.draw_line(p2, p3, color);
152 gizmos.draw_line(p3, p0, color);
153 gizmos.draw_line(p4, p5, color);
154 gizmos.draw_line(p5, p6, color);
155 gizmos.draw_line(p6, p7, color);
156 gizmos.draw_line(p7, p4, color);
157 gizmos.draw_line(p0, p4, color);
158 gizmos.draw_line(p1, p5, color);
159 gizmos.draw_line(p2, p6, color);
160 gizmos.draw_line(p3, p7, color);
161 };
162
163 if let Some(q) = world.query::<(&gizmo_physics_core::Transform, &gizmo_physics_core::components::Hitbox)>() {
164 for (_, (trans, hitbox)) in q.iter() {
165 if hitbox.active {
166 draw_oriented_box(trans, hitbox.offset, hitbox.half_extents, [1.0, 0.0, 0.0, 1.0], &mut gizmos); }
168 }
169 }
170
171 if let Some(q) = world.query::<(&gizmo_physics_core::Transform, &gizmo_physics_core::components::Hurtbox)>() {
172 for (_, (trans, hurtbox)) in q.iter() {
173 draw_oriented_box(trans, hurtbox.offset, hurtbox.half_extents, [0.0, 1.0, 0.0, 1.0], &mut gizmos); }
175 }
176
177 let soft_color = [1.0, 0.4, 0.8, 1.0]; #[cfg(feature = "physics-soft")]
179 if let Some(q) = world.query::<&gizmo_physics_soft::SoftBodyMesh>() {
180 for (_, sm) in q.iter() {
181 for elem in &sm.elements {
182 let p0 = sm.nodes[elem.node_indices[0] as usize].position;
183 let p1 = sm.nodes[elem.node_indices[1] as usize].position;
184 let p2 = sm.nodes[elem.node_indices[2] as usize].position;
185 let p3 = sm.nodes[elem.node_indices[3] as usize].position;
186
187 gizmos.draw_line(p0, p1, soft_color);
189 gizmos.draw_line(p0, p2, soft_color);
190 gizmos.draw_line(p0, p3, soft_color);
191 gizmos.draw_line(p1, p2, soft_color);
192 gizmos.draw_line(p1, p3, soft_color);
193 gizmos.draw_line(p2, p3, soft_color);
194 }
195 }
196 }
197
198 #[cfg(feature = "physics-dynamics")]
200 if let Some(q) = world.query::<(
201 &gizmo_physics_core::Transform,
202 &gizmo_physics_dynamics::VehicleController,
203 )>() {
204 for (_, (trans, vehicle)) in q.iter() {
205 for wheel in &vehicle.wheels {
206 let attach_world =
207 trans.position + trans.rotation.mul_vec3(wheel.attachment_local_pos);
208 let ray_dir = trans.rotation.mul_vec3(wheel.direction_local).normalize();
209 let ray_end = attach_world
210 + ray_dir
211 * (wheel.suspension_rest_length
212 + wheel.suspension_max_travel
213 + wheel.radius);
214
215 gizmos.draw_line(attach_world, ray_end, [1.0, 1.0, 0.0, 1.0]);
217
218 if wheel.is_grounded {
219 if let Some(hit) = &wheel.ground_hit {
220 let force_dir = -ray_dir;
222 let force_len = (wheel.suspension_force / 10000.0).clamp(0.1, 2.0);
223 let arrow_end = hit.point + force_dir * force_len;
224 gizmos.draw_line(hit.point, arrow_end, [0.0, 0.0, 1.0, 1.0]);
225
226 let wheel_center = attach_world + ray_dir * wheel.suspension_length;
228 gizmos.draw_line(wheel_center, hit.point, [1.0, 0.5, 0.0, 1.0]);
229 }
230 }
231 }
232 }
233 }
234
235 if let Some(phys_world) = world.get_resource::<gizmo_physics_rigid::world::PhysicsWorld>() {
240 for event in phys_world.collision_events() {
241 for contact in &event.contact_points {
242 let p1 = contact.point;
243 let p2 = contact.point + contact.normal * 0.5; gizmos.draw_line(p1, p2, [1.0, 0.0, 0.0, 1.0]); let p_pen = contact.point - contact.normal * contact.penetration;
247 gizmos.draw_line(p1, p_pen, [1.0, 0.0, 1.0, 1.0]); }
249 }
250 }
251 tracing::info!(
252 "physics_debug_system: gizmos lines count = {}",
253 gizmos.lines.len()
254 );
255 }
256}
257
258static NEXT_STATIC_COLLIDER_SLOT: std::sync::atomic::AtomicU32 =
262 std::sync::atomic::AtomicU32::new(3);
263
264#[cfg(feature = "render")]
265pub fn gpu_physics_submit_system(world: &mut crate::core::World, renderer: &Renderer) {
266 use gizmo_physics_rigid::components::Velocity;
267
268 if let Some(physics) = &renderer.gpu_physics {
269 let mut unlinked_entities = Vec::new();
270 if let Some(q) = world.query::<(&RigidBody, &Transform, &Collider)>() {
271 let links = world.borrow::<GpuPhysicsLink>();
272 let velocities = world.borrow::<Velocity>();
273 for (e, (rb, trans, col)) in q.iter() {
274 if links.get(e).is_none() {
275 let vel = velocities.get(e).copied().unwrap_or_default();
276 unlinked_entities.push((e, *rb, *trans, col.clone(), vel));
277 }
278 }
279 }
280
281 let mut next_dynamic_id = world
282 .query::<&GpuPhysicsLink>()
283 .map(|q| q.iter().count() as u32)
284 .unwrap_or(0);
285
286 for (e, rb, trans, col, vel) in unlinked_entities {
287 if matches!(col.shape, ColliderShape::Plane(_)) {
288 let slot =
290 NEXT_STATIC_COLLIDER_SLOT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
291 if slot >= 100 {
292 tracing::error!("[GpuPhysics] Statik collider slot limiti (100) aşıldı, collider atlanıyor.");
293 NEXT_STATIC_COLLIDER_SLOT.fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
294 continue;
295 }
296
297 let gpu_col = gizmo_renderer::gpu_physics::GpuCollider {
298 shape_type: match col.shape {
299 ColliderShape::Plane(_) => 1,
300 _ => 0, },
302 _pad1: [0; 3],
303 data1: match &col.shape {
304 ColliderShape::Plane(p) => [p.normal.x, p.normal.y, p.normal.z, 0.0],
305 ColliderShape::Box(b) => {
306 let min = trans.position - b.half_extents;
307 [min.x, min.y, min.z, 0.0]
308 }
309 _ => [0.0; 4],
310 },
311 data2: match &col.shape {
312 ColliderShape::Plane(p) => [p.distance, 0.0, 0.0, 0.0],
313 ColliderShape::Box(b) => {
314 let max = trans.position + b.half_extents;
315 [max.x, max.y, max.z, 0.0]
316 }
317 _ => [0.0; 4],
318 },
319 };
320 physics.update_collider(&renderer.queue, slot, &gpu_col);
321 } else {
322 let id = next_dynamic_id;
324 next_dynamic_id += 1;
325
326 let (extents, offset) = match &col.shape {
327 ColliderShape::Box(b) => ([b.half_extents.x, b.half_extents.y, b.half_extents.z], Vec3::ZERO),
328 ColliderShape::Compound(shapes) => {
329 if let Some((local_t, box_shape)) = shapes.first() {
330 if let ColliderShape::Box(b) = &**box_shape {
331 ([b.half_extents.x, b.half_extents.y, b.half_extents.z], local_t.position)
332 } else {
333 ([0.5, 0.5, 0.5], Vec3::ZERO)
334 }
335 } else {
336 ([0.5, 0.5, 0.5], Vec3::ZERO)
337 }
338 }
339 _ => ([0.5, 0.5, 0.5], Vec3::ZERO),
340 };
341
342 let world_pos = trans.position + trans.rotation.mul_vec3(offset);
343
344 let gpu_box = gizmo_renderer::gpu_physics::GpuBox {
345 position: [world_pos.x, world_pos.y, world_pos.z],
346 mass: rb.mass,
347 velocity: [vel.linear.x, vel.linear.y, vel.linear.z],
348 state: 0,
349 rotation: [
350 trans.rotation.x,
351 trans.rotation.y,
352 trans.rotation.z,
353 trans.rotation.w,
354 ],
355 angular_velocity: [vel.angular.x, vel.angular.y, vel.angular.z],
356 sleep_counter: if rb.is_sleeping { 60 } else { 0 },
357 color: [0.3, 0.8, 1.0, 1.0],
358 half_extents: extents,
359 _pad: 0,
360 };
361 physics.update_box(&renderer.queue, id, &gpu_box);
362
363 world.add_component(world.get_entity(e).unwrap(), GpuPhysicsLink { id });
364 }
365 }
366 }
367}
368
369pub fn gpu_physics_readback_system(world: &mut crate::core::World, renderer: &Renderer) {
372 if let Some(physics) = &renderer.gpu_physics {
373 if let Some(gpu_data) = physics.poll_readback_data(&renderer.device) {
374 if let Some(mut q) =
375 world.query_mut::<(gizmo_core::prelude::Mut<Transform>, &GpuPhysicsLink, &gizmo_physics_core::Collider)>()
376 {
377 for (_, (mut trans, link, col)) in q.iter_mut() {
378 let idx = link.id as usize;
379 if idx < gpu_data.len() {
380 let box_data = &gpu_data[idx];
381
382 let offset = match &col.shape {
383 gizmo_physics_core::ColliderShape::Compound(shapes) => {
384 if let Some((local_t, _)) = shapes.first() {
385 local_t.position
386 } else {
387 gizmo_math::Vec3::ZERO
388 }
389 }
390 _ => gizmo_math::Vec3::ZERO,
391 };
392
393 let new_rot = gizmo_math::Quat::from_xyzw(
394 box_data.rotation[0],
395 box_data.rotation[1],
396 box_data.rotation[2],
397 box_data.rotation[3],
398 );
399
400 trans.position = gizmo_math::Vec3::new(
401 box_data.position[0],
402 box_data.position[1],
403 box_data.position[2],
404 ) - new_rot.mul_vec3(offset);
405
406 trans.rotation = new_rot;
407 trans.update_local_matrix();
408 }
409 }
410 }
411 }
412 }
413}
414
415pub fn cpu_physics_step_system(world: &crate::core::World, dt: f32) {
418 gizmo_physics_rigid::system::physics_step_system(world, dt);
419
420 #[cfg(feature = "physics-soft")]
421 {
422 let gravity = world.get_resource::<gizmo_physics_rigid::world::PhysicsWorld>()
424 .map(|w| w.integrator.gravity)
425 .unwrap_or(gizmo_math::Vec3::new(0.0, -9.81, 0.0));
426
427 gizmo_physics_soft::system::soft_body_step_system(world, dt, gravity);
428 gizmo_physics_soft::system::cloth_step_system(world, dt, gravity);
429 gizmo_physics_soft::system::rope_step_system(world, dt, gravity);
430 }
431}
432
433pub struct PhysicsStepSystem;
440
441impl gizmo_core::system::System for PhysicsStepSystem {
442 fn access_info(&self) -> gizmo_core::system::AccessInfo {
443 let mut info = gizmo_core::system::AccessInfo::new();
444 info.is_exclusive = true;
446 info
447 }
448
449 fn run(&mut self, world: &crate::core::World, dt: f32) {
450 cpu_physics_step_system(world, dt);
451 }
452}
453
454#[cfg(test)]
455mod physics_step_tests {
456 use super::*;
457 use crate::core::World;
458 use gizmo_core::system::System;
459 use gizmo_physics_rigid::components::Velocity;
460 use gizmo_physics_rigid::world::PhysicsWorld;
461
462 #[test]
463 fn physics_step_system_advances_the_world_under_gravity() {
464 let mut world = World::new();
467 world.insert_resource(PhysicsWorld::new().with_gravity(Vec3::new(0.0, -9.81, 0.0)));
468
469 let body = world.spawn();
470 world.add_component(body, Transform::new(Vec3::new(0.0, 10.0, 0.0)));
471 world.add_component(body, RigidBody::new(1.0, true));
472 world.add_component(body, Velocity::default());
473 world.add_component(body, Collider::sphere(0.5));
474
475 let y0 = world.borrow::<Transform>().get(body.id()).unwrap().position.y;
476 let mut sys = PhysicsStepSystem;
477 for _ in 0..30 {
478 sys.run(&world, 1.0 / 60.0);
479 }
480 let y1 = world.borrow::<Transform>().get(body.id()).unwrap().position.y;
481 assert!(y1 < y0 - 0.5, "body must fall under gravity via the scheduled step: {y0} -> {y1}");
482 }
483}