En mi tutorial anterior sobre detección de colisiones entre un círculo y una línea, cubrí la proyección en una línea usando el producto de puntos de un vector. En este tutorial, veremos el producto de punto perpendicular y lo usaremos para predecir el punto de intersección para dos líneas.
Echemos un vistazo al resultado final en el que trabajaremos. Use las teclas de flecha izquierda y derecha para dirigir la nave (triángulo) y presione hacia arriba para aumentar la velocidad temporalmente. Si el punto de colisión futuro proyectado está en la pared (la línea), se pintará un punto rojo en ella. Para una colisión que "ya" sucedió (es decir, habría ocurrido en el pasado, según la dirección actual), un punto rojo todavía estará pintado pero ligeramente transparente.
También puede hacer clic con el ratón y arrastrar los puntos negros para mover la pared. Tenga en cuenta que no solo predecimos la ubicación de la colisión, sino también el tiempo.
Antes de entrar en el tema, hagamos una revisión. Aquí está la ecuación del producto de puntos (anteriormente cubierto aquí):
Y aquí está la definición de producto de punto perpendicular extraída de Wolfram:
Ahora, para ayudarnos a formar una imagen mental, preparé la imagen a continuación. Estoy seguro de que en este punto usted es capaz de derivar los componentes verticales y horizontales de un vector, por lo que los componentes relacionados con el seno y el coseno no deberían ser un desafío.
Sustituyamos ambos componentes por su equivalente. He usado A con un sombrero para representar el vector unitario de A (es decir, un vector que apunta en la misma dirección que A, pero tiene una magnitud de exactamente 1). Otro detalle es que la perpendicular de B es en realidad la normal correcta de B: más en las normales, el siguiente paso.
En el diagrama de arriba podemos ver que la proyección de B en A producirá | B | * cos (theta)
. Pero ¿por qué la proyección de la producción normal de B | B | * sin (theta)
?
Para entender mejor esto, he incluido una demostración en Flash a continuación. Haga clic y arrastre la punta de flecha negra. A medida que lo mueva suavemente, notará que también sigue su eje perpendicular. A medida que giren, las líneas rojas en negrita también serán animadas. Tenga en cuenta que estas dos longitudes son iguales, de ahí la ecuación del producto de punto perpendicular.
Las normas, por definición, se encuentran en una línea perpendicular que cruza su línea de interés. Tratemos de imaginar estas líneas en un plano geométrico..
El sistema de coordenadas cartesiano se utiliza en el diagrama de arriba. B es la normal izquierda y C la normal derecha. Vemos que el componente x de B es negativo (porque apunta a la izquierda) y el componente y de C es negativo (porque apunta hacia abajo).
Pero echa un vistazo a las similitudes entre B y C. Sus componentes x e y son los mismos que los de A, excepto el swizzled. La diferencia es solo la posición del signo. Así llegamos a una conclusión por la imagen de abajo..
Tenga en cuenta que nos estamos refiriendo específicamente a la cartesiano Sistema de coordenadas en este ejemplo. El eje y del espacio de coordenadas de Flash es un reflejo del cartesiano, lo que resulta en un intercambio entre la normal izquierda y la derecha.
Para averiguar el punto de colisión del vector k en el plano A, vincularemos la cola de k con un punto arbitrario en el plano A primero. Para el caso a continuación, el vector j es el vector de enlace; luego obtenemos la proyección perpendicular de k y j en el plano A.
El punto rojo en la imagen de abajo es el punto de colisión. Y espero que puedan ver el triángulo similar en el diagrama a continuación.
Entonces, dados los tres componentes anteriores, podemos usar el concepto de relación para deducir la longitud entre los puntos rojo y azul. Finalmente, establecemos la magnitud del vector k a dicha longitud y tenemos nuestro punto de colisión!
Así que aquí viene la implementación de ActionScript. He incluido una demostración a continuación. Intenta mover las puntas de las flechas para que ambas líneas se crucen. Un pequeño punto negro marcará el punto de intersección de las líneas. Tenga en cuenta que estos segmentos no necesariamente se intersecan, pero las líneas infinitas que representan son.
Aquí está el guión que hace los cálculos. Revisa Basic.as
en la fuente de descarga para el script completo.
función privada recalcular (): void reorient (); / * Explique: * v1 y v2 son vectores para representar ambos segmentos de línea * v1 une set1b (cola) a set1a (cabecera) - análogo al vector k en el diagrama * v2 une set2b (cola) a set2a (cabecera) * toV2b es vector análogo al del vector j en el diagrama * / var perp1: Number = v1.perpProduct (v2.normalise ()); var toV2b: Vector2D = nuevo Vector2D (set2b.x - set1b.x, set2b.y - set1b.y); var perp2: Number = toV2b.perpProduct (v2.normalise ()); / * Explicación: * la longitud se calcula a partir de la proporción de triángulos similares * luego se usa como magnitud para un vector * que apunta en la dirección de v1 * / var length: Number = perp2 / perp1 * v1.getMagnitude (); var length_v1: Vector2D = v1.clone (); length_v1.setMagnitude (length); / * Explica * amplía para localizar la ubicación exacta del punto de colisión * / intersec.x = set1b.x + length_v1.x; intersec.y = set1b.y + length_v1.y;
Así que espero que el primer enfoque que he presentado sea fácil de entender. Entiendo que el rendimiento para obtener el punto de intersección es importante, por lo que a continuación ofreceré enfoques alternativos, aunque eso requerirá algunas revisiones de matemáticas. Tengan paciencia conmigo!
Primero, hablemos de las ecuaciones de línea. Hay varias formas de ecuación de línea, pero solo tocaremos dos de ellas en este tutorial:
He incluido la imagen de abajo para ayudarte a recordar. Los interesados en esto pueden referirse a esta entrada en Wikipedia..
Antes de realizar cualquier manipulación en dos ecuaciones de línea, primero debemos derivar estas ecuaciones de línea. Consideremos el escenario donde nos dan coordenadas de dos puntos. p1 (a, b)
. y p2 (c, d)
. Podemos formar una ecuación de línea que conecta estos dos puntos desde los gradientes:
Luego, usando esta ecuación, podemos derivar las constantes A, B y C para la forma estándar:
A continuación, podemos proceder a resolver ecuaciones lineales simultáneas..
Ahora que podemos formar ecuaciones de línea, procedamos a tomar dos ecuaciones de línea y resolverlas simultáneamente. Dadas estas dos ecuaciones de línea:
Presentaré estos coeficientes de acuerdo con la forma general Ax + By = C.
UNA | segundo | do |
mi | F | sol |
PAG | Q | R |
Para obtener el valor de y, hacemos lo siguiente:
UNA | segundo | do | Multiplicar por |
mi | F | sol | PAG |
PAG | Q | R | mi |
Y llegamos a la siguiente tabla..
UNA | segundo | do |
EP | FP | Médico de familia |
EDUCACIÓN FÍSICA | QE | RE |
Después de restar dos ecuaciones, llegamos a:
Pasando a obtener x:
UNA | segundo | do | Multiplicar por |
mi | F | sol | Q |
PAG | Q | R | F |
Llegamos a la siguiente tabla.
UNA | segundo | do |
Ecualizador | FQ | GQ |
PF | QF | RF |
Después de restar las dos ecuaciones, llegamos a:
Vamos a reorganizar más y.
Así llegamos al punto de intersección de x e y. Notamos que comparten el mismo denominador..
Ahora que hemos desarrollado las operaciones matemáticas y obtenido el resultado, simplemente desplume los valores y tenemos el punto de intersección.
Aquí está la implementación de Actionscript. Por lo tanto, todas las operaciones vectoriales se reducen a aritmética simple, pero requerirá algunas operaciones de álgebra inicialmente.
función privada recalcular (): void reorient (); var E: Number = set1b.y - set1a.y; var F: Number = set1a.x - set1b.x; var G: Number = set1a.x * set1b.y - set1a.y * set1b.x; var P: Number = set2b.y - set2a.y; var Q: Number = set2a.x - set2b.x; var R: Número = set2a.x * set2b.y - set2a.y * set2b.x; denominador var: Número = (E * Q - P * F); intersec.x = (G * Q - R * F) / denominador; intersec.y = (R * E - G * P) / denominador;
Por supuesto, es el mismo resultado que la demostración anterior, solo con menos matemática involucrada y sin uso del Vector2D
clase.
Otra alternativa para resolver este problema es mediante el uso de matrices matemáticas. De nuevo, invito a los lectores interesados a sumergirse en la conferencia del profesor Wildberger sobre ecuaciones de líneas. Aquí, simplemente pasaremos el concepto rápidamente..
Según el profesor Wildberger, hay dos marcos que podemos adoptar:
Vayamos primero por el cartesiano. Echa un vistazo a la imagen de abajo.
Tenga en cuenta que la matriz T y S contienen valores constantes. Lo que se desconoce es A. Por lo tanto, reorganizar la ecuación matricial en términos de A nos dará el resultado. Sin embargo, debemos obtener la matriz inversa de T.
Aquí está la implementación de lo anterior con ActionScript:
función privada recalcular (): void reorient (); var E: Number = set1b.y - set1a.y; var F: Number = set1a.x - set1b.x; var G: Number = set1a.x * set1b.y - set1a.y * set1b.x; var P: Number = set2b.y - set2a.y; var Q: Number = set2a.x - set2b.x; var R: Número = set2a.x * set2b.y - set2a.y * set2b.x; var T: Matriz = Matriz nueva (E, P, F, Q); T.invert (); var S: Matriz = Matriz nueva (); S.a = G; S.b = R; S.concat (T); // multiplicando la matriz intersec.x = S.a; intersec.y = S.b;
Por último, está la forma paramétrica de la ecuación de línea, y trataremos de resolverla de nuevo a través de matematicas matriciales.
Nos gustaría obtener el punto de intersección. Dada toda la información a excepción de tu
y v
que intentamos encontrar, reescribiremos ambas ecuaciones en forma de matriz y las resolveremos.
Así que de nuevo, realizamos manipulaciones matriciales para llegar a nuestro resultado..
Así que aquí está la implementación de la forma de matriz:
rivate function recalculation (): void reorient (); / * Explique: * r, s en realidad se refieren a componentes de v2 normalizados * p, q en realidad se refieren a componentes de v1 normalizados * / var norm_v2: Vector2D = v2.normalise (); var norm_v1: Vector2D = v1.normalise (); var a_c: Number = set1b.x - set2b.x; var b_d: Number = set1b.y - set2b.y; var R: Matriz = Matriz nueva; R.a = norm_v2.x; R.c = norm_v1.x; R.b = norm_v2.y; R.d = norm_v1.y; R.invert (); var L: Matriz = Matriz nueva; L.a = a_c; L.b = b_d; L.concat (R); intersec.x = set2b.x + L.a * norm_v2.x; intersec.y = set2b.y + L.a * norm_v2.y;
Hemos cubierto cuatro enfoques para resolver este pequeño problema. Entonces, ¿qué pasa con el rendimiento? Bueno, creo que dejaré este tema a los lectores para que lo juzguen, aunque creo que la diferencia es insignificante. Siéntase libre de utilizar este arnés de pruebas de rendimiento de Grant Skinner.
Así que ahora que hemos obtenido este entendimiento, ¿qué sigue? Apliquelo!
Supongamos que una partícula se está moviendo en un camino obligado a chocar con una pared. Podemos calcular el tiempo de impacto mediante la simple ecuación de:
Velocidad = Desplazamiento / Tiempo
Imagina que estás dentro de esta partícula redonda de color naranja y por cada fotograma que pasa y el anuncio se realiza en el momento de chocar con la pared. Oirás
"Tiempo de impacto: 1.5 cuadros" - Cuadro 1
"Tiempo de impacto: 0.5 cuadros" - Cuadro 2
"Tiempo de impacto: -0.5 cuadros" - Cuadro 3
Cuando llegamos al fotograma 3, ya ha ocurrido la colisión con la línea (como lo indica el signo negativo). Tienes que rebobinar el tiempo para llegar al punto de colisión. Obviamente, la colisión debería ocurrir en algún momento entre los cuadros 2 y 3, pero Flash se mueve en incrementos de un solo cuadro. Entonces, si la colisión ocurrió a mitad de camino entre los fotogramas, un giro del signo a negativo indicará que la colisión ya ha ocurrido..
Para obtener un tiempo negativo, usaremos el producto vector punto. Sabemos que cuando tenemos dos vectores y la dirección de uno no está dentro de los 90 grados a cada lado del otro, producirán un producto de punto negativo. Además, el producto punto es una medida de cómo son paralelos dos vectores. Entonces, cuando ya haya ocurrido la colisión, la velocidad y la dirección de un vector a un punto en la pared serán negativas, y viceversa.
Así que aquí está el guión (incluido en CollisionTime.as
). También he agregado la detección de colisiones dentro del segmento de línea aquí. Para aquellos que no lo conocen, consulte mi tutorial sobre detección de colisiones entre un círculo y un segmento de línea, Paso 6. Y para obtener ayuda sobre cómo dirigir barcos, aquí hay otra referencia..
// decidir si dentro del segmento de muro var w2_collision: Vector2D = nuevo Vector2D (collision.x - w2.x, collision.y - w2.y); colision.alpha = 0; // cuando el barco se dirige a la izquierda de la pared si (w2_collision.dotProduct (v1) < 0) t.text = "Ship is heading to left of wall"; else //when ship is heading to right of wall if (w2_collision.getMagnitude() > v1.getMagnitude ()) t.text = "El barco se dirige a la derecha de la pared" // cuando el barco se dirige al segmento de la pared else var ship_collision: Vector2D = nuevo Vector2D (collision.x - ship.x, collision. y - ship.y); desplazamiento var: Number = ship_collision.getMagnitude (); if (ship_collision.dotProduct (velo) < 0) displacement *= -1; //showing text var time:Number = displacement / velo.getMagnitude(); t.text = "Frames to impact: " + time.toPrecision(3) + " frames.\n"; time /= stage.frameRate; t.appendText("Time to impact: " + time.toPrecision(3) + " seconds.\n"); //drop down alpha if collision had happened if (displacement > 0) colision.alpha = 1; else colision.alpha = 0.5; t.appendText ("La colisión ya había ocurrido.")
Así que aquí hay una demostración de lo que llegará. Usa las teclas de flecha izquierda y derecha para dirigir la nave (triángulo), y presiona Arriba para aumentar la velocidad temporalmente. Si el punto de colisión futuro previsto está en la pared (la línea), se pintará un punto rojo en ella. Para una colisión que ya ha ocurrido, un punto rojo todavía estará pintado pero ligeramente transparente. También puedes arrastrar los puntos negros a ambos lados de la pared para moverlo.
Así que espero que este tutorial haya sido informativo. Comparta si realmente ha aplicado esta idea en otro lugar de la que he mencionado. Estoy planeando una breve reseña sobre su aplicación para pintar objetivos láser. ¿Qué piensas? Gracias por leer y avisarme si hay algún error..