From 5cf8cb7445a45a20ea798404f5c8b09113537194 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Thu, 2 Jul 2020 23:44:27 +0200 Subject: [PATCH 01/74] Make PhysicsWorld::createIslands() method faster --- src/engine/PhysicsWorld.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/engine/PhysicsWorld.cpp b/src/engine/PhysicsWorld.cpp index b188f635..b79a6969 100644 --- a/src/engine/PhysicsWorld.cpp +++ b/src/engine/PhysicsWorld.cpp @@ -759,6 +759,9 @@ void PhysicsWorld::createIslands() { // Create a stack for the bodies to visit during the Depth First Search Stack bodyEntityIndicesToVisit(mMemoryManager.getSingleFrameAllocator()); + // List of static bodies added to the current island (used to reset the isAlreadyInIsland variable of static bodies) + List staticBodiesAddedToIsland(mMemoryManager.getSingleFrameAllocator()); + uint nbTotalManifolds = 0; // For each rigid body component @@ -795,7 +798,13 @@ void PhysicsWorld::createIslands() { rigidBodyToVisit->setIsSleeping(false); // If the current body is static, we do not want to perform the DFS search across that body - if (rigidBodyToVisit->getType() == BodyType::STATIC) continue; + if (rigidBodyToVisit->getType() == BodyType::STATIC) { + + staticBodiesAddedToIsland.add(bodyToVisitEntity); + + // Go to the next body + continue; + } // If the body is involved in contacts with other bodies auto itBodyContactPairs = mCollisionDetection.mMapBodyToContactPairs.find(bodyToVisitEntity); @@ -863,12 +872,13 @@ void PhysicsWorld::createIslands() { // Reset the isAlreadyIsland variable of the static bodies so that they // can also be included in the other islands - for (uint j=0; j < mRigidBodyComponents.getNbEnabledComponents(); j++) { + for (uint j=0; j < staticBodiesAddedToIsland.size(); j++) { - if (mRigidBodyComponents.mBodyTypes[j] == BodyType::STATIC) { - mRigidBodyComponents.mIsAlreadyInIsland[j] = false; - } + assert(mRigidBodyComponents.getBodyType(staticBodiesAddedToIsland[j]) == BodyType::STATIC); + mRigidBodyComponents.setIsAlreadyInIsland(staticBodiesAddedToIsland[j], false); } + + staticBodiesAddedToIsland.clear(); } mCollisionDetection.mMapBodyToContactPairs.clear(true); From 98ac47cbad3e799c0b7c855e44bd5d6751779a7b Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Tue, 7 Jul 2020 18:21:41 +0200 Subject: [PATCH 02/74] Optimization of the islands computation --- include/reactphysics3d/engine/Islands.h | 72 +++++++++++++++++++++---- src/engine/PhysicsWorld.cpp | 71 +++++++++++++----------- src/systems/ContactSolverSystem.cpp | 2 +- 3 files changed, 104 insertions(+), 41 deletions(-) diff --git a/include/reactphysics3d/engine/Islands.h b/include/reactphysics3d/engine/Islands.h index 2ffc26a5..59d09f7e 100644 --- a/include/reactphysics3d/engine/Islands.h +++ b/include/reactphysics3d/engine/Islands.h @@ -46,10 +46,14 @@ struct Islands { private: - // -------------------- Attributes -------------------- // + /// Number of islands in the previous frame + uint32 mNbIslandsPreviousFrame; - /// Reference to the memory allocator - MemoryAllocator& memoryAllocator; + /// Maximum number of bodies in a single island in the previous frame + uint32 mNbMaxBodiesInIslandPreviousFrame; + + /// Maximum number of bodies in a single island in the current frame + uint32 mNbMaxBodiesInIslandCurrentFrame; public: @@ -62,15 +66,22 @@ struct Islands { /// For each island, number of contact manifolds in the island List nbContactManifolds; - /// For each island, list of all the entities of the bodies in the island - List> bodyEntities; + /// List of all the entities of the bodies in the islands (stored sequentially) + List bodyEntities; + + /// For each island we store the starting index of the bodies of that island in the "bodyEntities" array + List startBodyEntitiesIndex; + + /// For each island, total number of bodies in the island + List nbBodiesInIsland; // -------------------- Methods -------------------- // /// Constructor Islands(MemoryAllocator& allocator) - :memoryAllocator(allocator), contactManifoldsIndices(allocator), nbContactManifolds(allocator), - bodyEntities(allocator) { + :mNbIslandsPreviousFrame(10), mNbMaxBodiesInIslandPreviousFrame(0), mNbMaxBodiesInIslandCurrentFrame(0), + contactManifoldsIndices(allocator), nbContactManifolds(allocator), + bodyEntities(allocator), startBodyEntitiesIndex(allocator), nbBodiesInIsland(allocator) { } @@ -78,7 +89,7 @@ struct Islands { ~Islands() = default; /// Assignment operator - Islands& operator=(const Islands& island) = default; + Islands& operator=(const Islands& island) = delete; /// Copy-constructor Islands(const Islands& island) = default; @@ -91,21 +102,62 @@ struct Islands { /// Add an island and return its index uint32 addIsland(uint32 contactManifoldStartIndex) { - uint32 islandIndex = contactManifoldsIndices.size(); + const uint32 islandIndex = contactManifoldsIndices.size(); contactManifoldsIndices.add(contactManifoldStartIndex); nbContactManifolds.add(0); - bodyEntities.add(List(memoryAllocator)); + startBodyEntitiesIndex.add(bodyEntities.size()); + nbBodiesInIsland.add(0); + + if (islandIndex > 0 && nbBodiesInIsland[islandIndex-1] > mNbMaxBodiesInIslandCurrentFrame) { + mNbMaxBodiesInIslandCurrentFrame = nbBodiesInIsland[islandIndex-1]; + } return islandIndex; } + void addBodyToIsland(Entity bodyEntity) { + + const uint32 islandIndex = contactManifoldsIndices.size(); + assert(islandIndex > 0); + + bodyEntities.add(bodyEntity); + nbBodiesInIsland[islandIndex - 1]++; + } + + /// Reserve memory for the current frame + void reserveMemory() { + + contactManifoldsIndices.reserve(mNbIslandsPreviousFrame); + nbContactManifolds.reserve(mNbIslandsPreviousFrame); + startBodyEntitiesIndex.reserve(mNbIslandsPreviousFrame); + nbBodiesInIsland.reserve(mNbIslandsPreviousFrame); + + bodyEntities.reserve(mNbMaxBodiesInIslandPreviousFrame); + } + /// Clear all the islands void clear() { + const uint32 nbIslands = nbContactManifolds.size(); + + if (nbIslands > 0 && nbBodiesInIsland[nbIslands-1] > mNbMaxBodiesInIslandCurrentFrame) { + mNbMaxBodiesInIslandCurrentFrame = nbBodiesInIsland[nbIslands-1]; + } + + mNbIslandsPreviousFrame = nbContactManifolds.size(); + mNbIslandsPreviousFrame = mNbMaxBodiesInIslandCurrentFrame; + mNbMaxBodiesInIslandCurrentFrame = 0; + contactManifoldsIndices.clear(true); nbContactManifolds.clear(true); bodyEntities.clear(true); + startBodyEntitiesIndex.clear(true); + nbBodiesInIsland.clear(true); + } + + uint32 getNbMaxBodiesInIslandPreviousFrame() const { + return mNbMaxBodiesInIslandPreviousFrame; } }; diff --git a/src/engine/PhysicsWorld.cpp b/src/engine/PhysicsWorld.cpp index b79a6969..02465fbd 100644 --- a/src/engine/PhysicsWorld.cpp +++ b/src/engine/PhysicsWorld.cpp @@ -756,11 +756,14 @@ void PhysicsWorld::createIslands() { mJointsComponents.mIsAlreadyInIsland[i] = false; } + // Reserve memory for the islands + mIslands.reserveMemory(); + // Create a stack for the bodies to visit during the Depth First Search - Stack bodyEntityIndicesToVisit(mMemoryManager.getSingleFrameAllocator()); + Stack bodyEntityIndicesToVisit(mMemoryManager.getSingleFrameAllocator(), mIslands.getNbMaxBodiesInIslandPreviousFrame()); // List of static bodies added to the current island (used to reset the isAlreadyInIsland variable of static bodies) - List staticBodiesAddedToIsland(mMemoryManager.getSingleFrameAllocator()); + List staticBodiesAddedToIsland(mMemoryManager.getSingleFrameAllocator()); uint nbTotalManifolds = 0; @@ -778,7 +781,7 @@ void PhysicsWorld::createIslands() { // Add the body into the stack of bodies to visit mRigidBodyComponents.mIsAlreadyInIsland[b] = true; - bodyEntityIndicesToVisit.push(mRigidBodyComponents.mBodiesEntities[b]); + bodyEntityIndicesToVisit.push(b); // Create the new island uint32 islandIndex = mIslands.addIsland(nbTotalManifolds); @@ -787,20 +790,26 @@ void PhysicsWorld::createIslands() { while (bodyEntityIndicesToVisit.size() > 0) { // Get the next body to visit from the stack - const Entity bodyToVisitEntity = bodyEntityIndicesToVisit.pop(); + const uint32 bodyToVisitIndex = bodyEntityIndicesToVisit.pop(); + + // Get the body entity + const Entity bodyToVisitEntity = mRigidBodyComponents.mBodiesEntities[bodyToVisitIndex]; // Add the body into the island - mIslands.bodyEntities[islandIndex].add(bodyToVisitEntity); + mIslands.addBodyToIsland(bodyToVisitEntity); - RigidBody* rigidBodyToVisit = static_cast(mCollisionBodyComponents.getBody(bodyToVisitEntity)); + RigidBody* rigidBodyToVisit = mRigidBodyComponents.mRigidBodies[bodyToVisitIndex]; - // Awake the body if it is sleeping + // Awake the body if it is sleeping (note that this called might change the body index in the mRigidBodyComponents array) rigidBodyToVisit->setIsSleeping(false); // If the current body is static, we do not want to perform the DFS search across that body if (rigidBodyToVisit->getType() == BodyType::STATIC) { - staticBodiesAddedToIsland.add(bodyToVisitEntity); + // Get the new body index in the mRigidBodyComponents (this index might have changed due to the call to rigidBodyToVisite->setIsSleeping(false)) + const uint32 newBodyIndex = mRigidBodyComponents.getEntityIndex(bodyToVisitEntity); + + staticBodiesAddedToIsland.add(newBodyIndex); // Go to the next body continue; @@ -831,13 +840,14 @@ void PhysicsWorld::createIslands() { pair.isAlreadyInIsland = true; const Entity otherBodyEntity = pair.body1Entity == bodyToVisitEntity ? pair.body2Entity : pair.body1Entity; + const uint32 otherBodyIndex = mRigidBodyComponents.getEntityIndex(otherBodyEntity); // Check if the other body has already been added to the island - if (mRigidBodyComponents.getIsAlreadyInIsland(otherBodyEntity)) continue; + if (mRigidBodyComponents.mIsAlreadyInIsland[otherBodyIndex]) continue; // Insert the other body into the stack of bodies to visit - bodyEntityIndicesToVisit.push(otherBodyEntity); - mRigidBodyComponents.setIsAlreadyInIsland(otherBodyEntity, true); + bodyEntityIndicesToVisit.push(otherBodyIndex); + mRigidBodyComponents.mIsAlreadyInIsland[otherBodyIndex] = true; } else { @@ -861,12 +871,14 @@ void PhysicsWorld::createIslands() { const Entity body2Entity = mJointsComponents.getBody2Entity(joints[i]); const Entity otherBodyEntity = body1Entity == bodyToVisitEntity ? body2Entity : body1Entity; + const uint32 otherBodyIndex = mRigidBodyComponents.getEntityIndex(otherBodyEntity); + // Check if the other body has already been added to the island - if (mRigidBodyComponents.getIsAlreadyInIsland(otherBodyEntity)) continue; + if (mRigidBodyComponents.mIsAlreadyInIsland[otherBodyIndex]) continue; // Insert the other body into the stack of bodies to visit - bodyEntityIndicesToVisit.push(otherBodyEntity); - mRigidBodyComponents.setIsAlreadyInIsland(otherBodyEntity, true); + bodyEntityIndicesToVisit.push(otherBodyIndex); + mRigidBodyComponents.mIsAlreadyInIsland[otherBodyIndex] = true; } } @@ -874,8 +886,8 @@ void PhysicsWorld::createIslands() { // can also be included in the other islands for (uint j=0; j < staticBodiesAddedToIsland.size(); j++) { - assert(mRigidBodyComponents.getBodyType(staticBodiesAddedToIsland[j]) == BodyType::STATIC); - mRigidBodyComponents.setIsAlreadyInIsland(staticBodiesAddedToIsland[j], false); + assert(mRigidBodyComponents.mBodyTypes[staticBodiesAddedToIsland[j]] == BodyType::STATIC); + mRigidBodyComponents.mIsAlreadyInIsland[staticBodiesAddedToIsland[j]] = false; } staticBodiesAddedToIsland.clear(); @@ -900,30 +912,29 @@ void PhysicsWorld::updateSleepingBodies(decimal timeStep) { decimal minSleepTime = DECIMAL_LARGEST; // For each body of the island - for (uint b=0; b < mIslands.bodyEntities[i].size(); b++) { + for (uint b=0; b < mIslands.nbBodiesInIsland[i]; b++) { - const Entity bodyEntity = mIslands.bodyEntities[i][b]; + const Entity bodyEntity = mIslands.bodyEntities[mIslands.startBodyEntitiesIndex[i] + b]; + const uint32 bodyIndex = mRigidBodyComponents.getEntityIndex(bodyEntity); // Skip static bodies - if (mRigidBodyComponents.getBodyType(bodyEntity) == BodyType::STATIC) continue; + if (mRigidBodyComponents.mBodyTypes[bodyIndex] == BodyType::STATIC) continue; // If the body is velocity is large enough to stay awake - if (mRigidBodyComponents.getLinearVelocity(bodyEntity).lengthSquare() > sleepLinearVelocitySquare || - mRigidBodyComponents.getAngularVelocity(bodyEntity).lengthSquare() > sleepAngularVelocitySquare || - !mRigidBodyComponents.getIsAllowedToSleep(bodyEntity)) { + if (mRigidBodyComponents.mLinearVelocities[bodyIndex].lengthSquare() > sleepLinearVelocitySquare || + mRigidBodyComponents.mAngularVelocities[bodyIndex].lengthSquare() > sleepAngularVelocitySquare || + !mRigidBodyComponents.mIsAllowedToSleep[bodyIndex]) { // Reset the sleep time of the body - mRigidBodyComponents.setSleepTime(bodyEntity, decimal(0.0)); + mRigidBodyComponents.mSleepTimes[bodyIndex] = decimal(0.0); minSleepTime = decimal(0.0); } else { // If the body velocity is below the sleeping velocity threshold // Increase the sleep time - decimal sleepTime = mRigidBodyComponents.getSleepTime(bodyEntity); - mRigidBodyComponents.setSleepTime(bodyEntity, sleepTime + timeStep); - sleepTime = mRigidBodyComponents.getSleepTime(bodyEntity); - if (sleepTime < minSleepTime) { - minSleepTime = sleepTime; + mRigidBodyComponents.mSleepTimes[bodyIndex] += timeStep; + if (mRigidBodyComponents.mSleepTimes[bodyIndex] < minSleepTime) { + minSleepTime = mRigidBodyComponents.mSleepTimes[bodyIndex]; } } } @@ -934,9 +945,9 @@ void PhysicsWorld::updateSleepingBodies(decimal timeStep) { if (minSleepTime >= mTimeBeforeSleep) { // Put all the bodies of the island to sleep - for (uint b=0; b < mIslands.bodyEntities[i].size(); b++) { + for (uint b=0; b < mIslands.nbBodiesInIsland[i]; b++) { - const Entity bodyEntity = mIslands.bodyEntities[i][b]; + const Entity bodyEntity = mIslands.bodyEntities[mIslands.startBodyEntitiesIndex[i] + b]; RigidBody* body = mRigidBodyComponents.getRigidBody(bodyEntity); body->setIsSleeping(true); } diff --git a/src/systems/ContactSolverSystem.cpp b/src/systems/ContactSolverSystem.cpp index f384a78a..1f6b6f74 100644 --- a/src/systems/ContactSolverSystem.cpp +++ b/src/systems/ContactSolverSystem.cpp @@ -113,7 +113,7 @@ void ContactSolverSystem::initializeForIsland(uint islandIndex) { RP3D_PROFILE("ContactSolver::initializeForIsland()", mProfiler); - assert(mIslands.bodyEntities[islandIndex].size() > 0); + assert(mIslands.nbBodiesInIsland[islandIndex] > 0); assert(mIslands.nbContactManifolds[islandIndex] > 0); // For each contact manifold of the island From 8fd5c589864db08aa60e182e2e6945cb9a141ec6 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Sat, 11 Jul 2020 14:06:39 +0200 Subject: [PATCH 03/74] Optimization of contacts processing --- include/reactphysics3d/collision/ContactManifoldInfo.h | 2 +- include/reactphysics3d/collision/ContactPair.h | 2 +- src/constraint/ContactPoint.cpp | 2 +- src/systems/CollisionDetectionSystem.cpp | 3 +++ src/systems/ContactSolverSystem.cpp | 10 +++++----- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/include/reactphysics3d/collision/ContactManifoldInfo.h b/include/reactphysics3d/collision/ContactManifoldInfo.h index 728ee1ed..6f6a0e74 100644 --- a/include/reactphysics3d/collision/ContactManifoldInfo.h +++ b/include/reactphysics3d/collision/ContactManifoldInfo.h @@ -55,7 +55,7 @@ struct ContactManifoldInfo { /// Constructor ContactManifoldInfo(uint64 pairId, MemoryAllocator& allocator) - : potentialContactPointsIndices(allocator), pairId(pairId) { + : potentialContactPointsIndices(allocator, 4), pairId(pairId) { } diff --git a/include/reactphysics3d/collision/ContactPair.h b/include/reactphysics3d/collision/ContactPair.h index 82bf9d20..f9a49817 100644 --- a/include/reactphysics3d/collision/ContactPair.h +++ b/include/reactphysics3d/collision/ContactPair.h @@ -91,7 +91,7 @@ struct ContactPair { /// Constructor ContactPair(uint64 pairId, Entity body1Entity, Entity body2Entity, Entity collider1Entity, Entity collider2Entity, uint contactPairIndex, bool collidingInPreviousFrame, bool isTrigger, MemoryAllocator& allocator) - : pairId(pairId), potentialContactManifoldsIndices(allocator), body1Entity(body1Entity), body2Entity(body2Entity), + : pairId(pairId), potentialContactManifoldsIndices(allocator, 8), body1Entity(body1Entity), body2Entity(body2Entity), collider1Entity(collider1Entity), collider2Entity(collider2Entity), isAlreadyInIsland(false), contactPairIndex(contactPairIndex), contactManifoldsIndex(0), nbContactManifolds(0), contactPointsIndex(0), nbToTalContactPoints(0), collidingInPreviousFrame(collidingInPreviousFrame), isTrigger(isTrigger) { diff --git a/src/constraint/ContactPoint.cpp b/src/constraint/ContactPoint.cpp index 6f0ba719..97667ed8 100644 --- a/src/constraint/ContactPoint.cpp +++ b/src/constraint/ContactPoint.cpp @@ -36,7 +36,7 @@ ContactPoint::ContactPoint(const ContactPointInfo* contactInfo, decimal persiste mPenetrationDepth(contactInfo->penetrationDepth), mLocalPointOnShape1(contactInfo->localPoint1), mLocalPointOnShape2(contactInfo->localPoint2), - mIsRestingContact(false), mIsObsolete(false), mNext(nullptr), mPrevious(nullptr), + mIsRestingContact(false), mIsObsolete(false), mPersistentContactDistanceThreshold(persistentContactDistanceThreshold) { assert(mPenetrationDepth > decimal(0.0)); diff --git a/src/systems/CollisionDetectionSystem.cpp b/src/systems/CollisionDetectionSystem.cpp index 24f9660c..9c15e7c7 100644 --- a/src/systems/CollisionDetectionSystem.cpp +++ b/src/systems/CollisionDetectionSystem.cpp @@ -520,6 +520,9 @@ void CollisionDetectionSystem::computeNarrowPhase() { // Swap the previous and current contacts lists swapPreviousAndCurrentContacts(); + mPotentialContactManifolds.reserve(mPreviousContactManifolds->size()); + mPotentialContactPoints.reserve(mPreviousContactPoints->size()); + // Test the narrow-phase collision detection on the batches to be tested testNarrowPhaseCollision(mNarrowPhaseInput, true, allocator); diff --git a/src/systems/ContactSolverSystem.cpp b/src/systems/ContactSolverSystem.cpp index 1f6b6f74..76a79c74 100644 --- a/src/systems/ContactSolverSystem.cpp +++ b/src/systems/ContactSolverSystem.cpp @@ -125,17 +125,17 @@ void ContactSolverSystem::initializeForIsland(uint islandIndex) { assert(externalManifold.nbContactPoints > 0); + const uint rigidBodyIndex1 = mRigidBodyComponents.getEntityIndex(externalManifold.bodyEntity1); + const uint rigidBodyIndex2 = mRigidBodyComponents.getEntityIndex(externalManifold.bodyEntity2); + // Get the two bodies of the contact - RigidBody* body1 = static_cast(mBodyComponents.getBody(externalManifold.bodyEntity1)); - RigidBody* body2 = static_cast(mBodyComponents.getBody(externalManifold.bodyEntity2)); + RigidBody* body1 = mRigidBodyComponents.mRigidBodies[rigidBodyIndex1]; + RigidBody* body2 = mRigidBodyComponents.mRigidBodies[rigidBodyIndex2]; assert(body1 != nullptr); assert(body2 != nullptr); assert(!mBodyComponents.getIsEntityDisabled(externalManifold.bodyEntity1)); assert(!mBodyComponents.getIsEntityDisabled(externalManifold.bodyEntity2)); - const uint rigidBodyIndex1 = mRigidBodyComponents.getEntityIndex(externalManifold.bodyEntity1); - const uint rigidBodyIndex2 = mRigidBodyComponents.getEntityIndex(externalManifold.bodyEntity2); - Collider* collider1 = mColliderComponents.getCollider(externalManifold.colliderEntity1); Collider* collider2 = mColliderComponents.getCollider(externalManifold.colliderEntity2); From 68212e26d875c1c758fc525159eee738ac94a936 Mon Sep 17 00:00:00 2001 From: manuel5975p Date: Sun, 12 Jul 2020 01:01:40 +0200 Subject: [PATCH 04/74] Fix typos in documentation Add variable type in declaration Fix PolygonaVertexArray -> PolygonVertexArray --- documentation/UserManual/ReactPhysics3D-UserManual.tex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/UserManual/ReactPhysics3D-UserManual.tex b/documentation/UserManual/ReactPhysics3D-UserManual.tex index a7a33052..8aaf65bf 100644 --- a/documentation/UserManual/ReactPhysics3D-UserManual.tex +++ b/documentation/UserManual/ReactPhysics3D-UserManual.tex @@ -1201,7 +1201,7 @@ indices[16]=2; indices[17]=3; indices[18]=7; indices[19]=6; indices[20]=0; indices[21]=4; indices[22]=7; indices[23]=3; // Description of the six faces of the convex mesh -polygonFaces = new PolygonVertexArray::PolygonFace[6]; +PolygonVertexArray::PolygonFace* polygonFaces = new PolygonVertexArray::PolygonFace[6]; PolygonVertexArray::PolygonFace* face = polygonFaces; for (int f = 0; f < 6; f++) { @@ -1215,7 +1215,7 @@ for (int f = 0; f < 6; f++) { } // Create the polygon vertex array -PolygonaVertexArray* polygonVertexArray = new PolygonVertexArray(8, vertices, 3 x sizeof(float), +PolygonVertexArray* polygonVertexArray = new PolygonVertexArray(8, vertices, 3 x sizeof(float), indices, sizeof(int), 6, polygonFaces, PolygonVertexArray::VertexDataType::VERTEX_FLOAT_TYPE, PolygonVertexArray::IndexDataType::INDEX_INTEGER_TYPE); From b410b26e2387bc2fc860c95e869f2feadc400573 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Tue, 14 Jul 2020 22:35:03 +0200 Subject: [PATCH 05/74] Fix issues in PhysicsWorld::createIslands() method and add more optimization in CollisionDetectionSystem::computeNarrowPhase() method --- .../reactphysics3d/components/Components.h | 16 +++ .../components/RigidBodyComponents.h | 13 ++ .../systems/CollisionDetectionSystem.h | 12 +- src/components/RigidBodyComponents.cpp | 10 +- src/engine/PhysicsWorld.cpp | 127 ++++++++---------- src/systems/CollisionDetectionSystem.cpp | 63 ++++----- 6 files changed, 126 insertions(+), 115 deletions(-) diff --git a/include/reactphysics3d/components/Components.h b/include/reactphysics3d/components/Components.h index 71785930..ba0c15f1 100644 --- a/include/reactphysics3d/components/Components.h +++ b/include/reactphysics3d/components/Components.h @@ -113,6 +113,9 @@ class Components { /// Return true if there is a component for a given entity bool hasComponent(Entity entity) const; + /// Return true if there is a component for a given entiy and if so set the entity index + bool hasComponentGetIndex(Entity entity, uint32& entityIndex) const; + /// Return the number of components uint32 getNbComponents() const; @@ -134,6 +137,19 @@ inline bool Components::hasComponent(Entity entity) const { return mMapEntityToComponentIndex.containsKey(entity); } +// Return true if there is a component for a given entiy and if so set the entity index +inline bool Components::hasComponentGetIndex(Entity entity, uint32& entityIndex) const { + + auto it = mMapEntityToComponentIndex.find(entity); + + if (it != mMapEntityToComponentIndex.end()) { + entityIndex = it->second; + return true; + } + + return false; +} + // Return the number of components inline uint32 Components::getNbComponents() const { return mNbComponents; diff --git a/include/reactphysics3d/components/RigidBodyComponents.h b/include/reactphysics3d/components/RigidBodyComponents.h index 1be18761..8097aaf3 100644 --- a/include/reactphysics3d/components/RigidBodyComponents.h +++ b/include/reactphysics3d/components/RigidBodyComponents.h @@ -145,6 +145,9 @@ class RigidBodyComponents : public Components { /// For each body, the list of joints entities the body is part of List* mJoints; + /// For each body, the list of the indices of contact pairs in which the body is involved + List* mContactPairs; + // -------------------- Methods -------------------- // /// Allocate memory for a given number of components @@ -342,6 +345,9 @@ class RigidBodyComponents : public Components { /// Remove a joint from a body component void removeJointFromBody(Entity bodyEntity, Entity jointEntity); + /// A an associated contact pairs into the contact pairs array of the body + void addContacPair(Entity bodyEntity, uint contactPairIndex); + // -------------------- Friendship -------------------- // friend class PhysicsWorld; @@ -769,6 +775,13 @@ inline void RigidBodyComponents::removeJointFromBody(Entity bodyEntity, Entity j mJoints[mMapEntityToComponentIndex[bodyEntity]].remove(jointEntity); } +// A an associated contact pairs into the contact pairs array of the body +inline void RigidBodyComponents::addContacPair(Entity bodyEntity, uint contactPairIndex) { + + assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); + mContactPairs[mMapEntityToComponentIndex[bodyEntity]].add(contactPairIndex); +} + } #endif diff --git a/include/reactphysics3d/systems/CollisionDetectionSystem.h b/include/reactphysics3d/systems/CollisionDetectionSystem.h index b2d24f39..22bda63e 100644 --- a/include/reactphysics3d/systems/CollisionDetectionSystem.h +++ b/include/reactphysics3d/systems/CollisionDetectionSystem.h @@ -83,6 +83,9 @@ class CollisionDetectionSystem { /// Reference the collider components ColliderComponents& mCollidersComponents; + /// Reference to the rigid bodies components + RigidBodyComponents& mRigidBodyComponents; + /// Collision Detection Dispatch configuration CollisionDispatch mCollisionDispatch; @@ -163,9 +166,6 @@ class CollisionDetectionSystem { /// Pointer to the contact points of the current frame (either mContactPoints1 or mContactPoints2) List* mCurrentContactPoints; - /// Map a body entity to the list of contact pairs in which it is involved - Map> mMapBodyToContactPairs; - #ifdef IS_RP3D_PROFILING_ENABLED /// Pointer to the profiler @@ -224,14 +224,12 @@ class CollisionDetectionSystem { void processPotentialContacts(NarrowPhaseInfoBatch& narrowPhaseInfoBatch, bool updateLastFrameInfo, List& potentialContactPoints, Map* mapPairIdToContactPairIndex, - List& potentialContactManifolds, List* contactPairs, - Map>& mapBodyToContactPairs); + List& potentialContactManifolds, List* contactPairs); /// Process the potential contacts after narrow-phase collision detection void processAllPotentialContacts(NarrowPhaseInput& narrowPhaseInput, bool updateLastFrameInfo, List& potentialContactPoints, Map* mapPairIdToContactPairIndex, - List& potentialContactManifolds, List* contactPairs, - Map>& mapBodyToContactPairs); + List& potentialContactManifolds, List* contactPairs); /// Reduce the potential contact manifolds and contact points of the overlapping pair contacts void reducePotentialContactManifolds(List* contactPairs, List& potentialContactManifolds, diff --git a/src/components/RigidBodyComponents.cpp b/src/components/RigidBodyComponents.cpp index fa3086e1..4653daab 100644 --- a/src/components/RigidBodyComponents.cpp +++ b/src/components/RigidBodyComponents.cpp @@ -43,7 +43,7 @@ RigidBodyComponents::RigidBodyComponents(MemoryAllocator& allocator) sizeof(Vector3) + sizeof(Vector3) + sizeof(Vector3) + sizeof(Vector3) + sizeof(Vector3) + sizeof(Vector3) + sizeof(Quaternion) + sizeof(Vector3) + sizeof(Vector3) + - sizeof(bool) + sizeof(bool) + sizeof(List)) { + sizeof(bool) + sizeof(bool) + sizeof(List) + sizeof(List)) { // Allocate memory for the components data allocate(INIT_NB_ALLOCATED_COMPONENTS); @@ -89,6 +89,7 @@ void RigidBodyComponents::allocate(uint32 nbComponentsToAllocate) { bool* newIsGravityEnabled = reinterpret_cast(newCentersOfMassWorld + nbComponentsToAllocate); bool* newIsAlreadyInIsland = reinterpret_cast(newIsGravityEnabled + nbComponentsToAllocate); List* newJoints = reinterpret_cast*>(newIsAlreadyInIsland + nbComponentsToAllocate); + List* newContactPairs = reinterpret_cast*>(newJoints + nbComponentsToAllocate); // If there was already components before if (mNbComponents > 0) { @@ -121,6 +122,7 @@ void RigidBodyComponents::allocate(uint32 nbComponentsToAllocate) { memcpy(newIsGravityEnabled, mIsGravityEnabled, mNbComponents * sizeof(bool)); memcpy(newIsAlreadyInIsland, mIsAlreadyInIsland, mNbComponents * sizeof(bool)); memcpy(newJoints, mJoints, mNbComponents * sizeof(List)); + memcpy(newContactPairs, mContactPairs, mNbComponents * sizeof(List)); // Deallocate previous memory mMemoryAllocator.release(mBuffer, mNbAllocatedComponents * mComponentDataSize); @@ -155,6 +157,7 @@ void RigidBodyComponents::allocate(uint32 nbComponentsToAllocate) { mIsGravityEnabled = newIsGravityEnabled; mIsAlreadyInIsland = newIsAlreadyInIsland; mJoints = newJoints; + mContactPairs = newContactPairs; } // Add a component @@ -191,6 +194,7 @@ void RigidBodyComponents::addComponent(Entity bodyEntity, bool isSleeping, const mIsGravityEnabled[index] = true; mIsAlreadyInIsland[index] = false; new (mJoints + index) List(mMemoryAllocator); + new (mContactPairs + index) List(mMemoryAllocator); // Map the entity with the new component lookup index mMapEntityToComponentIndex.add(Pair(bodyEntity, index)); @@ -235,6 +239,7 @@ void RigidBodyComponents::moveComponentToIndex(uint32 srcIndex, uint32 destIndex mIsGravityEnabled[destIndex] = mIsGravityEnabled[srcIndex]; mIsAlreadyInIsland[destIndex] = mIsAlreadyInIsland[srcIndex]; new (mJoints + destIndex) List(mJoints[srcIndex]); + new (mContactPairs + destIndex) List(mContactPairs[srcIndex]); // Destroy the source component destroyComponent(srcIndex); @@ -278,6 +283,7 @@ void RigidBodyComponents::swapComponents(uint32 index1, uint32 index2) { bool isGravityEnabled1 = mIsGravityEnabled[index1]; bool isAlreadyInIsland1 = mIsAlreadyInIsland[index1]; List joints1 = mJoints[index1]; + List contactPairs1 = mContactPairs[index1]; // Destroy component 1 destroyComponent(index1); @@ -312,6 +318,7 @@ void RigidBodyComponents::swapComponents(uint32 index1, uint32 index2) { mIsGravityEnabled[index2] = isGravityEnabled1; mIsAlreadyInIsland[index2] = isAlreadyInIsland1; new (mJoints + index2) List(joints1); + new (mContactPairs + index2) List(contactPairs1); // Update the entity to component index mapping mMapEntityToComponentIndex.add(Pair(entity1, index2)); @@ -347,4 +354,5 @@ void RigidBodyComponents::destroyComponent(uint32 index) { mCentersOfMassLocal[index].~Vector3(); mCentersOfMassWorld[index].~Vector3(); mJoints[index].~List(); + mContactPairs[index].~List(); } diff --git a/src/engine/PhysicsWorld.cpp b/src/engine/PhysicsWorld.cpp index 02465fbd..a9c73a60 100644 --- a/src/engine/PhysicsWorld.cpp +++ b/src/engine/PhysicsWorld.cpp @@ -233,9 +233,9 @@ void PhysicsWorld::setBodyDisabled(Entity bodyEntity, bool isDisabled) { mCollisionBodyComponents.setIsEntityDisabled(bodyEntity, isDisabled); mTransformComponents.setIsEntityDisabled(bodyEntity, isDisabled); - if (mRigidBodyComponents.hasComponent(bodyEntity)) { - mRigidBodyComponents.setIsEntityDisabled(bodyEntity, isDisabled); - } + assert(mRigidBodyComponents.hasComponent(bodyEntity)); + + mRigidBodyComponents.setIsEntityDisabled(bodyEntity, isDisabled); // For each collider of the body const List& collidersEntities = mCollisionBodyComponents.getColliders(bodyEntity); @@ -245,27 +245,23 @@ void PhysicsWorld::setBodyDisabled(Entity bodyEntity, bool isDisabled) { } // Disable the joints of the body if necessary - if (mRigidBodyComponents.hasComponent(bodyEntity)) { + // For each joint of the body + const List& joints = mRigidBodyComponents.getJoints(bodyEntity); + for(uint32 i=0; i < joints.size(); i++) { - // For each joint of the body - const List& joints = mRigidBodyComponents.getJoints(bodyEntity); - for(uint32 i=0; i < joints.size(); i++) { + const Entity body1Entity = mJointsComponents.getBody1Entity(joints[i]); + const Entity body2Entity = mJointsComponents.getBody2Entity(joints[i]); - const Entity body1Entity = mJointsComponents.getBody1Entity(joints[i]); - const Entity body2Entity = mJointsComponents.getBody2Entity(joints[i]); + // If both bodies of the joint are disabled + if (mRigidBodyComponents.getIsEntityDisabled(body1Entity) && mRigidBodyComponents.getIsEntityDisabled(body2Entity)) { - // If both bodies of the joint are disabled - if (mRigidBodyComponents.getIsEntityDisabled(body1Entity) && - mRigidBodyComponents.getIsEntityDisabled(body2Entity)) { + // We disable the joint + setJointDisabled(joints[i], true); + } + else { - // We disable the joint - setJointDisabled(joints[i], true); - } - else { - - // Enable the joint - setJointDisabled(joints[i], false); - } + // Enable the joint + setJointDisabled(joints[i], false); } } } @@ -760,10 +756,10 @@ void PhysicsWorld::createIslands() { mIslands.reserveMemory(); // Create a stack for the bodies to visit during the Depth First Search - Stack bodyEntityIndicesToVisit(mMemoryManager.getSingleFrameAllocator(), mIslands.getNbMaxBodiesInIslandPreviousFrame()); + Stack bodyEntitiesToVisit(mMemoryManager.getSingleFrameAllocator(), mIslands.getNbMaxBodiesInIslandPreviousFrame()); // List of static bodies added to the current island (used to reset the isAlreadyInIsland variable of static bodies) - List staticBodiesAddedToIsland(mMemoryManager.getSingleFrameAllocator()); + List staticBodiesAddedToIsland(mMemoryManager.getSingleFrameAllocator()); uint nbTotalManifolds = 0; @@ -777,83 +773,75 @@ void PhysicsWorld::createIslands() { if (mRigidBodyComponents.mBodyTypes[b] == BodyType::STATIC) continue; // Reset the stack of bodies to visit - bodyEntityIndicesToVisit.clear(); + bodyEntitiesToVisit.clear(); // Add the body into the stack of bodies to visit mRigidBodyComponents.mIsAlreadyInIsland[b] = true; - bodyEntityIndicesToVisit.push(b); + bodyEntitiesToVisit.push(mRigidBodyComponents.mBodiesEntities[b]); // Create the new island uint32 islandIndex = mIslands.addIsland(nbTotalManifolds); // While there are still some bodies to visit in the stack - while (bodyEntityIndicesToVisit.size() > 0) { - - // Get the next body to visit from the stack - const uint32 bodyToVisitIndex = bodyEntityIndicesToVisit.pop(); + while (bodyEntitiesToVisit.size() > 0) { // Get the body entity - const Entity bodyToVisitEntity = mRigidBodyComponents.mBodiesEntities[bodyToVisitIndex]; + const Entity bodyToVisitEntity = bodyEntitiesToVisit.pop(); // Add the body into the island mIslands.addBodyToIsland(bodyToVisitEntity); - RigidBody* rigidBodyToVisit = mRigidBodyComponents.mRigidBodies[bodyToVisitIndex]; + RigidBody* rigidBodyToVisit = mRigidBodyComponents.getRigidBody(bodyToVisitEntity); // Awake the body if it is sleeping (note that this called might change the body index in the mRigidBodyComponents array) rigidBodyToVisit->setIsSleeping(false); - // If the current body is static, we do not want to perform the DFS search across that body - if (rigidBodyToVisit->getType() == BodyType::STATIC) { + // Compute the body index in the array (Note that it could have changed because of the previous call to rigidBodyToVisit->setIsSleeping(false)) + const uint32 bodyToVisitIndex = mRigidBodyComponents.getEntityIndex(bodyToVisitEntity); - // Get the new body index in the mRigidBodyComponents (this index might have changed due to the call to rigidBodyToVisite->setIsSleeping(false)) - const uint32 newBodyIndex = mRigidBodyComponents.getEntityIndex(bodyToVisitEntity); + // If the currenbodyEntityIndicesToVisitt body is static, we do not want to perform the DFS search across that body + if (mRigidBodyComponents.mBodyTypes[bodyToVisitIndex] == BodyType::STATIC) { - staticBodiesAddedToIsland.add(newBodyIndex); + staticBodiesAddedToIsland.add(bodyToVisitEntity); // Go to the next body continue; } // If the body is involved in contacts with other bodies - auto itBodyContactPairs = mCollisionDetection.mMapBodyToContactPairs.find(bodyToVisitEntity); - if (itBodyContactPairs != mCollisionDetection.mMapBodyToContactPairs.end()) { + // For each contact pair in which the current body is involded + for (uint p=0; p < mRigidBodyComponents.mContactPairs[bodyToVisitIndex].size(); p++) { - // For each contact pair in which the current body is involded - List& contactPairs = itBodyContactPairs->second; - for (uint p=0; p < contactPairs.size(); p++) { + ContactPair& pair = (*mCollisionDetection.mCurrentContactPairs)[mRigidBodyComponents.mContactPairs[bodyToVisitIndex][p]]; - ContactPair& pair = (*mCollisionDetection.mCurrentContactPairs)[contactPairs[p]]; + // Check if the current contact pair has already been added into an island + if (pair.isAlreadyInIsland) continue; - // Check if the current contact pair has already been added into an island - if (pair.isAlreadyInIsland) continue; + const Entity otherBodyEntity = pair.body1Entity == bodyToVisitEntity ? pair.body2Entity : pair.body1Entity; - // If the colliding body is a RigidBody (and not a CollisionBody) and is not a trigger - if (mRigidBodyComponents.hasComponent(pair.body1Entity) && mRigidBodyComponents.hasComponent(pair.body2Entity) - && !mCollidersComponents.getIsTrigger(pair.collider1Entity) && !mCollidersComponents.getIsTrigger(pair.collider2Entity)) { + // If the colliding body is a RigidBody (and not a CollisionBody) and is not a trigger + uint32 otherBodyIndex; + if (mRigidBodyComponents.hasComponentGetIndex(otherBodyEntity, otherBodyIndex) + && !mCollidersComponents.getIsTrigger(pair.collider1Entity) && !mCollidersComponents.getIsTrigger(pair.collider2Entity)) { - assert(pair.potentialContactManifoldsIndices.size() > 0); - nbTotalManifolds += pair.potentialContactManifoldsIndices.size(); + assert(pair.potentialContactManifoldsIndices.size() > 0); + nbTotalManifolds += pair.potentialContactManifoldsIndices.size(); - // Add the contact manifold into the island - mIslands.nbContactManifolds[islandIndex] += pair.potentialContactManifoldsIndices.size(); - pair.isAlreadyInIsland = true; + // Add the contact manifold into the island + mIslands.nbContactManifolds[islandIndex] += pair.potentialContactManifoldsIndices.size(); + pair.isAlreadyInIsland = true; - const Entity otherBodyEntity = pair.body1Entity == bodyToVisitEntity ? pair.body2Entity : pair.body1Entity; - const uint32 otherBodyIndex = mRigidBodyComponents.getEntityIndex(otherBodyEntity); + // Check if the other body has already been added to the island + if (mRigidBodyComponents.mIsAlreadyInIsland[otherBodyIndex]) continue; - // Check if the other body has already been added to the island - if (mRigidBodyComponents.mIsAlreadyInIsland[otherBodyIndex]) continue; + // Insert the other body into the stack of bodies to visit + bodyEntitiesToVisit.push(otherBodyEntity); + mRigidBodyComponents.mIsAlreadyInIsland[otherBodyIndex] = true; + } + else { - // Insert the other body into the stack of bodies to visit - bodyEntityIndicesToVisit.push(otherBodyIndex); - mRigidBodyComponents.mIsAlreadyInIsland[otherBodyIndex] = true; - } - else { - - // Add the contact pair index in the list of contact pairs that won't be part of islands - pair.isAlreadyInIsland = true; - } + // Add the contact pair index in the list of contact pairs that won't be part of islands + pair.isAlreadyInIsland = true; } } @@ -877,7 +865,7 @@ void PhysicsWorld::createIslands() { if (mRigidBodyComponents.mIsAlreadyInIsland[otherBodyIndex]) continue; // Insert the other body into the stack of bodies to visit - bodyEntityIndicesToVisit.push(otherBodyIndex); + bodyEntitiesToVisit.push(otherBodyEntity); mRigidBodyComponents.mIsAlreadyInIsland[otherBodyIndex] = true; } } @@ -886,14 +874,17 @@ void PhysicsWorld::createIslands() { // can also be included in the other islands for (uint j=0; j < staticBodiesAddedToIsland.size(); j++) { - assert(mRigidBodyComponents.mBodyTypes[staticBodiesAddedToIsland[j]] == BodyType::STATIC); - mRigidBodyComponents.mIsAlreadyInIsland[staticBodiesAddedToIsland[j]] = false; + assert(mRigidBodyComponents.getBodyType(staticBodiesAddedToIsland[j]) == BodyType::STATIC); + mRigidBodyComponents.setIsAlreadyInIsland(staticBodiesAddedToIsland[j], false); } staticBodiesAddedToIsland.clear(); } - mCollisionDetection.mMapBodyToContactPairs.clear(true); + // Clear the associated contacts pairs of rigid bodies + for (uint b=0; b < mRigidBodyComponents.getNbEnabledComponents(); b++) { + mRigidBodyComponents.mContactPairs[b].clear(); + } } // Put bodies to sleep if needed. diff --git a/src/systems/CollisionDetectionSystem.cpp b/src/systems/CollisionDetectionSystem.cpp index 9c15e7c7..492b4088 100644 --- a/src/systems/CollisionDetectionSystem.cpp +++ b/src/systems/CollisionDetectionSystem.cpp @@ -49,9 +49,10 @@ using namespace reactphysics3d; using namespace std; // Constructor -CollisionDetectionSystem::CollisionDetectionSystem(PhysicsWorld* world, ColliderComponents& collidersComponents, TransformComponents& transformComponents, - CollisionBodyComponents& collisionBodyComponents, RigidBodyComponents& rigidBodyComponents, MemoryManager& memoryManager) - : mMemoryManager(memoryManager), mCollidersComponents(collidersComponents), +CollisionDetectionSystem::CollisionDetectionSystem(PhysicsWorld* world, ColliderComponents& collidersComponents, TransformComponents& transformComponents, + CollisionBodyComponents& collisionBodyComponents, RigidBodyComponents& rigidBodyComponents, + MemoryManager& memoryManager) + : mMemoryManager(memoryManager), mCollidersComponents(collidersComponents), mRigidBodyComponents(rigidBodyComponents), mCollisionDispatch(mMemoryManager.getPoolAllocator()), mWorld(world), mNoCollisionPairs(mMemoryManager.getPoolAllocator()), mOverlappingPairs(mMemoryManager.getPoolAllocator(), mMemoryManager.getSingleFrameAllocator(), mCollidersComponents, @@ -67,7 +68,7 @@ CollisionDetectionSystem::CollisionDetectionSystem(PhysicsWorld* world, Collider mContactManifolds2(mMemoryManager.getPoolAllocator()), mPreviousContactManifolds(&mContactManifolds1), mCurrentContactManifolds(&mContactManifolds2), mContactPoints1(mMemoryManager.getPoolAllocator()), mContactPoints2(mMemoryManager.getPoolAllocator()), mPreviousContactPoints(&mContactPoints1), - mCurrentContactPoints(&mContactPoints2), mMapBodyToContactPairs(mMemoryManager.getSingleFrameAllocator()) { + mCurrentContactPoints(&mContactPoints2) { #ifdef IS_RP3D_PROFILING_ENABLED @@ -481,8 +482,7 @@ void CollisionDetectionSystem::processAllPotentialContacts(NarrowPhaseInput& nar List& potentialContactPoints, Map* mapPairIdToContactPairIndex, List& potentialContactManifolds, - List* contactPairs, - Map>& mapBodyToContactPairs) { + List* contactPairs) { assert(contactPairs->size() == 0); assert(mapPairIdToContactPairIndex->size() == 0); @@ -497,17 +497,17 @@ void CollisionDetectionSystem::processAllPotentialContacts(NarrowPhaseInput& nar // Process the potential contacts processPotentialContacts(sphereVsSphereBatch, updateLastFrameInfo, potentialContactPoints, mapPairIdToContactPairIndex, - potentialContactManifolds, contactPairs, mapBodyToContactPairs); + potentialContactManifolds, contactPairs); processPotentialContacts(sphereVsCapsuleBatch, updateLastFrameInfo, potentialContactPoints, mapPairIdToContactPairIndex, - potentialContactManifolds, contactPairs, mapBodyToContactPairs); + potentialContactManifolds, contactPairs); processPotentialContacts(capsuleVsCapsuleBatch, updateLastFrameInfo, potentialContactPoints, mapPairIdToContactPairIndex, - potentialContactManifolds, contactPairs, mapBodyToContactPairs); + potentialContactManifolds, contactPairs); processPotentialContacts(sphereVsConvexPolyhedronBatch, updateLastFrameInfo, potentialContactPoints, mapPairIdToContactPairIndex, - potentialContactManifolds, contactPairs, mapBodyToContactPairs); + potentialContactManifolds, contactPairs); processPotentialContacts(capsuleVsConvexPolyhedronBatch, updateLastFrameInfo, potentialContactPoints, mapPairIdToContactPairIndex, - potentialContactManifolds, contactPairs, mapBodyToContactPairs); + potentialContactManifolds, contactPairs); processPotentialContacts(convexPolyhedronVsConvexPolyhedronBatch, updateLastFrameInfo, potentialContactPoints, mapPairIdToContactPairIndex, - potentialContactManifolds, contactPairs, mapBodyToContactPairs); + potentialContactManifolds, contactPairs); } // Compute the narrow-phase collision detection @@ -528,7 +528,7 @@ void CollisionDetectionSystem::computeNarrowPhase() { // Process all the potential contacts after narrow-phase collision processAllPotentialContacts(mNarrowPhaseInput, true, mPotentialContactPoints, mCurrentMapPairIdToContactPairIndex, - mPotentialContactManifolds, mCurrentContactPairs, mMapBodyToContactPairs); + mPotentialContactManifolds, mCurrentContactPairs); // Reduce the number of contact points in the manifolds reducePotentialContactManifolds(mCurrentContactPairs, mPotentialContactManifolds, mPotentialContactPoints); @@ -662,18 +662,15 @@ bool CollisionDetectionSystem::computeNarrowPhaseCollisionSnapshot(NarrowPhaseIn List lostContactPairs(allocator); // Not used during collision snapshots List contactManifolds(allocator); List contactPoints(allocator); - Map> mapBodyToContactPairs(allocator); // Process all the potential contacts after narrow-phase collision - processAllPotentialContacts(narrowPhaseInput, true, potentialContactPoints, &mapPairIdToContactPairIndex, potentialContactManifolds, - &contactPairs, mapBodyToContactPairs); + processAllPotentialContacts(narrowPhaseInput, true, potentialContactPoints, &mapPairIdToContactPairIndex, potentialContactManifolds, &contactPairs); // Reduce the number of contact points in the manifolds reducePotentialContactManifolds(&contactPairs, potentialContactManifolds, potentialContactPoints); // Create the actual contact manifolds and contact points - createSnapshotContacts(contactPairs, contactManifolds, contactPoints, potentialContactManifolds, - potentialContactPoints); + createSnapshotContacts(contactPairs, contactManifolds, contactPoints, potentialContactManifolds, potentialContactPoints); // Report the contacts to the user reportContacts(callback, &contactPairs, &contactManifolds, &contactPoints, lostContactPairs); @@ -728,6 +725,14 @@ void CollisionDetectionSystem::createContacts() { contactPair.nbContactManifolds = contactPair.potentialContactManifoldsIndices.size(); contactPair.contactPointsIndex = mCurrentContactPoints->size(); + // Add the associated contact pair to both bodies of the pair (used to create islands later) + if (mRigidBodyComponents.hasComponent(contactPair.body1Entity)) { + mRigidBodyComponents.addContacPair(contactPair.body1Entity, p); + } + if (mRigidBodyComponents.hasComponent(contactPair.body2Entity)) { + mRigidBodyComponents.addContacPair(contactPair.body2Entity, p); + } + // For each potential contact manifold of the pair for (uint m=0; m < contactPair.potentialContactManifoldsIndices.size(); m++) { @@ -985,8 +990,7 @@ void CollisionDetectionSystem::processPotentialContacts(NarrowPhaseInfoBatch& na List& potentialContactPoints, Map* mapPairIdToContactPairIndex, List& potentialContactManifolds, - List* contactPairs, - Map>& mapBodyToContactPairs) { + List* contactPairs) { RP3D_PROFILE("CollisionDetectionSystem::processPotentialContacts()", mProfiler); @@ -1035,25 +1039,6 @@ void CollisionDetectionSystem::processPotentialContacts(NarrowPhaseInfoBatch& na contactPairs->add(overlappingPairContact); pairContact = &((*contactPairs)[newContactPairIndex]); mapPairIdToContactPairIndex->add(Pair(pairId, newContactPairIndex)); - - auto itbodyContactPairs = mapBodyToContactPairs.find(body1Entity); - if (itbodyContactPairs != mapBodyToContactPairs.end()) { - itbodyContactPairs->second.add(newContactPairIndex); - } - else { - List contactPairs(mMemoryManager.getPoolAllocator(), 1); - contactPairs.add(newContactPairIndex); - mapBodyToContactPairs.add(Pair>(body1Entity, contactPairs)); - } - itbodyContactPairs = mapBodyToContactPairs.find(body2Entity); - if (itbodyContactPairs != mapBodyToContactPairs.end()) { - itbodyContactPairs->second.add(newContactPairIndex); - } - else { - List contactPairs(mMemoryManager.getPoolAllocator(), 1); - contactPairs.add(newContactPairIndex); - mapBodyToContactPairs.add(Pair>(body2Entity, contactPairs)); - } } else { // If a ContactPair already exists for this overlapping pair, we use this one From 0032fef4732b52dde3d759cd2bd3e1d2a39381db Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Sat, 18 Jul 2020 17:03:44 +0200 Subject: [PATCH 06/74] Working on optimizations in contacts creation --- .../collision/ContactManifold.h | 15 +- .../collision/ContactManifoldInfo.h | 9 +- .../reactphysics3d/collision/ContactPair.h | 17 +- .../collision/ContactPointInfo.h | 3 - include/reactphysics3d/configuration.h | 9 + include/reactphysics3d/containers/List.h | 15 + include/reactphysics3d/engine/PhysicsWorld.h | 5 - .../systems/CollisionDetectionSystem.h | 23 +- src/collision/ContactManifold.cpp | 7 +- src/engine/PhysicsWorld.cpp | 6 +- src/systems/CollisionDetectionSystem.cpp | 333 +++++++++++------- 11 files changed, 258 insertions(+), 184 deletions(-) diff --git a/include/reactphysics3d/collision/ContactManifold.h b/include/reactphysics3d/collision/ContactManifold.h index 3e4c546a..3dfeb0df 100644 --- a/include/reactphysics3d/collision/ContactManifold.h +++ b/include/reactphysics3d/collision/ContactManifold.h @@ -58,7 +58,7 @@ class ContactManifold { // -------------------- Constants -------------------- // /// Maximum number of contact points in a reduced contact manifold - const int MAX_CONTACT_POINTS_IN_MANIFOLD = 4; + static constexpr int MAX_CONTACT_POINTS_IN_MANIFOLD = 4; // -------------------- Attributes -------------------- // @@ -78,7 +78,7 @@ class ContactManifold { Entity colliderEntity2; /// Number of contacts in the cache - int8 nbContactPoints; + uint8 nbContactPoints; /// First friction vector of the contact manifold Vector3 frictionVector1; @@ -107,16 +107,7 @@ class ContactManifold { /// Constructor ContactManifold(Entity bodyEntity1, Entity bodyEntity2, Entity colliderEntity1, Entity colliderEntity2, - uint contactPointsIndex, int8 nbContactPoints); - - /// Destructor - ~ContactManifold(); - - /// Copy-constructor - ContactManifold(const ContactManifold& contactManifold) = default; - - /// Assignment operator - ContactManifold& operator=(const ContactManifold& contactManifold) = default; + uint contactPointsIndex, uint8 nbContactPoints); // -------------------- Friendship -------------------- // diff --git a/include/reactphysics3d/collision/ContactManifoldInfo.h b/include/reactphysics3d/collision/ContactManifoldInfo.h index 6f6a0e74..b6dd9219 100644 --- a/include/reactphysics3d/collision/ContactManifoldInfo.h +++ b/include/reactphysics3d/collision/ContactManifoldInfo.h @@ -45,8 +45,12 @@ struct ContactManifoldInfo { // -------------------- Attributes -------------------- // + /// Number of potential contact points + uint8 nbPotentialContactPoints; + /// Indices of the contact points in the mPotentialContactPoints array - List potentialContactPointsIndices; + uint potentialContactPointsIndices[NB_MAX_CONTACT_POINTS_IN_POTENTIAL_MANIFOLD]; + /// Overlapping pair id uint64 pairId; @@ -54,8 +58,7 @@ struct ContactManifoldInfo { // -------------------- Methods -------------------- // /// Constructor - ContactManifoldInfo(uint64 pairId, MemoryAllocator& allocator) - : potentialContactPointsIndices(allocator, 4), pairId(pairId) { + ContactManifoldInfo(uint64 pairId) : nbPotentialContactPoints(0), pairId(pairId) { } diff --git a/include/reactphysics3d/collision/ContactPair.h b/include/reactphysics3d/collision/ContactPair.h index f9a49817..abd648fd 100644 --- a/include/reactphysics3d/collision/ContactPair.h +++ b/include/reactphysics3d/collision/ContactPair.h @@ -47,8 +47,11 @@ struct ContactPair { /// Overlapping pair Id uint64 pairId; + /// Number of potential contact manifolds + uint8 nbPotentialContactManifolds; + /// Indices of the potential contact manifolds - List potentialContactManifoldsIndices; + uint potentialContactManifoldsIndices[NB_MAX_POTENTIAL_CONTACT_MANIFOLDS]; /// Entity of the first body of the contact Entity body1Entity; @@ -90,13 +93,21 @@ struct ContactPair { /// Constructor ContactPair(uint64 pairId, Entity body1Entity, Entity body2Entity, Entity collider1Entity, - Entity collider2Entity, uint contactPairIndex, bool collidingInPreviousFrame, bool isTrigger, MemoryAllocator& allocator) - : pairId(pairId), potentialContactManifoldsIndices(allocator, 8), body1Entity(body1Entity), body2Entity(body2Entity), + Entity collider2Entity, uint contactPairIndex, bool collidingInPreviousFrame, bool isTrigger) + : pairId(pairId), nbPotentialContactManifolds(0), body1Entity(body1Entity), body2Entity(body2Entity), collider1Entity(collider1Entity), collider2Entity(collider2Entity), isAlreadyInIsland(false), contactPairIndex(contactPairIndex), contactManifoldsIndex(0), nbContactManifolds(0), contactPointsIndex(0), nbToTalContactPoints(0), collidingInPreviousFrame(collidingInPreviousFrame), isTrigger(isTrigger) { } + + // Remove a potential manifold at a given index in the array + void removePotentialManifoldAtIndex(uint index) { + assert(index < nbPotentialContactManifolds); + + potentialContactManifoldsIndices[index] = potentialContactManifoldsIndices[nbPotentialContactManifolds - 1]; + nbPotentialContactManifolds--; + } }; } diff --git a/include/reactphysics3d/collision/ContactPointInfo.h b/include/reactphysics3d/collision/ContactPointInfo.h index cb44673d..387f2363 100644 --- a/include/reactphysics3d/collision/ContactPointInfo.h +++ b/include/reactphysics3d/collision/ContactPointInfo.h @@ -76,9 +76,6 @@ struct ContactPointInfo { assert(contactNormal.lengthSquare() > decimal(0.8)); assert(penDepth > decimal(0.0)); } - - /// Destructor - ~ContactPointInfo() = default; }; } diff --git a/include/reactphysics3d/configuration.h b/include/reactphysics3d/configuration.h index 6283a488..0fbd2b6a 100644 --- a/include/reactphysics3d/configuration.h +++ b/include/reactphysics3d/configuration.h @@ -103,6 +103,15 @@ constexpr decimal PI_TIMES_2 = decimal(6.28318530); /// without triggering a large modification of the tree each frame which can be costly constexpr decimal DYNAMIC_TREE_FAT_AABB_INFLATE_PERCENTAGE = decimal(0.08); +/// Maximum number of contact manifolds in an overlapping pair +constexpr uint8 NB_MAX_CONTACT_MANIFOLDS = 3; + +/// Maximum number of potential contact manifolds in an overlapping pair +constexpr uint8 NB_MAX_POTENTIAL_CONTACT_MANIFOLDS = 4 * NB_MAX_CONTACT_MANIFOLDS; + +/// Maximum number of contact points in potential contact manifold +constexpr uint8 NB_MAX_CONTACT_POINTS_IN_POTENTIAL_MANIFOLD = 12; + /// Current version of ReactPhysics3D const std::string RP3D_VERSION = std::string("0.8.0"); diff --git a/include/reactphysics3d/containers/List.h b/include/reactphysics3d/containers/List.h index a85fb65a..140f0b56 100755 --- a/include/reactphysics3d/containers/List.h +++ b/include/reactphysics3d/containers/List.h @@ -294,6 +294,21 @@ class List { mSize++; } + /// Add an element into the list by constructing it directly into the list (in order to avoid a copy) + template + void emplace(Ts&&... args) { + + // If we need to allocate more memory + if (mSize == mCapacity) { + reserve(mCapacity == 0 ? 1 : mCapacity * 2); + } + + // Construct the element directly at its location in the array + new (static_cast(mBuffer) + mSize * sizeof(T)) T(std::forward(args)...); + + mSize++; + } + /// Add a given numbers of elements at the end of the list but do not init them void addWithoutInit(uint nbElements) { diff --git a/include/reactphysics3d/engine/PhysicsWorld.h b/include/reactphysics3d/engine/PhysicsWorld.h index 0d564bd7..c4c841ef 100644 --- a/include/reactphysics3d/engine/PhysicsWorld.h +++ b/include/reactphysics3d/engine/PhysicsWorld.h @@ -116,9 +116,6 @@ class PhysicsWorld { /// might enter sleeping mode decimal defaultSleepAngularVelocity; - /// Maximum number of contact manifolds in an overlapping pair - uint nbMaxContactManifolds; - /// This is used to test if two contact manifold are similar (same contact normal) in order to /// merge them. If the cosine of the angle between the normals of the two manifold are larger /// than the value bellow, the manifold are considered to be similar. @@ -139,7 +136,6 @@ class PhysicsWorld { defaultTimeBeforeSleep = 1.0f; defaultSleepLinearVelocity = decimal(0.02); defaultSleepAngularVelocity = decimal(3.0) * (PI_RP3D / decimal(180.0)); - nbMaxContactManifolds = 3; cosAngleSimilarContactManifold = decimal(0.95); } @@ -163,7 +159,6 @@ class PhysicsWorld { ss << "defaultTimeBeforeSleep=" << defaultTimeBeforeSleep << std::endl; ss << "defaultSleepLinearVelocity=" << defaultSleepLinearVelocity << std::endl; ss << "defaultSleepAngularVelocity=" << defaultSleepAngularVelocity << std::endl; - ss << "nbMaxContactManifolds=" << nbMaxContactManifolds << std::endl; ss << "cosAngleSimilarContactManifold=" << cosAngleSimilarContactManifold << std::endl; return ss.str(); diff --git a/include/reactphysics3d/systems/CollisionDetectionSystem.h b/include/reactphysics3d/systems/CollisionDetectionSystem.h index 22bda63e..6ae515f6 100644 --- a/include/reactphysics3d/systems/CollisionDetectionSystem.h +++ b/include/reactphysics3d/systems/CollisionDetectionSystem.h @@ -128,19 +128,9 @@ class CollisionDetectionSystem { /// List of lost contact pairs (contact pairs in contact in previous frame but not in the current one) List mLostContactPairs; - /// First map of overlapping pair id to the index of the corresponding pair contact - Map mMapPairIdToContactPairIndex1; - - /// Second map of overlapping pair id to the index of the corresponding pair contact - Map mMapPairIdToContactPairIndex2; - /// Pointer to the map of overlappingPairId to the index of contact pair of the previous frame /// (either mMapPairIdToContactPairIndex1 or mMapPairIdToContactPairIndex2) - Map* mPreviousMapPairIdToContactPairIndex; - - /// Pointer to the map of overlappingPairId to the index of contact pair of the current frame - /// (either mMapPairIdToContactPairIndex1 or mMapPairIdToContactPairIndex2) - Map* mCurrentMapPairIdToContactPairIndex; + Map mPreviousMapPairIdToContactPairIndex; /// First list with the contact manifolds List mContactManifolds1; @@ -223,12 +213,11 @@ class CollisionDetectionSystem { /// Convert the potential contact into actual contacts void processPotentialContacts(NarrowPhaseInfoBatch& narrowPhaseInfoBatch, bool updateLastFrameInfo, List& potentialContactPoints, - Map* mapPairIdToContactPairIndex, - List& potentialContactManifolds, List* contactPairs); + List& potentialContactManifolds, + Map& mapPairIdToContactPairIndex, List* contactPairs); /// Process the potential contacts after narrow-phase collision detection void processAllPotentialContacts(NarrowPhaseInput& narrowPhaseInput, bool updateLastFrameInfo, List& potentialContactPoints, - Map* mapPairIdToContactPairIndex, List& potentialContactManifolds, List* contactPairs); /// Reduce the potential contact manifolds and contact points of the overlapping pair contacts @@ -238,6 +227,9 @@ class CollisionDetectionSystem { /// Create the actual contact manifolds and contacts points (from potential contacts) for a given contact pair void createContacts(); + /// Compute the map from contact pairs ids to contact pair for the next frame + void computeMapPreviousContactPairs(); + /// Compute the lost contact pairs (contact pairs in contact in the previous frame but not in the current one) void computeLostContactPairs(); @@ -277,6 +269,9 @@ class CollisionDetectionSystem { /// Filter the overlapping pairs to keep only the pairs where two given bodies are involved void filterOverlappingPairs(Entity body1Entity, Entity body2Entity, List& convexPairs, List& concavePairs) const; + /// Remove an element in an array (and replace it by the last one in the array) + void removeItemAtInArray(uint array[], uint8 index, uint8& arraySize) const; + public : // -------------------- Methods -------------------- // diff --git a/src/collision/ContactManifold.cpp b/src/collision/ContactManifold.cpp index 10c5c957..46f886de 100644 --- a/src/collision/ContactManifold.cpp +++ b/src/collision/ContactManifold.cpp @@ -32,14 +32,9 @@ using namespace reactphysics3d; // Constructor ContactManifold::ContactManifold(Entity bodyEntity1, Entity bodyEntity2, Entity colliderEntity1, Entity colliderEntity2, - uint contactPointsIndex, int8 nbContactPoints) + uint contactPointsIndex, uint8 nbContactPoints) :contactPointsIndex(contactPointsIndex), bodyEntity1(bodyEntity1), bodyEntity2(bodyEntity2), colliderEntity1(colliderEntity1), colliderEntity2(colliderEntity2), nbContactPoints(nbContactPoints), frictionImpulse1(0), frictionImpulse2(0), frictionTwistImpulse(0), isAlreadyInIsland(false) { } - -// Destructor -ContactManifold::~ContactManifold() { - -} diff --git a/src/engine/PhysicsWorld.cpp b/src/engine/PhysicsWorld.cpp index a9c73a60..0ef91ca8 100644 --- a/src/engine/PhysicsWorld.cpp +++ b/src/engine/PhysicsWorld.cpp @@ -824,11 +824,11 @@ void PhysicsWorld::createIslands() { if (mRigidBodyComponents.hasComponentGetIndex(otherBodyEntity, otherBodyIndex) && !mCollidersComponents.getIsTrigger(pair.collider1Entity) && !mCollidersComponents.getIsTrigger(pair.collider2Entity)) { - assert(pair.potentialContactManifoldsIndices.size() > 0); - nbTotalManifolds += pair.potentialContactManifoldsIndices.size(); + assert(pair.nbPotentialContactManifolds > 0); + nbTotalManifolds += pair.nbPotentialContactManifolds; // Add the contact manifold into the island - mIslands.nbContactManifolds[islandIndex] += pair.potentialContactManifoldsIndices.size(); + mIslands.nbContactManifolds[islandIndex] += pair.nbPotentialContactManifolds; pair.isAlreadyInIsland = true; // Check if the other body has already been added to the island diff --git a/src/systems/CollisionDetectionSystem.cpp b/src/systems/CollisionDetectionSystem.cpp index 492b4088..e11f7e86 100644 --- a/src/systems/CollisionDetectionSystem.cpp +++ b/src/systems/CollisionDetectionSystem.cpp @@ -62,13 +62,11 @@ CollisionDetectionSystem::CollisionDetectionSystem(PhysicsWorld* world, Collider mNarrowPhaseInput(mMemoryManager.getSingleFrameAllocator(), mOverlappingPairs), mPotentialContactPoints(mMemoryManager.getSingleFrameAllocator()), mPotentialContactManifolds(mMemoryManager.getSingleFrameAllocator()), mContactPairs1(mMemoryManager.getPoolAllocator()), mContactPairs2(mMemoryManager.getPoolAllocator()), mPreviousContactPairs(&mContactPairs1), mCurrentContactPairs(&mContactPairs2), - mLostContactPairs(mMemoryManager.getSingleFrameAllocator()), mMapPairIdToContactPairIndex1(mMemoryManager.getPoolAllocator()), - mMapPairIdToContactPairIndex2(mMemoryManager.getPoolAllocator()), mPreviousMapPairIdToContactPairIndex(&mMapPairIdToContactPairIndex1), - mCurrentMapPairIdToContactPairIndex(&mMapPairIdToContactPairIndex2), mContactManifolds1(mMemoryManager.getPoolAllocator()), - mContactManifolds2(mMemoryManager.getPoolAllocator()), mPreviousContactManifolds(&mContactManifolds1), mCurrentContactManifolds(&mContactManifolds2), - mContactPoints1(mMemoryManager.getPoolAllocator()), - mContactPoints2(mMemoryManager.getPoolAllocator()), mPreviousContactPoints(&mContactPoints1), - mCurrentContactPoints(&mContactPoints2) { + mLostContactPairs(mMemoryManager.getSingleFrameAllocator()), mPreviousMapPairIdToContactPairIndex(mMemoryManager.getHeapAllocator()), + mContactManifolds1(mMemoryManager.getPoolAllocator()), mContactManifolds2(mMemoryManager.getPoolAllocator()), + mPreviousContactManifolds(&mContactManifolds1), mCurrentContactManifolds(&mContactManifolds2), + mContactPoints1(mMemoryManager.getPoolAllocator()), mContactPoints2(mMemoryManager.getPoolAllocator()), + mPreviousContactPoints(&mContactPoints1), mCurrentContactPoints(&mContactPoints2) { #ifdef IS_RP3D_PROFILING_ENABLED @@ -159,7 +157,7 @@ void CollisionDetectionSystem::addLostContactPair(uint64 overlappingPairIndex) { // Create a lost contact pair ContactPair lostContactPair(mOverlappingPairs.mPairIds[overlappingPairIndex], body1Entity, body2Entity, collider1Entity, collider2Entity, mLostContactPairs.size(), - true, isTrigger, mMemoryManager.getHeapAllocator()); + true, isTrigger); mLostContactPairs.add(lostContactPair); } @@ -480,13 +478,14 @@ bool CollisionDetectionSystem::testNarrowPhaseCollision(NarrowPhaseInput& narrow // Process the potential contacts after narrow-phase collision detection void CollisionDetectionSystem::processAllPotentialContacts(NarrowPhaseInput& narrowPhaseInput, bool updateLastFrameInfo, List& potentialContactPoints, - Map* mapPairIdToContactPairIndex, List& potentialContactManifolds, List* contactPairs) { assert(contactPairs->size() == 0); assert(mapPairIdToContactPairIndex->size() == 0); + Map mapPairIdToContactPairIndex(mMemoryManager.getHeapAllocator(), mPreviousMapPairIdToContactPairIndex.size()); + // get the narrow-phase batches to test for collision NarrowPhaseInfoBatch& sphereVsSphereBatch = narrowPhaseInput.getSphereVsSphereBatch(); NarrowPhaseInfoBatch& sphereVsCapsuleBatch = narrowPhaseInput.getSphereVsCapsuleBatch(); @@ -496,18 +495,13 @@ void CollisionDetectionSystem::processAllPotentialContacts(NarrowPhaseInput& nar NarrowPhaseInfoBatch& convexPolyhedronVsConvexPolyhedronBatch = narrowPhaseInput.getConvexPolyhedronVsConvexPolyhedronBatch(); // Process the potential contacts - processPotentialContacts(sphereVsSphereBatch, updateLastFrameInfo, potentialContactPoints, mapPairIdToContactPairIndex, - potentialContactManifolds, contactPairs); - processPotentialContacts(sphereVsCapsuleBatch, updateLastFrameInfo, potentialContactPoints, mapPairIdToContactPairIndex, - potentialContactManifolds, contactPairs); - processPotentialContacts(capsuleVsCapsuleBatch, updateLastFrameInfo, potentialContactPoints, mapPairIdToContactPairIndex, - potentialContactManifolds, contactPairs); - processPotentialContacts(sphereVsConvexPolyhedronBatch, updateLastFrameInfo, potentialContactPoints, mapPairIdToContactPairIndex, - potentialContactManifolds, contactPairs); - processPotentialContacts(capsuleVsConvexPolyhedronBatch, updateLastFrameInfo, potentialContactPoints, mapPairIdToContactPairIndex, - potentialContactManifolds, contactPairs); - processPotentialContacts(convexPolyhedronVsConvexPolyhedronBatch, updateLastFrameInfo, potentialContactPoints, mapPairIdToContactPairIndex, - potentialContactManifolds, contactPairs); + processPotentialContacts(sphereVsSphereBatch, updateLastFrameInfo, potentialContactPoints, potentialContactManifolds, mapPairIdToContactPairIndex, contactPairs); + processPotentialContacts(sphereVsCapsuleBatch, updateLastFrameInfo, potentialContactPoints, potentialContactManifolds, mapPairIdToContactPairIndex, contactPairs); + processPotentialContacts(capsuleVsCapsuleBatch, updateLastFrameInfo, potentialContactPoints, potentialContactManifolds, mapPairIdToContactPairIndex, contactPairs); + processPotentialContacts(sphereVsConvexPolyhedronBatch, updateLastFrameInfo, potentialContactPoints, potentialContactManifolds, mapPairIdToContactPairIndex, contactPairs); + processPotentialContacts(capsuleVsConvexPolyhedronBatch, updateLastFrameInfo, potentialContactPoints, potentialContactManifolds, mapPairIdToContactPairIndex, contactPairs); + processPotentialContacts(convexPolyhedronVsConvexPolyhedronBatch, updateLastFrameInfo, potentialContactPoints, + potentialContactManifolds, mapPairIdToContactPairIndex, contactPairs); } // Compute the narrow-phase collision detection @@ -527,7 +521,7 @@ void CollisionDetectionSystem::computeNarrowPhase() { testNarrowPhaseCollision(mNarrowPhaseInput, true, allocator); // Process all the potential contacts after narrow-phase collision - processAllPotentialContacts(mNarrowPhaseInput, true, mPotentialContactPoints, mCurrentMapPairIdToContactPairIndex, + processAllPotentialContacts(mNarrowPhaseInput, true, mPotentialContactPoints, mPotentialContactManifolds, mCurrentContactPairs); // Reduce the number of contact points in the manifolds @@ -539,9 +533,21 @@ void CollisionDetectionSystem::computeNarrowPhase() { // Create the actual narrow-phase contacts createContacts(); + // Compute the map from contact pairs ids to contact pair for the next frame + computeMapPreviousContactPairs(); + mNarrowPhaseInput.clear(); } +/// Compute the map from contact pairs ids to contact pair for the next frame +void CollisionDetectionSystem::computeMapPreviousContactPairs() { + + mPreviousMapPairIdToContactPairIndex.clear(); + for (uint i=0; i < mCurrentContactPairs->size(); i++) { + mPreviousMapPairIdToContactPairIndex.add(Pair((*mCurrentContactPairs)[i].pairId, i)); + } +} + // Compute the narrow-phase collision detection for the testOverlap() methods. /// This method returns true if contacts are found. bool CollisionDetectionSystem::computeNarrowPhaseOverlapSnapshot(NarrowPhaseInput& narrowPhaseInput, OverlapCallback* callback) { @@ -629,8 +635,7 @@ void CollisionDetectionSystem::computeOverlapSnapshotContactPairs(NarrowPhaseInf const bool isTrigger = mCollidersComponents.mIsTrigger[collider1Index] || mCollidersComponents.mIsTrigger[collider2Index]; // Create a new contact pair - ContactPair contactPair(narrowPhaseInfoBatch.overlappingPairIds[i], body1Entity, body2Entity, collider1Entity, collider2Entity, - contactPairs.size(), isTrigger, false, mMemoryManager.getHeapAllocator()); + ContactPair contactPair(narrowPhaseInfoBatch.overlappingPairIds[i], body1Entity, body2Entity, collider1Entity, collider2Entity, contactPairs.size(), isTrigger, false); contactPairs.add(contactPair); setOverlapContactPairId.add(narrowPhaseInfoBatch.overlappingPairIds[i]); @@ -657,14 +662,13 @@ bool CollisionDetectionSystem::computeNarrowPhaseCollisionSnapshot(NarrowPhaseIn List potentialContactPoints(allocator); List potentialContactManifolds(allocator); - Map mapPairIdToContactPairIndex(allocator); List contactPairs(allocator); List lostContactPairs(allocator); // Not used during collision snapshots List contactManifolds(allocator); List contactPoints(allocator); // Process all the potential contacts after narrow-phase collision - processAllPotentialContacts(narrowPhaseInput, true, potentialContactPoints, &mapPairIdToContactPairIndex, potentialContactManifolds, &contactPairs); + processAllPotentialContacts(narrowPhaseInput, true, potentialContactPoints, potentialContactManifolds, &contactPairs); // Reduce the number of contact points in the manifolds reducePotentialContactManifolds(&contactPairs, potentialContactManifolds, potentialContactPoints); @@ -687,24 +691,20 @@ void CollisionDetectionSystem::swapPreviousAndCurrentContacts() { mPreviousContactPairs = &mContactPairs2; mPreviousContactManifolds = &mContactManifolds2; mPreviousContactPoints = &mContactPoints2; - mPreviousMapPairIdToContactPairIndex = &mMapPairIdToContactPairIndex2; mCurrentContactPairs = &mContactPairs1; mCurrentContactManifolds = &mContactManifolds1; mCurrentContactPoints = &mContactPoints1; - mCurrentMapPairIdToContactPairIndex = &mMapPairIdToContactPairIndex1; } else { mPreviousContactPairs = &mContactPairs1; mPreviousContactManifolds = &mContactManifolds1; mPreviousContactPoints = &mContactPoints1; - mPreviousMapPairIdToContactPairIndex = &mMapPairIdToContactPairIndex1; mCurrentContactPairs = &mContactPairs2; mCurrentContactManifolds = &mContactManifolds2; mCurrentContactPoints = &mContactPoints2; - mCurrentMapPairIdToContactPairIndex = &mMapPairIdToContactPairIndex2; } } @@ -722,7 +722,7 @@ void CollisionDetectionSystem::createContacts() { ContactPair& contactPair = (*mCurrentContactPairs)[p]; contactPair.contactManifoldsIndex = mCurrentContactManifolds->size(); - contactPair.nbContactManifolds = contactPair.potentialContactManifoldsIndices.size(); + contactPair.nbContactManifolds = contactPair.nbPotentialContactManifolds; contactPair.contactPointsIndex = mCurrentContactPoints->size(); // Add the associated contact pair to both bodies of the pair (used to create islands later) @@ -734,13 +734,13 @@ void CollisionDetectionSystem::createContacts() { } // For each potential contact manifold of the pair - for (uint m=0; m < contactPair.potentialContactManifoldsIndices.size(); m++) { + for (uint m=0; m < contactPair.nbPotentialContactManifolds; m++) { ContactManifoldInfo& potentialManifold = mPotentialContactManifolds[contactPair.potentialContactManifoldsIndices[m]]; // Start index and number of contact points for this manifold const uint contactPointsIndex = mCurrentContactPoints->size(); - const int8 nbContactPoints = static_cast(potentialManifold.potentialContactPointsIndices.size()); + const int8 nbContactPoints = static_cast(potentialManifold.nbPotentialContactPoints); contactPair.nbToTalContactPoints += nbContactPoints; // We create a new contact manifold @@ -750,10 +750,10 @@ void CollisionDetectionSystem::createContacts() { // Add the contact manifold mCurrentContactManifolds->add(contactManifold); - assert(potentialManifold.potentialContactPointsIndices.size() > 0); + assert(potentialManifold.nbPotentialContactPoints > 0); // For each contact point of the manifold - for (uint c=0; c < potentialManifold.potentialContactPointsIndices.size(); c++) { + for (uint c=0; c < potentialManifold.nbPotentialContactPoints; c++) { ContactPointInfo& potentialContactPoint = mPotentialContactPoints[potentialManifold.potentialContactPointsIndices[c]]; @@ -775,7 +775,6 @@ void CollisionDetectionSystem::createContacts() { mPreviousContactPoints->clear(); mPreviousContactManifolds->clear(); mPreviousContactPairs->clear(); - mPreviousMapPairIdToContactPairIndex->clear(); // Reset the potential contacts mPotentialContactPoints.clear(true); @@ -817,20 +816,20 @@ void CollisionDetectionSystem::createSnapshotContacts(List& contact for (uint p=0; p < contactPairs.size(); p++) { ContactPair& contactPair = contactPairs[p]; - assert(contactPair.potentialContactManifoldsIndices.size() > 0); + assert(contactPair.nbPotentialContactManifolds > 0); contactPair.contactManifoldsIndex = contactManifolds.size(); - contactPair.nbContactManifolds = contactPair.potentialContactManifoldsIndices.size(); + contactPair.nbContactManifolds = contactPair.nbPotentialContactManifolds; contactPair.contactPointsIndex = contactPoints.size(); // For each potential contact manifold of the pair - for (uint m=0; m < contactPair.potentialContactManifoldsIndices.size(); m++) { + for (uint m=0; m < contactPair.nbPotentialContactManifolds; m++) { ContactManifoldInfo& potentialManifold = potentialContactManifolds[contactPair.potentialContactManifoldsIndices[m]]; // Start index and number of contact points for this manifold const uint contactPointsIndex = contactPoints.size(); - const int8 nbContactPoints = static_cast(potentialManifold.potentialContactPointsIndices.size()); + const uint8 nbContactPoints = potentialManifold.nbPotentialContactPoints; contactPair.nbToTalContactPoints += nbContactPoints; // We create a new contact manifold @@ -840,10 +839,10 @@ void CollisionDetectionSystem::createSnapshotContacts(List& contact // Add the contact manifold contactManifolds.add(contactManifold); - assert(potentialManifold.potentialContactPointsIndices.size() > 0); + assert(potentialManifold.nbPotentialContactPoints > 0); // For each contact point of the manifold - for (uint c=0; c < potentialManifold.potentialContactPointsIndices.size(); c++) { + for (uint c=0; c < potentialManifold.nbPotentialContactPoints; c++) { ContactPointInfo& potentialContactPoint = potentialContactPoints[potentialManifold.potentialContactPointsIndices[c]]; @@ -866,10 +865,10 @@ void CollisionDetectionSystem::initContactsWithPreviousOnes() { ContactPair& currentContactPair = (*mCurrentContactPairs)[i]; // Find the corresponding contact pair in the previous frame (if any) - auto itPrevContactPair = mPreviousMapPairIdToContactPairIndex->find(currentContactPair.pairId); + auto itPrevContactPair = mPreviousMapPairIdToContactPairIndex.find(currentContactPair.pairId); // If we have found a corresponding contact pair in the previous frame - if (itPrevContactPair != mPreviousMapPairIdToContactPairIndex->end()) { + if (itPrevContactPair != mPreviousMapPairIdToContactPairIndex.end()) { const uint previousContactPairIndex = itPrevContactPair->second; ContactPair& previousContactPair = (*mPreviousContactPairs)[previousContactPairIndex]; @@ -988,8 +987,8 @@ void CollisionDetectionSystem::raycast(RaycastCallback* raycastCallback, // Convert the potential contact into actual contacts void CollisionDetectionSystem::processPotentialContacts(NarrowPhaseInfoBatch& narrowPhaseInfoBatch, bool updateLastFrameInfo, List& potentialContactPoints, - Map* mapPairIdToContactPairIndex, List& potentialContactManifolds, + Map& mapPairIdToContactPairIndex, List* contactPairs) { RP3D_PROFILE("CollisionDetectionSystem::processPotentialContacts()", mProfiler); @@ -1012,100 +1011,151 @@ void CollisionDetectionSystem::processPotentialContacts(NarrowPhaseInfoBatch& na mOverlappingPairs.mCollidingInCurrentFrame[pairIndex] = true; - // If there is not already a contact pair for this overlapping pair - auto it = mapPairIdToContactPairIndex->find(pairId); - ContactPair* pairContact = nullptr; - if (it == mapPairIdToContactPairIndex->end()) { + const Entity collider1Entity = narrowPhaseInfoBatch.colliderEntities1[i]; + const Entity collider2Entity = narrowPhaseInfoBatch.colliderEntities2[i]; + + const uint32 collider1Index = mCollidersComponents.getEntityIndex(collider1Entity); + const uint32 collider2Index = mCollidersComponents.getEntityIndex(collider2Entity); + + const Entity body1Entity = mCollidersComponents.mBodiesEntities[collider1Index]; + const Entity body2Entity = mCollidersComponents.mBodiesEntities[collider2Index]; + + // If we have a convex vs convex collision (if we consider the base collision shapes of the colliders) + if (mCollidersComponents.mCollisionShapes[collider1Index]->isConvex() && + mCollidersComponents.mCollisionShapes[collider2Index]->isConvex()) { // Create a new ContactPair - const Entity collider1Entity = narrowPhaseInfoBatch.colliderEntities1[i]; - const Entity collider2Entity = narrowPhaseInfoBatch.colliderEntities2[i]; - - const uint32 collider1Index = mCollidersComponents.getEntityIndex(collider1Entity); - const uint32 collider2Index = mCollidersComponents.getEntityIndex(collider2Entity); - - const Entity body1Entity = mCollidersComponents.mBodiesEntities[collider1Index]; - const Entity body2Entity = mCollidersComponents.mBodiesEntities[collider2Index]; - const bool isTrigger = mCollidersComponents.mIsTrigger[collider1Index] || mCollidersComponents.mIsTrigger[collider2Index]; assert(!mWorld->mCollisionBodyComponents.getIsEntityDisabled(body1Entity) || !mWorld->mCollisionBodyComponents.getIsEntityDisabled(body2Entity)); const uint newContactPairIndex = contactPairs->size(); - ContactPair overlappingPairContact(pairId, body1Entity, body2Entity, - collider1Entity, collider2Entity, - newContactPairIndex, mOverlappingPairs.getCollidingInPreviousFrame(pairId), isTrigger, mMemoryManager.getHeapAllocator()); - contactPairs->add(overlappingPairContact); - pairContact = &((*contactPairs)[newContactPairIndex]); - mapPairIdToContactPairIndex->add(Pair(pairId, newContactPairIndex)); - } - else { // If a ContactPair already exists for this overlapping pair, we use this one - assert(it->first == pairId); + contactPairs->emplace(pairId, body1Entity, body2Entity, collider1Entity, collider2Entity, + newContactPairIndex, mOverlappingPairs.getCollidingInPreviousFrame(pairId), isTrigger); - const uint pairContactIndex = it->second; - pairContact = &((*contactPairs)[pairContactIndex]); - } + ContactPair* pairContact = &((*contactPairs)[newContactPairIndex]); - assert(pairContact != nullptr); + // Create a new potential contact manifold for the overlapping pair + uint contactManifoldIndex = static_cast(potentialContactManifolds.size()); + potentialContactManifolds.emplace(pairId); + ContactManifoldInfo& contactManifoldInfo = potentialContactManifolds[contactManifoldIndex]; - // Add the potential contacts - for (uint j=0; j < narrowPhaseInfoBatch.contactPoints[i].size(); j++) { + const uint contactPointIndexStart = static_cast(potentialContactPoints.size()); - const ContactPointInfo& contactPoint = *(narrowPhaseInfoBatch.contactPoints[i][j]); + // Add the potential contacts + for (uint j=0; j < narrowPhaseInfoBatch.contactPoints[i].size(); j++) { - // Add the contact point to the list of potential contact points - const uint contactPointIndex = static_cast(potentialContactPoints.size()); - - potentialContactPoints.add(contactPoint); - - bool similarManifoldFound = false; - - // For each contact manifold of the overlapping pair - for (uint m=0; m < pairContact->potentialContactManifoldsIndices.size(); m++) { - - uint contactManifoldIndex = pairContact->potentialContactManifoldsIndices[m]; - - // Get the first contact point of the current manifold - assert(potentialContactManifolds[contactManifoldIndex].potentialContactPointsIndices.size() > 0); - const uint manifoldContactPointIndex = potentialContactManifolds[contactManifoldIndex].potentialContactPointsIndices[0]; - const ContactPointInfo& manifoldContactPoint = potentialContactPoints[manifoldContactPointIndex]; - - // If we have found a corresponding manifold for the new contact point - // (a manifold with a similar contact normal direction) - if (manifoldContactPoint.normal.dot(contactPoint.normal) >= mWorld->mConfig.cosAngleSimilarContactManifold) { + if (contactManifoldInfo.nbPotentialContactPoints < NB_MAX_CONTACT_POINTS_IN_POTENTIAL_MANIFOLD) { // Add the contact point to the manifold - potentialContactManifolds[contactManifoldIndex].potentialContactPointsIndices.add(contactPointIndex); + contactManifoldInfo.potentialContactPointsIndices[contactManifoldInfo.nbPotentialContactPoints] = contactPointIndexStart + j; + contactManifoldInfo.nbPotentialContactPoints++; - similarManifoldFound = true; + // Add the contact point to the list of potential contact points + const ContactPointInfo& contactPoint = *(narrowPhaseInfoBatch.contactPoints[i][j]); - break; + potentialContactPoints.add(contactPoint); } } - // If we have not found a manifold with a similar contact normal for the contact point - if (!similarManifoldFound) { + // Add the contact manifold to the overlapping pair contact + assert(overlappingPairContact.pairId == contactManifoldInfo.pairId); + pairContact->potentialContactManifoldsIndices[0] = contactManifoldIndex; + pairContact->nbPotentialContactManifolds = 1; + } + else { - // Create a new contact manifold for the overlapping pair - ContactManifoldInfo contactManifoldInfo(pairId, mMemoryManager.getPoolAllocator()); + // If there is not already a contact pair for this overlapping pair + auto it = mapPairIdToContactPairIndex.find(pairId); + ContactPair* pairContact = nullptr; + if (it == mapPairIdToContactPairIndex.end()) { - // Add the contact point to the manifold - contactManifoldInfo.potentialContactPointsIndices.add(contactPointIndex); + // Create a new ContactPair - assert(pairContact != nullptr); + const bool isTrigger = mCollidersComponents.mIsTrigger[collider1Index] || mCollidersComponents.mIsTrigger[collider2Index]; - // Add the potential contact manifold - uint contactManifoldIndex = static_cast(potentialContactManifolds.size()); - potentialContactManifolds.add(contactManifoldInfo); + assert(!mWorld->mCollisionBodyComponents.getIsEntityDisabled(body1Entity) || !mWorld->mCollisionBodyComponents.getIsEntityDisabled(body2Entity)); - // Add the contact manifold to the overlapping pair contact - assert(pairContact->pairId == contactManifoldInfo.pairId); - pairContact->potentialContactManifoldsIndices.add(contactManifoldIndex); + const uint newContactPairIndex = contactPairs->size(); + contactPairs->emplace(pairId, body1Entity, body2Entity, collider1Entity, collider2Entity, + newContactPairIndex, mOverlappingPairs.getCollidingInPreviousFrame(pairId), isTrigger); + pairContact = &((*contactPairs)[newContactPairIndex]); + mapPairIdToContactPairIndex.add(Pair(pairId, newContactPairIndex)); + + } + else { // If a ContactPair already exists for this overlapping pair, we use this one + + assert(it->first == pairId); + + const uint pairContactIndex = it->second; + pairContact = &((*contactPairs)[pairContactIndex]); } - assert(pairContact->potentialContactManifoldsIndices.size() > 0); + assert(pairContact != nullptr); + + // Add the potential contacts + for (uint j=0; j < narrowPhaseInfoBatch.contactPoints[i].size(); j++) { + + const ContactPointInfo& contactPoint = *(narrowPhaseInfoBatch.contactPoints[i][j]); + + // Add the contact point to the list of potential contact points + const uint contactPointIndex = static_cast(potentialContactPoints.size()); + + potentialContactPoints.add(contactPoint); + + bool similarManifoldFound = false; + + // For each contact manifold of the overlapping pair + for (uint m=0; m < pairContact->nbPotentialContactManifolds; m++) { + + uint contactManifoldIndex = pairContact->potentialContactManifoldsIndices[m]; + + if (potentialContactManifolds[contactManifoldIndex].nbPotentialContactPoints < NB_MAX_CONTACT_POINTS_IN_POTENTIAL_MANIFOLD) { + + // Get the first contact point of the current manifold + assert(potentialContactManifolds[contactManifoldIndex].nbPotentialContactPoints > 0); + const uint manifoldContactPointIndex = potentialContactManifolds[contactManifoldIndex].potentialContactPointsIndices[0]; + const ContactPointInfo& manifoldContactPoint = potentialContactPoints[manifoldContactPointIndex]; + + // If we have found a corresponding manifold for the new contact point + // (a manifold with a similar contact normal direction) + if (manifoldContactPoint.normal.dot(contactPoint.normal) >= mWorld->mConfig.cosAngleSimilarContactManifold) { + + // Add the contact point to the manifold + potentialContactManifolds[contactManifoldIndex].potentialContactPointsIndices[potentialContactManifolds[contactManifoldIndex].nbPotentialContactPoints] = contactPointIndex; + potentialContactManifolds[contactManifoldIndex].nbPotentialContactPoints++; + + similarManifoldFound = true; + + break; + } + } + } + + // If we have not found a manifold with a similar contact normal for the contact point + if (!similarManifoldFound && pairContact->nbPotentialContactManifolds < NB_MAX_POTENTIAL_CONTACT_MANIFOLDS) { + + // Create a new potential contact manifold for the overlapping pair + uint contactManifoldIndex = static_cast(potentialContactManifolds.size()); + potentialContactManifolds.emplace(pairId); + ContactManifoldInfo& contactManifoldInfo = potentialContactManifolds[contactManifoldIndex]; + + // Add the contact point to the manifold + contactManifoldInfo.potentialContactPointsIndices[0] = contactPointIndex; + contactManifoldInfo.nbPotentialContactPoints = 1; + + assert(pairContact != nullptr); + + // Add the contact manifold to the overlapping pair contact + assert(pairContact->pairId == contactManifoldInfo.pairId); + pairContact->potentialContactManifoldsIndices[pairContact->nbPotentialContactManifolds] = contactManifoldIndex; + pairContact->nbPotentialContactManifolds++; + } + + assert(pairContact->nbPotentialContactManifolds > 0); + } } narrowPhaseInfoBatch.resetContactPoints(i); @@ -1126,12 +1176,12 @@ void CollisionDetectionSystem::reducePotentialContactManifolds(List ContactPair& contactPair = (*contactPairs)[i]; // While there are too many manifolds in the contact pair - while(contactPair.potentialContactManifoldsIndices.size() > mWorld->mConfig.nbMaxContactManifolds) { + while(contactPair.nbPotentialContactManifolds > NB_MAX_CONTACT_MANIFOLDS) { // Look for a manifold with the smallest contact penetration depth. decimal minDepth = DECIMAL_LARGEST; int minDepthManifoldIndex = -1; - for (uint j=0; j < contactPair.potentialContactManifoldsIndices.size(); j++) { + for (uint j=0; j < contactPair.nbPotentialContactManifolds; j++) { ContactManifoldInfo& manifold = potentialContactManifolds[contactPair.potentialContactManifoldsIndices[j]]; @@ -1146,7 +1196,7 @@ void CollisionDetectionSystem::reducePotentialContactManifolds(List // Remove the non optimal manifold assert(minDepthManifoldIndex >= 0); - contactPair.potentialContactManifoldsIndices.removeAt(minDepthManifoldIndex); + contactPair.removePotentialManifoldAtIndex(minDepthManifoldIndex); } } @@ -1156,12 +1206,12 @@ void CollisionDetectionSystem::reducePotentialContactManifolds(List const ContactPair& pairContact = (*contactPairs)[i]; // For each potential contact manifold - for (uint j=0; j < pairContact.potentialContactManifoldsIndices.size(); j++) { + for (uint j=0; j < pairContact.nbPotentialContactManifolds; j++) { ContactManifoldInfo& manifold = potentialContactManifolds[pairContact.potentialContactManifoldsIndices[j]]; // If there are two many contact points in the manifold - if (manifold.potentialContactPointsIndices.size() > MAX_CONTACT_POINTS_IN_MANIFOLD) { + if (manifold.nbPotentialContactPoints > MAX_CONTACT_POINTS_IN_MANIFOLD) { Entity collider1 = mOverlappingPairs.mColliders1[mOverlappingPairs.mMapPairIdToPairIndex[manifold.pairId]]; @@ -1171,7 +1221,7 @@ void CollisionDetectionSystem::reducePotentialContactManifolds(List reduceContactPoints(manifold, shape1LocalToWorldTransoform, potentialContactPoints); } - assert(manifold.potentialContactPointsIndices.size() <= MAX_CONTACT_POINTS_IN_MANIFOLD); + assert(manifold.nbPotentialContactPoints <= MAX_CONTACT_POINTS_IN_MANIFOLD); } } } @@ -1182,9 +1232,9 @@ decimal CollisionDetectionSystem::computePotentialManifoldLargestContactDepth(co decimal largestDepth = 0.0f; - assert(manifold.potentialContactPointsIndices.size() > 0); + assert(manifold.nbPotentialContactPoints > 0); - for (uint i=0; i < manifold.potentialContactPointsIndices.size(); i++) { + for (uint i=0; i < manifold.nbPotentialContactPoints; i++) { decimal depth = potentialContactPoints[manifold.potentialContactPointsIndices[i]].penetrationDepth; if (depth > largestDepth) { @@ -1202,14 +1252,18 @@ decimal CollisionDetectionSystem::computePotentialManifoldLargestContactDepth(co void CollisionDetectionSystem::reduceContactPoints(ContactManifoldInfo& manifold, const Transform& shape1ToWorldTransform, const List& potentialContactPoints) const { - assert(manifold.potentialContactPointsIndices.size() > MAX_CONTACT_POINTS_IN_MANIFOLD); + assert(manifold.nbPotentialContactPoints > MAX_CONTACT_POINTS_IN_MANIFOLD); // The following algorithm only works to reduce to a maximum of 4 contact points assert(MAX_CONTACT_POINTS_IN_MANIFOLD == 4); // List of the candidate contact points indices in the manifold. Every time that we have found a // point we want to keep, we will remove it from this list - List candidatePointsIndices(manifold.potentialContactPointsIndices); + uint candidatePointsIndices[NB_MAX_CONTACT_POINTS_IN_POTENTIAL_MANIFOLD]; + uint8 nbCandidatePoints = manifold.nbPotentialContactPoints; + for (uint8 i=0 ; i < manifold.nbPotentialContactPoints; i++) { + candidatePointsIndices[i] = manifold.potentialContactPointsIndices[i]; + } int8 nbReducedPoints = 0; @@ -1233,7 +1287,7 @@ void CollisionDetectionSystem::reduceContactPoints(ContactManifoldInfo& manifold const Vector3 searchDirection(1, 1, 1); decimal maxDotProduct = DECIMAL_SMALLEST; uint elementIndexToKeep = 0; - for (uint i=0; i < candidatePointsIndices.size(); i++) { + for (uint i=0; i < nbCandidatePoints; i++) { const ContactPointInfo& element = potentialContactPoints[candidatePointsIndices[i]]; decimal dotProduct = searchDirection.dot(element.localPoint1); @@ -1244,7 +1298,8 @@ void CollisionDetectionSystem::reduceContactPoints(ContactManifoldInfo& manifold } } pointsToKeepIndices[0] = candidatePointsIndices[elementIndexToKeep]; - candidatePointsIndices.removeAt(elementIndexToKeep); + removeItemAtInArray(candidatePointsIndices, elementIndexToKeep, nbCandidatePoints); + //candidatePointsIndices.removeAt(elementIndexToKeep); assert(nbReducedPoints == 1); // Compute the second contact point we need to keep. @@ -1252,7 +1307,7 @@ void CollisionDetectionSystem::reduceContactPoints(ContactManifoldInfo& manifold decimal maxDistance = decimal(0.0); elementIndexToKeep = 0; - for (uint i=0; i < candidatePointsIndices.size(); i++) { + for (uint i=0; i < nbCandidatePoints; i++) { const ContactPointInfo& element = potentialContactPoints[candidatePointsIndices[i]]; const ContactPointInfo& pointToKeep0 = potentialContactPoints[pointsToKeepIndices[0]]; @@ -1268,7 +1323,7 @@ void CollisionDetectionSystem::reduceContactPoints(ContactManifoldInfo& manifold } pointsToKeepIndices[1] = candidatePointsIndices[elementIndexToKeep]; - candidatePointsIndices.removeAt(elementIndexToKeep); + removeItemAtInArray(candidatePointsIndices, elementIndexToKeep, nbCandidatePoints); assert(nbReducedPoints == 2); // Compute the third contact point we need to keep. @@ -1281,7 +1336,7 @@ void CollisionDetectionSystem::reduceContactPoints(ContactManifoldInfo& manifold decimal minArea = decimal(0.0); decimal maxArea = decimal(0.0); bool isPreviousAreaPositive = true; - for (uint i=0; i < candidatePointsIndices.size(); i++) { + for (uint i=0; i < nbCandidatePoints; i++) { const ContactPointInfo& element = potentialContactPoints[candidatePointsIndices[i]]; const ContactPointInfo& pointToKeep0 = potentialContactPoints[pointsToKeepIndices[0]]; @@ -1310,12 +1365,12 @@ void CollisionDetectionSystem::reduceContactPoints(ContactManifoldInfo& manifold if (maxArea > (-minArea)) { isPreviousAreaPositive = true; pointsToKeepIndices[2] = candidatePointsIndices[thirdPointMaxAreaIndex]; - candidatePointsIndices.removeAt(thirdPointMaxAreaIndex); + removeItemAtInArray(candidatePointsIndices, thirdPointMaxAreaIndex, nbCandidatePoints); } else { isPreviousAreaPositive = false; pointsToKeepIndices[2] = candidatePointsIndices[thirdPointMinAreaIndex]; - candidatePointsIndices.removeAt(thirdPointMinAreaIndex); + removeItemAtInArray(candidatePointsIndices, thirdPointMinAreaIndex, nbCandidatePoints); } nbReducedPoints = 3; @@ -1328,7 +1383,7 @@ void CollisionDetectionSystem::reduceContactPoints(ContactManifoldInfo& manifold decimal area; // For each remaining candidate points - for (uint i=0; i < candidatePointsIndices.size(); i++) { + for (uint i=0; i < nbCandidatePoints; i++) { const ContactPointInfo& element = potentialContactPoints[candidatePointsIndices[i]]; @@ -1365,14 +1420,22 @@ void CollisionDetectionSystem::reduceContactPoints(ContactManifoldInfo& manifold } } pointsToKeepIndices[3] = candidatePointsIndices[elementIndexToKeep]; - candidatePointsIndices.removeAt(elementIndexToKeep); + removeItemAtInArray(candidatePointsIndices, elementIndexToKeep, nbCandidatePoints); // Only keep the four selected contact points in the manifold - manifold.potentialContactPointsIndices.clear(); - manifold.potentialContactPointsIndices.add(pointsToKeepIndices[0]); - manifold.potentialContactPointsIndices.add(pointsToKeepIndices[1]); - manifold.potentialContactPointsIndices.add(pointsToKeepIndices[2]); - manifold.potentialContactPointsIndices.add(pointsToKeepIndices[3]); + manifold.potentialContactPointsIndices[0] = pointsToKeepIndices[0]; + manifold.potentialContactPointsIndices[1] = pointsToKeepIndices[1]; + manifold.potentialContactPointsIndices[2] = pointsToKeepIndices[2]; + manifold.potentialContactPointsIndices[3] = pointsToKeepIndices[3]; + manifold.nbPotentialContactPoints = 4; +} + +// Remove an element in an array (and replace it by the last one in the array) +void CollisionDetectionSystem::removeItemAtInArray(uint array[], uint8 index, uint8& arraySize) const { + assert(index < arraySize); + assert(arraySize > 0); + array[index] = array[arraySize - 1]; + arraySize--; } // Report contacts and triggers From 3b2f973ffdffbcbfaa998bc59bb478a245d631a7 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Sun, 19 Jul 2020 01:02:02 +0200 Subject: [PATCH 07/74] Fix issue in Quaternion::Quaternion(Matrix3x3& matrix) constructor and add corresponding unit test --- src/mathematics/Quaternion.cpp | 2 +- test/tests/mathematics/TestQuaternion.h | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/mathematics/Quaternion.cpp b/src/mathematics/Quaternion.cpp index d1451a3d..a8918f93 100644 --- a/src/mathematics/Quaternion.cpp +++ b/src/mathematics/Quaternion.cpp @@ -106,7 +106,7 @@ Quaternion::Quaternion(const Matrix3x3& matrix) { // Compute the quaternion x = decimal(0.5) * r; y = (matrix[0][1] + matrix[1][0]) * s; - z = (matrix[2][0] - matrix[0][2]) * s; + z = (matrix[2][0] + matrix[0][2]) * s; w = (matrix[2][1] - matrix[1][2]) * s; } } diff --git a/test/tests/mathematics/TestQuaternion.h b/test/tests/mathematics/TestQuaternion.h index 3a25cba9..042aa577 100644 --- a/test/tests/mathematics/TestQuaternion.h +++ b/test/tests/mathematics/TestQuaternion.h @@ -90,6 +90,21 @@ class TestQuaternion : public Test { rp3d_test(approxEqual(quaternion4.z, mQuaternion1.z)); rp3d_test(approxEqual(quaternion4.w, mQuaternion1.w)); + Matrix3x3 original(0.001743,-0.968608,0.248589,-0.614229,-0.197205,-0.764090,0.789126,-0.151359,-0.595290); + Matrix3x3 converted = Quaternion(original).getMatrix(); + rp3d_test(approxEqual(original[0][0], converted[0][0], 0.0001)); + rp3d_test(approxEqual(original[0][1], converted[0][1], 0.0001)); + rp3d_test(approxEqual(original[0][2], converted[0][2], 0.0001)); + rp3d_test(approxEqual(original[1][0], converted[1][0], 0.0001)); + rp3d_test(approxEqual(original[1][1], converted[1][1], 0.0001)); + rp3d_test(approxEqual(original[1][2], converted[1][2], 0.0001)); + rp3d_test(approxEqual(original[2][0], converted[2][0], 0.0001)); + rp3d_test(approxEqual(original[2][1], converted[2][1], 0.0001)); + rp3d_test(approxEqual(original[2][2], converted[2][2], 0.0001)); + + std::cout << original.to_string() << std::endl; + std::cout << converted.to_string() << std::endl; + // Test conversion from Euler angles to quaternion const decimal PI_OVER_2 = PI * decimal(0.5); From a871bfdd6ad178de2d566b5e642d9a6f5ba127c6 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Mon, 20 Jul 2020 00:33:50 +0200 Subject: [PATCH 08/74] More optimizations --- include/reactphysics3d/body/RigidBody.h | 4 +- .../components/RigidBodyComponents.h | 16 ++++++- include/reactphysics3d/engine/PhysicsWorld.h | 3 ++ src/body/RigidBody.cpp | 14 +++--- src/components/RigidBodyComponents.cpp | 12 ++++- src/engine/PhysicsWorld.cpp | 12 +++++ src/systems/ContactSolverSystem.cpp | 46 +++++++++++++++---- src/systems/DynamicsSystem.cpp | 4 +- src/systems/SolveBallAndSocketJointSystem.cpp | 15 ++++-- src/systems/SolveFixedJointSystem.cpp | 15 ++++-- src/systems/SolveHingeJointSystem.cpp | 15 ++++-- src/systems/SolveSliderJointSystem.cpp | 15 ++++-- 12 files changed, 126 insertions(+), 45 deletions(-) diff --git a/include/reactphysics3d/body/RigidBody.h b/include/reactphysics3d/body/RigidBody.h index bc2353e0..b2481e2b 100644 --- a/include/reactphysics3d/body/RigidBody.h +++ b/include/reactphysics3d/body/RigidBody.h @@ -65,8 +65,8 @@ class RigidBody : public CollisionBody { /// Compute the local-space inertia tensor and total mass of the body using its colliders void computeMassAndInertiaTensorLocal(Vector3& inertiaTensorLocal, decimal& totalMass) const; - /// Return the inverse of the inertia tensor in world coordinates. - static const Matrix3x3 getWorldInertiaTensorInverse(PhysicsWorld& world, Entity bodyEntity); + /// Compute the inverse of the inertia tensor in world coordinates. + static void computeWorldInertiaTensorInverse(const Matrix3x3& orientation, const Vector3& inverseInertiaTensorLocal, Matrix3x3& outInverseInertiaTensorWorld); public : diff --git a/include/reactphysics3d/components/RigidBodyComponents.h b/include/reactphysics3d/components/RigidBodyComponents.h index 8097aaf3..a78f071b 100644 --- a/include/reactphysics3d/components/RigidBodyComponents.h +++ b/include/reactphysics3d/components/RigidBodyComponents.h @@ -109,9 +109,12 @@ class RigidBodyComponents : public Components { /// Array with the inertia tensor of each component Vector3* mLocalInertiaTensors; - /// Array with the inverse of the inertia tensor of each component + /// Array with the inverse of the local inertia tensor of each component Vector3* mInverseInertiaTensorsLocal; + /// Array with the inverse of the world inertia tensor of each component + Matrix3x3* mInverseInertiaTensorsWorld; + /// Array with the constrained linear velocity of each component Vector3* mConstrainedLinearVelocities; @@ -261,6 +264,9 @@ class RigidBodyComponents : public Components { /// Return the inverse local inertia tensor of an entity const Vector3& getInertiaTensorLocalInverse(Entity bodyEntity); + /// Return the inverse world inertia tensor of an entity + const Matrix3x3& getInertiaTensorWorldInverse(Entity bodyEntity); + /// Set the external force of an entity void setExternalForce(Entity bodyEntity, const Vector3& externalForce); @@ -523,6 +529,14 @@ inline const Vector3& RigidBodyComponents::getInertiaTensorLocalInverse(Entity b return mInverseInertiaTensorsLocal[mMapEntityToComponentIndex[bodyEntity]]; } +// Return the inverse world inertia tensor of an entity +inline const Matrix3x3& RigidBodyComponents::getInertiaTensorWorldInverse(Entity bodyEntity) { + + assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); + + return mInverseInertiaTensorsWorld[mMapEntityToComponentIndex[bodyEntity]]; +} + // Set the external force of an entity inline void RigidBodyComponents::setExternalForce(Entity bodyEntity, const Vector3& externalForce) { diff --git a/include/reactphysics3d/engine/PhysicsWorld.h b/include/reactphysics3d/engine/PhysicsWorld.h index c4c841ef..58fe99e7 100644 --- a/include/reactphysics3d/engine/PhysicsWorld.h +++ b/include/reactphysics3d/engine/PhysicsWorld.h @@ -301,6 +301,9 @@ class PhysicsWorld { /// Add the joint to the list of joints of the two bodies involved in the joint void addJointToBodies(Entity body1, Entity body2, Entity joint); + /// Update the world inverse inertia tensors of rigid bodies + void updateBodiesInverseWorldInertiaTensors(); + /// Destructor ~PhysicsWorld(); diff --git a/src/body/RigidBody.cpp b/src/body/RigidBody.cpp index be893ff4..76db1b04 100644 --- a/src/body/RigidBody.cpp +++ b/src/body/RigidBody.cpp @@ -886,16 +886,14 @@ void RigidBody::updateOverlappingPairs() { } } -/// Return the inverse of the inertia tensor in world coordinates. -const Matrix3x3 RigidBody::getWorldInertiaTensorInverse(PhysicsWorld& world, Entity bodyEntity) { +/// Compute the inverse of the inertia tensor in world coordinates. +void RigidBody::computeWorldInertiaTensorInverse(const Matrix3x3& orientation, const Vector3& inverseInertiaTensorLocal, Matrix3x3& outInverseInertiaTensorWorld) { - Matrix3x3 orientation = world.mTransformComponents.getTransform(bodyEntity).getOrientation().getMatrix(); - const Vector3& inverseInertiaLocalTensor = world.mRigidBodyComponents.getInertiaTensorLocalInverse(bodyEntity); Matrix3x3 orientationTranspose = orientation.getTranspose(); - orientationTranspose[0] *= inverseInertiaLocalTensor.x; - orientationTranspose[1] *= inverseInertiaLocalTensor.y; - orientationTranspose[2] *= inverseInertiaLocalTensor.z; - return orientation * orientationTranspose; + orientationTranspose[0] *= inverseInertiaTensorLocal.x; + orientationTranspose[1] *= inverseInertiaTensorLocal.y; + orientationTranspose[2] *= inverseInertiaTensorLocal.z; + outInverseInertiaTensorWorld = orientation * orientationTranspose; } // Set whether or not the body is allowed to go to sleep diff --git a/src/components/RigidBodyComponents.cpp b/src/components/RigidBodyComponents.cpp index 4653daab..6d2a389a 100644 --- a/src/components/RigidBodyComponents.cpp +++ b/src/components/RigidBodyComponents.cpp @@ -40,7 +40,7 @@ RigidBodyComponents::RigidBodyComponents(MemoryAllocator& allocator) sizeof(Vector3) + sizeof(Vector3) + sizeof(Vector3) + sizeof(Vector3) + sizeof(decimal) + sizeof(decimal) + sizeof(decimal) + sizeof(decimal) + sizeof(Vector3) + - sizeof(Vector3) + sizeof(Vector3) + sizeof(Vector3) + + sizeof(Vector3) + + sizeof(Matrix3x3) + sizeof(Vector3) + sizeof(Vector3) + sizeof(Vector3) + sizeof(Vector3) + sizeof(Vector3) + sizeof(Quaternion) + sizeof(Vector3) + sizeof(Vector3) + sizeof(bool) + sizeof(bool) + sizeof(List) + sizeof(List)) { @@ -78,7 +78,8 @@ void RigidBodyComponents::allocate(uint32 nbComponentsToAllocate) { decimal* newInverseMasses = reinterpret_cast(newMasses + nbComponentsToAllocate); Vector3* newInertiaTensorLocal = reinterpret_cast(newInverseMasses + nbComponentsToAllocate); Vector3* newInertiaTensorLocalInverses = reinterpret_cast(newInertiaTensorLocal + nbComponentsToAllocate); - Vector3* newConstrainedLinearVelocities = reinterpret_cast(newInertiaTensorLocalInverses + nbComponentsToAllocate); + Matrix3x3* newInertiaTensorWorldInverses = reinterpret_cast(newInertiaTensorLocalInverses + nbComponentsToAllocate); + Vector3* newConstrainedLinearVelocities = reinterpret_cast(newInertiaTensorWorldInverses + nbComponentsToAllocate); Vector3* newConstrainedAngularVelocities = reinterpret_cast(newConstrainedLinearVelocities + nbComponentsToAllocate); Vector3* newSplitLinearVelocities = reinterpret_cast(newConstrainedAngularVelocities + nbComponentsToAllocate); Vector3* newSplitAngularVelocities = reinterpret_cast(newSplitLinearVelocities + nbComponentsToAllocate); @@ -111,6 +112,7 @@ void RigidBodyComponents::allocate(uint32 nbComponentsToAllocate) { memcpy(newInverseMasses, mInverseMasses, mNbComponents * sizeof(decimal)); memcpy(newInertiaTensorLocal, mLocalInertiaTensors, mNbComponents * sizeof(Vector3)); memcpy(newInertiaTensorLocalInverses, mInverseInertiaTensorsLocal, mNbComponents * sizeof(Vector3)); + memcpy(newInertiaTensorWorldInverses, mInverseInertiaTensorsWorld, mNbComponents * sizeof(Matrix3x3)); memcpy(newConstrainedLinearVelocities, mConstrainedLinearVelocities, mNbComponents * sizeof(Vector3)); memcpy(newConstrainedAngularVelocities, mConstrainedAngularVelocities, mNbComponents * sizeof(Vector3)); memcpy(newSplitLinearVelocities, mSplitLinearVelocities, mNbComponents * sizeof(Vector3)); @@ -146,6 +148,7 @@ void RigidBodyComponents::allocate(uint32 nbComponentsToAllocate) { mInverseMasses = newInverseMasses; mLocalInertiaTensors = newInertiaTensorLocal; mInverseInertiaTensorsLocal = newInertiaTensorLocalInverses; + mInverseInertiaTensorsWorld = newInertiaTensorWorldInverses; mConstrainedLinearVelocities = newConstrainedLinearVelocities; mConstrainedAngularVelocities = newConstrainedAngularVelocities; mSplitLinearVelocities = newSplitLinearVelocities; @@ -183,6 +186,7 @@ void RigidBodyComponents::addComponent(Entity bodyEntity, bool isSleeping, const mInverseMasses[index] = decimal(1.0); new (mLocalInertiaTensors + index) Vector3(1.0, 1.0, 1.0); new (mInverseInertiaTensorsLocal + index) Vector3(1.0, 1.0, 1.0); + new (mInverseInertiaTensorsWorld + index) Matrix3x3(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0); new (mConstrainedLinearVelocities + index) Vector3(0, 0, 0); new (mConstrainedAngularVelocities + index) Vector3(0, 0, 0); new (mSplitLinearVelocities + index) Vector3(0, 0, 0); @@ -228,6 +232,7 @@ void RigidBodyComponents::moveComponentToIndex(uint32 srcIndex, uint32 destIndex mInverseMasses[destIndex] = mInverseMasses[srcIndex]; new (mLocalInertiaTensors + destIndex) Vector3(mLocalInertiaTensors[srcIndex]); new (mInverseInertiaTensorsLocal + destIndex) Vector3(mInverseInertiaTensorsLocal[srcIndex]); + new (mInverseInertiaTensorsWorld + destIndex) Matrix3x3(mInverseInertiaTensorsWorld[srcIndex]); new (mConstrainedLinearVelocities + destIndex) Vector3(mConstrainedLinearVelocities[srcIndex]); new (mConstrainedAngularVelocities + destIndex) Vector3(mConstrainedAngularVelocities[srcIndex]); new (mSplitLinearVelocities + destIndex) Vector3(mSplitLinearVelocities[srcIndex]); @@ -272,6 +277,7 @@ void RigidBodyComponents::swapComponents(uint32 index1, uint32 index2) { decimal inverseMass1 = mInverseMasses[index1]; Vector3 inertiaTensorLocal1 = mLocalInertiaTensors[index1]; Vector3 inertiaTensorLocalInverse1 = mInverseInertiaTensorsLocal[index1]; + Matrix3x3 inertiaTensorWorldInverse1 = mInverseInertiaTensorsWorld[index1]; Vector3 constrainedLinearVelocity1(mConstrainedLinearVelocities[index1]); Vector3 constrainedAngularVelocity1(mConstrainedAngularVelocities[index1]); Vector3 splitLinearVelocity1(mSplitLinearVelocities[index1]); @@ -307,6 +313,7 @@ void RigidBodyComponents::swapComponents(uint32 index1, uint32 index2) { mInverseMasses[index2] = inverseMass1; mLocalInertiaTensors[index2] = inertiaTensorLocal1; mInverseInertiaTensorsLocal[index2] = inertiaTensorLocalInverse1; + mInverseInertiaTensorsWorld[index2] = inertiaTensorWorldInverse1; new (mConstrainedLinearVelocities + index2) Vector3(constrainedLinearVelocity1); new (mConstrainedAngularVelocities + index2) Vector3(constrainedAngularVelocity1); new (mSplitLinearVelocities + index2) Vector3(splitLinearVelocity1); @@ -345,6 +352,7 @@ void RigidBodyComponents::destroyComponent(uint32 index) { mExternalTorques[index].~Vector3(); mLocalInertiaTensors[index].~Vector3(); mInverseInertiaTensorsLocal[index].~Vector3(); + mInverseInertiaTensorsWorld[index].~Matrix3x3(); mConstrainedLinearVelocities[index].~Vector3(); mConstrainedAngularVelocities[index].~Vector3(); mSplitLinearVelocities[index].~Vector3(); diff --git a/src/engine/PhysicsWorld.cpp b/src/engine/PhysicsWorld.cpp index 0ef91ca8..04fd5598 100644 --- a/src/engine/PhysicsWorld.cpp +++ b/src/engine/PhysicsWorld.cpp @@ -341,6 +341,9 @@ void PhysicsWorld::update(decimal timeStep) { // Report the contacts to the user mCollisionDetection.reportContactsAndTriggers(); + // Recompute the inverse inertia tensors of rigid bodies + updateBodiesInverseWorldInertiaTensors(); + // Disable the joints for pair of sleeping bodies disableJointsOfSleepingBodies(); @@ -379,6 +382,15 @@ void PhysicsWorld::update(decimal timeStep) { mMemoryManager.resetFrameAllocator(); } +// Update the world inverse inertia tensors of rigid bodies +void PhysicsWorld::updateBodiesInverseWorldInertiaTensors() { + + for (uint i=0; i < mRigidBodyComponents.getNbEnabledComponents(); i++) { + const Matrix3x3 orientation = mTransformComponents.getTransform(mRigidBodyComponents.mBodiesEntities[i]).getOrientation().getMatrix(); + + RigidBody::computeWorldInertiaTensorInverse(orientation, mRigidBodyComponents.mInverseInertiaTensorsLocal[i], mRigidBodyComponents.mInverseInertiaTensorsWorld[i]); + } +} // Solve the contacts and constraints void PhysicsWorld::solveContactsAndConstraints(decimal timeStep) { diff --git a/src/systems/ContactSolverSystem.cpp b/src/systems/ContactSolverSystem.cpp index 76a79c74..b5854a54 100644 --- a/src/systems/ContactSolverSystem.cpp +++ b/src/systems/ContactSolverSystem.cpp @@ -147,8 +147,8 @@ void ContactSolverSystem::initializeForIsland(uint islandIndex) { new (mContactConstraints + mNbContactManifolds) ContactManifoldSolver(); mContactConstraints[mNbContactManifolds].rigidBodyComponentIndexBody1 = rigidBodyIndex1; mContactConstraints[mNbContactManifolds].rigidBodyComponentIndexBody2 = rigidBodyIndex2; - mContactConstraints[mNbContactManifolds].inverseInertiaTensorBody1 = RigidBody::getWorldInertiaTensorInverse(mWorld, externalManifold.bodyEntity1); - mContactConstraints[mNbContactManifolds].inverseInertiaTensorBody2 = RigidBody::getWorldInertiaTensorInverse(mWorld, externalManifold.bodyEntity2); + mContactConstraints[mNbContactManifolds].inverseInertiaTensorBody1 = mRigidBodyComponents.mInverseInertiaTensorsWorld[rigidBodyIndex1]; + mContactConstraints[mNbContactManifolds].inverseInertiaTensorBody2 = mRigidBodyComponents.mInverseInertiaTensorsWorld[rigidBodyIndex2]; mContactConstraints[mNbContactManifolds].massInverseBody1 = mRigidBodyComponents.mInverseMasses[rigidBodyIndex1]; mContactConstraints[mNbContactManifolds].massInverseBody2 = mRigidBodyComponents.mInverseMasses[rigidBodyIndex2]; mContactConstraints[mNbContactManifolds].nbContacts = externalManifold.nbContactPoints; @@ -668,14 +668,20 @@ void ContactSolverSystem::solve() { mRigidBodyComponents.mConstrainedLinearVelocities[mContactConstraints[c].rigidBodyComponentIndexBody1].y -= mContactConstraints[c].massInverseBody1 * linearImpulseBody2.y; mRigidBodyComponents.mConstrainedLinearVelocities[mContactConstraints[c].rigidBodyComponentIndexBody1].z -= mContactConstraints[c].massInverseBody1 * linearImpulseBody2.z; - mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody1] += mContactConstraints[c].inverseInertiaTensorBody1 * angularImpulseBody1; + Vector3 angularVelocity1 = mContactConstraints[c].inverseInertiaTensorBody1 * angularImpulseBody1; + mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody1].x += angularVelocity1.x; + mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody1].y += angularVelocity1.y; + mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody1].z += angularVelocity1.z; // Update the velocities of the body 2 by applying the impulse P mRigidBodyComponents.mConstrainedLinearVelocities[mContactConstraints[c].rigidBodyComponentIndexBody2].x += mContactConstraints[c].massInverseBody2 * linearImpulseBody2.x; mRigidBodyComponents.mConstrainedLinearVelocities[mContactConstraints[c].rigidBodyComponentIndexBody2].y += mContactConstraints[c].massInverseBody2 * linearImpulseBody2.y; mRigidBodyComponents.mConstrainedLinearVelocities[mContactConstraints[c].rigidBodyComponentIndexBody2].z += mContactConstraints[c].massInverseBody2 * linearImpulseBody2.z; - mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody2] += mContactConstraints[c].inverseInertiaTensorBody2 * angularImpulseBody2; + Vector3 angularVelocity2 = mContactConstraints[c].inverseInertiaTensorBody2 * angularImpulseBody2; + mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody2].x += angularVelocity2.x; + mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody2].y += angularVelocity2.y; + mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody2].z += angularVelocity2.z; // ------ Second friction constraint at the center of the contact manifold ----- // @@ -716,13 +722,21 @@ void ContactSolverSystem::solve() { mRigidBodyComponents.mConstrainedLinearVelocities[mContactConstraints[c].rigidBodyComponentIndexBody1].x -= mContactConstraints[c].massInverseBody1 * linearImpulseBody2.x; mRigidBodyComponents.mConstrainedLinearVelocities[mContactConstraints[c].rigidBodyComponentIndexBody1].y -= mContactConstraints[c].massInverseBody1 * linearImpulseBody2.y; mRigidBodyComponents.mConstrainedLinearVelocities[mContactConstraints[c].rigidBodyComponentIndexBody1].z -= mContactConstraints[c].massInverseBody1 * linearImpulseBody2.z; - mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody1] += mContactConstraints[c].inverseInertiaTensorBody1 * angularImpulseBody1; + + angularVelocity1 = mContactConstraints[c].inverseInertiaTensorBody1 * angularImpulseBody1; + mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody1].x += angularVelocity1.x; + mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody1].y += angularVelocity1.y; + mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody1].z += angularVelocity1.z; // Update the velocities of the body 2 by applying the impulse P mRigidBodyComponents.mConstrainedLinearVelocities[mContactConstraints[c].rigidBodyComponentIndexBody2].x += mContactConstraints[c].massInverseBody2 * linearImpulseBody2.x; mRigidBodyComponents.mConstrainedLinearVelocities[mContactConstraints[c].rigidBodyComponentIndexBody2].y += mContactConstraints[c].massInverseBody2 * linearImpulseBody2.y; mRigidBodyComponents.mConstrainedLinearVelocities[mContactConstraints[c].rigidBodyComponentIndexBody2].z += mContactConstraints[c].massInverseBody2 * linearImpulseBody2.z; - mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody2] += mContactConstraints[c].inverseInertiaTensorBody2 * angularImpulseBody2; + + angularVelocity2 = mContactConstraints[c].inverseInertiaTensorBody2 * angularImpulseBody2; + mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody2].x += angularVelocity2.x; + mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody2].y += angularVelocity2.y; + mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody2].z += angularVelocity2.z; // ------ Twist friction constraint at the center of the contact manifol ------ // @@ -745,10 +759,16 @@ void ContactSolverSystem::solve() { angularImpulseBody2.z = mContactConstraints[c].normal.z * deltaLambda; // Update the velocities of the body 1 by applying the impulse P - mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody1] -= mContactConstraints[c].inverseInertiaTensorBody1 * angularImpulseBody2; + angularVelocity1 = mContactConstraints[c].inverseInertiaTensorBody1 * angularImpulseBody2; + mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody1].x -= angularVelocity1.x; + mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody1].y -= angularVelocity1.y; + mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody1].z -= angularVelocity1.z; // Update the velocities of the body 1 by applying the impulse P - mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody2] += mContactConstraints[c].inverseInertiaTensorBody2 * angularImpulseBody2; + angularVelocity2 = mContactConstraints[c].inverseInertiaTensorBody2 * angularImpulseBody2; + mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody2].x += angularVelocity2.x; + mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody2].y += angularVelocity2.y; + mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody2].z += angularVelocity2.z; // --------- Rolling resistance constraint at the center of the contact manifold --------- // @@ -766,10 +786,16 @@ void ContactSolverSystem::solve() { deltaLambdaRolling = mContactConstraints[c].rollingResistanceImpulse - lambdaTempRolling; // Update the velocities of the body 1 by applying the impulse P - mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody1] -= mContactConstraints[c].inverseInertiaTensorBody1 * deltaLambdaRolling; + angularVelocity1 = mContactConstraints[c].inverseInertiaTensorBody1 * deltaLambdaRolling; + mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody1].x -= angularVelocity1.x; + mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody1].y -= angularVelocity1.y; + mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody1].z -= angularVelocity1.z; // Update the velocities of the body 2 by applying the impulse P - mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody2] += mContactConstraints[c].inverseInertiaTensorBody2 * deltaLambdaRolling; + angularVelocity2 = mContactConstraints[c].inverseInertiaTensorBody2 * deltaLambdaRolling; + mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody2].x += angularVelocity2.x; + mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody2].y += angularVelocity2.y; + mRigidBodyComponents.mConstrainedAngularVelocities[mContactConstraints[c].rigidBodyComponentIndexBody2].z += angularVelocity2.z; } } } diff --git a/src/systems/DynamicsSystem.cpp b/src/systems/DynamicsSystem.cpp index fae61e82..b30506d6 100644 --- a/src/systems/DynamicsSystem.cpp +++ b/src/systems/DynamicsSystem.cpp @@ -130,8 +130,8 @@ void DynamicsSystem::integrateRigidBodiesVelocities(decimal timeStep) { // Integrate the external force to get the new velocity of the body mRigidBodyComponents.mConstrainedLinearVelocities[i] = linearVelocity + timeStep * mRigidBodyComponents.mInverseMasses[i] * mRigidBodyComponents.mExternalForces[i]; - mRigidBodyComponents.mConstrainedAngularVelocities[i] = angularVelocity + timeStep * - RigidBody::getWorldInertiaTensorInverse(mWorld, mRigidBodyComponents.mBodiesEntities[i]) * mRigidBodyComponents.mExternalTorques[i]; + mRigidBodyComponents.mConstrainedAngularVelocities[i] = angularVelocity + timeStep * mRigidBodyComponents.mInverseInertiaTensorsWorld[i] * + mRigidBodyComponents.mExternalTorques[i]; } // Apply gravity force diff --git a/src/systems/SolveBallAndSocketJointSystem.cpp b/src/systems/SolveBallAndSocketJointSystem.cpp index f481d319..078a1dd7 100644 --- a/src/systems/SolveBallAndSocketJointSystem.cpp +++ b/src/systems/SolveBallAndSocketJointSystem.cpp @@ -60,8 +60,8 @@ void SolveBallAndSocketJointSystem::initBeforeSolve() { assert(!mRigidBodyComponents.getIsEntityDisabled(body2Entity)); // Get the inertia tensor of bodies - mBallAndSocketJointComponents.mI1[i] = RigidBody::getWorldInertiaTensorInverse(mWorld, body1Entity); - mBallAndSocketJointComponents.mI2[i] = RigidBody::getWorldInertiaTensorInverse(mWorld, body2Entity); + mBallAndSocketJointComponents.mI1[i] = mRigidBodyComponents.getInertiaTensorWorldInverse(body1Entity); + mBallAndSocketJointComponents.mI2[i] = mRigidBodyComponents.getInertiaTensorWorldInverse(body2Entity); } // For each joint @@ -260,9 +260,14 @@ void SolveBallAndSocketJointSystem::solvePositionConstraint() { const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); - // Recompute the inverse inertia tensors - mBallAndSocketJointComponents.mI1[i] = RigidBody::getWorldInertiaTensorInverse(mWorld, body1Entity); - mBallAndSocketJointComponents.mI2[i] = RigidBody::getWorldInertiaTensorInverse(mWorld, body2Entity); + // Recompute the world inverse inertia tensors + const Matrix3x3 orientation1 = mTransformComponents.getTransform(body1Entity).getOrientation().getMatrix(); + RigidBody::computeWorldInertiaTensorInverse(orientation1, mRigidBodyComponents.getInertiaTensorLocalInverse(body1Entity), + mBallAndSocketJointComponents.mI1[i]); + + const Matrix3x3 orientation2 = mTransformComponents.getTransform(body2Entity).getOrientation().getMatrix(); + RigidBody::computeWorldInertiaTensorInverse(orientation2, mRigidBodyComponents.getInertiaTensorLocalInverse(body2Entity), + mBallAndSocketJointComponents.mI2[i]); } // For each joint component diff --git a/src/systems/SolveFixedJointSystem.cpp b/src/systems/SolveFixedJointSystem.cpp index 7143bb43..59a07089 100644 --- a/src/systems/SolveFixedJointSystem.cpp +++ b/src/systems/SolveFixedJointSystem.cpp @@ -60,8 +60,8 @@ void SolveFixedJointSystem::initBeforeSolve() { assert(!mRigidBodyComponents.getIsEntityDisabled(body2Entity)); // Get the inertia tensor of bodies - mFixedJointComponents.mI1[i] = RigidBody::getWorldInertiaTensorInverse(mWorld, body1Entity); - mFixedJointComponents.mI2[i] = RigidBody::getWorldInertiaTensorInverse(mWorld, body2Entity); + mFixedJointComponents.mI1[i] = mRigidBodyComponents.getInertiaTensorWorldInverse(body1Entity); + mFixedJointComponents.mI2[i] = mRigidBodyComponents.getInertiaTensorWorldInverse(body2Entity); } // For each joint @@ -346,9 +346,14 @@ void SolveFixedJointSystem::solvePositionConstraint() { const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); - // Recompute the inverse inertia tensors - mFixedJointComponents.mI1[i] = RigidBody::getWorldInertiaTensorInverse(mWorld, body1Entity); - mFixedJointComponents.mI2[i] = RigidBody::getWorldInertiaTensorInverse(mWorld, body2Entity); + // Recompute the world inverse inertia tensors + const Matrix3x3 orientation1 = mTransformComponents.getTransform(body1Entity).getOrientation().getMatrix(); + RigidBody::computeWorldInertiaTensorInverse(orientation1, mRigidBodyComponents.getInertiaTensorLocalInverse(body1Entity), + mFixedJointComponents.mI1[i]); + + const Matrix3x3 orientation2 = mTransformComponents.getTransform(body2Entity).getOrientation().getMatrix(); + RigidBody::computeWorldInertiaTensorInverse(orientation2, mRigidBodyComponents.getInertiaTensorLocalInverse(body2Entity), + mFixedJointComponents.mI2[i]); } // For each joint diff --git a/src/systems/SolveHingeJointSystem.cpp b/src/systems/SolveHingeJointSystem.cpp index a1582750..a4f9946e 100644 --- a/src/systems/SolveHingeJointSystem.cpp +++ b/src/systems/SolveHingeJointSystem.cpp @@ -60,8 +60,8 @@ void SolveHingeJointSystem::initBeforeSolve() { assert(!mRigidBodyComponents.getIsEntityDisabled(body2Entity)); // Get the inertia tensor of bodies - mHingeJointComponents.mI1[i] = RigidBody::getWorldInertiaTensorInverse(mWorld, body1Entity); - mHingeJointComponents.mI2[i] = RigidBody::getWorldInertiaTensorInverse(mWorld, body2Entity); + mHingeJointComponents.mI1[i] = mRigidBodyComponents.getInertiaTensorWorldInverse(body1Entity); + mHingeJointComponents.mI2[i] = mRigidBodyComponents.getInertiaTensorWorldInverse(body2Entity); } // For each joint @@ -539,9 +539,14 @@ void SolveHingeJointSystem::solvePositionConstraint() { Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); - // Recompute the inverse inertia tensors - mHingeJointComponents.mI1[i] = RigidBody::getWorldInertiaTensorInverse(mWorld, body1Entity); - mHingeJointComponents.mI2[i] = RigidBody::getWorldInertiaTensorInverse(mWorld, body2Entity); + // Recompute the world inverse inertia tensors + const Matrix3x3 orientation1 = mTransformComponents.getTransform(body1Entity).getOrientation().getMatrix(); + RigidBody::computeWorldInertiaTensorInverse(orientation1, mRigidBodyComponents.getInertiaTensorLocalInverse(body1Entity), + mHingeJointComponents.mI1[i]); + + const Matrix3x3 orientation2 = mTransformComponents.getTransform(body2Entity).getOrientation().getMatrix(); + RigidBody::computeWorldInertiaTensorInverse(orientation2, mRigidBodyComponents.getInertiaTensorLocalInverse(body2Entity), + mHingeJointComponents.mI2[i]); } // For each joint component diff --git a/src/systems/SolveSliderJointSystem.cpp b/src/systems/SolveSliderJointSystem.cpp index d68645f7..ac44ef7d 100644 --- a/src/systems/SolveSliderJointSystem.cpp +++ b/src/systems/SolveSliderJointSystem.cpp @@ -60,8 +60,8 @@ void SolveSliderJointSystem::initBeforeSolve() { assert(!mRigidBodyComponents.getIsEntityDisabled(body2Entity)); // Get the inertia tensor of bodies - mSliderJointComponents.mI1[i] = RigidBody::getWorldInertiaTensorInverse(mWorld, body1Entity); - mSliderJointComponents.mI2[i] = RigidBody::getWorldInertiaTensorInverse(mWorld, body2Entity); + mSliderJointComponents.mI1[i] = mRigidBodyComponents.getInertiaTensorWorldInverse(body1Entity); + mSliderJointComponents.mI2[i] = mRigidBodyComponents.getInertiaTensorWorldInverse(body2Entity); } // For each joint @@ -619,9 +619,14 @@ void SolveSliderJointSystem::solvePositionConstraint() { const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); - // Recompute the inverse inertia tensors - mSliderJointComponents.mI1[i] = RigidBody::getWorldInertiaTensorInverse(mWorld, body1Entity); - mSliderJointComponents.mI2[i] = RigidBody::getWorldInertiaTensorInverse(mWorld, body2Entity); + // Recompute the world inverse inertia tensors + const Matrix3x3 orientation1 = mTransformComponents.getTransform(body1Entity).getOrientation().getMatrix(); + RigidBody::computeWorldInertiaTensorInverse(orientation1, mRigidBodyComponents.getInertiaTensorLocalInverse(body1Entity), + mSliderJointComponents.mI1[i]); + + const Matrix3x3 orientation2 = mTransformComponents.getTransform(body2Entity).getOrientation().getMatrix(); + RigidBody::computeWorldInertiaTensorInverse(orientation2, mRigidBodyComponents.getInertiaTensorLocalInverse(body2Entity), + mSliderJointComponents.mI2[i]); } // For each joint component From d6b88baee73c91a1dd79f04c1fab9a0cb1fd55db Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Mon, 20 Jul 2020 00:34:10 +0200 Subject: [PATCH 09/74] Fix issues in some unit tests --- test/tests/collision/TestCollisionWorld.h | 10 +++++----- test/tests/collision/TestPointInside.h | 6 +++--- test/tests/collision/TestRaycast.h | 6 +++--- test/tests/mathematics/TestQuaternion.h | 16 ++++++++-------- test/tests/mathematics/TestTransform.h | 16 ++++++++-------- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/test/tests/collision/TestCollisionWorld.h b/test/tests/collision/TestCollisionWorld.h index 4c02d49c..583d7b1a 100644 --- a/test/tests/collision/TestCollisionWorld.h +++ b/test/tests/collision/TestCollisionWorld.h @@ -610,7 +610,7 @@ class TestCollisionWorld : public Test { Transform initTransform2 = mSphereBody2->getTransform(); Transform transform1(Vector3(10, 20, 50), Quaternion::identity()); - Transform transform2(Vector3(17, 20, 50), Quaternion::fromEulerAngles(rp3d::PI / 8.0f, rp3d::PI / 4.0f, rp3d::PI / 16.0f)); + Transform transform2(Vector3(17, 20, 50), Quaternion::fromEulerAngles(rp3d::PI_RP3D / 8.0f, rp3d::PI_RP3D / 4.0f, rp3d::PI_RP3D / 16.0f)); // Move spheres to collide with each other mSphereBody1->setTransform(transform1); @@ -2200,7 +2200,7 @@ class TestCollisionWorld : public Test { *********************************************************************************/ Transform transform1(Vector3(10, 20, 50), Quaternion::identity()); - Transform transform2(Vector3(17, 21, 50), Quaternion::fromEulerAngles(0, 0, rp3d::PI * 0.5f)); + Transform transform2(Vector3(17, 21, 50), Quaternion::fromEulerAngles(0, 0, rp3d::PI_RP3D * 0.5f)); // Move spheres to collide with each other mBoxBody1->setTransform(transform1); @@ -2315,7 +2315,7 @@ class TestCollisionWorld : public Test { *********************************************************************************/ Transform transform1(Vector3(10, 20, 50), Quaternion::identity()); - Transform transform2(Vector3(17, 21, 50), Quaternion::fromEulerAngles(0, 0, rp3d::PI * 0.5f)); + Transform transform2(Vector3(17, 21, 50), Quaternion::fromEulerAngles(0, 0, rp3d::PI_RP3D * 0.5f)); // Move spheres to collide with each other mConvexMeshBody1->setTransform(transform1); @@ -2626,7 +2626,7 @@ class TestCollisionWorld : public Test { *********************************************************************************/ Transform transform1(Vector3(10, 20, 50), Quaternion::identity()); - Transform transform2(Vector3(16, 23, 50), Quaternion::fromEulerAngles(0, 0, rp3d::PI * 0.5f)); + Transform transform2(Vector3(16, 23, 50), Quaternion::fromEulerAngles(0, 0, rp3d::PI_RP3D * 0.5f)); // Move spheres to collide with each other mCapsuleBody1->setTransform(transform1); @@ -2732,7 +2732,7 @@ class TestCollisionWorld : public Test { *********************************************************************************/ transform1 = Transform(Vector3(10, 20, 50), Quaternion::identity()); - transform2 = Transform(Vector3(10, 27, 50), Quaternion::fromEulerAngles(0, 0, rp3d::PI * 0.5f)); + transform2 = Transform(Vector3(10, 27, 50), Quaternion::fromEulerAngles(0, 0, rp3d::PI_RP3D * 0.5f)); // Move spheres to collide with each other mCapsuleBody1->setTransform(transform1); diff --git a/test/tests/collision/TestPointInside.h b/test/tests/collision/TestPointInside.h index 9170a2d8..63fac628 100644 --- a/test/tests/collision/TestPointInside.h +++ b/test/tests/collision/TestPointInside.h @@ -101,7 +101,7 @@ class TestPointInside : public Test { // Body transform Vector3 position(-3, 2, 7); - Quaternion orientation = Quaternion::fromEulerAngles(PI / 5, PI / 6, PI / 7); + Quaternion orientation = Quaternion::fromEulerAngles(PI_RP3D / 5, PI_RP3D / 6, PI_RP3D / 7); mBodyTransform = Transform(position, orientation); // Create the bodies @@ -117,7 +117,7 @@ class TestPointInside : public Test { // Collision shape transform Vector3 shapePosition(1, -4, -3); - Quaternion shapeOrientation = Quaternion::fromEulerAngles(3 * PI / 6 , -PI / 8, PI / 3); + Quaternion shapeOrientation = Quaternion::fromEulerAngles(3 * PI_RP3D / 6 , -PI_RP3D / 8, PI_RP3D / 3); mShapeTransform = Transform(shapePosition, shapeOrientation); // Compute the the transform from a local shape point to world-space @@ -167,7 +167,7 @@ class TestPointInside : public Test { // Compound shape is a capsule and a sphere Vector3 positionShape2(Vector3(4, 2, -3)); - Quaternion orientationShape2 = Quaternion::fromEulerAngles(-3 * PI / 8, 1.5 * PI/ 3, PI / 13); + Quaternion orientationShape2 = Quaternion::fromEulerAngles(-3 * PI_RP3D / 8, 1.5 * PI_RP3D/ 3, PI_RP3D / 13); Transform shapeTransform2(positionShape2, orientationShape2); mLocalShape2ToWorld = mBodyTransform * shapeTransform2; mCompoundBody->addCollider(mCapsuleShape, mShapeTransform); diff --git a/test/tests/collision/TestRaycast.h b/test/tests/collision/TestRaycast.h index 51f6f7de..f2b0a248 100644 --- a/test/tests/collision/TestRaycast.h +++ b/test/tests/collision/TestRaycast.h @@ -176,7 +176,7 @@ class TestRaycast : public Test { // Body transform Vector3 position(-3, 2, 7); - Quaternion orientation = Quaternion::fromEulerAngles(PI / 5, PI / 6, PI / 7); + Quaternion orientation = Quaternion::fromEulerAngles(PI_RP3D / 5, PI_RP3D / 6, PI_RP3D / 7); mBodyTransform = Transform(position, orientation); // Create the bodies @@ -191,7 +191,7 @@ class TestRaycast : public Test { // Collision shape transform Vector3 shapePosition(1, -4, -3); - Quaternion shapeOrientation = Quaternion::fromEulerAngles(3 * PI / 6 , -PI / 8, PI / 3); + Quaternion shapeOrientation = Quaternion::fromEulerAngles(3 * PI_RP3D / 6 , -PI_RP3D / 8, PI_RP3D / 3); mShapeTransform = Transform(shapePosition, shapeOrientation); // Compute the the transform from a local shape point to world-space @@ -243,7 +243,7 @@ class TestRaycast : public Test { // Compound shape is a cylinder and a sphere Vector3 positionShape2(Vector3(4, 2, -3)); - Quaternion orientationShape2 = Quaternion::fromEulerAngles(-3 *PI / 8, 1.5 * PI/ 3, PI / 13); + Quaternion orientationShape2 = Quaternion::fromEulerAngles(-3 * PI_RP3D / 8, 1.5 * PI_RP3D/ 3, PI_RP3D / 13); Transform shapeTransform2(positionShape2, orientationShape2); mLocalShape2ToWorld = mBodyTransform * shapeTransform2; mCompoundCapsuleCollider = mCompoundBody->addCollider(mCapsuleShape, mShapeTransform); diff --git a/test/tests/mathematics/TestQuaternion.h b/test/tests/mathematics/TestQuaternion.h index 3a25cba9..c116bccd 100644 --- a/test/tests/mathematics/TestQuaternion.h +++ b/test/tests/mathematics/TestQuaternion.h @@ -56,8 +56,8 @@ class TestQuaternion : public Test { /// Constructor TestQuaternion(const std::string& name) : Test(name), mIdentity(Quaternion::identity()) { - decimal sinA = sin(decimal(PI/8.0)); - decimal cosA = cos(decimal(PI/8.0)); + decimal sinA = sin(decimal(PI_RP3D/8.0)); + decimal cosA = cos(decimal(PI_RP3D/8.0)); Vector3 vector(2, 3, 4); vector.normalize(); mQuaternion1 = Quaternion(vector.x * sinA, vector.y * sinA, vector.z * sinA, cosA); @@ -92,7 +92,7 @@ class TestQuaternion : public Test { // Test conversion from Euler angles to quaternion - const decimal PI_OVER_2 = PI * decimal(0.5); + const decimal PI_OVER_2 = PI_RP3D * decimal(0.5); const decimal PI_OVER_4 = PI_OVER_2 * decimal(0.5); Quaternion quaternion5 = Quaternion::fromEulerAngles(PI_OVER_2, 0, 0); Quaternion quaternionTest5(std::sin(PI_OVER_4), 0, 0, std::cos(PI_OVER_4)); @@ -189,7 +189,7 @@ class TestQuaternion : public Test { Vector3 originalAxis = Vector3(2, 3, 4).getUnit(); mQuaternion1.getRotationAngleAxis(angle, axis); rp3d_test(approxEqual(axis.x, originalAxis.x)); - rp3d_test(approxEqual(angle, decimal(PI/4.0), decimal(10e-6))); + rp3d_test(approxEqual(angle, decimal(PI_RP3D/4.0), decimal(10e-6))); // Test the method that returns the corresponding matrix Matrix3x3 matrix = mQuaternion1.getMatrix(); @@ -207,14 +207,14 @@ class TestQuaternion : public Test { Quaternion test2 = Quaternion::slerp(quatStart, quatEnd, 1.0); rp3d_test(test1 == quatStart); rp3d_test(test2 == quatEnd); - decimal sinA = sin(decimal(PI/4.0)); - decimal cosA = cos(decimal(PI/4.0)); + decimal sinA = sin(decimal(PI_RP3D/4.0)); + decimal cosA = cos(decimal(PI_RP3D/4.0)); Quaternion quat(sinA, 0, 0, cosA); Quaternion test3 = Quaternion::slerp(mIdentity, quat, decimal(0.5)); - rp3d_test(approxEqual(test3.x, sin(decimal(PI/8.0)))); + rp3d_test(approxEqual(test3.x, sin(decimal(PI_RP3D/8.0)))); rp3d_test(approxEqual(test3.y, 0.0)); rp3d_test(approxEqual(test3.z, 0.0)); - rp3d_test(approxEqual(test3.w, cos(decimal(PI/8.0)), decimal(10e-6))); + rp3d_test(approxEqual(test3.w, cos(decimal(PI_RP3D/8.0)), decimal(10e-6))); } /// Test overloaded operators diff --git a/test/tests/mathematics/TestTransform.h b/test/tests/mathematics/TestTransform.h index cb020df9..74006034 100644 --- a/test/tests/mathematics/TestTransform.h +++ b/test/tests/mathematics/TestTransform.h @@ -65,12 +65,12 @@ class TestTransform : public Test { Vector3 unitVec(1, 1, 1); unitVec.normalize(); - decimal sinA = std::sin(PI/8.0f); - decimal cosA = std::cos(PI/8.0f); + decimal sinA = std::sin(PI_RP3D/8.0f); + decimal cosA = std::cos(PI_RP3D/8.0f); mTransform1 = Transform(Vector3(4, 5, 6), Quaternion(sinA * unitVec, cosA)); - decimal sinB = std::sin(PI/3.0f); - decimal cosB = std::cos(PI/3.0f); + decimal sinB = std::sin(PI_RP3D/3.0f); + decimal cosB = std::cos(PI_RP3D/3.0f); mTransform2 = Transform(Vector3(8, 45, -6), Quaternion(sinB * unitVec, cosB)); } @@ -162,10 +162,10 @@ class TestTransform : public Test { rp3d_test(transformStart == mTransform1); rp3d_test(transformEnd == mTransform2); - decimal sinA = sin(PI/3.0f); - decimal cosA = cos(PI/3.0f); - decimal sinB = sin(PI/6.0f); - decimal cosB = cos(PI/6.0f); + decimal sinA = sin(PI_RP3D/3.0f); + decimal cosA = cos(PI_RP3D/3.0f); + decimal sinB = sin(PI_RP3D/6.0f); + decimal cosB = cos(PI_RP3D/6.0f); Transform transform1(Vector3(4, 5, 6), Quaternion::identity()); Transform transform2(Vector3(8, 11, 16), Quaternion(sinA, sinA, sinA, cosA)); Transform transform = Transform::interpolateTransforms(transform1, transform2, 0.5); From e7d9e106e92584013b76c4f08269615557687496 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Fri, 24 Jul 2020 07:37:09 +0200 Subject: [PATCH 10/74] Remove unused friend class declaration --- include/reactphysics3d/collision/shapes/CollisionShape.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/reactphysics3d/collision/shapes/CollisionShape.h b/include/reactphysics3d/collision/shapes/CollisionShape.h index 77b56de6..ad7bb3d2 100644 --- a/include/reactphysics3d/collision/shapes/CollisionShape.h +++ b/include/reactphysics3d/collision/shapes/CollisionShape.h @@ -164,7 +164,6 @@ class CollisionShape { friend class Collider; friend class CollisionBody; friend class RigidBody; - friend class PhyscisWorld; friend class BroadPhaseSystem; }; From 92884e2486ee82a5165ce755be12304f6b1ccf4b Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Sun, 26 Jul 2020 16:55:07 +0200 Subject: [PATCH 11/74] Working on NarrowPhaseInfoBatch optimizations --- CMakeLists.txt | 6 - .../collision/ContactPointInfo.h | 16 --- .../narrowphase/CapsuleVsCapsuleAlgorithm.h | 4 +- .../CapsuleVsCapsuleNarrowPhaseInfoBatch.h | 78 ----------- .../CapsuleVsConvexPolyhedronAlgorithm.h | 1 + ...vexPolyhedronVsConvexPolyhedronAlgorithm.h | 1 + .../narrowphase/NarrowPhaseInfoBatch.h | 122 ++++++++++-------- .../collision/narrowphase/NarrowPhaseInput.h | 23 ++-- .../narrowphase/SphereVsCapsuleAlgorithm.h | 4 +- .../SphereVsCapsuleNarrowPhaseInfoBatch.h | 78 ----------- .../SphereVsConvexPolyhedronAlgorithm.h | 1 + .../narrowphase/SphereVsSphereAlgorithm.h | 4 +- .../SphereVsSphereNarrowPhaseInfoBatch.h | 72 ----------- include/reactphysics3d/configuration.h | 29 ++++- .../reactphysics3d/engine/OverlappingPairs.h | 10 +- .../narrowphase/CapsuleVsCapsuleAlgorithm.cpp | 43 +++--- .../CapsuleVsCapsuleNarrowPhaseInfoBatch.cpp | 84 ------------ .../CapsuleVsConvexPolyhedronAlgorithm.cpp | 42 +++--- ...xPolyhedronVsConvexPolyhedronAlgorithm.cpp | 4 +- .../narrowphase/GJK/GJKAlgorithm.cpp | 16 +-- .../narrowphase/NarrowPhaseInfoBatch.cpp | 113 +++++----------- .../narrowphase/NarrowPhaseInput.cpp | 8 +- .../narrowphase/SAT/SATAlgorithm.cpp | 104 +++++++-------- .../narrowphase/SphereVsCapsuleAlgorithm.cpp | 38 +++--- .../SphereVsCapsuleNarrowPhaseInfoBatch.cpp | 88 ------------- .../SphereVsConvexPolyhedronAlgorithm.cpp | 12 +- .../narrowphase/SphereVsSphereAlgorithm.cpp | 32 +++-- .../SphereVsSphereNarrowPhaseInfoBatch.cpp | 77 ----------- src/systems/CollisionDetectionSystem.cpp | 56 ++++---- 29 files changed, 342 insertions(+), 824 deletions(-) delete mode 100644 include/reactphysics3d/collision/narrowphase/CapsuleVsCapsuleNarrowPhaseInfoBatch.h delete mode 100644 include/reactphysics3d/collision/narrowphase/SphereVsCapsuleNarrowPhaseInfoBatch.h delete mode 100644 include/reactphysics3d/collision/narrowphase/SphereVsSphereNarrowPhaseInfoBatch.h delete mode 100644 src/collision/narrowphase/CapsuleVsCapsuleNarrowPhaseInfoBatch.cpp delete mode 100644 src/collision/narrowphase/SphereVsCapsuleNarrowPhaseInfoBatch.cpp delete mode 100644 src/collision/narrowphase/SphereVsSphereNarrowPhaseInfoBatch.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 11cbd844..c6f2e52a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,9 +63,6 @@ set (REACTPHYSICS3D_HEADERS "include/reactphysics3d/collision/narrowphase/ConvexPolyhedronVsConvexPolyhedronAlgorithm.h" "include/reactphysics3d/collision/narrowphase/NarrowPhaseInput.h" "include/reactphysics3d/collision/narrowphase/NarrowPhaseInfoBatch.h" - "include/reactphysics3d/collision/narrowphase/SphereVsSphereNarrowPhaseInfoBatch.h" - "include/reactphysics3d/collision/narrowphase/CapsuleVsCapsuleNarrowPhaseInfoBatch.h" - "include/reactphysics3d/collision/narrowphase/SphereVsCapsuleNarrowPhaseInfoBatch.h" "include/reactphysics3d/collision/shapes/AABB.h" "include/reactphysics3d/collision/shapes/ConvexShape.h" "include/reactphysics3d/collision/shapes/ConvexPolyhedronShape.h" @@ -168,9 +165,6 @@ set (REACTPHYSICS3D_SOURCES "src/collision/narrowphase/ConvexPolyhedronVsConvexPolyhedronAlgorithm.cpp" "src/collision/narrowphase/NarrowPhaseInput.cpp" "src/collision/narrowphase/NarrowPhaseInfoBatch.cpp" - "src/collision/narrowphase/SphereVsSphereNarrowPhaseInfoBatch.cpp" - "src/collision/narrowphase/CapsuleVsCapsuleNarrowPhaseInfoBatch.cpp" - "src/collision/narrowphase/SphereVsCapsuleNarrowPhaseInfoBatch.cpp" "src/collision/shapes/AABB.cpp" "src/collision/shapes/ConvexShape.cpp" "src/collision/shapes/ConvexPolyhedronShape.cpp" diff --git a/include/reactphysics3d/collision/ContactPointInfo.h b/include/reactphysics3d/collision/ContactPointInfo.h index 387f2363..b75b9b97 100644 --- a/include/reactphysics3d/collision/ContactPointInfo.h +++ b/include/reactphysics3d/collision/ContactPointInfo.h @@ -45,10 +45,6 @@ class CollisionBody; */ struct ContactPointInfo { - private: - - // -------------------- Methods -------------------- // - public: // -------------------- Attributes -------------------- // @@ -64,18 +60,6 @@ struct ContactPointInfo { /// Contact point of body 2 in local space of body 2 Vector3 localPoint2; - - // -------------------- Methods -------------------- // - - /// Constructor - ContactPointInfo(const Vector3& contactNormal, decimal penDepth, - const Vector3& localPt1, const Vector3& localPt2) - : normal(contactNormal), penetrationDepth(penDepth), - localPoint1(localPt1), localPoint2(localPt2) { - - assert(contactNormal.lengthSquare() > decimal(0.8)); - assert(penDepth > decimal(0.0)); - } }; } diff --git a/include/reactphysics3d/collision/narrowphase/CapsuleVsCapsuleAlgorithm.h b/include/reactphysics3d/collision/narrowphase/CapsuleVsCapsuleAlgorithm.h index a34d58bf..f065235c 100644 --- a/include/reactphysics3d/collision/narrowphase/CapsuleVsCapsuleAlgorithm.h +++ b/include/reactphysics3d/collision/narrowphase/CapsuleVsCapsuleAlgorithm.h @@ -34,7 +34,7 @@ namespace reactphysics3d { // Declarations -struct CapsuleVsCapsuleNarrowPhaseInfoBatch; +struct NarrowPhaseInfoBatch; class ContactPoint; // Class CapsuleVsCapsuleAlgorithm @@ -66,7 +66,7 @@ class CapsuleVsCapsuleAlgorithm : public NarrowPhaseAlgorithm { CapsuleVsCapsuleAlgorithm& operator=(const CapsuleVsCapsuleAlgorithm& algorithm) = delete; /// Compute the narrow-phase collision detection between two capsules - bool testCollision(CapsuleVsCapsuleNarrowPhaseInfoBatch& narrowPhaseInfoBatch, uint batchStartIndex, + bool testCollision(NarrowPhaseInfoBatch& narrowPhaseInfoBatch, uint batchStartIndex, uint batchNbItems, MemoryAllocator& memoryAllocator); }; diff --git a/include/reactphysics3d/collision/narrowphase/CapsuleVsCapsuleNarrowPhaseInfoBatch.h b/include/reactphysics3d/collision/narrowphase/CapsuleVsCapsuleNarrowPhaseInfoBatch.h deleted file mode 100644 index 2312a2e2..00000000 --- a/include/reactphysics3d/collision/narrowphase/CapsuleVsCapsuleNarrowPhaseInfoBatch.h +++ /dev/null @@ -1,78 +0,0 @@ -/******************************************************************************** -* ReactPhysics3D physics library, http://www.reactphysics3d.com * -* Copyright (c) 2010-2020 Daniel Chappuis * -********************************************************************************* -* * -* This software is provided 'as-is', without any express or implied warranty. * -* In no event will the authors be held liable for any damages arising from the * -* use of this software. * -* * -* Permission is granted to anyone to use this software for any purpose, * -* including commercial applications, and to alter it and redistribute it * -* freely, subject to the following restrictions: * -* * -* 1. The origin of this software must not be misrepresented; you must not claim * -* that you wrote the original software. If you use this software in a * -* product, an acknowledgment in the product documentation would be * -* appreciated but is not required. * -* * -* 2. Altered source versions must be plainly marked as such, and must not be * -* misrepresented as being the original software. * -* * -* 3. This notice may not be removed or altered from any source distribution. * -* * -********************************************************************************/ - -#ifndef REACTPHYSICS3D_CAPSULE_VS_CAPSULE_NARROW_PHASE_INFO_BATCH_H -#define REACTPHYSICS3D_CAPSULE_VS_CAPSULE_NARROW_PHASE_INFO_BATCH_H - -// Libraries -#include - -/// Namespace ReactPhysics3D -namespace reactphysics3d { - -// Struct CapsuleVsCapsuleNarrowPhaseInfoBatch -/** - * This structure collects all the potential collisions from the middle-phase algorithm - * that have to be tested during narrow-phase collision detection. This class collects all the - * capsule vs capsule collision detection tests. - */ -struct CapsuleVsCapsuleNarrowPhaseInfoBatch : public NarrowPhaseInfoBatch { - - public: - - /// List of radiuses for the first capsules - List capsule1Radiuses; - - /// List of radiuses for the second capsules - List capsule2Radiuses; - - /// List of heights for the first capsules - List capsule1Heights; - - /// List of heights for the second capsules - List capsule2Heights; - - /// Constructor - CapsuleVsCapsuleNarrowPhaseInfoBatch(MemoryAllocator& allocator, OverlappingPairs& overlappingPairs); - - /// Destructor - virtual ~CapsuleVsCapsuleNarrowPhaseInfoBatch() override = default; - - /// Add shapes to be tested during narrow-phase collision detection into the batch - virtual void addNarrowPhaseInfo(uint64 pairId, uint64 pairIndex, Entity collider1, Entity collider2, CollisionShape* shape1, - CollisionShape* shape2, const Transform& shape1Transform, - const Transform& shape2Transform, bool needToReportContacts, MemoryAllocator& shapeAllocator) override; - - // Initialize the containers using cached capacity - virtual void reserveMemory() override; - - /// Clear all the objects in the batch - virtual void clear() override; -}; - -} - -#endif - diff --git a/include/reactphysics3d/collision/narrowphase/CapsuleVsConvexPolyhedronAlgorithm.h b/include/reactphysics3d/collision/narrowphase/CapsuleVsConvexPolyhedronAlgorithm.h index 80987bf3..f72e7db2 100644 --- a/include/reactphysics3d/collision/narrowphase/CapsuleVsConvexPolyhedronAlgorithm.h +++ b/include/reactphysics3d/collision/narrowphase/CapsuleVsConvexPolyhedronAlgorithm.h @@ -34,6 +34,7 @@ namespace reactphysics3d { // Declarations class ContactPoint; +struct NarrowPhaseInfoBatch; // Class CapsuleVsConvexPolyhedronAlgorithm /** diff --git a/include/reactphysics3d/collision/narrowphase/ConvexPolyhedronVsConvexPolyhedronAlgorithm.h b/include/reactphysics3d/collision/narrowphase/ConvexPolyhedronVsConvexPolyhedronAlgorithm.h index c3cb6e0d..2b6525e7 100644 --- a/include/reactphysics3d/collision/narrowphase/ConvexPolyhedronVsConvexPolyhedronAlgorithm.h +++ b/include/reactphysics3d/collision/narrowphase/ConvexPolyhedronVsConvexPolyhedronAlgorithm.h @@ -34,6 +34,7 @@ namespace reactphysics3d { // Declarations class ContactPoint; +struct NarrowPhaseInfoBatch; // Class ConvexPolyhedronVsConvexPolyhedronAlgorithm /** diff --git a/include/reactphysics3d/collision/narrowphase/NarrowPhaseInfoBatch.h b/include/reactphysics3d/collision/narrowphase/NarrowPhaseInfoBatch.h index 73ce4e33..06b8e0cc 100644 --- a/include/reactphysics3d/collision/narrowphase/NarrowPhaseInfoBatch.h +++ b/include/reactphysics3d/collision/narrowphase/NarrowPhaseInfoBatch.h @@ -28,6 +28,7 @@ // Libraries #include +#include /// Namespace ReactPhysics3D namespace reactphysics3d { @@ -40,16 +41,68 @@ struct ContactPointInfo; // Struct NarrowPhaseInfoBatch /** - * This abstract structure collects all the potential collisions from the middle-phase algorithm - * that have to be tested during narrow-phase collision detection. There is an implementation of - * this class for each kind of collision detection test. For instance, one for sphere vs sphere, - * one for sphere vs capsule, ... + * This structure collects all the potential collisions from the middle-phase algorithm + * that have to be tested during narrow-phase collision detection. */ struct NarrowPhaseInfoBatch { + struct NarrowPhaseInfo { + + /// Broadphase overlapping pairs ids + uint64 overlappingPairId; + + /// Entity of the first collider to test collision with + Entity colliderEntity1; + + /// Entity of the second collider to test collision with + Entity colliderEntity2; + + /// Collision info of the previous frame + // TODO OPTI : Do we really need to use a pointer here ? why not flat object instead ? + LastFrameCollisionInfo* lastFrameCollisionInfo; + + /// Memory allocator for the collision shape (Used to release TriangleShape memory in destructor) + MemoryAllocator* collisionShapeAllocator; + + /// Shape local to world transform of sphere 1 + Transform shape1ToWorldTransform; + + /// Shape local to world transform of sphere 2 + Transform shape2ToWorldTransform; + + /// Pointer to the first collision shapes to test collision with + CollisionShape* collisionShape1; + + /// Pointer to the second collision shapes to test collision with + CollisionShape* collisionShape2; + + /// True if we need to report contacts (false for triggers for instance) + bool reportContacts; + + /// Result of the narrow-phase collision detection test + bool isColliding; + + /// Number of contact points + uint8 nbContactPoints; + + /// List of contact points created during the narrow-phase + ContactPointInfo contactPoints[NB_MAX_CONTACT_POINTS_IN_NARROWPHASE_INFO]; + + /// Constructor + NarrowPhaseInfo(uint64 pairId, Entity collider1, Entity collider2, LastFrameCollisionInfo* lastFrameInfo, MemoryAllocator& shapeAllocator, + const Transform& shape1ToWorldTransform, const Transform& shape2ToWorldTransform, CollisionShape* shape1, + CollisionShape* shape2, bool needToReportContacts) + : overlappingPairId(pairId), colliderEntity1(collider1), colliderEntity2(collider2), lastFrameCollisionInfo(lastFrameInfo), + collisionShapeAllocator(&shapeAllocator), shape1ToWorldTransform(shape1ToWorldTransform), + shape2ToWorldTransform(shape2ToWorldTransform), collisionShape1(shape1), + collisionShape2(shape2), reportContacts(needToReportContacts), isColliding(false), nbContactPoints(0) { + + } + }; + protected: - /// Memory allocator + /// Reference to the memory allocator MemoryAllocator& mMemoryAllocator; /// Reference to all the broad-phase overlapping pairs @@ -60,73 +113,40 @@ struct NarrowPhaseInfoBatch { public: - /// List of Broadphase overlapping pairs ids - List overlappingPairIds; - - /// List of pointers to the first colliders to test collision with - List colliderEntities1; - - /// List of pointers to the second colliders to test collision with - List colliderEntities2; - - /// List of pointers to the first collision shapes to test collision with - List collisionShapes1; - - /// List of pointers to the second collision shapes to test collision with - List collisionShapes2; - - /// List of transforms that maps from collision shape 1 local-space to world-space - List shape1ToWorldTransforms; - - /// List of transforms that maps from collision shape 2 local-space to world-space - List shape2ToWorldTransforms; - - /// True for each pair of objects that we need to report contacts (false for triggers for instance) - List reportContacts; - - /// Result of the narrow-phase collision detection test - List isColliding; - - /// List of contact points created during the narrow-phase - List> contactPoints; - - /// Memory allocators for the collision shape (Used to release TriangleShape memory in destructor) - List collisionShapeAllocators; - - /// Collision infos of the previous frame - List lastFrameCollisionInfos; + /// For each collision test, we keep some meta data + List narrowPhaseInfos; /// Constructor - NarrowPhaseInfoBatch(MemoryAllocator& allocator, OverlappingPairs& overlappingPairs); + NarrowPhaseInfoBatch(OverlappingPairs& overlappingPairs, MemoryAllocator& allocator); /// Destructor - virtual ~NarrowPhaseInfoBatch(); + ~NarrowPhaseInfoBatch(); + + /// Add shapes to be tested during narrow-phase collision detection into the batch + void addNarrowPhaseInfo(uint64 pairId, uint64 pairIndex, Entity collider1, Entity collider2, CollisionShape* shape1, + CollisionShape* shape2, const Transform& shape1Transform, const Transform& shape2Transform, + bool needToReportContacts, MemoryAllocator& shapeAllocator); /// Return the number of objects in the batch uint getNbObjects() const; - /// Add shapes to be tested during narrow-phase collision detection into the batch - virtual void addNarrowPhaseInfo(uint64 pairId, uint64 pairIndex, Entity collider1, Entity collider2, CollisionShape* shape1, - CollisionShape* shape2, const Transform& shape1Transform, - const Transform& shape2Transform, bool needToReportContacts, MemoryAllocator& shapeAllocator); - /// Add a new contact point - virtual void addContactPoint(uint index, const Vector3& contactNormal, decimal penDepth, + void addContactPoint(uint index, const Vector3& contactNormal, decimal penDepth, const Vector3& localPt1, const Vector3& localPt2); /// Reset the remaining contact points void resetContactPoints(uint index); // Initialize the containers using cached capacity - virtual void reserveMemory(); + void reserveMemory(); /// Clear all the objects in the batch - virtual void clear(); + void clear(); }; /// Return the number of objects in the batch inline uint NarrowPhaseInfoBatch::getNbObjects() const { - return overlappingPairIds.size(); + return narrowPhaseInfos.size(); } } diff --git a/include/reactphysics3d/collision/narrowphase/NarrowPhaseInput.h b/include/reactphysics3d/collision/narrowphase/NarrowPhaseInput.h index 5e8b4d9b..4ad189da 100644 --- a/include/reactphysics3d/collision/narrowphase/NarrowPhaseInput.h +++ b/include/reactphysics3d/collision/narrowphase/NarrowPhaseInput.h @@ -29,9 +29,6 @@ // Libraries #include #include -#include -#include -#include /// Namespace ReactPhysics3D namespace reactphysics3d { @@ -55,9 +52,9 @@ class NarrowPhaseInput { private: - SphereVsSphereNarrowPhaseInfoBatch mSphereVsSphereBatch; - SphereVsCapsuleNarrowPhaseInfoBatch mSphereVsCapsuleBatch; - CapsuleVsCapsuleNarrowPhaseInfoBatch mCapsuleVsCapsuleBatch; + NarrowPhaseInfoBatch mSphereVsSphereBatch; + NarrowPhaseInfoBatch mSphereVsCapsuleBatch; + NarrowPhaseInfoBatch mCapsuleVsCapsuleBatch; NarrowPhaseInfoBatch mSphereVsConvexPolyhedronBatch; NarrowPhaseInfoBatch mCapsuleVsConvexPolyhedronBatch; NarrowPhaseInfoBatch mConvexPolyhedronVsConvexPolyhedronBatch; @@ -74,13 +71,13 @@ class NarrowPhaseInput { MemoryAllocator& shapeAllocator); /// Get a reference to the sphere vs sphere batch - SphereVsSphereNarrowPhaseInfoBatch& getSphereVsSphereBatch(); + NarrowPhaseInfoBatch& getSphereVsSphereBatch(); /// Get a reference to the sphere vs capsule batch - SphereVsCapsuleNarrowPhaseInfoBatch& getSphereVsCapsuleBatch(); + NarrowPhaseInfoBatch& getSphereVsCapsuleBatch(); /// Get a reference to the capsule vs capsule batch - CapsuleVsCapsuleNarrowPhaseInfoBatch& getCapsuleVsCapsuleBatch(); + NarrowPhaseInfoBatch& getCapsuleVsCapsuleBatch(); /// Get a reference to the sphere vs convex polyhedron batch NarrowPhaseInfoBatch& getSphereVsConvexPolyhedronBatch(); @@ -100,17 +97,17 @@ class NarrowPhaseInput { // Get a reference to the sphere vs sphere batch contacts -inline SphereVsSphereNarrowPhaseInfoBatch& NarrowPhaseInput::getSphereVsSphereBatch() { +inline NarrowPhaseInfoBatch& NarrowPhaseInput::getSphereVsSphereBatch() { return mSphereVsSphereBatch; } // Get a reference to the sphere vs capsule batch contacts -inline SphereVsCapsuleNarrowPhaseInfoBatch& NarrowPhaseInput::getSphereVsCapsuleBatch() { +inline NarrowPhaseInfoBatch& NarrowPhaseInput::getSphereVsCapsuleBatch() { return mSphereVsCapsuleBatch; } // Get a reference to the capsule vs capsule batch contacts -inline CapsuleVsCapsuleNarrowPhaseInfoBatch& NarrowPhaseInput::getCapsuleVsCapsuleBatch() { +inline NarrowPhaseInfoBatch& NarrowPhaseInput::getCapsuleVsCapsuleBatch() { return mCapsuleVsCapsuleBatch; } @@ -125,7 +122,7 @@ inline NarrowPhaseInfoBatch& NarrowPhaseInput::getCapsuleVsConvexPolyhedronBatch } // Get a reference to the convex polyhedron vs convex polyhedron batch contacts -inline NarrowPhaseInfoBatch& NarrowPhaseInput::getConvexPolyhedronVsConvexPolyhedronBatch() { +inline NarrowPhaseInfoBatch &NarrowPhaseInput::getConvexPolyhedronVsConvexPolyhedronBatch() { return mConvexPolyhedronVsConvexPolyhedronBatch; } diff --git a/include/reactphysics3d/collision/narrowphase/SphereVsCapsuleAlgorithm.h b/include/reactphysics3d/collision/narrowphase/SphereVsCapsuleAlgorithm.h index 21c85918..49d27a4f 100644 --- a/include/reactphysics3d/collision/narrowphase/SphereVsCapsuleAlgorithm.h +++ b/include/reactphysics3d/collision/narrowphase/SphereVsCapsuleAlgorithm.h @@ -34,7 +34,7 @@ namespace reactphysics3d { // Declarations class ContactPoint; -struct SphereVsCapsuleNarrowPhaseInfoBatch; +struct NarrowPhaseInfoBatch; // Class SphereVsCapsuleAlgorithm /** @@ -65,7 +65,7 @@ class SphereVsCapsuleAlgorithm : public NarrowPhaseAlgorithm { SphereVsCapsuleAlgorithm& operator=(const SphereVsCapsuleAlgorithm& algorithm) = delete; /// Compute the narrow-phase collision detection between a sphere and a capsule - bool testCollision(SphereVsCapsuleNarrowPhaseInfoBatch& narrowPhaseInfoBatch, uint batchStartIndex, + bool testCollision(NarrowPhaseInfoBatch& narrowPhaseInfoBatch, uint batchStartIndex, uint batchNbItems, MemoryAllocator& memoryAllocator); }; diff --git a/include/reactphysics3d/collision/narrowphase/SphereVsCapsuleNarrowPhaseInfoBatch.h b/include/reactphysics3d/collision/narrowphase/SphereVsCapsuleNarrowPhaseInfoBatch.h deleted file mode 100644 index 4841a3cf..00000000 --- a/include/reactphysics3d/collision/narrowphase/SphereVsCapsuleNarrowPhaseInfoBatch.h +++ /dev/null @@ -1,78 +0,0 @@ -/******************************************************************************** -* ReactPhysics3D physics library, http://www.reactphysics3d.com * -* Copyright (c) 2010-2020 Daniel Chappuis * -********************************************************************************* -* * -* This software is provided 'as-is', without any express or implied warranty. * -* In no event will the authors be held liable for any damages arising from the * -* use of this software. * -* * -* Permission is granted to anyone to use this software for any purpose, * -* including commercial applications, and to alter it and redistribute it * -* freely, subject to the following restrictions: * -* * -* 1. The origin of this software must not be misrepresented; you must not claim * -* that you wrote the original software. If you use this software in a * -* product, an acknowledgment in the product documentation would be * -* appreciated but is not required. * -* * -* 2. Altered source versions must be plainly marked as such, and must not be * -* misrepresented as being the original software. * -* * -* 3. This notice may not be removed or altered from any source distribution. * -* * -********************************************************************************/ - -#ifndef REACTPHYSICS3D_SPHERE_VS_CAPSULE_NARROW_PHASE_INFO_BATCH_H -#define REACTPHYSICS3D_SPHERE_VS_CAPSULE_NARROW_PHASE_INFO_BATCH_H - -// Libraries -#include - -/// Namespace ReactPhysics3D -namespace reactphysics3d { - -// Struct SphereVsCapsuleNarrowPhaseInfoBatch -/** - * This structure collects all the potential collisions from the middle-phase algorithm - * that have to be tested during narrow-phase collision detection. This class collects all the - * sphere vs capsule collision detection tests. - */ -struct SphereVsCapsuleNarrowPhaseInfoBatch : public NarrowPhaseInfoBatch { - - public: - - /// List of boolean values to know if the the sphere is the first or second shape - List isSpheresShape1; - - /// List of radiuses for the spheres - List sphereRadiuses; - - /// List of radiuses for the capsules - List capsuleRadiuses; - - /// List of heights for the capsules - List capsuleHeights; - - /// Constructor - SphereVsCapsuleNarrowPhaseInfoBatch(MemoryAllocator& allocator, OverlappingPairs& overlappingPairs); - - /// Destructor - virtual ~SphereVsCapsuleNarrowPhaseInfoBatch() override = default; - - /// Add shapes to be tested during narrow-phase collision detection into the batch - virtual void addNarrowPhaseInfo(uint64 pairId, uint64 pairIndex, Entity collider1, Entity collider2, CollisionShape* shape1, - CollisionShape* shape2, const Transform& shape1Transform, - const Transform& shape2Transform, bool needToReportContacts, MemoryAllocator& shapeAllocator) override; - - // Initialize the containers using cached capacity - virtual void reserveMemory() override; - - /// Clear all the objects in the batch - virtual void clear() override; -}; - -} - -#endif - diff --git a/include/reactphysics3d/collision/narrowphase/SphereVsConvexPolyhedronAlgorithm.h b/include/reactphysics3d/collision/narrowphase/SphereVsConvexPolyhedronAlgorithm.h index 264e9f5a..41a9a87e 100644 --- a/include/reactphysics3d/collision/narrowphase/SphereVsConvexPolyhedronAlgorithm.h +++ b/include/reactphysics3d/collision/narrowphase/SphereVsConvexPolyhedronAlgorithm.h @@ -34,6 +34,7 @@ namespace reactphysics3d { // Declarations class ContactPoint; +struct NarrowPhaseInfoBatch; // Class SphereVsConvexPolyhedronAlgorithm /** diff --git a/include/reactphysics3d/collision/narrowphase/SphereVsSphereAlgorithm.h b/include/reactphysics3d/collision/narrowphase/SphereVsSphereAlgorithm.h index e6f68e7a..85193f52 100644 --- a/include/reactphysics3d/collision/narrowphase/SphereVsSphereAlgorithm.h +++ b/include/reactphysics3d/collision/narrowphase/SphereVsSphereAlgorithm.h @@ -34,7 +34,7 @@ namespace reactphysics3d { // Declarations class ContactPoint; -struct SphereVsSphereNarrowPhaseInfoBatch; +struct NarrowPhaseInfoBatch; // Class SphereVsSphereAlgorithm /** @@ -65,7 +65,7 @@ class SphereVsSphereAlgorithm : public NarrowPhaseAlgorithm { SphereVsSphereAlgorithm& operator=(const SphereVsSphereAlgorithm& algorithm) = delete; /// Compute a contact info if the two bounding volume collide - bool testCollision(SphereVsSphereNarrowPhaseInfoBatch& narrowPhaseInfoBatch, uint batchStartIndex, + bool testCollision(NarrowPhaseInfoBatch& narrowPhaseInfoBatch, uint batchStartIndex, uint batchNbItems, MemoryAllocator& memoryAllocator); }; diff --git a/include/reactphysics3d/collision/narrowphase/SphereVsSphereNarrowPhaseInfoBatch.h b/include/reactphysics3d/collision/narrowphase/SphereVsSphereNarrowPhaseInfoBatch.h deleted file mode 100644 index 63792c71..00000000 --- a/include/reactphysics3d/collision/narrowphase/SphereVsSphereNarrowPhaseInfoBatch.h +++ /dev/null @@ -1,72 +0,0 @@ -/******************************************************************************** -* ReactPhysics3D physics library, http://www.reactphysics3d.com * -* Copyright (c) 2010-2020 Daniel Chappuis * -********************************************************************************* -* * -* This software is provided 'as-is', without any express or implied warranty. * -* In no event will the authors be held liable for any damages arising from the * -* use of this software. * -* * -* Permission is granted to anyone to use this software for any purpose, * -* including commercial applications, and to alter it and redistribute it * -* freely, subject to the following restrictions: * -* * -* 1. The origin of this software must not be misrepresented; you must not claim * -* that you wrote the original software. If you use this software in a * -* product, an acknowledgment in the product documentation would be * -* appreciated but is not required. * -* * -* 2. Altered source versions must be plainly marked as such, and must not be * -* misrepresented as being the original software. * -* * -* 3. This notice may not be removed or altered from any source distribution. * -* * -********************************************************************************/ - -#ifndef REACTPHYSICS3D_SPHERE_VS_SPHERE_NARROW_PHASE_INFO_BATCH_H -#define REACTPHYSICS3D_SPHERE_VS_SPHERE_NARROW_PHASE_INFO_BATCH_H - -// Libraries -#include - -/// Namespace ReactPhysics3D -namespace reactphysics3d { - -// Struct SphereVsSphereNarrowPhaseInfoBatch -/** - * This structure collects all the potential collisions from the middle-phase algorithm - * that have to be tested during narrow-phase collision detection. This class collects all the - * sphere vs sphere collision detection tests. - */ -struct SphereVsSphereNarrowPhaseInfoBatch : public NarrowPhaseInfoBatch { - - public: - - /// List of radiuses for the first spheres - List sphere1Radiuses; - - /// List of radiuses for the second spheres - List sphere2Radiuses; - - /// Constructor - SphereVsSphereNarrowPhaseInfoBatch(MemoryAllocator& allocator, OverlappingPairs& overlappingPairs); - - /// Destructor - virtual ~SphereVsSphereNarrowPhaseInfoBatch() override = default; - - /// Add shapes to be tested during narrow-phase collision detection into the batch - virtual void addNarrowPhaseInfo(uint64 airId, uint64 pairIndex, Entity collider1, Entity collider2, CollisionShape* shape1, - CollisionShape* shape2, const Transform& shape1Transform, - const Transform& shape2Transform, bool needToReportContacts, MemoryAllocator& shapeAllocator) override; - - // Initialize the containers using cached capacity - virtual void reserveMemory() override; - - /// Clear all the objects in the batch - virtual void clear() override; -}; - -} - -#endif - diff --git a/include/reactphysics3d/configuration.h b/include/reactphysics3d/configuration.h index 0fbd2b6a..77832d0d 100644 --- a/include/reactphysics3d/configuration.h +++ b/include/reactphysics3d/configuration.h @@ -35,15 +35,35 @@ #include #include -// Windows platform +// Platforms #if defined(WIN32) ||defined(_WIN32) || defined(_WIN64) ||defined(__WIN32__) || defined(__WINDOWS__) #define WINDOWS_OS -#elif defined(__APPLE__) // Apple platform +#elif defined(__APPLE__) #define APPLE_OS #elif defined(__linux__) || defined(linux) || defined(__linux) // Linux platform #define LINUX_OS #endif +// Compilers +#if defined(_MSC_VER) + #define RP3D_COMPILER_VISUAL_STUDIO +#elif defined(__clang__) + #define RP3D_COMPILER_CLANG +#elif defined(__GNUC__) + #define RP3D_COMPILER_GCC +#else + #define RP3D_COMPILER_UNKNOWN +#endif + +// Force inline macro +#if defined(RP3D_COMPILER_VISUAL_STUDIO) + #define RP3D_FORCE_INLINE __forceinline +#elif defined(RP3D_COMPILER_GCC) || defined(RP3D_COMPILER_CLANG) + #define RP3D_FORCE_INLINE inline __attribute__((always_inline)) +#else + #define RP3D_FORCE_INLINE inline +#endif + /// Namespace reactphysics3d namespace reactphysics3d { @@ -103,6 +123,9 @@ constexpr decimal PI_TIMES_2 = decimal(6.28318530); /// without triggering a large modification of the tree each frame which can be costly constexpr decimal DYNAMIC_TREE_FAT_AABB_INFLATE_PERCENTAGE = decimal(0.08); +/// Maximum number of contact points in a narrow phase info object +constexpr uint8 NB_MAX_CONTACT_POINTS_IN_NARROWPHASE_INFO = 16; + /// Maximum number of contact manifolds in an overlapping pair constexpr uint8 NB_MAX_CONTACT_MANIFOLDS = 3; @@ -110,7 +133,7 @@ constexpr uint8 NB_MAX_CONTACT_MANIFOLDS = 3; constexpr uint8 NB_MAX_POTENTIAL_CONTACT_MANIFOLDS = 4 * NB_MAX_CONTACT_MANIFOLDS; /// Maximum number of contact points in potential contact manifold -constexpr uint8 NB_MAX_CONTACT_POINTS_IN_POTENTIAL_MANIFOLD = 12; +constexpr uint8 NB_MAX_CONTACT_POINTS_IN_POTENTIAL_MANIFOLD = 16; /// Current version of ReactPhysics3D const std::string RP3D_VERSION = std::string("0.8.0"); diff --git a/include/reactphysics3d/engine/OverlappingPairs.h b/include/reactphysics3d/engine/OverlappingPairs.h index 13dfad0c..a3920148 100644 --- a/include/reactphysics3d/engine/OverlappingPairs.h +++ b/include/reactphysics3d/engine/OverlappingPairs.h @@ -54,6 +54,8 @@ class CollisionDispatch; */ struct LastFrameCollisionInfo { + // TODO OPTI : Use bit flags instead of bools here + /// True if we have information about the previous frame bool isValid; @@ -77,9 +79,9 @@ struct LastFrameCollisionInfo { // SAT Algorithm bool satIsAxisFacePolyhedron1; bool satIsAxisFacePolyhedron2; - uint satMinAxisFaceIndex; - uint satMinEdge1Index; - uint satMinEdge2Index; + uint8 satMinAxisFaceIndex; + uint8 satMinEdge1Index; + uint8 satMinEdge2Index; /// Constructor LastFrameCollisionInfo() { @@ -118,7 +120,7 @@ class OverlappingPairs { MemoryAllocator& mPersistentAllocator; /// Memory allocator used to allocated memory for the ContactManifoldInfo and ContactPointInfo - // TODO : Do we need to keep this ? + // TODO OPTI : Do we need to keep this ? MemoryAllocator& mTempMemoryAllocator; /// Current number of components diff --git a/src/collision/narrowphase/CapsuleVsCapsuleAlgorithm.cpp b/src/collision/narrowphase/CapsuleVsCapsuleAlgorithm.cpp index 4fa4595f..aa201ac8 100755 --- a/src/collision/narrowphase/CapsuleVsCapsuleAlgorithm.cpp +++ b/src/collision/narrowphase/CapsuleVsCapsuleAlgorithm.cpp @@ -26,7 +26,7 @@ // Libraries #include #include -#include +#include // We want to use the ReactPhysics3D namespace using namespace reactphysics3d; @@ -34,30 +34,37 @@ using namespace reactphysics3d; // Compute the narrow-phase collision detection between two capsules // This technique is based on the "Robust Contact Creation for Physics Simulations" presentation // by Dirk Gregorius. -bool CapsuleVsCapsuleAlgorithm::testCollision(CapsuleVsCapsuleNarrowPhaseInfoBatch& narrowPhaseInfoBatch, uint batchStartIndex, uint batchNbItems, - MemoryAllocator& memoryAllocator) { +bool CapsuleVsCapsuleAlgorithm::testCollision(NarrowPhaseInfoBatch& narrowPhaseInfoBatch, uint batchStartIndex, uint batchNbItems, MemoryAllocator& memoryAllocator) { bool isCollisionFound = false; for (uint batchIndex = batchStartIndex; batchIndex < batchStartIndex + batchNbItems; batchIndex++) { - assert(narrowPhaseInfoBatch.contactPoints[batchIndex].size() == 0); + assert(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].nbContactPoints == 0); - assert(!narrowPhaseInfoBatch.isColliding[batchIndex]); + assert(!narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].isColliding); // Get the transform from capsule 1 local-space to capsule 2 local-space - const Transform capsule1ToCapsule2SpaceTransform = narrowPhaseInfoBatch.shape2ToWorldTransforms[batchIndex].getInverse() * - narrowPhaseInfoBatch.shape1ToWorldTransforms[batchIndex]; + const Transform capsule1ToCapsule2SpaceTransform = narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape2ToWorldTransform.getInverse() * + narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape1ToWorldTransform; + + const CapsuleShape* capsuleShape1 = static_cast(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1); + const CapsuleShape* capsuleShape2 = static_cast(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape2); + + const decimal capsule1Height = capsuleShape1->getHeight(); + const decimal capsule2Height = capsuleShape2->getHeight(); + const decimal capsule1Radius = capsuleShape1->getRadius(); + const decimal capsule2Radius = capsuleShape2->getRadius(); // Compute the end-points of the inner segment of the first capsule - const decimal capsule1HalfHeight = narrowPhaseInfoBatch.capsule1Heights[batchIndex] * decimal(0.5); + const decimal capsule1HalfHeight = capsule1Height * decimal(0.5); Vector3 capsule1SegA(0, -capsule1HalfHeight, 0); Vector3 capsule1SegB(0, capsule1HalfHeight, 0); capsule1SegA = capsule1ToCapsule2SpaceTransform * capsule1SegA; capsule1SegB = capsule1ToCapsule2SpaceTransform * capsule1SegB; // Compute the end-points of the inner segment of the second capsule - const decimal capsule2HalfHeight = narrowPhaseInfoBatch.capsule2Heights[batchIndex] * decimal(0.5); + const decimal capsule2HalfHeight = capsule2Height * decimal(0.5); const Vector3 capsule2SegA(0, -capsule2HalfHeight, 0); const Vector3 capsule2SegB(0, capsule2HalfHeight, 0); @@ -66,8 +73,6 @@ bool CapsuleVsCapsuleAlgorithm::testCollision(CapsuleVsCapsuleNarrowPhaseInfoBat const Vector3 seg2 = capsule2SegB - capsule2SegA; // Compute the sum of the radius of the two capsules (virtual spheres) - const decimal capsule1Radius = narrowPhaseInfoBatch.capsule1Radiuses[batchIndex]; - const decimal capsule2Radius = narrowPhaseInfoBatch.capsule2Radiuses[batchIndex]; const decimal sumRadius = capsule1Radius + capsule2Radius; // If the two capsules are parallel (we create two contact points) @@ -95,7 +100,7 @@ bool CapsuleVsCapsuleAlgorithm::testCollision(CapsuleVsCapsuleNarrowPhaseInfoBat // If the segments were overlapping (the clip segment is valid) if (t1 > decimal(0.0) && t2 > decimal(0.0)) { - if (narrowPhaseInfoBatch.reportContacts[batchIndex]) { + if (narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].reportContacts) { // Clip the inner segment of capsule 2 if (t1 > decimal(1.0)) t1 = decimal(1.0); @@ -145,14 +150,14 @@ bool CapsuleVsCapsuleAlgorithm::testCollision(CapsuleVsCapsuleNarrowPhaseInfoBat decimal penetrationDepth = sumRadius - segmentsPerpendicularDistance; - const Vector3 normalWorld = narrowPhaseInfoBatch.shape2ToWorldTransforms[batchIndex].getOrientation() * normalCapsule2SpaceNormalized; + const Vector3 normalWorld = narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape2ToWorldTransform.getOrientation() * normalCapsule2SpaceNormalized; // Create the contact info object narrowPhaseInfoBatch.addContactPoint(batchIndex, normalWorld, penetrationDepth, contactPointACapsule1Local, contactPointACapsule2Local); narrowPhaseInfoBatch.addContactPoint(batchIndex, normalWorld, penetrationDepth, contactPointBCapsule1Local, contactPointBCapsule2Local); } - narrowPhaseInfoBatch.isColliding[batchIndex] = true; + narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].isColliding = true; isCollisionFound = true; continue; } @@ -171,7 +176,7 @@ bool CapsuleVsCapsuleAlgorithm::testCollision(CapsuleVsCapsuleNarrowPhaseInfoBat // If the collision shapes overlap if (closestPointsDistanceSquare < sumRadius * sumRadius) { - if (narrowPhaseInfoBatch.reportContacts[batchIndex]) { + if (narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].reportContacts) { // If the distance between the inner segments is not zero if (closestPointsDistanceSquare > MACHINE_EPSILON) { @@ -182,7 +187,7 @@ bool CapsuleVsCapsuleAlgorithm::testCollision(CapsuleVsCapsuleNarrowPhaseInfoBat const Vector3 contactPointCapsule1Local = capsule1ToCapsule2SpaceTransform.getInverse() * (closestPointCapsule1Seg + closestPointsSeg1ToSeg2 * capsule1Radius); const Vector3 contactPointCapsule2Local = closestPointCapsule2Seg - closestPointsSeg1ToSeg2 * capsule2Radius; - const Vector3 normalWorld = narrowPhaseInfoBatch.shape2ToWorldTransforms[batchIndex].getOrientation() * closestPointsSeg1ToSeg2; + const Vector3 normalWorld = narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape2ToWorldTransform.getOrientation() * closestPointsSeg1ToSeg2; decimal penetrationDepth = sumRadius - closestPointsDistance; @@ -205,7 +210,7 @@ bool CapsuleVsCapsuleAlgorithm::testCollision(CapsuleVsCapsuleNarrowPhaseInfoBat const Vector3 contactPointCapsule1Local = capsule1ToCapsule2SpaceTransform.getInverse() * (closestPointCapsule1Seg + normalCapsuleSpace2 * capsule1Radius); const Vector3 contactPointCapsule2Local = closestPointCapsule2Seg - normalCapsuleSpace2 * capsule2Radius; - const Vector3 normalWorld = narrowPhaseInfoBatch.shape2ToWorldTransforms[batchIndex].getOrientation() * normalCapsuleSpace2; + const Vector3 normalWorld = narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape2ToWorldTransform.getOrientation() * normalCapsuleSpace2; // Create the contact info object narrowPhaseInfoBatch.addContactPoint(batchIndex, normalWorld, sumRadius, contactPointCapsule1Local, contactPointCapsule2Local); @@ -221,7 +226,7 @@ bool CapsuleVsCapsuleAlgorithm::testCollision(CapsuleVsCapsuleNarrowPhaseInfoBat const Vector3 contactPointCapsule1Local = capsule1ToCapsule2SpaceTransform.getInverse() * (closestPointCapsule1Seg + normalCapsuleSpace2 * capsule1Radius); const Vector3 contactPointCapsule2Local = closestPointCapsule2Seg - normalCapsuleSpace2 * capsule2Radius; - const Vector3 normalWorld = narrowPhaseInfoBatch.shape2ToWorldTransforms[batchIndex].getOrientation() * normalCapsuleSpace2; + const Vector3 normalWorld = narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape2ToWorldTransform.getOrientation() * normalCapsuleSpace2; // Create the contact info object narrowPhaseInfoBatch.addContactPoint(batchIndex, normalWorld, sumRadius, contactPointCapsule1Local, contactPointCapsule2Local); @@ -229,7 +234,7 @@ bool CapsuleVsCapsuleAlgorithm::testCollision(CapsuleVsCapsuleNarrowPhaseInfoBat } } - narrowPhaseInfoBatch.isColliding[batchIndex] = true; + narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].isColliding = true; isCollisionFound = true; } } diff --git a/src/collision/narrowphase/CapsuleVsCapsuleNarrowPhaseInfoBatch.cpp b/src/collision/narrowphase/CapsuleVsCapsuleNarrowPhaseInfoBatch.cpp deleted file mode 100644 index 6af03018..00000000 --- a/src/collision/narrowphase/CapsuleVsCapsuleNarrowPhaseInfoBatch.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/******************************************************************************** -* ReactPhysics3D physics library, http://www.reactphysics3d.com * -* Copyright (c) 2010-2020 Daniel Chappuis * -********************************************************************************* -* * -* This software is provided 'as-is', without any express or implied warranty. * -* In no event will the authors be held liable for any damages arising from the * -* use of this software. * -* * -* Permission is granted to anyone to use this software for any purpose, * -* including commercial applications, and to alter it and redistribute it * -* freely, subject to the following restrictions: * -* * -* 1. The origin of this software must not be misrepresented; you must not claim * -* that you wrote the original software. If you use this software in a * -* product, an acknowledgment in the product documentation would be * -* appreciated but is not required. * -* * -* 2. Altered source versions must be plainly marked as such, and must not be * -* misrepresented as being the original software. * -* * -* 3. This notice may not be removed or altered from any source distribution. * -* * -********************************************************************************/ - -// Libraries -#include -#include - -using namespace reactphysics3d; - -// Constructor -CapsuleVsCapsuleNarrowPhaseInfoBatch::CapsuleVsCapsuleNarrowPhaseInfoBatch(MemoryAllocator& allocator, - OverlappingPairs& overlappingPairs) - : NarrowPhaseInfoBatch(allocator, overlappingPairs), capsule1Radiuses(allocator), capsule2Radiuses(allocator), - capsule1Heights(allocator), capsule2Heights(allocator) { - -} - -// Add shapes to be tested during narrow-phase collision detection into the batch -void CapsuleVsCapsuleNarrowPhaseInfoBatch::addNarrowPhaseInfo(uint64 pairId, uint64 pairIndex, Entity collider1, Entity collider2, CollisionShape* shape1, CollisionShape* shape2, - const Transform& shape1Transform, const Transform& shape2Transform, bool needToReportContacts, MemoryAllocator &shapeAllocator) { - - NarrowPhaseInfoBatch::addNarrowPhaseInfo(pairId, pairIndex, collider1, collider2, shape1, shape2, shape1Transform, - shape2Transform, needToReportContacts, shapeAllocator); - - assert(shape1->getType() == CollisionShapeType::CAPSULE); - assert(shape2->getType() == CollisionShapeType::CAPSULE); - - const CapsuleShape* capsule1 = static_cast(shape1); - const CapsuleShape* capsule2 = static_cast(shape2); - - capsule1Radiuses.add(capsule1->getRadius()); - capsule2Radiuses.add(capsule2->getRadius()); - capsule1Heights.add(capsule1->getHeight()); - capsule2Heights.add(capsule2->getHeight()); -} - -// Initialize the containers using cached capacity -void CapsuleVsCapsuleNarrowPhaseInfoBatch::reserveMemory() { - - NarrowPhaseInfoBatch::reserveMemory(); - - capsule1Radiuses.reserve(mCachedCapacity); - capsule2Radiuses.reserve(mCachedCapacity); - capsule1Heights.reserve(mCachedCapacity); - capsule2Heights.reserve(mCachedCapacity); -} - -// Clear all the objects in the batch -void CapsuleVsCapsuleNarrowPhaseInfoBatch::clear() { - - // Note that we clear the following containers and we release their allocated memory. Therefore, - // if the memory allocator is a single frame allocator, the memory is deallocated and will be - // allocated in the next frame at a possibly different location in memory (remember that the - // location of the allocated memory of a single frame allocator might change between two frames) - - NarrowPhaseInfoBatch::clear(); - - capsule1Radiuses.clear(true); - capsule2Radiuses.clear(true); - capsule1Heights.clear(true); - capsule2Heights.clear(true); -} diff --git a/src/collision/narrowphase/CapsuleVsConvexPolyhedronAlgorithm.cpp b/src/collision/narrowphase/CapsuleVsConvexPolyhedronAlgorithm.cpp index 8e082822..49dcbc0b 100644 --- a/src/collision/narrowphase/CapsuleVsConvexPolyhedronAlgorithm.cpp +++ b/src/collision/narrowphase/CapsuleVsConvexPolyhedronAlgorithm.cpp @@ -66,21 +66,21 @@ bool CapsuleVsConvexPolyhedronAlgorithm::testCollision(NarrowPhaseInfoBatch& nar for (uint batchIndex = batchStartIndex; batchIndex < batchStartIndex + batchNbItems; batchIndex++) { // Get the last frame collision info - LastFrameCollisionInfo* lastFrameCollisionInfo = narrowPhaseInfoBatch.lastFrameCollisionInfos[batchIndex]; + LastFrameCollisionInfo* lastFrameCollisionInfo = narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].lastFrameCollisionInfo; lastFrameCollisionInfo->wasUsingGJK = true; lastFrameCollisionInfo->wasUsingSAT = false; - assert(narrowPhaseInfoBatch.collisionShapes1[batchIndex]->getType() == CollisionShapeType::CONVEX_POLYHEDRON || - narrowPhaseInfoBatch.collisionShapes2[batchIndex]->getType() == CollisionShapeType::CONVEX_POLYHEDRON); - assert(narrowPhaseInfoBatch.collisionShapes1[batchIndex]->getType() == CollisionShapeType::CAPSULE || - narrowPhaseInfoBatch.collisionShapes2[batchIndex]->getType() == CollisionShapeType::CAPSULE); + assert(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1->getType() == CollisionShapeType::CONVEX_POLYHEDRON || + narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape2->getType() == CollisionShapeType::CONVEX_POLYHEDRON); + assert(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1->getType() == CollisionShapeType::CAPSULE || + narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape2->getType() == CollisionShapeType::CAPSULE); // If we have found a contact point inside the margins (shallow penetration) if (gjkResults[batchIndex] == GJKAlgorithm::GJKResult::COLLIDE_IN_MARGIN) { // If we need to report contacts - if (narrowPhaseInfoBatch.reportContacts[batchIndex]) { + if (narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].reportContacts) { bool noContact = false; @@ -89,20 +89,20 @@ bool CapsuleVsConvexPolyhedronAlgorithm::testCollision(NarrowPhaseInfoBatch& nar // two contact points instead of a single one (as in the deep contact case with SAT algorithm) // Get the contact point created by GJK - assert(narrowPhaseInfoBatch.contactPoints[batchIndex].size() > 0); - ContactPointInfo*& contactPoint = narrowPhaseInfoBatch.contactPoints[batchIndex][0]; + assert(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].nbContactPoints > 0); + ContactPointInfo& contactPoint = narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].contactPoints[0]; - bool isCapsuleShape1 = narrowPhaseInfoBatch.collisionShapes1[batchIndex]->getType() == CollisionShapeType::CAPSULE; + bool isCapsuleShape1 = narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1->getType() == CollisionShapeType::CAPSULE; // Get the collision shapes - const CapsuleShape* capsuleShape = static_cast(isCapsuleShape1 ? narrowPhaseInfoBatch.collisionShapes1[batchIndex] : narrowPhaseInfoBatch.collisionShapes2[batchIndex]); - const ConvexPolyhedronShape* polyhedron = static_cast(isCapsuleShape1 ? narrowPhaseInfoBatch.collisionShapes2[batchIndex] : narrowPhaseInfoBatch.collisionShapes1[batchIndex]); + const CapsuleShape* capsuleShape = static_cast(isCapsuleShape1 ? narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1 : narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape2); + const ConvexPolyhedronShape* polyhedron = static_cast(isCapsuleShape1 ? narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape2 : narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1); // For each face of the polyhedron for (uint f = 0; f < polyhedron->getNbFaces(); f++) { - const Transform polyhedronToWorld = isCapsuleShape1 ? narrowPhaseInfoBatch.shape2ToWorldTransforms[batchIndex] : narrowPhaseInfoBatch.shape1ToWorldTransforms[batchIndex]; - const Transform capsuleToWorld = isCapsuleShape1 ? narrowPhaseInfoBatch.shape1ToWorldTransforms[batchIndex] : narrowPhaseInfoBatch.shape2ToWorldTransforms[batchIndex]; + const Transform polyhedronToWorld = isCapsuleShape1 ? narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape2ToWorldTransform : narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape1ToWorldTransform; + const Transform capsuleToWorld = isCapsuleShape1 ? narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape1ToWorldTransform : narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape2ToWorldTransform; // Get the face normal const Vector3 faceNormal = polyhedron->getFaceNormal(f); @@ -113,18 +113,18 @@ bool CapsuleVsConvexPolyhedronAlgorithm::testCollision(NarrowPhaseInfoBatch& nar Vector3 capsuleInnerSegmentDirection = capsuleToWorld.getOrientation() * (capsuleSegB - capsuleSegA); capsuleInnerSegmentDirection.normalize(); - bool isFaceNormalInDirectionOfContactNormal = faceNormalWorld.dot(contactPoint->normal) > decimal(0.0); + bool isFaceNormalInDirectionOfContactNormal = faceNormalWorld.dot(contactPoint.normal) > decimal(0.0); bool isFaceNormalInContactDirection = (isCapsuleShape1 && !isFaceNormalInDirectionOfContactNormal) || (!isCapsuleShape1 && isFaceNormalInDirectionOfContactNormal); // If the polyhedron face normal is orthogonal to the capsule inner segment and parallel to the contact point normal and the face normal // is in direction of the contact normal (from the polyhedron point of view). if (isFaceNormalInContactDirection && areOrthogonalVectors(faceNormalWorld, capsuleInnerSegmentDirection) - && areParallelVectors(faceNormalWorld, contactPoint->normal)) { + && areParallelVectors(faceNormalWorld, contactPoint.normal)) { // Remove the previous contact point computed by GJK narrowPhaseInfoBatch.resetContactPoints(batchIndex); - const Transform capsuleToWorld = isCapsuleShape1 ? narrowPhaseInfoBatch.shape1ToWorldTransforms[batchIndex] : narrowPhaseInfoBatch.shape2ToWorldTransforms[batchIndex]; + const Transform capsuleToWorld = isCapsuleShape1 ? narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape1ToWorldTransform : narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape2ToWorldTransform; const Transform polyhedronToCapsuleTransform = capsuleToWorld.getInverse() * polyhedronToWorld; // Compute the end-points of the inner segment of the capsule @@ -143,13 +143,13 @@ bool CapsuleVsConvexPolyhedronAlgorithm::testCollision(NarrowPhaseInfoBatch& nar } // Compute and create two contact points - bool contactsFound = satAlgorithm.computeCapsulePolyhedronFaceContactPoints(f, capsuleShape->getRadius(), polyhedron, contactPoint->penetrationDepth, + bool contactsFound = satAlgorithm.computeCapsulePolyhedronFaceContactPoints(f, capsuleShape->getRadius(), polyhedron, contactPoint.penetrationDepth, polyhedronToCapsuleTransform, faceNormalWorld, separatingAxisCapsuleSpace, capsuleSegAPolyhedronSpace, capsuleSegBPolyhedronSpace, narrowPhaseInfoBatch, batchIndex, isCapsuleShape1); if (!contactsFound) { noContact = true; - narrowPhaseInfoBatch.isColliding[batchIndex] = false; + narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].isColliding = false; break; } @@ -166,7 +166,7 @@ bool CapsuleVsConvexPolyhedronAlgorithm::testCollision(NarrowPhaseInfoBatch& nar lastFrameCollisionInfo->wasUsingGJK = false; // Colision found - narrowPhaseInfoBatch.isColliding[batchIndex] = true; + narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].isColliding = true; isCollisionFound = true; continue; } @@ -175,12 +175,12 @@ bool CapsuleVsConvexPolyhedronAlgorithm::testCollision(NarrowPhaseInfoBatch& nar if (gjkResults[batchIndex] == GJKAlgorithm::GJKResult::INTERPENETRATE) { // Run the SAT algorithm to find the separating axis and compute contact point - narrowPhaseInfoBatch.isColliding[batchIndex] = satAlgorithm.testCollisionCapsuleVsConvexPolyhedron(narrowPhaseInfoBatch, batchIndex); + narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].isColliding = satAlgorithm.testCollisionCapsuleVsConvexPolyhedron(narrowPhaseInfoBatch, batchIndex); lastFrameCollisionInfo->wasUsingGJK = false; lastFrameCollisionInfo->wasUsingSAT = true; - if (narrowPhaseInfoBatch.isColliding[batchIndex]) { + if (narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].isColliding) { isCollisionFound = true; } } diff --git a/src/collision/narrowphase/ConvexPolyhedronVsConvexPolyhedronAlgorithm.cpp b/src/collision/narrowphase/ConvexPolyhedronVsConvexPolyhedronAlgorithm.cpp index a44ba93b..9630e975 100644 --- a/src/collision/narrowphase/ConvexPolyhedronVsConvexPolyhedronAlgorithm.cpp +++ b/src/collision/narrowphase/ConvexPolyhedronVsConvexPolyhedronAlgorithm.cpp @@ -35,7 +35,7 @@ using namespace reactphysics3d; // Compute the narrow-phase collision detection between two convex polyhedra // This technique is based on the "Robust Contact Creation for Physics Simulations" presentation // by Dirk Gregorius. -bool ConvexPolyhedronVsConvexPolyhedronAlgorithm::testCollision(NarrowPhaseInfoBatch& narrowPhaseInfoBatch, +bool ConvexPolyhedronVsConvexPolyhedronAlgorithm::testCollision(NarrowPhaseInfoBatch &narrowPhaseInfoBatch, uint batchStartIndex, uint batchNbItems, bool clipWithPreviousAxisIfStillColliding, MemoryAllocator& memoryAllocator) { @@ -54,7 +54,7 @@ bool ConvexPolyhedronVsConvexPolyhedronAlgorithm::testCollision(NarrowPhaseInfoB for (uint batchIndex = batchStartIndex; batchIndex < batchStartIndex + batchNbItems; batchIndex++) { // Get the last frame collision info - LastFrameCollisionInfo* lastFrameCollisionInfo = narrowPhaseInfoBatch.lastFrameCollisionInfos[batchIndex]; + LastFrameCollisionInfo* lastFrameCollisionInfo = narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].lastFrameCollisionInfo; lastFrameCollisionInfo->wasUsingSAT = true; lastFrameCollisionInfo->wasUsingGJK = false; diff --git a/src/collision/narrowphase/GJK/GJKAlgorithm.cpp b/src/collision/narrowphase/GJK/GJKAlgorithm.cpp index 28c3ff0f..f3d664a7 100644 --- a/src/collision/narrowphase/GJK/GJKAlgorithm.cpp +++ b/src/collision/narrowphase/GJK/GJKAlgorithm.cpp @@ -64,15 +64,15 @@ void GJKAlgorithm::testCollision(NarrowPhaseInfoBatch& narrowPhaseInfoBatch, uin decimal prevDistSquare; bool contactFound = false; - assert(narrowPhaseInfoBatch.collisionShapes1[batchIndex]->isConvex()); - assert(narrowPhaseInfoBatch.collisionShapes2[batchIndex]->isConvex()); + assert(narrowPhaseInfoBatch.narrowPhaseInfo[[batchIndex].collisionShape1->isConvex()); + assert(narrowPhaseInfoBatch.narrowPhaseInfo[[batchIndex].collisionShape2->isConvex()); - const ConvexShape* shape1 = static_cast(narrowPhaseInfoBatch.collisionShapes1[batchIndex]); - const ConvexShape* shape2 = static_cast(narrowPhaseInfoBatch.collisionShapes2[batchIndex]); + const ConvexShape* shape1 = static_cast(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1); + const ConvexShape* shape2 = static_cast(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape2); // Get the local-space to world-space transforms - const Transform& transform1 = narrowPhaseInfoBatch.shape1ToWorldTransforms[batchIndex]; - const Transform& transform2 = narrowPhaseInfoBatch.shape2ToWorldTransforms[batchIndex]; + const Transform& transform1 = narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape1ToWorldTransform; + const Transform& transform2 = narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape2ToWorldTransform; // Transform a point from local space of body 2 to local // space of body 1 (the GJK algorithm is done in local space of body 1) @@ -92,7 +92,7 @@ void GJKAlgorithm::testCollision(NarrowPhaseInfoBatch& narrowPhaseInfoBatch, uin VoronoiSimplex simplex; // Get the last collision frame info - LastFrameCollisionInfo* lastFrameCollisionInfo = narrowPhaseInfoBatch.lastFrameCollisionInfos[batchIndex]; + LastFrameCollisionInfo* lastFrameCollisionInfo = narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].lastFrameCollisionInfo; // Get the previous point V (last cached separating axis) Vector3 v; @@ -215,7 +215,7 @@ void GJKAlgorithm::testCollision(NarrowPhaseInfoBatch& narrowPhaseInfoBatch, uin } // If we need to report contacts - if (narrowPhaseInfoBatch.reportContacts[batchIndex]) { + if (narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].reportContacts) { // Compute smooth triangle mesh contact if one of the two collision shapes is a triangle TriangleShape::computeSmoothTriangleMeshContact(shape1, shape2, pA, pB, transform1, transform2, diff --git a/src/collision/narrowphase/NarrowPhaseInfoBatch.cpp b/src/collision/narrowphase/NarrowPhaseInfoBatch.cpp index ff59d32b..754161fd 100644 --- a/src/collision/narrowphase/NarrowPhaseInfoBatch.cpp +++ b/src/collision/narrowphase/NarrowPhaseInfoBatch.cpp @@ -33,12 +33,8 @@ using namespace reactphysics3d; // Constructor -NarrowPhaseInfoBatch::NarrowPhaseInfoBatch(MemoryAllocator& allocator, OverlappingPairs& overlappingPairs) - : mMemoryAllocator(allocator), mOverlappingPairs(overlappingPairs), overlappingPairIds(allocator), - colliderEntities1(allocator), colliderEntities2(allocator), collisionShapes1(allocator), collisionShapes2(allocator), - shape1ToWorldTransforms(allocator), shape2ToWorldTransforms(allocator), reportContacts(allocator), - isColliding(allocator), contactPoints(allocator), collisionShapeAllocators(allocator), - lastFrameCollisionInfos(allocator) { +NarrowPhaseInfoBatch::NarrowPhaseInfoBatch(OverlappingPairs& overlappingPairs, MemoryAllocator& allocator) + : mMemoryAllocator(allocator), mOverlappingPairs(overlappingPairs), narrowPhaseInfos(allocator){ } @@ -48,99 +44,65 @@ NarrowPhaseInfoBatch::~NarrowPhaseInfoBatch() { } // Add shapes to be tested during narrow-phase collision detection into the batch -void NarrowPhaseInfoBatch::addNarrowPhaseInfo(uint64 pairId, uint64 pairIndex, Entity collider1, Entity collider2, CollisionShape* shape1, CollisionShape* shape2, - const Transform& shape1Transform, const Transform& shape2Transform, bool needToReportContacts, - MemoryAllocator& shapeAllocator) { - - overlappingPairIds.add(pairId); - colliderEntities1.add(collider1); - colliderEntities2.add(collider2); - collisionShapes1.add(shape1); - collisionShapes2.add(shape2); - shape1ToWorldTransforms.add(shape1Transform); - shape2ToWorldTransforms.add(shape2Transform); - reportContacts.add(needToReportContacts); - collisionShapeAllocators.add(&shapeAllocator); - contactPoints.add(List(mMemoryAllocator)); - isColliding.add(false); +void NarrowPhaseInfoBatch::addNarrowPhaseInfo(uint64 pairId, uint64 pairIndex, Entity collider1, Entity collider2, CollisionShape* shape1, + CollisionShape* shape2, const Transform& shape1Transform, const Transform& shape2Transform, + bool needToReportContacts, MemoryAllocator& shapeAllocator) { // Add a collision info for the two collision shapes into the overlapping pair (if not present yet) + // TODO OPTI : Can we better manage this LastFrameCollisionInfo* lastFrameInfo = mOverlappingPairs.addLastFrameInfoIfNecessary(pairIndex, shape1->getId(), shape2->getId()); - lastFrameCollisionInfos.add(lastFrameInfo); + + // Create a meta data object + narrowPhaseInfos.emplace(pairId, collider1, collider2, lastFrameInfo, shapeAllocator, shape1Transform, shape2Transform, shape1, shape2, needToReportContacts); } // Add a new contact point -void NarrowPhaseInfoBatch::addContactPoint(uint index, const Vector3& contactNormal, decimal penDepth, - const Vector3& localPt1, const Vector3& localPt2) { +void NarrowPhaseInfoBatch::addContactPoint(uint index, const Vector3& contactNormal, decimal penDepth, const Vector3& localPt1, const Vector3& localPt2) { - assert(reportContacts[index]); assert(penDepth > decimal(0.0)); - // Get the memory allocator - MemoryAllocator& allocator = mOverlappingPairs.getTemporaryAllocator(); + if (narrowPhaseInfos[index].nbContactPoints < NB_MAX_CONTACT_POINTS_IN_NARROWPHASE_INFO) { - // Create the contact point info - ContactPointInfo* contactPointInfo = new (allocator.allocate(sizeof(ContactPointInfo))) - ContactPointInfo(contactNormal, penDepth, localPt1, localPt2); + assert(contactNormal.length() > 0.8f); - // Add it into the list of contact points - contactPoints[index].add(contactPointInfo); + // Add it into the array of contact points + narrowPhaseInfos[index].contactPoints[narrowPhaseInfos[index].nbContactPoints].normal = contactNormal; + narrowPhaseInfos[index].contactPoints[narrowPhaseInfos[index].nbContactPoints].penetrationDepth = penDepth; + narrowPhaseInfos[index].contactPoints[narrowPhaseInfos[index].nbContactPoints].localPoint1 = localPt1; + narrowPhaseInfos[index].contactPoints[narrowPhaseInfos[index].nbContactPoints].localPoint2 = localPt2; + narrowPhaseInfos[index].nbContactPoints++; + } } // Reset the remaining contact points void NarrowPhaseInfoBatch::resetContactPoints(uint index) { - // Get the memory allocator - MemoryAllocator& allocator = mOverlappingPairs.getTemporaryAllocator(); - - // For each remaining contact point info - for (uint i=0; i < contactPoints[index].size(); i++) { - - ContactPointInfo* contactPoint = contactPoints[index][i]; - - // Call the destructor - contactPoint->~ContactPointInfo(); - - // Delete the current element - allocator.release(contactPoint, sizeof(ContactPointInfo)); - } - - contactPoints[index].clear(); + narrowPhaseInfos[index].nbContactPoints = 0; } // Initialize the containers using cached capacity void NarrowPhaseInfoBatch::reserveMemory() { - overlappingPairIds.reserve(mCachedCapacity); - colliderEntities1.reserve(mCachedCapacity); - colliderEntities2.reserve(mCachedCapacity); - collisionShapes1.reserve(mCachedCapacity); - collisionShapes2.reserve(mCachedCapacity); - shape1ToWorldTransforms.reserve(mCachedCapacity); - shape2ToWorldTransforms.reserve(mCachedCapacity); - reportContacts.reserve(mCachedCapacity); - collisionShapeAllocators.reserve(mCachedCapacity); - lastFrameCollisionInfos.reserve(mCachedCapacity); - isColliding.reserve(mCachedCapacity); - contactPoints.reserve(mCachedCapacity); + narrowPhaseInfos.reserve(mCachedCapacity); } // Clear all the objects in the batch void NarrowPhaseInfoBatch::clear() { - for (uint i=0; i < overlappingPairIds.size(); i++) { + // TODO OPTI : Better manage this + for (uint i=0; i < narrowPhaseInfos.size(); i++) { - assert(contactPoints[i].size() == 0); + assert(narrowPhaseOutputInfos[i].nbContactPoints == 0); // Release the memory of the TriangleShape (this memory was allocated in the // MiddlePhaseTriangleCallback::testTriangle() method) - if (collisionShapes1.size() > 0 && collisionShapes1[i]->getName() == CollisionShapeName::TRIANGLE) { - collisionShapes1[i]->~CollisionShape(); - collisionShapeAllocators[i]->release(collisionShapes1[i], sizeof(TriangleShape)); + if (narrowPhaseInfos[i].collisionShape1->getName() == CollisionShapeName::TRIANGLE) { + narrowPhaseInfos[i].collisionShape1->~CollisionShape(); + narrowPhaseInfos[i].collisionShapeAllocator->release(narrowPhaseInfos[i].collisionShape1, sizeof(TriangleShape)); } - if (collisionShapes2.size() > 0 && collisionShapes2[i]->getName() == CollisionShapeName::TRIANGLE) { - collisionShapes2[i]->~CollisionShape(); - collisionShapeAllocators[i]->release(collisionShapes2[i], sizeof(TriangleShape)); + if (narrowPhaseInfos[i].collisionShape2->getName() == CollisionShapeName::TRIANGLE) { + narrowPhaseInfos[i].collisionShape2->~CollisionShape(); + narrowPhaseInfos[i].collisionShapeAllocator->release(narrowPhaseInfos[i].collisionShape2, sizeof(TriangleShape)); } } @@ -149,18 +111,7 @@ void NarrowPhaseInfoBatch::clear() { // allocated in the next frame at a possibly different location in memory (remember that the // location of the allocated memory of a single frame allocator might change between two frames) - mCachedCapacity = overlappingPairIds.size(); + mCachedCapacity = narrowPhaseInfos.size(); - overlappingPairIds.clear(true); - colliderEntities1.clear(true); - colliderEntities2.clear(true); - collisionShapes1.clear(true); - collisionShapes2.clear(true); - shape1ToWorldTransforms.clear(true); - shape2ToWorldTransforms.clear(true); - reportContacts.clear(true); - collisionShapeAllocators.clear(true); - lastFrameCollisionInfos.clear(true); - isColliding.clear(true); - contactPoints.clear(true); + narrowPhaseInfos.clear(true); } diff --git a/src/collision/narrowphase/NarrowPhaseInput.cpp b/src/collision/narrowphase/NarrowPhaseInput.cpp index 767fd3c2..454717f3 100644 --- a/src/collision/narrowphase/NarrowPhaseInput.cpp +++ b/src/collision/narrowphase/NarrowPhaseInput.cpp @@ -31,10 +31,10 @@ using namespace reactphysics3d; /// Constructor NarrowPhaseInput::NarrowPhaseInput(MemoryAllocator& allocator, OverlappingPairs& overlappingPairs) - :mSphereVsSphereBatch(allocator, overlappingPairs), mSphereVsCapsuleBatch(allocator, overlappingPairs), - mCapsuleVsCapsuleBatch(allocator, overlappingPairs), mSphereVsConvexPolyhedronBatch(allocator, overlappingPairs), - mCapsuleVsConvexPolyhedronBatch(allocator, overlappingPairs), - mConvexPolyhedronVsConvexPolyhedronBatch(allocator, overlappingPairs) { + :mSphereVsSphereBatch(overlappingPairs, allocator), mSphereVsCapsuleBatch(overlappingPairs, allocator), + mCapsuleVsCapsuleBatch(overlappingPairs, allocator), mSphereVsConvexPolyhedronBatch(overlappingPairs, allocator), + mCapsuleVsConvexPolyhedronBatch(overlappingPairs, allocator), + mConvexPolyhedronVsConvexPolyhedronBatch(overlappingPairs, allocator) { } diff --git a/src/collision/narrowphase/SAT/SATAlgorithm.cpp b/src/collision/narrowphase/SAT/SATAlgorithm.cpp index 4271b900..22118b78 100644 --- a/src/collision/narrowphase/SAT/SATAlgorithm.cpp +++ b/src/collision/narrowphase/SAT/SATAlgorithm.cpp @@ -64,19 +64,19 @@ bool SATAlgorithm::testCollisionSphereVsConvexPolyhedron(NarrowPhaseInfoBatch& n for (uint batchIndex = batchStartIndex; batchIndex < batchStartIndex + batchNbItems; batchIndex++) { - bool isSphereShape1 = narrowPhaseInfoBatch.collisionShapes1[batchIndex]->getType() == CollisionShapeType::SPHERE; + bool isSphereShape1 = narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1->getType() == CollisionShapeType::SPHERE; - assert(narrowPhaseInfoBatch.collisionShapes1[batchIndex]->getType() == CollisionShapeType::CONVEX_POLYHEDRON || - narrowPhaseInfoBatch.collisionShapes2[batchIndex]->getType() == CollisionShapeType::CONVEX_POLYHEDRON); - assert(narrowPhaseInfoBatch.collisionShapes1[batchIndex]->getType() == CollisionShapeType::SPHERE || - narrowPhaseInfoBatch.collisionShapes2[batchIndex]->getType() == CollisionShapeType::SPHERE); + assert(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1->getType() == CollisionShapeType::CONVEX_POLYHEDRON || + narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape2->getType() == CollisionShapeType::CONVEX_POLYHEDRON); + assert(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1->getType() == CollisionShapeType::SPHERE || + narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape2->getType() == CollisionShapeType::SPHERE); // Get the capsule collision shapes - const SphereShape* sphere = static_cast(isSphereShape1 ? narrowPhaseInfoBatch.collisionShapes1[batchIndex] : narrowPhaseInfoBatch.collisionShapes2[batchIndex]); - const ConvexPolyhedronShape* polyhedron = static_cast(isSphereShape1 ? narrowPhaseInfoBatch.collisionShapes2[batchIndex] : narrowPhaseInfoBatch.collisionShapes1[batchIndex]); + const SphereShape* sphere = static_cast(isSphereShape1 ? narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1 : narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape2); + const ConvexPolyhedronShape* polyhedron = static_cast(isSphereShape1 ? narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape2 : narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1); - const Transform& sphereToWorldTransform = isSphereShape1 ? narrowPhaseInfoBatch.shape1ToWorldTransforms[batchIndex] : narrowPhaseInfoBatch.shape2ToWorldTransforms[batchIndex]; - const Transform& polyhedronToWorldTransform = isSphereShape1 ? narrowPhaseInfoBatch.shape2ToWorldTransforms[batchIndex] : narrowPhaseInfoBatch.shape1ToWorldTransforms[batchIndex]; + const Transform& sphereToWorldTransform = isSphereShape1 ? narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape1ToWorldTransform : narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape2ToWorldTransform; + const Transform& polyhedronToWorldTransform = isSphereShape1 ? narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape2ToWorldTransform : narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape1ToWorldTransform; // Get the transform from sphere local-space to polyhedron local-space const Transform worldToPolyhedronTransform = polyhedronToWorldTransform.getInverse(); @@ -115,7 +115,7 @@ bool SATAlgorithm::testCollisionSphereVsConvexPolyhedron(NarrowPhaseInfoBatch& n } // If we need to report contacts - if (narrowPhaseInfoBatch.reportContacts[batchIndex]) { + if (narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].reportContacts) { const Vector3 minFaceNormal = polyhedron->getFaceNormal(minFaceIndex); Vector3 minFaceNormalWorld = polyhedronToWorldTransform.getOrientation() * minFaceNormal; @@ -125,10 +125,10 @@ bool SATAlgorithm::testCollisionSphereVsConvexPolyhedron(NarrowPhaseInfoBatch& n Vector3 normalWorld = isSphereShape1 ? -minFaceNormalWorld : minFaceNormalWorld; // Compute smooth triangle mesh contact if one of the two collision shapes is a triangle - TriangleShape::computeSmoothTriangleMeshContact(narrowPhaseInfoBatch.collisionShapes1[batchIndex], narrowPhaseInfoBatch.collisionShapes2[batchIndex], + TriangleShape::computeSmoothTriangleMeshContact(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1, narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape2, isSphereShape1 ? contactPointSphereLocal : contactPointPolyhedronLocal, isSphereShape1 ? contactPointPolyhedronLocal : contactPointSphereLocal, - narrowPhaseInfoBatch.shape1ToWorldTransforms[batchIndex], narrowPhaseInfoBatch.shape2ToWorldTransforms[batchIndex], + narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape1ToWorldTransform, narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape2ToWorldTransform, minPenetrationDepth, normalWorld); // Create the contact info object @@ -137,7 +137,7 @@ bool SATAlgorithm::testCollisionSphereVsConvexPolyhedron(NarrowPhaseInfoBatch& n isSphereShape1 ? contactPointPolyhedronLocal : contactPointSphereLocal); } - narrowPhaseInfoBatch.isColliding[batchIndex] = true; + narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].isColliding = true; isCollisionFound = true; } @@ -167,19 +167,19 @@ bool SATAlgorithm::testCollisionCapsuleVsConvexPolyhedron(NarrowPhaseInfoBatch& RP3D_PROFILE("SATAlgorithm::testCollisionCapsuleVsConvexPolyhedron()", mProfiler); - bool isCapsuleShape1 = narrowPhaseInfoBatch.collisionShapes1[batchIndex]->getType() == CollisionShapeType::CAPSULE; + bool isCapsuleShape1 = narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1->getType() == CollisionShapeType::CAPSULE; - assert(narrowPhaseInfoBatch.collisionShapes1[batchIndex]->getType() == CollisionShapeType::CONVEX_POLYHEDRON || - narrowPhaseInfoBatch.collisionShapes2[batchIndex]->getType() == CollisionShapeType::CONVEX_POLYHEDRON); - assert(narrowPhaseInfoBatch.collisionShapes1[batchIndex]->getType() == CollisionShapeType::CAPSULE || - narrowPhaseInfoBatch.collisionShapes2[batchIndex]->getType() == CollisionShapeType::CAPSULE); + assert(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1->getType() == CollisionShapeType::CONVEX_POLYHEDRON || + narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape2->getType() == CollisionShapeType::CONVEX_POLYHEDRON); + assert(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1->getType() == CollisionShapeType::CAPSULE || + narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape2->getType() == CollisionShapeType::CAPSULE); // Get the collision shapes - const CapsuleShape* capsuleShape = static_cast(isCapsuleShape1 ? narrowPhaseInfoBatch.collisionShapes1[batchIndex] : narrowPhaseInfoBatch.collisionShapes2[batchIndex]); - const ConvexPolyhedronShape* polyhedron = static_cast(isCapsuleShape1 ? narrowPhaseInfoBatch.collisionShapes2[batchIndex] : narrowPhaseInfoBatch.collisionShapes1[batchIndex]); + const CapsuleShape* capsuleShape = static_cast(isCapsuleShape1 ? narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1 : narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape2); + const ConvexPolyhedronShape* polyhedron = static_cast(isCapsuleShape1 ? narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape2 : narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1); - const Transform capsuleToWorld = isCapsuleShape1 ? narrowPhaseInfoBatch.shape1ToWorldTransforms[batchIndex] : narrowPhaseInfoBatch.shape2ToWorldTransforms[batchIndex]; - const Transform polyhedronToWorld = isCapsuleShape1 ? narrowPhaseInfoBatch.shape2ToWorldTransforms[batchIndex] : narrowPhaseInfoBatch.shape1ToWorldTransforms[batchIndex]; + const Transform capsuleToWorld = isCapsuleShape1 ? narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape1ToWorldTransform : narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape2ToWorldTransform; + const Transform polyhedronToWorld = isCapsuleShape1 ? narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape2ToWorldTransform : narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape1ToWorldTransform; const Transform polyhedronToCapsuleTransform = capsuleToWorld.getInverse() * polyhedronToWorld; @@ -279,7 +279,7 @@ bool SATAlgorithm::testCollisionCapsuleVsConvexPolyhedron(NarrowPhaseInfoBatch& if (isMinPenetrationFaceNormal) { // If we need to report contacts - if (narrowPhaseInfoBatch.reportContacts[batchIndex]) { + if (narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].reportContacts) { return computeCapsulePolyhedronFaceContactPoints(minFaceIndex, capsuleRadius, polyhedron, minPenetrationDepth, polyhedronToCapsuleTransform, normalWorld, separatingAxisCapsuleSpace, @@ -290,7 +290,7 @@ bool SATAlgorithm::testCollisionCapsuleVsConvexPolyhedron(NarrowPhaseInfoBatch& else { // The separating axis is the cross product of a polyhedron edge and the inner capsule segment // If we need to report contacts - if (narrowPhaseInfoBatch.reportContacts[batchIndex]) { + if (narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].reportContacts) { // Compute the closest points between the inner capsule segment and the // edge of the polyhedron in polyhedron local-space @@ -303,10 +303,10 @@ bool SATAlgorithm::testCollisionCapsuleVsConvexPolyhedron(NarrowPhaseInfoBatch& Vector3 contactPointCapsule = (polyhedronToCapsuleTransform * closestPointCapsuleInnerSegment) - separatingAxisCapsuleSpace * capsuleRadius; // Compute smooth triangle mesh contact if one of the two collision shapes is a triangle - TriangleShape::computeSmoothTriangleMeshContact(narrowPhaseInfoBatch.collisionShapes1[batchIndex], narrowPhaseInfoBatch.collisionShapes2[batchIndex], + TriangleShape::computeSmoothTriangleMeshContact(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1, narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape2, isCapsuleShape1 ? contactPointCapsule : closestPointPolyhedronEdge, isCapsuleShape1 ? closestPointPolyhedronEdge : contactPointCapsule, - narrowPhaseInfoBatch.shape1ToWorldTransforms[batchIndex], narrowPhaseInfoBatch.shape2ToWorldTransforms[batchIndex], + narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape1ToWorldTransform, narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape2ToWorldTransform, minPenetrationDepth, normalWorld); // Create the contact point @@ -448,10 +448,10 @@ bool SATAlgorithm::computeCapsulePolyhedronFaceContactPoints(uint referenceFaceI // Compute smooth triangle mesh contact if one of the two collision shapes is a triangle - TriangleShape::computeSmoothTriangleMeshContact(narrowPhaseInfoBatch.collisionShapes1[batchIndex], narrowPhaseInfoBatch.collisionShapes2[batchIndex], + TriangleShape::computeSmoothTriangleMeshContact(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1, narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape2, isCapsuleShape1 ? contactPointCapsule : contactPointPolyhedron, isCapsuleShape1 ? contactPointPolyhedron : contactPointCapsule, - narrowPhaseInfoBatch.shape1ToWorldTransforms[batchIndex], narrowPhaseInfoBatch.shape2ToWorldTransforms[batchIndex], + narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape1ToWorldTransform, narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape2ToWorldTransform, penetrationDepth, normalWorld); @@ -486,14 +486,14 @@ bool SATAlgorithm::testCollisionConvexPolyhedronVsConvexPolyhedron(NarrowPhaseIn for (uint batchIndex = batchStartIndex; batchIndex < batchStartIndex + batchNbItems; batchIndex++) { - assert(narrowPhaseInfoBatch.collisionShapes1[batchIndex]->getType() == CollisionShapeType::CONVEX_POLYHEDRON); - assert(narrowPhaseInfoBatch.collisionShapes2[batchIndex]->getType() == CollisionShapeType::CONVEX_POLYHEDRON); - assert(narrowPhaseInfoBatch.contactPoints[batchIndex].size() == 0); + assert(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1->getType() == CollisionShapeType::CONVEX_POLYHEDRON); + assert(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape2->getType() == CollisionShapeType::CONVEX_POLYHEDRON); + assert(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].nbContactPoints == 0); - const ConvexPolyhedronShape* polyhedron1 = static_cast(narrowPhaseInfoBatch.collisionShapes1[batchIndex]); - const ConvexPolyhedronShape* polyhedron2 = static_cast(narrowPhaseInfoBatch.collisionShapes2[batchIndex]); + const ConvexPolyhedronShape* polyhedron1 = static_cast(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1); + const ConvexPolyhedronShape* polyhedron2 = static_cast(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape2); - const Transform polyhedron1ToPolyhedron2 = narrowPhaseInfoBatch.shape2ToWorldTransforms[batchIndex].getInverse() * narrowPhaseInfoBatch.shape1ToWorldTransforms[batchIndex]; + const Transform polyhedron1ToPolyhedron2 = narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape2ToWorldTransform.getInverse() * narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape1ToWorldTransform; const Transform polyhedron2ToPolyhedron1 = polyhedron1ToPolyhedron2.getInverse(); decimal minPenetrationDepth = DECIMAL_LARGEST; @@ -507,7 +507,7 @@ bool SATAlgorithm::testCollisionConvexPolyhedronVsConvexPolyhedron(NarrowPhaseIn Vector3 minEdgeVsEdgeSeparatingAxisPolyhedron2Space; const bool isShape1Triangle = polyhedron1->getName() == CollisionShapeName::TRIANGLE; - LastFrameCollisionInfo* lastFrameCollisionInfo = narrowPhaseInfoBatch.lastFrameCollisionInfos[batchIndex]; + LastFrameCollisionInfo* lastFrameCollisionInfo = narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].lastFrameCollisionInfo; // If the last frame collision info is valid and was also using SAT algorithm if (lastFrameCollisionInfo->isValid && lastFrameCollisionInfo->wasUsingSAT) { @@ -549,7 +549,7 @@ bool SATAlgorithm::testCollisionConvexPolyhedronVsConvexPolyhedron(NarrowPhaseIn // The shapes are still overlapping in the previous axis (the contact manifold is not empty). // Therefore, we can return without running the whole SAT algorithm - narrowPhaseInfoBatch.isColliding[batchIndex] = true; + narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].isColliding = true; isCollisionFound = true; continue; } @@ -589,7 +589,7 @@ bool SATAlgorithm::testCollisionConvexPolyhedronVsConvexPolyhedron(NarrowPhaseIn // The shapes are still overlapping in the previous axis (the contact manifold is not empty). // Therefore, we can return without running the whole SAT algorithm - narrowPhaseInfoBatch.isColliding[batchIndex] = true; + narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].isColliding = true; isCollisionFound = true; continue; } @@ -651,18 +651,18 @@ bool SATAlgorithm::testCollisionConvexPolyhedronVsConvexPolyhedron(NarrowPhaseIn if (t1 >= decimal(0.0) && t1 <= decimal(1) && t2 >= decimal(0.0) && t2 <= decimal(1.0)) { // If we need to report contact points - if (narrowPhaseInfoBatch.reportContacts[batchIndex]) { + if (narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].reportContacts) { // Compute the contact point on polyhedron 1 edge in the local-space of polyhedron 1 Vector3 closestPointPolyhedron1EdgeLocalSpace = polyhedron2ToPolyhedron1 * closestPointPolyhedron1Edge; // Compute the world normal - Vector3 normalWorld = narrowPhaseInfoBatch.shape2ToWorldTransforms[batchIndex].getOrientation() * separatingAxisPolyhedron2Space; + Vector3 normalWorld = narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape2ToWorldTransform.getOrientation() * separatingAxisPolyhedron2Space; // Compute smooth triangle mesh contact if one of the two collision shapes is a triangle - TriangleShape::computeSmoothTriangleMeshContact(narrowPhaseInfoBatch.collisionShapes1[batchIndex], narrowPhaseInfoBatch.collisionShapes2[batchIndex], + TriangleShape::computeSmoothTriangleMeshContact(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1, narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape2, closestPointPolyhedron1EdgeLocalSpace, closestPointPolyhedron2Edge, - narrowPhaseInfoBatch.shape1ToWorldTransforms[batchIndex], narrowPhaseInfoBatch.shape2ToWorldTransforms[batchIndex], + narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape1ToWorldTransform, narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape2ToWorldTransform, penetrationDepth, normalWorld); // Create the contact point @@ -673,7 +673,7 @@ bool SATAlgorithm::testCollisionConvexPolyhedronVsConvexPolyhedron(NarrowPhaseIn // The shapes are overlapping on the previous axis (the contact manifold is not empty). Therefore // we return without running the whole SAT algorithm - narrowPhaseInfoBatch.isColliding[batchIndex] = true; + narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].isColliding = true; isCollisionFound = true; continue; } @@ -848,7 +848,7 @@ bool SATAlgorithm::testCollisionConvexPolyhedronVsConvexPolyhedron(NarrowPhaseIn else { // If we have an edge vs edge contact // If we need to report contacts - if (narrowPhaseInfoBatch.reportContacts[batchIndex]) { + if (narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].reportContacts) { // Compute the closest points between the two edges (in the local-space of poylhedron 2) Vector3 closestPointPolyhedron1Edge, closestPointPolyhedron2Edge; @@ -859,12 +859,12 @@ bool SATAlgorithm::testCollisionConvexPolyhedronVsConvexPolyhedron(NarrowPhaseIn Vector3 closestPointPolyhedron1EdgeLocalSpace = polyhedron2ToPolyhedron1 * closestPointPolyhedron1Edge; // Compute the world normal - Vector3 normalWorld = narrowPhaseInfoBatch.shape2ToWorldTransforms[batchIndex].getOrientation() * minEdgeVsEdgeSeparatingAxisPolyhedron2Space; + Vector3 normalWorld = narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape2ToWorldTransform.getOrientation() * minEdgeVsEdgeSeparatingAxisPolyhedron2Space; // Compute smooth triangle mesh contact if one of the two collision shapes is a triangle - TriangleShape::computeSmoothTriangleMeshContact(narrowPhaseInfoBatch.collisionShapes1[batchIndex], narrowPhaseInfoBatch.collisionShapes2[batchIndex], + TriangleShape::computeSmoothTriangleMeshContact(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1, narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape2, closestPointPolyhedron1EdgeLocalSpace, closestPointPolyhedron2Edge, - narrowPhaseInfoBatch.shape1ToWorldTransforms[batchIndex], narrowPhaseInfoBatch.shape2ToWorldTransforms[batchIndex], + narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape1ToWorldTransform, narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape2ToWorldTransform, minPenetrationDepth, normalWorld); // Create the contact point @@ -878,7 +878,7 @@ bool SATAlgorithm::testCollisionConvexPolyhedronVsConvexPolyhedron(NarrowPhaseIn lastFrameCollisionInfo->satMinEdge2Index = minSeparatingEdge2Index; } - narrowPhaseInfoBatch.isColliding[batchIndex] = true; + narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].isColliding = true; isCollisionFound = true; } @@ -903,8 +903,8 @@ bool SATAlgorithm::computePolyhedronVsPolyhedronFaceContactPoints(bool isMinPene const Vector3 axisIncidentSpace = referenceToIncidentTransform.getOrientation() * axisReferenceSpace; // Compute the world normal - Vector3 normalWorld = isMinPenetrationFaceNormalPolyhedron1 ? narrowPhaseInfoBatch.shape1ToWorldTransforms[batchIndex].getOrientation() * axisReferenceSpace : - -(narrowPhaseInfoBatch.shape2ToWorldTransforms[batchIndex].getOrientation() * axisReferenceSpace); + Vector3 normalWorld = isMinPenetrationFaceNormalPolyhedron1 ? narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape1ToWorldTransform.getOrientation() * axisReferenceSpace : + -(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape2ToWorldTransform.getOrientation() * axisReferenceSpace); // Get the reference face const HalfEdgeStructure::Face& referenceFace = referencePolyhedron->getFace(minFaceIndex); @@ -975,7 +975,7 @@ bool SATAlgorithm::computePolyhedronVsPolyhedronFaceContactPoints(bool isMinPene contactPointsFound = true; // If we need to report contacts - if (narrowPhaseInfoBatch.reportContacts[batchIndex]) { + if (narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].reportContacts) { Vector3 outWorldNormal = normalWorld; @@ -986,10 +986,10 @@ bool SATAlgorithm::computePolyhedronVsPolyhedronFaceContactPoints(bool isMinPene Vector3 contactPointReferencePolyhedron = projectPointOntoPlane(clipPolygonVertices[i], axisReferenceSpace, referenceFaceVertex); // Compute smooth triangle mesh contact if one of the two collision shapes is a triangle - TriangleShape::computeSmoothTriangleMeshContact(narrowPhaseInfoBatch.collisionShapes1[batchIndex], narrowPhaseInfoBatch.collisionShapes2[batchIndex], + TriangleShape::computeSmoothTriangleMeshContact(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1, narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape2, isMinPenetrationFaceNormalPolyhedron1 ? contactPointReferencePolyhedron : contactPointIncidentPolyhedron, isMinPenetrationFaceNormalPolyhedron1 ? contactPointIncidentPolyhedron : contactPointReferencePolyhedron, - narrowPhaseInfoBatch.shape1ToWorldTransforms[batchIndex], narrowPhaseInfoBatch.shape2ToWorldTransforms[batchIndex], + narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape1ToWorldTransform, narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape2ToWorldTransform, penetrationDepth, outWorldNormal); // Create a new contact point diff --git a/src/collision/narrowphase/SphereVsCapsuleAlgorithm.cpp b/src/collision/narrowphase/SphereVsCapsuleAlgorithm.cpp index a441cbca..d839f7cb 100755 --- a/src/collision/narrowphase/SphereVsCapsuleAlgorithm.cpp +++ b/src/collision/narrowphase/SphereVsCapsuleAlgorithm.cpp @@ -27,7 +27,7 @@ #include #include #include -#include +#include // We want to use the ReactPhysics3D namespace using namespace reactphysics3d; @@ -35,21 +35,27 @@ using namespace reactphysics3d; // Compute the narrow-phase collision detection between a sphere and a capsule // This technique is based on the "Robust Contact Creation for Physics Simulations" presentation // by Dirk Gregorius. -bool SphereVsCapsuleAlgorithm::testCollision(SphereVsCapsuleNarrowPhaseInfoBatch& narrowPhaseInfoBatch, uint batchStartIndex, uint batchNbItems, - MemoryAllocator& memoryAllocator) { +bool SphereVsCapsuleAlgorithm::testCollision(NarrowPhaseInfoBatch& narrowPhaseInfoBatch, uint batchStartIndex, uint batchNbItems, MemoryAllocator& memoryAllocator) { bool isCollisionFound = false; for (uint batchIndex = batchStartIndex; batchIndex < batchStartIndex + batchNbItems; batchIndex++) { - assert(!narrowPhaseInfoBatch.isColliding[batchIndex]); - assert(narrowPhaseInfoBatch.contactPoints[batchIndex].size() == 0); + assert(!narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].isColliding); + assert(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].nbContactPoints == 0); - const bool isSphereShape1 = narrowPhaseInfoBatch.isSpheresShape1[batchIndex]; + const bool isSphereShape1 = narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1->getType() == CollisionShapeType::SPHERE; + + const SphereShape* sphereShape = static_cast(isSphereShape1 ? narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1 : narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape2); + const CapsuleShape* capsuleShape = static_cast(isSphereShape1 ? narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape2 : narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1); + + const decimal capsuleHeight = capsuleShape->getHeight(); + const decimal sphereRadius = sphereShape->getRadius(); + const decimal capsuleRadius = capsuleShape->getRadius(); // Get the transform from sphere local-space to capsule local-space - const Transform& sphereToWorldTransform = isSphereShape1 ? narrowPhaseInfoBatch.shape1ToWorldTransforms[batchIndex] : narrowPhaseInfoBatch.shape2ToWorldTransforms[batchIndex]; - const Transform& capsuleToWorldTransform = isSphereShape1 ? narrowPhaseInfoBatch.shape2ToWorldTransforms[batchIndex] : narrowPhaseInfoBatch.shape1ToWorldTransforms[batchIndex]; + const Transform& sphereToWorldTransform = isSphereShape1 ? narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape1ToWorldTransform : narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape2ToWorldTransform; + const Transform& capsuleToWorldTransform = isSphereShape1 ? narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape2ToWorldTransform : narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape1ToWorldTransform; const Transform worldToCapsuleTransform = capsuleToWorldTransform.getInverse(); const Transform sphereToCapsuleSpaceTransform = worldToCapsuleTransform * sphereToWorldTransform; @@ -57,7 +63,7 @@ bool SphereVsCapsuleAlgorithm::testCollision(SphereVsCapsuleNarrowPhaseInfoBatch const Vector3 sphereCenter = sphereToCapsuleSpaceTransform.getPosition(); // Compute the end-points of the inner segment of the capsule - const decimal capsuleHalfHeight = narrowPhaseInfoBatch.capsuleHeights[batchIndex] * decimal(0.5); + const decimal capsuleHalfHeight = capsuleHeight * decimal(0.5); const Vector3 capsuleSegA(0, -capsuleHalfHeight, 0); const Vector3 capsuleSegB(0, capsuleHalfHeight, 0); @@ -69,7 +75,7 @@ bool SphereVsCapsuleAlgorithm::testCollision(SphereVsCapsuleNarrowPhaseInfoBatch const decimal sphereSegmentDistanceSquare = sphereCenterToSegment.lengthSquare(); // Compute the sum of the radius of the sphere and the capsule (virtual sphere) - decimal sumRadius = narrowPhaseInfoBatch.sphereRadiuses[batchIndex] + narrowPhaseInfoBatch.capsuleRadiuses[batchIndex]; + decimal sumRadius = sphereRadius + capsuleRadius; // If the collision shapes overlap if (sphereSegmentDistanceSquare < sumRadius * sumRadius) { @@ -80,7 +86,7 @@ bool SphereVsCapsuleAlgorithm::testCollision(SphereVsCapsuleNarrowPhaseInfoBatch Vector3 contactPointCapsuleLocal; // If we need to report contacts - if (narrowPhaseInfoBatch.reportContacts[batchIndex]) { + if (narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].reportContacts) { // If the sphere center is not on the capsule inner segment if (sphereSegmentDistanceSquare > MACHINE_EPSILON) { @@ -88,8 +94,8 @@ bool SphereVsCapsuleAlgorithm::testCollision(SphereVsCapsuleNarrowPhaseInfoBatch decimal sphereSegmentDistance = std::sqrt(sphereSegmentDistanceSquare); sphereCenterToSegment /= sphereSegmentDistance; - contactPointSphereLocal = sphereToCapsuleSpaceTransform.getInverse() * (sphereCenter + sphereCenterToSegment * narrowPhaseInfoBatch.sphereRadiuses[batchIndex]); - contactPointCapsuleLocal = closestPointOnSegment - sphereCenterToSegment * narrowPhaseInfoBatch.capsuleRadiuses[batchIndex]; + contactPointSphereLocal = sphereToCapsuleSpaceTransform.getInverse() * (sphereCenter + sphereCenterToSegment * sphereRadius); + contactPointCapsuleLocal = closestPointOnSegment - sphereCenterToSegment * capsuleRadius; normalWorld = capsuleToWorldTransform.getOrientation() * sphereCenterToSegment; @@ -120,8 +126,8 @@ bool SphereVsCapsuleAlgorithm::testCollision(SphereVsCapsuleNarrowPhaseInfoBatch normalWorld = capsuleToWorldTransform.getOrientation() * normalCapsuleSpace; // Compute the two local contact points - contactPointSphereLocal = sphereToCapsuleSpaceTransform.getInverse() * (sphereCenter + normalCapsuleSpace * narrowPhaseInfoBatch.sphereRadiuses[batchIndex]); - contactPointCapsuleLocal = sphereCenter - normalCapsuleSpace * narrowPhaseInfoBatch.capsuleRadiuses[batchIndex]; + contactPointSphereLocal = sphereToCapsuleSpaceTransform.getInverse() * (sphereCenter + normalCapsuleSpace * sphereRadius); + contactPointCapsuleLocal = sphereCenter - normalCapsuleSpace * capsuleRadius; } if (penetrationDepth <= decimal(0.0)) { @@ -136,7 +142,7 @@ bool SphereVsCapsuleAlgorithm::testCollision(SphereVsCapsuleNarrowPhaseInfoBatch isSphereShape1 ? contactPointCapsuleLocal : contactPointSphereLocal); } - narrowPhaseInfoBatch.isColliding[batchIndex] = true; + narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].isColliding = true; isCollisionFound = true; continue; } diff --git a/src/collision/narrowphase/SphereVsCapsuleNarrowPhaseInfoBatch.cpp b/src/collision/narrowphase/SphereVsCapsuleNarrowPhaseInfoBatch.cpp deleted file mode 100644 index e4433b0d..00000000 --- a/src/collision/narrowphase/SphereVsCapsuleNarrowPhaseInfoBatch.cpp +++ /dev/null @@ -1,88 +0,0 @@ -/******************************************************************************** -* ReactPhysics3D physics library, http://www.reactphysics3d.com * -* Copyright (c) 2010-2020 Daniel Chappuis * -********************************************************************************* -* * -* This software is provided 'as-is', without any express or implied warranty. * -* In no event will the authors be held liable for any damages arising from the * -* use of this software. * -* * -* Permission is granted to anyone to use this software for any purpose, * -* including commercial applications, and to alter it and redistribute it * -* freely, subject to the following restrictions: * -* * -* 1. The origin of this software must not be misrepresented; you must not claim * -* that you wrote the original software. If you use this software in a * -* product, an acknowledgment in the product documentation would be * -* appreciated but is not required. * -* * -* 2. Altered source versions must be plainly marked as such, and must not be * -* misrepresented as being the original software. * -* * -* 3. This notice may not be removed or altered from any source distribution. * -* * -********************************************************************************/ - -// Libraries -#include -#include -#include - -using namespace reactphysics3d; - -// Constructor -SphereVsCapsuleNarrowPhaseInfoBatch::SphereVsCapsuleNarrowPhaseInfoBatch(MemoryAllocator& allocator, - OverlappingPairs& overlappingPairs) - : NarrowPhaseInfoBatch(allocator, overlappingPairs), isSpheresShape1(allocator), sphereRadiuses(allocator), capsuleRadiuses(allocator), - capsuleHeights(allocator) { - -} - -// Add shapes to be tested during narrow-phase collision detection into the batch -void SphereVsCapsuleNarrowPhaseInfoBatch::addNarrowPhaseInfo(uint64 pairId, uint64 pairIndex, Entity collider1, Entity collider2, CollisionShape* shape1, CollisionShape* shape2, - const Transform& shape1Transform, const Transform& shape2Transform, - bool needToReportContacts, MemoryAllocator& shapeAllocator) { - - NarrowPhaseInfoBatch::addNarrowPhaseInfo(pairId, pairIndex, collider1, collider2, shape1, shape2, shape1Transform, - shape2Transform, needToReportContacts, shapeAllocator); - - bool isSphereShape1 = shape1->getType() == CollisionShapeType::SPHERE; - isSpheresShape1.add(isSphereShape1); - - assert(isSphereShape1 || shape1->getType() == CollisionShapeType::CAPSULE); - - // Get the collision shapes - const SphereShape* sphereShape = static_cast(isSphereShape1 ? shape1 : shape2); - const CapsuleShape* capsuleShape = static_cast(isSphereShape1 ? shape2 : shape1); - - sphereRadiuses.add(sphereShape->getRadius()); - capsuleRadiuses.add(capsuleShape->getRadius()); - capsuleHeights.add(capsuleShape->getHeight()); -} - -// Initialize the containers using cached capacity -void SphereVsCapsuleNarrowPhaseInfoBatch::reserveMemory() { - - NarrowPhaseInfoBatch::reserveMemory(); - - isSpheresShape1.reserve(mCachedCapacity); - sphereRadiuses.reserve(mCachedCapacity); - capsuleRadiuses.reserve(mCachedCapacity); - capsuleHeights.reserve(mCachedCapacity); -} - -// Clear all the objects in the batch -void SphereVsCapsuleNarrowPhaseInfoBatch::clear() { - - // Note that we clear the following containers and we release their allocated memory. Therefore, - // if the memory allocator is a single frame allocator, the memory is deallocated and will be - // allocated in the next frame at a possibly different location in memory (remember that the - // location of the allocated memory of a single frame allocator might change between two frames) - - NarrowPhaseInfoBatch::clear(); - - isSpheresShape1.clear(true); - sphereRadiuses.clear(true); - capsuleRadiuses.clear(true); - capsuleHeights.clear(true); -} diff --git a/src/collision/narrowphase/SphereVsConvexPolyhedronAlgorithm.cpp b/src/collision/narrowphase/SphereVsConvexPolyhedronAlgorithm.cpp index 0c0f609b..6e732d12 100644 --- a/src/collision/narrowphase/SphereVsConvexPolyhedronAlgorithm.cpp +++ b/src/collision/narrowphase/SphereVsConvexPolyhedronAlgorithm.cpp @@ -57,13 +57,13 @@ bool SphereVsConvexPolyhedronAlgorithm::testCollision(NarrowPhaseInfoBatch& narr // For each item in the batch for (uint batchIndex = batchStartIndex; batchIndex < batchStartIndex + batchNbItems; batchIndex++) { - assert(narrowPhaseInfoBatch.collisionShapes1[batchIndex]->getType() == CollisionShapeType::CONVEX_POLYHEDRON || - narrowPhaseInfoBatch.collisionShapes2[batchIndex]->getType() == CollisionShapeType::CONVEX_POLYHEDRON); - assert(narrowPhaseInfoBatch.collisionShapes1[batchIndex]->getType() == CollisionShapeType::SPHERE || - narrowPhaseInfoBatch.collisionShapes2[batchIndex]->getType() == CollisionShapeType::SPHERE); + assert(narrowPhaseInfoBatch.narrowPhaseInfo[[batchIndex].collisionShape1->getType() == CollisionShapeType::CONVEX_POLYHEDRON || + narrowPhaseInfoBatch.narrowPhaseInfo[[batchIndex].collisionShape2->getType() == CollisionShapeType::CONVEX_POLYHEDRON); + assert(narrowPhaseInfoBatch.narrowPhaseInfo[[batchIndex].collisionShape1->getType() == CollisionShapeType::SPHERE || + narrowPhaseInfoBatch.narrowPhaseInfo[[batchIndex].collisionShape2->getType() == CollisionShapeType::SPHERE); // Get the last frame collision info - LastFrameCollisionInfo* lastFrameCollisionInfo = narrowPhaseInfoBatch.lastFrameCollisionInfos[batchIndex]; + LastFrameCollisionInfo* lastFrameCollisionInfo = narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].lastFrameCollisionInfo; lastFrameCollisionInfo->wasUsingGJK = true; lastFrameCollisionInfo->wasUsingSAT = false; @@ -72,7 +72,7 @@ bool SphereVsConvexPolyhedronAlgorithm::testCollision(NarrowPhaseInfoBatch& narr if (gjkResults[batchIndex] == GJKAlgorithm::GJKResult::COLLIDE_IN_MARGIN) { // Return true - narrowPhaseInfoBatch.isColliding[batchIndex] = true; + narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].isColliding = true; isCollisionFound = true; continue; } diff --git a/src/collision/narrowphase/SphereVsSphereAlgorithm.cpp b/src/collision/narrowphase/SphereVsSphereAlgorithm.cpp index 943ae89d..78f817db 100755 --- a/src/collision/narrowphase/SphereVsSphereAlgorithm.cpp +++ b/src/collision/narrowphase/SphereVsSphereAlgorithm.cpp @@ -26,31 +26,37 @@ // Libraries #include #include -#include +#include // We want to use the ReactPhysics3D namespace using namespace reactphysics3d; -bool SphereVsSphereAlgorithm::testCollision(SphereVsSphereNarrowPhaseInfoBatch& narrowPhaseInfoBatch, uint batchStartIndex, uint batchNbItems, MemoryAllocator& memoryAllocator) { +bool SphereVsSphereAlgorithm::testCollision(NarrowPhaseInfoBatch& narrowPhaseInfoBatch, uint batchStartIndex, uint batchNbItems, MemoryAllocator& memoryAllocator) { bool isCollisionFound = false; // For each item in the batch for (uint batchIndex = batchStartIndex; batchIndex < batchStartIndex + batchNbItems; batchIndex++) { - assert(narrowPhaseInfoBatch.contactPoints[batchIndex].size() == 0); - assert(!narrowPhaseInfoBatch.isColliding[batchIndex]); + assert(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].nbContactPoints == 0); + assert(!narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].isColliding); // Get the local-space to world-space transforms - const Transform& transform1 = narrowPhaseInfoBatch.shape1ToWorldTransforms[batchIndex]; - const Transform& transform2 = narrowPhaseInfoBatch.shape2ToWorldTransforms[batchIndex]; + const Transform& transform1 = narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape1ToWorldTransform; + const Transform& transform2 = narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape2ToWorldTransform; // Compute the distance between the centers Vector3 vectorBetweenCenters = transform2.getPosition() - transform1.getPosition(); decimal squaredDistanceBetweenCenters = vectorBetweenCenters.lengthSquare(); + const SphereShape* sphereShape1 = static_cast(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1); + const SphereShape* sphereShape2 = static_cast(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape2); + + const decimal sphere1Radius = sphereShape1->getRadius(); + const decimal sphere2Radius = sphereShape2->getRadius(); + // Compute the sum of the radius - decimal sumRadiuses = narrowPhaseInfoBatch.sphere1Radiuses[batchIndex] + narrowPhaseInfoBatch.sphere2Radiuses[batchIndex]; + decimal sumRadiuses = sphere1Radius + sphere2Radius; // Compute the product of the sum of the radius decimal sumRadiusesProducts = sumRadiuses * sumRadiuses; @@ -59,7 +65,7 @@ bool SphereVsSphereAlgorithm::testCollision(SphereVsSphereNarrowPhaseInfoBatch& if (squaredDistanceBetweenCenters < sumRadiusesProducts) { // If we need to report contacts - if (narrowPhaseInfoBatch.reportContacts[batchIndex]) { + if (narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].reportContacts) { const Transform transform1Inverse = transform1.getInverse(); const Transform transform2Inverse = transform2.getInverse(); @@ -75,8 +81,8 @@ bool SphereVsSphereAlgorithm::testCollision(SphereVsSphereNarrowPhaseInfoBatch& Vector3 centerSphere2InBody1LocalSpace = transform1Inverse * transform2.getPosition(); Vector3 centerSphere1InBody2LocalSpace = transform2Inverse * transform1.getPosition(); - intersectionOnBody1 = narrowPhaseInfoBatch.sphere1Radiuses[batchIndex] * centerSphere2InBody1LocalSpace.getUnit(); - intersectionOnBody2 = narrowPhaseInfoBatch.sphere2Radiuses[batchIndex] * centerSphere1InBody2LocalSpace.getUnit(); + intersectionOnBody1 = sphere1Radius * centerSphere2InBody1LocalSpace.getUnit(); + intersectionOnBody2 = sphere2Radius * centerSphere1InBody2LocalSpace.getUnit(); normal = vectorBetweenCenters.getUnit(); } else { // If the sphere centers are at the same position (degenerate case) @@ -84,15 +90,15 @@ bool SphereVsSphereAlgorithm::testCollision(SphereVsSphereNarrowPhaseInfoBatch& // Take any contact normal direction normal.setAllValues(0, 1, 0); - intersectionOnBody1 = narrowPhaseInfoBatch.sphere1Radiuses[batchIndex] * (transform1Inverse.getOrientation() * normal); - intersectionOnBody2 = narrowPhaseInfoBatch.sphere2Radiuses[batchIndex] * (transform2Inverse.getOrientation() * normal); + intersectionOnBody1 = sphere1Radius * (transform1Inverse.getOrientation() * normal); + intersectionOnBody2 = sphere2Radius * (transform2Inverse.getOrientation() * normal); } // Create the contact info object narrowPhaseInfoBatch.addContactPoint(batchIndex, normal, penetrationDepth, intersectionOnBody1, intersectionOnBody2); } - narrowPhaseInfoBatch.isColliding[batchIndex] = true; + narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].isColliding = true; isCollisionFound = true; } } diff --git a/src/collision/narrowphase/SphereVsSphereNarrowPhaseInfoBatch.cpp b/src/collision/narrowphase/SphereVsSphereNarrowPhaseInfoBatch.cpp deleted file mode 100644 index 36970cd2..00000000 --- a/src/collision/narrowphase/SphereVsSphereNarrowPhaseInfoBatch.cpp +++ /dev/null @@ -1,77 +0,0 @@ -/******************************************************************************** -* ReactPhysics3D physics library, http://www.reactphysics3d.com * -* Copyright (c) 2010-2020 Daniel Chappuis * -********************************************************************************* -* * -* This software is provided 'as-is', without any express or implied warranty. * -* In no event will the authors be held liable for any damages arising from the * -* use of this software. * -* * -* Permission is granted to anyone to use this software for any purpose, * -* including commercial applications, and to alter it and redistribute it * -* freely, subject to the following restrictions: * -* * -* 1. The origin of this software must not be misrepresented; you must not claim * -* that you wrote the original software. If you use this software in a * -* product, an acknowledgment in the product documentation would be * -* appreciated but is not required. * -* * -* 2. Altered source versions must be plainly marked as such, and must not be * -* misrepresented as being the original software. * -* * -* 3. This notice may not be removed or altered from any source distribution. * -* * -********************************************************************************/ - -// Libraries -#include -#include - -using namespace reactphysics3d; - -// Constructor -SphereVsSphereNarrowPhaseInfoBatch::SphereVsSphereNarrowPhaseInfoBatch(MemoryAllocator& allocator, OverlappingPairs& overlappingPairs) - : NarrowPhaseInfoBatch(allocator, overlappingPairs), sphere1Radiuses(allocator), sphere2Radiuses(allocator) { - -} - -// Add shapes to be tested during narrow-phase collision detection into the batch -void SphereVsSphereNarrowPhaseInfoBatch::addNarrowPhaseInfo(uint64 pairId, uint64 pairIndex, Entity collider1, Entity collider2, CollisionShape* shape1, CollisionShape* shape2, - const Transform& shape1Transform, const Transform& shape2Transform, bool needToReportContacts, MemoryAllocator &shapeAllocator) { - - NarrowPhaseInfoBatch::addNarrowPhaseInfo(pairId, pairIndex, collider1, collider2, shape1, shape2, shape1Transform, - shape2Transform, needToReportContacts, shapeAllocator); - - assert(shape1->getType() == CollisionShapeType::SPHERE); - assert(shape2->getType() == CollisionShapeType::SPHERE); - - const SphereShape* sphere1 = static_cast(shape1); - const SphereShape* sphere2 = static_cast(shape2); - - sphere1Radiuses.add(sphere1->getRadius()); - sphere2Radiuses.add(sphere2->getRadius()); -} - -// Initialize the containers using cached capacity -void SphereVsSphereNarrowPhaseInfoBatch::reserveMemory() { - - NarrowPhaseInfoBatch::reserveMemory(); - - sphere1Radiuses.reserve(mCachedCapacity); - sphere2Radiuses.reserve(mCachedCapacity); -} - -// Clear all the objects in the batch -void SphereVsSphereNarrowPhaseInfoBatch::clear() { - - // Note that we clear the following containers and we release their allocated memory. Therefore, - // if the memory allocator is a single frame allocator, the memory is deallocated and will be - // allocated in the next frame at a possibly different location in memory (remember that the - // location of the allocated memory of a single frame allocator might change between two frames) - - NarrowPhaseInfoBatch::clear(); - - sphere1Radiuses.clear(true); - sphere2Radiuses.clear(true); -} - diff --git a/src/systems/CollisionDetectionSystem.cpp b/src/systems/CollisionDetectionSystem.cpp index e11f7e86..4d28ae89 100644 --- a/src/systems/CollisionDetectionSystem.cpp +++ b/src/systems/CollisionDetectionSystem.cpp @@ -445,9 +445,9 @@ bool CollisionDetectionSystem::testNarrowPhaseCollision(NarrowPhaseInput& narrow ConvexPolyhedronVsConvexPolyhedronAlgorithm* convexPolyVsConvexPolyAlgo = mCollisionDispatch.getConvexPolyhedronVsConvexPolyhedronAlgorithm(); // get the narrow-phase batches to test for collision for contacts - SphereVsSphereNarrowPhaseInfoBatch& sphereVsSphereBatchContacts = narrowPhaseInput.getSphereVsSphereBatch(); - SphereVsCapsuleNarrowPhaseInfoBatch& sphereVsCapsuleBatchContacts = narrowPhaseInput.getSphereVsCapsuleBatch(); - CapsuleVsCapsuleNarrowPhaseInfoBatch& capsuleVsCapsuleBatchContacts = narrowPhaseInput.getCapsuleVsCapsuleBatch(); + NarrowPhaseInfoBatch& sphereVsSphereBatchContacts = narrowPhaseInput.getSphereVsSphereBatch(); + NarrowPhaseInfoBatch& sphereVsCapsuleBatchContacts = narrowPhaseInput.getSphereVsCapsuleBatch(); + NarrowPhaseInfoBatch& capsuleVsCapsuleBatchContacts = narrowPhaseInput.getCapsuleVsCapsuleBatch(); NarrowPhaseInfoBatch& sphereVsConvexPolyhedronBatchContacts = narrowPhaseInput.getSphereVsConvexPolyhedronBatch(); NarrowPhaseInfoBatch& capsuleVsConvexPolyhedronBatchContacts = narrowPhaseInput.getCapsuleVsConvexPolyhedronBatch(); NarrowPhaseInfoBatch& convexPolyhedronVsConvexPolyhedronBatchContacts = narrowPhaseInput.getConvexPolyhedronVsConvexPolyhedronBatch(); @@ -482,7 +482,6 @@ void CollisionDetectionSystem::processAllPotentialContacts(NarrowPhaseInput& nar List* contactPairs) { assert(contactPairs->size() == 0); - assert(mapPairIdToContactPairIndex->size() == 0); Map mapPairIdToContactPairIndex(mMemoryManager.getHeapAllocator(), mPreviousMapPairIdToContactPairIndex.size()); @@ -618,13 +617,13 @@ void CollisionDetectionSystem::computeOverlapSnapshotContactPairs(NarrowPhaseInf for(uint i=0; i < narrowPhaseInfoBatch.getNbObjects(); i++) { // If there is a collision - if (narrowPhaseInfoBatch.isColliding[i]) { + if (narrowPhaseInfoBatch.narrowPhaseInfos[i].isColliding) { // If the contact pair does not already exist - if (!setOverlapContactPairId.contains(narrowPhaseInfoBatch.overlappingPairIds[i])) { + if (!setOverlapContactPairId.contains(narrowPhaseInfoBatch.narrowPhaseInfos[i].overlappingPairId)) { - const Entity collider1Entity = narrowPhaseInfoBatch.colliderEntities1[i]; - const Entity collider2Entity = narrowPhaseInfoBatch.colliderEntities2[i]; + const Entity collider1Entity = narrowPhaseInfoBatch.narrowPhaseInfos[i].colliderEntity1; + const Entity collider2Entity = narrowPhaseInfoBatch.narrowPhaseInfos[i].colliderEntity2; const uint32 collider1Index = mCollidersComponents.getEntityIndex(collider1Entity); const uint32 collider2Index = mCollidersComponents.getEntityIndex(collider2Entity); @@ -635,10 +634,10 @@ void CollisionDetectionSystem::computeOverlapSnapshotContactPairs(NarrowPhaseInf const bool isTrigger = mCollidersComponents.mIsTrigger[collider1Index] || mCollidersComponents.mIsTrigger[collider2Index]; // Create a new contact pair - ContactPair contactPair(narrowPhaseInfoBatch.overlappingPairIds[i], body1Entity, body2Entity, collider1Entity, collider2Entity, contactPairs.size(), isTrigger, false); + ContactPair contactPair(narrowPhaseInfoBatch.narrowPhaseInfos[i].overlappingPairId, body1Entity, body2Entity, collider1Entity, collider2Entity, contactPairs.size(), isTrigger, false); contactPairs.add(contactPair); - setOverlapContactPairId.add(narrowPhaseInfoBatch.overlappingPairIds[i]); + setOverlapContactPairId.add(narrowPhaseInfoBatch.narrowPhaseInfos[i].overlappingPairId); } } @@ -993,26 +992,31 @@ void CollisionDetectionSystem::processPotentialContacts(NarrowPhaseInfoBatch& na RP3D_PROFILE("CollisionDetectionSystem::processPotentialContacts()", mProfiler); + if (updateLastFrameInfo) { + + // For each narrow phase info object + for(uint i=0; i < narrowPhaseInfoBatch.getNbObjects(); i++) { + + narrowPhaseInfoBatch.narrowPhaseInfos[i].lastFrameCollisionInfo->wasColliding = narrowPhaseInfoBatch.narrowPhaseInfos[i].isColliding; + + // The previous frame collision info is now valid + narrowPhaseInfoBatch.narrowPhaseInfos[i].lastFrameCollisionInfo->isValid = true; + } + } + // For each narrow phase info object for(uint i=0; i < narrowPhaseInfoBatch.getNbObjects(); i++) { - if (updateLastFrameInfo) { - narrowPhaseInfoBatch.lastFrameCollisionInfos[i]->wasColliding = narrowPhaseInfoBatch.isColliding[i]; - - // The previous frame collision info is now valid - narrowPhaseInfoBatch.lastFrameCollisionInfos[i]->isValid = true; - } - - const uint64 pairId = narrowPhaseInfoBatch.overlappingPairIds[i]; + const uint64 pairId = narrowPhaseInfoBatch.narrowPhaseInfos[i].overlappingPairId; const uint64 pairIndex = mOverlappingPairs.mMapPairIdToPairIndex[pairId]; // If the two colliders are colliding - if (narrowPhaseInfoBatch.isColliding[i]) { + if (narrowPhaseInfoBatch.narrowPhaseInfos[i].isColliding) { mOverlappingPairs.mCollidingInCurrentFrame[pairIndex] = true; - const Entity collider1Entity = narrowPhaseInfoBatch.colliderEntities1[i]; - const Entity collider2Entity = narrowPhaseInfoBatch.colliderEntities2[i]; + const Entity collider1Entity = narrowPhaseInfoBatch.narrowPhaseInfos[i].colliderEntity1; + const Entity collider2Entity = narrowPhaseInfoBatch.narrowPhaseInfos[i].colliderEntity2; const uint32 collider1Index = mCollidersComponents.getEntityIndex(collider1Entity); const uint32 collider2Index = mCollidersComponents.getEntityIndex(collider2Entity); @@ -1045,7 +1049,7 @@ void CollisionDetectionSystem::processPotentialContacts(NarrowPhaseInfoBatch& na const uint contactPointIndexStart = static_cast(potentialContactPoints.size()); // Add the potential contacts - for (uint j=0; j < narrowPhaseInfoBatch.contactPoints[i].size(); j++) { + for (uint j=0; j < narrowPhaseInfoBatch.narrowPhaseInfos[i].nbContactPoints; j++) { if (contactManifoldInfo.nbPotentialContactPoints < NB_MAX_CONTACT_POINTS_IN_POTENTIAL_MANIFOLD) { @@ -1054,14 +1058,14 @@ void CollisionDetectionSystem::processPotentialContacts(NarrowPhaseInfoBatch& na contactManifoldInfo.nbPotentialContactPoints++; // Add the contact point to the list of potential contact points - const ContactPointInfo& contactPoint = *(narrowPhaseInfoBatch.contactPoints[i][j]); + const ContactPointInfo& contactPoint = narrowPhaseInfoBatch.narrowPhaseInfos[i].contactPoints[j]; potentialContactPoints.add(contactPoint); } } // Add the contact manifold to the overlapping pair contact - assert(overlappingPairContact.pairId == contactManifoldInfo.pairId); + assert(pairId == contactManifoldInfo.pairId); pairContact->potentialContactManifoldsIndices[0] = contactManifoldIndex; pairContact->nbPotentialContactManifolds = 1; } @@ -1096,9 +1100,9 @@ void CollisionDetectionSystem::processPotentialContacts(NarrowPhaseInfoBatch& na assert(pairContact != nullptr); // Add the potential contacts - for (uint j=0; j < narrowPhaseInfoBatch.contactPoints[i].size(); j++) { + for (uint j=0; j < narrowPhaseInfoBatch.narrowPhaseInfos[i].nbContactPoints; j++) { - const ContactPointInfo& contactPoint = *(narrowPhaseInfoBatch.contactPoints[i][j]); + const ContactPointInfo& contactPoint = narrowPhaseInfoBatch.narrowPhaseInfos[i].contactPoints[j]; // Add the contact point to the list of potential contact points const uint contactPointIndex = static_cast(potentialContactPoints.size()); From fd09fcf6602d83cd4bec219f0277e68de77280f1 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Sun, 26 Jul 2020 20:47:23 +0200 Subject: [PATCH 12/74] Add force inline macro --- include/reactphysics3d/body/CollisionBody.h | 6 +- include/reactphysics3d/collision/Collider.h | 12 +- .../collision/CollisionCallback.h | 12 +- .../collision/HalfEdgeStructure.h | 16 +- .../collision/OverlapCallback.h | 4 +- .../collision/PolygonVertexArray.h | 18 +- .../reactphysics3d/collision/PolyhedronMesh.h | 10 +- .../reactphysics3d/collision/TriangleMesh.h | 6 +- .../collision/TriangleVertexArray.h | 22 +-- .../collision/broadphase/DynamicAABBTree.h | 16 +- .../collision/narrowphase/CollisionDispatch.h | 14 +- .../collision/narrowphase/GJK/GJKAlgorithm.h | 2 +- .../narrowphase/GJK/VoronoiSimplex.h | 10 +- .../narrowphase/NarrowPhaseAlgorithm.h | 2 +- .../narrowphase/NarrowPhaseInfoBatch.h | 39 ++++- .../collision/narrowphase/NarrowPhaseInput.h | 43 ++++- .../collision/narrowphase/SAT/SATAlgorithm.h | 2 +- .../reactphysics3d/collision/shapes/AABB.h | 26 +-- .../collision/shapes/BoxShape.h | 34 ++-- .../collision/shapes/CapsuleShape.h | 20 +-- .../collision/shapes/CollisionShape.h | 12 +- .../collision/shapes/ConcaveMeshShape.h | 8 +- .../collision/shapes/ConcaveShape.h | 16 +- .../collision/shapes/ConvexMeshShape.h | 30 ++-- .../collision/shapes/ConvexPolyhedronShape.h | 2 +- .../collision/shapes/ConvexShape.h | 4 +- .../collision/shapes/HeightFieldShape.h | 14 +- .../collision/shapes/SphereShape.h | 20 +-- .../collision/shapes/TriangleShape.h | 36 ++-- .../components/BallAndSocketJointComponents.h | 40 ++--- .../components/ColliderComponents.h | 36 ++-- .../components/CollisionBodyComponents.h | 16 +- .../reactphysics3d/components/Components.h | 12 +- .../components/FixedJointComponents.h | 56 +++---- .../components/HingeJointComponents.h | 136 +++++++-------- .../components/JointComponents.h | 20 +-- .../components/RigidBodyComponents.h | 108 ++++++------ .../components/SliderJointComponents.h | 156 +++++++++--------- .../components/TransformComponents.h | 4 +- include/reactphysics3d/configuration.h | 2 +- .../constraint/BallAndSocketJoint.h | 2 +- .../reactphysics3d/constraint/ContactPoint.h | 20 +-- .../reactphysics3d/constraint/FixedJoint.h | 2 +- include/reactphysics3d/constraint/Joint.h | 2 +- .../reactphysics3d/constraint/SliderJoint.h | 2 +- .../reactphysics3d/containers/LinkedList.h | 6 +- .../containers/containers_common.h | 1 + include/reactphysics3d/engine/Entity.h | 8 +- include/reactphysics3d/engine/EntityManager.h | 2 +- include/reactphysics3d/engine/Island.h | 12 +- include/reactphysics3d/engine/Material.h | 20 +-- .../reactphysics3d/engine/OverlappingPairs.h | 30 ++-- include/reactphysics3d/engine/PhysicsCommon.h | 4 +- include/reactphysics3d/engine/PhysicsWorld.h | 50 +++--- include/reactphysics3d/engine/Timer.h | 20 +-- .../reactphysics3d/mathematics/Matrix2x2.h | 60 +++---- .../reactphysics3d/mathematics/Matrix3x3.h | 62 +++---- .../reactphysics3d/mathematics/Quaternion.h | 66 ++++---- .../reactphysics3d/mathematics/Transform.h | 38 ++--- include/reactphysics3d/mathematics/Vector2.h | 76 ++++----- include/reactphysics3d/mathematics/Vector3.h | 85 +++++----- .../mathematics/mathematics_functions.h | 12 +- include/reactphysics3d/memory/MemoryManager.h | 12 +- .../reactphysics3d/systems/BroadPhaseSystem.h | 8 +- .../systems/CollisionDetectionSystem.h | 20 +-- .../systems/ConstraintSolverSystem.h | 2 +- .../systems/ContactSolverSystem.h | 6 +- .../reactphysics3d/systems/DynamicsSystem.h | 2 +- .../systems/SolveBallAndSocketJointSystem.h | 6 +- .../systems/SolveFixedJointSystem.h | 6 +- .../systems/SolveHingeJointSystem.h | 6 +- .../systems/SolveSliderJointSystem.h | 6 +- include/reactphysics3d/utils/DebugRenderer.h | 24 +-- include/reactphysics3d/utils/Profiler.h | 44 ++--- .../narrowphase/NarrowPhaseInfoBatch.cpp | 37 ----- .../narrowphase/NarrowPhaseInput.cpp | 32 ---- src/systems/ContactSolverSystem.cpp | 2 +- 77 files changed, 918 insertions(+), 917 deletions(-) diff --git a/include/reactphysics3d/body/CollisionBody.h b/include/reactphysics3d/body/CollisionBody.h index 9b77bec6..98c22c4a 100644 --- a/include/reactphysics3d/body/CollisionBody.h +++ b/include/reactphysics3d/body/CollisionBody.h @@ -178,7 +178,7 @@ class CollisionBody { * @param worldAABB The AABB (in world-space coordinates) that will be used to test overlap * @return True if the given AABB overlaps with the AABB of the collision body */ -inline bool CollisionBody::testAABBOverlap(const AABB& worldAABB) const { +RP3D_FORCE_INLINE bool CollisionBody::testAABBOverlap(const AABB& worldAABB) const { return worldAABB.testCollision(getAABB()); } @@ -186,14 +186,14 @@ inline bool CollisionBody::testAABBOverlap(const AABB& worldAABB) const { /** * @return The entity of the body */ -inline Entity CollisionBody::getEntity() const { +RP3D_FORCE_INLINE Entity CollisionBody::getEntity() const { return mEntity; } #ifdef IS_RP3D_PROFILING_ENABLED // Set the profiler -inline void CollisionBody::setProfiler(Profiler* profiler) { +RP3D_FORCE_INLINE void CollisionBody::setProfiler(Profiler* profiler) { mProfiler = profiler; } diff --git a/include/reactphysics3d/collision/Collider.h b/include/reactphysics3d/collision/Collider.h index 9fee33da..ae590d9f 100644 --- a/include/reactphysics3d/collision/Collider.h +++ b/include/reactphysics3d/collision/Collider.h @@ -188,7 +188,7 @@ class Collider { /** * @return The entity of the collider */ -inline Entity Collider::getEntity() const { +RP3D_FORCE_INLINE Entity Collider::getEntity() const { return mEntity; } @@ -196,7 +196,7 @@ inline Entity Collider::getEntity() const { /** * @return Pointer to the parent body */ -inline CollisionBody* Collider::getBody() const { +RP3D_FORCE_INLINE CollisionBody* Collider::getBody() const { return mBody; } @@ -204,7 +204,7 @@ inline CollisionBody* Collider::getBody() const { /** * @return A pointer to the user data stored into the collider */ -inline void* Collider::getUserData() const { +RP3D_FORCE_INLINE void* Collider::getUserData() const { return mUserData; } @@ -212,7 +212,7 @@ inline void* Collider::getUserData() const { /** * @param userData Pointer to the user data you want to store within the collider */ -inline void Collider::setUserData(void* userData) { +RP3D_FORCE_INLINE void Collider::setUserData(void* userData) { mUserData = userData; } @@ -221,7 +221,7 @@ inline void Collider::setUserData(void* userData) { * @param worldAABB The AABB (in world-space coordinates) that will be used to test overlap * @return True if the given AABB overlaps with the AABB of the collision body */ -inline bool Collider::testAABBOverlap(const AABB& worldAABB) const { +RP3D_FORCE_INLINE bool Collider::testAABBOverlap(const AABB& worldAABB) const { return worldAABB.testCollision(getWorldAABB()); } @@ -229,7 +229,7 @@ inline bool Collider::testAABBOverlap(const AABB& worldAABB) const { /** * @return A reference to the material of the body */ -inline Material& Collider::getMaterial() { +RP3D_FORCE_INLINE Material& Collider::getMaterial() { return mMaterial; } diff --git a/include/reactphysics3d/collision/CollisionCallback.h b/include/reactphysics3d/collision/CollisionCallback.h index f3f2b41d..716de01f 100644 --- a/include/reactphysics3d/collision/CollisionCallback.h +++ b/include/reactphysics3d/collision/CollisionCallback.h @@ -296,7 +296,7 @@ class CollisionCallback { /** * @return The number of contact pairs */ -inline uint CollisionCallback::CallbackData::getNbContactPairs() const { +RP3D_FORCE_INLINE uint CollisionCallback::CallbackData::getNbContactPairs() const { return mContactPairsIndices.size() + mLostContactPairsIndices.size(); } @@ -304,7 +304,7 @@ inline uint CollisionCallback::CallbackData::getNbContactPairs() const { /** * @return The number of contact points */ -inline uint CollisionCallback::ContactPair::getNbContactPoints() const { +RP3D_FORCE_INLINE uint CollisionCallback::ContactPair::getNbContactPoints() const { return mContactPair.nbToTalContactPoints; } @@ -312,7 +312,7 @@ inline uint CollisionCallback::ContactPair::getNbContactPoints() const { /** * @return The penetration depth (larger than zero) */ -inline decimal CollisionCallback::ContactPoint::getPenetrationDepth() const { +RP3D_FORCE_INLINE decimal CollisionCallback::ContactPoint::getPenetrationDepth() const { return mContactPoint.getPenetrationDepth(); } @@ -320,7 +320,7 @@ inline decimal CollisionCallback::ContactPoint::getPenetrationDepth() const { /** * @return The contact normal direction at the contact point (in world-space) */ -inline const Vector3& CollisionCallback::ContactPoint::getWorldNormal() const { +RP3D_FORCE_INLINE const Vector3& CollisionCallback::ContactPoint::getWorldNormal() const { return mContactPoint.getNormal(); } @@ -328,7 +328,7 @@ inline const Vector3& CollisionCallback::ContactPoint::getWorldNormal() const { /** * @return The contact point in the local-space of the first collider (from body1) in contact */ -inline const Vector3& CollisionCallback::ContactPoint::getLocalPointOnCollider1() const { +RP3D_FORCE_INLINE const Vector3& CollisionCallback::ContactPoint::getLocalPointOnCollider1() const { return mContactPoint.getLocalPointOnShape1(); } @@ -336,7 +336,7 @@ inline const Vector3& CollisionCallback::ContactPoint::getLocalPointOnCollider1( /** * @return The contact point in the local-space of the second collider (from body2) in contact */ -inline const Vector3& CollisionCallback::ContactPoint::getLocalPointOnCollider2() const { +RP3D_FORCE_INLINE const Vector3& CollisionCallback::ContactPoint::getLocalPointOnCollider2() const { return mContactPoint.getLocalPointOnShape2(); } diff --git a/include/reactphysics3d/collision/HalfEdgeStructure.h b/include/reactphysics3d/collision/HalfEdgeStructure.h index d0d915df..ca68064a 100644 --- a/include/reactphysics3d/collision/HalfEdgeStructure.h +++ b/include/reactphysics3d/collision/HalfEdgeStructure.h @@ -129,7 +129,7 @@ class HalfEdgeStructure { /** * @param vertexPointIndex Index of the vertex in the vertex data array */ -inline uint HalfEdgeStructure::addVertex(uint vertexPointIndex) { +RP3D_FORCE_INLINE uint HalfEdgeStructure::addVertex(uint vertexPointIndex) { Vertex vertex(vertexPointIndex); mVertices.add(vertex); return mVertices.size() - 1; @@ -140,7 +140,7 @@ inline uint HalfEdgeStructure::addVertex(uint vertexPointIndex) { * @param faceVertices List of the vertices in a face (ordered in CCW order as seen from outside * the polyhedron */ -inline void HalfEdgeStructure::addFace(List faceVertices) { +RP3D_FORCE_INLINE void HalfEdgeStructure::addFace(List faceVertices) { // Create a new face Face face(faceVertices); @@ -151,7 +151,7 @@ inline void HalfEdgeStructure::addFace(List faceVertices) { /** * @return The number of faces in the polyhedron */ -inline uint HalfEdgeStructure::getNbFaces() const { +RP3D_FORCE_INLINE uint HalfEdgeStructure::getNbFaces() const { return static_cast(mFaces.size()); } @@ -159,7 +159,7 @@ inline uint HalfEdgeStructure::getNbFaces() const { /** * @return The number of edges in the polyhedron */ -inline uint HalfEdgeStructure::getNbHalfEdges() const { +RP3D_FORCE_INLINE uint HalfEdgeStructure::getNbHalfEdges() const { return static_cast(mEdges.size()); } @@ -167,7 +167,7 @@ inline uint HalfEdgeStructure::getNbHalfEdges() const { /** * @return The number of vertices in the polyhedron */ -inline uint HalfEdgeStructure::getNbVertices() const { +RP3D_FORCE_INLINE uint HalfEdgeStructure::getNbVertices() const { return static_cast(mVertices.size()); } @@ -175,7 +175,7 @@ inline uint HalfEdgeStructure::getNbVertices() const { /** * @return A given face of the polyhedron */ -inline const HalfEdgeStructure::Face& HalfEdgeStructure::getFace(uint index) const { +RP3D_FORCE_INLINE const HalfEdgeStructure::Face& HalfEdgeStructure::getFace(uint index) const { assert(index < mFaces.size()); return mFaces[index]; } @@ -184,7 +184,7 @@ inline const HalfEdgeStructure::Face& HalfEdgeStructure::getFace(uint index) con /** * @return A given edge of the polyhedron */ -inline const HalfEdgeStructure::Edge& HalfEdgeStructure::getHalfEdge(uint index) const { +RP3D_FORCE_INLINE const HalfEdgeStructure::Edge& HalfEdgeStructure::getHalfEdge(uint index) const { assert(index < mEdges.size()); return mEdges[index]; } @@ -193,7 +193,7 @@ inline const HalfEdgeStructure::Edge& HalfEdgeStructure::getHalfEdge(uint index) /** * @return A given vertex of the polyhedron */ -inline const HalfEdgeStructure::Vertex& HalfEdgeStructure::getVertex(uint index) const { +RP3D_FORCE_INLINE const HalfEdgeStructure::Vertex& HalfEdgeStructure::getVertex(uint index) const { assert(index < mVertices.size()); return mVertices[index]; } diff --git a/include/reactphysics3d/collision/OverlapCallback.h b/include/reactphysics3d/collision/OverlapCallback.h index 1fb99660..6601b879 100644 --- a/include/reactphysics3d/collision/OverlapCallback.h +++ b/include/reactphysics3d/collision/OverlapCallback.h @@ -185,7 +185,7 @@ class OverlapCallback { }; // Return the number of overlapping pairs of bodies -inline uint OverlapCallback::CallbackData::getNbOverlappingPairs() const { +RP3D_FORCE_INLINE uint OverlapCallback::CallbackData::getNbOverlappingPairs() const { return mContactPairsIndices.size() + mLostContactPairsIndices.size(); } @@ -193,7 +193,7 @@ inline uint OverlapCallback::CallbackData::getNbOverlappingPairs() const { /// Note that the returned OverlapPair object is only valid during the call of the CollisionCallback::onOverlap() /// method. Therefore, you need to get contact data from it and make a copy. Do not make a copy of the OverlapPair /// object itself because it won't be valid after the CollisionCallback::onOverlap() call. -inline OverlapCallback::OverlapPair OverlapCallback::CallbackData::getOverlappingPair(uint index) const { +RP3D_FORCE_INLINE OverlapCallback::OverlapPair OverlapCallback::CallbackData::getOverlappingPair(uint index) const { assert(index < getNbOverlappingPairs()); diff --git a/include/reactphysics3d/collision/PolygonVertexArray.h b/include/reactphysics3d/collision/PolygonVertexArray.h index fe97d146..4512bc15 100644 --- a/include/reactphysics3d/collision/PolygonVertexArray.h +++ b/include/reactphysics3d/collision/PolygonVertexArray.h @@ -140,7 +140,7 @@ class PolygonVertexArray { /** * @return The data type of the vertices in the array */ -inline PolygonVertexArray::VertexDataType PolygonVertexArray::getVertexDataType() const { +RP3D_FORCE_INLINE PolygonVertexArray::VertexDataType PolygonVertexArray::getVertexDataType() const { return mVertexDataType; } @@ -148,7 +148,7 @@ inline PolygonVertexArray::VertexDataType PolygonVertexArray::getVertexDataType( /** * @return The data type of the indices in the array */ -inline PolygonVertexArray::IndexDataType PolygonVertexArray::getIndexDataType() const { +RP3D_FORCE_INLINE PolygonVertexArray::IndexDataType PolygonVertexArray::getIndexDataType() const { return mIndexDataType; } @@ -156,7 +156,7 @@ inline PolygonVertexArray::IndexDataType PolygonVertexArray::getIndexDataType() /** * @return The number of vertices in the array */ -inline uint PolygonVertexArray::getNbVertices() const { +RP3D_FORCE_INLINE uint PolygonVertexArray::getNbVertices() const { return mNbVertices; } @@ -164,7 +164,7 @@ inline uint PolygonVertexArray::getNbVertices() const { /** * @return The number of faces in the array */ -inline uint PolygonVertexArray::getNbFaces() const { +RP3D_FORCE_INLINE uint PolygonVertexArray::getNbFaces() const { return mNbFaces; } @@ -172,7 +172,7 @@ inline uint PolygonVertexArray::getNbFaces() const { /** * @return The number of bytes between two vertices */ -inline int PolygonVertexArray::getVerticesStride() const { +RP3D_FORCE_INLINE int PolygonVertexArray::getVerticesStride() const { return mVerticesStride; } @@ -180,7 +180,7 @@ inline int PolygonVertexArray::getVerticesStride() const { /** * @return The number of bytes between two consecutive face indices */ -inline int PolygonVertexArray::getIndicesStride() const { +RP3D_FORCE_INLINE int PolygonVertexArray::getIndicesStride() const { return mIndicesStride; } @@ -189,7 +189,7 @@ inline int PolygonVertexArray::getIndicesStride() const { * @param faceIndex Index of a given face * @return A polygon face */ -inline PolygonVertexArray::PolygonFace* PolygonVertexArray::getPolygonFace(uint faceIndex) const { +RP3D_FORCE_INLINE PolygonVertexArray::PolygonFace* PolygonVertexArray::getPolygonFace(uint faceIndex) const { assert(faceIndex < mNbFaces); return &mPolygonFacesStart[faceIndex]; } @@ -198,7 +198,7 @@ inline PolygonVertexArray::PolygonFace* PolygonVertexArray::getPolygonFace(uint /** * @return A pointer to the start of the vertex array of the polyhedron */ -inline const unsigned char* PolygonVertexArray::getVerticesStart() const { +RP3D_FORCE_INLINE const unsigned char* PolygonVertexArray::getVerticesStart() const { return mVerticesStart; } @@ -206,7 +206,7 @@ inline const unsigned char* PolygonVertexArray::getVerticesStart() const { /** * @return A pointer to the start of the face indices array of the polyhedron */ -inline const unsigned char* PolygonVertexArray::getIndicesStart() const { +RP3D_FORCE_INLINE const unsigned char* PolygonVertexArray::getIndicesStart() const { return mIndicesStart; } diff --git a/include/reactphysics3d/collision/PolyhedronMesh.h b/include/reactphysics3d/collision/PolyhedronMesh.h index e7fd932f..f4e57bc9 100644 --- a/include/reactphysics3d/collision/PolyhedronMesh.h +++ b/include/reactphysics3d/collision/PolyhedronMesh.h @@ -117,7 +117,7 @@ class PolyhedronMesh { /** * @return The number of vertices in the mesh */ -inline uint PolyhedronMesh::getNbVertices() const { +RP3D_FORCE_INLINE uint PolyhedronMesh::getNbVertices() const { return mHalfEdgeStructure.getNbVertices(); } @@ -125,7 +125,7 @@ inline uint PolyhedronMesh::getNbVertices() const { /** * @return The number of faces in the mesh */ -inline uint PolyhedronMesh::getNbFaces() const { +RP3D_FORCE_INLINE uint PolyhedronMesh::getNbFaces() const { return mHalfEdgeStructure.getNbFaces(); } @@ -134,7 +134,7 @@ inline uint PolyhedronMesh::getNbFaces() const { * @param faceIndex The index of a given face of the mesh * @return The normal vector of a given face of the mesh */ -inline Vector3 PolyhedronMesh::getFaceNormal(uint faceIndex) const { +RP3D_FORCE_INLINE Vector3 PolyhedronMesh::getFaceNormal(uint faceIndex) const { assert(faceIndex < mHalfEdgeStructure.getNbFaces()); return mFacesNormals[faceIndex]; } @@ -143,7 +143,7 @@ inline Vector3 PolyhedronMesh::getFaceNormal(uint faceIndex) const { /** * @return The Half-Edge structure of the mesh */ -inline const HalfEdgeStructure& PolyhedronMesh::getHalfEdgeStructure() const { +RP3D_FORCE_INLINE const HalfEdgeStructure& PolyhedronMesh::getHalfEdgeStructure() const { return mHalfEdgeStructure; } @@ -151,7 +151,7 @@ inline const HalfEdgeStructure& PolyhedronMesh::getHalfEdgeStructure() const { /** * @return The centroid of the mesh */ -inline Vector3 PolyhedronMesh::getCentroid() const { +RP3D_FORCE_INLINE Vector3 PolyhedronMesh::getCentroid() const { return mCentroid; } diff --git a/include/reactphysics3d/collision/TriangleMesh.h b/include/reactphysics3d/collision/TriangleMesh.h index 155f7a44..1cb4d516 100644 --- a/include/reactphysics3d/collision/TriangleMesh.h +++ b/include/reactphysics3d/collision/TriangleMesh.h @@ -78,7 +78,7 @@ class TriangleMesh { /** * @param triangleVertexArray Pointer to the TriangleVertexArray to add into the mesh */ -inline void TriangleMesh::addSubpart(TriangleVertexArray* triangleVertexArray) { +RP3D_FORCE_INLINE void TriangleMesh::addSubpart(TriangleVertexArray* triangleVertexArray) { mTriangleArrays.add(triangleVertexArray ); } @@ -87,7 +87,7 @@ inline void TriangleMesh::addSubpart(TriangleVertexArray* triangleVertexArray) { * @param indexSubpart The index of the sub-part of the mesh * @return A pointer to the triangle vertex array of a given sub-part of the mesh */ -inline TriangleVertexArray* TriangleMesh::getSubpart(uint indexSubpart) const { +RP3D_FORCE_INLINE TriangleVertexArray* TriangleMesh::getSubpart(uint indexSubpart) const { assert(indexSubpart < mTriangleArrays.size()); return mTriangleArrays[indexSubpart]; } @@ -96,7 +96,7 @@ inline TriangleVertexArray* TriangleMesh::getSubpart(uint indexSubpart) const { /** * @return The number of sub-parts of the mesh */ -inline uint TriangleMesh::getNbSubparts() const { +RP3D_FORCE_INLINE uint TriangleMesh::getNbSubparts() const { return mTriangleArrays.size(); } diff --git a/include/reactphysics3d/collision/TriangleVertexArray.h b/include/reactphysics3d/collision/TriangleVertexArray.h index b5002be5..f68c8e22 100644 --- a/include/reactphysics3d/collision/TriangleVertexArray.h +++ b/include/reactphysics3d/collision/TriangleVertexArray.h @@ -182,7 +182,7 @@ class TriangleVertexArray { /** * @return The data type of the vertices in the array */ -inline TriangleVertexArray::VertexDataType TriangleVertexArray::getVertexDataType() const { +RP3D_FORCE_INLINE TriangleVertexArray::VertexDataType TriangleVertexArray::getVertexDataType() const { return mVertexDataType; } @@ -190,7 +190,7 @@ inline TriangleVertexArray::VertexDataType TriangleVertexArray::getVertexDataTyp /** * @return The data type of the normals in the array */ -inline TriangleVertexArray::NormalDataType TriangleVertexArray::getVertexNormalDataType() const { +RP3D_FORCE_INLINE TriangleVertexArray::NormalDataType TriangleVertexArray::getVertexNormalDataType() const { return mVertexNormaldDataType; } @@ -198,7 +198,7 @@ inline TriangleVertexArray::NormalDataType TriangleVertexArray::getVertexNormalD /** * @return The data type of the face indices in the array */ -inline TriangleVertexArray::IndexDataType TriangleVertexArray::getIndexDataType() const { +RP3D_FORCE_INLINE TriangleVertexArray::IndexDataType TriangleVertexArray::getIndexDataType() const { return mIndexDataType; } @@ -206,7 +206,7 @@ inline TriangleVertexArray::IndexDataType TriangleVertexArray::getIndexDataType( /** * @return The number of vertices in the array */ -inline uint TriangleVertexArray::getNbVertices() const { +RP3D_FORCE_INLINE uint TriangleVertexArray::getNbVertices() const { return mNbVertices; } @@ -214,7 +214,7 @@ inline uint TriangleVertexArray::getNbVertices() const { /** * @return The number of triangles in the array */ -inline uint TriangleVertexArray::getNbTriangles() const { +RP3D_FORCE_INLINE uint TriangleVertexArray::getNbTriangles() const { return mNbTriangles; } @@ -222,7 +222,7 @@ inline uint TriangleVertexArray::getNbTriangles() const { /** * @return The number of bytes separating two consecutive vertices in the array */ -inline uint TriangleVertexArray::getVerticesStride() const { +RP3D_FORCE_INLINE uint TriangleVertexArray::getVerticesStride() const { return mVerticesStride; } @@ -230,7 +230,7 @@ inline uint TriangleVertexArray::getVerticesStride() const { /** * @return The number of bytes separating two consecutive normals in the array */ -inline uint TriangleVertexArray::getVerticesNormalsStride() const { +RP3D_FORCE_INLINE uint TriangleVertexArray::getVerticesNormalsStride() const { return mVerticesNormalsStride; } @@ -238,7 +238,7 @@ inline uint TriangleVertexArray::getVerticesNormalsStride() const { /** * @return The number of bytes separating two consecutive face indices in the array */ -inline uint TriangleVertexArray::getIndicesStride() const { +RP3D_FORCE_INLINE uint TriangleVertexArray::getIndicesStride() const { return mIndicesStride; } @@ -246,7 +246,7 @@ inline uint TriangleVertexArray::getIndicesStride() const { /** * @return A pointer to the start of the vertices data in the array */ -inline const void* TriangleVertexArray::getVerticesStart() const { +RP3D_FORCE_INLINE const void* TriangleVertexArray::getVerticesStart() const { return mVerticesStart; } @@ -254,7 +254,7 @@ inline const void* TriangleVertexArray::getVerticesStart() const { /** * @return A pointer to the start of the normals data in the array */ -inline const void* TriangleVertexArray::getVerticesNormalsStart() const { +RP3D_FORCE_INLINE const void* TriangleVertexArray::getVerticesNormalsStart() const { return mVerticesNormalsStart; } @@ -262,7 +262,7 @@ inline const void* TriangleVertexArray::getVerticesNormalsStart() const { /** * @return A pointer to the start of the face indices data in the array */ -inline const void* TriangleVertexArray::getIndicesStart() const { +RP3D_FORCE_INLINE const void* TriangleVertexArray::getIndicesStart() const { return mIndicesStart; } diff --git a/include/reactphysics3d/collision/broadphase/DynamicAABBTree.h b/include/reactphysics3d/collision/broadphase/DynamicAABBTree.h index 315d54d1..1ff0aa67 100644 --- a/include/reactphysics3d/collision/broadphase/DynamicAABBTree.h +++ b/include/reactphysics3d/collision/broadphase/DynamicAABBTree.h @@ -264,38 +264,38 @@ class DynamicAABBTree { }; // Return true if the node is a leaf of the tree -inline bool TreeNode::isLeaf() const { +RP3D_FORCE_INLINE bool TreeNode::isLeaf() const { return (height == 0); } // Return the fat AABB corresponding to a given node ID -inline const AABB& DynamicAABBTree::getFatAABB(int32 nodeID) const { +RP3D_FORCE_INLINE const AABB& DynamicAABBTree::getFatAABB(int32 nodeID) const { assert(nodeID >= 0 && nodeID < mNbAllocatedNodes); return mNodes[nodeID].aabb; } // Return the pointer to the data array of a given leaf node of the tree -inline int32* DynamicAABBTree::getNodeDataInt(int32 nodeID) const { +RP3D_FORCE_INLINE int32* DynamicAABBTree::getNodeDataInt(int32 nodeID) const { assert(nodeID >= 0 && nodeID < mNbAllocatedNodes); assert(mNodes[nodeID].isLeaf()); return mNodes[nodeID].dataInt; } // Return the pointer to the data pointer of a given leaf node of the tree -inline void* DynamicAABBTree::getNodeDataPointer(int32 nodeID) const { +RP3D_FORCE_INLINE void* DynamicAABBTree::getNodeDataPointer(int32 nodeID) const { assert(nodeID >= 0 && nodeID < mNbAllocatedNodes); assert(mNodes[nodeID].isLeaf()); return mNodes[nodeID].dataPointer; } // Return the root AABB of the tree -inline AABB DynamicAABBTree::getRootAABB() const { +RP3D_FORCE_INLINE AABB DynamicAABBTree::getRootAABB() const { return getFatAABB(mRootNodeID); } // Add an object into the tree. This method creates a new leaf node in the tree and // returns the ID of the corresponding node. -inline int32 DynamicAABBTree::addObject(const AABB& aabb, int32 data1, int32 data2) { +RP3D_FORCE_INLINE int32 DynamicAABBTree::addObject(const AABB& aabb, int32 data1, int32 data2) { int32 nodeId = addObjectInternal(aabb); @@ -307,7 +307,7 @@ inline int32 DynamicAABBTree::addObject(const AABB& aabb, int32 data1, int32 dat // Add an object into the tree. This method creates a new leaf node in the tree and // returns the ID of the corresponding node. -inline int32 DynamicAABBTree::addObject(const AABB& aabb, void* data) { +RP3D_FORCE_INLINE int32 DynamicAABBTree::addObject(const AABB& aabb, void* data) { int32 nodeId = addObjectInternal(aabb); @@ -319,7 +319,7 @@ inline int32 DynamicAABBTree::addObject(const AABB& aabb, void* data) { #ifdef IS_RP3D_PROFILING_ENABLED // Set the profiler -inline void DynamicAABBTree::setProfiler(Profiler* profiler) { +RP3D_FORCE_INLINE void DynamicAABBTree::setProfiler(Profiler* profiler) { mProfiler = profiler; } diff --git a/include/reactphysics3d/collision/narrowphase/CollisionDispatch.h b/include/reactphysics3d/collision/narrowphase/CollisionDispatch.h index 8b77f8d9..95bfb990 100644 --- a/include/reactphysics3d/collision/narrowphase/CollisionDispatch.h +++ b/include/reactphysics3d/collision/narrowphase/CollisionDispatch.h @@ -173,39 +173,39 @@ class CollisionDispatch { }; // Get the Sphere vs Sphere narrow-phase collision detection algorithm -inline SphereVsSphereAlgorithm* CollisionDispatch::getSphereVsSphereAlgorithm() { +RP3D_FORCE_INLINE SphereVsSphereAlgorithm* CollisionDispatch::getSphereVsSphereAlgorithm() { return mSphereVsSphereAlgorithm; } // Get the Sphere vs Capsule narrow-phase collision detection algorithm -inline SphereVsCapsuleAlgorithm* CollisionDispatch::getSphereVsCapsuleAlgorithm() { +RP3D_FORCE_INLINE SphereVsCapsuleAlgorithm* CollisionDispatch::getSphereVsCapsuleAlgorithm() { return mSphereVsCapsuleAlgorithm; } // Get the Capsule vs Capsule narrow-phase collision detection algorithm -inline CapsuleVsCapsuleAlgorithm* CollisionDispatch::getCapsuleVsCapsuleAlgorithm() { +RP3D_FORCE_INLINE CapsuleVsCapsuleAlgorithm* CollisionDispatch::getCapsuleVsCapsuleAlgorithm() { return mCapsuleVsCapsuleAlgorithm; } // Get the Sphere vs Convex Polyhedron narrow-phase collision detection algorithm -inline SphereVsConvexPolyhedronAlgorithm* CollisionDispatch::getSphereVsConvexPolyhedronAlgorithm() { +RP3D_FORCE_INLINE SphereVsConvexPolyhedronAlgorithm* CollisionDispatch::getSphereVsConvexPolyhedronAlgorithm() { return mSphereVsConvexPolyhedronAlgorithm; } // Get the Capsule vs Convex Polyhedron narrow-phase collision detection algorithm -inline CapsuleVsConvexPolyhedronAlgorithm* CollisionDispatch::getCapsuleVsConvexPolyhedronAlgorithm() { +RP3D_FORCE_INLINE CapsuleVsConvexPolyhedronAlgorithm* CollisionDispatch::getCapsuleVsConvexPolyhedronAlgorithm() { return mCapsuleVsConvexPolyhedronAlgorithm; } // Get the Convex Polyhedron vs Convex Polyhedron narrow-phase collision detection algorithm -inline ConvexPolyhedronVsConvexPolyhedronAlgorithm* CollisionDispatch::getConvexPolyhedronVsConvexPolyhedronAlgorithm() { +RP3D_FORCE_INLINE ConvexPolyhedronVsConvexPolyhedronAlgorithm* CollisionDispatch::getConvexPolyhedronVsConvexPolyhedronAlgorithm() { return mConvexPolyhedronVsConvexPolyhedronAlgorithm; } #ifdef IS_RP3D_PROFILING_ENABLED // Set the profiler -inline void CollisionDispatch::setProfiler(Profiler* profiler) { +RP3D_FORCE_INLINE void CollisionDispatch::setProfiler(Profiler* profiler) { mProfiler = profiler; mSphereVsSphereAlgorithm->setProfiler(profiler); diff --git a/include/reactphysics3d/collision/narrowphase/GJK/GJKAlgorithm.h b/include/reactphysics3d/collision/narrowphase/GJK/GJKAlgorithm.h index 7b727920..2aba4e73 100644 --- a/include/reactphysics3d/collision/narrowphase/GJK/GJKAlgorithm.h +++ b/include/reactphysics3d/collision/narrowphase/GJK/GJKAlgorithm.h @@ -112,7 +112,7 @@ class GJKAlgorithm { #ifdef IS_RP3D_PROFILING_ENABLED // Set the profiler -inline void GJKAlgorithm::setProfiler(Profiler* profiler) { +RP3D_FORCE_INLINE void GJKAlgorithm::setProfiler(Profiler* profiler) { mProfiler = profiler; } diff --git a/include/reactphysics3d/collision/narrowphase/GJK/VoronoiSimplex.h b/include/reactphysics3d/collision/narrowphase/GJK/VoronoiSimplex.h index 4d721b0d..fb9ddc8d 100644 --- a/include/reactphysics3d/collision/narrowphase/GJK/VoronoiSimplex.h +++ b/include/reactphysics3d/collision/narrowphase/GJK/VoronoiSimplex.h @@ -171,17 +171,17 @@ class VoronoiSimplex { }; // Return true if the simplex contains 4 points -inline bool VoronoiSimplex::isFull() const { +RP3D_FORCE_INLINE bool VoronoiSimplex::isFull() const { return mNbPoints == 4; } // Return true if the simple is empty -inline bool VoronoiSimplex::isEmpty() const { +RP3D_FORCE_INLINE bool VoronoiSimplex::isEmpty() const { return mNbPoints == 0; } // Set the barycentric coordinates of the closest point -inline void VoronoiSimplex::setBarycentricCoords(decimal a, decimal b, decimal c, decimal d) { +RP3D_FORCE_INLINE void VoronoiSimplex::setBarycentricCoords(decimal a, decimal b, decimal c, decimal d) { mBarycentricCoords[0] = a; mBarycentricCoords[1] = b; mBarycentricCoords[2] = c; @@ -189,7 +189,7 @@ inline void VoronoiSimplex::setBarycentricCoords(decimal a, decimal b, decimal c } // Compute the closest point "v" to the origin of the current simplex. -inline bool VoronoiSimplex::computeClosestPoint(Vector3& v) { +RP3D_FORCE_INLINE bool VoronoiSimplex::computeClosestPoint(Vector3& v) { bool isValid = recomputeClosestPoint(); v = mClosestPoint; @@ -197,7 +197,7 @@ inline bool VoronoiSimplex::computeClosestPoint(Vector3& v) { } // Return true if the -inline bool VoronoiSimplex::checkClosestPointValid() const { +RP3D_FORCE_INLINE bool VoronoiSimplex::checkClosestPointValid() const { return mBarycentricCoords[0] >= decimal(0.0) && mBarycentricCoords[1] >= decimal(0.0) && mBarycentricCoords[2] >= decimal(0.0) && mBarycentricCoords[3] >= decimal(0.0); } diff --git a/include/reactphysics3d/collision/narrowphase/NarrowPhaseAlgorithm.h b/include/reactphysics3d/collision/narrowphase/NarrowPhaseAlgorithm.h index c360b2bc..444b1744 100644 --- a/include/reactphysics3d/collision/narrowphase/NarrowPhaseAlgorithm.h +++ b/include/reactphysics3d/collision/narrowphase/NarrowPhaseAlgorithm.h @@ -106,7 +106,7 @@ class NarrowPhaseAlgorithm { #ifdef IS_RP3D_PROFILING_ENABLED // Set the profiler -inline void NarrowPhaseAlgorithm::setProfiler(Profiler* profiler) { +RP3D_FORCE_INLINE void NarrowPhaseAlgorithm::setProfiler(Profiler* profiler) { mProfiler = profiler; } diff --git a/include/reactphysics3d/collision/narrowphase/NarrowPhaseInfoBatch.h b/include/reactphysics3d/collision/narrowphase/NarrowPhaseInfoBatch.h index 06b8e0cc..bcad2c5b 100644 --- a/include/reactphysics3d/collision/narrowphase/NarrowPhaseInfoBatch.h +++ b/include/reactphysics3d/collision/narrowphase/NarrowPhaseInfoBatch.h @@ -29,6 +29,7 @@ // Libraries #include #include +#include /// Namespace ReactPhysics3D namespace reactphysics3d { @@ -145,10 +146,46 @@ struct NarrowPhaseInfoBatch { }; /// Return the number of objects in the batch -inline uint NarrowPhaseInfoBatch::getNbObjects() const { +RP3D_FORCE_INLINE uint NarrowPhaseInfoBatch::getNbObjects() const { return narrowPhaseInfos.size(); } +// Add shapes to be tested during narrow-phase collision detection into the batch +RP3D_FORCE_INLINE void NarrowPhaseInfoBatch::addNarrowPhaseInfo(uint64 pairId, uint64 pairIndex, Entity collider1, Entity collider2, CollisionShape* shape1, + CollisionShape* shape2, const Transform& shape1Transform, const Transform& shape2Transform, + bool needToReportContacts, MemoryAllocator& shapeAllocator) { + + // Add a collision info for the two collision shapes into the overlapping pair (if not present yet) + // TODO OPTI : Can we better manage this + LastFrameCollisionInfo* lastFrameInfo = mOverlappingPairs.addLastFrameInfoIfNecessary(pairIndex, shape1->getId(), shape2->getId()); + + // Create a meta data object + narrowPhaseInfos.emplace(pairId, collider1, collider2, lastFrameInfo, shapeAllocator, shape1Transform, shape2Transform, shape1, shape2, needToReportContacts); +} + +// Add a new contact point +RP3D_FORCE_INLINE void NarrowPhaseInfoBatch::addContactPoint(uint index, const Vector3& contactNormal, decimal penDepth, const Vector3& localPt1, const Vector3& localPt2) { + + assert(penDepth > decimal(0.0)); + + if (narrowPhaseInfos[index].nbContactPoints < NB_MAX_CONTACT_POINTS_IN_NARROWPHASE_INFO) { + + assert(contactNormal.length() > 0.8f); + + // Add it into the array of contact points + narrowPhaseInfos[index].contactPoints[narrowPhaseInfos[index].nbContactPoints].normal = contactNormal; + narrowPhaseInfos[index].contactPoints[narrowPhaseInfos[index].nbContactPoints].penetrationDepth = penDepth; + narrowPhaseInfos[index].contactPoints[narrowPhaseInfos[index].nbContactPoints].localPoint1 = localPt1; + narrowPhaseInfos[index].contactPoints[narrowPhaseInfos[index].nbContactPoints].localPoint2 = localPt2; + narrowPhaseInfos[index].nbContactPoints++; + } +} + +// Reset the remaining contact points +RP3D_FORCE_INLINE void NarrowPhaseInfoBatch::resetContactPoints(uint index) { + narrowPhaseInfos[index].nbContactPoints = 0; +} + } #endif diff --git a/include/reactphysics3d/collision/narrowphase/NarrowPhaseInput.h b/include/reactphysics3d/collision/narrowphase/NarrowPhaseInput.h index 4ad189da..639117a7 100644 --- a/include/reactphysics3d/collision/narrowphase/NarrowPhaseInput.h +++ b/include/reactphysics3d/collision/narrowphase/NarrowPhaseInput.h @@ -29,6 +29,7 @@ // Libraries #include #include +#include /// Namespace ReactPhysics3D namespace reactphysics3d { @@ -97,34 +98,64 @@ class NarrowPhaseInput { // Get a reference to the sphere vs sphere batch contacts -inline NarrowPhaseInfoBatch& NarrowPhaseInput::getSphereVsSphereBatch() { +RP3D_FORCE_INLINE NarrowPhaseInfoBatch& NarrowPhaseInput::getSphereVsSphereBatch() { return mSphereVsSphereBatch; } // Get a reference to the sphere vs capsule batch contacts -inline NarrowPhaseInfoBatch& NarrowPhaseInput::getSphereVsCapsuleBatch() { +RP3D_FORCE_INLINE NarrowPhaseInfoBatch& NarrowPhaseInput::getSphereVsCapsuleBatch() { return mSphereVsCapsuleBatch; } // Get a reference to the capsule vs capsule batch contacts -inline NarrowPhaseInfoBatch& NarrowPhaseInput::getCapsuleVsCapsuleBatch() { +RP3D_FORCE_INLINE NarrowPhaseInfoBatch& NarrowPhaseInput::getCapsuleVsCapsuleBatch() { return mCapsuleVsCapsuleBatch; } // Get a reference to the sphere vs convex polyhedron batch contacts -inline NarrowPhaseInfoBatch& NarrowPhaseInput::getSphereVsConvexPolyhedronBatch() { +RP3D_FORCE_INLINE NarrowPhaseInfoBatch& NarrowPhaseInput::getSphereVsConvexPolyhedronBatch() { return mSphereVsConvexPolyhedronBatch; } // Get a reference to the capsule vs convex polyhedron batch contacts -inline NarrowPhaseInfoBatch& NarrowPhaseInput::getCapsuleVsConvexPolyhedronBatch() { +RP3D_FORCE_INLINE NarrowPhaseInfoBatch& NarrowPhaseInput::getCapsuleVsConvexPolyhedronBatch() { return mCapsuleVsConvexPolyhedronBatch; } // Get a reference to the convex polyhedron vs convex polyhedron batch contacts -inline NarrowPhaseInfoBatch &NarrowPhaseInput::getConvexPolyhedronVsConvexPolyhedronBatch() { +RP3D_FORCE_INLINE NarrowPhaseInfoBatch &NarrowPhaseInput::getConvexPolyhedronVsConvexPolyhedronBatch() { return mConvexPolyhedronVsConvexPolyhedronBatch; } +// Add shapes to be tested during narrow-phase collision detection into the batch +RP3D_FORCE_INLINE void NarrowPhaseInput::addNarrowPhaseTest(uint64 pairId, uint64 pairIndex, Entity collider1, Entity collider2, CollisionShape* shape1, CollisionShape* shape2, + const Transform& shape1Transform, const Transform& shape2Transform, + NarrowPhaseAlgorithmType narrowPhaseAlgorithmType, bool reportContacts, MemoryAllocator& shapeAllocator) { + + switch (narrowPhaseAlgorithmType) { + case NarrowPhaseAlgorithmType::SphereVsSphere: + mSphereVsSphereBatch.addNarrowPhaseInfo(pairId, pairIndex, collider1, collider2, shape1, shape2, shape1Transform, shape2Transform, reportContacts, shapeAllocator); + break; + case NarrowPhaseAlgorithmType::SphereVsCapsule: + mSphereVsCapsuleBatch.addNarrowPhaseInfo(pairId, pairIndex, collider1, collider2, shape1, shape2, shape1Transform, shape2Transform, reportContacts, shapeAllocator); + break; + case NarrowPhaseAlgorithmType::CapsuleVsCapsule: + mCapsuleVsCapsuleBatch.addNarrowPhaseInfo(pairId, pairIndex, collider1, collider2, shape1, shape2, shape1Transform, shape2Transform, reportContacts, shapeAllocator); + break; + case NarrowPhaseAlgorithmType::SphereVsConvexPolyhedron: + mSphereVsConvexPolyhedronBatch.addNarrowPhaseInfo(pairId, pairIndex, collider1, collider2, shape1, shape2, shape1Transform, shape2Transform, reportContacts, shapeAllocator); + break; + case NarrowPhaseAlgorithmType::CapsuleVsConvexPolyhedron: + mCapsuleVsConvexPolyhedronBatch.addNarrowPhaseInfo(pairId, pairIndex, collider1, collider2, shape1, shape2, shape1Transform, shape2Transform, reportContacts, shapeAllocator); + break; + case NarrowPhaseAlgorithmType::ConvexPolyhedronVsConvexPolyhedron: + mConvexPolyhedronVsConvexPolyhedronBatch.addNarrowPhaseInfo(pairId, pairIndex, collider1, collider2, shape1, shape2, shape1Transform, shape2Transform, reportContacts, shapeAllocator); + break; + case NarrowPhaseAlgorithmType::None: + // Must never happen + assert(false); + break; + } +} } #endif diff --git a/include/reactphysics3d/collision/narrowphase/SAT/SATAlgorithm.h b/include/reactphysics3d/collision/narrowphase/SAT/SATAlgorithm.h index 9f8a4bef..9f486013 100644 --- a/include/reactphysics3d/collision/narrowphase/SAT/SATAlgorithm.h +++ b/include/reactphysics3d/collision/narrowphase/SAT/SATAlgorithm.h @@ -185,7 +185,7 @@ class SATAlgorithm { #ifdef IS_RP3D_PROFILING_ENABLED // Set the profiler -inline void SATAlgorithm::setProfiler(Profiler* profiler) { +RP3D_FORCE_INLINE void SATAlgorithm::setProfiler(Profiler* profiler) { mProfiler = profiler; } diff --git a/include/reactphysics3d/collision/shapes/AABB.h b/include/reactphysics3d/collision/shapes/AABB.h index 17aea61c..5dbcb6f1 100644 --- a/include/reactphysics3d/collision/shapes/AABB.h +++ b/include/reactphysics3d/collision/shapes/AABB.h @@ -127,44 +127,44 @@ class AABB { }; // Return the center point of the AABB in world coordinates -inline Vector3 AABB::getCenter() const { +RP3D_FORCE_INLINE Vector3 AABB::getCenter() const { return (mMinCoordinates + mMaxCoordinates) * decimal(0.5); } // Return the minimum coordinates of the AABB -inline const Vector3& AABB::getMin() const { +RP3D_FORCE_INLINE const Vector3& AABB::getMin() const { return mMinCoordinates; } // Set the minimum coordinates of the AABB -inline void AABB::setMin(const Vector3& min) { +RP3D_FORCE_INLINE void AABB::setMin(const Vector3& min) { mMinCoordinates = min; } // Return the maximum coordinates of the AABB -inline const Vector3& AABB::getMax() const { +RP3D_FORCE_INLINE const Vector3& AABB::getMax() const { return mMaxCoordinates; } // Set the maximum coordinates of the AABB -inline void AABB::setMax(const Vector3& max) { +RP3D_FORCE_INLINE void AABB::setMax(const Vector3& max) { mMaxCoordinates = max; } // Return the size of the AABB in the three dimension x, y and z -inline Vector3 AABB::getExtent() const { +RP3D_FORCE_INLINE Vector3 AABB::getExtent() const { return mMaxCoordinates - mMinCoordinates; } // Inflate each side of the AABB by a given size -inline void AABB::inflate(decimal dx, decimal dy, decimal dz) { +RP3D_FORCE_INLINE void AABB::inflate(decimal dx, decimal dy, decimal dz) { mMaxCoordinates += Vector3(dx, dy, dz); mMinCoordinates -= Vector3(dx, dy, dz); } // Return true if the current AABB is overlapping with the AABB in argument. /// Two AABBs overlap if they overlap in the three x, y and z axis at the same time -inline bool AABB::testCollision(const AABB& aabb) const { +RP3D_FORCE_INLINE bool AABB::testCollision(const AABB& aabb) const { if (mMaxCoordinates.x < aabb.mMinCoordinates.x || aabb.mMaxCoordinates.x < mMinCoordinates.x) return false; if (mMaxCoordinates.y < aabb.mMinCoordinates.y || @@ -175,13 +175,13 @@ inline bool AABB::testCollision(const AABB& aabb) const { } // Return the volume of the AABB -inline decimal AABB::getVolume() const { +RP3D_FORCE_INLINE decimal AABB::getVolume() const { const Vector3 diff = mMaxCoordinates - mMinCoordinates; return (diff.x * diff.y * diff.z); } // Return true if the AABB of a triangle intersects the AABB -inline bool AABB::testCollisionTriangleAABB(const Vector3* trianglePoints) const { +RP3D_FORCE_INLINE bool AABB::testCollisionTriangleAABB(const Vector3* trianglePoints) const { if (min3(trianglePoints[0].x, trianglePoints[1].x, trianglePoints[2].x) > mMaxCoordinates.x) return false; if (min3(trianglePoints[0].y, trianglePoints[1].y, trianglePoints[2].y) > mMaxCoordinates.y) return false; @@ -195,7 +195,7 @@ inline bool AABB::testCollisionTriangleAABB(const Vector3* trianglePoints) const } // Return true if a point is inside the AABB -inline bool AABB::contains(const Vector3& point) const { +RP3D_FORCE_INLINE bool AABB::contains(const Vector3& point) const { return (point.x >= mMinCoordinates.x - MACHINE_EPSILON && point.x <= mMaxCoordinates.x + MACHINE_EPSILON && point.y >= mMinCoordinates.y - MACHINE_EPSILON && point.y <= mMaxCoordinates.y + MACHINE_EPSILON && @@ -203,13 +203,13 @@ inline bool AABB::contains(const Vector3& point) const { } // Apply a scale factor to the AABB -inline void AABB::applyScale(const Vector3& scale) { +RP3D_FORCE_INLINE void AABB::applyScale(const Vector3& scale) { mMinCoordinates = mMinCoordinates * scale; mMaxCoordinates = mMaxCoordinates * scale; } // Assignment operator -inline AABB& AABB::operator=(const AABB& aabb) { +RP3D_FORCE_INLINE AABB& AABB::operator=(const AABB& aabb) { if (this != &aabb) { mMinCoordinates = aabb.mMinCoordinates; mMaxCoordinates = aabb.mMaxCoordinates; diff --git a/include/reactphysics3d/collision/shapes/BoxShape.h b/include/reactphysics3d/collision/shapes/BoxShape.h index fc78c353..c54def68 100644 --- a/include/reactphysics3d/collision/shapes/BoxShape.h +++ b/include/reactphysics3d/collision/shapes/BoxShape.h @@ -140,7 +140,7 @@ class BoxShape : public ConvexPolyhedronShape { /** * @return The vector with the three half-extents of the box shape */ -inline Vector3 BoxShape::getHalfExtents() const { +RP3D_FORCE_INLINE Vector3 BoxShape::getHalfExtents() const { return mHalfExtents; } @@ -150,7 +150,7 @@ inline Vector3 BoxShape::getHalfExtents() const { /** * @param halfExtents The vector with the three half-extents of the box */ -inline void BoxShape::setHalfExtents(const Vector3& halfExtents) { +RP3D_FORCE_INLINE void BoxShape::setHalfExtents(const Vector3& halfExtents) { mHalfExtents = halfExtents; notifyColliderAboutChangedSize(); @@ -162,7 +162,7 @@ inline void BoxShape::setHalfExtents(const Vector3& halfExtents) { * @param min The minimum bounds of the shape in local-space coordinates * @param max The maximum bounds of the shape in local-space coordinates */ -inline void BoxShape::getLocalBounds(Vector3& min, Vector3& max) const { +RP3D_FORCE_INLINE void BoxShape::getLocalBounds(Vector3& min, Vector3& max) const { // Maximum bounds max = mHalfExtents; @@ -172,12 +172,12 @@ inline void BoxShape::getLocalBounds(Vector3& min, Vector3& max) const { } // Return the number of bytes used by the collision shape -inline size_t BoxShape::getSizeInBytes() const { +RP3D_FORCE_INLINE size_t BoxShape::getSizeInBytes() const { return sizeof(BoxShape); } // Return a local support point in a given direction without the object margin -inline Vector3 BoxShape::getLocalSupportPointWithoutMargin(const Vector3& direction) const { +RP3D_FORCE_INLINE Vector3 BoxShape::getLocalSupportPointWithoutMargin(const Vector3& direction) const { return Vector3(direction.x < decimal(0.0) ? -mHalfExtents.x : mHalfExtents.x, direction.y < decimal(0.0) ? -mHalfExtents.y : mHalfExtents.y, @@ -185,36 +185,36 @@ inline Vector3 BoxShape::getLocalSupportPointWithoutMargin(const Vector3& direct } // Return true if a point is inside the collision shape -inline bool BoxShape::testPointInside(const Vector3& localPoint, Collider* collider) const { +RP3D_FORCE_INLINE bool BoxShape::testPointInside(const Vector3& localPoint, Collider* collider) const { return (localPoint.x < mHalfExtents[0] && localPoint.x > -mHalfExtents[0] && localPoint.y < mHalfExtents[1] && localPoint.y > -mHalfExtents[1] && localPoint.z < mHalfExtents[2] && localPoint.z > -mHalfExtents[2]); } // Return the number of faces of the polyhedron -inline uint BoxShape::getNbFaces() const { +RP3D_FORCE_INLINE uint BoxShape::getNbFaces() const { return 6; } // Return a given face of the polyhedron -inline const HalfEdgeStructure::Face& BoxShape::getFace(uint faceIndex) const { +RP3D_FORCE_INLINE const HalfEdgeStructure::Face& BoxShape::getFace(uint faceIndex) const { assert(faceIndex < mHalfEdgeStructure.getNbFaces()); return mHalfEdgeStructure.getFace(faceIndex); } // Return the number of vertices of the polyhedron -inline uint BoxShape::getNbVertices() const { +RP3D_FORCE_INLINE uint BoxShape::getNbVertices() const { return 8; } // Return a given vertex of the polyhedron -inline HalfEdgeStructure::Vertex BoxShape::getVertex(uint vertexIndex) const { +RP3D_FORCE_INLINE HalfEdgeStructure::Vertex BoxShape::getVertex(uint vertexIndex) const { assert(vertexIndex < getNbVertices()); return mHalfEdgeStructure.getVertex(vertexIndex); } // Return the position of a given vertex -inline Vector3 BoxShape::getVertexPosition(uint vertexIndex) const { +RP3D_FORCE_INLINE Vector3 BoxShape::getVertexPosition(uint vertexIndex) const { assert(vertexIndex < getNbVertices()); Vector3 extent = getHalfExtents(); @@ -235,7 +235,7 @@ inline Vector3 BoxShape::getVertexPosition(uint vertexIndex) const { } // Return the normal vector of a given face of the polyhedron -inline Vector3 BoxShape::getFaceNormal(uint faceIndex) const { +RP3D_FORCE_INLINE Vector3 BoxShape::getFaceNormal(uint faceIndex) const { assert(faceIndex < getNbFaces()); switch(faceIndex) { @@ -252,27 +252,27 @@ inline Vector3 BoxShape::getFaceNormal(uint faceIndex) const { } // Return the centroid of the box -inline Vector3 BoxShape::getCentroid() const { +RP3D_FORCE_INLINE Vector3 BoxShape::getCentroid() const { return Vector3::zero(); } // Compute and return the volume of the collision shape -inline decimal BoxShape::getVolume() const { +RP3D_FORCE_INLINE decimal BoxShape::getVolume() const { return 8 * mHalfExtents.x * mHalfExtents.y * mHalfExtents.z; } // Return the string representation of the shape -inline std::string BoxShape::to_string() const { +RP3D_FORCE_INLINE std::string BoxShape::to_string() const { return "BoxShape{extents=" + mHalfExtents.to_string() + "}"; } // Return the number of half-edges of the polyhedron -inline uint BoxShape::getNbHalfEdges() const { +RP3D_FORCE_INLINE uint BoxShape::getNbHalfEdges() const { return 24; } // Return a given half-edge of the polyhedron -inline const HalfEdgeStructure::Edge& BoxShape::getHalfEdge(uint edgeIndex) const { +RP3D_FORCE_INLINE const HalfEdgeStructure::Edge& BoxShape::getHalfEdge(uint edgeIndex) const { assert(edgeIndex < getNbHalfEdges()); return mHalfEdgeStructure.getHalfEdge(edgeIndex); } diff --git a/include/reactphysics3d/collision/shapes/CapsuleShape.h b/include/reactphysics3d/collision/shapes/CapsuleShape.h index b9cfbc63..f8a54643 100644 --- a/include/reactphysics3d/collision/shapes/CapsuleShape.h +++ b/include/reactphysics3d/collision/shapes/CapsuleShape.h @@ -126,7 +126,7 @@ class CapsuleShape : public ConvexShape { /** * @return The radius of the capsule shape (in meters) */ -inline decimal CapsuleShape::getRadius() const { +RP3D_FORCE_INLINE decimal CapsuleShape::getRadius() const { return mMargin; } @@ -136,7 +136,7 @@ inline decimal CapsuleShape::getRadius() const { /** * @param radius The radius of the capsule (in meters) */ -inline void CapsuleShape::setRadius(decimal radius) { +RP3D_FORCE_INLINE void CapsuleShape::setRadius(decimal radius) { assert(radius > decimal(0.0)); mMargin = radius; @@ -148,7 +148,7 @@ inline void CapsuleShape::setRadius(decimal radius) { /** * @return The height of the capsule shape (in meters) */ -inline decimal CapsuleShape::getHeight() const { +RP3D_FORCE_INLINE decimal CapsuleShape::getHeight() const { return mHalfHeight + mHalfHeight; } @@ -158,7 +158,7 @@ inline decimal CapsuleShape::getHeight() const { /** * @param height The height of the capsule (in meters) */ -inline void CapsuleShape::setHeight(decimal height) { +RP3D_FORCE_INLINE void CapsuleShape::setHeight(decimal height) { assert(height > decimal(0.0)); mHalfHeight = height * decimal(0.5); @@ -167,7 +167,7 @@ inline void CapsuleShape::setHeight(decimal height) { } // Return the number of bytes used by the collision shape -inline size_t CapsuleShape::getSizeInBytes() const { +RP3D_FORCE_INLINE size_t CapsuleShape::getSizeInBytes() const { return sizeof(CapsuleShape); } @@ -177,7 +177,7 @@ inline size_t CapsuleShape::getSizeInBytes() const { * @param min The minimum bounds of the shape in local-space coordinates * @param max The maximum bounds of the shape in local-space coordinates */ -inline void CapsuleShape::getLocalBounds(Vector3& min, Vector3& max) const { +RP3D_FORCE_INLINE void CapsuleShape::getLocalBounds(Vector3& min, Vector3& max) const { // Maximum bounds max.x = mMargin; @@ -191,12 +191,12 @@ inline void CapsuleShape::getLocalBounds(Vector3& min, Vector3& max) const { } // Compute and return the volume of the collision shape -inline decimal CapsuleShape::getVolume() const { +RP3D_FORCE_INLINE decimal CapsuleShape::getVolume() const { return reactphysics3d::PI_RP3D * mMargin * mMargin * (decimal(4.0) * mMargin / decimal(3.0) + decimal(2.0) * mHalfHeight); } // Return true if the collision shape is a polyhedron -inline bool CapsuleShape::isPolyhedron() const { +RP3D_FORCE_INLINE bool CapsuleShape::isPolyhedron() const { return false; } @@ -207,7 +207,7 @@ inline bool CapsuleShape::isPolyhedron() const { /// Therefore, in this method, we compute the support points of both top and bottom spheres of /// the capsule and return the point with the maximum dot product with the direction vector. Note /// that the object margin is implicitly the radius and height of the capsule. -inline Vector3 CapsuleShape::getLocalSupportPointWithoutMargin(const Vector3& direction) const { +RP3D_FORCE_INLINE Vector3 CapsuleShape::getLocalSupportPointWithoutMargin(const Vector3& direction) const { // Support point top sphere decimal dotProductTop = mHalfHeight * direction.y; @@ -225,7 +225,7 @@ inline Vector3 CapsuleShape::getLocalSupportPointWithoutMargin(const Vector3& di } // Return the string representation of the shape -inline std::string CapsuleShape::to_string() const { +RP3D_FORCE_INLINE std::string CapsuleShape::to_string() const { return "CapsuleShape{halfHeight=" + std::to_string(mHalfHeight) + ", radius=" + std::to_string(getRadius()) + "}"; } diff --git a/include/reactphysics3d/collision/shapes/CollisionShape.h b/include/reactphysics3d/collision/shapes/CollisionShape.h index 0fdca0b4..beca8bcb 100644 --- a/include/reactphysics3d/collision/shapes/CollisionShape.h +++ b/include/reactphysics3d/collision/shapes/CollisionShape.h @@ -172,7 +172,7 @@ class CollisionShape { /** * @return The name of the collision shape (box, sphere, triangle, ...) */ -inline CollisionShapeName CollisionShape::getName() const { +RP3D_FORCE_INLINE CollisionShapeName CollisionShape::getName() const { return mName; } @@ -180,29 +180,29 @@ inline CollisionShapeName CollisionShape::getName() const { /** * @return The type of the collision shape (sphere, capsule, convex polyhedron, concave mesh) */ -inline CollisionShapeType CollisionShape::getType() const { +RP3D_FORCE_INLINE CollisionShapeType CollisionShape::getType() const { return mType; } // Return the id of the shape -inline uint32 CollisionShape::getId() const { +RP3D_FORCE_INLINE uint32 CollisionShape::getId() const { return mId; } // Assign a new collider to the collision shape -inline void CollisionShape::addCollider(Collider* collider) { +RP3D_FORCE_INLINE void CollisionShape::addCollider(Collider* collider) { mColliders.add(collider); } // Remove an assigned collider from the collision shape -inline void CollisionShape::removeCollider(Collider* collider) { +RP3D_FORCE_INLINE void CollisionShape::removeCollider(Collider* collider) { mColliders.remove(collider); } #ifdef IS_RP3D_PROFILING_ENABLED // Set the profiler -inline void CollisionShape::setProfiler(Profiler* profiler) { +RP3D_FORCE_INLINE void CollisionShape::setProfiler(Profiler* profiler) { mProfiler = profiler; } diff --git a/include/reactphysics3d/collision/shapes/ConcaveMeshShape.h b/include/reactphysics3d/collision/shapes/ConcaveMeshShape.h index 3ecfc5e0..dab3b9dd 100644 --- a/include/reactphysics3d/collision/shapes/ConcaveMeshShape.h +++ b/include/reactphysics3d/collision/shapes/ConcaveMeshShape.h @@ -212,7 +212,7 @@ class ConcaveMeshShape : public ConcaveShape { }; // Return the number of bytes used by the collision shape -inline size_t ConcaveMeshShape::getSizeInBytes() const { +RP3D_FORCE_INLINE size_t ConcaveMeshShape::getSizeInBytes() const { return sizeof(ConcaveMeshShape); } @@ -222,7 +222,7 @@ inline size_t ConcaveMeshShape::getSizeInBytes() const { * @param min The minimum bounds of the shape in local-space coordinates * @param max The maximum bounds of the shape in local-space coordinates */ -inline void ConcaveMeshShape::getLocalBounds(Vector3& min, Vector3& max) const { +RP3D_FORCE_INLINE void ConcaveMeshShape::getLocalBounds(Vector3& min, Vector3& max) const { // Get the AABB of the whole tree AABB treeAABB = mDynamicAABBTree.getRootAABB(); @@ -233,7 +233,7 @@ inline void ConcaveMeshShape::getLocalBounds(Vector3& min, Vector3& max) const { // Called when a overlapping node has been found during the call to // DynamicAABBTree:reportAllShapesOverlappingWithAABB() -inline void ConvexTriangleAABBOverlapCallback::notifyOverlappingNode(int nodeId) { +RP3D_FORCE_INLINE void ConvexTriangleAABBOverlapCallback::notifyOverlappingNode(int nodeId) { // Get the node data (triangle index and mesh subpart index) int32* data = mDynamicAABBTree.getNodeDataInt(nodeId); @@ -253,7 +253,7 @@ inline void ConvexTriangleAABBOverlapCallback::notifyOverlappingNode(int nodeId) #ifdef IS_RP3D_PROFILING_ENABLED // Set the profiler -inline void ConcaveMeshShape::setProfiler(Profiler* profiler) { +RP3D_FORCE_INLINE void ConcaveMeshShape::setProfiler(Profiler* profiler) { CollisionShape::setProfiler(profiler); diff --git a/include/reactphysics3d/collision/shapes/ConcaveShape.h b/include/reactphysics3d/collision/shapes/ConcaveShape.h index d4ec18eb..3ee13b42 100644 --- a/include/reactphysics3d/collision/shapes/ConcaveShape.h +++ b/include/reactphysics3d/collision/shapes/ConcaveShape.h @@ -120,22 +120,22 @@ class ConcaveShape : public CollisionShape { }; // Return true if the collision shape is convex, false if it is concave -inline bool ConcaveShape::isConvex() const { +RP3D_FORCE_INLINE bool ConcaveShape::isConvex() const { return false; } // Return true if the collision shape is a polyhedron -inline bool ConcaveShape::isPolyhedron() const { +RP3D_FORCE_INLINE bool ConcaveShape::isPolyhedron() const { return true; } // Return true if a point is inside the collision shape -inline bool ConcaveShape::testPointInside(const Vector3& localPoint, Collider* collider) const { +RP3D_FORCE_INLINE bool ConcaveShape::testPointInside(const Vector3& localPoint, Collider* collider) const { return false; } // Return the raycast test type (front, back, front-back) -inline TriangleRaycastSide ConcaveShape::getRaycastTestType() const { +RP3D_FORCE_INLINE TriangleRaycastSide ConcaveShape::getRaycastTestType() const { return mRaycastTestType; } @@ -143,19 +143,19 @@ inline TriangleRaycastSide ConcaveShape::getRaycastTestType() const { /** * @param testType Raycast test type for the triangle (front, back, front-back) */ -inline void ConcaveShape::setRaycastTestType(TriangleRaycastSide testType) { +RP3D_FORCE_INLINE void ConcaveShape::setRaycastTestType(TriangleRaycastSide testType) { mRaycastTestType = testType; } // Return the scale of the shape -inline const Vector3& ConcaveShape::getScale() const { +RP3D_FORCE_INLINE const Vector3& ConcaveShape::getScale() const { return mScale; } // Set the scale of the shape /// Note that you might want to recompute the inertia tensor and center of mass of the body /// after changing the scale of a collision shape -inline void ConcaveShape::setScale(const Vector3& scale) { +RP3D_FORCE_INLINE void ConcaveShape::setScale(const Vector3& scale) { mScale = scale; notifyColliderAboutChangedSize(); @@ -165,7 +165,7 @@ inline void ConcaveShape::setScale(const Vector3& scale) { /** * @param mass Mass to use to compute the inertia tensor of the collision shape */ -inline Vector3 ConcaveShape::getLocalInertiaTensor(decimal mass) const { +RP3D_FORCE_INLINE Vector3 ConcaveShape::getLocalInertiaTensor(decimal mass) const { // Default inertia tensor // Note that this is not very realistic for a concave triangle mesh. diff --git a/include/reactphysics3d/collision/shapes/ConvexMeshShape.h b/include/reactphysics3d/collision/shapes/ConvexMeshShape.h index 0cbb5c43..f8a629e0 100644 --- a/include/reactphysics3d/collision/shapes/ConvexMeshShape.h +++ b/include/reactphysics3d/collision/shapes/ConvexMeshShape.h @@ -147,19 +147,19 @@ class ConvexMeshShape : public ConvexPolyhedronShape { }; // Return the number of bytes used by the collision shape -inline size_t ConvexMeshShape::getSizeInBytes() const { +RP3D_FORCE_INLINE size_t ConvexMeshShape::getSizeInBytes() const { return sizeof(ConvexMeshShape); } // Return the scaling vector -inline const Vector3& ConvexMeshShape::getScale() const { +RP3D_FORCE_INLINE const Vector3& ConvexMeshShape::getScale() const { return mScale; } // Set the scale /// Note that you might want to recompute the inertia tensor and center of mass of the body /// after changing the scale of a collision shape -inline void ConvexMeshShape::setScale(const Vector3& scale) { +RP3D_FORCE_INLINE void ConvexMeshShape::setScale(const Vector3& scale) { mScale = scale; recalculateBounds(); notifyColliderAboutChangedSize(); @@ -170,7 +170,7 @@ inline void ConvexMeshShape::setScale(const Vector3& scale) { * @param min The minimum bounds of the shape in local-space coordinates * @param max The maximum bounds of the shape in local-space coordinates */ -inline void ConvexMeshShape::getLocalBounds(Vector3& min, Vector3& max) const { +RP3D_FORCE_INLINE void ConvexMeshShape::getLocalBounds(Vector3& min, Vector3& max) const { min = mMinBounds; max = mMaxBounds; } @@ -181,7 +181,7 @@ inline void ConvexMeshShape::getLocalBounds(Vector3& min, Vector3& max) const { /** * @param mass Mass to use to compute the inertia tensor of the collision shape */ -inline Vector3 ConvexMeshShape::getLocalInertiaTensor(decimal mass) const { +RP3D_FORCE_INLINE Vector3 ConvexMeshShape::getLocalInertiaTensor(decimal mass) const { const decimal factor = (decimal(1.0) / decimal(3.0)) * mass; const Vector3 realExtent = decimal(0.5) * (mMaxBounds - mMinBounds); assert(realExtent.x > 0 && realExtent.y > 0 && realExtent.z > 0); @@ -192,58 +192,58 @@ inline Vector3 ConvexMeshShape::getLocalInertiaTensor(decimal mass) const { } // Return the number of faces of the polyhedron -inline uint ConvexMeshShape::getNbFaces() const { +RP3D_FORCE_INLINE uint ConvexMeshShape::getNbFaces() const { return mPolyhedronMesh->getHalfEdgeStructure().getNbFaces(); } // Return a given face of the polyhedron -inline const HalfEdgeStructure::Face& ConvexMeshShape::getFace(uint faceIndex) const { +RP3D_FORCE_INLINE const HalfEdgeStructure::Face& ConvexMeshShape::getFace(uint faceIndex) const { assert(faceIndex < getNbFaces()); return mPolyhedronMesh->getHalfEdgeStructure().getFace(faceIndex); } // Return the number of vertices of the polyhedron -inline uint ConvexMeshShape::getNbVertices() const { +RP3D_FORCE_INLINE uint ConvexMeshShape::getNbVertices() const { return mPolyhedronMesh->getHalfEdgeStructure().getNbVertices(); } // Return a given vertex of the polyhedron -inline HalfEdgeStructure::Vertex ConvexMeshShape::getVertex(uint vertexIndex) const { +RP3D_FORCE_INLINE HalfEdgeStructure::Vertex ConvexMeshShape::getVertex(uint vertexIndex) const { assert(vertexIndex < getNbVertices()); return mPolyhedronMesh->getHalfEdgeStructure().getVertex(vertexIndex); } // Return the number of half-edges of the polyhedron -inline uint ConvexMeshShape::getNbHalfEdges() const { +RP3D_FORCE_INLINE uint ConvexMeshShape::getNbHalfEdges() const { return mPolyhedronMesh->getHalfEdgeStructure().getNbHalfEdges(); } // Return a given half-edge of the polyhedron -inline const HalfEdgeStructure::Edge& ConvexMeshShape::getHalfEdge(uint edgeIndex) const { +RP3D_FORCE_INLINE const HalfEdgeStructure::Edge& ConvexMeshShape::getHalfEdge(uint edgeIndex) const { assert(edgeIndex < getNbHalfEdges()); return mPolyhedronMesh->getHalfEdgeStructure().getHalfEdge(edgeIndex); } // Return the position of a given vertex -inline Vector3 ConvexMeshShape::getVertexPosition(uint vertexIndex) const { +RP3D_FORCE_INLINE Vector3 ConvexMeshShape::getVertexPosition(uint vertexIndex) const { assert(vertexIndex < getNbVertices()); return mPolyhedronMesh->getVertex(vertexIndex) * mScale; } // Return the normal vector of a given face of the polyhedron -inline Vector3 ConvexMeshShape::getFaceNormal(uint faceIndex) const { +RP3D_FORCE_INLINE Vector3 ConvexMeshShape::getFaceNormal(uint faceIndex) const { assert(faceIndex < getNbFaces()); return mPolyhedronMesh->getFaceNormal(faceIndex); } // Return the centroid of the polyhedron -inline Vector3 ConvexMeshShape::getCentroid() const { +RP3D_FORCE_INLINE Vector3 ConvexMeshShape::getCentroid() const { return mPolyhedronMesh->getCentroid() * mScale; } // Compute and return the volume of the collision shape -inline decimal ConvexMeshShape::getVolume() const { +RP3D_FORCE_INLINE decimal ConvexMeshShape::getVolume() const { return mPolyhedronMesh->getVolume(); } diff --git a/include/reactphysics3d/collision/shapes/ConvexPolyhedronShape.h b/include/reactphysics3d/collision/shapes/ConvexPolyhedronShape.h index 6a037cf8..d723d28a 100644 --- a/include/reactphysics3d/collision/shapes/ConvexPolyhedronShape.h +++ b/include/reactphysics3d/collision/shapes/ConvexPolyhedronShape.h @@ -94,7 +94,7 @@ class ConvexPolyhedronShape : public ConvexShape { }; // Return true if the collision shape is a polyhedron -inline bool ConvexPolyhedronShape::isPolyhedron() const { +RP3D_FORCE_INLINE bool ConvexPolyhedronShape::isPolyhedron() const { return true; } diff --git a/include/reactphysics3d/collision/shapes/ConvexShape.h b/include/reactphysics3d/collision/shapes/ConvexShape.h index 9335d662..ddb1c467 100644 --- a/include/reactphysics3d/collision/shapes/ConvexShape.h +++ b/include/reactphysics3d/collision/shapes/ConvexShape.h @@ -83,7 +83,7 @@ class ConvexShape : public CollisionShape { }; // Return true if the collision shape is convex, false if it is concave -inline bool ConvexShape::isConvex() const { +RP3D_FORCE_INLINE bool ConvexShape::isConvex() const { return true; } @@ -91,7 +91,7 @@ inline bool ConvexShape::isConvex() const { /** * @return The margin (in meters) around the collision shape */ -inline decimal ConvexShape::getMargin() const { +RP3D_FORCE_INLINE decimal ConvexShape::getMargin() const { return mMargin; } diff --git a/include/reactphysics3d/collision/shapes/HeightFieldShape.h b/include/reactphysics3d/collision/shapes/HeightFieldShape.h index adb4c947..a6405334 100644 --- a/include/reactphysics3d/collision/shapes/HeightFieldShape.h +++ b/include/reactphysics3d/collision/shapes/HeightFieldShape.h @@ -167,27 +167,27 @@ class HeightFieldShape : public ConcaveShape { }; // Return the number of rows in the height field -inline int HeightFieldShape::getNbRows() const { +RP3D_FORCE_INLINE int HeightFieldShape::getNbRows() const { return mNbRows; } // Return the number of columns in the height field -inline int HeightFieldShape::getNbColumns() const { +RP3D_FORCE_INLINE int HeightFieldShape::getNbColumns() const { return mNbColumns; } // Return the type of height value in the height field -inline HeightFieldShape::HeightDataType HeightFieldShape::getHeightDataType() const { +RP3D_FORCE_INLINE HeightFieldShape::HeightDataType HeightFieldShape::getHeightDataType() const { return mHeightDataType; } // Return the number of bytes used by the collision shape -inline size_t HeightFieldShape::getSizeInBytes() const { +RP3D_FORCE_INLINE size_t HeightFieldShape::getSizeInBytes() const { return sizeof(HeightFieldShape); } // Return the height of a given (x,y) point in the height field -inline decimal HeightFieldShape::getHeightAt(int x, int y) const { +RP3D_FORCE_INLINE decimal HeightFieldShape::getHeightAt(int x, int y) const { assert(x >= 0 && x < mNbColumns); assert(y >= 0 && y < mNbRows); @@ -201,12 +201,12 @@ inline decimal HeightFieldShape::getHeightAt(int x, int y) const { } // Return the closest inside integer grid value of a given floating grid value -inline int HeightFieldShape::computeIntegerGridValue(decimal value) const { +RP3D_FORCE_INLINE int HeightFieldShape::computeIntegerGridValue(decimal value) const { return (value < decimal(0.0)) ? value - decimal(0.5) : value + decimal(0.5); } // Compute the shape Id for a given triangle -inline uint HeightFieldShape::computeTriangleShapeId(uint iIndex, uint jIndex, uint secondTriangleIncrement) const { +RP3D_FORCE_INLINE uint HeightFieldShape::computeTriangleShapeId(uint iIndex, uint jIndex, uint secondTriangleIncrement) const { return (jIndex * (mNbColumns - 1) + iIndex) * 2 + secondTriangleIncrement; } diff --git a/include/reactphysics3d/collision/shapes/SphereShape.h b/include/reactphysics3d/collision/shapes/SphereShape.h index 97126eec..6db4d4cd 100644 --- a/include/reactphysics3d/collision/shapes/SphereShape.h +++ b/include/reactphysics3d/collision/shapes/SphereShape.h @@ -111,7 +111,7 @@ class SphereShape : public ConvexShape { /** * @return Radius of the sphere */ -inline decimal SphereShape::getRadius() const { +RP3D_FORCE_INLINE decimal SphereShape::getRadius() const { return mMargin; } @@ -121,7 +121,7 @@ inline decimal SphereShape::getRadius() const { /** * @param radius Radius of the sphere */ -inline void SphereShape::setRadius(decimal radius) { +RP3D_FORCE_INLINE void SphereShape::setRadius(decimal radius) { assert(radius > decimal(0.0)); mMargin = radius; @@ -132,7 +132,7 @@ inline void SphereShape::setRadius(decimal radius) { /** * @return False because the sphere shape is not a polyhedron */ -inline bool SphereShape::isPolyhedron() const { +RP3D_FORCE_INLINE bool SphereShape::isPolyhedron() const { return false; } @@ -140,12 +140,12 @@ inline bool SphereShape::isPolyhedron() const { /** * @return The size (in bytes) of the sphere shape */ -inline size_t SphereShape::getSizeInBytes() const { +RP3D_FORCE_INLINE size_t SphereShape::getSizeInBytes() const { return sizeof(SphereShape); } // Return a local support point in a given direction without the object margin -inline Vector3 SphereShape::getLocalSupportPointWithoutMargin(const Vector3& direction) const { +RP3D_FORCE_INLINE Vector3 SphereShape::getLocalSupportPointWithoutMargin(const Vector3& direction) const { // Return the center of the sphere (the radius is taken into account in the object margin) return Vector3(0.0, 0.0, 0.0); @@ -157,7 +157,7 @@ inline Vector3 SphereShape::getLocalSupportPointWithoutMargin(const Vector3& dir * @param min The minimum bounds of the shape in local-space coordinates * @param max The maximum bounds of the shape in local-space coordinates */ -inline void SphereShape::getLocalBounds(Vector3& min, Vector3& max) const { +RP3D_FORCE_INLINE void SphereShape::getLocalBounds(Vector3& min, Vector3& max) const { // Maximum bounds max.x = mMargin; @@ -174,23 +174,23 @@ inline void SphereShape::getLocalBounds(Vector3& min, Vector3& max) const { /** * @param mass Mass to use to compute the inertia tensor of the collision shape */ -inline Vector3 SphereShape::getLocalInertiaTensor(decimal mass) const { +RP3D_FORCE_INLINE Vector3 SphereShape::getLocalInertiaTensor(decimal mass) const { decimal diag = decimal(0.4) * mass * mMargin * mMargin; return Vector3(diag, diag, diag); } // Compute and return the volume of the collision shape -inline decimal SphereShape::getVolume() const { +RP3D_FORCE_INLINE decimal SphereShape::getVolume() const { return decimal(4.0) / decimal(3.0) * reactphysics3d::PI_RP3D * mMargin * mMargin * mMargin; } // Return true if a point is inside the collision shape -inline bool SphereShape::testPointInside(const Vector3& localPoint, Collider* collider) const { +RP3D_FORCE_INLINE bool SphereShape::testPointInside(const Vector3& localPoint, Collider* collider) const { return (localPoint.lengthSquare() < mMargin * mMargin); } // Return the string representation of the shape -inline std::string SphereShape::to_string() const { +RP3D_FORCE_INLINE std::string SphereShape::to_string() const { return "SphereShape{radius=" + std::to_string(getRadius()) + "}"; } diff --git a/include/reactphysics3d/collision/shapes/TriangleShape.h b/include/reactphysics3d/collision/shapes/TriangleShape.h index 9bfc754d..823f33bd 100644 --- a/include/reactphysics3d/collision/shapes/TriangleShape.h +++ b/include/reactphysics3d/collision/shapes/TriangleShape.h @@ -188,12 +188,12 @@ class TriangleShape : public ConvexPolyhedronShape { }; // Return the number of bytes used by the collision shape -inline size_t TriangleShape::getSizeInBytes() const { +RP3D_FORCE_INLINE size_t TriangleShape::getSizeInBytes() const { return sizeof(TriangleShape); } // Return a local support point in a given direction without the object margin -inline Vector3 TriangleShape::getLocalSupportPointWithoutMargin(const Vector3& direction) const { +RP3D_FORCE_INLINE Vector3 TriangleShape::getLocalSupportPointWithoutMargin(const Vector3& direction) const { Vector3 dotProducts(direction.dot(mPoints[0]), direction.dot(mPoints[1]), direction.dot(mPoints[2])); return mPoints[dotProducts.getMaxAxis()]; } @@ -204,7 +204,7 @@ inline Vector3 TriangleShape::getLocalSupportPointWithoutMargin(const Vector3& d * @param min The minimum bounds of the shape in local-space coordinates * @param max The maximum bounds of the shape in local-space coordinates */ -inline void TriangleShape::getLocalBounds(Vector3& min, Vector3& max) const { +RP3D_FORCE_INLINE void TriangleShape::getLocalBounds(Vector3& min, Vector3& max) const { const Vector3 xAxis(mPoints[0].x, mPoints[1].x, mPoints[2].x); const Vector3 yAxis(mPoints[0].y, mPoints[1].y, mPoints[2].y); @@ -222,33 +222,33 @@ inline void TriangleShape::getLocalBounds(Vector3& min, Vector3& max) const { * coordinates * @param mass Mass to use to compute the inertia tensor of the collision shape */ -inline Vector3 TriangleShape::getLocalInertiaTensor(decimal mass) const { +RP3D_FORCE_INLINE Vector3 TriangleShape::getLocalInertiaTensor(decimal mass) const { return Vector3(0, 0, 0); } // Return true if a point is inside the collision shape -inline bool TriangleShape::testPointInside(const Vector3& localPoint, Collider* collider) const { +RP3D_FORCE_INLINE bool TriangleShape::testPointInside(const Vector3& localPoint, Collider* collider) const { return false; } // Return the number of faces of the polyhedron -inline uint TriangleShape::getNbFaces() const { +RP3D_FORCE_INLINE uint TriangleShape::getNbFaces() const { return 2; } // Return a given face of the polyhedron -inline const HalfEdgeStructure::Face& TriangleShape::getFace(uint faceIndex) const { +RP3D_FORCE_INLINE const HalfEdgeStructure::Face& TriangleShape::getFace(uint faceIndex) const { assert(faceIndex < 2); return mFaces[faceIndex]; } // Return the number of vertices of the polyhedron -inline uint TriangleShape::getNbVertices() const { +RP3D_FORCE_INLINE uint TriangleShape::getNbVertices() const { return 3; } // Return a given vertex of the polyhedron -inline HalfEdgeStructure::Vertex TriangleShape::getVertex(uint vertexIndex) const { +RP3D_FORCE_INLINE HalfEdgeStructure::Vertex TriangleShape::getVertex(uint vertexIndex) const { assert(vertexIndex < 3); HalfEdgeStructure::Vertex vertex(vertexIndex); @@ -261,35 +261,35 @@ inline HalfEdgeStructure::Vertex TriangleShape::getVertex(uint vertexIndex) cons } // Return a given half-edge of the polyhedron -inline const HalfEdgeStructure::Edge& TriangleShape::getHalfEdge(uint edgeIndex) const { +RP3D_FORCE_INLINE const HalfEdgeStructure::Edge& TriangleShape::getHalfEdge(uint edgeIndex) const { assert(edgeIndex < getNbHalfEdges()); return mEdges[edgeIndex]; } // Return the position of a given vertex -inline Vector3 TriangleShape::getVertexPosition(uint vertexIndex) const { +RP3D_FORCE_INLINE Vector3 TriangleShape::getVertexPosition(uint vertexIndex) const { assert(vertexIndex < 3); return mPoints[vertexIndex]; } // Return the normal vector of a given face of the polyhedron -inline Vector3 TriangleShape::getFaceNormal(uint faceIndex) const { +RP3D_FORCE_INLINE Vector3 TriangleShape::getFaceNormal(uint faceIndex) const { assert(faceIndex < 2); return faceIndex == 0 ? mNormal : -mNormal; } // Return the centroid of the box -inline Vector3 TriangleShape::getCentroid() const { +RP3D_FORCE_INLINE Vector3 TriangleShape::getCentroid() const { return (mPoints[0] + mPoints[1] + mPoints[2]) / decimal(3.0); } // Return the number of half-edges of the polyhedron -inline uint TriangleShape::getNbHalfEdges() const { +RP3D_FORCE_INLINE uint TriangleShape::getNbHalfEdges() const { return 6; } // Return the raycast test type (front, back, front-back) -inline TriangleRaycastSide TriangleShape::getRaycastTestType() const { +RP3D_FORCE_INLINE TriangleRaycastSide TriangleShape::getRaycastTestType() const { return mRaycastTestType; } @@ -297,18 +297,18 @@ inline TriangleRaycastSide TriangleShape::getRaycastTestType() const { /** * @param testType Raycast test type for the triangle (front, back, front-back) */ -inline void TriangleShape::setRaycastTestType(TriangleRaycastSide testType) { +RP3D_FORCE_INLINE void TriangleShape::setRaycastTestType(TriangleRaycastSide testType) { mRaycastTestType = testType; } // Return the string representation of the shape -inline std::string TriangleShape::to_string() const { +RP3D_FORCE_INLINE std::string TriangleShape::to_string() const { return "TriangleShape{v1=" + mPoints[0].to_string() + ", v2=" + mPoints[1].to_string() + "," + "v3=" + mPoints[2].to_string() + "}"; } // Compute and return the volume of the collision shape -inline decimal TriangleShape::getVolume() const { +RP3D_FORCE_INLINE decimal TriangleShape::getVolume() const { return decimal(0.0); } diff --git a/include/reactphysics3d/components/BallAndSocketJointComponents.h b/include/reactphysics3d/components/BallAndSocketJointComponents.h index 7778bf10..75ec4081 100644 --- a/include/reactphysics3d/components/BallAndSocketJointComponents.h +++ b/include/reactphysics3d/components/BallAndSocketJointComponents.h @@ -188,140 +188,140 @@ class BallAndSocketJointComponents : public Components { }; // Return a pointer to a given joint -inline BallAndSocketJoint* BallAndSocketJointComponents::getJoint(Entity jointEntity) const { +RP3D_FORCE_INLINE BallAndSocketJoint* BallAndSocketJointComponents::getJoint(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mJoints[mMapEntityToComponentIndex[jointEntity]]; } // Set the joint pointer to a given joint -inline void BallAndSocketJointComponents::setJoint(Entity jointEntity, BallAndSocketJoint* joint) const { +RP3D_FORCE_INLINE void BallAndSocketJointComponents::setJoint(Entity jointEntity, BallAndSocketJoint* joint) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mJoints[mMapEntityToComponentIndex[jointEntity]] = joint; } // Return the local anchor point of body 1 for a given joint -inline const Vector3& BallAndSocketJointComponents::getLocalAnchorPointBody1(Entity jointEntity) const { +RP3D_FORCE_INLINE const Vector3& BallAndSocketJointComponents::getLocalAnchorPointBody1(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mLocalAnchorPointBody1[mMapEntityToComponentIndex[jointEntity]]; } // Set the local anchor point of body 1 for a given joint -inline void BallAndSocketJointComponents::setLocalAnchorPointBody1(Entity jointEntity, const Vector3& localAnchoirPointBody1) { +RP3D_FORCE_INLINE void BallAndSocketJointComponents::setLocalAnchorPointBody1(Entity jointEntity, const Vector3& localAnchoirPointBody1) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mLocalAnchorPointBody1[mMapEntityToComponentIndex[jointEntity]] = localAnchoirPointBody1; } // Return the local anchor point of body 2 for a given joint -inline const Vector3& BallAndSocketJointComponents::getLocalAnchorPointBody2(Entity jointEntity) const { +RP3D_FORCE_INLINE const Vector3& BallAndSocketJointComponents::getLocalAnchorPointBody2(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mLocalAnchorPointBody2[mMapEntityToComponentIndex[jointEntity]]; } // Set the local anchor point of body 2 for a given joint -inline void BallAndSocketJointComponents::setLocalAnchorPointBody2(Entity jointEntity, const Vector3& localAnchoirPointBody2) { +RP3D_FORCE_INLINE void BallAndSocketJointComponents::setLocalAnchorPointBody2(Entity jointEntity, const Vector3& localAnchoirPointBody2) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mLocalAnchorPointBody2[mMapEntityToComponentIndex[jointEntity]] = localAnchoirPointBody2; } // Return the vector from center of body 1 to anchor point in world-space -inline const Vector3& BallAndSocketJointComponents::getR1World(Entity jointEntity) const { +RP3D_FORCE_INLINE const Vector3& BallAndSocketJointComponents::getR1World(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mR1World[mMapEntityToComponentIndex[jointEntity]]; } // Set the vector from center of body 1 to anchor point in world-space -inline void BallAndSocketJointComponents::setR1World(Entity jointEntity, const Vector3& r1World) { +RP3D_FORCE_INLINE void BallAndSocketJointComponents::setR1World(Entity jointEntity, const Vector3& r1World) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mR1World[mMapEntityToComponentIndex[jointEntity]] = r1World; } // Return the vector from center of body 2 to anchor point in world-space -inline const Vector3& BallAndSocketJointComponents::getR2World(Entity jointEntity) const { +RP3D_FORCE_INLINE const Vector3& BallAndSocketJointComponents::getR2World(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mR2World[mMapEntityToComponentIndex[jointEntity]]; } // Set the vector from center of body 2 to anchor point in world-space -inline void BallAndSocketJointComponents::setR2World(Entity jointEntity, const Vector3& r2World) { +RP3D_FORCE_INLINE void BallAndSocketJointComponents::setR2World(Entity jointEntity, const Vector3& r2World) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mR2World[mMapEntityToComponentIndex[jointEntity]] = r2World; } // Return the inertia tensor of body 1 (in world-space coordinates) -inline const Matrix3x3& BallAndSocketJointComponents::getI1(Entity jointEntity) const { +RP3D_FORCE_INLINE const Matrix3x3& BallAndSocketJointComponents::getI1(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mI1[mMapEntityToComponentIndex[jointEntity]]; } // Set the inertia tensor of body 1 (in world-space coordinates) -inline void BallAndSocketJointComponents::setI1(Entity jointEntity, const Matrix3x3& i1) { +RP3D_FORCE_INLINE void BallAndSocketJointComponents::setI1(Entity jointEntity, const Matrix3x3& i1) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mI1[mMapEntityToComponentIndex[jointEntity]] = i1; } // Return the inertia tensor of body 2 (in world-space coordinates) -inline const Matrix3x3& BallAndSocketJointComponents::getI2(Entity jointEntity) const { +RP3D_FORCE_INLINE const Matrix3x3& BallAndSocketJointComponents::getI2(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mI2[mMapEntityToComponentIndex[jointEntity]]; } // Set the inertia tensor of body 2 (in world-space coordinates) -inline void BallAndSocketJointComponents::setI2(Entity jointEntity, const Matrix3x3& i2) { +RP3D_FORCE_INLINE void BallAndSocketJointComponents::setI2(Entity jointEntity, const Matrix3x3& i2) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mI2[mMapEntityToComponentIndex[jointEntity]] = i2; } // Return the bias vector for the constraint -inline Vector3 &BallAndSocketJointComponents::getBiasVector(Entity jointEntity) { +RP3D_FORCE_INLINE Vector3 &BallAndSocketJointComponents::getBiasVector(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mBiasVector[mMapEntityToComponentIndex[jointEntity]]; } // Set the bias vector for the constraint -inline void BallAndSocketJointComponents::setBiasVector(Entity jointEntity, const Vector3& biasVector) { +RP3D_FORCE_INLINE void BallAndSocketJointComponents::setBiasVector(Entity jointEntity, const Vector3& biasVector) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mBiasVector[mMapEntityToComponentIndex[jointEntity]] = biasVector; } // Return the inverse mass matrix K=JM^-1J^-t of the constraint -inline Matrix3x3& BallAndSocketJointComponents::getInverseMassMatrix(Entity jointEntity) { +RP3D_FORCE_INLINE Matrix3x3& BallAndSocketJointComponents::getInverseMassMatrix(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mInverseMassMatrix[mMapEntityToComponentIndex[jointEntity]]; } // Set the inverse mass matrix K=JM^-1J^-t of the constraint -inline void BallAndSocketJointComponents::setInverseMassMatrix(Entity jointEntity, const Matrix3x3& inverseMassMatrix) { +RP3D_FORCE_INLINE void BallAndSocketJointComponents::setInverseMassMatrix(Entity jointEntity, const Matrix3x3& inverseMassMatrix) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mInverseMassMatrix[mMapEntityToComponentIndex[jointEntity]] = inverseMassMatrix; } // Return the accumulated impulse -inline Vector3 &BallAndSocketJointComponents::getImpulse(Entity jointEntity) { +RP3D_FORCE_INLINE Vector3 &BallAndSocketJointComponents::getImpulse(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mImpulse[mMapEntityToComponentIndex[jointEntity]]; } // Set the accumulated impulse -inline void BallAndSocketJointComponents::setImpulse(Entity jointEntity, const Vector3& impulse) { +RP3D_FORCE_INLINE void BallAndSocketJointComponents::setImpulse(Entity jointEntity, const Vector3& impulse) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mImpulse[mMapEntityToComponentIndex[jointEntity]] = impulse; diff --git a/include/reactphysics3d/components/ColliderComponents.h b/include/reactphysics3d/components/ColliderComponents.h index 86c6c5af..8dfa5bf3 100644 --- a/include/reactphysics3d/components/ColliderComponents.h +++ b/include/reactphysics3d/components/ColliderComponents.h @@ -213,7 +213,7 @@ class ColliderComponents : public Components { }; // Return the body entity of a given collider -inline Entity ColliderComponents::getBody(Entity colliderEntity) const { +RP3D_FORCE_INLINE Entity ColliderComponents::getBody(Entity colliderEntity) const { assert(mMapEntityToComponentIndex.containsKey(colliderEntity)); @@ -221,7 +221,7 @@ inline Entity ColliderComponents::getBody(Entity colliderEntity) const { } // Return a pointer to a given collider -inline Collider *ColliderComponents::getCollider(Entity colliderEntity) const { +RP3D_FORCE_INLINE Collider *ColliderComponents::getCollider(Entity colliderEntity) const { assert(mMapEntityToComponentIndex.containsKey(colliderEntity)); @@ -229,7 +229,7 @@ inline Collider *ColliderComponents::getCollider(Entity colliderEntity) const { } // Return the local-to-body transform of a collider -inline const Transform& ColliderComponents::getLocalToBodyTransform(Entity colliderEntity) const { +RP3D_FORCE_INLINE const Transform& ColliderComponents::getLocalToBodyTransform(Entity colliderEntity) const { assert(mMapEntityToComponentIndex.containsKey(colliderEntity)); @@ -237,7 +237,7 @@ inline const Transform& ColliderComponents::getLocalToBodyTransform(Entity colli } // Set the local-to-body transform of a collider -inline void ColliderComponents::setLocalToBodyTransform(Entity colliderEntity, const Transform& transform) { +RP3D_FORCE_INLINE void ColliderComponents::setLocalToBodyTransform(Entity colliderEntity, const Transform& transform) { assert(mMapEntityToComponentIndex.containsKey(colliderEntity)); @@ -245,7 +245,7 @@ inline void ColliderComponents::setLocalToBodyTransform(Entity colliderEntity, c } // Return a pointer to the collision shape of a collider -inline CollisionShape* ColliderComponents::getCollisionShape(Entity colliderEntity) const { +RP3D_FORCE_INLINE CollisionShape* ColliderComponents::getCollisionShape(Entity colliderEntity) const { assert(mMapEntityToComponentIndex.containsKey(colliderEntity)); @@ -253,7 +253,7 @@ inline CollisionShape* ColliderComponents::getCollisionShape(Entity colliderEnti } // Return the broad-phase id of a given collider -inline int32 ColliderComponents::getBroadPhaseId(Entity colliderEntity) const { +RP3D_FORCE_INLINE int32 ColliderComponents::getBroadPhaseId(Entity colliderEntity) const { assert(mMapEntityToComponentIndex.containsKey(colliderEntity)); @@ -261,7 +261,7 @@ inline int32 ColliderComponents::getBroadPhaseId(Entity colliderEntity) const { } // Set the broad-phase id of a given collider -inline void ColliderComponents::setBroadPhaseId(Entity colliderEntity, int32 broadPhaseId) { +RP3D_FORCE_INLINE void ColliderComponents::setBroadPhaseId(Entity colliderEntity, int32 broadPhaseId) { assert(mMapEntityToComponentIndex.containsKey(colliderEntity)); @@ -269,7 +269,7 @@ inline void ColliderComponents::setBroadPhaseId(Entity colliderEntity, int32 bro } // Return the collision category bits of a given collider -inline unsigned short ColliderComponents::getCollisionCategoryBits(Entity colliderEntity) const { +RP3D_FORCE_INLINE unsigned short ColliderComponents::getCollisionCategoryBits(Entity colliderEntity) const { assert(mMapEntityToComponentIndex.containsKey(colliderEntity)); @@ -277,7 +277,7 @@ inline unsigned short ColliderComponents::getCollisionCategoryBits(Entity collid } // Return the "collide with" mask bits of a given collider -inline unsigned short ColliderComponents::getCollideWithMaskBits(Entity colliderEntity) const { +RP3D_FORCE_INLINE unsigned short ColliderComponents::getCollideWithMaskBits(Entity colliderEntity) const { assert(mMapEntityToComponentIndex.containsKey(colliderEntity)); @@ -285,7 +285,7 @@ inline unsigned short ColliderComponents::getCollideWithMaskBits(Entity collider } // Set the collision category bits of a given collider -inline void ColliderComponents::setCollisionCategoryBits(Entity colliderEntity, unsigned short collisionCategoryBits) { +RP3D_FORCE_INLINE void ColliderComponents::setCollisionCategoryBits(Entity colliderEntity, unsigned short collisionCategoryBits) { assert(mMapEntityToComponentIndex.containsKey(colliderEntity)); @@ -293,7 +293,7 @@ inline void ColliderComponents::setCollisionCategoryBits(Entity colliderEntity, } // Set the "collide with" mask bits of a given collider -inline void ColliderComponents::setCollideWithMaskBits(Entity colliderEntity, unsigned short collideWithMaskBits) { +RP3D_FORCE_INLINE void ColliderComponents::setCollideWithMaskBits(Entity colliderEntity, unsigned short collideWithMaskBits) { assert(mMapEntityToComponentIndex.containsKey(colliderEntity)); @@ -301,7 +301,7 @@ inline void ColliderComponents::setCollideWithMaskBits(Entity colliderEntity, un } // Return the local-to-world transform of a collider -inline const Transform& ColliderComponents::getLocalToWorldTransform(Entity colliderEntity) const { +RP3D_FORCE_INLINE const Transform& ColliderComponents::getLocalToWorldTransform(Entity colliderEntity) const { assert(mMapEntityToComponentIndex.containsKey(colliderEntity)); @@ -309,7 +309,7 @@ inline const Transform& ColliderComponents::getLocalToWorldTransform(Entity coll } // Set the local-to-world transform of a collider -inline void ColliderComponents::setLocalToWorldTransform(Entity colliderEntity, const Transform& transform) { +RP3D_FORCE_INLINE void ColliderComponents::setLocalToWorldTransform(Entity colliderEntity, const Transform& transform) { assert(mMapEntityToComponentIndex.containsKey(colliderEntity)); @@ -317,7 +317,7 @@ inline void ColliderComponents::setLocalToWorldTransform(Entity colliderEntity, } // Return a reference to the list of overlapping pairs for a given collider -inline List& ColliderComponents::getOverlappingPairs(Entity colliderEntity) { +RP3D_FORCE_INLINE List& ColliderComponents::getOverlappingPairs(Entity colliderEntity) { assert(mMapEntityToComponentIndex.containsKey(colliderEntity)); @@ -325,7 +325,7 @@ inline List& ColliderComponents::getOverlappingPairs(Entity colliderEnti } // Return true if the size of collision shape of the collider has been changed by the user -inline bool ColliderComponents::getHasCollisionShapeChangedSize(Entity colliderEntity) const { +RP3D_FORCE_INLINE bool ColliderComponents::getHasCollisionShapeChangedSize(Entity colliderEntity) const { assert(mMapEntityToComponentIndex.containsKey(colliderEntity)); @@ -333,7 +333,7 @@ inline bool ColliderComponents::getHasCollisionShapeChangedSize(Entity colliderE } // Return true if the size of collision shape of the collider has been changed by the user -inline void ColliderComponents::setHasCollisionShapeChangedSize(Entity colliderEntity, bool hasCollisionShapeChangedSize) { +RP3D_FORCE_INLINE void ColliderComponents::setHasCollisionShapeChangedSize(Entity colliderEntity, bool hasCollisionShapeChangedSize) { assert(mMapEntityToComponentIndex.containsKey(colliderEntity)); @@ -342,7 +342,7 @@ inline void ColliderComponents::setHasCollisionShapeChangedSize(Entity colliderE // Return true if a collider is a trigger -inline bool ColliderComponents::getIsTrigger(Entity colliderEntity) const { +RP3D_FORCE_INLINE bool ColliderComponents::getIsTrigger(Entity colliderEntity) const { assert(mMapEntityToComponentIndex.containsKey(colliderEntity)); @@ -350,7 +350,7 @@ inline bool ColliderComponents::getIsTrigger(Entity colliderEntity) const { } // Set whether a collider is a trigger -inline void ColliderComponents::setIsTrigger(Entity colliderEntity, bool isTrigger) { +RP3D_FORCE_INLINE void ColliderComponents::setIsTrigger(Entity colliderEntity, bool isTrigger) { assert(mMapEntityToComponentIndex.containsKey(colliderEntity)); diff --git a/include/reactphysics3d/components/CollisionBodyComponents.h b/include/reactphysics3d/components/CollisionBodyComponents.h index c97414d5..08141341 100644 --- a/include/reactphysics3d/components/CollisionBodyComponents.h +++ b/include/reactphysics3d/components/CollisionBodyComponents.h @@ -130,7 +130,7 @@ class CollisionBodyComponents : public Components { }; // Add a collider to a body component -inline void CollisionBodyComponents::addColliderToBody(Entity bodyEntity, Entity colliderEntity) { +RP3D_FORCE_INLINE void CollisionBodyComponents::addColliderToBody(Entity bodyEntity, Entity colliderEntity) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -138,7 +138,7 @@ inline void CollisionBodyComponents::addColliderToBody(Entity bodyEntity, Entity } // Remove a collider from a body component -inline void CollisionBodyComponents::removeColliderFromBody(Entity bodyEntity, Entity colliderEntity) { +RP3D_FORCE_INLINE void CollisionBodyComponents::removeColliderFromBody(Entity bodyEntity, Entity colliderEntity) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -146,7 +146,7 @@ inline void CollisionBodyComponents::removeColliderFromBody(Entity bodyEntity, E } // Return a pointer to a body -inline CollisionBody *CollisionBodyComponents::getBody(Entity bodyEntity) { +RP3D_FORCE_INLINE CollisionBody *CollisionBodyComponents::getBody(Entity bodyEntity) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -154,7 +154,7 @@ inline CollisionBody *CollisionBodyComponents::getBody(Entity bodyEntity) { } // Return the list of colliders of a body -inline const List& CollisionBodyComponents::getColliders(Entity bodyEntity) const { +RP3D_FORCE_INLINE const List& CollisionBodyComponents::getColliders(Entity bodyEntity) const { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -162,7 +162,7 @@ inline const List& CollisionBodyComponents::getColliders(Entity bodyEnti } // Return true if the body is active -inline bool CollisionBodyComponents::getIsActive(Entity bodyEntity) const { +RP3D_FORCE_INLINE bool CollisionBodyComponents::getIsActive(Entity bodyEntity) const { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -170,7 +170,7 @@ inline bool CollisionBodyComponents::getIsActive(Entity bodyEntity) const { } // Set the value to know if the body is active -inline void CollisionBodyComponents::setIsActive(Entity bodyEntity, bool isActive) const { +RP3D_FORCE_INLINE void CollisionBodyComponents::setIsActive(Entity bodyEntity, bool isActive) const { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -178,7 +178,7 @@ inline void CollisionBodyComponents::setIsActive(Entity bodyEntity, bool isActiv } // Return the user data associated with the body -inline void* CollisionBodyComponents::getUserData(Entity bodyEntity) const { +RP3D_FORCE_INLINE void* CollisionBodyComponents::getUserData(Entity bodyEntity) const { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -186,7 +186,7 @@ inline void* CollisionBodyComponents::getUserData(Entity bodyEntity) const { } // Set the user data associated with the body -inline void CollisionBodyComponents::setUserData(Entity bodyEntity, void* userData) const { +RP3D_FORCE_INLINE void CollisionBodyComponents::setUserData(Entity bodyEntity, void* userData) const { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); diff --git a/include/reactphysics3d/components/Components.h b/include/reactphysics3d/components/Components.h index ba0c15f1..3470ff96 100644 --- a/include/reactphysics3d/components/Components.h +++ b/include/reactphysics3d/components/Components.h @@ -127,18 +127,18 @@ class Components { }; // Return true if an entity is sleeping -inline bool Components::getIsEntityDisabled(Entity entity) const { +RP3D_FORCE_INLINE bool Components::getIsEntityDisabled(Entity entity) const { assert(hasComponent(entity)); return mMapEntityToComponentIndex[entity] >= mDisabledStartIndex; } // Return true if there is a component for a given entity -inline bool Components::hasComponent(Entity entity) const { +RP3D_FORCE_INLINE bool Components::hasComponent(Entity entity) const { return mMapEntityToComponentIndex.containsKey(entity); } // Return true if there is a component for a given entiy and if so set the entity index -inline bool Components::hasComponentGetIndex(Entity entity, uint32& entityIndex) const { +RP3D_FORCE_INLINE bool Components::hasComponentGetIndex(Entity entity, uint32& entityIndex) const { auto it = mMapEntityToComponentIndex.find(entity); @@ -151,17 +151,17 @@ inline bool Components::hasComponentGetIndex(Entity entity, uint32& entityIndex) } // Return the number of components -inline uint32 Components::getNbComponents() const { +RP3D_FORCE_INLINE uint32 Components::getNbComponents() const { return mNbComponents; } // Return the number of enabled components -inline uint32 Components::getNbEnabledComponents() const { +RP3D_FORCE_INLINE uint32 Components::getNbEnabledComponents() const { return mDisabledStartIndex; } // Return the index in the arrays for a given entity -inline uint32 Components::getEntityIndex(Entity entity) const { +RP3D_FORCE_INLINE uint32 Components::getEntityIndex(Entity entity) const { assert(hasComponent(entity)); return mMapEntityToComponentIndex[entity]; } diff --git a/include/reactphysics3d/components/FixedJointComponents.h b/include/reactphysics3d/components/FixedJointComponents.h index 990a4ba0..a6751d68 100644 --- a/include/reactphysics3d/components/FixedJointComponents.h +++ b/include/reactphysics3d/components/FixedJointComponents.h @@ -224,133 +224,133 @@ class FixedJointComponents : public Components { }; // Return a pointer to a given joint -inline FixedJoint* FixedJointComponents::getJoint(Entity jointEntity) const { +RP3D_FORCE_INLINE FixedJoint* FixedJointComponents::getJoint(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mJoints[mMapEntityToComponentIndex[jointEntity]]; } // Set the joint pointer to a given joint -inline void FixedJointComponents::setJoint(Entity jointEntity, FixedJoint* joint) const { +RP3D_FORCE_INLINE void FixedJointComponents::setJoint(Entity jointEntity, FixedJoint* joint) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mJoints[mMapEntityToComponentIndex[jointEntity]] = joint; } // Return the local anchor point of body 1 for a given joint -inline const Vector3& FixedJointComponents::getLocalAnchorPointBody1(Entity jointEntity) const { +RP3D_FORCE_INLINE const Vector3& FixedJointComponents::getLocalAnchorPointBody1(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mLocalAnchorPointBody1[mMapEntityToComponentIndex[jointEntity]]; } // Set the local anchor point of body 1 for a given joint -inline void FixedJointComponents::setLocalAnchorPointBody1(Entity jointEntity, const Vector3& localAnchorPointBody1) { +RP3D_FORCE_INLINE void FixedJointComponents::setLocalAnchorPointBody1(Entity jointEntity, const Vector3& localAnchorPointBody1) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mLocalAnchorPointBody1[mMapEntityToComponentIndex[jointEntity]] = localAnchorPointBody1; } // Return the local anchor point of body 2 for a given joint -inline const Vector3& FixedJointComponents::getLocalAnchorPointBody2(Entity jointEntity) const { +RP3D_FORCE_INLINE const Vector3& FixedJointComponents::getLocalAnchorPointBody2(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mLocalAnchorPointBody2[mMapEntityToComponentIndex[jointEntity]]; } // Set the local anchor point of body 2 for a given joint -inline void FixedJointComponents::setLocalAnchorPointBody2(Entity jointEntity, const Vector3& localAnchorPointBody2) { +RP3D_FORCE_INLINE void FixedJointComponents::setLocalAnchorPointBody2(Entity jointEntity, const Vector3& localAnchorPointBody2) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mLocalAnchorPointBody2[mMapEntityToComponentIndex[jointEntity]] = localAnchorPointBody2; } // Return the vector from center of body 1 to anchor point in world-space -inline const Vector3& FixedJointComponents::getR1World(Entity jointEntity) const { +RP3D_FORCE_INLINE const Vector3& FixedJointComponents::getR1World(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mR1World[mMapEntityToComponentIndex[jointEntity]]; } // Set the vector from center of body 1 to anchor point in world-space -inline void FixedJointComponents::setR1World(Entity jointEntity, const Vector3& r1World) { +RP3D_FORCE_INLINE void FixedJointComponents::setR1World(Entity jointEntity, const Vector3& r1World) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mR1World[mMapEntityToComponentIndex[jointEntity]] = r1World; } // Return the vector from center of body 2 to anchor point in world-space -inline const Vector3& FixedJointComponents::getR2World(Entity jointEntity) const { +RP3D_FORCE_INLINE const Vector3& FixedJointComponents::getR2World(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mR2World[mMapEntityToComponentIndex[jointEntity]]; } // Set the vector from center of body 2 to anchor point in world-space -inline void FixedJointComponents::setR2World(Entity jointEntity, const Vector3& r2World) { +RP3D_FORCE_INLINE void FixedJointComponents::setR2World(Entity jointEntity, const Vector3& r2World) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mR2World[mMapEntityToComponentIndex[jointEntity]] = r2World; } // Return the inertia tensor of body 1 (in world-space coordinates) -inline const Matrix3x3& FixedJointComponents::getI1(Entity jointEntity) const { +RP3D_FORCE_INLINE const Matrix3x3& FixedJointComponents::getI1(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mI1[mMapEntityToComponentIndex[jointEntity]]; } // Set the inertia tensor of body 1 (in world-space coordinates) -inline void FixedJointComponents::setI1(Entity jointEntity, const Matrix3x3& i1) { +RP3D_FORCE_INLINE void FixedJointComponents::setI1(Entity jointEntity, const Matrix3x3& i1) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mI1[mMapEntityToComponentIndex[jointEntity]] = i1; } // Return the inertia tensor of body 2 (in world-space coordinates) -inline const Matrix3x3& FixedJointComponents::getI2(Entity jointEntity) const { +RP3D_FORCE_INLINE const Matrix3x3& FixedJointComponents::getI2(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mI2[mMapEntityToComponentIndex[jointEntity]]; } // Set the inertia tensor of body 2 (in world-space coordinates) -inline void FixedJointComponents::setI2(Entity jointEntity, const Matrix3x3& i2) { +RP3D_FORCE_INLINE void FixedJointComponents::setI2(Entity jointEntity, const Matrix3x3& i2) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mI2[mMapEntityToComponentIndex[jointEntity]] = i2; } // Return the translation impulse -inline Vector3& FixedJointComponents::getImpulseTranslation(Entity jointEntity) { +RP3D_FORCE_INLINE Vector3& FixedJointComponents::getImpulseTranslation(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mImpulseTranslation[mMapEntityToComponentIndex[jointEntity]]; } // Set the translation impulse -inline void FixedJointComponents::setImpulseTranslation(Entity jointEntity, const Vector3& impulseTranslation) { +RP3D_FORCE_INLINE void FixedJointComponents::setImpulseTranslation(Entity jointEntity, const Vector3& impulseTranslation) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mImpulseTranslation[mMapEntityToComponentIndex[jointEntity]] = impulseTranslation; } // Return the translation impulse -inline Vector3& FixedJointComponents::getImpulseRotation(Entity jointEntity) { +RP3D_FORCE_INLINE Vector3& FixedJointComponents::getImpulseRotation(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mImpulseRotation[mMapEntityToComponentIndex[jointEntity]]; } // Set the translation impulse -inline void FixedJointComponents::setImpulseRotation(Entity jointEntity, const Vector3& impulseTranslation) { +RP3D_FORCE_INLINE void FixedJointComponents::setImpulseRotation(Entity jointEntity, const Vector3& impulseTranslation) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mImpulseRotation[mMapEntityToComponentIndex[jointEntity]] = impulseTranslation; } // Return the translation inverse mass matrix of the constraint -inline Matrix3x3& FixedJointComponents::getInverseMassMatrixTranslation(Entity jointEntity) { +RP3D_FORCE_INLINE Matrix3x3& FixedJointComponents::getInverseMassMatrixTranslation(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mInverseMassMatrixTranslation[mMapEntityToComponentIndex[jointEntity]]; @@ -358,63 +358,63 @@ inline Matrix3x3& FixedJointComponents::getInverseMassMatrixTranslation(Entity j // Set the translation inverse mass matrix of the constraint -inline void FixedJointComponents::setInverseMassMatrixTranslation(Entity jointEntity, const Matrix3x3& inverseMassMatrix) { +RP3D_FORCE_INLINE void FixedJointComponents::setInverseMassMatrixTranslation(Entity jointEntity, const Matrix3x3& inverseMassMatrix) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mInverseMassMatrixTranslation[mMapEntityToComponentIndex[jointEntity]] = inverseMassMatrix; } // Return the rotation inverse mass matrix of the constraint -inline Matrix3x3& FixedJointComponents::getInverseMassMatrixRotation(Entity jointEntity) { +RP3D_FORCE_INLINE Matrix3x3& FixedJointComponents::getInverseMassMatrixRotation(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mInverseMassMatrixRotation[mMapEntityToComponentIndex[jointEntity]]; } // Set the rotation inverse mass matrix of the constraint -inline void FixedJointComponents::setInverseMassMatrixRotation(Entity jointEntity, const Matrix3x3& inverseMassMatrix) { +RP3D_FORCE_INLINE void FixedJointComponents::setInverseMassMatrixRotation(Entity jointEntity, const Matrix3x3& inverseMassMatrix) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mInverseMassMatrixRotation[mMapEntityToComponentIndex[jointEntity]] = inverseMassMatrix; } // Return the translation bias -inline Vector3& FixedJointComponents::getBiasTranslation(Entity jointEntity) { +RP3D_FORCE_INLINE Vector3& FixedJointComponents::getBiasTranslation(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mBiasTranslation[mMapEntityToComponentIndex[jointEntity]]; } // Set the translation impulse -inline void FixedJointComponents::setBiasTranslation(Entity jointEntity, const Vector3 &impulseTranslation) { +RP3D_FORCE_INLINE void FixedJointComponents::setBiasTranslation(Entity jointEntity, const Vector3 &impulseTranslation) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mBiasTranslation[mMapEntityToComponentIndex[jointEntity]] = impulseTranslation; } // Return the rotation bias -inline Vector3& FixedJointComponents::getBiasRotation(Entity jointEntity) { +RP3D_FORCE_INLINE Vector3& FixedJointComponents::getBiasRotation(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mBiasRotation[mMapEntityToComponentIndex[jointEntity]]; } // Set the rotation impulse -inline void FixedJointComponents::setBiasRotation(Entity jointEntity, const Vector3& impulseRotation) { +RP3D_FORCE_INLINE void FixedJointComponents::setBiasRotation(Entity jointEntity, const Vector3& impulseRotation) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mBiasRotation[mMapEntityToComponentIndex[jointEntity]] = impulseRotation; } // Return the initial orientation difference -inline Quaternion& FixedJointComponents::getInitOrientationDifferenceInv(Entity jointEntity) { +RP3D_FORCE_INLINE Quaternion& FixedJointComponents::getInitOrientationDifferenceInv(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mInitOrientationDifferenceInv[mMapEntityToComponentIndex[jointEntity]]; } // Set the rotation impulse -inline void FixedJointComponents::setInitOrientationDifferenceInv(Entity jointEntity, const Quaternion& initOrientationDifferenceInv) { +RP3D_FORCE_INLINE void FixedJointComponents::setInitOrientationDifferenceInv(Entity jointEntity, const Quaternion& initOrientationDifferenceInv) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mInitOrientationDifferenceInv[mMapEntityToComponentIndex[jointEntity]] = initOrientationDifferenceInv; diff --git a/include/reactphysics3d/components/HingeJointComponents.h b/include/reactphysics3d/components/HingeJointComponents.h index d50db707..4ee9402f 100644 --- a/include/reactphysics3d/components/HingeJointComponents.h +++ b/include/reactphysics3d/components/HingeJointComponents.h @@ -415,133 +415,133 @@ class HingeJointComponents : public Components { }; // Return a pointer to a given joint -inline HingeJoint* HingeJointComponents::getJoint(Entity jointEntity) const { +RP3D_FORCE_INLINE HingeJoint* HingeJointComponents::getJoint(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mJoints[mMapEntityToComponentIndex[jointEntity]]; } // Set the joint pointer to a given joint -inline void HingeJointComponents::setJoint(Entity jointEntity, HingeJoint* joint) const { +RP3D_FORCE_INLINE void HingeJointComponents::setJoint(Entity jointEntity, HingeJoint* joint) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mJoints[mMapEntityToComponentIndex[jointEntity]] = joint; } // Return the local anchor point of body 1 for a given joint -inline const Vector3& HingeJointComponents::getLocalAnchorPointBody1(Entity jointEntity) const { +RP3D_FORCE_INLINE const Vector3& HingeJointComponents::getLocalAnchorPointBody1(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mLocalAnchorPointBody1[mMapEntityToComponentIndex[jointEntity]]; } // Set the local anchor point of body 1 for a given joint -inline void HingeJointComponents::setLocalAnchorPointBody1(Entity jointEntity, const Vector3& localAnchoirPointBody1) { +RP3D_FORCE_INLINE void HingeJointComponents::setLocalAnchorPointBody1(Entity jointEntity, const Vector3& localAnchoirPointBody1) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mLocalAnchorPointBody1[mMapEntityToComponentIndex[jointEntity]] = localAnchoirPointBody1; } // Return the local anchor point of body 2 for a given joint -inline const Vector3& HingeJointComponents::getLocalAnchorPointBody2(Entity jointEntity) const { +RP3D_FORCE_INLINE const Vector3& HingeJointComponents::getLocalAnchorPointBody2(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mLocalAnchorPointBody2[mMapEntityToComponentIndex[jointEntity]]; } // Set the local anchor point of body 2 for a given joint -inline void HingeJointComponents::setLocalAnchorPointBody2(Entity jointEntity, const Vector3& localAnchoirPointBody2) { +RP3D_FORCE_INLINE void HingeJointComponents::setLocalAnchorPointBody2(Entity jointEntity, const Vector3& localAnchoirPointBody2) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mLocalAnchorPointBody2[mMapEntityToComponentIndex[jointEntity]] = localAnchoirPointBody2; } // Return the vector from center of body 1 to anchor point in world-space -inline const Vector3& HingeJointComponents::getR1World(Entity jointEntity) const { +RP3D_FORCE_INLINE const Vector3& HingeJointComponents::getR1World(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mR1World[mMapEntityToComponentIndex[jointEntity]]; } // Set the vector from center of body 1 to anchor point in world-space -inline void HingeJointComponents::setR1World(Entity jointEntity, const Vector3& r1World) { +RP3D_FORCE_INLINE void HingeJointComponents::setR1World(Entity jointEntity, const Vector3& r1World) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mR1World[mMapEntityToComponentIndex[jointEntity]] = r1World; } // Return the vector from center of body 2 to anchor point in world-space -inline const Vector3& HingeJointComponents::getR2World(Entity jointEntity) const { +RP3D_FORCE_INLINE const Vector3& HingeJointComponents::getR2World(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mR2World[mMapEntityToComponentIndex[jointEntity]]; } // Set the vector from center of body 2 to anchor point in world-space -inline void HingeJointComponents::setR2World(Entity jointEntity, const Vector3& r2World) { +RP3D_FORCE_INLINE void HingeJointComponents::setR2World(Entity jointEntity, const Vector3& r2World) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mR2World[mMapEntityToComponentIndex[jointEntity]] = r2World; } // Return the inertia tensor of body 1 (in world-space coordinates) -inline const Matrix3x3& HingeJointComponents::getI1(Entity jointEntity) const { +RP3D_FORCE_INLINE const Matrix3x3& HingeJointComponents::getI1(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mI1[mMapEntityToComponentIndex[jointEntity]]; } // Set the inertia tensor of body 1 (in world-space coordinates) -inline void HingeJointComponents::setI1(Entity jointEntity, const Matrix3x3& i1) { +RP3D_FORCE_INLINE void HingeJointComponents::setI1(Entity jointEntity, const Matrix3x3& i1) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mI1[mMapEntityToComponentIndex[jointEntity]] = i1; } // Return the inertia tensor of body 2 (in world-space coordinates) -inline const Matrix3x3& HingeJointComponents::getI2(Entity jointEntity) const { +RP3D_FORCE_INLINE const Matrix3x3& HingeJointComponents::getI2(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mI2[mMapEntityToComponentIndex[jointEntity]]; } // Set the inertia tensor of body 2 (in world-space coordinates) -inline void HingeJointComponents::setI2(Entity jointEntity, const Matrix3x3& i2) { +RP3D_FORCE_INLINE void HingeJointComponents::setI2(Entity jointEntity, const Matrix3x3& i2) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mI2[mMapEntityToComponentIndex[jointEntity]] = i2; } // Return the translation impulse -inline Vector3& HingeJointComponents::getImpulseTranslation(Entity jointEntity) { +RP3D_FORCE_INLINE Vector3& HingeJointComponents::getImpulseTranslation(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mImpulseTranslation[mMapEntityToComponentIndex[jointEntity]]; } // Set the translation impulse -inline void HingeJointComponents::setImpulseTranslation(Entity jointEntity, const Vector3& impulseTranslation) { +RP3D_FORCE_INLINE void HingeJointComponents::setImpulseTranslation(Entity jointEntity, const Vector3& impulseTranslation) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mImpulseTranslation[mMapEntityToComponentIndex[jointEntity]] = impulseTranslation; } // Return the translation impulse -inline Vector2& HingeJointComponents::getImpulseRotation(Entity jointEntity) { +RP3D_FORCE_INLINE Vector2& HingeJointComponents::getImpulseRotation(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mImpulseRotation[mMapEntityToComponentIndex[jointEntity]]; } // Set the translation impulse -inline void HingeJointComponents::setImpulseRotation(Entity jointEntity, const Vector2& impulseTranslation) { +RP3D_FORCE_INLINE void HingeJointComponents::setImpulseRotation(Entity jointEntity, const Vector2& impulseTranslation) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mImpulseRotation[mMapEntityToComponentIndex[jointEntity]] = impulseTranslation; } // Return the translation inverse mass matrix of the constraint -inline Matrix3x3& HingeJointComponents::getInverseMassMatrixTranslation(Entity jointEntity) { +RP3D_FORCE_INLINE Matrix3x3& HingeJointComponents::getInverseMassMatrixTranslation(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mInverseMassMatrixTranslation[mMapEntityToComponentIndex[jointEntity]]; @@ -549,91 +549,91 @@ inline Matrix3x3& HingeJointComponents::getInverseMassMatrixTranslation(Entity j // Set the translation inverse mass matrix of the constraint -inline void HingeJointComponents::setInverseMassMatrixTranslation(Entity jointEntity, const Matrix3x3& inverseMassMatrix) { +RP3D_FORCE_INLINE void HingeJointComponents::setInverseMassMatrixTranslation(Entity jointEntity, const Matrix3x3& inverseMassMatrix) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mInverseMassMatrixTranslation[mMapEntityToComponentIndex[jointEntity]] = inverseMassMatrix; } // Return the rotation inverse mass matrix of the constraint -inline Matrix2x2& HingeJointComponents::getInverseMassMatrixRotation(Entity jointEntity) { +RP3D_FORCE_INLINE Matrix2x2& HingeJointComponents::getInverseMassMatrixRotation(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mInverseMassMatrixRotation[mMapEntityToComponentIndex[jointEntity]]; } // Set the rotation inverse mass matrix of the constraint -inline void HingeJointComponents::setInverseMassMatrixRotation(Entity jointEntity, const Matrix2x2& inverseMassMatrix) { +RP3D_FORCE_INLINE void HingeJointComponents::setInverseMassMatrixRotation(Entity jointEntity, const Matrix2x2& inverseMassMatrix) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mInverseMassMatrixRotation[mMapEntityToComponentIndex[jointEntity]] = inverseMassMatrix; } // Return the translation bias -inline Vector3& HingeJointComponents::getBiasTranslation(Entity jointEntity) { +RP3D_FORCE_INLINE Vector3& HingeJointComponents::getBiasTranslation(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mBiasTranslation[mMapEntityToComponentIndex[jointEntity]]; } // Set the translation impulse -inline void HingeJointComponents::setBiasTranslation(Entity jointEntity, const Vector3 &impulseTranslation) { +RP3D_FORCE_INLINE void HingeJointComponents::setBiasTranslation(Entity jointEntity, const Vector3 &impulseTranslation) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mBiasTranslation[mMapEntityToComponentIndex[jointEntity]] = impulseTranslation; } // Return the rotation bias -inline Vector2 &HingeJointComponents::getBiasRotation(Entity jointEntity) { +RP3D_FORCE_INLINE Vector2 &HingeJointComponents::getBiasRotation(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mBiasRotation[mMapEntityToComponentIndex[jointEntity]]; } // Set the rotation impulse -inline void HingeJointComponents::setBiasRotation(Entity jointEntity, const Vector2& impulseRotation) { +RP3D_FORCE_INLINE void HingeJointComponents::setBiasRotation(Entity jointEntity, const Vector2& impulseRotation) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mBiasRotation[mMapEntityToComponentIndex[jointEntity]] = impulseRotation; } // Return the initial orientation difference -inline Quaternion& HingeJointComponents::getInitOrientationDifferenceInv(Entity jointEntity) { +RP3D_FORCE_INLINE Quaternion& HingeJointComponents::getInitOrientationDifferenceInv(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mInitOrientationDifferenceInv[mMapEntityToComponentIndex[jointEntity]]; } // Set the rotation impulse -inline void HingeJointComponents::setInitOrientationDifferenceInv(Entity jointEntity, const Quaternion& initOrientationDifferenceInv) { +RP3D_FORCE_INLINE void HingeJointComponents::setInitOrientationDifferenceInv(Entity jointEntity, const Quaternion& initOrientationDifferenceInv) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mInitOrientationDifferenceInv[mMapEntityToComponentIndex[jointEntity]] = initOrientationDifferenceInv; } // Return the hinge rotation axis (in local-space coordinates of body 1) -inline Vector3& HingeJointComponents::getHingeLocalAxisBody1(Entity jointEntity) { +RP3D_FORCE_INLINE Vector3& HingeJointComponents::getHingeLocalAxisBody1(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mHingeLocalAxisBody1[mMapEntityToComponentIndex[jointEntity]]; } // Set the hinge rotation axis (in local-space coordinates of body 1) -inline void HingeJointComponents::setHingeLocalAxisBody1(Entity jointEntity, const Vector3& hingeLocalAxisBody1) { +RP3D_FORCE_INLINE void HingeJointComponents::setHingeLocalAxisBody1(Entity jointEntity, const Vector3& hingeLocalAxisBody1) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mHingeLocalAxisBody1[mMapEntityToComponentIndex[jointEntity]] = hingeLocalAxisBody1; } // Return the hinge rotation axis (in local-space coordiantes of body 2) -inline Vector3& HingeJointComponents::getHingeLocalAxisBody2(Entity jointEntity) { +RP3D_FORCE_INLINE Vector3& HingeJointComponents::getHingeLocalAxisBody2(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mHingeLocalAxisBody2[mMapEntityToComponentIndex[jointEntity]]; } // Set the hinge rotation axis (in local-space coordiantes of body 2) -inline void HingeJointComponents::setHingeLocalAxisBody2(Entity jointEntity, const Vector3& hingeLocalAxisBody2) { +RP3D_FORCE_INLINE void HingeJointComponents::setHingeLocalAxisBody2(Entity jointEntity, const Vector3& hingeLocalAxisBody2) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mHingeLocalAxisBody2[mMapEntityToComponentIndex[jointEntity]] = hingeLocalAxisBody2; @@ -641,56 +641,56 @@ inline void HingeJointComponents::setHingeLocalAxisBody2(Entity jointEntity, con // Return the hinge rotation axis (in world-space coordinates) computed from body 1 -inline Vector3& HingeJointComponents::getA1(Entity jointEntity) { +RP3D_FORCE_INLINE Vector3& HingeJointComponents::getA1(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mA1[mMapEntityToComponentIndex[jointEntity]]; } // Set the hinge rotation axis (in world-space coordinates) computed from body 1 -inline void HingeJointComponents::setA1(Entity jointEntity, const Vector3& a1) { +RP3D_FORCE_INLINE void HingeJointComponents::setA1(Entity jointEntity, const Vector3& a1) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mA1[mMapEntityToComponentIndex[jointEntity]] = a1; } // Return the cross product of vector b2 and a1 -inline Vector3& HingeJointComponents::getB2CrossA1(Entity jointEntity) { +RP3D_FORCE_INLINE Vector3& HingeJointComponents::getB2CrossA1(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mB2CrossA1[mMapEntityToComponentIndex[jointEntity]]; } // Set the cross product of vector b2 and a1 -inline void HingeJointComponents::setB2CrossA1(Entity jointEntity, const Vector3& b2CrossA1) { +RP3D_FORCE_INLINE void HingeJointComponents::setB2CrossA1(Entity jointEntity, const Vector3& b2CrossA1) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mB2CrossA1[mMapEntityToComponentIndex[jointEntity]] = b2CrossA1; } // Return the cross product of vector c2 and a1; -inline Vector3& HingeJointComponents::getC2CrossA1(Entity jointEntity) { +RP3D_FORCE_INLINE Vector3& HingeJointComponents::getC2CrossA1(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mC2CrossA1[mMapEntityToComponentIndex[jointEntity]]; } // Set the cross product of vector c2 and a1; -inline void HingeJointComponents::setC2CrossA1(Entity jointEntity, const Vector3& c2CrossA1) { +RP3D_FORCE_INLINE void HingeJointComponents::setC2CrossA1(Entity jointEntity, const Vector3& c2CrossA1) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mC2CrossA1[mMapEntityToComponentIndex[jointEntity]] = c2CrossA1; } // Return the accumulated impulse for the lower limit constraint -inline decimal HingeJointComponents::getImpulseLowerLimit(Entity jointEntity) const { +RP3D_FORCE_INLINE decimal HingeJointComponents::getImpulseLowerLimit(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mImpulseLowerLimit[mMapEntityToComponentIndex[jointEntity]]; } // Set the accumulated impulse for the lower limit constraint -inline void HingeJointComponents::setImpulseLowerLimit(Entity jointEntity, decimal impulseLowerLimit) { +RP3D_FORCE_INLINE void HingeJointComponents::setImpulseLowerLimit(Entity jointEntity, decimal impulseLowerLimit) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mImpulseLowerLimit[mMapEntityToComponentIndex[jointEntity]] = impulseLowerLimit; @@ -698,14 +698,14 @@ inline void HingeJointComponents::setImpulseLowerLimit(Entity jointEntity, decim // Return the accumulated impulse for the upper limit constraint -inline decimal HingeJointComponents::getImpulseUpperLimit(Entity jointEntity) const { +RP3D_FORCE_INLINE decimal HingeJointComponents::getImpulseUpperLimit(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mImpulseUpperLimit[mMapEntityToComponentIndex[jointEntity]]; } // Set the accumulated impulse for the upper limit constraint -inline void HingeJointComponents::setImpulseUpperLimit(Entity jointEntity, decimal impulseUpperLimit) const { +RP3D_FORCE_INLINE void HingeJointComponents::setImpulseUpperLimit(Entity jointEntity, decimal impulseUpperLimit) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mImpulseUpperLimit[mMapEntityToComponentIndex[jointEntity]] = impulseUpperLimit; @@ -713,182 +713,182 @@ inline void HingeJointComponents::setImpulseUpperLimit(Entity jointEntity, decim // Return the accumulated impulse for the motor constraint; -inline decimal HingeJointComponents::getImpulseMotor(Entity jointEntity) const { +RP3D_FORCE_INLINE decimal HingeJointComponents::getImpulseMotor(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mImpulseMotor[mMapEntityToComponentIndex[jointEntity]]; } // Set the accumulated impulse for the motor constraint; -inline void HingeJointComponents::setImpulseMotor(Entity jointEntity, decimal impulseMotor) { +RP3D_FORCE_INLINE void HingeJointComponents::setImpulseMotor(Entity jointEntity, decimal impulseMotor) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mImpulseMotor[mMapEntityToComponentIndex[jointEntity]] = impulseMotor; } // Return the inverse of mass matrix K=JM^-1J^t for the limits and motor constraints (1x1 matrix) -inline decimal HingeJointComponents::getInverseMassMatrixLimitMotor(Entity jointEntity) const { +RP3D_FORCE_INLINE decimal HingeJointComponents::getInverseMassMatrixLimitMotor(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mInverseMassMatrixLimitMotor[mMapEntityToComponentIndex[jointEntity]]; } // Set the inverse of mass matrix K=JM^-1J^t for the limits and motor constraints (1x1 matrix) -inline void HingeJointComponents::setInverseMassMatrixLimitMotor(Entity jointEntity, decimal inverseMassMatrixLimitMotor) { +RP3D_FORCE_INLINE void HingeJointComponents::setInverseMassMatrixLimitMotor(Entity jointEntity, decimal inverseMassMatrixLimitMotor) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mInverseMassMatrixLimitMotor[mMapEntityToComponentIndex[jointEntity]] = inverseMassMatrixLimitMotor; } // Return the inverse of mass matrix K=JM^-1J^t for the motor -inline decimal HingeJointComponents::getInverseMassMatrixMotor(Entity jointEntity) { +RP3D_FORCE_INLINE decimal HingeJointComponents::getInverseMassMatrixMotor(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mInverseMassMatrixMotor[mMapEntityToComponentIndex[jointEntity]]; } // Return the inverse of mass matrix K=JM^-1J^t for the motor -inline void HingeJointComponents::setInverseMassMatrixMotor(Entity jointEntity, decimal inverseMassMatrixMotor) { +RP3D_FORCE_INLINE void HingeJointComponents::setInverseMassMatrixMotor(Entity jointEntity, decimal inverseMassMatrixMotor) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mInverseMassMatrixMotor[mMapEntityToComponentIndex[jointEntity]] = inverseMassMatrixMotor; } // Return the bias of the lower limit constraint -inline decimal HingeJointComponents::getBLowerLimit(Entity jointEntity) const { +RP3D_FORCE_INLINE decimal HingeJointComponents::getBLowerLimit(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mBLowerLimit[mMapEntityToComponentIndex[jointEntity]]; } // Set the bias of the lower limit constraint -inline void HingeJointComponents::setBLowerLimit(Entity jointEntity, decimal bLowerLimit) const { +RP3D_FORCE_INLINE void HingeJointComponents::setBLowerLimit(Entity jointEntity, decimal bLowerLimit) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mBLowerLimit[mMapEntityToComponentIndex[jointEntity]] = bLowerLimit; } // Return the bias of the upper limit constraint -inline decimal HingeJointComponents::getBUpperLimit(Entity jointEntity) const { +RP3D_FORCE_INLINE decimal HingeJointComponents::getBUpperLimit(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mBUpperLimit[mMapEntityToComponentIndex[jointEntity]]; } // Set the bias of the upper limit constraint -inline void HingeJointComponents::setBUpperLimit(Entity jointEntity, decimal bUpperLimit) { +RP3D_FORCE_INLINE void HingeJointComponents::setBUpperLimit(Entity jointEntity, decimal bUpperLimit) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mBUpperLimit[mMapEntityToComponentIndex[jointEntity]] = bUpperLimit; } // Return true if the joint limits are enabled -inline bool HingeJointComponents::getIsLimitEnabled(Entity jointEntity) const { +RP3D_FORCE_INLINE bool HingeJointComponents::getIsLimitEnabled(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mIsLimitEnabled[mMapEntityToComponentIndex[jointEntity]]; } // Set to true if the joint limits are enabled -inline void HingeJointComponents::setIsLimitEnabled(Entity jointEntity, bool isLimitEnabled) { +RP3D_FORCE_INLINE void HingeJointComponents::setIsLimitEnabled(Entity jointEntity, bool isLimitEnabled) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mIsLimitEnabled[mMapEntityToComponentIndex[jointEntity]] = isLimitEnabled; } // Return true if the motor of the joint in enabled -inline bool HingeJointComponents::getIsMotorEnabled(Entity jointEntity) const { +RP3D_FORCE_INLINE bool HingeJointComponents::getIsMotorEnabled(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mIsMotorEnabled[mMapEntityToComponentIndex[jointEntity]]; } // Set to true if the motor of the joint in enabled -inline void HingeJointComponents::setIsMotorEnabled(Entity jointEntity, bool isMotorEnabled) const { +RP3D_FORCE_INLINE void HingeJointComponents::setIsMotorEnabled(Entity jointEntity, bool isMotorEnabled) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mIsMotorEnabled[mMapEntityToComponentIndex[jointEntity]] = isMotorEnabled; } // Return the Lower limit (minimum allowed rotation angle in radian) -inline decimal HingeJointComponents::getLowerLimit(Entity jointEntity) const { +RP3D_FORCE_INLINE decimal HingeJointComponents::getLowerLimit(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mLowerLimit[mMapEntityToComponentIndex[jointEntity]]; } // Set the Lower limit (minimum allowed rotation angle in radian) -inline void HingeJointComponents::setLowerLimit(Entity jointEntity, decimal lowerLimit) const { +RP3D_FORCE_INLINE void HingeJointComponents::setLowerLimit(Entity jointEntity, decimal lowerLimit) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mLowerLimit[mMapEntityToComponentIndex[jointEntity]] = lowerLimit; } // Return the upper limit (maximum translation distance) -inline decimal HingeJointComponents::getUpperLimit(Entity jointEntity) const { +RP3D_FORCE_INLINE decimal HingeJointComponents::getUpperLimit(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mUpperLimit[mMapEntityToComponentIndex[jointEntity]]; } // Set the upper limit (maximum translation distance) -inline void HingeJointComponents::setUpperLimit(Entity jointEntity, decimal upperLimit) { +RP3D_FORCE_INLINE void HingeJointComponents::setUpperLimit(Entity jointEntity, decimal upperLimit) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mUpperLimit[mMapEntityToComponentIndex[jointEntity]] = upperLimit; } // Return true if the lower limit is violated -inline bool HingeJointComponents::getIsLowerLimitViolated(Entity jointEntity) const { +RP3D_FORCE_INLINE bool HingeJointComponents::getIsLowerLimitViolated(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mIsLowerLimitViolated[mMapEntityToComponentIndex[jointEntity]]; } // Set to true if the lower limit is violated -inline void HingeJointComponents::setIsLowerLimitViolated(Entity jointEntity, bool isLowerLimitViolated) { +RP3D_FORCE_INLINE void HingeJointComponents::setIsLowerLimitViolated(Entity jointEntity, bool isLowerLimitViolated) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mIsLowerLimitViolated[mMapEntityToComponentIndex[jointEntity]] = isLowerLimitViolated; } // Return true if the upper limit is violated -inline bool HingeJointComponents::getIsUpperLimitViolated(Entity jointEntity) const { +RP3D_FORCE_INLINE bool HingeJointComponents::getIsUpperLimitViolated(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mIsUpperLimitViolated[mMapEntityToComponentIndex[jointEntity]]; } // Set to true if the upper limit is violated -inline void HingeJointComponents::setIsUpperLimitViolated(Entity jointEntity, bool isUpperLimitViolated) const { +RP3D_FORCE_INLINE void HingeJointComponents::setIsUpperLimitViolated(Entity jointEntity, bool isUpperLimitViolated) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mIsUpperLimitViolated[mMapEntityToComponentIndex[jointEntity]] = isUpperLimitViolated; } // Return the motor speed (in rad/s) -inline decimal HingeJointComponents::getMotorSpeed(Entity jointEntity) const { +RP3D_FORCE_INLINE decimal HingeJointComponents::getMotorSpeed(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mMotorSpeed[mMapEntityToComponentIndex[jointEntity]]; } // Set the motor speed (in rad/s) -inline void HingeJointComponents::setMotorSpeed(Entity jointEntity, decimal motorSpeed) { +RP3D_FORCE_INLINE void HingeJointComponents::setMotorSpeed(Entity jointEntity, decimal motorSpeed) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mMotorSpeed[mMapEntityToComponentIndex[jointEntity]] = motorSpeed; } // Return the maximum motor torque (in Newtons) that can be applied to reach to desired motor speed -inline decimal HingeJointComponents::getMaxMotorTorque(Entity jointEntity) const { +RP3D_FORCE_INLINE decimal HingeJointComponents::getMaxMotorTorque(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mMaxMotorTorque[mMapEntityToComponentIndex[jointEntity]]; } // Set the maximum motor torque (in Newtons) that can be applied to reach to desired motor speed -inline void HingeJointComponents::setMaxMotorTorque(Entity jointEntity, decimal maxMotorTorque) { +RP3D_FORCE_INLINE void HingeJointComponents::setMaxMotorTorque(Entity jointEntity, decimal maxMotorTorque) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mMaxMotorTorque[mMapEntityToComponentIndex[jointEntity]] = maxMotorTorque; diff --git a/include/reactphysics3d/components/JointComponents.h b/include/reactphysics3d/components/JointComponents.h index f82744c0..e535dbd1 100644 --- a/include/reactphysics3d/components/JointComponents.h +++ b/include/reactphysics3d/components/JointComponents.h @@ -160,61 +160,61 @@ class JointComponents : public Components { }; // Return the entity of the first body of a joint -inline Entity JointComponents::getBody1Entity(Entity jointEntity) const { +RP3D_FORCE_INLINE Entity JointComponents::getBody1Entity(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mBody1Entities[mMapEntityToComponentIndex[jointEntity]]; } // Return the entity of the second body of a joint -inline Entity JointComponents::getBody2Entity(Entity jointEntity) const { +RP3D_FORCE_INLINE Entity JointComponents::getBody2Entity(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mBody2Entities[mMapEntityToComponentIndex[jointEntity]]; } // Return a pointer to the joint -inline Joint* JointComponents::getJoint(Entity jointEntity) const { +RP3D_FORCE_INLINE Joint* JointComponents::getJoint(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mJoints[mMapEntityToComponentIndex[jointEntity]]; } // Return the type of a joint -inline JointType JointComponents::getType(Entity jointEntity) const { +RP3D_FORCE_INLINE JointType JointComponents::getType(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mTypes[mMapEntityToComponentIndex[jointEntity]]; } // Return the position correction technique of a joint -inline JointsPositionCorrectionTechnique JointComponents::getPositionCorrectionTechnique(Entity jointEntity) const { +RP3D_FORCE_INLINE JointsPositionCorrectionTechnique JointComponents::getPositionCorrectionTechnique(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mPositionCorrectionTechniques[mMapEntityToComponentIndex[jointEntity]]; } // Set the position correction technique of a joint -inline void JointComponents::getPositionCorrectionTechnique(Entity jointEntity, JointsPositionCorrectionTechnique positionCorrectionTechnique) { +RP3D_FORCE_INLINE void JointComponents::getPositionCorrectionTechnique(Entity jointEntity, JointsPositionCorrectionTechnique positionCorrectionTechnique) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mPositionCorrectionTechniques[mMapEntityToComponentIndex[jointEntity]] = positionCorrectionTechnique; } // Return true if the collision is enabled between the two bodies of a joint -inline bool JointComponents::getIsCollisionEnabled(Entity jointEntity) const { +RP3D_FORCE_INLINE bool JointComponents::getIsCollisionEnabled(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mIsCollisionEnabled[mMapEntityToComponentIndex[jointEntity]]; } // Set whether the collision is enabled between the two bodies of a joint -inline void JointComponents::setIsCollisionEnabled(Entity jointEntity, bool isCollisionEnabled) { +RP3D_FORCE_INLINE void JointComponents::setIsCollisionEnabled(Entity jointEntity, bool isCollisionEnabled) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mIsCollisionEnabled[mMapEntityToComponentIndex[jointEntity]] = isCollisionEnabled; } // Return true if the joint has already been added into an island during island creation -inline bool JointComponents::getIsAlreadyInIsland(Entity jointEntity) const { +RP3D_FORCE_INLINE bool JointComponents::getIsAlreadyInIsland(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mIsAlreadyInIsland[mMapEntityToComponentIndex[jointEntity]]; } // Set to true if the joint has already been added into an island during island creation -inline void JointComponents::setIsAlreadyInIsland(Entity jointEntity, bool isAlreadyInIsland) { +RP3D_FORCE_INLINE void JointComponents::setIsAlreadyInIsland(Entity jointEntity, bool isAlreadyInIsland) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mIsAlreadyInIsland[mMapEntityToComponentIndex[jointEntity]] = isAlreadyInIsland; } diff --git a/include/reactphysics3d/components/RigidBodyComponents.h b/include/reactphysics3d/components/RigidBodyComponents.h index a78f071b..7ceb9a1d 100644 --- a/include/reactphysics3d/components/RigidBodyComponents.h +++ b/include/reactphysics3d/components/RigidBodyComponents.h @@ -370,7 +370,7 @@ class RigidBodyComponents : public Components { }; // Return a pointer to a body rigid -inline RigidBody* RigidBodyComponents::getRigidBody(Entity bodyEntity) { +RP3D_FORCE_INLINE RigidBody* RigidBodyComponents::getRigidBody(Entity bodyEntity) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -378,7 +378,7 @@ inline RigidBody* RigidBodyComponents::getRigidBody(Entity bodyEntity) { } // Return true if the body is allowed to sleep -inline bool RigidBodyComponents::getIsAllowedToSleep(Entity bodyEntity) const { +RP3D_FORCE_INLINE bool RigidBodyComponents::getIsAllowedToSleep(Entity bodyEntity) const { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -386,7 +386,7 @@ inline bool RigidBodyComponents::getIsAllowedToSleep(Entity bodyEntity) const { } // Set the value to know if the body is allowed to sleep -inline void RigidBodyComponents::setIsAllowedToSleep(Entity bodyEntity, bool isAllowedToSleep) const { +RP3D_FORCE_INLINE void RigidBodyComponents::setIsAllowedToSleep(Entity bodyEntity, bool isAllowedToSleep) const { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -394,7 +394,7 @@ inline void RigidBodyComponents::setIsAllowedToSleep(Entity bodyEntity, bool isA } // Return true if the body is sleeping -inline bool RigidBodyComponents::getIsSleeping(Entity bodyEntity) const { +RP3D_FORCE_INLINE bool RigidBodyComponents::getIsSleeping(Entity bodyEntity) const { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -402,7 +402,7 @@ inline bool RigidBodyComponents::getIsSleeping(Entity bodyEntity) const { } // Set the value to know if the body is sleeping -inline void RigidBodyComponents::setIsSleeping(Entity bodyEntity, bool isSleeping) const { +RP3D_FORCE_INLINE void RigidBodyComponents::setIsSleeping(Entity bodyEntity, bool isSleeping) const { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -410,7 +410,7 @@ inline void RigidBodyComponents::setIsSleeping(Entity bodyEntity, bool isSleepin } // Return the sleep time -inline decimal RigidBodyComponents::getSleepTime(Entity bodyEntity) const { +RP3D_FORCE_INLINE decimal RigidBodyComponents::getSleepTime(Entity bodyEntity) const { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -418,7 +418,7 @@ inline decimal RigidBodyComponents::getSleepTime(Entity bodyEntity) const { } // Set the sleep time -inline void RigidBodyComponents::setSleepTime(Entity bodyEntity, decimal sleepTime) const { +RP3D_FORCE_INLINE void RigidBodyComponents::setSleepTime(Entity bodyEntity, decimal sleepTime) const { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -426,7 +426,7 @@ inline void RigidBodyComponents::setSleepTime(Entity bodyEntity, decimal sleepTi } // Return the body type of a body -inline BodyType RigidBodyComponents::getBodyType(Entity bodyEntity) { +RP3D_FORCE_INLINE BodyType RigidBodyComponents::getBodyType(Entity bodyEntity) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -434,7 +434,7 @@ inline BodyType RigidBodyComponents::getBodyType(Entity bodyEntity) { } // Set the body type of a body -inline void RigidBodyComponents::setBodyType(Entity bodyEntity, BodyType bodyType) { +RP3D_FORCE_INLINE void RigidBodyComponents::setBodyType(Entity bodyEntity, BodyType bodyType) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -442,7 +442,7 @@ inline void RigidBodyComponents::setBodyType(Entity bodyEntity, BodyType bodyTyp } // Return the linear velocity of an entity -inline const Vector3& RigidBodyComponents::getLinearVelocity(Entity bodyEntity) const { +RP3D_FORCE_INLINE const Vector3& RigidBodyComponents::getLinearVelocity(Entity bodyEntity) const { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -450,7 +450,7 @@ inline const Vector3& RigidBodyComponents::getLinearVelocity(Entity bodyEntity) } // Return the angular velocity of an entity -inline const Vector3& RigidBodyComponents::getAngularVelocity(Entity bodyEntity) const { +RP3D_FORCE_INLINE const Vector3& RigidBodyComponents::getAngularVelocity(Entity bodyEntity) const { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -458,7 +458,7 @@ inline const Vector3& RigidBodyComponents::getAngularVelocity(Entity bodyEntity) } // Set the linear velocity of an entity -inline void RigidBodyComponents::setLinearVelocity(Entity bodyEntity, const Vector3& linearVelocity) { +RP3D_FORCE_INLINE void RigidBodyComponents::setLinearVelocity(Entity bodyEntity, const Vector3& linearVelocity) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -466,7 +466,7 @@ inline void RigidBodyComponents::setLinearVelocity(Entity bodyEntity, const Vect } // Set the angular velocity of an entity -inline void RigidBodyComponents::setAngularVelocity(Entity bodyEntity, const Vector3& angularVelocity) { +RP3D_FORCE_INLINE void RigidBodyComponents::setAngularVelocity(Entity bodyEntity, const Vector3& angularVelocity) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -474,7 +474,7 @@ inline void RigidBodyComponents::setAngularVelocity(Entity bodyEntity, const Vec } // Return the external force of an entity -inline const Vector3& RigidBodyComponents::getExternalForce(Entity bodyEntity) const { +RP3D_FORCE_INLINE const Vector3& RigidBodyComponents::getExternalForce(Entity bodyEntity) const { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -482,7 +482,7 @@ inline const Vector3& RigidBodyComponents::getExternalForce(Entity bodyEntity) c } // Return the external torque of an entity -inline const Vector3& RigidBodyComponents::getExternalTorque(Entity bodyEntity) const { +RP3D_FORCE_INLINE const Vector3& RigidBodyComponents::getExternalTorque(Entity bodyEntity) const { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -490,7 +490,7 @@ inline const Vector3& RigidBodyComponents::getExternalTorque(Entity bodyEntity) } // Return the linear damping factor of an entity -inline decimal RigidBodyComponents::getLinearDamping(Entity bodyEntity) const { +RP3D_FORCE_INLINE decimal RigidBodyComponents::getLinearDamping(Entity bodyEntity) const { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -498,7 +498,7 @@ inline decimal RigidBodyComponents::getLinearDamping(Entity bodyEntity) const { } // Return the angular damping factor of an entity -inline decimal RigidBodyComponents::getAngularDamping(Entity bodyEntity) const { +RP3D_FORCE_INLINE decimal RigidBodyComponents::getAngularDamping(Entity bodyEntity) const { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -506,7 +506,7 @@ inline decimal RigidBodyComponents::getAngularDamping(Entity bodyEntity) const { } // Return the mass of an entity -inline decimal RigidBodyComponents::getMass(Entity bodyEntity) const { +RP3D_FORCE_INLINE decimal RigidBodyComponents::getMass(Entity bodyEntity) const { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -514,7 +514,7 @@ inline decimal RigidBodyComponents::getMass(Entity bodyEntity) const { } // Return the inverse mass of an entity -inline decimal RigidBodyComponents::getMassInverse(Entity bodyEntity) const { +RP3D_FORCE_INLINE decimal RigidBodyComponents::getMassInverse(Entity bodyEntity) const { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -522,7 +522,7 @@ inline decimal RigidBodyComponents::getMassInverse(Entity bodyEntity) const { } // Return the inverse local inertia tensor of an entity -inline const Vector3& RigidBodyComponents::getInertiaTensorLocalInverse(Entity bodyEntity) { +RP3D_FORCE_INLINE const Vector3& RigidBodyComponents::getInertiaTensorLocalInverse(Entity bodyEntity) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -530,7 +530,7 @@ inline const Vector3& RigidBodyComponents::getInertiaTensorLocalInverse(Entity b } // Return the inverse world inertia tensor of an entity -inline const Matrix3x3& RigidBodyComponents::getInertiaTensorWorldInverse(Entity bodyEntity) { +RP3D_FORCE_INLINE const Matrix3x3& RigidBodyComponents::getInertiaTensorWorldInverse(Entity bodyEntity) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -538,7 +538,7 @@ inline const Matrix3x3& RigidBodyComponents::getInertiaTensorWorldInverse(Entity } // Set the external force of an entity -inline void RigidBodyComponents::setExternalForce(Entity bodyEntity, const Vector3& externalForce) { +RP3D_FORCE_INLINE void RigidBodyComponents::setExternalForce(Entity bodyEntity, const Vector3& externalForce) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -546,7 +546,7 @@ inline void RigidBodyComponents::setExternalForce(Entity bodyEntity, const Vecto } // Set the external force of an entity -inline void RigidBodyComponents::setExternalTorque(Entity bodyEntity, const Vector3& externalTorque) { +RP3D_FORCE_INLINE void RigidBodyComponents::setExternalTorque(Entity bodyEntity, const Vector3& externalTorque) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -554,7 +554,7 @@ inline void RigidBodyComponents::setExternalTorque(Entity bodyEntity, const Vect } // Set the linear damping factor of an entity -inline void RigidBodyComponents::setLinearDamping(Entity bodyEntity, decimal linearDamping) { +RP3D_FORCE_INLINE void RigidBodyComponents::setLinearDamping(Entity bodyEntity, decimal linearDamping) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -562,7 +562,7 @@ inline void RigidBodyComponents::setLinearDamping(Entity bodyEntity, decimal lin } // Set the angular damping factor of an entity -inline void RigidBodyComponents::setAngularDamping(Entity bodyEntity, decimal angularDamping) { +RP3D_FORCE_INLINE void RigidBodyComponents::setAngularDamping(Entity bodyEntity, decimal angularDamping) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -570,7 +570,7 @@ inline void RigidBodyComponents::setAngularDamping(Entity bodyEntity, decimal an } // Set the mass of an entity -inline void RigidBodyComponents::setMass(Entity bodyEntity, decimal mass) { +RP3D_FORCE_INLINE void RigidBodyComponents::setMass(Entity bodyEntity, decimal mass) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -578,7 +578,7 @@ inline void RigidBodyComponents::setMass(Entity bodyEntity, decimal mass) { } // Set the mass inverse of an entity -inline void RigidBodyComponents::setMassInverse(Entity bodyEntity, decimal inverseMass) { +RP3D_FORCE_INLINE void RigidBodyComponents::setMassInverse(Entity bodyEntity, decimal inverseMass) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -586,7 +586,7 @@ inline void RigidBodyComponents::setMassInverse(Entity bodyEntity, decimal inver } // Return the local inertia tensor of an entity -inline const Vector3& RigidBodyComponents::getLocalInertiaTensor(Entity bodyEntity) { +RP3D_FORCE_INLINE const Vector3& RigidBodyComponents::getLocalInertiaTensor(Entity bodyEntity) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -594,7 +594,7 @@ inline const Vector3& RigidBodyComponents::getLocalInertiaTensor(Entity bodyEnti } // Set the local inertia tensor of an entity -inline void RigidBodyComponents::setLocalInertiaTensor(Entity bodyEntity, const Vector3& inertiaTensorLocal) { +RP3D_FORCE_INLINE void RigidBodyComponents::setLocalInertiaTensor(Entity bodyEntity, const Vector3& inertiaTensorLocal) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -602,7 +602,7 @@ inline void RigidBodyComponents::setLocalInertiaTensor(Entity bodyEntity, const } // Set the inverse local inertia tensor of an entity -inline void RigidBodyComponents::setInverseInertiaTensorLocal(Entity bodyEntity, const Vector3& inertiaTensorLocalInverse) { +RP3D_FORCE_INLINE void RigidBodyComponents::setInverseInertiaTensorLocal(Entity bodyEntity, const Vector3& inertiaTensorLocalInverse) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -610,7 +610,7 @@ inline void RigidBodyComponents::setInverseInertiaTensorLocal(Entity bodyEntity, } // Return the constrained linear velocity of an entity -inline const Vector3& RigidBodyComponents::getConstrainedLinearVelocity(Entity bodyEntity) const { +RP3D_FORCE_INLINE const Vector3& RigidBodyComponents::getConstrainedLinearVelocity(Entity bodyEntity) const { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -618,7 +618,7 @@ inline const Vector3& RigidBodyComponents::getConstrainedLinearVelocity(Entity b } // Return the constrained angular velocity of an entity -inline const Vector3& RigidBodyComponents::getConstrainedAngularVelocity(Entity bodyEntity) const { +RP3D_FORCE_INLINE const Vector3& RigidBodyComponents::getConstrainedAngularVelocity(Entity bodyEntity) const { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -626,7 +626,7 @@ inline const Vector3& RigidBodyComponents::getConstrainedAngularVelocity(Entity } // Return the split linear velocity of an entity -inline const Vector3& RigidBodyComponents::getSplitLinearVelocity(Entity bodyEntity) const { +RP3D_FORCE_INLINE const Vector3& RigidBodyComponents::getSplitLinearVelocity(Entity bodyEntity) const { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -634,7 +634,7 @@ inline const Vector3& RigidBodyComponents::getSplitLinearVelocity(Entity bodyEnt } // Return the split angular velocity of an entity -inline const Vector3& RigidBodyComponents::getSplitAngularVelocity(Entity bodyEntity) const { +RP3D_FORCE_INLINE const Vector3& RigidBodyComponents::getSplitAngularVelocity(Entity bodyEntity) const { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -642,7 +642,7 @@ inline const Vector3& RigidBodyComponents::getSplitAngularVelocity(Entity bodyEn } // Return the constrained position of an entity -inline Vector3& RigidBodyComponents::getConstrainedPosition(Entity bodyEntity) { +RP3D_FORCE_INLINE Vector3& RigidBodyComponents::getConstrainedPosition(Entity bodyEntity) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -650,7 +650,7 @@ inline Vector3& RigidBodyComponents::getConstrainedPosition(Entity bodyEntity) { } // Return the constrained orientation of an entity -inline Quaternion& RigidBodyComponents::getConstrainedOrientation(Entity bodyEntity) { +RP3D_FORCE_INLINE Quaternion& RigidBodyComponents::getConstrainedOrientation(Entity bodyEntity) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -658,7 +658,7 @@ inline Quaternion& RigidBodyComponents::getConstrainedOrientation(Entity bodyEnt } // Return the local center of mass of an entity -inline const Vector3& RigidBodyComponents::getCenterOfMassLocal(Entity bodyEntity) { +RP3D_FORCE_INLINE const Vector3& RigidBodyComponents::getCenterOfMassLocal(Entity bodyEntity) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -666,7 +666,7 @@ inline const Vector3& RigidBodyComponents::getCenterOfMassLocal(Entity bodyEntit } // Return the world center of mass of an entity -inline const Vector3& RigidBodyComponents::getCenterOfMassWorld(Entity bodyEntity) { +RP3D_FORCE_INLINE const Vector3& RigidBodyComponents::getCenterOfMassWorld(Entity bodyEntity) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -674,7 +674,7 @@ inline const Vector3& RigidBodyComponents::getCenterOfMassWorld(Entity bodyEntit } // Set the constrained linear velocity of an entity -inline void RigidBodyComponents::setConstrainedLinearVelocity(Entity bodyEntity, const Vector3& constrainedLinearVelocity) { +RP3D_FORCE_INLINE void RigidBodyComponents::setConstrainedLinearVelocity(Entity bodyEntity, const Vector3& constrainedLinearVelocity) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -682,7 +682,7 @@ inline void RigidBodyComponents::setConstrainedLinearVelocity(Entity bodyEntity, } // Set the constrained angular velocity of an entity -inline void RigidBodyComponents::setConstrainedAngularVelocity(Entity bodyEntity, const Vector3& constrainedAngularVelocity) { +RP3D_FORCE_INLINE void RigidBodyComponents::setConstrainedAngularVelocity(Entity bodyEntity, const Vector3& constrainedAngularVelocity) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -690,7 +690,7 @@ inline void RigidBodyComponents::setConstrainedAngularVelocity(Entity bodyEntity } // Set the split linear velocity of an entity -inline void RigidBodyComponents::setSplitLinearVelocity(Entity bodyEntity, const Vector3& splitLinearVelocity) { +RP3D_FORCE_INLINE void RigidBodyComponents::setSplitLinearVelocity(Entity bodyEntity, const Vector3& splitLinearVelocity) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -698,7 +698,7 @@ inline void RigidBodyComponents::setSplitLinearVelocity(Entity bodyEntity, const } // Set the split angular velocity of an entity -inline void RigidBodyComponents::setSplitAngularVelocity(Entity bodyEntity, const Vector3& splitAngularVelocity) { +RP3D_FORCE_INLINE void RigidBodyComponents::setSplitAngularVelocity(Entity bodyEntity, const Vector3& splitAngularVelocity) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -706,7 +706,7 @@ inline void RigidBodyComponents::setSplitAngularVelocity(Entity bodyEntity, cons } // Set the constrained position of an entity -inline void RigidBodyComponents::setConstrainedPosition(Entity bodyEntity, const Vector3& constrainedPosition) { +RP3D_FORCE_INLINE void RigidBodyComponents::setConstrainedPosition(Entity bodyEntity, const Vector3& constrainedPosition) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -714,7 +714,7 @@ inline void RigidBodyComponents::setConstrainedPosition(Entity bodyEntity, const } // Set the constrained orientation of an entity -inline void RigidBodyComponents::setConstrainedOrientation(Entity bodyEntity, const Quaternion& constrainedOrientation) { +RP3D_FORCE_INLINE void RigidBodyComponents::setConstrainedOrientation(Entity bodyEntity, const Quaternion& constrainedOrientation) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -722,7 +722,7 @@ inline void RigidBodyComponents::setConstrainedOrientation(Entity bodyEntity, co } // Set the local center of mass of an entity -inline void RigidBodyComponents::setCenterOfMassLocal(Entity bodyEntity, const Vector3& centerOfMassLocal) { +RP3D_FORCE_INLINE void RigidBodyComponents::setCenterOfMassLocal(Entity bodyEntity, const Vector3& centerOfMassLocal) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -730,7 +730,7 @@ inline void RigidBodyComponents::setCenterOfMassLocal(Entity bodyEntity, const V } // Set the world center of mass of an entity -inline void RigidBodyComponents::setCenterOfMassWorld(Entity bodyEntity, const Vector3& centerOfMassWorld) { +RP3D_FORCE_INLINE void RigidBodyComponents::setCenterOfMassWorld(Entity bodyEntity, const Vector3& centerOfMassWorld) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -738,7 +738,7 @@ inline void RigidBodyComponents::setCenterOfMassWorld(Entity bodyEntity, const V } // Return true if gravity is enabled for this entity -inline bool RigidBodyComponents::getIsGravityEnabled(Entity bodyEntity) const { +RP3D_FORCE_INLINE bool RigidBodyComponents::getIsGravityEnabled(Entity bodyEntity) const { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -746,7 +746,7 @@ inline bool RigidBodyComponents::getIsGravityEnabled(Entity bodyEntity) const { } // Return true if the entity is already in an island -inline bool RigidBodyComponents::getIsAlreadyInIsland(Entity bodyEntity) const { +RP3D_FORCE_INLINE bool RigidBodyComponents::getIsAlreadyInIsland(Entity bodyEntity) const { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -754,7 +754,7 @@ inline bool RigidBodyComponents::getIsAlreadyInIsland(Entity bodyEntity) const { } // Set the value to know if the gravity is enabled for this entity -inline void RigidBodyComponents::setIsGravityEnabled(Entity bodyEntity, bool isGravityEnabled) { +RP3D_FORCE_INLINE void RigidBodyComponents::setIsGravityEnabled(Entity bodyEntity, bool isGravityEnabled) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); @@ -762,35 +762,35 @@ inline void RigidBodyComponents::setIsGravityEnabled(Entity bodyEntity, bool isG } // Set the value to know if the entity is already in an island -inline void RigidBodyComponents::setIsAlreadyInIsland(Entity bodyEntity, bool isAlreadyInIsland) { +RP3D_FORCE_INLINE void RigidBodyComponents::setIsAlreadyInIsland(Entity bodyEntity, bool isAlreadyInIsland) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); mIsAlreadyInIsland[mMapEntityToComponentIndex[bodyEntity]] = isAlreadyInIsland; } // Return the list of joints of a body -inline const List& RigidBodyComponents::getJoints(Entity bodyEntity) const { +RP3D_FORCE_INLINE const List& RigidBodyComponents::getJoints(Entity bodyEntity) const { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); return mJoints[mMapEntityToComponentIndex[bodyEntity]]; } // Add a joint to a body component -inline void RigidBodyComponents::addJointToBody(Entity bodyEntity, Entity jointEntity) { +RP3D_FORCE_INLINE void RigidBodyComponents::addJointToBody(Entity bodyEntity, Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); mJoints[mMapEntityToComponentIndex[bodyEntity]].add(jointEntity); } // Remove a joint from a body component -inline void RigidBodyComponents::removeJointFromBody(Entity bodyEntity, Entity jointEntity) { +RP3D_FORCE_INLINE void RigidBodyComponents::removeJointFromBody(Entity bodyEntity, Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); mJoints[mMapEntityToComponentIndex[bodyEntity]].remove(jointEntity); } // A an associated contact pairs into the contact pairs array of the body -inline void RigidBodyComponents::addContacPair(Entity bodyEntity, uint contactPairIndex) { +RP3D_FORCE_INLINE void RigidBodyComponents::addContacPair(Entity bodyEntity, uint contactPairIndex) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); mContactPairs[mMapEntityToComponentIndex[bodyEntity]].add(contactPairIndex); diff --git a/include/reactphysics3d/components/SliderJointComponents.h b/include/reactphysics3d/components/SliderJointComponents.h index f15388ed..56672259 100644 --- a/include/reactphysics3d/components/SliderJointComponents.h +++ b/include/reactphysics3d/components/SliderJointComponents.h @@ -460,266 +460,266 @@ class SliderJointComponents : public Components { }; // Return a pointer to a given joint -inline SliderJoint* SliderJointComponents::getJoint(Entity jointEntity) const { +RP3D_FORCE_INLINE SliderJoint* SliderJointComponents::getJoint(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mJoints[mMapEntityToComponentIndex[jointEntity]]; } // Set the joint pointer to a given joint -inline void SliderJointComponents::setJoint(Entity jointEntity, SliderJoint* joint) const { +RP3D_FORCE_INLINE void SliderJointComponents::setJoint(Entity jointEntity, SliderJoint* joint) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mJoints[mMapEntityToComponentIndex[jointEntity]] = joint; } // Return the local anchor point of body 1 for a given joint -inline const Vector3& SliderJointComponents::getLocalAnchorPointBody1(Entity jointEntity) const { +RP3D_FORCE_INLINE const Vector3& SliderJointComponents::getLocalAnchorPointBody1(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mLocalAnchorPointBody1[mMapEntityToComponentIndex[jointEntity]]; } // Set the local anchor point of body 1 for a given joint -inline void SliderJointComponents::setLocalAnchorPointBody1(Entity jointEntity, const Vector3& localAnchoirPointBody1) { +RP3D_FORCE_INLINE void SliderJointComponents::setLocalAnchorPointBody1(Entity jointEntity, const Vector3& localAnchoirPointBody1) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mLocalAnchorPointBody1[mMapEntityToComponentIndex[jointEntity]] = localAnchoirPointBody1; } // Return the local anchor point of body 2 for a given joint -inline const Vector3& SliderJointComponents::getLocalAnchorPointBody2(Entity jointEntity) const { +RP3D_FORCE_INLINE const Vector3& SliderJointComponents::getLocalAnchorPointBody2(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mLocalAnchorPointBody2[mMapEntityToComponentIndex[jointEntity]]; } // Set the local anchor point of body 2 for a given joint -inline void SliderJointComponents::setLocalAnchorPointBody2(Entity jointEntity, const Vector3& localAnchoirPointBody2) { +RP3D_FORCE_INLINE void SliderJointComponents::setLocalAnchorPointBody2(Entity jointEntity, const Vector3& localAnchoirPointBody2) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mLocalAnchorPointBody2[mMapEntityToComponentIndex[jointEntity]] = localAnchoirPointBody2; } // Return the inertia tensor of body 1 (in world-space coordinates) -inline const Matrix3x3& SliderJointComponents::getI1(Entity jointEntity) const { +RP3D_FORCE_INLINE const Matrix3x3& SliderJointComponents::getI1(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mI1[mMapEntityToComponentIndex[jointEntity]]; } // Set the inertia tensor of body 1 (in world-space coordinates) -inline void SliderJointComponents::setI1(Entity jointEntity, const Matrix3x3& i1) { +RP3D_FORCE_INLINE void SliderJointComponents::setI1(Entity jointEntity, const Matrix3x3& i1) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mI1[mMapEntityToComponentIndex[jointEntity]] = i1; } // Return the inertia tensor of body 2 (in world-space coordinates) -inline const Matrix3x3& SliderJointComponents::getI2(Entity jointEntity) const { +RP3D_FORCE_INLINE const Matrix3x3& SliderJointComponents::getI2(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mI2[mMapEntityToComponentIndex[jointEntity]]; } // Set the inertia tensor of body 2 (in world-space coordinates) -inline void SliderJointComponents::setI2(Entity jointEntity, const Matrix3x3& i2) { +RP3D_FORCE_INLINE void SliderJointComponents::setI2(Entity jointEntity, const Matrix3x3& i2) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mI2[mMapEntityToComponentIndex[jointEntity]] = i2; } // Return the translation impulse -inline Vector2& SliderJointComponents::getImpulseTranslation(Entity jointEntity) { +RP3D_FORCE_INLINE Vector2& SliderJointComponents::getImpulseTranslation(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mImpulseTranslation[mMapEntityToComponentIndex[jointEntity]]; } // Set the translation impulse -inline void SliderJointComponents::setImpulseTranslation(Entity jointEntity, const Vector2& impulseTranslation) { +RP3D_FORCE_INLINE void SliderJointComponents::setImpulseTranslation(Entity jointEntity, const Vector2& impulseTranslation) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mImpulseTranslation[mMapEntityToComponentIndex[jointEntity]] = impulseTranslation; } // Return the translation impulse -inline Vector3& SliderJointComponents::getImpulseRotation(Entity jointEntity) { +RP3D_FORCE_INLINE Vector3& SliderJointComponents::getImpulseRotation(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mImpulseRotation[mMapEntityToComponentIndex[jointEntity]]; } // Set the translation impulse -inline void SliderJointComponents::setImpulseRotation(Entity jointEntity, const Vector3& impulseTranslation) { +RP3D_FORCE_INLINE void SliderJointComponents::setImpulseRotation(Entity jointEntity, const Vector3& impulseTranslation) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mImpulseRotation[mMapEntityToComponentIndex[jointEntity]] = impulseTranslation; } // Return the translation inverse mass matrix of the constraint -inline Matrix2x2& SliderJointComponents::getInverseMassMatrixTranslation(Entity jointEntity) { +RP3D_FORCE_INLINE Matrix2x2& SliderJointComponents::getInverseMassMatrixTranslation(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mInverseMassMatrixTranslation[mMapEntityToComponentIndex[jointEntity]]; } // Set the translation inverse mass matrix of the constraint -inline void SliderJointComponents::setInverseMassMatrixTranslation(Entity jointEntity, const Matrix2x2& inverseMassMatrix) { +RP3D_FORCE_INLINE void SliderJointComponents::setInverseMassMatrixTranslation(Entity jointEntity, const Matrix2x2& inverseMassMatrix) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mInverseMassMatrixTranslation[mMapEntityToComponentIndex[jointEntity]] = inverseMassMatrix; } // Return the rotation inverse mass matrix of the constraint -inline Matrix3x3& SliderJointComponents::getInverseMassMatrixRotation(Entity jointEntity) { +RP3D_FORCE_INLINE Matrix3x3& SliderJointComponents::getInverseMassMatrixRotation(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mInverseMassMatrixRotation[mMapEntityToComponentIndex[jointEntity]]; } // Set the rotation inverse mass matrix of the constraint -inline void SliderJointComponents::setInverseMassMatrixRotation(Entity jointEntity, const Matrix3x3& inverseMassMatrix) { +RP3D_FORCE_INLINE void SliderJointComponents::setInverseMassMatrixRotation(Entity jointEntity, const Matrix3x3& inverseMassMatrix) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mInverseMassMatrixRotation[mMapEntityToComponentIndex[jointEntity]] = inverseMassMatrix; } // Return the translation bias -inline Vector2& SliderJointComponents::getBiasTranslation(Entity jointEntity) { +RP3D_FORCE_INLINE Vector2& SliderJointComponents::getBiasTranslation(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mBiasTranslation[mMapEntityToComponentIndex[jointEntity]]; } // Set the translation impulse -inline void SliderJointComponents::setBiasTranslation(Entity jointEntity, const Vector2& impulseTranslation) { +RP3D_FORCE_INLINE void SliderJointComponents::setBiasTranslation(Entity jointEntity, const Vector2& impulseTranslation) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mBiasTranslation[mMapEntityToComponentIndex[jointEntity]] = impulseTranslation; } // Return the rotation bias -inline Vector3& SliderJointComponents::getBiasRotation(Entity jointEntity) { +RP3D_FORCE_INLINE Vector3& SliderJointComponents::getBiasRotation(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mBiasRotation[mMapEntityToComponentIndex[jointEntity]]; } // Set the rotation impulse -inline void SliderJointComponents::setBiasRotation(Entity jointEntity, const Vector3& impulseRotation) { +RP3D_FORCE_INLINE void SliderJointComponents::setBiasRotation(Entity jointEntity, const Vector3& impulseRotation) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mBiasRotation[mMapEntityToComponentIndex[jointEntity]] = impulseRotation; } // Return the initial orientation difference -inline Quaternion& SliderJointComponents::getInitOrientationDifferenceInv(Entity jointEntity) { +RP3D_FORCE_INLINE Quaternion& SliderJointComponents::getInitOrientationDifferenceInv(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mInitOrientationDifferenceInv[mMapEntityToComponentIndex[jointEntity]]; } // Set the rotation impulse -inline void SliderJointComponents::setInitOrientationDifferenceInv(Entity jointEntity, const Quaternion& initOrientationDifferenceInv) { +RP3D_FORCE_INLINE void SliderJointComponents::setInitOrientationDifferenceInv(Entity jointEntity, const Quaternion& initOrientationDifferenceInv) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mInitOrientationDifferenceInv[mMapEntityToComponentIndex[jointEntity]] = initOrientationDifferenceInv; } // Return the slider axis (in local-space coordinates of body 1) -inline Vector3& SliderJointComponents::getSliderAxisBody1(Entity jointEntity) { +RP3D_FORCE_INLINE Vector3& SliderJointComponents::getSliderAxisBody1(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mSliderAxisBody1[mMapEntityToComponentIndex[jointEntity]]; } // Set the slider axis (in local-space coordinates of body 1) -inline void SliderJointComponents::setSliderAxisBody1(Entity jointEntity, const Vector3& sliderAxisBody1) { +RP3D_FORCE_INLINE void SliderJointComponents::setSliderAxisBody1(Entity jointEntity, const Vector3& sliderAxisBody1) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mSliderAxisBody1[mMapEntityToComponentIndex[jointEntity]] = sliderAxisBody1; } // Retunr the slider axis in world-space coordinates -inline Vector3& SliderJointComponents::getSliderAxisWorld(Entity jointEntity) { +RP3D_FORCE_INLINE Vector3& SliderJointComponents::getSliderAxisWorld(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mSliderAxisWorld[mMapEntityToComponentIndex[jointEntity]]; } // Set the slider axis in world-space coordinates -inline void SliderJointComponents::setSliderAxisWorld(Entity jointEntity, const Vector3& sliderAxisWorld) { +RP3D_FORCE_INLINE void SliderJointComponents::setSliderAxisWorld(Entity jointEntity, const Vector3& sliderAxisWorld) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mSliderAxisWorld[mMapEntityToComponentIndex[jointEntity]] = sliderAxisWorld; } // Return the vector r1 in world-space coordinates -inline Vector3& SliderJointComponents::getR1(Entity jointEntity) { +RP3D_FORCE_INLINE Vector3& SliderJointComponents::getR1(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mR1[mMapEntityToComponentIndex[jointEntity]]; } // Set the vector r1 in world-space coordinates -inline void SliderJointComponents::setR1(Entity jointEntity, const Vector3& r1) { +RP3D_FORCE_INLINE void SliderJointComponents::setR1(Entity jointEntity, const Vector3& r1) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mR1[mMapEntityToComponentIndex[jointEntity]] = r1; } // Return the vector r2 in world-space coordinates -inline Vector3& SliderJointComponents::getR2(Entity jointEntity) { +RP3D_FORCE_INLINE Vector3& SliderJointComponents::getR2(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mR2[mMapEntityToComponentIndex[jointEntity]]; } // Set the vector r2 in world-space coordinates -inline void SliderJointComponents::setR2(Entity jointEntity, const Vector3& r2) { +RP3D_FORCE_INLINE void SliderJointComponents::setR2(Entity jointEntity, const Vector3& r2) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mR2[mMapEntityToComponentIndex[jointEntity]] = r2; } // Return the first vector orthogonal to the slider axis local-space of body 1 -inline Vector3& SliderJointComponents::getN1(Entity jointEntity) { +RP3D_FORCE_INLINE Vector3& SliderJointComponents::getN1(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mN1[mMapEntityToComponentIndex[jointEntity]]; } // Set the first vector orthogonal to the slider axis local-space of body 1 -inline void SliderJointComponents::setN1(Entity jointEntity, const Vector3& n1) { +RP3D_FORCE_INLINE void SliderJointComponents::setN1(Entity jointEntity, const Vector3& n1) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mN1[mMapEntityToComponentIndex[jointEntity]] = n1; } // Return the second vector orthogonal to the slider axis and mN1 in local-space of body 1 -inline Vector3& SliderJointComponents::getN2(Entity jointEntity) { +RP3D_FORCE_INLINE Vector3& SliderJointComponents::getN2(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mN2[mMapEntityToComponentIndex[jointEntity]]; } // Set the second vector orthogonal to the slider axis and mN1 in local-space of body 1 -inline void SliderJointComponents::setN2(Entity jointEntity, const Vector3& n2) { +RP3D_FORCE_INLINE void SliderJointComponents::setN2(Entity jointEntity, const Vector3& n2) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mN2[mMapEntityToComponentIndex[jointEntity]] = n2; } // Return the accumulated impulse for the lower limit constraint -inline decimal SliderJointComponents::getImpulseLowerLimit(Entity jointEntity) const { +RP3D_FORCE_INLINE decimal SliderJointComponents::getImpulseLowerLimit(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mImpulseLowerLimit[mMapEntityToComponentIndex[jointEntity]]; } // Set the accumulated impulse for the lower limit constraint -inline void SliderJointComponents::setImpulseLowerLimit(Entity jointEntity, decimal impulseLowerLimit) { +RP3D_FORCE_INLINE void SliderJointComponents::setImpulseLowerLimit(Entity jointEntity, decimal impulseLowerLimit) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mImpulseLowerLimit[mMapEntityToComponentIndex[jointEntity]] = impulseLowerLimit; @@ -727,14 +727,14 @@ inline void SliderJointComponents::setImpulseLowerLimit(Entity jointEntity, deci // Return the accumulated impulse for the upper limit constraint -inline decimal SliderJointComponents::getImpulseUpperLimit(Entity jointEntity) const { +RP3D_FORCE_INLINE decimal SliderJointComponents::getImpulseUpperLimit(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mImpulseUpperLimit[mMapEntityToComponentIndex[jointEntity]]; } // Set the accumulated impulse for the upper limit constraint -inline void SliderJointComponents::setImpulseUpperLimit(Entity jointEntity, decimal impulseUpperLimit) const { +RP3D_FORCE_INLINE void SliderJointComponents::setImpulseUpperLimit(Entity jointEntity, decimal impulseUpperLimit) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mImpulseUpperLimit[mMapEntityToComponentIndex[jointEntity]] = impulseUpperLimit; @@ -742,266 +742,266 @@ inline void SliderJointComponents::setImpulseUpperLimit(Entity jointEntity, deci // Return the accumulated impulse for the motor constraint; -inline decimal SliderJointComponents::getImpulseMotor(Entity jointEntity) const { +RP3D_FORCE_INLINE decimal SliderJointComponents::getImpulseMotor(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mImpulseMotor[mMapEntityToComponentIndex[jointEntity]]; } // Set the accumulated impulse for the motor constraint; -inline void SliderJointComponents::setImpulseMotor(Entity jointEntity, decimal impulseMotor) { +RP3D_FORCE_INLINE void SliderJointComponents::setImpulseMotor(Entity jointEntity, decimal impulseMotor) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mImpulseMotor[mMapEntityToComponentIndex[jointEntity]] = impulseMotor; } // Return the inverse of mass matrix K=JM^-1J^t for the limits (1x1 matrix) -inline decimal SliderJointComponents::getInverseMassMatrixLimit(Entity jointEntity) const { +RP3D_FORCE_INLINE decimal SliderJointComponents::getInverseMassMatrixLimit(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mInverseMassMatrixLimit[mMapEntityToComponentIndex[jointEntity]]; } // Set the inverse of mass matrix K=JM^-1J^t for the limits (1x1 matrix) -inline void SliderJointComponents::setInverseMassMatrixLimit(Entity jointEntity, decimal inverseMassMatrixLimitMotor) { +RP3D_FORCE_INLINE void SliderJointComponents::setInverseMassMatrixLimit(Entity jointEntity, decimal inverseMassMatrixLimitMotor) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mInverseMassMatrixLimit[mMapEntityToComponentIndex[jointEntity]] = inverseMassMatrixLimitMotor; } // Return the inverse of mass matrix K=JM^-1J^t for the motor -inline decimal SliderJointComponents::getInverseMassMatrixMotor(Entity jointEntity) { +RP3D_FORCE_INLINE decimal SliderJointComponents::getInverseMassMatrixMotor(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mInverseMassMatrixMotor[mMapEntityToComponentIndex[jointEntity]]; } // Return the inverse of mass matrix K=JM^-1J^t for the motor -inline void SliderJointComponents::setInverseMassMatrixMotor(Entity jointEntity, decimal inverseMassMatrixMotor) { +RP3D_FORCE_INLINE void SliderJointComponents::setInverseMassMatrixMotor(Entity jointEntity, decimal inverseMassMatrixMotor) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mInverseMassMatrixMotor[mMapEntityToComponentIndex[jointEntity]] = inverseMassMatrixMotor; } // Return the bias of the lower limit constraint -inline decimal SliderJointComponents::getBLowerLimit(Entity jointEntity) const { +RP3D_FORCE_INLINE decimal SliderJointComponents::getBLowerLimit(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mBLowerLimit[mMapEntityToComponentIndex[jointEntity]]; } // Set the bias of the lower limit constraint -inline void SliderJointComponents::setBLowerLimit(Entity jointEntity, decimal bLowerLimit) const { +RP3D_FORCE_INLINE void SliderJointComponents::setBLowerLimit(Entity jointEntity, decimal bLowerLimit) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mBLowerLimit[mMapEntityToComponentIndex[jointEntity]] = bLowerLimit; } // Return the bias of the upper limit constraint -inline decimal SliderJointComponents::getBUpperLimit(Entity jointEntity) const { +RP3D_FORCE_INLINE decimal SliderJointComponents::getBUpperLimit(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mBUpperLimit[mMapEntityToComponentIndex[jointEntity]]; } // Set the bias of the upper limit constraint -inline void SliderJointComponents::setBUpperLimit(Entity jointEntity, decimal bUpperLimit) { +RP3D_FORCE_INLINE void SliderJointComponents::setBUpperLimit(Entity jointEntity, decimal bUpperLimit) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mBUpperLimit[mMapEntityToComponentIndex[jointEntity]] = bUpperLimit; } // Return true if the joint limits are enabled -inline bool SliderJointComponents::getIsLimitEnabled(Entity jointEntity) const { +RP3D_FORCE_INLINE bool SliderJointComponents::getIsLimitEnabled(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mIsLimitEnabled[mMapEntityToComponentIndex[jointEntity]]; } // Set to true if the joint limits are enabled -inline void SliderJointComponents::setIsLimitEnabled(Entity jointEntity, bool isLimitEnabled) { +RP3D_FORCE_INLINE void SliderJointComponents::setIsLimitEnabled(Entity jointEntity, bool isLimitEnabled) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mIsLimitEnabled[mMapEntityToComponentIndex[jointEntity]] = isLimitEnabled; } // Return true if the motor of the joint in enabled -inline bool SliderJointComponents::getIsMotorEnabled(Entity jointEntity) const { +RP3D_FORCE_INLINE bool SliderJointComponents::getIsMotorEnabled(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mIsMotorEnabled[mMapEntityToComponentIndex[jointEntity]]; } // Set to true if the motor of the joint in enabled -inline void SliderJointComponents::setIsMotorEnabled(Entity jointEntity, bool isMotorEnabled) const { +RP3D_FORCE_INLINE void SliderJointComponents::setIsMotorEnabled(Entity jointEntity, bool isMotorEnabled) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mIsMotorEnabled[mMapEntityToComponentIndex[jointEntity]] = isMotorEnabled; } // Return the Lower limit (minimum allowed rotation angle in radian) -inline decimal SliderJointComponents::getLowerLimit(Entity jointEntity) const { +RP3D_FORCE_INLINE decimal SliderJointComponents::getLowerLimit(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mLowerLimit[mMapEntityToComponentIndex[jointEntity]]; } // Set the Lower limit (minimum allowed rotation angle in radian) -inline void SliderJointComponents::setLowerLimit(Entity jointEntity, decimal lowerLimit) const { +RP3D_FORCE_INLINE void SliderJointComponents::setLowerLimit(Entity jointEntity, decimal lowerLimit) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mLowerLimit[mMapEntityToComponentIndex[jointEntity]] = lowerLimit; } // Return the upper limit (maximum translation distance) -inline decimal SliderJointComponents::getUpperLimit(Entity jointEntity) const { +RP3D_FORCE_INLINE decimal SliderJointComponents::getUpperLimit(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mUpperLimit[mMapEntityToComponentIndex[jointEntity]]; } // Set the upper limit (maximum translation distance) -inline void SliderJointComponents::setUpperLimit(Entity jointEntity, decimal upperLimit) { +RP3D_FORCE_INLINE void SliderJointComponents::setUpperLimit(Entity jointEntity, decimal upperLimit) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mUpperLimit[mMapEntityToComponentIndex[jointEntity]] = upperLimit; } // Return true if the lower limit is violated -inline bool SliderJointComponents::getIsLowerLimitViolated(Entity jointEntity) const { +RP3D_FORCE_INLINE bool SliderJointComponents::getIsLowerLimitViolated(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mIsLowerLimitViolated[mMapEntityToComponentIndex[jointEntity]]; } // Set to true if the lower limit is violated -inline void SliderJointComponents::setIsLowerLimitViolated(Entity jointEntity, bool isLowerLimitViolated) { +RP3D_FORCE_INLINE void SliderJointComponents::setIsLowerLimitViolated(Entity jointEntity, bool isLowerLimitViolated) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mIsLowerLimitViolated[mMapEntityToComponentIndex[jointEntity]] = isLowerLimitViolated; } // Return true if the upper limit is violated -inline bool SliderJointComponents::getIsUpperLimitViolated(Entity jointEntity) const { +RP3D_FORCE_INLINE bool SliderJointComponents::getIsUpperLimitViolated(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mIsUpperLimitViolated[mMapEntityToComponentIndex[jointEntity]]; } // Set to true if the upper limit is violated -inline void SliderJointComponents::setIsUpperLimitViolated(Entity jointEntity, bool isUpperLimitViolated) const { +RP3D_FORCE_INLINE void SliderJointComponents::setIsUpperLimitViolated(Entity jointEntity, bool isUpperLimitViolated) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mIsUpperLimitViolated[mMapEntityToComponentIndex[jointEntity]] = isUpperLimitViolated; } // Return the motor speed (in rad/s) -inline decimal SliderJointComponents::getMotorSpeed(Entity jointEntity) const { +RP3D_FORCE_INLINE decimal SliderJointComponents::getMotorSpeed(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mMotorSpeed[mMapEntityToComponentIndex[jointEntity]]; } // Set the motor speed (in rad/s) -inline void SliderJointComponents::setMotorSpeed(Entity jointEntity, decimal motorSpeed) { +RP3D_FORCE_INLINE void SliderJointComponents::setMotorSpeed(Entity jointEntity, decimal motorSpeed) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mMotorSpeed[mMapEntityToComponentIndex[jointEntity]] = motorSpeed; } // Return the maximum motor torque (in Newtons) that can be applied to reach to desired motor speed -inline decimal SliderJointComponents::getMaxMotorForce(Entity jointEntity) const { +RP3D_FORCE_INLINE decimal SliderJointComponents::getMaxMotorForce(Entity jointEntity) const { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mMaxMotorForce[mMapEntityToComponentIndex[jointEntity]]; } // Set the maximum motor torque (in Newtons) that can be applied to reach to desired motor speed -inline void SliderJointComponents::setMaxMotorForce(Entity jointEntity, decimal maxMotorForce) { +RP3D_FORCE_INLINE void SliderJointComponents::setMaxMotorForce(Entity jointEntity, decimal maxMotorForce) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mMaxMotorForce[mMapEntityToComponentIndex[jointEntity]] = maxMotorForce; } // Return the cross product of r2 and n1 -inline Vector3& SliderJointComponents::getR2CrossN1(Entity jointEntity) { +RP3D_FORCE_INLINE Vector3& SliderJointComponents::getR2CrossN1(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mR2CrossN1[mMapEntityToComponentIndex[jointEntity]]; } // Set the cross product of r2 and n1 -inline void SliderJointComponents::setR2CrossN1(Entity jointEntity, const Vector3& r2CrossN1) { +RP3D_FORCE_INLINE void SliderJointComponents::setR2CrossN1(Entity jointEntity, const Vector3& r2CrossN1) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mR2CrossN1[mMapEntityToComponentIndex[jointEntity]] = r2CrossN1; } // Return the cross product of r2 and n2 -inline Vector3& SliderJointComponents::getR2CrossN2(Entity jointEntity) { +RP3D_FORCE_INLINE Vector3& SliderJointComponents::getR2CrossN2(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mR2CrossN2[mMapEntityToComponentIndex[jointEntity]]; } // Set the cross product of r2 and n2 -inline void SliderJointComponents::setR2CrossN2(Entity jointEntity, const Vector3& r2CrossN2) { +RP3D_FORCE_INLINE void SliderJointComponents::setR2CrossN2(Entity jointEntity, const Vector3& r2CrossN2) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mR2CrossN2[mMapEntityToComponentIndex[jointEntity]] = r2CrossN2; } // Return the cross product of r2 and the slider axis -inline Vector3& SliderJointComponents::getR2CrossSliderAxis(Entity jointEntity) { +RP3D_FORCE_INLINE Vector3& SliderJointComponents::getR2CrossSliderAxis(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mR2CrossSliderAxis[mMapEntityToComponentIndex[jointEntity]]; } // Set the cross product of r2 and the slider axis -inline void SliderJointComponents::setR2CrossSliderAxis(Entity jointEntity, const Vector3& r2CrossSliderAxis) { +RP3D_FORCE_INLINE void SliderJointComponents::setR2CrossSliderAxis(Entity jointEntity, const Vector3& r2CrossSliderAxis) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mR2CrossSliderAxis[mMapEntityToComponentIndex[jointEntity]] = r2CrossSliderAxis; } // Return the cross product of vector (r1 + u) and n1 -inline Vector3& SliderJointComponents::getR1PlusUCrossN1(Entity jointEntity) { +RP3D_FORCE_INLINE Vector3& SliderJointComponents::getR1PlusUCrossN1(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mR1PlusUCrossN1[mMapEntityToComponentIndex[jointEntity]]; } // Set the cross product of vector (r1 + u) and n1 -inline void SliderJointComponents::setR1PlusUCrossN1(Entity jointEntity, const Vector3& r1PlusUCrossN1) { +RP3D_FORCE_INLINE void SliderJointComponents::setR1PlusUCrossN1(Entity jointEntity, const Vector3& r1PlusUCrossN1) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mR1PlusUCrossN1[mMapEntityToComponentIndex[jointEntity]] = r1PlusUCrossN1; } // Return the cross product of vector (r1 + u) and n2 -inline Vector3& SliderJointComponents::getR1PlusUCrossN2(Entity jointEntity) { +RP3D_FORCE_INLINE Vector3& SliderJointComponents::getR1PlusUCrossN2(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mR1PlusUCrossN2[mMapEntityToComponentIndex[jointEntity]]; } // Set the cross product of vector (r1 + u) and n2 -inline void SliderJointComponents::setR1PlusUCrossN2(Entity jointEntity, const Vector3& r1PlusUCrossN2) { +RP3D_FORCE_INLINE void SliderJointComponents::setR1PlusUCrossN2(Entity jointEntity, const Vector3& r1PlusUCrossN2) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mR1PlusUCrossN2[mMapEntityToComponentIndex[jointEntity]] = r1PlusUCrossN2; } // Return the cross product of vector (r1 + u) and the slider axis -inline Vector3& SliderJointComponents::getR1PlusUCrossSliderAxis(Entity jointEntity) { +RP3D_FORCE_INLINE Vector3& SliderJointComponents::getR1PlusUCrossSliderAxis(Entity jointEntity) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); return mR1PlusUCrossSliderAxis[mMapEntityToComponentIndex[jointEntity]]; } // Set the cross product of vector (r1 + u) and the slider axis -inline void SliderJointComponents::setR1PlusUCrossSliderAxis(Entity jointEntity, const Vector3& r1PlusUCrossSliderAxis) { +RP3D_FORCE_INLINE void SliderJointComponents::setR1PlusUCrossSliderAxis(Entity jointEntity, const Vector3& r1PlusUCrossSliderAxis) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mR1PlusUCrossSliderAxis[mMapEntityToComponentIndex[jointEntity]] = r1PlusUCrossSliderAxis; diff --git a/include/reactphysics3d/components/TransformComponents.h b/include/reactphysics3d/components/TransformComponents.h index a0d5878a..011be27d 100644 --- a/include/reactphysics3d/components/TransformComponents.h +++ b/include/reactphysics3d/components/TransformComponents.h @@ -107,13 +107,13 @@ class TransformComponents : public Components { }; // Return the transform of an entity -inline Transform& TransformComponents::getTransform(Entity bodyEntity) const { +RP3D_FORCE_INLINE Transform& TransformComponents::getTransform(Entity bodyEntity) const { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); return mTransforms[mMapEntityToComponentIndex[bodyEntity]]; } // Set the transform of an entity -inline void TransformComponents::setTransform(Entity bodyEntity, const Transform& transform) { +RP3D_FORCE_INLINE void TransformComponents::setTransform(Entity bodyEntity, const Transform& transform) { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); mTransforms[mMapEntityToComponentIndex[bodyEntity]] = transform; } diff --git a/include/reactphysics3d/configuration.h b/include/reactphysics3d/configuration.h index 77832d0d..4aca00cf 100644 --- a/include/reactphysics3d/configuration.h +++ b/include/reactphysics3d/configuration.h @@ -55,7 +55,7 @@ #define RP3D_COMPILER_UNKNOWN #endif -// Force inline macro +// Force RP3D_FORCE_INLINE macro #if defined(RP3D_COMPILER_VISUAL_STUDIO) #define RP3D_FORCE_INLINE __forceinline #elif defined(RP3D_COMPILER_GCC) || defined(RP3D_COMPILER_CLANG) diff --git a/include/reactphysics3d/constraint/BallAndSocketJoint.h b/include/reactphysics3d/constraint/BallAndSocketJoint.h index fe834a6b..ee671664 100644 --- a/include/reactphysics3d/constraint/BallAndSocketJoint.h +++ b/include/reactphysics3d/constraint/BallAndSocketJoint.h @@ -129,7 +129,7 @@ class BallAndSocketJoint : public Joint { }; // Return the number of bytes used by the joint -inline size_t BallAndSocketJoint::getSizeInBytes() const { +RP3D_FORCE_INLINE size_t BallAndSocketJoint::getSizeInBytes() const { return sizeof(BallAndSocketJoint); } diff --git a/include/reactphysics3d/constraint/ContactPoint.h b/include/reactphysics3d/constraint/ContactPoint.h index 66d321a5..d8491088 100644 --- a/include/reactphysics3d/constraint/ContactPoint.h +++ b/include/reactphysics3d/constraint/ContactPoint.h @@ -144,7 +144,7 @@ class ContactPoint { /** * @return The contact normal */ -inline const Vector3& ContactPoint::getNormal() const { +RP3D_FORCE_INLINE const Vector3& ContactPoint::getNormal() const { return mNormal; } @@ -152,7 +152,7 @@ inline const Vector3& ContactPoint::getNormal() const { /** * @return The contact point on the first collider in the local-space of the collider */ -inline const Vector3& ContactPoint::getLocalPointOnShape1() const { +RP3D_FORCE_INLINE const Vector3& ContactPoint::getLocalPointOnShape1() const { return mLocalPointOnShape1; } @@ -160,7 +160,7 @@ inline const Vector3& ContactPoint::getLocalPointOnShape1() const { /** * @return The contact point on the second collider in the local-space of the collider */ -inline const Vector3& ContactPoint::getLocalPointOnShape2() const { +RP3D_FORCE_INLINE const Vector3& ContactPoint::getLocalPointOnShape2() const { return mLocalPointOnShape2; } @@ -168,12 +168,12 @@ inline const Vector3& ContactPoint::getLocalPointOnShape2() const { /** * @return The penetration impulse */ -inline decimal ContactPoint::getPenetrationImpulse() const { +RP3D_FORCE_INLINE decimal ContactPoint::getPenetrationImpulse() const { return mPenetrationImpulse; } // Return true if the contact point is similar (close enougth) to another given contact point -inline bool ContactPoint::isSimilarWithContactPoint(const ContactPointInfo* localContactPointBody1) const { +RP3D_FORCE_INLINE bool ContactPoint::isSimilarWithContactPoint(const ContactPointInfo* localContactPointBody1) const { return (localContactPointBody1->localPoint1 - mLocalPointOnShape1).lengthSquare() <= (mPersistentContactDistanceThreshold * mPersistentContactDistanceThreshold); } @@ -182,7 +182,7 @@ inline bool ContactPoint::isSimilarWithContactPoint(const ContactPointInfo* loca /** * @param impulse Penetration impulse */ -inline void ContactPoint::setPenetrationImpulse(decimal impulse) { +RP3D_FORCE_INLINE void ContactPoint::setPenetrationImpulse(decimal impulse) { mPenetrationImpulse = impulse; } @@ -190,7 +190,7 @@ inline void ContactPoint::setPenetrationImpulse(decimal impulse) { /** * @return True if it is a resting contact */ -inline bool ContactPoint::getIsRestingContact() const { +RP3D_FORCE_INLINE bool ContactPoint::getIsRestingContact() const { return mIsRestingContact; } @@ -198,7 +198,7 @@ inline bool ContactPoint::getIsRestingContact() const { /** * @param isRestingContact True if it is a resting contact */ -inline void ContactPoint::setIsRestingContact(bool isRestingContact) { +RP3D_FORCE_INLINE void ContactPoint::setIsRestingContact(bool isRestingContact) { mIsRestingContact = isRestingContact; } @@ -206,7 +206,7 @@ inline void ContactPoint::setIsRestingContact(bool isRestingContact) { /** * @return the penetration depth (in meters) */ -inline decimal ContactPoint::getPenetrationDepth() const { +RP3D_FORCE_INLINE decimal ContactPoint::getPenetrationDepth() const { return mPenetrationDepth; } @@ -214,7 +214,7 @@ inline decimal ContactPoint::getPenetrationDepth() const { /** * @return The size of the contact point in memory (in bytes) */ -inline size_t ContactPoint::getSizeInBytes() const { +RP3D_FORCE_INLINE size_t ContactPoint::getSizeInBytes() const { return sizeof(ContactPoint); } diff --git a/include/reactphysics3d/constraint/FixedJoint.h b/include/reactphysics3d/constraint/FixedJoint.h index 84b7508a..fa2ac350 100644 --- a/include/reactphysics3d/constraint/FixedJoint.h +++ b/include/reactphysics3d/constraint/FixedJoint.h @@ -119,7 +119,7 @@ class FixedJoint : public Joint { }; // Return the number of bytes used by the joint -inline size_t FixedJoint::getSizeInBytes() const { +RP3D_FORCE_INLINE size_t FixedJoint::getSizeInBytes() const { return sizeof(FixedJoint); } diff --git a/include/reactphysics3d/constraint/Joint.h b/include/reactphysics3d/constraint/Joint.h index e3856830..dc16d311 100644 --- a/include/reactphysics3d/constraint/Joint.h +++ b/include/reactphysics3d/constraint/Joint.h @@ -157,7 +157,7 @@ class Joint { /** * @return The entity of the joint */ -inline Entity Joint::getEntity() const { +RP3D_FORCE_INLINE Entity Joint::getEntity() const { return mEntity; } diff --git a/include/reactphysics3d/constraint/SliderJoint.h b/include/reactphysics3d/constraint/SliderJoint.h index f9f4830f..517716a9 100644 --- a/include/reactphysics3d/constraint/SliderJoint.h +++ b/include/reactphysics3d/constraint/SliderJoint.h @@ -309,7 +309,7 @@ class SliderJoint : public Joint { }; // Return the number of bytes used by the joint -inline size_t SliderJoint::getSizeInBytes() const { +RP3D_FORCE_INLINE size_t SliderJoint::getSizeInBytes() const { return sizeof(SliderJoint); } diff --git a/include/reactphysics3d/containers/LinkedList.h b/include/reactphysics3d/containers/LinkedList.h index c2221d4f..914bfaa3 100644 --- a/include/reactphysics3d/containers/LinkedList.h +++ b/include/reactphysics3d/containers/LinkedList.h @@ -93,20 +93,20 @@ class LinkedList { // Return the first element of the list template -inline typename LinkedList::ListElement* LinkedList::getListHead() const { +RP3D_FORCE_INLINE typename LinkedList::ListElement* LinkedList::getListHead() const { return mListHead; } // Insert an element at the beginning of the linked list template -inline void LinkedList::insert(const T& data) { +RP3D_FORCE_INLINE void LinkedList::insert(const T& data) { ListElement* element = new (mAllocator.allocate(sizeof(ListElement))) ListElement(data, mListHead); mListHead = element; } // Remove all the elements of the list template -inline void LinkedList::reset() { +RP3D_FORCE_INLINE void LinkedList::reset() { // Release all the list elements ListElement* element = mListHead; diff --git a/include/reactphysics3d/containers/containers_common.h b/include/reactphysics3d/containers/containers_common.h index 84854f90..66b93aac 100644 --- a/include/reactphysics3d/containers/containers_common.h +++ b/include/reactphysics3d/containers/containers_common.h @@ -29,6 +29,7 @@ // Libraries #include #include +#include namespace reactphysics3d { diff --git a/include/reactphysics3d/engine/Entity.h b/include/reactphysics3d/engine/Entity.h index 0b671349..c8eb20ee 100644 --- a/include/reactphysics3d/engine/Entity.h +++ b/include/reactphysics3d/engine/Entity.h @@ -107,23 +107,23 @@ struct Entity { }; // Return the lookup index of the entity in a array -inline uint32 Entity::getIndex() const { +RP3D_FORCE_INLINE uint32 Entity::getIndex() const { return id & ENTITY_INDEX_MASK; } // Return the generation number of the entity -inline uint32 Entity::getGeneration() const { +RP3D_FORCE_INLINE uint32 Entity::getGeneration() const { return (id >> ENTITY_INDEX_BITS) & ENTITY_GENERATION_MASK; } // Equality operator -inline bool Entity::operator==(const Entity& entity) const { +RP3D_FORCE_INLINE bool Entity::operator==(const Entity& entity) const { return entity.id == id; } // Inequality operator -inline bool Entity::operator!=(const Entity& entity) const { +RP3D_FORCE_INLINE bool Entity::operator!=(const Entity& entity) const { return entity.id != id; } diff --git a/include/reactphysics3d/engine/EntityManager.h b/include/reactphysics3d/engine/EntityManager.h index f29ca09d..383e509b 100644 --- a/include/reactphysics3d/engine/EntityManager.h +++ b/include/reactphysics3d/engine/EntityManager.h @@ -71,7 +71,7 @@ class EntityManager { }; // Return true if the entity is still valid (not destroyed) -inline bool EntityManager::isValid(Entity entity) const { +RP3D_FORCE_INLINE bool EntityManager::isValid(Entity entity) const { return mGenerations[entity.getIndex()] == entity.getGeneration(); } diff --git a/include/reactphysics3d/engine/Island.h b/include/reactphysics3d/engine/Island.h index f1b24575..dae4ae51 100644 --- a/include/reactphysics3d/engine/Island.h +++ b/include/reactphysics3d/engine/Island.h @@ -105,35 +105,35 @@ class Island { }; // Add a body into the island -inline void Island::addBody(RigidBody* body) { +RP3D_FORCE_INLINE void Island::addBody(RigidBody* body) { assert(!body->isSleeping()); mBodies[mNbBodies] = body; mNbBodies++; } // Add a contact manifold into the island -inline void Island::addContactManifold(ContactManifold* contactManifold) { +RP3D_FORCE_INLINE void Island::addContactManifold(ContactManifold* contactManifold) { mContactManifolds[mNbContactManifolds] = contactManifold; mNbContactManifolds++; } // Return the number of bodies in the island -inline uint Island::getNbBodies() const { +RP3D_FORCE_INLINE uint Island::getNbBodies() const { return mNbBodies; } // Return the number of contact manifolds in the island -inline uint Island::getNbContactManifolds() const { +RP3D_FORCE_INLINE uint Island::getNbContactManifolds() const { return mNbContactManifolds; } // Return a pointer to the array of bodies -inline RigidBody** Island::getBodies() { +RP3D_FORCE_INLINE RigidBody** Island::getBodies() { return mBodies; } // Return a pointer to the array of contact manifolds -inline ContactManifold** Island::getContactManifolds() { +RP3D_FORCE_INLINE ContactManifold** Island::getContactManifolds() { return mContactManifolds; } diff --git a/include/reactphysics3d/engine/Material.h b/include/reactphysics3d/engine/Material.h index 0882e5b2..c4406b74 100644 --- a/include/reactphysics3d/engine/Material.h +++ b/include/reactphysics3d/engine/Material.h @@ -111,7 +111,7 @@ class Material { /** * @return Bounciness factor (between 0 and 1) where 1 is very bouncy */ -inline decimal Material::getBounciness() const { +RP3D_FORCE_INLINE decimal Material::getBounciness() const { return mBounciness; } @@ -121,7 +121,7 @@ inline decimal Material::getBounciness() const { /** * @param bounciness Bounciness factor (between 0 and 1) where 1 is very bouncy */ -inline void Material::setBounciness(decimal bounciness) { +RP3D_FORCE_INLINE void Material::setBounciness(decimal bounciness) { assert(bounciness >= decimal(0.0) && bounciness <= decimal(1.0)); mBounciness = bounciness; } @@ -130,7 +130,7 @@ inline void Material::setBounciness(decimal bounciness) { /** * @return Friction coefficient (positive value) */ -inline decimal Material::getFrictionCoefficient() const { +RP3D_FORCE_INLINE decimal Material::getFrictionCoefficient() const { return mFrictionCoefficient; } @@ -140,7 +140,7 @@ inline decimal Material::getFrictionCoefficient() const { /** * @param frictionCoefficient Friction coefficient (positive value) */ -inline void Material::setFrictionCoefficient(decimal frictionCoefficient) { +RP3D_FORCE_INLINE void Material::setFrictionCoefficient(decimal frictionCoefficient) { assert(frictionCoefficient >= decimal(0.0)); mFrictionCoefficient = frictionCoefficient; } @@ -151,7 +151,7 @@ inline void Material::setFrictionCoefficient(decimal frictionCoefficient) { /** * @return The rolling resistance factor (positive value) */ -inline decimal Material::getRollingResistance() const { +RP3D_FORCE_INLINE decimal Material::getRollingResistance() const { return mRollingResistance; } @@ -161,13 +161,13 @@ inline decimal Material::getRollingResistance() const { /** * @param rollingResistance The rolling resistance factor */ -inline void Material::setRollingResistance(decimal rollingResistance) { +RP3D_FORCE_INLINE void Material::setRollingResistance(decimal rollingResistance) { assert(rollingResistance >= 0); mRollingResistance = rollingResistance; } // Return the mass density of the collider -inline decimal Material::getMassDensity() const { +RP3D_FORCE_INLINE decimal Material::getMassDensity() const { return mMassDensity; } @@ -175,12 +175,12 @@ inline decimal Material::getMassDensity() const { /** * @param massDensity The mass density of the collider */ -inline void Material::setMassDensity(decimal massDensity) { +RP3D_FORCE_INLINE void Material::setMassDensity(decimal massDensity) { mMassDensity = massDensity; } // Return a string representation for the material -inline std::string Material::to_string() const { +RP3D_FORCE_INLINE std::string Material::to_string() const { std::stringstream ss; @@ -192,7 +192,7 @@ inline std::string Material::to_string() const { } // Overloaded assignment operator -inline Material& Material::operator=(const Material& material) { +RP3D_FORCE_INLINE Material& Material::operator=(const Material& material) { // Check for self-assignment if (this != &material) { diff --git a/include/reactphysics3d/engine/OverlappingPairs.h b/include/reactphysics3d/engine/OverlappingPairs.h index a3920148..9d65b3e0 100644 --- a/include/reactphysics3d/engine/OverlappingPairs.h +++ b/include/reactphysics3d/engine/OverlappingPairs.h @@ -313,21 +313,21 @@ class OverlappingPairs { }; // Return the entity of the first collider -inline Entity OverlappingPairs::getCollider1(uint64 pairId) const { +RP3D_FORCE_INLINE Entity OverlappingPairs::getCollider1(uint64 pairId) const { assert(mMapPairIdToPairIndex.containsKey(pairId)); assert(mMapPairIdToPairIndex[pairId] < mNbPairs); return mColliders1[mMapPairIdToPairIndex[pairId]]; } // Return the entity of the second collider -inline Entity OverlappingPairs::getCollider2(uint64 pairId) const { +RP3D_FORCE_INLINE Entity OverlappingPairs::getCollider2(uint64 pairId) const { assert(mMapPairIdToPairIndex.containsKey(pairId)); assert(mMapPairIdToPairIndex[pairId] < mNbPairs); return mColliders2[mMapPairIdToPairIndex[pairId]]; } // Notify if a given pair is active or not -inline void OverlappingPairs::setIsPairActive(uint64 pairId, bool isActive) { +RP3D_FORCE_INLINE void OverlappingPairs::setIsPairActive(uint64 pairId, bool isActive) { assert(mMapPairIdToPairIndex.containsKey(pairId)); assert(mMapPairIdToPairIndex[pairId] < mNbPairs); @@ -335,13 +335,13 @@ inline void OverlappingPairs::setIsPairActive(uint64 pairId, bool isActive) { } // Return the index of a given overlapping pair in the internal array -inline uint64 OverlappingPairs::getPairIndex(uint64 pairId) const { +RP3D_FORCE_INLINE uint64 OverlappingPairs::getPairIndex(uint64 pairId) const { assert(mMapPairIdToPairIndex.containsKey(pairId)); return mMapPairIdToPairIndex[pairId]; } // Return the last frame collision info for a given shape id or nullptr if none is found -inline LastFrameCollisionInfo* OverlappingPairs::getLastFrameCollisionInfo(uint64 pairId, uint64 shapesId) { +RP3D_FORCE_INLINE LastFrameCollisionInfo* OverlappingPairs::getLastFrameCollisionInfo(uint64 pairId, uint64 shapesId) { assert(mMapPairIdToPairIndex.containsKey(pairId)); const uint64 index = mMapPairIdToPairIndex[pairId]; @@ -356,7 +356,7 @@ inline LastFrameCollisionInfo* OverlappingPairs::getLastFrameCollisionInfo(uint6 } // Return the pair of bodies index -inline bodypair OverlappingPairs::computeBodiesIndexPair(Entity body1Entity, Entity body2Entity) { +RP3D_FORCE_INLINE bodypair OverlappingPairs::computeBodiesIndexPair(Entity body1Entity, Entity body2Entity) { // Construct the pair of body index bodypair indexPair = body1Entity.id < body2Entity.id ? @@ -367,44 +367,44 @@ inline bodypair OverlappingPairs::computeBodiesIndexPair(Entity body1Entity, Ent } // Return the number of pairs -inline uint64 OverlappingPairs::getNbPairs() const { +RP3D_FORCE_INLINE uint64 OverlappingPairs::getNbPairs() const { return mNbPairs; } // Return the number of convex vs convex pairs -inline uint64 OverlappingPairs::getNbConvexVsConvexPairs() const { +RP3D_FORCE_INLINE uint64 OverlappingPairs::getNbConvexVsConvexPairs() const { return mConcavePairsStartIndex; } // Return the number of convex vs concave pairs -inline uint64 OverlappingPairs::getNbConvexVsConcavePairs() const { +RP3D_FORCE_INLINE uint64 OverlappingPairs::getNbConvexVsConcavePairs() const { return mNbPairs - mConcavePairsStartIndex; } // Return the starting index of the convex vs concave pairs -inline uint64 OverlappingPairs::getConvexVsConcavePairsStartIndex() const { +RP3D_FORCE_INLINE uint64 OverlappingPairs::getConvexVsConcavePairsStartIndex() const { return mConcavePairsStartIndex; } // Return a reference to the temporary memory allocator -inline MemoryAllocator& OverlappingPairs::getTemporaryAllocator() { +RP3D_FORCE_INLINE MemoryAllocator& OverlappingPairs::getTemporaryAllocator() { return mTempMemoryAllocator; } // Set if we need to test a given pair for overlap -inline void OverlappingPairs::setNeedToTestOverlap(uint64 pairId, bool needToTestOverlap) { +RP3D_FORCE_INLINE void OverlappingPairs::setNeedToTestOverlap(uint64 pairId, bool needToTestOverlap) { assert(mMapPairIdToPairIndex.containsKey(pairId)); mNeedToTestOverlap[mMapPairIdToPairIndex[pairId]] = needToTestOverlap; } // Return true if the two colliders of the pair were already colliding the previous frame -inline bool OverlappingPairs::getCollidingInPreviousFrame(uint64 pairId) const { +RP3D_FORCE_INLINE bool OverlappingPairs::getCollidingInPreviousFrame(uint64 pairId) const { assert(mMapPairIdToPairIndex.containsKey(pairId)); return mCollidingInPreviousFrame[mMapPairIdToPairIndex[pairId]]; } // Set to true if the two colliders of the pair were already colliding the previous frame -inline void OverlappingPairs::setCollidingInPreviousFrame(uint64 pairId, bool wereCollidingInPreviousFrame) { +RP3D_FORCE_INLINE void OverlappingPairs::setCollidingInPreviousFrame(uint64 pairId, bool wereCollidingInPreviousFrame) { assert(mMapPairIdToPairIndex.containsKey(pairId)); mCollidingInPreviousFrame[mMapPairIdToPairIndex[pairId]] = wereCollidingInPreviousFrame; } @@ -412,7 +412,7 @@ inline void OverlappingPairs::setCollidingInPreviousFrame(uint64 pairId, bool we #ifdef IS_RP3D_PROFILING_ENABLED // Set the profiler -inline void OverlappingPairs::setProfiler(Profiler* profiler) { +RP3D_FORCE_INLINE void OverlappingPairs::setProfiler(Profiler* profiler) { mProfiler = profiler; } diff --git a/include/reactphysics3d/engine/PhysicsCommon.h b/include/reactphysics3d/engine/PhysicsCommon.h index 68574f7f..939c8b39 100644 --- a/include/reactphysics3d/engine/PhysicsCommon.h +++ b/include/reactphysics3d/engine/PhysicsCommon.h @@ -193,7 +193,7 @@ class PhysicsCommon { /** * @return A pointer to the current logger */ -inline Logger* PhysicsCommon::getLogger() { +RP3D_FORCE_INLINE Logger* PhysicsCommon::getLogger() { return mLogger; } @@ -201,7 +201,7 @@ inline Logger* PhysicsCommon::getLogger() { /** * @param logger A pointer to the logger to use */ -inline void PhysicsCommon::setLogger(Logger* logger) { +RP3D_FORCE_INLINE void PhysicsCommon::setLogger(Logger* logger) { mLogger = logger; } diff --git a/include/reactphysics3d/engine/PhysicsWorld.h b/include/reactphysics3d/engine/PhysicsWorld.h index 58fe99e7..ea507ef8 100644 --- a/include/reactphysics3d/engine/PhysicsWorld.h +++ b/include/reactphysics3d/engine/PhysicsWorld.h @@ -489,7 +489,7 @@ class PhysicsWorld { * @param CollisionDispatch Pointer to a collision dispatch object describing * which collision detection algorithm to use for two given collision shapes */ -inline CollisionDispatch& PhysicsWorld::getCollisionDispatch() { +RP3D_FORCE_INLINE CollisionDispatch& PhysicsWorld::getCollisionDispatch() { return mCollisionDetection.getCollisionDispatch(); } @@ -500,7 +500,7 @@ inline CollisionDispatch& PhysicsWorld::getCollisionDispatch() { * @param raycastWithCategoryMaskBits Bits mask corresponding to the category of * bodies to be raycasted */ -inline void PhysicsWorld::raycast(const Ray& ray, +RP3D_FORCE_INLINE void PhysicsWorld::raycast(const Ray& ray, RaycastCallback* raycastCallback, unsigned short raycastWithCategoryMaskBits) const { mCollisionDetection.raycast(raycastCallback, ray, raycastWithCategoryMaskBits); @@ -516,7 +516,7 @@ inline void PhysicsWorld::raycast(const Ray& ray, * @param body2 Pointer to the second body to test * @param callback Pointer to the object with the callback method */ -inline void PhysicsWorld::testCollision(CollisionBody* body1, CollisionBody* body2, CollisionCallback& callback) { +RP3D_FORCE_INLINE void PhysicsWorld::testCollision(CollisionBody* body1, CollisionBody* body2, CollisionCallback& callback) { mCollisionDetection.testCollision(body1, body2, callback); } @@ -529,7 +529,7 @@ inline void PhysicsWorld::testCollision(CollisionBody* body1, CollisionBody* bod * @param body Pointer to the body against which we need to test collision * @param callback Pointer to the object with the callback method to report contacts */ -inline void PhysicsWorld::testCollision(CollisionBody* body, CollisionCallback& callback) { +RP3D_FORCE_INLINE void PhysicsWorld::testCollision(CollisionBody* body, CollisionCallback& callback) { mCollisionDetection.testCollision(body, callback); } @@ -541,7 +541,7 @@ inline void PhysicsWorld::testCollision(CollisionBody* body, CollisionCallback& /** * @param callback Pointer to the object with the callback method to report contacts */ -inline void PhysicsWorld::testCollision(CollisionCallback& callback) { +RP3D_FORCE_INLINE void PhysicsWorld::testCollision(CollisionCallback& callback) { mCollisionDetection.testCollision(callback); } @@ -553,7 +553,7 @@ inline void PhysicsWorld::testCollision(CollisionCallback& callback) { * @param body Pointer to the collision body to test overlap with * @param overlapCallback Pointer to the callback class to report overlap */ -inline void PhysicsWorld::testOverlap(CollisionBody* body, OverlapCallback& overlapCallback) { +RP3D_FORCE_INLINE void PhysicsWorld::testOverlap(CollisionBody* body, OverlapCallback& overlapCallback) { mCollisionDetection.testOverlap(body, overlapCallback); } @@ -564,12 +564,12 @@ inline void PhysicsWorld::testOverlap(CollisionBody* body, OverlapCallback& over /** * @param overlapCallback Pointer to the callback class to report overlap */ -inline void PhysicsWorld::testOverlap(OverlapCallback& overlapCallback) { +RP3D_FORCE_INLINE void PhysicsWorld::testOverlap(OverlapCallback& overlapCallback) { mCollisionDetection.testOverlap(overlapCallback); } // Return a reference to the memory manager of the world -inline MemoryManager& PhysicsWorld::getMemoryManager() { +RP3D_FORCE_INLINE MemoryManager& PhysicsWorld::getMemoryManager() { return mMemoryManager; } @@ -577,7 +577,7 @@ inline MemoryManager& PhysicsWorld::getMemoryManager() { /** * @return Name of the world */ -inline const std::string& PhysicsWorld::getName() const { +RP3D_FORCE_INLINE const std::string& PhysicsWorld::getName() const { return mName; } @@ -587,7 +587,7 @@ inline const std::string& PhysicsWorld::getName() const { /** * @return A pointer to the profiler */ -inline Profiler* PhysicsWorld::getProfiler() { +RP3D_FORCE_INLINE Profiler* PhysicsWorld::getProfiler() { return mProfiler; } @@ -597,7 +597,7 @@ inline Profiler* PhysicsWorld::getProfiler() { /** * @return The number of iterations of the velocity constraint solver */ -inline uint PhysicsWorld::getNbIterationsVelocitySolver() const { +RP3D_FORCE_INLINE uint PhysicsWorld::getNbIterationsVelocitySolver() const { return mNbVelocitySolverIterations; } @@ -605,7 +605,7 @@ inline uint PhysicsWorld::getNbIterationsVelocitySolver() const { /** * @return The number of iterations of the position constraint solver */ -inline uint PhysicsWorld::getNbIterationsPositionSolver() const { +RP3D_FORCE_INLINE uint PhysicsWorld::getNbIterationsPositionSolver() const { return mNbPositionSolverIterations; } @@ -613,7 +613,7 @@ inline uint PhysicsWorld::getNbIterationsPositionSolver() const { /** * @param technique Technique used for the position correction (Baumgarte or Split Impulses) */ -inline void PhysicsWorld::setContactsPositionCorrectionTechnique( +RP3D_FORCE_INLINE void PhysicsWorld::setContactsPositionCorrectionTechnique( ContactsPositionCorrectionTechnique technique) { if (technique == ContactsPositionCorrectionTechnique::BAUMGARTE_CONTACTS) { mContactSolverSystem.setIsSplitImpulseActive(false); @@ -627,7 +627,7 @@ inline void PhysicsWorld::setContactsPositionCorrectionTechnique( /** * @return The current gravity vector (in meter per seconds squared) */ -inline Vector3 PhysicsWorld::getGravity() const { +RP3D_FORCE_INLINE Vector3 PhysicsWorld::getGravity() const { return mConfig.gravity; } @@ -635,7 +635,7 @@ inline Vector3 PhysicsWorld::getGravity() const { /** * @return True if the gravity is enabled in the world */ -inline bool PhysicsWorld::isGravityEnabled() const { +RP3D_FORCE_INLINE bool PhysicsWorld::isGravityEnabled() const { return mIsGravityEnabled; } @@ -643,7 +643,7 @@ inline bool PhysicsWorld::isGravityEnabled() const { /** * @return True if the sleeping technique is enabled and false otherwise */ -inline bool PhysicsWorld::isSleepingEnabled() const { +RP3D_FORCE_INLINE bool PhysicsWorld::isSleepingEnabled() const { return mIsSleepingEnabled; } @@ -651,7 +651,7 @@ inline bool PhysicsWorld::isSleepingEnabled() const { /** * @return The sleep linear velocity (in meters per second) */ -inline decimal PhysicsWorld::getSleepLinearVelocity() const { +RP3D_FORCE_INLINE decimal PhysicsWorld::getSleepLinearVelocity() const { return mSleepLinearVelocity; } @@ -659,7 +659,7 @@ inline decimal PhysicsWorld::getSleepLinearVelocity() const { /** * @return The sleep angular velocity (in radian per second) */ -inline decimal PhysicsWorld::getSleepAngularVelocity() const { +RP3D_FORCE_INLINE decimal PhysicsWorld::getSleepAngularVelocity() const { return mSleepAngularVelocity; } @@ -667,7 +667,7 @@ inline decimal PhysicsWorld::getSleepAngularVelocity() const { /** * @return Time a body is required to stay still before sleeping (in seconds) */ -inline decimal PhysicsWorld::getTimeBeforeSleep() const { +RP3D_FORCE_INLINE decimal PhysicsWorld::getTimeBeforeSleep() const { return mTimeBeforeSleep; } @@ -677,7 +677,7 @@ inline decimal PhysicsWorld::getTimeBeforeSleep() const { * @param eventListener Pointer to the event listener object that will receive * event callbacks during the simulation */ -inline void PhysicsWorld::setEventListener(EventListener* eventListener) { +RP3D_FORCE_INLINE void PhysicsWorld::setEventListener(EventListener* eventListener) { mEventListener = eventListener; } @@ -686,7 +686,7 @@ inline void PhysicsWorld::setEventListener(EventListener* eventListener) { /** * @return The number of collision bodies in the physics world */ -inline uint PhysicsWorld::getNbCollisionBodies() const { +RP3D_FORCE_INLINE uint PhysicsWorld::getNbCollisionBodies() const { return mCollisionBodies.size(); } @@ -694,7 +694,7 @@ inline uint PhysicsWorld::getNbCollisionBodies() const { /** * @return The number of rigid bodies in the physics world */ -inline uint PhysicsWorld::getNbRigidBodies() const { +RP3D_FORCE_INLINE uint PhysicsWorld::getNbRigidBodies() const { return mRigidBodies.size(); } @@ -702,7 +702,7 @@ inline uint PhysicsWorld::getNbRigidBodies() const { /** * @return True if the debug rendering is enabled and false otherwise */ -inline bool PhysicsWorld::getIsDebugRenderingEnabled() const { +RP3D_FORCE_INLINE bool PhysicsWorld::getIsDebugRenderingEnabled() const { return mIsDebugRenderingEnabled; } @@ -710,7 +710,7 @@ inline bool PhysicsWorld::getIsDebugRenderingEnabled() const { /** * @param isEnabled True if you want to enable the debug rendering and false otherwise */ -inline void PhysicsWorld::setIsDebugRenderingEnabled(bool isEnabled) { +RP3D_FORCE_INLINE void PhysicsWorld::setIsDebugRenderingEnabled(bool isEnabled) { mIsDebugRenderingEnabled = isEnabled; } @@ -718,7 +718,7 @@ inline void PhysicsWorld::setIsDebugRenderingEnabled(bool isEnabled) { /** * @return A reference to the DebugRenderer object of the world */ -inline DebugRenderer& PhysicsWorld::getDebugRenderer() { +RP3D_FORCE_INLINE DebugRenderer& PhysicsWorld::getDebugRenderer() { return mDebugRenderer; } diff --git a/include/reactphysics3d/engine/Timer.h b/include/reactphysics3d/engine/Timer.h index 8db66360..70d887a9 100644 --- a/include/reactphysics3d/engine/Timer.h +++ b/include/reactphysics3d/engine/Timer.h @@ -120,28 +120,28 @@ class Timer { }; // Return the timestep of the physics engine -inline double Timer::getTimeStep() const { +RP3D_FORCE_INLINE double Timer::getTimeStep() const { return mTimeStep; } // Set the timestep of the physics engine -inline void Timer::setTimeStep(double timeStep) { +RP3D_FORCE_INLINE void Timer::setTimeStep(double timeStep) { assert(timeStep > 0.0f); mTimeStep = timeStep; } // Return the current time -inline long double Timer::getPhysicsTime() const { +RP3D_FORCE_INLINE long double Timer::getPhysicsTime() const { return mLastUpdateTime; } // Return if the timer is running -inline bool Timer::getIsRunning() const { +RP3D_FORCE_INLINE bool Timer::getIsRunning() const { return mIsRunning; } // Start the timer -inline void Timer::start() { +RP3D_FORCE_INLINE void Timer::start() { if (!mIsRunning) { // Get the current system time @@ -153,17 +153,17 @@ inline void Timer::start() { } // Stop the timer -inline void Timer::stop() { +RP3D_FORCE_INLINE void Timer::stop() { mIsRunning = false; } // True if it's possible to take a new step -inline bool Timer::isPossibleToTakeStep() const { +RP3D_FORCE_INLINE bool Timer::isPossibleToTakeStep() const { return (mAccumulator >= mTimeStep); } // Take a new step => update the timer by adding the timeStep value to the current time -inline void Timer::nextStep() { +RP3D_FORCE_INLINE void Timer::nextStep() { assert(mIsRunning); // Update the accumulator value @@ -171,12 +171,12 @@ inline void Timer::nextStep() { } // Compute the interpolation factor -inline decimal Timer::computeInterpolationFactor() { +RP3D_FORCE_INLINE decimal Timer::computeInterpolationFactor() { return (decimal(mAccumulator / mTimeStep)); } // Compute the time since the last update() call and add it to the accumulator -inline void Timer::update() { +RP3D_FORCE_INLINE void Timer::update() { // Get the current system time long double currentTime = getCurrentSystemTime(); diff --git a/include/reactphysics3d/mathematics/Matrix2x2.h b/include/reactphysics3d/mathematics/Matrix2x2.h index 29e2a54d..5f69e982 100644 --- a/include/reactphysics3d/mathematics/Matrix2x2.h +++ b/include/reactphysics3d/mathematics/Matrix2x2.h @@ -151,57 +151,57 @@ class Matrix2x2 { }; // Constructor of the class Matrix2x2 -inline Matrix2x2::Matrix2x2() { +RP3D_FORCE_INLINE Matrix2x2::Matrix2x2() { // Initialize all values in the matrix to zero setAllValues(0.0, 0.0, 0.0, 0.0); } // Constructor -inline Matrix2x2::Matrix2x2(decimal value) { +RP3D_FORCE_INLINE Matrix2x2::Matrix2x2(decimal value) { setAllValues(value, value, value, value); } // Constructor with arguments -inline Matrix2x2::Matrix2x2(decimal a1, decimal a2, decimal b1, decimal b2) { +RP3D_FORCE_INLINE Matrix2x2::Matrix2x2(decimal a1, decimal a2, decimal b1, decimal b2) { // Initialize the matrix with the values setAllValues(a1, a2, b1, b2); } // Copy-constructor -inline Matrix2x2::Matrix2x2(const Matrix2x2& matrix) { +RP3D_FORCE_INLINE Matrix2x2::Matrix2x2(const Matrix2x2& matrix) { setAllValues(matrix.mRows[0][0], matrix.mRows[0][1], matrix.mRows[1][0], matrix.mRows[1][1]); } // Method to set all the values in the matrix -inline void Matrix2x2::setAllValues(decimal a1, decimal a2, +RP3D_FORCE_INLINE void Matrix2x2::setAllValues(decimal a1, decimal a2, decimal b1, decimal b2) { mRows[0][0] = a1; mRows[0][1] = a2; mRows[1][0] = b1; mRows[1][1] = b2; } // Set the matrix to zero -inline void Matrix2x2::setToZero() { +RP3D_FORCE_INLINE void Matrix2x2::setToZero() { mRows[0].setToZero(); mRows[1].setToZero(); } // Return a column -inline Vector2 Matrix2x2::getColumn(int i) const { +RP3D_FORCE_INLINE Vector2 Matrix2x2::getColumn(int i) const { assert(i>= 0 && i<2); return Vector2(mRows[0][i], mRows[1][i]); } // Return a row -inline Vector2 Matrix2x2::getRow(int i) const { +RP3D_FORCE_INLINE Vector2 Matrix2x2::getRow(int i) const { assert(i>= 0 && i<2); return mRows[i]; } // Return the transpose matrix -inline Matrix2x2 Matrix2x2::getTranspose() const { +RP3D_FORCE_INLINE Matrix2x2 Matrix2x2::getTranspose() const { // Return the transpose matrix return Matrix2x2(mRows[0][0], mRows[1][0], @@ -209,45 +209,45 @@ inline Matrix2x2 Matrix2x2::getTranspose() const { } // Return the determinant of the matrix -inline decimal Matrix2x2::getDeterminant() const { +RP3D_FORCE_INLINE decimal Matrix2x2::getDeterminant() const { // Compute and return the determinant of the matrix return mRows[0][0] * mRows[1][1] - mRows[1][0] * mRows[0][1]; } // Return the trace of the matrix -inline decimal Matrix2x2::getTrace() const { +RP3D_FORCE_INLINE decimal Matrix2x2::getTrace() const { // Compute and return the trace return (mRows[0][0] + mRows[1][1]); } // Set the matrix to the identity matrix -inline void Matrix2x2::setToIdentity() { +RP3D_FORCE_INLINE void Matrix2x2::setToIdentity() { mRows[0][0] = 1.0; mRows[0][1] = 0.0; mRows[1][0] = 0.0; mRows[1][1] = 1.0; } // Return the 2x2 identity matrix -inline Matrix2x2 Matrix2x2::identity() { +RP3D_FORCE_INLINE Matrix2x2 Matrix2x2::identity() { // Return the isdentity matrix return Matrix2x2(1.0, 0.0, 0.0, 1.0); } // Return the 2x2 zero matrix -inline Matrix2x2 Matrix2x2::zero() { +RP3D_FORCE_INLINE Matrix2x2 Matrix2x2::zero() { return Matrix2x2(0.0, 0.0, 0.0, 0.0); } // Return the matrix with absolute values -inline Matrix2x2 Matrix2x2::getAbsoluteMatrix() const { +RP3D_FORCE_INLINE Matrix2x2 Matrix2x2::getAbsoluteMatrix() const { return Matrix2x2(fabs(mRows[0][0]), fabs(mRows[0][1]), fabs(mRows[1][0]), fabs(mRows[1][1])); } // Overloaded operator for addition -inline Matrix2x2 operator+(const Matrix2x2& matrix1, const Matrix2x2& matrix2) { +RP3D_FORCE_INLINE Matrix2x2 operator+(const Matrix2x2& matrix1, const Matrix2x2& matrix2) { return Matrix2x2(matrix1.mRows[0][0] + matrix2.mRows[0][0], matrix1.mRows[0][1] + matrix2.mRows[0][1], matrix1.mRows[1][0] + matrix2.mRows[1][0], @@ -255,7 +255,7 @@ inline Matrix2x2 operator+(const Matrix2x2& matrix1, const Matrix2x2& matrix2) { } // Overloaded operator for substraction -inline Matrix2x2 operator-(const Matrix2x2& matrix1, const Matrix2x2& matrix2) { +RP3D_FORCE_INLINE Matrix2x2 operator-(const Matrix2x2& matrix1, const Matrix2x2& matrix2) { return Matrix2x2(matrix1.mRows[0][0] - matrix2.mRows[0][0], matrix1.mRows[0][1] - matrix2.mRows[0][1], matrix1.mRows[1][0] - matrix2.mRows[1][0], @@ -263,24 +263,24 @@ inline Matrix2x2 operator-(const Matrix2x2& matrix1, const Matrix2x2& matrix2) { } // Overloaded operator for the negative of the matrix -inline Matrix2x2 operator-(const Matrix2x2& matrix) { +RP3D_FORCE_INLINE Matrix2x2 operator-(const Matrix2x2& matrix) { return Matrix2x2(-matrix.mRows[0][0], -matrix.mRows[0][1], -matrix.mRows[1][0], -matrix.mRows[1][1]); } // Overloaded operator for multiplication with a number -inline Matrix2x2 operator*(decimal nb, const Matrix2x2& matrix) { +RP3D_FORCE_INLINE Matrix2x2 operator*(decimal nb, const Matrix2x2& matrix) { return Matrix2x2(matrix.mRows[0][0] * nb, matrix.mRows[0][1] * nb, matrix.mRows[1][0] * nb, matrix.mRows[1][1] * nb); } // Overloaded operator for multiplication with a matrix -inline Matrix2x2 operator*(const Matrix2x2& matrix, decimal nb) { +RP3D_FORCE_INLINE Matrix2x2 operator*(const Matrix2x2& matrix, decimal nb) { return nb * matrix; } // Overloaded operator for matrix multiplication -inline Matrix2x2 operator*(const Matrix2x2& matrix1, const Matrix2x2& matrix2) { +RP3D_FORCE_INLINE Matrix2x2 operator*(const Matrix2x2& matrix1, const Matrix2x2& matrix2) { return Matrix2x2(matrix1.mRows[0][0] * matrix2.mRows[0][0] + matrix1.mRows[0][1] * matrix2.mRows[1][0], matrix1.mRows[0][0] * matrix2.mRows[0][1] + matrix1.mRows[0][1] * @@ -292,38 +292,38 @@ inline Matrix2x2 operator*(const Matrix2x2& matrix1, const Matrix2x2& matrix2) { } // Overloaded operator for multiplication with a vector -inline Vector2 operator*(const Matrix2x2& matrix, const Vector2& vector) { +RP3D_FORCE_INLINE Vector2 operator*(const Matrix2x2& matrix, const Vector2& vector) { return Vector2(matrix.mRows[0][0]*vector.x + matrix.mRows[0][1]*vector.y, matrix.mRows[1][0]*vector.x + matrix.mRows[1][1]*vector.y); } // Overloaded operator for equality condition -inline bool Matrix2x2::operator==(const Matrix2x2& matrix) const { +RP3D_FORCE_INLINE bool Matrix2x2::operator==(const Matrix2x2& matrix) const { return (mRows[0][0] == matrix.mRows[0][0] && mRows[0][1] == matrix.mRows[0][1] && mRows[1][0] == matrix.mRows[1][0] && mRows[1][1] == matrix.mRows[1][1]); } // Overloaded operator for the is different condition -inline bool Matrix2x2::operator!= (const Matrix2x2& matrix) const { +RP3D_FORCE_INLINE bool Matrix2x2::operator!= (const Matrix2x2& matrix) const { return !(*this == matrix); } // Overloaded operator for addition with assignment -inline Matrix2x2& Matrix2x2::operator+=(const Matrix2x2& matrix) { +RP3D_FORCE_INLINE Matrix2x2& Matrix2x2::operator+=(const Matrix2x2& matrix) { mRows[0][0] += matrix.mRows[0][0]; mRows[0][1] += matrix.mRows[0][1]; mRows[1][0] += matrix.mRows[1][0]; mRows[1][1] += matrix.mRows[1][1]; return *this; } // Overloaded operator for substraction with assignment -inline Matrix2x2& Matrix2x2::operator-=(const Matrix2x2& matrix) { +RP3D_FORCE_INLINE Matrix2x2& Matrix2x2::operator-=(const Matrix2x2& matrix) { mRows[0][0] -= matrix.mRows[0][0]; mRows[0][1] -= matrix.mRows[0][1]; mRows[1][0] -= matrix.mRows[1][0]; mRows[1][1] -= matrix.mRows[1][1]; return *this; } // Overloaded operator for multiplication with a number with assignment -inline Matrix2x2& Matrix2x2::operator*=(decimal nb) { +RP3D_FORCE_INLINE Matrix2x2& Matrix2x2::operator*=(decimal nb) { mRows[0][0] *= nb; mRows[0][1] *= nb; mRows[1][0] *= nb; mRows[1][1] *= nb; return *this; @@ -332,19 +332,19 @@ inline Matrix2x2& Matrix2x2::operator*=(decimal nb) { // Overloaded operator to return a row of the matrix. /// This operator is also used to access a matrix value using the syntax /// matrix[row][col]. -inline const Vector2& Matrix2x2::operator[](int row) const { +RP3D_FORCE_INLINE const Vector2& Matrix2x2::operator[](int row) const { return mRows[row]; } // Overloaded operator to return a row of the matrix. /// This operator is also used to access a matrix value using the syntax /// matrix[row][col]. -inline Vector2& Matrix2x2::operator[](int row) { +RP3D_FORCE_INLINE Vector2& Matrix2x2::operator[](int row) { return mRows[row]; } // Get the string representation -inline std::string Matrix2x2::to_string() const { +RP3D_FORCE_INLINE std::string Matrix2x2::to_string() const { return "Matrix2x2(" + std::to_string(mRows[0][0]) + "," + std::to_string(mRows[0][1]) + "," + std::to_string(mRows[1][0]) + "," + std::to_string(mRows[1][1]) + ")"; } diff --git a/include/reactphysics3d/mathematics/Matrix3x3.h b/include/reactphysics3d/mathematics/Matrix3x3.h index 267a3ebb..575adfc4 100644 --- a/include/reactphysics3d/mathematics/Matrix3x3.h +++ b/include/reactphysics3d/mathematics/Matrix3x3.h @@ -158,18 +158,18 @@ class Matrix3x3 { }; // Constructor of the class Matrix3x3 -inline Matrix3x3::Matrix3x3() { +RP3D_FORCE_INLINE Matrix3x3::Matrix3x3() { // Initialize all values in the matrix to zero setAllValues(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); } // Constructor -inline Matrix3x3::Matrix3x3(decimal value) { +RP3D_FORCE_INLINE Matrix3x3::Matrix3x3(decimal value) { setAllValues(value, value, value, value, value, value, value, value, value); } // Constructor with arguments -inline Matrix3x3::Matrix3x3(decimal a1, decimal a2, decimal a3, +RP3D_FORCE_INLINE Matrix3x3::Matrix3x3(decimal a1, decimal a2, decimal a3, decimal b1, decimal b2, decimal b3, decimal c1, decimal c2, decimal c3) { // Initialize the matrix with the values @@ -177,14 +177,14 @@ inline Matrix3x3::Matrix3x3(decimal a1, decimal a2, decimal a3, } // Copy-constructor -inline Matrix3x3::Matrix3x3(const Matrix3x3& matrix) { +RP3D_FORCE_INLINE Matrix3x3::Matrix3x3(const Matrix3x3& matrix) { setAllValues(matrix.mRows[0][0], matrix.mRows[0][1], matrix.mRows[0][2], matrix.mRows[1][0], matrix.mRows[1][1], matrix.mRows[1][2], matrix.mRows[2][0], matrix.mRows[2][1], matrix.mRows[2][2]); } // Method to set all the values in the matrix -inline void Matrix3x3::setAllValues(decimal a1, decimal a2, decimal a3, +RP3D_FORCE_INLINE void Matrix3x3::setAllValues(decimal a1, decimal a2, decimal a3, decimal b1, decimal b2, decimal b3, decimal c1, decimal c2, decimal c3) { mRows[0][0] = a1; mRows[0][1] = a2; mRows[0][2] = a3; @@ -193,26 +193,26 @@ inline void Matrix3x3::setAllValues(decimal a1, decimal a2, decimal a3, } // Set the matrix to zero -inline void Matrix3x3::setToZero() { +RP3D_FORCE_INLINE void Matrix3x3::setToZero() { mRows[0].setToZero(); mRows[1].setToZero(); mRows[2].setToZero(); } // Return a column -inline Vector3 Matrix3x3::getColumn(int i) const { +RP3D_FORCE_INLINE Vector3 Matrix3x3::getColumn(int i) const { assert(i>= 0 && i<3); return Vector3(mRows[0][i], mRows[1][i], mRows[2][i]); } // Return a row -inline Vector3 Matrix3x3::getRow(int i) const { +RP3D_FORCE_INLINE Vector3 Matrix3x3::getRow(int i) const { assert(i>= 0 && i<3); return mRows[i]; } // Return the transpose matrix -inline Matrix3x3 Matrix3x3::getTranspose() const { +RP3D_FORCE_INLINE Matrix3x3 Matrix3x3::getTranspose() const { // Return the transpose matrix return Matrix3x3(mRows[0][0], mRows[1][0], mRows[2][0], @@ -221,7 +221,7 @@ inline Matrix3x3 Matrix3x3::getTranspose() const { } // Return the determinant of the matrix -inline decimal Matrix3x3::getDeterminant() const { +RP3D_FORCE_INLINE decimal Matrix3x3::getDeterminant() const { // Compute and return the determinant of the matrix return (mRows[0][0]*(mRows[1][1]*mRows[2][2]-mRows[2][1]*mRows[1][2]) - @@ -230,44 +230,44 @@ inline decimal Matrix3x3::getDeterminant() const { } // Return the trace of the matrix -inline decimal Matrix3x3::getTrace() const { +RP3D_FORCE_INLINE decimal Matrix3x3::getTrace() const { // Compute and return the trace return (mRows[0][0] + mRows[1][1] + mRows[2][2]); } // Set the matrix to the identity matrix -inline void Matrix3x3::setToIdentity() { +RP3D_FORCE_INLINE void Matrix3x3::setToIdentity() { mRows[0][0] = 1.0; mRows[0][1] = 0.0; mRows[0][2] = 0.0; mRows[1][0] = 0.0; mRows[1][1] = 1.0; mRows[1][2] = 0.0; mRows[2][0] = 0.0; mRows[2][1] = 0.0; mRows[2][2] = 1.0; } // Return the 3x3 identity matrix -inline Matrix3x3 Matrix3x3::identity() { +RP3D_FORCE_INLINE Matrix3x3 Matrix3x3::identity() { return Matrix3x3(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0); } // Return the 3x3 zero matrix -inline Matrix3x3 Matrix3x3::zero() { +RP3D_FORCE_INLINE Matrix3x3 Matrix3x3::zero() { return Matrix3x3(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); } // Return a skew-symmetric matrix using a given vector that can be used // to compute cross product with another vector using matrix multiplication -inline Matrix3x3 Matrix3x3::computeSkewSymmetricMatrixForCrossProduct(const Vector3& vector) { +RP3D_FORCE_INLINE Matrix3x3 Matrix3x3::computeSkewSymmetricMatrixForCrossProduct(const Vector3& vector) { return Matrix3x3(0, -vector.z, vector.y, vector.z, 0, -vector.x, -vector.y, vector.x, 0); } // Return the matrix with absolute values -inline Matrix3x3 Matrix3x3::getAbsoluteMatrix() const { +RP3D_FORCE_INLINE Matrix3x3 Matrix3x3::getAbsoluteMatrix() const { return Matrix3x3(std::fabs(mRows[0][0]), std::fabs(mRows[0][1]), std::fabs(mRows[0][2]), std::fabs(mRows[1][0]), std::fabs(mRows[1][1]), std::fabs(mRows[1][2]), std::fabs(mRows[2][0]), std::fabs(mRows[2][1]), std::fabs(mRows[2][2])); } // Overloaded operator for addition -inline Matrix3x3 operator+(const Matrix3x3& matrix1, const Matrix3x3& matrix2) { +RP3D_FORCE_INLINE Matrix3x3 operator+(const Matrix3x3& matrix1, const Matrix3x3& matrix2) { return Matrix3x3(matrix1.mRows[0][0] + matrix2.mRows[0][0], matrix1.mRows[0][1] + matrix2.mRows[0][1], matrix1.mRows[0][2] + matrix2.mRows[0][2], matrix1.mRows[1][0] + matrix2.mRows[1][0], matrix1.mRows[1][1] + @@ -277,7 +277,7 @@ inline Matrix3x3 operator+(const Matrix3x3& matrix1, const Matrix3x3& matrix2) { } // Overloaded operator for substraction -inline Matrix3x3 operator-(const Matrix3x3& matrix1, const Matrix3x3& matrix2) { +RP3D_FORCE_INLINE Matrix3x3 operator-(const Matrix3x3& matrix1, const Matrix3x3& matrix2) { return Matrix3x3(matrix1.mRows[0][0] - matrix2.mRows[0][0], matrix1.mRows[0][1] - matrix2.mRows[0][1], matrix1.mRows[0][2] - matrix2.mRows[0][2], matrix1.mRows[1][0] - matrix2.mRows[1][0], matrix1.mRows[1][1] - @@ -287,26 +287,26 @@ inline Matrix3x3 operator-(const Matrix3x3& matrix1, const Matrix3x3& matrix2) { } // Overloaded operator for the negative of the matrix -inline Matrix3x3 operator-(const Matrix3x3& matrix) { +RP3D_FORCE_INLINE Matrix3x3 operator-(const Matrix3x3& matrix) { return Matrix3x3(-matrix.mRows[0][0], -matrix.mRows[0][1], -matrix.mRows[0][2], -matrix.mRows[1][0], -matrix.mRows[1][1], -matrix.mRows[1][2], -matrix.mRows[2][0], -matrix.mRows[2][1], -matrix.mRows[2][2]); } // Overloaded operator for multiplication with a number -inline Matrix3x3 operator*(decimal nb, const Matrix3x3& matrix) { +RP3D_FORCE_INLINE Matrix3x3 operator*(decimal nb, const Matrix3x3& matrix) { return Matrix3x3(matrix.mRows[0][0] * nb, matrix.mRows[0][1] * nb, matrix.mRows[0][2] * nb, matrix.mRows[1][0] * nb, matrix.mRows[1][1] * nb, matrix.mRows[1][2] * nb, matrix.mRows[2][0] * nb, matrix.mRows[2][1] * nb, matrix.mRows[2][2] * nb); } // Overloaded operator for multiplication with a matrix -inline Matrix3x3 operator*(const Matrix3x3& matrix, decimal nb) { +RP3D_FORCE_INLINE Matrix3x3 operator*(const Matrix3x3& matrix, decimal nb) { return nb * matrix; } // Overloaded operator for matrix multiplication -inline Matrix3x3 operator*(const Matrix3x3& matrix1, const Matrix3x3& matrix2) { +RP3D_FORCE_INLINE Matrix3x3 operator*(const Matrix3x3& matrix1, const Matrix3x3& matrix2) { return Matrix3x3(matrix1.mRows[0][0]*matrix2.mRows[0][0] + matrix1.mRows[0][1] * matrix2.mRows[1][0] + matrix1.mRows[0][2]*matrix2.mRows[2][0], matrix1.mRows[0][0]*matrix2.mRows[0][1] + matrix1.mRows[0][1] * @@ -328,7 +328,7 @@ inline Matrix3x3 operator*(const Matrix3x3& matrix1, const Matrix3x3& matrix2) { } // Overloaded operator for multiplication with a vector -inline Vector3 operator*(const Matrix3x3& matrix, const Vector3& vector) { +RP3D_FORCE_INLINE Vector3 operator*(const Matrix3x3& matrix, const Vector3& vector) { return Vector3(matrix.mRows[0][0]*vector.x + matrix.mRows[0][1]*vector.y + matrix.mRows[0][2]*vector.z, matrix.mRows[1][0]*vector.x + matrix.mRows[1][1]*vector.y + @@ -338,7 +338,7 @@ inline Vector3 operator*(const Matrix3x3& matrix, const Vector3& vector) { } // Overloaded operator for equality condition -inline bool Matrix3x3::operator==(const Matrix3x3& matrix) const { +RP3D_FORCE_INLINE bool Matrix3x3::operator==(const Matrix3x3& matrix) const { return (mRows[0][0] == matrix.mRows[0][0] && mRows[0][1] == matrix.mRows[0][1] && mRows[0][2] == matrix.mRows[0][2] && mRows[1][0] == matrix.mRows[1][0] && mRows[1][1] == matrix.mRows[1][1] && @@ -348,12 +348,12 @@ inline bool Matrix3x3::operator==(const Matrix3x3& matrix) const { } // Overloaded operator for the is different condition -inline bool Matrix3x3::operator!= (const Matrix3x3& matrix) const { +RP3D_FORCE_INLINE bool Matrix3x3::operator!= (const Matrix3x3& matrix) const { return !(*this == matrix); } // Overloaded operator for addition with assignment -inline Matrix3x3& Matrix3x3::operator+=(const Matrix3x3& matrix) { +RP3D_FORCE_INLINE Matrix3x3& Matrix3x3::operator+=(const Matrix3x3& matrix) { mRows[0][0] += matrix.mRows[0][0]; mRows[0][1] += matrix.mRows[0][1]; mRows[0][2] += matrix.mRows[0][2]; mRows[1][0] += matrix.mRows[1][0]; mRows[1][1] += matrix.mRows[1][1]; mRows[1][2] += matrix.mRows[1][2]; @@ -363,7 +363,7 @@ inline Matrix3x3& Matrix3x3::operator+=(const Matrix3x3& matrix) { } // Overloaded operator for substraction with assignment -inline Matrix3x3& Matrix3x3::operator-=(const Matrix3x3& matrix) { +RP3D_FORCE_INLINE Matrix3x3& Matrix3x3::operator-=(const Matrix3x3& matrix) { mRows[0][0] -= matrix.mRows[0][0]; mRows[0][1] -= matrix.mRows[0][1]; mRows[0][2] -= matrix.mRows[0][2]; mRows[1][0] -= matrix.mRows[1][0]; mRows[1][1] -= matrix.mRows[1][1]; mRows[1][2] -= matrix.mRows[1][2]; @@ -373,7 +373,7 @@ inline Matrix3x3& Matrix3x3::operator-=(const Matrix3x3& matrix) { } // Overloaded operator for multiplication with a number with assignment -inline Matrix3x3& Matrix3x3::operator*=(decimal nb) { +RP3D_FORCE_INLINE Matrix3x3& Matrix3x3::operator*=(decimal nb) { mRows[0][0] *= nb; mRows[0][1] *= nb; mRows[0][2] *= nb; mRows[1][0] *= nb; mRows[1][1] *= nb; mRows[1][2] *= nb; mRows[2][0] *= nb; mRows[2][1] *= nb; mRows[2][2] *= nb; @@ -383,19 +383,19 @@ inline Matrix3x3& Matrix3x3::operator*=(decimal nb) { // Overloaded operator to return a row of the matrix. /// This operator is also used to access a matrix value using the syntax /// matrix[row][col]. -inline const Vector3& Matrix3x3::operator[](int row) const { +RP3D_FORCE_INLINE const Vector3& Matrix3x3::operator[](int row) const { return mRows[row]; } // Overloaded operator to return a row of the matrix. /// This operator is also used to access a matrix value using the syntax /// matrix[row][col]. -inline Vector3& Matrix3x3::operator[](int row) { +RP3D_FORCE_INLINE Vector3& Matrix3x3::operator[](int row) { return mRows[row]; } // Get the string representation -inline std::string Matrix3x3::to_string() const { +RP3D_FORCE_INLINE std::string Matrix3x3::to_string() const { return "Matrix3x3(" + std::to_string(mRows[0][0]) + "," + std::to_string(mRows[0][1]) + "," + std::to_string(mRows[0][2]) + "," + std::to_string(mRows[1][0]) + "," + std::to_string(mRows[1][1]) + "," + std::to_string(mRows[1][2]) + "," + std::to_string(mRows[2][0]) + "," + std::to_string(mRows[2][1]) + "," + std::to_string(mRows[2][2]) + ")"; diff --git a/include/reactphysics3d/mathematics/Quaternion.h b/include/reactphysics3d/mathematics/Quaternion.h index 8db5682d..15313474 100644 --- a/include/reactphysics3d/mathematics/Quaternion.h +++ b/include/reactphysics3d/mathematics/Quaternion.h @@ -182,28 +182,28 @@ struct Quaternion { }; // Constructor of the class -inline Quaternion::Quaternion() : x(0.0), y(0.0), z(0.0), w(0.0) { +RP3D_FORCE_INLINE Quaternion::Quaternion() : x(0.0), y(0.0), z(0.0), w(0.0) { } // Constructor with arguments -inline Quaternion::Quaternion(decimal newX, decimal newY, decimal newZ, decimal newW) +RP3D_FORCE_INLINE Quaternion::Quaternion(decimal newX, decimal newY, decimal newZ, decimal newW) :x(newX), y(newY), z(newZ), w(newW) { } // Constructor with the component w and the vector v=(x y z) -inline Quaternion::Quaternion(decimal newW, const Vector3& v) : x(v.x), y(v.y), z(v.z), w(newW) { +RP3D_FORCE_INLINE Quaternion::Quaternion(decimal newW, const Vector3& v) : x(v.x), y(v.y), z(v.z), w(newW) { } // Constructor with the component w and the vector v=(x y z) -inline Quaternion::Quaternion(const Vector3& v, decimal newW) : x(v.x), y(v.y), z(v.z), w(newW) { +RP3D_FORCE_INLINE Quaternion::Quaternion(const Vector3& v, decimal newW) : x(v.x), y(v.y), z(v.z), w(newW) { } // Set all the values -inline void Quaternion::setAllValues(decimal newX, decimal newY, decimal newZ, decimal newW) { +RP3D_FORCE_INLINE void Quaternion::setAllValues(decimal newX, decimal newY, decimal newZ, decimal newW) { x = newX; y = newY; z = newZ; @@ -211,7 +211,7 @@ inline void Quaternion::setAllValues(decimal newX, decimal newY, decimal newZ, d } // Set the quaternion to zero -inline void Quaternion::setToZero() { +RP3D_FORCE_INLINE void Quaternion::setToZero() { x = 0; y = 0; z = 0; @@ -219,7 +219,7 @@ inline void Quaternion::setToZero() { } // Set to the identity quaternion -inline void Quaternion::setToIdentity() { +RP3D_FORCE_INLINE void Quaternion::setToIdentity() { x = 0; y = 0; z = 0; @@ -227,24 +227,24 @@ inline void Quaternion::setToIdentity() { } // Return the vector v=(x y z) of the quaternion -inline Vector3 Quaternion::getVectorV() const { +RP3D_FORCE_INLINE Vector3 Quaternion::getVectorV() const { // Return the vector v return Vector3(x, y, z); } -// Return the length of the quaternion (inline) -inline decimal Quaternion::length() const { +// Return the length of the quaternion (RP3D_FORCE_INLINE) +RP3D_FORCE_INLINE decimal Quaternion::length() const { return std::sqrt(x*x + y*y + z*z + w*w); } // Return the square of the length of the quaternion -inline decimal Quaternion::lengthSquare() const { +RP3D_FORCE_INLINE decimal Quaternion::lengthSquare() const { return x*x + y*y + z*z + w*w; } // Normalize the quaternion -inline void Quaternion::normalize() { +RP3D_FORCE_INLINE void Quaternion::normalize() { decimal l = length(); @@ -258,7 +258,7 @@ inline void Quaternion::normalize() { } // Inverse the quaternion -inline void Quaternion::inverse() { +RP3D_FORCE_INLINE void Quaternion::inverse() { // Use the conjugate of the current quaternion x = -x; @@ -267,7 +267,7 @@ inline void Quaternion::inverse() { } // Return the unit quaternion -inline Quaternion Quaternion::getUnit() const { +RP3D_FORCE_INLINE Quaternion Quaternion::getUnit() const { decimal lengthQuaternion = length(); // Check if the length is not equal to zero @@ -279,60 +279,60 @@ inline Quaternion Quaternion::getUnit() const { } // Return the identity quaternion -inline Quaternion Quaternion::identity() { +RP3D_FORCE_INLINE Quaternion Quaternion::identity() { return Quaternion(0.0, 0.0, 0.0, 1.0); } -// Return the conjugate of the quaternion (inline) -inline Quaternion Quaternion::getConjugate() const { +// Return the conjugate of the quaternion (RP3D_FORCE_INLINE) +RP3D_FORCE_INLINE Quaternion Quaternion::getConjugate() const { return Quaternion(-x, -y, -z, w); } -// Return the inverse of the quaternion (inline) -inline Quaternion Quaternion::getInverse() const { +// Return the inverse of the quaternion (RP3D_FORCE_INLINE) +RP3D_FORCE_INLINE Quaternion Quaternion::getInverse() const { // Return the conjugate quaternion return Quaternion(-x, -y, -z, w); } // Scalar product between two quaternions -inline decimal Quaternion::dot(const Quaternion& quaternion) const { +RP3D_FORCE_INLINE decimal Quaternion::dot(const Quaternion& quaternion) const { return (x*quaternion.x + y*quaternion.y + z*quaternion.z + w*quaternion.w); } // Return true if the values are not NAN OR INF -inline bool Quaternion::isFinite() const { +RP3D_FORCE_INLINE bool Quaternion::isFinite() const { return std::isfinite(x) && std::isfinite(y) && std::isfinite(z) && std::isfinite(w); } // Return true if it is a unit quaternion -inline bool Quaternion::isUnit() const { +RP3D_FORCE_INLINE bool Quaternion::isUnit() const { const decimal length = std::sqrt(x*x + y*y + z*z + w*w); const decimal tolerance = 1e-5f; return std::abs(length - decimal(1.0)) < tolerance; } // Return true if it is a valid quaternion -inline bool Quaternion::isValid() const { +RP3D_FORCE_INLINE bool Quaternion::isValid() const { return isFinite() && isUnit(); } // Overloaded operator for the addition of two quaternions -inline Quaternion Quaternion::operator+(const Quaternion& quaternion) const { +RP3D_FORCE_INLINE Quaternion Quaternion::operator+(const Quaternion& quaternion) const { // Return the result quaternion return Quaternion(x + quaternion.x, y + quaternion.y, z + quaternion.z, w + quaternion.w); } // Overloaded operator for the substraction of two quaternions -inline Quaternion Quaternion::operator-(const Quaternion& quaternion) const { +RP3D_FORCE_INLINE Quaternion Quaternion::operator-(const Quaternion& quaternion) const { // Return the result of the substraction return Quaternion(x - quaternion.x, y - quaternion.y, z - quaternion.z, w - quaternion.w); } // Overloaded operator for addition with assignment -inline Quaternion& Quaternion::operator+=(const Quaternion& quaternion) { +RP3D_FORCE_INLINE Quaternion& Quaternion::operator+=(const Quaternion& quaternion) { x += quaternion.x; y += quaternion.y; z += quaternion.z; @@ -341,7 +341,7 @@ inline Quaternion& Quaternion::operator+=(const Quaternion& quaternion) { } // Overloaded operator for substraction with assignment -inline Quaternion& Quaternion::operator-=(const Quaternion& quaternion) { +RP3D_FORCE_INLINE Quaternion& Quaternion::operator-=(const Quaternion& quaternion) { x -= quaternion.x; y -= quaternion.y; z -= quaternion.z; @@ -350,12 +350,12 @@ inline Quaternion& Quaternion::operator-=(const Quaternion& quaternion) { } // Overloaded operator for the multiplication with a constant -inline Quaternion Quaternion::operator*(decimal nb) const { +RP3D_FORCE_INLINE Quaternion Quaternion::operator*(decimal nb) const { return Quaternion(nb * x, nb * y, nb * z, nb * w); } // Overloaded operator for the multiplication of two quaternions -inline Quaternion Quaternion::operator*(const Quaternion& quaternion) const { +RP3D_FORCE_INLINE Quaternion Quaternion::operator*(const Quaternion& quaternion) const { /* The followin code is equivalent to this return Quaternion(w * quaternion.w - getVectorV().dot(quaternion.getVectorV()), @@ -371,7 +371,7 @@ inline Quaternion Quaternion::operator*(const Quaternion& quaternion) const { // Overloaded operator for the multiplication with a vector. /// This methods rotates a point given the rotation of a quaternion. -inline Vector3 Quaternion::operator*(const Vector3& point) const { +RP3D_FORCE_INLINE Vector3 Quaternion::operator*(const Vector3& point) const { /* The following code is equivalent to this * Quaternion p(point.x, point.y, point.z, 0.0); @@ -388,7 +388,7 @@ inline Vector3 Quaternion::operator*(const Vector3& point) const { } // Overloaded operator for the assignment -inline Quaternion& Quaternion::operator=(const Quaternion& quaternion) { +RP3D_FORCE_INLINE Quaternion& Quaternion::operator=(const Quaternion& quaternion) { // Check for self-assignment if (this != &quaternion) { @@ -403,13 +403,13 @@ inline Quaternion& Quaternion::operator=(const Quaternion& quaternion) { } // Overloaded operator for equality condition -inline bool Quaternion::operator==(const Quaternion& quaternion) const { +RP3D_FORCE_INLINE bool Quaternion::operator==(const Quaternion& quaternion) const { return (x == quaternion.x && y == quaternion.y && z == quaternion.z && w == quaternion.w); } // Get the string representation -inline std::string Quaternion::to_string() const { +RP3D_FORCE_INLINE std::string Quaternion::to_string() const { return "Quaternion(" + std::to_string(x) + "," + std::to_string(y) + "," + std::to_string(z) + "," + std::to_string(w) + ")"; } diff --git a/include/reactphysics3d/mathematics/Transform.h b/include/reactphysics3d/mathematics/Transform.h index aca59848..575eca7f 100644 --- a/include/reactphysics3d/mathematics/Transform.h +++ b/include/reactphysics3d/mathematics/Transform.h @@ -125,62 +125,62 @@ class Transform { }; // Constructor -inline Transform::Transform() : mPosition(Vector3(0.0, 0.0, 0.0)), mOrientation(Quaternion::identity()) { +RP3D_FORCE_INLINE Transform::Transform() : mPosition(Vector3(0.0, 0.0, 0.0)), mOrientation(Quaternion::identity()) { } // Constructor -inline Transform::Transform(const Vector3& position, const Matrix3x3& orientation) +RP3D_FORCE_INLINE Transform::Transform(const Vector3& position, const Matrix3x3& orientation) : mPosition(position), mOrientation(Quaternion(orientation)) { } // Constructor -inline Transform::Transform(const Vector3& position, const Quaternion& orientation) +RP3D_FORCE_INLINE Transform::Transform(const Vector3& position, const Quaternion& orientation) : mPosition(position), mOrientation(orientation) { } // Copy-constructor -inline Transform::Transform(const Transform& transform) +RP3D_FORCE_INLINE Transform::Transform(const Transform& transform) : mPosition(transform.mPosition), mOrientation(transform.mOrientation) { } // Return the position of the transform -inline const Vector3& Transform::getPosition() const { +RP3D_FORCE_INLINE const Vector3& Transform::getPosition() const { return mPosition; } // Set the origin of the transform -inline void Transform::setPosition(const Vector3& position) { +RP3D_FORCE_INLINE void Transform::setPosition(const Vector3& position) { mPosition = position; } // Return the rotation matrix -inline const Quaternion& Transform::getOrientation() const { +RP3D_FORCE_INLINE const Quaternion& Transform::getOrientation() const { return mOrientation; } // Set the rotation matrix of the transform -inline void Transform::setOrientation(const Quaternion& orientation) { +RP3D_FORCE_INLINE void Transform::setOrientation(const Quaternion& orientation) { mOrientation = orientation; } // Set the transform to the identity transform -inline void Transform::setToIdentity() { +RP3D_FORCE_INLINE void Transform::setToIdentity() { mPosition = Vector3(0.0, 0.0, 0.0); mOrientation = Quaternion::identity(); } // Return the inverse of the transform -inline Transform Transform::getInverse() const { +RP3D_FORCE_INLINE Transform Transform::getInverse() const { const Quaternion& invQuaternion = mOrientation.getInverse(); return Transform(invQuaternion * (-mPosition), invQuaternion); } // Return an interpolated transform -inline Transform Transform::interpolateTransforms(const Transform& oldTransform, +RP3D_FORCE_INLINE Transform Transform::interpolateTransforms(const Transform& oldTransform, const Transform& newTransform, decimal interpolationFactor) { @@ -195,22 +195,22 @@ inline Transform Transform::interpolateTransforms(const Transform& oldTransform, } // Return the identity transform -inline Transform Transform::identity() { +RP3D_FORCE_INLINE Transform Transform::identity() { return Transform(Vector3(0, 0, 0), Quaternion::identity()); } // Return true if it is a valid transform -inline bool Transform::isValid() const { +RP3D_FORCE_INLINE bool Transform::isValid() const { return mPosition.isFinite() && mOrientation.isValid(); } // Return the transformed vector -inline Vector3 Transform::operator*(const Vector3& vector) const { +RP3D_FORCE_INLINE Vector3 Transform::operator*(const Vector3& vector) const { return (mOrientation * vector) + mPosition; } // Operator of multiplication of a transform with another one -inline Transform Transform::operator*(const Transform& transform2) const { +RP3D_FORCE_INLINE Transform Transform::operator*(const Transform& transform2) const { // The following code is equivalent to this //return Transform(mPosition + mOrientation * transform2.mPosition, @@ -239,17 +239,17 @@ inline Transform Transform::operator*(const Transform& transform2) const { } // Return true if the two transforms are equal -inline bool Transform::operator==(const Transform& transform2) const { +RP3D_FORCE_INLINE bool Transform::operator==(const Transform& transform2) const { return (mPosition == transform2.mPosition) && (mOrientation == transform2.mOrientation); } // Return true if the two transforms are different -inline bool Transform::operator!=(const Transform& transform2) const { +RP3D_FORCE_INLINE bool Transform::operator!=(const Transform& transform2) const { return !(*this == transform2); } // Assignment operator -inline Transform& Transform::operator=(const Transform& transform) { +RP3D_FORCE_INLINE Transform& Transform::operator=(const Transform& transform) { if (&transform != this) { mPosition = transform.mPosition; mOrientation = transform.mOrientation; @@ -258,7 +258,7 @@ inline Transform& Transform::operator=(const Transform& transform) { } // Get the string representation -inline std::string Transform::to_string() const { +RP3D_FORCE_INLINE std::string Transform::to_string() const { return "Transform(" + mPosition.to_string() + "," + mOrientation.to_string() + ")"; } diff --git a/include/reactphysics3d/mathematics/Vector2.h b/include/reactphysics3d/mathematics/Vector2.h index 7a088398..aee3214d 100644 --- a/include/reactphysics3d/mathematics/Vector2.h +++ b/include/reactphysics3d/mathematics/Vector2.h @@ -161,50 +161,50 @@ struct Vector2 { }; // Constructor -inline Vector2::Vector2() : x(0.0), y(0.0) { +RP3D_FORCE_INLINE Vector2::Vector2() : x(0.0), y(0.0) { } // Constructor with arguments -inline Vector2::Vector2(decimal newX, decimal newY) : x(newX), y(newY) { +RP3D_FORCE_INLINE Vector2::Vector2(decimal newX, decimal newY) : x(newX), y(newY) { } // Copy-constructor -inline Vector2::Vector2(const Vector2& vector) : x(vector.x), y(vector.y) { +RP3D_FORCE_INLINE Vector2::Vector2(const Vector2& vector) : x(vector.x), y(vector.y) { } // Set the vector to zero -inline void Vector2::setToZero() { +RP3D_FORCE_INLINE void Vector2::setToZero() { x = 0; y = 0; } // Set all the values of the vector -inline void Vector2::setAllValues(decimal newX, decimal newY) { +RP3D_FORCE_INLINE void Vector2::setAllValues(decimal newX, decimal newY) { x = newX; y = newY; } // Return the length of the vector -inline decimal Vector2::length() const { +RP3D_FORCE_INLINE decimal Vector2::length() const { return std::sqrt(x*x + y*y); } // Return the square of the length of the vector -inline decimal Vector2::lengthSquare() const { +RP3D_FORCE_INLINE decimal Vector2::lengthSquare() const { return x*x + y*y; } -// Scalar product of two vectors (inline) -inline decimal Vector2::dot(const Vector2& vector) const { +// Scalar product of two vectors (RP3D_FORCE_INLINE) +RP3D_FORCE_INLINE decimal Vector2::dot(const Vector2& vector) const { return (x*vector.x + y*vector.y); } // Normalize the vector -inline void Vector2::normalize() { +RP3D_FORCE_INLINE void Vector2::normalize() { decimal l = length(); if (l < MACHINE_EPSILON) { return; @@ -214,68 +214,68 @@ inline void Vector2::normalize() { } // Return the corresponding absolute value vector -inline Vector2 Vector2::getAbsoluteVector() const { +RP3D_FORCE_INLINE Vector2 Vector2::getAbsoluteVector() const { return Vector2(std::abs(x), std::abs(y)); } // Return the axis with the minimal value -inline int Vector2::getMinAxis() const { +RP3D_FORCE_INLINE int Vector2::getMinAxis() const { return (x < y ? 0 : 1); } // Return the axis with the maximal value -inline int Vector2::getMaxAxis() const { +RP3D_FORCE_INLINE int Vector2::getMaxAxis() const { return (x < y ? 1 : 0); } // Return true if the vector is unit and false otherwise -inline bool Vector2::isUnit() const { +RP3D_FORCE_INLINE bool Vector2::isUnit() const { return approxEqual(lengthSquare(), 1.0); } // Return true if the values are not NAN OR INF -inline bool Vector2::isFinite() const { +RP3D_FORCE_INLINE bool Vector2::isFinite() const { return std::isfinite(x) && std::isfinite(y); } // Return true if the vector is the zero vector -inline bool Vector2::isZero() const { +RP3D_FORCE_INLINE bool Vector2::isZero() const { return approxEqual(lengthSquare(), 0.0); } // Overloaded operator for the equality condition -inline bool Vector2::operator== (const Vector2& vector) const { +RP3D_FORCE_INLINE bool Vector2::operator== (const Vector2& vector) const { return (x == vector.x && y == vector.y); } // Overloaded operator for the is different condition -inline bool Vector2::operator!= (const Vector2& vector) const { +RP3D_FORCE_INLINE bool Vector2::operator!= (const Vector2& vector) const { return !(*this == vector); } // Overloaded operator for addition with assignment -inline Vector2& Vector2::operator+=(const Vector2& vector) { +RP3D_FORCE_INLINE Vector2& Vector2::operator+=(const Vector2& vector) { x += vector.x; y += vector.y; return *this; } // Overloaded operator for substraction with assignment -inline Vector2& Vector2::operator-=(const Vector2& vector) { +RP3D_FORCE_INLINE Vector2& Vector2::operator-=(const Vector2& vector) { x -= vector.x; y -= vector.y; return *this; } // Overloaded operator for multiplication with a number with assignment -inline Vector2& Vector2::operator*=(decimal number) { +RP3D_FORCE_INLINE Vector2& Vector2::operator*=(decimal number) { x *= number; y *= number; return *this; } // Overloaded operator for division by a number with assignment -inline Vector2& Vector2::operator/=(decimal number) { +RP3D_FORCE_INLINE Vector2& Vector2::operator/=(decimal number) { assert(number > std::numeric_limits::epsilon()); x /= number; y /= number; @@ -283,60 +283,60 @@ inline Vector2& Vector2::operator/=(decimal number) { } // Overloaded operator for value access -inline decimal& Vector2::operator[] (int index) { +RP3D_FORCE_INLINE decimal& Vector2::operator[] (int index) { return (&x)[index]; } // Overloaded operator for value access -inline const decimal& Vector2::operator[] (int index) const { +RP3D_FORCE_INLINE const decimal& Vector2::operator[] (int index) const { return (&x)[index]; } // Overloaded operator for addition -inline Vector2 operator+(const Vector2& vector1, const Vector2& vector2) { +RP3D_FORCE_INLINE Vector2 operator+(const Vector2& vector1, const Vector2& vector2) { return Vector2(vector1.x + vector2.x, vector1.y + vector2.y); } // Overloaded operator for substraction -inline Vector2 operator-(const Vector2& vector1, const Vector2& vector2) { +RP3D_FORCE_INLINE Vector2 operator-(const Vector2& vector1, const Vector2& vector2) { return Vector2(vector1.x - vector2.x, vector1.y - vector2.y); } // Overloaded operator for the negative of a vector -inline Vector2 operator-(const Vector2& vector) { +RP3D_FORCE_INLINE Vector2 operator-(const Vector2& vector) { return Vector2(-vector.x, -vector.y); } // Overloaded operator for multiplication with a number -inline Vector2 operator*(const Vector2& vector, decimal number) { +RP3D_FORCE_INLINE Vector2 operator*(const Vector2& vector, decimal number) { return Vector2(number * vector.x, number * vector.y); } // Overloaded operator for multiplication of two vectors -inline Vector2 operator*(const Vector2& vector1, const Vector2& vector2) { +RP3D_FORCE_INLINE Vector2 operator*(const Vector2& vector1, const Vector2& vector2) { return Vector2(vector1.x * vector2.x, vector1.y * vector2.y); } // Overloaded operator for division by a number -inline Vector2 operator/(const Vector2& vector, decimal number) { +RP3D_FORCE_INLINE Vector2 operator/(const Vector2& vector, decimal number) { assert(number > MACHINE_EPSILON); return Vector2(vector.x / number, vector.y / number); } // Overload operator for division between two vectors -inline Vector2 operator/(const Vector2& vector1, const Vector2& vector2) { +RP3D_FORCE_INLINE Vector2 operator/(const Vector2& vector1, const Vector2& vector2) { assert(vector2.x > MACHINE_EPSILON); assert(vector2.y > MACHINE_EPSILON); return Vector2(vector1.x / vector2.x, vector1.y / vector2.y); } // Overloaded operator for multiplication with a number -inline Vector2 operator*(decimal number, const Vector2& vector) { +RP3D_FORCE_INLINE Vector2 operator*(decimal number, const Vector2& vector) { return vector * number; } // Assignment operator -inline Vector2& Vector2::operator=(const Vector2& vector) { +RP3D_FORCE_INLINE Vector2& Vector2::operator=(const Vector2& vector) { if (&vector != this) { x = vector.x; y = vector.y; @@ -345,29 +345,29 @@ inline Vector2& Vector2::operator=(const Vector2& vector) { } // Overloaded less than operator for ordering to be used inside std::set for instance -inline bool Vector2::operator<(const Vector2& vector) const { +RP3D_FORCE_INLINE bool Vector2::operator<(const Vector2& vector) const { return (x == vector.x ? y < vector.y : x < vector.x); } // Return a vector taking the minimum components of two vectors -inline Vector2 Vector2::min(const Vector2& vector1, const Vector2& vector2) { +RP3D_FORCE_INLINE Vector2 Vector2::min(const Vector2& vector1, const Vector2& vector2) { return Vector2(std::min(vector1.x, vector2.x), std::min(vector1.y, vector2.y)); } // Return a vector taking the maximum components of two vectors -inline Vector2 Vector2::max(const Vector2& vector1, const Vector2& vector2) { +RP3D_FORCE_INLINE Vector2 Vector2::max(const Vector2& vector1, const Vector2& vector2) { return Vector2(std::max(vector1.x, vector2.x), std::max(vector1.y, vector2.y)); } // Get the string representation -inline std::string Vector2::to_string() const { +RP3D_FORCE_INLINE std::string Vector2::to_string() const { return "Vector2(" + std::to_string(x) + "," + std::to_string(y) + ")"; } // Return the zero vector -inline Vector2 Vector2::zero() { +RP3D_FORCE_INLINE Vector2 Vector2::zero() { return Vector2(0, 0); } diff --git a/include/reactphysics3d/mathematics/Vector3.h b/include/reactphysics3d/mathematics/Vector3.h index ffec1b56..b1adf487 100644 --- a/include/reactphysics3d/mathematics/Vector3.h +++ b/include/reactphysics3d/mathematics/Vector3.h @@ -30,6 +30,7 @@ #include #include #include +#include /// ReactPhysics3D namespace namespace reactphysics3d { @@ -173,58 +174,58 @@ struct Vector3 { }; // Constructor of the struct Vector3 -inline Vector3::Vector3() : x(0.0), y(0.0), z(0.0) { +RP3D_FORCE_INLINE Vector3::Vector3() : x(0.0), y(0.0), z(0.0) { } // Constructor with arguments -inline Vector3::Vector3(decimal newX, decimal newY, decimal newZ) : x(newX), y(newY), z(newZ) { +RP3D_FORCE_INLINE Vector3::Vector3(decimal newX, decimal newY, decimal newZ) : x(newX), y(newY), z(newZ) { } // Copy-constructor -inline Vector3::Vector3(const Vector3& vector) : x(vector.x), y(vector.y), z(vector.z) { +RP3D_FORCE_INLINE Vector3::Vector3(const Vector3& vector) : x(vector.x), y(vector.y), z(vector.z) { } // Set the vector to zero -inline void Vector3::setToZero() { +RP3D_FORCE_INLINE void Vector3::setToZero() { x = 0; y = 0; z = 0; } // Set all the values of the vector -inline void Vector3::setAllValues(decimal newX, decimal newY, decimal newZ) { +RP3D_FORCE_INLINE void Vector3::setAllValues(decimal newX, decimal newY, decimal newZ) { x = newX; y = newY; z = newZ; } // Return the length of the vector -inline decimal Vector3::length() const { +RP3D_FORCE_INLINE decimal Vector3::length() const { return std::sqrt(x*x + y*y + z*z); } // Return the square of the length of the vector -inline decimal Vector3::lengthSquare() const { +RP3D_FORCE_INLINE decimal Vector3::lengthSquare() const { return x*x + y*y + z*z; } -// Scalar product of two vectors (inline) -inline decimal Vector3::dot(const Vector3& vector) const { +// Scalar product of two vectors (RP3D_FORCE_INLINE) +RP3D_FORCE_INLINE decimal Vector3::dot(const Vector3& vector) const { return (x*vector.x + y*vector.y + z*vector.z); } -// Cross product of two vectors (inline) -inline Vector3 Vector3::cross(const Vector3& vector) const { +// Cross product of two vectors (RP3D_FORCE_INLINE) +RP3D_FORCE_INLINE Vector3 Vector3::cross(const Vector3& vector) const { return Vector3(y * vector.z - z * vector.y, z * vector.x - x * vector.z, x * vector.y - y * vector.x); } // Normalize the vector -inline void Vector3::normalize() { +RP3D_FORCE_INLINE void Vector3::normalize() { decimal l = length(); if (l < MACHINE_EPSILON) { return; @@ -235,47 +236,47 @@ inline void Vector3::normalize() { } // Return the corresponding absolute value vector -inline Vector3 Vector3::getAbsoluteVector() const { +RP3D_FORCE_INLINE Vector3 Vector3::getAbsoluteVector() const { return Vector3(std::abs(x), std::abs(y), std::abs(z)); } // Return the axis with the minimal value -inline int Vector3::getMinAxis() const { +RP3D_FORCE_INLINE int Vector3::getMinAxis() const { return (x < y ? (x < z ? 0 : 2) : (y < z ? 1 : 2)); } // Return the axis with the maximal value -inline int Vector3::getMaxAxis() const { +RP3D_FORCE_INLINE int Vector3::getMaxAxis() const { return (x < y ? (y < z ? 2 : 1) : (x < z ? 2 : 0)); } // Return true if the vector is unit and false otherwise -inline bool Vector3::isUnit() const { +RP3D_FORCE_INLINE bool Vector3::isUnit() const { return approxEqual(lengthSquare(), 1.0); } // Return true if the values are not NAN OR INF -inline bool Vector3::isFinite() const { +RP3D_FORCE_INLINE bool Vector3::isFinite() const { return std::isfinite(x) && std::isfinite(y) && std::isfinite(z); } // Return true if the vector is the zero vector -inline bool Vector3::isZero() const { +RP3D_FORCE_INLINE bool Vector3::isZero() const { return approxEqual(lengthSquare(), 0.0); } // Overloaded operator for the equality condition -inline bool Vector3::operator== (const Vector3& vector) const { +RP3D_FORCE_INLINE bool Vector3::operator== (const Vector3& vector) const { return (x == vector.x && y == vector.y && z == vector.z); } // Overloaded operator for the is different condition -inline bool Vector3::operator!= (const Vector3& vector) const { +RP3D_FORCE_INLINE bool Vector3::operator!= (const Vector3& vector) const { return !(*this == vector); } // Overloaded operator for addition with assignment -inline Vector3& Vector3::operator+=(const Vector3& vector) { +RP3D_FORCE_INLINE Vector3& Vector3::operator+=(const Vector3& vector) { x += vector.x; y += vector.y; z += vector.z; @@ -283,7 +284,7 @@ inline Vector3& Vector3::operator+=(const Vector3& vector) { } // Overloaded operator for substraction with assignment -inline Vector3& Vector3::operator-=(const Vector3& vector) { +RP3D_FORCE_INLINE Vector3& Vector3::operator-=(const Vector3& vector) { x -= vector.x; y -= vector.y; z -= vector.z; @@ -291,7 +292,7 @@ inline Vector3& Vector3::operator-=(const Vector3& vector) { } // Overloaded operator for multiplication with a number with assignment -inline Vector3& Vector3::operator*=(decimal number) { +RP3D_FORCE_INLINE Vector3& Vector3::operator*=(decimal number) { x *= number; y *= number; z *= number; @@ -299,7 +300,7 @@ inline Vector3& Vector3::operator*=(decimal number) { } // Overloaded operator for division by a number with assignment -inline Vector3& Vector3::operator/=(decimal number) { +RP3D_FORCE_INLINE Vector3& Vector3::operator/=(decimal number) { assert(number > std::numeric_limits::epsilon()); x /= number; y /= number; @@ -308,43 +309,43 @@ inline Vector3& Vector3::operator/=(decimal number) { } // Overloaded operator for value access -inline decimal& Vector3::operator[] (int index) { +RP3D_FORCE_INLINE decimal& Vector3::operator[] (int index) { return (&x)[index]; } // Overloaded operator for value access -inline const decimal& Vector3::operator[] (int index) const { +RP3D_FORCE_INLINE const decimal& Vector3::operator[] (int index) const { return (&x)[index]; } // Overloaded operator for addition -inline Vector3 operator+(const Vector3& vector1, const Vector3& vector2) { +RP3D_FORCE_INLINE Vector3 operator+(const Vector3& vector1, const Vector3& vector2) { return Vector3(vector1.x + vector2.x, vector1.y + vector2.y, vector1.z + vector2.z); } // Overloaded operator for substraction -inline Vector3 operator-(const Vector3& vector1, const Vector3& vector2) { +RP3D_FORCE_INLINE Vector3 operator-(const Vector3& vector1, const Vector3& vector2) { return Vector3(vector1.x - vector2.x, vector1.y - vector2.y, vector1.z - vector2.z); } // Overloaded operator for the negative of a vector -inline Vector3 operator-(const Vector3& vector) { +RP3D_FORCE_INLINE Vector3 operator-(const Vector3& vector) { return Vector3(-vector.x, -vector.y, -vector.z); } // Overloaded operator for multiplication with a number -inline Vector3 operator*(const Vector3& vector, decimal number) { +RP3D_FORCE_INLINE Vector3 operator*(const Vector3& vector, decimal number) { return Vector3(number * vector.x, number * vector.y, number * vector.z); } // Overloaded operator for division by a number -inline Vector3 operator/(const Vector3& vector, decimal number) { +RP3D_FORCE_INLINE Vector3 operator/(const Vector3& vector, decimal number) { assert(number > MACHINE_EPSILON); return Vector3(vector.x / number, vector.y / number, vector.z / number); } // Overload operator for division between two vectors -inline Vector3 operator/(const Vector3& vector1, const Vector3& vector2) { +RP3D_FORCE_INLINE Vector3 operator/(const Vector3& vector1, const Vector3& vector2) { assert(vector2.x > MACHINE_EPSILON); assert(vector2.y > MACHINE_EPSILON); assert(vector2.z > MACHINE_EPSILON); @@ -352,17 +353,17 @@ inline Vector3 operator/(const Vector3& vector1, const Vector3& vector2) { } // Overloaded operator for multiplication with a number -inline Vector3 operator*(decimal number, const Vector3& vector) { +RP3D_FORCE_INLINE Vector3 operator*(decimal number, const Vector3& vector) { return vector * number; } // Overload operator for multiplication between two vectors -inline Vector3 operator*(const Vector3& vector1, const Vector3& vector2) { +RP3D_FORCE_INLINE Vector3 operator*(const Vector3& vector1, const Vector3& vector2) { return Vector3(vector1.x * vector2.x, vector1.y * vector2.y, vector1.z * vector2.z); } // Assignment operator -inline Vector3& Vector3::operator=(const Vector3& vector) { +RP3D_FORCE_INLINE Vector3& Vector3::operator=(const Vector3& vector) { if (&vector != this) { x = vector.x; y = vector.y; @@ -372,41 +373,41 @@ inline Vector3& Vector3::operator=(const Vector3& vector) { } // Overloaded less than operator for ordering to be used inside std::set for instance -inline bool Vector3::operator<(const Vector3& vector) const { +RP3D_FORCE_INLINE bool Vector3::operator<(const Vector3& vector) const { return (x == vector.x ? (y == vector.y ? z < vector.z : y < vector.y) : x < vector.x); } // Return a vector taking the minimum components of two vectors -inline Vector3 Vector3::min(const Vector3& vector1, const Vector3& vector2) { +RP3D_FORCE_INLINE Vector3 Vector3::min(const Vector3& vector1, const Vector3& vector2) { return Vector3(std::min(vector1.x, vector2.x), std::min(vector1.y, vector2.y), std::min(vector1.z, vector2.z)); } // Return a vector taking the maximum components of two vectors -inline Vector3 Vector3::max(const Vector3& vector1, const Vector3& vector2) { +RP3D_FORCE_INLINE Vector3 Vector3::max(const Vector3& vector1, const Vector3& vector2) { return Vector3(std::max(vector1.x, vector2.x), std::max(vector1.y, vector2.y), std::max(vector1.z, vector2.z)); } // Return the minimum value among the three components of a vector -inline decimal Vector3::getMinValue() const { +RP3D_FORCE_INLINE decimal Vector3::getMinValue() const { return std::min(std::min(x, y), z); } // Return the maximum value among the three components of a vector -inline decimal Vector3::getMaxValue() const { +RP3D_FORCE_INLINE decimal Vector3::getMaxValue() const { return std::max(std::max(x, y), z); } // Get the string representation -inline std::string Vector3::to_string() const { +RP3D_FORCE_INLINE std::string Vector3::to_string() const { return "Vector3(" + std::to_string(x) + "," + std::to_string(y) + "," + std::to_string(z) + ")"; } // Return the zero vector -inline Vector3 Vector3::zero() { +RP3D_FORCE_INLINE Vector3 Vector3::zero() { return Vector3(0, 0, 0); } diff --git a/include/reactphysics3d/mathematics/mathematics_functions.h b/include/reactphysics3d/mathematics/mathematics_functions.h index 9ef9ff3a..74b7a86a 100755 --- a/include/reactphysics3d/mathematics/mathematics_functions.h +++ b/include/reactphysics3d/mathematics/mathematics_functions.h @@ -44,7 +44,7 @@ struct Vector2; /// Function to test if two real numbers are (almost) equal /// We test if two numbers a and b are such that (a-b) are in [-EPSILON; EPSILON] -inline bool approxEqual(decimal a, decimal b, decimal epsilon = MACHINE_EPSILON) { +RP3D_FORCE_INLINE bool approxEqual(decimal a, decimal b, decimal epsilon = MACHINE_EPSILON) { return (std::fabs(a - b) < epsilon); } @@ -56,30 +56,30 @@ bool approxEqual(const Vector2& vec1, const Vector2& vec2, decimal epsilon = MAC /// Function that returns the result of the "value" clamped by /// two others values "lowerLimit" and "upperLimit" -inline int clamp(int value, int lowerLimit, int upperLimit) { +RP3D_FORCE_INLINE int clamp(int value, int lowerLimit, int upperLimit) { assert(lowerLimit <= upperLimit); return std::min(std::max(value, lowerLimit), upperLimit); } /// Function that returns the result of the "value" clamped by /// two others values "lowerLimit" and "upperLimit" -inline decimal clamp(decimal value, decimal lowerLimit, decimal upperLimit) { +RP3D_FORCE_INLINE decimal clamp(decimal value, decimal lowerLimit, decimal upperLimit) { assert(lowerLimit <= upperLimit); return std::min(std::max(value, lowerLimit), upperLimit); } /// Return the minimum value among three values -inline decimal min3(decimal a, decimal b, decimal c) { +RP3D_FORCE_INLINE decimal min3(decimal a, decimal b, decimal c) { return std::min(std::min(a, b), c); } /// Return the maximum value among three values -inline decimal max3(decimal a, decimal b, decimal c) { +RP3D_FORCE_INLINE decimal max3(decimal a, decimal b, decimal c) { return std::max(std::max(a, b), c); } /// Return true if two values have the same sign -inline bool sameSign(decimal a, decimal b) { +RP3D_FORCE_INLINE bool sameSign(decimal a, decimal b) { return a * b >= decimal(0.0); } diff --git a/include/reactphysics3d/memory/MemoryManager.h b/include/reactphysics3d/memory/MemoryManager.h index 55388e00..a7a5466c 100644 --- a/include/reactphysics3d/memory/MemoryManager.h +++ b/include/reactphysics3d/memory/MemoryManager.h @@ -102,7 +102,7 @@ class MemoryManager { }; // Allocate memory of a given type -inline void* MemoryManager::allocate(AllocationType allocationType, size_t size) { +RP3D_FORCE_INLINE void* MemoryManager::allocate(AllocationType allocationType, size_t size) { switch (allocationType) { case AllocationType::Base: return mBaseAllocator->allocate(size); @@ -115,7 +115,7 @@ inline void* MemoryManager::allocate(AllocationType allocationType, size_t size) } // Release previously allocated memory. -inline void MemoryManager::release(AllocationType allocationType, void* pointer, size_t size) { +RP3D_FORCE_INLINE void MemoryManager::release(AllocationType allocationType, void* pointer, size_t size) { switch (allocationType) { case AllocationType::Base: mBaseAllocator->release(pointer, size); break; @@ -126,22 +126,22 @@ inline void MemoryManager::release(AllocationType allocationType, void* pointer, } // Return the pool allocator -inline PoolAllocator& MemoryManager::getPoolAllocator() { +RP3D_FORCE_INLINE PoolAllocator& MemoryManager::getPoolAllocator() { return mPoolAllocator; } // Return the single frame stack allocator -inline SingleFrameAllocator& MemoryManager::getSingleFrameAllocator() { +RP3D_FORCE_INLINE SingleFrameAllocator& MemoryManager::getSingleFrameAllocator() { return mSingleFrameAllocator; } // Return the heap allocator -inline HeapAllocator& MemoryManager::getHeapAllocator() { +RP3D_FORCE_INLINE HeapAllocator& MemoryManager::getHeapAllocator() { return mHeapAllocator; } // Reset the single frame allocator -inline void MemoryManager::resetFrameAllocator() { +RP3D_FORCE_INLINE void MemoryManager::resetFrameAllocator() { mSingleFrameAllocator.reset(); } diff --git a/include/reactphysics3d/systems/BroadPhaseSystem.h b/include/reactphysics3d/systems/BroadPhaseSystem.h index e399619f..9b4baf8a 100644 --- a/include/reactphysics3d/systems/BroadPhaseSystem.h +++ b/include/reactphysics3d/systems/BroadPhaseSystem.h @@ -208,27 +208,27 @@ class BroadPhaseSystem { }; // Return the fat AABB of a given broad-phase shape -inline const AABB& BroadPhaseSystem::getFatAABB(int broadPhaseId) const { +RP3D_FORCE_INLINE const AABB& BroadPhaseSystem::getFatAABB(int broadPhaseId) const { return mDynamicAABBTree.getFatAABB(broadPhaseId); } // Remove a collider from the array of colliders that have moved in the last simulation step // and that need to be tested again for broad-phase overlapping. -inline void BroadPhaseSystem::removeMovedCollider(int broadPhaseID) { +RP3D_FORCE_INLINE void BroadPhaseSystem::removeMovedCollider(int broadPhaseID) { // Remove the broad-phase ID from the set mMovedShapes.remove(broadPhaseID); } // Return the collider corresponding to the broad-phase node id in parameter -inline Collider* BroadPhaseSystem::getColliderForBroadPhaseId(int broadPhaseId) const { +RP3D_FORCE_INLINE Collider* BroadPhaseSystem::getColliderForBroadPhaseId(int broadPhaseId) const { return static_cast(mDynamicAABBTree.getNodeDataPointer(broadPhaseId)); } #ifdef IS_RP3D_PROFILING_ENABLED // Set the profiler -inline void BroadPhaseSystem::setProfiler(Profiler* profiler) { +RP3D_FORCE_INLINE void BroadPhaseSystem::setProfiler(Profiler* profiler) { mProfiler = profiler; mDynamicAABBTree.setProfiler(profiler); } diff --git a/include/reactphysics3d/systems/CollisionDetectionSystem.h b/include/reactphysics3d/systems/CollisionDetectionSystem.h index 6ae515f6..683df3cc 100644 --- a/include/reactphysics3d/systems/CollisionDetectionSystem.h +++ b/include/reactphysics3d/systems/CollisionDetectionSystem.h @@ -373,12 +373,12 @@ class CollisionDetectionSystem { }; // Return a reference to the collision dispatch configuration -inline CollisionDispatch& CollisionDetectionSystem::getCollisionDispatch() { +RP3D_FORCE_INLINE CollisionDispatch& CollisionDetectionSystem::getCollisionDispatch() { return mCollisionDispatch; } // Add a body to the collision detection -inline void CollisionDetectionSystem::addCollider(Collider* collider, const AABB& aabb) { +RP3D_FORCE_INLINE void CollisionDetectionSystem::addCollider(Collider* collider, const AABB& aabb) { // Add the body to the broad-phase mBroadPhaseSystem.addCollider(collider, aabb); @@ -392,19 +392,19 @@ inline void CollisionDetectionSystem::addCollider(Collider* collider, const AABB } // Add a pair of bodies that cannot collide with each other -inline void CollisionDetectionSystem::addNoCollisionPair(Entity body1Entity, Entity body2Entity) { +RP3D_FORCE_INLINE void CollisionDetectionSystem::addNoCollisionPair(Entity body1Entity, Entity body2Entity) { mNoCollisionPairs.add(OverlappingPairs::computeBodiesIndexPair(body1Entity, body2Entity)); } // Remove a pair of bodies that cannot collide with each other -inline void CollisionDetectionSystem::removeNoCollisionPair(Entity body1Entity, Entity body2Entity) { +RP3D_FORCE_INLINE void CollisionDetectionSystem::removeNoCollisionPair(Entity body1Entity, Entity body2Entity) { mNoCollisionPairs.remove(OverlappingPairs::computeBodiesIndexPair(body1Entity, body2Entity)); } // Ask for a collision shape to be tested again during broad-phase. /// We simply put the shape in the list of collision shape that have moved in the /// previous frame so that it is tested for collision again in the broad-phase. -inline void CollisionDetectionSystem::askForBroadPhaseCollisionCheck(Collider* collider) { +RP3D_FORCE_INLINE void CollisionDetectionSystem::askForBroadPhaseCollisionCheck(Collider* collider) { if (collider->getBroadPhaseId() != -1) { mBroadPhaseSystem.addMovedCollider(collider->getBroadPhaseId(), collider); @@ -412,31 +412,31 @@ inline void CollisionDetectionSystem::askForBroadPhaseCollisionCheck(Collider* c } // Return a pointer to the world -inline PhysicsWorld* CollisionDetectionSystem::getWorld() { +RP3D_FORCE_INLINE PhysicsWorld* CollisionDetectionSystem::getWorld() { return mWorld; } // Return a reference to the memory manager -inline MemoryManager& CollisionDetectionSystem::getMemoryManager() const { +RP3D_FORCE_INLINE MemoryManager& CollisionDetectionSystem::getMemoryManager() const { return mMemoryManager; } // Update a collider (that has moved for instance) -inline void CollisionDetectionSystem::updateCollider(Entity colliderEntity, decimal timeStep) { +RP3D_FORCE_INLINE void CollisionDetectionSystem::updateCollider(Entity colliderEntity, decimal timeStep) { // Update the collider component mBroadPhaseSystem.updateCollider(colliderEntity, timeStep); } // Update all the enabled colliders -inline void CollisionDetectionSystem::updateColliders(decimal timeStep) { +RP3D_FORCE_INLINE void CollisionDetectionSystem::updateColliders(decimal timeStep) { mBroadPhaseSystem.updateColliders(timeStep); } #ifdef IS_RP3D_PROFILING_ENABLED // Set the profiler -inline void CollisionDetectionSystem::setProfiler(Profiler* profiler) { +RP3D_FORCE_INLINE void CollisionDetectionSystem::setProfiler(Profiler* profiler) { mProfiler = profiler; mBroadPhaseSystem.setProfiler(profiler); mCollisionDispatch.setProfiler(profiler); diff --git a/include/reactphysics3d/systems/ConstraintSolverSystem.h b/include/reactphysics3d/systems/ConstraintSolverSystem.h index f3b68d80..594fc983 100644 --- a/include/reactphysics3d/systems/ConstraintSolverSystem.h +++ b/include/reactphysics3d/systems/ConstraintSolverSystem.h @@ -215,7 +215,7 @@ class ConstraintSolverSystem { #ifdef IS_RP3D_PROFILING_ENABLED // Set the profiler -inline void ConstraintSolverSystem::setProfiler(Profiler* profiler) { +RP3D_FORCE_INLINE void ConstraintSolverSystem::setProfiler(Profiler* profiler) { mProfiler = profiler; mSolveBallAndSocketJointSystem.setProfiler(profiler); mSolveFixedJointSystem.setProfiler(profiler); diff --git a/include/reactphysics3d/systems/ContactSolverSystem.h b/include/reactphysics3d/systems/ContactSolverSystem.h index 11c4aa31..ba30a0ac 100644 --- a/include/reactphysics3d/systems/ContactSolverSystem.h +++ b/include/reactphysics3d/systems/ContactSolverSystem.h @@ -398,19 +398,19 @@ class ContactSolverSystem { }; // Return true if the split impulses position correction technique is used for contacts -inline bool ContactSolverSystem::isSplitImpulseActive() const { +RP3D_FORCE_INLINE bool ContactSolverSystem::isSplitImpulseActive() const { return mIsSplitImpulseActive; } // Activate or Deactivate the split impulses for contacts -inline void ContactSolverSystem::setIsSplitImpulseActive(bool isActive) { +RP3D_FORCE_INLINE void ContactSolverSystem::setIsSplitImpulseActive(bool isActive) { mIsSplitImpulseActive = isActive; } #ifdef IS_RP3D_PROFILING_ENABLED // Set the profiler -inline void ContactSolverSystem::setProfiler(Profiler* profiler) { +RP3D_FORCE_INLINE void ContactSolverSystem::setProfiler(Profiler* profiler) { mProfiler = profiler; } diff --git a/include/reactphysics3d/systems/DynamicsSystem.h b/include/reactphysics3d/systems/DynamicsSystem.h index 76723ae1..ea87d0eb 100644 --- a/include/reactphysics3d/systems/DynamicsSystem.h +++ b/include/reactphysics3d/systems/DynamicsSystem.h @@ -114,7 +114,7 @@ class DynamicsSystem { #ifdef IS_RP3D_PROFILING_ENABLED // Set the profiler -inline void DynamicsSystem::setProfiler(Profiler* profiler) { +RP3D_FORCE_INLINE void DynamicsSystem::setProfiler(Profiler* profiler) { mProfiler = profiler; } diff --git a/include/reactphysics3d/systems/SolveBallAndSocketJointSystem.h b/include/reactphysics3d/systems/SolveBallAndSocketJointSystem.h index bd073781..43b2eec7 100644 --- a/include/reactphysics3d/systems/SolveBallAndSocketJointSystem.h +++ b/include/reactphysics3d/systems/SolveBallAndSocketJointSystem.h @@ -123,20 +123,20 @@ class SolveBallAndSocketJointSystem { #ifdef IS_RP3D_PROFILING_ENABLED // Set the profiler -inline void SolveBallAndSocketJointSystem::setProfiler(Profiler* profiler) { +RP3D_FORCE_INLINE void SolveBallAndSocketJointSystem::setProfiler(Profiler* profiler) { mProfiler = profiler; } #endif // Set the time step -inline void SolveBallAndSocketJointSystem::setTimeStep(decimal timeStep) { +RP3D_FORCE_INLINE void SolveBallAndSocketJointSystem::setTimeStep(decimal timeStep) { assert(timeStep > decimal(0.0)); mTimeStep = timeStep; } // Set to true to enable warm starting -inline void SolveBallAndSocketJointSystem::setIsWarmStartingActive(bool isWarmStartingActive) { +RP3D_FORCE_INLINE void SolveBallAndSocketJointSystem::setIsWarmStartingActive(bool isWarmStartingActive) { mIsWarmStartingActive = isWarmStartingActive; } diff --git a/include/reactphysics3d/systems/SolveFixedJointSystem.h b/include/reactphysics3d/systems/SolveFixedJointSystem.h index 84d77685..79e6c6c2 100644 --- a/include/reactphysics3d/systems/SolveFixedJointSystem.h +++ b/include/reactphysics3d/systems/SolveFixedJointSystem.h @@ -120,20 +120,20 @@ class SolveFixedJointSystem { #ifdef IS_RP3D_PROFILING_ENABLED // Set the profiler -inline void SolveFixedJointSystem::setProfiler(Profiler* profiler) { +RP3D_FORCE_INLINE void SolveFixedJointSystem::setProfiler(Profiler* profiler) { mProfiler = profiler; } #endif // Set the time step -inline void SolveFixedJointSystem::setTimeStep(decimal timeStep) { +RP3D_FORCE_INLINE void SolveFixedJointSystem::setTimeStep(decimal timeStep) { assert(timeStep > decimal(0.0)); mTimeStep = timeStep; } // Set to true to enable warm starting -inline void SolveFixedJointSystem::setIsWarmStartingActive(bool isWarmStartingActive) { +RP3D_FORCE_INLINE void SolveFixedJointSystem::setIsWarmStartingActive(bool isWarmStartingActive) { mIsWarmStartingActive = isWarmStartingActive; } diff --git a/include/reactphysics3d/systems/SolveHingeJointSystem.h b/include/reactphysics3d/systems/SolveHingeJointSystem.h index 05e5f059..811c5544 100644 --- a/include/reactphysics3d/systems/SolveHingeJointSystem.h +++ b/include/reactphysics3d/systems/SolveHingeJointSystem.h @@ -138,20 +138,20 @@ class SolveHingeJointSystem { #ifdef IS_RP3D_PROFILING_ENABLED // Set the profiler -inline void SolveHingeJointSystem::setProfiler(Profiler* profiler) { +RP3D_FORCE_INLINE void SolveHingeJointSystem::setProfiler(Profiler* profiler) { mProfiler = profiler; } #endif // Set the time step -inline void SolveHingeJointSystem::setTimeStep(decimal timeStep) { +RP3D_FORCE_INLINE void SolveHingeJointSystem::setTimeStep(decimal timeStep) { assert(timeStep > decimal(0.0)); mTimeStep = timeStep; } // Set to true to enable warm starting -inline void SolveHingeJointSystem::setIsWarmStartingActive(bool isWarmStartingActive) { +RP3D_FORCE_INLINE void SolveHingeJointSystem::setIsWarmStartingActive(bool isWarmStartingActive) { mIsWarmStartingActive = isWarmStartingActive; } diff --git a/include/reactphysics3d/systems/SolveSliderJointSystem.h b/include/reactphysics3d/systems/SolveSliderJointSystem.h index 9e7d595c..9d60927c 100644 --- a/include/reactphysics3d/systems/SolveSliderJointSystem.h +++ b/include/reactphysics3d/systems/SolveSliderJointSystem.h @@ -124,20 +124,20 @@ class SolveSliderJointSystem { #ifdef IS_RP3D_PROFILING_ENABLED // Set the profiler -inline void SolveSliderJointSystem::setProfiler(Profiler* profiler) { +RP3D_FORCE_INLINE void SolveSliderJointSystem::setProfiler(Profiler* profiler) { mProfiler = profiler; } #endif // Set the time step -inline void SolveSliderJointSystem::setTimeStep(decimal timeStep) { +RP3D_FORCE_INLINE void SolveSliderJointSystem::setTimeStep(decimal timeStep) { assert(timeStep > decimal(0.0)); mTimeStep = timeStep; } // Set to true to enable warm starting -inline void SolveSliderJointSystem::setIsWarmStartingActive(bool isWarmStartingActive) { +RP3D_FORCE_INLINE void SolveSliderJointSystem::setIsWarmStartingActive(bool isWarmStartingActive) { mIsWarmStartingActive = isWarmStartingActive; } diff --git a/include/reactphysics3d/utils/DebugRenderer.h b/include/reactphysics3d/utils/DebugRenderer.h index cae56c8c..e8e31473 100644 --- a/include/reactphysics3d/utils/DebugRenderer.h +++ b/include/reactphysics3d/utils/DebugRenderer.h @@ -263,7 +263,7 @@ class DebugRenderer : public EventListener { /** * @return The number of lines in the array of lines to draw */ -inline uint32 DebugRenderer::getNbLines() const { +RP3D_FORCE_INLINE uint32 DebugRenderer::getNbLines() const { return mLines.size(); } @@ -271,7 +271,7 @@ inline uint32 DebugRenderer::getNbLines() const { /** * @return The list of lines to draw */ -inline const List& DebugRenderer::getLines() const { +RP3D_FORCE_INLINE const List& DebugRenderer::getLines() const { return mLines; } @@ -279,7 +279,7 @@ inline const List& DebugRenderer::getLines() const { /** * @return A pointer to the first element of the lines array to draw */ -inline const DebugRenderer::DebugLine* DebugRenderer::getLinesArray() const { +RP3D_FORCE_INLINE const DebugRenderer::DebugLine* DebugRenderer::getLinesArray() const { return &(mLines[0]); } @@ -287,7 +287,7 @@ inline const DebugRenderer::DebugLine* DebugRenderer::getLinesArray() const { /** * @return The number of triangles in the array of triangles to draw */ -inline uint32 DebugRenderer::getNbTriangles() const { +RP3D_FORCE_INLINE uint32 DebugRenderer::getNbTriangles() const { return mTriangles.size(); } @@ -295,7 +295,7 @@ inline uint32 DebugRenderer::getNbTriangles() const { /** * @return The list of triangles to draw */ -inline const List& DebugRenderer::getTriangles() const { +RP3D_FORCE_INLINE const List& DebugRenderer::getTriangles() const { return mTriangles; } @@ -303,7 +303,7 @@ inline const List& DebugRenderer::getTriangles() c /** * @return A pointer to the first element of the triangles array to draw */ -inline const DebugRenderer::DebugTriangle* DebugRenderer::getTrianglesArray() const { +RP3D_FORCE_INLINE const DebugRenderer::DebugTriangle* DebugRenderer::getTrianglesArray() const { return &(mTriangles[0]); } @@ -312,7 +312,7 @@ inline const DebugRenderer::DebugTriangle* DebugRenderer::getTrianglesArray() co * @param item A debug item * @return True if the given debug item is being displayed and false otherwise */ -inline bool DebugRenderer::getIsDebugItemDisplayed(DebugItem item) const { +RP3D_FORCE_INLINE bool DebugRenderer::getIsDebugItemDisplayed(DebugItem item) const { return mDisplayedDebugItems & static_cast(item); } @@ -321,7 +321,7 @@ inline bool DebugRenderer::getIsDebugItemDisplayed(DebugItem item) const { * @param item A debug item to draw * @param isDisplayed True if the given debug item has to be displayed and false otherwise */ -inline void DebugRenderer::setIsDebugItemDisplayed(DebugItem item, bool isDisplayed) { +RP3D_FORCE_INLINE void DebugRenderer::setIsDebugItemDisplayed(DebugItem item, bool isDisplayed) { const uint32 itemFlag = static_cast(item); uint32 resetBit = ~(itemFlag); mDisplayedDebugItems &= resetBit; @@ -334,7 +334,7 @@ inline void DebugRenderer::setIsDebugItemDisplayed(DebugItem item, bool isDispla /** * @return The radius of the sphere used to display a contact point */ -inline decimal DebugRenderer::getContactPointSphereRadius() const { +RP3D_FORCE_INLINE decimal DebugRenderer::getContactPointSphereRadius() const { return mContactPointSphereRadius; } @@ -342,7 +342,7 @@ inline decimal DebugRenderer::getContactPointSphereRadius() const { /** * @param radius The radius of the sphere used to display a contact point */ -inline void DebugRenderer::setContactPointSphereRadius(decimal radius) { +RP3D_FORCE_INLINE void DebugRenderer::setContactPointSphereRadius(decimal radius) { assert(radius > decimal(0.0)); mContactPointSphereRadius = radius; } @@ -352,7 +352,7 @@ inline void DebugRenderer::setContactPointSphereRadius(decimal radius) { /** * @return The length of the contact normal to display */ -inline decimal DebugRenderer::getContactNormalLength() const { +RP3D_FORCE_INLINE decimal DebugRenderer::getContactNormalLength() const { return mContactNormalLength; } @@ -360,7 +360,7 @@ inline decimal DebugRenderer::getContactNormalLength() const { /** * @param contactNormalLength The length of the contact normal to display */ -inline void DebugRenderer::setContactNormalLength(decimal contactNormalLength) { +RP3D_FORCE_INLINE void DebugRenderer::setContactNormalLength(decimal contactNormalLength) { mContactNormalLength = contactNormalLength; } diff --git a/include/reactphysics3d/utils/Profiler.h b/include/reactphysics3d/utils/Profiler.h index a3ef2a2b..43f4ae0f 100644 --- a/include/reactphysics3d/utils/Profiler.h +++ b/include/reactphysics3d/utils/Profiler.h @@ -401,113 +401,113 @@ class ProfileSample { #define RP3D_PROFILE(name, profiler) ProfileSample profileSample(name, profiler) // Return true if we are at the root of the profiler tree -inline bool ProfileNodeIterator::isRoot() { +RP3D_FORCE_INLINE bool ProfileNodeIterator::isRoot() { return (mCurrentParentNode->getParentNode() == nullptr); } // Return true if we are at the end of a branch of the profiler tree -inline bool ProfileNodeIterator::isEnd() { +RP3D_FORCE_INLINE bool ProfileNodeIterator::isEnd() { return (mCurrentChildNode == nullptr); } // Return the name of the current node -inline const char* ProfileNodeIterator::getCurrentName() { +RP3D_FORCE_INLINE const char* ProfileNodeIterator::getCurrentName() { return mCurrentChildNode->getName(); } // Return the total time of the current node -inline long double ProfileNodeIterator::getCurrentTotalTime() { +RP3D_FORCE_INLINE long double ProfileNodeIterator::getCurrentTotalTime() { return mCurrentChildNode->getTotalTime(); } // Return the total number of calls of the current node -inline uint ProfileNodeIterator::getCurrentNbTotalCalls() { +RP3D_FORCE_INLINE uint ProfileNodeIterator::getCurrentNbTotalCalls() { return mCurrentChildNode->getNbTotalCalls(); } // Return the name of the current parent node -inline const char* ProfileNodeIterator::getCurrentParentName() { +RP3D_FORCE_INLINE const char* ProfileNodeIterator::getCurrentParentName() { return mCurrentParentNode->getName(); } // Return the total time of the current parent node -inline long double ProfileNodeIterator::getCurrentParentTotalTime() { +RP3D_FORCE_INLINE long double ProfileNodeIterator::getCurrentParentTotalTime() { return mCurrentParentNode->getTotalTime(); } // Return the total number of calls of the current parent node -inline uint ProfileNodeIterator::getCurrentParentNbTotalCalls() { +RP3D_FORCE_INLINE uint ProfileNodeIterator::getCurrentParentNbTotalCalls() { return mCurrentParentNode->getNbTotalCalls(); } // Go to the first node -inline void ProfileNodeIterator::first() { +RP3D_FORCE_INLINE void ProfileNodeIterator::first() { mCurrentChildNode = mCurrentParentNode->getChildNode(); } // Go to the next node -inline void ProfileNodeIterator::next() { +RP3D_FORCE_INLINE void ProfileNodeIterator::next() { mCurrentChildNode = mCurrentChildNode->getSiblingNode(); } // Return a pointer to the parent node -inline ProfileNode* ProfileNode::getParentNode() { +RP3D_FORCE_INLINE ProfileNode* ProfileNode::getParentNode() { return mParentNode; } // Return a pointer to a sibling node -inline ProfileNode* ProfileNode::getSiblingNode() { +RP3D_FORCE_INLINE ProfileNode* ProfileNode::getSiblingNode() { return mSiblingNode; } // Return a pointer to a child node -inline ProfileNode* ProfileNode::getChildNode() { +RP3D_FORCE_INLINE ProfileNode* ProfileNode::getChildNode() { return mChildNode; } // Return the name of the node -inline const char* ProfileNode::getName() { +RP3D_FORCE_INLINE const char* ProfileNode::getName() { return mName; } // Return the total number of call of the corresponding block of code -inline uint ProfileNode::getNbTotalCalls() const { +RP3D_FORCE_INLINE uint ProfileNode::getNbTotalCalls() const { return mNbTotalCalls; } // Return the total time spent in the block of code -inline long double ProfileNode::getTotalTime() const { +RP3D_FORCE_INLINE long double ProfileNode::getTotalTime() const { return mTotalTime; } // Return the number of frames -inline uint Profiler::getNbFrames() { +RP3D_FORCE_INLINE uint Profiler::getNbFrames() { return mFrameCounter; } // Return the elasped time since the start/reset of the profiling -inline long double Profiler::getElapsedTimeSinceStart() { +RP3D_FORCE_INLINE long double Profiler::getElapsedTimeSinceStart() { long double currentTime = Timer::getCurrentSystemTime() * 1000.0L; return currentTime - mProfilingStartTime; } // Increment the frame counter -inline void Profiler::incrementFrameCounter() { +RP3D_FORCE_INLINE void Profiler::incrementFrameCounter() { mFrameCounter++; } // Return an iterator over the profiler tree starting at the root -inline ProfileNodeIterator* Profiler::getIterator() { +RP3D_FORCE_INLINE ProfileNodeIterator* Profiler::getIterator() { return new ProfileNodeIterator(&mRootNode); } // Destroy a previously allocated iterator -inline void Profiler::destroyIterator(ProfileNodeIterator* iterator) { +RP3D_FORCE_INLINE void Profiler::destroyIterator(ProfileNodeIterator* iterator) { delete iterator; } // Destroy the profiler (release the memory) -inline void Profiler::destroy() { +RP3D_FORCE_INLINE void Profiler::destroy() { mRootNode.destroy(); } diff --git a/src/collision/narrowphase/NarrowPhaseInfoBatch.cpp b/src/collision/narrowphase/NarrowPhaseInfoBatch.cpp index 754161fd..3aef65e3 100644 --- a/src/collision/narrowphase/NarrowPhaseInfoBatch.cpp +++ b/src/collision/narrowphase/NarrowPhaseInfoBatch.cpp @@ -43,43 +43,6 @@ NarrowPhaseInfoBatch::~NarrowPhaseInfoBatch() { clear(); } -// Add shapes to be tested during narrow-phase collision detection into the batch -void NarrowPhaseInfoBatch::addNarrowPhaseInfo(uint64 pairId, uint64 pairIndex, Entity collider1, Entity collider2, CollisionShape* shape1, - CollisionShape* shape2, const Transform& shape1Transform, const Transform& shape2Transform, - bool needToReportContacts, MemoryAllocator& shapeAllocator) { - - // Add a collision info for the two collision shapes into the overlapping pair (if not present yet) - // TODO OPTI : Can we better manage this - LastFrameCollisionInfo* lastFrameInfo = mOverlappingPairs.addLastFrameInfoIfNecessary(pairIndex, shape1->getId(), shape2->getId()); - - // Create a meta data object - narrowPhaseInfos.emplace(pairId, collider1, collider2, lastFrameInfo, shapeAllocator, shape1Transform, shape2Transform, shape1, shape2, needToReportContacts); -} - -// Add a new contact point -void NarrowPhaseInfoBatch::addContactPoint(uint index, const Vector3& contactNormal, decimal penDepth, const Vector3& localPt1, const Vector3& localPt2) { - - assert(penDepth > decimal(0.0)); - - if (narrowPhaseInfos[index].nbContactPoints < NB_MAX_CONTACT_POINTS_IN_NARROWPHASE_INFO) { - - assert(contactNormal.length() > 0.8f); - - // Add it into the array of contact points - narrowPhaseInfos[index].contactPoints[narrowPhaseInfos[index].nbContactPoints].normal = contactNormal; - narrowPhaseInfos[index].contactPoints[narrowPhaseInfos[index].nbContactPoints].penetrationDepth = penDepth; - narrowPhaseInfos[index].contactPoints[narrowPhaseInfos[index].nbContactPoints].localPoint1 = localPt1; - narrowPhaseInfos[index].contactPoints[narrowPhaseInfos[index].nbContactPoints].localPoint2 = localPt2; - narrowPhaseInfos[index].nbContactPoints++; - } -} - -// Reset the remaining contact points -void NarrowPhaseInfoBatch::resetContactPoints(uint index) { - - narrowPhaseInfos[index].nbContactPoints = 0; -} - // Initialize the containers using cached capacity void NarrowPhaseInfoBatch::reserveMemory() { diff --git a/src/collision/narrowphase/NarrowPhaseInput.cpp b/src/collision/narrowphase/NarrowPhaseInput.cpp index 454717f3..fac3d8c3 100644 --- a/src/collision/narrowphase/NarrowPhaseInput.cpp +++ b/src/collision/narrowphase/NarrowPhaseInput.cpp @@ -25,7 +25,6 @@ // Libraries #include -#include using namespace reactphysics3d; @@ -38,37 +37,6 @@ NarrowPhaseInput::NarrowPhaseInput(MemoryAllocator& allocator, OverlappingPairs& } -// Add shapes to be tested during narrow-phase collision detection into the batch -void NarrowPhaseInput::addNarrowPhaseTest(uint64 pairId, uint64 pairIndex, Entity collider1, Entity collider2, CollisionShape* shape1, CollisionShape* shape2, - const Transform& shape1Transform, const Transform& shape2Transform, - NarrowPhaseAlgorithmType narrowPhaseAlgorithmType, bool reportContacts, MemoryAllocator& shapeAllocator) { - - switch (narrowPhaseAlgorithmType) { - case NarrowPhaseAlgorithmType::SphereVsSphere: - mSphereVsSphereBatch.addNarrowPhaseInfo(pairId, pairIndex, collider1, collider2, shape1, shape2, shape1Transform, shape2Transform, reportContacts, shapeAllocator); - break; - case NarrowPhaseAlgorithmType::SphereVsCapsule: - mSphereVsCapsuleBatch.addNarrowPhaseInfo(pairId, pairIndex, collider1, collider2, shape1, shape2, shape1Transform, shape2Transform, reportContacts, shapeAllocator); - break; - case NarrowPhaseAlgorithmType::CapsuleVsCapsule: - mCapsuleVsCapsuleBatch.addNarrowPhaseInfo(pairId, pairIndex, collider1, collider2, shape1, shape2, shape1Transform, shape2Transform, reportContacts, shapeAllocator); - break; - case NarrowPhaseAlgorithmType::SphereVsConvexPolyhedron: - mSphereVsConvexPolyhedronBatch.addNarrowPhaseInfo(pairId, pairIndex, collider1, collider2, shape1, shape2, shape1Transform, shape2Transform, reportContacts, shapeAllocator); - break; - case NarrowPhaseAlgorithmType::CapsuleVsConvexPolyhedron: - mCapsuleVsConvexPolyhedronBatch.addNarrowPhaseInfo(pairId, pairIndex, collider1, collider2, shape1, shape2, shape1Transform, shape2Transform, reportContacts, shapeAllocator); - break; - case NarrowPhaseAlgorithmType::ConvexPolyhedronVsConvexPolyhedron: - mConvexPolyhedronVsConvexPolyhedronBatch.addNarrowPhaseInfo(pairId, pairIndex, collider1, collider2, shape1, shape2, shape1Transform, shape2Transform, reportContacts, shapeAllocator); - break; - case NarrowPhaseAlgorithmType::None: - // Must never happen - assert(false); - break; - } -} - /// Reserve memory for the containers with cached capacity void NarrowPhaseInput::reserveMemory() { diff --git a/src/systems/ContactSolverSystem.cpp b/src/systems/ContactSolverSystem.cpp index b5854a54..c5616648 100644 --- a/src/systems/ContactSolverSystem.cpp +++ b/src/systems/ContactSolverSystem.cpp @@ -818,7 +818,7 @@ decimal ContactSolverSystem::computeMixedFrictionCoefficient(Collider* collider1 } // Compute th mixed rolling resistance factor between two colliders -inline decimal ContactSolverSystem::computeMixedRollingResistance(Collider* collider1, Collider* collider2) const { +RP3D_FORCE_INLINE decimal ContactSolverSystem::computeMixedRollingResistance(Collider* collider1, Collider* collider2) const { return decimal(0.5f) * (collider1->getMaterial().getRollingResistance() + collider2->getMaterial().getRollingResistance()); } From aa6b228e101d727f8b2c8f862475cc2fd77fec28 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Tue, 28 Jul 2020 23:34:25 +0200 Subject: [PATCH 13/74] Optimizations --- .../collision/ContactManifold.h | 8 +- include/reactphysics3d/engine/Material.h | 45 ++-- .../reactphysics3d/mathematics/Matrix3x3.h | 3 + src/collision/ContactManifold.cpp | 4 +- src/engine/Material.cpp | 11 +- src/mathematics/Matrix3x3.cpp | 21 ++ src/systems/CollisionDetectionSystem.cpp | 66 ++--- src/systems/ContactSolverSystem.cpp | 248 +++++++++--------- 8 files changed, 215 insertions(+), 191 deletions(-) diff --git a/include/reactphysics3d/collision/ContactManifold.h b/include/reactphysics3d/collision/ContactManifold.h index 3dfeb0df..cdd95e58 100644 --- a/include/reactphysics3d/collision/ContactManifold.h +++ b/include/reactphysics3d/collision/ContactManifold.h @@ -101,13 +101,19 @@ class ContactManifold { /// True if the contact manifold has already been added into an island bool isAlreadyInIsland; + /// Index of the first body of the manifold in the mRigidBodyComponents array (only if body 1 is a RigidBody) + uint32 rigidBody1Index; + + /// Index of the second body of the manifold in the mRigidBodyComponents array (only if body 2 is a RigidBody) + uint32 rigidBody2Index; + public: // -------------------- Methods -------------------- // /// Constructor ContactManifold(Entity bodyEntity1, Entity bodyEntity2, Entity colliderEntity1, Entity colliderEntity2, - uint contactPointsIndex, uint8 nbContactPoints); + uint contactPointsIndex, uint8 nbContactPoints, uint32 rigidBody1Index, uint32 rigidBody2Index); // -------------------- Friendship -------------------- // diff --git a/include/reactphysics3d/engine/Material.h b/include/reactphysics3d/engine/Material.h index c4406b74..8a8bf2b4 100644 --- a/include/reactphysics3d/engine/Material.h +++ b/include/reactphysics3d/engine/Material.h @@ -28,6 +28,7 @@ // Libraries #include +#include #include namespace reactphysics3d { @@ -44,8 +45,8 @@ class Material { // -------------------- Attributes -------------------- // - /// Friction coefficient (positive value) - decimal mFrictionCoefficient; + /// Square root of the friction coefficient + decimal mFrictionCoefficientSqrt; /// Rolling resistance factor (positive value) decimal mRollingResistance; @@ -59,14 +60,7 @@ class Material { // -------------------- Methods -------------------- // /// Constructor - Material(decimal frictionCoefficient, decimal rollingResistance, decimal bounciness, - decimal massDensity = decimal(1.0)); - - /// Copy-constructor - Material(const Material& material); - - /// Destructor - ~Material() = default; + Material(decimal frictionCoefficient, decimal rollingResistance, decimal bounciness, decimal massDensity = decimal(1.0)); public : @@ -84,6 +78,9 @@ class Material { /// Set the friction coefficient. void setFrictionCoefficient(decimal frictionCoefficient); + /// Return the square root friction coefficient + decimal getFrictionCoefficientSqrt() const; + /// Return the rolling resistance factor decimal getRollingResistance() const; @@ -99,9 +96,6 @@ class Material { /// Return a string representation for the material std::string to_string() const; - /// Overloaded assignment operator - Material& operator=(const Material& material); - // ---------- Friendship ---------- // friend class Collider; @@ -131,7 +125,7 @@ RP3D_FORCE_INLINE void Material::setBounciness(decimal bounciness) { * @return Friction coefficient (positive value) */ RP3D_FORCE_INLINE decimal Material::getFrictionCoefficient() const { - return mFrictionCoefficient; + return mFrictionCoefficientSqrt * mFrictionCoefficientSqrt; } // Set the friction coefficient. @@ -142,7 +136,12 @@ RP3D_FORCE_INLINE decimal Material::getFrictionCoefficient() const { */ RP3D_FORCE_INLINE void Material::setFrictionCoefficient(decimal frictionCoefficient) { assert(frictionCoefficient >= decimal(0.0)); - mFrictionCoefficient = frictionCoefficient; + mFrictionCoefficientSqrt = std::sqrt(frictionCoefficient); +} + +// Return the square root friction coefficient +RP3D_FORCE_INLINE decimal Material::getFrictionCoefficientSqrt() const { + return mFrictionCoefficientSqrt; } // Return the rolling resistance factor. If this value is larger than zero, @@ -184,27 +183,13 @@ RP3D_FORCE_INLINE std::string Material::to_string() const { std::stringstream ss; - ss << "frictionCoefficient=" << mFrictionCoefficient << std::endl; + ss << "frictionCoefficient=" << (mFrictionCoefficientSqrt * mFrictionCoefficientSqrt) << std::endl; ss << "rollingResistance=" << mRollingResistance << std::endl; ss << "bounciness=" << mBounciness << std::endl; return ss.str(); } -// Overloaded assignment operator -RP3D_FORCE_INLINE Material& Material::operator=(const Material& material) { - - // Check for self-assignment - if (this != &material) { - mFrictionCoefficient = material.mFrictionCoefficient; - mBounciness = material.mBounciness; - mRollingResistance = material.mRollingResistance; - } - - // Return this material - return *this; -} - } #endif diff --git a/include/reactphysics3d/mathematics/Matrix3x3.h b/include/reactphysics3d/mathematics/Matrix3x3.h index 575adfc4..6d14d993 100644 --- a/include/reactphysics3d/mathematics/Matrix3x3.h +++ b/include/reactphysics3d/mathematics/Matrix3x3.h @@ -95,6 +95,9 @@ class Matrix3x3 { /// Return the inverse matrix Matrix3x3 getInverse() const; + /// Return the inverse matrix + Matrix3x3 getInverseWithDeterminant(decimal determinant) const; + /// Return the matrix with absolute values Matrix3x3 getAbsoluteMatrix() const; diff --git a/src/collision/ContactManifold.cpp b/src/collision/ContactManifold.cpp index 46f886de..cabdace2 100644 --- a/src/collision/ContactManifold.cpp +++ b/src/collision/ContactManifold.cpp @@ -32,9 +32,9 @@ using namespace reactphysics3d; // Constructor ContactManifold::ContactManifold(Entity bodyEntity1, Entity bodyEntity2, Entity colliderEntity1, Entity colliderEntity2, - uint contactPointsIndex, uint8 nbContactPoints) + uint contactPointsIndex, uint8 nbContactPoints, uint32 rigidBody1Index, uint32 rigidBody2Index) :contactPointsIndex(contactPointsIndex), bodyEntity1(bodyEntity1), bodyEntity2(bodyEntity2), colliderEntity1(colliderEntity1), colliderEntity2(colliderEntity2), nbContactPoints(nbContactPoints), frictionImpulse1(0), frictionImpulse2(0), - frictionTwistImpulse(0), isAlreadyInIsland(false) { + frictionTwistImpulse(0), isAlreadyInIsland(false), rigidBody1Index(rigidBody1Index), rigidBody2Index(rigidBody2Index) { } diff --git a/src/engine/Material.cpp b/src/engine/Material.cpp index bb2aaa35..3c73b11e 100644 --- a/src/engine/Material.cpp +++ b/src/engine/Material.cpp @@ -30,14 +30,7 @@ using namespace reactphysics3d; // Constructor Material::Material(decimal frictionCoefficient, decimal rollingResistance, decimal bounciness, decimal massDensity) - : mFrictionCoefficient(frictionCoefficient), mRollingResistance(rollingResistance), mBounciness(bounciness), - mMassDensity(massDensity) { - -} - -// Copy-constructor -Material::Material(const Material& material) - : mFrictionCoefficient(material.mFrictionCoefficient), mRollingResistance(material.mRollingResistance), - mBounciness(material.mBounciness) { + : mFrictionCoefficientSqrt(std::sqrt(frictionCoefficient)), + mRollingResistance(rollingResistance), mBounciness(bounciness), mMassDensity(massDensity) { } diff --git a/src/mathematics/Matrix3x3.cpp b/src/mathematics/Matrix3x3.cpp index 2cca4a2c..16502f0d 100644 --- a/src/mathematics/Matrix3x3.cpp +++ b/src/mathematics/Matrix3x3.cpp @@ -66,5 +66,26 @@ Matrix3x3 Matrix3x3::getInverse() const { return (invDeterminant * tempMatrix); } +// Return the inverse matrix +Matrix3x3 Matrix3x3::getInverseWithDeterminant(decimal determinant) const { + + // Check if the determinant is equal to zero + assert(determinant != decimal(0.0)); + + decimal invDeterminant = decimal(1.0) / determinant; + + Matrix3x3 tempMatrix((mRows[1][1]*mRows[2][2]-mRows[2][1]*mRows[1][2]), + -(mRows[0][1]*mRows[2][2]-mRows[2][1]*mRows[0][2]), + (mRows[0][1]*mRows[1][2]-mRows[0][2]*mRows[1][1]), + -(mRows[1][0]*mRows[2][2]-mRows[2][0]*mRows[1][2]), + (mRows[0][0]*mRows[2][2]-mRows[2][0]*mRows[0][2]), + -(mRows[0][0]*mRows[1][2]-mRows[1][0]*mRows[0][2]), + (mRows[1][0]*mRows[2][1]-mRows[2][0]*mRows[1][1]), + -(mRows[0][0]*mRows[2][1]-mRows[2][0]*mRows[0][1]), + (mRows[0][0]*mRows[1][1]-mRows[0][1]*mRows[1][0])); + + // Return the inverse matrix + return (invDeterminant * tempMatrix); +} diff --git a/src/systems/CollisionDetectionSystem.cpp b/src/systems/CollisionDetectionSystem.cpp index 4d28ae89..e474dfcd 100644 --- a/src/systems/CollisionDetectionSystem.cpp +++ b/src/systems/CollisionDetectionSystem.cpp @@ -167,7 +167,8 @@ void CollisionDetectionSystem::updateOverlappingPairs(const List nodePair = overlappingNodes[i]; @@ -241,7 +242,8 @@ void CollisionDetectionSystem::computeMiddlePhase(NarrowPhaseInput& narrowPhaseI mOverlappingPairs.clearObsoleteLastFrameCollisionInfos(); // For each possible convex vs convex pair of bodies - for (uint64 i=0; i < mOverlappingPairs.getNbConvexVsConvexPairs(); i++) { + const uint64 nbConvexVsConvexPairs = mOverlappingPairs.getNbConvexVsConvexPairs(); + for (uint64 i=0; i < nbConvexVsConvexPairs; i++) { assert(mCollidersComponents.getBroadPhaseId(mOverlappingPairs.mColliders1[i]) != -1); assert(mCollidersComponents.getBroadPhaseId(mOverlappingPairs.mColliders2[i]) != -1); @@ -278,7 +280,8 @@ void CollisionDetectionSystem::computeMiddlePhase(NarrowPhaseInput& narrowPhaseI // For each possible convex vs concave pair of bodies const uint64 convexVsConcaveStartIndex = mOverlappingPairs.getConvexVsConcavePairsStartIndex(); - for (uint64 i=convexVsConcaveStartIndex; i < convexVsConcaveStartIndex + mOverlappingPairs.getNbConvexVsConcavePairs(); i++) { + const uint64 nbConvexVsConcavePairs = mOverlappingPairs.getNbConvexVsConcavePairs(); + for (uint64 i=convexVsConcaveStartIndex; i < convexVsConcaveStartIndex + nbConvexVsConcavePairs; i++) { assert(mCollidersComponents.getBroadPhaseId(mOverlappingPairs.mColliders1[i]) != -1); assert(mCollidersComponents.getBroadPhaseId(mOverlappingPairs.mColliders2[i]) != -1); @@ -307,7 +310,8 @@ void CollisionDetectionSystem::computeMiddlePhaseCollisionSnapshot(List& mOverlappingPairs.clearObsoleteLastFrameCollisionInfos(); // For each possible convex vs convex pair of bodies - for (uint64 p=0; p < convexPairs.size(); p++) { + const uint64 nbConvexPairs = convexPairs.size(); + for (uint64 p=0; p < nbConvexPairs; p++) { const uint64 pairId = convexPairs[p]; @@ -339,7 +343,8 @@ void CollisionDetectionSystem::computeMiddlePhaseCollisionSnapshot(List& } // For each possible convex vs concave pair of bodies - for (uint p=0; p < concavePairs.size(); p++) { + const uint nbConcavePairs = concavePairs.size(); + for (uint p=0; p < nbConcavePairs; p++) { const uint64 pairId = concavePairs[p]; const uint64 pairIndex = mOverlappingPairs.mMapPairIdToPairIndex[pairId]; @@ -407,7 +412,8 @@ void CollisionDetectionSystem::computeConvexVsConcaveMiddlePhase(uint64 pairInde const bool reportContacts = !isCollider1Trigger && !isCollider2Trigger; // For each overlapping triangle - for (uint i=0; i < shapeIds.size(); i++) + const uint nbShapeIds = shapeIds.size(); + for (uint i=0; i < nbShapeIds; i++) { // Create a triangle collision shape (the allocated memory for the TriangleShape will be released in the // destructor of the corresponding NarrowPhaseInfo. @@ -716,7 +722,8 @@ void CollisionDetectionSystem::createContacts() { mCurrentContactPoints->reserve(mCurrentContactManifolds->size()); // For each contact pair - for (uint p=0; p < (*mCurrentContactPairs).size(); p++) { + const uint nbCurrentContactPairs = (*mCurrentContactPairs).size(); + for (uint p=0; p < nbCurrentContactPairs; p++) { ContactPair& contactPair = (*mCurrentContactPairs)[p]; @@ -725,10 +732,12 @@ void CollisionDetectionSystem::createContacts() { contactPair.contactPointsIndex = mCurrentContactPoints->size(); // Add the associated contact pair to both bodies of the pair (used to create islands later) - if (mRigidBodyComponents.hasComponent(contactPair.body1Entity)) { + uint32 rigidBody1Index = 0; + uint32 rigidBody2Index = 0; + if (mRigidBodyComponents.hasComponentGetIndex(contactPair.body1Entity, rigidBody1Index)) { mRigidBodyComponents.addContacPair(contactPair.body1Entity, p); } - if (mRigidBodyComponents.hasComponent(contactPair.body2Entity)) { + if (mRigidBodyComponents.hasComponentGetIndex(contactPair.body2Entity, rigidBody2Index)) { mRigidBodyComponents.addContacPair(contactPair.body2Entity, p); } @@ -742,12 +751,9 @@ void CollisionDetectionSystem::createContacts() { const int8 nbContactPoints = static_cast(potentialManifold.nbPotentialContactPoints); contactPair.nbToTalContactPoints += nbContactPoints; - // We create a new contact manifold - ContactManifold contactManifold(contactPair.body1Entity, contactPair.body2Entity, contactPair.collider1Entity, - contactPair.collider2Entity, contactPointsIndex, nbContactPoints); - - // Add the contact manifold - mCurrentContactManifolds->add(contactManifold); + // Create and add the contact manifold + mCurrentContactManifolds->emplace(contactPair.body1Entity, contactPair.body2Entity, contactPair.collider1Entity, + contactPair.collider2Entity, contactPointsIndex, nbContactPoints, rigidBody1Index, rigidBody2Index); assert(potentialManifold.nbPotentialContactPoints > 0); @@ -756,11 +762,8 @@ void CollisionDetectionSystem::createContacts() { ContactPointInfo& potentialContactPoint = mPotentialContactPoints[potentialManifold.potentialContactPointsIndices[c]]; - // Create a new contact point - ContactPoint contactPoint(potentialContactPoint, mWorld->mConfig.persistentContactDistanceThreshold); - - // Add the contact point - mCurrentContactPoints->add(contactPoint); + // Create and add the contact point + mCurrentContactPoints->emplace(potentialContactPoint, mWorld->mConfig.persistentContactDistanceThreshold); } } } @@ -812,7 +815,8 @@ void CollisionDetectionSystem::createSnapshotContacts(List& contact contactPoints.reserve(contactManifolds.size()); // For each contact pair - for (uint p=0; p < contactPairs.size(); p++) { + const uint nbContactPairs = contactPairs.size(); + for (uint p=0; p < nbContactPairs; p++) { ContactPair& contactPair = contactPairs[p]; assert(contactPair.nbPotentialContactManifolds > 0); @@ -831,12 +835,9 @@ void CollisionDetectionSystem::createSnapshotContacts(List& contact const uint8 nbContactPoints = potentialManifold.nbPotentialContactPoints; contactPair.nbToTalContactPoints += nbContactPoints; - // We create a new contact manifold - ContactManifold contactManifold(contactPair.body1Entity, contactPair.body2Entity, contactPair.collider1Entity, - contactPair.collider2Entity, contactPointsIndex, nbContactPoints); - - // Add the contact manifold - contactManifolds.add(contactManifold); + // Create and add the contact manifold + contactManifolds.emplace(contactPair.body1Entity, contactPair.body2Entity, contactPair.collider1Entity, + contactPair.collider2Entity, contactPointsIndex, nbContactPoints, 0, 0); assert(potentialManifold.nbPotentialContactPoints > 0); @@ -992,10 +993,12 @@ void CollisionDetectionSystem::processPotentialContacts(NarrowPhaseInfoBatch& na RP3D_PROFILE("CollisionDetectionSystem::processPotentialContacts()", mProfiler); + const uint nbObjects = narrowPhaseInfoBatch.getNbObjects(); + if (updateLastFrameInfo) { // For each narrow phase info object - for(uint i=0; i < narrowPhaseInfoBatch.getNbObjects(); i++) { + for(uint i=0; i < nbObjects; i++) { narrowPhaseInfoBatch.narrowPhaseInfos[i].lastFrameCollisionInfo->wasColliding = narrowPhaseInfoBatch.narrowPhaseInfos[i].isColliding; @@ -1005,7 +1008,7 @@ void CollisionDetectionSystem::processPotentialContacts(NarrowPhaseInfoBatch& na } // For each narrow phase info object - for(uint i=0; i < narrowPhaseInfoBatch.getNbObjects(); i++) { + for(uint i=0; i < nbObjects; i++) { const uint64 pairId = narrowPhaseInfoBatch.narrowPhaseInfos[i].overlappingPairId; const uint64 pairIndex = mOverlappingPairs.mMapPairIdToPairIndex[pairId]; @@ -1175,7 +1178,8 @@ void CollisionDetectionSystem::reducePotentialContactManifolds(List RP3D_PROFILE("CollisionDetectionSystem::reducePotentialContactManifolds()", mProfiler); // Reduce the number of potential contact manifolds in a contact pair - for (uint i=0; i < contactPairs->size(); i++) { + const uint nbContactPairs = contactPairs->size(); + for (uint i=0; i < nbContactPairs; i++) { ContactPair& contactPair = (*contactPairs)[i]; @@ -1205,7 +1209,7 @@ void CollisionDetectionSystem::reducePotentialContactManifolds(List } // Reduce the number of potential contact points in the manifolds - for (uint i=0; i < contactPairs->size(); i++) { + for (uint i=0; i < nbContactPairs; i++) { const ContactPair& pairContact = (*contactPairs)[i]; diff --git a/src/systems/ContactSolverSystem.cpp b/src/systems/ContactSolverSystem.cpp index c5616648..27f74cf6 100644 --- a/src/systems/ContactSolverSystem.cpp +++ b/src/systems/ContactSolverSystem.cpp @@ -90,7 +90,8 @@ void ContactSolverSystem::init(List* contactManifolds, List 0) { initializeForIsland(i); @@ -117,25 +118,24 @@ void ContactSolverSystem::initializeForIsland(uint islandIndex) { assert(mIslands.nbContactManifolds[islandIndex] > 0); // For each contact manifold of the island - uint contactManifoldsIndex = mIslands.contactManifoldsIndices[islandIndex]; - uint nbContactManifolds = mIslands.nbContactManifolds[islandIndex]; + const uint contactManifoldsIndex = mIslands.contactManifoldsIndices[islandIndex]; + const uint nbContactManifolds = mIslands.nbContactManifolds[islandIndex]; for (uint m=contactManifoldsIndex; m < contactManifoldsIndex + nbContactManifolds; m++) { ContactManifold& externalManifold = (*mAllContactManifolds)[m]; assert(externalManifold.nbContactPoints > 0); - const uint rigidBodyIndex1 = mRigidBodyComponents.getEntityIndex(externalManifold.bodyEntity1); - const uint rigidBodyIndex2 = mRigidBodyComponents.getEntityIndex(externalManifold.bodyEntity2); + const uint rigidBodyIndex1 = externalManifold.rigidBody1Index; + const uint rigidBodyIndex2 = externalManifold.rigidBody2Index; // Get the two bodies of the contact - RigidBody* body1 = mRigidBodyComponents.mRigidBodies[rigidBodyIndex1]; - RigidBody* body2 = mRigidBodyComponents.mRigidBodies[rigidBodyIndex2]; assert(body1 != nullptr); assert(body2 != nullptr); assert(!mBodyComponents.getIsEntityDisabled(externalManifold.bodyEntity1)); assert(!mBodyComponents.getIsEntityDisabled(externalManifold.bodyEntity2)); + // TODO OPTI : Do not use pointers to colliders here, maybe we call totally remove those pointers Collider* collider1 = mColliderComponents.getCollider(externalManifold.colliderEntity1); Collider* collider2 = mColliderComponents.getCollider(externalManifold.colliderEntity2); @@ -152,7 +152,9 @@ void ContactSolverSystem::initializeForIsland(uint islandIndex) { mContactConstraints[mNbContactManifolds].massInverseBody1 = mRigidBodyComponents.mInverseMasses[rigidBodyIndex1]; mContactConstraints[mNbContactManifolds].massInverseBody2 = mRigidBodyComponents.mInverseMasses[rigidBodyIndex2]; mContactConstraints[mNbContactManifolds].nbContacts = externalManifold.nbContactPoints; + // TODO OPTI : Do not compute this using colliders pointers mContactConstraints[mNbContactManifolds].frictionCoefficient = computeMixedFrictionCoefficient(collider1, collider2); + // TODO OPTI : Do not compute this using colliders pointers mContactConstraints[mNbContactManifolds].rollingResistanceFactor = computeMixedRollingResistance(collider1, collider2); mContactConstraints[mNbContactManifolds].externalContactManifold = &externalManifold; mContactConstraints[mNbContactManifolds].normal.setToZero(); @@ -165,17 +167,21 @@ void ContactSolverSystem::initializeForIsland(uint islandIndex) { const Vector3& v2 = mRigidBodyComponents.mLinearVelocities[rigidBodyIndex2]; const Vector3& w2 = mRigidBodyComponents.mAngularVelocities[rigidBodyIndex2]; + // TODO OPTI : Maybe use collider index here + const Transform& collider1LocalToWorldTransform = mColliderComponents.getLocalToWorldTransform(externalManifold.colliderEntity1); + const Transform& collider2LocalToWorldTransform = mColliderComponents.getLocalToWorldTransform(externalManifold.colliderEntity2); + // For each contact point of the contact manifold assert(externalManifold.nbContactPoints > 0); - uint contactPointsStartIndex = externalManifold.contactPointsIndex; - uint nbContactPoints = static_cast(externalManifold.nbContactPoints); + const uint contactPointsStartIndex = externalManifold.contactPointsIndex; + const uint nbContactPoints = static_cast(externalManifold.nbContactPoints); for (uint c=contactPointsStartIndex; c < contactPointsStartIndex + nbContactPoints; c++) { ContactPoint& externalContact = (*mAllContactPoints)[c]; // Get the contact point on the two bodies - Vector3 p1 = mColliderComponents.getLocalToWorldTransform(externalManifold.colliderEntity1) * externalContact.getLocalPointOnShape1(); - Vector3 p2 = mColliderComponents.getLocalToWorldTransform(externalManifold.colliderEntity2) * externalContact.getLocalPointOnShape2(); + const Vector3 p1 = collider1LocalToWorldTransform * externalContact.getLocalPointOnShape1(); + const Vector3 p2 = collider2LocalToWorldTransform * externalContact.getLocalPointOnShape2(); new (mContactPoints + mNbContactPoints) ContactPointSolver(); mContactPoints[mNbContactPoints].externalContact = &externalContact; @@ -270,20 +276,20 @@ void ContactSolverSystem::initializeForIsland(uint islandIndex) { mContactConstraints[mNbContactManifolds].frictionTwistImpulse = externalManifold.frictionTwistImpulse; // Compute the inverse K matrix for the rolling resistance constraint - bool isBody1DynamicType = body1->getType() == BodyType::DYNAMIC; - bool isBody2DynamicType = body2->getType() == BodyType::DYNAMIC; + const bool isBody1DynamicType = mRigidBodyComponents.mBodyTypes[rigidBodyIndex1] == BodyType::DYNAMIC; + const bool isBody2DynamicType = mRigidBodyComponents.mBodyTypes[rigidBodyIndex2] == BodyType::DYNAMIC; mContactConstraints[mNbContactManifolds].inverseRollingResistance.setToZero(); if (mContactConstraints[mNbContactManifolds].rollingResistanceFactor > 0 && (isBody1DynamicType || isBody2DynamicType)) { mContactConstraints[mNbContactManifolds].inverseRollingResistance = mContactConstraints[mNbContactManifolds].inverseInertiaTensorBody1 + mContactConstraints[mNbContactManifolds].inverseInertiaTensorBody2; - decimal det = mContactConstraints[mNbContactManifolds].inverseRollingResistance.getDeterminant(); + const decimal det = mContactConstraints[mNbContactManifolds].inverseRollingResistance.getDeterminant(); // If the matrix is not inversible if (approxEqual(det, decimal(0.0))) { mContactConstraints[mNbContactManifolds].inverseRollingResistance.setToZero(); } else { - mContactConstraints[mNbContactManifolds].inverseRollingResistance = mContactConstraints[mNbContactManifolds].inverseRollingResistance.getInverse(); + mContactConstraints[mNbContactManifolds].inverseRollingResistance = mContactConstraints[mNbContactManifolds].inverseRollingResistance.getInverseWithDeterminant(det); } } @@ -352,10 +358,12 @@ void ContactSolverSystem::warmStart() { for (short int i=0; igetMaterial().getFrictionCoefficient() * - collider2->getMaterial().getFrictionCoefficient()); + return collider1->getMaterial().getFrictionCoefficientSqrt() * collider2->getMaterial().getFrictionCoefficientSqrt(); } // Compute th mixed rolling resistance factor between two colliders -RP3D_FORCE_INLINE decimal ContactSolverSystem::computeMixedRollingResistance(Collider* collider1, Collider* collider2) const { +decimal ContactSolverSystem::computeMixedRollingResistance(Collider* collider1, Collider* collider2) const { return decimal(0.5f) * (collider1->getMaterial().getRollingResistance() + collider2->getMaterial().getRollingResistance()); } @@ -851,8 +864,7 @@ void ContactSolverSystem::storeImpulses() { // Compute the two unit orthogonal vectors "t1" and "t2" that span the tangential friction plane // for a contact manifold. The two vectors have to be such that : t1 x t2 = contactNormal. -void ContactSolverSystem::computeFrictionVectors(const Vector3& deltaVelocity, - ContactManifoldSolver& contact) const { +void ContactSolverSystem::computeFrictionVectors(const Vector3& deltaVelocity, ContactManifoldSolver& contact) const { RP3D_PROFILE("ContactSolver::computeFrictionVectors()", mProfiler); From 7f219dd99c1c8c371e1075cbd8f2b81c833b2817 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Tue, 28 Jul 2020 23:35:11 +0200 Subject: [PATCH 14/74] More materials into the ColliderComponents and more optimizations --- include/reactphysics3d/collision/Collider.h | 11 ---- .../components/ColliderComponents.h | 33 +++++++++- include/reactphysics3d/engine/Material.h | 2 + .../systems/ContactSolverSystem.h | 30 +++++++-- src/body/CollisionBody.cpp | 7 +- src/body/RigidBody.cpp | 28 ++++---- src/collision/Collider.cpp | 15 +++-- src/components/ColliderComponents.cpp | 10 ++- src/systems/ContactSolverSystem.cpp | 65 ++++++------------- 9 files changed, 119 insertions(+), 82 deletions(-) diff --git a/include/reactphysics3d/collision/Collider.h b/include/reactphysics3d/collision/Collider.h index ae590d9f..20b83405 100644 --- a/include/reactphysics3d/collision/Collider.h +++ b/include/reactphysics3d/collision/Collider.h @@ -59,9 +59,6 @@ class Collider { /// Pointer to the parent body CollisionBody* mBody; - /// Material properties of the rigid body - Material mMaterial; - /// Pointer to user data void* mUserData; @@ -225,14 +222,6 @@ RP3D_FORCE_INLINE bool Collider::testAABBOverlap(const AABB& worldAABB) const { return worldAABB.testCollision(getWorldAABB()); } -// Return a reference to the material properties of the collider -/** - * @return A reference to the material of the body - */ -RP3D_FORCE_INLINE Material& Collider::getMaterial() { - return mMaterial; -} - } #endif diff --git a/include/reactphysics3d/components/ColliderComponents.h b/include/reactphysics3d/components/ColliderComponents.h index 8dfa5bf3..8207309c 100644 --- a/include/reactphysics3d/components/ColliderComponents.h +++ b/include/reactphysics3d/components/ColliderComponents.h @@ -32,6 +32,7 @@ #include #include #include +#include // ReactPhysics3D namespace namespace reactphysics3d { @@ -99,6 +100,9 @@ class ColliderComponents : public Components { /// True if the collider is a trigger bool* mIsTrigger; + /// Array with the material of each collider + Material* mMaterials; + // -------------------- Methods -------------------- // @@ -127,14 +131,15 @@ class ColliderComponents : public Components { unsigned short collisionCategoryBits; unsigned short collideWithMaskBits; const Transform& localToWorldTransform; + const Material& material; /// Constructor ColliderComponent(Entity bodyEntity, Collider* collider, AABB localBounds, const Transform& localToBodyTransform, CollisionShape* collisionShape, unsigned short collisionCategoryBits, - unsigned short collideWithMaskBits, const Transform& localToWorldTransform) + unsigned short collideWithMaskBits, const Transform& localToWorldTransform, const Material& material) :bodyEntity(bodyEntity), collider(collider), localBounds(localBounds), localToBodyTransform(localToBodyTransform), collisionShape(collisionShape), collisionCategoryBits(collisionCategoryBits), collideWithMaskBits(collideWithMaskBits), - localToWorldTransform(localToWorldTransform) { + localToWorldTransform(localToWorldTransform), material(material) { } }; @@ -204,12 +209,20 @@ class ColliderComponents : public Components { /// Set whether a collider is a trigger void setIsTrigger(Entity colliderEntity, bool isTrigger); + /// Return a reference to the material of a collider + Material& getMaterial(Entity colliderEntity); + + /// Set the material of a collider + void setMaterial(Entity colliderEntity, const Material& material); + // -------------------- Friendship -------------------- // friend class BroadPhaseSystem; friend class CollisionDetectionSystem; + friend class ContactSolverSystem; friend class DynamicsSystem; friend class OverlappingPairs; + friend class RigidBody; }; // Return the body entity of a given collider @@ -357,6 +370,22 @@ RP3D_FORCE_INLINE void ColliderComponents::setIsTrigger(Entity colliderEntity, b mIsTrigger[mMapEntityToComponentIndex[colliderEntity]] = isTrigger; } +// Return a reference to the material of a collider +RP3D_FORCE_INLINE Material& ColliderComponents::getMaterial(Entity colliderEntity) { + + assert(mMapEntityToComponentIndex.containsKey(colliderEntity)); + + return mMaterials[mMapEntityToComponentIndex[colliderEntity]]; +} + +// Set the material of a collider +RP3D_FORCE_INLINE void ColliderComponents::setMaterial(Entity colliderEntity, const Material& material) { + + assert(mMapEntityToComponentIndex.containsKey(colliderEntity)); + + mMaterials[mMapEntityToComponentIndex[colliderEntity]] = material; +} + } #endif diff --git a/include/reactphysics3d/engine/Material.h b/include/reactphysics3d/engine/Material.h index 8a8bf2b4..a8462c20 100644 --- a/include/reactphysics3d/engine/Material.h +++ b/include/reactphysics3d/engine/Material.h @@ -99,6 +99,8 @@ class Material { // ---------- Friendship ---------- // friend class Collider; + friend class CollisionBody; + friend class RigidBody; }; // Return the bounciness diff --git a/include/reactphysics3d/systems/ContactSolverSystem.h b/include/reactphysics3d/systems/ContactSolverSystem.h index ba30a0ac..11960b68 100644 --- a/include/reactphysics3d/systems/ContactSolverSystem.h +++ b/include/reactphysics3d/systems/ContactSolverSystem.h @@ -30,6 +30,7 @@ #include #include #include +#include /// ReactPhysics3D namespace namespace reactphysics3d { @@ -338,14 +339,13 @@ class ContactSolverSystem { // -------------------- Methods -------------------- // /// Compute the collision restitution factor from the restitution factor of each collider - decimal computeMixedRestitutionFactor(Collider* collider1, - Collider* collider2) const; + decimal computeMixedRestitutionFactor(const Material& material1, const Material& material2) const; /// Compute the mixed friction coefficient from the friction coefficient of each collider - decimal computeMixedFrictionCoefficient(Collider* collider1, Collider* collider2) const; + decimal computeMixedFrictionCoefficient(const Material &material1, const Material &material2) const; /// Compute th mixed rolling resistance factor between two colliders - decimal computeMixedRollingResistance(Collider* collider1, Collider* collider2) const; + decimal computeMixedRollingResistance(const Material& material1, const Material& material2) const; /// Compute the two unit orthogonal vectors "t1" and "t2" that span the tangential friction /// plane for a contact manifold. The two vectors have to be @@ -407,6 +407,28 @@ RP3D_FORCE_INLINE void ContactSolverSystem::setIsSplitImpulseActive(bool isActiv mIsSplitImpulseActive = isActive; } +// Compute the collision restitution factor from the restitution factor of each collider +RP3D_FORCE_INLINE decimal ContactSolverSystem::computeMixedRestitutionFactor(const Material& material1, const Material& material2) const { + + const decimal restitution1 = material1.getBounciness(); + const decimal restitution2 = material2.getBounciness(); + + // Return the largest restitution factor + return (restitution1 > restitution2) ? restitution1 : restitution2; +} + +// Compute the mixed friction coefficient from the friction coefficient of each collider +RP3D_FORCE_INLINE decimal ContactSolverSystem::computeMixedFrictionCoefficient(const Material& material1, const Material& material2) const { + + // Use the geometric mean to compute the mixed friction coefficient + return material1.getFrictionCoefficientSqrt() * material2.getFrictionCoefficientSqrt(); +} + +// Compute th mixed rolling resistance factor between two colliders +RP3D_FORCE_INLINE decimal ContactSolverSystem::computeMixedRollingResistance(const Material& material1, const Material& material2) const { + return decimal(0.5f) * (material1.getRollingResistance() + material2.getRollingResistance()); +} + #ifdef IS_RP3D_PROFILING_ENABLED // Set the profiler diff --git a/src/body/CollisionBody.cpp b/src/body/CollisionBody.cpp index 16279cf7..5c9c9610 100644 --- a/src/body/CollisionBody.cpp +++ b/src/body/CollisionBody.cpp @@ -89,12 +89,13 @@ Collider* CollisionBody::addCollider(CollisionShape* collisionShape, const Trans // TODO : Maybe this method can directly returns an AABB collisionShape->getLocalBounds(localBoundsMin, localBoundsMax); const Transform localToWorldTransform = mWorld.mTransformComponents.getTransform(mEntity) * transform; - ColliderComponents::ColliderComponent colliderComponent(mEntity, collider, - AABB(localBoundsMin, localBoundsMax), - transform, collisionShape, 0x0001, 0xFFFF, localToWorldTransform); + Material material(mWorld.mConfig.defaultFrictionCoefficient, mWorld.mConfig.defaultRollingRestistance, mWorld.mConfig.defaultBounciness); + ColliderComponents::ColliderComponent colliderComponent(mEntity, collider, AABB(localBoundsMin, localBoundsMax), + transform, collisionShape, 0x0001, 0xFFFF, localToWorldTransform, material); bool isActive = mWorld.mCollisionBodyComponents.getIsActive(mEntity); mWorld.mCollidersComponents.addComponent(colliderEntity, !isActive, colliderComponent); + mWorld.mCollisionBodyComponents.addColliderToBody(mEntity, colliderEntity); // Assign the collider with the collision shape diff --git a/src/body/RigidBody.cpp b/src/body/RigidBody.cpp index 76db1b04..4e884efc 100644 --- a/src/body/RigidBody.cpp +++ b/src/body/RigidBody.cpp @@ -331,15 +331,15 @@ Vector3 RigidBody::computeCenterOfMass() const { const List& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); for (uint i=0; i < colliderEntities.size(); i++) { - Collider* collider = mWorld.mCollidersComponents.getCollider(colliderEntities[i]); + const uint colliderIndex = mWorld.mCollidersComponents.getEntityIndex(colliderEntities[i]); - const decimal colliderVolume = mWorld.mCollidersComponents.getCollisionShape(colliderEntities[i])->getVolume(); - const decimal colliderMassDensity = collider->getMaterial().getMassDensity(); + const decimal colliderVolume = mWorld.mCollidersComponents.mCollisionShapes[colliderIndex]->getVolume(); + const decimal colliderMassDensity = mWorld.mCollidersComponents.mMaterials[colliderIndex].getMassDensity(); const decimal colliderMass = colliderVolume * colliderMassDensity; totalMass += colliderMass; - centerOfMassLocal += colliderMass * mWorld.mCollidersComponents.getLocalToBodyTransform(colliderEntities[i]).getPosition(); + centerOfMassLocal += colliderMass * mWorld.mCollidersComponents.mLocalToBodyTransforms[colliderIndex].getPosition(); } if (totalMass > decimal(0.0)) { @@ -363,19 +363,19 @@ void RigidBody::computeMassAndInertiaTensorLocal(Vector3& inertiaTensorLocal, de const List& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); for (uint i=0; i < colliderEntities.size(); i++) { - Collider* collider = mWorld.mCollidersComponents.getCollider(colliderEntities[i]); + const uint colliderIndex = mWorld.mCollidersComponents.getEntityIndex(colliderEntities[i]); - const decimal colliderVolume = mWorld.mCollidersComponents.getCollisionShape(colliderEntities[i])->getVolume(); - const decimal colliderMassDensity = collider->getMaterial().getMassDensity(); + const decimal colliderVolume = mWorld.mCollidersComponents.mCollisionShapes[colliderIndex]->getVolume(); + const decimal colliderMassDensity = mWorld.mCollidersComponents.mMaterials[colliderIndex].getMassDensity(); const decimal colliderMass = colliderVolume * colliderMassDensity; totalMass += colliderMass; // Get the inertia tensor of the collider in its local-space - Vector3 shapeLocalInertiaTensor = collider->getCollisionShape()->getLocalInertiaTensor(colliderMass); + Vector3 shapeLocalInertiaTensor = mWorld.mCollidersComponents.mCollisionShapes[colliderIndex]->getLocalInertiaTensor(colliderMass); // Convert the collider inertia tensor into the local-space of the body - const Transform& shapeTransform = collider->getLocalToBodyTransform(); + const Transform& shapeTransform = mWorld.mCollidersComponents.mLocalToBodyTransforms[colliderIndex]; Matrix3x3 rotationMatrix = shapeTransform.getOrientation().getMatrix(); Matrix3x3 rotationMatrixTranspose = rotationMatrix.getTranspose(); rotationMatrixTranspose[0] *= shapeLocalInertiaTensor.x; @@ -437,10 +437,11 @@ void RigidBody::updateMassFromColliders() { // Compute the total mass of the body const List& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); for (uint i=0; i < colliderEntities.size(); i++) { - Collider* collider = mWorld.mCollidersComponents.getCollider(colliderEntities[i]); - const decimal colliderVolume = mWorld.mCollidersComponents.getCollisionShape(colliderEntities[i])->getVolume(); - const decimal colliderMassDensity = collider->getMaterial().getMassDensity(); + const uint colliderIndex = mWorld.mCollidersComponents.getEntityIndex(colliderEntities[i]); + + const decimal colliderVolume = mWorld.mCollidersComponents.mCollisionShapes[colliderIndex]->getVolume(); + const decimal colliderMassDensity = mWorld.mCollidersComponents.mMaterials[colliderIndex].getMassDensity(); const decimal colliderMass = colliderVolume * colliderMassDensity; @@ -589,8 +590,9 @@ Collider* RigidBody::addCollider(CollisionShape* collisionShape, const Transform // TODO : Maybe this method can directly returns an AABB collisionShape->getLocalBounds(localBoundsMin, localBoundsMax); const Transform localToWorldTransform = mWorld.mTransformComponents.getTransform(mEntity) * transform; + Material material(mWorld.mConfig.defaultFrictionCoefficient, mWorld.mConfig.defaultRollingRestistance, mWorld.mConfig.defaultBounciness); ColliderComponents::ColliderComponent colliderComponent(mEntity, collider, AABB(localBoundsMin, localBoundsMax), - transform, collisionShape, 0x0001, 0xFFFF, localToWorldTransform); + transform, collisionShape, 0x0001, 0xFFFF, localToWorldTransform, material); bool isSleeping = mWorld.mRigidBodyComponents.getIsSleeping(mEntity); mWorld.mCollidersComponents.addComponent(colliderEntity, isSleeping, colliderComponent); diff --git a/src/collision/Collider.cpp b/src/collision/Collider.cpp index 1ae22bdb..a5948ce1 100644 --- a/src/collision/Collider.cpp +++ b/src/collision/Collider.cpp @@ -42,8 +42,7 @@ using namespace reactphysics3d; */ Collider::Collider(Entity entity, CollisionBody* body, MemoryManager& memoryManager) :mMemoryManager(memoryManager), mEntity(entity), mBody(body), - mMaterial(body->mWorld.mConfig.defaultFrictionCoefficient, body->mWorld.mConfig.defaultRollingRestistance, - body->mWorld.mConfig.defaultBounciness), mUserData(nullptr) { + mUserData(nullptr) { } @@ -223,10 +222,10 @@ void Collider::setHasCollisionShapeChangedSize(bool hasCollisionShapeChangedSize */ void Collider::setMaterial(const Material& material) { - mMaterial = material; + mBody->mWorld.mCollidersComponents.setMaterial(mEntity, material); RP3D_LOG(mBody->mWorld.mConfig.worldName, Logger::Level::Information, Logger::Category::Collider, - "Collider " + std::to_string(mEntity.id) + ": Set Material" + mMaterial.to_string(), __FILE__, __LINE__); + "Collider " + std::to_string(mEntity.id) + ": Set Material" + material.to_string(), __FILE__, __LINE__); } // Return the local to world transform @@ -254,6 +253,14 @@ void Collider::setIsTrigger(bool isTrigger) const { mBody->mWorld.mCollidersComponents.setIsTrigger(mEntity, isTrigger); } +// Return a reference to the material properties of the collider +/** + * @return A reference to the material of the body + */ +Material& Collider::getMaterial() { + return mBody->mWorld.mCollidersComponents.getMaterial(mEntity); +} + #ifdef IS_RP3D_PROFILING_ENABLED diff --git a/src/components/ColliderComponents.cpp b/src/components/ColliderComponents.cpp index 3c296e58..550acd2f 100644 --- a/src/components/ColliderComponents.cpp +++ b/src/components/ColliderComponents.cpp @@ -38,7 +38,7 @@ ColliderComponents::ColliderComponents(MemoryAllocator& allocator) :Components(allocator, sizeof(Entity) + sizeof(Entity) + sizeof(Collider*) + sizeof(int32) + sizeof(Transform) + sizeof(CollisionShape*) + sizeof(unsigned short) + sizeof(unsigned short) + sizeof(Transform) + sizeof(List) + sizeof(bool) + - sizeof(bool)) { + sizeof(bool) + sizeof(Material)) { // Allocate memory for the components data allocate(INIT_NB_ALLOCATED_COMPONENTS); @@ -69,6 +69,7 @@ void ColliderComponents::allocate(uint32 nbComponentsToAllocate) { List* newOverlappingPairs = reinterpret_cast*>(newLocalToWorldTransforms + nbComponentsToAllocate); bool* hasCollisionShapeChangedSize = reinterpret_cast(newOverlappingPairs + nbComponentsToAllocate); bool* isTrigger = reinterpret_cast(hasCollisionShapeChangedSize + nbComponentsToAllocate); + Material* materials = reinterpret_cast(isTrigger + nbComponentsToAllocate); // If there was already components before if (mNbComponents > 0) { @@ -86,6 +87,7 @@ void ColliderComponents::allocate(uint32 nbComponentsToAllocate) { memcpy(newOverlappingPairs, mOverlappingPairs, mNbComponents * sizeof(List)); memcpy(hasCollisionShapeChangedSize, mHasCollisionShapeChangedSize, mNbComponents * sizeof(bool)); memcpy(isTrigger, mIsTrigger, mNbComponents * sizeof(bool)); + memcpy(materials, mMaterials, mNbComponents * sizeof(Material)); // Deallocate previous memory mMemoryAllocator.release(mBuffer, mNbAllocatedComponents * mComponentDataSize); @@ -105,6 +107,7 @@ void ColliderComponents::allocate(uint32 nbComponentsToAllocate) { mOverlappingPairs = newOverlappingPairs; mHasCollisionShapeChangedSize = hasCollisionShapeChangedSize; mIsTrigger = isTrigger; + mMaterials = materials; mNbAllocatedComponents = nbComponentsToAllocate; } @@ -128,6 +131,7 @@ void ColliderComponents::addComponent(Entity colliderEntity, bool isSleeping, co new (mOverlappingPairs + index) List(mMemoryAllocator); mHasCollisionShapeChangedSize[index] = false; mIsTrigger[index] = false; + mMaterials[index] = component.material; // Map the entity with the new component lookup index mMapEntityToComponentIndex.add(Pair(colliderEntity, index)); @@ -156,6 +160,7 @@ void ColliderComponents::moveComponentToIndex(uint32 srcIndex, uint32 destIndex) new (mOverlappingPairs + destIndex) List(mOverlappingPairs[srcIndex]); mHasCollisionShapeChangedSize[destIndex] = mHasCollisionShapeChangedSize[srcIndex]; mIsTrigger[destIndex] = mIsTrigger[srcIndex]; + mMaterials[destIndex] = mMaterials[srcIndex]; // Destroy the source component destroyComponent(srcIndex); @@ -184,6 +189,7 @@ void ColliderComponents::swapComponents(uint32 index1, uint32 index2) { List overlappingPairs = mOverlappingPairs[index1]; bool hasCollisionShapeChangedSize = mHasCollisionShapeChangedSize[index1]; bool isTrigger = mIsTrigger[index1]; + Material material = mMaterials[index1]; // Destroy component 1 destroyComponent(index1); @@ -203,6 +209,7 @@ void ColliderComponents::swapComponents(uint32 index1, uint32 index2) { new (mOverlappingPairs + index2) List(overlappingPairs); mHasCollisionShapeChangedSize[index2] = hasCollisionShapeChangedSize; mIsTrigger[index2] = isTrigger; + mMaterials[index2] = material; // Update the entity to component index mapping mMapEntityToComponentIndex.add(Pair(colliderEntity1, index2)); @@ -228,4 +235,5 @@ void ColliderComponents::destroyComponent(uint32 index) { mCollisionShapes[index] = nullptr; mLocalToWorldTransforms[index].~Transform(); mOverlappingPairs[index].~List(); + mMaterials[index].~Material(); } diff --git a/src/systems/ContactSolverSystem.cpp b/src/systems/ContactSolverSystem.cpp index 27f74cf6..cfa69713 100644 --- a/src/systems/ContactSolverSystem.cpp +++ b/src/systems/ContactSolverSystem.cpp @@ -34,6 +34,7 @@ #include #include #include +#include using namespace reactphysics3d; using namespace std; @@ -70,8 +71,8 @@ void ContactSolverSystem::init(List* contactManifolds, Listsize(); - uint nbContactPoints = mAllContactPoints->size(); + const uint nbContactManifolds = mAllContactManifolds->size(); + const uint nbContactPoints = mAllContactPoints->size(); mNbContactManifolds = 0; mNbContactPoints = 0; @@ -135,9 +136,8 @@ void ContactSolverSystem::initializeForIsland(uint islandIndex) { assert(!mBodyComponents.getIsEntityDisabled(externalManifold.bodyEntity1)); assert(!mBodyComponents.getIsEntityDisabled(externalManifold.bodyEntity2)); - // TODO OPTI : Do not use pointers to colliders here, maybe we call totally remove those pointers - Collider* collider1 = mColliderComponents.getCollider(externalManifold.colliderEntity1); - Collider* collider2 = mColliderComponents.getCollider(externalManifold.colliderEntity2); + const uint collider1Index = mColliderComponents.getEntityIndex(externalManifold.colliderEntity1); + const uint collider2Index = mColliderComponents.getEntityIndex(externalManifold.colliderEntity2); // Get the position of the two bodies const Vector3& x1 = mRigidBodyComponents.mCentersOfMassWorld[rigidBodyIndex1]; @@ -152,10 +152,8 @@ void ContactSolverSystem::initializeForIsland(uint islandIndex) { mContactConstraints[mNbContactManifolds].massInverseBody1 = mRigidBodyComponents.mInverseMasses[rigidBodyIndex1]; mContactConstraints[mNbContactManifolds].massInverseBody2 = mRigidBodyComponents.mInverseMasses[rigidBodyIndex2]; mContactConstraints[mNbContactManifolds].nbContacts = externalManifold.nbContactPoints; - // TODO OPTI : Do not compute this using colliders pointers - mContactConstraints[mNbContactManifolds].frictionCoefficient = computeMixedFrictionCoefficient(collider1, collider2); - // TODO OPTI : Do not compute this using colliders pointers - mContactConstraints[mNbContactManifolds].rollingResistanceFactor = computeMixedRollingResistance(collider1, collider2); + mContactConstraints[mNbContactManifolds].frictionCoefficient = computeMixedFrictionCoefficient(mColliderComponents.mMaterials[collider1Index], mColliderComponents.mMaterials[collider2Index]); + mContactConstraints[mNbContactManifolds].rollingResistanceFactor = computeMixedRollingResistance(mColliderComponents.mMaterials[collider1Index], mColliderComponents.mMaterials[collider2Index]); mContactConstraints[mNbContactManifolds].externalContactManifold = &externalManifold; mContactConstraints[mNbContactManifolds].normal.setToZero(); mContactConstraints[mNbContactManifolds].frictionPointBody1.setToZero(); @@ -167,9 +165,8 @@ void ContactSolverSystem::initializeForIsland(uint islandIndex) { const Vector3& v2 = mRigidBodyComponents.mLinearVelocities[rigidBodyIndex2]; const Vector3& w2 = mRigidBodyComponents.mAngularVelocities[rigidBodyIndex2]; - // TODO OPTI : Maybe use collider index here - const Transform& collider1LocalToWorldTransform = mColliderComponents.getLocalToWorldTransform(externalManifold.colliderEntity1); - const Transform& collider2LocalToWorldTransform = mColliderComponents.getLocalToWorldTransform(externalManifold.colliderEntity2); + const Transform& collider1LocalToWorldTransform = mColliderComponents.mLocalToWorldTransforms[collider1Index]; + const Transform& collider2LocalToWorldTransform = mColliderComponents.mLocalToWorldTransforms[collider2Index]; // For each contact point of the contact manifold assert(externalManifold.nbContactPoints > 0); @@ -179,13 +176,14 @@ void ContactSolverSystem::initializeForIsland(uint islandIndex) { ContactPoint& externalContact = (*mAllContactPoints)[c]; + new (mContactPoints + mNbContactPoints) ContactPointSolver(); + mContactPoints[mNbContactPoints].externalContact = &externalContact; + mContactPoints[mNbContactPoints].normal = externalContact.getNormal(); + // Get the contact point on the two bodies const Vector3 p1 = collider1LocalToWorldTransform * externalContact.getLocalPointOnShape1(); const Vector3 p2 = collider2LocalToWorldTransform * externalContact.getLocalPointOnShape2(); - new (mContactPoints + mNbContactPoints) ContactPointSolver(); - mContactPoints[mNbContactPoints].externalContact = &externalContact; - mContactPoints[mNbContactPoints].normal = externalContact.getNormal(); mContactPoints[mNbContactPoints].r1.x = p1.x - x1.x; mContactPoints[mNbContactPoints].r1.y = p1.y - x1.y; mContactPoints[mNbContactPoints].r1.z = p1.z - x1.z; @@ -247,7 +245,7 @@ void ContactSolverSystem::initializeForIsland(uint islandIndex) { decimal deltaVDotN = deltaV.x * mContactPoints[mNbContactPoints].normal.x + deltaV.y * mContactPoints[mNbContactPoints].normal.y + deltaV.z * mContactPoints[mNbContactPoints].normal.z; - const decimal restitutionFactor = computeMixedRestitutionFactor(collider1, collider2); + const decimal restitutionFactor = computeMixedRestitutionFactor(mColliderComponents.mMaterials[collider1Index], mColliderComponents.mMaterials[collider2Index]); if (deltaVDotN < -mRestitutionVelocityThreshold) { mContactPoints[mNbContactPoints].restitutionBias = restitutionFactor * deltaVDotN; } @@ -259,8 +257,8 @@ void ContactSolverSystem::initializeForIsland(uint islandIndex) { mNbContactPoints++; } - mContactConstraints[mNbContactManifolds].frictionPointBody1 /=static_cast(mContactConstraints[mNbContactManifolds].nbContacts); - mContactConstraints[mNbContactManifolds].frictionPointBody2 /=static_cast(mContactConstraints[mNbContactManifolds].nbContacts); + mContactConstraints[mNbContactManifolds].frictionPointBody1 /= static_cast(mContactConstraints[mNbContactManifolds].nbContacts); + mContactConstraints[mNbContactManifolds].frictionPointBody2 /= static_cast(mContactConstraints[mNbContactManifolds].nbContacts); mContactConstraints[mNbContactManifolds].r1Friction.x = mContactConstraints[mNbContactManifolds].frictionPointBody1.x - x1.x; mContactConstraints[mNbContactManifolds].r1Friction.y = mContactConstraints[mNbContactManifolds].frictionPointBody1.y - x1.y; mContactConstraints[mNbContactManifolds].r1Friction.z = mContactConstraints[mNbContactManifolds].frictionPointBody1.z - x1.z; @@ -547,7 +545,7 @@ void ContactSolverSystem::solve() { // Compute the bias "b" of the constraint decimal biasPenetrationDepth = 0.0; if (mContactPoints[contactPointIndex].penetrationDepth > SLOP) biasPenetrationDepth = -(beta/mTimeStep) * - max(0.0f, float(mContactPoints[contactPointIndex].penetrationDepth - SLOP)); + std::max(0.0f, float(mContactPoints[contactPointIndex].penetrationDepth - SLOP)); decimal b = biasPenetrationDepth + mContactPoints[contactPointIndex].restitutionBias; // Compute the Lagrange multiplier lambda @@ -814,27 +812,6 @@ void ContactSolverSystem::solve() { } } -// Compute the collision restitution factor from the restitution factor of each collider -decimal ContactSolverSystem::computeMixedRestitutionFactor(Collider* collider1, Collider* collider2) const { - decimal restitution1 = collider1->getMaterial().getBounciness(); - decimal restitution2 = collider2->getMaterial().getBounciness(); - - // Return the largest restitution factor - return (restitution1 > restitution2) ? restitution1 : restitution2; -} - -// Compute the mixed friction coefficient from the friction coefficient of each collider -decimal ContactSolverSystem::computeMixedFrictionCoefficient(Collider* collider1, Collider* collider2) const { - - // Use the geometric mean to compute the mixed friction coefficient - return collider1->getMaterial().getFrictionCoefficientSqrt() * collider2->getMaterial().getFrictionCoefficientSqrt(); -} - -// Compute th mixed rolling resistance factor between two colliders -decimal ContactSolverSystem::computeMixedRollingResistance(Collider* collider1, Collider* collider2) const { - return decimal(0.5f) * (collider1->getMaterial().getRollingResistance() + collider2->getMaterial().getRollingResistance()); -} - // Store the computed impulses to use them to // warm start the solver at the next iteration void ContactSolverSystem::storeImpulses() { @@ -871,14 +848,14 @@ void ContactSolverSystem::computeFrictionVectors(const Vector3& deltaVelocity, C assert(contact.normal.length() > decimal(0.0)); // Compute the velocity difference vector in the tangential plane - Vector3 normalVelocity(deltaVelocity.x * contact.normal.x * contact.normal.x, + const Vector3 normalVelocity(deltaVelocity.x * contact.normal.x * contact.normal.x, deltaVelocity.y * contact.normal.y * contact.normal.y, deltaVelocity.z * contact.normal.z * contact.normal.z); - Vector3 tangentVelocity(deltaVelocity.x - normalVelocity.x, deltaVelocity.y - normalVelocity.y, + const Vector3 tangentVelocity(deltaVelocity.x - normalVelocity.x, deltaVelocity.y - normalVelocity.y, deltaVelocity.z - normalVelocity.z); // If the velocty difference in the tangential plane is not zero - decimal lengthTangentVelocity = tangentVelocity.length(); + const decimal lengthTangentVelocity = tangentVelocity.length(); if (lengthTangentVelocity > MACHINE_EPSILON) { // Compute the first friction vector in the direction of the tangent @@ -893,5 +870,5 @@ void ContactSolverSystem::computeFrictionVectors(const Vector3& deltaVelocity, C // The second friction vector is computed by the cross product of the first // friction vector and the contact normal - contact.frictionVector2 = contact.normal.cross(contact.frictionVector1).getUnit(); + contact.frictionVector2 = contact.normal.cross(contact.frictionVector1); } From 012667780890c0c2cdc411d5b4ca928df2689882 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Wed, 29 Jul 2020 00:24:52 +0200 Subject: [PATCH 15/74] Fix issue --- include/reactphysics3d/collision/ContactManifold.h | 8 +------- src/collision/ContactManifold.cpp | 4 ++-- src/systems/CollisionDetectionSystem.cpp | 10 ++++------ src/systems/ContactSolverSystem.cpp | 4 ++-- 4 files changed, 9 insertions(+), 17 deletions(-) diff --git a/include/reactphysics3d/collision/ContactManifold.h b/include/reactphysics3d/collision/ContactManifold.h index cdd95e58..3dfeb0df 100644 --- a/include/reactphysics3d/collision/ContactManifold.h +++ b/include/reactphysics3d/collision/ContactManifold.h @@ -101,19 +101,13 @@ class ContactManifold { /// True if the contact manifold has already been added into an island bool isAlreadyInIsland; - /// Index of the first body of the manifold in the mRigidBodyComponents array (only if body 1 is a RigidBody) - uint32 rigidBody1Index; - - /// Index of the second body of the manifold in the mRigidBodyComponents array (only if body 2 is a RigidBody) - uint32 rigidBody2Index; - public: // -------------------- Methods -------------------- // /// Constructor ContactManifold(Entity bodyEntity1, Entity bodyEntity2, Entity colliderEntity1, Entity colliderEntity2, - uint contactPointsIndex, uint8 nbContactPoints, uint32 rigidBody1Index, uint32 rigidBody2Index); + uint contactPointsIndex, uint8 nbContactPoints); // -------------------- Friendship -------------------- // diff --git a/src/collision/ContactManifold.cpp b/src/collision/ContactManifold.cpp index cabdace2..46f886de 100644 --- a/src/collision/ContactManifold.cpp +++ b/src/collision/ContactManifold.cpp @@ -32,9 +32,9 @@ using namespace reactphysics3d; // Constructor ContactManifold::ContactManifold(Entity bodyEntity1, Entity bodyEntity2, Entity colliderEntity1, Entity colliderEntity2, - uint contactPointsIndex, uint8 nbContactPoints, uint32 rigidBody1Index, uint32 rigidBody2Index) + uint contactPointsIndex, uint8 nbContactPoints) :contactPointsIndex(contactPointsIndex), bodyEntity1(bodyEntity1), bodyEntity2(bodyEntity2), colliderEntity1(colliderEntity1), colliderEntity2(colliderEntity2), nbContactPoints(nbContactPoints), frictionImpulse1(0), frictionImpulse2(0), - frictionTwistImpulse(0), isAlreadyInIsland(false), rigidBody1Index(rigidBody1Index), rigidBody2Index(rigidBody2Index) { + frictionTwistImpulse(0), isAlreadyInIsland(false) { } diff --git a/src/systems/CollisionDetectionSystem.cpp b/src/systems/CollisionDetectionSystem.cpp index e474dfcd..3eab8488 100644 --- a/src/systems/CollisionDetectionSystem.cpp +++ b/src/systems/CollisionDetectionSystem.cpp @@ -732,12 +732,10 @@ void CollisionDetectionSystem::createContacts() { contactPair.contactPointsIndex = mCurrentContactPoints->size(); // Add the associated contact pair to both bodies of the pair (used to create islands later) - uint32 rigidBody1Index = 0; - uint32 rigidBody2Index = 0; - if (mRigidBodyComponents.hasComponentGetIndex(contactPair.body1Entity, rigidBody1Index)) { + if (mRigidBodyComponents.hasComponent(contactPair.body1Entity)) { mRigidBodyComponents.addContacPair(contactPair.body1Entity, p); } - if (mRigidBodyComponents.hasComponentGetIndex(contactPair.body2Entity, rigidBody2Index)) { + if (mRigidBodyComponents.hasComponent(contactPair.body2Entity)) { mRigidBodyComponents.addContacPair(contactPair.body2Entity, p); } @@ -753,7 +751,7 @@ void CollisionDetectionSystem::createContacts() { // Create and add the contact manifold mCurrentContactManifolds->emplace(contactPair.body1Entity, contactPair.body2Entity, contactPair.collider1Entity, - contactPair.collider2Entity, contactPointsIndex, nbContactPoints, rigidBody1Index, rigidBody2Index); + contactPair.collider2Entity, contactPointsIndex, nbContactPoints); assert(potentialManifold.nbPotentialContactPoints > 0); @@ -837,7 +835,7 @@ void CollisionDetectionSystem::createSnapshotContacts(List& contact // Create and add the contact manifold contactManifolds.emplace(contactPair.body1Entity, contactPair.body2Entity, contactPair.collider1Entity, - contactPair.collider2Entity, contactPointsIndex, nbContactPoints, 0, 0); + contactPair.collider2Entity, contactPointsIndex, nbContactPoints); assert(potentialManifold.nbPotentialContactPoints > 0); diff --git a/src/systems/ContactSolverSystem.cpp b/src/systems/ContactSolverSystem.cpp index cfa69713..0dbb6649 100644 --- a/src/systems/ContactSolverSystem.cpp +++ b/src/systems/ContactSolverSystem.cpp @@ -127,8 +127,8 @@ void ContactSolverSystem::initializeForIsland(uint islandIndex) { assert(externalManifold.nbContactPoints > 0); - const uint rigidBodyIndex1 = externalManifold.rigidBody1Index; - const uint rigidBodyIndex2 = externalManifold.rigidBody2Index; + const uint rigidBodyIndex1 = mRigidBodyComponents.getEntityIndex(externalManifold.bodyEntity1); + const uint rigidBodyIndex2 = mRigidBodyComponents.getEntityIndex(externalManifold.bodyEntity2); // Get the two bodies of the contact assert(body1 != nullptr); From eeb5b07f35c002b453a509e81734c8acf3e31b16 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Wed, 29 Jul 2020 22:49:19 +0200 Subject: [PATCH 16/74] Remove rolling restistance constraint from contact solver. Angular damping has to be used instead --- CHANGELOG.md | 5 ++ .../collision/ContactManifold.h | 3 -- include/reactphysics3d/engine/Material.h | 33 +----------- include/reactphysics3d/engine/PhysicsWorld.h | 5 -- .../reactphysics3d/mathematics/Matrix3x3.h | 3 -- .../systems/ContactSolverSystem.h | 17 ------ src/body/CollisionBody.cpp | 2 +- src/body/RigidBody.cpp | 2 +- src/engine/Material.cpp | 5 +- src/mathematics/Matrix3x3.cpp | 24 --------- src/systems/CollisionDetectionSystem.cpp | 1 - src/systems/ContactSolverSystem.cpp | 54 ------------------- .../scenes/concavemesh/ConcaveMeshScene.cpp | 5 -- testbed/scenes/cubestack/CubeStackScene.cpp | 2 + .../scenes/heightfield/HeightFieldScene.cpp | 5 -- testbed/scenes/pile/PileScene.cpp | 5 -- 16 files changed, 12 insertions(+), 159 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fe03754..add9c854 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,8 +14,13 @@ do not hesitate to take a look at the user manual. ### Changed + - Rolling resistance constraint is not solved anymore in the solver. Angular damping needs to be used instead to simulate this. + ### Removed + - Method Material::getRollingResistance() has been removed (angular damping has to be used instead of rolling resistance) + - Method Material::setRollingResistance() has been removed (angular damping has to be used instead of rolling resistance) + ### Fixed - Issue with concave vs convex shape collision detection has been fixed diff --git a/include/reactphysics3d/collision/ContactManifold.h b/include/reactphysics3d/collision/ContactManifold.h index 3dfeb0df..b76906da 100644 --- a/include/reactphysics3d/collision/ContactManifold.h +++ b/include/reactphysics3d/collision/ContactManifold.h @@ -95,9 +95,6 @@ class ContactManifold { /// Twist friction constraint accumulated impulse decimal frictionTwistImpulse; - /// Accumulated rolling resistance impulse - Vector3 rollingResistanceImpulse; - /// True if the contact manifold has already been added into an island bool isAlreadyInIsland; diff --git a/include/reactphysics3d/engine/Material.h b/include/reactphysics3d/engine/Material.h index a8462c20..e15d2532 100644 --- a/include/reactphysics3d/engine/Material.h +++ b/include/reactphysics3d/engine/Material.h @@ -48,9 +48,6 @@ class Material { /// Square root of the friction coefficient decimal mFrictionCoefficientSqrt; - /// Rolling resistance factor (positive value) - decimal mRollingResistance; - /// Bounciness during collisions (between 0 and 1) where 1 is for a very bouncy body decimal mBounciness; @@ -60,7 +57,7 @@ class Material { // -------------------- Methods -------------------- // /// Constructor - Material(decimal frictionCoefficient, decimal rollingResistance, decimal bounciness, decimal massDensity = decimal(1.0)); + Material(decimal frictionCoefficient, decimal bounciness, decimal massDensity = decimal(1.0)); public : @@ -81,12 +78,6 @@ class Material { /// Return the square root friction coefficient decimal getFrictionCoefficientSqrt() const; - /// Return the rolling resistance factor - decimal getRollingResistance() const; - - /// Set the rolling resistance factor - void setRollingResistance(decimal rollingResistance); - /// Return the mass density of the collider decimal getMassDensity() const; @@ -146,27 +137,6 @@ RP3D_FORCE_INLINE decimal Material::getFrictionCoefficientSqrt() const { return mFrictionCoefficientSqrt; } -// Return the rolling resistance factor. If this value is larger than zero, -// it will be used to slow down the body when it is rolling -// against another body. -/** - * @return The rolling resistance factor (positive value) - */ -RP3D_FORCE_INLINE decimal Material::getRollingResistance() const { - return mRollingResistance; -} - -// Set the rolling resistance factor. If this value is larger than zero, -// it will be used to slow down the body when it is rolling -// against another body. -/** - * @param rollingResistance The rolling resistance factor - */ -RP3D_FORCE_INLINE void Material::setRollingResistance(decimal rollingResistance) { - assert(rollingResistance >= 0); - mRollingResistance = rollingResistance; -} - // Return the mass density of the collider RP3D_FORCE_INLINE decimal Material::getMassDensity() const { return mMassDensity; @@ -186,7 +156,6 @@ RP3D_FORCE_INLINE std::string Material::to_string() const { std::stringstream ss; ss << "frictionCoefficient=" << (mFrictionCoefficientSqrt * mFrictionCoefficientSqrt) << std::endl; - ss << "rollingResistance=" << mRollingResistance << std::endl; ss << "bounciness=" << mBounciness << std::endl; return ss.str(); diff --git a/include/reactphysics3d/engine/PhysicsWorld.h b/include/reactphysics3d/engine/PhysicsWorld.h index ea507ef8..e2618959 100644 --- a/include/reactphysics3d/engine/PhysicsWorld.h +++ b/include/reactphysics3d/engine/PhysicsWorld.h @@ -93,9 +93,6 @@ class PhysicsWorld { /// Velocity threshold for contact velocity restitution decimal restitutionVelocityThreshold; - /// Default rolling resistance - decimal defaultRollingRestistance; - /// True if the sleeping technique is enabled bool isSleepingEnabled; @@ -129,7 +126,6 @@ class PhysicsWorld { defaultFrictionCoefficient = decimal(0.3); defaultBounciness = decimal(0.5); restitutionVelocityThreshold = decimal(0.5); - defaultRollingRestistance = decimal(0.0); isSleepingEnabled = true; defaultVelocitySolverNbIterations = 10; defaultPositionSolverNbIterations = 5; @@ -152,7 +148,6 @@ class PhysicsWorld { ss << "defaultFrictionCoefficient=" << defaultFrictionCoefficient << std::endl; ss << "defaultBounciness=" << defaultBounciness << std::endl; ss << "restitutionVelocityThreshold=" << restitutionVelocityThreshold << std::endl; - ss << "defaultRollingRestistance=" << defaultRollingRestistance << std::endl; ss << "isSleepingEnabled=" << isSleepingEnabled << std::endl; ss << "defaultVelocitySolverNbIterations=" << defaultVelocitySolverNbIterations << std::endl; ss << "defaultPositionSolverNbIterations=" << defaultPositionSolverNbIterations << std::endl; diff --git a/include/reactphysics3d/mathematics/Matrix3x3.h b/include/reactphysics3d/mathematics/Matrix3x3.h index 6d14d993..575adfc4 100644 --- a/include/reactphysics3d/mathematics/Matrix3x3.h +++ b/include/reactphysics3d/mathematics/Matrix3x3.h @@ -95,9 +95,6 @@ class Matrix3x3 { /// Return the inverse matrix Matrix3x3 getInverse() const; - /// Return the inverse matrix - Matrix3x3 getInverseWithDeterminant(decimal determinant) const; - /// Return the matrix with absolute values Matrix3x3 getAbsoluteMatrix() const; diff --git a/include/reactphysics3d/systems/ContactSolverSystem.h b/include/reactphysics3d/systems/ContactSolverSystem.h index 11960b68..8c484328 100644 --- a/include/reactphysics3d/systems/ContactSolverSystem.h +++ b/include/reactphysics3d/systems/ContactSolverSystem.h @@ -199,9 +199,6 @@ class ContactSolverSystem { /// Mix friction coefficient for the two bodies decimal frictionCoefficient; - /// Rolling resistance factor between the two bodies - decimal rollingResistanceFactor; - // - Variables used when friction constraints are apply at the center of the manifold-// /// Average normal vector of the contact manifold @@ -240,9 +237,6 @@ class ContactSolverSystem { /// Matrix K for the twist friction constraint decimal inverseTwistFrictionMass; - /// Matrix K for the rolling resistance constraint - Matrix3x3 inverseRollingResistance; - /// First friction direction at contact manifold center Vector3 frictionVector1; @@ -264,9 +258,6 @@ class ContactSolverSystem { /// Twist friction impulse at contact manifold center decimal frictionTwistImpulse; - /// Rolling resistance impulse - Vector3 rollingResistanceImpulse; - /// Number of contact points int8 nbContacts; }; @@ -344,9 +335,6 @@ class ContactSolverSystem { /// Compute the mixed friction coefficient from the friction coefficient of each collider decimal computeMixedFrictionCoefficient(const Material &material1, const Material &material2) const; - /// Compute th mixed rolling resistance factor between two colliders - decimal computeMixedRollingResistance(const Material& material1, const Material& material2) const; - /// Compute the two unit orthogonal vectors "t1" and "t2" that span the tangential friction /// plane for a contact manifold. The two vectors have to be /// such that : t1 x t2 = contactNormal. @@ -424,11 +412,6 @@ RP3D_FORCE_INLINE decimal ContactSolverSystem::computeMixedFrictionCoefficient(c return material1.getFrictionCoefficientSqrt() * material2.getFrictionCoefficientSqrt(); } -// Compute th mixed rolling resistance factor between two colliders -RP3D_FORCE_INLINE decimal ContactSolverSystem::computeMixedRollingResistance(const Material& material1, const Material& material2) const { - return decimal(0.5f) * (material1.getRollingResistance() + material2.getRollingResistance()); -} - #ifdef IS_RP3D_PROFILING_ENABLED // Set the profiler diff --git a/src/body/CollisionBody.cpp b/src/body/CollisionBody.cpp index 5c9c9610..4f065264 100644 --- a/src/body/CollisionBody.cpp +++ b/src/body/CollisionBody.cpp @@ -89,7 +89,7 @@ Collider* CollisionBody::addCollider(CollisionShape* collisionShape, const Trans // TODO : Maybe this method can directly returns an AABB collisionShape->getLocalBounds(localBoundsMin, localBoundsMax); const Transform localToWorldTransform = mWorld.mTransformComponents.getTransform(mEntity) * transform; - Material material(mWorld.mConfig.defaultFrictionCoefficient, mWorld.mConfig.defaultRollingRestistance, mWorld.mConfig.defaultBounciness); + Material material(mWorld.mConfig.defaultFrictionCoefficient, mWorld.mConfig.defaultBounciness); ColliderComponents::ColliderComponent colliderComponent(mEntity, collider, AABB(localBoundsMin, localBoundsMax), transform, collisionShape, 0x0001, 0xFFFF, localToWorldTransform, material); bool isActive = mWorld.mCollisionBodyComponents.getIsActive(mEntity); diff --git a/src/body/RigidBody.cpp b/src/body/RigidBody.cpp index 4e884efc..8b08cac2 100644 --- a/src/body/RigidBody.cpp +++ b/src/body/RigidBody.cpp @@ -590,7 +590,7 @@ Collider* RigidBody::addCollider(CollisionShape* collisionShape, const Transform // TODO : Maybe this method can directly returns an AABB collisionShape->getLocalBounds(localBoundsMin, localBoundsMax); const Transform localToWorldTransform = mWorld.mTransformComponents.getTransform(mEntity) * transform; - Material material(mWorld.mConfig.defaultFrictionCoefficient, mWorld.mConfig.defaultRollingRestistance, mWorld.mConfig.defaultBounciness); + Material material(mWorld.mConfig.defaultFrictionCoefficient, mWorld.mConfig.defaultBounciness); ColliderComponents::ColliderComponent colliderComponent(mEntity, collider, AABB(localBoundsMin, localBoundsMax), transform, collisionShape, 0x0001, 0xFFFF, localToWorldTransform, material); bool isSleeping = mWorld.mRigidBodyComponents.getIsSleeping(mEntity); diff --git a/src/engine/Material.cpp b/src/engine/Material.cpp index 3c73b11e..dee9c015 100644 --- a/src/engine/Material.cpp +++ b/src/engine/Material.cpp @@ -29,8 +29,7 @@ using namespace reactphysics3d; // Constructor -Material::Material(decimal frictionCoefficient, decimal rollingResistance, decimal bounciness, decimal massDensity) - : mFrictionCoefficientSqrt(std::sqrt(frictionCoefficient)), - mRollingResistance(rollingResistance), mBounciness(bounciness), mMassDensity(massDensity) { +Material::Material(decimal frictionCoefficient, decimal bounciness, decimal massDensity) + : mFrictionCoefficientSqrt(std::sqrt(frictionCoefficient)), mBounciness(bounciness), mMassDensity(massDensity) { } diff --git a/src/mathematics/Matrix3x3.cpp b/src/mathematics/Matrix3x3.cpp index 16502f0d..18f96a1b 100644 --- a/src/mathematics/Matrix3x3.cpp +++ b/src/mathematics/Matrix3x3.cpp @@ -65,27 +65,3 @@ Matrix3x3 Matrix3x3::getInverse() const { // Return the inverse matrix return (invDeterminant * tempMatrix); } - -// Return the inverse matrix -Matrix3x3 Matrix3x3::getInverseWithDeterminant(decimal determinant) const { - - // Check if the determinant is equal to zero - assert(determinant != decimal(0.0)); - - decimal invDeterminant = decimal(1.0) / determinant; - - Matrix3x3 tempMatrix((mRows[1][1]*mRows[2][2]-mRows[2][1]*mRows[1][2]), - -(mRows[0][1]*mRows[2][2]-mRows[2][1]*mRows[0][2]), - (mRows[0][1]*mRows[1][2]-mRows[0][2]*mRows[1][1]), - -(mRows[1][0]*mRows[2][2]-mRows[2][0]*mRows[1][2]), - (mRows[0][0]*mRows[2][2]-mRows[2][0]*mRows[0][2]), - -(mRows[0][0]*mRows[1][2]-mRows[1][0]*mRows[0][2]), - (mRows[1][0]*mRows[2][1]-mRows[2][0]*mRows[1][1]), - -(mRows[0][0]*mRows[2][1]-mRows[2][0]*mRows[0][1]), - (mRows[0][0]*mRows[1][1]-mRows[0][1]*mRows[1][0])); - - // Return the inverse matrix - return (invDeterminant * tempMatrix); -} - - diff --git a/src/systems/CollisionDetectionSystem.cpp b/src/systems/CollisionDetectionSystem.cpp index 3eab8488..b7de88b0 100644 --- a/src/systems/CollisionDetectionSystem.cpp +++ b/src/systems/CollisionDetectionSystem.cpp @@ -903,7 +903,6 @@ void CollisionDetectionSystem::initContactsWithPreviousOnes() { currentContactManifold.frictionImpulse1 = previousContactManifold.frictionImpulse1; currentContactManifold.frictionImpulse2 = previousContactManifold.frictionImpulse2; currentContactManifold.frictionTwistImpulse = previousContactManifold.frictionTwistImpulse; - currentContactManifold.rollingResistanceImpulse = previousContactManifold.rollingResistanceImpulse; break; } diff --git a/src/systems/ContactSolverSystem.cpp b/src/systems/ContactSolverSystem.cpp index 0dbb6649..a3d4936d 100644 --- a/src/systems/ContactSolverSystem.cpp +++ b/src/systems/ContactSolverSystem.cpp @@ -153,7 +153,6 @@ void ContactSolverSystem::initializeForIsland(uint islandIndex) { mContactConstraints[mNbContactManifolds].massInverseBody2 = mRigidBodyComponents.mInverseMasses[rigidBodyIndex2]; mContactConstraints[mNbContactManifolds].nbContacts = externalManifold.nbContactPoints; mContactConstraints[mNbContactManifolds].frictionCoefficient = computeMixedFrictionCoefficient(mColliderComponents.mMaterials[collider1Index], mColliderComponents.mMaterials[collider2Index]); - mContactConstraints[mNbContactManifolds].rollingResistanceFactor = computeMixedRollingResistance(mColliderComponents.mMaterials[collider1Index], mColliderComponents.mMaterials[collider2Index]); mContactConstraints[mNbContactManifolds].externalContactManifold = &externalManifold; mContactConstraints[mNbContactManifolds].normal.setToZero(); mContactConstraints[mNbContactManifolds].frictionPointBody1.setToZero(); @@ -273,24 +272,6 @@ void ContactSolverSystem::initializeForIsland(uint islandIndex) { mContactConstraints[mNbContactManifolds].friction2Impulse = externalManifold.frictionImpulse2; mContactConstraints[mNbContactManifolds].frictionTwistImpulse = externalManifold.frictionTwistImpulse; - // Compute the inverse K matrix for the rolling resistance constraint - const bool isBody1DynamicType = mRigidBodyComponents.mBodyTypes[rigidBodyIndex1] == BodyType::DYNAMIC; - const bool isBody2DynamicType = mRigidBodyComponents.mBodyTypes[rigidBodyIndex2] == BodyType::DYNAMIC; - mContactConstraints[mNbContactManifolds].inverseRollingResistance.setToZero(); - if (mContactConstraints[mNbContactManifolds].rollingResistanceFactor > 0 && (isBody1DynamicType || isBody2DynamicType)) { - - mContactConstraints[mNbContactManifolds].inverseRollingResistance = mContactConstraints[mNbContactManifolds].inverseInertiaTensorBody1 + mContactConstraints[mNbContactManifolds].inverseInertiaTensorBody2; - const decimal det = mContactConstraints[mNbContactManifolds].inverseRollingResistance.getDeterminant(); - - // If the matrix is not inversible - if (approxEqual(det, decimal(0.0))) { - mContactConstraints[mNbContactManifolds].inverseRollingResistance.setToZero(); - } - else { - mContactConstraints[mNbContactManifolds].inverseRollingResistance = mContactConstraints[mNbContactManifolds].inverseRollingResistance.getInverseWithDeterminant(det); - } - } - mContactConstraints[mNbContactManifolds].normal.normalize(); // deltaVFrictionPoint = v2 + w2.cross(mContactConstraints[mNbContactManifolds].r2Friction) - @@ -479,11 +460,6 @@ void ContactSolverSystem::warmStart() { // Update the velocities of the body 2 by applying the impulse P mRigidBodyComponents.mConstrainedAngularVelocities[rigidBody2Index] += mContactConstraints[c].inverseInertiaTensorBody2 * angularImpulseBody2; - // ------ Rolling resistance at the center of the contact manifold ------ // - - // Compute the impulse P = J^T * lambda - angularImpulseBody2 = mContactConstraints[c].rollingResistanceImpulse; - // Update the velocities of the body 1 by applying the impulse P mRigidBodyComponents.mConstrainedAngularVelocities[rigidBody1Index] -= mContactConstraints[c].inverseInertiaTensorBody1 * angularImpulseBody2; @@ -496,7 +472,6 @@ void ContactSolverSystem::warmStart() { mContactConstraints[c].friction1Impulse = 0.0; mContactConstraints[c].friction2Impulse = 0.0; mContactConstraints[c].frictionTwistImpulse = 0.0; - mContactConstraints[c].rollingResistanceImpulse.setToZero(); } } } @@ -781,34 +756,6 @@ void ContactSolverSystem::solve() { mRigidBodyComponents.mConstrainedAngularVelocities[rigidBody2Index].x += angularVelocity2.x; mRigidBodyComponents.mConstrainedAngularVelocities[rigidBody2Index].y += angularVelocity2.y; mRigidBodyComponents.mConstrainedAngularVelocities[rigidBody2Index].z += angularVelocity2.z; - - // --------- Rolling resistance constraint at the center of the contact manifold --------- // - - if (mContactConstraints[c].rollingResistanceFactor > 0) { - - // Compute J*v - const Vector3 JvRolling = w2 - w1; - - // Compute the Lagrange multiplier lambda - Vector3 deltaLambdaRolling = mContactConstraints[c].inverseRollingResistance * (-JvRolling); - decimal rollingLimit = mContactConstraints[c].rollingResistanceFactor * sumPenetrationImpulse; - Vector3 lambdaTempRolling = mContactConstraints[c].rollingResistanceImpulse; - mContactConstraints[c].rollingResistanceImpulse = clamp(mContactConstraints[c].rollingResistanceImpulse + - deltaLambdaRolling, rollingLimit); - deltaLambdaRolling = mContactConstraints[c].rollingResistanceImpulse - lambdaTempRolling; - - // Update the velocities of the body 1 by applying the impulse P - angularVelocity1 = mContactConstraints[c].inverseInertiaTensorBody1 * deltaLambdaRolling; - mRigidBodyComponents.mConstrainedAngularVelocities[rigidBody1Index].x -= angularVelocity1.x; - mRigidBodyComponents.mConstrainedAngularVelocities[rigidBody1Index].y -= angularVelocity1.y; - mRigidBodyComponents.mConstrainedAngularVelocities[rigidBody1Index].z -= angularVelocity1.z; - - // Update the velocities of the body 2 by applying the impulse P - angularVelocity2 = mContactConstraints[c].inverseInertiaTensorBody2 * deltaLambdaRolling; - mRigidBodyComponents.mConstrainedAngularVelocities[rigidBody2Index].x += angularVelocity2.x; - mRigidBodyComponents.mConstrainedAngularVelocities[rigidBody2Index].y += angularVelocity2.y; - mRigidBodyComponents.mConstrainedAngularVelocities[rigidBody2Index].z += angularVelocity2.z; - } } } @@ -833,7 +780,6 @@ void ContactSolverSystem::storeImpulses() { mContactConstraints[c].externalContactManifold->frictionImpulse1 = mContactConstraints[c].friction1Impulse; mContactConstraints[c].externalContactManifold->frictionImpulse2 = mContactConstraints[c].friction2Impulse; mContactConstraints[c].externalContactManifold->frictionTwistImpulse = mContactConstraints[c].frictionTwistImpulse; - mContactConstraints[c].externalContactManifold->rollingResistanceImpulse = mContactConstraints[c].rollingResistanceImpulse; mContactConstraints[c].externalContactManifold->frictionVector1 = mContactConstraints[c].frictionVector1; mContactConstraints[c].externalContactManifold->frictionVector2 = mContactConstraints[c].frictionVector2; } diff --git a/testbed/scenes/concavemesh/ConcaveMeshScene.cpp b/testbed/scenes/concavemesh/ConcaveMeshScene.cpp index e08ef540..c0c1b3e0 100644 --- a/testbed/scenes/concavemesh/ConcaveMeshScene.cpp +++ b/testbed/scenes/concavemesh/ConcaveMeshScene.cpp @@ -108,9 +108,6 @@ ConcaveMeshScene::ConcaveMeshScene(const std::string& name, EngineSettings& sett // Create a sphere and a corresponding rigid in the physics world Sphere* sphere = new Sphere(true, SPHERE_RADIUS, mPhysicsCommon, mPhysicsWorld, meshFolderPath); - // Add some rolling resistance - sphere->getCollider()->getMaterial().setRollingResistance(rp3d::decimal(0.08)); - // Set the box color sphere->setColor(mObjectColorDemo); sphere->setSleepingColor(mSleepingColorDemo); @@ -131,8 +128,6 @@ ConcaveMeshScene::ConcaveMeshScene(const std::string& name, EngineSettings& sett Capsule* capsule = new Capsule(true, CAPSULE_RADIUS, CAPSULE_HEIGHT, mPhysicsCommon, mPhysicsWorld, meshFolderPath); - capsule->getCollider()->getMaterial().setRollingResistance(rp3d::decimal(0.08)); - // Set the box color capsule->setColor(mObjectColorDemo); capsule->setSleepingColor(mSleepingColorDemo); diff --git a/testbed/scenes/cubestack/CubeStackScene.cpp b/testbed/scenes/cubestack/CubeStackScene.cpp index b45f4977..40b2b66d 100644 --- a/testbed/scenes/cubestack/CubeStackScene.cpp +++ b/testbed/scenes/cubestack/CubeStackScene.cpp @@ -145,6 +145,8 @@ void CubeStackScene::reset() { 0); box->setTransform(rp3d::Transform(position, rp3d::Quaternion::identity())); + box->getRigidBody()->setLinearVelocity(rp3d::Vector3::zero()); + box->getRigidBody()->setAngularVelocity(rp3d::Vector3::zero()); index++; } diff --git a/testbed/scenes/heightfield/HeightFieldScene.cpp b/testbed/scenes/heightfield/HeightFieldScene.cpp index 53ac20c7..4620ca54 100644 --- a/testbed/scenes/heightfield/HeightFieldScene.cpp +++ b/testbed/scenes/heightfield/HeightFieldScene.cpp @@ -107,9 +107,6 @@ HeightFieldScene::HeightFieldScene(const std::string& name, EngineSettings& sett // Create a sphere and a corresponding rigid in the physics world Sphere* sphere = new Sphere(true, SPHERE_RADIUS, mPhysicsCommon, mPhysicsWorld, meshFolderPath); - // Add some rolling resistance - sphere->getCollider()->getMaterial().setRollingResistance(0.08f); - // Set the box color sphere->setColor(mObjectColorDemo); sphere->setSleepingColor(mSleepingColorDemo); @@ -129,8 +126,6 @@ HeightFieldScene::HeightFieldScene(const std::string& name, EngineSettings& sett // Create a cylinder and a corresponding rigid in the physics world Capsule* capsule = new Capsule(true, CAPSULE_RADIUS, CAPSULE_HEIGHT, mPhysicsCommon, mPhysicsWorld, meshFolderPath); - capsule->getCollider()->getMaterial().setRollingResistance(0.08f); - // Set the box color capsule->setColor(mObjectColorDemo); capsule->setSleepingColor(mSleepingColorDemo); diff --git a/testbed/scenes/pile/PileScene.cpp b/testbed/scenes/pile/PileScene.cpp index 6698bc6e..636fe087 100644 --- a/testbed/scenes/pile/PileScene.cpp +++ b/testbed/scenes/pile/PileScene.cpp @@ -105,9 +105,6 @@ PileScene::PileScene(const std::string& name, EngineSettings& settings) // Create a sphere and a corresponding rigid in the physics world Sphere* sphere = new Sphere(true, SPHERE_RADIUS, mPhysicsCommon, mPhysicsWorld, meshFolderPath); - // Add some rolling resistance - sphere->getCollider()->getMaterial().setRollingResistance(rp3d::decimal(0.08)); - // Set the box color sphere->setColor(mObjectColorDemo); sphere->setSleepingColor(mSleepingColorDemo); @@ -128,8 +125,6 @@ PileScene::PileScene(const std::string& name, EngineSettings& settings) Capsule* capsule = new Capsule(true, CAPSULE_RADIUS, CAPSULE_HEIGHT, mPhysicsCommon, mPhysicsWorld, meshFolderPath); - capsule->getCollider()->getMaterial().setRollingResistance(rp3d::decimal(0.08f)); - // Set the box color capsule->setColor(mObjectColorDemo); capsule->setSleepingColor(mSleepingColorDemo); From 05e3b00689e80836f378b147c994b54a3429610f Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Thu, 6 Aug 2020 16:00:50 +0200 Subject: [PATCH 17/74] Remove mIsActive variable from OverlappingPairs --- include/reactphysics3d/body/RigidBody.h | 2 +- .../components/RigidBodyComponents.h | 1 + .../reactphysics3d/engine/OverlappingPairs.h | 17 --- .../mathematics/mathematics_functions.h | 6 +- src/body/RigidBody.cpp | 17 ++- .../narrowphase/GJK/GJKAlgorithm.cpp | 4 +- .../narrowphase/NarrowPhaseInfoBatch.cpp | 2 +- .../SphereVsConvexPolyhedronAlgorithm.cpp | 8 +- src/engine/OverlappingPairs.cpp | 43 +------ src/mathematics/mathematics_functions.cpp | 10 -- src/systems/CollisionDetectionSystem.cpp | 116 ++++++++++-------- src/systems/ContactSolverSystem.cpp | 2 - 12 files changed, 91 insertions(+), 137 deletions(-) diff --git a/include/reactphysics3d/body/RigidBody.h b/include/reactphysics3d/body/RigidBody.h index b2481e2b..bbc583b0 100644 --- a/include/reactphysics3d/body/RigidBody.h +++ b/include/reactphysics3d/body/RigidBody.h @@ -57,7 +57,7 @@ class RigidBody : public CollisionBody { void setIsSleeping(bool isSleeping); /// Update whether the current overlapping pairs where this body is involed are active or not - void updateOverlappingPairs(); + void resetOverlappingPairs(); /// Compute and return the local-space center of mass of the body using its colliders Vector3 computeCenterOfMass() const; diff --git a/include/reactphysics3d/components/RigidBodyComponents.h b/include/reactphysics3d/components/RigidBodyComponents.h index 7ceb9a1d..45d986e8 100644 --- a/include/reactphysics3d/components/RigidBodyComponents.h +++ b/include/reactphysics3d/components/RigidBodyComponents.h @@ -358,6 +358,7 @@ class RigidBodyComponents : public Components { friend class PhysicsWorld; friend class ContactSolverSystem; + friend class CollisionDetectionSystem; friend class SolveBallAndSocketJointSystem; friend class SolveFixedJointSystem; friend class SolveHingeJointSystem; diff --git a/include/reactphysics3d/engine/OverlappingPairs.h b/include/reactphysics3d/engine/OverlappingPairs.h index 9d65b3e0..206d43bc 100644 --- a/include/reactphysics3d/engine/OverlappingPairs.h +++ b/include/reactphysics3d/engine/OverlappingPairs.h @@ -166,9 +166,6 @@ class OverlappingPairs { /// True if we need to test if the convex vs convex overlapping pairs of shapes still overlap bool* mNeedToTestOverlap; - /// True if the overlapping pair is active (at least one body of the pair is active and not static) - bool* mIsActive; - /// Array with the pointer to the narrow-phase algorithm for each overlapping pair NarrowPhaseAlgorithmType* mNarrowPhaseAlgorithmType; @@ -263,9 +260,6 @@ class OverlappingPairs { /// Return the entity of the second collider Entity getCollider2(uint64 pairId) const; - /// Notify if a given pair is active or not - void setIsPairActive(uint64 pairId, bool isActive); - /// Return the index of a given overlapping pair in the internal array uint64 getPairIndex(uint64 pairId) const; @@ -278,9 +272,6 @@ class OverlappingPairs { /// Add a new last frame collision info if it does not exist for the given shapes already LastFrameCollisionInfo* addLastFrameInfoIfNecessary(uint64 pairIndex, uint32 shapeId1, uint32 shapeId2); - /// Update whether a given overlapping pair is active or not - void updateOverlappingPairIsActive(uint64 pairId); - /// Delete all the obsolete last frame collision info void clearObsoleteLastFrameCollisionInfos(); @@ -326,14 +317,6 @@ RP3D_FORCE_INLINE Entity OverlappingPairs::getCollider2(uint64 pairId) const { return mColliders2[mMapPairIdToPairIndex[pairId]]; } -// Notify if a given pair is active or not -RP3D_FORCE_INLINE void OverlappingPairs::setIsPairActive(uint64 pairId, bool isActive) { - - assert(mMapPairIdToPairIndex.containsKey(pairId)); - assert(mMapPairIdToPairIndex[pairId] < mNbPairs); - mIsActive[mMapPairIdToPairIndex[pairId]] = isActive; -} - // Return the index of a given overlapping pair in the internal array RP3D_FORCE_INLINE uint64 OverlappingPairs::getPairIndex(uint64 pairId) const { assert(mMapPairIdToPairIndex.containsKey(pairId)); diff --git a/include/reactphysics3d/mathematics/mathematics_functions.h b/include/reactphysics3d/mathematics/mathematics_functions.h index 74b7a86a..cd98140e 100755 --- a/include/reactphysics3d/mathematics/mathematics_functions.h +++ b/include/reactphysics3d/mathematics/mathematics_functions.h @@ -133,7 +133,11 @@ bool isPrimeNumber(int number); /// Here we assume that the two parameter numbers are sorted such that /// number1 = max(number1, number2) /// http://szudzik.com/ElegantPairing.pdf -uint64 pairNumbers(uint32 number1, uint32 number2); +RP3D_FORCE_INLINE uint64 pairNumbers(uint32 number1, uint32 number2) { + assert(number1 == std::max(number1, number2)); + return number1 * number1 + number1 + number2; +} + } diff --git a/src/body/RigidBody.cpp b/src/body/RigidBody.cpp index 8b08cac2..222a97d9 100644 --- a/src/body/RigidBody.cpp +++ b/src/body/RigidBody.cpp @@ -108,11 +108,7 @@ void RigidBody::setType(BodyType type) { setIsSleeping(false); // Update the active status of currently overlapping pairs - updateOverlappingPairs(); - - // Ask the broad-phase to test again the collision shapes of the body for collision - // detection (as if the body has moved) - askForBroadPhaseCollisionCheck(); + resetOverlappingPairs(); // Reset the force and torque on the body mWorld.mRigidBodyComponents.setExternalForce(mEntity, Vector3::zero()); @@ -856,7 +852,7 @@ void RigidBody::setIsSleeping(bool isSleeping) { mWorld.setBodyDisabled(mEntity, isSleeping); // Update the currently overlapping pairs - updateOverlappingPairs(); + resetOverlappingPairs(); if (isSleeping) { @@ -871,8 +867,8 @@ void RigidBody::setIsSleeping(bool isSleeping) { (isSleeping ? "true" : "false"), __FILE__, __LINE__); } -// Update whether the current overlapping pairs where this body is involed are active or not -void RigidBody::updateOverlappingPairs() { +// Remove all the overlapping pairs in which this body is involved and ask the broad-phase to recompute pairs in the next frame +void RigidBody::resetOverlappingPairs() { // For each collider of the body const List& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); @@ -883,9 +879,12 @@ void RigidBody::updateOverlappingPairs() { for (uint j=0; j < overlappingPairs.size(); j++) { - mWorld.mCollisionDetection.mOverlappingPairs.updateOverlappingPairIsActive(overlappingPairs[j]); + mWorld.mCollisionDetection.mOverlappingPairs.removePair(overlappingPairs[j]); } } + + // Make sure we recompute the overlapping pairs with this body in the next frame + askForBroadPhaseCollisionCheck(); } /// Compute the inverse of the inertia tensor in world coordinates. diff --git a/src/collision/narrowphase/GJK/GJKAlgorithm.cpp b/src/collision/narrowphase/GJK/GJKAlgorithm.cpp index f3d664a7..74a0dbaa 100644 --- a/src/collision/narrowphase/GJK/GJKAlgorithm.cpp +++ b/src/collision/narrowphase/GJK/GJKAlgorithm.cpp @@ -64,8 +64,8 @@ void GJKAlgorithm::testCollision(NarrowPhaseInfoBatch& narrowPhaseInfoBatch, uin decimal prevDistSquare; bool contactFound = false; - assert(narrowPhaseInfoBatch.narrowPhaseInfo[[batchIndex].collisionShape1->isConvex()); - assert(narrowPhaseInfoBatch.narrowPhaseInfo[[batchIndex].collisionShape2->isConvex()); + assert(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1->isConvex()); + assert(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape2->isConvex()); const ConvexShape* shape1 = static_cast(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1); const ConvexShape* shape2 = static_cast(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape2); diff --git a/src/collision/narrowphase/NarrowPhaseInfoBatch.cpp b/src/collision/narrowphase/NarrowPhaseInfoBatch.cpp index 3aef65e3..d0266741 100644 --- a/src/collision/narrowphase/NarrowPhaseInfoBatch.cpp +++ b/src/collision/narrowphase/NarrowPhaseInfoBatch.cpp @@ -55,7 +55,7 @@ void NarrowPhaseInfoBatch::clear() { // TODO OPTI : Better manage this for (uint i=0; i < narrowPhaseInfos.size(); i++) { - assert(narrowPhaseOutputInfos[i].nbContactPoints == 0); + assert(narrowPhaseInfos[i].nbContactPoints == 0); // Release the memory of the TriangleShape (this memory was allocated in the // MiddlePhaseTriangleCallback::testTriangle() method) diff --git a/src/collision/narrowphase/SphereVsConvexPolyhedronAlgorithm.cpp b/src/collision/narrowphase/SphereVsConvexPolyhedronAlgorithm.cpp index 6e732d12..7126b35f 100644 --- a/src/collision/narrowphase/SphereVsConvexPolyhedronAlgorithm.cpp +++ b/src/collision/narrowphase/SphereVsConvexPolyhedronAlgorithm.cpp @@ -57,10 +57,10 @@ bool SphereVsConvexPolyhedronAlgorithm::testCollision(NarrowPhaseInfoBatch& narr // For each item in the batch for (uint batchIndex = batchStartIndex; batchIndex < batchStartIndex + batchNbItems; batchIndex++) { - assert(narrowPhaseInfoBatch.narrowPhaseInfo[[batchIndex].collisionShape1->getType() == CollisionShapeType::CONVEX_POLYHEDRON || - narrowPhaseInfoBatch.narrowPhaseInfo[[batchIndex].collisionShape2->getType() == CollisionShapeType::CONVEX_POLYHEDRON); - assert(narrowPhaseInfoBatch.narrowPhaseInfo[[batchIndex].collisionShape1->getType() == CollisionShapeType::SPHERE || - narrowPhaseInfoBatch.narrowPhaseInfo[[batchIndex].collisionShape2->getType() == CollisionShapeType::SPHERE); + assert(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1->getType() == CollisionShapeType::CONVEX_POLYHEDRON || + narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape2->getType() == CollisionShapeType::CONVEX_POLYHEDRON); + assert(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1->getType() == CollisionShapeType::SPHERE || + narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape2->getType() == CollisionShapeType::SPHERE); // Get the last frame collision info LastFrameCollisionInfo* lastFrameCollisionInfo = narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].lastFrameCollisionInfo; diff --git a/src/engine/OverlappingPairs.cpp b/src/engine/OverlappingPairs.cpp index 2121143e..543a32ef 100644 --- a/src/engine/OverlappingPairs.cpp +++ b/src/engine/OverlappingPairs.cpp @@ -39,7 +39,7 @@ OverlappingPairs::OverlappingPairs(MemoryAllocator& persistentMemoryAllocator, M : mPersistentAllocator(persistentMemoryAllocator), mTempMemoryAllocator(temporaryMemoryAllocator), mNbPairs(0), mConcavePairsStartIndex(0), mPairDataSize(sizeof(uint64) + sizeof(int32) + sizeof(int32) + sizeof(Entity) + sizeof(Entity) + sizeof(Map) + - sizeof(bool) + sizeof(bool) + sizeof(NarrowPhaseAlgorithmType) + + sizeof(bool) + sizeof(NarrowPhaseAlgorithmType) + sizeof(bool) + sizeof(bool) + sizeof(bool)), mNbAllocatedPairs(0), mBuffer(nullptr), mMapPairIdToPairIndex(persistentMemoryAllocator), @@ -208,8 +208,7 @@ void OverlappingPairs::allocate(uint64 nbPairsToAllocate) { Entity* newColliders2 = reinterpret_cast(newColliders1 + nbPairsToAllocate); Map* newLastFrameCollisionInfos = reinterpret_cast*>(newColliders2 + nbPairsToAllocate); bool* newNeedToTestOverlap = reinterpret_cast(newLastFrameCollisionInfos + nbPairsToAllocate); - bool* newIsActive = reinterpret_cast(newNeedToTestOverlap + nbPairsToAllocate); - NarrowPhaseAlgorithmType* newNarrowPhaseAlgorithmType = reinterpret_cast(newIsActive + nbPairsToAllocate); + NarrowPhaseAlgorithmType* newNarrowPhaseAlgorithmType = reinterpret_cast(newNeedToTestOverlap + nbPairsToAllocate); bool* newIsShape1Convex = reinterpret_cast(newNarrowPhaseAlgorithmType + nbPairsToAllocate); bool* wereCollidingInPreviousFrame = reinterpret_cast(newIsShape1Convex + nbPairsToAllocate); bool* areCollidingInCurrentFrame = reinterpret_cast(wereCollidingInPreviousFrame + nbPairsToAllocate); @@ -225,7 +224,6 @@ void OverlappingPairs::allocate(uint64 nbPairsToAllocate) { memcpy(newColliders2, mColliders2, mNbPairs * sizeof(Entity)); memcpy(newLastFrameCollisionInfos, mLastFrameCollisionInfos, mNbPairs * sizeof(Map)); memcpy(newNeedToTestOverlap, mNeedToTestOverlap, mNbPairs * sizeof(bool)); - memcpy(newIsActive, mIsActive, mNbPairs * sizeof(bool)); memcpy(newNarrowPhaseAlgorithmType, mNarrowPhaseAlgorithmType, mNbPairs * sizeof(NarrowPhaseAlgorithmType)); memcpy(newIsShape1Convex, mIsShape1Convex, mNbPairs * sizeof(bool)); memcpy(wereCollidingInPreviousFrame, mCollidingInPreviousFrame, mNbPairs * sizeof(bool)); @@ -243,7 +241,6 @@ void OverlappingPairs::allocate(uint64 nbPairsToAllocate) { mColliders2 = newColliders2; mLastFrameCollisionInfos = newLastFrameCollisionInfos; mNeedToTestOverlap = newNeedToTestOverlap; - mIsActive = newIsActive; mNarrowPhaseAlgorithmType = newNarrowPhaseAlgorithmType; mIsShape1Convex = newIsShape1Convex; mCollidingInPreviousFrame = wereCollidingInPreviousFrame; @@ -301,7 +298,6 @@ uint64 OverlappingPairs::addPair(Collider* shape1, Collider* shape2) { new (mColliders2 + index) Entity(shape2->getEntity()); new (mLastFrameCollisionInfos + index) Map(mPersistentAllocator); new (mNeedToTestOverlap + index) bool(false); - new (mIsActive + index) bool(true); new (mNarrowPhaseAlgorithmType + index) NarrowPhaseAlgorithmType(algorithmType); new (mIsShape1Convex + index) bool(isShape1Convex); new (mCollidingInPreviousFrame + index) bool(false); @@ -321,8 +317,6 @@ uint64 OverlappingPairs::addPair(Collider* shape1, Collider* shape2) { assert(mConcavePairsStartIndex <= mNbPairs); assert(mNbPairs == static_cast(mMapPairIdToPairIndex.size())); - updateOverlappingPairIsActive(pairId); - return pairId; } @@ -340,7 +334,6 @@ void OverlappingPairs::movePairToIndex(uint64 srcIndex, uint64 destIndex) { new (mColliders2 + destIndex) Entity(mColliders2[srcIndex]); new (mLastFrameCollisionInfos + destIndex) Map(mLastFrameCollisionInfos[srcIndex]); mNeedToTestOverlap[destIndex] = mNeedToTestOverlap[srcIndex]; - mIsActive[destIndex] = mIsActive[srcIndex]; new (mNarrowPhaseAlgorithmType + destIndex) NarrowPhaseAlgorithmType(mNarrowPhaseAlgorithmType[srcIndex]); mIsShape1Convex[destIndex] = mIsShape1Convex[srcIndex]; mCollidingInPreviousFrame[destIndex] = mCollidingInPreviousFrame[srcIndex]; @@ -368,7 +361,6 @@ void OverlappingPairs::swapPairs(uint64 index1, uint64 index2) { Entity collider2 = mColliders2[index1]; Map lastFrameCollisionInfo(mLastFrameCollisionInfos[index1]); bool needTestOverlap = mNeedToTestOverlap[index1]; - bool isActive = mIsActive[index1]; NarrowPhaseAlgorithmType narrowPhaseAlgorithmType = mNarrowPhaseAlgorithmType[index1]; bool isShape1Convex = mIsShape1Convex[index1]; bool wereCollidingInPreviousFrame = mCollidingInPreviousFrame[index1]; @@ -387,7 +379,6 @@ void OverlappingPairs::swapPairs(uint64 index1, uint64 index2) { new (mColliders2 + index2) Entity(collider2); new (mLastFrameCollisionInfos + index2) Map(lastFrameCollisionInfo); mNeedToTestOverlap[index2] = needTestOverlap; - mIsActive[index2] = isActive; new (mNarrowPhaseAlgorithmType + index2) NarrowPhaseAlgorithmType(narrowPhaseAlgorithmType); mIsShape1Convex[index2] = isShape1Convex; mCollidingInPreviousFrame[index2] = wereCollidingInPreviousFrame; @@ -416,36 +407,6 @@ void OverlappingPairs::destroyPair(uint64 index) { mNarrowPhaseAlgorithmType[index].~NarrowPhaseAlgorithmType(); } -// Update whether a given overlapping pair is active or not -void OverlappingPairs::updateOverlappingPairIsActive(uint64 pairId) { - - assert(mMapPairIdToPairIndex.containsKey(pairId)); - - const uint64 pairIndex = mMapPairIdToPairIndex[pairId]; - - const Entity collider1 = mColliders1[pairIndex]; - const Entity collider2 = mColliders2[pairIndex]; - - const Entity body1 = mColliderComponents.getBody(collider1); - const Entity body2 = mColliderComponents.getBody(collider2); - - const bool isBody1Enabled = !mCollisionBodyComponents.getIsEntityDisabled(body1); - const bool isBody2Enabled = !mCollisionBodyComponents.getIsEntityDisabled(body2); - const bool isBody1Static = mRigidBodyComponents.hasComponent(body1) && - mRigidBodyComponents.getBodyType(body1) == BodyType::STATIC; - const bool isBody2Static = mRigidBodyComponents.hasComponent(body2) && - mRigidBodyComponents.getBodyType(body2) == BodyType::STATIC; - - const bool isBody1Active = isBody1Enabled && !isBody1Static; - const bool isBody2Active = isBody2Enabled && !isBody2Static; - - // Check if the bodies are in the set of bodies that cannot collide between each other - bodypair bodiesIndex = OverlappingPairs::computeBodiesIndexPair(body1, body2); - bool bodiesCanCollide = !mNoCollisionPairs.contains(bodiesIndex); - - mIsActive[pairIndex] = bodiesCanCollide && (isBody1Active || isBody2Active); -} - // Add a new last frame collision info if it does not exist for the given shapes already LastFrameCollisionInfo* OverlappingPairs::addLastFrameInfoIfNecessary(uint64 pairIndex, uint32 shapeId1, uint32 shapeId2) { diff --git a/src/mathematics/mathematics_functions.cpp b/src/mathematics/mathematics_functions.cpp index 66c3efda..5438c980 100755 --- a/src/mathematics/mathematics_functions.cpp +++ b/src/mathematics/mathematics_functions.cpp @@ -403,13 +403,3 @@ bool reactphysics3d::isPrimeNumber(int number) { return number == 2; } - -// Return an unique integer from two integer numbers (pairing function) -/// Here we assume that the two parameter numbers are sorted such that -/// number1 = max(number1, number2) -/// http://szudzik.com/ElegantPairing.pdf -uint64 reactphysics3d::pairNumbers(uint32 number1, uint32 number2) { - assert(number1 == std::max(number1, number2)); - return number1 * number1 + number1 + number2; -} - diff --git a/src/systems/CollisionDetectionSystem.cpp b/src/systems/CollisionDetectionSystem.cpp index b7de88b0..4315cdb5 100644 --- a/src/systems/CollisionDetectionSystem.cpp +++ b/src/systems/CollisionDetectionSystem.cpp @@ -192,39 +192,64 @@ void CollisionDetectionSystem::updateOverlappingPairs(const ListgetCollisionShape()->isConvex() || shape2->getCollisionShape()->isConvex()) { + const unsigned short shape1CollideWithMaskBits = mCollidersComponents.mCollideWithMaskBits[collider1Index]; + const unsigned short shape2CollideWithMaskBits = mCollidersComponents.mCollideWithMaskBits[collider2Index]; - // Add the new overlapping pair - mOverlappingPairs.addPair(shape1, shape2); + const unsigned short shape1CollisionCategoryBits = mCollidersComponents.mCollisionCategoryBits[collider1Index]; + const unsigned short shape2CollisionCategoryBits = mCollidersComponents.mCollisionCategoryBits[collider2Index]; + + // Check if the collision filtering allows collision between the two shapes + if ((shape1CollideWithMaskBits & shape2CollisionCategoryBits) != 0 && + (shape1CollisionCategoryBits & shape2CollideWithMaskBits) != 0) { + + Collider* shape1 = mCollidersComponents.mColliders[collider1Index]; + Collider* shape2 = mCollidersComponents.mColliders[collider2Index]; + + // Check that at least one collision shape is convex + if (shape1->getCollisionShape()->isConvex() || shape2->getCollisionShape()->isConvex()) { + + // Add the new overlapping pair + mOverlappingPairs.addPair(shape1, shape2); + } + } + } + else { + + // We do not need to test the pair for overlap because it has just been reported that they still overlap + mOverlappingPairs.mNeedToTestOverlap[it->second] = false; } } } - else { - - // We do not need to test the pair for overlap because it has just been reported that they still overlap - mOverlappingPairs.mNeedToTestOverlap[it->second] = false; - } } } } @@ -249,33 +274,30 @@ void CollisionDetectionSystem::computeMiddlePhase(NarrowPhaseInput& narrowPhaseI assert(mCollidersComponents.getBroadPhaseId(mOverlappingPairs.mColliders2[i]) != -1); assert(mCollidersComponents.getBroadPhaseId(mOverlappingPairs.mColliders1[i]) != mCollidersComponents.getBroadPhaseId(mOverlappingPairs.mColliders2[i])); - // Check that at least one body is enabled (active and awake) and not static - if (mOverlappingPairs.mIsActive[i]) { - const Entity collider1Entity = mOverlappingPairs.mColliders1[i]; - const Entity collider2Entity = mOverlappingPairs.mColliders2[i]; + const Entity collider1Entity = mOverlappingPairs.mColliders1[i]; + const Entity collider2Entity = mOverlappingPairs.mColliders2[i]; - const uint collider1Index = mCollidersComponents.getEntityIndex(collider1Entity); - const uint collider2Index = mCollidersComponents.getEntityIndex(collider2Entity); + const uint collider1Index = mCollidersComponents.getEntityIndex(collider1Entity); + const uint collider2Index = mCollidersComponents.getEntityIndex(collider2Entity); - CollisionShape* collisionShape1 = mCollidersComponents.mCollisionShapes[collider1Index]; - CollisionShape* collisionShape2 = mCollidersComponents.mCollisionShapes[collider2Index]; + CollisionShape* collisionShape1 = mCollidersComponents.mCollisionShapes[collider1Index]; + CollisionShape* collisionShape2 = mCollidersComponents.mCollisionShapes[collider2Index]; - NarrowPhaseAlgorithmType algorithmType = mOverlappingPairs.mNarrowPhaseAlgorithmType[i]; + NarrowPhaseAlgorithmType algorithmType = mOverlappingPairs.mNarrowPhaseAlgorithmType[i]; - const bool isCollider1Trigger = mCollidersComponents.mIsTrigger[collider1Index]; - const bool isCollider2Trigger = mCollidersComponents.mIsTrigger[collider2Index]; - const bool reportContacts = needToReportContacts && !isCollider1Trigger && !isCollider2Trigger; + const bool isCollider1Trigger = mCollidersComponents.mIsTrigger[collider1Index]; + const bool isCollider2Trigger = mCollidersComponents.mIsTrigger[collider2Index]; + const bool reportContacts = needToReportContacts && !isCollider1Trigger && !isCollider2Trigger; - // No middle-phase is necessary, simply create a narrow phase info - // for the narrow-phase collision detection - narrowPhaseInput.addNarrowPhaseTest(mOverlappingPairs.mPairIds[i], i, collider1Entity, collider2Entity, collisionShape1, collisionShape2, - mCollidersComponents.mLocalToWorldTransforms[collider1Index], - mCollidersComponents.mLocalToWorldTransforms[collider2Index], - algorithmType, reportContacts, mMemoryManager.getSingleFrameAllocator()); + // No middle-phase is necessary, simply create a narrow phase info + // for the narrow-phase collision detection + narrowPhaseInput.addNarrowPhaseTest(mOverlappingPairs.mPairIds[i], i, collider1Entity, collider2Entity, collisionShape1, collisionShape2, + mCollidersComponents.mLocalToWorldTransforms[collider1Index], + mCollidersComponents.mLocalToWorldTransforms[collider2Index], + algorithmType, reportContacts, mMemoryManager.getSingleFrameAllocator()); - mOverlappingPairs.mCollidingInCurrentFrame[i] = false; - } + mOverlappingPairs.mCollidingInCurrentFrame[i] = false; } // For each possible convex vs concave pair of bodies @@ -287,13 +309,9 @@ void CollisionDetectionSystem::computeMiddlePhase(NarrowPhaseInput& narrowPhaseI assert(mCollidersComponents.getBroadPhaseId(mOverlappingPairs.mColliders2[i]) != -1); assert(mCollidersComponents.getBroadPhaseId(mOverlappingPairs.mColliders1[i]) != mCollidersComponents.getBroadPhaseId(mOverlappingPairs.mColliders2[i])); - // Check that at least one body is enabled (active and awake) and not static - if (mOverlappingPairs.mIsActive[i]) { + computeConvexVsConcaveMiddlePhase(i, mMemoryManager.getSingleFrameAllocator(), narrowPhaseInput); - computeConvexVsConcaveMiddlePhase(i, mMemoryManager.getSingleFrameAllocator(), narrowPhaseInput); - - mOverlappingPairs.mCollidingInCurrentFrame[i] = false; - } + mOverlappingPairs.mCollidingInCurrentFrame[i] = false; } } diff --git a/src/systems/ContactSolverSystem.cpp b/src/systems/ContactSolverSystem.cpp index a3d4936d..1944c143 100644 --- a/src/systems/ContactSolverSystem.cpp +++ b/src/systems/ContactSolverSystem.cpp @@ -131,8 +131,6 @@ void ContactSolverSystem::initializeForIsland(uint islandIndex) { const uint rigidBodyIndex2 = mRigidBodyComponents.getEntityIndex(externalManifold.bodyEntity2); // Get the two bodies of the contact - assert(body1 != nullptr); - assert(body2 != nullptr); assert(!mBodyComponents.getIsEntityDisabled(externalManifold.bodyEntity1)); assert(!mBodyComponents.getIsEntityDisabled(externalManifold.bodyEntity2)); From 089d227434fa29594c004572eedb23b1054e34b2 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Mon, 10 Aug 2020 00:03:35 +0200 Subject: [PATCH 18/74] Fix issue in List class --- include/reactphysics3d/containers/List.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/reactphysics3d/containers/List.h b/include/reactphysics3d/containers/List.h index 140f0b56..2274657f 100755 --- a/include/reactphysics3d/containers/List.h +++ b/include/reactphysics3d/containers/List.h @@ -313,7 +313,7 @@ class List { void addWithoutInit(uint nbElements) { // If we need to allocate more memory - if (mSize == mCapacity) { + if ((mSize + nbElements) > mCapacity) { reserve(mCapacity == 0 ? nbElements : (mCapacity + nbElements) * 2); } From b5a7454f4cfc1a955191e7a8207ebfe1970a2c84 Mon Sep 17 00:00:00 2001 From: SlavicPotato Date: Tue, 11 Aug 2020 09:14:18 +0200 Subject: [PATCH 19/74] Fix DebugRenderer contact point sphere radius --- src/utils/DebugRenderer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/DebugRenderer.cpp b/src/utils/DebugRenderer.cpp index e403253b..4e1533f7 100644 --- a/src/utils/DebugRenderer.cpp +++ b/src/utils/DebugRenderer.cpp @@ -463,7 +463,7 @@ void DebugRenderer::onContact(const CollisionCallback::CallbackData& callbackDat if (getIsDebugItemDisplayed(DebugItem::CONTACT_POINT)) { // Contact point - drawSphere(point, DEFAULT_CONTACT_POINT_SPHERE_RADIUS, mMapDebugItemWithColor[DebugItem::CONTACT_POINT]); + drawSphere(point, mContactPointSphereRadius, mMapDebugItemWithColor[DebugItem::CONTACT_POINT]); } if (getIsDebugItemDisplayed(DebugItem::CONTACT_NORMAL)) { From 5dd48c195c2701c5824da6c1a51c277565a4559e Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Tue, 11 Aug 2020 18:14:14 +0200 Subject: [PATCH 20/74] Fix issue in List --- include/reactphysics3d/containers/List.h | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/include/reactphysics3d/containers/List.h b/include/reactphysics3d/containers/List.h index 2274657f..11c63915 100755 --- a/include/reactphysics3d/containers/List.h +++ b/include/reactphysics3d/containers/List.h @@ -232,6 +232,11 @@ class List { /// Copy constructor List(const List& list) : mBuffer(nullptr), mSize(0), mCapacity(0), mAllocator(list.mAllocator) { + // If we need to allocate more memory + if (list.mCapacity > 0) { + reserve(list.mCapacity); + } + // All all the elements of the list to the current one addRange(list); } @@ -368,6 +373,27 @@ class List { return Iterator(mBuffer, index, mSize); } + /// Remove an element from the list at a given index and replace it by the last one of the list (if any) + void removeAtAndReplaceByLast(uint index) { + + assert(index >= 0 && index < mSize); + + // Call the destructor + (static_cast(mBuffer)[index]).~T(); + + // If there is another element in the list + if (mSize > 1 && index < (mSize - 1)) { + + // Copy the last element of the list at the location of the removed element + new (static_cast(mBuffer) + index * sizeof(T)) T(static_cast(mBuffer)[mSize - 1]); + + // Call the destructor of the last element + (static_cast(mBuffer)[mSize - 1]).~T(); + } + + mSize--; + } + /// Append another list at the end of the current one void addRange(const List& list) { From a1e0e0aa946a8881d3ee2074a2db91058b021f96 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Tue, 11 Aug 2020 18:14:32 +0200 Subject: [PATCH 21/74] Refactor OverlappingPairs --- .../narrowphase/NarrowPhaseInfoBatch.h | 13 +- .../collision/narrowphase/NarrowPhaseInput.h | 21 +- .../reactphysics3d/engine/OverlappingPairs.h | 390 +++++++------- .../systems/CollisionDetectionSystem.h | 4 +- src/collision/broadphase/DynamicAABBTree.cpp | 3 + .../narrowphase/NarrowPhaseInfoBatch.cpp | 3 +- src/collision/shapes/ConcaveMeshShape.cpp | 2 +- src/collision/shapes/HeightFieldShape.cpp | 6 +- src/engine/OverlappingPairs.cpp | 475 ++++-------------- src/systems/BroadPhaseSystem.cpp | 6 +- src/systems/CollisionDetectionSystem.cpp | 305 +++++++---- 11 files changed, 542 insertions(+), 686 deletions(-) diff --git a/include/reactphysics3d/collision/narrowphase/NarrowPhaseInfoBatch.h b/include/reactphysics3d/collision/narrowphase/NarrowPhaseInfoBatch.h index bcad2c5b..73b44cb3 100644 --- a/include/reactphysics3d/collision/narrowphase/NarrowPhaseInfoBatch.h +++ b/include/reactphysics3d/collision/narrowphase/NarrowPhaseInfoBatch.h @@ -59,7 +59,6 @@ struct NarrowPhaseInfoBatch { Entity colliderEntity2; /// Collision info of the previous frame - // TODO OPTI : Do we really need to use a pointer here ? why not flat object instead ? LastFrameCollisionInfo* lastFrameCollisionInfo; /// Memory allocator for the collision shape (Used to release TriangleShape memory in destructor) @@ -124,9 +123,9 @@ struct NarrowPhaseInfoBatch { ~NarrowPhaseInfoBatch(); /// Add shapes to be tested during narrow-phase collision detection into the batch - void addNarrowPhaseInfo(uint64 pairId, uint64 pairIndex, Entity collider1, Entity collider2, CollisionShape* shape1, + void addNarrowPhaseInfo(uint64 pairId, Entity collider1, Entity collider2, CollisionShape* shape1, CollisionShape* shape2, const Transform& shape1Transform, const Transform& shape2Transform, - bool needToReportContacts, MemoryAllocator& shapeAllocator); + bool needToReportContacts, LastFrameCollisionInfo* lastFrameInfo, MemoryAllocator& shapeAllocator); /// Return the number of objects in the batch uint getNbObjects() const; @@ -151,13 +150,9 @@ RP3D_FORCE_INLINE uint NarrowPhaseInfoBatch::getNbObjects() const { } // Add shapes to be tested during narrow-phase collision detection into the batch -RP3D_FORCE_INLINE void NarrowPhaseInfoBatch::addNarrowPhaseInfo(uint64 pairId, uint64 pairIndex, Entity collider1, Entity collider2, CollisionShape* shape1, +RP3D_FORCE_INLINE void NarrowPhaseInfoBatch::addNarrowPhaseInfo(uint64 pairId, Entity collider1, Entity collider2, CollisionShape* shape1, CollisionShape* shape2, const Transform& shape1Transform, const Transform& shape2Transform, - bool needToReportContacts, MemoryAllocator& shapeAllocator) { - - // Add a collision info for the two collision shapes into the overlapping pair (if not present yet) - // TODO OPTI : Can we better manage this - LastFrameCollisionInfo* lastFrameInfo = mOverlappingPairs.addLastFrameInfoIfNecessary(pairIndex, shape1->getId(), shape2->getId()); + bool needToReportContacts, LastFrameCollisionInfo* lastFrameInfo, MemoryAllocator& shapeAllocator) { // Create a meta data object narrowPhaseInfos.emplace(pairId, collider1, collider2, lastFrameInfo, shapeAllocator, shape1Transform, shape2Transform, shape1, shape2, needToReportContacts); diff --git a/include/reactphysics3d/collision/narrowphase/NarrowPhaseInput.h b/include/reactphysics3d/collision/narrowphase/NarrowPhaseInput.h index 639117a7..9f64574a 100644 --- a/include/reactphysics3d/collision/narrowphase/NarrowPhaseInput.h +++ b/include/reactphysics3d/collision/narrowphase/NarrowPhaseInput.h @@ -66,10 +66,10 @@ class NarrowPhaseInput { NarrowPhaseInput(MemoryAllocator& allocator, OverlappingPairs& overlappingPairs); /// Add shapes to be tested during narrow-phase collision detection into the batch - void addNarrowPhaseTest(uint64 pairId, uint64 pairIndex, Entity collider1, Entity collider2, CollisionShape* shape1, + void addNarrowPhaseTest(uint64 pairId, Entity collider1, Entity collider2, CollisionShape* shape1, CollisionShape* shape2, const Transform& shape1Transform, const Transform& shape2Transform, NarrowPhaseAlgorithmType narrowPhaseAlgorithmType, bool reportContacts, - MemoryAllocator& shapeAllocator); + LastFrameCollisionInfo* lastFrameInfo, MemoryAllocator& shapeAllocator); /// Get a reference to the sphere vs sphere batch NarrowPhaseInfoBatch& getSphereVsSphereBatch(); @@ -128,28 +128,29 @@ RP3D_FORCE_INLINE NarrowPhaseInfoBatch &NarrowPhaseInput::getConvexPolyhedronVsC } // Add shapes to be tested during narrow-phase collision detection into the batch -RP3D_FORCE_INLINE void NarrowPhaseInput::addNarrowPhaseTest(uint64 pairId, uint64 pairIndex, Entity collider1, Entity collider2, CollisionShape* shape1, CollisionShape* shape2, +RP3D_FORCE_INLINE void NarrowPhaseInput::addNarrowPhaseTest(uint64 pairId, Entity collider1, Entity collider2, CollisionShape* shape1, CollisionShape* shape2, const Transform& shape1Transform, const Transform& shape2Transform, - NarrowPhaseAlgorithmType narrowPhaseAlgorithmType, bool reportContacts, MemoryAllocator& shapeAllocator) { + NarrowPhaseAlgorithmType narrowPhaseAlgorithmType, bool reportContacts, LastFrameCollisionInfo* lastFrameInfo, + MemoryAllocator& shapeAllocator) { switch (narrowPhaseAlgorithmType) { case NarrowPhaseAlgorithmType::SphereVsSphere: - mSphereVsSphereBatch.addNarrowPhaseInfo(pairId, pairIndex, collider1, collider2, shape1, shape2, shape1Transform, shape2Transform, reportContacts, shapeAllocator); + mSphereVsSphereBatch.addNarrowPhaseInfo(pairId, collider1, collider2, shape1, shape2, shape1Transform, shape2Transform, reportContacts, lastFrameInfo, shapeAllocator); break; case NarrowPhaseAlgorithmType::SphereVsCapsule: - mSphereVsCapsuleBatch.addNarrowPhaseInfo(pairId, pairIndex, collider1, collider2, shape1, shape2, shape1Transform, shape2Transform, reportContacts, shapeAllocator); + mSphereVsCapsuleBatch.addNarrowPhaseInfo(pairId, collider1, collider2, shape1, shape2, shape1Transform, shape2Transform, reportContacts, lastFrameInfo, shapeAllocator); break; case NarrowPhaseAlgorithmType::CapsuleVsCapsule: - mCapsuleVsCapsuleBatch.addNarrowPhaseInfo(pairId, pairIndex, collider1, collider2, shape1, shape2, shape1Transform, shape2Transform, reportContacts, shapeAllocator); + mCapsuleVsCapsuleBatch.addNarrowPhaseInfo(pairId, collider1, collider2, shape1, shape2, shape1Transform, shape2Transform, reportContacts, lastFrameInfo, shapeAllocator); break; case NarrowPhaseAlgorithmType::SphereVsConvexPolyhedron: - mSphereVsConvexPolyhedronBatch.addNarrowPhaseInfo(pairId, pairIndex, collider1, collider2, shape1, shape2, shape1Transform, shape2Transform, reportContacts, shapeAllocator); + mSphereVsConvexPolyhedronBatch.addNarrowPhaseInfo(pairId, collider1, collider2, shape1, shape2, shape1Transform, shape2Transform, reportContacts, lastFrameInfo, shapeAllocator); break; case NarrowPhaseAlgorithmType::CapsuleVsConvexPolyhedron: - mCapsuleVsConvexPolyhedronBatch.addNarrowPhaseInfo(pairId, pairIndex, collider1, collider2, shape1, shape2, shape1Transform, shape2Transform, reportContacts, shapeAllocator); + mCapsuleVsConvexPolyhedronBatch.addNarrowPhaseInfo(pairId, collider1, collider2, shape1, shape2, shape1Transform, shape2Transform, reportContacts, lastFrameInfo, shapeAllocator); break; case NarrowPhaseAlgorithmType::ConvexPolyhedronVsConvexPolyhedron: - mConvexPolyhedronVsConvexPolyhedronBatch.addNarrowPhaseInfo(pairId, pairIndex, collider1, collider2, shape1, shape2, shape1Transform, shape2Transform, reportContacts, shapeAllocator); + mConvexPolyhedronVsConvexPolyhedronBatch.addNarrowPhaseInfo(pairId, collider1, collider2, shape1, shape2, shape1Transform, shape2Transform, reportContacts, lastFrameInfo, shapeAllocator); break; case NarrowPhaseAlgorithmType::None: // Must never happen diff --git a/include/reactphysics3d/engine/OverlappingPairs.h b/include/reactphysics3d/engine/OverlappingPairs.h index 206d43bc..e00dcd54 100644 --- a/include/reactphysics3d/engine/OverlappingPairs.h +++ b/include/reactphysics3d/engine/OverlappingPairs.h @@ -106,77 +106,199 @@ struct LastFrameCollisionInfo { */ class OverlappingPairs { + public: + + struct OverlappingPair { + + /// Ids of the convex vs convex pairs + uint64 pairID; + + /// Broad-phase Id of the first shape + // TODO OPTI : Is this used ? + int32 broadPhaseId1; + + /// Broad-phase Id of the second shape + // TODO OPTI : Is this used ? + int32 broadPhaseId2; + + /// Entity of the first collider of the convex vs convex pairs + Entity collider1; + + /// Entity of the second collider of the convex vs convex pairs + Entity collider2; + + /// True if we need to test if the convex vs convex overlapping pairs of shapes still overlap + bool needToTestOverlap; + + /// Pointer to the narrow-phase algorithm + NarrowPhaseAlgorithmType narrowPhaseAlgorithmType; + + /// True if the colliders of the overlapping pair were colliding in the previous frame + bool collidingInPreviousFrame; + + /// True if the colliders of the overlapping pair are colliding in the current frame + bool collidingInCurrentFrame; + + /// Constructor + OverlappingPair(uint64 pairId, int32 broadPhaseId1, int32 broadPhaseId2, Entity collider1, Entity collider2, + NarrowPhaseAlgorithmType narrowPhaseAlgorithmType) + : pairID(pairId), broadPhaseId1(broadPhaseId1), broadPhaseId2(broadPhaseId2), collider1(collider1) , collider2(collider2), + needToTestOverlap(false), narrowPhaseAlgorithmType(narrowPhaseAlgorithmType), collidingInPreviousFrame(false), + collidingInCurrentFrame(false) { + + } + + /// Destructor + virtual ~OverlappingPair() = default; + }; + + // Overlapping pair between two convex colliders + struct ConvexOverlappingPair : public OverlappingPair { + + /// Temporal coherence collision data for each overlapping collision shapes of this pair. + /// Temporal coherence data store collision information about the last frame. + /// If two convex shapes overlap, we have a single collision data but if one shape is concave, + /// we might have collision data for several overlapping triangles. + LastFrameCollisionInfo lastFrameCollisionInfo; + + /// Constructor + ConvexOverlappingPair(uint64 pairId, int32 broadPhaseId1, int32 broadPhaseId2, Entity collider1, Entity collider2, + NarrowPhaseAlgorithmType narrowPhaseAlgorithmType) + : OverlappingPair(pairId, broadPhaseId1, broadPhaseId2, collider1, collider2, narrowPhaseAlgorithmType) { + + } + }; + + // Overlapping pair between two a convex collider and a concave collider + struct ConcaveOverlappingPair : public OverlappingPair { + + private: + + MemoryAllocator& mPoolAllocator; + + public: + + /// True if the first shape of the pair is convex + bool isShape1Convex; + + /// Temporal coherence collision data for each overlapping collision shapes of this pair. + /// Temporal coherence data store collision information about the last frame. + /// If two convex shapes overlap, we have a single collision data but if one shape is concave, + /// we might have collision data for several overlapping triangles. The key in the map is the + /// shape Ids of the two collision shapes. + Map lastFrameCollisionInfos; + + /// Constructor + ConcaveOverlappingPair(uint64 pairId, int32 broadPhaseId1, int32 broadPhaseId2, Entity collider1, Entity collider2, + NarrowPhaseAlgorithmType narrowPhaseAlgorithmType, + bool isShape1Convex, MemoryAllocator& poolAllocator, MemoryAllocator& heapAllocator) + : OverlappingPair(pairId, broadPhaseId1, broadPhaseId2, collider1, collider2, narrowPhaseAlgorithmType), mPoolAllocator(poolAllocator), + isShape1Convex(isShape1Convex), lastFrameCollisionInfos(heapAllocator, 16) { + + } + + // Destroy all the LastFrameCollisionInfo objects + void destroyLastFrameCollisionInfos() { + + for (auto it = lastFrameCollisionInfos.begin(); it != lastFrameCollisionInfos.end(); ++it) { + + // Call the destructor + it->second->LastFrameCollisionInfo::~LastFrameCollisionInfo(); + + // Release memory + mPoolAllocator.release(it->second, sizeof(LastFrameCollisionInfo)); + } + + lastFrameCollisionInfos.clear(); + } + + // Add a new last frame collision info if it does not exist for the given shapes already + LastFrameCollisionInfo* addLastFrameInfoIfNecessary(uint32 shapeId1, uint32 shapeId2) { + + uint32 maxShapeId = shapeId1; + uint32 minShapeId = shapeId2; + if (shapeId1 < shapeId2) { + maxShapeId = shapeId2; + minShapeId = shapeId1; + } + + // Try to get the corresponding last frame collision info + const uint64 shapesId = pairNumbers(maxShapeId, minShapeId); + + // If there is no collision info for those two shapes already + auto it = lastFrameCollisionInfos.find(shapesId); + if (it == lastFrameCollisionInfos.end()) { + + LastFrameCollisionInfo* lastFrameInfo = new (mPoolAllocator.allocate(sizeof(LastFrameCollisionInfo))) LastFrameCollisionInfo(); + + // Add it into the map of collision infos + lastFrameCollisionInfos.add(Pair(shapesId, lastFrameInfo)); + + return lastFrameInfo; + } + else { + + // The existing collision info is not obsolete + it->second->isObsolete = false; + + return it->second; + } + } + + /// Clear the obsolete LastFrameCollisionInfo objects + void clearObsoleteLastFrameInfos() { + + // For each last frame collision info + for (auto it = lastFrameCollisionInfos.begin(); it != lastFrameCollisionInfos.end(); ) { + + // If the collision info is obsolete + if (it->second->isObsolete) { + + // Call the destructor + it->second->LastFrameCollisionInfo::~LastFrameCollisionInfo(); + + // Release memory + mPoolAllocator.release(it->second, sizeof(LastFrameCollisionInfo)); + + it = lastFrameCollisionInfos.remove(it); + } + else { // If the collision info is not obsolete + + // Do not delete it but mark it as obsolete + it->second->isObsolete = true; + + ++it; + } + } + } + + /// Destructor + virtual ~ConcaveOverlappingPair() { + + } + }; + private: - // -------------------- Constants -------------------- // - - - /// Number of pairs to allocated at the beginning - const uint32 INIT_NB_ALLOCATED_PAIRS = 10; - // -------------------- Attributes -------------------- // - /// Persistent memory allocator - MemoryAllocator& mPersistentAllocator; + /// Pool memory allocator + MemoryAllocator& mPoolAllocator; - /// Memory allocator used to allocated memory for the ContactManifoldInfo and ContactPointInfo - // TODO OPTI : Do we need to keep this ? - MemoryAllocator& mTempMemoryAllocator; + /// Heap memory allocator + MemoryAllocator& mHeapAllocator; - /// Current number of components - uint64 mNbPairs; + /// List of convex vs convex overlapping pairs + List mConvexPairs; - /// Index in the array of the first convex vs concave pair - uint64 mConcavePairsStartIndex; - - /// Size (in bytes) of a single pair - size_t mPairDataSize; - - /// Number of allocated pairs - uint64 mNbAllocatedPairs; - - /// Allocated memory for all the data of the pairs - void* mBuffer; + /// List of convex vs concave overlapping pairs + List mConcavePairs; /// Map a pair id to the internal array index - Map mMapPairIdToPairIndex; + Map mMapConvexPairIdToPairIndex; - /// Ids of the convex vs convex pairs - uint64* mPairIds; - - /// Array with the broad-phase Ids of the first shape - int32* mPairBroadPhaseId1; - - /// Array with the broad-phase Ids of the second shape - int32* mPairBroadPhaseId2; - - /// Array of Entity of the first collider of the convex vs convex pairs - Entity* mColliders1; - - /// Array of Entity of the second collider of the convex vs convex pairs - Entity* mColliders2; - - /// Temporal coherence collision data for each overlapping collision shapes of this pair. - /// Temporal coherence data store collision information about the last frame. - /// If two convex shapes overlap, we have a single collision data but if one shape is concave, - /// we might have collision data for several overlapping triangles. The key in the map is the - /// shape Ids of the two collision shapes. - Map* mLastFrameCollisionInfos; - - /// True if we need to test if the convex vs convex overlapping pairs of shapes still overlap - bool* mNeedToTestOverlap; - - /// Array with the pointer to the narrow-phase algorithm for each overlapping pair - NarrowPhaseAlgorithmType* mNarrowPhaseAlgorithmType; - - /// True if the first shape of the pair is convex - bool* mIsShape1Convex; - - /// True if the colliders of the overlapping pair were colliding in the previous frame - bool* mCollidingInPreviousFrame; - - /// True if the colliders of the overlapping pair are colliding in the current frame - bool* mCollidingInCurrentFrame; + /// Map a pair id to the internal array index + Map mMapConcavePairIdToPairIndex; /// Reference to the colliders components ColliderComponents& mColliderComponents; @@ -202,15 +324,6 @@ class OverlappingPairs { // -------------------- Methods -------------------- // - /// Allocate memory for a given number of pairs - void allocate(uint64 nbPairsToAllocate); - - /// Compute the index where we need to insert the new pair - uint64 prepareAddPair(bool isConvexVsConvex); - - /// Destroy a pair at a given index - void destroyPair(uint64 index); - // Move a pair from a source to a destination index in the pairs array void movePairToIndex(uint64 srcIndex, uint64 destIndex); @@ -222,8 +335,8 @@ class OverlappingPairs { // -------------------- Methods -------------------- // /// Constructor - OverlappingPairs(MemoryAllocator& persistentMemoryAllocator, MemoryAllocator& temporaryMemoryAllocator, - ColliderComponents& colliderComponents, CollisionBodyComponents& collisionBodyComponents, + OverlappingPairs(MemoryManager& memoryManager, ColliderComponents& colliderComponents, + CollisionBodyComponents& collisionBodyComponents, RigidBodyComponents& rigidBodyComponents, Set& noCollisionPairs, CollisionDispatch& collisionDispatch); @@ -237,40 +350,13 @@ class OverlappingPairs { OverlappingPairs& operator=(const OverlappingPairs& pair) = delete; /// Add an overlapping pair - uint64 addPair(Collider* shape1, Collider* shape2); + uint64 addPair(uint32 collider1Index, uint32 collider2Index, bool isConvexVsConvex); /// Remove a component at a given index void removePair(uint64 pairId); - /// Return the number of pairs - uint64 getNbPairs() const; - - /// Return the number of convex vs convex pairs - uint64 getNbConvexVsConvexPairs() const; - - /// Return the number of convex vs concave pairs - uint64 getNbConvexVsConcavePairs() const; - - /// Return the starting index of the convex vs concave pairs - uint64 getConvexVsConcavePairsStartIndex() const; - - /// Return the entity of the first collider - Entity getCollider1(uint64 pairId) const; - - /// Return the entity of the second collider - Entity getCollider2(uint64 pairId) const; - - /// Return the index of a given overlapping pair in the internal array - uint64 getPairIndex(uint64 pairId) const; - - /// Return the last frame collision info - LastFrameCollisionInfo* getLastFrameCollisionInfo(uint64, uint64 shapesId); - - /// Return a reference to the temporary memory allocator - MemoryAllocator& getTemporaryAllocator(); - - /// Add a new last frame collision info if it does not exist for the given shapes already - LastFrameCollisionInfo* addLastFrameInfoIfNecessary(uint64 pairIndex, uint32 shapeId1, uint32 shapeId2); + /// Remove a pair + void removePair(uint64 pairIndex, bool isConvexVsConvex); /// Delete all the obsolete last frame collision info void clearObsoleteLastFrameCollisionInfos(); @@ -284,11 +370,8 @@ class OverlappingPairs { /// Set if we need to test a given pair for overlap void setNeedToTestOverlap(uint64 pairId, bool needToTestOverlap); - /// Return true if the two colliders of the pair were already colliding the previous frame - bool getCollidingInPreviousFrame(uint64 pairId) const; - - /// Set to true if the two colliders of the pair were already colliding the previous frame - void setCollidingInPreviousFrame(uint64 pairId, bool wereCollidingInPreviousFrame); + /// Return a reference to an overlapping pair + OverlappingPair* getOverlappingPair(uint64 pairId); #ifdef IS_RP3D_PROFILING_ENABLED @@ -303,41 +386,6 @@ class OverlappingPairs { friend class CollisionDetectionSystem; }; -// Return the entity of the first collider -RP3D_FORCE_INLINE Entity OverlappingPairs::getCollider1(uint64 pairId) const { - assert(mMapPairIdToPairIndex.containsKey(pairId)); - assert(mMapPairIdToPairIndex[pairId] < mNbPairs); - return mColliders1[mMapPairIdToPairIndex[pairId]]; -} - -// Return the entity of the second collider -RP3D_FORCE_INLINE Entity OverlappingPairs::getCollider2(uint64 pairId) const { - assert(mMapPairIdToPairIndex.containsKey(pairId)); - assert(mMapPairIdToPairIndex[pairId] < mNbPairs); - return mColliders2[mMapPairIdToPairIndex[pairId]]; -} - -// Return the index of a given overlapping pair in the internal array -RP3D_FORCE_INLINE uint64 OverlappingPairs::getPairIndex(uint64 pairId) const { - assert(mMapPairIdToPairIndex.containsKey(pairId)); - return mMapPairIdToPairIndex[pairId]; -} - -// Return the last frame collision info for a given shape id or nullptr if none is found -RP3D_FORCE_INLINE LastFrameCollisionInfo* OverlappingPairs::getLastFrameCollisionInfo(uint64 pairId, uint64 shapesId) { - - assert(mMapPairIdToPairIndex.containsKey(pairId)); - const uint64 index = mMapPairIdToPairIndex[pairId]; - assert(index < mNbPairs); - - Map::Iterator it = mLastFrameCollisionInfos[index].find(shapesId); - if (it != mLastFrameCollisionInfos[index].end()) { - return it->second; - } - - return nullptr; -} - // Return the pair of bodies index RP3D_FORCE_INLINE bodypair OverlappingPairs::computeBodiesIndexPair(Entity body1Entity, Entity body2Entity) { @@ -349,47 +397,33 @@ RP3D_FORCE_INLINE bodypair OverlappingPairs::computeBodiesIndexPair(Entity body1 return indexPair; } -// Return the number of pairs -RP3D_FORCE_INLINE uint64 OverlappingPairs::getNbPairs() const { - return mNbPairs; -} - -// Return the number of convex vs convex pairs -RP3D_FORCE_INLINE uint64 OverlappingPairs::getNbConvexVsConvexPairs() const { - return mConcavePairsStartIndex; -} - -// Return the number of convex vs concave pairs -RP3D_FORCE_INLINE uint64 OverlappingPairs::getNbConvexVsConcavePairs() const { - return mNbPairs - mConcavePairsStartIndex; -} - -// Return the starting index of the convex vs concave pairs -RP3D_FORCE_INLINE uint64 OverlappingPairs::getConvexVsConcavePairsStartIndex() const { - return mConcavePairsStartIndex; -} - -// Return a reference to the temporary memory allocator -RP3D_FORCE_INLINE MemoryAllocator& OverlappingPairs::getTemporaryAllocator() { - return mTempMemoryAllocator; -} - // Set if we need to test a given pair for overlap RP3D_FORCE_INLINE void OverlappingPairs::setNeedToTestOverlap(uint64 pairId, bool needToTestOverlap) { - assert(mMapPairIdToPairIndex.containsKey(pairId)); - mNeedToTestOverlap[mMapPairIdToPairIndex[pairId]] = needToTestOverlap; + + assert(mMapConvexPairIdToPairIndex.containsKey(pairId) || mMapConcavePairIdToPairIndex.containsKey(pairId)); + + auto it = mMapConvexPairIdToPairIndex.find(pairId); + if (it != mMapConvexPairIdToPairIndex.end()) { + mConvexPairs[it->second].needToTestOverlap = needToTestOverlap; + } + else { + mConcavePairs[mMapConcavePairIdToPairIndex[pairId]].needToTestOverlap = needToTestOverlap; + } } -// Return true if the two colliders of the pair were already colliding the previous frame -RP3D_FORCE_INLINE bool OverlappingPairs::getCollidingInPreviousFrame(uint64 pairId) const { - assert(mMapPairIdToPairIndex.containsKey(pairId)); - return mCollidingInPreviousFrame[mMapPairIdToPairIndex[pairId]]; -} +// Return a reference to an overlapping pair +RP3D_FORCE_INLINE OverlappingPairs::OverlappingPair* OverlappingPairs::getOverlappingPair(uint64 pairId) { -// Set to true if the two colliders of the pair were already colliding the previous frame -RP3D_FORCE_INLINE void OverlappingPairs::setCollidingInPreviousFrame(uint64 pairId, bool wereCollidingInPreviousFrame) { - assert(mMapPairIdToPairIndex.containsKey(pairId)); - mCollidingInPreviousFrame[mMapPairIdToPairIndex[pairId]] = wereCollidingInPreviousFrame; + auto it = mMapConvexPairIdToPairIndex.find(pairId); + if (it != mMapConvexPairIdToPairIndex.end()) { + return &(mConvexPairs[it->second]); + } + it = mMapConcavePairIdToPairIndex.find(pairId); + if (it != mMapConcavePairIdToPairIndex.end()) { + return &(mConcavePairs[it->second]); + } + + return nullptr; } #ifdef IS_RP3D_PROFILING_ENABLED diff --git a/include/reactphysics3d/systems/CollisionDetectionSystem.h b/include/reactphysics3d/systems/CollisionDetectionSystem.h index 683df3cc..5cb28461 100644 --- a/include/reactphysics3d/systems/CollisionDetectionSystem.h +++ b/include/reactphysics3d/systems/CollisionDetectionSystem.h @@ -198,13 +198,13 @@ class CollisionDetectionSystem { void removeNonOverlappingPairs(); /// Add a lost contact pair (pair of colliders that are not in contact anymore) - void addLostContactPair(uint64 overlappingPairIndex); + void addLostContactPair(OverlappingPairs::OverlappingPair& overlappingPair); /// Execute the narrow-phase collision detection algorithm on batches bool testNarrowPhaseCollision(NarrowPhaseInput& narrowPhaseInput, bool clipWithPreviousAxisIfStillColliding, MemoryAllocator& allocator); /// Compute the concave vs convex middle-phase algorithm for a given pair of bodies - void computeConvexVsConcaveMiddlePhase(uint64 pairIndex, MemoryAllocator& allocator, + void computeConvexVsConcaveMiddlePhase(OverlappingPairs::ConcaveOverlappingPair& overlappingPair, MemoryAllocator& allocator, NarrowPhaseInput& narrowPhaseInput); /// Swap the previous and current contacts lists diff --git a/src/collision/broadphase/DynamicAABBTree.cpp b/src/collision/broadphase/DynamicAABBTree.cpp index a5a7ad21..655e3a1e 100644 --- a/src/collision/broadphase/DynamicAABBTree.cpp +++ b/src/collision/broadphase/DynamicAABBTree.cpp @@ -640,6 +640,9 @@ void DynamicAABBTree::reportAllShapesOverlappingWithAABB(const AABB& aabb, List< // Get the next node ID to visit const int32 nodeIDToVisit = stack.pop(); + assert(nodeIDToVisit >= 0); + assert(nodeIDToVisit < mNbAllocatedNodes); + // Skip it if it is a null node if (nodeIDToVisit == TreeNode::NULL_TREE_NODE) continue; diff --git a/src/collision/narrowphase/NarrowPhaseInfoBatch.cpp b/src/collision/narrowphase/NarrowPhaseInfoBatch.cpp index d0266741..dcde890e 100644 --- a/src/collision/narrowphase/NarrowPhaseInfoBatch.cpp +++ b/src/collision/narrowphase/NarrowPhaseInfoBatch.cpp @@ -52,11 +52,12 @@ void NarrowPhaseInfoBatch::reserveMemory() { // Clear all the objects in the batch void NarrowPhaseInfoBatch::clear() { - // TODO OPTI : Better manage this for (uint i=0; i < narrowPhaseInfos.size(); i++) { assert(narrowPhaseInfos[i].nbContactPoints == 0); + // TODO OPTI : Better manage this + // Release the memory of the TriangleShape (this memory was allocated in the // MiddlePhaseTriangleCallback::testTriangle() method) if (narrowPhaseInfos[i].collisionShape1->getName() == CollisionShapeName::TRIANGLE) { diff --git a/src/collision/shapes/ConcaveMeshShape.cpp b/src/collision/shapes/ConcaveMeshShape.cpp index c395b0ec..f977b752 100644 --- a/src/collision/shapes/ConcaveMeshShape.cpp +++ b/src/collision/shapes/ConcaveMeshShape.cpp @@ -139,7 +139,7 @@ void ConcaveMeshShape::computeOverlappingTriangles(const AABB& localAABB, List overlappingNodes(allocator); + List overlappingNodes(allocator, 64); mDynamicAABBTree.reportAllShapesOverlappingWithAABB(aabb, overlappingNodes); const uint nbOverlappingNodes = overlappingNodes.size(); diff --git a/src/collision/shapes/HeightFieldShape.cpp b/src/collision/shapes/HeightFieldShape.cpp index 7c2e99cd..dd056ca3 100644 --- a/src/collision/shapes/HeightFieldShape.cpp +++ b/src/collision/shapes/HeightFieldShape.cpp @@ -236,9 +236,9 @@ bool HeightFieldShape::raycast(const Ray& ray, RaycastInfo& raycastInfo, Collide const AABB rayAABB(Vector3::min(ray.point1, rayEnd), Vector3::max(ray.point1, rayEnd)); // Compute the triangles overlapping with the ray AABB - List triangleVertices(allocator); - List triangleVerticesNormals(allocator); - List shapeIds(allocator); + List triangleVertices(allocator, 64); + List triangleVerticesNormals(allocator, 64); + List shapeIds(allocator, 64); computeOverlappingTriangles(rayAABB, triangleVertices, triangleVerticesNormals, shapeIds, allocator); assert(triangleVertices.size() == triangleVerticesNormals.size()); diff --git a/src/engine/OverlappingPairs.cpp b/src/engine/OverlappingPairs.cpp index 543a32ef..c0ad16ef 100644 --- a/src/engine/OverlappingPairs.cpp +++ b/src/engine/OverlappingPairs.cpp @@ -30,459 +30,194 @@ #include #include #include +#include using namespace reactphysics3d; // Constructor -OverlappingPairs::OverlappingPairs(MemoryAllocator& persistentMemoryAllocator, MemoryAllocator& temporaryMemoryAllocator, ColliderComponents &colliderComponents, +OverlappingPairs::OverlappingPairs(MemoryManager& memoryManager, ColliderComponents& colliderComponents, CollisionBodyComponents& collisionBodyComponents, RigidBodyComponents& rigidBodyComponents, Set &noCollisionPairs, CollisionDispatch &collisionDispatch) - : mPersistentAllocator(persistentMemoryAllocator), mTempMemoryAllocator(temporaryMemoryAllocator), - mNbPairs(0), mConcavePairsStartIndex(0), mPairDataSize(sizeof(uint64) + sizeof(int32) + sizeof(int32) + sizeof(Entity) + - sizeof(Entity) + sizeof(Map) + - sizeof(bool) + sizeof(NarrowPhaseAlgorithmType) + - sizeof(bool) + sizeof(bool) + sizeof(bool)), - mNbAllocatedPairs(0), mBuffer(nullptr), - mMapPairIdToPairIndex(persistentMemoryAllocator), + : mPoolAllocator(memoryManager.getPoolAllocator()), mHeapAllocator(memoryManager.getHeapAllocator()), mConvexPairs(memoryManager.getHeapAllocator()), + mConcavePairs(memoryManager.getHeapAllocator()), mMapConvexPairIdToPairIndex(memoryManager.getHeapAllocator()), mMapConcavePairIdToPairIndex(memoryManager.getHeapAllocator()), mColliderComponents(colliderComponents), mCollisionBodyComponents(collisionBodyComponents), mRigidBodyComponents(rigidBodyComponents), mNoCollisionPairs(noCollisionPairs), mCollisionDispatch(collisionDispatch) { - // Allocate memory for the components data - allocate(INIT_NB_ALLOCATED_PAIRS); } // Destructor OverlappingPairs::~OverlappingPairs() { - // If there are allocated pairs - if (mNbAllocatedPairs > 0) { + // Destroy the convex pairs + while (mConvexPairs.size() > 0) { - // Destroy all the remaining pairs - for (uint32 i = 0; i < mNbPairs; i++) { - - // Remove all the remaining last frame collision info - for (auto it = mLastFrameCollisionInfos[i].begin(); it != mLastFrameCollisionInfos[i].end(); ++it) { - - // Call the constructor - it->second->~LastFrameCollisionInfo(); - - // Release memory - mPersistentAllocator.release(it->second, sizeof(LastFrameCollisionInfo)); - } - - // Remove the involved overlapping pair to the two colliders - assert(mColliderComponents.getOverlappingPairs(mColliders1[i]).find(mPairIds[i]) != mColliderComponents.getOverlappingPairs(mColliders1[i]).end()); - assert(mColliderComponents.getOverlappingPairs(mColliders2[i]).find(mPairIds[i]) != mColliderComponents.getOverlappingPairs(mColliders2[i]).end()); - mColliderComponents.getOverlappingPairs(mColliders1[i]).remove(mPairIds[i]); - mColliderComponents.getOverlappingPairs(mColliders2[i]).remove(mPairIds[i]); - - destroyPair(i); - } - - // Size for the data of a single pair (in bytes) - const size_t totalSizeBytes = mNbAllocatedPairs * mPairDataSize; - - // Release the allocated memory - mPersistentAllocator.release(mBuffer, totalSizeBytes); - } -} - -// Compute the index where we need to insert the new pair -uint64 OverlappingPairs::prepareAddPair(bool isConvexVsConvex) { - - // If we need to allocate more components - if (mNbPairs == mNbAllocatedPairs) { - allocate(mNbAllocatedPairs * 2); + removePair(mConvexPairs.size() - 1, true); } - uint64 index; + // Destroy the concave pairs + while (mConcavePairs.size() > 0) { - // If the pair to add is not convex vs convex or there are no concave pairs yet - if (!isConvexVsConvex) { - - // Add the component at the end of the array - index = mNbPairs; + removePair(mConcavePairs.size() - 1, false); } - // If the pair to add is convex vs convex - else { - - // If there already are convex vs concave pairs - if (mConcavePairsStartIndex != mNbPairs) { - - // Move the first convex vs concave pair to the end of the array - movePairToIndex(mConcavePairsStartIndex, mNbPairs); - } - - index = mConcavePairsStartIndex; - - mConcavePairsStartIndex++; - } - - return index; } // Remove a component at a given index void OverlappingPairs::removePair(uint64 pairId) { - RP3D_PROFILE("OverlappingPairs::removePair()", mProfiler); + assert(mMapConvexPairIdToPairIndex.containsKey(pairId) || mMapConcavePairIdToPairIndex.containsKey(pairId)); - assert(mMapPairIdToPairIndex.containsKey(pairId)); - - uint64 index = mMapPairIdToPairIndex[pairId]; - assert(index < mNbPairs); - - // We want to keep the arrays tightly packed. Therefore, when a pair is removed, - // we replace it with the last element of the array. But we need to make sure that convex - // and concave pairs stay grouped together. - - // Remove all the remaining last frame collision info - for (auto it = mLastFrameCollisionInfos[index].begin(); it != mLastFrameCollisionInfos[index].end(); ++it) { - - // Call the constructor - it->second->~LastFrameCollisionInfo(); - - // Release memory - mPersistentAllocator.release(it->second, sizeof(LastFrameCollisionInfo)); + auto it = mMapConvexPairIdToPairIndex.find(pairId); + if (it != mMapConvexPairIdToPairIndex.end()) { + removePair(it->second, true); } - - // Remove the involved overlapping pair to the two colliders - assert(mColliderComponents.getOverlappingPairs(mColliders1[index]).find(pairId) != mColliderComponents.getOverlappingPairs(mColliders1[index]).end()); - assert(mColliderComponents.getOverlappingPairs(mColliders2[index]).find(pairId) != mColliderComponents.getOverlappingPairs(mColliders2[index]).end()); - mColliderComponents.getOverlappingPairs(mColliders1[index]).remove(pairId); - mColliderComponents.getOverlappingPairs(mColliders2[index]).remove(pairId); - - // Destroy the pair - destroyPair(index); - - // If the pair to remove is convex vs concave - if (index >= mConcavePairsStartIndex) { - - // If the pair is not the last one - if (index != mNbPairs - 1) { - - // We replace it by the last convex vs concave pair - movePairToIndex(mNbPairs - 1, index); - } + else { + removePair(mMapConcavePairIdToPairIndex[pairId], false); } - else { // If the pair to remove is convex vs convex - - // If it not the last convex vs convex pair - if (index != mConcavePairsStartIndex - 1) { - - // We replace it by the last convex vs convex pair - movePairToIndex(mConcavePairsStartIndex - 1, index); - } - - // If there are convex vs concave pairs at the end - if (mConcavePairsStartIndex != mNbPairs) { - - // We replace the last convex vs convex pair by the last convex vs concave pair - movePairToIndex(mNbPairs - 1, mConcavePairsStartIndex - 1); - } - - mConcavePairsStartIndex--; - } - - mNbPairs--; - - assert(mConcavePairsStartIndex <= mNbPairs); - assert(mNbPairs == static_cast(mMapPairIdToPairIndex.size())); } -// Allocate memory for a given number of pairs -void OverlappingPairs::allocate(uint64 nbPairsToAllocate) { +// Remove a component at a given index +void OverlappingPairs::removePair(uint64 pairIndex, bool isConvexVsConvex) { - assert(nbPairsToAllocate > mNbAllocatedPairs); + RP3D_PROFILE("OverlappingPairs::removePair()", mProfiler); - // Size for the data of a single component (in bytes) - const size_t totalSizeBytes = nbPairsToAllocate * mPairDataSize; + if (isConvexVsConvex) { - // Allocate memory - void* newBuffer = mPersistentAllocator.allocate(totalSizeBytes); - assert(newBuffer != nullptr); + const uint nbConvexPairs = mConvexPairs.size(); - // New pointers to components data - uint64* newPairIds = static_cast(newBuffer); - int32* newPairBroadPhaseId1 = reinterpret_cast(newPairIds + nbPairsToAllocate); - int32* newPairBroadPhaseId2 = reinterpret_cast(newPairBroadPhaseId1 + nbPairsToAllocate); - Entity* newColliders1 = reinterpret_cast(newPairBroadPhaseId2 + nbPairsToAllocate); - Entity* newColliders2 = reinterpret_cast(newColliders1 + nbPairsToAllocate); - Map* newLastFrameCollisionInfos = reinterpret_cast*>(newColliders2 + nbPairsToAllocate); - bool* newNeedToTestOverlap = reinterpret_cast(newLastFrameCollisionInfos + nbPairsToAllocate); - NarrowPhaseAlgorithmType* newNarrowPhaseAlgorithmType = reinterpret_cast(newNeedToTestOverlap + nbPairsToAllocate); - bool* newIsShape1Convex = reinterpret_cast(newNarrowPhaseAlgorithmType + nbPairsToAllocate); - bool* wereCollidingInPreviousFrame = reinterpret_cast(newIsShape1Convex + nbPairsToAllocate); - bool* areCollidingInCurrentFrame = reinterpret_cast(wereCollidingInPreviousFrame + nbPairsToAllocate); + assert(pairIndex < nbConvexPairs); - // If there was already pairs before - if (mNbPairs > 0) { + // Remove the involved overlapping pair from the two colliders + assert(mColliderComponents.getOverlappingPairs(mConvexPairs[pairIndex].collider1).find(mConvexPairs[pairIndex].pairID) != mColliderComponents.getOverlappingPairs(mConvexPairs[pairIndex].collider1).end()); + assert(mColliderComponents.getOverlappingPairs(mConvexPairs[pairIndex].collider2).find(mConvexPairs[pairIndex].pairID) != mColliderComponents.getOverlappingPairs(mConvexPairs[pairIndex].collider2).end()); + mColliderComponents.getOverlappingPairs(mConvexPairs[pairIndex].collider1).remove(mConvexPairs[pairIndex].pairID); + mColliderComponents.getOverlappingPairs(mConvexPairs[pairIndex].collider2).remove(mConvexPairs[pairIndex].pairID); - // Copy component data from the previous buffer to the new one - memcpy(newPairIds, mPairIds, mNbPairs * sizeof(uint64)); - memcpy(newPairBroadPhaseId1, mPairBroadPhaseId1, mNbPairs * sizeof(int32)); - memcpy(newPairBroadPhaseId2, mPairBroadPhaseId2, mNbPairs * sizeof(int32)); - memcpy(newColliders1, mColliders1, mNbPairs * sizeof(Entity)); - memcpy(newColliders2, mColliders2, mNbPairs * sizeof(Entity)); - memcpy(newLastFrameCollisionInfos, mLastFrameCollisionInfos, mNbPairs * sizeof(Map)); - memcpy(newNeedToTestOverlap, mNeedToTestOverlap, mNbPairs * sizeof(bool)); - memcpy(newNarrowPhaseAlgorithmType, mNarrowPhaseAlgorithmType, mNbPairs * sizeof(NarrowPhaseAlgorithmType)); - memcpy(newIsShape1Convex, mIsShape1Convex, mNbPairs * sizeof(bool)); - memcpy(wereCollidingInPreviousFrame, mCollidingInPreviousFrame, mNbPairs * sizeof(bool)); - memcpy(areCollidingInCurrentFrame, mCollidingInCurrentFrame, mNbPairs * sizeof(bool)); + assert(mMapConvexPairIdToPairIndex[mConvexPairs[pairIndex].pairID] == pairIndex); + mMapConvexPairIdToPairIndex.remove(mConvexPairs[pairIndex].pairID); - // Deallocate previous memory - mPersistentAllocator.release(mBuffer, mNbAllocatedPairs * mPairDataSize); + // Change the mapping between the pairId and the index in the convex pairs array if we swap the last item with the one to remove + if (mConvexPairs.size() > 1 && pairIndex < (nbConvexPairs - 1)) { + + mMapConvexPairIdToPairIndex[mConvexPairs[nbConvexPairs - 1].pairID] = pairIndex; + } + + // We want to keep the arrays tightly packed. Therefore, when a pair is removed, + // we replace it with the last element of the array. + mConvexPairs.removeAtAndReplaceByLast(pairIndex); } + else { - mBuffer = newBuffer; - mPairIds = newPairIds; - mPairBroadPhaseId1 = newPairBroadPhaseId1; - mPairBroadPhaseId2 = newPairBroadPhaseId2; - mColliders1 = newColliders1; - mColliders2 = newColliders2; - mLastFrameCollisionInfos = newLastFrameCollisionInfos; - mNeedToTestOverlap = newNeedToTestOverlap; - mNarrowPhaseAlgorithmType = newNarrowPhaseAlgorithmType; - mIsShape1Convex = newIsShape1Convex; - mCollidingInPreviousFrame = wereCollidingInPreviousFrame; - mCollidingInCurrentFrame = areCollidingInCurrentFrame; + const uint nbConcavePairs = mConcavePairs.size(); - mNbAllocatedPairs = nbPairsToAllocate; + assert(pairIndex < nbConcavePairs); + + // Remove the involved overlapping pair to the two colliders + assert(mColliderComponents.getOverlappingPairs(mConcavePairs[pairIndex].collider1).find(mConcavePairs[pairIndex].pairID) != mColliderComponents.getOverlappingPairs(mConcavePairs[pairIndex].collider1).end()); + assert(mColliderComponents.getOverlappingPairs(mConcavePairs[pairIndex].collider2).find(mConcavePairs[pairIndex].pairID) != mColliderComponents.getOverlappingPairs(mConcavePairs[pairIndex].collider2).end()); + mColliderComponents.getOverlappingPairs(mConcavePairs[pairIndex].collider1).remove(mConcavePairs[pairIndex].pairID); + mColliderComponents.getOverlappingPairs(mConcavePairs[pairIndex].collider2).remove(mConcavePairs[pairIndex].pairID); + + assert(mMapConcavePairIdToPairIndex[mConcavePairs[pairIndex].pairID] == pairIndex); + mMapConcavePairIdToPairIndex.remove(mConcavePairs[pairIndex].pairID); + + // Destroy all the LastFrameCollisionInfo objects + mConcavePairs[pairIndex].destroyLastFrameCollisionInfos(); + + // Change the mapping between the pairId and the index in the convex pairs array if we swap the last item with the one to remove + if (mConcavePairs.size() > 1 && pairIndex < (nbConcavePairs - 1)) { + + mMapConcavePairIdToPairIndex[mConcavePairs[nbConcavePairs - 1].pairID] = pairIndex; + } + + // We want to keep the arrays tightly packed. Therefore, when a pair is removed, + // we replace it with the last element of the array. + mConcavePairs.removeAtAndReplaceByLast(pairIndex); + } } // Add an overlapping pair -uint64 OverlappingPairs::addPair(Collider* shape1, Collider* shape2) { +uint64 OverlappingPairs::addPair(uint32 collider1Index, uint32 collider2Index, bool isConvexVsConvex) { RP3D_PROFILE("OverlappingPairs::addPair()", mProfiler); - const Entity collider1 = shape1->getEntity(); - const Entity collider2 = shape2->getEntity(); - - const uint collider1Index = mColliderComponents.getEntityIndex(collider1); - const uint collider2Index = mColliderComponents.getEntityIndex(collider2); + assert(mColliderComponents.mBroadPhaseIds[collider1Index] >= 0 && mColliderComponents.mBroadPhaseIds[collider2Index] >= 0); const CollisionShape* collisionShape1 = mColliderComponents.mCollisionShapes[collider1Index]; const CollisionShape* collisionShape2 = mColliderComponents.mCollisionShapes[collider2Index]; - const bool isShape1Convex = collisionShape1->isConvex(); - const bool isShape2Convex = collisionShape2->isConvex(); - const bool isConvexVsConvex = isShape1Convex && isShape2Convex; + const Entity collider1Entity = mColliderComponents.mCollidersEntities[collider1Index]; + const Entity collider2Entity = mColliderComponents.mCollidersEntities[collider2Index]; - // Prepare to add new pair (allocate memory if necessary and compute insertion index) - uint64 index = prepareAddPair(isConvexVsConvex); - - const uint32 broadPhase1Id = static_cast(shape1->getBroadPhaseId()); - const uint32 broadPhase2Id = static_cast(shape2->getBroadPhaseId()); + const uint32 broadPhase1Id = static_cast(mColliderComponents.mBroadPhaseIds[collider1Index]); + const uint32 broadPhase2Id = static_cast(mColliderComponents.mBroadPhaseIds[collider2Index]); // Compute a unique id for the overlapping pair const uint64 pairId = pairNumbers(std::max(broadPhase1Id, broadPhase2Id), std::min(broadPhase1Id, broadPhase2Id)); - assert(!mMapPairIdToPairIndex.containsKey(pairId)); - // Select the narrow phase algorithm to use according to the two collision shapes - NarrowPhaseAlgorithmType algorithmType; if (isConvexVsConvex) { - algorithmType = mCollisionDispatch.selectNarrowPhaseAlgorithm(collisionShape1->getType(), collisionShape2->getType()); + assert(!mMapConvexPairIdToPairIndex.containsKey(pairId)); + NarrowPhaseAlgorithmType algorithmType = mCollisionDispatch.selectNarrowPhaseAlgorithm(collisionShape1->getType(), collisionShape2->getType()); + + // Map the entity with the new component lookup index + mMapConvexPairIdToPairIndex.add(Pair(pairId, mConvexPairs.size())); + + // Create and add a new convex pair + mConvexPairs.emplace(pairId, broadPhase1Id, broadPhase2Id, collider1Entity, collider2Entity, algorithmType); } else { - algorithmType = mCollisionDispatch.selectNarrowPhaseAlgorithm(isShape1Convex ? collisionShape1->getType() : collisionShape2->getType(), + const bool isShape1Convex = collisionShape1->isConvex(); + + assert(!mMapConcavePairIdToPairIndex.containsKey(pairId)); + NarrowPhaseAlgorithmType algorithmType = mCollisionDispatch.selectNarrowPhaseAlgorithm(isShape1Convex ? collisionShape1->getType() : collisionShape2->getType(), CollisionShapeType::CONVEX_POLYHEDRON); + // Map the entity with the new component lookup index + mMapConcavePairIdToPairIndex.add(Pair(pairId, mConcavePairs.size())); + + // Create and add a new concave pair + mConcavePairs.emplace(pairId, broadPhase1Id, broadPhase2Id, collider1Entity, collider2Entity, algorithmType, + isShape1Convex, mPoolAllocator, mHeapAllocator); } - // Insert the new component data - new (mPairIds + index) uint64(pairId); - new (mPairBroadPhaseId1 + index) int32(shape1->getBroadPhaseId()); - new (mPairBroadPhaseId2 + index) int32(shape2->getBroadPhaseId()); - new (mColliders1 + index) Entity(shape1->getEntity()); - new (mColliders2 + index) Entity(shape2->getEntity()); - new (mLastFrameCollisionInfos + index) Map(mPersistentAllocator); - new (mNeedToTestOverlap + index) bool(false); - new (mNarrowPhaseAlgorithmType + index) NarrowPhaseAlgorithmType(algorithmType); - new (mIsShape1Convex + index) bool(isShape1Convex); - new (mCollidingInPreviousFrame + index) bool(false); - new (mCollidingInCurrentFrame + index) bool(false); - - // Map the entity with the new component lookup index - mMapPairIdToPairIndex.add(Pair(pairId, index)); - // Add the involved overlapping pair to the two colliders assert(mColliderComponents.mOverlappingPairs[collider1Index].find(pairId) == mColliderComponents.mOverlappingPairs[collider1Index].end()); assert(mColliderComponents.mOverlappingPairs[collider2Index].find(pairId) == mColliderComponents.mOverlappingPairs[collider2Index].end()); mColliderComponents.mOverlappingPairs[collider1Index].add(pairId); mColliderComponents.mOverlappingPairs[collider2Index].add(pairId); - mNbPairs++; - - assert(mConcavePairsStartIndex <= mNbPairs); - assert(mNbPairs == static_cast(mMapPairIdToPairIndex.size())); - return pairId; } -// Move a pair from a source to a destination index in the pairs array -// The destination location must contain a constructed object -void OverlappingPairs::movePairToIndex(uint64 srcIndex, uint64 destIndex) { - - const uint64 pairId = mPairIds[srcIndex]; - - // Copy the data of the source pair to the destination location - mPairIds[destIndex] = mPairIds[srcIndex]; - mPairBroadPhaseId1[destIndex] = mPairBroadPhaseId1[srcIndex]; - mPairBroadPhaseId2[destIndex] = mPairBroadPhaseId2[srcIndex]; - new (mColliders1 + destIndex) Entity(mColliders1[srcIndex]); - new (mColliders2 + destIndex) Entity(mColliders2[srcIndex]); - new (mLastFrameCollisionInfos + destIndex) Map(mLastFrameCollisionInfos[srcIndex]); - mNeedToTestOverlap[destIndex] = mNeedToTestOverlap[srcIndex]; - new (mNarrowPhaseAlgorithmType + destIndex) NarrowPhaseAlgorithmType(mNarrowPhaseAlgorithmType[srcIndex]); - mIsShape1Convex[destIndex] = mIsShape1Convex[srcIndex]; - mCollidingInPreviousFrame[destIndex] = mCollidingInPreviousFrame[srcIndex]; - mCollidingInCurrentFrame[destIndex] = mCollidingInCurrentFrame[srcIndex]; - - // Destroy the source pair - destroyPair(srcIndex); - - assert(!mMapPairIdToPairIndex.containsKey(pairId)); - - // Update the pairId to pair index mapping - mMapPairIdToPairIndex.add(Pair(pairId, destIndex)); - - assert(mMapPairIdToPairIndex[mPairIds[destIndex]] == destIndex); -} - -// Swap two pairs in the array -void OverlappingPairs::swapPairs(uint64 index1, uint64 index2) { - - // Copy pair 1 data - uint64 pairId = mPairIds[index1]; - int32 pairBroadPhaseId1 = mPairBroadPhaseId1[index1]; - int32 pairBroadPhaseId2 = mPairBroadPhaseId2[index1]; - Entity collider1 = mColliders1[index1]; - Entity collider2 = mColliders2[index1]; - Map lastFrameCollisionInfo(mLastFrameCollisionInfos[index1]); - bool needTestOverlap = mNeedToTestOverlap[index1]; - NarrowPhaseAlgorithmType narrowPhaseAlgorithmType = mNarrowPhaseAlgorithmType[index1]; - bool isShape1Convex = mIsShape1Convex[index1]; - bool wereCollidingInPreviousFrame = mCollidingInPreviousFrame[index1]; - bool areCollidingInCurrentFrame = mCollidingInCurrentFrame[index1]; - - // Destroy pair 1 - destroyPair(index1); - - movePairToIndex(index2, index1); - - // Reconstruct pair 1 at pair 2 location - mPairIds[index2] = pairId; - mPairBroadPhaseId1[index2] = pairBroadPhaseId1; - mPairBroadPhaseId2[index2] = pairBroadPhaseId2; - new (mColliders1 + index2) Entity(collider1); - new (mColliders2 + index2) Entity(collider2); - new (mLastFrameCollisionInfos + index2) Map(lastFrameCollisionInfo); - mNeedToTestOverlap[index2] = needTestOverlap; - new (mNarrowPhaseAlgorithmType + index2) NarrowPhaseAlgorithmType(narrowPhaseAlgorithmType); - mIsShape1Convex[index2] = isShape1Convex; - mCollidingInPreviousFrame[index2] = wereCollidingInPreviousFrame; - mCollidingInCurrentFrame[index2] = areCollidingInCurrentFrame; - - // Update the pairID to pair index mapping - mMapPairIdToPairIndex.add(Pair(pairId, index2)); - - assert(mMapPairIdToPairIndex[mPairIds[index1]] == index1); - assert(mMapPairIdToPairIndex[mPairIds[index2]] == index2); - assert(mNbPairs == static_cast(mMapPairIdToPairIndex.size())); -} - -// Destroy a pair at a given index -void OverlappingPairs::destroyPair(uint64 index) { - - assert(index < mNbPairs); - - assert(mMapPairIdToPairIndex[mPairIds[index]] == index); - - mMapPairIdToPairIndex.remove(mPairIds[index]); - - mColliders1[index].~Entity(); - mColliders2[index].~Entity(); - mLastFrameCollisionInfos[index].~Map(); - mNarrowPhaseAlgorithmType[index].~NarrowPhaseAlgorithmType(); -} - -// Add a new last frame collision info if it does not exist for the given shapes already -LastFrameCollisionInfo* OverlappingPairs::addLastFrameInfoIfNecessary(uint64 pairIndex, uint32 shapeId1, uint32 shapeId2) { - - RP3D_PROFILE("OverlappingPairs::addLastFrameInfoIfNecessary()", mProfiler); - - assert(pairIndex < mNbPairs); - - uint32 maxShapeId = shapeId1; - uint32 minShapeId = shapeId2; - if (shapeId1 < shapeId2) { - maxShapeId = shapeId2; - minShapeId = shapeId1; - } - - // Try to get the corresponding last frame collision info - const uint64 shapesId = pairNumbers(maxShapeId, minShapeId); - - // If there is no collision info for those two shapes already - auto it = mLastFrameCollisionInfos[pairIndex].find(shapesId); - if (it == mLastFrameCollisionInfos[pairIndex].end()) { - - // Create a new collision info - LastFrameCollisionInfo* collisionInfo = new (mPersistentAllocator.allocate(sizeof(LastFrameCollisionInfo))) - LastFrameCollisionInfo(); - - // Add it into the map of collision infos - mLastFrameCollisionInfos[pairIndex].add(Pair(shapesId, collisionInfo)); - - return collisionInfo; - } - else { - - // The existing collision info is not obsolete - it->second->isObsolete = false; - - return it->second; - } -} - // Delete all the obsolete last frame collision info void OverlappingPairs::clearObsoleteLastFrameCollisionInfos() { RP3D_PROFILE("OverlappingPairs::clearObsoleteLastFrameCollisionInfos()", mProfiler); - // For each overlapping pair - for (uint64 i=0; i < mNbPairs; i++) { + // For each concave overlapping pair + const uint64 nbConcavePairs = mConcavePairs.size(); + for (uint64 i=0; i < nbConcavePairs; i++) { - // For each collision info - for (auto it = mLastFrameCollisionInfos[i].begin(); it != mLastFrameCollisionInfos[i].end(); ) { - - // If the collision info is obsolete - if (it->second->isObsolete) { - - // Delete it - it->second->~LastFrameCollisionInfo(); - mPersistentAllocator.release(it->second, sizeof(LastFrameCollisionInfo)); - - it = mLastFrameCollisionInfos[i].remove(it); - } - else { // If the collision info is not obsolete - - // Do not delete it but mark it as obsolete - it->second->isObsolete = true; - - ++it; - } - } + mConcavePairs[i].clearObsoleteLastFrameInfos(); } } // Set the collidingInPreviousFrame value with the collidinginCurrentFrame value for each pair void OverlappingPairs::updateCollidingInPreviousFrame() { - // For each overlapping pair - for (uint64 i=0; i < mNbPairs; i++) { + RP3D_PROFILE("OverlappingPairs::updateCollidingInPreviousFrame()", mProfiler); - mCollidingInPreviousFrame[i] = mCollidingInCurrentFrame[i]; + // For each convex overlapping pair + const uint64 nbConvexPairs = mConvexPairs.size(); + for (uint64 i=0; i < nbConvexPairs; i++) { + + mConvexPairs[i].collidingInPreviousFrame = mConvexPairs[i].collidingInCurrentFrame; + } + + // For each concave overlapping pair + const uint64 nbConcavePairs = mConcavePairs.size(); + for (uint64 i=0; i < nbConcavePairs; i++) { + + mConcavePairs[i].collidingInPreviousFrame = mConcavePairs[i].collidingInCurrentFrame; } } diff --git a/src/systems/BroadPhaseSystem.cpp b/src/systems/BroadPhaseSystem.cpp index 53a2993e..8c1326a6 100644 --- a/src/systems/BroadPhaseSystem.cpp +++ b/src/systems/BroadPhaseSystem.cpp @@ -37,9 +37,9 @@ using namespace reactphysics3d; // Constructor BroadPhaseSystem::BroadPhaseSystem(CollisionDetectionSystem& collisionDetection, ColliderComponents& collidersComponents, TransformComponents& transformComponents, RigidBodyComponents& rigidBodyComponents) - :mDynamicAABBTree(collisionDetection.getMemoryManager().getPoolAllocator(), DYNAMIC_TREE_FAT_AABB_INFLATE_PERCENTAGE), + :mDynamicAABBTree(collisionDetection.getMemoryManager().getHeapAllocator(), DYNAMIC_TREE_FAT_AABB_INFLATE_PERCENTAGE), mCollidersComponents(collidersComponents), mTransformsComponents(transformComponents), - mRigidBodyComponents(rigidBodyComponents), mMovedShapes(collisionDetection.getMemoryManager().getPoolAllocator()), + mRigidBodyComponents(rigidBodyComponents), mMovedShapes(collisionDetection.getMemoryManager().getHeapAllocator()), mCollisionDetection(collisionDetection) { #ifdef IS_RP3D_PROFILING_ENABLED @@ -211,7 +211,7 @@ void BroadPhaseSystem::computeOverlappingPairs(MemoryManager& memoryManager, Lis RP3D_PROFILE("BroadPhaseSystem::computeOverlappingPairs()", mProfiler); // Get the list of the colliders that have moved or have been created in the last frame - List shapesToTest = mMovedShapes.toList(memoryManager.getPoolAllocator()); + List shapesToTest = mMovedShapes.toList(memoryManager.getHeapAllocator()); // Ask the dynamic AABB tree to report all collision shapes that overlap with the shapes to test mDynamicAABBTree.reportAllShapesOverlappingWithShapes(shapesToTest, 0, shapesToTest.size(), overlappingNodes); diff --git a/src/systems/CollisionDetectionSystem.cpp b/src/systems/CollisionDetectionSystem.cpp index 4315cdb5..ab706182 100644 --- a/src/systems/CollisionDetectionSystem.cpp +++ b/src/systems/CollisionDetectionSystem.cpp @@ -55,8 +55,8 @@ CollisionDetectionSystem::CollisionDetectionSystem(PhysicsWorld* world, Collider : mMemoryManager(memoryManager), mCollidersComponents(collidersComponents), mRigidBodyComponents(rigidBodyComponents), mCollisionDispatch(mMemoryManager.getPoolAllocator()), mWorld(world), mNoCollisionPairs(mMemoryManager.getPoolAllocator()), - mOverlappingPairs(mMemoryManager.getPoolAllocator(), mMemoryManager.getSingleFrameAllocator(), mCollidersComponents, - collisionBodyComponents, rigidBodyComponents, mNoCollisionPairs, mCollisionDispatch), + mOverlappingPairs(mMemoryManager, mCollidersComponents, collisionBodyComponents, rigidBodyComponents, + mNoCollisionPairs, mCollisionDispatch), mBroadPhaseSystem(*this, mCollidersComponents, transformComponents, rigidBodyComponents), mMapBroadPhaseIdToColliderEntity(memoryManager.getPoolAllocator()), mNarrowPhaseInput(mMemoryManager.getSingleFrameAllocator(), mOverlappingPairs), mPotentialContactPoints(mMemoryManager.getSingleFrameAllocator()), @@ -102,7 +102,7 @@ void CollisionDetectionSystem::computeBroadPhase() { // Ask the broad-phase to compute all the shapes overlapping with the shapes that // have moved or have been added in the last frame. This call can only add new // overlapping pairs in the collision detection. - List> overlappingNodes(mMemoryManager.getPoolAllocator(), 32); + List> overlappingNodes(mMemoryManager.getHeapAllocator(), 32); mBroadPhaseSystem.computeOverlappingPairs(mMemoryManager, overlappingNodes); // Create new overlapping pairs if necessary @@ -117,25 +117,55 @@ void CollisionDetectionSystem::removeNonOverlappingPairs() { RP3D_PROFILE("CollisionDetectionSystem::removeNonOverlappingPairs()", mProfiler); - for (uint64 i=0; i < mOverlappingPairs.getNbPairs(); i++) { + // For each convex pairs + for (uint64 i=0; i < mOverlappingPairs.mConvexPairs.size(); i++) { + + OverlappingPairs::ConvexOverlappingPair& overlappingPair = mOverlappingPairs.mConvexPairs[i]; // Check if we need to test overlap. If so, test if the two shapes are still overlapping. // Otherwise, we destroy the overlapping pair - if (mOverlappingPairs.mNeedToTestOverlap[i]) { + if (overlappingPair.needToTestOverlap) { - if(mBroadPhaseSystem.testOverlappingShapes(mOverlappingPairs.mPairBroadPhaseId1[i], mOverlappingPairs.mPairBroadPhaseId2[i])) { - mOverlappingPairs.mNeedToTestOverlap[i] = false; + if(mBroadPhaseSystem.testOverlappingShapes(overlappingPair.broadPhaseId1, overlappingPair.broadPhaseId2)) { + overlappingPair.needToTestOverlap = false; } else { // If the two colliders of the pair were colliding in the previous frame - if (mOverlappingPairs.mCollidingInPreviousFrame[i]) { + if (overlappingPair.collidingInPreviousFrame) { // Create a new lost contact pair - addLostContactPair(i); + addLostContactPair(overlappingPair); } - mOverlappingPairs.removePair(mOverlappingPairs.mPairIds[i]); + mOverlappingPairs.removePair(i, true); + i--; + } + } + } + + // For each concave pairs + for (uint64 i=0; i < mOverlappingPairs.mConcavePairs.size(); i++) { + + OverlappingPairs::ConcaveOverlappingPair& overlappingPair = mOverlappingPairs.mConcavePairs[i]; + + // Check if we need to test overlap. If so, test if the two shapes are still overlapping. + // Otherwise, we destroy the overlapping pair + if (overlappingPair.needToTestOverlap) { + + if(mBroadPhaseSystem.testOverlappingShapes(overlappingPair.broadPhaseId1, overlappingPair.broadPhaseId2)) { + overlappingPair.needToTestOverlap = false; + } + else { + + // If the two colliders of the pair were colliding in the previous frame + if (overlappingPair.collidingInPreviousFrame) { + + // Create a new lost contact pair + addLostContactPair(overlappingPair); + } + + mOverlappingPairs.removePair(i, false); i--; } } @@ -143,20 +173,20 @@ void CollisionDetectionSystem::removeNonOverlappingPairs() { } // Add a lost contact pair (pair of colliders that are not in contact anymore) -void CollisionDetectionSystem::addLostContactPair(uint64 overlappingPairIndex) { +void CollisionDetectionSystem::addLostContactPair(OverlappingPairs::OverlappingPair& overlappingPair) { - const Entity collider1Entity = mOverlappingPairs.mColliders1[overlappingPairIndex]; - const Entity collider2Entity = mOverlappingPairs.mColliders2[overlappingPairIndex]; + const uint32 collider1Index = mCollidersComponents.getEntityIndex(overlappingPair.collider1); + const uint32 collider2Index = mCollidersComponents.getEntityIndex(overlappingPair.collider2); - const Entity body1Entity = mCollidersComponents.getBody(collider1Entity); - const Entity body2Entity = mCollidersComponents.getBody(collider2Entity); + const Entity body1Entity = mCollidersComponents.mBodiesEntities[collider1Index]; + const Entity body2Entity = mCollidersComponents.mBodiesEntities[collider2Index]; - const bool isCollider1Trigger = mCollidersComponents.getIsTrigger(collider1Entity); - const bool isCollider2Trigger = mCollidersComponents.getIsTrigger(collider2Entity); + const bool isCollider1Trigger = mCollidersComponents.mIsTrigger[collider1Index]; + const bool isCollider2Trigger = mCollidersComponents.mIsTrigger[collider2Index]; const bool isTrigger = isCollider1Trigger || isCollider2Trigger; // Create a lost contact pair - ContactPair lostContactPair(mOverlappingPairs.mPairIds[overlappingPairIndex], body1Entity, body2Entity, collider1Entity, collider2Entity, mLostContactPairs.size(), + ContactPair lostContactPair(overlappingPair.pairID, body1Entity, body2Entity, overlappingPair.collider1, overlappingPair.collider2, mLostContactPairs.size(), true, isTrigger); mLostContactPairs.add(lostContactPair); } @@ -219,8 +249,8 @@ void CollisionDetectionSystem::updateOverlappingPairs(const ListgetCollisionShape()->isConvex() || shape2->getCollisionShape()->isConvex()) { + const bool isShape1Convex = shape1->getCollisionShape()->isConvex(); + const bool isShape2Convex = shape2->getCollisionShape()->isConvex(); + if (isShape1Convex || isShape2Convex) { // Add the new overlapping pair - mOverlappingPairs.addPair(shape1, shape2); + mOverlappingPairs.addPair(collider1Index, collider2Index, isShape1Convex && isShape2Convex); } } } else { // We do not need to test the pair for overlap because it has just been reported that they still overlap - mOverlappingPairs.mNeedToTestOverlap[it->second] = false; + overlappingPair->needToTestOverlap = false; } } } @@ -267,16 +299,18 @@ void CollisionDetectionSystem::computeMiddlePhase(NarrowPhaseInput& narrowPhaseI mOverlappingPairs.clearObsoleteLastFrameCollisionInfos(); // For each possible convex vs convex pair of bodies - const uint64 nbConvexVsConvexPairs = mOverlappingPairs.getNbConvexVsConvexPairs(); + const uint64 nbConvexVsConvexPairs = mOverlappingPairs.mConvexPairs.size(); for (uint64 i=0; i < nbConvexVsConvexPairs; i++) { - assert(mCollidersComponents.getBroadPhaseId(mOverlappingPairs.mColliders1[i]) != -1); - assert(mCollidersComponents.getBroadPhaseId(mOverlappingPairs.mColliders2[i]) != -1); - assert(mCollidersComponents.getBroadPhaseId(mOverlappingPairs.mColliders1[i]) != mCollidersComponents.getBroadPhaseId(mOverlappingPairs.mColliders2[i])); + OverlappingPairs::ConvexOverlappingPair& overlappingPair = mOverlappingPairs.mConvexPairs[i]; + + assert(mCollidersComponents.getBroadPhaseId(overlappingPair.collider1) != -1); + assert(mCollidersComponents.getBroadPhaseId(overlappingPair.collider2) != -1); + assert(mCollidersComponents.getBroadPhaseId(overlappingPair.collider1) != mCollidersComponents.getBroadPhaseId(overlappingPair.collider2)); - const Entity collider1Entity = mOverlappingPairs.mColliders1[i]; - const Entity collider2Entity = mOverlappingPairs.mColliders2[i]; + const Entity collider1Entity = overlappingPair.collider1; + const Entity collider2Entity = overlappingPair.collider2; const uint collider1Index = mCollidersComponents.getEntityIndex(collider1Entity); const uint collider2Index = mCollidersComponents.getEntityIndex(collider2Entity); @@ -284,7 +318,7 @@ void CollisionDetectionSystem::computeMiddlePhase(NarrowPhaseInput& narrowPhaseI CollisionShape* collisionShape1 = mCollidersComponents.mCollisionShapes[collider1Index]; CollisionShape* collisionShape2 = mCollidersComponents.mCollisionShapes[collider2Index]; - NarrowPhaseAlgorithmType algorithmType = mOverlappingPairs.mNarrowPhaseAlgorithmType[i]; + NarrowPhaseAlgorithmType algorithmType = overlappingPair.narrowPhaseAlgorithmType; const bool isCollider1Trigger = mCollidersComponents.mIsTrigger[collider1Index]; const bool isCollider2Trigger = mCollidersComponents.mIsTrigger[collider2Index]; @@ -292,32 +326,34 @@ void CollisionDetectionSystem::computeMiddlePhase(NarrowPhaseInput& narrowPhaseI // No middle-phase is necessary, simply create a narrow phase info // for the narrow-phase collision detection - narrowPhaseInput.addNarrowPhaseTest(mOverlappingPairs.mPairIds[i], i, collider1Entity, collider2Entity, collisionShape1, collisionShape2, - mCollidersComponents.mLocalToWorldTransforms[collider1Index], - mCollidersComponents.mLocalToWorldTransforms[collider2Index], - algorithmType, reportContacts, mMemoryManager.getSingleFrameAllocator()); + narrowPhaseInput.addNarrowPhaseTest(overlappingPair.pairID, collider1Entity, collider2Entity, collisionShape1, collisionShape2, + mCollidersComponents.mLocalToWorldTransforms[collider1Index], + mCollidersComponents.mLocalToWorldTransforms[collider2Index], + algorithmType, reportContacts, &overlappingPair.lastFrameCollisionInfo, + mMemoryManager.getSingleFrameAllocator()); - mOverlappingPairs.mCollidingInCurrentFrame[i] = false; + overlappingPair.collidingInCurrentFrame = false; } // For each possible convex vs concave pair of bodies - const uint64 convexVsConcaveStartIndex = mOverlappingPairs.getConvexVsConcavePairsStartIndex(); - const uint64 nbConvexVsConcavePairs = mOverlappingPairs.getNbConvexVsConcavePairs(); - for (uint64 i=convexVsConcaveStartIndex; i < convexVsConcaveStartIndex + nbConvexVsConcavePairs; i++) { + const uint64 nbConcavePairs = mOverlappingPairs.mConcavePairs.size(); + for (uint64 i=0; i < nbConcavePairs; i++) { - assert(mCollidersComponents.getBroadPhaseId(mOverlappingPairs.mColliders1[i]) != -1); - assert(mCollidersComponents.getBroadPhaseId(mOverlappingPairs.mColliders2[i]) != -1); - assert(mCollidersComponents.getBroadPhaseId(mOverlappingPairs.mColliders1[i]) != mCollidersComponents.getBroadPhaseId(mOverlappingPairs.mColliders2[i])); + OverlappingPairs::ConcaveOverlappingPair& overlappingPair = mOverlappingPairs.mConcavePairs[i]; - computeConvexVsConcaveMiddlePhase(i, mMemoryManager.getSingleFrameAllocator(), narrowPhaseInput); + assert(mCollidersComponents.getBroadPhaseId(overlappingPair.collider1) != -1); + assert(mCollidersComponents.getBroadPhaseId(overlappingPair.collider2) != -1); + assert(mCollidersComponents.getBroadPhaseId(overlappingPair.collider1) != mCollidersComponents.getBroadPhaseId(overlappingPair.collider2)); - mOverlappingPairs.mCollidingInCurrentFrame[i] = false; + computeConvexVsConcaveMiddlePhase(overlappingPair, mMemoryManager.getSingleFrameAllocator(), narrowPhaseInput); + + overlappingPair.collidingInCurrentFrame = false; } } // Compute the middle-phase collision detection -void CollisionDetectionSystem::computeMiddlePhaseCollisionSnapshot(List& convexPairs, List& concavePairs, NarrowPhaseInput& narrowPhaseInput, - bool reportContacts) { +void CollisionDetectionSystem::computeMiddlePhaseCollisionSnapshot(List& convexPairs, List& concavePairs, + NarrowPhaseInput& narrowPhaseInput, bool reportContacts) { RP3D_PROFILE("CollisionDetectionSystem::computeMiddlePhase()", mProfiler); @@ -333,11 +369,11 @@ void CollisionDetectionSystem::computeMiddlePhaseCollisionSnapshot(List& const uint64 pairId = convexPairs[p]; - const uint64 pairIndex = mOverlappingPairs.mMapPairIdToPairIndex[pairId]; - assert(pairIndex < mOverlappingPairs.getNbPairs()); + const uint64 pairIndex = mOverlappingPairs.mMapConvexPairIdToPairIndex[pairId]; + assert(pairIndex < mOverlappingPairs.mConvexPairs.size()); - const Entity collider1Entity = mOverlappingPairs.mColliders1[pairIndex]; - const Entity collider2Entity = mOverlappingPairs.mColliders2[pairIndex]; + const Entity collider1Entity = mOverlappingPairs.mConvexPairs[pairIndex].collider1; + const Entity collider2Entity = mOverlappingPairs.mConvexPairs[pairIndex].collider2; const uint collider1Index = mCollidersComponents.getEntityIndex(collider1Entity); const uint collider2Index = mCollidersComponents.getEntityIndex(collider2Entity); @@ -349,14 +385,14 @@ void CollisionDetectionSystem::computeMiddlePhaseCollisionSnapshot(List& CollisionShape* collisionShape1 = mCollidersComponents.mCollisionShapes[collider1Index]; CollisionShape* collisionShape2 = mCollidersComponents.mCollisionShapes[collider2Index]; - NarrowPhaseAlgorithmType algorithmType = mOverlappingPairs.mNarrowPhaseAlgorithmType[pairIndex]; + NarrowPhaseAlgorithmType algorithmType = mOverlappingPairs.mConvexPairs[pairIndex].narrowPhaseAlgorithmType; // No middle-phase is necessary, simply create a narrow phase info // for the narrow-phase collision detection - narrowPhaseInput.addNarrowPhaseTest(pairId, pairIndex, collider1Entity, collider2Entity, collisionShape1, collisionShape2, + narrowPhaseInput.addNarrowPhaseTest(pairId, collider1Entity, collider2Entity, collisionShape1, collisionShape2, mCollidersComponents.mLocalToWorldTransforms[collider1Index], mCollidersComponents.mLocalToWorldTransforms[collider2Index], - algorithmType, reportContacts, mMemoryManager.getSingleFrameAllocator()); + algorithmType, reportContacts, &mOverlappingPairs.mConvexPairs[pairIndex].lastFrameCollisionInfo, mMemoryManager.getSingleFrameAllocator()); } @@ -365,23 +401,23 @@ void CollisionDetectionSystem::computeMiddlePhaseCollisionSnapshot(List& for (uint p=0; p < nbConcavePairs; p++) { const uint64 pairId = concavePairs[p]; - const uint64 pairIndex = mOverlappingPairs.mMapPairIdToPairIndex[pairId]; + const uint64 pairIndex = mOverlappingPairs.mMapConcavePairIdToPairIndex[pairId]; - assert(mCollidersComponents.getBroadPhaseId(mOverlappingPairs.mColliders1[pairIndex]) != -1); - assert(mCollidersComponents.getBroadPhaseId(mOverlappingPairs.mColliders2[pairIndex]) != -1); - assert(mCollidersComponents.getBroadPhaseId(mOverlappingPairs.mColliders1[pairIndex]) != mCollidersComponents.getBroadPhaseId(mOverlappingPairs.mColliders2[pairIndex])); + assert(mCollidersComponents.getBroadPhaseId(mOverlappingPairs.mConcavePairs[pairIndex].collider1) != -1); + assert(mCollidersComponents.getBroadPhaseId(mOverlappingPairs.mConcavePairs[pairIndex].collider2) != -1); + assert(mCollidersComponents.getBroadPhaseId(mOverlappingPairs.mConcavePairs[pairIndex].collider1) != mCollidersComponents.getBroadPhaseId(mOverlappingPairs.mConcavePairs[pairIndex].collider2)); - computeConvexVsConcaveMiddlePhase(pairIndex, mMemoryManager.getSingleFrameAllocator(), narrowPhaseInput); + computeConvexVsConcaveMiddlePhase(mOverlappingPairs.mConcavePairs[pairIndex], mMemoryManager.getSingleFrameAllocator(), narrowPhaseInput); } } // Compute the concave vs convex middle-phase algorithm for a given pair of bodies -void CollisionDetectionSystem::computeConvexVsConcaveMiddlePhase(uint64 pairIndex, MemoryAllocator& allocator, NarrowPhaseInput& narrowPhaseInput) { +void CollisionDetectionSystem::computeConvexVsConcaveMiddlePhase(OverlappingPairs::ConcaveOverlappingPair& overlappingPair, MemoryAllocator& allocator, NarrowPhaseInput& narrowPhaseInput) { RP3D_PROFILE("CollisionDetectionSystem::computeConvexVsConcaveMiddlePhase()", mProfiler); - const Entity collider1 = mOverlappingPairs.mColliders1[pairIndex]; - const Entity collider2 = mOverlappingPairs.mColliders2[pairIndex]; + const Entity collider1 = overlappingPair.collider1; + const Entity collider2 = overlappingPair.collider2; const uint collider1Index = mCollidersComponents.getEntityIndex(collider1); const uint collider2Index = mCollidersComponents.getEntityIndex(collider2); @@ -394,8 +430,7 @@ void CollisionDetectionSystem::computeConvexVsConcaveMiddlePhase(uint64 pairInde // Collision shape 1 is convex, collision shape 2 is concave ConvexShape* convexShape; ConcaveShape* concaveShape; - const bool isShape1Convex = mOverlappingPairs.mIsShape1Convex[pairIndex]; - if (isShape1Convex) { + if (overlappingPair.isShape1Convex) { convexShape = static_cast(mCollidersComponents.mCollisionShapes[collider1Index]); concaveShape = static_cast(mCollidersComponents.mCollisionShapes[collider2Index]); convexToConcaveTransform = shape2LocalToWorldTransform.getInverse() * shape1LocalToWorldTransform; @@ -408,16 +443,16 @@ void CollisionDetectionSystem::computeConvexVsConcaveMiddlePhase(uint64 pairInde assert(convexShape->isConvex()); assert(!concaveShape->isConvex()); - assert(mOverlappingPairs.mNarrowPhaseAlgorithmType[pairIndex] != NarrowPhaseAlgorithmType::None); + assert(overlappingPair.narrowPhaseAlgorithmType != NarrowPhaseAlgorithmType::None); // Compute the convex shape AABB in the local-space of the convex shape AABB aabb; convexShape->computeAABB(aabb, convexToConcaveTransform); // Compute the concave shape triangles that are overlapping with the convex mesh AABB - List triangleVertices(allocator); - List triangleVerticesNormals(allocator); - List shapeIds(allocator); + List triangleVertices(allocator, 64); + List triangleVerticesNormals(allocator, 64); + List shapeIds(allocator, 64); concaveShape->computeOverlappingTriangles(aabb, triangleVertices, triangleVerticesNormals, shapeIds, allocator); assert(triangleVertices.size() == triangleVerticesNormals.size()); @@ -429,10 +464,20 @@ void CollisionDetectionSystem::computeConvexVsConcaveMiddlePhase(uint64 pairInde const bool isCollider2Trigger = mCollidersComponents.mIsTrigger[collider2Index]; const bool reportContacts = !isCollider1Trigger && !isCollider2Trigger; + CollisionShape* shape1; + CollisionShape* shape2; + + if (overlappingPair.isShape1Convex) { + shape1 = convexShape; + } + else { + shape2 = convexShape; + } + // For each overlapping triangle const uint nbShapeIds = shapeIds.size(); - for (uint i=0; i < nbShapeIds; i++) - { + for (uint i=0; i < nbShapeIds; i++) { + // Create a triangle collision shape (the allocated memory for the TriangleShape will be released in the // destructor of the corresponding NarrowPhaseInfo. TriangleShape* triangleShape = new (allocator.allocate(sizeof(TriangleShape))) @@ -446,11 +491,20 @@ void CollisionDetectionSystem::computeConvexVsConcaveMiddlePhase(uint64 pairInde #endif + if (overlappingPair.isShape1Convex) { + shape2 = triangleShape; + } + else { + shape1 = triangleShape; + } + + // Add a collision info for the two collision shapes into the overlapping pair (if not present yet) + LastFrameCollisionInfo* lastFrameInfo = overlappingPair.addLastFrameInfoIfNecessary(shape1->getId(), shape2->getId()); + // Create a narrow phase info for the narrow-phase collision detection - narrowPhaseInput.addNarrowPhaseTest(mOverlappingPairs.mPairIds[pairIndex], pairIndex, collider1, collider2, isShape1Convex ? convexShape : triangleShape, - isShape1Convex ? triangleShape : convexShape, - shape1LocalToWorldTransform, shape2LocalToWorldTransform, - mOverlappingPairs.mNarrowPhaseAlgorithmType[pairIndex], reportContacts, allocator); + narrowPhaseInput.addNarrowPhaseTest(overlappingPair.pairID, collider1, collider2, shape1, shape2, + shape1LocalToWorldTransform, shape2LocalToWorldTransform, + overlappingPair.narrowPhaseAlgorithmType, reportContacts, lastFrameInfo, allocator); } } @@ -624,7 +678,8 @@ void CollisionDetectionSystem::notifyOverlappingPairsToTestOverlap(Collider* col // Get the overlapping pairs involved with this collider List& overlappingPairs = mCollidersComponents.getOverlappingPairs(collider->getEntity()); - for (uint i=0; i < overlappingPairs.size(); i++) { + const uint nbPairs = overlappingPairs.size(); + for (uint i=0; i < nbPairs; i++) { // Notify that the overlapping pair needs to be testbed for overlap mOverlappingPairs.setNeedToTestOverlap(overlappingPairs[i], true); @@ -802,17 +857,34 @@ void CollisionDetectionSystem::createContacts() { // Compute the lost contact pairs (contact pairs in contact in the previous frame but not in the current one) void CollisionDetectionSystem::computeLostContactPairs() { - // For each overlapping pair - for (uint i=0; i < mOverlappingPairs.getNbPairs(); i++) { + // For each convex pair + const uint nbConvexPairs = mOverlappingPairs.mConvexPairs.size(); + for (uint i=0; i < nbConvexPairs; i++) { // If the two colliders of the pair were colliding in the previous frame but not in the current one - if (mOverlappingPairs.mCollidingInPreviousFrame[i] && !mOverlappingPairs.mCollidingInCurrentFrame[i]) { + if (mOverlappingPairs.mConvexPairs[i].collidingInPreviousFrame && !mOverlappingPairs.mConvexPairs[i].collidingInCurrentFrame) { // If both bodies still exist - if (mCollidersComponents.hasComponent(mOverlappingPairs.mColliders1[i]) && mCollidersComponents.hasComponent(mOverlappingPairs.mColliders2[i])) { + if (mCollidersComponents.hasComponent(mOverlappingPairs.mConvexPairs[i].collider1) && mCollidersComponents.hasComponent(mOverlappingPairs.mConvexPairs[i].collider2)) { // Create a lost contact pair - addLostContactPair(i); + addLostContactPair(mOverlappingPairs.mConvexPairs[i]); + } + } + } + + // For each convex pair + const uint nbConcavePairs = mOverlappingPairs.mConcavePairs.size(); + for (uint i=0; i < nbConcavePairs; i++) { + + // If the two colliders of the pair were colliding in the previous frame but not in the current one + if (mOverlappingPairs.mConcavePairs[i].collidingInPreviousFrame && !mOverlappingPairs.mConcavePairs[i].collidingInCurrentFrame) { + + // If both bodies still exist + if (mCollidersComponents.hasComponent(mOverlappingPairs.mConcavePairs[i].collider1) && mCollidersComponents.hasComponent(mOverlappingPairs.mConcavePairs[i].collider2)) { + + // Create a lost contact pair + addLostContactPair(mOverlappingPairs.mConcavePairs[i]); } } } @@ -1025,13 +1097,14 @@ void CollisionDetectionSystem::processPotentialContacts(NarrowPhaseInfoBatch& na // For each narrow phase info object for(uint i=0; i < nbObjects; i++) { - const uint64 pairId = narrowPhaseInfoBatch.narrowPhaseInfos[i].overlappingPairId; - const uint64 pairIndex = mOverlappingPairs.mMapPairIdToPairIndex[pairId]; - // If the two colliders are colliding if (narrowPhaseInfoBatch.narrowPhaseInfos[i].isColliding) { - mOverlappingPairs.mCollidingInCurrentFrame[pairIndex] = true; + const uint64 pairId = narrowPhaseInfoBatch.narrowPhaseInfos[i].overlappingPairId; + OverlappingPairs::OverlappingPair* overlappingPair = mOverlappingPairs.getOverlappingPair(pairId); + assert(overlappingPair != nullptr); + + overlappingPair->collidingInCurrentFrame = true; const Entity collider1Entity = narrowPhaseInfoBatch.narrowPhaseInfos[i].colliderEntity1; const Entity collider2Entity = narrowPhaseInfoBatch.narrowPhaseInfos[i].colliderEntity2; @@ -1043,8 +1116,7 @@ void CollisionDetectionSystem::processPotentialContacts(NarrowPhaseInfoBatch& na const Entity body2Entity = mCollidersComponents.mBodiesEntities[collider2Index]; // If we have a convex vs convex collision (if we consider the base collision shapes of the colliders) - if (mCollidersComponents.mCollisionShapes[collider1Index]->isConvex() && - mCollidersComponents.mCollisionShapes[collider2Index]->isConvex()) { + if (mCollidersComponents.mCollisionShapes[collider1Index]->isConvex() && mCollidersComponents.mCollisionShapes[collider2Index]->isConvex()) { // Create a new ContactPair @@ -1055,7 +1127,7 @@ void CollisionDetectionSystem::processPotentialContacts(NarrowPhaseInfoBatch& na const uint newContactPairIndex = contactPairs->size(); contactPairs->emplace(pairId, body1Entity, body2Entity, collider1Entity, collider2Entity, - newContactPairIndex, mOverlappingPairs.getCollidingInPreviousFrame(pairId), isTrigger); + newContactPairIndex, overlappingPair->collidingInPreviousFrame, isTrigger); ContactPair* pairContact = &((*contactPairs)[newContactPairIndex]); @@ -1102,7 +1174,7 @@ void CollisionDetectionSystem::processPotentialContacts(NarrowPhaseInfoBatch& na const uint newContactPairIndex = contactPairs->size(); contactPairs->emplace(pairId, body1Entity, body2Entity, collider1Entity, collider2Entity, - newContactPairIndex, mOverlappingPairs.getCollidingInPreviousFrame(pairId), isTrigger); + newContactPairIndex, overlappingPair->collidingInPreviousFrame , isTrigger); pairContact = &((*contactPairs)[newContactPairIndex]); mapPairIdToContactPairIndex.add(Pair(pairId, newContactPairIndex)); @@ -1236,9 +1308,7 @@ void CollisionDetectionSystem::reducePotentialContactManifolds(List // If there are two many contact points in the manifold if (manifold.nbPotentialContactPoints > MAX_CONTACT_POINTS_IN_MANIFOLD) { - Entity collider1 = mOverlappingPairs.mColliders1[mOverlappingPairs.mMapPairIdToPairIndex[manifold.pairId]]; - - Transform shape1LocalToWorldTransoform = mCollidersComponents.getLocalToWorldTransform(collider1); + Transform shape1LocalToWorldTransoform = mCollidersComponents.getLocalToWorldTransform(pairContact.collider1Entity); // Reduce the number of contact points in the manifold reduceContactPoints(manifold, shape1LocalToWorldTransoform, potentialContactPoints); @@ -1655,18 +1725,25 @@ void CollisionDetectionSystem::testCollision(CollisionCallback& callback) { // Filter the overlapping pairs to keep only the pairs where a given body is involved void CollisionDetectionSystem::filterOverlappingPairs(Entity bodyEntity, List& convexPairs, List& concavePairs) const { - // For each possible collision pair of bodies - for (uint i=0; i < mOverlappingPairs.getNbPairs(); i++) { + // For each convex pairs + const uint nbConvexPairs = mOverlappingPairs.mConvexPairs.size(); + for (uint i=0; i < nbConvexPairs; i++) { - if (mCollidersComponents.getBody(mOverlappingPairs.mColliders1[i]) == bodyEntity || - mCollidersComponents.getBody(mOverlappingPairs.mColliders2[i]) == bodyEntity) { + if (mCollidersComponents.getBody(mOverlappingPairs.mConvexPairs[i].collider1) == bodyEntity || + mCollidersComponents.getBody(mOverlappingPairs.mConvexPairs[i].collider2) == bodyEntity) { - if (i < mOverlappingPairs.getNbConvexVsConvexPairs()) { - convexPairs.add(mOverlappingPairs.mPairIds[i]); - } - else { - concavePairs.add(mOverlappingPairs.mPairIds[i]); - } + convexPairs.add(mOverlappingPairs.mConvexPairs[i].pairID); + } + } + + // For each concave pairs + const uint nbConcavePairs = mOverlappingPairs.mConcavePairs.size(); + for (uint i=0; i < nbConcavePairs; i++) { + + if (mCollidersComponents.getBody(mOverlappingPairs.mConcavePairs[i].collider1) == bodyEntity || + mCollidersComponents.getBody(mOverlappingPairs.mConcavePairs[i].collider2) == bodyEntity) { + + concavePairs.add(mOverlappingPairs.mConcavePairs[i].pairID); } } } @@ -1674,21 +1751,31 @@ void CollisionDetectionSystem::filterOverlappingPairs(Entity bodyEntity, List& convexPairs, List& concavePairs) const { - // For each possible collision pair of bodies - for (uint i=0; i < mOverlappingPairs.getNbPairs(); i++) { + // For each convex pair + const uint nbConvexPairs = mOverlappingPairs.mConvexPairs.size(); + for (uint i=0; i < nbConvexPairs; i++) { - const Entity collider1Body = mCollidersComponents.getBody(mOverlappingPairs.mColliders1[i]); - const Entity collider2Body = mCollidersComponents.getBody(mOverlappingPairs.mColliders2[i]); + const Entity collider1Body = mCollidersComponents.getBody(mOverlappingPairs.mConvexPairs[i].collider1); + const Entity collider2Body = mCollidersComponents.getBody(mOverlappingPairs.mConvexPairs[i].collider2); if ((collider1Body == body1Entity && collider2Body == body2Entity) || (collider1Body == body2Entity && collider2Body == body1Entity)) { - if (i < mOverlappingPairs.getNbConvexVsConvexPairs()) { - convexPairs.add(mOverlappingPairs.mPairIds[i]); - } - else { - concavePairs.add(mOverlappingPairs.mPairIds[i]); - } + convexPairs.add(mOverlappingPairs.mConvexPairs[i].pairID); + } + } + + // For each concave pair + const uint nbConcavePairs = mOverlappingPairs.mConcavePairs.size(); + for (uint i=0; i < nbConcavePairs; i++) { + + const Entity collider1Body = mCollidersComponents.getBody(mOverlappingPairs.mConcavePairs[i].collider1); + const Entity collider2Body = mCollidersComponents.getBody(mOverlappingPairs.mConcavePairs[i].collider2); + + if ((collider1Body == body1Entity && collider2Body == body2Entity) || + (collider1Body == body2Entity && collider2Body == body1Entity)) { + + concavePairs.add(mOverlappingPairs.mConcavePairs[i].pairID); } } } From de6630a03d9369c0b7972047b4b6711693a6513f Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Fri, 28 Aug 2020 23:10:19 +0200 Subject: [PATCH 22/74] Refactoring and optimization of List and Set containers --- include/reactphysics3d/containers/Map.h | 733 ++++++++---------- include/reactphysics3d/containers/Set.h | 685 ++++++++-------- include/reactphysics3d/engine/PhysicsCommon.h | 34 +- .../mathematics/mathematics_functions.h | 19 +- src/engine/PhysicsCommon.cpp | 174 ++++- src/mathematics/mathematics_functions.cpp | 24 - test/main.cpp | 2 +- test/tests/containers/TestList.h | 2 +- .../mathematics/TestMathematicsFunctions.h | 28 + 9 files changed, 844 insertions(+), 857 deletions(-) diff --git a/include/reactphysics3d/containers/Map.h b/include/reactphysics3d/containers/Map.h index a486f529..6bb4df00 100755 --- a/include/reactphysics3d/containers/Map.h +++ b/include/reactphysics3d/containers/Map.h @@ -47,202 +47,59 @@ class Map { private: - /// An entry of the map - struct Entry { - - size_t hashCode; // Hash code of the entry - int next; // Index of the next entry - Pair* keyValue; // Pointer to the pair with key and value - - /// Constructor - Entry() { - next = -1; - keyValue = nullptr; - } - - /// Constructor - Entry(size_t hashcode, int nextEntry) { - hashCode = hashcode; - next = nextEntry; - keyValue = nullptr; - } - - /// Copy-constructor - Entry(const Entry& entry) { - hashCode = entry.hashCode; - next = entry.next; - keyValue = entry.keyValue; - } - - /// Destructor - ~Entry() { - - } - - }; - // -------------------- Constants -------------------- // - /// Number of prime numbers in array - static constexpr int NB_PRIMES = 70; + /// Default load factor + static constexpr float DEFAULT_LOAD_FACTOR = 0.75; - /// Array of prime numbers for the size of the map - static const int PRIMES[NB_PRIMES]; - - /// Largest prime number - static int LARGEST_PRIME; + /// Invalid index in the array + static constexpr uint32 INVALID_INDEX = 0xffffffff; // -------------------- Attributes -------------------- // - /// Current number of used entries in the map - int mNbUsedEntries; + /// Total number of allocated entries + uint32 mNbAllocatedEntries; - /// Number of free entries among the used ones - int mNbFreeEntries; + /// Number of items in the set + uint32 mNbEntries; - /// Current capacity of the map - int mCapacity; + /// Number of buckets and size of the hash table (nbEntries = loadFactor * mHashSize) + uint32 mHashSize ; /// Array with all the buckets - int* mBuckets; + uint32* mBuckets; /// Array with all the entries - Entry* mEntries; + Pair* mEntries; + + /// For each entry, index of the next entry at the same bucket + uint32* mNextEntries; /// Memory allocator MemoryAllocator& mAllocator; /// Index to the fist free entry - int mFreeIndex; + uint32 mFreeIndex; // -------------------- Methods -------------------- // - /// Initialize the map - void initialize(int capacity) { - - // Compute the next larger prime size - mCapacity = getPrimeSize(capacity); - assert(mCapacity >= 0); - - // Allocate memory for the buckets - mBuckets = static_cast(mAllocator.allocate(mCapacity * sizeof(int))); - - // Allocate memory for the entries - mEntries = static_cast(mAllocator.allocate(mCapacity * sizeof(Entry))); - - // Initialize the buckets and entries - for (int i=0; i= 0); - } - - /// Expand the capacity of the map - void expand(int newCapacity) { - - assert(newCapacity > mCapacity); - assert(isPrimeNumber(newCapacity)); - - // Allocate memory for the buckets - int* newBuckets = static_cast(mAllocator.allocate(newCapacity * sizeof(int))); - - // Allocate memory for the entries - Entry* newEntries = static_cast(mAllocator.allocate(newCapacity * sizeof(Entry))); - - // Initialize the new buckets - for (int i=0; i 0) { - - // Copy the old entries to the new allocated memory location - std::uninitialized_copy(mEntries, mEntries + mNbUsedEntries, newEntries); - - // Destruct the old entries in the previous location - for (int i=0; i < mNbUsedEntries; i++) { - mEntries[i].~Entry(); - } - } - - // Construct the new entries - for (int i=mNbUsedEntries; i(&newEntries[i])) Entry(); - } - - // For each used entry - for (int i=0; i= 0); - } - /// Return the index of the entry with a given key or -1 if there is no entry with this key int findEntry(const K& key) const { - if (mCapacity > 0) { + if (mHashSize > 0) { const size_t hashCode = Hash()(key); - int bucket = hashCode % mCapacity; + const uint32 bucket = hashCode & (mHashSize - 1); auto keyEqual = KeyEqual(); - for (int i = mBuckets[bucket]; i >= 0; i = mEntries[i].next) { - if (mEntries[i].hashCode == hashCode && keyEqual(mEntries[i].keyValue->first, key)) { + for (uint32 i = mBuckets[bucket]; i != INVALID_INDEX; i = mNextEntries[i]) { + if (Hash()(mEntries[i].first) == hashCode && keyEqual(mEntries[i].first, key)) { return i; } } } - return -1; - } - - /// Return the prime number that is larger or equal to the number in parameter - /// for the size of the map - static int getPrimeSize(int number) { - - // Check if the next larger prime number is in the precomputed array of primes - for (int i = 0; i < NB_PRIMES; i++) { - if (PRIMES[i] >= number) return PRIMES[i]; - } - - // Manually compute the next larger prime number - for (int i = (number | 1); i < std::numeric_limits::max(); i+=2) { - - if (isPrimeNumber(i)) { - return i; - } - } - - return number; + return INVALID_INDEX; } public: @@ -255,36 +112,36 @@ class Map { private: - /// Array of entries - const Entry* mEntries; + /// Pointer to the map + const Map* mMap; - /// Capacity of the map - int mCapacity; - - /// Number of used entries in the map - int mNbUsedEntries; + /// Index of the current bucket + uint32 mCurrentBucketIndex; /// Index of the current entry - int mCurrentEntry; + uint32 mCurrentEntryIndex; /// Advance the iterator void advance() { - // If we are trying to move past the end - assert(mCurrentEntry < mNbUsedEntries); + assert(mCurrentBucketIndex < mMap->mHashSize); + assert(mCurrentEntryIndex < mMap->mNbAllocatedEntries); - for (mCurrentEntry += 1; mCurrentEntry < mNbUsedEntries; mCurrentEntry++) { - - // If the entry is not empty - if (mEntries[mCurrentEntry].keyValue != nullptr) { - - // We have found the next non empty entry - return; - } + // Try the next entry + if (mMap->mNextEntries[mCurrentEntryIndex] != INVALID_INDEX) { + mCurrentEntryIndex = mMap->mNextEntries[mCurrentEntryIndex]; + return; } - // We have not find a non empty entry, we return an iterator to the end - mCurrentEntry = mCapacity; + // Try to move to the next bucket + mCurrentEntryIndex = 0; + mCurrentBucketIndex++; + while(mCurrentBucketIndex < mMap->mHashSize && mMap->mBuckets[mCurrentBucketIndex] == INVALID_INDEX) { + mCurrentBucketIndex++; + } + if (mCurrentBucketIndex < mMap->mHashSize) { + mCurrentEntryIndex = mMap->mBuckets[mCurrentBucketIndex]; + } } public: @@ -300,29 +157,29 @@ class Map { Iterator() = default; /// Constructor - Iterator(const Entry* entries, int capacity, int nbUsedEntries, int currentEntry) - :mEntries(entries), mCapacity(capacity), mNbUsedEntries(nbUsedEntries), mCurrentEntry(currentEntry) { + Iterator(const Map* map, uint32 bucketIndex, uint32 entryIndex) + :mMap(map), mCurrentBucketIndex(bucketIndex), mCurrentEntryIndex(entryIndex) { } /// Copy constructor Iterator(const Iterator& it) - :mEntries(it.mEntries), mCapacity(it.mCapacity), mNbUsedEntries(it.mNbUsedEntries), mCurrentEntry(it.mCurrentEntry) { + :mMap(it.mMap), mCurrentBucketIndex(it.mCurrentBucketIndex), mCurrentEntryIndex(it.mCurrentEntryIndex) { } /// Deferencable reference operator*() const { - assert(mCurrentEntry >= 0 && mCurrentEntry < mNbUsedEntries); - assert(mEntries[mCurrentEntry].keyValue != nullptr); - return *(mEntries[mCurrentEntry].keyValue); + assert(mCurrentEntryIndex < mMap->mNbAllocatedEntries); + assert(mCurrentEntryIndex != INVALID_INDEX); + return mMap->mEntries[mCurrentEntryIndex]; } /// Deferencable pointer operator->() const { - assert(mCurrentEntry >= 0 && mCurrentEntry < mNbUsedEntries); - assert(mEntries[mCurrentEntry].keyValue != nullptr); - return mEntries[mCurrentEntry].keyValue; + assert(mCurrentEntryIndex < mMap->mNbAllocatedEntries); + assert(mCurrentEntryIndex != INVALID_INDEX); + return &(mMap->mEntries[mCurrentEntryIndex]); } /// Post increment (it++) @@ -332,7 +189,7 @@ class Map { } /// Pre increment (++it) - Iterator operator++(int number) { + Iterator operator++(int) { Iterator tmp = *this; advance(); return tmp; @@ -340,69 +197,73 @@ class Map { /// Equality operator (it == end()) bool operator==(const Iterator& iterator) const { - return mCurrentEntry == iterator.mCurrentEntry && mEntries == iterator.mEntries; + return mCurrentBucketIndex == iterator.mCurrentBucketIndex && mCurrentEntryIndex == iterator.mCurrentEntryIndex && mMap == iterator.mMap; } /// Inequality operator (it != end()) bool operator!=(const Iterator& iterator) const { return !(*this == iterator); } + + /// Overloaded assignment operator + Iterator& operator=(const Iterator& it) { + + // Check for self assignment + if (this != &it) { + + mMap = it.mMap; + mCurrentBucketIndex = it.mCurrentBucketIndex; + mCurrentEntryIndex = it.mCurrentEntryIndex; + } + + return *this; + } }; // -------------------- Methods -------------------- // /// Constructor - Map(MemoryAllocator& allocator, size_t capacity = 0) - : mNbUsedEntries(0), mNbFreeEntries(0), mCapacity(0), mBuckets(nullptr), - mEntries(nullptr), mAllocator(allocator), mFreeIndex(-1) { - - // If the largest prime has not been computed yet - if (LARGEST_PRIME == -1) { - - // Compute the largest prime number (largest map capacity) - LARGEST_PRIME = getPrimeSize(PRIMES[NB_PRIMES - 1] + 2); - } + Map(MemoryAllocator& allocator, uint32 capacity = 0) + : mNbAllocatedEntries(0), mNbEntries(0), mHashSize(0), mBuckets(nullptr), + mEntries(nullptr), mNextEntries(nullptr), mAllocator(allocator), mFreeIndex(INVALID_INDEX) { if (capacity > 0) { - initialize(capacity); + reserve(capacity); } } /// Copy constructor Map(const Map& map) - :mNbUsedEntries(map.mNbUsedEntries), mNbFreeEntries(map.mNbFreeEntries), mCapacity(map.mCapacity), - mBuckets(nullptr), mEntries(nullptr), mAllocator(map.mAllocator), mFreeIndex(map.mFreeIndex) { + :mNbAllocatedEntries(map.mNbAllocatedEntries), mNbEntries(map.mNbEntries), mHashSize(map.mHashSize), + mBuckets(nullptr), mEntries(nullptr), mNextEntries(nullptr), mAllocator(map.mAllocator), mFreeIndex(map.mFreeIndex) { - assert(capacity() >= 0); + // Allocate memory for the buckets + mBuckets = static_cast(mAllocator.allocate(mHashSize * sizeof(uint32))); - if (mCapacity > 0) { + // Allocate memory for the entries + mEntries = static_cast*>(mAllocator.allocate(mNbAllocatedEntries * sizeof(Pair))); + mNextEntries = static_cast(mAllocator.allocate(mNbAllocatedEntries * sizeof(uint32))); - // Allocate memory for the buckets - mBuckets = static_cast(mAllocator.allocate(mCapacity * sizeof(int))); + // Copy the buckets array + std::memcpy(mBuckets, map.mBuckets, mHashSize * sizeof(uint32)); - // Allocate memory for the entries - mEntries = static_cast(mAllocator.allocate(mCapacity * sizeof(Entry))); + // Copy the next entries indices + std::memcpy(mNextEntries, map.mNextEntries, mNbAllocatedEntries * sizeof(uint32)); - // Copy the buckets - std::uninitialized_copy(map.mBuckets, map.mBuckets + mCapacity, mBuckets); + // Copy the entries + for (uint32 i=0; i(map.mEntries[entryIndex]); - if (map.mEntries[i].keyValue != nullptr) { - mEntries[i].keyValue = static_cast*>(mAllocator.allocate(sizeof(Pair))); - new (mEntries[i].keyValue) Pair(*(map.mEntries[i].keyValue)); - } + entryIndex = mNextEntries[entryIndex]; } - } - - assert(size() >= 0); - assert((*this) == map); } /// Destructor @@ -412,95 +273,162 @@ class Map { } /// Allocate memory for a given number of elements - void reserve(int capacity) { + void reserve(uint32 capacity) { - if (capacity <= mCapacity) return; + if (capacity <= mHashSize) return; - if (capacity > LARGEST_PRIME && LARGEST_PRIME > mCapacity) { - capacity = LARGEST_PRIME; - } - else { - capacity = getPrimeSize(capacity); - } + if (capacity < 16) capacity = 16; - expand(capacity); + // Make sure we have a power of two size + if (!isPowerOfTwo(capacity)) { + capacity = nextPowerOfTwo32Bits(capacity); + } + + assert(capacity < INVALID_INDEX); + + assert(capacity > mHashSize); + + // Allocate memory for the buckets + uint32* newBuckets = static_cast(mAllocator.allocate(capacity * sizeof(uint32))); + + // Allocate memory for the entries + const uint32 nbAllocatedEntries = capacity * DEFAULT_LOAD_FACTOR; + assert(nbAllocatedEntries > 0); + Pair* newEntries = static_cast*>(mAllocator.allocate(nbAllocatedEntries * sizeof(Pair))); + uint32* newNextEntries = static_cast(mAllocator.allocate(nbAllocatedEntries * sizeof(uint32))); + + assert(newEntries != nullptr); + assert(newNextEntries != nullptr); + + // Initialize the new buckets + for (uint32 i=0; i 0) { + + assert(mNextEntries != nullptr); + + // Copy the free nodes indices in the nextEntries array + std::memcpy(newNextEntries, mNextEntries, mNbAllocatedEntries * sizeof(uint32)); + } + + // Recompute the buckets (hash) with the new hash size + for (uint32 i=0; i(mEntries[entryIndex]); + mEntries[entryIndex].~Pair(); + + entryIndex = mNextEntries[entryIndex]; + } + } + + if (mNbAllocatedEntries > 0) { + + // Release previously allocated memory + mAllocator.release(mBuckets, mHashSize * sizeof(uint32)); + mAllocator.release(mEntries, mNbAllocatedEntries * sizeof(Pair)); + mAllocator.release(mNextEntries, mNbAllocatedEntries * sizeof(uint32)); + } + + // Add the new entries to the free list + for (uint32 i=mNbAllocatedEntries; i < nbAllocatedEntries-1; i++) { + newNextEntries[i] = i + 1; + } + newNextEntries[nbAllocatedEntries - 1] = mFreeIndex; + mFreeIndex = mNbAllocatedEntries; + + mHashSize = capacity; + mNbAllocatedEntries = nbAllocatedEntries; + mBuckets = newBuckets; + mEntries = newEntries; + mNextEntries = newNextEntries; + + assert(mFreeIndex != INVALID_INDEX); } /// Return true if the map contains an item with the given key bool containsKey(const K& key) const { - return findEntry(key) != -1; + return findEntry(key) != INVALID_INDEX; } /// Add an element into the map - void add(const Pair& keyValue, bool insertIfAlreadyPresent = false) { + /// Returns true if the item has been inserted and false otherwise. + bool add(const Pair& keyValue, bool insertIfAlreadyPresent = false) { - if (mCapacity == 0) { - initialize(0); - } + uint32 bucket; - // Compute the hash code of the key + // Compute the hash code of the value const size_t hashCode = Hash()(keyValue.first); - // Compute the corresponding bucket index - int bucket = hashCode % mCapacity; + if (mHashSize > 0) { - auto keyEqual = KeyEqual(); + // Compute the corresponding bucket index + bucket = hashCode & (mHashSize - 1); - // Check if the item is already in the map - for (int i = mBuckets[bucket]; i >= 0; i = mEntries[i].next) { + auto keyEqual = KeyEqual(); - // If there is already an item with the same key in the map - if (mEntries[i].hashCode == hashCode && keyEqual(mEntries[i].keyValue->first, keyValue.first)) { + // Check if the item is already in the set + for (uint32 i = mBuckets[bucket]; i != INVALID_INDEX; i = mNextEntries[i]) { - if (insertIfAlreadyPresent) { + // If there is already an item with the same value in the set + if (Hash()(mEntries[i].first) == hashCode && keyEqual(mEntries[i].first, keyValue.first)) { - // Destruct the previous key/value - mEntries[i].keyValue->~Pair(); + if (insertIfAlreadyPresent) { - // Copy construct the new key/value - new (mEntries[i].keyValue) Pair(keyValue); + // Destruct the previous key/value + mEntries[i].~Pair(); - return; - } - else { - throw std::runtime_error("The key and value pair already exists in the map"); + // Copy construct the new key/value + new (mEntries + i) Pair(keyValue); + + return true; + } + else { + assert(false); + throw std::runtime_error("The key and value pair already exists in the map"); + } } } } size_t entryIndex; - // If there are free entries to use - if (mNbFreeEntries > 0) { - assert(mFreeIndex >= 0); - entryIndex = mFreeIndex; - mFreeIndex = mEntries[entryIndex].next; - mNbFreeEntries--; - } - else { + // If there are no more free entries to use + if (mFreeIndex == INVALID_INDEX) { - // If we need to allocator more entries - if (mNbUsedEntries == mCapacity) { + // Allocate more memory + reserve(mHashSize == 0 ? 16 : mHashSize * 2); - // Allocate more memory - reserve(mCapacity * 2); - - // Recompute the bucket index - bucket = hashCode % mCapacity; - } - - entryIndex = mNbUsedEntries; - mNbUsedEntries++; + // Recompute the bucket index + bucket = hashCode & (mHashSize - 1); } - assert(size() >= 0); - assert(mEntries[entryIndex].keyValue == nullptr); - mEntries[entryIndex].hashCode = hashCode; - mEntries[entryIndex].next = mBuckets[bucket]; - mEntries[entryIndex].keyValue = static_cast*>(mAllocator.allocate(sizeof(Pair))); - assert(mEntries[entryIndex].keyValue != nullptr); - new (mEntries[entryIndex].keyValue) Pair(keyValue); + assert(mNbEntries < mNbAllocatedEntries); + assert(mFreeIndex != INVALID_INDEX); + + // Get the next free entry + entryIndex = mFreeIndex; + mFreeIndex = mNextEntries[entryIndex]; + + mNbEntries++; + + mNextEntries[entryIndex] = mBuckets[bucket]; + new (mEntries + entryIndex) Pair(keyValue); mBuckets[bucket] = entryIndex; + + return true; } /// Remove the element pointed by some iterator @@ -517,110 +445,102 @@ class Map { /// the one that has been removed Iterator remove(const K& key) { - if (mCapacity > 0) { + if (mHashSize > 0) { const size_t hashcode = Hash()(key); - int bucket = hashcode % mCapacity; - int last = -1; auto keyEqual = KeyEqual(); + const uint32 bucket = hashcode & (mHashSize - 1); + uint32 last = INVALID_INDEX; + for (uint32 i = mBuckets[bucket]; i != INVALID_INDEX; last = i, i = mNextEntries[i]) { - for (int i = mBuckets[bucket]; i >= 0; last = i, i = mEntries[i].next) { + // If we have found the item + if (Hash()(mEntries[i].first) == hashcode && keyEqual(mEntries[i].first, key)) { - if (mEntries[i].hashCode == hashcode && keyEqual(mEntries[i].keyValue->first, key)) { - - if (last < 0 ) { - mBuckets[bucket] = mEntries[i].next; + if (last == INVALID_INDEX) { + mBuckets[bucket] = mNextEntries[i]; } else { - mEntries[last].next = mEntries[i].next; + mNextEntries[last] = mNextEntries[i]; } - // Release memory for the key/value pair if any - if (mEntries[i].keyValue != nullptr) { - mEntries[i].keyValue->~Pair(); - mAllocator.release(mEntries[i].keyValue, sizeof(Pair)); - mEntries[i].keyValue = nullptr; - } - assert(mEntries[i].keyValue == nullptr); - mEntries[i].next = mFreeIndex; + uint32 nextEntryIndex = mNextEntries[i]; + uint32 nextBucketIndex = bucket; + + mEntries[i].~Pair(); + mNextEntries[i] = mFreeIndex; mFreeIndex = i; - mNbFreeEntries++; + mNbEntries--; - // Find the next entry to return the iterator - for (i += 1; i < mNbUsedEntries; i++) { - - // If the entry is not empty - if (mEntries[i].keyValue != nullptr) { - - // We have found the next non empty entry - return Iterator(mEntries, mCapacity, mNbUsedEntries, i); + // Find the next entry to return an iterator + if (nextEntryIndex == INVALID_INDEX) { + nextEntryIndex = 0; + nextBucketIndex++; + while(nextBucketIndex < mHashSize && mBuckets[nextBucketIndex] == INVALID_INDEX) { + nextBucketIndex++; + } + if (nextBucketIndex < mHashSize) { + nextEntryIndex = mBuckets[nextBucketIndex]; } } - return end(); + // We have found the next non empty entry + return Iterator(this, nextBucketIndex, nextEntryIndex); } } } - assert(size() >= 0); - - // Return the end iterator return end(); } /// Clear the map void clear(bool releaseMemory = false) { - if (mNbUsedEntries > 0) { + for (uint32 i=0; i~Pair(); - mAllocator.release(mEntries[i].keyValue, sizeof(Pair)); - mEntries[i].keyValue = nullptr; - } + // Destroy the entry + mEntries[entryIndex].~Pair(); + + uint32 nextEntryIndex = mNextEntries[entryIndex]; + + // Add entry to the free list + mNextEntries[entryIndex] = mFreeIndex; + mFreeIndex = entryIndex; + + entryIndex = nextEntryIndex; } - mFreeIndex = -1; - mNbUsedEntries = 0; - mNbFreeEntries = 0; - - assert(size() >= 0); + mBuckets[i] = INVALID_INDEX; } - // If elements have been allocated - if (releaseMemory && mCapacity > 0) { + if (releaseMemory && mNbAllocatedEntries > 0) { - // Destroy the entries - for (int i=0; i < mCapacity; i++) { - mEntries[i].~Entry(); - } + // Release previously allocated memory + mAllocator.release(mBuckets, mHashSize * sizeof(uint32)); + mAllocator.release(mEntries, mNbAllocatedEntries * sizeof(Pair)); + mAllocator.release(mNextEntries, mNbAllocatedEntries * sizeof(uint32)); - // Release memory - mAllocator.release(mBuckets, mCapacity * sizeof(int)); - mAllocator.release(mEntries, mCapacity * sizeof(Entry)); - - mCapacity = 0; mBuckets = nullptr; mEntries = nullptr; + mNextEntries = nullptr; + + mNbAllocatedEntries = 0; + mHashSize = 0; } - assert(size() == 0); - } + mNbEntries = 0; + } /// Return the number of elements in the map - int size() const { - assert(mNbUsedEntries - mNbFreeEntries >= 0); - return mNbUsedEntries - mNbFreeEntries; + uint32 size() const { + return mNbEntries; } /// Return the capacity of the map - int capacity() const { - return mCapacity; + uint32 capacity() const { + return mHashSize; } /// Try to find an item of the map given a key. @@ -628,68 +548,54 @@ class Map { /// an iterator pointing to the end if not found Iterator find(const K& key) const { - int bucket; - int entry = -1; + uint32 bucket; + uint32 entry = INVALID_INDEX; - if (mCapacity > 0) { + if (mHashSize > 0) { const size_t hashCode = Hash()(key); - bucket = hashCode % mCapacity; - + bucket = hashCode & (mHashSize - 1); auto keyEqual = KeyEqual(); - for (int i = mBuckets[bucket]; i >= 0; i = mEntries[i].next) { - if (mEntries[i].hashCode == hashCode && keyEqual(mEntries[i].keyValue->first, key)) { + for (uint32 i = mBuckets[bucket]; i != INVALID_INDEX; i = mNextEntries[i]) { + if (Hash()(mEntries[i].first) == hashCode && keyEqual(mEntries[i].first, key)) { entry = i; break; } } } - if (entry == -1) { + if (entry == INVALID_INDEX) { return end(); } - assert(mEntries[entry].keyValue != nullptr); - - return Iterator(mEntries, mCapacity, mNbUsedEntries, entry); + return Iterator(this, bucket, entry); } /// Overloaded index operator V& operator[](const K& key) { - int entry = -1; + const uint32 entry = findEntry(key); - if (mCapacity > 0) { - entry = findEntry(key); - } - - if (entry == -1) { + if (entry == INVALID_INDEX) { assert(false); throw std::runtime_error("No item with given key has been found in the map"); } - assert(mEntries[entry].keyValue != nullptr); - - return mEntries[entry].keyValue->second; + return mEntries[entry].second; } /// Overloaded index operator const V& operator[](const K& key) const { - int entry = -1; + const uint32 entry = findEntry(key); - if (mCapacity > 0) { - entry = findEntry(key); - } - - if (entry == -1) { + if (entry == INVALID_INDEX) { + assert(false); throw std::runtime_error("No item with given key has been found in the map"); } - assert(mEntries[entry].keyValue != nullptr); - - return mEntries[entry].keyValue->second; + return mEntries[entry].second; } /// Overloaded equality operator @@ -729,40 +635,38 @@ class Map { // Clear the map clear(true); - if (map.mCapacity > 0) { + mNbAllocatedEntries = map.mNbAllocatedEntries; + mNbEntries = map.mNbEntries; + mHashSize = map.mHashSize; + mFreeIndex = map.mFreeIndex; - // Compute the next larger prime size - mCapacity = getPrimeSize(map.mCapacity); - assert(mCapacity >= 0); + // Allocate memory for the buckets + mBuckets = static_cast(mAllocator.allocate(mHashSize * sizeof(uint32))); - // Allocate memory for the buckets - mBuckets = static_cast(mAllocator.allocate(mCapacity * sizeof(int))); + // Allocate memory for the entries + mEntries = static_cast*>(mAllocator.allocate(mNbAllocatedEntries * sizeof(Pair))); + mNextEntries = static_cast(mAllocator.allocate(mNbAllocatedEntries * sizeof(uint32))); - // Allocate memory for the entries - mEntries = static_cast(mAllocator.allocate(mCapacity * sizeof(Entry))); + // Copy the buckets array + std::memcpy(mBuckets, map.mBuckets, mHashSize * sizeof(uint32)); - // Copy the buckets - std::uninitialized_copy(map.mBuckets, map.mBuckets + mCapacity, mBuckets); + // Copy the next entries indices + std::memcpy(mNextEntries, map.mNextEntries, mNbAllocatedEntries * sizeof(uint32)); - // Copy the entries - for (int i=0; i < mCapacity; i++) { + // Copy the entries + for (uint32 i=0; i*>(mAllocator.allocate(sizeof(Pair))); - new (mEntries[i].keyValue) Pair(*(map.mEntries[i].keyValue)); - } + // Copy the entry to the new location and destroy the previous one + new (mEntries + entryIndex) Pair(map.mEntries[entryIndex]); + + entryIndex = mNextEntries[entryIndex]; } - - mNbUsedEntries = map.mNbUsedEntries; - mNbFreeEntries = map.mNbFreeEntries; - mFreeIndex = map.mFreeIndex; } } - assert(size() >= 0); - return *this; } @@ -777,33 +681,28 @@ class Map { } // Find the first used entry - int entry; - for (entry=0; entry < mNbUsedEntries; entry++) { - if (mEntries[entry].keyValue != nullptr) { - return Iterator(mEntries, mCapacity, mNbUsedEntries, entry); - } + uint32 bucketIndex = 0; + while (mBuckets[bucketIndex] == INVALID_INDEX) { + + bucketIndex++; } - assert(false); - return end(); + assert(bucketIndex < mHashSize); + assert(mBuckets[bucketIndex] != INVALID_INDEX); + + return Iterator(this, bucketIndex, mBuckets[bucketIndex]); } /// Return a end iterator Iterator end() const { - return Iterator(mEntries, mCapacity, mNbUsedEntries, mCapacity); + return Iterator(this, mHashSize, 0); } + + // ---------- Friendship ---------- // + + friend class Iterator; }; -template -const int Map::PRIMES[NB_PRIMES] = {3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, - 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, - 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, - 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, - 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559}; - -template -int Map::LARGEST_PRIME = -1; - } #endif diff --git a/include/reactphysics3d/containers/Set.h b/include/reactphysics3d/containers/Set.h index 17088fe3..f54c7a69 100755 --- a/include/reactphysics3d/containers/Set.h +++ b/include/reactphysics3d/containers/Set.h @@ -47,197 +47,59 @@ class Set { private: - /// An entry of the set - struct Entry { - - size_t hashCode; // Hash code of the entry - int next; // Index of the next entry - V* value; // Pointer to the value stored in the entry - - /// Constructor - Entry() { - next = -1; - value = nullptr; - } - - /// Constructor - Entry(size_t hashcode, int nextEntry) { - hashCode = hashcode; - next = nextEntry; - value = nullptr; - } - - /// Copy-constructor - Entry(const Entry& entry) { - hashCode = entry.hashCode; - next = entry.next; - value = entry.value; - } - - /// Destructor - ~Entry() { - - } - - }; - // -------------------- Constants -------------------- // - /// Number of prime numbers in array - static constexpr int NB_PRIMES = 70; + /// Default load factor + static constexpr float DEFAULT_LOAD_FACTOR = 0.75; - /// Array of prime numbers for the size of the set - static const int PRIMES[NB_PRIMES]; - - /// Largest prime number - static int LARGEST_PRIME; + /// Invalid index in the array + static constexpr uint32 INVALID_INDEX = 0xffffffff; // -------------------- Attributes -------------------- // - /// Current number of used entries in the set - int mNbUsedEntries; + /// Total number of allocated entries + uint32 mNbAllocatedEntries; - /// Number of free entries among the used ones - int mNbFreeEntries; + /// Number of items in the set + uint32 mNbEntries; - /// Current capacity of the set - int mCapacity; + /// Number of buckets and size of the hash table (nbEntries = loadFactor * mHashSize) + uint32 mHashSize ; /// Array with all the buckets - int* mBuckets; + uint32* mBuckets; - /// Array with all the entries - Entry* mEntries; + /// Array with all the entries (nbEntries = loadFactor * mHashSize) + V* mEntries; + + /// For each entry, index of the next entry at the same bucket + uint32* mNextEntries; /// Memory allocator MemoryAllocator& mAllocator; /// Index to the fist free entry - int mFreeIndex; + uint32 mFreeIndex; // -------------------- Methods -------------------- // - /// Initialize the set - void initialize(int capacity) { - - // Compute the next larger prime size - mCapacity = getPrimeSize(capacity); - - // Allocate memory for the buckets - mBuckets = static_cast(mAllocator.allocate(mCapacity * sizeof(int))); - - // Allocate memory for the entries - mEntries = static_cast(mAllocator.allocate(mCapacity * sizeof(Entry))); - - // Initialize the buckets and entries - for (int i=0; i mCapacity); - assert(isPrimeNumber(newCapacity)); - - // Allocate memory for the buckets - int* newBuckets = static_cast(mAllocator.allocate(newCapacity * sizeof(int))); - - // Allocate memory for the entries - Entry* newEntries = static_cast(mAllocator.allocate(newCapacity * sizeof(Entry))); - - // Initialize the new buckets - for (int i=0; i 0) { - - // Copy the old entries to the new allocated memory location - std::uninitialized_copy(mEntries, mEntries + mNbUsedEntries, newEntries); - - // Destruct the old entries at previous location - for (int i=0; i(&newEntries[i])) Entry(); - } - - // For each used entry - for (int i=0; i 0) { + if (mHashSize > 0) { - size_t hashCode = Hash()(value); - int bucket = hashCode % mCapacity; - auto keyEqual = KeyEqual(); + const size_t hashCode = Hash()(value); + const uint32 bucket = hashCode & (mHashSize - 1); + auto keyEqual = KeyEqual(); - for (int i = mBuckets[bucket]; i >= 0; i = mEntries[i].next) { - if (mEntries[i].hashCode == hashCode && keyEqual(*mEntries[i].value, value)) { + for (uint32 i = mBuckets[bucket]; i != INVALID_INDEX; i = mNextEntries[i]) { + if (Hash()(mEntries[i]) == hashCode && keyEqual(mEntries[i], value)) { return i; } } } - return -1; - } - - /// Return the prime number that is larger or equal to the number in parameter - /// for the size of the set - static int getPrimeSize(int number) { - - // Check if the next larger prime number is in the precomputed array of primes - for (int i = 0; i < NB_PRIMES; i++) { - if (PRIMES[i] >= number) return PRIMES[i]; - } - - // Manually compute the next larger prime number - for (int i = (number | 1); i < std::numeric_limits::max(); i+=2) { - - if (isPrimeNumber(i)) { - return i; - } - } - - return number; + return INVALID_INDEX; } public: @@ -250,36 +112,36 @@ class Set { private: - /// Array of entries - const Entry* mEntries; + /// Pointer to the set + const Set* mSet; - /// Capacity of the map - int mCapacity; - - /// Number of used entries in the map - int mNbUsedEntries; + /// Index of the current bucket + uint32 mCurrentBucketIndex; /// Index of the current entry - int mCurrentEntry; + uint32 mCurrentEntryIndex; /// Advance the iterator void advance() { - // If we are trying to move past the end - assert(mCurrentEntry < mNbUsedEntries); + assert(mCurrentBucketIndex < mSet->mHashSize); + assert(mCurrentEntryIndex < mSet->mNbAllocatedEntries); - for (mCurrentEntry += 1; mCurrentEntry < mNbUsedEntries; mCurrentEntry++) { - - // If the entry is not empty - if (mEntries[mCurrentEntry].value != nullptr) { - - // We have found the next non empty entry - return; - } + // Try the next entry + if (mSet->mNextEntries[mCurrentEntryIndex] != INVALID_INDEX) { + mCurrentEntryIndex = mSet->mNextEntries[mCurrentEntryIndex]; + return; } - // We have not find a non empty entry, we return an iterator to the end - mCurrentEntry = mCapacity; + // Try to move to the next bucket + mCurrentEntryIndex = 0; + mCurrentBucketIndex++; + while(mCurrentBucketIndex < mSet->mHashSize && mSet->mBuckets[mCurrentBucketIndex] == INVALID_INDEX) { + mCurrentBucketIndex++; + } + if (mCurrentBucketIndex < mSet->mHashSize) { + mCurrentEntryIndex = mSet->mBuckets[mCurrentBucketIndex]; + } } public: @@ -295,29 +157,29 @@ class Set { Iterator() = default; /// Constructor - Iterator(const Entry* entries, int capacity, int nbUsedEntries, int currentEntry) - :mEntries(entries), mCapacity(capacity), mNbUsedEntries(nbUsedEntries), mCurrentEntry(currentEntry) { + Iterator(const Set* set, uint32 bucketIndex, uint32 entryIndex) + :mSet(set), mCurrentBucketIndex(bucketIndex), mCurrentEntryIndex(entryIndex) { } /// Copy constructor Iterator(const Iterator& it) - :mEntries(it.mEntries), mCapacity(it.mCapacity), mNbUsedEntries(it.mNbUsedEntries), mCurrentEntry(it.mCurrentEntry) { + :mSet(it.mSet), mCurrentBucketIndex(it.mCurrentBucketIndex), mCurrentEntryIndex(it.mCurrentEntryIndex) { } /// Deferencable reference operator*() const { - assert(mCurrentEntry >= 0 && mCurrentEntry < mNbUsedEntries); - assert(mEntries[mCurrentEntry].value != nullptr); - return *(mEntries[mCurrentEntry].value); + assert(mCurrentEntryIndex < mSet->mNbAllocatedEntries); + assert(mCurrentEntryIndex != INVALID_INDEX); + return mSet->mEntries[mCurrentEntryIndex]; } /// Deferencable pointer operator->() const { - assert(mCurrentEntry >= 0 && mCurrentEntry < mNbUsedEntries); - assert(mEntries[mCurrentEntry].value != nullptr); - return mEntries[mCurrentEntry].value; + assert(mCurrentEntryIndex < mSet->mNbAllocatedEntries); + assert(mCurrentEntryIndex != INVALID_INDEX); + return &(mSet->mEntries[mCurrentEntryIndex]); } /// Post increment (it++) @@ -335,61 +197,71 @@ class Set { /// Equality operator (it == end()) bool operator==(const Iterator& iterator) const { - return mCurrentEntry == iterator.mCurrentEntry && mEntries == iterator.mEntries; + return mCurrentBucketIndex == iterator.mCurrentBucketIndex && mCurrentEntryIndex == iterator.mCurrentEntryIndex && mSet == iterator.mSet; } /// Inequality operator (it != end()) bool operator!=(const Iterator& iterator) const { return !(*this == iterator); } + + /// Overloaded assignment operator + Iterator& operator=(const Iterator& it) { + + // Check for self assignment + if (this != &it) { + + mSet = it.mSet; + mCurrentBucketIndex = it.mCurrentBucketIndex; + mCurrentEntryIndex = it.mCurrentEntryIndex; + } + + return *this; + } }; // -------------------- Methods -------------------- // /// Constructor - Set(MemoryAllocator& allocator, size_t capacity = 0) - : mNbUsedEntries(0), mNbFreeEntries(0), mCapacity(0), mBuckets(nullptr), - mEntries(nullptr), mAllocator(allocator), mFreeIndex(-1) { - - // If the largest prime has not been computed yet - if (LARGEST_PRIME == -1) { - - // Compute the largest prime number (largest map capacity) - LARGEST_PRIME = getPrimeSize(PRIMES[NB_PRIMES - 1] + 2); - } + Set(MemoryAllocator& allocator, uint32 capacity = 0) + : mNbAllocatedEntries(0), mNbEntries(0), mHashSize(0), mBuckets(nullptr), + mEntries(nullptr), mNextEntries(nullptr), mAllocator(allocator), mFreeIndex(INVALID_INDEX) { if (capacity > 0) { - initialize(capacity); + reserve(capacity); } } /// Copy constructor Set(const Set& set) - :mNbUsedEntries(set.mNbUsedEntries), mNbFreeEntries(set.mNbFreeEntries), mCapacity(set.mCapacity), - mBuckets(nullptr), mEntries(nullptr), mAllocator(set.mAllocator), mFreeIndex(set.mFreeIndex) { + :mNbAllocatedEntries(set.mNbAllocatedEntries), mNbEntries(set.mNbEntries), mHashSize(set.mHashSize), + mBuckets(nullptr), mEntries(nullptr), mNextEntries(nullptr), mAllocator(set.mAllocator), mFreeIndex(set.mFreeIndex) { - if (mCapacity > 0) { + // Allocate memory for the buckets + mBuckets = static_cast(mAllocator.allocate(mHashSize * sizeof(uint32))); - // Allocate memory for the buckets - mBuckets = static_cast(mAllocator.allocate(mCapacity * sizeof(int))); + // Allocate memory for the entries + mEntries = static_cast(mAllocator.allocate(mNbAllocatedEntries * sizeof(V))); + mNextEntries = static_cast(mAllocator.allocate(mNbAllocatedEntries * sizeof(uint32))); - // Allocate memory for the entries - mEntries = static_cast(mAllocator.allocate(mCapacity * sizeof(Entry))); + // Copy the buckets array + std::memcpy(mBuckets, set.mBuckets, mHashSize * sizeof(uint32)); - // Copy the buckets - std::uninitialized_copy(set.mBuckets, set.mBuckets + mCapacity, mBuckets); + // Copy the next entries indices + std::memcpy(mNextEntries, set.mNextEntries, mNbAllocatedEntries * sizeof(uint32)); - // Copy the entries - for (int i=0; i < mCapacity; i++) { + // Copy the entries + for (uint32 i=0; i(mAllocator.allocate(sizeof(V))); - new (mEntries[i].value) V(*(set.mEntries[i].value)); - } + // Copy the entry to the new location and destroy the previous one + new (mEntries + entryIndex) V(set.mEntries[entryIndex]); + + entryIndex = mNextEntries[entryIndex]; } } } @@ -401,82 +273,146 @@ class Set { } /// Allocate memory for a given number of elements - void reserve(int capacity) { + void reserve(uint32 capacity) { - if (capacity <= mCapacity) return; + if (capacity <= mHashSize) return; - if (capacity > LARGEST_PRIME && LARGEST_PRIME > mCapacity) { - capacity = LARGEST_PRIME; - } - else { - capacity = getPrimeSize(capacity); - } + if (capacity < 16) capacity = 16; - expand(capacity); + // Make sure we have a power of two size + if (!isPowerOfTwo(capacity)) { + capacity = nextPowerOfTwo32Bits(capacity); + } + + assert(capacity < INVALID_INDEX); + + assert(capacity > mHashSize); + + // Allocate memory for the buckets + uint32* newBuckets = static_cast(mAllocator.allocate(capacity * sizeof(uint32))); + + // Allocate memory for the entries + const uint32 nbAllocatedEntries = capacity * DEFAULT_LOAD_FACTOR; + assert(nbAllocatedEntries > 0); + V* newEntries = static_cast(mAllocator.allocate(nbAllocatedEntries * sizeof(V))); + uint32* newNextEntries = static_cast(mAllocator.allocate(nbAllocatedEntries * sizeof(uint32))); + + assert(newEntries != nullptr); + assert(newNextEntries != nullptr); + + // Initialize the new buckets + for (uint32 i=0; i 0) { + + assert(mNextEntries != nullptr); + + // Copy the free nodes indices in the nextEntries array + std::memcpy(newNextEntries, mNextEntries, mNbAllocatedEntries * sizeof(uint32)); + } + + // Recompute the buckets (hash) with the new hash size + for (uint32 i=0; i 0) { + + // Release previously allocated memory + mAllocator.release(mBuckets, mHashSize * sizeof(uint32)); + mAllocator.release(mEntries, mNbAllocatedEntries * sizeof(V)); + mAllocator.release(mNextEntries, mNbAllocatedEntries * sizeof(uint32)); + } + + // Add the new entries to the free list + for (uint32 i=mNbAllocatedEntries; i < nbAllocatedEntries-1; i++) { + newNextEntries[i] = i + 1; + } + newNextEntries[nbAllocatedEntries - 1] = mFreeIndex; + mFreeIndex = mNbAllocatedEntries; + + mHashSize = capacity; + mNbAllocatedEntries = nbAllocatedEntries; + mBuckets = newBuckets; + mEntries = newEntries; + mNextEntries = newNextEntries; + + assert(mFreeIndex != INVALID_INDEX); } /// Return true if the set contains a given value bool contains(const V& value) const { - return findEntry(value) != -1; + return findEntry(value) != INVALID_INDEX; } /// Add a value into the set. /// Returns true if the item has been inserted and false otherwise. bool add(const V& value) { - if (mCapacity == 0) { - initialize(0); - } + uint32 bucket; // Compute the hash code of the value - size_t hashCode = Hash()(value); + const size_t hashCode = Hash()(value); - // Compute the corresponding bucket index - int bucket = hashCode % mCapacity; + if (mHashSize > 0) { - auto keyEqual = KeyEqual(); + // Compute the corresponding bucket index + bucket = hashCode & (mHashSize - 1); - // Check if the item is already in the set - for (int i = mBuckets[bucket]; i >= 0; i = mEntries[i].next) { + auto keyEqual = KeyEqual(); - // If there is already an item with the same value in the set - if (mEntries[i].hashCode == hashCode && keyEqual(*mEntries[i].value, value)) { + // Check if the item is already in the set + for (uint32 i = mBuckets[bucket]; i != INVALID_INDEX; i = mNextEntries[i]) { - return false; + // If there is already an item with the same value in the set + if (Hash()(mEntries[i]) == hashCode && keyEqual(mEntries[i], value)) { + + return false; + } } } size_t entryIndex; - // If there are free entries to use - if (mNbFreeEntries > 0) { - assert(mFreeIndex >= 0); - entryIndex = mFreeIndex; - mFreeIndex = mEntries[entryIndex].next; - mNbFreeEntries--; - } - else { + // If there are no more free entries to use + if (mFreeIndex == INVALID_INDEX) { - // If we need to allocator more entries - if (mNbUsedEntries == mCapacity) { + // Allocate more memory + reserve(mHashSize == 0 ? 16 : mHashSize * 2); - // Allocate more memory - reserve(mCapacity * 2); - - // Recompute the bucket index - bucket = hashCode % mCapacity; - } - - entryIndex = mNbUsedEntries; - mNbUsedEntries++; + // Recompute the bucket index + bucket = hashCode & (mHashSize - 1); } - assert(mEntries[entryIndex].value == nullptr); - mEntries[entryIndex].hashCode = hashCode; - mEntries[entryIndex].next = mBuckets[bucket]; - mEntries[entryIndex].value = static_cast(mAllocator.allocate(sizeof(V))); - assert(mEntries[entryIndex].value != nullptr); - new (mEntries[entryIndex].value) V(value); + assert(mNbEntries < mNbAllocatedEntries); + assert(mFreeIndex != INVALID_INDEX); + + // Get the next free entry + entryIndex = mFreeIndex; + mFreeIndex = mNextEntries[entryIndex]; + + mNbEntries++; + + mNextEntries[entryIndex] = mBuckets[bucket]; + new (mEntries + entryIndex) V(value); mBuckets[bucket] = entryIndex; return true; @@ -495,46 +431,46 @@ class Set { /// element after the one that has been removed Iterator remove(const V& value) { - if (mCapacity > 0) { + if (mHashSize > 0) { - size_t hashcode = Hash()(value); - auto keyEqual = KeyEqual(); - int bucket = hashcode % mCapacity; - int last = -1; - for (int i = mBuckets[bucket]; i >= 0; last = i, i = mEntries[i].next) { + const size_t hashcode = Hash()(value); + auto keyEqual = KeyEqual(); + const uint32 bucket = hashcode & (mHashSize - 1); + uint32 last = INVALID_INDEX; + for (uint32 i = mBuckets[bucket]; i != INVALID_INDEX; last = i, i = mNextEntries[i]) { - if (mEntries[i].hashCode == hashcode && keyEqual(*mEntries[i].value, value)) { + // If we have found the item + if (Hash()(mEntries[i]) == hashcode && keyEqual(mEntries[i], value)) { - if (last < 0 ) { - mBuckets[bucket] = mEntries[i].next; + if (last == INVALID_INDEX) { + mBuckets[bucket] = mNextEntries[i]; } else { - mEntries[last].next = mEntries[i].next; + mNextEntries[last] = mNextEntries[i]; } - // Release memory for the value if any - if (mEntries[i].value != nullptr) { - mEntries[i].value->~V(); - mAllocator.release(mEntries[i].value, sizeof(V)); - mEntries[i].value = nullptr; - } - assert(mEntries[i].value == nullptr); - mEntries[i].next = mFreeIndex; + uint32 nextEntryIndex = mNextEntries[i]; + uint32 nextBucketIndex = bucket; + + mEntries[i].~V(); + mNextEntries[i] = mFreeIndex; mFreeIndex = i; - mNbFreeEntries++; + mNbEntries--; - // Find the next valid entry to return an iterator - for (i += 1; i < mNbUsedEntries; i++) { - - // If the entry is not empty - if (mEntries[i].value != nullptr) { - - // We have found the next non empty entry - return Iterator(mEntries, mCapacity, mNbUsedEntries, i); + // Find the next entry to return an iterator + if (nextEntryIndex == INVALID_INDEX) { + nextEntryIndex = 0; + nextBucketIndex++; + while(nextBucketIndex < mHashSize && mBuckets[nextBucketIndex] == INVALID_INDEX) { + nextBucketIndex++; + } + if (nextBucketIndex < mHashSize) { + nextEntryIndex = mBuckets[nextBucketIndex]; } } - return end(); + // We have found the next non empty entry + return Iterator(this, nextBucketIndex, nextEntryIndex); } } } @@ -547,10 +483,8 @@ class Set { List list(listAllocator); - for (int i=0; i < mCapacity; i++) { - if (mEntries[i].value != nullptr) { - list.add(*(mEntries[i].value)); - } + for (auto it = begin(); it != end(); ++it) { + list.add(*it); } return list; @@ -559,50 +493,52 @@ class Set { /// Clear the set void clear(bool releaseMemory = false) { - if (mNbUsedEntries > 0) { + for (uint32 i=0; i~V(); - mAllocator.release(mEntries[i].value, sizeof(V)); - mEntries[i].value = nullptr; - } + uint32 entryIndex = mBuckets[i]; + while(entryIndex != INVALID_INDEX) { + + // Destroy the entry + mEntries[entryIndex].~V(); + + uint32 nextEntryIndex = mNextEntries[entryIndex]; + + // Add entry to the free list + mNextEntries[entryIndex] = mFreeIndex; + mFreeIndex = entryIndex; + + entryIndex = nextEntryIndex; } - mFreeIndex = -1; - mNbUsedEntries = 0; - mNbFreeEntries = 0; + mBuckets[i] = INVALID_INDEX; } - // If elements have been allocated - if (releaseMemory && mCapacity > 0) { + if (releaseMemory && mNbAllocatedEntries > 0) { - // Destroy the entries - for (int i=0; i < mCapacity; i++) { - mEntries[i].~Entry(); - } + // Release previously allocated memory + mAllocator.release(mBuckets, mHashSize * sizeof(uint32)); + mAllocator.release(mEntries, mNbAllocatedEntries * sizeof(V)); + mAllocator.release(mNextEntries, mNbAllocatedEntries * sizeof(uint32)); - mAllocator.release(mBuckets, mCapacity * sizeof(int)); - mAllocator.release(mEntries, mCapacity * sizeof(Entry)); - - mCapacity = 0; mBuckets = nullptr; mEntries = nullptr; + mNextEntries = nullptr; + + mNbAllocatedEntries = 0; + mHashSize = 0; } - assert(size() == 0); + mNbEntries = 0; } /// Return the number of elements in the set - int size() const { - return mNbUsedEntries - mNbFreeEntries; + uint32 size() const { + return mNbEntries; } /// Return the capacity of the set - int capacity() const { - return mCapacity; + uint32 capacity() const { + return mHashSize; } /// Try to find an item of the set given a key. @@ -610,30 +546,28 @@ class Set { /// an iterator pointing to the end if not found Iterator find(const V& value) const { - int bucket; - int entry = -1; + uint32 bucket; + uint32 entry = INVALID_INDEX; - if (mCapacity > 0) { + if (mHashSize > 0) { - size_t hashCode = Hash()(value); - bucket = hashCode % mCapacity; - auto keyEqual = KeyEqual(); + const size_t hashCode = Hash()(value); + bucket = hashCode & (mHashSize - 1); + auto keyEqual = KeyEqual(); - for (int i = mBuckets[bucket]; i >= 0; i = mEntries[i].next) { - if (mEntries[i].hashCode == hashCode && keyEqual(*(mEntries[i].value), value)) { + for (uint32 i = mBuckets[bucket]; i != INVALID_INDEX; i = mNextEntries[i]) { + if (Hash()(mEntries[i]) == hashCode && keyEqual(mEntries[i], value)) { entry = i; break; } } } - if (entry == -1) { + if (entry == INVALID_INDEX) { return end(); } - assert(mEntries[entry].value != nullptr); - - return Iterator(mEntries, mCapacity, mNbUsedEntries, entry); + return Iterator(this, bucket, entry); } /// Overloaded equality operator @@ -665,34 +599,35 @@ class Set { // Clear the set clear(true); - if (set.mCapacity > 0) { + mNbAllocatedEntries = set.mNbAllocatedEntries; + mNbEntries = set.mNbEntries; + mHashSize = set.mHashSize; + mFreeIndex = set.mFreeIndex; - // Compute the next larger prime size - mCapacity = getPrimeSize(set.mCapacity); + // Allocate memory for the buckets + mBuckets = static_cast(mAllocator.allocate(mHashSize * sizeof(uint32))); - // Allocate memory for the buckets - mBuckets = static_cast(mAllocator.allocate(mCapacity * sizeof(int))); + // Allocate memory for the entries + mEntries = static_cast(mAllocator.allocate(mNbAllocatedEntries * sizeof(V))); + mNextEntries = static_cast(mAllocator.allocate(mNbAllocatedEntries * sizeof(uint32))); - // Allocate memory for the entries - mEntries = static_cast(mAllocator.allocate(mCapacity * sizeof(Entry))); + // Copy the buckets array + std::memcpy(mBuckets, set.mBuckets, mHashSize * sizeof(uint32)); - // Copy the buckets - std::uninitialized_copy(set.mBuckets, set.mBuckets + mCapacity, mBuckets); + // Copy the next entries indices + std::memcpy(mNextEntries, set.mNextEntries, mNbAllocatedEntries * sizeof(uint32)); - // Copy the entries - for (int i=0; i < mCapacity; i++) { + // Copy the entries + for (uint32 i=0; i(mAllocator.allocate(sizeof(V))); - new (mEntries[i].value) V(*(set.mEntries[i].value)); - } + // Copy the entry to the new location and destroy the previous one + new (mEntries + entryIndex) V(set.mEntries[entryIndex]); + + entryIndex = mNextEntries[entryIndex]; } - - mNbUsedEntries = set.mNbUsedEntries; - mNbFreeEntries = set.mNbFreeEntries; - mFreeIndex = set.mFreeIndex; } } @@ -702,7 +637,7 @@ class Set { /// Return a begin iterator Iterator begin() const { - // If the map is empty + // If the set is empty if (size() == 0) { // Return an iterator to the end @@ -710,33 +645,29 @@ class Set { } // Find the first used entry - int entry; - for (entry=0; entry < mNbUsedEntries; entry++) { - if (mEntries[entry].value != nullptr) { - return Iterator(mEntries, mCapacity, mNbUsedEntries, entry); - } + uint32 bucketIndex = 0; + while (mBuckets[bucketIndex] == INVALID_INDEX) { + + bucketIndex++; } - assert(false); - return end(); + assert(bucketIndex < mHashSize); + assert(mBuckets[bucketIndex] != INVALID_INDEX); + + return Iterator(this, bucketIndex, mBuckets[bucketIndex]); } /// Return a end iterator Iterator end() const { - return Iterator(mEntries, mCapacity, mNbUsedEntries, mCapacity); + + return Iterator(this, mHashSize, 0); } + + // ---------- Friendship ---------- // + + friend class Iterator; }; -template -const int Set::PRIMES[NB_PRIMES] = {3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, - 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, - 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, - 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, - 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559}; - -template -int Set::LARGEST_PRIME = -1; - } #endif diff --git a/include/reactphysics3d/engine/PhysicsCommon.h b/include/reactphysics3d/engine/PhysicsCommon.h index 939c8b39..f2e827c8 100644 --- a/include/reactphysics3d/engine/PhysicsCommon.h +++ b/include/reactphysics3d/engine/PhysicsCommon.h @@ -97,6 +97,36 @@ class PhysicsCommon { /// Destroy and release everything that has been allocated void release(); + /// Delete an instance of PhysicsWorld + void deletePhysicsWorld(PhysicsWorld* world); + + /// Delete a sphere collision shape + void deleteSphereShape(SphereShape* sphereShape); + + /// Delete a box collision shape + void deleteBoxShape(BoxShape* boxShape); + + /// Delete a capsule collision shape + void deleteCapsuleShape(CapsuleShape* capsuleShape); + + /// Delete a convex mesh shape + void deleteConvexMeshShape(ConvexMeshShape* convexMeshShape); + + /// Delete a height-field shape + void deleteHeightFieldShape(HeightFieldShape* heightFieldShape); + + /// Delete a concave mesh shape + void deleteConcaveMeshShape(ConcaveMeshShape* concaveMeshShape); + + /// Delete a polyhedron mesh + void deletePolyhedronMesh(PolyhedronMesh* polyhedronMesh); + + /// Delete a triangle mesh + void deleteTriangleMesh(TriangleMesh* triangleMesh); + + /// Delete a default logger + void deleteDefaultLogger(DefaultLogger* logger); + // If profiling is enabled #ifdef IS_RP3D_PROFILING_ENABLED @@ -106,6 +136,9 @@ class PhysicsCommon { /// Destroy a profiler void destroyProfiler(Profiler* profiler); + /// Delete a profiler + void deleteProfiler(Profiler* profiler); + #endif public : @@ -186,7 +219,6 @@ class PhysicsCommon { /// Set the logger static void setLogger(Logger* logger); - }; // Return the current logger diff --git a/include/reactphysics3d/mathematics/mathematics_functions.h b/include/reactphysics3d/mathematics/mathematics_functions.h index cd98140e..cf0f6880 100755 --- a/include/reactphysics3d/mathematics/mathematics_functions.h +++ b/include/reactphysics3d/mathematics/mathematics_functions.h @@ -126,8 +126,23 @@ Vector3 projectPointOntoPlane(const Vector3& point, const Vector3& planeNormal, /// Return the distance between a point and a plane (the plane normal must be normalized) decimal computePointToPlaneDistance(const Vector3& point, const Vector3& planeNormal, const Vector3& planePoint); -/// Return true if the given number is prime -bool isPrimeNumber(int number); +/// Return true if a number is a power of two +RP3D_FORCE_INLINE bool isPowerOfTwo(uint32 number) { + return number != 0 && !(number & (number -1)); +} + +/// Return the next power of two larger than the number in parameter +RP3D_FORCE_INLINE uint32 nextPowerOfTwo32Bits(uint32 number) { + number--; + number |= number >> 1; + number |= number >> 2; + number |= number >> 4; + number |= number >> 8; + number |= number >> 16; + number++; + number += (number == 0); + return number; +} /// Return an unique integer from two integer numbers (pairing function) /// Here we assume that the two parameter numbers are sorted such that diff --git a/src/engine/PhysicsCommon.cpp b/src/engine/PhysicsCommon.cpp index 3147c702..08ef4248 100644 --- a/src/engine/PhysicsCommon.cpp +++ b/src/engine/PhysicsCommon.cpp @@ -58,53 +58,63 @@ void PhysicsCommon::release() { // Destroy the physics worlds for (auto it = mPhysicsWorlds.begin(); it != mPhysicsWorlds.end(); ++it) { - destroyPhysicsWorld(*it); + deletePhysicsWorld(*it); } + mPhysicsWorlds.clear(); // Destroy the sphere shapes for (auto it = mSphereShapes.begin(); it != mSphereShapes.end(); ++it) { - destroySphereShape(*it); + deleteSphereShape(*it); } + mSphereShapes.clear(); // Destroy the box shapes for (auto it = mBoxShapes.begin(); it != mBoxShapes.end(); ++it) { - destroyBoxShape(*it); + deleteBoxShape(*it); } + mBoxShapes.clear(); // Destroy the capsule shapes for (auto it = mCapsuleShapes.begin(); it != mCapsuleShapes.end(); ++it) { - destroyCapsuleShape(*it); + deleteCapsuleShape(*it); } + mCapsuleShapes.clear(); // Destroy the convex mesh shapes for (auto it = mConvexMeshShapes.begin(); it != mConvexMeshShapes.end(); ++it) { - destroyConvexMeshShape(*it); + deleteConvexMeshShape(*it); } + mConvexMeshShapes.clear(); // Destroy the heigh-field shapes for (auto it = mHeightFieldShapes.begin(); it != mHeightFieldShapes.end(); ++it) { - destroyHeightFieldShape(*it); + deleteHeightFieldShape(*it); } + mHeightFieldShapes.clear(); // Destroy the concave mesh shapes for (auto it = mConcaveMeshShapes.begin(); it != mConcaveMeshShapes.end(); ++it) { - destroyConcaveMeshShape(*it); + deleteConcaveMeshShape(*it); } + mConcaveMeshShapes.clear(); // Destroy the polyhedron mesh for (auto it = mPolyhedronMeshes.begin(); it != mPolyhedronMeshes.end(); ++it) { - destroyPolyhedronMesh(*it); + deletePolyhedronMesh(*it); } + mPolyhedronMeshes.clear(); // Destroy the triangle mesh for (auto it = mTriangleMeshes.begin(); it != mTriangleMeshes.end(); ++it) { - destroyTriangleMesh(*it); + deleteTriangleMesh(*it); } + mTriangleMeshes.clear(); // Destroy the default loggers for (auto it = mDefaultLoggers.begin(); it != mDefaultLoggers.end(); ++it) { - destroyDefaultLogger(*it); + deleteDefaultLogger(*it); } + mDefaultLoggers.clear(); // If profiling is enabled #ifdef IS_RP3D_PROFILING_ENABLED @@ -112,8 +122,9 @@ void PhysicsCommon::release() { // Destroy the profilers for (auto it = mProfilers.begin(); it != mProfilers.end(); ++it) { - destroyProfiler(*it); + deleteProfiler(*it); } + mProfilers.clear(); #endif @@ -151,13 +162,22 @@ PhysicsWorld* PhysicsCommon::createPhysicsWorld(const PhysicsWorld::WorldSetting */ void PhysicsCommon::destroyPhysicsWorld(PhysicsWorld* world) { + deletePhysicsWorld(world); + + mPhysicsWorlds.remove(world); +} + +// Delete an instance of PhysicsWorld +/** + * @param world A pointer to the physics world to destroy + */ +void PhysicsCommon::deletePhysicsWorld(PhysicsWorld* world) { + // Call the destructor of the world world->~PhysicsWorld(); // Release allocated memory mMemoryManager.release(MemoryManager::AllocationType::Heap, world, sizeof(PhysicsWorld)); - - mPhysicsWorlds.remove(world); } // Create and return a sphere collision shape @@ -185,6 +205,17 @@ SphereShape* PhysicsCommon::createSphereShape(const decimal radius) { */ void PhysicsCommon::destroySphereShape(SphereShape* sphereShape) { + deleteSphereShape(sphereShape); + + mSphereShapes.remove(sphereShape); +} + +// Delete a sphere collision shape +/** + * @param sphereShape A pointer to the sphere collision shape to destroy + */ +void PhysicsCommon::deleteSphereShape(SphereShape* sphereShape) { + // If the shape is still part of some colliders if (sphereShape->mColliders.size() > 0) { @@ -197,8 +228,6 @@ void PhysicsCommon::destroySphereShape(SphereShape* sphereShape) { // Release allocated memory mMemoryManager.release(MemoryManager::AllocationType::Pool, sphereShape, sizeof(SphereShape)); - - mSphereShapes.remove(sphereShape); } // Create and return a box collision shape @@ -226,6 +255,17 @@ BoxShape* PhysicsCommon::createBoxShape(const Vector3& halfExtents) { */ void PhysicsCommon::destroyBoxShape(BoxShape* boxShape) { + deleteBoxShape(boxShape); + + mBoxShapes.remove(boxShape); +} + +// Delete a box collision shape +/** + * @param boxShape A pointer to the box shape to destroy + */ +void PhysicsCommon::deleteBoxShape(BoxShape* boxShape) { + // If the shape is still part of some colliders if (boxShape->mColliders.size() > 0) { @@ -238,8 +278,6 @@ void PhysicsCommon::destroyBoxShape(BoxShape* boxShape) { // Release allocated memory mMemoryManager.release(MemoryManager::AllocationType::Pool, boxShape, sizeof(BoxShape)); - - mBoxShapes.remove(boxShape); } // Create and return a capsule shape @@ -275,6 +313,17 @@ CapsuleShape* PhysicsCommon::createCapsuleShape(decimal radius, decimal height) */ void PhysicsCommon::destroyCapsuleShape(CapsuleShape* capsuleShape) { + deleteCapsuleShape(capsuleShape); + + mCapsuleShapes.remove(capsuleShape); +} + +// Delete a capsule collision shape +/** + * @param capsuleShape A pointer to the capsule shape to destroy + */ +void PhysicsCommon::deleteCapsuleShape(CapsuleShape* capsuleShape) { + // If the shape is still part of some colliders if (capsuleShape->mColliders.size() > 0) { @@ -287,8 +336,6 @@ void PhysicsCommon::destroyCapsuleShape(CapsuleShape* capsuleShape) { // Release allocated memory mMemoryManager.release(MemoryManager::AllocationType::Pool, capsuleShape, sizeof(CapsuleShape)); - - mCapsuleShapes.remove(capsuleShape); } // Create and return a convex mesh shape @@ -312,6 +359,17 @@ ConvexMeshShape* PhysicsCommon::createConvexMeshShape(PolyhedronMesh* polyhedron */ void PhysicsCommon::destroyConvexMeshShape(ConvexMeshShape* convexMeshShape) { + deleteConvexMeshShape(convexMeshShape); + + mConvexMeshShapes.remove(convexMeshShape); +} + +// Delete a convex mesh shape +/** + * @param convexMeshShape A pointer to the convex mesh shape to destroy + */ +void PhysicsCommon::deleteConvexMeshShape(ConvexMeshShape* convexMeshShape) { + // If the shape is still part of some colliders if (convexMeshShape->mColliders.size() > 0) { @@ -324,8 +382,6 @@ void PhysicsCommon::destroyConvexMeshShape(ConvexMeshShape* convexMeshShape) { // Release allocated memory mMemoryManager.release(MemoryManager::AllocationType::Pool, convexMeshShape, sizeof(ConvexMeshShape)); - - mConvexMeshShapes.remove(convexMeshShape); } // Create and return a height-field shape @@ -358,6 +414,17 @@ HeightFieldShape* PhysicsCommon::createHeightFieldShape(int nbGridColumns, int n */ void PhysicsCommon::destroyHeightFieldShape(HeightFieldShape* heightFieldShape) { + deleteHeightFieldShape(heightFieldShape); + + mHeightFieldShapes.remove(heightFieldShape); +} + +// Delete a height-field shape +/** + * @param heightFieldShape A pointer to the height field shape to destroy + */ +void PhysicsCommon::deleteHeightFieldShape(HeightFieldShape* heightFieldShape) { + // If the shape is still part of some colliders if (heightFieldShape->mColliders.size() > 0) { @@ -370,8 +437,6 @@ void PhysicsCommon::destroyHeightFieldShape(HeightFieldShape* heightFieldShape) // Release allocated memory mMemoryManager.release(MemoryManager::AllocationType::Pool, heightFieldShape, sizeof(HeightFieldShape)); - - mHeightFieldShapes.remove(heightFieldShape); } // Create and return a concave mesh shape @@ -395,6 +460,17 @@ ConcaveMeshShape* PhysicsCommon::createConcaveMeshShape(TriangleMesh* triangleMe */ void PhysicsCommon::destroyConcaveMeshShape(ConcaveMeshShape* concaveMeshShape) { + deleteConcaveMeshShape(concaveMeshShape); + + mConcaveMeshShapes.remove(concaveMeshShape); +} + +// Delete a concave mesh shape +/** + * @param concaveMeshShape A pointer to the concave mesh shape to destroy + */ +void PhysicsCommon::deleteConcaveMeshShape(ConcaveMeshShape* concaveMeshShape) { + // If the shape is still part of some colliders if (concaveMeshShape->mColliders.size() > 0) { @@ -407,8 +483,6 @@ void PhysicsCommon::destroyConcaveMeshShape(ConcaveMeshShape* concaveMeshShape) // Release allocated memory mMemoryManager.release(MemoryManager::AllocationType::Pool, concaveMeshShape, sizeof(ConcaveMeshShape)); - - mConcaveMeshShapes.remove(concaveMeshShape); } // Create a polyhedron mesh @@ -431,13 +505,22 @@ PolyhedronMesh* PhysicsCommon::createPolyhedronMesh(PolygonVertexArray* polygonV */ void PhysicsCommon::destroyPolyhedronMesh(PolyhedronMesh* polyhedronMesh) { + deletePolyhedronMesh(polyhedronMesh); + + mPolyhedronMeshes.remove(polyhedronMesh); +} + +// Delete a polyhedron mesh +/** + * @param polyhedronMesh A pointer to the polyhedron mesh to destroy + */ +void PhysicsCommon::deletePolyhedronMesh(PolyhedronMesh* polyhedronMesh) { + // Call the destructor of the shape polyhedronMesh->~PolyhedronMesh(); // Release allocated memory mMemoryManager.release(MemoryManager::AllocationType::Pool, polyhedronMesh, sizeof(PolyhedronMesh)); - - mPolyhedronMeshes.remove(polyhedronMesh); } // Create a triangle mesh @@ -459,13 +542,22 @@ TriangleMesh* PhysicsCommon::createTriangleMesh() { */ void PhysicsCommon::destroyTriangleMesh(TriangleMesh* triangleMesh) { + deleteTriangleMesh(triangleMesh); + + mTriangleMeshes.remove(triangleMesh); +} + +// Delete a triangle mesh +/** + * @param A pointer to the triangle mesh to destroy + */ +void PhysicsCommon::deleteTriangleMesh(TriangleMesh* triangleMesh) { + // Call the destructor of the shape triangleMesh->~TriangleMesh(); // Release allocated memory mMemoryManager.release(MemoryManager::AllocationType::Pool, triangleMesh, sizeof(TriangleMesh)); - - mTriangleMeshes.remove(triangleMesh); } // Create and return a new logger @@ -487,13 +579,22 @@ DefaultLogger* PhysicsCommon::createDefaultLogger() { */ void PhysicsCommon::destroyDefaultLogger(DefaultLogger* logger) { + deleteDefaultLogger(logger); + + mDefaultLoggers.remove(logger); +} + +// Delete a logger +/** + * @param A pointer to the default logger to destroy + */ +void PhysicsCommon::deleteDefaultLogger(DefaultLogger* logger) { + // Call the destructor of the logger logger->~DefaultLogger(); // Release allocated memory mMemoryManager.release(MemoryManager::AllocationType::Pool, logger, sizeof(DefaultLogger)); - - mDefaultLoggers.remove(logger); } // If profiling is enabled @@ -514,13 +615,18 @@ Profiler* PhysicsCommon::createProfiler() { // Destroy a profiler void PhysicsCommon::destroyProfiler(Profiler* profiler) { + deleteProfiler(profiler); + + mProfilers.remove(profiler); +} + +// Delete a profiler +void PhysicsCommon::deleteProfiler(Profiler* profiler) { + // Call the destructor of the profiler profiler->~Profiler(); // Release allocated memory mMemoryManager.release(MemoryManager::AllocationType::Pool, profiler, sizeof(Profiler)); - - mProfilers.remove(profiler); } - #endif diff --git a/src/mathematics/mathematics_functions.cpp b/src/mathematics/mathematics_functions.cpp index 5438c980..daf021b0 100755 --- a/src/mathematics/mathematics_functions.cpp +++ b/src/mathematics/mathematics_functions.cpp @@ -379,27 +379,3 @@ Vector3 reactphysics3d::projectPointOntoPlane(const Vector3& point, const Vector decimal reactphysics3d::computePointToPlaneDistance(const Vector3& point, const Vector3& planeNormal, const Vector3& planePoint) { return planeNormal.dot(point - planePoint); } - -// Return true if the given number is prime -bool reactphysics3d::isPrimeNumber(int number) { - - // If it's a odd number - if ((number & 1) != 0) { - - int limit = static_cast(std::sqrt(number)); - - for (int divisor = 3; divisor <= limit; divisor += 2) { - - // If we have found a divisor - if ((number % divisor) == 0) { - - // It is not a prime number - return false; - } - } - - return true; - } - - return number == 2; -} diff --git a/test/main.cpp b/test/main.cpp index 6a965039..3d2ba550 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -53,9 +53,9 @@ int main() { // ---------- Containers tests ---------- // + testSuite.addTest(new TestSet("Set")); testSuite.addTest(new TestList("List")); testSuite.addTest(new TestMap("Map")); - testSuite.addTest(new TestSet("Set")); testSuite.addTest(new TestDeque("Deque")); testSuite.addTest(new TestStack("Stack")); diff --git a/test/tests/containers/TestList.h b/test/tests/containers/TestList.h index 5190e341..10c500aa 100644 --- a/test/tests/containers/TestList.h +++ b/test/tests/containers/TestList.h @@ -94,7 +94,7 @@ class TestList : public Test { rp3d_test(list4.size() == 0); List list5(list3); - rp3d_test(list5.capacity() == list3.size()); + rp3d_test(list5.capacity() == list3.capacity()); rp3d_test(list5.size() == list3.size()); for (uint i=0; i #include +#include /// Reactphysics3D namespace namespace reactphysics3d { @@ -267,6 +268,33 @@ class TestMathematicsFunctions : public Test { rp3d_test(approxEqual(clipPolygonVertices[3].y, 4, 0.000001)); rp3d_test(approxEqual(clipPolygonVertices[3].z, 0, 0.000001)); + // Test isPowerOfTwo() + rp3d_test(!isPowerOfTwo(0)); + rp3d_test(!isPowerOfTwo(3)); + rp3d_test(!isPowerOfTwo(144)); + rp3d_test(!isPowerOfTwo(13)); + rp3d_test(!isPowerOfTwo(18)); + rp3d_test(!isPowerOfTwo(1000)); + + rp3d_test(isPowerOfTwo(1)); + rp3d_test(isPowerOfTwo(2)); + rp3d_test(isPowerOfTwo(4)); + rp3d_test(isPowerOfTwo(8)); + rp3d_test(isPowerOfTwo(256)); + rp3d_test(isPowerOfTwo(1024)); + rp3d_test(isPowerOfTwo(2048)); + + // Test nextPowerOfTwo32Bits() + rp3d_test(nextPowerOfTwo32Bits(0) == 1); + rp3d_test(nextPowerOfTwo32Bits(1) == 1); + rp3d_test(nextPowerOfTwo32Bits(2) == 2); + rp3d_test(nextPowerOfTwo32Bits(3) == 4); + rp3d_test(nextPowerOfTwo32Bits(5) == 8); + rp3d_test(nextPowerOfTwo32Bits(6) == 8); + rp3d_test(nextPowerOfTwo32Bits(7) == 8); + rp3d_test(nextPowerOfTwo32Bits(1000) == 1024); + rp3d_test(nextPowerOfTwo32Bits(129) == 256); + rp3d_test(nextPowerOfTwo32Bits(260) == 512); } }; From 2b362b5098dfc6e6ea9997cc3a352ca251695530 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Sun, 30 Aug 2020 00:00:47 +0200 Subject: [PATCH 23/74] Use uint32 type for size/capacity of List --- include/reactphysics3d/body/CollisionBody.h | 2 +- .../collision/CollisionCallback.h | 4 +- .../reactphysics3d/collision/ContactPair.h | 8 +-- .../collision/HalfEdgeStructure.h | 26 +++---- .../collision/OverlapCallback.h | 8 +-- .../reactphysics3d/collision/TriangleMesh.h | 4 +- .../narrowphase/NarrowPhaseInfoBatch.h | 8 +-- include/reactphysics3d/containers/List.h | 38 +++++----- include/reactphysics3d/engine/Islands.h | 2 +- include/reactphysics3d/engine/PhysicsWorld.h | 4 +- src/body/CollisionBody.cpp | 21 +++--- src/body/RigidBody.cpp | 12 ++-- src/collision/CollisionCallback.cpp | 6 +- src/collision/HalfEdgeStructure.cpp | 36 +++++----- src/collision/OverlapCallback.cpp | 4 +- src/collision/PolyhedronMesh.cpp | 2 +- .../narrowphase/NarrowPhaseInfoBatch.cpp | 2 +- .../narrowphase/SAT/SATAlgorithm.cpp | 12 ++-- src/collision/shapes/CollisionShape.cpp | 2 +- src/collision/shapes/ConcaveMeshShape.cpp | 2 +- src/collision/shapes/ConvexMeshShape.cpp | 2 +- src/collision/shapes/HeightFieldShape.cpp | 2 +- src/engine/EntityManager.cpp | 2 +- src/engine/OverlappingPairs.cpp | 4 +- src/engine/PhysicsWorld.cpp | 6 +- src/mathematics/mathematics_functions.cpp | 10 +-- src/systems/CollisionDetectionSystem.cpp | 72 +++++++++---------- src/systems/ContactSolverSystem.cpp | 4 +- src/utils/DefaultLogger.cpp | 2 +- 29 files changed, 154 insertions(+), 153 deletions(-) diff --git a/include/reactphysics3d/body/CollisionBody.h b/include/reactphysics3d/body/CollisionBody.h index 98c22c4a..41d7923c 100644 --- a/include/reactphysics3d/body/CollisionBody.h +++ b/include/reactphysics3d/body/CollisionBody.h @@ -143,7 +143,7 @@ class CollisionBody { Collider* getCollider(uint colliderIndex); /// Return the number of colliders associated with this body - uint getNbColliders() const; + uint32 getNbColliders() const; /// Return the world-space coordinates of a point given the local-space coordinates of the body Vector3 getWorldPoint(const Vector3& localPoint) const; diff --git a/include/reactphysics3d/collision/CollisionCallback.h b/include/reactphysics3d/collision/CollisionCallback.h index 716de01f..e1210fad 100644 --- a/include/reactphysics3d/collision/CollisionCallback.h +++ b/include/reactphysics3d/collision/CollisionCallback.h @@ -278,7 +278,7 @@ class CollisionCallback { * @param index Index of the contact pair to retrieve * @return A contact pair object */ - ContactPair getContactPair(uint index) const; + ContactPair getContactPair(uint32 index) const; // -------------------- Friendship -------------------- // @@ -296,7 +296,7 @@ class CollisionCallback { /** * @return The number of contact pairs */ -RP3D_FORCE_INLINE uint CollisionCallback::CallbackData::getNbContactPairs() const { +RP3D_FORCE_INLINE uint32 CollisionCallback::CallbackData::getNbContactPairs() const { return mContactPairsIndices.size() + mLostContactPairsIndices.size(); } diff --git a/include/reactphysics3d/collision/ContactPair.h b/include/reactphysics3d/collision/ContactPair.h index abd648fd..53e4cbfb 100644 --- a/include/reactphysics3d/collision/ContactPair.h +++ b/include/reactphysics3d/collision/ContactPair.h @@ -69,19 +69,19 @@ struct ContactPair { bool isAlreadyInIsland; /// Index of the contact pair in the array of pairs - uint contactPairIndex; + uint32 contactPairIndex; /// Index of the first contact manifold in the array - uint contactManifoldsIndex; + uint32 contactManifoldsIndex; /// Number of contact manifolds int8 nbContactManifolds; /// Index of the first contact point in the array of contact points - uint contactPointsIndex; + uint32 contactPointsIndex; /// Total number of contact points in all the manifolds of the contact pair - uint nbToTalContactPoints; + uint32 nbToTalContactPoints; /// True if the colliders of the pair were already colliding in the previous frame bool collidingInPreviousFrame; diff --git a/include/reactphysics3d/collision/HalfEdgeStructure.h b/include/reactphysics3d/collision/HalfEdgeStructure.h index ca68064a..a8d5ac2e 100644 --- a/include/reactphysics3d/collision/HalfEdgeStructure.h +++ b/include/reactphysics3d/collision/HalfEdgeStructure.h @@ -65,11 +65,11 @@ class HalfEdgeStructure { /// Vertex struct Vertex { - uint vertexPointIndex; // Index of the vertex point in the origin vertex array - uint edgeIndex; // Index of one edge emanting from this vertex + uint32 vertexPointIndex; // Index of the vertex point in the origin vertex array + uint32 edgeIndex; // Index of one edge emanting from this vertex /// Constructor - Vertex(uint vertexCoordsIndex) : vertexPointIndex(vertexCoordsIndex) { } + Vertex(uint32 vertexCoordsIndex) : vertexPointIndex(vertexCoordsIndex) { } }; private: @@ -129,7 +129,7 @@ class HalfEdgeStructure { /** * @param vertexPointIndex Index of the vertex in the vertex data array */ -RP3D_FORCE_INLINE uint HalfEdgeStructure::addVertex(uint vertexPointIndex) { +RP3D_FORCE_INLINE uint32 HalfEdgeStructure::addVertex(uint32 vertexPointIndex) { Vertex vertex(vertexPointIndex); mVertices.add(vertex); return mVertices.size() - 1; @@ -151,31 +151,31 @@ RP3D_FORCE_INLINE void HalfEdgeStructure::addFace(List faceVertices) { /** * @return The number of faces in the polyhedron */ -RP3D_FORCE_INLINE uint HalfEdgeStructure::getNbFaces() const { - return static_cast(mFaces.size()); +RP3D_FORCE_INLINE uint32 HalfEdgeStructure::getNbFaces() const { + return mFaces.size(); } // Return the number of edges /** * @return The number of edges in the polyhedron */ -RP3D_FORCE_INLINE uint HalfEdgeStructure::getNbHalfEdges() const { - return static_cast(mEdges.size()); +RP3D_FORCE_INLINE uint32 HalfEdgeStructure::getNbHalfEdges() const { + return mEdges.size(); } // Return the number of vertices /** * @return The number of vertices in the polyhedron */ -RP3D_FORCE_INLINE uint HalfEdgeStructure::getNbVertices() const { - return static_cast(mVertices.size()); +RP3D_FORCE_INLINE uint32 HalfEdgeStructure::getNbVertices() const { + return mVertices.size(); } // Return a given face /** * @return A given face of the polyhedron */ -RP3D_FORCE_INLINE const HalfEdgeStructure::Face& HalfEdgeStructure::getFace(uint index) const { +RP3D_FORCE_INLINE const HalfEdgeStructure::Face& HalfEdgeStructure::getFace(uint32 index) const { assert(index < mFaces.size()); return mFaces[index]; } @@ -184,7 +184,7 @@ RP3D_FORCE_INLINE const HalfEdgeStructure::Face& HalfEdgeStructure::getFace(uint /** * @return A given edge of the polyhedron */ -RP3D_FORCE_INLINE const HalfEdgeStructure::Edge& HalfEdgeStructure::getHalfEdge(uint index) const { +RP3D_FORCE_INLINE const HalfEdgeStructure::Edge& HalfEdgeStructure::getHalfEdge(uint32 index) const { assert(index < mEdges.size()); return mEdges[index]; } @@ -193,7 +193,7 @@ RP3D_FORCE_INLINE const HalfEdgeStructure::Edge& HalfEdgeStructure::getHalfEdge( /** * @return A given vertex of the polyhedron */ -RP3D_FORCE_INLINE const HalfEdgeStructure::Vertex& HalfEdgeStructure::getVertex(uint index) const { +RP3D_FORCE_INLINE const HalfEdgeStructure::Vertex& HalfEdgeStructure::getVertex(uint32 index) const { assert(index < mVertices.size()); return mVertices[index]; } diff --git a/include/reactphysics3d/collision/OverlapCallback.h b/include/reactphysics3d/collision/OverlapCallback.h index 6601b879..e0126132 100644 --- a/include/reactphysics3d/collision/OverlapCallback.h +++ b/include/reactphysics3d/collision/OverlapCallback.h @@ -165,10 +165,10 @@ class OverlapCallback { // -------------------- Methods -------------------- // /// Return the number of overlapping pairs of bodies - uint getNbOverlappingPairs() const; + uint32 getNbOverlappingPairs() const; /// Return a given overlapping pair of bodies - OverlapPair getOverlappingPair(uint index) const; + OverlapPair getOverlappingPair(uint32 index) const; // -------------------- Friendship -------------------- // @@ -185,7 +185,7 @@ class OverlapCallback { }; // Return the number of overlapping pairs of bodies -RP3D_FORCE_INLINE uint OverlapCallback::CallbackData::getNbOverlappingPairs() const { +RP3D_FORCE_INLINE uint32 OverlapCallback::CallbackData::getNbOverlappingPairs() const { return mContactPairsIndices.size() + mLostContactPairsIndices.size(); } @@ -193,7 +193,7 @@ RP3D_FORCE_INLINE uint OverlapCallback::CallbackData::getNbOverlappingPairs() co /// Note that the returned OverlapPair object is only valid during the call of the CollisionCallback::onOverlap() /// method. Therefore, you need to get contact data from it and make a copy. Do not make a copy of the OverlapPair /// object itself because it won't be valid after the CollisionCallback::onOverlap() call. -RP3D_FORCE_INLINE OverlapCallback::OverlapPair OverlapCallback::CallbackData::getOverlappingPair(uint index) const { +RP3D_FORCE_INLINE OverlapCallback::OverlapPair OverlapCallback::CallbackData::getOverlappingPair(uint32 index) const { assert(index < getNbOverlappingPairs()); diff --git a/include/reactphysics3d/collision/TriangleMesh.h b/include/reactphysics3d/collision/TriangleMesh.h index 1cb4d516..87163407 100644 --- a/include/reactphysics3d/collision/TriangleMesh.h +++ b/include/reactphysics3d/collision/TriangleMesh.h @@ -87,7 +87,7 @@ RP3D_FORCE_INLINE void TriangleMesh::addSubpart(TriangleVertexArray* triangleVer * @param indexSubpart The index of the sub-part of the mesh * @return A pointer to the triangle vertex array of a given sub-part of the mesh */ -RP3D_FORCE_INLINE TriangleVertexArray* TriangleMesh::getSubpart(uint indexSubpart) const { +RP3D_FORCE_INLINE TriangleVertexArray* TriangleMesh::getSubpart(uint32 indexSubpart) const { assert(indexSubpart < mTriangleArrays.size()); return mTriangleArrays[indexSubpart]; } @@ -96,7 +96,7 @@ RP3D_FORCE_INLINE TriangleVertexArray* TriangleMesh::getSubpart(uint indexSubpar /** * @return The number of sub-parts of the mesh */ -RP3D_FORCE_INLINE uint TriangleMesh::getNbSubparts() const { +RP3D_FORCE_INLINE uint32 TriangleMesh::getNbSubparts() const { return mTriangleArrays.size(); } diff --git a/include/reactphysics3d/collision/narrowphase/NarrowPhaseInfoBatch.h b/include/reactphysics3d/collision/narrowphase/NarrowPhaseInfoBatch.h index 73b44cb3..f4f94d38 100644 --- a/include/reactphysics3d/collision/narrowphase/NarrowPhaseInfoBatch.h +++ b/include/reactphysics3d/collision/narrowphase/NarrowPhaseInfoBatch.h @@ -109,7 +109,7 @@ struct NarrowPhaseInfoBatch { OverlappingPairs& mOverlappingPairs; /// Cached capacity - uint mCachedCapacity = 0; + uint32 mCachedCapacity = 0; public: @@ -128,10 +128,10 @@ struct NarrowPhaseInfoBatch { bool needToReportContacts, LastFrameCollisionInfo* lastFrameInfo, MemoryAllocator& shapeAllocator); /// Return the number of objects in the batch - uint getNbObjects() const; + uint32 getNbObjects() const; /// Add a new contact point - void addContactPoint(uint index, const Vector3& contactNormal, decimal penDepth, + void addContactPoint(uint32 index, const Vector3& contactNormal, decimal penDepth, const Vector3& localPt1, const Vector3& localPt2); /// Reset the remaining contact points @@ -145,7 +145,7 @@ struct NarrowPhaseInfoBatch { }; /// Return the number of objects in the batch -RP3D_FORCE_INLINE uint NarrowPhaseInfoBatch::getNbObjects() const { +RP3D_FORCE_INLINE uint32 NarrowPhaseInfoBatch::getNbObjects() const { return narrowPhaseInfos.size(); } diff --git a/include/reactphysics3d/containers/List.h b/include/reactphysics3d/containers/List.h index 11c63915..d0c5a731 100755 --- a/include/reactphysics3d/containers/List.h +++ b/include/reactphysics3d/containers/List.h @@ -51,10 +51,10 @@ class List { void* mBuffer; /// Number of elements in the list - size_t mSize; + uint32 mSize; /// Number of allocated elements in the list - size_t mCapacity; + uint32 mCapacity; /// Memory allocator MemoryAllocator& mAllocator; @@ -69,9 +69,9 @@ class List { private: - size_t mCurrentIndex; + uint32 mCurrentIndex; T* mBuffer; - size_t mSize; + uint32 mSize; public: @@ -88,7 +88,7 @@ class List { Iterator() = default; /// Constructor - Iterator(void* buffer, size_t index, size_t size) + Iterator(void* buffer, uint32 index, uint32 size) :mCurrentIndex(index), mBuffer(static_cast(buffer)), mSize(size) { } @@ -219,7 +219,7 @@ class List { // -------------------- Methods -------------------- // /// Constructor - List(MemoryAllocator& allocator, size_t capacity = 0) + List(MemoryAllocator& allocator, uint32 capacity = 0) : mBuffer(nullptr), mSize(0), mCapacity(0), mAllocator(allocator) { if (capacity > 0) { @@ -253,7 +253,7 @@ class List { } /// Allocate memory for a given number of elements - void reserve(size_t capacity) { + void reserve(uint32 capacity) { if (capacity <= mCapacity) return; @@ -270,7 +270,7 @@ class List { std::uninitialized_copy(items, items + mSize, destination); // Destruct the previous items - for (size_t i=0; i mCapacity) { @@ -330,7 +330,7 @@ class List { /// this method returns the end() iterator Iterator find(const T& element) { - for (uint i=0; i(mBuffer)[i]) { return Iterator(mBuffer, i, mSize); } @@ -352,7 +352,7 @@ class List { } /// Remove an element from the list at a given index (all the following items will be moved) - Iterator removeAt(uint index) { + Iterator removeAt(uint32 index) { assert(index >= 0 && index < mSize); @@ -374,7 +374,7 @@ class List { } /// Remove an element from the list at a given index and replace it by the last one of the list (if any) - void removeAtAndReplaceByLast(uint index) { + void removeAtAndReplaceByLast(uint32 index) { assert(index >= 0 && index < mSize); @@ -405,7 +405,7 @@ class List { } // Add the elements of the list to the current one - for(uint i=0; i(mBuffer) + mSize * sizeof(T)) T(list[i]); mSize++; @@ -416,7 +416,7 @@ class List { void clear(bool releaseMemory = false) { // Call the destructor of each element - for (uint i=0; i < mSize; i++) { + for (uint32 i=0; i < mSize; i++) { (static_cast(mBuffer)[i]).~T(); } @@ -434,23 +434,23 @@ class List { } /// Return the number of elements in the list - size_t size() const { + uint32 size() const { return mSize; } /// Return the capacity of the list - size_t capacity() const { + uint32 capacity() const { return mCapacity; } /// Overloaded index operator - T& operator[](const uint index) { + T& operator[](const uint32 index) { assert(index >= 0 && index < mSize); return (static_cast(mBuffer)[index]); } /// Overloaded const index operator - const T& operator[](const uint index) const { + const T& operator[](const uint32 index) const { assert(index >= 0 && index < mSize); return (static_cast(mBuffer)[index]); } @@ -461,7 +461,7 @@ class List { if (mSize != list.mSize) return false; T* items = static_cast(mBuffer); - for (size_t i=0; i < mSize; i++) { + for (uint32 i=0; i < mSize; i++) { if (items[i] != list[i]) { return false; } diff --git a/include/reactphysics3d/engine/Islands.h b/include/reactphysics3d/engine/Islands.h index 59d09f7e..bfe57081 100644 --- a/include/reactphysics3d/engine/Islands.h +++ b/include/reactphysics3d/engine/Islands.h @@ -96,7 +96,7 @@ struct Islands { /// Return the number of islands uint32 getNbIslands() const { - return static_cast(contactManifoldsIndices.size()); + return contactManifoldsIndices.size(); } /// Add an island and return its index diff --git a/include/reactphysics3d/engine/PhysicsWorld.h b/include/reactphysics3d/engine/PhysicsWorld.h index e2618959..baee7c3b 100644 --- a/include/reactphysics3d/engine/PhysicsWorld.h +++ b/include/reactphysics3d/engine/PhysicsWorld.h @@ -681,7 +681,7 @@ RP3D_FORCE_INLINE void PhysicsWorld::setEventListener(EventListener* eventListen /** * @return The number of collision bodies in the physics world */ -RP3D_FORCE_INLINE uint PhysicsWorld::getNbCollisionBodies() const { +RP3D_FORCE_INLINE uint32 PhysicsWorld::getNbCollisionBodies() const { return mCollisionBodies.size(); } @@ -689,7 +689,7 @@ RP3D_FORCE_INLINE uint PhysicsWorld::getNbCollisionBodies() const { /** * @return The number of rigid bodies in the physics world */ -RP3D_FORCE_INLINE uint PhysicsWorld::getNbRigidBodies() const { +RP3D_FORCE_INLINE uint32 PhysicsWorld::getNbRigidBodies() const { return mRigidBodies.size(); } diff --git a/src/body/CollisionBody.cpp b/src/body/CollisionBody.cpp index 4f065264..dbfada79 100644 --- a/src/body/CollisionBody.cpp +++ b/src/body/CollisionBody.cpp @@ -131,8 +131,8 @@ Collider* CollisionBody::addCollider(CollisionShape* collisionShape, const Trans /** * @return The number of colliders associated with this body */ -uint CollisionBody::getNbColliders() const { - return static_cast(mWorld.mCollisionBodyComponents.getColliders(mEntity).size()); +uint32 CollisionBody::getNbColliders() const { + return mWorld.mCollisionBodyComponents.getColliders(mEntity).size(); } // Return a const pointer to a given collider of the body @@ -202,7 +202,7 @@ void CollisionBody::removeAllColliders() { // Look for the collider that contains the collision shape in parameter. // Note that we need to copy the list of collider entities because we are deleting them in a loop. const List collidersEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); - for (uint i=0; i < collidersEntities.size(); i++) { + for (uint32 i=0; i < collidersEntities.size(); i++) { removeCollider(mWorld.mCollidersComponents.getCollider(collidersEntities[i])); } @@ -223,7 +223,8 @@ void CollisionBody::updateBroadPhaseState(decimal timeStep) const { // For all the colliders of the body const List& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); - for (uint i=0; i < colliderEntities.size(); i++) { + const uint32 nbColliderEntities = colliderEntities.size(); + for (uint32 i=0; i < nbColliderEntities; i++) { // Update the local-to-world transform of the collider mWorld.mCollidersComponents.setLocalToWorldTransform(colliderEntities[i], @@ -253,7 +254,7 @@ void CollisionBody::setIsActive(bool isActive) { // For each collider of the body const List& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); - for (uint i=0; i < colliderEntities.size(); i++) { + for (uint32 i=0; i < colliderEntities.size(); i++) { Collider* collider = mWorld.mCollidersComponents.getCollider(colliderEntities[i]); @@ -269,7 +270,7 @@ void CollisionBody::setIsActive(bool isActive) { // For each collider of the body const List& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); - for (uint i=0; i < colliderEntities.size(); i++) { + for (uint32 i=0; i < colliderEntities.size(); i++) { Collider* collider = mWorld.mCollidersComponents.getCollider(colliderEntities[i]); @@ -292,7 +293,7 @@ void CollisionBody::askForBroadPhaseCollisionCheck() const { // For all the colliders of the body const List& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); - for (uint i=0; i < colliderEntities.size(); i++) { + for (uint32 i=0; i < colliderEntities.size(); i++) { Collider* collider = mWorld.mCollidersComponents.getCollider(colliderEntities[i]); @@ -310,7 +311,7 @@ bool CollisionBody::testPointInside(const Vector3& worldPoint) const { // For each collider of the body const List& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); - for (uint i=0; i < colliderEntities.size(); i++) { + for (uint32 i=0; i < colliderEntities.size(); i++) { Collider* collider = mWorld.mCollidersComponents.getCollider(colliderEntities[i]); @@ -339,7 +340,7 @@ bool CollisionBody::raycast(const Ray& ray, RaycastInfo& raycastInfo) { // For each collider of the body const List& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); - for (uint i=0; i < colliderEntities.size(); i++) { + for (uint32 i=0; i < colliderEntities.size(); i++) { Collider* collider = mWorld.mCollidersComponents.getCollider(colliderEntities[i]); @@ -370,7 +371,7 @@ AABB CollisionBody::getAABB() const { collider->getCollisionShape()->computeAABB(bodyAABB, transform * collider->getLocalToBodyTransform()); // For each collider of the body - for (uint i=1; i < colliderEntities.size(); i++) { + for (uint32 i=1; i < colliderEntities.size(); i++) { Collider* collider = mWorld.mCollidersComponents.getCollider(colliderEntities[i]); diff --git a/src/body/RigidBody.cpp b/src/body/RigidBody.cpp index 222a97d9..74cdf8ed 100644 --- a/src/body/RigidBody.cpp +++ b/src/body/RigidBody.cpp @@ -325,7 +325,7 @@ Vector3 RigidBody::computeCenterOfMass() const { // Compute the local center of mass const List& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); - for (uint i=0; i < colliderEntities.size(); i++) { + for (uint32 i=0; i < colliderEntities.size(); i++) { const uint colliderIndex = mWorld.mCollidersComponents.getEntityIndex(colliderEntities[i]); @@ -357,7 +357,7 @@ void RigidBody::computeMassAndInertiaTensorLocal(Vector3& inertiaTensorLocal, de // Compute the inertia tensor using all the colliders const List& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); - for (uint i=0; i < colliderEntities.size(); i++) { + for (uint32 i=0; i < colliderEntities.size(); i++) { const uint colliderIndex = mWorld.mCollidersComponents.getEntityIndex(colliderEntities[i]); @@ -432,7 +432,7 @@ void RigidBody::updateMassFromColliders() { // Compute the total mass of the body const List& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); - for (uint i=0; i < colliderEntities.size(); i++) { + for (uint32 i=0; i < colliderEntities.size(); i++) { const uint colliderIndex = mWorld.mCollidersComponents.getEntityIndex(colliderEntities[i]); @@ -872,12 +872,12 @@ void RigidBody::resetOverlappingPairs() { // For each collider of the body const List& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); - for (uint i=0; i < colliderEntities.size(); i++) { + for (uint32 i=0; i < colliderEntities.size(); i++) { // Get the currently overlapping pairs for this collider List overlappingPairs = mWorld.mCollidersComponents.getOverlappingPairs(colliderEntities[i]); - for (uint j=0; j < overlappingPairs.size(); j++) { + for (uint32 j=0; j < overlappingPairs.size(); j++) { mWorld.mCollisionDetection.mOverlappingPairs.removePair(overlappingPairs[j]); } @@ -952,7 +952,7 @@ void RigidBody::setProfiler(Profiler* profiler) { // Set the profiler for each collider const List& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); - for (uint i=0; i < colliderEntities.size(); i++) { + for (uint32 i=0; i < colliderEntities.size(); i++) { Collider* collider = mWorld.mCollidersComponents.getCollider(colliderEntities[i]); diff --git a/src/collision/CollisionCallback.cpp b/src/collision/CollisionCallback.cpp index 88779023..c9fbc5ff 100644 --- a/src/collision/CollisionCallback.cpp +++ b/src/collision/CollisionCallback.cpp @@ -82,7 +82,7 @@ CollisionCallback::CallbackData::CallbackData(List* mContactPairsIndices(world.mMemoryManager.getHeapAllocator()), mLostContactPairsIndices(world.mMemoryManager.getHeapAllocator()), mWorld(world) { // Filter the contact pairs to only keep the contact events (not the overlap/trigger events) - for (uint i=0; i < mContactPairs->size(); i++) { + for (uint32 i=0; i < mContactPairs->size(); i++) { // If the contact pair contains contacts (and is therefore not an overlap/trigger event) if (!(*mContactPairs)[i].isTrigger) { @@ -90,7 +90,7 @@ CollisionCallback::CallbackData::CallbackData(List* } } // Filter the lost contact pairs to only keep the contact events (not the overlap/trigger events) - for (uint i=0; i < mLostContactPairs.size(); i++) { + for (uint32 i=0; i < mLostContactPairs.size(); i++) { // If the contact pair contains contacts (and is therefore not an overlap/trigger event) if (!mLostContactPairs[i].isTrigger) { @@ -114,7 +114,7 @@ CollisionCallback::ContactPoint CollisionCallback::ContactPair::getContactPoint( /// Note that the returned ContactPair object is only valid during the call of the CollisionCallback::onContact() /// method. Therefore, you need to get contact data from it and make a copy. Do not make a copy of the ContactPair /// object itself because it won't be valid after the CollisionCallback::onContact() call. -CollisionCallback::ContactPair CollisionCallback::CallbackData::getContactPair(uint index) const { +CollisionCallback::ContactPair CollisionCallback::CallbackData::getContactPair(uint32 index) const { assert(index < getNbContactPairs()); diff --git a/src/collision/HalfEdgeStructure.cpp b/src/collision/HalfEdgeStructure.cpp index b1da7ef8..0892b77c 100644 --- a/src/collision/HalfEdgeStructure.cpp +++ b/src/collision/HalfEdgeStructure.cpp @@ -36,24 +36,24 @@ void HalfEdgeStructure::init() { Map edges(mAllocator); Map nextEdges(mAllocator); - Map mapEdgeToStartVertex(mAllocator); - Map mapEdgeToIndex(mAllocator); - Map mapEdgeIndexToKey(mAllocator); - Map mapFaceIndexToEdgeKey(mAllocator); + Map mapEdgeToStartVertex(mAllocator); + Map mapEdgeToIndex(mAllocator); + Map mapEdgeIndexToKey(mAllocator); + Map mapFaceIndexToEdgeKey(mAllocator); List currentFaceEdges(mAllocator, mFaces[0].faceVertices.size()); // For each face - for (uint f=0; f(pairV1V2, v1Index), true); - mapEdgeToStartVertex.add(Pair(pairV2V1, v2Index), true); + mapEdgeToStartVertex.add(Pair(pairV1V2, v1Index), true); + mapEdgeToStartVertex.add(Pair(pairV2V1, v2Index), true); - mapFaceIndexToEdgeKey.add(Pair(f, pairV1V2), true); + mapFaceIndexToEdgeKey.add(Pair(f, pairV1V2), true); auto itEdge = edges.find(pairV2V1); if (itEdge != edges.end()) { - const uint edgeIndex = mEdges.size(); + const uint32 edgeIndex = mEdges.size(); itEdge->second.twinEdgeIndex = edgeIndex + 1; edge.twinEdgeIndex = edgeIndex; - mapEdgeIndexToKey.add(Pair(edgeIndex, pairV2V1)); - mapEdgeIndexToKey.add(Pair(edgeIndex + 1, pairV1V2)); + mapEdgeIndexToKey.add(Pair(edgeIndex, pairV2V1)); + mapEdgeIndexToKey.add(Pair(edgeIndex + 1, pairV1V2)); mVertices[v1Index].edgeIndex = edgeIndex + 1; mVertices[v2Index].edgeIndex = edgeIndex; - mapEdgeToIndex.add(Pair(pairV1V2, edgeIndex + 1)); - mapEdgeToIndex.add(Pair(pairV2V1, edgeIndex)); + mapEdgeToIndex.add(Pair(pairV1V2, edgeIndex + 1)); + mapEdgeToIndex.add(Pair(pairV2V1, edgeIndex)); mEdges.add(itEdge->second); mEdges.add(edge); @@ -107,12 +107,12 @@ void HalfEdgeStructure::init() { } // Set next edges - for (uint i=0; i < mEdges.size(); i++) { + for (uint32 i=0; i < mEdges.size(); i++) { mEdges[i].nextEdgeIndex = mapEdgeToIndex[nextEdges[mapEdgeIndexToKey[i]]]; } // Set face edge - for (uint f=0; f < mFaces.size(); f++) { + for (uint32 f=0; f < mFaces.size(); f++) { mFaces[f].edgeIndex = mapEdgeToIndex[mapFaceIndexToEdgeKey[f]]; } } diff --git a/src/collision/OverlapCallback.cpp b/src/collision/OverlapCallback.cpp index 5cc9b571..55bdf9f4 100644 --- a/src/collision/OverlapCallback.cpp +++ b/src/collision/OverlapCallback.cpp @@ -72,7 +72,7 @@ OverlapCallback::CallbackData::CallbackData(List& contactPairs, Lis mContactPairsIndices(world.mMemoryManager.getHeapAllocator()), mLostContactPairsIndices(world.mMemoryManager.getHeapAllocator()), mWorld(world) { // Filter the contact pairs to only keep the overlap/trigger events (not the contact events) - for (uint i=0; i < mContactPairs.size(); i++) { + for (uint32 i=0; i < mContactPairs.size(); i++) { // If the contact pair contains contacts (and is therefore not an overlap/trigger event) if (!onlyReportTriggers || mContactPairs[i].isTrigger) { @@ -80,7 +80,7 @@ OverlapCallback::CallbackData::CallbackData(List& contactPairs, Lis } } // Filter the lost contact pairs to only keep the overlap/trigger events (not the contact events) - for (uint i=0; i < mLostContactPairs.size(); i++) { + for (uint32 i=0; i < mLostContactPairs.size(); i++) { // If the contact pair contains contacts (and is therefore not an overlap/trigger event) if (!onlyReportTriggers || mLostContactPairs[i].isTrigger) { diff --git a/src/collision/PolyhedronMesh.cpp b/src/collision/PolyhedronMesh.cpp index 28ae4754..653fde1a 100644 --- a/src/collision/PolyhedronMesh.cpp +++ b/src/collision/PolyhedronMesh.cpp @@ -166,7 +166,7 @@ decimal PolyhedronMesh::getFaceArea(uint faceIndex) const { Vector3 v1 = getVertex(face.faceVertices[0]); // For each vertex of the face - for (uint i=2; i < face.faceVertices.size(); i++) { + for (uint32 i=2; i < face.faceVertices.size(); i++) { const Vector3 v2 = getVertex(face.faceVertices[i-1]); const Vector3 v3 = getVertex(face.faceVertices[i]); diff --git a/src/collision/narrowphase/NarrowPhaseInfoBatch.cpp b/src/collision/narrowphase/NarrowPhaseInfoBatch.cpp index dcde890e..07d17fdd 100644 --- a/src/collision/narrowphase/NarrowPhaseInfoBatch.cpp +++ b/src/collision/narrowphase/NarrowPhaseInfoBatch.cpp @@ -52,7 +52,7 @@ void NarrowPhaseInfoBatch::reserveMemory() { // Clear all the objects in the batch void NarrowPhaseInfoBatch::clear() { - for (uint i=0; i < narrowPhaseInfos.size(); i++) { + for (uint32 i=0; i < narrowPhaseInfos.size(); i++) { assert(narrowPhaseInfos[i].nbContactPoints == 0); diff --git a/src/collision/narrowphase/SAT/SATAlgorithm.cpp b/src/collision/narrowphase/SAT/SATAlgorithm.cpp index 22118b78..b67c94a2 100644 --- a/src/collision/narrowphase/SAT/SATAlgorithm.cpp +++ b/src/collision/narrowphase/SAT/SATAlgorithm.cpp @@ -430,7 +430,7 @@ bool SATAlgorithm::computeCapsulePolyhedronFaceContactPoints(uint referenceFaceI bool contactFound = false; // For each of the two clipped points - for (uint i = 0; igetFace(incidentFaceIndex); - uint nbIncidentFaceVertices = static_cast(incidentFace.faceVertices.size()); + uint32 nbIncidentFaceVertices = incidentFace.faceVertices.size(); List polygonVertices(mMemoryAllocator, nbIncidentFaceVertices); // Vertices to clip of the incident face List planesNormals(mMemoryAllocator, nbIncidentFaceVertices); // Normals of the clipping planes List planesPoints(mMemoryAllocator, nbIncidentFaceVertices); // Points on the clipping planes // Get all the vertices of the incident face (in the reference local-space) - for (uint i=0; i < incidentFace.faceVertices.size(); i++) { + for (uint32 i=0; i < incidentFace.faceVertices.size(); i++) { const Vector3 faceVertexIncidentSpace = incidentPolyhedron->getVertexPosition(incidentFace.faceVertices[i]); polygonVertices.add(incidentToReferenceTransform * faceVertexIncidentSpace); } // Get the reference face clipping planes - uint currentEdgeIndex = referenceFace.edgeIndex; - uint firstEdgeIndex = currentEdgeIndex; + uint32 currentEdgeIndex = referenceFace.edgeIndex; + uint32 firstEdgeIndex = currentEdgeIndex; do { // Get the adjacent edge @@ -963,7 +963,7 @@ bool SATAlgorithm::computePolyhedronVsPolyhedronFaceContactPoints(bool isMinPene // We only keep the clipped points that are below the reference face const Vector3 referenceFaceVertex = referencePolyhedron->getVertexPosition(referencePolyhedron->getHalfEdge(firstEdgeIndex).vertexIndex); bool contactPointsFound = false; - for (uint i=0; isetHasCollisionShapeChangedSize(true); } } diff --git a/src/collision/shapes/ConcaveMeshShape.cpp b/src/collision/shapes/ConcaveMeshShape.cpp index f977b752..766d6d7f 100644 --- a/src/collision/shapes/ConcaveMeshShape.cpp +++ b/src/collision/shapes/ConcaveMeshShape.cpp @@ -142,7 +142,7 @@ void ConcaveMeshShape::computeOverlappingTriangles(const AABB& localAABB, List overlappingNodes(allocator, 64); mDynamicAABBTree.reportAllShapesOverlappingWithAABB(aabb, overlappingNodes); - const uint nbOverlappingNodes = overlappingNodes.size(); + const uint32 nbOverlappingNodes = overlappingNodes.size(); // Add space in the list of triangles vertices/normals for the new triangles triangleVertices.addWithoutInit(nbOverlappingNodes * 3); diff --git a/src/collision/shapes/ConvexMeshShape.cpp b/src/collision/shapes/ConvexMeshShape.cpp index b90f11b6..06589d4e 100644 --- a/src/collision/shapes/ConvexMeshShape.cpp +++ b/src/collision/shapes/ConvexMeshShape.cpp @@ -231,7 +231,7 @@ std::string ConvexMeshShape::to_string() const { ss << "["; - for (uint v=0; v < face.faceVertices.size(); v++) { + for (uint32 v=0; v < face.faceVertices.size(); v++) { ss << face.faceVertices[v]; if (v != face.faceVertices.size() - 1) { diff --git a/src/collision/shapes/HeightFieldShape.cpp b/src/collision/shapes/HeightFieldShape.cpp index dd056ca3..e6c3f7bb 100644 --- a/src/collision/shapes/HeightFieldShape.cpp +++ b/src/collision/shapes/HeightFieldShape.cpp @@ -250,7 +250,7 @@ bool HeightFieldShape::raycast(const Ray& ray, RaycastInfo& raycastInfo, Collide decimal smallestHitFraction = ray.maxFraction; // For each overlapping triangle - for (uint i=0; i < shapeIds.size(); i++) + for (uint32 i=0; i < shapeIds.size(); i++) { // Create a triangle collision shape TriangleShape triangleShape(&(triangleVertices[i * 3]), &(triangleVerticesNormals[i * 3]), shapeIds[i], allocator); diff --git a/src/engine/EntityManager.cpp b/src/engine/EntityManager.cpp index e7cd4231..15b6aca7 100644 --- a/src/engine/EntityManager.cpp +++ b/src/engine/EntityManager.cpp @@ -53,7 +53,7 @@ Entity EntityManager::createEntity() { mGenerations.add(0); // Create a new indice - index = static_cast(mGenerations.size()) - 1; + index = mGenerations.size() - 1; assert(index < (1 << Entity::ENTITY_INDEX_BITS)); } diff --git a/src/engine/OverlappingPairs.cpp b/src/engine/OverlappingPairs.cpp index c0ad16ef..372e7231 100644 --- a/src/engine/OverlappingPairs.cpp +++ b/src/engine/OverlappingPairs.cpp @@ -81,7 +81,7 @@ void OverlappingPairs::removePair(uint64 pairIndex, bool isConvexVsConvex) { if (isConvexVsConvex) { - const uint nbConvexPairs = mConvexPairs.size(); + const uint32 nbConvexPairs = mConvexPairs.size(); assert(pairIndex < nbConvexPairs); @@ -106,7 +106,7 @@ void OverlappingPairs::removePair(uint64 pairIndex, bool isConvexVsConvex) { } else { - const uint nbConcavePairs = mConcavePairs.size(); + const uint32 nbConcavePairs = mConcavePairs.size(); assert(pairIndex < nbConcavePairs); diff --git a/src/engine/PhysicsWorld.cpp b/src/engine/PhysicsWorld.cpp index 04fd5598..5b67261a 100644 --- a/src/engine/PhysicsWorld.cpp +++ b/src/engine/PhysicsWorld.cpp @@ -239,7 +239,7 @@ void PhysicsWorld::setBodyDisabled(Entity bodyEntity, bool isDisabled) { // For each collider of the body const List& collidersEntities = mCollisionBodyComponents.getColliders(bodyEntity); - for (uint i=0; i < collidersEntities.size(); i++) { + for (uint32 i=0; i < collidersEntities.size(); i++) { mCollidersComponents.setIsEntityDisabled(collidersEntities[i], isDisabled); } @@ -822,7 +822,7 @@ void PhysicsWorld::createIslands() { // If the body is involved in contacts with other bodies // For each contact pair in which the current body is involded - for (uint p=0; p < mRigidBodyComponents.mContactPairs[bodyToVisitIndex].size(); p++) { + for (uint32 p=0; p < mRigidBodyComponents.mContactPairs[bodyToVisitIndex].size(); p++) { ContactPair& pair = (*mCollisionDetection.mCurrentContactPairs)[mRigidBodyComponents.mContactPairs[bodyToVisitIndex][p]]; @@ -884,7 +884,7 @@ void PhysicsWorld::createIslands() { // Reset the isAlreadyIsland variable of the static bodies so that they // can also be included in the other islands - for (uint j=0; j < staticBodiesAddedToIsland.size(); j++) { + for (uint32 j=0; j < staticBodiesAddedToIsland.size(); j++) { assert(mRigidBodyComponents.getBodyType(staticBodiesAddedToIsland[j]) == BodyType::STATIC); mRigidBodyComponents.setIsAlreadyInIsland(staticBodiesAddedToIsland[j], false); diff --git a/src/mathematics/mathematics_functions.cpp b/src/mathematics/mathematics_functions.cpp index daf021b0..4db9367c 100755 --- a/src/mathematics/mathematics_functions.cpp +++ b/src/mathematics/mathematics_functions.cpp @@ -234,7 +234,7 @@ List reactphysics3d::clipSegmentWithPlanes(const Vector3& segA, const V inputVertices.add(segB); // For each clipping plane - for (uint p=0; p reactphysics3d::clipPolygonWithPlanes(const List& polygon assert(planesPoints.size() == planesNormals.size()); - uint nbMaxElements = polygonVertices.size() + planesPoints.size(); + uint32 nbMaxElements = polygonVertices.size() + planesPoints.size(); List inputVertices(allocator, nbMaxElements); List outputVertices(allocator, nbMaxElements); inputVertices.addRange(polygonVertices); // For each clipping plane - for (uint p=0; p nodePair = overlappingNodes[i]; @@ -475,8 +475,8 @@ void CollisionDetectionSystem::computeConvexVsConcaveMiddlePhase(OverlappingPair } // For each overlapping triangle - const uint nbShapeIds = shapeIds.size(); - for (uint i=0; i < nbShapeIds; i++) { + const uint32 nbShapeIds = shapeIds.size(); + for (uint32 i=0; i < nbShapeIds; i++) { // Create a triangle collision shape (the allocated memory for the TriangleShape will be released in the // destructor of the corresponding NarrowPhaseInfo. @@ -620,7 +620,7 @@ void CollisionDetectionSystem::computeNarrowPhase() { void CollisionDetectionSystem::computeMapPreviousContactPairs() { mPreviousMapPairIdToContactPairIndex.clear(); - for (uint i=0; i < mCurrentContactPairs->size(); i++) { + for (uint32 i=0; i < mCurrentContactPairs->size(); i++) { mPreviousMapPairIdToContactPairIndex.add(Pair((*mCurrentContactPairs)[i].pairId, i)); } } @@ -678,8 +678,8 @@ void CollisionDetectionSystem::notifyOverlappingPairsToTestOverlap(Collider* col // Get the overlapping pairs involved with this collider List& overlappingPairs = mCollidersComponents.getOverlappingPairs(collider->getEntity()); - const uint nbPairs = overlappingPairs.size(); - for (uint i=0; i < nbPairs; i++) { + const uint32 nbPairs = overlappingPairs.size(); + for (uint32 i=0; i < nbPairs; i++) { // Notify that the overlapping pair needs to be testbed for overlap mOverlappingPairs.setNeedToTestOverlap(overlappingPairs[i], true); @@ -818,7 +818,7 @@ void CollisionDetectionSystem::createContacts() { ContactManifoldInfo& potentialManifold = mPotentialContactManifolds[contactPair.potentialContactManifoldsIndices[m]]; // Start index and number of contact points for this manifold - const uint contactPointsIndex = mCurrentContactPoints->size(); + const uint32 contactPointsIndex = mCurrentContactPoints->size(); const int8 nbContactPoints = static_cast(potentialManifold.nbPotentialContactPoints); contactPair.nbToTalContactPoints += nbContactPoints; @@ -858,8 +858,8 @@ void CollisionDetectionSystem::createContacts() { void CollisionDetectionSystem::computeLostContactPairs() { // For each convex pair - const uint nbConvexPairs = mOverlappingPairs.mConvexPairs.size(); - for (uint i=0; i < nbConvexPairs; i++) { + const uint32 nbConvexPairs = mOverlappingPairs.mConvexPairs.size(); + for (uint32 i=0; i < nbConvexPairs; i++) { // If the two colliders of the pair were colliding in the previous frame but not in the current one if (mOverlappingPairs.mConvexPairs[i].collidingInPreviousFrame && !mOverlappingPairs.mConvexPairs[i].collidingInCurrentFrame) { @@ -874,8 +874,8 @@ void CollisionDetectionSystem::computeLostContactPairs() { } // For each convex pair - const uint nbConcavePairs = mOverlappingPairs.mConcavePairs.size(); - for (uint i=0; i < nbConcavePairs; i++) { + const uint32 nbConcavePairs = mOverlappingPairs.mConcavePairs.size(); + for (uint32 i=0; i < nbConcavePairs; i++) { // If the two colliders of the pair were colliding in the previous frame but not in the current one if (mOverlappingPairs.mConcavePairs[i].collidingInPreviousFrame && !mOverlappingPairs.mConcavePairs[i].collidingInCurrentFrame) { @@ -903,8 +903,8 @@ void CollisionDetectionSystem::createSnapshotContacts(List& contact contactPoints.reserve(contactManifolds.size()); // For each contact pair - const uint nbContactPairs = contactPairs.size(); - for (uint p=0; p < nbContactPairs; p++) { + const uint32 nbContactPairs = contactPairs.size(); + for (uint32 p=0; p < nbContactPairs; p++) { ContactPair& contactPair = contactPairs[p]; assert(contactPair.nbPotentialContactManifolds > 0); @@ -914,12 +914,12 @@ void CollisionDetectionSystem::createSnapshotContacts(List& contact contactPair.contactPointsIndex = contactPoints.size(); // For each potential contact manifold of the pair - for (uint m=0; m < contactPair.nbPotentialContactManifolds; m++) { + for (uint32 m=0; m < contactPair.nbPotentialContactManifolds; m++) { ContactManifoldInfo& potentialManifold = potentialContactManifolds[contactPair.potentialContactManifoldsIndices[m]]; // Start index and number of contact points for this manifold - const uint contactPointsIndex = contactPoints.size(); + const uint32 contactPointsIndex = contactPoints.size(); const uint8 nbContactPoints = potentialManifold.nbPotentialContactPoints; contactPair.nbToTalContactPoints += nbContactPoints; @@ -930,7 +930,7 @@ void CollisionDetectionSystem::createSnapshotContacts(List& contact assert(potentialManifold.nbPotentialContactPoints > 0); // For each contact point of the manifold - for (uint c=0; c < potentialManifold.nbPotentialContactPoints; c++) { + for (uint32 c=0; c < potentialManifold.nbPotentialContactPoints; c++) { ContactPointInfo& potentialContactPoint = potentialContactPoints[potentialManifold.potentialContactPointsIndices[c]]; @@ -948,7 +948,7 @@ void CollisionDetectionSystem::createSnapshotContacts(List& contact void CollisionDetectionSystem::initContactsWithPreviousOnes() { // For each contact pair of the current frame - for (uint i=0; i < mCurrentContactPairs->size(); i++) { + for (uint32 i=0; i < mCurrentContactPairs->size(); i++) { ContactPair& currentContactPair = (*mCurrentContactPairs)[i]; @@ -1124,7 +1124,7 @@ void CollisionDetectionSystem::processPotentialContacts(NarrowPhaseInfoBatch& na assert(!mWorld->mCollisionBodyComponents.getIsEntityDisabled(body1Entity) || !mWorld->mCollisionBodyComponents.getIsEntityDisabled(body2Entity)); - const uint newContactPairIndex = contactPairs->size(); + const uint32 newContactPairIndex = contactPairs->size(); contactPairs->emplace(pairId, body1Entity, body2Entity, collider1Entity, collider2Entity, newContactPairIndex, overlappingPair->collidingInPreviousFrame, isTrigger); @@ -1132,11 +1132,11 @@ void CollisionDetectionSystem::processPotentialContacts(NarrowPhaseInfoBatch& na ContactPair* pairContact = &((*contactPairs)[newContactPairIndex]); // Create a new potential contact manifold for the overlapping pair - uint contactManifoldIndex = static_cast(potentialContactManifolds.size()); + uint32 contactManifoldIndex = static_cast(potentialContactManifolds.size()); potentialContactManifolds.emplace(pairId); ContactManifoldInfo& contactManifoldInfo = potentialContactManifolds[contactManifoldIndex]; - const uint contactPointIndexStart = static_cast(potentialContactPoints.size()); + const uint32 contactPointIndexStart = static_cast(potentialContactPoints.size()); // Add the potential contacts for (uint j=0; j < narrowPhaseInfoBatch.narrowPhaseInfos[i].nbContactPoints; j++) { @@ -1172,7 +1172,7 @@ void CollisionDetectionSystem::processPotentialContacts(NarrowPhaseInfoBatch& na assert(!mWorld->mCollisionBodyComponents.getIsEntityDisabled(body1Entity) || !mWorld->mCollisionBodyComponents.getIsEntityDisabled(body2Entity)); - const uint newContactPairIndex = contactPairs->size(); + const uint32 newContactPairIndex = contactPairs->size(); contactPairs->emplace(pairId, body1Entity, body2Entity, collider1Entity, collider2Entity, newContactPairIndex, overlappingPair->collidingInPreviousFrame , isTrigger); pairContact = &((*contactPairs)[newContactPairIndex]); @@ -1195,7 +1195,7 @@ void CollisionDetectionSystem::processPotentialContacts(NarrowPhaseInfoBatch& na const ContactPointInfo& contactPoint = narrowPhaseInfoBatch.narrowPhaseInfos[i].contactPoints[j]; // Add the contact point to the list of potential contact points - const uint contactPointIndex = static_cast(potentialContactPoints.size()); + const uint32 contactPointIndex = potentialContactPoints.size(); potentialContactPoints.add(contactPoint); @@ -1232,7 +1232,7 @@ void CollisionDetectionSystem::processPotentialContacts(NarrowPhaseInfoBatch& na if (!similarManifoldFound && pairContact->nbPotentialContactManifolds < NB_MAX_POTENTIAL_CONTACT_MANIFOLDS) { // Create a new potential contact manifold for the overlapping pair - uint contactManifoldIndex = static_cast(potentialContactManifolds.size()); + uint32 contactManifoldIndex = potentialContactManifolds.size(); potentialContactManifolds.emplace(pairId); ContactManifoldInfo& contactManifoldInfo = potentialContactManifolds[contactManifoldIndex]; @@ -1265,8 +1265,8 @@ void CollisionDetectionSystem::reducePotentialContactManifolds(List RP3D_PROFILE("CollisionDetectionSystem::reducePotentialContactManifolds()", mProfiler); // Reduce the number of potential contact manifolds in a contact pair - const uint nbContactPairs = contactPairs->size(); - for (uint i=0; i < nbContactPairs; i++) { + const uint32 nbContactPairs = contactPairs->size(); + for (uint32 i=0; i < nbContactPairs; i++) { ContactPair& contactPair = (*contactPairs)[i]; @@ -1276,7 +1276,7 @@ void CollisionDetectionSystem::reducePotentialContactManifolds(List // Look for a manifold with the smallest contact penetration depth. decimal minDepth = DECIMAL_LARGEST; int minDepthManifoldIndex = -1; - for (uint j=0; j < contactPair.nbPotentialContactManifolds; j++) { + for (uint32 j=0; j < contactPair.nbPotentialContactManifolds; j++) { ContactManifoldInfo& manifold = potentialContactManifolds[contactPair.potentialContactManifoldsIndices[j]]; @@ -1296,12 +1296,12 @@ void CollisionDetectionSystem::reducePotentialContactManifolds(List } // Reduce the number of potential contact points in the manifolds - for (uint i=0; i < nbContactPairs; i++) { + for (uint32 i=0; i < nbContactPairs; i++) { const ContactPair& pairContact = (*contactPairs)[i]; // For each potential contact manifold - for (uint j=0; j < pairContact.nbPotentialContactManifolds; j++) { + for (uint32 j=0; j < pairContact.nbPotentialContactManifolds; j++) { ContactManifoldInfo& manifold = potentialContactManifolds[pairContact.potentialContactManifoldsIndices[j]]; @@ -1327,7 +1327,7 @@ decimal CollisionDetectionSystem::computePotentialManifoldLargestContactDepth(co assert(manifold.nbPotentialContactPoints > 0); - for (uint i=0; i < manifold.nbPotentialContactPoints; i++) { + for (uint32 i=0; i < manifold.nbPotentialContactPoints; i++) { decimal depth = potentialContactPoints[manifold.potentialContactPointsIndices[i]].penetrationDepth; if (depth > largestDepth) { @@ -1726,8 +1726,8 @@ void CollisionDetectionSystem::testCollision(CollisionCallback& callback) { void CollisionDetectionSystem::filterOverlappingPairs(Entity bodyEntity, List& convexPairs, List& concavePairs) const { // For each convex pairs - const uint nbConvexPairs = mOverlappingPairs.mConvexPairs.size(); - for (uint i=0; i < nbConvexPairs; i++) { + const uint32 nbConvexPairs = mOverlappingPairs.mConvexPairs.size(); + for (uint32 i=0; i < nbConvexPairs; i++) { if (mCollidersComponents.getBody(mOverlappingPairs.mConvexPairs[i].collider1) == bodyEntity || mCollidersComponents.getBody(mOverlappingPairs.mConvexPairs[i].collider2) == bodyEntity) { @@ -1737,8 +1737,8 @@ void CollisionDetectionSystem::filterOverlappingPairs(Entity bodyEntity, List& convexPairs, List& concavePairs) const { // For each convex pair - const uint nbConvexPairs = mOverlappingPairs.mConvexPairs.size(); - for (uint i=0; i < nbConvexPairs; i++) { + const uint32 nbConvexPairs = mOverlappingPairs.mConvexPairs.size(); + for (uint32 i=0; i < nbConvexPairs; i++) { const Entity collider1Body = mCollidersComponents.getBody(mOverlappingPairs.mConvexPairs[i].collider1); const Entity collider2Body = mCollidersComponents.getBody(mOverlappingPairs.mConvexPairs[i].collider2); diff --git a/src/systems/ContactSolverSystem.cpp b/src/systems/ContactSolverSystem.cpp index 1944c143..40220359 100644 --- a/src/systems/ContactSolverSystem.cpp +++ b/src/systems/ContactSolverSystem.cpp @@ -71,8 +71,8 @@ void ContactSolverSystem::init(List* contactManifolds, Listsize(); - const uint nbContactPoints = mAllContactPoints->size(); + const uint32 nbContactManifolds = mAllContactManifolds->size(); + const uint32 nbContactPoints = mAllContactPoints->size(); mNbContactManifolds = 0; mNbContactPoints = 0; diff --git a/src/utils/DefaultLogger.cpp b/src/utils/DefaultLogger.cpp index bb1a2dd2..cd3b2d35 100644 --- a/src/utils/DefaultLogger.cpp +++ b/src/utils/DefaultLogger.cpp @@ -79,7 +79,7 @@ void DefaultLogger::addStreamDestination(std::ostream& outputStream, uint logLev void DefaultLogger::removeAllDestinations() { // Delete all the destinations - for (uint i=0; igetSizeBytes(); From 70d4aed3c1c53e6da44e892ba4a1c3450602e1a1 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Sun, 30 Aug 2020 08:32:49 +0200 Subject: [PATCH 24/74] Reduce the number of calls to List::reserve() --- include/reactphysics3d/engine/Islands.h | 12 ++++++++---- .../systems/CollisionDetectionSystem.h | 3 +++ src/collision/CollisionCallback.cpp | 3 ++- .../SphereVsConvexPolyhedronAlgorithm.cpp | 2 +- src/engine/PhysicsWorld.cpp | 2 +- src/systems/CollisionDetectionSystem.cpp | 10 +++++++--- 6 files changed, 22 insertions(+), 10 deletions(-) diff --git a/include/reactphysics3d/engine/Islands.h b/include/reactphysics3d/engine/Islands.h index bfe57081..cc3d42b9 100644 --- a/include/reactphysics3d/engine/Islands.h +++ b/include/reactphysics3d/engine/Islands.h @@ -49,6 +49,9 @@ struct Islands { /// Number of islands in the previous frame uint32 mNbIslandsPreviousFrame; + /// Number of items in the bodyEntities array in the previous frame + uint32 mNbBodyEntitiesPreviousFrame; + /// Maximum number of bodies in a single island in the previous frame uint32 mNbMaxBodiesInIslandPreviousFrame; @@ -79,7 +82,7 @@ struct Islands { /// Constructor Islands(MemoryAllocator& allocator) - :mNbIslandsPreviousFrame(10), mNbMaxBodiesInIslandPreviousFrame(0), mNbMaxBodiesInIslandCurrentFrame(0), + :mNbIslandsPreviousFrame(16), mNbBodyEntitiesPreviousFrame(32), mNbMaxBodiesInIslandPreviousFrame(0), mNbMaxBodiesInIslandCurrentFrame(0), contactManifoldsIndices(allocator), nbContactManifolds(allocator), bodyEntities(allocator), startBodyEntitiesIndex(allocator), nbBodiesInIsland(allocator) { @@ -133,7 +136,7 @@ struct Islands { startBodyEntitiesIndex.reserve(mNbIslandsPreviousFrame); nbBodiesInIsland.reserve(mNbIslandsPreviousFrame); - bodyEntities.reserve(mNbMaxBodiesInIslandPreviousFrame); + bodyEntities.reserve(mNbBodyEntitiesPreviousFrame); } /// Clear all the islands @@ -145,9 +148,10 @@ struct Islands { mNbMaxBodiesInIslandCurrentFrame = nbBodiesInIsland[nbIslands-1]; } - mNbIslandsPreviousFrame = nbContactManifolds.size(); - mNbIslandsPreviousFrame = mNbMaxBodiesInIslandCurrentFrame; + mNbMaxBodiesInIslandPreviousFrame = mNbMaxBodiesInIslandCurrentFrame; + mNbIslandsPreviousFrame = nbIslands; mNbMaxBodiesInIslandCurrentFrame = 0; + mNbBodyEntitiesPreviousFrame = bodyEntities.size(); contactManifoldsIndices.clear(true); nbContactManifolds.clear(true); diff --git a/include/reactphysics3d/systems/CollisionDetectionSystem.h b/include/reactphysics3d/systems/CollisionDetectionSystem.h index 5cb28461..65c9d4b8 100644 --- a/include/reactphysics3d/systems/CollisionDetectionSystem.h +++ b/include/reactphysics3d/systems/CollisionDetectionSystem.h @@ -98,6 +98,9 @@ class CollisionDetectionSystem { /// Broad-phase overlapping pairs OverlappingPairs mOverlappingPairs; + /// Overlapping nodes during broad-phase computation + List> mBroadPhaseOverlappingNodes; + /// Broad-phase system BroadPhaseSystem mBroadPhaseSystem; diff --git a/src/collision/CollisionCallback.cpp b/src/collision/CollisionCallback.cpp index c9fbc5ff..34d40f51 100644 --- a/src/collision/CollisionCallback.cpp +++ b/src/collision/CollisionCallback.cpp @@ -79,7 +79,8 @@ CollisionCallback::ContactPair::EventType CollisionCallback::ContactPair::getEve CollisionCallback::CallbackData::CallbackData(List* contactPairs, List* manifolds, List* contactPoints, List& lostContactPairs, PhysicsWorld& world) :mContactPairs(contactPairs), mContactManifolds(manifolds), mContactPoints(contactPoints), mLostContactPairs(lostContactPairs), - mContactPairsIndices(world.mMemoryManager.getHeapAllocator()), mLostContactPairsIndices(world.mMemoryManager.getHeapAllocator()), mWorld(world) { + mContactPairsIndices(world.mMemoryManager.getHeapAllocator(), contactPairs->size()), mLostContactPairsIndices(world.mMemoryManager.getHeapAllocator(), lostContactPairs.size()), + mWorld(world) { // Filter the contact pairs to only keep the contact events (not the overlap/trigger events) for (uint32 i=0; i < mContactPairs->size(); i++) { diff --git a/src/collision/narrowphase/SphereVsConvexPolyhedronAlgorithm.cpp b/src/collision/narrowphase/SphereVsConvexPolyhedronAlgorithm.cpp index 7126b35f..f0c199d5 100644 --- a/src/collision/narrowphase/SphereVsConvexPolyhedronAlgorithm.cpp +++ b/src/collision/narrowphase/SphereVsConvexPolyhedronAlgorithm.cpp @@ -50,7 +50,7 @@ bool SphereVsConvexPolyhedronAlgorithm::testCollision(NarrowPhaseInfoBatch& narr #endif - List gjkResults(memoryAllocator); + List gjkResults(memoryAllocator, batchNbItems); gjkAlgorithm.testCollision(narrowPhaseInfoBatch, batchStartIndex, batchNbItems, gjkResults); assert(gjkResults.size() == batchNbItems); diff --git a/src/engine/PhysicsWorld.cpp b/src/engine/PhysicsWorld.cpp index 5b67261a..29a66590 100644 --- a/src/engine/PhysicsWorld.cpp +++ b/src/engine/PhysicsWorld.cpp @@ -771,7 +771,7 @@ void PhysicsWorld::createIslands() { Stack bodyEntitiesToVisit(mMemoryManager.getSingleFrameAllocator(), mIslands.getNbMaxBodiesInIslandPreviousFrame()); // List of static bodies added to the current island (used to reset the isAlreadyInIsland variable of static bodies) - List staticBodiesAddedToIsland(mMemoryManager.getSingleFrameAllocator()); + List staticBodiesAddedToIsland(mMemoryManager.getSingleFrameAllocator(), 16); uint nbTotalManifolds = 0; diff --git a/src/systems/CollisionDetectionSystem.cpp b/src/systems/CollisionDetectionSystem.cpp index 0529ddaf..acc61f30 100644 --- a/src/systems/CollisionDetectionSystem.cpp +++ b/src/systems/CollisionDetectionSystem.cpp @@ -57,6 +57,7 @@ CollisionDetectionSystem::CollisionDetectionSystem(PhysicsWorld* world, Collider mNoCollisionPairs(mMemoryManager.getPoolAllocator()), mOverlappingPairs(mMemoryManager, mCollidersComponents, collisionBodyComponents, rigidBodyComponents, mNoCollisionPairs, mCollisionDispatch), + mBroadPhaseOverlappingNodes(mMemoryManager.getHeapAllocator(), 32), mBroadPhaseSystem(*this, mCollidersComponents, transformComponents, rigidBodyComponents), mMapBroadPhaseIdToColliderEntity(memoryManager.getPoolAllocator()), mNarrowPhaseInput(mMemoryManager.getSingleFrameAllocator(), mOverlappingPairs), mPotentialContactPoints(mMemoryManager.getSingleFrameAllocator()), @@ -99,17 +100,20 @@ void CollisionDetectionSystem::computeBroadPhase() { RP3D_PROFILE("CollisionDetectionSystem::computeBroadPhase()", mProfiler); + assert(mBroadPhaseOverlappingNodes.size() == 0); + // Ask the broad-phase to compute all the shapes overlapping with the shapes that // have moved or have been added in the last frame. This call can only add new // overlapping pairs in the collision detection. - List> overlappingNodes(mMemoryManager.getHeapAllocator(), 32); - mBroadPhaseSystem.computeOverlappingPairs(mMemoryManager, overlappingNodes); + mBroadPhaseSystem.computeOverlappingPairs(mMemoryManager, mBroadPhaseOverlappingNodes); // Create new overlapping pairs if necessary - updateOverlappingPairs(overlappingNodes); + updateOverlappingPairs(mBroadPhaseOverlappingNodes); // Remove non overlapping pairs removeNonOverlappingPairs(); + + mBroadPhaseOverlappingNodes.clear(); } // Remove pairs that are not overlapping anymore From ec00ac6f7fe7555f87fba310063ae1de4390d241 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Tue, 1 Sep 2020 21:15:18 +0200 Subject: [PATCH 25/74] Add tests for the List --- test/tests/containers/TestList.h | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/tests/containers/TestList.h b/test/tests/containers/TestList.h index 10c500aa..5e20c32d 100644 --- a/test/tests/containers/TestList.h +++ b/test/tests/containers/TestList.h @@ -226,6 +226,28 @@ class TestList : public Test { list7.add("new"); rp3d_test(list7.size() == 1); rp3d_test(list7[0] == "new"); + + // ----- Test removeAtAndReplaceByLast() ----- // + + List list8(mAllocator); + list8.add(1); + list8.add(2); + list8.add(3); + list8.add(4); + list8.removeAtAndReplaceByLast(1); + rp3d_test(list8.size() == 3); + rp3d_test(list8[0] == 1); + rp3d_test(list8[1] == 4); + rp3d_test(list8[2] == 3); + list8.removeAtAndReplaceByLast(2); + rp3d_test(list8.size() == 2); + rp3d_test(list8[0] == 1); + rp3d_test(list8[1] == 4); + list8.removeAtAndReplaceByLast(0); + rp3d_test(list8.size() == 1); + rp3d_test(list8[0] == 4); + list8.removeAtAndReplaceByLast(0); + rp3d_test(list8.size() == 0); } void testAssignment() { From 98ba2f10e6f3c21b684d94e2970c89c8fd6a9499 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Wed, 2 Sep 2020 21:59:19 +0200 Subject: [PATCH 26/74] Fix issue with contact manifolds order in islands creation process --- include/reactphysics3d/engine/PhysicsWorld.h | 5 +++ .../systems/CollisionDetectionSystem.h | 6 ++-- src/engine/PhysicsWorld.cpp | 20 ++++++++++- src/systems/CollisionDetectionSystem.cpp | 33 ++++++++++++++----- 4 files changed, 52 insertions(+), 12 deletions(-) diff --git a/include/reactphysics3d/engine/PhysicsWorld.h b/include/reactphysics3d/engine/PhysicsWorld.h index 8ebf4ab1..907bf7b5 100644 --- a/include/reactphysics3d/engine/PhysicsWorld.h +++ b/include/reactphysics3d/engine/PhysicsWorld.h @@ -241,6 +241,11 @@ class PhysicsWorld { /// All the islands of bodies of the current frame Islands mIslands; + /// Order in which to process the ContactPairs for contact creation such that + /// all the contact manifolds and contact points of a given island are packed together + /// This array contains the indices of the ContactPairs. + List mProcessContactPairsOrderIslands; + /// Contact solver system ContactSolverSystem mContactSolverSystem; diff --git a/include/reactphysics3d/systems/CollisionDetectionSystem.h b/include/reactphysics3d/systems/CollisionDetectionSystem.h index b2d24f39..4909aca5 100644 --- a/include/reactphysics3d/systems/CollisionDetectionSystem.h +++ b/include/reactphysics3d/systems/CollisionDetectionSystem.h @@ -237,9 +237,6 @@ class CollisionDetectionSystem { void reducePotentialContactManifolds(List* contactPairs, List& potentialContactManifolds, const List& potentialContactPoints) const; - /// Create the actual contact manifolds and contacts points (from potential contacts) for a given contact pair - void createContacts(); - /// Compute the lost contact pairs (contact pairs in contact in the previous frame but not in the current one) void computeLostContactPairs(); @@ -279,6 +276,9 @@ class CollisionDetectionSystem { /// Filter the overlapping pairs to keep only the pairs where two given bodies are involved void filterOverlappingPairs(Entity body1Entity, Entity body2Entity, List& convexPairs, List& concavePairs) const; + /// Create the actual contact manifolds and contacts points (from potential contacts) for a given contact pair + void createContacts(); + public : // -------------------- Methods -------------------- // diff --git a/src/engine/PhysicsWorld.cpp b/src/engine/PhysicsWorld.cpp index 19e55eab..fc59195d 100644 --- a/src/engine/PhysicsWorld.cpp +++ b/src/engine/PhysicsWorld.cpp @@ -59,7 +59,7 @@ PhysicsWorld::PhysicsWorld(MemoryManager& memoryManager, const WorldSettings& wo mSliderJointsComponents(mMemoryManager.getHeapAllocator()), mCollisionDetection(this, mCollidersComponents, mTransformComponents, mCollisionBodyComponents, mRigidBodyComponents, mMemoryManager), mCollisionBodies(mMemoryManager.getHeapAllocator()), mEventListener(nullptr), - mName(worldSettings.worldName), mIslands(mMemoryManager.getSingleFrameAllocator()), + mName(worldSettings.worldName), mIslands(mMemoryManager.getSingleFrameAllocator()), mProcessContactPairsOrderIslands(mMemoryManager.getSingleFrameAllocator()), mContactSolverSystem(mMemoryManager, *this, mIslands, mCollisionBodyComponents, mRigidBodyComponents, mCollidersComponents, mConfig.restitutionVelocityThreshold), mConstraintSolverSystem(*this, mIslands, mRigidBodyComponents, mTransformComponents, mJointsComponents, @@ -342,6 +342,9 @@ void PhysicsWorld::update(decimal timeStep) { // Create the islands createIslands(); + // Create the actual narrow-phase contacts + mCollisionDetection.createContacts(); + // Report the contacts to the user mCollisionDetection.reportContactsAndTriggers(); @@ -374,6 +377,8 @@ void PhysicsWorld::update(decimal timeStep) { // Reset the islands mIslands.clear(); + mProcessContactPairsOrderIslands.clear(true); + // Generate debug rendering primitives (if enabled) if (mIsDebugRenderingEnabled) { mDebugRenderer.computeDebugRenderingPrimitives(*this); @@ -747,6 +752,8 @@ void PhysicsWorld::createIslands() { RP3D_PROFILE("PhysicsWorld::createIslands()", mProfiler); + assert(mProcessContactPairsOrderIslands.size() == 0); + // Reset all the isAlreadyInIsland variables of bodies and joints for (uint b=0; b < mRigidBodyComponents.getNbComponents(); b++) { @@ -814,6 +821,8 @@ void PhysicsWorld::createIslands() { if (mRigidBodyComponents.hasComponent(pair.body1Entity) && mRigidBodyComponents.hasComponent(pair.body2Entity) && !mCollidersComponents.getIsTrigger(pair.collider1Entity) && !mCollidersComponents.getIsTrigger(pair.collider2Entity)) { + mProcessContactPairsOrderIslands.add(contactPairs[p]); + assert(pair.potentialContactManifoldsIndices.size() > 0); nbTotalManifolds += pair.potentialContactManifoldsIndices.size(); @@ -871,6 +880,15 @@ void PhysicsWorld::createIslands() { } } + for (uint32 i=0; i < (*mCollisionDetection.mCurrentContactPairs).size(); i++) { + + ContactPair& contactPair = (*mCollisionDetection.mCurrentContactPairs)[i]; + + if (mRigidBodyComponents.hasComponent(contactPair.body1Entity) && mRigidBodyComponents.hasComponent(contactPair.body2Entity)) { + + } + } + mCollisionDetection.mMapBodyToContactPairs.clear(true); } diff --git a/src/systems/CollisionDetectionSystem.cpp b/src/systems/CollisionDetectionSystem.cpp index 24f9660c..b794b32d 100644 --- a/src/systems/CollisionDetectionSystem.cpp +++ b/src/systems/CollisionDetectionSystem.cpp @@ -532,11 +532,6 @@ void CollisionDetectionSystem::computeNarrowPhase() { assert(mCurrentContactManifolds->size() == 0); assert(mCurrentContactPoints->size() == 0); - - // Create the actual narrow-phase contacts - createContacts(); - - mNarrowPhaseInput.clear(); } // Compute the narrow-phase collision detection for the testOverlap() methods. @@ -716,10 +711,30 @@ void CollisionDetectionSystem::createContacts() { mCurrentContactManifolds->reserve(mCurrentContactPairs->size()); mCurrentContactPoints->reserve(mCurrentContactManifolds->size()); - // For each contact pair - for (uint p=0; p < (*mCurrentContactPairs).size(); p++) { + // We go through all the contact pairs and add the pairs with a least a CollisionBody at the end of the + // mProcessContactPairsOrderIslands array because those pairs have not been added during the islands + // creation (only the pairs with two RigidBodies are added during island creation) + Set processedContactPairsIndices(mMemoryManager.getSingleFrameAllocator(), mCurrentContactPairs->size()); + for (uint32 i=0; i < mWorld->mProcessContactPairsOrderIslands.size(); i++) { + processedContactPairsIndices.add(mWorld->mProcessContactPairsOrderIslands[i]); + } + for (uint32 i=0; i < mCurrentContactPairs->size(); i++) { + if (!processedContactPairsIndices.contains(i)) { + mWorld->mProcessContactPairsOrderIslands.add(i); + } + } - ContactPair& contactPair = (*mCurrentContactPairs)[p]; + assert(mWorld->mProcessContactPairsOrderIslands.size() == (*mCurrentContactPairs).size()); + + // Process the contact pairs in the order defined by the islands such that the contact manifolds and + // contact points of a given island are packed together in the array of manifolds and contact points + for (uint p=0; p < mWorld->mProcessContactPairsOrderIslands.size(); p++) { + + uint32 contactPairIndex = mWorld->mProcessContactPairsOrderIslands[p]; + + processedContactPairsIndices.add(contactPairIndex); + + ContactPair& contactPair = (*mCurrentContactPairs)[contactPairIndex]; contactPair.contactManifoldsIndex = mCurrentContactManifolds->size(); contactPair.nbContactManifolds = contactPair.potentialContactManifoldsIndices.size(); @@ -772,6 +787,8 @@ void CollisionDetectionSystem::createContacts() { // Reset the potential contacts mPotentialContactPoints.clear(true); mPotentialContactManifolds.clear(true); + + mNarrowPhaseInput.clear(); } // Compute the lost contact pairs (contact pairs in contact in the previous frame but not in the current one) From 18924135e093dfcdf02d3861c8bbf03659c089a2 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Wed, 2 Sep 2020 23:00:15 +0200 Subject: [PATCH 27/74] Remove some code --- src/engine/PhysicsWorld.cpp | 9 --------- src/systems/CollisionDetectionSystem.cpp | 2 -- 2 files changed, 11 deletions(-) diff --git a/src/engine/PhysicsWorld.cpp b/src/engine/PhysicsWorld.cpp index fc59195d..ec9645ad 100644 --- a/src/engine/PhysicsWorld.cpp +++ b/src/engine/PhysicsWorld.cpp @@ -880,15 +880,6 @@ void PhysicsWorld::createIslands() { } } - for (uint32 i=0; i < (*mCollisionDetection.mCurrentContactPairs).size(); i++) { - - ContactPair& contactPair = (*mCollisionDetection.mCurrentContactPairs)[i]; - - if (mRigidBodyComponents.hasComponent(contactPair.body1Entity) && mRigidBodyComponents.hasComponent(contactPair.body2Entity)) { - - } - } - mCollisionDetection.mMapBodyToContactPairs.clear(true); } diff --git a/src/systems/CollisionDetectionSystem.cpp b/src/systems/CollisionDetectionSystem.cpp index b794b32d..924d1457 100644 --- a/src/systems/CollisionDetectionSystem.cpp +++ b/src/systems/CollisionDetectionSystem.cpp @@ -732,8 +732,6 @@ void CollisionDetectionSystem::createContacts() { uint32 contactPairIndex = mWorld->mProcessContactPairsOrderIslands[p]; - processedContactPairsIndices.add(contactPairIndex); - ContactPair& contactPair = (*mCurrentContactPairs)[contactPairIndex]; contactPair.contactManifoldsIndex = mCurrentContactManifolds->size(); From f17941b708e3b43d1548a8c7f6d9d15a409830cb Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Wed, 2 Sep 2020 23:33:12 +0200 Subject: [PATCH 28/74] Optimization --- .../systems/CollisionDetectionSystem.h | 3 ++ src/systems/CollisionDetectionSystem.cpp | 28 +++++++++++-------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/include/reactphysics3d/systems/CollisionDetectionSystem.h b/include/reactphysics3d/systems/CollisionDetectionSystem.h index 93fd9dc3..89ed2f32 100644 --- a/include/reactphysics3d/systems/CollisionDetectionSystem.h +++ b/include/reactphysics3d/systems/CollisionDetectionSystem.h @@ -159,6 +159,9 @@ class CollisionDetectionSystem { /// Pointer to the contact points of the current frame (either mContactPoints1 or mContactPoints2) List* mCurrentContactPoints; + /// Array with the indices of all the contact pairs that have at least one CollisionBody + List mCollisionBodyContactPairsIndices; + #ifdef IS_RP3D_PROFILING_ENABLED /// Pointer to the profiler diff --git a/src/systems/CollisionDetectionSystem.cpp b/src/systems/CollisionDetectionSystem.cpp index 0e4146f2..756acda1 100644 --- a/src/systems/CollisionDetectionSystem.cpp +++ b/src/systems/CollisionDetectionSystem.cpp @@ -67,7 +67,7 @@ CollisionDetectionSystem::CollisionDetectionSystem(PhysicsWorld* world, Collider mContactManifolds1(mMemoryManager.getPoolAllocator()), mContactManifolds2(mMemoryManager.getPoolAllocator()), mPreviousContactManifolds(&mContactManifolds1), mCurrentContactManifolds(&mContactManifolds2), mContactPoints1(mMemoryManager.getPoolAllocator()), mContactPoints2(mMemoryManager.getPoolAllocator()), - mPreviousContactPoints(&mContactPoints1), mCurrentContactPoints(&mContactPoints2) { + mPreviousContactPoints(&mContactPoints1), mCurrentContactPoints(&mContactPoints2), mCollisionBodyContactPairsIndices(mMemoryManager.getSingleFrameAllocator()) { #ifdef IS_RP3D_PROFILING_ENABLED @@ -623,13 +623,23 @@ void CollisionDetectionSystem::addContactPairsToBodies() { ContactPair& contactPair = (*mCurrentContactPairs)[p]; + const bool isBody1Rigid = mRigidBodyComponents.hasComponent(contactPair.body1Entity); + const bool isBody2Rigid = mRigidBodyComponents.hasComponent(contactPair.body2Entity); + // Add the associated contact pair to both bodies of the pair (used to create islands later) - if (mRigidBodyComponents.hasComponent(contactPair.body1Entity)) { + if (isBody1Rigid) { mRigidBodyComponents.addContacPair(contactPair.body1Entity, p); } - if (mRigidBodyComponents.hasComponent(contactPair.body2Entity)) { + if (isBody2Rigid) { mRigidBodyComponents.addContacPair(contactPair.body2Entity, p); } + + // If at least of body is a CollisionBody + if (!isBody1Rigid || !isBody2Rigid) { + + // Add the pair index to the array of pairs with CollisionBody + mCollisionBodyContactPairsIndices.add(p); + } } } @@ -814,15 +824,7 @@ void CollisionDetectionSystem::createContacts() { // We go through all the contact pairs and add the pairs with a least a CollisionBody at the end of the // mProcessContactPairsOrderIslands array because those pairs have not been added during the islands // creation (only the pairs with two RigidBodies are added during island creation) - Set processedContactPairsIndices(mMemoryManager.getSingleFrameAllocator(), mCurrentContactPairs->size()); - for (uint32 i=0; i < mWorld->mProcessContactPairsOrderIslands.size(); i++) { - processedContactPairsIndices.add(mWorld->mProcessContactPairsOrderIslands[i]); - } - for (uint32 i=0; i < mCurrentContactPairs->size(); i++) { - if (!processedContactPairsIndices.contains(i)) { - mWorld->mProcessContactPairsOrderIslands.add(i); - } - } + mWorld->mProcessContactPairsOrderIslands.addRange(mCollisionBodyContactPairsIndices); assert(mWorld->mProcessContactPairsOrderIslands.size() == (*mCurrentContactPairs).size()); @@ -883,6 +885,8 @@ void CollisionDetectionSystem::createContacts() { // Compute the map from contact pairs ids to contact pair for the next frame computeMapPreviousContactPairs(); + mCollisionBodyContactPairsIndices.clear(true); + mNarrowPhaseInput.clear(); } From 068f65d9722b35131215d24afb06089096f73074 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Fri, 4 Sep 2020 17:50:57 +0200 Subject: [PATCH 29/74] Edit changelog file --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index add9c854..89988769 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ do not hesitate to take a look at the user manual. ### Fixed +- Issue [#165](https://github.com/DanielChappuis/reactphysics3d/issues/165) with order of contact manifolds in islands creation has been fixed - Issue with concave vs convex shape collision detection has been fixed - Issue with edge vs edge collision has been fixed in SAT algorithm (wrong contact normal was computed) From 089c9ea2db1ccdc37a7a02efc030b105e29aafeb Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Fri, 4 Sep 2020 17:51:37 +0200 Subject: [PATCH 30/74] Refactor the List::removeAtAndReplaceByLast() method --- include/reactphysics3d/containers/List.h | 16 ++++------------ include/reactphysics3d/engine/OverlappingPairs.h | 15 +++++---------- 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/include/reactphysics3d/containers/List.h b/include/reactphysics3d/containers/List.h index d0c5a731..1bfb10d7 100755 --- a/include/reactphysics3d/containers/List.h +++ b/include/reactphysics3d/containers/List.h @@ -376,20 +376,12 @@ class List { /// Remove an element from the list at a given index and replace it by the last one of the list (if any) void removeAtAndReplaceByLast(uint32 index) { - assert(index >= 0 && index < mSize); + assert(index < mSize); - // Call the destructor - (static_cast(mBuffer)[index]).~T(); + static_cast(mBuffer)[index] = static_cast(mBuffer)[mSize - 1]; - // If there is another element in the list - if (mSize > 1 && index < (mSize - 1)) { - - // Copy the last element of the list at the location of the removed element - new (static_cast(mBuffer) + index * sizeof(T)) T(static_cast(mBuffer)[mSize - 1]); - - // Call the destructor of the last element - (static_cast(mBuffer)[mSize - 1]).~T(); - } + // Call the destructor of the last element + (static_cast(mBuffer)[mSize - 1]).~T(); mSize--; } diff --git a/include/reactphysics3d/engine/OverlappingPairs.h b/include/reactphysics3d/engine/OverlappingPairs.h index e00dcd54..cdde8865 100644 --- a/include/reactphysics3d/engine/OverlappingPairs.h +++ b/include/reactphysics3d/engine/OverlappingPairs.h @@ -174,7 +174,7 @@ class OverlappingPairs { private: - MemoryAllocator& mPoolAllocator; + MemoryAllocator* mPoolAllocator; public: @@ -192,7 +192,7 @@ class OverlappingPairs { ConcaveOverlappingPair(uint64 pairId, int32 broadPhaseId1, int32 broadPhaseId2, Entity collider1, Entity collider2, NarrowPhaseAlgorithmType narrowPhaseAlgorithmType, bool isShape1Convex, MemoryAllocator& poolAllocator, MemoryAllocator& heapAllocator) - : OverlappingPair(pairId, broadPhaseId1, broadPhaseId2, collider1, collider2, narrowPhaseAlgorithmType), mPoolAllocator(poolAllocator), + : OverlappingPair(pairId, broadPhaseId1, broadPhaseId2, collider1, collider2, narrowPhaseAlgorithmType), mPoolAllocator(&poolAllocator), isShape1Convex(isShape1Convex), lastFrameCollisionInfos(heapAllocator, 16) { } @@ -206,7 +206,7 @@ class OverlappingPairs { it->second->LastFrameCollisionInfo::~LastFrameCollisionInfo(); // Release memory - mPoolAllocator.release(it->second, sizeof(LastFrameCollisionInfo)); + mPoolAllocator->release(it->second, sizeof(LastFrameCollisionInfo)); } lastFrameCollisionInfos.clear(); @@ -229,7 +229,7 @@ class OverlappingPairs { auto it = lastFrameCollisionInfos.find(shapesId); if (it == lastFrameCollisionInfos.end()) { - LastFrameCollisionInfo* lastFrameInfo = new (mPoolAllocator.allocate(sizeof(LastFrameCollisionInfo))) LastFrameCollisionInfo(); + LastFrameCollisionInfo* lastFrameInfo = new (mPoolAllocator->allocate(sizeof(LastFrameCollisionInfo))) LastFrameCollisionInfo(); // Add it into the map of collision infos lastFrameCollisionInfos.add(Pair(shapesId, lastFrameInfo)); @@ -258,7 +258,7 @@ class OverlappingPairs { it->second->LastFrameCollisionInfo::~LastFrameCollisionInfo(); // Release memory - mPoolAllocator.release(it->second, sizeof(LastFrameCollisionInfo)); + mPoolAllocator->release(it->second, sizeof(LastFrameCollisionInfo)); it = lastFrameCollisionInfos.remove(it); } @@ -271,11 +271,6 @@ class OverlappingPairs { } } } - - /// Destructor - virtual ~ConcaveOverlappingPair() { - - } }; private: From 0d203d949083e041a7095b16cd4bbadf82dabdca Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Sat, 5 Sep 2020 11:58:30 +0200 Subject: [PATCH 31/74] Modifications in List class --- include/reactphysics3d/containers/List.h | 40 +++++++++++------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/include/reactphysics3d/containers/List.h b/include/reactphysics3d/containers/List.h index 1bfb10d7..651a2961 100755 --- a/include/reactphysics3d/containers/List.h +++ b/include/reactphysics3d/containers/List.h @@ -48,7 +48,7 @@ class List { // -------------------- Attributes -------------------- // /// Buffer for the list elements - void* mBuffer; + T* mBuffer; /// Number of elements in the list uint32 mSize; @@ -259,19 +259,18 @@ class List { // Allocate memory for the new array void* newMemory = mAllocator.allocate(capacity * sizeof(T)); + T* destination = static_cast(newMemory); if (mBuffer != nullptr) { if (mSize > 0) { // Copy the elements to the new allocated memory location - T* destination = static_cast(newMemory); - T* items = static_cast(mBuffer); - std::uninitialized_copy(items, items + mSize, destination); + std::uninitialized_copy(mBuffer, mBuffer + mSize, destination); // Destruct the previous items for (uint32 i=0; i(mBuffer) + mSize * sizeof(T)) T(element); + new (reinterpret_cast(mBuffer + mSize)) T(element); mSize++; } @@ -309,7 +308,7 @@ class List { } // Construct the element directly at its location in the array - new (static_cast(mBuffer) + mSize * sizeof(T)) T(std::forward(args)...); + new (reinterpret_cast(mBuffer + mSize)) T(std::forward(args)...); mSize++; } @@ -331,7 +330,7 @@ class List { Iterator find(const T& element) { for (uint32 i=0; i(mBuffer)[i]) { + if (element == mBuffer[i]) { return Iterator(mBuffer, i, mSize); } } @@ -354,18 +353,18 @@ class List { /// Remove an element from the list at a given index (all the following items will be moved) Iterator removeAt(uint32 index) { - assert(index >= 0 && index < mSize); + assert(index < mSize); // Call the destructor - (static_cast(mBuffer)[index]).~T(); + mBuffer[index].~T(); mSize--; if (index != mSize) { // Move the elements to fill in the empty slot - char* dest = static_cast(mBuffer) + index * sizeof(T); - char* src = dest + sizeof(T); + void* dest = reinterpret_cast(mBuffer + index); + void* src = dest + sizeof(T); std::memmove(static_cast(dest), static_cast(src), (mSize - index) * sizeof(T)); } @@ -378,10 +377,10 @@ class List { assert(index < mSize); - static_cast(mBuffer)[index] = static_cast(mBuffer)[mSize - 1]; + mBuffer[index] = mBuffer[mSize - 1]; // Call the destructor of the last element - (static_cast(mBuffer)[mSize - 1]).~T(); + mBuffer[mSize - 1].~T(); mSize--; } @@ -399,7 +398,7 @@ class List { // Add the elements of the list to the current one for(uint32 i=0; i(mBuffer) + mSize * sizeof(T)) T(list[i]); + new (reinterpret_cast(mBuffer + mSize)) T(list[i]); mSize++; } } @@ -409,7 +408,7 @@ class List { // Call the destructor of each element for (uint32 i=0; i < mSize; i++) { - (static_cast(mBuffer)[i]).~T(); + mBuffer[i].~T(); } mSize = 0; @@ -438,13 +437,13 @@ class List { /// Overloaded index operator T& operator[](const uint32 index) { assert(index >= 0 && index < mSize); - return (static_cast(mBuffer)[index]); + return mBuffer[index]; } /// Overloaded const index operator const T& operator[](const uint32 index) const { assert(index >= 0 && index < mSize); - return (static_cast(mBuffer)[index]); + return mBuffer[index]; } /// Overloaded equality operator @@ -452,9 +451,8 @@ class List { if (mSize != list.mSize) return false; - T* items = static_cast(mBuffer); for (uint32 i=0; i < mSize; i++) { - if (items[i] != list[i]) { + if (mBuffer[i] != list[i]) { return false; } } From cd4bc7573fca64c26675567a771e02559274d796 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Sat, 5 Sep 2020 15:06:51 +0200 Subject: [PATCH 32/74] Rename the List class into Array --- CHANGELOG.md | 3 +- .../collision/CollisionCallback.h | 34 +- .../collision/ContactManifold.h | 2 +- .../collision/HalfEdgeStructure.h | 16 +- .../collision/OverlapCallback.h | 20 +- .../reactphysics3d/collision/TriangleMesh.h | 4 +- .../collision/broadphase/DynamicAABBTree.h | 10 +- .../collision/narrowphase/GJK/GJKAlgorithm.h | 4 +- .../narrowphase/NarrowPhaseInfoBatch.h | 4 +- .../collision/narrowphase/NarrowPhaseInput.h | 2 +- .../collision/shapes/CollisionShape.h | 6 +- .../collision/shapes/ConcaveMeshShape.h | 10 +- .../collision/shapes/ConcaveShape.h | 4 +- .../collision/shapes/HeightFieldShape.h | 4 +- .../components/ColliderComponents.h | 12 +- .../components/CollisionBodyComponents.h | 12 +- .../components/RigidBodyComponents.h | 16 +- .../containers/{List.h => Array.h} | 95 ++-- include/reactphysics3d/containers/Set.h | 10 +- include/reactphysics3d/engine/EntityManager.h | 6 +- include/reactphysics3d/engine/Islands.h | 14 +- .../reactphysics3d/engine/OverlappingPairs.h | 8 +- include/reactphysics3d/engine/PhysicsWorld.h | 10 +- .../mathematics/mathematics_functions.h | 12 +- include/reactphysics3d/reactphysics3d.h | 2 +- .../reactphysics3d/systems/BroadPhaseSystem.h | 6 +- .../systems/CollisionDetectionSystem.h | 112 ++--- .../systems/ContactSolverSystem.h | 10 +- include/reactphysics3d/utils/DebugRenderer.h | 32 +- include/reactphysics3d/utils/DefaultLogger.h | 4 +- include/reactphysics3d/utils/Logger.h | 2 +- include/reactphysics3d/utils/Profiler.h | 2 +- src/body/CollisionBody.cpp | 18 +- src/body/RigidBody.cpp | 12 +- src/collision/CollisionCallback.cpp | 6 +- src/collision/HalfEdgeStructure.cpp | 2 +- src/collision/OverlapCallback.cpp | 2 +- src/collision/PolyhedronMesh.cpp | 2 +- src/collision/broadphase/DynamicAABBTree.cpp | 10 +- .../CapsuleVsConvexPolyhedronAlgorithm.cpp | 2 +- .../narrowphase/GJK/GJKAlgorithm.cpp | 4 +- .../narrowphase/SAT/SATAlgorithm.cpp | 14 +- .../SphereVsConvexPolyhedronAlgorithm.cpp | 2 +- src/collision/shapes/BoxShape.cpp | 12 +- src/collision/shapes/ConcaveMeshShape.cpp | 10 +- src/collision/shapes/ConvexMeshShape.cpp | 2 +- src/collision/shapes/HeightFieldShape.cpp | 10 +- src/components/ColliderComponents.cpp | 16 +- src/components/CollisionBodyComponents.cpp | 16 +- src/components/RigidBodyComponents.cpp | 30 +- src/engine/PhysicsWorld.cpp | 26 +- src/mathematics/mathematics_functions.cpp | 18 +- src/systems/BroadPhaseSystem.cpp | 6 +- src/systems/CollisionDetectionSystem.cpp | 110 ++--- src/systems/ContactSolverSystem.cpp | 2 +- test/CMakeLists.txt | 2 +- test/main.cpp | 4 +- test/tests/collision/TestDynamicAABBTree.h | 4 +- test/tests/collision/TestHalfEdgeStructure.h | 20 +- test/tests/containers/TestArray.h | 449 ++++++++++++++++++ test/tests/containers/TestList.h | 449 ------------------ test/tests/containers/TestSet.h | 20 +- .../mathematics/TestMathematicsFunctions.h | 16 +- 63 files changed, 893 insertions(+), 891 deletions(-) rename include/reactphysics3d/containers/{List.h => Array.h} (84%) create mode 100644 test/tests/containers/TestArray.h delete mode 100644 test/tests/containers/TestList.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 89988769..fcb86105 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,8 @@ do not hesitate to take a look at the user manual. ### Changed - - Rolling resistance constraint is not solved anymore in the solver. Angular damping needs to be used instead to simulate this. + - Rolling resistance constraint is not solved anymore in the solver. Angular damping needs to be used instead to simulate this + - The List class has been renamed to Array ### Removed diff --git a/include/reactphysics3d/collision/CollisionCallback.h b/include/reactphysics3d/collision/CollisionCallback.h index e1210fad..a398a3c9 100644 --- a/include/reactphysics3d/collision/CollisionCallback.h +++ b/include/reactphysics3d/collision/CollisionCallback.h @@ -27,7 +27,7 @@ #define REACTPHYSICS3D_COLLISION_CALLBACK_H // Libraries -#include +#include #include #include @@ -114,7 +114,7 @@ class CollisionCallback { // Class ContactPair /** * This class represents the contact between two colliders of the physics world. - * A contact pair contains a list of contact points. + * A contact pair contains an array of contact points. */ class ContactPair { @@ -141,7 +141,7 @@ class CollisionCallback { const reactphysics3d::ContactPair& mContactPair; /// Pointer to the contact points - List* mContactPoints; + Array* mContactPoints; /// Reference to the physics world PhysicsWorld& mWorld; @@ -152,7 +152,7 @@ class CollisionCallback { // -------------------- Methods -------------------- // /// Constructor - ContactPair(const reactphysics3d::ContactPair& contactPair, List* contactPoints, + ContactPair(const reactphysics3d::ContactPair& contactPair, Array* contactPoints, PhysicsWorld& world, bool mIsLostContactPair); public: @@ -226,23 +226,23 @@ class CollisionCallback { // -------------------- Attributes -------------------- // - /// Pointer to the list of contact pairs (contains contacts and triggers events) - List* mContactPairs; + /// Pointer to the array of contact pairs (contains contacts and triggers events) + Array* mContactPairs; - /// Pointer to the list of contact manifolds - List* mContactManifolds; + /// Pointer to the array of contact manifolds + Array* mContactManifolds; /// Pointer to the contact points - List* mContactPoints; + Array* mContactPoints; - /// Pointer to the list of lost contact pairs (contains contacts and triggers events) - List& mLostContactPairs; + /// Pointer to the array of lost contact pairs (contains contacts and triggers events) + Array& mLostContactPairs; - /// List of indices of the mContactPairs list that are contact events (not overlap/triggers) - List mContactPairsIndices; + /// Array of indices in the mContactPairs array that are contact events (not overlap/triggers) + Array mContactPairsIndices; - /// List of indices of the mLostContactPairs list that are contact events (not overlap/triggers) - List mLostContactPairsIndices; + /// Array of indices in the mLostContactPairs array that are contact events (not overlap/triggers) + Array mLostContactPairsIndices; /// Reference to the physics world PhysicsWorld& mWorld; @@ -250,8 +250,8 @@ class CollisionCallback { // -------------------- Methods -------------------- // /// Constructor - CallbackData(List* contactPairs, List* manifolds, - List* contactPoints, List& lostContactPairs, + CallbackData(Array* contactPairs, Array* manifolds, + Array* contactPoints, Array& lostContactPairs, PhysicsWorld& world); /// Deleted copy constructor diff --git a/include/reactphysics3d/collision/ContactManifold.h b/include/reactphysics3d/collision/ContactManifold.h index b76906da..738e1565 100644 --- a/include/reactphysics3d/collision/ContactManifold.h +++ b/include/reactphysics3d/collision/ContactManifold.h @@ -62,7 +62,7 @@ class ContactManifold { // -------------------- Attributes -------------------- // - /// Index of the first contact point of the manifold in the list of contact points + /// Index of the first contact point of the manifold in the array of contact points uint contactPointsIndex; /// Entity of the first body in contact diff --git a/include/reactphysics3d/collision/HalfEdgeStructure.h b/include/reactphysics3d/collision/HalfEdgeStructure.h index a8d5ac2e..8b9f8e4a 100644 --- a/include/reactphysics3d/collision/HalfEdgeStructure.h +++ b/include/reactphysics3d/collision/HalfEdgeStructure.h @@ -54,13 +54,13 @@ class HalfEdgeStructure { /// Face struct Face { uint edgeIndex; // Index of an half-edge of the face - List faceVertices; // Index of the vertices of the face + Array faceVertices; // Index of the vertices of the face /// Constructor Face(MemoryAllocator& allocator) : faceVertices(allocator) {} /// Constructor - Face(List vertices) : faceVertices(vertices) {} + Face(Array vertices) : faceVertices(vertices) {} }; /// Vertex @@ -78,13 +78,13 @@ class HalfEdgeStructure { MemoryAllocator& mAllocator; /// All the faces - List mFaces; + Array mFaces; /// All the vertices - List mVertices; + Array mVertices; /// All the half-edges - List mEdges; + Array mEdges; public: @@ -103,7 +103,7 @@ class HalfEdgeStructure { uint addVertex(uint vertexPointIndex); /// Add a face - void addFace(List faceVertices); + void addFace(Array faceVertices); /// Return the number of faces uint getNbFaces() const; @@ -137,10 +137,10 @@ RP3D_FORCE_INLINE uint32 HalfEdgeStructure::addVertex(uint32 vertexPointIndex) { // Add a face /** - * @param faceVertices List of the vertices in a face (ordered in CCW order as seen from outside + * @param faceVertices Array of the vertices in a face (ordered in CCW order as seen from outside * the polyhedron */ -RP3D_FORCE_INLINE void HalfEdgeStructure::addFace(List faceVertices) { +RP3D_FORCE_INLINE void HalfEdgeStructure::addFace(Array faceVertices) { // Create a new face Face face(faceVertices); diff --git a/include/reactphysics3d/collision/OverlapCallback.h b/include/reactphysics3d/collision/OverlapCallback.h index e0126132..aa0b55de 100644 --- a/include/reactphysics3d/collision/OverlapCallback.h +++ b/include/reactphysics3d/collision/OverlapCallback.h @@ -27,7 +27,7 @@ #define REACTPHYSICS3D_OVERLAP_CALLBACK_H // Libraries -#include +#include #include /// ReactPhysics3D namespace @@ -131,17 +131,17 @@ class OverlapCallback { // -------------------- Attributes -------------------- // - /// Reference to the list of contact pairs (contains contacts and triggers events) - List& mContactPairs; + /// Reference to the array of contact pairs (contains contacts and triggers events) + Array& mContactPairs; - /// Reference to the list of lost contact pairs (contains contacts and triggers events) - List& mLostContactPairs; + /// Reference to the array of lost contact pairs (contains contacts and triggers events) + Array& mLostContactPairs; - /// List of indices of the mContactPairs list that are overlap/triggers events (not contact events) - List mContactPairsIndices; + /// Array of indices of the mContactPairs array that are overlap/triggers events (not contact events) + Array mContactPairsIndices; - /// List of indices of the mLostContactPairs list that are overlap/triggers events (not contact events) - List mLostContactPairsIndices; + /// Array of indices of the mLostContactPairs array that are overlap/triggers events (not contact events) + Array mLostContactPairsIndices; /// Reference to the physics world PhysicsWorld& mWorld; @@ -149,7 +149,7 @@ class OverlapCallback { // -------------------- Methods -------------------- // /// Constructor - CallbackData(List& contactPairs, List& lostContactPairs, bool onlyReportTriggers, PhysicsWorld& world); + CallbackData(Array& contactPairs, Array& lostContactPairs, bool onlyReportTriggers, PhysicsWorld& world); /// Deleted copy constructor CallbackData(const CallbackData& callbackData) = delete; diff --git a/include/reactphysics3d/collision/TriangleMesh.h b/include/reactphysics3d/collision/TriangleMesh.h index 87163407..2088a312 100644 --- a/include/reactphysics3d/collision/TriangleMesh.h +++ b/include/reactphysics3d/collision/TriangleMesh.h @@ -28,7 +28,7 @@ // Libraries #include -#include +#include #include namespace reactphysics3d { @@ -49,7 +49,7 @@ class TriangleMesh { protected: /// All the triangle arrays of the mesh (one triangle array per part) - List mTriangleArrays; + Array mTriangleArrays; /// Constructor TriangleMesh(reactphysics3d::MemoryAllocator& allocator); diff --git a/include/reactphysics3d/collision/broadphase/DynamicAABBTree.h b/include/reactphysics3d/collision/broadphase/DynamicAABBTree.h index 1ff0aa67..a354de7c 100644 --- a/include/reactphysics3d/collision/broadphase/DynamicAABBTree.h +++ b/include/reactphysics3d/collision/broadphase/DynamicAABBTree.h @@ -58,7 +58,7 @@ struct TreeNode { // -------------------- Attributes -------------------- // - // A node is either in the tree (has a parent) or in the free nodes list + // A node is either in the tree (has a parent) or in the free nodes array // (has a next node) union { @@ -149,7 +149,7 @@ class DynamicAABBTree { /// ID of the root node of the tree int32 mRootNodeID; - /// ID of the first node of the list of free (allocated) nodes in the tree that we can use + /// ID of the first node of the array of free (allocated) nodes in the tree that we can use int32 mFreeNodeID; /// Number of allocated nodes in the tree @@ -236,11 +236,11 @@ class DynamicAABBTree { void* getNodeDataPointer(int32 nodeID) const; /// Report all shapes overlapping with all the shapes in the map in parameter - void reportAllShapesOverlappingWithShapes(const List& nodesToTest, size_t startIndex, - size_t endIndex, List>& outOverlappingNodes) const; + void reportAllShapesOverlappingWithShapes(const Array& nodesToTest, size_t startIndex, + size_t endIndex, Array>& outOverlappingNodes) const; /// Report all shapes overlapping with the AABB given in parameter. - void reportAllShapesOverlappingWithAABB(const AABB& aabb, List& overlappingNodes) const; + void reportAllShapesOverlappingWithAABB(const AABB& aabb, Array& overlappingNodes) const; /// Ray casting method void raycast(const Ray& ray, DynamicAABBTreeRaycastCallback& callback) const; diff --git a/include/reactphysics3d/collision/narrowphase/GJK/GJKAlgorithm.h b/include/reactphysics3d/collision/narrowphase/GJK/GJKAlgorithm.h index 2aba4e73..2383be1f 100644 --- a/include/reactphysics3d/collision/narrowphase/GJK/GJKAlgorithm.h +++ b/include/reactphysics3d/collision/narrowphase/GJK/GJKAlgorithm.h @@ -39,7 +39,7 @@ struct NarrowPhaseInfoBatch; class ConvexShape; class Profiler; class VoronoiSimplex; -template class List; +template class Array; // Constants constexpr decimal REL_ERROR = decimal(1.0e-3); @@ -98,7 +98,7 @@ class GJKAlgorithm { /// Compute a contact info if the two bounding volumes collide. void testCollision(NarrowPhaseInfoBatch& narrowPhaseInfoBatch, uint batchStartIndex, - uint batchNbItems, List& gjkResults); + uint batchNbItems, Array& gjkResults); #ifdef IS_RP3D_PROFILING_ENABLED diff --git a/include/reactphysics3d/collision/narrowphase/NarrowPhaseInfoBatch.h b/include/reactphysics3d/collision/narrowphase/NarrowPhaseInfoBatch.h index f4f94d38..fd296536 100644 --- a/include/reactphysics3d/collision/narrowphase/NarrowPhaseInfoBatch.h +++ b/include/reactphysics3d/collision/narrowphase/NarrowPhaseInfoBatch.h @@ -85,7 +85,7 @@ struct NarrowPhaseInfoBatch { /// Number of contact points uint8 nbContactPoints; - /// List of contact points created during the narrow-phase + /// Array of contact points created during the narrow-phase ContactPointInfo contactPoints[NB_MAX_CONTACT_POINTS_IN_NARROWPHASE_INFO]; /// Constructor @@ -114,7 +114,7 @@ struct NarrowPhaseInfoBatch { public: /// For each collision test, we keep some meta data - List narrowPhaseInfos; + Array narrowPhaseInfos; /// Constructor NarrowPhaseInfoBatch(OverlappingPairs& overlappingPairs, MemoryAllocator& allocator); diff --git a/include/reactphysics3d/collision/narrowphase/NarrowPhaseInput.h b/include/reactphysics3d/collision/narrowphase/NarrowPhaseInput.h index 9f64574a..b3770176 100644 --- a/include/reactphysics3d/collision/narrowphase/NarrowPhaseInput.h +++ b/include/reactphysics3d/collision/narrowphase/NarrowPhaseInput.h @@ -27,7 +27,7 @@ #define REACTPHYSICS3D_NARROW_PHASE_INPUT_H // Libraries -#include +#include #include #include diff --git a/include/reactphysics3d/collision/shapes/CollisionShape.h b/include/reactphysics3d/collision/shapes/CollisionShape.h index beca8bcb..4c8c5561 100644 --- a/include/reactphysics3d/collision/shapes/CollisionShape.h +++ b/include/reactphysics3d/collision/shapes/CollisionShape.h @@ -30,7 +30,7 @@ #include #include #include -#include +#include /// ReactPhysics3D namespace namespace reactphysics3d { @@ -76,8 +76,8 @@ class CollisionShape { /// Unique identifier of the shape inside an overlapping pair uint32 mId; - /// List of the colliders associated with this shape - List mColliders; + /// Array of the colliders associated with this shape + Array mColliders; #ifdef IS_RP3D_PROFILING_ENABLED diff --git a/include/reactphysics3d/collision/shapes/ConcaveMeshShape.h b/include/reactphysics3d/collision/shapes/ConcaveMeshShape.h index dab3b9dd..106f0844 100644 --- a/include/reactphysics3d/collision/shapes/ConcaveMeshShape.h +++ b/include/reactphysics3d/collision/shapes/ConcaveMeshShape.h @@ -29,7 +29,7 @@ // Libraries #include #include -#include +#include namespace reactphysics3d { @@ -72,7 +72,7 @@ class ConcaveMeshRaycastCallback : public DynamicAABBTreeRaycastCallback { private : - List mHitAABBNodes; + Array mHitAABBNodes; const DynamicAABBTree& mDynamicAABBTree; const ConcaveMeshShape& mConcaveMeshShape; Collider* mCollider; @@ -156,7 +156,7 @@ class ConcaveMeshShape : public ConcaveShape { /// Insert all the triangles into the dynamic AABB tree void initBVHTree(); - /// Return the three vertices coordinates (in the list outTriangleVertices) of a triangle + /// Return the three vertices coordinates (in the array outTriangleVertices) of a triangle void getTriangleVertices(uint subPart, uint triangleIndex, Vector3* outTriangleVertices) const; /// Return the three vertex normals (in the array outVerticesNormals) of a triangle @@ -166,8 +166,8 @@ class ConcaveMeshShape : public ConcaveShape { uint computeTriangleShapeId(uint subPart, uint triangleIndex) const; /// Compute all the triangles of the mesh that are overlapping with the AABB in parameter - virtual void computeOverlappingTriangles(const AABB& localAABB, List& triangleVertices, - List &triangleVerticesNormals, List& shapeIds, + virtual void computeOverlappingTriangles(const AABB& localAABB, Array& triangleVertices, + Array &triangleVerticesNormals, Array& shapeIds, MemoryAllocator& allocator) const override; /// Destructor diff --git a/include/reactphysics3d/collision/shapes/ConcaveShape.h b/include/reactphysics3d/collision/shapes/ConcaveShape.h index 3ee13b42..b49f2e27 100644 --- a/include/reactphysics3d/collision/shapes/ConcaveShape.h +++ b/include/reactphysics3d/collision/shapes/ConcaveShape.h @@ -111,8 +111,8 @@ class ConcaveShape : public CollisionShape { virtual bool isPolyhedron() const override; /// Use a callback method on all triangles of the concave shape inside a given AABB - virtual void computeOverlappingTriangles(const AABB& localAABB, List& triangleVertices, - List& triangleVerticesNormals, List& shapeIds, + virtual void computeOverlappingTriangles(const AABB& localAABB, Array& triangleVertices, + Array& triangleVerticesNormals, Array& shapeIds, MemoryAllocator& allocator) const=0; /// Compute and return the volume of the collision shape diff --git a/include/reactphysics3d/collision/shapes/HeightFieldShape.h b/include/reactphysics3d/collision/shapes/HeightFieldShape.h index a6405334..c991de12 100644 --- a/include/reactphysics3d/collision/shapes/HeightFieldShape.h +++ b/include/reactphysics3d/collision/shapes/HeightFieldShape.h @@ -152,8 +152,8 @@ class HeightFieldShape : public ConcaveShape { virtual void getLocalBounds(Vector3& min, Vector3& max) const override; /// Use a callback method on all triangles of the concave shape inside a given AABB - virtual void computeOverlappingTriangles(const AABB& localAABB, List& triangleVertices, - List& triangleVerticesNormals, List& shapeIds, + virtual void computeOverlappingTriangles(const AABB& localAABB, Array& triangleVertices, + Array& triangleVerticesNormals, Array& shapeIds, MemoryAllocator& allocator) const override; /// Return the string representation of the shape diff --git a/include/reactphysics3d/components/ColliderComponents.h b/include/reactphysics3d/components/ColliderComponents.h index 8207309c..e6c4eb20 100644 --- a/include/reactphysics3d/components/ColliderComponents.h +++ b/include/reactphysics3d/components/ColliderComponents.h @@ -90,8 +90,8 @@ class ColliderComponents : public Components { /// Array with the local-to-world transforms of the colliders Transform* mLocalToWorldTransforms; - /// Array with the list of involved overlapping pairs for each collider - List* mOverlappingPairs; + /// Array with the involved overlapping pairs for each collider + Array* mOverlappingPairs; /// True if the size of the collision shape associated with the collider /// has been changed by the user @@ -194,8 +194,8 @@ class ColliderComponents : public Components { /// Set the local-to-world transform of a collider void setLocalToWorldTransform(Entity colliderEntity, const Transform& transform); - /// Return a reference to the list of overlapping pairs for a given collider - List& getOverlappingPairs(Entity colliderEntity); + /// Return a reference to the array of overlapping pairs for a given collider + Array& getOverlappingPairs(Entity colliderEntity); /// Return true if the size of collision shape of the collider has been changed by the user bool getHasCollisionShapeChangedSize(Entity colliderEntity) const; @@ -329,8 +329,8 @@ RP3D_FORCE_INLINE void ColliderComponents::setLocalToWorldTransform(Entity colli mLocalToWorldTransforms[mMapEntityToComponentIndex[colliderEntity]] = transform; } -// Return a reference to the list of overlapping pairs for a given collider -RP3D_FORCE_INLINE List& ColliderComponents::getOverlappingPairs(Entity colliderEntity) { +// Return a reference to the array of overlapping pairs for a given collider +RP3D_FORCE_INLINE Array& ColliderComponents::getOverlappingPairs(Entity colliderEntity) { assert(mMapEntityToComponentIndex.containsKey(colliderEntity)); diff --git a/include/reactphysics3d/components/CollisionBodyComponents.h b/include/reactphysics3d/components/CollisionBodyComponents.h index 08141341..a9ddd003 100644 --- a/include/reactphysics3d/components/CollisionBodyComponents.h +++ b/include/reactphysics3d/components/CollisionBodyComponents.h @@ -57,8 +57,8 @@ class CollisionBodyComponents : public Components { /// Array of pointers to the corresponding bodies CollisionBody** mBodies; - /// Array with the list of colliders of each body - List* mColliders; + /// Array with the colliders of each body + Array* mColliders; /// Array of boolean values to know if the body is active. bool* mIsActive; @@ -113,8 +113,8 @@ class CollisionBodyComponents : public Components { /// Return a pointer to a body CollisionBody* getBody(Entity bodyEntity); - /// Return the list of colliders of a body - const List& getColliders(Entity bodyEntity) const; + /// Return the array of colliders of a body + const Array& getColliders(Entity bodyEntity) const; /// Return true if the body is active bool getIsActive(Entity bodyEntity) const; @@ -153,8 +153,8 @@ RP3D_FORCE_INLINE CollisionBody *CollisionBodyComponents::getBody(Entity bodyEnt return mBodies[mMapEntityToComponentIndex[bodyEntity]]; } -// Return the list of colliders of a body -RP3D_FORCE_INLINE const List& CollisionBodyComponents::getColliders(Entity bodyEntity) const { +// Return the array of colliders of a body +RP3D_FORCE_INLINE const Array& CollisionBodyComponents::getColliders(Entity bodyEntity) const { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); diff --git a/include/reactphysics3d/components/RigidBodyComponents.h b/include/reactphysics3d/components/RigidBodyComponents.h index 45d986e8..69266262 100644 --- a/include/reactphysics3d/components/RigidBodyComponents.h +++ b/include/reactphysics3d/components/RigidBodyComponents.h @@ -145,11 +145,11 @@ class RigidBodyComponents : public Components { /// Array with the boolean value to know if the body has already been added into an island bool* mIsAlreadyInIsland; - /// For each body, the list of joints entities the body is part of - List* mJoints; + /// For each body, the array of joints entities the body is part of + Array* mJoints; - /// For each body, the list of the indices of contact pairs in which the body is involved - List* mContactPairs; + /// For each body, the array of the indices of contact pairs in which the body is involved + Array* mContactPairs; // -------------------- Methods -------------------- // @@ -342,8 +342,8 @@ class RigidBodyComponents : public Components { /// Set the value to know if the entity is already in an island void setIsAlreadyInIsland(Entity bodyEntity, bool isAlreadyInIsland); - /// Return the list of joints of a body - const List& getJoints(Entity bodyEntity) const; + /// Return the array of joints of a body + const Array& getJoints(Entity bodyEntity) const; /// Add a joint to a body component void addJointToBody(Entity bodyEntity, Entity jointEntity); @@ -769,8 +769,8 @@ RP3D_FORCE_INLINE void RigidBodyComponents::setIsAlreadyInIsland(Entity bodyEnti mIsAlreadyInIsland[mMapEntityToComponentIndex[bodyEntity]] = isAlreadyInIsland; } -// Return the list of joints of a body -RP3D_FORCE_INLINE const List& RigidBodyComponents::getJoints(Entity bodyEntity) const { +// Return the array of joints of a body +RP3D_FORCE_INLINE const Array& RigidBodyComponents::getJoints(Entity bodyEntity) const { assert(mMapEntityToComponentIndex.containsKey(bodyEntity)); return mJoints[mMapEntityToComponentIndex[bodyEntity]]; diff --git a/include/reactphysics3d/containers/List.h b/include/reactphysics3d/containers/Array.h similarity index 84% rename from include/reactphysics3d/containers/List.h rename to include/reactphysics3d/containers/Array.h index 651a2961..b48c8717 100755 --- a/include/reactphysics3d/containers/List.h +++ b/include/reactphysics3d/containers/Array.h @@ -23,8 +23,8 @@ * * ********************************************************************************/ -#ifndef REACTPHYSICS3D_LIST_H -#define REACTPHYSICS3D_LIST_H +#ifndef REACTPHYSICS3D_ARRAY_H +#define REACTPHYSICS3D_ARRAY_H // Libraries #include @@ -36,24 +36,24 @@ namespace reactphysics3d { -// Class List +// Class Array /** - * This class represents a simple generic list with custom memory allocator. + * This class represents a simple dynamic array with custom memory allocator. */ template -class List { +class Array { private: // -------------------- Attributes -------------------- // - /// Buffer for the list elements + /// Buffer for the array elements T* mBuffer; - /// Number of elements in the list + /// Number of elements in the array uint32 mSize; - /// Number of allocated elements in the list + /// Number of allocated elements in the array uint32 mCapacity; /// Memory allocator @@ -63,7 +63,7 @@ class List { /// Class Iterator /** - * This class represents an iterator for the List + * This class represents an iterator for the array */ class Iterator { @@ -198,7 +198,7 @@ class List { bool operator==(const Iterator& iterator) const { assert(mCurrentIndex >= 0 && mCurrentIndex <= mSize); - // If both iterators points to the end of the list + // If both iterators points to the end of the array if (mCurrentIndex == mSize && iterator.mCurrentIndex == iterator.mSize) { return true; } @@ -212,14 +212,14 @@ class List { } /// Frienship - friend class List; + friend class Array; }; // -------------------- Methods -------------------- // /// Constructor - List(MemoryAllocator& allocator, uint32 capacity = 0) + Array(MemoryAllocator& allocator, uint32 capacity = 0) : mBuffer(nullptr), mSize(0), mCapacity(0), mAllocator(allocator) { if (capacity > 0) { @@ -230,24 +230,24 @@ class List { } /// Copy constructor - List(const List& list) : mBuffer(nullptr), mSize(0), mCapacity(0), mAllocator(list.mAllocator) { + Array(const Array& array) : mBuffer(nullptr), mSize(0), mCapacity(0), mAllocator(array.mAllocator) { // If we need to allocate more memory - if (list.mCapacity > 0) { - reserve(list.mCapacity); + if (array.mCapacity > 0) { + reserve(array.mCapacity); } - // All all the elements of the list to the current one - addRange(list); + // All all the elements of the array to the current one + addRange(array); } /// Destructor - ~List() { + ~Array() { // If elements have been allocated if (mCapacity > 0) { - // Clear the list + // Clear the array clear(true); } } @@ -284,7 +284,7 @@ class List { mCapacity = capacity; } - /// Add an element into the list + /// Add an element into the array void add(const T& element) { // If we need to allocate more memory @@ -298,7 +298,7 @@ class List { mSize++; } - /// Add an element into the list by constructing it directly into the list (in order to avoid a copy) + /// Add an element into the array by constructing it directly into the array (in order to avoid a copy) template void emplace(Ts&&... args) { @@ -313,7 +313,7 @@ class List { mSize++; } - /// Add a given numbers of elements at the end of the list but do not init them + /// Add a given numbers of elements at the end of the array but do not init them void addWithoutInit(uint32 nbElements) { // If we need to allocate more memory @@ -324,8 +324,8 @@ class List { mSize += nbElements; } - /// Try to find a given item of the list and return an iterator - /// pointing to that element if it exists in the list. Otherwise, + /// Try to find a given item of the array and return an iterator + /// pointing to that element if it exists in the array. Otherwise, /// this method returns the end() iterator Iterator find(const T& element) { @@ -338,19 +338,19 @@ class List { return end(); } - /// Look for an element in the list and remove it + /// Look for an element in the array and remove it Iterator remove(const T& element) { return remove(find(element)); } - /// Remove an element from the list and return a iterator + /// Remove an element from the array and return a iterator /// pointing to the element after the removed one (or end() if none) Iterator remove(const Iterator& it) { assert(it.mBuffer == mBuffer); return removeAt(it.mCurrentIndex); } - /// Remove an element from the list at a given index (all the following items will be moved) + /// Remove an element from the array at a given index (all the following items will be moved) Iterator removeAt(uint32 index) { assert(index < mSize); @@ -379,31 +379,32 @@ class List { mBuffer[index] = mBuffer[mSize - 1]; - // Call the destructor of the last element + // Call the destructor of the last element mBuffer[mSize - 1].~T(); mSize--; } - /// Append another list at the end of the current one - void addRange(const List& list) { + /// Remove an element from the array at a given index and replace it by the last one of the array (if any) + /// Append another array at the end of the current one + void addRange(const Array& array) { // If we need to allocate more memory - if (mSize + list.size() > mCapacity) { + if (mSize + array.size() > mCapacity) { // Allocate memory - reserve(mSize + list.size()); + reserve(mSize + array.size()); } - // Add the elements of the list to the current one - for(uint32 i=0; i(mBuffer + mSize)) T(list[i]); + new (reinterpret_cast(mBuffer + mSize)) T(array[i]); mSize++; } } - /// Clear the list + /// Clear the array void clear(bool releaseMemory = false) { // Call the destructor of each element @@ -424,12 +425,12 @@ class List { } } - /// Return the number of elements in the list + /// Return the number of elements in the array uint32 size() const { return mSize; } - /// Return the capacity of the list + /// Return the capacity of the array uint32 capacity() const { return mCapacity; } @@ -447,12 +448,12 @@ class List { } /// Overloaded equality operator - bool operator==(const List& list) const { + bool operator==(const Array& array) const { - if (mSize != list.mSize) return false; + if (mSize != array.mSize) return false; for (uint32 i=0; i < mSize; i++) { - if (mBuffer[i] != list[i]) { + if (mBuffer[i] != array[i]) { return false; } } @@ -461,21 +462,21 @@ class List { } /// Overloaded not equal operator - bool operator!=(const List& list) const { + bool operator!=(const Array& array) const { - return !((*this) == list); + return !((*this) == array); } /// Overloaded assignment operator - List& operator=(const List& list) { + Array& operator=(const Array& array) { - if (this != &list) { + if (this != &array) { // Clear all the elements clear(); - // Add all the elements of the list to the current one - addRange(list); + // Add all the elements of the array to the current one + addRange(array); } return *this; diff --git a/include/reactphysics3d/containers/Set.h b/include/reactphysics3d/containers/Set.h index f54c7a69..3be397b8 100755 --- a/include/reactphysics3d/containers/Set.h +++ b/include/reactphysics3d/containers/Set.h @@ -478,16 +478,16 @@ class Set { return end(); } - /// Return a list with all the values of the set - List toList(MemoryAllocator& listAllocator) const { + /// Return an array with all the values of the set + Array toArray(MemoryAllocator& arrayAllocator) const { - List list(listAllocator); + Array array(arrayAllocator); for (auto it = begin(); it != end(); ++it) { - list.add(*it); + array.add(*it); } - return list; + return array; } /// Clear the set diff --git a/include/reactphysics3d/engine/EntityManager.h b/include/reactphysics3d/engine/EntityManager.h index 383e509b..f2a92bb1 100644 --- a/include/reactphysics3d/engine/EntityManager.h +++ b/include/reactphysics3d/engine/EntityManager.h @@ -28,7 +28,7 @@ // Libraries #include -#include +#include #include #include @@ -45,8 +45,8 @@ class EntityManager { // -------------------- Attributes -------------------- // - /// List storing the generations of the created entities - List mGenerations; + /// Array storing the generations of the created entities + Array mGenerations; /// Deque with the indices of destroyed entities that can be reused Deque mFreeIndices; diff --git a/include/reactphysics3d/engine/Islands.h b/include/reactphysics3d/engine/Islands.h index cc3d42b9..b2931222 100644 --- a/include/reactphysics3d/engine/Islands.h +++ b/include/reactphysics3d/engine/Islands.h @@ -28,7 +28,7 @@ // Libraries #include -#include +#include #include #include @@ -64,19 +64,19 @@ struct Islands { /// For each island, index of the first contact manifold of the island in the array of contact manifolds - List contactManifoldsIndices; + Array contactManifoldsIndices; /// For each island, number of contact manifolds in the island - List nbContactManifolds; + Array nbContactManifolds; - /// List of all the entities of the bodies in the islands (stored sequentially) - List bodyEntities; + /// Array of all the entities of the bodies in the islands (stored sequentially) + Array bodyEntities; /// For each island we store the starting index of the bodies of that island in the "bodyEntities" array - List startBodyEntitiesIndex; + Array startBodyEntitiesIndex; /// For each island, total number of bodies in the island - List nbBodiesInIsland; + Array nbBodiesInIsland; // -------------------- Methods -------------------- // diff --git a/include/reactphysics3d/engine/OverlappingPairs.h b/include/reactphysics3d/engine/OverlappingPairs.h index cdde8865..21583ef0 100644 --- a/include/reactphysics3d/engine/OverlappingPairs.h +++ b/include/reactphysics3d/engine/OverlappingPairs.h @@ -283,11 +283,11 @@ class OverlappingPairs { /// Heap memory allocator MemoryAllocator& mHeapAllocator; - /// List of convex vs convex overlapping pairs - List mConvexPairs; + /// Array of convex vs convex overlapping pairs + Array mConvexPairs; - /// List of convex vs concave overlapping pairs - List mConcavePairs; + /// Array of convex vs concave overlapping pairs + Array mConcavePairs; /// Map a pair id to the internal array index Map mMapConvexPairIdToPairIndex; diff --git a/include/reactphysics3d/engine/PhysicsWorld.h b/include/reactphysics3d/engine/PhysicsWorld.h index f25253bd..fa603517 100644 --- a/include/reactphysics3d/engine/PhysicsWorld.h +++ b/include/reactphysics3d/engine/PhysicsWorld.h @@ -28,7 +28,7 @@ // Libraries #include -#include +#include #include #include #include @@ -210,7 +210,7 @@ class PhysicsWorld { CollisionDetectionSystem mCollisionDetection; /// All the collision bodies of the world - List mCollisionBodies; + Array mCollisionBodies; /// Pointer to an event listener object EventListener* mEventListener; @@ -233,7 +233,7 @@ class PhysicsWorld { /// Order in which to process the ContactPairs for contact creation such that /// all the contact manifolds and contact points of a given island are packed together /// This array contains the indices of the ContactPairs. - List mProcessContactPairsOrderIslands; + Array mProcessContactPairsOrderIslands; /// Contact solver system ContactSolverSystem mContactSolverSystem; @@ -254,7 +254,7 @@ class PhysicsWorld { bool mIsSleepingEnabled; /// All the rigid bodies of the physics world - List mRigidBodies; + Array mRigidBodies; /// True if the gravity force is on bool mIsGravityEnabled; @@ -298,7 +298,7 @@ class PhysicsWorld { /// Put bodies to sleep if needed. void updateSleepingBodies(decimal timeStep); - /// Add the joint to the list of joints of the two bodies involved in the joint + /// Add the joint to the array of joints of the two bodies involved in the joint void addJointToBodies(Entity body1, Entity body2, Entity joint); /// Update the world inverse inertia tensors of rigid bodies diff --git a/include/reactphysics3d/mathematics/mathematics_functions.h b/include/reactphysics3d/mathematics/mathematics_functions.h index cf0f6880..2f1aa0d5 100755 --- a/include/reactphysics3d/mathematics/mathematics_functions.h +++ b/include/reactphysics3d/mathematics/mathematics_functions.h @@ -32,7 +32,7 @@ #include #include #include -#include +#include /// ReactPhysics3D namespace namespace reactphysics3d { @@ -111,14 +111,14 @@ decimal computePlaneSegmentIntersection(const Vector3& segA, const Vector3& segB decimal computePointToLineDistance(const Vector3& linePointA, const Vector3& linePointB, const Vector3& point); /// Clip a segment against multiple planes and return the clipped segment vertices -List clipSegmentWithPlanes(const Vector3& segA, const Vector3& segB, - const List& planesPoints, - const List& planesNormals, +Array clipSegmentWithPlanes(const Vector3& segA, const Vector3& segB, + const Array& planesPoints, + const Array& planesNormals, MemoryAllocator& allocator); /// Clip a polygon against multiple planes and return the clipped polygon vertices -List clipPolygonWithPlanes(const List& polygonVertices, const List& planesPoints, - const List& planesNormals, MemoryAllocator& allocator); +Array clipPolygonWithPlanes(const Array& polygonVertices, const Array& planesPoints, + const Array& planesNormals, MemoryAllocator& allocator); /// Project a point onto a plane that is given by a point and its unit length normal Vector3 projectPointOntoPlane(const Vector3& point, const Vector3& planeNormal, const Vector3& planePoint); diff --git a/include/reactphysics3d/reactphysics3d.h b/include/reactphysics3d/reactphysics3d.h index 2b32999c..918deb73 100644 --- a/include/reactphysics3d/reactphysics3d.h +++ b/include/reactphysics3d/reactphysics3d.h @@ -64,7 +64,7 @@ #include #include #include -#include +#include /// Alias to the ReactPhysics3D namespace namespace rp3d = reactphysics3d; diff --git a/include/reactphysics3d/systems/BroadPhaseSystem.h b/include/reactphysics3d/systems/BroadPhaseSystem.h index 9b4baf8a..3cbaa4ec 100644 --- a/include/reactphysics3d/systems/BroadPhaseSystem.h +++ b/include/reactphysics3d/systems/BroadPhaseSystem.h @@ -51,10 +51,10 @@ class AABBOverlapCallback : public DynamicAABBTreeOverlapCallback { public: - List& mOverlappingNodes; + Array& mOverlappingNodes; // Constructor - AABBOverlapCallback(List& overlappingNodes) : mOverlappingNodes(overlappingNodes) { + AABBOverlapCallback(Array& overlappingNodes) : mOverlappingNodes(overlappingNodes) { } @@ -184,7 +184,7 @@ class BroadPhaseSystem { void removeMovedCollider(int broadPhaseID); /// Compute all the overlapping pairs of collision shapes - void computeOverlappingPairs(MemoryManager& memoryManager, List>& overlappingNodes); + void computeOverlappingPairs(MemoryManager& memoryManager, Array>& overlappingNodes); /// Return the collider corresponding to the broad-phase node id in parameter Collider* getColliderForBroadPhaseId(int broadPhaseId) const; diff --git a/include/reactphysics3d/systems/CollisionDetectionSystem.h b/include/reactphysics3d/systems/CollisionDetectionSystem.h index 89ed2f32..9431279b 100644 --- a/include/reactphysics3d/systems/CollisionDetectionSystem.h +++ b/include/reactphysics3d/systems/CollisionDetectionSystem.h @@ -99,7 +99,7 @@ class CollisionDetectionSystem { OverlappingPairs mOverlappingPairs; /// Overlapping nodes during broad-phase computation - List> mBroadPhaseOverlappingNodes; + Array> mBroadPhaseOverlappingNodes; /// Broad-phase system BroadPhaseSystem mBroadPhaseSystem; @@ -110,57 +110,57 @@ class CollisionDetectionSystem { /// Narrow-phase collision detection input NarrowPhaseInput mNarrowPhaseInput; - /// List of the potential contact points - List mPotentialContactPoints; + /// Array of the potential contact points + Array mPotentialContactPoints; - /// List of the potential contact manifolds - List mPotentialContactManifolds; + /// Array of the potential contact manifolds + Array mPotentialContactManifolds; - /// First list of narrow-phase pair contacts - List mContactPairs1; + /// First array of narrow-phase pair contacts + Array mContactPairs1; - /// Second list of narrow-phase pair contacts - List mContactPairs2; + /// Second array of narrow-phase pair contacts + Array mContactPairs2; - /// Pointer to the list of contact pairs of the previous frame (either mContactPairs1 or mContactPairs2) - List* mPreviousContactPairs; + /// Pointer to the array of contact pairs of the previous frame (either mContactPairs1 or mContactPairs2) + Array* mPreviousContactPairs; - /// Pointer to the list of contact pairs of the current frame (either mContactPairs1 or mContactPairs2) - List* mCurrentContactPairs; + /// Pointer to the array of contact pairs of the current frame (either mContactPairs1 or mContactPairs2) + Array* mCurrentContactPairs; - /// List of lost contact pairs (contact pairs in contact in previous frame but not in the current one) - List mLostContactPairs; + /// Array of lost contact pairs (contact pairs in contact in previous frame but not in the current one) + Array mLostContactPairs; /// Pointer to the map of overlappingPairId to the index of contact pair of the previous frame /// (either mMapPairIdToContactPairIndex1 or mMapPairIdToContactPairIndex2) Map mPreviousMapPairIdToContactPairIndex; - /// First list with the contact manifolds - List mContactManifolds1; + /// First array with the contact manifolds + Array mContactManifolds1; - /// Second list with the contact manifolds - List mContactManifolds2; + /// Second array with the contact manifolds + Array mContactManifolds2; - /// Pointer to the list of contact manifolds from the previous frame (either mContactManifolds1 or mContactManifolds2) - List* mPreviousContactManifolds; + /// Pointer to the array of contact manifolds from the previous frame (either mContactManifolds1 or mContactManifolds2) + Array* mPreviousContactManifolds; - /// Pointer to the list of contact manifolds from the current frame (either mContactManifolds1 or mContactManifolds2) - List* mCurrentContactManifolds; + /// Pointer to the array of contact manifolds from the current frame (either mContactManifolds1 or mContactManifolds2) + Array* mCurrentContactManifolds; - /// Second list of contact points (contact points from either the current frame of the previous frame) - List mContactPoints1; + /// Second array of contact points (contact points from either the current frame of the previous frame) + Array mContactPoints1; - /// Second list of contact points (contact points from either the current frame of the previous frame) - List mContactPoints2; + /// Second array of contact points (contact points from either the current frame of the previous frame) + Array mContactPoints2; /// Pointer to the contact points of the previous frame (either mContactPoints1 or mContactPoints2) - List* mPreviousContactPoints; + Array* mPreviousContactPoints; /// Pointer to the contact points of the current frame (either mContactPoints1 or mContactPoints2) - List* mCurrentContactPoints; + Array* mCurrentContactPoints; /// Array with the indices of all the contact pairs that have at least one CollisionBody - List mCollisionBodyContactPairsIndices; + Array mCollisionBodyContactPairsIndices; #ifdef IS_RP3D_PROFILING_ENABLED @@ -178,7 +178,7 @@ class CollisionDetectionSystem { void computeMiddlePhase(NarrowPhaseInput& narrowPhaseInput, bool needToReportContacts); // Compute the middle-phase collision detection - void computeMiddlePhaseCollisionSnapshot(List& convexPairs, List& concavePairs, NarrowPhaseInput& narrowPhaseInput, + void computeMiddlePhaseCollisionSnapshot(Array& convexPairs, Array& concavePairs, NarrowPhaseInput& narrowPhaseInput, bool reportContacts); /// Compute the narrow-phase collision detection @@ -191,14 +191,14 @@ class CollisionDetectionSystem { bool computeNarrowPhaseCollisionSnapshot(NarrowPhaseInput& narrowPhaseInput, CollisionCallback& callback); /// Process the potential contacts after narrow-phase collision detection - void computeOverlapSnapshotContactPairs(NarrowPhaseInput& narrowPhaseInput, List& contactPairs) const; + void computeOverlapSnapshotContactPairs(NarrowPhaseInput& narrowPhaseInput, Array& contactPairs) const; /// Convert the potential contact into actual contacts - void computeOverlapSnapshotContactPairs(NarrowPhaseInfoBatch& narrowPhaseInfoBatch, List& contactPairs, + void computeOverlapSnapshotContactPairs(NarrowPhaseInfoBatch& narrowPhaseInfoBatch, Array& contactPairs, Set& setOverlapContactPairId) const; - /// Take a list of overlapping nodes in the broad-phase and create new overlapping pairs if necessary - void updateOverlappingPairs(const List >& overlappingNodes); + /// Take an array of overlapping nodes in the broad-phase and create new overlapping pairs if necessary + void updateOverlappingPairs(const Array >& overlappingNodes); /// Remove pairs that are not overlapping anymore void removeNonOverlappingPairs(); @@ -213,22 +213,22 @@ class CollisionDetectionSystem { void computeConvexVsConcaveMiddlePhase(OverlappingPairs::ConcaveOverlappingPair& overlappingPair, MemoryAllocator& allocator, NarrowPhaseInput& narrowPhaseInput); - /// Swap the previous and current contacts lists + /// Swap the previous and current contacts arrays void swapPreviousAndCurrentContacts(); /// Convert the potential contact into actual contacts void processPotentialContacts(NarrowPhaseInfoBatch& narrowPhaseInfoBatch, - bool updateLastFrameInfo, List& potentialContactPoints, - List& potentialContactManifolds, - Map& mapPairIdToContactPairIndex, List* contactPairs); + bool updateLastFrameInfo, Array& potentialContactPoints, + Array& potentialContactManifolds, + Map& mapPairIdToContactPairIndex, Array* contactPairs); /// Process the potential contacts after narrow-phase collision detection - void processAllPotentialContacts(NarrowPhaseInput& narrowPhaseInput, bool updateLastFrameInfo, List& potentialContactPoints, - List& potentialContactManifolds, List* contactPairs); + void processAllPotentialContacts(NarrowPhaseInput& narrowPhaseInput, bool updateLastFrameInfo, Array& potentialContactPoints, + Array& potentialContactManifolds, Array* contactPairs); /// Reduce the potential contact manifolds and contact points of the overlapping pair contacts - void reducePotentialContactManifolds(List* contactPairs, List& potentialContactManifolds, - const List& potentialContactPoints) const; + void reducePotentialContactManifolds(Array* contactPairs, Array& potentialContactManifolds, + const Array& potentialContactPoints) const; /// Create the actual contact manifolds and contacts points (from potential contacts) for a given contact pair void createContacts(); @@ -243,40 +243,40 @@ class CollisionDetectionSystem { void computeLostContactPairs(); /// Create the actual contact manifolds and contacts points for testCollision() methods - void createSnapshotContacts(List& contactPairs, List &contactManifolds, - List& contactPoints, - List& potentialContactManifolds, - List& potentialContactPoints); + void createSnapshotContacts(Array& contactPairs, Array &contactManifolds, + Array& contactPoints, + Array& potentialContactManifolds, + Array& potentialContactPoints); /// Initialize the current contacts with the contacts from the previous frame (for warmstarting) void initContactsWithPreviousOnes(); /// Reduce the number of contact points of a potential contact manifold void reduceContactPoints(ContactManifoldInfo& manifold, const Transform& shape1ToWorldTransform, - const List& potentialContactPoints) const; + const Array& potentialContactPoints) const; /// Report contacts - void reportContacts(CollisionCallback& callback, List* contactPairs, - List* manifolds, List* contactPoints, List& lostContactPairs); + void reportContacts(CollisionCallback& callback, Array* contactPairs, + Array* manifolds, Array* contactPoints, Array& lostContactPairs); /// Report all triggers - void reportTriggers(EventListener& eventListener, List* contactPairs, List& lostContactPairs); + void reportTriggers(EventListener& eventListener, Array* contactPairs, Array& lostContactPairs); /// Report all contacts for debug rendering - void reportDebugRenderingContacts(List* contactPairs, List* manifolds, List* contactPoints, List& lostContactPairs); + void reportDebugRenderingContacts(Array* contactPairs, Array* manifolds, Array* contactPoints, Array& lostContactPairs); /// Return the largest depth of all the contact points of a potential manifold decimal computePotentialManifoldLargestContactDepth(const ContactManifoldInfo& manifold, - const List& potentialContactPoints) const; + const Array& potentialContactPoints) const; /// Process the potential contacts where one collion is a concave shape void processSmoothMeshContacts(OverlappingPair* pair); /// Filter the overlapping pairs to keep only the pairs where a given body is involved - void filterOverlappingPairs(Entity bodyEntity, List& convexPairs, List& concavePairs) const; + void filterOverlappingPairs(Entity bodyEntity, Array& convexPairs, Array& concavePairs) const; /// Filter the overlapping pairs to keep only the pairs where two given bodies are involved - void filterOverlappingPairs(Entity body1Entity, Entity body2Entity, List& convexPairs, List& concavePairs) const; + void filterOverlappingPairs(Entity body1Entity, Entity body2Entity, Array& convexPairs, Array& concavePairs) const; /// Remove an element in an array (and replace it by the last one in the array) void removeItemAtInArray(uint array[], uint8 index, uint8& arraySize) const; @@ -411,7 +411,7 @@ RP3D_FORCE_INLINE void CollisionDetectionSystem::removeNoCollisionPair(Entity bo } // Ask for a collision shape to be tested again during broad-phase. -/// We simply put the shape in the list of collision shape that have moved in the +/// We simply put the shape in the array of collision shape that have moved in the /// previous frame so that it is tested for collision again in the broad-phase. RP3D_FORCE_INLINE void CollisionDetectionSystem::askForBroadPhaseCollisionCheck(Collider* collider) { diff --git a/include/reactphysics3d/systems/ContactSolverSystem.h b/include/reactphysics3d/systems/ContactSolverSystem.h index 8c484328..9c5a87db 100644 --- a/include/reactphysics3d/systems/ContactSolverSystem.h +++ b/include/reactphysics3d/systems/ContactSolverSystem.h @@ -302,11 +302,11 @@ class ContactSolverSystem { /// Reference to the islands Islands& mIslands; - /// Pointer to the list of contact manifolds from narrow-phase - List* mAllContactManifolds; + /// Pointer to the array of contact manifolds from narrow-phase + Array* mAllContactManifolds; - /// Pointer to the list of contact points from narrow-phase - List* mAllContactPoints; + /// Pointer to the array of contact points from narrow-phase + Array* mAllContactPoints; /// Reference to the body components CollisionBodyComponents& mBodyComponents; @@ -356,7 +356,7 @@ class ContactSolverSystem { ~ContactSolverSystem() = default; /// Initialize the contact constraints - void init(List* contactManifolds, List* contactPoints, decimal timeStep); + void init(Array* contactManifolds, Array* contactPoints, decimal timeStep); /// Initialize the constraint solver for a given island void initializeForIsland(uint islandIndex); diff --git a/include/reactphysics3d/utils/DebugRenderer.h b/include/reactphysics3d/utils/DebugRenderer.h index e8e31473..c5321f8e 100644 --- a/include/reactphysics3d/utils/DebugRenderer.h +++ b/include/reactphysics3d/utils/DebugRenderer.h @@ -27,7 +27,7 @@ #define REACTPHYSICS3D_DEBUG_RENDERER_H // Libraries -#include +#include #include #include #include @@ -47,7 +47,7 @@ class PhysicsWorld; /** * This class is used to display physics debug information directly into the user application view. * For instance, it is possible to display AABBs of colliders, colliders or contact points. This class - * can be used to get the debug information as lists of basic primitives (points, linges, triangles, ...). + * can be used to get the debug information as arrays of basic primitives (points, linges, triangles, ...). * You can use this to render physics debug information in your simulation on top of your object. Note that * you should use this only for debugging purpose and you should disable it when you compile the final release * version of your application because computing/rendering phyiscs debug information can be expensive. @@ -159,11 +159,11 @@ class DebugRenderer : public EventListener { /// Memory allocator MemoryAllocator& mAllocator; - /// List with all the debug lines - List mLines; + /// Array with all the debug lines + Array mLines; - /// List with all the debug triangles - List mTriangles; + /// Array with all the debug triangles + Array mTriangles; /// 32-bits integer that contains all the flags of debug items to display uint32 mDisplayedDebugItems; @@ -216,8 +216,8 @@ class DebugRenderer : public EventListener { /// Return the number of lines uint32 getNbLines() const; - /// Return a reference to the list of lines - const List& getLines() const; + /// Return a reference to the array of lines + const Array& getLines() const; /// Return a pointer to the array of lines const DebugLine* getLinesArray() const; @@ -225,8 +225,8 @@ class DebugRenderer : public EventListener { /// Return the number of triangles uint32 getNbTriangles() const; - /// Return a reference to the list of triangles - const List& getTriangles() const; + /// Return a reference to the array of triangles + const Array& getTriangles() const; /// Return a pointer to the array of triangles const DebugTriangle* getTrianglesArray() const; @@ -267,11 +267,11 @@ RP3D_FORCE_INLINE uint32 DebugRenderer::getNbLines() const { return mLines.size(); } -// Return a reference to the list of lines +// Return a reference to the array of lines /** - * @return The list of lines to draw + * @return The array of lines to draw */ -RP3D_FORCE_INLINE const List& DebugRenderer::getLines() const { +RP3D_FORCE_INLINE const Array& DebugRenderer::getLines() const { return mLines; } @@ -291,11 +291,11 @@ RP3D_FORCE_INLINE uint32 DebugRenderer::getNbTriangles() const { return mTriangles.size(); } -// Return a reference to the list of triangles +// Return a reference to the array of triangles /** - * @return The list of triangles to draw + * @return The array of triangles to draw */ -RP3D_FORCE_INLINE const List& DebugRenderer::getTriangles() const { +RP3D_FORCE_INLINE const Array& DebugRenderer::getTriangles() const { return mTriangles; } diff --git a/include/reactphysics3d/utils/DefaultLogger.h b/include/reactphysics3d/utils/DefaultLogger.h index bcc2f450..5f6733ee 100644 --- a/include/reactphysics3d/utils/DefaultLogger.h +++ b/include/reactphysics3d/utils/DefaultLogger.h @@ -28,7 +28,7 @@ // Libraries #include -#include +#include #include #include #include @@ -445,7 +445,7 @@ class DefaultLogger : public Logger { MemoryAllocator& mAllocator; /// All the log destinations - List mDestinations; + Array mDestinations; /// Map a log format to the given formatter object Map mFormatters; diff --git a/include/reactphysics3d/utils/Logger.h b/include/reactphysics3d/utils/Logger.h index ba0144e2..c54ae10e 100644 --- a/include/reactphysics3d/utils/Logger.h +++ b/include/reactphysics3d/utils/Logger.h @@ -27,7 +27,7 @@ #define REACTPHYSICS3D_LOGGER_H // Libraries -#include +#include #include #include #include diff --git a/include/reactphysics3d/utils/Profiler.h b/include/reactphysics3d/utils/Profiler.h index 43f4ae0f..8c46e7ca 100644 --- a/include/reactphysics3d/utils/Profiler.h +++ b/include/reactphysics3d/utils/Profiler.h @@ -34,7 +34,7 @@ #include #include #include -#include +#include /// ReactPhysics3D namespace namespace reactphysics3d { diff --git a/src/body/CollisionBody.cpp b/src/body/CollisionBody.cpp index dbfada79..ba89b3a7 100644 --- a/src/body/CollisionBody.cpp +++ b/src/body/CollisionBody.cpp @@ -200,8 +200,8 @@ void CollisionBody::removeCollider(Collider* collider) { void CollisionBody::removeAllColliders() { // Look for the collider that contains the collision shape in parameter. - // Note that we need to copy the list of collider entities because we are deleting them in a loop. - const List collidersEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); + // Note that we need to copy the array of collider entities because we are deleting them in a loop. + const Array collidersEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); for (uint32 i=0; i < collidersEntities.size(); i++) { removeCollider(mWorld.mCollidersComponents.getCollider(collidersEntities[i])); @@ -222,7 +222,7 @@ const Transform& CollisionBody::getTransform() const { void CollisionBody::updateBroadPhaseState(decimal timeStep) const { // For all the colliders of the body - const List& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); + const Array& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); const uint32 nbColliderEntities = colliderEntities.size(); for (uint32 i=0; i < nbColliderEntities; i++) { @@ -253,7 +253,7 @@ void CollisionBody::setIsActive(bool isActive) { const Transform& transform = mWorld.mTransformComponents.getTransform(mEntity); // For each collider of the body - const List& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); + const Array& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); for (uint32 i=0; i < colliderEntities.size(); i++) { Collider* collider = mWorld.mCollidersComponents.getCollider(colliderEntities[i]); @@ -269,7 +269,7 @@ void CollisionBody::setIsActive(bool isActive) { else { // If we have to deactivate the body // For each collider of the body - const List& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); + const Array& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); for (uint32 i=0; i < colliderEntities.size(); i++) { Collider* collider = mWorld.mCollidersComponents.getCollider(colliderEntities[i]); @@ -292,7 +292,7 @@ void CollisionBody::setIsActive(bool isActive) { void CollisionBody::askForBroadPhaseCollisionCheck() const { // For all the colliders of the body - const List& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); + const Array& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); for (uint32 i=0; i < colliderEntities.size(); i++) { Collider* collider = mWorld.mCollidersComponents.getCollider(colliderEntities[i]); @@ -310,7 +310,7 @@ void CollisionBody::askForBroadPhaseCollisionCheck() const { bool CollisionBody::testPointInside(const Vector3& worldPoint) const { // For each collider of the body - const List& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); + const Array& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); for (uint32 i=0; i < colliderEntities.size(); i++) { Collider* collider = mWorld.mCollidersComponents.getCollider(colliderEntities[i]); @@ -339,7 +339,7 @@ bool CollisionBody::raycast(const Ray& ray, RaycastInfo& raycastInfo) { Ray rayTemp(ray); // For each collider of the body - const List& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); + const Array& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); for (uint32 i=0; i < colliderEntities.size(); i++) { Collider* collider = mWorld.mCollidersComponents.getCollider(colliderEntities[i]); @@ -362,7 +362,7 @@ AABB CollisionBody::getAABB() const { AABB bodyAABB; - const List& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); + const Array& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); if (colliderEntities.size() == 0) return bodyAABB; const Transform& transform = mWorld.mTransformComponents.getTransform(mEntity); diff --git a/src/body/RigidBody.cpp b/src/body/RigidBody.cpp index 74cdf8ed..bd5586a1 100644 --- a/src/body/RigidBody.cpp +++ b/src/body/RigidBody.cpp @@ -324,7 +324,7 @@ Vector3 RigidBody::computeCenterOfMass() const { Vector3 centerOfMassLocal(0, 0, 0); // Compute the local center of mass - const List& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); + const Array& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); for (uint32 i=0; i < colliderEntities.size(); i++) { const uint colliderIndex = mWorld.mCollidersComponents.getEntityIndex(colliderEntities[i]); @@ -356,7 +356,7 @@ void RigidBody::computeMassAndInertiaTensorLocal(Vector3& inertiaTensorLocal, de const Vector3 centerOfMassLocal = mWorld.mRigidBodyComponents.getCenterOfMassLocal(mEntity); // Compute the inertia tensor using all the colliders - const List& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); + const Array& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); for (uint32 i=0; i < colliderEntities.size(); i++) { const uint colliderIndex = mWorld.mCollidersComponents.getEntityIndex(colliderEntities[i]); @@ -431,7 +431,7 @@ void RigidBody::updateMassFromColliders() { decimal totalMass = decimal(0.0); // Compute the total mass of the body - const List& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); + const Array& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); for (uint32 i=0; i < colliderEntities.size(); i++) { const uint colliderIndex = mWorld.mCollidersComponents.getEntityIndex(colliderEntities[i]); @@ -871,11 +871,11 @@ void RigidBody::setIsSleeping(bool isSleeping) { void RigidBody::resetOverlappingPairs() { // For each collider of the body - const List& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); + const Array& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); for (uint32 i=0; i < colliderEntities.size(); i++) { // Get the currently overlapping pairs for this collider - List overlappingPairs = mWorld.mCollidersComponents.getOverlappingPairs(colliderEntities[i]); + Array overlappingPairs = mWorld.mCollidersComponents.getOverlappingPairs(colliderEntities[i]); for (uint32 j=0; j < overlappingPairs.size(); j++) { @@ -951,7 +951,7 @@ void RigidBody::setProfiler(Profiler* profiler) { CollisionBody::setProfiler(profiler); // Set the profiler for each collider - const List& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); + const Array& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); for (uint32 i=0; i < colliderEntities.size(); i++) { Collider* collider = mWorld.mCollidersComponents.getCollider(colliderEntities[i]); diff --git a/src/collision/CollisionCallback.cpp b/src/collision/CollisionCallback.cpp index 34d40f51..f537bdb9 100644 --- a/src/collision/CollisionCallback.cpp +++ b/src/collision/CollisionCallback.cpp @@ -39,7 +39,7 @@ CollisionCallback::ContactPoint::ContactPoint(const reactphysics3d::ContactPoint // Contact Pair Constructor CollisionCallback::ContactPair::ContactPair(const reactphysics3d::ContactPair& contactPair, - List* contactPoints, PhysicsWorld& world, bool isLostContactPair) + Array* contactPoints, PhysicsWorld& world, bool isLostContactPair) :mContactPair(contactPair), mContactPoints(contactPoints), mWorld(world), mIsLostContactPair(isLostContactPair) { @@ -76,8 +76,8 @@ CollisionCallback::ContactPair::EventType CollisionCallback::ContactPair::getEve } // Constructor -CollisionCallback::CallbackData::CallbackData(List* contactPairs, List* manifolds, - List* contactPoints, List& lostContactPairs, PhysicsWorld& world) +CollisionCallback::CallbackData::CallbackData(Array* contactPairs, Array* manifolds, + Array* contactPoints, Array& lostContactPairs, PhysicsWorld& world) :mContactPairs(contactPairs), mContactManifolds(manifolds), mContactPoints(contactPoints), mLostContactPairs(lostContactPairs), mContactPairsIndices(world.mMemoryManager.getHeapAllocator(), contactPairs->size()), mLostContactPairsIndices(world.mMemoryManager.getHeapAllocator(), lostContactPairs.size()), mWorld(world) { diff --git a/src/collision/HalfEdgeStructure.cpp b/src/collision/HalfEdgeStructure.cpp index 0892b77c..574062c3 100644 --- a/src/collision/HalfEdgeStructure.cpp +++ b/src/collision/HalfEdgeStructure.cpp @@ -41,7 +41,7 @@ void HalfEdgeStructure::init() { Map mapEdgeIndexToKey(mAllocator); Map mapFaceIndexToEdgeKey(mAllocator); - List currentFaceEdges(mAllocator, mFaces[0].faceVertices.size()); + Array currentFaceEdges(mAllocator, mFaces[0].faceVertices.size()); // For each face for (uint32 f=0; f& contactPairs, List& lostContactPairs, bool onlyReportTriggers, PhysicsWorld& world) +OverlapCallback::CallbackData::CallbackData(Array& contactPairs, Array& lostContactPairs, bool onlyReportTriggers, PhysicsWorld& world) :mContactPairs(contactPairs), mLostContactPairs(lostContactPairs), mContactPairsIndices(world.mMemoryManager.getHeapAllocator()), mLostContactPairsIndices(world.mMemoryManager.getHeapAllocator()), mWorld(world) { diff --git a/src/collision/PolyhedronMesh.cpp b/src/collision/PolyhedronMesh.cpp index 653fde1a..e413bce0 100644 --- a/src/collision/PolyhedronMesh.cpp +++ b/src/collision/PolyhedronMesh.cpp @@ -75,7 +75,7 @@ void PolyhedronMesh::createHalfEdgeStructure() { // Get the polygon face PolygonVertexArray::PolygonFace* face = mPolygonVertexArray->getPolygonFace(f); - List faceVertices(mMemoryAllocator, face->nbVertices); + Array faceVertices(mMemoryAllocator, face->nbVertices); // For each vertex of the face for (uint v=0; v < face->nbVertices; v++) { diff --git a/src/collision/broadphase/DynamicAABBTree.cpp b/src/collision/broadphase/DynamicAABBTree.cpp index 655e3a1e..651def8a 100644 --- a/src/collision/broadphase/DynamicAABBTree.cpp +++ b/src/collision/broadphase/DynamicAABBTree.cpp @@ -573,9 +573,9 @@ int32 DynamicAABBTree::balanceSubTreeAtNode(int32 nodeID) { return nodeID; } -/// Take a list of shapes to be tested for broad-phase overlap and return a list of pair of overlapping shapes -void DynamicAABBTree::reportAllShapesOverlappingWithShapes(const List& nodesToTest, size_t startIndex, - size_t endIndex, List>& outOverlappingNodes) const { +/// Take an array of shapes to be tested for broad-phase overlap and return an array of pair of overlapping shapes +void DynamicAABBTree::reportAllShapesOverlappingWithShapes(const Array& nodesToTest, size_t startIndex, + size_t endIndex, Array>& outOverlappingNodes) const { RP3D_PROFILE("DynamicAABBTree::reportAllShapesOverlappingWithAABB()", mProfiler); @@ -609,7 +609,7 @@ void DynamicAABBTree::reportAllShapesOverlappingWithShapes(const List& no // If the node is a leaf if (nodeToVisit->isLeaf()) { - // Add the node in the list of overlapping nodes + // Add the node in the array of overlapping nodes outOverlappingNodes.add(Pair(nodesToTest[i], nodeIDToVisit)); } else { // If the node is not a leaf @@ -626,7 +626,7 @@ void DynamicAABBTree::reportAllShapesOverlappingWithShapes(const List& no } // Report all shapes overlapping with the AABB given in parameter. -void DynamicAABBTree::reportAllShapesOverlappingWithAABB(const AABB& aabb, List& overlappingNodes) const { +void DynamicAABBTree::reportAllShapesOverlappingWithAABB(const AABB& aabb, Array& overlappingNodes) const { RP3D_PROFILE("DynamicAABBTree::reportAllShapesOverlappingWithAABB()", mProfiler); diff --git a/src/collision/narrowphase/CapsuleVsConvexPolyhedronAlgorithm.cpp b/src/collision/narrowphase/CapsuleVsConvexPolyhedronAlgorithm.cpp index 49dcbc0b..b09df90f 100644 --- a/src/collision/narrowphase/CapsuleVsConvexPolyhedronAlgorithm.cpp +++ b/src/collision/narrowphase/CapsuleVsConvexPolyhedronAlgorithm.cpp @@ -59,7 +59,7 @@ bool CapsuleVsConvexPolyhedronAlgorithm::testCollision(NarrowPhaseInfoBatch& nar #endif // Run the GJK algorithm - List gjkResults(memoryAllocator); + Array gjkResults(memoryAllocator); gjkAlgorithm.testCollision(narrowPhaseInfoBatch, batchStartIndex, batchNbItems, gjkResults); assert(gjkResults.size() == batchNbItems); diff --git a/src/collision/narrowphase/GJK/GJKAlgorithm.cpp b/src/collision/narrowphase/GJK/GJKAlgorithm.cpp index 74a0dbaa..d35fd6e0 100644 --- a/src/collision/narrowphase/GJK/GJKAlgorithm.cpp +++ b/src/collision/narrowphase/GJK/GJKAlgorithm.cpp @@ -30,7 +30,7 @@ #include #include #include -#include +#include #include #include #include @@ -48,7 +48,7 @@ using namespace reactphysics3d; /// origin, they we give that simplex polytope to the EPA algorithm which will compute /// the correct penetration depth and contact points between the enlarged objects. void GJKAlgorithm::testCollision(NarrowPhaseInfoBatch& narrowPhaseInfoBatch, uint batchStartIndex, - uint batchNbItems, List& gjkResults) { + uint batchNbItems, Array& gjkResults) { RP3D_PROFILE("GJKAlgorithm::testCollision()", mProfiler); diff --git a/src/collision/narrowphase/SAT/SATAlgorithm.cpp b/src/collision/narrowphase/SAT/SATAlgorithm.cpp index b67c94a2..0d0eccfe 100644 --- a/src/collision/narrowphase/SAT/SATAlgorithm.cpp +++ b/src/collision/narrowphase/SAT/SATAlgorithm.cpp @@ -395,8 +395,8 @@ bool SATAlgorithm::computeCapsulePolyhedronFaceContactPoints(uint referenceFaceI uint firstEdgeIndex = face.edgeIndex; uint edgeIndex = firstEdgeIndex; - List planesPoints(mMemoryAllocator, 2); - List planesNormals(mMemoryAllocator, 2); + Array planesPoints(mMemoryAllocator, 2); + Array planesNormals(mMemoryAllocator, 2); // For each adjacent edge of the separating face of the polyhedron do { @@ -422,7 +422,7 @@ bool SATAlgorithm::computeCapsulePolyhedronFaceContactPoints(uint referenceFaceI } while(edgeIndex != firstEdgeIndex); // First we clip the inner segment of the capsule with the four planes of the adjacent faces - List clipSegment = clipSegmentWithPlanes(capsuleSegAPolyhedronSpace, capsuleSegBPolyhedronSpace, planesPoints, planesNormals, mMemoryAllocator); + Array clipSegment = clipSegmentWithPlanes(capsuleSegAPolyhedronSpace, capsuleSegBPolyhedronSpace, planesPoints, planesNormals, mMemoryAllocator); // Project the two clipped points into the polyhedron face const Vector3 delta = faceNormal * (penetrationDepth - capsuleRadius); @@ -916,9 +916,9 @@ bool SATAlgorithm::computePolyhedronVsPolyhedronFaceContactPoints(bool isMinPene const HalfEdgeStructure::Face& incidentFace = incidentPolyhedron->getFace(incidentFaceIndex); uint32 nbIncidentFaceVertices = incidentFace.faceVertices.size(); - List polygonVertices(mMemoryAllocator, nbIncidentFaceVertices); // Vertices to clip of the incident face - List planesNormals(mMemoryAllocator, nbIncidentFaceVertices); // Normals of the clipping planes - List planesPoints(mMemoryAllocator, nbIncidentFaceVertices); // Points on the clipping planes + Array polygonVertices(mMemoryAllocator, nbIncidentFaceVertices); // Vertices to clip of the incident face + Array planesNormals(mMemoryAllocator, nbIncidentFaceVertices); // Normals of the clipping planes + Array planesPoints(mMemoryAllocator, nbIncidentFaceVertices); // Points on the clipping planes // Get all the vertices of the incident face (in the reference local-space) for (uint32 i=0; i < incidentFace.faceVertices.size(); i++) { @@ -958,7 +958,7 @@ bool SATAlgorithm::computePolyhedronVsPolyhedronFaceContactPoints(bool isMinPene assert(planesNormals.size() == planesPoints.size()); // Clip the reference faces with the adjacent planes of the reference face - List clipPolygonVertices = clipPolygonWithPlanes(polygonVertices, planesPoints, planesNormals, mMemoryAllocator); + Array clipPolygonVertices = clipPolygonWithPlanes(polygonVertices, planesPoints, planesNormals, mMemoryAllocator); // We only keep the clipped points that are below the reference face const Vector3 referenceFaceVertex = referencePolyhedron->getVertexPosition(referencePolyhedron->getHalfEdge(firstEdgeIndex).vertexIndex); diff --git a/src/collision/narrowphase/SphereVsConvexPolyhedronAlgorithm.cpp b/src/collision/narrowphase/SphereVsConvexPolyhedronAlgorithm.cpp index f0c199d5..039aa9bb 100644 --- a/src/collision/narrowphase/SphereVsConvexPolyhedronAlgorithm.cpp +++ b/src/collision/narrowphase/SphereVsConvexPolyhedronAlgorithm.cpp @@ -50,7 +50,7 @@ bool SphereVsConvexPolyhedronAlgorithm::testCollision(NarrowPhaseInfoBatch& narr #endif - List gjkResults(memoryAllocator, batchNbItems); + Array gjkResults(memoryAllocator, batchNbItems); gjkAlgorithm.testCollision(narrowPhaseInfoBatch, batchStartIndex, batchNbItems, gjkResults); assert(gjkResults.size() == batchNbItems); diff --git a/src/collision/shapes/BoxShape.cpp b/src/collision/shapes/BoxShape.cpp index 60f5a2b2..35448295 100644 --- a/src/collision/shapes/BoxShape.cpp +++ b/src/collision/shapes/BoxShape.cpp @@ -56,17 +56,17 @@ BoxShape::BoxShape(const Vector3& halfExtents, MemoryAllocator& allocator) mHalfEdgeStructure.addVertex(7); // Faces - List face0(allocator, 4); + Array face0(allocator, 4); face0.add(0); face0.add(1); face0.add(2); face0.add(3); - List face1(allocator, 4); + Array face1(allocator, 4); face1.add(1); face1.add(5); face1.add(6); face1.add(2); - List face2(allocator, 4); + Array face2(allocator, 4); face2.add(4); face2.add(7); face2.add(6); face2.add(5); - List face3(allocator, 4); + Array face3(allocator, 4); face3.add(4); face3.add(0); face3.add(3); face3.add(7); - List face4(allocator, 4); + Array face4(allocator, 4); face4.add(4); face4.add(5); face4.add(1); face4.add(0); - List face5(allocator, 4); + Array face5(allocator, 4); face5.add(2); face5.add(6); face5.add(7); face5.add(3); mHalfEdgeStructure.addFace(face0); diff --git a/src/collision/shapes/ConcaveMeshShape.cpp b/src/collision/shapes/ConcaveMeshShape.cpp index 766d6d7f..cedb55f2 100644 --- a/src/collision/shapes/ConcaveMeshShape.cpp +++ b/src/collision/shapes/ConcaveMeshShape.cpp @@ -127,8 +127,8 @@ uint ConcaveMeshShape::getNbTriangles(uint subPart) const } // Compute all the triangles of the mesh that are overlapping with the AABB in parameter -void ConcaveMeshShape::computeOverlappingTriangles(const AABB& localAABB, List& triangleVertices, - List& triangleVerticesNormals, List& shapeIds, +void ConcaveMeshShape::computeOverlappingTriangles(const AABB& localAABB, Array& triangleVertices, + Array& triangleVerticesNormals, Array& shapeIds, MemoryAllocator& allocator) const { RP3D_PROFILE("ConcaveMeshShape::computeOverlappingTriangles()", mProfiler); @@ -139,12 +139,12 @@ void ConcaveMeshShape::computeOverlappingTriangles(const AABB& localAABB, List overlappingNodes(allocator, 64); + Array overlappingNodes(allocator, 64); mDynamicAABBTree.reportAllShapesOverlappingWithAABB(aabb, overlappingNodes); const uint32 nbOverlappingNodes = overlappingNodes.size(); - // Add space in the list of triangles vertices/normals for the new triangles + // Add space in the array of triangles vertices/normals for the new triangles triangleVertices.addWithoutInit(nbOverlappingNodes * 3); triangleVerticesNormals.addWithoutInit(nbOverlappingNodes * 3); @@ -230,7 +230,7 @@ decimal ConcaveMeshRaycastCallback::raycastBroadPhaseShape(int32 nodeId, const R // Raycast all collision shapes that have been collected void ConcaveMeshRaycastCallback::raycastTriangles() { - List::Iterator it; + Array::Iterator it; decimal smallestHitFraction = mRay.maxFraction; for (it = mHitAABBNodes.begin(); it != mHitAABBNodes.end(); ++it) { diff --git a/src/collision/shapes/ConvexMeshShape.cpp b/src/collision/shapes/ConvexMeshShape.cpp index 06589d4e..a8912ee2 100644 --- a/src/collision/shapes/ConvexMeshShape.cpp +++ b/src/collision/shapes/ConvexMeshShape.cpp @@ -49,7 +49,7 @@ ConvexMeshShape::ConvexMeshShape(PolyhedronMesh* polyhedronMesh, MemoryAllocator // Return a local support point in a given direction without the object margin. /// If the edges information is not used for collision detection, this method will go through -/// the whole vertices list and pick up the vertex with the largest dot product in the support +/// the whole vertices array and pick up the vertex with the largest dot product in the support /// direction. This is an O(n) process with "n" being the number of vertices in the mesh. /// However, if the edges information is used, we can cache the previous support vertex and use /// it as a start in a hill-climbing (local search) process to find the new support vertex which diff --git a/src/collision/shapes/HeightFieldShape.cpp b/src/collision/shapes/HeightFieldShape.cpp index e6c3f7bb..ff5419a9 100644 --- a/src/collision/shapes/HeightFieldShape.cpp +++ b/src/collision/shapes/HeightFieldShape.cpp @@ -91,8 +91,8 @@ void HeightFieldShape::getLocalBounds(Vector3& min, Vector3& max) const { // of the body when need to test and see against which triangles of the height-field we need // to test for collision. We compute the sub-grid points that are inside the other body's AABB // and then for each rectangle in the sub-grid we generate two triangles that we use to test collision. -void HeightFieldShape::computeOverlappingTriangles(const AABB& localAABB, List& triangleVertices, - List& triangleVerticesNormals, List& shapeIds, +void HeightFieldShape::computeOverlappingTriangles(const AABB& localAABB, Array& triangleVertices, + Array& triangleVerticesNormals, Array& shapeIds, MemoryAllocator& allocator) const { RP3D_PROFILE("HeightFieldShape::computeOverlappingTriangles()", mProfiler); @@ -236,9 +236,9 @@ bool HeightFieldShape::raycast(const Ray& ray, RaycastInfo& raycastInfo, Collide const AABB rayAABB(Vector3::min(ray.point1, rayEnd), Vector3::max(ray.point1, rayEnd)); // Compute the triangles overlapping with the ray AABB - List triangleVertices(allocator, 64); - List triangleVerticesNormals(allocator, 64); - List shapeIds(allocator, 64); + Array triangleVertices(allocator, 64); + Array triangleVerticesNormals(allocator, 64); + Array shapeIds(allocator, 64); computeOverlappingTriangles(rayAABB, triangleVertices, triangleVerticesNormals, shapeIds, allocator); assert(triangleVertices.size() == triangleVerticesNormals.size()); diff --git a/src/components/ColliderComponents.cpp b/src/components/ColliderComponents.cpp index 550acd2f..03cdd37e 100644 --- a/src/components/ColliderComponents.cpp +++ b/src/components/ColliderComponents.cpp @@ -37,7 +37,7 @@ using namespace reactphysics3d; ColliderComponents::ColliderComponents(MemoryAllocator& allocator) :Components(allocator, sizeof(Entity) + sizeof(Entity) + sizeof(Collider*) + sizeof(int32) + sizeof(Transform) + sizeof(CollisionShape*) + sizeof(unsigned short) + - sizeof(unsigned short) + sizeof(Transform) + sizeof(List) + sizeof(bool) + + sizeof(unsigned short) + sizeof(Transform) + sizeof(Array) + sizeof(bool) + sizeof(bool) + sizeof(Material)) { // Allocate memory for the components data @@ -66,7 +66,7 @@ void ColliderComponents::allocate(uint32 nbComponentsToAllocate) { unsigned short* newCollisionCategoryBits = reinterpret_cast(newCollisionShapes + nbComponentsToAllocate); unsigned short* newCollideWithMaskBits = reinterpret_cast(newCollisionCategoryBits + nbComponentsToAllocate); Transform* newLocalToWorldTransforms = reinterpret_cast(newCollideWithMaskBits + nbComponentsToAllocate); - List* newOverlappingPairs = reinterpret_cast*>(newLocalToWorldTransforms + nbComponentsToAllocate); + Array* newOverlappingPairs = reinterpret_cast*>(newLocalToWorldTransforms + nbComponentsToAllocate); bool* hasCollisionShapeChangedSize = reinterpret_cast(newOverlappingPairs + nbComponentsToAllocate); bool* isTrigger = reinterpret_cast(hasCollisionShapeChangedSize + nbComponentsToAllocate); Material* materials = reinterpret_cast(isTrigger + nbComponentsToAllocate); @@ -84,7 +84,7 @@ void ColliderComponents::allocate(uint32 nbComponentsToAllocate) { memcpy(newCollisionCategoryBits, mCollisionCategoryBits, mNbComponents * sizeof(unsigned short)); memcpy(newCollideWithMaskBits, mCollideWithMaskBits, mNbComponents * sizeof(unsigned short)); memcpy(newLocalToWorldTransforms, mLocalToWorldTransforms, mNbComponents * sizeof(Transform)); - memcpy(newOverlappingPairs, mOverlappingPairs, mNbComponents * sizeof(List)); + memcpy(newOverlappingPairs, mOverlappingPairs, mNbComponents * sizeof(Array)); memcpy(hasCollisionShapeChangedSize, mHasCollisionShapeChangedSize, mNbComponents * sizeof(bool)); memcpy(isTrigger, mIsTrigger, mNbComponents * sizeof(bool)); memcpy(materials, mMaterials, mNbComponents * sizeof(Material)); @@ -128,7 +128,7 @@ void ColliderComponents::addComponent(Entity colliderEntity, bool isSleeping, co new (mCollisionCategoryBits + index) unsigned short(component.collisionCategoryBits); new (mCollideWithMaskBits + index) unsigned short(component.collideWithMaskBits); new (mLocalToWorldTransforms + index) Transform(component.localToWorldTransform); - new (mOverlappingPairs + index) List(mMemoryAllocator); + new (mOverlappingPairs + index) Array(mMemoryAllocator); mHasCollisionShapeChangedSize[index] = false; mIsTrigger[index] = false; mMaterials[index] = component.material; @@ -157,7 +157,7 @@ void ColliderComponents::moveComponentToIndex(uint32 srcIndex, uint32 destIndex) new (mCollisionCategoryBits + destIndex) unsigned short(mCollisionCategoryBits[srcIndex]); new (mCollideWithMaskBits + destIndex) unsigned short(mCollideWithMaskBits[srcIndex]); new (mLocalToWorldTransforms + destIndex) Transform(mLocalToWorldTransforms[srcIndex]); - new (mOverlappingPairs + destIndex) List(mOverlappingPairs[srcIndex]); + new (mOverlappingPairs + destIndex) Array(mOverlappingPairs[srcIndex]); mHasCollisionShapeChangedSize[destIndex] = mHasCollisionShapeChangedSize[srcIndex]; mIsTrigger[destIndex] = mIsTrigger[srcIndex]; mMaterials[destIndex] = mMaterials[srcIndex]; @@ -186,7 +186,7 @@ void ColliderComponents::swapComponents(uint32 index1, uint32 index2) { unsigned short collisionCategoryBits1 = mCollisionCategoryBits[index1]; unsigned short collideWithMaskBits1 = mCollideWithMaskBits[index1]; Transform localToWorldTransform1 = mLocalToWorldTransforms[index1]; - List overlappingPairs = mOverlappingPairs[index1]; + Array overlappingPairs = mOverlappingPairs[index1]; bool hasCollisionShapeChangedSize = mHasCollisionShapeChangedSize[index1]; bool isTrigger = mIsTrigger[index1]; Material material = mMaterials[index1]; @@ -206,7 +206,7 @@ void ColliderComponents::swapComponents(uint32 index1, uint32 index2) { new (mCollisionCategoryBits + index2) unsigned short(collisionCategoryBits1); new (mCollideWithMaskBits + index2) unsigned short(collideWithMaskBits1); new (mLocalToWorldTransforms + index2) Transform(localToWorldTransform1); - new (mOverlappingPairs + index2) List(overlappingPairs); + new (mOverlappingPairs + index2) Array(overlappingPairs); mHasCollisionShapeChangedSize[index2] = hasCollisionShapeChangedSize; mIsTrigger[index2] = isTrigger; mMaterials[index2] = material; @@ -234,6 +234,6 @@ void ColliderComponents::destroyComponent(uint32 index) { mLocalToBodyTransforms[index].~Transform(); mCollisionShapes[index] = nullptr; mLocalToWorldTransforms[index].~Transform(); - mOverlappingPairs[index].~List(); + mOverlappingPairs[index].~Array(); mMaterials[index].~Material(); } diff --git a/src/components/CollisionBodyComponents.cpp b/src/components/CollisionBodyComponents.cpp index eeadf088..8aee03db 100644 --- a/src/components/CollisionBodyComponents.cpp +++ b/src/components/CollisionBodyComponents.cpp @@ -34,7 +34,7 @@ using namespace reactphysics3d; // Constructor CollisionBodyComponents::CollisionBodyComponents(MemoryAllocator& allocator) - :Components(allocator, sizeof(Entity) + sizeof(CollisionBody*) + sizeof(List) + + :Components(allocator, sizeof(Entity) + sizeof(CollisionBody*) + sizeof(Array) + sizeof(bool) + sizeof(void*)) { // Allocate memory for the components data @@ -56,7 +56,7 @@ void CollisionBodyComponents::allocate(uint32 nbComponentsToAllocate) { // New pointers to components data Entity* newBodiesEntities = static_cast(newBuffer); CollisionBody** newBodies = reinterpret_cast(newBodiesEntities + nbComponentsToAllocate); - List* newColliders = reinterpret_cast*>(newBodies + nbComponentsToAllocate); + Array* newColliders = reinterpret_cast*>(newBodies + nbComponentsToAllocate); bool* newIsActive = reinterpret_cast(newColliders + nbComponentsToAllocate); void** newUserData = reinterpret_cast(newIsActive + nbComponentsToAllocate); @@ -66,7 +66,7 @@ void CollisionBodyComponents::allocate(uint32 nbComponentsToAllocate) { // Copy component data from the previous buffer to the new one memcpy(newBodiesEntities, mBodiesEntities, mNbComponents * sizeof(Entity)); memcpy(newBodies, mBodies, mNbComponents * sizeof(CollisionBody*)); - memcpy(newColliders, mColliders, mNbComponents * sizeof(List)); + memcpy(newColliders, mColliders, mNbComponents * sizeof(Array)); memcpy(newIsActive, mIsActive, mNbComponents * sizeof(bool)); memcpy(newUserData, mUserData, mNbComponents * sizeof(void*)); @@ -92,7 +92,7 @@ void CollisionBodyComponents::addComponent(Entity bodyEntity, bool isSleeping, c // Insert the new component data new (mBodiesEntities + index) Entity(bodyEntity); mBodies[index] = component.body; - new (mColliders + index) List(mMemoryAllocator); + new (mColliders + index) Array(mMemoryAllocator); mIsActive[index] = true; mUserData[index] = nullptr; @@ -114,7 +114,7 @@ void CollisionBodyComponents::moveComponentToIndex(uint32 srcIndex, uint32 destI // Copy the data of the source component to the destination location new (mBodiesEntities + destIndex) Entity(mBodiesEntities[srcIndex]); mBodies[destIndex] = mBodies[srcIndex]; - new (mColliders + destIndex) List(mColliders[srcIndex]); + new (mColliders + destIndex) Array(mColliders[srcIndex]); mIsActive[destIndex] = mIsActive[srcIndex]; mUserData[destIndex] = mUserData[srcIndex]; @@ -135,7 +135,7 @@ void CollisionBodyComponents::swapComponents(uint32 index1, uint32 index2) { // Copy component 1 data Entity entity1(mBodiesEntities[index1]); CollisionBody* body1 = mBodies[index1]; - List colliders1(mColliders[index1]); + Array colliders1(mColliders[index1]); bool isActive1 = mIsActive[index1]; void* userData1 = mUserData[index1]; @@ -146,7 +146,7 @@ void CollisionBodyComponents::swapComponents(uint32 index1, uint32 index2) { // Reconstruct component 1 at component 2 location new (mBodiesEntities + index2) Entity(entity1); - new (mColliders + index2) List(colliders1); + new (mColliders + index2) Array(colliders1); mBodies[index2] = body1; mIsActive[index2] = isActive1; mUserData[index2] = userData1; @@ -170,6 +170,6 @@ void CollisionBodyComponents::destroyComponent(uint32 index) { mBodiesEntities[index].~Entity(); mBodies[index] = nullptr; - mColliders[index].~List(); + mColliders[index].~Array(); mUserData[index] = nullptr; } diff --git a/src/components/RigidBodyComponents.cpp b/src/components/RigidBodyComponents.cpp index 6d2a389a..ce851b4d 100644 --- a/src/components/RigidBodyComponents.cpp +++ b/src/components/RigidBodyComponents.cpp @@ -43,7 +43,7 @@ RigidBodyComponents::RigidBodyComponents(MemoryAllocator& allocator) sizeof(Vector3) + + sizeof(Matrix3x3) + sizeof(Vector3) + sizeof(Vector3) + sizeof(Vector3) + sizeof(Vector3) + sizeof(Vector3) + sizeof(Quaternion) + sizeof(Vector3) + sizeof(Vector3) + - sizeof(bool) + sizeof(bool) + sizeof(List) + sizeof(List)) { + sizeof(bool) + sizeof(bool) + sizeof(Array) + sizeof(Array)) { // Allocate memory for the components data allocate(INIT_NB_ALLOCATED_COMPONENTS); @@ -89,8 +89,8 @@ void RigidBodyComponents::allocate(uint32 nbComponentsToAllocate) { Vector3* newCentersOfMassWorld = reinterpret_cast(newCentersOfMassLocal + nbComponentsToAllocate); bool* newIsGravityEnabled = reinterpret_cast(newCentersOfMassWorld + nbComponentsToAllocate); bool* newIsAlreadyInIsland = reinterpret_cast(newIsGravityEnabled + nbComponentsToAllocate); - List* newJoints = reinterpret_cast*>(newIsAlreadyInIsland + nbComponentsToAllocate); - List* newContactPairs = reinterpret_cast*>(newJoints + nbComponentsToAllocate); + Array* newJoints = reinterpret_cast*>(newIsAlreadyInIsland + nbComponentsToAllocate); + Array* newContactPairs = reinterpret_cast*>(newJoints + nbComponentsToAllocate); // If there was already components before if (mNbComponents > 0) { @@ -123,8 +123,8 @@ void RigidBodyComponents::allocate(uint32 nbComponentsToAllocate) { memcpy(newCentersOfMassWorld, mCentersOfMassWorld, mNbComponents * sizeof(Vector3)); memcpy(newIsGravityEnabled, mIsGravityEnabled, mNbComponents * sizeof(bool)); memcpy(newIsAlreadyInIsland, mIsAlreadyInIsland, mNbComponents * sizeof(bool)); - memcpy(newJoints, mJoints, mNbComponents * sizeof(List)); - memcpy(newContactPairs, mContactPairs, mNbComponents * sizeof(List)); + memcpy(newJoints, mJoints, mNbComponents * sizeof(Array)); + memcpy(newContactPairs, mContactPairs, mNbComponents * sizeof(Array)); // Deallocate previous memory mMemoryAllocator.release(mBuffer, mNbAllocatedComponents * mComponentDataSize); @@ -197,8 +197,8 @@ void RigidBodyComponents::addComponent(Entity bodyEntity, bool isSleeping, const new (mCentersOfMassWorld + index) Vector3(component.worldPosition); mIsGravityEnabled[index] = true; mIsAlreadyInIsland[index] = false; - new (mJoints + index) List(mMemoryAllocator); - new (mContactPairs + index) List(mMemoryAllocator); + new (mJoints + index) Array(mMemoryAllocator); + new (mContactPairs + index) Array(mMemoryAllocator); // Map the entity with the new component lookup index mMapEntityToComponentIndex.add(Pair(bodyEntity, index)); @@ -243,8 +243,8 @@ void RigidBodyComponents::moveComponentToIndex(uint32 srcIndex, uint32 destIndex new (mCentersOfMassWorld + destIndex) Vector3(mCentersOfMassWorld[srcIndex]); mIsGravityEnabled[destIndex] = mIsGravityEnabled[srcIndex]; mIsAlreadyInIsland[destIndex] = mIsAlreadyInIsland[srcIndex]; - new (mJoints + destIndex) List(mJoints[srcIndex]); - new (mContactPairs + destIndex) List(mContactPairs[srcIndex]); + new (mJoints + destIndex) Array(mJoints[srcIndex]); + new (mContactPairs + destIndex) Array(mContactPairs[srcIndex]); // Destroy the source component destroyComponent(srcIndex); @@ -288,8 +288,8 @@ void RigidBodyComponents::swapComponents(uint32 index1, uint32 index2) { Vector3 centerOfMassWorld1 = mCentersOfMassWorld[index1]; bool isGravityEnabled1 = mIsGravityEnabled[index1]; bool isAlreadyInIsland1 = mIsAlreadyInIsland[index1]; - List joints1 = mJoints[index1]; - List contactPairs1 = mContactPairs[index1]; + Array joints1 = mJoints[index1]; + Array contactPairs1 = mContactPairs[index1]; // Destroy component 1 destroyComponent(index1); @@ -324,8 +324,8 @@ void RigidBodyComponents::swapComponents(uint32 index1, uint32 index2) { mCentersOfMassWorld[index2] = centerOfMassWorld1; mIsGravityEnabled[index2] = isGravityEnabled1; mIsAlreadyInIsland[index2] = isAlreadyInIsland1; - new (mJoints + index2) List(joints1); - new (mContactPairs + index2) List(contactPairs1); + new (mJoints + index2) Array(joints1); + new (mContactPairs + index2) Array(contactPairs1); // Update the entity to component index mapping mMapEntityToComponentIndex.add(Pair(entity1, index2)); @@ -361,6 +361,6 @@ void RigidBodyComponents::destroyComponent(uint32 index) { mConstrainedOrientations[index].~Quaternion(); mCentersOfMassLocal[index].~Vector3(); mCentersOfMassWorld[index].~Vector3(); - mJoints[index].~List(); - mContactPairs[index].~List(); + mJoints[index].~Array(); + mContactPairs[index].~Array(); } diff --git a/src/engine/PhysicsWorld.cpp b/src/engine/PhysicsWorld.cpp index f7ad6433..c1b6ff1e 100644 --- a/src/engine/PhysicsWorld.cpp +++ b/src/engine/PhysicsWorld.cpp @@ -217,7 +217,7 @@ void PhysicsWorld::destroyCollisionBody(CollisionBody* collisionBody) { // Call the destructor of the collision body collisionBody->~CollisionBody(); - // Remove the collision body from the list of bodies + // Remove the collision body from the array of bodies mCollisionBodies.remove(collisionBody); // Free the object from the memory allocator @@ -238,7 +238,7 @@ void PhysicsWorld::setBodyDisabled(Entity bodyEntity, bool isDisabled) { mRigidBodyComponents.setIsEntityDisabled(bodyEntity, isDisabled); // For each collider of the body - const List& collidersEntities = mCollisionBodyComponents.getColliders(bodyEntity); + const Array& collidersEntities = mCollisionBodyComponents.getColliders(bodyEntity); for (uint32 i=0; i < collidersEntities.size(); i++) { mCollidersComponents.setIsEntityDisabled(collidersEntities[i], isDisabled); @@ -246,7 +246,7 @@ void PhysicsWorld::setBodyDisabled(Entity bodyEntity, bool isDisabled) { // Disable the joints of the body if necessary // For each joint of the body - const List& joints = mRigidBodyComponents.getJoints(bodyEntity); + const Array& joints = mRigidBodyComponents.getJoints(bodyEntity); for(uint32 i=0; i < joints.size(); i++) { const Entity body1Entity = mJointsComponents.getBody1Entity(joints[i]); @@ -518,7 +518,7 @@ void PhysicsWorld::destroyRigidBody(RigidBody* rigidBody) { rigidBody->removeAllColliders(); // Destroy all the joints in which the rigid body to be destroyed is involved - const List& joints = mRigidBodyComponents.getJoints(rigidBody->getEntity()); + const Array& joints = mRigidBodyComponents.getJoints(rigidBody->getEntity()); for (uint32 i=0; i < joints.size(); i++) { destroyJoint(mJointsComponents.getJoint(joints[i])); } @@ -532,7 +532,7 @@ void PhysicsWorld::destroyRigidBody(RigidBody* rigidBody) { // Call the destructor of the rigid body rigidBody->~RigidBody(); - // Remove the rigid body from the list of rigid bodies + // Remove the rigid body from the array of rigid bodies mRigidBodies.remove(rigidBody); // Free the object from the memory allocator @@ -656,7 +656,7 @@ Joint* PhysicsWorld::createJoint(const JointInfo& jointInfo) { RP3D_LOG(mConfig.worldName, Logger::Level::Information, Logger::Category::Joint, "Joint " + std::to_string(newJoint->getEntity().id) + ": " + newJoint->to_string(), __FILE__, __LINE__); - // Add the joint into the joint list of the bodies involved in the joint + // Add the joint into the joint array of the bodies involved in the joint addJointToBodies(jointInfo.body1->getEntity(), jointInfo.body2->getEntity(), entity); // Return the pointer to the created joint @@ -688,7 +688,7 @@ void PhysicsWorld::destroyJoint(Joint* joint) { body1->setIsSleeping(false); body2->setIsSleeping(false); - // Remove the joint from the joint list of the bodies involved in the joint + // Remove the joint from the joint array of the bodies involved in the joint mRigidBodyComponents.removeJointFromBody(body1->getEntity(), joint->getEntity()); mRigidBodyComponents.removeJointFromBody(body2->getEntity(), joint->getEntity()); @@ -732,7 +732,7 @@ void PhysicsWorld::setNbIterationsVelocitySolver(uint nbIterations) { "Physics World: Set nb iterations velocity solver to " + std::to_string(nbIterations), __FILE__, __LINE__); } -// Add the joint to the list of joints of the two bodies involved in the joint +// Add the joint to the array of joints of the two bodies involved in the joint void PhysicsWorld::addJointToBodies(Entity body1, Entity body2, Entity joint) { mRigidBodyComponents.addJointToBody(body1, joint); @@ -777,8 +777,8 @@ void PhysicsWorld::createIslands() { // Create a stack for the bodies to visit during the Depth First Search Stack bodyEntitiesToVisit(mMemoryManager.getSingleFrameAllocator(), mIslands.getNbMaxBodiesInIslandPreviousFrame()); - // List of static bodies added to the current island (used to reset the isAlreadyInIsland variable of static bodies) - List staticBodiesAddedToIsland(mMemoryManager.getSingleFrameAllocator(), 16); + // Array of static bodies added to the current island (used to reset the isAlreadyInIsland variable of static bodies) + Array staticBodiesAddedToIsland(mMemoryManager.getSingleFrameAllocator(), 16); uint nbTotalManifolds = 0; @@ -862,13 +862,13 @@ void PhysicsWorld::createIslands() { } else { - // Add the contact pair index in the list of contact pairs that won't be part of islands + // Add the contact pair index in the array of contact pairs that won't be part of islands pair.isAlreadyInIsland = true; } } // For each joint in which the current body is involved - const List& joints = mRigidBodyComponents.getJoints(rigidBodyToVisit->getEntity()); + const Array& joints = mRigidBodyComponents.getJoints(rigidBodyToVisit->getEntity()); for (uint32 i=0; i < joints.size(); i++) { // Check if the current joint has already been added into an island @@ -981,7 +981,7 @@ void PhysicsWorld::enableSleeping(bool isSleepingEnabled) { if (!mIsSleepingEnabled) { // For each body of the world - List::Iterator it; + Array::Iterator it; for (it = mRigidBodies.begin(); it != mRigidBodies.end(); ++it) { // Wake up the rigid body diff --git a/src/mathematics/mathematics_functions.cpp b/src/mathematics/mathematics_functions.cpp index 4db9367c..a237a6d2 100755 --- a/src/mathematics/mathematics_functions.cpp +++ b/src/mathematics/mathematics_functions.cpp @@ -221,14 +221,14 @@ decimal reactphysics3d::computePointToLineDistance(const Vector3& linePointA, co // Clip a segment against multiple planes and return the clipped segment vertices // This method implements the Sutherland–Hodgman clipping algorithm -List reactphysics3d::clipSegmentWithPlanes(const Vector3& segA, const Vector3& segB, - const List& planesPoints, - const List& planesNormals, +Array reactphysics3d::clipSegmentWithPlanes(const Vector3& segA, const Vector3& segB, + const Array& planesPoints, + const Array& planesNormals, MemoryAllocator& allocator) { assert(planesPoints.size() == planesNormals.size()); - List inputVertices(allocator, 2); - List outputVertices(allocator, 2); + Array inputVertices(allocator, 2); + Array outputVertices(allocator, 2); inputVertices.add(segA); inputVertices.add(segB); @@ -296,14 +296,14 @@ List reactphysics3d::clipSegmentWithPlanes(const Vector3& segA, const V // Clip a polygon against multiple planes and return the clipped polygon vertices // This method implements the Sutherland–Hodgman clipping algorithm -List reactphysics3d::clipPolygonWithPlanes(const List& polygonVertices, const List& planesPoints, - const List& planesNormals, MemoryAllocator& allocator) { +Array reactphysics3d::clipPolygonWithPlanes(const Array& polygonVertices, const Array& planesPoints, + const Array& planesNormals, MemoryAllocator& allocator) { assert(planesPoints.size() == planesNormals.size()); uint32 nbMaxElements = polygonVertices.size() + planesPoints.size(); - List inputVertices(allocator, nbMaxElements); - List outputVertices(allocator, nbMaxElements); + Array inputVertices(allocator, nbMaxElements); + Array outputVertices(allocator, nbMaxElements); inputVertices.addRange(polygonVertices); diff --git a/src/systems/BroadPhaseSystem.cpp b/src/systems/BroadPhaseSystem.cpp index 8c1326a6..a66a760b 100644 --- a/src/systems/BroadPhaseSystem.cpp +++ b/src/systems/BroadPhaseSystem.cpp @@ -206,12 +206,12 @@ void BroadPhaseSystem::addMovedCollider(int broadPhaseID, Collider* collider) { } // Compute all the overlapping pairs of collision shapes -void BroadPhaseSystem::computeOverlappingPairs(MemoryManager& memoryManager, List>& overlappingNodes) { +void BroadPhaseSystem::computeOverlappingPairs(MemoryManager& memoryManager, Array>& overlappingNodes) { RP3D_PROFILE("BroadPhaseSystem::computeOverlappingPairs()", mProfiler); - // Get the list of the colliders that have moved or have been created in the last frame - List shapesToTest = mMovedShapes.toList(memoryManager.getHeapAllocator()); + // Get the array of the colliders that have moved or have been created in the last frame + Array shapesToTest = mMovedShapes.toArray(memoryManager.getHeapAllocator()); // Ask the dynamic AABB tree to report all collision shapes that overlap with the shapes to test mDynamicAABBTree.reportAllShapesOverlappingWithShapes(shapesToTest, 0, shapesToTest.size(), overlappingNodes); diff --git a/src/systems/CollisionDetectionSystem.cpp b/src/systems/CollisionDetectionSystem.cpp index 756acda1..247d8467 100644 --- a/src/systems/CollisionDetectionSystem.cpp +++ b/src/systems/CollisionDetectionSystem.cpp @@ -195,8 +195,8 @@ void CollisionDetectionSystem::addLostContactPair(OverlappingPairs::OverlappingP mLostContactPairs.add(lostContactPair); } -// Take a list of overlapping nodes in the broad-phase and create new overlapping pairs if necessary -void CollisionDetectionSystem::updateOverlappingPairs(const List>& overlappingNodes) { +// Take an array of overlapping nodes in the broad-phase and create new overlapping pairs if necessary +void CollisionDetectionSystem::updateOverlappingPairs(const Array>& overlappingNodes) { RP3D_PROFILE("CollisionDetectionSystem::updateOverlappingPairs()", mProfiler); @@ -356,7 +356,7 @@ void CollisionDetectionSystem::computeMiddlePhase(NarrowPhaseInput& narrowPhaseI } // Compute the middle-phase collision detection -void CollisionDetectionSystem::computeMiddlePhaseCollisionSnapshot(List& convexPairs, List& concavePairs, +void CollisionDetectionSystem::computeMiddlePhaseCollisionSnapshot(Array& convexPairs, Array& concavePairs, NarrowPhaseInput& narrowPhaseInput, bool reportContacts) { RP3D_PROFILE("CollisionDetectionSystem::computeMiddlePhase()", mProfiler); @@ -454,9 +454,9 @@ void CollisionDetectionSystem::computeConvexVsConcaveMiddlePhase(OverlappingPair convexShape->computeAABB(aabb, convexToConcaveTransform); // Compute the concave shape triangles that are overlapping with the convex mesh AABB - List triangleVertices(allocator, 64); - List triangleVerticesNormals(allocator, 64); - List shapeIds(allocator, 64); + Array triangleVertices(allocator, 64); + Array triangleVerticesNormals(allocator, 64); + Array shapeIds(allocator, 64); concaveShape->computeOverlappingTriangles(aabb, triangleVertices, triangleVerticesNormals, shapeIds, allocator); assert(triangleVertices.size() == triangleVerticesNormals.size()); @@ -559,9 +559,9 @@ bool CollisionDetectionSystem::testNarrowPhaseCollision(NarrowPhaseInput& narrow // Process the potential contacts after narrow-phase collision detection void CollisionDetectionSystem::processAllPotentialContacts(NarrowPhaseInput& narrowPhaseInput, bool updateLastFrameInfo, - List& potentialContactPoints, - List& potentialContactManifolds, - List* contactPairs) { + Array& potentialContactPoints, + Array& potentialContactManifolds, + Array* contactPairs) { assert(contactPairs->size() == 0); @@ -592,7 +592,7 @@ void CollisionDetectionSystem::computeNarrowPhase() { MemoryAllocator& allocator = mMemoryManager.getSingleFrameAllocator(); - // Swap the previous and current contacts lists + // Swap the previous and current contacts arrays swapPreviousAndCurrentContacts(); mPotentialContactManifolds.reserve(mPreviousContactManifolds->size()); @@ -665,8 +665,8 @@ bool CollisionDetectionSystem::computeNarrowPhaseOverlapSnapshot(NarrowPhaseInpu if (collisionFound && callback != nullptr) { // Compute the overlapping colliders - List contactPairs(allocator); - List lostContactPairs(allocator); // Always empty in this case (snapshot) + Array contactPairs(allocator); + Array lostContactPairs(allocator); // Always empty in this case (snapshot) computeOverlapSnapshotContactPairs(narrowPhaseInput, contactPairs); // Report overlapping colliders @@ -678,7 +678,7 @@ bool CollisionDetectionSystem::computeNarrowPhaseOverlapSnapshot(NarrowPhaseInpu } // Process the potential overlapping bodies for the testOverlap() methods -void CollisionDetectionSystem::computeOverlapSnapshotContactPairs(NarrowPhaseInput& narrowPhaseInput, List& contactPairs) const { +void CollisionDetectionSystem::computeOverlapSnapshotContactPairs(NarrowPhaseInput& narrowPhaseInput, Array& contactPairs) const { Set setOverlapContactPairId(mMemoryManager.getHeapAllocator()); @@ -703,7 +703,7 @@ void CollisionDetectionSystem::computeOverlapSnapshotContactPairs(NarrowPhaseInp void CollisionDetectionSystem::notifyOverlappingPairsToTestOverlap(Collider* collider) { // Get the overlapping pairs involved with this collider - List& overlappingPairs = mCollidersComponents.getOverlappingPairs(collider->getEntity()); + Array& overlappingPairs = mCollidersComponents.getOverlappingPairs(collider->getEntity()); const uint32 nbPairs = overlappingPairs.size(); for (uint32 i=0; i < nbPairs; i++) { @@ -714,7 +714,7 @@ void CollisionDetectionSystem::notifyOverlappingPairsToTestOverlap(Collider* col } // Convert the potential overlapping bodies for the testOverlap() methods -void CollisionDetectionSystem::computeOverlapSnapshotContactPairs(NarrowPhaseInfoBatch& narrowPhaseInfoBatch, List& contactPairs, +void CollisionDetectionSystem::computeOverlapSnapshotContactPairs(NarrowPhaseInfoBatch& narrowPhaseInfoBatch, Array& contactPairs, Set& setOverlapContactPairId) const { RP3D_PROFILE("CollisionDetectionSystem::computeSnapshotContactPairs()", mProfiler); @@ -765,12 +765,12 @@ bool CollisionDetectionSystem::computeNarrowPhaseCollisionSnapshot(NarrowPhaseIn // If collision has been found, create contacts if (collisionFound) { - List potentialContactPoints(allocator); - List potentialContactManifolds(allocator); - List contactPairs(allocator); - List lostContactPairs(allocator); // Not used during collision snapshots - List contactManifolds(allocator); - List contactPoints(allocator); + Array potentialContactPoints(allocator); + Array potentialContactManifolds(allocator); + Array contactPairs(allocator); + Array lostContactPairs(allocator); // Not used during collision snapshots + Array contactManifolds(allocator); + Array contactPoints(allocator); // Process all the potential contacts after narrow-phase collision processAllPotentialContacts(narrowPhaseInput, true, potentialContactPoints, potentialContactManifolds, &contactPairs); @@ -788,7 +788,7 @@ bool CollisionDetectionSystem::computeNarrowPhaseCollisionSnapshot(NarrowPhaseIn return collisionFound; } -// Swap the previous and current contacts lists +// Swap the previous and current contacts arrays void CollisionDetectionSystem::swapPreviousAndCurrentContacts() { if (mPreviousContactPairs == &mContactPairs1) { @@ -927,11 +927,11 @@ void CollisionDetectionSystem::computeLostContactPairs() { } // Create the actual contact manifolds and contacts points for testCollision() methods -void CollisionDetectionSystem::createSnapshotContacts(List& contactPairs, - List& contactManifolds, - List& contactPoints, - List& potentialContactManifolds, - List& potentialContactPoints) { +void CollisionDetectionSystem::createSnapshotContacts(Array& contactPairs, + Array& contactManifolds, + Array& contactPoints, + Array& potentialContactManifolds, + Array& potentialContactPoints) { RP3D_PROFILE("CollisionDetectionSystem::createSnapshotContacts()", mProfiler); @@ -1078,10 +1078,10 @@ void CollisionDetectionSystem::removeCollider(Collider* collider) { assert(mMapBroadPhaseIdToColliderEntity.containsKey(colliderBroadPhaseId)); // Remove all the overlapping pairs involving this collider - List& overlappingPairs = mCollidersComponents.getOverlappingPairs(collider->getEntity()); + Array& overlappingPairs = mCollidersComponents.getOverlappingPairs(collider->getEntity()); while(overlappingPairs.size() > 0) { - // TODO : Remove all the contact manifold of the overlapping pair from the contact manifolds list of the two bodies involved + // TODO : Remove all the contact manifold of the overlapping pair from the contact manifolds array of the two bodies involved // Remove the overlapping pair mOverlappingPairs.removePair(overlappingPairs[0]); @@ -1109,10 +1109,10 @@ void CollisionDetectionSystem::raycast(RaycastCallback* raycastCallback, // Convert the potential contact into actual contacts void CollisionDetectionSystem::processPotentialContacts(NarrowPhaseInfoBatch& narrowPhaseInfoBatch, bool updateLastFrameInfo, - List& potentialContactPoints, - List& potentialContactManifolds, + Array& potentialContactPoints, + Array& potentialContactManifolds, Map& mapPairIdToContactPairIndex, - List* contactPairs) { + Array* contactPairs) { RP3D_PROFILE("CollisionDetectionSystem::processPotentialContacts()", mProfiler); @@ -1183,7 +1183,7 @@ void CollisionDetectionSystem::processPotentialContacts(NarrowPhaseInfoBatch& na contactManifoldInfo.potentialContactPointsIndices[contactManifoldInfo.nbPotentialContactPoints] = contactPointIndexStart + j; contactManifoldInfo.nbPotentialContactPoints++; - // Add the contact point to the list of potential contact points + // Add the contact point to the array of potential contact points const ContactPointInfo& contactPoint = narrowPhaseInfoBatch.narrowPhaseInfos[i].contactPoints[j]; potentialContactPoints.add(contactPoint); @@ -1230,7 +1230,7 @@ void CollisionDetectionSystem::processPotentialContacts(NarrowPhaseInfoBatch& na const ContactPointInfo& contactPoint = narrowPhaseInfoBatch.narrowPhaseInfos[i].contactPoints[j]; - // Add the contact point to the list of potential contact points + // Add the contact point to the array of potential contact points const uint32 contactPointIndex = potentialContactPoints.size(); potentialContactPoints.add(contactPoint); @@ -1294,9 +1294,9 @@ void CollisionDetectionSystem::processPotentialContacts(NarrowPhaseInfoBatch& na } // Clear the obsolete manifolds and contact points and reduce the number of contacts points of the remaining manifolds -void CollisionDetectionSystem::reducePotentialContactManifolds(List* contactPairs, - List& potentialContactManifolds, - const List& potentialContactPoints) const { +void CollisionDetectionSystem::reducePotentialContactManifolds(Array* contactPairs, + Array& potentialContactManifolds, + const Array& potentialContactPoints) const { RP3D_PROFILE("CollisionDetectionSystem::reducePotentialContactManifolds()", mProfiler); @@ -1357,7 +1357,7 @@ void CollisionDetectionSystem::reducePotentialContactManifolds(List // Return the largest depth of all the contact points of a potential manifold decimal CollisionDetectionSystem::computePotentialManifoldLargestContactDepth(const ContactManifoldInfo& manifold, - const List& potentialContactPoints) const { + const Array& potentialContactPoints) const { decimal largestDepth = 0.0f; @@ -1379,15 +1379,15 @@ decimal CollisionDetectionSystem::computePotentialManifoldLargestContactDepth(co // "Contacts Creation" GDC presentation. This method will reduce the number of // contact points to a maximum of 4 points (but it can be less). void CollisionDetectionSystem::reduceContactPoints(ContactManifoldInfo& manifold, const Transform& shape1ToWorldTransform, - const List& potentialContactPoints) const { + const Array& potentialContactPoints) const { assert(manifold.nbPotentialContactPoints > MAX_CONTACT_POINTS_IN_MANIFOLD); // The following algorithm only works to reduce to a maximum of 4 contact points assert(MAX_CONTACT_POINTS_IN_MANIFOLD == 4); - // List of the candidate contact points indices in the manifold. Every time that we have found a - // point we want to keep, we will remove it from this list + // Array of the candidate contact points indices in the manifold. Every time that we have found a + // point we want to keep, we will remove it from this array uint candidatePointsIndices[NB_MAX_CONTACT_POINTS_IN_POTENTIAL_MANIFOLD]; uint8 nbCandidatePoints = manifold.nbPotentialContactPoints; for (uint8 i=0 ; i < manifold.nbPotentialContactPoints; i++) { @@ -1589,8 +1589,8 @@ void CollisionDetectionSystem::reportContactsAndTriggers() { } // Report all contacts to the user -void CollisionDetectionSystem::reportContacts(CollisionCallback& callback, List* contactPairs, - List* manifolds, List* contactPoints, List& lostContactPairs) { +void CollisionDetectionSystem::reportContacts(CollisionCallback& callback, Array* contactPairs, + Array* manifolds, Array* contactPoints, Array& lostContactPairs) { RP3D_PROFILE("CollisionDetectionSystem::reportContacts()", mProfiler); @@ -1605,7 +1605,7 @@ void CollisionDetectionSystem::reportContacts(CollisionCallback& callback, List< } // Report all triggers to the user -void CollisionDetectionSystem::reportTriggers(EventListener& eventListener, List* contactPairs, List& lostContactPairs) { +void CollisionDetectionSystem::reportTriggers(EventListener& eventListener, Array* contactPairs, Array& lostContactPairs) { RP3D_PROFILE("CollisionDetectionSystem::reportTriggers()", mProfiler); @@ -1620,7 +1620,7 @@ void CollisionDetectionSystem::reportTriggers(EventListener& eventListener, List } // Report all contacts for debug rendering -void CollisionDetectionSystem::reportDebugRenderingContacts(List* contactPairs, List* manifolds, List* contactPoints, List& lostContactPairs) { +void CollisionDetectionSystem::reportDebugRenderingContacts(Array* contactPairs, Array* manifolds, Array* contactPoints, Array& lostContactPairs) { RP3D_PROFILE("CollisionDetectionSystem::reportDebugRenderingContacts()", mProfiler); @@ -1643,8 +1643,8 @@ bool CollisionDetectionSystem::testOverlap(CollisionBody* body1, CollisionBody* computeBroadPhase(); // Filter the overlapping pairs to get only the ones with the selected body involved - List convexPairs(mMemoryManager.getPoolAllocator()); - List concavePairs(mMemoryManager.getPoolAllocator()); + Array convexPairs(mMemoryManager.getPoolAllocator()); + Array concavePairs(mMemoryManager.getPoolAllocator()); filterOverlappingPairs(body1->getEntity(), body2->getEntity(), convexPairs, concavePairs); if (convexPairs.size() > 0 || concavePairs.size() > 0) { @@ -1683,8 +1683,8 @@ void CollisionDetectionSystem::testOverlap(CollisionBody* body, OverlapCallback& computeBroadPhase(); // Filter the overlapping pairs to get only the ones with the selected body involved - List convexPairs(mMemoryManager.getPoolAllocator()); - List concavePairs(mMemoryManager.getPoolAllocator()); + Array convexPairs(mMemoryManager.getPoolAllocator()); + Array concavePairs(mMemoryManager.getPoolAllocator()); filterOverlappingPairs(body->getEntity(), convexPairs, concavePairs); if (convexPairs.size() > 0 || concavePairs.size() > 0) { @@ -1706,8 +1706,8 @@ void CollisionDetectionSystem::testCollision(CollisionBody* body1, CollisionBody computeBroadPhase(); // Filter the overlapping pairs to get only the ones with the selected body involved - List convexPairs(mMemoryManager.getPoolAllocator()); - List concavePairs(mMemoryManager.getPoolAllocator()); + Array convexPairs(mMemoryManager.getPoolAllocator()); + Array concavePairs(mMemoryManager.getPoolAllocator()); filterOverlappingPairs(body1->getEntity(), body2->getEntity(), convexPairs, concavePairs); if (convexPairs.size() > 0 || concavePairs.size() > 0) { @@ -1729,8 +1729,8 @@ void CollisionDetectionSystem::testCollision(CollisionBody* body, CollisionCallb computeBroadPhase(); // Filter the overlapping pairs to get only the ones with the selected body involved - List convexPairs(mMemoryManager.getPoolAllocator()); - List concavePairs(mMemoryManager.getPoolAllocator()); + Array convexPairs(mMemoryManager.getPoolAllocator()); + Array concavePairs(mMemoryManager.getPoolAllocator()); filterOverlappingPairs(body->getEntity(), convexPairs, concavePairs); if (convexPairs.size() > 0 || concavePairs.size() > 0) { @@ -1759,7 +1759,7 @@ void CollisionDetectionSystem::testCollision(CollisionCallback& callback) { } // Filter the overlapping pairs to keep only the pairs where a given body is involved -void CollisionDetectionSystem::filterOverlappingPairs(Entity bodyEntity, List& convexPairs, List& concavePairs) const { +void CollisionDetectionSystem::filterOverlappingPairs(Entity bodyEntity, Array& convexPairs, Array& concavePairs) const { // For each convex pairs const uint32 nbConvexPairs = mOverlappingPairs.mConvexPairs.size(); @@ -1785,7 +1785,7 @@ void CollisionDetectionSystem::filterOverlappingPairs(Entity bodyEntity, List& convexPairs, List& concavePairs) const { +void CollisionDetectionSystem::filterOverlappingPairs(Entity body1Entity, Entity body2Entity, Array& convexPairs, Array& concavePairs) const { // For each convex pair const uint32 nbConvexPairs = mOverlappingPairs.mConvexPairs.size(); diff --git a/src/systems/ContactSolverSystem.cpp b/src/systems/ContactSolverSystem.cpp index 40220359..18139b8f 100644 --- a/src/systems/ContactSolverSystem.cpp +++ b/src/systems/ContactSolverSystem.cpp @@ -62,7 +62,7 @@ ContactSolverSystem::ContactSolverSystem(MemoryManager& memoryManager, PhysicsWo } // Initialize the contact constraints -void ContactSolverSystem::init(List* contactManifolds, List* contactPoints, decimal timeStep) { +void ContactSolverSystem::init(Array* contactManifolds, Array* contactPoints, decimal timeStep) { mAllContactManifolds = contactManifolds; mAllContactPoints = contactPoints; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 804f7cb8..c125f2ac 100755 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -15,7 +15,7 @@ set (RP3D_TESTS_HEADERS "tests/collision/TestPointInside.h" "tests/collision/TestRaycast.h" "tests/collision/TestTriangleVertexArray.h" - "tests/containers/TestList.h" + "tests/containers/TestArray.h" "tests/containers/TestMap.h" "tests/containers/TestSet.h" "tests/containers/TestStack.h" diff --git a/test/main.cpp b/test/main.cpp index 3d2ba550..0346ee57 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -39,7 +39,7 @@ #include "tests/collision/TestDynamicAABBTree.h" #include "tests/collision/TestHalfEdgeStructure.h" #include "tests/collision/TestTriangleVertexArray.h" -#include "tests/containers/TestList.h" +#include "tests/containers/TestArray.h" #include "tests/containers/TestMap.h" #include "tests/containers/TestSet.h" #include "tests/containers/TestDeque.h" @@ -54,7 +54,7 @@ int main() { // ---------- Containers tests ---------- // testSuite.addTest(new TestSet("Set")); - testSuite.addTest(new TestList("List")); + testSuite.addTest(new TestArray("Array")); testSuite.addTest(new TestMap("Map")); testSuite.addTest(new TestDeque("Deque")); testSuite.addTest(new TestStack("Stack")); diff --git a/test/tests/collision/TestDynamicAABBTree.h b/test/tests/collision/TestDynamicAABBTree.h index 69b876be..22fc4155 100755 --- a/test/tests/collision/TestDynamicAABBTree.h +++ b/test/tests/collision/TestDynamicAABBTree.h @@ -126,7 +126,7 @@ class TestDynamicAABBTree : public Test { } - bool isOverlapping(int nodeId, const List& overlappingNodes) const { + bool isOverlapping(int nodeId, const Array& overlappingNodes) const { return std::find(overlappingNodes.begin(), overlappingNodes.end(), nodeId) != overlappingNodes.end(); } @@ -223,7 +223,7 @@ class TestDynamicAABBTree : public Test { // ---------- Tests ---------- // - List overlappingNodes(mAllocator); + Array overlappingNodes(mAllocator); // AABB overlapping nothing overlappingNodes.clear(); diff --git a/test/tests/collision/TestHalfEdgeStructure.h b/test/tests/collision/TestHalfEdgeStructure.h index 447c1c97..b6f7e834 100644 --- a/test/tests/collision/TestHalfEdgeStructure.h +++ b/test/tests/collision/TestHalfEdgeStructure.h @@ -71,17 +71,17 @@ class TestHalfEdgeStructure : public Test { cubeStructure.addVertex(7); // Faces - List face0(mAllocator, 4); + Array face0(mAllocator, 4); face0.add(0); face0.add(1); face0.add(2); face0.add(3); - List face1(mAllocator, 4); + Array face1(mAllocator, 4); face1.add(1); face1.add(5); face1.add(6); face1.add(2); - List face2(mAllocator, 4); + Array face2(mAllocator, 4); face2.add(5); face2.add(4); face2.add(7); face2.add(6); - List face3(mAllocator, 4); + Array face3(mAllocator, 4); face3.add(4); face3.add(0); face3.add(3); face3.add(7); - List face4(mAllocator, 4); + Array face4(mAllocator, 4); face4.add(0); face4.add(4); face4.add(5); face4.add(1); - List face5(mAllocator, 4); + Array face5(mAllocator, 4); face5.add(2); face5.add(6); face5.add(7); face5.add(3); cubeStructure.addFace(face0); @@ -188,13 +188,13 @@ class TestHalfEdgeStructure : public Test { tetrahedron.addVertex(3); // Faces - List face0(mAllocator, 3); + Array face0(mAllocator, 3); face0.add(0); face0.add(1); face0.add(2); - List face1(mAllocator, 3); + Array face1(mAllocator, 3); face1.add(0); face1.add(3); face1.add(1); - List face2(mAllocator, 3); + Array face2(mAllocator, 3); face2.add(1); face2.add(3); face2.add(2); - List face3(mAllocator, 3); + Array face3(mAllocator, 3); face3.add(0); face3.add(2); face3.add(3); tetrahedron.addFace(face0); diff --git a/test/tests/containers/TestArray.h b/test/tests/containers/TestArray.h new file mode 100644 index 00000000..8faaa8dd --- /dev/null +++ b/test/tests/containers/TestArray.h @@ -0,0 +1,449 @@ +/******************************************************************************** +* ReactPhysics3D physics library, http://www.reactphysics3d.com * +* Copyright (c) 2010-2016 Daniel Chappuis * +********************************************************************************* +* * +* This software is provided 'as-is', without any express or implied warranty. * +* In no event will the authors be held liable for any damages arising from the * +* use of this software. * +* * +* Permission is granted to anyone to use this software for any purpose, * +* including commercial applications, and to alter it and redistribute it * +* freely, subject to the following restrictions: * +* * +* 1. The origin of this software must not be misrepresented; you must not claim * +* that you wrote the original software. If you use this software in a * +* product, an acknowledgment in the product documentation would be * +* appreciated but is not required. * +* * +* 2. Altered source versions must be plainly marked as such, and must not be * +* misrepresented as being the original software. * +* * +* 3. This notice may not be removed or altered from any source distribution. * +* * +********************************************************************************/ + +#ifndef TEST_ARRAY_H +#define TEST_ARRAY_H + +// Libraries +#include "Test.h" +#include +#include + +/// Reactphysics3D namespace +namespace reactphysics3d { + +// Class TestArray +/** + * Unit test for the Array class + */ +class TestArray : public Test { + + private : + + // ---------- Atributes ---------- // + + DefaultAllocator mAllocator; + + public : + + // ---------- Methods ---------- // + + /// Constructor + TestArray(const std::string& name) : Test(name) { + + } + + /// Run the tests + void run() { + + testConstructors(); + testAddRemoveClear(); + testAssignment(); + testIndexing(); + testFind(); + testEquality(); + testReserve(); + testIterators(); + } + + void testConstructors() { + + // ----- Constructors ----- // + + Array array1(mAllocator); + rp3d_test(array1.capacity() == 0); + rp3d_test(array1.size() == 0); + + Array array2(mAllocator, 100); + rp3d_test(array2.capacity() == 100); + rp3d_test(array2.size() == 0); + + Array array3(mAllocator); + array3.add(1); + array3.add(2); + array3.add(3); + rp3d_test(array3.capacity() == 4); + rp3d_test(array3.size() == 3); + + // ----- Copy Constructors ----- // + + Array array4(array1); + rp3d_test(array4.capacity() == 0); + rp3d_test(array4.size() == 0); + + Array array5(array3); + rp3d_test(array5.capacity() == array3.capacity()); + rp3d_test(array5.size() == array3.size()); + for (uint i=0; i arra6(mAllocator, 20); + rp3d_test(arra6.capacity() == 20); + for (uint i=0; i<20; i++) { + arra6.add("test"); + } + rp3d_test(arra6.capacity() == 20); + arra6.add("test"); + rp3d_test(arra6.capacity() == 40); + } + + void testAddRemoveClear() { + + // ----- Test add() ----- // + + Array array1(mAllocator); + array1.add(4); + rp3d_test(array1.size() == 1); + rp3d_test(array1[0] == 4); + array1.add(9); + rp3d_test(array1.size() == 2); + rp3d_test(array1[0] == 4); + rp3d_test(array1[1] == 9); + + const int arraySize = 15; + int arrayTest[arraySize] = {3, 145, -182, 34, 12, 95, -1834, 4143, -111, -111, 4343, 234, 22983, -3432, 753}; + Array array2(mAllocator); + for (uint i=0; i array3(mAllocator); + array3.add(1); + array3.add(2); + array3.add(3); + array3.add(4); + + auto it = array3.removeAt(3); + rp3d_test(array3.size() == 3); + rp3d_test(array3.capacity() == 4); + rp3d_test(it == array3.end()); + rp3d_test(array3[0] == 1); + rp3d_test(array3[1] == 2); + rp3d_test(array3[2] == 3); + + it = array3.removeAt(1); + rp3d_test(array3.size() == 2); + rp3d_test(array3.capacity() == 4); + rp3d_test(array3[0] == 1); + rp3d_test(array3[1] == 3); + rp3d_test(*it == 3); + + array3.removeAt(0); + rp3d_test(array3.size() == 1); + rp3d_test(array3.capacity() == 4); + rp3d_test(array3[0] == 3); + + it = array3.removeAt(0); + rp3d_test(array3.size() == 0); + rp3d_test(array3.capacity() == 4); + rp3d_test(it == array3.end()); + + array3.add(1); + array3.add(2); + array3.add(3); + it = array3.begin(); + array3.remove(it); + rp3d_test(array3.size() == 2); + rp3d_test(array3[0] == 2); + rp3d_test(array3[1] == 3); + it = array3.find(3); + array3.remove(it); + rp3d_test(array3.size() == 1); + rp3d_test(array3[0] == 2); + + array3.add(5); + array3.add(6); + array3.add(7); + it = array3.remove(7); + rp3d_test(it == array3.end()); + rp3d_test(array3.size() == 3); + it = array3.remove(5); + rp3d_test((*it) == 6); + + // ----- Test addRange() ----- // + + Array array4(mAllocator); + array4.add(1); + array4.add(2); + array4.add(3); + + Array array5(mAllocator); + array5.add(4); + array5.add(5); + + Array array6(mAllocator); + array6.addRange(array5); + rp3d_test(array6.size() == array5.size()); + rp3d_test(array6[0] == 4); + rp3d_test(array6[1] == 5); + + array4.addRange(array5); + rp3d_test(array4.size() == 3 + array5.size()); + rp3d_test(array4[0] == 1); + rp3d_test(array4[1] == 2); + rp3d_test(array4[2] == 3); + rp3d_test(array4[3] == 4); + rp3d_test(array4[4] == 5); + + // ----- Test clear() ----- // + + Array array7(mAllocator); + array7.add("test1"); + array7.add("test2"); + array7.add("test3"); + array7.clear(); + rp3d_test(array7.size() == 0); + array7.add("new"); + rp3d_test(array7.size() == 1); + rp3d_test(array7[0] == "new"); + + // ----- Test removeAtAndReplaceByLast() ----- // + + Array array8(mAllocator); + array8.add(1); + array8.add(2); + array8.add(3); + array8.add(4); + array8.removeAtAndReplaceByLast(1); + rp3d_test(array8.size() == 3); + rp3d_test(array8[0] == 1); + rp3d_test(array8[1] == 4); + rp3d_test(array8[2] == 3); + array8.removeAtAndReplaceByLast(2); + rp3d_test(array8.size() == 2); + rp3d_test(array8[0] == 1); + rp3d_test(array8[1] == 4); + array8.removeAtAndReplaceByLast(0); + rp3d_test(array8.size() == 1); + rp3d_test(array8[0] == 4); + array8.removeAtAndReplaceByLast(0); + rp3d_test(array8.size() == 0); + } + + void testAssignment() { + + Array array1(mAllocator); + array1.add(1); + array1.add(2); + array1.add(3); + + Array array2(mAllocator); + array2.add(5); + array2.add(6); + + Array array3(mAllocator); + Array array4(mAllocator); + array4.add(1); + array4.add(2); + + Array array5(mAllocator); + array5.add(1); + array5.add(2); + array5.add(3); + + array3 = array2; + rp3d_test(array2.size() == array3.size()); + rp3d_test(array2[0] == array3[0]); + rp3d_test(array2[1] == array3[1]); + + array4 = array1; + rp3d_test(array4.size() == array1.size()) + rp3d_test(array4[0] == array1[0]); + rp3d_test(array4[1] == array1[1]); + rp3d_test(array4[2] == array1[2]); + + array5 = array2; + rp3d_test(array5.size() == array2.size()); + rp3d_test(array5[0] == array2[0]); + rp3d_test(array5[1] == array2[1]); + } + + void testIndexing() { + + Array array1(mAllocator); + array1.add(1); + array1.add(2); + array1.add(3); + + rp3d_test(array1[0] == 1); + rp3d_test(array1[1] == 2); + rp3d_test(array1[2] == 3); + + array1[0] = 6; + array1[1] = 7; + array1[2] = 8; + + rp3d_test(array1[0] == 6); + rp3d_test(array1[1] == 7); + rp3d_test(array1[2] == 8); + + const int a = array1[0]; + const int b = array1[1]; + rp3d_test(a == 6); + rp3d_test(b == 7); + + array1[0]++; + array1[1]++; + rp3d_test(array1[0] == 7); + rp3d_test(array1[1] == 8); + } + + void testFind() { + + Array array1(mAllocator); + array1.add(1); + array1.add(2); + array1.add(3); + array1.add(4); + array1.add(5); + + rp3d_test(array1.find(1) == array1.begin()); + rp3d_test(*(array1.find(2)) == 2); + rp3d_test(*(array1.find(5)) == 5); + } + + void testEquality() { + + Array array1(mAllocator); + array1.add(1); + array1.add(2); + array1.add(3); + + Array array2(mAllocator); + array2.add(1); + array2.add(2); + + Array array3(mAllocator); + array3.add(1); + array3.add(2); + array3.add(3); + + Array array4(mAllocator); + array4.add(1); + array4.add(5); + array4.add(3); + + rp3d_test(array1 == array1); + rp3d_test(array1 != array2); + rp3d_test(array1 == array3); + rp3d_test(array1 != array4); + rp3d_test(array2 != array4); + } + + void testReserve() { + + Array array1(mAllocator); + array1.reserve(10); + rp3d_test(array1.size() == 0); + rp3d_test(array1.capacity() == 10); + array1.add(1); + array1.add(2); + rp3d_test(array1.capacity() == 10); + rp3d_test(array1.size() == 2); + rp3d_test(array1[0] == 1); + rp3d_test(array1[1] == 2); + + array1.reserve(1); + rp3d_test(array1.capacity() == 10); + + array1.reserve(100); + rp3d_test(array1.capacity() == 100); + rp3d_test(array1[0] == 1); + rp3d_test(array1[1] == 2); + } + + void testIterators() { + + Array array1(mAllocator); + + rp3d_test(array1.begin() == array1.end()); + + array1.add(5); + array1.add(6); + array1.add(8); + array1.add(-1); + + Array::Iterator itBegin = array1.begin(); + Array::Iterator itEnd = array1.end(); + Array::Iterator it = array1.begin(); + + rp3d_test(itBegin < itEnd); + rp3d_test(itBegin <= itEnd); + rp3d_test(itEnd > itBegin); + rp3d_test(itEnd >= itBegin); + + rp3d_test(itBegin == it); + rp3d_test(*it == 5); + rp3d_test(*(it++) == 5); + rp3d_test(*it == 6); + rp3d_test(*(it--) == 6); + rp3d_test(*it == 5); + rp3d_test(*(++it) == 6); + rp3d_test(*it == 6); + rp3d_test(*(--it) == 5); + rp3d_test(*it == 5); + rp3d_test(it == itBegin); + + it = array1.end(); + rp3d_test(it == itEnd); + it--; + rp3d_test(*it == -1); + it++; + rp3d_test(it == itEnd); + + Array array2(mAllocator); + for (auto it = array1.begin(); it != array1.end(); ++it) { + array2.add(*it); + } + + rp3d_test(array1 == array2); + + it = itBegin; + rp3d_test(*(it + 2) == 8); + it += 2; + rp3d_test(*it == 8); + rp3d_test(*(it - 2) == 5); + it -= 2; + rp3d_test(*it == 5); + rp3d_test((itEnd - itBegin) == 4); + + it = itBegin; + *it = 19; + rp3d_test(*it == 19); + } + + }; + +} + +#endif diff --git a/test/tests/containers/TestList.h b/test/tests/containers/TestList.h deleted file mode 100644 index 5e20c32d..00000000 --- a/test/tests/containers/TestList.h +++ /dev/null @@ -1,449 +0,0 @@ -/******************************************************************************** -* ReactPhysics3D physics library, http://www.reactphysics3d.com * -* Copyright (c) 2010-2016 Daniel Chappuis * -********************************************************************************* -* * -* This software is provided 'as-is', without any express or implied warranty. * -* In no event will the authors be held liable for any damages arising from the * -* use of this software. * -* * -* Permission is granted to anyone to use this software for any purpose, * -* including commercial applications, and to alter it and redistribute it * -* freely, subject to the following restrictions: * -* * -* 1. The origin of this software must not be misrepresented; you must not claim * -* that you wrote the original software. If you use this software in a * -* product, an acknowledgment in the product documentation would be * -* appreciated but is not required. * -* * -* 2. Altered source versions must be plainly marked as such, and must not be * -* misrepresented as being the original software. * -* * -* 3. This notice may not be removed or altered from any source distribution. * -* * -********************************************************************************/ - -#ifndef TEST_LIST_H -#define TEST_LIST_H - -// Libraries -#include "Test.h" -#include -#include - -/// Reactphysics3D namespace -namespace reactphysics3d { - -// Class TestList -/** - * Unit test for the List class - */ -class TestList : public Test { - - private : - - // ---------- Atributes ---------- // - - DefaultAllocator mAllocator; - - public : - - // ---------- Methods ---------- // - - /// Constructor - TestList(const std::string& name) : Test(name) { - - } - - /// Run the tests - void run() { - - testConstructors(); - testAddRemoveClear(); - testAssignment(); - testIndexing(); - testFind(); - testEquality(); - testReserve(); - testIterators(); - } - - void testConstructors() { - - // ----- Constructors ----- // - - List list1(mAllocator); - rp3d_test(list1.capacity() == 0); - rp3d_test(list1.size() == 0); - - List list2(mAllocator, 100); - rp3d_test(list2.capacity() == 100); - rp3d_test(list2.size() == 0); - - List list3(mAllocator); - list3.add(1); - list3.add(2); - list3.add(3); - rp3d_test(list3.capacity() == 4); - rp3d_test(list3.size() == 3); - - // ----- Copy Constructors ----- // - - List list4(list1); - rp3d_test(list4.capacity() == 0); - rp3d_test(list4.size() == 0); - - List list5(list3); - rp3d_test(list5.capacity() == list3.capacity()); - rp3d_test(list5.size() == list3.size()); - for (uint i=0; i list6(mAllocator, 20); - rp3d_test(list6.capacity() == 20); - for (uint i=0; i<20; i++) { - list6.add("test"); - } - rp3d_test(list6.capacity() == 20); - list6.add("test"); - rp3d_test(list6.capacity() == 40); - } - - void testAddRemoveClear() { - - // ----- Test add() ----- // - - List list1(mAllocator); - list1.add(4); - rp3d_test(list1.size() == 1); - rp3d_test(list1[0] == 4); - list1.add(9); - rp3d_test(list1.size() == 2); - rp3d_test(list1[0] == 4); - rp3d_test(list1[1] == 9); - - const int arraySize = 15; - int arrayTest[arraySize] = {3, 145, -182, 34, 12, 95, -1834, 4143, -111, -111, 4343, 234, 22983, -3432, 753}; - List list2(mAllocator); - for (uint i=0; i list3(mAllocator); - list3.add(1); - list3.add(2); - list3.add(3); - list3.add(4); - - auto it = list3.removeAt(3); - rp3d_test(list3.size() == 3); - rp3d_test(list3.capacity() == 4); - rp3d_test(it == list3.end()); - rp3d_test(list3[0] == 1); - rp3d_test(list3[1] == 2); - rp3d_test(list3[2] == 3); - - it = list3.removeAt(1); - rp3d_test(list3.size() == 2); - rp3d_test(list3.capacity() == 4); - rp3d_test(list3[0] == 1); - rp3d_test(list3[1] == 3); - rp3d_test(*it == 3); - - list3.removeAt(0); - rp3d_test(list3.size() == 1); - rp3d_test(list3.capacity() == 4); - rp3d_test(list3[0] == 3); - - it = list3.removeAt(0); - rp3d_test(list3.size() == 0); - rp3d_test(list3.capacity() == 4); - rp3d_test(it == list3.end()); - - list3.add(1); - list3.add(2); - list3.add(3); - it = list3.begin(); - list3.remove(it); - rp3d_test(list3.size() == 2); - rp3d_test(list3[0] == 2); - rp3d_test(list3[1] == 3); - it = list3.find(3); - list3.remove(it); - rp3d_test(list3.size() == 1); - rp3d_test(list3[0] == 2); - - list3.add(5); - list3.add(6); - list3.add(7); - it = list3.remove(7); - rp3d_test(it == list3.end()); - rp3d_test(list3.size() == 3); - it = list3.remove(5); - rp3d_test((*it) == 6); - - // ----- Test addRange() ----- // - - List list4(mAllocator); - list4.add(1); - list4.add(2); - list4.add(3); - - List list5(mAllocator); - list5.add(4); - list5.add(5); - - List list6(mAllocator); - list6.addRange(list5); - rp3d_test(list6.size() == list5.size()); - rp3d_test(list6[0] == 4); - rp3d_test(list6[1] == 5); - - list4.addRange(list5); - rp3d_test(list4.size() == 3 + list5.size()); - rp3d_test(list4[0] == 1); - rp3d_test(list4[1] == 2); - rp3d_test(list4[2] == 3); - rp3d_test(list4[3] == 4); - rp3d_test(list4[4] == 5); - - // ----- Test clear() ----- // - - List list7(mAllocator); - list7.add("test1"); - list7.add("test2"); - list7.add("test3"); - list7.clear(); - rp3d_test(list7.size() == 0); - list7.add("new"); - rp3d_test(list7.size() == 1); - rp3d_test(list7[0] == "new"); - - // ----- Test removeAtAndReplaceByLast() ----- // - - List list8(mAllocator); - list8.add(1); - list8.add(2); - list8.add(3); - list8.add(4); - list8.removeAtAndReplaceByLast(1); - rp3d_test(list8.size() == 3); - rp3d_test(list8[0] == 1); - rp3d_test(list8[1] == 4); - rp3d_test(list8[2] == 3); - list8.removeAtAndReplaceByLast(2); - rp3d_test(list8.size() == 2); - rp3d_test(list8[0] == 1); - rp3d_test(list8[1] == 4); - list8.removeAtAndReplaceByLast(0); - rp3d_test(list8.size() == 1); - rp3d_test(list8[0] == 4); - list8.removeAtAndReplaceByLast(0); - rp3d_test(list8.size() == 0); - } - - void testAssignment() { - - List list1(mAllocator); - list1.add(1); - list1.add(2); - list1.add(3); - - List list2(mAllocator); - list2.add(5); - list2.add(6); - - List list3(mAllocator); - List list4(mAllocator); - list4.add(1); - list4.add(2); - - List list5(mAllocator); - list5.add(1); - list5.add(2); - list5.add(3); - - list3 = list2; - rp3d_test(list2.size() == list3.size()); - rp3d_test(list2[0] == list3[0]); - rp3d_test(list2[1] == list3[1]); - - list4 = list1; - rp3d_test(list4.size() == list1.size()) - rp3d_test(list4[0] == list1[0]); - rp3d_test(list4[1] == list1[1]); - rp3d_test(list4[2] == list1[2]); - - list5 = list2; - rp3d_test(list5.size() == list2.size()); - rp3d_test(list5[0] == list2[0]); - rp3d_test(list5[1] == list2[1]); - } - - void testIndexing() { - - List list1(mAllocator); - list1.add(1); - list1.add(2); - list1.add(3); - - rp3d_test(list1[0] == 1); - rp3d_test(list1[1] == 2); - rp3d_test(list1[2] == 3); - - list1[0] = 6; - list1[1] = 7; - list1[2] = 8; - - rp3d_test(list1[0] == 6); - rp3d_test(list1[1] == 7); - rp3d_test(list1[2] == 8); - - const int a = list1[0]; - const int b = list1[1]; - rp3d_test(a == 6); - rp3d_test(b == 7); - - list1[0]++; - list1[1]++; - rp3d_test(list1[0] == 7); - rp3d_test(list1[1] == 8); - } - - void testFind() { - - List list1(mAllocator); - list1.add(1); - list1.add(2); - list1.add(3); - list1.add(4); - list1.add(5); - - rp3d_test(list1.find(1) == list1.begin()); - rp3d_test(*(list1.find(2)) == 2); - rp3d_test(*(list1.find(5)) == 5); - } - - void testEquality() { - - List list1(mAllocator); - list1.add(1); - list1.add(2); - list1.add(3); - - List list2(mAllocator); - list2.add(1); - list2.add(2); - - List list3(mAllocator); - list3.add(1); - list3.add(2); - list3.add(3); - - List list4(mAllocator); - list4.add(1); - list4.add(5); - list4.add(3); - - rp3d_test(list1 == list1); - rp3d_test(list1 != list2); - rp3d_test(list1 == list3); - rp3d_test(list1 != list4); - rp3d_test(list2 != list4); - } - - void testReserve() { - - List list1(mAllocator); - list1.reserve(10); - rp3d_test(list1.size() == 0); - rp3d_test(list1.capacity() == 10); - list1.add(1); - list1.add(2); - rp3d_test(list1.capacity() == 10); - rp3d_test(list1.size() == 2); - rp3d_test(list1[0] == 1); - rp3d_test(list1[1] == 2); - - list1.reserve(1); - rp3d_test(list1.capacity() == 10); - - list1.reserve(100); - rp3d_test(list1.capacity() == 100); - rp3d_test(list1[0] == 1); - rp3d_test(list1[1] == 2); - } - - void testIterators() { - - List list1(mAllocator); - - rp3d_test(list1.begin() == list1.end()); - - list1.add(5); - list1.add(6); - list1.add(8); - list1.add(-1); - - List::Iterator itBegin = list1.begin(); - List::Iterator itEnd = list1.end(); - List::Iterator it = list1.begin(); - - rp3d_test(itBegin < itEnd); - rp3d_test(itBegin <= itEnd); - rp3d_test(itEnd > itBegin); - rp3d_test(itEnd >= itBegin); - - rp3d_test(itBegin == it); - rp3d_test(*it == 5); - rp3d_test(*(it++) == 5); - rp3d_test(*it == 6); - rp3d_test(*(it--) == 6); - rp3d_test(*it == 5); - rp3d_test(*(++it) == 6); - rp3d_test(*it == 6); - rp3d_test(*(--it) == 5); - rp3d_test(*it == 5); - rp3d_test(it == itBegin); - - it = list1.end(); - rp3d_test(it == itEnd); - it--; - rp3d_test(*it == -1); - it++; - rp3d_test(it == itEnd); - - List list2(mAllocator); - for (auto it = list1.begin(); it != list1.end(); ++it) { - list2.add(*it); - } - - rp3d_test(list1 == list2); - - it = itBegin; - rp3d_test(*(it + 2) == 8); - it += 2; - rp3d_test(*it == 8); - rp3d_test(*(it - 2) == 5); - it -= 2; - rp3d_test(*it == 5); - rp3d_test((itEnd - itBegin) == 4); - - it = itBegin; - *it = 19; - rp3d_test(*it == 19); - } - - }; - -} - -#endif diff --git a/test/tests/containers/TestSet.h b/test/tests/containers/TestSet.h index 0d1f4091..6d6c488c 100644 --- a/test/tests/containers/TestSet.h +++ b/test/tests/containers/TestSet.h @@ -432,18 +432,18 @@ class TestSet : public Test { set1.add(3); set1.add(4); - List list1 = set1.toList(mAllocator); - rp3d_test(list1.size() == 4); - rp3d_test(list1.find(1) != list1.end()); - rp3d_test(list1.find(2) != list1.end()); - rp3d_test(list1.find(3) != list1.end()); - rp3d_test(list1.find(4) != list1.end()); - rp3d_test(list1.find(5) == list1.end()); - rp3d_test(list1.find(6) == list1.end()); + Array array1 = set1.toArray(mAllocator); + rp3d_test(array1.size() == 4); + rp3d_test(array1.find(1) != array1.end()); + rp3d_test(array1.find(2) != array1.end()); + rp3d_test(array1.find(3) != array1.end()); + rp3d_test(array1.find(4) != array1.end()); + rp3d_test(array1.find(5) == array1.end()); + rp3d_test(array1.find(6) == array1.end()); Set set2(mAllocator); - List list2 = set2.toList(mAllocator); - rp3d_test(list2.size() == 0); + Array array2 = set2.toArray(mAllocator); + rp3d_test(array2.size() == 0); } }; diff --git a/test/tests/mathematics/TestMathematicsFunctions.h b/test/tests/mathematics/TestMathematicsFunctions.h index 7caab95f..b5c77fe2 100644 --- a/test/tests/mathematics/TestMathematicsFunctions.h +++ b/test/tests/mathematics/TestMathematicsFunctions.h @@ -27,7 +27,7 @@ #define TEST_MATHEMATICS_FUNCTIONS_H // Libraries -#include +#include #include #include @@ -187,12 +187,12 @@ class TestMathematicsFunctions : public Test { segmentVertices.push_back(Vector3(-6, 3, 0)); segmentVertices.push_back(Vector3(8, 3, 0)); - List planesNormals(mAllocator, 2); - List planesPoints(mAllocator, 2); + Array planesNormals(mAllocator, 2); + Array planesPoints(mAllocator, 2); planesNormals.add(Vector3(-1, 0, 0)); planesPoints.add(Vector3(4, 0, 0)); - List clipSegmentVertices = clipSegmentWithPlanes(segmentVertices[0], segmentVertices[1], + Array clipSegmentVertices = clipSegmentWithPlanes(segmentVertices[0], segmentVertices[1], planesPoints, planesNormals, mAllocator); rp3d_test(clipSegmentVertices.size() == 2); rp3d_test(approxEqual(clipSegmentVertices[0].x, -6, 0.000001)); @@ -236,14 +236,14 @@ class TestMathematicsFunctions : public Test { rp3d_test(clipSegmentVertices.size() == 0); // Test clipPolygonWithPlanes() - List polygonVertices(mAllocator); + Array polygonVertices(mAllocator); polygonVertices.add(Vector3(-4, 2, 0)); polygonVertices.add(Vector3(7, 2, 0)); polygonVertices.add(Vector3(7, 4, 0)); polygonVertices.add(Vector3(-4, 4, 0)); - List polygonPlanesNormals(mAllocator); - List polygonPlanesPoints(mAllocator); + Array polygonPlanesNormals(mAllocator); + Array polygonPlanesPoints(mAllocator); polygonPlanesNormals.add(Vector3(1, 0, 0)); polygonPlanesPoints.add(Vector3(0, 0, 0)); polygonPlanesNormals.add(Vector3(0, 1, 0)); @@ -253,7 +253,7 @@ class TestMathematicsFunctions : public Test { polygonPlanesNormals.add(Vector3(0, -1, 0)); polygonPlanesPoints.add(Vector3(10, 5, 0)); - List clipPolygonVertices = clipPolygonWithPlanes(polygonVertices, polygonPlanesPoints, polygonPlanesNormals, mAllocator); + Array clipPolygonVertices = clipPolygonWithPlanes(polygonVertices, polygonPlanesPoints, polygonPlanesNormals, mAllocator); rp3d_test(clipPolygonVertices.size() == 4); rp3d_test(approxEqual(clipPolygonVertices[0].x, 0, 0.000001)); rp3d_test(approxEqual(clipPolygonVertices[0].y, 2, 0.000001)); From 17f14f518e9d0b80d6734c9f1b4ee18558b6b6ce Mon Sep 17 00:00:00 2001 From: SlavicPotato Date: Sat, 5 Sep 2020 15:58:16 +0200 Subject: [PATCH 33/74] Fix arithmetic performed on void * Removed unnecessary cast --- include/reactphysics3d/containers/Array.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/reactphysics3d/containers/Array.h b/include/reactphysics3d/containers/Array.h index b48c8717..6b450256 100755 --- a/include/reactphysics3d/containers/Array.h +++ b/include/reactphysics3d/containers/Array.h @@ -364,8 +364,8 @@ class Array { // Move the elements to fill in the empty slot void* dest = reinterpret_cast(mBuffer + index); - void* src = dest + sizeof(T); - std::memmove(static_cast(dest), static_cast(src), (mSize - index) * sizeof(T)); + std::uintptr_t src = reinterpret_cast(dest) + sizeof(T); + std::memmove(dest, reinterpret_cast(src), (mSize - index) * sizeof(T)); } // Return an iterator pointing to the element after the removed one From dab4fc914381990a5aff9d79c84baf3a93a3f272 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Sun, 6 Sep 2020 11:55:33 +0200 Subject: [PATCH 34/74] Rename List into Array in the CMakeLists.txt file --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c6f2e52a..9b396a6d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -137,7 +137,7 @@ set (REACTPHYSICS3D_HEADERS "include/reactphysics3d/memory/MemoryManager.h" "include/reactphysics3d/containers/Stack.h" "include/reactphysics3d/containers/LinkedList.h" - "include/reactphysics3d/containers/List.h" + "include/reactphysics3d/containers/Array.h" "include/reactphysics3d/containers/Map.h" "include/reactphysics3d/containers/Set.h" "include/reactphysics3d/containers/Pair.h" From b55626df67b7cb5cfd50b26223fd99e3f31a8548 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Sun, 6 Sep 2020 18:42:05 +0200 Subject: [PATCH 35/74] Cache the size of lists before loops --- src/body/CollisionBody.cpp | 9 ++++++--- src/body/RigidBody.cpp | 3 ++- src/collision/CollisionCallback.cpp | 6 ++++-- src/collision/HalfEdgeStructure.cpp | 11 +++++++---- src/collision/OverlapCallback.cpp | 6 ++++-- src/collision/PolyhedronMesh.cpp | 3 ++- .../narrowphase/NarrowPhaseInfoBatch.cpp | 3 ++- src/collision/narrowphase/SAT/SATAlgorithm.cpp | 10 ++++++---- src/collision/shapes/CollisionShape.cpp | 3 ++- src/collision/shapes/HeightFieldShape.cpp | 3 ++- src/engine/PhysicsWorld.cpp | 18 ++++++++++++------ src/mathematics/mathematics_functions.cpp | 6 ++++-- src/systems/CollisionDetectionSystem.cpp | 6 ++++-- src/utils/DebugRenderer.cpp | 3 ++- 14 files changed, 59 insertions(+), 31 deletions(-) diff --git a/src/body/CollisionBody.cpp b/src/body/CollisionBody.cpp index ba89b3a7..44513f55 100644 --- a/src/body/CollisionBody.cpp +++ b/src/body/CollisionBody.cpp @@ -293,7 +293,8 @@ void CollisionBody::askForBroadPhaseCollisionCheck() const { // For all the colliders of the body const Array& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); - for (uint32 i=0; i < colliderEntities.size(); i++) { + const uint32 nbColliderEntities = colliderEntities.size(); + for (uint32 i=0; i < nbColliderEntities; i++) { Collider* collider = mWorld.mCollidersComponents.getCollider(colliderEntities[i]); @@ -340,7 +341,8 @@ bool CollisionBody::raycast(const Ray& ray, RaycastInfo& raycastInfo) { // For each collider of the body const Array& colliderEntities = mWorld.mCollisionBodyComponents.getColliders(mEntity); - for (uint32 i=0; i < colliderEntities.size(); i++) { + const uint32 nbColliderEntities = colliderEntities.size(); + for (uint32 i=0; i < nbColliderEntities; i++) { Collider* collider = mWorld.mCollidersComponents.getCollider(colliderEntities[i]); @@ -371,7 +373,8 @@ AABB CollisionBody::getAABB() const { collider->getCollisionShape()->computeAABB(bodyAABB, transform * collider->getLocalToBodyTransform()); // For each collider of the body - for (uint32 i=1; i < colliderEntities.size(); i++) { + const uint32 nbColliderEntities = colliderEntities.size(); + for (uint32 i=1; i < nbColliderEntities; i++) { Collider* collider = mWorld.mCollidersComponents.getCollider(colliderEntities[i]); diff --git a/src/body/RigidBody.cpp b/src/body/RigidBody.cpp index bd5586a1..86d7cb62 100644 --- a/src/body/RigidBody.cpp +++ b/src/body/RigidBody.cpp @@ -877,7 +877,8 @@ void RigidBody::resetOverlappingPairs() { // Get the currently overlapping pairs for this collider Array overlappingPairs = mWorld.mCollidersComponents.getOverlappingPairs(colliderEntities[i]); - for (uint32 j=0; j < overlappingPairs.size(); j++) { + const uint32 nbOverlappingPairs = overlappingPairs.size(); + for (uint32 j=0; j < nbOverlappingPairs; j++) { mWorld.mCollisionDetection.mOverlappingPairs.removePair(overlappingPairs[j]); } diff --git a/src/collision/CollisionCallback.cpp b/src/collision/CollisionCallback.cpp index f537bdb9..eb792ab5 100644 --- a/src/collision/CollisionCallback.cpp +++ b/src/collision/CollisionCallback.cpp @@ -83,7 +83,8 @@ CollisionCallback::CallbackData::CallbackData(Array mWorld(world) { // Filter the contact pairs to only keep the contact events (not the overlap/trigger events) - for (uint32 i=0; i < mContactPairs->size(); i++) { + const uint32 nbContactPairs = mContactPairs->size(); + for (uint32 i=0; i < nbContactPairs; i++) { // If the contact pair contains contacts (and is therefore not an overlap/trigger event) if (!(*mContactPairs)[i].isTrigger) { @@ -91,7 +92,8 @@ CollisionCallback::CallbackData::CallbackData(Array } } // Filter the lost contact pairs to only keep the contact events (not the overlap/trigger events) - for (uint32 i=0; i < mLostContactPairs.size(); i++) { + const uint32 nbLostContactPairs = mLostContactPairs.size(); + for (uint32 i=0; i < nbLostContactPairs; i++) { // If the contact pair contains contacts (and is therefore not an overlap/trigger event) if (!mLostContactPairs[i].isTrigger) { diff --git a/src/collision/HalfEdgeStructure.cpp b/src/collision/HalfEdgeStructure.cpp index 574062c3..5ec064fa 100644 --- a/src/collision/HalfEdgeStructure.cpp +++ b/src/collision/HalfEdgeStructure.cpp @@ -44,14 +44,16 @@ void HalfEdgeStructure::init() { Array currentFaceEdges(mAllocator, mFaces[0].faceVertices.size()); // For each face - for (uint32 f=0; f& contactPairs, Ar mContactPairsIndices(world.mMemoryManager.getHeapAllocator()), mLostContactPairsIndices(world.mMemoryManager.getHeapAllocator()), mWorld(world) { // Filter the contact pairs to only keep the overlap/trigger events (not the contact events) - for (uint32 i=0; i < mContactPairs.size(); i++) { + const uint32 nbContactPairs = mContactPairs.size(); + for (uint32 i=0; i < nbContactPairs; i++) { // If the contact pair contains contacts (and is therefore not an overlap/trigger event) if (!onlyReportTriggers || mContactPairs[i].isTrigger) { @@ -80,7 +81,8 @@ OverlapCallback::CallbackData::CallbackData(Array& contactPairs, Ar } } // Filter the lost contact pairs to only keep the overlap/trigger events (not the contact events) - for (uint32 i=0; i < mLostContactPairs.size(); i++) { + const uint32 nbLostContactPairs = mLostContactPairs.size(); + for (uint32 i=0; i < nbLostContactPairs; i++) { // If the contact pair contains contacts (and is therefore not an overlap/trigger event) if (!onlyReportTriggers || mLostContactPairs[i].isTrigger) { diff --git a/src/collision/PolyhedronMesh.cpp b/src/collision/PolyhedronMesh.cpp index e413bce0..9d92fc00 100644 --- a/src/collision/PolyhedronMesh.cpp +++ b/src/collision/PolyhedronMesh.cpp @@ -166,7 +166,8 @@ decimal PolyhedronMesh::getFaceArea(uint faceIndex) const { Vector3 v1 = getVertex(face.faceVertices[0]); // For each vertex of the face - for (uint32 i=2; i < face.faceVertices.size(); i++) { + const uint32 nbFaceVertices = face.faceVertices.size(); + for (uint32 i=2; i < nbFaceVertices; i++) { const Vector3 v2 = getVertex(face.faceVertices[i-1]); const Vector3 v3 = getVertex(face.faceVertices[i]); diff --git a/src/collision/narrowphase/NarrowPhaseInfoBatch.cpp b/src/collision/narrowphase/NarrowPhaseInfoBatch.cpp index 07d17fdd..ea08cf5b 100644 --- a/src/collision/narrowphase/NarrowPhaseInfoBatch.cpp +++ b/src/collision/narrowphase/NarrowPhaseInfoBatch.cpp @@ -52,7 +52,8 @@ void NarrowPhaseInfoBatch::reserveMemory() { // Clear all the objects in the batch void NarrowPhaseInfoBatch::clear() { - for (uint32 i=0; i < narrowPhaseInfos.size(); i++) { + const uint32 nbNarrowPhaseInfos = narrowPhaseInfos.size(); + for (uint32 i=0; i < nbNarrowPhaseInfos; i++) { assert(narrowPhaseInfos[i].nbContactPoints == 0); diff --git a/src/collision/narrowphase/SAT/SATAlgorithm.cpp b/src/collision/narrowphase/SAT/SATAlgorithm.cpp index 0d0eccfe..bf175b01 100644 --- a/src/collision/narrowphase/SAT/SATAlgorithm.cpp +++ b/src/collision/narrowphase/SAT/SATAlgorithm.cpp @@ -430,7 +430,8 @@ bool SATAlgorithm::computeCapsulePolyhedronFaceContactPoints(uint referenceFaceI bool contactFound = false; // For each of the two clipped points - for (uint32 i = 0; igetFace(incidentFaceIndex); - uint32 nbIncidentFaceVertices = incidentFace.faceVertices.size(); + const uint32 nbIncidentFaceVertices = incidentFace.faceVertices.size(); Array polygonVertices(mMemoryAllocator, nbIncidentFaceVertices); // Vertices to clip of the incident face Array planesNormals(mMemoryAllocator, nbIncidentFaceVertices); // Normals of the clipping planes Array planesPoints(mMemoryAllocator, nbIncidentFaceVertices); // Points on the clipping planes // Get all the vertices of the incident face (in the reference local-space) - for (uint32 i=0; i < incidentFace.faceVertices.size(); i++) { + for (uint32 i=0; i < nbIncidentFaceVertices; i++) { const Vector3 faceVertexIncidentSpace = incidentPolyhedron->getVertexPosition(incidentFace.faceVertices[i]); polygonVertices.add(incidentToReferenceTransform * faceVertexIncidentSpace); } @@ -963,7 +964,8 @@ bool SATAlgorithm::computePolyhedronVsPolyhedronFaceContactPoints(bool isMinPene // We only keep the clipped points that are below the reference face const Vector3 referenceFaceVertex = referencePolyhedron->getVertexPosition(referencePolyhedron->getHalfEdge(firstEdgeIndex).vertexIndex); bool contactPointsFound = false; - for (uint32 i=0; isetHasCollisionShapeChangedSize(true); } } diff --git a/src/collision/shapes/HeightFieldShape.cpp b/src/collision/shapes/HeightFieldShape.cpp index ff5419a9..3322d88d 100644 --- a/src/collision/shapes/HeightFieldShape.cpp +++ b/src/collision/shapes/HeightFieldShape.cpp @@ -250,7 +250,8 @@ bool HeightFieldShape::raycast(const Ray& ray, RaycastInfo& raycastInfo, Collide decimal smallestHitFraction = ray.maxFraction; // For each overlapping triangle - for (uint32 i=0; i < shapeIds.size(); i++) + const uint32 nbShapeIds = shapeIds.size(); + for (uint32 i=0; i < nbShapeIds; i++) { // Create a triangle collision shape TriangleShape triangleShape(&(triangleVertices[i * 3]), &(triangleVerticesNormals[i * 3]), shapeIds[i], allocator); diff --git a/src/engine/PhysicsWorld.cpp b/src/engine/PhysicsWorld.cpp index c1b6ff1e..2dfa9896 100644 --- a/src/engine/PhysicsWorld.cpp +++ b/src/engine/PhysicsWorld.cpp @@ -239,7 +239,8 @@ void PhysicsWorld::setBodyDisabled(Entity bodyEntity, bool isDisabled) { // For each collider of the body const Array& collidersEntities = mCollisionBodyComponents.getColliders(bodyEntity); - for (uint32 i=0; i < collidersEntities.size(); i++) { + const uint32 nbColliderEntities = collidersEntities.size(); + for (uint32 i=0; i < nbColliderEntities; i++) { mCollidersComponents.setIsEntityDisabled(collidersEntities[i], isDisabled); } @@ -247,7 +248,8 @@ void PhysicsWorld::setBodyDisabled(Entity bodyEntity, bool isDisabled) { // Disable the joints of the body if necessary // For each joint of the body const Array& joints = mRigidBodyComponents.getJoints(bodyEntity); - for(uint32 i=0; i < joints.size(); i++) { + const uint32 nbJoints = joints.size(); + for(uint32 i=0; i < nbJoints; i++) { const Entity body1Entity = mJointsComponents.getBody1Entity(joints[i]); const Entity body2Entity = mJointsComponents.getBody2Entity(joints[i]); @@ -519,7 +521,8 @@ void PhysicsWorld::destroyRigidBody(RigidBody* rigidBody) { // Destroy all the joints in which the rigid body to be destroyed is involved const Array& joints = mRigidBodyComponents.getJoints(rigidBody->getEntity()); - for (uint32 i=0; i < joints.size(); i++) { + const uint32 nbJoints = joints.size(); + for (uint32 i=0; i < nbJoints; i++) { destroyJoint(mJointsComponents.getJoint(joints[i])); } @@ -829,7 +832,8 @@ void PhysicsWorld::createIslands() { // If the body is involved in contacts with other bodies // For each contact pair in which the current body is involded - for (uint32 p=0; p < mRigidBodyComponents.mContactPairs[bodyToVisitIndex].size(); p++) { + const uint32 nbBodyContactPairs = mRigidBodyComponents.mContactPairs[bodyToVisitIndex].size(); + for (uint32 p=0; p < nbBodyContactPairs; p++) { const uint32 contactPairIndex = mRigidBodyComponents.mContactPairs[bodyToVisitIndex][p]; ContactPair& pair = (*mCollisionDetection.mCurrentContactPairs)[contactPairIndex]; @@ -869,7 +873,8 @@ void PhysicsWorld::createIslands() { // For each joint in which the current body is involved const Array& joints = mRigidBodyComponents.getJoints(rigidBodyToVisit->getEntity()); - for (uint32 i=0; i < joints.size(); i++) { + const uint32 nbBodyJoints = joints.size(); + for (uint32 i=0; i < nbBodyJoints; i++) { // Check if the current joint has already been added into an island if (mJointsComponents.getIsAlreadyInIsland(joints[i])) continue; @@ -894,7 +899,8 @@ void PhysicsWorld::createIslands() { // Reset the isAlreadyIsland variable of the static bodies so that they // can also be included in the other islands - for (uint32 j=0; j < staticBodiesAddedToIsland.size(); j++) { + const uint32 nbStaticBodiesAddedToIsland = staticBodiesAddedToIsland.size(); + for (uint32 j=0; j < nbStaticBodiesAddedToIsland; j++) { assert(mRigidBodyComponents.getBodyType(staticBodiesAddedToIsland[j]) == BodyType::STATIC); mRigidBodyComponents.setIsAlreadyInIsland(staticBodiesAddedToIsland[j], false); diff --git a/src/mathematics/mathematics_functions.cpp b/src/mathematics/mathematics_functions.cpp index a237a6d2..57995192 100755 --- a/src/mathematics/mathematics_functions.cpp +++ b/src/mathematics/mathematics_functions.cpp @@ -234,7 +234,8 @@ Array reactphysics3d::clipSegmentWithPlanes(const Vector3& segA, const inputVertices.add(segB); // For each clipping plane - for (uint32 p=0; p reactphysics3d::clipPolygonWithPlanes(const Array& polyg inputVertices.addRange(polygonVertices); // For each clipping plane - for (uint32 p=0; psize(); i++) { + const uint32 nbCurrentContactPairs = mCurrentContactPairs->size(); + for (uint32 i=0; i < nbCurrentContactPairs; i++) { mPreviousMapPairIdToContactPairIndex.add(Pair((*mCurrentContactPairs)[i].pairId, i)); } } @@ -984,7 +985,8 @@ void CollisionDetectionSystem::createSnapshotContacts(Array& contac void CollisionDetectionSystem::initContactsWithPreviousOnes() { // For each contact pair of the current frame - for (uint32 i=0; i < mCurrentContactPairs->size(); i++) { + const uint32 nbCurrentContactPairs = mCurrentContactPairs->size(); + for (uint32 i=0; i < nbCurrentContactPairs; i++) { ContactPair& currentContactPair = (*mCurrentContactPairs)[i]; diff --git a/src/utils/DebugRenderer.cpp b/src/utils/DebugRenderer.cpp index ede7035c..2105ea84 100644 --- a/src/utils/DebugRenderer.cpp +++ b/src/utils/DebugRenderer.cpp @@ -280,7 +280,8 @@ void DebugRenderer::drawConvexMesh(const Transform& transform, const ConvexMeshS assert(face.faceVertices.size() >= 3); // Perform a fan triangulation of the convex polygon face - for (uint32 v = 2; v < face.faceVertices.size(); v++) { + const uint32 nbFaceVertices = face.faceVertices.size(); + for (uint32 v = 2; v < nbFaceVertices; v++) { uint v1Index = face.faceVertices[v - 2]; uint v2Index = face.faceVertices[v - 1]; From e7b951b8e4201dcf6e207566d578b68e082d341d Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Tue, 8 Sep 2020 19:03:22 +0200 Subject: [PATCH 36/74] Refactor the mathematics_functions.h file --- .../narrowphase/GJK/VoronoiSimplex.h | 1 + .../reactphysics3d/mathematics/Matrix2x2.h | 4 +- .../reactphysics3d/mathematics/Matrix3x3.h | 6 +- include/reactphysics3d/mathematics/Vector2.h | 12 +- include/reactphysics3d/mathematics/Vector3.h | 16 +- .../mathematics/mathematics_common.h | 50 +++ .../mathematics/mathematics_functions.h | 366 +++++++++++++++-- .../systems/ContactSolverSystem.h | 1 + .../narrowphase/SAT/SATAlgorithm.cpp | 3 +- src/mathematics/Vector3.cpp | 2 +- src/mathematics/mathematics_functions.cpp | 383 ------------------ src/systems/SolveHingeJointSystem.cpp | 8 +- .../mathematics/TestMathematicsFunctions.h | 3 +- 13 files changed, 416 insertions(+), 439 deletions(-) create mode 100644 include/reactphysics3d/mathematics/mathematics_common.h delete mode 100755 src/mathematics/mathematics_functions.cpp diff --git a/include/reactphysics3d/collision/narrowphase/GJK/VoronoiSimplex.h b/include/reactphysics3d/collision/narrowphase/GJK/VoronoiSimplex.h index fb9ddc8d..785d4edf 100644 --- a/include/reactphysics3d/collision/narrowphase/GJK/VoronoiSimplex.h +++ b/include/reactphysics3d/collision/narrowphase/GJK/VoronoiSimplex.h @@ -28,6 +28,7 @@ // Libraries #include +#include /// ReactPhysics3D namespace namespace reactphysics3d { diff --git a/include/reactphysics3d/mathematics/Matrix2x2.h b/include/reactphysics3d/mathematics/Matrix2x2.h index 5f69e982..66c651f6 100644 --- a/include/reactphysics3d/mathematics/Matrix2x2.h +++ b/include/reactphysics3d/mathematics/Matrix2x2.h @@ -242,8 +242,8 @@ RP3D_FORCE_INLINE Matrix2x2 Matrix2x2::zero() { // Return the matrix with absolute values RP3D_FORCE_INLINE Matrix2x2 Matrix2x2::getAbsoluteMatrix() const { - return Matrix2x2(fabs(mRows[0][0]), fabs(mRows[0][1]), - fabs(mRows[1][0]), fabs(mRows[1][1])); + return Matrix2x2(std::abs(mRows[0][0]), std::abs(mRows[0][1]), + std::abs(mRows[1][0]), std::abs(mRows[1][1])); } // Overloaded operator for addition diff --git a/include/reactphysics3d/mathematics/Matrix3x3.h b/include/reactphysics3d/mathematics/Matrix3x3.h index 575adfc4..0a2661a0 100644 --- a/include/reactphysics3d/mathematics/Matrix3x3.h +++ b/include/reactphysics3d/mathematics/Matrix3x3.h @@ -261,9 +261,9 @@ RP3D_FORCE_INLINE Matrix3x3 Matrix3x3::computeSkewSymmetricMatrixForCrossProduct // Return the matrix with absolute values RP3D_FORCE_INLINE Matrix3x3 Matrix3x3::getAbsoluteMatrix() const { - return Matrix3x3(std::fabs(mRows[0][0]), std::fabs(mRows[0][1]), std::fabs(mRows[0][2]), - std::fabs(mRows[1][0]), std::fabs(mRows[1][1]), std::fabs(mRows[1][2]), - std::fabs(mRows[2][0]), std::fabs(mRows[2][1]), std::fabs(mRows[2][2])); + return Matrix3x3(std::abs(mRows[0][0]), std::abs(mRows[0][1]), std::abs(mRows[0][2]), + std::abs(mRows[1][0]), std::abs(mRows[1][1]), std::abs(mRows[1][2]), + std::abs(mRows[2][0]), std::abs(mRows[2][1]), std::abs(mRows[2][2])); } // Overloaded operator for addition diff --git a/include/reactphysics3d/mathematics/Vector2.h b/include/reactphysics3d/mathematics/Vector2.h index aee3214d..a19598a6 100644 --- a/include/reactphysics3d/mathematics/Vector2.h +++ b/include/reactphysics3d/mathematics/Vector2.h @@ -148,6 +148,9 @@ struct Vector2 { /// Return the zero vector static Vector2 zero(); + /// Function to test if two vectors are (almost) equal + static bool approxEqual(const Vector2& vec1, const Vector2& vec2, decimal epsilon = MACHINE_EPSILON); + // -------------------- Friends -------------------- // friend Vector2 operator+(const Vector2& vector1, const Vector2& vector2); @@ -230,7 +233,7 @@ RP3D_FORCE_INLINE int Vector2::getMaxAxis() const { // Return true if the vector is unit and false otherwise RP3D_FORCE_INLINE bool Vector2::isUnit() const { - return approxEqual(lengthSquare(), 1.0); + return reactphysics3d::approxEqual(lengthSquare(), decimal(1.0)); } // Return true if the values are not NAN OR INF @@ -240,7 +243,7 @@ RP3D_FORCE_INLINE bool Vector2::isFinite() const { // Return true if the vector is the zero vector RP3D_FORCE_INLINE bool Vector2::isZero() const { - return approxEqual(lengthSquare(), 0.0); + return reactphysics3d::approxEqual(lengthSquare(), decimal(0.0)); } // Overloaded operator for the equality condition @@ -371,6 +374,11 @@ RP3D_FORCE_INLINE Vector2 Vector2::zero() { return Vector2(0, 0); } +// Function to test if two vectors are (almost) equal +RP3D_FORCE_INLINE bool approxEqual(const Vector2& vec1, const Vector2& vec2, decimal epsilon) { + return approxEqual(vec1.x, vec2.x, epsilon) && approxEqual(vec1.y, vec2.y, epsilon); +} + } #endif diff --git a/include/reactphysics3d/mathematics/Vector3.h b/include/reactphysics3d/mathematics/Vector3.h index b1adf487..a6dae046 100644 --- a/include/reactphysics3d/mathematics/Vector3.h +++ b/include/reactphysics3d/mathematics/Vector3.h @@ -28,8 +28,9 @@ // Libraries #include -#include +#include #include +#include #include /// ReactPhysics3D namespace @@ -161,6 +162,9 @@ struct Vector3 { /// Return the zero vector static Vector3 zero(); + /// Function to test if two vectors are (almost) equal + static bool approxEqual(const Vector3& vec1, const Vector3& vec2, decimal epsilon = MACHINE_EPSILON); + // -------------------- Friends -------------------- // friend Vector3 operator+(const Vector3& vector1, const Vector3& vector2); @@ -252,7 +256,7 @@ RP3D_FORCE_INLINE int Vector3::getMaxAxis() const { // Return true if the vector is unit and false otherwise RP3D_FORCE_INLINE bool Vector3::isUnit() const { - return approxEqual(lengthSquare(), 1.0); + return reactphysics3d::approxEqual(lengthSquare(), decimal(1.0)); } // Return true if the values are not NAN OR INF @@ -262,7 +266,7 @@ RP3D_FORCE_INLINE bool Vector3::isFinite() const { // Return true if the vector is the zero vector RP3D_FORCE_INLINE bool Vector3::isZero() const { - return approxEqual(lengthSquare(), 0.0); + return reactphysics3d::approxEqual(lengthSquare(), decimal(0.0)); } // Overloaded operator for the equality condition @@ -411,6 +415,12 @@ RP3D_FORCE_INLINE Vector3 Vector3::zero() { return Vector3(0, 0, 0); } +// Function to test if two vectors are (almost) equal +RP3D_FORCE_INLINE bool approxEqual(const Vector3& vec1, const Vector3& vec2, decimal epsilon) { + return approxEqual(vec1.x, vec2.x, epsilon) && approxEqual(vec1.y, vec2.y, epsilon) && + approxEqual(vec1.z, vec2.z, epsilon); +} + } #endif diff --git a/include/reactphysics3d/mathematics/mathematics_common.h b/include/reactphysics3d/mathematics/mathematics_common.h new file mode 100644 index 00000000..8c1ef6f5 --- /dev/null +++ b/include/reactphysics3d/mathematics/mathematics_common.h @@ -0,0 +1,50 @@ +/******************************************************************************** +* ReactPhysics3D physics library, http://www.reactphysics3d.com * +* Copyright (c) 2010-2020 Daniel Chappuis * +********************************************************************************* +* * +* This software is provided 'as-is', without any express or implied warranty. * +* In no event will the authors be held liable for any damages arising from the * +* use of this software. * +* * +* Permission is granted to anyone to use this software for any purpose, * +* including commercial applications, and to alter it and redistribute it * +* freely, subject to the following restrictions: * +* * +* 1. The origin of this software must not be misrepresented; you must not claim * +* that you wrote the original software. If you use this software in a * +* product, an acknowledgment in the product documentation would be * +* appreciated but is not required. * +* * +* 2. Altered source versions must be plainly marked as such, and must not be * +* misrepresented as being the original software. * +* * +* 3. This notice may not be removed or altered from any source distribution. * +* * +********************************************************************************/ + +#ifndef REACTPHYSICS3D_MATHEMATICS_COMMON_H +#define REACTPHYSICS3D_MATHEMATICS_COMMON_H + +// Libraries +#include +#include +#include +#include + +/// ReactPhysics3D namespace +namespace reactphysics3d { + +// ---------- Mathematics functions ---------- // + +/// Function to test if two real numbers are (almost) equal +/// We test if two numbers a and b are such that (a-b) are in [-EPSILON; EPSILON] +RP3D_FORCE_INLINE bool approxEqual(decimal a, decimal b, decimal epsilon = MACHINE_EPSILON) { + return (std::abs(a - b) < epsilon); +} + + +} + + +#endif diff --git a/include/reactphysics3d/mathematics/mathematics_functions.h b/include/reactphysics3d/mathematics/mathematics_functions.h index 2f1aa0d5..fb333658 100755 --- a/include/reactphysics3d/mathematics/mathematics_functions.h +++ b/include/reactphysics3d/mathematics/mathematics_functions.h @@ -29,6 +29,7 @@ // Libraries #include #include +#include #include #include #include @@ -42,18 +43,6 @@ struct Vector2; // ---------- Mathematics functions ---------- // -/// Function to test if two real numbers are (almost) equal -/// We test if two numbers a and b are such that (a-b) are in [-EPSILON; EPSILON] -RP3D_FORCE_INLINE bool approxEqual(decimal a, decimal b, decimal epsilon = MACHINE_EPSILON) { - return (std::fabs(a - b) < epsilon); -} - -/// Function to test if two vectors are (almost) equal -bool approxEqual(const Vector3& vec1, const Vector3& vec2, decimal epsilon = MACHINE_EPSILON); - -/// Function to test if two vectors are (almost) equal -bool approxEqual(const Vector2& vec1, const Vector2& vec2, decimal epsilon = MACHINE_EPSILON); - /// Function that returns the result of the "value" clamped by /// two others values "lowerLimit" and "upperLimit" RP3D_FORCE_INLINE int clamp(int value, int lowerLimit, int upperLimit) { @@ -83,48 +72,347 @@ RP3D_FORCE_INLINE bool sameSign(decimal a, decimal b) { return a * b >= decimal(0.0); } -/// Return true if two vectors are parallel -bool areParallelVectors(const Vector3& vector1, const Vector3& vector2); +// Return true if two vectors are parallel +RP3D_FORCE_INLINE bool areParallelVectors(const Vector3& vector1, const Vector3& vector2) { + return vector1.cross(vector2).lengthSquare() < decimal(0.00001); +} -/// Return true if two vectors are orthogonal -bool areOrthogonalVectors(const Vector3& vector1, const Vector3& vector2); -/// Clamp a vector such that it is no longer than a given maximum length -Vector3 clamp(const Vector3& vector, decimal maxLength); +// Return true if two vectors are orthogonal +RP3D_FORCE_INLINE bool areOrthogonalVectors(const Vector3& vector1, const Vector3& vector2) { + return std::abs(vector1.dot(vector2)) < decimal(0.001); +} + + +// Clamp a vector such that it is no longer than a given maximum length +RP3D_FORCE_INLINE Vector3 clamp(const Vector3& vector, decimal maxLength) { + if (vector.lengthSquare() > maxLength * maxLength) { + return vector.getUnit() * maxLength; + } + return vector; +} // Compute and return a point on segment from "segPointA" and "segPointB" that is closest to point "pointC" -Vector3 computeClosestPointOnSegment(const Vector3& segPointA, const Vector3& segPointB, const Vector3& pointC); +RP3D_FORCE_INLINE Vector3 computeClosestPointOnSegment(const Vector3& segPointA, const Vector3& segPointB, const Vector3& pointC) { + + const Vector3 ab = segPointB - segPointA; + + decimal abLengthSquare = ab.lengthSquare(); + + // If the segment has almost zero length + if (abLengthSquare < MACHINE_EPSILON) { + + // Return one end-point of the segment as the closest point + return segPointA; + } + + // Project point C onto "AB" line + decimal t = (pointC - segPointA).dot(ab) / abLengthSquare; + + // If projected point onto the line is outside the segment, clamp it to the segment + if (t < decimal(0.0)) t = decimal(0.0); + if (t > decimal(1.0)) t = decimal(1.0); + + // Return the closest point on the segment + return segPointA + t * ab; +} // Compute the closest points between two segments -void computeClosestPointBetweenTwoSegments(const Vector3& seg1PointA, const Vector3& seg1PointB, - const Vector3& seg2PointA, const Vector3& seg2PointB, - Vector3& closestPointSeg1, Vector3& closestPointSeg2); +// This method uses the technique described in the book Real-Time +// collision detection by Christer Ericson. +RP3D_FORCE_INLINE void computeClosestPointBetweenTwoSegments(const Vector3& seg1PointA, const Vector3& seg1PointB, + const Vector3& seg2PointA, const Vector3& seg2PointB, + Vector3& closestPointSeg1, Vector3& closestPointSeg2) { -/// Compute the barycentric coordinates u, v, w of a point p inside the triangle (a, b, c) -void computeBarycentricCoordinatesInTriangle(const Vector3& a, const Vector3& b, const Vector3& c, - const Vector3& p, decimal& u, decimal& v, decimal& w); + const Vector3 d1 = seg1PointB - seg1PointA; + const Vector3 d2 = seg2PointB - seg2PointA; + const Vector3 r = seg1PointA - seg2PointA; + decimal a = d1.lengthSquare(); + decimal e = d2.lengthSquare(); + decimal f = d2.dot(r); + decimal s, t; -/// Compute the intersection between a plane and a segment -decimal computePlaneSegmentIntersection(const Vector3& segA, const Vector3& segB, const decimal planeD, const Vector3& planeNormal); + // If both segments degenerate into points + if (a <= MACHINE_EPSILON && e <= MACHINE_EPSILON) { -/// Compute the distance between a point and a line -decimal computePointToLineDistance(const Vector3& linePointA, const Vector3& linePointB, const Vector3& point); + closestPointSeg1 = seg1PointA; + closestPointSeg2 = seg2PointA; + return; + } + if (a <= MACHINE_EPSILON) { // If first segment degenerates into a point -/// Clip a segment against multiple planes and return the clipped segment vertices -Array clipSegmentWithPlanes(const Vector3& segA, const Vector3& segB, + s = decimal(0.0); + + // Compute the closest point on second segment + t = clamp(f / e, decimal(0.0), decimal(1.0)); + } + else { + + decimal c = d1.dot(r); + + // If the second segment degenerates into a point + if (e <= MACHINE_EPSILON) { + + t = decimal(0.0); + s = clamp(-c / a, decimal(0.0), decimal(1.0)); + } + else { + + decimal b = d1.dot(d2); + decimal denom = a * e - b * b; + + // If the segments are not parallel + if (denom != decimal(0.0)) { + + // Compute the closest point on line 1 to line 2 and + // clamp to first segment. + s = clamp((b * f - c * e) / denom, decimal(0.0), decimal(1.0)); + } + else { + + // Pick an arbitrary point on first segment + s = decimal(0.0); + } + + // Compute the point on line 2 closest to the closest point + // we have just found + t = (b * s + f) / e; + + // If this closest point is inside second segment (t in [0, 1]), we are done. + // Otherwise, we clamp the point to the second segment and compute again the + // closest point on segment 1 + if (t < decimal(0.0)) { + t = decimal(0.0); + s = clamp(-c / a, decimal(0.0), decimal(1.0)); + } + else if (t > decimal(1.0)) { + t = decimal(1.0); + s = clamp((b - c) / a, decimal(0.0), decimal(1.0)); + } + } + } + + // Compute the closest points on both segments + closestPointSeg1 = seg1PointA + d1 * s; + closestPointSeg2 = seg2PointA + d2 * t; +} + +// Compute the barycentric coordinates u, v, w of a point p inside the triangle (a, b, c) +// This method uses the technique described in the book Real-Time collision detection by +// Christer Ericson. +RP3D_FORCE_INLINE void computeBarycentricCoordinatesInTriangle(const Vector3& a, const Vector3& b, const Vector3& c, + const Vector3& p, decimal& u, decimal& v, decimal& w) { + const Vector3 v0 = b - a; + const Vector3 v1 = c - a; + const Vector3 v2 = p - a; + + const decimal d00 = v0.dot(v0); + const decimal d01 = v0.dot(v1); + const decimal d11 = v1.dot(v1); + const decimal d20 = v2.dot(v0); + const decimal d21 = v2.dot(v1); + + const decimal denom = d00 * d11 - d01 * d01; + v = (d11 * d20 - d01 * d21) / denom; + w = (d00 * d21 - d01 * d20) / denom; + u = decimal(1.0) - v - w; +} + +// Compute the intersection between a plane and a segment +// Let the plane define by the equation planeNormal.dot(X) = planeD with X a point on the plane and "planeNormal" the plane normal. This method +// computes the intersection P between the plane and the segment (segA, segB). The method returns the value "t" such +// that P = segA + t * (segB - segA). Note that it only returns a value in [0, 1] if there is an intersection. Otherwise, +// there is no intersection between the plane and the segment. +RP3D_FORCE_INLINE decimal computePlaneSegmentIntersection(const Vector3& segA, const Vector3& segB, const decimal planeD, const Vector3& planeNormal) { + + const decimal parallelEpsilon = decimal(0.0001); + decimal t = decimal(-1); + + const decimal nDotAB = planeNormal.dot(segB - segA); + + // If the segment is not parallel to the plane + if (std::abs(nDotAB) > parallelEpsilon) { + t = (planeD - planeNormal.dot(segA)) / nDotAB; + } + + return t; +} + +// Compute the distance between a point "point" and a line given by the points "linePointA" and "linePointB" +RP3D_FORCE_INLINE decimal computePointToLineDistance(const Vector3& linePointA, const Vector3& linePointB, const Vector3& point) { + + decimal distAB = (linePointB - linePointA).length(); + + if (distAB < MACHINE_EPSILON) { + return (point - linePointA).length(); + } + + return ((point - linePointA).cross(point - linePointB)).length() / distAB; +} + + +// Clip a segment against multiple planes and return the clipped segment vertices +// This method implements the Sutherland–Hodgman clipping algorithm +RP3D_FORCE_INLINE Array clipSegmentWithPlanes(const Vector3& segA, const Vector3& segB, const Array& planesPoints, const Array& planesNormals, - MemoryAllocator& allocator); + MemoryAllocator& allocator) { + assert(planesPoints.size() == planesNormals.size()); -/// Clip a polygon against multiple planes and return the clipped polygon vertices -Array clipPolygonWithPlanes(const Array& polygonVertices, const Array& planesPoints, - const Array& planesNormals, MemoryAllocator& allocator); + Array inputVertices(allocator, 2); + Array outputVertices(allocator, 2); -/// Project a point onto a plane that is given by a point and its unit length normal -Vector3 projectPointOntoPlane(const Vector3& point, const Vector3& planeNormal, const Vector3& planePoint); + inputVertices.add(segA); + inputVertices.add(segB); -/// Return the distance between a point and a plane (the plane normal must be normalized) -decimal computePointToPlaneDistance(const Vector3& point, const Vector3& planeNormal, const Vector3& planePoint); + // For each clipping plane + const uint32 nbPlanesPoints = planesPoints.size(); + for (uint32 p=0; p < nbPlanesPoints; p++) { + + // If there is no more vertices, stop + if (inputVertices.size() == 0) return inputVertices; + + assert(inputVertices.size() == 2); + + outputVertices.clear(); + + Vector3& v1 = inputVertices[0]; + Vector3& v2 = inputVertices[1]; + + decimal v1DotN = (v1 - planesPoints[p]).dot(planesNormals[p]); + decimal v2DotN = (v2 - planesPoints[p]).dot(planesNormals[p]); + + // If the second vertex is in front of the clippling plane + if (v2DotN >= decimal(0.0)) { + + // If the first vertex is not in front of the clippling plane + if (v1DotN < decimal(0.0)) { + + // The second point we keep is the intersection between the segment v1, v2 and the clipping plane + decimal t = computePlaneSegmentIntersection(v1, v2, planesNormals[p].dot(planesPoints[p]), planesNormals[p]); + + if (t >= decimal(0) && t <= decimal(1.0)) { + outputVertices.add(v1 + t * (v2 - v1)); + } + else { + outputVertices.add(v2); + } + } + else { + outputVertices.add(v1); + } + + // Add the second vertex + outputVertices.add(v2); + } + else { // If the second vertex is behind the clipping plane + + // If the first vertex is in front of the clippling plane + if (v1DotN >= decimal(0.0)) { + + outputVertices.add(v1); + + // The first point we keep is the intersection between the segment v1, v2 and the clipping plane + decimal t = computePlaneSegmentIntersection(v1, v2, -planesNormals[p].dot(planesPoints[p]), -planesNormals[p]); + + if (t >= decimal(0.0) && t <= decimal(1.0)) { + outputVertices.add(v1 + t * (v2 - v1)); + } + } + } + + inputVertices = outputVertices; + } + + return outputVertices; +} + +// Clip a polygon against multiple planes and return the clipped polygon vertices +// This method implements the Sutherland–Hodgman clipping algorithm +RP3D_FORCE_INLINE void clipPolygonWithPlanes(const Array& polygonVertices, const Array& planesPoints, + const Array& planesNormals, Array& outClippedPolygonVertices, + MemoryAllocator& allocator) { + + assert(planesPoints.size() == planesNormals.size()); + + const uint32 nbMaxElements = polygonVertices.size() + planesPoints.size(); + Array inputVertices(allocator, nbMaxElements); + + outClippedPolygonVertices.reserve(nbMaxElements); + + inputVertices.addRange(polygonVertices); + + // For each clipping plane + const uint32 nbPlanesPoints = planesPoints.size(); + for (uint32 p=0; p < nbPlanesPoints; p++) { + + outClippedPolygonVertices.clear(); + + const uint32 nbInputVertices = inputVertices.size(); + uint32 vStart = nbInputVertices - 1; + + // For each edge of the polygon + for (uint vEnd = 0; vEnd= decimal(0.0)) { + + // If the first vertex is not in front of the clippling plane + if (v1DotN < decimal(0.0)) { + + // The second point we keep is the intersection between the segment v1, v2 and the clipping plane + decimal t = computePlaneSegmentIntersection(v1, v2, planesNormals[p].dot(planesPoints[p]), planesNormals[p]); + + if (t >= decimal(0) && t <= decimal(1.0)) { + outClippedPolygonVertices.add(v1 + t * (v2 - v1)); + } + else { + outClippedPolygonVertices.add(v2); + } + } + + // Add the second vertex + outClippedPolygonVertices.add(v2); + } + else { // If the second vertex is behind the clipping plane + + // If the first vertex is in front of the clippling plane + if (v1DotN >= decimal(0.0)) { + + // The first point we keep is the intersection between the segment v1, v2 and the clipping plane + decimal t = computePlaneSegmentIntersection(v1, v2, -planesNormals[p].dot(planesPoints[p]), -planesNormals[p]); + + if (t >= decimal(0.0) && t <= decimal(1.0)) { + outClippedPolygonVertices.add(v1 + t * (v2 - v1)); + } + else { + outClippedPolygonVertices.add(v1); + } + } + } + + vStart = vEnd; + } + + inputVertices = outClippedPolygonVertices; + } +} + +// Project a point onto a plane that is given by a point and its unit length normal +RP3D_FORCE_INLINE Vector3 projectPointOntoPlane(const Vector3& point, const Vector3& unitPlaneNormal, const Vector3& planePoint) { + return point - unitPlaneNormal.dot(point - planePoint) * unitPlaneNormal; +} + +// Return the distance between a point and a plane (the plane normal must be normalized) +RP3D_FORCE_INLINE decimal computePointToPlaneDistance(const Vector3& point, const Vector3& planeNormal, const Vector3& planePoint) { + return planeNormal.dot(point - planePoint); +} /// Return true if a number is a power of two RP3D_FORCE_INLINE bool isPowerOfTwo(uint32 number) { diff --git a/include/reactphysics3d/systems/ContactSolverSystem.h b/include/reactphysics3d/systems/ContactSolverSystem.h index 9c5a87db..213e9baa 100644 --- a/include/reactphysics3d/systems/ContactSolverSystem.h +++ b/include/reactphysics3d/systems/ContactSolverSystem.h @@ -30,6 +30,7 @@ #include #include #include +#include #include /// ReactPhysics3D namespace diff --git a/src/collision/narrowphase/SAT/SATAlgorithm.cpp b/src/collision/narrowphase/SAT/SATAlgorithm.cpp index bf175b01..3e1daa4c 100644 --- a/src/collision/narrowphase/SAT/SATAlgorithm.cpp +++ b/src/collision/narrowphase/SAT/SATAlgorithm.cpp @@ -959,7 +959,8 @@ bool SATAlgorithm::computePolyhedronVsPolyhedronFaceContactPoints(bool isMinPene assert(planesNormals.size() == planesPoints.size()); // Clip the reference faces with the adjacent planes of the reference face - Array clipPolygonVertices = clipPolygonWithPlanes(polygonVertices, planesPoints, planesNormals, mMemoryAllocator); + Array clipPolygonVertices(mMemoryAllocator); + clipPolygonWithPlanes(polygonVertices, planesPoints, planesNormals, clipPolygonVertices, mMemoryAllocator); // We only keep the clipped points that are below the reference face const Vector3 referenceFaceVertex = referencePolyhedron->getVertexPosition(referencePolyhedron->getHalfEdge(firstEdgeIndex).vertexIndex); diff --git a/src/mathematics/Vector3.cpp b/src/mathematics/Vector3.cpp index 5509e853..b6bfca54 100644 --- a/src/mathematics/Vector3.cpp +++ b/src/mathematics/Vector3.cpp @@ -48,7 +48,7 @@ Vector3 Vector3::getOneUnitOrthogonalVector() const { assert(length() > MACHINE_EPSILON); // Get the minimum element of the vector - Vector3 vectorAbs(std::fabs(x), std::fabs(y), std::fabs(z)); + Vector3 vectorAbs(std::abs(x), std::abs(y), std::abs(z)); int minElement = vectorAbs.getMinAxis(); if (minElement == 0) { diff --git a/src/mathematics/mathematics_functions.cpp b/src/mathematics/mathematics_functions.cpp deleted file mode 100755 index 57995192..00000000 --- a/src/mathematics/mathematics_functions.cpp +++ /dev/null @@ -1,383 +0,0 @@ -/******************************************************************************** -* ReactPhysics3D physics library, http://www.reactphysics3d.com * -* Copyright (c) 2010-2020 Daniel Chappuis * -********************************************************************************* -* * -* This software is provided 'as-is', without any express or implied warranty. * -* In no event will the authors be held liable for any damages arising from the * -* use of this software. * -* * -* Permission is granted to anyone to use this software for any purpose, * -* including commercial applications, and to alter it and redistribute it * -* freely, subject to the following restrictions: * -* * -* 1. The origin of this software must not be misrepresented; you must not claim * -* that you wrote the original software. If you use this software in a * -* product, an acknowledgment in the product documentation would be * -* appreciated but is not required. * -* * -* 2. Altered source versions must be plainly marked as such, and must not be * -* misrepresented as being the original software. * -* * -* 3. This notice may not be removed or altered from any source distribution. * -* * -********************************************************************************/ - -// Libraries -#include -#include -#include -#include - -using namespace reactphysics3d; - - -// Function to test if two vectors are (almost) equal -bool reactphysics3d::approxEqual(const Vector3& vec1, const Vector3& vec2, decimal epsilon) { - return approxEqual(vec1.x, vec2.x, epsilon) && approxEqual(vec1.y, vec2.y, epsilon) && - approxEqual(vec1.z, vec2.z, epsilon); -} - -// Function to test if two vectors are (almost) equal -bool reactphysics3d::approxEqual(const Vector2& vec1, const Vector2& vec2, decimal epsilon) { - return approxEqual(vec1.x, vec2.x, epsilon) && approxEqual(vec1.y, vec2.y, epsilon); -} - -// Compute the barycentric coordinates u, v, w of a point p inside the triangle (a, b, c) -// This method uses the technique described in the book Real-Time collision detection by -// Christer Ericson. -void reactphysics3d::computeBarycentricCoordinatesInTriangle(const Vector3& a, const Vector3& b, const Vector3& c, - const Vector3& p, decimal& u, decimal& v, decimal& w) { - const Vector3 v0 = b - a; - const Vector3 v1 = c - a; - const Vector3 v2 = p - a; - - decimal d00 = v0.dot(v0); - decimal d01 = v0.dot(v1); - decimal d11 = v1.dot(v1); - decimal d20 = v2.dot(v0); - decimal d21 = v2.dot(v1); - - decimal denom = d00 * d11 - d01 * d01; - v = (d11 * d20 - d01 * d21) / denom; - w = (d00 * d21 - d01 * d20) / denom; - u = decimal(1.0) - v - w; -} - -// Clamp a vector such that it is no longer than a given maximum length -Vector3 reactphysics3d::clamp(const Vector3& vector, decimal maxLength) { - if (vector.lengthSquare() > maxLength * maxLength) { - return vector.getUnit() * maxLength; - } - return vector; -} - -// Return true if two vectors are parallel -bool reactphysics3d::areParallelVectors(const Vector3& vector1, const Vector3& vector2) { - return vector1.cross(vector2).lengthSquare() < decimal(0.00001); -} - -// Return true if two vectors are orthogonal -bool reactphysics3d::areOrthogonalVectors(const Vector3& vector1, const Vector3& vector2) { - return std::abs(vector1.dot(vector2)) < decimal(0.001); -} - -// Compute and return a point on segment from "segPointA" and "segPointB" that is closest to point "pointC" -Vector3 reactphysics3d::computeClosestPointOnSegment(const Vector3& segPointA, const Vector3& segPointB, const Vector3& pointC) { - - const Vector3 ab = segPointB - segPointA; - - decimal abLengthSquare = ab.lengthSquare(); - - // If the segment has almost zero length - if (abLengthSquare < MACHINE_EPSILON) { - - // Return one end-point of the segment as the closest point - return segPointA; - } - - // Project point C onto "AB" line - decimal t = (pointC - segPointA).dot(ab) / abLengthSquare; - - // If projected point onto the line is outside the segment, clamp it to the segment - if (t < decimal(0.0)) t = decimal(0.0); - if (t > decimal(1.0)) t = decimal(1.0); - - // Return the closest point on the segment - return segPointA + t * ab; -} - -// Compute the closest points between two segments -// This method uses the technique described in the book Real-Time -// collision detection by Christer Ericson. -void reactphysics3d::computeClosestPointBetweenTwoSegments(const Vector3& seg1PointA, const Vector3& seg1PointB, - const Vector3& seg2PointA, const Vector3& seg2PointB, - Vector3& closestPointSeg1, Vector3& closestPointSeg2) { - - const Vector3 d1 = seg1PointB - seg1PointA; - const Vector3 d2 = seg2PointB - seg2PointA; - const Vector3 r = seg1PointA - seg2PointA; - decimal a = d1.lengthSquare(); - decimal e = d2.lengthSquare(); - decimal f = d2.dot(r); - decimal s, t; - - // If both segments degenerate into points - if (a <= MACHINE_EPSILON && e <= MACHINE_EPSILON) { - - closestPointSeg1 = seg1PointA; - closestPointSeg2 = seg2PointA; - return; - } - if (a <= MACHINE_EPSILON) { // If first segment degenerates into a point - - s = decimal(0.0); - - // Compute the closest point on second segment - t = clamp(f / e, decimal(0.0), decimal(1.0)); - } - else { - - decimal c = d1.dot(r); - - // If the second segment degenerates into a point - if (e <= MACHINE_EPSILON) { - - t = decimal(0.0); - s = clamp(-c / a, decimal(0.0), decimal(1.0)); - } - else { - - decimal b = d1.dot(d2); - decimal denom = a * e - b * b; - - // If the segments are not parallel - if (denom != decimal(0.0)) { - - // Compute the closest point on line 1 to line 2 and - // clamp to first segment. - s = clamp((b * f - c * e) / denom, decimal(0.0), decimal(1.0)); - } - else { - - // Pick an arbitrary point on first segment - s = decimal(0.0); - } - - // Compute the point on line 2 closest to the closest point - // we have just found - t = (b * s + f) / e; - - // If this closest point is inside second segment (t in [0, 1]), we are done. - // Otherwise, we clamp the point to the second segment and compute again the - // closest point on segment 1 - if (t < decimal(0.0)) { - t = decimal(0.0); - s = clamp(-c / a, decimal(0.0), decimal(1.0)); - } - else if (t > decimal(1.0)) { - t = decimal(1.0); - s = clamp((b - c) / a, decimal(0.0), decimal(1.0)); - } - } - } - - // Compute the closest points on both segments - closestPointSeg1 = seg1PointA + d1 * s; - closestPointSeg2 = seg2PointA + d2 * t; -} - -// Compute the intersection between a plane and a segment -// Let the plane define by the equation planeNormal.dot(X) = planeD with X a point on the plane and "planeNormal" the plane normal. This method -// computes the intersection P between the plane and the segment (segA, segB). The method returns the value "t" such -// that P = segA + t * (segB - segA). Note that it only returns a value in [0, 1] if there is an intersection. Otherwise, -// there is no intersection between the plane and the segment. -decimal reactphysics3d::computePlaneSegmentIntersection(const Vector3& segA, const Vector3& segB, const decimal planeD, const Vector3& planeNormal) { - - const decimal parallelEpsilon = decimal(0.0001); - decimal t = decimal(-1); - - decimal nDotAB = planeNormal.dot(segB - segA); - - // If the segment is not parallel to the plane - if (std::abs(nDotAB) > parallelEpsilon) { - t = (planeD - planeNormal.dot(segA)) / nDotAB; - } - - return t; -} - -// Compute the distance between a point "point" and a line given by the points "linePointA" and "linePointB" -decimal reactphysics3d::computePointToLineDistance(const Vector3& linePointA, const Vector3& linePointB, const Vector3& point) { - - decimal distAB = (linePointB - linePointA).length(); - - if (distAB < MACHINE_EPSILON) { - return (point - linePointA).length(); - } - - return ((point - linePointA).cross(point - linePointB)).length() / distAB; -} - -// Clip a segment against multiple planes and return the clipped segment vertices -// This method implements the Sutherland–Hodgman clipping algorithm -Array reactphysics3d::clipSegmentWithPlanes(const Vector3& segA, const Vector3& segB, - const Array& planesPoints, - const Array& planesNormals, - MemoryAllocator& allocator) { - assert(planesPoints.size() == planesNormals.size()); - - Array inputVertices(allocator, 2); - Array outputVertices(allocator, 2); - - inputVertices.add(segA); - inputVertices.add(segB); - - // For each clipping plane - const uint32 nbPlanesPoints = planesPoints.size(); - for (uint32 p=0; p < nbPlanesPoints; p++) { - - // If there is no more vertices, stop - if (inputVertices.size() == 0) return inputVertices; - - assert(inputVertices.size() == 2); - - outputVertices.clear(); - - Vector3& v1 = inputVertices[0]; - Vector3& v2 = inputVertices[1]; - - decimal v1DotN = (v1 - planesPoints[p]).dot(planesNormals[p]); - decimal v2DotN = (v2 - planesPoints[p]).dot(planesNormals[p]); - - // If the second vertex is in front of the clippling plane - if (v2DotN >= decimal(0.0)) { - - // If the first vertex is not in front of the clippling plane - if (v1DotN < decimal(0.0)) { - - // The second point we keep is the intersection between the segment v1, v2 and the clipping plane - decimal t = computePlaneSegmentIntersection(v1, v2, planesNormals[p].dot(planesPoints[p]), planesNormals[p]); - - if (t >= decimal(0) && t <= decimal(1.0)) { - outputVertices.add(v1 + t * (v2 - v1)); - } - else { - outputVertices.add(v2); - } - } - else { - outputVertices.add(v1); - } - - // Add the second vertex - outputVertices.add(v2); - } - else { // If the second vertex is behind the clipping plane - - // If the first vertex is in front of the clippling plane - if (v1DotN >= decimal(0.0)) { - - outputVertices.add(v1); - - // The first point we keep is the intersection between the segment v1, v2 and the clipping plane - decimal t = computePlaneSegmentIntersection(v1, v2, -planesNormals[p].dot(planesPoints[p]), -planesNormals[p]); - - if (t >= decimal(0.0) && t <= decimal(1.0)) { - outputVertices.add(v1 + t * (v2 - v1)); - } - } - } - - inputVertices = outputVertices; - } - - return outputVertices; -} - -// Clip a polygon against multiple planes and return the clipped polygon vertices -// This method implements the Sutherland–Hodgman clipping algorithm -Array reactphysics3d::clipPolygonWithPlanes(const Array& polygonVertices, const Array& planesPoints, - const Array& planesNormals, MemoryAllocator& allocator) { - - assert(planesPoints.size() == planesNormals.size()); - - uint32 nbMaxElements = polygonVertices.size() + planesPoints.size(); - Array inputVertices(allocator, nbMaxElements); - Array outputVertices(allocator, nbMaxElements); - - inputVertices.addRange(polygonVertices); - - // For each clipping plane - const uint32 nbPlanesPoints = planesPoints.size(); - for (uint32 p=0; p < nbPlanesPoints; p++) { - - outputVertices.clear(); - - uint32 nbInputVertices = inputVertices.size(); - uint32 vStart = nbInputVertices - 1; - - // For each edge of the polygon - for (uint vEnd = 0; vEnd= decimal(0.0)) { - - // If the first vertex is not in front of the clippling plane - if (v1DotN < decimal(0.0)) { - - // The second point we keep is the intersection between the segment v1, v2 and the clipping plane - decimal t = computePlaneSegmentIntersection(v1, v2, planesNormals[p].dot(planesPoints[p]), planesNormals[p]); - - if (t >= decimal(0) && t <= decimal(1.0)) { - outputVertices.add(v1 + t * (v2 - v1)); - } - else { - outputVertices.add(v2); - } - } - - // Add the second vertex - outputVertices.add(v2); - } - else { // If the second vertex is behind the clipping plane - - // If the first vertex is in front of the clippling plane - if (v1DotN >= decimal(0.0)) { - - // The first point we keep is the intersection between the segment v1, v2 and the clipping plane - decimal t = computePlaneSegmentIntersection(v1, v2, -planesNormals[p].dot(planesPoints[p]), -planesNormals[p]); - - if (t >= decimal(0.0) && t <= decimal(1.0)) { - outputVertices.add(v1 + t * (v2 - v1)); - } - else { - outputVertices.add(v1); - } - } - } - - vStart = vEnd; - } - - inputVertices = outputVertices; - } - - return outputVertices; -} - -// Project a point onto a plane that is given by a point and its unit length normal -Vector3 reactphysics3d::projectPointOntoPlane(const Vector3& point, const Vector3& unitPlaneNormal, const Vector3& planePoint) { - return point - unitPlaneNormal.dot(point - planePoint) * unitPlaneNormal; -} - -// Return the distance between a point and a plane (the plane normal must be normalized) -decimal reactphysics3d::computePointToPlaneDistance(const Vector3& point, const Vector3& planeNormal, const Vector3& planePoint) { - return planeNormal.dot(point - planePoint); -} diff --git a/src/systems/SolveHingeJointSystem.cpp b/src/systems/SolveHingeJointSystem.cpp index a4f9946e..ee8864ea 100644 --- a/src/systems/SolveHingeJointSystem.cpp +++ b/src/systems/SolveHingeJointSystem.cpp @@ -852,13 +852,13 @@ decimal SolveHingeJointSystem::computeCorrespondingAngleNearLimits(decimal input return inputAngle; } else if (inputAngle > upperLimitAngle) { - decimal diffToUpperLimit = std::fabs(computeNormalizedAngle(inputAngle - upperLimitAngle)); - decimal diffToLowerLimit = std::fabs(computeNormalizedAngle(inputAngle - lowerLimitAngle)); + decimal diffToUpperLimit = std::abs(computeNormalizedAngle(inputAngle - upperLimitAngle)); + decimal diffToLowerLimit = std::abs(computeNormalizedAngle(inputAngle - lowerLimitAngle)); return (diffToUpperLimit > diffToLowerLimit) ? (inputAngle - PI_TIMES_2) : inputAngle; } else if (inputAngle < lowerLimitAngle) { - decimal diffToUpperLimit = std::fabs(computeNormalizedAngle(upperLimitAngle - inputAngle)); - decimal diffToLowerLimit = std::fabs(computeNormalizedAngle(lowerLimitAngle - inputAngle)); + decimal diffToUpperLimit = std::abs(computeNormalizedAngle(upperLimitAngle - inputAngle)); + decimal diffToLowerLimit = std::abs(computeNormalizedAngle(lowerLimitAngle - inputAngle)); return (diffToUpperLimit > diffToLowerLimit) ? inputAngle : (inputAngle + PI_TIMES_2); } else { diff --git a/test/tests/mathematics/TestMathematicsFunctions.h b/test/tests/mathematics/TestMathematicsFunctions.h index b5c77fe2..77c4ee52 100644 --- a/test/tests/mathematics/TestMathematicsFunctions.h +++ b/test/tests/mathematics/TestMathematicsFunctions.h @@ -253,7 +253,8 @@ class TestMathematicsFunctions : public Test { polygonPlanesNormals.add(Vector3(0, -1, 0)); polygonPlanesPoints.add(Vector3(10, 5, 0)); - Array clipPolygonVertices = clipPolygonWithPlanes(polygonVertices, polygonPlanesPoints, polygonPlanesNormals, mAllocator); + Array clipPolygonVertices(mAllocator); + clipPolygonWithPlanes(polygonVertices, polygonPlanesPoints, polygonPlanesNormals, clipPolygonVertices, mAllocator); rp3d_test(clipPolygonVertices.size() == 4); rp3d_test(approxEqual(clipPolygonVertices[0].x, 0, 0.000001)); rp3d_test(approxEqual(clipPolygonVertices[0].y, 2, 0.000001)); From 9e4e0477968e5f76ec79a084255a8711db77eb35 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Sat, 12 Sep 2020 21:12:07 +0200 Subject: [PATCH 37/74] Edit user manual --- .../UserManual/ReactPhysics3D-UserManual.pdf | Bin 1064782 -> 1064341 bytes .../UserManual/ReactPhysics3D-UserManual.tex | 30 +++++++----------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/documentation/UserManual/ReactPhysics3D-UserManual.pdf b/documentation/UserManual/ReactPhysics3D-UserManual.pdf index f98fe369165886c930e93a4afe128753abf46e12..2b92b676e2f815dd3798d78a6b1fa9c433641572 100644 GIT binary patch delta 191920 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�`u&>d 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 HD)4^+*mF6h delta 192413 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%0DfW@2j1E1+yEIhBH=S09)A$tTmu5EE z2mI#Gj6NMIEJ|K&V4T(h9>^rEi@R9pC8}EeMg|z_ zq*uiT%A!>Z?NWtG#5KG);@Eh!Kev{^I?68Tvh*<1OhJaVg(*|pGVq{R)sFrKWJe|+ zI6-QM)m5M3!CbyvrSXcjWHCjD`2)Sn2mT5i6Ed9fNO|P~UVckDzn(l+VdcM)M-Q2C zG-1ihpdqcvz+x+)ldaq^HU7b-sp=%HJTV1}ald#9i?G@E<&NnXeS0(ocm;O%5=VQL zD1SRD>}%aaDuL;~R{cj#pPACcA$Q++V`~*)pHt<1>y$gYCtOT^Snkg12VfmqYY6TD zqbaYFs-8kNOSqbKS_eublENGKhaJ!URpdd#I8Q0EC3cL}VaqMlnuv|nG1ENmdLHxr z2aJ}diA+D<-E_1fXOgdIm-WU+5Q5b&ON9qx%vL~=a4870JxG)b&Qh{=35-- z@NH2)+$_K0tx%hYxD<}{IZwd?tOKabeJ(jh{teEb@HztDAme|aziZve^hK?IiQvVp5> zTKFaT7EYZytQWr?fU>fSTj$!KQDDHXb-gqyu#oU+w|W3L1yA7n)#$;CB@03iS|1A3 zVJI|**Q@1$FQzSSO56}Q3<;+V8@h@iB9RlGhHTl|%kB)DN@^!Io@mw?2ak)-d5rKl z3J)O-N00l{P34B#W3m)Jt9AwgH)=M;JJ$rwJ93{X<*6JBz!jSt0pv}g|5cNienp^L zv87AWoaSO(q(^`HD3x7xcP!N%H1&4Vv1OIZaNaxsYoA3ValME4K@c-X1Drmg3UMM4TrcO<3f3!u9hh$1diBXRnmQwivr-P`aIl9@NRie z0qcN(9Nq$*T1`!*r-57%4DePNnmUX6*=;?>M8D;bECpr1RV7{>?NW5limY}?f5b$) z=<5scZk+7})oq-u=Q>TeQC^M91#w@cYz|u2iUULf+`EoYJ_pH5_mxz09!*6sHZ*ui zE5SKu(%V~+1hum00`^i1dMbHmPYAE8(=~wNb*7@d_zeAA`h7yqW3ZpvVv53IHi|Pi zZLHI;^b2b*f(WNmSK5eDqUyFTy7E6{3I_g2Udh85d`g`qPp}_<|Hpd}GY3%uGDX^&{!cbD>;IY!12nX) zbvTjy1bao|E~8@EIHe6h+IL%KMWpWHmk){4ii1P9_F;>@dv@^54^{>Ah8_EkEsB1# z7S3%p@zA9d6_P9Zc;U#$sAwjoBw8`PEQ=kokz2_-@1Hl3ag?%4&y7U}L@n%cfbHZY zL-vqbLh9B_6N(02vxS0Luj&R^`dM&r zMp{Fs|BV|-EMrTDqYIC`$F^g-NeBdvwgy3Mc!9GS-7@D&sGBk9WXqik2FS&ehXx3D zG>O$Mo6M%l`kK9<1A+1?gU+I5ZIsoS6kN520?y&RsL9~4=2TGAN}vHcTf4hp8#gBF zD5CC@E^v9wPqzhiMt!sE4*iq(K8B(g8l%xaQw9}OxWncO%$N}Zsqtj7m|<8k(6)0R z19i6Js_YMH>uiQ+342EbT*=TNEMeLl7C-r08h?w&-IYitTDxR#=A3mFflJ6KWG_YsDQ6?iWgYGE$g2x9GW=M(!SmR<@ z2eiXamzlo6QC8cLc@!^Sv4)cbK4W!B=^hjoTKc8RqgBaxB^<8+0AJvYCI9LvV(x{I zY#0T6y;%9ZT(E4&8^!yL+DWU7431^n2C&*<|2ZOoVoso^p@H1z{WHV>ZB2+0?=Qh# zK?Sp(0{ub;b)G@#LZukPSHuBb$=I3^H2|{-MTP9IHi;|6($KC!>#{9stfKeW7$nDx z46HQ$odDUd>nywvs9tua<-EG~XyyMcDQPJXnoGNORAX!c3;d8Mq-WrnWqPtw9b~ksMeXegOTA}s9WSyw*Z9N>dR$Z2#+%&3Y0$O>gSC7O{pa=8trK)4 zsaV=MrAzW2a19TSvB}ok<>B5l+Y8wIVx9W!R!YcG8dY>>_T~Cz>*u9}IpFFYCwePX zg1f!HE3a@_Or+99YX_4N;N69FZJGgC>s}}S^Hs|F@1t$Q?0pUX!FW+2RG;L!U{&8u z&w_H6=qkI68pU4K}r$Z|(Tf4ZM6_4b*tsXJr2=iQ##km4Jpy9QAe$ro$&(ut@@bfZnC2 z$GCk-YeTPTkFlnlDp>(!3${c54@5|0E^=c32yO+7~f= zHK!tuR$uRhBZNV@XiYf!+$qt6|R{u16w)RzzwEBqo^!v zkg&G;;TqrXKbQI-9ph*~>}(IVA^wVl5F3R(mZFmPJi);sErLPCJA^p-4Zne=;!@rK z=e>)EnPUO8xp+7@xALp%C+oXL<|T5fFV-MIk;a>&0q*M+M1L}wikM(%;gnj$!h+rq zCU7R~z0{qn?CYEOuj{+4Ze;!yaS3KnmFasf1I>zV)}f<~VWdCi^AJ?_`&!piA`z1^y_ z25=nDV(wTY>spJj>4JaB1WX;&0O;0~mq?9LBH5k>?AI*3!yVEgxk25Vz?<-Vh zJf5>?X544u5i(SN?e7+|?qOeO)8*Ei2DxrD4mUWF$xZ>6Q)pcf&6Ne+#Yp@F6QFSJ zCz>qKexfN5%wU@ELJMJ3Gns`+!3j6=E{7$pnnHi2RLFN6)wiHA;)N4yWBQ&CmwtD< zc70_SLz8O~Z{l4~V^z<=b8>GUnp=ki`a}~c?KICv*XZZI$iTrrx<&k0!-DXiwNeO_ ztiO^GND>H=OU2BHVtySCDgg8&!1k)smysqt4Yrd1j>)3Xhn&DNV1I*YDqAJOP|477 zYD`Np-DGORsu_6xfrcQZohv)5txNvIsJZ1mym8(~U0!jPM?36RJ#(?Ox+}A@MPYEK zsneO_rf2@bxq_hi6CmA)O$m?YN;+Eq!F4U3r;<@er0>E*wqwA7Z2{m*g|B)*Fa_qa zE{qejzR$OdGR{EGk-F}-2-zsunye=KS3~3TWH>!mZG5dVSE~2v|A;p+^^hmJ1zg7t z;xpXgs}gZE-#oc`0}`#qptB*=J>qnCOPmgqXeKN#{q*ArpC3=eA^v#c?#B}~*#F&E z&5^LSd=*jsXK=voQ3>DRsQUQ}VTA0oyV3bL3B4EYs7%yD3Vcxs&(8q@kp>w;n18PY z$C(=#G8YemM%{MWSZ&K|3L(q^B6UW5U(eMWlGN1sB%_Mm1j)6va*{aD&H4G;Js5$l z%Yq>PEtnSHLl`VLCyJ{7H_8D2O(y|vS4rp?8d(}cT3DLq@%t!0qLv8_;6~Ytd1dQ? z+F^^OKh4jUzjdej_|)b0n$hzJnxp4+a(PK%bC;?r-DsMf1pzw}z%SNdtvqceJh9=n za94@P&@PQILcj^0`aYIUi%N!U_*8mEXf7bJIYlw@Gh7LKMU&^M&%vVHLMGOW-wAuV>ud<}z`VtlkFL za>@v8Xk<(7NevZ|@OxK>BLzzcam@NzvyLUCF}%}1Yjy;>Uz_#`=qJQtit?5ZkgTNb8OQS ziagDVTC;}ZuQFY}*+M_nx-*grW4~Fa=T;fn!lV(MVCo@{E>?dQ6~4DcS*vl_Znf($ z(B!MJuA{;Im4XHM80sfFg6ki5r>OGlLp%9UJNW=HKn5~>U{=#V;n$A-A8>MF{eTnV z2b{&Y{_U?C>f6=}$>Ji8iHh!BcY>Ujk&3JUY7aRXc66q4I*{i@tLUtYL<^!?ZBs&n zm=3NIl^!pz{_Wf>Viy#g5DODmgn4N5C$0|aoZ+D}LAGJ&6TX6Ehw|WT2lE2%QkgBz z$!h??HAZ0iwX1>((heBAy$jV~tFA5MQp)FNTczxK)MaBeLWu1QVa3Th$JL5EUR~_b zokcDF#3g&Z%IN?v3mS*qq)}aKt z%F7L3*QysN~Ip>`}0_rni`f!>f9jl1W;|t|W)Hd%6$Mu5`A^F7=5Jdo4 z>jl%jH@Y^Em=1NY45-Fz`NS7rMZ)oc$?H<5XD}6^mbNN4*Pg0447-;>FbLb8@J`E@ z`gII0l^_F(geS70=J3{|g$@o?g6l{lQ{0#a*$HLEP6*bpl44&rj2)$h#3_C~gFgg7I_3>dFP9O*W9ArPrOY zzNo{!fHAyRf9#Ues}I+@*O@;aDCGM}AGhq^D9;*fB8L{uy(;mT;dBZfV}3$8%Nbgw z(UG~&yvNsY)KJ?N)6)=Ye$V*JC_VUsOk%o1fang9_rY!*6QFnsE zpWh3g3;44-cs9AFJ!`8`W)jsnkg+TerkvPK_Dwj<1V4?Nzm7~}B z7N@xyiU}>)p~2-?MlesQhX(+3RHB9ph>6YodTb~TRP|g$*oxuDlhQv$AG=yb%YN$6 z(CMDm5m`@r`1IDqin}}R87Ws7G8g_WXW=B#$_B7Kp+D7be&sY1-Wx??m?Qq+`az@6`ca#`MW;LF53w{-oCVEdH%$ z%#CBOLSA;MFm$o9=pe&z~So!j(K zB$b8Ih+I^i-|i0TNV&6S(PZNSy|1DRzul($f>xb;e+3S6-Kbb)Q|}y5y^>w+J5Nzm zzPHaxzgy-YGV?jTpQ-@-eO)SLY@6J@rKITzp%{`H2%TxXh;}5q?=?It=(2QGwy9tw zp9j+=fN@6-x5nsAYtY-MOr5fO^|tMI9!qVX(!`e)-{zc{p`Ne^1pJ=p5oNyLr2m&}aL5xyO#IQ8J^1-s%7 zbl@9Ha)5*QYCZI`pPQRb|H4E*-_hzTNdA~Zirn$aHc1RQv8m7=X06N@|Is^N<;UGm zW>o@Pk@%_9MX)9JA><~gA@Fj@oK>~*fwaneqwJWA6i{pys(;-;+#@Gs=1i+lXKy5zQ?=`IJM<;99I^JN`iX=@ym?u-~vv=F%2%O=w z2*)NKnMb@uS?yfyk!%$_T6+p16hN1P?R7t@hI04Blq_c}8+&-)+9JHZ_UlHid*wnB z&X7B3?`_IW%**=U~8S9 zO>&*w`Ex_x4EvwV#q^mZgE(nSoIa#>F&r)$b@Z^tt=V5@^ih&_bTq2@fYRVf%M~}K z9DwjygY)EC+e@O~Zp)%(qQK)t&2yn+wNkl)KY#*?RBx_#K_1LysgcXMW`4bcA)im( zpCuI0hHr$U$8jTJ-fm;>8bxQGT&4Bt-#S+5ATg@#Sfl2-@hZR6a$_>=A5GOgth z^jrZDJF^8nA;^}Ld#DNYu{0J_uJNKeMxXr7q2+ja)#9jdT)GW*AX)q%l^7}-EiagR zIXy75iwG)6N#=&%#jyUl0nJj*0y(s^qn67Z^t+-$!OYfH;Zn3h7SeMV!N8-BH0B%f zMR1Wk4sdlHw1?WK(f zFszp)8XwBXuNe7OG`?slr2IJZ>n1C?VQe;XV5aHfME6B#xqiOu72|$C{#a`4=XhFx zQMf8uv8~7ZL2Tf)cbC=MoeaiZUy?zJaPvU;pCTacHH;PFutUR%_ubEM30p1}nzATV z_ULHrOpTeo+XPo^?~pKu6w~Q@4_2pZbtEwIKHd7=ucY}2jR*h`t z5$n!|DLUiXg(47p2r6X-2KsLu9V>PWA>z5iX+V}rnxZVEqAY6y zWhGp+1tCNPb#;AM3yS{Z1hO1TaF4^ff+6buhu>v@Nr;(b?OZhen~ok z8Bakbftl2i4q_wN3{Gnu(27~%+a!SS<=gP+{b*Yd>7Tc)<82(&lpwZ2%Y!On!{c`w z)7=bYZ};eY^c8jmi70+$RtGC0V%H1VW@rF(s_(g);Fj|gaFL{p$M@7 zv!~@VfTMykvvZ{p1Hex}&}GtK{K5AC;u>!1|Mx%np zTG$0lXEEfgsWLk%#!ISf*TM>XH5;j@U}U&zJkb_sAn&(CvkBq(H%>6Rx7y?dNGnLS>nMPEFMiq?6;?wBWW>6it zV?51{=h#b;p7s}=Pak0I3n0W&8hby&9HDP7;chEB(gYuGDL7omTRiyNJTp^7nr zD81E4^!v~}6H2-Qn5mGzKDwss=L-V}tZ9UtXBAB|?szcs8Cx`sr3QJB z2w?6v6*~p0h2_QVBL6k;pRYQrsXkpe+2<4mYnW68{7>?~8}=K;94M%MUThi_M#tb%GdqmEG?a;+~tq8e6&Mo8S#QHVoeV zWccamZ~C*f_i)8lU$R?~sp*kz`DakFm(R)e)KlT}Jf>FKW*j3;HDS{p zSv%O!PC!$eN?MclbcOH%06SA%Zp*W(9+}d~4~P+{I@1ii=ycbXR{zDWe7!~^3LsX5l)4S4XFYQo(s%Y!zWU}h%wN|(A1qJ));Ax8 z&Px`wsR8yzCG-Ul1LrQs(pfl?Q^hRbecV&s%(}A3nS}lHZm>XE=hIhdFOWhbK2;gQ z$2^`qQP=GY_QE(>`TLN7mIU5?k;7P7sn5<=b(vRh zb31na??}d}-bD3%M-*6g+2`#Vh7!Q}qUsX!!srF5OVQ%)@nXS9D#CHhNNP9|Y_0JG zEm27vJ^#z;5}<~OLE?1$ z5AdNe6XyCup-(LP3RxKpvmm@m;Wa#5t$tlNH>QfRI)9vL!>BE(F7m>in5Nr@#{vzj%lUhm03lW_>x_xSH03OkofWZ4`kp*L|pG=<|09 z*v?9Ih&}ZKr1M(g9^5Z`mUsX{MHF>_Bdrui%m`Jbl>2~SirbFJVtHCah0Tq)Ehq

aIm+H03+4KB-$YIlFM+3N&m?>i}G}x%qASkooAl+g6RhmVo?# zzUflVzb{ee^kBj~*s-zc(9UJhuN8%X3#ACBH&msgbSle7YTm~8t&wkUxgnCe>5qI* zw|np0LyjoEBUNVKrokLnw2Z7CaTDGWCXTL*j>R197xN zZi%~l%_BUvx>2la2`AjMQ0&Oc;*EG)?X$GYd(L@35R4#hSIdP)LC;siUSOo0PQ(m?6)+jaeLUBpOO86yAk+*PG9_eSPwFyK3{g6( zt7?RhrRz2d3a{sitI)Tq>UH_$9!)F6*LT;9j;9qLTB{u|e&EN0Hdsf4^wG-CX+ZF^ z1i`9a+e$m4+1HTT-J|K93d}S1Qu(xw?nl2<%)6}7ayq;4`s$8h=k7zYnn99z&<{%e zn;zQ+V=HCyjO#xDG|2NmJN$%0_grKvEf)CWtd zP@h$1m)Ax*y2S|{(X>56)OG5J+~ZliY;~rPMWW4%iGKS4=Gjzyn~*Ue;77vj^$Ef3 zSK?Ctws&QK?0pljjfH4vqhm4XuNPVuan0S5nri3}Z=dFzoKTV2ec!ucSM<&z;m{m9 zoBOh~Nr5&;sek?&WwWfI1g58E!f26l5+Z3O2{03*#QG0xDCb#8n{CT?1%Zn2?=#ti zT;cI|D&eF6n(BCybbJ!fcs+_hTSPYEFEVF1)T=X>-F#J-lYAJ2DyT-2ep{4{cP2Q! zV4By~3LAb3&IDsqX&6C@I$wI~tiZ!X&IO-frM@4d+R&IU4(&_Yy`@sHX`lLs>Q=OO zM$a`=(QI%vxAOv6Uk;7a&1K=Mqr9&hoKyP!Odw(aAyv*cs_V`Jka97k-$TsTa@)k# z26N2u1!RU<-#;O42rfQM|NT+Q?9w#mF+ib_nu6~Mips+$)pX%muB+gD#;joRoG*Zi z-UspGnb)J^599nnUc=V@kJs&VU$)ZhST6gZ_yTVnVjT5enER0WqK2QA1m5_kqA(mM z{t)*8fH~$bOS@GqRM|H>8B_AiPl$G3VpvW9!7*-Po`c>v@5(5&NJN}E; zsVS@X;8}saDCs+kH^0K6Y&?XSIDi*V59aMySvhepv0(QaqEsH;&7K|=+fM=G-ulOD z8xhi%2WyWlzAhtEWL>1QU!_IivMY|Ei!PiLph>0vQi{Xc!4zzCNSggl`JH3Do-R=5 zG=_cBq~GGo-J3m-S5W#)d{ccG9HLV9hZ1^7ERrGhOWh=a@W~lSNgT}90p5H}iz9J9 zY1BAD)pD{(pyW-Xclw(WRa5W#wPxh2vZY4j8tL;wCUGR|bzCTPdj*VxAEiK>NWdT$XK`1BANY?=&{)^DBpt9?aRPO8BM|<@U*w0J8k6i+@WIjB21ba!o zgrdy;MH!xRL6)D_oPjNwrzx{cqvI0ofJhW8COuT%!w=$cZo5YR+zrhWklv17xZsT< z$1c-bcVdLyf$8NfJ@uK=6g|UBI-y`rgT+c5ObD7C9Ja5kp*Mk*DqNxaDLMOpRPgqI`wQDr;|i;xCtzgm13p6@s=Pi6&f3vf;_5*?#tCkLa^uRR=!oufVjs- zC8g6>svS-~XZ}6>IU94YlFmS(}tpiKpB}hXud6@9~q6dRA66R+2S=tg4$A(X~j|wslRNP?~(RXt!UFZWWr*9Z?$*W?Uu7YYGM7w=Y ziiIJ2;r_a*9!ijRA8cpmo1FP#l(MTh_g&a8%HT;Qrm$igd_Qx@$GD8v~f9qyEdz*Lrybsl|=3Vae9?X0^F4cX({u5A!P2T zT&&kMy)t2C@||LK(DL7(9B&De){@TX{Sz%MC&&7LE>mW!F5?(9>ETsTI!SF(tm-lP z1*S<71|#ReAd?SU7r+xXfKqV8Zk?~7DCBJm`DY^_Tuj(u@UQ6|DxctBKA}nTD?Y+3 z8@))!w2^4T$pRaMq#Ie`4h)RQz`XYyI2kS_j~2^AVk|nSd1g(l8lz`wF^G1C&R@%s zz+{Qez=0B)2&_x{LfwkLZf>)VuIXt&6xET=H_S`8^_pdMm4CWrv)M*V5Lq$5$vixo zU=w};Kx&hc@yZeUqJjk`!XU*yOoP_) z7%+UU3$M~g(XF6RcPKQi=^fs-Ul*{HRrW$ zA7ORDXK^Hr$*E@7CBBL(Ld6ffVno(9_6_*NJuO+xQACrdK!l};IH}U*REeTuLLdKPO!u5K~Qq2MjyObLwF{RT- z#dRyR(Sd{y_GG|GC{ToLe>VN^Z{gWFPekKrjIK}-+(IgsxTrn2H2;{!S?JjGtd0ss zJBAe|+bqovixauhYmQ@V3i_zC1jbXtWyW)5PD15Xwo8#!&MG_}r*K6iA9WJ+Une;L2tZ zz_2Rdu!-4_(Y7o8cFyRUv`Tm)U(Rpr8+LUw%@r%ure*$Nm)wK*_o?(2ASbqqIi@wF zLdqy51sM~|#k0mVPJrzHX2t~;5a)7^WVRgl!=1XTHiTwPvb`Soh;m-bxlUte!haL2 zTxk(eZxJDq>PMlHMAJ(^sulO}fT~#m7HzC@{Dh42K%{*jmVfoN8g=!w?V!Qz~SVTQxE(3<&(?!0EtMgG+t@?SD!dr%Ppo;Aqj;77>5|$nT zCN<3$tVkj2tmrN^92oL%NWwzv@4m;~;lC0Hng`4jLy8$l#`T(*Eil zS+tF)TnbP;*D^70AI@+LI~saKm>PA#=w%zCnHbIG+&8~IbwRpCpSC$8pLx)G9x}{H zr@;0`D{E73l+TT4pIodfn3Gp1Kh+UJK~(};g{ z68^YuSJ;OlPf)5@;1SP@j=U$HNe>B@&|9}4%M@|}WO9=_ie~w9!SyFhsU&YSx5?JtOH&-QaYR6p8Veg@uWp|)n|>9KiLDL z*^_XkL#SNItZSlx#@Jj#`#&8+1|^n3T)`^WC0qQ2yT|nDfa~3QL^1k1epNWQZ4YS8 z7RLyI!J;5uB#e_6%sO>&y&srzLKROgzXPk#j#rcdLQMfzEgl%SPBg%#M$DdE=e|ON zuqjPydVM$Sxe=UX*-hMn6{4EfQelu?Jh{9hN^#|0F$!eRv(h|8eB-V@V!D6SOo)1{ zx@>fk+UshH{N=N^39{G;;ek^v0VFkIuEa*NPPlo3sx#1*lOv`y(Oa|As+~@u(*_;y zH@-_(LYO3jS5T(L3|x&`4jdmtAO3Us=-EePb5ZGqc4q}2p7=ybYVe3jo$ zqV#gn(Fpa2{jRWfwdVdDu@06|kWC6VW>jYw09qk>4RC+~)zhgIvY=xCSwna-cE{r# z>qLqZsCrElur!J@31~x9$)ZjHI_TC+V0HobgAyX4e*kMj-niOka0~p{YEu8|M9pfT zqWkjetb2@uqKWnExPb(J*vUQLPe|w|sTgDv2fo1D{m71Izx>-3Ecwxd;q&lC9du0A zKy||$)LlZLD#Ro`;RWa(TD*y;x@Nvh_sAHYNT8OyVXhs$Vvb@RzcNqUh$2yS)KyH8 zT*Gdxa`BauXlPf@xnkttlW}0(%z0uu)yaPndZe68_so#&fG?9Vr1CEL5g6@`4K$Q3 z3E;AMCW$)b?E{@HVd(^iof3@MO|k<`;oaLd*!!$4?UHi=5Q|8q*y*B58#_xrvc79x zV1j-rjq4Bu`|~)C%Fyni5ZLI^1L19x9I*jbA$^e%#BNPQ9Xz@&h1Sbaoh1q^24zcT zyVa*O5A>#$t~>m}Czqa#6ebfvl$3_(bEi*VWRCa$?V4lf78DSu2mq{-z|hdj?=eIw zRD0&iW`7d*Hslh0O%=~~qfJXw5xQik?X6jy-nCSE#ov)B%q?Nj7N@|(5bgmPXK4*g z44Ra)`uv*2VscQ}mf%&ew-lQW=R3H)Ek|@rRx=?V^6&QfGSDCLysK981A-|YB~^U? zeCHqKx)rMXMlIWA0I@UWZYpTBDg1QU!XvD}Vq@kZ;Bv7sgl+TFqy4w~G=+Q7%2|gh zogj3}Kwc%pY-ylg(SfepU-MrO!%Uimk*#Vik^B!&$0T6owQeF({mp@ele%@m!Db`(C3FvPY^Ip*yhX@nE~c7OX&gG zw~Hnfb-86qN9g03CLM$7MXpfK6~08GjTAyhrn5HMutj+F{Ev09Kiia5{9>TaW`Tg9~$dKiN+XUpNX@Z9|D)F6I(}I&6Vv}i?Q^JmYxHen-1H||4GU`80C;+nn;hL|P>Dk0 zipaOXvd)fw?E<4|W(XVe#$!LRZ>41^g+74FJC-#t+WZlrL_{(l2Ji1Fg^!(fhA1#) zr8^Req)kqC9bT`1kEY!@bW!$YOnycLIo)`xKEaFe>p?i?Nu^TwD zx;_N`=?d_8KH&Kgt!9`3Ef?T_w#0hE<+z0V_KW;G62CnN9%rC0I4PLQTa+U(+5)fx zG^S^FpfMQ%jcJquXiUq%0xAmHpMvTBH^D>hVzj=TsXOb6ZD;PY?&zkg56c6qY5w*Q zA5;Pz5GNs%GI?$Y1*W(xz%3Oja5sVrbhz#4|AmcRN&1vv>#W1Iw*N270m9Ua-kVeb zrbXS1BcyJE%MLnP|PLUegH5W2{5{b*bCQ{67 z4JCgQImmT#64TmG0R46zD699MtQ=4lk>r1}JrA@I{LwCwscC9)QLv)uI%>cSq6iEO zSCzmFA_B}HTpyoBtp!?|=U+IitJaKv>ftv}ii(uK{I66n?roQp?*D-F?(T!@Uo`>&$pn(Bz&`4t6*G@Plul@g~^23Y>_oJchm9=NBRel_z_E!$v?)7?>5$iiysjIN(Mz4tvj0~JDpe> z0Sf>`PFZrQU%dQ&+N#dqd9yBcz{#Z0+1jc{Bc{u)Gd&rkc&hIHcm5*$FON+T# zi;l@YGNfC*3wjdVqhm+hav^l{_4L4Jr==UQJccx*S`BQvqa6F3uj8U>91wK!M7(?w zX`FiDwhAr+ZgO_qB@Fg#>eVx-=rMtxUjdb~YfI@%Ym5CxwE^XE&b0>yqS=$!x%wpO z#OG~{ZFa@MtS6au*Y#MlvHhOzI#|9W34N?<{ZldLdk}T#xHo(j~cX)ff<>5f2T_<^SjI;HR8EM*UWIV+Ara!GLlz zzuWsemI92x*&_(a6cgnZ9R&2YLkvJJ_7_cxx59PNFh>k?5Kxrm{1<;N+JE|YByJ{* zHu$OSd9MiiTq7L$qGeZm-8J5Pu~4ek?Sh;p=7n;3-OhcgXF_X%h<=Bkmi^EwvS;r< zxX&-;KpJ}Eg?Rzm%UB<0<03b~4zX#HF;32>!U#g@So{XiaM)twmZ>*D`Y(V5Q?(9d z2$fJl!+<8Q&RKgSbDW@x4R40J4~9JXExMRJWa`s3ut9I!YYeQ~^4~9XLT@wzmO~&HAB4+oqm={q> z-}9@T-KUjo@zO&Pv|kVrH3KLqpr9eB7mJcLfe^foa=~NrRLvGGM}z1s#P`<+n$ALj z*Gc7NhOrE?vz8s;Bs0#^gLRYZ9#H{i!k%Ue z3(jTatU)!}<|6i@#SvG-v|TM?`y-&Gg^D?e3_Oy(8-)0(rx-aOD*RvvM2%7`mB>g5 zI9lTs-beVhdW?sEJlX(m=1}2}>_~zd-8t!0HL*TerozWj!1}_Bpn^FZ{Zw9obI~kk zd$-5LujFo5&f!M~^|qC#FJLZdq=9Rl4}s*2X`BdCv9&7CA{KI=)f?;?1&mFe%M=c@ zZDV%|7rv1%*41^{Qzx2RYgIsxfzXL6rXDL}?Mn&ZNkS$3)nyzi9w%7+seuFH`jbN>b`oik3u zK;L^oZpOF3c0E*z5fAGF*oC6FzR#M<^cF35TX6RkT!g6O!69aX$JV70?uiyjvNS}- z)y>R~L#{aBsVF>j=_2tmONx*N>Ov7gis9BqQ-<)4ja7*C1Opdz!K$!zz=&vZ4~FHM z*A@D|)d85}dHRD(MM0v~c*#hfnKT$>?hNwjk<2C!&!U+SBa$s7lP4q07!gUt_bvI# zrGznyrGr8pt{Aj_k0LOnK5O~|hVtMCb&A-wyW;!V3Y+qZ9Y1$Y+7N!1qD;E=BCBnYVgW8LC{vgVSv_pgsDD0vnu$do%&y_m zLzM*+UuEJMExBnkR%P<#o$*Y{ze>gTI2kAU-Z+^wzhAr0^LY<43ukUqQqOEv+nlGz z#>KmfzY$QFh!77&3xpuZOOwsH;opw2oENcHME#j*teYrjRKSd0h+dEDdp;$cW8w97Z!H zDfnovD_m0KebsK~Jf4E26#D+g)EBGR0t{iNnyTOn@wC|K6z?KbEu}=1_YY7qxi!&u z)#teplLMee4kraQ?Nf=mJqavYA8(ASzyLWLoGIuPolwn6Jel#wIc>3oLOW)aBo{nszT2}0g;4zOmI#iLbg=TS*dvnzlEIgM%FeII1y=Hmr`9UqTO5|9&$l;sEqr z=9hDCjaW6YskU=8>mvEut7jSG48vWO7oB+noOK_ClnQ4zwqibp?d@5TsS1gFWp`jf z%abs4=%2ztSFAzYJ(4VSoifIsuVW-*tak@w9-Dl`!jkxI><0)JHzELzYT4s)4vZOu zDSyTlV=8I$90zYciV|-y7iK`JvqN zMyTieb;HO>=WCxQq7Vj4cRWBt0`_Ck;-m1swju!&8iPh1P&NO1Szuxr4K zQ(ZC9vUmO;E>u3;NLhS${NRt8dvEL%44MPcUy^bf(?xnwVrEbP?nk!Hewe3Y)kTHj z0p$XvnNCD9GsE|mAHQ5uhG!2$>zjtrpuCD4;rtxAx#@!=5O>5Y@0I~f>3XzfpxUE= z8n*dAj}wQ4R#+iCVy&bxcf)wE^!po@vC|S?$Ms zjGJ8LKN^IM6d}Q25tLw*A3g%&A7s&gMY>zGt>B|^;sUy<0pwu{v#Mb+UGM4bazlEb}B|6XAPN2B%UMW71AXH|;Ms9|~}psRDaRKmwQ_09ijr!~@N zVwb5cv-b>(qA)c5bAm5h`HDs4##faCNa|kAEbf1Y=^qb}krcsGK)&>K^F($qGgg7bJV@|?R`F2Tat19a`vT0jCzcZmfmRGlc7Hz*H9 z6DOSlBqV8t2%Y~@O%rD*=M&!2>fPUH&Ft{M>HqV&$wGpEX58yhW$#fkD-Tsn$t{|NfUl=y2%gnQ?$quFccLyM)W|#G z+=pr3xlBx%(w`pd$8n=9-Lyz%J(5!QW@z@aU~wql`Ek?d)%USNXbEFVW#w{7Piuuo zf!gpe>FFuWMU$w`c6C9MLpZ;7e|@8IzH_8CuP3h%bCMtwvj&z$qVJCg9gDo^0{!h3 z4!FAX&Dd3^qx7R3lq2x!;oeg=yx;si7&(jlb8%=`76(CQ9)^dZL|ig+Z}e2R(;^4N zo;=V+n<*pMOpiaL(^#sn&5-&+-G%o@=E0>nU;AzdZtkxd-M+N6BU!|yX5BGZdrihu zuj<~sUo%Z>_rYw;#wfL{V zHWWAr>BmPd)PMZoIG(QMAyUWd|q_e%Q9Af)LgWmAZB5Ej&1 z5=MbV1|OYWBoDFj7J!5fJdUsZVG# z^(QH3Xi&Wv2Q24e&8X>+v*Jo)VeP54ea4`p2EUSVCu&a}g*TmYK*=KolWH^G1=$df zfoD=%QL%wJT(`zw-k>AN+b-G_HIZE@XAhiT7mzKbp%6id8MWcmmf_E6>wusFznuyv zXtw6h{qsxib!>#ZJu`_!CL40)27lvDn?)`4|^6Z0bKLDEOoBhrjv0Xcw%v$ zk*1Q@k-|hl$-?hDo*XC+TTL?+Gw$+>iaE`n?6&eI?R4|h4VzwuRw&>gLtu{9FIkwX zh0-b9DCet@1oc!f{m7P3blRZM&;N;qbJHs_ETd(EIe2xk+Y4m^=S znE9F2Zwqke3!7`4Dss?|(j^57eH1O`a`h@_KMrKjpm8^nSUV64kMsU4G?G1j$t!RG z%ie_73(!ij;xGQerOG0y1uFKwmQE@|W_A9IwBtU+2oxiV+B=wntp~IPz+Ce~GA-4N z?#~0Y>rx5Su71{_BW9YsapzsnyR!`Ff-v~PYDDfzuV^1_E{SK0gQtDoLg)+l7W^II zTu_0H(vw1QAFqH1ooC(u!_-@b#o03r!+5bSUfkW?-JRm@?(Vigu|jcK+#QO$J4K7T zOL2Gq_PU?<$MA;~wC3@ezZ>J4~E zqTrB5r1--JI|8)lU_>gH@{9#-5Z=^ryL=NC z;kl~X@^9B42n^gIODT2)xOzsRNu5=8wRp#5HQVkBl6+CI9tpC*Gfk#b%vAio(-J!Z z1bOlpg7>itr^X0k{F-A!I=Q{Fz@~i9hHh~kalCLlGMjyMoy&TyeSRCCyz#4q)It78PfQkWg}0m) z&au7nKOp{2`o%AOph-BVJ$UF|I0uhZ`uqtxX6D`bxc?Kb$h3@Jup#xsF{i(GHPx;+ z;}hlgQc}?W9mmcYA#;KHFTo|&n*RT(_B2y+=qym?!D|qVcpSV%oUVV!Fm2Q-ub_%P zyPe7yJl`k_cJEBD3yG})*!5))ixXuKsqdds=6M6LfV#*GDoHrrrWdSbKP!jwC{3b7 zzwcr}(}6SX*Ez~zQ)yOEC9@Ppeo>s(WpK0&+t%+pR6VEzDb-aJn*h%8F<)9sHEksO zDLoKbFufv*3Hs#_)NpSlAmca%LQ$e4nxMF`#@iq(0Z#oYg~1`GmVoCFUX)PyiE0Cg z+eiq1#`tNxQnW090~+K>ifZc!>_Y^K{Ma7e>_1`;_vh z3(i5jJyCU^kN*&21fWHGXIdp%EJ9qmsSb+ImY5!xR~VDU=ib#!qn2NYGDSkD z3ui;eV_;iV24x6X?IocEEs($-v1sy0acRyMu|XN)OObD*RB_Bs+mI!)&@YWB)- z{>1vK>PbY3>MURy)<46w94f3C!Y0#&rTuLb_PL)Op6w=nhINF`+`8Tw)2)m^+>mVC zlCK1)EWX`)a%9~U7_US_qdQw;^M2Q|rGlLGTioDQVcOgG`QK#2LN>qOG**wZFS>&u zcJd;HF5_vVb^bcqv@Ayx!F7%V_N1ca%qlHA8_&;bPOZCNvW<3B`|smNmTf^!x^j*N z|9U4lL2Rew?aB`}#OV}sjdAmS&0Ru)B0s8uutTgr;S8E46EmxV)f$i~ra4bM(7$$b zr8B}p8I_*Gtw~qE$kGxxeP^BpC+iazzSF%dX?rrTHE(Zx@zt@X>_Fl8#`66B#TWik zU3BwhX64~vZ&H#^_EZ`rcJ5{L-?`*V-pOdsr~OsjA<$m)^i>&LLxLx65`6Jx;bmzO z`228lcJR-?B{;S%m%GMLxThr`Qwfo(cE(D|OL7e^I9GJ*mK`2J;PT4r`}Y=9-}<$N zfvWF~D807tPd7tDPantk$3~U+UFR#6lPl9IIlqDW!ungn`PXQcU7_L=S!Qknsi|g2 z7PPz%*t_eM(4Jl}*DkuBwE^{tGW}kMK|r{BX9>De{(vKz+fuWX}-;4N-|)u;on zhrD%s49Km;9q$Byy~faPa``9}{g9Jw)dz2s-mH5)&Jk^S_U5sQDt2g*E{UyNd7{6% zfE?}Ekpld&vG!z%pws4ofJ>+h6kF=O^vxv~+I$ESo<%CL$(6)sY#x)WYE+PFu*Pfv z*>I>Z4JTPk_Ib?dxHIPRaxfG~58UW*JnB1eg|r%4F*T$QY6x~rTB3s<1RR=q4#gTq z(T30_t&+$UcsJ>(W{L}<|58P$aG%Xt6F`hwB@Ixm*l_W>@N;A+0xlYWM-dHZzwomd z9#$ne0MkuV!~BlU6LZ$<+nxUd2O9`}(Pu5Zv~_=eHor7G>)LvrM(eJv@EEpqAH;mk zkFpXakaZO(W^2CihWqW}w%6=$(cypnu^7JTd%o+*Z&t?B4#^}mqp26SWNy!M4ItpU zXpqFdh(C{Kx8&dcCEpfgAxi1mv0$@Tsk3Ag?D0;rXmg!eHRmMDm7k%(!8=v1zt^@o ztQOs}%-;PbH0`+G41#v``}%gFL}K!FGT7{N#b&q%=kNee)}?iAfT;GYVt6VPum!=ZIM7+l zoT?-1AD0UMIxKXKl+l89{`&@Ksb%gx9lf>eeX%jDlIg#Kn(zBI$FvsSU6~X4WZYoE zFKvO^6{#IcC3^Qba=C!TLQPgBhqq=Mfg!6JbQMyeQ3j~a@aMGB$G|3f+mZ>nFnFihO|6R#ziu z3-*#mDdEDI9@c1qGt~cDY`^NyQnA6zF0T`Jp&G5Ib>KXTa8*QYz|S&+ZPE1UybZjn z)JTrE`nohqeM0t}RB^|GHua#H)b#J6Sl@y%Do79A3^6(M)@D_KMa_%yqp6v+x6RMr zI5{dxQy?=fX%I)L6DNxpWC3lSJuV{M>K=7`!dsV`&KFwMyl>Iv^B8m%E(9LAP7WRA zn(jn_|9bfdz84LBD@IVyEj3X?+qbrEPKRGo8fZ(VjaC;>cRze?_R0KYPvUE|HbU!w zj_xe7K({=II{N(yB%xmlbI78K4W1?B1$+~)=kC6u#2r4u-T-^eB^PCD0-(GNE*>@^Pb)L`5eHXM0&wkTBQXcLqT! zk ztG8Tr%^&BPe%>K@dSQSmUgG)6@zM2HPuL^nU~6>htF1_qmn609QQ)wPj)T{0bH`qo zitJX4>^dQi_--@DsC5}_Yu;o(J%LO$bhhm@iCACYXNxd@sjM=_h!BX_<{kx$46t-@dQmSpz^EgW{p{7^84 zJ(P|h$Ojaxh)4(ImPm)MRWra#{w#JQ*jXQMcd;P>VOJ5;FLW zbE?Ux?DCP7e)WHuRXGU`u4Ob{gf*UCNx8(`J=fY4m`pOC#7;s2>u=j&k8Uv1h8&hULwJMA+%{;un5Pn3a>k8WKn(!PriSIFdx$~>xFR) zlJ&N&dxz%~f(x{NB76%=fR3icH9%hu&a+i5eYBKao6JV{0m@N7YRcA5TGRgSuF|Y0 zfNqhiASdAt`o`X&R!j{wgAUH#0o((VMMz(e_j3!$<|qBw*Fb-?WfqYs`H*I=1vnH* zRUB(y-ZgkpGrS15~=qk5_RD)VSiIlE>8#(M{Y=u1n;CcxALuL~3;ySj9PVyh82E zOc%O7f8N-ha@nu<-0!po58#TBUtTj=oh$tZ;s1Os_2^%+MwpVafs4eo`XNhH?kQ10 zIFmp(o4jkIa9uI;3n(|QVc~Z(Y#kV{F*^Nj(84}Vs$yMTR;{gK)If&*Ibc#MJ?bGd z>M_sW2B2+=t(L8>sZ|m-7u}CD&E_o6UtGzZwK%nB=Ue6*>HS?_h8B;nbeiUYn1&PL zA=f8{g?B2E2`{rn8zp+@y>$fs&08ACe%sBOq4HTUlcaTj1&r(xdl8YQM{aoFq39$U zA*2ukn?a2JQ!k+f)V$hZ3_Oe{KVG)-zM`z1me z7DQkXv4atNLzgOV#fukSoP>`w1bG*0Jy(v5K0OBktll2>W2zX@79w)VTUTu>U@N&P zcFDAyc})F*NGYQlhc<^_1UG!ArQBXs;UE<;e5c}Escs9iA3n$DHZB@QI@g|`^jW6| zI2$Zvx>X((I1Z`zorXPl9k#wt8yBdou>2OodpfLP^J6Mi?iONPV9wV@-(yEdGbwso z!^;8yyi6!FD-}4{RLN`acrY%7ni9bMWx4~7?6*yDWRaY}k?sF4vSa^?tirTPuxVEG zONlA2ilzF+IX$L!U_rh0gbr~BK+X2)BSI!UKKJ)g(qre0ldTvzzZm*8oYoC z@xe<>SxUr_=eL`QhFeMF=V$Fp4~}N>UozgYQ!i3ZW*sVT4ARmPymebJ$64xnuu{U<%M?;&X?k^=0V`aOq#B-fqS= zT@7;eQ%-B5bL67we9IC(htcx`pDH{D@y){v76nsJmyj`W6XPAsK*w9Y_z-#bEBEQtqbq@T7Yy`owlnn zHUCI%OzK)v=B=6%tw{1(1RGH6J}LOfJM?aPPCJ_D4~4ZHeEidGjte{GHoCa{_bfwQ z_T9=CO~hJC2>O_Y(iGfK)b2X7@m7{5L>Bvry$SVio~$^2R73(DK0rSZ(N*XZW8c%< z#G4*po6XSLIbHUMWcw)2?oJ0V*V*Pd^$izqEq;v;seyR^ z`r-Ddr&+;%4+W(@iVA#v_l3g~nd6Ep*+Rtakon`DfK|WH=(_L?r5Flz0{`t2UfYjJ zR<;#ooB|qFQ&GPESSOJo1E#f!k$|6)_X>e8xs+1c)i3k#dYqS_lrkG2FKs~sgB0+2 zmzc88#6;iQ;IoN#Eo(AJO$wk6JI<@APV8j2-$x*9*r%PW3Ic}K7)Q5HDyCYZ&RLYn z%s+i!G94Pz51;*$1;-yA6F~R~F+igtqecv%@u0%ry^FzFVf#g0X`VgpW&T^R%RUZq z)$X;4>ttR`miRFj4ieT07Ix~4Wz0WuKkzW z5}7d_9Fxe!S8FzR1LbMCx)4u}b;% zmUSX&gRX+tns-j}mbBxIWQJ&2labPF?Dp0fyl`ce**&Z21fup2eG!RPayBGdZRKw7 z5%QPeKDVvFXLIXy>Npu#n4A=TpPux~nHw5sq9lG)EYiK40HesnUrJ;u0oUo49f@>G zPv@|Sh|xf0JKPiBLE1YT8Zpg4F+@MgO_ETyVlrKUUuOXUdphpcJc)eHx1oQM2~4!C zXtQ!ICX_q-?P2_AkP|!ykLhwu>?~@w%oiI?R?*?L4jteRf4#c@3i5uOh$F*7#s1RKDK|G)$A-GM_-t7=7S&17^oSX_?BCGsaP5xoG`D6^XV_$ZD3mg`*#e%5;3)e^(Or-qP^PvA;-zkKGZW0oGMPf+Ck$&CHp)njPIJ zcz_pAHpS9J*TkUUT1ar04q1+{Clk^ieBRo=ff@*0|T!ooozm?IWcDR^qlG` z?hlL!2&zbCX&>QP>9qq}rlO4lajbntzl43Gb@lDkw|0oRWkZ`9bgc=%gkt9gv1WpQ z>whW&=s<7sfMrmZJOHcoSOEYDno|ILYXKvmAVDXJ01Oc3cR(H}xEvOvW#l^m5)#z- z9n83{45$MAQ3havvQ+@TL6IupK0{T&8c0qR3@NGsZa}wSWzw7@$1v;n!GYHcv&rUTdk8S8+t*1CWa zkh(4y0xO~iDe3`gK>$5)6Jrm+0Ls$=z=I_90Y0Gj|N3V20Y#v0eJ~Vd02c3Q0EUEO zz-%Jl0SF*?dw?it#1P;P5;O#0wtP1Ne1`-H7=hK*83W!yImTeKK2N}|IX3~UfT~Tv zhzm=w5?C|9GicZp+?5BjuIR2FcWRSgc-H~Do9X-Etqi54$uYa zwF5)zK45i!?E&kcczZBSw*vqU5>)8`7P0IIcmNGLf<^2(0d7H4PGI!0GXMt?wCD`( z6pRIrLc|qt4oX}CV?^8lJdhv;H!$PJIDqm0yar4Fz=Dje0YV@c4}cWN#~q9{^Z=ZJ zfdq~EfWaqU03IZ0 z-xn;zI0@`<)DE!2b^XCp4Ba1q1#+we8{`rI*iM?Y#sqS+XvzzPF&fEGi+ zBsy6DCJ*cw{->ub72J6U0!)GCL0|}V5NzOG8sHVwlm=#m$pA1wf-cklqfr23oH7AR zAeKxpH`oK<=u2gRb%tdFL?A(su3)0&9Prc~%mEwsKS5}DU_rCFU>cHq0B0{M2L~dv zf`y}%yEPjL7dsm-NQ4vx6PlZy_5bQF#(00>SPNL|DbSo;<)6>_+4Z>TH&2HaDigmv zpQDkfTin?Et;tITv&jy-siL_6e6eJ;~FAUvq>Nfb(iHmJV3juIXo0xAqPNQ4y1(-VUR5$+kE+L-%?H)%e6>pajJNaH7Z zreduyL=>huFWC#JyU79}g9Cw{1ocxMG8lrCMES-^G8BXS1b!L|!kTm&l>)MZ zt$$z(>EtX#Y~=YBtrzwmTo482&~soWH0B1Wp>nMtL7rWlYjA5JdC0j5gc%GDqUiJI z04(Si65;_VCC$<4DZTM!ET)JzkayhW7ScbT20I812I2Z9mk-F(`=8 zco$0YgOWvxC*S|Piv)rL;ztOnGsWs4{ssIsR9+$UFHR4L7b4UrqcJ`p?G@w)#}^26 zi1+ag;!~s8ysNF@2V<80jRB$ylrVnitUMNY5hF!uh=Yh-=;Z-IAnob^Fdn|Kt1AwC z_<9+n`IOx*D55xnY8+R6^*unQ7JD8#3{yl5{L%#G+X38$rABKZTmJ?jlr$+q;6W+W zeMrzuz$5k>yFNEhKQ__R<0t~6m-~xGcwtLK9xcJi@z)vk=fWT1@Er*zc|~by zH1H7DFd}cgOd&l`X-|G4z(6G6z!6>uv>+-G1hqSEym1)@%)2ORWaDEngtFmg{C4jr z=G(0V1QaAezAa|4mj@_{!`D0+&m z7*Hh7TJ1+d@7eW#i2=^E0Y&Th!op_gw5I^JAHwh7MKMR8`tGN-oVB6vEn0i0;76@*ZPEN!K@;5I&85bm0 z3R^_cE`C{Z2+K$SA!u%}0g@G>?&n%QB3~P-*-v9F@;Jc4U>JF_LS7>Q9pnwE|vI2b6GRX<{!J3%C2C)UX4&s z>xG2l2523&6x@_fGocWw9Qr2e z>A**$7P$!`U0q+$($=HIQ>#ozi|&~P2QQ;!&^Ms`hw}*pqlH|KCIT+%W^s-xq!mqv+Am62A(6|YY^eh2v7TZ zdn2Ovq+8UH9}{KlmM(D~{CgZS&$c}%XhN+j4W@mz4n)g|O25fb;I6i2`rk{Ay0V?g z_*=G#9GBWe68fx&pF)+62&4BnWOEUG)23M*+Kd<~_EHS5S`!5RU3TdaP9QyD0!82x4m|LU!aAS% zVafJOXEOF%+c@JYf{({y{b@_06g@c=&N zmk1PU(-sBe&^EwItApD?89(G`_iR=_E`5eZn)AKYT)2bM2J!api}Z`{w_iHI^YAe~ zi3sGmKd^UEvW#0hcBA8U#+x`e2{&P1^NPIdB9NF9`$lZ=(E3g}q>@TII{xZo`va1! zAZ{5(>WMlVzp_@m*sJXjb&0+}>f)6fJpHNRw{xS?Yc zIEi3h#U9&$P8*srogAfgpTF=ev%bRHpvbZKys6 zWH;-#E7AYl>CEa}-DPtZx-4-9*GmD~Pa;J1bXrZq32 zh|WP3H)ZnP7$kotf}Kb5blN}``@i0Q=0zH&XqEjM{b6LWCB0cpbv6uJnOhI%vN5b0J2~6|e)c|))63y)f|yd)81=`v z9j7OBO^G4xa!@+OtnH5bZcY|jW3H3Uh~dxH<8jr?&nopaR@kCcU_j_KBQA+LF{2vL zbKot0Qk)_7@TWynZ%Bhz;0g!dqs18B%r{5=@x{S}S|3+?aVp4hnN@4CW@ia0Pb8|E z-z%<%wQ2U5TZLXRGrsPT_I701cHK$s^hOiO~sg_-BO6lvz?nUn5Ds$oZeP6W9x%KoaZg*W`kW zM`(){X%WS`9QDV(8P)|Pqx4#*8!Y&VVs+Q)STxtN@}bUH80DC`y+{UT#G$%D|Kc`JRnJC2_9@DR2TOyFl9A(wlo;6^6A zW~e6uim2R)fw2|cK*`5unFwvuv&rK(v#TrF0h|z1q6Ru4E$PB1@ydshxujwLiz=tq zoafGlXk+=$U)>4KyCLg(F#DeEml)N=SM>8cP?s4y`g3kM|6&$mb5ja`jl&tm^NcYs z=qj5gxk+kENpuN%&9OVx`Zv^YQQu0^wlcY`{dTOw26_!y=h~V0PH0-kW@AQ{dwX;p z60L+Mu*lVaMRJW3QRrHu$Ij5F*2AiW&e$A(pKn$y7V{Yh;2 z*R1&D;c^PGTfnSnRdQ0&#K1+_@87TC(AJ5kb8Jy;3S=o=hp%08N;g-Y#0JbSPJmNme+YDOYU>m5PKwYFRPvfCD- z*g(SwFLF+OQ8>BVj6=5lsn#|$&)on0CIG9f{c;B9yVurJ{j-FNF=q)PbMwUp-MN0l zu9UC5*B8V^9sUXz1M4so^nVcq3yh|}*dq7Jfk3$6Wa<>26nE37cE-VJ76a>`)}iaM z#JsAw5kHiHm7rg4!UHGtev|w8240uLuvzx9caN|hSmV5#tuLAT0)SHo7B}LjH}jOm zhP$qY8&roe*|DAV%*8CdM97vkE=AK6kT}d%0a0_worUBUlO)T{nQ(%_(IZ~wqQ82i zGw?Up#*z^EqZ^|=R`1~eZrip8#KJ`9hum^yI+EH9FGpt_C4uLrbuWr^Rx`1e^Vlyv z&#G;ar$>_G(BT@wU>ih{=+o?-uhV3`TzMwRM#v)7-+;l$e$Y((R?1T#^r9&4N* z&^?=RweJbL37|-`pG86rM&gIXBSby|BtSjaUFYERr{&T@!H%X-K1&yr?^_%C2CWz> zy=T%t?U!jEV6^3lC|Xk1y4dVP*g~frygPYM<<0T>ha^i(qh64-B`8c-6=FMgKVH)F z{}eeB7w%7>6&Wb+YwA1;ZFX~$1C=-yxjEve=G!)ihxx8np~^--W*l<$i}FDD@Ap?_ntSC8ubLfM2X05j!UsHv z*b!SkqFg@@C~*7K6nm^l?|-|=JLU;WM_HNcM9d(#?Xz0cnA1fBR8(>>n~_-Hv%uy0 z`HQkHw6#cOT2W7kb$oG~sY+ls{s4ZA`biGMI{H8Ltt@A^+ax`>*KeNfOJ>htnf~4w z*mKasvR-M@7)`)b{dYWKnNO5jgE?Np3KDI@Nq=m=b zns1uU#+7+&Fo%S(p7QTsIf1nnINHfS*~au7Vn#5HEv~{)+oxwd(eOise%Md6`VkOY z50RW;+1f6#4LF{~dSX(GhsrM@)3$UZr%fmk*AvQCd}~sr>=;8As89H!9_WL2seRt-Q&Fk3U#|K9idhE=4%Lr!R1)UYjKqe^rfF?5e2?VV9%divlpzv^=wfp& zcn&i1=;t9xJzlLuM6etloN4)0Ovg)ACi(}Zt#eiqB z$L+cm>MH;BvZr0TD&xu5TEaz5sh|4Kq6Gu#p3yRACl(QsppVfgtHlK( z6)LiKk`S@WmmuTf`wb{(T&K3v?Qf7jp(;+83R>n?tx^W9oqwA*-F07#FW*7)uTROG z9PWewz6&K69qC*V*-ug`xti`L2L^>BPMe0zf)Yh!?xqV^S(!qYnM3^X2_J80gC{eB ztg|AUST>?HiQ!<(Df(I<6x=A@B$o7?as0%X^;R_XVYUK>`zxoHljlEr@43D~W^sHA zMB*2>_DfQWdc~Fj3PJ5NK34g!YuMcm&Z$QS6-&g`n^TG8!)NsPK6|!mKT|7|47-#3ewTu(b1lZ ztYaa0S=zYtU;nu6ESH7-twPw|P7-nd_rE#bECa}uxy87FQ?Ub{+Kh#mcRXzi&NsjM zC?*@!9K@j3E)E-kWIDFk{-E_M=nKV!4o_1O>7d35cP$~MwAbr)EcLf{9F2F!&D)wR z0rjPvZ~0U$+aol%FUK$~W}*VmuB2YN7jHYPrE{q#@4ptB3Xnei^iLO1ma(Dek7VU_ zR==j-zyuTg!Rkp>D`hN}+pMsuk&dXKkI*dp32PPL5IzzP{JX-mVmPik9e|iSnY87i zjtb|NvrCPtPx8G}$hAbTA{T2VLOY#iUGLNPZp# zD@}^4g8O=sb!a!XnEsn+9ejh~uXEtP71qOeESDuEwg?zE*me>V-oPFWl1K>-2=k(o7l5Uez2{v4n!)oEo zOlQh@QffA;i>r?@rPeSm(R@OVkaVfx>dIHG`d21UF%{4i>a4HQ>B}ri32loIee9l% z%tDWEvu%0b5rmwbw%r^Ky!}HA4oiJg)}MUJf8^f7z!J>|QZ;dlkL6!Vn|nA*z+G9S zus*hq40}EAzal-V$WtqCI{EDnS;6uf;$6O#miCjR9ofsu7ME#o5 zp_R4Z_uZ$r#wzk~8K)jj7bu&*^hrJOQ0%lKkuF-~o7?${IVwp$P{TVhy6lva7V+PY z2vzbM082?%qyGi_w5yHx-!#a^jeEp7bjzpD$*ILZvrtPXSZ*|HCv_8+4`xLXYuSqC zP#D)%&@vkJn%?ivGCw8gNyl`LV8|Jsu@B&v(taI*J zzF1UEMKntgmI?Vv)~t%*SnWv~$qp}!GM>)e0^34F;j&pKHX_A-Z~$B|qhM|)^aMq8 z*L2Kx%y(In+>^MuPd&5m4y5;fO%UbA?i5x!jiH?GcQlf%!l*514=KRf2D$3-q?L9O zH+?N73vR3uS4+XD?=Ue+SQ~#u3xsFPTv$O&eeBjUeUXRii@On5=@=j_Fqf9Jv>Q0^ z0CsAa4=HLeZ|So}jVz^;4Y(OjCO^J9IRLs82RLbEl)rVUKDrvR5BbQ`z2q>`{*#50 zR#sFM*Amzq_(gdi6~p`UjhbH~Y=Ng;D3e~ldcJHn@*WFfW~q+sY3W@*QzcVKeB$vR zhsui^t$W?YAQZx13?rf)d>s$fI4jaSbD;iZ!0_uRZufmN;a#GC>{E<83;U=_a+Bj9 zwJT)MJOM?y<_80vZ}wYYGklU~l^e3B&lU(<@Ia4Z9}&TaO5-@lM(LGZ^C9p&?@Rb< zUvAXqZ(_eniNoR{v@f~Np@uyv@tCbSv<_>odABN1x8Y@;*JBufs8n!K4vO!LxUGyEbzIGqraWXW_ca`1%fw>r%M@mo^seTltpVf*ZhuNwn=$Y8=$M+!;_&vL`Q5o2u~RE&eZ!4SPVB7MZIbrI=kZ>N zML-V@;2J@=VEhhiW_@`Fq?~lhR0I|=coyB}2bllFjWJ92c}QBp-VqIK&z?;D{j#`S z-kKp0VtMwv67uK%>b3mhy5k{7@lz>6VZbfim!GAxa?RngF9$aLTOHoX7w}7x-UDm3 z%Nd#+uRpyU5zp>iNQ8;~NsOmV{Tgma1k#KWXO*uUu1g#VDL9M*tNo0ft?Gd#JKr#l zbH?FP75#q>s9=BGd#))JZ^Xt=3=YXy3Nv#Z6Gi2h77W`m)NrM0R$f`Pb@wBAZeUJc z9XODw%MN6rC`sHkavDVE4ZKKP)>WtD9iS@TStc(jx~cuqSDm6|Z|y`7;S0QOzE;P< zn$z}~(>>BfGnmC6)Ybibz9Rwpgy$I>Pe-}RQEwJQu+Q8uSr9%Fh^eo~xE@%o3O=Cf z$Ee#OchmLE6SmvPJK1q<50o5@E^xu8h-otcu9q3 z7Gpc(%(ak(GMeh(f48vauWN-;86RcVF^z4&x*U0T-h>~g20Pqf&5Q&bi9u;tbyV{A zdU&HnsPa%fKTLcuPI>3uhsnYEY1n}TBlQ!>Rffka(1NnJl19G zz*Y0A$%T$}oamx%6~U)96GHX1d(vFKfaXjsOt`ir_mo{{a+(I{J{}QIvh6xoObZe_FT>jcNo_xXx z4Er0v%~(OvL|Lq^ z?ErqG9}Hc<3E2o~l{M8D@GhMyUbSP_K39lmw!8tg)Xgr!#%KUhjlu|b?0U2+?g{C7 z4q+!4)&KP8cjdhs-2$P0w?}09G(~z96K6jpu^_ z9=$9m-v@Zw0RMjKgh5Lt@B%5U3)?$MUA=uLCm&mx66fY-wlC|)U*ECoH3u$EbNZKQ zlkohCHmW~@Lu-DQ9p-kwW^=@<27P6j9QNcM#_7)uTgc#{s-AQ2CyN;D?|HCAF>UyQjfR<*3z!q>(0AfI)c5{Dq0oA^unZ*+H3NzA8R zR#LG`U;nM<0pWr3x=axT;Bdv{8iOt=r2)+BiKr2B3+C%_-RUU&$l^h=z8SlC7Nf82 z?#G})A5&pAU)HX*XqLuq!1eyn`D1PmBkvxL$xC$&!tE%~t?9((RYa=E5#L<5dd=k1<4ov>lpub=G$zp7 zCfN*U^VdMWLM7=c-L}75mrte!B14V0&0&T^<`QEQkrd6VzPSkF3+po%R|K&d#r=(f z9#iZB3)Jh6vYZz|g?~P3d)v0p5be#ZX#D}4d$<2cv8V`oV$7nfG#bBAg$n_)cl{Bl zc%$7_o1hFXlf;gB92IGhpoek8dY*wt!j07?8t0?Itm(357MetZI}2_>7+c2;Y+UrM zdlAe5D7js}evp#)7|&@8l3SxT%GToDv=in^e{3`ANRCI1&gK>=`=S5J)ZG>@;Sg>ZPSxQsF4;9;_ol^zoqn7M{jOgeJ zr@Rf`eKDz$))V2xXF!M+VM?_Bs>Nco1O}iKu|RWibN%n1PFPkJ4h~k3$}uc8(8J9_ zTVHXJ5y6qPRebN;`i4PBhdA`tuQ44RuwD`IsOyxi7$F@P>l@kQuT$CSuao|dyuwNg zx*dzDx{{J5Rl_v409IHz(X%XEopkLd2ssixHEe2541QQCjl5!{?XUYBq6G!2UwD56 ze@gbC?Z%2=SfLIo^$$MBXtlUP0%K>h8baZ*4 ztvSIPG!Bl?JHE-m!2xWV?^_b23`Dj_swc!rOBg*Wa@{BWTxKZ8G~nn7sM+{zR5$jG zD)Ar5Vb3b;v@S3I+AS;lnqC^j77GpdV6FSfj!0;{BFd|evyK`{V-=RcaS;|TY)l$N>tS%K@EjS(<7&! zlplwHpD9?z05&(cG$_Wc>Yc3;YyvSqxaxr4E)m_`ececd9-IbdAwNnJ(FbwO;b5je)+k;WvKX8`ykF}+ zYW_VFzkS-~e@>7dK*4=(2%ih%ed+YLfKi5cTi{u8KYgh-vHEz7hNctG5&?GUK#e> z1#Ke2Ky3tD${;nMvVRz#W^6BZ(5Z-V51*?%5HZDMYGJDafJMyPUYQD)9Vu7#Q3$*@ zH9m%h7SovFF!)S7WJDv?YSaxP1BfZFd-pm-H&V%aod4mzI;UfHjQ?ufz z#4(_Z4(5v)W;Z!a-$HRp3ZanvZ-{*l)5E8N-| zMB?>3;|`c;pog(GUA?~az@T7E7L)7145OtFe)#o-eGLv>>6UvLN21$gI&HoZE4R_B z%$Kzv8kwCkuN_n0x5#d-J(<%Z5{wq%j!RXxuh92?YAFkC@5I15(=FdHr;(ysPGH_O zD^3Fnt|r){R&j|lG}{xTt9`#6?yGFK(e%pKI52E*0>3tVoooW1@G~)fE%@=JRyWi4 zlAXiK!9|PeYq+QWP{-QA5_d#?#l38vdgL2P%Fp2Hkoa(DCmqhh>Y8Ky*}6%B#m<5- zs8WyNG?Jc&sMa$WnAsl$yBn-cY6D2(w#(ngin+TC-C#1mALw_YhV?IMtiEaC9`jOm zthBSiDgy^iOL54{O)-Cte6Kxrjzv1HUVXR0M^O}+RFvUP+=wAUE0+I@a*|{gFoF)* z)#tZB+&BLZd7qtJq7vRTgrep*18Iddt}!|u<835!*tEP4+w3blzKT~Inr-FIec*{C zJAEp|XXlZ#buttlWm*52gh1>D2r=JLn8~?Z6CJ2w@6GHQFPy*48Xz$r_0{MJM- zh1F5Fo^psX$01s5LTRm-aT0nce9qL=(ky23kgKWey8v}25p~4qglgNq{qwh6Yj6KN zw>P4=l!tYaGrbut>9O{;uuIH4BdoB*6rt&xY4y_;Dcq*`63e!kMH)@8hoQtq+_lx^ z6de$==RtQ$AcVB~RF{46 zjThO6Ro4k)-fz(w-I`}EUsxhunVM+H;^lxa_1K(Uot@9YY;}@ap7KVbxNGJ>zPh@f zXlv!gqu71lSE!Z=zx=Tgwj(53qPJZ_KwK=B;;OO5l?UIz@!VW25UBLw9A zwsh(OC#HqX?C2fQV!NJ1R#FT1vC^@TJbV~nWPELcA)s77R@RGcI@pS@Bhne|c2YuR zHp$leMnkQ}3SeAa{+>1^V;ov=%u3 zvr8|?4{go#?xXM=h`@7G0$p_wo=S_pJ1Ra?Y0Ed>KiiA#@(oIl*Tg|%hHTqdfnRSX z$fg z&Kx`sG=dmE^@?l12>QtEb!4A7dX!~1E`wSsn*6YGYdu7%bB2~{J+b}PmUsmqMTalL zf0eI7|rJ;y1@Kb;C5HJj95X``1#`z9awf}4{O z0s0^hkBfLvuS399xDZ_%d7fwuoZ!s_wK+THy{f+p4%NjZ+_OB;-4!Sw{?)bM%N*-y zr?G7QLUUu27;CLI2yu4b=aLu&1k7NPiK+6P91c8-j%^dUBIC}+r%;M?dt`EN=q1tq z^?{Q3(t^~CYD5ftYG4f=uGSfcK>Ozx`Y=<~n~U#}+}-L8+O<<&U$i2YQ?P4+wjOvJ2x$33O6 zfm^KJEUw|F2d(5O*sZ>|cAe$RU1gg$krZHz^9SuHixupg0WZWd$uF^#sMA0@H?Nwd zfPh71A_+z?+Jv96*3_|Lv2@HV^~qsNNN%j4U;omeIdMfOIC6ll!{c+vRaD=-#Vt&|k#m?brc)l@HDkIVfHUknlrSz*P&`a(F5 zj9Kd&St7~n>Ftk^tGW}WrLkuMSA6!@MW74+h_cL8*`+Ms08Teiwa147e1!3`UwnwbiS=C&qyW_czO=3sWo=`^&#EuE#T;?X z?-Yp%oidZlYur=L>F`t_n9dyw7Z0fPjKCgSR=M%v)7L9|OrY09aLRf`T&ZGUm->8h z1w$mjiIAJy%<(A!2xKShfa!{8!*zx9lHKiX568ig7UdoKyeu7Ih{jUIkV#rpjdwxo zy^FPlqW;DV|Fvfh&aydiAKZ(;cA_oUl|WXJM3}%;&wybB*=oinXVW;g4MjM>kvFPk z_R|I5jbqx?Cg02S(|V(8DGXT_2<+%TNF446$*G>ztV3T7bbFEn z8<^R$kM~aHr!xUC8DhjqBl@+hxOF=1YmDd2vu&^kEGLHmwJFGs^PT;fRmb=vxWz;hSS(gy z>Y`#JD!Z(EI7)dP|T^knnnDXigX9BrGWZt`Ns z&^SU4>NXoMAh|NQd!K?89Fv{>5dz-j6(llC`F5b@%uet1;Q-6T;!{s7Dy@MeF)z{c zB#D&(uY)f{zQI4gDee3>dU$#kzvutdSL$&d-SWsK6ln~&r#%$LfLEm{ayM8t9TWjB zk!KR&{X6U+`IGv~1!l-iC`8$tLSsEAucdPCuqxVRt^@y}Oi~)*t4Q{ePA=mOvD?d` z_QD%d-#^E0V&Zt?V^5~uuDC?nKWCoZhupFQjM!Wzx|%9K9yAo3ZbJ$9(E={C5qUTV zpe1z8h_o#h&#NyGW;A3!3zJrcRcM)dgo-MLKmWv^X7t`ZXGl4mJaZi73DTW+X@Ue* zMT4?a?_bq;{ej~>y&%;8OuG1*!T)nF=~jPklpd<;9r&9USwVii+6kkSSY%I!nJG&c z@UL`ji&8TvYKl^R$7=U;M?~?jrKKeE#w@ebctc)@TI?Y7C05|Pb z`FW0YMN30vj}+Jx(M9FpkfDrZHX$QzX7Chb>e;>Vk4F;S)cS{GTW<(8I!@K?=Qp^s zM7|h)&KRS@fPgIOYns1!P=H^*VGzv~uzx_#er~~^?bt$?IDTv2@VFQF+-gqV1ccn$ z_CSnQIfItgTY}CQ#lYGv})X658eB5%+Zo0Q@8i z-L-NeUrN?|@993K)VUG+j`_VKupFa2Irw+yd$W(voB$=S>;5wSv7CY8d|%bRaoU|s z=I<9*h7sN$nWW$2P-cpFW9d$?F?D-i3JOq=8INNp3FQ-qgxQ(mGpvvNCLK>@^jks> z6;EsDkI-7vM|lX#kXt+h8XZ?y0DDpf#icY|56&*JsR#H%)ZQRGTi&U}H;yQh3BFV) z@nlV0FWVvk{nAX0xu5p^Q!AOX8kY){z^bZ17Ly(5oRMifH(1_(_Wm1 ztWq)FXqSk+vZy0c>~fCa7hkDw6!f(WVX%|GA(R2;NsuD2JxcEIySc5&xMg*j$5K2q zR8GlRawI8)Y8%@Jwtn#N?R&7W;0>%IKyA`#X{Ao9X52h}4mlj5n~G&w3XO6Ou+_o# z)g>-UKZxwgWVI8_sPyn}0M^spOLnWk!Zdf|nBP}C80;|BM^wcm%VioQ#(yY?yNywG zUm>5~q_lUl0+-Qc^nCc(J(9!bh^_nQ^!UzSaqW9ccO(_Gqgd> z-cU|&k7s8m&&nZyL8cUygcG!WC#|sUCXdBjEeBMoK$vtR5A+@!iZ#|;^TLeAgtgkqhNL^ zn;keemT)MkHRHK?US6CL^VTl2oNu0&(oIXOGfC^Sy62I^ji zFk8D5X*NNZ=Svzgh9%J&88ZW?ZI|MbK?uVc5$s8U#mC`JO0O!);8~LCWW>hCL6u^- za2%msi&(NRy*-vO&0d5deY@K8%RF*Ib)a9-lhR<~4~ifyfQ50I&SC`B)8x8RBY$Ex zULP!(fZsHNE)CK^G2AYsuX^q@idET^{U8CZ{XCV z>(Wpz)Oqa49RuD&iB`NwVHZRNz5> z*>f)Oi1C-5}pYIQqN%60W=8o=>Q|uH+25IJAKkR9xVAXiwt>1&D zIEskBWCJ<^nWV`R+M-1a)Ew!%Zgw5Je9MhNP7^^yi*+17-f4P&U z@nTQ90SVvqFHET%gJ*SwiPiU!EH+fd3zx4R26%@=)Cz<;{8cf?wsD$yWLv9z|6I6F z3i^cpy9pP84=M3)Usi$QNXFwFd#7E^|0gPDq#&DTENr=i%Cko>Xe1qP`-#Ky>B^|( z-E8s`e+kb%^k!lxJG;NkKAlL=URf>}To2J5gHz<2iiZJt2Pt4#y3gYyU`>2sxz^>A zO<_d6a?nOdBLMYD4mp}WNVv5@$3u83hiznu98ENgTstPRGI3ENI}yuJovkPdYd+AB ztiV}a5}N>qupfLC5qulo*~*quWcM3TP6txADW?wHQhaR9v|o9I}43_RgqB zqbj_ljyU)V^@>i?q=+j{?~gR3&r1IHrKm~wl3yyJv%?z@U(6U^Qf_;nS}L0OH0Yuj zn_+Z%_77?H_(&ucpDxpf?-U=;g(#DD1tr08lnPr46~4eo;KAp$fa*Rde8|?^^!f#z z7O?o)stOmjM?YEeY`xn`y1KR03@1}kXTakB1F99?fnr=S^D;9RbSY1-__5bW2l;Fa zBfwBF?Og)K6kh1h|_Kq`qvB z7I+7=8kul!FS%qiGw`|`?898RfwwP^01&DR{e+xsJ#gd)*rqwvns#DXYyfTIX6L-2 zXUwrh1f)?N+QaQ3z-I0|7NqGmx&GQkkgc3UsHQHQH)_VKBK;F>sSZh$4%{CsC`88Td_;`jRWfgkW&#*e3ix^$qn=HglLv~+8q0pR|ec2rY_ zb_dh#wqHDjOeYe0PEO=3&c>1rCL^_BL$45k3HR84HngbOIdCBv8zMXQ4p80?C(^Un z%wF3e4;;Z)i*nH_)e*GQan=nszPs+uxtZ8?#;V%Ew<0~-JKm%Ip(20=g_WV9UJ>zN zI9Ekh++-g8YR29QudPqP0DMbKLDGA5nm-BLbA-pPPQg(Lk=LMJbD=@ndO}S|$@mE? z2>~seT7u2*h(=sW>pXvp=>6@|v>$lz}cIRoj zVG_E%*5#a5N!O?8GTWzxWk7or<^eIhSV^s`{S0 z_z|>~e)=~=X0mo#4e(On)OQ2lV1tX_RTeJzBJn1=88SKU5Otf|-|P*y_D9zkl(7>B z{7OoGtXw>;1UZ5+LV0k_@F8%KHa6s)-fEZRwU-Tm>(8*EI6V;hptfUp+=)QUI4_)! z84=UJY3{F%b>!js&qpr~l1eu{2IHCc)A{L@g}d7EspcPcB*0BG?xF6F zs?o+DdYq=UF1(DNPF}A#<0^Ww!OyZ?W6hu6;S815`v_}g>Yl58@~c3P@8)>)xMy1Y z(-t8)j15;W4*>DjKM<67AL#sDO1|#FHyVI>DPBmZ_?MR2zf$Z`{a%v0^M|W(hByxV zF0zrpLdJ>pnSNFeJ zTDz~Xk`&nb$yC|#+=xRX0`frm4^y9W9U=s6XX;WfFCcIWw{+ju$Q$OfaJ%yEb&B)( z3A~cE(h5W6+Cv56Mj7{$Ku~p*u2;EcjO2HbpK(RVY-PA91nG?QKat(Hhd{3mF&~7n z*(EtkB-Xzv(<8ln8)5~=dF*W?t+x?5(Rt43qB7&4|Edx?G`{4* z7Mr=FSpn+M>@_^GYusj3>cHr!M?_xJbd7)aRiylLR_7BS^{hxbdq7?27FhfDjIGBq z`TQh6MhzioB1beWd`=!5!Q7%3FKcsrE*@(0bY(y7Ct{2thlRapOdQAtCR|4IL zF=I(K5cA;Qe(Uh~(b1FWsdXiDwsyss1W_;>IRG*=zPBl&H-hp)^FKz*P8Cpxdmklm zQ#AvQhxJN*>br^adh~s%lwD(t5NUs#k*@<0vlV?m1>;6f07$d-|PQD%xw|NW*z65z%XK-Y<98Qpb((7;<^?^rhth$p3skx(!!V|0_*8;3ru64qie!M%^@zRaD)e%}wb@ zlRq%9KWrp|gOv=G^1(v-_(1qkW2xt_Vhixs;J#*P>(O`WUg?#rMG#w$)~JL2+(#0W zhHVf(4j!f#yqeV=2U!>Ffkx(>K~Aif;-`7DuZZz-?9=#~66Tn*)Sa#3$r(*aw8sb> zlCECn7$%Hm{RBmtAZ2G6cWnHpk@jrB0*?{-H?2xx$Q3s@Y1dkSm6;RU=W>>F*ZcnXHB0cVsB{g-(39OPV|_uA|hRjc%A3;_M``G)trrhOu>EHdPK zls4dZYPAi~F)yLk5R6QDeJEb;>9(cT6Wi;6Bmm#-L*^qm1sQh(8OhBAO98;2Qx|iq z)XY&Ot`)&xq?48$P{Su$d~I?gE+`{w@AL~NTr1;}Bt6Tv7n<)<_HFnbIo03evL?%i z5eb~dOklPAh%FcCGINSgJ35?ogG4uMaPU`&1)}h+?Mxy3QTltd@W$8Oi69)d;jxE{ z8XT=;6^IGeI#eyxoI)Zf`y+tce?c6ghfKbi$eh*TV+hLvtrJIHUkSY?lrCm)Fnh52 z+8dmtnX2;EhmC7V2lBoc=2G?Og~BF0+~=j!I9QHr{9gzo2!w2#Q(g%>L-pX=zGoI} zSK$1oK9|CN@6K^I`)V>sm-j6zHkoMj6hu|`lj{@ql)k)~px`bC);yrGo`m>PYsz3sN1Yy znFcZ;W`T)HTq7)x4JTmHu`aNTSr%b<^R}y z_13m#%Ba2?nBcc79=tQD6GBX5(Si&8KI0k&7Lc6T-aqJX)7lUSpbACAHFEGI@PQf< zLMzgss~FfIkN`*~J5bIXYR=Y#`LL$&rh{W}G&6rkO!iUXhEz z+O(>$74q%mx`lnwc--J{R)pF273_b~K}X^Fpzl+Is;dNVNW$z3Fd6iWss*3(I1ECD zs+2g_@?%W~he(iXp_N2txS6mGn4oA5uKx-bH*zMA!Uj0n&Z^Tf-CpH>$?$u_=(h&0 z%}CDk1)d%~FT3ELd5C5pq-%D_+`*xZ(}$xSZcjhoS^KH7Rr?~F8yEmz1kd9fpcCuc zneXN`v6+Y8W88mfZIKu@6KEFcdjX!DMj-^>2Y?Tpkq)$d&y0^CSQ!yHPGhiCf~)td zz<7j*O##sgY7=W?m|-?pEU^F<6~iR zNLOG6k)285?I`ZiWxZ>FF44>wA1kFM0hr8IXQA7EaMe81yjO`mR3n|8r9|9I1~!* z0zl;M6;KH4>T4S&YL4PbDZ~W2&~TM0H~qVj-(qfMR`3?r&e%qV|Ka>qqK?DLt-M4L zY~sB%>!(h34xYeX;y@X z;LthED7jbb?b+)m9HCe);;WL(J{|guB*5`6of06r%T&49nB>#?T+ms)a!+#%>!z13 zv*$OFsL{2wz2mF+II5!K+a#08bIOC^#;)mP{`N!KY_*t_lB$>%HSpxbu(>BpVY;6C zv)h?O6Yb@`=GpMQF1cv`(CM|bRDj!! z=`91r%k3)hEa|A&jau_B7{Rf0Dm5Dqsuk_1$Mar&b;?caA+xt2Sk*xcU!euvj9*i1y|?<%^ZS=~us&A%;w#w$ z0i4Rp4Y_`b&7{D|cV;^RDL?crXr2Yfn{wqUQdf%Y-BkCNnNJ-@1f7#ktN4p9T&nWW za1@g>+l%zHH5O0ZE{vB&mN6g_rZG?%USajiAI3s-ce2p@38ByBpoFr=IUg&`gY`KC zTjw;1NmoX?t?AZb_CF}37_R$9?D)g1@05=&k z$(NU^g9e#D6C5aOAC zKTP)k_yj!NJHTC&!`aZNS*A&>@TMrZyVY3elXRYtH36A7<_LyfR}^{)Y2CQt|Jrdo zUD7iI=1!*{@2}LeRaA>!u|~R~;|qWqm3OtEN^6^IelgHWKtl*PIrqh=Nb?D*#+cC+Hg&G2h1@_VbfFD4_eE2J294f zag!x;0|0HzadxD;NUpbmCf$>P+YB!OGjueT%5e6J=XRbmGv{J8z+8xQ zQlc2SSg=pLZwvC5euX*|7bgTGb8xlJSEoO@F~i_(L9Yz2OlcFMx!WVX(QY%;u<+2v zU@qY}onQFlzIweFSUo7>qb?PU$=XmRNqqkws)AezshgQ2S{Oy48fC{Xu1d1NxhrwC zNkE8w=fD7}CU9GOTxE>h;-fKJMl4-UICK%s^NExv z6{&jFE&rs>7mWy}S-Q`DPiWp3^IbM7%?Zw2X`ah&|)T9_A{87wf;NKF@()itp8;3U*EPNw+a^fdI0z3 zs$ZqYtr_y8XGS@gj_0=g+}|@y%rIqdi_U)r4~0ja-e2pUK}VT94SE46{HxkPG3(Ml z%@)_L%RcWlY)}~}DY11!c?GBB$SP#uo5|cPmI5OA6G_9ddw!|#A(6WmP@2@wX7wE? z8b+Hru$-*t`8Uv6G@@ZlPd%w11ON>UIWMtw30V#(x+-$-wi{k}?utW^tM>>>09Ul> z6-p#0a)1$cn)20bj2qoVM_MvTbHsI8_^BO>6_+%}?mj7VzxtK8pPJhCWN>sTSXdiS zY$f6^@}8^Kkmeeh83OQoJjFy!oQBWFWrSxUMjc{kY3Eb@)z30tjo4l>Ml77f7&M7L z_S_?THsrTe7NDEP2j4(Gt{V0KTR7>ug0P4W%*K&2mkvb*&~(IMulHH0Ngdf*zx(II zeYj4gn;(U;A#O!(zc&v;JBQAmSQ@H?dtI|rg9Cyn7{^4~aaJgkrSOv&G|#o=-xoA2 zm8#FWNEEMb$o_h#Gk+tni%{yN)u1Yr!HLzNNi2vr-K*0lJ^UuIrJMrn&}>YnB2i9idqih(Uq25l!Txm6bkuR!;!9ToHM z;dLpyor@b)5b4)$0#6DQ>adj0$uUQL8IX{&l#6>(`8h*x_Pc77L*jGg9<&KxH2Uj- zdo?=1gUOkTdsBoUR~VpL4dml2m|K>SS`}Fzm^Lo}==cOZvBY5Zxea+DL5hP}RN;Yf zY|)ZfCdDrBK%4PJ1fUw@A_ZtW<&DhYB;%d628m^_92lMcYB>8MPoA|>28JGB?;Bzi zKtRwNrjTGw1~@Yr!6A&0$cDQYwWETh=rNd&zTmG@odP$hm{Az|7W`4iH^!V#=u4Vg zq+C-6fFX}2xHpBcNLme&wUo0AA6;&$snu3OaQ18}2J!KTiJTe1Cv8!FB~CJQW1IeP- zlyzMjZf0n_>s(h9sFVa1+T~fEyH_N){9_yJrq8^Ga zhD6*eyMFtS!$4mz{`VahhEcTjL?@M8CoWIiK#D`(9;ec zU2VVr{F$!G&ZG>dhHPiYhIOi)E0D#Z(_+Wn+t!99$Ir~6pFPd!T#a|@Tt4y#)N8HA zhGgJ|hD1yxoRT)giSAk63h@Yl{XGjM2{^K)X*HPY-@|yHC8By-*F%FVM^FejJ6zg8 z^FWm~J?;sEc)oW0-s`)yXy(A6ABv&Ro_%atSUTGS1iswt3IaH@aBJvf_~=YWBpv4o z=%)wA_Fm3lxujc{4h+a-R81WLx2~aH>}BSYYC1!nmFuBv&Fs-CH?x-JU-bsmWgfD6 zzrO2~VlRo8J&UYjNcUU}=!u@+*Ulc!XeZv>9C$OP39@A9=&DU58J9&SlP4-p>ydb5 zX#g4OCp9wy)}A=L!dm{=@`n0bDH`Ib<5%KvT5FQWPeswiuJhNDmx?)n&Fw{%XHcN$ z!sRk@wOVr4FUuzKI+$wJcaQnh)sfgIVilwcMJdlJnJMf>&$9OWGoyz`Ldyfw8X1vG zcKYTsuLdUDOvkRn%j5xU@BlMNaGaoQt>ha}H+tBU=_O(dH7saKKe=lza=YFd6YN2m$8zo_oy~)GH zPc#!)c=Mx19Qm)rY&SeD$@O64fh{S)dBls*JfRwZm&wzBzQiQ~Gt1B5I*hZ(86b9c zH^}G(fvhNwft_cBoL(@~jkkH-wS5#+lIN$xYf4=%9W9KhuNKGpZWPy?|H_@tmP+*2ujNYRA%wiM^B zYqHJH$LmS#RzYk7h(H&BqHy*PY~eo_kZH_bR8m`vAWZqW@p5K3XpJvCuNgUW7GTav z2DiUZ7Zpg|#u`H6Zx-P3!R=m^MscuwvmbLC4u)(YK?Dq}9XbRBy&^Ll^7CZDqFvM@ z49+wpES)kPqj6YxJNtBUq-FKYK!N)REe`6XDZ&ZXdKfaYEHLQ3$T*}Zyng_m>An@_ zBS|+tFKVdnt3A07CQkae*gT(Y!qK8dPH+r|0_kb6bsm0Iq~Luba058qDV}P(Bvd2G|E)qMz!2FE- zjN**EjG~N!j1tXU%|e_exkUka7-3q}|8pZv%hCjxT@+A+k)~~|@HOm*Qed~D9m^>l zQYfGazBv5_foLFo7*)D@hL6BlGd@*xwLD)h@vSU03IjH*P z-U^U1AAMK3jCPfMso-=Iy%1R>(*FJy?er|nx8AR=`yxNQ@YWy0)VPcRpE>&=%qL^^ zp07*D_mZ#s57r|tLGd+j6xm_9VNonu&IpP`lA<0oo)lG`qYOWsCe8>-I9co`&cgk`2)ln)5N zB(G2kZ`f6);)=Kb8FjZ(FPi5OuTV<=RP;O|>DKNs1}teW;V6vU#tHv>Xxkr1%akaQ zJSyQ~I#CKRI}L-!60myTXW{7-W^uwAd8h0)DE#Edy#1F@WyX9t%dTBCRZ=Yx!+R!7 zB-Rfx7)i$klpAp#&PE+~z8Nc{1h~lrVpo^$uu6PtGc*`?9`OWrIj#nA z84LI$_>|{`*Qf{#?3vCT;6g2$tIe9*1)2@sm~yh<|3n0Ot0m{O74;rry8d}Au-YG;UG`v}2hmE7O zsmyT$XX>4J6gPU=Th7e{Vi6vGMDcdz*a~8G1z%e6b;el9#MDs@-5PS%1Yd`3MzUS$ zRlirrp2PrN!P7sVxAR@q1wfkCFg6_~yrcj4csL*DQW1bGMz}BM;pWhmy;#%697sir}~T%H?t-kz^^@Y z6UeD8eEFlc3P_scEouvx+xw!cud-iYTduN^C6hHSPCBjX3hv8?qNmT%xw&lr8?gQv zs0NFeen+R_BG9Nk@v`d6zb^xepc_R}gS7xf55V^u(i72RT>UX^eoO+tA;ceZ@W(U< zfTD-M0ns`HDxXmK*65|ofO)MCt|M4mX!NSuEvEo`73HAc+>*01sQ0Fh3seI>T* z=_}@;I_;;l#gS=9$=u>2p$-3~y>$zj5h5ihro-IovV5DJf5TYC0q;MBOE+n>gIkwagp zxi8y2IWKy$J+;ixG7dCmdph(z0WB$d(+CuMMwAo-N)^BiF#hx`?eA8bm}|bG+W?Fj zz{e>fcF$^AEoso{#`+8A+xVYGZ%}nG4oL2ev=Z$@#xm}0Vl8JQ@vcSw?ACP$|LXx*qU%F=?9xJi!kK>VC?ek!(xwXC zsdFQ@gl4{4-;Om$Ru!;krQl0wec{q|Y`;K|AA z>eA@W%;f8OwqxX5t8VM}+0D+Q4Nr6uOU7PmvQa*^P}Z)f9Vi$tZ#r5HZ%XGwL>MBz zRr3l8`=67yxL3hU^cmtpy4SWs_es zh^Cw0WfPC|4A~mcO(XuDHpS$K;PG^s%)!<5IligS{9^ob?AL5zGN07S#h0c-7lV#NKl1bc)tM@D0np)w$iCnwi91*RPB! zUKC8NEmZbcE%&9WGW;8X>#$bWaJgV+i3xT$wR$A*4hGD&+GQLgu{W&T|HB5KA=E;T z>>M);+04Y74u}%PM*zo1cbB@8+uxOVUEq#%ld!2C0KWFaHneFlf@iF70mauQFvr2M z9-IkDI85YY>jYmD0b%||56%1ZNRZ0Gs$w4-uVEiszTgL8hpP3C|E)0NzGm}sl8@7E&e4vNunQOnw0Vil#ex6q*p0}vc|oqjkwLi{ zHX3Di1yLx9NxT2foGp=cxc~N|a9}6nB^UD*x(`_)%2C!sh?~N2=x_gFBynUXW0XCm zI~stmY_fUD$tmgcqJ00fky7Bz>-C6d6;t1%I|N0t?mm5}n^hvz>1z^Jlh3$vvTS)H9ozCUgvtH@iO9&?Rg#>l7i{|+V$}c2}X~0O@oo{#3ki_ZS@5K zJXgmR?pdTiG^D z4VqXRco&2@<5JHHWKyj$3{IMbZY|*OJg%d7T!f7q)n={#uG7iTDaz2mU`YpC-k_;j zPFB4d?w%~Iv34kHB1mj6qvvEQTt7l9jNDI0HgQaUn7aC!JXv&3W z8Qt1*eCDDXj6k*9qx4#m)EjsE_!?Msv{&dG(J4d}b}DXMnt=rL zHkoV*2Mr8~wlnV-@jMXy zTi9TLM1~o&IG3QD7dO1V`~eV~A7#}Vz?&IKG9MBYiQ}y%!Pcxb8O7(_3b`YOQh_-} z*{x=24;|zEBr{hxq+%yEo@1~tw-&fWvP8*rG%Dj~Z(tuD5p*W4UuT9WJfxnaB&VfB zGc+iiq$;Mhq>Dsa6Hl2dB+;@bZd8uy%-E!!@q01DUmjSRGKD2J-w`m@E<}JbajLLP zwV3dbU{8i#W-)VGa?(g*xC3pBqMXl`BG-CKPTL-Ml=r#bh_*xwAzYAy`kOo=Kgnb) zSx}QCMFqwZXg%PGvK=|fP~M0XY@*m(eNo-ftW+Um9lA-u<~H#_jWz^YzQ|est{T1h zI6nAJXR>n9JUUrcQx_n9ry?{@z}wPZMeiNu^?q8i5%cZl^Zr(NiR=XSb@{YUP#M%5 z1NgkVd-$xX8o0bpDfw;|UjKSKJO$XEC9mu9eS3wYCAL4SnPXAI*Fg^L;YZk29(gaa zy1%<#$3xi0GbH{#)f(4-zrUY2*Q~jx*VUP7<$D|1@mb-|IRhjrW_7o1tp36V)_igg&}I87&)4$Aej9ZEi_v$%qWM@~v`6boVnOrjZD1jJ2iH64bm z!!0HZE4C{h2f*N(mRLa-y$Q~&467mY&cfVEH7KgkGH5xE!?{W(Dx zk~|*w5)u6_{CVJrGjfPC^2#}hlefSiWeW+=DM~x9l4`}|H2rb0wBV3FqS^IHfkB#s z0!FBa-*$o(2@-N>JHHUME#IiH7~Pq}YiFo&Nvk7(Mk(c*3-|sh)YaJp})UHkca5xwIom>!Z0u8 z>l_Jwpx^!8Lt7ED-fw*0w{Yt4xHgDrQaT9$5}a&VUH8#kEz{iV$hzVbPReEqhhp_b zB!MaI@Pj`m3h{Nytp;(&wY(2bdYSG_L34Gl41_p~W@0~kVj`f`*f~rWd5SZ{fXTZ= z@x)#YxMrxngqt9h%UNEi#ovo0MnYR8e`G10aEHsaUp>BKNe(GAgMFsdLjQmxZh{xB z(wWZE892+`FnwaFXFe(B^<*Y%T2ct!#sas-;tnakxz?Idbg@##TK`f5)oc$)s#1KO z=C~_j)Fqg#*TQx{rvd4Ei%d=lfJY>feR>@fQ_KY?nwW&YRkak(*pvNAxUE!EC_R>XgQT)tYDTlsvfU=8V+!l?euE)2Hz zk^*N0xf`{h7?r!?3Pl{X2<9+MJJ0Y=_VpM4l?qSwcblzgEEoA8kcUMH{U8tdz-fmkAYN75;Gh3qb15S_*5tkt-6gCuQkg**RtmMeZS) z#81TpLP1AmND2a+ucAs-o<8-Zaq;`Kjm1zlU zC1BIv-$x%U@X2$@sB8*YVsSme7mqOQw~%kXQR7ugZ%FRl=UyXdk`$7F;qSEH?-_Yx z2H}Y7Yl;@=vy)?}M2*GqbL~BG08r|wjBKtd{j}detQzH0z)w(&!oQFV!a*0)1*U-c z!{*w*kN**s{&%jwVU)~h1{pL)$+Zbu)qisr9?`)vp08HdY z=HyC$(E}#2)UglhftSnwn-vsQ6ON31a1b{{NSO24G{B}*tNjaM)LOJ?I8%q$ao&RO1&4K45Age}cZ{)8>9=5G|f{$=Q{ zcG|vE6qoy3u%pI~n3`E5SIqvG3v|KMM62Ro(k^+0t^F-a9*^&kxm#jk@(Kwx{Sq5X z@wsA`4&ri!E?tGN(jv@B2scaIH}Ai6JMxf~%C=ZQeiSRnpbd46Kpec);Zi6~^Bq;q z99XyRR*Eq)BE5~i0J>!!#i#)-B6GbO#GpUT@~^sT-=&FQgP+%8)}DnbWz$ZL9dZKt z=?lR_fnjJ=6+pvWe~0T4NxH@;K`V6`6wl>ef06wcin-3n7O*dA7SahDO>e>I-FGWY zTWqVrs$X%twL-_+gj1jFa9w&$Zxr~AbfMRwligjE~Qhzx>b`OP2?x_N{>vZJ%FAawH7(pHpvtyzy!i{nH z8F)bBn7lE6oJx!ZY4t)uvxdEqC?5LCYu^1eZb4JoO{n9i9KHP($sDy)HhU9FxF>yx z_()a8u{&&5OSiq`)p6q|Pi6sHTEOJMYl)f`41E$f zH9KZmr1^?Fhb%PD?H^vww-9HX*jSjNFEUD7v_uki?N?>ZCt*efQklb`X!noC&N_C6dgMg*gW1XsYVaTmOz(N(G7B zpw%|Wuj%g&6;c!-Y99m1G@ zi|ED70H?-v=ud6{2C+x2&`r(HB?vnD>kg3evv>f3 z@9Tzn$>AsR(6aOWC24Tj!DY)`B?PWS7(~P{tfh%AB;ZW|>-TnP1j%A%EK1X;RoFa8 zvS_O$Ub;gi_-~>Z){h?N));PF8xwfE>nb!TIbtz6^~hHUZ3;+SO?})cI1u(WHDr<@ znBU|jTFtCY%sR{+w0{$B6ySSvc=EFvLcZs(pAUL4B%=OLal>OOectMoK;7R@A1a(# zUra&VQ077)bJ$b<&@>Osf=q4$YbfELJsxX~D6RO_>LCU7A{DTJ4N(n>kmewOZ7ui5}5QbgQ=z zf?!uCONbiLTSW9Ii&Y~88(nmQB!uW)Lh^3z-*Z3D`~3C3|9t0~Yp%J@%sFT3XXbp> z%rMgyyHAuv{0SGJ=cnOMk>%4_a~*{0U>*jlCPOkN6JcWHb`c$yM;Igqv&(X=J8Qc4 zjQ3UiL;PSBgEwj12#iR3!I@T?s{(I(iK22wd@q`T&#`Ml;~zL~FxkR6+z7?lPt$sI z4LU&wX#sn%fDHmWMy3$DHif8zG-Gk{j<3_YMYff*qJIIitv|AY?&HpGoE?&`jWdGz zjr^%mY<6B~Syo;36P0PZwnq&O*d2g1+m#2rwmC(TUzzn&747B z8lEnvMyrZ`A~~ymWc!Xg^OoY|w-w>21J7tavcibHoKA7b<1PQ>kuuu#ms(E04C7Ro zk!gGe#Lu`#=~|OOA*ROByU#E_A))D|t5NKhrEHuJ;hdX8sT5@}jYu+eDlJ5Udi1VK zwuhLb>@>%oZs3;6YcV3SLzii?QmMEegK|mYAh6TejuP9h7#lPRBE(5|hh_bRusJF2 zZOH?6wGE?bvgnbG*YtOm#r~WXz~ZN~kQQTrM6gnyi9#%I`2&j3w@g)cM`fxF$*oiwO1|IcotUx3@iD&j(`9$x$y#_! z3Q%%D09(%+sIyQfgL84R6urYas$nDdM*pciA-O;XhOUX%>8Yg2ck*MByrq!aAVYI7 z9_<*Xn*?TJ*YIXi+k&95();3klpM*ST@B|F^miczOMa;KTDd_dv4+U))_cC+ zD;3QeID`BOjQkIf`9?l|`3xQtQX)dSjwPOOJ_keU?=DB3Sf3^DEuu)N{VFdl%0afT z0J&%@m5dPD*OG3;P5Gft^K*PHr4{oqt4|-BMh91F8mc(w+ofd!1r#s?1wLhM#SP`T zyGt!i&Zj==vvZ*2CQYf90D(^0sfLO6|SqE{b-VEGGKQ@{4Y&8al9yoal4N<}5*y$l!(lmfo-hTHs&((wgt z{u$w9T~IE*=5K);?^MAoN&#!U<@p6|eoU^05U#TEiM02zNrJ3RKfagOqUW*--V@#* z^?3A=EdyC2%7A|Vo}-VDfg(0tE2k_iE^B!*xDq?KY)B>hTn3}lp7_YzbW*>pT(;Qa zvo?>rT0>WQR_e^jDt^#P8`cJBXJ|<3NoHh!(ZDIWY=d;ufZA<|3X5A$Yf7b+y6vpD zxBIa1&#WYUGPW7Fg-wMN$7Wu&5?Uz*<9X$)Gjr} z<40D~Rtn{*X61K&II9g8af}CfyW^IAJm$P=ZQ^_SOgDG5Xxarvc1Pr!b1I1mzBlX^ zxc#k3>Q0vP8m~d_agUuBnRi01C4`b66+zNl)bInLm(>hXH)EzD?}Mfxl3i*AIBORp z%_Oxsamx8pD9@!q5{`sA4M}aOTvRYuTb}c?*5CTsW!RIIwyFC9d0Mw;hY8>kBND+wsFSMUx1Vv(;9RqB}Lh>co@7Q&Bwr!qscnCUN1^7_UcKLFV1z? zo7~p@k%Y3xMIy+m=iPnK1R+ZSzo2NnA9JNR)cCjr3tu-1evaisvbTdjNt>9y@o;-% zGDSw27Hj!xBUVZnVtnlQcGH}v3crg9B56+68>g{~0`+?Fl-lDE#sxJ7DO^lTD)9)s z7`jLd4YYaRasuZR1F1HCjMFVWc{f*v^~q^wncQmmXhpg8$l9f*+`F_<%-GPkCAd+S z)A2s`)ivGIST1d33yOsL=mCet0WUja{lVVZZof%zCRTvGR~0VSh}(!(EfyWhh8l0F zS)FB7KH8f0px0eIHxNhQt0Ia%G2cp&zri^A!$-mJ5S$b?6(bAagT$0Y^yt_-jU6Zz zlD9$C$BG!zNtZz_RhR2~5K z=uj1W4%MQT&59ILFJ18;l6?C#7BjADw%qlQ28|e12l1@0E{TF=Hg4BOPT1ilL}uHQ zFEc;;iyQdTiE<tz9j$odyCx$1W~P4vnR8oEmPJX1 ztwxa%GL$~DtbE@xe3xQ*`ozZV!$_mQb@G;F8+nM)Dn1TnE1|{jhxQ%N43Ts`aN5l> zK%QG`dAA{&1wfW3)j=^Jbit=*skOPTc;fSMwRudFi4u2j!fOTyhaoH0+hk*Em1#^LkLR!7llH6CF2TZZs>VJz7_L#rcJTl*2G(9yzA4C+kEcT;5R@X*NssIwz^=robLq-!|R zi9MNwAi1cH*Ax&^!4&z(*ldg63-I(xuN2>$A(Kt6zL{S(T{Tz@?1;0K3p{i87%G`F zEb}ufM-QP8j?T`wX~xbnBjIs_94*`Futm!s+-LO8Az5Bet$e_gB3^nMM*g4B(A3ob ztF?S@YH~W5xucYw<_B`xq4<I5n-AHYaXISt1i7Q0 zvBrKw%Qx9Pj7&;x7UW4dZTpsri!PD4r7XuYD0piy6dyz4c9m?j1?7NY}4A(cZdR7n(_#iS(99dq1|z<-rU^c=l-%7H38_+5?S)2!2r&tE?m>B{y`A!#~b}x z`q`XCfgTPW5FK#)(v?>pabgz>Vr52XONybKQ5E4HmBunHSdeYxDL#y+N+g zZ6vw*&k-URc}{QLo$ylZ@+LleOYc$?trybEknLQdK<~-Pk2$(n8%f~Pp`Rj#9P#diklH<&<1dd>2{pO2k#u~1Jz_! zm+%;Cr-2WfQntc`7$%m3i-GqTK`(Hd)^5m}E{FI>=nIBq_bB#>7hCjG$^BAl?MR75 zhvNBH&ns84KL?qococlxWq2vKune>`uq3sEHW%b^fvmHVq|RH4$H|^`_Y$m z%~Rs~(eT$5EIskO`*l8%1NJXddkMWH3wj!^4N4Sf8#{00wY$hsg8PuDJtuQ^g;$@Q6or z$Yi^&|FH9^zwmyVQq^jEX!8Xvu^upRxRcIN_k0-ba=Cqms|+5QvGZZBo0UOwBMz|) zJlWyJ4?5D83j=e8-JhJH2e1O-4zDiP*Kn0#o7STlc~2MC`d_k0tB0E*hPQ$XG^vHU#&?XH>t2cA zN9msDo=a4|xpnXJpX*JY-`90^PHQtM+^2!?AQVROkZa=_r1!f&smCk4-Dl!}F<1F# zSm)aD*ON(C;_!mJh@WA8$07b!#lydb+&_$U?QRamrR&aaTc&J!cAK>#zbNku7);Hi z0K0jF8p68s=Mzj}AW?iElXXEO?h9*CoQ_Ir{%80?m zBr&Cebo!)5vTzMGbxBDz7)(wRuBxFbEh&9RLtXCH9f>=dccdj$m3bBapAwsY^I-By zNlM+yUZW%tMhdgzcV8VJUUf-^;;tk1pFDmDVL@MmxzYVjU>4C$Wa09ULks_1{+EOgn>gK!Gf0Z) zyh*&%;D9tYYd6LW%_25$&Rs$TMOh6?|M-B2u8N2i;jSWreXyu}eDr=+G)0rtg5|JI zJ+Ow@JLxTjNoi(jesMLR*^Sw)G$YVn+)8l4eK>R+)xzo>^Ojz!Sh|$jc#o(hY2T!n z`u-je2^^;>qBi0thPs8j5oOe@(k<{>-Tvk<9v8&1%Y;z)u7Zu3xf?>vrHM_N(aj;r zcbgqIFbazFkve=G4+lQ8gucG3pc=xJr;`38-6$fAp@w9atvv5FZ4I+NjUelMN)Ta9 zF8-m0&YMkNOr67(ZI{bRs9b8ty-8cDjYQ%bl4^l#;l7T<3FM`-k&oRwKW=EsjH-xg zJE|q_roIqgo<_P}%Y7Y`_ghN;K)1=Asa1vr@B-g3Zk6I?o;oBK(CFs>jAdBPkJSTHKkSK+ zQ1pyS8+psP%s6P}?2TjB?hcxK5lESo7beb;znoGUSTu1vOde~OaC766(d7I!=p~!x z=cBEi2#E!_m9${~^1af;kQ3d)(Y?h{+ZW<0i|X30=aO!p+)mu_oO#iWJ5@y);%7zF zMML+#7JYJKoq%Wft2VniW^md146Gm##iK=;Mw1*7L@ytawMkh``l@-})iqv^`kXWq zdlM5I{Tm?FmgT6kbZ$Lf=A@gR%JqkC!L$h^Sb4lSE;CUkr(NxvtMoiG-kML){XC^a zx+IbLgsGdch8$|3Eq;uh0P#8sC{5pv3!;PG(cVA1S2T~-YfU4P$08YOn7xtcw9&ou z;u~rL>dN`c`*#(RT2%2n0GwDDA1u7*dLBQqFfk*%we#D(3BoadiU;gX#%@&^kAnU{ z7Qh^B=XfThNpXQ%2X6@Qrc74;GBtm#J?vkvhqnA^$>RuL+E&e}URQ8$=^D1+zHBoT!t`2MWE5}C6 zph5wjy>ZSrB9aW%hYZcHTK`IFy%xKCD1cXDX{Dl(*Q*4#$gEUEZxPc(%Z$^=_t5(> zIjahNCtT(QK8yUaO8gxlq$>WMs6yp78$q-^8C5jBA4gJ@q&-z0$!1Roj9d&A%8bL{ zY@S7n_|fY{>#o9E30-<1FNn;06sE_? z9@=l|N%77_Ca>00l)*2P>d3S}0;Qpb|OnYsoW|2sN73Dbz-OGEN7#wElh%bTwK@y4lYYiRKEGtfUzNYKy# z@$=KuCnjk9e^3z`tvt^Y+z9DX|1tfi95Kl^X&ZU>6VvvGq|^T|zXTNxNQq!QRc$n< zJqcYZQkeFi613mnoFiP!gOf5PL=zg4G4PTG5HJAzZ-D%7^cU0vO(H!E ziRu-4_6V@itvX>xK#AHM88SRkP;_5#?;`2}?W2h=aX9&ql4{bSTJ zAm}gf=Oy~-lj(Optcoaz+Qf748@+ofH~EM~QY(S@xwW2S0xVIE`DzGnBRPjcDpD>+ zkFFlVW}bK+W@8Yu*~`>(p|W?xP*eyE=3Wg!ZT3o|ntMBR!cJu52>!F(D*jRUrnC1) zH3McybM+|C2!DLL=CLOzFh)5%t|frYjS3Q|?>wNlx6juF#7(s^7*Y(YkY+?v7?Pi% zz=qTwDm6M+tu_Yp1RTBONW(e?J7s=5smSmm_8lOwM|GUgs+Z4?ttm2S9PkWP%1bG) z%KDvD&X0;q$OyhAPyT}ATd2wU#QqTCBud$j;v^aXBa7>Su>mT2g>MlJ^jP}Q}^cbBW;nxj=Y>HG$r;|15y9g-a4yx0I$s-04p#s@ML$ zKtD71x5I=p|9}wfn3Cf8!BSrU9_*aeFZTJ+V(~!7bOL+ZTm2Xv?*yLrYvdv+?MTw; zxJt;-=n){~rawqP-6dA!U;P9q+5AMrze*3N(-!*OECofTK1;Dr-c6@-by|V|KZu1nR34E!mG}7E> z{Y=#WYespoy>%FjL z4xyqY$=;KuV?{dmrBbV(P{navNfUuj<3DFH9vZ^_A!VrM{{aGZwt6zH9s{P^l4-8$ z|FZuD1`D54AyBcgN?z#0xMBASMTUFW0^zuM8;G{cKGv=Gi))8 zo`~YTDxVe#But3x=OvjeA|6cBVIO?`sMpD#DU0oh8GJg%Y-C1gsa#1YtzxFVP2eeT z8QSwytBby#CharO;(Bl&SRL|}me5T8s0MYKdn*T~nuz z+15+hVO0rRoFnUir{(&)U?`<^d?dtmt!*G6d6Q#Iy1;X1X0E2e+6gn@lf2Ag& z{V5#0Y$UkZAD~1y(^IeGx}Ppy|38AD|Fy8OYZ6S4O&n70s`5N`O+ z|2|>>@dm=0^H;I5EY?OF_fm?4VXI42PIu! zts`Xj9A{H3*N+M`mH%TlVUa)N_*&FhfGSX8y5G=z#uAfLsO*{ul}*X&`SXnALjyU+soc( zGB25B=0q6M2%24Sw)kI%@lwkbo zcu%goC>Rg#I71??qxyeLty;5S!al82*I8T*l(JwJvOX@Iz3Jc0yLy(<@B47gcd7nj zcg*^VX7=jl0hZ^jA9q9W@gJ)tQ`f^Rah=hx&<>4A z5$JC0c~^SkmiD;Q`EZXFveL)gQ8%)Fe#~-SYtd1-_=o7jI#C_wf*1eA;o+JMI>m2| zCwz3>sp;ZFg!AEDC=1W$mJ4F!*@wcnbJslR+u9ak{w86iy6taE47wj}-4e^6LhhR7 z{8DyAo{Lz0pZeTtieM8{V^yG&?XH^c+Jc7F{{v0pKda6J4}Xmw>;+;4Y``N@s0o1**NI<@Y5aVXpE zdx&BC34M3)xM{$tu>Rr~t#z1!4{}YYx7#wTg1y7*Fva<(kRSb_ElbExv0+r*?vp#8atXbU@}3m>p@P^(S`pLlMTBSm+tFX zRWwf?0chv*&>rpOB9D^yVY(Um&-dm8x4gomEy4C)T#Dt@jtKgX7bdv854qkw1Nr@n zU5;~1Bipq^6w7@LKHiQAlPDUt7Qd@%4Vk-4ll6tZ%)XwG* zX~7jM!Ti0fWTScZ%o{TEzw9!o@63;setCollideWithMaskBits(CATEGORY2); \subsection{Material} \label{sec:material} - The material of a rigid body is used to describe the physical properties it is made of. This is represented by the \texttt{Material} class. Each body that - you create will have a default material. You can get the material of the rigid body using the \texttt{RigidBody::\allowbreak getMaterial()} method. \\ + The material of a collider is used to describe the physical properties it is made of. This is represented by the \texttt{Material} class. Each collider that you create will have a default material. You can get the material of the collider using the \texttt{Collider::\allowbreak getMaterial()} method. \\ You can use the material to set those physical properties. \\ - For instance, you can change the bounciness of the rigid body. The bounciness is a value between 0 and 1. The value 1 is used for a very bouncy - object and the value 0 means that the body will not be bouncy at all. To change the bounciness of the material, you can use the - \texttt{Material::\allowbreak setBounciness()} method. \\ + For instance, you can change the bounciness of the collider. The bounciness is a value between 0 and 1. The value 1 is used for + a very bouncy object and the value 0 means that the collider will not be bouncy at all. To change the bounciness of the material, + you can use the \texttt{Material::\allowbreak setBounciness()} method. \\ \begin{sloppypar} It is also possible to set the mass density of the collider which has a default value of 1. As described in section \ref{sec:rigidbodymass}, the @@ -1455,25 +1454,20 @@ colliderBody4->setCollideWithMaskBits(CATEGORY2); mass density of a collider, you need to use the \texttt{Material::setMassDensity()} method. \\ \end{sloppypar} - You are also able to change the friction coefficient of the body. This value needs to be between 0 and 1. If the value is 0, no friction will be - applied when the body is in contact with another body. However, if the value is 1, the friction force will be high. You can change the - friction coefficient of the material with the \texttt{Material::\allowbreak setFrictionCoefficient()} method. \\ + You are also able to change the friction coefficient of the collider. This value needs to be between 0 and 1. If the value is 0, + no friction will be applied when the collider is in contact with another collider. However, if the value is 1, the friction force will be high. You can + change the friction coefficient of the material with the \texttt{Material::\allowbreak setFrictionCoefficient()} method. \\ - You can use the material to add rolling resistance to a rigid body. Rolling resistance can be used to stop - a rolling object on a flat surface for instance. You should use this only with SphereShape or - CapsuleShape collision shapes. By default, rolling resistance is zero but you can - set a positive value using the \texttt{Material::\allowbreak setRollingResistance()} method to increase resistance. \\ - - Here is how to get the material of a rigid body and how to modify some of its properties: \\ + Here is how to get the material of a collider and how to modify some of its properties: \\ \begin{lstlisting} -// Get the current material of the body -Material& material = rigidBody->getMaterial(); +// Get the current material of the collider +Material& material = collider->getMaterial(); -// Change the bounciness of the body +// Change the bounciness of the collider material.setBounciness(0.4); -// Change the friction coefficient of the body +// Change the friction coefficient of the collider material.setFrictionCoefficient(0.2); \end{lstlisting} From 30af76dfaf51b0fc4cbb67bec9398706566d0c2f Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Mon, 21 Sep 2020 13:52:23 +0200 Subject: [PATCH 38/74] Small optimization of clipPolygonWithPlanes() function --- CMakeLists.txt | 2 +- include/reactphysics3d/containers/Array.h | 10 ++-- .../mathematics/mathematics_functions.h | 53 ++++++++++++------- 3 files changed, 40 insertions(+), 25 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b396a6d..6688ba44 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -121,6 +121,7 @@ set (REACTPHYSICS3D_HEADERS "include/reactphysics3d/collision/CollisionCallback.h" "include/reactphysics3d/collision/OverlapCallback.h" "include/reactphysics3d/mathematics/mathematics.h" + "include/reactphysics3d/mathematics/mathematics_common.h" "include/reactphysics3d/mathematics/mathematics_functions.h" "include/reactphysics3d/mathematics/Matrix2x2.h" "include/reactphysics3d/mathematics/Matrix3x3.h" @@ -220,7 +221,6 @@ set (REACTPHYSICS3D_SOURCES "src/components/SliderJointComponents.cpp" "src/collision/CollisionCallback.cpp" "src/collision/OverlapCallback.cpp" - "src/mathematics/mathematics_functions.cpp" "src/mathematics/Matrix2x2.cpp" "src/mathematics/Matrix3x3.cpp" "src/mathematics/Quaternion.cpp" diff --git a/include/reactphysics3d/containers/Array.h b/include/reactphysics3d/containers/Array.h index 6b450256..f79ac094 100755 --- a/include/reactphysics3d/containers/Array.h +++ b/include/reactphysics3d/containers/Array.h @@ -387,17 +387,19 @@ class Array { /// Remove an element from the array at a given index and replace it by the last one of the array (if any) /// Append another array at the end of the current one - void addRange(const Array& array) { + void addRange(const Array& array, uint32 startIndex = 0) { + + assert(startIndex <= array.size()); // If we need to allocate more memory - if (mSize + array.size() > mCapacity) { + if (mSize + (array.size() - startIndex) > mCapacity) { // Allocate memory - reserve(mSize + array.size()); + reserve(mSize + array.size() - startIndex); } // Add the elements of the array to the current one - for(uint32 i=0; i(mBuffer + mSize)) T(array[i]); mSize++; diff --git a/include/reactphysics3d/mathematics/mathematics_functions.h b/include/reactphysics3d/mathematics/mathematics_functions.h index fb333658..4f0e45bb 100755 --- a/include/reactphysics3d/mathematics/mathematics_functions.h +++ b/include/reactphysics3d/mathematics/mathematics_functions.h @@ -330,32 +330,37 @@ RP3D_FORCE_INLINE Array clipSegmentWithPlanes(const Vector3& segA, cons // Clip a polygon against multiple planes and return the clipped polygon vertices // This method implements the Sutherland–Hodgman clipping algorithm RP3D_FORCE_INLINE void clipPolygonWithPlanes(const Array& polygonVertices, const Array& planesPoints, - const Array& planesNormals, Array& outClippedPolygonVertices, - MemoryAllocator& allocator) { + const Array& planesNormals, Array& outClippedPolygonVertices, + MemoryAllocator& allocator) { assert(planesPoints.size() == planesNormals.size()); - const uint32 nbMaxElements = polygonVertices.size() + planesPoints.size(); - Array inputVertices(allocator, nbMaxElements); + const uint32 nbPlanesPoints = planesPoints.size(); + + const uint32 nbMaxElements = polygonVertices.size() * 2 * nbPlanesPoints; + Array vertices(allocator, nbMaxElements * nbPlanesPoints); outClippedPolygonVertices.reserve(nbMaxElements); - inputVertices.addRange(polygonVertices); + vertices.addRange(polygonVertices); + + uint32 currentPolygonStartIndex = 0; + uint32 nbInputVertices = polygonVertices.size(); // For each clipping plane - const uint32 nbPlanesPoints = planesPoints.size(); for (uint32 p=0; p < nbPlanesPoints; p++) { - outClippedPolygonVertices.clear(); + uint32 nextNbInputVertices = 0; - const uint32 nbInputVertices = inputVertices.size(); - uint32 vStart = nbInputVertices - 1; + uint32 vStart = currentPolygonStartIndex + nbInputVertices - 1; + + const decimal planeNormalDotPlanePoint = planesNormals[p].dot(planesPoints[p]); // For each edge of the polygon - for (uint vEnd = 0; vEnd& polygonVertic if (v1DotN < decimal(0.0)) { // The second point we keep is the intersection between the segment v1, v2 and the clipping plane - decimal t = computePlaneSegmentIntersection(v1, v2, planesNormals[p].dot(planesPoints[p]), planesNormals[p]); + const decimal t = computePlaneSegmentIntersection(v1, v2, planeNormalDotPlanePoint, planesNormals[p]); if (t >= decimal(0) && t <= decimal(1.0)) { - outClippedPolygonVertices.add(v1 + t * (v2 - v1)); + vertices.add(v1 + t * (v2 - v1)); + nextNbInputVertices++; } else { - outClippedPolygonVertices.add(v2); + vertices.add(v2); + nextNbInputVertices++; } } // Add the second vertex - outClippedPolygonVertices.add(v2); + vertices.add(v2); + nextNbInputVertices++; } else { // If the second vertex is behind the clipping plane @@ -386,13 +394,15 @@ RP3D_FORCE_INLINE void clipPolygonWithPlanes(const Array& polygonVertic if (v1DotN >= decimal(0.0)) { // The first point we keep is the intersection between the segment v1, v2 and the clipping plane - decimal t = computePlaneSegmentIntersection(v1, v2, -planesNormals[p].dot(planesPoints[p]), -planesNormals[p]); + const decimal t = computePlaneSegmentIntersection(v1, v2, -planeNormalDotPlanePoint, -planesNormals[p]); if (t >= decimal(0.0) && t <= decimal(1.0)) { - outClippedPolygonVertices.add(v1 + t * (v2 - v1)); + vertices.add(v1 + t * (v2 - v1)); + nextNbInputVertices++; } else { - outClippedPolygonVertices.add(v1); + vertices.add(v1); + nextNbInputVertices++; } } } @@ -400,8 +410,11 @@ RP3D_FORCE_INLINE void clipPolygonWithPlanes(const Array& polygonVertic vStart = vEnd; } - inputVertices = outClippedPolygonVertices; + currentPolygonStartIndex += nbInputVertices; + nbInputVertices = nextNbInputVertices; } + + outClippedPolygonVertices.addRange(vertices, currentPolygonStartIndex); } // Project a point onto a plane that is given by a point and its unit length normal From 71f7980fdf1eb8c3fd8f7103adb8c2010b28b05c Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Mon, 21 Sep 2020 13:59:23 +0200 Subject: [PATCH 39/74] Log an error when convex mesh is created with duplicated vertices and add in user manual that this is not supported --- .../UserManual/ReactPhysics3D-UserManual.tex | 3 + .../reactphysics3d/collision/PolyhedronMesh.h | 6 +- src/collision/PolyhedronMesh.cpp | 78 +++++++++++++++---- src/engine/PhysicsCommon.cpp | 11 ++- 4 files changed, 78 insertions(+), 20 deletions(-) diff --git a/documentation/UserManual/ReactPhysics3D-UserManual.tex b/documentation/UserManual/ReactPhysics3D-UserManual.tex index 60ff1353..dabb5bc8 100644 --- a/documentation/UserManual/ReactPhysics3D-UserManual.tex +++ b/documentation/UserManual/ReactPhysics3D-UserManual.tex @@ -1236,6 +1236,9 @@ ConvexMeshShape* convexMeshShape = physicsCommon.createConvexMeshShape(polyhedro that you need to avoid coplanar faces in your convex mesh shape. Coplanar faces have to be merged together. Remember that convex meshes are not limited to triangular faces, you can create faces with more than three vertices. \\ + Also note that meshes with duplicated vertices are not supported. The number of vertices you pass to create the PolygonVertexArray must be exactly + the number of vertices in your convex mesh. \\ + When you specify the vertices for each face of your convex mesh, be careful with their order. The vertices of a face must be specified in counter clockwise order as seen from the outside of your convex mesh. \\ diff --git a/include/reactphysics3d/collision/PolyhedronMesh.h b/include/reactphysics3d/collision/PolyhedronMesh.h index f4e57bc9..210173d7 100644 --- a/include/reactphysics3d/collision/PolyhedronMesh.h +++ b/include/reactphysics3d/collision/PolyhedronMesh.h @@ -28,6 +28,7 @@ // Libraries #include +#include #include "HalfEdgeStructure.h" namespace reactphysics3d { @@ -69,7 +70,7 @@ class PolyhedronMesh { PolyhedronMesh(PolygonVertexArray* polygonVertexArray, MemoryAllocator& allocator); /// Create the half-edge structure of the mesh - void createHalfEdgeStructure(); + bool createHalfEdgeStructure(); /// Compute the faces normals void computeFacesNormals(); @@ -80,6 +81,9 @@ class PolyhedronMesh { /// Compute and return the area of a face decimal getFaceArea(uint faceIndex) const; + /// Static factory method to create a polyhedron mesh + static PolyhedronMesh* create(PolygonVertexArray* polygonVertexArray, MemoryAllocator& polyhedronMeshAllocator, MemoryAllocator& dataAllocator); + public: // -------------------- Methods -------------------- // diff --git a/src/collision/PolyhedronMesh.cpp b/src/collision/PolyhedronMesh.cpp index 9d92fc00..b205b1cd 100644 --- a/src/collision/PolyhedronMesh.cpp +++ b/src/collision/PolyhedronMesh.cpp @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include using namespace reactphysics3d; @@ -37,38 +39,62 @@ using namespace reactphysics3d; * Create a polyhedron mesh given an array of polygons. * @param polygonVertexArray Pointer to the array of polygons and their vertices */ -PolyhedronMesh::PolyhedronMesh(PolygonVertexArray* polygonVertexArray, MemoryAllocator &allocator) +PolyhedronMesh::PolyhedronMesh(PolygonVertexArray* polygonVertexArray, MemoryAllocator& allocator) : mMemoryAllocator(allocator), mHalfEdgeStructure(allocator, polygonVertexArray->getNbFaces(), polygonVertexArray->getNbVertices(), - (polygonVertexArray->getNbFaces() + polygonVertexArray->getNbVertices() - 2) * 2) { + (polygonVertexArray->getNbFaces() + polygonVertexArray->getNbVertices() - 2) * 2), mFacesNormals(nullptr) { mPolygonVertexArray = polygonVertexArray; - - // Create the half-edge structure of the mesh - createHalfEdgeStructure(); - - // Create the face normals array - mFacesNormals = new Vector3[mHalfEdgeStructure.getNbFaces()]; - - // Compute the faces normals - computeFacesNormals(); - - // Compute the centroid - computeCentroid(); } // Destructor PolyhedronMesh::~PolyhedronMesh() { - delete[] mFacesNormals; + + if (mFacesNormals != nullptr) { + + for (uint f=0; f < mHalfEdgeStructure.getNbFaces(); f++) { + mFacesNormals[f].~Vector3(); + } + + mMemoryAllocator.release(mFacesNormals, mHalfEdgeStructure.getNbFaces() * sizeof(Vector3)); + } +} + +/// Static factory method to create a polyhedron mesh. This methods returns null_ptr if the mesh is not valid +PolyhedronMesh* PolyhedronMesh::create(PolygonVertexArray* polygonVertexArray, MemoryAllocator& polyhedronMeshAllocator, MemoryAllocator& dataAllocator) { + + PolyhedronMesh* mesh = new (polyhedronMeshAllocator.allocate(sizeof(PolyhedronMesh))) PolyhedronMesh(polygonVertexArray, dataAllocator); + + // Create the half-edge structure of the mesh + bool isValid = mesh->createHalfEdgeStructure(); + + if (isValid) { + + // Compute the faces normals + mesh->computeFacesNormals(); + + // Compute the centroid + mesh->computeCentroid(); + } + else { + mesh->~PolyhedronMesh(); + polyhedronMeshAllocator.release(mesh, sizeof(PolyhedronMesh)); + mesh = nullptr; + } + + return mesh; } // Create the half-edge structure of the mesh -void PolyhedronMesh::createHalfEdgeStructure() { +/// This method returns true if the mesh is valid or false otherwise +bool PolyhedronMesh::createHalfEdgeStructure() { // For each vertex of the mesh for (uint v=0; v < mPolygonVertexArray->getNbVertices(); v++) { mHalfEdgeStructure.addVertex(v); } + uint32 nbEdges = 0; + // For each polygon face of the mesh for (uint f=0; f < mPolygonVertexArray->getNbFaces(); f++) { @@ -82,14 +108,34 @@ void PolyhedronMesh::createHalfEdgeStructure() { faceVertices.add(mPolygonVertexArray->getVertexIndexInFace(f, v)); } + nbEdges += face->nbVertices; + assert(faceVertices.size() >= 3); // Addd the face into the half-edge structure mHalfEdgeStructure.addFace(faceVertices); } + nbEdges /= 2; + + // If the mesh is valid (check Euler formula V + F - E = 2) and does not use duplicated vertices + if (2 + nbEdges - mPolygonVertexArray->getNbFaces() != mPolygonVertexArray->getNbVertices()) { + + RP3D_LOG("PhysicsCommon", Logger::Level::Error, Logger::Category::PhysicCommon, + "Error when creating a PolyhedronMesh: input PolygonVertexArray is not valid. Mesh with duplicated vertices is not supported.", __FILE__, __LINE__); + + assert(false); + + return false; + } + // Initialize the half-edge structure mHalfEdgeStructure.init(); + + // Create the face normals array + mFacesNormals = new (mMemoryAllocator.allocate(mHalfEdgeStructure.getNbFaces() * sizeof(Vector3))) Vector3[mHalfEdgeStructure.getNbFaces()]; + + return true; } /// Return a vertex diff --git a/src/engine/PhysicsCommon.cpp b/src/engine/PhysicsCommon.cpp index 08ef4248..a36e6e26 100644 --- a/src/engine/PhysicsCommon.cpp +++ b/src/engine/PhysicsCommon.cpp @@ -488,13 +488,18 @@ void PhysicsCommon::deleteConcaveMeshShape(ConcaveMeshShape* concaveMeshShape) { // Create a polyhedron mesh /** * @param polygonVertexArray A pointer to the polygon vertex array to use to create the polyhedron mesh - * @return A pointer to the created polyhedron mesh + * @return A pointer to the created polyhedron mesh or nullptr if the mesh is not valid */ PolyhedronMesh* PhysicsCommon::createPolyhedronMesh(PolygonVertexArray* polygonVertexArray) { - PolyhedronMesh* mesh = new (mMemoryManager.allocate(MemoryManager::AllocationType::Pool, sizeof(PolyhedronMesh))) PolyhedronMesh(polygonVertexArray, mMemoryManager.getHeapAllocator()); + // Create the polyhedron mesh + PolyhedronMesh* mesh = PolyhedronMesh::create(polygonVertexArray, mMemoryManager.getPoolAllocator(), mMemoryManager.getHeapAllocator()); - mPolyhedronMeshes.add(mesh); + // If the mesh is valid + if (mesh != nullptr) { + + mPolyhedronMeshes.add(mesh); + } return mesh; } From fa722c129d5b52806ce5c3a97f8d16abdb9e394d Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Sat, 26 Sep 2020 14:55:22 +0200 Subject: [PATCH 40/74] Better optimization to allocate correct amount of memory for potential manifolds and contact points --- .../reactphysics3d/systems/CollisionDetectionSystem.h | 6 ++++++ src/systems/CollisionDetectionSystem.cpp | 10 +++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/include/reactphysics3d/systems/CollisionDetectionSystem.h b/include/reactphysics3d/systems/CollisionDetectionSystem.h index 9431279b..29b21d73 100644 --- a/include/reactphysics3d/systems/CollisionDetectionSystem.h +++ b/include/reactphysics3d/systems/CollisionDetectionSystem.h @@ -162,6 +162,12 @@ class CollisionDetectionSystem { /// Array with the indices of all the contact pairs that have at least one CollisionBody Array mCollisionBodyContactPairsIndices; + /// Number of potential contact manifolds in the previous frame + uint32 mNbPreviousPotentialContactManifolds; + + /// Number of potential contact points in the previous frame + uint32 mNbPreviousPotentialContactPoints; + #ifdef IS_RP3D_PROFILING_ENABLED /// Pointer to the profiler diff --git a/src/systems/CollisionDetectionSystem.cpp b/src/systems/CollisionDetectionSystem.cpp index e80d07df..00e4d78c 100644 --- a/src/systems/CollisionDetectionSystem.cpp +++ b/src/systems/CollisionDetectionSystem.cpp @@ -67,7 +67,8 @@ CollisionDetectionSystem::CollisionDetectionSystem(PhysicsWorld* world, Collider mContactManifolds1(mMemoryManager.getPoolAllocator()), mContactManifolds2(mMemoryManager.getPoolAllocator()), mPreviousContactManifolds(&mContactManifolds1), mCurrentContactManifolds(&mContactManifolds2), mContactPoints1(mMemoryManager.getPoolAllocator()), mContactPoints2(mMemoryManager.getPoolAllocator()), - mPreviousContactPoints(&mContactPoints1), mCurrentContactPoints(&mContactPoints2), mCollisionBodyContactPairsIndices(mMemoryManager.getSingleFrameAllocator()) { + mPreviousContactPoints(&mContactPoints1), mCurrentContactPoints(&mContactPoints2), mCollisionBodyContactPairsIndices(mMemoryManager.getSingleFrameAllocator()), + mNbPreviousPotentialContactManifolds(0), mNbPreviousPotentialContactPoints(0) { #ifdef IS_RP3D_PROFILING_ENABLED @@ -595,8 +596,8 @@ void CollisionDetectionSystem::computeNarrowPhase() { // Swap the previous and current contacts arrays swapPreviousAndCurrentContacts(); - mPotentialContactManifolds.reserve(mPreviousContactManifolds->size()); - mPotentialContactPoints.reserve(mPreviousContactPoints->size()); + mPotentialContactManifolds.reserve(mNbPreviousPotentialContactManifolds); + mPotentialContactPoints.reserve(mNbPreviousPotentialContactPoints); // Test the narrow-phase collision detection on the batches to be tested testNarrowPhaseCollision(mNarrowPhaseInput, true, allocator); @@ -879,6 +880,9 @@ void CollisionDetectionSystem::createContacts() { mPreviousContactManifolds->clear(); mPreviousContactPairs->clear(); + mNbPreviousPotentialContactManifolds = mPotentialContactManifolds.capacity(); + mNbPreviousPotentialContactPoints = mPotentialContactPoints.capacity(); + // Reset the potential contacts mPotentialContactPoints.clear(true); mPotentialContactManifolds.clear(true); From 21f9b39bc4432b139e33652e46770995f07e70ca Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Sat, 26 Sep 2020 15:07:20 +0200 Subject: [PATCH 41/74] Small optimizations --- .../collision/shapes/TriangleShape.h | 65 ++++++++++++++++++ .../narrowphase/SAT/SATAlgorithm.cpp | 18 +++-- .../shapes/ConvexPolyhedronShape.cpp | 5 +- src/collision/shapes/TriangleShape.cpp | 66 ------------------- 4 files changed, 81 insertions(+), 73 deletions(-) diff --git a/include/reactphysics3d/collision/shapes/TriangleShape.h b/include/reactphysics3d/collision/shapes/TriangleShape.h index 823f33bd..5224f242 100644 --- a/include/reactphysics3d/collision/shapes/TriangleShape.h +++ b/include/reactphysics3d/collision/shapes/TriangleShape.h @@ -312,6 +312,71 @@ RP3D_FORCE_INLINE decimal TriangleShape::getVolume() const { return decimal(0.0); } +// Get a smooth contact normal for collision for a triangle of the mesh +/// This is used to avoid the internal edges issue that occurs when a shape is colliding with +/// several triangles of a concave mesh. If the shape collide with an edge of the triangle for instance, +/// the computed contact normal from this triangle edge is not necessarily in the direction of the surface +/// normal of the mesh at this point. The idea to solve this problem is to use the real (smooth) surface +/// normal of the mesh at this point as the contact normal. This technique is described in the chapter 5 +/// of the Game Physics Pearl book by Gino van der Bergen and Dirk Gregorius. The vertices normals of the +/// mesh are either provided by the user or precomputed if the user did not provide them. Note that we only +/// use the interpolated normal if the contact point is on an edge of the triangle. If the contact is in the +/// middle of the triangle, we return the true triangle normal. +RP3D_FORCE_INLINE Vector3 TriangleShape::computeSmoothLocalContactNormalForTriangle(const Vector3& localContactPoint) const { + + // Compute the barycentric coordinates of the point in the triangle + decimal u, v, w; + computeBarycentricCoordinatesInTriangle(mPoints[0], mPoints[1], mPoints[2], localContactPoint, u, v, w); + + // If the contact is in the middle of the triangle face (not on the edges) + if (u > MACHINE_EPSILON && v > MACHINE_EPSILON && w > MACHINE_EPSILON) { + + // We return the true triangle face normal (not the interpolated one) + return mNormal; + } + + // We compute the contact normal as the barycentric interpolation of the three vertices normals + const Vector3 interpolatedNormal = u * mVerticesNormals[0] + v * mVerticesNormals[1] + w * mVerticesNormals[2]; + + // If the interpolated normal is degenerated + if (interpolatedNormal.lengthSquare() < MACHINE_EPSILON) { + + // Return the original normal + return mNormal; + } + + return interpolatedNormal.getUnit(); +} + +// This method compute the smooth mesh contact with a triangle in case one of the two collision +// shapes is a triangle. The idea in this case is to use a smooth vertex normal of the triangle mesh +// at the contact point instead of the triangle normal to avoid the internal edge collision issue. +// This method will return the new smooth world contact +// normal of the triangle and the the local contact point on the other shape. +RP3D_FORCE_INLINE void TriangleShape::computeSmoothTriangleMeshContact(const CollisionShape* shape1, const CollisionShape* shape2, + Vector3& localContactPointShape1, Vector3& localContactPointShape2, + const Transform& shape1ToWorld, const Transform& shape2ToWorld, + decimal penetrationDepth, Vector3& outSmoothVertexNormal) { + + assert(shape1->getName() != CollisionShapeName::TRIANGLE || shape2->getName() != CollisionShapeName::TRIANGLE); + + // If one the shape is a triangle + bool isShape1Triangle = shape1->getName() == CollisionShapeName::TRIANGLE; + if (isShape1Triangle || shape2->getName() == CollisionShapeName::TRIANGLE) { + + const TriangleShape* triangleShape = isShape1Triangle ? static_cast(shape1): + static_cast(shape2); + + // Compute the smooth triangle mesh contact normal and recompute the local contact point on the other shape + triangleShape->computeSmoothMeshContact(isShape1Triangle ? localContactPointShape1 : localContactPointShape2, + isShape1Triangle ? shape1ToWorld : shape2ToWorld, + isShape1Triangle ? shape2ToWorld.getInverse() : shape1ToWorld.getInverse(), + penetrationDepth, isShape1Triangle, + isShape1Triangle ? localContactPointShape2 : localContactPointShape1, + outSmoothVertexNormal); + } +} + } #endif diff --git a/src/collision/narrowphase/SAT/SATAlgorithm.cpp b/src/collision/narrowphase/SAT/SATAlgorithm.cpp index 3e1daa4c..aa7797bb 100644 --- a/src/collision/narrowphase/SAT/SATAlgorithm.cpp +++ b/src/collision/narrowphase/SAT/SATAlgorithm.cpp @@ -55,8 +55,7 @@ SATAlgorithm::SATAlgorithm(bool clipWithPreviousAxisIfStillColliding, MemoryAllo } // Test collision between a sphere and a convex mesh -bool SATAlgorithm::testCollisionSphereVsConvexPolyhedron(NarrowPhaseInfoBatch& narrowPhaseInfoBatch, - uint batchStartIndex, uint batchNbItems) const { +bool SATAlgorithm::testCollisionSphereVsConvexPolyhedron(NarrowPhaseInfoBatch& narrowPhaseInfoBatch, uint batchStartIndex, uint batchNbItems) const { bool isCollisionFound = false; @@ -895,11 +894,20 @@ bool SATAlgorithm::computePolyhedronVsPolyhedronFaceContactPoints(bool isMinPene RP3D_PROFILE("SATAlgorithm::computePolyhedronVsPolyhedronFaceContactPoints", mProfiler); - const ConvexPolyhedronShape* referencePolyhedron = isMinPenetrationFaceNormalPolyhedron1 ? polyhedron1 : polyhedron2; - const ConvexPolyhedronShape* incidentPolyhedron = isMinPenetrationFaceNormalPolyhedron1 ? polyhedron2 : polyhedron1; + const ConvexPolyhedronShape* referencePolyhedron; + const ConvexPolyhedronShape* incidentPolyhedron; const Transform& referenceToIncidentTransform = isMinPenetrationFaceNormalPolyhedron1 ? polyhedron1ToPolyhedron2 : polyhedron2ToPolyhedron1; const Transform& incidentToReferenceTransform = isMinPenetrationFaceNormalPolyhedron1 ? polyhedron2ToPolyhedron1 : polyhedron1ToPolyhedron2; + if (isMinPenetrationFaceNormalPolyhedron1) { + referencePolyhedron = polyhedron1; + incidentPolyhedron = polyhedron2; + } + else { + referencePolyhedron = polyhedron2; + incidentPolyhedron = polyhedron1; + } + const Vector3 axisReferenceSpace = referencePolyhedron->getFaceNormal(minFaceIndex); const Vector3 axisIncidentSpace = referenceToIncidentTransform.getOrientation() * axisReferenceSpace; @@ -972,7 +980,7 @@ bool SATAlgorithm::computePolyhedronVsPolyhedronFaceContactPoints(bool isMinPene // the maximal penetration depth of any contact point for this separating axis decimal penetrationDepth = (referenceFaceVertex - clipPolygonVertices[i]).dot(axisReferenceSpace); - // If the clip point is bellow the reference face + // If the clip point is below the reference face if (penetrationDepth > decimal(0.0)) { contactPointsFound = true; diff --git a/src/collision/shapes/ConvexPolyhedronShape.cpp b/src/collision/shapes/ConvexPolyhedronShape.cpp index bb907a2b..9ec5f1a8 100644 --- a/src/collision/shapes/ConvexPolyhedronShape.cpp +++ b/src/collision/shapes/ConvexPolyhedronShape.cpp @@ -45,10 +45,11 @@ uint ConvexPolyhedronShape::findMostAntiParallelFace(const Vector3& direction) c uint mostAntiParallelFace = 0; // For each face of the polyhedron - for (uint i=0; i < getNbFaces(); i++) { + uint32 nbFaces = getNbFaces(); + for (uint32 i=0; i < nbFaces; i++) { // Get the face normal - decimal dotProduct = getFaceNormal(i).dot(direction); + const decimal dotProduct = getFaceNormal(i).dot(direction); if (dotProduct < minDotProduct) { minDotProduct = dotProduct; mostAntiParallelFace = i; diff --git a/src/collision/shapes/TriangleShape.cpp b/src/collision/shapes/TriangleShape.cpp index 2d1b1380..091d2507 100644 --- a/src/collision/shapes/TriangleShape.cpp +++ b/src/collision/shapes/TriangleShape.cpp @@ -122,36 +122,6 @@ TriangleShape::TriangleShape(const Vector3* vertices, const Vector3* verticesNor mId = shapeId; } -// This method compute the smooth mesh contact with a triangle in case one of the two collision -// shapes is a triangle. The idea in this case is to use a smooth vertex normal of the triangle mesh -// at the contact point instead of the triangle normal to avoid the internal edge collision issue. -// This method will return the new smooth world contact -// normal of the triangle and the the local contact point on the other shape. -void TriangleShape::computeSmoothTriangleMeshContact(const CollisionShape* shape1, const CollisionShape* shape2, - Vector3& localContactPointShape1, Vector3& localContactPointShape2, - const Transform& shape1ToWorld, const Transform& shape2ToWorld, - decimal penetrationDepth, Vector3& outSmoothVertexNormal) { - - assert(shape1->getName() != CollisionShapeName::TRIANGLE || shape2->getName() != CollisionShapeName::TRIANGLE); - - // If one the shape is a triangle - bool isShape1Triangle = shape1->getName() == CollisionShapeName::TRIANGLE; - if (isShape1Triangle || shape2->getName() == CollisionShapeName::TRIANGLE) { - - const TriangleShape* triangleShape = isShape1Triangle ? static_cast(shape1): - static_cast(shape2); - - // Compute the smooth triangle mesh contact normal and recompute the local contact point on the other shape - triangleShape->computeSmoothMeshContact(isShape1Triangle ? localContactPointShape1 : localContactPointShape2, - isShape1Triangle ? shape1ToWorld : shape2ToWorld, - isShape1Triangle ? shape2ToWorld.getInverse() : shape1ToWorld.getInverse(), - penetrationDepth, isShape1Triangle, - isShape1Triangle ? localContactPointShape2 : localContactPointShape1, - outSmoothVertexNormal); - } -} - - // This method implements the technique described in Game Physics Pearl book // by Gino van der Bergen and Dirk Gregorius to get smooth triangle mesh collision. The idea is // to replace the contact normal of the triangle shape with the precomputed normal of the triangle @@ -186,42 +156,6 @@ void TriangleShape::computeSmoothMeshContact(Vector3 localContactPointTriangle, outNewLocalContactPointOtherShape.setAllValues(otherShapePoint.x, otherShapePoint.y, otherShapePoint.z); } -// Get a smooth contact normal for collision for a triangle of the mesh -/// This is used to avoid the internal edges issue that occurs when a shape is colliding with -/// several triangles of a concave mesh. If the shape collide with an edge of the triangle for instance, -/// the computed contact normal from this triangle edge is not necessarily in the direction of the surface -/// normal of the mesh at this point. The idea to solve this problem is to use the real (smooth) surface -/// normal of the mesh at this point as the contact normal. This technique is described in the chapter 5 -/// of the Game Physics Pearl book by Gino van der Bergen and Dirk Gregorius. The vertices normals of the -/// mesh are either provided by the user or precomputed if the user did not provide them. Note that we only -/// use the interpolated normal if the contact point is on an edge of the triangle. If the contact is in the -/// middle of the triangle, we return the true triangle normal. -Vector3 TriangleShape::computeSmoothLocalContactNormalForTriangle(const Vector3& localContactPoint) const { - - // Compute the barycentric coordinates of the point in the triangle - decimal u, v, w; - computeBarycentricCoordinatesInTriangle(mPoints[0], mPoints[1], mPoints[2], localContactPoint, u, v, w); - - // If the contact is in the middle of the triangle face (not on the edges) - if (u > MACHINE_EPSILON && v > MACHINE_EPSILON && w > MACHINE_EPSILON) { - - // We return the true triangle face normal (not the interpolated one) - return mNormal; - } - - // We compute the contact normal as the barycentric interpolation of the three vertices normals - Vector3 interpolatedNormal = u * mVerticesNormals[0] + v * mVerticesNormals[1] + w * mVerticesNormals[2]; - - // If the interpolated normal is degenerated - if (interpolatedNormal.lengthSquare() < MACHINE_EPSILON) { - - // Return the original normal - return mNormal; - } - - return interpolatedNormal.getUnit(); -} - // Update the AABB of a body using its collision shape /** * @param[out] aabb The axis-aligned bounding box (AABB) of the collision shape From d4be363e7c7728697c0d1a1ccc36c0665c2a2757 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Wed, 30 Sep 2020 00:22:16 +0200 Subject: [PATCH 42/74] Optimization of face vs face clipping in SAT algorithm --- .../mathematics/mathematics_functions.h | 111 +++++++----------- .../narrowphase/SAT/SATAlgorithm.cpp | 53 ++++++--- 2 files changed, 79 insertions(+), 85 deletions(-) diff --git a/include/reactphysics3d/mathematics/mathematics_functions.h b/include/reactphysics3d/mathematics/mathematics_functions.h index 4f0e45bb..a163712a 100755 --- a/include/reactphysics3d/mathematics/mathematics_functions.h +++ b/include/reactphysics3d/mathematics/mathematics_functions.h @@ -327,94 +327,69 @@ RP3D_FORCE_INLINE Array clipSegmentWithPlanes(const Vector3& segA, cons return outputVertices; } -// Clip a polygon against multiple planes and return the clipped polygon vertices -// This method implements the Sutherland–Hodgman clipping algorithm -RP3D_FORCE_INLINE void clipPolygonWithPlanes(const Array& polygonVertices, const Array& planesPoints, - const Array& planesNormals, Array& outClippedPolygonVertices, - MemoryAllocator& allocator) { +// Clip a polygon against a single plane and return the clipped polygon vertices +// This method implements the Sutherland–Hodgman polygon clipping algorithm +RP3D_FORCE_INLINE void clipPolygonWithPlane(const Array& polygonVertices, const Vector3& planePoint, + const Vector3& planeNormal, Array& outClippedPolygonVertices) { - assert(planesPoints.size() == planesNormals.size()); - - const uint32 nbPlanesPoints = planesPoints.size(); - - const uint32 nbMaxElements = polygonVertices.size() * 2 * nbPlanesPoints; - Array vertices(allocator, nbMaxElements * nbPlanesPoints); - - outClippedPolygonVertices.reserve(nbMaxElements); - - vertices.addRange(polygonVertices); - - uint32 currentPolygonStartIndex = 0; uint32 nbInputVertices = polygonVertices.size(); - // For each clipping plane - for (uint32 p=0; p < nbPlanesPoints; p++) { + assert(outClippedPolygonVertices.size() == 0); - uint32 nextNbInputVertices = 0; + uint32 vStartIndex = nbInputVertices - 1; - uint32 vStart = currentPolygonStartIndex + nbInputVertices - 1; + const decimal planeNormalDotPlanePoint = planeNormal.dot(planePoint); - const decimal planeNormalDotPlanePoint = planesNormals[p].dot(planesPoints[p]); + decimal vStartDotN = (polygonVertices[vStartIndex] - planePoint).dot(planeNormal); - // For each edge of the polygon - for (uint vEnd = currentPolygonStartIndex; vEnd < currentPolygonStartIndex + nbInputVertices; vEnd++) { + // For each edge of the polygon + for (uint vEndIndex = 0; vEndIndex < nbInputVertices; vEndIndex++) { - Vector3& v1 = vertices[vStart]; - Vector3& v2 = vertices[vEnd]; + const Vector3& vStart = polygonVertices[vStartIndex]; + const Vector3& vEnd = polygonVertices[vEndIndex]; - const decimal v1DotN = (v1 - planesPoints[p]).dot(planesNormals[p]); - const decimal v2DotN = (v2 - planesPoints[p]).dot(planesNormals[p]); + const decimal vEndDotN = (vEnd - planePoint).dot(planeNormal); - // If the second vertex is in front of the clippling plane - if (v2DotN >= decimal(0.0)) { + // If the second vertex is in front of the clippling plane + if (vEndDotN >= decimal(0.0)) { - // If the first vertex is not in front of the clippling plane - if (v1DotN < decimal(0.0)) { + // If the first vertex is not in front of the clippling plane + if (vStartDotN < decimal(0.0)) { - // The second point we keep is the intersection between the segment v1, v2 and the clipping plane - const decimal t = computePlaneSegmentIntersection(v1, v2, planeNormalDotPlanePoint, planesNormals[p]); + // The second point we keep is the intersection between the segment v1, v2 and the clipping plane + const decimal t = computePlaneSegmentIntersection(vStart, vEnd, planeNormalDotPlanePoint, planeNormal); - if (t >= decimal(0) && t <= decimal(1.0)) { - vertices.add(v1 + t * (v2 - v1)); - nextNbInputVertices++; - } - else { - vertices.add(v2); - nextNbInputVertices++; - } + if (t >= decimal(0) && t <= decimal(1.0)) { + outClippedPolygonVertices.add(vStart + t * (vEnd - vStart)); } - - // Add the second vertex - vertices.add(v2); - nextNbInputVertices++; - } - else { // If the second vertex is behind the clipping plane - - // If the first vertex is in front of the clippling plane - if (v1DotN >= decimal(0.0)) { - - // The first point we keep is the intersection between the segment v1, v2 and the clipping plane - const decimal t = computePlaneSegmentIntersection(v1, v2, -planeNormalDotPlanePoint, -planesNormals[p]); - - if (t >= decimal(0.0) && t <= decimal(1.0)) { - vertices.add(v1 + t * (v2 - v1)); - nextNbInputVertices++; - } - else { - vertices.add(v1); - nextNbInputVertices++; - } + else { + outClippedPolygonVertices.add(vEnd); } } - vStart = vEnd; + // Add the second vertex + outClippedPolygonVertices.add(vEnd); + } + else { // If the second vertex is behind the clipping plane + + // If the first vertex is in front of the clippling plane + if (vStartDotN >= decimal(0.0)) { + + // The first point we keep is the intersection between the segment v1, v2 and the clipping plane + const decimal t = computePlaneSegmentIntersection(vStart, vEnd, -planeNormalDotPlanePoint, -planeNormal); + + if (t >= decimal(0.0) && t <= decimal(1.0)) { + outClippedPolygonVertices.add(vStart + t * (vEnd - vStart)); + } + else { + outClippedPolygonVertices.add(vStart); + } + } } - currentPolygonStartIndex += nbInputVertices; - nbInputVertices = nextNbInputVertices; + vStartIndex = vEndIndex; + vStartDotN = vEndDotN; } - - outClippedPolygonVertices.addRange(vertices, currentPolygonStartIndex); } // Project a point onto a plane that is given by a point and its unit length normal diff --git a/src/collision/narrowphase/SAT/SATAlgorithm.cpp b/src/collision/narrowphase/SAT/SATAlgorithm.cpp index aa7797bb..bfc32951 100644 --- a/src/collision/narrowphase/SAT/SATAlgorithm.cpp +++ b/src/collision/narrowphase/SAT/SATAlgorithm.cpp @@ -925,21 +925,28 @@ bool SATAlgorithm::computePolyhedronVsPolyhedronFaceContactPoints(bool isMinPene const HalfEdgeStructure::Face& incidentFace = incidentPolyhedron->getFace(incidentFaceIndex); const uint32 nbIncidentFaceVertices = incidentFace.faceVertices.size(); - Array polygonVertices(mMemoryAllocator, nbIncidentFaceVertices); // Vertices to clip of the incident face - Array planesNormals(mMemoryAllocator, nbIncidentFaceVertices); // Normals of the clipping planes - Array planesPoints(mMemoryAllocator, nbIncidentFaceVertices); // Points on the clipping planes + const uint32 nbMaxElements = nbIncidentFaceVertices * 2 * referenceFace.faceVertices.size(); + Array verticesTemp1(mMemoryAllocator, nbMaxElements); + Array verticesTemp2(mMemoryAllocator, nbMaxElements); // Get all the vertices of the incident face (in the reference local-space) for (uint32 i=0; i < nbIncidentFaceVertices; i++) { const Vector3 faceVertexIncidentSpace = incidentPolyhedron->getVertexPosition(incidentFace.faceVertices[i]); - polygonVertices.add(incidentToReferenceTransform * faceVertexIncidentSpace); + verticesTemp1.add(incidentToReferenceTransform * faceVertexIncidentSpace); } - // Get the reference face clipping planes + // For each edge of the reference we use it to clip the incident face polygon using Sutherland-Hodgman algorithm uint32 currentEdgeIndex = referenceFace.edgeIndex; uint32 firstEdgeIndex = currentEdgeIndex; + Vector3 planeNormal; + Vector3 planePoint; + bool areVertices1Input = false; + uint32 nbOutputVertices; do { + // Switch the input/output arrays of vertices + areVertices1Input = !areVertices1Input; + // Get the adjacent edge const HalfEdgeStructure::Edge& edge = referencePolyhedron->getHalfEdge(currentEdgeIndex); @@ -955,30 +962,42 @@ bool SATAlgorithm::computePolyhedronVsPolyhedronFaceContactPoints(bool isMinPene // The clipping plane is perpendicular to the edge direction and the reference face normal Vector3 clipPlaneNormal = axisReferenceSpace.cross(edgeDirection); - planesNormals.add(clipPlaneNormal); - planesPoints.add(edgeV1); + planeNormal = clipPlaneNormal; + planePoint = edgeV1; + + assert(areVertices1Input && verticesTemp1.size() > 0 || !areVertices1Input); + assert(!areVertices1Input && verticesTemp2.size() > 0 || areVertices1Input); + + // Clip the incident face with one adjacent plane (corresponding to one edge) of the reference face + clipPolygonWithPlane(areVertices1Input ? verticesTemp1 : verticesTemp2, planePoint, planeNormal, areVertices1Input ? verticesTemp2 : verticesTemp1); // Go to the next adjacent edge of the reference face currentEdgeIndex = edge.nextEdgeIndex; - } while (currentEdgeIndex != firstEdgeIndex); + // Clear the input array of vertices before the next loop + if (areVertices1Input) { + verticesTemp1.clear(); + nbOutputVertices = verticesTemp2.size(); + } + else { + verticesTemp2.clear(); + nbOutputVertices = verticesTemp1.size(); + } - assert(planesNormals.size() > 0); - assert(planesNormals.size() == planesPoints.size()); + } while (currentEdgeIndex != firstEdgeIndex && nbOutputVertices > 0); - // Clip the reference faces with the adjacent planes of the reference face - Array clipPolygonVertices(mMemoryAllocator); - clipPolygonWithPlanes(polygonVertices, planesPoints, planesNormals, clipPolygonVertices, mMemoryAllocator); + // Reference to the output clipped polygon vertices + Array& clippedPolygonVertices = areVertices1Input ? verticesTemp2 : verticesTemp1; // We only keep the clipped points that are below the reference face const Vector3 referenceFaceVertex = referencePolyhedron->getVertexPosition(referencePolyhedron->getHalfEdge(firstEdgeIndex).vertexIndex); bool contactPointsFound = false; - const uint32 nbClipPolygonVertices = clipPolygonVertices.size(); + const uint32 nbClipPolygonVertices = clippedPolygonVertices.size(); for (uint32 i=0; i < nbClipPolygonVertices; i++) { // Compute the penetration depth of this contact point (can be different from the minPenetration depth which is // the maximal penetration depth of any contact point for this separating axis - decimal penetrationDepth = (referenceFaceVertex - clipPolygonVertices[i]).dot(axisReferenceSpace); + decimal penetrationDepth = (referenceFaceVertex - clippedPolygonVertices[i]).dot(axisReferenceSpace); // If the clip point is below the reference face if (penetrationDepth > decimal(0.0)) { @@ -991,10 +1010,10 @@ bool SATAlgorithm::computePolyhedronVsPolyhedronFaceContactPoints(bool isMinPene Vector3 outWorldNormal = normalWorld; // Convert the clip incident polyhedron vertex into the incident polyhedron local-space - Vector3 contactPointIncidentPolyhedron = referenceToIncidentTransform * clipPolygonVertices[i]; + Vector3 contactPointIncidentPolyhedron = referenceToIncidentTransform * clippedPolygonVertices[i]; // Project the contact point onto the reference face - Vector3 contactPointReferencePolyhedron = projectPointOntoPlane(clipPolygonVertices[i], axisReferenceSpace, referenceFaceVertex); + Vector3 contactPointReferencePolyhedron = projectPointOntoPlane(clippedPolygonVertices[i], axisReferenceSpace, referenceFaceVertex); // Compute smooth triangle mesh contact if one of the two collision shapes is a triangle TriangleShape::computeSmoothTriangleMeshContact(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape1, narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].collisionShape2, From 85103f7027eda6d183203f834b8d4c59959db8e5 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Fri, 2 Oct 2020 21:26:14 +0200 Subject: [PATCH 43/74] Small optimizations --- .../collision/shapes/BoxShape.h | 18 +++++++-------- .../collision/shapes/ConvexPolyhedronShape.h | 23 +++++++++++++++++++ .../shapes/ConvexPolyhedronShape.cpp | 23 ------------------- 3 files changed, 31 insertions(+), 33 deletions(-) diff --git a/include/reactphysics3d/collision/shapes/BoxShape.h b/include/reactphysics3d/collision/shapes/BoxShape.h index c54def68..bbf08d0e 100644 --- a/include/reactphysics3d/collision/shapes/BoxShape.h +++ b/include/reactphysics3d/collision/shapes/BoxShape.h @@ -217,17 +217,15 @@ RP3D_FORCE_INLINE HalfEdgeStructure::Vertex BoxShape::getVertex(uint vertexIndex RP3D_FORCE_INLINE Vector3 BoxShape::getVertexPosition(uint vertexIndex) const { assert(vertexIndex < getNbVertices()); - Vector3 extent = getHalfExtents(); - switch(vertexIndex) { - case 0: return Vector3(-extent.x, -extent.y, extent.z); - case 1: return Vector3(extent.x, -extent.y, extent.z); - case 2: return Vector3(extent.x, extent.y, extent.z); - case 3: return Vector3(-extent.x, extent.y, extent.z); - case 4: return Vector3(-extent.x, -extent.y, -extent.z); - case 5: return Vector3(extent.x, -extent.y, -extent.z); - case 6: return Vector3(extent.x, extent.y, -extent.z); - case 7: return Vector3(-extent.x, extent.y, -extent.z); + case 0: return Vector3(-mHalfExtents.x, -mHalfExtents.y, mHalfExtents.z); + case 1: return Vector3(mHalfExtents.x, -mHalfExtents.y, mHalfExtents.z); + case 2: return Vector3(mHalfExtents.x, mHalfExtents.y, mHalfExtents.z); + case 3: return Vector3(-mHalfExtents.x, mHalfExtents.y, mHalfExtents.z); + case 4: return Vector3(-mHalfExtents.x, -mHalfExtents.y, -mHalfExtents.z); + case 5: return Vector3(mHalfExtents.x, -mHalfExtents.y, -mHalfExtents.z); + case 6: return Vector3(mHalfExtents.x, mHalfExtents.y, -mHalfExtents.z); + case 7: return Vector3(-mHalfExtents.x, mHalfExtents.y, -mHalfExtents.z); } assert(false); diff --git a/include/reactphysics3d/collision/shapes/ConvexPolyhedronShape.h b/include/reactphysics3d/collision/shapes/ConvexPolyhedronShape.h index d723d28a..68dce6be 100644 --- a/include/reactphysics3d/collision/shapes/ConvexPolyhedronShape.h +++ b/include/reactphysics3d/collision/shapes/ConvexPolyhedronShape.h @@ -99,6 +99,29 @@ RP3D_FORCE_INLINE bool ConvexPolyhedronShape::isPolyhedron() const { } +// Find and return the index of the polyhedron face with the most anti-parallel face +// normal given a direction vector. This is used to find the incident face on +// a polyhedron of a given reference face of another polyhedron +RP3D_FORCE_INLINE uint ConvexPolyhedronShape::findMostAntiParallelFace(const Vector3& direction) const { + + decimal minDotProduct = DECIMAL_LARGEST; + uint mostAntiParallelFace = 0; + + // For each face of the polyhedron + const uint32 nbFaces = getNbFaces(); + for (uint32 i=0; i < nbFaces; i++) { + + // Get the face normal + const decimal dotProduct = getFaceNormal(i).dot(direction); + if (dotProduct < minDotProduct) { + minDotProduct = dotProduct; + mostAntiParallelFace = i; + } + } + + return mostAntiParallelFace; +} + } #endif diff --git a/src/collision/shapes/ConvexPolyhedronShape.cpp b/src/collision/shapes/ConvexPolyhedronShape.cpp index 9ec5f1a8..28c81c20 100644 --- a/src/collision/shapes/ConvexPolyhedronShape.cpp +++ b/src/collision/shapes/ConvexPolyhedronShape.cpp @@ -35,26 +35,3 @@ ConvexPolyhedronShape::ConvexPolyhedronShape(CollisionShapeName name, MemoryAllo : ConvexShape(name, CollisionShapeType::CONVEX_POLYHEDRON, allocator) { } - -// Find and return the index of the polyhedron face with the most anti-parallel face -// normal given a direction vector. This is used to find the incident face on -// a polyhedron of a given reference face of another polyhedron -uint ConvexPolyhedronShape::findMostAntiParallelFace(const Vector3& direction) const { - - decimal minDotProduct = DECIMAL_LARGEST; - uint mostAntiParallelFace = 0; - - // For each face of the polyhedron - uint32 nbFaces = getNbFaces(); - for (uint32 i=0; i < nbFaces; i++) { - - // Get the face normal - const decimal dotProduct = getFaceNormal(i).dot(direction); - if (dotProduct < minDotProduct) { - minDotProduct = dotProduct; - mostAntiParallelFace = i; - } - } - - return mostAntiParallelFace; -} From 7071213617992740945ec3826d6fc24273335573 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Sun, 4 Oct 2020 16:08:12 +0200 Subject: [PATCH 44/74] Optimizations and small modifications --- .../collision/ContactPointInfo.h | 7 ++-- include/reactphysics3d/containers/Array.h | 2 +- .../narrowphase/SAT/SATAlgorithm.cpp | 37 +++++++++---------- src/systems/CollisionDetectionSystem.cpp | 12 ++++-- 4 files changed, 31 insertions(+), 27 deletions(-) diff --git a/include/reactphysics3d/collision/ContactPointInfo.h b/include/reactphysics3d/collision/ContactPointInfo.h index b75b9b97..0b296b5b 100644 --- a/include/reactphysics3d/collision/ContactPointInfo.h +++ b/include/reactphysics3d/collision/ContactPointInfo.h @@ -52,14 +52,15 @@ struct ContactPointInfo { /// Normalized normal vector of the collision contact in world space Vector3 normal; - /// Penetration depth of the contact - decimal penetrationDepth; - /// Contact point of body 1 in local space of body 1 Vector3 localPoint1; /// Contact point of body 2 in local space of body 2 Vector3 localPoint2; + + /// Penetration depth of the contact + decimal penetrationDepth; + }; } diff --git a/include/reactphysics3d/containers/Array.h b/include/reactphysics3d/containers/Array.h index f79ac094..74c9a507 100755 --- a/include/reactphysics3d/containers/Array.h +++ b/include/reactphysics3d/containers/Array.h @@ -292,7 +292,7 @@ class Array { reserve(mCapacity == 0 ? 1 : mCapacity * 2); } - // Use the copy-constructor to construct the element + // Use the constructor to construct the element new (reinterpret_cast(mBuffer + mSize)) T(element); mSize++; diff --git a/src/collision/narrowphase/SAT/SATAlgorithm.cpp b/src/collision/narrowphase/SAT/SATAlgorithm.cpp index bfc32951..835ff13a 100644 --- a/src/collision/narrowphase/SAT/SATAlgorithm.cpp +++ b/src/collision/narrowphase/SAT/SATAlgorithm.cpp @@ -520,7 +520,7 @@ bool SATAlgorithm::testCollisionConvexPolyhedronVsConvexPolyhedron(NarrowPhaseIn // was a face normal of polyhedron 1 if (lastFrameCollisionInfo->satIsAxisFacePolyhedron1) { - decimal penetrationDepth = testSingleFaceDirectionPolyhedronVsPolyhedron(polyhedron1, polyhedron2, polyhedron1ToPolyhedron2, + const decimal penetrationDepth = testSingleFaceDirectionPolyhedronVsPolyhedron(polyhedron1, polyhedron2, polyhedron1ToPolyhedron2, lastFrameCollisionInfo->satMinAxisFaceIndex); // If the previous axis was a separating axis and is still a separating axis in this frame @@ -912,7 +912,7 @@ bool SATAlgorithm::computePolyhedronVsPolyhedronFaceContactPoints(bool isMinPene const Vector3 axisIncidentSpace = referenceToIncidentTransform.getOrientation() * axisReferenceSpace; // Compute the world normal - Vector3 normalWorld = isMinPenetrationFaceNormalPolyhedron1 ? narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape1ToWorldTransform.getOrientation() * axisReferenceSpace : + const Vector3 normalWorld = isMinPenetrationFaceNormalPolyhedron1 ? narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape1ToWorldTransform.getOrientation() * axisReferenceSpace : -(narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].shape2ToWorldTransform.getOrientation() * axisReferenceSpace); // Get the reference face @@ -936,43 +936,42 @@ bool SATAlgorithm::computePolyhedronVsPolyhedronFaceContactPoints(bool isMinPene } // For each edge of the reference we use it to clip the incident face polygon using Sutherland-Hodgman algorithm - uint32 currentEdgeIndex = referenceFace.edgeIndex; - uint32 firstEdgeIndex = currentEdgeIndex; - Vector3 planeNormal; - Vector3 planePoint; + uint32 firstEdgeIndex = referenceFace.edgeIndex; bool areVertices1Input = false; uint32 nbOutputVertices; + uint currentEdgeIndex; + + // Get the adjacent edge + const HalfEdgeStructure::Edge* currentEdge = &(referencePolyhedron->getHalfEdge(firstEdgeIndex)); + Vector3 edgeV1 = referencePolyhedron->getVertexPosition(currentEdge->vertexIndex); + do { // Switch the input/output arrays of vertices areVertices1Input = !areVertices1Input; // Get the adjacent edge - const HalfEdgeStructure::Edge& edge = referencePolyhedron->getHalfEdge(currentEdgeIndex); - - // Get the twin edge - const HalfEdgeStructure::Edge& twinEdge = referencePolyhedron->getHalfEdge(edge.twinEdgeIndex); + const HalfEdgeStructure::Edge* nextEdge = &(referencePolyhedron->getHalfEdge(currentEdge->nextEdgeIndex)); // Compute the edge vertices and edge direction - Vector3 edgeV1 = referencePolyhedron->getVertexPosition(edge.vertexIndex); - Vector3 edgeV2 = referencePolyhedron->getVertexPosition(twinEdge.vertexIndex); - Vector3 edgeDirection = edgeV2 - edgeV1; + const Vector3 edgeV2 = referencePolyhedron->getVertexPosition(nextEdge->vertexIndex); + const Vector3 edgeDirection = edgeV2 - edgeV1; // Compute the normal of the clipping plane for this edge // The clipping plane is perpendicular to the edge direction and the reference face normal - Vector3 clipPlaneNormal = axisReferenceSpace.cross(edgeDirection); - - planeNormal = clipPlaneNormal; - planePoint = edgeV1; + const Vector3 planeNormal = axisReferenceSpace.cross(edgeDirection); assert(areVertices1Input && verticesTemp1.size() > 0 || !areVertices1Input); assert(!areVertices1Input && verticesTemp2.size() > 0 || areVertices1Input); // Clip the incident face with one adjacent plane (corresponding to one edge) of the reference face - clipPolygonWithPlane(areVertices1Input ? verticesTemp1 : verticesTemp2, planePoint, planeNormal, areVertices1Input ? verticesTemp2 : verticesTemp1); + clipPolygonWithPlane(areVertices1Input ? verticesTemp1 : verticesTemp2, edgeV1, planeNormal, areVertices1Input ? verticesTemp2 : verticesTemp1); + + currentEdgeIndex = currentEdge->nextEdgeIndex; // Go to the next adjacent edge of the reference face - currentEdgeIndex = edge.nextEdgeIndex; + currentEdge = nextEdge; + edgeV1 = edgeV2; // Clear the input array of vertices before the next loop if (areVertices1Input) { diff --git a/src/systems/CollisionDetectionSystem.cpp b/src/systems/CollisionDetectionSystem.cpp index 00e4d78c..b09c338a 100644 --- a/src/systems/CollisionDetectionSystem.cpp +++ b/src/systems/CollisionDetectionSystem.cpp @@ -832,7 +832,7 @@ void CollisionDetectionSystem::createContacts() { // Process the contact pairs in the order defined by the islands such that the contact manifolds and // contact points of a given island are packed together in the array of manifolds and contact points - uint32 nbContactPairsToProcess = mWorld->mProcessContactPairsOrderIslands.size(); + const uint32 nbContactPairsToProcess = mWorld->mProcessContactPairsOrderIslands.size(); for (uint p=0; p < nbContactPairsToProcess; p++) { uint32 contactPairIndex = mWorld->mProcessContactPairsOrderIslands[p]; @@ -988,6 +988,8 @@ void CollisionDetectionSystem::createSnapshotContacts(Array& contac // Initialize the current contacts with the contacts from the previous frame (for warmstarting) void CollisionDetectionSystem::initContactsWithPreviousOnes() { + const decimal persistentContactDistThresholdSqr = mWorld->mConfig.persistentContactDistanceThreshold * mWorld->mConfig.persistentContactDistanceThreshold; + // For each contact pair of the current frame const uint32 nbCurrentContactPairs = mCurrentContactPairs->size(); for (uint32 i=0; i < nbCurrentContactPairs; i++) { @@ -1052,16 +1054,18 @@ void CollisionDetectionSystem::initContactsWithPreviousOnes() { assert(c < mCurrentContactPoints->size()); ContactPoint& currentContactPoint = (*mCurrentContactPoints)[c]; + const Vector3& currentContactPointLocalShape1 = currentContactPoint.getLocalPointOnShape1(); + // Find a similar contact point among the contact points from the previous frame (for warmstarting) const uint previousContactPointsIndex = previousContactPair.contactPointsIndex; const uint previousNbContactPoints = previousContactPair.nbToTalContactPoints; for (uint p=previousContactPointsIndex; p < previousContactPointsIndex + previousNbContactPoints; p++) { - ContactPoint& previousContactPoint = (*mPreviousContactPoints)[p]; + const ContactPoint& previousContactPoint = (*mPreviousContactPoints)[p]; // If the previous contact point is very close to th current one - const decimal distSquare = (currentContactPoint.getLocalPointOnShape1() - previousContactPoint.getLocalPointOnShape1()).lengthSquare(); - if (distSquare <= mWorld->mConfig.persistentContactDistanceThreshold * mWorld->mConfig.persistentContactDistanceThreshold) { + const decimal distSquare = (currentContactPointLocalShape1 - previousContactPoint.getLocalPointOnShape1()).lengthSquare(); + if (distSquare <= persistentContactDistThresholdSqr) { // Transfer data from the previous contact point to the current one currentContactPoint.setPenetrationImpulse(previousContactPoint.getPenetrationImpulse()); From 44e1b12aaf257479611c2560281dcd529d4775f7 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Sat, 10 Oct 2020 00:10:05 +0200 Subject: [PATCH 45/74] =?UTF-8?q?Fix=20robustness=20issue=20with=20SphereS?= =?UTF-8?q?hape=20vs=C2=A0SphereShape=20collision=20detection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../narrowphase/SphereVsSphereAlgorithm.cpp | 62 ++++++++++--------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/src/collision/narrowphase/SphereVsSphereAlgorithm.cpp b/src/collision/narrowphase/SphereVsSphereAlgorithm.cpp index 78f817db..1d488ad5 100755 --- a/src/collision/narrowphase/SphereVsSphereAlgorithm.cpp +++ b/src/collision/narrowphase/SphereVsSphereAlgorithm.cpp @@ -56,50 +56,56 @@ bool SphereVsSphereAlgorithm::testCollision(NarrowPhaseInfoBatch& narrowPhaseInf const decimal sphere2Radius = sphereShape2->getRadius(); // Compute the sum of the radius - decimal sumRadiuses = sphere1Radius + sphere2Radius; + const decimal sumRadiuses = sphere1Radius + sphere2Radius; // Compute the product of the sum of the radius - decimal sumRadiusesProducts = sumRadiuses * sumRadiuses; + const decimal sumRadiusesProducts = sumRadiuses * sumRadiuses; // If the sphere collision shapes intersect if (squaredDistanceBetweenCenters < sumRadiusesProducts) { - // If we need to report contacts - if (narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].reportContacts) { + const decimal penetrationDepth = sumRadiuses - std::sqrt(squaredDistanceBetweenCenters); - const Transform transform1Inverse = transform1.getInverse(); - const Transform transform2Inverse = transform2.getInverse(); + // Make sure the penetration depth is not zero (even if the previous condition test was true the penetration depth can still be + // zero because of precision issue of the computation at the previous line) + if (penetrationDepth > 0) { - decimal penetrationDepth = sumRadiuses - std::sqrt(squaredDistanceBetweenCenters); - Vector3 intersectionOnBody1; - Vector3 intersectionOnBody2; - Vector3 normal; + // If we need to report contacts + if (narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].reportContacts) { - // If the two sphere centers are not at the same position - if (squaredDistanceBetweenCenters > MACHINE_EPSILON) { + const Transform transform1Inverse = transform1.getInverse(); + const Transform transform2Inverse = transform2.getInverse(); - Vector3 centerSphere2InBody1LocalSpace = transform1Inverse * transform2.getPosition(); - Vector3 centerSphere1InBody2LocalSpace = transform2Inverse * transform1.getPosition(); + Vector3 intersectionOnBody1; + Vector3 intersectionOnBody2; + Vector3 normal; - intersectionOnBody1 = sphere1Radius * centerSphere2InBody1LocalSpace.getUnit(); - intersectionOnBody2 = sphere2Radius * centerSphere1InBody2LocalSpace.getUnit(); - normal = vectorBetweenCenters.getUnit(); - } - else { // If the sphere centers are at the same position (degenerate case) + // If the two sphere centers are not at the same position + if (squaredDistanceBetweenCenters > MACHINE_EPSILON) { - // Take any contact normal direction - normal.setAllValues(0, 1, 0); + const Vector3 centerSphere2InBody1LocalSpace = transform1Inverse * transform2.getPosition(); + const Vector3 centerSphere1InBody2LocalSpace = transform2Inverse * transform1.getPosition(); - intersectionOnBody1 = sphere1Radius * (transform1Inverse.getOrientation() * normal); - intersectionOnBody2 = sphere2Radius * (transform2Inverse.getOrientation() * normal); + intersectionOnBody1 = sphere1Radius * centerSphere2InBody1LocalSpace.getUnit(); + intersectionOnBody2 = sphere2Radius * centerSphere1InBody2LocalSpace.getUnit(); + normal = vectorBetweenCenters.getUnit(); + } + else { // If the sphere centers are at the same position (degenerate case) + + // Take any contact normal direction + normal.setAllValues(0, 1, 0); + + intersectionOnBody1 = sphere1Radius * (transform1Inverse.getOrientation() * normal); + intersectionOnBody2 = sphere2Radius * (transform2Inverse.getOrientation() * normal); + } + + // Create the contact info object + narrowPhaseInfoBatch.addContactPoint(batchIndex, normal, penetrationDepth, intersectionOnBody1, intersectionOnBody2); } - // Create the contact info object - narrowPhaseInfoBatch.addContactPoint(batchIndex, normal, penetrationDepth, intersectionOnBody1, intersectionOnBody2); + narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].isColliding = true; + isCollisionFound = true; } - - narrowPhaseInfoBatch.narrowPhaseInfos[batchIndex].isColliding = true; - isCollisionFound = true; } } From 881b0fafe566d627415cc1cff2a1c9bcffd79e40 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Sun, 11 Oct 2020 10:55:32 +0200 Subject: [PATCH 46/74] Edit user manual --- .../UserManual/ReactPhysics3D-UserManual.pdf | Bin 1064341 -> 1065818 bytes .../UserManual/ReactPhysics3D-UserManual.tex | 7 +++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/documentation/UserManual/ReactPhysics3D-UserManual.pdf b/documentation/UserManual/ReactPhysics3D-UserManual.pdf index 2b92b676e2f815dd3798d78a6b1fa9c433641572..fdff5ec65412d2d7b40ac6081e27fcdeaca5a9ba 100644 GIT binary patch delta 107000 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�P+e;w)>iA)#%3o}Det_f5HY%RdYvph=vW2Rxv&cPu!x8+A-u5-Lnj8*0@ zzabbdQkF4Zm}sPdUW$mNch)8|T8eF#m3ZT4LBsGxVR&a4_A&CFS~qDFVMD*ffhAV~ z5C=H3V1mNCv(&CNGsW%T3b&RQU!V0Bf57hyTP&i~fE1)0=MRUUuTjN&<*_6&ozJwD z%)_RJ!mSY!6%44eQICVV?{T5iw*IR|19McpGnod6T$hLGf=0r)gXCP*@Gi~tf< zq6Ym)`ob9gb$+Ek%#kmIm?`qkiglg|V(6TmRW(QFsc3DPzn8=#-b%LssIpeFch5VvCzBv3Uadnmu!;|_R)c44TQ?p9NX>W zb!?|d<7L)sB9b}l6o)?>`S~G8kYd7v9)M@v@Mt5B z6Q4C;Zqfq>mK4O#wW@@HbVWI^yKiQD@Z8@!p|nrt)`Xd@7F^Cye)>bZ!AxSY3Qck< zTwYt*+bAXftjwZtREpc=P%G^KLA`%C07*wW$4*lU@ArfE3RXaqO&8`3gosyNXoST- zdq)Zu{t=3dL6A2Wk8F^)5(?l&=RlnYV~vnTZsb~gza1JGt;OJ}Prk6G2vAOH^Zmnc zWhho|+6L*j)Z*>M`@?_7edn={O|peQVA)UP3}FHL4FpQd?(b;T$xd=aSmb~_>qN&8 zdTPlXNqtO{G#HlcbU0y)CzHmZm;H#SqMaz~hIbuI>ytVl zn;D{1@Gupz6kZ+ZrCGH1dvC81{zaZg;0E2l!^TDjEw2#ee!&ah`!tM<<~|#SmgG2g zjN}0M_w}XGP>dH2CYKyMU*HXRPVG@kh^cPHZVJxo(%*f@e+t&OuS-fkU^9=AWO`ZT>qgZMY~5oLw`7`QT2)gI-+ zM6DBO*h&|H_pkOjTP}9;1GH%>7)Xmhm@Cw>5d+s+BS-l+B=TPUq&HnO=29Ie2g_Z& zks2J^T7R@)#Z&?&`{=!@WSE0~umFM`ecnn6M6{-g;ujKc<})A`ESHW?bXD zn84_VtFnQ7;_xolDTAawb7wCLzUFpqPk3R5fFw!ZtyQMS9TSKHM&Wx?kh!%?Y~@;?+5IzU|wkYG+m>XT$AZaUy*gG7>>j zeGl`cRBsJ9oGj8BZC-H03@a8Kf5yYWyr;+!C;;) zg0bUvkb;RuE8~WXcioE9Xx^P@HBSwU%oe!n>Q7$^=h9&g?_Ce4)>Da}J~*Ok2RI7_hzNz`t(iCB(x|+X7IxBFE2gxz zG9Qe-GLkMirf6}wtgd`vHK^5D6#Y!8gZF$RR-Jr?PDHNE`~sg{-9-6!Pe}{T!p!qO zuuWUXWxWmaA0wetJ{<`v3-8M~_Oej2<|IC;{^QL*A>eSWTj;Ic%=?5VeKd(p48A4Z)p|M`a=1V^kTcB0HX+?j#L#h{t z`P2_M1U1W#&R@Cfbe-EZZLxAUFU2(ROXR(QQ=Im!vVj6>Xo8jDRb_-@PUZ{&)BL^E z#r?PD)8KHpu)XdR%DCMe0OAH$jb4MltPNJr9n_)Vji^Zxo1n(*y2(xsM={UKQJ~sN zH;qiPmumFXZK#6K!M{ND(ZIwrX|_J(dBNGEFn%`j(V+G9-l$RHBh;f(GcH8(d0m*_ zP=)T*!+S(`D(q3*8~E>~-~D1wv)AO{Ha5i(_Bs1K99K6+BvY#A0Z8*5CilSY^dXn~ zL(01qs?7phiJC(z)OWT6@m2mZ#e??{wFSeY_$yO3w+hxTM_7<~eVLJ{F*7WFe*-Ma zqa9K|g|-JZsWW@IOk-B$bM|~w({)M6YxJB3I@6CA3ki`uv`EYKPAOFWT;wW-Cf)$c zzN{Im_wqG<8w>);6tGV|ROO)($JYQR;ud1C|EwM!qg-S&sV^mSWi!3yLxy_BTr#%8 zhQK3i6&|+c(gvz6Hadt)I9fUK2gSle9On|hdbPJhvNkz*xXh^<;GHzAH1z<<8EsEy@n2e}v{K1Ie zj{9@@gSj9h*o$F4Fj6fsohI;OGOMCqoYM~LoBdP*SoLQlc@Z2R1#!T`;INofa zT`b*roi-8$Dez(}8JVfc#7%LBeU@Imzitg;O}s`XS})mKmFe0Q-uEGwNwvBX_lXTU z1Tb`6i6Ac54}hqX8L{e;(&TTV!-g;DckGt4DVff|Orqa~ZFim)Gu6yvXy`alVi{96 zkUCEVo#e-lT2(M7GyKI3=(IgV@II=VH@7{$ibV5Fx6n=ghSjz0$p5W^m(d&v@YIlL zdnSNTYFe7~+e{{}`6bhUR*uZ4u0j*-d0iy^u|l_Y6;K|A)lB!(u2;^boa`4&y5kHI zYjdWM}A+;2*?#bYdE!va(94!7gt7CTFUrKvc-Fes&{fhp0JM~nX z%T}M!7QOZ{^B-GOoH=SvSN;nO1Wu+>#X|RwF6+g^Xis`I zx*3izSd3g0m7O-!6L2-q>m_a4GKm7PHwb;k*EI;!2za=T$O$jJt9i=vJAV+uRg zZ8m3(WherZE~tb4tP0pWudMGuqySyP({1SL-(RJ2S*}Sq`dyCFGRPKP1rzI{RzqyN z0RZR)&X6Au;9G>vZXVVYtP~^>6VOxJaMyEck=#G?ws0#>dD;vRQ*`&NFgIWwQk5c* z)54|>tx>SK7kg`?K_+(KesP9+3w{OtV)fs%!9^oVuj>@t(eav@5!(K|pxU|Y96+=? zJl7xKDLLjYA>rAb69cUElmh1NBtSJWCIKrkN5^hBXMePaL&{;YNO>?WeS31m1TdfJ zAP6vbiM+?STM&I$+sHw4D*HVhDRl*CKDdSNzKj_X3doK!c<06RB(hPK5jLg7OM^r$ z9}pQvWs({y#iO729gBE+>+HzFXvo?=Ue8-Gmq>i`l&6qnUZY5@f@76v#q7`e$pNXH zB)ELHwG)p%Fq33)8hlsc+T%t3b_GK>M)za~i zxS)<3OJKqh>9`eo*}MY200&ut)F?*t9cR_}Y42PK=0iN0M9cY-)9;4u_Hcg1_0FY7 z>y=!snqNHwWFnsPvI)Y{C(;4Uvj8oJmL5^>5a@()_~6-*EvmcA+Rr(SFEM|_?osyB zoA>+5>qBOUUAz#-QO}|Xo98`3jjfrw{?BqW$X6Ccr=17m*kFu0j6;E735vgcA?_g& zDP#c?TRcazN|+LKyn47|j891>!hKrMSmTfuERdS5L%~xd^u%l{Ir;8YNPyg_diQWX z)7u3eLVg{a{+_((W(CaV<^#(6W$g#B48CB^zS0F?&K=7R3xUSY6mp+hfDkC;II+#9 z+dHIbFvQxWUC)I1uS36#5vAbXAl;N5L>z4pX;~mUnDM$g#&GkU>i5s(a5#h&05o`6 z2=zjUOaX){$fN41L%AMmF+ecgDPc#&&>$fos->BNn=XIm*2;I-kb_R?M#8xHz}3Di zXY$yy=&^{OPw87;BWKI>pS9I0B=NF9Z)rUAkbHRMHc-ki-?cbJ{97wALA&3jw2pb* z`B+)(bH_Qe2CmEYP`T%Mzw167!iQFrvI107_jg9KHW^xSuou_W$)X06O|r5E`La12Z@>nYw1B?Xe%5A4UGMY2^LixZxq=DBD+9-!hyrkr#w@jOIq{b6UCv?O+kUl$Y-v~O3%gicd+WPlQ&BwsSjx`V zMMHlSp>5&r#)zVcP&y5Kv?=Z7O;0~0u12aU1ae6S0?Hu=39JDFUqu0&SNGV98l@9r zLMg$ba4NsweInPZs#B{gGbR*zy|wvwxpJix=-84Ges##ntODwJu{jiuTnRW{gL0zr zQfGDWPq1eJ-piq*Kez|?Y!f?bPvKTW+AlyT@7ApioOW+xljVoAgDrG9aHD64K zETWHy%g^ET)n7lE&2B?sw)=eSfPf5wi$T_=u;{U6`YT-gqRHj^NP5L=esFXsHvW3@ zS;9}U)}h;U76nWNt$j3;J$^}v@yYR01uT{bj9)r#5M$?|-m-%p=R|!`x>SH{lJQF7 z=cs({CD<5yew@nycW(~uN;q1Lp6crIW-mDDp2B-`GUA1igBd_5xOV9q2srqtuVTuF~ruJEOk~ z45qlnf3`1d_oom>dAs1W#7K5~v7VkZtiKH`4f!8NCdE^RFCt*I{cJZpMyy_QU7LRh zPZV3HjgJviLW9I3cmjiI+H<7>$(}x@`MfXY$M$E+Z-ioD%4@zdcLQ;OP!owjWS5eN zyE{kb!r~nX*o5@88aHaL~_s`;iTY7q^tD{N!YNS2HD&Ls@yhv}`oAY>G; zXtfE>9Pja%T1xu9hS$>|BW=dbFU;skP#8m;flW zp*r6>^5-2h6mBPTFvftpC$HgnJL!eJU8!jia`z___4-rwc!>mYQpkiMk*P0P%l$7* zpIV~tf2B(WvuR_#8hYz5p3+aoR z1=)7=D5P<Re*h<=CZO@nYaxGRWe@blZi#&Qn~c+H#m z(o6r^KLfTejm+W-XgX!96O<>i4J4D#u96U*n$}Pfq3x`^7HQ|(5Z1j!3h!0p zymeNZ5^NRKzZ4Wcz8Nnjp=Rf59UjwPEEaB0Idu`yt~Q$oYf~R2ye+921#1i8Q^4j3BbRQHuUtvUXt!uRk0$^LJ!ibL$c*Ru(82c; z%AmYC^LX#!l#GB75PE{+W#Y?mfkRueQpAZ&=hxctYOIe{L)4{BLiquDTVi2x=0QCX z<%m~ep%^oGZ~SsX=BxbhcN>uMe0>;uVC;?1Jh3#~?m+YR4twG5;N|u9eDl_~aqQls zmuVZ|d1v`So7I7qbp4x#RXx7=g5$oMCm3ZLW&-NVu~}OTaC~A8?%HT?brmq|>iqIp zAoO)>^zg^nyGhWem;a1+{_N!F0SVq%ao+ zwb859hcO{aF!40Y)u1=#!*`FV=9OonkK}+gSgkJZS-FmF5CD>CePq*|auZ#bS)pY< zjSDSLYzf)~fI#nPPMUmX&J8OYU)MLYW*uq6)X_XD`sK}O{p9_&wH4rfuQ#;N`<5QQ z);fkn@ciNV?ZhnX-kEFO;OQ5EDC4TTNj*Vjv=LjKzatLV8y(&W3HL$S#z-G``cO$Q z=bjvnEbd@~)4sCBbIxqh`QL4(@y&Kine^S*&0L`u0Lte+_;X|QRJEkgHBulV<+o%% z4PTyg-0_4l8a&8^#F&Ea+sxPs>7zx-xwV2{ zOe#)8<>Dqz-)GRGk*6!yN0Vv~zNT?4y0V>8F0-8^p-ndsuhn1Zx%D|$!9Qh!@T!k# zc*UUC0W@ngL8d^h?x*PeLiauxK-ODDedbkJj@*DFYZ&3UmzbL zzf&HOu|PTg3n%;Mfb@UeGz?;aPNINqMbdj|Nt0}vNU~;7xP9;TGsr(w-3)P1sd=+& zx;oViFAO~EACK8WNeo^Gn-AmP_ARA8x!JOCkuGL+yz-xR>1^LfqA}(d0d23|lmW!v z1F{Pws5vo%eNqnf?V|P=MN|(V2x?j0d}o__A9rR_PQ)h@SNVX}ka%Ll8o6i>#XC;! z)Oh%-Bcf~9yu^eEW;hH60&CoGUa2A;9Yx9kl7uk{+Y&<&g5lJg6M2;0bA&t&uRXKZ7;^oKw* zV$;aBI{|o>3n@T8hEsI|2W5An%M7OR@8 zvr2o&W`~+1=!E3CTfdr{kZ`{6iaElfM-$9%;(YOix4xy~0>#q7;oNM~~6fkh4| z+LslMV&TGHoTQvmG<1rlgf+_MuacI$mIC*K=WhY(-#blazt4Z&k(a0>O;XSy^B9UX z`SCMeF2?}QOADVR)`FCSu;mh2sJ!EX(a*yu==B>M#qCf-Ke;A-fo{Y{%d8zzl6=DQ zQSp9`hoYhQiQa;97RmVt(*u< zMu2u$t(kZ81WViyNxdNnBqde7ecz&^f6$UC_P>i~(j9O>W!QaOnkarI+?OFOluZ%w z<#NU2`iV@bE&q`(t}0@p`>2oFO}2*C&G+;@NW3^OAvGcod~p$m7bpW5JUU*UASR>M z>9cSD28+6YI)c;}?U={2Ma`wE9N?GNQo>p4KDYPYtl4b)bIUc$bbpB`v+$_YhwjgP zP0J$ZqCc;<+xN?hDHnGWlJ0GA@AtTYh^TzdIEC;;9cO!5blCf2h}+ncF2$jpqj>1{ zvE!rn1J@+iv{J~?Ucc-)-Oy0eu>3c=yjo0fN- z6dfVC!toS2`p;Pb>FpWQV@)NmQJI$sEc4`4dQ-|zcVrW0Go2-ODvU0XBd3g4uR(gO zA7gj+jBJn1-RszLT$r(pcK~l4)3&)_e67yabq=R9UuCw!j*nP9c8=Q0%e0qO^CI=mXA;M)@&;B9pXZ0~)QeON|97r7GGVRdQ9nEY9b1*f=2CCX|kUh>vhh?v+ zK|5sYKRmd_bqKAstDJ4aYYn032RHTNmPWYro|+OQ^u1N*7yb{65rD~cNbn^r35{SB zR@9Rkqo80O}MHO ziPNu!797faesO*Pk_B6=D5$rNITK1s8iYbw7~9odfuXgT>;xcZO)ETsaT%OPkIv>6 z(HiF4W3UF4Z3ZbugCazxGJ!d^`C3Q30W!{A~qJr)m9h8@+;vLxb>GK41i22C*dYKK>5*VtE@#CWTv zLHul(g#K;mV-{PzV7DQ%oo^jXhhG@$ z!1B!KZy2gfO*i3p0{b+r8^QS^yf^0}^smusMj_w7sK5Bnw-0b2zW&1M$^DhWPb}kZ ztNm5{6${r53%v>@75zeHT+d@!iDqmozLOvO_d&WpOuPlK2P$bsG{W(CS!Rj17KMcr z6X``@fCzA<9zM3u%&u-#U+uJjsr>7IA|8{E7B7?gx)l zBsM!TziSS7D+r`v9asY6Uo0kj=U0&UJ*XzGde<=_v4c()$p3nVFk#fhIGwi+0(%XI z#Kx-!X4Q5Sl?A4LEL`q-j75DbU$iUWOt!t)@}l)r(OZ} z&dSZ-)q19HkA?YB*hVXh?9(5A{I<9gR3^S8Wx1rl+_uaKt6zdf{FUum-b%XnL*qLD zExcA-awN|9RIHco=ekN3yM)udj%tONcx^N*SH+MSEP)EHe1zyAV`nni?Q#+%WPU~Z zy}VL@o$2mT=9-oK8_T#OYiSC_w#+!XSvtZeu2}%dTcSQxtOaK{kNi2mHF7gJ% zpjP)xu@WdA;p0yfmm8gxNU;{z^kQ5>5UPfVzuwTrDMzgyzOJ!a=Tku+kug&RC7@XT zXA3iIhtN7~hqyJHn#Tm(ss;R04D^Pf0}$9a?6uwa_=gH%vep@iM<$hFFve}3>n&WG zEmQ@HISDXe=+uWcQfg{s9BzA&eECiI;kmnZG&(lC zgglS$G|i9sdv4#W2-Bt85kqRswM;T`^%(oeZ890Xc=(z1I#kv4d4HN=wJLRP1>ln1 z%Hp9H9{tS=h3*LYeB7ChR+>v9Nx>xK+Q_%OIbaoR!~mgAoz(!t{PaY9vX6|DJX4}6 zhO30lhvBY#j52Ya%X2~PvSK#fe8P-1>JG8F`OZVL{dr^wxkvO=tT?1t{~07q zzc)Hp|IkCCtE1=h{$V1u)4{{w3eXF9TbLTOxUV!%ulr5gdak5EQ5ZbFmSZu%Q8?Ua7|LaJ>qB;^0~{QPkExU0yu zEeQC|@Q6yu&HC@QLrcrm6Q9uNgvB%@(#p`nMiD9htX??EWQ9mHej?xnz~}oUV5c-R z&Mq5eshu{$D?4v&avC1DE6P0fROlkq{=)w9dHrzd?~Id8#TkaH;s{c{HYv<_)9k`%9;4Kq?I@6<`U(><`GzjsOR7Fvb}C+Sk7g$ zG7cI1Mf<+FEXW1Gsfy`dzhJCKAnHJU8)B2Rxe1`f97w>EW8$Kh2Plz+AZ1xJluVd~ z9rsY3Y`7{mrf1jvNXw75T1&A)0lWUS@KZ^5p3fiN1ZUdnTOSN` zQTO~1=6gD3N})qtA5j&5UG^;w#9dD(cwlD`ZJGx#ts5X;1F>D9M;PEDV8{}jO}94-q6v=~+qse{_Kl0g1z(kXt$m)ygzQ!lYvb4FT7Wo$B6rkkcc9>50Co?}{G zVw|iB@+j3pTVWVsd$FV~p5me>OEeD$Q6`AWLK?^R7@#~pO;RSh-OAU#2VHq@j6Fpj zq|whaHIV1Ga6l<#93jWJGREm)VfuwMgeUJ=fhj0DCwf}PwnE_ zU`sE=irN&9a98b#^^nKqTheC@t3!~iGte`&m zlRPwYW`kyPax&8StY?6FYNUsM>YgE<1^{p60b01b;1F-o_=S84Cvm2{Mda$osxo>FciE0I)<+)e*@3mNqAn6{Baxpx^kmmU=k6UC>Nb%TgE7j@C1t$) z)D?najCxaGQ_yo0z;hiw2cD~W3+zR82Jl=}%0RjQ5HAZ}U@$8OyF2&0zK~+Ok&ytr zsGlJ1_gd!e#1G&fGSg$D=aOD6qI0?`&IDfJoWQfoXM5LN;v8p$pvRQ@?ITEwIzcN+ zU3R9p_vjKqtemNdmX#CRFW>_mN{exvwSwzz>z zKLoff^GI0_gW=wPLVQGLd2GC%>vjU(FR95pB8T?|gJRIGxWz4b-)8OXEd|4zSkDjd zQf+|$_OQY@$64xSRs4MZ+@HXc=Xjvtqo&iw^ZR_OXF|0>;;|3bvi}}+Nqp6QkhN>9 za1iN@fSsy#rQnRx^X6NA>^+Da3pZsRH0#Gn{bk_}y|nxt9}N6+kJhreB% zjOY#>^vx-7#^`tR6)iNZQ8l${5(haX%OUo)f7ZqLy2i;2vQ?Sg3O&vR61E+%K1m-Y zz>lBCz>lwwSh@|G)s$DHOPBzrD{eIA0~$-_H}ulas#-^MsB7vo)F7^2nHt}c#V`1d zXpb`ZmmXHX^Npp$8?BLJ@*cWfQ#U<)AD++U zSAH=Dhz1@ICQF!1TWd;5=tyPC6lN=89QYOYJ?5OES8OYNpf6FYeJKDQ!kzu4Jl3OI zTZGQMYVLNsbPj}R;i@I}G*;I=DjBw2Dj6jDiXNer+kuiwJ_AZ>ycsAd=YNt){7+IW zrKe6Mi(h`HUj>Sx-ALU=7+pXC^mmH!R__1>K>rLBK$0_10O4+vrOs2jPrZND2iGdY zx*jW(Y5`r=OLy6)PikEH5AoxFAy zhkSsp7!UCEfZBKIJO23)5iLAxitM*mH zr6Cl$ws6JEbO>ru)Mlf#IrK~0dwILgp`e7rRUeNyhN{ zYVaN?BFRb-$n@_VTan0=`?f02lo)(oaREr8qX!u}M99^_KUX!kf(i3z_^FCwxU@)- zWv{Ye4YF~H6+^hjbHZvcG06A*%{}pGZk{YOOO|l=;FdJhjUGa=!tLA@l5V zRnY3zTmaUen@J9uQcv02+-2pQ+LeF!u4r-#?O9gTf&PfGIElF&(~d*%M6YT^AQhmU zAyC@SI6TtBcn31rR)|uf;7(#E&)G*S@yiu=WY4TCk~X?3bYb25O0siQ1-U$wuY247 zRW3{&sf@*AF=`#c?Nu!QAb2RDJy`{2x*Pe1sf9*4ySeUKu4;J|L-6YOP81w^87*> zA{{V+Vv<=5yFm>~IPa0Zods^ScdB`^ZG;S4(b&{q!xd&#@tj4kg7kl#vAl7Dn(Oqa z3ele+coVIKHKp}H4BH3HUVk6{9ITB3!7%WmV-Pi%q6ZgLv zaw%!sv@^M-aUeN6(;Mh)47!nV8u8Bud-||QC5y0B>iU}hu<{peK>ca=YGY$2+Or4& z!_P@c+c00iUgUSqdVAa~#?ZK2t8|8AZ2^m*+hvpHqEySeu5I;7mS5Kn>&Th-qA{Ge zxyTGS%X$AX_d7SahZ94ZI8he>D<_TWZpRq9kQj{{nSv8-h}=$^5JU50*{X?~R<_!D zun7{RW_o>R8u#fK2bAH}u4&;RH7fhvAiv9JOa#{1%oh@TW^tRcyay%%qfbN#V|I6#)Ef;X zj8uOaBn)s)7WUK|b0k`To0@FmItc>Mv(mB20uKS_eMQG$UE1e_G*w|e*d#Hn#)Lnh za}!`t_tRpb2%CH?;rU)VC=Z?Ncp+@)Zqq+e;Fdr+(nZ<1Nr+4a2g@x~X4**|zUgZA zXJEEZurw_|8ZXh_C82AGqU2)f zDk(zzlln5v+d;r}zDVdPw{cVyqOTc+0mQtZPhHwi0DiG*;leki&?al%1@;M zH_3gjwpTNiQc=zz@n^^D|+*;&WkVJOzO<7H$4_K1VAB7Sg}Y z<#>EA(9j`IAXo$dY{<@Ms03)()!gJW5pU@q0p`LwZPr0n`D#9Fm_3xKb$z}30nW_4 z4??-s)EX#d+k=cKjQl}LZ*vPu@Ui&_T{{U$0U@ZlDJTi&RoTVe(Ajy6mCTfw;^#4% zmLfk&M5NtOdKA%bt(RB*FbI z#FjI|zx0D6wqk0M>*FFGR%1EfO$+7%*S6yoszGWWf1R1QC`CMd7PbjE|!w* z3w{J{|G@J$`%4Dt>didE+>DJpb6Nf)O1*ljQ?44L*2X9Itis%JxW{3H`poNhJ8|d% z4w#cjfF5g}W!GQX=u$)UI=5YmE{}M*74DSjhI&)dYjUYd8e>nVSKKuxUJMukM1m)tx508A}AsU+MaUGD^$MH(!UmxSBF zgH!Jde9`lF_y4pP-X)9F+9iv;9Ra+<<3$XKW(^Avf=0rpT zOGp5h2}aPHr=^sVW}8G{9-0JUD=PX)MnbYl7$jBd>H#y4CKK#Re*Kqc#%ua1>q_@6 zwtLy{%X{;zj~nkJ5XCj|u* zwW0oGP$^~>z|l^L3DCGK$TL& zPOBz_>$AF^{23J<{^TGoEGi0Sc=3V_t`yWUV7m+QxE_kX2tW;d&%WRcp~FCOYiA_5 zUDp_eDnba%_ishqX%fqiOL%{m!biEs^4Yh(gF%wGaoNPGLQ~ zzAj-PAwW=Ya3Jf#*0zYow&xs-%BFIC7*WO{4?&qB1;M}|LHf350Gfxb;qW4T|A(w^ z4ALanqMT`WPusR_yQgj2wvDgNY1_7K+qP}n+wbjeZ0v5_h!dwWvodeRtshyHH}l}| ze6uctaR~hWI0Fj~UlZZ}Uit>mpdiCH>%W2w4E*-;-|GYX`t7&QOY%4H3qu(k3qJKnf<>g zKEDIs$v#a1PVOz0M94j4_%CD-j>>-S`~;lz$SYT}i63EDJp=$P;S$(Bl&5#b?Y2;e zkAV5+06&}^In`xNiZ20y-53C|a&Q0wJo*M|PDmRH(Fo|B?hYG_NPEl1)M<2l+vBID zEpR&x0@S0gKV&f(yk05(`&~|Oy@I=kYgZycu8TY%ARddko?gF&=qOrL&3df?7Vjj8 zDSFUQG12Mm(ugkBU>H$W0nzm6pDG$kz6=Sg1)1w!Hl5dD9zR~%(*zG||cy-VxD%#11Q-SkVtsO3L6q#iJhX67t(2uH_tbq_opDe= zUOTa|kU|UbLBz@8&D`5!Cz9RxpFU!l?Kz}Uamhy?jG<%EtOv{pmj3RAd=y|g8~De)5HHV_7h16$&@nh>x~&HjU+Z48)W!o0|wYSLCmu{vG5 zmK;{C2l*S3cH)N~)A6!hu90-tfFeymv&8oFF16v89a6xwN>rGa&jz$o4U<6R>U z63y!#DA&${wFy!aI#@lgC-Z9TD5(#Mq-89@p2p7XiH_=su7vu-ixB&mThEpSMwMIa zL=S>_o~j01EE+_G3@=sCZVYpEhaV@U(*weCqaj?S(NPObOBNrW7|%%Ev&i#9b9yH3 zZ5aD*r?DpvVGU_z$-HwE3*i}nj%z`L+A~}mfo#+VmFtKyVtE~_>*}@1Yjut|)#PqY2-;8V!hI4=hUll8(>C9t=lWfQzlyC5egZH4Q4g>fRpk~rO9rFG_B(=F{4%5LTa z3Dd-`;|(eY8USGvKBVzZx~enag7uuwWlZ`VVAxn zbnF+88)-TuQgSV^^w)p1!MBQl_O?=HgpxQP>K7nvR>j!DyQDv0EV%7RDePz5Zyp&V zismchai2TRjUuV8feOGOhfhz9?{cqirVgQEAEW#&l+;Z11d3JP(~kZSmsOr^wNH*~ zfCL*x*!{HwfPEgT(c!amZc>XGokV@?sP&){Ko}WJmp}STG(&LK%v5EwH4MjwiQ@d6 zi(rusr(hbzs4@n-Y&4}PUD)hVASf1^_*vu9(9~%c^auN^ODUsAHR=-dUSge6>1IE4 zBrXUqpRBq72p+?*2@uqrh~sABzxs7l=&RoN8y`Ro$eK{%{TN(r_Bx7L)+=#!Fr44u zPoK01&DF?ye5YBO*5j7yYhT9F4a*IVIQyu3cBPW>^~p=Xdb#&bsjdoF7_7PAR!QSu zop?K?<-HMN0p)G>$K$x0EOt&Zt)GfOZh;NdiA&yQ2%HdOwNPW)JL=Y>t4Z`k3X_an zlg51mAS#Q{c0G%3b|T)ZzF9TAd8uqvcGuBYovecDYeut{Y5`1oL$#*^^+WyE~N!n595_& zL(ZZFsdK#>3LE1yhBb8x<2R}QsUn9y=dQ;WyAb*GVU%+Z2o%1D(5nOGXTbF3ajJb( zYO=eBPO|h}s?-z&_E$rCB*f@EC8Say#9Yb?$3azPr9qoIRDmbX%r=6=rs%^uLuX#s zh~!w&S><}tuKes{r7;Bdxo%=U+bh}uDHaY1=RfM#?|~A$0y3vZHl5JA9< zm4gPwNQ9`dqR11$r8vh9j9$kYLio&d%9e4?`c#Xh5W&_lYR&0|X{s3`ZR?Bx#zF?C zy5Qq+Jg$=0P?bp171ZYTK(7r8fcQ1u*HKXyLjWb>_EOs;5u|H}Z+@V3RPHmE{GN8lc<88V1tu*_mbVAKQGCxtgC|!|Dn22ul z$&OCA+m*{OLFdr$XcOz{5Qpu(`hD`Skh`y};J4VHuP^PJhA%bB{3|8FKpxgz%52eph;5@%GVNvzYgn8fPn$>$aw7(ro~W*slBB z9JVBP33uWtkEJ|5qi)HAAbMC&?7fl+!1bum5qx9iK?sB^r7PXuACPtjB za+k>d?nkr{b3T>>?kT&ODRgArXF3vV+>Xc429-S*nwn(!GXM+_7%sg&yIMfZL}XmR z!aaD7pol+8Oeq??bH{F3n78$arUu@aaV*|2oj2!cd49=8!iB!N4f@GelsR?p_tmae zy+2&011ka3xG%DbOf}W!E>gNb+QB4p{Gh)6BKeLYo`#&#Anv*Ft=DqMHYIN#$ZXU9 zd(>t0CxypRxJ3f+ZM0(7PL(D7dS11*uR3`Zzwf*lgScr(QTg(w20Jpcd>2&vFEbhD zMlY2RU(zih>G~=xs}_7?Gn}qD7RplCkn)-OlU$-hSQQAvwl!Q(`LHLwOT51s80d1% zem|e~!nptyq*xX=b+U7aWU@Qw_r|w#YK)dOb~j1#^UyBfZ(Lq&mzQ&K^`m)VGE{GT zA}t1Pe`NoQX`#nNb$)OMP@EGe^GL4_O{0jc#`Kn5koQnxJ;hqFBfBb2)b!!8C7av` z6ajtj7HY6gv_O9}fi>29T-M(vrPBI{Hns{6d!4*1iYoXJ@WxxEz9mD`@IZ%<4lLQP{}Z`1Oz5~q@^O+h$nl0%Ckm2^HdSaYlrwag0)HLzI?;q-G0abS@td%xcj5GMKajJ)7r=xF^o zMl=0@7ckInUVF!gvAecgI&E!}{s4olOzi?X0)^Ej0<^lENLVia=R-?Aw|^0wYD5!N zcQTExE6LBlA*%{owsX&IA-DdphRc|V%vURx^BK2|o&K{gSH+Z~L5AG%_PD;oQAv_2 zX>n-qGBTlew&;D8?`$MQTF91zl4KmKrS=8~lrL7!Bs}Mh()pROZ%O<%U{y&=3K31b zCy&~?h$1I*3A&D9pFQ^LZ zBNcX~eQm^&WSC`jl`B4@GU7C6Yh9^86SDT3M}nOqZNy#sqFBtG zkO_9tT7+BIj60Ag*OnE=BYZ=vO-cAG#q|4VgNtMWVY_0(T4W5*`mekb5yDu5@hiXk z_Q;Io!{CIdw<#7&fp4AnrhViCal-^$+?hwVAojH9pa7xT^ZSBi((vL2n=}h_*8xd; zsi#m@D(SCf$)__94#%|9(bLTv&q|?eTfk7~PiFxKJtsuCQNHGefUFJnDzpn1ruXIqk#bL20%mF;hFI^ zak~k5w0CeUjmU6kk?SS}B<5L+;k&X5{V3+uKGl|}O&pX#9U1(qwo?WG5D#(SbyUNo z%0qK1eIsvgpw~H}byVA^PjR#LXXa8As&w5+VXp3-qXK2yacBK2NB+RU0sQ5udq|fm zD4XjUy^jEsz#aItsCGTNLi0rLL#~N$EF1g?L1V2$#`bRplpAmDA`R_Cbzj*7<+2}p zr0QeiotEiYxo1jy)$2MAV2V;$o=Wn2yr}!mPtkU=6_d4XnEftTj8q+0z02yXz{BNI z?%}pje=$>UQ}FY)&L#M@g0gFmYhWlKlRqD!RsiGOtc$FAg%Vd+aDVxk%OsNAmKPan zEDf2W>bG>6GVuW!X{6L1AxL-}TpKk_m(6@1T*sJ*bRSG~WACjdE4*vTMLMLj7-bpOm?Z=>cK>m@R>U1af!#O< zkry3T6AE^|D;IBl)9Trs{g(j+IR}Cy-_k&+PL8qp70O z8muyjrz2*Up)}s3)M1!4nsxiDH3owK6jSUkl*`y`aRv!XsV8dxdxBInnhKm=leOn6 z)eJv4z{Yf_EAN*-21TSIDm8l9_JBnxp$~1<8~Q{8c!I6BOkjAC()SxV=}D`DOtCp( z6+oAe@kbSfR;xBZF|#*WU3kf4p=gd!x8L4jPvLy48>(<#k~PhUNE;v}V1a`ViHxI( zXcLz^m#fOd<@5XT=FR7*lRC&CT$9596yT`}5Dv;0_pAlkqiXMjmA6%8`oajaPnFTeda$w~yxz-f(W$PbbpRhRV@Cs9j`P1H4G$IJJ9 z;ioC0$kFb9;%)5HV0c)b1ydMRDYPCtcqgwu@8QjFOau4b>ZHs|rvXBbCT3B**c(X( zNS-;(O96Y|2x%QA=RoUDM4uH!>FzJpE+chLL!3|GM zOf0n+&N%ch!^!O3h`sPi!m<*i>Qv;+Zm)^jJpCcB){-1A&Pp$kAsZu2-5~41$yUalMAHOq57LR6Nukr7c{{bj+c3lVQw5m+>QWhK+$lsC1L*|PqCHdh9^ zq}Jvm!U0@$1THh)VsmyEH0^joER@oW?GlHbm4HztNEn@Sl|)lpHwh_{qH2@paNmiI zsd6Zzy3S`8;#v|nX0})Kh_Oupz(niI2&;)CAFIb?lSa-q^&m!AmRl|#rIAr2IRDU= zD0ceCB)f8j{Sv0fgf4Ls{Sbxp*Hw_*CHWstfzQyH!F7FqvZYOz&z$t;MVO(ftvp7M z-3n;b>FLUa%XBQQYlk-ldscKfE9#2Z5Zzg?bqqZN_Sq-@`{~19?Gc}SfFOb@=DN_^ z({Wwd9$)31*MSs4moW}=Jmz}3&&Fa!kFmEdu1f?LORo9pO2bEDo$2sl z`vDd?)Pf>y-$cc9%x<0n05{X*>ma9yn%YpHiD{&%O7#Tg3 z3QSdV5=lK)pYPf>Ga9 zJnXn+HCW^Q`k$T3tk&AJ&u`=lmEK$AbTj`_$tLY+Cp@_AvXtW@z!>lu9~9G2NiAe% z`n>LAcL#gG-wV8FWVsS_*&WF?_v(P9XSPI$%qwi%xWFtj;kf2#YY9uML%w6(vwkN( ztUr2FP0u7y=eIG*F3M+M^aj4|#J~aVQf8qhUx3 z4A6Aw0UgZZ4SSH}11xiuU|{=dZNAAnrIB>Zk?n0AvS`qspj3-Mm)2s+V2W4@GU~0^ z^q<)^^w6i37GteDNJiEnqyK1gND}I)C(5#DTU7zoZd`7JfL@x)p3-jRLcJzlhofgi zl|HPac}0WoLY_upDj}GNhJat1kdq6Z6wkHmaXa*U6IJIJ;wY{1c#h#c z5nM{Fug_D&I<~LSK*x9D&4mkyA+FTb7VTUGIg+CLyWmzbUUS^%6a`dHy$3|*RKqs8 zbz<7FCbhJufbH#{-;NH794W&C`JkuNi~eDJqta*Tm5UclWa1@hCA9SeQM!I`k7nXs zA}`3Tt(`BNq2Cm09E{#1FZxAyCe&HUrcP8*Wa<-TtwtZN2JvA1bkNh*B%F(;x7m7a z=L*m~4n;Ai@+F70#;XVA1yy47jojoBGka()>&+ZA0I!ihh&OGuX*7A-#CG8IWG7Pe z*izcWZl=|xbY5}pNG>PvcqzOe8YuU@{t%NC=`*uDVT8XeJ_L&OTwrz9EAgaZPBP;( zvJ}&|{usbv)ww2yxHa*}YuH9?YiFV);cgq*X68*0hYq|G6iRXMCw8YSM)8bE z^n8ICyNaoi9^V#vzLQyuSJihTDo(ZVIQ@`Tn^(>#LCFMDve(X6)iJ~0c)ha}X8+Cn z{>>0?XHS;Q`veCK#P%Zy#tuyh(A0FqVyp96u1*==Tw}EF&UCO&q?@fmS{J`~K`ejZ zmuwh>A)zEj8Gb*XUXJ`7nk$wXr+sXd$yu0&05;Fh)`k@c7n#dA>Qbjj+B)U&cHLfZ z3h1Fx_}y$)8pCNF%A5zURb0`Kc^d}RRo|;X;3QKBjBIesJzp?I733cW$aM9CD*H1h zC*ay!G_|r4G|*5Kp)!ZHT$oe(i<6`@F&Ldw+^O<}!4AB{(WxFvfnC-a^RkmTm$}75 z^wYu#g%~nd0Fn9`++Ju86+bLklm9mZ=gkbhxQO#muK8Xs<+{0er{;oXp;-%N+6;9q zLuS#_kUKKat$$U6VwQv%;7lxcsYS*UrKi_drIwhKA5^X}0DlG)ofEp&?M;7RgmA>FmpastE+ z)JKl9Yne5`29HkC!GeF*k~vG@a=DI#oX`KYe=Tl3CuVpDxDd_U3+({#d$D(`IX_>! z?#*~U(c3sfkP(oL!`Z2(X}|uS&(=!Doe6p|XW(<^Pjgd2z$b?|9qPo;Kj*>5wApFe%s{g}QA71@cyGJkPuut1(? zc!@+iA?6|m%(594L1+zQUy2d_3o;V@U8k!!B`!4@ZEXrHxT`GjGzvSb`$*GdV=3tb+eaIZic5y0jz-$Zf`mP=ozuFsZ&7&uN zoTLTibeqkmYeOa6Wm#5ulBpuW)zX@nJ(bt-5a-qbxR=%tfGOgJhTIRH8ufOL8{W#^ z{^jliCR}8I4E&9!PVb1k?u_kTAudB9rKhu@FA&q&;n6Laym$Jr$9B8(~a-{j%3#PC2>6eqI_K)LekSEO$UXHP?HprXP}7=P!M#N75} z(Iw*j)4r3l%VXB@+5YXI6QidSYgb1{8ttGRYs9I8JBzxFj8@LWC)t%R`#RSM{T%P+ z)|O}b3%Q>4zm=KU%<*Ax(W%zhOKkoKN1s3+7AXjDLd+f41wF%GP_`cs=sHF6C)6<@ z05h{f5)eL#4O|AKMqHDcl{tM_>(=uaI((~#r^n|x-J7zdMq{fneC=QSFiUdcg#OM5 ze{^Pg*w7R8warGL4|w`eC&)43MEo`(`Rl=Zf*x3Rw0*T}^BWP2;W!flgaE(mFq2PhAELw0oIM;x-6=-fT{=MR!sNwY{J$u!|n#NjT1&A@SCSetVG0m3v> z=X7mWpDRgL2bQ|R!dYh(-rjt3c+lx3n9ZR}t)t)Vkyg!Umva{E3pwDYhmVn9*WBy) zu&PlN!;6SY$whXm6QY;~g`%2DT8hQVOLDnv44H%@CeeFt?(|(Je(%|}sa3(Rqw;ft z@13>)dR#vFJ5sZ%rjj^ldT-y_0TJ=9KX0<(l&l?!JW_!1l+wd{0#QUNSjaBdsJ4Xl z=l7NNm#8>Xf|ctgplqW3s&X9dy0?t&^F|+tnjhE0r~>^yNVq=&0rg{k6#Ost0{%^b zhG8N4eW9JX78u%^ddT=bRLjg5DT7OnIv$p2n}+h+=8I|AL0Lt02Txl|47p?H_rTrTvBplD7JuLj`;aqh>tej{Y_=r z-tV%P$wZYHSDzO&H;*toM=CzC;5y0~Lvn;T40CXnNhXt6=1|OH*Vo#%pR4K+I!@@L zl1;Eutm2k8X}qW(&R9_l!n1~=fZXJhZ<9eO%{(^JbKhIgi(QK))j9--zM87OxKBwZ zxnw=;LAg$QP#ErDx6^rhV76CZvnQ($9jo65;T2zc?+&-h>9X5BttH>u<8J-H*5(gP z@gZ|?NaKc6`x!wBC(V#uzLUj~Agcq7B8F4P{uH4~EUw8J-pFfYTIlc|;U;V;c{FwJDMS-o>a zMh&Y?a99`h3p0H9?fU*kmO>xR%ndg{`}=p()BE-z&er(VTg!pWXm{gZUB_+U{i<3j z)@#eZ=Nb>;m+NoR7WUK`)m{3;lJlP0`xv&rmX^{+$ong6nfA4y^v*H8T^H7aSX_s? zmTzp)mNU>nRf#tSoi&I0|Air-F2AVUho?`l_4PQpX=6Q!yY^7LOo;Z;DkUtW1Y*CVF4UBA7iO2GD7ahigo&%6hgI{x@I&%rm~q#t_P2R|P~ zQ1nMg3N1K=7MejD-Jp&Be^(58%y|Ee@$bevvsd>+2k1tOH-t6ft%WtTRP^N928~Pb z(a`S8TIJLM4ci17PaV6KbW>4NIjC}Az6^R;exdO++f~gl*<5ZXMm)|p7`khIZUCiW zIK!lkjtIo(s#EBOn8<;fA79N(T>G1JS@AE;Oe9N-0Z(>Vbf`qpC*JYAYiVvN-!}pZ zhm+pZ0OK~P!L~LQ+F#ee&L6D&{i7`eF|A+77kY%Yeq*Jho%-7?+}bJDgPXiJ2@!h@WFr_Vh$yScu$XdBKQF)PTN>87c~ z>Jy9cSFH{y#8@@mp{6EPvi$EosHV$%?5M6o0GjUGR);hI*`=ZXkWwSlvjsk)ZpRx!%+i|)-*bY z!0GY6=j*Ibua_&^=qw)3JZ7DVA-v>F>t$b0U-M^X0prrzH@V@x-n+mqrW?qF=pjJz zcZL*S)-HoVBLVBfRaRcItjhlC!$I%h@G1yPiCb%0M)wuD^i7kkvAKSpnXKff?m+H~ z_`kt0b`&Ig(xN@ViNV5_4C4wOFay>;rQ*~yeHgo z{rshn%1Y1%Q^Rd+oXm&ZV!;?Mza;#yr()4_#6sFc7K1*FdWP+z^muLe7{c=@-si# z9+1i)a?+C8Ww__Ic9F`!R`WS-smu6aXw`$$WKlek(9+J3K^>2OH6Z+g&mPF$tABY$ zC0n>-Epq?p?_cKQwwAs(xVyDTHYX>Z9gP6V6Psc#k{6mU$u*yVod*n%l5$wv7^Y@d zuO8uO9)B)cv8va`l7nsPm94I6m}b*W=iGB)L(2lS~$GhP>Fq=HI>fm0l zIgJeNSoFX9EMJb!>Rx3gHup|uAi{|E!4>F6X>Dh?o;}~nxQ#fX9`fQ$(KG7fZX8jt z%AXWpsOYX((n(cS_XA+1t=F|&tPGhwg{YfaHR5~!n?bivzeM1u->mn1yqb?l7AEqn zdydJiA6?yCd0xR;VW?BBtFP@>@OXl)l5L%L_S&IQ$)B=ro2WHee8b0qe7Zk=X>#i%LfNQn|(S!E!VIN<^?FTw1q(yE5yY4(=5R{;Q7^^{tEY?6NWic2%n zcmh11guEOgJzIu&ST{1QTSL|zw?Pn>Uy7x_E_a;AH@C9hrv|+h|J@n?-3JLs_-7+K zKKDpB`*X}0muj!w=}P7{nc8$$mMffbk~c{9ND&NVBE5< z_50jc$SH+)Qz8IqUHkh_@l?`r#;>b4S;n?6;Lv?8hEqPnlecKtwl5^Vs&9|t^X}Ty zH=%#+45#<&+I)Ci*}yP_HeV%c#~U0q4P9JF*+@*kiq&}t@M87&VgaGZ4tX7aID(wm zNExP2Xpa<8D%z{Qv(FW$%;ro-TutNd zj-as!_j&`s(YETIRZekwkR6}9@motx(JVzffmLCwncr_MSb(QAp5zz0RU9H+|s%)%u#o5!R;Ml@tkyA#O z6&d+gXA>}->!j)&$%RuBlbNMW^*U3LQ%*ls=tt-*<&!K+i4EDvi)o`%21bLTSFv1--I)pOF7 zf6ym^&aKl_Ehnj54RuXsmRVaD)?g(z7m~5b6|C#SH8h2TvEDIXI@gZy94f8d^P3KP zE+LJb-@n1=y+#?6{V1W3l9wUi+5f8-lgthY4+oSdmMjhlPYGc3bl3_?)0TOQz!}^T zETxS(xjso*)Ecp0ymoYn3;!}fyL?yDZn#!-D!rp2b{H(Tov1RJ+rB6>LM!0B^LrFp zfWu^g;2>nOcR`LD(wm$VhzIjdiBLg$*gZK#_2W$;gXb_ax zia{$EreKJ-R~B~+6sIf>N+LX{Af=$>9z?ACi6Z|%nF83P;11YJ)sfg5$D4=6(?M_=J*ztnPhaKnQM?ox^yIgZ1W}_ zL7VGt)(!ArwO5MXSfj-F8+KHlcvQesmOXNqr*%R`bI;a@8njlFTh)JXKP=$KFhx{1wJUStf`Q$!cTv<)*PDMzmksV;%(Djh` z`ZO=wjQ4%}@$@LZPI3bEv3@zsr}oDh@B8iW@D%W_sTs1qO(*+p9ntXlI6nWqwLsI< z?eq1DOhxW|UAxDK3Rep;7>65SS8?RMz~cVydL0LDE1^$LGU!MVaydOsS8dxl)$QfZ zvj2602YA;8_AXPDA^GAVi$=i{Z%P2&rO^)2Qr$A=*NuVw`TQrtnft}dbaypj+P9^7 z^$(CTo%x!;5Y_2`Su$l>7Ci&Ls4o%6xfqsw2Zreob%$%$lwq8Ze4L@3!w%_0ys9Ab zSMnlmpyh#=4hG8{Uj>bPjxq^)$6i~THuWHm0o8)}loP`6nx?xHU~!VsHeVW`g@Co`tQkyocoorT5+k9X0vfqA(4amCvA~vg9CA{!Bn&(kAker% z09hbU3@vD!5)}>+GA^|JZ%0BXco@i~{6_0B$+P}W7Q%C`BOb0@w$#Y*DKIN94UW8?$f`y?w z4vroVzTS69Yx2_3G7Ep)<(yo~rDv9WC(k51b!+4d!9YdGPCkHz_XnVRU0=6F6M!=c zfd=P*3FNB5tZTOGCVm{-PTMLP-|u%%k%P6XwE*4yWwH86D-bi`$5n>`#Te6mb_1y^ z(?8#3N^0#|HH9Qk0r5S}K|Hvu+%uAcboh&lNlA8)i+EstB?HC)TzwPxK4=U>rXi-3 zITk}@785i1YXz24=oT=N!(y43J`RPOz183sG zMb5tjzYr?euieiYf{nvL7R-L`|30VJh@KTp3akw?+sXG2bP!V%bSE`x3kZ)z+aiTHSwzC%U+dn1GwrFA&}}9EkpEa znH@4vS=25Mhz;M7$~qlMJty@z8z89WWNeVCl4dc&fS5uYIifmrBk>V~7?Nt=MoKyz zNj?4j@|-{z2Vv9zY`>%p{NX@qFpi^gVb<(6prx`7HyS^2$rNr0Gi)+J)O~hPBZf3Y zqKMf95cPkgEl5#6!|?txN#_T`9v(6>F=Ak>!;grK{TTxTi6}+fXE$U7n_0sQ**g;G^t^sZ! z6*{bNckf>lP6A{(YfcE!)J1>TwE{0wQv7l|5h#1~s@Vj!^T(be=Ao3h_WsV`SbvDx z{e`|nPw%}YX%Nx@8%AqP>Dqb8L!D=9Pv$rVug43TLasQt)8#E+%j^FIb@<>0^ z8G-6Yc9=~-cb+6IqR{?|3?YHfZ-ZXc+F#6qL=3(uyWjDK&zr5+M;%W03(JaZyiM^d zHl7jbc*pV^Zv=(Ade|DyxG(N4DA)=4p2L3FhZn*59tj>$QAdR;U{RzqW&37d7&={( z`K;Gzi1%L_^l{Nb+#+Vj1e^HlWAf9mfQC_dWByo`XbY06`TQmgdm~XC)aBRQ`)lm{ z#?qTm$4@yr`%U6mDyJ;AMx=00x)AZ<%yw)O&;<`3>g$ci8hv}CkYq62O}J`c?xK|j z(Twxq4#f?3>`Q1|#~adMu4o75OM989r73pRXx2Vjp=cfd>l(u{@XBD>{VdjDKuUop z+J^gsVTS%2Lli;0kNL4XbY^p!=pni^RMY{w5LOhaU6OgbJ9{L@+8g^wNLk%kJ#9w203^VZ!%oucF*eS$b937P9Aju?(SMF6D-XT(@0g@i5XcN#Y=it71-2;>BJq)X>4~SndTWFtS76h7<0t(! z2bFP}Y(pD;B(&&7&k>VPKf0;%%KThJFQ)rBG_FIRijXi(SEP>3g!W)L0iswe9i=G} zq)fb+|G2py7CF{Val*eoDd2 z_+v4!11bexs=my@5HYX^0J?3T_Ygdvk6qKUL&C>o+wSkr+gEJORCb zt(>%>fp1$FK#$(^Aeaq~qSOsq1x*9Q3${w)CEI}nH^$}Aesoy3#xP@@^dJfC7okB( z5erEvN4|eiCVz{osgE%S2SVSbgiOc<^P8+hshPEkS%Qvi|_G!5>dpi8~P=w}28!_RHqLjl%!xl@tM%-XY1?L3A4}O$O0% zn8Gn}Sw%v2hi~Qbzll?%gX}n1kj&93U8edELD6vl6vIzkTn6g`;9IeLP|7$T6@rp8 zJWBf{BmWPigPKff9b5W0@`i5U2)LEX)A{KZRw@67P;@MmBa~LJp-L@Kp+CXI`;!DLo=|5vttnQc8vmJY)2Q zw-t$`moS95P~sHQD`tF}*b11v@=umSherm`3P;kfSVdv#FI?W)UPC(mg!>UYreuyu zs!%73doQ+epF^WKRy?kpO}k2{J*?o z7#`0G1Ga~l8%6##28$R^&1fawbl2m?2Mm4h{DS_oB@c5 zz4GPB2w*THIUzCD%@)LSs+1rw)>n#2OV21>`{5wB%@!oZUPSVB1W}nW9}yXwW=oP| zY9mFCfqv*vmYz15-lL}fjY6@GU>_SN8+*q>X>yM&2N0bzC&o{^Dz=ZcfRW7`MYmGL z|HPm)8JU{%F|JQC(*rV8!RJR3Wot&!{m1Kz9qz(|9j?NS9Zr&kJaYw^q^-ZpMuI^C zEcmPOp01leD;wZI=3C8m1T^@@G2lkf7QZYV)N(bVUN9>)U-lf)7kb3;k)@U?j&8AmQG(UdTdpD+RI zphKu0uF^!-=2mm2i%CPIkt+Y8=8I;R34#vxkxQe22?8uXA)ZTWUA|_0u{Re&#z|() z6eyk4ga4 zL4+p0iI8n!OQ9ZqUA}(e1L>~BA7c^~rc$?4O`{Pyp-m9saI&=Hnb8q+bbXQIFOq+_2&eGGw z5;H^k7hk*QHYYEW_Yy@Q3N44`F}v_)r`Tek>`^`?aq>thDcE>lD=vAraDD0IxxaOc zIiqb#I%CvbHE_Kz4Olxmz5=F6P7JY8tI|YGcYLi&X{E8-^wvzM(+VOt&8}k1=bM4{ z#JD)-REDy95n(8vSCo}|n6X9B>FMTtPG}^%WuB|FjV93T`8W^}Z^_*lFI&>0$|b(m$*{c@(eIF@xUxpFdq zRjx@~Ww8%0cr`U~XBsXSxl6x%F#IY}cUrS-CKGPvT{>KhPBW@R19k^Pn@8d|W9;hQ z1#4G)yb>sDUPdn7K|sRHWRwwVV-Gbn=SCYa2D|WCer4pNu@mJ8i9Mq^HBarMD&4qE zohVZ}Wjh!Jy81okr0pr@jG(zNwblH>)C*mbz=}MWs#s?p6%x2F3IBtWgMqL<&$SG- zZHazjOd5y%f|2dq=X76tMeQ)bi9Vfku@FBUswLklF)a5+LQ4)vkBAMQg-m*l9YrR! zg#$O{8i?3g`$B*!%61uONJ3l(+lD3C{R)W@m?9GyiFKM+O5GT{@?nBQNQ_Hn;Y>z` za!hwpyxHQx^3 z1868vDZ9+0P|d&vYn&BNW-e2pZif-s;M-ymHekBWy8?`op?wEHo}GT|M| zev>6{%1%$p1MgUmT#msnIu61xUX-D=n@D!!Bs{)go@GrWv+|QvLhaA?2i!|r7p6=uC_%R5oZZe& zue395dM98rJ75f`!bNT{mQMT>A)m7|heyp%liB5>e;jE{)+X@BzO~8Zv+tPm3E5Kk z`;0)74;7@ArAt%sWwl?AKR=xCnj6aPRKpD5dw%v+qeM?UGQAiCXh<|N#B>JTm=^<=oX}Wv3W6mMQ)>Ac;l<#Zq}}YA71(Ok?AqLEy(scn#>rby zeetM`nGu{2APPk`Zku_yIF6={4^cwCF{m)-tn##yxylIvOy&V`|TvumC%|gC9yjz2WSG5}isuaDJ8~YW!Hv zzNzcSxI80qD8Wa`#?Fpwrn6AmIqlCpudlx+98Vd&?aw&pDlvc6KW0D3UvNG{dQ(Q< zd3lt|2mI)F>^DRAbfW^hzk>ZU|GxRDj_!>8_owM>TiLo19TxVrOly$3k0K|3D)M(e zf?dzko|4IDf!QKJ~lRfHeyxpyeaJ`zD6tjElwOnxAPQ{uVSz^Z=0vS+Ar~R zf3?1NoJ!?{M#7%#uGynDcvrltN&ODd%eXz@4 zM+&#-BiIci>CcAocD;T8I+(k#x1%U-=wsfUyjsCI0Y>?EyRUZk$j36Lw8x0T3Slpz z!sJ^+3tyOp1p5^6R4^AsF6|5@WTLo_s|NO(?D?1dihkayc(6ARyOy45(Yf~NMeB${ zQA*|21b&grw6#>=MCXNRy(!5>2sto^G$FKD7hj&(~c+U4(Et*u2RJ&i!mbxzU> zUq;Ra!(y6sEU}R zxTu7>govoJx~P(}s<5P_xQMd4sIi{I9=vapi{w+RQ`_tYt&Z z3!lj%YX4aD)XtI1akWH3tY^1ZkPuX}*(1;a_Y?64@W%2`a8{1uCw@x6sJa%)5cS}G zF>!e#mu`WL)pEvG(*{~n*I3zpk_(JBVuSK*=5U`yMO~jBUw6I=JsQ1Hh0SQ0%QKk?WB8!Nu5Eg?4q#_D>QPkb_btg9lu#C}t(KNIdLS zOyEVj=r?R-kZ2YgoPuP579S{rikAU4mi2bgv-)k+&{CLK>32i%Xj_jJj}KX+4iZ+Z zRuo$-87eYq!CC%-9!l*`W_8;r8cS`JWt7`k8%uOcm|x3cEl%{kx;G5)9e-dbu*g2F z=S!s9S&(%ykT?-s!d#XG3VJ2>YN&jZJ$xKc(queLx32 z=11TQZ*jsFC9U|&>BAUxMZosZ_{_nu-aYdf<09- z-B_{dCV=C?#&$Tl6Pqkf3%v9ync@4TOoY@PC=aKfoO#Nb%MwO**i(9>(0o-_f{{|w zl7r$GxThX79#jpo7K)iRVg~nOxEfbHs3u(NW13;CwQ2seH6^vh_d8aMLreDdr@(WB zq_xW(1gW1`fW=M46AH@G;TCb++or~o3o5DH>Ju#(Lo2Ztuoi*%#NsyICwu9t=}uwm zE;vkA>Q!FWkZ9Sc1-%T!*iaLjmc!t!)LM@Z9w$8h6IzX=9+q06#SfvvTK5F)`y)}X zyP0bI?q zJo+{GzHOe0wOZyj=ev@ts>&B+-fCH8a-M3{8r1w>!H%Fsy+$0ZFw6NgZtOdt-I5KZ zMs8?2VwIUiIe_B%2^V4D544b#=prMG>!sYj`ckTT0;W>)vCV{D@t8{eDA^hxw8vC{ za6H5pmV~jeXe_0x2dhYjhfp2xF5+OkWK(lAIDWEYKyvd_asB)56k{c|B@^x#osdE# z*@)5PLN95S4EBuxrbXz$TkX1-_UlDCn1o)oPntP97=>158co8+dWq$`BO@6{b`k~4 zoD=<`5#`6)Q-Xew2g{#L(R z7saMJ!@9elGwJ$(?<*WMb*mx%xzbX`J^AvEcRz=GWEB!XOXn<;FDB=cvk)(6SA(4U zMvs2^$*uGFI?KT3v4dmO$MJ%r-ReR6*i!X$jqu1~+I>Q=bu14;=5@BOc!`R1l4NOc zZ(o`-xZOD6WjB^2L0T;JI;SMQUaaUkM3UlDwAGeC^I!`TUhg^%JvLo8moGy~jIa=Z zyNfdqXUah{^pTkV2Yv_a5#FQsAhiN~Z3l+&Pf42&hM2F{WqDwCl~|1`6Nrb)3_>4| zp!l7h(eNJ4hJlmM+a0%_tAkw$Olvv>F)McpOuA3kGR#G25mJ+GroIQn6D|+c#HpUs z%*2qGQm*nGc^j*}WzIIIFB)9N{E$T2^sImBgg{F3iQ?p-^ZG+&0F{46Mr<4t=O!CF zA+eO^ppr5SKy`uVmETxdT9i7ZMy#StPt}t_nGh$$8OP2}M(n6b^nWr5$+MFUQL=&l z68#;=#z~oLRI18|%Rbwk`+o?nM=9S$Fu;$8nDPii6~$o)HO0GBRL?Z;nsqa_4hzT? zD`NBFsj=Zhc?k*>8C?{KdGQOBfLYQ0_4uBDbdd5jUAHc)xRsmo&AfPqWc0)pD+W$S zhmB)3<31-k!qZjUZXZ>2YYgu;tk)=CWJU0!8iQFghW4Y_jOTF`x$BtWyWhfi91!nU z@$>TQSg~O`hqK`(RY#BfC+x;4^1SL;ae1&Zhrb)cD>pA0y72rXDTTajSaImOAoOOH zCAj_*cmDg{w&J|*CZj}cn>_q1-qt0n8$p1c+D17;;A-`FIo%`{_>V>`-^ujsJ^`l7 zW`~W5)zLXd#2w%1*Pv(p2f+SCe}V}!18hDe+O9*Qc8>it5|eKmJ9353#h=v6^c23a zE0;%Koj_VK%)a+LJvUFt)WWe2H89tY<5unjYs@oaz|gaittHcMKb+d$Kn&5cJ%^fR zcjuXhE@Ro$kl(rvHD?(^SR5a7YLm#OyRa)c!!(c)8BN$ZYO#DlV>_GbAuQy$De0Tm zc6eu}L}MJX>1f$}91Dc38R9AKLh@jeJPf(`U!uIZ3= zGx-+pc0m#fwEa9$rS;*g-UCVb#T0&w-A_o*>JWheg@hq2>!I)+3@EQUKh&`G-{&Zt z3<~h4O(8+@eZO=zLc|s`QAWdw@Ko8@aJt16f5liS;^Ew-g!0PG3(|FsJ0wnK3mAQl z`crjOYwi3n66VJ~2#&O^$ZBO>kAh!eAN~{Vci2E2Bf3EBWr}*4u0yPL+!!Hw&sEGOyBbmV<~aTd{ zRj=v{mI?Bw{hb=7R27rgLi@Y2d8D&*?!t4*7BkaD8k9Q&!@lV%8jgcsOjHIZ_``u( zGnNd~rTL6QiXwt32CL2j%!WnSKwxXQXORh;CUaO?V|-bt}(a#_B#*%=P&mLDhi5i9PC)ffx#r3{QwDJEqi&`^|+Bnj)1 z{A#{yUY8qA26tYUl*HDHF(Q$<({)OE$8faTglw7DZSb$`HTq zq7@={>w@Irp0ZH@yY5ncG4JBDzzy+kebXHfyYrgTp`$w__L$%cc|Q!4BEgW&5TPy4 zeP#aX=FA%`fh#K!;*DNjZ|e@|Re$~$G1c?w#QtkLS3di!>Olhfe$c{?o)C@#TY4Xt zWYRCGo}PCkIhHI1u*S#>LBHMWmmFHUy<=Ah+R3>KqI@k}^E){t1~9P+#>js$E{>7C z(OJsBCJqL{_QmJD{s%i4^q0W~>z^Dr^wP1N+=Lb!klik^Lj;-R+#ntCJWrtoNIi%v z^+TCRtHsSXvhnH@_!5+l_$DbS)L zlPsEC75EK!F;CKEU_Yxt|IPVLdP%3}h{GF&y5ZGd9Ti$W@9+Q8gYcV`@97#jA=8JV z2-X|Zr{wjnBo9!RHo+FnZ}uz0H=Y0X+~07-2*@W~eup!iFJUKKy7cCz+Anl_8Nn7% z9R6|(=h@6Q{_73I2S>&W$IAMyJl~kODWrACqi=)qKE?P*k1QVvg}3)E+X7A>%WRHG zL2LpoYVZ_-E&Ky!_fKL0{KkN4cc$5=UAatktGm7{%z&c{{lvYCKJs$FN8i~Ex>LZB z+yT0iJJCKrAmla}?Ue|+o!D?AhjnhDqgkb{wfZ;A+b#si0U!HgVdz}u>k4N1KG9q0 z*6*T#mUuu*uGF<#b+6B;^zyM%B89@b?~XasVc5zB$J%3}dy>~bz=`c`Cvr1@pGHwx z+->^N63mA?#8DM<8_p6^5!W|>qV+x4iCv=p$~GG7{A_?J>gWAL!cg=YEPQl>-Rioe zg;_jJ^153902>@&`4!X4nk{+lm)grg?Nqq`W=b&fvRwb@c**pKsCQ+?4J7r{<1?Dx z>pRbwWW*r~P*fqt;^TL2+)cq$IPZHd`USoDlj+yp9ds@ab79Yo;Iz#345F8A2`l|- z&gR0Wbu|}#fKmtuSqwObEqMW81MWo^WxZmH>qkcc=OnP}eGtID7xgwX)@Ph@gBoy) zwoLRflae(jjrxX;s8BIkN8|WKY%nLnK3jIi8{Vd*u5AzB`j2k>;m+LID`)rl>en6x zb7y<+M)*XXw1XmwnUCv)m554I0|mQc5%@nyc;; z-)K;X_+z8VJ8^DIrbFq=^%HZSN!(vMYKg2L_tBa1i7l7RQDF_Sk*0_Jd0$1jFa5tw zyh8enp+Fz1AT4*_UYabRjZdoVvu$Cy9gGI9k7fDard1*pShLIrOc%8EQTz$I_Mf=v zw<8juw@>-TqEyph^BQm_HdNBFs}M;)oN0Y%Xqqzvcaz_NaRf_hEE8AuLJ-Tp5JFh) zFT%&;>Hh;vnBrf|HBkE^^0JWcEpaGWg61e?CBt1Yl`^*{(Lv12<%e`5OkJ zOb-)OMt$E%Els49F76B#P8f>TJ?K{*&3nT@E#_S6rZbIMRGu$vD9ZitAM1{C{)Xw* z3xu%rZ{Lj!w-?i7Df!WcqCyX0tRDDG$jMWvoc`58F)kC{OL&0a$KRth)-46r&!#n8 zNx3trduLbJ72CgryKZs2taOWJJwXq}DaEXl@>5wxyHmGiwXRg)nSn5RUz*|9FWnMh zY7I|kAyLpw%LC_*^y4^nYDQg;#`NPCPadQVL)|^{d&Nd83$`Wo0`w}Rx~5+2KS;ZV zx<}`$D39)zY+D!(d^*38aEM4b7VaOQXLbI61Hu2`*7D}WOoA!rF(B-g;;iu*GWU@j UB@3Cju&|^Q2^W`|zB>Wt=S(z|AW9Rt%e0OUgd!I`C0 zJ}}oBRfnoUD^3pDJQ@zJ)^dHY;0iG)`9(RMeXUg^R&mbsSA+J$=G4PMRR<{06k+X&qYzPQGCFe4;wMx4?=OP7d06!DLP@)~IQ1r}e3;0_y6BQ_eo}dVZQqTblI6hV&YEqe>bT(|q*C;hJhKM5B)ahoY&~ zxwKLD_noZFrcV-3+a1uW3H0JY1v_N_Z)G&!P|v+?G@3qITQjvfT2-7sn8ew(fYn=F>S6 z|KRFvo1UexCatW7#|g?-RCe+&LmQh6E+bq4abnCIKdS27X|6@VkOMS~ir?d=_G$}V zupo)11!<$3^+yoL+L|fB4i>twoj4p+q%2)mOj+1FZ;zw^ACNAYU2X7$GGTSAy9Cfe z@j(U;B!Pb6UVv0Xh)q39Uu1O$%$ZsIh}Lgutjw@m>bLD&~<+R;78V!oIDqs zt-|9t256~lu;0+`8 z=?n)NkJtGS$f1N#l`yOOZr_gvL{@_oKpga&rXtl`s1;6iRK1HCTzOyRFvj?^_aRn< zFvU_##4TOw+=4cVwi&^JB50&GW^R-2j)V~d^}i4xBTk#(VTw+tA5t3$m1*HKio0f! zCqH$m+_z>fNhA=umV3q{B6dr2l)xkAeqZ;QZVC*<;4U}5+UA!Jbg0#ffs<_4p5?ZD za<2r2Tm4BQE2}jNW1q~43b33Bq@HoXFDgmcE~6$gV#(0xIu@t}SfAK!mrh;u_{+sy zh#3_UXg5p43IAN{cTdfy!MN4%7iz91@PF{M$aC`+YkDK#FgO_vB`9=`jk;8QSGEUz zeM?1Eft*@)I`i0tAUVRJh_2&L?ez-zF5A8=o8FzoIRhfCLqO38JGpq;G{Uh#7JHYe zAOy;{q#(4h%zS|cyeO-GRLK`fn%l-pYp%%Fxf6%IBQ7>GB$IH;*qsNZ{n@~fK8db~ zeP>f2Z23j_;*P_XsSi+0d=yLgKHNY?U#e&6X~jDs?u0_FHG zqf-NkvbJlC2!U76=-g7!W%Es5=ZVH={N99j6=y`wYD;E^AT`Q*pMM?po5S!SdRh&z z7sJSAymd~AsNf{zyxV{D*z8Nnc*7s4b~g|s8|l5Udc;N2lm8`$P#rXn-9!P6^<(Kb zpV;eTmW3w2p&V>a@C@Yp-xuZSv45|dnPZX;nw zy*mP4Nt-?X5ZOP^Q{??FOkR@0P%YevBPnJXs7YFVsS>Ew>up9JYxXFNQI}KlyK%O?TyzS3#ZP zvl3**F+G2CX*yh1J!+Gn0>dh6Pu+j3`u)@VhY9x2*PQTEv;~_U{3!le`y^GYcba(0 zo{uoo>^JI-s zqSkBs-V0<-bcL@E8n!2>taMe6t^d^j6hhW^oRmZJjT+FbS{p?9E6^nzg{CmGuj4k7 zmPQ@`-z@PhM{MR^AGxJ=`8qc`KI-9$heIiUd~ex{(8cAEormRh0wHfBNW1>^Oqui| zD@xw>FRKUUP)=jX3ch?Br&7*DowkKc@_~yht?MV!hx_BjN45j{`Xe=5D~*d2qn4*) z#v_wmi2#tvH}%r2%6|R+p_i*y4ky6)TEA4?d=%+AkdlP5hF;sqQy~|-BDQ3Z#WDDt z1ZXa@;>%nDi~^=$tO?d{Ej0wn#8Nnu#U0OfZqY5 zG_UQW`hHOWHi7h)iXg@&8(z!-ooQx!MARH3{iRYr^a51W6+fsnotwf26E1MG(AOsl z4(X&@(uW#+NWt^8>#+s}nC7;7m}Xk=XeUw1v%`79y}WAS(E5`zScgj?rFKkIgIJ1O z`~xUJltuuD4`EBoEU}1gOwgovU29LKMb>qVXv(&V8zz#7p+Gs9wi>KF==V{dSSA9E zUPjgHU>l9HUW=&RUw!JtKg0dOs>P5b#~bYlUC6ta*%Xv_dRl#p*fQoA^>I86*vaDM zBmpL~|5)O1jJMeuL?;%-4(&jf`kR37fj}IW$j)i$JE4|48VR2}UZn=MP>7H>hX6T7 z4@J{&qJLHzyG3`_#i2W|mFBIFZC9b2Z8N}D!;kl(r|e$c$;(|J!U{rnJgpK%_Jnud z9!@If0!JW|M$pF~IN;p`>`Mo0RLtj47lH1GT%mpiD#v4jztwzjIs0xUo%>w}OT#tCNJ zj_ahZ^L@TUb8FlYGCUQq5AMs2CLBZQ^rYifMafQN?%+@86aX?eIO(>?r zoXc#UO!NK?t?Xz0CywY_N8eITtedcHHU@jY&55{fxMPy>Z^`@7KT^)g`@mK{DlRqK z1jSpBa%v@J3zjJZ0hsUhCF!LS;i6EYKe>t}zLiwU952^h(iryrd#R^6n#m1sOB`Wa zONEktY3%dmC9~EUgH?GSZLMqlHe%cXn&1qC95e!0a;Reqs!Xs*7z5n)mxpk&-ik}Y zZ8|bSTJXD1q5_=!?;rWZfSud%&+X?ntobalXSp>Pxd|7syZb}!xT}v@zV|n_k%He) zgzC&mdoHvJ+^nvMvE4u3F}=WCRoOEW(Hd|Wt53t3gwE2Hne-U3h>kuS*aRre0%t&b zZ{37aTZe2@8R34mKK3;fHkaP12g{H_5Q==jbYxoNx0p!xliGL~gVKR-t zbn;Q;6qDhyOLC4P{qD%B!PTdaR}F-x@!B(f&}IlCCl*Gu69tR72T9b&@CSR^+5R02 zWHa8LJg9I+EQNEQ(snnLk^_ayBA=)_LxS`WvPy5vJIg&ZlRz6+GL0r}4?1ec$~nh# z@bXhXNtfOAm?XdA0b;$HY-$nt6_fKvnz%Jm$9K9csT9HA?fHG?@nWm@6nIHFdqj{R z7c`bB*&5Mm%#zb4#!>#^5&HPw=hGR(nwXw@GPUCwE*|u7k^$NLV-`|~`(cPJptY1A z;8g|33%tsupy2U(Ai&NY8`+OBrbZ4s(i@4HBsg(Q;YVU_K(DF4T@UHoaLV;R?S2w;ixdYtT z{6A(#q~Fab%Eilt3eDzCp4wpud?I$mCgJ9qcYDp}K-Ji?4e61ZozO8;rG?g2O?QVf zD?T;k?y=GcfjwUtm+$L46JjjQpF5pY`vQk3OGL;zJNw;?=Wb@DmEI0ov0_?veeuw$ zCjB*f8vbU!4oyWv#0xoad8|5J6pRbU#g~3BPJ~^_3UJaDIM7yh_OVsSyeMPwJ1^n2 z&!A`Myc~-?$oC9J4ZfHP#>LMJ;P|ATogwe%io0r807J(QLUz00x+f*YG;2fgHIt8^ zEAh`0>#er6L!PiZMwaJr->J_4X#83*jtHa?lf+c?j% zBGHY$_#Gbx0TVYX3idl8szGK*Mbon_bUPR``*sUyA5fVV6jY3T~kT0Nc&j3 zY3h`}Kq=_mhcY6(O%LRzxkyCz7USriHJ=m;weljss-s*J=+QqJh4NWQ1}BVO?~PP| zd&8e7(n1{H`U@g<*it-Y=u^}C;HALgSr;58es-@H7LVS30)!0Kd|F93ClRIqVx%|$ z49PrRoSj#$z4X4;vsCUt>Z~=;B@Ckk##zh`D8)QkcguwJUK`s^R0a7nD+jS-v%ek~ z-n#zeK(4YafUvpQq@X!Q*tX!njb@KVGwq!1yEbfK5>Ck1UmUa<1}%3Zlwus4uh;nI z(C2(RMwcWq;fb8R3_|8gzAxy}<|0{*H1-k2Va?(N%pnS%7;(YmslRzQ=6e;Jk+CLm zF17t#6yVmK+;Ew|AA;+q`1~zIBqw&0j=PAywBk6OWr+`hT|A>`RhBI!*p>du%Kn_xYvmCajBaVp)6>5-xn zd&q0foFo&NXcksM+KOtNb$8aL=1pp*SV&1Y_vp7n2Ez}6@k*=H{EN9QjjXPU9`*He zY6-KdbKmNx#JK3F=~5F*?6jJZMWO>inap@4W?j_GLv3?=Zb_&OgF328nToR{AbPK6 zb1C!KuCWZ(R?yPGlJq#LHJ?D&L3g{FIK=?8e7r0qDIK#L2`g+sNvX+j`jDrtHW_!e zXrc?Ct+oEUH))&58dsjukTWV5UF3bxNNSPT4Tsy%eEVD&z+A%o?3^AR2_LVeE%YB( zO6?C(s(XGBK630elsgU|kSVAGteM(6{{1PdIdYTziyZ&NDDV+Gj$wCZ=@#r`@}p)O z=R*duYz4YlFI$DSu*4I#hc^q4u-=dpsV9y#Q3lZ)CbmCbGRp-9OI+^R$(VP^L7ofM zDQ;M;x*VGc$CNrv$k^A^5Zr}P0e}9cQQd5<*-(TZ|HN!<73JKu9=oCh(6%08_TOAp zM9k(wF6Tslkwd6hJ2i1h+R_|?;+npq9SOF#y5>Bl58zF)+aon!GsZ zUf=3gQn@lz_Uop&-5h5xM84SsA0hEdU@#)jUzRB z9HoFOPwebYR(Fg{!NscwM2+ih`$ZRt-0n|0Wh28N{8$&Mdtr z8vIFb4Ke#ubcs$MLP@NfTev7t!he50~Bs!lrps37h9ZH(bX{nG8 zHlXge+y)&ePa49%>(o>K9CutNVgX|sy34POwnO{^S(<}4Lhgnpa)eblKU$@4u;$3Nf7yy~q{6r=^HjDdYYuDfXt!-#Py`i?GIlnvP+e649Vh&QdWr2IW74Jbn!+>tbQTk z*8QIubER0vU~ei!0_irT7Iy$w2I#KuMgu{gRUuL&%HVNDHsOJrTR(zM_m{i^rDz2M z=IG?S*ha|;9mknmk(ngnk{SJ&(;IT2&Xj(-Rx|fh*akKfA9KXR$J4~yBAZ&Oicz?I zqc(tB{MPH|q|ixV#68;#ed0R5=I1}HIrajQde@zLE=2i@4EtBY0PRSj6&sy;kMPI= zdXAB|B_6V&cO8e>(Gn#t1}?@vrL96IAmScSvNHgZXRJI1>qJPJm;xglgfyCjG#4{? zGJRFdfiHYq!M5|Y?+`b;vv49Qv0U4GX0LVn*GtN363-DX9@+$AF%4z;gaxbz8^73bFUvPEli&R+gA-Ko}y+_5Ql8BF7gGHCK%YnVfLR+ z7g&_W6;L21`-FexUk>OPbWB_dYJylac5x}$m{_1p>)51-|J*&l9oHt&&3p5|?eMb5 z4$W)$UY;SQyNz=GM5&u+qly>& zE%4+kJm!i>B3L@U!lN(<_)=~1KXx8IC&E%&B zSm|Mnr_r(f$44`+*Z!zgv8qI1e|5+^?Sw9;}($<^aN>+3-Kv@4trh)HN9H zRwbg6Ahx8;+*5RCNj;kLb4Rcd>PcXsBk$_KE`Hsi(Z*r#b)0gD)l8!fB@1NC^}pfU z!sRfLKr;bXVH*y`h^jK(Vn0n)S0h`rPmgf5>4K;+eitVVC|E0j#3rxWma6Vqe;-41 zL87Lz)I5{>ECN>hj>k4W4*8*{pvGRgJKIrd!#X##V$F0|t!Z2_x<7P(r3pt>{4N>h zPUx+0@0_vr{h-E<6Mf{l4E(hyTW!K$_4I5}{0dhd(unV_X?^$%Ui#9<+{F<1f@CS_ zwP~9bEdR*OJ4b=ST)bq@ICam>8&`w2K6I82!gT4(y9&&>WsHnsg;ngEW0;a6)^Iyz z^Qs1!5=K-aWjW8gI%A32UKB(9=O8r;L}MmeOvi*5zk_<+7Y+jrB`NTvS2>oi)|ytZ zIH7KDmvIrrVCdI`d-r0bal97WnSZ!-nf~^dHjQX@)FGYBNN-C1om`nwUn|R7td?=i z$A(@I4y61NDqa?PR!q#AC@icKlpk*v1s zOOBkIh#9-1?DG@SD$?A+GzMZ6AOt|YsR7hi$Z}ieiYmn-Kry#J}{OAC5JkrELG8#xus~YZd4CdJFJr#TV!yW$( zohRW}O`~^sU_@{BARMezQEzVlNr3D$RF{F02ykh9S>wR1VL~UAw&J{ML_8%s&XGmn z6+@Pt5t%!=rj}$>$_-Ik&eh_vE#a57t*NexZIZKa$ef(dnztPzkHQa(@t#fM1epU= zY*}ZN`7RIjLBNW2&dfL9MP_d63a(%s$N%EFeR>iS;Lf#qu?x!?ibyEWaPCAJtG|?>>+M8Ihbr2Fd*l#)gF)H@qe0AqH+y+ z9|Xkx?RJW~sv75je6mh zjF#4CvoW*l=EB{!fWzIKlrO14q3oHt!hyexDQk8=nSc3ek-VxDtFjc3Ic69z55q4n zmD6}liBe0NynWdGNi46G%S5(IiirAfCHP$XWlGMqFW)fv^W_@|Uv~;Xc^(RecgleO z%JoN++vab5i^-isYf+`pc{}+%CES(zKBti99~v$0r<<8;hUBL@DbC-PVS%>0gz+o;ditQq#ksktMRh3yAwSUt9)(VXj7ZkW>8~>n8|UlH zEBoJ>H$2lSL+Kr=R$R`bF=dsnw9_x>oHb@-R*QACHu!U5ZMYDDYChicYWrM4$Ka(W zWWhcp zaE_RLcQGQC$&cX5eZl03^j|tA4^cLBSHd=Jr_N_*$fFjkBS{3Mtcy3Sro<6{SKa7u?Z#F{Z5Nv z#Q+@HvC(*#JA5Slnyf;B zl%dR=wMLWI(Rf%ok13KG=n=^Yo_vC{>q_-3Ry%e+Khj?H*9%F#0@X< z>Z&+KJXQ@kxGvL&KhD~T=?mAz{?)ZnVXZ<6U#sNYassi>^%NLw1}j zD9jVxt3hYfYy(S=-{drq-E?K4{8xKFErZH{0)`5|Dc{Ed-a5T7UK_v7lh4daHW?^V zRWqCF!_KNM9eZ8Six3`&mxqlYg)Qf8Bh+xRdu*bc@Ko4Ll{D3~I_I5cs<|tD@P?;f ziu??sS2RmKo&ve5mkMY=qFcAMH}Iq^d!O7-4q?_kLrU9LqMp6<$vjA{JsgD6gaC)1 zEGU5(LjRq)J!?&roIK+I;e3yPSqUt3ALJJ6Yb+&+XLR*I6lGqWS!95R-7ZM+FclUJ zKD>9*W2_!_Pkb%}3L1LSO0V(}li^o508qY^8@NWBKE8O)iBmfQmR*0R%qR~Qe>xw zqnhnTdsvvK+^(9t*t|`Sqrw-?;V|TXEiYY#KS}jWZVNQrs-q<(g+fau4S~Z^18QS7 zS{w))Q>w@Cti)7G#Ck?E5cIW_IQi=Nh_w>yLNml6;6tFZT~|BKT7P(1nBGBn3A8s! zLU(sWQcturQs@g@JqMrx4G9CYfWiJJN}0$SjK=6GV!C}D(H1vLvGw1ZFd8sZS}&}g z(uz6EN3`6#?ijcgoDIxShbWsUz<$3m&S4D5_oVeHSVW?_-L0lUt7`ifjSRBE!5P+f0M;u+KP0|o zQrDm`ZdRIjcT5BmO|E}Ejl!t3{<@b^A5DE)j?{c5(Vk`-@bvt7z!~_^Mt6?O{euG3srPOj`r!&~6;V@$l z-pF=mZJx|_H3*sMiC_Yd4eEP|MVmC&L(sdHqI{ygH0@gflm}hZ_gq2 zTm}2{_f`+5k;zuabv9$}KTMxW;1m#IR-pw}L`METGd+;Cs!2#P2Hszu1V-mqzd?FM z;_$!RKlBAPl~Kq)8kE?ploROneK>5-So%S7RX3ET_CJ=l5Tj?lGyQA(0kH*T!>pEC zry+ew+nqkpYn-QyYV`}2jTJhPP$X6l3VS$=?)v7W=+43E*Wyqyyl=32AwJ0t_ssz~ zShJU^Te!a>T7;W-DDV(gHy5|XesmzK-sR?^wOGZzH97XVs{8)x?uU8xv0A<;Uo(3+ z_l)^#LTt(6-Qn)w{%mFBa7;-8bA};J;Pda(+)2}Ip-^@ux^wZkob!uj`Qs%5^))U0 zn?>IYY~{am_^iGim(_=TjK^6n5z`Bf499Qlea-y z8}jLg^p-@~7?vu)>vQUGUwGgy$}IgN(Gv`g>i)j5y|vnirb>+0$PGXt^wrj4jm`iC05xPC}h%dEJOl^dCk=5Y1r(af@s-o#`y(zc&&j$?>S( z&S9iiPLT~CFV)VGPP@!^y7Q=&E&!>cwB$QQBI)n?-Ri{J9))G+{A^B$C4SS%K#(|h z)N)_u*#5`gYuTj971X9Z{R`r=2zY{)rjoupJzO5Byv=YFeP9$f+&?kaQC89sh=p|< znPT-)6F9MPnl*^`c^6ofAdQ;>w?_#t6h{Lu*%1?<6A?Q^=BED!^)2gM=K%kk|KEwf z+P$pt2({q{uY$P;hE*}EWO45Fd5ic3G>b)$6zaMHj|M89JsX>3z|*~lU%hQ-&v`}2{Bkl!tW$VV1`Hju%FOaE=6b=!7Jm{@Gy4>Rt;7YCCH{7F%D z^XC`Rx}DYc8q&30t%z8wvFlY@c=ie@$wsk~R~;O zzbYT&%NpA&=_Z2-YKUX%P!F(=&gRU#(Wt%(Xp9L#KHcPQ(`NUeIHZhDkBoVBSvJL= z?{gOuV;uIphT$*?!z&j_DZl;USfukwSBqCJ93H8B*TVbnAoJpX+Sa}4tGo+SO)ZWA z&%PAgFV#{%>Yk6*x>&mfHaFi~`U(Q!xmlbBQRRx6>ymMEvC=kdZFD-<7`a~^-S4U@ zxs~b{^y9x9-_sWQ_A7CF*{G`F7FSxS=b<$jbD_yX5b6^Z9i`#bTix@BnTq;yf5dn~ zUxcW{ul~T^NIG-w92@J^zWfFW|EsJXxHX>b4Y_2dSiY()VEDy7!v1I7q^-P|d%KOg z;`UG3yj2e=2^^~*Ld+~~$7tgkSDs?(8cuj<=Qw119ZeT2uJOPKM0a%u;e`#(-w&$5fTQJRbk>{`*J2BWn#u zEak;?rYL8-c71Dwdoe<~so}()+*F+acZzN9U%~@gsEe?@enjimx_VGGuw(}HZzHj> zuqcBj6N3OTxN8fo&1NNW#3}%hWxI!-r5uNi1&y(fU9|T2%J`r`D7HRLnT22hovsgV zj;*|!-RU-wd$-@V+|18<(><97S=;8vEb(WQHTx=(Xd551baHBk@KR|q5^jJg$(u*t zgS`Z4C>BW?Q-hkx)YVuKz<)`5TDw7%dp#F=7TC#c@Y9KwTczCw{cZM2B}?WQx9O-9P$YrJ2CONr0-2FlitSPsdJG78bT#n3V~_ z4&&>v+PBQ@e6xdD9B?!Y(PN{=2@_on<$Wd{x-0Kdq{E^qr$&EGVB1!-``3#EgX!Q;xH3gtf(7lJauJ$pS1b zW@G@9C0O2fN-0D~KoO$9bXtu}BKJ(vy^}E{BZ+W=DBv5+o!mzunXB<)GCk9K@NGTW z7Ka(5rz_ai3I&$5^Zm&$N|dZVEweeYLJS|VsbnC^Jmh5VW>1nfcPj%9JvZbSX>KVw zF}ady!?3kyK4JaA=k4PcR$e2LZcZa=gGzBlZ!6j&Sq*&v;7nC6{d2(lF7Ej50K7Il z3%v2i`IJELOek&T5yEeU`ia7(EgZ_xd9dw__N4&+hSz7^m)?@u6+Gr%4=UOIQWFocB$rmq{GI>(#t{dNJFz zeL9_0pA?NPu*=zmdJmtv>Bes^c1lt9<2QqPBR^INc*eXqRZ4_Z=G?>{cUfN|_xVbN zhCQQy7Jsv2fz*%i=Z5bBO)kFG-xgXtE}Y!EN%Kh&##zV&G?P_tm4}UnOM?EngZ48D zfZrECbFE^DfX018!%3DwVQk0-123eBh5|3l3k-yk0pk9Dn|O;6O8N3Y)URowz5R0$&^=zBe2vCPzGST6X8CwJs|5`lw1XLB29b>28`6vkt{Pg#61T)9 zGxvM2Xu^wjZ4f+ibUR{7|~t0J(h__^)EaaB&=$2pbW<3`4j! zwhV5c1F-7gIk^nr4+GlvU_axh@#BFf3a&(({T*nbyvYy~jPBMhF~HQvI%K>De)t9U zr${R4ODG6cp{4%{M*<#z+-#H{9|*&ZNQR`46OR?eDiiXxJrz^H1`bDNw*#n}Dl|OY zFzV3P8$CofB5vWw{+;&;92xqdfC2x&+m509xd62}oQ_Ay^n!~UZh^Mys*=!@#c|ESW3dAP~AmHXvoE^$`cXzGW+t5T1f<`LOr)Len8 z{*$xg1qtshTO{}0OZoYdawzTCMBnm=`wjT{ySSwBN;j%7(Ag5Qy${`PlUg~v%Kt^R zwQSMpHQ1b;8zDcn-y#+A4wTv{{7VGhpH8?qygy~WkS%>7!#jJ+B%kDzOy1JId2IWW zT54h%H`q2ZGD(i_WtQOjnnv(WP@KspTa=?FEdQuNMu+bt9h6_-cX?j8LWUHYw-y(?Japa*1COms{e^-2^}FB-D+h~O!T{)%jnfTn`aTL+kV zbeA|H;=vWEOBokOz(H@#%fB(7u6XcbB3koKWSsQ;h?{l^CS5jEKfJ0t=*YdZu)_Vc z!%Fj;F~Tl{+cNsA>nVG!nqy3$^f*MTc`6A`)dtlNe*LA${w!;7fpDKAnSl|g-Q7(W zW1ugLH4!awL#4*g7A|2WFi)1+2!v8L|5E-^z~}3mJ&OCCWr{(m&BY8pOE$c)yZXL8 z@EuBz3+R8$(;PRAz{~p`UCyYR@xq0_a|G0o$II;2@($f)aEWj}1&(Kf`Pg}~rchyNDWv8Aa9q5GP?B?>>+9@IRsIwug z5w@@ayt@s|1NP6FhIl-Wy9no$&erwv!YR=RdWQuH#k?@B2g&(kVpT7C3nP2Hj^2R# zvQAC8xF1YjK`;XPo4~+gQ}Sm6y5uMN9FV;qmE1M(R#ovfmHb&G-swcE5G%npQdx)b z>sXDEz}$`Yn4Q&)Ho4aZ5um8n;#^F*jyZ)8WDAG=JL{(xu_CgQ)LGV;mQOV7*JCuB zu?9U%!(pQdLI9oN)COYUw&&ta5G41*Gz{g4I(cJY_`Qh06-ZX;AI~EG#`Hu%D(K`H zj4kT63i>zgJBmW5BQe;IyD7{9276upLpMfne|H~omg>RHxldnVwZ!4GsUOWE=j&m0>e*H?Zz0E{F} z6wVhD2b(|e8i63Wv)N?zqhjIT2WjJcp(Y7Ovbm_)u3PplOY<*(iz-Yj$j;$vxwdxX zPI_Z2YCu)ny^htMBkK)pTZ6{8x<$wcw@l7xH;i>)CTkuTR|W~-o`O<#Hs|0zRl4n+ z$E;MX$DVW2c|yzF&HxT4a&6qPe#Uw_fBfQiIf0zWPkh$_0CcFHR}0SA#>mmh!C2q= zzbjh<3pg&WuXg!WD5fOca9H-FZ!eILNnNSXu%MhlBdO3$BdIV(H=vyV_Y;?24dC`h z-@%!=SmFe=sR1p`sI_MFzgfCF;@H9HI?j?aV3MguirE+J*m@{NPeL_pjn%bm4XkMh zp&w6~juNN4V{S&Jw(eN5y9awMak&Hfbr0ba{OgQqT`e8%&pIR6M>+E~sT-RUsQAn& zlLiI7qziRx3+K`bwzaRGo!{(HfBlIwjTlLbupV}$Sp)Va_oEzpd{28Gy1E#`j2F=s z=+i%4UYoF^L@MT1gh?5ZIqp0&9Rm#YU@*Umg( z9>zkh)Dki8v{ZkSARm9Z^~#S~`DN)d4wo31?ni0%lEo3_TvNXFd(FDx&x1wFR2}&z z6~kiM;del4TeT4T&EAg{ZB00sGMrd1xJ;pBn9%0VMA=7+^r)vvwv&a72HO*a-W+2t zIwLn`j5IvkEQIyGo#EC&Of-X-Tvm9%fP*-lAzrEHtd_MddbIj_Nb8soTGD4a%^+o| z5C=Ut-Wjfc)mq1vmLx#rMXC<`iWw<|e;fZHRuf2TUQ=BI|09HGkB?@lg!0RC;DkWC zDz}m374GX!RE(By9%+N9etE2vHVj8*f*0p5U_?$Wld=E9Ail4826`pVXt(|+>NKmJ zdxNkJp^Mc6NF0Kw4{J0O)|H1EbU!zZ*UwoFR(8M5ZsAT#!HS=3GX5x-A`8)B)C6d< z)^LDsSnt(e5gV(R>NY2P3rspW@D8Lp-zA>ZWX~nxkcb*)LKq83D^2kj+v}pTk=Ep?7_~zwd7!2RpdMuZ0!OYIlrv`V@zCa+- z`gO@dWxtVW?Fb`KSk}Cabpd45sb&#!!M(A*08ZhlPy(e$V2T5+GbKgOJ`~PF+2@XI$YZd&zaqmf~m5&^|U?Amc)6l>P}W8|C#=oMzq*zNATi@eMF_)-HE# z3{N?xjIPJ+!B|>bMnUs%NB|aft6Q7&9X4TmzF`N~!`U3+rOED<2GClxCHF$*G_m+M zYRg6HZ=@X-fE-J3)szz~KWIuHm8m*!))PHf>|Ea<9v_s@ReYn4mui7rx-brq8_&(( zsLf?^yA+CeN>~#+&NrR-7q4#3ISra&0)EuXjTY_~UjUl|h1v@}FacbxJa$^Sl2_Nf zQ*Ev3Tla^@z)<8L0j zyD3?AAKM#=wjh*IG$E|HJ6Yn%JFC8`fry0dyoxhsQ8q@kzX(`3gN?ZB1q5k#dkdzXZu`vAE$si= zR8Cb~Rqzrz2%X(HhE?XZP}hU-XN7&-1Jz?A;bxXfOZ*Ml#vwx^^+Z=u*}$KCda@i6 zS!`$)PHx|iGzu)C43TKN^b_T6MsfzMYaFErIuNMohaW+8CuLs5j9=dDsV_lHh3lCf zOp^D!Y@;K-!9N-z|Jp>Nn>jRE=XQf1l=>re9UnNGKFd~sUhwDij?=qmCkF20t~>)& zqsjOE7oNPq27Y{%bCh_i)rQE=j*35UEe#l4KKpMqaw|Yhv_d2(tItM>m!Zg9e2=A+ zEtQw}3AEIwaM*!OBWP&P~rah8NIv+(-xPD?t8Rz{RL zq`+07Gm)R*q_zMV`=RmvOIVE~h}f@whtXNG4;!+oIwz)t0(b%GBR9JYIfJk6h^m7B z;GBp6KRf8WzVIym4^L*TQ;$eVWxW&E=&Z4CVy39s26PEd2Yyh+S6>#>CDQ0p^Osue zmtSfP@_ni0moDhAWXN|zk02bX{L1{M>w*0-=a^S0p*~EpLf|BkX~ITuoU4M^a?e&u zWpsG12Uenu?7YJ$*N-wBB5#0)9}iSHhkpSf_|oOV@C%1V#((-J$&khpN%xbI&k8-K z4!h-#kuw|3uZb2$YnCm-k{pyBS z7kus>ucMv)$bp>;SOu+LYF>={A(JW@2SCT`W=ce4sW+ymtP{3tA z#h?Gegd0pA{nZL}=bM^HDqfY15f_qH70%mEg<(4WuCBkpKLqlffT_2PU+ zgCGk_F7y=SAasyE3giI;s7ih)rINfYezy<%HxfyF?!(1R6!Jt(i?3h%3M{s4l^KL< z(i)An-ps1&jhZp#&My%fC`b%7KY!CEbUy1aMMkIx9}ADRL%Siz3X$mUC9X%O#ap{(Xc<@8O{DkC|cc zx9gK)9jebC;?2GC^Q_`<`@wVYM8af6H&@{%rF$ z=uZ^z5C2^1>97Q;Abp>tjv&71kFJl?Ob0U7gY%W-{-Krw+{m*b-_9o9L+`)(kBYzl zo<3K)Zj@a!Zy8li@_%eUF;=~SWEBzbQ{Oy;d}4n~66k))&U0*z>D5cRs%_%k#mpP8 zEUP-P3B1%+i)iSuBoG(MXiCz&3Et3>n0X%qXzh)zRLxyMk)_s+E=>{|Im(>uvQ}Of zT=vtElE)L!g816)MeP`nUhfZ=I6qg)WFyIuG*w6OWP4}(gIMjy5MASL41N9kYM&%u zy3Pe^jqV_BR}%TpBM5>3+L%j+Ll@Ivo`=|1c z_v=jcvl{VF5h6rGw-(5NK#k=nK_gaJEU=tsAPpIy{$I@HK$L+d=)lGs6B*5&fyd zt63AlQ4g9rY1n|lf~rwmq+c2#(%;VDgV&wg-5~iz2r03_0`}&1BxcM3a%3gTJX+VD z1MYt~m!mGqRe1x<6W0HhTIn#Q%j&8(05rNI0t{}XGamZ#P(IVtH3t=K0lS<{>8h9R zEp0DKub{?up;`xh6?U0+%hLCVAL2MVDi>Z05<7QE1GY&(pgCv|D^uj(rXWW!P$#Ooo>A+UWjOE($A*l4Vwb(x*ZHe%!2-a#zgiF;4Rxu zw*hy=x-`M)#}q=FAFarU*a%H@sfORZbVevDRi6W+mSG6 z6Q_burnp84;fPB9EvwoEeYA}|UIF;`6k0?iV>_>#rb(#6H(sy)`AE0F?{r9u3XqP$ zGMSK1^LEexZ!h~l9t1m4YWRgqfD36|;BUXs0Jp>RB|5ED`p=M&NJe|=$a=}im_DlS z>elkAgTKZ|4oI`p(DEBMLQg2CZb6_N*#Yw&0=i#Qr{*jE4KPphv00NRk}Iv~c=GdA zizUhziw;N~&Sl(whm+grHBOV@j;Df_iUdb+KI#K{^7InS=mpzj{@4PvK%yiP)s8U7 zMCJsO#*x-+DuQbZJta@( zl)z{zX0JC!_SUeN@PDaguCq;ubl+(OG(`_m67Q?VS5bD5kM@m(JNhv@GT}3c!~KLPG0<{ME>OLp*2E#Ex2i;{?(cxUQ}oW7`4bNol`fg47)R)Xx17sfed+58UUm=cD#5duYXbSb{; zs+$z*oM*+=z)3c`Fih>D{dgG2-D^T*mA`J-;KT@f2{XRm7VyHzvoGdAuUW z6|~7Mo+{*cbm!4aq#at|WjX{5up(B@(%sTM8o0#HtuZ8`U<7Rm~l&p_UN zU%nT64+^Sez$Xx@{T6J44{OrE1qL08N@N*lyT>Wj&rT3 zEBhUu;yCTlcb2=E{&81YCP#e9ahahQjvMz$hagE9ZgdA;;oDs0SCDRMKX(Q56joiG9 zLL9gAO>0WNRA+boQr+6(OLc3DFV$Tbzf?bZ(3H%8&8rS6}inK zfI9#>#j#1*x-LpxbF@y`={qpS5o$U-^STr=8m!wnc6E5ntOhq=^GK4MHzNs{MnSgP z49Y0as@(BVrqH$zmE9{@yjgQ#{5#EZR032Oel;h03}n4pv?XYmtNbf--EGGpXpkUs z-I&E>m`}OGVVJs^^eoY{J-NNf_kr*y4 z%~~qIn4%jlfJ*k|&W?q4uO&|A(mrka%1&(CLnW35b`7AveB;ONL8Y_bN`yBUSa#F( z!|Ki1wEh2>ddKienBww+9D+qR8~or!JliEZ1q?TKyMb~4fA+t2%*bFTBd@2k73 z>aOZ)tW`w8s~i9yk6P&_cM^&@02%ukih$> z1qP4DXD!@=&y~>So7U6{%#T#7=4=!Oz^xGuW@qd;Rnkuml(Vq&h z7Zw*fd1tujj7ov{%A7{sv;qNr+-s7SZP4C_Pd*yE1%Dz4dc4raW>x1M10PTpL^OvX zAkcN7i4{I!sxTes>RV~z0nb6vWE-L#I;C5Op801d)Ykbsiv>SQT3;Qj?+IhQ(5Rnd zK^V#sOaA&``zimsRCEwF>Z!@a8T7!Smd`)TgYp!gAcv;t^6|KmNpozVsX!4l5joF? z3TYzz7Z1WrVkdjPRD_VcM!y)~U4CBKZsQxJz(YuWD6-(FpR^Gg1Q=6&%a!o4m9gBm z<<#P$J|UIm#GrWm4ZXb03aTN>u59JU9Qz^%p}tffWaI)1qdh_D_7j#RD~J5iWwPwV zwrTXlmz~rYpIJve2pW7E!qt>s5Q$e=Mr8C%WNsi;f6mw+-xXoh;LwyMxrTi}qdV#4 zBlD7++;!>)7)rHq0N_bIpDaOk2ka}5Ajfu$c@cweqNLHjdrmN#%8AIioeHI9}{(qqXF!#?l6KNhFQ@$SxQMi_JmDDl?I&cG8>zzgBuOb~XLT{O(Z- zea%B^Vh@WuQ0tz-Q~Z5$_+G25wC^(5UlZK=8blrZ%tN>z3Q#uC>%A>3a5++G+6ihs zkau5w{!EntEt!(&B35PIw_@r+zJgjn!Flu2=?obX6H-m81;@h zZEx1d5R@V^2XF^@rX%+xB0b*dXht;D50SBnD22UNJ4xIIz0sxmX+9=$a_t$f-@wS1 zA@9pyYv9bU^f(7VrVv(8yU|5snO=`0;@-XXHJSx`AJ18Aeq`}7lW&XhQcRf_{>U;0E7bbr*)UBLSjY1DJs>1B2&!tMaX`0B6YT$S;YSr+O2 z*&2(=5a5n@s8%t?X4k=XUpzzWucIM10+>McuXQr5$#uYh?vuHk>BW`)_PPOpw_ zh8iD@R_%dz!yhDWL=IgFmAJ6=_FEY_DCfhAQs}I6CO(?1!iFgfVf-jUTWky`$hU>W zk_)vY?qI^g>37QV6q=!h{n09Z&7|%@y}$bC3IOFuO`dxf?-#o$X_R2hl%ORT+Ld5{PeS=CN2xe~N60%E}4Dos{<1Ia4cCRJvM>p&s||%9z<^S@e9cE#n{MMAsoFh z2NK0C4yH|>T1>s3eT*shiC;AOJD1E4k%$IjO7DQl4=uRc&B*i1W}TEtAFJ*w7JW8` z$qt8<+KcM3G$EnSH^sjV`Y?u2#6fq)?}k{|#eG{0y(r)`zLQ13oN$(F(ub@?^H$Z| zU%K*VaQDOo6=hR)LfYLgjjPvnUWN{*lp}e6`2>Ob-n5NX-Xn^I?F2UYCcJ`qaWB&~4TC z#?EUe-d*#4+WHkA`V}QPnqcS;bjgj6WVRk^t7olYiG8;gxd*!oOFm8G^ZJ#_q)>;y zne7EJyfOVH*0|nEaunSMf3CeUYr~QP6uST!Mg+Hlp->7;oRS6SId%i55KpNTAFsDn z&HoTszamQBWN<)*#_2;>=!W{SjS;HDhe>KYhBv56KphT$bhK+?(d#;e0T@cm6H=hA zQ)zyR)<0J|7E9?Z3LbZDsQhR#HdOtA#$`{mHrHX8VDFD6TEof=Mp=LbJ@oD!j0g_^ zyB4@=jP!ZtNB$^Oq-)C@f1NE8fQ&Z21Qlx>sT~gBW;j=IR+oSZ%uRx8Vq$ z1;YMxGOUXMy*bQ9ZFF^PUuZurcRpX4@XoP$8x}}DdMGHjU2NziW8^rzBqD4Mn5yp3a6wEJixd8#9uD;1Y?4dLrde91r(?OH!!Iwri-SUS*cFA)pXL15fID$()gL#9u5*OtpdM zhr2X2@&1_Etes+9?P|CK)Si|xK6~u)8>O!4BL5nX<7ONM{44064Kwnks2dwVFj0v6 zmS0f6CMn_jZ5&O9t(m3AU8l>3Jl9d3<92Ba*_GZa8v$_yKYA^!f$Oc4h`4Kp#T!wi zTdUWBw+8+K$xyX>Bf;cBdg?0^w`1zh+boM>*1vy>E|EQUg|@yv6b}}iNU)u*=xDGd z!~8MUhA$k$Tw}6}bs+qWiX<`sjGP_-hrG|J%J%u2}Drt!4Uz`B9QwSJkbcgHBZ+Q+^Y}5 zJS5CeKT7(qJOB!jMa0_k6& zJ^yNZ?rlrmb$4cfN^+ajlR=$NBQPuc`Gz6npCO*6t}sv)C}E|Vm`>C#=mtm7z^N{% zu8^-dl4&%!{O8_Lq8Yyc;BruTXU_@vsbvv@-b?4DayeiUc4g>qX#|nzINt=3pSrm8 z*M+UhoS_WDPKT)mO(Y6>KfV%m0bdn=@2TPoV zoHS%mD@m1F%9dtJM>PXAM3*TA>=KBXA%!pLe?|MJ_wSzuEYW){jNeurPj;Ir$Fh&Z zsq4017<|rEA5ekMo&PCtH3dR#G+(6VggJq*DuVI>N>Nkc$hJUD!3?X1W0N=Rg!0Z? zBe3ES1Pc>?}t8OMcyn&fJ~gJ`);9UwNs&Eu!KLk77YIDwwR13YqL5r>XV zRMqZyFxL*+<(E_Mp;wtnqqrD*cj8yVIoDD^Bx`f&B$Nz=&%u{GnbW;|XS1~Dt;0@~ zumd+tAIn)*ki+_tE2J1jh(PXuPu^A0R`x1nEpuKrHv}MtNNLpaTOf51TbQ6Yv_BO@ z^DDTeWkJqgy08)88D1J;#Q_I+PT%Cwe@)*PrSh4=_qM6=g_>5_b7SCQTWGtd|L*ar zF3lXf&aTZGb1Xa?^(Z&-=`mMLZJ`M5kMr%)QR1$$vwpedJ21HLcyvp%k;_;j47i3rmo9QH39o1 z$+L5|yyN2g6A}G83T6_^d(Y> z&r&a;Ll7p8RZD7OH$&G51-z<(&8+!|#l=SWZ%z)K0}&z>P#^wXk$Z znKcioC2?E9GbzRX$3?qoKl0M7lgo%Vz-28vK3hh_dxaFu=})JT`4N}&-^gur(G$>& z1`%PwRelbvt&tgLZr+AgSnKdCeCG>a?HGTf%9%woC3ahxgD)gNM1(8$32Pk)O zzqRAyAvWWZwy?PzV#BW!0K93UdAkhrSO&Z-+d=#C+QwQf#&@5x({6ISG)TGD-OX@u zM9Il^q)A{2X@_|;S*&8^NChT2O3!o@53_MuWW8RjK1X(|^C0(wsVtIIQu95QPeNDB zA65(*_-pY&9VK#tUnK1IpXzF z2}>KO&&tr)4MMXC-=$jopC?zF9Qy#B(kk}Dm{Tmrl<2)+*uuZGH^V1oIB?Oy|7vw* zbF!Hv$a;_N#>v35#@L|1+(`0V>VE(8j9I4+Grtizk!)#FMh%5&2oWAc7j>oE6i981 zGvU1Pg5k&aT<6{t2B1NI2Dogj@YMJ-66=+di-xoF6D&kJp!L>&YMBxj*v&<~Ockyr z|KK+!B7Cd=gFS`QS?nB3Mk6pPr}QRW{deQ9{A%4TFQbym#1lQtJM&tWch|bKp*Rk7 za^>860z2Z;V-R|1|4v?ISYoLUEFBy?q95dQ05qG~()s*3I)K1AUvTI3E4M9XRgRg4 z_?wCc|MmP*Bz@seZ}d`ihF{$24>liMTA}pdf$o`oZ-Z}ln#|^Z<_daz!NYMNq8yW1 zHNY?nP3W-a`rLmAjI+r6=JUE;RsV~C7D)UEBQ%I3CfivxQoQU8XLC=&&r+3uv-wx` zT{VHC_e{VQIdR3m0R3r}W zdWq29=0HSRr?SpXfaC3NO3E<|Li$|%@QZA#{H#M1t%=JxvErXb8s-WAkbAL>0 zg_R;RSGBuIZF}`&XXxzJL)-uTGU}2v_QUNu?1u*Um-gO+3*l48)G>a7p9R&y(&ZMv z-b<|c`wnDCK3$d3!I_5_+q1^p0u&zK>$Ek>-u?z4_AcA^5$j%`o3`8c-qFj%4sy!4 z6S+gM2AA!uw=wI3#hm}3noA%kK1)A~A#bsV`CbgvdGw3$I}uVSlcFFNX^ba^#NwO@ z7!QKqUrpZ2`G#z4+_e6Gj^ugLNOu4KNAh@+&WG*yH+6FfvNb%OC=lE{)1{4FY+*~3 zhZ8@o-H}-(bh8tr!-@~5cFTxBh#JkbOJjLpg7IP`J&L;KJ;R5;MCtu|(f)lLPA_-m zEx3{_YGsk@Z*4JTM}*8^amtvaWeiVy-%})7dohHd%$Idmce=lgPM3KIQFxP309>13 z-O;0Lj!kIjU+=F`tVJ9`J#QY7HO*W7yv+qZDzbt z@lgmsukIG1PG98%sktNo2Vesq9R9xB?J9x#6N;Ka$1adtUMZm+?!m5dkTi$nhF!p? zrN6Brgd=yLH$c7xv2ghi%ZrJaK1RaQ>s_b3BJoyvAgfkQ^cZ+);K5iKKR&u`H-88C zS-YwBr`%W>;xhw}XyG~r832v{gUBQ<8aX4ClarDR;GSVNBecqQB)=&F6@%WPPI`d| zrqgy@d-PLIF;>e6QeVwMAf6neJTKH|H{s~FgM-TN!6vZ&my{y0)6RlB2=7RMj%E`j`^rHX$%TuK6+5rAtoH(k`434&;U@sP>Q$(>tOR9L^^3PxEL|dT~^= zcv#rc59JzTgnU49BY8@w;G6rh$&v;xjzyNwVP}bSwKVRUt6U-q2!)99CB*<($$(^? z!UYTLKFFCJ4yv(YE#Qma=EtVEB9%8x>Bz;I>97c<7YpTGAjkov@T_Nm5;J``Lx zHRwS~iwLD0w=E{ZA7v49fX4!PoD& zWqm(pC`;*=;%#^^+J>Elh7lIlfZ^(~s8;=e1$JFZ_S9u&$as*s%*c@vx2L)i`EJs3 zw(;D*&k()Hh@%(I`oSpuQ}U()UDZZi44J9l4WkhSUVx){lT6^73q>n&7%b)W)(`Oq zyz0VgBUye{ifYs7JsANhz2RiYtEbo}P(2p+t*8worGQrUX<(9fb zft$#5YXC_qoFTLD9tguRV^)v^s-UjIY@IdXlG!!XSvipY?MQ;S@0K_P9OtGBC`Kgt zKk{RT3ur zDA9v9NnwviqhKRG`WexX6$Fy*Smiw;BG8HkXgaw@j)3ICBs6?+#5Lk3@WC`^1zX{bAn za8{i|R7_A9imu>x!7bgO99L9WYl^^=CODl=dYK}~dU&KEIIhVXsK_^NoxyC$o0L1N z$yjT3y*V z8+yPCbdvamR1(FhURSY`t-#7Q5ALcEA&i%z{!`uk^wA<-IPx=R6O%Q%?3jacR-IFQ zP&QKLQ^jOXWuW%?odP0a(<>4BxVarmM4+s3?eed4u%rj}nkCc04R}SzjlW>Y;tfC$ z)3vqBP}HF(%0SqK&2o5ecMc8Q1F@0k^J{b-#E89;J)$YT;fO5H)WHV%E&DsMXyYtz z@-iB2LY@?KYOdTst*1bhh-^DxuRF>eH za!d^~g(U?fq24CY7OjW3;5Uj|1Os40B`W3btd(s5+9llhzD%-Wp->;K1z4Q=%*aNk zoaa&al6;&mcu5jr2i2KgTzB|RAhy0JaH2V5^U2E%YU>j6X2cw;;xlEg8{(ybGn@(k zX?x{=+SWX_oX}ZQyIAMhFr+54DW|7q(pBjx@^<2s(ZSven#UXK3jheC`SoZeLI);e zG||!CQai7nQsqvZR<{*r)fsS!8EoccWmjNatr(2VLaVxbefr9lefiR!*$VXq?|AQa z|2{ccN0f1MQ>ISy##RbD2nvNicXA|<5CpCQWTwNv*Il*lHe*Jkr%}~zF(=FX`07*wwB1t zj)tf4JN=mVJ&ed=<&kZKilu8f(!ZVF#^VO6^t@3(Ies2yeiKK@bv}p`oN0$*_+Hm6 z-p3H{B}!^tA*?HZyiR`}iT?FL#;vixKMC7$(cWhw1V9*2x-^|GL?%>v^G9%vf679l zX1@=am}*y-d2{30ezeD1$zN!^@aW#4H9a{?RWHY?V{1Im-+phCU}ynLJ;x8j ziNvW8UP+YrJGEwks-6*-@JqV1lk-k)KL2O}hEs}HmBo%zC!RY_!5Jeoyc3cR0v4u( zT?7k~0?dJ(3%kJPgq%5o)=X2nT^WI3v7egg);v2h3eOMZ!33ktLegK05ahsf^I|&| zxqwQYg1IJ!#F15eiuZ)NfS%Iu?+)Xkex0uytZPN0y_hBEZz^KD_YwZ3_i;Bug<|!n zF~tq6oT2pV_xcAN=PMZbe{rYEna~(Xzn!p)#1 zaG~^E(s{}=+G0g%kOu^SGU1F02jUPCWh40BkwPfqkEcUA{u8W|oroT>mRefF=E(AH zaWGSpr70GsQK25rBD?N9?he!LS2td?OJJT1FYfVtIR2bkst)VT|L@Sz5=5X`jcn()U zJ(QcK;ZQao70J_E{)srkQ-;7yXOJWUhZ$}(C>s+9nqY>3fJ-WAte9|vVN`d+4kf-V z3*^*eLC>z*)V^EXhpuLQqSQ9yrl-PI;Wl$dAD~J4p>9OzhOWEhdsB>A1u!RM$p5uS zl@gmW3T4*O&Jdt&aW3PoqabU|2@g;!A(XlwUYEHb)U#V^}tb?t!cZ z^UrB>C>U$565-jL9Q6cqdw^$6LA=E;J1sArgSbS2n0$$G>r#$l5~{-m)}JObmeCrw z@$+zvE!-hS%f@V~4KREGHJRr~uh`SIaMCTj=rC2*KKZnWyyi!=TIzMF3(F>;nz9e# z7i0eLi5Au5an5PP>hfR)A(=9VwnR4{Xwr*edhnK@a&`ny&qRq{m%mV_ZMzSg@;bKPD96B2@0Y9Xw zx>@R9)_!5o<0bG1?QM}28I1IkH^f>8Sd@N`+7D^&wQ-(t0uiIc?WJ0t%uA5=Hg?QQ zC5~ok@BtVeiel$xcHR_xtok>$OtrQIR%f!2(ThGw2Ti3we zJn;6b(f50Qy6E}T5jdGn@hrI-W!o=~qn-|sq&W(QJtd|Uz-nH#-QUZac|k0P#S+`f zo-{41zKuQ*2i+hEjgfjwPn8mUklnw8mbtJ?^frtPvNu81h&d6G^Lm9B%)DyFK|5U{ zRLNNn#Jpsz?q|5-TL)H)pQc#`lJYHk?Q0-TjaXVGxO-9%z)w@&FCNUkc6s}tJS z%5!a){Cb;p0Dr%a(}(TQ;ikUvnQUk{BIc#{!yv@`DRz)vP4Km6IiAvkN&kurdPX3J z#R?1ECg`042Z-Hifrx7g_ojmRfNX;YoN%*t`10^Aa+0xO|Bc@WU z%(V_Th(}m1*WkGylzD5%w6dnZ2|HJ32A^ENoC)@xh!{m)jC&&g2xZ#17ZtdQmvw;e zup@^6eiRe*?-(HMHLcSKMA@_Fk6u6LOJwFOG+7RK8N18Q7P2XVG`V&d)<-q!xN}O-E37Y=){&U@aA}Lt2_;-&yxd2Ca~aJv!-$!SKO^pRjmZq1k16s)_#tldx`ZblWt98e zu$S*#^b;3c*F7ToeRj?V`<(1{#G`Yv=ZCD)2VzN!4@a_ok*-i7^UZH$y%>x7K(8fn zi#irIqZ53&3+Vo~vPJySfu-t%=t8xD0$$`o7b^TpFAV)lO{8TuLXrz9<<|}aK9 zGz$##QgkSTr2xU~qlwa8h0@eJW4-5*zJc_^F)n!Da;7q-VuyUZllSWE zwO{i?az9f2*)p697Wx!RC5!u5r(@HLW%8&N^+-$@7ZK;_@UW$(R!Y@`&CpQdAf~Ox z@c<;%g%mbuI*Wsw86NB_>_6H2Uifczu7BJL*~b-yjj^fRQSX~VW8w*okRd|H`-ubr zYe4k4`4fq_>}!<;hR5U5J&>pICrz{yJ1^X&jO>|EtQV5&CYwbiUTKs%_^`d^KEjBT za7*Q_sjz-6N{rBRbGvHTDko7SsV8=7hpCXArh5pXkDECVDlAn~*d*eLBxeNy5^m*<{goPej>JY_Gl{a=%{jk`+Zgka?BuiV?A;vxz1_iLO zka&7w&8J7hZa&OtHZky5lM5P7T;qeWh2{HbF5nAH-_4<+ZckASmS{g^cpsiA@FdWG z`H>Pk*5zgjHZ^&fb<$;>!oum-ZU=wp@|gp!<1hu(6Dxkv#CQo%JK17$o9b{IF&ySc~MMq5an zPL;ejQsGCJm+Z}T1@aa1em0Ff(qDKV76^lvo0AhwJ|9>;Kh?X*zj}TMZvfCu)GzJ8 zJE?P45&}*$RGG$FBxj51}QkBoDZdS(8x(270$81UD#e1C+vDWcLV35#TqG$ zFqLll>!Ks3Tb%$a6tHlXNXz*7xz3TKvv5g7KEs>Oetr{da4<0hK7Owi*<3SdCBv?Z zS5?Oy)~RRRq;4MQ;ch%kZGcVOPKcpDD#ESmHM+}zxr@+^Jl(^x#oxJ($HOH_GNON~ zwr>wZ>0e&0Rd}m!M9pF;wwDR}gy=o&Lz)EI3cvS67n9o7-!OMzr}42i_VKGNRBA+) z&$!XHK6VK1@H-#Tvu(e`dHL#AVux!+Ys^~G?TIWN7s4R-$m2+hmjSJUAP!{6Q*MMl zCigQven4JCYqWYuSwcEAe)RLCok>}O3p5HN-H|nk1 zF&CIUJGt=4dHpQd((rahXuEybzy5O2-PS|H^>2JW_rO6gTbGId_^NQtE7F2*pH43- znoe*+GdSU6=HdMySrTBCrA=2@$}TWPr-t{-Y$Pf^+8oE2yP(4#(@v002VHC#r(UUE z1=Un&IS6h`Nk4$SJ&zZK-Bk!v!eLuKCLaijpQ=&JW15miOohg$_Ym1%HnXOW{z6nC zI$6R`HF&_EB@9K8xD8J$X{2rDj&O15Ara^Og&h^0=r zfP~16cP19laRC^uN^fN2c>Q)$a4=Kcp8O*k3`yKR^d8hjOeM*tj0DgZemz-BfRYrU z#*GEW9^rpuqQ&)xH(S~WtLkfki^d&sCdo?Nj}fSsOT|jzy^H}PlmKh!4lh&Wf4V8a zk2y!kby1NwgT}Nb1@aUqm>JF)p+^+7-Ly*1>4B^Oe(~TnX&Zy7vFG&iOcV>|PH{3y z&%nV=1P(UjTQX6Hx4AA41Dqsgjty8up1$Pg+ep3n49xzeSokeO%2T70{eA*6HjH6P z2Z0_5&&`3LNjTIjj{etki*{Z!Wzy$&%F}WDpXsp7+hNN%&)H;DQ^UsQ4Kh$c`slGkre41Yt*? znoF=+UjCp9fG@VP6zmS%`OCETEoChqJlnKQdGx|ZCnV%qQtt8ON7g={6xgOdlZttO zaD6pMig1j&P2m+Qo2ett@EAx5X;!81-QdYM+N*IY)jp3r$H;HmT(<$@+J7SM{yhB0 z-b-{8{RA15T-#oMtOx`|L+3~k`kquA+g^Ss>3QHQ_re$3aTlZ%5H~FYOSiD3suD%r zJfo`g!CDQ}`EHwd?_-h|ewxrga%u9Z`ExS0%nm)Y^{r&4Pv2`~_S&ZM!e76c(= zy*Cuc@-S;(6o5iqV50%-q)>O=P~faviUfTM^&gcSV56KjZ7(7&O9sI;d~_BWYxtKX z)M};NX6Spqd)C;*#5W_tsOpeouRrFFYnZ`A18H2BhqWxNw5uq{Sca;-<>lLXQK5^Rd}LLu;Ee9o!i|EL-n9NSnU3ZHs}< zSMB->KdkE6(ai3)1NcD5mB>hHx4(qymi0iU41D?*NW`B=SU|_(@%*1-z3HU~hCoqR zuR}&r4?BIc+;QYVsxgEO*Zg~7uA|2%7FAw1LNRK$bbQ8he_>Na3@h2imhB$|LzGDk zm22in-9WB&Z2JbWsx3|1*9Tr*vatS#DgyH1vjG!PARm5O-xe$~;9es+k+Z+0o!mz{ zuvt5wno*=c-)5C}ZDDxD{q>(;m{??@t=BJxWsI?~=UAExk~c?l?!@tes4z% z@4g+qo;S)^>}b@2X1%Q~CNzn1X>1+Y#A0iwCIjDa9IEX4<46_RqT48a6{6I>oo>Km z492$Zxydl3uZzbQpsAc|5_6TmlB)w%tC?mZY%A5TIM%(chgxrl6QV1AogWl= zZK0cgA4dNO;II|SHe>AUeaGNl*0h%&w~{p*_b-%hKLFM{zAG>WUB~V3;9YqaHS2qb zYOfG?Jgd}-Kz8Iq<9Vyp_WF-50l_1;n2S~R=u|X1Q<@6ZEp1tk{$bFV3xYj9*;oZS z8EIOzcI3-%3)s1JLIhO_iA}27YSxTz3%r#;$4b0xz-_caA6Jr>{*I=_rb6cX^0m|w zk8S_*YPYT-Eon-f;U=TtiP&6rQt(IZ)`nN@i#>jg+;66z9nn+W5w#F<5z^{;GE4Hx z7%2~DhH{kKTO$Zp=gQhvNw~I0%xx4cT_*CC?OcyITt5*@{Hi#WEtgMZXDbUpmM3>& z-QDYl0e#96G2g6>ZW@=QbjZq6-tV{g7KgoPe)f33;MlpAM80=2N0h^$s8A_$h85fv zKR~6^krYElr*g}TkZ6wvkU%*W5FsIN1=u|)`7UcIE3rFKu)egG?F&1kVFYr|ypwPX z7tM1Q4!r{DrfrLBx*+F&iqM5++3V9NDn&{10p#Ffb6>|JAtOTa0+3r{(YASoQDADJ zJh6BolF@R#n@*5I-EF87spkh_I3bE)V+5u&nGqXe&+5b;N6-q2iZDc`S;m%qEudBH}Ep##HFbqB*x&LGC$x0IuW3 zgqfG=StCIHw5zPoz)F-@R2gt6(26GTd(ie404!ZPHkP!Su%&;EZD=>l@oezL}!mks? zb0d%fM|>t|x?9ou#GisR0>lKzv$7y}`(j~Q6^Ls5SK}b^2^z1O)aCA^%nj-Utez2$ zmKoEp&rCyGI1Y`&I&tht# zN!nnuNb2P->9OoL`0wCA8hl`fh~7}ZHFG8;PadS4T%LE9Sxz1xtq6~fT}#MJ|J94$ ze^dVlXuDh90K3o!27i0LfV+2cw5X&rCdT@O6G&$H5}m8*sb<6P{Xou;S`j0du_CGD z$AbLp2W5p}@dz*Znh-dSmUHRD(iI}h`ikj+EUq}*bohQe_~l~UoNNgRopNQ&j9B(s z(ysuhsxkcTH!DC+0B~b4Daug+8{#u4+QR-0%1Is@*n^Viq8c}S!vpqJ;WDg>^j4mj@1GH_f!;J9rOz;Q>tX*N(D zhrT&iM2gOO@}r8{2;K&y*czhDV-HE_sut6fIVS+xR^x@sGDGU=ZQ6SbwMvF%EQiul zo3yb5NCW+0g}z?VLl`oNo><^E1)_+yK>m#sB$G+Audeg`IOO6lP(X@XWOl8|)4l1-6ND+?jz`6n0>KM5LgOkgicw9x zs3q-RBR*S`25R=VMV~^cc4OBMwJX5`fKSJ|tczpR$N0(+`}^aZ={mb<%D?ej99djb zeHO-|I%C-5p)3HegzZoAJ_?t(Jl(Ct4#JJZ4xIzK^SUs-Ap*g_*Y}I=oGx*J3K18s zh8@tuO}&yH-o61%d(p}~lery02W$Gz!RY^UFs%O^%=bSB3(bb~m}ntx!lOkNybnjD>RA|F8W)+iw_5I#E-f-!ON9tmKZ9F>ju(F(JS_am!IA#6H=B2GZs5a7r1aE^t={Z{ zs3Uq<6if_euG651hkCOR{;9kS!kjB{1gNnR&$K0K75Js5m=2CwK12Nm{-xb;eIllu zLn1672MVJ=h=!6C4N8go$IWii77h)?UY~+FqGh;BKPkPM>~^4%(=PNJNa#bqdJ3d+ zZYrp!_8C#JVqVxtSpy)4D5xuUE6+YK`Z{b3OY80fhG* z;uNCt1nr_u-0?A&o{AQ(^ca)fqgkkx$x<}zLbmR{d@cA&?^=3{OFgRbC*u~g(nVK= z(yAdT|D2!(&`~WCy*7m#$s~n<*SSB2A|QY_g@-HV!jfolMTOTR&40F8H19hbKPe(S+ICv1~^kSjb@4Y zYo^#_?SnIM@(AGno4XhyiAJ-7LR#FJU0VQ&YaoxnU^)$QdaMawxp$g@YEExFq1}_sWFqQP+#^JA$*X8nr%jNS_q!%(f^RKQ*>K z#Y+=Z)+$DYKYS@0Y)2*U@t#M}6aD(rLnx&%oU}*RDw%4!y<2g2f z{XBX)nuG>m%2t9opE&*VgHM-<9!Jh1`%Zp#!uQ4V_0=6t@jmmNu>8#U-xGXJ&HjRo zmcD~$;Emh(PFHM z;=o8j{b0awO1;KIRo%Z6ur z%5t=_VGc1KVya*71Q~}(!80$jA7Jo-vfLzcmMwVW+=qdIt#}4SZ zJ3{9rI1R023ve`s940OVO2P|CrT*WH%1<&#gFJL=!zzXtm{47kj;jasrte8V=thSjX)*H6FWh)U zlfULjH{Q%0yFxWoOzcH)wNwn2)sI+2&-ib~dw!0KAQq@gI>7_GY-1*{+G2RZMRbP; zYA@6If7*lN$ITlc0u%_|M57@jK2A)HR$z`436WkOtahSLrb+`gBP`RT`-YrXBYv-1 zkl<)tt_l`{af)C;oD?;qyL`S93F;)B5pj?BTRlX(3S!jg6BNmiMa0vFmbm4EF%*xC;>Jtr8Zi^2lVNvFOl?3-moUA=pS2;#8hIe z3o~zO0XR(`8hcnbvrHA9ig=qhSdgNtMS4^bn z?vcy^R_-OqOSCf_@RvdkrxuL5H-wC#hEkkG9;Ng^YYHfn8BnH8`Mi06QBed?sOA4F zRAd2$|3Uc}Dy_N%u)RteC^IIo*2;sdSN|X8&U62Vt#6L5 ztP8$O$LQELJGO0iY}$jKMzXvmT(LBLZBZQ;%Wj>^cz;JJrm9TQBi+Sos{-6iw3o{A9PvBHdPdMxT}z-_d!icgAP6!?GwEIdzf@5y7q|{N0a^e$AJ760qyAX{Tm*d^ zdJct>`6cKHw^*Z=_ocP*(8V`V4|X3r#2(g}Yrgq)z%=nLpvA>mu;fnRwE--c;$>m> z)jFdt!Y$k#mb|KtUV|nPNP4)Uosy{-K2c5ZhWNr-6~iK)#qgRQgyRy=9+f1!6RafWBHmeHuRwK{H?1W|> zQd6rA8FJo4C9EhQ;irAqI#c@ZVJf@6W9k>cA`!K!LKH&OKk;@A9Bc?Dh#`bqQ^TzE zrY!8PvxcUouCPVHv)iwZBio4n_qDC31|katOGRi}JwBa)p+2J6%enKXk-0dkp-N!G z%=uJ*v6@pMw6dl^OTEVWg9452Fy;FbGr#S}Fsj@zB|80ngLjqG0l3qP#&I!eOVbEo z7<}5+k;oaTTz2cIZNPFJaDTLgd~WpEIfKZybyZH_OHpwc@C4PvV1r@}3%7om-)8#_ z|H9W^eti5@G}$a29g)ZZzT_bF8!h>U7nMg$&!+8)+W`gwgaDbYMb4Tnl7N3G+$#*d zYJ?dq-i?@~BEqno^SxC|W#uB#ca3Af()jUv_k{1}{D%sL)3l9=;g{{8z0Q-=Zbh!o z`S`f;Qrpst>6?9*rjbb6!oXF-wQ-AcSua2NN_q6I?^x;!0wWW{ANl-m%5=xyY2Otu zGuj$L4c&&m3A0_12xuz@=w16HJ~mIP>oFv;pD@~dm}$Nof!W?YH|L_Lj&`d6yj&h* z6&vWEy0aTWIwj(Fxunw6 zNH)U`*(z7N!qzdTsoq}Dzsp)`sWlNoM|B};^Bq=P?jI9iZb7_USVHPlv&$hoDJM!z zDS|aaNdOw_X|ixGmLtzB+r~ct2c*62sc};=A}#6yZ0Wm~yt>-~=y-CtZ=Bg-s*a^r zD<__Zov=4C%G5*TzwLX&hT?v15)wob4}f;bxt{Mwx)b`ttRyD4tOTq%?wtLM@cg55 z*Cfv^*!g35eM8?R@Nmi?py&H97K*nh_Jv%lLj&5mN$qk|7W-^Ww2tLFaEzJVx?19bY^~328FvR<+ z1;n>pnTM7nLZxL+DjSV9{oF-?8wfB${&(x?poUb|!ZEAxG>Wpy!}W}+8i#DbU~f=X z_U@(_C~b`Mjm)w2TM2;GaoqN2ZQtWJ zWOW(B7qh#p6#qB9s{;2xBV*INk!iH&ZZo%QuwCG>VtFZ!dKdx302d{}Ue|=ing^!icyq$+@%*aEF6QeKy{zt#f4TAN81+%o-d*@(^MdUy74Nsu zrsc~2py+wO2=N#Z1JPkzPDJ~!aqyC4$GH-`#@?jKy z!OWG-<2ft1irSuCYg(CU?e2EyX*eVeROx_J;Gqz}L#uoLfj1}c(BjB{Ax}Jd#Lu8R zs&GSzB0-3B1lDpwUwV-x(vZmv>}aB<_55n>3`J=y(>|HRmP-l7sIREmcIV}q&O>bz znaTavAZ#7&qThr(3zk`8FxRxwgAZ7iI0%eh`3zs^06VnGCl4-@GjLf7;2ZcmN1m zz@^TnGf?#fFr%L@QSK}p#_&s0fM9J*H?hsFI@+8&`=LZc+hy=O`K#nB%5%-N(KkP- zdTN}L%|{A`9V?j6Fx?A!Ot{tCvjG}C-+Pj)YEc$Ti%!4$y--ZFzqb+9jTA7t)X0fR zHFi%7-&2vZ&(IG=VY|xjmo6iFbVJC70g~4}vFh5b_ZY8E+131pJzb~P@e@lO$h88q zg`|Z{q$mVg($q#`C^_^g%JXTAIpA~YK~UhR#EnlrrCjR<5w%H>OflcaSGZG1r!q%W z6z$)j^IQDXz$~W0B3I|q>ppJJh9+u~!K;w*t=rq%AresO)gzSq#p>JFs<$K<04tfI zB5xl{>+xw|lU!S8AL7*LAC_HGt}4cvBAMdY^K|nsMx(4uvE{65LKQh+L-10y|5gnWEwjuZda#AqF%;3&KRF1q?Y{d4KHQF$U_8B^EzTH@qniu8`}p5I z-XBWd`|+bMuFn9q7@w#9$)y6k21ig~zJIU{w*E6kQ?cm?ZvaSSDe2?5~+-y=+{Ll{FTK~ zQ0n&=fpelF;3JZYE<@0x#a}y_%37>o6&c}|$MhUyyPZd$k!HO|we|ecs)L-hTQOii|eOv&#kyf<>7GKWg z?!gQFkQR$u?;F^(-%yw!%o}Ww8>@g6QpyB3CzhZbN36!{jTflSpfX(dBi|XWyWplN zMOx#ynVEuyn73rP?F*n)RTjlxg=$=$NfvUA(VC97;~Cwxr7EYaQ=^HO+;-TQ+_kmn zx$M@NO3I3CkUt9?nZFpL9!)A z=3T*#h1y9xAurchc0j-v&=qLix~Lxr-Eb|f;Bh7tU%>D*=22;RqR7ZKz+$XlVFRA*`sOnvKLSWsIui3U&CiwC#YUG9o%oIQ^)pXx3aLg7JDAa6aGpgf0 zjd&aC+0y2Q5CXjA8>6?o19M$vh+XZRITNCJIo?r) zk;jqAOf7n;2`DAPMYmuH<`_MA7VP1<(x;gNp|hF-`JYs53?bKlTwS# zokT%Wp;T|08@#!{zN+{&f%})0cSqum@ipu^o?aaN5b)esrCj{CZt3_;p8Iu^3xMc7 zZVJ&}sk2kB6fj{&g*r#LQ#d}o^2kY_{aUUQZF@@NGLqdC!(Os8^zGKAENWXEaP06Tpjj>_ag|Lc4BWAujWl5D^j?YSq zhyB^c5{{p7DZi>>o~h$i)%HxiIm!HQi4{kCO(CRARtmvV-;=7g3ePktozJiT(7a)=J;-E677gHR8?-yg>9_raHj^!ouh}1yziph=9{(>dKcF!k7_h2 zD_IKCu2n@^NS$+#x4`VpZau{O?PWD5jLF<;#g73$h@K*B2yKV-iip(b+pJH8oe5L<-~Ns|!MDGWs=|vmTR)56rgodTpN(d}`+yV0$UZnKo5|TD#f=9F;RzOh zzl+c-mbLoLL7_{HQ9Y&Wr@X<>NwJ2E^tPvX5k0kaO-IVpdi?a~_!fX)YY5EEr*jzx z>t*9(trfup?_b|fwVm4Bx7XdKi`37_rTXbuXUuY*y0bB=4q{hCgC~OA}I>o*oY51s${QMHnn`#uap>~n1OmawgITO z`|)Hcc1Rfe84Cvg7<998qC3!3!WHtY{>TYn?Rerr zS#-a|6t(}T1gdC5EsOE+AU%hJV)x+nz4!3`8Y3fD86)!J7kqhr0Rn{p5GXE>fk5$Q z_8(9NY6^C8L)b=2Qd$*zue}M z0E1G&NWm-!?9B}v4mBN!J0!s26s-U0H|wUz_rC(a%9iiGa>wk*7Rh8z7T6cccWUh6 z+B&z{oIWyQx(x)uY1j2@KBkqEy`0xBu z0+kT*K$Av6uUHb4DcZ@%e#4g(ZEOJ1B@LTN>Kj`t@8g{&!UK|Z{8aic z1Puz*jOX2AUHs@RJ2+Jfxrw!kiFItj4^ORT1T$QlNQHcL>B6?SpP~P0N~fztmq2Pl zF~tL8HPL^u+9)tqLxlkVgEg&x!P zsI>hTTwcSL>3h=={G%w1C_1H9*q1F$kTqJgt9A4eg+)y*psH?4{2kIeh14&y%D4H< z+e)^K`_oS}H-}o4iA*{(Y44j<-P6Af6liwOSDM0M7{aEf?GU#fxI!)YZlIO2iZDNuy%KN|ltVN*+m zEF`OPK=yHm8>P28VXZC#q<)$|pdq$RvjFha^Jk=oF~H>^^@-!nQrZOPU~0J}W} zY)ZVkEU7!-N6>V;2CAU-mZHM@RJR!!@@wiRSZb^HN7BzS-FBtu; ze&NJ;?|(}VP@t5}oIL_67#9;Wkf%odpE^xDvfnmPo#ute%mcmn0kl!fsa8li%k8;% zl#8mfnO)65Mq7J*^ZgAWba5(0eL2B0a<6mNIN@~^d-}Ts=}(s@Q*mx3+kJ z_7%&R%&Fa;erOt9|04&rN_cWHiFGL27wk(T6b)AhE^N*X}ST2QLMUa=%BO;o1HUGiA6Chk*0!l-kqN587P?KRzm>QmW)|!^3@(<|;@&w8R-Z@;6g1Diyguo zde}v=fLEG=bNQ*RbI*dp@e`^B_Bo}VHc3k^7Fd$r;4w3%bnvEK79r7skwJjoDbD2m zuLe?$HuyisFSa zCEXL{a%~}1Y1WLF^Y3?~k}lJhjZ-=w6buV)nR=K;P zV?M-Wv3B*wHv)in7hCJ)!!yTJbaXd~x`of8>toPH7%0gC-L@IpWK7$@2sb;{A1kdt zV6B$t23f*}Igx{Kw?!=<$|`qWzuoq+8PE#1QE%*32FD<@(xg#W^VdiPD z3?d+B(qN*0s)r^tWy~rV`;V*l+Rk_y+}X>L*6QTUAH8Nj&8fmJU0 z=90BCO)Xg!yuu|Lo0q_8k%c3enB&N&1 z*bdjhQI?z@!6HC9Xs&cGdQ*)PY`fAVch@aj&=j^}j0BjuV>@aYzwY@XFg=*KESO)v ztO6*lwnev^qKL<=mVk_Ty?KhO8aMK-Wy(0!V<_kBAcb4)GYaF+QgJB7#WQgb(!C}C z94{+Mo-4!&3eW_tQ_8B&1li`2qOvFB>l=SA%i_K=q+_3z(ZG$=1pvk?qHXy{7=ws( z*gKdeL6`o5#W0OFbmSf9hIooI7^8h#6a6FJ@+>o9W?|n;POT;5H;u z%u$FuvP46@bvW+TUU0s+ay(|p**It!NwQ|!X?JyCuf3oz*mcRE34bf8yz`(UAFhJFrtC^32DD$ zA&H_o&UtY__!}8Hd;}X3C2*3~e3Y-HB6(wb8AKGHenz;(>ZwWE*ETi-x+PvIi)4zN zi{Of&2)a)4f0D(J6qvj4Eo0-$=O&L*jQn7jyv3m`=(AZzt4T$*%=gLum#d_l2O@L$ zKV<$3MCJfC6om+lc$JW!@fhRk|C25IX^##=++wB)CiySA@=&snrjqO0AA6qKzj*!G z6rJAuHRRz0SP7@BKD zIaybd`XBHDDR8XS&1@`U*1p!Km)TmH32kN&X~v+kpGle+t5}hi0YSzi11SZv zE(#&(w`nJ+U~QnyF3yV3Y;r%qJPm4ApoPENvmVXBB**+=?)Ag zmf&G>a@yS8pK?`KT7vI#WML`hF)$I=rn*%o)~zagFQ_A-btTD z{lyKeSTtPh3cgc)1~L-Qw#*l`H$_IPe$IjX6KL9X{+=bGoAv(66{0UN0*Z_GUpXq`A=IlE*Q9<5@!|3ACq6_IPc3fyx+GEzGja-aOHL^%2pYvd>m6KR}V zsDUXMML2YqCQ&7p7|aA5e6rJJELgSVd>l_O>M~r2sFlY&+!gU~H;zLBqOPfY{pP){ z3WuNE^2#mQZ&7Q~}T(JBugf>)5K|zgrj2z$6J{0y`;J!KCkK{_Z>M(C$V@(>->>|2HNuaO=2z73<)7eSNSg|by?Ryx zeY1Kq8yJyD0}2-eQa37-_ddHE#epPQsG0l*eYWr5;Sdw7&l z9Tc7WwHw3S^Q0iDwTlBB!@<;9k-t}keS=7B1Mm~XI)6o0{so6s*$~;aFmxM<6uh@?3A5ASCu3{E+xKh zpY~EALi8EV8H9qSf&-r>I6Cj80*eVFE~q3~(Z5NA4`wf>p`H%8 zN(u+{dg26v95&5~J8Khf^^3Sps+K9QjREDm#nnXMx2eGX3cwt3ADKv^Yy?#=LV9zO zfBDujc2t|V3F`2M$QZbGrYIJ)%DRrZk_%Hvi7?LY7V9iegd|n?9SM#_d3_i=Wsw-G zXkL3*h)H?%8MjU8^XA^~HIfVwXMIgbFxSc}rxRQjFZi<~n%2<9v>3ER~#PiLw(O#3hHizfkn-&QW4*0vZw6qlEY&CkQ#*RzEY_t10W z*s2D71iC6>`D9*KVSpzaY%e{%1xJrg+P7`LFEGT# zka~m_?zmLM+^rsfGe%UVRha#1rBFak;cvPnU!_Yr7{GG1YHvaoVSt@Q7}=T@+hjeN zmY3@dX}DQg1iN%W=1RIN?tL{Zg$oK_TTG6)d(FGWAA9Qo+r{Mu?A>MS_P7J3hJyjw z(#=oIUGj_)9i5~PavK~y+Vi>z3q6%Xk3y!n;Y|Q7?rBN zxxD#$+IYJ(RLzvIfEqd}yUzFXBu_h~q7X1hSU}2m`xcHgTKvQ}k3dsuwc+*5kgpAD zPHf?AUR@XC_LFA?Oy6;rpKCtZBYq#QH@aP~qA;cAbH0nK3kepet$g(sw#n?eyq=?f<%O|i5x zi-10L0xqXWPhXXLIt-KYB#IyWd1s5X5;uqHe;Q20JlM2dd`|f{a;6J$GfJ{Io37xe z-Xd2{!TGQow|U{;d^q8~0rRY6$50D5({GEcybuDP)Q#b4!3rs2h2bX7V{OMzmlT*4 zJ`?S>MAumfVz^%V^+LlboF#`FYMt^MWB@2LpA8pWDFIMA<^~HB6WN)9c;sn{p!m3) z<)|tyFQ)L}Wt*HTZewe^j4EzJYqc3jDUtHuxB<0nGVREcw_^7eq&`B%)@C#H;Kum~ zQ}sc$93$7<>9I5SKMrHD!t4;C3MSkUMHhJlkQDl25eL1??Ng;X+xFCV(KN7iwuv@S zn%|*WTXMvdc~~@?XZH$~WC|&}_v8UmuZZHUOeg=|YoMkA%i_^fS55zARJCPd4_e{7 zpKEUB&}BUzYiX?$XyZ07tP%PsEXknFFatWq`3{AmH>thn`M&tc$a~J_=u>^9;zTBF zq#iX3BqIteFnpjjZU*q3XY8-8yI=XGKy~w|yUdX~CvR%Uq%uX*J+rzPN(kbnU#!VZE`7 z!JmH7mo3hwbANN|?g~pQm9(CF+Pr9J*Q+X)?+41*A~5ozV<5IwOx)!6>tmrZ{IDE} zd?bX)9u4jEEGauEuB!b~drC!IY6f8hiB>xoE!kB#L?5+x*y-a2u>I_ptvzx?8wtmE zXqZ6ca!Gd7SbWk{dup2!*vUj}v#ueyyhpZbUi!^(qn}0MU*#qOrWK+Ysi_uCUjNbE zZz#|*5tJP5t?b;pR$2M1xzK%KkQ-WldX^JuCc$F3&xLpQO1-@k8PuG1GubeOP{vy6 z5c6Xw&a`ef#dNk1z-@CMcsc)R;XHb|?mvBDJo&TK0WWy0;bua*ciyKN-u3p>@@r_` z)UM-smUD1#j?MhA&C}%2Ec9L+#;+bF;DAB4uAs3o8RCc9+hx1oI6GIP^LoT}zc~+l zc?Z#+j+l74GWJ)QPv!I&7j&$o$$F31UueA8N}X^O2^cC$08(C7KQYP7+8;F2&E5;A zB!7!|I0-qwxKVKKMv>%*!g3I+zwQ2bC+F8T+kx_~k{wFU3 zo9@r5h@Y?woGdhQJ^+Zm&zx)`2MQ91k81xg>}n@3NKr=& znDoCZYiJt_G!kW~Sm6GEPhOVwA6tc&cM25+hI7hFIN8koeQk)g?a*;j^+ z+xdI+$r^8w;TpB?e_7MJF}kg(e>UuZM= zTb+4iDc}ucTzS)8Fhk|JF#NUrjl{=V)dgny#aLI-HThRH3jz6B`RlftHP|Oe5?(OE zNytgW30SfdZO9c&eJ1l=r(6^cOh$XYlLQ<3925p2ujI2CHqO~aU zVm0SeWJ!mku&5oGvZGD;0_QLdX5C+-g!7$qAu>!7@$d)9eSC?S3GYvPWKH00t^e<~ zR=m@2@{3Eu+4r1j-xVOJ939A*igJh^HxQ2jw5*+GKt3V;Ip15;j3H5}N6k`I_!9t! z)ki#${V(Z=gPE~099;}+lWwIdDA>vL1;>0zH90d98O!&#qFs+ZFx{^0Z6L?*b@V45 z1t7e;$f)(v@0IYUfpfcXb6j)U1>4BFuD+7yc_qZJlm#*cgt{{_-I(T7JJSpGe#wsm zMxc=}l~qEFr(SocH9zDlS6p=%(5wJT?k9hgUV_J!k&OE}Mv}R^qll_SBr7A!svVft zGgfqEc{Cqj(mUrV2dNU3h|n?^aNckXwy@rW!K-|Q^%tNQ;%n9=<|+>uOres*zRwAgR2 z87i^jHf02T$cknLjwCuQgft2qPF1h%S^k*oCC@USWID-WlMMJ>CxSq1+M)H@wt7vM ze0e^u6x#U`6<|IuWK|z<_Y@5%tWz$^r6)l#w1-QGOk5#J5Z3i&N)B`E-0BK63$p)X z+EnFL+aedPyN8a5+ukkdfNg2RnQYVZ1u7%{Oyxj}_*J)8*YN=YJpsk>R3GempTf;t z$K8H;A}UMbe3}sy0Mdx=mZZ4EQhEtBGe!SH;GlXS->csZOU}3^FAof`sU}p|mHR8= zDF*(`A>?p*uF+}$NC9Z)QX#8s&bI(8H;PQm=x!ezF6)IgqDZe0Z?J#1F1n8VT-OM% z?imZyge6sv)Vlw66ury~`65^Jz;vuGD7)?)>(~hC%g$0FzcIhF^h_d@{w?V@r61UQ z5v3pNPkjX+ zi!crj2l63r)PWO8LG6@Xk6cg}CTOdbdoCcU?2OYT&k2ySnfMbt{g*xp0}36U;LBR| zi{wJHHPAcOo4~iZy&L{BTruPza7Bpo{x>RN?@~ZMP~brZaM)28*mHsKX)fQV4gp*8IhsT^)3&`+a>P<+nOe7*q@ESm+R9~&-N zJYiy5qm{84@4`FbKvk)?qskI&%e=8E_HzpbDg_V`aI3|Rz}wr&glb3o(5B^AKV2NDrQgC){VpT4}VO86=mY( zrsf0gYNTj{=V4Mtrtg+9PLpJ^Hyo>L+Z9Rj$B+DcDkhD;*U5^8O~0W`Z`Lrp%$_7{ zE58G*5fGYUS?781-XVW?xh5xR$tO+WN?4_F&>M<;%t2l(n;Qc-NqOjBVU9ur)eH|5 zu`urg!In51W9V3MJnSO5aXBvV>ab~J^A9y|YrjMd7F&*Uv1!vS3s)sIt9LY*F`1hl z8%rtpVC>o%$ir%|{vJ|v>N^_tNcvZK$S45dm4~=04COoV4hBUOQc)3A_WhKPqbq6& zk;zhm)8A|gj7QtvT|0-PI(!}qu8RJ4Fng~auQwnA(&AApG0^~cU7N*E>v3%D7IjDa zs@+*OTdi51bmGJw@XckBecEN)D=%&NF!*(?ZwCjSD>McpQ!9l7^r-rj@!fGDh*kiP ztdQ94&{YmY;~qP!8{#18i^CzNFHa+q;*M`$8cBqA>OF*vw0ma{{J+Faw>9ybiT0T8-0_5tw6NYh!nx+_P0vtL@WCrjN|(Zk^B|c+8TcZY2o-os z>pKG>b5p6U0I1$Xa8*qsth6jre*7A2|C%1;rdJ(&bHQPN2l|w%574J#VgZzARR=() zQl9$fRFBd|_^Bagw%L_JM%i-lZ-Oq}4o3Jakwe(k(q)OMN@t8-9HhnaIVFqP5{m58 zWx6^pA`da+Pb_pr0E%{!cA|E&S6LmETyHph>AsWBxVbvBQhs>zVO=b5OuDbOUivH38|wy-eZ9wf|!mRh32sE-)&SsPIb=mU< zjI3<`x`q5}&G#P*^jW_T$O07)vuFND8+-IxImk~u;=Od4&qN@d!*H%YiEz>RdfwFa zN269wp>lYg#e~-J7of-JfHT09F6f{N_iLA4(@%kkc76RgJ=<2tJCPbJq%{taP$Q>J zp1b>mE&F9^iQzcbI9{NOys7u`$V;{)@%k;g3X%?Rzf|;b~EP3ffb>txeEW2y-x-VQJVoD*$jCh!lJ$ys)m-=RKRxvy;NDF^JB2{!du*WTfM6`d) zvSdvoQ$WjSO5bc6daOUP+gLjp}#1}(>Q zM?bqDw<@f(spmNYy7k5p5_C16>?t<)y}rLsYJdJ!cH*MBtLQc1?CT70hW#kz z_>njC;txh#|JR*?PF8_cZABs`EkoS6>qvMA;Qy#H_$BLdH%|ahFPnmtzwr|WE-@Jj z4%I1(?3Rs)0tHDz2aG-jhd9q=@(9T3j<~9;3HSBgSXm_)G3SgVGxRC>cqX7()RdLr zzN8KLucUdjisc5I3^Ru92tXU;juCDoT%6qiGEi2!QRAKPpgMf*ous|(Jg6lk(bf>| zd)bLD??8|aOo=mZnm9pNk&svz|7hZZyVpqyMz7ZE#Mh2Yjui^ zRCUx1Zt2S6AFCEg0Y}Qk`IN|v9hhMzno+=9oI%bU83lP2wL(sx2Iw1u!h8|=$`<*Mffk3G^5BbI8LC)3ltJd<(KGNv0f&e{+~TkiF@n#{O(v^9K6LUrmE0!07D-L?#EPLl!Jba)p|H zH-?P$L(d?cnq7RZRfA(!JK&GLTE*VwA3ve>C{vxO zGfb~E%vXqPX-G(dJx-&GAwaUJ}op_a6YS4{>twQVgO$3_S`2WPZ(6zz}uWhKo^;oe9xCC^-UhXDl=~X@~mdJ<;U}p>fur-DyIyM5UhS*V@Hb)i9zXG$)Xq7lC$NQbL;DTn79NpiFby zKbhu({<@geL}?^eV|MiEJcNPHg#OMm)3j9cbh!KSI>wq}xnj6u9OfGK-VV|T0Nf?N zd!OC{+*3lk@|SvG*MQ4h(Z)PE6DJ*bYqn?X8va2sr7ej6A2@Ey+z`(FB3~{-@2MP$ z$T^S=q4!b5pT*RumbeT9oY9bC|2}lv0HqiQ4~B)?2WZ3JoEoNmN9fr@5Cc7MP^eIj z^r-NH3&ff#VhhqdviBESf{6fcy+?=DH@6gnHft@u)^)mVm=){3k+C{VRfwSKuAV(R zQtz!}ne}V+Lrp=47n>!J>quDT)i^;zCK3Ax7F~vIl4!HQ(oEq9M^E)?ycd}!FSjPA z_2`1N*1pOO9<&Q9ViQb(z?9z!2f?{8ttv z2$T-dH9vvqVuqS~ygv`6Ef7D*H_Kdui+2>iV>RxvH_-H@;c)nS3f9B^oF>+FmnJvD z=Z}vLY9iK7k^m>SIN>Sg@QxK)Mr9a(iZTQpblSRc= z1yd>tpKa9CB{O9u-mLbGSNf~tYL|h{r#wL!BUbcP$B!Z0uoJ-d0|%uWuqrHQK*#Mt zcPpR@|I$r;v&Cb8>u&D$tgb9T^KZL+n0i@TrVZJJ=>Q{;`u^Nf-S@n6`P?hs;`=?~ z(fG~Zv#9WT05UBB<;d&M(P>4xzCubNUaRIlv|k~5@J%iGyqO+%rSA`G9e4S!j=lW5 zD|A&@??yW~8#=($`|v(wD3}a9?R5BResw8$bWTy!gS>70)jW!%xVFf6xzh}1EB|Jj zn#2UYB@t55o6E1a9SdO*ijY2~r6Usl!x`f%5y_NQdc(SngC=uGbMUz_!@re*Dk+Xr zq5U}AsOL#22tTeJ#%1Lr2&~tmRase;=_+3b+q=FE&&L+_zSbUeRNnF zzFL?(3XfBFg1Py{c1|@0O{8%Xc%An&MVvuNtp{GT8xAS!Oda^I%jd7e`WUttzt^h9 z=}vT`wdDYAPZY|MV;7yCTYLw>nX;=pb+X08nUgF;^7hPNe=RuDl}#OK%*erO*(cB^ z_>^`|y|`z`1|A)f^3Bh=OqQq2TNmj=xqoPo2O0H;=p8MmWjQ|A3G86Kl$ ztSO~gxdOESkOZa2;#mt9yu9hAbyKZN`T3-bsi^^`j8&fkJ@9taC5Ba)=l6?5+5T0* zO!pq~d@7~5=`k*AvEQL^E+m2AWc~>RC%p63k0mrbnAyr3@3NjpCEp3XllC)m&_hMU z=WmRYSj(U6rrvM$aw_dSwuBN4u`!$7tYvVchC#E*QAQz($23E}#lcfa6g?xOf^}l; z7y!;Dr&{-Ly0YGs#ts6?r5DUzPYUeg1|5)VAqbpV2uVu#6N9{bNY+STK~$~?FwUsK zLYmoWHNB*TBs*0VG81a)&i{-bj~K7FMmH8O9ElR=Dy(COrZ~rqX6nVNW`U{V+R_na z@6)jvZcIOfbwQ+A)Bz$Udnynyr%Vw5ZLT!@fd`$lcOtgQte|%T^cj*zpXrYQ^ z;J(53~BM;*A5qqgHefv`<^r;@+vEI4Nk z;4(z)vX^K6mEIbsy9eim+?CLm_`DiNh;MD%+gH>@#UD;Kz7#yokfR`c zOB(%|R6&myoZtmJU}_-xVZw}(J8pK_-j)=CL(%5b4*56AUMB|1Yi~PNkvmofEBkA- zevLh?%oT|rv$;i?GleCw{6x#Lq=+z|IZ)o9_zPk~tGwpFWN@n66AS_nw_AUbm&Z%ewfXSWJj+qEE18c~^olA% zKh9bn4UH3By}6rkv#WcSrxwht>wR<)L|Xpzt9-Nqn8se2RX%=C0SG<`s=qw0KOU}3 znq0L^t9Pt>_R5-hE&+cyPZp;bdGM8ws>T=4Qci9Z9(z@?PKjL4L)L3>qcKr!e}6O! zq;^E8yVX|oG$Z>Cis;^gKwY;02V%Q1+ zxWp>)67!v!*IvVE_cIspzPs=~Zte|H!S|_-8I>q%(b`T zp^0(^Z9IsBf?QAxw!-a1ncCLc*hFONFLK~J7Neq0eFuXlGwC+J^(|*4i7R7ofXy=& ztlk!@o3%=HU>(#zDYx5@$d5~=zh*ZcfUds*c-tPkdOs-1R(Y4+UJ!(-9%D=yB2T`1 z0Ogv|iuDTA8%F!LfEpgCW5k@NEeAWD%<83`$_Q_@?^TtD&ddG}Ixqc#nbet_CCA5Z zMEXVtg*fT3ceFvG7J(FGy}v$%UNMei>M_q%Ua(h&b>JA45Slv0NaT5 zOAg?H7zJoz{6*4RHkpZ8{?KfU0yyucT4DG_EdQ35_+j?j&Qwte#YRaGeXR2+he!;e*g?%Tx8~T9OZF~E zwI(D%-!dcQaG78~7Y^DE)Y+AX0by2#VqkPT;>$fLsQ%#b$|#|CUNq+=m{feq^zJxk z_hqndYR+nmq3paCv{9l@h!U^_(@=VrjOefi`3Zay@|2!}Rv}MiIIYS#j4Z#v1C$BU z+Gs*Ul9=Q;VAEMOCl+=cvQVQrQumGVLwCrY%D!14bGldgvIh&_09eGgeB(Kx zM#*fVY51e6Af${~_!u#+s3`y%8+(W_qAwb}DvK$dYU6Hf(A6#y~=JmKd2*c6rK?H7@_SOk{;WcAX|(3N59#UUm0Z z=6h3?)2C_p)TIX*kf=Qf)G0N@JNyH3U*@KMls&U77c4zQz`Ck7kpQE6km}JcZ_37~ z|3%k7M)&!AZ=i5vr?G9@XpBaUZQIrz+iq;zwwCYIrZ7~c%T4tV7trINcE{(Mi50=u|+Gr+&XS!s`q z9C6WWsT$@jsJ(Vfdkm?fbPxj46J;Dj&yxt$*pfyD+oLh)W=nLHMZnayr)j=%hr55j z)GEAJ6m5?03vL?WwnK?SBG5uHU{AqNuo4@|>Fkifr?-atQ?LB3HJuc#uxA8YA!?J6 z^4>_86Vs(_OUdIQG$4kfI?$UFM|Be*@ue-T9zBFf z>Lf_wD?DRoxpwt$xr936NoZ&4oe@_y?2C|xjBq=nFdIG@X|ukoVOdlg3Jv{rD?0HI zUjBCs3f$JKQ?=WuT99?EiH^JJ2GclGYp`N-FBIldK0i1DtiK;0djM)SY4N9hbd$2c zSS^hAQJ^2Dd&5F8yF3xVrMmAqk|LI!!4f1k8B^-Oh1vT&R&S~{cSPJqEXC08=}lYM zd4z@TeD}6pEkx}3gm*cCFL!1F6EL%P9IGBjO($|RNC()63Mq2+bWacXH#2w}nQI|j z_X)LPPxs1!J?TWI&Y!fi?lGZ}5pzS{JE0`Tqf>ITzLugqL(u~uVR#A2!vndk%L_uk zU1RXoA&eq=TJt41ef24Q7WC^&g~wNhT40m_rc^f`ZGl~LAfzAOyNJT_30|33dH`en zt>13Zncm=^Fhkc?$wj50wLnXuli*T&u8ptgitb591EUj#2ydPg9OCUhEw&;2dl}2) zz0t3eIw@NkpG}~PJWRb~$!T0T-fip@jw3mMCiwAH4qf;IXR#qOZ6Ak;iH&=XN^2|M zr&bB&2O5r%dE7UJUzW^blLj-{X`ep0=m&JaGbBh8=1LQuUB=%Z(@5oUTQ{j2P_Ztk zVfmM``_Eva%(~a{|E{6Zy2lw#!H&i!L?Rg=yS_M+2#)}otIz2tO<#iIzMm+?U2aHv zqZJofi2bWu$i^l(d^CYGHCFa{c>b-Sl;&mj;1J`3;^m7sCdesCS>&5IYV=YD&$$;v z`WeXB7|$47;9I)B{vp;fOLO*5?Lz-4Ui~T2+n9r1liQ9+utP#ZdSs1pNwl$Nxoam@vR& zL2z*Xmxe+E%;?G{AGD(O-qa}`CG4x3)q6s0wmV0l^S9DZm^Wf-SRX)W>|c&oC5|R7 zCx55=6pV#_Nup6galAERL6PZdxpJOI&v`AcMd79UP7-F>dq`FoG#=gh8F zGr(Ecnio^Fsy4kI(62E4q)560WmIRbwdQX1y={C4>P!8;(7|Nf0QpT&1aDYOXeBv< z2a)l&Cq9}pAIO9;F_UQ4BfOA+prEK>3OdvYh?BK~RC^T_WrSu=v@Ax9RfHn_Zjy)J z49}Gu2UVGyee}E46s~KjDKDpy6#9}#4v|g(axg#V(VaB)*z)YI;=FmQv4q_TTln_6 z)=WOo0m-m0bu#+&?op=?m|R zMjc~m%sv*i+5|ejr#Uxu|6Ez7_kMreZLtCa8Kk8gTCmF^C1?ypR+)xkBFS)RVh)V7 zY?e%5(O-&oVFMTJwphCIJg8~QggwI8jX9sQvkWA&Z;9c^qJ%)geH{M5lJ@f%NSPig zeB>(yesM=-+pksIYF&y?{I1Q*t)2(?b23q7zxLt6^sw?8LV6jKkj`bR=YB*NB`yFn zhWd`H4@G-dQlU$NMl*r+ei!<;nF7u1u+HF0G3#=SH9!N<-p=eRc=tUH%~^V!8QOnzkzx;gqnR zOy%nvb?_0suwXo9==c_h^hyy@Bd&7 zvf;h&GP`50JT*gQMdu26!e?0NnfC`2DD9i!$4VWW(WEw-FYC?XA06s-O}yf-vJP0Y z_E)3=>ytbkP>34wK7gjVsQZqE8fnsG!(E)5FGC!)u!HyNJwQiZ!emmD76{!^b>=zPly> zzwiTlyjvVXXLFpZ)HC3hXf_5CNt8ugjF5W zV7GvIkd!)CC)N<4yqsoN`g?BJ!|IH8P@?k>hhO2kNOm?ThpJNnQvFF-r{T+AMf?cw z{4pzwd>M)AB;IV1uXGEzx*+O4L}W;LH)yRt%P2hI$G;jRv%#>_GL*qAzssn5TR84T zWyQQko;*oX4>8&=ZJ(OnVacGzF*WFh>jj~foA}a20eOW0WPEo(E9p!#v zDJ?4o$4@lSh-ErK&y<+t6oo8_XBd1V*LP+S4W6c(TK4`*mx_8Py%>W7pE(^t;B8aG z6AWs7mUYn4nD_liWnYMwrT}A)Lv~peR1*zUGX+#rE)rC8=6^L6ltfd$XrtL{wnYpA zC+ziHk$U1wmN_;Qzrd%KaB}>88eU~2num+NPI${_^FmPxm>k5>(t4^*av#CQr_LW2%g(z{6{hvT+)NWt`DS25yH4Jg1E8qz6N+uocg%T zT>1`olshs#X#)M;gvT`6=o!a0EPKltLm`o}UufclLtb3s+h$G)oku7Fbv&?ERzylV zdqJM9aAR<+Js!>LT-5%DXY_nE(lUc70ksSZ^OStOb{9PWp8w0C^iJ=$R6&zLd@>B; zyO%Ms=5)njN%kaNMA+hiX`(ifnR*J%H!QiJfsouO2#HD9nI!l{(h@XMJj+ZN2-;+; z1QCmxqf(nI>vC3lY!WgLmwZ@GQm<Ew;jTd%4LGigYj9XXu({v`ZyZzq+u<~E z9+wGSUW>1vov%J`h^-C0+_7z2={F6${-t94`#1$I)A&^oxSrBykW@0%_OEBZVlpx7 z8rKBBTxJnFLJI9T9^LG>@^(K``$V(|HTM`XiAgq#xm!$EEuD*TdT99Fm(6G_*`L(D z$D&L8GKzn&zGD8{U6oa^q>opV%~7H(Abn7+3vIRit@|?VVe47ph1;PO6M0|+T>ki? zfC~pG-!h|C0{)rw0IS0D1&9pRT-QSMx#w3s=oS#af}n~1Elhr1e7~4;CuCY7h7iBP z*tO1T5h7sn@$Q%1)Z8l1M)4=m&vcaaa^J1hQCzeR>}qE>X9?>HHLGSE;t#)Z+z*|y zDD{mt2Z#5o{JTr!ps93|Gts_0(<9{eS-%OqYLhornPd$kQa`)s6dgXc>d%Q`E6{(u zExhiAp(4n^y>(aVk2cTZ8m)6t+kN}1Irl6d|6{{jqpkQ) zNXQPQWv2wLIYcI55zfW1)m~YAth45wbCAMmm||t$$Y^&xVn2`j+K-I7Psrpjtf|H-5)Q8%-*`a^L$3&sAPEkGzdJz6AR3SV`Kc*C->y}s?_2Yx$+#l_ zP^%wUdp;tZ)ZgmWQYQr6%6O6EQ6i0vF4^-X_)e(m7Ero5-i!KbX)WlnxvJjo6WoDL z<@gF|`oh6){We>pw;4M9`mvW;>J{Q5g3`Yw7&TcfA`Ft1AqfzR8J5XKgVY7Suo6O2 z#1z4S7pAhdj7=j=1G$$)M5_BiyYR&mPy>Pa6$n8L-ZmDT!N+LzOmFQl+)3qBV6;a3 zuT$<1?Q3Zj-a1o{ocL)Ufw^Hv9nk)zntr~9i{iRXpTUh~I=E{n=zvKDHO?z(0 zZu8{j7D%jCL9%s)iDP;is z^Q7;pbBCDJCGlc!wr`LdS1nwfmFrFZb5n-xyS>*lX@y_`2_E& zNJ*x&z^~>=kQ-cype3d3{{}_}UPWFmY`St&*68NK%}X8}gK${9i>s+}qD8w6SK~5M z;_dNOEv#$nG58t#;30Fc?FXKdde1!|mNmCfoZ=T1-bVec*K*$_FM|^!yRi zAd!D7fDlpnr-;?V*-QT48msU^Qj=eS5yGvdaIkaxTWaT)!=i7mV$OG{sju5OSN5$O zed|K}nQvjlz()!g_TEAkkB9O?g;SoUNun+(5tA{K<(TKtf>x$-p$wkYegG6!)*lto zSqT@j=jBEhL`t-!K)v$<^{zD@)Vu#@+C`mZEQfG*{D32N=(wN^@BPy&q4D_b1nsjW z`4_cv^exse`$KZ%bRHS2NMo(ufTds=N-=?79Jrv}R5B@Oivls5-{D%Vq9czXj7hVQ zL4|O-yxRmBJcknl^gn|ij&Hb7UWa#XE$k5P%y_Lf+I`dieOqX?7RtDth#=KgjuR(o z!cq4+zMZzvZZ(62@=SImYyv;zhp-%rY5mbn(n;Xx6uQUI)x^AjxLX6eD z^R|S{w^?eVrmbdtjT+z8o>;@DoVl>GAN~#*Q+a*xWyzjA92ovRJef@Cp_ZmrVdp{b)g3X_5v^c9$_(u-S0XK zr@jqq8U^Pr9X{E#?Pf41RG%2u9;?w%%Y9+cdc=F@g;&J6A^7VbB1<1G9lD>x9lFc0 zRWs&21{xcVxSgMPGQc0jcCc^#xsQw9OI;8#Vm2Y>+8c)Qk33oQjQ2w>xbOz`d1{uu zS`BNPG&wtSkUVYh9@Ukl6W;L(?-1E|Fy<4xSlz9RQ)+(LTZ|9sertk}-L^>H9W0WC z=_N_id|&?+`TK3)ydb&${rA4LkNNKrotxlexWfodlI6+{;ufe75YU(hn!Ref%?Hm5 z&Yo94JB~|xv8T9%7(QadQXWghZl&6vyig@kubDke`CrDqBPg1+Lxfv~A4J$y+39Tv zSfDC+P>x7-(6>!B`pc;SxgK%OL0(%KPex8$3Z&8wm#{O~Q|vTP+(L zjZRzYUwd`c-~pZCY(5qSPi zuwVZ+Zrh4lY0Z!i`3%SriYv899usOb!N%W< zd?J)to()kfna@23>E#9XuDb4T(TLh>>WIKQ^d~vyz5!zA@3Q0rwgq*Bu(R3paWJBM zDqX~~OYRSNGGEPJ{@x%W>e*T@JEhqUS)X%bd2iEb1XD3T67GtfBZ*Iwv{GJlZhPGd zAVE5upk}jTf#C*|yZUtauBFmvOJ}}{1g2MqwBHG1EO;gABa>QlWx?ZqmiX1BX#VRP zDVv*-H3dEiu2;hs-x#t9CXcV!eNlx+yrY}o+PURU6d%m|Jc+t19ou(IG;Vn4iwDvv z{QQCE^!KjMO}P3r3o?3ms?c#cqYO+o9skZD@7MycvRmW_rj69KgRPwn>?@EsjL?@Q zi0OEt2V1CglFJM_-=|RfzmUKlT-GJ=Z2)@@IIz>Z^sV`r`U)x?5icIX@;XE912F`Z z1%}qghZxZmPy77Lp6BVNpAjpd$8@vOyqaXvFHkFfVPsV?2T=*et;2!Sb*n=*Sdj3{ zcxQb9A8$QoNmXSdQEsozT@~Gv2|PM<`zO_n8;gyY1+Rj5!iuBZH?S2pQoW4K?V01& z0Dyc3w&o|{bVujq!6t7yz@POFz9_~s*nMOb7kD2yd1NZ>&_Ju;;poc!WmhNg3l`+9 zH{|V;YdB(KZs0>QOL=gV**qwP8X5^H)?5+3uSF@Z-{npdd)0)bfFnT@Pg5Vo?}IlB zo1mx<~Zqoo$^r4QFq>Hr3#G9D_lJ2J~M9P?Ho0Q zVxK^bZ-ct5TUfyOe?w7nTmnTjqbLSvAA|a$~92|V&$}cs9%m0;k+S4)!sOWz@r44SYm{VHHi@XQhJz-iu zsk&q!B1WV6D$#f=jsH!YTyn*k{OlL|dRSoKovX8)+cjVje+V>1`L=eMz3(!X#rI|K zSj@grG%3Jf@?XeF;pMr7g-Oow(f(bY#efsQ4s_e99^!nUb6=)az?qHQBTL&eB6|=Y z3qq65>r?b{Ny{u6p5JDrO+qPx!TcVBpCB3OWNOL4n)FfwcZ`u6J=Q4E6~uU?Hr(2FoIHTyae%?~?xxL(86eCBe3pCS1Mh63esbysd>ud+&Xn3xQ4FVvM5QUI} zf(1G0y*4iY|NTI`s`i0;R2xRzU5j7S`@F`R_jU)gJO3y$%d?`<(>X9**Ndv-w+aop zpBebz7gBGey)3QgDbTg_!+;a+DJ>tU)1>_HunyFHsNZ=#Su_>hPFPq$nzR3e4-HT^ z23&Y@pGMbQvO1e3HjlL(D8F~6`}O>!kl#9%NR3~HFXhWo_4z&2t>hB|J6VPExPXar z@KzMz@7kPwis;|ctnD?W`alVztG-yW6fVgw1%9N~fnc$8(2#AXYG)U}v@CO)->Tl9 zkl#95snXIy2K~sHQdrhnx-1X6;UxRb30T`yleIj`9k%RduD3ny;!Y}nf}t)h6!AIM zG+mUOVsZ45FxEYZI5suOP%aks9Y-iYt5y+aEnuj$;LB&<(cSQ27DA7 zEv{`%ZrDcKIwrdK4htDf2raJmJvxb}ZYzRIZa*U~>ruCQjBmjul1D#ml7iDV0L2~e zt%S*Cp0#pY`5$HoSKgI&gEp!gp3dqmg!$)Xt(A7?i>-*E$Gp)lMV`gM>MWm0vR4v> z90_t-oTYnd)A#60jD<yFyM-AiP64zVeG|f zu8wQB#Fl7tAyS^YC)gf*S3DBk1G@RLY6ZA7&1euQo54&0*B=HsH7OMb&~+S6V$u+9 zTws4%M#hSWy!SK9+7EKuTdA)EpUSDPllXLK#`p#v+xa$l+zMy8YTcS_ey=Hdm;3zE zun>E!;FLPIEkFOgCZZ7^6GlVieN=6l74=~~(fUov_Fb~!YL2Eg)C_8t0qDy$?2;kf zyq>WbY9q_nWj;;gVT>TNQeGPBX01s2#WJTSr|bqb?5T{o5KmQ+c^T6nNIHQqn(Ujp zMJiDnS_Toz6dJBRS{tJBQcXDTSqF+LU&>7g;SxQo^S(AYj~HZ0Hsx;-iz=RV%~v`{ ztcfX6fY0N+%k8&WP;@9)1)vZkt)7$t2T}VyQUkfBiFFX5kAZ;H8w8}sQvU(^{~t(o zMzfHXcH$FsG{1h6e>ndCKt_C!A&SLqKQ-r_%0nzk_M4=?+qE#5vP~f5;=sqllW)TC zx2Z_%cR|%8uQDhApj6l&dTaK0?*YYIZ^^-(_4pJi!YP2=J>=-0DIuyEVn3O;_!E6dhuTvePF29c%$L z90O{27zfmF45;B?-oTUnhHXV>ggvc?i<_N%AVJ4*wp#Wc;x~~h100CWBH7T=k^>He zu1K5zLtm{{PpMBMYLO6E=|nI#LQ9TsVd|q{QlM{0p4tCDT;atCiX?lYHe~B7+w3Xc z^!ssy{|j;@OKymv#wWRg$hT$T^M2pzyhvchP3GaZtV3r_Ny7OpzHL2SL{%vxm-bU@ zJ6F#^XWMr|u2FXj0}#D4AI4ybBV88#RD10bMJG;ksK(?cSn`9=?*F~Dyhy-vgR}lm znG$R!UC{^;697Z&ulJKCd)YNZ!C^HAXAF3F_8Mu(N!*H(Y5m$EeVIF^ZGagquS=0C zuFGNd_?ZU%3J6F)Kz+F0N&J`|!X|fOXDFvIfv6GO!ATkfQZS7@}4}3+0X1NT&53|2IlqK%gRt3VWr-i0(uI%O|t2`#ykRvI#h4aB=o(Afb#nuA>1zf6nm zm2iB62n3~W+L2dRTJ&!|?=y*g&FXL~wGX*TIA=RSF1O?>5&n?}CUIQs*}K=!p0kM) zZAG5!C0pS*0Ndv*4cIi`Es%mq{bGY8>g0kZrnjz8T{h9)x%#b5kqKhXVDHw5lxTKQc zy-vn*MA@gog&-HwAIU7h7Unm#HIQg?;QIxA+qz*|ocOM-po9|kjiW86kfocRn8>h) zEbWH4TYJOiW@3h-+6^Z5Bq26%cl(D{42AAe=;1uMU?brkwM zK@mH)~+WjNIeY`)>CBHWLl=?r~PV8;UfgucSx&d`Ix6pDh+ajJ%j{QuZ(7 z<E0}Hm9X$ouEcVwRG6_ac#2*$)~{+)OC`cjz*wl%>}21TbVIH! z`Q*YA5tht3ttKai>mrX5JFQ8~{g_f@erF(fqI5yG)^qf0O(m#~({ewlim+OSEW$J>iH5Abh#cWDw>1@ii~bAJ#X zz1|<(FW%qA90zAT!Xq2C&mldV6lKC#6Fi4(I%)LV=K5UqZ{-F0WTp{6%{{>deqQgN zA3<^Tt=+IG9p4Cwk6ATzHB;+UfN?yi)4-b77cFJo`w3Q!y?#5tnWDAN^JgO@>W^Q) z2sfa3{MARwSm~<}En^@b4QTgLGD5o2&@gXiH|?M}agG|rTp;2p{3BCV?S-JN4RpYb zif{PL)#OIBt}^VfQq+-PPg3HIViH7S@UKOv90#&ItS+m%V=mqK?4(`pM{u;xK2isT-s1~v*KxmAshEccNp zbu&jQBfCQr0)*@M+t4obu$MuDO_ptbdNJaV_i3~|T(Nswtrf_9BvNMd? z?s%XlAa`r>6)d|9|L+vFpTQMY)xOEN9`95Ka4&x*Ij-;J%{TY5rs3UNtDW#)+6R^n zj9!Sta(!|CUm<_&Z0V@6$QTeDTmwNR-|2^(d{A?)pAU3TJ%h7P;n*6mJIJ*TL zXr<{wIYOYC@irWADMgBoq${Q`l+|7tb`rn&QD>7H68#F^4GRct zsYnimN|X)~sN;S^(?iKY-$L8c>wMAI_8QfIh5}`}550N1lQiskSP+fE{WOxUpkyDn z&~({v6ZdHDrBIOn6SjQ3*^M)!a@q>lcXEZU`q7-doUg7JqC^&hD(%0_R*c7>QOe>Nn1%Z&xxR1 zQ+#U7XY9N3@pPB^dSI7DsC#OFr)M6v83Tkf4>ea7~NHg(5EGWWfi!5@F zg-B4LYQMWTZHn4SjfUvmd^atOq;A&Xa7#~jn{wdI>&&5t8-W5btqyw3=L zyYIam4lQ5x|#f zhsD{IdzxHb;{(NxEjB;DHJkZOnYKNWZMS!yery(IkF_$apF%xUIgg+fIPDV6vtgL3 zI3y_)n9y^b(N>Og(AXoi7uagDQ3Pl#zl7++Y!e!RJs<^CkYyqz6m{*!JZ7%b!?IBS zgW)5%y+V-`5H}JZTzUoIx#`Akc(EQ`oVSiZMz7H+2n&K0THPG~@Bt{AmtI z7@!3=K8jZfBO5(9(pulGCJ6yp53uT{N(V4s8ua{;csNmrx4ZqG2w z*s688uSuRaA`aK6=O)hHs+g9V+}&otgIJo9+>jhBDEQPQRfxsuxhF+w@lr6}FeoY0 zXH0ePDtybod)N;fa=Qsy?48?X$NnLw3sN{v$oRX_N=0SkQ3b-zvdxOqe!;LDLFk-r zB07l^`V<&QF}swd^P}Rjoq?1nQDK!p;HGATo&d}AKI51hCx-yPE~iEs2F_1_VMze* zk83cc`wV@;l+wW7sl?(7+fezb32gBXtPe`Zi1=hlkz{**Aq8t8yL@L-Yv@lB%c7d> z4KKf_4-$2I6xHSVW0?=#{y7g@?Fu7-0;1u?k|!1+$(42l3<&q0EuK>EP!9FCH_Py< zw^e3)y%p}b0hZ(ZvmSQa^FwsuDb3U0!g{7;BWPm$Km5~oqaTR7)7}{V@5Y>+jU&Bb z9hxQm-vT&d__#8B+qg1<{4xX^8|VK;c5BRlP#&@Ogw{31+&UbMa;>-2YTdMy{pWDH z+#&>{lZGwjg2}?x%M}7jK7&_FQ&)#3By7;j>z8@5&Uo=Z@Av~d9`D=^LMdh`lWsrl z+7>>T*0F3zJr=0uRyOoduP#pOcxG0#8f8LJ?&?m0iIUV>*G3I;DwE`fKiRa-0DWrU zMyTgs!W51CO5^HbxsrI59QerOQ>+nGu+Uz5tZ{DPT*LR`25hVwG}Z zmE$|J@QIf1K>r$hq+muXWrI3B;K)jEtUj+P@F1Kui`K+vb;NOJ?cx!xf02AmX!nVZ z7<{W-(h243i(bTKcwc9j z&M#9+5tvO|t0O%5Xf3Kl*11DIElg*8K=xNQco13YxmCe>;3HDzV=U<%&=%8W?{0_X zvAjn(4w{FqqYbDLQ6zo41Y@_n&9;)whXn$*8<_*ExnPUsMgFv&lW?B*WC1Yh?6(+a zkOK7+yCSTJs7$rxBAjWxA2+U2fhUK^p`Yh-WMp3?QJUK!>Kt#0v6d2Fo=G8Yy=2(;08f^ ze8ZS8IYgQBIqGa1BFW7V=`T1ung;x#@u~2t^0+r;u|d&+>t84VJnPp`PbQo>z|v)h zr`nCM^V-G{5%K^h#GEHB>psc^gM6hsgW1hcPnK{=b6xN&-|5mYDSjlccGNJWkUsN% zxV!HwM6_EuM~Bbz2T9ywa>fy}X>U`ha+l>&~@!Z`^ zL9e}LI$i?>~l>pEvEQN*nl+J>Y1gKC<3$28VMqvBxl#T{056tz6UO7`fVmslS&?-+8s z#^3UJqRSW-!p?bx#!SB$KE|EH90b;lSWM>b*BOZ=X1^^pcYOc~trYzyfyfyI{-Rg5BX(Jq!V_XT;~{M0Su$koC1t-kQxqwNfeeG z_dlEML+0U0_hA_WT4G7b{X7;3pwuE3U2;<{Lsh{?{k7dfo;rrc!ACuVr5-2Sxpdzh zO3IkyAKzGqOTjCRAQCIlaRWOSD0NcfWKrz&6|DZ?N4-B+&BxlONy~M+9ptQtNGsfT z6_OZ_5FrgBR|#LO%#gSy@Av*8CRbd8F_Fb)ub`^~p(PshBfgE}1?x5~hgAp9a0N5} zjlNgne80tq|GQ&2V?xA)U}xb?7si051MoNOFI(@n3>tofVx7`aj9kFYhf|zD(gtw| zvMORwdnL3q)|mG=Q}Wg-{n_DXFShqFko`&@OXJMrf!smf>Zw^j^32V!+1JtYdY-(D zP zeLiw%1OA;S%VA|hFp;BT>r{ut^0E`nFdwOGWBl;yIi!1ga85m?@E#gCLJ*L~?AecY z-Plcz%lx%NpXo}gn(`iIw}#QjM%CC%J85?{wyklbg7b{?CMd*&j@7dyq5dA``>tsF zz_Mb}`&d)aCm6!pIu4gKYsR2N1xS0xWemQLs_2`q$x2iYPo+ZdZ4P!*K-MV2D$oXs zI&xv@f{BX$x%(=7Yof&c%2kIvF+G`qyW&2_Wu^E2+UR-_*tNN1aPE6m;8R>%eJcX~ zZk2!g3YDe#M|asPUxgR@AIfdY5?`GFY@uYS#%7cz5AQMIaiD&hlmvuKG9dRg1-fP< zd;CiT2K00FkyqtCC4C{5Dga#v5!yi*x_e7dJT?vWn$|ulHDD7fh2(mYS$fjZKrL}9G zRhAnc4Y#{Ttf9NbL^OQP3&`XYdLGU4J>hqV>aw2DO7U(9Gcp~Ak>gSJ1fZ73+U$Oa~y-ga5~ZnrVS}mZ|JJTPFVY+l#$1!r-UzEpQOd5F8A~v-nd| z`~6F}+-^>WaKF#Gxfs>mI@Dmb?g=r<4N^4WDyq0fCjdO$Jst0GN2D{pfK#DnGjn(} zfW0-9;oGK*zUj2;u=c)2s(zhLE)8-Qt{~3o$npUCc`QpMdM4{!QE!I?Ys>O4|+FC5B@<)mD*h zT3H-2*(r1!DCD~18R2waN(cr+h%=4~{f)kdGypZQ=Cq8_kRJghKdi{%lqtP597KgB zt)TZ_Ul#tXHP7{SBhi80+2&NAm(tYk5pP}TV-SA^bo<2P=3H*OjMunK;DVf7`ROWe zg-Ksx&YDQHq>A1_wCIJ7%g(nZ4$A2)TgIh$_SX$J7Fektko|&-6M0KY)Y@C`F;o?J zQGzNRnZYU;?e$n*nz{0rF`KoLu`qGW38H*R&Jc0F8FTZ6$xn&fSe*S_fZn}`zM^Yr zP<;Sa)j*!;>BXOQ>&4>c*lK_rX$t+HBOR&o(kwje(UE^Z2FL$1I1VyM#Rf9iGE35N z)*cr^QezZ~j-}pNKb*6ZFu(StKb)H)tkKJ7FU+*vY}eM&oCFrB?{@l2n9EOHRK7Ek zmDzo4!O9AMC&AJOe^ztXMgw4)v~4!cB4HC!*Fv<+m8x}m#)u3;6dQ`^{Aq;dypsak zV}$0MmpDTl0qTQST*OQ>xp5=_F_&cdoKY$nRE3)J6_T8ZdMpsvbQ$QlcCCH0WWi-Z;;m+F{5=5(~7o!quRyJ2{CUh~?T z37#&DC-b(k^Lak&o&iWcZ;f_koJ&nc99!~`mSAlb+CaXwrJxZ3)nGpz`z#KdbCVK- z_WUs86!I3O`cI>2U6sD%_S$vwp)-WaPiIX_*Drl+4b6I2Y8^-zd6kf?Wadm(;V*?m z47Wj{PvJg7Y6c&WUtywy|5hD0knC#;AK1i-8yf=0qa%EpmH^pfeKvGnV|wQR8coHf zJrrK=c5~rakAVy*J8oXasjj z&Y(3}wE|(kS3oDf9fkQCO<4Je-a8>%y?Ct z@NYt}n)}^AvG8NoX5v54G5=Uu1dnevoFT$gzqkclFwOXX-WG)R%4Yp>Y)OIYJIC#M z*U05OCGRzcznQCGR)avkZ=H>yFA`R@Ov6k1K>(e3PY&q)$yoM3MO`1tCSNDMKQ8Mc zS;|{1to)(<6QyB6k0by78)e)$e0Iv?qan+jVOnxBON5dywf}E_G6Rf=1Px00?d7D` zvDvF~pnMWTdo)2T#t%^%+^K?Ma`6(CbnPwJ!5>=Gk3DH)iSHV7-b&=ttLb2Lp4~se z2zIj4Vih=nmJT?k*7K!pF)C*#%s=ehp^~H(pCO zt^+_vtC=t9^GSx2Z8aEyIG%5i=k?YJf#rnfN$l+vf*MWMd z%DrK6Kj<_H_A7G>!?JeV5WRxzo^JU&4U1KNL@0k(7MKoCt9jXS!g5Z}J#(UY8 z-Nh6St(eu5qL9%_MnodL1nrfHW9E9x*Yj+4{KfkR42g}zDe+2zzN!543*vY2YbCcd zMDS+s@R=?FU$(@X-yR;K!{KywQ!@z0LOHL2G&6=20xuF0AhD{mhS+<2NaSODfd-(V z0Rp{jX8vm9VPbfVPa5$>zqMRm(+}0U&!$39WTf?78&W$@nOUIlqSn{S4q`C3`5p5G zg&vF(<%qO-VE8oG!*=tZDs<-CjY`bf9ipvjFx+1hzVKaJmI*-|1O{74pZLO`fYq<_ z{CAwwO@}X5?5q?8Ix&gS{-~t0zO4Y6>$Hau;@$Fo7@tSZESUGB4<=-r*`oW%?u8Ie ze{fDspGSEvJ#$;3Z^aPox~o#zeLjis>+<^0&bRCxmzwoEm|ibdCfml_sT?lBk>xmq zUq2D}Txw=FQFEMvMdYsNudWQC4UKC$Xa7tj!dJS8xV?N=tmETW%&{qn_k#vR$GAsY zw*n8~cEEC|lrL$(b*?vZVt_wnN#DD^Vk&LDL_qI4N`fBV!hIG(Rw$^y#I15MU`J&U z^7{S#u|N=6|F7`$d7|*7DE+I#$q_o5BN4mV=w+Mef%h=@b6V8Q-UUSuJ;^B>28wIG zL#967XF{vx1bY25tk9@cb6p!fbX!q0T)Ao_#AK=DOS}BFG?%Q~w-}ulk4vBvtX(9&>@=5JZ&%XTV zy4v#hzXw-KCk{{sERyZNjh%e@XebK0buCGyl_p27yzlf{wTR+YxI35{#r%Bri7tSx zC+?^ByMlF%*0+XjTidJI7F&-emzE8pbo%hy=Bl=_;F=Yk_+Kk7S~qo+Gvkk6+W>7A z`ChTsOX&|tlsIdL84%rryE?9&SOymDXNO6$~?y`IMP z=904weYm`upq>ld!ItC8%l%qTs~_ONPe7a|rBOvp&mI;m#fy}Z(*xoD#xkV#JWG;}DgtFv=FzvG)GM*bIDhBFA(g{MRpw_FXHpE!s1a1dXV%xHrx$rY z9`z0D&+{ew_x0HWA3EBuF zyE+J7NO)adEu+NY{1A0DYb*86nVNphDRB``RcaNichk4@xbej(&(|>WQi$rdH1*i@ zd0=Poi!V%}l9?=RQlhpZ5K)-ab^W)o!ooO;+vFXD=k|Biwwg(;Mc71?7l*9mXFSQM#m!8ONAOLkbn`Q6S=+uG{# z7bnh0#^kwI-xw!*cuy9qR3<`PMb-!hVKyX-8o#Q=3PVHlX|7!^w}?xg%PqI z>0OR7Lzj|{>7w!s^m;ma-p-;1vd@ATWk^s>5GrttLN%+2>g2irmf(8d0XUejtE!QI zO9p>aUoh&dZUUl^k3EFuNAQrrTHu(YN2D%aU;+1#nSBQq;kjzaJw5<#Aos@{3eHSq zh(9?AB$Vul#McPZltbN-X)!hh$ON-SHiec=r8sayBXhCLIUmjrcmnw zrnwoBt_eNu`!i_(`{Y=op5C%aTSem0=BKEGzZ$wFC^B(%ZGuW*dH2w;@i&jztvtr% zcP0uRd%LZX6BN!=#X^l+(N+>ayXYJH=Gnbpeh`7^FU>&~bDBFp!f0h&P=hc2!O*l+ zIW!0;K+{Jf8ZWcWS<`5__rIY|!B5#K?#u-m>`ilNGBpSQ?TrXejUJq;M?v$Vlm*rZ zn6gV}(nG3O@Obh|7~T(mWb!*;84Bf7zBo^eY|AzMds~S15{K0Y% z1V5z9Vs2<(f#g!qJNAZO4fHn-rZFzZYkopsaHhf7^5#A^KQgt=>&;h33g{=TyM_*9 zJF__=j+O}nwy}>$%Qn?@Owm+Ikdb6G*@)iB%6qDqQTd9Hzmpxl`mDcoz*dfbaMF3Q z{h1xGbIb|ZW>jV9DI-T(?4q~i=utVHQDF-_CaVt%;;aT`I+6(rcQisjgU7QY zqy2N7&iP+4bWmKc%@>v8c}m1dSk6crIYf>6OC2C4^qXP#?_mi$Et3v+rNx+491dwu5)kDI!JYuU)gPJPU{4acjdxx;4~oM%6i%=j%)pgF|K8yS z9ySF0Hv8sNP2BJi^GKPNg?>Q*KOz(8rImcRq*==xYJdk`$J2`!2XDp8N2h7pJu%@f za_o6JMcjpYYZIg5fzxE~Tfx6zM<_nQ)#(>|gr+mtbnVNp0Hf_MkSuEz5#T@);mgSt zfze3;rRfI0GA}yP83r2O+5&pZW*g}O?jOJjWM=f(xeK^1x;D7wz<1ivE+Bqw4vjqn zY1z&zoKd#AuKt63xexJ;Snpp<0^j?R7h5smEE|qlFrx?2gKu@|CxS_iUb<8JXp=8! z*qs6OWzAZ>JfegJxNOBJ$HWkCBHA>s;H33beYGS4D`QSqZ#>s{lu& zQ?p{jGANF8RiHCSazhHf=T%J}H0i2wf8v*k&!a&HBPvX`Ps>+aGM1|I%hf!&DEGmW zcTtYI(lZd3T3B+$6`khbN`B|MZbqeck&1T-$a7SXCHBhQX(WEy@22P&PiAQejWRn2 zO^H%>s91Ar_#Q8J%KweUAw1w|okT~2+qP}n=EOE8=ES$3_xtWz_s9KTovMRfr#p4d-srC$_=LT<%`q@26Q0HyJulo~ zGf0DZ4Sr!yt9)J5^Y*M=uVudmiH`(* z1ohIt_@C4(VZa8?XChs}B3}vMId>@Lb$HbB+TtBl=VI=qZfWi0KUp4s<`uNnn_1`# z|Jw>W4u;JgKF})HhytqG*f;_|NJk&C)15;4pMe5!U}b?&Nk?U%D#a1g0@$nxKLm|s zNh5YLO=S!Ib1@YFCYY7XLf+pgg|CJ$_YM)H4M_Hzlm`)%K>@ zkfDciA#BzU8>!@(QW1~vuC57g+8K?(G7#C!`Q~o;4Qm^~uKt!U@TI2xC3k|a0hhY4gqY*-TftTX_C1^T0-QBZ1h z6R*_Ioz*ccqOW;~RL+lw$t%o~%`QwbSn>w(k=a^Bt+o`^@Nb5q%xoUc8n1QL6SLF;1x~2gvX7M zolD;b>_6a@5?F7qLr2eMN_8B;#Il16fQ3OASQuEEfrUXBSQwh915OSa-hOE@w=?1> z$of<4+bMs~o|Vw4JO1{@-!fk`2fWL0t(h+l@FgKVXEc+jZlCWamFjQUYCW;BcgY(0 z@a04$N@N@ zucrUI!@`5b6oi%;;wH%?EXXfeY){KMm4pYk0$+$P8NTCv9{B6@MXS|`6z?~k)I_HTu+>qD{xhtVy9=o@HmA%SC8CO1Eo&*!_EM6{y& zaV))hn5v-$oPMdGPE4|gUR_(X-J}(KZKEf_Ml!T197ijYU0;^$95-t1&1QR6 zE+qGx_&(!DUq=9bm#oi3-4yO)Z%a4H+wX3h?V2je_F{;@Yb27oKZ2L9{W1rlya0&u zF;1Y_5orSdjWF0j9^*<(3J9c`f8l9rxaQ_t9D+|Nel2=G^BEm6H$+V-=3VKt+bB}AhnI`L3+Dsn0>pp&ap$C!p#m? zFa7>plV+nK+h?9IP6kyWEixxWWPTHw*ASyv&>k8DCe~P|%U_sA6SEaE_da~a)@b`B zy1NmR6Mh7rAbmHD+Ik(Mj)pM%prLP=i}(5^?7l51Xxh;ge7?Q3_ogO-IZwsx`#~O! z`@hnFy42r&j#abcb-1tMTkVKC{V>lM(deubKPo|0Xx~do5S!|N$@H#tHp4>xUoz<# z=@Us7Jak-oW8&80NUb*&e5|-N%GL$dW}yt_wjqtSUK*3Eahwm3aT1uw-DewvU`&Mr zOK$WT9QI5&@Y+zz{x5OBIkYm+MV3ebbFKfssq?CVnTV`x+DNF?6{ZWj(?;xpZ2cjs z$vC(uG7Q9GCpkDGjye#Fn^Hh5A_1|8=(_(xSSl)!czSI(0f=iY(aDNKZ#_ydvbAKM zucvb@j&3c1;Cg^UscTtRz}$IF7RDdpB#{D4Rg4Zp6hu^4ghaBH0nhpT2Y4Oj1I^76 z1e=)|>%1*>d&ndjg&TBYTP~#}OC#8p-NeOnij z{rdd{BGC5%9##N&;fQAZ*!?=08)Uc>5uM-vcFi8rddOxdw)w_OzO>uur~ZZ>hC9ai zzby!y|95DQ{r`0R0#dLhVmHN}uj}R$z=%uT5`CkLxo@KlId~8epd?Wg(7Li3w9q9c z<-R|=W1=Xf+z22rs`ONQd}&%dG|ETp>;4>lKW$g#?S|CPvm=qqV!FRX{TZ7yiyH3W zocTDg>-xvL$j3gFq9!HkKBXe+`da^o+N-~#ygY3~eCB?x3jp~1m|YK9X?ThlYWibj zO6xPh)WF)9;L_U88mg zSBybGVWtC()k380Kz}$^hQzG}amEXjtOx})Qu&OFeM?`JWZaj6-hlj^rfQ*aPKVu; z@&cb)AOOm7|6VsD=Ev^t(=*NOyD)6w7I@al^@M@>S${yp?CTlQ?;u?5_35#l4dv0N zHISiTMMf>mkG=`_E+Mst9)ybn|3N5iOVg6g(LvTXk&<%*rE@{Xmi{<|22_QWE+F>< z@em$Mb>`X7)uFSpUFNsilbyG-Gmp(fYdblyEnsOa_TC5}wLiBzFpX!@NU}NR{u*bBmlqRN9`=G|2+E!q#i3~xpF7bx1HaAX+0tlN? zlw}g|x2$Rpb`Ju^rY|H420AxVLj($5N&b{>w7rTEPR!RTAjg6#4gSf^c&ST-&^Z># z93gVt-G?|D8;Idt$g%PJILgIQ?bO(5z)9F=|65)* zM3j-E7+i+SqHomSD^WvuYfs9^6b*&~g%A!uk$vAlcdO_}xs@i6J|{@;^%gu)cbw&p z^aRMNanST|hWNX$!{}f8WbO~~*IGfy+f;B&*_jKxU&*qgn)nA-e{DT&@2TI-3&q6@odO+;Qx54(j)QS*~>iih_BRIY-A3AfE&Gwql4`n_1 z$zs7F<^spGBoXfM{}9Sl5&}#r8(<#>+%h45pmEb-h_uKkDTW!S4?_7XhS(%67_vFn zSH~g}^jp$_sy5Mr#w8;;9+YTaRc-<*;Y{S|kF6F23R-TGklZsU(Mw%vWzr%U4Ikcw zG9X68(os(yY<@`{?7j0`QK(ps5xZvGBG=-Iz!B)82!+eBtJh;L^#j_gnli9CQ~sGL zb0qwr-*)ey19zZ;c+jyQl38km4(XtwhXkpRfuf|2-_KeL|M6hcJUJ#828*+6HWZyxxh=v|PO9=ksyg;Nh^6pCVj=lTAi2T*b+pr^y;j}p zo%1M{%ZQ**>OL{y`~tAbek&0!GRC6J3nHbxFw*ESjn9R(M4<~4Y@&%AT68jlCAylC zEX{;+{KHC}2X`*IclRDyc3OUbLG^HZ5%?{xsPV>L6+5^@tNb{rGl|w684D)BXxNNz#GG`Q-(EB-%XKX~AiksqjlVonN(tyg607+1Bx9YZqz|^z zSt#c`CXmWNgcOX3^EElpko7l~JY3##DV+hXm=7MNj6 z_oW{9>A@d4PrWp&T@^E@R)X1L#99((EL<)u9_=XL-w<98?&(bl0;~_-zRn zvU9DsMPX*#l)xJEG_U+72g4atJg!y2fzU1nUfvYNdI<0ug4pa}z@8}#fG*oNeF}{X z;=ZvN!27cq2C!4k+6!&{IgKP~T7PI{6&Ajs zHC*9_Y8#4y#b@%EQ&`*B-4Q^u%fWx3)DxK$Kg){qY732~9S36scA7t}Ovq+_AVPNE z`Qym;v=u;(pMSGC(-w>|ffGfy5fST&hStNG=G1ltraAD?-&6qK4hrl@Ah@Bfc_ii#sS;p-2Qq7O6h;~xXXps zoUkuT1R~pz;InH2H5nW+um7PJf=qRN*%3JnFARY0WxunQ);;RNAQb%#GJ|`e$9La7 zRKm`Gp}?P(<5o&m6)!Axo zNh5%<7hvs$rK1n#=|ty`?8tyZ{@iQ_BC)aFTQyCJW76>aV{l#LFdCFcp&f#c9VaJE zU>M@ANX6YsYrHm9DX7LQpqgcI^6~r(&m1l2w{Q#9Y3$EWbw|LKB%b!&Hwxul6@w`^ zwS$V3)_m=$MC>oM;gVyipj+Ot#{k7}zt^%?7%&QaF0+u6#6oMT!4CKu1`0KH<6Kj=lM7N@#SXJWM<-Co5n_cSP&;<@;&Hv}Q z#$5&BCpZfyC-Aj~640Wf`5$|qU!H!!q%`LQQcTj=k;iRAYm(zgL)m0Xqb_}|2wnrJ zoQM@i)?}PK!5-DcUc9FUj15T4wb)A-)VL;-_RR3*6!*R#zi;pRQOyDim9MQZi?`lv zu|jW5k){Pu^4IOTJN@&_H(`491zAjVXX;H4{>HdB<5U6$(1ZM<0N}hG0;`JJ0VZH_9b~g>=mJ4vI41DbjTVziquP z-9bX%!HlGP5VmREX{zh->-4m58sFqh8)vBQE(TU31DW=+lu`*r0AJPxq4g+sbKv~$zHxgx3A!=BVWX>rmVF%`p^6Rb8DzR;(W0Gh zbq96CV||BBwQ3~Jb=#v^i4u*y_=seOR`wjbFG!yNn-|AjD>K5E?1y1p|X#_8pd4 zHEJS3Cq_BwGC64^J0&09(@bym5i1y!Rn6s`M>22qJB$*RakAc9H|s3no*U4@NQLuj z0ElQP5Tm5766vkLUgs(>CE7UcTJ`iG(8sqpC?T=ZN_C6wHC!6^%uOm{jT7@AGL>5T{M;rtHp=L2J zXEOEUeffbyo=?4Rr>k@*&{vwX4jr)$y_oc)IX|z|9!tL&SF!&UqRo1&NxaB2aKhSR z&L|mFK74M3YijkrR9dl)72sb!Pcct6#7Nyh1BZj1uS3=!M6hqMJd4fA#qm8LKq`!N znzpi5TO&i_M|a4MwopU}zu_00VoV~n&RRGkocp*IyHXQl=tCTsRX6jMt_(H^_N3L? zgqAd50UH}nJ~XH-O2vD`bosh~{k&{lT zXb!>|+a!g6$i+;>%oN7o+9d)~Ue0pZEUm;KV3u;)5yLrn#ld}K!jkrI$1tiRN#&kV zEc1%0ia$lGuPL*6{xa3YkbCpx8io~^KB0;GE;X3}B?)oK^RSJ#hzLizEc4CVQ}v1?Se7b0*N#=4IpDa-rnP$y0W@4tCEz!S zoe=({?j|HsQ0Q7WZu^jh3g@P#;an+O#YORO<2+$D>WgK7#YRjimrQ#V32N=Z7+ zsFWOPDWV$5A>a`syB5kye4!zMD`I={U*@3Gp=S>49}s4K3~Pn?t8a&o2ymw>e;sbZ zRiyD2a3|YH0?NE{2>lp3FRDN4F%4m}zIdcq2`^prObC=Z{cSAw57vA)p&#eTzdRXO zZ!#z2R8D}3KiM3Vd8&!fJF!Q*Y?SYO6Q4hbvr*#Bg8}pCNzF`xKawr-))e{do9S_U zV^!=b%mtq)s9C7hW0*_$%AOH~q%Lp6&m7&ziT=J1$x31x3IF=XrIKVIJYz-6j&SON zhqDnGI~q!LsW$Qek%}|$R|=@Db}TL>e?ZJu)j-djz2u5%NyD`vT3WB&0-S=jgQ&}L z;>CdQ1CBw2DR$@yXiG_;z(lNDc^H{lFcjdyOuodtxiPX#G1Sh+7WoeBVezR!oF}q_ zL2g2)W{LTVn)2xoco`x=ttScotdn$IqV|r}LCRsfcr|?rR^LfRkm5qb%^R{*C^BRF z$rkF|8H;TzDqb{-?p8(Y3pN#V>I%gK!brOp>ISIOB*KVf6_XJ`1%H}duShr*tQ{Sz zsz%k4S1DELt-EOa@f6HdI*sRx0>8@?^oe9K{&vR=4e|^B7q!Pc!uh=B3Wv&U#JzM; zt^#jajc+N6gPZ(cM`0w%@5(83zlZ3X6$ImS@yU6kD3cBy{usyoHTj; zmWQ5id$GS|et_lW(ClA5qTOVZuU9L!S{gckbkjf0`tvfZ>5U*Ir!Wu+^Zo{ z-rqf_tp=@M<>qUVI>q0M<*W4=PolmK_^p6iF<%F!i}UHt&KqiDepgAUxp-%@P>ghw zj{I|YY*&lqj4F5TzvT(U+aQ8cuV|?@a4?mcF(LW;*|ow7+6xOPtRdUfYw7(?bwH{g(KP^uK}o$8 zHjNmU1a*hb3t~LcvCeT)H<~n8xT%;u*Cn>t;F1KfSU=Mjj*X$Xm{k`B3^K2gsD`_` zOVVuws*N$W<6m!Vn9Qk8l(J1*>kSiW(>R-8s)W`!9ZAAj@(r&878OVpEjES~R@wcnCh7 zte9v)6%d3i5o!UESP1ko-U86eV$DD=7t8~_Jok4C1J&e&S*4e;Q;1axY+hX5j{D@7 z(Byk%t-L)^G6Dd)y_Aj7lPAJ@rFyjU#6fOVx2fjLVl{ivE`?jxpyRIFOGjBWpw0+U zhGcj6+LHDxW+{H#t1e|XT#j8`}fEA$H9e96i^8!!(90xpg&Bgz6 zqm}yyU4SIx+fv#M&T0NW2kAeG@IyO^!6f@QhMuSnNZo;SL)M9x5JWX&5(^+gP=N?> zfdC?8@-M}I2*K>-O=W&8y0?$hdw{fmy}G99-3w86k`up3o(!BUn+?$JFy?sq{m7(p z?%QEG>$~Rjb!_(9v+Pd`^(`YQr31loP9V?b(R&|P79Z3zy}v;qzukZEMj#&(U*z@? zFnZ?K?y?U6S7Q2;lB+2V;E*>ou$^KvnE8fYlS``p-_NX6891c04+8{TBsNYs1{qU3 za~BIjCRWZgPzD4lK#Rtdoel@W_ME!Kx*aRAs1lk1#5lMec(cX4Ny5rFahzZR`xCk! z$ocMzf-!}gPg235%i%&w+&>;-GDW&%sX{Hzh*x{rpilMNvUh-g& zDj7bEN-d$FDWoduTVxSjsg&pFqS-1ZGO}DgY?91(%f%CK03F*3RdrFi7WM&N*WA4X zuObXpddv8r0*_y`rkHAxS(E7$PVII3R6bffH8l>(_1WC@Mz^DQs+pASwN~-^Oy;rW z7-nsXpe%79K6LT(++|qVptks+*fEJ+W022X$U1wSIFk?trUL?@Q{>U1;zeGx+i!3k zmkm|X0rT0|fL|39!i2(6I57GeBcDGl7WWm=*36baf0WnPDiB3(wU=S6AF<<&F<-OD zgW>s-!&U4BsI>Qm=fhWA;CC8?2%B=sRB<1Ooj(=}hJbMhs~Z~EB$2{FTje>2C*5WX zqZD_vsVS+t6(J-pZ?SR4G3Xo48(z>LaA~2QF`ij)0n`P=ahxGt9y64KV)?L)=b6ZX z^zYFVSjEDVq_W^;(hnFlfoVzXj1uD)R*7IP=`;u=IW$^{T;MxgVk~b^Zy3%jzx?>z=^^{f==M?6Hp%#-Y~ctc(~Ooaf_(GMWx@^YVy9Hx!J+Z@f23+ez4-_^?AP-x!&yH zy9_a8BNe(H1=e7QM47=x)h0uOVT&lw;olH;N?fx=rV3^x3=npn=S|rJgzo#we zI)m;OCvS^Y2ZgKsoT(*}owm zuqS%7SZ&&_mT#8txB7hiSYH45I={NpuX+l)n#h`&_@k&-4bb7& zfviDMsrVUmFrJbTC{|B|_A^<=BH?q%tF=$a*Y&)@Hh|a5{dvuH&A82APPUqf3$P8{ zON%?o_Hz7(u0W7oZ+3gp(&CrSrJ{@YO&hA{=J=>0CePN?2;-_U;=yWn3E4C&+~k!o7BDN$=1 zEL5TkYtw3j7KJ*=K^6m{IMj+RGkXE^AHf-illN;k+T<16?MP03CmTAr_umAk5{ED7WYuBDv^xbP zbWo-wM-y~#P9QhPfDcdxOvBb5ND1N3eL)64)GOQ+!AD{v)J%wl zU4lM!_5SSedTL7>Koy=GsU+wie5G0eSG(2ym5hI-=*o%egxQ0N*uGH`NAq?}zFQJ^ z5qj$I6;2}}KByHZ{1`S}wiVsNAoZe*7+O77^j_BI8(Z9T7cx{OT~swF3iVGpa13W= z9JI)f2C+x1*OQXXS*?w^$U-4=UXJEN)iT*fxLEKygkgp%K;bxO?RV$83HPSQDGf<% zG$v+R+ORMRGC9_J1p_Lc_L4-PL1XpgPal~m-ITPNQ=f~$ADEu&&6*u9jxhL3DCb8^ z6ksH%6gb+K7H%pybCo>1qx1=1tJcsmeq?_aF2g;*OCnVcJyx zDr}iv0c=@&;c?i(G*rPXtr)b36a#1WM`-si z((jsaoqo)efJL`X`w&;x!(=NU{?2-0U`xN3$^WfGw$pq?0?8hgyFCCLoBkflaW9+E?l5Lk{?_XD; zEu>`*vvu>Eg%nb+xK!LHy8v51q|S^;719-&O}EB9zS2+us|(?v~?|@ zBA(h66ja>~w{d6_1yx;KV*dLZ?4Ed8d5@vfpb&E^GM_guQBQX76^8Ra4K0Z@V%ifdy1LHLUD7@EN0iUdCF#& zPRsqK@E3E-X4&11@6KCA745g2o1Fr9CZL;!OH$~CC3ecHYqM<|k0Dy>tylKoy8jni zTZyEYuM(&9J0`METjw394k7`Uw{UJ;(UM}YiA-QqT-rh&i~iAxqj$B+Frn4m|X9}3w2nOwx| z16OMlkx*B_3#ljq8`an3UG0+=m^5?w40m)WaM>CbUmdC3`%edev9!QMe(3u-wJ9nv^+TzKrpzrp#%$Rhq%N5L35|354& z^$rMa^$v(T^PvAPn>ib6+CCQ~H9#}&upQO!M!%p;0BY1Gftacy2qJxY8p*6FL&gh7 z10rcesCC)a)MlM#>ii0jS4r&prXmfCLWo@ISuy^0QH(U_>BMt-7-RnZ{pjHQp7V$| zn^6aL&cnl1V3bSnJ0O`hI{BgAp|?|%hk;xfHTc>s$!uiy&!LSW^@FV19AKRNw@PwX ztU9m_cn*1Y*Mk-f4zu_>j)MT-&aY>`zW3CDA~Jn%3pRMHo6+UuFV1C}4)a9FAqzi3 zHjg8|{g;|;r*z$=J!%SFras^~8xp}68S_=~jAv<>?PD5_=2To`Z@kur9LzU1uw#ci zzn^yieYz_!_z|r1^A-km1i(w~!)rni`7Q&uP7qB$N$0?7aA;X7pjbT&5j8j-Fp?Zi z4|^$~z*e#3-Bf&aA{WukUy|fqMY?9zoUEp=Mxz-UpiQ=n8jm9om9bs1m;?c*V4Q&I z_(-}1?yDpRqPvkanzSWX$`zRSAv9tpd!mu|;ED|q@NUSANWxLX3_2QZQ4Y$L2L-4N+QKxF$Z$A=Su^70?u8YR_C0 z@ct`*R@l+ffoo-JY@mic4pWXu?m9S~tR}!>klTprEnV6`y5v#t7JEUpe5$gm_(@S| zU!DtOm0M7fpLZl82Do!&3_4vCbQ$Y7l8y}^C~T9oF~-RMsVBLqNJt-z#`a$FC>-@o z5-`J#I$`_GrnEF(erJMBkhwbiCp}}dQqsWDX7~Fx+57<=ipkkjMBkJXpUjPO7^K$~kPJDOuPiXZEC6d0iV$)Z zbsdR(%fv|dM}Wl5D^)8jRa=xh;VLoV>LTmp8{+x~Ac7|bp#QCwL~NP;N~YH~&srE* z`3+^4A{yhr<)AfIt=vD1QmqX4vvOX8VnamWO->P^aO_=dE_#ypDJ$b8W+D0wGbXpY zGEDpuV-cE5%P_rVjA;Lf6A5-CIJwBe9cYc)OPPyi*Jd=*$~BWi6{{1(5!8IA3+_So zxkL=0keIEafO;`+(G&w{uZHMoe+}ASm~q5>*KNmenxAI_?)iY?wVdW_u2s~&?^hV+ zuRtVJX)cBC14`6I*l^=e)C_H-?2pb4*pu~zFYYMftfTZlWHoj@VJ_eZKXTO>aeicB zHFSH>HQ_T^$8zZfY1)ZLeZ5?Fh|cDxD|Y}&@q-Q`FFrMq3f1$NF?hG<7k-zt;|dSB zGtxh43iAs${N2Dl(lg98R_HpSTSbSp%hH?ica4lWMeGt3tTi+wPoRAEsYD*E>QxI z5OQR%WN0H%BV?=~=6u9$I~h>myJmz*ok(IPF9*?6?sAA#jp>|dsA6CN8HD>BS)OFH0d&G(0aUWB?#DXnvw?K1aIGjuvpu^Vz1AF^lU_F3= z{mBOmtZM9kfi(||g+o8o`2*kpb^TR84y16Y^(iG zD&5vw_M#v9=IyiIR=$)8P^|bHx(b! z#$U)`1IvAU?d-c-XsZzFo~8~XFT*Lp!p%R6dV6;AUnkpE6NK_#k z5}CLC$}nnCsmLs12n}+@?84vDj;H8zZu~Zmsz$cA{kp=VAdKP9admgriG71nQ_|{^ zj5;*C2TpBKbPe1`(CeH^dkH|{6Iet?k}7pIH7v7vYo-Z8!XgQbLWDzsxtNqxK#2aBv}f_M83@}l z@^V{%VMo%X+XkWf2RFaF$M4=}TEeBwKq!!c(6C!)o)5}u`(W`_6WkfHLl^TwJ8)1+M|!)Up5Pu5%LUTksD3qMPOn z!A52~JP0-}Qmd#IPgp{Q56q9jr4yGDPvHSHAiPNl7OorCzbVagAVGc03XEhC(>>4R zzh=*$lGSUp^kdr!*Yev(7H5Z`hFZ|suJ)0Gxxkx(<(%`JSKFPc`qlPWXyc0uDP>cf zz>h%=+sKQkV1LJuV=-5axU|KbO1}CYk?R*@1#7(@lU~7m&BWd|O>(IbkcM^p$@a{ z@?$XmXIakkPp4V66NcDO6%v-LQ-y0g+dF*x{7s8;{?h9WNw`EinZxt?^{-~Wn+NCY zJEd(?Mm<)2jSlHStRT&8vVW5$V0Mc|G??09h;$YYL(XO4-|fw@wu2?BT+9GlXlC$7 z>ERN5`lX|LaysUuygAdBW|r-Ij2Ej~t=gJgtI=zY}Wq1{`XBDBTY^Y=?54yOX?F6EkIB{m=Pu9<|FdA9P%Ga z%K{zQKW7~rPyW*Gmem4q7SFrdt;54h-b=GS85|IpgdWsq^WNH9yp?dldntD4Yb-E{ zI&DzcCGD1MYxsH#NZbE9>XlUCx@ZlxntnG$Iz)Da+x2P=F2|*P${upHf1Ib8IzyY{ zbsB$7nq=-e-C2r}j^#Ej!bHqjLwzs?@CEB2&`)=`b1RfliMexGY_B!Bzk`)nw6EAI zjigjMBYd-sMZm`F%2>&0PmCTtx&8PMHwmnl230Dyz7O^(``m^nUDm;dG~KqK2g)$( z*nYY4t{q@@FlkVR0ZnhaQ$T)*f?)s6%AR%`h>Q-w!NJ6ywonNCx1g)5j>_g2J0jYm zE_k@hseiDG`wJri;~x|j7ikGt%59=RRtf5DvYf!+;$Ro*+ac%cj?y%5D;O5I)EdATWB0!ZwLXrBc#1k$lJTfyU0k`d%kK<$S>yLjwsOPt%6xb zegB^jNRIA-ojeLojEsX=2VkqB?VVhLMC@EB$iD{h5bjXG*Z5Ta-DH3^GO81}m*?=} z_E**!kB<%vymAaeLx6#<@Bx@WeuqQ$T6JTY-J(;sd?@YniIDWF}${0Xoh z_FvnuxH|j0bw0jQ8bdnQJ{EEj2UEm%AumGxDW^Wi_CW`~aGHYpL4~?Mx&`zUL52}Q z?w~dwKICdn4*_tmK}YY1LBKNlp<`GJq8*>{$huMk?P6`h9d*vHIXKG@AY}oNnLw* zvcNzbGdyf82>m;ske>Zpg8Bw5bX`FA5~TAMnkjJ0_6z=_bO6TvCG7X7qY`p2;L5fD znM{aq2Zix@kQRX!|3m?2#Yy!udn3x)StVav1Mq!-FUSy%cN*M18?#sA2eF!J;fZZ)u7kQ9GNJ0NEiJ^d}Rcgei9d-(Ds6i7S5(qomu7> z;0t zz>-SDKI`K~Df1W}oExQ*dULfk4#EFHsBglCS2SH(Ok9Yy{G*R6$cHU@k&xY8Eizh4USg0_n{I>tg8Tw88k&CEh zb#?Z)UJ>;Psli<3G0u401Gi_NTMu9-iE3O5F7Hh@MJF9uj~Oxh@8_gXMDew+92OFY zU-UuP>JeEV5fgmVP8&Ylibxfseit%~;Kh{E;soh-#1F3^GuTFQXeR!ygy;+02&Ix} z9Q%_qU9z^G>mX!l6t5Qyz&~8KkXNWbYyv_CX!L1leWfnyR|!MjVR+;Ow8wzFn=I=- z%-joa!S2+E_hzc#lrAUg*kl2rq*#}%(DG8O8oLURf0UqJJ0L7}M?O6<^7H3m>~eI3 zJ}xx(MT)-NT(C<)P$ZH_{6#Lq|K>@OKoJ8s4fj8~3t>i2MwCQe6S1oFnL z`no)}>;-blDOPqhN>9PRrQ87r|LE#C;V-i5Ew&{^nX=s{gp~#gNYjX&|2{JnOd3+E$l)e652OvCUL!PG9AI71zAdZV`v~T1ZPCJcbDPaPdAO0@A3s$XQ zUOq~0{9L~Dxkq97UhtqjRAc%yhmjc>_%e+T(bsL6ww_w9XQKmU%WnMj_YO`T3ByXj zsfmjXDd<$5W@_%}r1hslVd4++BR{#Dv+>a(R`neHW^&DKMHTx$`Z3uSL>=d|8tqYg zO)8{Y9RJq-0!X%m;&W{QR>ki0QjP&LPOrL{v~T=m@XIgv89|m9!9Ai|E_L1zSL>Cs z;u|+XP*V{M;h~uxBxW>a7`Iy+)`+OtE3|S_nkAxl@Uz zBS#A~3&O>{jn>A>M0G2dl&czPlz-{0odu*!UPxqmKN*=Ya(V_9`V>9S*S>*n`LAd% z3K#jP`uBY333Fkg4L5x?{@hLbz<;sqIog-&&` zRNfI0d~_Gw4bw!&8Rmd4=e=FCIxozy)NxD1{>iastVL*p5fBvJNP>ZWUi#xg!d)+;=BZR1?>QP98BA{~v4VUOMqC+Hv+`~A5Em-%w zWdngVe}32w)0#;)=HQu9+2gzAw`$|rbkZypbrp6=|8!4~@CR?}S!}p7=}>Y3>qtw0 zQ1KS-y$$p`vpoo~xZHO$(n8kfl!Mq3g)Pb#dM>3r?qXNl0nPrKF&ct~HpYr?Ge$+@ ztmW8M=#;(9vvo7{SS9_lQ3dxo)$I{xux{|F7BcWRUsi_U&Lo7|2}T)_Z0 zjA~V79mhNFtMzBM0BfTA-dveW=LT0>Smy& zP@H~pw4uQ<2*b-SwM#eV+rF>LJ~!_boSWvRNh98z2;{Vf@!G8xTy1I5+ltr)LRJ`{ zb7}IM{?zeGJo>PXePM=Bg2!ENJhgm}q-Qvd85O`QssJo!nhw*rtBOC@>(um|v~VDy zj_G7d{5{~&cZo)iV7R4^i_FmdM9-Z3xu%wwvdb8`emCM>w0twG-hJl4v5Hs}H>ycT z+rHN8$aAMkHh;|S)Tp$$Te_#ap+jFi;9E}=vOLg@d&0z&jThy#A7R9GuoV9)`K8pa zWecDhgk?LMkQ2}D;W6%b(>!b>vxvYW<}G%p=>Al4CYcSfkvFJkmX*hjo}l;?ynfJ@ zl4Vq0)5A_zbdJ-Ioo$6r_Gq~KYKn+UiB&1<64gJf-aud9ViG_qEWPWeeEGYVOPSn& zP9Kt6*~UU#^$--H#M61uy>uY8yC$3ikq@x6nV3+g$B56XA|2M(wjzi+FDskcIE><1 zNVH2nE;@M3cZRMQ&vnbBS(YTm@q^oaIv{x!s2O}+`$rmmV)mWqG%;UKy^Q}MS-p6D z;CTAqu5hcL=1a(^lSI>MB@TV`q4Rhl7~SzW8j#jFYbf%E+|xewHWUMf{A9L*c(i0zvvio+LJD^^cbPi(`RxSsI1!uqth!ll|Vx#Ze@Gyw{ZcNc&Nx}sdX z7Q8?FZr=7S{duCrmri6Y|k} ze!u$6nPl)1US>wz4T0!HDepxLya7wZ)Iyn9-d zE&A1=y^>?0Hd91U4$MeT8)2TWwCX3cJ3AuaR*w7F*j_(>%>&{vHZWM24iRdCSyc?p zImMhFa#LR;-Sf=>J=H7z3ifh=xDKLcXQ>W@kSp&V#StyxEU52X@)7XuGWeoKU(VT( zBI!H+Uy6$JuG9l;L7g~m;^-+^P3$n1cxY7NZ9I*l$etk`!0q!=rd!Tcn)((lm00K z;o1;3-rCA5!&ZyZl18K0aSRb!L-+MP({Us(*xn`FJfc(O!>QF=^RnYm(_9D+N02EE zt?Kenrx_XgJWTSCO|z>9KEHJpPaZG&by^;A=B*kWu&amZ^O*oL?xJUo=oP~?^rYHs z3f_hUFLUw~6J?zD3P73eEjaNBQ)Epl86eEk%;NGQzoOBk;|47`q!mJDn+pA$2x~Fw zQwD$+gaeHSsHSA_^vKw3fzCBHw0(b!9Oh*?4XIL1Bs-6H!cVGcZ zhwTq0!!T$gKNOc-=desd;LO@Zc!Gf7^^p@-!hmHFO>L{^;9x3msLGD5bXa*?YnL31 z&(f|)!9}t7h0v%$PeSr1VXTzNd`WeprVFFz{%d1%)_P`xATwLg<=AXXdd3ld{WI{} zmZU3>f(_u(9WBJ5D`LMPS(D`3^of8=%%@LwR)EgrR$aGl}jlRSEXOi)OhAwjJ z_GDEVO&n8~tPmoS`dDUI`R>k=C$Hv(?;-8kS9hCBHqT3zwJBo#YdiYt7n8X~;)-7U zr0e(VVakY&!g6cXY^k$v(OZ7wd$|O}PGDUxEgayDNRsvP)H9aU?mT_B1tFF}{&2;I z3OeLDay1JS&5|;ASX?j?BQfTjks(Je6%EnYeu#%Sk2$QANsEVW4ac^S6V|0c<--V#`hCpYMY! zJqLh4=p0>G@}Aqdv1MgS;ZU6mJ-wVDXsRJpHgWA+hNu+<;92QowKBv~x8%qop?hkg zCaySPJ{L6z%bd_tBi8ufbiFl9N9-;e9K0;UQY*K>0=`QniWUC`_)b1h6^G1ws+o_p zUT3;ikeV!C41RH+oGS4n;)j|qYn%vn79&8g4DV|Ch2E=+eYFx~%DO*RkCt=sr%|dS zC)R2GMX15*P*eZy=hoSvgP+HHMLf!?#n?kcX!Dt*pSCm6-Pj{}Ud>_rSWQX8>^qSMcr>uTYjT?7{`mkWmQ+<3Qf zxr`mv?{e~L)?XrO6ctL)Fy_p`cPX~+0z@OpnEK~lZtP^&4FU_LnA%OXvEt<8Ah-5l0-uQlp9g~s4L*C~K%2NBa9RP=gjop+ZVLZyXY?#ixYz=TN_%|&1E2@|`NOJ}`%g9KwZNGo} zeyB6?rrVP+Oq3d#s*egeYYv!TBiWVV{P_9df0$8KBE}Uf3E5OK%sTe^+=>cV?U%wi zXJ(#xZz!Xk<3&l#a7X=-e$N@nv27bri%?`HM?n8ZEc!zfUR2`%%#nZZec>ZY;Wr+A zelJSiLJWeA)QNfph_yW(HJmQN{f0>+*i%yauhWG;a)R%9)|-?Cd|v<7Q!zK56r`;v zHC{9wK?4~2N1yvfWkb>5f59q*AecamC5 z7&G!gciKOxY}+7ua+V;>K2H>Dorn9!gT`a$Qwkf*cbV{dtgQ9NAs?*Z*u8s;YP(@V zym0Kxf%I=$GAjCEm3wj~F5$4M!58ixI^Lg^4zOhyBHUuUjnUm3?(#L$m{o>~)R_~}(C}Lp_irliup^>ZgD$X2sx9=wL znufO0(=8P@TSsR4ezzPwe8Hg3j$x&JyqwovY$8)l{BiY)0|}B)9fHGSiVZDo{4Hi( zc6c^C-}g*vnq?JK)2Mqu85h;v_!G37q5EOmN%{QZCJdGah)_fF?>M$6t_DprdA8ED zi~g|OEjLIW>ri1R)QCP#yG&rgqvk5zcn>s%O?ta^6@8uN-~BFZ;fpNWV48_x4I6w- zm(*RkSV>v$I5`_&hQ^Ttd%YQONd5_@Uzb`eVY&PcdI+6`^xG)40@e$ zCAuJ2R6pMttc_BP9V|4Rbb!Ow@LO{<7ELah`4y8;f_ZkQdxt&}lL?B|aDC!0bU#@t zPY(I=T@8KL#zOp`i%&Q7elgZ9R?9QS;*p36uL<82W^n~ZB@)()oeg&MW5#<<4KBi2 zKqT$!F8ii;?friNik}EpG+eSu^+vf)IhED0JQ|zuv;XG5D|I~6ekaRZys6L$+BUyW zmzok$iA7J1Ly}F!{L(vU4YDrStYZ$tQ+_y%U#ju!E$yKn^i@fMYz?Zr3o} znwy1mgA9r??54fNHi5zQ=k?_6ke``MruR{3ACIbhwCVVOt0}ns^u?54mu(ipFJ0)e z+V}V0M>MOoQd$i#O9)-s!JuN|<dBKh z9`EXr6V3IsFO~5Or96p(lyzTv-P^Lelsj%0*WhSDv0i`LsgSd+!{3bLf=~@;F}L-) zLca@hS1s!TIAPu_VPC`4Y-nAX`TWAzU4t3>Oao(T|&Cc;sG}E%F_BQa-^hh8i6q+NsXXLezWM` z!X9scEhc%78Ow;8qkXpkz`uHo&{`=oZ?xiUsTZfZZ*1!-Sg(KnE+$w!+BAI9E6xN< ze!O7~o0J=BS^74+Q5_?>$bY@0h}oA7e#u-D0!)6=E#um(Qe+BtGVml6t$`5Z?fHU> zfH0aWRaRN{&&zWoesN|CaU~2VbKMCXOu_Ad-CTV1W$ebQ^iljnZ}T0_1YeZ|JzWx4 zTV%JI>}9s;9s@rYbc!G;<8KQD#F@ihAZuZtRabBnT{gw>pH60Ov0y>n@Lt+XiD-g z@#4z94!=B~@b9M57Y}qUp4nPWr1;nXRBX0+j9RjumV`y!Tq`M+!Vo`O+tg2jGu{BDtmo9&yC-p~@-h!dn*fo>t0> z`ridv|MdD#;&XXWfj`na8aKuRn3)`b`~lTmxJXJTeNx%q>3avdohfxrpKo+WQ10@N z-x~JD=szD01!U)|ehNv=AvF7n~RT&41mn_u8_8no+~ z@Ky=q1C#MA#9Uhz3O8Bn;Gc{ssnt+_zk(w)cujHCgxC}33+R7ar8Ln1JU*S7PPz@( z1nBiDx4fP6qj3E^Xw%I_Qu!t0X6{~kOq;j!Y>+ojdIAlQ0zPXSWnqu3J)iC@V&Egs zncuz&kTo$95Ae~eLcH*)Dkv=gziU||5y*YlrE$-?zwZ=#R$gz#l59a=(oqR;E538H=uSC$1oq@?zdQQ5YGtQI1bWz@x=F1pCn z@ln0C!GaCD!2}34Swc6&4joq$;&~ZHd8@re&le(;OD5QCr+^oL&vg~{1dUo_-)Bygt9bMea7Qw!Js2dgheYc)lJUTR6_J zt~upZTRcnYB~GRfll0d46Ua3(CiAhU}Zg-%8Y(8#*&?QVq4 zLh;#V?C;2E!r1E$#0}9%I@Eq~{-a$8Za1ey>bmx4L2C7Mr!<$l01L~-+$T5Sw2}>m zW@WlvL|*6EA+KiPmpEWh%56)@xu|2u#N6gWXzx=A2Z=+;pkI_ORgU(dx^me0J7c?= z!SJ00JjK?Wc5q9!Ml(Jk^aLY|M%Y567ze&p@tl4XMUWVWr935K7I@1|RtlXF;&+9+ z4Kg<^f7U?9Mkiz_1xra^no#seBTScpN&=9jbs42gk?nzbGYG&WAdGM<3BS)njv;`M z#foMHP9AdTlKyatl`N50=m!%*+Ekcut%F!c4&@aHmK@oAKdlVH<)#rL&V_7RlgJX8 zr%z?!cVmt7?26J&NPmD?O$gB$A7B|1L{Cxy<{zMY0f+JE0>s(}lV%&EXbE$}1aDD% zJe6A6C`zdLDgeD*30_TKU)3DrA#ZmQD$5iC;9LT9_!EE!hG z`TFpUNv=YEYE45M4mi!IufM&vxB+VEpg5=xW!eN!QgY~dm=G@q zY+Za|MnWC_jQPI$koSA+#6XuLaGx0S^vD}e;j>UhLj|^2@=Lt=Bw$Ks1Y_Z2-`{tb zJ;!J0i1^gK8J^oS;4-StGt(SuInbfdO#F?)gO$)Vo2tm-mxbNT0ZX?Ffc z*qei6_u19<^&qK7VcYN&a@Rh|4;9{-5goW+dWulGVb{z!Bh1AWSF+UI0H2j`;u1h* z``UXfF>|p8tET`TYAnnAgHKaoTUqH}e-D0LCO|b)Y{^6?>4CQ7<&@5ty@f8F*Slgaqf8dG2hQ*8ZV{i z`~0*G+TS=E!y6Ka)Nm;1{|)n<@2zpK-*t%Aoq8x7=J*)F<&BEDd61~4sV$cQhx28Z z3IMaWyLV^eH8wCxe%s_hMwAyo;wq9Uc*liZ&@LgZcOiV7bk@{Ihp$3h2)!6t(L(<~ zlRZ5Rz(Tp&KK>jE++VbDWYP-*&g9HK0zr#sd)GofFE@ETnQ}4eI3>iuhW(;nr?EKZ zdnS%PuFz>z8&*!N7$p@AZ7=j1-2}@AfFCL9w?q}3M$MPe1z5ZfTIm9e>r&-g7%Khx zTIgeY8rRA7d?{h;bzP9#*X9+Vik8Xq{jpaYZh#ASTWf1N3xk=vF$y-@FdKB{+P_$G= ztx-$O@oCjW&H7EU=BbII*A``=jkqpCBg<&b+SO_)cT;*XOP#u)21b)yz}uD0s>icV z^(Z6Q6GP|(%=l&cupp3Yx^3$Yd(u!hFo8Wacq?o;IEcFUCHH3dc|~M2NWvDaMlg^F z)Bl#=(Hl~b_Yrm84UX|Kr33jD(BBQy<@?$7fYVt%v&LrdqZMIg9n!j#f|4R)+(9I) zkn)mXtt3QN_(OA7ix>D62~%yi5jv{6R$VfSa<1>*s@T~az-y_Si_aEa zkdgKFcW4??QnV}PeMse5?=PFkc*8CcUqjI9vFf)j?*wdugslN`t!%C6gzP#Fp><~C zKBJCdzwC>+>sL6C=aq0m%Io^Cg9G+t&B}7DQ0f>s*sBU-EvTZp%#<6IR?M`EhFM zPSe;uBC7bImDlQRsDD*GI90wJ=1DOTtpUv!Fk>g*1(>@41iA5M3 z(9m{>j#cG}W$0^LC+RpheUHtRiWY}060_tgk!EyAsa~Lw&OhTB2bSH%IJq%h>z0Xm zHq`TFD?rcE>RTlV(63v*U@k@=CUv=#J0Qi$t#&FEAZ9Y@IG3+2-qIYNk9%Z47)kSt zpK7U#Jy!yNWP#MHzNetXi|%gnRp0oThNB6OB&eqDC~-Wa^9k6@g9lN5Ij46reG;CR z|3FYbb;+~vE~Nui;|+QkP{9LkP@1MQXz&*(Z#eu(!6Q$l5h)lb(HYb)O`#{W8+^CP ziLFP&EaQ2RH(a*Tk8l`o%h2giFMzH0^N|Pvz#IWEOc$OhQ^h^awoC_?rBmgEytYionk-a> zu3qLOTIQKSrygMzSgGTvr>9o^Glzj*JxPXoRJEL)+~iPdE-5aVep+f`JbCZJ<521l zfaqB^&l;Wh<+QCBPd<{=YTJ|PuHpmDA^owfs_znlqNDh(I}RgP#=~Dbr&BC_RB}P> zGB4QpAQFm5+Whx;fj3H=-34#townPk@b7Qj=)Mx&xi2+1`*x`edEI0p--Sq_xF948 zmUeLH!pQRoPWt%s4OF2yXS3N<83H4K9oOuloA56~_@wSrPs0EvcudjwWJM*H0S875 z6cKTlU~c;cPAC(M8$qK=-{3r?>MqBo?SKl(4hyj*;aBeF5PnkE_8-m7PC-?wfmPCt z7PYQ$c#O?N;4mGP`N|PKGsw&Qv@Iv&lh+pP` zHOh@Z7jy3uq@G1t8kGNNhAPGabh5Bi?F9Q~ zBZaw8v6IZJDqN*CEc}sZfi502*HwP~5eP3M%wKNzyRgC_{0n|Zd;tPSpP0EY1c7d_ zs=v5Al8uY2^zYiaMMf|%QFSh`_}Ecd2uxk5El8vu3vH83 nowsvH-&hQWxpIPUk<;Z(@bJR?ql)P=~uL3XTPY^eq2y6 zYcYbc49;*WQxvN%u5mldu#4gU1_%^*8J!?$Pv}}Je@D#>nXV14p8S0@0{cyi za@YxgN1F3`?l)90pa*^k!H}lfAp{9IR|5WCsF=xaqMiD>&(yU1+na^~sa)0A9U%M2pFdVualjVfa zlrGUW8~UpYk17{?8gpU$yb)G;W`&#-Wo$VyRf>MYL^y}eUVdx`lr*g`Z!*@*ST<%a zbn(`ZOsQoDm^37pkgVDqG6H5ew8)pVJ@uf5wLJy4#^258<+pb^v_0SGDp$w7KXobr z(FcBA$;%rJ|Nb#P{>x*0G{B^pJ?>f6dFXNIrHV&Fc=RVmoJp3YzdNjEMEu&S?N6)@ zSvjVW`{-eRKEAD@gx36fI$3R?mf9ckx$|3Hbwg@}Np+LQP)S(>;q+_35HZA_ifmFV z@Tcqe@MU!g=+3oZ!>WQluCnui@@k2I*Nd0F*Y{UU`J0^~E3YSIamxU8VR~r<3{h__ zsZTQGp=Tqqn4$302xV4`Vpn{;{BAxL zCvuQUZ|}3fwb4_%+oE&GYc_<}T7ZfB`Xv>w51Zvl%;9n}_kUX|#&kL}{_4W|t8+{z#{N82L3}PMoe@c(CecMlPNny3nZ+WA6!oCWbNUHd7Q{0U5M0A+F-(da(+g?yr5wZ-B~gc z9PaqVS5)c->&D3w`ggkesA6>p@Ug#hxj>HnHxWs4-s%f#kJE1ozkbT8h6p}Rlo zA2mSmnfW%_gDyMVe#kuxY-R}Iw!nkd4_zQqeGg|as3i=3B(k9aZX|>kAh>1d#D2fe z7a9eK)F0BxBz4K0*`V#DsexljOaN0!#<$Dn8R4jEz6a5+5+R8gm5)@b|;v1nOiw^S-l)-g+G zf{71@DbAWzZu^>}`_l+OvsP4 z@M!u4kI9jZgB#})uN$;m?_8@{!B;?;e4RG|m}R&V`yyL=rFo0~f#A zuUrTSyhIj46AzPm#5+T_#Q4(zOoQ5Ck7k+O+*Cu!3hevCtoKW%Znbw^dD^@Fp6~#lWI4y((V{V; z(AnDg=%tr?ggXMYFLc(cZ_Vruri!UFRgiN95?crc9yvZYrrND%@S3(G1>WjCC386S z(ZuusSr^jp_GZ)%b82$dAnM{*`?BK)doG{2Iiwr`!c9v++%9>D-OE8P0YB1K~XKEDGtMLwrwMlJ7{ z4j|_@p~F4J+*xzV3>IAnwm*20dT69PNjuj`Xo-UiOvf@yG7tK<8Nix%ooH6p$I#ad zlO|y(8yy<}!iK1UfV~U^uUy4fhO-#qvR!Y_qIs4iB0uZgMm#-B^{+a$!wTF;-wF60 z%%Oer74+H!=@JEyS}kchyi4z#yWc9B-Vh@08Uix7Imt=)JkHE{5cQE<2U4;CLK&Mg zh(vdivWgDdtnmL1+-jxf;)vh}m?`bfR)^N+c6wC-!UR*@9a>ZV4JpEymmv()qKcos zc5=_=Zl<=}+F(lQPB+hg?&EcIB7an>ZtKedj&q)y^V}@#mZuh@rzTd9TORJC`xnK` z>)q7k6y05q#IkJnD>KKH%#V(yRn}K~kHyT}-PEQN-Cc^sjkH>del)1H;ZohAgwo8q^j|4QLmN;>?rsB1&@O7~k>uC$cGAhF0xs)_JR4$jR zpB67cMVW;>a!yq=9VK_vG5$d4ey#}LE^`#Crkk}R zK@tF?PRWyUHtIZ!q9|aYg6$+?2EU3%9gW8BZXi4U=0Kx`bUrW#iq<5Kk=&FdmRpSg zg*FTuOo@s1nAM=*RONKJs}y{yas&ZT&aAK!)R*&%logkaERDJp6;b>83A_xKP;5+b zrV3D~5mFv@3$c{$LOL}VrNYyqDx}h-A9l6AB<>LS4++BE{RdenOYjge&O^&loE6oQ ziIf@z5xKs4m-x~%jdv_SDM^TC#Bth}CuWkgD-2hSI>FR0ZTcBZ&F@`uG6*oD|4Di% zUk9hoYRDn!8X-dwrfh&t`6WXUekN-uIZqNHRZmxzTUe?QAsIne6IWQ?Ll&V&sr)-W z$!-W^OojH$$SRT(YB?i75nPrQha)cEd9GcASULqXRH<4@eM-I0pjlYXSCSI|N{I5M zSCLu3Y-~fnl@+j@3(g3AvIc-S%ZSpjqWzS@&>17t(v_PD*T~u$wL}nRi5lhmk_`x{ zMb*f(IWH&jr_+Unlh+p1N|T);ODICEeyN0|YvG7pTcLR~S+viR)3#X`<%iFodv{kOZ zR<7SG%V9YbLavR{un@)Fx3pRASq+pNzu#gEg8oY~oRf3EH)zv>t zZObn%GEQgV@Xix$LeBA6qm*ge_2DDi7d>ZHXj;M6oaueVk4Wmjaza@dQ1%NW?HsVwMPt``K`e4`l;pkk9?<73{f# z%P50qeR6{GI#ek}*8IA&V7lr)rg=~ueXkl`ww&;55`u1&mi*%ZR#Brh@N-ofJXV9E&xWPjlM?m5Z*W6hnu<#+aS+CUieGG znT{0s0dIxb(5}B~z}S)Bb+U)=;xMCk`GY3{dJGGf@)jrBK=d`p?Y?I6BMqe8YwKOlw(!Kee;%C&{$d1*aU-Po8~BQq z92$JGM_I7vz5mHRzHgk%Y7!r4pE>hX$(cd42ziL-k=6Od#hY&8+behPid$-t{p83k z4{~lAn2Zn}m}uMGuit%d%__G2$;`w)^yZLdcCLV%UgVoJo#@i3l{J7ENs|do#zYMO zfWK^QyAlf{n}on4RzeHr1>iTgxb@Jz&TMCI7mpu}0J0Rn5_uh<4#KTg+>XZLCjao( zv=qh>()n|8J`yyyZI$&%_CIxtkQa02LEx1s#Q9 zDQG&#s%jOLrc|&8!&#&A<{I99)&+3rYhOv3J^kClYhpQ@g91=j6_5Ij5in|b%lt00h(HC4$e(N12PX~~e~tv9tFVhcJ!2=9&sxuZ*90k+b9q2jE5Xwar_Z=S5=kP>lLrtpVI56~ z8zPa`1G84u>rCnO*ssV9LUIzt4kYwX$rFr$(?oQdRFAabcZI4{^}N*rPfTI)Oq%^E z7sN2^Xk@~guJjwbfEKPESJr`=u%gq$_uC|=KjhDt=rO{$q0!L*1~IXiQ6m$R)RIm6 zJA92c{jQYWc{P5E)`fBA-xGihAE{vMjwTO(s*aWySXnpcTg%`oEH78>01)-X(f*G~ zOLt=Z+_5VbqWuPKL_?(uB4J2bw>%2H-F2(3H($tEyFyF_rNo+ksrAK#T!~93NrghU zu0lvzu^)#hH;X(s@89%0^3YYvHre6I7p~F~>(<9B6ezSkljDcfh zxDPhQCQo!cop;ZTwRj6DK3B5$jz&B&Tl^#}f8vI!Wx@Cn*+Ib^o@_aii2v%?EhQ?- z4k+*}OZG2-Ydrgz4hR7<-ENtm`_jCjnq;;{{TTt#qe{s!I{i2LV3-#ALiG+`@bL~`%&NsGPQFJ06(jwD8Byyi+Yoe??c zYiKmr>Gp|+@_n38wtXbe*vlm<>-c*fMa3aE8}u!<8PQ%;3ZZoI=FQTDraj8Ilp zP~ylUy#YbkDzv7gi}gBJGLhDx$%s9zAk9;U#omtb8X+;qus*_*EaY7cSA*c83cUd> z$cPOwyg`uVLc!QBbZTeJtgmXX9C@8iTwdX_;e=)j(Qi@mu+IPR+J#KgZdmYh_1$>-t_k2E0TnqfW+5gx5 z7MZEPdfCf3vwuB8u=llg;-i{Mz!=dhb<_RE7Y0B|z}#&DYyQ1~W(59@XCdaiwBb3X`xAUXu_uNVWmDL7@pzE9psN zG614EL_e^A6|$R9g(Cpu60}Ya;s>p88;P(F*9MQ14eXK=~>fT_S2(v-{ zzDbx1r@Bc{WQOZJSda#1{&kG}qvHZDVk2w=OVDGi3<#;`bVjLAvv7{}MF2(c%x%iB zW>0Q?HQQkuP(ARfSzcxR(sErYyG3IE7lx<6f0$R_|LW7S7x@}X=Rzs4w?=81?mq-m z&uT%MMXHFrk7|)=C3>CCO%NHnN(E=7Vcr$aTV?M?0_a@;TdYrj*!OUHoW1%!?3B#3 z3`^yUx-5bZEclL!4T$!z9tA}bLg7rNaYWRiQ8x%G4Th_b1e3yx;2M2BGKBaxfx6{w zVY&*|t;~x>5P9v0B!d}>!_hUl;S5UGqUzwS@(vb?11&dw9`rE}+xX>3l0@~wqtI3Fo3D%VE)GleDn=UjV4> zWSbbcF$sP=cjE?&1IZWp<(Fe0h9Q8shM)lHi-bgp+kN#lVzJwN-xvR@CASzmc( zH7(KPh0UGQ8$Tt6^3ClC3mZW{xMKDYBccN_xwR5naADNVSK%sH@J)78Y^kt!zsY zTME_nB&NhIJ?!{xj=f_npsvqXxTDnT)^G7y=;SzHtkYWCJYYCS1Q)E>X@c(C&%HOX z^bVLLLinW~;aro-F|y6)YKo6FUn)*LvHw|l`MqzDxatlvoeVXS$|I>b1J>g1WI3v2 z$GH|8acH+yKzfB>mFZ7+btTVBO^9`je6q%gd$NqD@ob`BKvk$UfBq067`uRHC29XI z!u!=!^8>?aypo9!D{vHa2REcc))eH0AB>5(hE5i(WNTSdw`pLL=f223pE6A4t7dCy zGjFl@MYGf{-H`(Y0Xqs3@+t)eIkRr|mJ@5|>R|lmNkZtJ-`#Dw1C=uRNN|giz6uv8 z7{n=8dT8rMOCwc4eh)uvyXok^ z@Y?AXIaE~30ag~}Dxg$s3WNrkDFjPljp8HI^-u|2oH_(A))I*r+q7kzW=%70ofWq< zI9o@rfm)o?Lv5* z%k@=FAY~~I>DEOxQkuV;{u9MAIJQ&P1XMIX4L0vEK(g-UY9$%9?1s)=l#Oa6S7!c5 zWp%bmSyxy!K}x%LKDhCM@vHH3D8Y^@Cmcv)ssx=iHXsuPFk7KgXCBxh(||B03}&bW zEBW_Na_?~Vt~Wo1lXggUqLjoLY$dcBO|d`R5JT;FE$&s}z>vkUTY7PK=yWGA()QBK zful#N=f_aPGYO}ZIvqk(kzzI@Io7m?syOJLK{q~<931t7lJE$+L_l1~U=J$mM=*=O55`rK! z7}ydVzeTEw;X7nQ#r|yijoVuW7Vxhb%}d=qz9ScRm^{4MypKFO0p%*%3FRzWDHbW@ zU?-a+(q;YLd)ufwTlD3xW)LE`nMVMHQT9+=9N|U-E_y09(1~Xf zD(#5XOPV8BQVf=yli6MQ&fBE2)xE#Ci~av_HIpWVNI-B_OU+Snb>s?Emjn7(4c|mt zIUziCyjY?+nu=t^ktU>SO1>$ca?{mom#dMG%>=V`?s!67r9YkYa6NQ61KtFFX}t-9 zb*>b?bGE5WlkJ}y%HNF@-{!7jNTwJKSZO8{wawU)dCwuS?TlEvO{tW#oDsFJ3w*Q- zL^>u-v;behq1Ti&8B_oN1T_ooZxiMR=?40_#j3~+Z;Npl{)#~|Ye@a9R1B3YU1*is ztrh!D_20mflG5x!+_K=Sm5yH%pw2#En8nSIiff845`_*&B<2Z;RDwahaO2chyQ$WO z5WsqxDc7FrTqt|FYE;UwI1vjMk*x!$gd9GkU|Co8L5U!3DN6N?l*%^reqyBa47udf zmEbPl0tFvT*k7Lt&8t0lIJ(}g%gqEBprlyMi9zII>LtJC75J{b>~dgM!kr-(7-#hc}ZFE&h3bD z#`|T%(y{|VK(H|EFkt|&12Zn^b&1Te>EDmU*yeZA7|@ou^xU1m7f92-F^tZcWPGU} zmOsMwQQN0>M(8fTO_cTUvD#ZVM=Coca}})IZ~nR>cUa!cSEgk+Dt5-QQvuSB_Nz4l zjP36FBq=5(Z@rgqH0Y$o4 zr8~N`xD*YT&>sK4&-cGQeuK%e`Lyx|`}7@1d_C14tsaVeU>DBB<%f6|_QduChj^8D zPi&mna-MC_2uJ&Tx4ePD_1zZtxQ~vD)DeapzLNo8o*T6FyU7Qm_Z5@9$pzgRb4FC6 zHSx@uA1_0BfXBnbk=)pdRy}k9*25XDsGJlp&X7T7I6eXnzfYm;)xjFQlSreNvmUN; zjgR4>5!D=;|5MtPheO%*|CzB26NbtzWQ$=KyOA|gl%1?GgvgS}7Umw5C2N-K89QY+ zFBLIjkjbuWS+XmYQMQoZ^gPe?yw4xM_xiop_50`CpYuJR^ZB0pd+u}2b=~)M-QVZ} zdM+8AE~|;bmF%3SnxEMPwEN_9_k!y+?9gpC_JHe>?Ajut#wu)lkB$91lSVr4ApJ86 z&pKJ5YX8#?xiYx_y`v9%?YL5n{UK1()#F+q&ROfyZtaaI|1v4V^DQZFP;5AivT1Xw zKd8pRXn9&QN>bSeuI)j^muS(Y|U|+T)Adugtqz0FKo(az4PG_g|0$)VZ(hZl88534glY=&|j8QFsP; zC9|-U3m4vb_2hLjx=rGA=y;E7`|)N^RWB6yB7a@>(p;@0JNAX3z>T^>zjZm0X#`W; zg?2fS_u{4ZOJ8DB8{J<}vnd|aOfm!?{Fzkp9D+3hi9*ZD$;ry1WK|?la_3N}a}qEa z!`m*pPIp}e#dTHXP;#h$Q<~o)SjXtiE-30LDC#Jn(TZq2Ssj#$p0b>h!Ub7HSv^@@ zWwiE14MDa4lXCs<1S<;iii&@{hY`&Q3A48fiC(>&^GJ{E-Y9bAF{e##X}w~TD|xE5 zH?``WZ8d31FO@N}$xR|<$1LVVI-z-pRor`2iS|*+{SuFy=#l%x~Wq|SL`8=9S>jN<&_D!Kso(BT2IuQc24(4n6a zyEJwy8x}>|ru?)qLoC!KXr_egbieMdo}P$jtmdrFZQv&0^L5VaOu~}zhD8CUO%Z_v zvU|D2=)Cw8JHb4?M+=%0Jf@!w?y2S&E6OiX0~-~+d8ID_gu%X`rtTP^O*w6abNCFT z8+gBn*g{J`U}S7HbL^uPj5MbNr%VkzCj8y0*&R+e_t6n-2tc5L><^(F7c(En*QonV zNrCsxBJVPK(-`I$T~K)_mhDxT4w`mZb-OiGGI@?DGR0$iYS|U_Kx~zjT-L#qledUk zmdes07sbtdpNHiv{bF5)SQBzGwbCl2B3uaw0!_MA2h3J6r^}1Is!+e?lf-xjz3r(S zU4;`$!z1ni>3|)E*ZY*=f_E)qWqP13t1cWh-uq^^Xn697k09*+170xjb+s@q5SP~w>U{^7OD zaVLI(2{+vR>{QkSE~I%%;Jd<)Q8{4xiqj5Y#?w ziR-t_B{jhftMKJ}mxezL`-!i+Vxc*-x?UwQKYNCGhMd!X_#BJmyy|@}iN-e`ln?O? zH%Y8-09a1jQ#{DoY=zH>Rr1`wU8sw&y@*4OR9{wY$ah^{FE{y~GvN1)TMZu&a zY}xH@r?v3W-@x!)#p(NUUyblU(YST+WkIcoLR_ZxUsTbv5Nnz%h@FSyaZOB0S{y?I zkCrgkNed;^RTP4-O1h-Fgg=FWU5(;Kome5NS%cy+jIpj(mSJC!emM4=VAjoB z{>yUjiK-JIn>xlOqSB3P8!W!6cv4i>!?_F{ z@QGlz;wU{h5IS*c!<;hLdZmVLmnH$8wM+Y4Af*t`J&(m5{BAvi3Bt`#K^9-Hb(;=p z27<1*YPzXL{Ho<9G;yZ)FiMb4?lZNv!V}Do2@*7isli=@v~NJ=!c$&=AJcUre$~V* z=>oz03E%|ylJq(7ks9$kXplw?UIVIPUa>q(QbdEgpwbUAb5l~^gWqCM!Yrlv(O;CWS?aQEm>%_AuM7Rw>Y&+P7=%=)K_~@udN-u74 zo;Fi5t;QTzX58{#P%g?^gs z2_&e@C;J(W(JngM%1xhC;F)S??K%R8@_SeADSu-(6mt&SLd|vt!xz;PcB#N0KB%s? zyZNEhzt(!W{ku_a9;YUsdE85OG)yl_qV%$s8Z$S zk{uy(q30k^-cFLfD`xCv-tNs9S$xHu0Wc_)ynT8&=Mi6dc>dUKWyX21sjqz)Ip?60 zCes`J$U|ikCATlet5^1cwyRe+7sr+DaEi54r-N1HcR{bib@he8r$s-PL&XflvARrV z*J}%!2B+TWv%Jw>Tn$da{9Mj%)Z*kH$SL;0#}G`8#a7eJC4AORTZ=7VM&`1<5>q{@ z>-DAy%B3@fSx1R!@_zxkgP4jA$I;?cfRdFL8x#4L{$Gwlj-4CMGSSQQ*BQ(z3qu9} zbpBW4KKEaOQdB?Avqc8jB^LDQNtDRz6b7JkD)B5Snw>X|P7TQfmYtTeNRP@gz^d{t zrcpN^c$YS0DGHK8`vA|-n`*$A@*v&JqzvZYCf#$R{;XYL>fqtkJ_8dvmy8nnI;GTV zx~f>0UGRJDh6C_3fypSGVzL(vrc`*E!FsOO0aT6HKWxZbWN@eXBFLN+c>_=50ub-` zLh@+y=TECJza(-GG2hb`ScBf|K>PAn;lLS(AE%dK(vjLI+UO2Z6hyCsD+`o1FP8-w z#n8WvM5Ev}IJ!E9$3(g32G94hIW(_XZgE!oaeHFUxO9k3K%5U1u^n?BYU9B*{@`xU zgmJD~S%({~-NDx@U@>CLH?0eScuS`aL$j0M#Dg4?ykU*P6*rzi#S^YPi;BPb4dPGdpz;{?WErBMsv4M;tWrV zVxZ^7`>d+^YeAK-qjQ(}f@@@LZsV|)6TdX}A;0Nni*)H2*{h`rc#5G`Q`HAenP5AI#YKl}=ROv2|Ruiyrl$^V5>X@E0US(e^^=1R4L_ zgNP@sa=?x+`A~6Jtf-sNLy>`sg^fm8OBH!!`G*rbQ9PD_3Ra(BYXWtdq2LfcAqH(K z3@+8RhV13-$Q&u1zL$%#g3l7U1w9iIRz?~cCKz4RDGDi;U7|zO+?Rx9)0abl2m5Gu-F}o3^F#FQ#e@@%l zOOGP0&GCMJN2eC} z7>!yG9AqD$A15jKJQ1OY)nO5hZv`46C9gEy@9;LSj7#pZxHaM?9Jtcx{t1nv=zl+HIBUIhP)P!tG7P5Px{F#rSwy(L7gKv z5SKf(z&UMfYk%MvpNk;IbJQN@ke>AsGo>8DTS&F9Iv2(@Jt(}*Humjv3xHxlLoo6j zFyqfryZU-=$cNIMi=6FJoMXazb}lr}`FsrPUg)2ze{Oo~PJDi0>8zaV$Jx5z&*2=m zL)%H;5SyAD-{yOpv9k-i;y{5IMZ=8(gwd_xTw_z+l-?TBt`}duz{v4&hyR5-DcR{f=h!o#SRHeh?6lNs5Z+0FK1sW4_ky z<*p+6e?1`0E&LLu{^-;);+t()I{7Fx=}^2Ulxa?5$27i2pmA@H6OhvAxUGryDD;Q| zxSIAZ{n|4Rgrwbr$P|b`jfYtaChPakod+4HAEDz1vBL`m0!^Fkw<#}rkQ&B(?VA-O zd3$mr7dvLrpKt%l?WVmgB!}ZiKF4KjdtfbTyxohE*vT<>)kaOzhi%rIQq5U<6dd+K zj(eH19QbrBNNl4SIL?7OH0()#{Hg4G@a=FXRDiJeD_IJ=DH$&?qQX{y)rId-LXp@8 zPs(d!B=%yG9w1Po82Npq;mSeU8SG)e#iogKI4?nR66JIjy}VYS zt?SKO{Jr#B5Np~iT07reXpDcIz-^o(>s4+f^s(e_g!j=Gd%3>}lngCU@^#ISk`m#g z^8+1VjrcJwb55FLZ=iIzFW49)Duy|{oGTsF^o399$C<#*Gff-$Fd}_xXLn(zQCVkO zW#>vwCrca9Iq<|BI-%n~q2Rwmv(u6vg)@|oxh@xTn_g|HoKL)F;cF>_f z(vTge{Ie(~=?FHW(Mx&y30MGXwj&t2cqSnTH4FTW{tKW%=vHnl$zzb!=-Ojl)JcH< t|G}Am3UvxUty+Kis`!UM_b&m+`GWaOlnyIDlOkGKP5~w=deK-9_CIwe)foT) diff --git a/documentation/UserManual/ReactPhysics3D-UserManual.tex b/documentation/UserManual/ReactPhysics3D-UserManual.tex index dabb5bc8..1f7d09e8 100644 --- a/documentation/UserManual/ReactPhysics3D-UserManual.tex +++ b/documentation/UserManual/ReactPhysics3D-UserManual.tex @@ -1319,8 +1319,11 @@ ConcaveMeshShape* concaveMesh = physicsCommon.createConcaveMeshShape(triangleMes \texttt{TriangleMesh} for multiple \texttt{ConcaveMeshShape} with a different scaling factor each time. \\ \end{sloppypar} - In the previous example, the vertex normals that are needed for collision detection are automatically computed. However, if you want to specify your own - vertex normals, you can do it by using another constructor for the \texttt{TriangleVertexArray}. \\ + In the previous example, the vertices normals that are needed for collision detection are automatically computed. However, you can specify your own + vertices normals by using another constructor for the \texttt{TriangleVertexArray}. Note that each vertex normal is computed as weighted average + of the face normals of all the neighboring triangle faces. Therefore, if you specify your mesh with duplicated vertices when you create the + \emph{TriangleVertexArray}, the automatic vertices normals computation will not give correct normals because each vertex of the mesh will only be + part of a single triangle face. In this case, you should provide your own vertices normals when you create the \emph{TriangleVertexArray}. \\ \subsubsection{Heightfield Shape} From 95d54aeaea7f97d1f210eaec249f7d06ff527d0e Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Sun, 11 Oct 2020 10:56:52 +0200 Subject: [PATCH 47/74] Add demo scene with BallAndSocket joints net in the testbed application --- testbed/CMakeLists.txt | 2 + .../BallAndSocketJointsNetScene.cpp | 202 ++++++++++++++++++ .../BallAndSocketJointsNetScene.h | 79 +++++++ testbed/src/TestbedApplication.cpp | 9 +- 4 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 testbed/scenes/ballandsocketjointsnet/BallAndSocketJointsNetScene.cpp create mode 100644 testbed/scenes/ballandsocketjointsnet/BallAndSocketJointsNetScene.h diff --git a/testbed/CMakeLists.txt b/testbed/CMakeLists.txt index 29459aaf..db74010a 100755 --- a/testbed/CMakeLists.txt +++ b/testbed/CMakeLists.txt @@ -123,6 +123,8 @@ set(COMMON_SOURCES # Examples scenes source files set(SCENES_SOURCES + scenes/ballandsocketjointsnet/BallAndSocketJointsNetScene.h + scenes/ballandsocketjointsnet/BallAndSocketJointsNetScene.cpp scenes/cubes/CubesScene.h scenes/cubes/CubesScene.cpp scenes/joints/JointsScene.h diff --git a/testbed/scenes/ballandsocketjointsnet/BallAndSocketJointsNetScene.cpp b/testbed/scenes/ballandsocketjointsnet/BallAndSocketJointsNetScene.cpp new file mode 100644 index 00000000..b0e9f038 --- /dev/null +++ b/testbed/scenes/ballandsocketjointsnet/BallAndSocketJointsNetScene.cpp @@ -0,0 +1,202 @@ +/******************************************************************************** +* ReactPhysics3D physics library, http://www.reactphysics3d.com * +* Copyright (c) 2010-2016 Daniel Chappuis * +********************************************************************************* +* * +* This software is provided 'as-is', without any express or implied warranty. * +* In no event will the authors be held liable for any damages arising from the * +* use of this software. * +* * +* Permission is granted to anyone to use this software for any purpose, * +* including commercial applications, and to alter it and redistribute it * +* freely, subject to the following restrictions: * +* * +* 1. The origin of this software must not be misrepresented; you must not claim * +* that you wrote the original software. If you use this software in a * +* product, an acknowledgment in the product documentation would be * +* appreciated but is not required. * +* * +* 2. Altered source versions must be plainly marked as such, and must not be * +* misrepresented as being the original software. * +* * +* 3. This notice may not be removed or altered from any source distribution. * +* * +********************************************************************************/ + +// Libraries +#include "BallAndSocketJointsNetScene.h" +#include + +// Namespaces +using namespace openglframework; +using namespace ballandsocketjointsnetscene; + +// Constructor +BallAndSocketJointsNetScene::BallAndSocketJointsNetScene(const std::string& name, EngineSettings& settings) + : SceneDemo(name, settings, true, SCENE_RADIUS) { + + std::string meshFolderPath("meshes/"); + + // Compute the radius and the center of the scene + openglframework::Vector3 center(0, 5, 0); + + // Set the center of the scene + setScenePosition(center, SCENE_RADIUS); + + // Gravity vector in the physics world + rp3d::Vector3 gravity(0, rp3d::decimal(-9.81), 0); + + rp3d::PhysicsWorld::WorldSettings worldSettings; + worldSettings.worldName = name; + + // Logger + rp3d::DefaultLogger* defaultLogger = mPhysicsCommon.createDefaultLogger(); + uint logLevel = static_cast(rp3d::Logger::Level::Information) | static_cast(rp3d::Logger::Level::Warning) | + static_cast(rp3d::Logger::Level::Error); + defaultLogger->addFileDestination("rp3d_log_" + name + ".html", logLevel, rp3d::DefaultLogger::Format::HTML); + mPhysicsCommon.setLogger(defaultLogger); + + // Create the physics world for the physics simulation + rp3d::PhysicsWorld* physicsWorld = mPhysicsCommon.createPhysicsWorld(worldSettings); + physicsWorld->setEventListener(this); + mPhysicsWorld = physicsWorld; + + // Create all the spheres of the scene + for (int i=0; isetColor(mObjectColorDemo); + sphere->setSleepingColor(mSleepingColorDemo); + + // Change the material properties of the rigid body + rp3d::Material& material = sphere->getCollider()->getMaterial(); + material.setBounciness(rp3d::decimal(0.0)); + + // Add the sphere the list of sphere in the scene + mNetSpheres[i][j] = sphere; + mPhysicsObjects.push_back(sphere); + } + } + + // Set the position of the spheres before the joints creation + reset(); + + // Create the Ball-and-Socket joints + createJoints(); + + // Create the main sphere + mMainSphere = new Sphere(true, 10, mPhysicsCommon, mPhysicsWorld, meshFolderPath); + mMainSphere->setColor(mObjectColorDemo); + mMainSphere->setSleepingColor(mSleepingColorDemo); + rp3d::Vector3 initPosition(0, 0, 0); + rp3d::Quaternion initOrientation = rp3d::Quaternion::identity(); + rp3d::Transform transform(initPosition, initOrientation); + mMainSphere->setTransform(transform); + mMainSphere->getRigidBody()->setType(rp3d::BodyType::STATIC); + rp3d::Material& material = mMainSphere->getCollider()->getMaterial(); + material.setBounciness(rp3d::decimal(0.0)); + mPhysicsObjects.push_back(mMainSphere); + + // Get the physics engine parameters + mEngineSettings.isGravityEnabled = mPhysicsWorld->isGravityEnabled(); + rp3d::Vector3 gravityVector = mPhysicsWorld->getGravity(); + mEngineSettings.gravity = openglframework::Vector3(gravityVector.x, gravityVector.y, gravityVector.z); + mEngineSettings.isSleepingEnabled = mPhysicsWorld->isSleepingEnabled(); + mEngineSettings.sleepLinearVelocity = mPhysicsWorld->getSleepLinearVelocity(); + mEngineSettings.sleepAngularVelocity = mPhysicsWorld->getSleepAngularVelocity(); + mEngineSettings.nbPositionSolverIterations = mPhysicsWorld->getNbIterationsPositionSolver(); + mEngineSettings.nbVelocitySolverIterations = mPhysicsWorld->getNbIterationsVelocitySolver(); + mEngineSettings.timeBeforeSleep = mPhysicsWorld->getTimeBeforeSleep(); +} + +// Destructor +BallAndSocketJointsNetScene::~BallAndSocketJointsNetScene() { + + // Destroy the joints + for (uint i=0; i < mBallAndSocketJoints.size(); i++) { + + mPhysicsWorld->destroyJoint(mBallAndSocketJoints[i]); + } + + // Destroy all the rigid bodies of the scene + for (int i=0; idestroyRigidBody(mNetSpheres[i][j]->getRigidBody()); + } + } + mPhysicsWorld->destroyRigidBody(mMainSphere->getRigidBody()); + + // Destroy the physics world + mPhysicsCommon.destroyPhysicsWorld(mPhysicsWorld); +} + +// Create the joints +void BallAndSocketJointsNetScene::createJoints() { + + for (int i=0; i 0) { + + // Create the joint info object + rp3d::RigidBody* body1 = mNetSpheres[i-1][j]->getRigidBody(); + rp3d::RigidBody* body2 = mNetSpheres[i][j]->getRigidBody(); + rp3d::Vector3 body1Position = body1->getTransform().getPosition(); + rp3d::Vector3 body2Position = body2->getTransform().getPosition(); + //const rp3d::Vector3 anchorPointWorldSpace = 0.5 * (body1Position + body2Position); + const rp3d::Vector3 anchorPointWorldSpace = body2Position; + rp3d::BallAndSocketJointInfo jointInfo(body1, body2, anchorPointWorldSpace); + jointInfo.isCollisionEnabled = false; + rp3d::BallAndSocketJoint* joint = dynamic_cast( mPhysicsWorld->createJoint(jointInfo)); + mBallAndSocketJoints.push_back(joint); + } + + if (j > 0) { + + // Create the joint info object + rp3d::RigidBody* body1 = mNetSpheres[i][j-1]->getRigidBody(); + rp3d::RigidBody* body2 = mNetSpheres[i][j]->getRigidBody(); + rp3d::Vector3 body1Position = body1->getTransform().getPosition(); + rp3d::Vector3 body2Position = body2->getTransform().getPosition(); + const rp3d::Vector3 anchorPointWorldSpace = body2Position; + rp3d::BallAndSocketJointInfo jointInfo(body1, body2, anchorPointWorldSpace); + jointInfo.isCollisionEnabled = false; + rp3d::BallAndSocketJoint* joint = dynamic_cast( mPhysicsWorld->createJoint(jointInfo)); + mBallAndSocketJoints.push_back(joint); + } + } + } +} + +// Reset the scene +void BallAndSocketJointsNetScene::reset() { + + SceneDemo::reset(); + + const float space = 0.3f; + const float startX = -(NB_ROWS_NET_SPHERES / 2.0f * (2.0 * SPHERE_RADIUS + space)); + const float startZ = -(NB_ROWS_NET_SPHERES / 2.0f * (2.0 * SPHERE_RADIUS + space)); + + const rp3d::Quaternion initOrientation = rp3d::Quaternion::identity(); + + for (int i=0; isetTransform(transform); + } + } +} diff --git a/testbed/scenes/ballandsocketjointsnet/BallAndSocketJointsNetScene.h b/testbed/scenes/ballandsocketjointsnet/BallAndSocketJointsNetScene.h new file mode 100644 index 00000000..5f9cc7e4 --- /dev/null +++ b/testbed/scenes/ballandsocketjointsnet/BallAndSocketJointsNetScene.h @@ -0,0 +1,79 @@ +/******************************************************************************** +* ReactPhysics3D physics library, http://www.reactphysics3d.com * +* Copyright (c) 2010-2016 Daniel Chappuis * +********************************************************************************* +* * +* This software is provided 'as-is', without any express or implied warranty. * +* In no event will the authors be held liable for any damages arising from the * +* use of this software. * +* * +* Permission is granted to anyone to use this software for any purpose, * +* including commercial applications, and to alter it and redistribute it * +* freely, subject to the following restrictions: * +* * +* 1. The origin of this software must not be misrepresented; you must not claim * +* that you wrote the original software. If you use this software in a * +* product, an acknowledgment in the product documentation would be * +* appreciated but is not required. * +* * +* 2. Altered source versions must be plainly marked as such, and must not be * +* misrepresented as being the original software. * +* * +* 3. This notice may not be removed or altered from any source distribution. * +* * +********************************************************************************/ + +#ifndef BALL_AND_SOCKET_JOINTS_NET_SCENE_H +#define BALL_AND_SOCKET_JOINTS_NET_SCENE_H + +// Libraries +#include "openglframework.h" +#include +#include "Sphere.h" +#include "SceneDemo.h" + +namespace ballandsocketjointsnetscene { + +// Constants +const float SCENE_RADIUS = 40.0f; +const float SPHERE_RADIUS = 0.5f; +const int NB_ROWS_NET_SPHERES = 40; + +// Class JointsScene +class BallAndSocketJointsNetScene : public SceneDemo { + + protected : + + // -------------------- Attributes -------------------- // + + /// Spheres in the Ball-And-Socket joint net + Sphere* mNetSpheres[NB_ROWS_NET_SPHERES][NB_ROWS_NET_SPHERES]; + + /// Main sphere + Sphere* mMainSphere; + + /// Ball-And-Socket joints of the chain + std::vector mBallAndSocketJoints; + + // -------------------- Methods -------------------- // + + /// Create the joints + void createJoints(); + + public: + + // -------------------- Methods -------------------- // + + /// Constructor + BallAndSocketJointsNetScene(const std::string& name, EngineSettings& settings); + + /// Destructor + virtual ~BallAndSocketJointsNetScene() override ; + + /// Reset the scene + virtual void reset() override; +}; + +} + +#endif diff --git a/testbed/src/TestbedApplication.cpp b/testbed/src/TestbedApplication.cpp index b5d1dcd4..f3888924 100755 --- a/testbed/src/TestbedApplication.cpp +++ b/testbed/src/TestbedApplication.cpp @@ -39,6 +39,7 @@ #include "cubestack/CubeStackScene.h" #include "pile/PileScene.h" #include "boxtower/BoxTowerScene.h" +#include "ballandsocketjointsnet/BallAndSocketJointsNetScene.h" using namespace openglframework; using namespace jointsscene; @@ -51,6 +52,7 @@ using namespace collisiondetectionscene; using namespace cubestackscene; using namespace pilescene; using namespace boxtowerscene; +using namespace ballandsocketjointsnetscene; // Initialization of static variables const float TestbedApplication::SCROLL_SENSITIVITY = 0.08f; @@ -152,7 +154,12 @@ void TestbedApplication::createScenes() { mScenes.push_back(boxTowerScene); assert(mScenes.size() > 0); - const int firstSceneIndex = 0; + // Ball and Socket joints Net scene + BallAndSocketJointsNetScene* ballAndSocketJointsNetScene = new BallAndSocketJointsNetScene("BallAndSocket Joints Net", mEngineSettings); + mScenes.push_back(ballAndSocketJointsNetScene); + assert(mScenes.size() > 0); + + const int firstSceneIndex = 10; switchScene(mScenes[firstSceneIndex]); } From ec39d00a70afb26dbbda73c905e72a5bb195e381 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Sun, 11 Oct 2020 10:57:11 +0200 Subject: [PATCH 48/74] Fix compilation error on Windows --- include/reactphysics3d/mathematics/Vector3.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/reactphysics3d/mathematics/Vector3.h b/include/reactphysics3d/mathematics/Vector3.h index a6dae046..79b47df9 100644 --- a/include/reactphysics3d/mathematics/Vector3.h +++ b/include/reactphysics3d/mathematics/Vector3.h @@ -29,6 +29,7 @@ // Libraries #include #include +#include #include #include #include From 916cefa96d604eb7a4f09277b11d6b00f10dc319 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Mon, 12 Oct 2020 23:01:57 +0200 Subject: [PATCH 49/74] Optimization of joints --- include/reactphysics3d/body/RigidBody.h | 18 +++ .../components/JointComponents.h | 5 + src/body/RigidBody.cpp | 10 -- src/systems/SolveBallAndSocketJointSystem.cpp | 84 +++++++----- src/systems/SolveFixedJointSystem.cpp | 106 ++++++++------- src/systems/SolveHingeJointSystem.cpp | 122 ++++++++++-------- src/systems/SolveSliderJointSystem.cpp | 122 ++++++++++-------- 7 files changed, 273 insertions(+), 194 deletions(-) diff --git a/include/reactphysics3d/body/RigidBody.h b/include/reactphysics3d/body/RigidBody.h index bbc583b0..3e262dcb 100644 --- a/include/reactphysics3d/body/RigidBody.h +++ b/include/reactphysics3d/body/RigidBody.h @@ -212,6 +212,24 @@ class RigidBody : public CollisionBody { friend class Joint; }; +/// Compute the inverse of the inertia tensor in world coordinates. +RP3D_FORCE_INLINE void RigidBody::computeWorldInertiaTensorInverse(const Matrix3x3& orientation, const Vector3& inverseInertiaTensorLocal, Matrix3x3& outInverseInertiaTensorWorld) { + + outInverseInertiaTensorWorld[0][0] = orientation[0][0] * inverseInertiaTensorLocal.x; + outInverseInertiaTensorWorld[0][1] = orientation[1][0] * inverseInertiaTensorLocal.x; + outInverseInertiaTensorWorld[0][2] = orientation[2][0] * inverseInertiaTensorLocal.x; + + outInverseInertiaTensorWorld[1][0] = orientation[0][1] * inverseInertiaTensorLocal.y; + outInverseInertiaTensorWorld[1][1] = orientation[1][1] * inverseInertiaTensorLocal.y; + outInverseInertiaTensorWorld[1][2] = orientation[2][1] * inverseInertiaTensorLocal.y; + + outInverseInertiaTensorWorld[2][0] = orientation[0][2] * inverseInertiaTensorLocal.z; + outInverseInertiaTensorWorld[2][1] = orientation[1][2] * inverseInertiaTensorLocal.z; + outInverseInertiaTensorWorld[2][2] = orientation[2][2] * inverseInertiaTensorLocal.z; + + outInverseInertiaTensorWorld = orientation * outInverseInertiaTensorWorld; +} + } #endif diff --git a/include/reactphysics3d/components/JointComponents.h b/include/reactphysics3d/components/JointComponents.h index e535dbd1..00c2b372 100644 --- a/include/reactphysics3d/components/JointComponents.h +++ b/include/reactphysics3d/components/JointComponents.h @@ -157,6 +157,11 @@ class JointComponents : public Components { friend class BroadPhaseSystem; friend class ConstraintSolverSystem; friend class PhysicsWorld; + friend class SolveBallAndSocketJointSystem; + friend class SolveFixedJointSystem; + friend class SolveHingeJointSystem; + friend class SolveSliderJointSystem; + }; // Return the entity of the first body of a joint diff --git a/src/body/RigidBody.cpp b/src/body/RigidBody.cpp index 86d7cb62..752e5f52 100644 --- a/src/body/RigidBody.cpp +++ b/src/body/RigidBody.cpp @@ -888,16 +888,6 @@ void RigidBody::resetOverlappingPairs() { askForBroadPhaseCollisionCheck(); } -/// Compute the inverse of the inertia tensor in world coordinates. -void RigidBody::computeWorldInertiaTensorInverse(const Matrix3x3& orientation, const Vector3& inverseInertiaTensorLocal, Matrix3x3& outInverseInertiaTensorWorld) { - - Matrix3x3 orientationTranspose = orientation.getTranspose(); - orientationTranspose[0] *= inverseInertiaTensorLocal.x; - orientationTranspose[1] *= inverseInertiaTensorLocal.y; - orientationTranspose[2] *= inverseInertiaTensorLocal.z; - outInverseInertiaTensorWorld = orientation * orientationTranspose; -} - // Set whether or not the body is allowed to go to sleep /** * @param isAllowedToSleep True if the body is allowed to sleep diff --git a/src/systems/SolveBallAndSocketJointSystem.cpp b/src/systems/SolveBallAndSocketJointSystem.cpp index 078a1dd7..e46707c4 100644 --- a/src/systems/SolveBallAndSocketJointSystem.cpp +++ b/src/systems/SolveBallAndSocketJointSystem.cpp @@ -48,13 +48,15 @@ SolveBallAndSocketJointSystem::SolveBallAndSocketJointSystem(PhysicsWorld& world void SolveBallAndSocketJointSystem::initBeforeSolve() { // For each joint - for (uint32 i=0; i < mBallAndSocketJointComponents.getNbEnabledComponents(); i++) { + const uint32 nbJoints = mBallAndSocketJointComponents.getNbEnabledComponents(); + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mBallAndSocketJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; assert(!mRigidBodyComponents.getIsEntityDisabled(body1Entity)); assert(!mRigidBodyComponents.getIsEntityDisabled(body2Entity)); @@ -65,13 +67,14 @@ void SolveBallAndSocketJointSystem::initBeforeSolve() { } // For each joint - for (uint32 i=0; i < mBallAndSocketJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mBallAndSocketJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const Quaternion& orientationBody1 = mTransformComponents.getTransform(body1Entity).getOrientation(); const Quaternion& orientationBody2 = mTransformComponents.getTransform(body2Entity).getOrientation(); @@ -82,9 +85,10 @@ void SolveBallAndSocketJointSystem::initBeforeSolve() { } // For each joint - for (uint32 i=0; i < mBallAndSocketJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mBallAndSocketJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // Compute the corresponding skew-symmetric matrices const Vector3& r1World = mBallAndSocketJointComponents.mR1World[i]; @@ -93,8 +97,8 @@ void SolveBallAndSocketJointSystem::initBeforeSolve() { Matrix3x3 skewSymmetricMatrixU2 = Matrix3x3::computeSkewSymmetricMatrixForCrossProduct(r2World); // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); @@ -122,13 +126,14 @@ void SolveBallAndSocketJointSystem::initBeforeSolve() { const decimal biasFactor = (BETA / mTimeStep); // For each joint - for (uint32 i=0; i < mBallAndSocketJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mBallAndSocketJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const Vector3& r1World = mBallAndSocketJointComponents.mR1World[i]; const Vector3& r2World = mBallAndSocketJointComponents.mR2World[i]; @@ -138,7 +143,7 @@ void SolveBallAndSocketJointSystem::initBeforeSolve() { // Compute the bias "b" of the constraint mBallAndSocketJointComponents.mBiasVector[i].setToZero(); - if (mJointComponents.getPositionCorrectionTechnique(jointEntity) == JointsPositionCorrectionTechnique::BAUMGARTE_JOINTS) { + if (mJointComponents.mPositionCorrectionTechniques[jointIndex] == JointsPositionCorrectionTechnique::BAUMGARTE_JOINTS) { mBallAndSocketJointComponents.mBiasVector[i] = biasFactor * (x2 + r2World - x1 - r1World); } } @@ -159,12 +164,14 @@ void SolveBallAndSocketJointSystem::initBeforeSolve() { void SolveBallAndSocketJointSystem::warmstart() { // For each joint component - for (uint32 i=0; i < mBallAndSocketJointComponents.getNbEnabledComponents(); i++) { + const uint32 nbJoints = mBallAndSocketJointComponents.getNbEnabledComponents(); + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mBallAndSocketJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); @@ -202,12 +209,14 @@ void SolveBallAndSocketJointSystem::warmstart() { void SolveBallAndSocketJointSystem::solveVelocityConstraint() { // For each joint component - for (uint32 i=0; i < mBallAndSocketJointComponents.getNbEnabledComponents(); i++) { + const uint32 nbJoints = mBallAndSocketJointComponents.getNbEnabledComponents(); + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mBallAndSocketJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); @@ -249,16 +258,18 @@ void SolveBallAndSocketJointSystem::solveVelocityConstraint() { void SolveBallAndSocketJointSystem::solvePositionConstraint() { // For each joint component - for (uint32 i=0; i < mBallAndSocketJointComponents.getNbEnabledComponents(); i++) { + const uint32 nbEnabledJoints = mBallAndSocketJointComponents.getNbEnabledComponents(); + for (uint32 i=0; i < nbEnabledJoints; i++) { const Entity jointEntity = mBallAndSocketJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // If the error position correction technique is not the non-linear-gauss-seidel, we do // do not execute this method - if (mJointComponents.getPositionCorrectionTechnique(jointEntity) != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; + if (mJointComponents.mPositionCorrectionTechniques[jointIndex] != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; // Recompute the world inverse inertia tensors const Matrix3x3 orientation1 = mTransformComponents.getTransform(body1Entity).getOrientation().getMatrix(); @@ -271,16 +282,17 @@ void SolveBallAndSocketJointSystem::solvePositionConstraint() { } // For each joint component - for (uint32 i=0; i < mBallAndSocketJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbEnabledJoints; i++) { const Entity jointEntity = mBallAndSocketJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // If the error position correction technique is not the non-linear-gauss-seidel, we do // do not execute this method - if (mJointComponents.getPositionCorrectionTechnique(jointEntity) != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; + if (mJointComponents.mPositionCorrectionTechniques[jointIndex] != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; // Compute the vector from body center to the anchor point in world-space mBallAndSocketJointComponents.mR1World[i] = mRigidBodyComponents.getConstrainedOrientation(body1Entity) * @@ -290,16 +302,17 @@ void SolveBallAndSocketJointSystem::solvePositionConstraint() { } // For each joint component - for (uint32 i=0; i < mBallAndSocketJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbEnabledJoints; i++) { const Entity jointEntity = mBallAndSocketJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // If the error position correction technique is not the non-linear-gauss-seidel, we do // do not execute this method - if (mJointComponents.getPositionCorrectionTechnique(jointEntity) != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; + if (mJointComponents.mPositionCorrectionTechniques[jointIndex] != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); @@ -330,16 +343,17 @@ void SolveBallAndSocketJointSystem::solvePositionConstraint() { } // For each joint component - for (uint32 i=0; i < mBallAndSocketJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbEnabledJoints; i++) { const Entity jointEntity = mBallAndSocketJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // If the error position correction technique is not the non-linear-gauss-seidel, we do // do not execute this method - if (mJointComponents.getPositionCorrectionTechnique(jointEntity) != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; + if (mJointComponents.mPositionCorrectionTechniques[jointIndex] != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); diff --git a/src/systems/SolveFixedJointSystem.cpp b/src/systems/SolveFixedJointSystem.cpp index 59a07089..054d78e5 100644 --- a/src/systems/SolveFixedJointSystem.cpp +++ b/src/systems/SolveFixedJointSystem.cpp @@ -48,13 +48,15 @@ SolveFixedJointSystem::SolveFixedJointSystem(PhysicsWorld& world, RigidBodyCompo void SolveFixedJointSystem::initBeforeSolve() { // For each joint - for (uint32 i=0; i < mFixedJointComponents.getNbEnabledComponents(); i++) { + const uint32 nbJoints = mFixedJointComponents.getNbEnabledComponents(); + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mFixedJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; assert(!mRigidBodyComponents.getIsEntityDisabled(body1Entity)); assert(!mRigidBodyComponents.getIsEntityDisabled(body2Entity)); @@ -65,13 +67,14 @@ void SolveFixedJointSystem::initBeforeSolve() { } // For each joint - for (uint32 i=0; i < mFixedJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mFixedJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const Quaternion& orientationBody1 = mTransformComponents.getTransform(body1Entity).getOrientation(); const Quaternion& orientationBody2 = mTransformComponents.getTransform(body2Entity).getOrientation(); @@ -82,13 +85,14 @@ void SolveFixedJointSystem::initBeforeSolve() { } // For each joint - for (uint32 i=0; i < mFixedJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mFixedJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; // Compute the corresponding skew-symmetric matrices Matrix3x3 skewSymmetricMatrixU1 = Matrix3x3::computeSkewSymmetricMatrixForCrossProduct(mFixedJointComponents.mR1World[i]); @@ -118,13 +122,14 @@ void SolveFixedJointSystem::initBeforeSolve() { const decimal biasFactor = BETA / mTimeStep; // For each joint - for (uint32 i=0; i < mFixedJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mFixedJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; // Get the bodies positions and orientations const Vector3& x1 = mRigidBodyComponents.getCenterOfMassWorld(body1Entity); @@ -135,19 +140,20 @@ void SolveFixedJointSystem::initBeforeSolve() { // Compute the bias "b" of the constraint for the 3 translation constraints mFixedJointComponents.mBiasTranslation[i].setToZero(); - if (mJointComponents.getPositionCorrectionTechnique(jointEntity) == JointsPositionCorrectionTechnique::BAUMGARTE_JOINTS) { + if (mJointComponents.mPositionCorrectionTechniques[jointIndex] == JointsPositionCorrectionTechnique::BAUMGARTE_JOINTS) { mFixedJointComponents.mBiasTranslation[i] = biasFactor * (x2 + r2World - x1 - r1World); } } // For each joint - for (uint32 i=0; i < mFixedJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mFixedJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; // Compute the inverse of the mass matrix K=JM^-1J^t for the 3 rotation contraints (3x3 matrix) mFixedJointComponents.mInverseMassMatrixRotation[i] = mFixedJointComponents.mI1[i] + mFixedJointComponents.mI2[i]; @@ -158,13 +164,14 @@ void SolveFixedJointSystem::initBeforeSolve() { } // For each joint - for (uint32 i=0; i < mFixedJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mFixedJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; // Compute the bias "b" for the 3 rotation constraints mFixedJointComponents.mBiasRotation[i].setToZero(); @@ -172,7 +179,7 @@ void SolveFixedJointSystem::initBeforeSolve() { const Quaternion& orientationBody1 = mTransformComponents.getTransform(body1Entity).getOrientation(); const Quaternion& orientationBody2 = mTransformComponents.getTransform(body2Entity).getOrientation(); - if (mJointComponents.getPositionCorrectionTechnique(jointEntity) == JointsPositionCorrectionTechnique::BAUMGARTE_JOINTS) { + if (mJointComponents.mPositionCorrectionTechniques[jointIndex] == JointsPositionCorrectionTechnique::BAUMGARTE_JOINTS) { const Quaternion qError = orientationBody2 * mFixedJointComponents.mInitOrientationDifferenceInv[i] * orientationBody1.getInverse(); mFixedJointComponents.mBiasRotation[i] = biasFactor * decimal(2.0) * qError.getVectorV(); } @@ -182,7 +189,7 @@ void SolveFixedJointSystem::initBeforeSolve() { if (!mIsWarmStartingActive) { // For each joint - for (uint32 i=0; i < mFixedJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbJoints; i++) { // Reset the accumulated impulses mFixedJointComponents.mImpulseTranslation[i].setToZero(); @@ -195,13 +202,15 @@ void SolveFixedJointSystem::initBeforeSolve() { void SolveFixedJointSystem::warmstart() { // For each joint - for (uint32 i=0; i < mFixedJointComponents.getNbEnabledComponents(); i++) { + const uint32 nbJoints = mFixedJointComponents.getNbEnabledComponents(); + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mFixedJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); @@ -253,13 +262,15 @@ void SolveFixedJointSystem::warmstart() { void SolveFixedJointSystem::solveVelocityConstraint() { // For each joint - for (uint32 i=0; i < mFixedJointComponents.getNbEnabledComponents(); i++) { + const uint32 nbJoints = mFixedJointComponents.getNbEnabledComponents(); + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mFixedJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); @@ -334,17 +345,19 @@ void SolveFixedJointSystem::solveVelocityConstraint() { void SolveFixedJointSystem::solvePositionConstraint() { // For each joint - for (uint32 i=0; i < mFixedJointComponents.getNbEnabledComponents(); i++) { + const uint32 nbEnabledJoints = mFixedJointComponents.getNbEnabledComponents(); + for (uint32 i=0; i < nbEnabledJoints; i++) { const Entity jointEntity = mFixedJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // If the error position correction technique is not the non-linear-gauss-seidel, we do // do not execute this method - if (mJointComponents.getPositionCorrectionTechnique(jointEntity) != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; + if (mJointComponents.mPositionCorrectionTechniques[jointIndex] != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; // Recompute the world inverse inertia tensors const Matrix3x3 orientation1 = mTransformComponents.getTransform(body1Entity).getOrientation().getMatrix(); @@ -357,39 +370,41 @@ void SolveFixedJointSystem::solvePositionConstraint() { } // For each joint - for (uint32 i=0; i < mFixedJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbEnabledJoints; i++) { const Entity jointEntity = mFixedJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // If the error position correction technique is not the non-linear-gauss-seidel, we do // do not execute this method - if (mJointComponents.getPositionCorrectionTechnique(jointEntity) != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; + if (mJointComponents.mPositionCorrectionTechniques[jointIndex] != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; // Get the bodies positions and orientations const Quaternion& q1 = mRigidBodyComponents.getConstrainedOrientation(body1Entity); const Quaternion& q2 = mRigidBodyComponents.getConstrainedOrientation(body2Entity); // Compute the vector from body center to the anchor point in world-space - mFixedJointComponents.mR1World[i] = q1 * mFixedJointComponents.getLocalAnchorPointBody1(jointEntity); - mFixedJointComponents.mR2World[i] = q2 * mFixedJointComponents.getLocalAnchorPointBody2(jointEntity); + mFixedJointComponents.mR1World[i] = q1 * mFixedJointComponents.mLocalAnchorPointBody1[i]; + mFixedJointComponents.mR2World[i] = q2 * mFixedJointComponents.mLocalAnchorPointBody2[i]; } // For each joint - for (uint32 i=0; i < mFixedJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbEnabledJoints; i++) { const Entity jointEntity = mFixedJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // If the error position correction technique is not the non-linear-gauss-seidel, we do // do not execute this method - if (mJointComponents.getPositionCorrectionTechnique(jointEntity) != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; + if (mJointComponents.mPositionCorrectionTechniques[jointIndex] != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); @@ -422,17 +437,18 @@ void SolveFixedJointSystem::solvePositionConstraint() { } // For each joint - for (uint32 i=0; i < mFixedJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbEnabledJoints; i++) { const Entity jointEntity = mFixedJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // If the error position correction technique is not the non-linear-gauss-seidel, we do // do not execute this method - if (mJointComponents.getPositionCorrectionTechnique(jointEntity) != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; + if (mJointComponents.mPositionCorrectionTechniques[jointIndex] != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const Vector3& r1World = mFixedJointComponents.mR1World[i]; const Vector3& r2World = mFixedJointComponents.mR2World[i]; diff --git a/src/systems/SolveHingeJointSystem.cpp b/src/systems/SolveHingeJointSystem.cpp index ee8864ea..45b66aed 100644 --- a/src/systems/SolveHingeJointSystem.cpp +++ b/src/systems/SolveHingeJointSystem.cpp @@ -48,13 +48,15 @@ SolveHingeJointSystem::SolveHingeJointSystem(PhysicsWorld& world, RigidBodyCompo void SolveHingeJointSystem::initBeforeSolve() { // For each joint - for (uint32 i=0; i < mHingeJointComponents.getNbEnabledComponents(); i++) { + const uint32 nbJoints = mHingeJointComponents.getNbEnabledComponents(); + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mHingeJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; assert(!mRigidBodyComponents.getIsEntityDisabled(body1Entity)); assert(!mRigidBodyComponents.getIsEntityDisabled(body2Entity)); @@ -65,13 +67,14 @@ void SolveHingeJointSystem::initBeforeSolve() { } // For each joint - for (uint32 i=0; i < mHingeJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mHingeJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const Quaternion& orientationBody1 = mTransformComponents.getTransform(body1Entity).getOrientation(); const Quaternion& orientationBody2 = mTransformComponents.getTransform(body2Entity).getOrientation(); @@ -84,13 +87,14 @@ void SolveHingeJointSystem::initBeforeSolve() { const decimal biasFactor = (BETA / mTimeStep); // For each joint - for (uint32 i=0; i < mHingeJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mHingeJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const Quaternion& orientationBody1 = mTransformComponents.getTransform(body1Entity).getOrientation(); const Quaternion& orientationBody2 = mTransformComponents.getTransform(body2Entity).getOrientation(); @@ -109,19 +113,20 @@ void SolveHingeJointSystem::initBeforeSolve() { // Compute the bias "b" of the rotation constraints mHingeJointComponents.mBiasRotation[i].setToZero(); - if (mJointComponents.getPositionCorrectionTechnique(jointEntity) == JointsPositionCorrectionTechnique::BAUMGARTE_JOINTS) { + if (mJointComponents.mPositionCorrectionTechniques[jointIndex] == JointsPositionCorrectionTechnique::BAUMGARTE_JOINTS) { mHingeJointComponents.mBiasRotation[i] = biasFactor * Vector2(a1.dot(b2), a1.dot(c2)); } } // For each joint - for (uint32 i=0; i < mHingeJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mHingeJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; // Compute the corresponding skew-symmetric matrices Matrix3x3 skewSymmetricMatrixU1= Matrix3x3::computeSkewSymmetricMatrixForCrossProduct(mHingeJointComponents.mR1World[i]); @@ -148,13 +153,14 @@ void SolveHingeJointSystem::initBeforeSolve() { } // For each joint - for (uint32 i=0; i < mHingeJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mHingeJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; // Get the bodies positions and orientations const Vector3& x1 = mRigidBodyComponents.getCenterOfMassWorld(body1Entity); @@ -162,19 +168,20 @@ void SolveHingeJointSystem::initBeforeSolve() { // Compute the bias "b" of the translation constraints mHingeJointComponents.mBiasTranslation[i].setToZero(); - if (mJointComponents.getPositionCorrectionTechnique(jointEntity) == JointsPositionCorrectionTechnique::BAUMGARTE_JOINTS) { + if (mJointComponents.mPositionCorrectionTechniques[jointIndex] == JointsPositionCorrectionTechnique::BAUMGARTE_JOINTS) { mHingeJointComponents.mBiasTranslation[i] = biasFactor * (x2 + mHingeJointComponents.mR2World[i] - x1 - mHingeJointComponents.mR1World[i]); } } // For each joint - for (uint32 i=0; i < mHingeJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mHingeJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const Matrix3x3& i1 = mHingeJointComponents.mI1[i]; const Matrix3x3& i2 = mHingeJointComponents.mI2[i]; @@ -202,7 +209,7 @@ void SolveHingeJointSystem::initBeforeSolve() { if (!mIsWarmStartingActive) { // For each joint - for (uint32 i=0; i < mHingeJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbJoints; i++) { // Reset all the accumulated impulses mHingeJointComponents.mImpulseTranslation[i].setToZero(); @@ -214,13 +221,14 @@ void SolveHingeJointSystem::initBeforeSolve() { } // For each joint - for (uint32 i=0; i < mHingeJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mHingeJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const Quaternion& orientationBody1 = mTransformComponents.getTransform(body1Entity).getOrientation(); const Quaternion& orientationBody2 = mTransformComponents.getTransform(body2Entity).getOrientation(); @@ -261,13 +269,13 @@ void SolveHingeJointSystem::initBeforeSolve() { // Compute the bias "b" of the lower limit constraint mHingeJointComponents.mBLowerLimit[i] = decimal(0.0); - if (mJointComponents.getPositionCorrectionTechnique(jointEntity) == JointsPositionCorrectionTechnique::BAUMGARTE_JOINTS) { + if (mJointComponents.mPositionCorrectionTechniques[jointIndex] == JointsPositionCorrectionTechnique::BAUMGARTE_JOINTS) { mHingeJointComponents.mBLowerLimit[i] = biasFactor * lowerLimitError; } // Compute the bias "b" of the upper limit constraint mHingeJointComponents.mBUpperLimit[i] = decimal(0.0); - if (mJointComponents.getPositionCorrectionTechnique(jointEntity) == JointsPositionCorrectionTechnique::BAUMGARTE_JOINTS) { + if (mJointComponents.mPositionCorrectionTechniques[jointIndex] == JointsPositionCorrectionTechnique::BAUMGARTE_JOINTS) { mHingeJointComponents.mBUpperLimit[i] = biasFactor * upperLimitError; } } @@ -279,13 +287,15 @@ void SolveHingeJointSystem::initBeforeSolve() { void SolveHingeJointSystem::warmstart() { // For each joint component - for (uint32 i=0; i < mHingeJointComponents.getNbEnabledComponents(); i++) { + const uint32 nbJoints = mHingeJointComponents.getNbEnabledComponents(); + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mHingeJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); @@ -357,13 +367,15 @@ void SolveHingeJointSystem::warmstart() { void SolveHingeJointSystem::solveVelocityConstraint() { // For each joint component - for (uint32 i=0; i < mHingeJointComponents.getNbEnabledComponents(); i++) { + const uint32 nbJoints = mHingeJointComponents.getNbEnabledComponents(); + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mHingeJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); @@ -528,16 +540,18 @@ void SolveHingeJointSystem::solveVelocityConstraint() { void SolveHingeJointSystem::solvePositionConstraint() { // For each joint component - for (uint32 i=0; i < mHingeJointComponents.getNbEnabledComponents(); i++) { + const uint32 nbEnabledJoints = mHingeJointComponents.getNbEnabledComponents(); + for (uint32 i=0; i < nbEnabledJoints; i++) { const Entity jointEntity = mHingeJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // If the error position correction technique is not the non-linear-gauss-seidel, we do not execute this method - if (mJointComponents.getPositionCorrectionTechnique(jointEntity) != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; + if (mJointComponents.mPositionCorrectionTechniques[jointIndex] != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; // Get the bodies entities - Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; // Recompute the world inverse inertia tensors const Matrix3x3 orientation1 = mTransformComponents.getTransform(body1Entity).getOrientation().getMatrix(); @@ -550,16 +564,17 @@ void SolveHingeJointSystem::solvePositionConstraint() { } // For each joint component - for (uint32 i=0; i < mHingeJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbEnabledJoints; i++) { const Entity jointEntity = mHingeJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // If the error position correction technique is not the non-linear-gauss-seidel, we do not execute this method - if (mJointComponents.getPositionCorrectionTechnique(jointEntity) != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; + if (mJointComponents.mPositionCorrectionTechniques[jointIndex] != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const Quaternion& q1 = mRigidBodyComponents.getConstrainedOrientation(body1Entity); const Quaternion& q2 = mRigidBodyComponents.getConstrainedOrientation(body2Entity); @@ -570,16 +585,17 @@ void SolveHingeJointSystem::solvePositionConstraint() { } // For each joint component - for (uint32 i=0; i < mHingeJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbEnabledJoints; i++) { const Entity jointEntity = mHingeJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // If the error position correction technique is not the non-linear-gauss-seidel, we do not execute this method - if (mJointComponents.getPositionCorrectionTechnique(jointEntity) != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; + if (mJointComponents.mPositionCorrectionTechniques[jointIndex] != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; // Compute the corresponding skew-symmetric matrices Matrix3x3 skewSymmetricMatrixU1 = Matrix3x3::computeSkewSymmetricMatrixForCrossProduct(mHingeJointComponents.mR1World[i]); @@ -607,16 +623,17 @@ void SolveHingeJointSystem::solvePositionConstraint() { } // For each joint component - for (uint32 i=0; i < mHingeJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbEnabledJoints; i++) { const Entity jointEntity = mHingeJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // If the error position correction technique is not the non-linear-gauss-seidel, we do not execute this method - if (mJointComponents.getPositionCorrectionTechnique(jointEntity) != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; + if (mJointComponents.mPositionCorrectionTechniques[jointIndex] != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); @@ -730,16 +747,17 @@ void SolveHingeJointSystem::solvePositionConstraint() { } // For each joint component - for (uint32 i=0; i < mHingeJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbEnabledJoints; i++) { const Entity jointEntity = mHingeJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // If the error position correction technique is not the non-linear-gauss-seidel, we do not execute this method - if (mJointComponents.getPositionCorrectionTechnique(jointEntity) != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; + if (mJointComponents.mPositionCorrectionTechniques[jointIndex] != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; Quaternion& q1 = mRigidBodyComponents.getConstrainedOrientation(body1Entity); Quaternion& q2 = mRigidBodyComponents.getConstrainedOrientation(body2Entity); diff --git a/src/systems/SolveSliderJointSystem.cpp b/src/systems/SolveSliderJointSystem.cpp index ac44ef7d..914ffcf8 100644 --- a/src/systems/SolveSliderJointSystem.cpp +++ b/src/systems/SolveSliderJointSystem.cpp @@ -48,13 +48,15 @@ SolveSliderJointSystem::SolveSliderJointSystem(PhysicsWorld& world, RigidBodyCom void SolveSliderJointSystem::initBeforeSolve() { // For each joint - for (uint32 i=0; i < mSliderJointComponents.getNbEnabledComponents(); i++) { + const uint32 nbJoints = mSliderJointComponents.getNbEnabledComponents(); + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mSliderJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; assert(!mRigidBodyComponents.getIsEntityDisabled(body1Entity)); assert(!mRigidBodyComponents.getIsEntityDisabled(body2Entity)); @@ -65,13 +67,14 @@ void SolveSliderJointSystem::initBeforeSolve() { } // For each joint - for (uint32 i=0; i < mSliderJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mSliderJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const Quaternion& orientationBody1 = mTransformComponents.getTransform(body1Entity).getOrientation(); const Quaternion& orientationBody2 = mTransformComponents.getTransform(body2Entity).getOrientation(); @@ -82,11 +85,12 @@ void SolveSliderJointSystem::initBeforeSolve() { } // For each joint - for (uint32 i=0; i < mSliderJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mSliderJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; const Quaternion& orientationBody1 = mTransformComponents.getTransform(body1Entity).getOrientation(); // Compute the two orthogonal vectors to the slider axis in world-space @@ -95,7 +99,7 @@ void SolveSliderJointSystem::initBeforeSolve() { } // For each joint - for (uint32 i=0; i < mSliderJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbJoints; i++) { mSliderJointComponents.mN1[i] = mSliderJointComponents.mSliderAxisWorld[i].getOneUnitOrthogonalVector(); mSliderJointComponents.mN2[i] = mSliderJointComponents.mSliderAxisWorld[i].cross(mSliderJointComponents.mN1[i]); @@ -104,13 +108,14 @@ void SolveSliderJointSystem::initBeforeSolve() { const decimal biasFactor = (BETA / mTimeStep); // For each joint - for (uint32 i=0; i < mSliderJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mSliderJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); @@ -147,7 +152,7 @@ void SolveSliderJointSystem::initBeforeSolve() { // Compute the bias "b" of the translation constraint mSliderJointComponents.mBiasTranslation[i].setToZero(); - if (mJointComponents.getPositionCorrectionTechnique(jointEntity) == JointsPositionCorrectionTechnique::BAUMGARTE_JOINTS) { + if (mJointComponents.mPositionCorrectionTechniques[jointIndex] == JointsPositionCorrectionTechnique::BAUMGARTE_JOINTS) { mSliderJointComponents.mBiasTranslation[i].x = u.dot(mSliderJointComponents.mN1[i]); mSliderJointComponents.mBiasTranslation[i].y = u.dot(mSliderJointComponents.mN2[i]); mSliderJointComponents.mBiasTranslation[i] *= biasFactor; @@ -173,20 +178,20 @@ void SolveSliderJointSystem::initBeforeSolve() { // Compute the bias "b" of the lower limit constraint mSliderJointComponents.mBLowerLimit[i] = decimal(0.0); - if (mJointComponents.getPositionCorrectionTechnique(jointEntity) == JointsPositionCorrectionTechnique::BAUMGARTE_JOINTS) { + if (mJointComponents.mPositionCorrectionTechniques[jointIndex] == JointsPositionCorrectionTechnique::BAUMGARTE_JOINTS) { mSliderJointComponents.mBLowerLimit[i] = biasFactor * lowerLimitError; } // Compute the bias "b" of the upper limit constraint mSliderJointComponents.mBUpperLimit[i] = decimal(0.0); - if (mJointComponents.getPositionCorrectionTechnique(jointEntity) == JointsPositionCorrectionTechnique::BAUMGARTE_JOINTS) { + if (mJointComponents.mPositionCorrectionTechniques[jointIndex] == JointsPositionCorrectionTechnique::BAUMGARTE_JOINTS) { mSliderJointComponents.mBUpperLimit[i] = biasFactor * upperLimitError; } } } // For each joint - for (uint32 i=0; i < mSliderJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbJoints; i++) { // Compute the cross products used in the Jacobians mSliderJointComponents.mR2CrossN1[i] = mSliderJointComponents.mR2[i].cross(mSliderJointComponents.mN1[i]); @@ -195,13 +200,14 @@ void SolveSliderJointSystem::initBeforeSolve() { } // For each joint - for (uint32 i=0; i < mSliderJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mSliderJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const Vector3& r2CrossN1 = mSliderJointComponents.mR2CrossN1[i]; const Vector3& r2CrossN2 = mSliderJointComponents.mR2CrossN2[i]; @@ -250,20 +256,21 @@ void SolveSliderJointSystem::initBeforeSolve() { } // For each joint - for (uint32 i=0; i < mSliderJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mSliderJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const Quaternion& orientationBody1 = mTransformComponents.getTransform(body1Entity).getOrientation(); const Quaternion& orientationBody2 = mTransformComponents.getTransform(body2Entity).getOrientation(); // Compute the bias "b" of the rotation constraint mSliderJointComponents.mBiasRotation[i].setToZero(); - if (mJointComponents.getPositionCorrectionTechnique(jointEntity) == JointsPositionCorrectionTechnique::BAUMGARTE_JOINTS) { + if (mJointComponents.mPositionCorrectionTechniques[jointIndex] == JointsPositionCorrectionTechnique::BAUMGARTE_JOINTS) { const Quaternion qError = orientationBody2 * mSliderJointComponents.mInitOrientationDifferenceInv[i] * orientationBody1.getInverse(); mSliderJointComponents.mBiasRotation[i] = biasFactor * decimal(2.0) * qError.getVectorV(); } @@ -286,7 +293,7 @@ void SolveSliderJointSystem::initBeforeSolve() { if (!mIsWarmStartingActive) { // For each joint - for (uint32 i=0; i < mSliderJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbJoints; i++) { // Reset all the accumulated impulses mSliderJointComponents.mImpulseTranslation[i].setToZero(); @@ -302,13 +309,15 @@ void SolveSliderJointSystem::initBeforeSolve() { void SolveSliderJointSystem::warmstart() { // For each joint component - for (uint32 i=0; i < mSliderJointComponents.getNbEnabledComponents(); i++) { + const uint32 nbJoints = mSliderJointComponents.getNbEnabledComponents(); + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mSliderJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); @@ -380,13 +389,15 @@ void SolveSliderJointSystem::warmstart() { void SolveSliderJointSystem::solveVelocityConstraint() { // For each joint component - for (uint32 i=0; i < mSliderJointComponents.getNbEnabledComponents(); i++) { + const uint32 nbJoints = mSliderJointComponents.getNbEnabledComponents(); + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mSliderJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); @@ -444,13 +455,14 @@ void SolveSliderJointSystem::solveVelocityConstraint() { } // For each joint component - for (uint32 i=0; i < mSliderJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mSliderJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); @@ -482,13 +494,14 @@ void SolveSliderJointSystem::solveVelocityConstraint() { } // For each joint component - for (uint32 i=0; i < mSliderJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbJoints; i++) { const Entity jointEntity = mSliderJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); @@ -607,17 +620,19 @@ void SolveSliderJointSystem::solveVelocityConstraint() { void SolveSliderJointSystem::solvePositionConstraint() { // For each joint component - for (uint32 i=0; i < mSliderJointComponents.getNbEnabledComponents(); i++) { + const uint32 nbEnabledJoints = mSliderJointComponents.getNbEnabledComponents(); + for (uint32 i=0; i < nbEnabledJoints; i++) { const Entity jointEntity = mSliderJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // If the error position correction technique is not the non-linear-gauss-seidel, we do // do not execute this method - if (mJointComponents.getPositionCorrectionTechnique(jointEntity) != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) return; + if (mJointComponents.mPositionCorrectionTechniques[jointIndex] != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) return; // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; // Recompute the world inverse inertia tensors const Matrix3x3 orientation1 = mTransformComponents.getTransform(body1Entity).getOrientation().getMatrix(); @@ -630,17 +645,18 @@ void SolveSliderJointSystem::solvePositionConstraint() { } // For each joint component - for (uint32 i=0; i < mSliderJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbEnabledJoints; i++) { const Entity jointEntity = mSliderJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // If the error position correction technique is not the non-linear-gauss-seidel, we do // do not execute this method - if (mJointComponents.getPositionCorrectionTechnique(jointEntity) != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) return; + if (mJointComponents.mPositionCorrectionTechniques[jointIndex] != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) return; // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const Quaternion& q1 = mRigidBodyComponents.getConstrainedOrientation(body1Entity); const Quaternion& q2 = mRigidBodyComponents.getConstrainedOrientation(body2Entity); @@ -651,17 +667,18 @@ void SolveSliderJointSystem::solvePositionConstraint() { } // For each joint component - for (uint32 i=0; i < mSliderJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbEnabledJoints; i++) { const Entity jointEntity = mSliderJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // If the error position correction technique is not the non-linear-gauss-seidel, we do // do not execute this method - if (mJointComponents.getPositionCorrectionTechnique(jointEntity) != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) return; + if (mJointComponents.mPositionCorrectionTechniques[jointIndex] != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) return; // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); @@ -776,17 +793,18 @@ void SolveSliderJointSystem::solvePositionConstraint() { } // For each joint component - for (uint32 i=0; i < mSliderJointComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbEnabledJoints; i++) { const Entity jointEntity = mSliderJointComponents.mJointEntities[i]; + const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // If the error position correction technique is not the non-linear-gauss-seidel, we do // do not execute this method - if (mJointComponents.getPositionCorrectionTechnique(jointEntity) != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) return; + if (mJointComponents.mPositionCorrectionTechniques[jointIndex] != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) return; // Get the bodies entities - const Entity body1Entity = mJointComponents.getBody1Entity(jointEntity); - const Entity body2Entity = mJointComponents.getBody2Entity(jointEntity); + const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; + const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); From 20789713319ee0f858c17090ebaff432a613849b Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Mon, 12 Oct 2020 23:56:33 +0200 Subject: [PATCH 50/74] Optimization of joints solver --- src/systems/SolveBallAndSocketJointSystem.cpp | 114 +-------- src/systems/SolveFixedJointSystem.cpp | 136 +---------- src/systems/SolveHingeJointSystem.cpp | 185 ++------------- src/systems/SolveSliderJointSystem.cpp | 217 ++---------------- 4 files changed, 66 insertions(+), 586 deletions(-) diff --git a/src/systems/SolveBallAndSocketJointSystem.cpp b/src/systems/SolveBallAndSocketJointSystem.cpp index e46707c4..c41388c7 100644 --- a/src/systems/SolveBallAndSocketJointSystem.cpp +++ b/src/systems/SolveBallAndSocketJointSystem.cpp @@ -47,6 +47,8 @@ SolveBallAndSocketJointSystem::SolveBallAndSocketJointSystem(PhysicsWorld& world // Initialize before solving the constraint void SolveBallAndSocketJointSystem::initBeforeSolve() { + const decimal biasFactor = (BETA / mTimeStep); + // For each joint const uint32 nbJoints = mBallAndSocketJointComponents.getNbEnabledComponents(); for (uint32 i=0; i < nbJoints; i++) { @@ -64,17 +66,6 @@ void SolveBallAndSocketJointSystem::initBeforeSolve() { // Get the inertia tensor of bodies mBallAndSocketJointComponents.mI1[i] = mRigidBodyComponents.getInertiaTensorWorldInverse(body1Entity); mBallAndSocketJointComponents.mI2[i] = mRigidBodyComponents.getInertiaTensorWorldInverse(body2Entity); - } - - // For each joint - for (uint32 i=0; i < nbJoints; i++) { - - const Entity jointEntity = mBallAndSocketJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // Get the bodies entities - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const Quaternion& orientationBody1 = mTransformComponents.getTransform(body1Entity).getOrientation(); const Quaternion& orientationBody2 = mTransformComponents.getTransform(body2Entity).getOrientation(); @@ -82,13 +73,6 @@ void SolveBallAndSocketJointSystem::initBeforeSolve() { // Compute the vector from body center to the anchor point in world-space mBallAndSocketJointComponents.mR1World[i] = orientationBody1 * mBallAndSocketJointComponents.mLocalAnchorPointBody1[i]; mBallAndSocketJointComponents.mR2World[i] = orientationBody2 * mBallAndSocketJointComponents.mLocalAnchorPointBody2[i]; - } - - // For each joint - for (uint32 i=0; i < nbJoints; i++) { - - const Entity jointEntity = mBallAndSocketJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); // Compute the corresponding skew-symmetric matrices const Vector3& r1World = mBallAndSocketJointComponents.mR1World[i]; @@ -96,10 +80,6 @@ void SolveBallAndSocketJointSystem::initBeforeSolve() { Matrix3x3 skewSymmetricMatrixU1 = Matrix3x3::computeSkewSymmetricMatrixForCrossProduct(r1World); Matrix3x3 skewSymmetricMatrixU2 = Matrix3x3::computeSkewSymmetricMatrixForCrossProduct(r2World); - // Get the bodies entities - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; - const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); @@ -121,22 +101,6 @@ void SolveBallAndSocketJointSystem::initBeforeSolve() { mRigidBodyComponents.mBodyTypes[componentIndexBody2] == BodyType::DYNAMIC) { mBallAndSocketJointComponents.mInverseMassMatrix[i] = massMatrix.getInverse(); } - } - - const decimal biasFactor = (BETA / mTimeStep); - - // For each joint - for (uint32 i=0; i < nbJoints; i++) { - - const Entity jointEntity = mBallAndSocketJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // Get the bodies entities - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; - - const Vector3& r1World = mBallAndSocketJointComponents.mR1World[i]; - const Vector3& r2World = mBallAndSocketJointComponents.mR2World[i]; const Vector3& x1 = mRigidBodyComponents.getCenterOfMassWorld(body1Entity); const Vector3& x2 = mRigidBodyComponents.getCenterOfMassWorld(body2Entity); @@ -146,13 +110,9 @@ void SolveBallAndSocketJointSystem::initBeforeSolve() { if (mJointComponents.mPositionCorrectionTechniques[jointIndex] == JointsPositionCorrectionTechnique::BAUMGARTE_JOINTS) { mBallAndSocketJointComponents.mBiasVector[i] = biasFactor * (x2 + r2World - x1 - r1World); } - } - // If warm-starting is not enabled - if (!mIsWarmStartingActive) { - - // For each joint - for (uint32 i=0; i < mBallAndSocketJointComponents.getNbEnabledComponents(); i++) { + // If warm-starting is not enabled + if (!mIsWarmStartingActive) { // Reset the accumulated impulse mBallAndSocketJointComponents.mImpulse[i].setToZero(); @@ -271,51 +231,23 @@ void SolveBallAndSocketJointSystem::solvePositionConstraint() { const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; + const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); + const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); + // Recompute the world inverse inertia tensors const Matrix3x3 orientation1 = mTransformComponents.getTransform(body1Entity).getOrientation().getMatrix(); - RigidBody::computeWorldInertiaTensorInverse(orientation1, mRigidBodyComponents.getInertiaTensorLocalInverse(body1Entity), + RigidBody::computeWorldInertiaTensorInverse(orientation1, mRigidBodyComponents.mInverseInertiaTensorsLocal[componentIndexBody1], mBallAndSocketJointComponents.mI1[i]); const Matrix3x3 orientation2 = mTransformComponents.getTransform(body2Entity).getOrientation().getMatrix(); - RigidBody::computeWorldInertiaTensorInverse(orientation2, mRigidBodyComponents.getInertiaTensorLocalInverse(body2Entity), + RigidBody::computeWorldInertiaTensorInverse(orientation2, mRigidBodyComponents.mInverseInertiaTensorsLocal[componentIndexBody2], mBallAndSocketJointComponents.mI2[i]); - } - - // For each joint component - for (uint32 i=0; i < nbEnabledJoints; i++) { - - const Entity jointEntity = mBallAndSocketJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // If the error position correction technique is not the non-linear-gauss-seidel, we do - // do not execute this method - if (mJointComponents.mPositionCorrectionTechniques[jointIndex] != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; - - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; // Compute the vector from body center to the anchor point in world-space - mBallAndSocketJointComponents.mR1World[i] = mRigidBodyComponents.getConstrainedOrientation(body1Entity) * + mBallAndSocketJointComponents.mR1World[i] = mRigidBodyComponents.mConstrainedOrientations[componentIndexBody1] * mBallAndSocketJointComponents.mLocalAnchorPointBody1[i]; - mBallAndSocketJointComponents.mR2World[i] = mRigidBodyComponents.getConstrainedOrientation(body2Entity) * + mBallAndSocketJointComponents.mR2World[i] = mRigidBodyComponents.mConstrainedOrientations[componentIndexBody2] * mBallAndSocketJointComponents.mLocalAnchorPointBody2[i]; - } - - // For each joint component - for (uint32 i=0; i < nbEnabledJoints; i++) { - - const Entity jointEntity = mBallAndSocketJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // If the error position correction technique is not the non-linear-gauss-seidel, we do - // do not execute this method - if (mJointComponents.mPositionCorrectionTechniques[jointIndex] != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; - - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; - - const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); - const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); const Vector3& r1World = mBallAndSocketJointComponents.mR1World[i]; const Vector3& r2World = mBallAndSocketJointComponents.mR2World[i]; @@ -340,30 +272,10 @@ void SolveBallAndSocketJointSystem::solvePositionConstraint() { mRigidBodyComponents.mBodyTypes[componentIndexBody2] == BodyType::DYNAMIC) { mBallAndSocketJointComponents.mInverseMassMatrix[i] = massMatrix.getInverse(); } - } - - // For each joint component - for (uint32 i=0; i < nbEnabledJoints; i++) { - - const Entity jointEntity = mBallAndSocketJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // If the error position correction technique is not the non-linear-gauss-seidel, we do - // do not execute this method - if (mJointComponents.mPositionCorrectionTechniques[jointIndex] != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; - - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; - - const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); - const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); Vector3& x1 = mRigidBodyComponents.mConstrainedPositions[componentIndexBody1]; Vector3& x2 = mRigidBodyComponents.mConstrainedPositions[componentIndexBody2]; - const Vector3& r1World = mBallAndSocketJointComponents.mR1World[i]; - const Vector3& r2World = mBallAndSocketJointComponents.mR2World[i]; - // Compute the constraint error (value of the C(x) function) const Vector3 constraintError = (x2 + r2World - x1 - r1World); @@ -376,10 +288,6 @@ void SolveBallAndSocketJointSystem::solvePositionConstraint() { const Vector3 linearImpulseBody1 = -lambda; const Vector3 angularImpulseBody1 = lambda.cross(r1World); - // Get the inverse mass and inverse inertia tensors of the bodies - const decimal inverseMassBody1 = mRigidBodyComponents.mInverseMasses[componentIndexBody1]; - const decimal inverseMassBody2 = mRigidBodyComponents.mInverseMasses[componentIndexBody2]; - // Compute the pseudo velocity of body 1 const Vector3 v1 = inverseMassBody1 * linearImpulseBody1; const Vector3 w1 = mBallAndSocketJointComponents.mI1[i] * angularImpulseBody1; diff --git a/src/systems/SolveFixedJointSystem.cpp b/src/systems/SolveFixedJointSystem.cpp index 054d78e5..ced19853 100644 --- a/src/systems/SolveFixedJointSystem.cpp +++ b/src/systems/SolveFixedJointSystem.cpp @@ -47,6 +47,8 @@ SolveFixedJointSystem::SolveFixedJointSystem(PhysicsWorld& world, RigidBodyCompo // Initialize before solving the constraint void SolveFixedJointSystem::initBeforeSolve() { + const decimal biasFactor = BETA / mTimeStep; + // For each joint const uint32 nbJoints = mFixedJointComponents.getNbEnabledComponents(); for (uint32 i=0; i < nbJoints; i++) { @@ -64,17 +66,6 @@ void SolveFixedJointSystem::initBeforeSolve() { // Get the inertia tensor of bodies mFixedJointComponents.mI1[i] = mRigidBodyComponents.getInertiaTensorWorldInverse(body1Entity); mFixedJointComponents.mI2[i] = mRigidBodyComponents.getInertiaTensorWorldInverse(body2Entity); - } - - // For each joint - for (uint32 i=0; i < nbJoints; i++) { - - const Entity jointEntity = mFixedJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // Get the bodies entities - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const Quaternion& orientationBody1 = mTransformComponents.getTransform(body1Entity).getOrientation(); const Quaternion& orientationBody2 = mTransformComponents.getTransform(body2Entity).getOrientation(); @@ -82,17 +73,6 @@ void SolveFixedJointSystem::initBeforeSolve() { // Compute the vector from body center to the anchor point in world-space mFixedJointComponents.mR1World[i] = orientationBody1 * mFixedJointComponents.mLocalAnchorPointBody1[i]; mFixedJointComponents.mR2World[i] = orientationBody2 * mFixedJointComponents.mLocalAnchorPointBody2[i]; - } - - // For each joint - for (uint32 i=0; i < nbJoints; i++) { - - const Entity jointEntity = mFixedJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // Get the bodies entities - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; // Compute the corresponding skew-symmetric matrices Matrix3x3 skewSymmetricMatrixU1 = Matrix3x3::computeSkewSymmetricMatrixForCrossProduct(mFixedJointComponents.mR1World[i]); @@ -117,19 +97,6 @@ void SolveFixedJointSystem::initBeforeSolve() { mRigidBodyComponents.mBodyTypes[componentIndexBody2] == BodyType::DYNAMIC) { mFixedJointComponents.mInverseMassMatrixTranslation[i] = massMatrix.getInverse(); } - } - - const decimal biasFactor = BETA / mTimeStep; - - // For each joint - for (uint32 i=0; i < nbJoints; i++) { - - const Entity jointEntity = mFixedJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // Get the bodies entities - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; // Get the bodies positions and orientations const Vector3& x1 = mRigidBodyComponents.getCenterOfMassWorld(body1Entity); @@ -143,17 +110,6 @@ void SolveFixedJointSystem::initBeforeSolve() { if (mJointComponents.mPositionCorrectionTechniques[jointIndex] == JointsPositionCorrectionTechnique::BAUMGARTE_JOINTS) { mFixedJointComponents.mBiasTranslation[i] = biasFactor * (x2 + r2World - x1 - r1World); } - } - - // For each joint - for (uint32 i=0; i < nbJoints; i++) { - - const Entity jointEntity = mFixedJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // Get the bodies entities - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; // Compute the inverse of the mass matrix K=JM^-1J^t for the 3 rotation contraints (3x3 matrix) mFixedJointComponents.mInverseMassMatrixRotation[i] = mFixedJointComponents.mI1[i] + mFixedJointComponents.mI2[i]; @@ -161,35 +117,17 @@ void SolveFixedJointSystem::initBeforeSolve() { mRigidBodyComponents.getBodyType(body2Entity) == BodyType::DYNAMIC) { mFixedJointComponents.mInverseMassMatrixRotation[i] = mFixedJointComponents.mInverseMassMatrixRotation[i].getInverse(); } - } - - // For each joint - for (uint32 i=0; i < nbJoints; i++) { - - const Entity jointEntity = mFixedJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // Get the bodies entities - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; // Compute the bias "b" for the 3 rotation constraints mFixedJointComponents.mBiasRotation[i].setToZero(); - const Quaternion& orientationBody1 = mTransformComponents.getTransform(body1Entity).getOrientation(); - const Quaternion& orientationBody2 = mTransformComponents.getTransform(body2Entity).getOrientation(); - if (mJointComponents.mPositionCorrectionTechniques[jointIndex] == JointsPositionCorrectionTechnique::BAUMGARTE_JOINTS) { const Quaternion qError = orientationBody2 * mFixedJointComponents.mInitOrientationDifferenceInv[i] * orientationBody1.getInverse(); mFixedJointComponents.mBiasRotation[i] = biasFactor * decimal(2.0) * qError.getVectorV(); } - } - // If warm-starting is not enabled - if (!mIsWarmStartingActive) { - - // For each joint - for (uint32 i=0; i < nbJoints; i++) { + // If warm-starting is not enabled + if (!mIsWarmStartingActive) { // Reset the accumulated impulses mFixedJointComponents.mImpulseTranslation[i].setToZero(); @@ -367,47 +305,17 @@ void SolveFixedJointSystem::solvePositionConstraint() { const Matrix3x3 orientation2 = mTransformComponents.getTransform(body2Entity).getOrientation().getMatrix(); RigidBody::computeWorldInertiaTensorInverse(orientation2, mRigidBodyComponents.getInertiaTensorLocalInverse(body2Entity), mFixedJointComponents.mI2[i]); - } - // For each joint - for (uint32 i=0; i < nbEnabledJoints; i++) { - - const Entity jointEntity = mFixedJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // If the error position correction technique is not the non-linear-gauss-seidel, we do - // do not execute this method - if (mJointComponents.mPositionCorrectionTechniques[jointIndex] != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; - - // Get the bodies entities - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; + const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); + const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); // Get the bodies positions and orientations - const Quaternion& q1 = mRigidBodyComponents.getConstrainedOrientation(body1Entity); - const Quaternion& q2 = mRigidBodyComponents.getConstrainedOrientation(body2Entity); + Quaternion& q1 = mRigidBodyComponents.mConstrainedOrientations[componentIndexBody1]; + Quaternion& q2 = mRigidBodyComponents.mConstrainedOrientations[componentIndexBody2]; // Compute the vector from body center to the anchor point in world-space mFixedJointComponents.mR1World[i] = q1 * mFixedJointComponents.mLocalAnchorPointBody1[i]; mFixedJointComponents.mR2World[i] = q2 * mFixedJointComponents.mLocalAnchorPointBody2[i]; - } - - // For each joint - for (uint32 i=0; i < nbEnabledJoints; i++) { - - const Entity jointEntity = mFixedJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // If the error position correction technique is not the non-linear-gauss-seidel, we do - // do not execute this method - if (mJointComponents.mPositionCorrectionTechniques[jointIndex] != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; - - // Get the bodies entities - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; - - const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); - const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); // Get the inverse mass and inverse inertia tensors of the bodies decimal inverseMassBody1 = mRigidBodyComponents.mInverseMasses[componentIndexBody1]; @@ -434,33 +342,9 @@ void SolveFixedJointSystem::solvePositionConstraint() { mRigidBodyComponents.mBodyTypes[componentIndexBody2] == BodyType::DYNAMIC) { mFixedJointComponents.mInverseMassMatrixTranslation[i] = massMatrix.getInverse(); } - } - - // For each joint - for (uint32 i=0; i < nbEnabledJoints; i++) { - - const Entity jointEntity = mFixedJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // If the error position correction technique is not the non-linear-gauss-seidel, we do - // do not execute this method - if (mJointComponents.mPositionCorrectionTechniques[jointIndex] != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; - - // Get the bodies entities - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; - - const Vector3& r1World = mFixedJointComponents.mR1World[i]; - const Vector3& r2World = mFixedJointComponents.mR2World[i]; - - const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); - const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); Vector3& x1 = mRigidBodyComponents.mConstrainedPositions[componentIndexBody1]; Vector3& x2 = mRigidBodyComponents.mConstrainedPositions[componentIndexBody2]; - Quaternion& q1 = mRigidBodyComponents.mConstrainedOrientations[componentIndexBody1]; - Quaternion& q2 = mRigidBodyComponents.mConstrainedOrientations[componentIndexBody2]; - // Compute position error for the 3 translation constraints const Vector3 errorTranslation = x2 + r2World - x1 - r1World; @@ -471,8 +355,6 @@ void SolveFixedJointSystem::solvePositionConstraint() { Vector3 linearImpulseBody1 = -lambdaTranslation; Vector3 angularImpulseBody1 = lambdaTranslation.cross(r1World); - const decimal inverseMassBody1 = mRigidBodyComponents.mInverseMasses[componentIndexBody1]; - // Compute the pseudo velocity of body 1 const Vector3 v1 = inverseMassBody1 * linearImpulseBody1; Vector3 w1 = mFixedJointComponents.mI2[i] * angularImpulseBody1; @@ -485,8 +367,6 @@ void SolveFixedJointSystem::solvePositionConstraint() { // Compute the impulse of body 2 Vector3 angularImpulseBody2 = -lambdaTranslation.cross(r2World); - const decimal inverseMassBody2 = mRigidBodyComponents.mInverseMasses[componentIndexBody2]; - // Compute the pseudo velocity of body 2 const Vector3 v2 = inverseMassBody2 * lambdaTranslation; Vector3 w2 = mFixedJointComponents.mI2[i] * angularImpulseBody2; diff --git a/src/systems/SolveHingeJointSystem.cpp b/src/systems/SolveHingeJointSystem.cpp index 45b66aed..8279664e 100644 --- a/src/systems/SolveHingeJointSystem.cpp +++ b/src/systems/SolveHingeJointSystem.cpp @@ -47,6 +47,8 @@ SolveHingeJointSystem::SolveHingeJointSystem(PhysicsWorld& world, RigidBodyCompo // Initialize before solving the constraint void SolveHingeJointSystem::initBeforeSolve() { + const decimal biasFactor = (BETA / mTimeStep); + // For each joint const uint32 nbJoints = mHingeJointComponents.getNbEnabledComponents(); for (uint32 i=0; i < nbJoints; i++) { @@ -58,23 +60,15 @@ void SolveHingeJointSystem::initBeforeSolve() { const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; - assert(!mRigidBodyComponents.getIsEntityDisabled(body1Entity)); - assert(!mRigidBodyComponents.getIsEntityDisabled(body2Entity)); + const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); + const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); + + assert(!mRigidBodyComponents.mIsEntityDisabled[componentIndexBody1]); + assert(!mRigidBodyComponents.mIsEntityDisabled[componentIndexBody2]); // Get the inertia tensor of bodies - mHingeJointComponents.mI1[i] = mRigidBodyComponents.getInertiaTensorWorldInverse(body1Entity); - mHingeJointComponents.mI2[i] = mRigidBodyComponents.getInertiaTensorWorldInverse(body2Entity); - } - - // For each joint - for (uint32 i=0; i < nbJoints; i++) { - - const Entity jointEntity = mHingeJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // Get the bodies entities - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; + mHingeJointComponents.mI1[i] = mRigidBodyComponents.mInverseInertiaTensorsWorld[componentIndexBody1]; + mHingeJointComponents.mI2[i] = mRigidBodyComponents.mInverseInertiaTensorsWorld[componentIndexBody2]; const Quaternion& orientationBody1 = mTransformComponents.getTransform(body1Entity).getOrientation(); const Quaternion& orientationBody2 = mTransformComponents.getTransform(body2Entity).getOrientation(); @@ -82,22 +76,6 @@ void SolveHingeJointSystem::initBeforeSolve() { // Compute the vector from body center to the anchor point in world-space mHingeJointComponents.mR1World[i] = orientationBody1 * mHingeJointComponents.mLocalAnchorPointBody1[i]; mHingeJointComponents.mR2World[i] = orientationBody2 * mHingeJointComponents.mLocalAnchorPointBody2[i]; - } - - const decimal biasFactor = (BETA / mTimeStep); - - // For each joint - for (uint32 i=0; i < nbJoints; i++) { - - const Entity jointEntity = mHingeJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // Get the bodies entities - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; - - const Quaternion& orientationBody1 = mTransformComponents.getTransform(body1Entity).getOrientation(); - const Quaternion& orientationBody2 = mTransformComponents.getTransform(body2Entity).getOrientation(); // Compute vectors needed in the Jacobian Vector3& a1 = mHingeJointComponents.mA1[i]; @@ -116,25 +94,11 @@ void SolveHingeJointSystem::initBeforeSolve() { if (mJointComponents.mPositionCorrectionTechniques[jointIndex] == JointsPositionCorrectionTechnique::BAUMGARTE_JOINTS) { mHingeJointComponents.mBiasRotation[i] = biasFactor * Vector2(a1.dot(b2), a1.dot(c2)); } - } - - // For each joint - for (uint32 i=0; i < nbJoints; i++) { - - const Entity jointEntity = mHingeJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // Get the bodies entities - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; // Compute the corresponding skew-symmetric matrices Matrix3x3 skewSymmetricMatrixU1= Matrix3x3::computeSkewSymmetricMatrixForCrossProduct(mHingeJointComponents.mR1World[i]); Matrix3x3 skewSymmetricMatrixU2= Matrix3x3::computeSkewSymmetricMatrixForCrossProduct(mHingeJointComponents.mR2World[i]); - const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); - const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); - // Compute the inverse mass matrix K=JM^-1J^t for the 3 translation constraints (3x3 matrix) decimal body1MassInverse = mRigidBodyComponents.mInverseMasses[componentIndexBody1]; decimal body2MassInverse = mRigidBodyComponents.mInverseMasses[componentIndexBody2]; @@ -150,38 +114,16 @@ void SolveHingeJointSystem::initBeforeSolve() { mRigidBodyComponents.mBodyTypes[componentIndexBody2] == BodyType::DYNAMIC) { mHingeJointComponents.mInverseMassMatrixTranslation[i] = massMatrix.getInverse(); } - } - - // For each joint - for (uint32 i=0; i < nbJoints; i++) { - - const Entity jointEntity = mHingeJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // Get the bodies entities - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; // Get the bodies positions and orientations - const Vector3& x1 = mRigidBodyComponents.getCenterOfMassWorld(body1Entity); - const Vector3& x2 = mRigidBodyComponents.getCenterOfMassWorld(body2Entity); + const Vector3& x1 = mRigidBodyComponents.mCentersOfMassWorld[componentIndexBody1]; + const Vector3& x2 = mRigidBodyComponents.mCentersOfMassWorld[componentIndexBody2]; // Compute the bias "b" of the translation constraints mHingeJointComponents.mBiasTranslation[i].setToZero(); if (mJointComponents.mPositionCorrectionTechniques[jointIndex] == JointsPositionCorrectionTechnique::BAUMGARTE_JOINTS) { mHingeJointComponents.mBiasTranslation[i] = biasFactor * (x2 + mHingeJointComponents.mR2World[i] - x1 - mHingeJointComponents.mR1World[i]); } - } - - // For each joint - for (uint32 i=0; i < nbJoints; i++) { - - const Entity jointEntity = mHingeJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // Get the bodies entities - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const Matrix3x3& i1 = mHingeJointComponents.mI1[i]; const Matrix3x3& i2 = mHingeJointComponents.mI2[i]; @@ -199,17 +141,13 @@ void SolveHingeJointSystem::initBeforeSolve() { const decimal el22 = c2CrossA1.dot(i1C2CrossA1) + c2CrossA1.dot(i2C2CrossA1); const Matrix2x2 matrixKRotation(el11, el12, el21, el22); mHingeJointComponents.mInverseMassMatrixRotation[i].setToZero(); - if (mRigidBodyComponents.getBodyType(body1Entity) == BodyType::DYNAMIC || - mRigidBodyComponents.getBodyType(body2Entity) == BodyType::DYNAMIC) { + if (mRigidBodyComponents.mBodyTypes[componentIndexBody1] == BodyType::DYNAMIC || + mRigidBodyComponents.mBodyTypes[componentIndexBody2] == BodyType::DYNAMIC) { mHingeJointComponents.mInverseMassMatrixRotation[i] = matrixKRotation.getInverse(); } - } - // If warm-starting is not enabled - if (!mIsWarmStartingActive) { - - // For each joint - for (uint32 i=0; i < nbJoints; i++) { + // If warm-starting is not enabled + if (!mIsWarmStartingActive) { // Reset all the accumulated impulses mHingeJointComponents.mImpulseTranslation[i].setToZero(); @@ -218,20 +156,6 @@ void SolveHingeJointSystem::initBeforeSolve() { mHingeJointComponents.mImpulseUpperLimit[i] = decimal(0.0); mHingeJointComponents.mImpulseMotor[i] = decimal(0.0); } - } - - // For each joint - for (uint32 i=0; i < nbJoints; i++) { - - const Entity jointEntity = mHingeJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // Get the bodies entities - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; - - const Quaternion& orientationBody1 = mTransformComponents.getTransform(body1Entity).getOrientation(); - const Quaternion& orientationBody2 = mTransformComponents.getTransform(body2Entity).getOrientation(); // Compute the current angle around the hinge axis decimal hingeAngle = computeCurrentHingeAngle(jointEntity, orientationBody1, orientationBody2); @@ -553,49 +477,24 @@ void SolveHingeJointSystem::solvePositionConstraint() { Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; + const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); + const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); + // Recompute the world inverse inertia tensors const Matrix3x3 orientation1 = mTransformComponents.getTransform(body1Entity).getOrientation().getMatrix(); - RigidBody::computeWorldInertiaTensorInverse(orientation1, mRigidBodyComponents.getInertiaTensorLocalInverse(body1Entity), + RigidBody::computeWorldInertiaTensorInverse(orientation1, mRigidBodyComponents.mInverseInertiaTensorsLocal[componentIndexBody1], mHingeJointComponents.mI1[i]); const Matrix3x3 orientation2 = mTransformComponents.getTransform(body2Entity).getOrientation().getMatrix(); - RigidBody::computeWorldInertiaTensorInverse(orientation2, mRigidBodyComponents.getInertiaTensorLocalInverse(body2Entity), + RigidBody::computeWorldInertiaTensorInverse(orientation2, mRigidBodyComponents.mInverseInertiaTensorsLocal[componentIndexBody2], mHingeJointComponents.mI2[i]); - } - // For each joint component - for (uint32 i=0; i < nbEnabledJoints; i++) { - - const Entity jointEntity = mHingeJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // If the error position correction technique is not the non-linear-gauss-seidel, we do not execute this method - if (mJointComponents.mPositionCorrectionTechniques[jointIndex] != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; - - // Get the bodies entities - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; - - const Quaternion& q1 = mRigidBodyComponents.getConstrainedOrientation(body1Entity); - const Quaternion& q2 = mRigidBodyComponents.getConstrainedOrientation(body2Entity); + Quaternion& q1 = mRigidBodyComponents.mConstrainedOrientations[componentIndexBody1]; + Quaternion& q2 = mRigidBodyComponents.mConstrainedOrientations[componentIndexBody2]; // Compute the vector from body center to the anchor point in world-space mHingeJointComponents.mR1World[i] = q1 * mHingeJointComponents.mLocalAnchorPointBody1[i]; mHingeJointComponents.mR2World[i] = q2 * mHingeJointComponents.mLocalAnchorPointBody2[i]; - } - - // For each joint component - for (uint32 i=0; i < nbEnabledJoints; i++) { - - const Entity jointEntity = mHingeJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // If the error position correction technique is not the non-linear-gauss-seidel, we do not execute this method - if (mJointComponents.mPositionCorrectionTechniques[jointIndex] != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; - - // Get the bodies entities - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; // Compute the corresponding skew-symmetric matrices Matrix3x3 skewSymmetricMatrixU1 = Matrix3x3::computeSkewSymmetricMatrixForCrossProduct(mHingeJointComponents.mR1World[i]); @@ -603,9 +502,6 @@ void SolveHingeJointSystem::solvePositionConstraint() { // --------------- Translation Constraints --------------- // - const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); - const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); - // Compute the matrix K=JM^-1J^t (3x3 matrix) for the 3 translation constraints const decimal body1InverseMass = mRigidBodyComponents.mInverseMasses[componentIndexBody1]; const decimal body2InverseMass = mRigidBodyComponents.mInverseMasses[componentIndexBody2]; @@ -620,26 +516,6 @@ void SolveHingeJointSystem::solvePositionConstraint() { mRigidBodyComponents.mBodyTypes[componentIndexBody2] == BodyType::DYNAMIC) { mHingeJointComponents.mInverseMassMatrixTranslation[i] = massMatrix.getInverse(); } - } - - // For each joint component - for (uint32 i=0; i < nbEnabledJoints; i++) { - - const Entity jointEntity = mHingeJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // If the error position correction technique is not the non-linear-gauss-seidel, we do not execute this method - if (mJointComponents.mPositionCorrectionTechniques[jointIndex] != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; - - // Get the bodies entities - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; - - const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); - const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); - - Quaternion& q1 = mRigidBodyComponents.mConstrainedOrientations[componentIndexBody1]; - Quaternion& q2 = mRigidBodyComponents.mConstrainedOrientations[componentIndexBody2]; Vector3& b2CrossA1 = mHingeJointComponents.mB2CrossA1[i]; Vector3& c2CrossA1 = mHingeJointComponents.mC2CrossA1[i]; @@ -744,23 +620,6 @@ void SolveHingeJointSystem::solvePositionConstraint() { // Update the body position/orientation of body 2 q2 += Quaternion(0, w2) * q2 * decimal(0.5); q2.normalize(); - } - - // For each joint component - for (uint32 i=0; i < nbEnabledJoints; i++) { - - const Entity jointEntity = mHingeJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // If the error position correction technique is not the non-linear-gauss-seidel, we do not execute this method - if (mJointComponents.mPositionCorrectionTechniques[jointIndex] != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) continue; - - // Get the bodies entities - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; - - Quaternion& q1 = mRigidBodyComponents.getConstrainedOrientation(body1Entity); - Quaternion& q2 = mRigidBodyComponents.getConstrainedOrientation(body2Entity); // Compute the current angle around the hinge axis const decimal hingeAngle = computeCurrentHingeAngle(jointEntity, q1, q2); diff --git a/src/systems/SolveSliderJointSystem.cpp b/src/systems/SolveSliderJointSystem.cpp index 914ffcf8..ee47357f 100644 --- a/src/systems/SolveSliderJointSystem.cpp +++ b/src/systems/SolveSliderJointSystem.cpp @@ -47,6 +47,8 @@ SolveSliderJointSystem::SolveSliderJointSystem(PhysicsWorld& world, RigidBodyCom // Initialize before solving the constraint void SolveSliderJointSystem::initBeforeSolve() { + const decimal biasFactor = (BETA / mTimeStep); + // For each joint const uint32 nbJoints = mSliderJointComponents.getNbEnabledComponents(); for (uint32 i=0; i < nbJoints; i++) { @@ -58,23 +60,15 @@ void SolveSliderJointSystem::initBeforeSolve() { const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; - assert(!mRigidBodyComponents.getIsEntityDisabled(body1Entity)); - assert(!mRigidBodyComponents.getIsEntityDisabled(body2Entity)); + const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); + const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); + + assert(!mRigidBodyComponents.mIsEntityDisabled[componentIndexBody1]); + assert(!mRigidBodyComponents.mIsEntityDisabled[componentIndexBody2]); // Get the inertia tensor of bodies - mSliderJointComponents.mI1[i] = mRigidBodyComponents.getInertiaTensorWorldInverse(body1Entity); - mSliderJointComponents.mI2[i] = mRigidBodyComponents.getInertiaTensorWorldInverse(body2Entity); - } - - // For each joint - for (uint32 i=0; i < nbJoints; i++) { - - const Entity jointEntity = mSliderJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // Get the bodies entities - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; + mSliderJointComponents.mI1[i] = mRigidBodyComponents.mInverseInertiaTensorsWorld[componentIndexBody1]; + mSliderJointComponents.mI2[i] = mRigidBodyComponents.mInverseInertiaTensorsWorld[componentIndexBody2]; const Quaternion& orientationBody1 = mTransformComponents.getTransform(body1Entity).getOrientation(); const Quaternion& orientationBody2 = mTransformComponents.getTransform(body2Entity).getOrientation(); @@ -82,43 +76,13 @@ void SolveSliderJointSystem::initBeforeSolve() { // Vector from body center to the anchor point mSliderJointComponents.mR1[i] = orientationBody1 * mSliderJointComponents.mLocalAnchorPointBody1[i]; mSliderJointComponents.mR2[i] = orientationBody2 * mSliderJointComponents.mLocalAnchorPointBody2[i]; - } - - // For each joint - for (uint32 i=0; i < nbJoints; i++) { - - const Entity jointEntity = mSliderJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Quaternion& orientationBody1 = mTransformComponents.getTransform(body1Entity).getOrientation(); // Compute the two orthogonal vectors to the slider axis in world-space mSliderJointComponents.mSliderAxisWorld[i] = orientationBody1 * mSliderJointComponents.mSliderAxisBody1[i]; mSliderJointComponents.mSliderAxisWorld[i].normalize(); - } - - // For each joint - for (uint32 i=0; i < nbJoints; i++) { mSliderJointComponents.mN1[i] = mSliderJointComponents.mSliderAxisWorld[i].getOneUnitOrthogonalVector(); mSliderJointComponents.mN2[i] = mSliderJointComponents.mSliderAxisWorld[i].cross(mSliderJointComponents.mN1[i]); - } - - const decimal biasFactor = (BETA / mTimeStep); - - // For each joint - for (uint32 i=0; i < nbJoints; i++) { - - const Entity jointEntity = mSliderJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // Get the bodies entities - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; - - const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); - const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); const Vector3& x1 = mRigidBodyComponents.mCentersOfMassWorld[componentIndexBody1]; const Vector3& x2 = mRigidBodyComponents.mCentersOfMassWorld[componentIndexBody2]; @@ -188,26 +152,11 @@ void SolveSliderJointSystem::initBeforeSolve() { mSliderJointComponents.mBUpperLimit[i] = biasFactor * upperLimitError; } } - } - - // For each joint - for (uint32 i=0; i < nbJoints; i++) { // Compute the cross products used in the Jacobians mSliderJointComponents.mR2CrossN1[i] = mSliderJointComponents.mR2[i].cross(mSliderJointComponents.mN1[i]); mSliderJointComponents.mR2CrossN2[i] = mSliderJointComponents.mR2[i].cross(mSliderJointComponents.mN2[i]); mSliderJointComponents.mR2CrossSliderAxis[i] = mSliderJointComponents.mR2[i].cross(mSliderJointComponents.mSliderAxisWorld[i]); - } - - // For each joint - for (uint32 i=0; i < nbJoints; i++) { - - const Entity jointEntity = mSliderJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // Get the bodies entities - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; const Vector3& r2CrossN1 = mSliderJointComponents.mR2CrossN1[i]; const Vector3& r2CrossN2 = mSliderJointComponents.mR2CrossN2[i]; @@ -217,9 +166,6 @@ void SolveSliderJointSystem::initBeforeSolve() { const Matrix3x3& i1 = mSliderJointComponents.mI1[i]; const Matrix3x3& i2 = mSliderJointComponents.mI2[i]; - const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); - const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); - // Compute the inverse of the mass matrix K=JM^-1J^t for the 2 translation // constraints (2x2 matrix) const decimal body1MassInverse = mRigidBodyComponents.mInverseMasses[componentIndexBody1]; @@ -253,20 +199,6 @@ void SolveSliderJointSystem::initBeforeSolve() { mSliderJointComponents.mInverseMassMatrixRotation[i] = mSliderJointComponents.mInverseMassMatrixRotation[i].getInverse(); } - } - - // For each joint - for (uint32 i=0; i < nbJoints; i++) { - - const Entity jointEntity = mSliderJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // Get the bodies entities - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; - - const Quaternion& orientationBody1 = mTransformComponents.getTransform(body1Entity).getOrientation(); - const Quaternion& orientationBody2 = mTransformComponents.getTransform(body2Entity).getOrientation(); // Compute the bias "b" of the rotation constraint mSliderJointComponents.mBiasRotation[i].setToZero(); @@ -278,8 +210,8 @@ void SolveSliderJointSystem::initBeforeSolve() { // If the motor is enabled if (mSliderJointComponents.mIsMotorEnabled[i]) { - const decimal body1MassInverse = mRigidBodyComponents.getMassInverse(body1Entity); - const decimal body2MassInverse = mRigidBodyComponents.getMassInverse(body2Entity); + const decimal body1MassInverse = mRigidBodyComponents.mInverseMasses[componentIndexBody1]; + const decimal body2MassInverse = mRigidBodyComponents.mInverseMasses[componentIndexBody2]; const decimal sumInverseMass = body1MassInverse + body2MassInverse; // Compute the inverse of mass matrix K=JM^-1J^t for the motor (1x1 matrix) @@ -287,13 +219,9 @@ void SolveSliderJointSystem::initBeforeSolve() { mSliderJointComponents.mInverseMassMatrixMotor[i] = (mSliderJointComponents.mInverseMassMatrixMotor[i] > decimal(0.0)) ? decimal(1.0) / mSliderJointComponents.mInverseMassMatrixMotor[i] : decimal(0.0); } - } - // If warm-starting is not enabled - if (!mIsWarmStartingActive) { - - // For each joint - for (uint32 i=0; i < nbJoints; i++) { + // If warm-starting is not enabled + if (!mIsWarmStartingActive) { // Reset all the accumulated impulses mSliderJointComponents.mImpulseTranslation[i].setToZero(); @@ -452,26 +380,9 @@ void SolveSliderJointSystem::solveVelocityConstraint() { // Apply the impulse to the body 2 v2 += inverseMassBody2 * linearImpulseBody2; w2 += i2 * angularImpulseBody2; - } - - // For each joint component - for (uint32 i=0; i < nbJoints; i++) { - - const Entity jointEntity = mSliderJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // Get the bodies entities - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; - - const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); - const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); // --------------- Rotation Constraints --------------- // - Vector3& w1 = mRigidBodyComponents.mConstrainedAngularVelocities[componentIndexBody1]; - Vector3& w2 = mRigidBodyComponents.mConstrainedAngularVelocities[componentIndexBody2]; - // Compute J*v for the 3 rotation constraints const Vector3 JvRotation = w2 - w1; @@ -481,36 +392,16 @@ void SolveSliderJointSystem::solveVelocityConstraint() { mSliderJointComponents.mImpulseRotation[i] += deltaLambda2; // Compute the impulse P=J^T * lambda for the 3 rotation constraints of body 1 - Vector3 angularImpulseBody1 = -deltaLambda2; + angularImpulseBody1 = -deltaLambda2; - // Apply the impulse to the body to body 1 + // Apply the impulse to the body 1 w1 += mSliderJointComponents.mI1[i] * angularImpulseBody1; // Compute the impulse P=J^T * lambda for the 3 rotation constraints of body 2 - Vector3 angularImpulseBody2 = deltaLambda2; + angularImpulseBody2 = deltaLambda2; // Apply the impulse to the body 2 w2 += mSliderJointComponents.mI2[i] * angularImpulseBody2; - } - - // For each joint component - for (uint32 i=0; i < nbJoints; i++) { - - const Entity jointEntity = mSliderJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // Get the bodies entities - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; - - const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); - const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); - - Vector3& v1 = mRigidBodyComponents.mConstrainedLinearVelocities[componentIndexBody1]; - Vector3& v2 = mRigidBodyComponents.mConstrainedLinearVelocities[componentIndexBody2]; - - decimal inverseMassBody1 = mRigidBodyComponents.mInverseMasses[componentIndexBody1]; - decimal inverseMassBody2 = mRigidBodyComponents.mInverseMasses[componentIndexBody2]; const Vector3& r2CrossSliderAxis = mSliderJointComponents.mR2CrossSliderAxis[i]; const Vector3& r1PlusUCrossSliderAxis = mSliderJointComponents.mR1PlusUCrossSliderAxis[i]; @@ -634,54 +525,24 @@ void SolveSliderJointSystem::solvePositionConstraint() { const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; + const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); + const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); + // Recompute the world inverse inertia tensors const Matrix3x3 orientation1 = mTransformComponents.getTransform(body1Entity).getOrientation().getMatrix(); - RigidBody::computeWorldInertiaTensorInverse(orientation1, mRigidBodyComponents.getInertiaTensorLocalInverse(body1Entity), + RigidBody::computeWorldInertiaTensorInverse(orientation1, mRigidBodyComponents.mInverseInertiaTensorsLocal[componentIndexBody1], mSliderJointComponents.mI1[i]); const Matrix3x3 orientation2 = mTransformComponents.getTransform(body2Entity).getOrientation().getMatrix(); - RigidBody::computeWorldInertiaTensorInverse(orientation2, mRigidBodyComponents.getInertiaTensorLocalInverse(body2Entity), + RigidBody::computeWorldInertiaTensorInverse(orientation2, mRigidBodyComponents.mInverseInertiaTensorsLocal[componentIndexBody2], mSliderJointComponents.mI2[i]); - } - // For each joint component - for (uint32 i=0; i < nbEnabledJoints; i++) { - - const Entity jointEntity = mSliderJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // If the error position correction technique is not the non-linear-gauss-seidel, we do - // do not execute this method - if (mJointComponents.mPositionCorrectionTechniques[jointIndex] != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) return; - - // Get the bodies entities - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; - - const Quaternion& q1 = mRigidBodyComponents.getConstrainedOrientation(body1Entity); - const Quaternion& q2 = mRigidBodyComponents.getConstrainedOrientation(body2Entity); + Quaternion& q1 = mRigidBodyComponents.mConstrainedOrientations[componentIndexBody1]; + Quaternion& q2 = mRigidBodyComponents.mConstrainedOrientations[componentIndexBody2]; // Vector from body center to the anchor point mSliderJointComponents.mR1[i] = q1 * mSliderJointComponents.mLocalAnchorPointBody1[i]; mSliderJointComponents.mR2[i] = q2 * mSliderJointComponents.mLocalAnchorPointBody2[i]; - } - - // For each joint component - for (uint32 i=0; i < nbEnabledJoints; i++) { - - const Entity jointEntity = mSliderJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // If the error position correction technique is not the non-linear-gauss-seidel, we do - // do not execute this method - if (mJointComponents.mPositionCorrectionTechniques[jointIndex] != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) return; - - // Get the bodies entities - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; - - const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); - const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); // Get the inverse mass and inverse inertia tensors of the bodies const decimal inverseMassBody1 = mRigidBodyComponents.mInverseMasses[componentIndexBody1]; @@ -699,9 +560,6 @@ void SolveSliderJointSystem::solvePositionConstraint() { // Compute the vector u (difference between anchor points) const Vector3 u = x2 + r2 - x1 - r1; - Quaternion& q1 = mRigidBodyComponents.mConstrainedOrientations[componentIndexBody1]; - Quaternion& q2 = mRigidBodyComponents.mConstrainedOrientations[componentIndexBody2]; - // Compute the two orthogonal vectors to the slider axis in world-space mSliderJointComponents.mSliderAxisWorld[i] = q1 * mSliderJointComponents.mSliderAxisBody1[i]; mSliderJointComponents.mSliderAxisWorld[i].normalize(); @@ -790,31 +648,6 @@ void SolveSliderJointSystem::solvePositionConstraint() { x2 += v2; q2 += Quaternion(0, w2) * q2 * decimal(0.5); q2.normalize(); - } - - // For each joint component - for (uint32 i=0; i < nbEnabledJoints; i++) { - - const Entity jointEntity = mSliderJointComponents.mJointEntities[i]; - const uint32 jointIndex = mJointComponents.getEntityIndex(jointEntity); - - // If the error position correction technique is not the non-linear-gauss-seidel, we do - // do not execute this method - if (mJointComponents.mPositionCorrectionTechniques[jointIndex] != JointsPositionCorrectionTechnique::NON_LINEAR_GAUSS_SEIDEL) return; - - // Get the bodies entities - const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; - const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; - - const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); - const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); - - Quaternion& q1 = mRigidBodyComponents.mConstrainedOrientations[componentIndexBody1]; - Quaternion& q2 = mRigidBodyComponents.mConstrainedOrientations[componentIndexBody2]; - - // Get the velocities - Vector3& w1 = mRigidBodyComponents.mConstrainedAngularVelocities[componentIndexBody1]; - Vector3& w2 = mRigidBodyComponents.mConstrainedAngularVelocities[componentIndexBody2]; // --------------- Rotation Constraints --------------- // @@ -858,7 +691,7 @@ void SolveSliderJointSystem::solvePositionConstraint() { Vector3 lambdaRotation = mSliderJointComponents.mInverseMassMatrixRotation[i] * (-errorRotation); // Compute the impulse P=J^T * lambda for the 3 rotation constraints of body 1 - Vector3 angularImpulseBody1 = -lambdaRotation; + angularImpulseBody1 = -lambdaRotation; // Apply the impulse to the body 1 w1 = mSliderJointComponents.mI1[i] * angularImpulseBody1; @@ -868,7 +701,7 @@ void SolveSliderJointSystem::solvePositionConstraint() { q1.normalize(); // Compute the impulse P=J^T * lambda for the 3 rotation constraints of body 2 - Vector3 angularImpulseBody2 = lambdaRotation; + angularImpulseBody2 = lambdaRotation; // Apply the impulse to the body 2 w2 = mSliderJointComponents.mI2[i] * angularImpulseBody2; From 79c42fa36e55408ee77be9ed37c13db109c76d08 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Wed, 14 Oct 2020 18:29:41 +0200 Subject: [PATCH 51/74] Change the default number of iterations of the velocity and position solver --- CHANGELOG.md | 2 ++ include/reactphysics3d/engine/PhysicsWorld.h | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcb86105..b0771ca3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ do not hesitate to take a look at the user manual. - Rolling resistance constraint is not solved anymore in the solver. Angular damping needs to be used instead to simulate this - The List class has been renamed to Array + - The default number of iterations for the velocity solver is now 6 instead of 10 + - The default number of iterations for the position solver is now 3 instead of 5 ### Removed diff --git a/include/reactphysics3d/engine/PhysicsWorld.h b/include/reactphysics3d/engine/PhysicsWorld.h index fa603517..bafe96c7 100644 --- a/include/reactphysics3d/engine/PhysicsWorld.h +++ b/include/reactphysics3d/engine/PhysicsWorld.h @@ -127,8 +127,8 @@ class PhysicsWorld { defaultBounciness = decimal(0.5); restitutionVelocityThreshold = decimal(0.5); isSleepingEnabled = true; - defaultVelocitySolverNbIterations = 10; - defaultPositionSolverNbIterations = 5; + defaultVelocitySolverNbIterations = 6; + defaultPositionSolverNbIterations = 3; defaultTimeBeforeSleep = 1.0f; defaultSleepLinearVelocity = decimal(0.02); defaultSleepAngularVelocity = decimal(3.0) * (PI_RP3D / decimal(180.0)); From 9be56213e05bbbc545107373259ebe9615265bc0 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Wed, 14 Oct 2020 18:30:21 +0200 Subject: [PATCH 52/74] Optimization in NarrowPhaseInfoBatch --- src/collision/narrowphase/NarrowPhaseInfoBatch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/collision/narrowphase/NarrowPhaseInfoBatch.cpp b/src/collision/narrowphase/NarrowPhaseInfoBatch.cpp index ea08cf5b..9109dd64 100644 --- a/src/collision/narrowphase/NarrowPhaseInfoBatch.cpp +++ b/src/collision/narrowphase/NarrowPhaseInfoBatch.cpp @@ -76,7 +76,7 @@ void NarrowPhaseInfoBatch::clear() { // allocated in the next frame at a possibly different location in memory (remember that the // location of the allocated memory of a single frame allocator might change between two frames) - mCachedCapacity = narrowPhaseInfos.size(); + mCachedCapacity = narrowPhaseInfos.capacity(); narrowPhaseInfos.clear(true); } From 8f5a7a12cb5b9bfa28697324e43f5f333fbabd4f Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Wed, 14 Oct 2020 19:42:40 +0200 Subject: [PATCH 53/74] Fix issue in position solver of joints (constrained orientations must be used instead of previous orientation when computing inertia tensor) --- src/systems/SolveBallAndSocketJointSystem.cpp | 12 +++++------- src/systems/SolveFixedJointSystem.cpp | 16 +++++++--------- src/systems/SolveHingeJointSystem.cpp | 16 +++++++--------- src/systems/SolveSliderJointSystem.cpp | 16 +++++++--------- 4 files changed, 26 insertions(+), 34 deletions(-) diff --git a/src/systems/SolveBallAndSocketJointSystem.cpp b/src/systems/SolveBallAndSocketJointSystem.cpp index c41388c7..b4902b98 100644 --- a/src/systems/SolveBallAndSocketJointSystem.cpp +++ b/src/systems/SolveBallAndSocketJointSystem.cpp @@ -234,13 +234,14 @@ void SolveBallAndSocketJointSystem::solvePositionConstraint() { const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); + Quaternion& q1 = mRigidBodyComponents.mConstrainedOrientations[componentIndexBody1]; + Quaternion& q2 = mRigidBodyComponents.mConstrainedOrientations[componentIndexBody2]; + // Recompute the world inverse inertia tensors - const Matrix3x3 orientation1 = mTransformComponents.getTransform(body1Entity).getOrientation().getMatrix(); - RigidBody::computeWorldInertiaTensorInverse(orientation1, mRigidBodyComponents.mInverseInertiaTensorsLocal[componentIndexBody1], + RigidBody::computeWorldInertiaTensorInverse(q1.getMatrix(), mRigidBodyComponents.mInverseInertiaTensorsLocal[componentIndexBody1], mBallAndSocketJointComponents.mI1[i]); - const Matrix3x3 orientation2 = mTransformComponents.getTransform(body2Entity).getOrientation().getMatrix(); - RigidBody::computeWorldInertiaTensorInverse(orientation2, mRigidBodyComponents.mInverseInertiaTensorsLocal[componentIndexBody2], + RigidBody::computeWorldInertiaTensorInverse(q2.getMatrix(), mRigidBodyComponents.mInverseInertiaTensorsLocal[componentIndexBody2], mBallAndSocketJointComponents.mI2[i]); // Compute the vector from body center to the anchor point in world-space @@ -292,9 +293,6 @@ void SolveBallAndSocketJointSystem::solvePositionConstraint() { const Vector3 v1 = inverseMassBody1 * linearImpulseBody1; const Vector3 w1 = mBallAndSocketJointComponents.mI1[i] * angularImpulseBody1; - Quaternion& q1 = mRigidBodyComponents.mConstrainedOrientations[componentIndexBody1]; - Quaternion& q2 = mRigidBodyComponents.mConstrainedOrientations[componentIndexBody2]; - // Update the body center of mass and orientation of body 1 x1 += v1; q1 += Quaternion(0, w1) * q1 * decimal(0.5); diff --git a/src/systems/SolveFixedJointSystem.cpp b/src/systems/SolveFixedJointSystem.cpp index ced19853..6f31972a 100644 --- a/src/systems/SolveFixedJointSystem.cpp +++ b/src/systems/SolveFixedJointSystem.cpp @@ -297,15 +297,6 @@ void SolveFixedJointSystem::solvePositionConstraint() { const Entity body1Entity = mJointComponents.mBody1Entities[jointIndex]; const Entity body2Entity = mJointComponents.mBody2Entities[jointIndex]; - // Recompute the world inverse inertia tensors - const Matrix3x3 orientation1 = mTransformComponents.getTransform(body1Entity).getOrientation().getMatrix(); - RigidBody::computeWorldInertiaTensorInverse(orientation1, mRigidBodyComponents.getInertiaTensorLocalInverse(body1Entity), - mFixedJointComponents.mI1[i]); - - const Matrix3x3 orientation2 = mTransformComponents.getTransform(body2Entity).getOrientation().getMatrix(); - RigidBody::computeWorldInertiaTensorInverse(orientation2, mRigidBodyComponents.getInertiaTensorLocalInverse(body2Entity), - mFixedJointComponents.mI2[i]); - const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); @@ -313,6 +304,13 @@ void SolveFixedJointSystem::solvePositionConstraint() { Quaternion& q1 = mRigidBodyComponents.mConstrainedOrientations[componentIndexBody1]; Quaternion& q2 = mRigidBodyComponents.mConstrainedOrientations[componentIndexBody2]; + // Recompute the world inverse inertia tensors + RigidBody::computeWorldInertiaTensorInverse(q1.getMatrix(), mRigidBodyComponents.getInertiaTensorLocalInverse(body1Entity), + mFixedJointComponents.mI1[i]); + + RigidBody::computeWorldInertiaTensorInverse(q2.getMatrix(), mRigidBodyComponents.getInertiaTensorLocalInverse(body2Entity), + mFixedJointComponents.mI2[i]); + // Compute the vector from body center to the anchor point in world-space mFixedJointComponents.mR1World[i] = q1 * mFixedJointComponents.mLocalAnchorPointBody1[i]; mFixedJointComponents.mR2World[i] = q2 * mFixedJointComponents.mLocalAnchorPointBody2[i]; diff --git a/src/systems/SolveHingeJointSystem.cpp b/src/systems/SolveHingeJointSystem.cpp index 8279664e..738b0192 100644 --- a/src/systems/SolveHingeJointSystem.cpp +++ b/src/systems/SolveHingeJointSystem.cpp @@ -480,18 +480,16 @@ void SolveHingeJointSystem::solvePositionConstraint() { const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); - // Recompute the world inverse inertia tensors - const Matrix3x3 orientation1 = mTransformComponents.getTransform(body1Entity).getOrientation().getMatrix(); - RigidBody::computeWorldInertiaTensorInverse(orientation1, mRigidBodyComponents.mInverseInertiaTensorsLocal[componentIndexBody1], - mHingeJointComponents.mI1[i]); - - const Matrix3x3 orientation2 = mTransformComponents.getTransform(body2Entity).getOrientation().getMatrix(); - RigidBody::computeWorldInertiaTensorInverse(orientation2, mRigidBodyComponents.mInverseInertiaTensorsLocal[componentIndexBody2], - mHingeJointComponents.mI2[i]); - Quaternion& q1 = mRigidBodyComponents.mConstrainedOrientations[componentIndexBody1]; Quaternion& q2 = mRigidBodyComponents.mConstrainedOrientations[componentIndexBody2]; + // Recompute the world inverse inertia tensors + RigidBody::computeWorldInertiaTensorInverse(q1.getMatrix(), mRigidBodyComponents.mInverseInertiaTensorsLocal[componentIndexBody1], + mHingeJointComponents.mI1[i]); + + RigidBody::computeWorldInertiaTensorInverse(q2.getMatrix(), mRigidBodyComponents.mInverseInertiaTensorsLocal[componentIndexBody2], + mHingeJointComponents.mI2[i]); + // Compute the vector from body center to the anchor point in world-space mHingeJointComponents.mR1World[i] = q1 * mHingeJointComponents.mLocalAnchorPointBody1[i]; mHingeJointComponents.mR2World[i] = q2 * mHingeJointComponents.mLocalAnchorPointBody2[i]; diff --git a/src/systems/SolveSliderJointSystem.cpp b/src/systems/SolveSliderJointSystem.cpp index ee47357f..4a6874e5 100644 --- a/src/systems/SolveSliderJointSystem.cpp +++ b/src/systems/SolveSliderJointSystem.cpp @@ -528,18 +528,16 @@ void SolveSliderJointSystem::solvePositionConstraint() { const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); - // Recompute the world inverse inertia tensors - const Matrix3x3 orientation1 = mTransformComponents.getTransform(body1Entity).getOrientation().getMatrix(); - RigidBody::computeWorldInertiaTensorInverse(orientation1, mRigidBodyComponents.mInverseInertiaTensorsLocal[componentIndexBody1], - mSliderJointComponents.mI1[i]); - - const Matrix3x3 orientation2 = mTransformComponents.getTransform(body2Entity).getOrientation().getMatrix(); - RigidBody::computeWorldInertiaTensorInverse(orientation2, mRigidBodyComponents.mInverseInertiaTensorsLocal[componentIndexBody2], - mSliderJointComponents.mI2[i]); - Quaternion& q1 = mRigidBodyComponents.mConstrainedOrientations[componentIndexBody1]; Quaternion& q2 = mRigidBodyComponents.mConstrainedOrientations[componentIndexBody2]; + // Recompute the world inverse inertia tensors + RigidBody::computeWorldInertiaTensorInverse(q1.getMatrix(), mRigidBodyComponents.mInverseInertiaTensorsLocal[componentIndexBody1], + mSliderJointComponents.mI1[i]); + + RigidBody::computeWorldInertiaTensorInverse(q2.getMatrix(), mRigidBodyComponents.mInverseInertiaTensorsLocal[componentIndexBody2], + mSliderJointComponents.mI2[i]); + // Vector from body center to the anchor point mSliderJointComponents.mR1[i] = q1 * mSliderJointComponents.mLocalAnchorPointBody1[i]; mSliderJointComponents.mR2[i] = q2 * mSliderJointComponents.mLocalAnchorPointBody2[i]; From 8c7a709ebeae5ea62460ece7d4fe9436f0701b4a Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Wed, 14 Oct 2020 20:40:41 +0200 Subject: [PATCH 54/74] Fix compilation issues --- src/systems/SolveHingeJointSystem.cpp | 4 ++-- src/systems/SolveSliderJointSystem.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/systems/SolveHingeJointSystem.cpp b/src/systems/SolveHingeJointSystem.cpp index 738b0192..5a061f3f 100644 --- a/src/systems/SolveHingeJointSystem.cpp +++ b/src/systems/SolveHingeJointSystem.cpp @@ -63,8 +63,8 @@ void SolveHingeJointSystem::initBeforeSolve() { const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); - assert(!mRigidBodyComponents.mIsEntityDisabled[componentIndexBody1]); - assert(!mRigidBodyComponents.mIsEntityDisabled[componentIndexBody2]); + assert(!mRigidBodyComponents.getIsEntityDisabled(body1Entity)); + assert(!mRigidBodyComponents.getIsEntityDisabled(body2Entity)); // Get the inertia tensor of bodies mHingeJointComponents.mI1[i] = mRigidBodyComponents.mInverseInertiaTensorsWorld[componentIndexBody1]; diff --git a/src/systems/SolveSliderJointSystem.cpp b/src/systems/SolveSliderJointSystem.cpp index 4a6874e5..851be91f 100644 --- a/src/systems/SolveSliderJointSystem.cpp +++ b/src/systems/SolveSliderJointSystem.cpp @@ -63,8 +63,8 @@ void SolveSliderJointSystem::initBeforeSolve() { const uint32 componentIndexBody1 = mRigidBodyComponents.getEntityIndex(body1Entity); const uint32 componentIndexBody2 = mRigidBodyComponents.getEntityIndex(body2Entity); - assert(!mRigidBodyComponents.mIsEntityDisabled[componentIndexBody1]); - assert(!mRigidBodyComponents.mIsEntityDisabled[componentIndexBody2]); + assert(!mRigidBodyComponents.getIsEntityDisabled(body1Entity)); + assert(!mRigidBodyComponents.getIsEntityDisabled(body2Entity)); // Get the inertia tensor of bodies mSliderJointComponents.mI1[i] = mRigidBodyComponents.mInverseInertiaTensorsWorld[componentIndexBody1]; From fcf7def57732185bab08487f6de882f838b27ba3 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Wed, 14 Oct 2020 20:41:05 +0200 Subject: [PATCH 55/74] Small optimizations --- src/engine/PhysicsWorld.cpp | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/engine/PhysicsWorld.cpp b/src/engine/PhysicsWorld.cpp index 2dfa9896..19a4bca3 100644 --- a/src/engine/PhysicsWorld.cpp +++ b/src/engine/PhysicsWorld.cpp @@ -766,11 +766,12 @@ void PhysicsWorld::createIslands() { assert(mProcessContactPairsOrderIslands.size() == 0); // Reset all the isAlreadyInIsland variables of bodies and joints - for (uint b=0; b < mRigidBodyComponents.getNbComponents(); b++) { - + const uint32 nbRigidBodyComponents = mRigidBodyComponents.getNbComponents(); + for (uint b=0; b < nbRigidBodyComponents; b++) { mRigidBodyComponents.mIsAlreadyInIsland[b] = false; } - for (uint32 i=0; i < mJointsComponents.getNbComponents(); i++) { + const uint32 nbJointsComponents = mJointsComponents.getNbComponents(); + for (uint32 i=0; i < nbJointsComponents; i++) { mJointsComponents.mIsAlreadyInIsland[i] = false; } @@ -876,14 +877,16 @@ void PhysicsWorld::createIslands() { const uint32 nbBodyJoints = joints.size(); for (uint32 i=0; i < nbBodyJoints; i++) { + const uint32 jointComponentIndex = mJointsComponents.getEntityIndex(joints[i]); + // Check if the current joint has already been added into an island - if (mJointsComponents.getIsAlreadyInIsland(joints[i])) continue; + if (mJointsComponents.mIsAlreadyInIsland[jointComponentIndex]) continue; // Add the joint into the island - mJointsComponents.setIsAlreadyInIsland(joints[i], true); + mJointsComponents.mIsAlreadyInIsland[jointComponentIndex] = true; - const Entity body1Entity = mJointsComponents.getBody1Entity(joints[i]); - const Entity body2Entity = mJointsComponents.getBody2Entity(joints[i]); + const Entity body1Entity = mJointsComponents.mBody1Entities[jointComponentIndex]; + const Entity body2Entity = mJointsComponents.mBody2Entities[jointComponentIndex]; const Entity otherBodyEntity = body1Entity == bodyToVisitEntity ? body2Entity : body1Entity; const uint32 otherBodyIndex = mRigidBodyComponents.getEntityIndex(otherBodyEntity); @@ -910,7 +913,8 @@ void PhysicsWorld::createIslands() { } // Clear the associated contacts pairs of rigid bodies - for (uint b=0; b < mRigidBodyComponents.getNbEnabledComponents(); b++) { + const uint32 nbRigidBodyEnabledComponents = mRigidBodyComponents.getNbEnabledComponents(); + for (uint b=0; b < nbRigidBodyEnabledComponents; b++) { mRigidBodyComponents.mContactPairs[b].clear(); } } @@ -926,7 +930,8 @@ void PhysicsWorld::updateSleepingBodies(decimal timeStep) { const decimal sleepAngularVelocitySquare = mSleepAngularVelocity * mSleepAngularVelocity; // For each island of the world - for (uint i=0; i Date: Thu, 15 Oct 2020 22:23:32 +0200 Subject: [PATCH 56/74] Optimizations --- src/systems/DynamicsSystem.cpp | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/systems/DynamicsSystem.cpp b/src/systems/DynamicsSystem.cpp index b30506d6..409c9dac 100644 --- a/src/systems/DynamicsSystem.cpp +++ b/src/systems/DynamicsSystem.cpp @@ -47,7 +47,8 @@ void DynamicsSystem::integrateRigidBodiesPositions(decimal timeStep, bool isSpli const decimal isSplitImpulseFactor = isSplitImpulseActive ? decimal(1.0) : decimal(0.0); - for (uint32 i=0; i < mRigidBodyComponents.getNbEnabledComponents(); i++) { + const uint32 nbRigidBodyComponents = mRigidBodyComponents.getNbEnabledComponents(); + for (uint32 i=0; i < nbRigidBodyComponents; i++) { // Get the constrained velocity Vector3 newLinVelocity = mRigidBodyComponents.mConstrainedLinearVelocities[i]; @@ -74,7 +75,8 @@ void DynamicsSystem::updateBodiesState() { RP3D_PROFILE("DynamicsSystem::updateBodiesState()", mProfiler); - for (uint32 i=0; i < mRigidBodyComponents.getNbEnabledComponents(); i++) { + const uint32 nbRigidBodyComponents = mRigidBodyComponents.getNbEnabledComponents(); + for (uint32 i=0; i < nbRigidBodyComponents; i++) { // Update the linear and angular velocity of the body mRigidBodyComponents.mLinearVelocities[i] = mRigidBodyComponents.mConstrainedLinearVelocities[i]; @@ -89,7 +91,7 @@ void DynamicsSystem::updateBodiesState() { } // Update the position of the body (using the new center of mass and new orientation) - for (uint32 i=0; i < mRigidBodyComponents.getNbEnabledComponents(); i++) { + for (uint32 i=0; i < nbRigidBodyComponents; i++) { Transform& transform = mTransformComponents.getTransform(mRigidBodyComponents.mBodiesEntities[i]); const Vector3& centerOfMassWorld = mRigidBodyComponents.mCentersOfMassWorld[i]; @@ -98,7 +100,8 @@ void DynamicsSystem::updateBodiesState() { } // Update the local-to-world transform of the colliders - for (uint32 i=0; i < mColliderComponents.getNbEnabledComponents(); i++) { + const uint32 nbColliderComponents = mColliderComponents.getNbEnabledComponents(); + for (uint32 i=0; i < nbColliderComponents; i++) { // Update the local-to-world transform of the collider mColliderComponents.mLocalToWorldTransforms[i] = mTransformComponents.getTransform(mColliderComponents.mBodiesEntities[i]) * @@ -119,7 +122,8 @@ void DynamicsSystem::integrateRigidBodiesVelocities(decimal timeStep) { resetSplitVelocities(); // Integration component velocities using force/torque - for (uint32 i=0; i < mRigidBodyComponents.getNbEnabledComponents(); i++) { + const uint32 nbEnabledRigidBodyComponents = mRigidBodyComponents.getNbEnabledComponents(); + for (uint32 i=0; i < nbEnabledRigidBodyComponents; i++) { assert(mRigidBodyComponents.mSplitLinearVelocities[i] == Vector3(0, 0, 0)); assert(mRigidBodyComponents.mSplitAngularVelocities[i] == Vector3(0, 0, 0)); @@ -137,7 +141,8 @@ void DynamicsSystem::integrateRigidBodiesVelocities(decimal timeStep) { // Apply gravity force if (mIsGravityEnabled) { - for (uint32 i=0; i < mRigidBodyComponents.getNbEnabledComponents(); i++) { + const uint32 nbRigidBodyComponents = mRigidBodyComponents.getNbEnabledComponents(); + for (uint32 i=0; i < nbRigidBodyComponents; i++) { // If the gravity has to be applied to this rigid body if (mRigidBodyComponents.mIsGravityEnabled[i]) { @@ -162,7 +167,8 @@ void DynamicsSystem::integrateRigidBodiesVelocities(decimal timeStep) { // Using Taylor Serie for e^(-x) : e^x ~ 1 + x + x^2/2! + ... // => e^(-x) ~ 1 - x // => v2 = v1 * (1 - c * dt) - for (uint32 i=0; i < mRigidBodyComponents.getNbEnabledComponents(); i++) { + const uint32 nbRigidBodyComponents = mRigidBodyComponents.getNbEnabledComponents(); + for (uint32 i=0; i < nbRigidBodyComponents; i++) { const decimal linDampingFactor = mRigidBodyComponents.mLinearDampings[i]; const decimal angDampingFactor = mRigidBodyComponents.mAngularDampings[i]; @@ -177,7 +183,8 @@ void DynamicsSystem::integrateRigidBodiesVelocities(decimal timeStep) { void DynamicsSystem::resetBodiesForceAndTorque() { // For each body of the world - for (uint32 i=0; i < mRigidBodyComponents.getNbComponents(); i++) { + const uint32 nbRigidBodyComponents = mRigidBodyComponents.getNbComponents(); + for (uint32 i=0; i < nbRigidBodyComponents; i++) { mRigidBodyComponents.mExternalForces[i].setToZero(); mRigidBodyComponents.mExternalTorques[i].setToZero(); } @@ -186,7 +193,8 @@ void DynamicsSystem::resetBodiesForceAndTorque() { // Reset the split velocities of the bodies void DynamicsSystem::resetSplitVelocities() { - for(uint32 i=0; i < mRigidBodyComponents.getNbEnabledComponents(); i++) { + const uint32 nbRigidBodyComponents = mRigidBodyComponents.getNbEnabledComponents(); + for(uint32 i=0; i < nbRigidBodyComponents; i++) { mRigidBodyComponents.mSplitLinearVelocities[i].setToZero(); mRigidBodyComponents.mSplitAngularVelocities[i].setToZero(); } From 86b431ede0fc18b645232712eb655abf3a2a01b6 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Fri, 16 Oct 2020 00:00:55 +0200 Subject: [PATCH 57/74] Edit ball and socket joints net scene in testbed application --- .../ballandsocketjointsnet/BallAndSocketJointsNetScene.cpp | 4 ++-- .../ballandsocketjointsnet/BallAndSocketJointsNetScene.h | 2 +- testbed/src/TestbedApplication.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/testbed/scenes/ballandsocketjointsnet/BallAndSocketJointsNetScene.cpp b/testbed/scenes/ballandsocketjointsnet/BallAndSocketJointsNetScene.cpp index b0e9f038..af38b7e5 100644 --- a/testbed/scenes/ballandsocketjointsnet/BallAndSocketJointsNetScene.cpp +++ b/testbed/scenes/ballandsocketjointsnet/BallAndSocketJointsNetScene.cpp @@ -90,7 +90,7 @@ BallAndSocketJointsNetScene::BallAndSocketJointsNetScene(const std::string& name createJoints(); // Create the main sphere - mMainSphere = new Sphere(true, 10, mPhysicsCommon, mPhysicsWorld, meshFolderPath); + mMainSphere = new Sphere(true, 7, mPhysicsCommon, mPhysicsWorld, meshFolderPath); mMainSphere->setColor(mObjectColorDemo); mMainSphere->setSleepingColor(mSleepingColorDemo); rp3d::Vector3 initPosition(0, 0, 0); @@ -181,7 +181,7 @@ void BallAndSocketJointsNetScene::reset() { SceneDemo::reset(); - const float space = 0.3f; + const float space = 0.5f; const float startX = -(NB_ROWS_NET_SPHERES / 2.0f * (2.0 * SPHERE_RADIUS + space)); const float startZ = -(NB_ROWS_NET_SPHERES / 2.0f * (2.0 * SPHERE_RADIUS + space)); diff --git a/testbed/scenes/ballandsocketjointsnet/BallAndSocketJointsNetScene.h b/testbed/scenes/ballandsocketjointsnet/BallAndSocketJointsNetScene.h index 5f9cc7e4..4e9e4826 100644 --- a/testbed/scenes/ballandsocketjointsnet/BallAndSocketJointsNetScene.h +++ b/testbed/scenes/ballandsocketjointsnet/BallAndSocketJointsNetScene.h @@ -37,7 +37,7 @@ namespace ballandsocketjointsnetscene { // Constants const float SCENE_RADIUS = 40.0f; const float SPHERE_RADIUS = 0.5f; -const int NB_ROWS_NET_SPHERES = 40; +const int NB_ROWS_NET_SPHERES = 20; // Class JointsScene class BallAndSocketJointsNetScene : public SceneDemo { diff --git a/testbed/src/TestbedApplication.cpp b/testbed/src/TestbedApplication.cpp index f3888924..78e5a812 100755 --- a/testbed/src/TestbedApplication.cpp +++ b/testbed/src/TestbedApplication.cpp @@ -159,7 +159,7 @@ void TestbedApplication::createScenes() { mScenes.push_back(ballAndSocketJointsNetScene); assert(mScenes.size() > 0); - const int firstSceneIndex = 10; + const int firstSceneIndex = 0; switchScene(mScenes[firstSceneIndex]); } From 152c239a2aab98bb00c8f340e39ffbb643720f01 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Sat, 17 Oct 2020 18:00:23 +0200 Subject: [PATCH 58/74] Add ball-and-socket joints chain and hinge joints chain demos in testbed application --- testbed/CMakeLists.txt | 4 + .../BallAndSocketJointsChainScene.cpp | 159 ++++++++++++++++++ .../BallAndSocketJointsChainScene.h | 76 +++++++++ .../HingeJointsChainScene.cpp | 159 ++++++++++++++++++ .../hingejointschain/HingeJointsChainScene.h | 76 +++++++++ testbed/src/TestbedApplication.cpp | 15 +- 6 files changed, 487 insertions(+), 2 deletions(-) create mode 100644 testbed/scenes/ballandsocketjointschain/BallAndSocketJointsChainScene.cpp create mode 100644 testbed/scenes/ballandsocketjointschain/BallAndSocketJointsChainScene.h create mode 100644 testbed/scenes/hingejointschain/HingeJointsChainScene.cpp create mode 100644 testbed/scenes/hingejointschain/HingeJointsChainScene.h diff --git a/testbed/CMakeLists.txt b/testbed/CMakeLists.txt index db74010a..ea09cef7 100755 --- a/testbed/CMakeLists.txt +++ b/testbed/CMakeLists.txt @@ -125,6 +125,10 @@ set(COMMON_SOURCES set(SCENES_SOURCES scenes/ballandsocketjointsnet/BallAndSocketJointsNetScene.h scenes/ballandsocketjointsnet/BallAndSocketJointsNetScene.cpp + scenes/ballandsocketjointschain/BallAndSocketJointsChainScene.h + scenes/ballandsocketjointschain/BallAndSocketJointsChainScene.cpp + scenes/hingejointschain/HingeJointsChainScene.h + scenes/hingejointschain/HingeJointsChainScene.cpp scenes/cubes/CubesScene.h scenes/cubes/CubesScene.cpp scenes/joints/JointsScene.h diff --git a/testbed/scenes/ballandsocketjointschain/BallAndSocketJointsChainScene.cpp b/testbed/scenes/ballandsocketjointschain/BallAndSocketJointsChainScene.cpp new file mode 100644 index 00000000..8653d1c2 --- /dev/null +++ b/testbed/scenes/ballandsocketjointschain/BallAndSocketJointsChainScene.cpp @@ -0,0 +1,159 @@ +/******************************************************************************** +* ReactPhysics3D physics library, http://www.reactphysics3d.com * +* Copyright (c) 2010-2016 Daniel Chappuis * +********************************************************************************* +* * +* This software is provided 'as-is', without any express or implied warranty. * +* In no event will the authors be held liable for any damages arising from the * +* use of this software. * +* * +* Permission is granted to anyone to use this software for any purpose, * +* including commercial applications, and to alter it and redistribute it * +* freely, subject to the following restrictions: * +* * +* 1. The origin of this software must not be misrepresented; you must not claim * +* that you wrote the original software. If you use this software in a * +* product, an acknowledgment in the product documentation would be * +* appreciated but is not required. * +* * +* 2. Altered source versions must be plainly marked as such, and must not be * +* misrepresented as being the original software. * +* * +* 3. This notice may not be removed or altered from any source distribution. * +* * +********************************************************************************/ + +// Libraries +#include "BallAndSocketJointsChainScene.h" +#include + +// Namespaces +using namespace openglframework; +using namespace ballandsocketjointschainscene; + +// Constructor +BallAndSocketJointsChainScene::BallAndSocketJointsChainScene(const std::string& name, EngineSettings& settings) + : SceneDemo(name, settings, true, SCENE_RADIUS) { + + std::string meshFolderPath("meshes/"); + + // Compute the radius and the center of the scene + openglframework::Vector3 center(0, 0, 0); + + // Set the center of the scene + setScenePosition(center, SCENE_RADIUS); + + // Gravity vector in the physics world + rp3d::Vector3 gravity(0, rp3d::decimal(-9.81), 0); + + rp3d::PhysicsWorld::WorldSettings worldSettings; + worldSettings.worldName = name; + + // Logger + rp3d::DefaultLogger* defaultLogger = mPhysicsCommon.createDefaultLogger(); + uint logLevel = static_cast(rp3d::Logger::Level::Information) | static_cast(rp3d::Logger::Level::Warning) | + static_cast(rp3d::Logger::Level::Error); + defaultLogger->addFileDestination("rp3d_log_" + name + ".html", logLevel, rp3d::DefaultLogger::Format::HTML); + mPhysicsCommon.setLogger(defaultLogger); + + // Create the physics world for the physics simulation + rp3d::PhysicsWorld* physicsWorld = mPhysicsCommon.createPhysicsWorld(worldSettings); + physicsWorld->setEventListener(this); + mPhysicsWorld = physicsWorld; + + // Create all the spheres of the scene + for (int i=0; isetColor(mObjectColorDemo); + mSpheres[i]->setSleepingColor(mSleepingColorDemo); + + // Change the material properties of the rigid body + rp3d::Material& material = mSpheres[i]->getCollider()->getMaterial(); + material.setBounciness(rp3d::decimal(0.0)); + + if (i == 0) { + mSpheres[i]->getRigidBody()->setType(rp3d::BodyType::STATIC); + } + + // Add the sphere the list of sphere in the scene + mPhysicsObjects.push_back(mSpheres[i]); + } + + // Set the position of the spheres before the joints creation + reset(); + + // Create the Ball-and-Socket joints + createJoints(); + + // Get the physics engine parameters + mEngineSettings.isGravityEnabled = mPhysicsWorld->isGravityEnabled(); + rp3d::Vector3 gravityVector = mPhysicsWorld->getGravity(); + mEngineSettings.gravity = openglframework::Vector3(gravityVector.x, gravityVector.y, gravityVector.z); + mEngineSettings.isSleepingEnabled = mPhysicsWorld->isSleepingEnabled(); + mEngineSettings.sleepLinearVelocity = mPhysicsWorld->getSleepLinearVelocity(); + mEngineSettings.sleepAngularVelocity = mPhysicsWorld->getSleepAngularVelocity(); + mEngineSettings.nbPositionSolverIterations = mPhysicsWorld->getNbIterationsPositionSolver(); + mEngineSettings.nbVelocitySolverIterations = mPhysicsWorld->getNbIterationsVelocitySolver(); + mEngineSettings.timeBeforeSleep = mPhysicsWorld->getTimeBeforeSleep(); +} + +// Destructor +BallAndSocketJointsChainScene::~BallAndSocketJointsChainScene() { + + // Destroy the joints + for (uint i=0; i < mBallAndSocketJoints.size(); i++) { + + mPhysicsWorld->destroyJoint(mBallAndSocketJoints[i]); + } + + // Destroy all the rigid bodies of the scene + for (int i=0; idestroyRigidBody(mSpheres[i]->getRigidBody()); + } + + // Destroy the physics world + mPhysicsCommon.destroyPhysicsWorld(mPhysicsWorld); +} + +// Create the joints +void BallAndSocketJointsChainScene::createJoints() { + + for (int i=0; i < NB_SPHERES-1; i++) { + + // Create the joint info object + rp3d::RigidBody* body1 = mSpheres[i]->getRigidBody(); + rp3d::RigidBody* body2 = mSpheres[i+1]->getRigidBody(); + rp3d::Vector3 body1Position = body1->getTransform().getPosition(); + rp3d::Vector3 body2Position = body2->getTransform().getPosition(); + const rp3d::Vector3 anchorPointWorldSpace = body1Position; + rp3d::BallAndSocketJointInfo jointInfo(body1, body2, anchorPointWorldSpace); + jointInfo.isCollisionEnabled = false; + rp3d::BallAndSocketJoint* joint = dynamic_cast( mPhysicsWorld->createJoint(jointInfo)); + mBallAndSocketJoints.push_back(joint); + } +} + +// Reset the scene +void BallAndSocketJointsChainScene::reset() { + + SceneDemo::reset(); + + const float space = 0.5f; + + const rp3d::Quaternion initOrientation = rp3d::Quaternion::identity(); + + for (int i=0; isetTransform(transform); + } +} diff --git a/testbed/scenes/ballandsocketjointschain/BallAndSocketJointsChainScene.h b/testbed/scenes/ballandsocketjointschain/BallAndSocketJointsChainScene.h new file mode 100644 index 00000000..c3bbdebd --- /dev/null +++ b/testbed/scenes/ballandsocketjointschain/BallAndSocketJointsChainScene.h @@ -0,0 +1,76 @@ +/******************************************************************************** +* ReactPhysics3D physics library, http://www.reactphysics3d.com * +* Copyright (c) 2010-2016 Daniel Chappuis * +********************************************************************************* +* * +* This software is provided 'as-is', without any express or implied warranty. * +* In no event will the authors be held liable for any damages arising from the * +* use of this software. * +* * +* Permission is granted to anyone to use this software for any purpose, * +* including commercial applications, and to alter it and redistribute it * +* freely, subject to the following restrictions: * +* * +* 1. The origin of this software must not be misrepresented; you must not claim * +* that you wrote the original software. If you use this software in a * +* product, an acknowledgment in the product documentation would be * +* appreciated but is not required. * +* * +* 2. Altered source versions must be plainly marked as such, and must not be * +* misrepresented as being the original software. * +* * +* 3. This notice may not be removed or altered from any source distribution. * +* * +********************************************************************************/ + +#ifndef BALL_AND_SOCKET_JOINTS_CHAIN_SCENE_H +#define BALL_AND_SOCKET_JOINTS_CHAIN_SCENE_H + +// Libraries +#include "openglframework.h" +#include +#include "Sphere.h" +#include "SceneDemo.h" + +namespace ballandsocketjointschainscene { + +// Constants +const float SCENE_RADIUS = 60.0f; +const float SPHERE_RADIUS = 1.0f; +const int NB_SPHERES = 20; + +// Class BallAndSocketJointsChain scene +class BallAndSocketJointsChainScene : public SceneDemo { + + protected : + + // -------------------- Attributes -------------------- // + + /// Spheres in the Ball-And-Socket joint net + Sphere* mSpheres[NB_SPHERES]; + + /// Ball-And-Socket joints of the chain + std::vector mBallAndSocketJoints; + + // -------------------- Methods -------------------- // + + /// Create the joints + void createJoints(); + + public: + + // -------------------- Methods -------------------- // + + /// Constructor + BallAndSocketJointsChainScene(const std::string& name, EngineSettings& settings); + + /// Destructor + virtual ~BallAndSocketJointsChainScene() override ; + + /// Reset the scene + virtual void reset() override; +}; + +} + +#endif diff --git a/testbed/scenes/hingejointschain/HingeJointsChainScene.cpp b/testbed/scenes/hingejointschain/HingeJointsChainScene.cpp new file mode 100644 index 00000000..e6ebc47f --- /dev/null +++ b/testbed/scenes/hingejointschain/HingeJointsChainScene.cpp @@ -0,0 +1,159 @@ +/******************************************************************************** +* ReactPhysics3D physics library, http://www.reactphysics3d.com * +* Copyright (c) 2010-2016 Daniel Chappuis * +********************************************************************************* +* * +* This software is provided 'as-is', without any express or implied warranty. * +* In no event will the authors be held liable for any damages arising from the * +* use of this software. * +* * +* Permission is granted to anyone to use this software for any purpose, * +* including commercial applications, and to alter it and redistribute it * +* freely, subject to the following restrictions: * +* * +* 1. The origin of this software must not be misrepresented; you must not claim * +* that you wrote the original software. If you use this software in a * +* product, an acknowledgment in the product documentation would be * +* appreciated but is not required. * +* * +* 2. Altered source versions must be plainly marked as such, and must not be * +* misrepresented as being the original software. * +* * +* 3. This notice may not be removed or altered from any source distribution. * +* * +********************************************************************************/ + +// Libraries +#include "HingeJointsChainScene.h" +#include + +// Namespaces +using namespace openglframework; +using namespace hingejointschainscene; + +// Constructor +HingeJointsChainScene::HingeJointsChainScene(const std::string& name, EngineSettings& settings) + : SceneDemo(name, settings, true, SCENE_RADIUS) { + + std::string meshFolderPath("meshes/"); + + // Compute the radius and the center of the scene + openglframework::Vector3 center(0, 0, 0); + + // Set the center of the scene + setScenePosition(center, SCENE_RADIUS); + + // Gravity vector in the physics world + rp3d::Vector3 gravity(0, rp3d::decimal(-9.81), 0); + + rp3d::PhysicsWorld::WorldSettings worldSettings; + worldSettings.worldName = name; + + // Logger + rp3d::DefaultLogger* defaultLogger = mPhysicsCommon.createDefaultLogger(); + uint logLevel = static_cast(rp3d::Logger::Level::Information) | static_cast(rp3d::Logger::Level::Warning) | + static_cast(rp3d::Logger::Level::Error); + defaultLogger->addFileDestination("rp3d_log_" + name + ".html", logLevel, rp3d::DefaultLogger::Format::HTML); + mPhysicsCommon.setLogger(defaultLogger); + + // Create the physics world for the physics simulation + rp3d::PhysicsWorld* physicsWorld = mPhysicsCommon.createPhysicsWorld(worldSettings); + physicsWorld->setEventListener(this); + mPhysicsWorld = physicsWorld; + + // Create all the boxes of the scene + for (int i=0; isetColor(mObjectColorDemo); + mBoxes[i]->setSleepingColor(mSleepingColorDemo); + + // Change the material properties of the rigid body + rp3d::Material& material = mBoxes[i]->getCollider()->getMaterial(); + material.setBounciness(rp3d::decimal(0.0)); + + if (i == 0) { + mBoxes[i]->getRigidBody()->setType(rp3d::BodyType::STATIC); + } + + // Add the box the list of boxes in the scene + mPhysicsObjects.push_back(mBoxes[i]); + } + + // Set the position of the boxes before the joints creation + reset(); + + // Create the Ball-and-Socket joints + createJoints(); + + // Get the physics engine parameters + mEngineSettings.isGravityEnabled = mPhysicsWorld->isGravityEnabled(); + rp3d::Vector3 gravityVector = mPhysicsWorld->getGravity(); + mEngineSettings.gravity = openglframework::Vector3(gravityVector.x, gravityVector.y, gravityVector.z); + mEngineSettings.isSleepingEnabled = mPhysicsWorld->isSleepingEnabled(); + mEngineSettings.sleepLinearVelocity = mPhysicsWorld->getSleepLinearVelocity(); + mEngineSettings.sleepAngularVelocity = mPhysicsWorld->getSleepAngularVelocity(); + mEngineSettings.nbPositionSolverIterations = mPhysicsWorld->getNbIterationsPositionSolver(); + mEngineSettings.nbVelocitySolverIterations = mPhysicsWorld->getNbIterationsVelocitySolver(); + mEngineSettings.timeBeforeSleep = mPhysicsWorld->getTimeBeforeSleep(); +} + +// Destructor +HingeJointsChainScene::~HingeJointsChainScene() { + + // Destroy the joints + for (uint i=0; i < mHingeJoints.size(); i++) { + + mPhysicsWorld->destroyJoint(mHingeJoints[i]); + } + + // Destroy all the rigid bodies of the scene + for (int i=0; idestroyRigidBody(mBoxes[i]->getRigidBody()); + } + + // Destroy the physics world + mPhysicsCommon.destroyPhysicsWorld(mPhysicsWorld); +} + +// Create the joints +void HingeJointsChainScene::createJoints() { + + for (int i=0; i < NB_BOXES-1; i++) { + + // Create the joint info object + rp3d::RigidBody* body1 = mBoxes[i]->getRigidBody(); + rp3d::RigidBody* body2 = mBoxes[i+1]->getRigidBody(); + rp3d::Vector3 body1Position = body1->getTransform().getPosition(); + rp3d::Vector3 body2Position = body2->getTransform().getPosition(); + const rp3d::Vector3 anchorPointWorldSpace = body1Position + rp3d::Vector3(BOX_SIZE.x / 2.0f, 0, 0); + rp3d::HingeJointInfo jointInfo(body1, body2, anchorPointWorldSpace, rp3d::Vector3(0, 0, 1)); + jointInfo.isCollisionEnabled = false; + rp3d::HingeJoint* joint = dynamic_cast(mPhysicsWorld->createJoint(jointInfo)); + mHingeJoints.push_back(joint); + } +} + +// Reset the scene +void HingeJointsChainScene::reset() { + + SceneDemo::reset(); + + const float space = 0.3f; + + const rp3d::Quaternion initOrientation = rp3d::Quaternion::identity(); + + for (int i=0; isetTransform(transform); + } +} diff --git a/testbed/scenes/hingejointschain/HingeJointsChainScene.h b/testbed/scenes/hingejointschain/HingeJointsChainScene.h new file mode 100644 index 00000000..ad37b612 --- /dev/null +++ b/testbed/scenes/hingejointschain/HingeJointsChainScene.h @@ -0,0 +1,76 @@ +/******************************************************************************** +* ReactPhysics3D physics library, http://www.reactphysics3d.com * +* Copyright (c) 2010-2016 Daniel Chappuis * +********************************************************************************* +* * +* This software is provided 'as-is', without any express or implied warranty. * +* In no event will the authors be held liable for any damages arising from the * +* use of this software. * +* * +* Permission is granted to anyone to use this software for any purpose, * +* including commercial applications, and to alter it and redistribute it * +* freely, subject to the following restrictions: * +* * +* 1. The origin of this software must not be misrepresented; you must not claim * +* that you wrote the original software. If you use this software in a * +* product, an acknowledgment in the product documentation would be * +* appreciated but is not required. * +* * +* 2. Altered source versions must be plainly marked as such, and must not be * +* misrepresented as being the original software. * +* * +* 3. This notice may not be removed or altered from any source distribution. * +* * +********************************************************************************/ + +#ifndef HINGE_JOINTS_CHAIN_SCENE_H +#define HINGE_JOINTS_CHAIN_SCENE_H + +// Libraries +#include "openglframework.h" +#include +#include "Box.h" +#include "SceneDemo.h" + +namespace hingejointschainscene { + +// Constants +const float SCENE_RADIUS = 60.0f; +const openglframework::Vector3 BOX_SIZE = openglframework::Vector3(2, 1, 1); +const int NB_BOXES = 20; + +// Class HingeJointsChain scene +class HingeJointsChainScene : public SceneDemo { + + protected : + + // -------------------- Attributes -------------------- // + + /// Boxes + Box* mBoxes[NB_BOXES]; + + /// Hinge joints of the chain + std::vector mHingeJoints; + + // -------------------- Methods -------------------- // + + /// Create the joints + void createJoints(); + + public: + + // -------------------- Methods -------------------- // + + /// Constructor + HingeJointsChainScene(const std::string& name, EngineSettings& settings); + + /// Destructor + virtual ~HingeJointsChainScene() override ; + + /// Reset the scene + virtual void reset() override; +}; + +} + +#endif diff --git a/testbed/src/TestbedApplication.cpp b/testbed/src/TestbedApplication.cpp index 78e5a812..337ea96a 100755 --- a/testbed/src/TestbedApplication.cpp +++ b/testbed/src/TestbedApplication.cpp @@ -40,6 +40,8 @@ #include "pile/PileScene.h" #include "boxtower/BoxTowerScene.h" #include "ballandsocketjointsnet/BallAndSocketJointsNetScene.h" +#include "ballandsocketjointschain/BallAndSocketJointsChainScene.h" +#include "hingejointschain/HingeJointsChainScene.h" using namespace openglframework; using namespace jointsscene; @@ -53,6 +55,8 @@ using namespace cubestackscene; using namespace pilescene; using namespace boxtowerscene; using namespace ballandsocketjointsnetscene; +using namespace ballandsocketjointschainscene; +using namespace hingejointschainscene; // Initialization of static variables const float TestbedApplication::SCROLL_SENSITIVITY = 0.08f; @@ -147,16 +151,23 @@ void TestbedApplication::createScenes() { // Pile scene PileScene* pileScene = new PileScene("Pile", mEngineSettings); mScenes.push_back(pileScene); - assert(mScenes.size() > 0); // Box Tower scene BoxTowerScene* boxTowerScene = new BoxTowerScene("Box Tower", mEngineSettings); mScenes.push_back(boxTowerScene); - assert(mScenes.size() > 0); // Ball and Socket joints Net scene BallAndSocketJointsNetScene* ballAndSocketJointsNetScene = new BallAndSocketJointsNetScene("BallAndSocket Joints Net", mEngineSettings); mScenes.push_back(ballAndSocketJointsNetScene); + + // Ball and Socket joints chain scene + BallAndSocketJointsChainScene* ballAndSocketJointsChainScene = new BallAndSocketJointsChainScene("BallAndSocket Joints Chain", mEngineSettings); + mScenes.push_back(ballAndSocketJointsChainScene); + + // Hinge joints chain scene + HingeJointsChainScene* hingeJointsChainScene = new HingeJointsChainScene("Hinge Joints Chain", mEngineSettings); + mScenes.push_back(hingeJointsChainScene); + assert(mScenes.size() > 0); const int firstSceneIndex = 0; From 860bc8b091b12f204687d322071e754c61fbb673 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Sat, 17 Oct 2020 21:58:12 +0200 Subject: [PATCH 59/74] Fix typo --- include/reactphysics3d/components/JointComponents.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/reactphysics3d/components/JointComponents.h b/include/reactphysics3d/components/JointComponents.h index 00c2b372..c752f815 100644 --- a/include/reactphysics3d/components/JointComponents.h +++ b/include/reactphysics3d/components/JointComponents.h @@ -138,7 +138,7 @@ class JointComponents : public Components { JointsPositionCorrectionTechnique getPositionCorrectionTechnique(Entity jointEntity) const; /// Set the position correction technique of a joint - void getPositionCorrectionTechnique(Entity jointEntity, JointsPositionCorrectionTechnique positionCorrectionTechnique); + void setPositionCorrectionTechnique(Entity jointEntity, JointsPositionCorrectionTechnique positionCorrectionTechnique); /// Return true if the collision is enabled between the two bodies of a joint bool getIsCollisionEnabled(Entity jointEntity) const; @@ -195,7 +195,7 @@ RP3D_FORCE_INLINE JointsPositionCorrectionTechnique JointComponents::getPosition } // Set the position correction technique of a joint -RP3D_FORCE_INLINE void JointComponents::getPositionCorrectionTechnique(Entity jointEntity, JointsPositionCorrectionTechnique positionCorrectionTechnique) { +RP3D_FORCE_INLINE void JointComponents::setPositionCorrectionTechnique(Entity jointEntity, JointsPositionCorrectionTechnique positionCorrectionTechnique) { assert(mMapEntityToComponentIndex.containsKey(jointEntity)); mPositionCorrectionTechniques[mMapEntityToComponentIndex[jointEntity]] = positionCorrectionTechnique; } From ec5350bb5f64b43d205164e2dbd4dae0e7f4039f Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Tue, 20 Oct 2020 22:46:20 +0200 Subject: [PATCH 60/74] Make AABB methods inline --- .../reactphysics3d/collision/shapes/AABB.h | 96 +++++++++++++++++++ src/collision/shapes/AABB.cpp | 96 ------------------- 2 files changed, 96 insertions(+), 96 deletions(-) diff --git a/include/reactphysics3d/collision/shapes/AABB.h b/include/reactphysics3d/collision/shapes/AABB.h index 5dbcb6f1..66bed556 100644 --- a/include/reactphysics3d/collision/shapes/AABB.h +++ b/include/reactphysics3d/collision/shapes/AABB.h @@ -217,6 +217,102 @@ RP3D_FORCE_INLINE AABB& AABB::operator=(const AABB& aabb) { return *this; } +// Merge the AABB in parameter with the current one +RP3D_FORCE_INLINE void AABB::mergeWithAABB(const AABB& aabb) { + mMinCoordinates.x = std::min(mMinCoordinates.x, aabb.mMinCoordinates.x); + mMinCoordinates.y = std::min(mMinCoordinates.y, aabb.mMinCoordinates.y); + mMinCoordinates.z = std::min(mMinCoordinates.z, aabb.mMinCoordinates.z); + + mMaxCoordinates.x = std::max(mMaxCoordinates.x, aabb.mMaxCoordinates.x); + mMaxCoordinates.y = std::max(mMaxCoordinates.y, aabb.mMaxCoordinates.y); + mMaxCoordinates.z = std::max(mMaxCoordinates.z, aabb.mMaxCoordinates.z); +} + +// Replace the current AABB with a new AABB that is the union of two AABBs in parameters +RP3D_FORCE_INLINE void AABB::mergeTwoAABBs(const AABB& aabb1, const AABB& aabb2) { + mMinCoordinates.x = std::min(aabb1.mMinCoordinates.x, aabb2.mMinCoordinates.x); + mMinCoordinates.y = std::min(aabb1.mMinCoordinates.y, aabb2.mMinCoordinates.y); + mMinCoordinates.z = std::min(aabb1.mMinCoordinates.z, aabb2.mMinCoordinates.z); + + mMaxCoordinates.x = std::max(aabb1.mMaxCoordinates.x, aabb2.mMaxCoordinates.x); + mMaxCoordinates.y = std::max(aabb1.mMaxCoordinates.y, aabb2.mMaxCoordinates.y); + mMaxCoordinates.z = std::max(aabb1.mMaxCoordinates.z, aabb2.mMaxCoordinates.z); +} + +// Return true if the current AABB contains the AABB given in parameter +RP3D_FORCE_INLINE bool AABB::contains(const AABB& aabb) const { + + bool isInside = true; + isInside = isInside && mMinCoordinates.x <= aabb.mMinCoordinates.x; + isInside = isInside && mMinCoordinates.y <= aabb.mMinCoordinates.y; + isInside = isInside && mMinCoordinates.z <= aabb.mMinCoordinates.z; + + isInside = isInside && mMaxCoordinates.x >= aabb.mMaxCoordinates.x; + isInside = isInside && mMaxCoordinates.y >= aabb.mMaxCoordinates.y; + isInside = isInside && mMaxCoordinates.z >= aabb.mMaxCoordinates.z; + return isInside; +} + +// Create and return an AABB for a triangle +RP3D_FORCE_INLINE AABB AABB::createAABBForTriangle(const Vector3* trianglePoints) { + + Vector3 minCoords(trianglePoints[0].x, trianglePoints[0].y, trianglePoints[0].z); + Vector3 maxCoords(trianglePoints[0].x, trianglePoints[0].y, trianglePoints[0].z); + + if (trianglePoints[1].x < minCoords.x) minCoords.x = trianglePoints[1].x; + if (trianglePoints[1].y < minCoords.y) minCoords.y = trianglePoints[1].y; + if (trianglePoints[1].z < minCoords.z) minCoords.z = trianglePoints[1].z; + + if (trianglePoints[2].x < minCoords.x) minCoords.x = trianglePoints[2].x; + if (trianglePoints[2].y < minCoords.y) minCoords.y = trianglePoints[2].y; + if (trianglePoints[2].z < minCoords.z) minCoords.z = trianglePoints[2].z; + + if (trianglePoints[1].x > maxCoords.x) maxCoords.x = trianglePoints[1].x; + if (trianglePoints[1].y > maxCoords.y) maxCoords.y = trianglePoints[1].y; + if (trianglePoints[1].z > maxCoords.z) maxCoords.z = trianglePoints[1].z; + + if (trianglePoints[2].x > maxCoords.x) maxCoords.x = trianglePoints[2].x; + if (trianglePoints[2].y > maxCoords.y) maxCoords.y = trianglePoints[2].y; + if (trianglePoints[2].z > maxCoords.z) maxCoords.z = trianglePoints[2].z; + + return AABB(minCoords, maxCoords); +} + +// Return true if the ray intersects the AABB +/// This method use the line vs AABB raycasting technique (SAT algorithm) described in +/// Real-time Collision Detection by Christer Ericson. +RP3D_FORCE_INLINE bool AABB::testRayIntersect(const Ray& ray) const { + + const Vector3 point2 = ray.point1 + ray.maxFraction * (ray.point2 - ray.point1); + const Vector3 e = mMaxCoordinates - mMinCoordinates; + const Vector3 d = point2 - ray.point1; + const Vector3 m = ray.point1 + point2 - mMinCoordinates - mMaxCoordinates; + + // Test if the AABB face normals are separating axis + decimal adx = std::abs(d.x); + if (std::abs(m.x) > e.x + adx) return false; + decimal ady = std::abs(d.y); + if (std::abs(m.y) > e.y + ady) return false; + decimal adz = std::abs(d.z); + if (std::abs(m.z) > e.z + adz) return false; + + // Add in an epsilon term to counteract arithmetic errors when segment is + // (near) parallel to a coordinate axis + const decimal epsilon = 0.00001; + adx += epsilon; + ady += epsilon; + adz += epsilon; + + // Test if the cross products between face normals and ray direction are + // separating axis + if (std::abs(m.y * d.z - m.z * d.y) > e.y * adz + e.z * ady) return false; + if (std::abs(m.z * d.x - m.x * d.z) > e.x * adz + e.z * adx) return false; + if (std::abs(m.x * d.y - m.y * d.x) > e.x * ady + e.y * adx) return false; + + // No separating axis has been found + return true; +} + } #endif diff --git a/src/collision/shapes/AABB.cpp b/src/collision/shapes/AABB.cpp index 2d9b29e5..5e9df2f6 100644 --- a/src/collision/shapes/AABB.cpp +++ b/src/collision/shapes/AABB.cpp @@ -42,99 +42,3 @@ AABB::AABB(const AABB& aabb) : mMinCoordinates(aabb.mMinCoordinates), mMaxCoordinates(aabb.mMaxCoordinates) { } - -// Merge the AABB in parameter with the current one -void AABB::mergeWithAABB(const AABB& aabb) { - mMinCoordinates.x = std::min(mMinCoordinates.x, aabb.mMinCoordinates.x); - mMinCoordinates.y = std::min(mMinCoordinates.y, aabb.mMinCoordinates.y); - mMinCoordinates.z = std::min(mMinCoordinates.z, aabb.mMinCoordinates.z); - - mMaxCoordinates.x = std::max(mMaxCoordinates.x, aabb.mMaxCoordinates.x); - mMaxCoordinates.y = std::max(mMaxCoordinates.y, aabb.mMaxCoordinates.y); - mMaxCoordinates.z = std::max(mMaxCoordinates.z, aabb.mMaxCoordinates.z); -} - -// Replace the current AABB with a new AABB that is the union of two AABBs in parameters -void AABB::mergeTwoAABBs(const AABB& aabb1, const AABB& aabb2) { - mMinCoordinates.x = std::min(aabb1.mMinCoordinates.x, aabb2.mMinCoordinates.x); - mMinCoordinates.y = std::min(aabb1.mMinCoordinates.y, aabb2.mMinCoordinates.y); - mMinCoordinates.z = std::min(aabb1.mMinCoordinates.z, aabb2.mMinCoordinates.z); - - mMaxCoordinates.x = std::max(aabb1.mMaxCoordinates.x, aabb2.mMaxCoordinates.x); - mMaxCoordinates.y = std::max(aabb1.mMaxCoordinates.y, aabb2.mMaxCoordinates.y); - mMaxCoordinates.z = std::max(aabb1.mMaxCoordinates.z, aabb2.mMaxCoordinates.z); -} - -// Return true if the current AABB contains the AABB given in parameter -bool AABB::contains(const AABB& aabb) const { - - bool isInside = true; - isInside = isInside && mMinCoordinates.x <= aabb.mMinCoordinates.x; - isInside = isInside && mMinCoordinates.y <= aabb.mMinCoordinates.y; - isInside = isInside && mMinCoordinates.z <= aabb.mMinCoordinates.z; - - isInside = isInside && mMaxCoordinates.x >= aabb.mMaxCoordinates.x; - isInside = isInside && mMaxCoordinates.y >= aabb.mMaxCoordinates.y; - isInside = isInside && mMaxCoordinates.z >= aabb.mMaxCoordinates.z; - return isInside; -} - -// Create and return an AABB for a triangle -AABB AABB::createAABBForTriangle(const Vector3* trianglePoints) { - - Vector3 minCoords(trianglePoints[0].x, trianglePoints[0].y, trianglePoints[0].z); - Vector3 maxCoords(trianglePoints[0].x, trianglePoints[0].y, trianglePoints[0].z); - - if (trianglePoints[1].x < minCoords.x) minCoords.x = trianglePoints[1].x; - if (trianglePoints[1].y < minCoords.y) minCoords.y = trianglePoints[1].y; - if (trianglePoints[1].z < minCoords.z) minCoords.z = trianglePoints[1].z; - - if (trianglePoints[2].x < minCoords.x) minCoords.x = trianglePoints[2].x; - if (trianglePoints[2].y < minCoords.y) minCoords.y = trianglePoints[2].y; - if (trianglePoints[2].z < minCoords.z) minCoords.z = trianglePoints[2].z; - - if (trianglePoints[1].x > maxCoords.x) maxCoords.x = trianglePoints[1].x; - if (trianglePoints[1].y > maxCoords.y) maxCoords.y = trianglePoints[1].y; - if (trianglePoints[1].z > maxCoords.z) maxCoords.z = trianglePoints[1].z; - - if (trianglePoints[2].x > maxCoords.x) maxCoords.x = trianglePoints[2].x; - if (trianglePoints[2].y > maxCoords.y) maxCoords.y = trianglePoints[2].y; - if (trianglePoints[2].z > maxCoords.z) maxCoords.z = trianglePoints[2].z; - - return AABB(minCoords, maxCoords); -} - -// Return true if the ray intersects the AABB -/// This method use the line vs AABB raycasting technique described in -/// Real-time Collision Detection by Christer Ericson. -bool AABB::testRayIntersect(const Ray& ray) const { - - const Vector3 point2 = ray.point1 + ray.maxFraction * (ray.point2 - ray.point1); - const Vector3 e = mMaxCoordinates - mMinCoordinates; - const Vector3 d = point2 - ray.point1; - const Vector3 m = ray.point1 + point2 - mMinCoordinates - mMaxCoordinates; - - // Test if the AABB face normals are separating axis - decimal adx = std::abs(d.x); - if (std::abs(m.x) > e.x + adx) return false; - decimal ady = std::abs(d.y); - if (std::abs(m.y) > e.y + ady) return false; - decimal adz = std::abs(d.z); - if (std::abs(m.z) > e.z + adz) return false; - - // Add in an epsilon term to counteract arithmetic errors when segment is - // (near) parallel to a coordinate axis (see text for detail) - const decimal epsilon = 0.00001; - adx += epsilon; - ady += epsilon; - adz += epsilon; - - // Test if the cross products between face normals and ray direction are - // separating axis - if (std::abs(m.y * d.z - m.z * d.y) > e.y * adz + e.z * ady) return false; - if (std::abs(m.z * d.x - m.x * d.z) > e.x * adz + e.z * adx) return false; - if (std::abs(m.x * d.y - m.y * d.x) > e.x * ady + e.y * adx) return false; - - // No separating axis has been found - return true; -} From 2b052969a98832113af0575e0bbca267dd8f5816 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Thu, 29 Oct 2020 23:09:15 +0100 Subject: [PATCH 61/74] Compute the half-edge structure of TriangleShape and BoxShape only once at the beginning --- .../collision/shapes/BoxShape.h | 25 +----- .../collision/shapes/ConcaveMeshShape.h | 5 +- .../collision/shapes/HeightFieldShape.h | 5 +- .../collision/shapes/TriangleShape.h | 38 ++++----- include/reactphysics3d/engine/PhysicsCommon.h | 22 +++++ include/reactphysics3d/engine/PhysicsWorld.h | 3 +- .../systems/CollisionDetectionSystem.h | 6 +- src/collision/HalfEdgeStructure.cpp | 2 +- src/collision/PolyhedronMesh.cpp | 3 +- src/collision/shapes/BoxShape.cpp | 57 +++++-------- src/collision/shapes/ConcaveMeshShape.cpp | 6 +- src/collision/shapes/HeightFieldShape.cpp | 7 +- src/collision/shapes/TriangleShape.cpp | 63 +------------- src/engine/PhysicsCommon.cpp | 85 +++++++++++++++++-- src/engine/PhysicsWorld.cpp | 4 +- src/systems/CollisionDetectionSystem.cpp | 6 +- 16 files changed, 179 insertions(+), 158 deletions(-) diff --git a/include/reactphysics3d/collision/shapes/BoxShape.h b/include/reactphysics3d/collision/shapes/BoxShape.h index bbf08d0e..a94df5b5 100644 --- a/include/reactphysics3d/collision/shapes/BoxShape.h +++ b/include/reactphysics3d/collision/shapes/BoxShape.h @@ -36,6 +36,7 @@ namespace reactphysics3d { // Declarations class CollisionBody; class DefaultAllocator; +class PhysicsCommon; // Class BoxShape /** @@ -53,13 +54,13 @@ class BoxShape : public ConvexPolyhedronShape { /// Half-extents of the box in the x, y and z direction Vector3 mHalfExtents; - /// Half-edge structure of the polyhedron - HalfEdgeStructure mHalfEdgeStructure; + /// Reference to the physics common object + PhysicsCommon& mPhysicsCommon; // -------------------- Methods -------------------- // /// Constructor - BoxShape(const Vector3& halfExtents, MemoryAllocator& allocator); + BoxShape(const Vector3& halfExtents, MemoryAllocator& allocator, PhysicsCommon& physicsCommon); /// Return a local support point in a given direction without the object margin virtual Vector3 getLocalSupportPointWithoutMargin(const Vector3& direction) const override; @@ -196,23 +197,11 @@ RP3D_FORCE_INLINE uint BoxShape::getNbFaces() const { return 6; } -// Return a given face of the polyhedron -RP3D_FORCE_INLINE const HalfEdgeStructure::Face& BoxShape::getFace(uint faceIndex) const { - assert(faceIndex < mHalfEdgeStructure.getNbFaces()); - return mHalfEdgeStructure.getFace(faceIndex); -} - // Return the number of vertices of the polyhedron RP3D_FORCE_INLINE uint BoxShape::getNbVertices() const { return 8; } -// Return a given vertex of the polyhedron -RP3D_FORCE_INLINE HalfEdgeStructure::Vertex BoxShape::getVertex(uint vertexIndex) const { - assert(vertexIndex < getNbVertices()); - return mHalfEdgeStructure.getVertex(vertexIndex); -} - // Return the position of a given vertex RP3D_FORCE_INLINE Vector3 BoxShape::getVertexPosition(uint vertexIndex) const { assert(vertexIndex < getNbVertices()); @@ -269,12 +258,6 @@ RP3D_FORCE_INLINE uint BoxShape::getNbHalfEdges() const { return 24; } -// Return a given half-edge of the polyhedron -RP3D_FORCE_INLINE const HalfEdgeStructure::Edge& BoxShape::getHalfEdge(uint edgeIndex) const { - assert(edgeIndex < getNbHalfEdges()); - return mHalfEdgeStructure.getHalfEdge(edgeIndex); -} - } #endif diff --git a/include/reactphysics3d/collision/shapes/ConcaveMeshShape.h b/include/reactphysics3d/collision/shapes/ConcaveMeshShape.h index 106f0844..af2112b3 100644 --- a/include/reactphysics3d/collision/shapes/ConcaveMeshShape.h +++ b/include/reactphysics3d/collision/shapes/ConcaveMeshShape.h @@ -142,10 +142,13 @@ class ConcaveMeshShape : public ConcaveShape { /// if the user did not provide its own vertices normals) Vector3** mComputedVerticesNormals; + /// Reference to the triangle half-edge structure + HalfEdgeStructure& mTriangleHalfEdgeStructure; + // -------------------- Methods -------------------- // /// Constructor - ConcaveMeshShape(TriangleMesh* triangleMesh, MemoryAllocator& allocator, const Vector3& scaling = Vector3(1, 1, 1)); + ConcaveMeshShape(TriangleMesh* triangleMesh, MemoryAllocator& allocator, HalfEdgeStructure& triangleHalfEdgeStructure, const Vector3& scaling = Vector3(1, 1, 1)); /// Raycast method with feedback information virtual bool raycast(const Ray& ray, RaycastInfo& raycastInfo, Collider* collider, MemoryAllocator& allocator) const override; diff --git a/include/reactphysics3d/collision/shapes/HeightFieldShape.h b/include/reactphysics3d/collision/shapes/HeightFieldShape.h index c991de12..efa2625e 100644 --- a/include/reactphysics3d/collision/shapes/HeightFieldShape.h +++ b/include/reactphysics3d/collision/shapes/HeightFieldShape.h @@ -91,12 +91,15 @@ class HeightFieldShape : public ConcaveShape { /// Local AABB of the height field (without scaling) AABB mAABB; + /// Reference to the half-edge structure + HalfEdgeStructure& mTriangleHalfEdgeStructure; + // -------------------- Methods -------------------- // /// Constructor HeightFieldShape(int nbGridColumns, int nbGridRows, decimal minHeight, decimal maxHeight, const void* heightFieldData, HeightDataType dataType, MemoryAllocator& allocator, - int upAxis = 1, decimal integerHeightScale = 1.0f, + HalfEdgeStructure& triangleHalfEdgeStructure, int upAxis = 1, decimal integerHeightScale = 1.0f, const Vector3& scaling = Vector3(1,1,1)); /// Raycast method with feedback information diff --git a/include/reactphysics3d/collision/shapes/TriangleShape.h b/include/reactphysics3d/collision/shapes/TriangleShape.h index 5224f242..aa9a4003 100644 --- a/include/reactphysics3d/collision/shapes/TriangleShape.h +++ b/include/reactphysics3d/collision/shapes/TriangleShape.h @@ -33,6 +33,9 @@ /// ReactPhysics3D namespace namespace reactphysics3d { +// Forward declarations +class PhysicsCommon; + /// Raycast test side for the triangle enum class TriangleRaycastSide { @@ -73,11 +76,8 @@ class TriangleShape : public ConvexPolyhedronShape { /// Raycast test type for the triangle (front, back, front-back) TriangleRaycastSide mRaycastTestType; - /// Faces information for the two faces of the triangle - HalfEdgeStructure::Face mFaces[2]; - - /// Edges information for the six edges of the triangle - HalfEdgeStructure::Edge mEdges[6]; + /// Reference to triangle half-edge structure + HalfEdgeStructure& mTriangleHalfEdgeStructure; // -------------------- Methods -------------------- // @@ -108,8 +108,8 @@ class TriangleShape : public ConvexPolyhedronShape { Vector3& outNewLocalContactPointOtherShape, Vector3& outSmoothWorldContactTriangleNormal) const; /// Constructor - TriangleShape(const Vector3* vertices, const Vector3* verticesNormals, - uint shapeId, MemoryAllocator& allocator); + TriangleShape(const Vector3* vertices, const Vector3* verticesNormals, uint shapeId, HalfEdgeStructure& triangleHalfEdgeStructure, + MemoryAllocator& allocator); /// Destructor virtual ~TriangleShape() override = default; @@ -236,12 +236,6 @@ RP3D_FORCE_INLINE uint TriangleShape::getNbFaces() const { return 2; } -// Return a given face of the polyhedron -RP3D_FORCE_INLINE const HalfEdgeStructure::Face& TriangleShape::getFace(uint faceIndex) const { - assert(faceIndex < 2); - return mFaces[faceIndex]; -} - // Return the number of vertices of the polyhedron RP3D_FORCE_INLINE uint TriangleShape::getNbVertices() const { return 3; @@ -260,12 +254,6 @@ RP3D_FORCE_INLINE HalfEdgeStructure::Vertex TriangleShape::getVertex(uint vertex return vertex; } -// Return a given half-edge of the polyhedron -RP3D_FORCE_INLINE const HalfEdgeStructure::Edge& TriangleShape::getHalfEdge(uint edgeIndex) const { - assert(edgeIndex < getNbHalfEdges()); - return mEdges[edgeIndex]; -} - // Return the position of a given vertex RP3D_FORCE_INLINE Vector3 TriangleShape::getVertexPosition(uint vertexIndex) const { assert(vertexIndex < 3); @@ -377,6 +365,18 @@ RP3D_FORCE_INLINE void TriangleShape::computeSmoothTriangleMeshContact(const Col } } +// Return a given face of the polyhedron +RP3D_FORCE_INLINE const HalfEdgeStructure::Face& TriangleShape::getFace(uint faceIndex) const { + assert(faceIndex < 2); + return mTriangleHalfEdgeStructure.getFace(faceIndex); +} + +// Return a given half-edge of the polyhedron +RP3D_FORCE_INLINE const HalfEdgeStructure::Edge& TriangleShape::getHalfEdge(uint edgeIndex) const { + assert(edgeIndex < getNbHalfEdges()); + return mTriangleHalfEdgeStructure.getHalfEdge(edgeIndex); +} + } #endif diff --git a/include/reactphysics3d/engine/PhysicsCommon.h b/include/reactphysics3d/engine/PhysicsCommon.h index f2e827c8..7f685458 100644 --- a/include/reactphysics3d/engine/PhysicsCommon.h +++ b/include/reactphysics3d/engine/PhysicsCommon.h @@ -92,8 +92,17 @@ class PhysicsCommon { /// Set of default loggers Set mDefaultLoggers; + /// Half-edge structure of a box polyhedron + HalfEdgeStructure mBoxShapeHalfEdgeStructure; + + /// Half-edge structure of a triangle shape + HalfEdgeStructure mTriangleShapeHalfEdgeStructure; + // -------------------- Methods -------------------- // + /// Initialization + void init(); + /// Destroy and release everything that has been allocated void release(); @@ -127,6 +136,12 @@ class PhysicsCommon { /// Delete a default logger void deleteDefaultLogger(DefaultLogger* logger); + /// Initialize the half-edge structure of a BoxShape + void initBoxShapeHalfEdgeStructure(); + + /// Initialize the static half-edge structure of a TriangleShape + void initTriangleShapeHalfEdgeStructure(); + // If profiling is enabled #ifdef IS_RP3D_PROFILING_ENABLED @@ -219,6 +234,13 @@ class PhysicsCommon { /// Set the logger static void setLogger(Logger* logger); + + + // ---------- Friendship ---------- // + + friend class BoxShape; + friend class TriangleShape; + friend class PhysicsWorld; }; // Return the current logger diff --git a/include/reactphysics3d/engine/PhysicsWorld.h b/include/reactphysics3d/engine/PhysicsWorld.h index bafe96c7..a6f894eb 100644 --- a/include/reactphysics3d/engine/PhysicsWorld.h +++ b/include/reactphysics3d/engine/PhysicsWorld.h @@ -59,6 +59,7 @@ namespace reactphysics3d { // Declarations class Island; class RigidBody; +class PhysicsCommon; struct JointInfo; // Class PhysicsWorld @@ -275,7 +276,7 @@ class PhysicsWorld { // -------------------- Methods -------------------- // /// Constructor - PhysicsWorld(MemoryManager& memoryManager, const WorldSettings& worldSettings = WorldSettings(), Profiler* profiler = nullptr); + PhysicsWorld(MemoryManager& memoryManager, PhysicsCommon& physicsCommon, const WorldSettings& worldSettings = WorldSettings(), Profiler* profiler = nullptr); /// Notify the world if a body is disabled (slepping or inactive) or not void setBodyDisabled(Entity entity, bool isDisabled); diff --git a/include/reactphysics3d/systems/CollisionDetectionSystem.h b/include/reactphysics3d/systems/CollisionDetectionSystem.h index 29b21d73..a978fd3a 100644 --- a/include/reactphysics3d/systems/CollisionDetectionSystem.h +++ b/include/reactphysics3d/systems/CollisionDetectionSystem.h @@ -43,6 +43,7 @@ #include #include #include +#include /// ReactPhysics3D namespace namespace reactphysics3d { @@ -168,6 +169,9 @@ class CollisionDetectionSystem { /// Number of potential contact points in the previous frame uint32 mNbPreviousPotentialContactPoints; + /// Reference to the half-edge structure of the triangle polyhedron + HalfEdgeStructure& mTriangleHalfEdgeStructure; + #ifdef IS_RP3D_PROFILING_ENABLED /// Pointer to the profiler @@ -294,7 +298,7 @@ class CollisionDetectionSystem { /// Constructor CollisionDetectionSystem(PhysicsWorld* world, ColliderComponents& collidersComponents, TransformComponents& transformComponents, CollisionBodyComponents& collisionBodyComponents, RigidBodyComponents& rigidBodyComponents, - MemoryManager& memoryManager); + MemoryManager& memoryManager, HalfEdgeStructure& triangleHalfEdgeStructure); /// Destructor ~CollisionDetectionSystem() = default; diff --git a/src/collision/HalfEdgeStructure.cpp b/src/collision/HalfEdgeStructure.cpp index 5ec064fa..e71e35e1 100644 --- a/src/collision/HalfEdgeStructure.cpp +++ b/src/collision/HalfEdgeStructure.cpp @@ -47,7 +47,7 @@ void HalfEdgeStructure::init() { const uint32 nbFaces = mFaces.size(); for (uint32 f=0; f < nbFaces; f++) { - Face face = mFaces[f]; + Face& face = mFaces[f]; VerticesPair firstEdgeKey(0, 0); diff --git a/src/collision/PolyhedronMesh.cpp b/src/collision/PolyhedronMesh.cpp index b205b1cd..f48f0e46 100644 --- a/src/collision/PolyhedronMesh.cpp +++ b/src/collision/PolyhedronMesh.cpp @@ -177,7 +177,8 @@ Vector3 PolyhedronMesh::getVertex(uint index) const { void PolyhedronMesh::computeFacesNormals() { // For each face - for (uint f=0; f < mHalfEdgeStructure.getNbFaces(); f++) { + const uint32 nbFaces = mHalfEdgeStructure.getNbFaces(); + for (uint f=0; f < nbFaces; f++) { const HalfEdgeStructure::Face& face = mHalfEdgeStructure.getFace(f); assert(face.faceVertices.size() >= 3); diff --git a/src/collision/shapes/BoxShape.cpp b/src/collision/shapes/BoxShape.cpp index 35448295..51335e0a 100644 --- a/src/collision/shapes/BoxShape.cpp +++ b/src/collision/shapes/BoxShape.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -37,46 +38,12 @@ using namespace reactphysics3d; /** * @param halfExtents The vector with the three half-extents of the box */ -BoxShape::BoxShape(const Vector3& halfExtents, MemoryAllocator& allocator) - : ConvexPolyhedronShape(CollisionShapeName::BOX, allocator), mHalfExtents(halfExtents), - mHalfEdgeStructure(allocator, 6, 8, 24) { +BoxShape::BoxShape(const Vector3& halfExtents, MemoryAllocator& allocator, PhysicsCommon& physicsCommon) + : ConvexPolyhedronShape(CollisionShapeName::BOX, allocator), mHalfExtents(halfExtents), mPhysicsCommon(physicsCommon) { assert(halfExtents.x > decimal(0.0)); assert(halfExtents.y > decimal(0.0)); assert(halfExtents.z > decimal(0.0)); - - // Vertices - mHalfEdgeStructure.addVertex(0); - mHalfEdgeStructure.addVertex(1); - mHalfEdgeStructure.addVertex(2); - mHalfEdgeStructure.addVertex(3); - mHalfEdgeStructure.addVertex(4); - mHalfEdgeStructure.addVertex(5); - mHalfEdgeStructure.addVertex(6); - mHalfEdgeStructure.addVertex(7); - - // Faces - Array face0(allocator, 4); - face0.add(0); face0.add(1); face0.add(2); face0.add(3); - Array face1(allocator, 4); - face1.add(1); face1.add(5); face1.add(6); face1.add(2); - Array face2(allocator, 4); - face2.add(4); face2.add(7); face2.add(6); face2.add(5); - Array face3(allocator, 4); - face3.add(4); face3.add(0); face3.add(3); face3.add(7); - Array face4(allocator, 4); - face4.add(4); face4.add(5); face4.add(1); face4.add(0); - Array face5(allocator, 4); - face5.add(2); face5.add(6); face5.add(7); face5.add(3); - - mHalfEdgeStructure.addFace(face0); - mHalfEdgeStructure.addFace(face1); - mHalfEdgeStructure.addFace(face2); - mHalfEdgeStructure.addFace(face3); - mHalfEdgeStructure.addFace(face4); - mHalfEdgeStructure.addFace(face5); - - mHalfEdgeStructure.init(); } // Return the local inertia tensor of the collision shape @@ -155,3 +122,21 @@ bool BoxShape::raycast(const Ray& ray, RaycastInfo& raycastInfo, Collider* colli return true; } + +// Return a given face of the polyhedron +const HalfEdgeStructure::Face& BoxShape::getFace(uint faceIndex) const { + assert(faceIndex < mPhysicsCommon.mBoxShapeHalfEdgeStructure.getNbFaces()); + return mPhysicsCommon.mBoxShapeHalfEdgeStructure.getFace(faceIndex); +} + +// Return a given vertex of the polyhedron +HalfEdgeStructure::Vertex BoxShape::getVertex(uint vertexIndex) const { + assert(vertexIndex < getNbVertices()); + return mPhysicsCommon.mBoxShapeHalfEdgeStructure.getVertex(vertexIndex); +} + +// Return a given half-edge of the polyhedron +const HalfEdgeStructure::Edge& BoxShape::getHalfEdge(uint edgeIndex) const { + assert(edgeIndex < getNbHalfEdges()); + return mPhysicsCommon.mBoxShapeHalfEdgeStructure.getHalfEdge(edgeIndex); +} diff --git a/src/collision/shapes/ConcaveMeshShape.cpp b/src/collision/shapes/ConcaveMeshShape.cpp index cedb55f2..77703c92 100644 --- a/src/collision/shapes/ConcaveMeshShape.cpp +++ b/src/collision/shapes/ConcaveMeshShape.cpp @@ -34,8 +34,8 @@ using namespace reactphysics3d; // Constructor -ConcaveMeshShape::ConcaveMeshShape(TriangleMesh* triangleMesh, MemoryAllocator& allocator, const Vector3& scaling) - : ConcaveShape(CollisionShapeName::TRIANGLE_MESH, allocator, scaling), mDynamicAABBTree(allocator) { +ConcaveMeshShape::ConcaveMeshShape(TriangleMesh* triangleMesh, MemoryAllocator& allocator, HalfEdgeStructure& triangleHalfEdgeStructure, const Vector3& scaling) + : ConcaveShape(CollisionShapeName::TRIANGLE_MESH, allocator, scaling), mDynamicAABBTree(allocator), mTriangleHalfEdgeStructure(triangleHalfEdgeStructure) { mTriangleMesh = triangleMesh; mRaycastTestType = TriangleRaycastSide::FRONT; @@ -247,7 +247,7 @@ void ConcaveMeshRaycastCallback::raycastTriangles() { mConcaveMeshShape.getTriangleVerticesNormals(data[0], data[1], verticesNormals); // Create a triangle collision shape - TriangleShape triangleShape(trianglePoints, verticesNormals, mConcaveMeshShape.computeTriangleShapeId(data[0], data[1]), mAllocator); + TriangleShape triangleShape(trianglePoints, verticesNormals, mConcaveMeshShape.computeTriangleShapeId(data[0], data[1]), mConcaveMeshShape.mTriangleHalfEdgeStructure, mAllocator); triangleShape.setRaycastTestType(mConcaveMeshShape.getRaycastTestType()); #ifdef IS_RP3D_PROFILING_ENABLED diff --git a/src/collision/shapes/HeightFieldShape.cpp b/src/collision/shapes/HeightFieldShape.cpp index 3322d88d..f3693fc1 100644 --- a/src/collision/shapes/HeightFieldShape.cpp +++ b/src/collision/shapes/HeightFieldShape.cpp @@ -42,12 +42,13 @@ using namespace reactphysics3d; * @param integerHeightScale Scaling factor used to scale the height values (only when height values type is integer) */ HeightFieldShape::HeightFieldShape(int nbGridColumns, int nbGridRows, decimal minHeight, decimal maxHeight, - const void* heightFieldData, HeightDataType dataType, MemoryAllocator& allocator, int upAxis, + const void* heightFieldData, HeightDataType dataType, MemoryAllocator& allocator, + HalfEdgeStructure& triangleHalfEdgeStructure, int upAxis, decimal integerHeightScale, const Vector3& scaling) : ConcaveShape(CollisionShapeName::HEIGHTFIELD, allocator, scaling), mNbColumns(nbGridColumns), mNbRows(nbGridRows), mWidth(nbGridColumns - 1), mLength(nbGridRows - 1), mMinHeight(minHeight), mMaxHeight(maxHeight), mUpAxis(upAxis), mIntegerHeightScale(integerHeightScale), - mHeightDataType(dataType) { + mHeightDataType(dataType), mTriangleHalfEdgeStructure(triangleHalfEdgeStructure) { assert(nbGridColumns >= 2); assert(nbGridRows >= 2); @@ -254,7 +255,7 @@ bool HeightFieldShape::raycast(const Ray& ray, RaycastInfo& raycastInfo, Collide for (uint32 i=0; i < nbShapeIds; i++) { // Create a triangle collision shape - TriangleShape triangleShape(&(triangleVertices[i * 3]), &(triangleVerticesNormals[i * 3]), shapeIds[i], allocator); + TriangleShape triangleShape(&(triangleVertices[i * 3]), &(triangleVerticesNormals[i * 3]), shapeIds[i], mTriangleHalfEdgeStructure, allocator); triangleShape.setRaycastTestType(getRaycastTestType()); #ifdef IS_RP3D_PROFILING_ENABLED diff --git a/src/collision/shapes/TriangleShape.cpp b/src/collision/shapes/TriangleShape.cpp index 091d2507..f2150e05 100644 --- a/src/collision/shapes/TriangleShape.cpp +++ b/src/collision/shapes/TriangleShape.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -45,9 +46,8 @@ using namespace reactphysics3d; * @param verticesNormals The three vertices normals for smooth mesh collision * @param margin The collision margin (in meters) around the collision shape */ -TriangleShape::TriangleShape(const Vector3* vertices, const Vector3* verticesNormals, uint shapeId, - MemoryAllocator& allocator) - : ConvexPolyhedronShape(CollisionShapeName::TRIANGLE, allocator), mFaces{HalfEdgeStructure::Face(allocator), HalfEdgeStructure::Face(allocator)} { +TriangleShape::TriangleShape(const Vector3* vertices, const Vector3* verticesNormals, uint shapeId, HalfEdgeStructure& triangleHalfEdgeStructure, MemoryAllocator& allocator) + : ConvexPolyhedronShape(CollisionShapeName::TRIANGLE, allocator), mTriangleHalfEdgeStructure(triangleHalfEdgeStructure) { mPoints[0] = vertices[0]; mPoints[1] = vertices[1]; @@ -61,62 +61,6 @@ TriangleShape::TriangleShape(const Vector3* vertices, const Vector3* verticesNor mVerticesNormals[1] = verticesNormals[1]; mVerticesNormals[2] = verticesNormals[2]; - // Faces - mFaces[0].faceVertices.reserve(3); - mFaces[0].faceVertices.add(0); - mFaces[0].faceVertices.add(1); - mFaces[0].faceVertices.add(2); - mFaces[0].edgeIndex = 0; - - mFaces[1].faceVertices.reserve(3); - mFaces[1].faceVertices.add(0); - mFaces[1].faceVertices.add(2); - mFaces[1].faceVertices.add(1); - mFaces[1].edgeIndex = 1; - - // Edges - for (uint i=0; i<6; i++) { - switch(i) { - case 0: - mEdges[0].vertexIndex = 0; - mEdges[0].twinEdgeIndex = 1; - mEdges[0].faceIndex = 0; - mEdges[0].nextEdgeIndex = 2; - break; - case 1: - mEdges[1].vertexIndex = 1; - mEdges[1].twinEdgeIndex = 0; - mEdges[1].faceIndex = 1; - mEdges[1].nextEdgeIndex = 5; - break; - case 2: - mEdges[2].vertexIndex = 1; - mEdges[2].twinEdgeIndex = 3; - mEdges[2].faceIndex = 0; - mEdges[2].nextEdgeIndex = 4; - break; - case 3: - mEdges[3].vertexIndex = 2; - mEdges[3].twinEdgeIndex = 2; - mEdges[3].faceIndex = 1; - mEdges[3].nextEdgeIndex = 1; - break; - case 4: - mEdges[4].vertexIndex = 2; - mEdges[4].twinEdgeIndex = 5; - mEdges[4].faceIndex = 0; - mEdges[4].nextEdgeIndex = 0; - break; - case 5: - mEdges[5].vertexIndex = 0; - mEdges[5].twinEdgeIndex = 4; - mEdges[5].faceIndex = 1; - mEdges[5].nextEdgeIndex = 3; - break; - } - } - - mRaycastTestType = TriangleRaycastSide::FRONT; mId = shapeId; @@ -249,4 +193,3 @@ bool TriangleShape::raycast(const Ray& ray, RaycastInfo& raycastInfo, Collider* return true; } - diff --git a/src/engine/PhysicsCommon.cpp b/src/engine/PhysicsCommon.cpp index a36e6e26..7d774fa2 100644 --- a/src/engine/PhysicsCommon.cpp +++ b/src/engine/PhysicsCommon.cpp @@ -42,8 +42,11 @@ PhysicsCommon::PhysicsCommon(MemoryAllocator* baseMemoryAllocator) mConvexMeshShapes(mMemoryManager.getHeapAllocator()), mConcaveMeshShapes(mMemoryManager.getHeapAllocator()), mHeightFieldShapes(mMemoryManager.getHeapAllocator()), mPolyhedronMeshes(mMemoryManager.getHeapAllocator()), mTriangleMeshes(mMemoryManager.getHeapAllocator()), - mProfilers(mMemoryManager.getHeapAllocator()), mDefaultLoggers(mMemoryManager.getHeapAllocator()) { + mProfilers(mMemoryManager.getHeapAllocator()), mDefaultLoggers(mMemoryManager.getHeapAllocator()), + mBoxShapeHalfEdgeStructure(mMemoryManager.getHeapAllocator(), 6, 8, 24), + mTriangleShapeHalfEdgeStructure(mMemoryManager.getHeapAllocator(), 2, 3, 6) { + init(); } // Destructor @@ -53,6 +56,77 @@ PhysicsCommon::~PhysicsCommon() { release(); } +/// Initialization +void PhysicsCommon::init() { + + // Initialize the static half-edge structure for the BoxShape collision shape + initBoxShapeHalfEdgeStructure(); + + // Initialize the static half-edge structure for the TriangleShape collision shape + initTriangleShapeHalfEdgeStructure(); +} + +// Initialize the static half-edge structure of a BoxShape +void PhysicsCommon::initBoxShapeHalfEdgeStructure() { + + // Vertices + mBoxShapeHalfEdgeStructure.addVertex(0); + mBoxShapeHalfEdgeStructure.addVertex(1); + mBoxShapeHalfEdgeStructure.addVertex(2); + mBoxShapeHalfEdgeStructure.addVertex(3); + mBoxShapeHalfEdgeStructure.addVertex(4); + mBoxShapeHalfEdgeStructure.addVertex(5); + mBoxShapeHalfEdgeStructure.addVertex(6); + mBoxShapeHalfEdgeStructure.addVertex(7); + + MemoryAllocator& allocator = mMemoryManager.getHeapAllocator(); + + // Faces + Array face0(allocator, 4); + face0.add(0); face0.add(1); face0.add(2); face0.add(3); + Array face1(allocator, 4); + face1.add(1); face1.add(5); face1.add(6); face1.add(2); + Array face2(allocator, 4); + face2.add(4); face2.add(7); face2.add(6); face2.add(5); + Array face3(allocator, 4); + face3.add(4); face3.add(0); face3.add(3); face3.add(7); + Array face4(allocator, 4); + face4.add(4); face4.add(5); face4.add(1); face4.add(0); + Array face5(allocator, 4); + face5.add(2); face5.add(6); face5.add(7); face5.add(3); + + mBoxShapeHalfEdgeStructure.addFace(face0); + mBoxShapeHalfEdgeStructure.addFace(face1); + mBoxShapeHalfEdgeStructure.addFace(face2); + mBoxShapeHalfEdgeStructure.addFace(face3); + mBoxShapeHalfEdgeStructure.addFace(face4); + mBoxShapeHalfEdgeStructure.addFace(face5); + + mBoxShapeHalfEdgeStructure.init(); +} + +// Initialize the static half-edge structure of a TriangleShape +void PhysicsCommon::initTriangleShapeHalfEdgeStructure() { + + // Vertices + mTriangleShapeHalfEdgeStructure.addVertex(0); + mTriangleShapeHalfEdgeStructure.addVertex(1); + mTriangleShapeHalfEdgeStructure.addVertex(2); + + MemoryAllocator& allocator = mMemoryManager.getHeapAllocator(); + + // Faces + Array face0(allocator, 3); + face0.add(0); face0.add(1); face0.add(2); + Array face1(allocator, 3); + face1.add(0); face1.add(2); face1.add(1); + + mTriangleShapeHalfEdgeStructure.addFace(face0); + mTriangleShapeHalfEdgeStructure.addFace(face1); + + mTriangleShapeHalfEdgeStructure.init(); +} + // Destroy and release everything that has been allocated void PhysicsCommon::release() { @@ -149,7 +223,7 @@ PhysicsWorld* PhysicsCommon::createPhysicsWorld(const PhysicsWorld::WorldSetting #endif - PhysicsWorld* world = new(mMemoryManager.allocate(MemoryManager::AllocationType::Heap, sizeof(PhysicsWorld))) PhysicsWorld(mMemoryManager, worldSettings, profiler); + PhysicsWorld* world = new(mMemoryManager.allocate(MemoryManager::AllocationType::Heap, sizeof(PhysicsWorld))) PhysicsWorld(mMemoryManager, *this, worldSettings, profiler); mPhysicsWorlds.add(world); @@ -242,7 +316,7 @@ BoxShape* PhysicsCommon::createBoxShape(const Vector3& halfExtents) { RP3D_LOG("PhysicsCommon", Logger::Level::Error, Logger::Category::PhysicCommon, "Error when creating a BoxShape: the half extents must be positive values", __FILE__, __LINE__); } - BoxShape* shape = new (mMemoryManager.allocate(MemoryManager::AllocationType::Pool, sizeof(BoxShape))) BoxShape(halfExtents, mMemoryManager.getHeapAllocator()); + BoxShape* shape = new (mMemoryManager.allocate(MemoryManager::AllocationType::Pool, sizeof(BoxShape))) BoxShape(halfExtents, mMemoryManager.getHeapAllocator(), *this); mBoxShapes.add(shape); @@ -401,7 +475,7 @@ HeightFieldShape* PhysicsCommon::createHeightFieldShape(int nbGridColumns, int n int upAxis, decimal integerHeightScale, const Vector3& scaling) { HeightFieldShape* shape = new (mMemoryManager.allocate(MemoryManager::AllocationType::Pool, sizeof(HeightFieldShape))) HeightFieldShape(nbGridColumns, nbGridRows, minHeight, maxHeight, - heightFieldData, dataType, mMemoryManager.getHeapAllocator(), upAxis, integerHeightScale, scaling); + heightFieldData, dataType, mMemoryManager.getHeapAllocator(), mTriangleShapeHalfEdgeStructure, upAxis, integerHeightScale, scaling); mHeightFieldShapes.add(shape); @@ -447,7 +521,8 @@ void PhysicsCommon::deleteHeightFieldShape(HeightFieldShape* heightFieldShape) { */ ConcaveMeshShape* PhysicsCommon::createConcaveMeshShape(TriangleMesh* triangleMesh, const Vector3& scaling) { - ConcaveMeshShape* shape = new (mMemoryManager.allocate(MemoryManager::AllocationType::Pool, sizeof(ConcaveMeshShape))) ConcaveMeshShape(triangleMesh, mMemoryManager.getHeapAllocator(), scaling); + ConcaveMeshShape* shape = new (mMemoryManager.allocate(MemoryManager::AllocationType::Pool, sizeof(ConcaveMeshShape))) ConcaveMeshShape(triangleMesh, + mMemoryManager.getHeapAllocator(), mTriangleShapeHalfEdgeStructure, scaling); mConcaveMeshShapes.add(shape); diff --git a/src/engine/PhysicsWorld.cpp b/src/engine/PhysicsWorld.cpp index 19a4bca3..a6fe538d 100644 --- a/src/engine/PhysicsWorld.cpp +++ b/src/engine/PhysicsWorld.cpp @@ -50,14 +50,14 @@ uint PhysicsWorld::mNbWorlds = 0; * @param worldSettings The settings of the world * @param profiler Pointer to the profiler */ -PhysicsWorld::PhysicsWorld(MemoryManager& memoryManager, const WorldSettings& worldSettings, Profiler* profiler) +PhysicsWorld::PhysicsWorld(MemoryManager& memoryManager, PhysicsCommon& physicsCommon, const WorldSettings& worldSettings, Profiler* profiler) : mMemoryManager(memoryManager), mConfig(worldSettings), mEntityManager(mMemoryManager.getHeapAllocator()), mDebugRenderer(mMemoryManager.getHeapAllocator()), mCollisionBodyComponents(mMemoryManager.getHeapAllocator()), mRigidBodyComponents(mMemoryManager.getHeapAllocator()), mTransformComponents(mMemoryManager.getHeapAllocator()), mCollidersComponents(mMemoryManager.getHeapAllocator()), mJointsComponents(mMemoryManager.getHeapAllocator()), mBallAndSocketJointsComponents(mMemoryManager.getHeapAllocator()), mFixedJointsComponents(mMemoryManager.getHeapAllocator()), mHingeJointsComponents(mMemoryManager.getHeapAllocator()), mSliderJointsComponents(mMemoryManager.getHeapAllocator()), mCollisionDetection(this, mCollidersComponents, mTransformComponents, mCollisionBodyComponents, mRigidBodyComponents, - mMemoryManager), + mMemoryManager, physicsCommon.mTriangleShapeHalfEdgeStructure), mCollisionBodies(mMemoryManager.getHeapAllocator()), mEventListener(nullptr), mName(worldSettings.worldName), mIslands(mMemoryManager.getSingleFrameAllocator()), mProcessContactPairsOrderIslands(mMemoryManager.getSingleFrameAllocator()), mContactSolverSystem(mMemoryManager, *this, mIslands, mCollisionBodyComponents, mRigidBodyComponents, diff --git a/src/systems/CollisionDetectionSystem.cpp b/src/systems/CollisionDetectionSystem.cpp index b09c338a..3cb40a27 100644 --- a/src/systems/CollisionDetectionSystem.cpp +++ b/src/systems/CollisionDetectionSystem.cpp @@ -51,7 +51,7 @@ using namespace std; // Constructor CollisionDetectionSystem::CollisionDetectionSystem(PhysicsWorld* world, ColliderComponents& collidersComponents, TransformComponents& transformComponents, CollisionBodyComponents& collisionBodyComponents, RigidBodyComponents& rigidBodyComponents, - MemoryManager& memoryManager) + MemoryManager& memoryManager, HalfEdgeStructure& triangleHalfEdgeStructure) : mMemoryManager(memoryManager), mCollidersComponents(collidersComponents), mRigidBodyComponents(rigidBodyComponents), mCollisionDispatch(mMemoryManager.getPoolAllocator()), mWorld(world), mNoCollisionPairs(mMemoryManager.getPoolAllocator()), @@ -68,7 +68,7 @@ CollisionDetectionSystem::CollisionDetectionSystem(PhysicsWorld* world, Collider mPreviousContactManifolds(&mContactManifolds1), mCurrentContactManifolds(&mContactManifolds2), mContactPoints1(mMemoryManager.getPoolAllocator()), mContactPoints2(mMemoryManager.getPoolAllocator()), mPreviousContactPoints(&mContactPoints1), mCurrentContactPoints(&mContactPoints2), mCollisionBodyContactPairsIndices(mMemoryManager.getSingleFrameAllocator()), - mNbPreviousPotentialContactManifolds(0), mNbPreviousPotentialContactPoints(0) { + mNbPreviousPotentialContactManifolds(0), mNbPreviousPotentialContactPoints(0), mTriangleHalfEdgeStructure(triangleHalfEdgeStructure) { #ifdef IS_RP3D_PROFILING_ENABLED @@ -486,7 +486,7 @@ void CollisionDetectionSystem::computeConvexVsConcaveMiddlePhase(OverlappingPair // Create a triangle collision shape (the allocated memory for the TriangleShape will be released in the // destructor of the corresponding NarrowPhaseInfo. TriangleShape* triangleShape = new (allocator.allocate(sizeof(TriangleShape))) - TriangleShape(&(triangleVertices[i * 3]), &(triangleVerticesNormals[i * 3]), shapeIds[i], allocator); + TriangleShape(&(triangleVertices[i * 3]), &(triangleVerticesNormals[i * 3]), shapeIds[i], mTriangleHalfEdgeStructure, allocator); #ifdef IS_RP3D_PROFILING_ENABLED From 9d645bdca73e72a1c2680ab54c4cee68973704f1 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Sun, 8 Nov 2020 15:13:17 +0100 Subject: [PATCH 62/74] =?UTF-8?q?Use=20faster=20ray=20vs=C2=A0ABBB=20inter?= =?UTF-8?q?section=20algorithm=20in=20raycasting=20DynamicAABBTree=20broad?= =?UTF-8?q?-phase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reactphysics3d/collision/RaycastInfo.h | 1 + .../reactphysics3d/collision/shapes/AABB.h | 54 +++++++++---------- .../collision/shapes/TriangleShape.h | 3 +- src/collision/Collider.cpp | 6 +-- src/collision/broadphase/DynamicAABBTree.cpp | 10 ++-- src/systems/BroadPhaseSystem.cpp | 7 ++- src/systems/CollisionDetectionSystem.cpp | 4 +- test/tests/collision/TestAABB.h | 33 +++++++++--- .../mathematics/TestMathematicsFunctions.h | 2 + 9 files changed, 70 insertions(+), 50 deletions(-) diff --git a/include/reactphysics3d/collision/RaycastInfo.h b/include/reactphysics3d/collision/RaycastInfo.h index 62b9aa93..21283abe 100644 --- a/include/reactphysics3d/collision/RaycastInfo.h +++ b/include/reactphysics3d/collision/RaycastInfo.h @@ -137,6 +137,7 @@ struct RaycastTest { /// Constructor RaycastTest(RaycastCallback* callback) { userCallback = callback; + } /// Ray cast test against a collider diff --git a/include/reactphysics3d/collision/shapes/AABB.h b/include/reactphysics3d/collision/shapes/AABB.h index 66bed556..11dac195 100644 --- a/include/reactphysics3d/collision/shapes/AABB.h +++ b/include/reactphysics3d/collision/shapes/AABB.h @@ -110,7 +110,7 @@ class AABB { bool testCollisionTriangleAABB(const Vector3* trianglePoints) const; /// Return true if the ray intersects the AABB - bool testRayIntersect(const Ray& ray) const; + bool testRayIntersect(const Vector3& rayOrigin, const Vector3& rayDirectionInv, decimal rayMaxFraction) const; /// Apply a scale factor to the AABB void applyScale(const Vector3& scale); @@ -279,38 +279,36 @@ RP3D_FORCE_INLINE AABB AABB::createAABBForTriangle(const Vector3* trianglePoints } // Return true if the ray intersects the AABB -/// This method use the line vs AABB raycasting technique (SAT algorithm) described in -/// Real-time Collision Detection by Christer Ericson. -RP3D_FORCE_INLINE bool AABB::testRayIntersect(const Ray& ray) const { +RP3D_FORCE_INLINE bool AABB::testRayIntersect(const Vector3& rayOrigin, const Vector3& rayDirectionInverse, decimal rayMaxFraction) const { - const Vector3 point2 = ray.point1 + ray.maxFraction * (ray.point2 - ray.point1); - const Vector3 e = mMaxCoordinates - mMinCoordinates; - const Vector3 d = point2 - ray.point1; - const Vector3 m = ray.point1 + point2 - mMinCoordinates - mMaxCoordinates; + // This algorithm relies on the IEE floating point properties (division by zero). If the rayDirection is zero, rayDirectionInverse and + // therfore t1 and t2 will be +-INFINITY. If the i coordinate of the ray's origin is inside the AABB (mMinCoordinates[i] < rayOrigin[i] < mMaxCordinates[i)), we have + // t1 = -t2 = +- INFINITY. Since max(n, -INFINITY) = min(n, INFINITY) = n for all n, tMin and tMax will stay unchanged. Secondly, if the i + // coordinate of the ray's origin is outside the box (rayOrigin[i] < mMinCoordinates[i] or rayOrigin[i] > mMaxCoordinates[i]) we have + // t1 = t2 = +- INFINITY and therefore either tMin = +INFINITY or tMax = -INFINITY. One of those values will stay until the end and make the + // method to return false. Unfortunately, if the ray lies exactly on a slab (rayOrigin[i] = mMinCoordinates[i] or rayOrigin[i] = mMaxCoordinates[i]) we + // have t1 = (mMinCoordinates[i] - rayOrigin[i]) * rayDirectionInverse[i] = 0 * INFINITY = NaN which is a problem for the remaining of the algorithm. + // This will cause the method to return true when the ray is not intersecting the AABB and therefore cause to traverse more nodes than necessary in + // the BVH tree. Because this should be rare, it is not really a big issue. + // Reference: https://tavianator.com/2011/ray_box.html - // Test if the AABB face normals are separating axis - decimal adx = std::abs(d.x); - if (std::abs(m.x) > e.x + adx) return false; - decimal ady = std::abs(d.y); - if (std::abs(m.y) > e.y + ady) return false; - decimal adz = std::abs(d.z); - if (std::abs(m.z) > e.z + adz) return false; + decimal t1 = (mMinCoordinates[0] - rayOrigin[0]) * rayDirectionInverse[0]; + decimal t2 = (mMaxCoordinates[0] - rayOrigin[0]) * rayDirectionInverse[0]; - // Add in an epsilon term to counteract arithmetic errors when segment is - // (near) parallel to a coordinate axis - const decimal epsilon = 0.00001; - adx += epsilon; - ady += epsilon; - adz += epsilon; + decimal tMin = std::min(t1, t2); + decimal tMax = std::max(t1, t2); + tMax = std::min(tMax, rayMaxFraction); - // Test if the cross products between face normals and ray direction are - // separating axis - if (std::abs(m.y * d.z - m.z * d.y) > e.y * adz + e.z * ady) return false; - if (std::abs(m.z * d.x - m.x * d.z) > e.x * adz + e.z * adx) return false; - if (std::abs(m.x * d.y - m.y * d.x) > e.x * ady + e.y * adx) return false; + for (uint i = 1; i < 3; i++) { - // No separating axis has been found - return true; + t1 = (mMinCoordinates[i] - rayOrigin[i]) * rayDirectionInverse[i]; + t2 = (mMaxCoordinates[i] - rayOrigin[i]) * rayDirectionInverse[i]; + + tMin = std::max(tMin, std::min(t1, t2)); + tMax = std::min(tMax, std::max(t1, t2)); + } + + return tMax >= std::max(tMin, decimal(0.0)); } } diff --git a/include/reactphysics3d/collision/shapes/TriangleShape.h b/include/reactphysics3d/collision/shapes/TriangleShape.h index aa9a4003..7703a956 100644 --- a/include/reactphysics3d/collision/shapes/TriangleShape.h +++ b/include/reactphysics3d/collision/shapes/TriangleShape.h @@ -91,8 +91,7 @@ class TriangleShape : public ConvexPolyhedronShape { virtual bool testPointInside(const Vector3& localPoint, Collider* collider) const override; /// Raycast method with feedback information - virtual bool raycast(const Ray& ray, RaycastInfo& raycastInfo, Collider* collider, - MemoryAllocator& allocator) const override; + virtual bool raycast(const Ray& ray, RaycastInfo& raycastInfo, Collider* collider, MemoryAllocator& allocator) const override; /// Return the number of bytes used by the collision shape virtual size_t getSizeInBytes() const override; diff --git a/src/collision/Collider.cpp b/src/collision/Collider.cpp index a5948ce1..514f101b 100644 --- a/src/collision/Collider.cpp +++ b/src/collision/Collider.cpp @@ -167,7 +167,7 @@ const Transform& Collider::getLocalToBodyTransform() const { // Raycast method with feedback information /** - * @param ray Ray to use for the raycasting + * @param ray Ray to use for the raycasting in world-space * @param[out] raycastInfo Result of the raycasting that is valid only if the * methods returned true * @return True if the ray hits the collision shape @@ -180,9 +180,7 @@ bool Collider::raycast(const Ray& ray, RaycastInfo& raycastInfo) { // Convert the ray into the local-space of the collision shape const Transform localToWorldTransform = mBody->mWorld.mCollidersComponents.getLocalToWorldTransform(mEntity); const Transform worldToLocalTransform = localToWorldTransform.getInverse(); - Ray rayLocal(worldToLocalTransform * ray.point1, - worldToLocalTransform * ray.point2, - ray.maxFraction); + Ray rayLocal(worldToLocalTransform * ray.point1, worldToLocalTransform * ray.point2, ray.maxFraction); const CollisionShape* collisionShape = mBody->mWorld.mCollidersComponents.getCollisionShape(mEntity); bool isHit = collisionShape->raycast(rayLocal, raycastInfo, this, mMemoryManager.getPoolAllocator()); diff --git a/src/collision/broadphase/DynamicAABBTree.cpp b/src/collision/broadphase/DynamicAABBTree.cpp index 651def8a..e01fbe16 100644 --- a/src/collision/broadphase/DynamicAABBTree.cpp +++ b/src/collision/broadphase/DynamicAABBTree.cpp @@ -675,6 +675,10 @@ void DynamicAABBTree::raycast(const Ray& ray, DynamicAABBTreeRaycastCallback& ca decimal maxFraction = ray.maxFraction; + // Compute the inverse ray direction + const Vector3 rayDirection = ray.point2 - ray.point1; + const Vector3 rayDirectionInverse(decimal(1.0) / rayDirection.x, decimal(1.0) / rayDirection.y, decimal(1.0) / rayDirection.z); + Stack stack(mAllocator, 128); stack.push(mRootNodeID); @@ -691,14 +695,14 @@ void DynamicAABBTree::raycast(const Ray& ray, DynamicAABBTreeRaycastCallback& ca // Get the corresponding node const TreeNode* node = mNodes + nodeID; - Ray rayTemp(ray.point1, ray.point2, maxFraction); - // Test if the ray intersects with the current node AABB - if (!node->aabb.testRayIntersect(rayTemp)) continue; + if (!node->aabb.testRayIntersect(ray.point1, rayDirectionInverse, maxFraction)) continue; // If the node is a leaf of the tree if (node->isLeaf()) { + Ray rayTemp(ray.point1, ray.point2, maxFraction); + // Call the callback that will raycast again the broad-phase shape decimal hitFraction = callback.raycastBroadPhaseShape(nodeID, rayTemp); diff --git a/src/systems/BroadPhaseSystem.cpp b/src/systems/BroadPhaseSystem.cpp index a66a760b..678c99ec 100644 --- a/src/systems/BroadPhaseSystem.cpp +++ b/src/systems/BroadPhaseSystem.cpp @@ -67,13 +67,16 @@ bool BroadPhaseSystem::testOverlappingShapes(int32 shape1BroadPhaseId, int32 sha } // Ray casting method -void BroadPhaseSystem::raycast(const Ray& ray, RaycastTest& raycastTest, - unsigned short raycastWithCategoryMaskBits) const { +void BroadPhaseSystem::raycast(const Ray& ray, RaycastTest& raycastTest, unsigned short raycastWithCategoryMaskBits) const { RP3D_PROFILE("BroadPhaseSystem::raycast()", mProfiler); BroadPhaseRaycastCallback broadPhaseRaycastCallback(mDynamicAABBTree, raycastWithCategoryMaskBits, raycastTest); + // Compute the inverse ray direction + const Vector3 rayDirection = ray.point2 - ray.point1; + const Vector3 rayDirectionInverse(decimal(1.0) / rayDirection.x, decimal(1.0) / rayDirection.y, decimal(1.0) / rayDirection.z); + mDynamicAABBTree.raycast(ray, broadPhaseRaycastCallback); } diff --git a/src/systems/CollisionDetectionSystem.cpp b/src/systems/CollisionDetectionSystem.cpp index 3cb40a27..2124f812 100644 --- a/src/systems/CollisionDetectionSystem.cpp +++ b/src/systems/CollisionDetectionSystem.cpp @@ -1104,9 +1104,7 @@ void CollisionDetectionSystem::removeCollider(Collider* collider) { } // Ray casting method -void CollisionDetectionSystem::raycast(RaycastCallback* raycastCallback, - const Ray& ray, - unsigned short raycastWithCategoryMaskBits) const { +void CollisionDetectionSystem::raycast(RaycastCallback* raycastCallback, const Ray& ray, unsigned short raycastWithCategoryMaskBits) const { RP3D_PROFILE("CollisionDetectionSystem::raycast()", mProfiler); diff --git a/test/tests/collision/TestAABB.h b/test/tests/collision/TestAABB.h index d51972dd..2383d87e 100644 --- a/test/tests/collision/TestAABB.h +++ b/test/tests/collision/TestAABB.h @@ -286,14 +286,31 @@ class TestAABB : public Test { Ray ray7(Vector3(-4, 6, -100), Vector3(-4, 6, -11), 0.6); Ray ray8(Vector3(-403, -432, -100), Vector3(134, 643, 23)); - rp3d_test(mAABB1.testRayIntersect(ray1)); - rp3d_test(!mAABB1.testRayIntersect(ray2)); - rp3d_test(mAABB1.testRayIntersect(ray3)); - rp3d_test(mAABB1.testRayIntersect(ray4)); - rp3d_test(mAABB1.testRayIntersect(ray5)); - rp3d_test(mAABB1.testRayIntersect(ray6)); - rp3d_test(!mAABB1.testRayIntersect(ray7)); - rp3d_test(!mAABB1.testRayIntersect(ray8)); + const Vector3 ray1Direction = ray1.point2 - ray1.point1; + const Vector3 ray1DirectionInv(decimal(1.0) / ray1Direction.x, decimal(1.0) / ray1Direction.y, decimal(1.0) / ray1Direction.z); + const Vector3 ray2Direction = ray2.point2 - ray2.point1; + const Vector3 ray2DirectionInv(decimal(1.0) / ray2Direction.x, decimal(1.0) / ray2Direction.y, decimal(1.0) / ray2Direction.z); + const Vector3 ray3Direction = ray3.point2 - ray3.point1; + const Vector3 ray3DirectionInv(decimal(1.0) / ray3Direction.x, decimal(1.0) / ray3Direction.y, decimal(1.0) / ray3Direction.z); + const Vector3 ray4Direction = ray4.point2 - ray4.point1; + const Vector3 ray4DirectionInv(decimal(1.0) / ray4Direction.x, decimal(1.0) / ray4Direction.y, decimal(1.0) / ray4Direction.z); + const Vector3 ray5Direction = ray5.point2 - ray5.point1; + const Vector3 ray5DirectionInv(decimal(1.0) / ray5Direction.x, decimal(1.0) / ray5Direction.y, decimal(1.0) / ray5Direction.z); + const Vector3 ray6Direction = ray6.point2 - ray6.point1; + const Vector3 ray6DirectionInv(decimal(1.0) / ray6Direction.x, decimal(1.0) / ray6Direction.y, decimal(1.0) / ray6Direction.z); + const Vector3 ray7Direction = ray7.point2 - ray7.point1; + const Vector3 ray7DirectionInv(decimal(1.0) / ray7Direction.x, decimal(1.0) / ray7Direction.y, decimal(1.0) / ray7Direction.z); + const Vector3 ray8Direction = ray8.point2 - ray8.point1; + const Vector3 ray8DirectionInv(decimal(1.0) / ray8Direction.x, decimal(1.0) / ray8Direction.y, decimal(1.0) / ray8Direction.z); + + rp3d_test(mAABB1.testRayIntersect(ray1.point1, ray1DirectionInv, decimal(1.0))); + rp3d_test(!mAABB1.testRayIntersect(ray2.point1, ray2DirectionInv, decimal(1.0))); + rp3d_test(mAABB1.testRayIntersect(ray3.point1, ray3DirectionInv, decimal(1.0))); + rp3d_test(mAABB1.testRayIntersect(ray4.point1, ray4DirectionInv, decimal(1.0))); + rp3d_test(mAABB1.testRayIntersect(ray5.point1, ray5DirectionInv, decimal(1.0))); + rp3d_test(mAABB1.testRayIntersect(ray6.point1, ray6DirectionInv, decimal(1.0))); + rp3d_test(!mAABB1.testRayIntersect(ray7.point1, ray7DirectionInv, decimal(1.0))); + rp3d_test(!mAABB1.testRayIntersect(ray8.point1, ray8DirectionInv, decimal(1.0))); } }; diff --git a/test/tests/mathematics/TestMathematicsFunctions.h b/test/tests/mathematics/TestMathematicsFunctions.h index 77c4ee52..7bd92432 100644 --- a/test/tests/mathematics/TestMathematicsFunctions.h +++ b/test/tests/mathematics/TestMathematicsFunctions.h @@ -235,6 +235,7 @@ class TestMathematicsFunctions : public Test { clipSegmentVertices = clipSegmentWithPlanes(segmentVertices[0], segmentVertices[1], planesPoints, planesNormals, mAllocator); rp3d_test(clipSegmentVertices.size() == 0); + /* TODO : UNCOMMENT THIS // Test clipPolygonWithPlanes() Array polygonVertices(mAllocator); polygonVertices.add(Vector3(-4, 2, 0)); @@ -268,6 +269,7 @@ class TestMathematicsFunctions : public Test { rp3d_test(approxEqual(clipPolygonVertices[3].x, 0, 0.000001)); rp3d_test(approxEqual(clipPolygonVertices[3].y, 4, 0.000001)); rp3d_test(approxEqual(clipPolygonVertices[3].z, 0, 0.000001)); + */ // Test isPowerOfTwo() rp3d_test(!isPowerOfTwo(0)); From 264705056e95a6d9e5b935af30a8f387132eccf1 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Mon, 9 Nov 2020 22:47:17 +0100 Subject: [PATCH 63/74] =?UTF-8?q?Fix=20issue=20when=20building=20the=20tes?= =?UTF-8?q?tbed=20application=20on=20MacOS=C2=A0X?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- testbed/CMakeLists.txt | 1 + testbed/src/TestbedApplication.cpp | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/testbed/CMakeLists.txt b/testbed/CMakeLists.txt index ffb813c5..80b3a0c3 100755 --- a/testbed/CMakeLists.txt +++ b/testbed/CMakeLists.txt @@ -8,6 +8,7 @@ project(Testbed) set(NANOGUI_BUILD_EXAMPLES OFF CACHE BOOL " " FORCE) set(NANOGUI_BUILD_PYTHON OFF CACHE BOOL " " FORCE) set(NANOGUI_INSTALL OFF CACHE BOOL " " FORCE) +set(NANOGUI_BACKEND OpenGL CACHE BOOL " " FORCE) # ---- Make sure to recursively clone all the git submodules for external libraries (nanogui) --- # find_package(Git QUIET) diff --git a/testbed/src/TestbedApplication.cpp b/testbed/src/TestbedApplication.cpp index 34ddab7f..49c5d000 100755 --- a/testbed/src/TestbedApplication.cpp +++ b/testbed/src/TestbedApplication.cpp @@ -95,12 +95,15 @@ void TestbedApplication::init() { glGetIntegerv(GL_MAJOR_VERSION, &glMajorVersion); glGetIntegerv(GL_MINOR_VERSION, &glMinorVersion); +#ifdef GL_DEBUG_OUTPUT + if (glMajorVersion > 4 || (glMajorVersion == 4 && glMinorVersion >= 3)) { // Enable OpenGL error reporting glEnable(GL_DEBUG_OUTPUT); glDebugMessageCallback(onOpenGLError, 0); } +#endif mIsInitialized = true; } @@ -307,6 +310,7 @@ void TestbedApplication::notifyEngineSetttingsChanged() { void GLAPIENTRY TestbedApplication::onOpenGLError(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam ) { +#ifdef GL_DEBUG_OUTPUT if (type == GL_DEBUG_TYPE_ERROR) { /* fprintf( stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n", @@ -314,6 +318,8 @@ void GLAPIENTRY TestbedApplication::onOpenGLError(GLenum source, GLenum type, GL type, severity, message ); */ } +#endif + } // Compute the FPS From d4a58b722841cf17c45650d170182bbea67e135c Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Sun, 15 Nov 2020 11:56:44 +0100 Subject: [PATCH 64/74] Do not recompute triangle normal during triangle raycasting --- src/collision/shapes/TriangleShape.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/collision/shapes/TriangleShape.cpp b/src/collision/shapes/TriangleShape.cpp index f2150e05..2a4bb869 100644 --- a/src/collision/shapes/TriangleShape.cpp +++ b/src/collision/shapes/TriangleShape.cpp @@ -182,8 +182,7 @@ bool TriangleShape::raycast(const Ray& ray, RaycastInfo& raycastInfo, Collider* if (hitFraction < decimal(0.0) || hitFraction > ray.maxFraction) return false; - Vector3 localHitNormal = (mPoints[1] - mPoints[0]).cross(mPoints[2] - mPoints[0]); - if (localHitNormal.dot(pq) > decimal(0.0)) localHitNormal = -localHitNormal; + Vector3 localHitNormal = mNormal.dot(pq) > decimal(0.0) ? mNormal : -mNormal; raycastInfo.body = collider->getBody(); raycastInfo.collider = collider; From cc63f7c0a10b61a16beff33d340650697da08355 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Wed, 25 Nov 2020 22:20:40 +0100 Subject: [PATCH 65/74] Fix issue 179 in FixedJoint --- src/systems/SolveFixedJointSystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/SolveFixedJointSystem.cpp b/src/systems/SolveFixedJointSystem.cpp index 7143bb43..42f33a5e 100644 --- a/src/systems/SolveFixedJointSystem.cpp +++ b/src/systems/SolveFixedJointSystem.cpp @@ -454,7 +454,7 @@ void SolveFixedJointSystem::solvePositionConstraint() { // Compute the pseudo velocity of body 1 const Vector3 v1 = inverseMassBody1 * linearImpulseBody1; - Vector3 w1 = mFixedJointComponents.mI2[i] * angularImpulseBody1; + Vector3 w1 = mFixedJointComponents.mI1[i] * angularImpulseBody1; // Update the body position/orientation of body 1 x1 += v1; From a4a386f82313bf77a4d6692652d1b803a0dfc3c5 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Thu, 26 Nov 2020 22:08:15 +0100 Subject: [PATCH 66/74] Fix issue in FixedJointComponents --- src/components/FixedJointComponents.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/FixedJointComponents.cpp b/src/components/FixedJointComponents.cpp index d7c8c129..4ae8552a 100644 --- a/src/components/FixedJointComponents.cpp +++ b/src/components/FixedJointComponents.cpp @@ -163,7 +163,7 @@ void FixedJointComponents::moveComponentToIndex(uint32 srcIndex, uint32 destInde new (mR2World + destIndex) Vector3(mR2World[srcIndex]); new (mI1 + destIndex) Matrix3x3(mI1[srcIndex]); new (mI2 + destIndex) Matrix3x3(mI2[srcIndex]); - new (mImpulseTranslation + destIndex) Vector3(mImpulseRotation[srcIndex]); + new (mImpulseTranslation + destIndex) Vector3(mImpulseTranslation[srcIndex]); new (mImpulseRotation + destIndex) Vector3(mImpulseRotation[srcIndex]); new (mInverseMassMatrixTranslation + destIndex) Matrix3x3(mInverseMassMatrixTranslation[srcIndex]); new (mInverseMassMatrixRotation + destIndex) Matrix3x3(mInverseMassMatrixRotation[srcIndex]); From 0fd8bbbacb16897c7915b0ef02a54bf6534fa375 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Thu, 26 Nov 2020 22:55:15 +0100 Subject: [PATCH 67/74] Edit changelog file --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0771ca3..967366a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ do not hesitate to take a look at the user manual. ### Fixed - Issue [#165](https://github.com/DanielChappuis/reactphysics3d/issues/165) with order of contact manifolds in islands creation has been fixed +- Issue [#179](https://github.com/DanielChappuis/reactphysics3d/issues/179) with FixedJoint constraint - Issue with concave vs convex shape collision detection has been fixed - Issue with edge vs edge collision has been fixed in SAT algorithm (wrong contact normal was computed) From 23abaa905dff0dfb0672ac2e7c2e472135e44bab Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Sat, 28 Nov 2020 19:45:38 +0100 Subject: [PATCH 68/74] Add the HingeJoint::getAngle() method to get the current angle of the hinge joint --- .../reactphysics3d/constraint/HingeJoint.h | 3 +++ .../systems/ConstraintSolverSystem.h | 3 +++ .../systems/SolveHingeJointSystem.h | 7 ++++-- src/constraint/HingeJoint.cpp | 23 ++++++++++++++++--- 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/include/reactphysics3d/constraint/HingeJoint.h b/include/reactphysics3d/constraint/HingeJoint.h index 10b0c590..f673b3af 100644 --- a/include/reactphysics3d/constraint/HingeJoint.h +++ b/include/reactphysics3d/constraint/HingeJoint.h @@ -210,6 +210,9 @@ class HingeJoint : public Joint { /// Return the intensity of the current torque applied for the joint motor decimal getMotorTorque(decimal timeStep) const; + /// Return the current hinge angle + decimal getAngle() const; + /// Return a string representation virtual std::string to_string() const override; }; diff --git a/include/reactphysics3d/systems/ConstraintSolverSystem.h b/include/reactphysics3d/systems/ConstraintSolverSystem.h index 54021aaa..9d574e0e 100644 --- a/include/reactphysics3d/systems/ConstraintSolverSystem.h +++ b/include/reactphysics3d/systems/ConstraintSolverSystem.h @@ -216,6 +216,9 @@ class ConstraintSolverSystem { #endif + // ---------- Friendship ---------- + + friend class HingeJoint; }; #ifdef IS_RP3D_PROFILING_ENABLED diff --git a/include/reactphysics3d/systems/SolveHingeJointSystem.h b/include/reactphysics3d/systems/SolveHingeJointSystem.h index 05e5f059..aa5342e4 100644 --- a/include/reactphysics3d/systems/SolveHingeJointSystem.h +++ b/include/reactphysics3d/systems/SolveHingeJointSystem.h @@ -92,8 +92,7 @@ class SolveHingeJointSystem { decimal upperLimitAngle) const; /// Compute the current angle around the hinge axis - decimal computeCurrentHingeAngle(Entity jointEntity, const Quaternion& orientationBody1, - const Quaternion& orientationBody2); + decimal computeCurrentHingeAngle(Entity jointEntity, const Quaternion& orientationBody1, const Quaternion& orientationBody2); public : @@ -133,6 +132,10 @@ class SolveHingeJointSystem { #endif + // ---------- Friendship ---------- + + friend class HingeJoint; + }; #ifdef IS_RP3D_PROFILING_ENABLED diff --git a/src/constraint/HingeJoint.cpp b/src/constraint/HingeJoint.cpp index f6523908..5f18c79c 100644 --- a/src/constraint/HingeJoint.cpp +++ b/src/constraint/HingeJoint.cpp @@ -207,7 +207,7 @@ decimal HingeJoint::getMaxAngleLimit() const { /** * @return The current speed of the joint motor (in radian per second) */ - decimal HingeJoint::getMotorSpeed() const { +decimal HingeJoint::getMotorSpeed() const { return mWorld.mHingeJointsComponents.getMotorSpeed(mEntity); } @@ -215,7 +215,7 @@ decimal HingeJoint::getMaxAngleLimit() const { /** * @return The maximum torque of the joint motor (in Newtons) */ - decimal HingeJoint::getMaxMotorTorque() const { +decimal HingeJoint::getMaxMotorTorque() const { return mWorld.mHingeJointsComponents.getMaxMotorTorque(mEntity); } @@ -224,10 +224,27 @@ decimal HingeJoint::getMaxAngleLimit() const { * @param timeStep The current time step (in seconds) * @return The intensity of the current torque (in Newtons) of the joint motor */ - decimal HingeJoint::getMotorTorque(decimal timeStep) const { +decimal HingeJoint::getMotorTorque(decimal timeStep) const { return mWorld.mHingeJointsComponents.getImpulseMotor(mEntity) / timeStep; } +// Return the current hinge angle +/** + * @return The current hinge angle (in radians) + */ +decimal HingeJoint::getAngle() const { + + // Get the bodies entities + const Entity body1Entity = mWorld.mJointsComponents.getBody1Entity(mEntity); + const Entity body2Entity = mWorld.mJointsComponents.getBody2Entity(mEntity); + + const Quaternion& orientationBody1 = mWorld.mTransformComponents.getTransform(body1Entity).getOrientation(); + const Quaternion& orientationBody2 = mWorld.mTransformComponents.getTransform(body2Entity).getOrientation(); + + // Compute the current angle around the hinge axis + return mWorld.mConstraintSolverSystem.mSolveHingeJointSystem.computeCurrentHingeAngle(mEntity, orientationBody1, orientationBody2); +} + // Return the number of bytes used by the joint size_t HingeJoint::getSizeInBytes() const { return sizeof(HingeJoint); From 161796fc9b1fbad4245c478d5417a50887349918 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Sat, 28 Nov 2020 20:43:50 +0100 Subject: [PATCH 69/74] Edit changelog file --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 967366a5..1ac59c33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,11 @@ do not hesitate to take a look at the user manual. ### Added - - Method RigidBody::resetForce() to reset the accumulated external force on a rigid body has beend added - - Method RigidBody::resetTorque() to reset the accumulated external torque on a rigid body has beend added + - Method RigidBody::resetForce() to reset the accumulated external force on a rigid body has been added + - Method RigidBody::resetTorque() to reset the accumulated external torque on a rigid body has been added - Constructors with local-space anchor/axis have been added to BallAndSocketJointInfo, HingeJointInfo, FixedJointInfo and SliderJointInfo classes - Robustness of polyhedron vs polyhedron collision detection has been improved in SAT algorithm (face contacts are favored over edge-edge contacts for better stability) + - Method HingeJoint::getAngle() to get the current angle of the hinge joint has been added ### Changed From 5443f0bd54f9471e5b8bffc69dfd91fd93bf620f Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Mon, 28 Dec 2020 00:04:23 +0100 Subject: [PATCH 70/74] Fix wrong normal orientation in TriangleShape raycasting --- src/collision/shapes/TriangleShape.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/collision/shapes/TriangleShape.cpp b/src/collision/shapes/TriangleShape.cpp index 2a4bb869..abafb92c 100644 --- a/src/collision/shapes/TriangleShape.cpp +++ b/src/collision/shapes/TriangleShape.cpp @@ -182,7 +182,7 @@ bool TriangleShape::raycast(const Ray& ray, RaycastInfo& raycastInfo, Collider* if (hitFraction < decimal(0.0) || hitFraction > ray.maxFraction) return false; - Vector3 localHitNormal = mNormal.dot(pq) > decimal(0.0) ? mNormal : -mNormal; + Vector3 localHitNormal = mNormal.dot(pq) > decimal(0.0) ? -mNormal : mNormal; raycastInfo.body = collider->getBody(); raycastInfo.collider = collider; From d0fa4c2755da4042d7794d55ad70992cf949fd6b Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Mon, 28 Dec 2020 00:07:08 +0100 Subject: [PATCH 71/74] Improve the raycasting performance against HeightFielfShape with a better middle-phase algorithm --- .../reactphysics3d/collision/shapes/AABB.h | 50 +++++ .../collision/shapes/HeightFieldShape.h | 7 + src/collision/shapes/HeightFieldShape.cpp | 197 ++++++++++++++++-- 3 files changed, 242 insertions(+), 12 deletions(-) diff --git a/include/reactphysics3d/collision/shapes/AABB.h b/include/reactphysics3d/collision/shapes/AABB.h index 11dac195..a4d9f921 100644 --- a/include/reactphysics3d/collision/shapes/AABB.h +++ b/include/reactphysics3d/collision/shapes/AABB.h @@ -112,6 +112,9 @@ class AABB { /// Return true if the ray intersects the AABB bool testRayIntersect(const Vector3& rayOrigin, const Vector3& rayDirectionInv, decimal rayMaxFraction) const; + /// Compute the intersection of a ray and the AABB + bool raycast(const Ray& ray, Vector3& hitPoint) const; + /// Apply a scale factor to the AABB void applyScale(const Vector3& scale); @@ -311,6 +314,53 @@ RP3D_FORCE_INLINE bool AABB::testRayIntersect(const Vector3& rayOrigin, const Ve return tMax >= std::max(tMin, decimal(0.0)); } +// Compute the intersection of a ray and the AABB +RP3D_FORCE_INLINE bool AABB::raycast(const Ray& ray, Vector3& hitPoint) const { + + decimal tMin = decimal(0.0); + decimal tMax = DECIMAL_LARGEST; + + const decimal epsilon = 0.00001; + + const Vector3 rayDirection = ray.point2 - ray.point1; + + // For all three slabs + for (int i=0; i < 3; i++) { + + // If the ray is parallel to the slab + if (std::abs(rayDirection[i]) < epsilon) { + + // If origin of the ray is not inside the slab, no hit + if (ray.point1[i] < mMinCoordinates[i] || ray.point1[i] > mMaxCoordinates[i]) return false; + } + else { + + decimal rayDirectionInverse = decimal(1.0) / rayDirection[i]; + decimal t1 = (mMinCoordinates[i] - ray.point1[i]) * rayDirectionInverse; + decimal t2 = (mMaxCoordinates[i] - ray.point1[i]) * rayDirectionInverse; + + if (t1 > t2) { + + // Swap t1 and t2 + decimal tTemp = t2; + t2 = t1; + t1 = tTemp; + } + + tMin = std::max(tMin, t1); + tMax = std::min(tMax, t2); + + // Exit with no collision + if (tMin > tMax) return false; + } + } + + // Compute the hit point + hitPoint = ray.point1 + tMin * rayDirection; + + return true; +} + } #endif diff --git a/include/reactphysics3d/collision/shapes/HeightFieldShape.h b/include/reactphysics3d/collision/shapes/HeightFieldShape.h index efa2625e..f706fc8d 100644 --- a/include/reactphysics3d/collision/shapes/HeightFieldShape.h +++ b/include/reactphysics3d/collision/shapes/HeightFieldShape.h @@ -102,6 +102,10 @@ class HeightFieldShape : public ConcaveShape { HalfEdgeStructure& triangleHalfEdgeStructure, int upAxis = 1, decimal integerHeightScale = 1.0f, const Vector3& scaling = Vector3(1,1,1)); + /// Raycast a single triangle of the height-field + bool raycastTriangle(const Ray& ray, const Vector3& p1, const Vector3& p2, const Vector3& p3, uint shapeId, + Collider *collider, RaycastInfo& raycastInfo, decimal &smallestHitFraction, MemoryAllocator& allocator) const; + /// Raycast method with feedback information virtual bool raycast(const Ray& ray, RaycastInfo& raycastInfo, Collider* collider, MemoryAllocator& allocator) const override; @@ -125,6 +129,9 @@ class HeightFieldShape : public ConcaveShape { /// Compute the shape Id for a given triangle uint computeTriangleShapeId(uint iIndex, uint jIndex, uint secondTriangleIncrement) const; + /// Compute the first grid cell of the heightfield intersected by a ray + bool computeEnteringRayGridCoordinates(const Ray& ray, int& i, int& j, Vector3& outHitPoint) const; + /// Destructor virtual ~HeightFieldShape() override = default; diff --git a/src/collision/shapes/HeightFieldShape.cpp b/src/collision/shapes/HeightFieldShape.cpp index f3693fc1..9ac5f03b 100644 --- a/src/collision/shapes/HeightFieldShape.cpp +++ b/src/collision/shapes/HeightFieldShape.cpp @@ -27,6 +27,7 @@ #include #include #include +#include using namespace reactphysics3d; @@ -232,24 +233,98 @@ bool HeightFieldShape::raycast(const Ray& ray, RaycastInfo& raycastInfo, Collide RP3D_PROFILE("HeightFieldShape::raycast()", mProfiler); - // Compute the AABB for the ray - const Vector3 rayEnd = ray.point1 + ray.maxFraction * (ray.point2 - ray.point1); - const AABB rayAABB(Vector3::min(ray.point1, rayEnd), Vector3::max(ray.point1, rayEnd)); + // Apply the concave mesh inverse scale factor because the mesh is stored without scaling + // inside the dynamic AABB tree + const Vector3 inverseScale(decimal(1.0) / mScale.x, decimal(1.0) / mScale.y, decimal(1.0) / mScale.z); + Ray scaledRay(ray.point1 * inverseScale, ray.point2 * inverseScale, ray.maxFraction); - // Compute the triangles overlapping with the ray AABB - Array triangleVertices(allocator, 64); - Array triangleVerticesNormals(allocator, 64); - Array shapeIds(allocator, 64); - computeOverlappingTriangles(rayAABB, triangleVertices, triangleVerticesNormals, shapeIds, allocator); + // Compute the grid coordinates where the ray is entering the AABB of the height field + int i, j; + Vector3 outHitGridPoint; + bool isIntersecting = computeEnteringRayGridCoordinates(scaledRay, i, j, outHitGridPoint); + assert(isIntersecting); - assert(triangleVertices.size() == triangleVerticesNormals.size()); - assert(shapeIds.size() == triangleVertices.size() / 3); - assert(triangleVertices.size() % 3 == 0); - assert(triangleVerticesNormals.size() % 3 == 0); + const int nbCellsI = mNbColumns - 1; + const int nbCellsJ = mNbRows - 1; + + const Vector3 aabbSize = mAABB.getExtent(); + + const Vector3 rayDirection = scaledRay.point2 - scaledRay.point1; + + int stepI, stepJ; + decimal tMaxI, tMaxJ, nextI, nextJ, tDeltaI, tDeltaJ, sizeI, sizeJ; + + switch(mUpAxis) { + case 0 : stepI = rayDirection.y > 0 ? 1 : (rayDirection.y < 0 ? -1 : 0); + stepJ = rayDirection.z > 0 ? 1 : (rayDirection.z < 0 ? -1 : 0); + nextI = stepI >= 0 ? i + 1 : i; + nextJ = stepJ >= 0 ? j + 1 : j; + sizeI = aabbSize.y / nbCellsI; + sizeJ = aabbSize.z / nbCellsJ; + tMaxI = ((nextI * sizeI) - outHitGridPoint.y) / rayDirection.y; + tMaxJ = ((nextJ * sizeJ) - outHitGridPoint.z) / rayDirection.z; + tDeltaI = sizeI / std::abs(rayDirection.y); + tDeltaJ = sizeJ / std::abs(rayDirection.z); + break; + case 1 : stepI = rayDirection.x > 0 ? 1 : (rayDirection.x < 0 ? -1 : 0); + stepJ = rayDirection.z > 0 ? 1 : (rayDirection.z < 0 ? -1 : 0); + nextI = stepI >= 0 ? i + 1 : i; + nextJ = stepJ >= 0 ? j + 1 : j; + sizeI = aabbSize.x / nbCellsI; + sizeJ = aabbSize.z / nbCellsJ; + tMaxI = ((nextI * sizeI) - outHitGridPoint.x) / rayDirection.x; + tMaxJ = ((nextJ * sizeJ) - outHitGridPoint.z) / rayDirection.z; + tDeltaI = sizeI / std::abs(rayDirection.x); + tDeltaJ = sizeJ / std::abs(rayDirection.z); + break; + case 2 : stepI = rayDirection.x > 0 ? 1 : (rayDirection.x < 0 ? -1 : 0); + stepJ = rayDirection.y > 0 ? 1 : (rayDirection.y < 0 ? -1 : 0); + nextI = stepI >= 0 ? i + 1 : i; + nextJ = stepJ >= 0 ? j + 1 : j; + sizeI = aabbSize.x / nbCellsI; + sizeJ = aabbSize.y / nbCellsJ; + tMaxI = ((nextI * sizeI) - outHitGridPoint.x) / rayDirection.x; + tMaxJ = ((nextJ * sizeJ) - outHitGridPoint.y) / rayDirection.y; + tDeltaI = sizeI / std::abs(rayDirection.x); + tDeltaJ = sizeJ / std::abs(rayDirection.y); + break; + } bool isHit = false; decimal smallestHitFraction = ray.maxFraction; + while (i >= 0 && i < nbCellsI && j >= 0 && j < nbCellsJ) { + + // TODO : Remove this + //std::cout << "Cell " << i << ", " << j << std::endl; + + // Compute the four point of the current quad + const Vector3 p1 = getVertexAt(i, j); + const Vector3 p2 = getVertexAt(i, j + 1); + const Vector3 p3 = getVertexAt(i + 1, j); + const Vector3 p4 = getVertexAt(i + 1, j + 1); + + // Raycast against the first triangle of the cell + uint shapeId = computeTriangleShapeId(i, j, 0); + isHit |= raycastTriangle(ray, p1, p2, p3, shapeId, collider, raycastInfo, smallestHitFraction, allocator); + + // Raycast against the second triangle of the cell + shapeId = computeTriangleShapeId(i, j, 1); + isHit |= raycastTriangle(ray, p3, p2, p4, shapeId, collider, raycastInfo, smallestHitFraction, allocator); + + if (stepI == 0 && stepJ == 0) break; + + if (tMaxI < tMaxJ) { + tMaxI += tDeltaI; + i += stepI; + } + else { + tMaxJ += tDeltaJ; + j += stepJ; + } + } + + /* // For each overlapping triangle const uint32 nbShapeIds = shapeIds.size(); for (uint32 i=0; i < nbShapeIds; i++) @@ -287,10 +362,108 @@ bool HeightFieldShape::raycast(const Ray& ray, RaycastInfo& raycastInfo, Collide isHit = true; } } + */ return isHit; } +// Raycast a single triangle of the height-field +bool HeightFieldShape::raycastTriangle(const Ray& ray, const Vector3& p1, const Vector3& p2, const Vector3& p3, uint shapeId, + Collider* collider, RaycastInfo& raycastInfo, decimal& smallestHitFraction, MemoryAllocator& allocator) const { + + // Generate the first triangle for the current grid rectangle + Vector3 triangleVertices[3] = {p1, p2, p3}; + + // Compute the triangle normal + Vector3 triangleNormal = (p2 - p1).cross(p3 - p1).getUnit(); + + // Use the triangle face normal as vertices normals (this is an aproximation. The correct + // solution would be to compute all the normals of the neighbor triangles and use their + // weighted average (with incident angle as weight) at the vertices. However, this solution + // seems too expensive (it requires to compute the normal of all neighbor triangles instead + // and compute the angle of incident edges with asin(). Maybe we could also precompute the + // vertices normal at the HeightFieldShape constructor but it will require extra memory to + // store them. + Vector3 triangleVerticesNormals[3] = {triangleNormal, triangleNormal, triangleNormal}; + + // Create a triangle collision shape + TriangleShape triangleShape(triangleVertices, triangleVerticesNormals, shapeId, mTriangleHalfEdgeStructure, allocator); + triangleShape.setRaycastTestType(getRaycastTestType()); + +#ifdef IS_RP3D_PROFILING_ENABLED + + + // Set the profiler to the triangle shape + triangleShape.setProfiler(mProfiler); + +#endif + + // Ray casting test against the collision shape + RaycastInfo triangleRaycastInfo; + bool isTriangleHit = triangleShape.raycast(ray, triangleRaycastInfo, collider, allocator); + + // If the ray hit the collision shape + if (isTriangleHit && triangleRaycastInfo.hitFraction <= smallestHitFraction) { + + assert(triangleRaycastInfo.hitFraction >= decimal(0.0)); + + raycastInfo.body = triangleRaycastInfo.body; + raycastInfo.collider = triangleRaycastInfo.collider; + raycastInfo.hitFraction = triangleRaycastInfo.hitFraction; + raycastInfo.worldPoint = triangleRaycastInfo.worldPoint; + raycastInfo.worldNormal = triangleRaycastInfo.worldNormal; + raycastInfo.meshSubpart = -1; + raycastInfo.triangleIndex = -1; + + smallestHitFraction = triangleRaycastInfo.hitFraction; + + return true; + } + + return false; +} + +// Compute the first grid cell of the heightfield intersected by a ray. +/// This method returns true if the ray hit the AABB of the height field and false otherwise +bool HeightFieldShape::computeEnteringRayGridCoordinates(const Ray& ray, int& i, int& j, Vector3& outHitGridPoint) const { + + decimal stepI, stepJ; + const Vector3 aabbSize = mAABB.getExtent(); + + const uint32 nbCellsI = mNbColumns - 1; + const uint32 nbCellsJ = mNbRows - 1; + + if (mAABB.raycast(ray, outHitGridPoint)) { + + // Map the hit point into the grid range [0, mNbColumns - 1], [0, mNbRows - 1] + outHitGridPoint -= mAABB.getMin(); + + switch(mUpAxis) { + case 0 : stepI = aabbSize.y / nbCellsI; + stepJ = aabbSize.z / nbCellsJ; + i = clamp(int(outHitGridPoint.y / stepI), 0, nbCellsI - 1); + j = clamp(int(outHitGridPoint.z / stepJ), 0, nbCellsJ - 1); + break; + case 1 : stepI = aabbSize.x / nbCellsI; + stepJ = aabbSize.z / nbCellsJ; + i = clamp(int(outHitGridPoint.x / stepI), 0, nbCellsI - 1); + j = clamp(int(outHitGridPoint.z / stepJ), 0, nbCellsJ - 1); + break; + case 2 : stepI = aabbSize.x / nbCellsI; + stepJ = aabbSize.y / nbCellsJ; + i = clamp(int(outHitGridPoint.x / stepI), 0, nbCellsI - 1); + j = clamp(int(outHitGridPoint.y / stepJ), 0, nbCellsJ - 1); + break; + } + + assert(i >= 0 && i < nbCellsI); + assert(j >= 0 && j < nbCellsJ); + return true; + } + + return false; +} + // Return the vertex (local-coordinates) of the height field at a given (x,y) position Vector3 HeightFieldShape::getVertexAt(int x, int y) const { From a8d2478a7ef5740469aba9bd7e4d411baab0e583 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Tue, 29 Dec 2020 23:32:37 +0100 Subject: [PATCH 72/74] Edit changelog file --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ac59c33..81ed4801 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ do not hesitate to take a look at the user manual. - The List class has been renamed to Array - The default number of iterations for the velocity solver is now 6 instead of 10 - The default number of iterations for the position solver is now 3 instead of 5 + - The raycasting broad-phase performance has been improved + - The raycasting performance against HeighFieldShape has been improved (better middle-phase algorithm) ### Removed From bacdf23f8ba4cb49a67b4c0ab3e82575f4ff5b85 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Thu, 31 Dec 2020 16:41:05 +0100 Subject: [PATCH 73/74] Fix issue and modifications in HeightFieldShape --- .../collision/shapes/TriangleShape.h | 6 + src/collision/shapes/HeightFieldShape.cpp | 203 +++++++----------- src/collision/shapes/TriangleShape.cpp | 37 +++- 3 files changed, 108 insertions(+), 138 deletions(-) diff --git a/include/reactphysics3d/collision/shapes/TriangleShape.h b/include/reactphysics3d/collision/shapes/TriangleShape.h index 7703a956..e2e783d5 100644 --- a/include/reactphysics3d/collision/shapes/TriangleShape.h +++ b/include/reactphysics3d/collision/shapes/TriangleShape.h @@ -110,6 +110,9 @@ class TriangleShape : public ConvexPolyhedronShape { TriangleShape(const Vector3* vertices, const Vector3* verticesNormals, uint shapeId, HalfEdgeStructure& triangleHalfEdgeStructure, MemoryAllocator& allocator); + /// Constructor + TriangleShape(const Vector3* vertices, uint shapeId, HalfEdgeStructure& triangleHalfEdgeStructure, MemoryAllocator& allocator); + /// Destructor virtual ~TriangleShape() override = default; @@ -262,6 +265,7 @@ RP3D_FORCE_INLINE Vector3 TriangleShape::getVertexPosition(uint vertexIndex) con // Return the normal vector of a given face of the polyhedron RP3D_FORCE_INLINE Vector3 TriangleShape::getFaceNormal(uint faceIndex) const { assert(faceIndex < 2); + assert(mNormal.length() > decimal(0.0)); return faceIndex == 0 ? mNormal : -mNormal; } @@ -311,6 +315,8 @@ RP3D_FORCE_INLINE decimal TriangleShape::getVolume() const { /// middle of the triangle, we return the true triangle normal. RP3D_FORCE_INLINE Vector3 TriangleShape::computeSmoothLocalContactNormalForTriangle(const Vector3& localContactPoint) const { + assert(mNormal.length() > decimal(0.0)); + // Compute the barycentric coordinates of the point in the triangle decimal u, v, w; computeBarycentricCoordinatesInTriangle(mPoints[0], mPoints[1], mPoints[2], localContactPoint, u, v, w); diff --git a/src/collision/shapes/HeightFieldShape.cpp b/src/collision/shapes/HeightFieldShape.cpp index 9ac5f03b..22d76431 100644 --- a/src/collision/shapes/HeightFieldShape.cpp +++ b/src/collision/shapes/HeightFieldShape.cpp @@ -238,132 +238,93 @@ bool HeightFieldShape::raycast(const Ray& ray, RaycastInfo& raycastInfo, Collide const Vector3 inverseScale(decimal(1.0) / mScale.x, decimal(1.0) / mScale.y, decimal(1.0) / mScale.z); Ray scaledRay(ray.point1 * inverseScale, ray.point2 * inverseScale, ray.maxFraction); + bool isHit = false; + // Compute the grid coordinates where the ray is entering the AABB of the height field int i, j; Vector3 outHitGridPoint; - bool isIntersecting = computeEnteringRayGridCoordinates(scaledRay, i, j, outHitGridPoint); - assert(isIntersecting); + if (computeEnteringRayGridCoordinates(scaledRay, i, j, outHitGridPoint)) { - const int nbCellsI = mNbColumns - 1; - const int nbCellsJ = mNbRows - 1; + const int nbCellsI = mNbColumns - 1; + const int nbCellsJ = mNbRows - 1; - const Vector3 aabbSize = mAABB.getExtent(); + const Vector3 aabbSize = mAABB.getExtent(); - const Vector3 rayDirection = scaledRay.point2 - scaledRay.point1; + const Vector3 rayDirection = scaledRay.point2 - scaledRay.point1; - int stepI, stepJ; - decimal tMaxI, tMaxJ, nextI, nextJ, tDeltaI, tDeltaJ, sizeI, sizeJ; + int stepI, stepJ; + decimal tMaxI, tMaxJ, nextI, nextJ, tDeltaI, tDeltaJ, sizeI, sizeJ; - switch(mUpAxis) { - case 0 : stepI = rayDirection.y > 0 ? 1 : (rayDirection.y < 0 ? -1 : 0); - stepJ = rayDirection.z > 0 ? 1 : (rayDirection.z < 0 ? -1 : 0); - nextI = stepI >= 0 ? i + 1 : i; - nextJ = stepJ >= 0 ? j + 1 : j; - sizeI = aabbSize.y / nbCellsI; - sizeJ = aabbSize.z / nbCellsJ; - tMaxI = ((nextI * sizeI) - outHitGridPoint.y) / rayDirection.y; - tMaxJ = ((nextJ * sizeJ) - outHitGridPoint.z) / rayDirection.z; - tDeltaI = sizeI / std::abs(rayDirection.y); - tDeltaJ = sizeJ / std::abs(rayDirection.z); - break; - case 1 : stepI = rayDirection.x > 0 ? 1 : (rayDirection.x < 0 ? -1 : 0); - stepJ = rayDirection.z > 0 ? 1 : (rayDirection.z < 0 ? -1 : 0); - nextI = stepI >= 0 ? i + 1 : i; - nextJ = stepJ >= 0 ? j + 1 : j; - sizeI = aabbSize.x / nbCellsI; - sizeJ = aabbSize.z / nbCellsJ; - tMaxI = ((nextI * sizeI) - outHitGridPoint.x) / rayDirection.x; - tMaxJ = ((nextJ * sizeJ) - outHitGridPoint.z) / rayDirection.z; - tDeltaI = sizeI / std::abs(rayDirection.x); - tDeltaJ = sizeJ / std::abs(rayDirection.z); - break; - case 2 : stepI = rayDirection.x > 0 ? 1 : (rayDirection.x < 0 ? -1 : 0); - stepJ = rayDirection.y > 0 ? 1 : (rayDirection.y < 0 ? -1 : 0); - nextI = stepI >= 0 ? i + 1 : i; - nextJ = stepJ >= 0 ? j + 1 : j; - sizeI = aabbSize.x / nbCellsI; - sizeJ = aabbSize.y / nbCellsJ; - tMaxI = ((nextI * sizeI) - outHitGridPoint.x) / rayDirection.x; - tMaxJ = ((nextJ * sizeJ) - outHitGridPoint.y) / rayDirection.y; - tDeltaI = sizeI / std::abs(rayDirection.x); - tDeltaJ = sizeJ / std::abs(rayDirection.y); - break; - } - - bool isHit = false; - decimal smallestHitFraction = ray.maxFraction; - - while (i >= 0 && i < nbCellsI && j >= 0 && j < nbCellsJ) { - - // TODO : Remove this - //std::cout << "Cell " << i << ", " << j << std::endl; - - // Compute the four point of the current quad - const Vector3 p1 = getVertexAt(i, j); - const Vector3 p2 = getVertexAt(i, j + 1); - const Vector3 p3 = getVertexAt(i + 1, j); - const Vector3 p4 = getVertexAt(i + 1, j + 1); - - // Raycast against the first triangle of the cell - uint shapeId = computeTriangleShapeId(i, j, 0); - isHit |= raycastTriangle(ray, p1, p2, p3, shapeId, collider, raycastInfo, smallestHitFraction, allocator); - - // Raycast against the second triangle of the cell - shapeId = computeTriangleShapeId(i, j, 1); - isHit |= raycastTriangle(ray, p3, p2, p4, shapeId, collider, raycastInfo, smallestHitFraction, allocator); - - if (stepI == 0 && stepJ == 0) break; - - if (tMaxI < tMaxJ) { - tMaxI += tDeltaI; - i += stepI; + switch(mUpAxis) { + case 0 : stepI = rayDirection.y > 0 ? 1 : (rayDirection.y < 0 ? -1 : 0); + stepJ = rayDirection.z > 0 ? 1 : (rayDirection.z < 0 ? -1 : 0); + nextI = stepI >= 0 ? i + 1 : i; + nextJ = stepJ >= 0 ? j + 1 : j; + sizeI = aabbSize.y / nbCellsI; + sizeJ = aabbSize.z / nbCellsJ; + tMaxI = ((nextI * sizeI) - outHitGridPoint.y) / rayDirection.y; + tMaxJ = ((nextJ * sizeJ) - outHitGridPoint.z) / rayDirection.z; + tDeltaI = sizeI / std::abs(rayDirection.y); + tDeltaJ = sizeJ / std::abs(rayDirection.z); + break; + case 1 : stepI = rayDirection.x > 0 ? 1 : (rayDirection.x < 0 ? -1 : 0); + stepJ = rayDirection.z > 0 ? 1 : (rayDirection.z < 0 ? -1 : 0); + nextI = stepI >= 0 ? i + 1 : i; + nextJ = stepJ >= 0 ? j + 1 : j; + sizeI = aabbSize.x / nbCellsI; + sizeJ = aabbSize.z / nbCellsJ; + tMaxI = ((nextI * sizeI) - outHitGridPoint.x) / rayDirection.x; + tMaxJ = ((nextJ * sizeJ) - outHitGridPoint.z) / rayDirection.z; + tDeltaI = sizeI / std::abs(rayDirection.x); + tDeltaJ = sizeJ / std::abs(rayDirection.z); + break; + case 2 : stepI = rayDirection.x > 0 ? 1 : (rayDirection.x < 0 ? -1 : 0); + stepJ = rayDirection.y > 0 ? 1 : (rayDirection.y < 0 ? -1 : 0); + nextI = stepI >= 0 ? i + 1 : i; + nextJ = stepJ >= 0 ? j + 1 : j; + sizeI = aabbSize.x / nbCellsI; + sizeJ = aabbSize.y / nbCellsJ; + tMaxI = ((nextI * sizeI) - outHitGridPoint.x) / rayDirection.x; + tMaxJ = ((nextJ * sizeJ) - outHitGridPoint.y) / rayDirection.y; + tDeltaI = sizeI / std::abs(rayDirection.x); + tDeltaJ = sizeJ / std::abs(rayDirection.y); + break; } - else { - tMaxJ += tDeltaJ; - j += stepJ; + + decimal smallestHitFraction = ray.maxFraction; + + while (i >= 0 && i < nbCellsI && j >= 0 && j < nbCellsJ) { + + // TODO : Remove this + //std::cout << "Cell " << i << ", " << j << std::endl; + + // Compute the four point of the current quad + const Vector3 p1 = getVertexAt(i, j); + const Vector3 p2 = getVertexAt(i, j + 1); + const Vector3 p3 = getVertexAt(i + 1, j); + const Vector3 p4 = getVertexAt(i + 1, j + 1); + + // Raycast against the first triangle of the cell + uint shapeId = computeTriangleShapeId(i, j, 0); + isHit |= raycastTriangle(ray, p1, p2, p3, shapeId, collider, raycastInfo, smallestHitFraction, allocator); + + // Raycast against the second triangle of the cell + shapeId = computeTriangleShapeId(i, j, 1); + isHit |= raycastTriangle(ray, p3, p2, p4, shapeId, collider, raycastInfo, smallestHitFraction, allocator); + + if (stepI == 0 && stepJ == 0) break; + + if (tMaxI < tMaxJ) { + tMaxI += tDeltaI; + i += stepI; + } + else { + tMaxJ += tDeltaJ; + j += stepJ; + } } } - /* - // For each overlapping triangle - const uint32 nbShapeIds = shapeIds.size(); - for (uint32 i=0; i < nbShapeIds; i++) - { - // Create a triangle collision shape - TriangleShape triangleShape(&(triangleVertices[i * 3]), &(triangleVerticesNormals[i * 3]), shapeIds[i], mTriangleHalfEdgeStructure, allocator); - triangleShape.setRaycastTestType(getRaycastTestType()); - - #ifdef IS_RP3D_PROFILING_ENABLED - - - // Set the profiler to the triangle shape - triangleShape.setProfiler(mProfiler); - - #endif - - // Ray casting test against the collision shape - RaycastInfo triangleRaycastInfo; - bool isTriangleHit = triangleShape.raycast(ray, triangleRaycastInfo, collider, allocator); - - // If the ray hit the collision shape - if (isTriangleHit && triangleRaycastInfo.hitFraction <= smallestHitFraction) { - - assert(triangleRaycastInfo.hitFraction >= decimal(0.0)); - - raycastInfo.body = triangleRaycastInfo.body; - raycastInfo.collider = triangleRaycastInfo.collider; - raycastInfo.hitFraction = triangleRaycastInfo.hitFraction; - raycastInfo.worldPoint = triangleRaycastInfo.worldPoint; - raycastInfo.worldNormal = triangleRaycastInfo.worldNormal; - raycastInfo.meshSubpart = -1; - raycastInfo.triangleIndex = -1; - - smallestHitFraction = triangleRaycastInfo.hitFraction; - isHit = true; - } - } - */ - return isHit; } @@ -374,20 +335,8 @@ bool HeightFieldShape::raycastTriangle(const Ray& ray, const Vector3& p1, const // Generate the first triangle for the current grid rectangle Vector3 triangleVertices[3] = {p1, p2, p3}; - // Compute the triangle normal - Vector3 triangleNormal = (p2 - p1).cross(p3 - p1).getUnit(); - - // Use the triangle face normal as vertices normals (this is an aproximation. The correct - // solution would be to compute all the normals of the neighbor triangles and use their - // weighted average (with incident angle as weight) at the vertices. However, this solution - // seems too expensive (it requires to compute the normal of all neighbor triangles instead - // and compute the angle of incident edges with asin(). Maybe we could also precompute the - // vertices normal at the HeightFieldShape constructor but it will require extra memory to - // store them. - Vector3 triangleVerticesNormals[3] = {triangleNormal, triangleNormal, triangleNormal}; - // Create a triangle collision shape - TriangleShape triangleShape(triangleVertices, triangleVerticesNormals, shapeId, mTriangleHalfEdgeStructure, allocator); + TriangleShape triangleShape(triangleVertices, shapeId, mTriangleHalfEdgeStructure, allocator); triangleShape.setRaycastTestType(getRaycastTestType()); #ifdef IS_RP3D_PROFILING_ENABLED diff --git a/src/collision/shapes/TriangleShape.cpp b/src/collision/shapes/TriangleShape.cpp index abafb92c..c072ad84 100644 --- a/src/collision/shapes/TriangleShape.cpp +++ b/src/collision/shapes/TriangleShape.cpp @@ -37,15 +37,6 @@ using namespace reactphysics3d; // Constructor -/** - * Do not use this constructor. It is supposed to be used internally only. - * Use a ConcaveMeshShape instead. - * @param point1 First point of the triangle - * @param point2 Second point of the triangle - * @param point3 Third point of the triangle - * @param verticesNormals The three vertices normals for smooth mesh collision - * @param margin The collision margin (in meters) around the collision shape - */ TriangleShape::TriangleShape(const Vector3* vertices, const Vector3* verticesNormals, uint shapeId, HalfEdgeStructure& triangleHalfEdgeStructure, MemoryAllocator& allocator) : ConvexPolyhedronShape(CollisionShapeName::TRIANGLE, allocator), mTriangleHalfEdgeStructure(triangleHalfEdgeStructure) { @@ -66,6 +57,27 @@ TriangleShape::TriangleShape(const Vector3* vertices, const Vector3* verticesNor mId = shapeId; } +// Constructor for raycasting +TriangleShape::TriangleShape(const Vector3* vertices, uint shapeId, HalfEdgeStructure& triangleHalfEdgeStructure, MemoryAllocator& allocator) + : ConvexPolyhedronShape(CollisionShapeName::TRIANGLE, allocator), mTriangleHalfEdgeStructure(triangleHalfEdgeStructure) { + + mPoints[0] = vertices[0]; + mPoints[1] = vertices[1]; + mPoints[2] = vertices[2]; + + // The normal is not used when creating the triangle shape with this constructor (for raycasting for instance) + mNormal = Vector3(0, 0, 0); + + // Interpolated normals are not used in this constructor (for raycasting for instance) + mVerticesNormals[0] = mNormal; + mVerticesNormals[1] = mNormal; + mVerticesNormals[2] = mNormal; + + mRaycastTestType = TriangleRaycastSide::FRONT; + + mId = shapeId; +} + // This method implements the technique described in Game Physics Pearl book // by Gino van der Bergen and Dirk Gregorius to get smooth triangle mesh collision. The idea is // to replace the contact normal of the triangle shape with the precomputed normal of the triangle @@ -182,13 +194,16 @@ bool TriangleShape::raycast(const Ray& ray, RaycastInfo& raycastInfo, Collider* if (hitFraction < decimal(0.0) || hitFraction > ray.maxFraction) return false; - Vector3 localHitNormal = mNormal.dot(pq) > decimal(0.0) ? -mNormal : mNormal; + // Compute the triangle face normal + Vector3 normal = (mPoints[1] - mPoints[0]).cross(mPoints[2] - mPoints[0]); + normal.normalize(); + normal = normal.dot(pq) > decimal(0.0) ? -normal : normal; raycastInfo.body = collider->getBody(); raycastInfo.collider = collider; raycastInfo.worldPoint = localHitPoint; raycastInfo.hitFraction = hitFraction; - raycastInfo.worldNormal = localHitNormal; + raycastInfo.worldNormal = normal; return true; } From 333cabed988465f035f000ee71525418ae440e4b Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Mon, 4 Jan 2021 21:36:38 +0100 Subject: [PATCH 74/74] Fix issue in testbed --- testbed/scenes/collisionshapes/CollisionShapesScene.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/testbed/scenes/collisionshapes/CollisionShapesScene.cpp b/testbed/scenes/collisionshapes/CollisionShapesScene.cpp index 0cc97c8e..5a5b92e4 100644 --- a/testbed/scenes/collisionshapes/CollisionShapesScene.cpp +++ b/testbed/scenes/collisionshapes/CollisionShapesScene.cpp @@ -107,9 +107,6 @@ CollisionShapesScene::CollisionShapesScene(const std::string& name, EngineSettin // Create a sphere and a corresponding rigid in the physics world Sphere* sphere = new Sphere(true, SPHERE_RADIUS, mPhysicsCommon, mPhysicsWorld, meshFolderPath); - // Add some rolling resistance - sphere->getCollider()->getMaterial().setRollingResistance(rp3d::decimal(0.08)); - // Set the box color sphere->setColor(mObjectColorDemo); sphere->setSleepingColor(mSleepingColorDemo); @@ -130,8 +127,6 @@ CollisionShapesScene::CollisionShapesScene(const std::string& name, EngineSettin Capsule* capsule = new Capsule(true, CAPSULE_RADIUS, CAPSULE_HEIGHT, mPhysicsCommon, mPhysicsWorld, meshFolderPath); - capsule->getCollider()->getMaterial().setRollingResistance(rp3d::decimal(0.08f)); - // Set the box color capsule->setColor(mObjectColorDemo); capsule->setSleepingColor(mSleepingColorDemo);

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 x#3>N^0r9}u#Keq(LASi=ck!J%k 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%0D}+|5cnwy6BQNdO@8Ah5)pt&A z=w+tpjKil)X#`CeIg|YH{o=x}tMD4~jEXBnhK#d=(isKf9vwL z@Yb8E9ct%10wAEX=h?>GwA%oWKYsY!Eh+O-B7$I}12WIGS}H1c5jw4^;R4c-o5)IG zxQ)G740a;h(N^?0?UxlO;7?o`;bp8-QImxEW07k7uPC%QQ%*SF&A1TWBGKC-S&2-i z;?j||Dy(T#Yf0Ena6qojltKEzjMq^YOq_y*%C_hU;uc=l_UGsB_2H;@4Az&$InTq} zA~b>>ptB8PEFQJ*i58TTfGonvptZxIQEL(UXt}tg71}R#XL@Ymmz5d}1|v4ky6?c5f{;V)tmBCtayCrayn)v_D%if1^H4{u8nt}R|Qa<`Tb5Zoq*3Pat8D1`8%Q7 zt25z!!~v)}zFvK+Nb}lLWvCCO2M!^I2PX!DfL;U1FralfVgmZFF}UBV5LUaXC6POt zpMMl;iOgM*u=Dt4%n|NKbfOr(j{%#WCLQyiDE>jfRBVz6GYD~A^kkd7)32d+cTYm>AeU- zC$8VCd2=Z!u*gU)=#`YwmQ4>SY!tE8)H%{#LQE(jz$V9n-M%-&D>5LuC!x+sk_&(Qv8nN9{_J z0Q#4DJ@X0TzLtS=q`Gn#KG~keFiD8s08g7;IkdDnYRWC)u(fBu5^FLdT3owBX0N+e zoFE`O2WvP(5XCgju86j1@1JjROP@11MQLTuU=SCGXq)+f26YVi<-lZ${M{@vlQeW{ zN99_j9^n@vgP8_}G<+q2Q&3ay+jUiBzz^2krR80+TFSVlT57cM>vX!|P7MSi}N121E!wdrSBfJVR37&GbM1-U7=f1__KX zm`0pgP1OBGA#jM8cS&U-!%A~IMkpbi*E=W~@T0i!m{XAORZ%l7$e24^>;_%lfP~^- zw+B_4F6TD!UUYw`TLk8$)2E$calt|3LhnPQAu|wlod#I>#lO6Z?=p^~n!amoN%&HP zdBODy0HrBS!T~(Nm`1tj!PxXJF!t#Vxn8%RE9B^$t9;8!#=X>3oWC{`|pmsc{nkA<}! z)3Ri)89rKBi^z{7@(r#czj)bX;h59Qq?IqBwCGot<<+}Hbcc->4t7B<|M23i>(Zh% z>N)o(dtTmMAqgmLd+Zcs%nf1N^4@ehxF)SRgp3)3ZFI3*B%`!0)vjgL0{HCoQ#aQ( zX$XBiAv`?x^H}1tC*C?~YGkyGn=0ko1T)KJpzmO5h9?epO}!V|krqvVwaR7U_-V4M zH#7j+!{fa4IWT{1tb#p><>{@QSMmKBP<)bXYf6I{@@R2*G@nbc6>Ut5H=}+e&rW-) zb+40%o_F-m9)jC`fII@t5 zt~gE^8C*J?H(grTf4sheuiM?<{W)nEis)R#&4EmLeFeEaFnIp_#=|6Ylqa5dIcX6ho9D3;_MkKtKMdT zuYSIy#B*;09%f*F1wiP=h5Oxov43I-t!t;kkJcM&C%V*0>j9mkDtXb1XqYL$84GqK zqDXLmoobC>Z{_yQ_;;mEb@o<*(125Yitp5(GtyCoFkuaxRgwY*y5IfpZNSq*Z3JF*|X`P`av&i9tYsgZ8)lS(n^2HgKOSZrAw+BygHign5$TIO%ZB>;X zNq%#=CY5lnPt}7lcJN`D1zbtuA6Za~cU4`+0w0;beaB?*>Ch8n&W{nQ%q{V?p9k{X zeKgoulOE1JcSOBS#x(g9vW;UTWGy{0k6kxwMEmu4wVZJ70? zSjChz7JQY0s-b6vvh?`5RM^}eh~XRq)X12y0>$mF4_*!>GG>U~$wVwUnEV>_D)DPS z%!34`v@*4o7I|LC7~rmMbRIF$@lqyN6Wd2%1!3Rs!2iyIe8XbMGk{1V17XNFYxe`| z@6AK}!3wxQluH?#h8~q~`{T3GG_EdUywyyNGCa#$p(L1}z;<~oDjxE|2j#fM37kIO z{c1Cl4V<*#&im;K;e%wQ9k;@X+hE_Pb>P7+&Ni|Y=@lV`joR=buhX3!8gu<8g@rYt zz3{DDCei0p`_=cmI_ID-)(JPe%&)`oH&o|nV!*ec^+i`);WaGMwO>LV!Y_n%%ac#k zfCQH~m#|x;!z3(fpBfCma}eQUl#qBt(TIg7!hyP#=*evi-=(2U8)J@rYx7H+dGcC|er`9Z;|BcAs?A;~2*Tz}fZ9iJ)Xn{SQgDMR0R@+v zQenTbz`UCiHZ3kbzRBMs0v<5eU9IDJ#pitW5-`CZQBTQPIhYm<`us*ZulkHZn!$OMfiB!>&H6c2H*&zNR=W-|?eC?$ zAhuH*$>{J-9rP`or^tRikY%x|5q7E+3uU>I5IFXv7pVnG7B#j%Vn7)p*uh+ZlxL+j zdbT30gc((bE=~-4&ZEI|$-ZRO%bCXVhtG_n8 zlO3x_>D)=Q344YFY88NK$*jk^!lM394B) z31$jNp_aDgBu=iGG@lLjBXiVH*hiGL9f#`KL#gnSoydBvH#{2ZO%q|OTT#r20sp-6 zJ{$*fiSftQpW)2W=hJDaTNjQxX?p`Q<~rkymA|Xr^0crk&VJP)aDa!OKr{BR1I^e5 zG~)-!KQp48B2LnbYo#c*`X~ia-XfGpqj9USEs7QWT}HA+06G_gjH4EVblQhwK%+b0 zR^iNnEwh7|bq}TYYrefhHFG7oX?tb2jTN^SVQ#kMfh>NEKYr$Ti%{o8wQN}vpmcsbkN2$=*7U)-haDjf+3KT|E9^z=AA&=`0(>0OrGP}AFL_XOD78&Z`iOW2_ zA@2wA=JZ%3W*xZJHdfDIipAF<&a0Sxsl}taP?hU-3zgrsh>%jZh2q8KeUYCi*NSfR z$K(c5h7>q|0A9?acvqe8>Z4LFC60f5%G^CvZu`Bs)Fh=E#g>?~TQ_Vk0}Ac79zBHw zmxx26GFkW=FtW1klYQhE7c>Bt_p0_kGo+#>;(dC)XTSN_w@T2(t7RMZUgN_%=i50_ zRnQE{FK)_TaVm#sZEzfJMfV6?ozR3C?kEH&|@0uXg0@S;fc6CL_H?*nu z`_=2N5y%^b4h~NLO1)6zE@*No-!~O<;lh@~CZtxkhUU#Kz;-4HjqJaxi46F?affO2`xwqltOUB` z?%p7&-uop(^Vip{)6-EGwT{yaY3kVUphprdri4$D)VRt|Oyulg#r_YEj_4w$=SFm6 zCH`GxLOuXv2V7$#b9&aqJD|$dwZp0hOhn>CK`^3_Yq@onVNmu<8xG?@ADz(bysY50 zra?1$WEblYdZA+VaMF-)>(`3{jhPS~MIvY+#R5vbq$=atb#)m}&-bQL{~qScS?pzc z^-9|>E%i4(x`tg7jM8KZ>rO4_@se11c-Fdo7f8Sb)+$+JeS~zh7{t=@doN29Ihaa& z(kD{v!F5%U-PNSMySarPRjvQSGqWM5Mc}Ak7lg5zB(D>l>3f5~5jh7Z8mYjZ5Q3%{ z1aepRJ6yGSuW4pTs9f6wOyDTWzKSlWmVJfb;kn5hq>ha>Gzcl3Am61j{quex3nzIq zr5b=3fTXUK5-z~tjct}qN_Wob-cvgvkC@)kVTHB2iyX;N=Jg9KP`)9t_ofYi3ng8AI0Txn8^7u2a77<#gLVoJ6Mmu5GlSZ z9lL7TmlZn(V@{rUcYAL&!ME{ zOUnbMMt!x^$@1pG;W*fM&;`9Q`?ql$Ul03;G)t#3dbvwhtzqrK8TE{?2AE0Nbbo-U zQ+8d$Fu_Tw(uu+8*Y?$xz{ZEqsPMpUFk`bt@SXUSYT3S7gTRLIFEJ(t%z8}gMy{in z%S}NYEdTi9Gv9*7h?fP%T@E#Y#h@)%1SuRuZ8fq)xAsP>%HZ?VwSMphVR+li*V|Mk6N38E3P8 z2Fx=EcYg=WHHE9SWJSqNo;DB2x7g05tO=X0G}p18`oP4~anSJ8MM_Mz39O1K?k^HA z3Bq6jn!d17X|jFnEqcv{@Z@)3A|BSF!r^9tRyuj7Zc@E!mm!M6tC$Uur3ipmbT^)8 z9jpI?E$|H#SgRuhMPz)zRZ&4-L0@TC7Gez+2}2CmCtUuGUp^dIs{I2hfjm1wgF~t= zN+5rAvKHq;&w6HFYu5OffLf6k06AZgo6me^d)zcOy?TV7d^g%@vfnTOsr=1f25*#6 zGwVW_s$xb6K+Hq+N-(Fs9uBaBvcWC7=Ka;$YOZK{2~c`uBZpi;2@;2pcmy{WJ@hZP zu+{bG2MLuGM>!GaA96Gj{VSxT3>#z{tmq(hcQ-Odd-8t&PO!89_t# z7Gzx??N{-<)=Sa}QB-XtLnK3#1A_$0_;2cDiKyV&1GiYbDnb-ZseM55h;v8R~K~Oh(t%4Z?u(DJjU~%o(Lq!x!)R3e~$tmAg^$R z>25C8GH(W;b#{=SI^qG~6Si5Uf0tPX{<6>Yk}QGe3R{^N|YMWIRn zQDaWh$!C(q9%GajSX>P^AA-dD@nVz_xMr!dX%{imJ23@`dTSG{lYZ zT;%0b)@Y<+ZLs%wR;~}~eeBbxXsO=i8*=d;Ldnmx)<|zW!Y^wpZY9odXRK%ThCt_S zi2^z=AmKr!d5}U%_N#R+ejoxx{!RTkuS1lm(ogY_iuSHGHoPe7k2w402U9$+`VQt5 zBV*VJGt)p4`Z`mzy$G!%l0}Jv77UhUeF;)ZImIt9>&4_5?1z>T?SrFc0Mb8QAXr%- z56JVx#ev$=vJhD7i363b9s??y?hZ8$@HW~+pBiHe63Au1&!&GxU2_=oF z?6W>0ezG4QSB~()hB(PNnZ9oy>6Ty0ZG-1|PCcP|+j2K?BQr(BI%aN1OR4LZm&E04 z7bxC>;slnRkYx%3`IZ>PI8kRoO_m(H(p?MqGh!c21W}d;F_t(a#qKl5Uwc z#G=zZS@R9QD{ znnmA;M?B-c-ZY(Q_yI)`Fa^w_pkxD`XnwU(wT4aS4;$+VgDzj4`iwMeXyf$d6Ff>i zGqym=+&kdPU3MQO>Yh7U{NsjdX;%``!wv%fk*jK+tfr;%PGv2+aw+!f{6ZtQNW$A2 zDteOZdGHXY;$GssoYU?!R4ASLHJQeb8+22zB_dSOV=%SdJQ{bbhW?o{|b=~)2X%T{V=aYWh ztkj9e)hm3N!qnRK#QywU3EJ>kD+%VUq&2iON!lm30T))_FDwF!ObGukte2+ReMb3( z8Ea8?tAsnCO!3K+x{fEagmoXU8{!X&q9r%%O3>{C8qn-f=`62%Mm)wUo~r7eblD!&rX%}JZ09y1-uZZ|M?g2{P??S(!EIyVQQ1V95gM|w zp!lm1YZJ*9wSE%@f>V7l4$bHZnrTqERBPln3L}|EDOfCe5oE}xo2NIE5LQVp)b~WU z!mhp#WZs|%x>p+QiV|Hga6b&xln}`^c7VZk6K*M#aUW&f3diabbDMkSizeAYvZ+~+ zVD58^G;gKd1J!z$gm(?Ee4OD({xm~ze0|yBXAHq0h32}Ro*Ld?EdCbmqqnI5UXb)} zgdcc6Yz&rt+dRB5Eez)@A>3`6{?Mv?qb!=JKFPspcd5ZGuczJZ_qVR9CbNclBykO* zeZmMFBk$fRU863n+t>2`V*0aplhdFrZXcmV{$XLC#kO+L&}7y?{q7Q5yB z+lUgFBtZ49Ijdh)TW2o!(E7^KqX5&2s-xBU`Z8W!3T?=N!SG>C39gvS>e`%T3kz#r z7(2OXD*7LoK~Q9Aq7v?xT}TCOq`Sx4?tMv0TN==~gF%&4G)I!2v60gbb*8^sxr~Wt_ zvFMsxb-4O!<2|~`#UxrHJoDQA2?reBYjBGo~Pn25hxtj{shF7=# zc^`lqp;Ssas&8L16)~5-;H!$QP){F*ow}Uz|5wfWLl_bhjD`DuQ!0i(N`SSjy^72U zvHQcxLV-Ddkfn2TyIfS#z33`a_GKD)FAy~mYJ#RWj~2?iD6p9UX~rc(sVRnH3FHA4 z*;g9Xr9nTp>i(RqYudGk%Se@+zFt=68#q^3()Fm&%01J@X=jL6zirahU=&U)MoSjP zE>(TkK(70A!%?v#`vCkU)3q-+{C*#&*7_>-;qgb4Otbp>yOC!>6WP98D5^t_3r-W8 zT#+4fesYtNSQJ*KbUO)V19GK4GDaDyt_ZenOvuL9`(0aIfbZK`jAJsk88&U%^VuV) zR6D3URCm!ZoU#Ez=@}p4z63~w5|bzN~eb@ z1d^_nn)O>cp(y|Lq+H#NeRsDf=rzN+{u6g!t6F zM+que*?#LXEM5&_3ySZA-CxJ3VZ1{b7m9_kCE8K-*QLZ8Bah{*W{n=HfRK{~y%U zRJ);&u2o3KhA^N#5lRw}7^ypVjR^b{zai%?O{6H8T*k%z!cIgIL$tCT)|Ya;Ng}&?7nI`jz?WO9e~2|ODW4tdKtY{iJ8{HkxE34)FLssK3IZ86GN)8%F=bV55(4VhNF6GRqTORU&sY5V<3+>|EFd zNsE=lm=r_n7ZJuP&`e|h%#;Z<6DiP4JsJR@srx#q$>ox%<#NCWww0e(bcBr*+!Wm! zu#?M3<$%rT58WVz@crtK+UK)QUk{BYF+v}n6d>s)+HAnIB1Uw$5hkU` zFFrbhGCZ@VcQIA$6-&7ffG?KrpEH^pTeQ;SgJQ*`%OGrYo57#q4MMr=COxOJ;uqkq zLhtMiEV#Cmgz>FohG+IlHck9h*Y1PwTWvrcE2>JmA76xV%w%5Pz~L}RHYq|gUA91! z4cw4N-Gi%qelTUU^-Y-}q^+Eun=ZH?ZtBz*(7MTQpPc*a9(*VP+StVcwgnGU%LLiX z`3shyKM32-BOzz?y&d!JoDs{m18)bts^V&W&n8|!J|&c98GC|~z*S{rg5);;qGu>O zL1N&e-d?wczO9t~Ks{n7!KB#)z(c@BE3}cK2dDe1Z~!4m z`xfW5{~;iI z%AoxR6WcUaHjCO~0iTv2b;a0#Tqe!KlQ{hTw{Jg-kapc|FA@WGnr?Wov6t)-jU^N~ zSD@`x@m_CD006>jHZ$vkrF`8C%~#j)zI@7!;e107=IdF45$5N+;eia@pQAXTZUeFg zE5Rs>IIRI!*AiPO!1+C?SqNAHD(1>)qb0q$^4ri(H!6Fi@yy)hdaw zS|eQCjVF_i^#7zNTVfrFIux(p5OaIhxK>6=)$Fk$X_q`!!bn#}?h>zsPnhu4V5-uJ zWbNCQy0`xnoIxe;w;Y8(p1K7+g%=1+n>{_$W%_m-oYH-;@|{}K-7uc5?g|JHOPdiJ z5*UDof8%03>g|VyHOxIu9+4I|8{+W$S=Xf>Vd|XsLEOlxn%m1C@)pU`{d!MGT9&5s zXA;hp5vknaEJwJYh>4rNnVwrl1a(;&;LrD- zax`w7u{M5k*Z52Q?s0yhfjZ$MlJPHgJwRzjg|Wj2eqzZKh+I$DK*9LKfykwCCG2vg zbsS$*G#dFsY1q#V$3W%y$gh9?2ULn|q>v&@Z-+7b-Gm+(DS4)`m&A7+ z+Snf*#+iS{`{h&;F$U>}A{GMlLjS@W3YzwN9@buTsd|gIcA~V;vw7EXrev!CC>z*I zgqgY7<;hL6XI%Rs78d>2)yiC?ELt+A=DqoAM$z+@YJ=l%bF*5LO}fe$vvoAH-TDL0>69Xc2~N~ zF3iIumH3hBxyzu@wT3gzLgP{b7;?Y2WJkzRB$}7kQBu8ezbA9S8Kc&r%rMF|>jP-X zw6WN{tVc*Gj?N|lwUFO3%uCYj8x~Sf2t0Uy_#Z_fq)vp#x~wL}ch}zWrp&|2DbU{Jc0)OC*IZCOS6?X9Otgf8h()Gj}t%8`#hu(BrgmPQ11fKnpou9h61>{&HpVB$1>l z=>Oce+Ry=UV2lXRCJ0{v07<}_p+)gvoHVJ$JcyBgi86I3!jvFf7c)9D!rAt}rRXgG zDPf;FTR&gysQqQj;d2av8!Nu%4;_=j8nZ<-LxAhKUh&@;znO<57bJu><3T5fP5ip%~ zKHibG?f(4lSRww#8+?;EJjZy<{KYwIdW*0m>fLb1p8}}PWt7ZnyQ3+d@wfuwjJ;1{ z4(~GjF|&7CcmuF8P{PZk_tXysa%JNqDh=!;J^eA00&irN-TBoZuf$cWyDOD0LK}v} z6lrYlm*OYFadl2i@aglBP+7~(zr;;9VV$y|d)>dQ+70pu~j{hY>!XF`zf^u|blHAAW};!7dN*KpZ3(1mfWA4p2UR z-+%IT)c=qK$qHRfbh|}`3@`tj)r$v&Dui6u^a@>%{AFhjI=7{BnvPlqG%xo%8GkzX z@7*b;=fx-9w_V2y9D^9Iq0J3fGF{&8$N{L82pA3b8w&K~?YZZ>E9G(AIoOF@@TLbt z4)E)bOxPF(hh|s2=&3a^w1L1$3dAS%p@@x3LAN(}s7+VJ4wP?ZvWQ-t;gn$RN6>Uy=Y2lx)J_^M*FpF0xS6awwm~J-ECO zh5kcuVm2uyCJM^<*m01AU|L3bM~z-inn$jcNz@y7gQxki^Kr>X(hoLI?KQ$S zm_q=VPe{vysUbyC`&$L`nCKFUB^+-Z#jRT+-$RpW9AGS~P@8Bag<*>=M7vlbLcRoO1`XQ42=|tb;c{Od~FK#F0FxqK>i0D z_f{$*7hAi&f4)AlmrD&7RUTL#2D~f*1K70lmj7Lrex7RlcSoSVi1GqRb+{n-n*HT; zGLG^nN-Rq(nuO>S;mJjaa)x?`z>_*;2hrDR&)TvH|sZ_4`Z z{-8o`1hte@nN~AO9TFKk(t+tCc-LwbRyAEJeSv~Jq&z;#HHIQwOnk7AbyyGh`V1P-#$1S*pKuG*9J4=l29yn$3nd&f1nXMpK#nYJb^|~ zTMRkaXi%u=GSUH(oW97i@^>0BHR`5rcn0hhtpr=jJvRDGxwW?LHx)?n+Opc{38>RPl`gI zTlzaBWk?+*%3Vs2JIKvl4g1L5RVWsln2_CYPe_}S)gIHZUEfa{Im@ugYXr!u(bH!h z36V08Flkk&2hbk@hKSSWEEiYJpA&UWkATO|c=iL93U?gl*c8GQRUy%+hRC!|yOd(F zYDoknId*+%E^5TwetNNS=G}aa4ye24KXQl?HX%Pv^zuZRt|cX5e#Xa(Ec*B&%-b9- zU0S(Y6)_cs$fK)Kj97&BO)0ss{tsQ>6kk{O{2kl2)7Wfmr?G9@Jh9m{P8u7H?KHM+ z+t&Z|`MtOA&AHjgIzVoU{M1$yMTN3%zMhW19gX>IT$Tw`Aj_NnUSxyNZ z@upWvQnPc6y1k7>!Q(&;BBRC5`-MTv(YwZ}Q}pH`o}%$bzvyMxKl3x;qE5y~Y@R)Y z+Y$#yPDBdv#<&I#m9{1|Py4X#-F8wF&AK4n#;kIQXsp3I=fLAlT;CpBO?`ha7F3kn zPCnX~V;(>NaS<9>@GK?0W1vmu>02{R_j<|l^tJmWH=;mc_%`6>rfNhBb8JX2Lqn5bfan3g zv&$@3UNaEReL#RiHKiF@tfsyZw{=iYNvw^ycL%@%E@?R|Qr@Y80eKY-2LiPZ&7=hE zu~~Brp9m}M{Ad-vSNgod#{Za;~FC>D84rhkrDEZHCj`v!x|u) z-{acBpu7;<%A~HPt1`EpF+afumTmhwEc9C<`6g;Z*yEzB$E@QY#QQ#a3}dm&GxKk zW@g8V1^VSztDy21IA)$M`oZ1qx9?0*%^6^4DyiBwaqUq!8Eq}Rql@DiJNlqshU&vhRtQrKAKZ()Vnm$hv6(M)X{sCau z#Tb^6)FUOU`XS=<^<7WBIO=WQI;yHIvC`i%#Gc6T=veNYU>YnhZL#z{6jy^9^oZUw zXQwGg$jrH5L{3XG!SYVW{EX=^p2KMYkJ@xG18$|UN~D^IvTi*^s#hhIY0On?zhYL* zs?Bqw?0i3A+_f^TMw?c10lLz(MigLoLIfyzO>o*%NDvYnO(UdsRi6rxPX|X|OeZub z_*#^+dA%yDJ}skl9;*!ljF!iG`9>K<2va+RnOH)W-{4o8Bv`!lLGq!uKXHR|ppb$3 zDdx@Aw)!8)^Ov8Pn|o<7IPazDALpq;W@0QT2+~hn4Mz3Xm>et-F4%D11OgV&Nl+3v7)UinmjHN@ z!6Oi)i_#+y6*HEm_)`96xekJjW1;Vi?7D&MZPSiDrDr0+*sUDd|HU=f7v!vyB9wm} zoQY?)7KxdE?nK5D%yIk%yipfBG%494LeNuZPN{QVoSyHKYsU;Sx4hKe0sY$~aCG0A z7tp`)J^}q3C;vbHHlKoz!(|eHGH{WS`S-zR9U)lt&(=`OJ%f{x?k-r%U$db$xj5jM zR2_N;=fx&d1&6f*Nyxn1KOEzGexMOMm%MLaJ>jO_P0;M$R{>KIWc91_Yvwy8sK;h7 zedh&I?N@bT-oa(H5B#%&WE=LvyQu7x;g5RziVU9a7`jtzZA5!9qkFMvoAfugV_dAb zdCYXz&Wu&y2d82{XUv-@P%!xe7|`MMKaqTgu1xg&*@wKNKUw6KOQ5z7SEz*f-Edw_ zykofPQl0px3!tZs{&xs5UTco5&&Ijg(%bwu^VQF&oBe8Z1@%r*l_cE18YYnBn_r*Q zD+hr$$HT(OAQd;xyA?okSU_^^p+Iu>y+Cp*kN?iyY+MkfU}mTzOP!>Klb1uD)XKYd zMDy1aO~Ms<@}6&P=sk=HT|@H_12nUe(6Xk!fR{vqz*H|1u{D#H#|T#*jA}iCLM7?` z2%qmfvw_YWjX=L$ya@CT^S;AXEXe=-HXi(J2%Oihq_{RPgiViQuO{>RUkICF4?Oxq zS4Ic1;{_9#4B^OIQk~Hdu|mUL&}P;d8F=8{&<>;N+!wL22aFq%qJESo{&pqf|gY=3bH~NwW zp4E|zNyy28Qe@YAx)f8&j7Y2YKX*2E%yw#vE3m(4S^w|SHddTcEUcVg@U)PO;a5cz zFlVaq?pt)a6IR=LI{3`LIqEdY_iU_L#WNyW2jB% z>%P4f-o<8-q`%Qsjf&Mx|I*`&ufX6s1G$ysap%9g1+ZkPPy`~s1tIbL&H;D71KhcO zwe*&HT6AcKIOmkdACucM2%CvymQ<+4M&VuD0i>{ufGtnvG^~*s{bKOg4A~r64VxR% zdhK3*Dc~U|aX3^8*LwxiEE9Q9|8(${7?Z13EY;%$#=LUM|l0ALf?@Eb0*ZvQw@Bf)fH2v^>?>G zg*pLv{yY7o$1^8rhW-5e^+jw%LIx*H-cjlUe^7o2%ylkw0{`3gmY`FR|1UIfJ0wRC zyGBv{e2Qm&|3rnVp~eaq@%%ov80hSV3;esD1W;E~cSP-D)BihH0ar#L3al%2IAlWz zVR1C7UBcHS9H;wZXa#`JW!Ygv&|V*%#M)AST{N<-h6NM8 zIFK3lp&n(mxrE-GEB6mejgw!zUXbdnpxICV!?rCiz4V7|{QCm5#_JPod{@-;x+uSn zUSH4U@bD9M11YQf;T!w~)m;Kc8(2KHVuuZh{9*?{Nks7@<7dmU=DeBgT#**V zI}|eIQdt}(&4pAN5cE(?X7B>DsGhS9xj+DRWGc-v<_N1euf!OJ_;tf~S8CVNLAcPk zT3gt$!8-9gljn@!xRJa4?-_K*nHuMVemC{Sn-%ia1}7Fa#HF#E=+H+inR*q?O(ulXo0rLNqxD21gwuUIM-!^#jgx>=_zo~d`72>ODDtyp zK%{9gwC17Jr=6p{P>EQ=&B!X4`RV{_VWPhoHN57RoH1Dy2WgSQJ6g-Z%L0ma9Qq9p zsMkrb9s+;$QjZ2|)9ohKg;+*SCGP`5;K;jLIy$)d7?_lP#0B~5BME-I%g)Ozs2Nm+ zDQx=rzCA7sBvB#f4pp+xS|0EuhJM9yRv`}Lxu|AJGgmBu#_ylQu#@vhH&|v{&B!Hh zKT)*l5Du47v{@nEj^vH_s8N`p@gv6lqW6}<+}EPztfi)Ar{ei7y!JG$v2+EnA3ZSp zuA5h?G*x1z0Zs)O@qQ1MAtJolB}O^x9T?GH|Fq=b&$sz2_2ByaMD2SAw|^UrL1Fjv zT|Bu!$LEEo(=$;<+?Bn+U(#vgkAa<+oBoHPCx`SGGKWxaw|xiZRhSNVi(pbN=h9@M3MuM0#xq&J*zCsXRPS zq$2`N^S^Es7ci~`>Ec5~9^%cCCh=VrNBkK+2Mxyp6&I&o@%G9ib(UFdZqA(N-1XL6 zlljm~y(<((c8%%gGkoRFoY&mD6*_nu9dH(N5d7L4V}odZGG>pu?gdJK@`CJxyt{dq z_`J(&l*PZ&>w?|6t%UCIqDfSP%}j^}=W8vp$2ONgA1Br+KTy}*kl)h{BIs-YAn!$b zAZ;vWGl}2VUYGMs*1D?V8F*MNbcl8CnH>j*^D#kd*zt@qfDiLWdT>+3JwPoyu{ONz zv}%?sGaj1csxMdQ6K4R91m^W(5PC%x^$^W`1WrF??bjX3r_#aqzQx3huA&}uQY>I> z6lD4Qb&jk5WE&IO)}UuQR-&I%An=?54<_zq_2@yq z)VUWDWnQm8wuGUGjSJfbJ-?WMkBn)zRCdhewPJ7j(_@U*b#!4R%XornlUt68+6c;h#I)uT+Z z8yt;XedTg*>4HskrSG1zqzd`WQ%)1P!LH{|q3vS%rJyt)*N3!-=1_^Py&DzA{VXO( z!&FEAN35ap3rn*lqj)9;&rZw1h1l(s(BTVmB=?*6KjVp!Y5@a>3C7Ht>a+n%1Gv!H zaM%Oh=}i4A-PN>tA^o}A$iIE*NZgJ*(1hJUeTfumJYvH@K-2hZ?fhTu?!&=wE#^s) zII)Pl)GO7bqq!84r0s*#fV$u&^Vj?1dh^L? z>b5||lbLoEmq=tTrW|r=FA(x$EV6a-s0x@kW5o`n2ow;*tNlen$yMd^OgqIfXvgDGevg zb(|m>TQ3;y@?&lL$pN6~Jz!QUPEbGq@*_i0YHzy*rr;qAI9!(awuA#|-nO0-7yYZD`O`EtHT}j`LtHm2 z1$EF(p5wKy^o`X3Bw%Zi?y68_4SKYEt=ACnJ90{sk8YYaNg0shbI~`5c%all|h<_bc{)x<1l()cinrCO4TSHMx<6xqLdu1+yRns7_zOMMUfn43U#Wu{n-- zPjX{CKTC-vnK{w!1=_N2)1oBqO!!7l4`MB$W3I@~J=kXOl-`-#P5V0@ee>m+U8!wE z=QeFfuPm!cq+qS?2z&DY*p1{Le$<1|!r{w0ym(7n)BCUcW+AhfeaZOOsQq)o)&Jh8L-OGGc zVVGgcgc$-oNE`+K#VR!w)Qb#hJHg51wvDDPu?ziDD2{#1ki0U2Y~Vg+d5L;H5Tf~a zowv00P=N5Ht`!+K+wM{pIAC-Jl7fr)ET*gi$CkeikQe!Zx_fmN8@d7Vi=YwdIOF)| zF?q6cY4|7XC_dY&#~O@0nw*>L!EbAs{CHLW+`7WoEXyT;WuGPD>c!)52ShSyvSL4Z zunrZwxU@QfawLNzLJw9yI5*M&noe#=iYA1;%dpYY`%s1ZcS{N7ko95l@zUT78;Q9@ z#>|-nz|4wyL&&=Jg26wjG1^snB81;sqGF!p9{#I`9HV75b#02G2jb6YV<)U5RGx9F zi-h0>bFp!$Z_RL?N|{MymW2SBwq>QT%jFDvv;g^n3!r{(Se`_{85yy_R=pf{NuBeE z?sH(jlH$ziux*>L9@805sm6NTpu*4je6MQ z;5%DowoDEF5ds`-qk3LB20jQ^of)j#j{dqiVz=9~mbXMAa}3xqW8p>lc2EP~G1;198+MLK$DFXI+I2cHBU zPDCNwboVoJg}JBoG{PbgGY+OS+~3{1yvp*9PT;_*n8Drp$U)_eP4tl_e>tSMM2QbD|7 zr@oV^TiD^XXLycrT9OHKVSigFFxG*ZhjDtENy88Z>hp!P3$s>^troZt@av!JM(WAuJ5tRvjeaAqTFfmW z&670~h4qQYMf!k?7(?@(kM|Ek)%Z~*zLlRPUs7hV?tBC6rOO?2dWP5AJT&Q>?Jg3 z8^TGjpPBJ)j6X^CWn^%jjoPF{TxtOMtOcw^w;v-r22J5#iRf3vzq2dUe2X6DD9wC7 zE+Y47Reci^w2-X<)%n1dgvw)42@ASc;A+V(pd%AQJRYz#N}immgF^#<)Wzmk1%1TX zJ>ei`$`(*5XFlGzx+@*oF*NbJ8ot9$y!t7)Zo(scRTMEiM4l*b3J?dPa6Zquz^kCg zE({#(N0ia9FCta#aG9^ z;?3dTU`_`0&ge7k(esqHP8##fnpv;_%|N{-P(cHk{wb)*q==_Quj_D#U3Mt4+1O?q zm9zmRWM^i^w_|?eDm{p2j#@(_6G#2A!62WsKL~^*K`iygy&6KgNQC1!k_F;Pp)7dt zM2C}a00)?mpc(Nd`CJWwz|Zzu$6>nhwUSaa^E?$PM-&@tfOfl4Dzsig*k@%fFcmDlvda-Nu5nam!1uf2E8&%Ya9N4CFz_&N_|(jsd~ zz=;U!(I00sz{h&;`o|fv8Yd^a5pcTuR<`dZ7u2%M-2qIk!Vnh^yTIePg7Xcf?Vlgc zH#g*{Pv30M59J@a!W38a46{aaY-Ig`vxlEcI-b)ypHTzNNHwOeC@fVkvkzDxCyEWb zZ;4%m&YD7mc|$#I{dfMThJd~VMdOdp2$Zyg8fm??hSex3#7)=8!%1do5<1XFB38vA z3Qpjr4!siEu5{id?QI3Z9E|QjG6gvXCsi4x5gsuL{{oA(^FS>7W|pjYYeD( z$fxr_zDuU#3U`yLu$&zAgZz7t>@&!nk?d2)%n12W1;@Z8OMt>tn0^#|B7_|eEy{TG z8!070i!Bdc3fNJiA^v8hY)k){WAwC@ZiF4Jch?p2g$ESb`+#f$n^|f0xzf1aT0Q zH3%)hpew%CC_!B(x;}Gh9q5n?le}E$F-~Y`@10uP){7dIf0VgQD9>$=6 zfF_&_$yGni?B^vNvR=5(W+IW#V7b&Cqo1^VUbVM_&?u&eDD|w18jv`Gen#k>Gh)Fn zpX|CPGWvTbK6Y24z@6Q{%|&nNJLZw12PwXk^TNKj_zqpWq*7S>4@hWD&pp zSa9o2!W(6`Dx;6QjXcFK*QgJ?<2$Ja5!4b*Hvj?I((h|Fn^kPR0YZ!}R;&rEwGxla zjn`@^t)SM&gNO3IJ}N)D7;ov43C5C-!J8uXsH^@d(^u-`4~mA3_tg4`z!X0fLpF{}6Y9%?(TM)BJP0!dAVSj*~)|?7%k~dvk>Iw?9!j#~i;8ZCNYPCZeoaPhFB{1U9eRQ5%MoR{zH_H1P`yRVQk93w)? zl2%GMx%_$4c~S1X4|iTu=qzd`YxWRBnN9@&IJV4%r4`3W!hJm|=7n(syQ5KtVxaJi z5n2%k(S@CX?ajiulN=GW(RpMLK>@TD z2i%TC*W2XN6PmdP*fJ{E8>sL>WbfR`IIUG*nn(p#o!`7akVBRrYVIc?gdkrLy0wS>5WJBBX2mUL8 z2Dqka^Jb8b==cuqh zKYZ}^VxJK-5^1KL2?x~9^F;IpfItKqEavRXYJU^f3^!3JfRGB~YA{E%s-wySLU`k={g-NNw9%uv%nQv2GAci8Rm*-7Bmm z-F~~ql2>1oPhigfCMa_4&O!as@f@y4z;hx%t}d&(y5#cvvID49=`Q5;9Tu%AXFkxnDI#kSe+5lQsVk>R&!5F9tDWDCQ!~16gEUIF(^qCp!r8Ej@ zuZ{;_WzQt>J!!QF{UTq{I}p_#T~2?pI%oQ)#_;Ktrbgv@cmjYn{MmbVuq z9`kq9Y$=DAu6KxsmKeu)z@O5w4L=ugB;ug=>Ou^pgm$+R9waXoUz~&41R*VEz?+B} z%`X~jyASH9AB&U%eywPxWXm1vYK%)EX?kBxHdRe@Z^kb54o+Hh#q+|m+G*aHnwD?!FI|*AX zIaAV;{ld}o_(KSsEv9cZlR9cH0C_f-h9PN^g_w`b2HaR2MH8cwvV$zCF7xoql(M0J zlqONpd&}Anhl^wzx~L*~bIn%VhYutF(o!2|tUZ0X4X|<9NdWP0bi6F-bi53b$AG{$ zP?00@FP%juhl&1y&^iSTraBBxGV}*33;er$YRTlk9xcNEI&b42v%f_&0o-#!zn4}l zE=p7C?xE7mQsMDNW@4TOt(=hw#JDN#b`eV?B=7`Xrm5y$pIg=dnE0M*8j}?+H{Pr* zk}`Nz=l83fbLbt$yrb)Spi6%1`E{4ApQVuE>@^#P_bqQc_C%jz`uMbyZeRxE!M&A% z4TN0r?$)i?T91aRq~b3P06y0jQ#PugA+>+zmI*aHa9NNTGbi**_afgLle}LHCi*#j zt!l4MRJWep#7!#QO*b-YKDWtORVq92u1GD9g&9p|P&wG!2A}o5T?NM4`yq_pnY>wE zOG$2pBQjHRj6aO-{Ax-zUHbW(tWhuZDLE4_lB)`D%}&pwDY(%J4{)mV=)T~$w@z1% z@p!y}wXCfVvvvVq2X2E{IaRitS8Ymz$1jb061+;bK_pFrYllcvF(^RpBCye>E=$K? zMS)V-UI6jdTM&-Rx`41){zPsYtqL`B&28{7G2wAw%Fv1k#M(hb# zAVQi?(I9teHwCvI%lQcWlrWx(jVlezW+|)k_@({r!mqK?q5AlgKwwQ+G~B1%(D1=Y z?}A`~V+`CcETxGqJE$>5h|%SKQiF7WwD2$G67W2)XX$E(UR>b=M*^8pe7HK1=VQ>|7xOmHR!t)(uO?Pbl-;*bc$$5A9CXC#K z%ouD?Ao~DUG1hkm-3z^pv$#)&bzGDD0-D(Hv+lQ8M^RWLlPt}g!{XBxH2OKE%Nii! zNtda>Yy%T68{WCB@vvRZ*MEN7Et~^dGM@wklQH`S`Krg^gt7+zid!-6!7&H1-=%*M zSjKT>_&AjOXuj57pdl5%(L3KdF=^B`of3lvh)_JzibE|9tdU9FGBM3vCbNS-H~7BX z{x?O}P*yCKuCXpBNn#qRV<0ys*i!7 zexv(+rQksH@#T8M5&O5s2+TM@1^VD(WFIu;>bPkHcj#Ee6eXD8Gdt_CfYgZ$ZW`n`;!0R=rn8u3JfBY+1B@g=#e9>zsG1M&xC5NIUnJ!Mm3iN`vd^;V&UOQI6X5E+0t*S2iTEn2AI|>A|y*u71H<{TVmLE5I z=Gpfknag|$Nfr*9!@B_Y07$<*}k&_(Av3K@9->*2}@eaSF8SBEtBF374?kG8nUzssVF&*J|Rp z3nAX{#6p*{K;Evx%l2C9_CQ4v#$2z)2URqk$|}g#vc9ScCj_#sQd>Vzc|Qq^89X6# z_fbI8Djp5o;A>F$>z&ot-C$4d#o)@0s;4?UG_B#y$8wz5-+IC0aB7M7u3P}KH`ztQ z7Sfd3#sNQ-JR}{UOPV%$}J|GnE$xI@^1tDO`JJ(IqlVco%!*`s2yP*vNBi|J*hNl# zx=KH^?%-yyfPkCvl+EgB>v)*u!MMkUQ1BQj>C|o=cV3b7>98gRRj+^mP@HgCzrsZ1 z_Rp-5V$^9`BQq9Jh9nZ$Qa7bA27rDa83%QVTyg=N>ZH0gZ=zY$caQDdo%tLrRJy$C ztb#~1i9SH1I(G?rQw+6PLyAGY9OH(AaB`BoSCbf6nol*_S=!C7%`DMv$OT;oeKEM- zwnjP93Pthv!`EOTQ2TZAxSeU1t#nka04b&TP86RC^t}X z;7C`VMM_gt+)I2-Z!bu+2#&6mQ2Kujg>n;Ue1Ce&+cTgc`)DAI{~!y;Dcnj5t`*Uf zKL~bS9y)L^o0!l7pUti*3UVNIEsp%1^ExwxHo&!Wyg=0kFMkS5%?i8WdE@Cp39u^R z{^&Jc7-0+Z?xPj9i9NBDhlH2=sT@D;QWl<+f-0PHn~q$z%lmfum&%v__(iL{=O=tA zby|u-7vJ^#wxy^2DOOpv$_%pQ2G|%`{U<5MSToC6i0 z>*>PM0{*n1K$Sn>2D&#z)!3+~c>Cx4>vLJw-mMI_2#q>JrBp6tAIy9aI(Q`669>WQ z8ES1>#?ISv z40~x!&ypgEW26n$=#5Hw0u_F`9J1}psV8?;B-kOXu6r>6Bde$hL!PSC`P3ZKk7(`7 zE;j+5=!MfrlAqFeP8(xY{{e&J0i`2VEo}4^)N@^pW3syr&dIktEZ*{qDehY%RUfgj6IjhfT(9t8pisEW7e*6@ z>;;Br6SX`5A!%KT<;Wm)c|D9gax#L%*+6QKdI~9Rs;HIK!M64iZ;P@ICY^H;&4417 zO{LqxbBuLDQ!_<-BlqR*sG4}KpDsAhsF#(LVx4Op!W5QEVZ!r#Mwfvra`8ta=yV0A zA*Z>{!zD4 z_6+%(cR?7gN((ryx~4RpMRD0i=n=yD&K$H5bq+WQ6rP31SrQWm`0D9HH`i=}-rhGd ztf_23X4}qoNL!mI^$Dy7-Rh?9^y)Oh zNa~UTN7sF+@TaJnygcDGy`oq`4k~ZWGWQQNxscMWRctV=%T?@Kr(GY~UGFmz%95<1 zRhu-WC9&fRxg!;(5UV3!jBhV<>75@2o=$sPdrL}V-6eMIqp@xs+$1DTmEr! z{I~!lAg4c**IVE>u;~lF;(js2Dfc!$l*RpITkA6{-GbqogWwHb-Jl%e%Whfr-9?wW zWo$et9-r)mTY6YhNId74ubT675atPBM8j-nL{e()Vr^B>5fv1}XcR?eRLdi1SbG!R zUL^Q+UO>?0I44{rCoKyBEs?Dr3?vgXCWwOX-n!6tno8xl3HsHY-|uAs`LFU)>Rszf z!Jx|oUaCs$SnD8_>x5ez<{Fx0)~^jdS;L?ssaC_{8h~|i3h}q9_Uzic3l?I)B#tsS z!`!bx)U&uss@ZeMeLBrE^9+z%(H}$h8JQ+Rx>(<0nS?iTeNL68e{-{dncDlQ7QS?v zDnFhc`oDCiD&la}4$;eUz8G8Iy4&BncTy%jVsh^7t;0lLkQZi~|Jc3{G+xnvz<-*BuwA}@W%Sj$rR zvs<2upXM{w)iv`rxDNS;`4$Vk6hbNsLm1>UfKW$@=n^!r3x= z^MLr+-R4kk)F70m7P?DtB75yn zileH|2h&?LzPZwI%u;vS8C|IAQ9ce>2U-09G2RX5gv^Cz4 zi&Z^Qn9FX>jf)I^A7D*6L3c}lUbU*NFs=sQl(zGrVA-F2Wi7s}6V;6E5V$_RI@AQEq5KO= zabmgV|9AQP4;(Y8it>akGKPjb6T!?Dc%46Ls5sR|5i1c7w#B;r0CztD1PbPAax-u$ z##|s>6J#Mq&|_!t#4XMUYz^j?cI3W;Jx!IINDjA`5~AM4EYF?tcWf+AeP6k>$G}gl zrc9FQEd%99yQ+XDSWAZRMRyK{`SZMYL|d+!&hXa@t4y_*v!ohLN6%SiON_>)jN6mO z7#**Xb7*AD2Oz1){s$F8r? z5=31fGrawlWtcI|Q+F`f$rAdn~SH?8xkfu+4>+Ry}WXB%?Bon`*pnK^K09*DT?sc{^qKe+p1@DyVU?>_;B zkc&J}ObX;KT3uk_cc=Raxy-J)LKskYV&|@W`3=6 z0CGU(2sKe;63Yz^SI^QH04D~3aho(e48V_li9Grg9{M1+-w-_Zd>OP&7i@}O#1zVhlXqYG&@RHjHNwy}t`3L8V!M-8} zP{`ev0~mS>MsEZ0)wHz?s~roo40b-85~U_d#6T)>Y zxul5ux69wHz;qyVG{CKx68hM9758PxqU!hOAr;G1BP275`xK{5Y4@jf9J>CeUA38c zXhn+vs&s>s`omAIwPFf!Wm8-xXvH#4%y@P?6;a_NJ6hn&S zXlr?UaPHQrn`Tf}SAB-a{6}}m1~o>L=FHndNbb*>yrtyUTc0X0Fvn0OYMBZk#hqAK+Hyqq`}QgBOohv=}4x5I^sHzJm|p`DVG zE9^nO$%6j$;Z?M=M#Im!gkjNbZS~CGM1057b4a|?3ESU3v)U>)NdksWo>Go)udo)N zKH16cK%~FAkpHm>_jS5Z9rkTGAy?Lme-UbD)Q}|@y|S>o6MZzsuIVxLm%_y5W>h`L zEFL0XTS4O+Y@wUQ8h$k=F~67zrlIUwEblp!k9UBqt&UwqB}z5n4?ID$N88>DyV#6H z*|aW(2oVw0Vq}g=r@*^S;UTun;g}Nu()eHM`!e@fpAJg1`sQyYHqB3$^B?`?Zbd)L zb;5X%TIJn6mIC%Cs#oaW87kG+9|}H%F@;x|2hQ&7K3I$0WKa^@c&*-chRkcPE}t80 zR?YWZan?&)Ou~jl5{$x;fuOjCE$$X`?6Mj`=I|Fje-|q`C8(-^$=Ju(DldJ&1+iSm zaM~>%hudMu#h}SO+{0=EN!42w;p-fhV!sZpf~?B5v$j#(_@X-;Z;idhlSJCzA8DpG z1eI1Ts+I3YReI{C2Od-ox_N%qSQT&B;cII4IhE%Rs~5Pz%M2T^l0mBlQ^4eLzvf&O z6wcG+5SBpYWA(LWbB=CVN&E&t%XaBZ4PK_XebIEu5#MCLcrKPe&J zq^Nsu@#A;-h^jr;8Ma2b;9`Zm#s;TNL52DEghHJjovY0F-*y$nZq*pa`MwpYgNEz& z7l2gx_G=Aa5Eb}!Ckvg=6T5LjRHy>#$tZ;Z=>-pW7!xLn#V6!&P9U^yCay3D#}u{l z0U{XKI9)J^wJ}ApxC{z2xG@iml%*s#gczNPb$PmTpy}_90uWj!AxFsB{eW$$>3L9L ztj>qd3}F{o&3b`LVJ|Z%Xm&647dOH1^@+*(UIZ7HiH;(lKS!*QnKlC4b>IPbAo|7@ z^fU!9N75>Axyra0@$COba_5d9jOI0Op0Zgo5}vIa+wI-O{{41-Zzc1^oo?uQ#oDCk zK1Q_ig6X%D!JitrS!8ieO1USW(B$-;MU++0!fM&pz&wq479kQNg%SY-_Z${s=$tN& zmum!;E#31|?i(duLvk<~bg638KT~W9G{wMRxQgrSKT`}4LzRiGy{5-7a-=D=Q?-|8 zhSYX-fYbO;;t_$LnG4dMqxuDoisR7`z#m(2ixeShbSAYwB5X1oSWho;&s9*Brhf!T)mc^|ui4w@l_mv0!G-!+kFeiS@H6k$mNqep7q0}mQ| zvbNU!;tbh8`}l3JU5uu`o&E(9RJA-3xUtM(pnXFh$!qS?0SXe-X3;a%GN>FuOSN^V zeR65+G{cmNFT!V4Ai8|4V)cGSZ9gON`Jq)65VcBkUvLn4^Ih(9>{~YXnc-XB)W-4b zkibCy85^;->W$YAF=rDqulH$C+6CkRa{0GB;2ZO&23p3gL}8Z`O$*OUy3{hk=3dg(A#E80UvMt_sQLf z@cuwKyUjuqn7QQ~hW3LM;MRa9wu}5KmOgMUNjLDEkMk`zit8>!DC1+31y!ZR-AybW zVFpI02>+tT|H6RmOea7emMUpcTSyKsqyr*GhrA+!I@opMgEQv1@2c1I)^CBivYf1o zF2(to{0gfN)63z|0(3P9ZF17$R9Dk5S?RQ_qVxOu5Y9eL0&$t!@zjZ;_+LoEF(D)p z!YFf)JO#pX=W@vt(j@cugd=Ybia-@(qofZap`}zY24+Z##VAC0k$fXn2c1xScfPi# zM}N`Kg$Ri~E8IEqGb7F&eL)9uFv9-tO5B7SE)g^)^?@5s0hA_H{5xC?009Y-342iL zzE1V0)=?f{{*MDbcz0l2H=tXmBfr~!H>^L0n@g?_rnu7%L?Ai*2<|nWb1HdBPLrSg zgXm3gX?_05c;zsBpnE2vuFDnX6J|+(sgJtv;~>r!`it8Nv>d@>8|A{0pw_Oj$|m!O zzVD>E@R{1eK`096k7LZUiMIuI{_ zE3y*YArS)Dg*c&tXHg_oho1(Kejj-A!-6Qul5G0niiMOPz=|S`;l2^PH{E66Mo7xS z`pKkh)m~rpBo86yD?2bR8gYVW@~54q{Kufk6#jwr^nOtnxUW=atufBpPdB|QKF{A! zLCtqClQ_`z*W0#~o5k;V2xL@}V(bvt1f8m7iO? zb4Q1ibpRxf^L{HotRF~bcaOMTFuBH^11k{g_{A=zFLeTZ$ogvB>sPfE9$A>P+p<3e zy1U=TJ~jZC5Sn9biI|W{BBsU6<8bK=f*VGigVO;jp5b1pfQ!-do6D<<=trDCWyAre zpoc>_UQI!=DDHBY0&gN*{z2W>A@YyNo;~UMfeu^N#M*aBk3q6fu|uHj9^F~qEpPp(G0T?WJv-sH|WI#T|fqbZ# zT3Q0w#es|Ps8PY$+Wft?PTHyIzJ`VboL4X`J}HPI zaE9aYJF*;fCkjIFNNeX|n*C|8z8MNqNoY*jIAHJQJ`Nt9d>n?q4LDEW(QYkcf~XAy zhuqSicw}~DCTc6M&cA`pcX%<*S|QYefNBk*Ehf@GL3=@Q?fQr)BfJ!7zm{Cr7d7H@ zDNv6}XIBKiA3!=sMeonI8R6Evd#6Ybbu>zAE(#CS+49VCkA;Ov)YFGq0Cp zD{ER7hsdGh`PHgJGdV%z#Z ziuENK{icSRzqX@AW}-z7<5jDR`xQT-iM&aWKW6XN>mJt;1$_RGu6GQtr0Lo~W81bh zv2EM7or$qyJDJ$F&54tl*tTs?a`y9n=jS=swg2taU0tgds=C*@Z;E`4!{#l<4QMAG z|Id+O9=B!cOn2UjRUIqd`PUjYc6Og-nZ0(8GU?q;n3@Yq!n#bY#%4vAL1#q->_!A+ z^h|1AtW@Fm&#*t)&5Hz-;m`3hNq|rsfeLBU%|Le9SaLD5sH9;h1?KWweg0)gRc+%C zCWsWnR`}wW#)S?FQzDL&N+Wn`L$>=FP)XF7ZV-Ft&@u@8;(>Y z)CJV8C5XlyGEUY-{IR)21m3 z-P#Hk4$;b?nOjwh$x=~Yh#}C*(dyV0tA$lqr=n+uSy^jERu8$d*w+d@qwMj%YG-H^ zv0gAS3S=6$8@{zIm>qp6*c(N&X)^hOw^gd(o-@}|dt_g89;^D|=NBVZPE*)PvNe1( zUklX!+jKVy=n_e^@O4-5o)l24!WnNGK-8jbI#D<8AA3Z}z`J6T%$dDJ*SCuTxZh)D8L?E8LhUn) zqIea}um$yuf<#ZV=qvmqgfGLb0yZ~|QUWgNi)*4h%dmB0OKYQ*=PhS{_v^F^%$Rq%I$GcJaGedMnBe^+zqawx=l54X$VhfMg>h1HH55+Y`iw z2Hc*9lOTQ{Cg_omNc*sj2!>2b6>!aEex-(cUz;XZ(F5VDt0Du~WtPmXVy|Fj44Lx| zt1ppgT^acat$)(;x3GFp<_ub01&;b&c~k(ZpF=5HP-Ov2Zp3O*7~z=|8$u{?MdsXU_6YKYZ~JsL;*=;<1;n z&CXh9mEP*f#Q|^n8&BsS9AieZOn}pJNHpSqSVg3)?CiYaz)=a8{}br4MP+C)`SVdP zdky?~QMM<>^Q2oYpB0!|%q)q83Deb!Zw55EDe&03!V#1Wta(Vav;RS zih(^EP7b^Oi>~8MBYf}3e6VCCV$leRXTrsIEG?{@{6H1r&O!VytV|Z7$YV4Lfu_6( z99>f$UHj_L?IcolY@6d*+H7UZKkZh>eWhb7sSzJ_)~RKc8~Q9bFovl|>==h=i0E^) z&Na1J^eUV;u(a`gu=2jG6rwPNsjcbjjJnrS&5aZ=_Naa#Q?CmZ|KE;NQ5=pF zjEy_Ju@;FIpe^H+&4tqYe}us!tpcz%Li64jg{g~0(qd|XB$O{%wEc^YI zk zCJhjqB+W%+&LdSR5!nQlyfu!i*MM2hjFQF4goa}>I~Ju&T{^$3IG2e1 z5Yr=^I@HBcFk>XfgI*qv%6M3sQk6gV)U2ma}s+xJynp44Kz)#CF*^ z4@^Cbju*5XzXjQz9eQxyR%VMvTo4}ep#aoz*~}5u85gj)>n-Nna3xbZYIz{bQ{z=U zdzn?O=5hZl4dAGVb_whdmW1i_K`qlovX7h{qCQ{5|BmcimRWUFyzYdu;EaOEt5zTz zuyU9tB~o5q4~z!2*n<7rpyK;LDBXZ`Pe{^_^gt+Ck;dvB?OMoMS`ch`H}HHuF$|dh zJ)X$`X;;+4%d5*cGY@<2P>^F(a`Ynf^(Z>w?gdBL9Xd5KmQb&iK;%z|2;CF}D)O^g zo-&s6Qx~I#oDV+4x>84Ef+UL;SzQXnzz0j2FTAX3zYwEA{Jq z2eABlFlBVHbdQm>oc+Ldldg7e>)j~0VVKjWmw)ARs{i5e?`tTVdPC`~#2B8DD4C|~QbkJq^8r_+=HKNa#b{~Ci}=jA1JZ{I z7UrZwXCgxVu;5dc`%^zX?`y5YZwaqj{+B8oDX{pch$T9t9sE|h&s|;2PN`Z$NI;Zk zwLrBkM(t@ew3{CaW2#68O%`BQODd))#`nL?%a0>>c9|H0<#o>1Hlw;YqDI7Wp0&) zIDfRJWOh_=lJqa@^(ZjGlW|nq4ER2o`#C!0wT>B`!^_R)syw zkr7KG#Jt_Q_n2)>V5? zD^d|8LW}h_eQ}T~j)60BlXVBC`zmdgrv&21OO(KbQjT_~i zgLQT!zjWM}4<;(|Nv>_51Hm1O(V!dF(^lx6P3VRtqqzg7TZ=33-WQEMAre8qxO5g( z1dMcPSvc%;5ez6gfYwCvh9pAo1-(~ERgp)%KHV>MZyBn#=Jl>ML1lHjVLT+-m{jVA zmw|bV*3rfTIdq6FQ1)ApMAoi2^Pg-f1_* z)e?C}Nvwlw{Oj-59B&u>Hcb&(+y04jXcYCv^%KpfLC;$U0K;W{DJNu%Z-9iBfSM{i zG<1TKjRhTAivOKJZn9|hVfOIk0BH49d*u2`HCm-u6v$57>EY}9u_chQ+xyQ$s0?Xv@uM3nf>@>BWnvq zz)u*iZicTtK+f6z{W!)v#~fy@+`d;)d-+(a?7`YksP;$z|1a9=z26L4E}+%1#8FI9 zOp(`-*FOmTe&q5W(8s``$Bmiz^KT?}y>>U_Q_*&sOjM9_olZAk4c|mw+xW}mgr8#8 zUUmAyc9q|*bAIKGP*;4~1n7t8ilAnxYxAYWHYsQE0$2yl&rXyBS{tNtS5W_8;S^x^ z0$&MmA^I8S*{rq)&s5{Q4|OK{L%;e6JukhN4sh4fR4Jv4)1i*8L0qu8iDvQ3&WoE@ z9)W3Ksr|6v&!=L2Ht4@qrr@30H*K!0ZaS@Q;}J#tZ9@DkK2E_}i7{V#dOj9aK!h3w z-N;IN1{ewA3VJ&{5-7QlfDcH>L)Nvb@$e`}XEKv7!O({?FuFpww5Mx9y0YvGU*}vN z{pXBsHZ`Xpt=pzxq^>aL1bNeyO9wjwDc7ho^qf_yvG8$aYa3|bv;EfaykUIeQf0Au z9lTz&hnQRMzXp9Zg*JpOA_9Nn>LprfEI7L}4e&+sIGGzPuk>iNBDTe1_~(z`mfCkm zxX@i^>zH}DA~J$+l=?5aG_8`OC5>_=Ui-saW0%t%j&mUv>g;$wSXIG{xlb7@x#Cb$^C`07bPQqkVjIDJMIT7|3 zj8}Hp(5~_te$<`AD~kR`i2T?`O2O@b6gqF~HFH5liog)tT`#pl?G%h&?3;fQDFq~i zWA)vfE&mQ-{$LzZ&U;MhvlcyHxx%o0*~5YEo(3Sf#g>_^I?hlHK7EP6G!@VtR#^&F z7?E~wFp7ShX1FX`XDlJ}a#LxZd}K7>97JR6p+t=&sSJ|#X--w#+M-|A%L||uWbazJ z6g4BT8=4n~lM=<-5)t}z4We~>(9ix=8Q8;7 zUwCagsK7CWF_*^rqI8aYNs$yuxqfF>%wl#Za3!_H`X-T{UD|dE7LWj-zzfk;;aR(3 z0+@DB_&94PS@UL*_Rmy(xrN17Ot5LBE}{BeE4&)8nkN~9sjTk z9vJ$JcP}%CiR3quwW~2ZGB8=_tr-A$-USL)Q98LTu328Ko!w&fPckp@ZQrm^L(iTn zks)MPJY(>+jXB(}bJN)r2cwTKYUmNmi|?rzU^u(1?=U7456OO?OybW&Mg~KSHciO| z;EruAu5o1}3@PIoR<5}B1y7OEy`EF-=U`7M-yN5Mh&>SIb#5P7q>`< zVNQ0u2dgmR5{X96@n)|Cx}4;?_sT?V4pYYVGl4dXD*YJzM-cqL8Y%E|CvC?}xID2^{p-k;N^UjZm9A7W?EKKuo@wi}JM z@X$(fqynq!ugVxY1lK{7Ti}2gJqqyZEC}>*Qix^{V`G>vxdPQxn#aK{cO`g;NgAFxbb0HK|cnZ3HRYX z6_FKA73njt3S`!U9Rkr1TR%nlv10)zLM@$61j}7W z1}_}kP{A$;qHF|z6}(4SqLb_lC)0Tw>Pq8n8Lsl62_J3u6XS*n7$r=DiR-cN#Q6F? zgGPA&jA?`1&}x!Hb+iMXyo(X$lhXECk73I(k6<-Z_(3@)(3J(1UL6K8%$1y{@_L+g zf92Hw=woL3g|~Sjm4*SxPGoG%wW6P*UPBuY;TK*D9^udn))%9)kPDR_LKfHXEa)QU!^4!9YY^fg`ak(T&)5!MgrLLIT3DYThx^h z*U$^E6iK-VG)j@9DLbAln&G_q^z75hcYLpY9@P!qhQ*Qdql`QS(Yyv}Ujs`(9El@M z_kw5!!BH_7u^6qhC6wiDwS^;7i+=&)3m${w&iEBOVipvreZ&7^xk9WxPNXjE64mf7 zC3KKm4#$U(jFkh(89PV4PIO&1AMZAT8h3YbeWt^>e_h2}m4mYk&>_ULNWhn6e{ne7 zW1Cv=nU>b4B9oKfDP;r+Ei{k9k~>n#)0GQU`)xLtp-ZN=3((8sXd@`{sJV0ple1(6 zk|3uSA5Uw%tB>UwT+f}IqQY=oB3qKxtl z=F=m0k@Ybsh>q#>`tO=xcg5Bp6xgiu4?N%H+ondj<~lrBw{FyMU2^q^m9zF=?R+AW zCN3QO-*$1I9sxVO)fJK!P>XK>WM#ec2ZZIr;a0c;#pTT>|G3G?&H^*^I>I)xtMbehc@nR1LYhV4)vcN%6s21p1e|B2~MPMid)(`9GDh zuMOu*A9Z!81g>z0lA`Tbg zt7|E9HCEt^7^291IMV+jzlS2lK(-@D#vyEJ_>pc{%G^VQ?Jzu`UD_krHh5cTA9XXn z&WvU|?%5>K#|;A)c9Lr3gSLV@KW?EMq|j|>ypb@{<%*p5r%+EYGdDqI}O z=|-8MehOy?u;-Rm@>D+URZMJfq7b0P!~>63t|=6Y;5}?EU=fWNm##m+dtopr#|~!^ z0$}QV~q6gnkiAbn5g2j4YmhlLeRoq6ZQZbfBQY8(i%0WRAcHSc5!G z!9Y)JvJo?fiA+Xd-RQRDslT7X2S_Es?&mw7UdzKoHzWu`--@FU7M2e_|}u05c)nauxh3 zn+IqLt<8+hg|Hl9)j^B*VqS;Ydmqt)%DV19L|lhdcgE^kx|2k`Y9L`N7pT^0g_^>2 z38ge+%cN^G5yGWR&=KgtIyK)732lROX~8U@X{`{~PS4L^q%lythZRgS_g9ca-tN6E z`>1J~Is5RxUr_=*rFQ-ttaf19Lp3n29rp!f9c6ZsD-Os^JHdjKe>i_Coa(+^psbzk z!cAAzz_dmmJ1-MeMNPj$$y?>RC(Kn&^TurAL1&A(qC1Pcj&YGHYmsGc9h?+jSCe4? zDv`__{XD!fCEkHpQzmEv*s-|$E2F2KAn6bQl2?2KHrq8cee_TG0UiT-+e%~~UawO< z3?}Va3Zn9k(Kd6q;UvK~@yYHoq)EHW&N#)2rEXev$g$}H{KVX%oxhntt5 zx1{jv5Z}Eto}w@DjF;3$4A)zQS?Ak;6*JzOZ81se?iV0Wr-3Wfgu-ukDg!m$FgOob zw+!hB+o~x2gtML~1l(Nv|0pjtE)bG^`RW>RW;pC`$%LUK$@C*5YJXgA-Qo>B&BK(4 zk7wkK^zf>yOgvXRZr6p!Dz;Vg94@a9A|z)AUTprMqQKGTkBRzOQI<9Slrfz>o;Wu_#R?>f zY@H_QJnM)LC8;$DLxHKx-r{1Nkvk;rQ$dwAj?bLS zkt85lo;A+kYPZN6aY(h>b&8%XZg1d(v+Hix7A>CcS5c(Wy zW5Gd=^*L%|-b17|0T0>n0|DyTLVP6U7b3r0(UwUT9&DZDPeBxfjvN92v}K~sMb|