Unravel Engine C++ Reference
Loading...
Searching...
No Matches
bullet_backend.cpp
Go to the documentation of this file.
1#include "bullet_backend.h"
2#include "graphics/graphics.h"
3
5#include <engine/events.h>
6#include <math/transform.hpp>
7
12#include <engine/ecs/ecs.h>
13#include <engine/engine.h>
18
20#define BT_USE_SSE_IN_API
21#include <BulletCollision/CollisionDispatch/btCollisionDispatcherMt.h>
22#include <BulletCollision/NarrowPhaseCollision/btRaycastCallback.h>
23#include <BulletDynamics/ConstraintSolver/btSequentialImpulseConstraintSolverMt.h>
24#include <BulletDynamics/Dynamics/btDiscreteDynamicsWorldMt.h>
25
26#include <btBulletCollisionCommon.h>
27#include <btBulletDynamicsCommon.h>
28#include <BulletDynamics/Character/btKinematicCharacterController.h>
29#include <BulletCollision/CollisionDispatch/btGhostObject.h>
30
31#include <hpp/flat_map.hpp>
32#include <logging/logging.h>
33
34#include <algorithm>
35#include <utility>
36#include <vector>
37
38#ifdef NDEBUG
39#define BULLET_MT 1
40#endif
41
42#ifdef BULLET_MT
43#include "LinearMath/btThreads.h"
44#include <thread>
45#endif
46
47namespace
48{
49struct contact_key
50{
51 entt::handle a, b;
52 bool operator<(contact_key const& o) const
53 {
54 return a < o.a || (a == o.a && b < o.b);
55 }
56 bool operator==(contact_key const& o) const
57 {
58 return a == o.a && b == o.b;
59 }
60};
61} // namespace
62namespace std
63{
64template<>
65struct hash<contact_key>
66{
67 size_t operator()(contact_key const& k) const noexcept
68 {
69 // simple 64-bit combine
70 return (uint64_t)k.a.entity() * 0x9e3779b97f4a7c15ULL ^ ((uint64_t)k.b.entity() << 1);
71 }
72};
73} // namespace std
74
75namespace bullet
76{
77namespace
78{
79bool enable_logging = false;
80
81enum class manifold_type
82{
83 collision,
84 sensor
85};
86
87enum class event_type
88{
89 enter,
90 exit,
91 stay
92};
93
94struct contact_manifold
95{
96 manifold_type type{};
97 event_type event{};
98 entt::handle a{};
99 entt::handle b{};
100
101 std::vector<unravel::manifold_point> contacts;
102};
103
104const btVector3 gravity_sun(btScalar(0), btScalar(-274), btScalar(0));
105const btVector3 gravity_mercury(btScalar(0), btScalar(-3.7), btScalar(0));
106const btVector3 gravity_venus(btScalar(0), btScalar(-8.87), btScalar(0));
107const btVector3 gravity_earth(btScalar(0), btScalar(-9.8), btScalar(0));
108const btVector3 gravity_mars(btScalar(0), btScalar(-3.72), btScalar(0));
109const btVector3 gravity_jupiter(btScalar(0), btScalar(-24.79), btScalar(0));
110const btVector3 gravity_saturn(btScalar(0), btScalar(-10.44), btScalar(0));
111const btVector3 gravity_uranus(btScalar(0), btScalar(-8.69), btScalar(0));
112const btVector3 gravity_neptune(btScalar(0), btScalar(-11.15), btScalar(0));
113const btVector3 gravity_pluto(btScalar(0), btScalar(-0.62), btScalar(0));
114const btVector3 gravity_moon(btScalar(0), btScalar(-1.625), btScalar(0));
115
116auto to_bullet(const math::vec3& v) -> btVector3
117{
118 return {v.x, v.y, v.z};
119}
120
121auto from_bullet(const btVector3& v) -> math::vec3
122{
123 return {v.getX(), v.getY(), v.getZ()};
124}
125
126auto to_bullet(const math::quat& q) -> btQuaternion
127{
128 return {q.x, q.y, q.z, q.w};
129}
130
131auto from_bullet(const btQuaternion& q) -> math::quat
132{
133 math::quat r;
134 r.x = q.getX();
135 r.y = q.getY();
136 r.z = q.getZ();
137 r.w = q.getW();
138 return r;
139}
140
141auto to_bx(const btVector3& data) -> bx::Vec3
142{
143 return {data.getX(), data.getY(), data.getZ()};
144}
145
146auto to_bx_color(const btVector3& in) -> uint32_t
147{
148#define COL32_R_SHIFT 0
149#define COL32_G_SHIFT 8
150#define COL32_B_SHIFT 16
151#define COL32_A_SHIFT 24
152#define COL32_A_MASK 0xFF000000
153
154 uint32_t out = ((uint32_t)(in.getX() * 255.0f)) << COL32_R_SHIFT;
155 out |= ((uint32_t)(in.getY() * 255.0f)) << COL32_G_SHIFT;
156 out |= ((uint32_t)(in.getZ() * 255.0f)) << COL32_B_SHIFT;
157 out |= ((uint32_t)(1.0f * 255.0f)) << COL32_A_SHIFT;
158 return out;
159}
160
161class debugdraw : public btIDebugDraw
162{
163 int debug_mode_ = /*btIDebugDraw::DBG_DrawWireframe | */ btIDebugDraw::DBG_DrawContactPoints;
164 DefaultColors our_colors_;
165 gfx::dd_raii& dd_;
166 std::unique_ptr<DebugDrawEncoderScopePush> scope_;
167
168public:
169 debugdraw(gfx::dd_raii& dd) : dd_(dd)
170 {
171 }
172
173 void startLines()
174 {
175 if(!scope_)
176 {
177 scope_ = std::make_unique<DebugDrawEncoderScopePush>(dd_.encoder);
178 }
179 }
180
181 auto getDefaultColors() const -> DefaultColors override
182 {
183 return our_colors_;
184 }
187 void setDefaultColors(const DefaultColors& colors) override
188 {
189 our_colors_ = colors;
190 }
191
192 void drawLine(const btVector3& from1, const btVector3& to1, const btVector3& color1) override
193 {
194 startLines();
195
196 dd_.encoder.setColor(to_bx_color(color1));
197 dd_.encoder.moveTo(to_bx(from1));
198 dd_.encoder.lineTo(to_bx(to1));
199 }
200
201 void drawContactPoint(const btVector3& point_on_b,
202 const btVector3& normal_on_b,
203 btScalar distance,
204 int life_time,
205 const btVector3& color) override
206 {
207 drawLine(point_on_b, point_on_b + normal_on_b * distance, color);
208 btVector3 ncolor(0, 0, 0);
209 drawLine(point_on_b, point_on_b + normal_on_b * 0.1, ncolor);
210 }
211
212 void setDebugMode(int debugMode) override
213 {
214 debug_mode_ = debugMode;
215 }
216
217 auto getDebugMode() const -> int override
218 {
219 return debug_mode_;
220 }
221
222 void flushLines() override
223 {
224 scope_.reset();
225 }
226
227 void reportErrorWarning(const char* warningString) override
228 {
229 }
230
231 void draw3dText(const btVector3& location, const char* textString) override
232 {
233 }
234};
235
236static constexpr int COMBINE_BITS = 2;
237static constexpr int COMBINE_MASK = (1 << COMBINE_BITS) - 1; // 0b11
238static constexpr int FRICTION_SHIFT = COMBINE_BITS; // friction in bits [3..2]
239static constexpr int RESTITUTION_SHIFT = 0; // bounce in bits [1..0]
240
241inline int encode_combine_modes(unravel::combine_mode friction, unravel::combine_mode bounce)
242{
243 int f = (static_cast<int>(friction) & COMBINE_MASK) << FRICTION_SHIFT;
244 int b = (static_cast<int>(bounce) & COMBINE_MASK) << RESTITUTION_SHIFT;
245 return f | b;
246}
247
248inline unravel::combine_mode decode_friction_combine(int code)
249{
250 return static_cast<unravel::combine_mode>((code >> FRICTION_SHIFT) & COMBINE_MASK);
251}
252
253inline unravel::combine_mode decode_restitution_combine(int code)
254{
255 return static_cast<unravel::combine_mode>((code >> RESTITUTION_SHIFT) & COMBINE_MASK);
256}
257
258//------------------------------------------------------------------------------
259// 2) Helper to pick a single combine-mode when two bodies collide.
260// If both bodies requested the same mode, we use that. Otherwise, default to Average.
261// You can adjust this tie-breaking however you like.
262//------------------------------------------------------------------------------
263static unravel::combine_mode pick_combine_mode(unravel::combine_mode modeA, unravel::combine_mode modeB)
264{
265 if(modeA == modeB)
266 {
267 return modeA;
268 }
269 // If only one of them left at default 0 (Multiply) and you want to treat that
270 // differently, you could check for that here. For simplicity we go to Average any time
271 // they differ:
273}
274
275//------------------------------------------------------------------------------
276// 3) The global callback that Bullet will call for each new contact.
277// We read userIndex2 from each body to decide how to combine their restitutions.
278//------------------------------------------------------------------------------
279static btScalar per_body_combine(const btCollisionObject* body0,
280 const btCollisionObject* body1,
281 btScalar e0,
282 btScalar e1,
285{
286 // 3.3) Pick final combine mode:
287 auto mode = pick_combine_mode(mode0, mode1);
288
289 // 3.5) Compute combined restitution according to chosenMode:
290 btScalar combined;
291 switch(mode)
292 {
294 combined = e0 * e1;
295 break;
296
298 combined = (e0 + e1) * btScalar(0.5);
299 break;
300
302 combined = btMin(e0, e1);
303 break;
304
306 combined = btMax(e0, e1);
307 break;
308
309 default:
310 combined = e0 * e1; // fallback if somehow we get out-of-range
311 break;
312 }
313
314 // 3.7) Return true to indicate “we handled it.”
315 return combined;
316}
317
318//--------------------------------------------------------------------------------------
319// 1) Define your own combine‐functions (matching the CalculateCombinedCallback signature)
320//--------------------------------------------------------------------------------------
321
322static btScalar combined_restitution_callback(const btCollisionObject* body0, const btCollisionObject* body1)
323{
324 int raw_mode0 = body0->getUserIndex2();
325 int raw_mode1 = body1->getUserIndex2();
326 auto mode0 = decode_restitution_combine(raw_mode0);
327 auto mode1 = decode_restitution_combine(raw_mode1);
328
329 return per_body_combine(body0, body1, body0->getRestitution(), body1->getRestitution(), mode0, mode1);
330}
331
332static btScalar combined_friction_callback(const btCollisionObject* body0,
333 const btCollisionObject* body1,
334 btScalar f0,
335 btScalar f1)
336{
337 int raw_mode0 = body0->getUserIndex2();
338 int raw_mode1 = body1->getUserIndex2();
339 auto mode0 = decode_restitution_combine(raw_mode0);
340 auto mode1 = decode_restitution_combine(raw_mode1);
341
342 auto friction = per_body_combine(body0, body1, f0, f1, mode0, mode1);
343 const btScalar MAX_FRICTION = btScalar(10.);
344 if(friction < -MAX_FRICTION)
345 friction = -MAX_FRICTION;
346 if(friction > MAX_FRICTION)
347 friction = MAX_FRICTION;
348 return friction;
349}
350
351static btScalar combined_friction_callback(const btCollisionObject* body0, const btCollisionObject* body1)
352{
353 auto f0 = body0->getFriction();
354 auto f1 = body1->getFriction();
355 return combined_friction_callback(body0, body1, f0, f1);
356}
357
358static btScalar combined_rolling_friction_callback(const btCollisionObject* body0, const btCollisionObject* body1)
359{
360 auto f0 = body0->getFriction() * body0->getRollingFriction();
361 auto f1 = body1->getFriction() * body1->getRollingFriction();
362 return combined_friction_callback(body0, body1, f0, f1);
363}
364
365static btScalar combined_spinning_friction_callback(const btCollisionObject* body0, const btCollisionObject* body1)
366{
367 auto f0 = body0->getFriction() * body0->getSpinningFriction();
368 auto f1 = body1->getFriction() * body1->getSpinningFriction();
369 return combined_friction_callback(body0, body1, f0, f1);
370}
371
372void override_combine_callbacks()
373{
374 // Restitution:
375 gCalculateCombinedRestitutionCallback = combined_restitution_callback;
376
377 // Friction:
378 gCalculateCombinedFrictionCallback = combined_friction_callback;
379 gCalculateCombinedRollingFrictionCallback = combined_rolling_friction_callback;
380 gCalculateCombinedSpinningFrictionCallback = combined_spinning_friction_callback;
381}
382
383void setup_task_scheduler()
384{
385#ifdef BULLET_MT
386 // Select and initialize a task scheduler
387 btITaskScheduler* scheduler = btGetTaskScheduler();
388 if(!scheduler)
389 scheduler = btCreateDefaultTaskScheduler(); // Use Intel TBB if available
390
391 if(!scheduler)
392 scheduler = btGetSequentialTaskScheduler(); // Fallback to single-threaded
393
394 // Set the chosen scheduler
395 if(scheduler)
396 {
397 btSetTaskScheduler(scheduler);
398 }
399#endif
400}
401
402void cleanup_task_scheduler()
403{
404#ifdef BULLET_MT
405 // Select and initialize a task scheduler
406 btITaskScheduler* scheduler = btGetTaskScheduler();
407 if(scheduler)
408 {
409 btSetTaskScheduler(nullptr);
410 delete scheduler;
411 }
412
413#endif
414}
415
416auto get_entity_from_user_index(unravel::ecs& ec, int index) -> entt::handle
417{
418 auto id = static_cast<entt::entity>(index);
419
420 return ec.get_scene().create_handle(id);
421}
422
423auto get_entity_id_from_user_index(int index) -> entt::entity
424{
425 auto& ctx = unravel::engine::context();
426 auto& ec = ctx.get_cached<unravel::ecs>();
427 auto id = static_cast<entt::entity>(index);
428
429 return id;
430}
431
432auto has_scripting(entt::handle a) -> bool
433{
434 if(!a)
435 {
436 return false;
437 }
438 auto a_scirpt_comp = a.try_get<unravel::script_component>();
439 bool a_has_scripting = a_scirpt_comp && a_scirpt_comp->has_script_components();
440 return a_has_scripting;
441}
442
443auto should_record_collision_event(entt::handle a, entt::handle b) -> bool
444{
445 if(has_scripting(a))
446 {
447 return true;
448 }
449 if(has_scripting(b))
450 {
451 return true;
452 }
453
454 return false;
455}
456
457auto should_record_sensor_event(entt::handle a, entt::handle b) -> bool
458{
459 if(has_scripting(a))
460 {
461 return true;
462 }
463
464 return false;
465}
466
467template<typename Callback>
468class filter_ray_callback : public Callback
469{
470public:
473
474 filter_ray_callback(const btVector3& from, const btVector3& to, int mask, bool sensors)
475 : Callback(from, to)
476 , layer_mask(mask)
477 , query_sensors(sensors)
478 {
479 }
480
481 // Override needsCollision to apply custom filtering
482 auto needsCollision(btBroadphaseProxy* proxy0) const -> bool override
483 {
484 if(!Callback::needsCollision(proxy0))
485 {
486 return false;
487 }
488
489 // Apply layer mask filtering
490 if((proxy0->m_collisionFilterGroup & layer_mask) == 0)
491 {
492 return false;
493 }
494
495 const auto* co = static_cast<const btCollisionObject*>(proxy0->m_clientObject);
496
497 if(!query_sensors && (co->getCollisionFlags() & btCollisionObject::CF_NO_CONTACT_RESPONSE))
498 {
499 // Ignore sensors if querySensors is false
500 return false;
501 }
502
503 return true;
504 }
505};
506
507using filter_closest_ray_callback = filter_ray_callback<btCollisionWorld::ClosestRayResultCallback>;
508using filter_all_hits_ray_callback = filter_ray_callback<btCollisionWorld::AllHitsRayResultCallback>;
509
510// A custom callback that checks layer_mask and optionally ignores sensors.
511class sphere_closest_convex_result_callback : public btCollisionWorld::ClosestConvexResultCallback
512{
513public:
514 int layer_mask;
515 bool query_sensors;
516
517 sphere_closest_convex_result_callback(const btVector3& from, const btVector3& to, int layerMask, bool sensors)
518 : btCollisionWorld::ClosestConvexResultCallback(from, to)
519 , layer_mask(layerMask)
520 , query_sensors(sensors)
521 {
522 }
523
524 // If you’re using a filter callback approach, override needsCollision:
525 bool needsCollision(btBroadphaseProxy* proxy0) const override
526 {
527 // First call base
528 if(!btCollisionWorld::ClosestConvexResultCallback::needsCollision(proxy0))
529 return false;
530
531 if((proxy0->m_collisionFilterGroup & layer_mask) == 0)
532 {
533 return false;
534 }
535
536 // Then check layer mask
537 const btCollisionObject* co = static_cast<const btCollisionObject*>(proxy0->m_clientObject);
538
539 // Check for sensors if needed
540 if(!query_sensors && (co->getCollisionFlags() & btCollisionObject::CF_NO_CONTACT_RESPONSE))
541 {
542 // Ignore sensors if querySensors is false
543 return false;
544 }
545
546 return true;
547 }
548};
549
550class sphere_all_convex_result_callback : public btCollisionWorld::ConvexResultCallback
551{
552public:
553 int layer_mask;
554 bool query_sensors;
555 // We store all hits here
556 struct hit_info
557 {
558 const btCollisionObject* object = nullptr;
559 btVector3 normal;
560 btScalar fraction;
561 };
563
564 sphere_all_convex_result_callback(int layerMask, bool sensors) : layer_mask(layerMask), query_sensors(sensors)
565 {
566 m_closestHitFraction = btScalar(1.f);
567 }
568
569 // Called with each contact
570 btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) override
571 {
572 // Store the fraction, normal, object, etc.
573 hit_info hi;
574 hi.object = convexResult.m_hitCollisionObject;
575 hi.fraction = convexResult.m_hitFraction;
576
577 if(normalInWorldSpace)
578 hi.normal = convexResult.m_hitNormalLocal;
579 else
580 {
581 // transform normal
582 hi.normal =
583 convexResult.m_hitCollisionObject->getWorldTransform().getBasis() * convexResult.m_hitNormalLocal;
584 }
585 hits.push_back(hi);
586
587 // Return fraction so bullet can continue
588 // If we wanted to limit to the first or closest, we might do something else
589 return m_closestHitFraction;
590 }
591
592 bool needsCollision(btBroadphaseProxy* proxy0) const override
593 {
594 if(!ConvexResultCallback::needsCollision(proxy0))
595 return false;
596
597 // Layer mask
598 if((proxy0->m_collisionFilterGroup & layer_mask) == 0)
599 {
600 return false;
601 }
602
603 const btCollisionObject* co = static_cast<const btCollisionObject*>(proxy0->m_clientObject);
604 // Sensors
605 if(!query_sensors && (co->getCollisionFlags() & btCollisionObject::CF_NO_CONTACT_RESPONSE))
606 {
607 return false;
608 }
609
610 return true;
611 }
612};
613
614struct sphere_overlap_callback : btCollisionWorld::ContactResultCallback
615{
616 btCollisionObject* me{};
617
618 int layer_mask;
619 bool query_sensors;
620
622
623 sphere_overlap_callback(btCollisionObject* obj, int layerMask, bool sensors)
624 : me(obj)
625 , layer_mask(layerMask)
626 , query_sensors(sensors)
627 {
628 m_closestDistanceThreshold = btScalar(1.f);
629 }
630
631 bool needsCollision(btBroadphaseProxy* proxy0) const override
632 {
633 if(!btCollisionWorld::ContactResultCallback::needsCollision(proxy0))
634 return false;
635
636 // Layer mask
637 if((proxy0->m_collisionFilterGroup & layer_mask) == 0)
638 {
639 return false;
640 }
641
642 const btCollisionObject* co = static_cast<const btCollisionObject*>(proxy0->m_clientObject);
643 // Sensors
644 if(!query_sensors && (co->getCollisionFlags() & btCollisionObject::CF_NO_CONTACT_RESPONSE))
645 {
646 return false;
647 }
648
649 return true;
650 }
651
652 btScalar addSingleResult(btManifoldPoint&,
653 const btCollisionObjectWrapper* w0,
654 int,
655 int,
656 const btCollisionObjectWrapper* w1,
657 int,
658 int) override
659 {
660 const btCollisionObject* other =
661 (w0->getCollisionObject() == me ? w1->getCollisionObject() : w0->getCollisionObject());
662 hits.push_back(const_cast<btCollisionObject*>(other));
663 return 0;
664 }
665};
666
667struct rigidbody
668{
669 std::shared_ptr<btRigidBody> internal{};
670 std::shared_ptr<btCollisionShape> internal_shape{};
673};
674
675struct character_controller
676{
677 std::shared_ptr<btPairCachingGhostObject> ghost{};
678 std::shared_ptr<btCapsuleShape> shape{};
679 std::shared_ptr<btKinematicCharacterController> controller{};
682};
683
684struct world
685{
686 std::shared_ptr<btBroadphaseInterface> broadphase;
687 std::shared_ptr<btCollisionDispatcher> dispatcher;
688 std::shared_ptr<btConstraintSolver> solver;
689 std::shared_ptr<btConstraintSolverPoolMt> solver_pool;
690 std::shared_ptr<btDefaultCollisionConfiguration> collision_config;
691 std::shared_ptr<btDiscreteDynamicsWorld> dynamics_world;
692
693 struct contact_record
694 {
695 contact_record()
696 {
697 // Reserve a small typical number of contacts to avoid per-frame reallocation
698 cm.contacts.reserve(4);
699 }
700
701 contact_manifold cm;
702 bool active_this_frame = false;
703 };
704 hpp::flat_map<contact_key, contact_record> contacts_cache;
707
709 float elapsed{};
710
711 void add_rigidbody(const rigidbody& body)
712 {
713 if(body.internal->isInWorld())
714 {
715 return;
716 }
717
718 btAssert(in_simulate == false);
719
720 dynamics_world->addRigidBody(body.internal.get(), body.collision_filter_group, body.collision_filter_mask);
721 }
722
723 void remove_rigidbody(const rigidbody& body)
724 {
725 if(!body.internal->isInWorld())
726 {
727 return;
728 }
729 btAssert(in_simulate == false);
730 dynamics_world->removeRigidBody(body.internal.get());
731 }
732
733 void add_character_controller(const character_controller& cc)
734 {
735 btAssert(in_simulate == false);
736 dynamics_world->addCollisionObject(cc.ghost.get(),
737 cc.collision_filter_group,
738 cc.collision_filter_mask);
739 dynamics_world->addAction(cc.controller.get());
740 }
741
742 void remove_character_controller(const character_controller& cc)
743 {
744 btAssert(in_simulate == false);
745 dynamics_world->removeAction(cc.controller.get());
746 dynamics_world->removeCollisionObject(cc.ghost.get());
747 }
748
749 void process_manifold(unravel::script_system& scripting, const contact_manifold& manifold)
750 {
751 switch(manifold.type)
752 {
753 case manifold_type::sensor:
754 {
755 if(manifold.event == event_type::enter)
756 {
757 scripting.on_sensor_enter(manifold.a, manifold.b, manifold.contacts);
758 }
759 else
760 {
761 scripting.on_sensor_exit(manifold.a, manifold.b, manifold.contacts);
762 }
763
764 break;
765 }
766
767 case manifold_type::collision:
768 {
769 if(manifold.event == event_type::enter)
770 {
771 scripting.on_collision_enter(manifold.a, manifold.b, manifold.contacts);
772 }
773 else
774 {
775 scripting.on_collision_exit(manifold.a, manifold.b, manifold.contacts);
776 }
777 break;
778 }
779
780 default:
781 {
782 break;
783 }
784 }
785 }
786
787 void process_manifolds()
788 {
789 APP_SCOPE_PERF("Physics/Bullet/Process Manifolds");
790 auto& ctx = unravel::engine::context();
791 auto& scripting = ctx.get_cached<unravel::script_system>();
792 auto& ec = ctx.get_cached<unravel::ecs>();
793
794 auto* dispatcher = dynamics_world->getDispatcher();
795 int nm = dispatcher->getNumManifolds();
796
797 // Phase 0: clear active flags
798 for(auto& kv : contacts_cache)
799 kv.second.active_this_frame = false;
800
801 to_enter.clear();
802 to_exit.clear();
803 to_enter.reserve(nm);
804 to_exit.reserve(contacts_cache.size());
805
806 // Phase 1: scan all current manifolds
807 for(int i = 0; i < nm; ++i)
808 {
809 auto* m = dispatcher->getManifoldByIndexInternal(i);
810 if(m->getNumContacts() == 0)
811 continue;
812
813 // Identify entities and sensor flags
814 auto* objA = m->getBody0();
815 auto* objB = m->getBody1();
816 bool isSensorA = objA->getCollisionFlags() & btCollisionObject::CF_NO_CONTACT_RESPONSE;
817 bool isSensorB = objB->getCollisionFlags() & btCollisionObject::CF_NO_CONTACT_RESPONSE;
818 auto eA = get_entity_from_user_index(ec, objA->getUserIndex());
819 auto eB = get_entity_from_user_index(ec, objB->getUserIndex());
820
821 // Handle trigger overlaps: A->B and B->A
822 if(isSensorA || isSensorB)
823 {
824 // A->B if A is sensor
825 if(isSensorA)
826 {
827 contact_key key{eA, eB};
828 auto it = contacts_cache.find(key);
829 if(it != contacts_cache.end())
830 {
831 it->second.active_this_frame = true;
832 }
833 else
834 {
835 contact_manifold cm;
836 cm.type = manifold_type::sensor;
837 cm.event = event_type::enter;
838 cm.a = eA;
839 cm.b = eB;
840 cm.contacts.reserve(m->getNumContacts());
841 for(int j = 0; j < m->getNumContacts(); ++j)
842 {
843 auto const& p = m->getContactPoint(j);
845 mp.a = from_bullet(p.getPositionWorldOnA());
846 mp.b = from_bullet(p.getPositionWorldOnB());
847 mp.normal_on_b = from_bullet(p.m_normalWorldOnB);
848 mp.normal_on_a = -mp.normal_on_b;
849 mp.impulse = p.getAppliedImpulse();
850 mp.distance = p.getDistance();
851 cm.contacts.push_back(mp);
852 }
853 to_enter.push_back(cm);
854 auto& rec = contacts_cache.emplace(key, contact_record{}).first->second;
855 rec.cm = cm;
856 rec.active_this_frame = true;
857 }
858 }
859 // B->A if B is sensor
860 if(isSensorB)
861 {
862 contact_key key{eB, eA};
863 auto it = contacts_cache.find(key);
864 if(it != contacts_cache.end())
865 {
866 it->second.active_this_frame = true;
867 }
868 else
869 {
870 contact_manifold cm;
871 cm.type = manifold_type::sensor;
872 cm.event = event_type::enter;
873 cm.a = eB;
874 cm.b = eA;
875 cm.contacts.reserve(m->getNumContacts());
876 for(int j = 0; j < m->getNumContacts(); ++j)
877 {
878 auto const& p = m->getContactPoint(j);
880 mp.a = from_bullet(p.getPositionWorldOnB());
881 mp.b = from_bullet(p.getPositionWorldOnA());
882 mp.normal_on_a = from_bullet(p.m_normalWorldOnB);
883 mp.normal_on_b = -mp.normal_on_a;
884 mp.impulse = p.getAppliedImpulse();
885 mp.distance = p.getDistance();
886 cm.contacts.push_back(mp);
887 }
888 to_enter.push_back(cm);
889 auto& rec = contacts_cache.emplace(key, contact_record{}).first->second;
890 rec.cm = cm;
891 rec.active_this_frame = true;
892 }
893 }
894 continue;
895 }
896
897 // Handle collisions: only new ones cause ENTER
898 contact_key key{eA, eB};
899 auto it = contacts_cache.find(key);
900 if(it != contacts_cache.end())
901 {
902 // existing: refresh
903 it->second.active_this_frame = true;
904 }
905 else
906 {
907 // new collision
908 contact_manifold cm;
909 cm.type = manifold_type::collision;
910 cm.event = event_type::enter;
911 cm.a = eA;
912 cm.b = eB;
913 cm.contacts.reserve(m->getNumContacts());
914 for(int j = 0; j < m->getNumContacts(); ++j)
915 {
916 auto const& p = m->getContactPoint(j);
918 mp.a = from_bullet(p.getPositionWorldOnA());
919 mp.b = from_bullet(p.getPositionWorldOnB());
920 mp.normal_on_b = from_bullet(p.m_normalWorldOnB);
921 mp.normal_on_a = -mp.normal_on_b;
922 mp.impulse = p.getAppliedImpulse();
923 mp.distance = p.getDistance();
924 cm.contacts.push_back(mp);
925 }
926 to_enter.push_back(cm);
927 auto& rec = contacts_cache.emplace(key, contact_record{}).first->second;
928 rec.cm = cm;
929 rec.active_this_frame = true;
930 }
931 }
932
933 // Phase 2: EXIT for stale entries
934 for(auto it = contacts_cache.begin(); it != contacts_cache.end();)
935 {
936 if(!it->second.active_this_frame)
937 {
938 auto cm = it->second.cm;
939 cm.event = event_type::exit;
940 to_exit.push_back(cm);
941 it = contacts_cache.erase(it);
942 }
943 else
944 {
945 ++it;
946 }
947 }
948
949 // Phase 3: dispatch
950 for(auto& cm : to_enter)
951 {
952 process_manifold(scripting, cm);
953 }
954 for(auto& cm : to_exit)
955 {
956 process_manifold(scripting, cm);
957 }
958 }
959
960 void simulate(btScalar dt, btScalar fixed_time_step = 1.0 / 60.0, int max_subs_steps = 10)
961 {
962 APP_SCOPE_PERF("Physics/Bullet/Simulate Step");
963 in_simulate = true;
964
965 dynamics_world->stepSimulation(dt, max_subs_steps, fixed_time_step);
966
967 in_simulate = false;
968 }
969
970 auto ray_cast_closest(const math::vec3& origin,
971 const math::vec3& direction,
972 float max_distance,
973 int layer_mask,
974 bool query_sensors) -> hpp::optional<unravel::raycast_hit>
975 {
976 if(!dynamics_world)
977 {
978 return {};
979 }
980
981 auto ray_origin = to_bullet(origin);
982 auto ray_end = to_bullet(origin + direction * max_distance);
983
984 filter_closest_ray_callback ray_callback(ray_origin, ray_end, layer_mask, query_sensors);
985
986 ray_callback.m_flags |= btTriangleRaycastCallback::kF_UseGjkConvexCastRaytest;
987 dynamics_world->rayTest(ray_origin, ray_end, ray_callback);
988 if(ray_callback.hasHit())
989 {
990 const btRigidBody* body = btRigidBody::upcast(ray_callback.m_collisionObject);
991 if(body)
992 {
994 hit.entity = get_entity_id_from_user_index(body->getUserIndex());
995 hit.point = from_bullet(ray_callback.m_hitPointWorld);
996 hit.normal = from_bullet(ray_callback.m_hitNormalWorld);
997 hit.distance = math::distance(origin, hit.point);
998
999 return hit;
1000 }
1001 }
1002 return {};
1003 }
1004
1005 auto ray_cast_all(const math::vec3& origin,
1006 const math::vec3& direction,
1007 float max_distance,
1008 int layer_mask,
1010 {
1011 if(!dynamics_world)
1012 {
1013 return {};
1014 }
1015
1016 auto ray_origin = to_bullet(origin);
1017 auto ray_end = to_bullet(origin + direction * max_distance);
1018
1019 filter_all_hits_ray_callback ray_callback(ray_origin, ray_end, layer_mask, query_sensors);
1020
1021 ray_callback.m_flags |= btTriangleRaycastCallback::kF_UseGjkConvexCastRaytest;
1022 dynamics_world->rayTest(ray_origin, ray_end, ray_callback);
1023
1024 if(!ray_callback.hasHit())
1025 {
1026 return {};
1027 }
1028
1030
1031 // Collect all hits
1032 hits.reserve(ray_callback.m_hitPointWorld.size());
1033 for(int i = 0; i < ray_callback.m_hitPointWorld.size(); ++i)
1034 {
1035 const btCollisionObject* collision_object = ray_callback.m_collisionObjects[i];
1036 const btRigidBody* body = btRigidBody::upcast(collision_object);
1037
1038 if(body)
1039 {
1040 auto& hit = hits.emplace_back();
1041
1042 hit.entity = get_entity_id_from_user_index(body->getUserIndex());
1043 hit.point = from_bullet(ray_callback.m_hitPointWorld[i]);
1044 hit.normal = from_bullet(ray_callback.m_hitNormalWorld[i]);
1045 hit.distance = math::distance(origin, hit.point);
1046 }
1047 }
1048 return hits;
1049 }
1050
1051 // Then the function itself
1052 auto sphere_cast_closest(const math::vec3& origin,
1053 const math::vec3& direction,
1054 float radius,
1055 float max_distance,
1056 int layer_mask,
1057 bool query_sensors) -> hpp::optional<unravel::raycast_hit>
1058 {
1059 if(!dynamics_world)
1060 {
1061 return {};
1062 }
1063
1064 // Convert origin, direction to bullet
1065 btVector3 btOrigin = to_bullet(origin);
1066 btVector3 btEnd = to_bullet(origin + direction * max_distance);
1067
1068 // Create a temporary sphere shape
1069 // (We do *not* add this shape to the world, just use it for sweeping)
1070 btSphereShape shape(radius);
1071 // shape.setMargin(0.f); // optionally set margin=0
1072
1073 // Build transform from=to
1074 btTransform start, end;
1075 start.setIdentity();
1076 end.setIdentity();
1077 start.setOrigin(btOrigin);
1078 end.setOrigin(btEnd);
1079
1080 // Setup our custom callback
1081 bullet::sphere_closest_convex_result_callback cb(btOrigin, btEnd, layer_mask, query_sensors);
1082
1083 // Perform the sweep
1084 dynamics_world->convexSweepTest(&shape, start, end, cb);
1085
1086 // Check if we got a hit
1087 if(!cb.hasHit())
1088 return {}; // no hit
1089
1090 // Build a raycast_hit
1092 // The collision object
1093 const btCollisionObject* obj = cb.m_hitCollisionObject;
1094 // The fraction
1095 float fraction = cb.m_closestHitFraction;
1096 btVector3 hitPoint = btOrigin.lerp(btEnd, fraction);
1097 btVector3 normal = cb.m_hitNormalWorld;
1098
1099 // If you store user index as entity, etc.:
1100 const btRigidBody* body = btRigidBody::upcast(obj);
1101 if(body)
1102 {
1103 // e.g. get entity id from bullet user pointer or user index
1104 hit.entity = get_entity_id_from_user_index(body->getUserIndex());
1105 }
1106 else
1107 {
1108 // fallback if needed
1109 hit.entity = entt::null;
1110 }
1111
1112 hit.point = from_bullet(hitPoint);
1113 hit.normal = from_bullet(normal.normalized());
1114 hit.distance = fraction * max_distance; // approximate
1115
1116 return hit;
1117 }
1118
1119 auto sphere_cast_all(const math::vec3& origin,
1120 const math::vec3& direction,
1121 float radius,
1122 float max_distance,
1123 int layer_mask,
1125 {
1126 if(!dynamics_world)
1127 {
1128 return {};
1129 }
1130 // bullet transforms
1131 btVector3 btOrigin = to_bullet(origin);
1132 btVector3 btEnd = to_bullet(origin + direction * max_distance);
1133
1134 btTransform start, end;
1135 start.setIdentity();
1136 end.setIdentity();
1137 start.setOrigin(btOrigin);
1138 end.setOrigin(btEnd);
1139
1140 // shape
1141 btSphereShape shape(radius);
1142
1143 // custom callback
1144 sphere_all_convex_result_callback cb(layer_mask, query_sensors);
1145
1146 dynamics_world->convexSweepTest(&shape, start, end, cb);
1147
1148 // Now cb.hits has all hits in the order they were encountered
1149 // Typically not sorted by fraction, so let's sort them:
1150 std::sort(cb.hits.begin(),
1151 cb.hits.end(),
1152 [](auto& a, auto& b)
1153 {
1154 return a.fraction < b.fraction;
1155 });
1156
1157 // Build the final results
1159 hits.reserve(cb.hits.size());
1160
1161 for(const auto& hi : cb.hits)
1162 {
1163 auto& hit = hits.emplace_back();
1164
1165 const btRigidBody* body = btRigidBody::upcast(hi.object);
1166 if(body)
1167 {
1168 hit.entity = get_entity_id_from_user_index(body->getUserIndex());
1169 }
1170 else
1171 {
1172 hit.entity = entt::null;
1173 }
1174
1175 btVector3 hitPoint = btOrigin.lerp(btEnd, hi.fraction);
1176 hit.point = from_bullet(hitPoint);
1177 hit.normal = from_bullet(hi.normal.normalized());
1178 hit.distance = hi.fraction * max_distance;
1179 }
1180
1181 return hits;
1182 }
1183
1184 auto sphere_overlap(const math::vec3& origin, float radius, int layer_mask, bool query_sensors)
1186 {
1187 btSphereShape sphere(radius);
1188 btCollisionObject tempObj;
1189 tempObj.setCollisionShape(&sphere);
1190 tempObj.setWorldTransform(btTransform(btQuaternion::getIdentity(), to_bullet(origin)));
1191
1192 sphere_overlap_callback cb(&tempObj, layer_mask, query_sensors);
1193 dynamics_world->contactTest(&tempObj, cb);
1194
1195 // Build the final results
1197 hits.reserve(cb.hits.size());
1198
1199 for(const auto& hi : cb.hits)
1200 {
1201 auto& hit = hits.emplace_back();
1202
1203 const btRigidBody* body = btRigidBody::upcast(hi);
1204 if(body)
1205 {
1206 hit = get_entity_id_from_user_index(body->getUserIndex());
1207 }
1208 else
1209 {
1210 hit = entt::null;
1211 }
1212 }
1213
1214 return hits;
1215 }
1216};
1217
1218auto get_world_from_user_pointer(void* pointer) -> world&
1219{
1220 auto world = reinterpret_cast<bullet::world*>(pointer);
1221 return *world;
1222}
1223
1224auto create_dynamics_world() -> bullet::world
1225{
1226 bullet::world world{};
1228 auto collision_config = std::make_shared<btDefaultCollisionConfiguration>();
1229 // collision_config->setConvexConvexMultipointIterations();
1230
1231 auto broadphase = std::make_shared<btDbvtBroadphase>();
1232
1233#ifdef BULLET_MT
1234 auto dispatcher = std::make_shared<btCollisionDispatcherMt>(collision_config.get());
1235 auto solver_pool = std::make_shared<btConstraintSolverPoolMt>(std::thread::hardware_concurrency() - 1);
1236 auto solver = std::make_shared<btSequentialImpulseConstraintSolverMt>();
1237 world.dynamics_world = std::make_shared<btDiscreteDynamicsWorldMt>(dispatcher.get(),
1238 broadphase.get(),
1239 solver_pool.get(),
1240 solver.get(),
1241 collision_config.get());
1242 world.solver_pool = solver_pool;
1243#else
1244
1245 auto dispatcher = std::make_shared<btCollisionDispatcher>(collision_config.get());
1246 auto solver = std::make_shared<btSequentialImpulseConstraintSolver>();
1247 world.dynamics_world = std::make_shared<btDiscreteDynamicsWorld>(dispatcher.get(),
1248 broadphase.get(),
1249 solver.get(),
1250 collision_config.get());
1251#endif
1252 world.collision_config = collision_config;
1253 world.dispatcher = dispatcher;
1254 world.broadphase = broadphase;
1255 world.solver = solver;
1256 world.dynamics_world->setGravity(gravity_earth);
1257 world.dynamics_world->setForceUpdateAllAabbs(false);
1258 world.dynamics_world->getPairCache()->setInternalGhostPairCallback(new btGhostPairCallback());
1259 return world;
1260}
1261
1262ATTRIBUTE_ALIGNED16(class)
1263btCompoundShapeOwning : public btCompoundShape
1264{
1265public:
1266 BT_DECLARE_ALIGNED_ALLOCATOR();
1267
1268 ~btCompoundShapeOwning() override
1269 {
1270 /*delete all the btBU_Simplex1to4 ChildShapes*/
1271 for(int i = 0; i < m_children.size(); i++)
1272 {
1273 delete m_children[i].m_childShape;
1274 }
1275 }
1276};
1277} // namespace
1278} // namespace bullet
1279
1280namespace unravel
1281{
1282
1283namespace
1284{
1285const uint8_t system_id = transform_component::dirty_ids::physics;
1286
1287void wake_up(bullet::rigidbody& body)
1288{
1289 if(body.internal)
1290 {
1291 body.internal->activate(true);
1292 }
1293}
1294
1295// Builds one Bullet collision shape per submesh, each paired with the submesh's node
1296// transform (relative to the model root). The vertex buffer stores positions in node-local
1297// space; keeping a separate shape per submesh lets us apply the matching node transform via
1298// addChildShape so the collision geometry stays aligned with the rendered mesh.
1299auto create_bullet_mesh_shapes(const physics_mesh_shape& shape)
1300 -> std::vector<std::pair<btCollisionShape*, btTransform>>
1301{
1302 std::vector<std::pair<btCollisionShape*, btTransform>> result;
1303 const auto& mesh_ref = shape.mesh_asset.get();
1304
1305 // Get vertex and index data from mesh
1306 auto* vertex_data = mesh_ref->get_system_vb();
1307 auto* index_data = mesh_ref->get_system_ib();
1308 auto vertex_count = mesh_ref->get_vertex_count();
1309 auto face_count = mesh_ref->get_face_count();
1310 const auto& vertex_format = mesh_ref->get_vertex_format();
1311
1312 if(!vertex_data || !index_data || vertex_count == 0 || face_count == 0)
1313 {
1314 return result;
1315 }
1316
1317 // Find position attribute offset in vertex format
1318 auto position_offset = vertex_format.getOffset(bgfx::Attrib::Position);
1319
1320 if(position_offset == UINT16_MAX)
1321 {
1322 return result; // No position data
1323 }
1324
1325 const auto& submeshes = mesh_ref->get_submeshes();
1326 const auto node_transforms = mesh_ref->get_submesh_node_transforms();
1327 result.reserve(submeshes.size());
1328
1329 for(size_t s = 0; s < submeshes.size(); ++s)
1330 {
1331 const auto* submesh = submeshes[s];
1332 if(!submesh || submesh->face_start < 0 || submesh->face_count == 0)
1333 {
1334 continue;
1335 }
1336 const auto face_begin = static_cast<uint32_t>(submesh->face_start);
1337 if(face_begin >= face_count)
1338 {
1339 continue;
1340 }
1341
1342 // Build a triangle mesh from this submesh's faces only.
1343 auto* triangle_mesh = new btTriangleMesh(true, false); // 32-bit indices, 3-component vertices
1344 const auto face_end = std::min(face_begin + submesh->face_count, face_count);
1345 for(uint32_t f = face_begin; f < face_end; ++f)
1346 {
1347 uint32_t i0 = index_data[f * 3 + 0];
1348 uint32_t i1 = index_data[f * 3 + 1];
1349 uint32_t i2 = index_data[f * 3 + 2];
1350
1351 float v0[4];
1352 float v1[4];
1353 float v2[4];
1354 gfx::vertex_unpack(v0, gfx::attribute::Position, vertex_format, vertex_data, i0);
1355 gfx::vertex_unpack(v1, gfx::attribute::Position, vertex_format, vertex_data, i1);
1356 gfx::vertex_unpack(v2, gfx::attribute::Position, vertex_format, vertex_data, i2);
1357
1358 btVector3 vertex0(v0[0], v0[1], v0[2]);
1359 btVector3 vertex1(v1[0], v1[1], v1[2]);
1360 btVector3 vertex2(v2[0], v2[1], v2[2]);
1361 triangle_mesh->addTriangle(vertex0, vertex1, vertex2);
1362 }
1363
1364 // Create appropriate collision shape based on type
1365 btCollisionShape* collision_shape = nullptr;
1366 if(shape.collision_type == mesh_collision_type::convex)
1367 {
1368 // Create convex hull shape (can be dynamic)
1369 collision_shape = new btConvexTriangleMeshShape(triangle_mesh);
1370 }
1371 else
1372 {
1373 // Create concave BVH triangle mesh shape (static only, but accurate)
1374 collision_shape = new btBvhTriangleMeshShape(triangle_mesh, true); // Use quantized AABB compression
1375 }
1376
1377 // Apply the submesh's node transform. btTransform only carries rotation/translation,
1378 // so any node scale is applied via the shape's local scaling.
1379 btTransform child_transform = btTransform::getIdentity();
1380 if(s < node_transforms.size())
1381 {
1382 const auto& node_transform = node_transforms[s];
1383 child_transform.setRotation(bullet::to_bullet(node_transform.get_rotation()));
1384 child_transform.setOrigin(bullet::to_bullet(node_transform.get_position()));
1385 collision_shape->setLocalScaling(bullet::to_bullet(node_transform.get_scale()));
1386 }
1387
1388 result.emplace_back(collision_shape, child_transform);
1389 }
1390
1391 return result;
1392}
1393
1394auto make_rigidbody_shape(physics_component& comp) -> std::shared_ptr<btCompoundShape>
1395{
1396 // use an ownning compound shape. When sharing is implemented we can go back to non owning
1397 auto cp = std::make_shared<bullet::btCompoundShapeOwning>();
1398
1399 auto compound_shapes = comp.get_shapes();
1400 if(compound_shapes.empty())
1401 {
1402 return cp;
1403 }
1404
1405 for(const auto& s : compound_shapes)
1406 {
1407 if(hpp::holds_alternative<physics_box_shape>(s.shape))
1408 {
1409 const auto& shape = hpp::get<physics_box_shape>(s.shape);
1410 auto half_extends = shape.extends * 0.5f;
1411
1412 btBoxShape* box_shape = new btBoxShape({half_extends.x, half_extends.y, half_extends.z});
1413
1414 btTransform local_transform = btTransform::getIdentity();
1415 local_transform.setOrigin(bullet::to_bullet(shape.center));
1416 cp->addChildShape(local_transform, box_shape);
1417 }
1418 else if(hpp::holds_alternative<physics_sphere_shape>(s.shape))
1419 {
1420 const auto& shape = hpp::get<physics_sphere_shape>(s.shape);
1421
1422 btSphereShape* sphere_shape = new btSphereShape(shape.radius);
1423
1424 btTransform local_transform = btTransform::getIdentity();
1425 local_transform.setOrigin(bullet::to_bullet(shape.center));
1426 cp->addChildShape(local_transform, sphere_shape);
1427 }
1428 else if(hpp::holds_alternative<physics_capsule_shape>(s.shape))
1429 {
1430 const auto& shape = hpp::get<physics_capsule_shape>(s.shape);
1431
1432 btCapsuleShape* capsule_shape = new btCapsuleShape(shape.radius, shape.length);
1433
1434 btTransform local_transform = btTransform::getIdentity();
1435 local_transform.setOrigin(bullet::to_bullet(shape.center));
1436 cp->addChildShape(local_transform, capsule_shape);
1437 }
1438 else if(hpp::holds_alternative<physics_cylinder_shape>(s.shape))
1439 {
1440 const auto& shape = hpp::get<physics_cylinder_shape>(s.shape);
1441
1442 btVector3 half_extends(shape.radius, shape.length * 0.5f, shape.radius);
1443 btCylinderShape* cylinder_shape = new btCylinderShape(half_extends);
1444
1445 btTransform local_transform = btTransform::getIdentity();
1446 local_transform.setOrigin(bullet::to_bullet(shape.center));
1447 cp->addChildShape(local_transform, cylinder_shape);
1448 }
1449 else if(hpp::holds_alternative<physics_mesh_shape>(s.shape))
1450 {
1451 const auto& shape = hpp::get<physics_mesh_shape>(s.shape);
1452
1453 // Only create mesh shape if we have a valid mesh asset
1454 if(shape.mesh_asset && shape.mesh_asset.is_ready())
1455 {
1456 // One collision shape per submesh, each carrying its own node transform.
1457 auto mesh_shapes = create_bullet_mesh_shapes(shape);
1458 for(auto& [mesh_shape, node_transform] : mesh_shapes)
1459 {
1460 if(!mesh_shape)
1461 {
1462 continue;
1463 }
1464 // Apply the shape center on top of the submesh node transform.
1465 btTransform local_transform = node_transform;
1466 local_transform.setOrigin(local_transform.getOrigin() + bullet::to_bullet(shape.center));
1467 cp->addChildShape(local_transform, mesh_shape);
1468 }
1469 }
1470 }
1471 }
1472
1473 return cp;
1474}
1475
1476void update_rigidbody_shape(bullet::rigidbody& body, physics_component& comp)
1477{
1478 auto shape = make_rigidbody_shape(comp);
1479
1480 body.internal->setCollisionShape(shape.get());
1481 body.internal_shape = shape;
1482}
1483
1484void update_rigidbody_shape_scale(bullet::world& world, bullet::rigidbody& body, const math::vec3& s)
1485{
1486 auto bt_scale = body.internal_shape->getLocalScaling();
1487 auto scale = bullet::from_bullet(bt_scale);
1488
1489 if(math::any(math::epsilonNotEqual(scale, s, math::epsilon<float>())))
1490 {
1491 bt_scale = bullet::to_bullet(s);
1492 body.internal_shape->setLocalScaling(bt_scale);
1493 world.dynamics_world->updateSingleAabb(body.internal.get());
1494 }
1495}
1496
1497// Updated to preserve existing collision flags when switching kinematic/dynamic
1498void update_rigidbody_kind(bullet::rigidbody& body, physics_component& comp)
1499{
1500 // Read current flags
1501 auto flags = body.internal->getCollisionFlags();
1502 auto rbFlags = body.internal->getFlags();
1503
1504 if(comp.is_kinematic())
1505 {
1506 // Set kinematic bit, clear static if previously set
1507 flags |= btCollisionObject::CF_KINEMATIC_OBJECT;
1508 flags &= ~btCollisionObject::CF_DYNAMIC_OBJECT;
1509
1510 body.internal->setCollisionFlags(flags);
1511 }
1512 else
1513 {
1514 // Clear kinematic bit, optionally set dynamic bit
1515 flags &= ~btCollisionObject::CF_KINEMATIC_OBJECT;
1516 flags |= btCollisionObject::CF_DYNAMIC_OBJECT; // ensure dynamic flag
1517 body.internal->setCollisionFlags(flags);
1518 }
1519}
1520
1521void update_rigidbody_constraints(bullet::rigidbody& body, physics_component& comp)
1522{
1523 // Get freeze constraints for position and apply them
1524 auto freeze_position = comp.get_freeze_position();
1525 btVector3 linear_factor(float(!freeze_position.x), float(!freeze_position.y), float(!freeze_position.z));
1526 body.internal->setLinearFactor(linear_factor);
1527
1528 // Adjust velocity to respect linear constraints
1529 auto velocity = body.internal->getLinearVelocity();
1530 velocity *= linear_factor;
1531 body.internal->setLinearVelocity(velocity);
1532
1533 // Get freeze constraints for rotation and apply them
1534 auto freeze_rotation = comp.get_freeze_rotation();
1535 btVector3 angular_factor(float(!freeze_rotation.x), float(!freeze_rotation.y), float(!freeze_rotation.z));
1536 body.internal->setAngularFactor(angular_factor);
1537
1538 // Adjust angular velocity to respect angular constraints
1539 auto angular_velocity = body.internal->getAngularVelocity();
1540 angular_velocity *= angular_factor;
1541 body.internal->setAngularVelocity(angular_velocity);
1542
1543 // Ensure the body is active
1544 wake_up(body);
1545}
1546
1547void update_rigidbody_velocity(bullet::rigidbody& body, physics_component& comp)
1548{
1549 body.internal->setLinearVelocity(bullet::to_bullet(comp.get_velocity()));
1550
1551 wake_up(body);
1552}
1553
1554void update_rigidbody_angular_velocity(bullet::rigidbody& body, physics_component& comp)
1555{
1556 body.internal->setAngularVelocity(bullet::to_bullet(comp.get_angular_velocity()));
1557
1558 wake_up(body);
1559}
1560
1561void update_rigidbody_collision_layer(bullet::world& world, bullet::rigidbody& body, physics_component& comp)
1562{
1563 int filter_group = comp.get_owner().get<layer_component>().layers.mask;
1564 int filter_mask = comp.get_collision_mask().mask;
1565 body.collision_filter_group = filter_group;
1566 body.collision_filter_mask = filter_mask;
1567
1568 // bool is_dynamic = !(body.internal->isStaticObject() || body.internal->isKinematicObject());
1569 // body.collision_filter_group = is_dynamic ? body.collision_filter_group : int(unravel::layer_reserved::static_filter);
1570 // body.collision_filter_mask =
1571 // is_dynamic ? body.collision_filter_mask : body.collision_filter_mask ^
1572 // int(unravel::layer_reserved::static_filter);
1573 // 1) Get the body’s broadphase proxy
1574 btBroadphaseProxy* proxy = body.internal->getBroadphaseHandle();
1575 if(!proxy)
1576 {
1577 return; // or handle error
1578 }
1579
1580 if(body.collision_filter_group != proxy->m_collisionFilterGroup ||
1581 body.collision_filter_mask != proxy->m_collisionFilterMask)
1582 {
1583 // 2) Clean up any old pair cache usage
1584 world.dynamics_world->getBroadphase()->getOverlappingPairCache()->cleanProxyFromPairs(
1585 proxy,
1586 world.dynamics_world->getDispatcher());
1587
1588 // 3) Update filter group / mask
1589 proxy->m_collisionFilterGroup = body.collision_filter_group;
1590 proxy->m_collisionFilterMask = body.collision_filter_mask;
1591
1592 // 4) Re-insert it into the broadphase
1593 world.dynamics_world->refreshBroadphaseProxy(body.internal.get());
1594 wake_up(body);
1595 }
1596}
1597
1598void update_rigidbody_mass_and_inertia(bullet::rigidbody& body, physics_component& comp)
1599{
1600 btScalar mass(0);
1601 btVector3 local_inertia(0, 0, 0);
1602 if(!comp.is_kinematic())
1603 {
1604 auto shape = body.internal->getCollisionShape();
1605 if(shape)
1606 {
1607 mass = comp.get_mass();
1608 shape->calculateLocalInertia(mass, local_inertia);
1609 }
1610 }
1611 body.internal->setMassProps(mass, local_inertia);
1612}
1613
1614void update_rigidbody_gravity(bullet::world& world, bullet::rigidbody& body, physics_component& comp)
1615{
1616 if(comp.is_using_gravity())
1617 {
1618 body.internal->setGravity(world.dynamics_world->getGravity());
1619 }
1620 else
1621 {
1622 body.internal->setGravity(btVector3{0, 0, 0});
1623 body.internal->setLinearVelocity(btVector3(0, 0, 0));
1624 }
1625}
1626
1627void update_rigidbody_material(bullet::rigidbody& body, physics_component& comp)
1628{
1629 auto mat = comp.get_material().get();
1630
1631 int packed = bullet::encode_combine_modes(mat->friction_combine, mat->restitution_combine);
1632 if(body.internal->getUserIndex2() != packed)
1633 {
1634 body.internal->setUserIndex2(packed);
1635 }
1636
1637 if(math::epsilonNotEqual(body.internal->getRestitution(), mat->restitution, math::epsilon<float>()))
1638 {
1639 body.internal->setRestitution(mat->restitution);
1640 }
1641 if(math::epsilonNotEqual(body.internal->getFriction(), mat->friction, math::epsilon<float>()))
1642 {
1643 body.internal->setFriction(mat->friction);
1644 }
1645
1646 auto stiffness = mat->get_stiffness();
1647 if(math::epsilonNotEqual(body.internal->getContactStiffness(), stiffness, math::epsilon<float>()) ||
1648 math::epsilonNotEqual(body.internal->getContactDamping(), mat->damping, math::epsilon<float>()))
1649 {
1650 body.internal->setContactStiffnessAndDamping(stiffness, mat->damping);
1651 }
1652}
1653
1654void update_rigidbody_sensor(bullet::rigidbody& body, physics_component& comp)
1655{
1656 auto flags = body.internal->getCollisionFlags();
1657 if(comp.is_sensor())
1658 {
1659 body.internal->setCollisionFlags(flags | btCollisionObject::CF_NO_CONTACT_RESPONSE);
1660 }
1661 else
1662 {
1663 body.internal->setCollisionFlags(flags & ~btCollisionObject::CF_NO_CONTACT_RESPONSE);
1664 }
1665}
1666
1667void set_rigidbody_active(bullet::world& world, bullet::rigidbody& body, bool enabled)
1668{
1669 if(enabled)
1670 {
1671 world.add_rigidbody(body);
1672 }
1673 else
1674 {
1675 world.remove_rigidbody(body);
1676 }
1677}
1678
1679void update_rigidbody_full(bullet::world& world, bullet::rigidbody& body, physics_component& comp)
1680{
1681 update_rigidbody_kind(body, comp);
1682 update_rigidbody_shape(body, comp);
1683 update_rigidbody_mass_and_inertia(body, comp);
1684 update_rigidbody_material(body, comp);
1685 update_rigidbody_sensor(body, comp);
1686 update_rigidbody_constraints(body, comp);
1687 update_rigidbody_velocity(body, comp);
1688 update_rigidbody_angular_velocity(body, comp);
1689 update_rigidbody_gravity(world, body, comp);
1690 update_rigidbody_collision_layer(world, body, comp);
1691}
1692
1693void make_rigidbody(bullet::world& world, entt::handle entity, physics_component& comp)
1694{
1695 auto& body = entity.emplace<bullet::rigidbody>();
1696
1697 body.internal = std::make_shared<btRigidBody>(comp.get_mass(), nullptr, nullptr);
1698 body.internal->setUserIndex(int(entity.entity()));
1699 body.internal->setUserPointer(&world);
1700 body.internal->setFlags(BT_DISABLE_WORLD_GRAVITY);
1701
1702 update_rigidbody_full(world, body, comp);
1703
1704 if(entity.all_of<active_component>())
1705 {
1706 world.add_rigidbody(body);
1707 }
1708}
1709
1710void destroy_phyisics_body(bullet::world& world, entt::handle entity, bool from_physics_component)
1711{
1712 auto body = entity.try_get<bullet::rigidbody>();
1713
1714 if(body && body->internal)
1715 {
1716 world.remove_rigidbody(*body);
1717 }
1718
1719 if(from_physics_component)
1720 {
1721 entity.remove<bullet::rigidbody>();
1722 }
1723}
1724
1725void sync_physics_body(bullet::world& world, physics_component& comp, bool force = false)
1726{
1727 auto owner = comp.get_owner();
1728
1729 if(force)
1730 {
1731 destroy_phyisics_body(world, comp.get_owner(), true);
1732 make_rigidbody(world, owner, comp);
1733 }
1734 else
1735 {
1736 auto& body = owner.get<bullet::rigidbody>();
1737
1738 if(comp.is_property_dirty(physics_property::kind))
1739 {
1740 set_rigidbody_active(world, body, false);
1741 update_rigidbody_full(world, body, comp);
1742 set_rigidbody_active(world, body, true);
1743 }
1744 else
1745 {
1746 if(comp.is_property_dirty(physics_property::shape))
1747 {
1748 comp.set_property_dirty(physics_property::mass, true);
1749 update_rigidbody_shape(body, comp);
1750 world.dynamics_world->updateSingleAabb(body.internal.get());
1751 }
1752 if(comp.is_property_dirty(physics_property::mass))
1753 {
1754 update_rigidbody_mass_and_inertia(body, comp);
1755 }
1756
1757 if(comp.is_property_dirty(physics_property::sensor))
1758 {
1759 update_rigidbody_sensor(body, comp);
1760 }
1761
1762 if(comp.is_property_dirty(physics_property::constraints))
1763 {
1764 update_rigidbody_constraints(body, comp);
1765 comp.set_property_dirty(physics_property::gravity, true);
1766 }
1767 if(comp.is_property_dirty(physics_property::velocity))
1768 {
1769 update_rigidbody_velocity(body, comp);
1770 }
1771 if(comp.is_property_dirty(physics_property::angular_velocity))
1772 {
1773 update_rigidbody_angular_velocity(body, comp);
1774 }
1775
1776 if(comp.is_property_dirty(physics_property::gravity))
1777 {
1778 update_rigidbody_gravity(world, body, comp);
1779 }
1780
1781 // here we check internally for a change
1782 update_rigidbody_material(body, comp);
1783 update_rigidbody_collision_layer(world, body, comp);
1784 }
1785
1786 if(!comp.is_kinematic())
1787 {
1788 if(comp.are_any_properties_dirty())
1789 {
1790 wake_up(body);
1791 }
1792 }
1793 }
1794
1795 comp.set_dirty(system_id, false);
1796}
1797
1798auto sync_transforms(bullet::world& world, physics_component& comp, const transform_component& transform) -> bool
1799{
1800 auto owner = comp.get_owner();
1801 auto& body = owner.get<bullet::rigidbody>();
1802
1803 if(!body.internal)
1804 {
1805 return false;
1806 }
1807
1808 const auto& p = transform.get_position_global();
1809 const auto& q = transform.get_rotation_global();
1810 const auto& s = transform.get_scale_global();
1811
1812 auto bt_pos = bullet::to_bullet(p);
1813 auto bt_rot = bullet::to_bullet(q);
1814 btTransform bt_trans(bt_rot, bt_pos);
1815 body.internal->setWorldTransform(bt_trans);
1816
1817 if(body.internal_shape && comp.is_autoscaled())
1818 {
1819 update_rigidbody_shape_scale(world, body, s);
1820 }
1821
1822 wake_up(body);
1823
1824 return true;
1825}
1826
1827auto sync_state(physics_component& comp) -> bool
1828{
1829 auto owner = comp.get_owner();
1830 auto body = owner.try_get<bullet::rigidbody>();
1831
1832 if(!body || !body->internal)
1833 {
1834 return false;
1835 }
1836
1837 if(!body->internal->isActive())
1838 {
1839 return false;
1840 }
1841
1842 comp.set_velocity(bullet::from_bullet(body->internal->getLinearVelocity()));
1843 comp.set_angular_velocity(bullet::from_bullet(body->internal->getAngularVelocity()));
1844
1845 return true;
1846}
1847
1848auto sync_transforms(physics_component& comp, transform_component& transform) -> bool
1849{
1850 auto owner = comp.get_owner();
1851 auto body = owner.try_get<bullet::rigidbody>();
1852
1853 if(!body || !body->internal)
1854 {
1855 return false;
1856 }
1857
1858 if(!body->internal->isActive())
1859 {
1860 return false;
1861 }
1862
1863 const auto& bt_trans = body->internal->getWorldTransform();
1864 auto p = bullet::from_bullet(bt_trans.getOrigin());
1865 auto q = bullet::from_bullet(bt_trans.getRotation());
1866
1867 // Here we are using a more generous epsilon to
1868 // take into account any conversion errors between us and bullet
1869 float epsilon = 0.009f;
1870 return transform.set_position_and_rotation_global(p, q, epsilon);
1871}
1872
1873auto to_physics(bullet::world& world, transform_component& transform, physics_component& comp) -> bool
1874{
1875 bool transform_dirty = transform.is_dirty(system_id);
1876 bool rigidbody_dirty = comp.is_dirty(system_id);
1877
1878 // if(rigidbody_dirty)
1879 {
1880 sync_physics_body(world, comp);
1881 }
1882
1883 if(transform_dirty || rigidbody_dirty)
1884 {
1885 return sync_transforms(world, comp, transform);
1886 }
1887
1888 return false;
1889}
1890
1891auto from_physics(bullet::world& world, transform_component& transform, physics_component& comp) -> bool
1892{
1893 sync_state(comp);
1894
1895 bool result = sync_transforms(comp, transform);
1896
1897 transform.set_dirty(system_id, false);
1898 comp.set_dirty(system_id, false);
1899
1900 return result;
1901}
1902
1903void make_character_controller_body(bullet::world& world,
1904 entt::handle entity,
1905 character_controller_component& comp)
1906{
1907 auto& cc = entity.emplace<bullet::character_controller>();
1908 float capsule_half_height = (comp.get_height() - 2.0f * comp.get_radius()) * 0.5f;
1909 if(capsule_half_height < 0.0f)
1910 {
1911 capsule_half_height = 0.0f;
1912 }
1913 cc.shape = std::make_shared<btCapsuleShape>(comp.get_radius(), capsule_half_height * 2.0f);
1914 cc.ghost = std::make_shared<btPairCachingGhostObject>();
1915 cc.ghost->setCollisionShape(cc.shape.get());
1916 cc.ghost->setCollisionFlags(btCollisionObject::CF_CHARACTER_OBJECT | btCollisionObject::CF_KINEMATIC_OBJECT);
1917 cc.ghost->setUserIndex(int(entity.entity()));
1918 cc.ghost->setUserPointer(&world);
1919 cc.collision_filter_group = entity.get<layer_component>().layers.mask;
1920 cc.collision_filter_mask = comp.get_collision_mask().mask;
1921 cc.controller = std::make_shared<btKinematicCharacterController>(
1922 cc.ghost.get(), cc.shape.get(), comp.get_step_height());
1923 cc.controller->setMaxSlope(math::radians(comp.get_slope_limit()));
1924 cc.controller->setGravity(world.dynamics_world->getGravity() * comp.get_gravity_scale());
1925 cc.controller->setFallSpeed(comp.get_terminal_velocity());
1926 cc.controller->setLinearDamping(comp.get_linear_damping());
1927 auto& transform = entity.get<transform_component>();
1928 const auto& p = transform.get_position_global();
1929 const auto& q = transform.get_rotation_global();
1930 btTransform bt_trans(bullet::to_bullet(q), bullet::to_bullet(p + comp.get_center()));
1931 cc.ghost->setWorldTransform(bt_trans);
1932 if(entity.all_of<active_component>())
1933 {
1934 world.add_character_controller(cc);
1935 }
1936}
1937
1938void destroy_character_controller_body(bullet::world& world,
1939 entt::handle entity,
1940 bool from_cc_component)
1941{
1942 auto cc = entity.try_get<bullet::character_controller>();
1943 if(cc && cc->controller)
1944 {
1945 world.remove_character_controller(*cc);
1946 }
1947 if(from_cc_component)
1948 {
1949 entity.remove<bullet::character_controller>();
1950 }
1951}
1952
1953void sync_character_controller_body(bullet::world& world,
1954 character_controller_component& comp,
1955 bool force = false)
1956{
1957 auto owner = comp.get_owner();
1958 if(force)
1959 {
1960 destroy_character_controller_body(world, owner, true);
1961 make_character_controller_body(world, owner, comp);
1962 }
1963 else
1964 {
1965 auto* cc = owner.try_get<bullet::character_controller>();
1966 if(!cc || !cc->controller)
1967 {
1968 return;
1969 }
1970 if(comp.is_property_dirty(character_controller_property::shape) ||
1971 comp.is_property_dirty(character_controller_property::skin_width))
1972 {
1973 destroy_character_controller_body(world, owner, true);
1974 make_character_controller_body(world, owner, comp);
1975 comp.set_dirty(system_id, false);
1976 return;
1977 }
1978 if(comp.is_property_dirty(character_controller_property::step_height))
1979 {
1980 cc->controller->setStepHeight(comp.get_step_height());
1981 }
1982 if(comp.is_property_dirty(character_controller_property::slope_limit))
1983 {
1984 cc->controller->setMaxSlope(math::radians(comp.get_slope_limit()));
1985 }
1986 if(comp.is_property_dirty(character_controller_property::gravity_scale))
1987 {
1988 cc->controller->setGravity(world.dynamics_world->getGravity() * comp.get_gravity_scale());
1989 }
1990 if(comp.is_property_dirty(character_controller_property::layer))
1991 {
1992 destroy_character_controller_body(world, owner, true);
1993 make_character_controller_body(world, owner, comp);
1994 comp.set_dirty(system_id, false);
1995 return;
1996 }
1997 if(comp.is_property_dirty(character_controller_property::movement_params))
1998 {
1999 cc->controller->setFallSpeed(comp.get_terminal_velocity());
2000 cc->controller->setLinearDamping(comp.get_linear_damping());
2001 }
2002 }
2003 comp.set_dirty(system_id, false);
2004}
2005
2006auto to_physics_cc(bullet::world& world,
2007 transform_component& transform,
2008 character_controller_component& comp) -> bool
2009{
2010 bool transform_dirty = transform.is_dirty(system_id);
2011 bool cc_dirty = comp.is_dirty(system_id);
2012 sync_character_controller_body(world, comp);
2013 if(transform_dirty || cc_dirty)
2014 {
2015 auto owner = comp.get_owner();
2016 auto* cc = owner.try_get<bullet::character_controller>();
2017 if(!cc || !cc->ghost)
2018 {
2019 return false;
2020 }
2021 const auto& p = transform.get_position_global();
2022 const auto& q = transform.get_rotation_global();
2023 btTransform bt_trans(bullet::to_bullet(q), bullet::to_bullet(p + comp.get_center()));
2024 cc->ghost->setWorldTransform(bt_trans);
2025 return true;
2026 }
2027 return false;
2028}
2029
2030auto from_physics_cc(bullet::world& world,
2031 transform_component& transform,
2032 character_controller_component& comp) -> bool
2033{
2034 auto owner = comp.get_owner();
2035 auto* cc = owner.try_get<bullet::character_controller>();
2036 if(!cc || !cc->ghost)
2037 {
2038 return false;
2039 }
2040 const auto& bt_trans = cc->ghost->getWorldTransform();
2041 auto p = bullet::from_bullet(bt_trans.getOrigin()) - comp.get_center();
2042 auto q = bullet::from_bullet(bt_trans.getRotation());
2043 float epsilon = 0.009f;
2044 bool changed = transform.set_position_and_rotation_global(p, q, epsilon);
2045
2046 comp.set_grounded(cc->controller->onGround());
2047 auto bt_vel = cc->controller->getLinearVelocity();
2048 comp.set_velocity_internal(bullet::from_bullet(bt_vel));
2049
2050 transform.set_dirty(system_id, false);
2051 comp.set_dirty(system_id, false);
2052 return changed;
2053}
2054
2055auto add_force(btRigidBody* body, const btVector3& force, force_mode mode) -> bool
2056{
2057 if(force.fuzzyZero())
2058 {
2059 return false;
2060 }
2061 // Apply force based on ForceMode
2062 switch(mode)
2063 {
2064 case force_mode::force: // Continuous force
2065 body->applyCentralForce(force);
2066 break;
2067
2069 { // Force independent of mass
2070 btVector3 acceleration_force = force * body->getMass();
2071 body->applyCentralForce(acceleration_force);
2072 break;
2073 }
2074
2075 case force_mode::impulse: // Instantaneous impulse
2076 body->applyCentralImpulse(force);
2077 break;
2078
2079 case force_mode::velocity_change: // Direct velocity change
2080 {
2081 btVector3 new_velocity = body->getLinearVelocity() + force; // Accumulate velocity
2082 body->setLinearVelocity(new_velocity);
2083 break;
2084 }
2085 }
2086 return true;
2087}
2088
2089auto add_torque(btRigidBody* body, const btVector3& torque, force_mode mode) -> bool
2090{
2091 if(torque.fuzzyZero())
2092 {
2093 return false;
2094 }
2095 // Apply force based on ForceMode
2096 switch(mode)
2097 {
2098 case force_mode::force: // Continuous torque
2099 body->applyTorque(torque);
2100 break;
2101
2102 case force_mode::acceleration: // Angular acceleration
2103 {
2104 btVector3 inertia_tensor = body->getInvInertiaDiagLocal();
2105 btVector3 angular_acceleration(
2106 inertia_tensor.getX() != 0 ? torque.getX() * (1.0f / inertia_tensor.getX()) : 0.0f,
2107 inertia_tensor.getY() != 0 ? torque.getY() * (1.0f / inertia_tensor.getY()) : 0.0f,
2108 inertia_tensor.getZ() != 0 ? torque.getZ() * (1.0f / inertia_tensor.getZ()) : 0.0f);
2109 body->applyTorque(angular_acceleration);
2110 }
2111 break;
2112
2113 case force_mode::impulse: // Angular impulse
2114 body->applyTorqueImpulse(torque);
2115 break;
2116
2117 case force_mode::velocity_change: // Direct angular velocity change
2118 {
2119 btVector3 new_velocity = body->getLinearVelocity() + torque; // Accumulate velocity
2120 body->setAngularVelocity(new_velocity);
2121 break;
2122 }
2123 }
2124
2125 return true;
2126}
2127
2128} // namespace
2129
2131{
2132 bullet::setup_task_scheduler();
2133 bullet::override_combine_callbacks();
2134}
2135
2137{
2138 bullet::cleanup_task_scheduler();
2139}
2140
2141void bullet_backend::on_create_component(entt::registry& r, entt::entity e)
2142{
2143 // this function will be called for both physics_component and bullet::rigidbody
2144 auto world = r.ctx().find<bullet::world>();
2145 if(world)
2146 {
2147 entt::handle entity(r, e);
2148 auto& phisics = entity.get<physics_component>();
2149 sync_physics_body(*world, phisics, true);
2150 }
2151}
2152
2153void bullet_backend::on_destroy_component(entt::registry& r, entt::entity e)
2154{
2155 // this function will be called for both physics_component and bullet::rigidbody
2156 auto world = r.ctx().find<bullet::world>();
2157 if(world)
2158 {
2159 entt::handle entity(r, e);
2160 destroy_phyisics_body(*world, entity, true);
2161 }
2162}
2163
2164void bullet_backend::on_destroy_bullet_rigidbody_component(entt::registry& r, entt::entity e)
2165{
2166 // this function will be called for both physics_component and bullet::rigidbody
2167 auto world = r.ctx().find<bullet::world>();
2168 if(world)
2169 {
2170 entt::handle entity(r, e);
2171 destroy_phyisics_body(*world, entity, false);
2172 }
2173}
2174
2175void bullet_backend::on_create_cc_component(entt::registry& r, entt::entity e)
2176{
2177 auto world = r.ctx().find<bullet::world>();
2178 if(world)
2179 {
2180 entt::handle entity(r, e);
2181 auto& comp = entity.get<character_controller_component>();
2182 sync_character_controller_body(*world, comp, true);
2183 }
2184}
2185
2186void bullet_backend::on_destroy_cc_component(entt::registry& r, entt::entity e)
2187{
2188 auto world = r.ctx().find<bullet::world>();
2189 if(world)
2190 {
2191 entt::handle entity(r, e);
2192 destroy_character_controller_body(*world, entity, true);
2193 }
2194}
2195
2196void bullet_backend::on_destroy_bullet_cc_component(entt::registry& r, entt::entity e)
2197{
2198 auto world = r.ctx().find<bullet::world>();
2199 if(world)
2200 {
2201 entt::handle entity(r, e);
2202 destroy_character_controller_body(*world, entity, false);
2203 }
2204}
2205
2206void bullet_backend::move_character(character_controller_component& comp, const math::vec3& displacement)
2207{
2208 auto owner = comp.get_owner();
2209 auto* cc = owner.try_get<bullet::character_controller>();
2210 if(!cc || !cc->controller)
2211 {
2212 return;
2213 }
2214 cc->controller->setWalkDirection(bullet::to_bullet(displacement));
2215}
2216
2218{
2219 auto owner = comp.get_owner();
2220 auto* cc = owner.try_get<bullet::character_controller>();
2221 if(!cc || !cc->controller)
2222 {
2223 return;
2224 }
2225 cc->controller->jump(bullet::to_bullet(direction));
2226}
2227
2229{
2230 auto owner = comp.get_owner();
2231 auto* cc = owner.try_get<bullet::character_controller>();
2232 if(!cc || !cc->controller)
2233 {
2234 return;
2235 }
2236 cc->controller->applyImpulse(bullet::to_bullet(impulse));
2237}
2238
2240{
2241 auto owner = comp.get_owner();
2242 auto* cc = owner.try_get<bullet::character_controller>();
2243 if(!cc || !cc->controller)
2244 {
2245 return;
2246 }
2247 cc->controller->warp(bullet::to_bullet(position + comp.get_center()));
2248}
2249
2251{
2252 auto owner = comp.get_owner();
2253 auto* cc = owner.try_get<bullet::character_controller>();
2254 if(!cc || !cc->controller)
2255 {
2256 return;
2257 }
2258 cc->controller->setLinearVelocity(bullet::to_bullet(velocity));
2259}
2260
2262{
2263 auto owner = comp.get_owner();
2264 auto* cc = owner.try_get<bullet::character_controller>();
2265 if(!cc || !cc->controller)
2266 {
2267 return;
2268 }
2269 comp.set_grounded(cc->controller->onGround());
2270 comp.set_velocity_internal(bullet::from_bullet(cc->controller->getLinearVelocity()));
2271}
2272
2273void bullet_backend::on_create_active_component(entt::registry& r, entt::entity e)
2274{
2275 auto world = r.ctx().find<bullet::world>();
2276 if(world)
2277 {
2278 entt::handle entity(r, e);
2279 auto body = entity.try_get<bullet::rigidbody>();
2280 if(body)
2281 {
2282 set_rigidbody_active(*world, *body, true);
2283 }
2284 auto cc = entity.try_get<bullet::character_controller>();
2285 if(cc)
2286 {
2287 world->add_character_controller(*cc);
2288 }
2289 }
2290}
2291
2292void bullet_backend::on_destroy_active_component(entt::registry& r, entt::entity e)
2293{
2294 auto world = r.ctx().find<bullet::world>();
2295 if(world)
2296 {
2297 entt::handle entity(r, e);
2298 auto body = entity.try_get<bullet::rigidbody>();
2299 if(body)
2300 {
2301 set_rigidbody_active(*world, *body, false);
2302 }
2303 auto cc = entity.try_get<bullet::character_controller>();
2304 if(cc)
2305 {
2306 world->remove_character_controller(*cc);
2307 }
2308 }
2309}
2310
2312 float explosion_force,
2313 const math::vec3& explosion_position,
2314 float explosion_radius,
2315 float upwards_modifier,
2316 force_mode mode)
2317{
2318 auto owner = comp.get_owner();
2319
2320 if(auto bbody = owner.try_get<bullet::rigidbody>())
2321 {
2322 const auto& body = bbody->internal;
2323
2324 // Ensure the object is a dynamic rigid body
2325 if(body && body->getInvMass() > 0)
2326 {
2327 // Get the position of the rigid body
2328 btVector3 body_position = body->getWorldTransform().getOrigin();
2329
2330 // Calculate the vector from the explosion position to the body
2331 btVector3 direction = body_position - bullet::to_bullet(explosion_position);
2332 float distance = direction.length();
2333
2334 // Skip objects outside the explosion radius
2335 if(distance > explosion_radius && explosion_radius > 0.0f)
2336 {
2337 return;
2338 }
2339
2340 // Normalize the direction vector
2341 if(distance > 0.0f)
2342 {
2343 direction /= distance; // Normalize direction
2344 }
2345 else
2346 {
2347 direction.setZero(); // If explosion is at the same position as the body
2348 }
2349
2350 // Apply upwards modifier
2351 if(upwards_modifier != 0.0f)
2352 {
2353 direction.setY(direction.getY() + upwards_modifier);
2354 direction.normalize();
2355 }
2356
2357 // Calculate the explosion force magnitude based on distance
2358 float attenuation = 1.0f - (distance / explosion_radius);
2359 btVector3 force = direction * explosion_force * attenuation;
2360
2361 if(add_force(body.get(), force, mode))
2362 {
2363 comp.set_velocity(bullet::from_bullet(body->getLinearVelocity()));
2364
2365 wake_up(*bbody);
2366 }
2367 }
2368 }
2369}
2370
2372{
2373 auto owner = comp.get_owner();
2374
2375 if(auto bbody = owner.try_get<bullet::rigidbody>())
2376 {
2377 const auto& body = bbody->internal;
2378 auto vector = bullet::to_bullet(force);
2379
2380 if(add_force(body.get(), vector, mode))
2381 {
2382 comp.set_velocity(bullet::from_bullet(body->getLinearVelocity()));
2383 wake_up(*bbody);
2384 }
2385 }
2386}
2387
2388void bullet_backend::apply_torque(physics_component& comp, const math::vec3& torque, force_mode mode)
2389{
2390 auto owner = comp.get_owner();
2391
2392 if(auto bbody = owner.try_get<bullet::rigidbody>())
2393 {
2394 auto vector = bullet::to_bullet(torque);
2395 const auto& body = bbody->internal;
2396
2397 if(add_torque(body.get(), vector, mode))
2398 {
2399 comp.set_angular_velocity(bullet::from_bullet(body->getAngularVelocity()));
2400 wake_up(*bbody);
2401 }
2402 }
2403}
2404
2406{
2407 if(comp.is_kinematic())
2408 {
2409 auto owner = comp.get_owner();
2410
2411 if(auto bbody = owner.try_get<bullet::rigidbody>())
2412 {
2413 bbody->internal->clearForces();
2414
2415 comp.set_velocity(bullet::from_bullet(bbody->internal->getLinearVelocity()));
2416 comp.set_angular_velocity(bullet::from_bullet(bbody->internal->getAngularVelocity()));
2417
2418 wake_up(*bbody);
2419 }
2420 }
2421}
2422
2423auto bullet_backend::ray_cast(const math::vec3& origin,
2424 const math::vec3& direction,
2425 float max_distance,
2426 int layer_mask,
2427 bool query_sensors) -> hpp::optional<raycast_hit>
2428{
2429 auto& ctx = engine::context();
2430 auto& ec = ctx.get_cached<ecs>();
2431 auto& registry = *ec.get_scene().registry;
2432
2433 auto& world = registry.ctx().get<bullet::world>();
2434
2435 return world.ray_cast_closest(origin, direction, max_distance, layer_mask, query_sensors);
2436}
2437
2438auto bullet_backend::ray_cast_all(const math::vec3& origin,
2439 const math::vec3& direction,
2440 float max_distance,
2441 int layer_mask,
2443{
2444 auto& ctx = engine::context();
2445 auto& ec = ctx.get_cached<ecs>();
2446 auto& registry = *ec.get_scene().registry;
2447
2448 auto& world = registry.ctx().get<bullet::world>();
2449
2450 return world.ray_cast_all(origin, direction, max_distance, layer_mask, query_sensors);
2451}
2452
2453auto bullet_backend::sphere_cast(const math::vec3& origin,
2454 const math::vec3& direction,
2455 float radius,
2456 float max_distance,
2457 int layer_mask,
2458 bool query_sensors) -> hpp::optional<raycast_hit>
2459{
2460 auto& ctx = engine::context();
2461 auto& ec = ctx.get_cached<ecs>();
2462 auto& registry = *ec.get_scene().registry;
2463
2464 auto& world = registry.ctx().get<bullet::world>();
2465
2466 return world.sphere_cast_closest(origin, direction, radius, max_distance, layer_mask, query_sensors);
2467}
2468
2469auto bullet_backend::sphere_cast_all(const math::vec3& origin,
2470 const math::vec3& direction,
2471 float radius,
2472 float max_distance,
2473 int layer_mask,
2475{
2476 auto& ctx = engine::context();
2477 auto& ec = ctx.get_cached<ecs>();
2478 auto& registry = *ec.get_scene().registry;
2479
2480 auto& world = registry.ctx().get<bullet::world>();
2481
2482 return world.sphere_cast_all(origin, direction, radius, max_distance, layer_mask, query_sensors);
2483}
2484
2485auto bullet_backend::sphere_overlap(const math::vec3& origin, float radius, int layer_mask, bool query_sensors)
2487{
2488 auto& ctx = engine::context();
2489 auto& ec = ctx.get_cached<ecs>();
2490 auto& registry = *ec.get_scene().registry;
2491
2492 auto& world = registry.ctx().get<bullet::world>();
2493
2494 return world.sphere_overlap(origin, radius, layer_mask, query_sensors);
2495}
2496
2498{
2499 auto& ec = ctx.get_cached<ecs>();
2500 auto& scn = ec.get_scene();
2501 auto& registry = *scn.registry;
2502
2503 auto& world = registry.ctx().emplace<bullet::world>(bullet::create_dynamics_world());
2504
2505 registry.on_destroy<bullet::rigidbody>().connect<&on_destroy_bullet_rigidbody_component>();
2506 registry.on_destroy<bullet::character_controller>().connect<&on_destroy_bullet_cc_component>();
2507 registry.on_construct<active_component>().connect<&on_create_active_component>();
2508 registry.on_destroy<active_component>().connect<&on_destroy_active_component>();
2509
2510 registry.view<physics_component>().each(
2511 [&](auto e, auto&& comp)
2512 {
2513 sync_physics_body(world, comp, true);
2514 });
2515 registry.view<character_controller_component>().each(
2516 [&](auto e, auto&& comp)
2517 {
2518 sync_character_controller_body(world, comp, true);
2519 });
2520}
2521
2523{
2524 auto& ec = ctx.get_cached<ecs>();
2525 auto& registry = *ec.get_scene().registry;
2526
2527 auto& world = registry.ctx().get<bullet::world>();
2528
2529 registry.view<character_controller_component>().each(
2530 [&](auto e, auto&& comp)
2531 {
2532 destroy_character_controller_body(world, comp.get_owner(), true);
2533 });
2534 registry.view<physics_component>().each(
2535 [&](auto e, auto&& comp)
2536 {
2537 destroy_phyisics_body(world, comp.get_owner(), true);
2538 });
2539
2540 registry.on_construct<active_component>().disconnect<&on_create_active_component>();
2541 registry.on_destroy<active_component>().disconnect<&on_destroy_active_component>();
2542 registry.on_destroy<bullet::character_controller>().disconnect<&on_destroy_bullet_cc_component>();
2543 registry.on_destroy<bullet::rigidbody>().disconnect<&on_destroy_bullet_rigidbody_component>();
2544
2545 registry.ctx().erase<bullet::world>();
2546}
2547
2551
2555
2557{
2558 delta_t step(1.0f / 60.0f);
2559 on_frame_update(ctx, step);
2560}
2561
2563{
2564 APP_SCOPE_PERF("Physics/Bullet/Update");
2565 auto& ev = ctx.get_cached<events>();
2566
2567 auto& ec = ctx.get_cached<ecs>();
2568 auto& registry = *ec.get_scene().registry;
2569 auto& world = registry.ctx().get<bullet::world>();
2570
2571 if(dt > delta_t::zero())
2572 {
2573 float fixed_time_step = 1.0f / 50.0f;
2574 int max_subs_steps = 3;
2575
2576 if(ctx.has<settings>())
2577 {
2578 auto& ss = ctx.get<settings>();
2579 fixed_time_step = ss.time.fixed_timestep;
2580 max_subs_steps = ss.time.max_fixed_steps;
2581 }
2582
2583 // Accumulate time
2584 world.elapsed += dt.count();
2585
2586 int steps = 0;
2587 while(world.elapsed >= fixed_time_step && steps < max_subs_steps)
2588 {
2589 APP_SCOPE_PERF("Physics/Bullet/Fixed Update");
2590 delta_t step_dt(fixed_time_step);
2591 ev.on_frame_fixed_update(ctx, step_dt);
2592
2593 // update phyiscs spatial properties from transform
2594 uint64_t physics_entities{};
2595 uint64_t physics_entities_synced{};
2596
2597 {
2598 APP_SCOPE_PERF("Physics/Bullet/Sync Transforms To Physics");
2600 [&](auto e, auto&& transform, auto&& rigidbody, auto&& active_comp)
2601 {
2602 physics_entities++;
2603 if(to_physics(world, transform, rigidbody))
2604 {
2605 physics_entities_synced++;
2606 }
2607 });
2609 [&](auto e, auto&& transform, auto&& cc_comp, auto&& active_comp)
2610 {
2611 to_physics_cc(world, transform, cc_comp);
2612 });
2613 }
2614
2615 world.simulate(fixed_time_step, fixed_time_step, 1);
2616
2617 physics_entities = {};
2618 physics_entities_synced = {};
2619 {
2620 APP_SCOPE_PERF("Physics/Bullet/Sync Transforms From Physics");
2622 [&](auto e, auto&& transform, auto&& rigidbody, auto&& active_comp)
2623 {
2624 physics_entities++;
2625 if(from_physics(world, transform, rigidbody))
2626 {
2627 physics_entities_synced++;
2628 }
2629 });
2631 [&](auto e, auto&& transform, auto&& cc_comp, auto&& active_comp)
2632 {
2633 from_physics_cc(world, transform, cc_comp);
2634 });
2635 }
2636
2637 // APPLOG_TRACE("Physics Update: entities {} -> synced from physics {}",
2638 // physics_entities,
2639 // physics_entities_synced);
2640
2641 world.process_manifolds();
2642
2643 world.elapsed -= fixed_time_step;
2644 steps++;
2645 }
2646 }
2647}
2648
2650{
2651 auto& ec = ctx.get_cached<ecs>();
2652 auto& registry = *ec.get_scene().registry;
2653 auto world = registry.ctx().find<bullet::world>();
2654 if(world)
2655 {
2656 bullet::debugdraw drawer(dd);
2657 world->dynamics_world->setDebugDrawer(&drawer);
2658
2659 world->dynamics_world->debugDrawWorld();
2660
2661 world->dynamics_world->setDebugDrawer(nullptr);
2662 }
2663}
2664
2668
2671 const camera& cam,
2672 gfx::dd_raii& dd)
2673{
2674 auto owner = comp.get_owner();
2675 if(!owner || !owner.all_of<transform_component>())
2676 {
2677 return;
2678 }
2679 auto& transform = owner.get<transform_component>();
2680 const auto& p = transform.get_position_global();
2681 const auto& q = transform.get_rotation_global();
2682 float cylinder_half_height = (comp.get_height() - 2.0f * comp.get_radius()) * 0.5f;
2683 if(cylinder_half_height < 0.0f)
2684 {
2685 cylinder_half_height = 0.0f;
2686 }
2687 auto center = p + comp.get_center();
2688 math::vec3 up = q * math::vec3(0.0f, 1.0f, 0.0f);
2689 auto top = center + up * cylinder_half_height;
2690 auto bottom = center - up * cylinder_half_height;
2691 dd.encoder.setColor(0xff00ffff);
2692 dd.encoder.setWireframe(true);
2693 dd.encoder.drawCapsule({bottom.x, bottom.y, bottom.z}, {top.x, top.y, top.z}, comp.get_radius());
2694}
2695
2696} // namespace unravel
entt::handle b
#define COL32_A_SHIFT
bool in_simulate
unravel::physics_vector< contact_manifold > to_exit
int layer_mask
std::shared_ptr< btBroadphaseInterface > broadphase
contact_manifold cm
btScalar fraction
std::vector< unravel::manifold_point > contacts
int collision_filter_group
bool query_sensors
hpp::flat_map< contact_key, contact_record > contacts_cache
std::shared_ptr< btDiscreteDynamicsWorld > dynamics_world
std::shared_ptr< btCollisionDispatcher > dispatcher
float elapsed
std::shared_ptr< btConstraintSolverPoolMt > solver_pool
btCollisionObject * me
std::shared_ptr< btCapsuleShape > shape
std::shared_ptr< btRigidBody > internal
std::shared_ptr< btDefaultCollisionConfiguration > collision_config
bool active_this_frame
std::shared_ptr< btConstraintSolver > solver
entt::handle a
#define COL32_G_SHIFT
unravel::physics_vector< hit_info > hits
#define COL32_B_SHIFT
int collision_filter_mask
std::shared_ptr< btCollisionShape > internal_shape
#define COL32_R_SHIFT
std::shared_ptr< btPairCachingGhostObject > ghost
unravel::physics_vector< contact_manifold > to_enter
std::shared_ptr< btKinematicCharacterController > controller
Class representing a camera. Contains functionality for manipulating and updating a camera....
Definition camera.h:62
Component for character controller physics with sweep-based movement.
void set_velocity_internal(const math::vec3 &vel) noexcept
auto get_center() const noexcept -> const math::vec3 &
auto get_owner() const noexcept -> entt::const_handle
Gets the owner of the component.
Component that handles physics properties and behaviors.
void set_velocity(const math::vec3 &velocity)
void set_angular_velocity(const math::vec3 &velocity)
auto is_kinematic() const noexcept -> bool
Checks if the component is kinematic.
Class that contains core data for audio listeners. There can only be one instance of it per scene.
auto has_script_components() const -> bool
Component that handles transformations (position, rotation, scale, etc.) in the ACE framework.
std::chrono::duration< float > delta_t
const char * id
math::vec3 position
Definition defaults.cpp:52
bool hit
Definition defaults.cpp:51
math::vec3 normal
Definition defaults.cpp:53
uint16_t index
texture_job_type type
bgfx::Transform transform
Definition graphics.h:42
void vertex_unpack(float _output[4], attribute _attr, const vertex_layout &_decl, const void *_data, uint32_t _index)
Definition graphics.cpp:364
void end(encoder *_encoder)
Definition graphics.cpp:427
Hash specialization for batch_key to enable use in std::unordered_map.
hpp::small_vector< T, SmallSizeCapacity > physics_vector
@ convex
Convex mesh collision (can be dynamic, faster)
auto to_bx(const glm::vec3 &data) -> bx::Vec3
Definition gizmos.cpp:12
std::vector< math::color > color
std::vector< float > scale
std::vector< math::vec3 > start
#define APP_SCOPE_PERF(name_literal)
Create a scoped performance timer that records to the timeline profiler. Only accepts string literals...
Definition profiler.h:675
entt::handle entity
void lineTo(float _x, float _y, float _z=0.0f)
void setColor(uint32_t _abgr)
void setWireframe(bool _wireframe)
void drawCapsule(const bx::Vec3 &_from, const bx::Vec3 &_to, float _radius)
void moveTo(float _x, float _y, float _z=0.0f)
DebugDrawEncoder encoder
Definition debugdraw.h:15
auto get_cached() -> T &
Definition context.hpp:49
auto has() const -> bool
Definition context.hpp:28
auto get() -> T &
Definition context.hpp:35
size_t operator()(contact_key const &k) const noexcept
void on_skip_next_frame(rtti::context &ctx)
static auto sphere_cast_all(const math::vec3 &origin, const math::vec3 &direction, float radius, float max_distance, int layer_mask, bool query_sensors) -> physics_vector< raycast_hit >
static void on_create_cc_component(entt::registry &r, entt::entity e)
static void on_create_component(entt::registry &r, entt::entity e)
static void set_character_linear_velocity(character_controller_component &comp, const math::vec3 &velocity)
static void warp_character(character_controller_component &comp, const math::vec3 &position)
static void on_create_active_component(entt::registry &r, entt::entity e)
static void draw_gizmo(rtti::context &ctx, physics_component &comp, const camera &cam, gfx::dd_raii &dd)
static auto ray_cast(const math::vec3 &origin, const math::vec3 &direction, float max_distance, int layer_mask, bool query_sensors) -> hpp::optional< raycast_hit >
void on_play_begin(rtti::context &ctx)
static void apply_force(physics_component &comp, const math::vec3 &force, force_mode mode)
static void sync_character_runtime_state(character_controller_component &comp)
static void on_destroy_active_component(entt::registry &r, entt::entity e)
void on_resume(rtti::context &ctx)
static void on_destroy_cc_component(entt::registry &r, entt::entity e)
void on_pause(rtti::context &ctx)
static void apply_impulse_character(character_controller_component &comp, const math::vec3 &impulse)
static void draw_system_gizmos(rtti::context &ctx, const camera &cam, gfx::dd_raii &dd)
static auto sphere_overlap(const math::vec3 &origin, float radius, int layer_mask, bool query_sensors) -> physics_vector< entt::entity >
static void move_character(character_controller_component &comp, const math::vec3 &displacement)
static void jump_character(character_controller_component &comp, const math::vec3 &direction)
static auto sphere_cast(const math::vec3 &origin, const math::vec3 &direction, float radius, float max_distance, int layer_mask, bool query_sensors) -> hpp::optional< raycast_hit >
static void apply_torque(physics_component &comp, const math::vec3 &toruqe, force_mode mode)
static void apply_explosion_force(physics_component &comp, float explosion_force, const math::vec3 &explosion_position, float explosion_radius, float upwards_modifier, force_mode mode)
static void on_destroy_component(entt::registry &r, entt::entity e)
static void clear_kinematic_velocities(physics_component &comp)
static void on_destroy_bullet_rigidbody_component(entt::registry &r, entt::entity e)
static void on_destroy_bullet_cc_component(entt::registry &r, entt::entity e)
void on_frame_update(rtti::context &ctx, delta_t dt)
static auto ray_cast_all(const math::vec3 &origin, const math::vec3 &direction, float max_distance, int layer_mask, bool query_sensors) -> physics_vector< raycast_hit >
void on_play_end(rtti::context &ctx)
Manages the entity-component-system (ECS) operations for the ACE framework.
Definition ecs.h:12
static auto context() -> rtti::context &
Definition engine.cpp:111
void on_collision_enter(entt::handle a, entt::handle b, const std::vector< manifold_point > &manifolds)
void on_collision_exit(entt::handle a, entt::handle b, const std::vector< manifold_point > &manifolds)
void on_sensor_enter(entt::handle sensor, entt::handle other, const std::vector< manifold_point > &manifolds)
void on_sensor_exit(entt::handle sensor, entt::handle other, const std::vector< manifold_point > &manifolds)
struct unravel::settings::time_settings time
@ physics
Physics backend transform sync (see bullet_backend.cpp).
std::string owner
bool enabled