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