Vulkan Schnee 0.0.1
High-performance rendering engine
Loading...
Searching...
No Matches
PhysicsBodyComponent.cpp
Go to the documentation of this file.
10
11#include <btBulletDynamicsCommon.h>
12#include <glm/gtc/type_ptr.hpp>
13#include <plog/Log.h>
14#include <algorithm>
15
16namespace EngineCore {
17
19 std::shared_ptr<SceneNode> sceneNode)
21 , entity_(entity)
22 , sceneNode_(sceneNode)
23 {
24 }
25
27 // Cleanup happens in endPlay(), but just in case
28 if (rigidBody_) {
30 }
31 if (compoundShape_) {
32 delete compoundShape_;
33 compoundShape_ = nullptr;
34 }
35 }
36
39
40 // Scan for existing CollisionComponents on the same entity
41 Entity* owner = getOwningEntity();
42 if (owner) {
43 auto existingCollisions = owner->getComponents<CollisionComponent>();
44 for (CollisionComponent* collision : existingCollisions) {
45 if (collision->getBodyComponent() == nullptr) {
46 registerCollision(collision);
47 }
48 }
49 }
50
51 isInitialized_ = true;
52
53 // Build initial shape if we have any collisions registered
54 if (!collisions_.empty()) {
57 }
58 }
59
61 // Clean up collision registrations
62 for (CollisionComponent* collision : collisions_) {
63 collision->bodyComponent_ = nullptr;
64 }
65 collisions_.clear();
66
67 // Clear ECS component pointers BEFORE removing to prevent double-free
68 // (PhysicsEngine::onPhysicsComponentDestroyed will try to delete them)
69 auto& registry = Ecs::RegistryManager::get();
70
71 // Use the SceneNode's entity (same as updateEcsComponent)
72 auto sceneNode = sceneNode_.lock();
73 if (sceneNode) {
74 entt::entity nodeEntity = sceneNode->getEntity();
75 if (registry.valid(nodeEntity) && registry.all_of<Ecs::SimulatesPhysics>(nodeEntity)) {
76 auto& physicsComp = registry.get<Ecs::SimulatesPhysics>(nodeEntity);
77 physicsComp.rigidBody = nullptr;
78 physicsComp.collisionShape = nullptr;
79 registry.remove<Ecs::SimulatesPhysics>(nodeEntity);
80 }
81 }
82
84
85 if (compoundShape_) {
86 delete compoundShape_;
87 compoundShape_ = nullptr;
88 }
89
90 isInitialized_ = false;
91
93 }
94
95 // =========================================================================
96 // Physics Properties
97 // =========================================================================
98
100 mass_ = mass;
101 if (rigidBody_ && !isStatic_) {
102 btVector3 localInertia(0, 0, 0);
103 if (mass_ > 0.0f && compoundShape_) {
104 compoundShape_->calculateLocalInertia(mass_, localInertia);
105 }
106 rigidBody_->setMassProps(mass_, localInertia);
107 rigidBody_->updateInertiaTensor();
108 }
109 }
110
112 friction_ = friction;
113 if (rigidBody_) {
114 rigidBody_->setFriction(friction_);
115 }
116 }
117
118 void PhysicsBodyComponent::setRestitution(float restitution) {
119 restitution_ = restitution;
120 if (rigidBody_) {
121 rigidBody_->setRestitution(restitution_);
122 }
123 }
124
126 isKinematic_ = kinematic;
128
129 if (rigidBody_) {
130 int flags = rigidBody_->getCollisionFlags();
131 if (kinematic) {
132 flags |= btCollisionObject::CF_KINEMATIC_OBJECT;
133 rigidBody_->setActivationState(DISABLE_DEACTIVATION);
134 } else {
135 flags &= ~btCollisionObject::CF_KINEMATIC_OBJECT;
136 rigidBody_->setActivationState(ACTIVE_TAG);
137 }
138 rigidBody_->setCollisionFlags(flags);
139 }
140 }
141
145
146 // Static objects need mass = 0 and rebuild
147 if (isStatic && rigidBody_) {
148 btVector3 localInertia(0, 0, 0);
149 rigidBody_->setMassProps(0.0f, localInertia);
150 int flags = rigidBody_->getCollisionFlags();
151 flags |= btCollisionObject::CF_STATIC_OBJECT;
152 rigidBody_->setCollisionFlags(flags);
153 }
154 }
155
156 // =========================================================================
157 // Transform Control
158 // =========================================================================
159
160 void PhysicsBodyComponent::teleport(const glm::vec3& position) {
161 auto sceneNode = sceneNode_.lock();
162 if (!sceneNode) return;
163
164 // Extract current rotation from world matrix
165 glm::mat4 worldMat = sceneNode->getWorldMatrix();
166 glm::quat currentRotation = glm::quat_cast(glm::mat3(worldMat));
167 teleport(position, currentRotation);
168 }
169
170 void PhysicsBodyComponent::teleport(const glm::vec3& position, const glm::quat& rotation) {
171 auto sceneNode = sceneNode_.lock();
172 if (!sceneNode) return;
173
174 // Update scene node
175 sceneNode->setWorldPosition(position);
176 sceneNode->setWorldRotation(rotation);
177
178 // Update Bullet transform
179 if (rigidBody_) {
180 btTransform newTransform;
181 newTransform.setIdentity();
182 newTransform.setOrigin(bt::getVec3(position));
183 newTransform.setRotation(bt::getQuat(rotation));
184
185 rigidBody_->setWorldTransform(newTransform);
186 rigidBody_->getMotionState()->setWorldTransform(newTransform);
187
188 // Clear velocities on teleport
189 rigidBody_->setLinearVelocity(btVector3(0, 0, 0));
190 rigidBody_->setAngularVelocity(btVector3(0, 0, 0));
191 rigidBody_->clearForces();
192
193 // Wake up the body
194 rigidBody_->activate(true);
195 }
196
197 // Mark transform dirty in ECS (use SceneNode's entity)
198 auto& registry = Ecs::RegistryManager::get();
199 registry.emplace_or_replace<Ecs::TransformDirty>(sceneNode->getEntity());
200 registry.emplace_or_replace<Ecs::TransformDirtyRenderer>(sceneNode->getEntity());
201 }
202
203 void PhysicsBodyComponent::setLinearVelocity(const glm::vec3& velocity) {
204 if (rigidBody_) {
205 rigidBody_->setLinearVelocity(bt::getVec3(velocity));
206 rigidBody_->activate(true);
207 }
208 }
209
211 if (rigidBody_) {
212 return glm::getVec3(rigidBody_->getLinearVelocity());
213 }
214 return glm::vec3(0.0f);
215 }
216
217 void PhysicsBodyComponent::setAngularVelocity(const glm::vec3& velocity) {
218 if (rigidBody_) {
219 rigidBody_->setAngularVelocity(bt::getVec3(velocity));
220 rigidBody_->activate(true);
221 }
222 }
223
225 if (rigidBody_) {
226 return glm::getVec3(rigidBody_->getAngularVelocity());
227 }
228 return glm::vec3(0.0f);
229 }
230
231 // =========================================================================
232 // Collision Management
233 // =========================================================================
234
236 if (!collision) return;
237
238 // Check if already registered
239 auto it = std::find(collisions_.begin(), collisions_.end(), collision);
240 if (it != collisions_.end()) return;
241
242 collisions_.push_back(collision);
243 collision->bodyComponent_ = this;
244 shapeDirty_ = true;
245
246 // If already initialized, rebuild immediately
247 if (isInitialized_) {
249 if (!rigidBody_) {
251 } else {
252 // Update existing rigid body with new shape
253 PhysicsEngine* physics = getPhysicsEngine();
254 if (physics) {
255 btDiscreteDynamicsWorld* world = physics->getDynamicsWorld();
256 world->removeRigidBody(rigidBody_);
257 rigidBody_->setCollisionShape(compoundShape_);
258 world->addRigidBody(rigidBody_);
259 }
260 }
261 }
262 }
263
265 if (!collision) return;
266
267 auto it = std::find(collisions_.begin(), collisions_.end(), collision);
268 if (it == collisions_.end()) return;
269
270 collisions_.erase(it);
271 collision->bodyComponent_ = nullptr;
272 shapeDirty_ = true;
273
274 // Rebuild if initialized
275 if (isInitialized_) {
276 if (collisions_.empty()) {
278 } else {
280 if (rigidBody_) {
281 PhysicsEngine* physics = getPhysicsEngine();
282 if (physics) {
283 btDiscreteDynamicsWorld* world = physics->getDynamicsWorld();
284 world->removeRigidBody(rigidBody_);
285 rigidBody_->setCollisionShape(compoundShape_);
286 world->addRigidBody(rigidBody_);
287 }
288 }
289 }
290 }
291 }
292
296
303
304 // =========================================================================
305 // Collision Callbacks
306 // =========================================================================
307
309 if (!rigidBody_) return;
310
311 PhysicsEngine* physics = getPhysicsEngine();
312 if (!physics) return;
313
314 btDiscreteDynamicsWorld* world = physics->getDynamicsWorld();
315 btDispatcher* dispatcher = world->getDispatcher();
316 int numManifolds = dispatcher->getNumManifolds();
317
318 std::set<CollisionComponent*> currentCollisions;
319
320 for (int i = 0; i < numManifolds; i++) {
321 btPersistentManifold* manifold = dispatcher->getManifoldByIndexInternal(i);
322 if (manifold->getNumContacts() == 0) continue;
323
324 const btCollisionObject* objA = manifold->getBody0();
325 const btCollisionObject* objB = manifold->getBody1();
326
327 // Check if this body is involved
328 bool isBodyA = (objA == rigidBody_);
329 bool isBodyB = (objB == rigidBody_);
330 if (!isBodyA && !isBodyB) continue;
331
332 // Get the other body
333 const btCollisionObject* otherObj = isBodyA ? objB : objA;
334
335 // Try to get the CollisionComponent from the other body's user pointer
336 void* userPtr = otherObj->getUserPointer();
337 if (userPtr) {
338 auto* otherBody = static_cast<PhysicsBodyComponent*>(userPtr);
339 // Add all collision components from the other body
340 for (CollisionComponent* otherCollision : otherBody->collisions_) {
341 currentCollisions.insert(otherCollision);
342 }
343 }
344 }
345
346 // Dispatch callbacks
347 for (CollisionComponent* other : currentCollisions) {
348 bool wasColliding = previousCollisions_.contains(other);
349
350 if (wasColliding) {
351 // Still colliding - dispatch stay
352 for (CollisionComponent* myCollision : collisions_) {
353 myCollision->onCollisionStay(other);
354 }
355 } else {
356 // New collision - dispatch begin
357 for (CollisionComponent* myCollision : collisions_) {
358 myCollision->onCollisionBegin(other);
359 }
360 }
361 }
362
363 // Check for ended collisions
365 if (!currentCollisions.contains(prev)) {
366 // Collision ended
367 for (CollisionComponent* myCollision : collisions_) {
368 myCollision->onCollisionEnd(prev);
369 }
370 }
371 }
372
373 previousCollisions_ = currentCollisions;
374 }
375
376 // =========================================================================
377 // Private Methods
378 // =========================================================================
379
381 // Delete old compound shape (but not the child shapes - they're owned by CollisionComponents)
382 if (compoundShape_) {
383 delete compoundShape_;
384 compoundShape_ = nullptr;
385 }
386
387 if (collisions_.empty()) return;
388
389 // Create new compound shape
390 compoundShape_ = new btCompoundShape();
391
392 for (CollisionComponent* collision : collisions_) {
393 btCollisionShape* shape = collision->getShape();
394 if (!shape) {
395 // Create and store the shape in the collision component
396 shape = collision->createShape();
397 collision->shape_ = shape;
398 }
399
400 if (shape) {
401 // Get the local transform for this collision
402 glm::mat4 localTransform = collision->getLocalTransform();
403 btTransform btLocal;
404 btLocal.setFromOpenGLMatrix(glm::value_ptr(localTransform));
405
406 compoundShape_->addChildShape(btLocal, shape);
407 }
408 }
409
410 shapeDirty_ = false;
411 }
412
414 PLOGI << "PhysicsBodyComponent::createRigidBody() starting";
415 if (rigidBody_ || !compoundShape_) {
416 PLOGI << "PhysicsBodyComponent::createRigidBody() early return - rigidBody_=" << (rigidBody_ ? "set" : "null") << " compoundShape_=" << (compoundShape_ ? "set" : "null");
417 return;
418 }
419
420 PhysicsEngine* physics = getPhysicsEngine();
421 if (!physics) {
422 PLOGI << "PhysicsBodyComponent::createRigidBody() - no physics engine";
423 return;
424 }
425
426 auto sceneNode = sceneNode_.lock();
427 if (!sceneNode) {
428 PLOGI << "PhysicsBodyComponent::createRigidBody() - sceneNode expired";
429 return;
430 }
431
432 // Get initial transform from scene node
433 glm::mat4 worldMatrix = sceneNode->getWorldMatrix();
434
436 info.transform = worldMatrix;
437 info.mass = isStatic_ ? 0.0f : mass_;
438 info.shape = compoundShape_;
439 info.friction = friction_;
441
442 rigidBody_ = physics->createRigidBody(info);
443
444 // Set user pointer for collision callbacks
445 rigidBody_->setUserPointer(this);
446
447 // Apply kinematic flag if needed
448 if (isKinematic_) {
449 int flags = rigidBody_->getCollisionFlags();
450 flags |= btCollisionObject::CF_KINEMATIC_OBJECT;
451 rigidBody_->setCollisionFlags(flags);
452 rigidBody_->setActivationState(DISABLE_DEACTIVATION);
453 }
454
455 // Apply trigger mode for all trigger collisions
456 bool hasTrigger = false;
457 for (CollisionComponent* collision : collisions_) {
458 if (collision->isTrigger()) {
459 hasTrigger = true;
460 break;
461 }
462 }
463 if (hasTrigger) {
464 int flags = rigidBody_->getCollisionFlags();
465 flags |= btCollisionObject::CF_NO_CONTACT_RESPONSE;
466 rigidBody_->setCollisionFlags(flags);
467 }
468
469 // Update ECS component
471
472 PLOGI << "PhysicsBodyComponent::createRigidBody() completed - rigidBody created with mass=" << info.mass;
473 }
474
476 if (!rigidBody_) return;
477
478 PhysicsEngine* physics = getPhysicsEngine();
479 if (physics) {
480 physics->removeRigidBody(rigidBody_);
481 }
482
483 rigidBody_ = nullptr;
484
485 // Note: ECS component cleanup is handled by PhysicsEngine::onPhysicsComponentDestroyed
486 // or we need to remove it manually
487 }
488
490 auto& registry = Ecs::RegistryManager::get();
491
492 // Use the SceneNode's entity, not the Actor's entity
493 // The physics sync loop requires SimulatesPhysics and LocalTransform on the same entity
494 auto sceneNode = sceneNode_.lock();
495 if (!sceneNode) return;
496
497 entt::entity nodeEntity = sceneNode->getEntity();
498 if (!registry.valid(nodeEntity)) return;
499
500 if (rigidBody_) {
501 auto& physicsComp = registry.get_or_emplace<Ecs::SimulatesPhysics>(nodeEntity);
502 physicsComp.rigidBody = rigidBody_;
503 physicsComp.collisionShape = compoundShape_;
504 physicsComp.isKinematic = isKinematic_;
505 physicsComp.isStatic = isStatic_;
506 }
507 }
508
511 return engine ? engine->getPhysicsEngine() : nullptr;
512 }
513
514} // namespace EngineCore
static entt::registry & get()
Gets the registry for all components.
Base class for collision shape components.
PhysicsBodyComponent * bodyComponent_
EngineCore::Engine * getEngineModule()
gets the pointer to the engine object
Definition Engine.cpp:1140
static EngineManager & getInstance()
gets a reference to the engine manager
Definition Engine.cpp:1135
PhysicsEngine * getPhysicsEngine() const
Getter for the pointer to the physics engine.
Definition Engine.cpp:1111
std::vector< T * > getComponents() const
Returns all components of type T.
Definition Entity.h:88
virtual void endPlay()
Called when the component is removed or the game ends.
virtual void beginPlay()
Called when the component is added to the scene or the game starts.
LogicComponent(Scene *owningScene)
Entity * getOwningEntity() const
Gets the entity this component belongs to.
void unregisterCollision(CollisionComponent *collision)
Unregisters a collision component from this body. Called by CollisionComponent::endPlay().
void registerCollision(CollisionComponent *collision)
Registers a collision component with this body. Called by CollisionComponent::beginPlay().
void beginPlay() override
Called when the component is added to the scene or the game starts.
void endPlay() override
Called when the component is removed or the game ends.
void setRestitution(float restitution)
Sets the restitution (bounciness).
void setFriction(float friction)
Sets the friction coefficient.
void setStatic(bool isStatic)
Sets whether the body is static (immovable).
glm::vec3 getAngularVelocity() const
Gets the angular velocity.
void markShapeDirty()
Marks the collision shape as needing rebuild. Called when a collision component's shape changes.
std::weak_ptr< SceneNode > sceneNode_
std::set< CollisionComponent * > previousCollisions_
bool isStatic() const
Checks if the body is static.
void setKinematic(bool kinematic)
Sets whether the body is kinematic. Kinematic bodies are moved by code, not physics.
void setAngularVelocity(const glm::vec3 &velocity)
Sets the angular velocity.
glm::vec3 getLinearVelocity() const
Gets the linear velocity.
void setMass(float mass)
Sets the mass of the rigid body.
void processCollisions()
Processes collision events and dispatches callbacks. Called after physics step.
void setLinearVelocity(const glm::vec3 &velocity)
Sets the linear velocity.
void rebuildIfDirty()
Rebuilds the collision shape if dirty.
void teleport(const glm::vec3 &position)
Teleports the body to a new position.
std::vector< CollisionComponent * > collisions_
PhysicsBodyComponent(Scene *scene, entt::entity entity, std::shared_ptr< SceneNode > sceneNode)
Constructs a PhysicsBodyComponent.
The physics engine manages creating and destroying physics objects for the physics simulation.
btRigidBody * createRigidBody(const RigidBodyCreateInfo &info) const
Creates a rigid body for the physics simulation.
btDiscreteDynamicsWorld * getDynamicsWorld() const
Gets the world where the physics simulation is performed inside.
void removeRigidBody(btRigidBody *body) const
Removes a rigid body from the physics simulation.
A scene is the overarching structure which can spawn actors.
Definition Scene.h:17
Log category system implementation.
btQuaternion getQuat(glm::quat quaternion)
Definition Converter.cpp:17
btVector3 getVec3(glm::vec3 v)
Definition Converter.cpp:12
vec3 getVec3(btVector3 v)
Definition Converter.cpp:25
Tag for EnTT which tags entities which should simulate physics.
Definition EcsData.h:101
btRigidBody * rigidBody
Definition EcsData.h:102
Tag for transforms that need GPU upload. Separate from TransformDirty because the renderer clears thi...
Definition EcsData.h:123
Tag for dirty transforms (used by scene graph synchronization)
Definition EcsData.h:113
Rigid body create info is used to create a rigid body. It delivers all informations like the initial ...
glm::mat4 transform
initial location for the rigid body
float restitution
energy absorption 0.0f means full absorption on impact, 0.5f half of the energy is maintained after i...
float friction
friction 0.0f - 1.0f. 0 is ice 0.3f - 0.5f wood or plastic. 0.7f - 1.0f High friction
btCollisionShape * shape
pointer to the collision shape
float mass
mass in Kilograms. Mass of 0 is a static object --> immovable