top of page
Search
  • cedarcantab

Understanding 2D Physics Engines with Phaser 3, Part 15: SAT - Line vs Circle vs Polygon vs Capsule

Updated: May 21, 2023

In previous posts I have dealt with checking for collisions of the following combinations:

  • polygon vs polygon

  • polygon vs line segment

  • polygon vs circle

  • capsule vs capsule



In the capsule vs capsule code, I had imitated the structure of dyn4j by passing the "focal points" of the "other object" to the getAxes method of the relevant object. I have now expanded this structure to all the shapes (ie circle, polygon, line segment in addition to the capsule). This makes is a lot easier for the sat method to deal with different combination of shapes as follows.


  sat(shape1, shape2, manifold) {

    if (shape1 instanceof Circle && shape2 instanceof Circle) {
      return this.intersectCircles(shape1, shape2, manifold)  
    }
    
    let n = new Phaser.Math.Vector2();
    let overlap = Number.MAX_SAFE_INTEGER;
    
    let foci1 = shape1.getFoci();
    let foci2 = shape2.getFoci();
    
    let axes1 = shape1.getAxes(foci2);
    let axes2 = shape2.getAxes(foci1);
    
    if (axes1 !== null)
    {
      for (let i = 0; i < axes1.length; i++)
      {
        const axis = axes1[i];    
        const shape2_MinMax = shape2.projectVertices(axis)
        const shape1_MinMax = shape1.projectVertices(axis)
        if ((shape2_MinMax.min >= shape1_MinMax.max || shape1_MinMax.min >= shape2_MinMax.max))
        { 
          return false
        } 
        let axisDepth = Math.min(shape1_MinMax.max-shape2_MinMax.min, shape2_MinMax.max-shape1_MinMax.min);
        if (axisDepth < overlap)
        {
          overlap = axisDepth;
          n = axis;
        }
      }
    }

    if (axes2 !== null)
    {
      for (let i = 0; i < axes2.length; i++)
      {
        const axis = axes2[i];
        const shape1_MinMax = shape1.projectVertices(axis);
        const shape2_MinMax = shape2.projectVertices(axis);
        if (shape2_MinMax.min >= shape1_MinMax.max || shape1_MinMax.min >= shape2_MinMax.max)
        {
          return false;
        }
        let axisDepth = Math.min(shape1_MinMax.max-shape2_MinMax.min, shape2_MinMax.max-shape1_MinMax.min);
        if (axisDepth < overlap)
        {
          overlap = axisDepth;
          n = axis;
        }
      }
    }
  
    manifold.normal.set(n.x, n.y);
		manifold.depth = overlap;
		// return true
    return true;

  }

What is the "focus" point of a polygon?


Of course, there isn't one, but I have added the following method to the polygon class to keep the code abstract.


 getFoci() {
    return null  
  }
  

And to deal with polygon vs capsules, the getAxes method for the polygon has been amended (borrows heavily from Dyn4j) as follows



 getAxes(foci) {
    
    let fociSize = foci !== null ? foci.length : 0;
    let axes = [];
    for (let i = 0; i < this.points.length; i++)
    {
      const va = this.points[i];
      const vb = this.points[(i+1) % this.points.length];
      // obtain projection axis by rotating the edge to its perpendicular and normalising it
      const axis = new Phaser.Math.Vector2(vb.x-va.x, vb.y-va.y).normalizeLeftHand().normalize();
      axes.push(axis);
    }
    for (let i = 0; i < fociSize; i++) {
      let cp = this.findClosestVertex(foci[i]);
      axes.push(new Phaser.Math.Vector2(cp.x - foci[i].x, cp.y - foci[i].y).normalize())      
    }

    return axes;
  }

CodePen


Here is a simple demo. If you move the polygon against other objects, the colliding objects will change color.


You can implement simple collision response using the minimum translation vector, as shown in the demo below.





With anything other than circles, clearly the response looks wrong because there is no rotation. That is for another post.


Key references




記事: Blog2_Post
bottom of page