diff options
Diffstat (limited to 'Runtime/Physics2D')
38 files changed, 7241 insertions, 0 deletions
diff --git a/Runtime/Physics2D/BoxCollider2D.cpp b/Runtime/Physics2D/BoxCollider2D.cpp new file mode 100644 index 0000000..7b32134 --- /dev/null +++ b/Runtime/Physics2D/BoxCollider2D.cpp @@ -0,0 +1,167 @@ +#include "UnityPrefix.h" + +#if ENABLE_2D_PHYSICS +#include "Runtime/Physics2D/BoxCollider2D.h" +#include "Runtime/Physics2D/RigidBody2D.h" +#include "Runtime/Physics2D/Physics2DManager.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Filters/AABBUtility.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Profiler/Profiler.h" +#include "Runtime/Utilities/ValidateArgs.h" + +#include "External/Box2D/Box2D/Box2D.h" + +PROFILER_INFORMATION(gPhysics2DProfileBoxColliderCreate, "Physics2D.BoxColliderCreate", kProfilerPhysics) + +IMPLEMENT_CLASS (BoxCollider2D) +IMPLEMENT_OBJECT_SERIALIZE (BoxCollider2D) + + +// -------------------------------------------------------------------------- + + +BoxCollider2D::BoxCollider2D (MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +{ +} + + +BoxCollider2D::~BoxCollider2D () +{ +} + + +template<class TransferFunction> +void BoxCollider2D::Transfer (TransferFunction& transfer) +{ + Super::Transfer (transfer); + + TRANSFER (m_Size); + TRANSFER (m_Center); +} + + +void BoxCollider2D::CheckConsistency () +{ + Super::CheckConsistency (); + + if (IsFinite (m_Size)) + { + m_Size.x = std::max (PHYSICS_2D_SMALL_RANGE_CLAMP, m_Size.x); + m_Size.y = std::max (PHYSICS_2D_SMALL_RANGE_CLAMP, m_Size.y); + } + else + { + m_Size.Set (1.0f, 1.0f); + } + + if (!IsFinite (m_Center)) + m_Center = Vector2f::zero; +} + + +void BoxCollider2D::Reset () +{ + Super::Reset (); + + m_Size.Set (1.0f, 1.0f); + m_Center = Vector2f::zero; +} + + +void BoxCollider2D::SmartReset () +{ + Super::SmartReset (); + + AABB aabb; + if (GetGameObjectPtr () && CalculateLocalAABB (GetGameObject (), &aabb)) + { + m_Size.x = aabb.GetExtent().x * 2.0f; + m_Size.y = aabb.GetExtent().y * 2.0f; + m_Center.x = aabb.GetCenter().x; + m_Center.y = aabb.GetCenter().y; + return; + } + + m_Size.Set (1.0f, 1.0f); + m_Center = Vector2f::zero; +} + + +void BoxCollider2D::SetSize (const Vector2f& size) +{ + ABORT_INVALID_VECTOR2 (size, size, BoxCollider2D); + + // Finish if no change. + if (m_Size == size) + return; + + m_Size.x = std::max (PHYSICS_2D_SMALL_RANGE_CLAMP, size.x); + m_Size.y = std::max (PHYSICS_2D_SMALL_RANGE_CLAMP, size.y); + + SetDirty (); + + Create(); +} + + +void BoxCollider2D::SetCenter (const Vector2f& center) +{ + ABORT_INVALID_VECTOR2 (center, center, BoxCollider2D); + + // Finish if no change. + if (m_Center == center) + return; + + m_Center = center; + SetDirty (); + + Create(); +} + + +// -------------------------------------------------------------------------- + + +void BoxCollider2D::Create (const Rigidbody2D* ignoreRigidbody) +{ + PROFILER_AUTO(gPhysics2DProfileBoxColliderCreate, NULL); + + // Ensure we're cleaned-up. + Cleanup (); + + // Ignore if not active. + if (!IsActive()) + return; + + // Calculate collider transformation. + Matrix4x4f relativeTransform; + b2Body* body; + CalculateColliderTransformation (ignoreRigidbody, &body, relativeTransform); + + // Calculate collider center. + const Vector3f scale = GetComponent(Transform).GetWorldScaleLossy(); + Vector3f center = relativeTransform.MultiplyPoint3 (Vector3f(m_Center.x * scale.x, m_Center.y * scale.y, 0.0f)); + + // Calculate rotation. + Quaternionf quat; + MatrixToQuaternion(relativeTransform, quat); + const Vector3f euler = QuaternionToEuler(quat); + + // Calculate scaled size. + Vector3f size = GetComponent (Transform).GetWorldScaleLossy (); + size.x = max(Abs(size.x * m_Size.x), PHYSICS_2D_SMALL_RANGE_CLAMP); + size.y = max(Abs(size.y * m_Size.y), PHYSICS_2D_SMALL_RANGE_CLAMP); + + // Create the shape. + b2PolygonShape shape; + shape.SetAsBox (size.x*0.5f, size.y*0.5f, b2Vec2(center.x, center.y), euler.z); + b2FixtureDef def; + def.shape = &shape; + + // Finalize the creation. + FinalizeCreate (def, body); +} + +#endif // #if ENABLE_2D_PHYSICS diff --git a/Runtime/Physics2D/BoxCollider2D.h b/Runtime/Physics2D/BoxCollider2D.h new file mode 100644 index 0000000..2c40727 --- /dev/null +++ b/Runtime/Physics2D/BoxCollider2D.h @@ -0,0 +1,38 @@ +#pragma once + +#if ENABLE_2D_PHYSICS || DOXYGEN + +#include "Runtime/Physics2D/Collider2D.h" +#include "Runtime/Math/Vector2.h" + + +// -------------------------------------------------------------------------- + + +class BoxCollider2D : public Collider2D +{ +public: + REGISTER_DERIVED_CLASS (BoxCollider2D, Collider2D) + DECLARE_OBJECT_SERIALIZE (BoxCollider2D) + + BoxCollider2D (MemLabelId label, ObjectCreationMode mode); + + virtual void CheckConsistency (); + virtual void Reset (); + virtual void SmartReset (); + + void SetSize (const Vector2f& size); + const Vector2f& GetSize () const { return m_Size; } + + void SetCenter (const Vector2f& center); + const Vector2f& GetCenter() const { return m_Center; } + +protected: + virtual void Create (const Rigidbody2D* ignoreRigidbody = NULL); + +private: + Vector2f m_Size; ///< The size of the box. + Vector2f m_Center; ///< The offset of the box. +}; + +#endif diff --git a/Runtime/Physics2D/CircleCollider2D.cpp b/Runtime/Physics2D/CircleCollider2D.cpp new file mode 100644 index 0000000..a34a94b --- /dev/null +++ b/Runtime/Physics2D/CircleCollider2D.cpp @@ -0,0 +1,157 @@ +#include "UnityPrefix.h" + +#if ENABLE_2D_PHYSICS +#include "Runtime/Physics2D/CircleCollider2D.h" +#include "Runtime/Physics2D/RigidBody2D.h" +#include "Runtime/Physics2D/Physics2DManager.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Filters/AABBUtility.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Profiler/Profiler.h" +#include "Runtime/Utilities/ValidateArgs.h" + +#include "External/Box2D/Box2D/Box2D.h" + +PROFILER_INFORMATION(gPhysics2DProfileCircleColliderCreate, "Physics2D.CircleColliderCreate", kProfilerPhysics) + + +IMPLEMENT_CLASS (CircleCollider2D) +IMPLEMENT_OBJECT_SERIALIZE (CircleCollider2D) + + +// -------------------------------------------------------------------------- + + +CircleCollider2D::CircleCollider2D (MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +{ +} + + +CircleCollider2D::~CircleCollider2D () +{ +} + + +template<class TransferFunction> +void CircleCollider2D::Transfer (TransferFunction& transfer) +{ + Super::Transfer (transfer); + + TRANSFER (m_Radius); + TRANSFER (m_Center); +} + + +void CircleCollider2D::CheckConsistency () +{ + Super::CheckConsistency (); + + m_Radius = clamp<float> (m_Radius, PHYSICS_2D_SMALL_RANGE_CLAMP, PHYSICS_2D_LARGE_RANGE_CLAMP); + + if (!IsFinite (m_Center)) + m_Center = Vector2f::zero; +} + + +void CircleCollider2D::Reset () +{ + Super::Reset (); + + m_Radius = 0.5f; + m_Center = Vector2f::zero; +} + + +void CircleCollider2D::SmartReset () +{ + Super::SmartReset (); + + AABB aabb; + if (GetGameObjectPtr () && CalculateLocalAABB (GetGameObject (), &aabb)) + { + Vector3f dist = aabb.GetExtent (); + m_Radius = clamp<float> (std::max(dist.x, dist.y), PHYSICS_2D_SMALL_RANGE_CLAMP, PHYSICS_2D_LARGE_RANGE_CLAMP); + m_Center.x = aabb.GetCenter().x; + m_Center.y = aabb.GetCenter().y; + return; + } + + m_Radius = 0.5f; + m_Center = Vector2f::zero; +} + + +void CircleCollider2D::SetRadius (float radius) +{ + ABORT_INVALID_FLOAT (radius, radius, CircleCollider2D); + + // Finish if no change. + if (m_Radius == radius) + return; + + m_Radius = clamp<float> (radius, PHYSICS_2D_SMALL_RANGE_CLAMP, PHYSICS_2D_LARGE_RANGE_CLAMP); + + if (GetShape() == NULL) + return; + + SetDirty (); + Create(); +} + + +void CircleCollider2D::SetCenter (const Vector2f& center) +{ + ABORT_INVALID_VECTOR2 (center, center, CircleCollider2D); + + // Finish if no change. + if (m_Center == center) + return; + + m_Center = center; + + SetDirty (); + Create(); +} + + +// -------------------------------------------------------------------------- + + +void CircleCollider2D::Create (const Rigidbody2D* ignoreRigidbody) +{ + PROFILER_AUTO(gPhysics2DProfileCircleColliderCreate, NULL); + + // Ensure we're cleaned-up. + Cleanup (); + + // Ignore if not active. + if (!IsActive()) + return; + + // Calculate collider transformation. + Matrix4x4f relativeTransform; + b2Body* body; + CalculateColliderTransformation (ignoreRigidbody, &body, relativeTransform); + + // Fetch scale. + const Vector3f scale = GetComponent(Transform).GetWorldScaleLossy(); + + // Calculate collider center. + Vector3f center = relativeTransform.MultiplyPoint3(Vector3f(m_Center.x * scale.x, m_Center.y * scale.y, 0.0f)); + + // Calculate scaled radius. + const float scaledRadius = clamp<float> (max( PHYSICS_2D_SMALL_RANGE_CLAMP, max (Abs (scale.x), Abs (scale.y)) * m_Radius), PHYSICS_2D_SMALL_RANGE_CLAMP, PHYSICS_2D_LARGE_RANGE_CLAMP); + + // Create the shape. + b2CircleShape shape; + shape.m_p.Set(center.x, center.y); + shape.m_radius = scaledRadius; + b2FixtureDef def; + def.shape = &shape; + + // Finalize the creation. + FinalizeCreate (def, body); +} + +#endif // #if ENABLE_2D_PHYSICS diff --git a/Runtime/Physics2D/CircleCollider2D.h b/Runtime/Physics2D/CircleCollider2D.h new file mode 100644 index 0000000..f30b733 --- /dev/null +++ b/Runtime/Physics2D/CircleCollider2D.h @@ -0,0 +1,39 @@ +#pragma once + +#if ENABLE_2D_PHYSICS || DOXYGEN + +#include "Runtime/Physics2D/Collider2D.h" +#include "Runtime/Math/Vector2.h" + + +// -------------------------------------------------------------------------- + + +class CircleCollider2D : public Collider2D +{ +public: + REGISTER_DERIVED_CLASS (CircleCollider2D, Collider2D) + DECLARE_OBJECT_SERIALIZE (CircleCollider2D) + + CircleCollider2D (MemLabelId label, ObjectCreationMode mode); + // virtual ~CircleCollider2D (); declared-by-macro + + virtual void CheckConsistency (); + virtual void Reset (); + virtual void SmartReset (); + + void SetRadius (float radius); + float GetRadius () const { return m_Radius; } + + void SetCenter (const Vector2f& center); + const Vector2f& GetCenter() const { return m_Center; } + +protected: + virtual void Create (const Rigidbody2D* ignoreRigidbody = NULL); + +private: + float m_Radius; ///< The radius of the circle. range { 0.0001, 1000000 } + Vector2f m_Center; ///< The offset of the circle. +}; + +#endif diff --git a/Runtime/Physics2D/Collider2D.cpp b/Runtime/Physics2D/Collider2D.cpp new file mode 100644 index 0000000..9e34e60 --- /dev/null +++ b/Runtime/Physics2D/Collider2D.cpp @@ -0,0 +1,441 @@ +#include "UnityPrefix.h" + +#if ENABLE_2D_PHYSICS +#include "Runtime/Physics2D/Collider2D.h" +#include "Runtime/Physics2D/RigidBody2D.h" +#include "Runtime/Physics2D/Physics2DManager.h" +#include "Runtime/Physics2D/Physics2DSettings.h" +#include "Runtime/Physics2D/Physics2DMaterial.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Profiler/Profiler.h" +#include "Runtime/Utilities/Utility.h" + + +PROFILER_INFORMATION(gPhysics2DProfileColliderCleanup, "Physics2D.ColliderCleanup", kProfilerPhysics) +PROFILER_INFORMATION(gPhysics2DProfileColliderShapeGeneration, "Physics2D.ColliderShapeGeneration", kProfilerPhysics) +PROFILER_INFORMATION(gPhysics2DProfileColliderTransformChanged, "Physics2D.ColliderTransformChanged", kProfilerPhysics) +PROFILER_INFORMATION(gPhysics2DProfileColliderTransformParentChanged, "Physics2D.ColliderTransformParentChanged", kProfilerPhysics) +PROFILER_INFORMATION(gPhysics2DProfileColliderTransformScaleChanged, "Physics2D.ColliderTransformScaleChanged", kProfilerPhysics) +PROFILER_INFORMATION(gPhysics2DProfileColliderTransformPositionRotationChanged, "Physics2D.ColliderTransformPositionRotationChanged", kProfilerPhysics) + + +IMPLEMENT_CLASS_HAS_INIT (Collider2D) +IMPLEMENT_OBJECT_SERIALIZE (Collider2D) +INSTANTIATE_TEMPLATE_TRANSFER (Collider2D) + +// -------------------------------------------------------------------------- + + +Collider2D::Collider2D (MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +, m_IsTrigger(false) +, m_IsStaticBody(false) +, m_LocalTransformInitialized(false) +{ +} + + +Collider2D::~Collider2D () +{ + Cleanup (); +} + + +void Collider2D::InitializeClass () +{ + REGISTER_MESSAGE (Collider2D, kTransformChanged, TransformChanged, int); +} + + +template<class TransferFunction> +void Collider2D::Transfer (TransferFunction& transfer) +{ + Super::Transfer (transfer); + + TRANSFER(m_Material); + transfer.Transfer (m_IsTrigger, "m_IsTrigger"); + transfer.Align(); +} + + +void Collider2D::Reset () +{ + Super::Reset (); + + m_Material = 0; + m_IsTrigger = false; +} + + +void Collider2D::AwakeFromLoad(AwakeFromLoadMode awakeMode) +{ + Super::AwakeFromLoad (awakeMode); + + // Ignore if not active. + if (!IsActive()) + return; + + // (Re)create collider if appropriate. + Create (); +} + + +void Collider2D::Deactivate (DeactivateOperation operation) +{ + Super::Deactivate (operation); + Cleanup (); + + // Destroy collider collisions immediately. + GetPhysics2DManager().DestroyColliderCollisions (this); +} + + +void Collider2D::Cleanup () +{ + PROFILER_AUTO(gPhysics2DProfileColliderCleanup, NULL) + + // Process any shapes. + if (m_Shapes.size() > 0) + { + if (m_IsStaticBody) + { + // Destroy the fixtures + b2Body* groundBody = GetPhysicsGroundBody (); + for (int i = 0; i < m_Shapes.size(); ++i) + groundBody->DestroyFixture(m_Shapes[i]); + } + else + { + Rigidbody2D* rigidBody = GetRigidbody(); + if (rigidBody) + { + // Destroy the fixtures. + b2Body* body = rigidBody->GetBody (); + for (int i = 0; i < m_Shapes.size(); ++i) + body->DestroyFixture(m_Shapes[i]); + + // Recalculate the collider body-mass. + rigidBody->CalculateColliderBodyMass (); + } + } + + m_Shapes.clear(); + + // Invalidate any persisted contact information. + GetPhysics2DManager().InvalidateColliderCollisions (this); + } + + m_IsStaticBody = false; +} + + +void Collider2D::RecreateCollider (const Rigidbody2D* ignoreRigidbody) +{ + if (IsActive () && GetEnabled()) + Create (ignoreRigidbody); + else + Cleanup (); +} + + +void Collider2D::AddToManager () +{ + // Create the collider if not already created. + if (m_Shapes.size() == 0) + Create (); +} + + +void Collider2D::RemoveFromManager () +{ + // Destroy the collider. + Cleanup (); +} + + +void Collider2D::SetIsTrigger (bool trigger) +{ + // Finish if no change. + if (m_IsTrigger == trigger) + return; + + m_IsTrigger = trigger; + + SetDirty(); + + if (IsActive () && GetEnabled()) + Create (); +} + + +PPtr<PhysicsMaterial2D> Collider2D::GetMaterial () +{ + return m_Material; +} + + +void Collider2D::SetMaterial (PPtr<PhysicsMaterial2D> material) +{ + if (m_Material == material) + return; + + SetDirty (); + m_Material = material; +} + + +Rigidbody2D* Collider2D::GetRigidbody () +{ + // Finish if a static body or no shapes. + if (m_IsStaticBody || m_Shapes.size() == 0) + return NULL; + + // Fetch body from first shape. + b2Body* body = m_Shapes[0]->GetBody(); + + if (!body) + return NULL; + + return (Rigidbody2D*)body->GetUserData(); +} + + +bool Collider2D::OverlapPoint (const Vector2f& point) const +{ + // Cannot perform query if not active. + if (!IsActive ()) + return false; + + const b2Vec2 testPoint(point.x, point.y); + + // Test all fixtures for the point. + for (FixtureArray::const_iterator fixtureItr = m_Shapes.begin (); fixtureItr != m_Shapes.end (); ++fixtureItr) + { + if ((*fixtureItr)->TestPoint (testPoint)) + return true; + } + + return false; +} + + +void Collider2D::TransformChanged (int changeMask) +{ + PROFILER_AUTO(gPhysics2DProfileColliderTransformChanged, NULL) + + // Finish if transform message is disabled. + if (!GetPhysics2DManager().IsTransformMessageEnabled()) + return; + + // Recreate the collider if the parent body has changed. + if (changeMask & Transform::kParentingChanged) + { + PROFILER_AUTO(gPhysics2DProfileColliderTransformParentChanged, NULL) + + Rigidbody2D* currentBody = GetRigidbody(); + Rigidbody2D* newBody = Rigidbody2D::FindRigidbody (GetGameObjectPtr()); + if (newBody != currentBody) + { + Create(); + return; + } + } + + // Recreate the collider if the scale has changed. + if (changeMask & Transform::kScaleChanged) + { + PROFILER_AUTO(gPhysics2DProfileColliderTransformScaleChanged, NULL) + + Create(); + return; + } + + // Recreate the collider if the position or rotation has changed. + if (changeMask & (Transform::kPositionChanged | Transform::kRotationChanged)) + { + PROFILER_AUTO(gPhysics2DProfileColliderTransformPositionRotationChanged, NULL) + + // Always recreate static bodies but only recreate non-static bodies if the rigid-body exists + // on a parent GameObject i.e. this is a compound object. + if (!m_IsStaticBody && (QueryComponent (Rigidbody2D) != NULL || !HasLocalTransformChanged ())) + return; + + Create(); + } +} + + +// -------------------------------------------------------------------------- + + +void Collider2D::FinalizeCreate(b2FixtureDef& def, b2Body* body, const dynamic_array<b2Shape*>* shapes) +{ + Assert (m_Shapes.size() == 0); + + PROFILER_AUTO(gPhysics2DProfileColliderShapeGeneration, NULL) + + // Don't create the collider if not active and enabled. + if (!(IsActive () && GetEnabled ())) + return; + + // Initialize fixture definition. + if (m_Material.IsNull ()) + { + PhysicsMaterial2D* defaultMaterial = GetPhysics2DSettings ().GetDefaultPhysicsMaterial (); + if (defaultMaterial != NULL ) + { + def.friction = defaultMaterial->GetFriction (); + def.restitution = defaultMaterial->GetBounciness (); + } + else + { + def.friction = 0.4f; + def.restitution = 0.0f; + } + } + else + { + def.friction = m_Material->GetFriction(); + def.restitution = m_Material->GetBounciness(); + } + def.density = 1.0f; // Body mass is calculated based upon a mass calculation of each shape unit-density. + def.userData = this; + def.isSensor = m_IsTrigger; + + // Fetch the associated rigid-body. + // NOTE: There won't be one if the body is the ground-body! + Rigidbody2D* rigidBody = (Rigidbody2D*)body->GetUserData (); + + // Do we have any shapes? + if (shapes) + { + // Yes, so add shape to existing set. + const int shapeCount = shapes->size(); + + Assert(shapeCount > 0); + Assert(def.shape == 0); + + m_Shapes.resize_uninitialized(shapeCount); + for (int i = 0; i < shapeCount; ++i) + { + b2FixtureDef prototype = def; + prototype.shape = (*shapes)[i]; + m_Shapes[i] = body->CreateFixture(&prototype); + } + } + else + { + // No, so add new shape to set. + m_Shapes.resize_uninitialized(1); + m_Shapes[0] = body->CreateFixture(&def); + } + + // Calculate the collider body mass. + if (rigidBody != NULL) + rigidBody->CalculateColliderBodyMass (); + + // Update the local transform. + UpdateLocalTransform (); +} + + +void Collider2D::CalculateColliderTransformation (const Rigidbody2D* ignoreRigidbody, b2Body** attachedBody, Matrix4x4f& matrix) +{ + // Fetch the transform. + const Transform& transform = GetComponent(Transform); + + // Do we have a rigid-body on the same game object? + Rigidbody2D* rigidBody = QueryComponent (Rigidbody2D); + if (rigidBody && rigidBody != ignoreRigidbody && rigidBody->IsActive () && rigidBody->GetBody () != NULL) + { + // Yes, so no transformation. + matrix.SetIdentity(); + + // Ensure the rigid-body is available. + Assert (rigidBody->GetBody() != NULL); + + // Set attached body. + *attachedBody = rigidBody->GetBody(); + + // Flag as non-static. + m_IsStaticBody = false; + + return; + } + + // Find valid rigid-body parent transform. + Transform* parentTransform = transform.GetParent (); + + while(parentTransform) + { + // Fetch the grandparent transform. + Transform* grandParentTransform = parentTransform->GetParent (); + + // Fetch the parent game object. + GameObject* go = parentTransform->GetGameObjectPtr (); + if (go) + { + // Fetch any rigid-body. + rigidBody = go->QueryComponent (Rigidbody2D); + + // Is the rigid-body valid or we're at the transform root? + if (rigidBody && rigidBody != ignoreRigidbody && rigidBody->IsActive () && rigidBody->GetBody () != NULL) + { + // Yes, so calculate its relative transformation. + Vector3f childPosition = transform.GetPosition (); + Quaternionf childRotation = transform.GetRotation (); + Matrix4x4f childMatrix, parentMatrix; + childMatrix.SetTR (childPosition, childRotation); + parentMatrix = parentTransform->GetWorldToLocalMatrixNoScale (); + MultiplyMatrices4x4 (&parentMatrix, &childMatrix, &matrix); + + // Ensure the rigid-body is available. + Assert (rigidBody->GetBody() != NULL); + + // Set attached body. + *attachedBody = rigidBody->GetBody(); + + // Flag as non-static. + m_IsStaticBody = false; + + return; + } + } + + // Move up hierarchy. + parentTransform = grandParentTransform; + } + + // Fetch the transformation from the origin (ground-body position). + matrix = transform.GetLocalToWorldMatrixNoScale(); + + // Set attached body (ground-body). + *attachedBody = GetPhysicsGroundBody(); + + // Flag as static. + m_IsStaticBody = true; +} + + +bool Collider2D::HasLocalTransformChanged () const +{ + // Fetch the transform. + const Transform& transform = GetComponent(Transform); + + return !m_LocalTransformInitialized || m_LocalPosition != transform.GetLocalPosition () || m_LocalRotation != transform.GetLocalRotation (); +} + + +void Collider2D::UpdateLocalTransform () +{ + // Fetch the transform. + const Transform& transform = GetComponent(Transform); + + m_LocalPosition = transform.GetLocalPosition (); + m_LocalRotation = transform.GetLocalRotation (); + m_LocalTransformInitialized = true; +} + + +#endif // #if ENABLE_2D_PHYSICS diff --git a/Runtime/Physics2D/Collider2D.h b/Runtime/Physics2D/Collider2D.h new file mode 100644 index 0000000..8b62ce5 --- /dev/null +++ b/Runtime/Physics2D/Collider2D.h @@ -0,0 +1,90 @@ +#pragma once + +#if ENABLE_2D_PHYSICS || DOXYGEN + +#include "Runtime/BaseClasses/GameObject.h" +#include "Runtime/GameCode/Behaviour.h" +#include "Runtime/Math/Quaternion.h" +#include "Runtime/Math/Vector2.h" +#include "Runtime/Math/Vector3.h" +#include "Runtime/Utilities/dynamic_array.h" +#include "External/Box2D/Box2D/Box2D.h" + +class Rigidbody2D; +class Transform; +class Matrix4x4f; +class PhysicsMaterial2D; + + +// -------------------------------------------------------------------------- + + +class Collider2D : public Behaviour +{ +public: + typedef dynamic_array<b2Fixture*> FixtureArray; + +public: + REGISTER_DERIVED_ABSTRACT_CLASS (Collider2D, Behaviour) + DECLARE_OBJECT_SERIALIZE (Collider2D) + + Collider2D (MemLabelId label, ObjectCreationMode mode); + // virtual ~Collider2D (); declared-by-macro + + static void InitializeClass (); + static void CleanupClass () {} + + virtual void Reset (); + virtual void AwakeFromLoad(AwakeFromLoadMode mode); + virtual void Deactivate (DeactivateOperation operation); + void Cleanup (); + + // Colliders are recreated when deleting a rigibody component from existing game object; + // since colliders now have to be attached to the "static body". Old RigidBody2D component + // is not destroyed at this point yet, and thus it should be ignored when finding which + // RB to attach colliders to. + void RecreateCollider (const Rigidbody2D* ignoreRigidbody); + + virtual void AddToManager (); + virtual void RemoveFromManager (); + + void SetIsTrigger (bool trigger); + bool GetIsTrigger () const { return m_IsTrigger; } + + PPtr<PhysicsMaterial2D> GetMaterial (); + void SetMaterial (PPtr<PhysicsMaterial2D> material); + + bool OverlapPoint (const Vector2f& point) const; + + // Shapes. + inline int GetShapeCount() const { return m_Shapes.size(); } + const b2Fixture* GetShape() const { return (m_Shapes.size() ? m_Shapes[0] : 0); } + b2Fixture* GetShape() { return (m_Shapes.size() ? m_Shapes[0] : 0); } + const FixtureArray& GetShapes() const { return m_Shapes; } + FixtureArray& GetShapes() { return m_Shapes; } + + Rigidbody2D* GetRigidbody (); + + void TransformChanged (int changeMask); + +protected: + virtual void Create (const Rigidbody2D* ignoreRigidbody = NULL) = 0; + void FinalizeCreate (b2FixtureDef& def, b2Body* body, const dynamic_array<b2Shape*>* shapes = 0); + void CalculateColliderTransformation (const Rigidbody2D* ignoreRigidbody, b2Body** attachedBody, Matrix4x4f& matrix); + + bool HasLocalTransformChanged () const; + void UpdateLocalTransform (); + +protected: + PPtr<PhysicsMaterial2D> m_Material; + bool m_IsTrigger; + bool m_IsStaticBody; + FixtureArray m_Shapes; + +private: + Vector3f m_LocalPosition; + Quaternionf m_LocalRotation; + bool m_LocalTransformInitialized; +}; + +#endif 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 diff --git a/Runtime/Physics2D/CollisionListener2D.h b/Runtime/Physics2D/CollisionListener2D.h new file mode 100644 index 0000000..f87cecb --- /dev/null +++ b/Runtime/Physics2D/CollisionListener2D.h @@ -0,0 +1,123 @@ +#pragma once + +#if ENABLE_2D_PHYSICS || DOXYGEN + +#include "Runtime/Utilities/dynamic_array.h" +#include "Runtime/Scripting/Backend/ScriptingTypes.h" +#include "Runtime/Utilities/dense_hash_map.h" +#include "Runtime/Physics2D/Collider2D.h" +#include "Box2D/Box2D.h" +#include <list> + +class Rigidbody2D; +class b2Contact; + + +// -------------------------------------------------------------------------- + + +struct Collision2D +{ + Collision2D() : + m_Rigidbody(NULL), + m_OtherRigidbody(NULL), + m_Collider(NULL), + m_OtherCollider(NULL), + m_ContactMode(ContactInvalid), + m_Flipped(false), + m_TriggerCollision(false), + m_ContactCount(0), + m_ContactReferences(0) {} + + Rigidbody2D* m_Rigidbody; + Rigidbody2D* m_OtherRigidbody; + Collider2D* m_Collider; + Collider2D* m_OtherCollider; + + UInt32 m_ContactCount; + UInt32 m_ContactReferences; + b2WorldManifold m_ContactManifold; + Vector2f m_RelativeVelocity; + + enum ContactMode + { + ContactInvalid, + ContactAdded, + ContactRemoved, + ContactStay + }; + + ContactMode m_ContactMode; + bool m_Flipped; + bool m_TriggerCollision; +}; + + +// -------------------------------------------------------------------------- + +#if ENABLE_SCRIPTING +struct ScriptingContactPoint2D +{ + Vector2f point; + Vector2f normal; + ScriptingObjectPtr collider; + ScriptingObjectPtr otherCollider; +}; + +struct ScriptingCollision2D +{ + ScriptingObjectPtr rigidbody; + ScriptingObjectPtr collider; + ScriptingArrayPtr contacts; + Vector2f relativeVelocity; +}; + +ScriptingObjectPtr ConvertCollision2DToScripting (Collision2D* input); +#endif + + +// -------------------------------------------------------------------------- + + +class CollisionListener2D : public b2ContactListener +{ +public: + CollisionListener2D(); + + // b2ContactListener interface + virtual void BeginContact(b2Contact* contact); + virtual void EndContact(b2Contact* contact); + virtual void PreSolve(b2Contact* contact, const b2Manifold* oldManifold); + + void ReportCollisions(); + void InvalidateColliderCollisions(Collider2D* collider); + void DestroyColliderCollisions(Collider2D* collider); + +private: + // Collision information is stored for each collider pair (the key is sorted by instance IDs so it always identifies the pair). + // In the collision information, contact points etc. are stored. + typedef std::pair<Collider2D*,Collider2D*> ColliderKey; + struct ColliderKeyHashFunctor + { + inline size_t operator()(const ColliderKey& x) const + { + UInt32 xa = x.first->GetInstanceID(); + UInt32 xb = x.second->GetInstanceID(); + UInt32 a = xa; + a = (a+0x7ed55d16) + (a<<12); + a = (a^0xc761c23c) ^ (a>>19); + a ^= xb; + a = (a+0x165667b1) + (a<<5); + a = (a+0xd3a2646c) ^ (a<<9); + return a; + } + }; + typedef std::pair<const ColliderKey, Collision2D> ColliderKeyToCollisionPair; + typedef dense_hash_map<ColliderKey, Collision2D, ColliderKeyHashFunctor, std::equal_to<ColliderKey>, STL_ALLOCATOR(kMemSTL, ColliderKeyToCollisionPair) > ColliderMap; + +private: + ColliderMap m_Collisions; + bool m_ReportingCollisions; +}; + +#endif diff --git a/Runtime/Physics2D/DistanceJoint2D.cpp b/Runtime/Physics2D/DistanceJoint2D.cpp new file mode 100644 index 0000000..85b932a --- /dev/null +++ b/Runtime/Physics2D/DistanceJoint2D.cpp @@ -0,0 +1,161 @@ +#include "UnityPrefix.h" + +#if ENABLE_2D_PHYSICS || DOXYGEN + +#include "Runtime/Physics2D/DistanceJoint2D.h" +#include "Runtime/Physics2D/RigidBody2D.h" + +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Utilities/ValidateArgs.h" + +#include "External/Box2D/Box2D/Box2D.h" + + +IMPLEMENT_CLASS (DistanceJoint2D) +IMPLEMENT_OBJECT_SERIALIZE (DistanceJoint2D) + + +// -------------------------------------------------------------------------- + + +DistanceJoint2D::DistanceJoint2D (MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +{ +} + + +DistanceJoint2D::~DistanceJoint2D () +{ +} + + +template<class TransferFunction> +void DistanceJoint2D::Transfer (TransferFunction& transfer) +{ + Super::Transfer(transfer); + + TRANSFER (m_Anchor); + TRANSFER (m_ConnectedAnchor); + TRANSFER (m_Distance); +} + + +void DistanceJoint2D::CheckConsistency () +{ + Super::CheckConsistency (); + + m_Distance = clamp<float> (m_Distance, b2_linearSlop, PHYSICS_2D_LARGE_RANGE_CLAMP); + + if (!IsFinite(m_Anchor)) + m_Anchor = Vector2f::zero; + + if (!IsFinite(m_ConnectedAnchor)) + m_ConnectedAnchor = Vector2f::zero; +} + + +void DistanceJoint2D::Reset () +{ + Super::Reset (); + + m_Distance = 1.0f; + m_Anchor = Vector2f::zero; + m_ConnectedAnchor = Vector2f::zero; +} + + +void DistanceJoint2D::SetAnchor (const Vector2f& anchor) +{ + ABORT_INVALID_VECTOR2 (anchor, anchor, DistanceJoint2D); + + m_Anchor = anchor; + SetDirty(); + + // Recreate the joint. + if (m_Joint != NULL) + ReCreate(); +} + + +void DistanceJoint2D::SetConnectedAnchor (const Vector2f& anchor) +{ + ABORT_INVALID_VECTOR2 (anchor, connectedAnchor, DistanceJoint2D); + + m_ConnectedAnchor = anchor; + SetDirty(); + + // Recreate the joint. + if (m_Joint != NULL) + ReCreate(); +} + + +void DistanceJoint2D::SetDistance (float distance) +{ + ABORT_INVALID_FLOAT (distance, distance, DistanceJoint2D); + + m_Distance = clamp<float> (distance, b2_linearSlop, PHYSICS_2D_LARGE_RANGE_CLAMP); + SetDirty(); + + if (m_Joint != NULL) + ((b2RopeJoint*)m_Joint)->SetMaxLength (m_Distance); +} + + +// -------------------------------------------------------------------------- + + +void DistanceJoint2D::Create () +{ + Assert (m_Joint == NULL); + + if (!IsActive ()) + return; + + // Fetch transform scales. + const Vector3f scale = GetComponent (Transform).GetWorldScaleLossy (); + const Vector3f connectedScale = m_ConnectedRigidBody.IsNull () ? Vector3f::one : m_ConnectedRigidBody->GetComponent (Transform).GetWorldScaleLossy (); + + // Configure the joint definition. + b2RopeJointDef jointDef; + jointDef.maxLength = m_Distance; + jointDef.localAnchorA.Set (m_Anchor.x * scale.x, m_Anchor.y * scale.y); + jointDef.localAnchorB.Set (m_ConnectedAnchor.x * connectedScale.x, m_ConnectedAnchor.y * connectedScale.y); + + // Create the joint. + FinalizeCreateJoint (&jointDef); +} + + +void DistanceJoint2D::AutoCalculateDistance () +{ + // Reset to default. + m_Distance = 1.0f; + + if (m_ConnectedRigidBody.IsNull ()) + return; + + // Find the appropriate rigid body A. + Rigidbody2D* rigidBodyA = QueryComponent(Rigidbody2D); + Assert (rigidBodyA != NULL); + + // Find the appropriate rigid body B. + Rigidbody2D* rigidBodyB = m_ConnectedRigidBody; + + Transform* transformA = QueryComponent (Transform); + Vector3f pointA = transformA->TransformPoint(Vector3f(m_Anchor.x, m_Anchor.y, 0.0f)); + + if (rigidBodyB == NULL) + { + m_Distance = Magnitude(Vector2f(pointA.x, pointA.y)); + return; + } + + Transform* transformB = rigidBodyB->GetGameObjectPtr ()->QueryComponent (Transform); + Vector3f pointB = transformB->TransformPoint(Vector3f(m_ConnectedAnchor.x, m_ConnectedAnchor.y, 0.0f)); + + m_Distance = Magnitude(Vector2f(pointA.x-pointB.x, pointA.y-pointB.y)); +} + +#endif //ENABLE_2D_PHYSICS diff --git a/Runtime/Physics2D/DistanceJoint2D.h b/Runtime/Physics2D/DistanceJoint2D.h new file mode 100644 index 0000000..5eeedc2 --- /dev/null +++ b/Runtime/Physics2D/DistanceJoint2D.h @@ -0,0 +1,46 @@ +#pragma once + +#if ENABLE_2D_PHYSICS || DOXYGEN + +#include "Runtime/Math/Vector2.h" +#include "Runtime/Physics2D/Joint2D.h" + +class Vector2f; + + +// -------------------------------------------------------------------------- + + +class DistanceJoint2D : public Joint2D +{ +public: + + REGISTER_DERIVED_CLASS (DistanceJoint2D, Joint2D) + DECLARE_OBJECT_SERIALIZE (DistanceJoint2D) + + DistanceJoint2D (MemLabelId label, ObjectCreationMode mode); + // virtual ~DistanceJoint2D (); declared-by-macro + + virtual void CheckConsistency(); + virtual void Reset (); + + Vector2f GetAnchor () const { return m_Anchor; } + virtual void SetAnchor (const Vector2f& anchor); + + Vector2f GetConnectedAnchor () const { return m_ConnectedAnchor; } + virtual void SetConnectedAnchor (const Vector2f& anchor); + + void SetDistance (float distance); + float GetDistance () const { return m_Distance; } + +protected: + virtual void Create (); + void AutoCalculateDistance (); + +protected: + float m_Distance; ///< The maximum distance which the joint should attempt to maintain between attached bodies. range { 0.005, 1000000 } + Vector2f m_Anchor; ///< The local-space anchor from the base rigid-body. + Vector2f m_ConnectedAnchor; ///< The local-space anchor from the connected rigid-body. +}; + +#endif diff --git a/Runtime/Physics2D/EdgeCollider2D.cpp b/Runtime/Physics2D/EdgeCollider2D.cpp new file mode 100644 index 0000000..1c49f96 --- /dev/null +++ b/Runtime/Physics2D/EdgeCollider2D.cpp @@ -0,0 +1,176 @@ +#include "UnityPrefix.h" + +#if ENABLE_2D_PHYSICS + +#include "Runtime/Physics2D/EdgeCollider2D.h" +#include "Runtime/Physics2D/RigidBody2D.h" +#include "Runtime/Physics2D/Physics2DManager.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Graphics/SpriteFrame.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Profiler/Profiler.h" +#include "Runtime/Filters/AABBUtility.h" +#include "Runtime/Filters/Mesh/SpriteRenderer.h" + +#include "External/Box2D/Box2D/Box2D.h" +#include "External/libtess2/libtess2/tesselator.h" + +PROFILER_INFORMATION(gPhysics2DProfileEdgeColliderCreate, "Physics2D.EdgeColliderCreate", kProfilerPhysics) + +IMPLEMENT_CLASS (EdgeCollider2D) +IMPLEMENT_OBJECT_SERIALIZE (EdgeCollider2D) + +#define EDGE_COLLIDER_2D_MIN_DISTANCE (b2_linearSlop * b2_linearSlop * 2.01f) + +// -------------------------------------------------------------------------- + + +EdgeCollider2D::EdgeCollider2D (MemLabelId label, ObjectCreationMode mode) + : Super(label, mode) +{ +} + + +EdgeCollider2D::~EdgeCollider2D () +{ +} + + +template<class TransferFunction> +void EdgeCollider2D::Transfer (TransferFunction& transfer) +{ + Super::Transfer (transfer); + + TRANSFER (m_Points); +} + + +void EdgeCollider2D::Reset () +{ + Super::Reset (); + + m_Points.clear (); + m_Points.push_back (Vector2f (-0.5f, 0.0f)); + m_Points.push_back (Vector2f (0.5f, 0.0f)); +} + + +void EdgeCollider2D::SmartReset () +{ + Super::SmartReset (); + + AABB aabb; + if (GetGameObjectPtr () && CalculateLocalAABB (GetGameObject (), &aabb)) + { + if (aabb.GetExtent ().x < EDGE_COLLIDER_2D_MIN_DISTANCE) + { + m_Points.clear (); + m_Points.push_back (Vector2f (-0.5f, 0.0f)); + m_Points.push_back (Vector2f (0.5f, 0.0f)); + return; + } + + const Vector3f min = aabb.GetMin(); + const Vector3f max = aabb.GetMax(); + const float y = (min.y + max.y) * 0.5f; + + Vector2f points[2] = { Vector2f(min.x, y), Vector2f(max.x, y) }; + SetPoints (points, 2); + } +} + + +bool EdgeCollider2D::SetPoints (const Vector2f* points, size_t count) +{ + // Fail if point count is invalid. + if (count < 2) + return false; + + m_Points.clear (); + while (count-- > 0) + { + m_Points.push_back (*points++); + } + + // Create the points. + Create(); + + return true; +} + + +// -------------------------------------------------------------------------- + + +void EdgeCollider2D::Create (const Rigidbody2D* ignoreRigidbody) +{ + PROFILER_AUTO(gPhysics2DProfileEdgeColliderCreate, NULL); + + // Ensure we're cleaned-up. + Cleanup (); + + // Ignore if not active. + if (!IsActive() || m_Points.size () < 2) + return; + + // Calculate collider transformation. + Matrix4x4f relativeTransform; + b2Body* body; + CalculateColliderTransformation (ignoreRigidbody, &body, relativeTransform); + + // Fetch the collider scale. + const Vector3f scale = GetComponent(Transform).GetWorldScaleLossy(); + + // Transform the chain. + b2Vec2* transformedPoints; + ALLOC_TEMP(transformedPoints, b2Vec2, m_Points.size () + 1); // We add an extra one in-case we need to extend. + const int validPointCount = TransformPoints (relativeTransform, scale, transformedPoints); + + // Invalid chain if a single edge doesn't exit. + if (validPointCount < 2) + return; + + // Check vertex distances. + for (int i = 1; i < validPointCount; ++i) + if (b2DistanceSquared (transformedPoints[i-1], transformedPoints[i]) < EDGE_COLLIDER_2D_MIN_DISTANCE) + return; + + // Generate the chain shape. + b2ChainShape chainShape; + chainShape.CreateChain (transformedPoints, validPointCount); + + // Create the chain fixture. + dynamic_array<b2Shape*> chainShapes(kMemTempAlloc); + chainShapes.push_back (&chainShape); + b2FixtureDef def; + FinalizeCreate(def, body, &chainShapes); +} + + +// -------------------------------------------------------------------------- + + +int EdgeCollider2D::TransformPoints(const Matrix4x4f& relativeTransform, const Vector3f& scale, b2Vec2* outPoints) +{ + const size_t inPointCount = m_Points.size (); + const Vector2f* edgePoint = m_Points.data (); + int outCount = 0; + for (size_t i = 0; i <inPointCount; ++i, ++edgePoint) + { + // Calculate transform points. + const Vector3f vertex3D = relativeTransform.MultiplyPoint3 (Vector3f(edgePoint->x * scale.x, edgePoint->y * scale.y, 0.0f)); + const b2Vec2 vertex2D(vertex3D.x, vertex3D.y); + + // Skip point if they end up being too close. Box2d fires asserts if distance between neighbors is less than b2_linearSlop. + if (outCount > 0 && b2DistanceSquared(*(outPoints-1), vertex2D) <= EDGE_COLLIDER_2D_MIN_DISTANCE) + continue; + + *outPoints++ = vertex2D; + ++outCount; + } + + return outCount; +} + + +#endif // #if ENABLE_2D_PHYSICS diff --git a/Runtime/Physics2D/EdgeCollider2D.h b/Runtime/Physics2D/EdgeCollider2D.h new file mode 100644 index 0000000..f595640 --- /dev/null +++ b/Runtime/Physics2D/EdgeCollider2D.h @@ -0,0 +1,45 @@ +#pragma once + +#if ENABLE_2D_PHYSICS || DOXYGEN + +#include "Runtime/Graphics/Polygon2D.h" +#include "Runtime/Math/Vector2.h" +#include "Runtime/Physics2D/Collider2D.h" +#include "Runtime/Utilities/dynamic_array.h" + + + +// -------------------------------------------------------------------------- + + +class EdgeCollider2D : public Collider2D +{ +public: + REGISTER_DERIVED_CLASS (EdgeCollider2D, Collider2D) + DECLARE_OBJECT_SERIALIZE (EdgeCollider2D) + + typedef dynamic_array<Vector2f> Points; + + EdgeCollider2D (MemLabelId label, ObjectCreationMode mode); + // virtual ~EdgeCollider2D (); declared-by-macro + + virtual void Reset (); + virtual void SmartReset (); + + bool SetPoints (const Vector2f* points, size_t count); + const Points& GetPoints() const { return m_Points; } + const Vector2f& GetPoint (int index) { Assert(index < m_Points.size ()); return m_Points[index]; } + size_t GetPointCount() const { return m_Points.size (); } + size_t GetEdgeCount() const { return m_Points.size () - 1; } + +protected: + virtual void Create (const Rigidbody2D* ignoreRigidbody = NULL); + +private: + int TransformPoints(const Matrix4x4f& relativeTransform, const Vector3f& scale, b2Vec2* outPoints); + +private: + Points m_Points; +}; + +#endif diff --git a/Runtime/Physics2D/HingeJoint2D.cpp b/Runtime/Physics2D/HingeJoint2D.cpp new file mode 100644 index 0000000..23e6429 --- /dev/null +++ b/Runtime/Physics2D/HingeJoint2D.cpp @@ -0,0 +1,211 @@ +#include "UnityPrefix.h" + +#if ENABLE_2D_PHYSICS || DOXYGEN + +#include "Runtime/Physics2D/HingeJoint2D.h" +#include "Runtime/Physics2D/RigidBody2D.h" + +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Math/Simd/math.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Utilities/ValidateArgs.h" + +#include "External/Box2D/Box2D/Box2D.h" + + +IMPLEMENT_CLASS (HingeJoint2D) +IMPLEMENT_OBJECT_SERIALIZE (HingeJoint2D) + + +// -------------------------------------------------------------------------- + + +HingeJoint2D::HingeJoint2D (MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +, m_OldReferenceAngle (std::numeric_limits<float>::infinity ()) +{ +} + + +HingeJoint2D::~HingeJoint2D () +{ +} + + +template<class TransferFunction> +void HingeJoint2D::Transfer (TransferFunction& transfer) +{ + Super::Transfer(transfer); + + TRANSFER (m_Anchor); + TRANSFER (m_ConnectedAnchor); + TRANSFER (m_UseMotor); + transfer.Align (); + TRANSFER (m_Motor); + TRANSFER (m_UseLimits); + transfer.Align (); + TRANSFER (m_AngleLimits); +} + + +void HingeJoint2D::CheckConsistency () +{ + Super::CheckConsistency (); + + m_Motor.CheckConsistency (); + m_AngleLimits.CheckConsistency (); + + if (!IsFinite(m_Anchor)) + m_Anchor = Vector2f::zero; + + if (!IsFinite(m_ConnectedAnchor)) + m_ConnectedAnchor = Vector2f::zero; +} + + +void HingeJoint2D::Reset () +{ + Super::Reset (); + + m_UseMotor = false; + m_UseLimits = false; + m_Motor.Initialize (); + m_AngleLimits.Initialize (); + + m_Anchor = Vector2f::zero; + m_ConnectedAnchor = Vector2f::zero; +} + + +void HingeJoint2D::SetAnchor (const Vector2f& anchor) +{ + ABORT_INVALID_VECTOR2 (anchor, anchor, HingeJoint2D); + + m_Anchor = anchor; + SetDirty(); + + // Recreate the joint. + if (m_Joint != NULL) + ReCreate(); +} + + +void HingeJoint2D::SetConnectedAnchor (const Vector2f& anchor) +{ + ABORT_INVALID_VECTOR2 (anchor, connectedAnchor, HingeJoint2D); + + m_ConnectedAnchor = anchor; + SetDirty(); + + // Recreate the joint. + if (m_Joint != NULL) + ReCreate(); +} + + +void HingeJoint2D::SetUseMotor (bool enable) +{ + m_UseMotor = enable; + SetDirty(); + + if (m_Joint != NULL) + ((b2RevoluteJoint*)m_Joint)->EnableMotor(m_UseMotor); +} + + +void HingeJoint2D::SetUseLimits (bool enable) +{ + m_UseLimits = enable; + SetDirty(); + + if (m_Joint != NULL) + ((b2RevoluteJoint*)m_Joint)->EnableLimit(m_UseLimits); +} + + +void HingeJoint2D::SetMotor (const JointMotor2D& motor) +{ + m_Motor = motor; + m_Motor.CheckConsistency (); + SetDirty(); + + // Motor is automatically enabled if motor is set. + SetUseMotor(true); + + if (m_Joint != NULL) + { + b2RevoluteJoint* joint = (b2RevoluteJoint*)m_Joint; + joint->SetMotorSpeed (math::radians (m_Motor.m_MotorSpeed)); + joint->SetMaxMotorTorque (m_Motor.m_MaximumMotorForce); + } +} + + +void HingeJoint2D::SetLimits (const JointAngleLimits2D& limits) +{ + m_AngleLimits = limits; + m_AngleLimits.CheckConsistency (); + SetDirty(); + + // Limits ares automatically enabled if limits are set. + SetUseLimits(true); + + if (m_Joint != NULL) + { + b2RevoluteJoint* joint = (b2RevoluteJoint*)m_Joint; + joint->SetLimits(math::radians (m_AngleLimits.m_LowerAngle), math::radians (m_AngleLimits.m_UpperAngle)); + } +} + + +// -------------------------------------------------------------------------- + + +void HingeJoint2D::Create () +{ + Assert (m_Joint == NULL); + + if (!IsActive ()) + return; + + // Fetch transform scales. + const Vector3f scale = GetComponent (Transform).GetWorldScaleLossy (); + const Vector3f connectedScale = m_ConnectedRigidBody.IsNull () ? Vector3f::one : m_ConnectedRigidBody->GetComponent (Transform).GetWorldScaleLossy (); + + // Configure the joint definition. + b2RevoluteJointDef jointDef; + jointDef.localAnchorA.Set (m_Anchor.x * scale.x, m_Anchor.y * scale.y); + jointDef.localAnchorB.Set (m_ConnectedAnchor.x * connectedScale.x, m_ConnectedAnchor.y * connectedScale.y); + jointDef.enableMotor = m_UseMotor; + jointDef.enableLimit = m_UseLimits; + jointDef.motorSpeed = math::radians (m_Motor.m_MotorSpeed); + jointDef.maxMotorTorque = m_Motor.m_MaximumMotorForce; + jointDef.lowerAngle = math::radians (m_AngleLimits.m_LowerAngle); + jointDef.upperAngle = math::radians (m_AngleLimits.m_UpperAngle); + if (jointDef.lowerAngle > jointDef.upperAngle) + std::swap(jointDef.lowerAngle, jointDef.upperAngle); + jointDef.referenceAngle = m_OldReferenceAngle == std::numeric_limits<float>::infinity () ? FetchBodyB()->GetAngle() - FetchBodyA()->GetAngle() : m_OldReferenceAngle; + + // Create the joint. + FinalizeCreateJoint (&jointDef); +} + + +void HingeJoint2D::ReCreate() +{ + // Do we have an existing joint and we're still active/enabled? + if (m_Joint != NULL && IsActive () && GetEnabled ()) + { + // Yes, so keep reference angle. + m_OldReferenceAngle = ((b2RevoluteJoint*)m_Joint)->GetReferenceAngle (); + } + else + { + // No, so reset reference angle. + m_OldReferenceAngle = std::numeric_limits<float>::infinity (); + } + + Super::ReCreate (); +} + +#endif //ENABLE_2D_PHYSICS diff --git a/Runtime/Physics2D/HingeJoint2D.h b/Runtime/Physics2D/HingeJoint2D.h new file mode 100644 index 0000000..6da16b7 --- /dev/null +++ b/Runtime/Physics2D/HingeJoint2D.h @@ -0,0 +1,61 @@ +#pragma once + +#if ENABLE_2D_PHYSICS || DOXYGEN + +#include "Runtime/Math/Vector2.h" +#include "Joint2D.h" + +class Vector2f; + + +// -------------------------------------------------------------------------- + + +class HingeJoint2D : public Joint2D +{ +public: + + REGISTER_DERIVED_CLASS (HingeJoint2D, Joint2D) + DECLARE_OBJECT_SERIALIZE (HingeJoint2D) + + HingeJoint2D (MemLabelId label, ObjectCreationMode mode); + // virtual ~HingeJoint2D (); declared-by-macro + + virtual void CheckConsistency(); + virtual void Reset (); + + Vector2f GetAnchor () const { return m_Anchor; } + virtual void SetAnchor (const Vector2f& anchor); + + Vector2f GetConnectedAnchor () const { return m_ConnectedAnchor; } + virtual void SetConnectedAnchor (const Vector2f& anchor); + + bool GetUseMotor () const { return m_UseMotor; } + void SetUseMotor (bool enable); + + bool GetUseLimits () const { return m_UseLimits; } + void SetUseLimits (bool enable); + + JointMotor2D GetMotor () const { return m_Motor; } + void SetMotor (const JointMotor2D& motor); + + JointAngleLimits2D GetLimits () const { return m_AngleLimits; } + void SetLimits (const JointAngleLimits2D& limits); + +protected: + virtual void Create (); + virtual void ReCreate(); + +protected: + Vector2f m_Anchor; ///< The local-space anchor from the base rigid-body. + Vector2f m_ConnectedAnchor; ///< The local-space anchor from the connected rigid-body. + JointMotor2D m_Motor; ///< The joint motor. + JointAngleLimits2D m_AngleLimits; ///< The joint angle limits. + bool m_UseMotor; ///< Whether to use the joint motor or not. + bool m_UseLimits; ///< Whether to use the angle limits or not. + +private: + float m_OldReferenceAngle; +}; + +#endif diff --git a/Runtime/Physics2D/Joint2D.cpp b/Runtime/Physics2D/Joint2D.cpp new file mode 100644 index 0000000..ed0de54 --- /dev/null +++ b/Runtime/Physics2D/Joint2D.cpp @@ -0,0 +1,201 @@ +#include "UnityPrefix.h" + +#if ENABLE_2D_PHYSICS || DOXYGEN +#include "Runtime/Physics2D/Joint2D.h" +#include "Runtime/Physics2D/RigidBody2D.h" +#include "Runtime/Physics2D/Physics2DManager.h" + +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Utilities/LogAssert.h" +#include "External/Box2D/Box2D/Box2D.h" + + +IMPLEMENT_CLASS (Joint2D) +IMPLEMENT_OBJECT_SERIALIZE (Joint2D) +INSTANTIATE_TEMPLATE_TRANSFER (Joint2D) + +// -------------------------------------------------------------------------- + + +Joint2D::Joint2D (MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +, m_Joint(NULL) +{ +} + + +Joint2D::~Joint2D () +{ + Cleanup (); +} + + +template<class TransferFunction> +void Joint2D::Transfer (TransferFunction& transfer) +{ + Super::Transfer(transfer); + + TRANSFER (m_CollideConnected); + transfer.Align(); + TRANSFER (m_ConnectedRigidBody); +} + + +void Joint2D::Reset () +{ + Super::Reset (); + + Cleanup (); + + m_ConnectedRigidBody = NULL; + m_CollideConnected = false; +} + + +void Joint2D::AwakeFromLoad (AwakeFromLoadMode awakeMode) +{ + Super::AwakeFromLoad (awakeMode); + + // Recreate joint if appropriate. + // Most of Box2D joint properties are immutable once created + // thus causing us to regenerate the joint if a property changes in the editor. + if ((awakeMode == kDefaultAwakeFromLoad)) + ReCreate (); +} + + +void Joint2D::Deactivate (DeactivateOperation operation) +{ + Cleanup (); + Super::Deactivate (operation); +} + + +void Joint2D::AddToManager () +{ + // Create the joint. + ReCreate (); +} + + +void Joint2D::RemoveFromManager () +{ + // Destroy the joint. + Cleanup (); +} + + +void Joint2D::RecreateJoint (const Rigidbody2D* ignoreRigidbody) +{ + if (IsActive () && GetEnabled()) + ReCreate (); + else + Cleanup (); +} + + +void Joint2D::SetConnectedBody (PPtr<Rigidbody2D> rigidBody) +{ + m_ConnectedRigidBody = rigidBody; + SetDirty (); + + ReCreate (); +} + + +void Joint2D::SetCollideConnected (bool collide) +{ + m_CollideConnected = collide; + SetDirty (); + + ReCreate (); +} + + +// -------------------------------------------------------------------------- + + +void Joint2D::ReCreate() +{ + Cleanup(); + + if (IsActive () && GetEnabled ()) + Create(); +} + + +void Joint2D::Cleanup () +{ + // Finish if no joint to clean-up. + if (!m_Joint) + return; + + // Destroy the joint. + GetPhysics2DWorld ()->DestroyJoint (m_Joint); + m_Joint = NULL; +} + + +b2Body* Joint2D::FetchBodyA () const +{ + // Find the rigid-body A. + Rigidbody2D* rigidBodyA = QueryComponent(Rigidbody2D); + Assert (rigidBodyA != NULL); + + // Ensure the rigid-body (body) is available. + if ( rigidBodyA ) + rigidBodyA->Create(); + + // Fetch the body A. + return rigidBodyA->GetBody(); +} + + +b2Body* Joint2D::FetchBodyB () const +{ + // Find the appropriate rigid body B. + Rigidbody2D* rigidBodyB = m_ConnectedRigidBody; + + // Ensure the rigid-body (body) is available. + if ( rigidBodyB ) + rigidBodyB->Create(); + + // Fetch the appropriate body B. + return rigidBodyB != NULL ? rigidBodyB->GetBody() : GetPhysicsGroundBody(); +} + + +void Joint2D::FinalizeCreateJoint (b2JointDef* jointDef) +{ + Assert (jointDef != NULL); + + if (!IsActive ()) + return; + + // Fetch the appropriate body A. + b2Body* bodyA = FetchBodyA(); + + // Fetch the appropriate body B. + b2Body* bodyB = FetchBodyB(); + + // Finish if the same body is being used. + if ( bodyA == bodyB ) + { + WarningStringObject(Format("Cannot create 2D joint on '%s' as it connects to itself.\n", + GetGameObjectPtr()->GetName()), this); + return; + } + + // Populate the basic joint definition information. + jointDef->bodyA = bodyA; + jointDef->bodyB = bodyB; + jointDef->collideConnected = m_CollideConnected; + jointDef->userData = this; + + // Create the joint. + m_Joint = GetPhysics2DWorld ()->CreateJoint (jointDef); +} + + +#endif //ENABLE_2D_PHYSICS diff --git a/Runtime/Physics2D/Joint2D.h b/Runtime/Physics2D/Joint2D.h new file mode 100644 index 0000000..a624709 --- /dev/null +++ b/Runtime/Physics2D/Joint2D.h @@ -0,0 +1,59 @@ +#pragma once + +#if ENABLE_2D_PHYSICS || DOXYGEN + +#include "Runtime/Physics2D/Physics2DManager.h" +#include "Runtime/Physics2D/JointDescriptions2D.h" +#include "Runtime/BaseClasses/GameObject.h" +#include "Runtime/GameCode/Behaviour.h" + +class Rigidbody2D; +class b2Joint; +class b2Body; +struct b2JointDef; + +// -------------------------------------------------------------------------- + + +class Joint2D : public Behaviour +{ + friend class Rigidbody2D; + +public: + REGISTER_DERIVED_ABSTRACT_CLASS (Joint2D, Behaviour) + DECLARE_OBJECT_SERIALIZE (Joint2D) + + Joint2D (MemLabelId label, ObjectCreationMode mode); + // virtual ~Joint2D (); declared-by-macro + + virtual void Reset (); + virtual void AwakeFromLoad (AwakeFromLoadMode awakeMode); + virtual void Deactivate (DeactivateOperation operation); + + virtual void AddToManager (); + virtual void RemoveFromManager (); + + void RecreateJoint (const Rigidbody2D* ignoreRigidbody); + + void SetConnectedBody (PPtr<Rigidbody2D> body); + PPtr<Rigidbody2D> GetConnectedBody () const { return m_ConnectedRigidBody; } + + void SetCollideConnected (bool Collide); + bool GetCollideConnected () const { return m_CollideConnected; } + +protected: + virtual void Create () = 0; + virtual void ReCreate(); + virtual void Cleanup (); + + b2Body* FetchBodyA () const; + b2Body* FetchBodyB () const; + void FinalizeCreateJoint (b2JointDef* jointDef); + +protected: + PPtr<Rigidbody2D> m_ConnectedRigidBody; ///< The rigid body to connect to. No rigid body connects to the scene. + bool m_CollideConnected; ///< Whether rigid bodies connected with this joint can collide or not. + b2Joint* m_Joint; +}; + +#endif //ENABLE_2D_PHYSICS diff --git a/Runtime/Physics2D/JointDescriptions2D.h b/Runtime/Physics2D/JointDescriptions2D.h new file mode 100644 index 0000000..23e2062 --- /dev/null +++ b/Runtime/Physics2D/JointDescriptions2D.h @@ -0,0 +1,101 @@ +#pragma once + +#if ENABLE_2D_PHYSICS || DOXYGEN + +#include "Runtime/Physics2D/Physics2DManager.h" +#include "Runtime/Serialize/SerializeUtility.h" +#include "Runtime/Utilities/ValidateArgs.h" + +// -------------------------------------------------------------------------- + + +struct JointMotor2D +{ + float m_MotorSpeed; ///< The target motor speed in degrees/second. range { -1000000, 1000000 } + float m_MaximumMotorForce; ///< The maximum force the motor can use to achieve the desired motor speed. range { 0.0, 1000000 } + + void Initialize () + { + m_MotorSpeed = 0.0f; + m_MaximumMotorForce = 10000.0f; + } + + void CheckConsistency () + { + m_MotorSpeed = clamp<float> (m_MotorSpeed, -PHYSICS_2D_LARGE_RANGE_CLAMP, PHYSICS_2D_LARGE_RANGE_CLAMP); + m_MaximumMotorForce = clamp<float> (m_MaximumMotorForce, 0, PHYSICS_2D_LARGE_RANGE_CLAMP); + } + + DECLARE_SERIALIZE_OPTIMIZE_TRANSFER (JointMotor2D) +}; + +template<class TransferFunction> +void JointMotor2D::Transfer (TransferFunction& transfer) +{ + TRANSFER (m_MotorSpeed); + TRANSFER (m_MaximumMotorForce); +} + + +// -------------------------------------------------------------------------- + + +struct JointAngleLimits2D +{ + float m_LowerAngle; ///< The lower angle (in degrees) limit to constrain the joint to. range { -359.9999, 359.9999 } + float m_UpperAngle; ///< The upper angle (in degrees) limit to constrain the joint to. range { -359.9999, 359.9999 } + + void Initialize () + { + m_LowerAngle = 0.0f; + m_UpperAngle = 359.0f; + } + + void CheckConsistency () + { + m_LowerAngle = clamp<float> (m_LowerAngle, -359.9999f, 359.9999f); + m_UpperAngle = clamp<float> (m_UpperAngle, -359.9999f, 359.9999f); + } + + DECLARE_SERIALIZE_OPTIMIZE_TRANSFER (JointAngleLimit2D) +}; + +template<class TransferFunction> +void JointAngleLimits2D::Transfer (TransferFunction& transfer) +{ + TRANSFER (m_LowerAngle); + TRANSFER (m_UpperAngle); +} + + +// -------------------------------------------------------------------------- + + +struct JointTranslationLimits2D +{ + float m_LowerTranslation; ///< The lower translation limit to constrain the joint to. range { -1000000, 1000000 } + float m_UpperTranslation; ///< The upper translation limit to constrain the joint to. range { -1000000, 1000000 } + + void Initialize () + { + m_LowerTranslation = 0.0f; + m_UpperTranslation = 0.0f; + } + + void CheckConsistency () + { + m_LowerTranslation = clamp<float> (m_LowerTranslation, -PHYSICS_2D_LARGE_RANGE_CLAMP, PHYSICS_2D_LARGE_RANGE_CLAMP); + m_UpperTranslation = clamp<float> (m_UpperTranslation, -PHYSICS_2D_LARGE_RANGE_CLAMP, PHYSICS_2D_LARGE_RANGE_CLAMP); + } + + DECLARE_SERIALIZE_OPTIMIZE_TRANSFER (JointTranslationLimits2D) +}; + +template<class TransferFunction> +void JointTranslationLimits2D::Transfer (TransferFunction& transfer) +{ + TRANSFER (m_LowerTranslation); + TRANSFER (m_UpperTranslation); +} + +#endif diff --git a/Runtime/Physics2D/Physics2DManager.cpp b/Runtime/Physics2D/Physics2DManager.cpp new file mode 100644 index 0000000..cf6dcd3 --- /dev/null +++ b/Runtime/Physics2D/Physics2DManager.cpp @@ -0,0 +1,1228 @@ +#include "UnityPrefix.h" + +#if ENABLE_2D_PHYSICS +#include "Runtime/Physics2D/Physics2DManager.h" +#include "Runtime/Physics2D/CollisionListener2D.h" +#include "Runtime/Physics2D/Physics2DSettings.h" +#include "Runtime/Physics2D/Collider2D.h" +#include "Runtime/Physics2D/RigidBody2D.h" + +#include "External/Box2D/Box2D/Box2D.h" +#include "Runtime/Core/Callbacks/PlayerLoopCallbacks.h" +#include "Runtime/Geometry/Ray.h" +#include "Runtime/Geometry/Plane.h" +#include "Runtime/Geometry/Intersection.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/BaseClasses/MessageHandler.h" +#include "Runtime/Profiler/Profiler.h" +#include "Runtime/Interfaces/IPhysics2D.h" +#include "Runtime/Input/TimeManager.h" +#include "Runtime/Math/Simd/math.h" +#include "Runtime/Profiler/ProfilerStats.h" +#include "Runtime/Scripting/ScriptingUtility.h" +#include "Runtime/Scripting/ScriptingExportUtility.h" + + +PROFILER_INFORMATION(gPhysics2DDynamicUpdateProfile, "Physics2D.DynamicUpdate", kProfilerPhysics) +PROFILER_INFORMATION(gPhysics2DFixedUpdateProfile, "Physics2D.FixedUpdate", kProfilerPhysics) +PROFILER_INFORMATION(gPhysics2DInterpolationsProfile, "Physics2D.Interpolation", kProfilerPhysics) +PROFILER_INFORMATION(gPhysics2DSimProfile, "Physics2D.Simulate", kProfilerPhysics) +PROFILER_INFORMATION(gPhysics2DUpdateTransformsProfile, "Physics2D.UpdateTransforms", kProfilerPhysics) +PROFILER_INFORMATION(gPhysics2DCallbacksProfile, "Physics2D.Callbacks", kProfilerPhysics) +PROFILER_INFORMATION(gLinecast2DProfile, "Physics2D.Linecast", kProfilerPhysics) +PROFILER_INFORMATION(gLinecastAll2DProfile, "Physics2D.LinecastAll", kProfilerPhysics) +PROFILER_INFORMATION(gRaycast2DProfile, "Physics2D.Raycast", kProfilerPhysics) +PROFILER_INFORMATION(gRaycastAll2DProfile, "Physics2D.RaycastAll", kProfilerPhysics) +PROFILER_INFORMATION(gOverlapPoint2DProfile, "Physics2D.OverlapPoint", kProfilerPhysics) +PROFILER_INFORMATION(gOverlapPointAll2DProfile, "Physics2D.OverlapPointAll", kProfilerPhysics) +PROFILER_INFORMATION(gOverlapCircle2DProfile, "Physics2D.OverlapCircle", kProfilerPhysics) +PROFILER_INFORMATION(gOverlapCircleAll2DProfile, "Physics2D.OverlapCircleAll", kProfilerPhysics) +PROFILER_INFORMATION(gOverlapArea2DProfile, "Physics2D.OverlapArea", kProfilerPhysics) +PROFILER_INFORMATION(gOverlapAreaAll2DProfile, "Physics2D.OverlapAreaAll", kProfilerPhysics) +PROFILER_INFORMATION(gGetRayIntersection2DProfile, "Physics2D.GetRayIntersection", kProfilerPhysics) +PROFILER_INFORMATION(gGetRayIntersectionAll2DProfile, "Physics2D.GetRayIntersectionAll", kProfilerPhysics) + + +// -------------------------------------------------------------------------- + + +inline bool CheckFixtureLayer(b2Fixture* fixture, int layerMask) +{ + DebugAssert(fixture); + + Collider2D* col = reinterpret_cast<Collider2D*>(fixture->GetUserData()); + if (!col) + return false; // no collider + + GameObject* go = col->GetGameObjectPtr(); + if (!go) + return false; // no GO + + int goMask = go->GetLayerMask(); + if ((goMask & layerMask) == 0) + return false; // ignoring this layer + + return true; // all passed +} + +// -------------------------------------------------------------------------- + + +inline bool CheckColliderDepth(Collider2D* collider, const float minDepth, const float maxDepth) +{ + DebugAssert(collider); + + // Fetch depth of the collider. + const float depth = collider->GetComponent(Transform).GetPosition ().z; + + // Check if in the depth range. + return !(depth < minDepth || depth > maxDepth); +} + + +// -------------------------------------------------------------------------- + + +inline void NormalizeDepthRange(float& minDepth, float& maxDepth) +{ + // Clamp any range bounds specified as +- infinity to real values. + minDepth = (minDepth == -std::numeric_limits<float>::infinity ()) ? -std::numeric_limits<float>::max () : minDepth; + maxDepth = (maxDepth == std::numeric_limits<float>::infinity ()) ? std::numeric_limits<float>::max () : maxDepth; + + if (minDepth < maxDepth) + return; + + std::swap (minDepth, maxDepth); +} + + +// -------------------------------------------------------------------------- + + +struct ContactFilter2D : public b2ContactFilter +{ + virtual bool ShouldCollide(b2Fixture* fixtureA, b2Fixture* fixtureB) + { + Collider2D* colliderA = reinterpret_cast<Collider2D*>(fixtureA->GetUserData ()); + Collider2D* colliderB = reinterpret_cast<Collider2D*>(fixtureB->GetUserData ()); + + if (!colliderA->GetEnabled () || !colliderB->GetEnabled ()) + return false; + + // Fetch collider layers. + const int layerA = colliderA->GetGameObject ().GetLayer(); + const int layerB = colliderB->GetGameObject ().GetLayer(); + + // Decide on the contact based upon the layer collision mask. + return GetPhysics2DSettings().GetLayerCollisionMask(layerA) & (1<<layerB); + } +}; + + +// -------------------------------------------------------------------------- + + +struct ColliderHitsByDepthComparitor +{ + inline bool operator()(const Collider2D* a, const Collider2D* b) + { + return a->GetComponent(Transform).GetPosition ().z < b->GetComponent(Transform).GetPosition ().z; + } + + inline int CompareDepth(const Collider2D* a, const Collider2D* b) + { + const float depthA = a->GetComponent(Transform).GetPosition ().z; + const float depthB = b->GetComponent(Transform).GetPosition ().z; + + if (depthA < depthB) return -1; + if (depthA > depthB) return 1; + return 0; + } +} m_CompareDepth; + + +// -------------------------------------------------------------------------- + + +struct RayHitsByDepthComparitor +{ + inline bool operator()(const RaycastHit2D& a, const RaycastHit2D& b) + { + return a.collider->GetComponent(Transform).GetPosition ().z < b.collider->GetComponent(Transform).GetPosition ().z; + } +}; + +struct RayHitsByInverseDepthComparitor +{ + inline bool operator()(const RaycastHit2D& a, const RaycastHit2D& b) + { + return a.collider->GetComponent(Transform).GetPosition ().z > b.collider->GetComponent(Transform).GetPosition ().z; + } +}; + + +// -------------------------------------------------------------------------- + + +class Raycast2DQuery : public b2RayCastCallback +{ +public: + Raycast2DQuery(const Vector2f& pointA, const Vector2f& pointB, const int layerMask, const float minDepth, const float maxDepth, dynamic_array<RaycastHit2D>& outHits) + : m_PointA(pointA) + , m_PointB(pointB) + , m_LayerMask(layerMask) + , m_MinDepth(minDepth) + , m_MaxDepth(maxDepth) + , m_Hits(outHits) + { + NormalizeDepthRange(m_MinDepth, m_MaxDepth); + } + + int RunQuery () + { + // Calculate if the ray has zero-length or not. + const bool rayHasMagnitude = SqrMagnitude (m_PointB - m_PointA) > b2_epsilon * b2_epsilon; + + // Perform a discrete check at the start-point (Box2D does not discover these for a ray-cast). + dynamic_array<Collider2D*> colliderHits(kMemTempAlloc); + if (GetPhysics2DManager ().OverlapPointAll (m_PointA, m_LayerMask, m_MinDepth, m_MaxDepth, &colliderHits) > 0) + { + const Vector2f collisionNormal = rayHasMagnitude ? NormalizeFast (m_PointA-m_PointB) : Vector2f::zero; + for (dynamic_array<Collider2D*>::iterator colliderItr = colliderHits.begin (); colliderItr != colliderHits.end (); ++colliderItr) + { + RaycastHit2D rayHit; + rayHit.collider = *colliderItr; + rayHit.point = m_PointA; + rayHit.normal = collisionNormal; + rayHit.fraction = 0.0f; + m_Hits.push_back (rayHit); + } + } + + if (rayHasMagnitude) + { + // Perform the ray-cast. + GetPhysics2DWorld ()->RayCast (this, b2Vec2(m_PointA.x, m_PointA.y), b2Vec2(m_PointB.x, m_PointB.y)); + + // Sort the hits by fraction. + std::sort (m_Hits.begin(), m_Hits.end(), RaycastHitsByFractionComparitor()); + } + + return m_Hits.size (); + } + + virtual float32 ReportFixture (b2Fixture* fixture, const b2Vec2& point, const b2Vec2& normal, float32 fraction) + { + // Handle whether ray-casts are hitting triggers or not. + if (fixture->IsSensor () && !GetPhysics2DSettings ().GetRaycastsHitTriggers ()) + return -1.0f; + + // Ignore if not in the selected fixture layer. + if (!CheckFixtureLayer (fixture, m_LayerMask)) + return -1.0f; // ignore and continue + + // Ignore if not in the selected depth-range. + Collider2D* collider = reinterpret_cast<Collider2D*>(fixture->GetUserData()); + if (!CheckColliderDepth (collider, m_MinDepth, m_MaxDepth)) + return -1.0f; // ignore and continue; + + RaycastHit2D hit; + hit.point.Set (point.x, point.y); + hit.normal.Set (normal.x, normal.y); + hit.fraction = fraction; + hit.collider = collider; + + // For chain colliders, Box2D will report all individual segments that are hit; also similar situation + // when using composite convex colliders. Try searching for a hit with same Collider2D, and replace info + // if closer hit. Unfortunately, this is is O(n). + for (size_t i = 0, n = m_Hits.size(); i != n; ++i) + { + if (m_Hits[i].collider == collider) + { + if (fraction < m_Hits[i].fraction) + m_Hits[i] = hit; + + return 1.0f; // continue + } + } + + // Add new hit + m_Hits.push_back (hit); + + return 1.0f; // continue + } + +private: + struct RaycastHitsByFractionComparitor + { + inline bool operator()(const RaycastHit2D& a, const RaycastHit2D& b) + { + return a.fraction < b.fraction; + } + }; + +private: + int m_LayerMask; + float m_MinDepth; + float m_MaxDepth; + Vector2f m_PointA; + Vector2f m_PointB; + dynamic_array<RaycastHit2D>& m_Hits; +}; + + +// -------------------------------------------------------------------------- + + +class OverlapPointQuery2D : public b2QueryCallback +{ +public: + OverlapPointQuery2D(const Vector2f& point, const int layerMask, const float minDepth, const float maxDepth, dynamic_array<Collider2D*>& outHits) + : m_LayerMask(layerMask) + , m_MinDepth(minDepth) + , m_MaxDepth(maxDepth) + , m_Hits(outHits) + { + NormalizeDepthRange(m_MinDepth, m_MaxDepth); + + m_Point.Set (point.x, point.y); + } + + int RunQuery () + { + // Reset results. + m_Hits.clear(); + + b2AABB aabb; + aabb.lowerBound = aabb.upperBound = m_Point; + GetPhysics2DWorld ()->QueryAABB (this, aabb); + + // Sort the hits by depth. + std::sort (m_Hits.begin(), m_Hits.end(), ColliderHitsByDepthComparitor()); + + return m_Hits.size (); + } + + virtual bool ReportFixture (b2Fixture* fixture) + { + // Handle whether ray-casts are hitting triggers or not. + if (fixture->IsSensor () && !GetPhysics2DSettings ().GetRaycastsHitTriggers ()) + return true; + + // Ignore if not in the selected fixture layer. + if (!CheckFixtureLayer (fixture, m_LayerMask)) + return true; // ignore and continue + + // Ignore if not in the selected depth-range. + Collider2D* collider = reinterpret_cast<Collider2D*>(fixture->GetUserData()); + if (!CheckColliderDepth (collider, m_MinDepth, m_MaxDepth)) + return true; // ignore and continue; + + // Ignore if we've already selected this collider and it has a higher depth. + for (size_t i = 0; i != m_Hits.size (); ++i) + { + if (m_Hits[i] == collider) + { + if (m_CompareDepth.CompareDepth (m_Hits[i], collider) == 1) + m_Hits[i] = collider; + return true; + } + } + + // Test point against fixture. + if (!fixture->TestPoint (m_Point)) + return true; + + // Add new hit + m_Hits.push_back (collider); + + return true; + } + +private: + b2Vec2 m_Point; + int m_LayerMask; + float m_MinDepth; + float m_MaxDepth; + dynamic_array<Collider2D*>& m_Hits; +}; + + +// -------------------------------------------------------------------------- + + +class OverlapCircleQuery2D : public b2QueryCallback +{ +public: + OverlapCircleQuery2D(const Vector2f& point, const float radius, const int layerMask, const float minDepth, const float maxDepth, dynamic_array<Collider2D*>& outHits) + : m_LayerMask(layerMask) + , m_MinDepth(minDepth) + , m_MaxDepth(maxDepth) + , m_Hits(outHits) + { + NormalizeDepthRange(m_MinDepth, m_MaxDepth); + + // Extremely small radii indicates a point query. + if (radius < 0.00001f) + { + m_Point.Set (point.x, point.y); + m_AABB.lowerBound = m_AABB.upperBound = m_Point; + m_PointQuery = true; + } + else + { + // Calculate circle shape and its AABB. + m_CircleShape.m_p.Set (point.x, point.y); + m_CircleShape.m_radius = radius; + m_QueryTransform.SetIdentity (); + m_CircleShape.ComputeAABB( &m_AABB, m_QueryTransform, 0 ); + m_PointQuery = false; + } + } + + + int RunQuery () + { + // Reset results. + m_Hits.clear(); + + GetPhysics2DWorld ()->QueryAABB (this, m_AABB); + + // Sort the hits by depth. + std::sort (m_Hits.begin(), m_Hits.end(), ColliderHitsByDepthComparitor()); + + return m_Hits.size (); + } + + virtual bool ReportFixture (b2Fixture* fixture) + { + // Handle whether ray-casts are hitting triggers or not. + if (fixture->IsSensor () && !GetPhysics2DSettings ().GetRaycastsHitTriggers ()) + return true; + + // Ignore if not in the selected fixture layer. + if (!CheckFixtureLayer (fixture, m_LayerMask)) + return true; // ignore and continue + + // Ignore if not in the selected depth-range. + Collider2D* collider = reinterpret_cast<Collider2D*>(fixture->GetUserData()); + if (!CheckColliderDepth (collider, m_MinDepth, m_MaxDepth)) + return true; // ignore and continue; + + // Ignore if we've already selected this collider and it has a higher depth. + for (size_t i = 0; i != m_Hits.size (); ++i) + { + if (m_Hits[i] == collider) + { + if (m_CompareDepth.CompareDepth (m_Hits[i], collider) == 1) + m_Hits[i] = collider; + return true; + } + } + + if (m_PointQuery) + { + // Test point against fixture. + if (!fixture->TestPoint (m_Point)) + return true; + } + else + { + // Test circle against fixture. + if ( !b2TestOverlap( &m_CircleShape, 0, fixture->GetShape (), 0, m_QueryTransform, fixture->GetBody ()->GetTransform () ) ) + return true; + } + + // Add new hit + m_Hits.push_back (collider); + + return true; + } + +private: + int m_LayerMask; + float m_MinDepth; + float m_MaxDepth; + b2Vec2 m_Point; + b2CircleShape m_CircleShape; + b2AABB m_AABB; + b2Transform m_QueryTransform; + dynamic_array<Collider2D*>& m_Hits; + bool m_PointQuery; +}; + + +// -------------------------------------------------------------------------- + + +class OverlapAreaQuery2D : public b2QueryCallback +{ +public: + OverlapAreaQuery2D(Vector2f pointA, Vector2f pointB, const int layerMask, const float minDepth, const float maxDepth, dynamic_array<Collider2D*>& outHits) + : m_LayerMask(layerMask) + , m_MinDepth(minDepth) + , m_MaxDepth(maxDepth) + , m_Hits(outHits) + { + NormalizeDepthRange(m_MinDepth, m_MaxDepth); + + // Normalize the points. + if (pointA.x > pointB.x) + std::swap (pointA.x, pointB.x); + if (pointA.y > pointB.y) + std::swap (pointA.y, pointB.y); + + // Calculate the AABB. + m_PolygonAABB.lowerBound.Set (pointA.x, pointA.y); + m_PolygonAABB.upperBound.Set (pointB.x, pointB.y); + + // Calculate polygon shape. + b2Vec2 verts[4]; + verts[0].Set( pointA.x, pointA.y ); + verts[1].Set( pointB.x, pointA.y ); + verts[2].Set( pointB.x, pointB.y ); + verts[3].Set( pointA.x, pointB.y ); + m_PolygonShape.Set( verts, 4 ); + + m_QueryTransform.SetIdentity (); + } + + int RunQuery () + { + // Reset results. + m_Hits.clear(); + + // Finish if AABB is invalid. + if (!m_PolygonAABB.IsValid()) + return 0; + + GetPhysics2DWorld ()->QueryAABB (this, m_PolygonAABB); + + // Sort the hits by depth. + std::sort (m_Hits.begin(), m_Hits.end(), ColliderHitsByDepthComparitor()); + + return m_Hits.size (); + } + + virtual bool ReportFixture (b2Fixture* fixture) + { + // Handle whether ray-casts are hitting triggers or not. + if (fixture->IsSensor () && !GetPhysics2DSettings ().GetRaycastsHitTriggers ()) + return true; + + // Ignore if not in the selected fixture layer. + if (!CheckFixtureLayer (fixture, m_LayerMask)) + return true; // ignore and continue + + // Ignore if not in the selected depth-range. + Collider2D* collider = reinterpret_cast<Collider2D*>(fixture->GetUserData()); + if (!CheckColliderDepth (collider, m_MinDepth, m_MaxDepth)) + return true; // ignore and continue; + + // Ignore if we've already selected this collider and it has a higher depth. + for (size_t i = 0; i != m_Hits.size (); ++i) + { + if (m_Hits[i] == collider) + { + if (m_CompareDepth.CompareDepth (m_Hits[i], collider) == 1) + m_Hits[i] = collider; + return true; + } + } + + // Test polygon against fixture. + if ( !b2TestOverlap( &m_PolygonShape, 0, fixture->GetShape (), 0, m_QueryTransform, fixture->GetBody ()->GetTransform () ) ) + return true; + + // Add new hit + m_Hits.push_back (collider); + + return true; + } + +private: + int m_LayerMask; + float m_MinDepth; + float m_MaxDepth; + b2PolygonShape m_PolygonShape; + b2AABB m_PolygonAABB; + b2Transform m_QueryTransform; + dynamic_array<Collider2D*>& m_Hits; +}; + + +// -------------------------------------------------------------------------- + + +struct Physics2DState +{ + Physics2DState() : + m_PhysicsManager(NULL), + m_PhysicsWorld(NULL), + m_PhysicsGroundBody(NULL) { } + + void Initialize(); + void Cleanup(); + + Physics2DManager* m_PhysicsManager; + b2World* m_PhysicsWorld; + b2Body* m_PhysicsGroundBody; + CollisionListener2D m_Collisions; + ContactFilter2D m_ContactFilter; +}; + +static Physics2DState g_Physics2DState; + + +// -------------------------------------------------------------------------- + +void Physics2DState::Initialize() +{ + Assert (m_PhysicsManager == NULL); + Assert (m_PhysicsWorld == NULL); + + m_PhysicsManager = new Physics2DManager (); + SetIPhysics2D (m_PhysicsManager); + + // Initialize the Box2D physics world. + b2Vec2 gravity(0.0f, -9.81f); + m_PhysicsWorld = new b2World (gravity); + m_PhysicsWorld->SetContactListener (&m_Collisions); + m_PhysicsWorld->SetContactFilter (&m_ContactFilter); + + // Initialize the static ground-body. + b2BodyDef groundBodyDef; + m_PhysicsGroundBody = GetPhysics2DWorld()->CreateBody (&groundBodyDef); + + // Register physics updates. + REGISTER_PLAYERLOOP_CALL (Physics2DFixedUpdate, GetPhysics2DManager().FixedUpdate()); + REGISTER_PLAYERLOOP_CALL (Physics2DUpdate, GetPhysics2DManager().DynamicUpdate()); + REGISTER_PLAYERLOOP_CALL (Physics2DResetInterpolatedTransformPosition, GetPhysics2DManager().ResetInterpolations()); +} + + +void Physics2DState::Cleanup() +{ + delete m_PhysicsWorld; + m_PhysicsWorld = NULL; + + delete m_PhysicsManager; + m_PhysicsManager = NULL; + SetIPhysics2D (NULL); +} + + +void InitializePhysics2DManager () +{ + g_Physics2DState.Initialize (); +} + + +void CleanupPhysics2DManager () +{ + g_Physics2DState.Cleanup (); +} + + +b2World* GetPhysics2DWorld () +{ + Assert (g_Physics2DState.m_PhysicsWorld); + return g_Physics2DState.m_PhysicsWorld; +} + + +b2Body* GetPhysicsGroundBody () +{ + Assert (g_Physics2DState.m_PhysicsGroundBody); + return g_Physics2DState.m_PhysicsGroundBody; +} + + +Physics2DManager& GetPhysics2DManager() +{ + Assert (g_Physics2DState.m_PhysicsManager); + return *g_Physics2DState.m_PhysicsManager; +} + + +// -------------------------------------------------------------------------- + + +Physics2DManager::Physics2DManager() + : m_RigidbodyTransformMessageEnabled(true) +{ + Object::FindAllDerivedClasses (ClassID (Collider2D), &m_AllCollider2DTypes); +} + + +void Physics2DManager::FixedUpdate() +{ + PROFILER_AUTO(gPhysics2DFixedUpdateProfile, NULL) + + // Gather interpolation info. + { + PROFILER_AUTO(gPhysics2DInterpolationsProfile, NULL) + + // Store interpolated position + for (InterpolatedBodiesIterator i=m_InterpolatedBodies.begin();i!=m_InterpolatedBodies.end();++i) + { + Rigidbody2D* rigidBody = i->body; + i->disabled = 0; + + if ( rigidBody->GetBody() == NULL ) + continue; + + if (rigidBody->GetInterpolation() == kInterpolate2D) + { + i->position = rigidBody->GetBodyPosition(); + i->rotation = rigidBody->GetBodyRotation(); + } + } + } + + // simulate + { + PROFILER_AUTO(gPhysics2DSimProfile, NULL) + + const Physics2DSettings& settings = GetPhysics2DSettings(); + g_Physics2DState.m_PhysicsWorld->Step (GetTimeManager().GetFixedDeltaTime(), settings.GetVelocityIterations(), settings.GetPositionIterations()); + } + + // update data back from simulation + { + PROFILER_AUTO(gPhysics2DUpdateTransformsProfile, NULL) + + // Disable rigid body, collider & joint transform changed message handler. + // Turns off setting the pose while we are fetching the state. + SetTransformMessageEnabled (false); + + // Update position / rotation of all rigid bodies + b2Body* body = GetPhysics2DWorld()->GetBodyList(); + while (body != NULL) + { + Rigidbody2D* rb = (Rigidbody2D*)body->GetUserData(); + if (rb != NULL && body->GetType() != b2_staticBody && body->IsAwake()) + { + GameObject& go = rb->GetGameObject(); + Transform& transform = go.GetComponent (Transform); + + // Calculate new position. + const b2Vec2& pos2 = body->GetPosition(); + Vector3f pos3 = transform.GetPosition(); + pos3.x = pos2.x; + pos3.y = pos2.y; + + // Calculate new rotation. + Vector3f localEuler = QuaternionToEuler (transform.GetLocalRotation ()); + localEuler.z = body->GetAngle (); + + // Update position and rotation. + transform.SetPositionAndRotation (pos3, EulerToQuaternion(localEuler)); + } + + body = body->GetNext(); + } + + // Enable transform change notifications back. + SetTransformMessageEnabled (true); + } + + // do script callbacks + { + PROFILER_AUTO(gPhysics2DCallbacksProfile, NULL) + + const bool oldDisableDestruction = GetDisableImmediateDestruction(); + SetDisableImmediateDestruction(true); + + // report collisions and triggers + g_Physics2DState.m_Collisions.ReportCollisions(); + + SetDisableImmediateDestruction(oldDisableDestruction); + } +} + + +void Physics2DManager::DynamicUpdate() +{ + PROFILER_AUTO(gPhysics2DDynamicUpdateProfile, NULL); + + { + PROFILER_AUTO(gPhysics2DInterpolationsProfile, NULL) + + // Disable rigid body / collider transform changed message handler. + // Turns off setting the pose while we are fetching the state. + SetTransformMessageEnabled (false); + + // Also disable rigidbody (2D) transform changed message, otherwise interpolation will affect physics results. + // This is not done in SetTransformMessageEnabled, as we need the rigidbody (2D) messages in the physics fixed update + // so kinematic child rigid-bodies (2D) are moved with their parents. + GameObject::GetMessageHandler ().SetMessageEnabled (ClassID(Rigidbody2D), kTransformChanged.messageID, false); + + // Interpolation time is [0...1] between the two steps + // Extrapolation time the delta time since the last fixed step + const float dynamicTime = GetTimeManager().GetCurTime(); + const float step = GetTimeManager().GetFixedDeltaTime(); + const float fixedTime = GetTimeManager().GetFixedTime(); + + // Update interpolated position + for (InterpolatedBodiesIterator i=m_InterpolatedBodies.begin ();i!=m_InterpolatedBodies.end ();++i) + { + Rigidbody2D* rigidBody = i->body; + const RigidbodyInterpolation2D interpolation = rigidBody->GetInterpolation (); + + // Ignore if disabled, no interpolation is specified or the body is sleeping. + if (i->disabled || interpolation == kNoInterpolation2D || rigidBody->IsSleeping ()) + continue; + + // Interpolate between this physics and last physics frame. + if (interpolation == kInterpolate2D) + { + const float interpolationTime = clamp01 ((dynamicTime - fixedTime) / step); + + // Interpolate position. + const Vector3f position = Lerp (i->position, rigidBody->GetBodyPosition(), interpolationTime); + + // Interpolate rotation. + const Quaternionf rotation = Slerp (i->rotation, rigidBody->GetBodyRotation (), interpolationTime); + + // Update position/rotation. + rigidBody->GetComponent(Transform).SetPositionAndRotationSafe (position, rotation); + continue; + } + + // Extrapolate current position using velocity. + else if (interpolation == kExtrapolate2D) + { + const float extrapolationTime = dynamicTime - fixedTime; + + // Interpolate position. + const Vector2f linearVelocity2D = rigidBody->GetVelocity(); + const Vector3f linearVelocity3D(linearVelocity2D.x * extrapolationTime, linearVelocity2D.y * extrapolationTime, 0.0f); + const Vector3f position = rigidBody->GetBodyPosition () + linearVelocity3D; + + // Interpolate rotation. + const float angularVelocity2D = rigidBody->GetAngularVelocity (); + const Quaternionf rotation = CompareApproximately (angularVelocity2D, 0.0f) ? AngularVelocityToQuaternion(Vector3f(0.0f, 0.0f, angularVelocity2D), extrapolationTime) * rigidBody->GetBodyRotation() : rigidBody->GetBodyRotation(); + + // Update position/rotation. + rigidBody->GetComponent(Transform).SetPositionAndRotationSafe (position, rotation); + continue; + } + } + + // Re-enable rigidbody (2D) transform changed messages. + GameObject::GetMessageHandler ().SetMessageEnabled (ClassID(Rigidbody2D), kTransformChanged.messageID, true); + + // Enable transform change notifications back. + SetTransformMessageEnabled (true); + } +} + + +void Physics2DManager::ResetInterpolations () +{ + PROFILER_AUTO(gPhysics2DInterpolationsProfile, NULL) + + for (InterpolatedBodiesIterator i=m_InterpolatedBodies.begin ();i!=m_InterpolatedBodies.end ();++i) + { + Rigidbody2D* rigidBody = i->body; + if (rigidBody->GetBody() == NULL || rigidBody->IsSleeping ()) + continue; + + Transform& transform = rigidBody->GetComponent(Transform); + + Vector3f pos = rigidBody->GetBodyPosition(); + Quaternionf rot = rigidBody->GetBodyRotation(); + transform.SetPositionAndRotationSafeWithoutNotification (pos, rot); + } +} + + +int Physics2DManager::Linecast (const Vector2f& pointA, const Vector2f& pointB, const int layerMask, const float minDepth, const float maxDepth, RaycastHit2D* outHits, const int outHitsSize) +{ + Assert(g_Physics2DState.m_PhysicsWorld); + Assert(outHits); + PROFILER_AUTO(gLinecast2DProfile, NULL) + + // Finish if no available hits capacity. + if (outHitsSize == 0) + return 0; + + dynamic_array<RaycastHit2D> raycastHits(kMemTempAlloc); + Raycast2DQuery query (pointA, pointB, layerMask, minDepth, maxDepth, raycastHits); + const int resultCount = query.RunQuery (); + + // Transfer the first n-results. + const int allowedResultCount = std::min (resultCount, outHitsSize); + for (int index = 0; index < allowedResultCount; ++index) + *(outHits++) = raycastHits[index]; + + return allowedResultCount; +} + + +int Physics2DManager::LinecastAll (const Vector2f& pointA, const Vector2f& pointB, const int layerMask, const float minDepth, const float maxDepth, dynamic_array<RaycastHit2D>* outHits) +{ + Assert(g_Physics2DState.m_PhysicsWorld); + Assert(outHits); + PROFILER_AUTO(gLinecastAll2DProfile, NULL) + + Raycast2DQuery query (pointA, pointB, layerMask, minDepth, maxDepth, *outHits); + return query.RunQuery (); +} + + +int Physics2DManager::Raycast (const Vector2f& origin, const Vector2f& direction, const float distance, const int layerMask, const float minDepth, const float maxDepth, RaycastHit2D* outHits, const int outHitsSize) +{ + Assert(g_Physics2DState.m_PhysicsWorld); + Assert(outHits); + PROFILER_AUTO(gRaycast2DProfile, NULL) + + // Finish if no available hits capacity. + if (outHitsSize == 0) + return 0; + + // Calculate destination point. + const bool isInfiniteDistance = distance == std::numeric_limits<float>::infinity(); + Vector2f normalizedDirection = NormalizeFast (direction); + const Vector2f pointB = origin + (normalizedDirection * (isInfiniteDistance ? PHYSICS_2D_RAYCAST_DISTANCE : distance)); + + dynamic_array<RaycastHit2D> raycastHits(kMemTempAlloc); + Raycast2DQuery query (origin, pointB, layerMask, minDepth, maxDepth, raycastHits); + const int resultCount = query.RunQuery (); + + // Transfer the first n-results. + const int allowedResultCount = std::min (resultCount, outHitsSize); + for (int index = 0; index < allowedResultCount; ++index) + { + RaycastHit2D& hit = raycastHits[index]; + if (isInfiniteDistance) + hit.fraction *= PHYSICS_2D_RAYCAST_DISTANCE; + + *(outHits++) = hit; + } + + return allowedResultCount; +} + + +int Physics2DManager::RaycastAll (const Vector2f& origin, const Vector2f& direction, const float distance, const int layerMask, const float minDepth, const float maxDepth, dynamic_array<RaycastHit2D>* outHits) +{ + Assert(g_Physics2DState.m_PhysicsWorld); + Assert(outHits); + PROFILER_AUTO(gRaycastAll2DProfile, NULL) + + // Calculate points. + const bool isInfiniteDistance = distance == std::numeric_limits<float>::infinity(); + Vector2f normalizedDirection = NormalizeFast (direction); + const Vector2f pointB = origin + (normalizedDirection * (isInfiniteDistance ? PHYSICS_2D_RAYCAST_DISTANCE : distance)); + + Raycast2DQuery query (origin, pointB, layerMask, minDepth, maxDepth, *outHits); + const int resultCount = query.RunQuery (); + + // Finish if not infinite distance. + if (!isInfiniteDistance || resultCount == 0) + return resultCount; + + // Change fraction to distance. + for (dynamic_array<RaycastHit2D>::iterator hitItr = outHits->begin(); hitItr != outHits->end(); ++hitItr) + hitItr->fraction *= PHYSICS_2D_RAYCAST_DISTANCE; + + return resultCount; +} + + +int Physics2DManager::GetRayIntersection(const Vector3f& origin, const Vector3f& direction, const float distance, const int layerMask, RaycastHit2D* outHits, const int outHitsSize) +{ + Assert(g_Physics2DState.m_PhysicsWorld); + Assert(outHits); + PROFILER_AUTO(gGetRayIntersection2DProfile, NULL) + + // Finish if no available hits capacity. + if (outHitsSize == 0) + return 0; + + dynamic_array<RaycastHit2D> raycastHits(kMemTempAlloc); + const int resultCount = GetRayIntersectionAll (origin, direction, distance, layerMask, &raycastHits); + + // Transfer the first n-results. + const int allowedResultCount = std::min (resultCount, outHitsSize); + for (int index = 0; index < allowedResultCount; ++index) + *(outHits++) = raycastHits[index]; + + return allowedResultCount; +} + + +int Physics2DManager::GetRayIntersectionAll(const Vector3f& origin, const Vector3f& direction, const float distance, const int layerMask, dynamic_array<RaycastHit2D>* outHits) +{ + Assert(g_Physics2DState.m_PhysicsWorld); + Assert(outHits); + PROFILER_AUTO(gGetRayIntersectionAll2DProfile, NULL) + + // Clear hits. + outHits->clear (); + + // Set ray. + Ray ray; + ray.SetOrigin (origin); + ray.SetApproxDirection (direction); + + // Calculate destination point. + const bool isInfiniteDistance = distance == std::numeric_limits<float>::infinity(); + const float rayDistanceScale = isInfiniteDistance ? 1.0f : 1.0f / distance; + const Vector3f destination = ray.GetPoint (isInfiniteDistance ? PHYSICS_2D_RAYCAST_DISTANCE : distance); + + // If the ray is parallel to the X/Y plane then no intersections are possible. + if (CompareApproximately (origin.z, destination.z)) + return 0; + + // Calculate 2D start/end points. + const Vector2f pointA = Vector2f(origin.x, origin.y); + const Vector2f pointB = Vector2f(destination.x, destination.y); + + // Find all the colliders that hit somewhere along the ray. + // NOTE:- These will all be unique colliders (not duplicates). + dynamic_array<RaycastHit2D> potentialHits(kMemTempAlloc); + if (LinecastAll (pointA, pointB, layerMask, origin.z, destination.z, &potentialHits) == 0) + return 0; + + // Sort the hits by depth. + const bool isForwardDepth = origin.z < direction.z; + if (isForwardDepth) + std::sort (potentialHits.begin(), potentialHits.end(), RayHitsByDepthComparitor()); + else + std::sort (potentialHits.begin(), potentialHits.end(), RayHitsByInverseDepthComparitor()); + + // Calculate the plane normal. + const Vector3f planeNormal(0.0f, 0.0f, isForwardDepth ? 1.0f : -1.0f); + + // Iterate all hits and check collider intersections. + for (dynamic_array<RaycastHit2D>::iterator hitItr = potentialHits.begin (); hitItr != potentialHits.end (); ++hitItr) + { + // Fetch the hit. + RaycastHit2D& hit = *hitItr; + + // Fetch the collider depth. + const Collider2D* collider = hit.collider; + const float depth = collider->GetGameObject ().GetComponent (Transform).GetPosition ().z; + + // Configure a collider plane. + Plane colliderPlane; + colliderPlane.SetNormalAndPosition (planeNormal, Vector3f(0.0f, 0.0f, depth)); + + // Test ray for intersection position. + float intersectionDistance; + if (!IntersectRayPlane (ray, colliderPlane, &intersectionDistance)) + continue; + + // Test if the intersection point overlaps the collider. + const Vector3f intersectionPoint3 = ray.GetPoint (intersectionDistance); + const Vector2f intersectionPoint2 = Vector2f(intersectionPoint3.x, intersectionPoint3.y); + if (!collider->OverlapPoint (intersectionPoint2)) + continue; + + // Update the hit with the 3D intersection details. + // NOTE: We leave the normal in 2D space as we can't use the plane normal. + hit.point = intersectionPoint2; + hit.fraction = intersectionDistance * rayDistanceScale; + + // Add hit result. + outHits->push_back (hit); + } + + return outHits->size (); +} + + +int Physics2DManager::OverlapPoint (const Vector2f& point, const int layerMask, const float minDepth, const float maxDepth, Collider2D** outHits, const int outHitsSize) +{ + Assert(g_Physics2DState.m_PhysicsWorld); + Assert(outHits); + PROFILER_AUTO(gOverlapPoint2DProfile, NULL) + + dynamic_array<Collider2D*> colliderHits(kMemTempAlloc); + OverlapPointQuery2D query (point, layerMask, minDepth, maxDepth, colliderHits); + const int resultCount = query.RunQuery (); + + // Transfer the first n-results. + const int allowedResultCount = std::min (resultCount, outHitsSize); + for (int index = 0; index < allowedResultCount; ++index) + *(outHits++) = colliderHits[index]; + + return allowedResultCount; +} + + +int Physics2DManager::OverlapPointAll (const Vector2f& point, const int layerMask, const float minDepth, const float maxDepth, dynamic_array<Collider2D*>* outHits) +{ + Assert(g_Physics2DState.m_PhysicsWorld); + Assert(outHits); + PROFILER_AUTO(gOverlapPointAll2DProfile, NULL) + + OverlapPointQuery2D query (point, layerMask, minDepth, maxDepth, *outHits); + return query.RunQuery (); +} + + +int Physics2DManager::OverlapCircle (const Vector2f& point, const float radius, const int layerMask, const float minDepth, const float maxDepth, Collider2D** outHits, const int outHitsSize) +{ + Assert(g_Physics2DState.m_PhysicsWorld); + Assert(outHits); + PROFILER_AUTO(gOverlapCircle2DProfile, NULL) + + dynamic_array<Collider2D*> colliderHits(kMemTempAlloc); + OverlapCircleQuery2D query (point, radius, layerMask, minDepth, maxDepth, colliderHits); + const int resultCount = query.RunQuery (); + + // Transfer the first n-results. + const int allowedResultCount = std::min (resultCount, outHitsSize); + for (int index = 0; index < allowedResultCount; ++index) + *(outHits++) = colliderHits[index]; + + return allowedResultCount; +} + + +int Physics2DManager::OverlapCircleAll (const Vector2f& point, const float radius, const int layerMask, const float minDepth, const float maxDepth, dynamic_array<Collider2D*>* outHits) +{ + Assert(g_Physics2DState.m_PhysicsWorld); + Assert(outHits); + PROFILER_AUTO(gOverlapCircleAll2DProfile, NULL) + + OverlapCircleQuery2D query (point, radius, layerMask, minDepth, maxDepth, *outHits); + return query.RunQuery (); +} + + +int Physics2DManager::OverlapArea (const Vector2f& pointA, const Vector2f& pointB, const int layerMask, const float minDepth, const float maxDepth, Collider2D** outHits, const int outHitsSize) +{ + Assert(g_Physics2DState.m_PhysicsWorld); + Assert(outHits); + PROFILER_AUTO(gOverlapArea2DProfile, NULL) + + dynamic_array<Collider2D*> colliderHits(kMemTempAlloc); + OverlapAreaQuery2D query (pointA, pointB, layerMask, minDepth, maxDepth, colliderHits); + const int resultCount = query.RunQuery (); + + // Transfer the first n-results. + const int allowedResultCount = std::min (resultCount, outHitsSize); + for (int index = 0; index < allowedResultCount; ++index) + *(outHits++) = colliderHits[index]; + + return allowedResultCount; +} + + +int Physics2DManager::OverlapAreaAll (const Vector2f& pointA, const Vector2f& pointB, const int layerMask, const float minDepth, const float maxDepth, dynamic_array<Collider2D*>* outHits) +{ + Assert(g_Physics2DState.m_PhysicsWorld); + Assert(outHits); + PROFILER_AUTO(gOverlapAreaAll2DProfile, NULL) + + OverlapAreaQuery2D query (pointA, pointB, layerMask, minDepth, maxDepth, *outHits); + return query.RunQuery (); +} + +void Physics2DManager::InvalidateColliderCollisions (Collider2D* collider) +{ + g_Physics2DState.m_Collisions.InvalidateColliderCollisions (collider); +} + + +void Physics2DManager::DestroyColliderCollisions (Collider2D* collider) +{ + g_Physics2DState.m_Collisions.DestroyColliderCollisions (collider); +} + + +#if ENABLE_PROFILER +void Physics2DManager::GetProfilerStats (Physics2DStats& stats) +{ + // Fetch the physics world. + b2World* world = g_Physics2DState.m_PhysicsWorld; + + // Cannot populate stats without a world. + if (world == NULL) + return; + + // Calculate body metrics. + int dynamicBodyCount = 0; + int kinematicBodyCount = 0; + int activeBodyCount = 0; + int sleepingBodyCount = 0; + int discreteBodyCount = 0; + int continuousBodyCount = 0; + int activeColliderShapesCount = 0; + int sleepingColliderShapesCount = 0; + + for (b2Body* body = world->GetBodyList (); body; body = body->GetNext ()) + { + // Check body type. + const b2BodyType bodyType = body->GetType (); + if (bodyType == b2_staticBody) + continue; + else if (bodyType == b2_dynamicBody) + dynamicBodyCount++; + else if (bodyType == b2_kinematicBody) + kinematicBodyCount++; + + // Check sleep state. + if (body->IsAwake ()) + { + activeBodyCount++; + activeColliderShapesCount += body->GetFixtureCount (); + } + else + { + sleepingBodyCount++; + sleepingColliderShapesCount += body->GetFixtureCount (); + } + + // Check CCD state. + if (body->IsBullet ()) + continuousBodyCount++; + else + discreteBodyCount++; + } + + // Populate profile counts. + stats.m_TotalBodyCount = world->GetBodyCount () - 1; // Ignore the hidden static ground-body. + stats.m_ActiveBodyCount = activeBodyCount; + stats.m_SleepingBodyCount = sleepingBodyCount; + stats.m_DynamicBodyCount = dynamicBodyCount; + stats.m_KinematicBodyCount = kinematicBodyCount; + stats.m_DiscreteBodyCount = discreteBodyCount; + stats.m_ContinuousBodyCount = continuousBodyCount; + stats.m_ActiveColliderShapesCount = activeColliderShapesCount; + stats.m_SleepingColliderShapesCount = sleepingColliderShapesCount; + stats.m_JointCount = world->GetJointCount (); + stats.m_ContactCount = world->GetContactCount (); + + // Populate profile times. + const b2Profile& timeProfile = world->GetProfile (); + const float millisecondUpscale = 1000000.0f; + stats.m_StepTime = (int)(timeProfile.step * millisecondUpscale); + stats.m_CollideTime = (int)(timeProfile.collide * millisecondUpscale); + stats.m_SolveTime = (int)(timeProfile.solve * millisecondUpscale); + stats.m_SolveInitialization = (int)(timeProfile.solveInit * millisecondUpscale); + stats.m_SolveVelocity = (int)(timeProfile.solveVelocity * millisecondUpscale); + stats.m_SolvePosition = (int)(timeProfile.solvePosition * millisecondUpscale); + stats.m_SolveBroadphase = (int)(timeProfile.broadphase * millisecondUpscale); + stats.m_SolveTimeOfImpact = (int)(timeProfile.solveTOI * millisecondUpscale); +} + +#endif + +// -------------------------------------------------------------------------- + + +void Physics2DManager::SetTransformMessageEnabled (const bool enable) +{ + for (size_t i = 0, n = m_AllCollider2DTypes.size(); i < n; ++i) + GameObject::GetMessageHandler ().SetMessageEnabled (m_AllCollider2DTypes[i], kTransformChanged.messageID, enable); + + m_RigidbodyTransformMessageEnabled = enable; +} + +#endif // #if ENABLE_2D_PHYSICS diff --git a/Runtime/Physics2D/Physics2DManager.h b/Runtime/Physics2D/Physics2DManager.h new file mode 100644 index 0000000..cef3917 --- /dev/null +++ b/Runtime/Physics2D/Physics2DManager.h @@ -0,0 +1,113 @@ +#pragma once + +#if ENABLE_2D_PHYSICS || DOXYGEN + +#include "Runtime/Interfaces/IPhysics2D.h" +#include "Runtime/Math/Vector2.h" +#include "Runtime/Math/Quaternion.h" +#include "Runtime/Scripting/Backend/ScriptingTypes.h" +#include "Runtime/Utilities/dynamic_array.h" +#include "Runtime/Utilities/LinkedList.h" + +class Rigidbody2D; +class Collider2D; +class b2World; +class b2Body; +class Ray; +struct Physics2DStats; + +#define PHYSICS_2D_RAYCAST_DISTANCE (1e+5f) +#define PHYSICS_2D_LARGE_RANGE_CLAMP (1e+6f) +#define PHYSICS_2D_SMALL_RANGE_CLAMP (0.0001f) + +// -------------------------------------------------------------------------- + + +struct Rigidbody2DInterpolationInfo : public ListElement +{ + Vector3f position; + Quaternionf rotation; + Rigidbody2D* body; + int disabled; +}; + + +// -------------------------------------------------------------------------- + + +struct RaycastHit2D +{ + Vector2f point; + Vector2f normal; + float fraction; + Collider2D* collider; +}; + +// -------------------------------------------------------------------------- + + +class Physics2DManager : public IPhysics2D +{ +private: + typedef List<Rigidbody2DInterpolationInfo> InterpolatedBodiesList; + typedef InterpolatedBodiesList::iterator InterpolatedBodiesIterator; + +public: + Physics2DManager(); + // ~Physics2DManager() // declared-by-macro + + // IPhysics2D interface + virtual void FixedUpdate (); + virtual void DynamicUpdate (); + virtual void ResetInterpolations (); + + // 2D line-casts. + int Linecast (const Vector2f& pointA, const Vector2f& pointB, const int layerMask, const float minDepth, const float maxDepth, RaycastHit2D* outHits, const int outHitsSize); + int LinecastAll (const Vector2f& pointA, const Vector2f& pointB, const int layerMask, const float minDepth, const float maxDepth, dynamic_array<RaycastHit2D>* outHits); + + // 2D ray-casts. + int Raycast (const Vector2f& origin, const Vector2f& direction, const float distance, const int layerMask, const float minDepth, const float maxDepth, RaycastHit2D* outHits, const int outHitsSize); + int RaycastAll (const Vector2f& origin, const Vector2f& direction, const float distance, const int layerMask, const float minDepth, const float maxDepth, dynamic_array<RaycastHit2D>* outHits); + + // 3D ray-intersections. + int GetRayIntersection(const Vector3f& origin, const Vector3f& direction, const float distance, const int layerMask, RaycastHit2D* outHits, const int outHitsSize); + int GetRayIntersectionAll(const Vector3f& origin, const Vector3f& direction, const float distance, const int layerMask, dynamic_array<RaycastHit2D>* outHits); + + // 2D geometry overlaps. + int OverlapPoint (const Vector2f& point, const int layerMask, const float minDepth, const float maxDepth, Collider2D** outHits, const int outHitsSize); + int OverlapPointAll (const Vector2f& point, const int layerMask, const float minDepth, const float maxDepth, dynamic_array<Collider2D*>* outHits); + int OverlapCircle (const Vector2f& point, const float radius, const int layerMask, const float minDepth, const float maxDepth, Collider2D** outHits, const int outHitsSize); + int OverlapCircleAll (const Vector2f& point, const float radius, const int layerMask, const float minDepth, const float maxDepth, dynamic_array<Collider2D*>* outHits); + int OverlapArea (const Vector2f& pointA, const Vector2f& pointB, const int layerMask, const float minDepth, const float maxDepth, Collider2D** outHits, const int outHitsSize); + int OverlapAreaAll (const Vector2f& pointA, const Vector2f& pointB, const int layerMask, const float minDepth, const float maxDepth, dynamic_array<Collider2D*>* outHits); + + void InvalidateColliderCollisions (Collider2D* collider); + void DestroyColliderCollisions (Collider2D* collider); + + inline bool IsTransformMessageEnabled() const { return m_RigidbodyTransformMessageEnabled; } + inline List<Rigidbody2DInterpolationInfo>& GetInterpolatedBodies() { return m_InterpolatedBodies; } + +#if ENABLE_PROFILER + virtual void GetProfilerStats (Physics2DStats& stats); +#endif + +private: + void SetTransformMessageEnabled (const bool enable); + +private: + std::vector<SInt32> m_AllCollider2DTypes; + bool m_RigidbodyTransformMessageEnabled; + InterpolatedBodiesList m_InterpolatedBodies; +}; + + +// -------------------------------------------------------------------------- + + +void InitializePhysics2DManager (); +void CleanupPhysics2DManager (); +b2World* GetPhysics2DWorld (); +b2Body* GetPhysicsGroundBody (); +Physics2DManager& GetPhysics2DManager (); + +#endif diff --git a/Runtime/Physics2D/Physics2DMaterial.cpp b/Runtime/Physics2D/Physics2DMaterial.cpp new file mode 100644 index 0000000..ff47e78 --- /dev/null +++ b/Runtime/Physics2D/Physics2DMaterial.cpp @@ -0,0 +1,65 @@ +#include "UnityPrefix.h" + +#if ENABLE_2D_PHYSICS +#include "Runtime/Physics2D/Physics2DMaterial.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" + +IMPLEMENT_CLASS (PhysicsMaterial2D) +IMPLEMENT_OBJECT_SERIALIZE (PhysicsMaterial2D) + + +// -------------------------------------------------------------------------- + + +PhysicsMaterial2D::PhysicsMaterial2D (MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +{ +} + + +PhysicsMaterial2D::~PhysicsMaterial2D () +{ +} + + +void PhysicsMaterial2D::Reset () +{ + Super::Reset(); + m_Friction = 0.4f; + m_Bounciness = 0.0f; +} + + +void PhysicsMaterial2D::CheckConsistency () +{ + Super::CheckConsistency (); + + m_Friction = clamp(m_Friction, 0.0f, 100000.0f); + m_Bounciness = clamp(m_Bounciness, 0.0f, 1.0f); +} + + +template<class TransferFunction> +void PhysicsMaterial2D::Transfer (TransferFunction& transfer) +{ + Super::Transfer (transfer); + + transfer.Transfer (m_Friction, "friction", kSimpleEditorMask); + transfer.Transfer (m_Bounciness, "bounciness", kSimpleEditorMask); +} + + +void PhysicsMaterial2D::SetFriction (float friction) +{ + m_Friction = clamp (friction, 0.0f, 100000.0f); + SetDirty (); +} + + +void PhysicsMaterial2D::SetBounciness (float bounce) +{ + m_Bounciness = clamp (bounce, 0.0f, 1.0f); + SetDirty (); +} + +#endif //ENABLE_2D_PHYSICS diff --git a/Runtime/Physics2D/Physics2DMaterial.h b/Runtime/Physics2D/Physics2DMaterial.h new file mode 100644 index 0000000..0130b3b --- /dev/null +++ b/Runtime/Physics2D/Physics2DMaterial.h @@ -0,0 +1,36 @@ +#pragma once + +#if ENABLE_2D_PHYSICS || DOXYGEN + +#include "Runtime/BaseClasses/NamedObject.h" + + +// -------------------------------------------------------------------------- + + +class PhysicsMaterial2D : public NamedObject +{ +public: + REGISTER_DERIVED_CLASS (PhysicsMaterial2D, NamedObject) + DECLARE_OBJECT_SERIALIZE (PhysicsMaterial2D) + + PhysicsMaterial2D (MemLabelId label, ObjectCreationMode mode); + // ~PhysicsMaterial2D (); declared-by-macro + + virtual void Reset (); + virtual void CheckConsistency (); + + float GetFriction () const { return m_Friction; } + void SetFriction (float friction); + + float GetBounciness () const { return m_Bounciness; } + void SetBounciness (float bounce); + +private: + float m_Friction; ///< Friction. Range { 0.0, 100000.0 } + float m_Bounciness; ///< Bounciness. Range { 0.0, 1.0 } + + PPtr<Object> m_Owner; +}; + +#endif //ENABLE_2D_PHYSICS diff --git a/Runtime/Physics2D/Physics2DModule.jam b/Runtime/Physics2D/Physics2DModule.jam new file mode 100644 index 0000000..85ef5aa --- /dev/null +++ b/Runtime/Physics2D/Physics2DModule.jam @@ -0,0 +1,178 @@ +rule Physics2DModule_ReportCpp +{ + local physics2DSources = + + Physics2DModule.jam + + Collider2D.cpp + Collider2D.h + BoxCollider2D.cpp + BoxCollider2D.h + CircleCollider2D.cpp + CircleCollider2D.h + EdgeCollider2D.cpp + EdgeCollider2D.h + PolygonColliderBase2D.cpp + PolygonColliderBase2D.h + PolygonCollider2D.cpp + PolygonCollider2D.h + SpriteCollider2D.cpp + SpriteCollider2D.h + CollisionListener2D.cpp + CollisionListener2D.h + DistanceJoint2D.cpp + DistanceJoint2D.h + HingeJoint2D.cpp + HingeJoint2D.h + Joint2D.cpp + Joint2D.h + Physics2DManager.cpp + Physics2DManager.h + Physics2DMaterial.cpp + Physics2DMaterial.h + Physics2DModuleRegistration.cpp + Physics2DSettings.cpp + Physics2DSettings.h + Rigidbody2D.cpp + Rigidbody2D.h + JointDescriptions2D.h + SliderJoint2D.cpp + SliderJoint2D.h + SpringJoint2D.cpp + SpringJoint2D.h + ; + + local box2DSources = + + Box2D.h + Collision/b2BroadPhase.cpp + Collision/b2BroadPhase.h + Collision/b2CollideCircle.cpp + Collision/b2CollideEdge.cpp + Collision/b2CollidePolygon.cpp + Collision/b2Collision.cpp + Collision/b2Collision.h + Collision/b2Distance.cpp + Collision/b2Distance.h + Collision/b2DynamicTree.cpp + Collision/b2DynamicTree.h + Collision/b2TimeOfImpact.cpp + Collision/b2TimeOfImpact.h + + Collision/Shapes/b2ChainShape.cpp + Collision/Shapes/b2ChainShape.h + Collision/Shapes/b2CircleShape.cpp + Collision/Shapes/b2CircleShape.h + Collision/Shapes/b2EdgeShape.cpp + Collision/Shapes/b2EdgeShape.h + Collision/Shapes/b2PolygonShape.cpp + Collision/Shapes/b2PolygonShape.h + Collision/Shapes/b2Shape.h + + Common/b2BlockAllocator.cpp + Common/b2BlockAllocator.h + Common/b2Draw.cpp + Common/b2Draw.h + Common/b2GrowableStack.h + Common/b2Math.cpp + Common/b2Math.h + Common/b2Settings.cpp + Common/b2Settings.h + Common/b2StackAllocator.cpp + Common/b2StackAllocator.h + Common/b2Timer.cpp + Common/b2Timer.h + + Dynamics/b2Body.cpp + Dynamics/b2Body.h + Dynamics/b2ContactManager.cpp + Dynamics/b2ContactManager.h + Dynamics/b2Fixture.cpp + Dynamics/b2Fixture.h + Dynamics/b2Island.cpp + Dynamics/b2Island.h + Dynamics/b2TimeStep.h + Dynamics/b2World.cpp + Dynamics/b2World.h + Dynamics/b2WorldCallbacks.cpp + Dynamics/b2WorldCallbacks.h + + Dynamics/Contacts/b2ChainAndCircleContact.cpp + Dynamics/Contacts/b2ChainAndCircleContact.h + Dynamics/Contacts/b2ChainAndPolygonContact.cpp + Dynamics/Contacts/b2ChainAndPolygonContact.h + Dynamics/Contacts/b2CircleContact.cpp + Dynamics/Contacts/b2CircleContact.h + Dynamics/Contacts/b2Contact.cpp + Dynamics/Contacts/b2Contact.h + Dynamics/Contacts/b2ContactSolver.cpp + Dynamics/Contacts/b2ContactSolver.h + Dynamics/Contacts/b2EdgeAndCircleContact.cpp + Dynamics/Contacts/b2EdgeAndCircleContact.h + Dynamics/Contacts/b2EdgeAndPolygonContact.cpp + Dynamics/Contacts/b2EdgeAndPolygonContact.h + Dynamics/Contacts/b2PolygonAndCircleContact.cpp + Dynamics/Contacts/b2PolygonAndCircleContact.h + Dynamics/Contacts/b2PolygonContact.cpp + Dynamics/Contacts/b2PolygonContact.h + + Dynamics/Joints/b2DistanceJoint.cpp + Dynamics/Joints/b2DistanceJoint.h + Dynamics/Joints/b2FrictionJoint.cpp + Dynamics/Joints/b2FrictionJoint.h + Dynamics/Joints/b2GearJoint.cpp + Dynamics/Joints/b2GearJoint.h + Dynamics/Joints/b2Joint.cpp + Dynamics/Joints/b2Joint.h + Dynamics/Joints/b2MotorJoint.cpp + Dynamics/Joints/b2MotorJoint.h + Dynamics/Joints/b2MouseJoint.cpp + Dynamics/Joints/b2MouseJoint.h + Dynamics/Joints/b2PrismaticJoint.cpp + Dynamics/Joints/b2PrismaticJoint.h + Dynamics/Joints/b2PulleyJoint.cpp + Dynamics/Joints/b2PulleyJoint.h + Dynamics/Joints/b2RevoluteJoint.cpp + Dynamics/Joints/b2RevoluteJoint.h + Dynamics/Joints/b2RopeJoint.cpp + Dynamics/Joints/b2RopeJoint.h + Dynamics/Joints/b2WeldJoint.cpp + Dynamics/Joints/b2WeldJoint.h + Dynamics/Joints/b2WheelJoint.cpp + Dynamics/Joints/b2WheelJoint.h + + Rope/b2Rope.cpp + Rope/b2Rope.h + ; + + local modulesources = + Runtime/Physics2D/$(physics2DSources) + External/Box2D/Box2D/$(box2DSources) + ; + + return $(modulesources) ; +} + +rule Physics2DModule_ReportTxt +{ + return + Runtime/Physics2D/ScriptBindings/Physics2DBindings.txt + ; +} + +rule Physics2DModule_ReportIncludes +{ + return + External/Box2D + Projects/PrecompiledHeaders + ; +} + +rule Physics2DModule_Init +{ + OverrideModule Physics2D : GetModule_Cpp : byOverridingWithMethod : Physics2DModule_ReportCpp ; + OverrideModule Physics2D : GetModule_Txt : byOverridingWithMethod : Physics2DModule_ReportTxt ; + OverrideModule Physics2D : GetModule_Inc : byOverridingWithMethod : Physics2DModule_ReportIncludes ; +} + +#RegisterModule Physics2D ; diff --git a/Runtime/Physics2D/Physics2DModuleRegistration.cpp b/Runtime/Physics2D/Physics2DModuleRegistration.cpp new file mode 100644 index 0000000..cd0b9b3 --- /dev/null +++ b/Runtime/Physics2D/Physics2DModuleRegistration.cpp @@ -0,0 +1,53 @@ +#include "UnityPrefix.h" +#include "Runtime/BaseClasses/ClassRegistration.h" +#include "Runtime/Modules/ModuleRegistration.h" + +#if ENABLE_2D_PHYSICS + +static void RegisterPhysics2DClasses (ClassRegistrationContext& context) +{ + REGISTER_CLASS (Physics2DSettings) + REGISTER_CLASS (Rigidbody2D) + + REGISTER_CLASS (Collider2D) + REGISTER_CLASS (CircleCollider2D) + REGISTER_CLASS (PolygonCollider2D) + REGISTER_CLASS (PolygonColliderBase2D) + #if ENABLE_SPRITECOLLIDER + REGISTER_CLASS (SpriteCollider2D) + #endif + REGISTER_CLASS (BoxCollider2D) + REGISTER_CLASS (EdgeCollider2D) + + REGISTER_CLASS (Joint2D) + REGISTER_CLASS (SpringJoint2D) + REGISTER_CLASS (DistanceJoint2D) + REGISTER_CLASS (HingeJoint2D) + REGISTER_CLASS (SliderJoint2D) + + REGISTER_CLASS (PhysicsMaterial2D) +} + + +#if ENABLE_MONO || UNITY_WINRT +void ExportPhysics2DBindings(); + +static void RegisterPhysics2DICallModule () +{ + #if !INTERNAL_CALL_STRIPPING + ExportPhysics2DBindings (); + #endif +} +#endif + +extern "C" EXPORT_MODULE void RegisterModule_Physics2D() +{ + ModuleRegistrationInfo info; + info.registerClassesCallback = &RegisterPhysics2DClasses; + #if ENABLE_MONO || UNITY_WINRT + info.registerIcallsCallback = &RegisterPhysics2DICallModule; + #endif + RegisterModuleInfo(info); +} + +#endif // #if ENABLE_2D_PHYSICS diff --git a/Runtime/Physics2D/Physics2DSettings.cpp b/Runtime/Physics2D/Physics2DSettings.cpp new file mode 100644 index 0000000..9dd61d7 --- /dev/null +++ b/Runtime/Physics2D/Physics2DSettings.cpp @@ -0,0 +1,167 @@ +#include "UnityPrefix.h" + +#if ENABLE_2D_PHYSICS +#include "Runtime/Physics2D/Physics2DSettings.h" +#include "Runtime/Physics2D/Physics2DManager.h" + +#include "Runtime/BaseClasses/Tags.h" +#include "Runtime/BaseClasses/ManagerContext.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" + +#include "External/Box2D/Box2D/Box2D.h" + + +// -------------------------------------------------------------------------- + + +Physics2DSettings::Physics2DSettings (MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +{ + m_VelocityIterations = 8; + m_PositionIterations = 3; + m_Gravity = Vector2f (0, -9.81f); + m_RaycastsHitTriggers = true; + m_LayerCollisionMatrix.resize_initialized (kNumLayers, 0xffffffff); +} + + +Physics2DSettings::~Physics2DSettings () +{ +} + + +void Physics2DSettings::InitializeClass () +{ + InitializePhysics2DManager (); +} + + +void Physics2DSettings::CleanupClass () +{ + CleanupPhysics2DManager (); +} + + +void Physics2DSettings::Reset () +{ + Super::Reset(); + m_VelocityIterations = 8; + m_PositionIterations = 3; + m_Gravity = Vector2f (0, -9.81F); + m_LayerCollisionMatrix.resize_initialized (kNumLayers, 0xffffffff); +} + + +void Physics2DSettings::AwakeFromLoad(AwakeFromLoadMode awakeMode) +{ + Super::AwakeFromLoad (awakeMode); + SetGravity (m_Gravity); +} + + +void Physics2DSettings::CheckConsistency () +{ + Super::CheckConsistency (); + + m_VelocityIterations = std::max (1, m_VelocityIterations); + m_PositionIterations = std::max (1, m_PositionIterations); +} + + +template<class TransferFunction> +void Physics2DSettings::Transfer (TransferFunction& transfer) +{ + Super::Transfer (transfer); + TRANSFER (m_Gravity); + TRANSFER (m_DefaultMaterial); + TRANSFER (m_VelocityIterations); + TRANSFER (m_PositionIterations); + TRANSFER (m_RaycastsHitTriggers); + transfer.Align (); + transfer.Transfer (m_LayerCollisionMatrix, "m_LayerCollisionMatrix", kHideInEditorMask); +} + + +void Physics2DSettings::SetGravity (const Vector2f& value) +{ + m_Gravity = value; + SetDirty (); + + GetPhysics2DWorld()->SetGravity(b2Vec2(m_Gravity.x, m_Gravity.y)); + + if (m_Gravity == Vector2f::zero) + return; + + // Wake all dynamic bodies that have non-zero gravity-scale. + for (b2Body* body = GetPhysics2DWorld()->GetBodyList (); body != NULL; body = body->GetNext ()) + { + if (body->GetType () == b2_dynamicBody && body->GetGravityScale () != 0.0f) + body->SetAwake (true); + } +} + + +void Physics2DSettings::SetVelocityIterations (const int velocityIterations) +{ + if ( velocityIterations == m_VelocityIterations ) + return; + + m_VelocityIterations = std::max (1,velocityIterations); + SetDirty (); +} + + +void Physics2DSettings::SetPositionIterations (const int positionIterations) +{ + if ( positionIterations == m_PositionIterations ) + return; + + m_PositionIterations = std::max (1,positionIterations); + SetDirty (); +} + + +void Physics2DSettings::IgnoreCollision(int layer1, int layer2, bool ignore) +{ + if (layer1 >= kNumLayers || layer2 >= kNumLayers) + { + ErrorString(Format("layer numbers must be between 0 and %d", kNumLayers)); + return; + } + + Assert (kNumLayers <= m_LayerCollisionMatrix.size()); + Assert (kNumLayers <= sizeof(m_LayerCollisionMatrix[0])*8); + + if (ignore) + { + m_LayerCollisionMatrix[layer1] &= ~(1<<layer2); + m_LayerCollisionMatrix[layer2] &= ~(1<<layer1); + } + else + { + m_LayerCollisionMatrix[layer1] |= 1<<layer2; + m_LayerCollisionMatrix[layer2] |= 1<<layer1; + } + SetDirty(); +} + + +bool Physics2DSettings::GetIgnoreCollision(int layer1, int layer2) const +{ + if (layer1 >= kNumLayers || layer2 >= kNumLayers) + { + ErrorString(Format("layer numbers must be between 0 and %d", kNumLayers)); + return false; + } + + bool collides = m_LayerCollisionMatrix[layer1] & (1<<layer2); + return !collides; +} + + +GET_MANAGER (Physics2DSettings) +GET_MANAGER_PTR (Physics2DSettings) +IMPLEMENT_CLASS_HAS_INIT (Physics2DSettings) +IMPLEMENT_OBJECT_SERIALIZE (Physics2DSettings) + +#endif // #if ENABLE_2D_PHYSICS diff --git a/Runtime/Physics2D/Physics2DSettings.h b/Runtime/Physics2D/Physics2DSettings.h new file mode 100644 index 0000000..8a8b3ed --- /dev/null +++ b/Runtime/Physics2D/Physics2DSettings.h @@ -0,0 +1,62 @@ +#pragma once + +#if ENABLE_2D_PHYSICS || DOXYGEN + +#include "Runtime/BaseClasses/GameManager.h" +#include "Runtime/Math/Vector2.h" +#include "Runtime/Physics2D/Physics2DMaterial.h" +#include "Runtime/Utilities/dynamic_array.h" + + +// -------------------------------------------------------------------------- + + +class Physics2DSettings : public GlobalGameManager +{ +public: + Physics2DSettings (MemLabelId label, ObjectCreationMode mode); + // ~Physics2DSettings (); declared-by-macro + + REGISTER_DERIVED_CLASS (Physics2DSettings, GlobalGameManager) + DECLARE_OBJECT_SERIALIZE (Physics2DSettings) + + static void InitializeClass (); + static void CleanupClass (); + + virtual void AwakeFromLoad(AwakeFromLoadMode mode); + virtual void CheckConsistency (); + virtual void Reset (); + + const Vector2f& GetGravity () const { return m_Gravity; } + void SetGravity (const Vector2f& value); + + int GetVelocityIterations () const { return m_VelocityIterations; } + void SetVelocityIterations (const int velocityIterations); + + int GetPositionIterations () const { return m_PositionIterations; } + void SetPositionIterations (const int positionIterations); + + inline bool GetRaycastsHitTriggers () const { return m_RaycastsHitTriggers; } + inline void SetRaycastsHitTriggers (const bool raycastsHitTriggers) { m_RaycastsHitTriggers = raycastsHitTriggers; } + + void IgnoreCollision (int layer1, int layer2, bool ignore); + bool GetIgnoreCollision(int layer1, int layer2) const; + UInt32 GetLayerCollisionMask(int layer) const { return m_LayerCollisionMatrix[layer]; } + + PhysicsMaterial2D* GetDefaultPhysicsMaterial () { return m_DefaultMaterial; } + +private: + Vector2f m_Gravity; ///< The gravity applied to all rigid bodies in the scene. + PPtr<PhysicsMaterial2D> m_DefaultMaterial; ///< The default material to use on a collider if no material is specified on it. + int m_VelocityIterations; ///< The number of iterations used to solve simulation velocities. More iterations yield a better simulation but is more expensive. (Default 8) range { 1 , infinity } + int m_PositionIterations; ///< The number of iterations used to solve simulation positions. More iterations yield a better simulation but is more expensive. (Default 3) range { 1 , infinity } + bool m_RaycastsHitTriggers; ///< Whether ray/line casts hit triggers or not. + + dynamic_array<UInt32> m_LayerCollisionMatrix; + +}; + +Physics2DSettings& GetPhysics2DSettings (); +Physics2DSettings* GetPhysics2DSettingsPtr (); + +#endif diff --git a/Runtime/Physics2D/PolygonCollider2D.cpp b/Runtime/Physics2D/PolygonCollider2D.cpp new file mode 100644 index 0000000..da41b0d --- /dev/null +++ b/Runtime/Physics2D/PolygonCollider2D.cpp @@ -0,0 +1,134 @@ +#include "UnityPrefix.h" + +#if ENABLE_2D_PHYSICS + +#include "Runtime/Physics2D/PolygonCollider2D.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Math/FloatConversion.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Filters/AABBUtility.h" +#if ENABLE_SPRITES +#include "Runtime/Graphics/SpriteFrame.h" +#include "Runtime/Filters/Mesh/SpriteRenderer.h" +#endif + +IMPLEMENT_CLASS (PolygonCollider2D) +IMPLEMENT_OBJECT_SERIALIZE (PolygonCollider2D) + + +// -------------------------------------------------------------------------- + + +PolygonCollider2D::PolygonCollider2D (MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +{ +} + + +PolygonCollider2D::~PolygonCollider2D () +{ +} + + +template<class TransferFunction> +void PolygonCollider2D::Transfer (TransferFunction& transfer) +{ + Super::Transfer (transfer); + + TRANSFER (m_Poly); +} + +void PolygonCollider2D::Reset () +{ + Super::Reset (); + + // Create pentagon shape + CreateNgon (5, Vector2f(1, 1), Vector2f(0,0), m_Poly); +} + + +void PolygonCollider2D::SmartReset () +{ + float radius; + Vector2f offset; + + GameObject* go = GetGameObjectPtr(); +#if ENABLE_SPRITES + if (go) + { + SpriteRenderer* sr = go->QueryComponentT<SpriteRenderer>(ClassID(SpriteRenderer)); + if (sr) + { + Sprite* sprite = sr->GetSprite(); + if (sprite) + { + m_Poly.GenerateFrom(sprite, Vector2f(0, 0), 0.25f, 200, true); + if (m_Poly.GetPathCount() > 0) // We might fail if all pixels are under the threshold. No workaround in 4.3. + return; + } + } + } +#endif + + // Resolve what size collider we should have from object bounds + AABB aabb; + if (go && CalculateLocalAABB (GetGameObject (), &aabb)) + { + Vector3f dist = aabb.GetExtent (); + radius = std::max(dist.x, dist.y); + if (radius <= 0.0f) + radius = 1.0f; + offset.x = aabb.GetCenter().x; + offset.y = aabb.GetCenter().y; + } + else + { + radius = 1.0f; + offset = Vector2f::zero; + } + + // Create pentagon shape + CreateNgon (5, Vector2f(radius, radius), offset, m_Poly); +} + + +void PolygonCollider2D::RefreshPoly() +{ + Create(); + SetDirty(); +} + + +void PolygonCollider2D::CreatePrimitive (int sides, Vector2f scale, Vector2f offset) +{ + Assert (sides > 2); + Assert (scale.x > 0.0f); + Assert (scale.y > 0.0f); + + CreateNgon (sides, scale, offset, m_Poly); + + // Create polygon shape. + Create(); + SetDirty(); +} + + +void PolygonCollider2D::CreateNgon (const int sides, const Vector2f scale, const Vector2f offset, Polygon2D& polygon2D) +{ + Polygon2D::TPath path; + + // Generate regular n-sided polygon. + const float tau = kPI * 2.0f; + const float chordAngle = tau / (float)sides; + float angle = 0.0f; + for (int chord = 0; chord < sides; ++chord, angle += chordAngle) + { + path.push_back (Vector2f(offset.x + scale.x * Sin(angle), offset.y + scale.y * Cos(angle))); + } + + // Set polygon path. + polygon2D.SetPathCount (1); + polygon2D.SetPath (0, path); +} + +#endif // #if ENABLE_2D_PHYSICS diff --git a/Runtime/Physics2D/PolygonCollider2D.h b/Runtime/Physics2D/PolygonCollider2D.h new file mode 100644 index 0000000..6a5ed55 --- /dev/null +++ b/Runtime/Physics2D/PolygonCollider2D.h @@ -0,0 +1,36 @@ +#pragma once + +#if ENABLE_2D_PHYSICS || DOXYGEN + +#include "Runtime/Math/Vector2.h" +#include "Runtime/Utilities/dynamic_array.h" +#include "Runtime/Physics2D/Collider2D.h" +#include "Runtime/Physics2D/PolygonColliderBase2D.h" + + +// -------------------------------------------------------------------------- + + +class PolygonCollider2D : public PolygonColliderBase2D +{ +public: + REGISTER_DERIVED_CLASS (PolygonCollider2D, PolygonColliderBase2D) + DECLARE_OBJECT_SERIALIZE (PolygonCollider2D) + + PolygonCollider2D (MemLabelId label, ObjectCreationMode mode); + + virtual void Reset (); + virtual void SmartReset (); + + virtual const Polygon2D& GetPoly() const { return m_Poly; } + Polygon2D& GetPoly() { return m_Poly; } + void RefreshPoly(); + + void CreatePrimitive (int sides, Vector2f scale = Vector2f(1.0f, 1.0f), Vector2f offset = Vector2f::zero); + static void CreateNgon (const int sides, const Vector2f scale, const Vector2f offset, Polygon2D& polygon2D); + +private: + Polygon2D m_Poly; +}; + +#endif diff --git a/Runtime/Physics2D/PolygonColliderBase2D.cpp b/Runtime/Physics2D/PolygonColliderBase2D.cpp new file mode 100644 index 0000000..1dfcb80 --- /dev/null +++ b/Runtime/Physics2D/PolygonColliderBase2D.cpp @@ -0,0 +1,336 @@ +#include "UnityPrefix.h" + +#if ENABLE_2D_PHYSICS + +#include "Runtime/Physics2D/PolygonColliderBase2D.h" +#include "Runtime/Physics2D/RigidBody2D.h" +#include "Runtime/Physics2D/Physics2DManager.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Graphics/SpriteFrame.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Profiler/Profiler.h" + +#include "External/Box2D/Box2D/Box2D.h" +#include "External/libtess2/libtess2/tesselator.h" + +PROFILER_INFORMATION(gPhysics2DProfilePolygonColliderBaseCreate, "Physics2D.PolygonColliderCreate", kProfilerPhysics) +PROFILER_INFORMATION(gPhysics2DProfilePolygonColliderBaseDecomposition, "Physics2D.PolygonColliderDecomposition", kProfilerPhysics) + +IMPLEMENT_CLASS (PolygonColliderBase2D) + + +// -------------------------------------------------------------------------- + + +PolygonColliderBase2D::PolygonColliderBase2D (MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +{ +} + + +PolygonColliderBase2D::~PolygonColliderBase2D () +{ +} + + +// -------------------------------------------------------------------------- + + +void PolygonColliderBase2D::Create (const Rigidbody2D* ignoreRigidbody) +{ + PROFILER_AUTO(gPhysics2DProfilePolygonColliderBaseCreate, NULL); + + // Ensure we're cleaned-up. + Cleanup (); + + // Ignore if not active. + if (!IsActive()) + return; + + const Polygon2D& poly = GetPoly(); + if (poly.IsEmpty()) + return; + + const int pathCount = poly.GetPathCount(); + if (pathCount == 0) + return; + + // Calculate collider transformation. + Matrix4x4f relativeTransform; + b2Body* body; + CalculateColliderTransformation (ignoreRigidbody, &body, relativeTransform); + + { + PROFILER_AUTO(gPhysics2DProfilePolygonColliderBaseDecomposition, NULL); + + // Extract the convex shapes from the path(s). + dynamic_array<b2Shape*> shapePtr; + b2Shape* shapeMem = ExtractConvexShapes(shapePtr, relativeTransform); + + // Finish if no shapes generated. + if (shapeMem == NULL) + return; + + b2FixtureDef def; + FinalizeCreate(def, body, &shapePtr); + + FREE_TEMP_MANUAL(shapeMem); + } +} + + +b2Shape* PolygonColliderBase2D::ExtractConvexShapes(dynamic_array<b2Shape*>& shapes, const Matrix4x4f& relativeTransform ) +{ + // Calculate the maximum number of vertices for the shape type. + const int kMaxPolygonVerts = b2_maxPolygonVertices; + const int kVertexSize = 2; + const Vector3f scale = GetComponent(Transform).GetWorldScaleLossy(); + + // Tessellation + TESStesselator* tess = tessNewTess (NULL); + + // Add all paths as a tessellation contour. + const Polygon2D& poly = GetPoly(); + const int pathCount = poly.GetPathCount(); + int addedContours = 0; + for (int pathIndex = 0; pathIndex < pathCount; ++pathIndex) + { + // Fetch the path. + const Polygon2D::TPath& path = poly.GetPath(pathIndex); + + // Ignore illegal path. + if (path.size() < 3) + continue; + + // Validate the path. + b2Vec2* points; + ALLOC_TEMP(points, b2Vec2, path.size()); + const int validPointCount = TransformPoints (path, relativeTransform, scale, points); + + // Add path contour. + tessAddContour (tess, kVertexSize, points, sizeof(b2Vec2), validPointCount); + addedContours++; + } + + // Finish if no contours added. + if (addedContours == 0) + return NULL; + + // Perform the tessellation. + const int tessError = tessTesselate(tess, TESS_WINDING_ODD, TESS_POLYGONS, kMaxPolygonVerts, kVertexSize, NULL); + AssertBreak(tessError == 1); + + // Allocate the shape array. + const int elemCount = tessGetElementCount (tess); + + // Finish if no elements. + if (elemCount == 0) + return NULL; + + shapes.resize_uninitialized(elemCount); + b2PolygonShape* polygons = ALLOC_TEMP_MANUAL(b2PolygonShape, elemCount); + + // Extract the tessellation results into the shape array. + const TESSindex* elements = tessGetElements(tess); + const TESSreal* real = tessGetVertices(tess); + b2Vec2* buffer; + ALLOC_TEMP(buffer, b2Vec2, kMaxPolygonVerts); + int totalElementCount=0; + for (int elementIndex = 0; elementIndex < elemCount; ++elementIndex) + { + const int* indices = &elements[elementIndex * kMaxPolygonVerts]; + + // Extract vertices + int bufSize = 0; + for (int i = 0; i < kMaxPolygonVerts && indices[i] != TESS_UNDEF; ++i) + { + const float& x = real[indices[i]*kVertexSize]; + const float& y = real[indices[i]*kVertexSize + 1]; + + b2Vec2 newPoint(x, y); + if (bufSize > 0 && b2DistanceSquared(buffer[bufSize-1], newPoint) <= b2_linearSlop * b2_linearSlop) + continue; + + buffer[bufSize] = newPoint; + ++bufSize; + } + + // Ignore small paths. + if (bufSize < 3) + continue; + + // Fill shape + if (ValidatePolygonShape (buffer, bufSize)) + { + b2PolygonShape& shape = polygons[totalElementCount]; + new (&shape) b2PolygonShape(); + shape.Set(buffer, bufSize); + shapes[totalElementCount++] = &shape; + } + } + + tessDeleteTess(tess); + + // Finish if nothing generated. + if (totalElementCount == 0) + { + if (polygons) + FREE_TEMP_MANUAL(polygons); + return NULL; + } + + shapes.resize_initialized(totalElementCount); + + return polygons; +} + + +int PolygonColliderBase2D::TransformPoints(const Polygon2D::TPath& path, const Matrix4x4f& relativeTransform, const Vector3f& scale, b2Vec2* outPoints) +{ + int outCount = 0; + for (size_t i = 0; i < path.size(); ++i) + { + // Calculate 3D vertex. + const Vector3f vertex3D = relativeTransform.MultiplyPoint3 (Vector3f(path[i].x * scale.x, path[i].y * scale.y, 0.0f)); + + // If any vertex are infinite or are a very large scale then abort transformation. + // We abort here rather than ignore the vertex otherwise we may end-up with large-scale collider geometry warping if + // only a few points are infinite or out-of-bounds. This less likely an issue with the small-scale. + if (!IsFinite (vertex3D) || SqrMagnitude(vertex3D ) > (PHYSICS_2D_LARGE_RANGE_CLAMP*PHYSICS_2D_LARGE_RANGE_CLAMP)) + return 0; + + // Fetch 2D vertex. + b2Vec2 vertex2D(vertex3D.x, vertex3D.y); + + // Skip point if they end up being too close. Box2d fires asserts if distance between neighbors is less than b2_linearSlop. + if (outCount > 0 && b2DistanceSquared(*(outPoints-1), vertex2D) <= b2_linearSlop * b2_linearSlop) + continue; + + *outPoints++ = vertex2D; + ++outCount; + } + + return outCount; +} + + +bool PolygonColliderBase2D::ValidatePolygonShape(const b2Vec2* const points, const int pointCount) +{ + // Invalid polygon if the vertex count isn't in range. + if (pointCount < 3 || pointCount > b2_maxPolygonVertices) + return false; + + // Validate the polygon using the exact same code that Box2D uses. This at least + // ensures that we don't trigger any runtime asserts in Box2D. + + // Copy vertices into local buffer + b2Vec2 ps[b2_maxPolygonVertices]; + for (int32 i = 0; i < pointCount; ++i) + ps[i] = points[i]; + + // Create the convex hull using the Gift wrapping algorithm + // http://en.wikipedia.org/wiki/Gift_wrapping_algorithm + + // Find the right most point on the hull + int32 i0 = 0; + float32 x0 = ps[0].x; + for (int32 i = 1; i < pointCount; ++i) + { + float32 x = ps[i].x; + if (x > x0 || (x == x0 && ps[i].y < ps[i0].y)) + { + i0 = i; + x0 = x; + } + } + + int32 hull[b2_maxPolygonVertices]; + int32 validPointCount = 0; + int32 ih = i0; + + for (;;) + { + hull[validPointCount] = ih; + + int32 ie = 0; + for (int32 j = 1; j < pointCount; ++j) + { + if (ie == ih) + { + ie = j; + continue; + } + + b2Vec2 r = ps[ie] - ps[hull[validPointCount]]; + b2Vec2 v = ps[j] - ps[hull[validPointCount]]; + float32 c = b2Cross(r, v); + if (c < 0.0f) + { + ie = j; + } + + // Collinearity check + if (c == 0.0f && v.LengthSquared() > r.LengthSquared()) + { + ie = j; + } + } + + ++validPointCount; + ih = ie; + + if (ie == i0) + { + break; + } + } + + // Finish if invalid point count. + if (validPointCount < 3) + return false; + + + // The following code is directly from Box2D. + // Unfortunately Box2D simply asserts if the area inside the polygon is below a specific threshold when it + // is calculating the centroid so using Box2Ds code and invalidating the polygon rather than throwing an assert is required. + + // Copy vertices. + b2Vec2 vertices[b2_maxPolygonVertices]; + for (int32 i = 0; i < validPointCount; ++i) + { + vertices[i] = ps[hull[i]]; + } + + b2Vec2 c; c.Set(0.0f, 0.0f); + float32 area = 0.0f; + + // pRef is the reference point for forming triangles. + // It's location doesn't change the result (except for rounding error). + b2Vec2 pRef(0.0f, 0.0f); + + const float32 inv3 = 1.0f / 3.0f; + + for (int32 i = 0; i < validPointCount; ++i) + { + // Triangle vertices. + b2Vec2 p1 = pRef; + b2Vec2 p2 = vertices[i]; + b2Vec2 p3 = i + 1 < validPointCount ? vertices[i+1] : vertices[0]; + + b2Vec2 e1 = p2 - p1; + b2Vec2 e2 = p3 - p1; + + float32 D = b2Cross(e1, e2); + + float32 triangleArea = 0.5f * D; + area += triangleArea; + + // Area weighted centroid + c += triangleArea * inv3 * (p1 + p2 + p3); + } + + // Check for valid area. + return IsFinite (area) && area > b2_epsilon; +} + +#endif // #if ENABLE_2D_PHYSICS diff --git a/Runtime/Physics2D/PolygonColliderBase2D.h b/Runtime/Physics2D/PolygonColliderBase2D.h new file mode 100644 index 0000000..02d7069 --- /dev/null +++ b/Runtime/Physics2D/PolygonColliderBase2D.h @@ -0,0 +1,33 @@ +#pragma once + +#if ENABLE_2D_PHYSICS || DOXYGEN + +#include "Runtime/Math/Vector2.h" +#include "Runtime/Utilities/dynamic_array.h" +#include "Collider2D.h" +#include "Runtime/Graphics/Polygon2D.h" + +class Vector3f; + + +// -------------------------------------------------------------------------- + + +class PolygonColliderBase2D : public Collider2D +{ +public: + REGISTER_DERIVED_ABSTRACT_CLASS (PolygonColliderBase2D, Collider2D) + + PolygonColliderBase2D (MemLabelId label, ObjectCreationMode mode); + + virtual const Polygon2D& GetPoly() const = 0; + +protected: + virtual void Create (const Rigidbody2D* ignoreRigidbody = NULL); + + b2Shape* ExtractConvexShapes(dynamic_array<b2Shape*>& shapes, const Matrix4x4f& relativeTransform); + static int TransformPoints(const Polygon2D::TPath& path, const Matrix4x4f& relativeTransform, const Vector3f& scale, b2Vec2* outPoints); + static bool ValidatePolygonShape(const b2Vec2* const points, const int pointCount); +}; + +#endif diff --git a/Runtime/Physics2D/RigidBody2D.h b/Runtime/Physics2D/RigidBody2D.h new file mode 100644 index 0000000..ed75dca --- /dev/null +++ b/Runtime/Physics2D/RigidBody2D.h @@ -0,0 +1,125 @@ +#pragma once + +#if ENABLE_2D_PHYSICS || DOXYGEN + +#include "Runtime/BaseClasses/GameObject.h" +#include "Runtime/Physics2D/Physics2DManager.h" +#include "Runtime/Physics2D/Physics2DSettings.h" + +class Vector2f; +class Vector3f; +class Quaternionf; +class b2Body; +struct b2Vec2; +struct RootMotionData; + + +// -------------------------------------------------------------------------- + +enum RigidbodyInterpolation2D { kNoInterpolation2D = 0, kInterpolate2D = 1, kExtrapolate2D = 2 }; +enum RigidbodySleepMode2D { kNeverSleep2D = 0, kStartAwake2D = 1, kStartAsleep2D = 2 }; +enum CollisionDetectionMode2D { kDiscreteCollision2D = 0, kContinuousCollision2D = 1 }; + +// -------------------------------------------------------------------------- + + +class Rigidbody2D : public Unity::Component +{ +public: + REGISTER_DERIVED_CLASS (Rigidbody2D, Unity::Component) + DECLARE_OBJECT_SERIALIZE (Rigidbody2D) + + Rigidbody2D (MemLabelId label, ObjectCreationMode mode); + // virtual ~Rigidbody2D(); declared-by-macro + + static void InitializeClass (); + static void CleanupClass () {} + + virtual void CheckConsistency (); + virtual void Reset (); + virtual void AwakeFromLoad (AwakeFromLoadMode awakeMode); + virtual void Deactivate (DeactivateOperation operation); + + void Create (); + + void TransformChanged (int changeMask); + void ApplyRootMotionBuiltin (RootMotionData* rootMotion); + + float GetDrag () const { return m_LinearDrag; } + void SetDrag (float drag); + + float GetAngularDrag () const { return m_AngularDrag; } + void SetAngularDrag (float drag); + + float GetGravityScale () const { return m_GravityScale; } + void SetGravityScale (float scale); + + void SetIsKinematic (bool isKinematic); + bool GetIsKinematic () const { return m_IsKinematic; } + + void SetFixedAngle (bool fixedAngle); + bool IsFixedAngle () const { return m_FixedAngle; } + + RigidbodyInterpolation2D GetInterpolation () { return (RigidbodyInterpolation2D)m_Interpolate; } + void SetInterpolation (RigidbodyInterpolation2D interpolation); + + RigidbodySleepMode2D GetSleepMode () { return (RigidbodySleepMode2D)m_SleepingMode; } + void SetSleepMode (RigidbodySleepMode2D mode); + + CollisionDetectionMode2D GetCollisionDetectionMode () { return (CollisionDetectionMode2D)m_CollisionDetection; } + void SetCollisionDetectionMode (CollisionDetectionMode2D mode); + + // Get position and rotation. This can be different from transform state when using interpolation. + Vector3f GetBodyPosition () const; + Quaternionf GetBodyRotation () const; + + // Linear velocity. + Vector2f GetVelocity () const; + void SetVelocity (const Vector2f& velocity); + + // Angular velocity. + float GetAngularVelocity () const; + void SetAngularVelocity (float velocity); + + // Sleeping. + void SetSleeping (bool sleeping); + bool IsSleeping () const; + + // Mass. + float GetMass () const { return m_Mass; } + void SetMass (float mass); + void CalculateColliderBodyMass (); + + // Add forces. + void AddForce (const Vector2f& force); + void AddTorque (float torque); + void AddForceAtPosition (const Vector2f& force, const Vector2f& position); + + inline b2Body* GetBody() { return m_Body; } + + static Rigidbody2D* FindRigidbody (const GameObject* gameObject, const Rigidbody2D* ignoreRigidbody = NULL); + +private: + void FetchPoseFromTransform (b2Vec2* outPos, float* outRot); + void UpdateInterpolationInfo (); + void Cleanup (); + void InformCollidersOfNewBody (); + void ReCreateInboundJoints (); + void RetargetDependencies (Rigidbody2D* ignoreRigidBody); + +private: + b2Body* m_Body; + Rigidbody2DInterpolationInfo* m_InterpolationInfo; + + float m_Mass; ///< The mass of the body. range { 0.0001, 1000000 } + float m_LinearDrag; ///< The linear drag coefficient. 0 means no damping. range { 0, 1000000 } + float m_AngularDrag; ///< The angular drag coefficient. 0 means no damping. range { 0, 1000000 } + float m_GravityScale; ///< How much gravity affects this body. range { -1000000, 1000000 } + bool m_FixedAngle; ///< Whether the body's angle is fixed or not. + bool m_IsKinematic; ///< Whether the body is kinematic or not. If not then the body is dynamic. + UInt8 m_Interpolate; ///< The per-frame update mode for the body. enum { None = 0, Interpolate = 1, Extrapolate = 2 } + UInt8 m_SleepingMode; ///< The sleeping mode for the body. enum { Never Sleep = 0, Start Awake = 1, Start Asleep = 2 } + UInt8 m_CollisionDetection; ///< The collision detection mode for the body. enum { Discrete = 0, Continuous = 1 } +}; + +#endif diff --git a/Runtime/Physics2D/Rigidbody2D.cpp b/Runtime/Physics2D/Rigidbody2D.cpp new file mode 100644 index 0000000..6b2b01a --- /dev/null +++ b/Runtime/Physics2D/Rigidbody2D.cpp @@ -0,0 +1,700 @@ +#include "UnityPrefix.h" + +#if ENABLE_2D_PHYSICS || DOXYGEN + +#include "Runtime/Physics2D/RigidBody2D.h" +#include "Runtime/Physics2D/Collider2D.h" +#include "Runtime/Physics2D/Joint2D.h" +#include "Runtime/Physics2D/Physics2DManager.h" + +#include "Runtime/Graphics/Transform.h" +#include "Runtime/BaseClasses/MessageHandler.h" +#include "Runtime/GameCode/RootMotionData.h" +#include "Runtime/Input/TimeManager.h" +#include "Runtime/Math/Simd/math.h" +#include "Runtime/Misc/GameObjectUtility.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Utilities/ValidateArgs.h" + +#include "External/Box2D/Box2D/Box2D.h" + +IMPLEMENT_CLASS_HAS_INIT (Rigidbody2D) +IMPLEMENT_OBJECT_SERIALIZE (Rigidbody2D) + + +// -------------------------------------------------------------------------- + + +Rigidbody2D::Rigidbody2D (MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +, m_Body(NULL) +, m_InterpolationInfo(NULL) +{ +} + +Rigidbody2D::~Rigidbody2D () +{ + Cleanup (); +} + + +void Rigidbody2D::InitializeClass () +{ + REGISTER_MESSAGE (Rigidbody2D, kTransformChanged, TransformChanged, int); + REGISTER_MESSAGE_PTR (Rigidbody2D, kAnimatorMoveBuiltin, ApplyRootMotionBuiltin, RootMotionData); +} + + +template<class TransferFunction> +void Rigidbody2D::Transfer (TransferFunction& transfer) +{ + Super::Transfer (transfer); + + TRANSFER (m_Mass); + TRANSFER (m_LinearDrag); + TRANSFER (m_AngularDrag); + TRANSFER (m_GravityScale); + TRANSFER (m_FixedAngle); + TRANSFER (m_IsKinematic); + TRANSFER (m_Interpolate); + TRANSFER (m_SleepingMode); + TRANSFER (m_CollisionDetection); + transfer.Align(); +} + + +void Rigidbody2D::CheckConsistency () +{ + Super::CheckConsistency (); + + m_Mass = clamp<float> (m_Mass, 0.0001f, PHYSICS_2D_LARGE_RANGE_CLAMP); + m_LinearDrag = clamp<float> (m_LinearDrag, 0.0f, PHYSICS_2D_LARGE_RANGE_CLAMP); + m_AngularDrag = clamp<float> (m_AngularDrag, 0.0f, PHYSICS_2D_LARGE_RANGE_CLAMP); + m_GravityScale = clamp<float> (m_GravityScale, -PHYSICS_2D_LARGE_RANGE_CLAMP, PHYSICS_2D_LARGE_RANGE_CLAMP); + + if (m_Interpolate>2) + m_Interpolate = kNoInterpolation2D; + + if (m_SleepingMode>2) + m_SleepingMode = kStartAwake2D; + + if (m_CollisionDetection!=0 && m_CollisionDetection!=1) + m_CollisionDetection = kDiscreteCollision2D; +} + + +void Rigidbody2D::Reset () +{ + Super::Reset (); + + m_Mass = 1.0f; + m_LinearDrag = 0.0f; + m_AngularDrag = 0.05f; + m_GravityScale = 1.0f; + m_FixedAngle = false; + m_IsKinematic = false; + m_Interpolate = (RigidbodyInterpolation2D)kNoInterpolation2D; + m_SleepingMode = (RigidbodySleepMode2D)kStartAwake2D; + m_CollisionDetection = (CollisionDetectionMode2D)kDiscreteCollision2D; +} + + +void Rigidbody2D::AwakeFromLoad (AwakeFromLoadMode awakeMode) +{ + Super::AwakeFromLoad (awakeMode); + + Assert (GameObject::GetMessageHandler().HasMessageCallback (ClassID(Rigidbody2D), kTransformChanged.messageID)); + + // Create the body if it's not created already. + if (IsActive() && m_Body == NULL) + Create (); + + // Apply all body properties. + // Note that we should not allow anything to adjust the current sleep-state of the body here when the component + // is first woken otherwise we'll potentially override the need to be initially asleep (sleep mode). + if (!(awakeMode & (kDidLoadFromDisk | kInstantiateOrCreateFromCodeAwakeFromLoad))) + { + SetMass (m_Mass); + SetDrag (m_LinearDrag); + SetAngularDrag (m_AngularDrag); + SetGravityScale (m_GravityScale); + SetIsKinematic (m_IsKinematic); + SetFixedAngle (m_FixedAngle); + SetCollisionDetectionMode ((CollisionDetectionMode2D)m_CollisionDetection); + SetSleepMode ((RigidbodySleepMode2D)m_SleepingMode); + } + + // Inform the colliders about the new body. + if (awakeMode & kInstantiateOrCreateFromCodeAwakeFromLoad) + InformCollidersOfNewBody (); +} + + +void Rigidbody2D::Deactivate (DeactivateOperation operation) +{ + Cleanup (); +} + + +void Rigidbody2D::Create () +{ + if (m_Body != NULL) + return; + + // Configure the body definitio0n. + b2BodyDef bodyDef; + bodyDef.type = m_IsKinematic ? b2_kinematicBody : b2_dynamicBody; + bodyDef.userData = this; + bodyDef.bullet = m_CollisionDetection == kContinuousCollision2D; + bodyDef.linearDamping = m_LinearDrag; + bodyDef.angularDamping = m_AngularDrag; + bodyDef.gravityScale = m_GravityScale; + bodyDef.fixedRotation = m_FixedAngle; + bodyDef.allowSleep = m_SleepingMode != kNeverSleep2D; + bodyDef.awake = m_SleepingMode != kStartAsleep2D;; + + // Fetch the body pose. + if (IsActive ()) + FetchPoseFromTransform (&bodyDef.position, &bodyDef.angle); + + // Create the body. + m_Body = GetPhysics2DWorld()->CreateBody (&bodyDef); + + // Calculate the collider body mass. + CalculateColliderBodyMass (); + + // Set interpolation. + SetInterpolation ((RigidbodyInterpolation2D)m_Interpolate); +} + + +void Rigidbody2D::TransformChanged (int changeMask) +{ + // Finish if no body exists. + if (!m_Body) + return; + + // Finish if transform message is disabled. + if (!GetPhysics2DManager().IsTransformMessageEnabled()) + return; + + // Update the rigid-body transform if the position or rotation has changed. + if (changeMask & (Transform::kPositionChanged | Transform::kRotationChanged)) + { + b2Vec2 position; + float angle; + FetchPoseFromTransform (&position, &angle); + m_Body->SetTransform (position, angle); + m_Body->SetAwake(true); + + // Disable interpolation if transform has changed. + if (m_InterpolationInfo) + m_InterpolationInfo->disabled = 1; + } + + // Recreate inbound joints if the scale has changed. + if (changeMask & Transform::kScaleChanged) + { + ReCreateInboundJoints (); + return; + } +} + + +void Rigidbody2D::ApplyRootMotionBuiltin (RootMotionData* rootMotion) +{ + if (m_Body == NULL || rootMotion->didApply) + return; + + if(GetIsKinematic()) + { + b2Vec2 position = m_Body->GetPosition (); + position.x += rootMotion->deltaPosition.x; + position.y += rootMotion->deltaPosition.y; + m_Body->SetTransform (position, rootMotion->targetRotation.z); + } + else + { + Quaternionf rotation = GetComponent(Transform).GetRotation(); + Quaternionf invRotation = Inverse(rotation); + + // Get the physics velocity in local space + const Vector2f velocity2 = GetVelocity(); + const Vector3f physicsVelocityLocal = RotateVectorByQuat(invRotation, Vector3f(velocity2.x, velocity2.y, 0.0f)); + + // Get the local space velocity and blend it with the physics velocity on the y-axis based on gravity weight + // We do this in local space in order to support moving on a curve earth with gravity changing direction + Vector3f animVelocityGlobal = rootMotion->deltaPosition * GetInvDeltaTime(); + Vector3f localVelocity = RotateVectorByQuat (invRotation, animVelocityGlobal); + localVelocity.y = Lerp (localVelocity.y, physicsVelocityLocal.y, rootMotion->gravityWeight); + + // If we use gravity, when we are in a jumping root motion, we have to cancel out the gravity + // applied by default. When doing for example a jump the only thing affecting velocity should be the animation data. + // The animation already has gravity applied in the animation data so to speak... + if (GetGravityScale () > 0.0f) + AddForce(GetPhysics2DSettings ().GetGravity() * -Lerp(1.0F, 0.0F, rootMotion->gravityWeight)); + + // Apply velocity & rotation + Vector3f globalVelocity = RotateVectorByQuat(rotation, localVelocity); + m_Body->SetLinearVelocity (b2Vec2(globalVelocity.x, globalVelocity.y)); + const Vector3f localEuler = QuaternionToEuler (rootMotion->targetRotation); + m_Body->SetTransform (m_Body->GetPosition (), localEuler.z); + } + + m_Body->SetAwake(true); + rootMotion->didApply = true; +} + + +void Rigidbody2D::SetDrag (float drag) +{ + ABORT_INVALID_FLOAT (drag, drag, Rigidbody2D); + + m_LinearDrag = clamp<float> (drag, 0.0f, PHYSICS_2D_LARGE_RANGE_CLAMP); + SetDirty (); + + if (m_Body) + m_Body->SetLinearDamping (m_LinearDrag); +} + + +void Rigidbody2D::SetAngularDrag (float drag) +{ + ABORT_INVALID_FLOAT (drag, angularDrag, Rigidbody2D); + + m_AngularDrag = clamp<float> (drag, 0.0f, PHYSICS_2D_LARGE_RANGE_CLAMP); + SetDirty (); + + if (m_Body) + m_Body->SetAngularDamping (m_AngularDrag); +} + + +void Rigidbody2D::SetGravityScale (float scale) +{ + ABORT_INVALID_FLOAT (scale, gravityScale, Rigidbody2D); + + m_GravityScale = clamp<float> (scale, -PHYSICS_2D_LARGE_RANGE_CLAMP, PHYSICS_2D_LARGE_RANGE_CLAMP); + SetDirty (); + + if (m_Body) + { + m_Body->SetGravityScale (m_GravityScale); + + // Wake body if a non-zero gravity is applied. + if (m_GravityScale < 0.0f || m_GravityScale > 0.0f) + m_Body->SetAwake (true); + } +} + + +void Rigidbody2D::SetIsKinematic (bool isKinematic) +{ + m_IsKinematic = isKinematic; + SetDirty(); + + if (m_Body) + { + m_Body->SetType (m_IsKinematic ? b2_kinematicBody : b2_dynamicBody); + + // Wake the body. + m_Body->SetAwake(true); + + // If transitioning into a kinematic body, reset velocities to match what 3D physics does. + if (m_IsKinematic) + { + m_Body->SetLinearVelocity (b2Vec2_zero); + m_Body->SetAngularVelocity (0.0f); + } + + // Calculate the collider body mass. + // NOTE: Unfortunately we must do this here as Box2D resets its mass-data when this is changed. + CalculateColliderBodyMass (); + } +} + + +void Rigidbody2D::SetFixedAngle (bool fixedAngle) +{ + m_FixedAngle = fixedAngle; + SetDirty(); + + if (m_Body) + { + m_Body->SetFixedRotation(m_FixedAngle); + + // Calculate the collider body mass. + // NOTE: Unfortunately we must do this here as Box2D resets its mass-data when this is changed. + CalculateColliderBodyMass (); + } +} + + +void Rigidbody2D::SetInterpolation (RigidbodyInterpolation2D interpolation) +{ + m_Interpolate = interpolation; + SetDirty(); + + if (m_Body) + UpdateInterpolationInfo(); +} + + +void Rigidbody2D::SetSleepMode (RigidbodySleepMode2D mode) +{ + m_SleepingMode = mode; + SetDirty (); + + if (m_Body) + m_Body->SetSleepingAllowed (mode != kNeverSleep2D); +} + + +void Rigidbody2D::SetCollisionDetectionMode (CollisionDetectionMode2D mode) +{ + m_CollisionDetection = mode; + SetDirty (); + + if (m_Body) + m_Body->SetBullet( mode == kContinuousCollision2D ); +} + + +Vector3f Rigidbody2D::GetBodyPosition () const +{ + Assert (m_Body != NULL); + + const b2Vec2& pos2D = m_Body->GetPosition(); + const Transform& transform = GetGameObject().GetComponent (Transform); + Vector3f pos3D = transform.GetPosition(); + pos3D.x = pos2D.x; + pos3D.y = pos2D.y; + + return pos3D; +} + + +Quaternionf Rigidbody2D::GetBodyRotation () const +{ + Assert (m_Body != NULL); + + return EulerToQuaternion (Vector3f(0, 0, m_Body->GetAngle())); +} + + +Vector2f Rigidbody2D::GetVelocity () const +{ + // Return no linear velocity if no body is available. + if (m_Body == NULL) + return Vector2f::zero; + + // Return the body linear velocity. + const b2Vec2& velocity = m_Body->GetLinearVelocity (); + return Vector2f (velocity.x, velocity.y); +} + + +void Rigidbody2D::SetVelocity (const Vector2f& velocity) +{ + ABORT_INVALID_VECTOR2 (velocity, velocity, Rigidbody2D); + + // Ignore linear velocity if no body is available. + if (m_Body == NULL) + return; + + m_Body->SetLinearVelocity( b2Vec2(velocity.x, velocity.y) ); +} + + +float Rigidbody2D::GetAngularVelocity () const +{ + // Return no angular velocity if no body is available. + if (m_Body == NULL) + return 0.0f; + + // Return the body angular velocity. + return math::degrees (m_Body->GetAngularVelocity()); +} + + +void Rigidbody2D::SetAngularVelocity (float velocity) +{ + ABORT_INVALID_FLOAT (velocity, angularVelocity, Rigidbody2D); + + // Ignore linear velocity if no body is available. + if (m_Body == NULL) + return; + + m_Body->SetAngularVelocity (math::radians (velocity)); +} + + +void Rigidbody2D::SetSleeping (bool sleeping) +{ + // Ignore sleeping if no body is available. + if (m_Body == NULL) + return; + + m_Body->SetAwake(!sleeping); +} + + +bool Rigidbody2D::IsSleeping() const +{ + // Not sleeping if no body is available. + if (m_Body == NULL) + return false; + + return !m_Body->IsAwake(); +} + + +void Rigidbody2D::SetMass (float mass) +{ + ABORT_INVALID_FLOAT (mass, mass, Rigidbody2D); + + // Clamp mass. + m_Mass = clamp<float> (mass, 0.0001f, PHYSICS_2D_LARGE_RANGE_CLAMP); + + // Calculate the collider body mass. + CalculateColliderBodyMass (); +} + + +void Rigidbody2D::CalculateColliderBodyMass () +{ + // Finish if body is not dynamic or has no fixtures. + if (m_Body == NULL || + m_Body->GetType () != b2_dynamicBody) + return; + + // Reset the mass-data. + m_Body->ResetMassData (); + + // Scale body mass to target mass. + b2MassData bodyMassData; + m_Body->GetMassData (&bodyMassData); + const float massScale = m_Mass / bodyMassData.mass; + bodyMassData.mass *= massScale; + + // Scale or set rotational inertia. + if (m_Body->GetFixtureCount () > 0) + bodyMassData.I *= massScale; + else + bodyMassData.I = m_Mass * 10.0f; + + m_Body->SetMassData (&bodyMassData); +} + + +void Rigidbody2D::AddForce (const Vector2f& force) +{ + ABORT_INVALID_ARG_VECTOR2 (force, force, AddForce, Rigidbody2D); + + // Ignore force if no body is available. + if (m_Body == NULL) + return; + + m_Body->ApplyForceToCenter (b2Vec2(force.x,force.y), true); +} + + +void Rigidbody2D::AddTorque (float torque) +{ + ABORT_INVALID_ARG_FLOAT (torque, torque, AddTorque, Rigidbody2D); + + // Ignore torque if no body is available. + if (m_Body == NULL) + return; + + m_Body->ApplyTorque (torque, true); +} + + +void Rigidbody2D::AddForceAtPosition (const Vector2f& force, const Vector2f& position) +{ + ABORT_INVALID_ARG_VECTOR2 (force, force, AddForceAtPosition, Rigidbody2D); + ABORT_INVALID_ARG_VECTOR2 (position, position, AddForceAtPosition, Rigidbody2D); + + // Ignore force-at-position if no body is available. + if (m_Body == NULL) + return; + + m_Body->ApplyForce (b2Vec2(force.x, force.y), b2Vec2(position.x, position.y), true); +} + + +Rigidbody2D* Rigidbody2D::FindRigidbody (const GameObject* gameObject, const Rigidbody2D* ignoreRigidbody) +{ + Assert (gameObject != NULL); + + // If there's a rigid-body on this game-object then return it. + Rigidbody2D* rigidBody = gameObject->QueryComponent (Rigidbody2D); + if (rigidBody && rigidBody != ignoreRigidbody && rigidBody->IsActive ()) + return rigidBody; + + // Search for a rigid-body up the transform hierarchy. + Transform* parent = gameObject->GetComponent (Transform).GetParent (); + while (parent) + { + GameObject* parentGameObject = parent->GetGameObjectPtr (); + if (parentGameObject) + rigidBody = parentGameObject->QueryComponent (Rigidbody2D); + else + rigidBody = NULL; + + if (rigidBody && rigidBody != ignoreRigidbody && rigidBody->IsActive ()) + return rigidBody; + + parent = parent->GetParent (); + } + + // Nothing found! + return NULL; +} + + +// -------------------------------------------------------------------------- + + +void Rigidbody2D::FetchPoseFromTransform (b2Vec2* outPos, float* outRot) +{ + Transform& transform = GetComponent (Transform); + Vector3f pos = transform.GetPosition (); + Quaternionf rot = transform.GetRotation (); + + AssertFiniteParameter(pos) + AssertFiniteParameter(rot) + + float rotZ = QuaternionToEuler(rot).z; + outPos->Set (pos.x, pos.y); + *outRot = rotZ; +} + + +void Rigidbody2D::UpdateInterpolationInfo () +{ + // Remove the interpolation info if no interpolation selected. + if (m_Interpolate == kNoInterpolation2D) + { + UNITY_DELETE(m_InterpolationInfo, kMemPhysics); + return; + } + + // Finish if we already have interpolation info. + if (m_InterpolationInfo != NULL) + return; + + // Generate the interpolation info. + m_InterpolationInfo = UNITY_NEW(Rigidbody2DInterpolationInfo, kMemPhysics); + Rigidbody2DInterpolationInfo& info = *m_InterpolationInfo; + info.body = this; + info.disabled = 1; + info.position = Vector3f::zero; + info.rotation = Quaternionf::identity(); + GetPhysics2DManager().GetInterpolatedBodies().push_back(*m_InterpolationInfo); +} + + +void Rigidbody2D::Cleanup () +{ + if (m_Body == NULL) + return; + + // Process all colliders as the rigid-body is going away. + const int fixtureCount = m_Body->GetFixtureCount (); + if (fixtureCount > 0 ) + { + dynamic_array<Collider2D*> attachedColliders(kMemTempAlloc); + + // Gather all attached colliders. + for (b2Fixture* fixture = m_Body->GetFixtureList (); fixture != NULL; fixture = fixture->GetNext ()) + { + Collider2D* collider = (Collider2D*)fixture->GetUserData (); + attachedColliders.push_back (collider); + } + + // Process all colliders. + for (dynamic_array<Collider2D*>::iterator colliderItr = attachedColliders.begin (); colliderItr != attachedColliders.end (); ++colliderItr) + (*colliderItr)->Cleanup (); + } + + // Process all joints as the rigid-body is going away. + if (m_Body->GetJointList () != NULL) + { + dynamic_array<Joint2D*> attachedJoints(kMemTempAlloc); + + // Gather all joints. + for (b2JointEdge* joints = m_Body->GetJointList(); joints != NULL; joints = joints->next) + attachedJoints.push_back ((Joint2D*)joints->joint->GetUserData()); + + // Process all joints. + for (dynamic_array<Joint2D*>::iterator jointItr = attachedJoints.begin (); jointItr != attachedJoints.end (); ++jointItr) + (*jointItr)->Cleanup (); + } + + // Destroy the body. + GetPhysics2DWorld()->DestroyBody (m_Body); + m_Body = NULL; + + // Destroy the body interpolation information. + UNITY_DELETE(m_InterpolationInfo, kMemPhysics); +} + + +void Rigidbody2D::InformCollidersOfNewBody () +{ + // Fetch all potential colliders. + dynamic_array<Unity::Component*> colliders (kMemTempAlloc); + GetComponentsInChildren (GetGameObject (), false, ClassID (Collider2D), colliders); + + // Finish if no colliders are found. + if (colliders.size () == 0) + return; + + // Recreate the colliders. + for (dynamic_array<Unity::Component*>::iterator colliderItr = colliders.begin (); colliderItr != colliders.end (); ++colliderItr) + { + Collider2D* collider = (Collider2D*)*colliderItr; + + // Ignore if not enabled. + if (!collider->GetEnabled ()) + continue; + + collider->RecreateCollider (NULL); + } +} + + +void Rigidbody2D::ReCreateInboundJoints () +{ + // Finish if not appropriate. + if (m_Body == NULL) + return; + + // Fetch the joints this body is connected to. + // This can occur when there's "inbound" joints from other components on other game objects. + b2JointEdge* joints = m_Body->GetJointList(); + + // Process all joints. + while (joints != NULL) + { + // Fetch the joint. + b2Joint* joint = joints->joint; + + // Fetch the next joint. + b2JointEdge* nextJoint = joints->next; + + // Fetch the joint component. + Joint2D* jointComponent = (Joint2D*)joint->GetUserData(); + Assert (jointComponent != NULL); + + // Recreate the joint. + jointComponent->ReCreate (); + + // Next joint. + joints = nextJoint; + } +} + +#endif // #if ENABLE_2D_PHYSICS diff --git a/Runtime/Physics2D/ScriptBindings/Physics2DBindings.txt b/Runtime/Physics2D/ScriptBindings/Physics2DBindings.txt new file mode 100644 index 0000000..6298c40 --- /dev/null +++ b/Runtime/Physics2D/ScriptBindings/Physics2DBindings.txt @@ -0,0 +1,820 @@ +C++RAW +#include "UnityPrefix.h" +#include "Configuration/UnityConfigure.h" +#include "Runtime/Scripting/ScriptingExportUtility.h" +#include "Runtime/Scripting/ScriptingUtility.h" +#include "Runtime/Scripting/ScriptingManager.h" + +#include "Runtime/Physics2D/Physics2DManager.h" +#include "Runtime/Physics2D/Physics2DMaterial.h" +#include "Runtime/Physics2D/Physics2DSettings.h" +#include "Runtime/Physics2D/RigidBody2D.h" +#include "Runtime/Physics2D/PolygonCollider2D.h" +#include "Runtime/Physics2D/SpriteCollider2D.h" +#include "Runtime/Physics2D/CircleCollider2D.h" +#include "Runtime/Physics2D/BoxCollider2D.h" +#include "Runtime/Physics2D/EdgeCollider2D.h" + +#include "Runtime/Physics2D/Joint2D.h" +#include "Runtime/Physics2D/SpringJoint2D.h" +#include "Runtime/Physics2D/DistanceJoint2D.h" +#include "Runtime/Physics2D/HingeJoint2D.h" +#include "Runtime/Physics2D/SliderJoint2D.h" + +#include "Runtime/Geometry/Ray.h" +#include "Runtime/Graphics/SpriteFrame.h" +#include "Runtime/Graphics/Polygon2D.h" +#include "Runtime/Misc/GameObjectUtility.h" + +CSRAW + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace UnityEngine +{ + + +CONDITIONAL ENABLE_2D_PHYSICS +NONSEALED_CLASS Physics2D + CSRAW public const int IgnoreRaycastLayer = 1 << 2; + CSRAW public const int DefaultRaycastLayers = ~IgnoreRaycastLayer; + CSRAW public const int AllLayers = ~0; + + + // -------------------------------------------------------------------------- + + + THREAD_SAFE + CUSTOM_PROP static int velocityIterations { return GetPhysics2DSettings().GetVelocityIterations (); } { SCRIPTINGAPI_THREAD_CHECK(SetVelocityIterations) return GetPhysics2DSettings().SetVelocityIterations (value); } + CUSTOM_PROP static int positionIterations { return GetPhysics2DSettings().GetPositionIterations (); } { SCRIPTINGAPI_THREAD_CHECK(SetPositionIterations) return GetPhysics2DSettings().SetPositionIterations (value); } + CUSTOM_PROP static Vector2 gravity { return GetPhysics2DSettings().GetGravity (); } { SCRIPTINGAPI_THREAD_CHECK(set_gravity) return GetPhysics2DSettings().SetGravity (value); } + CUSTOM_PROP static bool raycastsHitTriggers { return GetPhysics2DSettings().GetRaycastsHitTriggers (); } { SCRIPTINGAPI_THREAD_CHECK(SetRaycastsHitTriggers) return GetPhysics2DSettings().SetRaycastsHitTriggers (value); } + + + // -------------------------------------------------------------------------- + + + CUSTOM static public void IgnoreLayerCollision (int layer1, int layer2, bool ignore = true) + { + GetPhysics2DSettings().IgnoreCollision(layer1, layer2, ignore); + } + + CUSTOM static public bool GetIgnoreLayerCollision (int layer1, int layer2) + { + return GetPhysics2DSettings().GetIgnoreCollision(layer1, layer2); + } + + + // -------------------------------------------------------------------------- + + + C++RAW + + // Create a managed array of native ray-cast hits. + static ScriptingArrayPtr CreateManagedRaycastArrayFromNative (RaycastHit2D* nativeHits, size_t size) + { + // Return an empty array if no hits. + if (size == 0) + return CreateEmptyStructArray (MONO_COMMON.raycastHit2D); + + // Generate scripting array. + ScriptingArrayPtr scriptArray = CreateScriptingArray<RaycastHit2D> (MONO_COMMON.raycastHit2D, size); + + // Transfer elements. + RaycastHit2D* scriptHit = Scripting::GetScriptingArrayStart<RaycastHit2D> (scriptArray); + for (size_t i = 0; i < size; ++i, ++scriptHit, ++nativeHits) + { + // Transfer members. + scriptHit->point = nativeHits->point; + scriptHit->normal = nativeHits->normal; + scriptHit->fraction = nativeHits->fraction; + scriptHit->collider = reinterpret_cast<Collider2D*>(ScriptingGetObjectReference (nativeHits->collider)); + } + return scriptArray; + } + + CSRAW + + + // -------------------------------------------------------------------------- + + + CUSTOM private static void Internal_Linecast (Vector2 start, Vector2 end, int layerMask, float minDepth, float maxDepth, out RaycastHit2D raycastHit) + { + if (GetPhysics2DManager ().Linecast (start, end, layerMask, minDepth, maxDepth, raycastHit, 1) == 1) + raycastHit->collider = reinterpret_cast<Collider2D*>(ScriptingGetObjectReference (raycastHit->collider)); + } + + // Returns the first hit along the specified line. + CSRAW public static RaycastHit2D Linecast (Vector2 start, Vector2 end, int layerMask = DefaultRaycastLayers, float minDepth = -Mathf.Infinity, float maxDepth = Mathf.Infinity) + { + RaycastHit2D raycastHit; + Internal_Linecast (start, end, layerMask, minDepth, maxDepth, out raycastHit); + return raycastHit; + } + + // Returns all hits along the specified line. + CUSTOM public static RaycastHit2D[] LinecastAll (Vector2 start, Vector2 end, int layerMask = DefaultRaycastLayers, float minDepth = -Mathf.Infinity, float maxDepth = Mathf.Infinity) + { + dynamic_array<RaycastHit2D> raycastHits(kMemTempAlloc); + if (GetPhysics2DManager ().LinecastAll (start, end, layerMask, minDepth, maxDepth, &raycastHits) == 0) + return CreateEmptyStructArray (MONO_COMMON.raycastHit2D); + + return CreateManagedRaycastArrayFromNative (raycastHits.data (), raycastHits.size ()); + } + + // Returns all hits along the line (limited by the size of the array). This does not produce any garbage. + CUSTOM public static int LinecastNonAlloc (Vector2 start, Vector2 end, RaycastHit2D[] results, int layerMask = DefaultRaycastLayers, float minDepth = -Mathf.Infinity, float maxDepth = Mathf.Infinity) + { + RaycastHit2D* raycastHits = Scripting::GetScriptingArrayStart<RaycastHit2D>(results); + const int resultCount = GetPhysics2DManager ().Linecast (start, end, layerMask, minDepth, maxDepth, raycastHits, GetScriptingArraySize(results)); + + for (size_t i = 0; i < resultCount; ++i, ++raycastHits) + raycastHits->collider = reinterpret_cast<Collider2D*>(ScriptingGetObjectReference (raycastHits->collider)); + + return resultCount; + } + + CUSTOM private static void Internal_Raycast (Vector2 origin, Vector2 direction, float distance, int layerMask, float minDepth, float maxDepth, out RaycastHit2D raycastHit) + { + if (GetPhysics2DManager ().Raycast (origin, direction, distance, layerMask, minDepth, maxDepth, raycastHit, 1) == 1) + raycastHit->collider = reinterpret_cast<Collider2D*>(ScriptingGetObjectReference (raycastHit->collider)); + } + + // Returns the first hit along the ray. + CSRAW public static RaycastHit2D Raycast (Vector2 origin, Vector2 direction, float distance = Mathf.Infinity, int layerMask = DefaultRaycastLayers, float minDepth = -Mathf.Infinity, float maxDepth = Mathf.Infinity) + { + RaycastHit2D raycastHit; + Internal_Raycast (origin, direction, distance, layerMask, minDepth, maxDepth, out raycastHit); + return raycastHit; + } + + // Returns all hits along the ray. + CUSTOM public static RaycastHit2D[] RaycastAll (Vector2 origin, Vector2 direction, float distance = Mathf.Infinity, int layerMask = DefaultRaycastLayers, float minDepth = -Mathf.Infinity, float maxDepth = Mathf.Infinity) + { + dynamic_array<RaycastHit2D> raycastHits(kMemTempAlloc); + if (GetPhysics2DManager ().RaycastAll (origin, direction, distance, layerMask, minDepth, maxDepth, &raycastHits) == 0) + return CreateEmptyStructArray(MONO_COMMON.raycastHit2D); + + return CreateManagedRaycastArrayFromNative (raycastHits.data (), raycastHits.size ()); + } + + // Returns all hits along the ray (limited by the size of the array). This does not produce any garbage. + CUSTOM public static int RaycastNonAlloc (Vector2 origin, Vector2 direction, RaycastHit2D[] results, float distance = Mathf.Infinity, int layerMask = DefaultRaycastLayers, float minDepth = -Mathf.Infinity, float maxDepth = Mathf.Infinity) + { + RaycastHit2D* raycastHits = Scripting::GetScriptingArrayStart<RaycastHit2D>(results); + const int resultCount = GetPhysics2DManager ().Raycast (origin, direction, distance, layerMask, minDepth, maxDepth, raycastHits, GetScriptingArraySize(results)); + + for (size_t i = 0; i < resultCount; ++i, ++raycastHits) + raycastHits->collider = reinterpret_cast<Collider2D*>(ScriptingGetObjectReference (raycastHits->collider)); + + return resultCount; + } + + + // -------------------------------------------------------------------------- + + + CUSTOM private static void Internal_GetRayIntersection (Ray ray, float distance, int layerMask, out RaycastHit2D raycastHit) + { + if (GetPhysics2DManager ().GetRayIntersection (ray.GetOrigin (), ray.GetDirection (), distance, layerMask, raycastHit, 1) == 1) + raycastHit->collider = reinterpret_cast<Collider2D*>(ScriptingGetObjectReference (raycastHit->collider)); + } + + // Returns the first hit intersecting the 3D ray. + CSRAW public static RaycastHit2D GetRayIntersection (Ray ray, float distance = Mathf.Infinity, int layerMask = DefaultRaycastLayers) + { + RaycastHit2D raycastHit; + Internal_GetRayIntersection (ray, distance, layerMask, out raycastHit); + return raycastHit; + } + + // Returns all hits intersecting the 3D ray. + CUSTOM public static RaycastHit2D[] GetRayIntersectionAll (Ray ray, float distance = Mathf.Infinity, int layerMask = DefaultRaycastLayers) + { + dynamic_array<RaycastHit2D> raycastHits(kMemTempAlloc); + if (GetPhysics2DManager ().GetRayIntersectionAll (ray.GetOrigin (), ray.GetDirection (), distance, layerMask, &raycastHits) == 0) + return CreateEmptyStructArray(MONO_COMMON.raycastHit2D); + + return CreateManagedRaycastArrayFromNative (raycastHits.data (), raycastHits.size ()); + } + + // Returns all hits intersecting the 3D ray (limited by the size of the array). This does not produce any garbage. + CUSTOM public static int GetRayIntersectionNonAlloc (Ray ray, RaycastHit2D[] results, float distance = Mathf.Infinity, int layerMask = DefaultRaycastLayers) + { + RaycastHit2D* raycastHits = Scripting::GetScriptingArrayStart<RaycastHit2D>(results); + const int resultCount = GetPhysics2DManager ().GetRayIntersection (ray.GetOrigin (), ray.GetDirection (), distance, layerMask, raycastHits, GetScriptingArraySize(results)); + + for (size_t i = 0; i < resultCount; ++i, ++raycastHits) + raycastHits->collider = reinterpret_cast<Collider2D*>(ScriptingGetObjectReference (raycastHits->collider)); + + return resultCount; + } + + + // -------------------------------------------------------------------------- + + + // Returns a collider overlapping the point. + CUSTOM public static Collider2D OverlapPoint (Vector2 point, int layerMask = DefaultRaycastLayers, float minDepth = -Mathf.Infinity, float maxDepth = Mathf.Infinity) + { + Collider2D* collider; + const int resultCount = GetPhysics2DManager ().OverlapPoint (point, layerMask, minDepth, maxDepth, &collider, 1); + + if (resultCount == 0) + return SCRIPTING_NULL; + + return Scripting::ScriptingWrapperFor (collider); + } + + // Returns all colliders overlapping the point. + CUSTOM public static Collider2D[] OverlapPointAll (Vector2 point, int layerMask = DefaultRaycastLayers, float minDepth = -Mathf.Infinity, float maxDepth = Mathf.Infinity) + { + dynamic_array<Collider2D*> colliderHits(kMemTempAlloc); + GetPhysics2DManager ().OverlapPointAll (point, layerMask, minDepth, maxDepth, &colliderHits); + + // Generate scripting array. + return CreateScriptingArrayFromUnityObjects(colliderHits, ScriptingClassFor(Collider2D)); + } + + // Returns all colliders overlapping the point (limited by the size of the array). This does not produce any garbage. + CUSTOM public static int OverlapPointNonAlloc (Vector2 point, Collider2D[] results, int layerMask = DefaultRaycastLayers, float minDepth = -Mathf.Infinity, float maxDepth = Mathf.Infinity) + { + Collider2D** colliderHits = Scripting::GetScriptingArrayStart<Collider2D*>(results); + const int resultCount = GetPhysics2DManager ().OverlapPoint (point, layerMask, minDepth, maxDepth, colliderHits, GetScriptingArraySize(results)); + + for (size_t i = 0; i < resultCount; ++i) + colliderHits[i] = reinterpret_cast<Collider2D*>(ScriptingGetObjectReference (colliderHits[i])); + + return resultCount; + } + + // Returns a collider overlapping the circle. + CUSTOM public static Collider2D OverlapCircle (Vector2 point, float radius, int layerMask = DefaultRaycastLayers, float minDepth = -Mathf.Infinity, float maxDepth = Mathf.Infinity) + { + Collider2D* collider; + const int resultCount = GetPhysics2DManager ().OverlapCircle (point, radius, layerMask, minDepth, maxDepth, &collider, 1); + + if (resultCount == 0) + return SCRIPTING_NULL; + + return Scripting::ScriptingWrapperFor (collider); + } + + // Returns all colliders overlapping the circle. + CUSTOM public static Collider2D[] OverlapCircleAll (Vector2 point, float radius, int layerMask = DefaultRaycastLayers, float minDepth = -Mathf.Infinity, float maxDepth = Mathf.Infinity) + { + dynamic_array<Collider2D*> colliderHits(kMemTempAlloc); + GetPhysics2DManager ().OverlapCircleAll (point, radius, layerMask, minDepth, maxDepth, &colliderHits); + + // Generate scripting array. + return CreateScriptingArrayFromUnityObjects(colliderHits, ScriptingClassFor(Collider2D)); + } + + // Returns all colliders overlapping the circle (limited by the size of the array). This does not produce any garbage. + CUSTOM public static int OverlapCircleNonAlloc (Vector2 point, float radius, Collider2D[] results, int layerMask = DefaultRaycastLayers, float minDepth = -Mathf.Infinity, float maxDepth = Mathf.Infinity) + { + Collider2D** colliderHits = Scripting::GetScriptingArrayStart<Collider2D*>(results); + const int resultCount = GetPhysics2DManager ().OverlapCircle (point, radius, layerMask, minDepth, maxDepth, colliderHits, GetScriptingArraySize(results)); + + for (size_t i = 0; i < resultCount; ++i) + colliderHits[i] = reinterpret_cast<Collider2D*>(ScriptingGetObjectReference (colliderHits[i])); + + return resultCount; + } + + // Returns a collider overlapping the area. + CUSTOM public static Collider2D OverlapArea (Vector2 pointA, Vector2 pointB, int layerMask = DefaultRaycastLayers, float minDepth = -Mathf.Infinity, float maxDepth = Mathf.Infinity) + { + Collider2D* collider; + const int resultCount = GetPhysics2DManager ().OverlapArea (pointA, pointB, layerMask, minDepth, maxDepth, &collider, 1); + + if (resultCount == 0) + return SCRIPTING_NULL; + + return Scripting::ScriptingWrapperFor (collider); + } + + // Returns all colliders overlapping the area. + CUSTOM public static Collider2D[] OverlapAreaAll (Vector2 pointA, Vector2 pointB, int layerMask = DefaultRaycastLayers, float minDepth = -Mathf.Infinity, float maxDepth = Mathf.Infinity) + { + dynamic_array<Collider2D*> colliderHits(kMemTempAlloc); + GetPhysics2DManager ().OverlapAreaAll (pointA, pointB, layerMask, minDepth, maxDepth, &colliderHits); + + // Generate scripting array. + return CreateScriptingArrayFromUnityObjects(colliderHits, ScriptingClassFor(Collider2D)); + } + + // Returns all colliders overlapping the area (limited by the size of the array). This does not produce any garbage. + CUSTOM public static int OverlapAreaNonAlloc (Vector2 pointA, Vector2 pointB, Collider2D[] results, int layerMask = DefaultRaycastLayers, float minDepth = -Mathf.Infinity, float maxDepth = Mathf.Infinity) + { + Collider2D** colliderHits = Scripting::GetScriptingArrayStart<Collider2D*>(results); + const int resultCount = GetPhysics2DManager ().OverlapArea (pointA, pointB, layerMask, minDepth, maxDepth, colliderHits, GetScriptingArraySize(results)); + + for (size_t i = 0; i < resultCount; ++i) + colliderHits[i] = reinterpret_cast<Collider2D*>(ScriptingGetObjectReference (colliderHits[i])); + + return resultCount; + } + +END + + +// NOTE: must match memory layout of native RaycastHit2D +CONDITIONAL ENABLE_2D_PHYSICS +STRUCT RaycastHit2D + CSRAW private Vector2 m_Point; + CSRAW private Vector2 m_Normal; + CSRAW private float m_Fraction; + #if UNITY_WINRT + CSRAW private int m_ColliderHandle; + #else + CSRAW private Collider2D m_Collider; + #endif + + CSRAW public Vector2 point { get { return m_Point; } set { m_Point = value; } } + CSRAW public Vector2 normal { get { return m_Normal; } set { m_Normal = value; } } + CSRAW public float fraction { get { return m_Fraction; } set { m_Fraction = value; } } + + CONDITIONAL !UNITY_WINRT + CSRAW public Collider2D collider { get { return m_Collider; } } + + CONDITIONAL UNITY_WINRT + CSRAW public Collider2D collider { get { return UnityEngineInternal.ScriptingUtils.GCHandleToObject<Collider2D>(m_ColliderHandle); } } + + CSRAW public Rigidbody2D rigidbody { get { return collider != null ? collider.attachedRigidbody : null; } } + + CSRAW public Transform transform { get { + Rigidbody2D body = rigidbody; + if (body != null) + return body.transform; + else if (collider != null) + return collider.transform; + else + return null; + } } + + // Implicitly convert a hit to a boolean based upon whether a collider reference exists or not. + CSRAW public static implicit operator bool (RaycastHit2D hit) { return hit.collider != null; } + + // Compare the hit by fraction along the ray. If no colliders exist then fraction is moved "up". This allows sorting an array of sparse results. + CSRAW public int CompareTo(RaycastHit2D other) { if (collider == null) return 1; if (other.collider == null) return -1; return fraction.CompareTo(other.fraction); } +END + + +// [[Rigidbody2D]] interpolation mode. +ENUM RigidbodyInterpolation2D + // No Interpolation. + None = 0, + + // Interpolation will always lag a little bit behind but can be smoother than extrapolation. + Interpolate = 1, + + // Extrapolation will predict the position of the rigidbody based on the current velocity. + Extrapolate = 2 +END + + +// [[Rigidbody2D]] sleeping mode. +ENUM RigidbodySleepMode2D + // Never sleep. + NeverSleep = 0, + + // Start the rigid body awake. + StartAwake = 1, + + // Start the rigid body asleep. + StartAsleep = 2 +END + + +// [[Rigidbody2D]] collision detection mode. +ENUM CollisionDetectionMode2D + // Provides standard (and least expensive) collision detection suitable for most circumstances. + None = 0, + + // Provides the most accurate collision detection to present tunnelling but is more expensive. Use for vert fast moving objects only. + Continuous = 1 +END + + +CONDITIONAL ENABLE_2D_PHYSICS +CLASS Rigidbody2D : Component + // The linear velocity vector of the object. + AUTO_PROP Vector2 velocity GetVelocity SetVelocity + + // The angular velocity vector of the object in degrees/sec. + AUTO_PROP float angularVelocity GetAngularVelocity SetAngularVelocity + + // The (linear) drag of the object. + AUTO_PROP float drag GetDrag SetDrag + + // The angular drag of the object. + AUTO_PROP float angularDrag GetAngularDrag SetAngularDrag + + // Controls the effect of gravity on the object. + AUTO_PROP float gravityScale GetGravityScale SetGravityScale + + // Controls whether the object is kinematic or dynamic. + AUTO_PROP bool isKinematic GetIsKinematic SetIsKinematic + + // Controls whether the objects angle can be changed by collisions with other objects. + AUTO_PROP bool fixedAngle IsFixedAngle SetFixedAngle + + // Checks whether the rigid body is sleeping or not. + CUSTOM bool IsSleeping() { return self->IsSleeping(); } + + // Checks whether the rigid body is awake or not. + CUSTOM bool IsAwake() { return !self->IsSleeping(); } + + // Sets the rigid body into a sleep state. + CUSTOM void Sleep() { self->SetSleeping(true); } + + // Wakes the rigid from sleeping. + CUSTOM void WakeUp() { self->SetSleeping(false); } + + // Interpolation allows you to smooth out the effect of running physics at a fixed rate. + AUTO_PROP RigidbodyInterpolation2D interpolation GetInterpolation SetInterpolation + + // Controls how the object sleeps. + AUTO_PROP RigidbodySleepMode2D sleepMode GetSleepMode SetSleepMode + + // The Rigidbody's collision detection mode. + AUTO_PROP CollisionDetectionMode2D collisionDetectionMode GetCollisionDetectionMode SetCollisionDetectionMode + + // Controls the mass of the object by adjusting the density of all colliders attached to the object. + AUTO_PROP float mass GetMass SetMass + + CUSTOM void AddForce (Vector2 force) { self->AddForce (force); } + CUSTOM void AddTorque (float torque) { self->AddTorque (torque); } + CUSTOM void AddForceAtPosition (Vector2 force, Vector2 position) { self->AddForceAtPosition (force, position); } +END + + +CONDITIONAL ENABLE_2D_PHYSICS +NONSEALED_CLASS Collider2D : Behaviour + AUTO_PROP bool isTrigger GetIsTrigger SetIsTrigger + AUTO_PTR_PROP Rigidbody2D attachedRigidbody GetRigidbody + + // Gets the number of shapes this collider has generated. + AUTO_PROP int shapeCount GetShapeCount + + // Checks whether the specified point overlaps the collider or not. + CUSTOM public bool OverlapPoint (Vector2 point) { return self->OverlapPoint(point); } + + // The shared physics material of this collider. + CUSTOM_PROP PhysicsMaterial2D sharedMaterial { return Scripting::ScriptingWrapperFor (self->GetMaterial ()); } { self->SetMaterial (value); } + + // OnCollisionEnter2D is called when this [[Collider2D]] has begun touching another [[Collider2D]]. + CSNONE void OnCollisionEnter2D (Collision2D info); + + // OnCollisionStay2D is called once per frame for this [[Collider2D]] if it is continuing to touch another [[Collider2D]]. + CSNONE void OnCollisionStay2D (Collision2D info); + + // OnCollisionExit2D is called when this [[Collider2D]] has stopped touching another [[Collider2D]]. + CSNONE void OnCollisionExit2D (Collision2D info); + + // OnTriggerEnter2D is called when a [[Collider2D]] has begun touching another [[Collider2D]] configured as a trigger. + CSNONE void OnTriggerEnter2D (Collider2D other); + + // OnTriggerStay2D is called once per frame for a [[Collider2D]] if it is continuing to touch another [[Collider2D]] configured as a trigger. + CSNONE void OnTriggerStay2D (Collider2D other); + + // OnTriggerExit2D is called when a [[Collider2D]] has stopped touching another [[Collider2D]] configured as a trigger. + CSNONE void OnTriggerExit2D (Collider2D other); + +END + + +CONDITIONAL ENABLE_2D_PHYSICS +CLASS CircleCollider2D : Collider2D + AUTO_PROP Vector2 center GetCenter SetCenter + AUTO_PROP float radius GetRadius SetRadius +END + + +CONDITIONAL ENABLE_2D_PHYSICS +CLASS BoxCollider2D : Collider2D + AUTO_PROP Vector2 center GetCenter SetCenter + AUTO_PROP Vector2 size GetSize SetSize +END + + +CONDITIONAL ENABLE_2D_PHYSICS +CLASS EdgeCollider2D : Collider2D + + // Reset to a single horizontal edge. + CUSTOM void Reset() { self->Reset (); } + + // Get the number of edges. This is one less than the number of points. + CUSTOM_PROP int edgeCount { { return self->GetEdgeCount (); } } + + // Get the number of points. This cannot be less than two which will form a single edge. + CUSTOM_PROP int pointCount { { return self->GetPointCount (); } } + + // Get or set the points defining multiple continuous edges. + CUSTOM_PROP Vector2[] points + { + return CreateScriptingArrayStride<Vector2f>(self->GetPoints ().data (), self->GetPointCount (), MONO_COMMON.vector2, sizeof(Vector2f)); + } + { + if (self->SetPoints (Scripting::GetScriptingArrayStart<Vector2f> (value), GetScriptingArraySize (value))) + return; + + ErrorString("Invalid points assigned to 2D edge collider."); + } + +END + + +CONDITIONAL (ENABLE_2D_PHYSICS && ENABLE_SPRITECOLLIDER) +CLASS SpriteCollider2D : Collider2D + + CUSTOM_PROP Sprite sprite + { + return Scripting::ScriptingWrapperFor(self->GetSprite()); + } + { + self->SetSprite(value); + } + +END + + +CONDITIONAL ENABLE_2D_PHYSICS +CLASS PolygonCollider2D : Collider2D + + // Get/Set a single path of points. + CUSTOM_PROP Vector2[] points + { + return CreateScriptingArrayStride<Vector2f>(self->GetPoly().GetPoints(), self->GetPoly().GetPointCount(), MONO_COMMON.vector2, sizeof(*self->GetPoly().GetPoints())); + } + { + self->GetPoly().SetPoints(Scripting::GetScriptingArrayStart<Vector2f>(value), GetScriptingArraySize(value)); + self->RefreshPoly(); + } + + // Get the specified path of points. + CUSTOM Vector2[] GetPath(int index) + { + if (index >= self->GetPoly().GetPathCount()) + { + Scripting::RaiseOutOfRangeException("Path %d does not exist.", index); + return SCRIPTING_NULL; + } + + const Polygon2D::TPath& path = self->GetPoly().GetPath(index); + return CreateScriptingArrayStride<Vector2f>(path.data(), path.size(), MONO_COMMON.vector2, sizeof(*path.data())); + } + + // Set the specified path of points. + CUSTOM void SetPath(int index, Vector2[] points) + { + const Vector2f* arrayStart = Scripting::GetScriptingArrayStart<Vector2f>(points); + const int arraySize = GetScriptingArraySize(points); + Polygon2D::TPath path; + path.assign(arrayStart, arrayStart+arraySize); + self->GetPoly().SetPath(index, path); + self->RefreshPoly(); + } + + // Get the number of paths. + CUSTOM_PROP int pathCount { return self->GetPoly().GetPathCount(); } { self->GetPoly().SetPathCount(value); self->RefreshPoly(); } + + // Get the total number of points in all paths. + CUSTOM int GetTotalPointCount() { return self->GetPoly().GetTotalPointCount(); } + + // Create a primitive n-sided polygon. + CUSTOM void CreatePrimitive(int sides, Vector2 scale = Vector2.one, Vector2 offset = Vector2.zero) + { + if (sides < 3) + { + ErrorString ("Cannot create a 2D polygon primitive collider with less than two sides."); + return; + } + + if (scale.x <= 0.0f || scale.y <= 0.0f) + { + ErrorString ("Cannot create a 2D polygon primitive collider with an axis scale less than or equal to zero."); + return; + } + + self->CreatePrimitive (sides, scale, offset); + } + +END + +// Describes a contact point where the collision occurs. +CONDITIONAL ENABLE_2D_PHYSICS +STRUCT ContactPoint2D + CSRAW internal Vector2 m_Point; + CSRAW internal Vector2 m_Normal; + CSRAW internal Collider2D m_Collider; + CSRAW internal Collider2D m_OtherCollider; + + // The point of contact. + CSRAW public Vector2 point { get { return m_Point; } } + + // Normal of the contact point. + CSRAW public Vector2 normal { get { return m_Normal; } } + + // The first collider in contact. + CSRAW public Collider2D collider { get { return m_Collider; } } + + // The other collider in contact. + CSRAW public Collider2D otherCollider { get { return m_OtherCollider; } } +END + +CONDITIONAL ENABLE_2D_PHYSICS +CSRAW [StructLayout (LayoutKind.Sequential)] +NONSEALED_CLASS Collision2D + CSRAW internal Rigidbody2D m_Rigidbody; + CSRAW internal Collider2D m_Collider; + CSRAW internal ContactPoint2D[] m_Contacts; + CSRAW internal Vector2 m_RelativeVelocity; + + CSRAW public Rigidbody2D rigidbody { get { return m_Rigidbody; } } + CSRAW public Collider2D collider { get { return m_Collider; } } + CSRAW public Transform transform { get { return rigidbody != null ? rigidbody.transform : collider.transform; } } + CSRAW public GameObject gameObject { get { return m_Rigidbody != null ? m_Rigidbody.gameObject : m_Collider.gameObject; } } + CSRAW public ContactPoint2D[] contacts { get { return m_Contacts; } } + CSRAW public Vector2 relativeVelocity { get { return m_RelativeVelocity; } } + +END + + +// JointAngleLimits2D is used by the [[HingeJoint2D]] to limit the joints angle. +CONDITIONAL ENABLE_2D_PHYSICS +STRUCT JointAngleLimits2D + CSRAW private float m_LowerAngle; + CSRAW private float m_UpperAngle; + + // The lower angle limit of the joint. + CSRAW public float min { get { return m_LowerAngle; } set { m_LowerAngle = value; } } + + // The upper angle limit of the joint. + CSRAW public float max { get { return m_UpperAngle; } set { m_UpperAngle = value; } } +END + + +// JointTranslationLimits2D is used by the [[SliderJoint2D]] to limit the joints translation. +CONDITIONAL ENABLE_2D_PHYSICS +STRUCT JointTranslationLimits2D + CSRAW private float m_LowerTranslation; + CSRAW private float m_UpperTranslation; + + // The lower translation limit of the joint. + CSRAW public float min { get { return m_LowerTranslation; } set { m_LowerTranslation = value; } } + + // The upper translation limit of the joint. + CSRAW public float max { get { return m_UpperTranslation; } set { m_UpperTranslation = value; } } +END + + +// JointMotor2D is used by the [[HingeJoint2D]] and [[SliderJoint2D]] to motorize a joint. +CONDITIONAL ENABLE_2D_PHYSICS +STRUCT JointMotor2D + CSRAW private float m_MotorSpeed; + CSRAW private float m_MaximumMotorTorque; + + // The target motor speed in degrees/second. + CSRAW public float motorSpeed { get { return m_MotorSpeed; } set { m_MotorSpeed = value; } } + + // The maximum torque in N-m the motor can use to achieve the desired motor speed. + CSRAW public float maxMotorTorque { get { return m_MaximumMotorTorque; } set { m_MaximumMotorTorque = value; } } +END + + +// Joint2D is the base class for all 2D joints. +CONDITIONAL ENABLE_2D_PHYSICS +NONSEALED_CLASS Joint2D : Behaviour + + // A reference to another rigid-body this joint connects to. + AUTO_PTR_PROP Rigidbody2D connectedBody GetConnectedBody SetConnectedBody + + // Whether the connected rigid-bodies should collide with each other or not. + AUTO_PROP bool collideConnected GetCollideConnected SetCollideConnected + +END + +// The SpringJoint2D ensures that the two connected rigid-bodies stay at a specific distance apart using a spring system. +CONDITIONAL ENABLE_2D_PHYSICS +CLASS SpringJoint2D : Joint2D + + // The Position of the anchor around which the joints motion is constrained. + AUTO_PROP Vector2 anchor GetAnchor SetAnchor + + // The Position of the anchor around which the joints motion is constrained. + AUTO_PROP Vector2 connectedAnchor GetConnectedAnchor SetConnectedAnchor + + // The distance the joint should maintain between the two connected rigid-bodies. + AUTO_PROP float distance GetDistance SetDistance + + // The damping ratio for the oscillation whilst trying to achieve the specified distance. 0 means no damping. 1 means critical damping. range { 0.0, 1.0 } + AUTO_PROP float dampingRatio GetDampingRatio SetDampingRatio + + // The frequency in Hertz for the oscillation whilst trying to achieve the specified distance. range { 0.0, infinity } + AUTO_PROP float frequency GetFrequency SetFrequency + +END + + +// The DistanceJoint2D ensures that the two connected rigid-bodies stay at a maximum specific distance apart. +CONDITIONAL ENABLE_2D_PHYSICS +CLASS DistanceJoint2D : Joint2D + + // The Position of the anchor around which the joints motion is constrained. + AUTO_PROP Vector2 anchor GetAnchor SetAnchor + + // The Position of the anchor around which the joints motion is constrained. + AUTO_PROP Vector2 connectedAnchor GetConnectedAnchor SetConnectedAnchor + + // The maximum distance the joint should maintain between the two connected rigid-bodies. + AUTO_PROP float distance GetDistance SetDistance + +END + + +// The HingeJoint2D constrains the two connected rigid-bodies around the anchor points not restricting the relative rotation of them. Can be used for wheels, rollers, chains, rag-dol joints, levers etc. +CONDITIONAL ENABLE_2D_PHYSICS +CLASS HingeJoint2D : Joint2D + + // Setting the motor or limit automatically enabled them. + + // The Position of the anchor around which the joints motion is constrained. + AUTO_PROP Vector2 anchor GetAnchor SetAnchor + + // The Position of the anchor around which the joints motion is constrained. + AUTO_PROP Vector2 connectedAnchor GetConnectedAnchor SetConnectedAnchor + + // Enables the joint's motor. + AUTO_PROP bool useMotor GetUseMotor SetUseMotor + + // Enables the joint's limits. + AUTO_PROP bool useLimits GetUseLimits SetUseLimits + + // The motor will apply a force up to a maximum torque to achieve the target velocity in degrees per second. + AUTO_PROP JointMotor2D motor GetMotor SetMotor + + // The limits of the hinge joint. + AUTO_PROP JointAngleLimits2D limits GetLimits SetLimits + +END + +// The SliderJoint2D constrains the two connected rigid-bodies to have on degree of freedom: translation along a fixed axis. Relative motion is prevented. +CONDITIONAL ENABLE_2D_PHYSICS +CLASS SliderJoint2D : Joint2D + + // Setting the motor or limit automatically enabled them. + + // The Position of the anchor around which the joints motion is constrained. + AUTO_PROP Vector2 anchor GetAnchor SetAnchor + + // The Position of the anchor around which the joints motion is constrained. + AUTO_PROP Vector2 connectedAnchor GetConnectedAnchor SetConnectedAnchor + + // The translation angle that the joint slides along. + AUTO_PROP float angle GetAngle SetAngle + + // Enables the joint's motor. + AUTO_PROP bool useMotor GetUseMotor SetUseMotor + + // Enables the joint's limits. + AUTO_PROP bool useLimits GetUseLimits SetUseLimits + + // The motor will apply a force up to a maximum torque to achieve the target velocity in degrees per second. + AUTO_PROP JointMotor2D motor GetMotor SetMotor + + // The limits of the slider joint. + AUTO_PROP JointTranslationLimits2D limits GetLimits SetLimits + +END + +CONDITIONAL ENABLE_2D_PHYSICS +CLASS PhysicsMaterial2D : Object + + CUSTOM private static void Internal_Create ([Writable]PhysicsMaterial2D mat, string name) + { + PhysicsMaterial2D* material = NEW_OBJECT (PhysicsMaterial2D); + SmartResetObject(*material); + material->SetNameCpp (name); + Scripting::ConnectScriptingWrapperToObject (mat.GetScriptingObject(), material); + } + + // Creates a new material. + CSRAW public PhysicsMaterial2D () { Internal_Create (this,null); } + + // Creates a new material named /name/. + CSRAW public PhysicsMaterial2D (string name) { Internal_Create (this,name); } + + // How bouncy is the surface? A value of 0 will not bounce. A value of 1 will bounce without any loss of energy. + AUTO_PROP float bounciness GetBounciness SetBounciness + + // The friction. + AUTO_PROP float friction GetFriction SetFriction + +END + +CSRAW } diff --git a/Runtime/Physics2D/SliderJoint2D.cpp b/Runtime/Physics2D/SliderJoint2D.cpp new file mode 100644 index 0000000..d802c17 --- /dev/null +++ b/Runtime/Physics2D/SliderJoint2D.cpp @@ -0,0 +1,231 @@ +#include "UnityPrefix.h" + +#if ENABLE_2D_PHYSICS || DOXYGEN + +#include "Runtime/Physics2D/SliderJoint2D.h" +#include "Runtime/Physics2D/RigidBody2D.h" +#include "Runtime/Physics2D/Physics2DManager.h" + +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Math/Simd/math.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "External/Box2D/Box2D/Box2D.h" + + +IMPLEMENT_CLASS (SliderJoint2D) +IMPLEMENT_OBJECT_SERIALIZE (SliderJoint2D) + + +// -------------------------------------------------------------------------- + + +SliderJoint2D::SliderJoint2D (MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +, m_OldReferenceAngle (std::numeric_limits<float>::infinity ()) +{ +} + + +SliderJoint2D::~SliderJoint2D () +{ +} + + +template<class TransferFunction> +void SliderJoint2D::Transfer (TransferFunction& transfer) +{ + Super::Transfer(transfer); + + TRANSFER (m_Anchor); + TRANSFER (m_ConnectedAnchor); + TRANSFER (m_Angle); + TRANSFER (m_UseMotor); + transfer.Align (); + TRANSFER (m_Motor); + TRANSFER (m_UseLimits); + transfer.Align (); + TRANSFER (m_TranslationLimits); +} + + +void SliderJoint2D::CheckConsistency () +{ + Super::CheckConsistency (); + + m_Motor.CheckConsistency (); + m_TranslationLimits.CheckConsistency (); + + m_Angle = clamp<float> (m_Angle, 0.0f, 359.9999f); + + if (!IsFinite(m_Anchor)) + m_Anchor = Vector2f::zero; + + if (!IsFinite(m_ConnectedAnchor)) + m_ConnectedAnchor = Vector2f::zero; +} + + +void SliderJoint2D::Reset () +{ + Super::Reset (); + + m_Angle = 0.0f; + m_UseMotor = false; + m_UseLimits = false; + m_Motor.Initialize (); + m_TranslationLimits.Initialize (); + + m_Anchor = Vector2f::zero; + m_ConnectedAnchor = Vector2f::zero; +} + + +void SliderJoint2D::SetAnchor (const Vector2f& anchor) +{ + ABORT_INVALID_VECTOR2 (anchor, anchor, SliderJoint2D); + + m_Anchor = anchor; + SetDirty(); + + // Recreate the joint. + if (m_Joint != NULL) + ReCreate(); +} + + +void SliderJoint2D::SetConnectedAnchor (const Vector2f& anchor) +{ + ABORT_INVALID_VECTOR2 (anchor, connectedAnchor, SliderJoint2D); + + m_ConnectedAnchor = anchor; + SetDirty(); + + // Recreate the joint. + if (m_Joint != NULL) + ReCreate(); +} + + +void SliderJoint2D::SetAngle (float angle) +{ + ABORT_INVALID_FLOAT (angle, angle, DistanceJoint2D); + + m_Angle = clamp<float> (angle, 0.0f, 359.9999f); + SetDirty(); + + // Recreate the joint. + if (m_Joint != NULL) + ReCreate(); +} + + +void SliderJoint2D::SetUseMotor (bool enable) +{ + m_UseMotor = enable; + SetDirty(); + + if (m_Joint != NULL) + ((b2PrismaticJoint*)m_Joint)->EnableMotor(m_UseMotor); +} + + +void SliderJoint2D::SetUseLimits (bool enable) +{ + m_UseLimits = enable; + SetDirty(); + + if (m_Joint != NULL) + ((b2PrismaticJoint*)m_Joint)->EnableLimit(m_UseLimits); +} + + +void SliderJoint2D::SetMotor (const JointMotor2D& motor) +{ + m_Motor = motor; + m_Motor.CheckConsistency (); + SetDirty(); + + // Motor is automatically enabled if motor is set. + SetUseMotor(true); + + if (m_Joint != NULL) + { + b2PrismaticJoint* joint = (b2PrismaticJoint*)m_Joint; + joint->SetMotorSpeed (math::radians (m_Motor.m_MotorSpeed)); + joint->SetMaxMotorForce (m_Motor.m_MaximumMotorForce); + } +} + + +void SliderJoint2D::SetLimits (const JointTranslationLimits2D& limits) +{ + m_TranslationLimits = limits; + m_TranslationLimits.CheckConsistency (); + SetDirty(); + + // Limits ares automatically enabled if limits are set. + SetUseLimits(true); + + if (m_Joint != NULL) + { + b2PrismaticJoint* joint = (b2PrismaticJoint*)m_Joint; + joint->SetLimits (m_TranslationLimits.m_LowerTranslation, m_TranslationLimits.m_UpperTranslation); + } +} + + +// -------------------------------------------------------------------------- + + +void SliderJoint2D::Create () +{ + Assert (m_Joint == NULL); + + if (!IsActive ()) + return; + + // Fetch transform scales. + const Vector3f scale = GetComponent (Transform).GetWorldScaleLossy (); + const Vector3f connectedScale = m_ConnectedRigidBody.IsNull () ? Vector3f::one : m_ConnectedRigidBody->GetComponent (Transform).GetWorldScaleLossy (); + + // Fetch bodies. + b2Body* bodyA = FetchBodyA(); + b2Body* bodyB = FetchBodyB(); + + // Configure the joint definition. + b2PrismaticJointDef jointDef; + jointDef.localAnchorA.Set (m_Anchor.x * scale.x, m_Anchor.y * scale.y); + jointDef.localAnchorB.Set (m_ConnectedAnchor.x * connectedScale.x, m_ConnectedAnchor.y * connectedScale.y); + jointDef.enableMotor = m_UseMotor; + jointDef.enableLimit = m_UseLimits; + jointDef.motorSpeed = math::radians (m_Motor.m_MotorSpeed); + jointDef.maxMotorForce = m_Motor.m_MaximumMotorForce; + jointDef.lowerTranslation = m_TranslationLimits.m_LowerTranslation; + jointDef.upperTranslation = m_TranslationLimits.m_UpperTranslation; + jointDef.referenceAngle = m_OldReferenceAngle == std::numeric_limits<float>::infinity () ? bodyB->GetAngle() - bodyA->GetAngle() : m_OldReferenceAngle; + + float angle = math::radians (m_Angle); + jointDef.localAxisA = bodyA->GetLocalVector (b2Vec2 (math::sin (angle), -math::cos(angle))); + + // Create the joint. + FinalizeCreateJoint (&jointDef); +} + + +void SliderJoint2D::ReCreate() +{ + // Do we have an existing joint and we're still active/enabled? + if (m_Joint != NULL && IsActive () && GetEnabled ()) + { + // Yes, so keep reference angle. + m_OldReferenceAngle = ((b2RevoluteJoint*)m_Joint)->GetReferenceAngle (); + } + else + { + // No, so reset reference angle. + m_OldReferenceAngle = std::numeric_limits<float>::infinity (); + } + + Super::ReCreate (); +} +#endif //ENABLE_2D_PHYSICS diff --git a/Runtime/Physics2D/SliderJoint2D.h b/Runtime/Physics2D/SliderJoint2D.h new file mode 100644 index 0000000..6088f44 --- /dev/null +++ b/Runtime/Physics2D/SliderJoint2D.h @@ -0,0 +1,65 @@ +#pragma once + +#if ENABLE_2D_PHYSICS || DOXYGEN + +#include "Runtime/Math/Vector2.h" +#include "Runtime/Physics2D/Joint2D.h" + +class Vector2f; + + +// -------------------------------------------------------------------------- + + +class SliderJoint2D : public Joint2D +{ +public: + + REGISTER_DERIVED_CLASS (SliderJoint2D, Joint2D) + DECLARE_OBJECT_SERIALIZE (SliderJoint2D) + + SliderJoint2D (MemLabelId label, ObjectCreationMode mode); + // virtual ~SliderJoint2D (); declared-by-macro + + virtual void CheckConsistency(); + virtual void Reset (); + + Vector2f GetAnchor () const { return m_Anchor; } + virtual void SetAnchor (const Vector2f& anchor); + + Vector2f GetConnectedAnchor () const { return m_ConnectedAnchor; } + virtual void SetConnectedAnchor (const Vector2f& anchor); + + float GetAngle () const { return m_Angle; } + void SetAngle (float angle); + + bool GetUseMotor () const { return m_UseMotor; } + void SetUseMotor (bool enable); + + bool GetUseLimits () const { return m_UseLimits; } + void SetUseLimits (bool enable); + + JointMotor2D GetMotor () const { return m_Motor; } + void SetMotor (const JointMotor2D& motor); + + JointTranslationLimits2D GetLimits () const { return m_TranslationLimits; } + void SetLimits (const JointTranslationLimits2D& limits); + +protected: + virtual void Create (); + virtual void ReCreate(); + +protected: + Vector2f m_Anchor; ///< The local-space anchor from the base rigid-body. + Vector2f m_ConnectedAnchor; ///< The local-space anchor from the connected rigid-body. + float m_Angle; ///< The translation angle that the joint slides along. range { 0.0, 359.9999 } + JointMotor2D m_Motor; ///< The joint motor. + JointTranslationLimits2D m_TranslationLimits; ///< The joint angle limits. + bool m_UseMotor; ///< Whether to use the joint motor or not. + bool m_UseLimits; ///< Whether to use the angle limits or not. + +private: + float m_OldReferenceAngle; +}; + +#endif diff --git a/Runtime/Physics2D/SpringJoint2D.cpp b/Runtime/Physics2D/SpringJoint2D.cpp new file mode 100644 index 0000000..cfbc6a0 --- /dev/null +++ b/Runtime/Physics2D/SpringJoint2D.cpp @@ -0,0 +1,161 @@ +#include "UnityPrefix.h" + +#if ENABLE_2D_PHYSICS || DOXYGEN + +#include "Runtime/Physics2D/SpringJoint2D.h" +#include "Runtime/Physics2D/RigidBody2D.h" + +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Utilities/ValidateArgs.h" + +#include "External/Box2D/Box2D/Box2D.h" + +IMPLEMENT_CLASS (SpringJoint2D) +IMPLEMENT_OBJECT_SERIALIZE (SpringJoint2D) + + +// -------------------------------------------------------------------------- + + +SpringJoint2D::SpringJoint2D (MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +{ +} + + +SpringJoint2D::~SpringJoint2D () +{ +} + + +template<class TransferFunction> +void SpringJoint2D::Transfer (TransferFunction& transfer) +{ + Super::Transfer(transfer); + + TRANSFER (m_Anchor); + TRANSFER (m_ConnectedAnchor); + TRANSFER (m_Distance); + TRANSFER (m_DampingRatio); + TRANSFER (m_Frequency); +} + + +void SpringJoint2D::CheckConsistency () +{ + Super::CheckConsistency (); + + m_Distance = clamp<float> (m_Distance, b2_linearSlop, PHYSICS_2D_LARGE_RANGE_CLAMP); + m_Frequency = clamp<float> (m_Frequency, 0.0f, PHYSICS_2D_LARGE_RANGE_CLAMP); + m_DampingRatio = clamp(m_DampingRatio, 0.0f, 1.0f); + + if (!IsFinite(m_Anchor)) + m_Anchor = Vector2f::zero; + + if (!IsFinite(m_ConnectedAnchor)) + m_ConnectedAnchor = Vector2f::zero; +} + + +void SpringJoint2D::Reset () +{ + Super::Reset (); + + m_Distance = 1.0f; + m_DampingRatio = 0.0f; + m_Frequency = 10.0f; + m_Anchor = Vector2f::zero; + m_ConnectedAnchor = Vector2f::zero; +} + + +void SpringJoint2D::SetAnchor (const Vector2f& anchor) +{ + ABORT_INVALID_VECTOR2 (anchor, anchor, SpringJoint2D); + + m_Anchor = anchor; + SetDirty(); + + // Recreate the joint. + if (m_Joint != NULL) + ReCreate(); +} + + +void SpringJoint2D::SetConnectedAnchor (const Vector2f& anchor) +{ + ABORT_INVALID_VECTOR2 (anchor, connectedAnchor, SpringJoint2D); + + m_ConnectedAnchor = anchor; + SetDirty(); + + // Recreate the joint. + if (m_Joint != NULL) + ReCreate(); +} + + +void SpringJoint2D::SetDistance (float distance) +{ + ABORT_INVALID_FLOAT (distance, distance, SpringJoint2D); + + m_Distance = clamp<float> (distance, b2_linearSlop, PHYSICS_2D_LARGE_RANGE_CLAMP); + SetDirty(); + + if (m_Joint != NULL) + ((b2DistanceJoint*)m_Joint)->SetLength (m_Distance); +} + + +void SpringJoint2D::SetDampingRatio (float ratio) +{ + ABORT_INVALID_FLOAT (ratio, dampingRatio, SpringJoint2D); + + m_DampingRatio = clamp(ratio, 0.0f, 1.0f); + SetDirty(); + + if (m_Joint != NULL) + ((b2DistanceJoint*)m_Joint)->SetDampingRatio (m_DampingRatio); +} + + +void SpringJoint2D::SetFrequency (float frequency) +{ + ABORT_INVALID_FLOAT (frequency, frequency, SpringJoint2D); + + m_Frequency = clamp<float> (frequency, 0.0f, PHYSICS_2D_LARGE_RANGE_CLAMP); + SetDirty(); + + if (m_Joint != NULL) + ((b2DistanceJoint*)m_Joint)->SetFrequency (m_Frequency); +} + +// -------------------------------------------------------------------------- + + +void SpringJoint2D::Create () +{ + Assert (m_Joint == NULL); + + if (!IsActive ()) + return; + + // Fetch transform scales. + const Vector3f scale = GetComponent (Transform).GetWorldScaleLossy (); + const Vector3f connectedScale = m_ConnectedRigidBody.IsNull () ? Vector3f::one : m_ConnectedRigidBody->GetComponent (Transform).GetWorldScaleLossy (); + + // Configure the joint definition. + b2DistanceJointDef jointDef; + jointDef.dampingRatio = m_DampingRatio; + jointDef.frequencyHz = m_Frequency; + jointDef.length = m_Distance; + jointDef.localAnchorA.Set (m_Anchor.x * scale.x, m_Anchor.y * scale.y); + jointDef.localAnchorB.Set (m_ConnectedAnchor.x * connectedScale.x, m_ConnectedAnchor.y * connectedScale.y); + + // Create the joint. + FinalizeCreateJoint (&jointDef); +} + + +#endif //ENABLE_2D_PHYSICS diff --git a/Runtime/Physics2D/SpringJoint2D.h b/Runtime/Physics2D/SpringJoint2D.h new file mode 100644 index 0000000..6d46cd4 --- /dev/null +++ b/Runtime/Physics2D/SpringJoint2D.h @@ -0,0 +1,54 @@ +#pragma once + +#if ENABLE_2D_PHYSICS || DOXYGEN + +#include "Runtime/Math/Vector2.h" +#include "Runtime/Physics2D/Joint2D.h" + +class Vector2f; + + +// -------------------------------------------------------------------------- + + +class SpringJoint2D : public Joint2D +{ +public: + + REGISTER_DERIVED_CLASS (SpringJoint2D, Joint2D) + DECLARE_OBJECT_SERIALIZE (SpringJoint2D) + + SpringJoint2D (MemLabelId label, ObjectCreationMode mode); + // virtual ~SpringJoint2D (); declared-by-macro + + virtual void CheckConsistency(); + virtual void Reset (); + + Vector2f GetAnchor () const { return m_Anchor; } + virtual void SetAnchor (const Vector2f& anchor); + + Vector2f GetConnectedAnchor () const { return m_ConnectedAnchor; } + virtual void SetConnectedAnchor (const Vector2f& anchor); + + void SetDistance (float distance); + float GetDistance () const { return m_Distance; } + + void SetDampingRatio (float ratio); + float GetDampingRatio () const { return m_DampingRatio; } + + void SetFrequency (float frequency); + float GetFrequency () const { return m_Frequency; } + +protected: + virtual void Create (); + void AutoCalculateDistance (); + +protected: + float m_Distance; ///< The distance which the joint should attempt to maintain between attached bodies. range { 0.005, 1000000 } + float m_DampingRatio; ///< The damping ratio for the oscillation whilst trying to achieve the specified distance. 0 means no damping. 1 means critical damping. range { 0.0, 1.0 } + float m_Frequency; ///< The frequency in Hertz for the oscillation whilst trying to achieve the specified distance. range { 0.0, 1000000 } + Vector2f m_Anchor; ///< The local-space anchor from the base rigid-body. + Vector2f m_ConnectedAnchor; ///< The local-space anchor from the connected rigid-body. +}; + +#endif diff --git a/Runtime/Physics2D/SpriteCollider2D.cpp b/Runtime/Physics2D/SpriteCollider2D.cpp new file mode 100644 index 0000000..772bf0d --- /dev/null +++ b/Runtime/Physics2D/SpriteCollider2D.cpp @@ -0,0 +1,91 @@ +#include "UnityPrefix.h" + +#if ENABLE_2D_PHYSICS && ENABLE_SPRITECOLLIDER + +#include "Runtime/Physics2D/SpriteCollider2D.h" +#include "Runtime/Physics2D/RigidBody2D.h" +#include "Runtime/Physics2D/Physics2DManager.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Profiler/Profiler.h" +#include "Runtime/Filters/Mesh/SpriteRenderer.h" + +#include "External/Box2D/Box2D/Box2D.h" +#include "External/libtess2/libtess2/tesselator.h" + +PROFILER_INFORMATION(gPhysics2DProfileSpriteColliderCreate, "Physics2D.SpriteColliderCreate", kProfilerPhysics) +PROFILER_INFORMATION(gPhysics2DProfileSpriteColliderDecomposition, "Physics2D.SpriteColliderDecomposition", kProfilerPhysics) + +IMPLEMENT_CLASS_INIT_ONLY (SpriteCollider2D) +IMPLEMENT_OBJECT_SERIALIZE (SpriteCollider2D) + + +// -------------------------------------------------------------------------- + + +static Polygon2D gEmptyPolygon2D; +void SpriteCollider2D::InitializeClass() +{ + gEmptyPolygon2D.Clear(); +} + + +SpriteCollider2D::SpriteCollider2D (MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +{ +} + + +SpriteCollider2D::~SpriteCollider2D () +{ +} + + +template<class TransferFunction> +void SpriteCollider2D::Transfer (TransferFunction& transfer) +{ + Super::Transfer (transfer); + + TRANSFER (m_Sprite); +} + + +void SpriteCollider2D::Reset () +{ + Super::Reset (); + + m_Sprite = NULL; +} + + +void SpriteCollider2D::SmartReset () +{ + Super::SmartReset (); +#if UNITY_EDITOR + GameObject* go = GetGameObjectPtr(); + if (go) + { + SpriteRenderer* sr = go->QueryComponent(SpriteRenderer); + if (sr) + m_Sprite = sr->GetSprite(); + } +#endif +} + +void SpriteCollider2D::SetSprite(PPtr<Sprite> sprite) +{ + if (m_Sprite != sprite) + { + m_Sprite = sprite; + + Create(); + SetDirty(); + } +} + +const Polygon2D& SpriteCollider2D::GetPoly() const +{ + return m_Sprite.IsNull() ? gEmptyPolygon2D : m_Sprite->GetPoly(); +} + +#endif // #if ENABLE_2D_PHYSICS diff --git a/Runtime/Physics2D/SpriteCollider2D.h b/Runtime/Physics2D/SpriteCollider2D.h new file mode 100644 index 0000000..948b296 --- /dev/null +++ b/Runtime/Physics2D/SpriteCollider2D.h @@ -0,0 +1,37 @@ +#pragma once + +#if (ENABLE_2D_PHYSICS || DOXYGEN) && ENABLE_SPRITECOLLIDER + +#include "Runtime/Math/Vector2.h" +#include "Runtime/Utilities/dynamic_array.h" +#include "Runtime/Graphics/SpriteFrame.h" +#include "Runtime/Physics2D/PolygonColliderBase2D.h" + +class Sprite; + + +// -------------------------------------------------------------------------- + + +class SpriteCollider2D : public PolygonColliderBase2D +{ +public: + REGISTER_DERIVED_CLASS (SpriteCollider2D, PolygonColliderBase2D) + DECLARE_OBJECT_SERIALIZE (SpriteCollider2D) + + SpriteCollider2D (MemLabelId label, ObjectCreationMode mode); + static void InitializeClass(); + + virtual const Polygon2D& GetPoly() const; + + virtual void Reset (); + virtual void SmartReset (); + + PPtr<Sprite> GetSprite() const { return m_Sprite; } + void SetSprite(PPtr<Sprite> sprite); + +private: + PPtr<Sprite> m_Sprite; +}; + +#endif |