Desarrollo de Videojuegos


Matemáticas Básicas
Álbegra Matricial. Modelado alámbrico de figuras 3D


Artículo realizado por
David Isla "Cid"





Capítulo 4.
La aplicación.

Llega la parte que os gusta más a todos; la practica de todo lo que hemos explicado anteriormente :D.

Para esto comentaré el proyecto en Visual C++ de un forma que sea entendibles por todos. Los que podáis trabajar en Visual C++, cread un proyecto SDI (Documento Simple), y llamadlo, por ejemplo, Alambre3D, los que no trabajen con ésta herramienta pueden crear un proyecto, o programa simple, como lo hacen habitualmente.

Bien, definiremos las siguientes estructuras de datos en nuestra aplicación:




// Vertices

struct Vertice

{

   double nX;

   double nY;

   double nZ;

   double nXPantalla;

   double nYPantalla;

   double nZPantalla;

};



// Lineas

struct Linea

{

   Vertice Inicio, Final;

};

Si estáis utilizando Visual C++, el lugar para definir esta estructura es al principio del fichero cabecera de la vista, este es Alambre3DView.h. Los que no utilicéis C, definid los valores de coordenadas (nX,nY,nZ,nXPantalla,nYPantalla,nZPantalla) con un tipo de valor que soporte coma flotante, o varios decimales.

A continuación definiremos las siguiente variables globales




   CArray<Linea,Linea     oLineas;      // Lineas del dibujo

   double GradosX = 0, GradosY = 0, GradosZ = 0;

El lugar en el que debemos incluir estas variables es el principio del fichero de la vista, es decir, Alambre3DView.cpp. Además debemos incluir el archivo de cabecera math.h, que contiene las definiciones de las funciones sin (función seno) y cos (función coseno) que utilizaremos mas adelante. Para incluirlo también insertaremos en el fichero Alambre3DView.cpp y junto a las demás sentencias #include la siguiente línea:




#include "math.h"

Los que no trabajéis en C, definid un array de líneas y 3 variables globales. Si no contáis que el tipo de datos CArray, os tendréis que construir una clase vosotros mismo o encontrar otra solución. Sea lo que fuere, yo explicaré cómo utilizo esta lista de líneas para que comprendáis mejor su funcionamiento y podáis exportarla a vuestro entorno de trabajo.

Teniendo esto, lo primero que vamos a hacer es insertar las funciones que anteriormente habíamos mencionados. Estas son la multiplicar matriz por vector, y la de multiplicar matriz por matriz. Para ello tendréis que sumar, los que trabajéis con Visual C++, dos nuevas funciones miembros llamadas MultiplicarMatriz y MultiplicarVector. Para ello añadiremos en la clase de nuestra vista las definiciones de las funciones que aportamos anteriormente. La clase nos quedará así:




class CAlambre3DView : public CView

{



   ....         // Codigo restante



// Operations

public:



   ....         // Codigo restante



void MultiplicarMatriz( double Resultado[4][4],

                        double Matriz1[4][4],

                        double Matriz2[4][4] );

void MultiplicarVector( double Resultado[4],

                        double Matriz1[4],

                        double Matriz2[4][4] );

Además de definir las funciones miembros en el fichero Alambre3DView.h, implementaremos el código en el archivo Alambre3DView.cpp de la forma siguiente:




void CAlambre3DView::MultiplicarMatriz( double Resultado[4][4],

                                        double Matriz1[4][4],

                                        double Matriz2[4][4] )

{

   for( int i=0; i <4 ; i++ )

   {

      for( int j=0; j <4 ; j++)

      {

         Resultado[i][j] = 0;

         for( int k=0; k<4 ; k++ )

         Resultado[i][j] += Matriz1[i][k] * Matriz2[k][j];

      } 

   }

}



void CAlambre3DView::MultiplicarVector( double Resultado[4],

                                        double Matriz1[4],

                                        double Matriz2[4][4] )

{

   Resultado[0] = Matriz1[0]*Matriz2[0][0] +

                  Matriz1[1]*Matriz2[0][1] +

                  Matriz1[2]*Matriz2[0][2] +

                  Matriz1[3]*Matriz2[0][3];

   Resultado[1] = Matriz1[0]*Matriz2[1][0] +

                  Matriz1[1]*Matriz2[1][1] +

                  Matriz1[2]*Matriz2[1][2] +

                  Matriz1[3]*Matriz2[1][3];

   Resultado[2] = Matriz1[0]*Matriz2[2][0] +

                  Matriz1[1]*Matriz2[2][1] +

                  Matriz1[2]*Matriz2[2][2] +

                  Matriz1[3]*Matriz2[2][3];

   Resultado[3] = Matriz1[0]*Matriz2[3][0] +

                  Matriz1[1]*Matriz2[3][1] +

                  Matriz1[2]*Matriz2[3][2] +

                  Matriz1[3]*Matriz2[3][3];

}

Esta operación la tendremos que repetir con las demás funciones que insertemos a continuación. Si no trabajas en C, define estas funciones de forma global y que toda la aplicación pueda utilizarla. Bien, ya tenemos el sistema implantado y podemos empezar a manejarlo. Vamos a introducir todas las líneas que conformaran la figura. Figura que es, por cierto, una M de Macedonia :D. Para ello los que trabajen en Visual C++ insertarán el siguiente código en el evento OnInitialUpdate() y los que no lo hagan, que lo inserten en el comienzo de la aplicación.




	Linea oLin;



	GradosX = 0;

	GradosY = 0;

	GradosZ = 0;



	// Delante

	// Linea 1

	oLin.Inicio.nX = -50; oLin.Inicio.nY = -50; oLin.Inicio.nZ = 0;

	oLin.Final.nX  = -50; oLin.Final.nY  = 50; oLin.Final.nZ  = 0;

	oLineas.Add( oLin );

	// Linea 2

	oLin.Inicio.nX = -50; oLin.Inicio.nY = -50; oLin.Inicio.nZ = 0;

	oLin.Final.nX  = 0; oLin.Final.nY  = 0; oLin.Final.nZ  = 0;

	oLineas.Add( oLin );

	// Linea 3

	oLin.Inicio.nX = 0; oLin.Inicio.nY = 0; oLin.Inicio.nZ = 0;

	oLin.Final.nX  = 50; oLin.Final.nY  = -50; oLin.Final.nZ  = 0;

	oLineas.Add( oLin );

	// Linea 4

	oLin.Inicio.nX = 50; oLin.Inicio.nY = -50; oLin.Inicio.nZ = 0;

	oLin.Final.nX  = 50; oLin.Final.nY  = 50; oLin.Final.nZ  = 0;

	oLineas.Add( oLin );



	// Atras

	// Linea 1

	oLin.Inicio.nX = -50; oLin.Inicio.nY = -50; oLin.Inicio.nZ = 20;

	oLin.Final.nX  = -50; oLin.Final.nY  = 50; oLin.Final.nZ  = 20;

	oLineas.Add( oLin );

	// Linea 2

	oLin.Inicio.nX = -50; oLin.Inicio.nY = -50; oLin.Inicio.nZ = 20;

	oLin.Final.nX  = 0; oLin.Final.nY  = 0; oLin.Final.nZ  = 20;

	oLineas.Add( oLin );

	// Linea 3

	oLin.Inicio.nX = 0; oLin.Inicio.nY = 0; oLin.Inicio.nZ = 20;

	oLin.Final.nX  = 50; oLin.Final.nY  = -50; oLin.Final.nZ  = 20;

	oLineas.Add( oLin );

	// Linea 4

	oLin.Inicio.nX = 50; oLin.Inicio.nY = -50; oLin.Inicio.nZ = 20;

	oLin.Final.nX  = 50; oLin.Final.nY  = 50; oLin.Final.nZ  = 20;

	oLineas.Add( oLin );



	// Vertices

	// Vertice 1

	oLin.Inicio.nX = -50; oLin.Inicio.nY = -50; oLin.Inicio.nZ = 0;

	oLin.Final.nX  = -50; oLin.Final.nY  = -50; oLin.Final.nZ  = 20;

	oLineas.Add( oLin );

	// Vertice 2

	oLin.Inicio.nX = -50; oLin.Inicio.nY = 50; oLin.Inicio.nZ = 0;

	oLin.Final.nX  = -50; oLin.Final.nY  = 50; oLin.Final.nZ  = 20;

	oLineas.Add( oLin );

	// Vertice 3

	oLin.Inicio.nX = 0; oLin.Inicio.nY = 0; oLin.Inicio.nZ = 0;

	oLin.Final.nX  = 0; oLin.Final.nY  = 0; oLin.Final.nZ  = 20;

	oLineas.Add( oLin );

	// Vertice 4

	oLin.Inicio.nX = 50; oLin.Inicio.nY = -50; oLin.Inicio.nZ = 0;

	oLin.Final.nX  = 50; oLin.Final.nY  = -50; oLin.Final.nZ  = 20;

	oLineas.Add( oLin );

	// Vertice 5

	oLin.Inicio.nX = 50; oLin.Inicio.nY = 50; oLin.Inicio.nZ = 0;

	oLin.Final.nX  = 50; oLin.Final.nY  = 50; oLin.Final.nZ  = 20;

	oLineas.Add( oLin );

Lo que aquí estamos haciendo es sumar todas las líneas que formaran nuestra figura a la lista oLineas que definimos anteriormente de forma global.

Lo siguiente que debemos hacer es poder dibujar todas estas líneas en pantalla. Si estamos utilizando Visual C++ debemos utilizar el evento OnPaint() que se ejecuta cada vez que la ventana debe ser dibujada. Si no trabajas en Visual C tendrás que colocar este código en alguna función que ejecutes cuando quieras mostrar el dibujo.




void CAlambre3DView::OnPaint() 

{

	CPaintDC dc(this); // device context for painting

	

	Linea oLin;

	int i;



	i = 0;



	for( i = 0; i <= oLineas.GetUpperBound() ; i++ )

	{

		oLin = oLineas.GetAt( i );

		dc.MoveTo( 100+(int) oLin.Inicio.nXPantalla, 100+(int) oLin.Inicio.nYPantalla );

		dc.LineTo( 100+(int) oLin.Final.nXPantalla,  100+(int) oLin.Final.nYPantalla );

	}



	// Do not call CView::OnPaint() for painting messages

}

Aquí recorremos todos elementos de la lista oLineas, y dibujamos en pantalla cada uno de ellas.

Llegados a este punto os explicaré como será la dinámica de la aplicación. Tendremos varios botones en una toolbar que modificaran los valores GradosX, GradosY y GradosZ aumentándolos o disminuyéndolos, y cada vez que hagamos esto calcularemos las nuevas posiciones de todas las líneas de la lista. Para ello, y como comente en capítulos anteriores, tendremos que multiplicar cada punto por las matrices de rotacion X, Y y Z. Dado que siempre multiplicaremos el vector del punto tridimensional por las tres matrices de rotación, crearemos una función que multiplique estas 3 matrices entre ellas mismas y que devuelva una 'maestra' que usaremos después para multiplicarla por todos los vectores de los puntos de la figura. Para que lo comprendáis mejor pensad que tenéis que multiplicar los números del 1 al 100 por 3, por 4 y por 8, seria mas fácil multiplicar los números del 1 al 100 por 96 (3 * 4 * 8 = 96). Por eso las matemáticas son tan importantes en los videojuegos. El saber que estas 3 matrices se pueden multiplicar entre ellas nos va ahorra muchas operaciones reiterativas. El código de la función es el siguiente:




void CAlambre3DView::Calculo( double MatrizMaestra[4][4] ) 

{

	double MatrizIden[4][4] = { 1,0,0,0,

				0,1,0,0,

				0,0,1,0,

				0,0,0,1 };

	double MatrizZ[4][4] = { cos(GradosZ), sin(GradosZ), 0, 0,

				-sin(GradosZ), cos(GradosZ), 0, 0,

				0, 0, 1, 0,

				0, 0, 0, 1	};

	double MatrizX[4][4] = { 1, 0, 0, 0,

				0, cos(GradosX), sin(GradosX), 0,

				0, -sin(GradosX), cos(GradosX), 0,

				0, 0, 0, 1	};

	double MatrizY[4][4] = { cos(GradosY), 0, -sin(GradosY), 0, 

				0, 1, 0, 0,

				sin(GradosY), 0, cos(GradosY), 0, 

				0, 0, 0, 1	};



	double MatrizAux[4][4];

	double MatrizAux2[4][4];



	MultiplicarMatriz( MatrizAux, MatrizIden, MatrizX );

	MultiplicarMatriz( MatrizAux2, MatrizAux, MatrizY );

	MultiplicarMatriz( MatrizMaestra, MatrizAux2, MatrizZ );



}

Y para terminar la aplicación expongo la aplicación que calculara las nuevas posiciones de todas las líneas:




void CAlambre3DView::OnRepintar() 

{

	Linea oLin;

	int i;

	double MatrizMaestra[4][4];

	double Vector[4];

	double Resultado[4];



	Calculo( MatrizMaestra );

	for( i = 0; i <= oLineas.GetUpperBound() ; i++ )

	{

		oLin = oLineas.GetAt( i );



		Vector[0] = oLin.Inicio.nX;

		Vector[1] = oLin.Inicio.nY;

		Vector[2] = oLin.Inicio.nZ;

		Vector[3] = 1;

		MultiplicarVector( Resultado, Vector, MatrizMaestra );

		oLin.Inicio.nXPantalla = Resultado[0];

		oLin.Inicio.nYPantalla = Resultado[1];

		oLin.Inicio.nZPantalla = Resultado[2];



		Vector[0] = oLin.Final.nX;

		Vector[1] = oLin.Final.nY;

		Vector[2] = oLin.Final.nZ;

		Vector[3] = 1;

		MultiplicarVector( Resultado, Vector, MatrizMaestra );

		oLin.Final.nXPantalla = Resultado[0];

		oLin.Final.nYPantalla = Resultado[1];

		oLin.Final.nZPantalla = Resultado[2];



		oLineas.SetAt(i,oLin);

	}



	Invalidate( TRUE );	

}

Como véis, calculo primero la matriz Maestra con la sentencia Calculo(MatrizMaestra) y a continuación recorro toda la lista obteniendo el punto origen y final de cada línea. Multiplico estos vectores por la matriz maestra y después de obtener el resultado, modifico la línea en la lista. Al final con invalidate(TRUE) fuerzo a que se llame a la función OnPaint() que volverá a recorrer la lista y dibujará las líneas en las nuevas posiciones.

Todas estas operaciones deben ser miembros de la vista CAlambre3Dview. Los que no trabajen en Visual C++, deben ser globales en toda la aplicación y debéis cambiar Invalidate( TRUE ) por la llamada a la función OnPaint() para que fuerce el dibujar de la figura.

Por ultimo nos quedaría crear los controles que modificarán las variables globales GradosX, GradosY y GradosZ. Para ello insertamos en la toolbar de la aplicación botones que resten y sumen radianes a estas variables, y que después llamen a la función OnRepintar para que calcule las nuevas posiciones forzando redibujar el objeto. Esta ultima operación se puede llevar acabo de muchas formas y depende de que herramientas estés utilizando. Yo ahora os doy el código que implementa el Visual C++ cuando creamos los botones y manejamos el evento OnCommand de cada uno de ellos.




void CAlambre3DView::OnMasx() 

{

	// TODO: Add your command handler code here

	GradosX += 0.1;

	OnRepintar();

	

}



void CAlambre3DView::OnMasy() 

{

	// TODO: Add your command handler code here

	GradosY += 0.1;

	OnRepintar();

	

}



void CAlambre3DView::OnMasz() 

{

	// TODO: Add your command handler code here

	GradosZ += 0.1;

	OnRepintar();

	

}



void CAlambre3DView::OnMenosx() 

{

	// TODO: Add your command handler code here

	GradosX -= 0.1;

	OnRepintar();

	

}



void CAlambre3DView::OnMenosy() 

{

	// TODO: Add your command handler code here

	GradosY -= 0.1;

	OnRepintar();

	

}



void CAlambre3DView::OnMenosz() 

{

	// TODO: Add your command handler code here

	GradosZ -=0.1;

	OnRepintar();

	

}

¡¡¡Y se acabo!!!, ¡¡¡por fin verdad!!. Aquí tenéis unas imágenes de cómo queda el proyecto, pero... se me ha olvidado algo....qué es, qué es.... ¿alguien lo sabe?. No hemos implementado las matrices de TRASLADAR y ESCALAR he escuchado por ahí, y la explicación es que es tan fácil que os lo dejo para vosotros. Venga animaos, que el proceso es el mismo que todos los demás, adelante y que la fuerza os acompañe. :)



Puedes conseguir el código fuente del artículo pulsando aquí


ÚLTIMA REVISIÓN EN FEBRERO DE 1999




DESARROLLO DE VIDEOJUEGOS
a
MACEDONIA Magazine