Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls

栏目: IT技术 · 发布时间: 4年前

内容简介:Source files and future updates are available onYou can follow me onThis post is part of my

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:

Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls

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:

Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls

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:

Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls

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:

Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls

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:

  Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls

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 Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls and Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls , 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:

  Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls

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

  Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls

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 Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls and can also be expressed using the dot product of the vector with itself:

  Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls

Recall the formula Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls . 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:

  Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls

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

  Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls

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 Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls denote the projection ofonto:

Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls

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:

  Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls

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

  Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls

Recall that Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls , so we can also write the projection formula as:

  Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls

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

  Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls

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:

Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls
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:

Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls

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

Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls

  Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls

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:

  Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls

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

Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls

Let’s denote the reflection):

  Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls

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.

Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls

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.

Gamedev Tutorial: Dot Product, Rulers, and Bouncing Balls

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!


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

刘强东自述

刘强东自述

刘强东 / 中信出版集团 / 2016-6-1 / 49.00

京东 1998年,京东还只是中关村一个经营光磁生意的小柜台,月营业额仅有几万元,如今则已经成长为中国营收规模超大的互联网企业,2015年全年营收1813亿,总交易额达到4627亿元; 为解决电商“最后一公里”的痛点,创立并自建B2C物流模式; 经常被争议,却始终坚持“不挣快钱”,选择上市不是因为“缺钱”,只为让合作伙伴睡得着觉,为用户和社会创造价值,由此成就让整个华尔街一片京东红......一起来看看 《刘强东自述》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试