|
@@ -6,672 +6,273 @@ Vector math
|
|
Introduction
|
|
Introduction
|
|
~~~~~~~~~~~~
|
|
~~~~~~~~~~~~
|
|
|
|
|
|
-This small tutorial aims to be a short and practical introduction to
|
|
|
|
-vector math, useful for 3D but also 2D games. Again, vector math is not
|
|
|
|
-only useful for 3D but *also* 2D games. It is an amazing tool once you
|
|
|
|
-get the grasp of it and makes programming of complex behaviors much
|
|
|
|
-simpler.
|
|
|
|
|
|
+This tutorial is a short and practical introduction to linear algebra as
|
|
|
|
+it applies to game development. Linear algebra is the study of vectors and
|
|
|
|
+their uses. Vectors have many applications in both 2D and 3D development
|
|
|
|
+and Godot uses them extensively. Developing a good understanding of vector
|
|
|
|
+math is essential to becoming a strong game developer.
|
|
|
|
|
|
-It often happens that young programmers rely too much on the *incorrect*
|
|
|
|
-math for solving a wide array of problems, for example using only
|
|
|
|
-trigonometry instead of vector of math for 2D games.
|
|
|
|
-
|
|
|
|
-This tutorial will focus on practical usage, with immediate application
|
|
|
|
-to the art of game programming.
|
|
|
|
|
|
+.. note:: This tutorial is **not** a formal textbook on linear algebra. We
|
|
|
|
+ will only be looking at how it is applied to game development.
|
|
|
|
+ For a broader look at the mathematics,
|
|
|
|
+ see https://www.khanacademy.org/math/linear-algebra
|
|
|
|
|
|
Coordinate systems (2D)
|
|
Coordinate systems (2D)
|
|
~~~~~~~~~~~~~~~~~~~~~~~
|
|
~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
-Typically, we define coordinates as an (x,y) pair, x representing the
|
|
|
|
-horizontal offset and y the vertical one. This makes sense given the
|
|
|
|
-screen is just a rectangle in two dimensions. As an example, here is a
|
|
|
|
-position in 2D space:
|
|
|
|
-
|
|
|
|
-.. image:: img/tutovec1.png
|
|
|
|
|
|
+In 2D space, coordinates are defined using a horizontal axis (``x``) and
|
|
|
|
+a vertical axis (``y``). A particular position in 2D space is written
|
|
|
|
+as a pair of values such as ``(4, 3)``.
|
|
|
|
|
|
-A position can be anywhere in space. The position (0,0) has a name, it's
|
|
|
|
-called the **origin**. Remember this term well because it has more
|
|
|
|
-implicit uses later. The (0,0) of a n-dimensions coordinate system is
|
|
|
|
-the **origin**.
|
|
|
|
|
|
+.. image:: img/vector_axis1.png
|
|
|
|
|
|
-In vector math, coordinates have two different uses, both equally
|
|
|
|
-important. They are used to represent a *position* but also a *vector*.
|
|
|
|
-The same position as before, when imagined as a vector, has a different
|
|
|
|
-meaning.
|
|
|
|
|
|
+.. note:: If you're new to computer graphics, it might seem odd that the
|
|
|
|
+ positive ``y`` axis points **downwards** instead of upwards,
|
|
|
|
+ as you probably learned in math class. However, this is common
|
|
|
|
+ in most computer graphics applications.
|
|
|
|
|
|
-.. image:: img/tutovec2.png
|
|
|
|
|
|
+Any position in the 2D plane can be identified by a pair of numbers in this
|
|
|
|
+way. However, we can also think of the position ``(4, 3)`` as an **offset**
|
|
|
|
+from the ``(0, 0)`` point, or **origin**. Draw an arrow pointing from
|
|
|
|
+the origin to the point:
|
|
|
|
|
|
-When imagined as a vector, two properties can be inferred, the
|
|
|
|
-**direction** and the **magnitude**. Every position in space can be a
|
|
|
|
-vector, with the exception of the **origin**. This is because
|
|
|
|
-coordinates (0,0) can't represent direction (magnitude 0).
|
|
|
|
|
|
+.. image:: img/vector_xy1.png
|
|
|
|
|
|
-.. image:: img/tutovec2b.png
|
|
|
|
|
|
+This is a **vector**. A vector represents a lot of useful information. As
|
|
|
|
+well as telling us that the point is at ``(4, 3)``, we can also think of
|
|
|
|
+it as an angle ``θ`` and a length (or magnitude) ``m``. In this case, the
|
|
|
|
+arrow is a **position vector** - it denotes a position in space, relative
|
|
|
|
+to the origin.
|
|
|
|
|
|
-Direction
|
|
|
|
----------
|
|
|
|
|
|
+A very important point to consider about vectors is that they only
|
|
|
|
+represent **relative** direction and magnitude. There is no concept of
|
|
|
|
+a vector's position. The following two vectors are identical:
|
|
|
|
|
|
-Direction is simply towards where the vector points to. Imagine an arrow
|
|
|
|
-that starts at the **origin** and goes towards a [STRIKEOUT:position].
|
|
|
|
-The tip of the arrow is in the position, so it always points outwards,
|
|
|
|
-away from the origin. Imagining vectors as arrows helps a lot.
|
|
|
|
|
|
+.. image:: img/vector_xy2.png
|
|
|
|
|
|
-.. image:: img/tutovec3b.png
|
|
|
|
|
|
+Both vectors represent a point 4 units to the left and 3 units below some
|
|
|
|
+starting point. It does not matter where on the plane you draw the vector,
|
|
|
|
+it always represents a relative direction and magnitude.
|
|
|
|
|
|
-Magnitude
|
|
|
|
----------
|
|
|
|
|
|
+Vector Operations
|
|
|
|
+~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
-Finally, the length of the vector is the distance from the origin to the
|
|
|
|
-position. Obtaining the length from a vector is easy, just use the
|
|
|
|
-`Pythagorean
|
|
|
|
-Theorem <http://en.wikipedia.org/wiki/Pythagorean_theorem>`__.
|
|
|
|
|
|
+You can use either method (x and y coordinates or angle and magnitude) to
|
|
|
|
+refer to a vector, but for convenience programmers typically use the
|
|
|
|
+coordinate notation. For example, in Godot the origin is the top-left
|
|
|
|
+corner of the screen, so to place a 2D node 400 pixels to the right and
|
|
|
|
+300 pixels down, use the following code:
|
|
|
|
|
|
::
|
|
::
|
|
|
|
|
|
- var len = sqrt( x*x + y*y )
|
|
|
|
-
|
|
|
|
-But... angles?
|
|
|
|
---------------
|
|
|
|
|
|
+ $Node2D.position = Vector2(400, 300)
|
|
|
|
|
|
-But why not using an *angle*? After all, we could also think of a vector
|
|
|
|
-as an angle and a magnitude, instead of a direction and a magnitude.
|
|
|
|
-Angles also are a more familiar concept.
|
|
|
|
|
|
+Godot supports both :ref:`Vector2 <class_Vector2>` and
|
|
|
|
+:ref:`Vector3 <class_Vector3>` for 2D and 3D usage respectively. The same
|
|
|
|
+mathematical rules discussed in this article apply for both types.
|
|
|
|
|
|
-To say truth, angles are not that useful in vector math, and most of the
|
|
|
|
-time they are not dealt with directly. Maybe they work in 2D, but in 3D
|
|
|
|
-a lot of what can usually be done with angles does not work anymore.
|
|
|
|
|
|
+- Member access
|
|
|
|
|
|
-Still, using angles is still not an excuse, even for 2D. Most of what
|
|
|
|
-takes a lot of work with angles in 2D, is still much more natural easier
|
|
|
|
-to accomplish with vector math. In vector math, angles are useful only
|
|
|
|
-as measure, but take little part in the math. So, give up the
|
|
|
|
-trigonometry already, prepare to embrace vectors!
|
|
|
|
-
|
|
|
|
-In any case, obtaining an angle from a vector is easy and can be
|
|
|
|
-accomplished with trig... er, what was that? I mean, the
|
|
|
|
-:ref:`atan2() <class_@GDScript_atan2>` function.
|
|
|
|
-
|
|
|
|
-Vectors in Godot
|
|
|
|
-~~~~~~~~~~~~~~~~
|
|
|
|
-
|
|
|
|
-To make examples easier, it is worth explaining how vectors are
|
|
|
|
-implemented in GDScript. GDscript has both
|
|
|
|
-:ref:`Vector2 <class_Vector2>` and :ref:`Vector3 <class_Vector3>`,
|
|
|
|
-for 2D and 3D math respectively. Godot uses Vector classes as both
|
|
|
|
-position and direction. They also contain x and y (for 2D) and x, y and
|
|
|
|
-z (for 3D) member variables.
|
|
|
|
|
|
+The individual components of the vector can be accessed directly by name.
|
|
|
|
|
|
::
|
|
::
|
|
|
|
|
|
- # create a vector with coordinates (2,5)
|
|
|
|
- var a = Vector2(2,5)
|
|
|
|
|
|
+ # create a vector with coordinates (2, 5)
|
|
|
|
+ var a = Vector2(2, 5)
|
|
# create a vector and assign x and y manually
|
|
# create a vector and assign x and y manually
|
|
var b = Vector2()
|
|
var b = Vector2()
|
|
- b.x = 7
|
|
|
|
- b.y = 8
|
|
|
|
|
|
+ b.x = 3
|
|
|
|
+ b.y = 1
|
|
|
|
+
|
|
|
|
+- Adding vectors
|
|
|
|
|
|
-When operating with vectors, it is not necessary to operate on the
|
|
|
|
-members directly (in fact this is much slower). Vectors support regular
|
|
|
|
-arithmetic operations:
|
|
|
|
|
|
+When adding or subtracting two vectors, the corresponding components are added:
|
|
|
|
|
|
::
|
|
::
|
|
|
|
|
|
- # add a and b
|
|
|
|
- var c = a + b
|
|
|
|
- # will result in c vector, with value (9,13)
|
|
|
|
|
|
+ var c = a + b # (2, 5) + (3, 1) = (5, 6)
|
|
|
|
|
|
-It is the same as doing:
|
|
|
|
|
|
+We can also see this visually by adding the second vector at the end of
|
|
|
|
+the first:
|
|
|
|
|
|
-::
|
|
|
|
|
|
+.. image:: img/vector_add1.png
|
|
|
|
|
|
- var c = Vector2()
|
|
|
|
- c.x = a.x + b.x
|
|
|
|
- c.y = a.y + b.y
|
|
|
|
|
|
+Note that adding ``a + b`` gives the same result as ``b + a``.
|
|
|
|
|
|
-Except the former is way more efficient and readable.
|
|
|
|
|
|
+- Scalar multiplication
|
|
|
|
|
|
-Regular arithmetic operations such as addition, subtraction,
|
|
|
|
-multiplication and division are supported.
|
|
|
|
|
|
+.. note:: Vectors represent both direction and magnitude. A value
|
|
|
|
+ representing only magnitude is called a **scalar**.
|
|
|
|
|
|
-Vector multiplication and division can also be mixed with single-digit
|
|
|
|
-numbers, also named **scalars**.
|
|
|
|
|
|
+A vector can be multiplied by a **scalar**:
|
|
|
|
|
|
::
|
|
::
|
|
|
|
|
|
- # multiplication of vector by scalar
|
|
|
|
- var c = a*2.0
|
|
|
|
- # will result in c vector, with value (4,10)
|
|
|
|
|
|
+ var c = a * 2 # (2, 5) * 2 = (4, 10)
|
|
|
|
+ var d = b / 3 # (3, 6) / 3 = (1, 2)
|
|
|
|
|
|
-Which is the same as doing
|
|
|
|
|
|
+.. image:: img/vector_mult1.png
|
|
|
|
|
|
-::
|
|
|
|
|
|
+.. note:: Multiplying a vector by a scalar does not change its direction,
|
|
|
|
+ only its magnitude. This is how you **scale** a vector.
|
|
|
|
|
|
- var c = Vector2()
|
|
|
|
- c.x = a.x*2.0
|
|
|
|
- c.y = a.y*2.0
|
|
|
|
|
|
+Practical applications
|
|
|
|
+~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
-Except, again, the former is way more efficient and readable.
|
|
|
|
|
|
+Let's look at two common uses for vector addition and subtraction.
|
|
|
|
|
|
-Perpendicular vectors
|
|
|
|
-~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
+- Movement
|
|
|
|
|
|
-Rotating a 2D vector 90° degrees to either side, left or right, is
|
|
|
|
-really easy, just swap x and y, then negate either x or y (direction of
|
|
|
|
-rotation depends on which is negated).
|
|
|
|
|
|
+A vector can represent **any** quantity with a magnitude and direction. In
|
|
|
|
+this image, the spaceship at step 1 has a position vector of ``(1,3)`` and
|
|
|
|
+a velocity vector of ``(2,1)``. The velocity vector represents how far the
|
|
|
|
+ship moves each step. We can find the position for step 2 by adding
|
|
|
|
+the velocity to the current position.
|
|
|
|
|
|
-.. image:: img/tutovec15.png
|
|
|
|
|
|
+.. image:: img/vector_movement1.png
|
|
|
|
|
|
-Example:
|
|
|
|
|
|
+.. tip:: Velocity measures the **change** in position per unit of time. The
|
|
|
|
+ new position is found by adding velocity to the previous position.
|
|
|
|
|
|
-::
|
|
|
|
|
|
+- Pointing toward a target
|
|
|
|
|
|
- var v = Vector2(0,1)
|
|
|
|
-
|
|
|
|
- # rotate right (clockwise)
|
|
|
|
- var v_right = Vector2(v.y, -v.x)
|
|
|
|
-
|
|
|
|
- # rotate left (counter-clockwise)
|
|
|
|
- var v_left = Vector2(-v.y, v.x)
|
|
|
|
|
|
+In this scenario, you have a tank that wishes to point its turret at a
|
|
|
|
+robot. Subtracting the tank's position from the robot's position gives the
|
|
|
|
+vector pointing from the tank to the robot.
|
|
|
|
|
|
-This is a handy trick that is often of use. It is impossible to do with
|
|
|
|
-3D vectors, because there are an infinite amount of perpendicular
|
|
|
|
-vectors.
|
|
|
|
|
|
+.. image:: img/vector_subtract2.png
|
|
|
|
+
|
|
|
|
+.. tip:: To find a vector pointing from ``A`` to ``B`` use ``B - A``.
|
|
|
|
|
|
Unit vectors
|
|
Unit vectors
|
|
~~~~~~~~~~~~
|
|
~~~~~~~~~~~~
|
|
|
|
|
|
-Ok, so we know what a vector is. It has a **direction** and a
|
|
|
|
-**magnitude**. We also know how to use them in Godot. The next step is
|
|
|
|
-learning about **unit vectors**. Any vector with **magnitude** of length
|
|
|
|
-1 is considered a **unit vector**. In 2D, imagine drawing a circle of
|
|
|
|
-radius one. That circle contains all unit vectors in existence for 2
|
|
|
|
-dimensions:
|
|
|
|
-
|
|
|
|
-.. image:: img/tutovec3.png
|
|
|
|
-
|
|
|
|
-So, what is so special about unit vectors? Unit vectors are amazing. In
|
|
|
|
-other words, unit vectors have **several, very useful properties**.
|
|
|
|
-
|
|
|
|
-Can't wait to know more about the fantastic properties of unit vectors,
|
|
|
|
-but one step at a time. So, how is a unit vector created from a regular
|
|
|
|
-vector?
|
|
|
|
|
|
+A vector with **magnitude** of ``1`` is called a **unit vector**. They are
|
|
|
|
+also sometimes referred to as **direction vectors** or **normals**. Unit
|
|
|
|
+vectors are helpful when you need to keep track of a direction.
|
|
|
|
|
|
Normalization
|
|
Normalization
|
|
-------------
|
|
-------------
|
|
|
|
|
|
-Taking any vector and reducing its **magnitude** to 1.0 while keeping
|
|
|
|
-its **direction** is called **normalization**. Normalization is
|
|
|
|
-performed by dividing the x and y (and z in 3D) components of a vector
|
|
|
|
|
|
+**Normalizing** a vector means reducing its length to ``1`` while
|
|
|
|
+preserving its direction. This is done by dividing each of its components
|
|
by its magnitude:
|
|
by its magnitude:
|
|
|
|
|
|
::
|
|
::
|
|
|
|
|
|
- var a = Vector2(2,4)
|
|
|
|
|
|
+ var a = Vector2(2, 4)
|
|
var m = sqrt(a.x*a.x + a.y*a.y)
|
|
var m = sqrt(a.x*a.x + a.y*a.y)
|
|
a.x /= m
|
|
a.x /= m
|
|
a.y /= m
|
|
a.y /= m
|
|
|
|
|
|
-As you might have guessed, if the vector has magnitude 0 (meaning, it's
|
|
|
|
-not a vector but the **origin** also called *null vector*), a division
|
|
|
|
-by zero occurs and the universe goes through a second big bang, except
|
|
|
|
-in reverse polarity and then back. As a result, humanity is safe but
|
|
|
|
-Godot will print an error. Remember! Vector(0,0) can't be normalized!.
|
|
|
|
-
|
|
|
|
-Of course, Vector2 and Vector3 already provide a method to do this:
|
|
|
|
|
|
+Because this is such a common operation, ``Vector2`` and ``Vector3`` provide
|
|
|
|
+a method for normalizing:
|
|
|
|
|
|
::
|
|
::
|
|
|
|
|
|
a = a.normalized()
|
|
a = a.normalized()
|
|
|
|
|
|
-Dot product
|
|
|
|
-~~~~~~~~~~~
|
|
|
|
-
|
|
|
|
-OK, the **dot product** is the most important part of vector math.
|
|
|
|
-Without the dot product, Quake would have never been made. This is the
|
|
|
|
-most important section of the tutorial, so make sure to grasp it
|
|
|
|
-properly. Most people trying to understand vector math give up here
|
|
|
|
-because, despite how simple it is, they can't make head or tails from
|
|
|
|
-it. Why? Here's why, it's because...
|
|
|
|
-
|
|
|
|
-The dot product takes two vectors and returns a **scalar**:
|
|
|
|
-
|
|
|
|
-::
|
|
|
|
-
|
|
|
|
- var s = a.x*b.x + a.y*b.y
|
|
|
|
-
|
|
|
|
-Yes, pretty much that. Multiply **x** from vector **a** by **x** from
|
|
|
|
-vector **b**. Do the same with y and add it together. In 3D it's pretty
|
|
|
|
-much the same:
|
|
|
|
-
|
|
|
|
-::
|
|
|
|
-
|
|
|
|
- var s = a.x*b.x + a.y*b.y + a.z*b.z
|
|
|
|
-
|
|
|
|
-I know, it's totally meaningless! You can even do it with a built-in
|
|
|
|
-function:
|
|
|
|
-
|
|
|
|
-::
|
|
|
|
-
|
|
|
|
- var s = a.dot(b)
|
|
|
|
-
|
|
|
|
-The order of two vectors does *not* matter, ``a.dot(b)`` returns the
|
|
|
|
-same value as ``b.dot(a)``.
|
|
|
|
-
|
|
|
|
-This is where despair begins and books and tutorials show you this
|
|
|
|
-formula:
|
|
|
|
-
|
|
|
|
-.. image:: img/tutovec4.png
|
|
|
|
-
|
|
|
|
-And you realize it's time to give up making 3D games or complex 2D
|
|
|
|
-games. How can something so simple be so complex? Someone else will have
|
|
|
|
-to make the next Zelda or Call of Duty. Top down RPGs don't look so bad
|
|
|
|
-after all. Yeah I hear someone did pretty well with one of those on
|
|
|
|
-Steam...
|
|
|
|
-
|
|
|
|
-So this is your moment, this is your time to shine. **DO NOT GIVE UP**!
|
|
|
|
-At this point, this tutorial will take a sharp turn and focus on what
|
|
|
|
-makes the dot product useful. This is, **why** it is useful. We will
|
|
|
|
-focus one by one in the use cases for the dot product, with real-life
|
|
|
|
-applications. No more formulas that don't make any sense. Formulas will
|
|
|
|
-make sense *once you learn* what they are useful for.
|
|
|
|
-
|
|
|
|
-Siding
|
|
|
|
-------
|
|
|
|
-
|
|
|
|
-The first useful and most important property of the dot product is to
|
|
|
|
-check what side stuff is looking at. Let's imagine we have any two
|
|
|
|
-vectors, **a** and **b**. Any **direction** or **magnitude** (neither
|
|
|
|
-**origin**). Does not matter what they are, but let's imagine we compute
|
|
|
|
-the dot product between them.
|
|
|
|
-
|
|
|
|
-::
|
|
|
|
-
|
|
|
|
- var s = a.dot(b)
|
|
|
|
-
|
|
|
|
-The operation will return a single floating point number (but since we
|
|
|
|
-are in vector world, we call them **scalar**, will keep using that term
|
|
|
|
-from now on). This number will tell us the following:
|
|
|
|
-
|
|
|
|
-- If the number is greater than zero, both are looking towards the same
|
|
|
|
- direction (the angle between them is < 90° degrees).
|
|
|
|
-- If the number is less than zero, both are looking towards opposite
|
|
|
|
- direction (the angle between them is > 90° degrees).
|
|
|
|
-- If the number is zero, vectors are shaped in L (the angle between
|
|
|
|
- them *is* 90° degrees).
|
|
|
|
-
|
|
|
|
-.. image:: img/tutovec5.png
|
|
|
|
-
|
|
|
|
-So let's think of a real use-case scenario. Imagine Snake is going
|
|
|
|
-through a forest, and then there is an enemy nearby. How can we quickly
|
|
|
|
-tell if the enemy has seen discovered Snake? In order to discover him,
|
|
|
|
-the enemy must be able to *see* Snake. Let's say, then that:
|
|
|
|
-
|
|
|
|
-- Snake is in position **A**.
|
|
|
|
-- The enemy is in position **B**.
|
|
|
|
-- The enemy is *facing* towards direction vector **F**.
|
|
|
|
-
|
|
|
|
-.. image:: img/tutovec6.png
|
|
|
|
-
|
|
|
|
-So, let's create a new vector **BA** that goes from the guard (**B**) to
|
|
|
|
-Snake (**A**), by subtracting the two:
|
|
|
|
-
|
|
|
|
-::
|
|
|
|
-
|
|
|
|
- var BA = A - B
|
|
|
|
-
|
|
|
|
-.. image:: img/tutovec7.png
|
|
|
|
-
|
|
|
|
-Ideally, if the guard was looking straight towards snake, to make eye to
|
|
|
|
-eye contact, it would do it in the same direction as vector BA.
|
|
|
|
-
|
|
|
|
-If the dot product between **F** and **BA** is greater than 0, then
|
|
|
|
-Snake will be discovered. This happens because we will be able to tell
|
|
|
|
-that the guard is facing towards him:
|
|
|
|
-
|
|
|
|
-::
|
|
|
|
-
|
|
|
|
- if (BA.dot(F) > 0):
|
|
|
|
- print("!")
|
|
|
|
-
|
|
|
|
-Seems Snake is safe so far.
|
|
|
|
-
|
|
|
|
-Siding with unit vectors
|
|
|
|
-~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
-
|
|
|
|
-Ok, so now we know that dot product between two vectors will let us know
|
|
|
|
-if they are looking towards the same side, opposite sides or are just
|
|
|
|
-perpendicular to each other.
|
|
|
|
-
|
|
|
|
-This works the same with all vectors, no matter the magnitude so **unit
|
|
|
|
-vectors** are not the exception. However, using the same property with
|
|
|
|
-unit vectors yields an even more interesting result, as an extra
|
|
|
|
-property is added:
|
|
|
|
-
|
|
|
|
-- If both vectors are facing towards the exact same direction (parallel
|
|
|
|
- to each other, angle between them is 0°), the resulting scalar is
|
|
|
|
- **1**.
|
|
|
|
-- If both vectors are facing towards the exact opposite direction
|
|
|
|
- (parallel to each other, but angle between them is 180°), the
|
|
|
|
- resulting scalar is **-1**.
|
|
|
|
-
|
|
|
|
-This means that dot product between unit vectors is always between the
|
|
|
|
-range of 1 and -1. So Again...
|
|
|
|
-
|
|
|
|
-- If their angle is **0°** dot product is **1**.
|
|
|
|
-- If their angle is **90°**, then dot product is **0**.
|
|
|
|
-- If their angle is **180°**, then dot product is **-1**.
|
|
|
|
-
|
|
|
|
-Uh.. this is oddly familiar... seen this before... where?
|
|
|
|
-
|
|
|
|
-Let's take two unit vectors. The first one is pointing up, the second
|
|
|
|
-too but we will rotate it all the way from up (0°) to down (180°
|
|
|
|
-degrees)...
|
|
|
|
-
|
|
|
|
-.. image:: img/tutovec8.png
|
|
|
|
-
|
|
|
|
-While plotting the resulting scalar!
|
|
|
|
-
|
|
|
|
-.. image:: img/tutovec9.png
|
|
|
|
-
|
|
|
|
-Aha! It all makes sense now, this is a
|
|
|
|
-`Cosine <http://mathworld.wolfram.com/Cosine.html>`__ function!
|
|
|
|
-
|
|
|
|
-We can say that, then, as a rule...
|
|
|
|
-
|
|
|
|
-The **dot product** between two **unit vectors** is the **cosine** of
|
|
|
|
-the **angle** between those two vectors. So, to obtain the angle between
|
|
|
|
-two vectors, we must do:
|
|
|
|
-
|
|
|
|
-::
|
|
|
|
-
|
|
|
|
- var angle_in_radians = acos( a.dot(b) )
|
|
|
|
-
|
|
|
|
-What is this useful for? Well obtaining the angle directly is probably
|
|
|
|
-not as useful, but just being able to tell the angle is useful for
|
|
|
|
-reference. One example is in the `Kinematic
|
|
|
|
-Character <https://github.com/godotengine/godot-demo-projects/blob/master/2d/kinematic_char/player.gd#L79>`__
|
|
|
|
-demo, when the character moves in a certain direction then we hit an
|
|
|
|
-object. How to tell if what we hit is the floor?
|
|
|
|
-
|
|
|
|
-By comparing the normal of the collision point with a previously
|
|
|
|
-computed angle.
|
|
|
|
-
|
|
|
|
-The beauty of this is that the same code works exactly the same and
|
|
|
|
-without modification in
|
|
|
|
-`3D <https://github.com/godotengine/godot-demo-projects/blob/master/3d/kinematic_char/cubio.gd#L57>`__.
|
|
|
|
-Vector math is, in a great deal, dimension-amount-independent, so adding
|
|
|
|
-or removing an axis only adds very little complexity.
|
|
|
|
-
|
|
|
|
-Planes
|
|
|
|
-~~~~~~
|
|
|
|
-
|
|
|
|
-The dot product has another interesting property with unit vectors.
|
|
|
|
-Imagine that perpendicular to that vector (and through the origin)
|
|
|
|
-passes a plane. Planes divide the entire space into positive
|
|
|
|
-(over the plane) and negative (under the plane), and (contrary to
|
|
|
|
-popular belief) you can also use their math in 2D:
|
|
|
|
-
|
|
|
|
-.. image:: img/tutovec10.png
|
|
|
|
-
|
|
|
|
-Unit vectors that are perpendicular to a surface (so, they describe the
|
|
|
|
-orientation of the surface) are called **unit normal vectors**. Though,
|
|
|
|
-usually they are just abbreviated as \*normals. Normals appear in
|
|
|
|
-planes, 3D geometry (to determine where each face or vertex is siding),
|
|
|
|
-etc. A **normal** *is* a **unit vector**, but it's called *normal*
|
|
|
|
-because of its usage. (Just like we call Origin to (0,0)!).
|
|
|
|
-
|
|
|
|
-It's as simple as it looks. The plane passes by the origin and the
|
|
|
|
-surface of it is perpendicular to the unit vector (or *normal*). The
|
|
|
|
-side towards the vector points to is the positive half-space, while the
|
|
|
|
-other side is the negative half-space. In 3D this is exactly the same,
|
|
|
|
-except that the plane is an infinite surface (imagine an infinite, flat
|
|
|
|
-sheet of paper that you can orient and is pinned to the origin) instead
|
|
|
|
-of a line.
|
|
|
|
-
|
|
|
|
-Distance to plane
|
|
|
|
------------------
|
|
|
|
-
|
|
|
|
-Now that it's clear what a plane is, let's go back to the dot product.
|
|
|
|
-The dot product between a **unit vector** and any **point in space**
|
|
|
|
-(yes, this time we do dot product between vector and position), returns
|
|
|
|
-the **distance from the point to the plane**:
|
|
|
|
-
|
|
|
|
-::
|
|
|
|
-
|
|
|
|
- var distance = normal.dot(point)
|
|
|
|
-
|
|
|
|
-But not just the absolute distance, if the point is in the negative half
|
|
|
|
-space the distance will be negative, too:
|
|
|
|
-
|
|
|
|
-.. image:: img/tutovec11.png
|
|
|
|
-
|
|
|
|
-This allows us to tell which side of the plane a point is.
|
|
|
|
|
|
+.. warning:: Because normalization involves dividing by the vector's length,
|
|
|
|
+ you cannot normalize a vector of length ``0``. Attempting to
|
|
|
|
+ do so will result in an error.
|
|
|
|
|
|
-Away from the origin
|
|
|
|
---------------------
|
|
|
|
-
|
|
|
|
-I know what you are thinking! So far this is nice, but *real* planes are
|
|
|
|
-everywhere in space, not only passing through the origin. You want real
|
|
|
|
-*plane* action and you want it *now*.
|
|
|
|
-
|
|
|
|
-Remember that planes not only split space in two, but they also have
|
|
|
|
-*polarity*. This means that it is possible to have perfectly overlapping
|
|
|
|
-planes, but their negative and positive half-spaces are swapped.
|
|
|
|
-
|
|
|
|
-With this in mind, let's describe a full plane as a **normal** *N* and a
|
|
|
|
-**distance from the origin** scalar *D*. Thus, our plane is represented
|
|
|
|
-by N and D. For example:
|
|
|
|
-
|
|
|
|
-.. image:: img/tutovec12.png
|
|
|
|
-
|
|
|
|
-For 3D math, Godot provides a :ref:`Plane <class_Plane>`
|
|
|
|
-built-in type that handles this.
|
|
|
|
|
|
+Reflection
|
|
|
|
+----------
|
|
|
|
|
|
-Basically, N and D can represent any plane in space, be it for 2D or 3D
|
|
|
|
-(depending on the amount of dimensions of N) and the math is the same
|
|
|
|
-for both. It's the same as before, but D is the distance from the origin
|
|
|
|
-to the plane, travelling in N direction. As an example, imagine you want
|
|
|
|
-to reach a point in the plane, you will just do:
|
|
|
|
|
|
+A common use of unit vectors is to indicate **normals**. Normal
|
|
|
|
+vectors are unit vectors aligned perpendicularly to a surface, defining
|
|
|
|
+its direction. They are commonly used for lighting, collisions, and other
|
|
|
|
+operations involving surfaces.
|
|
|
|
|
|
-::
|
|
|
|
-
|
|
|
|
- var point_in_plane = N*D
|
|
|
|
-
|
|
|
|
-This will stretch (resize) the normal vector and make it touch the
|
|
|
|
-plane. This math might seem confusing, but it's actually much simpler
|
|
|
|
-than it seems. If we want to tell, again, the distance from the point to
|
|
|
|
-the plane, we do the same but adjusting for distance:
|
|
|
|
-
|
|
|
|
-::
|
|
|
|
|
|
+For example, imagine we have a moving ball that we want to bounce off a
|
|
|
|
+wall or other object:
|
|
|
|
|
|
- var distance = N.dot(point) - D
|
|
|
|
|
|
+.. image:: img/vector_reflect1.png
|
|
|
|
|
|
-The same thing, using a built-in function:
|
|
|
|
|
|
+The surface normal has a value of ``(0, -1)`` because this is a horizontal
|
|
|
|
+surface. When the ball collides, we take its remaining motion (the amount
|
|
|
|
+left over when it hits the surface) and reflect it using the normal. In
|
|
|
|
+Godot, the :ref:`Vector2 <class_Vector2>` class has a ``bounce()`` method
|
|
|
|
+to handle this. Here is a GDScript example of the diagram above using a
|
|
|
|
+:ref:`KinematicBody2D <class_KinematicBody2D>`:
|
|
|
|
|
|
::
|
|
::
|
|
|
|
|
|
- var distance = plane.distance_to(point)
|
|
|
|
-
|
|
|
|
-This will, again, return either a positive or negative distance.
|
|
|
|
-
|
|
|
|
-Flipping the polarity of the plane is also very simple, just negate both
|
|
|
|
-N and D. This will result in a plane in the same position, but with
|
|
|
|
-inverted negative and positive half spaces:
|
|
|
|
-
|
|
|
|
-::
|
|
|
|
-
|
|
|
|
- N = -N
|
|
|
|
- D = -D
|
|
|
|
-
|
|
|
|
-Of course, Godot also implements this operator in :ref:`Plane <class_Plane>`,
|
|
|
|
-so doing:
|
|
|
|
-
|
|
|
|
-::
|
|
|
|
-
|
|
|
|
- var inverted_plane = -plane
|
|
|
|
-
|
|
|
|
-Will work as expected.
|
|
|
|
-
|
|
|
|
-So, remember, a plane is just that and its main practical use is
|
|
|
|
-calculating the distance to it. So, why is it useful to calculate the
|
|
|
|
-distance from a point to a plane? It's extremely useful! Let's see some
|
|
|
|
-simple examples..
|
|
|
|
-
|
|
|
|
-Constructing a plane in 2D
|
|
|
|
---------------------------
|
|
|
|
-
|
|
|
|
-Planes clearly don't come out of nowhere, so they must be built.
|
|
|
|
-Constructing them in 2D is easy, this can be done from either a normal
|
|
|
|
-(unit vector) and a point, or from two points in space.
|
|
|
|
-
|
|
|
|
-In the case of a normal and a point, most of the work is done, as the
|
|
|
|
-normal is already computed, so just calculate D from the dot product of
|
|
|
|
-the normal and the point.
|
|
|
|
|
|
+ var collision = move_and_collide(velocity * delta)
|
|
|
|
+ if collision:
|
|
|
|
+ var reflect = collision.remainder.bounce(collision.normal)
|
|
|
|
+ velocity = velocity.bounce(collision.normal)
|
|
|
|
+ move_and_collide(reflect)
|
|
|
|
+
|
|
|
|
+Dot product
|
|
|
|
+~~~~~~~~~~~
|
|
|
|
|
|
-::
|
|
|
|
|
|
+The **dot product** is one of the most important concepts in vector math,
|
|
|
|
+but is often misunderstood. Dot product is an operation on two vectors that
|
|
|
|
+returns a **scalar**. Unlike a vector, which contains both magnitude and
|
|
|
|
+direction, a scalar value has only magnitude.
|
|
|
|
|
|
- var N = normal
|
|
|
|
- var D = normal.dot(point)
|
|
|
|
|
|
+The formula for dot product takes two common
|
|
|
|
+forms:
|
|
|
|
|
|
-For two points in space, there are actually two planes that pass through
|
|
|
|
-them, sharing the same space but with normal pointing to the opposite
|
|
|
|
-directions. To compute the normal from the two points, the direction
|
|
|
|
-vector must be obtained first, and then it needs to be rotated 90°
|
|
|
|
-degrees to either side:
|
|
|
|
|
|
+.. image:: img/vector_dot1.png
|
|
|
|
|
|
-::
|
|
|
|
|
|
+and
|
|
|
|
|
|
- # calculate vector from a to b
|
|
|
|
- var dvec = (point_b - point_a).normalized()
|
|
|
|
- # rotate 90 degrees
|
|
|
|
- var normal = Vector2(dvec.y,-dev.x)
|
|
|
|
- # or alternatively
|
|
|
|
- # var normal = Vector2(-dvec.y,dev.x)
|
|
|
|
- # depending the desired side of the normal
|
|
|
|
|
|
+.. image:: img/vector_dot2.png
|
|
|
|
|
|
-The rest is the same as the previous example, either point_a or
|
|
|
|
-point_b will work since they are in the same plane:
|
|
|
|
|
|
+However, in most cases it is easiest to use the built-in method. Note that
|
|
|
|
+the order of the two vectors does not matter:
|
|
|
|
|
|
::
|
|
::
|
|
|
|
|
|
- var N = normal
|
|
|
|
- var D = normal.dot(point_a)
|
|
|
|
- # this works the same
|
|
|
|
- # var D = normal.dot(point_b)
|
|
|
|
-
|
|
|
|
-Doing the same in 3D is a little more complex and will be explained
|
|
|
|
-further down.
|
|
|
|
-
|
|
|
|
-Some examples of planes
|
|
|
|
------------------------
|
|
|
|
|
|
+ var c = a.dot(b)
|
|
|
|
+ var d = b.dot(a) # these are equivalent
|
|
|
|
|
|
-Here is a simple example of what planes are useful for. Imagine you have
|
|
|
|
-a `convex <http://www.mathsisfun.com/definitions/convex.html>`__
|
|
|
|
-polygon. For example, a rectangle, a trapezoid, a triangle, or just any
|
|
|
|
-polygon where faces that don't bend inwards.
|
|
|
|
|
|
+The dot product is most useful when used with unit vectors, making the
|
|
|
|
+first formula reduce to just ``cosθ``. This means we can use the dot
|
|
|
|
+product to tell us something about the angle between two vectors:
|
|
|
|
|
|
-For every segment of the polygon, we compute the plane that passes by
|
|
|
|
-that segment. Once we have the list of planes, we can do neat things,
|
|
|
|
-for example checking if a point is inside the polygon.
|
|
|
|
|
|
+.. image:: img/vector_dot3.png
|
|
|
|
|
|
-We go through all planes, if we can find a plane where the distance to
|
|
|
|
-the point is positive, then the point is outside the polygon. If we
|
|
|
|
-can't, then the point is inside.
|
|
|
|
|
|
+When using unit vectors, the result will always be between ``-1`` (180°)
|
|
|
|
+and ``1`` (0°).
|
|
|
|
|
|
-.. image:: img/tutovec13.png
|
|
|
|
-
|
|
|
|
-Code should be something like this:
|
|
|
|
-
|
|
|
|
-::
|
|
|
|
-
|
|
|
|
- var inside = true
|
|
|
|
- for p in planes:
|
|
|
|
- # check if distance to plane is positive
|
|
|
|
- if (N.dot(point) - D > 0):
|
|
|
|
- inside = false
|
|
|
|
- break # with one that fails, it's enough
|
|
|
|
|
|
+Facing
|
|
|
|
+------
|
|
|
|
|
|
-Pretty cool, huh? But this gets much better! With a little more effort,
|
|
|
|
-similar logic will let us know when two convex polygons are overlapping
|
|
|
|
-too. This is called the Separating Axis Theorem (or SAT) and most
|
|
|
|
-physics engines use this to detect collision.
|
|
|
|
|
|
+We can use this fact to detect whether an object is facing toward another
|
|
|
|
+object. In the diagram below, the player ``P`` is trying to avoid the
|
|
|
|
+zombies ``A`` and ``B``. Can the zombies see the player?
|
|
|
|
|
|
-The idea is really simple! With a point, just checking if a plane
|
|
|
|
-returns a positive distance is enough to tell if the point is outside.
|
|
|
|
-With another polygon, we must find a plane where *all the **other**
|
|
|
|
-polygon points* return a positive distance to it. This check is
|
|
|
|
-performed with the planes of A against the points of B, and then with
|
|
|
|
-the planes of B against the points of A:
|
|
|
|
|
|
+.. image:: img/vector_facing2.png
|
|
|
|
|
|
-.. image:: img/tutovec14.png
|
|
|
|
|
|
+The green arrows ``fA`` and ``fB`` are **unit vectors** representing the
|
|
|
|
+zombies' facing directions and the blue semicircle represents its field of
|
|
|
|
+view. For zombie ``A``, we find the direction vector ``AP`` pointing to
|
|
|
|
+the player using ``P - A`` and normalize it. If the angle between this
|
|
|
|
+vector and the facing vector is less than 90°, then the zombie can see
|
|
|
|
+the player.
|
|
|
|
|
|
-Code should be something like this:
|
|
|
|
|
|
+In GDScript it would look like this:
|
|
|
|
|
|
::
|
|
::
|
|
|
|
|
|
- var overlapping = true
|
|
|
|
-
|
|
|
|
- for p in planes_of_A:
|
|
|
|
- var all_out = true
|
|
|
|
- for v in points_of_B:
|
|
|
|
- if (p.distance_to(v) < 0):
|
|
|
|
- all_out = false
|
|
|
|
- break
|
|
|
|
-
|
|
|
|
- if (all_out):
|
|
|
|
- # a separating plane was found
|
|
|
|
- # do not continue testing
|
|
|
|
- overlapping = false
|
|
|
|
- break
|
|
|
|
-
|
|
|
|
- if (overlapping):
|
|
|
|
- # only do this check if no separating plane
|
|
|
|
- # was found in planes of A
|
|
|
|
- for p in planes_of_B:
|
|
|
|
- var all_out = true
|
|
|
|
- for v in points_of_A:
|
|
|
|
- if (p.distance_to(v) < 0):
|
|
|
|
- all_out = false
|
|
|
|
- break
|
|
|
|
-
|
|
|
|
- if (all_out):
|
|
|
|
- overlapping = false
|
|
|
|
- break
|
|
|
|
-
|
|
|
|
- if (overlapping):
|
|
|
|
- print("Polygons Collided!")
|
|
|
|
-
|
|
|
|
-As you can see, planes are quite useful, and this is the tip of the
|
|
|
|
-iceberg. You might be wondering what happens with non convex polygons.
|
|
|
|
-This is usually just handled by splitting the concave polygon into
|
|
|
|
-smaller convex polygons, or using a technique such as BSP (which is not
|
|
|
|
-used much nowadays).
|
|
|
|
-
|
|
|
|
|
|
+ var AP = (P - A).normalized()
|
|
|
|
+ if AP.dot(fA) > 0:
|
|
|
|
+ print("A sees P!")
|
|
|
|
+
|
|
Cross product
|
|
Cross product
|
|
--------------
|
|
|
|
-
|
|
|
|
-Quite a lot can be done with the dot product! But the party would not be
|
|
|
|
-complete without the cross product. Remember back at the beginning of
|
|
|
|
-this tutorial? Specifically how to obtain a perpendicular (rotated 90
|
|
|
|
-degrees) vector by swapping x and y, then negating either of them for
|
|
|
|
-right (clockwise) or left (counter-clockwise) rotation? That ended up
|
|
|
|
-being useful for calculating a 2D plane normal from two points.
|
|
|
|
-
|
|
|
|
-As mentioned before, no such thing exists in 3D because a 3D vector has
|
|
|
|
-infinite perpendicular vectors. It would also not make sense to obtain a
|
|
|
|
-3D plane from 2 points, as 3 points are needed instead.
|
|
|
|
-
|
|
|
|
-To aid in this kind stuff, the brightest minds of humanity's top
|
|
|
|
-mathematicians brought us the **cross product**.
|
|
|
|
|
|
+~~~~~~~~~~~~~
|
|
|
|
|
|
-The cross product takes two vectors and returns another vector. The
|
|
|
|
-returned third vector is always perpendicular to the first two. The
|
|
|
|
-source vectors, of course, must not be the same, and must not be
|
|
|
|
-parallel or opposite, else the resulting vector will be (0,0,0):
|
|
|
|
|
|
+Like the dot product, the cross product is an operation on two vectors.
|
|
|
|
+However, the result of the cross product is a vector with a direction
|
|
|
|
+that is perpendicular to both and with a length that is the average of
|
|
|
|
+the the two lengths.
|
|
|
|
|
|
.. image:: img/tutovec16.png
|
|
.. image:: img/tutovec16.png
|
|
|
|
|
|
-The formula for the cross product is:
|
|
|
|
|
|
+The cross product is calculated like this:
|
|
|
|
|
|
::
|
|
::
|
|
|
|
|
|
@@ -680,218 +281,50 @@ The formula for the cross product is:
|
|
c.y = (a.z * b.x) - (a.x * b.z)
|
|
c.y = (a.z * b.x) - (a.x * b.z)
|
|
c.z = (a.x * b.y) - (a.y * b.x)
|
|
c.z = (a.x * b.y) - (a.y * b.x)
|
|
|
|
|
|
-This can be simplified, in Godot, to:
|
|
|
|
|
|
+In GDScript, you can use the built-in method:
|
|
|
|
|
|
::
|
|
::
|
|
|
|
|
|
var c = a.cross(b)
|
|
var c = a.cross(b)
|
|
|
|
|
|
-However, unlike the dot product, doing ``a.cross(b)`` and ``b.cross(a)``
|
|
|
|
-will yield different results. Specifically, the returned vector will be
|
|
|
|
-negated in the second case. As you might have realized, this coincides
|
|
|
|
-with creating perpendicular vectors in 2D. In 3D, there are also two
|
|
|
|
-possible perpendicular vectors to a pair of 2D vectors.
|
|
|
|
|
|
+.. note:: In the cross product, order matters. ``a.cross(b)`` does not
|
|
|
|
+ give the same result as ``b.cross(a)``. The resulting vectors
|
|
|
|
+ point in **opposite** directions.
|
|
|
|
|
|
-Also, the resulting cross product of two unit vectors is *not* a unit
|
|
|
|
-vector. Result will need to be renormalized.
|
|
|
|
|
|
+Calculating Normals
|
|
|
|
+-------------------
|
|
|
|
|
|
-Area of a triangle
|
|
|
|
-~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
+One common use of cross products is to find the surface normal of a plane
|
|
|
|
+or surface in 3D space. If we have the triangle ``ABC`` we can use vector
|
|
|
|
+subtraction to find two edges ``AB`` and ``AC``. Using the cross product,
|
|
|
|
+``AB x AC`` produces a vector perpendicular to both: the surface normal.
|
|
|
|
|
|
-Cross product can be used to obtain the surface area of a triangle in
|
|
|
|
-3D. Given a triangle consisting of 3 points, **A**, **B** and **C**:
|
|
|
|
-
|
|
|
|
-.. image:: img/tutovec17.png
|
|
|
|
-
|
|
|
|
-Take any of them as a pivot and compute the adjacent vectors to the
|
|
|
|
-other two points. As example, we will use B as a pivot:
|
|
|
|
-
|
|
|
|
-::
|
|
|
|
-
|
|
|
|
- var BA = A - B
|
|
|
|
- var BC = C - B
|
|
|
|
-
|
|
|
|
-.. image:: img/tutovec18.png
|
|
|
|
-
|
|
|
|
-Compute the cross product between **BA** and **BC** to obtain the
|
|
|
|
-perpendicular vector **P**:
|
|
|
|
-
|
|
|
|
-::
|
|
|
|
-
|
|
|
|
- var P = BA.cross(BC)
|
|
|
|
-
|
|
|
|
-.. image:: img/tutovec19.png
|
|
|
|
-
|
|
|
|
-The length (magnitude) of **P** is the surface area of the parallelogram
|
|
|
|
-built by the two vectors **BA** and **BC**, therefore the surface area
|
|
|
|
-of the triangle is half of it.
|
|
|
|
-
|
|
|
|
-::
|
|
|
|
-
|
|
|
|
- var area = P.length()/2
|
|
|
|
-
|
|
|
|
-Plane of the triangle
|
|
|
|
-~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
-
|
|
|
|
-With **P** computed from the previous step, normalize it to get the
|
|
|
|
-normal of the plane.
|
|
|
|
-
|
|
|
|
-::
|
|
|
|
-
|
|
|
|
- var N = P.normalized()
|
|
|
|
-
|
|
|
|
-And obtain the distance by doing the dot product of P with any of the 3
|
|
|
|
-points of the **ABC** triangle:
|
|
|
|
|
|
+Here is a function to calculate a triangle's normal in GDScript:
|
|
|
|
|
|
::
|
|
::
|
|
|
|
|
|
- var D = P.dot(A)
|
|
|
|
|
|
+ func get_triangle_normal(a, b, c):
|
|
|
|
+ # find the surface normal given 3 vertices
|
|
|
|
+ var side1 = b - a
|
|
|
|
+ var side2 = c - a
|
|
|
|
+ var normal = side1.cross(side2)
|
|
|
|
+ return normal
|
|
|
|
|
|
-Fantastic! You computed the plane from a triangle!
|
|
|
|
-
|
|
|
|
-Here's some useful info (that you can find in Godot source code anyway).
|
|
|
|
-Computing a plane from a triangle can result in 2 planes, so a sort of
|
|
|
|
-convention needs to be set. This usually depends (in video games and 3D
|
|
|
|
-visualization) to use the front-facing side of the triangle.
|
|
|
|
-
|
|
|
|
-In Godot, front-facing triangles are those that, when looking at the
|
|
|
|
-camera, are in clockwise order. Triangles that look Counter-clockwise
|
|
|
|
-when looking at the camera are not drawn (this helps to draw less, so
|
|
|
|
-the back-part of the objects is not drawn).
|
|
|
|
-
|
|
|
|
-To make it a little clearer, in the image below, the triangle **ABC**
|
|
|
|
-appears clock-wise when looked at from the *Front Camera*, but to the
|
|
|
|
-*Rear Camera* it appears counter-clockwise so it will not be drawn.
|
|
|
|
-
|
|
|
|
-.. image:: img/tutovec20.png
|
|
|
|
-
|
|
|
|
-Normals of triangles often are sided towards the direction they can be
|
|
|
|
-viewed from, so in this case, the normal of triangle ABC would point
|
|
|
|
-towards the front camera:
|
|
|
|
-
|
|
|
|
-.. image:: img/tutovec21.png
|
|
|
|
-
|
|
|
|
-So, to obtain N, the correct formula is:
|
|
|
|
-
|
|
|
|
-::
|
|
|
|
-
|
|
|
|
- # clockwise normal from triangle formula
|
|
|
|
- var N = (A-C).cross(A-B).normalized()
|
|
|
|
- # for counter-clockwise:
|
|
|
|
- # var N = (A-B).cross(A-C).normalized()
|
|
|
|
- var D = N.dot(A)
|
|
|
|
-
|
|
|
|
-Collision detection in 3D
|
|
|
|
-~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
-
|
|
|
|
-This is another bonus bit, a reward for being patient and keeping up
|
|
|
|
-with this long tutorial. Here is another piece of wisdom. This might
|
|
|
|
-not be something with a direct use case (Godot already does collision
|
|
|
|
-detection pretty well) but It's a really cool algorithm to understand
|
|
|
|
-anyway, because it's used by almost all physics engines and collision
|
|
|
|
-detection libraries :)
|
|
|
|
-
|
|
|
|
-Remember that converting a convex shape in 2D to an array of 2D planes
|
|
|
|
-was useful for collision detection? You could detect if a point was
|
|
|
|
-inside any convex shape, or if two 2D convex shapes were overlapping.
|
|
|
|
-
|
|
|
|
-Well, this works in 3D too, if two 3D polyhedral shapes are colliding,
|
|
|
|
-you won't be able to find a separating plane. If a separating plane is
|
|
|
|
-found, then the shapes are definitely not colliding.
|
|
|
|
-
|
|
|
|
-To refresh a bit a separating plane means that all vertices of polygon A
|
|
|
|
-are in one side of the plane, and all vertices of polygon B are in the
|
|
|
|
-other side. This plane is always one of the face-planes of either
|
|
|
|
-polygon A or polygon B.
|
|
|
|
-
|
|
|
|
-In 3D though, there is a problem to this approach, because it is
|
|
|
|
-possible that, in some cases a separating plane can't be found. This is
|
|
|
|
-an example of such situation:
|
|
|
|
-
|
|
|
|
-.. image:: img/tutovec22.png
|
|
|
|
|
|
+Pointing to a Target
|
|
|
|
+--------------------
|
|
|
|
|
|
-To avoid it, some extra planes need to be tested as separators, these
|
|
|
|
-planes are the cross product between the edges of polygon A and the
|
|
|
|
-edges of polygon B
|
|
|
|
|
|
+In the dot product section above, we saw how it could be used to find the
|
|
|
|
+angle between two vectors. However, in 3D this is not enough information.
|
|
|
|
+We also need to know what axis to rotate around. We can find that by
|
|
|
|
+calculating the cross product of the current facing direction and the
|
|
|
|
+target direction. The resulting perpendicular vector is the axis of
|
|
|
|
+rotation.
|
|
|
|
|
|
-.. image:: img/tutovec23.png
|
|
|
|
|
|
|
|
-So the final algorithm is something like:
|
|
|
|
|
|
+More Information
|
|
|
|
+~~~~~~~~~~~~~~~~
|
|
|
|
|
|
-::
|
|
|
|
|
|
+For more information on using vector math in Godot, see the following articles:
|
|
|
|
|
|
- var overlapping = true
|
|
|
|
-
|
|
|
|
- for p in planes_of_A:
|
|
|
|
- var all_out = true
|
|
|
|
- for v in points_of_B:
|
|
|
|
- if (p.distance_to(v) < 0):
|
|
|
|
- all_out = false
|
|
|
|
- break
|
|
|
|
-
|
|
|
|
- if (all_out):
|
|
|
|
- # a separating plane was found
|
|
|
|
- # do not continue testing
|
|
|
|
- overlapping = false
|
|
|
|
- break
|
|
|
|
-
|
|
|
|
- if (overlapping):
|
|
|
|
- # only do this check if no separating plane
|
|
|
|
- # was found in planes of A
|
|
|
|
- for p in planes_of_B:
|
|
|
|
- var all_out = true
|
|
|
|
- for v in points_of_A:
|
|
|
|
- if (p.distance_to(v) < 0):
|
|
|
|
- all_out = false
|
|
|
|
- break
|
|
|
|
-
|
|
|
|
- if (all_out):
|
|
|
|
- overlapping = false
|
|
|
|
- break
|
|
|
|
-
|
|
|
|
- if (overlapping):
|
|
|
|
- for ea in edges_of_A:
|
|
|
|
- for eb in edges_of_B:
|
|
|
|
- var n = ea.cross(eb)
|
|
|
|
- if (n.length() == 0):
|
|
|
|
- continue
|
|
|
|
-
|
|
|
|
- var max_A = -1e20 # tiny number
|
|
|
|
- var min_A = 1e20 # huge number
|
|
|
|
-
|
|
|
|
- # we are using the dot product directly
|
|
|
|
- # so we can map a maximum and minimum range
|
|
|
|
- # for each polygon, then check if they
|
|
|
|
- # overlap.
|
|
|
|
-
|
|
|
|
- for v in points_of_A:
|
|
|
|
- var d = n.dot(v)
|
|
|
|
- if (d > max_A):
|
|
|
|
- max_A = d
|
|
|
|
- if (d < min_A):
|
|
|
|
- min_A = d
|
|
|
|
-
|
|
|
|
- var max_B = -1e20 # tiny number
|
|
|
|
- var min_B = 1e20 # huge number
|
|
|
|
-
|
|
|
|
- for v in points_of_B:
|
|
|
|
- var d = n.dot(v)
|
|
|
|
- if (d > max_B):
|
|
|
|
- max_B = d
|
|
|
|
- if (d < min_B):
|
|
|
|
- min_B = d
|
|
|
|
-
|
|
|
|
- if (min_A > max_B or min_B > max_A):
|
|
|
|
- # not overlapping!
|
|
|
|
- overlapping = false
|
|
|
|
- break
|
|
|
|
-
|
|
|
|
- if (not overlapping):
|
|
|
|
- break
|
|
|
|
-
|
|
|
|
- if (overlapping):
|
|
|
|
- print("Polygons collided!")
|
|
|
|
-
|
|
|
|
-This was all! Hope it was helpful, and please give feedback and let know
|
|
|
|
-if something in this tutorial is not clear! You should be now ready for
|
|
|
|
-the next challenge... :ref:`doc_matrices_and_transforms`!
|
|
|
|
|
|
+- :ref:`doc_vectors_advanced`
|
|
|
|
+- :ref:`doc_matrices_and_transforms`
|