29 Rutinas matemáticas 3D



Allegro también contiene algunas funciones de ayuda de 3d para manipular vectores, construir o usar matrices de transformación, y hacer proyecciones de perspectiva de un espacio 3d en la pantalla. Estas funciones no son, y nunca serán, una librería 3d total (mi objetivo es dar rutinas de soporte genéricas, y no código gráfico muy especializado :-) pero estas funciones pueden serle útiles para desarrollar su propio código 3d.

Hay dos versiones de todas las funciones matemáticas de 3d: una usando aritmética de punto fijo, y la otra usando coma flotante. La sintaxis para ambas es idéntica, pero las funciones y estructuras de coma flotante tienen el sufijo '_f'. Ejemplo: la función cross_product() de punto fijo tiene el equivalente de coma flotante en cross_product_f(). Si está programando en C++, Allegro también sobrecarga estas funciones para que las use con la clase "fija".

La transformación 3d se realiza modelando una matriz. Esta es un array de 4x4 números que pueden ser multiplicados con un punto 3d para producir otro punto 3d. Si ponemos los valores correctos en la matriz, podemos usarla para varias operaciones como translación, rotación y escalado. El truco consiste en que puede multiplicar dos matrices para producir una tercera, y esta tendrá el mismo efecto en los puntos 3d que aplicando las dos matrices originales una después de la otra. Por ejemplo, si tiene una matriz que rota un punto, y otra que lo mueve en una dirección, puede combinarlas para producir una matriz que realizara la rotación y translación en un paso. Puede crear transformaciones extremadamente complejas de este modo, teniendo que multiplicar cada punto 3d por solo una matriz.

Allegro hace trampa al implementar la estructura de la matriz. La rotación y el escalado de un punto 3d puede ser realizado con una matriz simple de 3x3, pero para trasladar el punto y proyectarlo en la pantalla, la matriz tiene que ser extendida a 4x4, y el punto extendido a una cuarta dimensión, al añadir una coordenada extra: w=1. Esto es algo malo en términos de eficiencia, pero afortunadamente, es posible realizar una optimización. Dada la siguiente matriz 4x4:

   ( a, b, c, d )
   ( e, f, g, h )
   ( i, j, k, l )
   ( m, n, o, p )

se puede observar un patrón de qué partes hacen qué. La rejilla 3x3 de arriba a la izquierda implementa la rotación y el escalado. Los tres valores de arriba de la cuarta columna (d, h y l) implementan la translación, y siempre y cuando la matriz sea usada sólo para transformaciones afines, m, n y o serán siempre cero y p siempre será 1. Si no sabe que significa 'afín', lea a Foley & Van Damme: básicamente cubre el escalado, la translación y rotación del objeto pero no la proyección. Ya que Allegro usa una función aparte para la proyección, las funciones de matriz sólo tienen que servir para la transformación afín, lo que significa que no hay que guardar la fila inferior de la matriz. Allegro asume que esta contiene (0,0,0,1), y por eso optimiza las funciones de manipulación de matrices.

Las matrices se almacenan en estructuras:

typedef struct MATRIX            - matriz de punto fijo
{
   fixed v[3][3];                - componente 3x3 de escalado y rotación
   fixed t[3];                   - componente x/y/z de translación
} MATRIX;

typedef struct MATRIX_f - matriz de coma flotante { float v[3][3]; - componente 3x3 de escalado y rotación float t[3]; - componente x/y/z de translación } MATRIX_f

extern MATRIX identity_matrix;
extern MATRIX_f identity_matrix_f;
Variable global que contiene la matriz con identidad 'vacía'. Multiplicar por la matriz de identidad no tiene ningún efecto.

void get_translation_matrix(MATRIX *m, fixed x, fixed y, fixed z);
void get_translation_matrix_f(MATRIX_f *m, float x, float y, float z);
Construye una matriz de translación, guardándola en m. Si se aplica a un punto (px, py, pz), esta matriz producirá el punto (px+x, py+y, pz+z). En otras palabras: mueve las cosas.

void get_scaling_matrix(MATRIX *m, fixed x, fixed y, fixed z);
void get_scaling_matrix_f(MATRIX_f *m, float x, float y, float z);
Construye una matriz de escalado, almacenándola en m. Cuando se aplica a un punto (px, py, pz), esta matriz produce un punto (px*x, py*y, pz*z). En otras palabras, agranda o empequeñece las cosas.

void get_x_rotate_matrix(MATRIX *m, fixed r);
void get_x_rotate_matrix_f(MATRIX_f *m, float r);
Construye las matrices de rotación del eje X, almacenándolas en m. Cuando se aplican a un punto, estas matrices lo rotarán sobre el eje X el ángulo especificado (en binario, 256 grados hacen un círculo).

void get_y_rotate_matrix(MATRIX *m, fixed r);
void get_y_rotate_matrix_f(MATRIX_f *m, float r);
Construye las matrices de rotación del eje Y, almacenándolas en m. Cuando se aplican a un punto, estas matrices lo rotarán sobre el eje Y el ángulo especificado (en binario, 256 grados hacen un círculo).

void get_z_rotate_matrix(MATRIX *m, fixed r);
void get_z_rotate_matrix_f(MATRIX_f *m, float r);
Construye las matrices de rotación del eje Z, almacenándolas en m. Cuando se aplican a un punto, estas matrices lo rotarán sobre el eje Z el ángulo especificado (en binario, 256 grados hacen un círculo).

void get_rotation_matrix(MATRIX *m, fixed x, fixed y, fixed z);
void get_rotation_matrix_f(MATRIX_f *m, float x, float y, float z);
Construye una matriz de transformación que rotará puntos en todos los ejes los grados especificados. (en binario, 256 grados hacen un círculo).

void get_align_matrix(MATRIX *m, fixed xfront, yfront, zfront, fixed xup, fixed yup, fixed zup);
Rota la matriz de tal forma que la alinea sobre las coordenadas de los vectores especificados (estos no tienen que ser normalizados o perpendiculares, pero up y front no pueden ser iguales). Un vector front de 1,0,0 y un vector up de 0,1,0 retornarán la matriz de identidad.

void get_align_matrix_f(MATRIX *m, float xfront, yfront, zfront, float xup, yup, zup);
Versión en coma flotante de get_align_matrix().

void get_vector_rotation_matrix(MATRIX *m, fixed x, y, z, fixed a);
void get_vector_rotation_matrix_f(MATRIX_f *m, float x, y, z, float a);
Construye una matriz de transformación que rotará puntos sobre todos los vectores x,y,z un ángulo especificado (en binario, 256 grados hacen un círculo).

void get_transformation_matrix(MATRIX *m, fixed scale, fixed xrot, yrot, zrot, x, y, z);
Construye una matriz de transformación que rotará puntos en todos los ejes los ángulos especificados (en binario, 256 grados hacen un círculo), escalará el resultado (pasa el valor 1 si no quiere cambiar la escala), y entonces los trasladará a la posición x, y, z requerida.

void get_transformation_matrix_f(MATRIX_f *m, float scale, float xrot, yrot, zrot, x, y, z);
Versión en coma flotante de get_transformation_matrix().

void get_camera_matrix(MATRIX *m, fixed x, y, z, xfront, yfront, zfront, fixed xup, yup, zup, fov, aspect);
Construye la matriz de cámara para trasladar objetos del espacio a una vista normalizada del espacio, preparada para la proyección de perspectiva. Los parámetros x, y, z especifican la posición de la cámara, xfront, yfront y zfront son los vectores 'de frente' que especifican hacia adonde apunta la cámara (estos pueden ser de cualquier tamaño, no es necesaria la normalización), y xup, yup y zup son los vectores de la dirección 'arriba'. El parámetro fov especifica el campo de visión (el ancho del foco de la cámara) en binario, haciendo 256 grados un círculo. Para proyecciones típicas, un campo de visión de entre 32 a 48 trabajara bien. Finalmente, la razón de aspecto es usada para el escalado en la dimensión Y relativamente al eje X, para que pueda ajustar las proporciones de la imagen final (ponga a uno para no escalar).

void get_camera_matrix_f(MATRIX_f *m, float x, y, z, xfront, yfront,zfront, float xup, yup, zup, fov, aspect);
Versión en coma flotante de get_camera_matrix().

void qtranslate_matrix(MATRIX *m, fixed x, fixed y, fixed z);
void qtranslate_matrix_f(MATRIX_f *m, float x, float y, float z);
Rutina optimizada para trasladar una matriz ya generada: esto simplemente añade el 'offset' de translación, por lo que no hay que crear dos matrices temporales y multiplicarlas.

void qscale_matrix(MATRIX *m, fixed scale);
void qscale_matrix_f(MATRIX_f *m, float scale);
Rutina optimizada para escalar una matriz ya generada: esto simplemente añade el factor de escalación, por lo que no hay que crear dos matrices temporales y multiplicarlas.

void matrix_mul(MATRIX *m1, MATRIX *m2, MATRIX *out);
void matrix_mul_f(MATRIX_f *m1, MATRIX_f *m2, MATRIX_f *out);
Multiplica dos matrices, almacenando el resultado en out (esto tiene que ser diferente de las matrices de entrada). La matriz resultante creará el mismo efecto que la combinación de m1 y m2. Esto es, si se aplica a un punto p, (p * out) = ((p * m1) * m2). Cualquier número de transformaciones puede ser concatenado de este modo. Fíjese que la matriz de multiplicación no es conmutativa, es decir: matrix_mul(m1, m2) != matrix_mul(m2, m1).

fixed vector_length(fixed x, fixed y, fixed z);
float vector_length_f(float x, float y, float z);
Calcula la longitud del vector (x, y, z), usando ese buen teorema de Pitágoras.

void normalize_vector(fixed *x, fixed *y, fixed *z);
void normalize_vector_f(float *x, float *y, float *z);
Convierte un vector (*x, *y, *z) a un vector normalizado. Este apunta en la misma dirección que el vector original, pero tiene una longitud de uno.

fixed dot_product(fixed x1, y1, z1, x2, y2, z2);
float dot_product_f(float x1, y1, z1, x2, y2, z2);
*** Esto no lo he sabido traducir bien. ***
Calcula el producto (x1, y1, z1) . (x2, y2, z2), devolviendo el resultado. Calculates the dot product (x1, y1, z1) . (x2, y2, z2), returning the result.

void cross_product(fixed x1, y1, z1, x2, y2, z2, *xout, *yout, *zout);
void cross_product_f(float x1, y1, z1, x2, y2, z2, *xout, *yout, *zout);
Calcula el cruce de producto (x1, y1, z1) x (x2, y2, z2), almacenando el resultado en (*xout, *yout, *zout). El resultado del producto es perpendicular a los dos vectores de entrada, para que pueda ser usado para generar las normales de los polígonos.

fixed polygon_z_normal(V3D *v1, V3D *v2, V3D *v3);
float polygon_z_normal_f(V3D_f *v1, V3D_f *v2, V3D_f *v3);
Encuentra la componente Z de la normal de un vector de tres vértices especificados (que deben ser parte de un polígono convexo). Esto es usado principalmente en la ocultación de caras. Las caras traseras de un poliedro cerrado nunca son visibles al espectador, y por tanto no necesitan ser dibujadas. Esto puede ocultar aproximadamente la mitad de los polígonos de una escena. Si la normal es negativa, el polígono se puede eliminar, si es cero, el polígono está perpendicular a la pantalla.

void apply_matrix(MATRIX *m, fixed x, y, z, *xout, *yout, *zout);
void apply_matrix_f(MATRIX_f *m, float x, y, z, *xout, *yout, *zout);
Multiplica el punto (x, y, z) por la transformación de la matriz m, almacenando el resultado en el punto (*xout, *yout, *zout).

void set_projection_viewport(int x, int y, int w, int h);
Ajusta el punto de visión usado para escalar la salida de la función persp_project(). Pase las dimensiones de la pantalla y el área donde la quiere dibujar, que típicamente será 0, 0, SCREEN_W, SCREEN_H.

void persp_project(fixed x, y, z, *xout, *yout);
void persp_project_f(float x, y, z, *xout, *yout);
Proyecta el punto 3d (x, y, z) del espacio sobre una pantalla 2d, almacenando el resultado en (*xout, *yout) usando los parámetros anteriormente ajustados por set_projection_viewport(). Esta función proyecta desde la pirámide de vista normalizada, que tiene una cámara en el origen apuntando al eje z positivo. El eje x va de izquierda a derecha, y va de arriba a abajo, y z se incrementa con la profundidad de la pantalla. La cámara tiene un ángulo de visión de 90 grados, es decir, los planos x=z y -x=z serán los bordes izquierdo y derecho de la pantalla, y los planos y=z y -y=z serán la parte superior e inferior de la pantalla. Si quiere un campo de visión diferente a la posición de la cámara, debería transformar todos sus objetos con la matriz de visión apropiada. Ejemplo, para obtener el efecto de haber girado la cámara 10 grados a la izquierda, rote todos sus objetos 10 grados a la derecha.




Volver al Indice