sandbox
Loading...
Searching...
No Matches
physics_module.hpp
1// SPDX-License-Identifier: MIT
2#ifndef LIBSBX_PHYSICS_PHYSICS_MODULE_HPP_
3#define LIBSBX_PHYSICS_PHYSICS_MODULE_HPP_
4
5#include <cmath>
6#include <optional>
7#include <vector>
8#include <algorithm>
9#include <ranges>
10#include <unordered_map> // Added for unordered_map
11
12#include <libsbx/utility/logger.hpp>
13
14#include <libsbx/core/engine.hpp>
15#include <libsbx/core/module.hpp>
16
17#include <libsbx/containers/octree.hpp>
18
20#include <libsbx/math/volume.hpp>
22
23#include <libsbx/scenes/scenes_module.hpp>
24
25#include <libsbx/scenes/components/global_transform.hpp>
26#include <libsbx/scenes/components/transform.hpp>
27#include <libsbx/scenes/components/id.hpp>
28#include <libsbx/scenes/components/tag.hpp>
29#include <libsbx/scenes/components/relationship.hpp>
30
31#include <libsbx/physics/shape_collider.hpp>
32#include <libsbx/physics/collision_detection.hpp>
33#include <libsbx/physics/rigidbody.hpp>
34
35namespace sbx::physics {
36
37class physics_module : public core::module<physics_module> {
38
39 inline static const auto is_registered = register_module(stage::fixed, dependencies<scenes::scenes_module>{});
40
41public:
42
43 physics_module() = default;
44
45 ~physics_module() override = default;
46
47 auto update() -> void override {
48 SBX_PROFILE_SCOPE("physics_module::update");
49 SBX_MEMORY_SCOPE(memory::allocation_category::physics);
50
51 const auto dt = core::engine::fixed_delta_time();
52
53 _integrate_velocities(dt);
54
55 const auto broad_phase_pairs = _collision_broad_phase();
56 const auto collisions = _collision_narrow_phase(broad_phase_pairs);
57
58 _resolve_collisions(collisions, dt);
59
60 _integrate_positions(dt);
61
62 _positional_correction(collisions);
63
64 _update_character_controllers();
65 }
66
67private:
68
70 using collision_pair_type = typename octree_type::intersection;
71
72 struct collision {
73 scenes::node node_a;
74 scenes::node node_b;
75 collision_manifold manifold;
76 }; // struct collision
77
78 struct contact_constraint {
79 math::vector3 normal;
80 math::vector3 tangent1;
81 math::vector3 tangent2;
82 math::vector3 r_a;
83 math::vector3 r_b;
84 std::float_t normal_mass{0.0f};
85 std::float_t tangent1_mass{0.0f};
86 std::float_t tangent2_mass{0.0f};
87 std::float_t bias{0.0f};
88 std::float_t normal_impulse{0.0f};
89 std::float_t tangent1_impulse{0.0f};
90 std::float_t tangent2_impulse{0.0f};
91 std::uint64_t last_seen{0};
92 }; // struct contact_constraint
93
94 struct solver_contact {
95 scenes::node node_a{};
96 scenes::node node_b{};
97 std::size_t key{};
98 contact_constraint constraint{};
99 }; // struct solver_contact
100
101 auto _integrate_velocities(std::float_t dt) -> void {
102 auto& scenes_module = core::engine::get_module<scenes::scenes_module>();
103 auto& scene = scenes_module.active_scene();
104 auto& environment = scene.environment();
105 auto& graph = scene.graph();
106
107 auto query = graph.query<scenes::transform, physics::rigidbody>();
108
109 for (auto&& [node, transform, rigidbody] : query.each()) {
110 if (rigidbody.is_static()) {
111 continue;
112 }
113
114 rigidbody.update_inertia_tensor_world(math::matrix_cast<math::matrix3x3>(transform.rotation()));
115
116 const auto linear_acceleration = (rigidbody.dynamic_forces() + rigidbody.constant_forces()) * rigidbody.inverse_mass();
117
118 rigidbody.add_velocity(linear_acceleration * dt);
119 rigidbody.set_velocity(rigidbody.velocity() * std::pow(1.0f - rigidbody.linear_damping(), dt));
120
121 const auto angular_acceleration = rigidbody.inverse_inertia_tensor_world() * rigidbody.torque();
122
123 rigidbody.add_angular_velocity(angular_acceleration * dt);
124 rigidbody.set_angular_velocity(rigidbody.angular_velocity() * std::pow(1.0f - rigidbody.angular_damping(), dt));
125
126 rigidbody.clear_dynamic_forces();
127 rigidbody.clear_torque();
128 }
129 }
130
131 auto _integrate_positions(std::float_t dt) -> void {
132 auto& scenes_module = core::engine::get_module<scenes::scenes_module>();
133 auto& scene = scenes_module.active_scene();
134 auto& environment = scene.environment();
135 auto& graph = scene.graph();
136
137 auto query = graph.query<scenes::transform, physics::rigidbody>();
138
139 for (auto&& [node, transform, rigidbody] : query.each()) {
140 if (rigidbody.is_static()) {
141 continue;
142 }
143
144 transform.position() += rigidbody.velocity() * dt;
145
146 const auto angular_velocity = rigidbody.angular_velocity();
147
148 if (angular_velocity.length_squared() > 0.0f) {
149 const auto delta_rotation = math::quaternion{angular_velocity, 0.0f} * transform.rotation();
150 transform.set_rotation(math::quaternion::normalized(transform.rotation() + delta_rotation * (0.5f * dt)));
151 }
152
153 rigidbody.update_inertia_tensor_world(math::matrix_cast<math::matrix3x3>(transform.rotation()));
154
155 transform.bump_version();
156 }
157 }
158
159 auto _collision_broad_phase() -> std::vector<collision_pair_type> {
160 auto& scenes_module = core::engine::get_module<scenes::scenes_module>();
161 auto& scene = scenes_module.active_scene();
162 auto& environment = scene.environment();
163 auto& graph = scene.graph();
164
165 auto tree = octree_type{math::volume{math::vector3{-500.0f}, math::vector3{500.0f}}, 64u};
166 auto query = graph.query<const physics::shape_collider, const physics::rigidbody>();
167
168 for (auto&& [node, collider, rigidbody] : query.each()) {
169 const auto translation = math::matrix4x4::translated(sbx::math::matrix4x4::identity, graph.world_position(node));
170 const auto rotation = math::matrix_cast<math::matrix4x4>(graph.world_rotation(node));
171
172 const auto volume = get_bounding_volume(collider, translation * rotation);
173
174 tree.insert(node, volume);
175 }
176
177 return tree.intersections();
178 }
179
180 auto _collision_narrow_phase(const std::vector<collision_pair_type>& pairs) -> std::vector<collision> {
181 auto& scenes_module = core::engine::get_module<scenes::scenes_module>();
182 auto& scene = scenes_module.active_scene();
183 auto& environment = scene.environment();
184 auto& graph = scene.graph();
185
186 auto collisions = std::vector<collision>{};
187 collisions.reserve(pairs.size());
188
189 for (auto [first, second] : pairs) {
190 const auto node_a = std::min(first, second);
191 const auto node_b = std::max(first, second);
192
193 const auto& body_a = graph.get_component<physics::rigidbody>(node_a);
194 const auto& body_b = graph.get_component<physics::rigidbody>(node_b);
195
196 if (body_a.is_static() && body_b.is_static()) {
197 continue;
198 }
199
200 const auto& collider_a = graph.get_component<physics::shape_collider>(node_a);
201 const auto& collider_b = graph.get_component<physics::shape_collider>(node_b);
202
203 const auto data_a = collider_data{graph.world_position(node_a), math::quaternion::normalized(graph.world_rotation(node_a)), collider_a};
204 const auto data_b = collider_data{graph.world_position(node_b), math::quaternion::normalized(graph.world_rotation(node_b)), collider_b};
205
206 if (auto manifold = check_collision(data_a, data_b); manifold) {
207 for (const auto& point : manifold->contact_points) {
208 scenes_module.add_debug_sphere(point.position, 0.2f, math::color::red(), 16u);
209 }
210
211 collisions.push_back({node_a, node_b, std::move(*manifold)});
212 }
213 }
214
215 return collisions;
216 }
217
218 auto _resolve_collisions(const std::vector<collision>& collisions, std::float_t dt) -> void {
219 SBX_PROFILE_SCOPE("physics_module::_resolve_collisions");
220
221 auto& scenes_module = core::engine::get_module<scenes::scenes_module>();
222 auto& scene = scenes_module.active_scene();
223 auto& graph = scene.graph();
224
225 ++_solver_tick;
226
227 constexpr auto solver_iterations = 32u;
228
229 constexpr auto baumgarte_beta = 0.2f; // 0.1 - 0.3 typical
230 constexpr auto penetration_slop = 0.003f; // match positional correction slop
231 constexpr auto max_bias_velocity = 2.0f; // meters/second
232
233 constexpr auto friction_mu = 0.6f; // friction (placeholder constant; plug in material mixing later)
234
235 constexpr auto warm_start_scale = 1.0f;
236
237 // build solver constraints
238 auto solver_contacts = std::vector<solver_contact>{};
239 solver_contacts.reserve(collisions.size() * 4u);
240
241 for (const auto& collision_entry : collisions) {
242 const auto node_a = collision_entry.node_a;
243 const auto node_b = collision_entry.node_b;
244
245 auto& rigidbody_a = graph.get_component<physics::rigidbody>(node_a);
246 auto& rigidbody_b = graph.get_component<physics::rigidbody>(node_b);
247
248 if (rigidbody_a.is_static() && rigidbody_b.is_static()) {
249 continue;
250 }
251
252 const auto world_position_a = graph.world_position(node_a);
253 const auto world_position_b = graph.world_position(node_b);
254
255 const auto rotation_matrix_a = math::matrix_cast<math::matrix3x3>(graph.world_rotation(node_a));
256 const auto rotation_matrix_b = math::matrix_cast<math::matrix3x3>(graph.world_rotation(node_b));
257
258 const auto inverse_rotation_matrix_a = math::matrix3x3::transposed(rotation_matrix_a);
259 const auto inverse_rotation_matrix_b = math::matrix3x3::transposed(rotation_matrix_b);
260
261 // Ensure manifold normal points A -> B
262 auto contact_normal = collision_entry.manifold.normal;
263 const auto delta_ab = world_position_b - world_position_a;
264
265 if (math::vector3::dot(delta_ab, contact_normal) < 0.0f) {
266 contact_normal = -contact_normal;
267 }
268
269 const auto contact_count = collision_entry.manifold.contact_points.size();
270
271 if (contact_count == 0u) {
272 continue;
273 }
274
275 // Shared values for all contacts in this manifold
276 const auto inverse_mass_a = rigidbody_a.inverse_mass();
277 const auto inverse_mass_b = rigidbody_b.inverse_mass();
278
279 const auto inverse_inertia_tensor_world_a = rigidbody_a.inverse_inertia_tensor_world();
280 const auto inverse_inertia_tensor_world_b = rigidbody_b.inverse_inertia_tensor_world();
281
282 // bias velocity (same per manifold)
283 const auto penetration_depth = collision_entry.manifold.depth;
284 const auto penetration_error = std::max(penetration_depth - penetration_slop, 0.0f);
285
286 auto bias_velocity = 0.0f;
287
288 if (dt > 0.0f) {
289 bias_velocity = std::clamp(baumgarte_beta * penetration_error / dt, 0.0f, max_bias_velocity);
290 }
291
292 // create one solver contact per contact point
293 for (auto contact_point_index = std::size_t{0u}; contact_point_index < contact_count; ++contact_point_index) {
294 const auto& contact = collision_entry.manifold.contact_points[contact_point_index];
295 const auto contact_point_world = contact.position;
296
297 const auto r_a_world = contact_point_world - world_position_a;
298 const auto r_b_world = contact_point_world - world_position_b;
299
300 const auto local_anchor_a = inverse_rotation_matrix_a * r_a_world;
301 const auto local_anchor_b = inverse_rotation_matrix_b * r_b_world;
302
303 auto constraint = contact_constraint{};
304 constraint.normal = contact_normal;
305
306 constraint.r_a = contact_point_world - world_position_a;
307 constraint.r_b = contact_point_world - world_position_b;
308
309 // Relative velocity at the contact
310 const auto velocity_at_contact_a = rigidbody_a.velocity() + math::vector3::cross(rigidbody_a.angular_velocity(), constraint.r_a);
311
312 const auto velocity_at_contact_b = rigidbody_b.velocity() + math::vector3::cross(rigidbody_b.angular_velocity(), constraint.r_b);
313
314 const auto relative_velocity = velocity_at_contact_b - velocity_at_contact_a;
315
316 // Tangent (opposes tangential motion)
317 auto tangent1 = relative_velocity - contact_normal * math::vector3::dot(relative_velocity, contact_normal);
318
319 if (tangent1.length_squared() > 1e-12f) {
320 tangent1 = math::vector3::normalized(tangent1);
321 } else {
322 tangent1 = _any_perpendicular(contact_normal);
323 }
324
325 auto tangent2 = math::vector3::cross(contact_normal, tangent1);
326
327 constraint.tangent1 = tangent1;
328 constraint.tangent2 = tangent2;
329
330 // normal effective mass
331 {
332 const auto r_a_cross_n = math::vector3::cross(constraint.r_a, contact_normal);
333 const auto r_b_cross_n = math::vector3::cross(constraint.r_b, contact_normal);
334
335 const auto angular_term_a = math::vector3::dot(contact_normal, math::vector3::cross(inverse_inertia_tensor_world_a * r_a_cross_n, constraint.r_a));
336 const auto angular_term_b = math::vector3::dot(contact_normal, math::vector3::cross(inverse_inertia_tensor_world_b * r_b_cross_n, constraint.r_b));
337
338 const auto effective_mass = inverse_mass_a + inverse_mass_b + angular_term_a + angular_term_b;
339
340 constraint.normal_mass = (effective_mass > 1e-12f) ? (1.0f / effective_mass) : 0.0f;
341 }
342
343 // tangent1 effective mass
344 {
345 const auto r_a_cross_t = math::vector3::cross(constraint.r_a, tangent1);
346 const auto r_b_cross_t = math::vector3::cross(constraint.r_b, tangent1);
347
348 const auto angular_term_a = math::vector3::dot(tangent1, math::vector3::cross(inverse_inertia_tensor_world_a * r_a_cross_t, constraint.r_a));
349 const auto angular_term_b = math::vector3::dot(tangent1, math::vector3::cross(inverse_inertia_tensor_world_b * r_b_cross_t, constraint.r_b));
350
351 const auto effective_mass = inverse_mass_a + inverse_mass_b + angular_term_a + angular_term_b;
352 constraint.tangent1_mass = (effective_mass > 1e-12f) ? (1.0f / effective_mass) : 0.0f;
353 }
354
355 // tangent2 effective mass
356 {
357 const auto r_a_cross_t = math::vector3::cross(constraint.r_a, tangent2);
358 const auto r_b_cross_t = math::vector3::cross(constraint.r_b, tangent2);
359
360 const auto angular_term_a = math::vector3::dot(tangent2, math::vector3::cross(inverse_inertia_tensor_world_a * r_a_cross_t, constraint.r_a));
361 const auto angular_term_b = math::vector3::dot(tangent2, math::vector3::cross(inverse_inertia_tensor_world_b * r_b_cross_t, constraint.r_b));
362
363 const auto effective_mass = inverse_mass_a + inverse_mass_b + angular_term_a + angular_term_b;
364 constraint.tangent2_mass = (effective_mass > 1e-12f) ? (1.0f / effective_mass) : 0.0f;
365 }
366
367 // per-contact bias
368 {
369 const auto per_contact_error = std::max(contact.depth - penetration_slop, 0.0f);
370 constraint.bias = (dt > 0.0f) ? std::clamp(baumgarte_beta * per_contact_error / dt, 0.0f, max_bias_velocity) : 0.0f;
371 }
372
373 // warm start (cached impulses)
374 const auto contact_key = _contact_key(node_a, node_b, local_anchor_a, local_anchor_b);
375
376 if (auto cached_it = _contact_cache.find(contact_key); cached_it != _contact_cache.end()) {
377 constraint.normal_impulse = cached_it->second.normal_impulse * warm_start_scale;
378 constraint.tangent1_impulse = cached_it->second.tangent1_impulse * warm_start_scale;
379 constraint.tangent2_impulse = cached_it->second.tangent2_impulse * warm_start_scale;
380 }
381
382 constraint.last_seen = _solver_tick;
383
384 solver_contacts.push_back({node_a, node_b, contact_key, constraint});
385 }
386 }
387
388 // warm start (apply cached impulses once before iterations)
389 for (auto& solver_contact_entry : solver_contacts) {
390 auto& rigidbody_a = graph.get_component<physics::rigidbody>(solver_contact_entry.node_a);
391 auto& rigidbody_b = graph.get_component<physics::rigidbody>(solver_contact_entry.node_b);
392
393 const auto normal_impulse = solver_contact_entry.constraint.normal * solver_contact_entry.constraint.normal_impulse;
394 const auto tangent1_impulse = solver_contact_entry.constraint.tangent1 * solver_contact_entry.constraint.tangent1_impulse;
395 const auto tangent2_impulse = solver_contact_entry.constraint.tangent2 * solver_contact_entry.constraint.tangent2_impulse;
396
397 const auto warm_start_impulse = normal_impulse + tangent1_impulse + tangent2_impulse;
398
399 _apply_impulse(rigidbody_a, rigidbody_b, warm_start_impulse, solver_contact_entry.constraint.r_a, solver_contact_entry.constraint.r_b);
400 }
401
402 // sequential impulse solver
403 for (auto iteration_index = 0; iteration_index < solver_iterations; ++iteration_index) {
404 for (auto& solver_contact_entry : solver_contacts) {
405 auto& rigidbody_a = graph.get_component<physics::rigidbody>(solver_contact_entry.node_a);
406 auto& rigidbody_b = graph.get_component<physics::rigidbody>(solver_contact_entry.node_b);
407
408 // Recompute relative velocity at the contact
409 const auto velocity_at_contact_a = rigidbody_a.velocity() + math::vector3::cross(rigidbody_a.angular_velocity(), solver_contact_entry.constraint.r_a);
410 const auto velocity_at_contact_b = rigidbody_b.velocity() + math::vector3::cross(rigidbody_b.angular_velocity(), solver_contact_entry.constraint.r_b);
411
412 const auto relative_velocity = velocity_at_contact_b - velocity_at_contact_a;
413
414 // normal impulse
415 const auto normal_relative_speed = math::vector3::dot(relative_velocity, solver_contact_entry.constraint.normal);
416
417 auto normal_lambda = solver_contact_entry.constraint.normal_mass * (-(normal_relative_speed) + solver_contact_entry.constraint.bias);
418
419 const auto previous_normal_impulse = solver_contact_entry.constraint.normal_impulse;
420
421 solver_contact_entry.constraint.normal_impulse = std::max(previous_normal_impulse + normal_lambda, 0.0f);
422
423 normal_lambda = solver_contact_entry.constraint.normal_impulse - previous_normal_impulse;
424
425 if (normal_lambda != 0.0f) {
426 const auto normal_impulse = solver_contact_entry.constraint.normal * normal_lambda;
427
428 _apply_impulse(rigidbody_a, rigidbody_b, normal_impulse, solver_contact_entry.constraint.r_a, solver_contact_entry.constraint.r_b);
429 }
430
431 // friction impulse — tangent1
432 {
433 const auto speed = math::vector3::dot(relative_velocity, solver_contact_entry.constraint.tangent1);
434 auto lambda = solver_contact_entry.constraint.tangent1_mass * (-speed);
435
436 const auto max_f = friction_mu * solver_contact_entry.constraint.normal_impulse;
437 const auto prev = solver_contact_entry.constraint.tangent1_impulse;
438 solver_contact_entry.constraint.tangent1_impulse = std::clamp(prev + lambda, -max_f, max_f);
439 lambda = solver_contact_entry.constraint.tangent1_impulse - prev;
440
441 if (lambda != 0.0f) {
442 _apply_impulse(rigidbody_a, rigidbody_b, solver_contact_entry.constraint.tangent1 * lambda, solver_contact_entry.constraint.r_a, solver_contact_entry.constraint.r_b);
443 }
444 }
445
446 // friction impulse — tangent2
447 {
448 const auto speed = math::vector3::dot(relative_velocity, solver_contact_entry.constraint.tangent2);
449 auto lambda = solver_contact_entry.constraint.tangent2_mass * (-speed);
450
451 const auto max_f = friction_mu * solver_contact_entry.constraint.normal_impulse;
452 const auto prev = solver_contact_entry.constraint.tangent2_impulse;
453 solver_contact_entry.constraint.tangent2_impulse = std::clamp(prev + lambda, -max_f, max_f);
454 lambda = solver_contact_entry.constraint.tangent2_impulse - prev;
455
456 if (lambda != 0.0f) {
457 _apply_impulse(rigidbody_a, rigidbody_b, solver_contact_entry.constraint.tangent2 * lambda, solver_contact_entry.constraint.r_a, solver_contact_entry.constraint.r_b);
458 }
459 }
460 }
461 }
462
463 // write back cache + prune stale constraints
464 _update_cache(solver_contacts);
465 }
466
467 auto _update_character_controllers() -> void {
468 SBX_PROFILE_SCOPE("physics_module::_update_character_controllers");
469
470 auto& scenes_module = core::engine::get_module<scenes::scenes_module>();
471 auto& scene = scenes_module.active_scene();
472 auto& environment = scene.environment();
473 auto& graph = scene.graph();
474
475
476 }
477
478 auto _update_cache(const std::vector<solver_contact>& solver_contacts) -> void {
479 // prune old cached constraints after some frames
480 constexpr auto cache_ttl = std::uint64_t{60};
481
482 for (auto& solver_contact_entry : solver_contacts) {
483 _contact_cache[solver_contact_entry.key] = solver_contact_entry.constraint;
484 }
485
486 for (auto entry = _contact_cache.begin(); entry != _contact_cache.end(); ) {
487 const auto is_expired = (_solver_tick - entry->second.last_seen) > cache_ttl;
488
489 entry = is_expired ? _contact_cache.erase(entry) : ++entry;
490 }
491 }
492
493 auto _positional_correction(const std::vector<collision>& collisions) -> void {
494 auto& scenes_module = core::engine::get_module<scenes::scenes_module>();
495 auto& scene = scenes_module.active_scene();
496 auto& graph = scene.graph();
497
498 constexpr auto correction_percent = 0.8f;
499 constexpr auto penetration_slop = 0.003f;
500 constexpr auto max_correction = 0.05f;
501
502 for (const auto& collision_pair : collisions) {
503 auto& rigidbody_a = graph.get_component<physics::rigidbody>(collision_pair.node_a);
504 auto& rigidbody_b = graph.get_component<physics::rigidbody>(collision_pair.node_b);
505
506 if (rigidbody_a.is_static() && rigidbody_b.is_static()) {
507 continue;
508 }
509
510 auto& transform_a = graph.get_component<scenes::transform>(collision_pair.node_a);
511 auto& transform_b = graph.get_component<scenes::transform>(collision_pair.node_b);
512
513 auto contact_normal = collision_pair.manifold.normal;
514
515 const auto world_position_a = graph.world_position(collision_pair.node_a);
516 const auto world_position_b = graph.world_position(collision_pair.node_b);
517 const auto delta_ab = world_position_b - world_position_a;
518
519 if (math::vector3::dot(delta_ab, contact_normal) < 0.0f) {
520 contact_normal = -contact_normal;
521 }
522
523 const auto inverse_mass_a = rigidbody_a.inverse_mass();
524 const auto inverse_mass_b = rigidbody_b.inverse_mass();
525 const auto inverse_mass_sum = inverse_mass_a + inverse_mass_b;
526
527 if (inverse_mass_sum <= 0.0f) {
528 continue;
529 }
530
531 const auto penetration_depth = collision_pair.manifold.depth;
532 const auto penetration_error = std::max(penetration_depth - penetration_slop, 0.0f);
533
534 const auto raw_correction = correction_percent * penetration_error / inverse_mass_sum;
535 const auto clamped_correction = std::min(raw_correction, max_correction);
536
537 transform_a.position() -= contact_normal * (clamped_correction * inverse_mass_a);
538 transform_b.position() += contact_normal * (clamped_correction * inverse_mass_b);
539
540 transform_a.bump_version();
541 transform_b.bump_version();
542 }
543 }
544
545 static auto _quantize_scalar(std::float_t value, std::float_t grid) -> std::int32_t {
546 return static_cast<std::int32_t>(std::lround(value / grid));
547 }
548
549 static auto _quantize_vector3(const math::vector3& value, std::float_t grid) -> std::array<std::int32_t, 3u> {
550 return {_quantize_scalar(value.x(), grid), _quantize_scalar(value.y(), grid), _quantize_scalar(value.z(), grid)};
551 }
552
553 static auto _contact_key(scenes::node node_a, scenes::node node_b, const math::vector3& local_anchor_a, const math::vector3& local_anchor_b) -> std::size_t {
554 // 1 cm bins (tune 0.005f - 0.02f)
555 constexpr auto anchor_grid = std::float_t{0.01f};
556
557 const auto quant_a = _quantize_vector3(local_anchor_a, anchor_grid);
558 const auto quant_b = _quantize_vector3(local_anchor_b, anchor_grid);
559
560 auto hash = std::size_t{0};
561
562 utility::hash_combine(hash, node_a, node_b, quant_a[0], quant_a[1], quant_a[2], quant_b[0], quant_b[1], quant_b[2]);
563
564 return hash;
565 }
566
567 static auto _any_perpendicular(const math::vector3& n) -> math::vector3 {
568 const auto ax = std::abs(n.x()) < 0.9f ? math::vector3{1.0f, 0.0f, 0.0f} : math::vector3{0.0f, 1.0f, 0.0f};
569
570 auto tangent = math::vector3::cross(ax, n);
571
572 return math::vector3::normalized(tangent);
573 }
574
575 static auto _apply_impulse(physics::rigidbody& a, physics::rigidbody& b, const math::vector3& impulse, const math::vector3& r_a, const math::vector3& r_b) -> void {
576 const auto inverse_mass_a = a.inverse_mass();
577 const auto inverse_mass_b = b.inverse_mass();
578
579 if (inverse_mass_a > 0.0f) {
580 a.add_velocity(-impulse * inverse_mass_a);
581 a.add_angular_velocity(-(a.inverse_inertia_tensor_world() * math::vector3::cross(r_a, impulse)));
582 }
583
584 if (inverse_mass_b > 0.0f) {
585 b.add_velocity(impulse * inverse_mass_b);
586 b.add_angular_velocity( (b.inverse_inertia_tensor_world() * math::vector3::cross(r_b, impulse)));
587 }
588 }
589
590 std::unordered_map<std::size_t, contact_constraint> _contact_cache{};
591 std::uint64_t _solver_tick{0};
592
593}; // class physics_module
594
595} // namespace sbx::physics
596
597#endif // LIBSBX_PHYSICS_PHYSICS_MODULE_HPP_
Definition: tests.cpp:6
Definition: octree.hpp:23
Definition: module.hpp:92
Definition: quaternion.hpp:25
Definition: vector3.hpp:23
Definition: volume.hpp:14
static auto red() noexcept -> color
Returns a red color.
Definition: color.cpp:53
Definition: physics_module.hpp:37
Definition: rigidbody.hpp:15
Definition: transform.hpp:16
Mathematical constants.
Matrix casting and decomposition utilities.
Definition: octree.hpp:133
Definition: collision_detection.hpp:17
Definition: collision_detection.hpp:28
Definition: shape_collider.hpp:44