summaryrefslogtreecommitdiff
path: root/Runtime/Physics2D/CollisionListener2D.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Runtime/Physics2D/CollisionListener2D.cpp')
-rw-r--r--Runtime/Physics2D/CollisionListener2D.cpp400
1 files changed, 400 insertions, 0 deletions
diff --git a/Runtime/Physics2D/CollisionListener2D.cpp b/Runtime/Physics2D/CollisionListener2D.cpp
new file mode 100644
index 0000000..2e7d2b6
--- /dev/null
+++ b/Runtime/Physics2D/CollisionListener2D.cpp
@@ -0,0 +1,400 @@
+#include "UnityPrefix.h"
+#include "CollisionListener2D.h"
+
+#if ENABLE_2D_PHYSICS
+
+#include "Runtime/Physics2D/Collider2D.h"
+#include "Runtime/Physics2D/RigidBody2D.h"
+#include "Runtime/Scripting/ScriptingUtility.h"
+#include "Runtime/Scripting/ScriptingExportUtility.h"
+#include "Runtime/Scripting/ScriptingManager.h"
+
+#include "Runtime/Profiler/Profiler.h"
+
+PROFILER_INFORMATION(gPhysics2DProfileContactPreSolveAcquire, "Physics2D.ContactPreSolveAcquire", kProfilerPhysics)
+PROFILER_INFORMATION(gPhysics2DProfileContactBeginAcquire, "Physics2D.ContactBeginAcquire", kProfilerPhysics)
+PROFILER_INFORMATION(gPhysics2DProfileContactEndAcquire, "Physics2D.ContactEndAcquire", kProfilerPhysics)
+PROFILER_INFORMATION(gPhysics2DProfileContactReporting, "Physics2D.ContactReporting", kProfilerPhysics)
+PROFILER_INFORMATION(gPhysics2DProfileContactReportTriggers, "Physics2D.ContactReportTriggers", kProfilerPhysics)
+PROFILER_INFORMATION(gPhysics2DProfileContactReportCollisions, "Physics2D.ContactReportCollisions", kProfilerPhysics)
+
+
+// --------------------------------------------------------------------------
+
+
+inline void VerifyObjectPtr(Object* obj)
+{
+ #if !UNITY_RELEASE
+ if (obj == NULL)
+ return;
+ Assert(Object::IDToPointer(obj->GetInstanceID()) == obj);
+ #endif
+}
+
+
+CollisionListener2D::CollisionListener2D()
+ : m_ReportingCollisions(false)
+{
+ m_Collisions.set_empty_key(std::make_pair((Collider2D*)NULL,(Collider2D*)NULL));
+ m_Collisions.set_deleted_key(std::make_pair((Collider2D*)~0,(Collider2D*)~0));
+}
+
+
+void CollisionListener2D::PreSolve(b2Contact* contact, const b2Manifold* oldManifold)
+{
+ PROFILER_AUTO(gPhysics2DProfileContactPreSolveAcquire, NULL);
+ Assert (!m_ReportingCollisions);
+
+ // Fetch the fixtures and colliders.
+ b2Fixture* fixture = contact->GetFixtureA();
+ b2Fixture* otherFixture = contact->GetFixtureB();
+ Collider2D* collider = reinterpret_cast<Collider2D*>(fixture->GetUserData());
+ VerifyObjectPtr (collider);
+ Collider2D* otherCollider = reinterpret_cast<Collider2D*>(otherFixture->GetUserData());
+ VerifyObjectPtr (otherCollider);
+
+ // Calculate the contact key.
+ Collider2D* firstCollider = collider;
+ Collider2D* secondCollider = otherCollider;
+ if (firstCollider->GetInstanceID() > secondCollider->GetInstanceID ())
+ std::swap (firstCollider, secondCollider);
+ const ColliderKey contactKey = std::make_pair (firstCollider, secondCollider);
+
+ // Find the contact.
+ ColliderMap::iterator colliderItr = m_Collisions.find (contactKey);
+ Assert (colliderItr != m_Collisions.end ());
+ Collision2D& collision = colliderItr->second;
+
+ // Ignore if the contact was not just added.
+ if (collision.m_ContactMode != Collision2D::ContactAdded)
+ return;
+
+ // Calculate the contacts.
+ collision.m_ContactCount = contact->GetManifold()->pointCount;
+ contact->GetWorldManifold( &collision.m_ContactManifold );
+
+ // Fetch the rigid-bodies.
+ Rigidbody2D* rigidbody = collider ? collider->GetRigidbody() : NULL;
+ VerifyObjectPtr (rigidbody);
+ Rigidbody2D* otherRigidbody = otherCollider ? otherCollider->GetRigidbody() : NULL;
+ VerifyObjectPtr (otherRigidbody);
+ b2Body* body = rigidbody != NULL ? rigidbody->GetBody () : GetPhysicsGroundBody ();
+ b2Body* otherBody = otherRigidbody != NULL ? otherRigidbody->GetBody () : GetPhysicsGroundBody ();
+
+ // Calculate relative velocity.
+ const b2Vec2& contactPoint = collision.m_ContactManifold.points[0];
+ const b2Vec2 bodyVelocity = body->GetLinearVelocityFromWorldPoint(contactPoint);
+ const b2Vec2 otherBodyVelocity = otherBody->GetLinearVelocityFromWorldPoint(contactPoint);
+ collision.m_RelativeVelocity.Set (otherBodyVelocity.x - bodyVelocity.x, otherBodyVelocity.y - bodyVelocity.y);
+}
+
+
+void CollisionListener2D::BeginContact(b2Contact* contact)
+{
+ PROFILER_AUTO(gPhysics2DProfileContactBeginAcquire, NULL);
+
+ Assert (!m_ReportingCollisions);
+ Assert (contact->IsTouching());
+
+ // Fetch the fixtures and colliders.
+ b2Fixture* fixture = contact->GetFixtureA();
+ b2Fixture* otherFixture = contact->GetFixtureB();
+ Collider2D* collider = reinterpret_cast<Collider2D*>(fixture->GetUserData());
+ VerifyObjectPtr (collider);
+ Collider2D* otherCollider = reinterpret_cast<Collider2D*>(otherFixture->GetUserData());
+ VerifyObjectPtr (otherCollider);
+
+ // Calculate the contact key.
+ Collider2D* firstCollider = collider;
+ Collider2D* secondCollider = otherCollider;
+ if (firstCollider->GetInstanceID() > secondCollider->GetInstanceID ())
+ std::swap (firstCollider, secondCollider);
+ const ColliderKey contactKey = std::make_pair (firstCollider, secondCollider);
+
+ // Find the contact.
+ ColliderMap::iterator colliderItr = m_Collisions.find (contactKey);
+
+ // If we already have a contact added for this collider-pair then we'll just bump-up the contact references.
+ // We do this because each collider can have multiple fixtures resulting in multiple contacts generated.
+ if (colliderItr != m_Collisions.end ())
+ {
+ Collision2D& collision = colliderItr->second;
+
+ // Increase contact references.
+ collision.m_ContactReferences++;
+
+ // Contact mode should revert to stay if it was removed else it's just added.
+ collision.m_ContactMode = collision.m_ContactMode == Collision2D::ContactRemoved ? Collision2D::ContactStay : Collision2D::ContactAdded;
+
+ return;
+ }
+
+ // Add the contact.
+ Collision2D& collision = m_Collisions[contactKey];
+
+ // Fetch the rigid-bodies.
+ Rigidbody2D* rigidbody = collider ? collider->GetRigidbody() : NULL;
+ VerifyObjectPtr (rigidbody);
+ Rigidbody2D* otherRigidbody = otherCollider ? otherCollider->GetRigidbody() : NULL;
+ VerifyObjectPtr (otherRigidbody);
+
+ // Populate the collision entry.
+ collision.m_ContactReferences = 1;
+ collision.m_Rigidbody = rigidbody;
+ collision.m_OtherRigidbody = otherRigidbody;
+ collision.m_Collider = collider;
+ collision.m_OtherCollider = otherCollider;
+ collision.m_Flipped = false;
+ collision.m_ContactMode = Collision2D::ContactAdded;
+ collision.m_TriggerCollision = fixture->IsSensor() || otherFixture->IsSensor();
+ collision.m_ContactCount = 0;
+ collision.m_RelativeVelocity = Vector2f::zero;
+}
+
+
+void CollisionListener2D::EndContact(b2Contact* contact)
+{
+ if (m_ReportingCollisions)
+ return;
+
+ PROFILER_AUTO(gPhysics2DProfileContactEndAcquire, NULL);
+
+ // Fetch the fixtures and colliders.
+ b2Fixture* fixture = contact->GetFixtureA();
+ b2Fixture* otherFixture = contact->GetFixtureB();
+ Collider2D* collider = reinterpret_cast<Collider2D*>(fixture->GetUserData());
+ VerifyObjectPtr (collider);
+ Collider2D* otherCollider = reinterpret_cast<Collider2D*>(otherFixture->GetUserData());
+ VerifyObjectPtr (otherCollider);
+
+ // Calculate the contact key.
+ Collider2D* firstCollider = collider;
+ Collider2D* secondCollider = otherCollider;
+ if (firstCollider->GetInstanceID() > secondCollider->GetInstanceID ())
+ std::swap (firstCollider, secondCollider);
+ const ColliderKey contactKey = std::make_pair (firstCollider, secondCollider);
+
+ // Find the contact.
+ ColliderMap::iterator colliderItr = m_Collisions.find (contactKey);
+ Assert (colliderItr != m_Collisions.end ());
+ Collision2D& collision = colliderItr->second;
+
+ // We need to reduce the contact references.
+ // We do this because each collider can have multiple fixtures resulting in multiple contacts generated.
+ UInt32& contactReferences = collision.m_ContactReferences;
+ contactReferences--;
+
+ // Finish if there still exists contact references.
+ if (contactReferences > 0)
+ return;
+
+ // Flag the contact as invalid if either collider is not active.
+ if (!collider->IsActive () || !collider->GetEnabled () || !otherCollider->IsActive() || !otherCollider->GetEnabled ())
+ {
+ collision.m_ContactMode = Collision2D::ContactInvalid;
+ return;
+ }
+
+ // Sanity!
+ VerifyObjectPtr (collision.m_Collider);
+ VerifyObjectPtr (collision.m_OtherCollider);
+ VerifyObjectPtr (collision.m_Rigidbody);
+ VerifyObjectPtr (collision.m_OtherRigidbody);
+
+ // Flag as just removed.
+ collision.m_ContactMode = Collision2D::ContactRemoved;
+}
+
+
+void CollisionListener2D::InvalidateColliderCollisions(Collider2D* collider)
+{
+ // Flag collider collision information for the specified collider as invalid.
+ // NOTE: Does not send any collision/trigger messages (matches what 3D physics is doing).
+ for (ColliderMap::iterator colliderItr = m_Collisions.begin(); colliderItr != m_Collisions.end(); ++colliderItr)
+ {
+ // Does this contact relate to this collider?
+ if (colliderItr->first.first == collider || colliderItr->first.second == collider)
+ {
+ // Yes, so flag it as a bad contact.
+ colliderItr->second.m_ContactMode = Collision2D::ContactInvalid;
+ }
+ }
+}
+
+
+void CollisionListener2D::DestroyColliderCollisions(Collider2D* collider)
+{
+ // Destroy collider collision information for the specified collider immediately.
+ for (ColliderMap::iterator colliderItr = m_Collisions.begin(); colliderItr != m_Collisions.end(); /**/)
+ {
+ // Fetch next collider.
+ ColliderMap::iterator nextColliderItr = colliderItr;
+ ++nextColliderItr;
+
+ // Does this contact relate to this collider?
+ if (colliderItr->first.first == collider || colliderItr->first.second == collider)
+ {
+ // Yes, so remove it.
+ m_Collisions.erase (colliderItr);
+ }
+
+ colliderItr = nextColliderItr;
+ }
+}
+
+
+void CollisionListener2D::ReportCollisions()
+{
+ PROFILER_AUTO(gPhysics2DProfileContactReporting, NULL);
+
+ Assert (!m_ReportingCollisions);
+ m_ReportingCollisions = true;
+
+ // Iterate all the active collider collisions.
+ for (ColliderMap::iterator colliderItr = m_Collisions.begin(); colliderItr != m_Collisions.end(); /**/)
+ {
+ // Fetch next collider.
+ ColliderMap::iterator nextColliderItr = colliderItr;
+ ++nextColliderItr;
+
+ // Fetch the collision.
+ Collision2D& collision = colliderItr->second;
+
+ // Fetch the contact mode.
+ Collision2D::ContactMode& contactMode = collision.m_ContactMode;
+
+ // Process the collision if it's not invalid.
+ if (contactMode != Collision2D::ContactInvalid)
+ {
+ // Further validate the collision.
+ VerifyObjectPtr (collision.m_Collider);
+ VerifyObjectPtr (collision.m_OtherCollider);
+ VerifyObjectPtr (collision.m_Rigidbody);
+ VerifyObjectPtr (collision.m_OtherRigidbody);
+
+ // Calculate the message targets.
+ Unity::Component* messageTarget = (Unity::Component*)collision.m_Collider;
+ Unity::Component* otherMessageTarget = (Unity::Component*)collision.m_OtherCollider;
+
+ // Reset the callback message.
+ const MessageIdentifier* callbackMessage = NULL;
+
+ // Is this a trigger collision?
+ if (collision.m_TriggerCollision)
+ {
+ PROFILER_AUTO(gPhysics2DProfileContactReportTriggers, NULL);
+
+ // Yes, so calculate the appropriate trigger callback message.
+ if (collision.m_ContactMode == Collision2D::ContactAdded)
+ callbackMessage = &kTriggerEnter2D;
+ else if (collision.m_ContactMode == Collision2D::ContactRemoved)
+ callbackMessage = &kTriggerExit2D;
+ else
+ callbackMessage = &kTriggerStay2D;
+
+ // Send trigger callbacks to both colliders
+ messageTarget->SendMessage (*callbackMessage, collision.m_OtherCollider, ClassID (Collider2D));
+ otherMessageTarget->SendMessage (*callbackMessage, collision.m_Collider, ClassID(Collider2D));
+ }
+ else
+ {
+ PROFILER_AUTO(gPhysics2DProfileContactReportCollisions, NULL);
+
+ // No, so calculate the appropriate collision callback message.
+ if (collision.m_ContactMode == Collision2D::ContactAdded)
+ callbackMessage = &kCollisionEnter2D;
+ else if (collision.m_ContactMode == Collision2D::ContactRemoved)
+ callbackMessage = &kCollisionExit2D;
+ else
+ callbackMessage = &kCollisionStay2D;
+
+ // Send collision callbacks to both colliders.
+ collision.m_Flipped = true;
+ messageTarget->SendMessage (*callbackMessage, &collision, ClassID (Collision2D));
+ collision.m_Flipped = false;
+ otherMessageTarget->SendMessage (*callbackMessage, &collision, ClassID (Collision2D));
+ }
+ }
+
+ // Remove contacts that are flagged as being removed.
+ if (contactMode == Collision2D::ContactRemoved || contactMode == Collision2D::ContactInvalid)
+ {
+ m_Collisions.erase (colliderItr);
+ }
+ else
+ {
+ // The collision is now at "stay" mode.
+ contactMode = Collision2D::ContactStay;
+ }
+
+ colliderItr = nextColliderItr;
+ }
+
+ m_ReportingCollisions = false;
+}
+
+
+#if ENABLE_SCRIPTING
+ScriptingObjectPtr ConvertCollision2DToScripting (Collision2D* input)
+{
+ Collision2D& collision = *reinterpret_cast<Collision2D*>(input);
+ ScriptingCollision2D scriptCollision;
+ ScriptingObjectPtr collider;
+ ScriptingObjectPtr otherCollider;
+
+ // Populate object targets.
+ if (collision.m_Flipped)
+ {
+ scriptCollision.rigidbody = Scripting::ScriptingWrapperFor (collision.m_OtherRigidbody);
+ collider = scriptCollision.collider = Scripting::ScriptingWrapperFor (collision.m_OtherCollider);
+ otherCollider = Scripting::ScriptingWrapperFor (collision.m_Collider);
+ scriptCollision.relativeVelocity = -collision.m_RelativeVelocity;
+ }
+ else
+ {
+ scriptCollision.rigidbody = Scripting::ScriptingWrapperFor (collision.m_Rigidbody);
+ collider = scriptCollision.collider = Scripting::ScriptingWrapperFor (collision.m_Collider);
+ otherCollider = Scripting::ScriptingWrapperFor (collision.m_OtherCollider);
+ scriptCollision.relativeVelocity = collision.m_RelativeVelocity;
+ }
+
+ // Populate contact array.
+ ScriptingArrayPtr contacts = CreateScriptingArray<ScriptingContactPoint2D>(GetScriptingManager ().GetCommonClasses ().contactPoint2D, collision.m_ContactCount);
+ scriptCollision.contacts = contacts;
+
+ // Fetch collision normal.
+ const b2Vec2& manifoldNormal = collision.m_Flipped ? -collision.m_ContactManifold.normal : collision.m_ContactManifold.normal;
+ const Vector2f collisionNormal (manifoldNormal.x, manifoldNormal.y);
+
+ // Populate contacts.
+ for (int index = 0; index < collision.m_ContactCount; ++index )
+ {
+#if UNITY_WINRT
+ ScriptingContactPoint2D contactPoint;
+#else
+ ScriptingContactPoint2D& contactPoint = Scripting::GetScriptingArrayElement<ScriptingContactPoint2D> (contacts, index);
+#endif
+ // Set contact point.
+ const b2Vec2& manifoldPoint = collision.m_ContactManifold.points[index];
+ contactPoint.point.Set (manifoldPoint.x, manifoldPoint.y);
+ contactPoint.normal = collisionNormal;
+
+ // Set colliders.
+ contactPoint.collider = collider;
+ contactPoint.otherCollider = otherCollider;
+
+#if UNITY_WINRT
+ // A slower way to set a value in the array:
+ // * we create a scripting object;
+ // * then marshal data from contactPoint to that scripting object
+ // * and only then we're setting it in the array
+ // At the moment there's no other way, unless we remove all ScriptingObjectPtr from ScriptingContactPoint2D
+ Scripting::SetScriptingArrayElement(contacts, index, CreateScriptingObjectFromNativeStruct<ScriptingContactPoint2D>(GetScriptingManager ().GetCommonClasses ().contactPoint2D, contactPoint));
+#endif
+ }
+
+ return CreateScriptingObjectFromNativeStruct<ScriptingCollision2D>(GetScriptingManager ().GetCommonClasses ().collision2D, scriptCollision);
+}
+#endif
+
+#endif // #if ENABLE_2D_PHYSICS