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;extern MATRIX identity_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
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.