Entendiendo las Transformaciones Afines con Matrix Mathematics

Inspirado por el profesor Wildberger en su serie de conferencias sobre álgebra lineal, pretendo implementar sus ideas matemáticas con Flash. No profundizaremos en la manipulación matemática de matrices a través del álgebra lineal: solo a través de vectores. Esta comprensión, aunque diluye la elegancia del álgebra lineal, es suficiente para lanzarnos a algunas posibilidades interesantes de la manipulación de la matriz 2x2. En particular, lo utilizaremos para aplicar diversos efectos de cizallamiento, sesgo, giro y escalado a las imágenes en tiempo de ejecución.


Vista previa del resultado final

Echemos un vistazo al resultado final en el que trabajaremos. Presione las cuatro teclas direccionales (arriba, abajo, izquierda, derecha) para ver algunos efectos que podemos lograr con transformaciones afines..

Si solo usas las teclas de flecha izquierda y derecha, el pez parece nadar en un espacio isométrico pseudo-3D.


Paso 1: Diferentes espacios de coordenadas

Los gráficos se dibujan en espacios de coordenadas. Así que para manipularlos, especialmente para traducir, rotar, escalar, reflejar y sesgar gráficos, es vital que entendamos los espacios de coordenadas. Por lo general, utilizamos no solo uno, sino múltiples espacios de coordenadas en un solo proyecto, esto es cierto no solo para los diseñadores que usan Flash IDE, sino también para los programadores que escriben ActionScript..

En Flash IDE esto sucede cada vez que convierte sus dibujos en símbolos de MovieClip: cada símbolo tiene su propio origen.

La imagen de arriba muestra el origen del espacio de coordenadas del escenario (punto rojo) y el del espacio de coordenadas del símbolo (punto de registro marcado por una cruz). Para saber en qué espacio se encuentra actualmente, observe la barra debajo de la línea de tiempo de Flash IDE como se muestra en la imagen a continuación..

(Estoy usando Flash CS3, por lo que su ubicación puede diferir para CS4 y CS5). Lo que quiero enfatizar es la existencia de diferentes espacios de coordenadas y el hecho de que ya está familiarizado con su uso..


Paso 2: La justificación

Ahora hay una buena razón para esto. Podemos usar un espacio de coordenadas como referencia para cambiar el otro espacio de coordenadas. Esto puede sonar extraño, así que he incluido la presentación de Flash a continuación para facilitar mi explicación. Haga clic y arrastre las flechas rojas. Jugar con eso.

En el fondo hay una cuadrícula azul y, en primer plano, una cuadrícula roja. Las flechas azul y roja se alinean inicialmente a lo largo de los ejes x e y del espacio de coordenadas de Flash, cuyo centro he desplazado a la mitad del escenario. La cuadrícula azul es una cuadrícula de referencia; Las líneas de cuadrícula no cambiarán cuando interactúes con las flechas rojas. La red roja, por otro lado, se puede reorientar y escalar arrastrando las flechas rojas.

Tenga en cuenta que las flechas también indican una propiedad importante de estas cuadrículas. Indican la noción de una unidad de x y una unidad de y en su respectiva cuadrícula. Hay dos flechas rojas en la rejilla roja. Cada uno de ellos indica la longitud de una unidad en el eje xy el eje y. También dictan la orientación del espacio de coordenadas. Tomemos la flecha roja que apunta a lo largo del eje x y la extendemos hasta que sea el doble de larga que la flecha original (que se muestra en azul). Observa las siguientes imágenes..

Vemos que la imagen (el cuadro verde) dibujada en la cuadrícula roja ahora se estira horizontalmente, debido al hecho de que esta cuadrícula roja en la que se dibuja es ahora el doble de ancho. El punto que trato de señalar es bastante simple: puedes usar un espacio de coordenadas como base para cambiar otro espacio de coordenadas.


Paso 3: Espacio de coordenadas afines

Entonces, ¿qué es un "espacio de coordenadas afines"? Bueno, estoy seguro de que tienes el cuidado de observar que estos espacios de coordenadas se dibujan utilizando cuadrículas paralelas. Tomemos el espacio afín rojo, por ejemplo: no hay garantía de que tanto el eje x como el eje y sean siempre perpendiculares entre sí, pero puede estar seguro de que, sin embargo, intente ajustar las flechas, nunca llegará a ese caso. como a continuación.


Este espacio de coordenadas es no un espacio afín de coordenadas.

De hecho, los ejes x y y generalmente se refieren al espacio de coordenadas cartesianas, como se muestra a continuación.

Tenga en cuenta que las rejillas horizontales y verticales son perpendiculares entre sí. Cartesiano es un tipo de espacio afín de coordenadas, pero podemos transformarlo en otros espacios afines como preferimos. Las rejillas horizontales y verticales no necesariamente tienen que ser perpendiculares entre sí.


Ejemplo de un espacio de coordenadas afines
Otro ejemplo de un espacio de coordenadas afines.

Paso 4: Transformaciones afines

Como habrás adivinado, las transformaciones afines son traslación, escalado, reflexión, sesgo y rotación..


Espacio afín original
Espacio afín a escala
Espacio afín reflejado
Espacio afilado sesgado
Espacio afín rotado y escalado

No hace falta decir, propiedades físicas como x, y, scaleX, scaleY y rotación Depende del espacio. Cuando hacemos llamadas a esas propiedades, en realidad estamos transformando coordenadas afines..


Paso 5: Entendiendo la Matriz

Espero que las imágenes que se muestran arriba sean lo suficientemente explícitas para llevar a casa la idea. Esto se debe a que, para un programador que trabaja con FlashDevelop, no veremos las cuadrículas que el IDE de Flash muestra convenientemente para los diseñadores. Todos estos tienen que vivir en tu cabeza..

Además de imaginar estas cuadrículas, también necesitamos contar con la ayuda de Matriz clase. Por lo tanto, tener una comprensión matemática de las matrices es importante, por lo que revisaremos las operaciones de la matriz aquí: suma y multiplicación.


Paso 6: Significado geométrico de la adición de matriz

Operaciones matriciales con significados geométricos. En otras palabras, puedes imaginar lo que significan en un gráfico. Supongamos que tenemos cuatro puntos en nuestro espacio de coordenadas y nos gustaría cambiarlos a un conjunto de nuevas ubicaciones. Esto se puede hacer mediante la adición de matriz. Echa un vistazo a la imagen de abajo.

Como puede ver, en realidad estamos desplazando todo el espacio de coordenadas locales (cuadrículas rojas) donde se dibujan estos cuatro puntos. La notación para realizar estas operaciones es la siguiente:

También podemos ver que este cambio se puede representar usando un vector de (tx, ty). Diferenciemos los vectores y los puntos estáticos en espacios de coordenadas mediante nuestro uso de paréntesis y corchetes. Los he reescrito en la imagen de abajo..


Paso 7: Implementación de ActionScript

Aquí hay una implementación simple de la adición de matriz. Echa un vistazo a los comentarios:

 La clase pública Addition extiende Sprite public function Addition () var m: Matrix = new Matrix (); // instanciar la matriz m.tx = stage.stageWidth * 0.5; // cambio en x m.ty = stage.stageHeight * 0.5; // cambio en y var d: DottedBox = new DottedBox (); // crear el gráfico personalizado (el cuadro de puntos es un Sprite) addChild (d); d.transform.matrix = m; // aplicar la matriz a nuestro gráfico

Paso 8: Significado geométrico de la multiplicación de matrices

La multiplicación de matrices es algo más sofisticada que la adición de matrices, pero el profesor Wildberger la ha dividido con elegancia en esta simple interpretación. Intentaré humildemente reiterar su explicación. Para aquellos que deseen profundizar en la comprensión del álgebra lineal que conduce a esto, consulte la serie de conferencias del profesor..

Empecemos abordando el caso de la matriz de identidad, I.

Por la imagen de arriba, sabemos que multiplicar una matriz arbitraria, A, por la matriz de identidad, I, siempre producirá A. Aquí hay una analogía: 6 x 1 = 6; La matriz de identidad se compara con el número 1 en esa multiplicación.

Alternativamente, podemos escribir el resultado en el siguiente formato vectorial que simplificará enormemente nuestra interpretación:

La interpretación geométrica de esta fórmula se muestra en la imagen de abajo..

Desde la cuadrícula cartesiana (cuadrícula izquierda), podemos ver que el punto azul se encuentra en (2, 1). Ahora, si tuviéramos que transformar esta cuadrícula original de x e y en una nueva cuadrícula (cuadrícula derecha) de acuerdo con un conjunto de vectores (debajo de la cuadrícula derecha), el punto azul se reubicará en (2, 1) en la nueva cuadrícula - pero cuando mapeamos esto de nuevo a la cuadrícula original, es el mismo punto que antes.

Debido a que estamos transformando la cuadrícula original en otra cuadrícula que comparte los mismos vectores para x e y, no vemos ninguna diferencia. De hecho, los cambios de x e y en esta transformación son nulos. Esto es lo que significa matriz de identidad, desde un punto de vista geométrico.

Sin embargo, si intentamos realizar un mapeo utilizando otras transformaciones, veremos alguna diferencia. Sé que este no fue el ejemplo más revelador para comenzar, así que pasemos a otro ejemplo..


Paso 9: Escalado a lo largo de X

La imagen de arriba muestra una escala del espacio de coordenadas. Compruebe el vector de x en el espacio de coordenadas transformado: una unidad de la x transformada representa dos unidades de la x original. En el espacio de coordenadas transformado, la coordenada del punto azul es todavía (2, 1). Sin embargo, si intenta asignar esta coordenada de la cuadrícula transformada a la cuadrícula original, es (4, 1).

Toda esta idea es capturada por la imagen de arriba. ¿Qué hay de la fórmula? El resultado debe ser consistente; vamos a ver.

Estoy seguro de que recuerdas estas fórmulas. Ahora, he añadido sus respectivos significados.

Ahora para ver el resultado numérico de nuestro ejemplo de escalado.

  • Coordenada original: (2, 1)
  • Vector en el eje x transformado: (2, 0)
  • Vector en el eje y transformado: (0, 1)
  • Resultado esperado: (2 * 2 + 0 * 1, 0 * 2 + 1 * 1) = (4, 1)

¡Están de acuerdo unos con otros! Ahora podemos aplicar esta idea felizmente a otras transformaciones. Pero antes de eso, una implementación de ActionScript..


Paso 10: Implementación de ActionScript

Consulte la implementación de ActionScript (y el SWF resultante) a continuación. Tenga en cuenta que uno de los cuadros superpuestos se está estirando a lo largo de x en una escala de 2. He resaltado los valores importantes. Estos valores se modificarán en los pasos posteriores para representar diferentes transformaciones.

 La multiplicación de la clase pública extiende Sprite la función pública Multiplicación () var ref: DottedBox = new DottedBox (); // crear el gráfico de referencia addChild (ref); ref.x = stage.stageWidth * 0.5; ref.y = stage.stageHeight * 0.5; var m: Matriz = Matriz nueva (); // instanciar la matriz m.tx = stage.stageWidth * 0.5; // cambio en x m.ty = stage.stageHeight * 0.5; // cambio en y m.a = 2; m.c = 0; m.b = 0; m.d = 1; var d: DottedBox = new DottedBox (); // crear el gráfico personalizado addChild (d); d.transform.matrix = m // aplique la matriz en nuestro gráfico

Paso 11: Escalado X e Y

Aquí hemos escalado la cuadrícula por un factor de dos a lo largo de los ejes x e y. El punto azul está en (2, 1) en la cuadrícula original antes de la transformación, y (4, 2) en la cuadrícula original después de la transformación. (Por supuesto, todavía está en (2, 1) en el nuevo cuadrícula después de la transformación.)

Y para confirmar el resultado numéricamente ...

… ¡Coinciden de nuevo! Para ver esto en la implementación de ActionScript, simplemente cambie el valor de Maryland de 1 a 2.

(Tenga en cuenta que la dirección de estiramiento desde y es hacia abajo, no hacia arriba, porque y se incrementa hacia abajo en Flash pero hacia arriba en el espacio de coordenadas cartesianas normales que usé en el diagrama).


Paso 12: Reflexión

Aquí hemos reflejado la cuadrícula a lo largo del eje x usando estos dos vectores, por lo que la posición del punto azul en la cuadrícula original cambia de (2, 1) a (-2, 1). El cálculo numérico es el siguiente:

La implementación de ActionScript es la misma que antes, pero en su lugar utiliza estos valores: m.a = -1, m.b = 0 para representar el vector para la transformación x, y: m.c = 0 y m. d = 1 para representar el vector para la transformación y.

A continuación, ¿qué hay de reflexionar simultáneamente en x y y? Echa un vistazo a la imagen de abajo.

Además, numéricamente se calcula en la imagen de abajo..

Para la implementación de ActionScript ... bueno, estoy seguro de que conoces los valores para poner en la matriz. m.a = -1, m.b = 0 para representar el vector para la transformación x; m.c = 0 y m. d = -1 Representar el vector para la transformación y. He incluido el SWF final a continuación.


Paso 13: Sesgar y esquilar

El sesgo viene con un poco de diversión. Para el caso de la imagen de abajo, la cuadrícula transformada ha reorientado y escalado su eje x. Compare las flechas rojas en las dos cuadrículas siguientes: son diferentes, pero el eje y permanece sin cambios.


Sesgo

Visualmente, parece que la distorsión ocurre a lo largo de la dirección y. Esto es cierto porque nuestro eje x transformado ahora tiene un componente y en su vector.

Numéricamente, esto es lo que sucede ...

En términos de implementación, he enumerado los ajustes a continuación..

  • m.a = 2
  • m.b = 1
  • m.c = 0
  • m.d = 1

Estoy seguro de que en este punto te gustaría probar cosas por ti mismo, así que sigue adelante y modifica

  • La orientación del eje y transformado manteniendo el eje x.
  • La orientación de ambos ejes en conjunto.

He incluido la salida de Flash para ambos casos como a continuación. Para los lectores que deseen ayuda con estos valores, consulte Multiplicación_final.as en la fuente de descarga.


Paso 14: Rotación

Considero la rotación como un subconjunto de sesgo. La única diferencia es que, en rotación, la magnitud de una unidad de ambos ejes x e y se mantiene, al igual que la perpendicularidad entre los dos ejes..

ActionScript en realidad proporciona un método en el Matriz clase, girar(), para hacer esto. Pero vamos a pasar por esto de todos modos.

Ahora no queremos alterar la magnitud de una unidad de longitud en xey de la cuadrícula original; Solo para cambiar la orientación de cada uno. Podemos utilizar la trigonometría para obtener el resultado que se muestra en la imagen de arriba. Dado un ángulo de rotación, a, obtendremos el resultado deseado utilizando vectores de (cos a, sin a) para el eje x y (-sin a, cos a) para el eje y. La magnitud de cada nuevo eje seguirá siendo una unidad, pero cada eje estará en un ángulo de a, en comparación con los originales.

Para la implementación de ActionScript, suponiendo que el ángulo, a, es de 45 grados (es decir, 0,25 * pi radianes), solo ajuste los valores de la matriz a lo siguiente:

 var a: Número = 0.25 * Math.PI m.a = Math.cos (a); m.c = -1 * Math.sin (a); m.b = Math.sin (a); m.d = Math.cos (a);

La fuente completa se puede consultar en Multiplicación_final.as.


Paso 15: Aplicación

Tener una interpretación vectorial de una matriz de 2x2 abre espacio para que podamos explorar. Su aplicación en la manipulación de bitmaps (BitmapData, LineBitmapStyle, LineGradientStyle, etc.) está muy extendido, pero creo que lo guardaré para otro tutorial. En el caso de este artículo, intentaremos sesgar nuestro sprite en tiempo de ejecución para que parezca que realmente se está volteando en 3D..


Vista de un mundo isométrico pseudo-3D.

En la imagen de arriba, podemos ver que, en un mundo con una vista isométrica, cualquier gráfico que esté "en pie" mantiene su vector del eje y sin cambios mientras el vector del eje x gira. Tenga en cuenta que una unidad de longitud para los ejes x e y no cambia; en otras palabras, no se debe realizar ningún escalado en ninguno de los dos ejes, solo la rotación alrededor del eje x.

Aquí hay un ejemplo de esta idea en flash. Haga clic en cualquier lugar del escenario y comience a arrastrar para ver la inclinación de los peces. Suelte para detener su interacción..

Aquí está el bit importante de Actionscript. He resaltado las líneas cruciales que manejan la rotación del eje x. También puede referirse a FakeIso.as.

 var privado f1: Pescado, m: Matriz; privada var disp: punto; privado var axisX: Point, axisY: Point; función pública FakeIso () disp = new Point (stage.stageWidth * 0.5, stage.stageHeight * 0.5); m = Matriz nueva (); m.tx = disp.x; m.ty = disp.y; // desplazarse al centro del escenario f1 = nuevo Pez (); addChild (f1); f1.transform.matrix = m; // aplicar la transformación en el eje de peces X = punto nuevo (1, 0); // vector para el eje x eje Y = nuevo punto (0, 1); // vector para y - axis stage.addEventListener (MouseEvent.MOUSE_DOWN, start); // iniciar la interacción stage.addEventListener (MouseEvent.MOUSE_UP, end); // fin de la interacción inicio de la función privada (e: MouseEvent): void f1.addEventListener (Event.ENTER_FRAME, actualización);  fin de la función privada (e: MouseEvent): void f1.removeEventListener (Event.ENTER_FRAME, actualización);  actualización de la función privada (e: Evento): void axisX.setTo (mouseX - f1.x, mouseY - f1.y); // determina la orientación (pero la magnitud también cambia) axisX.normalize (1); // arreglar magnitud de vector con nueva orientación a 1 unidad apply2Matrix (); // aplicar matrix en fish función privada apply2Matrix (): void m.setTo (axisX.x, axisX.y, axisY.x, axisY.y, disp.x, disp.y); f1.transform.matrix = m; 

Aquí, he usado la clase Point para almacenar vectores.


Paso 16: Agregar el control del teclado

En este paso, intentaremos agregar controles de teclado. La ubicación del pez se actualizará de acuerdo a su velocidad., velo. Definiremos pasos incrementales para la rotación positiva (en el sentido de las agujas del reloj) y también para la rotación negativa (en el sentido contrario a las agujas del reloj).

 velo = nuevo punto (1, 0); // velo se utilizará para definir el eje x ejeY = nuevo punto (0, 1); delta_positive = new Matrix (); delta_positive.rotate (Math.PI * 0.01); // rotación positiva delta_negative = new Matrix (); delta_negative.rotate (Math.PI * -0.01); // rotación negativa

Al presionar una tecla, velo girará:

 función privada keyUp (e: KeyboardEvent): void if (e.keyCode == Keyboard.LEFT) velo = delta_negative.transformPoint (velo) // rotar velo en sentido contrario a las agujas del reloj else if (e.keyCode == Keyboard.RIGHT ) velo = delta_positive.transformPoint (velo) // rotar velo hacia la derecha

Ahora, para cada cuadro, intentaremos colorear la parte frontal del pez, y sesgarlo también. Si la velocidad, velo, tiene una magnitud de más de 1 y lo aplicamos a la matriz del pez, metro, También obtendremos un efecto de escalado, así que para eliminar esta posibilidad, normalizaremos la velocidad y luego aplicaremos eso a la matriz del pez..

 actualización de la función privada (e: Evento): void var front_side: Boolean = velo.x> 0 // verificando el lado frontal de fish if (front_side) f1.colorBody (0x002233,0.5) // colorea el lado frontal de fish else f1.colorBody (0xFFFFFF, 0.5) // blanco aplicado al lado trasero de fish disp = disp.add (velo); // actualizar el desplazamiento actual con la velocidad var velo_norm: Point = velo.clone (); // en el caso de velo> 0, necesitamos recalcular 1 unidad de longitud para x. velo_norm.normalize (1); // tenga en cuenta que el eje x más de 1 realizará el escalado. No queremos que por ahora m.setTo (velo_norm.x, velo_norm.y, axisY.x, axisY.y, disp.x, disp.y); f1.transform.matrix = m; 

Paso 17: Tu pez

Haga clic en el escenario, luego presione las teclas de flecha izquierda y derecha para ver cómo los peces cambian de dirección.


Paso 18: Otro control de teclado

Para condimentar las cosas, permitamos también el control del vector del eje y.

 función privada keyUp (e: KeyboardEvent): void if (e.keyCode == Keyboard.LEFT) velo = delta_negative.transformPoint (velo) else if (e.keyCode == Keyboard.RIGHT) velo = delta_positive.transformPoint (velo) if (e.keyCode == Keyboard.UP) axisY = delta_negative.transformPoint (axisY) else if ((e.keyCode == Keyboard.DOWN) axisY = delta_positive.transformPoint (axisY)

También para determinar el lado frontal del pez, ahora necesitamos incorporar el eje y. Aquí está el código para eso:

 var front_side: Boolean = velo.x * axisY.y> 0 if (front_side) f1.colorBody (0x002233,0.5) else f1.colorBody (0xFFFFFF, 0.5)

Paso 19: Tu pez no tan regular

Bueno, para algunos el resultado de controlar ambos ejes puede resultar un poco confuso, pero el punto es que ahora puede sesgar su pez, traducirlo, reflejarlo e incluso rotarlo. Prueba los combos de arriba + izquierda, arriba + derecha, abajo + izquierda, abajo + derecha.

Además, vea si puede mantener el lado "frontal" de los peces (los peces estarán en gris). Sugerencia: toque arriba continuamente, luego a la izquierda, luego hacia abajo, luego a la derecha. Estas haciendo una rotacion!

Conclusión

Espero que encuentre la matriz matemática como un activo valioso para sus proyectos después de leer este artículo. Espero escribir un poco más sobre las aplicaciones de matriz 2x2 en pequeños Consejos rápidos que se bifurcan en este artículo, y en Matriz3d Lo cual es esencial para las manipulaciones 3D. Gracias por la lectura, terima kasih.