Skip to main content

gizmo_renderer/gpu_physics/
system.rs

1use std::sync::atomic::{AtomicU8, Ordering};
2use std::sync::Arc;
3use wgpu::util::DeviceExt;
4
5use super::pipeline::{create_physics_pipelines, PhysicsPipelines};
6use super::types::*;
7
8pub struct GpuPhysicsSystem {
9    pub max_boxes: u32,
10    pub grid_size: u32,
11    pub boxes_buffer: wgpu::Buffer,
12    pub params_buffer: wgpu::Buffer,
13    pub grid_heads_buffer: wgpu::Buffer,
14    pub linked_nodes_buffer: wgpu::Buffer,
15    pub box_contacts_buffer: wgpu::Buffer,
16    pub colliders_buffer: wgpu::Buffer,
17    pub awake_flags_buffer: wgpu::Buffer,
18    pub joints_buffer: wgpu::Buffer,
19    pub joint_count: u32,
20    pub max_joints: u32,
21
22    pub pipelines: PhysicsPipelines,
23
24    pub box_vertex_buffer: wgpu::Buffer,
25    pub box_index_buffer: wgpu::Buffer,
26    pub index_count: u32,
27
28    pub readback_buffer: wgpu::Buffer,
29    // 0 = Idle, 1 = Copied to buffer (awaiting map), 2 = Mapping, 3 = Mapped (ready to read)
30    pub readback_state: Arc<AtomicU8>,
31
32    pub indirect_buffer: wgpu::Buffer,
33    pub culled_boxes_buffer: wgpu::Buffer,
34
35    // ═══ Debug Renderer ═══
36    pub debug_enabled: bool,
37    pub debug_line_buffer: wgpu::Buffer,
38    pub debug_line_count_buffer: wgpu::Buffer,
39    pub debug_params_buffer: wgpu::Buffer,
40    pub debug_compute_bind_group: wgpu::BindGroup,
41    pub debug_compute_pipeline: wgpu::ComputePipeline,
42    pub debug_render_pipeline: wgpu::RenderPipeline,
43    pub debug_max_lines: u32,
44}
45
46impl GpuPhysicsSystem {
47    pub fn new(
48        device: &wgpu::Device,
49        max_boxes: u32,
50        global_bind_group_layout: &wgpu::BindGroupLayout,
51        output_format: wgpu::TextureFormat,
52        depth_format: wgpu::TextureFormat,
53    ) -> Self {
54        let mut initial_boxes = Vec::with_capacity(max_boxes as usize);
55        let grid_dim = (max_boxes as f32).powf(1.0 / 3.0).ceil() as u32;
56        let spacing = 2.1f32;
57        let offset = (grid_dim as f32 * spacing) / 2.0;
58
59        for i in 0..max_boxes {
60            let ix = i % grid_dim;
61            let iy = (i / grid_dim) % grid_dim;
62            let iz = i / (grid_dim * grid_dim);
63
64            let x = (ix as f32 * spacing) - offset;
65            let y = 30.0 + (iy as f32 * spacing); // Y=30'dan yukarı doğru diz
66            let z = (iz as f32 * spacing) - offset;
67
68            // Görselliği arttırmak için Y koordinatına göre renk gradyanı:
69            let color_r = ix as f32 / grid_dim as f32;
70            let color_g = iy as f32 / grid_dim as f32;
71            let color_b = iz as f32 / grid_dim as f32;
72
73            initial_boxes.push(GpuBox {
74                position: [x, y, z],
75                mass: 1.0,
76                velocity: [0.0, 0.0, 0.0],
77                state: 0,
78                rotation: [0.0, 0.0, 0.0, 1.0],
79                angular_velocity: [0.0, 0.0, 0.0],
80                sleep_counter: 0,
81                color: [color_r, color_g, color_b, 1.0],
82                half_extents: [1.0, 1.0, 1.0],
83                _pad: 0,
84            });
85        }
86
87        let boxes_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
88            label: Some("GPU Physics Buffer"),
89            contents: bytemuck::cast_slice(&initial_boxes),
90            usage: wgpu::BufferUsages::STORAGE
91                | wgpu::BufferUsages::VERTEX
92                | wgpu::BufferUsages::COPY_DST
93                | wgpu::BufferUsages::COPY_SRC,
94        });
95
96        let mut initial_colliders = Vec::new();
97        // 1. Zemin (Sonsuz Plane) -> Y = 0
98        initial_colliders.push(GpuCollider {
99            shape_type: 1,
100            _pad1: [0; 3],
101            data1: [0.0, 1.0, 0.0, 0.0], // Normal vec
102            data2: [0.0, 0.0, 0.0, 0.0], // distance = 0
103        });
104
105        // 2. Ortadaki Devasa Zemin Platformu (AABB)
106        initial_colliders.push(GpuCollider {
107            shape_type: 0,
108            _pad1: [0; 3],
109            data1: [-40.0, 0.0, -40.0, 0.0], // aabb_min
110            data2: [40.0, 20.0, 40.0, 0.0],  // aabb_max
111        });
112
113        // 3. Eğik bir rampa veya duvar
114        initial_colliders.push(GpuCollider {
115            shape_type: 0,
116            _pad1: [0; 3],
117            data1: [45.0, 0.0, -40.0, 0.0], // aabb_min
118            data2: [55.0, 40.0, 40.0, 0.0], // aabb_max (Sağ Duvar)
119        });
120
121        let max_static_colliders = 100;
122        let num_initial = initial_colliders.len();
123        if num_initial < max_static_colliders {
124            let empty_col = GpuCollider {
125                shape_type: 0,
126                _pad1: [0; 3],
127                data1: [0.0; 4],
128                data2: [0.0; 4],
129            };
130            initial_colliders.resize(max_static_colliders, empty_col);
131        }
132
133        let colliders_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
134            label: Some("GPU Static Colliders Buffer"),
135            contents: bytemuck::cast_slice(&initial_colliders),
136            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
137        });
138
139        let initial_awake_flags: Vec<u32> = vec![0; max_boxes as usize];
140        let awake_flags_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
141            label: Some("GPU Physics Awake Flags Buffer"),
142            contents: bytemuck::cast_slice(&initial_awake_flags),
143            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
144        });
145
146        let params = PhysicsSimParams {
147            dt: 0.016,
148            _pad0: [0; 3],
149            _pad1: [0.0; 3],
150            _pad1b: 0,
151            gravity: [0.0, -9.81, 0.0],
152            damping: 0.99,
153            num_boxes: max_boxes,
154            num_colliders: initial_colliders.len() as u32,
155            num_joints: 0,
156            _pad2: 0,
157        };
158
159        let params_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
160            label: Some("GPU Physics Params Buffer"),
161            contents: bytemuck::cast_slice(&[params]),
162            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
163        });
164
165        let grid_size = 262144u32;
166        let initial_heads = vec![-1i32; grid_size as usize];
167        let grid_heads_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
168            label: Some("GPU Physics Grid Heads Buffer"),
169            contents: bytemuck::cast_slice(&initial_heads),
170            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
171        });
172
173        let initial_nodes = vec![-1i32; max_boxes as usize];
174        let linked_nodes_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
175            label: Some("GPU Physics Linked Nodes Buffer"),
176            contents: bytemuck::cast_slice(&initial_nodes),
177            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
178        });
179
180        let (vertices, indices) = create_cube();
181
182        let box_vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
183            label: Some("Box Vertex Buffer"),
184            contents: bytemuck::cast_slice(&vertices),
185            usage: wgpu::BufferUsages::VERTEX,
186        });
187
188        let box_index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
189            label: Some("Box Index Buffer"),
190            contents: bytemuck::cast_slice(&indices),
191            usage: wgpu::BufferUsages::INDEX,
192        });
193
194        let indirect_data: [u32; 5] = [
195            indices.len() as u32, // vertex_count
196            0,                    // instance_count
197            0,                    // first_index
198            0,                    // base_vertex
199            0,                    // first_instance
200        ];
201
202        let indirect_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
203            label: Some("Culling Indirect Buffer"),
204            contents: bytemuck::cast_slice(&indirect_data),
205            usage: wgpu::BufferUsages::INDIRECT
206                | wgpu::BufferUsages::STORAGE
207                | wgpu::BufferUsages::COPY_DST,
208        });
209
210        let culled_boxes_buffer = device.create_buffer(&wgpu::BufferDescriptor {
211            label: Some("Culled Boxes Buffer"),
212            size: (max_boxes as wgpu::BufferAddress)
213                * std::mem::size_of::<GpuBox>() as wgpu::BufferAddress,
214            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::VERTEX,
215            mapped_at_creation: false,
216        });
217
218        // Joint buffer — max 16384 joints
219        let max_joints = 16384u32;
220        let joints_buffer = device.create_buffer(&wgpu::BufferDescriptor {
221            label: Some("GPU Physics Joints Buffer"),
222            size: (max_joints as wgpu::BufferAddress)
223                * std::mem::size_of::<GpuJoint>() as wgpu::BufferAddress,
224            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
225            mapped_at_creation: false,
226        });
227
228        // 336 bytes per box (4 count, 12 pad, 32 neighbors, 128 normals, 128 accum_impulse, 32 is_active)
229        let box_contacts_buffer = device.create_buffer(&wgpu::BufferDescriptor {
230            label: Some("GPU Physics Box Contacts Cache"),
231            size: (max_boxes as wgpu::BufferAddress) * 336,
232            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
233            mapped_at_creation: false,
234        });
235
236        let pipelines = create_physics_pipelines(
237            device,
238            global_bind_group_layout,
239            output_format,
240            depth_format,
241            &params_buffer,
242            &boxes_buffer,
243            &grid_heads_buffer,
244            &linked_nodes_buffer,
245            &colliders_buffer,
246            &awake_flags_buffer,
247            &joints_buffer,
248            &box_contacts_buffer,
249            &culled_boxes_buffer,
250            &indirect_buffer,
251        );
252
253        Self {
254            max_boxes,
255            grid_size,
256            boxes_buffer,
257            params_buffer,
258            grid_heads_buffer,
259            linked_nodes_buffer,
260            box_contacts_buffer,
261            colliders_buffer,
262            awake_flags_buffer,
263            joints_buffer,
264            joint_count: 0,
265            max_joints,
266            pipelines,
267            box_vertex_buffer,
268            box_index_buffer,
269            index_count: indices.len() as u32,
270
271            readback_buffer: device.create_buffer(&wgpu::BufferDescriptor {
272                label: Some("GPU Physics Readback Buffer"),
273                size: (max_boxes as wgpu::BufferAddress)
274                    * std::mem::size_of::<GpuBox>() as wgpu::BufferAddress,
275                usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
276                mapped_at_creation: false,
277            }),
278            readback_state: Arc::new(AtomicU8::new(0)),
279
280            indirect_buffer,
281            culled_boxes_buffer,
282
283            // Debug Renderer — bind group ve pipeline enable_debug() ile oluşturulur
284            debug_enabled: false,
285            debug_line_buffer: device.create_buffer(&wgpu::BufferDescriptor {
286                label: Some("Debug Line Buffer"),
287                size: 32768 * 2 * std::mem::size_of::<DebugVertex>() as wgpu::BufferAddress,
288                usage: wgpu::BufferUsages::STORAGE
289                    | wgpu::BufferUsages::VERTEX
290                    | wgpu::BufferUsages::COPY_DST,
291                mapped_at_creation: false,
292            }),
293            debug_line_count_buffer: device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
294                label: Some("Debug Line Count"),
295                contents: bytemuck::cast_slice(&[0u32, 1u32, 0u32, 0u32]), // IndirectDrawArgs: vertex_count, instance_count, first_vertex, first_instance
296                usage: wgpu::BufferUsages::STORAGE
297                    | wgpu::BufferUsages::INDIRECT
298                    | wgpu::BufferUsages::COPY_DST,
299            }),
300            debug_params_buffer: {
301                let dp = DebugParams {
302                    num_boxes: max_boxes,
303                    num_joints: 0,
304                    show_wireframes: 0,
305                    _pad: 0,
306                };
307                device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
308                    label: Some("Debug Params"),
309                    contents: bytemuck::cast_slice(&[dp]),
310                    usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
311                })
312            },
313            // Dummy — enable_debug() ile yeniden oluşturulur
314            debug_compute_bind_group: device.create_bind_group(&wgpu::BindGroupDescriptor {
315                layout: &device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
316                    entries: &[],
317                    label: Some("empty_layout"),
318                }),
319                entries: &[],
320                label: Some("debug_placeholder"),
321            }),
322            debug_compute_pipeline: {
323                let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
324                    label: Some("Physics Debug Compute Shader"),
325                    source: wgpu::ShaderSource::Wgsl(
326                        include_str!("../shaders/physics_debug.wgsl").into(),
327                    ),
328                });
329                let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
330                    label: Some("Debug Compute Layout"),
331                    bind_group_layouts: &[&device.create_bind_group_layout(
332                        &wgpu::BindGroupLayoutDescriptor {
333                            entries: &[
334                                wgpu::BindGroupLayoutEntry {
335                                    binding: 0,
336                                    visibility: wgpu::ShaderStages::COMPUTE,
337                                    ty: wgpu::BindingType::Buffer {
338                                        ty: wgpu::BufferBindingType::Uniform,
339                                        has_dynamic_offset: false,
340                                        min_binding_size: None,
341                                    },
342                                    count: None,
343                                },
344                                wgpu::BindGroupLayoutEntry {
345                                    binding: 1,
346                                    visibility: wgpu::ShaderStages::COMPUTE,
347                                    ty: wgpu::BindingType::Buffer {
348                                        ty: wgpu::BufferBindingType::Storage { read_only: true },
349                                        has_dynamic_offset: false,
350                                        min_binding_size: None,
351                                    },
352                                    count: None,
353                                },
354                                wgpu::BindGroupLayoutEntry {
355                                    binding: 2,
356                                    visibility: wgpu::ShaderStages::COMPUTE,
357                                    ty: wgpu::BindingType::Buffer {
358                                        ty: wgpu::BufferBindingType::Storage { read_only: true },
359                                        has_dynamic_offset: false,
360                                        min_binding_size: None,
361                                    },
362                                    count: None,
363                                },
364                                wgpu::BindGroupLayoutEntry {
365                                    binding: 3,
366                                    visibility: wgpu::ShaderStages::COMPUTE,
367                                    ty: wgpu::BindingType::Buffer {
368                                        ty: wgpu::BufferBindingType::Storage { read_only: false },
369                                        has_dynamic_offset: false,
370                                        min_binding_size: None,
371                                    },
372                                    count: None,
373                                },
374                                wgpu::BindGroupLayoutEntry {
375                                    binding: 4,
376                                    visibility: wgpu::ShaderStages::COMPUTE,
377                                    ty: wgpu::BindingType::Buffer {
378                                        ty: wgpu::BufferBindingType::Storage { read_only: false },
379                                        has_dynamic_offset: false,
380                                        min_binding_size: None,
381                                    },
382                                    count: None,
383                                },
384                            ],
385                            label: Some("debug_compute_layout_inner"),
386                        },
387                    )],
388                    push_constant_ranges: &[],
389                });
390                device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
391                    label: Some("Physics Debug Compute"),
392                    layout: Some(&layout),
393                    module: &shader,
394                    entry_point: "generate_debug_lines",
395                    compilation_options: Default::default(),
396                })
397            },
398            debug_render_pipeline: {
399                let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
400                    label: Some("Physics Debug Shader"),
401                    source: wgpu::ShaderSource::Wgsl(
402                        include_str!("../shaders/physics_debug.wgsl").into(),
403                    ),
404                });
405                let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
406                    label: Some("Debug Render Layout"),
407                    bind_group_layouts: &[global_bind_group_layout],
408                    push_constant_ranges: &[],
409                });
410                device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
411                    label: Some("Physics Debug Lines"),
412                    layout: Some(&layout),
413                    vertex: wgpu::VertexState {
414                        module: &shader,
415                        entry_point: "vs_debug",
416                        compilation_options: Default::default(),
417                        buffers: &[DebugVertex::desc()],
418                    },
419                    fragment: Some(wgpu::FragmentState {
420                        module: &shader,
421                        entry_point: "fs_debug",
422                        compilation_options: Default::default(),
423                        targets: &[Some(wgpu::ColorTargetState {
424                            format: output_format,
425                            blend: Some(wgpu::BlendState::ALPHA_BLENDING),
426                            write_mask: wgpu::ColorWrites::ALL,
427                        })],
428                    }),
429                    primitive: wgpu::PrimitiveState {
430                        topology: wgpu::PrimitiveTopology::LineList,
431                        ..Default::default()
432                    },
433                    depth_stencil: Some(wgpu::DepthStencilState {
434                        format: depth_format,
435                        depth_write_enabled: false,
436                        depth_compare: wgpu::CompareFunction::LessEqual,
437                        stencil: wgpu::StencilState::default(),
438                        bias: wgpu::DepthBiasState::default(),
439                    }),
440                    multisample: wgpu::MultisampleState::default(),
441                    multiview: None,
442                })
443            },
444            debug_max_lines: 32768,
445        }
446    }
447
448    pub fn update_box(&self, queue: &wgpu::Queue, index: u32, box_struct: &GpuBox) {
449        if index < self.max_boxes {
450            let offset = (index as wgpu::BufferAddress)
451                * std::mem::size_of::<GpuBox>() as wgpu::BufferAddress;
452            queue.write_buffer(
453                &self.boxes_buffer,
454                offset,
455                bytemuck::cast_slice(&[*box_struct]),
456            );
457        }
458    }
459
460    pub fn update_collider(&self, queue: &wgpu::Queue, index: u32, collider: &GpuCollider) {
461        if index < 100 {
462            let offset = (index as wgpu::BufferAddress)
463                * std::mem::size_of::<GpuCollider>() as wgpu::BufferAddress;
464            queue.write_buffer(
465                &self.colliders_buffer,
466                offset,
467                bytemuck::cast_slice(&[*collider]),
468            );
469        }
470    }
471
472    /// Joint ekle — indeksini döndürür.
473    pub fn add_joint(&mut self, queue: &wgpu::Queue, joint: GpuJoint) -> Option<u32> {
474        if self.joint_count >= self.max_joints {
475            return None;
476        }
477        let idx = self.joint_count;
478        let offset =
479            (idx as wgpu::BufferAddress) * std::mem::size_of::<GpuJoint>() as wgpu::BufferAddress;
480        queue.write_buffer(&self.joints_buffer, offset, bytemuck::cast_slice(&[joint]));
481        self.joint_count += 1;
482        Some(idx)
483    }
484
485    /// Joint'i deaktive et.
486    pub fn remove_joint(&self, queue: &wgpu::Queue, index: u32) {
487        if index < self.joint_count {
488            let mut empty = GpuJoint::ball(0, 0, [0.0; 3], [0.0; 3]);
489            empty.flags = 0; // inactive
490            let offset = (index as wgpu::BufferAddress)
491                * std::mem::size_of::<GpuJoint>() as wgpu::BufferAddress;
492            queue.write_buffer(&self.joints_buffer, offset, bytemuck::cast_slice(&[empty]));
493        }
494    }
495
496    /// Simülasyon parametrelerini güncelle (dt, num_joints, vb.)
497    pub fn update_params(&self, queue: &wgpu::Queue, dt: f32, gravity: [f32; 3]) {
498        let params = PhysicsSimParams {
499            dt,
500            _pad0: [0; 3],
501            _pad1: [0.0; 3],
502            _pad1b: 0,
503            gravity,
504            damping: 0.99,
505            num_boxes: self.max_boxes,
506            num_colliders: 100, // max static colliders
507            num_joints: self.joint_count,
508            _pad2: 0,
509        };
510        queue.write_buffer(&self.params_buffer, 0, bytemuck::cast_slice(&[params]));
511    }
512
513    /// Debug görselleştirmeyi etkinleştir. Bind group'u gerçek buffer referanslarıyla oluşturur.
514    pub fn enable_debug(&mut self, device: &wgpu::Device, _show_flags: u32) {
515        let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
516            entries: &[
517                wgpu::BindGroupLayoutEntry {
518                    binding: 0,
519                    visibility: wgpu::ShaderStages::COMPUTE,
520                    ty: wgpu::BindingType::Buffer {
521                        ty: wgpu::BufferBindingType::Uniform,
522                        has_dynamic_offset: false,
523                        min_binding_size: None,
524                    },
525                    count: None,
526                },
527                wgpu::BindGroupLayoutEntry {
528                    binding: 1,
529                    visibility: wgpu::ShaderStages::COMPUTE,
530                    ty: wgpu::BindingType::Buffer {
531                        ty: wgpu::BufferBindingType::Storage { read_only: true },
532                        has_dynamic_offset: false,
533                        min_binding_size: None,
534                    },
535                    count: None,
536                },
537                wgpu::BindGroupLayoutEntry {
538                    binding: 2,
539                    visibility: wgpu::ShaderStages::COMPUTE,
540                    ty: wgpu::BindingType::Buffer {
541                        ty: wgpu::BufferBindingType::Storage { read_only: true },
542                        has_dynamic_offset: false,
543                        min_binding_size: None,
544                    },
545                    count: None,
546                },
547                wgpu::BindGroupLayoutEntry {
548                    binding: 3,
549                    visibility: wgpu::ShaderStages::COMPUTE,
550                    ty: wgpu::BindingType::Buffer {
551                        ty: wgpu::BufferBindingType::Storage { read_only: false },
552                        has_dynamic_offset: false,
553                        min_binding_size: None,
554                    },
555                    count: None,
556                },
557                wgpu::BindGroupLayoutEntry {
558                    binding: 4,
559                    visibility: wgpu::ShaderStages::COMPUTE,
560                    ty: wgpu::BindingType::Buffer {
561                        ty: wgpu::BufferBindingType::Storage { read_only: false },
562                        has_dynamic_offset: false,
563                        min_binding_size: None,
564                    },
565                    count: None,
566                },
567            ],
568            label: Some("debug_compute_layout"),
569        });
570
571        self.debug_compute_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
572            layout: &layout,
573            entries: &[
574                wgpu::BindGroupEntry {
575                    binding: 0,
576                    resource: self.debug_params_buffer.as_entire_binding(),
577                },
578                wgpu::BindGroupEntry {
579                    binding: 1,
580                    resource: self.boxes_buffer.as_entire_binding(),
581                },
582                wgpu::BindGroupEntry {
583                    binding: 2,
584                    resource: self.joints_buffer.as_entire_binding(),
585                },
586                wgpu::BindGroupEntry {
587                    binding: 3,
588                    resource: self.debug_line_buffer.as_entire_binding(),
589                },
590                wgpu::BindGroupEntry {
591                    binding: 4,
592                    resource: self.debug_line_count_buffer.as_entire_binding(),
593                },
594            ],
595            label: Some("debug_compute_bind_group"),
596        });
597
598        self.debug_enabled = true;
599    }
600
601    /// Debug'u aç/kapat.
602    pub fn toggle_debug(&mut self) {
603        self.debug_enabled = !self.debug_enabled;
604    }
605
606    pub fn compute_pass(&self, encoder: &mut wgpu::CommandEncoder) {
607        let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
608            label: Some("Physics Compute Pass"),
609            timestamp_writes: None,
610        });
611        cpass.set_bind_group(0, &self.pipelines.compute_bind_group, &[]);
612
613        // ═══ Sequential Impulse Solver ═══
614        // Faz 1: Grid'i bir kez inşa et
615        cpass.set_pipeline(&self.pipelines.pipeline_clear);
616        cpass.dispatch_workgroups(self.grid_size.div_ceil(256), 1, 1);
617
618        cpass.set_pipeline(&self.pipelines.pipeline_build);
619        cpass.dispatch_workgroups(self.max_boxes.div_ceil(256), 1, 1);
620
621        // Faz 2: Narrowphase (Çarpışma Tespiti ve Contact Caching)
622        cpass.set_pipeline(&self.pipelines.pipeline_narrowphase);
623        cpass.dispatch_workgroups(self.max_boxes.div_ceil(256), 1, 1);
624
625        // Faz 3: Çarpışma çözümünü N kez tekrarla (SI iterasyon)
626        // Artık grid üzerinden değil, doğrudan contact cache üzerinden hesaplama yapıyor!
627        let si_iterations = 6;
628        for _ in 0..si_iterations {
629            cpass.set_pipeline(&self.pipelines.pipeline_solve);
630            cpass.dispatch_workgroups(self.max_boxes.div_ceil(256), 1, 1);
631        }
632
633        // Faz 4: Hız ve pozisyon entegrasyonu (tek seferde)
634        cpass.set_pipeline(&self.pipelines.pipeline_integrate);
635        cpass.dispatch_workgroups(self.max_boxes.div_ceil(256), 1, 1);
636
637        // Faz 4: Joint constraint çözümü (entegrasyondan sonra)
638        if self.joint_count > 0 {
639            let joint_iterations = 4;
640            for _ in 0..joint_iterations {
641                cpass.set_pipeline(&self.pipelines.pipeline_solve_joints);
642                cpass.dispatch_workgroups(self.max_boxes.div_ceil(256), 1, 1);
643            }
644        }
645    }
646
647    pub fn cull_pass(
648        &self,
649        encoder: &mut wgpu::CommandEncoder,
650        global_bind_group: &wgpu::BindGroup,
651    ) {
652        encoder.clear_buffer(&self.indirect_buffer, 4, Some(4));
653
654        let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
655            label: Some("Physics Culling Pass"),
656            timestamp_writes: None,
657        });
658        cpass.set_pipeline(&self.pipelines.pipeline_culling);
659        cpass.set_bind_group(0, global_bind_group, &[]);
660        cpass.set_bind_group(1, &self.pipelines.culling_bind_group, &[]);
661        cpass.dispatch_workgroups(self.max_boxes.div_ceil(256), 1, 1);
662    }
663
664    pub fn render_pass<'a>(
665        &'a self,
666        rpass: &mut wgpu::RenderPass<'a>,
667        global_bind_group: &'a wgpu::BindGroup,
668    ) {
669        rpass.set_pipeline(&self.pipelines.render_pipeline);
670        rpass.set_bind_group(0, global_bind_group, &[]);
671        rpass.set_vertex_buffer(0, self.box_vertex_buffer.slice(..));
672        rpass.set_vertex_buffer(1, self.culled_boxes_buffer.slice(..));
673        rpass.set_index_buffer(self.box_index_buffer.slice(..), wgpu::IndexFormat::Uint32);
674        rpass.draw_indexed_indirect(&self.indirect_buffer, 0);
675    }
676
677    pub fn debug_compute_pass(&self, encoder: &mut wgpu::CommandEncoder) {
678        if !self.debug_enabled {
679            return;
680        }
681
682        // Clear line count to 0 (4 bytes)
683        encoder.clear_buffer(&self.debug_line_count_buffer, 0, Some(4));
684
685        let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
686            label: Some("Physics Debug Compute Pass"),
687            timestamp_writes: None,
688        });
689
690        cpass.set_pipeline(&self.debug_compute_pipeline);
691        cpass.set_bind_group(0, &self.debug_compute_bind_group, &[]);
692        // Dispatch enough workgroups for all boxes
693        cpass.dispatch_workgroups(
694            self.max_boxes
695                .div_ceil(256)
696                .max(self.max_joints.div_ceil(256)),
697            1,
698            1,
699        );
700    }
701
702    pub fn debug_render_pass<'a>(
703        &'a self,
704        rpass: &mut wgpu::RenderPass<'a>,
705        global_bind_group: &'a wgpu::BindGroup,
706    ) {
707        if !self.debug_enabled {
708            return;
709        }
710
711        rpass.set_pipeline(&self.debug_render_pipeline);
712        rpass.set_bind_group(0, global_bind_group, &[]);
713        rpass.set_vertex_buffer(0, self.debug_line_buffer.slice(..));
714        rpass.draw_indirect(&self.debug_line_count_buffer, 0);
715    }
716
717    pub fn request_readback(&self, encoder: &mut wgpu::CommandEncoder) {
718        if self
719            .readback_state
720            .compare_exchange(0, 1, Ordering::SeqCst, Ordering::SeqCst)
721            .is_ok()
722        {
723            let size = (self.max_boxes as wgpu::BufferAddress)
724                * std::mem::size_of::<GpuBox>() as wgpu::BufferAddress;
725            encoder.copy_buffer_to_buffer(&self.boxes_buffer, 0, &self.readback_buffer, 0, size);
726        }
727    }
728
729    pub fn poll_readback_data(&self, device: &wgpu::Device) -> Option<Vec<GpuBox>> {
730        if self
731            .readback_state
732            .compare_exchange(1, 2, Ordering::SeqCst, Ordering::SeqCst)
733            .is_ok()
734        {
735            let slice = self.readback_buffer.slice(..);
736            let state_clone = self.readback_state.clone();
737            slice.map_async(wgpu::MapMode::Read, move |result| {
738                if result.is_ok() {
739                    state_clone.store(3, Ordering::SeqCst);
740                } else {
741                    state_clone.store(0, Ordering::SeqCst);
742                }
743            });
744        }
745
746        device.poll(wgpu::Maintain::Poll);
747
748        if self.readback_state.load(Ordering::SeqCst) == 3 {
749            let slice = self.readback_buffer.slice(..);
750            let view = slice.get_mapped_range();
751
752            let data: &[GpuBox] = bytemuck::cast_slice(&view);
753            let vec_data = data.to_vec();
754
755            drop(view);
756            self.readback_buffer.unmap();
757
758            self.readback_state.store(0, Ordering::SeqCst);
759
760            return Some(vec_data);
761        }
762        None
763    }
764}
765
766fn create_cube() -> (Vec<crate::gpu_types::Vertex>, Vec<u32>) {
767    let s = 1.0f32;
768    let faces: [([f32; 3], [[f32; 3]; 4]); 6] = [
769        (
770            [0.0, 0.0, 1.0],
771            [[-s, -s, s], [s, -s, s], [s, s, s], [-s, s, s]],
772        ),
773        (
774            [0.0, 0.0, -1.0],
775            [[s, -s, -s], [-s, -s, -s], [-s, s, -s], [s, s, -s]],
776        ),
777        (
778            [1.0, 0.0, 0.0],
779            [[s, -s, s], [s, -s, -s], [s, s, -s], [s, s, s]],
780        ),
781        (
782            [-1.0, 0.0, 0.0],
783            [[-s, -s, -s], [-s, -s, s], [-s, s, s], [-s, s, -s]],
784        ),
785        (
786            [0.0, 1.0, 0.0],
787            [[-s, s, s], [s, s, s], [s, s, -s], [-s, s, -s]],
788        ),
789        (
790            [0.0, -1.0, 0.0],
791            [[-s, -s, -s], [s, -s, -s], [s, -s, s], [-s, -s, s]],
792        ),
793    ];
794
795    let mut vertices = Vec::with_capacity(24);
796    let mut indices = Vec::with_capacity(36);
797
798    for (normal, corners) in &faces {
799        let base = vertices.len() as u32;
800        for &p in corners {
801            vertices.push(crate::gpu_types::Vertex {
802                position: p,
803                color: [1.0, 1.0, 1.0],
804                normal: *normal,
805                tex_coords: [0.0, 0.0],
806                joint_indices: [0; 4],
807                joint_weights: [0.0; 4],
808            });
809        }
810        indices.extend_from_slice(&[base, base + 1, base + 2, base + 2, base + 3, base]);
811    }
812
813    (vertices, indices)
814}