Skip to main content

gizmo_engine/systems/
physics.rs

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 {
72                            shape: *sub_shape.clone(),
73                            ..Default::default()
74                        };
75                        draw_collider(&sub_trans, &temp_col, color, gizmos);
76                    }
77                }
78                _ => {
79                    let min = trans.position - Vec3::new(1.0, 1.0, 1.0);
80                    let max = trans.position + Vec3::new(1.0, 1.0, 1.0);
81                    gizmos.draw_box(min, max, color);
82                }
83            }
84        }
85
86        if let Some(q) = world.query::<(
87            &gizmo_physics_core::Transform,
88            &gizmo_physics_core::Collider,
89            &gizmo_physics_rigid::components::RigidBody,
90        )>() {
91            for (_, (trans, col, rb)) in q.iter() {
92                let color = if rb.is_static() {
93                    [0.5, 0.5, 0.5, 1.0]
94                } else if rb.is_sleeping {
95                    [0.9, 0.1, 0.1, 1.0]
96                } else {
97                    [0.1, 0.9, 0.1, 1.0]
98                };
99                draw_collider(trans, col, color, &mut gizmos);
100            }
101        }
102
103        if let Some(q) = world.query::<(
104            &gizmo_physics_core::Transform,
105            &gizmo_physics_core::Collider,
106            crate::core::query::Without<gizmo_physics_rigid::components::RigidBody>,
107        )>() {
108            for (_, (trans, col, _)) in q.iter() {
109                draw_collider(trans, col, [0.5, 0.5, 0.5, 1.0], &mut gizmos);
110            }
111        }
112
113        // --- Phase 5.5: Hitbox / Hurtbox Visuals ---
114        let draw_oriented_box = |trans: &gizmo_physics_core::Transform,
115                                 offset: gizmo_math::Vec3,
116                                 h: gizmo_math::Vec3,
117                                 color: [f32; 4],
118                                 gizmos: &mut crate::renderer::Gizmos| {
119            let p = trans.position + trans.rotation.mul_vec3(offset);
120            let r = trans.rotation;
121            let p0 = p + r.mul_vec3(Vec3::new(-h.x, -h.y, -h.z));
122            let p1 = p + r.mul_vec3(Vec3::new(h.x, -h.y, -h.z));
123            let p2 = p + r.mul_vec3(Vec3::new(h.x, h.y, -h.z));
124            let p3 = p + r.mul_vec3(Vec3::new(-h.x, h.y, -h.z));
125            let p4 = p + r.mul_vec3(Vec3::new(-h.x, -h.y, h.z));
126            let p5 = p + r.mul_vec3(Vec3::new(h.x, -h.y, h.z));
127            let p6 = p + r.mul_vec3(Vec3::new(h.x, h.y, h.z));
128            let p7 = p + r.mul_vec3(Vec3::new(-h.x, h.y, h.z));
129
130            gizmos.draw_line(p0, p1, color);
131            gizmos.draw_line(p1, p2, color);
132            gizmos.draw_line(p2, p3, color);
133            gizmos.draw_line(p3, p0, color);
134            gizmos.draw_line(p4, p5, color);
135            gizmos.draw_line(p5, p6, color);
136            gizmos.draw_line(p6, p7, color);
137            gizmos.draw_line(p7, p4, color);
138            gizmos.draw_line(p0, p4, color);
139            gizmos.draw_line(p1, p5, color);
140            gizmos.draw_line(p2, p6, color);
141            gizmos.draw_line(p3, p7, color);
142        };
143
144        if let Some(q) = world.query::<(&gizmo_physics_core::Transform, &gizmo_physics_core::components::Hitbox)>() {
145            for (_, (trans, hitbox)) in q.iter() {
146                if hitbox.active {
147                    draw_oriented_box(trans, hitbox.offset, hitbox.half_extents, [1.0, 0.0, 0.0, 1.0], &mut gizmos); // RED
148                }
149            }
150        }
151
152        if let Some(q) = world.query::<(&gizmo_physics_core::Transform, &gizmo_physics_core::components::Hurtbox)>() {
153            for (_, (trans, hurtbox)) in q.iter() {
154                draw_oriented_box(trans, hurtbox.offset, hurtbox.half_extents, [0.0, 1.0, 0.0, 1.0], &mut gizmos); // GREEN
155            }
156        }
157
158        let soft_color = [1.0, 0.4, 0.8, 1.0]; // Pinkish for soft body
159        #[cfg(feature = "physics-soft")]
160        if let Some(q) = world.query::<&gizmo_physics_soft::SoftBodyMesh>() {
161            for (_, sm) in q.iter() {
162                for elem in &sm.elements {
163                    let p0 = sm.nodes[elem.node_indices[0] as usize].position;
164                    let p1 = sm.nodes[elem.node_indices[1] as usize].position;
165                    let p2 = sm.nodes[elem.node_indices[2] as usize].position;
166                    let p3 = sm.nodes[elem.node_indices[3] as usize].position;
167
168                    // 6 edges of a tetrahedron
169                    gizmos.draw_line(p0, p1, soft_color);
170                    gizmos.draw_line(p0, p2, soft_color);
171                    gizmos.draw_line(p0, p3, soft_color);
172                    gizmos.draw_line(p1, p2, soft_color);
173                    gizmos.draw_line(p1, p3, soft_color);
174                    gizmos.draw_line(p2, p3, soft_color);
175                }
176            }
177        }
178
179        // --- Phase 6.1: Süspansiyon Raycast Çizgisi + Kuvvet Okları ---
180        #[cfg(feature = "physics-dynamics")]
181        if let Some(q) = world.query::<(
182            &gizmo_physics_core::Transform,
183            &gizmo_physics_dynamics::VehicleController,
184        )>() {
185            for (_, (trans, vehicle)) in q.iter() {
186                for wheel in &vehicle.wheels {
187                    let attach_world =
188                        trans.position + trans.rotation.mul_vec3(wheel.attachment_local_pos);
189                    let ray_dir = trans.rotation.mul_vec3(wheel.direction_local).normalize();
190                    let ray_end = attach_world
191                        + ray_dir
192                            * (wheel.suspension_rest_length
193                                + wheel.suspension_max_travel
194                                + wheel.radius);
195
196                    // Draw raycast maximum extent (Yellow line)
197                    gizmos.draw_line(attach_world, ray_end, [1.0, 1.0, 0.0, 1.0]);
198
199                    if wheel.is_grounded {
200                        if let Some(hit) = &wheel.ground_hit {
201                            // Kuvvet oku (Mavi) - sadece uzunluğu normalize edip görselleştirmek için / 10000 kullanıyoruz
202                            let force_dir = -ray_dir;
203                            let force_len = (wheel.suspension_force / 10000.0).clamp(0.1, 2.0);
204                            let arrow_end = hit.point + force_dir * force_len;
205                            gizmos.draw_line(hit.point, arrow_end, [0.0, 0.0, 1.0, 1.0]);
206
207                            // Mevcut süspansiyon uzunluğu + tekerlek merkezi çizgisi (Turuncu)
208                            let wheel_center = attach_world + ray_dir * wheel.suspension_length;
209                            gizmos.draw_line(wheel_center, hit.point, [1.0, 0.5, 0.0, 1.0]);
210                        }
211                    }
212                }
213            }
214        }
215
216        // --- Phase 6.2: Temas Normalleri ve Penetrasyon Derinliği ---
217        if let Some(phys_world) = world.get_resource::<gizmo_physics_rigid::world::PhysicsWorld>() {
218            for event in phys_world.collision_events() {
219                for contact in &event.contact_points {
220                    let p1 = contact.point;
221                    let p2 = contact.point + contact.normal * 0.5; // Normal arrow
222                    gizmos.draw_line(p1, p2, [1.0, 0.0, 0.0, 1.0]); // Red normal
223
224                    let p_pen = contact.point - contact.normal * contact.penetration;
225                    gizmos.draw_line(p1, p_pen, [1.0, 0.0, 1.0, 1.0]); // Magenta penetration depth
226                }
227            }
228        }
229        tracing::info!(
230            "physics_debug_system: gizmos lines count = {}",
231            gizmos.lines.len()
232        );
233    }
234}
235
236/// ECS'deki yeni yaratılmış Fiziksel Objeleri (RigidBody + Transform + Collider)
237/// GPU Physics çekirdeğinin otoyoluna (GpuPhysicsSystem::spheres_buffer) kaydeder.
238/// Statik collider'lar için ayrı sayaç. İlk 3 slot başlangıç collider'larına ayrılmıştır.
239static NEXT_STATIC_COLLIDER_SLOT: std::sync::atomic::AtomicU32 =
240    std::sync::atomic::AtomicU32::new(3);
241
242#[cfg(feature = "render")]
243pub fn gpu_physics_submit_system(world: &mut crate::core::World, renderer: &Renderer) {
244    use gizmo_physics_rigid::components::Velocity;
245
246    if let Some(physics) = &renderer.gpu_physics {
247        let mut unlinked_entities = Vec::new();
248        if let Some(q) = world.query::<(&RigidBody, &Transform, &Collider)>() {
249            let links = world.borrow::<GpuPhysicsLink>();
250            let velocities = world.borrow::<Velocity>();
251            for (e, (rb, trans, col)) in q.iter() {
252                if links.get(e).is_none() {
253                    let vel = velocities.get(e).copied().unwrap_or_default();
254                    unlinked_entities.push((e, *rb, *trans, col.clone(), vel));
255                }
256            }
257        }
258
259        let mut next_dynamic_id = world
260            .query::<&GpuPhysicsLink>()
261            .map(|q| q.iter().count() as u32)
262            .unwrap_or(0);
263
264        for (e, rb, trans, col, vel) in unlinked_entities {
265            if matches!(col.shape, ColliderShape::Plane(_)) {
266                // Statik engel — ayrı slot sayacı kullan
267                let slot =
268                    NEXT_STATIC_COLLIDER_SLOT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
269                if slot >= 100 {
270                    tracing::error!("[GpuPhysics] Statik collider slot limiti (100) aşıldı, collider atlanıyor.");
271                    NEXT_STATIC_COLLIDER_SLOT.fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
272                    continue;
273                }
274
275                let gpu_col = gizmo_renderer::gpu_physics::GpuCollider {
276                    shape_type: match col.shape {
277                        ColliderShape::Plane(_) => 1,
278                        _ => 0, // Varsayılan Box (AABB)
279                    },
280                    _pad1: [0; 3],
281                    data1: match &col.shape {
282                        ColliderShape::Plane(p) => [p.normal.x, p.normal.y, p.normal.z, 0.0],
283                        ColliderShape::Box(b) => {
284                            let min = trans.position - b.half_extents;
285                            [min.x, min.y, min.z, 0.0]
286                        }
287                        _ => [0.0; 4],
288                    },
289                    data2: match &col.shape {
290                        ColliderShape::Plane(p) => [p.distance, 0.0, 0.0, 0.0],
291                        ColliderShape::Box(b) => {
292                            let max = trans.position + b.half_extents;
293                            [max.x, max.y, max.z, 0.0]
294                        }
295                        _ => [0.0; 4],
296                    },
297                };
298                physics.update_collider(&renderer.queue, slot, &gpu_col);
299            } else {
300                // Dinamik Kutu (AABB)
301                let id = next_dynamic_id;
302                next_dynamic_id += 1;
303
304                let (extents, offset) = match &col.shape {
305                    ColliderShape::Box(b) => ([b.half_extents.x, b.half_extents.y, b.half_extents.z], Vec3::ZERO),
306                    ColliderShape::Compound(shapes) => {
307                        if let Some((local_t, box_shape)) = shapes.first() {
308                            if let ColliderShape::Box(b) = &**box_shape {
309                                ([b.half_extents.x, b.half_extents.y, b.half_extents.z], local_t.position)
310                            } else {
311                                ([0.5, 0.5, 0.5], Vec3::ZERO)
312                            }
313                        } else {
314                            ([0.5, 0.5, 0.5], Vec3::ZERO)
315                        }
316                    }
317                    _ => ([0.5, 0.5, 0.5], Vec3::ZERO),
318                };
319
320                let world_pos = trans.position + trans.rotation.mul_vec3(offset);
321
322                let gpu_box = gizmo_renderer::gpu_physics::GpuBox {
323                    position: [world_pos.x, world_pos.y, world_pos.z],
324                    mass: rb.mass,
325                    velocity: [vel.linear.x, vel.linear.y, vel.linear.z],
326                    state: 0,
327                    rotation: [
328                        trans.rotation.x,
329                        trans.rotation.y,
330                        trans.rotation.z,
331                        trans.rotation.w,
332                    ],
333                    angular_velocity: [vel.angular.x, vel.angular.y, vel.angular.z],
334                    sleep_counter: if rb.is_sleeping { 60 } else { 0 },
335                    color: [0.3, 0.8, 1.0, 1.0],
336                    half_extents: extents,
337                    _pad: 0,
338                };
339                physics.update_box(&renderer.queue, id, &gpu_box);
340
341                world.add_component(world.get_entity(e).unwrap(), GpuPhysicsLink { id });
342            }
343        }
344    }
345}
346
347/// GPU'dan Asenkron (0ms) çekilen devasa Fizik lokasyon durumlarını,
348/// Ekrandaki objelerin render edilmesi için ECS'deki Transform'larına kopyalar.
349pub fn gpu_physics_readback_system(world: &mut crate::core::World, renderer: &Renderer) {
350    if let Some(physics) = &renderer.gpu_physics {
351        if let Some(gpu_data) = physics.poll_readback_data(&renderer.device) {
352            if let Some(mut q) =
353                world.query::<(gizmo_core::prelude::Mut<Transform>, &GpuPhysicsLink, &gizmo_physics_core::Collider)>()
354            {
355                for (_, (mut trans, link, col)) in q.iter_mut() {
356                    let idx = link.id as usize;
357                    if idx < gpu_data.len() {
358                        let box_data = &gpu_data[idx];
359                        
360                        let offset = match &col.shape {
361                            gizmo_physics_core::ColliderShape::Compound(shapes) => {
362                                if let Some((local_t, _)) = shapes.first() {
363                                    local_t.position
364                                } else {
365                                    gizmo_math::Vec3::ZERO
366                                }
367                            }
368                            _ => gizmo_math::Vec3::ZERO,
369                        };
370
371                        let new_rot = gizmo_math::Quat::from_xyzw(
372                            box_data.rotation[0],
373                            box_data.rotation[1],
374                            box_data.rotation[2],
375                            box_data.rotation[3],
376                        );
377
378                        trans.position = gizmo_math::Vec3::new(
379                            box_data.position[0],
380                            box_data.position[1],
381                            box_data.position[2],
382                        ) - new_rot.mul_vec3(offset);
383                        
384                        trans.rotation = new_rot;
385                        trans.update_local_matrix();
386                    }
387                }
388            }
389        }
390    }
391}
392
393/// Phase 7.1: Fluid-Rigid Coupling
394/// Senkronize eder: GpuPhysicsLink sahibi objeleri FluidCollider buffer'ına yazar.
395
396pub fn cpu_physics_step_system(world: &crate::core::World, dt: f32) {
397    gizmo_physics_rigid::system::physics_step_system(world, dt);
398
399    #[cfg(feature = "physics-soft")]
400    {
401        // Obtain gravity from the PhysicsWorld if it exists
402        let gravity = world.get_resource::<gizmo_physics_rigid::world::PhysicsWorld>()
403            .map(|w| w.integrator.gravity)
404            .unwrap_or(gizmo_math::Vec3::new(0.0, -9.81, 0.0));
405            
406        gizmo_physics_soft::system::soft_body_step_system(world, dt, gravity);
407        gizmo_physics_soft::system::cloth_step_system(world, dt, gravity);
408        gizmo_physics_soft::system::rope_step_system(world, dt, gravity);
409    }
410}