[Tutorial] III.6 - Triangulos, cuadrilateros y glutMotionFunc() - Parte 2

Ver el tema anterior Ver el tema siguiente Ir abajo

[Tutorial] III.6 - Triangulos, cuadrilateros y glutMotionFunc() - Parte 2

Mensaje  HarZe el Vie Jul 31, 2009 4:27 pm

El funcionamiento del programa para el usuario es sencillo, pulsando con el botón derecho del ratón sobre un vértice, lo seleccionas; si mantienes pulsado el botón izquierdo del ratón mientras lo mueves, estarás cambiando de sitio el vértice actualmente seleccionado.
Ahora analizamos el código, las novedades son muchas, el nivel ha subido, asi que, excepto algunas cosas muy básicas, explicaré todo.
Código:
typedef struct {
   GLfloat verticeXYZ[3];
   GLfloat colorRGB[3];
} Vertice;

typedef struct {
   float x,y;
   int izda, dcha;
} EstadoRaton;
Al principio, declaramos estos dos tipos de estructuras. typedef es una palabra reservada de C, que se parece al #define, poner la última palabra antes del punto y coma es como poner todo lo que hay del typedef en adelante sin contar esa última palabra. Con esto mismo como ejemplo, cada vez que pongas Vértice en realidad pones: struct { GLfloat verticeXYZ[3]; GLfloat colorRGB[3]; }. La estructura Vértice guarda dos vectores, uno guarda los tres datos XYZ de la posición de un vértice, el segundo guarda el color con 3 elementos, para RGB.
La segunda estructura es para guardar, y poder acceder luego, a los datos actuales del ratón, su posición previamente calculada en el espacio vectorial, y si está pulsado o no el botón izquierdo y derecho.
Código:
Vertice cuadrado[4] = {
   {{1,0,0},{1,0,0}},
   {{5,-4,0},{1,1,0}},
   {{9,0,0},{0,1,0}},
   {{5,4,0},{0,0,1}}
};
Vertice triangulo[3] = {
   {{-9,4,0},{1,0,0}},
   {{-5,-4,0},{0,1,0}},
   {{-1,4,0},{0,0,1}}
};
Es una aplicación de las anteriores estructuras, como un cuadrado tiene 4 vértices, creamos un array de 4 de tipo Vértice, dentro de cada elemento, hay dos elementos, y dentro de cada uno de ellos hay 3 más, lo mejor es organizarlo en líneas para mantener un orden y no equivocarnos. Para que quede más claro, el primer vértice del cuadrado, X es 1, Y es 0, Z es 0, y el color es 1 en ROJO, 0 en VERDE y 0 en AZUL, quedando rojo puro por color.
Código:
Vertice selector = {{-9,4,0},{1,1,1}};
EstadoRaton raton;
int ventana[2], vertice_sel;
GLfloat formato_global;
Por orden, la primera es un vértice, que sirve para un punto, ese punto blanco que nos indica qué vértice estamos moviendo, con su posición XYZ y color RGB, como cualquier vértice.
En la siguiente declaramos un estructura para guardar los datos del ratón, no le damos valores, pues aún no los sabemos.
Las siguientes variables son necesarias, ya veremos por qué, la primera guardará el ancho y alto de la ventana, y la siguiente nos dirá que vértice estamos seleccionando.
La última es para guardar el formato, por si se cambian las dimensiones de la ventana, para que todo siga encajando, ya veremos que se necesita sacar el formato de la función de Reshape.
IniciarGLUT() sigue como hasta ahora.
Código:
void PintarEscena() {
  glMatrixMode(GL_MODELVIEW);
  glClear(GL_COLOR_BUFFER_BIT);
  glLoadIdentity();
 
  int i;
 
  glBegin(GL_TRIANGLES);
      for (i=0; i<3; i++) {
         glColor3fv(triangulo[i].colorRGB);
         glVertex3fv(triangulo[i].verticeXYZ);
      }
  glEnd();
 
  glBegin(GL_QUADS);
        for (i=0; i<4; i++) {
         glColor3fv(cuadrado[i].colorRGB);
         glVertex3fv(cuadrado[i].verticeXYZ);
      }
  glEnd();
 
  glPointSize(7);
  glBegin(GL_POINTS);
      glColor3fv(selector.colorRGB);
      glVertex3fv(selector.verticeXYZ);
  glEnd();
 
  glutSwapBuffers();
}
El sistema funciona igual que con puntos y líneas, GL_TRIANGLES lee los vértices de 3 en 3, y GL_QUADS de 4 en 4, aquí usamos un bucle para dar los distintos vértices en forma de array que hay dentro de la estructura, cada uno con su color. En forma de vector, ahorras trabajo al PC.
Simplemente añadir que debes manejarte bien con las estructuras, y que finalmente ponemos a dibujar el punto que nos sirve de referencia, con un tamaño de 7 para verlo, porque sino...
A la función ReProyectar solo le hemos añadido esto:
Código:
...
  ventana[0] = w;
  ventana[1] = h;
...
formato_global = formato;
Para exportar esas variables, que ya veremos luego para que hacen falta.
Código:
float ObtenerPosPlanoX(float x, int ancho_ventana, int alto_ventana, float pos_x_min, float pos_x_max, float format) {
   float pos_x_relativa = ((float)x/ancho_ventana); //La posición relativa de 0 a 1 en X
   float pos_plano;
   
   if (ancho_ventana<=alto_ventana) pos_plano = (pos_x_min+((pos_x_max-pos_x_min)*pos_x_relativa));
   else pos_plano = ((pos_x_min * format)+(((pos_x_max-pos_x_min) * format)*pos_x_relativa));
   
   return pos_plano;
}

float ObtenerPosPlanoY(float y, int ancho_ventana, int alto_ventana, float pos_y_min, float pos_y_max, float format) {
   float pos_y_relativa = ((float)y/alto_ventana); //La posición relativa de 0 a 1 en X
   float pos_plano;
   
   if (ancho_ventana<=alto_ventana) pos_plano = -((pos_y_min / format)+(((pos_y_max-pos_y_min) / format)*pos_y_relativa));
   else pos_plano = -(pos_y_min+((pos_y_max-pos_y_min)*pos_y_relativa));
   
   return pos_plano;
}
Dos funciones casi iguales, creadas por mi, que sirven para resolver un problema que te puede llevar de cabeza (lo se por experiencia) pero ahora que está solucionado, solo queda entenderlo y usarlo.
El problema que se presenta es que la función asignada a glutMotionFunc() recibe la posición XY del ratón cada vez que ésta posición cambia. Pero es la posición sobre la pantalla, partiendo de 0,0 en la esquina de arriba a la izquierda, y por ejemplo, el ratón en el centro de la ventana tiene una posición 300,300; pero a nosotros nos interesa saber su posición en el espacio virtual en el que dibujamos, para situar ahí nuestro vértice o comprobar si está ahí alguno, al ser el centro, sabemos que es 0.0f,0.0f,0.0f, pero necesitamos una función que nos "traduzca" éstas posiciones, en realidad es sencillo.
La teoría cómo se "traduce": tenemos que conseguir una proporción. Por ejemplo, si tiene la ventana 100 de ancho, y está en una posición X de 40, sabemos que está a un 40% de la izquierda. Tras saber eso, podemos aplicarlo a nuestro espacio virtual. Si sabemos que va desde el -10 al 10, sabemos que tiene 20 de ancho, pues el 40% de 20 es 8. Ya sabemos que está a 8 unidades virtuales, y como empieza desde -10, la posición de nuestro ratón en el espacio virtual es... -2. Sencillo si lo vemos así. Ahora hay que pasar estas operaciones a una función, y eso he hecho. Analicemos la primera función, después de leer esto la entenderás mucho mejor, vamos operación por operación:
Datos necesarios (los atributos de la función): la posición X sobre pantalla, ancho de la ventana, alto de la ventana, extremo izquierdo de nuestro espacio virtual, extremo derecho, y el formato (por si ha sido deformada la ventana, ajustar bien).
Operaciones:
  • float pos_x_relativa = ((float)x/ancho_ventana), el porcentaje del que hablabamos antes, la posición relativa es la posición del ancho entre el total posible.
  • if (ancho_ventana<=alto_ventana) pos_plano = (pos_x_min+((pos_x_max-pos_x_min)*pos_x_relativa));, como pasa en ReProyectar(), el formato solo se aplica al eje más ancho, la operación es: la posición en X en el esp. virtual es igual al valor más pequeño posible en X mas el ancho total posible por la posición relativa.
  • else pos_plano = ((pos_x_min * format)+(((pos_x_max-pos_x_min) * format)*pos_x_relativa))
    , lo de antes pero aplicando el formato, porque si hemos llegado al else es que el ancho es mayor

Lo último es devolver el dato.
La función ObtenerPosPlanoY() tiene solo dos pequeños cambios: se aplica el formato de otra forma, cuando el alto es mayor. Lo segundo y más importante, en tu espacio virtual la zona negativa está hacia abajo, pero las coordenadas que recibes son mas pequeñas, más hacia arriba, asi que hay que invertirlo con un - delante.
No hay nada como experimentar para entender. Seguimos.
Código:
void ControlRaton(int button, int state, int x, int y) {
   if (button==GLUT_LEFT_BUTTON) {
      if (state==GLUT_DOWN) raton.izda = 1;
      else raton.izda = 0;
   }
   else if (button==GLUT_RIGHT_BUTTON) {
      if (state==GLUT_DOWN) {
            raton.dcha = 1;
         
         raton.x = ObtenerPosPlanoX(x,ventana[0],ventana[1],-10,10,formato_global);
         raton.y = ObtenerPosPlanoY(y,ventana[0],ventana[1],-10,10,formato_global);
   
         int i;
         for (i=0; i<3; i++) {
            if (raton.x > triangulo[i].verticeXYZ[0] - 0.5f &&
               raton.x < triangulo[i].verticeXYZ[0] + 0.5f &&
               raton.y > triangulo[i].verticeXYZ[1] - 0.5f &&
               raton.y < triangulo[i].verticeXYZ[1] + 0.5f ) {
               vertice_sel = i;
            }
         }
         for (i=0; i<4; i++) {
            if (raton.x > cuadrado[i].verticeXYZ[0] - 0.5f &&
               raton.x < cuadrado[i].verticeXYZ[0] + 0.5f &&
               raton.y > cuadrado[i].verticeXYZ[1] - 0.5f &&
               raton.y < cuadrado[i].verticeXYZ[1] + 0.5f ) {
               vertice_sel = 3 + i;
            }
         }
                }
      else raton.dcha = 0;
   }
}
A parte de informar a raton.izda y raton.dcha del estado de los botones, se calcula la verdadera posición en el espacio virtual del ratón (el puntero más bien, el ratón lo tienes tú en la mano, pero prefiero decir ratón aunque no es correcto porque puntero es también un término en C y no quiero liarlo más) con la función que acabo de explicar. Lo siguente que parece tan largo tiene un buen motivo. Lo que pretendemos es que si la posición del ratón es el mismo que el del vértice, se seleccione. PERO, es prácticamente imposible que aciertes, asi que doy un margen de 0.5f unidades alrededor para que aciertes más fácilmente. En otras palabras, si la posición del ratón está a menos de 0.5f del vértice en concreto, en cualquier dirección XY, se selecciona.
Aclaro que, para vertice_sel, de 0 a 2 es el triángulo y 3 a 6 es el cuadrilátero.
Código:
void MovimRaton(int x, int y) {
   raton.x = ObtenerPosPlanoX(x,ventana[0],ventana[1],-10,10,formato_global);
   raton.y = ObtenerPosPlanoY(y,ventana[0],ventana[1],-10,10,formato_global);
}
Es sencillo, cada vez que el ratón se mueve, se calcula de nuevo la posición en el espacio virtual, para usar en la próxima función, que se repetirá cada 33 milisegundos:
Código:
void MoverVertice(int value) {
   switch (vertice_sel) {
      case 0: case 1: case 2:
         selector.verticeXYZ[0] = triangulo[vertice_sel].verticeXYZ[0];
         selector.verticeXYZ[1] = triangulo[vertice_sel].verticeXYZ[1];
      break;
      case 3: case 4: case 5: case 6:
         selector.verticeXYZ[0] = cuadrado[vertice_sel-3].verticeXYZ[0];
         selector.verticeXYZ[1] = cuadrado[vertice_sel-3].verticeXYZ[1];
      break;
   }
   
   if (raton.izda) {
      if (vertice_sel<=2) {
         triangulo[vertice_sel].verticeXYZ[0] = selector.verticeXYZ[0] = raton.x;
         triangulo[vertice_sel].verticeXYZ[1] = selector.verticeXYZ[1] = raton.y;
      }
      else {
         cuadrado[vertice_sel-3].verticeXYZ[0] = selector.verticeXYZ[0] = raton.x;
         cuadrado[vertice_sel-3].verticeXYZ[1] = selector.verticeXYZ[1] = raton.y;
      }
   }
   
   glutTimerFunc(33,MoverVertice,1);
   glutPostRedisplay();
}
Primero, se analiza con switch qué vértice está seleccionado y hacemos que las coordenadas del punto de selección coincidan con el vértice con el que estamos tratando.
Después, si está pulsado en botón izquierdo del ratón, tanto el vértice seleccionado, como el punto de selección, como la posición espacial del ratón son obviamente iguales.

No hay nada más que decir, bastante he explicado ya, experimenta, y ten en cuanta que ahora el nivel será así... hasta que suba más.

HarZe
WebMaster & Desarrollador

Cantidad de envíos : 58
Fecha de inscripción : 21/06/2009
Edad : 24

Ver perfil de usuario http://opengl-esp.superforo.net

Volver arriba Ir abajo

Ver el tema anterior Ver el tema siguiente Volver arriba

- Temas similares

 
Permisos de este foro:
No puedes responder a temas en este foro.