Para la visualización de un objeto 3D, se requieren 3 pasos:
En primer lugar se necesita una base de datos con las coordenadas (x,y,z) de los vértices y ademas los polígonos que forman el objeto.
En segundo lugar el objeto primero se rota y luego se traslada hasta la localización adecuada, con lo que se obtienen unas nuevas coordenadas (x,y,z) para los vértices.
Finalmente, se eliminan los polígonos que no son visibles por el observador, se aplica la perspectiva y se dibuja el objeto en la pantalla.
Base de Datos del Objeto 3D
Mi objeto esta compuesto por polígonos de tres lados, y cada poligono tiene 3 vértices cuyas coordenadas son (x,y,z).
Número máximo de vértices que puede tener el objeto
#define NUM_VERTICES
Cantidad máxima de polígonos que tiene el objeto
#define NUM_FACES
Definición de un vértice
typedef struct VTX
{
double x, y, z;
};
Definición de un polígono
typedef struct TRI
{
int a, b, c;
};
Estructura para el ordenamiento qsort, donde para cada polígono
almaceno la cordenada z y el indice del polígono
d=coordenada z del poligono, index=indice del polígono
typedef struct LSTR
{
int d;
int index;
};
Tabla que contiene los vértices originales
VTX points[NUM_VERTICES];
Tabla con los vértices transformados
VTX outpoints[NUM_VERTICES];
Tabla que contiene los polígonos
TRI faces[NUM_FACES];
Tabla de polígonos para aplicar el ordenamiento qsort
LSTRI ListFaces[NUM_FACES];
Número de vértices, y polígonos del objeto
int vertices, poligonos;
Vértices del objeto
V0 = points[0].x, points[0].y, points[0].z
V1 = points[1].x, points[1].y, points[1].z
V7 = points[7].x, points[7].y, points[7].z
Polígonos del objeto
faces[0].a = 0 faces[0].b = 2 faces[0].c = 1
faces[1].a = 2 faces[1].b = 3 faces[1].c = 1
faces[5].a = 6 faces[5].b = 7 faces[5].c = 5
Matrices de Transformación 3D
Sistema de Coordenadas
El sistema de coordenadas tridimensional utilizado es el llamado de la mano izquierda
Rotación en torno al eje X
Código en C
void matriz_rotacion_x(MATRIZ *m, double angulo)
{
double tmpsin, tmpcos;
tmpsin=sin(angulo);
tmpcos=cos(angulo);
*m = matriz_identidad;
m->v[1][1] = tmpcos;
m->v[1][2] = tmpsin;
m->v[2][1] = -tmpsin;
m->v[2][2] = tmpcos;
}
Ejemplo: matriz_rotacion_x(&Rx, 0.7)
Esto crea la matriz de rotación alrededor del eje X, donde Rx es una matriz 4x4 y 0.7 es el angulo de rotación, en radianes.
Rotación en torno al eje Y
Código en C.
void matriz_rotacion_y(MATRIZ *m, double angulo)
{
double tmpsin, tmpcos;
tmpsin=sin(angulo);
tmpcos=cos(angulo);
*m = matriz_identidad;
m->v[0][0] = tmpcos;
m->v[0][2] = -tmpsin;
m->v[2][0] = tmpsin;
m->v[2][2] = tmpcos;
}
Rotación en torno al eje Z
Código en C
void matriz_rotacion_z(MATRIZ *m, double angulo)
{
double tmpsin, tmpcos;
tmpsin=sin(angulo);
tmpcos=cos(angulo);
*m = matriz_identidad;
m->v[0][0] = tmpcos;
m->v[0][1] = tmpsin;
m->v[1][0] = -tmpsin;
m->v[1][1] = tmpcos;
}
Ejemplo : matriz_rotacion_z(&Rz, 0.7)
donde Rz es la matriz de 4x4, y 0.7 es el angulo de rotación en torno al eje Z.
Traslación
Código en C
void matriz_traslacion(MATRIZ *m, double Tx, double Ty, double Tz)
{
*m = matriz_identidad;
m->v[3][0] = Tx;
m->v[3][1] = Ty;
m->v[3][2] = Tz;
}
ESTRUCTURA DE TODO EL PROGRAMA.
#include <graphics.h>
#include <stdlib.h>
#include <math.h>
#include <stdio.h>
#include <conio.h>
#include <dos.h>
#include "svga256.h"
#define NUM_VERTICES 512
#define NUM_FACES 1024
typedef struct MATRIZ
{
double v[4][4];
};
typedef struct VTX
{
double x, y, z;
};
typedef struct TRI
{
int a, b, c;
};
typedef struct LSTRI
{
int d;
int index;
};
VTX points[NUM_VERTICES];
VTX outpoints[NUM_VERTICES];
TRI faces[NUM_FACES];
LSTRI ListFaces[NUM_FACES];
int vertices, poligonos;
//posicion de la fuente de luz
double xlight=1.0, ylight=-1.0, zlight=1.0;
unsigned char paleta[256][3];
MATRIZ matriz_identidad = {
{
{ 1.0, 0.0, 0.0, 0.0},
{ 0.0, 1.0, 0.0, 0.0},
{ 0.0, 0.0, 1.0, 0.0},
{ 0.0, 0.0, 0.0, 1.0}
}
};
double persp_xoffset;
double persp_yoffset;
double D;
int huge deteccion()
{
int modo;
clrscr();
printf("Selecciona un modo de video...\n");
printf(" 0) 320x200x256 VGA\n");
printf(" 1) 640x400x256 SVGA\n");
printf(" 2) 640x480x256 SVGA\n");
printf(" 3) 800x600x256 SVGA\n");
printf(" 4) 1024x768x256 SVGA\n\n>");
scanf("%d", &modo);
return modo;
}
void aplicar_matriz(MATRIZ *m, double x, double y, double z, double *xout, double *yout, double *zout)
{
*xout = x*m->v[0][0] + y*m->v[1][0] + z*m->v[2][0] + 1.0*m->v[3][0];
*yout = x*m->v[0][1] + y*m->v[1][1] + z*m->v[2][1] + 1.0*m->v[3][1];
*zout = x*m->v[0][2] + y*m->v[1][2] + z*m->v[2][2] + 1.0*m->v[3][2];
}
void matriz_multiplicacion(MATRIZ *m1, MATRIZ *m2, MATRIZ *out)
{
int i, j, k;
for (i=0; i<4; i++) {
for (j=0; j<4; j++) {
out->v[i][j] = 0.0;
for (k=0; k<4; k++)
out->v[i][j] = out->v[i][j] + m1->v[i][k]*m2->v[k][j];
}
}
}
void matriz_traslacion(MATRIZ *m, double Tx, double Ty, double Tz)
{
*m = matriz_identidad;
m->v[3][0] = Tx;
m->v[3][1] = Ty;
m->v[3][2] = Tz;
}
void matriz_escalamiento(MATRIZ *m, double Ex, double Ey, double Ez)
{
*m = matriz_identidad;
m->v[0][0] = Ex;
m->v[1][1] = Ey;
m->v[2][2] = Ez;
}
void matriz_rotacion_x(MATRIZ *m, double angulo)
{
double tmpsin, tmpcos;
tmpsin=sin(angulo);
tmpcos=cos(angulo);
*m = matriz_identidad;
m->v[1][1] = tmpcos;
m->v[1][2] = tmpsin;
m->v[2][1] = -tmpsin;
m->v[2][2] = tmpcos;
}
void matriz_rotacion_y(MATRIZ *m, double angulo)
{
double tmpsin, tmpcos;
tmpsin=sin(angulo);
tmpcos=cos(angulo);
*m = matriz_identidad;
m->v[0][0] = tmpcos;
m->v[0][2] = -tmpsin;
m->v[2][0] = tmpsin;
m->v[2][2] = tmpcos;
}
void matriz_rotacion_z(MATRIZ *m, double angulo)
{
double tmpsin, tmpcos;
tmpsin=sin(angulo);
tmpcos=cos(angulo);
*m = matriz_identidad;
m->v[0][0] = tmpcos;
m->v[0][1] = tmpsin;
m->v[1][0] = -tmpsin;
m->v[1][1] = tmpcos;
}
void persp_projeccion(double x, double y, double z, double *xout, double *yout)
{
double z1 = 1.0 / z;
*xout = ((x * z1) * D) + persp_xoffset;
*yout = ((y * z1) * D) + persp_yoffset;
}
void set_projeccion_viewport(int w, int h)
{
D = 800;
persp_xoffset = w/2;
persp_yoffset = h/2;
}
void ver_matriz(MATRIZ *m)
{
printf("%lf %lf %lf %lf\n", m->v[0][0], m->v[0][1], m->v[0][2], m->v[0][3]);
printf("%lf %lf %lf %lf\n", m->v[1][0], m->v[1][1], m->v[1][2], m->v[1][3]);
printf("%lf %lf %lf %lf\n", m->v[2][0], m->v[2][1], m->v[2][2], m->v[2][3]);
printf("%lf %lf %lf %lf\n", m->v[3][0], m->v[3][1], m->v[3][2], m->v[3][3]);
}
double producto_escalar(double x1, double y1, double z1, double x2, double y2, double z2)
{
return ((x1 * x2) + (y1 * y2) + (z1 * z2));
}
void producto_vectorial(VTX v1, VTX v2, VTX *out)
{
out->x = (v1.y * v2.z) - (v1.z * v2.y);
out->y = (v1.z * v2.x) - (v1.x * v2.z);
out->z = (v1.x * v2.y) - (v1.y * v2.x);
}
double modulo_vector(double x, double y, double z)
{
return sqrt(x*x + y*y + z*z);
}
void vector_normalizado(double *x, double *y, double *z)
{
double length = 1.0 / modulo_vector(*x, *y, *z);
*x *= length;
*y *= length;
*z *= length;
}
//Esta funcion permite conocer si un poligono esta de cara
//o de espaldas al observador, segun devuelva un valor mayor
//o menor que cero
double Hidden(VTX v1, VTX v2, VTX v3)
{
VTX u, v;
VTX out;
double xout, yout, zout;
double temp;
u.x = v2.x - v1.x;
u.y = v2.y - v1.y;
u.z = v2.z - v1.z;
v.x = v3.x - v1.x;
v.y = v3.y - v1.y;
v.z = v3.z - v1.z;
producto_vectorial(u, v, &out);
temp = producto_escalar(out.x, out.y, out.z, 0.0, 0.0, -1.0);
return temp;
}
//funcion usada para calcular el nivel de intensidad de la
//luz que recibe un poligono
int Light(VTX v1, VTX v2, VTX v3)
{
VTX u, v, out;
double temp;
u.x = v2.x - v1.x;
u.y = v2.y - v1.y;
u.z = v2.z - v1.z;
v.x = v3.x - v1.x;
v.y = v3.y - v1.y;
v.z = v3.z - v1.z;
//out es el vector normal al poligono
producto_vectorial(u, v, &out);
vector_normalizado(&out.x, &out.y, &out.z);
vector_normalizado(&xlight, &ylight, &zlight);
//El resultado de todas estas operaciones dar un valor comprendido
//entre 0.0 (m¡nima luz) y 1.0f (m xima luz)
temp = producto_escalar(out.x, out.y, out.z, xlight, ylight, zlight);
temp = 256.0*temp;
if (temp<0.0)
temp=0.0;
else
if (temp>255.0)
temp=255.0;
return (int) (temp);
}
//me dibuja un poligono solido, el color lo determino
//de acuerdo al nivel de luz que recibe.
void DibujarPoligono(int num)
{
int A, B, C;
int poly[6];
int i, color;
i=ListFaces[num].index;
A=faces[i].a;
B=faces[i].b;
C=faces[i].c;
//verifico si el poligono es visible o no
if (Hidden(outpoints[A], outpoints[B], outpoints[C])<0.0)
{
//se llama a la funci¢n que calcula la luz de un pol¡gono
color = Light(outpoints[A], outpoints[B], outpoints[C]);
poly[0]=(int) outpoints[A].x;
poly[1]=(int) outpoints[A].y;
poly[2]=(int) outpoints[B].x;
poly[3]=(int) outpoints[B].y;
poly[4]=(int) outpoints[C].x;
poly[5]=(int) outpoints[C].y;
setfillstyle(SOLID_FILL, color);
setcolor(color);
fillpoly(3, poly);
}
}
int comparar(const void * e1, const void * e2)
{
int d1, d2;
LSTRI *q1 = (LSTRI *)e1;
LSTRI *q2 = (LSTRI *)e2;
d1=q1->d;
d2=q2->d;
return (d2-d1);
}
void setvgapalette256(DacPalette256 *PalBuf)
{
struct REGPACK reg;
reg.r_ax = 0x1012;
reg.r_bx = 0;
reg.r_cx = 256;
reg.r_es = FP_SEG(PalBuf);
reg.r_dx = FP_OFF(PalBuf);
intr(0x10,®);
}
void LeerObjeto(char *nombre)
{
int i;
FILE *in = fopen(nombre, "r");
if (!in)
return;
fscanf(in, "%d\n", &vertices);
//printf("%d\n", vertices);
for (i = 0; i<vertices; i++)
{
fscanf(in, "%lf %lf %lf\n", &points[i].x, &points[i].y, &points[i].z);
//printf("%lf %lf %lf\n", points[i].x, points[i].y, points[i].z);
};
fscanf(in, "%d\n", &poligonos);
//printf("%d\n", poligonos);
for (i = 0; i<poligonos; i++)
{
fscanf(in, "%d %d %d\n", &faces[i].a, &faces[i].b, &faces[i].c);
//printf("%d %d %d\n", faces[i].a, faces[i].b, faces[i].c);
}
//printf("Presionar una tecla para continuar...");
//getch();
fclose(in);
}
int main(void)
{
MATRIZ Rx, Ry, Rz, Rot;
MATRIZ Tr, Mat;
MATRIZ temp;
int i;
//Carga el modelo en memoria
LeerObjeto("toro.dat");
//Inicializa el modo grafico
int driver = DETECT, modo = DETECT;
installuserdriver("Svga256", deteccion);
initgraph(&driver, &modo, "");
for (i=0; i<256; i++)
{
paleta[i][0]=i/4;
paleta[i][1]=i/4;
paleta[i][2]=i/4;
}
//me cambia la paleta de colores a una escala de 64 tonos de grises
setvgapalette256(&paleta);
for (i=0; i<256; i++)
{
setcolor(i);
line(i,0,i,20);
};
set_projeccion_viewport(getmaxx(), getmaxy());
//Inicializa las matrices de rotacion y
//traslacion.
matriz_rotacion_x(&Rx, -0.785);
matriz_rotacion_y(&Ry, 0.523);
matriz_rotacion_z(&Rz, 0.0);
matriz_traslacion(&Tr, 0.0, 0.0, -350.0);
//multiplica las matrices
//Mat=Rx*Ry*Rz*Tr (matriz de transformacion)
matriz_multiplicacion(&Rx, &Ry, &temp);
matriz_multiplicacion(&temp, &Rz, &Rot);
matriz_multiplicacion(&Rot, &Tr, &Mat);
//aplica la matriz de transformacion a cada uno
//de los vertices del modelo.
for (i=0; i<vertices; i++)
{
aplicar_matriz(&Mat, points[i].x, points[i].y, points[i].z,
&outpoints[i].x, &outpoints[i].y, &outpoints[i].z);
persp_projeccion(outpoints[i].x, outpoints[i].y, outpoints[i].z,
&outpoints[i].x, &outpoints[i].y);
};
//construyo una tabla con las coordenadas z, de cada uno
//de los poligonos.
for (i=0; i<poligonos; i++)
{
ListFaces[i].d = (int) fabs(outpoints[faces[i].a].z + outpoints[faces[i].b].z + outpoints[faces[i].c].z);
ListFaces[i].index=i;
}
//ordeno los poligonos de acuerdo a z.
qsort(ListFaces, poligonos, sizeof(ListFaces[0]), comparar);
//dibujo los poligonos
for (i=0; i<poligonos; i++)
DibujarPoligono(i);
getch();
closegraph();
return 0;
}