andersch.dev

<2023-04-09 Sun>

2D Physics

2D physics refers to the simulation of physical behavior and interactions in two-dimensional spaces. These behaviours can include movement, collision, and gravity. This is achieved using a physics engine.

List of 2D physics engines

Concepts

  • Collision Detection: Identifying when two objects have collided.
  • Collision Resolution: Separating collided objects and preventing interpenetration.
  • Broadphase and Narrowphase: Techniques to optimize collision detection.
  • Impulses: Sudden changes in velocity applied during collision resolution.
  • Kinematic, Static, and Rigid Bodies: Types of physical objects with different properties and behaviors.
    • Kinematic Bodies: Objects with a predetermined motion or trajectory. The body's position, velocity, and acceleration are pre-defined by the programmer, rather than determined by the physics engine.
    • Static Bodies: Objects that do not move or change position.
    • Rigid Bodies: Objects with mass and velocity that can move and rotate in response to physical forces like gravity and collisions but cannot deform.
  • Forces: Influencing the motion of objects.
  • Impulse: Instantaneous change in velocity
  • Friction: Resistance to movement between two objects.
  • Gravity: Attractive force between objects with mass.
  • Restitution: The elasticity or bounciness of an object

Further concepts:

  • Collision pairs
  • Contacts, Manifolds
  • Convex/Concave polygons, shapes
  • Collision groups and categories
  • Impulses
  • Impact solver
  • Contact
  • Joints

Impulse Resolution

Impulse resolution is a type of collision resolution strategy. It use impulses to separate colliding objects based on the mass, position, and velocity of those objects.

We want large objects colliding with smaller ones to move a little bit during collision, and to send the small objects flying away. We also want objects with infinite mass to not move at all.

The following factors are provided from the collision detection in the form of manifolds:

  • Collision normal: Direction in which the impulse will be applied
  • Penetration depth: Determines how large the impulse will be

This means the only value that we need to find out is the magnitude of our impulse.

void collision_resolve(object_t A, object_t B )
{
  vec2 rel_velocity = B.velocity - A.velocity;
  float velAlongNormal = DotProduct(rv, normal);  // calculate rel. velocity in terms of normal direction
  if(velAlongNormal > 0) { return; }              // don't resolve if velocities are separating

  float e = min(A.restitution, B.restitution);

  // Calculate impulse scalar
  float j = -(1 + e) * velAlongNormal;
  j /= 1 / A.mass + 1 / B.mass;                   // NOTE: possible speed-up: store inverse mass

  // Apply impulse
  vec2 impulse = j * normal;
  A.velocity -= 1 / A.mass * impulse;
  B.velocity += 1 / B.mass * impulse;
}

Further things to consider: Positional correction using Linear projection to avoid sinking of objects.

Simple Manifold Generation

A manifold (in physics engines) is referring to a piece of data that contains information about a collision between two objects. These manifolds are created by the collision detection phase.

struct Manifold
{
  Object *A;
  Object *B;
  float  penetration;
  Vec2   normal;
};
bool Circle_vs_Circle(Manifold *m)
{
  // Setup a couple pointers to each object
  Object *A = m->A;
  Object *B = m->B;

  vec2 n  = B->pos - A->pos;
  float r = A->radius + B->radius;
  r *= r;

  if(n.LengthSquared( ) > r) { return false }

  /* Circles have collided, now compute manifold */

  float d = n.Length(); // perform actual sqrt

  if(d != 0) // If distance between circles is not zero
  {
    m->penetration = r - d; // Distance is difference between radius and distance

    // Utilize our d since we performed sqrt on it already within Length()
    // Points from A to B, and is a unit vector
    c->normal = t / d;

    return true
  }
  else /* Circles are on same position */
  {
    // Choose random (but consistent) values
    c->penetration = A->radius
    c->normal = Vec( 1, 0 )
    return true
  }
}
bool AABB_vs_AABB(Manifold *m)
{
  // Setup a couple pointers to each object
  Object *A = m->A
  Object *B = m->B

  // Vector from A to B
  Vec2 n = B->pos - A->pos

  AABB abox = A->aabb
  AABB bbox = B->aabb

  // Calculate half extents along x axis for each object
  float a_extent = (abox.max.x - abox.min.x) / 2
  float b_extent = (bbox.max.x - bbox.min.x) / 2

  // Calculate overlap on x axis
  float x_overlap = a_extent + b_extent - abs( n.x )

  // SAT test on x axis
  if(x_overlap > 0)
  {
    // Calculate half extents along x axis for each object
    float a_extent = (abox.max.y - abox.min.y) / 2
    float b_extent = (bbox.max.y - bbox.min.y) / 2

    // Calculate overlap on y axis
    float y_overlap = a_extent + b_extent - abs( n.y )

    // SAT test on y axis
    if(y_overlap > 0)
    {
      if(x_overlap > y_overlap) // Find out which axis is axis of least penetration
      {
        // Point towards B knowing that n points from A to B
        if(n.x < 0)
          m->normal = Vec2( -1, 0 )
        else
          m->normal = Vec2( 0, 0 )

        m->penetration = x_overlap

        return true
      }
      else
      {
        // Point toward B knowing that n points from A to B
        if(n.y < 0)
          m->normal = Vec2( 0, -1 )
        else
          m->normal = Vec2( 0, 1 )

        m->penetration = y_overlap

        return true
      }
    }
  }
}
bool AABB_vs_Circle(Manifold* m)
{
  // Setup a couple pointers to each object
  Object *A = m->A
  Object *B = m->B

  // Vector from A to B
  Vec2 n = B->pos - A->pos

  // Closest point on A to center of B
  Vec2 closest = n

  // Calculate half extents along each axis
  float x_extent = (A->aabb.max.x - A->aabb.min.x) / 2
  float y_extent = (A->aabb.max.y - A->aabb.min.y) / 2

  // Clamp point to edges of the AABB
  closest.x = Clamp( -x_extent, x_extent, closest.x )
  closest.y = Clamp( -y_extent, y_extent, closest.y )

  bool inside = false

  // Circle is inside the AABB, so we need to clamp the circle's center
  // to the closest edge
  if(n == closest)
  {
    inside = true

    // Find closest axis
    if(abs( n.x ) > abs( n.y ))
    {
      // Clamp to closest extent
      if(closest.x > 0)
        closest.x = x_extent
      else
        closest.x = -x_extent
    }
    else // y axis is shorter
    {
      // Clamp to closest extent
      if(closest.y > 0)
        closest.y = y_extent;
      else
        closest.y = -y_extent;
    }
  }

  Vec2 normal = n - closest;
  real d = normal.LengthSquared();
  real r = B->radius;

  // Early out of the radius is shorter than distance to closest point and
  // Circle not inside the AABB
  if(d > r * r && !inside) { return false; }

  d = sqrt(d); // Avoided sqrt until needed

  // Collision normal needs to be flipped to point outside if circle was
  // inside the AABB
  if(inside)
  {
    m->normal = -n
    m->penetration = r - d
  }
  else
  {
    m->normal = n
    m->penetration = r - d
  }

  return true
}

Resources