0
\$\begingroup\$

I have a ball, which should bounce when it collides with a surface. I know the formula for reflections;

$$ v_1 - 2n(v_1 \cdot n) $$


However, I cannot obtain the required information. I know:

  • the ball position,
  • the ball velocity,
  • the ball radius,
  • the polygon center
  • the polgygon width and height
  • the polgyon corner coordinates

Diagram


How does one calculate the surface normal, in 2D collisions?

\$\endgroup\$
3
  • 1
    \$\begingroup\$ Does the accepted answer here help? stackoverflow.com/questions/1966587/… \$\endgroup\$ Jan 19, 2017 at 22:27
  • \$\begingroup\$ Thanks buddy but I think the example posted is for 3D space which ironically is a lot easier I think. OR maybe I just dont understand that example :). See marked answer, and my answer below for a working solution. \$\endgroup\$ Jan 20, 2017 at 18:49
  • \$\begingroup\$ 2D space is the exact same except operating on 2D vectors (alternatively replace Z with 0). \$\endgroup\$ Jan 21, 2017 at 9:25

3 Answers 3

5
\$\begingroup\$

I think the following may help. You have the sphere center, box center and hopefully the details of the rectangle. Since the rectangle may be rotated we need the rectangle extents, and the orthogonal unit vectors, e.g.

enter image description here

Now we need the closest point on the rectangle to the point c.

vector d = c - r; 

// project d onto ux to get distance along ux from c
float dx = dot(d,ux)
if(dx > ex)    dx = ex;
if(dx < -ex)   dx = -ex;

// project d onto uy to get distance along uy from c
float dy = dot(d,uy)
if(dy > ey)    dy = ey;
if(dy < -ey)   dy = -ey;

// calculate closest point p on box to c
vector p = r + dx*ux + dy*uy;

vector collision_norm = norm(c - p);

Now this should hopefully give you the required collision normal.

\$\endgroup\$
5
  • 1
    \$\begingroup\$ This drawing is gorgeous. \$\endgroup\$
    – Quentin
    Jan 20, 2017 at 12:43
  • \$\begingroup\$ Hi there, thanks so much for your answer. Unfortunately, I have not come across the term 'extent' and I'm not sure how I would go about obtaining this value. Is it a vector from center out towards the edge? How do I know from which edge to project? Thanks. \$\endgroup\$ Jan 20, 2017 at 16:16
  • \$\begingroup\$ You have the polygon (rectangle) width and height, so the extents are ex = width/2 and ey = height/2. The terms come from a definition of a Oriented Bounding Box (OBB) where you can define a rotated box by its center (r), orthonormal unit vectors (ux,uy) and the distance along those vectors to the sides of the rectangles (ex,ey). \$\endgroup\$ Jan 20, 2017 at 16:32
  • \$\begingroup\$ Hi Biggy, Thanks. Im trying to implement this but it only appears to be working when the ball hits the center of the rectangle, so I think I have done it wrong. Edit to come... \$\endgroup\$ Jan 20, 2017 at 17:21
  • \$\begingroup\$ Ok, So I managed to get it working - with some modifications to ensure it works within the engine I'm using. See my posted answer for the source. Thanks very much for your help! :) \$\endgroup\$ Jan 20, 2017 at 18:25
0
\$\begingroup\$

Based on the answer posted by Biggy Smith - See engine specific CPP code below.

CVector Helper::GetNormal(CSprite* collisionSource, CSprite* collisiontarget)
{
    // Work out the displacement vector
    auto d = collisionSource->GetPos() - collisiontarget->GetPos();

    // Find the extants of the rectangle
    auto ex = collisiontarget->GetWidth() / 2.0f;
    auto ey = collisiontarget->GetHeight() / 2.0f;

    // Find the bottom right point of the rectangle
    CVector bottomRight = CVector(collisiontarget->GetRight(), collisiontarget->GetBottom());

    // Find the bottom left point of the rectangle
    CVector bottomLeft = collisiontarget->GetBottomLeft();

    // Find the top left point of the rectangle
    CVector topLeft = CVector(collisiontarget->GetLeft(), collisiontarget->GetTop());

    // Find orthonomal unit vectors (I think? These are normalised anyway...)
    // In this instance I just took the displacement of the two appropriate corner points.
    CVector ux = Normalise(bottomRight - bottomLeft);
    CVector uy = Normalise(topLeft - bottomLeft);

    // Project d onto ux to get distance along ux from c
    float dx = Dot(d, ux);

    if (dx > ex) 
    { 
        dx = ex;
    }
    else if (dx < -ex)
    { 
        dx = -ex;
    }

    // Project d onto uy to get distance along uy from c
    float dy = Dot(d, uy);

    if (dy > ey)
    {
        dy = ey;
    }
    else if (dy < -ey)
    {
        dy = -ey;
    }

    // calculate closest point p on box to c
    CVector contactPoint = collisiontarget->GetPosition() + dx*ux + dy*uy;

    // Calculate the normal at this point
    CVector surfaceNormal = Normalise(collisionSource->GetPosition() - contactPoint);

    return CVector(round(surfaceNormal.X()), round(surfaceNormal.Y()));
}
\$\endgroup\$
0
\$\begingroup\$

Created a Javascript solution based on Biggy Smith's answer.

Uses no external libraries. All input arguments are numbers.

// cX = circle center x
// cY = circle center y
// rX = rectangle top left x
// rY = rectangle top left y
// rW = rectangle width
// rH = rectangle height
// rA = rectangle rotation amount around center (in radians)

function getCollisionNormal(cX, cY, rX, rY, rW, rH, rA) {
  const eX = rW / 2;
  const eY = rH / 2;

  const rCX = rX + eX;
  const rCY = rY + eY;

  const uxX = Math.cos(rA);
  const uxY = Math.sin(rA);

  const uyX = Math.cos(rA - 1.571)
  const uyY = Math.sin(rA - 1.571);

  const distanceX = cX - rCX;
  const distanceY = cY - rCY;

  let dx = distanceX * uxX + distanceY * uxY;
  let dy = distanceX * uyX + distanceY * uyY;

  if (dx > eX)  { dx = eX; }
  if (dx < -eX) { dx = -eX; }
  if (dy > eY)  { dy = eY; }
  if (dy < -eY) { dy = -eY; }

  const pX = rCX + dx * uxX + dy * uyX;
  const pY = rCY + dx * uxY + dy * uyY;

  const deltaX = cX - pX;
  const deltaY = cY - pY;

  const magnitude = Math.sqrt(deltaX * deltaX + deltaY * deltaY);

  if (magnitude === 0) {
    return { x: 0, y: 0, xDir: 0, yDir: 0 };
  } else {
    return { x: pX, y: pY, xDir: deltaX / magnitude, yDir: deltaY / magnitude };
  }
}
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .