Principal | Gráficos 3D | Gráficos 2D | Fractales | Math | Códigos | Tutoriales | Links

Cámara en OpenGL

Aqui vamos a ver como visualizar y recorrer un mundo tridimencional que nosotros mismo creamos usando una sencilla camara para ello, esta se puede desplazar en el plano XZ, puede avanzar, retroceder, desplazarce lateralmente al estilo del Quake, rotar la camara, es decir nos permite ver en cualquier direccion.

No realizo deteccion de coliciones, la camara puede atravesar los objetos de la escena.


En el movimiento de la camara se distinguen claramente en dos partes:

  • Movimiento de la camara a una nueva posicion: Es decir desplazar la camara a otro punto. Cuando se mueve la camara a otro sitio, lo que se hace en realidad es mover todos los vertices de la escena en sentido opuesto al de la camara.
  • Movimiento de rotacion de la camara: Aqui no se mueve la camara del sitio, sino que lo que se hace es rotarla para que enfoque a otra pocision. Aqui tambien se realiza el movimiento de rotacion en sentido opuesto, es decir, si se rota la camara 45 grados en un determinado eje , lo que se hace es rotar todos los vertices de la escena 45 grados en el mismo eje, pero en sentido opuesto.

Los controles de la camara son :

"w" : desplazamiento hacia adelante
"s" : desplazamiento hacia atras
"a" y "d" : desplazamiento laterales a la izquierda y a la derecha, al estilo del Quake.
con las teclas de direccion del teclado: se rota la camara, a la izquierda, derecha, abajo y arriba.
con el mouse: se rota la camara, a la izquierda, derecha, arriba y abajo.

El programa carga los objetos que componen la escena y la definicion de los materiales (el color) desde dos archivo de texto.

La escena fue modelada en el 3D Studio MAX, aqui una vista en moco wireframe que nos permite ver los poligonos que la forman.

Esta es la salida del programa.

Código Fuente

Todos los archivos necesarios para la compilación, el código fuente, el ejecutable, la archivo con los objetos de la escena, y los materiales: camara.zip

El codigo esta compilado con VisualC++ 6.0, uso OpenGL, podes cargar el proyecto abriendo directamente el archivo camara.dsw, si vas a utilizar una version anterior del compilador no te olvides que cuando armas el proyecto incluir las librerias opengl32.lib, glut32.lib y glu32.lib

/*
Autor  = Ramiro Alcocer
email  = valcoey@hotmail.com
web    = www.oocities.org/valcoey/index.html
*/
#include <GL/glut.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define M_PI			3.141592653
#define DEF_dollyStepSize	1.0
#define DEF_angleSensitivity	1.0

struct Point3f
	{
	float x, y, z;
	float u, v;
	};

struct Face
	{
	int material;				
	int vertexIndices[3];
	int smoothingGroup;
	Point3f normal[3];
	Point3f faceNormal;
	};

struct Object3D
	{
	int nVertices;
	Point3f *pVertices;			
	int nFaces;
	Face *pFaces;						
	};

struct Material
{
  float diffuse[4];          
  float ambient[4];         
  float specular[4];        
  float shininess;           
};

Object3D	*object=NULL;
int		nObjetos=0;
Material	*pMaterial;

float LightPos0[] = { 0.75f, 1.0f, 1.0f, 0.0f};  
float LightAmb0[] = { 0.75f, 0.75f, 0.75f, 1.0f}; 
float LightDif0[] = { 1.0f, 1.0f, 1.0f, 1.0f};   
float LightSpc0[] = { 1.0f, 1.0f, 1.0f, 1.0f}; 

GLfloat	camX = 0.0;
GLfloat	camY = 8.0;
GLfloat	camZ = -40.0;
GLfloat	camYaw = 180.0;
GLfloat	camPitch = 0.0;

int	isInMouseDrag = false;
int	mouseIsInverted = true;
int	viewPortCenterX = -1;
int	viewPortCenterY = -1;
int	oldCursorID = 0;
int	oldCursorX = 0;
int	oldCursorY = 0;

void ReadMaterial(char *filename)
{
FILE	*f;
char	line[80];
char	mode[15];
char	trash[9];
int		i,numMaterial;

if((f = fopen(filename, "rt"))==NULL)
	{
	printf("File Not Found : %s\n",filename);
	exit(1);
	}
fgets(line, 80, f );
sscanf(line,"%s %d", &mode, &numMaterial);
pMaterial= new Material[numMaterial];
for (i=0; i<numMaterial; i++)
	{
	fgets(line, 80, f);
	sscanf(line, "%s %f %f %f %f",&trash, &pMaterial[i].ambient[0], &pMaterial[i].ambient[1], 
		                                  &pMaterial[i].ambient[2], &pMaterial[i].ambient[3]);
	fgets(line, 80, f);
	sscanf(line, "%s %f %f %f %f",&trash, &pMaterial[i].diffuse[0], &pMaterial[i].diffuse[1], 
		                                  &pMaterial[i].diffuse[2], &pMaterial[i].diffuse[3]);
	fgets(line, 80, f);
	sscanf(line, "%s %f %f %f %f",&trash, &pMaterial[i].specular[0], &pMaterial[i].specular[1], 
		                                  &pMaterial[i].specular[2], &pMaterial[i].specular[3]);
	fgets(line, 80, f);
	sscanf(line, "%s %f", &trash, &pMaterial[i].shininess);
	}
fclose(f);
}

void CargarModelo(char *filename)
{
 FILE  *file;
 char  *tempString = new char [80];
 char  trash[15];
 float tempX, tempY, tempZ;
 int   tempA, tempB, tempC;
 int   indexMaterial, indexSmoothing;
 int   i,j;

 if ((file = fopen(filename, "rt"))==NULL)
	{
	 printf("No puedo abrir el archivo\n");
	 exit(1);
	}
 while(strncmp(tempString, "Objetos",7))
	{
	fscanf(file, "%s", tempString);
	if (feof(file))
		{
		printf("String \"Objetos\" no existe\n");
		exit(1);
		}
	}
 fgetc(file);			
 fscanf(file, "%d", &nObjetos);
 object=new Object3D[nObjetos];
 printf("Objetos en la escena : %d\n", nObjetos);
 for (j=0; j<nObjetos; j++)
	{
	while(strncmp(tempString, "Vertices",8))
		{
		fscanf(file, "%s", tempString);
		if (feof(file))
			{
			printf("String \"Vertices\" no existe\n");
			exit(1);
			}
		}
	fgetc(file);				
	fscanf(file, "%d", &object[j].nVertices);
	object[j].pVertices = new Point3f[object[j].nVertices];
	for (i=0; i<object[j].nVertices; i++)
		{
		//si mi archivo tiene las coordenadas de mapeo de textura utilizo :
 		//fscanf(file, "%f %f %f %f %f\n", &tempX, &tempY, &tempZ, &tempU, &tempV);
 		fscanf(file, "%f %f %f\n", &tempX, &tempY, &tempZ);
		object[j].pVertices[i].x=tempX;
		object[j].pVertices[i].y=tempY;
		object[j].pVertices[i].z=tempZ;
		}
	while(strncmp(tempString, "Faces",5))
		{
		fscanf(file, "%s", tempString);
		if (feof(file))
			{
			printf("String \"Faces\" no existe\n");
			exit(1);
			}
		}
	fgetc(file);				
	fscanf(file, "%d", &object[j].nFaces);
	object[j].pFaces = new Face[object[j].nFaces];
	printf("objeto: %d vertices: %d faces: %d\n",j,  object[j].nVertices, object[j].nFaces);
	for (i=0; i<object[j].nFaces; i++)
		{
 		fscanf(file, "%d %d %d\n", &tempA, &tempB, &tempC);
		fscanf(file, "%s %d\n", &trash, &indexMaterial);
		fscanf(file, "%s %d\n", &trash, &indexSmoothing);
		object[j].pFaces[i].vertexIndices[0]=tempA;
		object[j].pFaces[i].vertexIndices[1]=tempB;
		object[j].pFaces[i].vertexIndices[2]=tempC;
		object[j].pFaces[i].material=indexMaterial;
		object[j].pFaces[i].smoothingGroup=indexSmoothing;
		}
	}
}

void CalcularNormales(void)
{
 float x1, y1, z1;
 float x2, y2, z2;
 float x3, y3, z3;
 float length;
 int   a, b, c;
 int   i, j;

 for (j=0; j<nObjetos; j++)
	{
	for (i=0; i<object[j].nFaces; i++)
		{
		Face& face = object[j].pFaces[i];
		a = face.vertexIndices[0];
		b = face.vertexIndices[1];
		c = face.vertexIndices[2];
		x1 = object[j].pVertices[b].x - object[j].pVertices[a].x;
		y1 = object[j].pVertices[b].y - object[j].pVertices[a].y;
		z1 = object[j].pVertices[b].z - object[j].pVertices[a].z;
		x2 = object[j].pVertices[c].x - object[j].pVertices[a].x;
		y2 = object[j].pVertices[c].y - object[j].pVertices[a].y;
		z2 = object[j].pVertices[c].z - object[j].pVertices[a].z;
		z3 = x1*y2 - y1*x2;
		x3 = y1*z2 - z1*y2;
		y3 = z1*x2 - x1*z2;
		length = sqrt(x3*x3 + y3*y3 + z3*z3);
		if (length == 0)
			{
			face.faceNormal.x=1;
			face.faceNormal.y=1;
			face.faceNormal.z=1;
			}
		else
			{
			face.faceNormal.x=x3/length;
			face.faceNormal.y=y3/length;
			face.faceNormal.z=z3/length;
			}
		face.normal[0].x=face.normal[1].x=face.normal[2].x=face.faceNormal.x;
		face.normal[0].y=face.normal[1].y=face.normal[2].y=face.faceNormal.y;
		face.normal[0].z=face.normal[1].z=face.normal[2].z=face.faceNormal.z;
		}
	}
}

void killObject(void)
{
 for (int i=0; i<nObjetos; i++)
	{
	delete[] object[i].pFaces;
	object[i].pFaces = NULL;
	object[i].nFaces = 0;
	delete[] object[i].pVertices;
	object[i].pVertices = NULL;
	object[i].nVertices = 0;
	}
 delete [] object;
 delete[] pMaterial;
 pMaterial = NULL;
}

void enterMouseDrag( int x, int y )
{
 if( isInMouseDrag )
	return;
 isInMouseDrag = true;
 if( viewPortCenterX < 0 )
	{
	viewPortCenterX = glutGet( GLUT_WINDOW_WIDTH ) / 2;
	viewPortCenterY = glutGet( GLUT_WINDOW_HEIGHT ) / 2;
	}
 oldCursorID = glutGet( GLUT_WINDOW_CURSOR );
 oldCursorX = x;
 oldCursorY = y;
 glutSetCursor( GLUT_CURSOR_NONE );
 glutWarpPointer( viewPortCenterX, viewPortCenterY );
}

void exitMouseDrag( int x, int y )
{
 if( !isInMouseDrag )
	return;
 isInMouseDrag = false;
 glutSetCursor( oldCursorID );
 glutWarpPointer( oldCursorX, oldCursorY );
}

void clampCamera()
{
 if( camPitch > 90.0 )
	camPitch = 90.0;
 else if( camPitch < -90.0 )
	camPitch = -90.0;
 while( camYaw < 0.0 )
	camYaw += 360.0;
 while( camYaw >= 360.0 )
	camYaw -= 360.0;
}

void dollyCamera( GLfloat dollyBy, GLfloat dollyAngle )
{
 GLfloat actualAngleInRadians;
 actualAngleInRadians = ( ( camYaw + dollyAngle ) * M_PI / 180.0 );
 camX -= sin( actualAngleInRadians ) * dollyBy * DEF_dollyStepSize;
 camZ -= cos( actualAngleInRadians ) * dollyBy * DEF_dollyStepSize;
}

void keyboard(unsigned char key, int x, int y)
{
   switch (key) {
// Movimiento hacia adelante
	case 'w':
	case 'W':
		dollyCamera( DEF_dollyStepSize, 0.0 );
		break;

// Movimiento hacia atras
	case 's':
	case 'S':
		dollyCamera( DEF_dollyStepSize, 180.0 );
		break;

// Strafe hacia la izquierda
	case 'a':
	case 'A':
		dollyCamera( DEF_dollyStepSize, 90.0 );
		break;

// Strafe derecha
	case 'd':
	case 'D':
		dollyCamera( DEF_dollyStepSize, 270.0 );
		break;


// Toggle 'inverted' mouse.
	case 'i':
	case 'I':
		mouseIsInverted = !mouseIsInverted;
		break;

//Esc salir
	case 27:				
		exitMouseDrag( 0, 0 );
		exit( 0 );
		break;
  }
}

void specialFunc( int key, int x, int y )
{
	switch( key )
	{
	//giro  a la izquierda
	case GLUT_KEY_LEFT:
		camYaw += 1.0;
		clampCamera();
		break;

	// giro a la derecha.
	case GLUT_KEY_RIGHT:
		camYaw -= 1.0;
		clampCamera();
		break;

	// mirar hacia arriba.
	case GLUT_KEY_UP:
		camPitch += 1.0;
		clampCamera();
		break;

	// mirar hacia abajo
	case GLUT_KEY_DOWN:
		camPitch -= 1.0;
		clampCamera();
		break;
	}
}

void mouseFunc( int button, int state, int x, int y )
{
 if( button == GLUT_LEFT_BUTTON && state == GLUT_DOWN )
	{
	if( !isInMouseDrag )
		enterMouseDrag( x, y );
	else
		exitMouseDrag( x, y );
	}
}

void allMotionFunc( int x, int y )
{
 int deltaX, deltaY;

 if( !isInMouseDrag )
	return;
 deltaX = x - viewPortCenterX;
 deltaY = y - viewPortCenterY;
 if( deltaX == 0 && deltaY == 0 )
	return;
 glutWarpPointer( viewPortCenterX, viewPortCenterY );
 camYaw -= DEF_angleSensitivity * deltaX;
 camPitch -= DEF_angleSensitivity * deltaY * ( mouseIsInverted ? -1.0 : 1.0 );
 clampCamera();
 glutPostRedisplay();
}

void reshape(int w, int h)
{
 if (!h)
	return;
 glViewport(0, 0,  (GLsizei) w, (GLsizei) h);
 glMatrixMode(GL_PROJECTION);
 glLoadIdentity();
 gluPerspective(60.0, (GLfloat) w/(GLfloat) h, 1.0, 1000.0);
 glMatrixMode(GL_MODELVIEW);
 glLoadIdentity();
 viewPortCenterX = w / 2;
 viewPortCenterY = h / 2;
}


void display(void)
{
 int a, b, c;
 int i, j, k;
 
 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 glPushMatrix();
 
 glRotatef( -camPitch, 1.0, 0.0, 0.0 );
 glRotatef( -camYaw, 0.0, 1.0, 0.0 );
 glTranslatef( -camX, -camY, -camZ );

 glBegin(GL_TRIANGLES);
 for (j=0; j<nObjetos; j++)
	{
	for (i=0; i<object[j].nFaces; i++)
		{
		const Face& face = object[j].pFaces[i];
		k=face.material;
		glMaterialfv(GL_FRONT, GL_AMBIENT, pMaterial[k].ambient);
		glMaterialfv(GL_FRONT, GL_DIFFUSE, pMaterial[k].diffuse);
		glMaterialfv(GL_FRONT, GL_SPECULAR, pMaterial[k].specular);
		glMaterialf(GL_FRONT, GL_SHININESS, pMaterial[k].shininess);
		a=face.vertexIndices[0];
		b=face.vertexIndices[1];
		c=face.vertexIndices[2];
		glNormal3f(face.faceNormal.x, face.faceNormal.y, face.faceNormal.z); 
		glVertex3f(object[j].pVertices[a].x, object[j].pVertices[a].y, object[j].pVertices[a].z);
		glVertex3f(object[j].pVertices[b].x, object[j].pVertices[b].y, object[j].pVertices[b].z);
		glVertex3f(object[j].pVertices[c].x, object[j].pVertices[c].y, object[j].pVertices[c].z);
		}
	}
 glEnd();
 glPopMatrix();
 glFlush();
 glutSwapBuffers();
}

void init(void)
{
 glClearColor(0.0, 0.0, 0.0, 0.0);
 glShadeModel(GL_SMOOTH);
 glCullFace(GL_BACK);
 glEnable(GL_DEPTH_TEST);
 glEnable(GL_CULL_FACE);

 glLightfv(GL_LIGHT0, GL_POSITION, LightPos0);     
 glLightfv(GL_LIGHT0, GL_AMBIENT, LightAmb0);         
 glLightfv(GL_LIGHT0, GL_DIFFUSE, LightDif0);        
 glLightfv(GL_LIGHT0, GL_SPECULAR, LightSpc0); 
 glEnable(GL_LIGHT0); 
 glEnable(GL_LIGHTING);
 CargarModelo("quake.dat");
 ReadMaterial("material.mat");
 CalcularNormales();
 enterMouseDrag( 0, 0 );
}

void idle(void)
{
 glutPostRedisplay();
}

int main(int argc, char **argv)
{
 glutInit(&argc, argv);
 glutInitWindowSize(500, 375);
 glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
 glutCreateWindow("Test 3D");
 init();
 glutDisplayFunc(display);
 glutReshapeFunc(reshape);
 glutKeyboardFunc(keyboard);
 glutSpecialFunc( specialFunc );
 glutMouseFunc( mouseFunc );
 glutMotionFunc( allMotionFunc );
 glutPassiveMotionFunc( allMotionFunc );
 glutIdleFunc(idle);
 glutMainLoop();
 killObject();
 return 0;
}

Principal | Gráficos 3D | Gráficos 2D | Fractales | Math | Códigos | Tutoriales | Links