Vectores euclidianos en Flash
() translation by (you can also view the original English article)
Dos veces al mes, revisamos algunas de las publicaciones favoritas de nuestros lectores de la historia de Activetuts +. El tutorial retroactivo de esta semana, publicado por primera vez en abril, es una guía de vectores euclidianos: qué son, por qué los usarían y cómo implementarlos en Flash con AS3.
Los vectores euclidianos son objetos en geometría con ciertas propiedades que son muy útiles para desarrollar juegos. Se pueden ver como puntos, pero también tienen una magnitud y una dirección. Se representan como flechas que van desde el punto inicial hasta el punto final, y así es como las dibujaremos en este artículo.
Los vectores euclidianos se utilizan comúnmente en matemáticas y física para muchas cosas: pueden representar la velocidad, la aceleración y las fuerzas en física, o ayudar a demostrar muchos teoremas importantes en matemáticas. En este tutorial, aprenderá sobre vectores euclidianos y construirá una clase que puede usar en sus propios proyectos Flash.
Tenga en cuenta que los vectores euclidianos son diferentes a la clase Vector de ActionScript y también diferentes al dibujo vectorial.
Los vectores se pueden usar en el entorno Flash para ayudarlo a lograr tareas complejas que de otra manera requerirían mucho esfuerzo si se realizaran sin ellas. En este artículo aprenderás a usarlos en Flash, y aprenderás muchos trucos geniales con vectores.
Paso 1: Coordenadas cartesianas y coordenadas del flash
Antes de saltar a los vectores, introduzcamos el sistema de coordenadas de Flash. Probablemente esté familiarizado con el sistema de coordenadas cartesianas (incluso si no lo conoce por su nombre):

El sistema de Flash es muy similar. La única diferencia es que el eje y está al revés:

Cuando comenzamos a trabajar con vectores en flash, debemos recordar eso. Sin embargo, son buenas noticias: este sistema diferente no hace mucha diferencia. Trabajar con vectores será básicamente como trabajar con vectores en el sistema cartesiano.
Paso 2: definir un vector
Para el propósito de este tutorial, definiremos y trabajaremos con los puntos iniciales de todos los vectores como el punto de registro de la etapa, tal como se usan comúnmente en las matemáticas. Un vector se definirá como un punto común, pero tendrá propiedades de magnitud y ángulo. Eche un vistazo a algunos vectores de ejemplo definidos en el escenario:



Como
puede ver, un vector está representado por una flecha, y cada vector
tiene una cierta longitud (o magnitud) y puntos a lo largo de cierto
ángulo. La cola de cada vector está en el punto de registro (0,
0)
.
Crearemos una clase EuclideanVector simple para este tutorial, usando la clase Point para mantener las coordenadas del vector. Vamos a crear la clase básica de vectores ahora:
1 |
package
|
2 |
{
|
3 |
import flash.geom.Point; |
4 |
|
5 |
public class EuclideanVector |
6 |
{
|
7 |
public var position:Point; |
8 |
public var magnitude:Number; |
9 |
public var angle:Number; |
10 |
|
11 |
public function EuclideanVector(endPoint:Point) |
12 |
{
|
13 |
position = endPoint; |
14 |
}
|
15 |
}
|
16 |
}
|
Durante este tutorial, hablaremos sobre el sentido y la dirección de un vector. Tenga en cuenta que la dirección solo define una línea que "contiene" el vector. El sentido es lo que define de qué manera el vector señala a lo largo de esta línea.
Paso 3: inverso de un vector
En este tutorial usaremos la expresión "inverso de un vector". El inverso de un vector es otro vector con la misma magnitud y dirección, pero un sentido contrario. Eso se traduce en un vector con la señal opuesta a las coordenadas del primer vector. Entonces un vector con un punto final de (x, y) tendría un vector inverso con un punto final de (-x, -y).

Agreguemos una función a nuestra clase EuclideanVector para devolver el vector inverso:
1 |
public function inverse():EuclideanVector |
2 |
{
|
3 |
return new EuclideanVector(new Point(-position.x, -position.y)); |
4 |
}
|
Paso 4: operaciones básicas Adición
Ahora que hemos aprendido cómo definir un vector, aprendamos cómo agregar dos vectores: es tan simple como agregar sus coordenadas por separado. Mira esta imagen:



Si observa en la imagen, el resultado de la adición de dos vectores es otro vector, y puede ver que sus coordenadas son la suma de las coordenadas de los otros dos vectores. En el código, se vería así:
1 |
public function sum(otherVector:EuclideanVector):EuclideanVector |
2 |
{
|
3 |
position.x += otherVector.position.x; |
4 |
position.y += otherVector.position.y; |
5 |
|
6 |
return this; |
7 |
}
|
Entonces podemos decir que:
1 |
vecR == vec1.sum(vec2); |
Paso 5: operaciones básicas Resta
La resta funciona casi igual que la suma, pero en su lugar agregaremos el inverso del segundo vector al primer vector.



Ya se sabe cómo sumar dos vectores, así que aquí está el código para restar:
1 |
public function subtract(otherVector:EuclideanVector):EuclideanVector |
2 |
{
|
3 |
position.x -= otherVector.position.x; |
4 |
position.y -= otherVector.position.y; |
5 |
|
6 |
return this; |
7 |
}
|
Este código es extremadamente útil para obtener un vector que va del punto de un vector al punto de otro. Mira nuevamente la imagen y verás que esto es verdad. Se usará mucho en los ejemplos posteriores.
Paso 6: operaciones básicas Multiplicación por un número
La multiplicación entre un vector y un número (los números regulares se conocen como "escalares" en matemáticas vectoriales) da como resultado un vector que ha tenido una magnitud multiplicada por este número, pero apuntando en la misma dirección; está "estirado" si el escalar es mayor que 1, y aplastado si el escalar está entre 0 y 1. El sentido del nuevo vector será el mismo que el vector original si el escalar es positivo, o el opuesto si es negativo. Básicamente, este número "escala" el vector. Mira la imagen:

En el código, solo multiplicamos las coordenadas de un vector por el número, que luego escalará el vector:
1 |
public function multiply(number:Number):EuclideanVector |
2 |
{
|
3 |
position.x *= number; |
4 |
position.y *= number; |
5 |
|
6 |
return this; |
7 |
}
|
Paso 7: Obtener la Magnitud de un Vector
Para obtener la magnitud de un vector, utilizaremos el teorema de Pitágoras. Si olvidó lo que es, aquí hay un repaso rápido:

El código es muy simple:
1 |
public function magnitude():Number |
2 |
{
|
3 |
return Math.sqrt((position.x * position.x) + (position.y * position.y)); |
4 |
}
|
También debe eliminar la línea public var magnitude:Number
, ya que esto es lo que usaremos a partir de ahora.
La magnitud de un vector siempre será positiva, ya que es la raíz cuadrada de la suma de dos números positivos.
Paso 8: obtener el ángulo de un vector
El ángulo de un vector es el ángulo entre el eje xy la línea de dirección del vector. El ángulo se mide desde el eje x y girando en sentido antihorario hasta la línea de dirección en el sistema cartesiano:



Sin embargo, en el sistema de coordenadas de Flash, dado que el eje y está al revés, este ángulo se medirá girando en el sentido de las agujas del reloj:



Esto se puede calcular fácilmente usando el siguiente código. El ángulo se devolverá en radianes, en un rango de 0 a 2pi. Si no sabes qué son los radianes o cómo usarlos, este tutorial de Michael James Williams te ayudará mucho.
1 |
public function angle():Number |
2 |
{
|
3 |
var angle:Number = Math.atan2(position.y, position.x); |
4 |
|
5 |
if (angle < 0) |
6 |
{
|
7 |
angle += Math.PI * 2; |
8 |
}
|
9 |
|
10 |
return angle; |
11 |
}
|
Paso 9: producto escalar
El producto escalar entre dos vectores es un número aparentemente sin significado, pero tiene dos usos útiles. Primero veamos cómo se puede calcular el producto escalar:



Pero también se puede obtener por las coordenadas de cada vector:



El producto escalar puede decirnos mucho sobre el ángulo entre los vectores: si es positivo, entonces el ángulo oscila entre 0 y 90 grados. Si es negativo, el ángulo oscila entre 90 y 180 grados. Si es cero, el ángulo es de 90 grados. Eso sucede porque en la primera fórmula, solo el coseno es responsable de darle al producto escalar una "señal": las magnitudes son siempre positivas. Pero sabemos que un coseno positivo significa que el ángulo oscila entre 0 y 90 grados, y así sucesivamente para los cosenos negativos y cero.
El producto de puntos también se puede usar para representar la longitud de un vector en la dirección del otro vector. Piense en ello como una proyección. Esto resulta extremadamente útil en cosas como el Teorema de Separación de Ejes (SAT) y su implementación en AS3 para la detección de colisiones y la respuesta en juegos.
Aquí está el código práctico para obtener el producto escalar entre dos vectores:
1 |
public function dot(otherVector:EuclideanVector):Number |
2 |
{
|
3 |
return (position.x * otherVector.position.x) + (position.y * otherVector.position.y); |
4 |
}
|
Paso 10: Ángulo más pequeño entre vectores
El ángulo entre vectores, como se ve en el Paso 9, puede ser dado por el producto de puntos. Aquí es cómo calcularlo:
1 |
public function angleBetween(otherVector:EuclideanVector):Number |
2 |
{
|
3 |
return Math.acos(dot(otherVector) / (magnitude() * otherVector.magnitude())); |
4 |
}
|
Paso 11: Ángulo a distancia entre vectores
También hay otra forma de calcular el ángulo, que da resultados entre -pi y pi y siempre calcula el ángulo que va del primer vector al segundo vector; esto es útil cuando desea integrarse fácilmente con la rotación de un objeto de visualización (que va de -180 a 180).
El método funciona obteniendo el ángulo para ambos vectores, luego restando los ángulos y trabajando en el resultado.



El código:
1 |
public function rangedAngleBetween(otherVector:EuclideanVector):Number |
2 |
{
|
3 |
var firstAngle:Number; |
4 |
var secondAngle:Number; |
5 |
|
6 |
var angle:Number; |
7 |
|
8 |
firstAngle = Math.atan2(otherVector.position.y, otherVector.position.x); |
9 |
secondAngle = Math.atan2(position.y, position.x); |
10 |
|
11 |
angle = secondAngle - firstAngle; |
12 |
|
13 |
while (angle > Math.PI) |
14 |
angle -= Math.PI * 2; |
15 |
while (angle < -Math.PI) |
16 |
angle += Math.PI * 2; |
17 |
|
18 |
return angle; |
19 |
}
|
Tenga en cuenta que este ángulo devuelve positivo si secondAngle es más alto que firstAngle, por lo que el orden en el que obtiene el ángulo de rango afectará el resultado.
Paso 12: normalizando un vector
La
normalización de un vector significa hacer que su magnitud sea igual a
1, mientras se preserva la dirección y el sentido del vector. Para hacer
eso, multiplicamos el vector por 1/magnitude
. De esta forma, su
magnitud se reducirá o aumentará a 1.
1 |
public function normalize():EuclideanVector |
2 |
{
|
3 |
var m:Number = magnitude(); |
4 |
position.x /= m; |
5 |
position.y /= m; |
6 |
|
7 |
return this; |
8 |
}
|
Paso 13: Obteniendo el normal de un vector
El normal de un vector es otro vector que forma un ángulo de 90 grados con respecto al primero. Se puede calcular mediante las siguientes fórmulas:

Las fórmulas se basan en el hecho de que, como la normalidad es siempre perpendicular a un vector, solo necesitamos cambiar el orden de las coordenadas xey e invertir una de ellas para obtener una normal. La siguiente imagen muestra el proceso:

En la imagen, Vec es el vector original, Vec2 es el vector con las coordenadas intercambiadas de Vec, y Vec3 es un vector con la coordenada y negativa de Vec2. Ang y Ang2 son variables, pero el ángulo entre Vec y Vec3 es siempre de 90 grados.
Y el código es simple
1 |
public function normalRight():EuclideanVector |
2 |
{
|
3 |
return new EuclideanVector(new Point(-position.y, position.x)); |
4 |
}
|
5 |
|
6 |
public function normalLeft():EuclideanVector |
7 |
{
|
8 |
return new EuclideanVector(new Point(position.y, -position.x)); |
9 |
}
|
Paso 14: girando un vector
Para rotar un vector, asumimos que la posición (0, 0) (su punto inicial) será el centro de rotación. El punto girado está dado por la fórmula:



Esta fórmula se obtiene aplicando una matriz de rotación a ese vector. Estaríamos yendo más allá del alcance de este tutorial si entramos en la matriz y cómo funciona, así que simplemente dejaré la fórmula aquí.
El código es más o menos el mismo:
1 |
public function rotate(angleInRadians:Number):EuclideanVector |
2 |
{
|
3 |
var newPosX:Number = (position.x * Math.cos(angleInRadians)) - (position.y * Math.sin(angleInRadians)); |
4 |
var newPosY:Number = (position.x * Math.sin(angleInRadians)) + (position.y * Math.cos(angleInRadians)); |
5 |
|
6 |
position.x = newPosX; |
7 |
position.y = newPosY; |
8 |
|
9 |
return this; |
10 |
}
|
Este es el final de nuestras operaciones básicas de vectores. Lo que verá a continuación es maneras de usar esta clase para hacer cosas interesantes. Aquí está nuestra clase hasta ahora:
1 |
package
|
2 |
{
|
3 |
import flash.geom.Point; |
4 |
|
5 |
public class EuclideanVector |
6 |
{
|
7 |
public var position:Point; |
8 |
public var angle:Number; |
9 |
|
10 |
public function EuclideanVector(endPoint:Point) |
11 |
{
|
12 |
position = endPoint; |
13 |
}
|
14 |
|
15 |
public function inverse():EuclideanVector |
16 |
{
|
17 |
return new EuclideanVector(new Point(-position.x, -position.y)); |
18 |
}
|
19 |
|
20 |
public function sum(otherVector:EuclideanVector):EuclideanVector |
21 |
{
|
22 |
position.x += otherVector.position.x; |
23 |
position.y += otherVector.position.y; |
24 |
|
25 |
return this; |
26 |
}
|
27 |
|
28 |
public function subtract(otherVector:EuclideanVector):EuclideanVector |
29 |
{
|
30 |
position.x -= otherVector.position.x; |
31 |
position.y -= otherVector.position.y; |
32 |
|
33 |
return this; |
34 |
}
|
35 |
|
36 |
public function multiply(number:Number):EuclideanVector |
37 |
{
|
38 |
position.x *= number; |
39 |
position.y *= number; |
40 |
|
41 |
return this; |
42 |
}
|
43 |
|
44 |
public function magnitude():Number |
45 |
{
|
46 |
return Math.sqrt((position.x * position.x) + (position.y * position.y)); |
47 |
}
|
48 |
|
49 |
public function angle():Number |
50 |
{
|
51 |
var angle:Number = Math.atan2(position.y, position.x); |
52 |
|
53 |
if (angle < 0) |
54 |
{
|
55 |
angle += Math.PI * 2; |
56 |
}
|
57 |
|
58 |
return angle; |
59 |
}
|
60 |
|
61 |
public function dot(otherVector:EuclideanVector):Number |
62 |
{
|
63 |
return (position.x * otherVector.position.x) + (position.y * otherVector.position.y); |
64 |
}
|
65 |
|
66 |
public function angleBetween(otherVector:EuclideanVector):Number |
67 |
{
|
68 |
return Math.acos(dot(otherVector) / (magnitude() * otherVector.magnitude())); |
69 |
}
|
70 |
|
71 |
public function rangedAngleBetween(otherVector:EuclideanVector):Number |
72 |
{
|
73 |
var firstAngle:Number; |
74 |
var secondAngle:Number; |
75 |
|
76 |
var angle:Number; |
77 |
|
78 |
firstAngle = Math.atan2(otherVector.position.y, otherVector.position.x); |
79 |
secondAngle = Math.atan2(position.y, position.x); |
80 |
|
81 |
angle = secondAngle - firstAngle; |
82 |
|
83 |
while (angle > Math.PI) |
84 |
angle -= Math.PI * 2; |
85 |
while (angle < -Math.PI) |
86 |
angle += Math.PI * 2; |
87 |
|
88 |
return angle; |
89 |
}
|
90 |
|
91 |
public function normalize():EuclideanVector |
92 |
{
|
93 |
position.x /= magnitude(); |
94 |
position.y /= magnitude(); |
95 |
|
96 |
return this; |
97 |
}
|
98 |
|
99 |
public function normalRight():EuclideanVector |
100 |
{
|
101 |
return new EuclideanVector(new Point(-position.y, position.x)); |
102 |
}
|
103 |
|
104 |
public function normalLeft():EuclideanVector |
105 |
{
|
106 |
return new EuclideanVector(new Point(position.y, -position.x)); |
107 |
}
|
108 |
|
109 |
public function rotate(angleInRadians:Number):EuclideanVector |
110 |
{
|
111 |
var newPosX:Number = (position.x * Math.cos(angleInRadians)) - (position.y * Math.sin(angleInRadians)); |
112 |
var newPosY:Number = (position.x * Math.sin(angleInRadians)) + (position.y * Math.cos(angleInRadians)); |
113 |
|
114 |
position.x = newPosX; |
115 |
position.y = newPosY; |
116 |
|
117 |
return this; |
118 |
}
|
119 |
}
|
120 |
}
|
OK, hemos cubierto la construcción de la clase vectorial, ahora tomemos una decisión de utilizarla.
Paso 15: determinar si un punto está dentro de un polígono
La acción comienza aquí. Determinar si un punto se encuentra dentro de un polígono o no es un tema muy interesante, y hay muchos métodos para lograrlo. En este artículo, presentaré los tres métodos que generalmente se usan:
- El Número de cruce o el algoritmo de regla par o impar, que determina si un punto está dentro de un polígono a partir del número de aristas que cruza un "rayo" desde el punto hasta el infinito.
- El algoritmo del número de cuerda, que da la respuesta basada en la suma de todos los ángulos formados entre los vértices consecutivos de un polígono y el punto a verificar.
- El algoritmo de polígono convexo, que, como su nombre lo indica, solo funciona para polígonos convexos y se basa en si un punto está o no en un cierto "lado" de cada borde del polígono.
Todos estos algoritmos dependerán del hecho de que conoces las coordenadas de los vértices (esquinas) que definen el polígono.
Paso 16: El número de cruce o el algoritmo de regla par impar
Este algoritmo se puede usar para cualquier forma. Esto es lo que lees: cualquier forma, tiene agujeros o no, ya sea convexo o no. Se basa en el hecho de que cualquier rayo que se proyecte desde el punto que deseas ver hasta el infinito cruzará un número par de aristas si el punto está fuera de la forma, o un número impar de aristas si el punto está dentro de la forma. Esto puede ser probado por el teorema de la curva de Jordan, lo que implica que tendrá que cruzar un límite entre una región y otra región si desea pasar de una a otra. En nuestro caso, nuestras regiones están "dentro de la forma" y "fuera de la forma".



El código para este algoritmo es el siguiente:
1 |
public function isPointInsideShape1(point:EuclideanVector, shapeVertices:Vector.<EuclideanVector>):Boolean |
2 |
{
|
3 |
var numberOfSides:int = shapeVertices.length; |
4 |
|
5 |
var i:int = 0; |
6 |
var j:int = numberOfSides - 1; |
7 |
|
8 |
var oddNodes:Boolean = false; |
9 |
|
10 |
while (i < numberOfSides) |
11 |
{
|
12 |
if ((shapeVertices[i].position.y < point.position.y && shapeVertices[j].position.y >= point.position.y) || |
13 |
(shapeVertices[j].position.y < point.position.y && shapeVertices[i].position.y >= point.position.y)) |
14 |
{
|
15 |
if (shapeVertices[i].position.x + (((point.position.y - shapeVertices[i].position.y) / (shapeVertices[j].position.y - shapeVertices[i].position.y)) * |
16 |
(shapeVertices[j].position.x - shapeVertices[i].position.x)) < point.position.x) |
17 |
{
|
18 |
oddNodes = !oddNodes; |
19 |
}
|
20 |
}
|
21 |
|
22 |
j = i; |
23 |
|
24 |
i++; |
25 |
}
|
26 |
|
27 |
return oddNodes; |
28 |
}
|
Devolverá false
si el punto no está dentro de la forma o true
si el punto está dentro de la forma.
Paso 17: Algoritmo del número de cuerda
El algoritmo de número de cuerda usa la suma de todos los ángulos hechos entre el punto a verificar y cada par de puntos que definen el polígono. Si la suma está cerca de 2pi, entonces el punto que se verifica está dentro del vector. Si está cerca de 0, entonces el punto está afuera.

1 |
public function isPointInsideShape2(point:EuclideanVector, shapeVertices:Vector.<EuclideanVector>):Boolean |
2 |
{
|
3 |
var numberOfSides:int = shapeVertices.length; |
4 |
|
5 |
var i:int = 0; |
6 |
var angle:Number = 0; |
7 |
|
8 |
var rawAngle:Number = 0; |
9 |
|
10 |
var firstVector:EuclideanVector; |
11 |
var secondVector:EuclideanVector; |
12 |
|
13 |
while(i < numberOfSides) |
14 |
{
|
15 |
firstVector = new EuclideanVector(new Point(shapeVertices[i].position.x - point.position.x, shapeVertices[i].position.y - point.position.y)); |
16 |
secondVector = new EuclideanVector(new Point(shapeVertices[(i + 1) % numberOfSides].position.x - point.position.x, shapeVertices[(i + 1) % numberOfSides].position.y - point.position.y)); |
17 |
|
18 |
angle += secondVector.rangedAngleBetween(firstVector); |
19 |
|
20 |
i++; |
21 |
}
|
22 |
|
23 |
if(Math.abs(angle) < Math.PI) |
24 |
return false; |
25 |
else
|
26 |
return true; |
27 |
}
|
El código usa el ángulo entre vectores y da espacio para imprecisiones: observe cómo estamos verificando los resultados de la suma de todos los ángulos. No verificamos si el ángulo es exactamente cero o 2pi. En cambio, verificamos si es menor que pi y mayor que pi, un valor mediano considerable.
Paso 18: Algoritmo del polígono cóncavo
El algoritmo de polígono cóncavo se basa en el hecho de que, para un polígono cóncavo, un punto dentro de él siempre está a la izquierda de los bordes (si los estamos bucleando en sentido contrario a las agujas del reloj) oa la derecha de los bordes (si estamos pasando por ellos en el sentido de las agujas del reloj).

Imagínese de pie en una habitación con la forma de la imagen de arriba, y camine alrededor de los bordes de la misma con su mano izquierda arrastrándose a lo largo de la pared. En el punto a lo largo de la pared donde estás más cerca del punto que te interesa, si está a tu derecha, entonces debe estar dentro de la habitación; si está a la izquierda, entonces debe estar afuera.
El problema radica en determinar si un punto está a la izquierda oa la derecha de un borde (que básicamente es un vector). Esto se hace a través de la siguiente fórmula:



Esa fórmula devuelve un número menor que 0 para los puntos a la derecha del borde, y mayor que 0 para los puntos a la izquierda del mismo. Si el número es igual a 0, el punto se encuentra en el borde y se considera dentro de la forma. El código es el siguiente:
1 |
public function isPointInsideShape3(point:EuclideanVector, shapeVertices:Vector.<EuclideanVector>):Boolean |
2 |
{
|
3 |
var numberOfSides:int = shapeVertices.length; |
4 |
|
5 |
var i:int = 0; |
6 |
|
7 |
var firstEdgePoint:EuclideanVector; |
8 |
var secondEdgePoint:EuclideanVector; |
9 |
|
10 |
var leftOrRightSide:Boolean; |
11 |
|
12 |
while(i < numberOfSides) |
13 |
{
|
14 |
firstEdgePoint = shapeVertices[i]; |
15 |
secondEdgePoint = shapeVertices[(i + 1) % numberOfSides]; |
16 |
|
17 |
if(i == 0) |
18 |
{
|
19 |
// Determining if the point is to the left or to the right of first edge
|
20 |
// true for left, false for right
|
21 |
|
22 |
leftOrRightSide = ((point.position.y - firstEdgePoint.position.y) * (secondEdgePoint.position.x - firstEdgePoint.position.x)) - ((point.position.x - firstEdgePoint.position.x) * (secondEdgePoint.position.y - firstEdgePoint.position.y)) > 0; |
23 |
}
|
24 |
else
|
25 |
{
|
26 |
// Now all edges must be on the same side
|
27 |
|
28 |
if(leftOrRightSide && ((point.position.y - firstEdgePoint.position.y) * (secondEdgePoint.position.x - firstEdgePoint.position.x)) - ((point.position.x - firstEdgePoint.position.x) * (secondEdgePoint.position.y - firstEdgePoint.position.y)) < 0) |
29 |
{
|
30 |
// Not all edges are on the same side!
|
31 |
|
32 |
return false; |
33 |
}
|
34 |
else if(!leftOrRightSide && ((point.position.y - firstEdgePoint.position.y) * (secondEdgePoint.position.x - firstEdgePoint.position.x)) - ((point.position.x - firstEdgePoint.position.x) * (secondEdgePoint.position.y - firstEdgePoint.position.y)) > 0) |
35 |
{
|
36 |
// Not all edges are on the same side!
|
37 |
|
38 |
return false; |
39 |
}
|
40 |
}
|
41 |
|
42 |
i++; |
43 |
}
|
44 |
|
45 |
// We looped through all vertices and didn't detect different sides
|
46 |
|
47 |
return true; |
48 |
}
|
Este código funciona independientemente de si tienes los vértices de la forma definidos en sentido horario o antihorario.
Paso 19: Ray Casting
Ray casting es una técnica que se usa a menudo para detectar colisiones y renderizar. Consiste en un rayo que se lanza de un punto a otro (o hasta el infinito). Este rayo está hecho de puntos o vectores, y generalmente se detiene cuando golpea un objeto o el borde de la pantalla. De forma similar a los algoritmos de punto en forma, hay muchas maneras de lanzar rayos, y veremos dos de ellos en esta publicación:
- El algoritmo de línea de Bresenham, que es una manera muy rápida de determinar puntos cercanos que darían una aproximación de una línea entre ellos.
- El método DDA (Digital Differential Analyzer), que también se usa para crear una línea.
En los próximos dos pasos analizaremos ambos métodos. Después de eso, veremos cómo hacer que nuestro rayo se detenga cuando golpea un objeto. Esto es muy útil cuando necesita detectar colisión contra objetos que se mueven rápidamente.
Paso 20: El algoritmo de la línea de Bresenham
Este algoritmo se usa muy a menudo en gráficos de computadora, y depende de la convención de que la línea siempre se creará apuntando hacia la derecha y hacia abajo. (Si se debe crear una línea en las direcciones hacia arriba y hacia la izquierda, todo se invierte más adelante). Vamos a entrar en el código:
1 |
public function createLineBresenham(startVector:EuclideanVector, endVector:EuclideanVector):Vector.<EuclideanVector> |
2 |
{
|
3 |
var points:Vector.<EuclideanVector> = new Vector.<EuclideanVector>(); |
4 |
|
5 |
var steep:Boolean = Math.abs(endVector.position.y - startVector.position.y) > Math.abs(endVector.position.x - startVector.position.x); |
6 |
|
7 |
var swapped:Boolean = false; |
8 |
|
9 |
if (steep) |
10 |
{
|
11 |
startVector = new EuclideanVector(new Point(startVector.position.y, startVector.position.x)); |
12 |
endVector = new EuclideanVector(new Point(endVector.position.y, endVector.position.x)); |
13 |
}
|
14 |
|
15 |
// Making the line go downward
|
16 |
if (startVector.position.x > endVector.position.x) |
17 |
{
|
18 |
var temporary:Number = startVector.position.x; |
19 |
|
20 |
startVector.position.x = endVector.position.x; |
21 |
|
22 |
endVector.position.x = temporary; |
23 |
|
24 |
temporary = startVector.position.y; |
25 |
|
26 |
startVector.position.y = endVector.position.y |
27 |
|
28 |
endVector.position.y = temporary; |
29 |
|
30 |
swapped = true; |
31 |
}
|
32 |
|
33 |
var deltaX:Number = endVector.position.x - startVector.position.x; |
34 |
var deltaY:Number = Math.abs(endVector.position.y - startVector.position.y); |
35 |
|
36 |
var error:Number = deltaX / 2; |
37 |
|
38 |
var currentY:Number = startVector.position.y; |
39 |
|
40 |
var step:int; |
41 |
|
42 |
if (startVector.position.y < endVector.position.y) |
43 |
{
|
44 |
step = 1; |
45 |
}
|
46 |
else
|
47 |
{
|
48 |
step = -1; |
49 |
}
|
50 |
|
51 |
var iterator:int = startVector.position.x; |
52 |
|
53 |
while (iterator < endVector.position.x) |
54 |
{
|
55 |
if (steep) |
56 |
{
|
57 |
points.push(new EuclideanVector(new Point(currentY, iterator))); |
58 |
}
|
59 |
else
|
60 |
{
|
61 |
points.push(new EuclideanVector(new Point(iterator, currentY))); |
62 |
}
|
63 |
|
64 |
error -= deltaY; |
65 |
|
66 |
if (error < 0) |
67 |
{
|
68 |
currentY += step; |
69 |
error += deltaX; |
70 |
}
|
71 |
|
72 |
iterator++; |
73 |
}
|
74 |
|
75 |
if (swapped) |
76 |
{
|
77 |
points.reverse(); |
78 |
}
|
79 |
|
80 |
return points; |
81 |
}
|
El código producirá un vector AS3 de vectores euclidianos que formarán la línea. Con este Vector, más adelante podemos verificar colisiones.
Paso 21: El método DDA
Una implementación del Analizador Diferencial Digital se usa para interpolar variables entre dos puntos. A diferencia del algoritmo de línea de Bresenham, este método solo creará vectores en posiciones enteras para simplificar. Aquí está el código:
1 |
public function createLineDDA(startVector:EuclideanVector, endVector:EuclideanVector):Vector.<EuclideanVector> |
2 |
{
|
3 |
var points:Vector.<EuclideanVector> = new Vector.<EuclideanVector>(); |
4 |
|
5 |
var dx:Number; |
6 |
var dy:Number; |
7 |
|
8 |
var _x:Number = startPoint.position.x; |
9 |
var _y:Number = startPoint.position.y; |
10 |
|
11 |
var m:Number; |
12 |
|
13 |
var i:int; |
14 |
|
15 |
dx = endPoint.position.x - startPoint.position.x; |
16 |
dy = endPoint.position.y - startPoint.position.y; |
17 |
|
18 |
if (Math.abs(dx) >= Math.abs(dy)) |
19 |
m = Math.abs(dx); |
20 |
else
|
21 |
m = Math.abs(dy); |
22 |
|
23 |
points.push(new EuclideanVector(new Point(int(_x), int(_y)))); |
24 |
|
25 |
i = 1; |
26 |
|
27 |
while (i <= m) |
28 |
{
|
29 |
_x += dx / m; |
30 |
_y += dy / m; |
31 |
|
32 |
points.push(new EuclideanVector(new Point(int(_x), int(_y)))); |
33 |
|
34 |
i++; |
35 |
}
|
36 |
|
37 |
return points; |
38 |
}
|
Este código también devolverá un vector AS3 de vectores euclidianos.
Paso 22: Verificar colisiones usando rayos
Verificar la colisión por
rayos es muy simple. Como
un rayo consta de muchos vectores, comprobaremos las colisiones entre
cada vector y una forma, hasta que se detecte uno o hasta que se alcance
el final. En el siguiente código, shapeToCheck
tendrá una
forma como las que hemos estado usando en los pasos 13-16. Aquí está el
código:
1 |
public function checkRayCollision(ray:Vector.<EuclideanVector>, shape:Vector.<EuclideanVector>):Boolean |
2 |
{
|
3 |
var rayLength:int = ray.length; |
4 |
|
5 |
var i:int = 0; |
6 |
|
7 |
while(i < rayLength) |
8 |
{
|
9 |
if(isPointInsideShape1(ray[i], shape)) |
10 |
{
|
11 |
return true; |
12 |
}
|
13 |
|
14 |
i++; |
15 |
}
|
16 |
|
17 |
return false; |
18 |
}
|
Puede usar cualquier función de punto dentro de la forma con la que se sienta cómodo, ¡pero preste atención a las limitaciones de la última!
Conclusión
¡Estás listo para comenzar a utilizar este conocimiento en todas partes ahora! Será útil muchas veces y le ahorrará muchos cálculos adicionales cuando intente hacer cosas más complejas en Flash.