1#[allow(dead_code, unused_variables, unused_mut, unused_imports)]
2
3use glam::{Vec2, Vec3, Vec4, Quat, Mat4};
4use std::collections::{HashMap, VecDeque, HashSet, BTreeMap};
5
6pub const GRAVITY: f32 = 9.80665;
11pub const PI: f32 = std::f32::consts::PI;
12pub const TWO_PI: f32 = 2.0 * PI;
13pub const HALF_PI: f32 = PI * 0.5;
14pub const DEG_TO_RAD: f32 = PI / 180.0;
15pub const RAD_TO_DEG: f32 = 180.0 / PI;
16pub const EPSILON: f32 = 1e-6;
17pub const SLEEP_LINEAR_THRESHOLD: f32 = 0.01;
18pub const SLEEP_ANGULAR_THRESHOLD: f32 = 0.01;
19pub const DEFAULT_FRICTION: f32 = 0.5;
20pub const DEFAULT_RESTITUTION: f32 = 0.3;
21pub const AIR_DENSITY: f32 = 1.225; pub const WATER_DENSITY: f32 = 1000.0;
23pub const STEEL_DENSITY: f32 = 7850.0;
24pub const WOOD_DENSITY: f32 = 700.0;
25pub const CONCRETE_DENSITY: f32 = 2400.0;
26pub const RUBBER_DENSITY: f32 = 1500.0;
27pub const GLASS_DENSITY: f32 = 2500.0;
28pub const ALUMINUM_DENSITY: f32 = 2700.0;
29pub const COPPER_DENSITY: f32 = 8960.0;
30pub const GOLD_DENSITY: f32 = 19300.0;
31pub const ICE_DENSITY: f32 = 917.0;
32pub const SAND_DENSITY: f32 = 1600.0;
33
34pub fn inertia_tensor_box(mass: f32, half_extents: Vec3) -> Mat4 {
41 let w = 2.0 * half_extents.x;
42 let h = 2.0 * half_extents.y;
43 let d = 2.0 * half_extents.z;
44 let ixx = (1.0 / 12.0) * mass * (h * h + d * d);
45 let iyy = (1.0 / 12.0) * mass * (w * w + d * d);
46 let izz = (1.0 / 12.0) * mass * (w * w + h * h);
47 Mat4::from_cols(
48 Vec4::new(ixx, 0.0, 0.0, 0.0),
49 Vec4::new(0.0, iyy, 0.0, 0.0),
50 Vec4::new(0.0, 0.0, izz, 0.0),
51 Vec4::new(0.0, 0.0, 0.0, 1.0),
52 )
53}
54
55pub fn inertia_tensor_sphere(mass: f32, radius: f32) -> Mat4 {
58 let i = (2.0 / 5.0) * mass * radius * radius;
59 Mat4::from_cols(
60 Vec4::new(i, 0.0, 0.0, 0.0),
61 Vec4::new(0.0, i, 0.0, 0.0),
62 Vec4::new(0.0, 0.0, i, 0.0),
63 Vec4::new(0.0, 0.0, 0.0, 1.0),
64 )
65}
66
67pub fn inertia_tensor_capsule(mass: f32, radius: f32, half_height: f32) -> Mat4 {
70 let h = 2.0 * half_height;
71 let r = radius;
72 let vol_cyl = PI * r * r * h;
74 let vol_sph = (4.0 / 3.0) * PI * r * r * r;
76 let total_vol = vol_cyl + vol_sph;
77 let m_cyl = mass * vol_cyl / total_vol;
78 let m_sph = mass * vol_sph / total_vol;
79 let iyy_cyl = 0.5 * m_cyl * r * r;
81 let ixx_cyl = (1.0 / 12.0) * m_cyl * (3.0 * r * r + h * h);
82 let i_sph_local = (2.0 / 5.0) * m_sph * r * r;
84 let d = half_height + 3.0 * r / 8.0;
86 let ixx_sph = i_sph_local + m_sph * d * d;
87 let iyy_sph = i_sph_local;
88 let ixx = ixx_cyl + ixx_sph;
89 let iyy = iyy_cyl + iyy_sph;
90 let izz = ixx; Mat4::from_cols(
92 Vec4::new(ixx, 0.0, 0.0, 0.0),
93 Vec4::new(0.0, iyy, 0.0, 0.0),
94 Vec4::new(0.0, 0.0, izz, 0.0),
95 Vec4::new(0.0, 0.0, 0.0, 1.0),
96 )
97}
98
99pub fn inertia_tensor_cylinder(mass: f32, radius: f32, half_height: f32) -> Mat4 {
102 let h = 2.0 * half_height;
103 let r = radius;
104 let iyy = 0.5 * mass * r * r;
105 let ixx = (1.0 / 12.0) * mass * (3.0 * r * r + h * h);
106 let izz = ixx;
107 Mat4::from_cols(
108 Vec4::new(ixx, 0.0, 0.0, 0.0),
109 Vec4::new(0.0, iyy, 0.0, 0.0),
110 Vec4::new(0.0, 0.0, izz, 0.0),
111 Vec4::new(0.0, 0.0, 0.0, 1.0),
112 )
113}
114
115pub fn inertia_tensor_cone(mass: f32, radius: f32, height: f32) -> Mat4 {
118 let r = radius;
119 let h = height;
120 let iyy = (3.0 / 10.0) * mass * r * r;
121 let ixx = (3.0 / 80.0) * mass * (4.0 * r * r + h * h);
122 let izz = ixx;
123 Mat4::from_cols(
124 Vec4::new(ixx, 0.0, 0.0, 0.0),
125 Vec4::new(0.0, iyy, 0.0, 0.0),
126 Vec4::new(0.0, 0.0, izz, 0.0),
127 Vec4::new(0.0, 0.0, 0.0, 1.0),
128 )
129}
130
131pub fn inertia_parallel_axis(i_cm: Mat4, mass: f32, displacement: Vec3) -> Mat4 {
134 let d = displacement;
135 let d2 = d.dot(d);
136 let dxx = d.x * d.x;
138 let dyy = d.y * d.y;
139 let dzz = d.z * d.z;
140 let dxy = d.x * d.y;
141 let dxz = d.x * d.z;
142 let dyz = d.y * d.z;
143 let shift = Mat4::from_cols(
145 Vec4::new(mass * (d2 - dxx), -mass * dxy, -mass * dxz, 0.0),
146 Vec4::new(-mass * dxy, mass * (d2 - dyy), -mass * dyz, 0.0),
147 Vec4::new(-mass * dxz, -mass * dyz, mass * (d2 - dzz), 0.0),
148 Vec4::new(0.0, 0.0, 0.0, 0.0),
149 );
150 let c0 = i_cm.col(0) + shift.col(0);
152 let c1 = i_cm.col(1) + shift.col(1);
153 let c2 = i_cm.col(2) + shift.col(2);
154 let c3 = i_cm.col(3);
155 Mat4::from_cols(c0, c1, c2, c3)
156}
157
158#[derive(Debug, Clone)]
163pub struct RigidBodyInspector {
164 pub id: u64,
165 pub name: String,
166 pub mass: f32,
167 pub inertia_tensor: Mat4,
168 pub center_of_mass: Vec3,
169 pub linear_damping: f32,
170 pub angular_damping: f32,
171 pub linear_sleep_threshold: f32,
172 pub angular_sleep_threshold: f32,
173 pub is_kinematic: bool,
174 pub is_static: bool,
175 pub use_gravity: bool,
176 pub gravity_scale: f32,
177 pub collision_group: u32,
178 pub collision_mask: u32,
179 pub ccd_enabled: bool,
180 pub max_linear_velocity: f32,
181 pub max_angular_velocity: f32,
182 pub shape_type: RigidBodyShapeType,
183 pub shape_params: ShapeParameters,
184 pub position: Vec3,
185 pub orientation: Quat,
186 pub linear_velocity: Vec3,
187 pub angular_velocity: Vec3,
188 pub force_accumulator: Vec3,
189 pub torque_accumulator: Vec3,
190 pub sleeping: bool,
191 pub sleep_timer: f32,
192}
193
194#[derive(Debug, Clone, PartialEq)]
195pub enum RigidBodyShapeType {
196 Box,
197 Sphere,
198 Capsule,
199 Cylinder,
200 Cone,
201 ConvexHull,
202 TriangleMesh,
203 Heightfield,
204 Compound,
205}
206
207#[derive(Debug, Clone)]
208pub struct ShapeParameters {
209 pub half_extents: Vec3,
210 pub radius: f32,
211 pub half_height: f32,
212 pub height: f32,
213}
214
215impl Default for ShapeParameters {
216 fn default() -> Self {
217 Self {
218 half_extents: Vec3::splat(0.5),
219 radius: 0.5,
220 half_height: 1.0,
221 height: 2.0,
222 }
223 }
224}
225
226impl RigidBodyInspector {
227 pub fn new(id: u64, name: &str) -> Self {
228 Self {
229 id,
230 name: name.to_string(),
231 mass: 1.0,
232 inertia_tensor: Mat4::IDENTITY,
233 center_of_mass: Vec3::ZERO,
234 linear_damping: 0.01,
235 angular_damping: 0.05,
236 linear_sleep_threshold: SLEEP_LINEAR_THRESHOLD,
237 angular_sleep_threshold: SLEEP_ANGULAR_THRESHOLD,
238 is_kinematic: false,
239 is_static: false,
240 use_gravity: true,
241 gravity_scale: 1.0,
242 collision_group: 1,
243 collision_mask: 0xFFFF_FFFF,
244 ccd_enabled: false,
245 max_linear_velocity: 500.0,
246 max_angular_velocity: 50.0,
247 shape_type: RigidBodyShapeType::Box,
248 shape_params: ShapeParameters::default(),
249 position: Vec3::ZERO,
250 orientation: Quat::IDENTITY,
251 linear_velocity: Vec3::ZERO,
252 angular_velocity: Vec3::ZERO,
253 force_accumulator: Vec3::ZERO,
254 torque_accumulator: Vec3::ZERO,
255 sleeping: false,
256 sleep_timer: 0.0,
257 }
258 }
259
260 pub fn recompute_inertia(&mut self) {
262 self.inertia_tensor = match self.shape_type {
263 RigidBodyShapeType::Box => {
264 inertia_tensor_box(self.mass, self.shape_params.half_extents)
265 }
266 RigidBodyShapeType::Sphere => {
267 inertia_tensor_sphere(self.mass, self.shape_params.radius)
268 }
269 RigidBodyShapeType::Capsule => {
270 inertia_tensor_capsule(self.mass, self.shape_params.radius, self.shape_params.half_height)
271 }
272 RigidBodyShapeType::Cylinder => {
273 inertia_tensor_cylinder(self.mass, self.shape_params.radius, self.shape_params.half_height)
274 }
275 RigidBodyShapeType::Cone => {
276 inertia_tensor_cone(self.mass, self.shape_params.radius, self.shape_params.height)
277 }
278 _ => Mat4::IDENTITY,
279 };
280 }
281
282 pub fn integrate(&mut self, dt: f32) {
284 if self.is_static || self.is_kinematic {
285 return;
286 }
287 if self.sleeping {
288 self.force_accumulator = Vec3::ZERO;
289 self.torque_accumulator = Vec3::ZERO;
290 return;
291 }
292 let inv_mass = if self.mass > EPSILON { 1.0 / self.mass } else { 0.0 };
293 let gravity_force = if self.use_gravity {
294 Vec3::new(0.0, -GRAVITY * self.gravity_scale * self.mass, 0.0)
295 } else {
296 Vec3::ZERO
297 };
298 let total_force = self.force_accumulator + gravity_force;
299 let lin_accel = total_force * inv_mass;
301 self.linear_velocity += lin_accel * dt;
302 let lin_damp_factor = (1.0 - self.linear_damping * dt).max(0.0);
304 self.linear_velocity *= lin_damp_factor;
305 let lv_len = self.linear_velocity.length();
307 if lv_len > self.max_linear_velocity {
308 self.linear_velocity *= self.max_linear_velocity / lv_len;
309 }
310 self.position += self.linear_velocity * dt;
311 let inv_inertia_diag = Vec3::new(
313 if self.inertia_tensor.col(0).x > EPSILON { 1.0 / self.inertia_tensor.col(0).x } else { 0.0 },
314 if self.inertia_tensor.col(1).y > EPSILON { 1.0 / self.inertia_tensor.col(1).y } else { 0.0 },
315 if self.inertia_tensor.col(2).z > EPSILON { 1.0 / self.inertia_tensor.col(2).z } else { 0.0 },
316 );
317 let ang_accel = Vec3::new(
318 self.torque_accumulator.x * inv_inertia_diag.x,
319 self.torque_accumulator.y * inv_inertia_diag.y,
320 self.torque_accumulator.z * inv_inertia_diag.z,
321 );
322 self.angular_velocity += ang_accel * dt;
323 let ang_damp_factor = (1.0 - self.angular_damping * dt).max(0.0);
324 self.angular_velocity *= ang_damp_factor;
325 let av_len = self.angular_velocity.length();
326 if av_len > self.max_angular_velocity {
327 self.angular_velocity *= self.max_angular_velocity / av_len;
328 }
329 let omega = self.angular_velocity;
331 let dq = Quat::from_xyzw(omega.x * 0.5, omega.y * 0.5, omega.z * 0.5, 0.0);
332 let q = self.orientation;
333 let nq = Quat::from_xyzw(
335 dq.w * q.x + dq.x * q.w + dq.y * q.z - dq.z * q.y,
336 dq.w * q.y - dq.x * q.z + dq.y * q.w + dq.z * q.x,
337 dq.w * q.z + dq.x * q.y - dq.y * q.x + dq.z * q.w,
338 dq.w * q.w - dq.x * q.x - dq.y * q.y - dq.z * q.z,
339 );
340 self.orientation = Quat::from_xyzw(
341 q.x + nq.x * dt,
342 q.y + nq.y * dt,
343 q.z + nq.z * dt,
344 q.w + nq.w * dt,
345 ).normalize();
346 let v2 = self.linear_velocity.length_squared();
348 let w2 = self.angular_velocity.length_squared();
349 let lt = self.linear_sleep_threshold * self.linear_sleep_threshold;
350 let at = self.angular_sleep_threshold * self.angular_sleep_threshold;
351 if v2 < lt && w2 < at {
352 self.sleep_timer += dt;
353 if self.sleep_timer > 0.5 {
354 self.sleeping = true;
355 }
356 } else {
357 self.sleep_timer = 0.0;
358 self.sleeping = false;
359 }
360 self.force_accumulator = Vec3::ZERO;
362 self.torque_accumulator = Vec3::ZERO;
363 }
364
365 pub fn apply_force(&mut self, force: Vec3) {
366 self.force_accumulator += force;
367 }
368
369 pub fn apply_torque(&mut self, torque: Vec3) {
370 self.torque_accumulator += torque;
371 }
372
373 pub fn apply_force_at_point(&mut self, force: Vec3, world_point: Vec3) {
374 self.force_accumulator += force;
375 let r = world_point - (self.position + self.orientation * self.center_of_mass);
376 self.torque_accumulator += r.cross(force);
377 }
378
379 pub fn wake_up(&mut self) {
380 self.sleeping = false;
381 self.sleep_timer = 0.0;
382 }
383}
384
385#[derive(Debug, Clone)]
390pub struct JacobianRow {
391 pub j_lin_a: Vec3,
393 pub j_ang_a: Vec3,
395 pub j_lin_b: Vec3,
397 pub j_ang_b: Vec3,
399 pub bias: f32,
401 pub effective_mass: f32,
403 pub lambda: f32,
405 pub lambda_min: f32,
407 pub lambda_max: f32,
409}
410
411impl JacobianRow {
412 pub fn new() -> Self {
413 Self {
414 j_lin_a: Vec3::ZERO,
415 j_ang_a: Vec3::ZERO,
416 j_lin_b: Vec3::ZERO,
417 j_ang_b: Vec3::ZERO,
418 bias: 0.0,
419 effective_mass: 1.0,
420 lambda: 0.0,
421 lambda_min: f32::NEG_INFINITY,
422 lambda_max: f32::INFINITY,
423 }
424 }
425
426 pub fn compute_effective_mass(&mut self,
428 inv_mass_a: f32, inv_inertia_a: Vec3,
429 inv_mass_b: f32, inv_inertia_b: Vec3) {
430 let ka = inv_mass_a * self.j_lin_a.dot(self.j_lin_a)
431 + (inv_inertia_a * self.j_ang_a).dot(self.j_ang_a);
432 let kb = inv_mass_b * self.j_lin_b.dot(self.j_lin_b)
433 + (inv_inertia_b * self.j_ang_b).dot(self.j_ang_b);
434 let k = ka + kb;
435 self.effective_mass = if k.abs() > EPSILON { 1.0 / k } else { 0.0 };
436 }
437
438 pub fn solve_velocity(&mut self,
440 vel_a: Vec3, omega_a: Vec3,
441 vel_b: Vec3, omega_b: Vec3) -> f32 {
442 let jv = self.j_lin_a.dot(vel_a)
443 + self.j_ang_a.dot(omega_a)
444 + self.j_lin_b.dot(vel_b)
445 + self.j_ang_b.dot(omega_b);
446 let delta = self.effective_mass * (-jv - self.bias);
447 let old_lambda = self.lambda;
448 self.lambda = (self.lambda + delta).clamp(self.lambda_min, self.lambda_max);
449 self.lambda - old_lambda
450 }
451}
452
453#[derive(Debug, Clone)]
456pub struct FixedConstraint {
457 pub body_a: u64,
458 pub body_b: u64,
459 pub anchor_a: Vec3,
460 pub anchor_b: Vec3,
461 pub initial_rotation_ab: Quat,
462 pub breaking_force: f32,
463 pub breaking_torque: f32,
464 pub rows: Vec<JacobianRow>,
465}
466
467impl FixedConstraint {
468 pub fn new(body_a: u64, body_b: u64, anchor_a: Vec3, anchor_b: Vec3) -> Self {
469 let mut rows = Vec::new();
470 for _ in 0..6 { rows.push(JacobianRow::new()); }
471 Self {
472 body_a, body_b, anchor_a, anchor_b,
473 initial_rotation_ab: Quat::IDENTITY,
474 breaking_force: f32::INFINITY,
475 breaking_torque: f32::INFINITY,
476 rows,
477 }
478 }
479
480 pub fn build_jacobian(
481 &mut self,
482 pos_a: Vec3, rot_a: Quat,
483 pos_b: Vec3, rot_b: Quat,
484 baumgarte: f32, dt: f32,
485 ) {
486 let ra = rot_a * self.anchor_a;
487 let rb = rot_b * self.anchor_b;
488 let world_a = pos_a + ra;
489 let world_b = pos_b + rb;
490 let err = world_b - world_a;
491 let bias_factor = baumgarte / dt;
492 let axes = [Vec3::X, Vec3::Y, Vec3::Z];
494 for (i, &ax) in axes.iter().enumerate() {
495 let row = &mut self.rows[i];
496 row.j_lin_a = -ax;
497 row.j_ang_a = -ra.cross(ax);
498 row.j_lin_b = ax;
499 row.j_ang_b = rb.cross(ax);
500 row.bias = bias_factor * err.dot(ax);
501 }
502 let rot_diff = rot_b * self.initial_rotation_ab.inverse() * rot_a.inverse();
504 let (axis_err, angle_err) = quat_to_axis_angle(rot_diff);
505 let ang_err = axis_err * angle_err;
506 for (i, &ax) in axes.iter().enumerate() {
507 let row = &mut self.rows[3 + i];
508 row.j_lin_a = Vec3::ZERO;
509 row.j_ang_a = -ax;
510 row.j_lin_b = Vec3::ZERO;
511 row.j_ang_b = ax;
512 row.bias = bias_factor * ang_err.dot(ax);
513 }
514 }
515}
516
517#[derive(Debug, Clone)]
520pub struct HingeConstraint {
521 pub body_a: u64,
522 pub body_b: u64,
523 pub anchor_a: Vec3,
524 pub anchor_b: Vec3,
525 pub axis_a: Vec3, pub axis_b: Vec3,
527 pub lower_limit: f32,
528 pub upper_limit: f32,
529 pub enable_limits: bool,
530 pub motor_enabled: bool,
531 pub motor_target_velocity: f32,
532 pub motor_max_impulse: f32,
533 pub rows: Vec<JacobianRow>,
534}
535
536impl HingeConstraint {
537 pub fn new(body_a: u64, body_b: u64, anchor: Vec3, axis: Vec3) -> Self {
538 let mut rows = Vec::new();
539 for _ in 0..7 { rows.push(JacobianRow::new()); }
540 Self {
541 body_a, body_b,
542 anchor_a: anchor,
543 anchor_b: anchor,
544 axis_a: axis.normalize(),
545 axis_b: axis.normalize(),
546 lower_limit: -PI,
547 upper_limit: PI,
548 enable_limits: false,
549 motor_enabled: false,
550 motor_target_velocity: 0.0,
551 motor_max_impulse: 10.0,
552 rows,
553 }
554 }
555
556 pub fn build_jacobian(
557 &mut self,
558 pos_a: Vec3, rot_a: Quat,
559 pos_b: Vec3, rot_b: Quat,
560 baumgarte: f32, dt: f32,
561 ) {
562 let ra = rot_a * self.anchor_a;
563 let rb = rot_b * self.anchor_b;
564 let world_a = pos_a + ra;
565 let world_b = pos_b + rb;
566 let err = world_b - world_a;
567 let bias_factor = baumgarte / dt;
568 let axes = [Vec3::X, Vec3::Y, Vec3::Z];
570 for (i, &ax) in axes.iter().enumerate() {
571 let row = &mut self.rows[i];
572 row.j_lin_a = -ax;
573 row.j_ang_a = -(ra.cross(ax));
574 row.j_lin_b = ax;
575 row.j_ang_b = rb.cross(ax);
576 row.bias = bias_factor * err.dot(ax);
577 }
578 let hinge_world_a = rot_a * self.axis_a;
580 let hinge_world_b = rot_b * self.axis_b;
581 let perp1 = perpendicular_to(hinge_world_a);
583 let perp2 = hinge_world_a.cross(perp1).normalize();
584 let ang_err_vec = hinge_world_a.cross(hinge_world_b);
585 for (i, &perp) in [perp1, perp2].iter().enumerate() {
586 let row = &mut self.rows[3 + i];
587 row.j_lin_a = Vec3::ZERO;
588 row.j_ang_a = -perp;
589 row.j_lin_b = Vec3::ZERO;
590 row.j_ang_b = perp;
591 row.bias = bias_factor * ang_err_vec.dot(perp);
592 }
593 if self.motor_enabled {
595 let row = &mut self.rows[5];
596 row.j_lin_a = Vec3::ZERO;
597 row.j_ang_a = -hinge_world_a;
598 row.j_lin_b = Vec3::ZERO;
599 row.j_ang_b = hinge_world_a;
600 row.bias = -self.motor_target_velocity;
601 row.lambda_min = -self.motor_max_impulse;
602 row.lambda_max = self.motor_max_impulse;
603 }
604 if self.enable_limits {
606 let angle = compute_hinge_angle(rot_a, rot_b, &self.axis_a, &self.axis_b);
607 let row = &mut self.rows[6];
608 row.j_lin_a = Vec3::ZERO;
609 row.j_ang_a = -hinge_world_a;
610 row.j_lin_b = Vec3::ZERO;
611 row.j_ang_b = hinge_world_a;
612 if angle < self.lower_limit {
613 row.bias = bias_factor * (angle - self.lower_limit);
614 row.lambda_min = 0.0;
615 row.lambda_max = f32::INFINITY;
616 } else if angle > self.upper_limit {
617 row.bias = bias_factor * (angle - self.upper_limit);
618 row.lambda_min = f32::NEG_INFINITY;
619 row.lambda_max = 0.0;
620 } else {
621 row.lambda_min = 0.0;
622 row.lambda_max = 0.0;
623 }
624 }
625 }
626
627 pub fn get_current_angle(&self, rot_a: Quat, rot_b: Quat) -> f32 {
628 compute_hinge_angle(rot_a, rot_b, &self.axis_a, &self.axis_b)
629 }
630}
631
632fn compute_hinge_angle(rot_a: Quat, rot_b: Quat, axis_a: &Vec3, axis_b: &Vec3) -> f32 {
633 let wa = rot_a * *axis_a;
634 let wb = rot_b * *axis_b;
635 let perp = perpendicular_to(wa);
636 let ref_vec = rot_a * perp;
637 let cur_vec = wb - wb.dot(wa) * wa;
638 let cur_len = cur_vec.length();
639 if cur_len < EPSILON { return 0.0; }
640 let cur_norm = cur_vec / cur_len;
641 let cos_a = ref_vec.dot(cur_norm).clamp(-1.0, 1.0);
642 let sin_a = wa.dot(ref_vec.cross(cur_norm));
643 sin_a.atan2(cos_a)
644}
645
646#[derive(Debug, Clone)]
649pub struct SliderConstraint {
650 pub body_a: u64,
651 pub body_b: u64,
652 pub anchor_a: Vec3,
653 pub anchor_b: Vec3,
654 pub slide_axis_a: Vec3,
655 pub lower_limit: f32,
656 pub upper_limit: f32,
657 pub enable_limits: bool,
658 pub motor_enabled: bool,
659 pub motor_target_velocity: f32,
660 pub motor_max_force: f32,
661 pub rows: Vec<JacobianRow>,
662}
663
664impl SliderConstraint {
665 pub fn new(body_a: u64, body_b: u64, anchor_a: Vec3, slide_axis: Vec3) -> Self {
666 let mut rows = Vec::new();
667 for _ in 0..6 { rows.push(JacobianRow::new()); }
668 Self {
669 body_a, body_b,
670 anchor_a,
671 anchor_b: anchor_a,
672 slide_axis_a: slide_axis.normalize(),
673 lower_limit: -1.0,
674 upper_limit: 1.0,
675 enable_limits: false,
676 motor_enabled: false,
677 motor_target_velocity: 0.0,
678 motor_max_force: 100.0,
679 rows,
680 }
681 }
682
683 pub fn build_jacobian(
684 &mut self,
685 pos_a: Vec3, rot_a: Quat,
686 pos_b: Vec3, rot_b: Quat,
687 baumgarte: f32, dt: f32,
688 ) {
689 let slide_world = rot_a * self.slide_axis_a;
690 let perp1 = perpendicular_to(slide_world);
691 let perp2 = slide_world.cross(perp1).normalize();
692 let ra = rot_a * self.anchor_a;
693 let rb = rot_b * self.anchor_b;
694 let diff = (pos_b + rb) - (pos_a + ra);
695 let bias_factor = baumgarte / dt;
696 for (i, &perp) in [perp1, perp2].iter().enumerate() {
698 let row = &mut self.rows[i];
699 row.j_lin_a = -perp;
700 row.j_ang_a = -(ra.cross(perp));
701 row.j_lin_b = perp;
702 row.j_ang_b = rb.cross(perp);
703 row.bias = bias_factor * diff.dot(perp);
704 }
705 let axes = [Vec3::X, Vec3::Y, Vec3::Z];
707 let rot_diff = rot_b * rot_a.inverse();
708 let (axis_err, angle_err) = quat_to_axis_angle(rot_diff);
709 let ang_err = axis_err * angle_err;
710 for (i, &ax) in axes.iter().enumerate() {
711 let row = &mut self.rows[2 + i];
712 row.j_lin_a = Vec3::ZERO;
713 row.j_ang_a = -ax;
714 row.j_lin_b = Vec3::ZERO;
715 row.j_ang_b = ax;
716 row.bias = bias_factor * ang_err.dot(ax);
717 }
718 }
720
721 pub fn current_position(&self, pos_a: Vec3, rot_a: Quat, pos_b: Vec3, rot_b: Quat) -> f32 {
722 let slide_world = rot_a * self.slide_axis_a;
723 let ra = rot_a * self.anchor_a;
724 let rb = rot_b * self.anchor_b;
725 let diff = (pos_b + rb) - (pos_a + ra);
726 diff.dot(slide_world)
727 }
728}
729
730#[derive(Debug, Clone)]
733pub struct BallSocketConstraint {
734 pub body_a: u64,
735 pub body_b: u64,
736 pub pivot_a: Vec3,
737 pub pivot_b: Vec3,
738 pub rows: Vec<JacobianRow>,
739}
740
741impl BallSocketConstraint {
742 pub fn new(body_a: u64, body_b: u64, pivot_a: Vec3, pivot_b: Vec3) -> Self {
743 let mut rows = Vec::new();
744 for _ in 0..3 { rows.push(JacobianRow::new()); }
745 Self { body_a, body_b, pivot_a, pivot_b, rows }
746 }
747
748 pub fn build_jacobian(
749 &mut self,
750 pos_a: Vec3, rot_a: Quat,
751 pos_b: Vec3, rot_b: Quat,
752 baumgarte: f32, dt: f32,
753 ) {
754 let ra = rot_a * self.pivot_a;
755 let rb = rot_b * self.pivot_b;
756 let err = (pos_b + rb) - (pos_a + ra);
757 let bf = baumgarte / dt;
758 let axes = [Vec3::X, Vec3::Y, Vec3::Z];
759 for (i, &ax) in axes.iter().enumerate() {
760 let row = &mut self.rows[i];
761 row.j_lin_a = -ax;
762 row.j_ang_a = -(ra.cross(ax));
763 row.j_lin_b = ax;
764 row.j_ang_b = rb.cross(ax);
765 row.bias = bf * err.dot(ax);
766 }
767 }
768}
769
770#[derive(Debug, Clone)]
773pub struct ConeTwistConstraint {
774 pub body_a: u64,
775 pub body_b: u64,
776 pub pivot_a: Vec3,
777 pub pivot_b: Vec3,
778 pub axis_a: Vec3,
779 pub axis_b: Vec3,
780 pub swing_span1: f32,
781 pub swing_span2: f32,
782 pub twist_span: f32,
783 pub softness: f32,
784 pub bias_factor: f32,
785 pub relaxation_factor: f32,
786 pub rows: Vec<JacobianRow>,
787}
788
789impl ConeTwistConstraint {
790 pub fn new(body_a: u64, body_b: u64, pivot: Vec3, axis: Vec3) -> Self {
791 let mut rows = Vec::new();
792 for _ in 0..6 { rows.push(JacobianRow::new()); }
793 Self {
794 body_a, body_b,
795 pivot_a: pivot, pivot_b: pivot,
796 axis_a: axis.normalize(), axis_b: axis.normalize(),
797 swing_span1: HALF_PI,
798 swing_span2: HALF_PI,
799 twist_span: PI,
800 softness: 1.0,
801 bias_factor: 0.3,
802 relaxation_factor: 1.0,
803 rows,
804 }
805 }
806
807 pub fn build_jacobian(
808 &mut self,
809 pos_a: Vec3, rot_a: Quat,
810 pos_b: Vec3, rot_b: Quat,
811 baumgarte: f32, dt: f32,
812 ) {
813 let ra = rot_a * self.pivot_a;
814 let rb = rot_b * self.pivot_b;
815 let err = (pos_b + rb) - (pos_a + ra);
816 let bf = baumgarte / dt;
817 let axes = [Vec3::X, Vec3::Y, Vec3::Z];
819 for (i, &ax) in axes.iter().enumerate() {
820 let row = &mut self.rows[i];
821 row.j_lin_a = -ax;
822 row.j_ang_a = -(ra.cross(ax));
823 row.j_lin_b = ax;
824 row.j_ang_b = rb.cross(ax);
825 row.bias = bf * err.dot(ax);
826 }
827 let axis_world_a = rot_a * self.axis_a;
829 let axis_world_b = rot_b * self.axis_b;
830 let (swing_q, twist_q) = decompose_swing_twist(
832 rot_b * rot_a.inverse(), axis_world_a
833 );
834 let (swing_axis, swing_angle) = quat_to_axis_angle(swing_q);
835 let (twist_axis, twist_angle) = quat_to_axis_angle(twist_q);
836 {
838 let row = &mut self.rows[3];
839 row.j_lin_a = Vec3::ZERO;
840 row.j_lin_b = Vec3::ZERO;
841 let perp = perpendicular_to(axis_world_a);
842 row.j_ang_a = -perp;
843 row.j_ang_b = perp;
844 let limit = self.swing_span1.max(EPSILON);
845 if swing_angle > limit {
846 row.bias = bf * (swing_angle - limit);
847 row.lambda_min = f32::NEG_INFINITY;
848 row.lambda_max = 0.0;
849 }
850 }
851 {
853 let row = &mut self.rows[4];
854 row.j_lin_a = Vec3::ZERO;
855 row.j_lin_b = Vec3::ZERO;
856 row.j_ang_a = -axis_world_a;
857 row.j_ang_b = axis_world_a;
858 let limit = self.twist_span.max(EPSILON);
859 if twist_angle.abs() > limit {
860 let sign = twist_angle.signum();
861 row.bias = bf * (twist_angle - sign * limit);
862 if sign > 0.0 {
863 row.lambda_min = f32::NEG_INFINITY;
864 row.lambda_max = 0.0;
865 } else {
866 row.lambda_min = 0.0;
867 row.lambda_max = f32::INFINITY;
868 }
869 }
870 }
871 }
872}
873
874#[derive(Debug, Clone)]
877pub struct Generic6DOFConstraint {
878 pub body_a: u64,
879 pub body_b: u64,
880 pub frame_a: Mat4, pub frame_b: Mat4, pub linear_lower: Vec3,
883 pub linear_upper: Vec3,
884 pub angular_lower: Vec3,
885 pub angular_upper: Vec3,
886 pub linear_enabled: [bool; 3],
887 pub angular_enabled: [bool; 3],
888 pub spring_stiffness: [f32; 6],
889 pub spring_damping: [f32; 6],
890 pub spring_enabled: [bool; 6],
891 pub rows: Vec<JacobianRow>,
892}
893
894impl Generic6DOFConstraint {
895 pub fn new(body_a: u64, body_b: u64, frame_a: Mat4, frame_b: Mat4) -> Self {
896 let mut rows = Vec::new();
897 for _ in 0..6 { rows.push(JacobianRow::new()); }
898 Self {
899 body_a, body_b,
900 frame_a, frame_b,
901 linear_lower: Vec3::ZERO,
902 linear_upper: Vec3::ZERO,
903 angular_lower: Vec3::splat(-PI),
904 angular_upper: Vec3::splat(PI),
905 linear_enabled: [true; 3],
906 angular_enabled: [true; 3],
907 spring_stiffness: [0.0; 6],
908 spring_damping: [0.0; 6],
909 spring_enabled: [false; 6],
910 rows,
911 }
912 }
913
914 pub fn set_linear_free(&mut self) {
915 self.linear_lower = Vec3::splat(f32::NEG_INFINITY);
916 self.linear_upper = Vec3::splat(f32::INFINITY);
917 }
918
919 pub fn set_angular_locked(&mut self) {
920 self.angular_lower = Vec3::ZERO;
921 self.angular_upper = Vec3::ZERO;
922 }
923
924 pub fn build_jacobian(
925 &mut self,
926 pos_a: Vec3, rot_a: Quat,
927 pos_b: Vec3, rot_b: Quat,
928 baumgarte: f32, dt: f32,
929 ) {
930 let bf = baumgarte / dt;
931 let r_a = Mat4::from_quat(rot_a);
933 let ax_ax = Vec3::new(r_a.col(0).x, r_a.col(0).y, r_a.col(0).z);
934 let ax_ay = Vec3::new(r_a.col(1).x, r_a.col(1).y, r_a.col(1).z);
935 let ax_az = Vec3::new(r_a.col(2).x, r_a.col(2).y, r_a.col(2).z);
936 let pivot_a = Vec3::new(self.frame_a.col(3).x, self.frame_a.col(3).y, self.frame_a.col(3).z);
937 let pivot_b = Vec3::new(self.frame_b.col(3).x, self.frame_b.col(3).y, self.frame_b.col(3).z);
938 let ra = rot_a * pivot_a;
939 let rb = rot_b * pivot_b;
940 let diff = (pos_b + rb) - (pos_a + ra);
941 let lin_axes = [ax_ax, ax_ay, ax_az];
942 for (i, &ax) in lin_axes.iter().enumerate() {
943 let row = &mut self.rows[i];
944 row.j_lin_a = -ax;
945 row.j_ang_a = -(ra.cross(ax));
946 row.j_lin_b = ax;
947 row.j_ang_b = rb.cross(ax);
948 let dist = diff.dot(ax);
949 let lo = match i { 0 => self.linear_lower.x, 1 => self.linear_lower.y, _ => self.linear_lower.z };
950 let hi = match i { 0 => self.linear_upper.x, 1 => self.linear_upper.y, _ => self.linear_upper.z };
951 if self.linear_enabled[i] {
952 if lo == hi {
953 row.bias = bf * (dist - lo);
954 } else if dist < lo {
955 row.bias = bf * (dist - lo);
956 row.lambda_min = 0.0;
957 row.lambda_max = f32::INFINITY;
958 } else if dist > hi {
959 row.bias = bf * (dist - hi);
960 row.lambda_min = f32::NEG_INFINITY;
961 row.lambda_max = 0.0;
962 } else {
963 row.lambda_min = 0.0;
964 row.lambda_max = 0.0;
965 }
966 }
967 }
968 let rot_diff = rot_b * rot_a.inverse();
970 let (axis_err, angle_err) = quat_to_axis_angle(rot_diff);
971 let ang_err = axis_err * angle_err;
972 for (i, &ax) in lin_axes.iter().enumerate() {
973 let row = &mut self.rows[3 + i];
974 row.j_lin_a = Vec3::ZERO;
975 row.j_ang_a = -ax;
976 row.j_lin_b = Vec3::ZERO;
977 row.j_ang_b = ax;
978 row.bias = bf * ang_err.dot(ax);
979 }
980 }
981}
982
983#[derive(Debug, Clone)]
986pub struct SpringConstraint {
987 pub body_a: u64,
988 pub body_b: u64,
989 pub anchor_a: Vec3,
990 pub anchor_b: Vec3,
991 pub rest_length: f32,
992 pub stiffness: f32,
993 pub damping: f32,
994 pub min_length: f32,
995 pub max_length: f32,
996 pub row: JacobianRow,
997}
998
999impl SpringConstraint {
1000 pub fn new(body_a: u64, body_b: u64, anchor_a: Vec3, anchor_b: Vec3,
1001 rest_length: f32, stiffness: f32, damping: f32) -> Self {
1002 Self {
1003 body_a, body_b, anchor_a, anchor_b,
1004 rest_length, stiffness, damping,
1005 min_length: 0.0,
1006 max_length: f32::INFINITY,
1007 row: JacobianRow::new(),
1008 }
1009 }
1010
1011 pub fn build_jacobian(
1012 &mut self,
1013 pos_a: Vec3, rot_a: Quat, vel_a: Vec3, omega_a: Vec3,
1014 pos_b: Vec3, rot_b: Quat, vel_b: Vec3, omega_b: Vec3,
1015 dt: f32,
1016 ) {
1017 let ra = rot_a * self.anchor_a;
1018 let rb = rot_b * self.anchor_b;
1019 let wa = pos_a + ra;
1020 let wb = pos_b + rb;
1021 let delta = wb - wa;
1022 let dist = delta.length();
1023 if dist < EPSILON { return; }
1024 let n = delta / dist;
1025 let extension = dist - self.rest_length;
1026 let v_a_pt = vel_a + omega_a.cross(ra);
1028 let v_b_pt = vel_b + omega_b.cross(rb);
1029 let v_rel = (v_b_pt - v_a_pt).dot(n);
1030 let spring_force = -self.stiffness * extension - self.damping * v_rel;
1031 let row = &mut self.row;
1032 row.j_lin_a = -n;
1033 row.j_ang_a = -(ra.cross(n));
1034 row.j_lin_b = n;
1035 row.j_ang_b = rb.cross(n);
1036 row.bias = spring_force / (self.stiffness.max(EPSILON) * dt);
1038 row.lambda_min = if dist < self.min_length { 0.0 } else { f32::NEG_INFINITY };
1039 row.lambda_max = if dist > self.max_length { 0.0 } else { f32::INFINITY };
1040 }
1041}
1042
1043#[derive(Debug, Clone)]
1046pub struct GearConstraint {
1047 pub body_a: u64,
1048 pub body_b: u64,
1049 pub axis_a: Vec3,
1050 pub axis_b: Vec3,
1051 pub ratio: f32, pub row: JacobianRow,
1053}
1054
1055impl GearConstraint {
1056 pub fn new(body_a: u64, body_b: u64, axis_a: Vec3, axis_b: Vec3, ratio: f32) -> Self {
1057 Self { body_a, body_b, axis_a: axis_a.normalize(), axis_b: axis_b.normalize(), ratio, row: JacobianRow::new() }
1058 }
1059
1060 pub fn build_jacobian(&mut self, rot_a: Quat, rot_b: Quat) {
1061 let wa = rot_a * self.axis_a;
1062 let wb = rot_b * self.axis_b;
1063 let row = &mut self.row;
1064 row.j_lin_a = Vec3::ZERO;
1065 row.j_ang_a = wa;
1066 row.j_lin_b = Vec3::ZERO;
1067 row.j_ang_b = wb * (-self.ratio);
1068 row.bias = 0.0;
1069 row.lambda_min = f32::NEG_INFINITY;
1070 row.lambda_max = f32::INFINITY;
1071 }
1072}
1073
1074#[derive(Debug, Clone)]
1077pub struct RackAndPinionConstraint {
1078 pub body_pinion: u64, pub body_rack: u64, pub pinion_axis: Vec3, pub rack_axis: Vec3, pub pitch_radius: f32, pub row: JacobianRow,
1084}
1085
1086impl RackAndPinionConstraint {
1087 pub fn new(body_pinion: u64, body_rack: u64, pinion_axis: Vec3, rack_axis: Vec3, pitch_radius: f32) -> Self {
1088 Self {
1089 body_pinion, body_rack,
1090 pinion_axis: pinion_axis.normalize(),
1091 rack_axis: rack_axis.normalize(),
1092 pitch_radius,
1093 row: JacobianRow::new(),
1094 }
1095 }
1096
1097 pub fn build_jacobian(&mut self, rot_pinion: Quat, rot_rack: Quat) {
1098 let wa = rot_pinion * self.pinion_axis;
1099 let trans_ax = rot_rack * self.rack_axis;
1100 let row = &mut self.row;
1102 row.j_lin_a = Vec3::ZERO;
1103 row.j_ang_a = wa * self.pitch_radius;
1104 row.j_lin_b = trans_ax * (-1.0);
1105 row.j_ang_b = Vec3::ZERO;
1106 row.bias = 0.0;
1107 }
1108}
1109
1110#[derive(Debug, Clone)]
1113pub struct PulleyConstraint {
1114 pub body_a: u64,
1115 pub body_b: u64,
1116 pub anchor_a: Vec3, pub anchor_b: Vec3, pub fixed_point_a: Vec3, pub fixed_point_b: Vec3, pub ratio: f32, pub total_length: f32, pub row: JacobianRow,
1123}
1124
1125impl PulleyConstraint {
1126 pub fn new(body_a: u64, body_b: u64,
1127 anchor_a: Vec3, anchor_b: Vec3,
1128 fixed_a: Vec3, fixed_b: Vec3,
1129 ratio: f32, total_length: f32) -> Self {
1130 Self {
1131 body_a, body_b, anchor_a, anchor_b,
1132 fixed_point_a: fixed_a, fixed_point_b: fixed_b,
1133 ratio, total_length,
1134 row: JacobianRow::new(),
1135 }
1136 }
1137
1138 pub fn build_jacobian(
1139 &mut self,
1140 pos_a: Vec3, rot_a: Quat,
1141 pos_b: Vec3, rot_b: Quat,
1142 baumgarte: f32, dt: f32,
1143 ) {
1144 let ra = rot_a * self.anchor_a;
1145 let rb = rot_b * self.anchor_b;
1146 let wa = pos_a + ra;
1147 let wb = pos_b + rb;
1148 let dir_a = self.fixed_point_a - wa;
1149 let dist_a = dir_a.length();
1150 let dir_b = self.fixed_point_b - wb;
1151 let dist_b = dir_b.length();
1152 let n_a = if dist_a > EPSILON { dir_a / dist_a } else { Vec3::Y };
1153 let n_b = if dist_b > EPSILON { dir_b / dist_b } else { Vec3::Y };
1154 let constraint_err = dist_a + self.ratio * dist_b - self.total_length;
1155 let row = &mut self.row;
1156 row.j_lin_a = -n_a;
1157 row.j_ang_a = -(ra.cross(n_a));
1158 row.j_lin_b = -n_b * self.ratio;
1159 row.j_ang_b = -(rb.cross(n_b)) * self.ratio;
1160 row.bias = (baumgarte / dt) * constraint_err;
1161 row.lambda_min = 0.0; row.lambda_max = f32::INFINITY;
1163 }
1164}
1165
1166#[derive(Debug, Clone)]
1169pub struct MotorConstraint {
1170 pub body_a: u64,
1171 pub body_b: u64,
1172 pub axis: Vec3,
1173 pub target_velocity: f32,
1174 pub max_torque: f32,
1175 pub servo_enabled: bool,
1176 pub target_angle: f32,
1177 pub servo_stiffness: f32,
1178 pub row: JacobianRow,
1179}
1180
1181impl MotorConstraint {
1182 pub fn new(body_a: u64, body_b: u64, axis: Vec3) -> Self {
1183 Self {
1184 body_a, body_b,
1185 axis: axis.normalize(),
1186 target_velocity: 0.0,
1187 max_torque: 100.0,
1188 servo_enabled: false,
1189 target_angle: 0.0,
1190 servo_stiffness: 10.0,
1191 row: JacobianRow::new(),
1192 }
1193 }
1194
1195 pub fn build_jacobian(&mut self, rot_a: Quat, rot_b: Quat, current_angle: f32, dt: f32) {
1196 let wa = rot_a * self.axis;
1197 let row = &mut self.row;
1198 row.j_lin_a = Vec3::ZERO;
1199 row.j_ang_a = -wa;
1200 row.j_lin_b = Vec3::ZERO;
1201 row.j_ang_b = wa;
1202 if self.servo_enabled {
1203 let angle_err = self.target_angle - current_angle;
1204 row.bias = -self.target_velocity - self.servo_stiffness * angle_err * dt;
1205 } else {
1206 row.bias = -self.target_velocity;
1207 }
1208 row.lambda_min = -self.max_torque * dt;
1209 row.lambda_max = self.max_torque * dt;
1210 }
1211}
1212
1213#[derive(Debug, Clone)]
1216pub struct LimitConstraint {
1217 pub body_a: u64,
1218 pub body_b: u64,
1219 pub axis: Vec3,
1220 pub linear: bool, pub lower: f32,
1222 pub upper: f32,
1223 pub anchor_a: Vec3,
1224 pub anchor_b: Vec3,
1225 pub restitution: f32,
1226 pub row: JacobianRow,
1227}
1228
1229impl LimitConstraint {
1230 pub fn new(body_a: u64, body_b: u64, axis: Vec3, lower: f32, upper: f32, linear: bool) -> Self {
1231 Self {
1232 body_a, body_b,
1233 axis: axis.normalize(),
1234 linear, lower, upper,
1235 anchor_a: Vec3::ZERO, anchor_b: Vec3::ZERO,
1236 restitution: 0.0,
1237 row: JacobianRow::new(),
1238 }
1239 }
1240
1241 pub fn build_jacobian(
1242 &mut self,
1243 pos_a: Vec3, rot_a: Quat, vel_a: Vec3, omega_a: Vec3,
1244 pos_b: Vec3, rot_b: Quat, vel_b: Vec3, omega_b: Vec3,
1245 baumgarte: f32, dt: f32,
1246 ) {
1247 let bf = baumgarte / dt;
1248 if self.linear {
1249 let wa_ax = rot_a * self.axis;
1250 let ra = rot_a * self.anchor_a;
1251 let rb = rot_b * self.anchor_b;
1252 let diff = (pos_b + rb) - (pos_a + ra);
1253 let pos = diff.dot(wa_ax);
1254 self.row.j_lin_a = -wa_ax;
1255 self.row.j_ang_a = -(ra.cross(wa_ax));
1256 self.row.j_lin_b = wa_ax;
1257 self.row.j_ang_b = rb.cross(wa_ax);
1258 if pos < self.lower {
1259 self.row.bias = bf * (pos - self.lower);
1260 let rel_vel = self.row.j_lin_a.dot(vel_a) + self.row.j_lin_b.dot(vel_b);
1261 if rel_vel < 0.0 { self.row.bias += self.restitution * rel_vel; }
1262 self.row.lambda_min = 0.0;
1263 self.row.lambda_max = f32::INFINITY;
1264 } else if pos > self.upper {
1265 self.row.bias = bf * (pos - self.upper);
1266 self.row.lambda_min = f32::NEG_INFINITY;
1267 self.row.lambda_max = 0.0;
1268 }
1269 } else {
1270 let wa = rot_a * self.axis;
1271 self.row.j_lin_a = Vec3::ZERO;
1272 self.row.j_ang_a = -wa;
1273 self.row.j_lin_b = Vec3::ZERO;
1274 self.row.j_ang_b = wa;
1275 }
1277 }
1278}
1279
1280#[derive(Debug, Clone)]
1283pub struct DistanceConstraint {
1284 pub body_a: u64,
1285 pub body_b: u64,
1286 pub anchor_a: Vec3,
1287 pub anchor_b: Vec3,
1288 pub min_distance: f32,
1289 pub max_distance: f32,
1290 pub row: JacobianRow,
1291}
1292
1293impl DistanceConstraint {
1294 pub fn new(body_a: u64, body_b: u64, anchor_a: Vec3, anchor_b: Vec3, dist: f32) -> Self {
1295 Self {
1296 body_a, body_b, anchor_a, anchor_b,
1297 min_distance: dist, max_distance: dist,
1298 row: JacobianRow::new(),
1299 }
1300 }
1301
1302 pub fn build_jacobian(
1303 &mut self,
1304 pos_a: Vec3, rot_a: Quat,
1305 pos_b: Vec3, rot_b: Quat,
1306 baumgarte: f32, dt: f32,
1307 ) {
1308 let ra = rot_a * self.anchor_a;
1309 let rb = rot_b * self.anchor_b;
1310 let wa = pos_a + ra;
1311 let wb = pos_b + rb;
1312 let delta = wb - wa;
1313 let dist = delta.length();
1314 if dist < EPSILON { return; }
1315 let n = delta / dist;
1316 let bf = baumgarte / dt;
1317 self.row.j_lin_a = -n;
1318 self.row.j_ang_a = -(ra.cross(n));
1319 self.row.j_lin_b = n;
1320 self.row.j_ang_b = rb.cross(n);
1321 if dist < self.min_distance {
1322 self.row.bias = bf * (dist - self.min_distance);
1323 self.row.lambda_min = 0.0;
1324 self.row.lambda_max = f32::INFINITY;
1325 } else if dist > self.max_distance {
1326 self.row.bias = bf * (dist - self.max_distance);
1327 self.row.lambda_min = f32::NEG_INFINITY;
1328 self.row.lambda_max = 0.0;
1329 } else {
1330 self.row.lambda_min = f32::NEG_INFINITY;
1331 self.row.lambda_max = f32::INFINITY;
1332 }
1333 }
1334}
1335
1336#[derive(Debug, Clone)]
1339pub struct PointToPointConstraint {
1340 pub body_a: u64,
1341 pub body_b: u64,
1342 pub pivot_a: Vec3,
1343 pub pivot_b: Vec3,
1344 pub tau: f32, pub damping: f32,
1346 pub impulse_clamp: f32,
1347 pub rows: Vec<JacobianRow>,
1348}
1349
1350impl PointToPointConstraint {
1351 pub fn new(body_a: u64, body_b: u64, pivot: Vec3) -> Self {
1352 let mut rows = Vec::new();
1353 for _ in 0..3 { rows.push(JacobianRow::new()); }
1354 Self {
1355 body_a, body_b,
1356 pivot_a: pivot, pivot_b: pivot,
1357 tau: 0.3, damping: 1.0,
1358 impulse_clamp: 0.0,
1359 rows,
1360 }
1361 }
1362
1363 pub fn build_jacobian(
1364 &mut self,
1365 pos_a: Vec3, rot_a: Quat, vel_a: Vec3, omega_a: Vec3,
1366 pos_b: Vec3, rot_b: Quat, vel_b: Vec3, omega_b: Vec3,
1367 dt: f32,
1368 ) {
1369 let ra = rot_a * self.pivot_a;
1370 let rb = rot_b * self.pivot_b;
1371 let err = (pos_b + rb) - (pos_a + ra);
1372 let axes = [Vec3::X, Vec3::Y, Vec3::Z];
1373 for (i, &ax) in axes.iter().enumerate() {
1374 let row = &mut self.rows[i];
1375 row.j_lin_a = -ax;
1376 row.j_ang_a = -(ra.cross(ax));
1377 row.j_lin_b = ax;
1378 row.j_ang_b = rb.cross(ax);
1379 let v_a_pt = vel_a + omega_a.cross(ra);
1381 let v_b_pt = vel_b + omega_b.cross(rb);
1382 let rel_v = (v_b_pt - v_a_pt).dot(ax);
1383 row.bias = (self.tau / dt) * err.dot(ax) + self.damping * rel_v;
1384 if self.impulse_clamp > 0.0 {
1385 row.lambda_min = -self.impulse_clamp;
1386 row.lambda_max = self.impulse_clamp;
1387 }
1388 }
1389 }
1390}
1391
1392#[derive(Debug, Clone)]
1395pub struct AngularConstraint {
1396 pub body_a: u64,
1397 pub body_b: u64,
1398 pub axis: Vec3,
1399 pub target_angle: f32,
1400 pub stiffness: f32,
1401 pub damping: f32,
1402 pub row: JacobianRow,
1403}
1404
1405impl AngularConstraint {
1406 pub fn new(body_a: u64, body_b: u64, axis: Vec3) -> Self {
1407 Self {
1408 body_a, body_b,
1409 axis: axis.normalize(),
1410 target_angle: 0.0,
1411 stiffness: 100.0,
1412 damping: 10.0,
1413 row: JacobianRow::new(),
1414 }
1415 }
1416
1417 pub fn build_jacobian(
1418 &mut self,
1419 rot_a: Quat, omega_a: Vec3,
1420 rot_b: Quat, omega_b: Vec3,
1421 current_angle: f32, dt: f32,
1422 ) {
1423 let wa = rot_a * self.axis;
1424 let angle_err = self.target_angle - current_angle;
1425 let rel_omega = (omega_b - omega_a).dot(wa);
1426 let row = &mut self.row;
1427 row.j_lin_a = Vec3::ZERO;
1428 row.j_ang_a = -wa;
1429 row.j_lin_b = Vec3::ZERO;
1430 row.j_ang_b = wa;
1431 row.bias = self.stiffness * angle_err * dt - self.damping * rel_omega;
1432 }
1433}
1434
1435#[derive(Debug, Clone)]
1437pub struct WeldConstraint {
1438 pub inner: FixedConstraint,
1439 pub allow_rotation: bool,
1440}
1441
1442impl WeldConstraint {
1443 pub fn new(body_a: u64, body_b: u64, anchor_a: Vec3, anchor_b: Vec3) -> Self {
1444 let inner = FixedConstraint::new(body_a, body_b, anchor_a, anchor_b);
1445 Self { inner, allow_rotation: false }
1446 }
1447}
1448
1449#[derive(Debug, Clone)]
1454pub enum Constraint {
1455 Fixed(FixedConstraint),
1456 Hinge(HingeConstraint),
1457 Slider(SliderConstraint),
1458 BallSocket(BallSocketConstraint),
1459 ConeTwist(ConeTwistConstraint),
1460 Generic6DOF(Generic6DOFConstraint),
1461 Spring(SpringConstraint),
1462 Gear(GearConstraint),
1463 RackAndPinion(RackAndPinionConstraint),
1464 Pulley(PulleyConstraint),
1465 Motor(MotorConstraint),
1466 Limit(LimitConstraint),
1467 Distance(DistanceConstraint),
1468 PointToPoint(PointToPointConstraint),
1469 Angular(AngularConstraint),
1470 Weld(WeldConstraint),
1471}
1472
1473impl Constraint {
1474 pub fn name(&self) -> &'static str {
1475 match self {
1476 Constraint::Fixed(_) => "Fixed",
1477 Constraint::Hinge(_) => "Hinge",
1478 Constraint::Slider(_) => "Slider",
1479 Constraint::BallSocket(_) => "BallSocket",
1480 Constraint::ConeTwist(_) => "ConeTwist",
1481 Constraint::Generic6DOF(_) => "Generic6DOF",
1482 Constraint::Spring(_) => "Spring",
1483 Constraint::Gear(_) => "Gear",
1484 Constraint::RackAndPinion(_) => "RackAndPinion",
1485 Constraint::Pulley(_) => "Pulley",
1486 Constraint::Motor(_) => "Motor",
1487 Constraint::Limit(_) => "Limit",
1488 Constraint::Distance(_) => "Distance",
1489 Constraint::PointToPoint(_) => "PointToPoint",
1490 Constraint::Angular(_) => "Angular",
1491 Constraint::Weld(_) => "Weld",
1492 }
1493 }
1494}
1495
1496#[derive(Debug, Clone)]
1501pub struct JointVisualizer {
1502 pub constraint_id: u64,
1503 pub pivot_world: Vec3,
1504 pub axis_world: Vec3,
1505 pub axis_color: Vec4,
1506 pub arc_segments: u32,
1507 pub arc_radius: f32,
1508 pub show_limits: bool,
1509 pub show_drive: bool,
1510 pub selected: bool,
1511}
1512
1513#[derive(Debug, Clone)]
1514pub struct ArcPoint {
1515 pub pos: Vec3,
1516 pub t: f32, }
1518
1519impl JointVisualizer {
1520 pub fn new(constraint_id: u64, pivot: Vec3, axis: Vec3) -> Self {
1521 Self {
1522 constraint_id,
1523 pivot_world: pivot,
1524 axis_world: axis.normalize(),
1525 axis_color: Vec4::new(1.0, 1.0, 0.0, 1.0),
1526 arc_segments: 32,
1527 arc_radius: 0.2,
1528 show_limits: true,
1529 show_drive: true,
1530 selected: false,
1531 }
1532 }
1533
1534 pub fn compute_limit_arc(&self, lower: f32, upper: f32) -> Vec<ArcPoint> {
1536 let mut points = Vec::new();
1537 let n = self.arc_segments as usize;
1538 if n == 0 { return points; }
1539 let perp1 = perpendicular_to(self.axis_world);
1541 let perp2 = self.axis_world.cross(perp1).normalize();
1542 let span = upper - lower;
1543 let step = span / n as f32;
1544 for i in 0..=n {
1545 let angle = lower + step * i as f32;
1546 let t = i as f32 / n as f32;
1547 let c = angle.cos();
1548 let s = angle.sin();
1549 let local = perp1 * c + perp2 * s;
1550 let pos = self.pivot_world + local * self.arc_radius;
1551 points.push(ArcPoint { pos, t });
1552 }
1553 points
1554 }
1555
1556 pub fn compute_cone_surface(&self, half_angle: f32) -> Vec<Vec3> {
1558 let mut pts = Vec::new();
1559 let n = self.arc_segments as usize;
1560 let perp1 = perpendicular_to(self.axis_world);
1561 let perp2 = self.axis_world.cross(perp1).normalize();
1562 let tip = self.pivot_world;
1563 let r = self.arc_radius * half_angle.tan();
1564 let apex = tip + self.axis_world * self.arc_radius;
1565 for i in 0..n {
1566 let angle = TWO_PI * i as f32 / n as f32;
1567 let base = apex + (perp1 * angle.cos() + perp2 * angle.sin()) * r;
1568 pts.push(tip);
1569 pts.push(base);
1570 let angle_next = TWO_PI * (i + 1) as f32 / n as f32;
1571 let base_next = apex + (perp1 * angle_next.cos() + perp2 * angle_next.sin()) * r;
1572 pts.push(base_next);
1573 }
1574 pts
1575 }
1576
1577 pub fn axis_line(&self, len: f32) -> (Vec3, Vec3) {
1579 let start = self.pivot_world - self.axis_world * len * 0.5;
1580 let end = self.pivot_world + self.axis_world * len * 0.5;
1581 (start, end)
1582 }
1583
1584 pub fn drive_indicator(&self, target_angle: f32) -> Vec3 {
1586 let perp1 = perpendicular_to(self.axis_world);
1587 let perp2 = self.axis_world.cross(perp1).normalize();
1588 let c = target_angle.cos();
1589 let s = target_angle.sin();
1590 self.pivot_world + (perp1 * c + perp2 * s) * self.arc_radius
1591 }
1592}
1593
1594#[derive(Debug, Clone)]
1595pub struct JointEditor {
1596 pub visualizers: HashMap<u64, JointVisualizer>,
1597 pub selected_joint: Option<u64>,
1598 pub next_id: u64,
1599 pub gizmo_mode: JointGizmoMode,
1600 pub snap_angle_deg: f32,
1601 pub snap_position: f32,
1602}
1603
1604#[derive(Debug, Clone, PartialEq)]
1605pub enum JointGizmoMode {
1606 Translate,
1607 Rotate,
1608 Scale,
1609}
1610
1611impl JointEditor {
1612 pub fn new() -> Self {
1613 Self {
1614 visualizers: HashMap::new(),
1615 selected_joint: None,
1616 next_id: 1,
1617 gizmo_mode: JointGizmoMode::Translate,
1618 snap_angle_deg: 5.0,
1619 snap_position: 0.1,
1620 }
1621 }
1622
1623 pub fn add_joint_visualizer(&mut self, pivot: Vec3, axis: Vec3) -> u64 {
1624 let id = self.next_id;
1625 self.next_id += 1;
1626 self.visualizers.insert(id, JointVisualizer::new(id, pivot, axis));
1627 id
1628 }
1629
1630 pub fn set_selected(&mut self, id: u64) {
1631 if let Some(old) = self.selected_joint {
1632 if let Some(v) = self.visualizers.get_mut(&old) {
1633 v.selected = false;
1634 }
1635 }
1636 self.selected_joint = Some(id);
1637 if let Some(v) = self.visualizers.get_mut(&id) {
1638 v.selected = true;
1639 }
1640 }
1641
1642 pub fn move_joint(&mut self, id: u64, delta: Vec3) {
1643 if let Some(v) = self.visualizers.get_mut(&id) {
1644 let snap = self.snap_position;
1645 let snapped = Vec3::new(
1646 snap_value(v.pivot_world.x + delta.x, snap),
1647 snap_value(v.pivot_world.y + delta.y, snap),
1648 snap_value(v.pivot_world.z + delta.z, snap),
1649 );
1650 v.pivot_world = snapped;
1651 }
1652 }
1653
1654 pub fn rotate_axis(&mut self, id: u64, rotation: Quat) {
1655 if let Some(v) = self.visualizers.get_mut(&id) {
1656 v.axis_world = (rotation * v.axis_world).normalize();
1657 }
1658 }
1659
1660 pub fn generate_all_debug_lines(&self) -> Vec<(Vec3, Vec3, Vec4)> {
1661 let mut lines = Vec::new();
1662 for (_, vis) in &self.visualizers {
1663 let (a, b) = vis.axis_line(0.5);
1664 lines.push((a, b, vis.axis_color));
1665 }
1666 lines
1667 }
1668}
1669
1670#[derive(Debug, Clone)]
1675pub struct ClothParticle {
1676 pub position: Vec3,
1677 pub prev_position: Vec3,
1678 pub velocity: Vec3,
1679 pub mass: f32,
1680 pub inv_mass: f32,
1681 pub pinned: bool,
1682 pub normal: Vec3,
1683 pub tex_coord: Vec2,
1684}
1685
1686impl ClothParticle {
1687 pub fn new(pos: Vec3, mass: f32) -> Self {
1688 Self {
1689 position: pos,
1690 prev_position: pos,
1691 velocity: Vec3::ZERO,
1692 mass,
1693 inv_mass: if mass > EPSILON { 1.0 / mass } else { 0.0 },
1694 pinned: false,
1695 normal: Vec3::Y,
1696 tex_coord: Vec2::ZERO,
1697 }
1698 }
1699}
1700
1701#[derive(Debug, Clone)]
1702pub struct ClothSpring {
1703 pub particle_a: usize,
1704 pub particle_b: usize,
1705 pub rest_length: f32,
1706 pub stiffness: f32,
1707 pub damping: f32,
1708 pub spring_type: ClothSpringType,
1709 pub torn: bool,
1710 pub tear_threshold: f32,
1711}
1712
1713#[derive(Debug, Clone, PartialEq)]
1714pub enum ClothSpringType {
1715 Stretch,
1716 Shear,
1717 Bend,
1718}
1719
1720#[derive(Debug, Clone)]
1721pub struct ClothSimulation {
1722 pub particles: Vec<ClothParticle>,
1723 pub springs: Vec<ClothSpring>,
1724 pub grid_width: usize,
1725 pub grid_height: usize,
1726 pub total_mass: f32,
1727 pub stretch_stiffness: f32,
1728 pub shear_stiffness: f32,
1729 pub bend_stiffness: f32,
1730 pub stretch_damping: f32,
1731 pub shear_damping: f32,
1732 pub bend_damping: f32,
1733 pub gravity: Vec3,
1734 pub wind_velocity: Vec3,
1735 pub wind_turbulence: f32,
1736 pub drag_coefficient: f32,
1737 pub thickness: f32,
1738 pub self_collision_enabled: bool,
1739 pub self_collision_radius: f32,
1740 pub tearing_enabled: bool,
1741 pub global_tear_threshold: f32,
1742 pub pinned_vertices: HashSet<usize>,
1743 pub iterations: u32,
1744}
1745
1746impl ClothSimulation {
1747 pub fn new(width: usize, height: usize, cell_size: f32, mass_per_particle: f32) -> Self {
1748 let mut particles = Vec::new();
1749 let mut springs = Vec::new();
1750 for j in 0..height {
1752 for i in 0..width {
1753 let pos = Vec3::new(i as f32 * cell_size, 0.0, j as f32 * cell_size);
1754 let mut p = ClothParticle::new(pos, mass_per_particle);
1755 p.tex_coord = Vec2::new(i as f32 / (width - 1) as f32, j as f32 / (height - 1) as f32);
1756 particles.push(p);
1757 }
1758 }
1759 let idx = |i: usize, j: usize| j * width + i;
1760 for j in 0..height {
1762 for i in 0..width {
1763 if i + 1 < width {
1764 let dist = cell_size;
1765 springs.push(ClothSpring {
1766 particle_a: idx(i, j),
1767 particle_b: idx(i + 1, j),
1768 rest_length: dist,
1769 stiffness: 1000.0,
1770 damping: 0.1,
1771 spring_type: ClothSpringType::Stretch,
1772 torn: false,
1773 tear_threshold: 2.0 * dist,
1774 });
1775 }
1776 if j + 1 < height {
1777 let dist = cell_size;
1778 springs.push(ClothSpring {
1779 particle_a: idx(i, j),
1780 particle_b: idx(i, j + 1),
1781 rest_length: dist,
1782 stiffness: 1000.0,
1783 damping: 0.1,
1784 spring_type: ClothSpringType::Stretch,
1785 torn: false,
1786 tear_threshold: 2.0 * dist,
1787 });
1788 }
1789 }
1790 }
1791 for j in 0..height - 1 {
1793 for i in 0..width - 1 {
1794 let diag = cell_size * 2.0_f32.sqrt();
1795 springs.push(ClothSpring {
1796 particle_a: idx(i, j),
1797 particle_b: idx(i + 1, j + 1),
1798 rest_length: diag,
1799 stiffness: 500.0,
1800 damping: 0.05,
1801 spring_type: ClothSpringType::Shear,
1802 torn: false,
1803 tear_threshold: 3.0 * diag,
1804 });
1805 springs.push(ClothSpring {
1806 particle_a: idx(i + 1, j),
1807 particle_b: idx(i, j + 1),
1808 rest_length: diag,
1809 stiffness: 500.0,
1810 damping: 0.05,
1811 spring_type: ClothSpringType::Shear,
1812 torn: false,
1813 tear_threshold: 3.0 * diag,
1814 });
1815 }
1816 }
1817 for j in 0..height {
1819 for i in 0..width {
1820 if i + 2 < width {
1821 let dist = 2.0 * cell_size;
1822 springs.push(ClothSpring {
1823 particle_a: idx(i, j),
1824 particle_b: idx(i + 2, j),
1825 rest_length: dist,
1826 stiffness: 100.0,
1827 damping: 0.01,
1828 spring_type: ClothSpringType::Bend,
1829 torn: false,
1830 tear_threshold: 4.0 * dist,
1831 });
1832 }
1833 if j + 2 < height {
1834 let dist = 2.0 * cell_size;
1835 springs.push(ClothSpring {
1836 particle_a: idx(i, j),
1837 particle_b: idx(i, j + 2),
1838 rest_length: dist,
1839 stiffness: 100.0,
1840 damping: 0.01,
1841 spring_type: ClothSpringType::Bend,
1842 torn: false,
1843 tear_threshold: 4.0 * dist,
1844 });
1845 }
1846 }
1847 }
1848 let total_mass = mass_per_particle * (width * height) as f32;
1849 Self {
1850 particles, springs,
1851 grid_width: width, grid_height: height,
1852 total_mass,
1853 stretch_stiffness: 1000.0,
1854 shear_stiffness: 500.0,
1855 bend_stiffness: 100.0,
1856 stretch_damping: 0.1,
1857 shear_damping: 0.05,
1858 bend_damping: 0.01,
1859 gravity: Vec3::new(0.0, -GRAVITY, 0.0),
1860 wind_velocity: Vec3::ZERO,
1861 wind_turbulence: 0.1,
1862 drag_coefficient: 0.05,
1863 thickness: 0.001,
1864 self_collision_enabled: true,
1865 self_collision_radius: 0.01,
1866 tearing_enabled: false,
1867 global_tear_threshold: 3.0,
1868 pinned_vertices: HashSet::new(),
1869 iterations: 10,
1870 }
1871 }
1872
1873 pub fn pin_vertex(&mut self, idx: usize) {
1874 if idx < self.particles.len() {
1875 self.particles[idx].pinned = true;
1876 self.particles[idx].inv_mass = 0.0;
1877 self.pinned_vertices.insert(idx);
1878 }
1879 }
1880
1881 pub fn unpin_vertex(&mut self, idx: usize) {
1882 if idx < self.particles.len() {
1883 self.particles[idx].pinned = false;
1884 if self.particles[idx].mass > EPSILON {
1885 self.particles[idx].inv_mass = 1.0 / self.particles[idx].mass;
1886 }
1887 self.pinned_vertices.remove(&idx);
1888 }
1889 }
1890
1891 pub fn wind_force_on_triangle(&self, p0: Vec3, p1: Vec3, p2: Vec3, time: f32) -> Vec3 {
1893 let edge1 = p1 - p0;
1894 let edge2 = p2 - p0;
1895 let normal = edge1.cross(edge2);
1896 let area = normal.length() * 0.5;
1897 if area < EPSILON { return Vec3::ZERO; }
1898 let n = normal / (2.0 * area); let turb = Vec3::new(
1901 (time * 2.3 + p0.x).sin() * self.wind_turbulence,
1902 (time * 1.7 + p0.y).cos() * self.wind_turbulence * 0.5,
1903 (time * 3.1 + p0.z).sin() * self.wind_turbulence,
1904 );
1905 let effective_wind = self.wind_velocity + turb;
1906 let relative_wind = effective_wind; let wind_dot_n = relative_wind.dot(n);
1908 let rho_half = AIR_DENSITY * 0.5;
1910 let force = n * (rho_half * wind_dot_n * wind_dot_n.abs() * area);
1911 force
1912 }
1913
1914 pub fn integrate(&mut self, dt: f32, time: f32) {
1916 let dt2 = dt * dt;
1917 for p in &mut self.particles {
1918 if p.pinned { continue; }
1919 let old_pos = p.position;
1921 let acc = self.gravity;
1923 let drag = -p.velocity * self.drag_coefficient;
1925 let total_acc = acc + drag * p.inv_mass;
1926 let new_pos = p.position * 2.0 - p.prev_position + total_acc * dt2;
1928 p.prev_position = old_pos;
1929 p.position = new_pos;
1930 p.velocity = (p.position - p.prev_position) / dt;
1931 }
1932 }
1933
1934 pub fn solve_springs(&mut self, dt: f32) {
1936 for _ in 0..self.iterations {
1937 for spring_idx in 0..self.springs.len() {
1938 if self.springs[spring_idx].torn { continue; }
1939 let a = self.springs[spring_idx].particle_a;
1940 let b = self.springs[spring_idx].particle_b;
1941 let rest = self.springs[spring_idx].rest_length;
1942 let stiff = self.springs[spring_idx].stiffness;
1943 let damp = self.springs[spring_idx].damping;
1944 let tear = self.springs[spring_idx].tear_threshold;
1945 let pa = self.particles[a].position;
1946 let pb = self.particles[b].position;
1947 let ia = self.particles[a].inv_mass;
1948 let ib = self.particles[b].inv_mass;
1949 let sum_inv = ia + ib;
1950 if sum_inv < EPSILON { continue; }
1951 let delta = pb - pa;
1952 let dist = delta.length();
1953 if dist < EPSILON { continue; }
1954 let stretch = (dist - rest) / dist;
1955 if self.tearing_enabled && dist > tear {
1957 self.springs[spring_idx].torn = true;
1958 continue;
1959 }
1960 let correction = delta * stretch;
1961 let w_a = ia / sum_inv;
1962 let w_b = ib / sum_inv;
1963 self.particles[a].position += correction * w_a;
1964 self.particles[b].position -= correction * w_b;
1965 }
1966 }
1967 }
1968
1969 pub fn compute_normals(&mut self) {
1971 for p in &mut self.particles {
1972 p.normal = Vec3::ZERO;
1973 }
1974 let w = self.grid_width;
1975 let h = self.grid_height;
1976 let idx = |i: usize, j: usize| j * w + i;
1977 for j in 0..h - 1 {
1978 for i in 0..w - 1 {
1979 let i00 = idx(i, j);
1980 let i10 = idx(i + 1, j);
1981 let i01 = idx(i, j + 1);
1982 let i11 = idx(i + 1, j + 1);
1983 let p00 = self.particles[i00].position;
1984 let p10 = self.particles[i10].position;
1985 let p01 = self.particles[i01].position;
1986 let p11 = self.particles[i11].position;
1987 let n1 = (p10 - p00).cross(p01 - p00);
1989 self.particles[i00].normal += n1;
1990 self.particles[i10].normal += n1;
1991 self.particles[i01].normal += n1;
1992 let n2 = (p11 - p10).cross(p01 - p10);
1994 self.particles[i10].normal += n2;
1995 self.particles[i11].normal += n2;
1996 self.particles[i01].normal += n2;
1997 }
1998 }
1999 for p in &mut self.particles {
2000 let l = p.normal.length();
2001 if l > EPSILON { p.normal /= l; }
2002 }
2003 }
2004}
2005
2006pub const SPH_POLY6_COEFF: f32 = 315.0 / (64.0 * PI);
2011pub const SPH_SPIKY_COEFF: f32 = 15.0 / PI;
2012pub const SPH_VISC_COEFF: f32 = 15.0 / (2.0 * PI);
2013
2014pub fn sph_kernel_poly6(r_sq: f32, h: f32) -> f32 {
2016 let h2 = h * h;
2017 if r_sq > h2 { return 0.0; }
2018 let diff = h2 - r_sq;
2019 SPH_POLY6_COEFF / h.powi(9) * diff * diff * diff
2020}
2021
2022pub fn sph_kernel_poly6_grad(r: Vec3, r_sq: f32, h: f32) -> Vec3 {
2024 let h2 = h * h;
2025 if r_sq > h2 { return Vec3::ZERO; }
2026 let diff = h2 - r_sq;
2027 let coeff = -6.0 * SPH_POLY6_COEFF / h.powi(9) * diff * diff;
2028 r * coeff
2029}
2030
2031pub fn sph_kernel_poly6_lap(r_sq: f32, h: f32) -> f32 {
2033 let h2 = h * h;
2034 if r_sq > h2 { return 0.0; }
2035 let diff = h2 - r_sq;
2036 -6.0 * SPH_POLY6_COEFF / h.powi(9) * diff * (3.0 * h2 - 7.0 * r_sq)
2037}
2038
2039pub fn sph_kernel_spiky_grad(r: Vec3, r_len: f32, h: f32) -> Vec3 {
2041 if r_len > h || r_len < EPSILON { return Vec3::ZERO; }
2042 let diff = h - r_len;
2043 let coeff = -3.0 * SPH_SPIKY_COEFF / h.powi(6) * diff * diff / r_len;
2044 r * coeff
2045}
2046
2047pub fn sph_kernel_viscosity_lap(r_len: f32, h: f32) -> f32 {
2049 if r_len > h { return 0.0; }
2050 (45.0 / (PI * h.powi(6))) * (h - r_len)
2051}
2052
2053#[derive(Debug, Clone)]
2054pub struct SphParticle {
2055 pub position: Vec3,
2056 pub velocity: Vec3,
2057 pub force: Vec3,
2058 pub density: f32,
2059 pub pressure: f32,
2060 pub mass: f32,
2061}
2062
2063impl SphParticle {
2064 pub fn new(pos: Vec3, mass: f32) -> Self {
2065 Self {
2066 position: pos,
2067 velocity: Vec3::ZERO,
2068 force: Vec3::ZERO,
2069 density: 0.0,
2070 pressure: 0.0,
2071 mass,
2072 }
2073 }
2074}
2075
2076#[derive(Debug, Clone)]
2077pub struct FluidSimulation {
2078 pub particles: Vec<SphParticle>,
2079 pub kernel_radius: f32,
2080 pub rest_density: f32,
2081 pub viscosity: f32,
2082 pub pressure_stiffness: f32,
2083 pub surface_tension: f32,
2084 pub gravity: Vec3,
2085 pub restitution: f32,
2086 pub particle_radius: f32,
2087 pub neighbor_grid: HashMap<(i32, i32, i32), Vec<usize>>,
2088 pub domain_min: Vec3,
2089 pub domain_max: Vec3,
2090 pub time_scale: f32,
2091 pub max_velocity: f32,
2092 pub color_field_threshold: f32,
2093}
2094
2095impl FluidSimulation {
2096 pub fn new(kernel_radius: f32, rest_density: f32) -> Self {
2097 Self {
2098 particles: Vec::new(),
2099 kernel_radius,
2100 rest_density,
2101 viscosity: 0.1,
2102 pressure_stiffness: 200.0,
2103 surface_tension: 0.0728,
2104 gravity: Vec3::new(0.0, -GRAVITY, 0.0),
2105 restitution: 0.0,
2106 particle_radius: kernel_radius * 0.5,
2107 neighbor_grid: HashMap::new(),
2108 domain_min: Vec3::splat(-10.0),
2109 domain_max: Vec3::splat(10.0),
2110 time_scale: 1.0,
2111 max_velocity: 50.0,
2112 color_field_threshold: 0.6,
2113 }
2114 }
2115
2116 pub fn spawn_block(&mut self, min: Vec3, max: Vec3, spacing: f32, mass: f32) {
2117 let h = spacing;
2118 let mut x = min.x;
2119 while x <= max.x {
2120 let mut y = min.y;
2121 while y <= max.y {
2122 let mut z = min.z;
2123 while z <= max.z {
2124 self.particles.push(SphParticle::new(Vec3::new(x, y, z), mass));
2125 z += h;
2126 }
2127 y += h;
2128 }
2129 x += h;
2130 }
2131 }
2132
2133 fn cell_key(&self, pos: Vec3) -> (i32, i32, i32) {
2134 let h = self.kernel_radius;
2135 (
2136 (pos.x / h).floor() as i32,
2137 (pos.y / h).floor() as i32,
2138 (pos.z / h).floor() as i32,
2139 )
2140 }
2141
2142 pub fn build_neighbor_grid(&mut self) {
2143 self.neighbor_grid.clear();
2144 for (i, p) in self.particles.iter().enumerate() {
2145 let key = self.cell_key(p.position);
2146 self.neighbor_grid.entry(key).or_default().push(i);
2147 }
2148 }
2149
2150 pub fn get_neighbors(&self, pos: Vec3) -> Vec<usize> {
2151 let mut result = Vec::new();
2152 let (cx, cy, cz) = self.cell_key(pos);
2153 for dx in -1..=1 {
2154 for dy in -1..=1 {
2155 for dz in -1..=1 {
2156 if let Some(list) = self.neighbor_grid.get(&(cx + dx, cy + dy, cz + dz)) {
2157 result.extend_from_slice(list);
2158 }
2159 }
2160 }
2161 }
2162 result
2163 }
2164
2165 pub fn compute_density(&mut self) {
2167 let h = self.kernel_radius;
2168 let n = self.particles.len();
2169 let positions: Vec<Vec3> = self.particles.iter().map(|p| p.position).collect();
2170 let masses: Vec<f32> = self.particles.iter().map(|p| p.mass).collect();
2171 for i in 0..n {
2172 let mut rho = 0.0f32;
2173 let neighbors = self.get_neighbors(positions[i]);
2174 for j in neighbors {
2175 let r = positions[i] - positions[j];
2176 let r_sq = r.length_squared();
2177 rho += masses[j] * sph_kernel_poly6(r_sq, h);
2178 }
2179 self.particles[i].density = rho.max(self.rest_density * 0.001);
2180 }
2181 }
2182
2183 pub fn compute_pressure(&mut self) {
2185 for p in &mut self.particles {
2186 p.pressure = self.pressure_stiffness * ((p.density / self.rest_density) - 1.0).max(0.0);
2187 }
2188 }
2189
2190 pub fn compute_forces(&mut self) {
2192 let h = self.kernel_radius;
2193 let n = self.particles.len();
2194 let positions: Vec<Vec3> = self.particles.iter().map(|p| p.position).collect();
2195 let velocities: Vec<Vec3> = self.particles.iter().map(|p| p.velocity).collect();
2196 let masses: Vec<f32> = self.particles.iter().map(|p| p.mass).collect();
2197 let densities: Vec<f32> = self.particles.iter().map(|p| p.density).collect();
2198 let pressures: Vec<f32> = self.particles.iter().map(|p| p.pressure).collect();
2199 for i in 0..n {
2200 let mut f_pressure = Vec3::ZERO;
2201 let mut f_viscosity = Vec3::ZERO;
2202 let mut f_surface = Vec3::ZERO;
2203 let mut color_grad = Vec3::ZERO;
2204 let mut color_lap = 0.0f32;
2205 let neighbors = self.get_neighbors(positions[i]);
2206 for j in neighbors {
2207 if i == j { continue; }
2208 let r = positions[i] - positions[j];
2209 let r_len = r.length();
2210 let r_sq = r_len * r_len;
2211 if r_sq >= h * h { continue; }
2212 let m_j = masses[j];
2213 let rho_j = densities[j];
2214 if rho_j < EPSILON { continue; }
2215 let p_term = pressures[i] / (densities[i] * densities[i])
2217 + pressures[j] / (rho_j * rho_j);
2218 let grad_spiky = sph_kernel_spiky_grad(r, r_len, h);
2219 f_pressure -= m_j * p_term * grad_spiky;
2220 let lap_visc = sph_kernel_viscosity_lap(r_len, h);
2222 f_viscosity += (velocities[j] - velocities[i]) * (m_j / rho_j * lap_visc);
2223 let grad_poly6 = sph_kernel_poly6_grad(r, r_sq, h);
2225 let lap_poly6 = sph_kernel_poly6_lap(r_sq, h);
2226 color_grad += grad_poly6 * (m_j / rho_j);
2227 color_lap += lap_poly6 * (m_j / rho_j);
2228 }
2229 let rho_i = densities[i];
2230 f_pressure *= rho_i;
2231 f_viscosity *= self.viscosity;
2232 let color_grad_len = color_grad.length();
2233 if color_grad_len > self.color_field_threshold {
2234 f_surface = -self.surface_tension * color_lap / color_grad_len * color_grad;
2235 }
2236 let gravity_force = self.gravity * masses[i];
2237 self.particles[i].force = f_pressure + f_viscosity + f_surface + gravity_force;
2238 }
2239 }
2240
2241 pub fn integrate(&mut self, dt: f32) {
2243 let dt_scaled = dt * self.time_scale;
2244 for p in &mut self.particles {
2245 if p.density < EPSILON { continue; }
2246 let acc = p.force / p.density;
2247 p.velocity += acc * dt_scaled;
2248 let v_len = p.velocity.length();
2250 if v_len > self.max_velocity {
2251 p.velocity *= self.max_velocity / v_len;
2252 }
2253 p.position += p.velocity * dt_scaled;
2254 let r = self.particle_radius;
2256 for dim in 0..3 {
2257 let lo = match dim { 0 => self.domain_min.x, 1 => self.domain_min.y, _ => self.domain_min.z };
2258 let hi = match dim { 0 => self.domain_max.x, 1 => self.domain_max.y, _ => self.domain_max.z };
2259 let pos_val = match dim { 0 => &mut p.position.x, 1 => &mut p.position.y, _ => &mut p.position.z };
2260 let vel_val = match dim { 0 => &mut p.velocity.x, 1 => &mut p.velocity.y, _ => &mut p.velocity.z };
2261 if *pos_val < lo + r {
2262 *pos_val = lo + r;
2263 *vel_val = vel_val.abs() * self.restitution;
2264 } else if *pos_val > hi - r {
2265 *pos_val = hi - r;
2266 *vel_val = -vel_val.abs() * self.restitution;
2267 }
2268 }
2269 }
2270 }
2271
2272 pub fn step(&mut self, dt: f32) {
2274 self.build_neighbor_grid();
2275 self.compute_density();
2276 self.compute_pressure();
2277 self.compute_forces();
2278 self.integrate(dt);
2279 }
2280}
2281
2282#[derive(Debug, Clone)]
2287pub struct VoronoiCell {
2288 pub seed: Vec3,
2289 pub vertices: Vec<Vec3>,
2290 pub mass: f32,
2291 pub volume: f32,
2292 pub intact: bool,
2293 pub stress: f32,
2294 pub velocity: Vec3,
2295 pub angular_velocity: Vec3,
2296}
2297
2298impl VoronoiCell {
2299 pub fn new(seed: Vec3) -> Self {
2300 Self {
2301 seed,
2302 vertices: Vec::new(),
2303 mass: 0.0,
2304 volume: 0.0,
2305 intact: true,
2306 stress: 0.0,
2307 velocity: Vec3::ZERO,
2308 angular_velocity: Vec3::ZERO,
2309 }
2310 }
2311
2312 pub fn compute_volume_from_vertices(&mut self) {
2314 if self.vertices.len() < 4 { self.volume = 0.0; return; }
2315 let centroid = self.vertices.iter().fold(Vec3::ZERO, |a, &v| a + v) / self.vertices.len() as f32;
2317 let mut vol = 0.0f32;
2318 let n = self.vertices.len();
2319 for i in 0..n {
2320 let a = self.vertices[i];
2321 let b = self.vertices[(i + 1) % n];
2322 let c = centroid;
2323 let d = Vec3::new(centroid.x, centroid.y + 0.01, centroid.z);
2324 let tet_vol = (b - a).cross(c - a).dot(d - a).abs() / 6.0;
2325 vol += tet_vol;
2326 }
2327 self.volume = vol;
2328 }
2329}
2330
2331#[derive(Debug, Clone)]
2332pub struct VoronoiFracture {
2333 pub cells: Vec<VoronoiCell>,
2334 pub bounds_min: Vec3,
2335 pub bounds_max: Vec3,
2336 pub num_cells: usize,
2337 pub material_strength: f32, pub toughness: f32, pub density: f32,
2340 pub cracks: Vec<(usize, usize)>, pub debris_particles: Vec<DebrisParticle>,
2342 pub fractured: bool,
2343}
2344
2345#[derive(Debug, Clone)]
2346pub struct DebrisParticle {
2347 pub position: Vec3,
2348 pub velocity: Vec3,
2349 pub angular_velocity: Vec3,
2350 pub mass: f32,
2351 pub lifetime: f32,
2352 pub scale: f32,
2353}
2354
2355pub struct FractureRng {
2357 state: u64,
2358}
2359impl FractureRng {
2360 pub fn new(seed: u64) -> Self { Self { state: seed } }
2361 pub fn next_f32(&mut self) -> f32 {
2362 self.state = self.state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
2363 let bits = ((self.state >> 33) as u32) | 0x3F80_0000;
2364 f32::from_bits(bits) - 1.0
2365 }
2366 pub fn next_range(&mut self, lo: f32, hi: f32) -> f32 {
2367 lo + self.next_f32() * (hi - lo)
2368 }
2369}
2370
2371impl VoronoiFracture {
2372 pub fn new(bounds_min: Vec3, bounds_max: Vec3, num_cells: usize, seed: u64) -> Self {
2373 let mut rng = FractureRng::new(seed);
2374 let mut cells = Vec::new();
2375 for _ in 0..num_cells {
2376 let seed_pt = Vec3::new(
2377 rng.next_range(bounds_min.x, bounds_max.x),
2378 rng.next_range(bounds_min.y, bounds_max.y),
2379 rng.next_range(bounds_min.z, bounds_max.z),
2380 );
2381 cells.push(VoronoiCell::new(seed_pt));
2382 }
2383 Self {
2384 cells, bounds_min, bounds_max, num_cells,
2385 material_strength: 1e7,
2386 toughness: 100.0,
2387 density: CONCRETE_DENSITY,
2388 cracks: Vec::new(),
2389 debris_particles: Vec::new(),
2390 fractured: false,
2391 }
2392 }
2393
2394 pub fn find_cell(&self, point: Vec3) -> usize {
2396 let mut best = 0;
2397 let mut best_dist = f32::INFINITY;
2398 for (i, cell) in self.cells.iter().enumerate() {
2399 let d = (point - cell.seed).length_squared();
2400 if d < best_dist {
2401 best_dist = d;
2402 best = i;
2403 }
2404 }
2405 best
2406 }
2407
2408 pub fn accumulate_stress(&mut self, cell_idx: usize, force_magnitude: f32, contact_area: f32) {
2410 if cell_idx >= self.cells.len() { return; }
2411 let stress_increment = if contact_area > EPSILON { force_magnitude / contact_area } else { 0.0 };
2412 self.cells[cell_idx].stress += stress_increment;
2413 }
2414
2415 pub fn check_fracture(&mut self) -> Vec<usize> {
2417 let mut fractured_cells = Vec::new();
2418 for (i, cell) in self.cells.iter_mut().enumerate() {
2419 if cell.intact && cell.stress > self.material_strength {
2420 cell.intact = false;
2421 fractured_cells.push(i);
2422 }
2423 }
2424 fractured_cells
2425 }
2426
2427 pub fn propagate_cracks(&mut self, initial_cells: &[usize]) {
2429 let mut queue: VecDeque<usize> = initial_cells.iter().copied().collect();
2430 let mut visited: HashSet<usize> = initial_cells.iter().copied().collect();
2431 while let Some(cell_idx) = queue.pop_front() {
2432 let seed = self.cells[cell_idx].seed;
2434 let avg_spacing = (self.bounds_max - self.bounds_min).length() / (self.num_cells as f32).cbrt();
2435 let crack_radius = avg_spacing * 1.5;
2436 let stress_here = self.cells[cell_idx].stress;
2437 for j in 0..self.cells.len() {
2438 if visited.contains(&j) { continue; }
2439 let dist = (self.cells[j].seed - seed).length();
2440 if dist < crack_radius {
2441 let transfer = stress_here * (1.0 - dist / crack_radius) * 0.5;
2443 self.cells[j].stress += transfer;
2444 self.cracks.push((cell_idx, j));
2445 if self.cells[j].stress > self.material_strength {
2446 self.cells[j].intact = false;
2447 visited.insert(j);
2448 queue.push_back(j);
2449 }
2450 }
2451 }
2452 }
2453 self.fractured = true;
2454 }
2455
2456 pub fn spawn_debris(&mut self, impact_velocity: Vec3, rng: &mut FractureRng) {
2458 for cell in &self.cells {
2459 if !cell.intact {
2460 let vel = impact_velocity + Vec3::new(
2461 rng.next_range(-2.0, 2.0),
2462 rng.next_range(1.0, 5.0),
2463 rng.next_range(-2.0, 2.0),
2464 );
2465 let ang_vel = Vec3::new(
2466 rng.next_range(-10.0, 10.0),
2467 rng.next_range(-10.0, 10.0),
2468 rng.next_range(-10.0, 10.0),
2469 );
2470 self.debris_particles.push(DebrisParticle {
2471 position: cell.seed,
2472 velocity: vel,
2473 angular_velocity: ang_vel,
2474 mass: cell.mass.max(0.001),
2475 lifetime: rng.next_range(2.0, 8.0),
2476 scale: rng.next_range(0.05, 0.3),
2477 });
2478 }
2479 }
2480 }
2481
2482 pub fn integrate_debris(&mut self, dt: f32) {
2484 let gravity = Vec3::new(0.0, -GRAVITY, 0.0);
2485 self.debris_particles.retain_mut(|d| {
2486 d.velocity += gravity * dt;
2487 d.position += d.velocity * dt;
2488 if d.position.y < 0.0 {
2490 d.position.y = 0.0;
2491 d.velocity.y = d.velocity.y.abs() * 0.4;
2492 d.velocity.x *= 0.8;
2493 d.velocity.z *= 0.8;
2494 }
2495 d.lifetime -= dt;
2496 d.lifetime > 0.0
2497 });
2498 }
2499
2500 pub fn are_connected(&self, a: usize, b: usize) -> bool {
2502 !self.cracks.contains(&(a, b)) && !self.cracks.contains(&(b, a))
2503 }
2504}
2505
2506#[derive(Debug, Clone)]
2511pub struct RagdollBone {
2512 pub name: String,
2513 pub body_id: u64,
2514 pub bone_index: u32,
2515 pub local_offset: Vec3,
2516 pub local_rotation: Quat,
2517 pub mass: f32,
2518 pub shape_type: RigidBodyShapeType,
2519 pub shape_params: ShapeParameters,
2520 pub muscle_tone: f32, pub blend_weight: f32, }
2523
2524#[derive(Debug, Clone)]
2525pub struct RagdollJoint {
2526 pub parent_bone: String,
2527 pub child_bone: String,
2528 pub constraint_id: u64,
2529 pub constraint_type: String,
2530 pub swing_limit: f32,
2531 pub twist_limit: f32,
2532 pub stiffness: f32,
2533 pub damping: f32,
2534}
2535
2536#[derive(Debug, Clone)]
2537pub struct RagdollEditor {
2538 pub bones: HashMap<String, RagdollBone>,
2539 pub joints: Vec<RagdollJoint>,
2540 pub blend_mode: RagdollBlendMode,
2541 pub animation_blend: f32, pub blend_time: f32,
2543 pub current_blend_timer: f32,
2544 pub active: bool,
2545 pub total_mass: f32,
2546 pub com_world: Vec3,
2547 pub next_body_id: u64,
2548}
2549
2550#[derive(Debug, Clone, PartialEq)]
2551pub enum RagdollBlendMode {
2552 FullRagdoll,
2553 FullAnimation,
2554 BlendedPhysics,
2555 KinematicDriven,
2556}
2557
2558impl RagdollEditor {
2559 pub fn new() -> Self {
2560 Self {
2561 bones: HashMap::new(),
2562 joints: Vec::new(),
2563 blend_mode: RagdollBlendMode::FullRagdoll,
2564 animation_blend: 0.0,
2565 blend_time: 0.3,
2566 current_blend_timer: 0.0,
2567 active: false,
2568 total_mass: 0.0,
2569 com_world: Vec3::ZERO,
2570 next_body_id: 1000,
2571 }
2572 }
2573
2574 pub fn build_humanoid_skeleton(&mut self) {
2575 self.add_bone("pelvis", 0, Vec3::ZERO, Quat::IDENTITY, 10.0, RigidBodyShapeType::Box,
2577 ShapeParameters { half_extents: Vec3::new(0.15, 0.1, 0.08), ..Default::default() });
2578 self.add_bone("spine", 1, Vec3::new(0.0, 0.2, 0.0), Quat::IDENTITY, 8.0, RigidBodyShapeType::Capsule,
2579 ShapeParameters { radius: 0.08, half_height: 0.1, ..Default::default() });
2580 self.add_bone("chest", 2, Vec3::new(0.0, 0.4, 0.0), Quat::IDENTITY, 12.0, RigidBodyShapeType::Box,
2581 ShapeParameters { half_extents: Vec3::new(0.18, 0.12, 0.09), ..Default::default() });
2582 self.add_bone("neck", 3, Vec3::new(0.0, 0.55, 0.0), Quat::IDENTITY, 1.5, RigidBodyShapeType::Capsule,
2583 ShapeParameters { radius: 0.04, half_height: 0.04, ..Default::default() });
2584 self.add_bone("head", 4, Vec3::new(0.0, 0.65, 0.0), Quat::IDENTITY, 5.0, RigidBodyShapeType::Sphere,
2585 ShapeParameters { radius: 0.12, ..Default::default() });
2586 self.add_bone("l_shoulder", 5, Vec3::new(0.22, 0.5, 0.0), Quat::IDENTITY, 2.0, RigidBodyShapeType::Sphere,
2588 ShapeParameters { radius: 0.04, ..Default::default() });
2589 self.add_bone("l_upper_arm", 6, Vec3::new(0.3, 0.45, 0.0), Quat::IDENTITY, 2.5, RigidBodyShapeType::Capsule,
2590 ShapeParameters { radius: 0.04, half_height: 0.15, ..Default::default() });
2591 self.add_bone("l_forearm", 7, Vec3::new(0.45, 0.3, 0.0), Quat::IDENTITY, 1.5, RigidBodyShapeType::Capsule,
2592 ShapeParameters { radius: 0.03, half_height: 0.12, ..Default::default() });
2593 self.add_bone("l_hand", 8, Vec3::new(0.55, 0.18, 0.0), Quat::IDENTITY, 0.5, RigidBodyShapeType::Box,
2594 ShapeParameters { half_extents: Vec3::new(0.04, 0.03, 0.06), ..Default::default() });
2595 self.add_bone("r_shoulder", 9, Vec3::new(-0.22, 0.5, 0.0), Quat::IDENTITY, 2.0, RigidBodyShapeType::Sphere,
2597 ShapeParameters { radius: 0.04, ..Default::default() });
2598 self.add_bone("r_upper_arm", 10, Vec3::new(-0.3, 0.45, 0.0), Quat::IDENTITY, 2.5, RigidBodyShapeType::Capsule,
2599 ShapeParameters { radius: 0.04, half_height: 0.15, ..Default::default() });
2600 self.add_bone("r_forearm", 11, Vec3::new(-0.45, 0.3, 0.0), Quat::IDENTITY, 1.5, RigidBodyShapeType::Capsule,
2601 ShapeParameters { radius: 0.03, half_height: 0.12, ..Default::default() });
2602 self.add_bone("r_hand", 12, Vec3::new(-0.55, 0.18, 0.0), Quat::IDENTITY, 0.5, RigidBodyShapeType::Box,
2603 ShapeParameters { half_extents: Vec3::new(0.04, 0.03, 0.06), ..Default::default() });
2604 self.add_bone("l_thigh", 13, Vec3::new(0.1, -0.1, 0.0), Quat::IDENTITY, 7.0, RigidBodyShapeType::Capsule,
2606 ShapeParameters { radius: 0.06, half_height: 0.22, ..Default::default() });
2607 self.add_bone("l_shin", 14, Vec3::new(0.1, -0.5, 0.0), Quat::IDENTITY, 4.0, RigidBodyShapeType::Capsule,
2608 ShapeParameters { radius: 0.04, half_height: 0.2, ..Default::default() });
2609 self.add_bone("l_foot", 15, Vec3::new(0.1, -0.85, 0.04), Quat::IDENTITY, 1.0, RigidBodyShapeType::Box,
2610 ShapeParameters { half_extents: Vec3::new(0.04, 0.03, 0.1), ..Default::default() });
2611 self.add_bone("r_thigh", 16, Vec3::new(-0.1, -0.1, 0.0), Quat::IDENTITY, 7.0, RigidBodyShapeType::Capsule,
2613 ShapeParameters { radius: 0.06, half_height: 0.22, ..Default::default() });
2614 self.add_bone("r_shin", 17, Vec3::new(-0.1, -0.5, 0.0), Quat::IDENTITY, 4.0, RigidBodyShapeType::Capsule,
2615 ShapeParameters { radius: 0.04, half_height: 0.2, ..Default::default() });
2616 self.add_bone("r_foot", 18, Vec3::new(-0.1, -0.85, 0.04), Quat::IDENTITY, 1.0, RigidBodyShapeType::Box,
2617 ShapeParameters { half_extents: Vec3::new(0.04, 0.03, 0.1), ..Default::default() });
2618 self.build_spine_chain();
2620 self.build_arm_chain("l");
2621 self.build_arm_chain("r");
2622 self.build_leg_chain("l");
2623 self.build_leg_chain("r");
2624 self.compute_total_mass();
2625 }
2626
2627 fn add_bone(&mut self, name: &str, bone_index: u32, offset: Vec3, rot: Quat,
2628 mass: f32, shape: RigidBodyShapeType, params: ShapeParameters) {
2629 let id = self.next_body_id;
2630 self.next_body_id += 1;
2631 self.bones.insert(name.to_string(), RagdollBone {
2632 name: name.to_string(),
2633 body_id: id,
2634 bone_index,
2635 local_offset: offset,
2636 local_rotation: rot,
2637 mass,
2638 shape_type: shape,
2639 shape_params: params,
2640 muscle_tone: 0.0,
2641 blend_weight: 1.0,
2642 });
2643 }
2644
2645 fn build_spine_chain(&mut self) {
2646 self.add_joint("pelvis", "spine", 45.0_f32.to_radians(), 30.0_f32.to_radians(), 100.0, 10.0);
2647 self.add_joint("spine", "chest", 30.0_f32.to_radians(), 20.0_f32.to_radians(), 150.0, 15.0);
2648 self.add_joint("chest", "neck", 40.0_f32.to_radians(), 30.0_f32.to_radians(), 80.0, 8.0);
2649 self.add_joint("neck", "head", 50.0_f32.to_radians(), 60.0_f32.to_radians(), 60.0, 6.0);
2650 }
2651
2652 fn build_arm_chain(&mut self, side: &str) {
2653 let s = side;
2654 let shoulder = format!("{}_shoulder", s);
2655 let upper_arm = format!("{}_upper_arm", s);
2656 let forearm = format!("{}_forearm", s);
2657 let hand = format!("{}_hand", s);
2658 let chest_key = "chest";
2659 self.add_joint(chest_key, &shoulder, 60.0_f32.to_radians(), 45.0_f32.to_radians(), 120.0, 12.0);
2660 self.add_joint(&shoulder, &upper_arm, 90.0_f32.to_radians(), 60.0_f32.to_radians(), 100.0, 10.0);
2661 self.add_joint(&upper_arm, &forearm, 120.0_f32.to_radians(), 5.0_f32.to_radians(), 80.0, 8.0);
2662 self.add_joint(&forearm, &hand, 70.0_f32.to_radians(), 30.0_f32.to_radians(), 40.0, 4.0);
2663 }
2664
2665 fn build_leg_chain(&mut self, side: &str) {
2666 let s = side;
2667 let thigh = format!("{}_thigh", s);
2668 let shin = format!("{}_shin", s);
2669 let foot = format!("{}_foot", s);
2670 self.add_joint("pelvis", &thigh, 80.0_f32.to_radians(), 45.0_f32.to_radians(), 200.0, 20.0);
2671 self.add_joint(&thigh, &shin, 130.0_f32.to_radians(), 5.0_f32.to_radians(), 160.0, 16.0);
2672 self.add_joint(&shin, &foot, 50.0_f32.to_radians(), 20.0_f32.to_radians(), 60.0, 6.0);
2673 }
2674
2675 fn add_joint(&mut self, parent: &str, child: &str, swing: f32, twist: f32, stiffness: f32, damping: f32) {
2676 let id = self.joints.len() as u64 + 2000;
2677 self.joints.push(RagdollJoint {
2678 parent_bone: parent.to_string(),
2679 child_bone: child.to_string(),
2680 constraint_id: id,
2681 constraint_type: "ConeTwist".to_string(),
2682 swing_limit: swing,
2683 twist_limit: twist,
2684 stiffness,
2685 damping,
2686 });
2687 }
2688
2689 pub fn compute_total_mass(&mut self) {
2690 self.total_mass = self.bones.values().map(|b| b.mass).sum();
2691 }
2692
2693 pub fn compute_blended_transforms(&self,
2696 physics_poses: &HashMap<String, (Vec3, Quat)>,
2697 animation_poses: &HashMap<String, (Vec3, Quat)>,
2698 ) -> HashMap<String, (Vec3, Quat)> {
2699 let mut result = HashMap::new();
2700 let blend = self.animation_blend;
2701 for (name, bone) in &self.bones {
2702 let phys = physics_poses.get(name).copied().unwrap_or((Vec3::ZERO, Quat::IDENTITY));
2703 let anim = animation_poses.get(name).copied().unwrap_or((Vec3::ZERO, Quat::IDENTITY));
2704 let w = blend * bone.blend_weight;
2705 let pos = phys.0.lerp(anim.0, w);
2706 let rot = phys.1.slerp(anim.1, w);
2707 result.insert(name.clone(), (pos, rot));
2708 }
2709 result
2710 }
2711
2712 pub fn muscle_torque(&self, joint: &RagdollJoint, bone_name: &str, current_angle: f32, target_angle: f32) -> f32 {
2714 let bone = match self.bones.get(bone_name) {
2715 Some(b) => b,
2716 None => return 0.0,
2717 };
2718 let tone = bone.muscle_tone;
2719 let error = target_angle - current_angle;
2720 tone * joint.stiffness * error
2721 }
2722
2723 pub fn set_blend_mode(&mut self, mode: RagdollBlendMode, blend_time: f32) {
2724 self.blend_mode = mode.clone();
2725 self.blend_time = blend_time;
2726 self.current_blend_timer = 0.0;
2727 match mode {
2728 RagdollBlendMode::FullRagdoll => self.animation_blend = 0.0,
2729 RagdollBlendMode::FullAnimation => self.animation_blend = 1.0,
2730 _ => {}
2731 }
2732 }
2733
2734 pub fn update_blend(&mut self, dt: f32) {
2735 self.current_blend_timer = (self.current_blend_timer + dt).min(self.blend_time);
2736 let t = if self.blend_time > EPSILON { self.current_blend_timer / self.blend_time } else { 1.0 };
2737 match self.blend_mode {
2738 RagdollBlendMode::BlendedPhysics => {
2739 self.animation_blend = smoothstep(0.0, 1.0, t) * 0.5;
2740 }
2741 RagdollBlendMode::FullAnimation => {
2742 self.animation_blend = smoothstep(0.0, 1.0, t);
2743 }
2744 RagdollBlendMode::FullRagdoll => {
2745 self.animation_blend = 1.0 - smoothstep(0.0, 1.0, t);
2746 }
2747 _ => {}
2748 }
2749 }
2750}
2751
2752pub fn pacejka_magic_formula(slip_angle_rad: f32, b: f32, c: f32, d: f32, e: f32) -> f32 {
2759 let ba = b * slip_angle_rad;
2760 let inner = ba - e * (ba - ba.atan());
2761 d * (c * inner.atan()).sin()
2762}
2763
2764pub fn pacejka_dry_asphalt() -> (f32, f32, f32, f32) {
2766 (10.0, 1.9, 1.0, 0.97)
2768}
2769
2770pub fn pacejka_wet_road() -> (f32, f32, f32, f32) {
2772 (7.0, 1.7, 0.7, 0.9)
2773}
2774
2775pub fn pacejka_ice() -> (f32, f32, f32, f32) {
2777 (4.0, 1.5, 0.2, 0.8)
2778}
2779
2780pub fn ackermann_steering(wheelbase: f32, track_width: f32, steer_angle: f32) -> (f32, f32) {
2783 let l = wheelbase;
2786 let w = track_width;
2787 let delta = steer_angle;
2788 if delta.abs() < EPSILON {
2789 return (0.0, 0.0);
2790 }
2791 let r_outer = l / delta.tan();
2792 let r_inner = r_outer - w;
2793 let inner_angle = (l / r_inner).atan();
2794 (delta, inner_angle)
2795}
2796
2797#[derive(Debug, Clone)]
2798pub struct WheelState {
2799 pub position_local: Vec3, pub radius: f32,
2801 pub width: f32,
2802 pub suspension_rest_length: f32,
2803 pub suspension_max_travel: f32,
2804 pub suspension_stiffness: f32,
2805 pub suspension_damping: f32,
2806 pub current_compression: f32,
2807 pub suspension_force: f32,
2808 pub contact_point: Vec3,
2809 pub contact_normal: Vec3,
2810 pub is_grounded: bool,
2811 pub slip_angle: f32,
2812 pub slip_ratio: f32,
2813 pub lateral_force: f32,
2814 pub longitudinal_force: f32,
2815 pub steer_angle: f32,
2816 pub angular_velocity: f32, pub brake_torque: f32,
2818 pub drive_torque: f32,
2819 pub friction_limit: f32,
2820 pub rolling_resistance: f32,
2821 pub camber_angle: f32,
2822 pub road_surface: SurfaceType,
2823}
2824
2825impl WheelState {
2826 pub fn new(position_local: Vec3, radius: f32) -> Self {
2827 Self {
2828 position_local,
2829 radius,
2830 width: 0.2,
2831 suspension_rest_length: 0.3,
2832 suspension_max_travel: 0.15,
2833 suspension_stiffness: 25000.0,
2834 suspension_damping: 2000.0,
2835 current_compression: 0.0,
2836 suspension_force: 0.0,
2837 contact_point: Vec3::ZERO,
2838 contact_normal: Vec3::Y,
2839 is_grounded: false,
2840 slip_angle: 0.0,
2841 slip_ratio: 0.0,
2842 lateral_force: 0.0,
2843 longitudinal_force: 0.0,
2844 steer_angle: 0.0,
2845 angular_velocity: 0.0,
2846 brake_torque: 0.0,
2847 drive_torque: 0.0,
2848 friction_limit: 1.0,
2849 rolling_resistance: 0.015,
2850 camber_angle: 0.0,
2851 road_surface: SurfaceType::Asphalt,
2852 }
2853 }
2854
2855 pub fn compute_suspension_force(&mut self, compression_velocity: f32) -> f32 {
2858 let spring_force = self.suspension_stiffness * self.current_compression;
2859 let damper_force = self.suspension_damping * compression_velocity;
2860 let force = (spring_force + damper_force).max(0.0);
2861 self.suspension_force = force;
2862 force
2863 }
2864
2865 pub fn compute_tire_forces(&mut self, normal_force: f32, surface_friction: f32) {
2867 if !self.is_grounded || normal_force < EPSILON {
2868 self.lateral_force = 0.0;
2869 self.longitudinal_force = 0.0;
2870 return;
2871 }
2872 let (b, c, d, e) = match self.road_surface {
2873 SurfaceType::Asphalt => pacejka_dry_asphalt(),
2874 SurfaceType::WetAsphalt | SurfaceType::Mud => pacejka_wet_road(),
2875 SurfaceType::Ice | SurfaceType::Snow => pacejka_ice(),
2876 _ => pacejka_dry_asphalt(),
2877 };
2878 let d_scaled = d * normal_force * surface_friction;
2880 self.lateral_force = pacejka_magic_formula(self.slip_angle, b, c, d_scaled, e);
2882 let long_slip = self.slip_ratio;
2884 self.longitudinal_force = pacejka_magic_formula(long_slip, b * 1.2, c * 1.1, d_scaled, e);
2885 let combined = (self.lateral_force * self.lateral_force + self.longitudinal_force * self.longitudinal_force).sqrt();
2887 let limit = d_scaled * self.friction_limit;
2888 if combined > limit && combined > EPSILON {
2889 let scale = limit / combined;
2890 self.lateral_force *= scale;
2891 self.longitudinal_force *= scale;
2892 }
2893 }
2894
2895 pub fn compute_slip_angle(&mut self, wheel_vel_local: Vec3) {
2897 let vx = wheel_vel_local.x;
2898 let vy = wheel_vel_local.z; if vx.abs() < 0.5 {
2900 self.slip_angle = 0.0;
2901 return;
2902 }
2903 self.slip_angle = (vy / vx.abs()).atan();
2904 }
2905
2906 pub fn compute_slip_ratio(&mut self, vehicle_speed: f32) {
2908 let wheel_speed = self.angular_velocity * self.radius;
2909 let v_ref = vehicle_speed.abs().max(0.1);
2910 self.slip_ratio = (wheel_speed - vehicle_speed) / v_ref;
2911 self.slip_ratio = self.slip_ratio.clamp(-1.0, 1.0);
2912 }
2913}
2914
2915#[derive(Debug, Clone)]
2916pub struct EngineTorqueCurve {
2917 pub rpm_points: Vec<f32>,
2919 pub torque_points: Vec<f32>,
2921 pub idle_rpm: f32,
2922 pub max_rpm: f32,
2923 pub redline_rpm: f32,
2924 pub current_rpm: f32,
2925 pub inertia: f32,
2926}
2927
2928impl EngineTorqueCurve {
2929 pub fn petrol_sport() -> Self {
2930 let rpm = vec![0.0, 1000.0, 2000.0, 3000.0, 4000.0, 4500.0, 5500.0, 6500.0, 7000.0, 7500.0];
2932 let torque = vec![0.0, 180.0, 250.0, 280.0, 295.0, 300.0, 290.0, 260.0, 220.0, 0.0];
2933 Self {
2934 rpm_points: rpm,
2935 torque_points: torque,
2936 idle_rpm: 800.0,
2937 max_rpm: 7500.0,
2938 redline_rpm: 7000.0,
2939 current_rpm: 800.0,
2940 inertia: 0.15,
2941 }
2942 }
2943
2944 pub fn diesel_truck() -> Self {
2945 let rpm = vec![0.0, 800.0, 1200.0, 1600.0, 2000.0, 2500.0, 3000.0, 3500.0, 4000.0];
2946 let torque = vec![0.0, 400.0, 800.0, 1000.0, 1100.0, 1100.0, 950.0, 700.0, 0.0];
2947 Self {
2948 rpm_points: rpm,
2949 torque_points: torque,
2950 idle_rpm: 600.0,
2951 max_rpm: 4000.0,
2952 redline_rpm: 3500.0,
2953 current_rpm: 600.0,
2954 inertia: 0.5,
2955 }
2956 }
2957
2958 pub fn evaluate(&self, rpm: f32) -> f32 {
2960 if self.rpm_points.is_empty() { return 0.0; }
2961 let rpm = rpm.clamp(0.0, *self.rpm_points.last().unwrap());
2962 for i in 0..self.rpm_points.len() - 1 {
2963 if rpm >= self.rpm_points[i] && rpm <= self.rpm_points[i + 1] {
2964 let t = (rpm - self.rpm_points[i]) / (self.rpm_points[i + 1] - self.rpm_points[i] + EPSILON);
2965 return self.torque_points[i] + t * (self.torque_points[i + 1] - self.torque_points[i]);
2966 }
2967 }
2968 0.0
2969 }
2970
2971 pub fn update_rpm(&mut self, wheel_ang_vel: f32, gear_ratio: f32, final_drive: f32) {
2973 let new_rpm = (wheel_ang_vel * gear_ratio * final_drive * 60.0 / TWO_PI).abs();
2974 self.current_rpm = new_rpm.clamp(self.idle_rpm, self.max_rpm);
2975 }
2976}
2977
2978#[derive(Debug, Clone)]
2979pub struct GearBox {
2980 pub gear_ratios: Vec<f32>, pub current_gear: i32, pub final_drive_ratio: f32,
2983 pub auto_shift: bool,
2984 pub shift_up_rpm: f32,
2985 pub shift_down_rpm: f32,
2986 pub shift_time: f32,
2987 pub shifting_timer: f32,
2988 pub efficiency: f32,
2989}
2990
2991impl GearBox {
2992 pub fn new() -> Self {
2993 Self {
2994 gear_ratios: vec![-3.5, 3.82, 2.36, 1.68, 1.31, 1.00, 0.78],
2995 current_gear: 1,
2996 final_drive_ratio: 3.73,
2997 auto_shift: false,
2998 shift_up_rpm: 6000.0,
2999 shift_down_rpm: 2500.0,
3000 shift_time: 0.2,
3001 shifting_timer: 0.0,
3002 efficiency: 0.92,
3003 }
3004 }
3005
3006 pub fn current_ratio(&self) -> f32 {
3007 if self.current_gear < 0 {
3008 self.gear_ratios[0]
3009 } else if self.current_gear == 0 {
3010 0.0
3011 } else {
3012 let idx = (self.current_gear as usize).min(self.gear_ratios.len() - 1);
3013 self.gear_ratios[idx]
3014 }
3015 }
3016
3017 pub fn output_torque(&self, engine_torque: f32) -> f32 {
3018 engine_torque * self.current_ratio() * self.final_drive_ratio * self.efficiency
3019 }
3020
3021 pub fn auto_shift_logic(&mut self, current_rpm: f32) {
3022 if !self.auto_shift || self.shifting_timer > 0.0 { return; }
3023 if current_rpm > self.shift_up_rpm && self.current_gear < (self.gear_ratios.len() as i32 - 1) {
3024 self.current_gear += 1;
3025 self.shifting_timer = self.shift_time;
3026 } else if current_rpm < self.shift_down_rpm && self.current_gear > 1 {
3027 self.current_gear -= 1;
3028 self.shifting_timer = self.shift_time;
3029 }
3030 }
3031
3032 pub fn update(&mut self, dt: f32) {
3033 if self.shifting_timer > 0.0 {
3034 self.shifting_timer = (self.shifting_timer - dt).max(0.0);
3035 }
3036 }
3037}
3038
3039#[derive(Debug, Clone, PartialEq)]
3040pub enum DifferentialType {
3041 Open,
3042 Locked,
3043 LimitedSlip { preload_torque: f32, ramp_angle_accel: f32, ramp_angle_decel: f32 },
3044 Torsen { worm_gear_ratio: f32 },
3045 Electronic { target_diff: f32, max_transfer: f32 },
3046}
3047
3048#[derive(Debug, Clone)]
3049pub struct Differential {
3050 pub diff_type: DifferentialType,
3051 pub left_torque: f32,
3052 pub right_torque: f32,
3053 pub left_rpm: f32,
3054 pub right_rpm: f32,
3055}
3056
3057impl Differential {
3058 pub fn new(diff_type: DifferentialType) -> Self {
3059 Self { diff_type, left_torque: 0.0, right_torque: 0.0, left_rpm: 0.0, right_rpm: 0.0 }
3060 }
3061
3062 pub fn distribute(&mut self, input_torque: f32) {
3064 match &self.diff_type {
3065 DifferentialType::Open => {
3066 self.left_torque = input_torque * 0.5;
3068 self.right_torque = input_torque * 0.5;
3069 }
3070 DifferentialType::Locked => {
3071 self.left_torque = input_torque * 0.5;
3073 self.right_torque = input_torque * 0.5;
3074 }
3075 DifferentialType::LimitedSlip { preload_torque, ramp_angle_accel, ramp_angle_decel } => {
3076 let speed_diff = (self.left_rpm - self.right_rpm).abs();
3077 let lock_torque = *preload_torque + speed_diff * ramp_angle_accel.tan();
3078 let base = input_torque * 0.5;
3079 let transfer = lock_torque.min(base.abs());
3080 if self.left_rpm > self.right_rpm {
3081 self.left_torque = base - transfer;
3082 self.right_torque = base + transfer;
3083 } else {
3084 self.left_torque = base + transfer;
3085 self.right_torque = base - transfer;
3086 }
3087 }
3088 DifferentialType::Torsen { worm_gear_ratio } => {
3089 let speed_diff = (self.left_rpm - self.right_rpm).abs();
3091 let torque_ratio = 1.0 + speed_diff * worm_gear_ratio;
3092 let t = input_torque;
3093 self.left_torque = t / (1.0 + 1.0 / torque_ratio);
3094 self.right_torque = t / (1.0 + torque_ratio);
3095 }
3096 DifferentialType::Electronic { target_diff, max_transfer } => {
3097 let speed_diff = self.left_rpm - self.right_rpm;
3098 let transfer = (speed_diff * target_diff).clamp(-*max_transfer, *max_transfer);
3099 let base = input_torque * 0.5;
3100 self.left_torque = base + transfer;
3101 self.right_torque = base - transfer;
3102 }
3103 }
3104 }
3105}
3106
3107#[derive(Debug, Clone)]
3108pub struct VehiclePhysics {
3109 pub body_id: u64,
3110 pub wheels: Vec<WheelState>,
3111 pub engine: EngineTorqueCurve,
3112 pub gearbox: GearBox,
3113 pub front_diff: Differential,
3114 pub rear_diff: Differential,
3115 pub center_diff: Option<Differential>,
3116 pub wheelbase: f32,
3117 pub track_width_front: f32,
3118 pub track_width_rear: f32,
3119 pub center_of_mass: Vec3,
3120 pub mass: f32,
3121 pub aerodynamic_drag: f32, pub downforce_coefficient: f32,
3123 pub brake_bias_front: f32,
3124 pub abs_enabled: bool,
3125 pub tcs_enabled: bool,
3126 pub esc_enabled: bool,
3127 pub throttle: f32,
3128 pub brake: f32,
3129 pub steering: f32,
3130 pub handbrake: bool,
3131}
3132
3133impl VehiclePhysics {
3134 pub fn new(mass: f32, wheelbase: f32, track_width: f32) -> Self {
3135 let hw = track_width * 0.5;
3137 let hl = wheelbase * 0.5;
3138 let wheels = vec![
3139 WheelState::new(Vec3::new(hw, 0.0, hl), 0.33), WheelState::new(Vec3::new(-hw, 0.0, hl), 0.33), WheelState::new(Vec3::new(hw, 0.0, -hl), 0.33), WheelState::new(Vec3::new(-hw, 0.0, -hl), 0.33), ];
3144 Self {
3145 body_id: 0,
3146 wheels,
3147 engine: EngineTorqueCurve::petrol_sport(),
3148 gearbox: GearBox::new(),
3149 front_diff: Differential::new(DifferentialType::Open),
3150 rear_diff: Differential::new(DifferentialType::LimitedSlip {
3151 preload_torque: 50.0, ramp_angle_accel: 30.0_f32.to_radians(), ramp_angle_decel: 50.0_f32.to_radians()
3152 }),
3153 center_diff: None,
3154 wheelbase,
3155 track_width_front: track_width,
3156 track_width_rear: track_width,
3157 center_of_mass: Vec3::new(0.0, 0.3, 0.0),
3158 mass,
3159 aerodynamic_drag: 0.3 * 2.2, downforce_coefficient: 0.0,
3161 brake_bias_front: 0.6,
3162 abs_enabled: true,
3163 tcs_enabled: true,
3164 esc_enabled: false,
3165 throttle: 0.0,
3166 brake: 0.0,
3167 steering: 0.0,
3168 handbrake: false,
3169 }
3170 }
3171
3172 pub fn apply_steering(&mut self) {
3173 let max_steer = 35.0_f32.to_radians();
3174 let steer_rad = self.steering * max_steer;
3175 let (delta_out, delta_in) = ackermann_steering(self.wheelbase, self.track_width_front, steer_rad);
3176 if steer_rad > 0.0 {
3178 self.wheels[0].steer_angle = delta_out;
3179 self.wheels[1].steer_angle = delta_in;
3180 } else {
3181 self.wheels[0].steer_angle = delta_in;
3182 self.wheels[1].steer_angle = delta_out;
3183 }
3184 }
3185
3186 pub fn aerodynamic_drag_force(&self, speed: f32) -> f32 {
3188 0.5 * AIR_DENSITY * self.aerodynamic_drag * speed * speed
3189 }
3190
3191 pub fn downforce(&self, speed: f32) -> f32 {
3193 0.5 * AIR_DENSITY * self.downforce_coefficient * speed * speed
3194 }
3195
3196 pub fn abs_update(&mut self, vehicle_speed: f32) {
3198 if !self.abs_enabled { return; }
3199 for wheel in &mut self.wheels {
3200 if !wheel.is_grounded { continue; }
3201 if wheel.slip_ratio < -0.2 {
3203 wheel.brake_torque *= 0.7;
3204 }
3205 }
3206 }
3207
3208 pub fn tcs_update(&mut self) {
3210 if !self.tcs_enabled { return; }
3211 for wheel in &mut self.wheels {
3212 if !wheel.is_grounded { continue; }
3213 if wheel.slip_ratio > 0.2 {
3214 wheel.drive_torque *= 0.7;
3215 }
3216 }
3217 }
3218
3219 pub fn update(&mut self, dt: f32, vehicle_speed: f32) {
3220 let wheel_ang_vel = if vehicle_speed.abs() > 0.1 { vehicle_speed / self.wheels[2].radius } else { 0.0 };
3222 self.engine.update_rpm(wheel_ang_vel, self.gearbox.current_ratio().abs(), self.gearbox.final_drive_ratio);
3223 self.gearbox.auto_shift_logic(self.engine.current_rpm);
3224 self.gearbox.update(dt);
3225 let engine_torque = self.engine.evaluate(self.engine.current_rpm) * self.throttle;
3226 let output_torque = self.gearbox.output_torque(engine_torque);
3227 self.rear_diff.left_rpm = self.wheels[2].angular_velocity * 60.0 / TWO_PI;
3229 self.rear_diff.right_rpm = self.wheels[3].angular_velocity * 60.0 / TWO_PI;
3230 self.rear_diff.distribute(output_torque);
3231 self.wheels[2].drive_torque = self.rear_diff.left_torque;
3232 self.wheels[3].drive_torque = self.rear_diff.right_torque;
3233 let total_brake_torque = self.brake * 5000.0;
3235 let front_brake = total_brake_torque * self.brake_bias_front;
3236 let rear_brake = total_brake_torque * (1.0 - self.brake_bias_front);
3237 self.wheels[0].brake_torque = front_brake * 0.5;
3238 self.wheels[1].brake_torque = front_brake * 0.5;
3239 let rear_brake_actual = if self.handbrake { total_brake_torque } else { rear_brake * 0.5 };
3240 self.wheels[2].brake_torque = rear_brake_actual;
3241 self.wheels[3].brake_torque = rear_brake_actual;
3242 self.apply_steering();
3244 let wheel_load = self.mass * GRAVITY / 4.0; for wheel in &mut self.wheels {
3247 if wheel.is_grounded {
3248 let surf_friction = surface_friction_coefficient(wheel.road_surface.clone(), false);
3249 wheel.compute_slip_ratio(vehicle_speed);
3250 wheel.compute_tire_forces(wheel.suspension_force.max(wheel_load), surf_friction);
3251 }
3252 }
3253 self.abs_update(vehicle_speed);
3254 self.tcs_update();
3255 }
3256}
3257
3258#[derive(Debug, Clone)]
3263pub struct Aabb {
3264 pub min: Vec3,
3265 pub max: Vec3,
3266}
3267
3268impl Aabb {
3269 pub fn new(min: Vec3, max: Vec3) -> Self { Self { min, max } }
3270
3271 pub fn from_points(points: &[Vec3]) -> Self {
3272 let mut min = Vec3::splat(f32::INFINITY);
3273 let mut max = Vec3::splat(f32::NEG_INFINITY);
3274 for &p in points {
3275 min = min.min(p);
3276 max = max.max(p);
3277 }
3278 Self { min, max }
3279 }
3280
3281 pub fn center(&self) -> Vec3 { (self.min + self.max) * 0.5 }
3282 pub fn half_extents(&self) -> Vec3 { (self.max - self.min) * 0.5 }
3283 pub fn surface_area(&self) -> f32 {
3284 let e = self.max - self.min;
3285 2.0 * (e.x * e.y + e.y * e.z + e.z * e.x)
3286 }
3287 pub fn volume(&self) -> f32 {
3288 let e = self.max - self.min;
3289 (e.x * e.y * e.z).max(0.0)
3290 }
3291 pub fn contains_point(&self, p: Vec3) -> bool {
3292 p.x >= self.min.x && p.x <= self.max.x &&
3293 p.y >= self.min.y && p.y <= self.max.y &&
3294 p.z >= self.min.z && p.z <= self.max.z
3295 }
3296 pub fn intersects(&self, other: &Aabb) -> bool {
3297 self.max.x >= other.min.x && self.min.x <= other.max.x &&
3298 self.max.y >= other.min.y && self.min.y <= other.max.y &&
3299 self.max.z >= other.min.z && self.min.z <= other.max.z
3300 }
3301 pub fn expand(&mut self, point: Vec3) {
3302 self.min = self.min.min(point);
3303 self.max = self.max.max(point);
3304 }
3305 pub fn merged(&self, other: &Aabb) -> Aabb {
3306 Aabb::new(self.min.min(other.min), self.max.max(other.max))
3307 }
3308}
3309
3310#[derive(Debug, Clone)]
3311pub struct Obb {
3312 pub center: Vec3,
3313 pub axes: [Vec3; 3], pub half_extents: Vec3,
3315}
3316
3317impl Obb {
3318 pub fn new(center: Vec3, axes: [Vec3; 3], half_extents: Vec3) -> Self {
3319 Self { center, axes, half_extents }
3320 }
3321
3322 pub fn project(&self, n: Vec3) -> (f32, f32) {
3324 let c = self.center.dot(n);
3325 let r = self.half_extents.x * self.axes[0].dot(n).abs()
3326 + self.half_extents.y * self.axes[1].dot(n).abs()
3327 + self.half_extents.z * self.axes[2].dot(n).abs();
3328 (c - r, c + r)
3329 }
3330
3331 pub fn overlaps(&self, other: &Obb) -> bool {
3333 let test_axes: Vec<Vec3> = {
3334 let mut v = Vec::new();
3335 for &a in &self.axes { v.push(a); }
3336 for &b in &other.axes { v.push(b); }
3337 for &a in &self.axes {
3338 for &b in &other.axes {
3339 let cross = a.cross(b);
3340 if cross.length_squared() > EPSILON * EPSILON {
3341 v.push(cross.normalize());
3342 }
3343 }
3344 }
3345 v
3346 };
3347 for axis in test_axes {
3348 let (a_min, a_max) = self.project(axis);
3349 let (b_min, b_max) = other.project(axis);
3350 if a_max < b_min || b_max < a_min { return false; }
3351 }
3352 true
3353 }
3354
3355 pub fn to_aabb(&self) -> Aabb {
3356 let mut corners = Vec::new();
3357 for sx in [-1.0, 1.0] {
3358 for sy in [-1.0, 1.0] {
3359 for sz in [-1.0, 1.0] {
3360 let pt = self.center
3361 + self.axes[0] * (self.half_extents.x * sx)
3362 + self.axes[1] * (self.half_extents.y * sy)
3363 + self.axes[2] * (self.half_extents.z * sz);
3364 corners.push(pt);
3365 }
3366 }
3367 }
3368 Aabb::from_points(&corners)
3369 }
3370}
3371
3372pub fn fit_aabb_to_triangles(triangles: &[(Vec3, Vec3, Vec3)]) -> Aabb {
3374 let mut min = Vec3::splat(f32::INFINITY);
3375 let mut max = Vec3::splat(f32::NEG_INFINITY);
3376 for &(a, b, c) in triangles {
3377 min = min.min(a).min(b).min(c);
3378 max = max.max(a).max(b).max(c);
3379 }
3380 Aabb::new(min, max)
3381}
3382
3383pub fn fit_obb_pca(points: &[Vec3]) -> Obb {
3386 if points.is_empty() { return Obb::new(Vec3::ZERO, [Vec3::X, Vec3::Y, Vec3::Z], Vec3::splat(0.5)); }
3387 let n = points.len() as f32;
3388 let mean = points.iter().fold(Vec3::ZERO, |a, &b| a + b) / n;
3389 let mut cov = [[0.0f32; 3]; 3];
3391 for &p in points {
3392 let d = p - mean;
3393 let dv = [d.x, d.y, d.z];
3394 for i in 0..3 {
3395 for j in 0..3 {
3396 cov[i][j] += dv[i] * dv[j];
3397 }
3398 }
3399 }
3400 for i in 0..3 { for j in 0..3 { cov[i][j] /= n; } }
3401 let (eigenvalues, eigenvectors) = jacobi_eigen_3x3(cov);
3403 let mut order = [0usize, 1, 2];
3405 order.sort_by(|&a, &b| eigenvalues[b].partial_cmp(&eigenvalues[a]).unwrap_or(std::cmp::Ordering::Equal));
3406 let ax0 = eigenvectors[order[0]];
3407 let ax1 = eigenvectors[order[1]];
3408 let ax2 = ax0.cross(ax1).normalize(); let mut mins = [f32::INFINITY; 3];
3411 let mut maxs = [f32::NEG_INFINITY; 3];
3412 let axes_arr = [ax0, ax1, ax2];
3413 for &p in points {
3414 for k in 0..3 {
3415 let proj = (p - mean).dot(axes_arr[k]);
3416 if proj < mins[k] { mins[k] = proj; }
3417 if proj > maxs[k] { maxs[k] = proj; }
3418 }
3419 }
3420 let he = Vec3::new(
3421 (maxs[0] - mins[0]) * 0.5,
3422 (maxs[1] - mins[1]) * 0.5,
3423 (maxs[2] - mins[2]) * 0.5,
3424 );
3425 let center = mean + ax0 * (mins[0] + maxs[0]) * 0.5
3426 + ax1 * (mins[1] + maxs[1]) * 0.5
3427 + ax2 * (mins[2] + maxs[2]) * 0.5;
3428 Obb::new(center, [ax0, ax1, ax2], he)
3429}
3430
3431pub fn jacobi_eigen_3x3(mut a: [[f32; 3]; 3]) -> ([f32; 3], [Vec3; 3]) {
3434 let mut v = [[0.0f32; 3]; 3];
3435 for i in 0..3 { v[i][i] = 1.0; }
3436 for _ in 0..50 {
3437 let mut max_val = 0.0f32;
3439 let (mut p, mut q) = (0, 1);
3440 for i in 0..3 {
3441 for j in i + 1..3 {
3442 if a[i][j].abs() > max_val {
3443 max_val = a[i][j].abs();
3444 p = i; q = j;
3445 }
3446 }
3447 }
3448 if max_val < 1e-7 { break; }
3449 let theta = if (a[q][q] - a[p][p]).abs() < EPSILON {
3451 HALF_PI * 0.25
3452 } else {
3453 0.5 * (2.0 * a[p][q] / (a[q][q] - a[p][p])).atan()
3454 };
3455 let c = theta.cos();
3456 let s = theta.sin();
3457 let app = a[p][p]; let aqq = a[q][q]; let apq = a[p][q];
3459 a[p][p] = c * c * app - 2.0 * s * c * apq + s * s * aqq;
3460 a[q][q] = s * s * app + 2.0 * s * c * apq + c * c * aqq;
3461 a[p][q] = 0.0; a[q][p] = 0.0;
3462 for k in 0..3 {
3463 if k != p && k != q {
3464 let aip = a[k][p]; let aiq = a[k][q];
3465 a[k][p] = c * aip - s * aiq;
3466 a[p][k] = a[k][p];
3467 a[k][q] = s * aip + c * aiq;
3468 a[q][k] = a[k][q];
3469 }
3470 }
3471 for k in 0..3 {
3472 let vkp = v[k][p]; let vkq = v[k][q];
3473 v[k][p] = c * vkp - s * vkq;
3474 v[k][q] = s * vkp + c * vkq;
3475 }
3476 }
3477 let eigenvalues = [a[0][0], a[1][1], a[2][2]];
3478 let ev0 = Vec3::new(v[0][0], v[1][0], v[2][0]).normalize();
3479 let ev1 = Vec3::new(v[0][1], v[1][1], v[2][1]).normalize();
3480 let ev2 = Vec3::new(v[0][2], v[1][2], v[2][2]).normalize();
3481 (eigenvalues, [ev0, ev1, ev2])
3482}
3483
3484pub fn compute_convex_hull_2d(points: &[Vec2]) -> Vec<Vec2> {
3487 if points.len() < 3 { return points.to_vec(); }
3488 let mut hull = Vec::new();
3489 let start = points.iter().enumerate().min_by(|(_, a), (_, b)| {
3491 a.x.partial_cmp(&b.x).unwrap_or(std::cmp::Ordering::Equal)
3492 }).map(|(i, _)| i).unwrap_or(0);
3493 let mut current = start;
3494 loop {
3495 hull.push(points[current]);
3496 let mut next = (current + 1) % points.len();
3497 for i in 0..points.len() {
3498 let cross = cross2d(points[next] - points[current], points[i] - points[current]);
3499 if cross < 0.0 { next = i; }
3500 }
3501 current = next;
3502 if current == start { break; }
3503 if hull.len() > points.len() { break; } }
3505 hull
3506}
3507
3508fn cross2d(a: Vec2, b: Vec2) -> f32 { a.x * b.y - a.y * b.x }
3509
3510#[derive(Debug, Clone)]
3511pub struct HeightfieldShape {
3512 pub width: usize,
3513 pub depth: usize,
3514 pub heights: Vec<f32>,
3515 pub scale: Vec3,
3516 pub min_height: f32,
3517 pub max_height: f32,
3518 pub up_axis: u8, }
3520
3521impl HeightfieldShape {
3522 pub fn new(width: usize, depth: usize, scale: Vec3) -> Self {
3523 let heights = vec![0.0; width * depth];
3524 Self {
3525 width, depth, heights,
3526 scale,
3527 min_height: 0.0, max_height: 0.0,
3528 up_axis: 1,
3529 }
3530 }
3531
3532 pub fn set_height(&mut self, x: usize, z: usize, h: f32) {
3533 if x < self.width && z < self.depth {
3534 self.heights[z * self.width + x] = h;
3535 if h < self.min_height { self.min_height = h; }
3536 if h > self.max_height { self.max_height = h; }
3537 }
3538 }
3539
3540 pub fn get_height(&self, x: usize, z: usize) -> f32 {
3541 if x < self.width && z < self.depth {
3542 self.heights[z * self.width + x]
3543 } else {
3544 0.0
3545 }
3546 }
3547
3548 pub fn height_at_world(&self, world_x: f32, world_z: f32) -> f32 {
3549 let lx = world_x / self.scale.x;
3550 let lz = world_z / self.scale.z;
3551 let ix = lx as usize;
3552 let iz = lz as usize;
3553 if ix + 1 >= self.width || iz + 1 >= self.depth { return 0.0; }
3554 let tx = lx - ix as f32;
3555 let tz = lz - iz as f32;
3556 let h00 = self.get_height(ix, iz);
3557 let h10 = self.get_height(ix + 1, iz);
3558 let h01 = self.get_height(ix, iz + 1);
3559 let h11 = self.get_height(ix + 1, iz + 1);
3560 let h0 = h00 * (1.0 - tx) + h10 * tx;
3562 let h1 = h01 * (1.0 - tx) + h11 * tx;
3563 (h0 * (1.0 - tz) + h1 * tz) * self.scale.y
3564 }
3565
3566 pub fn compute_aabb(&self) -> Aabb {
3567 Aabb::new(
3568 Vec3::ZERO,
3569 Vec3::new(
3570 (self.width - 1) as f32 * self.scale.x,
3571 self.max_height * self.scale.y,
3572 (self.depth - 1) as f32 * self.scale.z,
3573 ),
3574 )
3575 }
3576}
3577
3578#[derive(Debug, Clone)]
3579pub struct CollisionShapeEditor {
3580 pub shape_type: CollisionShapeType,
3581 pub box_half_extents: Vec3,
3582 pub sphere_radius: f32,
3583 pub capsule_radius: f32,
3584 pub capsule_half_height: f32,
3585 pub cylinder_radius: f32,
3586 pub cylinder_half_height: f32,
3587 pub convex_hull_points: Vec<Vec3>,
3588 pub triangle_mesh: Vec<(Vec3, Vec3, Vec3)>,
3589 pub heightfield: Option<HeightfieldShape>,
3590 pub aabb: Aabb,
3591 pub obb: Option<Obb>,
3592 pub mass_properties_dirty: bool,
3593 pub volume: f32,
3594 pub surface_area: f32,
3595 pub margin: f32, pub local_transform: Mat4,
3597}
3598
3599#[derive(Debug, Clone, PartialEq)]
3600pub enum CollisionShapeType {
3601 Box,
3602 Sphere,
3603 Capsule,
3604 Cylinder,
3605 ConvexHull,
3606 TriangleMesh,
3607 Heightfield,
3608 Compound,
3609}
3610
3611impl CollisionShapeEditor {
3612 pub fn new() -> Self {
3613 Self {
3614 shape_type: CollisionShapeType::Box,
3615 box_half_extents: Vec3::splat(0.5),
3616 sphere_radius: 0.5,
3617 capsule_radius: 0.25,
3618 capsule_half_height: 0.5,
3619 cylinder_radius: 0.25,
3620 cylinder_half_height: 0.5,
3621 convex_hull_points: Vec::new(),
3622 triangle_mesh: Vec::new(),
3623 heightfield: None,
3624 aabb: Aabb::new(Vec3::splat(-0.5), Vec3::splat(0.5)),
3625 obb: None,
3626 mass_properties_dirty: true,
3627 volume: 0.0,
3628 surface_area: 0.0,
3629 margin: 0.01,
3630 local_transform: Mat4::IDENTITY,
3631 }
3632 }
3633
3634 pub fn compute_aabb(&mut self) {
3635 self.aabb = match self.shape_type {
3636 CollisionShapeType::Box => {
3637 let he = self.box_half_extents;
3638 Aabb::new(-he, he)
3639 }
3640 CollisionShapeType::Sphere => {
3641 let r = self.sphere_radius;
3642 Aabb::new(Vec3::splat(-r), Vec3::splat(r))
3643 }
3644 CollisionShapeType::Capsule => {
3645 let r = self.capsule_radius;
3646 let hh = self.capsule_half_height;
3647 Aabb::new(Vec3::new(-r, -(hh + r), -r), Vec3::new(r, hh + r, r))
3648 }
3649 CollisionShapeType::Cylinder => {
3650 let r = self.cylinder_radius;
3651 let hh = self.cylinder_half_height;
3652 Aabb::new(Vec3::new(-r, -hh, -r), Vec3::new(r, hh, r))
3653 }
3654 CollisionShapeType::ConvexHull | CollisionShapeType::TriangleMesh => {
3655 Aabb::from_points(&self.convex_hull_points)
3656 }
3657 CollisionShapeType::Heightfield => {
3658 if let Some(ref hf) = self.heightfield {
3659 hf.compute_aabb()
3660 } else {
3661 Aabb::new(Vec3::ZERO, Vec3::ZERO)
3662 }
3663 }
3664 CollisionShapeType::Compound => self.aabb.clone(),
3665 };
3666 }
3667
3668 pub fn compute_volume(&mut self) {
3669 self.volume = match self.shape_type {
3670 CollisionShapeType::Box => {
3671 let e = self.box_half_extents * 2.0;
3672 e.x * e.y * e.z
3673 }
3674 CollisionShapeType::Sphere => {
3675 (4.0 / 3.0) * PI * self.sphere_radius.powi(3)
3676 }
3677 CollisionShapeType::Capsule => {
3678 let r = self.capsule_radius;
3679 let h = self.capsule_half_height * 2.0;
3680 PI * r * r * h + (4.0 / 3.0) * PI * r * r * r
3681 }
3682 CollisionShapeType::Cylinder => {
3683 let r = self.cylinder_radius;
3684 let h = self.cylinder_half_height * 2.0;
3685 PI * r * r * h
3686 }
3687 CollisionShapeType::ConvexHull => {
3688 self.aabb.volume() * 0.7
3690 }
3691 _ => self.aabb.volume(),
3692 };
3693 }
3694
3695 pub fn compute_surface_area(&mut self) {
3696 self.surface_area = match self.shape_type {
3697 CollisionShapeType::Box => {
3698 let e = self.box_half_extents * 2.0;
3699 2.0 * (e.x * e.y + e.y * e.z + e.z * e.x)
3700 }
3701 CollisionShapeType::Sphere => {
3702 4.0 * PI * self.sphere_radius * self.sphere_radius
3703 }
3704 CollisionShapeType::Capsule => {
3705 let r = self.capsule_radius;
3706 let h = self.capsule_half_height * 2.0;
3707 TWO_PI * r * (h + 2.0 * r)
3708 }
3709 CollisionShapeType::Cylinder => {
3710 let r = self.cylinder_radius;
3711 let h = self.cylinder_half_height * 2.0;
3712 TWO_PI * r * (h + r)
3713 }
3714 _ => self.aabb.surface_area(),
3715 };
3716 }
3717
3718 pub fn fit_to_point_cloud(&mut self, points: &[Vec3]) {
3719 self.convex_hull_points = points.to_vec();
3720 self.obb = Some(fit_obb_pca(points));
3721 self.compute_aabb();
3722 }
3723
3724 pub fn generate_box_wireframe(&self) -> Vec<(Vec3, Vec3)> {
3725 let he = match self.shape_type {
3726 CollisionShapeType::Box => self.box_half_extents,
3727 _ => self.aabb.half_extents(),
3728 };
3729 let c = Vec3::ZERO;
3730 let corners = [
3731 Vec3::new(-he.x, -he.y, -he.z), Vec3::new(he.x, -he.y, -he.z),
3732 Vec3::new(he.x, he.y, -he.z), Vec3::new(-he.x, he.y, -he.z),
3733 Vec3::new(-he.x, -he.y, he.z), Vec3::new(he.x, -he.y, he.z),
3734 Vec3::new(he.x, he.y, he.z), Vec3::new(-he.x, he.y, he.z),
3735 ];
3736 let edges = [
3737 (0,1),(1,2),(2,3),(3,0), (4,5),(5,6),(6,7),(7,4), (0,4),(1,5),(2,6),(3,7), ];
3741 edges.iter().map(|&(a, b)| (corners[a], corners[b])).collect()
3742 }
3743
3744 pub fn generate_sphere_wireframe(&self, segments: usize) -> Vec<(Vec3, Vec3)> {
3745 let r = self.sphere_radius;
3746 let mut lines = Vec::new();
3747 for plane in 0..3 {
3748 let mut prev = Vec3::ZERO;
3749 for i in 0..=segments {
3750 let t = TWO_PI * i as f32 / segments as f32;
3751 let c = t.cos() * r;
3752 let s = t.sin() * r;
3753 let pt = match plane {
3754 0 => Vec3::new(c, s, 0.0),
3755 1 => Vec3::new(c, 0.0, s),
3756 _ => Vec3::new(0.0, c, s),
3757 };
3758 if i > 0 { lines.push((prev, pt)); }
3759 prev = pt;
3760 }
3761 }
3762 lines
3763 }
3764}
3765
3766#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3771pub enum SurfaceType {
3772 Asphalt,
3773 WetAsphalt,
3774 Concrete,
3775 WetConcrete,
3776 Gravel,
3777 Dirt,
3778 Mud,
3779 Sand,
3780 Snow,
3781 Ice,
3782 Grass,
3783 WetGrass,
3784 Rubber,
3785 Metal,
3786 WetMetal,
3787 Wood,
3788 WetWood,
3789 Stone,
3790 WetStone,
3791 Carpet,
3792 Fabric,
3793 Leather,
3794 Glass,
3795 WetGlass,
3796 Ceramic,
3797 Marble,
3798 Granite,
3799 Foam,
3800 Cork,
3801 Plastic,
3802 HardPlastic,
3803 SoftPlastic,
3804 ToughRubber,
3805 Silicon,
3806 Wax,
3807 Oil,
3808 WaterSurface,
3809 DeepWater,
3810 MudThick,
3811 Clay,
3812 Chalk,
3813 Coal,
3814 Sandstone,
3815 Limestone,
3816 Brick,
3817 WetBrick,
3818 TarmacRoad,
3819 DirtRoad,
3820 SnowRoad,
3821 Forest,
3822 RockFace,
3823 Custom,
3824}
3825
3826#[derive(Debug, Clone)]
3827pub struct PhysicsMaterial {
3828 pub name: String,
3829 pub surface_type: SurfaceType,
3830 pub static_friction: f32,
3831 pub dynamic_friction: f32,
3832 pub rolling_friction: f32,
3833 pub restitution: f32, pub density: f32, pub young_modulus: f32, pub poisson_ratio: f32,
3837 pub hardness: f32, pub thermal_conductivity: f32, pub sound_damping: f32, }
3841
3842impl PhysicsMaterial {
3843 pub fn new(name: &str, surface_type: SurfaceType,
3844 sf: f32, df: f32, rf: f32, rest: f32, density: f32) -> Self {
3845 Self {
3846 name: name.to_string(),
3847 surface_type,
3848 static_friction: sf,
3849 dynamic_friction: df,
3850 rolling_friction: rf,
3851 restitution: rest,
3852 density,
3853 young_modulus: 1e9,
3854 poisson_ratio: 0.3,
3855 hardness: 100.0,
3856 thermal_conductivity: 1.0,
3857 sound_damping: 0.5,
3858 }
3859 }
3860}
3861
3862pub fn surface_friction_coefficient(surface: SurfaceType, wet: bool) -> f32 {
3863 match (surface, wet) {
3864 (SurfaceType::Asphalt, false) => 0.8,
3865 (SurfaceType::Asphalt, true) | (SurfaceType::WetAsphalt, _) => 0.5,
3866 (SurfaceType::Concrete, false) => 0.75,
3867 (SurfaceType::Concrete, true) | (SurfaceType::WetConcrete, _) => 0.45,
3868 (SurfaceType::Gravel, _) => 0.6,
3869 (SurfaceType::Dirt, false) => 0.55,
3870 (SurfaceType::Dirt, true) => 0.4,
3871 (SurfaceType::Mud, _) | (SurfaceType::MudThick, _) => 0.3,
3872 (SurfaceType::Sand, _) => 0.45,
3873 (SurfaceType::Snow, _) => 0.2,
3874 (SurfaceType::Ice, _) => 0.05,
3875 (SurfaceType::Grass, false) => 0.5,
3876 (SurfaceType::Grass, true) | (SurfaceType::WetGrass, _) => 0.35,
3877 (SurfaceType::Rubber, _) | (SurfaceType::ToughRubber, _) => 0.9,
3878 (SurfaceType::Metal, false) => 0.4,
3879 (SurfaceType::Metal, true) | (SurfaceType::WetMetal, _) => 0.2,
3880 (SurfaceType::Wood, false) => 0.4,
3881 (SurfaceType::Wood, true) | (SurfaceType::WetWood, _) => 0.25,
3882 (SurfaceType::Stone, false) | (SurfaceType::Granite, _) | (SurfaceType::Marble, _) => 0.6,
3883 (SurfaceType::Stone, true) | (SurfaceType::WetStone, _) => 0.35,
3884 (SurfaceType::Carpet, _) => 0.7,
3885 (SurfaceType::Fabric, _) => 0.6,
3886 (SurfaceType::Leather, _) => 0.5,
3887 (SurfaceType::Glass, false) => 0.4,
3888 (SurfaceType::Glass, true) | (SurfaceType::WetGlass, _) => 0.1,
3889 (SurfaceType::Ceramic, _) => 0.55,
3890 (SurfaceType::Foam, _) => 0.55,
3891 (SurfaceType::Cork, _) => 0.7,
3892 (SurfaceType::Plastic, _) | (SurfaceType::SoftPlastic, _) => 0.35,
3893 (SurfaceType::HardPlastic, _) => 0.3,
3894 (SurfaceType::Silicon, _) => 0.8,
3895 (SurfaceType::Wax, _) => 0.05,
3896 (SurfaceType::Oil, _) => 0.03,
3897 (SurfaceType::WaterSurface, _) | (SurfaceType::DeepWater, _) => 0.01,
3898 (SurfaceType::Clay, _) => 0.45,
3899 (SurfaceType::Chalk, _) => 0.65,
3900 (SurfaceType::Coal, _) => 0.4,
3901 (SurfaceType::Sandstone, _) => 0.55,
3902 (SurfaceType::Limestone, _) => 0.6,
3903 (SurfaceType::Brick, false) => 0.65,
3904 (SurfaceType::Brick, true) | (SurfaceType::WetBrick, _) => 0.4,
3905 (SurfaceType::TarmacRoad, _) => 0.75,
3906 (SurfaceType::DirtRoad, _) => 0.5,
3907 (SurfaceType::SnowRoad, _) => 0.2,
3908 (SurfaceType::Forest, _) => 0.5,
3909 (SurfaceType::RockFace, _) => 0.65,
3910 _ => DEFAULT_FRICTION,
3911 }
3912}
3913
3914pub fn build_material_library() -> HashMap<String, PhysicsMaterial> {
3915 let mut lib = HashMap::new();
3916 let add = |lib: &mut HashMap<String, PhysicsMaterial>, m: PhysicsMaterial| {
3917 lib.insert(m.name.clone(), m);
3918 };
3919 add(&mut lib, PhysicsMaterial::new("Asphalt", SurfaceType::Asphalt, 0.9, 0.8, 0.02, 0.1, 2300.0));
3921 add(&mut lib, PhysicsMaterial::new("Wet Asphalt", SurfaceType::WetAsphalt, 0.55, 0.5, 0.03, 0.1, 2300.0));
3922 add(&mut lib, PhysicsMaterial::new("Concrete", SurfaceType::Concrete, 0.8, 0.75, 0.02, 0.15, CONCRETE_DENSITY));
3923 add(&mut lib, PhysicsMaterial::new("Wet Concrete", SurfaceType::WetConcrete, 0.5, 0.45, 0.025, 0.15, CONCRETE_DENSITY));
3924 add(&mut lib, PhysicsMaterial::new("Gravel", SurfaceType::Gravel, 0.65, 0.6, 0.04, 0.2, 1650.0));
3925 add(&mut lib, PhysicsMaterial::new("Dirt", SurfaceType::Dirt, 0.6, 0.55, 0.04, 0.2, 1600.0));
3926 add(&mut lib, PhysicsMaterial::new("Mud", SurfaceType::Mud, 0.35, 0.3, 0.05, 0.05, 1800.0));
3927 add(&mut lib, PhysicsMaterial::new("Sand", SurfaceType::Sand, 0.5, 0.45, 0.06, 0.1, SAND_DENSITY));
3928 add(&mut lib, PhysicsMaterial::new("Snow", SurfaceType::Snow, 0.25, 0.2, 0.04, 0.05, 200.0));
3929 add(&mut lib, PhysicsMaterial::new("Ice", SurfaceType::Ice, 0.07, 0.05, 0.01, 0.02, ICE_DENSITY));
3930 add(&mut lib, PhysicsMaterial::new("Grass", SurfaceType::Grass, 0.55, 0.5, 0.04, 0.2, 1200.0));
3931 add(&mut lib, PhysicsMaterial::new("Wet Grass", SurfaceType::WetGrass, 0.4, 0.35, 0.05, 0.1, 1200.0));
3932 add(&mut lib, PhysicsMaterial::new("Rubber", SurfaceType::Rubber, 1.0, 0.9, 0.015, 0.8, RUBBER_DENSITY));
3933 add(&mut lib, PhysicsMaterial::new("Metal", SurfaceType::Metal, 0.45, 0.4, 0.01, 0.3, STEEL_DENSITY));
3934 add(&mut lib, PhysicsMaterial::new("Wet Metal", SurfaceType::WetMetal, 0.25, 0.2, 0.01, 0.3, STEEL_DENSITY));
3935 add(&mut lib, PhysicsMaterial::new("Steel", SurfaceType::Metal, 0.5, 0.45, 0.01, 0.35, STEEL_DENSITY));
3936 add(&mut lib, PhysicsMaterial::new("Aluminum", SurfaceType::Metal, 0.42, 0.38, 0.01, 0.3, ALUMINUM_DENSITY));
3937 add(&mut lib, PhysicsMaterial::new("Copper", SurfaceType::Metal, 0.48, 0.44, 0.01, 0.3, COPPER_DENSITY));
3938 add(&mut lib, PhysicsMaterial::new("Gold", SurfaceType::Metal, 0.35, 0.3, 0.01, 0.3, GOLD_DENSITY));
3939 add(&mut lib, PhysicsMaterial::new("Wood", SurfaceType::Wood, 0.45, 0.4, 0.02, 0.35, WOOD_DENSITY));
3940 add(&mut lib, PhysicsMaterial::new("Wet Wood", SurfaceType::WetWood, 0.3, 0.25, 0.025, 0.35, WOOD_DENSITY));
3941 add(&mut lib, PhysicsMaterial::new("Stone", SurfaceType::Stone, 0.65, 0.6, 0.015, 0.2, 2600.0));
3942 add(&mut lib, PhysicsMaterial::new("Granite", SurfaceType::Granite, 0.68, 0.62, 0.015, 0.18, 2700.0));
3943 add(&mut lib, PhysicsMaterial::new("Marble", SurfaceType::Marble, 0.55, 0.5, 0.015, 0.25, 2700.0));
3944 add(&mut lib, PhysicsMaterial::new("Glass", SurfaceType::Glass, 0.45, 0.4, 0.01, 0.65, GLASS_DENSITY));
3945 add(&mut lib, PhysicsMaterial::new("Wet Glass", SurfaceType::WetGlass, 0.15, 0.1, 0.005, 0.65, GLASS_DENSITY));
3946 add(&mut lib, PhysicsMaterial::new("Ceramic", SurfaceType::Ceramic, 0.6, 0.55, 0.015, 0.4, 2400.0));
3947 add(&mut lib, PhysicsMaterial::new("Carpet", SurfaceType::Carpet, 0.75, 0.7, 0.03, 0.05, 300.0));
3948 add(&mut lib, PhysicsMaterial::new("Fabric", SurfaceType::Fabric, 0.65, 0.6, 0.03, 0.1, 200.0));
3949 add(&mut lib, PhysicsMaterial::new("Leather", SurfaceType::Leather, 0.55, 0.5, 0.02, 0.3, 900.0));
3950 add(&mut lib, PhysicsMaterial::new("Foam", SurfaceType::Foam, 0.6, 0.55, 0.03, 0.05, 30.0));
3951 add(&mut lib, PhysicsMaterial::new("Cork", SurfaceType::Cork, 0.75, 0.7, 0.03, 0.5, 200.0));
3952 add(&mut lib, PhysicsMaterial::new("Plastic", SurfaceType::Plastic, 0.4, 0.35, 0.01, 0.4, 950.0));
3953 add(&mut lib, PhysicsMaterial::new("Hard Plastic", SurfaceType::HardPlastic, 0.35, 0.3, 0.01, 0.45, 1100.0));
3954 add(&mut lib, PhysicsMaterial::new("Soft Plastic", SurfaceType::SoftPlastic, 0.55, 0.5, 0.015, 0.2, 900.0));
3955 add(&mut lib, PhysicsMaterial::new("Silicone", SurfaceType::Silicon, 0.85, 0.8, 0.015, 0.5, 1100.0));
3956 add(&mut lib, PhysicsMaterial::new("Wax", SurfaceType::Wax, 0.07, 0.05, 0.005, 0.15, 900.0));
3957 add(&mut lib, PhysicsMaterial::new("Brick", SurfaceType::Brick, 0.7, 0.65, 0.02, 0.15, 1800.0));
3958 add(&mut lib, PhysicsMaterial::new("Wet Brick", SurfaceType::WetBrick, 0.45, 0.4, 0.025, 0.1, 1800.0));
3959 add(&mut lib, PhysicsMaterial::new("Clay", SurfaceType::Clay, 0.5, 0.45, 0.04, 0.1, 1500.0));
3960 add(&mut lib, PhysicsMaterial::new("Chalk", SurfaceType::Chalk, 0.7, 0.65, 0.02, 0.1, 2000.0));
3961 add(&mut lib, PhysicsMaterial::new("Coal", SurfaceType::Coal, 0.45, 0.4, 0.02, 0.2, 1400.0));
3962 add(&mut lib, PhysicsMaterial::new("Sandstone", SurfaceType::Sandstone, 0.6, 0.55, 0.02, 0.15, 2200.0));
3963 add(&mut lib, PhysicsMaterial::new("Limestone", SurfaceType::Limestone, 0.65, 0.6, 0.015, 0.15, 2300.0));
3964 add(&mut lib, PhysicsMaterial::new("Water Surface", SurfaceType::WaterSurface, 0.02, 0.01, 0.001, 0.0, WATER_DENSITY));
3965 add(&mut lib, PhysicsMaterial::new("Thick Mud", SurfaceType::MudThick, 0.4, 0.35, 0.06, 0.02, 2000.0));
3966 add(&mut lib, PhysicsMaterial::new("Tarmac Road", SurfaceType::TarmacRoad, 0.8, 0.75, 0.02, 0.1, 2300.0));
3967 add(&mut lib, PhysicsMaterial::new("Dirt Road", SurfaceType::DirtRoad, 0.55, 0.5, 0.04, 0.15, 1600.0));
3968 add(&mut lib, PhysicsMaterial::new("Snow Road", SurfaceType::SnowRoad, 0.25, 0.2, 0.03, 0.05, 300.0));
3969 lib
3970}
3971
3972#[derive(Debug, Clone)]
3977pub struct ContactPoint {
3978 pub world_position: Vec3,
3979 pub world_normal: Vec3,
3980 pub penetration_depth: f32,
3981 pub impulse: Vec3,
3982 pub normal_impulse: f32,
3983 pub tangent_impulse1: f32,
3984 pub tangent_impulse2: f32,
3985 pub body_a: u64,
3986 pub body_b: u64,
3987 pub lifetime: f32, pub warm_impulse: f32,
3989}
3990
3991impl ContactPoint {
3992 pub fn new(pos: Vec3, normal: Vec3, depth: f32, body_a: u64, body_b: u64) -> Self {
3993 Self {
3994 world_position: pos,
3995 world_normal: normal,
3996 penetration_depth: depth,
3997 impulse: Vec3::ZERO,
3998 normal_impulse: 0.0,
3999 tangent_impulse1: 0.0,
4000 tangent_impulse2: 0.0,
4001 body_a, body_b,
4002 lifetime: 3.0,
4003 warm_impulse: 0.0,
4004 }
4005 }
4006}
4007
4008#[derive(Debug, Clone)]
4009pub struct ContactVisualizer {
4010 pub contacts: Vec<ContactPoint>,
4011 pub show_normals: bool,
4012 pub show_impulses: bool,
4013 pub show_penetration: bool,
4014 pub normal_length: f32,
4015 pub impulse_scale: f32,
4016 pub contact_sphere_radius: f32,
4017 pub color_by_depth: bool,
4018 pub color_no_contact: Vec4,
4019 pub color_shallow: Vec4,
4020 pub color_deep: Vec4,
4021 pub max_display_contacts: usize,
4022}
4023
4024impl ContactVisualizer {
4025 pub fn new() -> Self {
4026 Self {
4027 contacts: Vec::new(),
4028 show_normals: true,
4029 show_impulses: true,
4030 show_penetration: true,
4031 normal_length: 0.1,
4032 impulse_scale: 0.001,
4033 contact_sphere_radius: 0.02,
4034 color_by_depth: true,
4035 color_no_contact: Vec4::new(0.0, 1.0, 0.0, 1.0),
4036 color_shallow: Vec4::new(1.0, 1.0, 0.0, 1.0),
4037 color_deep: Vec4::new(1.0, 0.0, 0.0, 1.0),
4038 max_display_contacts: 256,
4039 }
4040 }
4041
4042 pub fn add_contact(&mut self, contact: ContactPoint) {
4043 if self.contacts.len() < self.max_display_contacts {
4044 self.contacts.push(contact);
4045 }
4046 }
4047
4048 pub fn update(&mut self, dt: f32) {
4049 for c in &mut self.contacts {
4050 c.lifetime -= dt * 60.0;
4051 }
4052 self.contacts.retain(|c| c.lifetime > 0.0);
4053 }
4054
4055 pub fn clear(&mut self) {
4056 self.contacts.clear();
4057 }
4058
4059 pub fn depth_color(&self, depth: f32, max_depth: f32) -> Vec4 {
4061 if !self.color_by_depth {
4062 return self.color_shallow;
4063 }
4064 let t = (depth / max_depth.max(EPSILON)).clamp(0.0, 1.0);
4065 Vec4::new(
4066 self.color_shallow.x + (self.color_deep.x - self.color_shallow.x) * t,
4067 self.color_shallow.y + (self.color_deep.y - self.color_shallow.y) * t,
4068 self.color_shallow.z + (self.color_deep.z - self.color_shallow.z) * t,
4069 1.0,
4070 )
4071 }
4072
4073 pub fn generate_debug_lines(&self) -> Vec<(Vec3, Vec3, Vec4)> {
4075 let mut lines = Vec::new();
4076 let max_depth = self.contacts.iter().map(|c| c.penetration_depth).fold(0.0f32, f32::max).max(0.001);
4077 for contact in &self.contacts {
4078 let alpha = (contact.lifetime / 3.0).clamp(0.0, 1.0);
4079 let mut col = self.depth_color(contact.penetration_depth, max_depth);
4080 col.w = alpha;
4081 if self.show_normals {
4082 let end = contact.world_position + contact.world_normal * self.normal_length;
4083 lines.push((contact.world_position, end, col));
4084 }
4085 if self.show_impulses && contact.normal_impulse > EPSILON {
4086 let imp_end = contact.world_position + contact.world_normal * contact.normal_impulse * self.impulse_scale;
4087 lines.push((contact.world_position, imp_end, Vec4::new(0.0, 0.5, 1.0, alpha)));
4088 }
4089 if self.show_penetration {
4090 let pen_end = contact.world_position - contact.world_normal * contact.penetration_depth;
4091 lines.push((contact.world_position, pen_end, Vec4::new(1.0, 0.5, 0.0, alpha)));
4092 }
4093 }
4094 lines
4095 }
4096
4097 pub fn contact_sphere_positions(&self) -> Vec<(Vec3, Vec4, f32)> {
4099 let max_depth = self.contacts.iter().map(|c| c.penetration_depth).fold(0.001f32, f32::max);
4100 self.contacts.iter().map(|c| {
4101 let alpha = (c.lifetime / 3.0).clamp(0.0, 1.0);
4102 let mut col = self.depth_color(c.penetration_depth, max_depth);
4103 col.w = alpha;
4104 (c.world_position, col, self.contact_sphere_radius)
4105 }).collect()
4106 }
4107
4108 pub fn stats(&self) -> ContactStats {
4109 let total = self.contacts.len();
4110 let max_depth = self.contacts.iter().map(|c| c.penetration_depth).fold(0.0f32, f32::max);
4111 let avg_depth = if total > 0 {
4112 self.contacts.iter().map(|c| c.penetration_depth).sum::<f32>() / total as f32
4113 } else { 0.0 };
4114 let total_impulse = self.contacts.iter().map(|c| c.normal_impulse).sum::<f32>();
4115 ContactStats { total, max_depth, avg_depth, total_impulse }
4116 }
4117}
4118
4119#[derive(Debug, Clone)]
4120pub struct ContactStats {
4121 pub total: usize,
4122 pub max_depth: f32,
4123 pub avg_depth: f32,
4124 pub total_impulse: f32,
4125}
4126
4127#[derive(Debug, Clone)]
4132pub struct SapEndpoint {
4133 pub value: f32,
4134 pub is_min: bool,
4135 pub body_id: u64,
4136}
4137
4138#[derive(Debug, Clone)]
4139pub struct BroadPhase {
4140 pub x_endpoints: Vec<SapEndpoint>,
4141 pub y_endpoints: Vec<SapEndpoint>,
4142 pub z_endpoints: Vec<SapEndpoint>,
4143 pub active_pairs: HashSet<(u64, u64)>,
4144 pub aabbs: HashMap<u64, Aabb>,
4145}
4146
4147impl BroadPhase {
4148 pub fn new() -> Self {
4149 Self {
4150 x_endpoints: Vec::new(),
4151 y_endpoints: Vec::new(),
4152 z_endpoints: Vec::new(),
4153 active_pairs: HashSet::new(),
4154 aabbs: HashMap::new(),
4155 }
4156 }
4157
4158 pub fn update_aabb(&mut self, body_id: u64, aabb: Aabb) {
4159 self.aabbs.insert(body_id, aabb);
4160 }
4161
4162 pub fn compute_overlapping_pairs(&self) -> Vec<(u64, u64)> {
4163 let mut result = Vec::new();
4164 let bodies: Vec<u64> = self.aabbs.keys().copied().collect();
4165 let n = bodies.len();
4166 for i in 0..n {
4167 for j in i + 1..n {
4168 let a = bodies[i]; let b = bodies[j];
4169 if let (Some(aa), Some(ab)) = (self.aabbs.get(&a), self.aabbs.get(&b)) {
4170 if aa.intersects(ab) {
4171 let pair = if a < b { (a, b) } else { (b, a) };
4172 result.push(pair);
4173 }
4174 }
4175 }
4176 }
4177 result
4178 }
4179}
4180
4181pub fn support_sphere(center: Vec3, radius: f32, dir: Vec3) -> Vec3 {
4187 let d = dir.normalize();
4188 center + d * radius
4189}
4190
4191pub fn support_box(half_extents: Vec3, dir: Vec3) -> Vec3 {
4193 Vec3::new(
4194 half_extents.x * dir.x.signum(),
4195 half_extents.y * dir.y.signum(),
4196 half_extents.z * dir.z.signum(),
4197 )
4198}
4199
4200pub fn support_minkowski(
4202 pa: Vec3, ha: Vec3,
4203 pb: Vec3, hb: Vec3,
4204 dir: Vec3,
4205) -> Vec3 {
4206 let sa = support_box(ha, dir) + pa;
4207 let sb = support_box(hb, -dir) + pb;
4208 sa - sb
4209}
4210
4211fn dot(a: Vec3, b: Vec3) -> f32 { a.dot(b) }
4213
4214pub fn gjk_intersect_box_box(
4216 pos_a: Vec3, half_a: Vec3,
4217 pos_b: Vec3, half_b: Vec3,
4218) -> bool {
4219 let diff = pos_b - pos_a;
4221 let axes = [Vec3::X, Vec3::Y, Vec3::Z];
4222 for &ax in &axes {
4223 let proj_a = half_a.dot(ax.abs());
4224 let proj_b = half_b.dot(ax.abs());
4225 let dist = diff.dot(ax).abs();
4226 if dist > proj_a + proj_b { return false; }
4227 }
4228 true
4229}
4230
4231pub fn epa_sphere_sphere(
4234 center_a: Vec3, radius_a: f32,
4235 center_b: Vec3, radius_b: f32,
4236) -> Option<(Vec3, f32)> {
4237 let diff = center_b - center_a;
4238 let dist = diff.length();
4239 let overlap = radius_a + radius_b - dist;
4240 if overlap < 0.0 { return None; }
4241 let normal = if dist > EPSILON { diff / dist } else { Vec3::Y };
4242 Some((normal, overlap))
4243}
4244
4245#[derive(Debug, Clone)]
4250pub struct PhysicsWorldSettings {
4251 pub gravity: Vec3,
4252 pub default_linear_damping: f32,
4253 pub default_angular_damping: f32,
4254 pub default_restitution: f32,
4255 pub default_friction: f32,
4256 pub solver_iterations: u32,
4257 pub solver_velocity_iterations: u32,
4258 pub baumgarte_factor: f32,
4259 pub sleep_enabled: bool,
4260 pub fixed_timestep: f32,
4261 pub max_substeps: u32,
4262 pub broadphase_type: BroadphaseType,
4263 pub warm_starting: bool,
4264 pub continuous_collision: bool,
4265 pub split_impulses: bool,
4266}
4267
4268#[derive(Debug, Clone, PartialEq)]
4269pub enum BroadphaseType {
4270 BruteForce,
4271 SweepAndPrune,
4272 DynamicAabbTree,
4273}
4274
4275impl Default for PhysicsWorldSettings {
4276 fn default() -> Self {
4277 Self {
4278 gravity: Vec3::new(0.0, -GRAVITY, 0.0),
4279 default_linear_damping: 0.01,
4280 default_angular_damping: 0.05,
4281 default_restitution: DEFAULT_RESTITUTION,
4282 default_friction: DEFAULT_FRICTION,
4283 solver_iterations: 10,
4284 solver_velocity_iterations: 10,
4285 baumgarte_factor: 0.2,
4286 sleep_enabled: true,
4287 fixed_timestep: 1.0 / 60.0,
4288 max_substeps: 4,
4289 broadphase_type: BroadphaseType::SweepAndPrune,
4290 warm_starting: true,
4291 continuous_collision: false,
4292 split_impulses: true,
4293 }
4294 }
4295}
4296
4297#[derive(Debug, Clone)]
4302pub struct PhysicsDebugOverlay {
4303 pub show_colliders: bool,
4304 pub show_contacts: bool,
4305 pub show_joints: bool,
4306 pub show_velocities: bool,
4307 pub show_forces: bool,
4308 pub show_center_of_mass: bool,
4309 pub show_inertia: bool,
4310 pub show_sleep_state: bool,
4311 pub show_broadphase: bool,
4312 pub collider_color: Vec4,
4313 pub contact_color: Vec4,
4314 pub velocity_color: Vec4,
4315 pub force_color: Vec4,
4316 pub com_color: Vec4,
4317 pub sleeping_color: Vec4,
4318 pub velocity_scale: f32,
4319 pub force_scale: f32,
4320 pub line_width: f32,
4321}
4322
4323impl Default for PhysicsDebugOverlay {
4324 fn default() -> Self {
4325 Self {
4326 show_colliders: true,
4327 show_contacts: true,
4328 show_joints: true,
4329 show_velocities: false,
4330 show_forces: false,
4331 show_center_of_mass: true,
4332 show_inertia: false,
4333 show_sleep_state: true,
4334 show_broadphase: false,
4335 collider_color: Vec4::new(0.0, 1.0, 0.0, 0.7),
4336 contact_color: Vec4::new(1.0, 0.0, 0.0, 1.0),
4337 velocity_color: Vec4::new(1.0, 1.0, 0.0, 1.0),
4338 force_color: Vec4::new(1.0, 0.5, 0.0, 1.0),
4339 com_color: Vec4::new(0.0, 0.5, 1.0, 1.0),
4340 sleeping_color: Vec4::new(0.5, 0.5, 0.5, 0.5),
4341 velocity_scale: 0.1,
4342 force_scale: 0.0001,
4343 line_width: 1.0,
4344 }
4345 }
4346}
4347
4348impl PhysicsDebugOverlay {
4349 pub fn generate_body_debug_lines(&self, body: &RigidBodyInspector) -> Vec<(Vec3, Vec3, Vec4)> {
4350 let mut lines = Vec::new();
4351 let col = if body.sleeping { self.sleeping_color } else { self.collider_color };
4352 if self.show_velocities && !body.is_static {
4354 let v_end = body.position + body.linear_velocity * self.velocity_scale;
4355 lines.push((body.position, v_end, self.velocity_color));
4356 }
4357 if self.show_center_of_mass {
4359 let com = body.position + body.orientation * body.center_of_mass;
4360 let d = 0.05;
4361 lines.push((com - Vec3::X * d, com + Vec3::X * d, self.com_color));
4362 lines.push((com - Vec3::Y * d, com + Vec3::Y * d, self.com_color));
4363 lines.push((com - Vec3::Z * d, com + Vec3::Z * d, self.com_color));
4364 }
4365 lines
4366 }
4367}
4368
4369#[derive(Debug)]
4374pub struct PhysicsEditor {
4375 pub rigid_bodies: HashMap<u64, RigidBodyInspector>,
4376 pub constraints: HashMap<u64, Constraint>,
4377 pub joint_editor: JointEditor,
4378 pub cloth_sims: HashMap<u64, ClothSimulation>,
4379 pub fluid_sims: HashMap<u64, FluidSimulation>,
4380 pub fracture_systems: HashMap<u64, VoronoiFracture>,
4381 pub ragdoll_editor: RagdollEditor,
4382 pub vehicles: HashMap<u64, VehiclePhysics>,
4383 pub collision_shapes: HashMap<u64, CollisionShapeEditor>,
4384 pub material_library: HashMap<String, PhysicsMaterial>,
4385 pub contact_visualizer: ContactVisualizer,
4386 pub broad_phase: BroadPhase,
4387 pub world_settings: PhysicsWorldSettings,
4388 pub debug_overlay: PhysicsDebugOverlay,
4389 pub next_id: u64,
4390 pub selected_body: Option<u64>,
4391 pub selected_constraint: Option<u64>,
4392 pub simulation_running: bool,
4393 pub accumulated_time: f32,
4394 pub simulation_time: f32,
4395 pub step_count: u64,
4396 pub history: VecDeque<PhysicsSnapshot>,
4397 pub max_history: usize,
4398 pub undo_stack: VecDeque<PhysicsEditorCommand>,
4399 pub redo_stack: VecDeque<PhysicsEditorCommand>,
4400}
4401
4402#[derive(Debug, Clone)]
4403pub struct PhysicsSnapshot {
4404 pub time: f32,
4405 pub body_states: HashMap<u64, (Vec3, Quat, Vec3, Vec3)>,
4406}
4407
4408#[derive(Debug, Clone)]
4409pub enum PhysicsEditorCommand {
4410 AddBody { id: u64 },
4411 RemoveBody { id: u64, body: RigidBodyInspector },
4412 MoveBody { id: u64, old_pos: Vec3, new_pos: Vec3 },
4413 ChangeProperty { id: u64, property: String, old_value: f32, new_value: f32 },
4414 AddConstraint { id: u64 },
4415 RemoveConstraint { id: u64, constraint: Constraint },
4416}
4417
4418impl PhysicsEditor {
4419 pub fn new() -> Self {
4420 Self {
4421 rigid_bodies: HashMap::new(),
4422 constraints: HashMap::new(),
4423 joint_editor: JointEditor::new(),
4424 cloth_sims: HashMap::new(),
4425 fluid_sims: HashMap::new(),
4426 fracture_systems: HashMap::new(),
4427 ragdoll_editor: RagdollEditor::new(),
4428 vehicles: HashMap::new(),
4429 collision_shapes: HashMap::new(),
4430 material_library: build_material_library(),
4431 contact_visualizer: ContactVisualizer::new(),
4432 broad_phase: BroadPhase::new(),
4433 world_settings: PhysicsWorldSettings::default(),
4434 debug_overlay: PhysicsDebugOverlay::default(),
4435 next_id: 1,
4436 selected_body: None,
4437 selected_constraint: None,
4438 simulation_running: false,
4439 accumulated_time: 0.0,
4440 simulation_time: 0.0,
4441 step_count: 0,
4442 history: VecDeque::new(),
4443 max_history: 60,
4444 undo_stack: VecDeque::new(),
4445 redo_stack: VecDeque::new(),
4446 }
4447 }
4448
4449 pub fn alloc_id(&mut self) -> u64 {
4450 let id = self.next_id;
4451 self.next_id += 1;
4452 id
4453 }
4454
4455 pub fn add_rigid_body(&mut self, name: &str) -> u64 {
4456 let id = self.alloc_id();
4457 let body = RigidBodyInspector::new(id, name);
4458 self.rigid_bodies.insert(id, body);
4459 self.undo_stack.push_back(PhysicsEditorCommand::AddBody { id });
4460 id
4461 }
4462
4463 pub fn remove_rigid_body(&mut self, id: u64) {
4464 if let Some(body) = self.rigid_bodies.remove(&id) {
4465 self.undo_stack.push_back(PhysicsEditorCommand::RemoveBody { id, body });
4466 }
4467 }
4468
4469 pub fn add_constraint(&mut self, constraint: Constraint) -> u64 {
4470 let id = self.alloc_id();
4471 self.constraints.insert(id, constraint);
4472 id
4473 }
4474
4475 pub fn add_cloth(&mut self, w: usize, h: usize, cell_size: f32) -> u64 {
4476 let id = self.alloc_id();
4477 self.cloth_sims.insert(id, ClothSimulation::new(w, h, cell_size, 0.01));
4478 id
4479 }
4480
4481 pub fn add_fluid(&mut self, kernel_radius: f32, rest_density: f32) -> u64 {
4482 let id = self.alloc_id();
4483 self.fluid_sims.insert(id, FluidSimulation::new(kernel_radius, rest_density));
4484 id
4485 }
4486
4487 pub fn add_fracture_system(&mut self, bounds_min: Vec3, bounds_max: Vec3, num_cells: usize, seed: u64) -> u64 {
4488 let id = self.alloc_id();
4489 self.fracture_systems.insert(id, VoronoiFracture::new(bounds_min, bounds_max, num_cells, seed));
4490 id
4491 }
4492
4493 pub fn add_vehicle(&mut self, mass: f32, wheelbase: f32, track: f32) -> u64 {
4494 let id = self.alloc_id();
4495 let mut vehicle = VehiclePhysics::new(mass, wheelbase, track);
4496 vehicle.body_id = id;
4497 self.vehicles.insert(id, vehicle);
4498 id
4499 }
4500
4501 pub fn add_collision_shape(&mut self, shape_type: CollisionShapeType) -> u64 {
4502 let id = self.alloc_id();
4503 let mut shape = CollisionShapeEditor::new();
4504 shape.shape_type = shape_type;
4505 shape.compute_aabb();
4506 shape.compute_volume();
4507 shape.compute_surface_area();
4508 self.collision_shapes.insert(id, shape);
4509 id
4510 }
4511
4512 pub fn update_broadphase(&mut self) {
4514 for (id, body) in &self.rigid_bodies {
4515 if let Some(shape) = self.collision_shapes.get(id) {
4516 let aabb = shape.aabb.clone();
4517 let center = body.position;
4519 let he = aabb.half_extents();
4520 let transformed = Aabb::new(center - he, center + he);
4521 self.broad_phase.update_aabb(*id, transformed);
4522 }
4523 }
4524 }
4525
4526 pub fn broad_phase_query(&self) -> Vec<(u64, u64)> {
4528 self.broad_phase.compute_overlapping_pairs()
4529 }
4530
4531 pub fn step(&mut self, dt: f32) {
4533 if !self.simulation_running { return; }
4534 self.accumulated_time += dt;
4535 let fixed_dt = self.world_settings.fixed_timestep;
4536 let mut steps = 0u32;
4537 while self.accumulated_time >= fixed_dt && steps < self.world_settings.max_substeps {
4538 self.fixed_step(fixed_dt);
4539 self.accumulated_time -= fixed_dt;
4540 steps += 1;
4541 }
4542 }
4543
4544 fn fixed_step(&mut self, dt: f32) {
4545 let ids: Vec<u64> = self.rigid_bodies.keys().copied().collect();
4547 for id in &ids {
4548 if let Some(body) = self.rigid_bodies.get_mut(id) {
4549 body.integrate(dt);
4550 }
4551 }
4552 let cloth_ids: Vec<u64> = self.cloth_sims.keys().copied().collect();
4554 for id in &cloth_ids {
4555 if let Some(cloth) = self.cloth_sims.get_mut(&id) {
4556 cloth.integrate(dt, self.simulation_time);
4557 cloth.solve_springs(dt);
4558 cloth.compute_normals();
4559 }
4560 }
4561 let fluid_ids: Vec<u64> = self.fluid_sims.keys().copied().collect();
4563 for id in &fluid_ids {
4564 if let Some(fluid) = self.fluid_sims.get_mut(&id) {
4565 fluid.step(dt);
4566 }
4567 }
4568 let fracture_ids: Vec<u64> = self.fracture_systems.keys().copied().collect();
4570 for id in &fracture_ids {
4571 if let Some(frac) = self.fracture_systems.get_mut(&id) {
4572 frac.integrate_debris(dt);
4573 }
4574 }
4575 self.contact_visualizer.update(dt);
4577 self.simulation_time += dt;
4578 self.step_count += 1;
4579 if self.step_count % 6 == 0 {
4581 self.record_snapshot();
4582 }
4583 }
4584
4585 fn record_snapshot(&mut self) {
4586 let mut states = HashMap::new();
4587 for (id, body) in &self.rigid_bodies {
4588 states.insert(*id, (body.position, body.orientation, body.linear_velocity, body.angular_velocity));
4589 }
4590 let snap = PhysicsSnapshot { time: self.simulation_time, body_states: states };
4591 if self.history.len() >= self.max_history {
4592 self.history.pop_front();
4593 }
4594 self.history.push_back(snap);
4595 }
4596
4597 pub fn play(&mut self) { self.simulation_running = true; }
4598 pub fn pause(&mut self) { self.simulation_running = false; }
4599
4600 pub fn reset(&mut self) {
4601 self.simulation_running = false;
4602 self.simulation_time = 0.0;
4603 self.step_count = 0;
4604 self.accumulated_time = 0.0;
4605 self.contact_visualizer.clear();
4606 self.history.clear();
4607 for body in self.rigid_bodies.values_mut() {
4609 body.linear_velocity = Vec3::ZERO;
4610 body.angular_velocity = Vec3::ZERO;
4611 body.force_accumulator = Vec3::ZERO;
4612 body.torque_accumulator = Vec3::ZERO;
4613 body.sleeping = false;
4614 body.sleep_timer = 0.0;
4615 }
4616 }
4617
4618 pub fn body_info_string(&self, id: u64) -> String {
4620 if let Some(body) = self.rigid_bodies.get(&id) {
4621 format!(
4622 "Body '{}' | Mass: {:.3}kg | Pos: ({:.3},{:.3},{:.3}) | V: {:.3}m/s | Sleep: {}",
4623 body.name,
4624 body.mass,
4625 body.position.x, body.position.y, body.position.z,
4626 body.linear_velocity.length(),
4627 body.sleeping,
4628 )
4629 } else {
4630 "No body selected".to_string()
4631 }
4632 }
4633
4634 pub fn create_demo_scene(&mut self) {
4636 let floor_id = self.add_rigid_body("Floor");
4638 if let Some(body) = self.rigid_bodies.get_mut(&floor_id) {
4639 body.is_static = true;
4640 body.shape_type = RigidBodyShapeType::Box;
4641 body.shape_params.half_extents = Vec3::new(10.0, 0.1, 10.0);
4642 body.position = Vec3::new(0.0, -0.1, 0.0);
4643 body.recompute_inertia();
4644 }
4645 for i in 0..5 {
4647 let box_id = self.add_rigid_body(&format!("Box_{}", i));
4648 if let Some(body) = self.rigid_bodies.get_mut(&box_id) {
4649 body.mass = 1.0;
4650 body.shape_type = RigidBodyShapeType::Box;
4651 body.shape_params.half_extents = Vec3::splat(0.25);
4652 body.position = Vec3::new(0.0, 0.5 + i as f32 * 0.6, 0.0);
4653 body.recompute_inertia();
4654 }
4655 }
4656 let sphere_id = self.add_rigid_body("Sphere");
4658 if let Some(body) = self.rigid_bodies.get_mut(&sphere_id) {
4659 body.mass = 2.0;
4660 body.shape_type = RigidBodyShapeType::Sphere;
4661 body.shape_params.radius = 0.3;
4662 body.position = Vec3::new(2.0, 1.5, 0.0);
4663 body.linear_velocity = Vec3::new(-3.0, 0.0, 0.0);
4664 body.recompute_inertia();
4665 }
4666 }
4667
4668 pub fn raycast(&self, ray_origin: Vec3, ray_dir: Vec3, max_dist: f32) -> Option<(u64, f32, Vec3)> {
4670 let mut closest: Option<(u64, f32, Vec3)> = None;
4671 let rd = ray_dir.normalize();
4672 for (id, _body) in &self.rigid_bodies {
4673 if let Some(shape) = self.collision_shapes.get(id) {
4674 if let Some(t) = ray_aabb_intersect(ray_origin, rd, &shape.aabb) {
4675 if t < max_dist {
4676 if closest.is_none() || t < closest.as_ref().unwrap().1 {
4677 let hit_pt = ray_origin + rd * t;
4678 closest = Some((*id, t, hit_pt));
4679 }
4680 }
4681 }
4682 }
4683 }
4684 closest
4685 }
4686
4687 pub fn select_body_at_ray(&mut self, ray_origin: Vec3, ray_dir: Vec3) -> Option<u64> {
4688 if let Some((id, _, _)) = self.raycast(ray_origin, ray_dir, 1000.0) {
4689 self.selected_body = Some(id);
4690 Some(id)
4691 } else {
4692 self.selected_body = None;
4693 None
4694 }
4695 }
4696
4697 pub fn generate_all_debug_lines(&self) -> Vec<(Vec3, Vec3, Vec4)> {
4698 let mut lines = Vec::new();
4699 if self.debug_overlay.show_colliders || self.debug_overlay.show_velocities {
4701 for body in self.rigid_bodies.values() {
4702 let mut body_lines = self.debug_overlay.generate_body_debug_lines(body);
4703 lines.append(&mut body_lines);
4704 }
4705 }
4706 if self.debug_overlay.show_contacts {
4708 let mut contact_lines = self.contact_visualizer.generate_debug_lines();
4709 lines.append(&mut contact_lines);
4710 }
4711 if self.debug_overlay.show_joints {
4713 let mut joint_lines = self.joint_editor.generate_all_debug_lines();
4714 lines.append(&mut joint_lines);
4715 }
4716 lines
4717 }
4718
4719 pub fn export_description(&self) -> String {
4721 let mut out = String::new();
4722 out.push_str(&format!("PhysicsWorld: {} bodies, {} constraints\n",
4723 self.rigid_bodies.len(), self.constraints.len()));
4724 for (id, body) in &self.rigid_bodies {
4725 out.push_str(&format!(" Body[{}] '{}' mass={:.2} pos=({:.2},{:.2},{:.2})\n",
4726 id, body.name, body.mass, body.position.x, body.position.y, body.position.z));
4727 }
4728 for (id, c) in &self.constraints {
4729 out.push_str(&format!(" Constraint[{}] type={}\n", id, c.name()));
4730 }
4731 out
4732 }
4733
4734 pub fn apply_explosion(&mut self, center: Vec3, force: f32, radius: f32) {
4736 for body in self.rigid_bodies.values_mut() {
4737 if body.is_static { continue; }
4738 let dir = body.position - center;
4739 let dist = dir.length();
4740 if dist < radius && dist > EPSILON {
4741 let falloff = 1.0 - (dist / radius);
4742 let impulse = (dir / dist) * force * falloff;
4743 body.apply_force_at_point(impulse, body.position);
4744 body.wake_up();
4745 }
4746 }
4747 }
4748}
4749
4750pub fn perpendicular_to(v: Vec3) -> Vec3 {
4755 let n = v.normalize();
4756 let candidate = if n.x.abs() < 0.9 { Vec3::X } else { Vec3::Y };
4757 n.cross(candidate).normalize()
4758}
4759
4760pub fn quat_to_axis_angle(q: Quat) -> (Vec3, f32) {
4761 let w = q.w.clamp(-1.0, 1.0);
4762 let angle = 2.0 * w.acos();
4763 let s = (1.0 - w * w).sqrt();
4764 let axis = if s > EPSILON {
4765 Vec3::new(q.x / s, q.y / s, q.z / s)
4766 } else {
4767 Vec3::X
4768 };
4769 (axis, angle)
4770}
4771
4772pub fn decompose_swing_twist(q: Quat, twist_axis: Vec3) -> (Quat, Quat) {
4773 let proj = Vec3::new(q.x, q.y, q.z).dot(twist_axis) * twist_axis;
4774 let twist = Quat::from_xyzw(proj.x, proj.y, proj.z, q.w).normalize();
4775 let swing = q * twist.inverse();
4776 (swing, twist)
4777}
4778
4779pub fn smoothstep(edge0: f32, edge1: f32, x: f32) -> f32 {
4780 let t = ((x - edge0) / (edge1 - edge0 + EPSILON)).clamp(0.0, 1.0);
4781 t * t * (3.0 - 2.0 * t)
4782}
4783
4784pub fn snap_value(v: f32, snap: f32) -> f32 {
4785 if snap < EPSILON { return v; }
4786 (v / snap).round() * snap
4787}
4788
4789pub fn ray_aabb_intersect(origin: Vec3, dir: Vec3, aabb: &Aabb) -> Option<f32> {
4790 let inv_dir = Vec3::new(
4791 if dir.x.abs() > EPSILON { 1.0 / dir.x } else { f32::INFINITY },
4792 if dir.y.abs() > EPSILON { 1.0 / dir.y } else { f32::INFINITY },
4793 if dir.z.abs() > EPSILON { 1.0 / dir.z } else { f32::INFINITY },
4794 );
4795 let t1 = (aabb.min - origin) * inv_dir;
4796 let t2 = (aabb.max - origin) * inv_dir;
4797 let t_min = t1.min(t2);
4798 let t_max = t1.max(t2);
4799 let t_enter = t_min.x.max(t_min.y).max(t_min.z);
4800 let t_exit = t_max.x.min(t_max.y).min(t_max.z);
4801 if t_enter <= t_exit && t_exit >= 0.0 {
4802 Some(t_enter.max(0.0))
4803 } else {
4804 None
4805 }
4806}
4807
4808pub fn angular_correction_impulse(
4810 rot_a: Quat, rot_b: Quat,
4811 inv_inertia_a: Vec3, inv_inertia_b: Vec3,
4812 baumgarte: f32, dt: f32,
4813) -> Vec3 {
4814 let rot_diff = rot_b * rot_a.inverse();
4815 let (axis, angle) = quat_to_axis_angle(rot_diff);
4816 let error = axis * angle * baumgarte / dt;
4817 let inv_sum = inv_inertia_a + inv_inertia_b;
4818 Vec3::new(
4819 if inv_sum.x > EPSILON { error.x / inv_sum.x } else { 0.0 },
4820 if inv_sum.y > EPSILON { error.y / inv_sum.y } else { 0.0 },
4821 if inv_sum.z > EPSILON { error.z / inv_sum.z } else { 0.0 },
4822 )
4823}
4824
4825pub fn compute_compound_com(masses: &[f32], positions: &[Vec3]) -> Vec3 {
4827 let total_mass: f32 = masses.iter().sum();
4828 if total_mass < EPSILON { return Vec3::ZERO; }
4829 let weighted: Vec3 = masses.iter().zip(positions.iter())
4830 .map(|(&m, &p)| p * m)
4831 .fold(Vec3::ZERO, |a, b| a + b);
4832 weighted / total_mass
4833}
4834
4835pub fn compute_compound_inertia(
4837 masses: &[f32],
4838 inertias: &[Mat4],
4839 positions: &[Vec3],
4840 com: Vec3,
4841) -> Mat4 {
4842 let mut result = Mat4::ZERO;
4843 for i in 0..masses.len() {
4844 let shifted = inertia_parallel_axis(inertias[i], masses[i], positions[i] - com);
4845 for col in 0..4 {
4847 let rc = result.col(col);
4848 let sc = shifted.col(col);
4849 let _ = (rc, sc); }
4852 let rc0 = result.col(0) + shifted.col(0);
4854 let rc1 = result.col(1) + shifted.col(1);
4855 let rc2 = result.col(2) + shifted.col(2);
4856 let rc3 = result.col(3) + shifted.col(3);
4857 result = Mat4::from_cols(rc0, rc1, rc2, rc3);
4858 }
4859 result
4860}
4861
4862pub fn baumgarte_position_correction(error: f32, effective_mass: f32, baumgarte: f32, dt: f32) -> f32 {
4864 -baumgarte / dt * error * effective_mass
4865}
4866
4867pub fn relative_velocity(
4869 vel_a: Vec3, omega_a: Vec3, r_a: Vec3,
4870 vel_b: Vec3, omega_b: Vec3, r_b: Vec3,
4871) -> Vec3 {
4872 let v_a = vel_a + omega_a.cross(r_a);
4873 let v_b = vel_b + omega_b.cross(r_b);
4874 v_b - v_a
4875}
4876
4877pub fn restitution_velocity(v_rel_normal: f32, restitution: f32) -> f32 {
4879 if v_rel_normal >= 0.0 { return 0.0; }
4880 -(1.0 + restitution) * v_rel_normal
4881}
4882
4883pub fn friction_impulse(
4885 j_normal: f32, friction_coeff: f32,
4886 tangent_impulse: f32,
4887) -> f32 {
4888 tangent_impulse.clamp(-friction_coeff * j_normal, friction_coeff * j_normal)
4889}
4890
4891pub fn angular_velocity_from_delta_quat(q_prev: Quat, q_curr: Quat, dt: f32) -> Vec3 {
4893 let dq = q_curr * q_prev.inverse();
4894 let (axis, angle) = quat_to_axis_angle(dq);
4895 axis * (angle / dt)
4896}
4897
4898pub fn omega_to_quat_deriv(omega: Vec3, q: Quat) -> Quat {
4900 let ox = omega.x;
4901 let oy = omega.y;
4902 let oz = omega.z;
4903 Quat::from_xyzw(
4904 0.5 * (oy * q.z - oz * q.y + ox * q.w),
4905 0.5 * (oz * q.x - ox * q.z + oy * q.w),
4906 0.5 * (ox * q.y - oy * q.x + oz * q.w),
4907 0.5 * (-ox * q.x - oy * q.y - oz * q.z),
4908 )
4909}
4910
4911pub fn cross_matrix(v: Vec3) -> [[f32; 3]; 3] {
4913 [
4914 [0.0, -v.z, v.y],
4915 [v.z, 0.0, -v.x],
4916 [-v.y, v.x, 0.0],
4917 ]
4918}
4919
4920pub fn mat3_mul_vec(m: [[f32; 3]; 3], v: Vec3) -> Vec3 {
4922 Vec3::new(
4923 m[0][0]*v.x + m[0][1]*v.y + m[0][2]*v.z,
4924 m[1][0]*v.x + m[1][1]*v.y + m[1][2]*v.z,
4925 m[2][0]*v.x + m[2][1]*v.y + m[2][2]*v.z,
4926 )
4927}
4928
4929pub fn mat3_transpose(m: [[f32; 3]; 3]) -> [[f32; 3]; 3] {
4931 [
4932 [m[0][0], m[1][0], m[2][0]],
4933 [m[0][1], m[1][1], m[2][1]],
4934 [m[0][2], m[1][2], m[2][2]],
4935 ]
4936}
4937
4938pub fn mat3_mul(a: [[f32; 3]; 3], b: [[f32; 3]; 3]) -> [[f32; 3]; 3] {
4940 let mut c = [[0.0f32; 3]; 3];
4941 for i in 0..3 {
4942 for j in 0..3 {
4943 for k in 0..3 {
4944 c[i][j] += a[i][k] * b[k][j];
4945 }
4946 }
4947 }
4948 c
4949}
4950
4951pub fn mat3_det(m: [[f32; 3]; 3]) -> f32 {
4953 m[0][0]*(m[1][1]*m[2][2]-m[1][2]*m[2][1])
4954 - m[0][1]*(m[1][0]*m[2][2]-m[1][2]*m[2][0])
4955 + m[0][2]*(m[1][0]*m[2][1]-m[1][1]*m[2][0])
4956}
4957
4958pub fn mat3_inverse(m: [[f32; 3]; 3]) -> Option<[[f32; 3]; 3]> {
4960 let det = mat3_det(m);
4961 if det.abs() < EPSILON { return None; }
4962 let inv_det = 1.0 / det;
4963 let result = [
4964 [
4965 (m[1][1]*m[2][2]-m[1][2]*m[2][1])*inv_det,
4966 (m[0][2]*m[2][1]-m[0][1]*m[2][2])*inv_det,
4967 (m[0][1]*m[1][2]-m[0][2]*m[1][1])*inv_det,
4968 ],
4969 [
4970 (m[1][2]*m[2][0]-m[1][0]*m[2][2])*inv_det,
4971 (m[0][0]*m[2][2]-m[0][2]*m[2][0])*inv_det,
4972 (m[0][2]*m[1][0]-m[0][0]*m[1][2])*inv_det,
4973 ],
4974 [
4975 (m[1][0]*m[2][1]-m[1][1]*m[2][0])*inv_det,
4976 (m[0][1]*m[2][0]-m[0][0]*m[2][1])*inv_det,
4977 (m[0][0]*m[1][1]-m[0][1]*m[1][0])*inv_det,
4978 ],
4979 ];
4980 Some(result)
4981}
4982
4983pub fn gram_schmidt(v0: Vec3, v1: Vec3, v2: Vec3) -> (Vec3, Vec3, Vec3) {
4985 let u0 = v0.normalize();
4986 let u1 = (v1 - v1.dot(u0) * u0).normalize();
4987 let u2 = (v2 - v2.dot(u0) * u0 - v2.dot(u1) * u1).normalize();
4988 (u0, u1, u2)
4989}
4990
4991pub fn young_modulus(surface: SurfaceType) -> f32 {
4997 match surface {
4998 SurfaceType::Metal => 210e9,
4999 SurfaceType::Glass => 70e9,
5000 SurfaceType::Concrete | SurfaceType::WetConcrete => 30e9,
5001 SurfaceType::Wood | SurfaceType::WetWood => 10e9,
5002 SurfaceType::Rubber | SurfaceType::ToughRubber => 0.05e9,
5003 SurfaceType::Foam => 0.001e9,
5004 SurfaceType::Plastic | SurfaceType::HardPlastic => 3e9,
5005 SurfaceType::Granite => 50e9,
5006 SurfaceType::Marble => 50e9,
5007 SurfaceType::Ceramic => 100e9,
5008 SurfaceType::Ice => 9e9,
5009 SurfaceType::Leather => 0.1e9,
5010 SurfaceType::Asphalt | SurfaceType::TarmacRoad => 5e9,
5011 _ => 1e9,
5012 }
5013}
5014
5015pub fn poisson_ratio(surface: SurfaceType) -> f32 {
5017 match surface {
5018 SurfaceType::Metal => 0.3,
5019 SurfaceType::Glass => 0.22,
5020 SurfaceType::Rubber | SurfaceType::ToughRubber => 0.48,
5021 SurfaceType::Concrete => 0.2,
5022 SurfaceType::Wood => 0.35,
5023 SurfaceType::Foam => 0.3,
5024 SurfaceType::Plastic | SurfaceType::HardPlastic => 0.35,
5025 SurfaceType::Ceramic => 0.25,
5026 SurfaceType::Ice => 0.33,
5027 _ => 0.3,
5028 }
5029}
5030
5031pub fn restitution_between(a: SurfaceType, b: SurfaceType) -> f32 {
5033 let ra = material_restitution(a);
5034 let rb = material_restitution(b);
5035 (ra * rb).sqrt() }
5037
5038pub fn material_restitution(s: SurfaceType) -> f32 {
5039 match s {
5040 SurfaceType::Rubber | SurfaceType::ToughRubber => 0.9,
5041 SurfaceType::Metal => 0.4,
5042 SurfaceType::Glass => 0.7,
5043 SurfaceType::Wood => 0.4,
5044 SurfaceType::Concrete => 0.2,
5045 SurfaceType::Ice => 0.1,
5046 SurfaceType::Foam => 0.05,
5047 SurfaceType::Mud | SurfaceType::MudThick => 0.02,
5048 SurfaceType::Sand => 0.1,
5049 SurfaceType::Carpet => 0.05,
5050 SurfaceType::Cork => 0.6,
5051 SurfaceType::Asphalt | SurfaceType::TarmacRoad => 0.15,
5052 SurfaceType::Marble | SurfaceType::Granite => 0.6,
5053 SurfaceType::Ceramic => 0.5,
5054 SurfaceType::Plastic | SurfaceType::HardPlastic => 0.4,
5055 _ => DEFAULT_RESTITUTION,
5056 }
5057}
5058
5059pub fn terminal_velocity(mass: f32, drag_coeff: f32, cross_section_area: f32) -> f32 {
5062 (2.0 * mass * GRAVITY / (AIR_DENSITY * drag_coeff * cross_section_area)).sqrt()
5063}
5064
5065pub fn buoyancy_force(fluid_density: f32, submerged_volume: f32) -> f32 {
5068 fluid_density * submerged_volume * GRAVITY
5069}
5070
5071pub fn drag_force(fluid_density: f32, velocity: f32, drag_coeff: f32, area: f32) -> f32 {
5073 0.5 * fluid_density * velocity * velocity * drag_coeff * area
5074}
5075
5076pub fn impact_force(mass: f32, delta_velocity: f32, contact_time: f32) -> f32 {
5079 mass * delta_velocity / contact_time.max(EPSILON)
5080}
5081
5082pub fn kinetic_energy(mass: f32, vel: Vec3, inertia_diag: Vec3, omega: Vec3) -> f32 {
5084 let linear = 0.5 * mass * vel.length_squared();
5085 let angular = 0.5 * (inertia_diag.x * omega.x * omega.x
5086 + inertia_diag.y * omega.y * omega.y
5087 + inertia_diag.z * omega.z * omega.z);
5088 linear + angular
5089}
5090
5091pub fn potential_energy(mass: f32, height: f32) -> f32 {
5093 mass * GRAVITY * height
5094}
5095
5096pub fn spring_potential_energy(k: f32, extension: f32) -> f32 {
5098 0.5 * k * extension * extension
5099}
5100
5101pub fn hertz_contact_time(mass: f32, eff_young: f32, rel_velocity: f32, radius: f32) -> f32 {
5104 let v = rel_velocity.abs().max(EPSILON);
5105 let factor = (mass / (eff_young * radius.sqrt() * v)).powf(0.4);
5106 2.87 * factor
5107}
5108
5109pub fn effective_young_modulus(e_a: f32, nu_a: f32, e_b: f32, nu_b: f32) -> f32 {
5112 let inv = (1.0 - nu_a * nu_a) / e_a + (1.0 - nu_b * nu_b) / e_b;
5113 1.0 / inv.max(EPSILON)
5114}
5115
5116pub fn hertz_contact_force(eff_young: f32, eff_radius: f32, penetration: f32) -> f32 {
5118 (4.0 / 3.0) * eff_young * eff_radius.sqrt() * penetration.powf(1.5)
5119}
5120
5121pub fn effective_radius(r_a: f32, r_b: f32) -> f32 {
5124 (r_a * r_b) / (r_a + r_b + EPSILON)
5125}
5126
5127#[derive(Debug, Clone)]
5132pub struct ForceAnalyzer {
5133 pub forces: Vec<(Vec3, Vec3, Vec3)>, pub show_resultant: bool,
5135 pub scale: f32,
5136}
5137
5138impl ForceAnalyzer {
5139 pub fn new() -> Self {
5140 Self { forces: Vec::new(), show_resultant: true, scale: 0.01 }
5141 }
5142
5143 pub fn add_force(&mut self, origin: Vec3, force: Vec3, color: Vec3) {
5144 self.forces.push((origin, force * self.scale, Vec3::new(color.x, color.y, color.z)));
5145 }
5146
5147 pub fn resultant(&self) -> Vec3 {
5148 self.forces.iter().fold(Vec3::ZERO, |acc, (_, f, _)| acc + *f)
5149 }
5150
5151 pub fn resultant_torque(&self, about: Vec3) -> Vec3 {
5152 self.forces.iter().fold(Vec3::ZERO, |acc, (origin, force, _)| {
5153 let r = *origin - about;
5154 acc + r.cross(*force)
5155 })
5156 }
5157
5158 pub fn clear(&mut self) { self.forces.clear(); }
5159
5160 pub fn generate_lines(&self) -> Vec<(Vec3, Vec3, Vec4)> {
5161 let mut lines: Vec<(Vec3, Vec3, Vec4)> = self.forces.iter().map(|(orig, f, col)| {
5162 (*orig, *orig + *f, Vec4::new(col.x, col.y, col.z, 1.0))
5163 }).collect();
5164 if self.show_resultant {
5165 let com = Vec3::ZERO;
5166 let res = self.resultant();
5167 lines.push((com, com + res, Vec4::new(1.0, 1.0, 1.0, 1.0)));
5168 }
5169 lines
5170 }
5171}
5172
5173pub fn mesh_signed_volume(triangles: &[(Vec3, Vec3, Vec3)]) -> f32 {
5179 let mut vol = 0.0f32;
5180 for &(a, b, c) in triangles {
5181 vol += a.dot(b.cross(c)) / 6.0;
5182 }
5183 vol
5184}
5185
5186pub fn mesh_surface_area(triangles: &[(Vec3, Vec3, Vec3)]) -> f32 {
5188 triangles.iter().map(|&(a, b, c)| {
5189 (b - a).cross(c - a).length() * 0.5
5190 }).sum()
5191}
5192
5193pub fn mesh_center_of_mass(triangles: &[(Vec3, Vec3, Vec3)]) -> Vec3 {
5195 let mut weighted_sum = Vec3::ZERO;
5196 let mut total_vol = 0.0f32;
5197 for &(a, b, c) in triangles {
5198 let tet_vol = a.dot(b.cross(c)) / 6.0;
5199 let tet_com = (a + b + c) / 4.0; weighted_sum += tet_com * tet_vol;
5201 total_vol += tet_vol;
5202 }
5203 if total_vol.abs() > EPSILON { weighted_sum / total_vol } else { Vec3::ZERO }
5204}
5205
5206pub fn mesh_inertia_tensor(triangles: &[(Vec3, Vec3, Vec3)], density: f32) -> [[f32; 3]; 3] {
5208 let mut i = [[0.0f32; 3]; 3];
5209 for &(a, b, c) in triangles {
5210 let vol = a.dot(b.cross(c)) / 6.0;
5211 let pts = [a, b, c, Vec3::ZERO]; let mut cov = [[0.0f32; 3]; 3];
5214 for p in &pts {
5215 let pv = [p.x, p.y, p.z];
5216 for ii in 0..3 {
5217 for jj in 0..3 {
5218 cov[ii][jj] += pv[ii] * pv[jj];
5219 }
5220 }
5221 }
5222 let scale = density * vol * 0.1; let trace = cov[0][0] + cov[1][1] + cov[2][2];
5224 for ii in 0..3 {
5225 for jj in 0..3 {
5226 let delta = if ii == jj { trace } else { 0.0 };
5227 i[ii][jj] += scale * (delta - cov[ii][jj]);
5228 }
5229 }
5230 }
5231 i
5232}
5233
5234pub struct ConstraintSolver {
5239 pub iterations: u32,
5240 pub baumgarte: f32,
5241 pub slop: f32, pub warm_starting: bool,
5243}
5244
5245impl ConstraintSolver {
5246 pub fn new() -> Self {
5247 Self { iterations: 10, baumgarte: 0.2, slop: 0.001, warm_starting: true }
5248 }
5249
5250 pub fn solve_row(
5252 &self, row: &mut JacobianRow,
5253 vel_a: &mut Vec3, omega_a: &mut Vec3, inv_mass_a: f32, inv_inertia_a: Vec3,
5254 vel_b: &mut Vec3, omega_b: &mut Vec3, inv_mass_b: f32, inv_inertia_b: Vec3,
5255 ) {
5256 let delta = row.solve_velocity(*vel_a, *omega_a, *vel_b, *omega_b);
5257 *vel_a += row.j_lin_a * (delta * inv_mass_a);
5258 *omega_a += row.j_ang_a * delta * inv_inertia_a;
5259 *vel_b += row.j_lin_b * (delta * inv_mass_b);
5260 *omega_b += row.j_ang_b * delta * inv_inertia_b;
5261 }
5262
5263 pub fn solve_rows(
5265 &self, rows: &mut Vec<JacobianRow>,
5266 vel_a: &mut Vec3, omega_a: &mut Vec3, inv_mass_a: f32, inv_inertia_a: Vec3,
5267 vel_b: &mut Vec3, omega_b: &mut Vec3, inv_mass_b: f32, inv_inertia_b: Vec3,
5268 ) {
5269 for _ in 0..self.iterations {
5270 for row in rows.iter_mut() {
5271 self.solve_row(row,
5272 vel_a, omega_a, inv_mass_a, inv_inertia_a,
5273 vel_b, omega_b, inv_mass_b, inv_inertia_b);
5274 }
5275 }
5276 }
5277
5278 pub fn warm_start(
5280 &self, rows: &[JacobianRow],
5281 vel_a: &mut Vec3, omega_a: &mut Vec3, inv_mass_a: f32, inv_inertia_a: Vec3,
5282 vel_b: &mut Vec3, omega_b: &mut Vec3, inv_mass_b: f32, inv_inertia_b: Vec3,
5283 ) {
5284 if !self.warm_starting { return; }
5285 for row in rows {
5286 let lambda = row.lambda;
5287 *vel_a += row.j_lin_a * (lambda * inv_mass_a);
5288 *omega_a += row.j_ang_a * lambda * inv_inertia_a;
5289 *vel_b += row.j_lin_b * (lambda * inv_mass_b);
5290 *omega_b += row.j_ang_b * lambda * inv_inertia_b;
5291 }
5292 }
5293}
5294
5295pub struct StabilityAnalyzer {
5300 pub energy_history: VecDeque<f32>,
5301 pub momentum_history: VecDeque<Vec3>,
5302 pub angular_momentum_history: VecDeque<Vec3>,
5303 pub history_size: usize,
5304}
5305
5306impl StabilityAnalyzer {
5307 pub fn new(history_size: usize) -> Self {
5308 Self {
5309 energy_history: VecDeque::with_capacity(history_size),
5310 momentum_history: VecDeque::with_capacity(history_size),
5311 angular_momentum_history: VecDeque::with_capacity(history_size),
5312 history_size,
5313 }
5314 }
5315
5316 pub fn record(&mut self, bodies: &HashMap<u64, RigidBodyInspector>) {
5317 let mut total_ke = 0.0f32;
5318 let mut total_mom = Vec3::ZERO;
5319 let mut total_ang = Vec3::ZERO;
5320 for body in bodies.values() {
5321 if body.is_static { continue; }
5322 let inertia_diag = Vec3::new(
5323 body.inertia_tensor.col(0).x,
5324 body.inertia_tensor.col(1).y,
5325 body.inertia_tensor.col(2).z,
5326 );
5327 total_ke += kinetic_energy(body.mass, body.linear_velocity, inertia_diag, body.angular_velocity);
5328 total_ke += potential_energy(body.mass, body.position.y);
5329 total_mom += body.linear_velocity * body.mass;
5330 let r = body.position;
5331 total_ang += r.cross(body.linear_velocity * body.mass);
5332 }
5333 if self.energy_history.len() >= self.history_size { self.energy_history.pop_front(); }
5334 if self.momentum_history.len() >= self.history_size { self.momentum_history.pop_front(); }
5335 if self.angular_momentum_history.len() >= self.history_size { self.angular_momentum_history.pop_front(); }
5336 self.energy_history.push_back(total_ke);
5337 self.momentum_history.push_back(total_mom);
5338 self.angular_momentum_history.push_back(total_ang);
5339 }
5340
5341 pub fn energy_variance(&self) -> f32 {
5342 let n = self.energy_history.len();
5343 if n < 2 { return 0.0; }
5344 let mean = self.energy_history.iter().sum::<f32>() / n as f32;
5345 let var = self.energy_history.iter().map(|e| (e - mean) * (e - mean)).sum::<f32>() / n as f32;
5346 var
5347 }
5348
5349 pub fn is_numerically_stable(&self) -> bool {
5350 let var = self.energy_variance();
5351 let mean = if self.energy_history.is_empty() { 1.0 } else {
5352 self.energy_history.iter().sum::<f32>() / self.energy_history.len() as f32
5353 };
5354 mean.abs() < EPSILON || var / mean.abs() < 0.01
5356 }
5357}
5358
5359#[derive(Debug, Clone)]
5364pub struct PhysicsPicker {
5365 pub active: bool,
5366 pub body_id: u64,
5367 pub pick_point_local: Vec3,
5368 pub pick_point_world: Vec3,
5369 pub target_point: Vec3,
5370 pub spring_stiffness: f32,
5371 pub spring_damping: f32,
5372 pub max_force: f32,
5373 pub mouse_ray_origin: Vec3,
5374 pub mouse_ray_dir: Vec3,
5375 pub pick_distance: f32,
5376}
5377
5378impl PhysicsPicker {
5379 pub fn new() -> Self {
5380 Self {
5381 active: false,
5382 body_id: 0,
5383 pick_point_local: Vec3::ZERO,
5384 pick_point_world: Vec3::ZERO,
5385 target_point: Vec3::ZERO,
5386 spring_stiffness: 300.0,
5387 spring_damping: 20.0,
5388 max_force: 500.0,
5389 mouse_ray_origin: Vec3::ZERO,
5390 mouse_ray_dir: Vec3::Z,
5391 pick_distance: 5.0,
5392 }
5393 }
5394
5395 pub fn begin_pick(&mut self, body_id: u64, local_pt: Vec3, world_pt: Vec3, dist: f32) {
5396 self.active = true;
5397 self.body_id = body_id;
5398 self.pick_point_local = local_pt;
5399 self.pick_point_world = world_pt;
5400 self.target_point = world_pt;
5401 self.pick_distance = dist;
5402 }
5403
5404 pub fn end_pick(&mut self) {
5405 self.active = false;
5406 }
5407
5408 pub fn update_target(&mut self, new_ray_origin: Vec3, new_ray_dir: Vec3) {
5409 self.mouse_ray_origin = new_ray_origin;
5410 self.mouse_ray_dir = new_ray_dir;
5411 self.target_point = new_ray_origin + new_ray_dir * self.pick_distance;
5412 }
5413
5414 pub fn compute_force(&self, body: &RigidBodyInspector) -> Vec3 {
5416 if !self.active { return Vec3::ZERO; }
5417 let current_world_pt = body.position + body.orientation * self.pick_point_local;
5418 let error = self.target_point - current_world_pt;
5419 let vel_at_pt = body.linear_velocity
5420 + body.angular_velocity.cross(body.orientation * self.pick_point_local);
5421 let force = error * self.spring_stiffness - vel_at_pt * self.spring_damping;
5422 let f_len = force.length();
5423 if f_len > self.max_force { force * (self.max_force / f_len) } else { force }
5424 }
5425}
5426
5427#[derive(Debug)]
5432pub struct SimulationReplay {
5433 pub snapshots: Vec<PhysicsSnapshot>,
5434 pub current_frame: usize,
5435 pub playback_speed: f32,
5436 pub looping: bool,
5437 pub playing: bool,
5438 pub time_accumulator: f32,
5439}
5440
5441impl SimulationReplay {
5442 pub fn new() -> Self {
5443 Self {
5444 snapshots: Vec::new(),
5445 current_frame: 0,
5446 playback_speed: 1.0,
5447 looping: false,
5448 playing: false,
5449 time_accumulator: 0.0,
5450 }
5451 }
5452
5453 pub fn record_snapshot(&mut self, snap: PhysicsSnapshot) {
5454 self.snapshots.push(snap);
5455 }
5456
5457 pub fn play(&mut self) { self.playing = true; }
5458 pub fn pause(&mut self) { self.playing = false; }
5459 pub fn stop(&mut self) { self.playing = false; self.current_frame = 0; }
5460
5461 pub fn advance(&mut self, dt: f32) -> Option<&PhysicsSnapshot> {
5462 if !self.playing || self.snapshots.is_empty() { return None; }
5463 self.time_accumulator += dt * self.playback_speed;
5464 let frame_dt = 1.0 / 60.0;
5466 while self.time_accumulator >= frame_dt {
5467 self.current_frame += 1;
5468 self.time_accumulator -= frame_dt;
5469 }
5470 if self.current_frame >= self.snapshots.len() {
5471 if self.looping {
5472 self.current_frame = 0;
5473 } else {
5474 self.current_frame = self.snapshots.len().saturating_sub(1);
5475 self.playing = false;
5476 }
5477 }
5478 self.snapshots.get(self.current_frame)
5479 }
5480
5481 pub fn seek_to_time(&mut self, target_time: f32) {
5482 for (i, snap) in self.snapshots.iter().enumerate() {
5483 if snap.time >= target_time {
5484 self.current_frame = i;
5485 return;
5486 }
5487 }
5488 self.current_frame = self.snapshots.len().saturating_sub(1);
5489 }
5490
5491 pub fn duration(&self) -> f32 {
5492 self.snapshots.last().map(|s| s.time).unwrap_or(0.0)
5493 }
5494}
5495
5496#[derive(Debug, Clone)]
5501pub struct FluidParamsPanel {
5502 pub kernel_radius: f32,
5503 pub rest_density: f32,
5504 pub viscosity: f32,
5505 pub pressure_stiffness: f32,
5506 pub surface_tension: f32,
5507 pub gravity_scale: f32,
5508 pub time_scale: f32,
5509 pub max_particles: usize,
5510 pub particle_mass: f32,
5511 pub domain_size: Vec3,
5512 pub domain_offset: Vec3,
5513 pub boundary_restitution: f32,
5514 pub display_radius_scale: f32,
5515 pub color_by_velocity: bool,
5516 pub color_low_vel: Vec4,
5517 pub color_high_vel: Vec4,
5518 pub color_velocity_scale: f32,
5519}
5520
5521impl Default for FluidParamsPanel {
5522 fn default() -> Self {
5523 Self {
5524 kernel_radius: 0.15,
5525 rest_density: 1000.0,
5526 viscosity: 0.1,
5527 pressure_stiffness: 200.0,
5528 surface_tension: 0.0728,
5529 gravity_scale: 1.0,
5530 time_scale: 1.0,
5531 max_particles: 4096,
5532 particle_mass: 0.02,
5533 domain_size: Vec3::splat(5.0),
5534 domain_offset: Vec3::ZERO,
5535 boundary_restitution: 0.1,
5536 display_radius_scale: 1.5,
5537 color_by_velocity: true,
5538 color_low_vel: Vec4::new(0.0, 0.2, 0.8, 0.8),
5539 color_high_vel: Vec4::new(0.8, 0.8, 1.0, 0.9),
5540 color_velocity_scale: 5.0,
5541 }
5542 }
5543}
5544
5545impl FluidParamsPanel {
5546 pub fn apply_to_sim(&self, sim: &mut FluidSimulation) {
5547 sim.kernel_radius = self.kernel_radius;
5548 sim.rest_density = self.rest_density;
5549 sim.viscosity = self.viscosity;
5550 sim.pressure_stiffness = self.pressure_stiffness;
5551 sim.surface_tension = self.surface_tension;
5552 sim.gravity = Vec3::new(0.0, -GRAVITY * self.gravity_scale, 0.0);
5553 sim.time_scale = self.time_scale;
5554 sim.restitution = self.boundary_restitution;
5555 let half = self.domain_size * 0.5;
5556 sim.domain_min = self.domain_offset - half;
5557 sim.domain_max = self.domain_offset + half;
5558 }
5559
5560 pub fn particle_color(&self, velocity: Vec3) -> Vec4 {
5561 if !self.color_by_velocity {
5562 return self.color_low_vel;
5563 }
5564 let t = (velocity.length() / self.color_velocity_scale).clamp(0.0, 1.0);
5565 Vec4::new(
5566 self.color_low_vel.x + (self.color_high_vel.x - self.color_low_vel.x) * t,
5567 self.color_low_vel.y + (self.color_high_vel.y - self.color_low_vel.y) * t,
5568 self.color_low_vel.z + (self.color_high_vel.z - self.color_low_vel.z) * t,
5569 self.color_low_vel.w + (self.color_high_vel.w - self.color_low_vel.w) * t,
5570 )
5571 }
5572}
5573
5574#[derive(Debug, Clone)]
5579pub struct DestructionPanel {
5580 pub num_fragments: usize,
5581 pub seed: u64,
5582 pub material_strength: f32,
5583 pub fracture_on_impact: bool,
5584 pub impact_threshold: f32,
5585 pub propagation_speed: f32,
5586 pub debris_lifetime: f32,
5587 pub debris_gravity_scale: f32,
5588 pub debris_air_resistance: f32,
5589 pub sound_on_fracture: bool,
5590 pub particle_effect_on_fracture: bool,
5591 pub fragment_density: f32,
5592 pub auto_remove_small_debris: bool,
5593 pub min_debris_mass: f32,
5594}
5595
5596impl Default for DestructionPanel {
5597 fn default() -> Self {
5598 Self {
5599 num_fragments: 20,
5600 seed: 42,
5601 material_strength: 1e6,
5602 fracture_on_impact: true,
5603 impact_threshold: 1000.0,
5604 propagation_speed: 5000.0,
5605 debris_lifetime: 5.0,
5606 debris_gravity_scale: 1.0,
5607 debris_air_resistance: 0.1,
5608 sound_on_fracture: true,
5609 particle_effect_on_fracture: true,
5610 fragment_density: CONCRETE_DENSITY,
5611 auto_remove_small_debris: true,
5612 min_debris_mass: 0.01,
5613 }
5614 }
5615}
5616
5617#[derive(Debug, Clone)]
5622pub struct ClothParamsPanel {
5623 pub grid_width: usize,
5624 pub grid_height: usize,
5625 pub cell_size: f32,
5626 pub total_mass: f32,
5627 pub stretch_stiffness: f32,
5628 pub shear_stiffness: f32,
5629 pub bend_stiffness: f32,
5630 pub gravity_scale: f32,
5631 pub wind_speed: f32,
5632 pub wind_direction: Vec3,
5633 pub wind_turbulence: f32,
5634 pub drag: f32,
5635 pub self_collision: bool,
5636 pub tearing: bool,
5637 pub tear_threshold: f32,
5638 pub iterations: u32,
5639 pub thickness: f32,
5640 pub visualize_springs: bool,
5641 pub spring_color_stretch: Vec4,
5642 pub spring_color_shear: Vec4,
5643 pub spring_color_bend: Vec4,
5644 pub visualize_normals: bool,
5645}
5646
5647impl Default for ClothParamsPanel {
5648 fn default() -> Self {
5649 Self {
5650 grid_width: 20,
5651 grid_height: 20,
5652 cell_size: 0.1,
5653 total_mass: 0.5,
5654 stretch_stiffness: 1000.0,
5655 shear_stiffness: 500.0,
5656 bend_stiffness: 100.0,
5657 gravity_scale: 1.0,
5658 wind_speed: 0.0,
5659 wind_direction: Vec3::new(1.0, 0.0, 0.0),
5660 wind_turbulence: 0.1,
5661 drag: 0.05,
5662 self_collision: true,
5663 tearing: false,
5664 tear_threshold: 3.0,
5665 iterations: 10,
5666 thickness: 0.001,
5667 visualize_springs: false,
5668 spring_color_stretch: Vec4::new(0.2, 0.8, 0.2, 1.0),
5669 spring_color_shear: Vec4::new(0.8, 0.8, 0.2, 1.0),
5670 spring_color_bend: Vec4::new(0.8, 0.2, 0.2, 1.0),
5671 visualize_normals: false,
5672 }
5673 }
5674}
5675
5676impl ClothParamsPanel {
5677 pub fn apply_to_sim(&self, sim: &mut ClothSimulation) {
5678 sim.stretch_stiffness = self.stretch_stiffness;
5679 sim.shear_stiffness = self.shear_stiffness;
5680 sim.bend_stiffness = self.bend_stiffness;
5681 sim.gravity = Vec3::new(0.0, -GRAVITY * self.gravity_scale, 0.0);
5682 sim.wind_velocity = self.wind_direction.normalize_or_zero() * self.wind_speed;
5683 sim.wind_turbulence = self.wind_turbulence;
5684 sim.drag_coefficient = self.drag;
5685 sim.self_collision_enabled = self.self_collision;
5686 sim.tearing_enabled = self.tearing;
5687 sim.global_tear_threshold = self.tear_threshold;
5688 sim.iterations = self.iterations;
5689 sim.thickness = self.thickness;
5690 for spring in &mut sim.springs {
5692 match spring.spring_type {
5693 ClothSpringType::Stretch => {
5694 spring.stiffness = self.stretch_stiffness;
5695 if self.tearing {
5696 spring.tear_threshold = spring.rest_length * self.tear_threshold;
5697 }
5698 }
5699 ClothSpringType::Shear => spring.stiffness = self.shear_stiffness,
5700 ClothSpringType::Bend => spring.stiffness = self.bend_stiffness,
5701 }
5702 }
5703 }
5704}
5705
5706#[derive(Debug, Clone)]
5711pub struct VehicleParamsPanel {
5712 pub mass: f32,
5713 pub wheelbase: f32,
5714 pub track_width: f32,
5715 pub com_height: f32,
5716 pub engine_type: EngineType,
5717 pub max_steering_angle: f32,
5718 pub brake_force_total: f32,
5719 pub brake_bias_front: f32,
5720 pub abs: bool,
5721 pub tcs: bool,
5722 pub esc: bool,
5723 pub diff_type_front: String,
5724 pub diff_type_rear: String,
5725 pub aero_drag: f32,
5726 pub downforce: f32,
5727 pub show_suspension: bool,
5728 pub show_tire_forces: bool,
5729 pub show_rpm: bool,
5730}
5731
5732#[derive(Debug, Clone, PartialEq)]
5733pub enum EngineType {
5734 PetrolSport,
5735 PetrolTurbo,
5736 DieselTruck,
5737 Electric,
5738 Custom,
5739}
5740
5741impl Default for VehicleParamsPanel {
5742 fn default() -> Self {
5743 Self {
5744 mass: 1500.0,
5745 wheelbase: 2.7,
5746 track_width: 1.6,
5747 com_height: 0.5,
5748 engine_type: EngineType::PetrolSport,
5749 max_steering_angle: 35.0,
5750 brake_force_total: 8000.0,
5751 brake_bias_front: 0.6,
5752 abs: true,
5753 tcs: true,
5754 esc: false,
5755 diff_type_front: "Open".to_string(),
5756 diff_type_rear: "LSD".to_string(),
5757 aero_drag: 0.6,
5758 downforce: 0.0,
5759 show_suspension: true,
5760 show_tire_forces: false,
5761 show_rpm: true,
5762 }
5763 }
5764}
5765
5766#[derive(Debug, Clone)]
5771pub struct MaterialEditorPanel {
5772 pub selected_material: Option<String>,
5773 pub preview_sphere_radius: f32,
5774 pub drop_height: f32,
5775 pub floor_material: String,
5776 pub show_bounce_preview: bool,
5777 pub filter_text: String,
5778 pub sort_by: MaterialSortBy,
5779}
5780
5781#[derive(Debug, Clone, PartialEq)]
5782pub enum MaterialSortBy {
5783 Name,
5784 Friction,
5785 Restitution,
5786 Density,
5787}
5788
5789impl MaterialEditorPanel {
5790 pub fn new() -> Self {
5791 Self {
5792 selected_material: None,
5793 preview_sphere_radius: 0.1,
5794 drop_height: 2.0,
5795 floor_material: "Concrete".to_string(),
5796 show_bounce_preview: true,
5797 filter_text: String::new(),
5798 sort_by: MaterialSortBy::Name,
5799 }
5800 }
5801
5802 pub fn compute_bounce_height(&self, restitution: f32) -> f32 {
5804 self.drop_height * restitution * restitution
5806 }
5807
5808 pub fn filtered_materials<'a>(&'a self, library: &'a HashMap<String, PhysicsMaterial>) -> Vec<&'a PhysicsMaterial> {
5810 let mut mats: Vec<&PhysicsMaterial> = library.values()
5811 .filter(|m| m.name.to_lowercase().contains(&self.filter_text.to_lowercase()))
5812 .collect();
5813 match self.sort_by {
5814 MaterialSortBy::Name => mats.sort_by(|a, b| a.name.cmp(&b.name)),
5815 MaterialSortBy::Friction => mats.sort_by(|a, b| b.static_friction.partial_cmp(&a.static_friction).unwrap_or(std::cmp::Ordering::Equal)),
5816 MaterialSortBy::Restitution => mats.sort_by(|a, b| b.restitution.partial_cmp(&a.restitution).unwrap_or(std::cmp::Ordering::Equal)),
5817 MaterialSortBy::Density => mats.sort_by(|a, b| b.density.partial_cmp(&a.density).unwrap_or(std::cmp::Ordering::Equal)),
5818 }
5819 mats
5820 }
5821}
5822
5823#[derive(Debug, Clone)]
5828pub struct RagdollPanel {
5829 pub selected_bone: Option<String>,
5830 pub muscle_tone_global: f32,
5831 pub blend_to_animation: bool,
5832 pub blend_time: f32,
5833 pub show_bone_bodies: bool,
5834 pub show_joint_axes: bool,
5835 pub show_muscle_forces: bool,
5836 pub impact_threshold: f32,
5837 pub collapse_on_impact: bool,
5838 pub get_up_enabled: bool,
5839 pub get_up_threshold_velocity: f32,
5840 pub get_up_blend_time: f32,
5841}
5842
5843impl Default for RagdollPanel {
5844 fn default() -> Self {
5845 Self {
5846 selected_bone: None,
5847 muscle_tone_global: 0.0,
5848 blend_to_animation: false,
5849 blend_time: 0.5,
5850 show_bone_bodies: true,
5851 show_joint_axes: true,
5852 show_muscle_forces: false,
5853 impact_threshold: 10.0,
5854 collapse_on_impact: true,
5855 get_up_enabled: true,
5856 get_up_threshold_velocity: 0.5,
5857 get_up_blend_time: 1.0,
5858 }
5859 }
5860}
5861
5862#[derive(Debug, Clone, Default)]
5867pub struct PhysicsStats {
5868 pub active_bodies: usize,
5869 pub sleeping_bodies: usize,
5870 pub static_bodies: usize,
5871 pub active_constraints: usize,
5872 pub contact_pairs: usize,
5873 pub cloth_particles: usize,
5874 pub fluid_particles: usize,
5875 pub debris_particles: usize,
5876 pub solver_iterations_used: u32,
5877 pub step_time_us: u64,
5878 pub broadphase_pairs: usize,
5879 pub narrowphase_contacts: usize,
5880}
5881
5882impl PhysicsStats {
5883 pub fn gather(editor: &PhysicsEditor) -> Self {
5884 let mut stats = PhysicsStats::default();
5885 for body in editor.rigid_bodies.values() {
5886 if body.is_static { stats.static_bodies += 1; }
5887 else if body.sleeping { stats.sleeping_bodies += 1; }
5888 else { stats.active_bodies += 1; }
5889 }
5890 stats.active_constraints = editor.constraints.len();
5891 stats.contact_pairs = editor.contact_visualizer.contacts.len();
5892 for cloth in editor.cloth_sims.values() {
5893 stats.cloth_particles += cloth.particles.len();
5894 }
5895 for fluid in editor.fluid_sims.values() {
5896 stats.fluid_particles += fluid.particles.len();
5897 }
5898 for frac in editor.fracture_systems.values() {
5899 stats.debris_particles += frac.debris_particles.len();
5900 }
5901 stats.solver_iterations_used = editor.world_settings.solver_iterations;
5902 stats
5903 }
5904
5905 pub fn summary(&self) -> String {
5906 format!(
5907 "Bodies: {} active / {} sleeping / {} static | Constraints: {} | Contacts: {} | SPH: {} | Cloth: {}",
5908 self.active_bodies, self.sleeping_bodies, self.static_bodies,
5909 self.active_constraints,
5910 self.contact_pairs,
5911 self.fluid_particles,
5912 self.cloth_particles,
5913 )
5914 }
5915}
5916
5917#[derive(Debug, Clone)]
5922pub struct BvhNode {
5923 pub aabb: Aabb,
5924 pub body_id: Option<u64>,
5925 pub left: Option<usize>,
5926 pub right: Option<usize>,
5927 pub parent: Option<usize>,
5928 pub height: i32,
5929}
5930
5931impl BvhNode {
5932 pub fn new_leaf(aabb: Aabb, body_id: u64) -> Self {
5933 Self { aabb, body_id: Some(body_id), left: None, right: None, parent: None, height: 0 }
5934 }
5935 pub fn is_leaf(&self) -> bool { self.left.is_none() }
5936}
5937
5938pub struct DynamicAabbTree {
5939 pub nodes: Vec<BvhNode>,
5940 pub root: Option<usize>,
5941 pub free_list: Vec<usize>,
5942}
5943
5944impl DynamicAabbTree {
5945 pub fn new() -> Self {
5946 Self { nodes: Vec::new(), root: None, free_list: Vec::new() }
5947 }
5948
5949 pub fn alloc_node(&mut self) -> usize {
5950 if let Some(idx) = self.free_list.pop() { return idx; }
5951 let idx = self.nodes.len();
5952 self.nodes.push(BvhNode {
5953 aabb: Aabb::new(Vec3::ZERO, Vec3::ZERO),
5954 body_id: None,
5955 left: None,
5956 right: None,
5957 parent: None,
5958 height: 0,
5959 });
5960 idx
5961 }
5962
5963 pub fn insert(&mut self, body_id: u64, aabb: Aabb) {
5964 let leaf = self.alloc_node();
5965 self.nodes[leaf] = BvhNode::new_leaf(aabb, body_id);
5966 if self.root.is_none() {
5967 self.root = Some(leaf);
5968 return;
5969 }
5970 let sibling = self.find_best_sibling(leaf);
5972 let old_parent = self.nodes[sibling].parent;
5973 let new_parent = self.alloc_node();
5974 let merged_aabb = self.nodes[sibling].aabb.merged(&self.nodes[leaf].aabb);
5975 self.nodes[new_parent].aabb = merged_aabb;
5976 self.nodes[new_parent].parent = old_parent;
5977 self.nodes[new_parent].left = Some(sibling);
5978 self.nodes[new_parent].right = Some(leaf);
5979 self.nodes[sibling].parent = Some(new_parent);
5980 self.nodes[leaf].parent = Some(new_parent);
5981 if let Some(op) = old_parent {
5982 if self.nodes[op].left == Some(sibling) {
5983 self.nodes[op].left = Some(new_parent);
5984 } else {
5985 self.nodes[op].right = Some(new_parent);
5986 }
5987 } else {
5988 self.root = Some(new_parent);
5989 }
5990 let mut current = Some(new_parent);
5992 while let Some(idx) = current {
5993 if let (Some(l), Some(r)) = (self.nodes[idx].left, self.nodes[idx].right) {
5994 let la = self.nodes[l].aabb.clone();
5995 let ra = self.nodes[r].aabb.clone();
5996 self.nodes[idx].aabb = la.merged(&ra);
5997 self.nodes[idx].height = 1 + self.nodes[l].height.max(self.nodes[r].height);
5998 }
5999 current = self.nodes[idx].parent;
6000 }
6001 }
6002
6003 fn find_best_sibling(&self, leaf: usize) -> usize {
6004 self.root.unwrap_or(leaf)
6006 }
6007
6008 pub fn query_aabb(&self, query: &Aabb) -> Vec<u64> {
6009 let mut result = Vec::new();
6010 let mut stack = Vec::new();
6011 if let Some(root) = self.root { stack.push(root); }
6012 while let Some(idx) = stack.pop() {
6013 let node = &self.nodes[idx];
6014 if !node.aabb.intersects(query) { continue; }
6015 if node.is_leaf() {
6016 if let Some(id) = node.body_id { result.push(id); }
6017 } else {
6018 if let Some(l) = node.left { stack.push(l); }
6019 if let Some(r) = node.right { stack.push(r); }
6020 }
6021 }
6022 result
6023 }
6024}
6025
6026pub fn run_physics_editor_smoke_test() -> bool {
6031 let mut editor = PhysicsEditor::new();
6032 let id = editor.add_rigid_body("TestBox");
6034 if let Some(body) = editor.rigid_bodies.get_mut(&id) {
6035 body.mass = 5.0;
6036 body.shape_type = RigidBodyShapeType::Box;
6037 body.shape_params.half_extents = Vec3::new(1.0, 0.5, 0.5);
6038 body.recompute_inertia();
6039 }
6040 let cloth_id = editor.add_cloth(10, 10, 0.1);
6042 if let Some(cloth) = editor.cloth_sims.get_mut(&cloth_id) {
6043 cloth.pin_vertex(0);
6044 cloth.pin_vertex(9);
6045 }
6046 let fluid_id = editor.add_fluid(0.1, 1000.0);
6048 if let Some(fluid) = editor.fluid_sims.get_mut(&fluid_id) {
6049 fluid.spawn_block(Vec3::ZERO, Vec3::splat(0.5), 0.1, 0.02);
6050 }
6051 let _veh_id = editor.add_vehicle(1500.0, 2.7, 1.6);
6053 let frac_id = editor.add_fracture_system(Vec3::ZERO, Vec3::ONE, 10, 1234);
6055 if let Some(frac) = editor.fracture_systems.get_mut(&frac_id) {
6056 frac.material_strength = 1e6;
6057 frac.accumulate_stress(0, 2e6, 0.01);
6058 let fractured = frac.check_fracture();
6059 let frac_ref = editor.fracture_systems.get_mut(&frac_id).unwrap();
6060 if !fractured.is_empty() {
6061 let f2 = fractured.clone();
6062 frac_ref.propagate_cracks(&f2);
6063 }
6064 }
6065 let i_box = inertia_tensor_box(1.0, Vec3::splat(0.5));
6067 let i_sphere = inertia_tensor_sphere(1.0, 0.5);
6068 let i_capsule = inertia_tensor_capsule(1.0, 0.25, 0.5);
6069 let _i_cylinder = inertia_tensor_cylinder(1.0, 0.25, 0.5);
6070 assert!(i_box.col(0).x > 0.0);
6071 assert!(i_sphere.col(0).x > 0.0);
6072 assert!(i_capsule.col(0).x > 0.0);
6073 let (b, c, d, e) = pacejka_dry_asphalt();
6075 let f = pacejka_magic_formula(5.0_f32.to_radians(), b, c, d, e);
6076 assert!(f.abs() > 0.0);
6077 let r = Vec3::new(0.05, 0.0, 0.0);
6079 let h = 0.1f32;
6080 let k = sph_kernel_poly6(r.length_squared(), h);
6081 assert!(k > 0.0);
6082 let aabb = Aabb::from_points(&[Vec3::ZERO, Vec3::ONE]);
6084 assert!(aabb.contains_point(Vec3::splat(0.5)));
6085 let pts: Vec<Vec3> = (0..20).map(|i| Vec3::new(i as f32 * 0.1, 0.0, 0.0)).collect();
6087 let obb = fit_obb_pca(&pts);
6088 assert!(obb.half_extents.length() > 0.0);
6089 let (outer, inner) = ackermann_steering(2.7, 1.6, 0.3);
6091 assert!(inner.abs() > outer.abs()); let lib = build_material_library();
6094 assert!(lib.len() >= 40);
6095 let mut bvh = DynamicAabbTree::new();
6097 bvh.insert(1, Aabb::new(Vec3::ZERO, Vec3::ONE));
6098 bvh.insert(2, Aabb::new(Vec3::new(0.5, 0.5, 0.5), Vec3::new(1.5, 1.5, 1.5)));
6099 let hits = bvh.query_aabb(&Aabb::new(Vec3::splat(0.4), Vec3::splat(0.6)));
6100 assert!(!hits.is_empty());
6101 let mut stability = StabilityAnalyzer::new(60);
6103 stability.record(&editor.rigid_bodies);
6104 editor.ragdoll_editor.build_humanoid_skeleton();
6106 assert!(editor.ragdoll_editor.bones.len() > 10);
6107 assert!(editor.ragdoll_editor.joints.len() > 5);
6108 editor.play();
6110 editor.step(1.0 / 60.0);
6111 editor.step(1.0 / 60.0);
6112 editor.step(1.0 / 60.0);
6113 true
6114}
6115
6116#[derive(Debug, Clone)]
6121pub struct PhysicsEditorLayout {
6122 pub show_rigid_body_panel: bool,
6123 pub show_constraint_panel: bool,
6124 pub show_joint_editor: bool,
6125 pub show_cloth_panel: bool,
6126 pub show_fluid_panel: bool,
6127 pub show_destruction_panel: bool,
6128 pub show_ragdoll_panel: bool,
6129 pub show_vehicle_panel: bool,
6130 pub show_collision_shape_panel: bool,
6131 pub show_material_panel: bool,
6132 pub show_contact_panel: bool,
6133 pub show_stats: bool,
6134 pub show_world_settings: bool,
6135 pub panel_width: f32,
6136 pub panel_height: f32,
6137 pub selected_tab: PhysicsTab,
6138}
6139
6140#[derive(Debug, Clone, PartialEq)]
6141pub enum PhysicsTab {
6142 RigidBodies,
6143 Constraints,
6144 Cloth,
6145 Fluid,
6146 Destruction,
6147 Ragdoll,
6148 Vehicles,
6149 Shapes,
6150 Materials,
6151 Contacts,
6152 Settings,
6153}
6154
6155impl Default for PhysicsEditorLayout {
6156 fn default() -> Self {
6157 Self {
6158 show_rigid_body_panel: true,
6159 show_constraint_panel: true,
6160 show_joint_editor: true,
6161 show_cloth_panel: false,
6162 show_fluid_panel: false,
6163 show_destruction_panel: false,
6164 show_ragdoll_panel: false,
6165 show_vehicle_panel: false,
6166 show_collision_shape_panel: true,
6167 show_material_panel: true,
6168 show_contact_panel: true,
6169 show_stats: true,
6170 show_world_settings: true,
6171 panel_width: 320.0,
6172 panel_height: 600.0,
6173 selected_tab: PhysicsTab::RigidBodies,
6174 }
6175 }
6176}
6177
6178impl PhysicsEditorLayout {
6179 pub fn tab_label(&self, tab: &PhysicsTab) -> &'static str {
6180 match tab {
6181 PhysicsTab::RigidBodies => "Rigid Bodies",
6182 PhysicsTab::Constraints => "Constraints",
6183 PhysicsTab::Cloth => "Cloth",
6184 PhysicsTab::Fluid => "Fluid",
6185 PhysicsTab::Destruction => "Destruction",
6186 PhysicsTab::Ragdoll => "Ragdoll",
6187 PhysicsTab::Vehicles => "Vehicles",
6188 PhysicsTab::Shapes => "Shapes",
6189 PhysicsTab::Materials => "Materials",
6190 PhysicsTab::Contacts => "Contacts",
6191 PhysicsTab::Settings => "Settings",
6192 }
6193 }
6194}
6195
6196#[derive(Debug, Clone)]
6201pub struct WindField {
6202 pub base_velocity: Vec3,
6203 pub gust_strength: f32,
6204 pub gust_frequency: f32,
6205 pub gust_duration: f32,
6206 pub turbulence_scale: f32,
6207 pub turbulence_frequency: f32,
6208 pub enabled: bool,
6209 pub altitude_gradient: f32, }
6211
6212impl WindField {
6213 pub fn new(base: Vec3) -> Self {
6214 Self {
6215 base_velocity: base,
6216 gust_strength: 5.0,
6217 gust_frequency: 0.2,
6218 gust_duration: 2.0,
6219 turbulence_scale: 1.0,
6220 turbulence_frequency: 0.5,
6221 enabled: true,
6222 altitude_gradient: 0.1,
6223 }
6224 }
6225
6226 pub fn velocity_at(&self, position: Vec3, time: f32) -> Vec3 {
6227 if !self.enabled { return Vec3::ZERO; }
6228 let mut v = self.base_velocity;
6229 let alt_factor = 1.0 + position.y * self.altitude_gradient;
6231 v *= alt_factor;
6232 let gust_phase = time * self.gust_frequency * TWO_PI;
6234 let gust = self.base_velocity.normalize_or_zero() * (gust_phase.sin() * 0.5 + 0.5) * self.gust_strength;
6235 v += gust;
6236 let tx = (position.x * 0.3 + time * self.turbulence_frequency).sin();
6238 let ty = (position.y * 0.4 + time * self.turbulence_frequency * 1.3).cos();
6239 let tz = (position.z * 0.25 + time * self.turbulence_frequency * 0.7).sin();
6240 v += Vec3::new(tx, ty * 0.3, tz) * self.turbulence_scale;
6241 v
6242 }
6243
6244 pub fn force_on_particle(&self, position: Vec3, time: f32, mass: f32, drag_area: f32) -> Vec3 {
6245 let wind = self.velocity_at(position, time);
6246 let drag = drag_force(AIR_DENSITY, wind.length(), 1.0, drag_area);
6247 wind.normalize_or_zero() * drag
6248 }
6249}
6250
6251pub fn heightfield_closest_point(hf: &HeightfieldShape, query: Vec3) -> Vec3 {
6256 let h = hf.height_at_world(query.x, query.z);
6257 Vec3::new(query.x.clamp(0.0, (hf.width - 1) as f32 * hf.scale.x),
6258 h,
6259 query.z.clamp(0.0, (hf.depth - 1) as f32 * hf.scale.z))
6260}
6261
6262pub fn sphere_heightfield_contact(center: Vec3, radius: f32, hf: &HeightfieldShape) -> Option<(Vec3, Vec3, f32)> {
6263 let closest = heightfield_closest_point(hf, center);
6264 let h = hf.height_at_world(center.x, center.z);
6265 let ground_y = h;
6266 let depth = radius - (center.y - ground_y);
6267 if depth > 0.0 {
6268 let contact_pt = Vec3::new(center.x, ground_y, center.z);
6269 Some((contact_pt, Vec3::Y, depth))
6270 } else {
6271 None
6272 }
6273}
6274
6275pub fn transform_rigid_body(body: &mut RigidBodyInspector, translation: Vec3, rotation: Quat) {
6280 body.position = translation;
6281 body.orientation = rotation;
6282 if !body.sleeping { body.wake_up(); }
6283}
6284
6285pub fn set_rigid_body_velocity(body: &mut RigidBodyInspector, linear: Vec3, angular: Vec3) {
6286 body.linear_velocity = linear;
6287 body.angular_velocity = angular;
6288 body.wake_up();
6289}
6290
6291pub fn world_to_screen(world_pos: Vec3, view_proj: Mat4, screen_width: f32, screen_height: f32) -> Vec2 {
6296 let clip = view_proj * Vec4::new(world_pos.x, world_pos.y, world_pos.z, 1.0);
6297 if clip.w.abs() < EPSILON { return Vec2::ZERO; }
6298 let ndc = Vec3::new(clip.x / clip.w, clip.y / clip.w, clip.z / clip.w);
6299 Vec2::new(
6300 (ndc.x * 0.5 + 0.5) * screen_width,
6301 (0.5 - ndc.y * 0.5) * screen_height,
6302 )
6303}
6304
6305pub fn screen_to_world_ray(
6306 screen_x: f32, screen_y: f32,
6307 screen_width: f32, screen_height: f32,
6308 inv_view_proj: Mat4,
6309) -> (Vec3, Vec3) {
6310 let ndc_x = (screen_x / screen_width) * 2.0 - 1.0;
6311 let ndc_y = 1.0 - (screen_y / screen_height) * 2.0;
6312 let near = inv_view_proj * Vec4::new(ndc_x, ndc_y, -1.0, 1.0);
6313 let far = inv_view_proj * Vec4::new(ndc_x, ndc_y, 1.0, 1.0);
6314 let origin = Vec3::new(near.x / near.w, near.y / near.w, near.z / near.w);
6315 let far_pt = Vec3::new(far.x / far.w, far.y / far.w, far.z / far.w);
6316 let direction = (far_pt - origin).normalize();
6317 (origin, direction)
6318}
6319
6320pub fn resolve_collision_impulse(
6325 body_a: &mut RigidBodyInspector,
6326 body_b: &mut RigidBodyInspector,
6327 contact: &ContactPoint,
6328 restitution: f32,
6329) {
6330 let ra = contact.world_position - body_a.position;
6331 let rb = contact.world_position - body_b.position;
6332 let v_a = body_a.linear_velocity + body_a.angular_velocity.cross(ra);
6333 let v_b = body_b.linear_velocity + body_b.angular_velocity.cross(rb);
6334 let v_rel = v_b - v_a;
6335 let v_rel_n = v_rel.dot(contact.world_normal);
6336 if v_rel_n > 0.0 { return; } let inv_ma = if body_a.is_static { 0.0 } else { 1.0 / body_a.mass };
6338 let inv_mb = if body_b.is_static { 0.0 } else { 1.0 / body_b.mass };
6339 let i_a = Vec3::new(
6340 body_a.inertia_tensor.col(0).x,
6341 body_a.inertia_tensor.col(1).y,
6342 body_a.inertia_tensor.col(2).z,
6343 );
6344 let i_b = Vec3::new(
6345 body_b.inertia_tensor.col(0).x,
6346 body_b.inertia_tensor.col(1).y,
6347 body_b.inertia_tensor.col(2).z,
6348 );
6349 let inv_ia = Vec3::new(
6350 if i_a.x > EPSILON { 1.0 / i_a.x } else { 0.0 },
6351 if i_a.y > EPSILON { 1.0 / i_a.y } else { 0.0 },
6352 if i_a.z > EPSILON { 1.0 / i_a.z } else { 0.0 },
6353 );
6354 let inv_ib = Vec3::new(
6355 if i_b.x > EPSILON { 1.0 / i_b.x } else { 0.0 },
6356 if i_b.y > EPSILON { 1.0 / i_b.y } else { 0.0 },
6357 if i_b.z > EPSILON { 1.0 / i_b.z } else { 0.0 },
6358 );
6359 let n = contact.world_normal;
6360 let ra_cross_n = ra.cross(n);
6361 let rb_cross_n = rb.cross(n);
6362 let denom = inv_ma + inv_mb
6363 + ra_cross_n.dot(inv_ia * ra_cross_n)
6364 + rb_cross_n.dot(inv_ib * rb_cross_n);
6365 if denom < EPSILON { return; }
6366 let j = -(1.0 + restitution) * v_rel_n / denom;
6367 let impulse = n * j;
6368 if !body_a.is_static {
6369 body_a.linear_velocity -= impulse * inv_ma;
6370 body_a.angular_velocity -= inv_ia * ra.cross(impulse);
6371 body_a.wake_up();
6372 }
6373 if !body_b.is_static {
6374 body_b.linear_velocity += impulse * inv_mb;
6375 body_b.angular_velocity += inv_ib * rb.cross(impulse);
6376 body_b.wake_up();
6377 }
6378}
6379
6380#[derive(Debug, Clone)]
6385pub struct TriggerVolume {
6386 pub id: u64,
6387 pub name: String,
6388 pub aabb: Aabb,
6389 pub bodies_inside: HashSet<u64>,
6390 pub on_enter_events: Vec<String>,
6391 pub on_exit_events: Vec<String>,
6392 pub active: bool,
6393}
6394
6395impl TriggerVolume {
6396 pub fn new(id: u64, name: &str, aabb: Aabb) -> Self {
6397 Self {
6398 id, name: name.to_string(), aabb,
6399 bodies_inside: HashSet::new(),
6400 on_enter_events: Vec::new(),
6401 on_exit_events: Vec::new(),
6402 active: true,
6403 }
6404 }
6405
6406 pub fn update(&mut self, bodies: &HashMap<u64, RigidBodyInspector>) -> (Vec<u64>, Vec<u64>) {
6407 let mut entered = Vec::new();
6408 let mut exited = Vec::new();
6409 let mut now_inside = HashSet::new();
6410 for (id, body) in bodies {
6411 if self.aabb.contains_point(body.position) {
6412 now_inside.insert(*id);
6413 if !self.bodies_inside.contains(id) {
6414 entered.push(*id);
6415 }
6416 }
6417 }
6418 for id in &self.bodies_inside {
6419 if !now_inside.contains(id) {
6420 exited.push(*id);
6421 }
6422 }
6423 self.bodies_inside = now_inside;
6424 (entered, exited)
6425 }
6426}
6427
6428#[derive(Debug, Clone)]
6433pub struct JointDrive {
6434 pub target_position: f32,
6435 pub target_velocity: f32,
6436 pub stiffness: f32,
6437 pub damping: f32,
6438 pub max_force: f32,
6439 pub force_mode: DriveForceMode,
6440 pub enabled: bool,
6441}
6442
6443#[derive(Debug, Clone, PartialEq)]
6444pub enum DriveForceMode { Force, Acceleration }
6445
6446impl JointDrive {
6447 pub fn new(stiffness: f32, damping: f32, max_force: f32) -> Self {
6448 Self { target_position: 0.0, target_velocity: 0.0, stiffness, damping, max_force, force_mode: DriveForceMode::Force, enabled: true }
6449 }
6450 pub fn compute_force(&self, current_pos: f32, current_vel: f32) -> f32 {
6451 if !self.enabled { return 0.0; }
6452 let f = self.stiffness * (self.target_position - current_pos) + self.damping * (self.target_velocity - current_vel);
6453 f.clamp(-self.max_force, self.max_force)
6454 }
6455 pub fn compute_torque(&self, current_angle: f32, current_ang_vel: f32) -> f32 { self.compute_force(current_angle, current_ang_vel) }
6456}
6457
6458#[derive(Debug, Clone)]
6459pub struct DOF6Drive {
6460 pub x_linear: JointDrive,
6461 pub y_linear: JointDrive,
6462 pub z_linear: JointDrive,
6463 pub x_angular: JointDrive,
6464 pub y_angular: JointDrive,
6465 pub z_angular: JointDrive,
6466}
6467impl DOF6Drive {
6468 pub fn new() -> Self {
6469 let d = || JointDrive::new(0.0, 0.0, f32::INFINITY);
6470 Self { x_linear: d(), y_linear: d(), z_linear: d(), x_angular: d(), y_angular: d(), z_angular: d() }
6471 }
6472 pub fn compute_linear_forces(&self, cur: Vec3, vel: Vec3) -> Vec3 {
6473 Vec3::new(self.x_linear.compute_force(cur.x, vel.x), self.y_linear.compute_force(cur.y, vel.y), self.z_linear.compute_force(cur.z, vel.z))
6474 }
6475 pub fn compute_angular_torques(&self, cur: Vec3, omega: Vec3) -> Vec3 {
6476 Vec3::new(self.x_angular.compute_torque(cur.x, omega.x), self.y_angular.compute_torque(cur.y, omega.y), self.z_angular.compute_torque(cur.z, omega.z))
6477 }
6478}
6479
6480#[derive(Debug, Clone)]
6485pub struct PneumaticSpring {
6486 pub natural_length: f32,
6487 pub area: f32,
6488 pub initial_pressure: f32,
6489 pub polytropic_index: f32,
6490 pub initial_volume: f32,
6491 pub damping: f32,
6492}
6493impl PneumaticSpring {
6494 pub fn new(natural_length: f32, area: f32, pressure: f32) -> Self {
6495 Self { natural_length, area, initial_pressure: pressure, polytropic_index: 1.4, initial_volume: area * natural_length, damping: 500.0 }
6496 }
6497 pub fn force(&self, current_length: f32, velocity: f32) -> f32 {
6498 let current_volume = self.area * current_length.max(EPSILON);
6499 let ratio = (self.initial_volume / current_volume).powf(self.polytropic_index);
6500 let gauge_pressure = self.initial_pressure * ratio - self.initial_pressure;
6501 gauge_pressure * self.area - self.damping * velocity
6502 }
6503}
6504
6505#[derive(Debug, Clone)]
6510pub struct ContactManifold {
6511 pub body_a: u64,
6512 pub body_b: u64,
6513 pub points: Vec<ContactPoint>,
6514 pub normal: Vec3,
6515 pub friction: f32,
6516 pub restitution: f32,
6517 pub age: u32,
6518 pub persistent_threshold: f32,
6519}
6520impl ContactManifold {
6521 pub fn new(body_a: u64, body_b: u64, friction: f32, restitution: f32) -> Self {
6522 Self { body_a, body_b, points: Vec::new(), normal: Vec3::Y, friction, restitution, age: 0, persistent_threshold: 0.02 }
6523 }
6524 pub fn add_point(&mut self, pt: ContactPoint) {
6525 for existing in &mut self.points {
6526 if (existing.world_position - pt.world_position).length_squared() < self.persistent_threshold * self.persistent_threshold {
6527 existing.warm_impulse = existing.normal_impulse;
6528 existing.normal_impulse = pt.normal_impulse;
6529 existing.world_position = pt.world_position;
6530 existing.penetration_depth = pt.penetration_depth;
6531 return;
6532 }
6533 }
6534 if self.points.len() < 4 { self.points.push(pt); }
6535 else {
6536 let min_idx = self.points.iter().enumerate()
6537 .min_by(|(_, a), (_, b)| a.penetration_depth.partial_cmp(&b.penetration_depth).unwrap_or(std::cmp::Ordering::Equal))
6538 .map(|(i, _)| i).unwrap_or(0);
6539 if pt.penetration_depth > self.points[min_idx].penetration_depth { self.points[min_idx] = pt; }
6540 }
6541 }
6542 pub fn purge_invalid(&mut self) {
6543 self.points.retain(|p| p.penetration_depth > -0.02);
6544 self.age += 1;
6545 }
6546 pub fn total_impulse(&self) -> f32 { self.points.iter().map(|p| p.normal_impulse).sum() }
6547}
6548
6549#[derive(Debug, Clone)]
6554pub struct SoftBodyNode {
6555 pub position: Vec3,
6556 pub velocity: Vec3,
6557 pub force: Vec3,
6558 pub mass: f32,
6559 pub inv_mass: f32,
6560 pub fixed: bool,
6561}
6562
6563#[derive(Debug, Clone)]
6564pub struct SoftBodySpring {
6565 pub node_a: usize,
6566 pub node_b: usize,
6567 pub rest_length: f32,
6568 pub stiffness: f32,
6569 pub damping: f32,
6570}
6571
6572#[derive(Debug, Clone)]
6573pub struct SoftBody {
6574 pub nodes: Vec<SoftBodyNode>,
6575 pub springs: Vec<SoftBodySpring>,
6576 pub rest_volume: f32,
6577 pub volume_stiffness: f32,
6578 pub pressure_stiffness: f32,
6579 pub density: f32,
6580 pub total_mass: f32,
6581}
6582
6583impl SoftBody {
6584 pub fn new_sphere(radius: f32, resolution: usize, mass: f32, density: f32) -> Self {
6585 let mut nodes = Vec::new();
6586 let mut springs = Vec::new();
6587 let stacks = resolution;
6588 let slices = resolution * 2;
6589 for i in 0..=stacks {
6590 let phi = PI * i as f32 / stacks as f32;
6591 for j in 0..slices {
6592 let theta = TWO_PI * j as f32 / slices as f32;
6593 let pos = Vec3::new(radius * phi.sin() * theta.cos(), radius * phi.cos(), radius * phi.sin() * theta.sin());
6594 let node_mass = mass / ((stacks + 1) * slices) as f32;
6595 nodes.push(SoftBodyNode { position: pos, velocity: Vec3::ZERO, force: Vec3::ZERO, mass: node_mass, inv_mass: 1.0 / node_mass, fixed: false });
6596 }
6597 }
6598 let n = nodes.len();
6599 for i in 0..n {
6600 for j in i + 1..n {
6601 let dist = (nodes[i].position - nodes[j].position).length();
6602 if dist < radius * 0.8 {
6603 springs.push(SoftBodySpring { node_a: i, node_b: j, rest_length: dist, stiffness: 1000.0, damping: 5.0 });
6604 }
6605 }
6606 }
6607 let total_mass = nodes.iter().map(|n| n.mass).sum();
6608 Self { nodes, springs, rest_volume: (4.0/3.0)*PI*radius*radius*radius, volume_stiffness: 500.0, pressure_stiffness: 100.0, density, total_mass }
6609 }
6610
6611 pub fn integrate(&mut self, dt: f32, gravity: Vec3) {
6612 for node in &mut self.nodes {
6613 if node.fixed { continue; }
6614 let acc = (node.force + gravity * node.mass) * node.inv_mass;
6615 node.velocity += acc * dt;
6616 node.velocity *= 0.99;
6617 node.position += node.velocity * dt;
6618 node.force = Vec3::ZERO;
6619 }
6620 }
6621
6622 pub fn solve_springs(&mut self) {
6623 for spring in &self.springs {
6624 let a = spring.node_a;
6625 let b = spring.node_b;
6626 let pa = self.nodes[a].position;
6627 let pb = self.nodes[b].position;
6628 let delta = pb - pa;
6629 let dist = delta.length();
6630 if dist < EPSILON { continue; }
6631 let stretch = dist - spring.rest_length;
6632 let n = delta / dist;
6633 let va = self.nodes[a].velocity;
6634 let vb = self.nodes[b].velocity;
6635 let damp = (vb - va).dot(n) * spring.damping;
6636 let force = n * (stretch * spring.stiffness + damp);
6637 let ia = self.nodes[a].inv_mass;
6638 let ib = self.nodes[b].inv_mass;
6639 let total = ia + ib;
6640 if total < EPSILON { continue; }
6641 self.nodes[a].force += force * ia / total;
6642 self.nodes[b].force -= force * ib / total;
6643 }
6644 }
6645
6646 pub fn compute_volume(&self) -> f32 {
6647 let center = self.nodes.iter().fold(Vec3::ZERO, |a, n| a + n.position) / self.nodes.len() as f32;
6648 let max_r = self.nodes.iter().map(|n| (n.position - center).length()).fold(0.0f32, f32::max);
6649 (4.0 / 3.0) * PI * max_r * max_r * max_r
6650 }
6651
6652 pub fn apply_pressure(&mut self) {
6653 let vol = self.compute_volume();
6654 let pressure = self.pressure_stiffness * (self.rest_volume - vol);
6655 let center = self.nodes.iter().fold(Vec3::ZERO, |a, n| a + n.position) / self.nodes.len() as f32;
6656 for node in &mut self.nodes {
6657 node.force += (node.position - center).normalize_or_zero() * pressure;
6658 }
6659 }
6660}
6661
6662#[derive(Debug, Clone)]
6667pub struct MotorVelocityController {
6668 pub kp: f32, pub ki: f32, pub kd: f32,
6669 pub integral: f32,
6670 pub prev_error: f32,
6671 pub output_min: f32,
6672 pub output_max: f32,
6673 pub integral_limit: f32,
6674 pub target: f32,
6675}
6676impl MotorVelocityController {
6677 pub fn new(kp: f32, ki: f32, kd: f32) -> Self {
6678 Self { kp, ki, kd, integral: 0.0, prev_error: 0.0, output_min: -1000.0, output_max: 1000.0, integral_limit: 100.0, target: 0.0 }
6679 }
6680 pub fn update(&mut self, current: f32, dt: f32) -> f32 {
6681 let error = self.target - current;
6682 self.integral = (self.integral + error * dt).clamp(-self.integral_limit, self.integral_limit);
6683 let derivative = (error - self.prev_error) / dt.max(EPSILON);
6684 self.prev_error = error;
6685 (self.kp * error + self.ki * self.integral + self.kd * derivative).clamp(self.output_min, self.output_max)
6686 }
6687 pub fn reset(&mut self) { self.integral = 0.0; self.prev_error = 0.0; }
6688}
6689
6690pub fn two_bone_ik(root: Vec3, bone1_length: f32, bone2_length: f32, target: Vec3, hint: Vec3) -> (Vec3, Vec3) {
6696 let total_len = bone1_length + bone2_length;
6697 let rtt = target - root;
6698 let dist = rtt.length().min(total_len * 0.9999);
6699 let a = bone2_length; let b = bone1_length; let c = dist;
6700 let cos_b = ((b*b + c*c - a*a) / (2.0*b*c)).clamp(-1.0, 1.0);
6701 let angle_b = cos_b.acos();
6702 let forward = rtt.normalize_or_zero();
6703 let right = forward.cross(hint).normalize_or_zero();
6704 let up = right.cross(forward);
6705 let joint1 = root + forward * b * angle_b.cos() + up * b * angle_b.sin();
6706 (joint1, target)
6707}
6708
6709pub fn fabrik_solve(positions: &mut Vec<Vec3>, bone_lengths: &[f32], target: Vec3, iterations: u32, tolerance: f32) {
6711 if positions.len() < 2 || bone_lengths.len() != positions.len() - 1 { return; }
6712 let root = positions[0];
6713 let n = positions.len();
6714 let total_length: f32 = bone_lengths.iter().sum();
6715 if (target - root).length() >= total_length {
6716 let dir = (target - root).normalize_or_zero();
6717 for i in 1..n { positions[i] = positions[i-1] + dir * bone_lengths[i-1]; }
6718 return;
6719 }
6720 for _ in 0..iterations {
6721 *positions.last_mut().unwrap() = target;
6722 for i in (0..n-1).rev() {
6723 let dir = (positions[i] - positions[i+1]).normalize_or_zero();
6724 positions[i] = positions[i+1] + dir * bone_lengths[i];
6725 }
6726 positions[0] = root;
6727 for i in 0..n-1 {
6728 let dir = (positions[i+1] - positions[i]).normalize_or_zero();
6729 positions[i+1] = positions[i] + dir * bone_lengths[i];
6730 }
6731 if (*positions.last().unwrap() - target).length() < tolerance { break; }
6732 }
6733}
6734
6735#[derive(Debug, Clone)]
6740pub struct ForcePlate {
6741 pub id: u64,
6742 pub position: Vec3,
6743 pub size: Vec2,
6744 pub total_force: Vec3,
6745 pub total_torque: Vec3,
6746 pub cop: Vec3,
6747 pub active: bool,
6748 pub history: VecDeque<Vec3>,
6749 pub history_max: usize,
6750}
6751impl ForcePlate {
6752 pub fn new(id: u64, position: Vec3, size: Vec2) -> Self {
6753 Self { id, position, size, total_force: Vec3::ZERO, total_torque: Vec3::ZERO, cop: Vec3::ZERO, active: true, history: VecDeque::new(), history_max: 256 }
6754 }
6755 pub fn record_contact(&mut self, contact: &ContactPoint) {
6756 let local_pt = contact.world_position - self.position;
6757 let hx = self.size.x * 0.5; let hz = self.size.y * 0.5;
6758 if local_pt.x.abs() <= hx && local_pt.z.abs() <= hz {
6759 let f = contact.world_normal * contact.normal_impulse;
6760 self.total_force += f;
6761 self.total_torque += local_pt.cross(f);
6762 }
6763 }
6764 pub fn update(&mut self) {
6765 if self.history.len() >= self.history_max { self.history.pop_front(); }
6766 self.history.push_back(self.total_force);
6767 let fy = self.total_force.y.abs();
6768 if fy > EPSILON { self.cop = Vec3::new(-self.total_torque.z / fy, 0.0, self.total_torque.x / fy); }
6769 self.total_force = Vec3::ZERO;
6770 self.total_torque = Vec3::ZERO;
6771 }
6772}
6773
6774#[derive(Debug, Clone)]
6779pub struct ExplosionWave {
6780 pub center: Vec3,
6781 pub initial_radius: f32,
6782 pub current_radius: f32,
6783 pub max_radius: f32,
6784 pub propagation_speed: f32,
6785 pub peak_pressure: f32,
6786 pub decay_exponent: f32,
6787 pub active: bool,
6788 pub time: f32,
6789}
6790impl ExplosionWave {
6791 pub fn new(center: Vec3, peak_pressure: f32, max_radius: f32) -> Self {
6792 Self { center, initial_radius: 0.01, current_radius: 0.01, max_radius, propagation_speed: 340.0, peak_pressure, decay_exponent: 2.0, active: true, time: 0.0 }
6793 }
6794 pub fn update(&mut self, dt: f32) {
6795 self.time += dt;
6796 self.current_radius = self.initial_radius + self.propagation_speed * self.time;
6797 if self.current_radius > self.max_radius { self.active = false; }
6798 }
6799 pub fn pressure_at(&self, dist: f32) -> f32 {
6800 if !self.active || dist < EPSILON { return 0.0; }
6801 if (dist - self.current_radius).abs() > 0.5 { return 0.0; }
6802 self.peak_pressure / (dist / self.initial_radius).powf(self.decay_exponent)
6803 }
6804 pub fn force_on_body(&self, body_pos: Vec3, body_cross_section: f32) -> Vec3 {
6805 let diff = body_pos - self.center;
6806 let dist = diff.length();
6807 let p = self.pressure_at(dist);
6808 if p < EPSILON { return Vec3::ZERO; }
6809 diff.normalize_or_zero() * p * body_cross_section
6810 }
6811}
6812
6813pub fn kg_to_pounds(kg: f32) -> f32 { kg * 2.20462 }
6818pub fn pounds_to_kg(lbs: f32) -> f32 { lbs / 2.20462 }
6819pub fn meters_to_feet(m: f32) -> f32 { m * 3.28084 }
6820pub fn feet_to_meters(ft: f32) -> f32 { ft / 3.28084 }
6821pub fn nm_to_ftlb(nm: f32) -> f32 { nm * 0.737562 }
6822pub fn ftlb_to_nm(ftlb: f32) -> f32 { ftlb / 0.737562 }
6823pub fn kph_to_ms(kph: f32) -> f32 { kph / 3.6 }
6824pub fn ms_to_kph(ms: f32) -> f32 { ms * 3.6 }
6825pub fn mph_to_ms(mph: f32) -> f32 { mph * 0.44704 }
6826pub fn ms_to_mph(ms: f32) -> f32 { ms / 0.44704 }
6827pub fn pa_to_psi(pa: f32) -> f32 { pa * 0.000145038 }
6828pub fn psi_to_pa(psi: f32) -> f32 { psi / 0.000145038 }
6829pub fn rpm_to_rads(rpm: f32) -> f32 { rpm * TWO_PI / 60.0 }
6830pub fn rads_to_rpm(rads: f32) -> f32 { rads * 60.0 / TWO_PI }
6831pub fn horsepower_to_watts(hp: f32) -> f32 { hp * 745.7 }
6832pub fn watts_to_horsepower(w: f32) -> f32 { w / 745.7 }
6833pub fn kwh_to_joules(kwh: f32) -> f32 { kwh * 3_600_000.0 }
6834pub fn joules_to_kwh(j: f32) -> f32 { j / 3_600_000.0 }
6835
6836pub fn anisotropic_friction(relative_velocity: Vec3, normal: Vec3, friction_x: f32, friction_z: f32, local_x: Vec3) -> Vec3 {
6841 let local_z = normal.cross(local_x).normalize_or_zero();
6842 let vx = relative_velocity.dot(local_x);
6843 let vz = relative_velocity.dot(local_z);
6844 -(local_x * vx * friction_x + local_z * vz * friction_z)
6845}
6846
6847pub fn rolling_friction_torque(normal_force: f32, roll_radius: f32, mu_r: f32) -> f32 {
6848 mu_r * normal_force * roll_radius
6849}
6850
6851pub fn stribeck_friction(relative_speed: f32, mu_static: f32, mu_kinetic: f32, stribeck_speed: f32) -> f32 {
6852 let delta = mu_static - mu_kinetic;
6853 mu_kinetic + delta * (-relative_speed / stribeck_speed.max(EPSILON)).exp()
6854}
6855
6856#[derive(Debug, Clone, Default)]
6861pub struct GjkSimplex {
6862 pub points: Vec<Vec3>,
6863}
6864
6865impl GjkSimplex {
6866 pub fn new() -> Self { Self { points: Vec::new() } }
6867 pub fn add(&mut self, p: Vec3) { self.points.push(p); }
6868 pub fn len(&self) -> usize { self.points.len() }
6869
6870 pub fn nearest_simplex(&mut self) -> Option<Vec3> {
6871 match self.len() {
6872 1 => Some(-self.points[0]),
6873 2 => self.line_case(),
6874 3 => self.triangle_case(),
6875 4 => self.tetrahedron_case(),
6876 _ => None,
6877 }
6878 }
6879
6880 fn line_case(&mut self) -> Option<Vec3> {
6881 let a = self.points[1]; let b = self.points[0];
6882 let ab = b - a; let ao = -a;
6883 if ab.dot(ao) > 0.0 {
6884 Some(ab.cross(ao).cross(ab))
6885 } else {
6886 self.points = vec![a];
6887 Some(ao)
6888 }
6889 }
6890
6891 fn triangle_case(&mut self) -> Option<Vec3> {
6892 let a = self.points[2]; let b = self.points[1]; let c = self.points[0];
6893 let ab = b - a; let ac = c - a; let ao = -a;
6894 let abc = ab.cross(ac);
6895 if abc.cross(ac).dot(ao) > 0.0 {
6896 if ac.dot(ao) > 0.0 {
6897 self.points = vec![c, a];
6898 return Some(ac.cross(ao).cross(ac));
6899 }
6900 self.points = vec![b, a];
6901 return self.line_case();
6902 }
6903 if ab.cross(abc).dot(ao) > 0.0 {
6904 self.points = vec![b, a];
6905 return self.line_case();
6906 }
6907 if abc.dot(ao) > 0.0 { Some(abc) } else { self.points = vec![b, c, a]; Some(-abc) }
6908 }
6909
6910 fn tetrahedron_case(&mut self) -> Option<Vec3> {
6911 let a = self.points[3]; let b = self.points[2]; let c = self.points[1]; let d = self.points[0];
6912 let ab = b - a; let ac = c - a; let ad = d - a; let ao = -a;
6913 if ab.cross(ac).dot(ao) > 0.0 {
6914 self.points = vec![c, b, a];
6915 return self.triangle_case();
6916 }
6917 if ac.cross(ad).dot(ao) > 0.0 {
6918 self.points = vec![d, c, a];
6919 return self.triangle_case();
6920 }
6921 if ad.cross(ab).dot(ao) > 0.0 {
6922 self.points = vec![b, d, a];
6923 return self.triangle_case();
6924 }
6925 None
6926 }
6927}
6928
6929pub fn gjk_intersect(
6930 support_a: impl Fn(Vec3) -> Vec3,
6931 support_b: impl Fn(Vec3) -> Vec3,
6932 initial_dir: Vec3, max_iter: u32,
6933) -> bool {
6934 let mut dir = if initial_dir.length_squared() > EPSILON { initial_dir.normalize() } else { Vec3::X };
6935 let mut simplex = GjkSimplex::new();
6936 let p0 = support_a(dir) - support_b(-dir);
6937 simplex.add(p0);
6938 let mut next_dir = -p0;
6939 for _ in 0..max_iter {
6940 if next_dir.length_squared() < EPSILON { return true; }
6941 let p = support_a(next_dir) - support_b(-next_dir);
6942 if p.dot(next_dir) < 0.0 { return false; }
6943 simplex.add(p);
6944 match simplex.nearest_simplex() {
6945 None => return true,
6946 Some(d) => next_dir = d,
6947 }
6948 }
6949 false
6950}
6951
6952#[derive(Debug, Clone)]
6957pub struct ConvexHullFace {
6958 pub vertices: [usize; 3],
6959 pub normal: Vec3,
6960}
6961
6962pub struct ConvexHull3D {
6963 pub vertices: Vec<Vec3>,
6964 pub faces: Vec<ConvexHullFace>,
6965}
6966
6967impl ConvexHull3D {
6968 pub fn from_points(points: &[Vec3]) -> Self {
6969 if points.len() < 4 { return Self { vertices: points.to_vec(), faces: Vec::new() }; }
6970 let mut hull = Self { vertices: points.to_vec(), faces: Vec::new() };
6971 let (i0, i1, i2, i3) = find_initial_tetrahedron(points);
6972 let p0 = points[i0]; let p1 = points[i1]; let p2 = points[i2]; let p3 = points[i3];
6973 let n = (p1 - p0).cross(p2 - p0);
6974 let triplets: [(usize,usize,usize); 4] = if n.dot(p3 - p0) < 0.0 {
6975 [(i0,i1,i2),(i0,i3,i1),(i0,i2,i3),(i1,i3,i2)]
6976 } else {
6977 [(i0,i2,i1),(i0,i1,i3),(i0,i3,i2),(i1,i2,i3)]
6978 };
6979 for (a,b,c) in &triplets {
6980 let normal = (points[*b]-points[*a]).cross(points[*c]-points[*a]).normalize_or_zero();
6981 hull.faces.push(ConvexHullFace { vertices: [*a,*b,*c], normal });
6982 }
6983 hull
6984 }
6985
6986 pub fn volume(&self) -> f32 {
6987 let mut vol = 0.0f32;
6988 for face in &self.faces {
6989 let a = self.vertices[face.vertices[0]];
6990 let b = self.vertices[face.vertices[1]];
6991 let c = self.vertices[face.vertices[2]];
6992 vol += a.dot(b.cross(c));
6993 }
6994 vol.abs() / 6.0
6995 }
6996
6997 pub fn support(&self, dir: Vec3) -> Vec3 {
6998 self.vertices.iter().copied()
6999 .max_by(|a, b| a.dot(dir).partial_cmp(&b.dot(dir)).unwrap_or(std::cmp::Ordering::Equal))
7000 .unwrap_or(Vec3::ZERO)
7001 }
7002
7003 pub fn aabb(&self) -> Aabb { Aabb::from_points(&self.vertices) }
7004}
7005
7006fn find_initial_tetrahedron(points: &[Vec3]) -> (usize, usize, usize, usize) {
7007 let n = points.len();
7008 if n < 4 { return (0, 1.min(n-1), 2.min(n-1), 3.min(n-1)); }
7009 let i0 = 0usize;
7010 let i1 = (0..n).max_by(|&a,&b| (points[a]-points[i0]).length_squared().partial_cmp(&(points[b]-points[i0]).length_squared()).unwrap_or(std::cmp::Ordering::Equal)).unwrap_or(1);
7011 let i2 = (0..n).max_by(|&a,&b| {
7012 let d = (points[i1]-points[i0]).normalize_or_zero();
7013 ((points[a]-points[i0]).cross(d).length_squared()).partial_cmp(&(points[b]-points[i0]).cross(d).length_squared()).unwrap_or(std::cmp::Ordering::Equal)
7014 }).unwrap_or(2);
7015 let n012 = (points[i1]-points[i0]).cross(points[i2]-points[i0]).normalize_or_zero();
7016 let i3 = (0..n).max_by(|&a,&b| (points[a]-points[i0]).dot(n012).abs().partial_cmp(&(points[b]-points[i0]).dot(n012).abs()).unwrap_or(std::cmp::Ordering::Equal)).unwrap_or(3);
7017 (i0, i1, i2, i3)
7018}
7019
7020#[derive(Debug, Clone)]
7025pub struct PendulumChain {
7026 pub nodes: Vec<Vec3>,
7027 pub velocities: Vec<Vec3>,
7028 pub masses: Vec<f32>,
7029 pub lengths: Vec<f32>,
7030 pub fixed: Vec<bool>,
7031 pub gravity: Vec3,
7032 pub iterations: u32,
7033 pub damping: f32,
7034}
7035impl PendulumChain {
7036 pub fn new_simple(num_links: usize, link_length: f32, mass_per_link: f32, pivot: Vec3) -> Self {
7037 let mut nodes = Vec::new();
7038 let mut velocities = Vec::new();
7039 let mut masses = Vec::new();
7040 let mut lengths = Vec::new();
7041 let mut fixed = Vec::new();
7042 for i in 0..=num_links {
7043 nodes.push(pivot + Vec3::new(0.0, -(i as f32 * link_length), 0.0));
7044 velocities.push(Vec3::ZERO);
7045 masses.push(if i == 0 { 0.0 } else { mass_per_link });
7046 fixed.push(i == 0);
7047 if i > 0 { lengths.push(link_length); }
7048 }
7049 Self { nodes, velocities, masses, lengths, fixed, gravity: Vec3::new(0.0, -GRAVITY, 0.0), iterations: 20, damping: 0.999 }
7050 }
7051
7052 pub fn integrate(&mut self, dt: f32) {
7053 let dt2 = dt * dt;
7054 for i in 0..self.nodes.len() {
7055 if self.fixed[i] { continue; }
7056 let prev = self.nodes[i] - self.velocities[i] * dt;
7057 let new_pos = self.nodes[i] * 2.0 - prev + self.gravity * dt2;
7058 self.velocities[i] = (new_pos - self.nodes[i]) / dt;
7059 self.velocities[i] *= self.damping;
7060 self.nodes[i] = new_pos;
7061 }
7062 for _ in 0..self.iterations {
7063 for j in 0..self.lengths.len() {
7064 let a = j; let b = j + 1;
7065 let delta = self.nodes[b] - self.nodes[a];
7066 let dist = delta.length();
7067 if dist < EPSILON { continue; }
7068 let diff = (dist - self.lengths[j]) / dist;
7069 let ma = if self.fixed[a] { f32::INFINITY } else { self.masses[a] };
7070 let mb = if self.fixed[b] { f32::INFINITY } else { self.masses[b] };
7071 let total = 1.0/ma + 1.0/mb;
7072 if total < EPSILON { continue; }
7073 let correction = delta * diff;
7074 if !self.fixed[a] { self.nodes[a] += correction * (1.0/ma) / total; }
7075 if !self.fixed[b] { self.nodes[b] -= correction * (1.0/mb) / total; }
7076 }
7077 }
7078 }
7079
7080 pub fn total_energy(&self) -> f32 {
7081 let mut e = 0.0f32;
7082 for i in 0..self.nodes.len() {
7083 if self.fixed[i] { continue; }
7084 e += 0.5 * self.masses[i] * self.velocities[i].length_squared();
7085 e += self.masses[i] * (-self.gravity.y) * self.nodes[i].y;
7086 }
7087 e
7088 }
7089}
7090
7091#[derive(Debug, Clone)]
7096pub struct ShallowWaterSim {
7097 pub width: usize,
7098 pub height: usize,
7099 pub cell_size: f32,
7100 pub depth: Vec<f32>,
7101 pub velocity_x: Vec<f32>,
7102 pub velocity_z: Vec<f32>,
7103 pub base_height: f32,
7104 pub gravity: f32,
7105 pub damping: f32,
7106}
7107impl ShallowWaterSim {
7108 pub fn new(width: usize, height: usize, cell_size: f32, base_h: f32) -> Self {
7109 let n = width * height;
7110 Self { width, height, cell_size, depth: vec![base_h; n], velocity_x: vec![0.0; (width+1)*height], velocity_z: vec![0.0; width*(height+1)], base_height: base_h, gravity: GRAVITY, damping: 0.999 }
7111 }
7112 pub fn perturb(&mut self, cx: usize, cz: usize, amplitude: f32) {
7113 for dz in -2i32..=2 {
7114 for dx in -2i32..=2 {
7115 let x = cx as i32 + dx; let z = cz as i32 + dz;
7116 if x >= 0 && x < self.width as i32 && z >= 0 && z < self.height as i32 {
7117 let r = ((dx*dx+dz*dz) as f32).sqrt();
7118 let w = (1.0 - r/3.0).max(0.0);
7119 self.depth[z as usize * self.width + x as usize] += amplitude * w;
7120 }
7121 }
7122 }
7123 }
7124 pub fn step(&mut self, dt: f32) {
7125 let w = self.width; let h = self.height;
7126 let dx = self.cell_size; let g = self.gravity; let damp = self.damping;
7127 for z in 0..h {
7128 for x in 1..w {
7129 let grad = (self.depth[z*w+x] - self.depth[z*w+(x-1)]) / dx;
7130 let vi = z*(w+1)+x;
7131 self.velocity_x[vi] -= g * grad * dt;
7132 self.velocity_x[vi] *= damp;
7133 }
7134 }
7135 for z in 1..h {
7136 for x in 0..w {
7137 let grad = (self.depth[z*w+x] - self.depth[(z-1)*w+x]) / dx;
7138 let vi = z*w+x;
7139 self.velocity_z[vi] -= g * grad * dt;
7140 self.velocity_z[vi] *= damp;
7141 }
7142 }
7143 let mut new_depth = self.depth.clone();
7144 for z in 0..h {
7145 for x in 0..w {
7146 let vx_r = self.velocity_x[z*(w+1)+x+1];
7147 let vx_l = self.velocity_x[z*(w+1)+x];
7148 let vz_t = if z+1 < h { self.velocity_z[(z+1)*w+x] } else { 0.0 };
7149 let vz_b = self.velocity_z[z*w+x];
7150 new_depth[z*w+x] -= self.depth[z*w+x] * (vx_r - vx_l + vz_t - vz_b) / dx * dt;
7151 }
7152 }
7153 self.depth = new_depth;
7154 }
7155 pub fn height_at(&self, x: usize, z: usize) -> f32 {
7156 if x < self.width && z < self.height { self.depth[z*self.width+x] } else { self.base_height }
7157 }
7158}
7159
7160#[derive(Debug, Clone, PartialEq)]
7165pub enum CurveLoopMode { Once, Loop, PingPong }
7166
7167#[derive(Debug, Clone)]
7168pub struct PhysicsPropertyCurve {
7169 pub name: String,
7170 pub keys: Vec<(f32, f32)>,
7171 pub loop_mode: CurveLoopMode,
7172}
7173impl PhysicsPropertyCurve {
7174 pub fn new(name: &str) -> Self { Self { name: name.to_string(), keys: Vec::new(), loop_mode: CurveLoopMode::Once } }
7175 pub fn add_key(&mut self, t: f32, v: f32) {
7176 self.keys.push((t, v));
7177 self.keys.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
7178 }
7179 pub fn evaluate(&self, t: f32) -> f32 {
7180 if self.keys.is_empty() { return 0.0; }
7181 let dur = self.keys.last().map(|k| k.0).unwrap_or(0.0);
7182 let tw = match self.loop_mode {
7183 CurveLoopMode::Once => t.clamp(0.0, dur),
7184 CurveLoopMode::Loop => if dur > EPSILON { t % dur } else { 0.0 },
7185 CurveLoopMode::PingPong => {
7186 if dur > EPSILON { let c = t % (2.0*dur); if c <= dur { c } else { 2.0*dur-c } } else { 0.0 }
7187 }
7188 };
7189 for i in 0..self.keys.len()-1 {
7190 if tw >= self.keys[i].0 && tw <= self.keys[i+1].0 {
7191 let dt = self.keys[i+1].0 - self.keys[i].0;
7192 let frac = if dt > EPSILON { (tw - self.keys[i].0) / dt } else { 0.0 };
7193 return self.keys[i].1 + (self.keys[i+1].1 - self.keys[i].1) * frac;
7194 }
7195 }
7196 self.keys.last().map(|k| k.1).unwrap_or(0.0)
7197 }
7198 pub fn duration(&self) -> f32 { self.keys.last().map(|k| k.0).unwrap_or(0.0) }
7199}
7200
7201