1use glam::{Vec2, Vec3, Vec4, Mat4, Quat};
7use std::collections::HashMap;
8
9#[derive(Debug, Clone)]
12pub enum DebugPrim {
13 Line { start: Vec3, end: Vec3, color: Vec4, width: f32 },
14 Arrow { from: Vec3, to: Vec3, color: Vec4, head_size: f32 },
15 Sphere { center: Vec3, radius: f32, color: Vec4, filled: bool },
16 Box { center: Vec3, half_extents: Vec3, rotation: Quat, color: Vec4, filled: bool },
17 Cylinder { base: Vec3, top: Vec3, radius: f32, color: Vec4 },
18 Cone { apex: Vec3, base_center: Vec3, radius: f32, color: Vec4 },
19 Disc { center: Vec3, normal: Vec3, radius: f32, color: Vec4 },
20 Grid { center: Vec3, normal: Vec3, size: f32, divisions: u32, color: Vec4 },
21 Cross { center: Vec3, size: f32, color: Vec4 },
22 Axes { transform: Mat4, size: f32 },
23 Text { pos: Vec3, text: String, color: Vec4, scale: f32 },
24 Quad { corners: [Vec3; 4], color: Vec4, filled: bool },
25 Circle { center: Vec3, normal: Vec3, radius: f32, segments: u32, color: Vec4 },
26 Frustum { view_proj_inv: Mat4, color: Vec4 },
27 Bezier { p0: Vec3, p1: Vec3, p2: Vec3, p3: Vec3, steps: u32, color: Vec4 },
28 Point { pos: Vec3, size: f32, color: Vec4 },
29 Torus { center: Vec3, normal: Vec3, major_r: f32, minor_r: f32, color: Vec4 },
30 Spring { start: Vec3, end: Vec3, coils: u32, radius: f32, color: Vec4 },
31}
32
33impl DebugPrim {
34 pub fn line(start: Vec3, end: Vec3, color: Vec4) -> Self {
35 Self::Line { start, end, color, width: 1.5 }
36 }
37 pub fn thin_line(start: Vec3, end: Vec3, color: Vec4) -> Self {
38 Self::Line { start, end, color, width: 1.0 }
39 }
40 pub fn thick_line(start: Vec3, end: Vec3, color: Vec4) -> Self {
41 Self::Line { start, end, color, width: 3.0 }
42 }
43 pub fn arrow(from: Vec3, to: Vec3, color: Vec4) -> Self {
44 Self::Arrow { from, to, color, head_size: 0.05 }
45 }
46 pub fn wire_sphere(center: Vec3, radius: f32, color: Vec4) -> Self {
47 Self::Sphere { center, radius, color, filled: false }
48 }
49 pub fn solid_sphere(center: Vec3, radius: f32, color: Vec4) -> Self {
50 Self::Sphere { center, radius, color, filled: true }
51 }
52 pub fn wire_box(center: Vec3, half: Vec3, rot: Quat, color: Vec4) -> Self {
53 Self::Box { center, half_extents: half, rotation: rot, color, filled: false }
54 }
55 pub fn aabb(min: Vec3, max: Vec3, color: Vec4) -> Self {
56 let center = (min + max) * 0.5;
57 let half = (max - min) * 0.5;
58 Self::Box { center, half_extents: half, rotation: Quat::IDENTITY, color, filled: false }
59 }
60 pub fn axes(transform: Mat4) -> Self {
61 Self::Axes { transform, size: 0.2 }
62 }
63 pub fn point(pos: Vec3, color: Vec4) -> Self {
64 Self::Point { pos, size: 6.0, color }
65 }
66}
67
68impl DebugPrim {
71 pub fn to_lines(&self) -> Vec<(Vec3, Vec3, Vec4)> {
72 match self {
73 Self::Line { start, end, color, .. } =>
74 vec![(*start, *end, *color)],
75
76 Self::Arrow { from, to, color, head_size } => {
77 let dir = (*to - *from).normalize_or_zero();
78 let perp = dir.any_orthogonal_vector().normalize();
79 let perp2 = dir.cross(perp).normalize();
80 let head_base = *to - dir * (*head_size * 3.0);
81 let mut lines = vec![(*from, *to, *color)];
82 for i in 0..4u32 {
83 let angle = i as f32 * std::f32::consts::FRAC_PI_2;
84 let offset = perp * angle.cos() * *head_size + perp2 * angle.sin() * *head_size;
85 lines.push((head_base + offset, *to, *color));
86 }
87 lines
88 }
89
90 Self::Sphere { center, radius, color, .. } => {
91 let mut lines = Vec::new();
92 let segs = 16u32;
93 for axis in 0..3 {
94 let prev_angle = 0.0f32;
95 let _ = prev_angle;
96 for i in 0..segs {
97 let a0 = i as f32 / segs as f32 * 2.0 * std::f32::consts::PI;
98 let a1 = (i + 1) as f32 / segs as f32 * 2.0 * std::f32::consts::PI;
99 let p0 = circle_point(*center, *radius, a0, axis);
100 let p1 = circle_point(*center, *radius, a1, axis);
101 lines.push((p0, p1, *color));
102 }
103 }
104 lines
105 }
106
107 Self::Box { center, half_extents, rotation, color, .. } => {
108 let he = *half_extents;
109 let corners_local = [
110 Vec3::new(-he.x,-he.y,-he.z), Vec3::new( he.x,-he.y,-he.z),
111 Vec3::new( he.x, he.y,-he.z), Vec3::new(-he.x, he.y,-he.z),
112 Vec3::new(-he.x,-he.y, he.z), Vec3::new( he.x,-he.y, he.z),
113 Vec3::new( he.x, he.y, he.z), Vec3::new(-he.x, he.y, he.z),
114 ];
115 let c: Vec<Vec3> = corners_local.iter()
116 .map(|&v| *center + rotation.mul_vec3(v))
117 .collect();
118 let edges = [
119 (0,1),(1,2),(2,3),(3,0),
120 (4,5),(5,6),(6,7),(7,4),
121 (0,4),(1,5),(2,6),(3,7),
122 ];
123 edges.iter().map(|&(a,b)| (c[a], c[b], *color)).collect()
124 }
125
126 Self::Cylinder { base, top, radius, color } => {
127 let mut lines = Vec::new();
128 let segs = 12u32;
129 let up = (*top - *base).normalize_or_zero();
130 let right = up.any_orthogonal_vector().normalize();
131 let fwd = up.cross(right).normalize();
132 for i in 0..segs {
133 let a0 = i as f32 / segs as f32 * 2.0 * std::f32::consts::PI;
134 let a1 = (i + 1) as f32 / segs as f32 * 2.0 * std::f32::consts::PI;
135 let r0 = right * a0.cos() * *radius + fwd * a0.sin() * *radius;
136 let r1 = right * a1.cos() * *radius + fwd * a1.sin() * *radius;
137 lines.push((*base + r0, *base + r1, *color));
138 lines.push((*top + r0, *top + r1, *color));
139 if i % 3 == 0 {
140 lines.push((*base + r0, *top + r0, *color));
141 }
142 }
143 lines
144 }
145
146 Self::Disc { center, normal, radius, color } => {
147 let mut lines = Vec::new();
148 let right = normal.any_orthogonal_vector().normalize();
149 let up = normal.cross(right).normalize();
150 let segs = 16u32;
151 for i in 0..segs {
152 let a0 = i as f32 / segs as f32 * 2.0 * std::f32::consts::PI;
153 let a1 = (i + 1) as f32 / segs as f32 * 2.0 * std::f32::consts::PI;
154 let p0 = *center + right * a0.cos() * *radius + up * a0.sin() * *radius;
155 let p1 = *center + right * a1.cos() * *radius + up * a1.sin() * *radius;
156 lines.push((p0, p1, *color));
157 }
158 lines
159 }
160
161 Self::Circle { center, normal, radius, segments, color } => {
162 let mut lines = Vec::new();
163 let right = normal.any_orthogonal_vector().normalize();
164 let up = normal.cross(right).normalize();
165 let s = *segments;
166 for i in 0..s {
167 let a0 = i as f32 / s as f32 * 2.0 * std::f32::consts::PI;
168 let a1 = (i + 1) as f32 / s as f32 * 2.0 * std::f32::consts::PI;
169 let p0 = *center + right * a0.cos() * *radius + up * a0.sin() * *radius;
170 let p1 = *center + right * a1.cos() * *radius + up * a1.sin() * *radius;
171 lines.push((p0, p1, *color));
172 }
173 lines
174 }
175
176 Self::Axes { transform, size } => {
177 let o = transform.col(3).truncate();
178 let x = o + transform.col(0).truncate().normalize_or_zero() * *size;
179 let y = o + transform.col(1).truncate().normalize_or_zero() * *size;
180 let z = o + transform.col(2).truncate().normalize_or_zero() * *size;
181 vec![
182 (o, x, Vec4::new(1.0, 0.2, 0.2, 1.0)),
183 (o, y, Vec4::new(0.2, 1.0, 0.2, 1.0)),
184 (o, z, Vec4::new(0.2, 0.4, 1.0, 1.0)),
185 ]
186 }
187
188 Self::Cross { center, size, color } => {
189 let h = *size * 0.5;
190 vec![
191 (*center - Vec3::X*h, *center + Vec3::X*h, *color),
192 (*center - Vec3::Y*h, *center + Vec3::Y*h, *color),
193 (*center - Vec3::Z*h, *center + Vec3::Z*h, *color),
194 ]
195 }
196
197 Self::Grid { center, normal, size, divisions, color } => {
198 let mut lines = Vec::new();
199 let right = normal.any_orthogonal_vector().normalize();
200 let fwd = normal.cross(right).normalize();
201 let half = *size * 0.5;
202 let step = *size / *divisions as f32;
203 for i in 0..=*divisions {
204 let t = -half + i as f32 * step;
205 lines.push((
206 *center + right * t - fwd * half,
207 *center + right * t + fwd * half,
208 *color,
209 ));
210 lines.push((
211 *center - right * half + fwd * t,
212 *center + right * half + fwd * t,
213 *color,
214 ));
215 }
216 lines
217 }
218
219 Self::Bezier { p0, p1, p2, p3, steps, color } => {
220 let mut lines = Vec::new();
221 let mut prev = *p0;
222 for i in 1..=*steps {
223 let t = i as f32 / *steps as f32;
224 let u = 1.0 - t;
225 let pt = *p0*(u*u*u) + *p1*(3.0*u*u*t) + *p2*(3.0*u*t*t) + *p3*(t*t*t);
226 lines.push((prev, pt, *color));
227 prev = pt;
228 }
229 lines
230 }
231
232 Self::Spring { start, end, coils, radius, color } => {
233 let mut lines = Vec::new();
234 let dir = (*end - *start).normalize_or_zero();
235 let right = dir.any_orthogonal_vector().normalize();
236 let up = dir.cross(right).normalize();
237 let total = (*end - *start).length();
238 let steps = *coils * 16;
239 let mut prev = *start;
240 for i in 1..=steps {
241 let t = i as f32 / steps as f32;
242 let along = t * total;
243 let angle = i as f32 / 16.0 * 2.0 * std::f32::consts::PI;
244 let pos = *start + dir * along
245 + right * angle.cos() * *radius
246 + up * angle.sin() * *radius;
247 lines.push((prev, pos, *color));
248 prev = pos;
249 }
250 lines
251 }
252
253 Self::Torus { center, normal, major_r, minor_r, color } => {
254 let mut lines = Vec::new();
255 let right = normal.any_orthogonal_vector().normalize();
256 let up = normal.cross(right).normalize();
257 let major_segs = 16u32;
258 let minor_segs = 8u32;
259 for i in 0..major_segs {
260 let phi0 = i as f32 / major_segs as f32 * 2.0 * std::f32::consts::PI;
261 let phi1 = (i+1) as f32 / major_segs as f32 * 2.0 * std::f32::consts::PI;
262 let mc0 = *center + right * phi0.cos() * *major_r + up * phi0.sin() * *major_r;
263 let mc1 = *center + right * phi1.cos() * *major_r + up * phi1.sin() * *major_r;
264 let rad0 = (right * phi0.cos() + up * phi0.sin()).normalize();
266 let _ = (mc0, mc1);
267 for j in 0..minor_segs {
268 let theta0 = j as f32 / minor_segs as f32 * 2.0 * std::f32::consts::PI;
269 let theta1 = (j+1) as f32 / minor_segs as f32 * 2.0 * std::f32::consts::PI;
270 let p0t = mc0 + rad0 * theta0.cos() * *minor_r + *normal * theta0.sin() * *minor_r;
271 let p1t = mc0 + rad0 * theta1.cos() * *minor_r + *normal * theta1.sin() * *minor_r;
272 lines.push((p0t, p1t, *color));
273 }
274 }
275 lines
276 }
277
278 Self::Frustum { view_proj_inv, color } => {
279 let ndc_corners = [
280 Vec4::new(-1.0,-1.0,-1.0,1.0), Vec4::new( 1.0,-1.0,-1.0,1.0),
281 Vec4::new( 1.0, 1.0,-1.0,1.0), Vec4::new(-1.0, 1.0,-1.0,1.0),
282 Vec4::new(-1.0,-1.0, 1.0,1.0), Vec4::new( 1.0,-1.0, 1.0,1.0),
283 Vec4::new( 1.0, 1.0, 1.0,1.0), Vec4::new(-1.0, 1.0, 1.0,1.0),
284 ];
285 let ws: Vec<Vec3> = ndc_corners.iter().map(|&v| {
286 let r = *view_proj_inv * v;
287 r.truncate() / r.w
288 }).collect();
289 let edges = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),(0,4),(1,5),(2,6),(3,7)];
290 edges.iter().map(|&(a,b)| (ws[a], ws[b], *color)).collect()
291 }
292
293 Self::Cone { apex, base_center, radius, color } => {
294 let mut lines = Vec::new();
295 let axis = (*apex - *base_center).normalize_or_zero();
296 let right = axis.any_orthogonal_vector().normalize();
297 let fwd = axis.cross(right).normalize();
298 let segs = 8u32;
299 for i in 0..segs {
300 let a = i as f32 / segs as f32 * 2.0 * std::f32::consts::PI;
301 let a1 = (i+1) as f32 / segs as f32 * 2.0 * std::f32::consts::PI;
302 let r0 = *base_center + right * a.cos() * *radius + fwd * a.sin() * *radius;
303 let r1 = *base_center + right * a1.cos() * *radius + fwd * a1.sin() * *radius;
304 lines.push((*apex, r0, *color));
305 lines.push((r0, r1, *color));
306 }
307 lines
308 }
309
310 Self::Quad { corners, color, .. } => {
311 vec![
312 (corners[0], corners[1], *color),
313 (corners[1], corners[2], *color),
314 (corners[2], corners[3], *color),
315 (corners[3], corners[0], *color),
316 ]
317 }
318
319 Self::Point { pos, size, color } => {
320 let h = *size * 0.003;
321 vec![
322 (*pos - Vec3::X*h, *pos + Vec3::X*h, *color),
323 (*pos - Vec3::Y*h, *pos + Vec3::Y*h, *color),
324 (*pos - Vec3::Z*h, *pos + Vec3::Z*h, *color),
325 ]
326 }
327
328 Self::Text { .. } => vec![], }
330 }
331}
332
333fn circle_point(center: Vec3, radius: f32, angle: f32, axis: usize) -> Vec3 {
334 let (s, c) = angle.sin_cos();
335 match axis {
336 0 => center + Vec3::new(0.0, c * radius, s * radius),
337 1 => center + Vec3::new(c * radius, 0.0, s * radius),
338 _ => center + Vec3::new(c * radius, s * radius, 0.0),
339 }
340}
341
342#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
345pub struct PhysBodyId(pub u32);
346
347#[derive(Debug, Clone, Copy, PartialEq, Eq)]
348pub enum BodyType {
349 Static,
350 Kinematic,
351 Dynamic,
352 Cloth,
353 SoftBody,
354 Fluid,
355 SdfCollider,
356}
357
358#[derive(Debug, Clone)]
359pub struct PhysBodyState {
360 pub id: PhysBodyId,
361 pub body_type: BodyType,
362 pub position: Vec3,
363 pub rotation: Quat,
364 pub linear_velocity: Vec3,
365 pub angular_velocity: Vec3,
366 pub aabb_min: Vec3,
367 pub aabb_max: Vec3,
368 pub mass: f32,
369 pub asleep: bool,
370 pub active: bool,
371}
372
373impl PhysBodyState {
374 pub fn new(id: PhysBodyId) -> Self {
375 Self {
376 id,
377 body_type: BodyType::Dynamic,
378 position: Vec3::ZERO,
379 rotation: Quat::IDENTITY,
380 linear_velocity: Vec3::ZERO,
381 angular_velocity: Vec3::ZERO,
382 aabb_min: Vec3::splat(-0.5),
383 aabb_max: Vec3::splat( 0.5),
384 mass: 1.0,
385 asleep: false,
386 active: true,
387 }
388 }
389}
390
391#[derive(Debug, Clone)]
394pub struct ContactPoint {
395 pub position: Vec3,
396 pub normal: Vec3,
397 pub depth: f32,
398 pub body_a: PhysBodyId,
399 pub body_b: PhysBodyId,
400 pub friction_impulse: f32,
401 pub normal_impulse: f32,
402}
403
404#[derive(Debug, Clone)]
405pub struct ConstraintViz {
406 pub body_a: Option<PhysBodyId>,
407 pub body_b: Option<PhysBodyId>,
408 pub anchor_a: Vec3,
409 pub anchor_b: Vec3,
410 pub kind: ConstraintKind,
411 pub active: bool,
412 pub broken: bool,
413 pub strain: f32,
414}
415
416#[derive(Debug, Clone, Copy, PartialEq, Eq)]
417pub enum ConstraintKind {
418 Fixed,
419 Hinge,
420 BallSocket,
421 Slider,
422 Spring,
423 Distance,
424 Cone,
425 Gear,
426 Ik,
427}
428
429impl ConstraintKind {
430 pub fn color(&self) -> Vec4 {
431 match self {
432 Self::Fixed => Vec4::new(0.8, 0.8, 0.8, 1.0),
433 Self::Hinge => Vec4::new(1.0, 0.6, 0.1, 1.0),
434 Self::BallSocket => Vec4::new(0.3, 0.9, 0.5, 1.0),
435 Self::Slider => Vec4::new(0.5, 0.5, 1.0, 1.0),
436 Self::Spring => Vec4::new(1.0, 1.0, 0.2, 1.0),
437 Self::Distance => Vec4::new(0.7, 0.3, 1.0, 1.0),
438 Self::Cone => Vec4::new(1.0, 0.4, 0.4, 1.0),
439 Self::Gear => Vec4::new(0.5, 0.8, 1.0, 1.0),
440 Self::Ik => Vec4::new(0.2, 1.0, 1.0, 1.0),
441 }
442 }
443}
444
445#[derive(Debug, Clone)]
448pub struct ForceFieldViz {
449 pub name: String,
450 pub position: Vec3,
451 pub radius: f32,
452 pub strength: f32,
453 pub field_type: ForceFieldType,
454 pub active: bool,
455}
456
457#[derive(Debug, Clone, Copy, PartialEq, Eq)]
458pub enum ForceFieldType {
459 Gravity,
460 Radial,
461 Directional,
462 Vortex,
463 Turbulence,
464 Attractor,
465 Repulsor,
466 Wind,
467 Explosion,
468 Magnetic,
469}
470
471impl ForceFieldType {
472 pub fn color(&self) -> Vec4 {
473 match self {
474 Self::Gravity => Vec4::new(0.5, 0.5, 1.0, 0.7),
475 Self::Radial => Vec4::new(1.0, 0.5, 0.0, 0.7),
476 Self::Directional => Vec4::new(0.2, 0.8, 0.2, 0.7),
477 Self::Vortex => Vec4::new(0.9, 0.2, 0.9, 0.7),
478 Self::Turbulence => Vec4::new(0.8, 0.8, 0.0, 0.7),
479 Self::Attractor => Vec4::new(0.2, 0.8, 1.0, 0.7),
480 Self::Repulsor => Vec4::new(1.0, 0.2, 0.2, 0.7),
481 Self::Wind => Vec4::new(0.7, 0.9, 1.0, 0.7),
482 Self::Explosion => Vec4::new(1.0, 0.6, 0.0, 0.9),
483 Self::Magnetic => Vec4::new(0.6, 0.3, 1.0, 0.7),
484 }
485 }
486
487 pub fn sample_at(&self, relative_pos: Vec3, strength: f32, radius: f32) -> Vec3 {
488 let dist = relative_pos.length();
489 if dist > radius { return Vec3::ZERO; }
490 let falloff = (1.0 - dist / radius).powi(2);
491 match self {
492 Self::Gravity => Vec3::NEG_Y * strength * falloff,
493 Self::Radial => relative_pos.normalize_or_zero() * strength * falloff,
494 Self::Directional => Vec3::Y * strength * falloff,
495 Self::Vortex => {
496 let r = Vec3::new(relative_pos.z, 0.0, -relative_pos.x).normalize_or_zero();
497 r * strength * falloff
498 }
499 Self::Attractor => -relative_pos.normalize_or_zero() * strength * falloff,
500 Self::Repulsor => relative_pos.normalize_or_zero() * strength * falloff,
501 Self::Wind => Vec3::X * strength * falloff,
502 Self::Turbulence => Vec3::new(
503 Self::hash_noise(relative_pos, 0),
504 Self::hash_noise(relative_pos, 1),
505 Self::hash_noise(relative_pos, 2),
506 ) * strength * falloff,
507 Self::Explosion => relative_pos.normalize_or_zero() * strength * falloff * 5.0,
508 Self::Magnetic => {
509 let right = Vec3::X;
510 let tan = right.cross(relative_pos.normalize_or_zero()).normalize_or_zero();
511 tan * strength * falloff
512 }
513 }
514 }
515
516 fn hash_noise(p: Vec3, seed: u32) -> f32 {
517 let xi = ((p.x * 5.0 + seed as f32 * 31.0) as i32) as u32;
518 let yi = ((p.y * 5.0 + seed as f32 * 67.0) as i32) as u32;
519 let zi = ((p.z * 5.0 + seed as f32 * 113.0) as i32) as u32;
520 let h = xi.wrapping_mul(0x9e3779b9).wrapping_add(
521 yi.wrapping_mul(0x85ebca6b)).wrapping_add(
522 zi.wrapping_mul(0xc2b2ae35));
523 (h as f32 / u32::MAX as f32) * 2.0 - 1.0
524 }
525}
526
527#[derive(Debug, Clone)]
530pub struct ClothMeshViz {
531 pub name: String,
532 pub vertices: Vec<Vec3>,
533 pub edges: Vec<(usize, usize)>,
534 pub pinned: Vec<bool>,
535 pub velocities: Vec<Vec3>,
536 pub show_velocities: bool,
537 pub show_normals: bool,
538 pub show_pinned: bool,
539 pub color: Vec4,
540 pub velocity_scale: f32,
541}
542
543impl ClothMeshViz {
544 pub fn new(name: String, rows: usize, cols: usize) -> Self {
545 let mut verts = Vec::new();
546 let mut edges = Vec::new();
547 let mut pinned = Vec::new();
548 for r in 0..rows {
550 for c in 0..cols {
551 let x = c as f32 / (cols-1).max(1) as f32 - 0.5;
552 let y = 0.0;
553 let z = r as f32 / (rows-1).max(1) as f32 - 0.5;
554 verts.push(Vec3::new(x, y, z));
555 pinned.push(r == 0);
556 }
557 }
558 for r in 0..rows {
560 for c in 0..cols-1 {
561 edges.push((r*cols+c, r*cols+c+1));
562 }
563 }
564 for r in 0..rows-1 {
566 for c in 0..cols {
567 edges.push((r*cols+c, (r+1)*cols+c));
568 }
569 }
570 for r in 0..rows-1 {
572 for c in 0..cols-1 {
573 edges.push((r*cols+c, (r+1)*cols+c+1));
574 edges.push((r*cols+c+1, (r+1)*cols+c));
575 }
576 }
577 let velocities = vec![Vec3::ZERO; verts.len()];
578 Self {
579 name,
580 vertices: verts,
581 edges,
582 pinned,
583 velocities,
584 show_velocities: false,
585 show_normals: false,
586 show_pinned: true,
587 color: Vec4::new(0.4, 0.7, 1.0, 0.6),
588 velocity_scale: 0.1,
589 }
590 }
591
592 pub fn build_primitives(&self) -> Vec<DebugPrim> {
593 let mut prims = Vec::new();
594 for &(a, b) in &self.edges {
596 if a < self.vertices.len() && b < self.vertices.len() {
597 prims.push(DebugPrim::line(self.vertices[a], self.vertices[b], self.color));
598 }
599 }
600 if self.show_pinned {
602 let pin_color = Vec4::new(1.0, 0.3, 0.3, 1.0);
603 for (i, &pinned) in self.pinned.iter().enumerate() {
604 if pinned && i < self.vertices.len() {
605 prims.push(DebugPrim::Point {
606 pos: self.vertices[i],
607 size: 8.0,
608 color: pin_color,
609 });
610 }
611 }
612 }
613 if self.show_velocities {
615 let vel_color = Vec4::new(0.2, 1.0, 0.2, 0.8);
616 for (i, &vel) in self.velocities.iter().enumerate() {
617 if i < self.vertices.len() && vel.length() > 0.001 {
618 prims.push(DebugPrim::arrow(
619 self.vertices[i],
620 self.vertices[i] + vel * self.velocity_scale,
621 vel_color,
622 ));
623 }
624 }
625 }
626 prims
627 }
628}
629
630#[derive(Debug, Clone)]
633pub struct IkChainViz {
634 pub name: String,
635 pub joints: Vec<Vec3>,
636 pub orientations: Vec<Quat>,
637 pub lengths: Vec<f32>,
638 pub target: Vec3,
639 pub target_reached: bool,
640 pub iterations: u32,
641 pub error: f32,
642}
643
644impl IkChainViz {
645 pub fn build_primitives(&self) -> Vec<DebugPrim> {
646 let mut prims = Vec::new();
647 let bone_color = Vec4::new(0.9, 0.8, 0.2, 1.0);
648 let joint_color = Vec4::new(0.3, 0.9, 0.3, 1.0);
649 let target_color = if self.target_reached {
650 Vec4::new(0.0, 1.0, 0.0, 1.0)
651 } else {
652 Vec4::new(1.0, 0.4, 0.1, 1.0)
653 };
654
655 for i in 0..self.joints.len().saturating_sub(1) {
656 prims.push(DebugPrim::thick_line(self.joints[i], self.joints[i+1], bone_color));
657 prims.push(DebugPrim::wire_sphere(self.joints[i], 0.02, joint_color));
658 let m = Mat4::from_rotation_translation(self.orientations[i], self.joints[i]);
660 prims.push(DebugPrim::axes(m));
661 }
662 if let Some(last) = self.joints.last() {
663 prims.push(DebugPrim::wire_sphere(*last, 0.02, joint_color));
664 }
665 prims.push(DebugPrim::Cross { center: self.target, size: 0.1, color: target_color });
667 prims.push(DebugPrim::wire_sphere(self.target, 0.03, target_color));
668 prims
669 }
670}
671
672#[derive(Debug, Clone)]
675pub struct ParticleDebugViz {
676 pub positions: Vec<Vec3>,
677 pub velocities: Vec<Vec3>,
678 pub life_normalized: Vec<f32>,
679 pub show_velocity: bool,
680 pub show_life_color: bool,
681 pub velocity_scale: f32,
682 pub point_size: f32,
683 pub max_shown: usize,
684}
685
686impl ParticleDebugViz {
687 pub fn new() -> Self {
688 Self {
689 positions: Vec::new(),
690 velocities: Vec::new(),
691 life_normalized: Vec::new(),
692 show_velocity: false,
693 show_life_color: true,
694 velocity_scale: 0.05,
695 point_size: 4.0,
696 max_shown: 10000,
697 }
698 }
699
700 pub fn build_primitives(&self) -> Vec<DebugPrim> {
701 let mut prims = Vec::new();
702 let count = self.positions.len().min(self.max_shown);
703 for i in 0..count {
704 let life = self.life_normalized.get(i).copied().unwrap_or(1.0);
705 let color = if self.show_life_color {
706 lerp_color(
707 Vec4::new(1.0, 0.3, 0.1, 0.8),
708 Vec4::new(0.1, 0.5, 1.0, 0.3),
709 life,
710 )
711 } else {
712 Vec4::new(0.8, 0.8, 0.8, 0.6)
713 };
714 prims.push(DebugPrim::Point { pos: self.positions[i], size: self.point_size, color });
715 if self.show_velocity && i < self.velocities.len() {
716 let vel = self.velocities[i];
717 if vel.length() > 0.001 {
718 prims.push(DebugPrim::line(
719 self.positions[i],
720 self.positions[i] + vel * self.velocity_scale,
721 Vec4::new(0.4, 1.0, 0.4, 0.5),
722 ));
723 }
724 }
725 }
726 prims
727 }
728}
729
730impl Default for ParticleDebugViz { fn default() -> Self { Self::new() } }
731
732fn lerp_color(a: Vec4, b: Vec4, t: f32) -> Vec4 {
733 a + (b - a) * t
734}
735
736#[derive(Debug, Clone)]
739pub struct PhysicsDebugFlags {
740 pub show_bodies: bool,
741 pub show_aabbs: bool,
742 pub show_velocities: bool,
743 pub show_contacts: bool,
744 pub show_constraints: bool,
745 pub show_force_fields: bool,
746 pub show_cloth: bool,
747 pub show_ik_chains: bool,
748 pub show_particles: bool,
749 pub show_sleep_state: bool,
750 pub show_mass_centers: bool,
751 pub show_inertia_tensors: bool,
752 pub show_broadphase: bool,
753 pub velocity_scale: f32,
754 pub body_alpha: f32,
755}
756
757impl Default for PhysicsDebugFlags {
758 fn default() -> Self {
759 Self {
760 show_bodies: true,
761 show_aabbs: false,
762 show_velocities: false,
763 show_contacts: true,
764 show_constraints: true,
765 show_force_fields: true,
766 show_cloth: true,
767 show_ik_chains: true,
768 show_particles: false,
769 show_sleep_state: true,
770 show_mass_centers: false,
771 show_inertia_tensors: false,
772 show_broadphase: false,
773 velocity_scale: 0.1,
774 body_alpha: 0.5,
775 }
776 }
777}
778
779#[derive(Debug)]
780pub struct PhysicsDebugger {
781 pub flags: PhysicsDebugFlags,
782 pub bodies: HashMap<PhysBodyId, PhysBodyState>,
783 pub contacts: Vec<ContactPoint>,
784 pub constraints: Vec<ConstraintViz>,
785 pub force_fields: Vec<ForceFieldViz>,
786 pub cloth_meshes: Vec<ClothMeshViz>,
787 pub ik_chains: Vec<IkChainViz>,
788 pub particles: ParticleDebugViz,
789 pub stats: PhysicsStats,
790 prim_buffer: Vec<DebugPrim>,
791}
792
793#[derive(Debug, Default, Clone)]
794pub struct PhysicsStats {
795 pub active_bodies: u32,
796 pub sleeping_bodies: u32,
797 pub contact_count: u32,
798 pub constraint_count: u32,
799 pub step_time_ms: f32,
800 pub broadphase_pairs: u32,
801 pub substeps: u32,
802}
803
804impl PhysicsStats {
805 pub fn summary(&self) -> String {
806 format!(
807 "Bodies: {} active / {} sleeping | Contacts: {} | Constraints: {} | Step: {:.2}ms",
808 self.active_bodies, self.sleeping_bodies,
809 self.contact_count, self.constraint_count,
810 self.step_time_ms,
811 )
812 }
813}
814
815impl PhysicsDebugger {
816 pub fn new() -> Self {
817 Self {
818 flags: PhysicsDebugFlags::default(),
819 bodies: HashMap::new(),
820 contacts: Vec::new(),
821 constraints: Vec::new(),
822 force_fields: Vec::new(),
823 cloth_meshes: Vec::new(),
824 ik_chains: Vec::new(),
825 particles: ParticleDebugViz::new(),
826 stats: PhysicsStats::default(),
827 prim_buffer: Vec::new(),
828 }
829 }
830
831 pub fn register_body(&mut self, body: PhysBodyState) {
832 self.bodies.insert(body.id, body);
833 }
834
835 pub fn update_body(&mut self, id: PhysBodyId, pos: Vec3, rot: Quat, vel: Vec3) {
836 if let Some(b) = self.bodies.get_mut(&id) {
837 b.position = pos;
838 b.rotation = rot;
839 b.linear_velocity = vel;
840 }
841 }
842
843 pub fn set_contacts(&mut self, contacts: Vec<ContactPoint>) {
844 self.contacts = contacts;
845 self.stats.contact_count = self.contacts.len() as u32;
846 }
847
848 pub fn set_constraints(&mut self, constraints: Vec<ConstraintViz>) {
849 self.constraints = constraints;
850 self.stats.constraint_count = self.constraints.len() as u32;
851 }
852
853 pub fn add_force_field(&mut self, ff: ForceFieldViz) {
854 self.force_fields.push(ff);
855 }
856
857 pub fn add_cloth(&mut self, cloth: ClothMeshViz) {
858 self.cloth_meshes.push(cloth);
859 }
860
861 pub fn add_ik_chain(&mut self, chain: IkChainViz) {
862 self.ik_chains.push(chain);
863 }
864
865 pub fn build_primitives(&mut self) {
867 self.prim_buffer.clear();
868 let flags = &self.flags;
869
870 if flags.show_bodies {
872 for body in self.bodies.values() {
873 if !body.active { continue; }
874 let col = if body.asleep && flags.show_sleep_state {
875 Vec4::new(0.4, 0.4, 0.4, flags.body_alpha)
876 } else {
877 match body.body_type {
878 BodyType::Static => Vec4::new(0.4, 0.4, 0.8, flags.body_alpha),
879 BodyType::Kinematic => Vec4::new(0.8, 0.8, 0.2, flags.body_alpha),
880 BodyType::Dynamic => Vec4::new(0.3, 0.9, 0.3, flags.body_alpha),
881 BodyType::Cloth => Vec4::new(0.5, 0.7, 1.0, flags.body_alpha),
882 BodyType::SoftBody => Vec4::new(0.9, 0.5, 0.3, flags.body_alpha),
883 BodyType::Fluid => Vec4::new(0.2, 0.6, 1.0, flags.body_alpha),
884 BodyType::SdfCollider => Vec4::new(1.0, 0.4, 0.8, flags.body_alpha),
885 }
886 };
887 let m = Mat4::from_rotation_translation(body.rotation, body.position);
888 self.prim_buffer.push(DebugPrim::axes(m));
889 self.prim_buffer.push(DebugPrim::wire_sphere(body.position, 0.05, col));
890
891 if flags.show_mass_centers {
892 self.prim_buffer.push(DebugPrim::Cross {
893 center: body.position,
894 size: 0.08,
895 color: Vec4::new(1.0, 1.0, 0.0, 1.0),
896 });
897 }
898 }
899 }
900
901 if flags.show_aabbs {
903 for body in self.bodies.values() {
904 let col = Vec4::new(0.6, 0.6, 0.6, 0.3);
905 self.prim_buffer.push(DebugPrim::aabb(body.aabb_min, body.aabb_max, col));
906 }
907 }
908
909 if flags.show_velocities {
911 for body in self.bodies.values() {
912 if body.linear_velocity.length() > 0.001 {
913 self.prim_buffer.push(DebugPrim::arrow(
914 body.position,
915 body.position + body.linear_velocity * flags.velocity_scale,
916 Vec4::new(0.1, 1.0, 0.3, 0.9),
917 ));
918 }
919 if body.angular_velocity.length() > 0.001 {
920 self.prim_buffer.push(DebugPrim::arrow(
921 body.position,
922 body.position + body.angular_velocity.normalize() * 0.1,
923 Vec4::new(1.0, 0.6, 0.1, 0.9),
924 ));
925 }
926 }
927 }
928
929 if flags.show_contacts {
931 for contact in &self.contacts {
932 let depth_color = lerp_color(
933 Vec4::new(0.0, 1.0, 0.0, 1.0),
934 Vec4::new(1.0, 0.0, 0.0, 1.0),
935 (contact.depth * 10.0).clamp(0.0, 1.0),
936 );
937 self.prim_buffer.push(DebugPrim::point(contact.position, depth_color));
938 self.prim_buffer.push(DebugPrim::arrow(
939 contact.position,
940 contact.position + contact.normal * 0.1,
941 Vec4::new(0.8, 0.2, 0.8, 0.9),
942 ));
943 }
944 }
945
946 if flags.show_constraints {
948 for c in &self.constraints {
949 let col = if c.broken {
950 Vec4::new(1.0, 0.0, 0.0, 0.9)
951 } else if c.strain > 0.8 {
952 lerp_color(Vec4::new(0.0,1.0,0.0,1.0), Vec4::new(1.0,0.0,0.0,1.0), c.strain)
953 } else {
954 c.kind.color()
955 };
956 self.prim_buffer.push(DebugPrim::line(c.anchor_a, c.anchor_b, col));
957 self.prim_buffer.push(DebugPrim::wire_sphere(c.anchor_a, 0.02, col));
958 self.prim_buffer.push(DebugPrim::wire_sphere(c.anchor_b, 0.02, col));
959
960 match c.kind {
961 ConstraintKind::Hinge => {
962 let axis = (c.anchor_b - c.anchor_a).normalize_or_zero();
963 let mid = (c.anchor_a + c.anchor_b) * 0.5;
964 self.prim_buffer.push(DebugPrim::Disc {
965 center: mid, normal: axis, radius: 0.05, color: col,
966 });
967 }
968 ConstraintKind::BallSocket => {
969 self.prim_buffer.push(DebugPrim::wire_sphere(c.anchor_a, 0.06, col));
970 }
971 ConstraintKind::Spring => {
972 self.prim_buffer.push(DebugPrim::Spring {
973 start: c.anchor_a, end: c.anchor_b,
974 coils: 6, radius: 0.02, color: col,
975 });
976 }
977 ConstraintKind::Ik => {
978 self.prim_buffer.push(DebugPrim::Cross {
979 center: c.anchor_b, size: 0.08, color: col,
980 });
981 }
982 _ => {}
983 }
984 }
985 }
986
987 if flags.show_force_fields {
989 for ff in &self.force_fields {
990 if !ff.active { continue; }
991 let col = ff.field_type.color();
992 self.prim_buffer.push(DebugPrim::wire_sphere(ff.position, ff.radius, col));
993 let grid = 4i32;
995 for ix in -grid..=grid {
996 for iz in -grid..=grid {
997 let rel = Vec3::new(ix as f32, 0.0, iz as f32)
998 / grid as f32 * ff.radius * 0.8;
999 let force = ff.field_type.sample_at(rel, ff.strength, ff.radius);
1000 if force.length() > 0.001 {
1001 let from = ff.position + rel;
1002 let scaled = force * 0.05;
1003 self.prim_buffer.push(DebugPrim::arrow(from, from + scaled, col));
1004 }
1005 }
1006 }
1007 }
1008 }
1009
1010 if flags.show_cloth {
1012 for cloth in &self.cloth_meshes {
1013 let prims = cloth.build_primitives();
1014 self.prim_buffer.extend(prims);
1015 }
1016 }
1017
1018 if flags.show_ik_chains {
1020 for chain in &self.ik_chains {
1021 let prims = chain.build_primitives();
1022 self.prim_buffer.extend(prims);
1023 }
1024 }
1025
1026 if flags.show_particles {
1028 let prims = self.particles.build_primitives();
1029 self.prim_buffer.extend(prims);
1030 }
1031 }
1032
1033 pub fn build_line_buffer(&self) -> Vec<(Vec3, Vec3, Vec4)> {
1035 self.prim_buffer.iter()
1036 .flat_map(|p| p.to_lines())
1037 .collect()
1038 }
1039
1040 pub fn primitives(&self) -> &[DebugPrim] {
1041 &self.prim_buffer
1042 }
1043
1044 pub fn clear(&mut self) {
1045 self.bodies.clear();
1046 self.contacts.clear();
1047 self.constraints.clear();
1048 self.force_fields.clear();
1049 self.cloth_meshes.clear();
1050 self.ik_chains.clear();
1051 self.prim_buffer.clear();
1052 }
1053}
1054
1055impl Default for PhysicsDebugger {
1056 fn default() -> Self { Self::new() }
1057}
1058
1059#[cfg(test)]
1062mod tests {
1063 use super::*;
1064
1065 #[test]
1066 fn aabb_lines_count() {
1067 let p = DebugPrim::aabb(Vec3::splat(-1.0), Vec3::splat(1.0), Vec4::ONE);
1068 assert_eq!(p.to_lines().len(), 12);
1069 }
1070
1071 #[test]
1072 fn axes_emit_three_lines() {
1073 let p = DebugPrim::axes(Mat4::IDENTITY);
1074 assert_eq!(p.to_lines().len(), 3);
1075 }
1076
1077 #[test]
1078 fn force_field_attractor_points_inward() {
1079 let ff = ForceFieldType::Attractor;
1080 let rel = Vec3::new(1.0, 0.0, 0.0);
1081 let f = ff.sample_at(rel, 1.0, 2.0);
1082 assert!(f.x < 0.0, "attractor should pull x negative");
1083 }
1084
1085 #[test]
1086 fn cloth_grid_has_edges() {
1087 let c = ClothMeshViz::new("test".into(), 4, 4);
1088 assert!(!c.edges.is_empty());
1089 }
1090
1091 #[test]
1092 fn physics_debugger_build() {
1093 let mut dbg = PhysicsDebugger::new();
1094 let mut b = PhysBodyState::new(PhysBodyId(1));
1095 b.body_type = BodyType::Dynamic;
1096 dbg.register_body(b);
1097 dbg.build_primitives();
1098 assert!(!dbg.prim_buffer.is_empty());
1099 }
1100}