Antkeeper  0.0.1
physics-system.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2023 Christopher J. Howard
3  *
4  * This file is part of Antkeeper source code.
5  *
6  * Antkeeper source code is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Antkeeper source code is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
25 #include <algorithm>
26 #include <engine/debug/log.hpp>
27 #include <engine/entity/id.hpp>
34 #include <execution>
35 
38 {
39  constexpr auto plane_i = std::to_underlying(physics::collider_type::plane);
40  constexpr auto sphere_i = std::to_underlying(physics::collider_type::sphere);
41  constexpr auto box_i = std::to_underlying(physics::collider_type::box);
42  constexpr auto capsule_i = std::to_underlying(physics::collider_type::capsule);
43 
44  narrow_phase_table[plane_i][plane_i] = std::bind_front(&physics_system::narrow_phase_plane_plane, this);
45  narrow_phase_table[plane_i][sphere_i] = std::bind_front(&physics_system::narrow_phase_plane_sphere, this);
46  narrow_phase_table[plane_i][box_i] = std::bind_front(&physics_system::narrow_phase_plane_box, this);
47  narrow_phase_table[plane_i][capsule_i] = std::bind_front(&physics_system::narrow_phase_plane_capsule, this);
48 
49  narrow_phase_table[sphere_i][plane_i] = std::bind_front(&physics_system::narrow_phase_sphere_plane, this);
50  narrow_phase_table[sphere_i][sphere_i] = std::bind_front(&physics_system::narrow_phase_sphere_sphere, this);
51  narrow_phase_table[sphere_i][box_i] = std::bind_front(&physics_system::narrow_phase_sphere_box, this);
52  narrow_phase_table[sphere_i][capsule_i] = std::bind_front(&physics_system::narrow_phase_sphere_capsule, this);
53 
54  narrow_phase_table[box_i][plane_i] = std::bind_front(&physics_system::narrow_phase_box_plane, this);
55  narrow_phase_table[box_i][sphere_i] = std::bind_front(&physics_system::narrow_phase_box_sphere, this);
56  narrow_phase_table[box_i][box_i] = std::bind_front(&physics_system::narrow_phase_box_box, this);
57  narrow_phase_table[box_i][capsule_i] = std::bind_front(&physics_system::narrow_phase_box_capsule, this);
58 
59  narrow_phase_table[capsule_i][plane_i] = std::bind_front(&physics_system::narrow_phase_capsule_plane, this);
60  narrow_phase_table[capsule_i][sphere_i] = std::bind_front(&physics_system::narrow_phase_capsule_sphere, this);
61  narrow_phase_table[capsule_i][box_i] = std::bind_front(&physics_system::narrow_phase_capsule_box, this);
62  narrow_phase_table[capsule_i][capsule_i] = std::bind_front(&physics_system::narrow_phase_capsule_capsule, this);
63 }
64 
65 void physics_system::update(float t, float dt)
66 {
67  detect_collisions_broad();
68  detect_collisions_narrow();
69  solve_constraints(dt);
70  resolve_collisions();
71  integrate(dt);
72  correct_positions();
73 
74  // Update transform component transforms
75  auto transform_view = registry.view<rigid_body_component, transform_component>();
76  for (const auto entity_id: transform_view)
77  {
78  const auto& body = *(transform_view.get<rigid_body_component>(entity_id).body);
79 
80  // Update transform
82  (
83  entity_id,
84  [&, dt](auto& transform)
85  {
86  // Integrate position
87  transform.local = body.get_transform();
88  }
89  );
90  }
91 }
92 
93 void physics_system::interpolate(float alpha)
94 {
95  // Interpolate rigid body states
96  auto view = registry.view<rigid_body_component, scene_component>();
97  std::for_each
98  (
99  std::execution::par_unseq,
100  view.begin(),
101  view.end(),
102  [&, alpha](auto entity_id)
103  {
104  const auto& rigid_body = *(view.get<rigid_body_component>(entity_id).body);
105  auto& scene_object = *(view.get<scene_component>(entity_id).object);
106 
107  scene_object.set_transform(rigid_body.interpolate(alpha));
108  }
109  );
110 }
111 
112 std::optional<std::tuple<entity::id, float, std::uint32_t, math::fvec3>> physics_system::trace(const geom::ray<float, 3>& ray, entity::id ignore_eid, std::uint32_t layer_mask) const
113 {
114  entity::id nearest_entity_id = entt::null;
115  float nearest_hit_sqr_distance = std::numeric_limits<float>::infinity();
116  std::uint32_t nearest_face_index = 0;
117  math::fvec3 nearest_hit_normal;
118 
119  // For each entity with a rigid body
120  auto rigid_body_view = registry.view<rigid_body_component>();
121  for (const auto entity_id: rigid_body_view)
122  {
123  if (entity_id == ignore_eid)
124  {
125  // Ignore a specific entity
126  continue;
127  }
128 
129  // Get rigid body and collider of the entity
130  const auto& rigid_body = *rigid_body_view.get<rigid_body_component>(entity_id).body;
131  const auto& collider = rigid_body.get_collider();
132 
133  if (!collider)
134  {
135  // Ignore rigid bodies without colliders
136  continue;
137  }
138 
139  if (!(collider->get_layer_mask() & layer_mask))
140  {
141  // Ignore rigid bodies without a common collision layer
142  continue;
143  }
144 
145  if (collider->type() == physics::collider_type::mesh)
146  {
147  // Transform ray into rigid body space
148  const auto& transform = rigid_body.get_transform();
149  geom::ray<float, 3> bs_ray;
150  bs_ray.origin = ((ray.origin - transform.translation) * transform.rotation) / transform.scale;
151  bs_ray.direction = math::normalize((ray.direction * transform.rotation) / transform.scale);
152 
153  const auto& mesh = static_cast<const physics::mesh_collider&>(*collider);
154  if (auto intersection = mesh.intersection(bs_ray))
155  {
156  const auto point = rigid_body.get_transform() * bs_ray.extrapolate(std::get<0>(*intersection));
157  const auto sqr_distance = math::sqr_distance(point, ray.origin);
158 
159  if (sqr_distance < nearest_hit_sqr_distance)
160  {
161  nearest_hit_sqr_distance = sqr_distance;
162  nearest_entity_id = entity_id;
163  nearest_face_index = std::get<1>(*intersection);
164  nearest_hit_normal = math::normalize(transform.rotation * (std::get<2>(*intersection) / transform.scale));
165  }
166  }
167  }
168  }
169 
170  if (nearest_entity_id == entt::null)
171  {
172  return std::nullopt;
173  }
174 
175  return std::make_tuple(nearest_entity_id, std::sqrt(nearest_hit_sqr_distance), nearest_face_index, nearest_hit_normal);
176 }
177 
178 void physics_system::integrate(float dt)
179 {
180  auto view = registry.view<rigid_body_component>();
181  std::for_each
182  (
183  std::execution::par_unseq,
184  view.begin(),
185  view.end(),
186  [&](auto entity_id)
187  {
188  auto& body = *(view.get<rigid_body_component>(entity_id).body);
189 
190  // Apply gravity
191  //body.apply_central_force(gravity / 10.0f * body.get_mass());
192 
193  body.integrate(dt);
194  }
195  );
196 }
197 
198 void physics_system::solve_constraints(float dt)
199 {
201  (
202  [dt](auto& component)
203  {
204  component.constraint->solve(dt);
205  }
206  );
207 }
208 
209 void physics_system::detect_collisions_broad()
210 {
211  broad_phase_pairs.clear();
212 
213  auto view = registry.view<rigid_body_component>();
214  for (auto i = view.begin(); i != view.end(); ++i)
215  {
216  auto& body_a = *view.get<rigid_body_component>(*i).body;
217 
218  const auto& collider_a = body_a.get_collider();
219  if (!collider_a)
220  {
221  continue;
222  }
223 
224  for (auto j = i + 1; j != view.end(); ++j)
225  {
226  auto& body_b = *view.get<rigid_body_component>(*j).body;
227 
228  const auto& collider_b = body_b.get_collider();
229  if (!collider_b)
230  {
231  continue;
232  }
233 
234  // Ignore pairs without a mutual layer
235  if (!(collider_a->get_layer_mask() & collider_b->get_layer_mask()))
236  {
237  continue;
238  }
239 
240  // Ignore static pairs
241  if (body_a.is_static() && body_b.is_static())
242  {
243  continue;
244  }
245 
246  broad_phase_pairs.emplace_back(&body_a, &body_b);
247  }
248  }
249 }
250 
251 void physics_system::detect_collisions_narrow()
252 {
253  narrow_phase_manifolds.clear();
254 
255  for (const auto& pair: broad_phase_pairs)
256  {
257  auto& body_a = *pair.first;
258  auto& body_b = *pair.second;
259 
260  narrow_phase_table[std::to_underlying(body_a.get_collider()->type())][std::to_underlying(body_b.get_collider()->type())](body_a, body_b);
261  }
262 }
263 
264 void physics_system::resolve_collisions()
265 {
266  for (const auto& manifold: narrow_phase_manifolds)
267  {
268  auto& body_a = *manifold.body_a;
269  auto& body_b = *manifold.body_b;
270 
271  const auto& material_a = *body_a.get_collider()->get_material();
272  const auto& material_b = *body_b.get_collider()->get_material();
273 
274  // Calculate coefficient of restitution
275  const auto restitution_combine_mode = std::max(material_a.get_restitution_combine_mode(), material_b.get_restitution_combine_mode());
276  float restitution_coef = physics::combine_restitution(material_a.get_restitution(), material_b.get_restitution(), restitution_combine_mode);
277 
278  // Calculate coefficients of friction
279  const auto friction_combine_mode = std::max(material_a.get_friction_combine_mode(), material_b.get_friction_combine_mode());
280  float static_friction_coef = physics::combine_friction(material_a.get_static_friction(), material_b.get_static_friction(), friction_combine_mode);
281  float dynamic_friction_coef = physics::combine_friction(material_a.get_dynamic_friction(), material_b.get_dynamic_friction(), friction_combine_mode);
282 
283  const float sum_inverse_mass = body_a.get_inverse_mass() + body_b.get_inverse_mass();
284  const float impulse_scale = 1.0f / static_cast<float>(manifold.contact_count);
285 
286  for (std::uint8_t i = 0; i < manifold.contact_count; ++i)
287  {
288  const physics::collision_contact& contact = manifold.contacts[i];
289 
290  const math::fvec3 radius_a = contact.point - body_a.get_position();
291  const math::fvec3 radius_b = contact.point - body_b.get_position();
292 
293  math::fvec3 relative_velocity = body_b.get_point_velocity(radius_b) - body_a.get_point_velocity(radius_a);
294 
295  const float contact_velocity = math::dot(relative_velocity, contact.normal);
296  if (contact_velocity > 0.0f)
297  {
298  continue;
299  }
300 
301  const float reaction_impulse_num = -(1.0f + restitution_coef) * contact_velocity;
302  const math::fvec3 ra_cross_n = math::cross(radius_a, contact.normal);
303  const math::fvec3 rb_cross_n = math::cross(radius_b, contact.normal);
304  const float reaction_impulse_den = sum_inverse_mass +
305  math::dot
306  (
307  math::cross(body_a.get_inverse_inertia() * ra_cross_n, radius_a) +
308  math::cross(body_b.get_inverse_inertia() * rb_cross_n, radius_b),
309  contact.normal
310  );
311  const float reaction_impulse_mag = (reaction_impulse_num / reaction_impulse_den) * impulse_scale;
312  const math::fvec3 reaction_impulse = contact.normal * reaction_impulse_mag;
313 
314  // Apply reaction impulses
315  body_a.apply_impulse(-reaction_impulse, radius_a);
316  body_b.apply_impulse(reaction_impulse, radius_b);
317 
318  //relative_velocity = body_b.get_point_velocity(radius_b) - body_a.get_point_velocity(radius_a);
319 
320  math::fvec3 contact_tangent = relative_velocity - contact.normal * contact_velocity;
321  const float sqr_tangent_length = math::sqr_length(contact_tangent);
322  if (sqr_tangent_length > 0.0f)
323  {
324  contact_tangent /= std::sqrt(sqr_tangent_length);
325  }
326 
327  const float friction_impulse_num = math::dot(relative_velocity, -contact_tangent);
328  const math::fvec3 ra_cross_t = math::cross(radius_a, contact_tangent);
329  const math::fvec3 rb_cross_t = math::cross(radius_b, contact_tangent);
330  const float friction_impulse_den = sum_inverse_mass +
331  math::dot
332  (
333  math::cross(body_a.get_inverse_inertia() * ra_cross_t, radius_a) +
334  math::cross(body_b.get_inverse_inertia() * rb_cross_t, radius_b),
335  contact_tangent
336  );
337  float friction_impulse_mag = (friction_impulse_num / friction_impulse_den) * impulse_scale;
338 
339  if (std::abs(friction_impulse_mag) >= reaction_impulse_mag * static_friction_coef)
340  {
341  friction_impulse_mag = -reaction_impulse_mag * dynamic_friction_coef;
342  }
343 
344  const math::fvec3 friction_impulse = contact_tangent * friction_impulse_mag;
345 
346  body_a.apply_impulse(-friction_impulse, radius_a);
347  body_b.apply_impulse(friction_impulse, radius_b);
348  }
349  }
350 }
351 
352 void physics_system::correct_positions()
353 {
354  const float depth_threshold = 0.01f;
355  const float correction_factor = 0.4f;
356 
357  for (const auto& manifold: narrow_phase_manifolds)
358  {
359  auto& body_a = *manifold.body_a;
360  auto& body_b = *manifold.body_b;
361  const float sum_inverse_mass = body_a.get_inverse_mass() + body_b.get_inverse_mass();
362 
363  for (std::uint8_t i = 0; i < manifold.contact_count; ++i)
364  {
365  const physics::collision_contact& contact = manifold.contacts[i];
366 
367  math::fvec3 correction = contact.normal * (std::max<float>(0.0f, contact.depth - depth_threshold) / sum_inverse_mass) * correction_factor;
368 
369  body_a.set_position(body_a.get_position() - correction * body_a.get_inverse_mass());
370  body_b.set_position(body_b.get_position() + correction * body_b.get_inverse_mass());
371  }
372  }
373 }
374 
375 void physics_system::narrow_phase_plane_plane(physics::rigid_body& body_a, physics::rigid_body& body_b)
376 {
377  return;
378 }
379 
380 void physics_system::narrow_phase_plane_sphere(physics::rigid_body& body_a, physics::rigid_body& body_b)
381 {
382  const auto& plane_a = static_cast<const physics::plane_collider&>(*body_a.get_collider());
383  const auto& sphere_b = static_cast<const physics::sphere_collider&>(*body_b.get_collider());
384 
385  // Transform plane into world-space
386  const math::fvec3 plane_normal = body_a.get_orientation() * plane_a.get_normal();
387  const float plane_constant = plane_a.get_constant() - math::dot(plane_normal, body_a.get_position());
388 
389  const float signed_distance = math::dot(plane_normal, body_b.get_position()) + plane_constant;
390  if (signed_distance > sphere_b.get_radius())
391  {
392  return;
393  }
394 
395  // Init collision manifold
396  collision_manifold_type manifold;
397  manifold.body_a = &body_a;
398  manifold.body_b = &body_b;
399  manifold.contact_count = 1;
400 
401  // Generate collision contact
402  auto& contact = manifold.contacts[0];
403  contact.point = body_b.get_position() - plane_normal * sphere_b.get_radius();
404  contact.normal = plane_normal;
405  contact.depth = std::abs(signed_distance - sphere_b.get_radius());
406 
407  narrow_phase_manifolds.emplace_back(std::move(manifold));
408 }
409 
410 void physics_system::narrow_phase_plane_box(physics::rigid_body& body_a, physics::rigid_body& body_b)
411 {
412  const auto& plane_a = static_cast<const physics::plane_collider&>(*body_a.get_collider());
413  const auto& box_b = static_cast<const physics::box_collider&>(*body_b.get_collider());
414 
415  // Transform plane into world-space
416  const math::fvec3 plane_normal = body_a.get_orientation() * plane_a.get_normal();
417  const float plane_constant = plane_a.get_constant() - math::dot(plane_normal, body_a.get_position());
418 
419  const auto& box_min = box_b.get_min();
420  const auto& box_max = box_b.get_max();
421  const math::fvec3 corners[8] =
422  {
423  {box_min.x(), box_min.y(), box_min.z()},
424  {box_min.x(), box_min.y(), box_max.z()},
425  {box_min.x(), box_max.y(), box_min.z()},
426  {box_min.x(), box_max.y(), box_max.z()},
427  {box_max.x(), box_min.y(), box_min.z()},
428  {box_max.x(), box_min.y(), box_max.z()},
429  {box_max.x(), box_max.y(), box_min.z()},
430  {box_max.x(), box_max.y(), box_max.z()}
431  };
432 
433  // Init collision manifold
434  collision_manifold_type manifold;
435  manifold.contact_count = 0;
436 
437  // Brute force
438  for (std::size_t i = 0; i < 8; ++i)
439  {
440  // Transform corner into world-space
441  const math::fvec3 point = body_b.get_transform() * corners[i];
442 
443  const float signed_distance = math::dot(plane_normal, point) + plane_constant;
444 
445  if (signed_distance <= 0.0f)
446  {
447  auto& contact = manifold.contacts[manifold.contact_count];
448  contact.point = point;
449  contact.normal = plane_normal;
450  contact.depth = std::abs(signed_distance);
451 
452  ++manifold.contact_count;
453 
454  if (manifold.contact_count >= 4)
455  {
456  break;
457  }
458  }
459  }
460 
461  if (manifold.contact_count)
462  {
463  manifold.body_a = &body_a;
464  manifold.body_b = &body_b;
465  narrow_phase_manifolds.emplace_back(std::move(manifold));
466  }
467 }
468 
469 void physics_system::narrow_phase_plane_capsule(physics::rigid_body& body_a, physics::rigid_body& body_b)
470 {
471  const auto& plane_a = static_cast<const physics::plane_collider&>(*body_a.get_collider());
472  const auto& capsule_b = static_cast<const physics::capsule_collider&>(*body_b.get_collider());
473 
474  // Transform plane into world-space
476  plane.normal = body_a.get_orientation() * plane_a.get_normal();
477  plane.constant = plane_a.get_constant() - math::dot(plane.normal, body_a.get_position());
478 
479  // Transform capsule into world-space
481  {
482  {
483  body_b.get_transform() * capsule_b.get_segment().a,
484  body_b.get_transform() * capsule_b.get_segment().b
485  },
486  capsule_b.get_radius()
487  };
488 
489  // Calculate signed distances to capsule segment endpoints
490  const float distance_a = plane.distance(capsule.segment.a);
491  const float distance_b = plane.distance(capsule.segment.b);
492 
493  collision_manifold_type manifold;
494  manifold.contact_count = 0;
495 
496  if (distance_a <= capsule.radius)
497  {
498  auto& contact = manifold.contacts[manifold.contact_count];
499 
500  contact.point = capsule.segment.a - plane.normal * capsule.radius;
501  contact.normal = plane.normal;
502  contact.depth = std::abs(distance_a - capsule.radius);
503 
504  ++manifold.contact_count;
505  }
506 
507  if (distance_b <= capsule.radius)
508  {
509  auto& contact = manifold.contacts[manifold.contact_count];
510 
511  contact.point = capsule.segment.b - plane.normal * capsule.radius;
512  contact.normal = plane.normal;
513  contact.depth = std::abs(distance_b - capsule.radius);
514 
515  ++manifold.contact_count;
516  }
517 
518  if (manifold.contact_count)
519  {
520  manifold.body_a = &body_a;
521  manifold.body_b = &body_b;
522  narrow_phase_manifolds.emplace_back(std::move(manifold));
523  }
524 }
525 
526 void physics_system::narrow_phase_sphere_plane(physics::rigid_body& body_a, physics::rigid_body& body_b)
527 {
528  narrow_phase_plane_sphere(body_b, body_a);
529 }
530 
531 void physics_system::narrow_phase_sphere_sphere(physics::rigid_body& body_a, physics::rigid_body& body_b)
532 {
533  const auto& collider_a = static_cast<const physics::sphere_collider&>(*body_a.get_collider());
534  const auto& collider_b = static_cast<const physics::sphere_collider&>(*body_b.get_collider());
535 
536  // Transform spheres into world-space
537  const math::fvec3 center_a = body_a.get_transform() * collider_a.get_center();
538  const math::fvec3 center_b = body_b.get_transform() * collider_b.get_center();
539  const float radius_a = collider_a.get_radius();
540  const float radius_b = collider_b.get_radius();
541 
542  // Sum sphere radii
543  const float sum_radii = radius_a + radius_b;
544 
545  // Get vector from center a to center b
546  const math::fvec3 difference = center_b - center_a;
547 
549  if (sqr_distance > sum_radii * sum_radii)
550  {
551  return;
552  }
553 
554  // Ignore degenerate case (sphere centers identical)
555  if (!sqr_distance)
556  {
557  return;
558  }
559 
560  // Init collision manifold
561  collision_manifold_type manifold;
562  manifold.body_a = &body_a;
563  manifold.body_b = &body_b;
564  manifold.contact_count = 1;
565 
566  // Generate collision contact
567  auto& contact = manifold.contacts[0];
568 
569  const float distance = std::sqrt(sqr_distance);
570 
571  contact.normal = difference / distance;
572  contact.depth = sum_radii - distance;
573  contact.point = center_a + contact.normal * (radius_a - contact.depth * 0.5f);
574 
575  narrow_phase_manifolds.emplace_back(std::move(manifold));
576 }
577 
578 void physics_system::narrow_phase_sphere_box(physics::rigid_body& body_a, physics::rigid_body& body_b)
579 {
580  return;
581 }
582 
583 void physics_system::narrow_phase_sphere_capsule(physics::rigid_body& body_a, physics::rigid_body& body_b)
584 {
585  const auto& collider_a = static_cast<const physics::sphere_collider&>(*body_a.get_collider());
586  const auto& collider_b = static_cast<const physics::capsule_collider&>(*body_b.get_collider());
587 
588  // Transform sphere into world-space
589  const math::fvec3 center_a = body_a.get_transform() * collider_a.get_center();
590  const float radius_a = collider_a.get_radius();
591 
592  // Transform capsule into world-space
593  const geom::line_segment<float, 3> segment_b
594  {
595  body_b.get_transform() * collider_b.get_segment().a,
596  body_b.get_transform() * collider_b.get_segment().b
597  };
598  const float radius_b = collider_b.get_radius();
599 
600  // Sum the two radii
601  const float sum_radii = radius_a + radius_b;
602 
603  // Find closest point on capsule segment to sphere center
604  const auto closest_point = geom::closest_point(segment_b, center_a);
605 
606  // Get vector from sphere center to point to on capsule segment
607  const math::fvec3 difference = closest_point - center_a;
608 
610  if (sqr_distance > sum_radii * sum_radii)
611  {
612  return;
613  }
614 
615  // Ignore degenerate case (sphere center on capsule segment)
616  if (!sqr_distance)
617  {
618  return;
619  }
620 
621  collision_manifold_type manifold;
622  manifold.contact_count = 1;
623  manifold.body_a = &body_a;
624  manifold.body_b = &body_b;
625 
626  auto& contact = manifold.contacts[0];
627 
628  const float distance = std::sqrt(sqr_distance);
629 
630  contact.depth = sum_radii - distance;
631  contact.normal = difference / distance;
632  contact.point = center_a + contact.normal * (radius_a - contact.depth * 0.5f);
633 
634  narrow_phase_manifolds.emplace_back(std::move(manifold));
635 }
636 
637 void physics_system::narrow_phase_box_plane(physics::rigid_body& body_a, physics::rigid_body& body_b)
638 {
639  narrow_phase_plane_box(body_b, body_a);
640 }
641 
642 void physics_system::narrow_phase_box_sphere(physics::rigid_body& body_a, physics::rigid_body& body_b)
643 {
644  return;
645 }
646 
647 void physics_system::narrow_phase_box_box(physics::rigid_body& body_a, physics::rigid_body& body_b)
648 {
649  return;
650 }
651 
652 void physics_system::narrow_phase_box_capsule(physics::rigid_body& body_a, physics::rigid_body& body_b)
653 {
654  return;
655 }
656 
657 void physics_system::narrow_phase_capsule_plane(physics::rigid_body& body_a, physics::rigid_body& body_b)
658 {
659  narrow_phase_plane_capsule(body_b, body_a);
660 }
661 
662 void physics_system::narrow_phase_capsule_sphere(physics::rigid_body& body_a, physics::rigid_body& body_b)
663 {
664  narrow_phase_sphere_capsule(body_b, body_a);
665 }
666 
667 void physics_system::narrow_phase_capsule_box(physics::rigid_body& body_a, physics::rigid_body& body_b)
668 {
669  return;
670 }
671 
672 void physics_system::narrow_phase_capsule_capsule(physics::rigid_body& body_a, physics::rigid_body& body_b)
673 {
674  const auto& collider_a = static_cast<const physics::capsule_collider&>(*body_a.get_collider());
675  const auto& collider_b = static_cast<const physics::capsule_collider&>(*body_b.get_collider());
676 
677  // Transform capsules into world-space
678  const geom::capsule<float> capsule_a
679  {
680  {
681  body_a.get_transform() * collider_a.get_segment().a,
682  body_a.get_transform() * collider_a.get_segment().b
683  },
684  collider_a.get_radius()
685  };
686  const geom::capsule<float> capsule_b
687  {
688  {
689  body_b.get_transform() * collider_b.get_segment().a,
690  body_b.get_transform() * collider_b.get_segment().b
691  },
692  collider_b.get_radius()
693  };
694 
695  // Calculate closest points between capsule segments
696  const auto [closest_a, closest_b] = geom::closest_point(capsule_a.segment, capsule_b.segment);
697 
698  // Sum sphere radii
699  const float sum_radii = capsule_a.radius + capsule_b.radius;
700 
701  // Get vector from closest point on segment a to closest point on segment b
702  const math::fvec3 difference = closest_b - closest_a;
703 
705  if (sqr_distance > sum_radii * sum_radii)
706  {
707  return;
708  }
709 
710  // Ignore degenerate case (closest points identical)
711  if (!sqr_distance)
712  {
713  return;
714  }
715 
716  // Init collision manifold
717  collision_manifold_type manifold;
718  manifold.body_a = &body_a;
719  manifold.body_b = &body_b;
720  manifold.contact_count = 1;
721 
722  // Generate collision contact
723  auto& contact = manifold.contacts[0];
724 
725  const float distance = std::sqrt(sqr_distance);
726 
727  contact.normal = difference / distance;
728  contact.depth = sum_radii - distance;
729  contact.point = closest_a + contact.normal * (capsule_a.radius - contact.depth * 0.5f);
730 
731  narrow_phase_manifolds.emplace_back(std::move(manifold));
732 }
Box collision object.
Capsule collision object.
Mesh collision object.
Plane collision object.
constexpr const std::shared_ptr< collider > & get_collider() const noexcept
Returns the collider of the rigid body.
Definition: rigid-body.hpp:326
constexpr const math::fquat & get_orientation() const noexcept
Returns the current orientation of the rigid body.
Definition: rigid-body.hpp:248
constexpr const math::fvec3 & get_position() const noexcept
Returns the current position of the rigid body.
Definition: rigid-body.hpp:242
constexpr const math::transform< float > & get_transform() const noexcept
Returns the transformation representing the current state of the rigid body.
Definition: rigid-body.hpp:236
Sphere collision object.
void interpolate(float alpha)
void update(float t, float dt) override
Perform's a system's update() function.
physics_system(entity::registry &registry)
std::optional< std::tuple< entity::id, float, std::uint32_t, math::fvec3 > > trace(const geom::ray< float, 3 > &ray, entity::id ignore_eid=entt::null, std::uint32_t layer_mask=~std::uint32_t{0}) const
Traces a ray to the nearest point of intersection.
Abstract base class for updatable systems.
entity::registry & registry
Registry on which the system operate.
constexpr int difference(T x, T y) noexcept
Returns the number of differing bits between two values, known as Hamming distance.
Definition: bit-math.hpp:280
entt::registry registry
Component registry type.
Definition: registry.hpp:28
entt::entity id
Entity ID type.
Definition: id.hpp:28
hypercapsule< T, 3 > capsule
3-dimensional capsule.
Definition: capsule.hpp:34
hyperplane< T, 3 > plane
3-dimensional hyperplane.
Definition: plane.hpp:34
math::vector< T, N > point
n-dimensional point.
Definition: point.hpp:35
constexpr std::optional< T > intersection(const ray< T, N > &ray, const hyperplane< T, N > &hyperplane) noexcept
Ray-hyperplane intersection test.
constexpr point< T, N > closest_point(const ray< T, N > &a, const point< T, N > &b) noexcept
Calculates the closest point on a ray to a point.
constexpr T sqr_distance(const vector< T, N > &p0, const vector< T, N > &p1) noexcept
Calculates the square distance between two points.
Definition: vector.hpp:1514
constexpr vector< T, N > max(const vector< T, N > &x, const vector< T, N > &y)
Returns a vector containing the maximum elements of two vectors.
Definition: vector.hpp:1328
quaternion< T > normalize(const quaternion< T > &q)
Normalizes a quaternion.
Definition: quaternion.hpp:679
constexpr vector< T, N > abs(const vector< T, N > &x)
Returns the absolute values of each element.
Definition: vector.hpp:985
constexpr T sqr_length(const quaternion< T > &q) noexcept
Calculates the square length of a quaternion.
Definition: quaternion.hpp:744
T distance(const vector< T, N > &p0, const vector< T, N > &p1)
Calculates the distance between two points.
Definition: vector.hpp:1106
constexpr vector< T, 3 > cross(const vector< T, 3 > &x, const vector< T, 3 > &y) noexcept
Calculates the cross product of two vectors.
Definition: vector.hpp:1095
vector< T, N > sqrt(const vector< T, N > &x)
Takes the square root of each element.
constexpr T dot(const quaternion< T > &a, const quaternion< T > &b) noexcept
Calculates the dot product of two quaternions.
Definition: quaternion.hpp:572
restitution_combine_mode
Specifies how coefficients of restitution should be calculated.
Definition: restitution.hpp:33
float combine_restitution(float a, float b, restitution_combine_mode mode) noexcept
Combines two restitution values into a coefficient of restitution.
Definition: restitution.cpp:25
@ mesh
Mesh collider.
@ capsule
Capsule collider.
@ sphere
Sphere collider.
@ box
Box collider.
@ plane
Plane collider.
friction_combine_mode
Specifies how coefficients of friction should be calculated.
Definition: friction.hpp:33
float combine_friction(float a, float b, friction_combine_mode mode) noexcept
Combines two friction values into a coefficient of friction.
Definition: friction.cpp:25
n-dimensional capsule.
n-dimensional plane.
Definition: hyperplane.hpp:36
Half of a line proceeding from an initial point.
Definition: ray.hpp:38
constexpr vector_type extrapolate(T distance) const noexcept
Extrapolates from the ray origin along the ray direction vector.
Definition: ray.hpp:55
vector_type direction
Ray direction vector.
Definition: ray.hpp:46
vector_type origin
Ray origin position.
Definition: ray.hpp:43
n-dimensional vector.
Definition: vector.hpp:44
constexpr element_type & x() noexcept
Returns a reference to the first element.
Definition: vector.hpp:164
Point of contact between two colliding bodies.
math::fvec3 point
World-space contact point.
math::fvec3 normal
Contact normal, pointing from body a to body b.
float depth
Contact penetration depth.
std::unique_ptr< physics::rigid_body > body