22

Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls

 4 years ago
source link: https://www.allenchou.net/2020/01/dot-product-projection-reflection/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Source files and future updates are available on Patreon .

You can follow me on Twitter .

This post is part of my Gamedev Tutorials Series .

Hereis the Chinese translation of this post.

本文之中文翻譯在此

Prerequisites

Overview

The dot product is a simple yet extremely useful mathematical tool. It encodes the relationship between two vectors’ magnitudes and directions into a single value. It is useful for computing projection, reflection, lighting, and so much more.

In this tutorial, you’ll learn:

  • The geometric meaning of the dot product.
  • How to project one vector onto another.
  • How to measure an object’s dimension along an arbitrary ruler axis.
  • How to reflect a vector relative to a plane.
  • How to bounce a ball off a slope.

The Dot Product

Let’s say we have two vectors,and. Since a vector consists of just a direction and a magnitude (length), it doesn’t matter where we place it in a figure. Let’s positionandso that they start at the same point:

faqii2u.png!web

The dot product is a mathematical operation that takes two vectors as input and returns a scalar value as output. It is the product of the signed magnitude of the first vector’s projection onto the second vector and the magnitude of the second vector . Think of projection as casting shadows using parallel light in the direction perpendicular to the vector being projected onto:

RB7bQnR.png!web

We write the dot product ofandas(read a dot b ).

If the angle between the two vectors is less than 90 degrees , the signed magnitude of the first vector is positive (thus simply the magnitude of the first vector). If the angle is larger than 90 degrees, the signed magnitude of the first vector is its negated magnitude.

Which one of the vectors is “the first vector” doesn’t matter. Reversing the vector order gives the same result:

 

Ifis a unit vector, the signed magnitude of the projection ofontois simply.

Cosine-Based Dot Product Formula

Notice that there’s a right triangle in the figure. Let the angle betweenandbe:

reaErmM.png!web

Recall fromthis tutorial that the length of the adjacent side of a right triangle is the length of its hypotenuse multiplied by the cosine of the angle, so the signed magnitude of the projection ofontois:

MfuE7vu.png!web

So the dot product of two vectors can be expressed as the product of each vector’s magnitude and the cosine of the angle between the two, which also reaffirms the property that the order of the vectors doesn’t matter:

  rUbmuia.png!web

If bothandare unit vectors, thensimply equals to.

If the two vectors are perpendicular (angle in between is), the dot product is zero. If the angle between the two vectors is smaller than, the dot product is positive. If the angle is larger than, the dot product is negative. Thus, we can use the sign of the dot product of two vectors to get a very rough sense of how aligned their directions are.

Sincemonotonically decreases all the way to, the more similar the directions of the two vectors are, the larger their dot product; the more opposite the directions of the two vectors are, the smaller their dot product. In the extreme cases where the two vectors point in the exact same direction () and the exact opposite directions (), their dot products are Zzyuueq.png!web and qIR7B32.png!web , respectively.

Component-Based Dot Product Formula

When we have two 3D vectors as triplets of floats, it isn’t immediately clear what the angle in between them are. Luckily, there’s an alternate way to compute the dot product of two vectors that doesn’t involve taking the cosine of the angle in between. Let’s denote the components ofandas follows:

  byUNBjj.png!web

Then the dot product of the two vectors is also equal to the sum of component-wise products, and can be written as:

  NVjiEzu.png!web

Simple, and no cosine needed!

Unity provides a function Vector3.Dot for computing the dot product of two vectors:

float dotProduct = Vector3.Dot(a, b);

Here is an implementation of the function:

Vector3 Dot(Vector3 a, Vector b)
{
  return a.x * b.x + a.y * b.y + a.z * b.z;
}

The formula for computing a vector’s magnitude is RBziU3I.png!web and can also be expressed using the dot product of the vector with itself:

  AVZNBfA.png!web

Recall the formula by6VZre.png!web . This means if we know the dot product and the magnitudes of two vectors, we can reverse-calculate the angle between them by using the arccosine function:

  2uY77rf.png!web

Ifandare unit vectors, we can further simplify the formulas above by skipping the computation of vector magnitudes:

  6nyqyaJ.png!web

Vector Projection

Now that we know the geometric meaning of the dot product as the product of a projected vector’s signed magnitude and another vector’s magnitude, let’s see how we can project one vector onto another. Let 6jqaUnu.png!web denote the projection ofonto:

Ar6JBrr.png!web

The unit vector in the direction ofis, so if we scale it by the signed magnitude of the projection ofonto, then we will get. In other words,is parallel to the direction ofand has a magnitude equal to that of the projection ofonto.

Since the dot productis the product of the magnitude ofand the signed magnitude of the projection ofonto, the signed magnitude ofis just the dot product ofanddivided by the magnitude of:

  qQFjY37.png!web

Multiplying this signed magnitude with the unit vectorgives us the formula for vector projection:

  FFJzMf6.png!web

Recall that Abuae2q.png!web , so we can also write the projection formula as:

  qu6fEbb.png!web

And if, the vector to projectonto, is a unit vector, the projection formula can be further simplified:

  ieYjArz.png!web

Unity provides a function Vector3.Project that computes the projection of one vector onto another:

Vector3 projection = Vector3.Project(vec, onto);

Here is an implementation of the function:

Vector3 Project(Vector3 vec, Vector3 onto)
{
  float numerator = Vector3.Dot(vec, onto);
  float denominator = Vector3.Dot(onto, onto);
  return (numerator / denominator) * onto;
}

Sometimes we need to guard against a potential degenerate case, where the vector being projected onto is a zero vector or a vector with an overly small magnitude, producing a numerical explosion as the projection involves division by zero or near-zero. This can happen with Unity’s Vector3.Project function.

One way to handle this is to compute the magnitude of the vector being projected onto. Then, if the magnitude is too small, use a fallback vector (e.g. the unit +X vector, the forward vector of a character, etc.):

Vector3 SafeProject(Vector3 vec, Vector3 onto, Vector3 fallback)
{
  float sqrMag = v.sqrMagnitude;
  
  if (sqrMag > Epsilon) // test against a small number
    return Vector3.Project(vec, onto);
  else
    return Vector3.Project(vec, fallback);
}

Exercise: Ruler

Here’s an exercise for vector projection: make a ruler that measures an object’s dimension along an arbitrary axis.

A ruler is represented by a base position (a point) and an axis (a unit vector):

struct Ruler
{
  Vector3 Base;
  Vector3 Axis;
}

Here’s how you project a point onto the ruler. First, find the relative vector from the ruler’s base position to the point. Next, project this relative vector onto the ruler’s axis. Finally, the point’s projection is the ruler’s base position offset by the projected relative vector:

U3UbErf.png!web
Vector3 Project(Vector3 vec, Ruler ruler)
{
  // compute relative vector
  Vector3 relative = vec - ruler.Base;
  
  // projection
  float relativeDot = Vector3.Dot(vec, ruler.Axis);
  Vector3 projectedRelative = relativeDot * ruler.Axis;

  // offset from base
  Vector3 result = ruler.Base+ projectedRelative;

  return result;
}

The intermediate relativeDot value above basically measures how far away the point’s projection is from the ruler’s base position, in the direction of the ruler’s axis if positive, or in the opposite direction of the ruler’s axis if negative.

If we compute such measurement for each vertex of an object’s mesh and find the minimum and maximum measurements, then we can obtain the object’s dimension measured along the ruler’s axis by subtracting the minimum from the maximum. Offsetting from the ruler’s base position by the ruler’s axis vector multiplied by these two extreme values gives us the two ends of the projection of the object onto the ruler.

void Measure
(
  Mesh mesh, 
  Ruler ruler, 
  out float dimension, 
  out Vector3 minPoint, 
  out Vector3 maxPoint
)
{
  float min = float.MaxValue;
  float max = float.MinValue;

  foreach (Vector3 vert in mesh.vertices)
  {
    Vector3 relative = vert- ruler.Base;
    float relativeDot = Vector3.Dot(relative , ruler.Axis);
    min = Mathf.Min(min, relativeDot);
    max = Mathf.Max(max, relativeDot);
  }
  
  dimension = max - min;
  minPoint = ruler.Base+ min * ruler.Axis;
  maxPoint = ruler.Base+ max * ruler.Axis;
}

Vector Reflection

Now we are going to take a look at how to reflect a vector, denoted, relative to a plane with its normal vector denoted:

ZJvyay3.png!web

We can decompose the vector to be reflected into a parallel component (denoted) and a perpendicular component (denoted) with respect to the plane:

YBbE3i7.png!web

  fEB7Nry.png!web

The perpendicular component is the projection of the vector onto the plane’s normal, and the parallel component can be obtained by subtracting the perpendicular component from the vector:

  VBBfUnn.png!web

Flipping the direction of the perpendicular component and adding it to the parallel component gives us the reflected vector off the plane.

emMFNjA.png!web

Let’s denote the reflection):

  aamEruZ.png!web

If we substitutewith, we get an alternative formula:

 

Unity provides a function Vector3.Reflect for computing vector reflection:

float reflection = Vector3.Reflect(vec, normal);

Here is an implementation of the function using the first reflection formula:

Vector3 Reflect(Vector vec, Vector normal)
{
  Vector3 perpendicular= Vector3.Project(vec, normal);
  Vector3 parallel = vec - perpendicular;
  return parallel - perpendicular;
}

And here is an implementation using the alternative formula:

Vector3 Reflect(Vector vec, Vector normal)
{
  return vec - 2.0f * Vector3.Project(vec, normal);
}

Exercise: Bouncing A Ball Off A Slope

Now that we know how to reflect a vector relative to a plane, we are well-equipped to simulate a ball bouncing off a slope.

We are going to use the Euler Method mentioned in aprevious tutorial to simulate the trajectory of a ball under the influence of gravity.

ballVelocity+= gravity * deltaTime;
ballCenter += ballVelocity* deltaTime;

In order to detect when the ball hits the slope, we need to know how to detect when a ball penetrates a plane.

A sphere can be defined by a center and a radius. A plane can be defined by a normal vector and a point on the plane. Let’s denote the sphere’s center, the sphere radius, the plane normal(a unit vector), and a point on the plane. Also, let the vector fromtobe denoted.

uqQZVfM.png!web

If the sphere does not penetrate the plane, the component ofperpendicular to the plane, denoted, should be in the same direction asand have a magnitude no less than.

VnUzAfF.png!web

In other words, the sphere does not penetrate the plane if; otherwise, the sphere is penetrating the plane by the amountand its position needs to be corrected.

In order to correct a penetrating sphere’s position, we can simply move the sphere in the direction of the plane’s normalby the penetration amount. This is an approximated solution and not physically correct, but it’s good enough for this exercise.

// returns original sphere center if not penetrating
// or corrected sphere center if penetrating
void SphereVsPlane
(
  Vector3 c,        // sphere center
  float r,          // sphere radius
  Vector3 n,        // plane normal (unit vector)
  Vector3 p,        // point on plane
  out Vector3 cNew, // sphere center output
)
{
  // original sphere position as default result
  cNew = c;

  Vector3 u = c - p;
  float d = Vector3.Dot(u, n);
  float penetration = r - d;

  // penetrating?
  if (penetration > 0.0f)
  {
    cNew = c + penetration * n;
  }
}

And then we insert the positional correction logic after the integration.

ballVelocity += gravity * deltaTime;
ballCenter += ballVelocity* deltaTime;

Vector3 newSpherePosition;
SphereVsPlane
(
  ballCenter, 
  ballRadius, 
  planeNormal, 
  pointOnPlane, 
  out newBallPosition
);

ballPosition = newBallPosition;

We also need to reflect the sphere’s velocity relative to the slope upon positional correction due to penetration, so it bounces off correctly.

The animation above shows a perfect reflection and doesn’t seem natural. We’d normally expect some sort of degradation in the bounced ball’s velocity, so it bounces less with each bounce.

This is typically modeled as a restitution value between the two colliding objects. With 100% restitution, the ball would bounce off the slope with perfect velocity reflection. With 50% restitution, the magnitude of the ball’s velocity component perpendicular to the slope would be cut in half. The restitution value is the ratio of magnitudes of the ball’s perpendicular velocity components after versus before the bounce. Here is a revised vector reflection function with restitution taken into account:

Vector3 Reflect
(
  Vector3 vec, 
  Vector3 normal, 
  float restitution
)
{
  Vector3 perpendicular= Vector3.Project(vec, normal);
  Vector3 parallel = vec - perpendicular;
  return parallel - restitution * perpendicular;
}

Here is the modified SphereVsPlane function that takes variable restitution into account:

// returns original sphere center if not penetrating
// or corrected sphere center if penetrating
void SphereVsPlane
(
  Vector3 c,        // sphere center
  float r,          // sphere radius
  Vector3 v,        // sphere velocity
  Vector3 n,        // plane normal (unit vector)
  Vector3 p,        // point on plane
  float e,          // restitution
  out Vector3 cNew, // sphere center output
  out Vector3 vNew  // sphere velocity output
)
{
  // original sphere position & velocity as default result
  cNew = c;
  vNew = v;

  Vector3 u = c - p;
  float d = Vector3.Dot(u, n);
  float penetration = r - d;

  // penetrating?
  if (penetration > 0.0f)
  {
    cNew = c + penetration * n;
    vNew = Reflect(v, n, e);
  }
}

And the positional correction logic is replaced with a complete bounce logic:

ballVelocity+= gravity * deltaTime;
spherePosition += ballVelocity* deltaTime;

Vector3 newSpherePosition;
Vector3 newSphereVelocity;
SphereVsPlane
(
  spherePosition , 
  ballRadius, 
  ballVelocity, 
  planeNormal, 
  pointOnPlane, 
  restitution, 
  out newBallPosition, 
  out newBallVelocity;
);

ballPosition= newBallPosition;
ballVelocity= newBallVelocity;

Finally, now we can have balls with different restitution values against a slope:

Summary

In this tutorial, we have been introduced to the geometric meaning of the dot product and its formulas (cosine-based and component-based).

We have also seen how to use the dot product to project vectors, and how to use vector projection to measure objects along an arbitrary ruler axis.

Finally, we have learned how to use the dot product to reflect vectors, and how to use vector reflection to simulate balls bouncing off a slope.

If you’ve enjoyed this tutorial and would like to see more, please consider supporting me on Patreon . By doing so, you can also get updates on future tutorials. Thanks!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK