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 make_rigidbody_shape(physics_component& comp) -> std::shared_ptr<btCompoundShape>
1223{
1224 // use an ownning compound shape. When sharing is implemented we can go back to non owning
1225 auto cp = std::make_shared<bullet::btCompoundShapeOwning>();
1226
1227 auto compound_shapes = comp.get_shapes();
1228 if(compound_shapes.empty())
1229 {
1230 return cp;
1231 }
1232
1233 for(const auto& s : compound_shapes)
1234 {
1235 if(hpp::holds_alternative<physics_box_shape>(s.shape))
1236 {
1237 const auto& shape = hpp::get<physics_box_shape>(s.shape);
1238 auto half_extends = shape.extends * 0.5f;
1239
1240 btBoxShape* box_shape = new btBoxShape({half_extends.x, half_extends.y, half_extends.z});
1241
1242 btTransform local_transform = btTransform::getIdentity();
1243 local_transform.setOrigin(bullet::to_bullet(shape.center));
1244 cp->addChildShape(local_transform, box_shape);
1245 }
1246 else if(hpp::holds_alternative<physics_sphere_shape>(s.shape))
1247 {
1248 const auto& shape = hpp::get<physics_sphere_shape>(s.shape);
1249
1250 btSphereShape* sphere_shape = new btSphereShape(shape.radius);
1251
1252 btTransform local_transform = btTransform::getIdentity();
1253 local_transform.setOrigin(bullet::to_bullet(shape.center));
1254 cp->addChildShape(local_transform, sphere_shape);
1255 }
1256 else if(hpp::holds_alternative<physics_capsule_shape>(s.shape))
1257 {
1258 const auto& shape = hpp::get<physics_capsule_shape>(s.shape);
1259
1260 btCapsuleShape* capsule_shape = new btCapsuleShape(shape.radius, shape.length);
1261
1262 btTransform local_transform = btTransform::getIdentity();
1263 local_transform.setOrigin(bullet::to_bullet(shape.center));
1264 cp->addChildShape(local_transform, capsule_shape);
1265 }
1266 else if(hpp::holds_alternative<physics_cylinder_shape>(s.shape))
1267 {
1268 const auto& shape = hpp::get<physics_cylinder_shape>(s.shape);
1269
1270 btVector3 half_extends(shape.radius, shape.length * 0.5f, shape.radius);
1271 btCylinderShape* cylinder_shape = new btCylinderShape(half_extends);
1272
1273 btTransform local_transform = btTransform::getIdentity();
1274 local_transform.setOrigin(bullet::to_bullet(shape.center));
1275 cp->addChildShape(local_transform, cylinder_shape);
1276 }
1277 }
1278
1279 return cp;
1280}
1281
1282void update_rigidbody_shape(bullet::rigidbody& body, physics_component& comp)
1283{
1284 auto shape = make_rigidbody_shape(comp);
1285
1286 body.internal->setCollisionShape(shape.get());
1287 body.internal_shape = shape;
1288}
1289
1290void update_rigidbody_shape_scale(bullet::world& world, bullet::rigidbody& body, const math::vec3& s)
1291{
1292 auto bt_scale = body.internal_shape->getLocalScaling();
1293 auto scale = bullet::from_bullet(bt_scale);
1294
1295 if(math::any(math::epsilonNotEqual(scale, s, math::epsilon<float>())))
1296 {
1297 bt_scale = bullet::to_bullet(s);
1298 body.internal_shape->setLocalScaling(bt_scale);
1299 world.dynamics_world->updateSingleAabb(body.internal.get());
1300 }
1301}
1302
1303// Updated to preserve existing collision flags when switching kinematic/dynamic
1304void update_rigidbody_kind(bullet::rigidbody& body, physics_component& comp)
1305{
1306 // Read current flags
1307 auto flags = body.internal->getCollisionFlags();
1308 auto rbFlags = body.internal->getFlags();
1309
1310 if(comp.is_kinematic())
1311 {
1312 // Set kinematic bit, clear static if previously set
1313 flags |= btCollisionObject::CF_KINEMATIC_OBJECT;
1314 flags &= ~btCollisionObject::CF_DYNAMIC_OBJECT;
1315
1316 body.internal->setCollisionFlags(flags);
1317 }
1318 else
1319 {
1320 // Clear kinematic bit, optionally set dynamic bit
1321 flags &= ~btCollisionObject::CF_KINEMATIC_OBJECT;
1322 flags |= btCollisionObject::CF_DYNAMIC_OBJECT; // ensure dynamic flag
1323 body.internal->setCollisionFlags(flags);
1324 }
1325}
1326
1327void update_rigidbody_constraints(bullet::rigidbody& body, physics_component& comp)
1328{
1329 // Get freeze constraints for position and apply them
1330 auto freeze_position = comp.get_freeze_position();
1331 btVector3 linear_factor(float(!freeze_position.x), float(!freeze_position.y), float(!freeze_position.z));
1332 body.internal->setLinearFactor(linear_factor);
1333
1334 // Adjust velocity to respect linear constraints
1335 auto velocity = body.internal->getLinearVelocity();
1336 velocity *= linear_factor;
1337 body.internal->setLinearVelocity(velocity);
1338
1339 // Get freeze constraints for rotation and apply them
1340 auto freeze_rotation = comp.get_freeze_rotation();
1341 btVector3 angular_factor(float(!freeze_rotation.x), float(!freeze_rotation.y), float(!freeze_rotation.z));
1342 body.internal->setAngularFactor(angular_factor);
1343
1344 // Adjust angular velocity to respect angular constraints
1345 auto angular_velocity = body.internal->getAngularVelocity();
1346 angular_velocity *= angular_factor;
1347 body.internal->setAngularVelocity(angular_velocity);
1348
1349 // Ensure the body is active
1350 wake_up(body);
1351}
1352
1353void update_rigidbody_velocity(bullet::rigidbody& body, physics_component& comp)
1354{
1355 body.internal->setLinearVelocity(bullet::to_bullet(comp.get_velocity()));
1356
1357 wake_up(body);
1358}
1359
1360void update_rigidbody_angular_velocity(bullet::rigidbody& body, physics_component& comp)
1361{
1362 body.internal->setAngularVelocity(bullet::to_bullet(comp.get_angular_velocity()));
1363
1364 wake_up(body);
1365}
1366
1367void update_rigidbody_collision_layer(bullet::world& world, bullet::rigidbody& body, physics_component& comp)
1368{
1369 int filter_group = comp.get_owner().get<layer_component>().layers.mask;
1370 int filter_mask = comp.get_collision_mask().mask;
1371 body.collision_filter_group = filter_group;
1372 body.collision_filter_mask = filter_mask;
1373
1374 // bool is_dynamic = !(body.internal->isStaticObject() || body.internal->isKinematicObject());
1375 // body.collision_filter_group = is_dynamic ? body.collision_filter_group : int(unravel::layer_reserved::static_filter);
1376 // body.collision_filter_mask =
1377 // is_dynamic ? body.collision_filter_mask : body.collision_filter_mask ^
1378 // int(unravel::layer_reserved::static_filter);
1379 // 1) Get the body’s broadphase proxy
1380 btBroadphaseProxy* proxy = body.internal->getBroadphaseHandle();
1381 if(!proxy)
1382 {
1383 return; // or handle error
1384 }
1385
1386 if(body.collision_filter_group != proxy->m_collisionFilterGroup ||
1387 body.collision_filter_mask != proxy->m_collisionFilterMask)
1388 {
1389 // 2) Clean up any old pair cache usage
1390 world.dynamics_world->getBroadphase()->getOverlappingPairCache()->cleanProxyFromPairs(
1391 proxy,
1392 world.dynamics_world->getDispatcher());
1393
1394 // 3) Update filter group / mask
1395 proxy->m_collisionFilterGroup = body.collision_filter_group;
1396 proxy->m_collisionFilterMask = body.collision_filter_mask;
1397
1398 // 4) Re-insert it into the broadphase
1399 world.dynamics_world->refreshBroadphaseProxy(body.internal.get());
1400 wake_up(body);
1401 }
1402}
1403
1404void update_rigidbody_mass_and_inertia(bullet::rigidbody& body, physics_component& comp)
1405{
1406 btScalar mass(0);
1407 btVector3 local_inertia(0, 0, 0);
1408 if(!comp.is_kinematic())
1409 {
1410 auto shape = body.internal->getCollisionShape();
1411 if(shape)
1412 {
1413 mass = comp.get_mass();
1414 shape->calculateLocalInertia(mass, local_inertia);
1415 }
1416 }
1417 body.internal->setMassProps(mass, local_inertia);
1418}
1419
1420void update_rigidbody_gravity(bullet::world& world, bullet::rigidbody& body, physics_component& comp)
1421{
1422 if(comp.is_using_gravity())
1423 {
1424 body.internal->setGravity(world.dynamics_world->getGravity());
1425 }
1426 else
1427 {
1428 body.internal->setGravity(btVector3{0, 0, 0});
1429 body.internal->setLinearVelocity(btVector3(0, 0, 0));
1430 }
1431}
1432
1433void update_rigidbody_material(bullet::rigidbody& body, physics_component& comp)
1434{
1435 auto mat = comp.get_material().get();
1436
1437 int packed = bullet::encode_combine_modes(mat->friction_combine, mat->restitution_combine);
1438 if(body.internal->getUserIndex2() != packed)
1439 {
1440 body.internal->setUserIndex2(packed);
1441 }
1442
1443 if(math::epsilonNotEqual(body.internal->getRestitution(), mat->restitution, math::epsilon<float>()))
1444 {
1445 body.internal->setRestitution(mat->restitution);
1446 }
1447 if(math::epsilonNotEqual(body.internal->getFriction(), mat->friction, math::epsilon<float>()))
1448 {
1449 body.internal->setFriction(mat->friction);
1450 }
1451
1452 auto stiffness = mat->get_stiffness();
1453 if(math::epsilonNotEqual(body.internal->getContactStiffness(), stiffness, math::epsilon<float>()) ||
1454 math::epsilonNotEqual(body.internal->getContactDamping(), mat->damping, math::epsilon<float>()))
1455 {
1456 body.internal->setContactStiffnessAndDamping(stiffness, mat->damping);
1457 }
1458}
1459
1460void update_rigidbody_sensor(bullet::rigidbody& body, physics_component& comp)
1461{
1462 auto flags = body.internal->getCollisionFlags();
1463 if(comp.is_sensor())
1464 {
1465 body.internal->setCollisionFlags(flags | btCollisionObject::CF_NO_CONTACT_RESPONSE);
1466 }
1467 else
1468 {
1469 body.internal->setCollisionFlags(flags & ~btCollisionObject::CF_NO_CONTACT_RESPONSE);
1470 }
1471}
1472
1473void set_rigidbody_active(bullet::world& world, bullet::rigidbody& body, bool enabled)
1474{
1475 if(enabled)
1476 {
1477 world.add_rigidbody(body);
1478 }
1479 else
1480 {
1481 world.remove_rigidbody(body);
1482 }
1483}
1484
1485void update_rigidbody_full(bullet::world& world, bullet::rigidbody& body, physics_component& comp)
1486{
1487 update_rigidbody_kind(body, comp);
1488 update_rigidbody_shape(body, comp);
1489 update_rigidbody_mass_and_inertia(body, comp);
1490 update_rigidbody_material(body, comp);
1491 update_rigidbody_sensor(body, comp);
1492 update_rigidbody_constraints(body, comp);
1493 update_rigidbody_velocity(body, comp);
1494 update_rigidbody_angular_velocity(body, comp);
1495 update_rigidbody_gravity(world, body, comp);
1496 update_rigidbody_collision_layer(world, body, comp);
1497}
1498
1499void make_rigidbody(bullet::world& world, entt::handle entity, physics_component& comp)
1500{
1501 auto& body = entity.emplace<bullet::rigidbody>();
1502
1503 body.internal = std::make_shared<btRigidBody>(comp.get_mass(), nullptr, nullptr);
1504 body.internal->setUserIndex(int(entity.entity()));
1505 body.internal->setUserPointer(&world);
1506 body.internal->setFlags(BT_DISABLE_WORLD_GRAVITY);
1507
1508 update_rigidbody_full(world, body, comp);
1509
1510 if(entity.all_of<active_component>())
1511 {
1512 world.add_rigidbody(body);
1513 }
1514}
1515
1516void destroy_phyisics_body(bullet::world& world, entt::handle entity, bool from_physics_component)
1517{
1518 auto body = entity.try_get<bullet::rigidbody>();
1519
1520 if(body && body->internal)
1521 {
1522 world.remove_rigidbody(*body);
1523 }
1524
1525 if(from_physics_component)
1526 {
1527 entity.remove<bullet::rigidbody>();
1528 }
1529}
1530
1531void sync_physics_body(bullet::world& world, physics_component& comp, bool force = false)
1532{
1533 auto owner = comp.get_owner();
1534
1535 if(force)
1536 {
1537 destroy_phyisics_body(world, comp.get_owner(), true);
1538 make_rigidbody(world, owner, comp);
1539 }
1540 else
1541 {
1542 auto& body = owner.get<bullet::rigidbody>();
1543
1544 if(comp.is_property_dirty(physics_property::kind))
1545 {
1546 set_rigidbody_active(world, body, false);
1547 update_rigidbody_full(world, body, comp);
1548 set_rigidbody_active(world, body, true);
1549 }
1550 else
1551 {
1552 if(comp.is_property_dirty(physics_property::shape))
1553 {
1554 comp.set_property_dirty(physics_property::mass, true);
1555 update_rigidbody_shape(body, comp);
1556 world.dynamics_world->updateSingleAabb(body.internal.get());
1557 }
1558 if(comp.is_property_dirty(physics_property::mass))
1559 {
1560 update_rigidbody_mass_and_inertia(body, comp);
1561 }
1562
1563 if(comp.is_property_dirty(physics_property::sensor))
1564 {
1565 update_rigidbody_sensor(body, comp);
1566 }
1567
1568 if(comp.is_property_dirty(physics_property::constraints))
1569 {
1570 update_rigidbody_constraints(body, comp);
1571 comp.set_property_dirty(physics_property::gravity, true);
1572 }
1573 if(comp.is_property_dirty(physics_property::velocity))
1574 {
1575 update_rigidbody_velocity(body, comp);
1576 }
1577 if(comp.is_property_dirty(physics_property::angular_velocity))
1578 {
1579 update_rigidbody_angular_velocity(body, comp);
1580 }
1581
1582 if(comp.is_property_dirty(physics_property::gravity))
1583 {
1584 update_rigidbody_gravity(world, body, comp);
1585 }
1586
1587 // here we check internally for a change
1588 update_rigidbody_material(body, comp);
1589 update_rigidbody_collision_layer(world, body, comp);
1590 }
1591
1592 if(!comp.is_kinematic())
1593 {
1594 if(comp.are_any_properties_dirty())
1595 {
1596 wake_up(body);
1597 }
1598 }
1599 }
1600
1601 comp.set_dirty(system_id, false);
1602}
1603
1604auto sync_transforms(bullet::world& world, physics_component& comp, const transform_component& transform) -> bool
1605{
1606 auto owner = comp.get_owner();
1607 auto& body = owner.get<bullet::rigidbody>();
1608
1609 if(!body.internal)
1610 {
1611 return false;
1612 }
1613
1614 const auto& p = transform.get_position_global();
1615 const auto& q = transform.get_rotation_global();
1616 const auto& s = transform.get_scale_global();
1617
1618 auto bt_pos = bullet::to_bullet(p);
1619 auto bt_rot = bullet::to_bullet(q);
1620 btTransform bt_trans(bt_rot, bt_pos);
1621 body.internal->setWorldTransform(bt_trans);
1622
1623 if(body.internal_shape && comp.is_autoscaled())
1624 {
1625 update_rigidbody_shape_scale(world, body, s);
1626 }
1627
1628 wake_up(body);
1629
1630 return true;
1631}
1632
1633auto sync_state(physics_component& comp) -> bool
1634{
1635 auto owner = comp.get_owner();
1636 auto body = owner.try_get<bullet::rigidbody>();
1637
1638 if(!body || !body->internal)
1639 {
1640 return false;
1641 }
1642
1643 if(!body->internal->isActive())
1644 {
1645 return false;
1646 }
1647
1648 comp.set_velocity(bullet::from_bullet(body->internal->getLinearVelocity()));
1649 comp.set_angular_velocity(bullet::from_bullet(body->internal->getAngularVelocity()));
1650
1651 return true;
1652}
1653
1654auto sync_transforms(physics_component& comp, transform_component& transform) -> bool
1655{
1656 auto owner = comp.get_owner();
1657 auto body = owner.try_get<bullet::rigidbody>();
1658
1659 if(!body || !body->internal)
1660 {
1661 return false;
1662 }
1663
1664 if(!body->internal->isActive())
1665 {
1666 return false;
1667 }
1668
1669 const auto& bt_trans = body->internal->getWorldTransform();
1670 auto p = bullet::from_bullet(bt_trans.getOrigin());
1671 auto q = bullet::from_bullet(bt_trans.getRotation());
1672
1673 // Here we are using a more generous epsilon to
1674 // take into account any conversion errors between us and bullet
1675 float epsilon = 0.009f;
1676 return transform.set_position_and_rotation_global(p, q, epsilon);
1677}
1678
1679auto to_physics(bullet::world& world, transform_component& transform, physics_component& comp) -> bool
1680{
1681 bool transform_dirty = transform.is_dirty(system_id);
1682 bool rigidbody_dirty = comp.is_dirty(system_id);
1683
1684 // if(rigidbody_dirty)
1685 {
1686 sync_physics_body(world, comp);
1687 }
1688
1689 if(transform_dirty || rigidbody_dirty)
1690 {
1691 return sync_transforms(world, comp, transform);
1692 }
1693
1694 return false;
1695}
1696
1697auto from_physics(bullet::world& world, transform_component& transform, physics_component& comp) -> bool
1698{
1699 sync_state(comp);
1700
1701 bool result = sync_transforms(comp, transform);
1702
1703 transform.set_dirty(system_id, false);
1704 comp.set_dirty(system_id, false);
1705
1706 return result;
1707}
1708
1709auto add_force(btRigidBody* body, const btVector3& force, force_mode mode) -> bool
1710{
1711 if(force.fuzzyZero())
1712 {
1713 return false;
1714 }
1715 // Apply force based on ForceMode
1716 switch(mode)
1717 {
1718 case force_mode::force: // Continuous force
1719 body->applyCentralForce(force);
1720 break;
1721
1723 { // Force independent of mass
1724 btVector3 acceleration_force = force * body->getMass();
1725 body->applyCentralForce(acceleration_force);
1726 break;
1727 }
1728
1729 case force_mode::impulse: // Instantaneous impulse
1730 body->applyCentralImpulse(force);
1731 break;
1732
1733 case force_mode::velocity_change: // Direct velocity change
1734 {
1735 btVector3 new_velocity = body->getLinearVelocity() + force; // Accumulate velocity
1736 body->setLinearVelocity(new_velocity);
1737 break;
1738 }
1739 }
1740 return true;
1741}
1742
1743auto add_torque(btRigidBody* body, const btVector3& torque, force_mode mode) -> bool
1744{
1745 if(torque.fuzzyZero())
1746 {
1747 return false;
1748 }
1749 // Apply force based on ForceMode
1750 switch(mode)
1751 {
1752 case force_mode::force: // Continuous torque
1753 body->applyTorque(torque);
1754 break;
1755
1756 case force_mode::acceleration: // Angular acceleration
1757 {
1758 btVector3 inertia_tensor = body->getInvInertiaDiagLocal();
1759 btVector3 angular_acceleration(
1760 inertia_tensor.getX() != 0 ? torque.getX() * (1.0f / inertia_tensor.getX()) : 0.0f,
1761 inertia_tensor.getY() != 0 ? torque.getY() * (1.0f / inertia_tensor.getY()) : 0.0f,
1762 inertia_tensor.getZ() != 0 ? torque.getZ() * (1.0f / inertia_tensor.getZ()) : 0.0f);
1763 body->applyTorque(angular_acceleration);
1764 }
1765 break;
1766
1767 case force_mode::impulse: // Angular impulse
1768 body->applyTorqueImpulse(torque);
1769 break;
1770
1771 case force_mode::velocity_change: // Direct angular velocity change
1772 {
1773 btVector3 new_velocity = body->getLinearVelocity() + torque; // Accumulate velocity
1774 body->setAngularVelocity(new_velocity);
1775 break;
1776 }
1777 }
1778
1779 return true;
1780}
1781
1782} // namespace
1783
1785{
1786 bullet::setup_task_scheduler();
1787 bullet::override_combine_callbacks();
1788}
1789
1791{
1792 bullet::cleanup_task_scheduler();
1793}
1794
1795void bullet_backend::on_create_component(entt::registry& r, entt::entity e)
1796{
1797 // this function will be called for both physics_component and bullet::rigidbody
1798 auto world = r.ctx().find<bullet::world>();
1799 if(world)
1800 {
1801 entt::handle entity(r, e);
1802 auto& phisics = entity.get<physics_component>();
1803 sync_physics_body(*world, phisics, true);
1804 }
1805}
1806
1807void bullet_backend::on_destroy_component(entt::registry& r, entt::entity e)
1808{
1809 // this function will be called for both physics_component and bullet::rigidbody
1810 auto world = r.ctx().find<bullet::world>();
1811 if(world)
1812 {
1813 entt::handle entity(r, e);
1814 destroy_phyisics_body(*world, entity, true);
1815 }
1816}
1817
1818void bullet_backend::on_destroy_bullet_rigidbody_component(entt::registry& r, entt::entity e)
1819{
1820 // this function will be called for both physics_component and bullet::rigidbody
1821 auto world = r.ctx().find<bullet::world>();
1822 if(world)
1823 {
1824 entt::handle entity(r, e);
1825 destroy_phyisics_body(*world, entity, false);
1826 }
1827}
1828
1829void bullet_backend::on_create_active_component(entt::registry& r, entt::entity e)
1830{
1831 // this function will be called for both physics_component and bullet::rigidbody
1832 auto world = r.ctx().find<bullet::world>();
1833 if(world)
1834 {
1835 entt::handle entity(r, e);
1836 auto body = entity.try_get<bullet::rigidbody>();
1837 if(body)
1838 {
1839 set_rigidbody_active(*world, *body, true);
1840 }
1841 }
1842}
1843
1844void bullet_backend::on_destroy_active_component(entt::registry& r, entt::entity e)
1845{
1846 // this function will be called for both physics_component and bullet::rigidbody
1847 auto world = r.ctx().find<bullet::world>();
1848 if(world)
1849 {
1850 entt::handle entity(r, e);
1851 auto body = entity.try_get<bullet::rigidbody>();
1852 if(body)
1853 {
1854 set_rigidbody_active(*world, *body, false);
1855 }
1856 }
1857}
1858
1860 float explosion_force,
1861 const math::vec3& explosion_position,
1862 float explosion_radius,
1863 float upwards_modifier,
1864 force_mode mode)
1865{
1866 auto owner = comp.get_owner();
1867
1868 if(auto bbody = owner.try_get<bullet::rigidbody>())
1869 {
1870 const auto& body = bbody->internal;
1871
1872 // Ensure the object is a dynamic rigid body
1873 if(body && body->getInvMass() > 0)
1874 {
1875 // Get the position of the rigid body
1876 btVector3 body_position = body->getWorldTransform().getOrigin();
1877
1878 // Calculate the vector from the explosion position to the body
1879 btVector3 direction = body_position - bullet::to_bullet(explosion_position);
1880 float distance = direction.length();
1881
1882 // Skip objects outside the explosion radius
1883 if(distance > explosion_radius && explosion_radius > 0.0f)
1884 {
1885 return;
1886 }
1887
1888 // Normalize the direction vector
1889 if(distance > 0.0f)
1890 {
1891 direction /= distance; // Normalize direction
1892 }
1893 else
1894 {
1895 direction.setZero(); // If explosion is at the same position as the body
1896 }
1897
1898 // Apply upwards modifier
1899 if(upwards_modifier != 0.0f)
1900 {
1901 direction.setY(direction.getY() + upwards_modifier);
1902 direction.normalize();
1903 }
1904
1905 // Calculate the explosion force magnitude based on distance
1906 float attenuation = 1.0f - (distance / explosion_radius);
1907 btVector3 force = direction * explosion_force * attenuation;
1908
1909 if(add_force(body.get(), force, mode))
1910 {
1911 comp.set_velocity(bullet::from_bullet(body->getLinearVelocity()));
1912
1913 wake_up(*bbody);
1914 }
1915 }
1916 }
1917}
1918
1920{
1921 auto owner = comp.get_owner();
1922
1923 if(auto bbody = owner.try_get<bullet::rigidbody>())
1924 {
1925 const auto& body = bbody->internal;
1926 auto vector = bullet::to_bullet(force);
1927
1928 if(add_force(body.get(), vector, mode))
1929 {
1930 comp.set_velocity(bullet::from_bullet(body->getLinearVelocity()));
1931 wake_up(*bbody);
1932 }
1933 }
1934}
1935
1936void bullet_backend::apply_torque(physics_component& comp, const math::vec3& torque, force_mode mode)
1937{
1938 auto owner = comp.get_owner();
1939
1940 if(auto bbody = owner.try_get<bullet::rigidbody>())
1941 {
1942 auto vector = bullet::to_bullet(torque);
1943 const auto& body = bbody->internal;
1944
1945 if(add_torque(body.get(), vector, mode))
1946 {
1947 comp.set_angular_velocity(bullet::from_bullet(body->getAngularVelocity()));
1948 wake_up(*bbody);
1949 }
1950 }
1951}
1952
1954{
1955 if(comp.is_kinematic())
1956 {
1957 auto owner = comp.get_owner();
1958
1959 if(auto bbody = owner.try_get<bullet::rigidbody>())
1960 {
1961 bbody->internal->clearForces();
1962
1963 comp.set_velocity(bullet::from_bullet(bbody->internal->getLinearVelocity()));
1964 comp.set_angular_velocity(bullet::from_bullet(bbody->internal->getAngularVelocity()));
1965
1966 wake_up(*bbody);
1967 }
1968 }
1969}
1970
1971auto bullet_backend::ray_cast(const math::vec3& origin,
1972 const math::vec3& direction,
1973 float max_distance,
1974 int layer_mask,
1975 bool query_sensors) -> hpp::optional<raycast_hit>
1976{
1977 auto& ctx = engine::context();
1978 auto& ec = ctx.get_cached<ecs>();
1979 auto& registry = *ec.get_scene().registry;
1980
1981 auto& world = registry.ctx().get<bullet::world>();
1982
1983 return world.ray_cast_closest(origin, direction, max_distance, layer_mask, query_sensors);
1984}
1985
1986auto bullet_backend::ray_cast_all(const math::vec3& origin,
1987 const math::vec3& direction,
1988 float max_distance,
1989 int layer_mask,
1991{
1992 auto& ctx = engine::context();
1993 auto& ec = ctx.get_cached<ecs>();
1994 auto& registry = *ec.get_scene().registry;
1995
1996 auto& world = registry.ctx().get<bullet::world>();
1997
1998 return world.ray_cast_all(origin, direction, max_distance, layer_mask, query_sensors);
1999}
2000
2001auto bullet_backend::sphere_cast(const math::vec3& origin,
2002 const math::vec3& direction,
2003 float radius,
2004 float max_distance,
2005 int layer_mask,
2006 bool query_sensors) -> hpp::optional<raycast_hit>
2007{
2008 auto& ctx = engine::context();
2009 auto& ec = ctx.get_cached<ecs>();
2010 auto& registry = *ec.get_scene().registry;
2011
2012 auto& world = registry.ctx().get<bullet::world>();
2013
2014 return world.sphere_cast_closest(origin, direction, radius, max_distance, layer_mask, query_sensors);
2015}
2016
2017auto bullet_backend::sphere_cast_all(const math::vec3& origin,
2018 const math::vec3& direction,
2019 float radius,
2020 float max_distance,
2021 int layer_mask,
2023{
2024 auto& ctx = engine::context();
2025 auto& ec = ctx.get_cached<ecs>();
2026 auto& registry = *ec.get_scene().registry;
2027
2028 auto& world = registry.ctx().get<bullet::world>();
2029
2030 return world.sphere_cast_all(origin, direction, radius, max_distance, layer_mask, query_sensors);
2031}
2032
2033auto bullet_backend::sphere_overlap(const math::vec3& origin, float radius, int layer_mask, bool query_sensors)
2035{
2036 auto& ctx = engine::context();
2037 auto& ec = ctx.get_cached<ecs>();
2038 auto& registry = *ec.get_scene().registry;
2039
2040 auto& world = registry.ctx().get<bullet::world>();
2041
2042 return world.sphere_overlap(origin, radius, layer_mask, query_sensors);
2043}
2044
2046{
2047 auto& ec = ctx.get_cached<ecs>();
2048 auto& scn = ec.get_scene();
2049 auto& registry = *scn.registry;
2050
2051 auto& world = registry.ctx().emplace<bullet::world>(bullet::create_dynamics_world());
2052
2053 registry.on_destroy<bullet::rigidbody>().connect<&on_destroy_bullet_rigidbody_component>();
2054 registry.on_construct<active_component>().connect<&on_create_active_component>();
2055 registry.on_destroy<active_component>().connect<&on_destroy_active_component>();
2056
2057 registry.view<physics_component>().each(
2058 [&](auto e, auto&& comp)
2059 {
2060 sync_physics_body(world, comp, true);
2061 });
2062}
2063
2065{
2066 auto& ec = ctx.get_cached<ecs>();
2067 auto& registry = *ec.get_scene().registry;
2068
2069 auto& world = registry.ctx().get<bullet::world>();
2070
2071 registry.view<physics_component>().each(
2072 [&](auto e, auto&& comp)
2073 {
2074 destroy_phyisics_body(world, comp.get_owner(), true);
2075 });
2076
2077 registry.on_construct<active_component>().disconnect<&on_create_active_component>();
2078 registry.on_destroy<active_component>().disconnect<&on_destroy_active_component>();
2079 registry.on_destroy<bullet::rigidbody>().disconnect<&on_destroy_bullet_rigidbody_component>();
2080
2081 registry.ctx().erase<bullet::world>();
2082}
2083
2087
2091
2093{
2094 delta_t step(1.0f / 60.0f);
2095 on_frame_update(ctx, step);
2096}
2097
2099{
2100 auto& ev = ctx.get_cached<events>();
2101
2102 auto& ec = ctx.get_cached<ecs>();
2103 auto& registry = *ec.get_scene().registry;
2104 auto& world = registry.ctx().get<bullet::world>();
2105
2106 if(dt > delta_t::zero())
2107 {
2108 float fixed_time_step = 1.0f / 50.0f;
2109 int max_subs_steps = 3;
2110
2111 if(ctx.has<settings>())
2112 {
2113 auto& ss = ctx.get<settings>();
2114 fixed_time_step = ss.time.fixed_timestep;
2115 max_subs_steps = ss.time.max_fixed_steps;
2116 }
2117
2118 // Accumulate time
2119 world.elapsed += dt.count();
2120
2121 int steps = 0;
2122 while(world.elapsed >= fixed_time_step && steps < max_subs_steps)
2123 {
2124 delta_t step_dt(fixed_time_step);
2125 ev.on_frame_fixed_update(ctx, step_dt);
2126
2127 // update phyiscs spatial properties from transform
2128 uint64_t physics_entities{};
2129 uint64_t physics_entities_synced{};
2130
2132 [&](auto e, auto&& transform, auto&& rigidbody, auto&& active_comp)
2133 {
2134 physics_entities++;
2135 if(to_physics(world, transform, rigidbody))
2136 {
2137 physics_entities_synced++;
2138 }
2139 });
2140
2141 // APPLOG_TRACE("Physics Update: entities {} -> synced to physics {}",
2142 // physics_entities,
2143 // physics_entities_synced);
2144 // update physics
2145 world.simulate(fixed_time_step, fixed_time_step, 1);
2146
2147 physics_entities = {};
2148 physics_entities_synced = {};
2149 // update transform from phyiscs interpolated spatial properties
2151 [&](auto e, auto&& transform, auto&& rigidbody, auto&& active_comp)
2152 {
2153 physics_entities++;
2154 if(from_physics(world, transform, rigidbody))
2155 {
2156 physics_entities_synced++;
2157 }
2158 });
2159
2160 // APPLOG_TRACE("Physics Update: entities {} -> synced from physics {}",
2161 // physics_entities,
2162 // physics_entities_synced);
2163
2164 world.process_manifolds();
2165
2166 world.elapsed -= fixed_time_step;
2167 steps++;
2168 }
2169 }
2170}
2171
2173{
2174 auto& ec = ctx.get_cached<ecs>();
2175 auto& registry = *ec.get_scene().registry;
2176 auto world = registry.ctx().find<bullet::world>();
2177 if(world)
2178 {
2179 bullet::debugdraw drawer(dd);
2180 world->dynamics_world->setDebugDrawer(&drawer);
2181
2182 world->dynamics_world->debugDrawWorld();
2183
2184 world->dynamics_world->setDebugDrawer(nullptr);
2185 }
2186}
2187
2191
2192} // 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:270
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
@ sphere
Sphere type reflection probe.
hpp::small_vector< T, SmallSizeCapacity > physics_vector
auto to_bx(const glm::vec3 &data) -> bx::Vec3
Definition gizmos.cpp:7
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:115
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