Universidad de Costa Rica Facultad de Ingeniería Escuela de Ingeniería Eléctrica IE – 0502 Proyecto Eléctrico “Desarrollo de un modelo y simulador tridimensional del brazo robot Stäubli RX 90 L” Por: David Cuenca Alpízar Ciudad Universitaria Rodrigo Facio Julio del 2006 “Desarrollo de un modelo y simulador tridimensional del brazo robot Stäubli RX 90 L” Por: David Cuenca Alpízar Sometido a la Escuela de Ingeniería Eléctrica de la Facultad de Ingeniería de la Universidad de Costa Rica como requisito parcial para optar por el grado de: BACHILLER EN INGENIERÍA ELÉCTRICA Aprobado por el Tribunal: _________________________________ Ing. Federico Ruiz Ugalde, Lic. Profesor Guía _________________________________ _________________________________ Ing. Francisco Siles Canales, Lic. Ing. Andrés Díaz Soto Profesor lector Profesor lector ii DEDICATORIA A mi familia y amigos. iii ÍNDICE GENERAL ÍNDICE DE FIGURAS ................................................................................vi ÍNDICE DE CUADROS ........................................................................... viii GLOSARIO ..................................................................................................ix RESUMEN....................................................................................................xi CAPÍTULO 1: Introducción.........................................................................1 1.1 Justificación ..................................................................................................................1 1.2 Objetivos.......................................................................................................................2 1.2.1 Objetivo general.................................................................................................2 1.2.2 Objetivos específicos .........................................................................................2 1.3 Metodología ..................................................................................................................2 CAPÍTULO 2: Desarrollo teórico ................................................................4 2.1 El sistema operativo GNU/Linux .................................................................................4 2.2 El lenguaje de programación C ....................................................................................5 2.3 Las interfaces de programación de aplicaciones (API’s)..............................................5 2.3.1 La interfaz OpenGL ............................................................................................5 2.3.2 La interfaz DIRECTX.........................................................................................9 2.3.3 Comparación general entre OpenGL y Direct3D ...............................................9 2.4 Teoría de modelado tridimensional aplicada a computadoras....................................11 2.4.1 Las coordenadas homogéneas...........................................................................12 2.4.2 Los sistemas de coordenadas Cámara, Global y Objeto...................................12 2.4.3 Trasladar, rotar y escalar objetos ......................................................................16 2.4.4 Sistemas gráficos ..............................................................................................19 2.4.5 Arquitectura Gráfica (PIPELINE) ....................................................................20 2.5 Introducción a OpenGL, GLUT y GTK .....................................................................22 2.5.1 La ventana de despliegue utilizando GLUT .....................................................22 2.5.2 La ventana de despliegue utilizando GTK........................................................23 2.5.3 Primitivas y vértices..........................................................................................24 2.5.4 La matriz CTM .................................................................................................25 2.5.5 La matriz de transformación del modelo ..........................................................26 2.5.6 Transformación de la visión y proyección de la escena. ..................................27 iv CAPÍTULO 3: La robótica .........................................................................30 3.1 Las bases de los robots................................................................................................30 3.2 El brazo robot..............................................................................................................31 3.2.1 El brazo robot Stäubli RX90.............................................................................32 3.2.2 Geometría de brazo robot Stäubli RX90...........................................................33 CAPÍTULO 4: Desarrollo de la aplicación de simulación ........................36 4.1 Simulador del brazo robot Stäubli RX90 L ................................................................38 4.1.1 El brazo robot Stäubli como un modelo jerárquico ..........................................39 4.1.2 El modelado del brazo robot Stäubli RX90 L...................................................42 4.1.3 El método de detección de colisiones ...............................................................51 CAPÍTULO 5: La interfaz gráfica de usuario de la aplicación ................64 5.1 Los botones de configuración del simulador .............................................................68 5.2 Creación, edición y ejecución de rutinas ....................................................................70 5.3 Envío de ordenes al simulador por medio del teclado y el ratón................................72 CAPITULO 6: Pruebas finales y sus resultados........................................74 6.1 Pruebas destructivas....................................................................................................74 6.2 Pruebas de velocidad ..................................................................................................74 6.3 Pruebas de memoria....................................................................................................77 CAPITULO 7: Conclusiones y Recomendaciones .....................................78 7.1 Conclusiones...............................................................................................................78 7.2 Recomendaciones .......................................................................................................80 BIBLIOGRAFÍA.........................................................................................81 APÉNDICES ...............................................................................................82 v ÍNDICE DE FIGURAS Figura 2.1 Plano 2D y espacio 3D ....................................................................................11 Figura 2.2 La cámara y el mundo .....................................................................................13 Figura 2.3 La cámara, el mundo y el objeto .....................................................................13 Figura 2.4 Sistemas de coordenadas Cámara, Global y Objeto........................................15 Figura 2.5 Rotación y Traslación......................................................................................18 Figura 2.6 Elementos típicos de un sistema gráfico .........................................................19 Figura 2.7 Arquitectura Gráfica (pipeline) .......................................................................20 Figura 2.8 Polígono y puntos............................................................................................25 Figura 2.9 Analogía entre cámara y computadora ............................................................28 Figura 2.10 Volumen de visión especificado por gluPerspective() ..................................29 Figura 3.1 Ejemplo de brazo robot con cuatro grados de libertad ....................................32 Figura 3.2 Partes del brazo robot Stäubli RX90 ...............................................................33 Figura 3.3. Medidas geométricas del brazo robot Stäubli RX90......................................34 Figura 3.4 Distribución de trabajo de las amplitudes de giro ...........................................35 Figura 4.1 Diagrama de flujo de la aplicación de simulación virtual ...............................37 Figura 4.2 Modelo de brazo robot de tres grados de libertad ...........................................39 Figura 4.3 Primer modelo creado del brazo robot. ...........................................................42 Figura 4.4 Principales componentes del brazo Stäubli RX90. .........................................43 Figura 4.5 Esfera con iluminación y sin iluminación. ......................................................49 Figura 4.6 Modelo del brazo robot Stäubli RX90 L. ........................................................50 Figura 4.7 Tipos de figuras de control ..............................................................................51 Figura 4.8 Figuras de control en el modelo del brazo robot .............................................52 Figura 4.9 Figuras de control (esquemático) ....................................................................52 Figura 4.10 Zonas de precaución ......................................................................................56 Figura 4.11 Orillas del hombro del brazo robot................................................................58 vi Figura 4.12 Prueba de colisión de orillas (1) ....................................................................59 Figura 4.13 Prueba de colisión de orillas (2) ....................................................................59 Figura 4.14 Prueba de colisión de orillas (3) ....................................................................60 Figura 5.1 La aplicación gráfica de usuario del simulador...............................................64 Figura 5.2 Los botones de configuración del simulador...................................................68 Figura 5.3 Introducción de una base externa ....................................................................69 Figura 5.4 Introducción del margen de error ....................................................................70 Figura 5.5 Ventana 1 de la aplicación...............................................................................70 vii ÍNDICE DE CUADROS Cuadro 3.1 Amplitudes de giro y velocidades nominales y máximas ..............................35 Cuadro 4.1 Puntos y figuras de control.............................................................................53 Cuadro 4.2 Niveles de jerarquía de las figuras de control ................................................54 Cuadro 4.3 Condiciones y resultados de la prueba de colisión de orillas.........................61 Cuadro 5.1 Envío de ordenes por medio del teclado ........................................................73 viii GLOSARIO GNU/Linux: Denominación para el sistema operativo que utiliza kernel <<linux>>. Es un sistema multitarea multiusuario para PC´s. Linux es una implementación del sistema operativo UNIX. UNIX: Sistema operativo multitareas, multiusuario y portable. Unix fue desarrollado a finales de los sesenta en los laboratorios Bell y hasta principios de los ochenta su uso estuvo restringido fundamentalmente al entorno académico. OpenGL: OpenGL es una biblioteca gráfica desarrollada originalmente por Silicon Graphics Incorporated (SGI). OpenGL significa Open Graphics Library o en español: biblioteca abierta de gráficos. Se utiliza para la creación y despliegue y manejo de primitivas geométricas. API: Un API (Application Programming Interface) o interfaz de programación de aplicaciones, es un conjunto de especificaciones de comunicación entre componentes software. Generalmente está compuesto por un grupo de funciones dedicadas un área específica, por ejemplo la comunicación con el sistema de ventanas. GLUT: GLUT (OpenGL Utility Toolkit) es una API multiplataforma que provee una reducida funcionalidad para el manejo de ventanas e interacción por medio de teclado y ratón. GTK: Inicialmente creado para construir el programa gráfico GIMP, GTK es la abreviatura de GIMP toolkit y es muy usada por los programadores de sistemas Linux para desarrollar interfaces gráficas de usuario. ix RGBA: RGBA es el termino utilizado para definir el modelo de color, en el cual los colores primarios (rojo, verde y azul: <<Red Blue Green>>) son combinados en forma específica con el fin de obtener algún color determinado. La A en RGBA se refiere al termino alfa, que define la transparencia de los colores. Buffer: Un buffer es un espacio de memoria, en el que se almacenan datos, con el fin de evitar que el recurso que los requiere, ya sea hardware o software, se quede en algún momento sin datos. Los buffers también son llamados almacenadores intermedios. Direct3D: Es una interfaz de programación de aplicaciones, desarrollada por Microsoft, que facilita el manejo y trazado de primitivas gráficas elementales, así como la utilización de transformaciones geométricas sobre las primitivas desplegadas. textview: Objeto de la librería Gtk que se utiliza para desplegar texto dentro de una ventana. IDE: Un IDE es un entorno integrado de programación, generalmente consiste en un editor de código, un compilador, un depurador y un constructor de interfaz gráfica. x RESUMEN La creación de un modelo tridimensional y un simulador virtual del brazo robot Stäubli RX90 L es el objetivo principal de este proyecto. Para el desarrollo del programa de simulación se empleó OpenGL como herramienta principal de trabajo. El código de la aplicación fue desarrollado en su totalidad en el lenguaje de programación C. La primera fase del proyecto la comprendió el estudio de la teoría de modelado tridimensional en computadoras. Con ayuda de la información obtenida durante esta etapa, se comenzó la búsqueda y análisis de las herramientas necesarias para el desarrollo de la aplicación. Se eligió la interfaz de programación de aplicaciones gráficas OpenGL, como la librería principal para la creación del programa de simulación virtual. Se estudió y comenzó a experimentar con las funciones brindadas por la librería. A partir de este punto, en el cual ya se tenía un mejor conocimiento de la teoría de modelado y de las herramientas al alcance, se dio inicio a la estructuración de la aplicación a desarrollar. El siguiente paso fue analizar la geometría del brazo robot Stäubli RX90 L, con el fin de determinar cuales características geométricas de éste son las más influyentes en los procesos de colisión. Después del estudio del objeto real, se procedió a crear un modelo virtual tridimensional de éste. A partir de este punto se establecieron dos objetivos principales: la creación de un motor de ejecución de espacio virtual, en el cual se pudiera manejar el modelo del brazo robot Stäubli y la implementación de un método de detección de colisiones al motor de ejecución. Para alcanzar el segundo objetivo se tuvo que desarrollar un método nuevo de detección de colisiones, el cual resultó ser, además de altamente efectivo y flexible, verdaderamente simple en comparación con otros métodos existentes. Una vez creados el motor de ejecución y los algoritmos de detección de colisiones, se inició la estructuración de una interfaz gráfica de usuario, que no solo facilitara el uso de la aplicación, sino que permitiera una futura unión con otras aplicaciones, en especial con otros programas desarrollados para el mismo brazo robot. La interfaz se desarrolló utilizando la librería GTK. Una vez finalizada la aplicación, se llevó a cabo una serie de pruebas mediante las cuales se descubrieron algunos problemas, solucionados posteriormente. Se logró cumplir con el objetivo de crear una aplicación de simulación para el sistema operativo GNU/Linux. La librería gráfica OpenGL y el lenguaje de programación C demostraron poseer las cualidades necesarias para el desarrollo de aplicaciones gráficas complejas. Los métodos de detección de colisiones implementados en el programa, mostraron un buen funcionamiento durante la etapa de prueba. xi CAPÍTULO 1: Introducción 1.1 Justificación El campo de la robótica se encuentra en constante crecimiento. Los robots se han convertido en asistentes imprescindibles en muchas áreas. Se encargan de tareas que pueden ser tediosas, pesadas, difíciles e inclusive imposibles para un ser humano. Los avances tecnológicos de los últimos años han permitido que los sistemas electromecánicos controlables puedan ser aplicados a diferentes áreas. Desde la industria del entretenimiento, hasta la exploración espacial, desde la industria automotriz, hasta las ciencias médicas, los robots afectan nuestras vidas muchas veces sin darnos cuenta. Una parte imprescindible en el desarrollo de nuevas tecnologías robóticas y la implementación de éstas, es el modelado y la simulación. Saber como se comportará el sistema ante diferentes situaciones es indispensable tanto para el diseñador como para el usuario. Mediante el uso de simuladores, los desarrolladores pueden detectar errores y corregirlos antes del proceso de fabricación. Por otro lado los usuarios pueden utilizar modelos virtuales del equipo para verificar previamente si las órdenes que se enviarán tendrán el resultado deseado. Simular con anterioridad los movimientos que se le ordenarán a un robot, incrementa no solo la eficiencia del proceso, sino que también ayuda a resguardar la seguridad de los usuarios y del robot mismo, ya que mediante la representación virtual se puede determinar si la ejecución de cierta orden por parte del equipo, presenta algún riesgo para las personas alrededor o para el equipo mismo. El uso de sistemas operativos propietarios no solo encarece el proceso de desarrollo de aplicaciones, sino que también resta flexibilidad al producto final, ya que muchas de las herramientas y aplicaciones desarrolladas sobre sistemas propietarios no pueden ser implementadas en otros sistemas. El hecho de utilizar una plataforma abierta, para crear modelos virtuales de equipos electromecánicos, presenta ventajas tanto para el desarrollador como para el usuario. Las observaciones anteriores representan algunas de las razones para el desarrollo de este proyecto. La creación de un motor de ejecución y un modelo tridimensional del brazo robot Stäubli RX90 L, además de la implementación de un método de detección de colisiones, ayudará a incrementar la eficiencia y seguridad de los movimientos del brazo robot. Además, la utilización del sistema operativo GNU/Linux y la biblioteca gráfica de código abierto OpenGL, como bases del simulador virtual, incrementa la versatilidad y estabilidad de la aplicación y permite que el código desarrollado pueda ser portado, sin mayor complicación, a proyectos futuros e implementado en otras aplicaciones. Aun cuando el objetivo específico de este proyecto sea la creación de un simulador virtual del brazo Robot Stäubli RX90 L, el trabajo aquí desarrollado podría utilizarse como base para crear modelos virtuales de otros sistemas eléctricos controlables. 1 2 1.2 Objetivos 1.2.1 Objetivo general Crear un modelo tridimensional y un simulador virtual del brazo robot Stäubli RX90 L. 1.2.2 1.3 Objetivos específicos • Investigar teoría del modelado tridimensional aplicado en computadoras. • Investigar acerca de las interfaces para programación de aplicaciones (API’s) gráficas. • Crear un motor de ejecución de un espacio tridimensional virtual, para implementar el modelo del brazo robot Stäubli RX90 L, capaz de acatar órdenes de movimiento enviadas por el usuario. • Implementar al motor de ejecución un método de detección de colisiones capaz de indicar cuando el brazo choca contra el piso o consigo mismo. • Utilizar el sistema operativo de licencia libre GNU/Linux para desarrollar la aplicación gráfica. • Desarrollar la estructura de la aplicación de manera que se facilite una futura unión entre ésta y el programa desarrollado en [4]. Metodología La metodología empleada para la realización del proyecto fue la siguiente: • Se determina el objetivo general y los objetivos específicos del proyecto y se utilizan como base para crear una estructuración de éste. • Se investiga acerca del sistema operativo GNU/Linux y de las interfaces de programación de aplicaciones gráficas en tercera dimensión, principalmente OpenGL y DirectX. • Se realiza un estudio profundo de la teoría del modelado tridimensional aplicado en computadoras. Con base en la información obtenida durante las 3 investigaciones bibliográficas referentes al tema, se decide utilizar la interfaz de programación OpenGL. • Se investiga acerca del brazo robot Stäubli RX90L y se lleva a cabo una recopilación y un estudio de los proyectos y tesis anteriores, referentes al tema. • Se desarrolla un modelo inicial del brazo robot y un motor de ejecución, que permite controlar el modelo utilizando el teclado y el ratón de la computadora. • Se investiga acerca de los métodos de detección de colisiones virtuales, utilizados en aplicaciones gráficas. • Se desarrolla un algoritmo de detección de colisiones para el brazo robot virtual y se implementa el algoritmo al motor de ejecución del espacio virtual. • Se lleva a cabo varias pruebas, para comprobar la eficiencia del método de detección de choques. • Se crea una interfaz de usuario para la aplicación y se adapta ésta, de manera que en un futuro pueda funcionar de forma conjunta a la aplicación desarrolla en [4]. • Se lleva a cabo algunas pruebas finales del funcionamiento de la aplicación desarrollada en el proyecto. • De forma simultánea al desarrollo de los puntos anteriores, se lleva a cabo el desarrollo del informe final del proyecto. de objetos CAPÍTULO 2: Desarrollo teórico 2.1 El sistema operativo GNU/Linux GNU/Linux o Linux son los nombres que suele recibir el sistema operativo de distribución libre y código abierto desarrollado en un principio por Linus Torvalds a principio de los años noventa (1991) y el proyecto GNU fundado por Richard Stallmann en 1983. El término Linux se refiere estrictamente al núcleo o kernel creado en un principio por Linus Torvalds y GNU/Linux al sistema operativo final, que utiliza bibliotecas y herramientas desarrolladas por el proyecto GNU y muchos otros proyectos y grupos de software. La palabra Linux también es usada comúnmente para referirse a las diferentes distribuciones Linux, las cuales son colecciones de software que suelen contener grandes cantidades de paquetes además del núcleo. La colección de utilidades para la programación de GNU es por mucho la familia de compiladores más utilizada en Linux. Tiene la capacidad de compilar C, C++, Java, Ada entre muchos otros lenguajes. Existen varios ambientes integrados de desarrollo disponibles para Linux, entre ellos están Anjuta, Kdevelop, NetBeans IDE y Eclipse. Aunque originalmente Linux fue diseñado solamente para soportar procesadores Intel 80386, actualmente soporta una gran variedad de arquitecturas, convirtiéndose en uno de los sistemas operativos con mayor portabilidad. El soporte técnico para usuarios de Linux normalmente se provee a través de foros en línea, grupos de noticias y listas de correos electrónicos. Los grupos de usuarios Linux LUG’s (por sus siglas en inglés: Linux Users Groups) tradicionalmente prestan soporte local para los usuarios de Linux y para las personas que desean introducirse en el mundo del software libre. Su alta eficiencia, gran portabilidad, variada gama de software libre compatible y muchas otras características, hacen que el sistema Linux represente una gran plataforma de investigación y desarrollo de aplicaciones. Al ser un sistema operativo de código abierto, el sistema Linux ha sufrido un crecimiento acelerado. Programadores provenientes de diferentes lugares del mundo se han unido al desarrollo y mejoramiento del sistema y las aplicaciones que este soporta. Para el desarrollo del kernel del sistema Linux se utiliza el lenguaje de programación C. 4 5 2.2 El lenguaje de programación C 1 El lenguaje C, el cual fue creado en 1963 por Ken Thompson y Dennis M. Ritchie en los laboratorios Bell, está basado en los lenguajes de programación BCPL y B. C está orientado a la implementación de sistemas operativos, concretamente Unix. C es el lenguaje de programación más utilizado para crear software de sistemas, se aprecia por la eficiencia del código que produce. Es un lenguaje de medio nivel, pero con muchas características de bajo nivel. Dispone de las estructuras típicas de los lenguajes de alto nivel, pero también dispone de construcciones del lenguaje que permiten un control a muy bajo nivel. Entre algunas de las ventajas de C se encuentra el hecho de que es un lenguaje muy eficiente, ya que es posible utilizar características de bajo nivel para realizar implementaciones óptimas. A pesar de su bajo nivel es el lenguaje más portado en existencia, existiendo compiladores para casi todos los sistemas operativos conocidos. Proporciona facilidades para realizar programas modulares y además de la posibilidad de utilizar código o bibliotecas existentes. 2.3 Las interfaces de programación de aplicaciones (API’s) 2.3.1 La interfaz OpenGL OpenGL es una especificación estándar, la cual define un API multilenguaje y multiplataformas para desarrollar aplicaciones gráficas con objetos de dos y tres dimensiones. La interfaz consiste en más de 250 funciones que pueden ser utilizadas para crear complejos gráficos tridimensionales a partir de simples primitivas geométricas. OpenGL se extiende mas allá de las PCs y computadoras marca Apple, a muchos tipos de sistemas UNIX. Desde un punto de vista básico, OpenGL es una especificación, es decir que es simplemente un documento que describe un conjunto de funciones y el comportamiento preciso que éstas deben tener. A partir de esta especificación, los desarrolladores de hardware crean implementaciones, bibliotecas de funciones desarrolladas de manera que respeten las especificaciones descritas por OpenGL. Los productores de hardware deben someter sus programas a diferentes pruebas, para poder calificar sus implementaciones como implementaciones de OpenGL. En comparación a Direct3D 2, OpenGL no es un API de alto nivel. Su propósito es dibujar objetos. Las tareas de edición de objetos y entradas y salidas de archivos (<<Input/Output>>) son relegadas a otras aplicaciones, como por ejemplo Open Inventor, propiedad de Silicon Graphics. La interfaz OpengGL es multiplataforma, es decir que 1 2 Fuente: Programación en C y C++, http://www.cprogramming.com/ Direct3D: Biblioteca gráfica desarrollada por Microsoft. 6 puede ser utilizada sobre diferentes sistemas operativos, como por ejemplo Windows, Mac OS-X, PlayStation 3, Linux y otros sistemas en base UNIX. Las funciones de OpenGL pueden ser llamadas desde varios lenguajes, entre ellos C/C++, FORTRAN, Ada y Java. El API OpenGL, comenzó como una iniciativa de la empresa Silicon Graphics para crear una sola interfaz de programación gráfica, independiente de desarrolladores de hardware. Antes de la introducción de OpenGL, muchos vendedores de hardware utilizaban diferentes librerías gráficas, lo cual encarecía el proceso de crear programas compatibles con diferentes plataformas de hardware. Esto llevo a Silicon Graphics a crear OpenGL, aplicación cuya base original es la biblioteca gráfica IRIS. OpenGL empezó como una especificación, luego Silicon Graphics creó una implementación, que podía se utilizada por los creadores de hardware para desarrollar sus propias implementaciones. Los desarrolladores de software no necesitan una licencia para utilizar OpenGL en sus aplicaciones, mientras que los desarrolladores de hardware si deben adquirir una licencia para poder crear una implementación de OpenGL. Por ejemplo, Mesa 3D es un API cuyo código es compatible con OpenGL, sin embargo para evitar el pago de licencias por creación de una implementación de OpenGL, no es llamada como una implementación directa de OpenGL, sino como un API muy similar. Una de la características más interesantes de OpenGL es que utiliza un mecanismo de servidor y cliente para procesar los gráficos. El cliente gráfico utiliza OpenGL y el sistema operativo para trasmitir las primitivas gráficas, coordenadas de vértices, colores y texturas al servidor. En el servidor se utiliza la información obtenida para generar los píxeles, los cuales luego son sometidos a las pruebas de mapeo de texturas, pruebas de profundidad, pruebas de unión de colores , entre otras. Aunque normalmente el cliente y el servidor se encuentran en la misma computadora, la ventaja de la separación del proceso es que hace posible que una máquina de bajo costo transmita, a través de una red, comandos de OpenGL a una máquina más costosa y de mayor eficiencia, sobre la cual corre el proceso del servidor, luego las imágenes finales pueden ser devueltas a la máquina de menor eficiencia para su despliegue. OpenGL se puede manejar bajo dos tipos de modos, modo inmediato en el cual una aplicación envía un comando a OpenGL y ésta los ejecuta inmediatamente y el modo de retención, en el cual secuencias de comandos gráficos son almacenadas en estructuras de datos conocidas como lista de despliegue o listas de exhibición. El uso de estas listas presenta dos ventajas importantes. Si se debe graficar un objeto complicado de manera frecuente, solamente es necesario referirse a la lista que posee la información del objeto, además al estar la información de objeto en una lista, ésta se puede enviar rápidamente a través de una red. La desventaja de las listas de exhibición es que si un objeto está destinado a sufrir modificaciones frecuentemente, se deben generar nuevas descripciones del objeto de igual manera. OpenGL elimina la complejidad que representa para el usuario interactuar con diferentes tarjetas gráficas, presentando al programador una interfaz única y uniforme para 7 el desarrollo y programación de gráficos tridmensionales. Además esconde las diferentes capacidades que poseen diversas plataformas de hardware, ya que es requerido que todas las implementaciones oficiales soporten todo el conjunto de funciones de OpenGL, inclusive utilizando emulación de software si es necesario. OpenGL es un API de proceso de bajo nivel, por lo que requiere que el programador le dicte los pasos necesarios para crear una escena. Esto contrasta con las API’s descriptivas, las cuales solo necesitan que el programador describa la escena y la librería se encarga del resto de la representación de la escena. Tal característica de OpenGL hace necesario que el programador posea un buen conocimiento del modelado tridimensional, pero también da un cierto nivel de libertad para implementar nuevos algoritmos de representación gráfica. OpenGL fue desarrollada como una aplicación multiplataforma, por lo que trabaja independiente del sistema de ventanas, razón por la cual no contiene comandos que interactúen con éste. Funciones como abrir, escalar, dar forma y cerrar ventanas, determinar la posición del mouse o cursor y determinar las entradas del teclado, son acciones que deben ser relevadas a otras librerías. Existen varias librerías que han sido desarrolladas con el propósito de brindar funcionalidad extra a los programadores que utilizan OpenGL. GLUT, una librería que contiene utilidades y herramientas desarrolladas para trabajar en conjunto con OpenGL, se encarga de todos los procesos de comunicación entre las aplicaciones OpenGL y el sistema de ventanas del sistema operativo. Al igual que GLUT existen muchas otras librerías, por ejemplo SDL, GLU, GLUI y FLTK, entre muchas otras, que se encargan de añadir funcionalidad extra al API OpenGL. La especificación de OpenGL es desarrollada y vigilada actualmente por ARB (OpenGL Architecture Review Board), junta que fue formada en el año 1992. ARB está constituida por diferentes compañías, las cuales están interesadas en crear un API consistente, que se mantenga disponible durante un largo periodo. En abril del año 2006, algunos miembros de ARB con poder de voto eran: Silicon Graphics, 3DLabs, ATI Technologies, NVIDIA, Intel, IBM, Apple Computer, Dell y Sun Microsystems. Microsoft quien fuera uno de los miembros fundadores, abandonó el grupo en marzo del año 2003. A parte de estas corporaciones, otras compañías son invitadas a formar parte del grupo OpenGL ARB durante periodos de un año. Con tantas compañías involucradas, representando intereses tan diversos, OpenGL se ha convertido en un API con un amplia gama de capacidades. Está planeado que la especificación de OpenGL pase a ser controlada por el grupo Khronos, a finales del año 2006, esto con el fin de incrementar el mercado y eliminar algunas barreras en el desarrollo de OpenGL y OpenGL ES, la cual es un API derivada de la original, creada para ser implementada en sistemas móviles, tales como teléfonos celulares, PDA’s y consolas de juegos de video. Gran parte de la popularidad de OpenGL se debe a la excelente documentación oficial existente. OpenGL ARB ha publicado una serie de manuales, los cuales son actualizados constantemente. Esto manuales son conocidos por los colores de sus portadas. 8 Algunas de las opciones implementadas en OpenGL son las siguientes: • Las primitivas geométricas, las cuales permiten construir descripciones matemáticas de objetos. Las primitivas que actualmente se encuentran implementadas en OpenGL son: puntos, polígonos, imágenes y mapas de bits (<<bitmaps>>). • OpenGL permite la codificación de color en los modos RGB y el modo de color indexado. • Existe la posibilidad de mover la cámara por el espacio de la escena. • Se pueden utilizar texturas , para así incrementar el realismo de la escena que se esta construyendo. • La iluminación, la cual es indispensable para poder distinguir entre un objeto tridimensional y uno de dos dimensiones, está provista en OpenGL a través de una serie de comandos que permiten calcular el color y las propiedades de los materiales, así como las fuentes de luz en la escena. • Para lograr crear animaciones que luzcan continuas en el tiempo es necesario utilizar la técnica del doble <<buffering>>. Esta técnica, la cual es posible utilizar en OpenGL, consiste en construir cada cuadro de la escena en un <<buffer>> separado de la memoria. Una vez terminado el cuadro de la escena, el <<buffer>> con la información nueva remplaza al <<buffer>> que se muestra actualmente en la pantalla, evitando de esta manera el parpadeo en la animación. • La técnica del anti-rizado, la cual permite reducir los bordes escalonados en las líneas del dibujo, producto de la baja resolución. • El sombreado de Gouraud, que se aplica para crear sombras suaves en objetos tridimensionales y así generar diferencias entre los colores de su superficie. • El <<buffer>> Z o <<buffer>> de fondo, el cual mantiene un registro de la coordenada Z de cada vértice, lo que permite establecer la proximidad entre el observador y el objeto y eliminar las superficies que se encuentran ocultas para el observador. • Efectos especiales como humo y neblina, los cuales añaden realismo a la escena. • La mezcla Alfa (<<alpha blending>>), opción del código de colores RGBA, que permite controlar las transparencias de los colores, por ejemplo para crear una ventana o una mesa de vidrio. • Transformaciones de objetos, entre ellas la rotación, la traslación y el escalado. 9 2.3.2 La interfaz DIRECTX Direct3D forma parte del API DirectX, propiedad de la empresa Microsoft. Direct3D solamente se encuentra disponible para los sistemas Windows, superiores al sistema Windows 95 y para las consolas de videojuegos XBox, también propiedad de Microsoft. Direct3D se utiliza para crear gráficos tridimensionales. La interfaz soporta aceleración de hardware, si la opción se encuentra disponible en la tarjeta de video del equipo. El API contienen muchos comandos para crear escenas tridimensionales, pero contiene pocos comandos para crear y controlar escenas en dos dimensiones. Direct3D no es multiplataforma, pero sí logra esconder algunas de las diferencias que existen entre los diferentes dispositivos de aceleración 3D, utilizando un proceso de emulación de hardware. Al igual que OpenGL, Direct3D puede operar tanto en modo inmediato como en modo de retención. En el modo de retención Direct3D no utiliza listas de definición, sino que ofrece una interfaz de alto nivel orientada a objetos. Después de haber cargado un objeto, éste se puede rotar, escalar y trasladar utilizando diferentes funciones del API. En el modo de retención, el API ofrece funciones con las cuales se puede leer y escribir un formato de archivo que guarda datos de objetos tridimensionales, tales como objetos predeterminados, texturas y conjuntos de animaciones. 2.3.3 Comparación general entre OpenGL y Direct3D1 Direct3D es un API propiedad de la corporación Microsoft, que provee aceleración 3D de hardware para las plataformas Windows. OpenGL es un estándar abierto de un API que provee un número de funciones para la producción de gráficos 2D y 3D. Una implementación de estos estándares se encuentra disponible en la mayoría de los sistemas operativos modernos. Portabilidad: Por el momento Direct3D solo se ha implementado en sistemas operativos de la familia Microsoft, incluyendo las versiones que se han utilizado en las consolas de videojuegos XBox. Algunas funciones del API Direct3D han sido implementadas en el proyecto Wine, el cual intenta trasladar APIs comúnmente utilizadas en Windows a Linux, pero el trabajo ha sido difícil debido a la dependencia entre DirectX y otros componentes del sistema Windows. 1 Fuentes: Artículo: “Comparison of Direct3D and OpenGL”, http://www.wikipedia.org Artículo: “Direct3D vs. OpenGL”, http://www.gamedev.net 10 OpenGL posee implementaciones disponibles para una gran variedad de sistemas operativos incluyendo Windows de Microsoft, Linux, sistemas basados en UNIX, Mac OS X y las consolas de videojuegos de Nintendo y Sony, por ejemplo PlayStation 3. A excepción de Windows, todos los sistemas operativos que permiten gráficos 3D con aceleración de hardware han determinado OpenGL como la interfaz para programación gráfica primaria. En términos de portabilidad, Direct3D posee más limitaciones que OpenGL, sin embargo este encierro solo presenta un problema para algunas aplicaciones. Facilidad de manejo: Antes de la versión 8, Direct3D era conocido por ser un tanto difícil de manejar, por ejemplo para realizar un cambio de estado se requería llevar a cabo un numero de operaciones complicadas. Por ejemplo para habilitar la combinación de colores conocida como <<alpha blending>>, se tenia que crear un <<buffer>> o almacenador intermedio llamado <<buffer>> de ejecución, amarrarlo, llenarlo con los códigos de operación correctos, junto con un encabezado estructural señalando cuántos códigos de operación contenía el <<buffer>> y un código de operación especial de salida, liberarlo y finalmente enviarlo al controlador de la tarjeta de video para su ejecución. Tal vez la queja mas famosa fue entablada por el famoso desarrollador de videojuegos John Carmack en el archivo “.plan”; él recomendaba a Microsoft abandonar Direct3D y utilizar OpenGL. Sin embargo se produjeron muchos cambios en la versión Direct3D 8, los cuales ayudaron a mejorar de manera notoria la imagen de Direct3D. Aun así Direct3D y OpenGL son guiados por paradigmas distintos. Direct3D está construido sobre el modelo de objetos COM de Microsoft, lo cual indica que el uso de codigo C++ es un tanto inusual. Las funciones para adquirir valores no devuelven el valor en el argumento de retorno, ya que todas las funciones COM devuelven un HRESULT que dice si la función se ejecutó correctamente o no. El lado positivo de usar el modelo COM, es que se puede utilizar la misma interfaz en cualquier lenguaje con arquitectura COM, como por ejemplo Visual Basic y Visual Basic Script, entre otros. OpenGL es una especificación basada en el lenguaje de programación C. Fue desarrollada utilizando el concepto de una máquina de estados finitos, aunque las últimas versiones de OpenGL lo han transformado más en un sistema basado en objetos. Aunque la especificación esta construida en C, también se puede implementar en otros lenguajes. En general Direct3D esta diseñada para ser una interfaz de hardware 3D. Su desarrollo depende del desarrollo del hardware y lo que el hardware puede proveer. Por otro lado OpenGL esta diseñado para ser un sistema de interpretación 3D que puede poseer aceleración de hardware, como tal, su desarrollo se deriva de todo aquello que es considerado como útil por el usuario. Direct3D espera que la aplicación maneje los recursos del hardware, mientras que OpenGL crea la implementación para hacerlo. Esto facilita al usuario el hecho de escribir una aplicación válida, pero lo deja más susceptible a implementar errores. Al mismo tiempo, como OpenGL esconde los detalles del hardware, 11 incluyendo el hecho de si el hardware esta siendo utilizado o no, el usuario debe confiar en que la implementación esta utilizando los mejores recursos del hardware. Desempeño: Después de que ambos APIs se establecieron como librerías gráficas viables, Microsoft y SGI comenzaron lo que se ha llamado la guerra de las APIs. La mayor parte de la disputa giraba en torno a cual interfaz ofrecía un mejor desempeño. En general se ha logrado establecer que ninguna de las dos APIs es superior a la otra en cuanto a velocidad. El desempeño de una aplicación depende de la habilidad del programador, la calidad de los controladores (<<drivers>>) y del hardware de gráficos. 2.4 Teoría de modelado tridimensional aplicada a computadoras El modelado 3D es la representación de objetos tridimensionales en un plano bidimensional, por ejemplo la pantalla de una computadora. Las técnicas de modelado tridimensional y la creación de motores tridimensionales se basan en axiomas matemáticos complejos. Aunque existen librerías gráficas que evitan que el programador tenga que lidiar directamente con todos los pasos algebraicos que implica crear una escena tridimensional en un computador, es importante comprender las bases de álgebra lineal que hay detrás de las escenas tridimensionales. Figura 2.1 Plano 2D y espacio 3D Un punto en el espacio se describe generalmente utilizando las coordenadas cartesianas (x,y,z), las cuales describen la posición del punto en un espacio tridimensional. En la programación de aplicaciones gráficas se utilizan como base puntos y vectores para describir las figuras geométricas que se desea dibujar. Los vectores se pueden definir como elementos de determinada magnitud y dirección en el espacio, pero también se pueden ver como resta entre dos puntos, por ejemplo un vector se puede definir como: V = Punto 2 − Punto1 = ( x 2, y 2, y3) − ( x1, y1, z1) = (a, b, c) 12 Para una computadora e incluso para el programador, las coordenadas cartesianas son confusas, ya que no existe ningún elemento que diferencie entre un vector y un punto, por ejemplo al leer el vector antes definido, es imposible para la computador determinar si los elementos (a,b,c) representan un vector o si son las coordenadas de un punto en el espacio. 2.4.1 Las coordenadas homogéneas Las coordenadas homogéneas son similares a las coordenadas cartesianas, la única diferencia es la existencia de un cuarto elemento en el vector coordenada, el elemento w. Por ejemplo si P en coordenadas cartesianas está descrito por los elementos (x,y,z), en coordenadas homogéneas P está representado por los elementos (x,y,z,w), en donde w es un cero si P es un vector y un uno si P es un punto. Por ejemplo: Punto1 P = (x1,y1,z1) → coordenadas cartesianas. Punto1 P = (x1,y1,z1,1) → coordenadas homogéneas. Vector1 V = (x1,y1,z1) → coordenadas cartesianas. Vector1 V = (x1,y1,z1,0) → coordenadas homogéneas. Generalmente se desea transformar los vértices de los objetos que se van a dibujar, por ejemplo trasladarlos, rotarlos con respecto a un eje e incluso escalarlos para cambiar el tamaño del objeto. Estos procesos de transformación, conocidos como transformaciones afines, se llevan a cabo utilizando matrices homogéneas. Para poder utilizar estas matrices de transformación, los puntos y vectores deben estar descritos utilizando coordenadas homogéneas en lugar de coordenadas cartesianas. Las coordenadas homogéneas impiden que una transformación actúe de igual manera sobre un punto que sobre un vector, lo cual, si llegara a pasar, sería un error que alteraría de forma inesperada el objeto dibujado. Por tales razones las coordenadas homogéneas se utilizan para crear aplicaciones gráficas. 2.4.2 Los sistemas de coordenadas Cámara, Global y Objeto En el espacio virtual de las aplicaciones tridimensionales existen varios sistemas de coordenadas, entre ellos se encuentra el sistema de coordenadas propio de la pantalla, la cual a partir de este punto será llamada Cámara y el sistema de coordenadas Global o Worldspace, que representa el sistema del mundo en el cual se encuentran todos los objetos geométricos que se desea dibujar, es decir es el sistema base del espacio virtual. 13 Figura 2.2 La cámara y el mundo La posición del punto A en la figura 2.2 está definida por las coordenadas homogéneas A(x,y,z,1), estas coordenadas son con respecto al sistema de coordenadas globales o sistema Worldspace. Para poder graficar el punto A en la pantalla es necesario determinar sus coordenadas respecto al sistema de coordenadas de la cámara, ya que este es el sistema físico en el cual se dibujará el punto. Figura 2.3 La cámara, el mundo y el objeto Para simplificar el proceso de crear mundos tridimensionales se utiliza un tercer sistema de coordenadas llamado sistema de coordenadas locales o sistema Objeto. Si se tiene un objeto que se encuentra en constante movimiento en el sistema global , se convierte en una tarea difícil y tediosa tener que calcular a cada instante las coordenadas de cada punto del objeto respecto al sistema global. Teniendo este nuevo sistema de coordenadas, cuando un objeto se mueve, las coordenadas de sus puntos respecto al sistema Objeto no varían, lo que varía es la posición y dirección del sistema de coordenadas Objeto respecto al sistema global y el sistema Cámara. 14 Teniendo estos tres sistemas de coordenadas en cuenta, el proceso que se debe llevar a cabo para dibujar un objeto tridimensional en la pantalla es el siguiente: Primero se deben pasar las coordenadas de cada punto del objeto del sistema de coordenadas objeto al sistema de coordenadas global. Luego las coordenadas resultantes deben trasladarse al sistema Cámara, obteniendo de esta manera las coordenadas físicas de cada punto del objeto en la pantalla. Para trasladar las coordenadas de un punto de un sistema a otro se utilizan matrices homogéneas llamadas matrices de transformación. En el espacio virtual, los sistemas de coordenadas se pueden representar mediante matrices de cuatro filas y cuatro columnas (matrices 4 x 4). a a' a' ' x b b' b' ' y A= c c' c' ' z 0 0 0 1 Los primeros tres vectores columna de la matriz A definen la base del sistema de coordenadas, es decir la dirección y magnitud de los vectores x, y, z base. La cuarta columna define el desplazamiento del sistema de coordenadas, en otras palabras, la ubicación del punto de origen del sistema de coordenadas. Al igual que se explicó antes para las coordenadas homogéneas, el cuarto valor de cada columna define si se está describiendo un punto o un vector. Se utiliza un cero para indicar que los valores de la columna pertenecen a un vector y un uno para indicar que los valores pertenecen a un punto. Por ejemplo la base canónica estaría representada por la matriz B, la cual a su vez es la matriz identidad. 1 0 B= 0 0 0 0 0 1 0 0 0 1 0 0 0 1 En la figura 2.4 se observan tres sistemas de coordenadas definidos como Cámara, Worldspace y Objeto. Los vectores base son los mismos para los tres sistemas, lo único que diferencia un sistema del otro es la ubicación de los puntos de origen. 15 Figura 2.4 Sistemas de coordenadas Cámara, Global y Objeto. Los sistemas Cámara, Worldspace (Global) y Objeto de la figura 2.4 están representados por la matrices C, W y O respectivamente. 1 0 C = 0 0 0 0 − 3 1 0 4 0 1 0 0 0 1 1 0 W = 0 0 0 0 0 1 0 0 0 1 0 0 0 1 1 0 O= 0 0 0 0 3 1 0 4 0 1 0 0 0 1 Para la figura 2.4, si se multiplica el vector de coordenadas (x,y,z,0) del punto P, las cuales son con respecto al sistema Objeto, por la matriz O, la asociada al sistema Objeto, se obtienen las coordenadas del punto P respecto al sistema Worldspace. Es importante recordar que se utilizan coordenadas homogéneas y no cartesianas, por lo que se le debe agregar un uno como cuarto elemento al vector de coordenadas , para indicar que lo que se está definiendo es un punto y no un vector . El vector P’ representa las coordenadas del punto P respecto al sistema Worldspace. 1 0 P'= O × P = 0 0 0 0 3 2 5 1 0 4 4 8 × = 0 1 0 0 0 0 0 1 1 1 16 La matriz asociada al sistema Objeto se puede denominar como T1(O→W), ya que pasa las coordenadas del sistema Objeto al sistema Worldspace. De forma análoga la matriz asociada al sistema Cámara, es decir la matriz C, pasa las coordenadas del sistema Cámara al sistema Worldspace, por lo que se puede denominar como T2(C→W). Cuando se crea una escena tridimensional, es necesario poseer las coordenadas de todos los objetos de la escena respecto al sistema Cámara, para así saber en qué posición de la pantalla se deben dibujar. La matriz inversa de la asociada al sistema Cámara, C’=C-1, pasa las coordenadas del sistema Worldspace al sistema Cámara. La matriz C’ se puede denominar como T3(W→C), entonces para obtener las coordenadas del punto P respecto al sistema cámara, se debe llevar a cabo la siguiente operación: P ' ' = C '×P ' = C −1 × (O × P ) en donde el vector ’’ representa el vector de coordenadas del punto P respecto al sistema Cámara. Es importante recordar que cada vez que la cámara cambia su posición , se debe cambiar la matriz C. De igual manera cada vez que el objeto al cual pertenece el punto P se mueve en el espacio virtual, se debe modificar la matriz O, para que represente de manera adecuada la nueva posición y rotación del sistema de coordenadas propio al objeto. 2.4.3 Trasladar, rotar y escalar objetos Muchas veces los objetos en una escena se deben trasladar, rotar e incluso escalar, estas transformaciones, llamadas transformaciones afines, se logran multiplicando los vértices de los objetos geométricos por matrices homogéneas de transformación. Estas matrices se pueden concatenar una tras otra y de esta manera hacer múltiples transformaciones a un mismo objeto geométrico. Las transformaciones afines están representadas por cinco matrices genéricas. Una matriz genérica para traslaciones, tres matrices genéricas para rotaciones y una matriz genérica para escalamientos. 1 0 Matriz de traslación genérica → T = 0 0 0 0 tx 1 0 ty 0 1 tz 0 0 1 17 En la matriz genérica de traslación, el cuarto vector columna (tx, ty, tz) representa los valores de traslación sobre el eje X, el eje Y y el eje Z respectivamente. Nótese que la matriz T se puede denominar como la matriz asociada a un sistema de coordenadas con origen en el punto (tx, ty, tz). 0 0 1 0 cos(α ) − sin(α ) Matriz genérica de rotación respecto al eje X→ R X = 0 sin(α ) cos(α ) 0 0 0 cos(α ) 0 Matriz genérica de rotación respecto al eje Y→ RY = − sin(α ) 0 0 0 0 1 0 sin(α ) 0 1 0 0 0 cos(α ) 0 0 0 1 cos(α ) − sin(α ) sin(α ) cos(α ) Matriz genérica de rotación respecto al eje Z→ RZ = 0 0 0 0 0 0 0 0 1 0 0 1 En las matrices genéricas de rotación, el ángulo α representa el ángulo de rotación respecto al eje de rotación determinado. sX 0 Matriz genérica de escalamiento→ S = 0 0 0 0 sY 0 0 sZ 0 0 0 0 0 1 18 En la matriz genérica de escalamiento, los argumentos sX, sY y sZ, determinan el factor al que se escalarán las coordenadas X, Y y Z de cada vértice, respectivamente. Es importante recordar que la multiplicación de matrices no es conmutativa, es decir para la multiplicación de las matrices A y B se tiene que A ⋅ B ≠ B ⋅ A . Esto es importante, ya que el orden de multiplicación de las matrices afecta la posición final del objeto en la escena. Figura 2.5 Rotación y Traslación1 En la figura 2.5 se observa cómo el resultado de rotar la maceta y luego trasladarla, es distinto al del proceso inverso. Aun cuando las transformaciones sean las mismas, si el orden en que éstas se aplican a un objeto se cambia, el resultado final será distinto. Entonces por ejemplo para el primer caso de la figura 2.5, en el que primero se rota el objeto y luego se traslada, cada vértice del objeto se tendría que transformar de la siguiente manera: V f = T ⋅ R ⋅ Vi en donde i y f son vectores columna que representan las coordenadas homogéneas iniciales y finales del vértice respectivamente, mientras que R es la matriz homogénea de rotación aplicada y T la matriz homogénea de traslación aplicada. Por otro lado para el segundo caso, en el que primero se traslada la figura y luego se rota, la multiplicación entre las matrices y cada vértice del objeto tendría el siguiente orden: V f = R ⋅ T ⋅ Vi Nótese que la dirección de concatenación de las matrices va de derecha a izquierda, siendo la matriz que está más a la derecha el primer proceso de transformación que sufre el objeto. Otro punto que se debe tener en cuenta es que las matrices se post-multiplican, es decir que los vértices de cada objeto se representan como vectores columna que se multiplican al conjunto de matrices de transformación por la derecha. 1 Figura tomada de la guía libre de programación OpenGL “Red Book”, http://www.glprogramming.com/red 19 Generalmente los programadores de aplicaciones gráficas no tienen que preocuparse por tener que estar introduciendo matrices de transformación al código, ya que al utilizar APIs como OpenGL y Direct3D, ellas mismas se encargan de hacer todos los cálculos matriciales necesarios, mientras que el programador solo tiene que llamar la función respectiva de rotación, traslación o escalamiento. Aun así es importante conocer cómo funcionan los procesos de transformación, ya que para algunos casos las funciones básicas de rotación, traslación y escalamiento provistas por las APIs no son suficientes o pueden llegar a ser un tanto rígidas, por ejemplo cuando se desea implementar un método de detección de colisiones. 2.4.4 Sistemas gráficos Los sistemas gráficos generalmente están compuestos por los siguientes elementos: Figura 2.6 Elementos típicos de un sistema gráfico Las entradas definen todo lo que se ha calculado en la aplicación gráfica y se desea dibujar, es decir el nuevo estado de la escena y los cambios que esto conlleva. El procesador o CPU se encarga de la comunicación entre los módulos, realiza operaciones aritméticas con ayuda de las unidades aritméticas (<<ALUs>>) y consulta la memoria cuando sea necesario. Los sistemas especializados en gráficos poseen varios procesadores que trabajan en paralelo, por lo que pueden calcular y dibujar al mismo tiempo, lo que mejora el rendimiento en tiempo real. 20 El <<frame buffer>> es la zona de la memoria en la cual se almacena todo lo que va a ser dibujado. Las API’s gráficas escriben en esta zona lo que se desea dibujar y luego envían esta información a la pantalla. La LUT (por sus siglas en inglés: <<Look Up Table>>), también conocida como paleta, es la tabla que contiene todos los colores disponibles en el sistema. El conversor D/A convierte la información del <<buffer>> de digital a analógica, para que esta pueda ser proyectada en la pantalla. El <<frame buffer>> está caracterizado por su resolución y su profundidad. La resolución es la multiplicación del ancho por el alto del conjunto de píxeles, es decir el numero de filas de píxeles por el numero de columnas de píxeles. El píxel es la unidad mínima de la pantalla. Éstos se encuentran aglomerados en filas y la combinación de los colores de cada píxel en conjunto da forma a la imagen que se ve en la pantalla. La profundidad del <<frame buffer>> determina el numero de bits que se utiliza para guardar la información de cada píxel. Este número depende de la cantidad de colores que se desea mostrar en la aplicación. 2.4.5 Arquitectura Gráfica (PIPELINE) El proceso por etapas o <<pipeline>> gráfico define los pasos necesarios para editar la escena que se desea dibujar en la pantalla, por lo tanto indica los pasos de actuación que debe seguir el API gráfico que se esté utilizando. La entrada del <<pipeline>> gráfico es la geometría u objeto geométrico que se desea dibujar y la salida es la imagen que se obtiene en la pantalla. Figura 2.7 Arquitectura Gráfica (pipeline) 21 A la entrada está el objeto geométrico que se desea dibujar, el cual está compuesto por primitivas geométricas como puntos y líneas. Estos objetos poseen atributos previamente establecidos, los cuales pueden ser móviles o fijos, por lo que debe existir la posibilidad de trasladarlos, rotarlos y escalarlos si es necesario, antes de que sean dibujados en la pantalla. Durante el proceso de transformación del modelo, se rotan, trasladan y escalan los objetos geométricos , de manera que se dibujen en la pantalla igual que se dispuso en el mundo que se desea crear. Para lograr esto se multiplican los vértices de los objetos por las matrices de rotación , traslación y escalamiento que sean necesarias. Una vez realizadas las transformaciones de los vértices, se obtienen las coordenadas de los objetos con respecto al mundo que se está creando, estas coordenadas son las coordenadas del mundo o coordenadas globales. El siguiente paso en el pipeline es el de iluminar los objetos para que sean visibles a la cámara (pantalla) y determinar las coordenadas de los objetos respecto a la cámara. Este proceso se llama transformación del visionado. Luego de haberlo realizado se sabe cuales son las coordenadas de todos los objetos con respecto a la cámara. El próximo paso es recortar todo aquello que existe pero no es visible para la cámara. Este proceso se conoce como <<clipping>>. Durante el proceso de proyección se pasan las coordenadas tridimensionales del mundo que se desea dibujar a coordenadas bidimensionales del plano de proyección. Una vez proyectado el gráfico se tienen las coordenadas de pantalla independientes del dispositivo o DISC por sus siglas en ingles (<<Device Independent Screen Coordinates>>). A estas coordenadas se les llama independientes del dispositivo, ya que aun no se encuentran ligadas a ningún tipo de monitor, es decir todavía no se sabe la resolución de la pantalla ni el tamaño del monitor. Finalmente se lleva acabo el proceso de rasterización el cual consiste en asociar todos los puntos de la imagen proyectada a píxeles en la pantalla, obteniendo de esta manera la imagen final en el monitor. El pipeline gráfico puede ser implementado a través de hardware o software. La implementación a través de software produce que todo el proceso sea más lento a menos de que se cuente con tarjetas de aceleración gráfica. 22 2.5 Introducción a OpenGL, GLUT y GTK 2.5.1 La ventana de despliegue utilizando GLUT Lo primero que se debe hacer a la hora de desarrollar una aplicación gráfica es crear una ventana donde se puedan desplegar los gráficos. Si se utiliza la biblioteca GLUT, la función gluInit() es la primera que se debe llamar, ésta se encarga de inicializar la biblioteca GLUT y negocia una sesión con el sistema de ventanas. Si GLUT no logra ser inicializado, el programa se cerrará y le dará un mensaje de error al usuario. Algunas causas de error de inicialización pueden ser fallos al conectarse con el sistema de ventanas, falta de soporte para OpenGL por parte del sistema de ventanas y opciones invalidas en la línea de comandos. Los parámetros de la función glutInit() deben ser los mismos que aquellos de la función main(), específicamente main(int argc, char**argv) y glutInit(&argc, argv). El próximo paso es escoger el modo en el que se desplegarán los modelos de colores en la ventana, utilizando la función glutDisplayMode(). Por ejemplo glutDisplayMode(GLUT_RGBA || GLUT_DOUBLE || GLUT_DEPTH ) indica que se utilizará el modelo de colores RGB con índice alfa, que se utilizarán dos <<buffers>> para desplegar los gráficos y un <<buffer>> para implementar pruebas de profundidad para cada píxel. Lo siguiente es proporcionar las características de la ventana a crear. La función glutInitWindowSize(ancho, alto) determina el tamaño de la ventana a crear, en donde el parámetro “ancho” es el ancho de la ventana en píxeles y “alto” es el alto de la ventana, también en píxeles. De manera similar glutInitWindowPosition(X,Y) determina la posición inicial de la esquina superior izquierda de la ventana en la pantalla, donde X y Y son coordenadas en la pantalla del monitor, medidas en píxeles. Para crear la ventana asociada a las características anteriormente determinadas se debe llamar la función glutCreateWindow(nombre), en donde la variable nombre determina el título que tendrá la ventana. La librería GLUT posee varias funciones de respuesta, en estas funciones se pueden almacenar rutinas especificadas por el programador, que son llamadas en respuesta a algún tipo de evento. Por ejemplo, la función de respuesta más importante en cualquier programa GLUT es la función glutDisplayFunc(), el argumento de esta función es llamado cada vez que GLUT determina que el contenido de la ventana debe desplegarse nuevamente. Todas la rutinas requeridas para dibujar una escena deberían estar en la función glutDisplayFunc(). El argumento de la función glutReshapeFunc() es llamado cuando se varía el tamaño de la ventana o ésta se mueve. Esto impide que se dé distorsión en el dibujo al cambiar el tamaño de la ventana. 23 Existen otras funciones de respuesta, como por ejemplo las funciones glutMouseFunc() y glutKeyboardFunc(), las cuales llaman las rutinas especificadas en sus argumentos, cuando se da algún evento en el ratón o el teclado del computador. La función de respuesta final que debe ser llamada en cualquier programa GLUT es la función glutMainLoop(). Esta función se encarga de enseñar todas la ventanas que han sido creadas. El comando glutMainLoop() representa un lazo, del cual nunca se sale, que se encarga de monitorear todos lo eventos, por ejemplo pulsaciones de teclas, movimientos del ratón y en caso que sea necesario, llamar a la función de respuesta asociada a cada evento. Una vez creada la ventana para desplegar los gráficos, es posible comenzar a introducir comandos y funciones de OpenGL y así crear los objetos geométricos deseados. 2.5.2 La ventana de despliegue utilizando GTK GTK (por sus siglas en inglés: <<GIMP ToolKit>>), es una librería que se utiliza para desarrollar interfaces gráficas de usuario. Posee una licencia LGPL, por lo que puede ser empleada en el desarrollo de software libre o abierto, e incluso en proyecto comerciales, de manera gratuita. GTK está construida sobre la biblioteca GDK (por sus siglas en inglés: <<GIMP Drawing Kit>>), la cual es básicamente un envoltorio alrededor de funciones de bajo nivel, que se utilizan para acceder las funciones del sistema de ventanas. Esencialmente, GTK es un API orientada a objetos, aún cuando todo su código se encuentra escrito en C. Las funciones de biblioteca se implementan utilizando clases y punteros. Los pasos a seguir para crear una ventana GTK son muy similares a los pasos para crear una ventana GLUT. /* Ejemplo: Como crear una ventana GTK */ #include <gtk/gtk.h> int main( int argc, char *argv[] ) { GtkWidget *ventana; gtk_init (&argc, &argv); ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_widget_show (venatna); gtk_main (); return 0; } 24 Todos los programas que utilicen GTK deben incluir el archivo “gtk.h”, en el cual se declaran las variables, funciones, estructuras, etc, que serán utilizadas por la aplicación GTK. La función gtk_init(gint *argc, gchar ***argv) es la encargada de inicializar GTK en la aplicación. Las interfaces gráficas están compuestas por objetos llamados <<widgets>>. Un <<widget>>, también conocido como artilugio o control, es un componente gráfico con el cual el usuario interactúa, como por ejemplo, una ventana, una barra de tareas, un botón o una caja de texto. En la aplicaciones GTK los <<widgets>> son definidos como GtkWidget, por ejemplo GtkWidget *ventana. En la línea ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL) del ejemplo anterior, se define el <<widget>> “ventana”, como un nueva ventana GTK. El argumento que fue pasado a la función indica que se desea que la ventana acepte las características de decoración y posición dadas por el controlador de ventanas del sistema operativo. En vez de crearse una ventana de tamaño 0 x 0, se establece una ventana de tamaño 200 x 200. Por supuesto estas dimensiones son argumentos predeterminados que pueden ser variados posteriormente a gusto del desarrollador. La función gtk_widget_show (GtkWidget *) le indica a Gtk que ya se establecieron todos los atributos que se deseaban para el <<widget>> y que éste ya puede ser desplegado. Al igual que las aplicaciones GLUT, todos los programas GTK poseen una estructura de lazo infinito, en el cual se está a la espera de que suceda algún evento. En GTK el lazo principal es representado por la función gtk_main(). En el código anterior no se estableció ninguna función de respuesta a eventos, por lo que cualquier evento será ignorado. En aplicaciones GTK las funciones de respuesta se conectan a los eventos de la siguiente manera: g_signal_connect (G_OBJECT (GtkObj), "evento", G_CALLBACK (func_respuesta), argumento), en donde GtkObj es el objeto del tipo GTK, por ejemplo una ventana o un botón, en el cual se dió el evento, func_respuesta es la función que debe llamarse como respuesta al evento y argumento representa posibles argumentos que se le pueden pasar a la función de respuesta. 2.5.3 Primitivas y vértices Los objetos geométricos se pueden describir por medio de conjuntos de vértices conectados entre sí por algún tipo de primitiva geométrica. Para determinar la posición de los vértices se utiliza el comando glVertex*(), en donde el símbolo * indica que existen variaciones para el comando base glVertex(). Algunos nombres de funciones poseen de uno a tres caracteres al final, los cuales determinan el numero y tipo de parámetros que se pasarán al comando. Por ejemplo la función glVertex3f() indica que se trata de un vector, donde tres representa el numero de dimensiones o elementos del vector y la letra f indica que los elementos del vector son números del tipo flotante. La posición de los vértices debe encontrarse entre las funciones glBegin() y glEnd(), las cuales señalan el principio y fin de un objeto geométrico. El argumento de la función 25 glBegin() indica el tipo de primitiva que se utilizará para unir los vectores. Algunos tipos soportados por la librería OpenGL son: GL_LINES, dibuja una línea cada dos vértices, GL_POINTS, dibuja un punto en cada vértice, GL_POLYGON, dibuja un polígono. Los polígonos deben ser convexos. La función glColor*(), especifica el color que OpenGL utiliza para dibujar las figuras, el color no cambia hasta que se vuelva a llamar la función glColor*(). Para el código que se presenta a continuación , dependiendo de si la variable PRIMITIVA se sustituye por el argumento GL_POLYGON o GL_POINTS se obtendrá uno de los objetos presentados en la figura 2.8. /* Ejemplo del uso de diferentes primitivas */ ... glBegin(PRIMITIVA); glVertex2f(0.0, 0.0); glVertex2f(0.0, 3.0); glVertex2f(4.0, 3.0); glVertex2f(6.0, 1.5); glVertex2f(4.0, 0.0); glEnd(); ... /*Determina el inicio de un conjunto de vértices y el tipo de primitiva que los une*/ /*Posición del vértice 1*/ /*Posición del vértice 2*/ /*Determina el fin de un conjunto de vértices*/ Figura 2.8 Polígono y puntos. La librería GLUT posee una serie de rutinas que pueden ser utilizadas para crear objetos tridimensionales específicos. Por ejemplo glutWireSphere() dibujar una esfera compuesta por líneas, glutSolidCube(), dibuja un cubo, entre otros. Las dimensiones de estos objetos se especifican en los argumentos de la función. 2.5.4 La matriz CTM La librería OpenGL posee funciones que se encargan de las transformaciones de posición y tamaño que se desean llevar a cabo en la escena. OpenGL transforma el modelo llevando a cabo multiplicaciones matriciales. En la aplicaciones gráficas como OpenGL, la geometría se ve afectada por la CTM (por sus siglas en inglés: <<Current Transformation Matriz>>) o matriz de transformación actual. Aquí se guarda la información de todas las 26 matrices que se han ido acumulando. Los vértices que son procesados se multiplican por esta matriz y por lo tanto son transformados. En OpenGL la CTM está compuesta por la multiplicación de dos matrices, la matriz “Model-View” o matriz de transformación del modelo y la matriz de proyección. La primera se encarga de las transformaciones que se llevarán a cabo y la segunda representa la proyección sobre el plano bidimensional, es decir el paso del mundo 3D al mundo 2D. 2.5.5 La matriz de transformación del modelo Lo primero que se debe hacer para llamar matrices de transformación es cargar la matriz “Model-View” y luego reiniciarla, lo cual se logra acumulando la matriz identidad. /* Ejemplo de como llamar la matriz de transformación del modelo e inicializarla */ ... glMatrixMode(GL_MODELVIEW); glLoadIndentity(); /*Se carga la matriz “Model-View”*/ /*Se carga la matriz identidad*/ ... Después de haber activado la matriz “Model-View” se pueden comenzar a acumular matrices de transformación para obtener el resultado deseado. Las funciones de transformación implementadas en OpenGL son glScale*(), glRotate*(), glTranslate*(). El comando glScalef(GLfloat sx, GLfloat sy, GLfloat sz) escala los valores de las coordenadas X, Y y Z de la geometría, de acuerdo a los valores de sx, sy y sz. Tanto la letra f al final del nombre de la función como el argumento GLfloat indican que los valores de sx, sy y sz son del tipo flotante. La función glTranslatef(GLfloat tx, GLfloat ty, GLfloat tz) traslada la geometría, según los factores tx, ty y tz. La función glRotatef(GLfloat ángulo, GLfloat vx, GLfloat vy, GLfloat vz) rota la geometría en un ángulo determinado. La rotación es en sentido anti-horario, respecto al vector definido por el conjunto de valores (vx, vy, vz). A medida que se van introduciendo transformaciones éstas se van postmultiplicando en la matriz de transformación, es decir que la última transformación introducida será la primera que se le aplicará a la geometría, esto se debe a que la matriz “Model-View” funciona como una pila LIFO (por sus siglas en inglés: <<Last In First Out>>), en donde la última transformación en entrar es la primera en salir. Utilizando la función glPushMatrix() se puede salvar el estado actual de la pila en cualquier momento y luego puede ser recuperado utilizando la función glPopMatrix(). Esto es muy útil por ejemplo en los casos que se deseen hacer transformaciones a solo algunas partes de la geometría. Se puede salvar el estado actual de la pila, transformar solo las partes de la 27 geometría que se desean transformar y luego recuperar el estado de la pila, sin que el resto de la geometría se vea alterada por las transformaciones que se llevaron a cabo. /* Ejemplo que muestra como se pueden usar las funciones de la pila (Model-View) para transformar solo algunas partes de la geometría */ ... dibujo_parte1(){...} /*se define alguna forma geométrica*/ dibujo_parte2(){...} /*se define alguna forma geométrica*/ ... glTranslatef... /*afecta a toda la geometría que se dibuje a partir de ahora*/ glRotatef... /*afecta a toda la geometría que se dibuje a partir de ahora*/ glPushMatrix(); /*salva el estado actual de la matriz, es decir las dos transformaciones anteriores*/ glTranslatef... /*afecta solo la geometría que se dibuje antes del próximo glPopMatrix()*/ glRotatef... /*afecta solo la geometría que se dibuje antes del próximo glPopMatrix()*/ dibujo_parte1(); /*geometría que se ve afectada por 4 transformaciones*/ glPopMatrix(); /*se recupera el estado anterior de la matriz*/ dibujo_parte2(); /*geometría que se ve afectada por solo 4 transformaciones*/ ... 2.5.6 Transformación de la visión y proyección de la escena. Transformar la visión equivale a cambiar la posición del observador. Lo primero que se debe hacer es activar la matriz de transformación e inicializarla, es decir cargar la matriz identidad. Luego se determina la posición del observador, para esto se puede utilizar la función gluLookAt(px, py, px, ox, oy, oz, ax, ay, az), en donde el vector (px, py, pz) determina la posición del observador, el vector (ox, oy, oz) determina hacia donde ve el observador y el vector (ax, ay, az) define el vector que apunta hacia arriba, por así decirlo, hacia el cielo. La función gluLookAt() es parte de la librería GLU. Una vez que se ha creado la escena tridimensional y se han aplicado todas las transformaciones a los objetos y al observador, el siguiente paso es pasar la escena del mundo tridimensional al plano de proyección bidimensional de la pantalla. La matriz de proyección determina el tipo de proyección y la escogencia de los atributos de ésta, es análogo a elegir el lente de una cámara, el cual determina el ámbito de visión y el acercamiento. La matriz de proyección se activa utilizando el comando glMatrixMode(GL_PROJECTION). Una vez inicializada la matriz de proyección se puede llamar la función glOrtho() para determinar las dimensiones del volumen de visión. El volumen de visión determina el rango visual del observador. Utilizando los comandos gluPerspective() o glFrustum() se define este volumen. El proceso de transformación de la visión y la matriz de proyección se puede comparar con el de tomar una fotografía, tal y como se muestra en la figura 2.9. 28 Figura 2.9 Analogía entre cámara y computadora1 A continuación se presenta un ejemplo de cómo configurar la visión y proyección de una escena. /* Ejemplo de configuración de las matrices de visión y proyección */ void reshape (int w, int h) /*función que se llama cuando la ventana se crea o cambia de tamaño*/ { glViewport (0, 0, (GLsizei) w, (GLsizei) h); /*tamaño del cuadro de visión*/ glMatrixMode (GL_PROJECTION); /*activa la matriz de proyección*/ glLoadIdentity (); /*la reinicia*/ gluPerspective(60.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0); /*determina la perspectiva*/ glMatrixMode(GL_MODELVIEW); /*activa la matriz de transformación*/ glLoadIdentity(); /*la reinicia*/ gluLookAt (0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); /*indica la posición del observador*/ } 1 Figura tomada de la guía libre de programación OpenGL “Red Book” 29 En el ejemplo anterior, la función glViewport() define el área de visión, en otras palabras el área de la ventana que se utiliza para dibujar la escena. En este caso se utilizó toda la ventana. Tras haber cargado e inicializado la matriz de proyección, se define el volumen de visión utilizando la función gluPerspective(), cuyos argumentos son: el ángulo del campo de visión (en el ejemplo es 60.0), la razón de aspecto, la cual se define como la división del ancho del área de visión entre el alto del área de visión, la posición del plano más cercano del volumen de visión, respecto al observador (en el ejemplo es 1.0) y la posición del plano más lejano del volumen de visión, respecto al observador (en el ejemplo es 2.0). La última función del ejemplo determina la posición del observador y el punto hacia el cual éste se encuentra mirando. La figura 2.10 es una representación de un volumen de visión especificado utilizando la función gluPerspective(). Figura 2.10 Volumen de visión especificado por gluPerspective()1 1 Figura tomada de la guía libre de programación OpenGL “Red Book” CAPÍTULO 3: La robótica Desde un punto de vista muy básico y técnico, los seres humanos están compuestos por cinco componentes principales: • Una estructura corporal. • Un sistema muscular que mueve la estructura corporal. • Un sistema sensorial que recibe información del cuerpo y el ambiente que lo rodea. • Una fuente de poder para activar los músculos y sensores. • Un cerebro que procesa la información sensorial y le indica a los músculos qué hacer. Por supuesto que los humanos también poseen atributos intangibles tales como la inteligencia y la moral, entre muchos otros. Pero si se analiza desde un nivel simplemente físico y técnico, los cinco atributos antes mencionados bastan para describir de manera general el cuerpo humano. Un robot está compuesto por los mismos componentes. Los robots típicos poseen una estructura física móvil, un motor de algún tipo, un sistema sensorial, una fuente de poder y una computadora que actúa como el cerebro y que controla todos los componentes anteriores. 3.1 Las bases de los robots La mayoría de los robots poseen ciertas cualidades comunes. En primer lugar todos tienen un cuerpo móvil. Algunos simplemente poseen ruedas motorizadas, mientras que otros poseen segmentos móviles con configuraciones muy complejas. Al igual que los huesos en el cuerpo humano, los segmentos de un robot se encuentra conectados entre sí por medio de articulaciones. Los actuadores son las unidades controladas que se encargan de mover las partes móviles de los robots. Los actuadores hidráulicos son aquellos que utilizan líquido, los neumáticos utilizan aire comprimido para generar el movimiento, los actuadores eléctricos, los cuales están compuestos por motores y solenoides son los más populares. En un robot, cada actuador se encuentra conectado a un circuito eléctrico. El circuito le brinda corriente a los motores y solenoides de forma directa, mientras que en el caso de los actuadores hidráulicos, el circuito se encarga de activar el sistema hidráulico por medio de la manipulación de válvulas eléctricas. Las válvulas determinan el camino del líquido presurizado, a través de la máquina. 30 31 La computadora del robot se encarga de controlar todo lo que esté conectado al circuito. Para mover el robot, la computadora enciende los motores y las válvulas que sean necesarias. No todos los robots poseen sistemas sensoriales, por lo que en algunos casos es necesario el desarrollo de programas de predicción que trabajen de forma paralela al sistema de control, por ejemplo programas de predicción de choques. 3.2 El brazo robot El término robot proviene de la palabra checa “robota”, que significa trabajo forzado, lo cual describe muy bien las tareas que cumplen muchos robots hoy en día. La mayoría de los robots son diseñados para llevar a cabo procesos industriales pesados y repetitivos. Se encargan de tareas que pueden ser difíciles, peligrosas e incluso aburridas para el ser humano. El robot industrial más popular es el brazo robot. El desarrollo de brazos robot comenzó en Europa hace más de treinta años, donde se idearon estos brazos para la industria automotriz. Hoy en día sus aplicaciones van desde la industria del entretenimiento hasta las ciencias de la salud. Normalmente los brazos robot se clasifican según su configuración, su tipo de actuador, el método de programación, la metodología de control y las tareas que lleva a acabo. La configuración de robot se refiera a la geometría de este, sus grados de libertad y sus ejes de movimiento. Una de la configuraciones más utilizadas es la de los sistemas antropomorfos, los cuales son sistemas de brazos robot que imitan la configuración del brazo humano. Entre este tipo de configuración el brazo más común es el de seis articulaciones, con seis grados de libertad. El brazo humano tiene siete grados de libertad. Los grados de libertad se refiere al numero de ángulos de giro que posee la extremidad, en otras palabras es la suma del número de ejes de giro de todas las articulaciones de la extremidad. Por ejemplo, un brazo mecánico de dos articulaciones, en el que la primera articulación gira respecto al eje X y la segunda respecto al eje Z, es un brazo con dos grados de libertad, mientras que otro con dos articulaciones en el que la primera gira respecto a X y la segunda respecto a X, Y y Z, posee cuatro grados de libertad. Los sistemas antropomorfos poseen el equivalente a un hombro, un brazo, un codo, un antebrazo y una muñeca. El hombro normalmente está montado sobre una base giratoria, en lugar de un cuerpo móvil. 32 Figura 3.1 Ejemplo de brazo robot con cuatro grados de libertad Entre los métodos de programación de robots se encuentran, la instrucción por dirección humana, los controles remotos y la programación fuera de línea. Los sistemas de control almacenan la ubicación del brazo en forma de puntos discretos, además proveen la especificación de cómo se debe mover el brazo. El trabajo del brazo humano es básicamente mover la mano de un lugar a otro, de manera similar el brazo robot tiene el trabajo de mover el mecanismo de punta, llamado efector, de un lugar a otro. 3.2.1 El brazo robot Stäubli RX90 El brazo robot Stäubli RX90 L, es un brazo antropomorfo con seis grados de libertad. Está conformado por siete partes principales, las cuales se encuentran unidas entre sí por medio de articulaciones. Los movimientos del brazo son generados por motores eléctricos. La velocidad de giro de cada motor puede variarse de manera independiente. Los principales elementos del brazo robot Stäubli son: la base (A), el hombro (B), el brazo (C), el codo (D), el antebrazo (E), la muñeca (F) y la mano (G) (ver figura 3.2). 33 Figura 3.2 Partes del brazo robot Stäubli RX901 3.2.2 Geometría de brazo robot Stäubli RX90 La geometría del objeto es la base de cualquier modelo tridimensional. El brazo Stäubli esta compuesto, desde un punto de vista geométrico, por diferentes tipos de figuras, tales como cilindros, cubos, pirámides, entre muchas otras, las cuales se junta para dar forma al brazo. En la figura 3.3 se observan las principales medidas del brazo, las cuales están dadas en milímetros. 1 Figura tomada de las hojas del fabricante, http://www.staubli.com/web/robot/division.nsf 34 Figura 3.3. Medidas geométricas del brazo robot Stäubli RX901 Las articulaciones del brazo robot poseen amplitudes de giro máximas, además de valores máximos y nominales para las velocidades angulares, los cuales se muestran en el cuadro 3.1. La numeración de las articulaciones en el cuadro 3.1, está basada en la numeración que recibieron en la figura 3.2. 1 Figura tomada de las hojas del fabricante, http://www.staubli.com/web/robot/division.nsf 35 Cuadro 3.1 Amplitudes de giro y velocidades nominales y máximas 1 Articulación 1 2 3 4 5 6 Amplitud (°) 320 275 285 540 225 540 Distribución de la amplitud de giro (°) A ±160 B ±137.5 C ±142.5 D ±270 E +120 -105 F ±270 Velocidad Nominal (°/s) 236 200 286 401 320 580 Velocidad Máxima (°/s) 356 356 296 409 800 1125 En la figura 3.4 se observa la distribución de trabajo de las amplitudes de giro, de las diferentes articulaciones. Figura 3.4 Distribución de trabajo de las amplitudes de giro1 1 Datos y figuras tomados de las hojas del fabricante, http://www.staubli.com/web/robot/division.nsf CAPÍTULO 4: Desarrollo de la aplicación de simulación La aplicación de simulación presenta la siguiente estructura de funcionamiento: • Se crea la ventana donde se desplegarán todos los objetos de la escena y las ventanas que componen la interfaz gráfica de usuario. En este punto también se establece la configuración inicial de las matrices de proyección y del campo visual del observador. • Se crean todos los objetos geométricos que se utilizarán en la escena y se inicializan los valores de posición de los puntos de control, los objetos geométricos y la cámara. Además se inicializa la velocidad de giro de cada articulación del brazo virtual y de la cámara. • Se da comienzo al monitoreo de eventos, el cual está compuesto por un lazo infinito, del cual no se sale a menos que se de la orden de terminar la aplicación. En este lazo se vigila el estado de las ventanas y de los periféricos como el teclado y el ratón. Si se da una orden por parte del usuario de modificar el estado de la escena, se llama a las rutinas necesarias para llevar a cabo el cambio. • Se ejecuta el movimiento de la cámara o del brazo robot solicitado por el usuario. Si durante la traslación de los elementos del brazo virtual se da una colisión de éste consigo mismo o con el piso, se despliega un mensaje de advertencia y se le da un valor de uno a la variable de choque. Esta variable se utiliza para comunicarle a otras aplicaciones que se ha dado una colisión. • Se regresa al inicio del lazo de monitoreo y se espera por un nuevo evento. 36 37 Figura 4.1 Diagrama de flujo de la aplicación de simulación virtual 38 El desarrollo de la aplicación de simulación del brazo robot Stäubli se dividió en cuatro segmentos principales: • La creación de un motor de ejecución de un espacio virtual de tres dimensiones. • El desarrollo de un modelo tridimensional del brazo robot, basado en las características geométricas dadas por el fabricante. • El desarrollo e implementación de un método de detección de colisiones. • El desarrollo de una interfaz gráfica de usuario. Para el desarrollo del motor de ejecución del modelo virtual, se utilizaron las bibliotecas OpenGL y GLU, luego se creo una interfaz gráfica, dándole la posibilidad al usuario de interactuar con los objetos y la configuración del espacio virtual. En un principio se empleó la librería GLUT, para el desarrollo de la interfaz, pero dada la necesidad de una interfaz más compleja, GLUT tuvo que ser sustituida por la biblioteca GTK-2.0. En general el programa final se puede dividir en dos secciones principales: la sección desarrollada utilizando OpenGL, la cual se encarga de graficar y manejar el espacio virtual y la sección desarrollada con GTK, en la cual se encuentra la interfaz gráfica de usuario y se llevan a cabo los procesos de comunicación con el sistema de ventanas, que sean necesarios. Para establecer una unión entre esta dos secciones y que fuera posible la comunicación entre ellas, se empleó la librería GtkGLext, la cuál es una extensión de GTK, que permite desplegar y manejar aplicaciones gráficas desarrolladas con OpenGL, desde interfaces GTK. El simulador fue desarrollado en el lenguaje de programación C, esta elección se realizó con base en las características positivas propias del lenguaje y además con el fin de simplificar el acople entre la aplicación aquí implementada y el programa en [4]. 4.1 Simulador del brazo robot Stäubli RX90 L Normalmente, la primera fase en el proceso de creación de gráficos tridimensionales, es el modelado de los objetos que se desean utilizar en la escena. Generalmente los desarrolladores de aplicaciones gráficas tridimensionales, no crean sus modelos directamente desde primitivas geométricas, como las ofrecidas en OpenGL, sino que utilizan editores gráficos tales como Lightwave 3D, 3D Studio Max, Maya, Blender, entre otros, los cuales facilitan la creación de las figuras tridimensionales y permiten crear modelos más exactos desde el punto de vista estético. Posteriormente estos modelos son cargados o traducidos a primitivas geométricas que la interfaz de programación gráfica, por ejemplo OpenGL, pueda interpretar. Esta traducción es llevada a cabo por programas o partes de código que son capaces de leer e interpretar los formatos de archivo creados por los programas de edición gráfica. Por ejemplo, existen muchos códigos que fueron 39 desarrollados para interpretar y traducir a primitivas geométricas OpenGL, la información de los archivos con formato .3ds , el cual es el formato de archivos que utiliza el editor 3D Studio Max. El problema es que la mayoría de estos códigos o programas de traducción de formatos no son libres. Además la mayoría solo interpretan formatos de archivos de editores gráficos propietarios, los cuales tienden a tener un costo monetario elevado. Una de las ideas que se mantuvo al desarrollar el simulador del brazo robot, fue la tratar de mantener el programa y el desarrollo de éste libre de programas propietarios. Por esta razón el modelado tridimensional del brazo se desarrolló utilizando directamente las primitivas geométricas que provee el API OpenGL. 4.1.1 El brazo robot Stäubli como un modelo jerárquico El principal aspecto que se debe tomar en cuenta al modelar un brazo robot, es que éste es un objeto articulado, está compuesto por partes rígidas la cuales se encuentran conectadas entre sí mediante articulaciones. Esta característica conlleva a la existencia de diferentes niveles de jerarquía para cada componente de la extremidad robótica. Existen dos diferentes niveles, el de los padres y el de los hijos. A continuación se explican los diferentes niveles de jerarquía utilizando el modelo de un brazo robot de tres grados de libertad. Figura 4.2 Modelo de brazo robot de tres grados de libertad En la figura 4.2 se observa un modelo simple de un brazo robot con tres grados de libertad, el cual está compuesto por tres piezas rígidas llamadas base, brazo1 y antebrazo. La base puede girar alrededor del eje Y y su ángulo de giro es θ, brazo1 gira alrededor del eje Z y su ángulo de giro es φ y el antebrazo gira alrededor del eje Z con un ángulo de giro llamado ψ. En la figura 4.2 se puede observar que cada parte del robot posee su propio sistema de coordenadas en los cuales se encuentran definidos los ángulos de rotación de cada una de las partes. Además es claro que la base está en el nivel de jerarquía más alto. Si la base rota, también rota el brazo1 y el antebrazo, en otras palabras la base es el padre del brazo1 y el antebrazo, mientras que brazo1 y el antebrazo por su parte son hijos de la base. Brazo1 es el padre del antebrazo, ya que si éste rota, el anterazo también sufre una rotación. 40 Todas las transformaciones de traslación o rotación que sufren los padres, también se dan en los hijos, pero las transformaciones ocurridas en los hijos no tienen porque ocurrir en los padres necesariamente. Por ejemplo si brazo1 rota 30 grados, el antebrazo se ve obligado a rotar 30 grados con respecto al origen de brazo1, pero si el antebrazo por su parte gira 50 grados, brazo1 no se ve obligado a sufrir ninguna rotación. A esto se le conoce como un modelo jerárquico. Una de las ventajas de definir un sistema de coordenadas para cada una de las partes del robot es la siguiente, por ejemplo, si brazo1 de la figura 4.2 es rotado 20°, la posición de antebrazo debe ser transformada para mantener la jerarquía del brazo robot, de manera que las partes se encuentren unidas correctamente a la hora de desplegar el modelo. Ahora bien, sería complicado tener que calcular las nuevas coordenadas que debe tener el antebrazo en su propio sistema de coordenadas debido a la rotación de brazo1. En lugar lo que se hace es que se mantienen las mismas coordenadas que el antebrazo posee con respecto a su propio sistema de coordenadas y se gira el sistema de coordenadas del antebrazo 20° con respecto al origen del sistema de brazo1. Teniendo lo anterior en cuenta y sabiendo que las transformaciones de rotación y traslación se llevan a cabo mediante la multiplicación de matrices, para graficar el modelo del robot de la figura 4.2 en donde la base tiene una altura h1 y una rotación de θ, brazo1 tiene una altura h2 y una rotación de φ y el antebrazo tiene una altura de h3 y una rotación de ψ, se llevaría acabo el siguiente proceso: Matriz M_model = matriz_Identidad; //Se inicia la Matriz de transformación actual ... dibujar_robot() { M_model = RotarY(θ); base(); /*Se multiplica a la matriz de Transf.. actual la rotación de la base*/ /*Se dibuja la base*/ /*Se multiplica a la matriz de transformación actual la rotación de la base, la traslación de la altura h1 y la rotación del brazo1*/ M_model = RotarY(θ)*Trasladar(0,h1,0)*RotarZ(φ); brazo1(); /*Se dibuja brazo1*/ /* Se multiplica a la matriz de transformación actual la rotación de la base, la traslación de la altura h1, la rotación del brazo1, la traslación de h2 y la rotación del antebrazo*/ M_model = RotarY(θ)*Trasladar(0,h1,0)*RotarZ(φ)*Trasladar(0,h2,0)*RotarZ(ψ); antebrazo1(); } …. /*Se dibujar el antebrazo/ 41 En el proceso anterior se nota que es poco práctico tener que estar calculando la matriz de transformación actual cada vez que se produce una transformación. En lugar de recalcular la matriz global, ésta se puede actualizar concatenando matrices a su derecha de la siguiente manera: Matriz M_model = matriz_Identidad; /*Se inicializa la Matriz de transformación actual*/ ... dibujar_robot() { M_model = RotarY(θ); base(); /*Se multiplica a la matriz de transf. actual la rotación de la base*/ /*Se dibuja la base*/ /*Se concatena la traslación de la altura h1 y la rotación del brazo1*/ M_model *= Trasladar(0,h1,0)*RotarZ(φ); brazo1(); /*Se dibuja brazo1*/ /* Se concatenan la traslación de h2 y la rotación del antebrazo*/ M_model *= Trasladar(0,h2,0)*RotarZ(ψ); antebrazo1(); /*Se dibujar el antebrazo/ } Como se explico en el capítulo dos, OpenGL mantiene una matriz global de estado llamada matriz Model-View, la cual es actualizada concatenando matrices a su derecha cada vez que se da una transformación en el modelo. Utilizando funciones OpenGL el proceso anterior se puede presentar de la siguiente manera: glMatrixMode(GL_MODELVIEW) glLoadIdentity(); ... dibujar_robot() { glRotatef( theta, 0.0, 1.0, 0.0 ); base(); glTranslatef( 0.0, h1, 0.0 ); glRotatef( phi, 0.0, 0.0, 1.0 ); brazo1(); glTranslatef( 0.0, h2, 0.0 ); glRotatef( psi, 0.0, 0.0, 1.0 ); antebrazo(); } … /*Se carga la Matriz de transformación actual*/ /*Se inicializa la matriz de transf. actual*/ 42 4.1.2 El modelado del brazo robot Stäubli RX90 L Para el desarrollo del modelo del brazo robot se utilizó la técnica de modelado de geometría sólida, en la cual se emplean primitivas geométricas básicas, tales como vértices y líneas para crear la figura tridimensional. La primera fase en el proceso de modelado del brazo robot Stäubli, la comprendió el estudio del nivel de jerarquía de cada uno de los componentes del brazo. Una vez que se establecieron los diferentes niveles de cada parte, se experimentó creando modelos de brazo más simples, con el fin de entender adecuadamente las características de las diferentes funciones de transformación que brinda OpenGL. Figura 4.3 Primer modelo creado del brazo robot. En la figura 4.3 se muestra el primer modelo de un brazo robot, que se creó en este proyecto. Con este modelo se llegó a comprender el funcionamiento de las figuras tridimensionales con diferentes niveles de jerarquía y como desplegaralas y controlarlas utilizando las funciones del API OpenGL. Este primer modelo de un brazo con tres grados de libertad, fue la base y punto de partida para el proceso de creación del modelo final del brazo Stäubli RX90 L. El brazo robot Stäubli RX90 L, posee seis grados de libertad y esta compuesto por siete partes principales: la base, el hombro, el brazo (o brazo1), el codo, el antebrazo (o brazo2), la muñeca y la mano (ver capítulo tres). 43 Figura 4.4 Principales componentes del brazo Stäubli RX901. Para cada uno de los componentes del brazo se estudió y determinó cuales de sus propiedades geométricas son las más influyentes durante los procesos de colisión del robot, ya sea con el piso o consigo mismo. Luego se modelaron los componentes utilizando primitivas geométricas, tomando como guía las medidas dadas por el fabricante del brazo robot. Se utilizaron listas de despliegue, las cuales almacenan la información gráfica de las diferentes partes del robot. En total se crearon siete listas de despliegue, una por cada componente principal del brazo robot. /* Ejemplo: creación de la muñeca del brazo robot*/ ... void crear_munieca(int slices, int stacks){ /* Se define la variables quadObj1, la cual es un puntero a un objeto cuadrático (lib. GLU) */ GLUquadricObj *quadObj1; quadObj1 = gluNewQuadric(); /* Se señala el inicio de la lista de despliegue MUNIECA*/ glNewList(MUNIECA, GL_COMPILE); 1 Figura (modificada) tomada de las hojas del fabricante, http://www.staubli.com/web/robot/division.nsf 44 /*Se apíla el estado de la matriz actual transformación, de manera que las transformaciones que se den a partir de este punto, no afecten ningún objeto que haya sido llamado(dibujado) antes de este punto*/ glPushMatrix(); glRotatef(-90, 0.0, 1.0, 0.0); /*Se crea un cilindro con un radio de 5 unidades en ambos extremos y un largos de 11 unidades, “slices” y “stacks” define el numero de cortes horizontales y verticales que forman el cilindró */ gluCylinder(quadObj1, 5, 5, 11, slices, stacks); glPushMatrix(); glRotatef(180, 1.0, 0.0, 0.0); /*Se crea un disco con un radio de 5 unidades*/ gluDisk(quadObj1, 0, 5, slices, stacks); glPopMatrix(); glTranslatef(0, 0, 11); /*Se crea un disco con un radio de 5 unidades*/ gluDisk(quadObj1, 0, 5, slices, stacks); /*Se desapila el estado anterior de la matriz de transformación*/ glPopMatrix(); /*Se indica el fin de la lista de despliegue*/ glEndList(); } ... En el ejemplo anterior se muestra el código de la función que crea y define la lista de despliegue encargada de guardar la información gráfica referente a la muñeca del brazo robot. Se observa que los objetos que describen la forma geométrica de la muñeca se encuentran definidos entre dos funciones, glPushMatrix() y glPopMatrix(), estas funciones apilan (salvan) y desapilan (cargan) el estado de la matriz de transformación actual (matriz Model-View) respectivamente. Esto impide que las transformaciones necesarias para crear la muñeca, surtan efecto en el resto de los componentes del brazo y de la escena virtual en general. En el código también es posible observar, que el sistema de coordenadas es rotado –90° con respecto al eje Y antes de crear el cilindro que conforma la muñeca, mediante la función glRotatef(90,0.0,1.0,0.0). Esto se debe llevar a cabo, debido a que la función gluCylinder() posiciona los cilindros que crea a lo largo del eje Z del sistema de coordenadas propio del objeto y se desea que el cilindro de la muñeca se encuentre posicionado a lo largo del eje X de su sistema de coordenadas propio. El sistema de coordenadas propio de cada disco también se rotó por la misma razón, además fueron trasladados, ya que se espera que los discos se encuentren en los extremos del cilindro. Se puede observar que con el uso correcto de las funciones glPushMatrix() y glPopMatrix() es posible llevar a cabo transformaciones sobre una parte especifica del modelo, sin afectar al resto de este. Como se señaló anteriormente, se crearon siete funciones encargadas de definir siete diferentes listas de despliegue, una para cada parte principal del robot. Se estableció un sistema de coordenadas propio para cada componente principal del brazo robot. Cada 45 sistema de coordenadas es hijo del sistema que le precede. El orden de jerarquía de los sistemas de coordenadas propios de los objetos es el siguiente: sistema Base, sistema Hombro, sistema Brazo1, sistema Codo, sistema Brazo2, sistema Muñeca, sistema Mano, en donde el sistema Base es quien posee el mayor nivel de jerarquía entre todos ellos, en otras palabras, el sistema Base es padre del resto de los sistemas de coordenadas propios de los componentes del robot. El punto de origen del sistema Base coincide con el punto de origen del sistema de coordenadas global de la escena o espacio virtual, de hecho como la base del brazo no sufre ninguna transformación, es decir no gira ni se traslada, entonces ambos sistemas serán realmente el mismo sistema de coordenadas en todo momento. A continuación se presenta el código de la función dibujar(), en la cuál se da el proceso de posicionamiento y despliegue de los diferentes objetos que componen la escena virtual. En otras palabras, aquí es donde se giran y trasladan los sistemas de coordenadas propios de cada componente y donde son llamadas las listas de despliegue, cada vez que alguna de las partes principales del brazo robot recibe una orden de movimiento por parte del usuario. /*Función: dibujar().Se encarga de dibujar todo lo que desea desplegar en la escena. Archivo: dibujo.c. */ gboolean dibujar(void){ GdkGLContext *glcontext = gtk_widget_get_gl_context (glarea1); GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (glarea1); /*** OpenGL INICIO ***/ if (!gdk_gl_drawable_make_current(gldrawable, glcontext)){ printf("Error al buscar area OpenGL"); return FALSE; } glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); if(checkmax==1){ /*limita los angulos de giro a sus amplitudes máximas*/ check_maximo(); } if(grid==1){ /* GRID. Dibujar el piso en configuración de rejilla */ glBegin(GL_LINES); glColor3f(0.0, 0.1, 1.0); int i; for(i=-300;i<=300;i+=30) { glVertex3f(i,altpiso,-300); glVertex3f(i,altpiso,300); glVertex3f(300,altpiso,i); glVertex3f(-300,altpiso,i); } glEnd(); }else { 46 /*PISO. Dibujar el piso en configuración de polígono sólido */ glBegin(GL_QUADS); glColor3f(0.9, 0.9, 0.9); glVertex3f(-500.0f, altpiso, -500.0f); glVertex3f(-500.0f, altpiso, 500.0f); glVertex3f(500.0f, altpiso, 500.0f); glVertex3f(500.0f, altpiso, -500.0f); glEnd(); } ... La primera etapa de la función dibujar(), consiste en buscar la ventana y el area de la ventana, destinada a desplegar los gráficos OpenGL, es decir el área en donde se debe llevar a cabo la graficación. En la línea que contiene la declaración if(!gdk_gl_drawable_make_current(gldrawable, glcontext)) es en donde se realiza esta búsqueda. Si el área no se encuentra, la función dibujar() termina, retornando el argumento FALSE. Si el área, por otro lado, es encontrada, se continua, primero reiniciando o limpiando el contenido de los almacenadores intermediarios (<<buffers>>) de color y profundidad mediante la función glClear(). Luego se inicializa la matriz actual de transformación utilizando la función glLoadIdentity() y se determina si se desea limitar los angulos de giro de las articulaciones del brazo robot a sus amplitudes máximas (ver cuadro 3.1), posteriormente se dibuja el piso de la escena, ya sea en forma de rejilla o de polígono sólido. /* Continuación de la función dibujar()*/ ... /*Base externa sobre la cual se monta el brazo (opcional)*/ GLUquadricObj *quadObj1; quadObj1 = gluNewQuadric(); glPushMatrix(); glColor3f(0.1, 0.1, 1.0); glRotatef(90, 1.0, 0.0, 0.0); gluCylinder(quadObj1, 12, 12, -1*altpiso, 30, 30); glPopMatrix(); glColor3f(0.7, 0.8, 0.7); //BASE (propia del brazo) glPushMatrix(); glTranslatef(0.0, 0.0, 0.0); glCallList(BASE); glPopMatrix(); //HOMBRO glPushMatrix(); glTranslatef(0.0, 0.0, 0.0); glTranslatef(0.0, 42.0, 0.0); glRotatef(angulo1, 0.0, 1.0, 0.0); 47 glCallList(HOMBRO); glTranslatef(-13.0, 0.0, 0.0); //BRAZO1 glPushMatrix(); glRotatef(angulo2, 1.0, 0.0, 0.0); glCallList(BRAZO1); glTranslatef(13.0, 45.0, 0.0); //CODO glPushMatrix(); glRotatef(angulo3, 1.0, 0.0, 0.0); glCallList(CODO); glTranslatef(0.0, 10.0, 0.0); //BRAZO2 glPushMatrix(); glRotatef(angulo4, 0.0, 1.0, 0.0); glCallList(BRAZO2); glTranslatef(0.0, 55.0, 0.0); //MUNIECA glPushMatrix(); glRotatef(angulo5, 1.0, 0.0, 0.0); glTranslatef(5.5, 0.0, 0.0); glCallList(MUNIECA); glTranslatef(-5.5, 0.0, 0.0); //MANO glPushMatrix(); glRotatef(angulo6, 0.0, 1.0, 0.0); glCallList(MANO); glPopMatrix(); glPopMatrix(); glPopMatrix(); glPopMatrix(); glPopMatrix(); glPopMatrix(); ... Como se observa en el extracto de código anterior, posterior a la creación del piso de la escena, se comienza con el posicionamiento de los sistemas de coordenadas propios de cada componente principal del robot y se grafican los diferentes componentes. El primer componente que se dibuja es la base externa sobre la cuál va montado el brazo robot. Esta base es opcional y su tamaño puede ser variado por el usuario tal y como se explica más adelante. La siguiente parte del robot que se dibuja es la base propia del brazo, cuyo 48 sistema de coordenadas se encuentra en el punto de origen de la escena. En la línea glCallList(BASE) se llama a la lista de despliegue que contiene la información gráfica de la base. Se puede observar en el código que para cada componente del brazo robot, primero se llama la función glPushMatrix() y luego se llevan a cabo las transformaciones necesarias y se llama la lista de despliegue propia del componente. Tal y como se explicó anteriormente, la función glPushMatrix() salva en una pila el estado actual de la matriz de transformación global, de manera que cada componente principal posee un sistema de coordenadas propio, hijo del sistema de coordenadas del componente que le antecede. Por ejemplo, en la línea glRotatef(angulo1,0.0,1.0 0.0), la cual se encuentra anterior al llamado de la lista de despliegue del hombro (glCallList(HOMBRO)), se establece una rotación con respecto al eje Y de magnitud igual al valor de la variable angulo1. Esta rotación afecta a todos los componentes que sean dibujados después de la función glPushMatrix() precedente más cercana, es decir que afecta al hombro, brazo1, codo, brazo2, muñeca y mano, pero a la base no. Esta es la idea principal que se sigue para posicionar y dibujar los componentes del brazo en la escena, de manera que se respete la jerarquía de cada uno de ellos. Se observa que cada una de las partes principales del robot está ligada a una variable de giro, excepto la base, ya que ésta no rota. Estas variables definen el giro de cada uno de los componentes, respecto a un eje determinado del sistema de coordenadas propio a cada componente y por lo tanto estás variables son quienes, en conjunto, determinan la posición o configuración final del brazo. La variable angulo1 especifica el giro del hombro, angulo2 el giro de brazo1, angulo3 el giro del codo, angulo4 el giro de brazo2, angulo5 el giro de la muñeca y angulo6 el giro de la mano. /* Continuación de la función dibujar()*/ ... if(lim==1){ dibujar_limites(); } probar_colision(marg); semaforo(); glFlush (); gdk_gl_drawable_swap_buffers (gldrawable); return TRUE; } ... En ultimo fragmento del código de la función dibujar() se llevan a cabo los últimos pasos necesarios para desplegar la escena en la ventana. Primero se determina si se desea dibujar los límites geométricos del brazo. Estos límites fueron creados durante el modelado del brazo para llevar a cabo pruebas sobre la geometría del modelo con respecto a la del brazo real, son una herramienta para el programador o modelador más que para el usuario. En la siguiente línea de código se llama a la función probar_colisión(), a la cual se le pasa el argumento marg. Esta función es la encargada de realizar las diferentes pruebas de 49 colisión que posee el simulador. El argumento marg determina la magnitud del margen de precaución con el cual se llevan a cabo las pruebas. Una vez terminadas las pruebas de colisión se llama a la función glFlush (), la cuál exige la ejecución de todos los comandos OpenGL que han sido llamados hasta este punto. La función gdk_gl_drawable_swap_buffers (gldrawable) es quien se encarga de intercambiar los almacenadores intermedios de cuadro o <<frame buffers>>, en los cuales se recolecta todo lo que se desea dibujar en la pantalla. Esto se debe llevar a cabo en los casos que se utiliza el método de doble <<buffer>>, el cuál consiste en guardar toda la información gráfica que se desea desplegar en la pantalla en un <<buffer>> de despliegue secundario, el cúal se intercambia con el <<buffer>> de despliegue primario, es decir se convierte en el primario, una vez que toda la información necesaria haya sido recopilada, de tal manera que nunca se manipula la información del <<buffer>> primario durante el despliegue, ya que esto podría causar que la animación se parezca cortada y poco fluida. La iluminación es un aspecto sumamente importante en una escena y puede llegar a ser difícil de dominar. Sin iluminación, no se podría distinguir entre un objeto bidimensional y uno tridimensional. Por ejemplo en la figura 4.5 se observa la misma esfera en dos cuadros diferentes. En el primero hay iluminación en la escena y en el segundo no. Figura 4.5 Esfera con iluminación y sin iluminación. A continuación se muestra parte del código de la función inicio() la cuál se encuentra implementada en el archivo fuente ventanagl.c. Esta función es la que se encarga de crear las luces y establecer las propiedades de éstas, así como las propiedades de los materiales de los objetos en la escena. /* Fragmentos del código de la función inicio(). Archivo fuente: ventanagl.c */ 50 gboolean inicio(void){ GLfloat ambient [] = { 0.1, 0.1, 0.1, 0.0 }; GLfloat specular []= { 1.0, 1.0, 1.0, 1.0 }; GLfloat shininess [] = { 100.0 }; GLfloat position0 [] = { 400.0, 400.0, 400.0, 1.0 }; … /* Determina la propiedad de reflejo del material*/ glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular); glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, shininess); glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE); glEnable(GL_COLOR_MATERIAL); /* Determina la posición de las luces y el tipo de luz*/ glLightfv(GL_LIGHT0, GL_POSITION, position0); glLightfv(GL_LIGHT0, GL_AMBIENT, ambient); … } En la figura 4.6 se observa el modelo final del brazo robot Stäubli RX90 L, que se desarrolló en el proyecto. Figura 4.6 Modelo del brazo robot Stäubli RX90 L. Algunas características geométricas del brazo real, que no presentaban relevancia para la detección precisa de colisiones, no fueron implementadas en el modelo. 51 4.1.3 El método de detección de colisiones Para la implementación de pruebas de detección de colisiones en el simulador, se establecieron nueve puntos de control sobre el brazo virtual, cada punto se encuentra ligado a una figura de control y se localiza en el centro de ésta en todo momento. Se utilizaron dos tipos de figuras para llevar a cabo el control de colisiones, círculos y cuadros. Las figuras están compuestas por un número determinado de vértices los cuales se encuentran unidos entre si mediante una línea continua. Los cuadros constan de cuatro vértices, mientras que los círculos están compuestos por treinta vértices cada uno. Las figuras de control poseen un radio característico a cada una, en el caso de los círculos el radio de control es el valor del radio del círculo propio, mientras que en el caso de los cuadros de control, el radio es la distancia desde el punto medio hasta el primer vértice del cuadro (ver figura 4.7). La figura 4.7 es una representación esquemática de la composición de las figuras de control a partir de vértices. En la gráfica también se observa la ubicación del punto de control central, así como el radio de cada figura. Figura 4.7 Tipos de figuras de control La figura 4.8 muestra la posición de las diferentes figuras de control en el modelo tridimensional del brazo robot. 52 Figura 4.8 Figuras de control en el modelo del brazo robot La figura 4.9 es un esquema que señala la posición de las diferentes figuras de control en el modelo del brazo robot. Los cuatro componentes principales del brazo, mano, muñeca, brazo2 y codo, fueron graficados separados uno del otro, con el fin de comprender mejor la ubicación de las figuras de control. En el esquema de la figura 4.9 se tienen dos perspectivas de visión, una frontal al brazo y una lateral. Figura 4.9 Figuras de control (esquemático) 53 El cuadro 4.1 enumera los diferentes puntos de control existentes y las figuras de control que se encuentran ligadas a cada punto. Cuadro 4.1 Puntos y figuras de control Punto de control Figura(s) de control ligada(s) al punto de control CBA (Control Brazo A) cuadBA CBB cuadBB CBC cuadBC, circBC CBD circBD CWA (Control Wrist A) circWA CWB circWB CWC circWC CHA (Control Hand A) circHA CHB circHA En los gráficos anteriores se observa que los puntos y figuras de control se encuentran ubicados en diferentes componentes del brazo robot. Es decir que no todas las figuras y puntos de control se ven afectados por el mismo nivel de jerarquía, en otras palabras, no todos se encuentran dentro del mismo sistema local de coordenadas. Nótese que por ejemplo, un movimiento de rotación en la articulación cuatro, es decir un cambio en valor del ángulo angulo4, no afectaría la posición de las figuras cuadBA, cuaBB, cuadBC, pero si afectaría la posición del resto de las figuras de control, mientras que un cambio en el ángulo angulo3 afectaría la posición de todas las figuras de control. El nivel de jerarquía que afecta las figuras y puntos de control es un factor sumamente importante a la hora de trasladar los puntos de cada figura de control de sus coordenadas locales a coordenadas globales, ya que es el nivel de jerarquía, quien determina cuales matrices de traslación y rotación deben ser utilizadas para llevar a cabo el cambio de coordenadas. El cuadro 4.2 indica el nivel de jerarquía que afecta a cada punto y figura de control. 54 Cuadro 4.2 Niveles de jerarquía de las figuras de control Nivel de jerarquía Ángulos influyentes Puntos de control Figuras de control 3 angulo1, angulo2, angulo3 CBA, CBB, CBC, CBD cuadBA, cuadBB, cuadBC 4 5 angulo1, angulo2, angulo3, angulo4 angulo1, angulo2, angulo3, angulo4, angulo5 CWA, CWB, CWC CHA, CHB circBC, circBD, circWA, circWB, circWC circHA, cirHB El traslado de las coordenadas de un punto de su sistema local o propio al sistema de coordenadas Global o Worldspace, se hace mediante la multiplicación de matrices homogéneas de traslación y rotación (ver capítulo 2). Si rotZ(ang) representa la matriz de rotación homogénea en el eje Z, rotY(ang) la matriz de rotación respecto al eje Y, rotX(ang) la matriz de rotación respecto al eje X, ang el ángulo de rotación específico a cada matriz y tras(vect) la matriz de traslación con un vector de traslación vect, entonces el proceso de traslación de las coordenadas de un punto, perteneciente al modelo del brazo robot, desde su sistema local hasta el sistema Global estaría dado por las siguientes ecuaciones: Nivel de jerarquía: 3 → Pcoord. Global = tras(br1)*rotX(angulo2)*tras(cod)*rotX(angulo3)*Pcoord. Local Nivel de jerarquía: 4 → Pcoord. Global = tras(br1)*rotX(angulo2)*tras(cod)*rotX(angulo3)*tras(br2) *rotY(angulo4)*Pcoord. Local Nivel de jerarquía: 5 → Pcoord. Global = tras(br1)*rotX(angulo2)*tras(cod)*rotX(angulo3)*tras(br2) *rotY(angulo4)*tras(man)*Pcoord. Local 55 en donde br1 es un vector que representa la posición en la que se encuentra el sistema de coodernadas propio de brazo1 (sistema Brazo1) en el sistema Global, cod representa la posición del sistema de coodernadas propio del codo (sistema Codo) en el sistema Brazo1, br2 representa la posición del sistema de coodernadas propio de brazo2 (sistema Brazo2) en el sistema Codo y man representa la posición del sistema de coodernadas propio de la mano (sistema Mano) en el sistema Brazo2. Pcoord Global es el vector de coordenadas del punto en el sistema Global, mientras que Pcoord Local es el vector de coordenadas del punto en el sistema Local. El procedimiento general de las pruebas de control de colisiones es el siguiente: • Se pasan las coordenadas locales de los puntos de control a coordenadas globales. • Si alguno de los puntos de control se encuentra dentro de alguna de las llamadas zonas de precaución, entonces se realiza la prueba de colisión necesaria. Este proceso se lleva acabo cada vez que el programa llama a la función dibujar(). En la aplicación se definieron dos zonas de precaución, una sobre el piso de la escena virtual y otra que encierra la base del brazo robot. El volumen de estás zonas de precaución es variable y su valor depende del radio de la figura de control ligada al punto de control que se encuentra en condición de prueba. Un punto de control se encuentra dentro de la zona de precaución del piso cuando el valor de su coordenada Y en el sistema Global es menor que la suma del radio de la figura de control ligada a él y el margen de precaución definido por el usuario, cuyo valor predeterminado es cero. Por otro lado, un punto de control se encuentra dentro de la zona de precaución de la base cuando su distancia ortogonal al eje Y es menor que la suma del radio de la base del brazo, más el radio de la figura de control ligada al punto y el margen de precaución determinado por el usuario. La siguiente figura permite un mejor entendimiento de las zonas de precaución y la variación de sus tamaños. 56 Figura 4.10 Zonas de precaución Por ejemplo, si se deseara saber si el punto de control CBB se encuentra dentro de alguna de las zonas de precaución, el procedimiento sería el siguiente: • Se pasan las coordenadas del punto de control CBB al sistema de coordenadas Global, mediante la multiplicación apropiada de matrices. • Se determina la altura de la zona de precaución del piso. Como el punto de control CBB se encuentra ligado a la figura de control cuadBB, entonces la altura de la zona de precaución sería la suma del radio de la figura cuadBB (ver figura 4.7) más el valor del margen de precaución, cuyo valor predeterminado es cero. • Si el valor de la coordenada Y del punto de control CBB en el sistema de coordenadas globales es menor que la altura de la zona de precaución del piso, calculada anteriormente, entonces el punto se encuentra dentro de la zona de precaución, por lo tanto se debe llevar a cabo la prueba de colisión de la figura cuadBB contra el piso. • Si el valor de la coordenada Y del punto de control CBB en el sistema de coordenadas globales es menor que la altura de la base del brazo robot virtual, entonces existe la posibilidad de que el punto de control se encuentre dentro de la zona de precaución de la base, por lo que se debe calcular el radio de tal zona de precaución para poder determinar de manera segura, si el punto se encuentra dentro de la zona o no. 57 • Como el punto de control CBB se encuentra ligado a la figura de control cuadBB, entonces el radio de la zona de precaución para este caso sería la suma del radio de la base del modelo del brazo, más el radio de la figura cuadBB, más el margen de precaución. • Si la raíz cuadrada de la suma de las coordenadas globales X y Z al cuadrado del punto de control, es decir X 2 + Y 2 , es menor que el radio de la zona de precaución de la base, entonces el punto de control CBB se encuentra dentro de la zona de precaución de la base, por lo tanto existe peligro de colisión y se debe realizar una prueba de colisión de la figura cuadBB contra la base. Para el punto de control CBC el cual se encuentra ligado a dos figuras de control, se utiliza la figura cuadBC para llevar a cabo estas pruebas de invasión de zona de precaución, ya que cuadBC posee un radio mayor que circBC, la cual es la otra figura ligada al punto. En el simulador se implementaron tres tipos de pruebas de detección de colisión: la prueba de detección de colisión contra el piso, la prueba de detección de colisión contra la base, las cuales ya fueron mencionadas anteriormente y la prueba de detección de colisión contra las orillas del hombro. Anterior a la realización de cada prueba se lleva a cabo una inicialización de ésta, fase en la que se trasladan las coordenadas de cada uno de los puntos que componen la figura de control que va a ser examinada, de su sistema de coordenadas propio o local al sistema de coordenadas Global. La prueba de detección de colisión contra el piso consiste en comparar el valor de la coordenada global Y de cada uno de los vértices que componen la figura de control en evaluación. Si alguno de estos puntos posee un valor de Y menor o igual a la suma de la altura del piso más el margen de precaución, entonces se declara que hubo un choque del brazo robot contra el suelo. En la prueba de detección de colisión contra la base se monitorea la distancia ortogonal de cada uno de los puntos de la figura de control examinada hasta el eje Y del sistema de coordenadas Global, es decir el valor resultante de la ecuación X 2 + Y 2 para cada uno de los puntos. Si las coordenadas X y Y de alguno de los puntos conlleva a un resultado menor o igual al radio de la base del brazo virtual más el margen de precaución dado por el usuario, entonces se determina que hubo un choque del brazo contra la base. Nótese que las dos pruebas explicadas anteriormente son una derivación de la popular prueba de colisión de la caja limítrofe (por su nombre en inglés: <<Bounding-Box Collision Test>>), la cual es prueba de detección de colisión más utilizada en el desarrollo de juegos de videos y simuladores virtuales. Esta consiste en encerrar un objeto en una caja no visible y determinar si algún otro objeto a invadido la caja. 58 Para el proyecto se tuvo que desarrollar un tercer tipo de prueba de detección de colisiones, de manera que se pudieran detectar todos los choque posibles. Debido a las características geométricas del robot Stäubli RX90 L, el único componente propio del brazo capaz de chocar contra el hombro es el antebrazo, también llamado brazo2. La colisiones del antebrazo contra el hombro suceden en las orillas del hombro, por lo que la prueba desarrollada para detectar estos choques fue denominada como “la prueba de colisión de orillas” o también “prueba de colisión de pendientes” como se explica más adelante. Figura 4.11 Orillas del hombro del brazo robot Ninguna de las figuras de control especificadas en el simulador es capaz de ingresar en el volumen definido por el componente Hombro, sin que antes ocurra una colisión entre una de las orillas del hombro y el antebrazo, por esta razón las pruebas de detección de colisiones basadas en áreas limítrofes son inservibles a la hora de detectar un choque en el hombro. Se observa en la figura 4.11 que el hombro posee cuatro orillas en las cuales se pueden dar colisiones del brazo consigo mismo. La prueba de colisión de orillas consiste en crear una línea recta, llamada línea de control, entre el punto de la figura de control circBC más cercano a la orilla del hombro y el punto de la figura de control circBD análogo, tal y como se observa en la figura 4.12. Posteriormente se lleva a cabo una comparación de pendientes para determinar si se ha dado una colisión del brazo robot contra su propio hombro. 59 Figura 4.12 Prueba de colisión de orillas (1) Nótese que si se observa el modelo del brazo robot desde una perspectiva lateral, las cuatro orillas de colisión del hombro, definidas anteriormente, aparecen como puntos en un plano bidimensional de coordenadas Z, Y, tal y como se observa en la figura 4.13. Esta es la idea principal detrás de la prueba de colisión de orillas. Las cuatro orillas del hombro se convierten en puntos de dos dimensiones y la línea de control entre las figuras de control circBC y circBD se transforma en una línea recta en el plano ZY, que puede ser descrita por la ecuación y = m × z + b , en donde m es la pendiente de la línea y b el valor de y cuando la línea interseca el eje Z. Figura 4.13 Prueba de colisión de orillas (2) 60 En la figura 4.13 es posible percibir una segunda línea recta, entre el punto de origen inferior de la línea de control y la orilla superior izquierda del hombro, esta línea se denomina “línea al hombro”. El nivel Y1 indica la altura a la cual se encuentran las orillas inferiores del hombro, mientras que el nivel Y2 es la altura de las orillas superiores. Si se calculan las pendientes de ambas líneas y se comparan entre sí es posible determinar si se dio una colisión entre el antebrazo y la orilla del hombro. Nótese en la figura 4.14 que si el origen de la línea de control y de la línea al hombro posee un valor de Z negativo y un valor Y menor o igual al valor Y de las orillas superiores del hombro, entonces se puede afirmar que se da una colisión en la esquina superior izquierda si y solo si la pendiente de la línea de control es menor o igual que la pendiente de la línea al hombro. Mientras que si el origen de las línea de control y la línea al hombro tiene un valor de Z positivo y un valor de Y menor o igual al valor Y de las orillas superiores del hombro, entonces se da una colisión en la esquina superior derecha si y solo si la pendiente de la línea de control es mayor que la pendiente de la línea al hombro. Figura 4.14 Prueba de colisión de orillas (3) Las características anteriores de la prueba de colisión de orillas son la razón por la que también fue nombrada como la prueba de colisión de pendientes. En el cuadro 4.3 se muestran las condiciones que se deben cumplir para que se lleve a cabo una prueba de colisión de pendientes y como determinar si hubo colisión según las condiciones de cada prueba. En el cuadro las coordenadas que se indican se encuentra en referencia al sistema Global (WorldSpace) y nivel Y1 y nivel Y2, son los valores de altura de las orillas inferiores y las orillas superiores del hombro, respectivamente (ver figura 4.13). MControl es la pendiente de la línea de control mientras que MHombro la pendiente de la línea al hombro. 61 Cuadro 4.3 Condiciones y resultados de la prueba de colisión de orillas Condiciones para que se lleva a cabo la prueba de colisión de orillas Orilla del hombro con la cual se lleva a cabo la prueba (ver figura 4.13) coord. Y de CBD < nivel Y1 && Si coord. Z de CBD ≤ 0 → orilla inferior izquierda coord. Y de CBC >nivel Y1-radio circBC && Se da un choque si : MControl ≥ MHombro Si coord. Z de CBD > 0 -40-margen ≤ coord. Z de CBD ≤ 40+margen → orilla inferior derecha coord. Y de CBD < nivel Y2+radio cuadBD && Si coord. Z de CBC ≤ 0 → orilla superior izquierda coord. Y de CBC >nivel Y2 && MControl ≤ MHombro Si coord. Z de CBD > 0 -40-margen ≤ coord. Z de CBC ≤ 40+margen → orilla superior derecha coord. Y de CBC < nivel Y2+radio cuadBC && Si coord. Z de CBD ≤ 0 → orilla superior izquierda coord. Y de CBD >nivel Y2 && Si coord. Z de CBD > 0 -40-margen ≤ coord. Z de CBD ≤ 40+margen → orilla superior derecha MControl ≤ MHombro 62 A continuación se muestra el fragmento de código perteneciente a la función probar_colision(), que se encarga de chequear si se están dando las condiciones necesarias para llevar a cabo una prueba de colisión de orillas. /* Fragmento de código. Función: probar_colision(flota margen). Archivo Fuente: dibujo.c. * Este fragmento de la función es el encargado de verificar si se cumplen las condiciones * para llevar a cabo las pruebas de colision de orillas. Si se cumplen las condiciones, llama la prueba . * Nivel Y1 = 28.5 cm , y Nivel Y2 =54.5 cm. */ ... //VORTEX nivel 1 (hombro) if((CBDreal[1]<28.5) && (CBCreal[1]>18.0) && (acbdz<=40+margen)){ dibujar_circBD(); check_vortex(circBD, circBC, circBDreal, 1); cuidado=1; } //VORTEX nivel 2 if((CBDreal[1]<=65) && (CBCreal[1]>54.5) && (acbcz<=40.0+margen)){ dibujar_circBD(); check_vortex(circBD, circBC, circBDreal, 2); cuidado=1; } if((CBCreal[1]<=65) && (CBDreal[1]>54.5) && (acbdz<=40+margen)){ dibujar_circBC(); check_vortex(circBC, circBD, circBCreal, 2); cuidado=1; } ... Nótese que la función chech_vortex(arg1, arg2, arg3, 2) es la encargada de llevar a cabo la prueba de colisión de pendientes. El argumento arg1 es la matriz de puntos con coordenadas locales de la figura de control que se encuentra por debajo de la altura de la orilla en prueba, arg2 es la matriz de puntos con coordenadas locales de la figura de control que se encuentra por encima de la altura de la orilla en prueba, arg3 la matriz de puntos con coordenadas globales de la figura de control pasada en el argumento numero uno y arg4 es el nivel de altura de la orillas en prueba (Y1 o Y2). Con cada llamada a la prueba de colisión de pendientes se da el siguiente proceso: • Se trasladan las coordenadas de los puntos de la figura de control señalada en arg1, de su sistema de coordenada local al sistema de coordenadas Global y se guardan tales coordenadas en la matriz señalada en arg3. 63 • Se busca el punto de la figura de control señalada en arg1, que se encuentra más cercano a la orilla en prueba. Se busca el punto análogo en la figura de control señalada en arg2 y se pasa este punto a coordenadas globales. • Se crea la línea de control entre los dos puntos anteriormente determinados. • Se compara la pendiente entre la línea de control y la línea al hombro. • Se determina si existe una colisión o no. La prueba de colisión de pendientes también fue nombrada como prueba VORTEX (por sus siglas en inglés: <<Virtual Object Rapid Touching Edges Examination>>) o “prueba rápida de las orillas en contacto del objeto virtual”. CAPÍTULO 5: La interfaz gráfica de usuario de la aplicación Para acceder al programa de simulación desarrollado en este proyecto, éste debe ser ejecutado desde la línea de comandos, dentro de un entorno gráfico que tenga soporte para OpenGL, GLU, GTK-2.0 y GtkGLext. Una vez que el programa se esté corriendo, el simulador puede ser controlado directamente desde el emulador de terminal (la línea de comandos) o mediante la interfaz gráfica de usuario GTK. Se recomienda utilizar siempre la interfaz gráfica en lugar de la línea de comandos, ya que ésta facilita el uso de la aplicación. Desde la interfaz de usuario se pueden dar órdenes de movimiento, de creación y ejecución de rutinas y de configuración al simulador. La interfaz está compuesta por tres ventanas principales. En la figura 5.1 se observan las tres ventanas de la aplicación, además de la ventana de la terminal desde la cual se encuentra corriendo el programa. Se recomienda mantener las cuatro ventanas visibles, de manera similar a la figura 5.1. A pesar de que algunos mensajes son desplegados en el <<textview>> de la aplicación GTK, muchos mensajes de importancias también son transmitidos a través de la terminal. Figura 5.1 La aplicación gráfica de usuario del simulador En la ventana superior derecha de la figura 5.1, la cual es la ventana de despliegue de la escena virtual, se muestra el modelo virtual del brazo robot Stäubli R90X L. En el área superior derecha de esta ventana se puede observar una esfera. Esta esfera cambia de lugar y de color dependiendo del riesgo que representa el movimiento actual del brazo. La 64 65 esfera de color verde indica que el movimiento es seguro y que el brazo no se encuentra colisionado, la esfera amarilla señala que aunque no se está dando una colisión, al menos un punto de control se localiza dentro de una de las áreas de precaución, por lo que el choque puede estar cercano a ocurrir, finalmente la esfera roja indica que se está dando un choque en al menos uno de los componentes del brazo. Las ventanas gráficas fueron programadas utilizando funciones de la librería gtk2.0. Se utilizaron objetos como botones, entradas de texto, etiquetas, etc, para formar la interfaz de usuario la cual consta de tres ventanas, dos ventanas en donde se ubican los botones y demás objetos para controlar el simular virtual y una tercer ventana en donde se despliega la escena virtual, es decir el mundo virtual en el cual se encuentra el modelo de brazo robot. Las tres ventanas gráficas son creadas en el momento en el que se inicia la aplicación. Las funciones encargadas de llamar y desplegar las ventanas se encuentran en el archivo fuente de código main.c. /* Creación y despliegue de las ventanas de interfaz gráfica. Archivo fuente: main.c*/ ... gtk_init (&argc, &argv); ventana1 = create_window1 (); gtk_widget_show (ventana1); ventana3 = create_window3 (); gtk_widget_show (ventana3); ventana2 = create_window2 (); gtk_widget_show (ventana2); … gtk_main (); En el segmento de código mostrado anteriormente se presentan las funciones encargadas de crear y desplegar las ventanas gráficas de la aplicación. Las funciones create_window1(), create_window2() y create_window3() crean las ventanas específicas, con los objetos gráficos que las componen. La información es pasada a ventana1, ventana2 y ventana3, punteros a objetos GTK del tipo GtkWindow. Nótese como después de la creación de cada ventana se llama a la función gtk_widget_show() y se le pasa como argumento el puntero a la ventana que acaba de ser creada. Esta función produce que se despliegue la ventana en la pantalla. La última función mostrada en el segmento de código es gtk_main(), ésta señala la entrada al lazo principal Gtk. Este es un lazo infinito, en el cuál la aplicación se encuentra en espera de algún evento. Básicamente las funciones encargadas de crear una ventana gráfica, tienen como tarea llamar a todos los objetos gráficos que sean necesarios y colocarlos dentro de un marco de manera que se obtenga la configuración de la ventana, además se debe ligar a 66 cada objeto gráfico que lo requiera, las función de respuesta a diferentes eventos, por ejemplo, los botones de una ventana gráfica deben tener funciones de respuesta que sean llamadas cada vez que éstos son presionados. A continuación se muestran algunos segmentos de código que componen la función encargada de crear la ventana gráficas superior izquierda de la figura 5.1. /*Ejemplo: Función encargada de crear la ventana 1. Archivo fuente: interface.c*/ … GtkWidget* create_window1 (void){ /* Se inicializan los objetos que configuran la ventana*/ GtkWidget *window1; GtkWidget *bsimular; … /*Se crea la ventana sobre la cual se colocaran los botones y demás objetos gráficos*/ window1 = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_widget_set_name (window1, "window1"); gtk_container_set_border_width (GTK_CONTAINER (window1), 3); gtk_window_set_title (GTK_WINDOW (window1), _("Controles del Simulador")); … /* Se agregan todos los objetos que configuran la ventana. Por ejemplo, a continuación se despliega la función que agregar el botón SIMULAR */ bsimular = gtk_button_new (); gtk_widget_set_name (bsimular, "bsimular"); gtk_widget_show (bsimular); gtk_box_pack_start (GTK_BOX (vbox3), bsimular, FALSE, FALSE, 0); … /*Se conectan los objetos gráficos y sus funciones de respuesta correspondientes*/ g_signal_connect ((gpointer) bsimular, "clicked", G_CALLBACK (on_bsimular_clicked), NULL); … /*Se devuelve la ventana, compuesta por los objetos gráficos respectivos*/ return window1; } Las tres funciones que crean las ventanas de la interfaz se encuentran desarrolladas en el archivo fuente interface.c. Nótese en el código anterior que la función create_window1 devuelve un puntero a un objeto del tipo GTK. Dentro de la función se inicializan todos los objetos gráficos que componen la ventana, en el ejemplo se muestran solo dos de los objetos de la ventana, la ventana principal window1 y el objeto del tipo botón bsimular (botón SIMULAR de la aplicación). En total, esta ventana se encuentra compuesta por cincuenta y un objetos, incluyendo botones, entradas de texto, marcos, etiquetas, etc. En el código se puede verificar la existencia de diferentes funciones que ofrece la librería Gtk, que se pueden utilizar dependiendo del objeto que se desea crear, por 67 ejemplo para crear una ventana vacía se utiliza la función gtk_window_new(), mientras que para crear un botón se utiliza la función gtk_button_new(). Algunos objetos gráficos requieren funciones de respuesta para eventos determinados, por ejemplo en el código anterior se conecta el objeto bsimular, es decir el botón SIMULAR de la aplicación, con la función on_simular_clicked, ésta función va a ser llamada cada vez que el botón SIMULAR sea presionado. La función utilizada para ligar el proceso de respuesta al botón fue la siguiente: g_signal_connect(arg1, arg2, arg3, arg4), a la función se le deben pasar cuatro argumentos, el primero indica el objeto gráfico en el que se da el evento, en el ejemplo anterior es bsimular, el segundo argumento señala el tipo de evento para el cual se debe llamar la función de respuesta, en el ejemplo el evento es del tipo “clicked”, es decir cuando se presione el botón utilizando el puntero del ratón, el tercer argumento de la función de conexión indica la función de respuesta que se desea ligar al evento y al objeto, mientras que el cuarto argumento se utiliza para pasar información a la función de respuesta. Todas la funciones de respuesta se encuentran desarrolladas en el archivo fuente callbacks.c. A continuación se muestra parte del código de la función on_bsimular_clicked, la cual es llamada cada vez que el botón SIMULAR es presionado. /*Función de respuesta del botón: Simular. Archivo fuente: callbacks.c*/ void on_bsimular_clicked (GtkButton *button, gpointer user_data){ /*Simula el movimiento del robot utilizando los valores de los botones 'spinner'*/ GtkSpinButton *spin1, *spin2, *spin3; int velocporciento; spin1=GTK_SPIN_BUTTON(spinbutton1); spin2=GTK_SPIN_BUTTON(spinbutton2); spin3=GTK_SPIN_BUTTON(spinbutton3); hubochoque=0; //Toma los valores de los botones 'spinners' joint = gtk_spin_button_get_value_as_int(spin1); angulodelta = gtk_spin_button_get_value_as_int(spin2); velocporciento = gtk_spin_button_get_value_as_int(spin3); set_velocidad(velocporciento, joint); selec_mover_joint(); if(hubochoque==1){ printtv("\n\nCUIDADO!!!-->Se produjo un choque durante la simulación del movimiento"); } } La función on_bsimular_clicked utiliza los valores de articulación, ángulo y velocidad, definidos por el usuario por medio de los botones correspondientes de la 68 aplicación, para simular el movimiento del brazo robot. Una vez tomados los valores se llama a la función selec_mover_joint(), la cual se encarga de producir el movimiento en el modelo virtual del robot. Cada botón de la aplicación se encuentra ligado a diferentes funciones de respuesta, las cuales en conjunto permiten el funcionamiento correcto de la aplicación. 5.1 Los botones de configuración del simulador En la figura 5.2 se encuentran algunos botones encargados de cambiar la configuración del simulador. Figura 5.2 Los botones de configuración del simulador El primer cuadro en la ventana contiene los botones encargados de guardar, cargar y reiniciar las posiciones del brazo robot y de la cámara. Los botones “APILAR” y “DESAPILAR” apilan y desapilan respectivamente, el estado del brazo virtual, en una pila capaz de contener sesenta posiciones. Mientras que el botón “Reiniciar Pila” borra todas las posiciones que hayan sido guardadas en la pila. “Reiniciar Robot” y “Reiniciar Cámara” pueden ser presionados cuando se desea volver a la posición inicial de estos objetos. En el segundo cuadro de la ventana se encuentra el botón “GRID”, el cual produce que el piso de la escena sea desplegado en forma de rejilla. El tercer marco contiene los botones encargados de configurar el proceso de simulación en sí. El seleccionar la opción “Lim. Angulos” ocasiona que el simulador envíe un mensaje al usuario cada vez que alguna articulación alcance su amplitud máxima de giro. Si esta opción se encuentra seleccionada y se alcanza una amplitud de giro máxima, ya sea durante la ejecución del comando “MOVER (DRIVE)”, la ejecución del comando “Simular” o la ejecución de una rutina, la rotación de la articulación se detendrá en su ángulo máximo. De manera similar, la selección de la opción “1° Colisión” ocasiona que el brazo virtual detenga su movimiento una vez que éste detecta su primera colisión, ya sea 69 durante la ejecución del comando “MOVER (DRIVE)”, la ejecución del comando “Simular” o la ejecución de una rutina. Si se da un choque y el botón “1° Colisión” se encuentra seleccionado”, el modelo no se podrá mover nuevamente hasta que se deshabilite el botón indicado o se recargue en el brazo su posición inicial o alguna en que no se encuentre colisionado, ya sea utilizando la opción “Reiniciar Robot” o “DESAPILAR”. Finalmente el botón “SIM ON” se encarga de habilitar y deshabilitar el simulador durante la edición y ejecución de rutinas o el uso del comando “MOVER (DRIVE)”. Existen dos cambios de configuración del simulador que se pueden llevar a cabo por medio de la entrada de texto del marco “Línea de comandos”: agregar una base externa al brazo virtual y definir un margen de precaución al proceso de detección de colisiones. Muchas veces los brazos mecánicos se encuentran motados sobre bases externas, con el fin de ajustarlos mejor a la zona de trabajo en la cual deben desempañarse. El simulador posee la opción de agregar una base externa al brazo robot, cuyo radio es igual a la base propia del modelo. Para añadir la base externa se introduce el comando “base” en la línea de entrada del marco “Linea de comandos” de la primer ventana GTK, seguido por la altura que se desea tenga la base, en centímetros, luego se ejecuta utilizando el botón “EJECUTAR”. Por ejemplo en la figura 5.3 se agregó al brazo virtual una base externa de 30 centímetros. Figura 5.3 Introducción de una base externa Para definir un margen de precaución se introduce en la entrada de texto del marco “Línea de comandos” la palabra “margen” seguida por la magnitud del margen de precaución que se desea, en centímetros, luego se presiona el botón “EJECUTAR”. 70 Figura 5.4 Introducción del margen de error En el ejemplo de la figura 5.4 se introdujo un margen de precaución de 20 centímetros, lo cual ocasiona que el simulador indique que se dio una colisión 20 centímetros antes de que ésta realmente ocurra. 5.2 Creación, edición y ejecución de rutinas Existen tres opciones para crear una rutina de movimientos para el brazo virtual. La primera es editar de forma manual el archivo de texto que contendrá la rutina, siguiendo el formato específico de los archivos de rutina de la aplicación. La segunda manera es seleccionar la ventana de despliegue de la escena virtual y presionar la tecla F5, luego se deben seguir las órdenes desplegadas en la ventana de la terminal. Figura 5.5 Ventana 1 de la aplicación 71 La opción más recomendable para crear una rutina es la de utilizar la interfaz gráfica del programa. Lo primero que debe hacerse es asegurarse de que el simulador se encuentre habilitado, utilizando el botón “SIM ON” en la ventana inferior izquierda de la figura 5.1, luego se deben seguir los siguientes pasos: • Introducir un nombre para la nueva rutina, en la entrada de texto del cuadro “Rutinas”, que se encuentra en la ventana de la figura 5.5 y presionar el botón “Crear nueva Rutina” del mismo cuadro, lo cual dará comienzo a la edición de la nueva rutina. • Para agregar movimientos a la rutina se utiliza el botón del comando “MOVER (DRIVE)” y los botones de ajuste “ARTICULACIÓN”, “ANGULO” y “VELOCIDAD(%)”. Cada movimiento agregado a la rutina será simulado y se indicará si el movimiento causó un choque o si se alcanzó el valor máximo de amplitud de giro para alguna articulación, siempre y cuando la opción se encuentre habilitada. Si el movimiento conllevó a una colisión en la simulación, la aplicación preguntará al usuario si éste debe ser agregado a la rutina o no. Nunca se recomienda agregar a la rutina movimientos que causen choques durante la simulación. El botón “SIMULAR”, puede utilizarse como opción al botón “MOVER (DRIVE)” con la diferencia de que esté simula el movimiento pero no lo guarda en el archivo de la rutina. Antes de utilizar el botón “SIMULAR”, se recomienda apilar el estado actual del brazo virtual, con ayuda del botón “APILAR”, en la ventana de la figura 5.2, de manera que se pueda volver a esta posición luego de haber simulado el movimiento y así el brazo virtual se encuentre en la posición correcta cuando se vuelva a utilizar el comando “MOVER (DRIVE)”. • Para finalizar la rutina se debe introducir el comando “e” por medio de la entrada de texto y el botón “EJECUTAR”, ubicados en el cuadro “Línea de Comandos”, en la ventana de la figura 5.5. Una vez finalizada la edición de la rutina, está se encuentra cargada en la memoria, para ejecutarla se debe presionar el botón “Ejecutar Rutina” del marco “Rutinas”. Si se desea cargar una rutina diferente, se debe presionar el botón “Cargar Rutina”, el cual despliega una ventana de selección de archivos, mediante la cual se puede seleccionar el archivo de rutina que se desea cargar. El simulador guarda las rutinas en la carpeta “rutinas”, ubicada dentro de la carpeta que contiene los archivos fuente de la aplicación. El botón de comando “MOVER (DRIVE)” también puede utilizarse para dar ordenes de movimiento fuera de la edición de una rutina, al igual que el botón “SIMULAR”. El proceso de cambiar, borrar o agregar movimientos a un archivo de rutina de simulación existente, debe realizarse de forma manual, directamente desde el archivo de texto específico, respetando el formato de los archivos de rutina del simulador. Todos los archivos de rutinas de movimiento del simulador deben comenzar con la palabra “inicio” y 72 terminar con la palabra “fin”. Los movimientos se especifican utilizando el siguiente formato de frase: “M(número), J (número), A (número), V (número),” , en donde el número después de la letra “M” señala el número de movimiento, el número posterior a “J” señala la articulación que se desea realice el movimiento, siendo (1, 2, 3, 4, 5, 6) los únicos números posibles, “A” indica que el numero que sigue se refiere al ángulo de giro (en grados), mientras que el número siguiente a “V” es la velocidad porcentual de giro, la cual debe ser mayor que cero y menor o igual que cien. Todos los números deben ser enteros excepto el ángulo de giro, el cual puede tener un dígito decimal (utilizar un punto para ingresar el decimal). Se deben respetar la comas, las mayúsculas y minúsculas. A continuación se muestra un ejemplo de un archivo de rutina. Ejemplo: Formato de un archivo de rutina inicio M1, J 1, A 10.0, V 100, M2, J 3, A 90.0, V 60, M3, J 2, A 50.0, V 73, M4, J 6, A 60.0, V 96, Fin El ejemplo revela el formato de un archivo de rutina de cuatro movimientos. La primer línea de texto del archivo señala el inicio de este y por lo tanto el inicio de la rutina. La segunda línea indica un movimiento en la primer articulación del brazo virtual, es decir la articulación que une la base y el hombro. El giro es de 10° y debe realizarse a una velocidad del 100% de la velocidad máxima de giro de tal articulación. La siguiente línea de texto se refiere a un giro de 90° en la tercer articulación (entre brazo1 y codo) a una velocidad del 60 % de la velocidad máxima del actuador de la articulación. De igual manera las siguientes dos líneas indican movimientos en la segunda y sexta articulación respectivamente. Nótese que las palabras que indican el inicio y el fin del archivo están escritas en minúscula. 5.3 Envío de ordenes al simulador por medio del teclado y el ratón Es posible enviar al simulador algunas órdenes por medio del teclado y el ratón. Por ejemplo, si se mueve el puntero del ratón dentro de la ventana de despliegue de la escena virtual, manteniendo el botón izquierdo (botón 1) presionado, la cámara en la escena virtual rota en dirección del movimiento, por otro lado, un desplazamiento del puntero dentro de la misma ventana pero manteniendo el botón central (botón 2) presionado, traslada la cámara en la dirección del movimiento, mientras que si el movimiento se hace manteniendo el botón derecho (botón 3) presionado, varia el acercamiento (<<zoom>>) de la cámara. 73 A continuación se muestra una listado de diferentes teclas y las órdenes ligadas a éstas. Para poder ejecutar órdenes por medio del teclado, la ventana de despliegue OpenGL, la cual es la ventana en donde se despliega la escena virtual, debe encontrarse seleccionada. Cuadro 5.1 Envío de ordenes por medio del teclado Tecla(s): r c z x Función Selecciona el Robot (obj = robot) Selecciona la Cámara (obj = cámara) Modo = Rotar la cámara Modo = Trasladar de la cámara if(obj = camara) keyLeft ← keyRight→ keyUp↑ keyDown↓ if(modo=rotar) if(modo=trasladar) + rotar(eje Y) - rotar(eje Y) + rotar(eje X) - rotar(eje X) + trasladar (eje Y) - trasladar (eje Y) if(obj = robot) + articulación 1 - articulación 1 + articulación 2 - articulación 2 PageUp PageDown g j Zoom Out Zoom In - + articulación 3 - articulación 3 + articulación 4 - articulación 4 y n o p - + articulación 5 - articulación 5 + articulación 6 - articulación 6 a d key Home key End F1 F2 F3 F5 F6 F7 F8 F9 F10 F11 F12 + velocidad cámara + velocidad robot - velocidad cámara - velocidad robot reiniciar posición robot reiniciar posición robot Desplegar Piso como Rejilla (ON/OFF) Limitar articulación a su amplitud de giro máxima (ON/OFF) Detener simulación al darse la primera colisión (ON/OFF) Abrir y Cargar Rutina (desde terminal) Crear Rutina (desde terminal) Salvar Rutina en memoria (desde terminal) Ejecutar Rutina en memoria Apilar posición actual del robot Desapilar Posición Limpiar Pila (borrar todas la posiciones apiladas) Desplegar los valores de los ángulos de las articulaciones CAPITULO 6: Pruebas finales y sus resultados Una vez finalizada la etapa de desarrollo de la aplicación de simulación, se llevo a cabo un proceso de pruebas, con el fin de determinar la estabilidad y desempeño de ésta. Las pruebas fueron realizadas en un computador con procesador Pentium 4 de 1.99GHz (<<giga hertz>>) de velocidad de procesamiento y 256 MB (<<mega bytes>>) de RAM (<<random access memory>>). 6.1 Pruebas destructivas Las primeras pruebas realizadas al programa fueron del tipo destructivas. En las entradas de texto de la aplicación se pasaron cadenas de caracteres de varios formatos y tamaños, se crearon rutinas que nunca fueron terminadas, se dieron ordenes de abrir archivos que no existían o cuyos formatos no concordaban con el formato de los archivos de rutina del simulador. Por medio de estás pruebas se detectó un fallo en la función encargada de copiar el texto ingresado por el usuario en las entradas de texto Gtk. Algunas veces después de copiar la cadena, no se añadía a ésta el caracter de finalización de cadena ‘\0’, lo que ocasionaba que se emitiera la señal de error de segmentación, al pasar la cadena como argumento de algunas funciones de la biblioteca estándar. Además la función muchas veces copiaba más caracteres de los que realmente había introducido el usuario, entonces al tener la cadena de caracteres un tamaño mayor al espacio de memoria destinado para ella, se daban también errores de segmentación. El problema fue solucionado limitando el número de caracteres que copia la función desde la entrada de texto y concatenado al final de todas la cadenas copiadas el carácter de terminación ‘\0’. Después de realizar estos cambios, se procedió nuevamente con las pruebas. No se volvieron a presentar en ninguna de ellas errores de segmentación. 6.2 Pruebas de velocidad También fueron realizadas pruebas de velocidad en diferentes puntos del programa. Algunas funciones poseen una velocidad que depende de la rapidez de reacción del usuario, por ejemplo, la velocidad del proceso de creación y edición de una rutina va a estar más ligada a la rapidez con la que el usuario introduzca los movimientos, que a la velocidad de procesamiento misma del programa. Las funciones y fragmentos de código que poseen velocidades de ejecución mayormente dependientes de la rapidez del usuario que del programa en sí, no fueron incluidos en estas pruebas. Por otro lado, el programa posee algunos procesos o fragmentos de éstos, sobre los cuales el usuario no tiene influencia. Se realizaron pruebas de velocidad a éstas segmentos de la aplicación, midiendo el tiempo del sistema a la entrada de cada segmento en prueba y a la salida de éste, luego se calculó la diferencia entre ambos valores, obteniendo así el tiempo de ejecución del proceso. Los tiempos fueron medidos en milisegundos. 74 75 Los procesos cronometrados fueron los siguientes: • Proceso que se lleva a cabo desde que se llama al programa, hasta que este entra al lazo de control principal infinito, en el cual están implicados a su ves los procesos de creación y despliegue de las ventanas. • El proceso de extraer los valores de las entradas de texto y de los botones de ajuste de la interfaz gráfica Gtk. • El proceso de cargar de los movimientos de un archivo de rutina a los espacios de memoria específicos. • El proceso realizado por la función dibujar(), la cuál se encargar de graficar la escena virtual. Cada proceso en prueba fue cronometrado treinta veces, luego se calculó el tiempo promedio de ejecución de cada uno. En promedio al programa le tomó 443 ms (milisegundos) realizar todas las tareas que implican la creación y el despliegue de las tres ventanas de la aplicación gráfica. La velocidad del proceso se considera buena, ya que es apenas perceptible por el usuario. Las mediciones de velocidad de las funciones de extracción y copia de información desde los objetos de la aplicación gráfica, mostraron resultados positivos. Todas las funciones que realizan este tipo de proceso extrajeron los valores, tanto de las entradas de texto como de los botones de ajuste Gtk, a velocidades realmente altas. De hecho no se pudo captar ninguna diferencia entre el tiempo de entrada y el de salida para estas funciones, en ninguna de las pruebas, es decir que el tiempo de ejecución siempre fue menor a 1 milisegundo. Estos resultados muestran un gran desempeño por parte de las funciones de extracción de información desde objetos Gtk. Se puede afirmar que la velocidad de éstas es lo suficientemente alta como para que no afecten de manera negativa, el desempeño de la aplicación. Al igual que para el proceso de extracción de información desde los objetos Gtk, la extracción de información desde archivos de texto, durante la función de carga de rutinas, también reveló tiempos de ejecución muy bajos. Para este caso tampoco se detectaron tiempos de ejecución mayores a un milisegundo, aún cuando se cargaron rutinas de hasta sesenta movimientos. La función dibujar(), es la encargada de graficar la escena virtual en la pantalla. Realmente esta función está compuesta por varias etapas. Lo primero que ésta hace es buscar el área de dibujo destinada al despliegue de objetos OpenGL, luego si el área existe y es encontrada la función la selecciona como área actual de dibujo. Como paso siguiente se deben limpiar los almacenadores intermedios de color y de profundidad (<<color buffer>> y <<depth buffer>>), determinar las opciones de dibujo elegidas por el usuario, llamar a la matriz de transformación actual, llevar a cabo las multiplicaciones de matrices de traslación y rotación que sean necesarias, así como el apile y “desapile” de la matriz de 76 transformación actual en los puntos que lo requieran y llamar las listas de despliegue específicas en los puntos necesarios. Luego se debe convocar a la función encargada de comparar la amplitud de giro de cada una de las articulaciones del modelo con sus amplitudes de giro máximas y esperar a que ésta termine de ser ejecutada. Posteriormente se llama a la función de prueba de colisiones y se aguarda a que esta función emita su resultado. Como último paso se exige la ejecución de todas las funciones OpenGL llamadas hasta este punto y se intercambian los almacenadores intermedios gráficos. Se observa que son varios pasos los que debe realizar la función dibujar(), de hecho esta es la función cuyo tiempo de ejecución posee mayor influencia en el desempeño del programa, en cuanto a velocidad de procesamiento se refiere, no solo por la cantidad de procesos que debe llevar a cabo, sino por que es una de las más utilizadas durante la ejecución de la aplicación. A pesar de todas las tareas que realiza, dibujar() expuso un tiempo promedio de ejecución de apenas 29 ms, de hecho el mayor tiempo que se logró cronometrar para esta función fue de 32 ms, el cual se considera como aceptable, si se toma en cuenta todos los pasos que conlleva el proceso. El tiempo es tan bajo que el usuario percibe la graficación como inmediata. Las pruebas de velocidad mostraron resultados muy positivos. En su mayoría los tiempos de ejecución de los procesos son tan bajos, que no pueden ser percibidos por el usuario. A continuación se muestran algunos de los resultados obtenidos durante las pruebas de velocidad. /* Ejemplo: Algunos valores de tiempo obtenidos en la pruebas de velocidad */ Tiempo Main: 382 ms Tiempo Main: 409 ms Tiempo Main: 190 ms Tiempo cargar movimiento a Simular: 0 ms Tiempo cargar movimiento a Simular: 0 ms Tiempo cargar movimiento a Simular: 0 ms Tiempo Cargar Rutina: 0 ms. Num. movimientos: 60 Tiempo Cargar Rutina: 0 ms. Num. movimientos: 37 Tiempo extracción valor botones de ajuste Mover (Drive): 0 ms Tiempo extracción valor botones de ajuste Mover (Drive): 0 ms Tiempo extracción valor entrada de texto: 0 ms Tiempo extracción valor entrada de texto: 0 ms Tiempo de dibujar(): 27 ms Tiempo de dibujar(): 32 ms Tiempo de dibujar(): 28 ms 77 6.3 Pruebas de memoria Las pruebas de memoria y búsqueda de fugas de memoria <<memory leaks>> también mostraron resultados positivos. Para llevar a cabo las pruebas se llamó a la aplicación y se examinó la cantidad de memoria utilizada en diferentes puntos de la ejecución de ésta, por ejemplo se crearon, editaron y cargaron archivos de rutinas, se dieron ordenes de movimientos al brazo, se cambio la configuración de éste etc, midiendo siempre la memoria en uso antes y después de cada proceso. Mediante las mediciones no se captó en ninguno momento un crecimiento elevado en el uso de memoria, lo que indica que no se produjeron fugas de ésta, por el contrario la memoria de acceso aleatorio utilizada por el programa se mantuvo prácticamente constante durante todo el tiempo que corrió el programa, para todas las ejecuciones que se llevaron a cabo. En promedio la aplicación presentó un consumo de 5 MB (mega bytes) de memoria RAM aproximadamente, el cual es un valor aceptable considerando las tareas que lleva a cabo el programa. A continuación se muestra una parte de los resultados de una de las pruebas de memoria que se llevaron a cabo. /* Ejemplo del proceso de las pruebas de memoria */ No se ha llamado el programa: total utilizada MemRAM: 256288 247080 libre 9208 Se corre el programa: total utilizada MemRAM: 256288 252864 libre 3424 Se ejecuta carga una rutina de treinta movimientos: total utilizada libre MemRAM: 256288 252292 3996 ... /*Se realizan otros procesos y se mide la memoria antes y después de cada uno */ Se cierra el programa: MemRAM: total 256288 utilizada 247432 libre 8856 Se corre el programa nuevamente: total utilizada libre MemRAM: 256288 252384 3904 … CAPITULO 7: Conclusiones y Recomendaciones 7.1 Conclusiones • El programa final es flexible en cuanto a las opciones de uso, se puede controlar ya sea desde un emulador de terminal o a través de la interfaz gráfica de usuario. • La interfaz gráfica de usuario creada en el proyecto, facilita el uso de la aplicación y la hace más amigable. • El API OpenGL demostró ser una biblioteca de gran robustez y flexibilidad, que permite el desarrollo de aplicaciones gráficas tanto simples como complejas. • El estudio previo y entendimiento de la teoría de modelado tridimensional en computadoras, facilita el uso de los API gráficos y permite al programador un mejor desenvolvimiento durante el desarrollo de aplicaciones gráficas. • Además de ser robusto, flexible, gratuito y amigable al usuario, el API OpenGL posee un amplio soporte por parte de los desarrolladores. Existe una gran cantidad de material de ayuda e información referente a la librería, lo cual facilita e incentiva el uso de ésta. • En general existe gran cantidad de documentación informativa y de ayuda tanto para el sistema GNU/Linux como para las aplicaciones y librerías de esta plataforma. El fácil acceso a esta documentación y la adquisición gratuita de programas libres promueve el desarrollo de nuevas aplicaciones de software y el mejoramiento de las ya existentes. • La aplicación desarrollada en el proyecto posee una flexibilidad que le permite adaptarse a diferentes configuraciones, lo que incrementa su rango de funcionalidad. 78 79 • El lenguaje de programación C presenta las características necesarias para el desarrollo de aplicaciones gráficas complejas. • La simulación virtual de un proceso permite al usuario obtener una idea previa del resultado a esperar, de manera que se pueden realizar los cambios necesarios para obtener una mayor eficiencia en el proceso, antes de que éste ocurra realmente. • La detección y prevención de colisiones es un tema de suma importancia, no solo en el área de la robótica, sino también en el área de control de otros tipos de dispositivos electromecánicos. Poder detectar una posible colisión antes de que ésta ocurra realmente, ayuda a incrementar tanto la seguridad del dispositivo como la de los usuarios. • Los métodos de detección de colisiones utilizados en el proyecto son los suficientemente flexibles como para ser adaptados a diferentes tipos de dispositivos electromecánicos. • En especial “la prueba de colisiones de orillas” (VORTEX) o “prueba de colisión de pendientes” creada en el proyecto, demostró ser no solamente efectiva y flexible, sino que presenta una estructura sumamente simple, en comparación con otros métodos de detección de colisiones normalmente utilizados en la industria de desarrollo de aplicaciones gráficas virtuales. • El lenguaje de programación utilizado, la interfaz gráfica de usuario y la estructura del código de la aplicación desarrollada en el proyecto, permiten una fácil implementación y adición del código creado en [4]. • El programa creado en este proyecto, puede ser expandido y aplicado a diferentes áreas, no solo referentes al desarrollo de escenas y mundos virtuales, sino que también ligadas al control de dispositivos electromecánicos. El proyecto representa una buena base, para futuras investigaciones ligadas al tema. 80 7.2 Recomendaciones • Es importante realizar una investigación previa a la creación de cualquier aplicación gráfica, con el fin de establecer cuales librerías gráficas representan la mejor opción tanto para el desarrollador como para el futuro usuario del programa. • Es posible añadir portabilidad a la aplicación desarrollada en el proyecto. El lograr que la aplicación sea compatible con diferentes sistemas operativos, representaría un gran beneficio para el usuario, ya que no se vería limitado al sistema GNU/Linux para poder utilizar el programa. • La realización de pruebas, utilizando tanto el simulador como el brazo real, permitiría, si fuera necesario, un mejor ajuste del margen de precaución del simulador, de manera que se obtenga el mejor desempeño posible de la aplicación de simulación. • Utilizando un programa de edición y diseño gráfico profesional sería posible crear un modelo virtual del brazo Stäubli RX90L más exacto, lo cual mejoraría la aplicación no solo desde el punto de vista estético, sino que podría permitir un incremento en la precisión del proceso de detección previa de colisiones. • La implementación de texturas en la escena virtual, podría conllevar a un mejoramiento en el aspecto y realismo de ésta. Esto haría la aplicación gráfica más agradable y mejoría la experiencia del usuario. • Es posible incrementar la funcionalidad del programa desarrollado en el proyecto, creando una función que permita al usuario cargar diferentes modelos virtuales de brazos robot, de manera que se podrían simular los movimientos en diferentes tipos de brazos y comparar los resultados. • Sería de gran utilidad para el usuario, que se desarrollara un algoritmo que calcule la combinación de ángulos necesaria, para llevar el extremo del brazo robot a una posición específica en el espacio. Tal algoritmo podría ser creado utilizando el método de cinemática inversa. 81 BIBLIOGRAFÍA [1] Giambruno, M. “3D Graphics & Animation: From Starting Up to Standing Out”, primera edición, New Riders Publishing, E.E.U.U, 1999. [2] Barrientos, A. “Fundamentos De La Robótica”, primera edición, McGrawHill, España, 1997. [3] Neider, J., Davis, T., Woo, M., “The OpenGL Programming Guide – The Red Book”, segunda edición, Addison-Wesley Publishing, E.E.U.U, 1999. [4] Montes Solano, C. “Envío de video hacia un cliente remoto utilizando el sistema video4linux y desarrollo de una aplicación gráfica GTK sobre una plataforma GNU/Linux, para el control de brazo robot Stäubli RX90”, proyecto para el grado de bachillerato en Ingeniería eléctrica, Universidad de Costa Rica, 2005. [5] Zeledón Chaves, E. Y Zeledón Méndez, P. “Creación de una interfase humano-maquina para el control, monitoreo y supervisión del brazo robot Stäubli RX90 vía internet”, proyecto para el grado de bachillerato en Ingeniería eléctrica, Universidad de Costa Rica, 2004. [6] OpenGL, “OpenGL Homepage”, http://www.opengl.org/, junio, 2006. [7] Wikipedia.<<Linux>>, Wikipedia, http://en.wikipedia.org/wiki/Linux, junio, 2006. [8] GTK, “GTK Homepage” , http://www.gtk.org/, junio, 2006. [9] Lenguaje C, “C programming”, http://www.cprogramming.com/, junio, 2006. [10] Wikipedia.<<DirectX>>, Wikipedia, http://en.wikipedia.org/wiki/Directx, junio, 2006. [11] “GL Programming”, http://www.glprogramming.com/, junio, 2006. [12] Stäubli, “Stäubli Robots”, http://www.staubli.com/web/robot/division. [13] Video juegos, “Game Development”, http://www.gamedev.net/ , junio 2006 APÉNDICES Archivos de código fuente de la aplicación desarrollada en el proyecto. callbacks.c: Funciones de respuesta de la interfaz gráfica de usuario /*************************************************************************** * SimQNK * Simulador virtual del brazo robot Stäubli RX90 L * callbacks.c: funciones de respuesta de la interfaz gráfica de usuario * Copyright(C) 2006 David Cuenca * Email: dcuenc@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #include <math.h> #include <stdlib.h> #include <string.h> #include <stdio.h> #include <glib.h> #include <gtk/gtk.h> #include <gtk/gtkgl.h> #include <gdk/gdk.h> #include <gdk/gdkgl.h> #include <gdk/gdkkeysyms.h> #include <GL/gl.h> #include <GL/glu.h> #include "callbacks.h" #include "interface.h" #include "dibujo.h" #include "ventanagl.h" #include "rutina.h" int simon=1; //variable de habilitación el simulador int edicion=0; //variable de habilitación de la edición de rutina int puntrutina; char nombrerutina[47]; const gchar *archivo_sel; FILE *archivo1; 82 83 //Posicion del puntero del mouse int lastx; int lasty; /*Función se encarga de Imprimir en TextView*/ void printtv(const char *texto){ int size_t; size_t=printf(texto); textbuffer1 = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview1)); gtk_text_buffer_get_end_iter(textbuffer1, &iter1); gtk_text_buffer_insert(textbuffer1, &iter1, texto, size_t); } /*Funcion del boton: Grid*/ void on_bgrid_toggled(GtkToggleButton *togglebutton, gpointer user_data){ if (GTK_TOGGLE_BUTTON (togglebutton)->active) { grid=1; //encender la rejilla }else { grid=0; //apagar la rejilla } dibujar(); } /*Funcion del boton: Fondo*/ /*Cambia el color de fondo de la ventana del mundo virtual*/ void on_btexturas_toggled(GtkToggleButton *togglebutton, gpointer user_data){ if (GTK_TOGGLE_BUTTON (togglebutton)->active) { /*Fondo de color Blanco*/ glClearColor(1.0, 1.0, 1.0, 0.0); }else { /*Fondo de color Negro*/ glClearColor(0.0, 0.0, 0.0, 0.0); } dibujar(); } /*Funcion del boton: Apilar*/ void on_bposicion1_clicked(GtkButton *button, gpointer user_data){ //Apilar posición actual push_posicion(); } /*Funcion del boton: Desapilar*/ void on_bposicion2_clicked(GtkButton *button, gpointer user_data){ //Desapilar posición anterior pull_posicion(); dibujar(); } /*Funcion del boton: Reiniciar Pila*/ void on_bposicion3_clicked(GtkButton *button, gpointer user_data){ //Reiniciar Pila de posiciones reiniciar_pila(); } /*Funcion del boton: Reiniciar Robot*/ void on_bposicion4_clicked(GtkButton *button, gpointer user_data){ //Reiniciar los valores de posición del Robot valores_iniciales_robot(); dibujar(); } /*Funcion del boton: Reiniciar Camara*/ 84 void on_bposicion5_clicked (GtkButton *button, gpointer user_data){ //Reiniciar los valores de posición de la Cámara valores_iniciales_camara(); reshape(ventanaw, ventanah); dibujar(); } /*Funcion del boton: Lim. Angulos*/ void on_bcheckmax_toggled(GtkToggleButton *togglebutton, gpointer user_data){ if (GTK_TOGGLE_BUTTON (togglebutton)->active) { checkmax=1; //habilitar prueba de angulos máximos }else { checkmax=0; //deshabilitar prueba de angulos máximos } } /*Funcion del boton: 1° Colision*/ void on_bcolision1_toggled(GtkToggleButton *togglebutton, gpointer user_data){ if (GTK_TOGGLE_BUTTON (togglebutton)->active) { checkcolision=1; //detener rutina al darse la primera colisión }else { checkcolision=0; //continuar rutina aun despues de la primera colision } } /*Funcion del boton: Simulador*/ void on_bsimulador_toggled (GtkToggleButton *togglebutton, gpointer user_data){ //ENCENDER SIMULADOR if (GTK_TOGGLE_BUTTON (togglebutton)->active) { simon=1; //simular las ordenes }else { simon=0; //no simular las ordenes } } /* Dialogo cuando se da una colision en la edición de la simulación */ gint dialogo_mensaje(void){ GtkWidget *dialog, *label; gint result; dialog = gtk_dialog_new_with_buttons ("Advertencia!", GTK_WINDOW(ventana1), GTK_DIALOG_MODAL, GTK_STOCK_YES, 1, GTK_STOCK_NO, 0, NULL); label = gtk_label_new ("CUIDADO->Se dio una colisión. \n¿Desea aun asi agregar el movimiento a la rutina?"); gtk_container_add (GTK_CONTAINER (GTK_DIALOG(dialog)->vbox), label); gtk_widget_show_all (dialog); /* Detiene la aplicación hasta que se de una respuesta al dialogo*/ result = gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy(dialog); return result; } /*Funcion del boton: MOVER (Drive)*/ void on_bdrive_clicked(GtkButton *button, gpointer user_data){ GtkSpinButton *spin1, *spin2, *spin3; int velocporciento; float *temp_angulo = malloc(sizeof (float) * 6); gint guardar; spin1=GTK_SPIN_BUTTON(spinbutton1); spin2=GTK_SPIN_BUTTON(spinbutton2); spin3=GTK_SPIN_BUTTON(spinbutton3); 85 hubochoque=0; /*Toma los valores de los botones 'spinners'*/ joint=gtk_spin_button_get_value_as_int(spin1); angulodelta=gtk_spin_button_get_value_as_int(spin2); velocporciento=gtk_spin_button_get_value_as_int(spin3); set_velocidad(velocporciento, joint); /*Simula el movimiento especificado por los valores de los spinners*/ if(simon==1 && edicion==0){ selec_mover_joint(); if(hubochoque==1){ printtv("\nSe produjo una colision-->La señal NO será enviada al robot\n"); }else if(hubochoque==0){ printtv("\nEnviando señal al robot\n"); /*Aqui se debe introducir el codigo para enviar la señal respectiva al brazo robot*/ } }else if(simon==1 && edicion==1){ /*edición de una rutina*/ /*guardar el movimiento en la matriz de rutinas*/ puntrutina+=1; rutina[puntrutina][0]=joint; rutina[puntrutina][1]=angulodelta; rutina[puntrutina][2]=velocporciento; rutina[0][0]+=1; /*Guarda los angulos actuales del robot en una memoria temporal, antes de moverlo*/ temp_angulo[0]=angulo1; temp_angulo[1]=angulo2; temp_angulo[2]=angulo3; temp_angulo[3]=angulo4; temp_angulo[4]=angulo5; temp_angulo[5]=angulo6; /*simular el movimiento del robot*/ selec_mover_joint(); /*Si no hubo choque guarda el movimient. en la rutina y envia la señal al robot*/ if(hubochoque!=1){ //guardar el movimiento en la matriz de rutinas fprintf(archivo1,"M%i, J %d, A %3.1f, V %d,\n",puntrutina,(int)rutina[puntrutina][0],rutina[puntrutina][1],(int)rutina[puntrutina][2]); /*Aqui se debe introducir el codigo para enviar la señal respectiva al brazo robot*/ } /*Si hubo choque pregunta si se debe guardar el movimiento.*/ if(hubochoque==1){ printtv("\n\n!!CUIDADO!!-->se produjo una colision durante la simulación del movimiento"); printtv("\nSe recomienda eliminar el ultimo movimiento de la rutina"); guardar=dialogo_mensaje(); if(guardar==1){ /*guardar el movimiento en la matriz de rutinas y enviar señal*/ printtv("\nSe guardo el movimiento en la rutina"); fprintf(archivo1,"M%i, J %d, A %3.1f, V %d,\n",puntrutina,(int)rutina[puntrutina][0],rutina[puntrutina][1],(int)rutina[puntrutina][2]); /*Aqui se debe introducir el codigo para enviar la señal respectiva al brazo robot*/ }else { /*Se devuelve el brazo y el puntero de rutina a la posición anterior*/ printtv("\nNo se guardo el movimiento en la rutina"); puntrutina-=1; angulo1=temp_angulo[0]; angulo2=temp_angulo[1]; angulo3=temp_angulo[2]; angulo4=temp_angulo[3]; angulo5=temp_angulo[4]; angulo6=temp_angulo[5]; dibujar(); /*No se le envÃa ninguna señal al robot*/ 86 } } /*Si se alcanzan los 60 movimientos-> Se termina la edición de rutina del simulador*/ if(puntrutina==60){ printtv("Movimiento #60 --> Máximo que puede guardar la rutina del simulador"); fprintf(archivo1,"fin"); fclose(archivo1); edicion=0; printtv("\n\nFin de la edición de la rutina"); printf("\nFin de la edición de la rutina: %s", nombrerutina); } }else if(simon==0){ printtv("\nSimulador Apagado. Enviando la señal directamente al robot"); /*Aqui se debe introducir el codigo para enviar la señal respectiva al brazo robot*/ } free(temp_angulo); fflush(stdout); } /*Funcion del boton: Simular*/ void on_bsimular_clicked (GtkButton *button, gpointer user_data){ /*Simula el movimiento del robot utilizando los valores de los botones 'spinner'*/ GtkSpinButton *spin1, *spin2, *spin3; int velocporciento; spin1=GTK_SPIN_BUTTON(spinbutton1); spin2=GTK_SPIN_BUTTON(spinbutton2); spin3=GTK_SPIN_BUTTON(spinbutton3); hubochoque=0; //Toma los valores de los botones 'spinners' joint = gtk_spin_button_get_value_as_int(spin1); angulodelta = gtk_spin_button_get_value_as_int(spin2); velocporciento = gtk_spin_button_get_value_as_int(spin3); set_velocidad(velocporciento, joint); selec_mover_joint(); if(hubochoque==1){ printtv("\n\n¡¡¡CUIDADO!!!-->Se produjo un choque durante la simulación del movimiento"); } } /*Funcion del boton: Ejecutar*/ void on_bejecutar_clicked (GtkButton *button, gpointer user_data){ /*Ejecutar comando introducido el linea de comandos*/ char entrada[30]; char *comandos[7]; char base_fija[20]; char margen_p[20]; int comp[7]; int pc; char letra_c; /*lista de comandos */ comandos[0]="e\0"; comandos[1]="edit "; comandos[2]="ex "; comandos[3]="drive "; comandos[4]="base"; comandos[5]="margen"; /*extrae la cadena de caracteres de la entrade de texto entrycomando*/ strncpy(entrada, gtk_entry_get_text(GTK_ENTRY(entrycomando)), sizeof(entrada)); entrada[sizeof(entrada)-1]='\0'; /*compara el comando recibido con la lista de comandos de arriba*/ comp[0] = strncmp(entrada, comandos[0], 2); 87 comp[1] = strncmp(entrada, comandos[1], 5); comp[2] = strncmp(entrada, comandos[2], 3); comp[3] = strncmp(entrada, comandos[3], 6); comp[4] = strncmp(entrada, comandos[4], 4); comp[5] = strncmp(entrada, comandos[5], 5); /*dependiendo del resultado de la comparación realiza alguna de las sigueintes acciones*/ if(comp[0] == 0 && edicion==1){ fprintf(archivo1,"fin"); fclose(archivo1); /*fin de la edición de la rutina*/ edicion=0; printtv("\n\nFin de la edición de la rutina"); printf("\nFin de la edición de la rutina: %s", nombrerutina); /*Aqui se debe introducir el codigo para enviar la señal respectiva al brazo robot*/ }else if((comp[1] ==0 || comp[2] ==0) && (simon==1)){ printtv("\n\nSi desea utilizar el simulador, los comandos de las Rutinas se deben dar utilizando los botones de la rutina"); printtv("\nNo se ejecutó la orden. Solucion: Utilizar botónes de Rutina o deshabilitar el simulador"); }else if(comp[3] == 0 && simon==1){ printtv("\n\nSi desea utilizar el simulador, los comandos DRIVE se deben dar utilizando el botón DRIVE"); printtv("\nNo se ejecutó la orden. Solucion: Utilizar botón DRIVE o deshabilitar el simulador"); }else if(comp[4] == 0){ /*Se recibio el comando de adaptar una base externa*/ printtv("\n\nAdaptar base\0"); pc=4; letra_c='e'; while(letra_c != '\0'){ letra_c=entrada[pc]; base_fija[pc-4]=entrada[pc]; pc++; } altpiso=-1*(atof(base_fija)); printf(" de: %.1f centÃmetros de alto", -1*altpiso); dibujar(); }else if(comp[5] == 0){ /*se recibio el comando de estblecer un margen de precaución*/ printtv("\n\nEstablecer margen de precaucion"); pc=6; letra_c='n'; while(letra_c != '\0'){ letra_c=entrada[pc]; margen_p[pc-6]=entrada[pc]; pc++; } marg=atof(margen_p); init_vortexfijos(marg); printf(" de: %.1f centÃmetros", marg); }else{ printtv("\nEnviando señal al robot"); /*Aqui se debe introducir el codigo para enviar la señal respectiva al brazo robot*/ } gtk_entry_set_text(GTK_ENTRY(entrycomando),""); fflush(stdout); /* previene que el handler predeterminado corra */ gtk_signal_emit_stop_by_name(GTK_OBJECT(button),"clicked"); } /*Funcion del boton: Crear Rutina*/ 88 void on_brutina1_clicked (GtkButton *button, gpointer user_data){ /*Establecer el nombre de edición de una nueva rutina y el inicio de la edición de esta misma*/ edicion=1; //se habilita la edición de la rutina puntrutina=0; //se inicializa el puntero de la matriz de rutinas rutina[0][0]=0; //se inicializa el numero de movimientos en la matriz de rutinas strcpy(nombrerutina, "./rutinas/\0"); strncat(nombrerutina, gtk_entry_get_text(GTK_ENTRY(entryrutina)),(sizeof(nombrerutina)-sizeof("./rutinas/\0")-3)); nombrerutina[sizeof(nombrerutina)-1]='\0'; printtv("\n\nInicio de la edición de la rutina"); printf("\nInicio de la edición de la rutina: %s", nombrerutina); fflush(stdout); archivo1=fopen(nombrerutina,"w"); fprintf(archivo1,"inicio\n"); } /* Guarda el nombre del archivo seleccionado */ void guardar_nombre (GtkWidget *widget, gpointer user_data) { GtkWidget *file_selector = GTK_WIDGET (user_data); archivo_sel = gtk_file_selection_get_filename (GTK_FILE_SELECTION (file_selector)); printtv("\n\nCargar Archivo seleccionado"); printf("\nCargar Archivo: %s\n", archivo_sel); } /* Crea la ventana de seleccion de archivos */ void crear_selector (void) { GtkWidget *file_selector; file_selector = gtk_file_selection_new ("Selector de Archivos"); g_signal_connect (GTK_FILE_SELECTION (file_selector)->ok_button, "clicked", G_CALLBACK (guardar_nombre), file_selector); g_signal_connect_swapped (GTK_FILE_SELECTION (file_selector)->ok_button, "clicked", G_CALLBACK (gtk_widget_destroy), file_selector); g_signal_connect_swapped (GTK_FILE_SELECTION (file_selector)->cancel_button, "clicked", G_CALLBACK (gtk_widget_destroy), file_selector); gtk_widget_show (file_selector); gtk_dialog_run(GTK_DIALOG(file_selector)); } /*Funcion del boton: Cargar Rutina*/ void on_brutina2clicked (GtkButton *button, gpointer user_data){ FILE *archivo2; char letra_ui; char articult_ui[3]; char angt_ui[7]; char veloct_ui[7]; char linea_ui[15]; int p1, p2, p3; //punteros /*llama a la ventana de seleccion de archivos*/ crear_selector(); /*abre el archivo seleccionado*/ archivo2=fopen(archivo_sel,"r"); if (!archivo2){ printtv("\n\n-ERROR-->No se pudo abrir el archivo"); printf("\n-ERROR-->No se pudo abrir el archivo %s", archivo_sel); } else { 89 /*comienza a extraer la información del archivo*/ p1=0; rutina[0][0]=0; do{ /*se trata de un nuevo movimiento*/ letra_ui=fgetc(archivo2); if(letra_ui=='M'){ if(p1<60){ p1++; rutina[0][0]++; } }else if(letra_ui=='A'){ /*se trata de un angulo*/ p2=0; while(letra_ui!=','){ letra_ui=fgetc(archivo2); angt_ui[p2]=letra_ui; p2++; } rutina[p1][1]=atof(angt_ui); }else if(letra_ui=='J'){ /*se trata de una articulación (joint)*/ p2=0; while(letra_ui!=','){ letra_ui=fgetc(archivo2); articult_ui[p2]=letra_ui; p2++; } rutina[p1][0]=atof(articult_ui); }else if(letra_ui=='V'){ /*se trata de la velocidad del movimiento*/ p2=0; while(letra_ui!=','){ letra_ui=fgetc(archivo2); veloct_ui[p2]=letra_ui; p2++; } rutina[p1][2]=atof(veloct_ui); } }while (letra_ui!='f'); /*se cierra el archivo*/ fclose(archivo2); } } /*Funcion del boton: Ejecutar Rutina*/ void on_brutina3_clicked(GtkButton *button, gpointer user_data){ //Ejecutar Rutina hubochoque=0; if(simon==1){ /*Se simula la rutina*/ ejecutar_rutina(); if(hubochoque==1){ /*se dio una colision*/ printtv("\nSe produjo una colision-->La señal de ejecutar rutina NO será enviada al robot\n"); }else if(hubochoque==0){ /*no se dio ninguna colision*/ printtv("\nRutina Segura. Enviando señal de ejecutar rutina al robot\n"); /*Aqui se debe introducir el codigo para enviar la señal respectiva al brazo robot*/ } }else{ /*Simulador apagado->No se simula la rutina*/ /*La señal se envÃa de una vez al robot. Sin simular*/ 90 printtv("\nSimulador Apagado. Enviando señal de ejecutar rutina al robot\n"); /*Aqui se debe introducir el codigo para enviar la señal respectiva al brazo robot*/ } } /***********VENTANA OPENGL************************/ /*Movimiento del Mouse genera movimiento de la camara, *si se mantiene alguno de los botones presionados*/ gboolean on_glarea_motion_notify_event(GtkWidget *widget, GdkEventMotion *event, gpointer user_data){ int x; int y; int difx; int dify; GdkModifierType state; if (event->is_hint) { gdk_window_get_pointer(event->window, &x, &y, &state); } /*calcula la diferencia entre las coordenadas actuales del ratón y las pasadas*/ difx=event->x - lastx; dify=event->y - lasty; lastx=event->x; lasty=event->y; /*hace un acercamiento de la camara*/ if (event->state & GDK_BUTTON3_MASK) { zoom_z -= (float) 0.1f * difx; }else if (event->state & GDK_BUTTON1_MASK) { /*rota la camara*/ angulo_xr += (float) 0.5f * dify; angulo_yr += (float) 0.5f * difx; }else if (event->state & GDK_BUTTON2_MASK) { /*traslada la camara*/ ancho_x += (float) 0.1f * difx; altura_y -= (float) 0.1f * dify; } reshape(ventanaw, ventanah); dibujar(); return TRUE; } //Boton del Mouse presionado gboolean on_glarea_button_press_event(GtkWidget *widget, GdkEventButton *event, gpointer user_data){ /*se guarda la posicion del mouse*/ lastx= event->x; lasty= event->y; return TRUE; } //Tecla presionada /*Se llama una función por cada tecla presionada dentro de la ventana OpenGL*/ gboolean on_glarea_key_press_event(GtkWidget *widget, GdkEventKey *event, gpointer user_data){ event->keyval; if(event->keyval==GDK_x){ printtv("\nDesplazar-->CAMARA"); mov_cam=1; }else if(event->keyval==GDK_z){ printtv("\nRotar-->CAMARA"); mov_cam=2; }else if(event->keyval==GDK_c){ 91 printtv("\nObjeto Seleccionado-->CAMARA"); mover_obj=2; }else if(event->keyval==GDK_r){ printtv("\nObjeto Seleccionado-->ROBOT"); mover_obj=1; }else if(event->keyval==GDK_l){ if(lim==0){ lim=1; }else { lim=0; } }else if(mover_obj == 1){ /*se mueve el modelo del robot*/ if(event->keyval==GDK_y){ angulo5-=aument; }else if(event->keyval==GDK_n){ angulo5+=aument; }else if(event->keyval==GDK_j){ angulo4-=aument; }else if(event->keyval==GDK_g){ angulo4+=aument; }else if(event->keyval==GDK_o){ angulo6+=aument; }else if(event->keyval==GDK_p){ angulo6-=aument; }else if(event->keyval==GDK_a){ printtv("\nAUMENTAR VELOCIDAD ROBOT"); aument+=1; if (aument > 45){ aument = 45; } }else if(event->keyval==GDK_d){ printtv("\nDISMINUIR VELOCIDAD ROBOT"); aument-=1; if (aument < 1){ aument = 1; } } }else if(mover_obj==2){ //*se mueve la camara en la escena*/ if(event->keyval==GDK_a){ printtv("\nAUMENTAR VELOCIDAD CAMARA"); aument_cam+=1; if (aument_cam > 45){ aument_cam = 45; } }else if(event->keyval==GDK_d){ printtv("\nDISMINUIR VELOCIDAD CAMARA"); aument_cam-=1; if (aument_cam < 1){ aument_cam = 1; } } } if(event->keyval==GDK_F1){ if(grid==0){ /*se activa la rejilla del piso*/ grid=1; gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (bgrid), TRUE); }else { /*se desactiva la rejilla*/ grid=0; gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (bgrid), FALSE); } }else if(event->keyval==GDK_F2){ if(checkmax==0){ 92 /*se activa la opción de limite de angulos máximos*/ checkmax=1; gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (bcheckmax), TRUE); }else { checkmax=0; gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (bcheckmax), FALSE); } }else if(event->keyval==GDK_F3){ if(checkcolision==0){ /* se activa la opción de detención de la sim. en el primer choque*/ checkcolision=1; gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (bcolision1), TRUE); }else { checkcolision=0; gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (bcolision1), FALSE); } }else if(event->keyval==GDK_F5){ printf("\nCREAR RUTINA"); crear_rutina(); }else if(event->keyval==GDK_F6){ printf("\nABRIR Y CARGAR RUTINA"); abrir_rutina(); }else if(event->keyval==GDK_F7){ printf("\nSALVAR RUTINA"); salvar_rutina(); }else if(event->keyval==GDK_F8){ printtv("\nEJECUTAR RUTINA"); ejecutar_rutina(); }else if(event->keyval==GDK_F9){ printtv("\nAPILAR POSICION"); push_posicion(); dibujar(); }else if(event->keyval==GDK_F10){ printtv("\nDESAPILAR POSICION"); pull_posicion(); dibujar(); }else if(event->keyval==GDK_F11){ printtv("\nREINICIAR PILA"); reiniciar_pila(); }else if(event->keyval==GDK_F12){ printf("\nInformación:"); printf("\nAngulos: A1 %.0f, A2 %.0f, A3 %.0f, A4 %.0f, A5 %.0f, A6 %.0f\n", angulo1,-1*angulo2,1*angulo3,angulo4,-1*angulo5,angulo6); } if(mover_obj == 1){/*se mueve el robot*/ if(event->keyval==GDK_Up){ angulo2-=aument; }else if(event->keyval==GDK_Down){ angulo2+=aument; }else if(event->keyval==GDK_Right){ angulo1-=aument; }else if(event->keyval==GDK_Left){ angulo1+=aument; }else if(event->keyval==GDK_Page_Up){ angulo3-=aument; }else if(event->keyval==GDK_Page_Down){ angulo3+=aument; } }else if(mover_obj== 2){/*se mueve la camara*/ if(event->keyval==GDK_Page_Up){ zoom_z-=1; }else if(event->keyval==GDK_Page_Down){ zoom_z+=1; }else if(mov_cam==1){ 93 if(event->keyval==GDK_Up){ altura_y+=aument_cam; }else if(event->keyval==GDK_Down){ altura_y-=aument_cam; } }else if(mov_cam==2){ if(event->keyval==GDK_Up){ angulo_xr+=aument_cam; }else if(event->keyval==GDK_Down){ angulo_xr-=aument_cam; }else if(event->keyval==GDK_Right){ angulo_yr+=aument_cam; }else if(event->keyval==GDK_Left){ angulo_yr-=aument_cam; } } reshape(ventanaw, ventanah); } if(event->keyval==GDK_Home){ printtv("\nREINICIANDO ROBOT"); mover_obj = 1; valores_iniciales_robot();//REINICIAR POSICION ROBOT }else if(event->keyval==GDK_End){ printtv("\nREINICIANDO CAMARA"); valores_iniciales_camara();//REINICIAR POSICION CAMARA reshape(ventanaw, ventanah); } dibujar(); return TRUE; } gboolean on_glarea_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer user_data){ /*se da un evento de exposición , la escena se dibuja de nuevo*/ dibujar(); return TRUE; } gboolean on_glarea_configure_event (GtkWidget* widget, GdkEventConfigure* event) { /*se cambio el tamaño de la ventana, se llama a la función de ajuste de ventana: reshape*/ ventanaw = widget->allocation.width; ventanah = widget->allocation.height; reshape(ventanaw, ventanah); return TRUE; } gboolean glarea_init (GtkWidget* widget) { /*la ventana esta lista para ser iniciada: se llama a la función inicio()*/ inicio(); return TRUE; } void on_bsim_clicked(GtkButton *button, gpointer user_data){ /*Simula el movimiento del robot utilizando los valores de los botones 'spinner'*/ GtkSpinButton *spin1, *spin2, *spin3; int velocporciento; spin1=GTK_SPIN_BUTTON(spinbutton1); spin2=GTK_SPIN_BUTTON(spinbutton2); spin3=GTK_SPIN_BUTTON(spinbutton3); hubochoque=0; /*Toma los valores de los botones 'spinners'*/ 94 joint = gtk_spin_button_get_value_as_int(spin1); angulodelta = gtk_spin_button_get_value_as_int(spin2); velocporciento=gtk_spin_button_get_value_as_int(spin3); set_velocidad(velocporciento, joint); /*simula el movimiento*/ selec_mover_joint(); if(hubochoque==1){ printtv("\n\n¡¡¡CUIDADO!!!-->Se produjo un choque durante la simulación del movimiento"); } } /* Información del Programa*/ void on_bacerca_clicked(GtkButton *button, gpointer user_data){ GtkWidget *mensaje; mensaje = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, "Programa (Program): SimQNK \nSimulador del brazo Robot Stäubli RX90L (Robotic Arm Simulator)\nAutor (developer): David Cuenca Alpízar\nJunio 2006 (June 2006)\n(C) Copyright 2006\nE-mail: dcuenc@gmail.com", NULL); gtk_widget_show(mensaje); /* Destruir Ventana de mensaje al presionar el botón */ g_signal_connect_swapped (mensaje, "response", G_CALLBACK (gtk_widget_destroy), mensaje); } /*************************************************************************************************************/ dibujo.c: Funciones de despliegue de la escena virtual y de pruebas de colisión /*************************************************************************** * SimQNK * Simulador virtual del brazo robot Stäubli RX90 L * dibujo.c: funciones de dibujo y pruebas de colision del modelo 3D * Copyright(C) 2006 David Cuenca * Email: dcuenc@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "dibujo.h" #define BASE 1 #define BRAZO1 2 #define BRAZO2 4 #define CODO 3 #define MUNIECA 5 95 #define MANO 6 #define ESFERA 7 #define CUBO 8 #define HOMBRO 9 #define EMPO 10 //Vectores para prueba de colision float menorz [4]; float menory [4]; float menorz2 [5]; float menory2 [5]; float vortexm [4]; /*Vectores de las orillas de choque del hombro*/ float vortexfijo1 [3] = {0, 28.5, 13.1}; float vortexfijo2 [3] = {0, 54.5, 13.1}; //Constantes para prueba de colision float nively1=28.5; float nively2=54.5; float zhombro=13.1; float numpuntos=30; int choquep; //control de choque contra el piso int choqueb; //control de choque contra la base int choqueh; //control de choque contra el hombro int choquev; //control de choque contra los vortices del hombro float radbase=12; int npuntos=30; //Punteros de control de colision int i; int j; int n; int cuidado; //Vectores para multiplicar matrices float vtemp [4]; float vectr[4]; //Figuras de control float circHB [31][4]; float circHA [31][4]; float circWA [31][4]; float circWB [31][4]; float circWC [31][4]; float circBD [31][4]; float circBC [31][4]; float cuadBA [5][4]={0,0,0,0, 6.6,-15,-5.7,1, -5.7,-15,-5.7,1, -5.7,-15,5.7,1, 6.6,-15,5.7,1}; float cuadBB [5][4]={0,0,0,0, 12,-10,-11.1,1, -10.2,-10,-11.1,1, -10.2,-10,11.1,1, 12,-10,11.1,1}; float cuadBC [5][4]={0,0,0,0, 12,10,-11.1,1, -10.2,10,-11.1,1, -10.2,10,11.1,1, 12,10,11.1,1}; float circHBreal [31][4]; float circHAreal [31][4]; float circWAreal [31][4]; float circWBreal [31][4]; float circWCreal [31][4]; float circBDreal [31][4]; float circBCreal [31][4]; float cuadBAreal [5][4]; float cuadBBreal [5][4]; float cuadBCreal [5][4]; //Puntos de control float CHB [5]={0.0, 8.5, 0.0, 1, 5}; float CHA [5]={0.0, 5.6, 0.0, 1, 5}; float CWA [5]={-5.5, 55.0, 0.0, 1, 4}; float CWB [5]={0.0, 55.0, 0.0, 1, 4}; 96 float CWC [5]={5.5, 55.0, 0.0, 1, 4}; float CBA [5]={0.0, -15, 0.0, 1, 3}; float CBB [5]={0.0, -10, 0.0, 1, 3}; float CBC [5]={0.0, 10, 0.0, 1, 3}; float CBD [5]={0.0, 58, 0.0, 1, 3}; float CHBreal [3]; float CHAreal [3]; float CWAreal [3]; float CWBreal [3]; float CWCreal [3]; float CBAreal [3]; float CBBreal [3]; float CBCreal [3]; float CBDreal [3]; //Matrices de rotacion y traslacion float rotx2 [4][4] = {1,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,1};//rotacion en x angulo2 float rotx3 [4][4] = {1,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,1};//rotacion en x angulo3 float rotx5 [4][4] = {1,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,1};//rotacion en x angulo5 float roty4 [4][4] = {0,0,0,0, 0,1,0,0, 0,0,0,0, 0,0,0,1};//rotacion en y angulo4 float tras1 [4][4] = {1,0,0,0, 0,1,0,42, 0,0,1,0, 0,0,0,1};//traslacion float tras2 [4][4] = {1,0,0,0, 0,1,0,45, 0,0,1,0, 0,0,0,1}; float tras3 [4][4] = {1,0,0,0, 0,1,0,10, 0,0,1,0, 0,0,0,1};//traslacion float tras4 [4][4] = {1,0,0,0, 0,1,0,55, 0,0,1,0, 0,0,0,1}; /*Valores de posicion inicial del robot*/ void valores_iniciales_robot(void){ printf("\nObjeto Seleccionado--> ROBOT"); angulo1 = 0; angulo2 = 0; angulo3 = 0; angulo4 = 0; angulo5 = 0; angulo6 = 0; aument = 1; } /*Llenar Matrices de rotacion*/ void llenar_matrices_rot(void){ rotx2[1][1]=cos(angulo2*M_PI/180); rotx2[1][2]=(-1)*sin(angulo2*M_PI/180); rotx2[2][1]=sin(angulo2*M_PI/180); rotx2[2][2]=cos(angulo2*M_PI/180); rotx3[1][1]=cos(angulo3*M_PI/180); rotx3[1][2]=(-1)*sin(angulo3*M_PI/180); rotx3[2][1]=sin(angulo3*M_PI/180); rotx3[2][2]=cos(angulo3*M_PI/180); rotx5[1][1]=cos(angulo5*M_PI/180); rotx5[1][2]=(-1)*sin(angulo5*M_PI/180); rotx5[2][1]=sin(angulo5*M_PI/180); rotx5[2][2]=cos(angulo5*M_PI/180); roty4[0][0]=cos(angulo4*M_PI/180); roty4[0][2]=sin(angulo4*M_PI/180); roty4[2][0]=(-1)*sin(angulo4*M_PI/180); roty4[2][2]=cos(angulo4*M_PI/180); } /*Llenar los vectores de posición de la orillas del hombro fijos*/ void init_vortexfijos(float margen){ 97 vortexfijo1[1]=28.5-(margen/2); vortexfijo1[2]=13.1+(margen/2); vortexfijo2[1]=54.5+(margen/2); vortexfijo2[2]=13.1+(margen/2); } /*DIBUJAR Linea de control de la prueba de colision de orillas*/ void dibujarvortex(float numero1[], float numero2[]){ glPushMatrix(); glRotatef(angulo1, 0, 1, 0); glBegin(GL_LINES); glVertex3f(numero1[0], numero1[1], numero1[2]); glVertex3f(numero2[0], numero2[1], numero2[2]); glEnd(); glPopMatrix(); } /*Multiplicacion Matriz(4x4) x Vector(4)*/ void mult_mat_vect(float mat[4][4],float vect[5]){ for(i=0; i<4; i++){ vectr[i]=mat[i][0]*vect[0]+mat[i][1]*vect[1]+mat[i][2]*vect[2]+mat[i][3]*vect[3]; } } /*Pasar a Vtemp. Vetor temporal*/ void llenar_vtemp(void){ for(i=0;i<4;i++){ vtemp[i]=vectr[i]; } } /*Pasar punto de coordenadas locales a coordenas globales*/ void coord_globales_pta(float numero[5], float numreal[3]){ int s; s=numero[4];//Nivel de jeraquía del sistema de coordenadas llenar_matrices_rot(); /*Concatenación de matrices para nivel de jerarquia 5*/ if(s==5){ mult_mat_vect(rotx5, numero); llenar_vtemp(); mult_mat_vect(tras4, vtemp); llenar_vtemp(); mult_mat_vect(roty4, vtemp); llenar_vtemp(); mult_mat_vect(tras3, vtemp); llenar_vtemp(); mult_mat_vect(rotx3, vtemp); llenar_vtemp(); mult_mat_vect(tras2, vtemp); llenar_vtemp(); mult_mat_vect(rotx2, vtemp); llenar_vtemp(); mult_mat_vect(tras1, vtemp); for(j=0;j<4;j++){ numreal[j]=vectr[j]; } } /*Concatenación de matrices para nivel de jerarquia 4*/ if(s==4){ mult_mat_vect(roty4, numero); llenar_vtemp(); mult_mat_vect(tras3, vtemp); llenar_vtemp(); 98 mult_mat_vect(rotx3, vtemp); llenar_vtemp(); mult_mat_vect(tras2, vtemp); llenar_vtemp(); mult_mat_vect(rotx2, vtemp); llenar_vtemp(); mult_mat_vect(tras1, vtemp); for(j=0;j<4;j++){ numreal[j]=vectr[j]; } } /*Concatenación de matrices para nivel de jerarquia 3*/ if(s==3){ mult_mat_vect(rotx3, numero); llenar_vtemp(); mult_mat_vect(tras2, vtemp); llenar_vtemp(); mult_mat_vect(rotx2, vtemp); llenar_vtemp(); mult_mat_vect(tras1, vtemp); for(j=0;j<4;j++){ numreal[j]=vectr[j]; } } } /*Comparar pendientes de la prueba de colision de orillas (VORTEX)*/ void vortex_test(float numero1[4], float numero2[4], float numero3[3], int mayor){ if((numero1[2]!=numero2[2]) && (numero1[2]!=numero3[2])){ /*Calcular la pendiente de la liena de control*/ float pendiente1 = (numero2[1]-numero1[1])/(numero2[2]-numero1[2]); float pendiente2; /*Prueba sobre las orillas inferiores*/ if(mayor==1){ if((pendiente1>0) && (numero1[2]>0)){ /*Calcular la pendiente de la linea al hombro (rango z positivo:orilla derecha)*/ pendiente2 = (numero3[1]-numero1[1])/(numero3[2]-numero1[2]); if((pendiente1 >= pendiente2) && (pendiente2 > 0)){ choquev=1; } } if((pendiente1<0) && (numero1[2]<0)){ /*Calcular la pendiente de la linea al hombro (rango z negativo:orilla izquierda)*/ pendiente2 = (numero3[1]-numero1[1])/((-1)*numero3[2]-numero1[2]); if((pendiente1 <= pendiente2) && (pendiente2 < 0)){ choquev=1; } } } /*Prueba sobre las orillas inferiores*/ else if(mayor==2){ if((pendiente1>0) && (numero1[2]<0)){ /*Calcular la pendiente de la linea al hombro (rango z negativo:orilla izquierda)*/ pendiente2 = (numero3[1]-numero1[1])/((-1)*numero3[2]-numero1[2]); if((pendiente1 <= pendiente2) && (pendiente2 > 0)){ choquev=1; } } if((pendiente1<0) && (numero1[2]>0)){ /*Calcular la pendiente de la linea al hombro (rango z positivo:orilla derecha)*/ pendiente2 = (numero3[1]-numero1[1])/(numero3[2]-numero1[2]); if((pendiente1 >= pendiente2) && (pendiente2 < 0)){ 99 choquev=1; } } if(pendiente1==0){ if(numero1[1] <= numero3[1]){ choquev=1; } } } } } /*Buscar el mismo punto en otra figura de control*/ void mismo_pt_a2(float numero[4], float array[][4], float numsalida[5]){ int n; n=numero[3], numsalida[0]=array[n][0]; numsalida[1]=array[n][1]; numsalida[2]=array[n][2]; numsalida[3]=array[n][3]; numsalida[4]=array[0][0]; } //Buscar el punto con el menor valor de Z void pila_menorz(float array[][4]){ menorz[0]=0; //valor x menorz[1]=0; //valor y menorz[2]=40; //valor z menorz[3]=0; //# del numero en el array numpuntos=array[0][1]; for(i=1; i<=numpuntos; i++){ if(fabs(array[i][2])<=fabs(menorz[2])){ menorz[0]=array[i][0]; menorz[1]=array[i][1]; menorz[2]=array[i][2]; menorz[3]=i; } } } //Buscar el punto con el menor valor de Y void pila_menory(float array[][4]){ menory[0]=0; //valor x menory[1]=300; //valor y menory[2]=0; //valor z menory[3]=0; //# del numero en el array numpuntos=array[0][1]; for(i=1; i<=numpuntos; i++){ if(array[i][1]<=menory[1]){ menory[0]=array[i][0]; menory[1]=array[i][1]; menory[2]=array[i][2]; menory[3]=i; } } } //Prueba de choque contra la base void lim_radiobase(float array[][4],float radiobase, float margen){ 100 float radio; numpuntos=array[0][1]; for(i=1; (i<=numpuntos)&&(choqueb!=1); i++){ radio=sqrt(pow(array[i][0],2)+pow(array[i][2],2)); if(radio <= radiobase+margen){ choqueb=1; } } } //Pruebar choque contra el piso void lim_piso(float array[][4], float margen){ numpuntos=array[0][1]; for(i=1; (i<=numpuntos)&&(choquep!=1); i++){ if(array[i][1] <=(altpiso+margen)){ choquep=1; } } } //Prueba choque contra el hombro void lim_box_yz(float array[][4], float margen, float y1, float y2, float z){ numpuntos=array[0][1]; for(i=1; (i<=numpuntos)&&(choqueh!=1); i++){ if((array[i][1]>=y1-margen) && (array[i][1]<=y2+margen) && (abs(array[i][2])<=z+margen)){ choqueh=1; } } } //Pasar todos puntos del array a coordenadas globales y guardarlo en arrayreal void coord_globales(float array[][4], float arrayreal[][4]){ int s=array[0][0]; //Nivel de jerarquía de los puntos numpuntos=array[0][1]; llenar_matrices_rot(); arrayreal[0][0]=array[0][0]; arrayreal[0][1]=array[0][1]; /*Concatenación de matrices para nivel de jerarquía 5*/ if(s==5){ for(n=1; n<=numpuntos; n++){ mult_mat_vect(rotx5, array[n]); llenar_vtemp(); mult_mat_vect(tras4, vtemp); llenar_vtemp(); mult_mat_vect(roty4, vtemp); llenar_vtemp(); mult_mat_vect(tras3, vtemp); llenar_vtemp(); mult_mat_vect(rotx3, vtemp); llenar_vtemp(); mult_mat_vect(tras2, vtemp); llenar_vtemp(); mult_mat_vect(rotx2, vtemp); llenar_vtemp(); mult_mat_vect(tras1, vtemp); for(j=0;j<4;j++){ arrayreal[n][j]=vectr[j]; } } } /*Concatenación de matrices para nivel de jerarquía 4*/ if(s==4){ for(n=1; n<=numpuntos; n++){ 101 mult_mat_vect(roty4, array[n]); llenar_vtemp(); mult_mat_vect(tras3, vtemp); llenar_vtemp(); mult_mat_vect(rotx3, vtemp); llenar_vtemp(); mult_mat_vect(tras2, vtemp); llenar_vtemp(); mult_mat_vect(rotx2, vtemp); llenar_vtemp(); mult_mat_vect(tras1, vtemp); for(j=0;j<4;j++){ arrayreal[n][j]=vectr[j]; } } } /*Concatenación de matrices para nivel de jerarquía 4*/ if(s==3){ for(n=1; n<=numpuntos; n++){ mult_mat_vect(rotx3, array[n]); llenar_vtemp(); mult_mat_vect(tras2, vtemp); llenar_vtemp(); mult_mat_vect(rotx2, vtemp); llenar_vtemp(); mult_mat_vect(tras1, vtemp); for(j=0;j<4;j++){ arrayreal[n][j]=vectr[j]; } } } } //Preparar prueba de choque contra el hombro void check_boundingH(float array[][4], float arrayreal[][4]){ choqueh=0; coord_globales(array, arrayreal); lim_box_yz(arrayreal, marg, nively1, nively2, zhombro); if(choqueh==1){ printf("\nCHOQUE HOMBRO"); } } //Preparar prueba de choque contra el vortice void check_vortex(float array1[][4], float array2[][4], float arrayreal1[][4], int nivel){ choquev=0; coord_globales(array1,arrayreal1); if(nivel==1){ pila_menorz(arrayreal1); mismo_pt_a2(menorz, array2, menorz2); coord_globales_pta(menorz2, vortexm); dibujarvortex(menorz,vortexm); vortex_test(menorz, vortexm, vortexfijo1, 1); if(choquev==1){ printf("\nCHOQUE Orilla INFERIOR del HOMBRO"); } } if(nivel==2){ pila_menory(arrayreal1); mismo_pt_a2(menory, array2, menory2); coord_globales_pta(menory2, vortexm); dibujarvortex(menory,vortexm); vortex_test(menory, vortexm, vortexfijo2, 2); 102 if(choquev==1){ printf("\nCHOQUE Orilla SUPERIOR del HOMBRO"); } } } //Preparar prueba de choque contra la base void check_base(float array[][4], float arrayreal[][4]){ choqueb=0; coord_globales(array, arrayreal); lim_radiobase(arrayreal, radbase, marg); if(choqueb==1){ printf("\nCHOQUE BASE"); } } //Preparar prueba de choque contra el piso void check_piso(float array[][4], float arrayreal[][4]){ choquep=0; coord_globales(array, arrayreal); lim_piso(arrayreal, marg); if(choquep==1){ printf("\nCHOQUE PISO"); } } //Crear las figuras de control void crear_controles(void){ //Crear circulos, cuadrados y puntos de control float dif=2*M_PI/npuntos; float alpha; alpha=0; circHA[0][0]=5;//Nivel del sistema de coordenadas de cada figura de control circHB[0][0]=5; circWA[0][0]=4; circWB[0][0]=4; circWC[0][0]=4; circBD[0][0]=4; circBC[0][0]=4; cuadBA[0][0]=3; cuadBB[0][0]=3; cuadBC[0][0]=3; circHA[0][1]=npuntos;//Numero de puntos de cada figura de control circHB[0][1]=npuntos; circWA[0][1]=npuntos; circWB[0][1]=npuntos; circWC[0][1]=npuntos; circBD[0][1]=npuntos; circBC[0][1]=npuntos; cuadBA[0][1]=4; cuadBB[0][1]=4; cuadBC[0][1]=4; //CIRULOS for(i=1; i<=npuntos; i++){ circHA[i][0]=(3*sin(alpha));//coord. x del punto 1 (local) circHA[i][1]=5.6;//coord. y del punto 1 (local) circHA[i][2]=(3*cos(alpha));//coord. z del punto 1 (local) circHA[i][3]=1;//indica que es un punto circHB[i][0]=(3*sin(alpha)); circHB[i][1]=8.5; 103 circHB[i][2]=(3*cos(alpha)); circHB[i][3]=1; circWA[i][0]=-5.5; circWA[i][1]=(55+5*sin(alpha)); circWA[i][2]=(5*cos(alpha)); circWA[i][3]=1; circWB[i][0]=0; circWB[i][1]=(55+5*sin(alpha)); circWB[i][2]=(5*cos(alpha)); circWB[i][3]=1; circWC[i][0]=5.5; circWC[i][1]=(55+5*sin(alpha)); circWC[i][2]=(5*cos(alpha)); circWC[i][3]=1; circBD[i][0]=(8.3*sin(alpha)); circBD[i][1]=48; circBD[i][2]=(0.7+8.3*cos(alpha)); circBD[i][3]=1; circBC[i][0]=(9*sin(alpha)); circBC[i][1]=0; circBC[i][2]=(0.7+9*cos(alpha)); circBC[i][3]=1; alpha+=dif; } } //Dibujar las figuras de control void dibujar_circHA(void){ numpuntos=circHA[0][1]; glPushMatrix(); glColor3f(1.0, 0.0, 0.0); glTranslatef(0.0, 0.0, 0.0); glTranslatef(0.0, 42.0, 0.0); glRotatef(angulo1, 0.0, 1.0, 0.0); glRotatef(angulo2, 1.0, 0.0, 0.0); glTranslatef(0.0, 45.0, 0.0); glRotatef(angulo3, 1.0, 0.0, 0.0); glTranslatef(0.0, 10.0, 0.0); glRotatef(angulo4, 0.0, 1.0, 0.0); glTranslatef(0.0, 55.0, 0.0); glRotatef(angulo5, 1.0, 0.0, 0.0); glBegin( GL_LINE_LOOP ); for(i=1; i<=numpuntos; i++){ glVertex3f(circHA[i][0],circHA[i][1],circHA[i][2]); } glEnd(); glPopMatrix(); } void dibujar_circHB(void){ numpuntos=circHB[0][1]; glPushMatrix(); glColor3f(1.0, 0.0, 0.0); glTranslatef(0.0, 0.0, 0.0); glTranslatef(0.0, 42.0, 0.0); glRotatef(angulo1, 0.0, 1.0, 0.0); 104 glRotatef(angulo2, 1.0, 0.0, 0.0); glTranslatef(0.0, 45.0, 0.0); glRotatef(angulo3, 1.0, 0.0, 0.0); glTranslatef(0.0, 10.0, 0.0); glRotatef(angulo4, 0.0, 1.0, 0.0); glTranslatef(0.0, 55.0, 0.0); glRotatef(angulo5, 1.0, 0.0, 0.0); glBegin( GL_LINE_LOOP ); for(i=1; i<=numpuntos; i++){ glVertex3f(circHB[i][0],circHB[i][1],circHB[i][2]); } glEnd(); glPopMatrix(); } void dibujar_circWA(void){ numpuntos=circWA[0][1]; glPushMatrix(); glColor3f(1.0, 0.0, 0.0); glTranslatef(0.0, 0.0, 0.0); glTranslatef(0.0, 42.0, 0.0); glRotatef(angulo1, 0.0, 1.0, 0.0); glRotatef(angulo2, 1.0, 0.0, 0.0); glTranslatef(0.0, 45.0, 0.0); glRotatef(angulo3, 1.0, 0.0, 0.0); glTranslatef(0.0, 10.0, 0.0); glRotatef(angulo4, 0.0, 1.0, 0.0); glBegin(GL_LINE_LOOP); for(i=1; i<=numpuntos; i++){ glVertex3f(circWA[i][0],circWA[i][1],circWA[i][2]); } glEnd(); glPopMatrix(); } void dibujar_circWB(void){ numpuntos=circWB[0][1]; glPushMatrix(); glColor3f(1.0, 0.0, 0.0); glTranslatef(0.0, 0.0, 0.0); glTranslatef(0.0, 42.0, 0.0); glRotatef(angulo1, 0.0, 1.0, 0.0); glRotatef(angulo2, 1.0, 0.0, 0.0); glTranslatef(0.0, 45.0, 0.0); glRotatef(angulo3, 1.0, 0.0, 0.0); glTranslatef(0.0, 10.0, 0.0); glRotatef(angulo4, 0.0, 1.0, 0.0); glBegin(GL_LINE_LOOP); for(i=1; i<=numpuntos; i++){ glVertex3f(circWB[i][0],circWB[i][1],circWB[i][2]); } glEnd(); glPopMatrix(); } void dibujar_circWC(void){ numpuntos=circWC[0][1]; glPushMatrix(); glColor3f(1.0, 0.0, 0.0); 105 glTranslatef(0.0, 0.0, 0.0); glTranslatef(0.0, 42.0, 0.0); glRotatef(angulo1, 0.0, 1.0, 0.0); glRotatef(angulo2, 1.0, 0.0, 0.0); glTranslatef(0.0, 45.0, 0.0); glRotatef(angulo3, 1.0, 0.0, 0.0); glTranslatef(0.0, 10.0, 0.0); glRotatef(angulo4, 0.0, 1.0, 0.0); glBegin(GL_LINE_LOOP); for(i=1; i<=numpuntos; i++){ glVertex3f(circWC[i][0],circWC[i][1],circWC[i][2]); } glEnd(); glPopMatrix(); } void dibujar_circBC(void){ numpuntos=circBC[0][1]; glPushMatrix(); glColor3f(1.0, 0.0, 0.0); glTranslatef(0.0, 0.0, 0.0); glTranslatef(0.0, 42.0, 0.0); glRotatef(angulo1, 0.0, 1.0, 0.0); glRotatef(angulo2, 1.0, 0.0, 0.0); glTranslatef(0.0, 45.0, 0.0); glRotatef(angulo3, 1.0, 0.0, 0.0); glTranslatef(0.0, 10.0, 0.0); glRotatef(angulo4, 0.0, 1.0, 0.0); glBegin(GL_LINE_LOOP); for(i=1; i<=numpuntos; i++){ glVertex3f(circBC[i][0],circBC[i][1],circBC[i][2]); } glEnd(); glPopMatrix(); } void dibujar_circBD(void){ numpuntos=circBD[0][1]; glPushMatrix(); glColor3f(1.0, 0.0, 0.0); glTranslatef(0.0, 0.0, 0.0); glTranslatef(0.0, 42.0, 0.0); glRotatef(angulo1, 0.0, 1.0, 0.0); glRotatef(angulo2, 1.0, 0.0, 0.0); glTranslatef(0.0, 45.0, 0.0); glRotatef(angulo3, 1.0, 0.0, 0.0); glTranslatef(0.0, 10.0, 0.0); glRotatef(angulo4, 0.0, 1.0, 0.0); glBegin(GL_LINE_LOOP); for(i=1; i<=numpuntos; i++){ glVertex3f(circBD[i][0],circBD[i][1],circBD[i][2]); } glEnd(); glPopMatrix(); } void dibujar_cuadBA(void){ numpuntos=cuadBA[0][1]; glPushMatrix(); glColor3f(1.0, 0.0, 0.0); glTranslatef(0.0, 0.0, 0.0); 106 glTranslatef(0.0, 42.0, 0.0); glRotatef(angulo1, 0.0, 1.0, 0.0); glRotatef(angulo2, 1.0, 0.0, 0.0); glTranslatef(0.0, 45.0, 0.0); glRotatef(angulo3, 1.0, 0.0, 0.0); glBegin(GL_LINE_LOOP); for(i=1; i<=numpuntos; i++){ glVertex3f(cuadBA[i][0],cuadBA[i][1],cuadBA[i][2]); } glEnd(); glPopMatrix(); } void dibujar_cuadBB(void){ numpuntos=cuadBB[0][1]; glPushMatrix(); glColor3f(1.0, 0.0, 0.0); glTranslatef(0.0, 0.0, 0.0); glTranslatef(0.0, 42.0, 0.0); glRotatef(angulo1, 0.0, 1.0, 0.0); glRotatef(angulo2, 1.0, 0.0, 0.0); glTranslatef(0.0, 45.0, 0.0); glRotatef(angulo3, 1.0, 0.0, 0.0); glBegin(GL_LINE_LOOP); for(i=1; i<=numpuntos; i++){ glVertex3f(cuadBB[i][0],cuadBB[i][1],cuadBB[i][2]); } glEnd(); glPopMatrix(); } void dibujar_cuadBC(void){ numpuntos=cuadBC[0][1]; glPushMatrix(); glColor3f(1.0, 0.0, 0.0); glTranslatef(0.0, 0.0, 0.0); glTranslatef(0.0, 42.0, 0.0); glRotatef(angulo1, 0.0, 1.0, 0.0); glRotatef(angulo2, 1.0, 0.0, 0.0); glTranslatef(0.0, 45.0, 0.0); glRotatef(angulo3, 1.0, 0.0, 0.0); glBegin(GL_LINE_LOOP); for(i=1; i<=numpuntos; i++){ glVertex3f(cuadBC[i][0],cuadBC[i][1],cuadBC[i][2]); } glEnd(); glPopMatrix(); } //Pasar los puntos de control a coordenadas globales void coord_globales_pts_control(void){ /* coordenas globales de solo los puntos de control * CHB, CHA, CWA, CWC, CWB, CBA, CBB, CBC, CBD, CBE*/ coord_globales_pta(CHB, CHBreal); coord_globales_pta(CHA, CHAreal); coord_globales_pta(CWA, CWAreal); coord_globales_pta(CWB, CWBreal); coord_globales_pta(CWC, CWCreal); coord_globales_pta(CBA, CBAreal); coord_globales_pta(CBB, CBBreal); coord_globales_pta(CBC, CBCreal); coord_globales_pta(CBD, CBDreal); 107 } /*Verificar si se cumplen las condiciones para realizar alguna de las pruebas de colision*/ void probar_colision(float margen){ coord_globales_pts_control(); /*pasar los puntos de control a coord globales*/ float radchb; float acwaz; float acwbz; float acwcz; float acbez; float acbdz; float acbcz; float acbbz; float acbaz; float achbz; float achaz; choque=choquep=choqueb=choqueh=choquev=0; cuidado=0; /*distancia del punto de control CHB al eje Y*/ radchb=sqrt(pow(CHBreal[2],2)+pow(CHBreal[0],2)); /*Valor absoluto de la coordena Z de los putnos de control*/ acwaz=fabs(CWAreal[2]); acwbz=fabs(CWBreal[2]); acwcz=fabs(CWCreal[2]); acbdz=fabs(CBDreal[2]); acbcz=fabs(CBCreal[2]); acbbz=fabs(CBBreal[2]); acbaz=fabs(CBAreal[2]); achbz=fabs(CHBreal[2]); achaz=fabs(CHAreal[2]); //PISO /*Verificar algun punto de control entro al area de precaución del piso*/ if(CBAreal[1] <= (6+margen)){ dibujar_cuadBA(); check_piso(cuadBA, cuadBAreal); cuidado=1; } if((CBBreal[1] <= (11.5+margen)) && (CBBreal[1] <= CBCreal[1])){ dibujar_cuadBB(); check_piso(cuadBB, cuadBBreal); cuidado=1; } if((CBCreal[1] <= (11.5+margen)) && (CBCreal[1] < CBBreal[1])){ dibujar_cuadBC(); check_piso(cuadBC, cuadBCreal); cuidado=1; } if((CWAreal[1] <= (5.5+margen)) && (CWAreal[1] <= CWCreal[1])){ dibujar_circWA(); check_piso(circWA, circWAreal); cuidado=1; } if((CWCreal[1] <= (5.5+margen)) && (CWCreal[1] < CWAreal[1])){ dibujar_circWC(); check_piso(circWC, circWCreal); cuidado=1; } if(CHBreal[1] <= (3.5+margen)){ dibujar_circHB(); check_piso(circHB, circHBreal); 108 cuidado=1; } //BASE /*Verificar algun punto de control entro al area de precaución de la base*/ if((CHBreal[1]<25.5) && (radchb<=15.5+margen)){ dibujar_circHB(); check_base(circHB, circHBreal); cuidado=1; } if((CWAreal[1]<23.5) && (acwaz<=17.5+margen)){ dibujar_circWA(); check_base(circWA, circWAreal); cuidado=1; } if((CWBreal[1]<23.5) && (acwbz<=17.5+margen)){ dibujar_circWB(); check_base(circWB, circWBreal); cuidado=1; } if((CWCreal[1]<23.5) && (acwcz<=17.5+margen)){ dibujar_circWC(); check_base(circWC, circWCreal); cuidado=1; } if((CBDreal[1]<28.5) && (acbdz<=21+margen)){ dibujar_circBD(); check_base(circBD, circBDreal); cuidado=1; } if((CBAreal[1]<28.5) && (acbaz<=25+margen)){ dibujar_cuadBA(); cuidado=1; choqueh=0; coord_globales(cuadBA, cuadBAreal); lim_box_yz(cuadBAreal, marg, altpiso, nively1, radbase); if(choqueh==1){ printf("CHOQUE BASE\n"); } } if((CBBreal[1]<28.5) && (acbbz<=25+margen)){ dibujar_cuadBB(); cuidado=1; choqueh=0; coord_globales(cuadBB, cuadBBreal); lim_box_yz(cuadBBreal, marg, altpiso, nively1, radbase); if(choqueh==1){ printf("CHOQUE BASE\n"); } } //HOMBRO //VORTEX nivel 1 (hombro) /*Verificar si se cumplen las condiciones para que este cerca una colision en las orillas inferiores*/ if((CBDreal[1]<28.5) && (CBCreal[1]>18.0) && (acbdz<=40+margen)){ dibujar_circBD(); check_vortex(circBD, circBC, circBDreal, 1); cuidado=1; } //VORTEX nivel 2 /*Verificar si se cumplen las condiciones para que se de una posible colision en las orillas superiores*/ if((CBDreal[1]<=65) && (CBCreal[1]>54.5) && (acbcz<=40.0+margen)){ dibujar_circBD(); 109 check_vortex(circBD, circBC, circBDreal, 2); cuidado=1; } if((CBCreal[1]<=65) && (CBDreal[1]>54.5) && (acbdz<=40+margen)){ dibujar_circBC(); check_vortex(circBC, circBD, circBCreal, 2); cuidado=1; } //ALGUN CHOQUE? /*Si se dio algun choque se llena la variable de hubochoque y la de choque*/ if((choquep==1) || (choqueb==1) || (choqueh==1) || (choquev==1)){ choque=1; hubochoque=1; } } //Verificar angulo máximo de las articulaciónes void check_maximo(void){ int numart; angmax=0; if(angulo1<(-160)){ numart=1; angmax=1; angulo1=-160; }else if(angulo1>160){ numart=1; angmax=1; angulo1=160; } if(angulo2<(-137.5)){ numart=2; angmax=1; angulo2=-137.5; }else if(angulo2>137.5){ numart=2; angmax=1; angulo2=137.5; } if(angulo3<(-142.5)){ numart=3; angmax=1; angulo3=-142.5; }else if(angulo3>142.5){ numart=3; angmax=1; angulo3=142.5; } if(angulo4<(-270)){ numart=4; angmax=1; angulo4=-270; }else if(angulo4>270){ numart=4; angmax=1; angulo4=270; } if(angulo5<(-120)){ numart=5; angmax=1; angulo5=-120; }else if(angulo5>105){ numart=5; 110 angmax=1; angulo5=105; } if(angulo6<(-270)){ numart=6; angmax=1; angulo6=-270; }else if(angulo6>270){ numart=6; angmax=1; angulo6=270; } if(angmax==1){ printf("\nArticulación %d alcanzo su máxima amplitud de giro",numart); } } //Limites geométricos del modelo /*Herramienta para el desarrollador o modelador*/ void dibujar_limites(void){ //Verticales glPushMatrix(); glColor3f(1.0, 0.0, 0.0); glBegin(GL_LINES); glVertex3f(-26.8, 0, 0); glVertex3f(-26.8, 180, 0); glVertex3f(-12.7, 0, 0); glVertex3f(-12.7, 180, 0); glVertex3f(15, 0, 0); glVertex3f(15, 180, 0); glVertex3f(11.9, 0, 0); glVertex3f(11.9, 180, 0); glVertex3f(0, 0, 0); glVertex3f(0, 200, 0); glVertex3f(-5.4, 132, 0); glVertex3f(-5.4, 150, 0); glVertex3f(5.4, 132, 0); glVertex3f(5.4, 150, 0); glVertex3f(0, 0, 13); glVertex3f(0, 180, 13); glVertex3f(0, 0, -13); glVertex3f(0, 180, -13); glVertex3f(0, 54, 11); glVertex3f(0, 180, 11); glVertex3f(0, 54, -11); glVertex3f(0, 180, -11); glEnd(); glPopMatrix(); //HORIZONTALES glPushMatrix(); glColor3f(1.0, 0.0, 0.0); glBegin(GL_LINES); glVertex3f(-20, 42.0, 0); glVertex3f(20, 42.0, 0); glVertex3f(-20, 54.5, 0); glVertex3f(20, 54.5, 0); glVertex3f(-20, 71.7, 0); glVertex3f(20, 71.7, 0); glVertex3f(-20, 87, 0); glVertex3f(20, 87, 0); glVertex3f(-20, 96.7, 0); glVertex3f(20, 96.7, 0); glVertex3f(-20, 111, 0); glVertex3f(20, 111, 0); 111 glVertex3f(-20, 152, 0); glVertex3f(20, 152, 0); glEnd(); glPopMatrix(); } //SEÑAL DE PRECAUCION /*Esferas de colores que indican el riesgo del movimiento actual*/ void semaforo(void){ GLUquadricObj *quadObj1; float semaf_x; float semaf_y; float color[3]; semaf_x=140*ventanaw/ventanah; quadObj1 = gluNewQuadric(); if(choque==1){ color[0]=1.0; color[1]=0.0; color[2]=0.0; semaf_y=150; }else if(cuidado==1){ color[0]=1.0; color[1]=0.9; color[2]=0.0; semaf_y=140; }else{ color[0]=0.0; color[1]=1.0; color[2]=0.5; semaf_y=130; } glPushMatrix(); glColor3fv(color); glRotatef(-1*angulo_yr, 0.0, 1.0, 0.0); glTranslatef(-1*ancho_x, -1*altura_y, 0.0); glRotatef(-1*angulo_xr, 1.0, 0.0, 0.0); glTranslatef(semaf_x*zoom_z/160, semaf_y*zoom_z/160, 600.0); gluSphere(quadObj1, 7*zoom_z/160, 30, 30); glPopMatrix(); } //----------------------DIBUJAR ESCENA---------------------------------------gboolean dibujar(void){ /*Buscar area OpenGL*/ GdkGLContext *glcontext = gtk_widget_get_gl_context (glarea1); GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (glarea1); /*** OpenGL INICIO ***/ if (!gdk_gl_drawable_make_current(gldrawable, glcontext)){ printf("\nError al buscar area OpenGL. Se crea la ventana nuevamente"); /* Error al buscar el area GL. Se crea la ventana 2 nuevamente y se vuelve a intentar*/ ventana2 = create_window2 (); gtk_widget_show (ventana2); printf("\nCreando area de nuevo"); glcontext = gtk_widget_get_gl_context (glarea1); gldrawable = gtk_widget_get_gl_drawable (glarea1); /* Si el error se da nuevamente, se retorna el valor FALSE*/ if (!gdk_gl_drawable_make_current(gldrawable, glcontext)){ printf("\nError al buscar area OpenGL. Segundo Intento"); return FALSE; } } /*Limpiar los buffers de profundidad y color*/ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 112 glLoadIdentity(); if(checkmax==1){ /*limita los angulos de giro a sus amplitudes máximas*/ check_maximo(); } /*Dibujar piso en forma de rejilla*/ if(grid==1){ // GRID glBegin(GL_LINES); glColor3f(0.0, 0.1, 1.0); glNormal3f(0.0, 1.0, 0.0); int i; for(i=-300;i<=300;i+=30) { glVertex3f(i,altpiso,-300); glVertex3f(i,altpiso,300); glVertex3f(300,altpiso,i); glVertex3f(-300,altpiso,i); } glEnd(); }else { //PISO /*Dibujar piso en forma poligono solido*/ glBegin(GL_QUADS); glColor3f(0.9, 0.9, 0.9); glNormal3f(0.0, 1.0, 0.0); glVertex3f(-500.0f, altpiso, -500.0f); glVertex3f(-500.0f, altpiso, 500.0f); glVertex3f(500.0f, altpiso, 500.0f); glVertex3f(500.0f, altpiso, -500.0f); glEnd(); } /*Base externa sobre la cual se monta el brazo (opcional)*/ GLUquadricObj *quadObj1; quadObj1 = gluNewQuadric(); glPushMatrix(); glColor3f(0.1, 0.1, 1.0); glRotatef(90, 1.0, 0.0, 0.0); gluCylinder(quadObj1, 12, 12, -1*altpiso, 30, 30); glPopMatrix(); /*Posicionar y dibujar los componentes del brazo en la escena*/ glColor3f(0.7, 0.8, 0.7); //BASE glPushMatrix(); glTranslatef(0.0, 0.0, 0.0); glCallList(BASE); glPopMatrix(); //HOMBRO glPushMatrix(); glTranslatef(0.0, 0.0, 0.0); glTranslatef(0.0, 42.0, 0.0); glRotatef(angulo1, 0.0, 1.0, 0.0); glCallList(HOMBRO); glTranslatef(-13.0, 0.0, 0.0); //BRAZO1 glPushMatrix(); glRotatef(angulo2, 1.0, 0.0, 0.0); glCallList(BRAZO1); glTranslatef(13.0, 45.0, 0.0); 113 //CODO glPushMatrix(); glRotatef(angulo3, 1.0, 0.0, 0.0); glCallList(CODO); glTranslatef(0.0, 10.0, 0.0); //BRAZO2 glPushMatrix(); glRotatef(angulo4, 0.0, 1.0, 0.0); glCallList(BRAZO2); glTranslatef(0.0, 55.0, 0.0); //MUNIECA glPushMatrix(); glRotatef(angulo5, 1.0, 0.0, 0.0); glTranslatef(5.5, 0.0, 0.0); glCallList(MUNIECA); glTranslatef(-5.5, 0.0, 0.0); //MANO glPushMatrix(); glRotatef(angulo6, 0.0, 1.0, 0.0); glCallList(MANO); glPopMatrix(); glPopMatrix(); glPopMatrix(); glPopMatrix(); glPopMatrix(); glPopMatrix(); if(lim==1){ dibujar_limites(); } /*Realizar las pruebas de colisiones necesarias*/ probar_colision(marg); /*Actualizar el semaforo de precaución*/ semaforo(); /*Exigir la ejecucuión de todos los comandos OpenGL llamados hasta ahora*/ glFlush (); /*Intercambiar los frame buffers. Tecnica de double buffering*/ gdk_gl_drawable_swap_buffers (gldrawable); return TRUE; } /*Funciones que crean las listas de despliegue de los componentes principales del robot*/ //BASE del Robot void crear_base(int slices, int stacks){ GLUquadricObj *quadObj1; quadObj1 = gluNewQuadric(); glNewList(BASE, GL_COMPILE); glPushMatrix(); glRotatef(-90, 1.0, 0.0, 0.0); gluCylinder(quadObj1, 12, 12, 30, slices, stacks); glPopMatrix(); glEndList(); } //HOMBRO del Robot void crear_hombro(){ GLUquadricObj *quadObj1; 114 quadObj1 = gluNewQuadric(); glNewList(HOMBRO, GL_COMPILE); glPushMatrix(); glRotatef(-90, 1.0, 0.0, 0.0); glRotatef(45, 0.0, 0.0, 1.0); glTranslatef(0.0, 0.0, -13.5); gluCylinder(quadObj1, 18.4, 18.4, 26, 4, 16); gluDisk(quadObj1, 0, 18.4, 4, 16); glTranslatef(0.0, 0.0, 26); gluDisk(quadObj1, 0, 18.4, 4, 16); glPopMatrix(); glPushMatrix(); glRotatef(90, 0.0, 1.0, 0.0); glTranslatef(0.0, -0.5, 13.0); glPushMatrix(); glRotatef(45, 0.0, 0.0, 1.0); gluCylinder(quadObj1, 18.4, 7.0, 2, 4, 16); glTranslatef(0.0, 0.0, 2.0); gluDisk(quadObj1, 0, 7, 4, 16); glPopMatrix(); glPopMatrix(); glEndList(); } //BRAZO del Robot void crear_brazo1(int slices, int stacks){ GLUquadricObj *quadObj1; quadObj1 = gluNewQuadric(); glNewList(BRAZO1, GL_COMPILE); glPushMatrix(); glRotatef(-90, 0.0, 1.0, 0.0);//ROTAR gluCylinder(quadObj1, 12, 13, 7, slices, stacks); glTranslatef(0.0, 0.0, 7.0); gluCylinder(quadObj1, 13, 13, 7, slices, stacks); glTranslatef(0.0, 0.0, 7.0); gluPartialDisk(quadObj1, 0, 13, slices, stacks, 90, 180); glTranslatef(0.0, 0.0, -3.5); glBegin(GL_QUADS); glNormal3f(0.0, 0.0, 1.0); //Izquierda glVertex3f(-11.0, 45.0, 3.5); glVertex3f(-13.0, 0.0, 3.5); glVertex3f(13.0, 0.0, 3.5); glVertex3f(11.0, 45.0, 3.5); glNormal3f(0.0, 0.0, -1.0); //Derecha glVertex3f(-11.0, 45.0, -3.5); glVertex3f(-13.0, 0.0, -3.5); glVertex3f(13.0, 0.0, -3.5); glVertex3f(11.0, 45.0, -3.5); glNormal3f(0.999, 0.044, 0.0); //Frontal glVertex3f(11.0, 45.0, 3.5); glVertex3f(13.0, 0.0, 3.5); glVertex3f(13.0, 0.0, -3.5); glVertex3f(11.0, 45.0, -3.5); glNormal3f(-0.999, 0.044, 0.0); //Trasero glVertex3f(-11.0, 45.0, 3.5); 115 glVertex3f(-13.0, 0.0, 3.5); glVertex3f(-13.0, 0.0, -3.5); glVertex3f(-11.0, 45.0, -3.5); glEnd(); glTranslatef(0.0, 0.0, -3.5); glBegin(GL_QUADS); glColor3f(0.1, 0.1, 1.0); glNormal3f(0.0, 0.0, -1.0); //Derecha COBERTO glVertex3f(-8.0, 40.0, -3.5); glVertex3f(8.0, 40.0, -3.5); glVertex3f(8.0, 7.0, -3.5); glVertex3f(-8.0, 7.0, -3.5); glNormal3f(1.0, 0.0, 0.0); //Frontal COBERTOR glVertex3f(8.0, 40.0, 3.5); glVertex3f(8.0, 7.0, 3.5); glVertex3f(8.0, 7.0, -3.5); glVertex3f(8.0, 40.0, -3.5); glNormal3f(-1.0, 0.0, 0.0); //Trasero COBERTOR glVertex3f(-8.0, 40.0, 3.5); glVertex3f(-8.0, 7.0, 3.5); glVertex3f(-8.0, 7.0, -3.5); glVertex3f(-8.0, 40.0, -3.5); glEnd(); glColor3f(0.7, 0.8, 0.7); glTranslatef(0.0, 45.0, -7.0); gluCylinder(quadObj1, 11, 11, 14, slices, stacks); gluDisk(quadObj1, 0, 11, slices, stacks); glTranslatef(0.0, 0.0, 14.0); gluPartialDisk(quadObj1, 0, 11, slices, stacks, 270, 180); glPopMatrix(); glEndList(); } //CODO del Robot void crear_codo(void){ GLUquadricObj *quadObj1; quadObj1 = gluNewQuadric(); glNewList(CODO, GL_COMPILE); glPushMatrix(); glPushMatrix(); glRotatef(-90, 0.0, 1.0, 0.0); glTranslatef(0.0, 0.0, 10.1); gluCylinder(quadObj1, 9.8, 9.8, 3, 12, 16); glPopMatrix(); glRotatef(-90, 1.0, 0.0, 0.0); glRotatef(45, 0.0, 0.0, 1.0); glTranslatef(0.45, -0.45, -10.0); gluCylinder(quadObj1, 15.556, 15.556, 20, 4, 16); glTranslatef(0.0, 0.0, -5.0); gluCylinder(quadObj1, 4.0, 15.556, 5, 4, 16); glPushMatrix(); glRotatef(180, 1.0, 0.0, 0.0); gluDisk(quadObj1, 0, 4.0, 4, 16); glPopMatrix(); glTranslatef(0.0, 0.0, 25.0); gluDisk(quadObj1, 0, 15.556, 4, 16); glPopMatrix(); glEndList(); } 116 //ANTERBRAZO del Robot void crear_brazo2(int slices, int stacks){ GLUquadricObj *quadObj1; quadObj1 = gluNewQuadric(); glNewList(BRAZO2, GL_COMPILE); glPushMatrix(); glPushMatrix(); glRotatef(-90, 1.0, 0.0, 0.0); gluCylinder(quadObj1, 9.0, 9.0, 14, slices, stacks); glTranslatef(0.0, 0.0, 14.0); gluDisk(quadObj1, 0, 9.0, slices, stacks); glPopMatrix(); glTranslatef(0.0, 0.0, 0.7); glPushMatrix(); glRotatef(-90, 1.0, 0.0, 0.0); glRotatef(45, 0.0, 0.0, 1.0); gluCylinder(quadObj1, 9.0, 8.3, 48, slices, stacks); glTranslatef(0.0, 0.0, 48); gluDisk(quadObj1, 0, 8.3, slices, stacks); gluCylinder(quadObj1, 8.3, 4.0, 7, 4, 16); glTranslatef(0.0, 0.0, 7.0); gluDisk(quadObj1, 0, 4.0, 4, stacks); glPopMatrix(); glPopMatrix(); glEndList(); } //MUÑECA del Robot void crear_munieca(int slices, int stacks){ GLUquadricObj *quadObj1; quadObj1 = gluNewQuadric(); glNewList(MUNIECA, GL_COMPILE); glPushMatrix(); glRotatef(-90, 0.0, 1.0, 0.0); gluCylinder(quadObj1, 5, 5, 11, slices, stacks); glPushMatrix(); glRotatef(180, 1.0, 0.0, 0.0); gluDisk(quadObj1, 0, 5, slices, stacks); glPopMatrix(); glTranslatef(0, 0, 11); gluDisk(quadObj1, 0, 5, slices, stacks); glPopMatrix(); glEndList(); } //MANO del Robot void crear_mano(int slices, int stacks){ GLUquadricObj *quadObj1; quadObj1 = gluNewQuadric(); glNewList(MANO, GL_COMPILE); glPushMatrix(); glColor3f(0.1, 0.1, 1.0); glRotatef(-90, 1.0, 0.0, 0.0); gluCylinder(quadObj1, 3, 3, 8.5, slices, stacks); glTranslatef(0, 0, 8); gluDisk(quadObj1, 0, 3, slices, stacks); glPopMatrix(); glEndList(); 117 } /*************************************************************************************************************/ interface.c: Interfaz gráfica de usuario /*************************************************************************** * SimQNK * Simulador virtual del brazo robot Stäubli RX90 L * interface.c: interfaz gráfica de usuario * Copyright(C) 2006 David Cuenca * Email: dcuenc@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #ifdef ENABLE_NLS # include <libintl.h> # undef _ # define _(String) dgettext (PACKAGE, String) # define Q_(String) g_strip_context ((String), gettext (String)) # ifdef gettext_noop # define N_(String) gettext_noop (String) # else # define N_(String) (String) # endif #else # define textdomain(String) (String) # define gettext(String) (String) # define dgettext(Domain,Message) (Message) # define dcgettext(Domain,Message,Type) (Message) # define bindtextdomain(Domain,Directory) (Domain) # define _(String) (String) # define Q_(String) g_strip_context ((String), (String)) # define N_(String) (String) #endif #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <string.h> #include <stdio.h> #include <gdk/gdkkeysyms.h> 118 #include <gdk/gdk.h> #include <gdk/gdkgl.h> #include <gdk/gdkkeysyms.h> #include <gtk/gtk.h> #include <gtk/gtkgl.h> #include "callbacks.h" #include "interface.h" #define GLADE_HOOKUP_OBJECT(component,widget,name) \ g_object_set_data_full (G_OBJECT (component), name, \ gtk_widget_ref (widget), (GDestroyNotify) gtk_widget_unref) #define GLADE_HOOKUP_OBJECT_NO_REF(component,widget,name) \ g_object_set_data (G_OBJECT (component), name, widget) /*Función de creación de la ventana 1*/ GtkWidget* create_window1 (void) { /*Objetos de la ventana 1*/ GtkWidget *window1; GtkWidget *vbox1; GtkWidget *hbox1; GtkWidget *frame1; GtkWidget *scrolledwindow1; GtkWidget *label1; GtkWidget *hbox2; GtkWidget *frame3; GtkWidget *alignment3; GtkWidget *vbox2; GtkWidget *label6; GtkObject *spinbutton1_adj; GtkWidget *label7; GtkObject *spinbutton2_adj; GtkWidget *label8; GtkObject *spinbutton3_adj; GtkWidget *bdrive; GtkWidget *label3; GtkWidget *frame4; GtkWidget *alignment4; GtkWidget *vbox3; GtkWidget *bsimular; GtkWidget *alignment6; GtkWidget *hbox3; GtkWidget *image1; GtkWidget *label9; GtkWidget *bejecutar; GtkWidget *alignment7; GtkWidget *hbox4; GtkWidget *image2; GtkWidget *label10; GtkWidget *label4; GtkWidget *frame5; GtkWidget *alignment5; GtkWidget *vbox4; GtkWidget *brutina1; GtkWidget *alignment15; GtkWidget *hbox11; GtkWidget *image8; GtkWidget *label18; GtkWidget *brutina2; GtkWidget *alignment16; GtkWidget *hbox12; 119 GtkWidget *image9; GtkWidget *label19; GtkWidget *brutina3; GtkWidget *alignment17; GtkWidget *hbox13; GtkWidget *image10; GtkWidget *label20; GtkWidget *label5; /*Creación y agrupamiento de los objetos de la ventana*/ window1 = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_widget_set_name (window1, "window1"); gtk_container_set_border_width (GTK_CONTAINER (window1), 3); gtk_window_set_title (GTK_WINDOW (window1), _("Controles del Simulador (Controles Rutinas)")); vbox1 = gtk_vbox_new (FALSE, 0); gtk_widget_set_name (vbox1, "vbox1"); gtk_widget_show (vbox1); gtk_container_add (GTK_CONTAINER (window1), vbox1); hbox1 = gtk_hbox_new (FALSE, 0); gtk_widget_set_name (hbox1, "hbox1"); gtk_widget_show (hbox1); gtk_box_pack_start (GTK_BOX (vbox1), hbox1, TRUE, TRUE, 0); frame1 = gtk_frame_new (NULL); gtk_widget_set_name (frame1, "frame1"); gtk_widget_show (frame1); gtk_box_pack_start (GTK_BOX (hbox1), frame1, TRUE, TRUE, 0); gtk_container_set_border_width (GTK_CONTAINER (frame1), 3); gtk_frame_set_shadow_type (GTK_FRAME (frame1), GTK_SHADOW_NONE); scrolledwindow1 = gtk_scrolled_window_new (NULL, NULL); gtk_widget_set_name (scrolledwindow1, "scrolledwindow1"); gtk_widget_show (scrolledwindow1); gtk_container_add (GTK_CONTAINER (frame1), scrolledwindow1); textview1 = gtk_text_view_new (); gtk_widget_set_name (textview1, "textview1"); gtk_widget_show (textview1); gtk_container_add (GTK_CONTAINER (scrolledwindow1), textview1); gtk_widget_set_size_request (textview1, 250, 100); label1 = gtk_label_new (_("<b>MENSAJES</b>")); gtk_widget_set_name (label1, "label1"); gtk_widget_show (label1); gtk_frame_set_label_widget (GTK_FRAME (frame1), label1); gtk_label_set_use_markup (GTK_LABEL (label1), TRUE); hbox2 = gtk_hbox_new (FALSE, 0); gtk_widget_set_name (hbox2, "hbox2"); gtk_widget_show (hbox2); gtk_box_pack_start (GTK_BOX (vbox1), hbox2, TRUE, TRUE, 0); frame3 = gtk_frame_new (NULL); gtk_widget_set_name (frame3, "frame3"); gtk_widget_show (frame3); gtk_box_pack_start (GTK_BOX (hbox2), frame3, TRUE, TRUE, 0); gtk_container_set_border_width (GTK_CONTAINER (frame3), 3); gtk_frame_set_shadow_type (GTK_FRAME (frame3), GTK_SHADOW_OUT); alignment3 = gtk_alignment_new (0.5, 0.5, 1, 1); gtk_widget_set_name (alignment3, "alignment3"); gtk_widget_show (alignment3); 120 gtk_container_add (GTK_CONTAINER (frame3), alignment3); gtk_alignment_set_padding (GTK_ALIGNMENT (alignment3), 0, 0, 12, 0); vbox2 = gtk_vbox_new (FALSE, 0); gtk_widget_set_name (vbox2, "vbox2"); gtk_widget_show (vbox2); gtk_container_add (GTK_CONTAINER (alignment3), vbox2); label6 = gtk_label_new (_("ARTICULACION")); gtk_widget_set_name (label6, "label6"); gtk_widget_show (label6); gtk_box_pack_start (GTK_BOX (vbox2), label6, TRUE, TRUE, 0); gtk_label_set_justify (GTK_LABEL (label6), GTK_JUSTIFY_CENTER); spinbutton1_adj = gtk_adjustment_new (1, 1, 6, 1, 10, 10); spinbutton1 = gtk_spin_button_new (GTK_ADJUSTMENT (spinbutton1_adj), 1, 0); gtk_widget_set_name (spinbutton1, "spinbutton1"); gtk_widget_show (spinbutton1); gtk_box_pack_start (GTK_BOX (vbox2), spinbutton1, TRUE, TRUE, 0); label7 = gtk_label_new (_("ANGULO")); gtk_widget_set_name (label7, "label7"); gtk_widget_show (label7); gtk_box_pack_start (GTK_BOX (vbox2), label7, TRUE, TRUE, 0); spinbutton2_adj = gtk_adjustment_new (0, -360, 360, 1, 10, 10); spinbutton2 = gtk_spin_button_new (GTK_ADJUSTMENT (spinbutton2_adj), 1, 0); gtk_widget_set_name (spinbutton2, "spinbutton2"); gtk_widget_show (spinbutton2); gtk_box_pack_start (GTK_BOX (vbox2), spinbutton2, TRUE, TRUE, 0); label8 = gtk_label_new (_("VELOCIDAD (%)")); gtk_widget_set_name (label8, "label8"); gtk_widget_show (label8); gtk_box_pack_start (GTK_BOX (vbox2), label8, TRUE, TRUE, 0); spinbutton3_adj = gtk_adjustment_new (100, 1, 100, 1, 10, 10); spinbutton3 = gtk_spin_button_new (GTK_ADJUSTMENT (spinbutton3_adj), 1, 0); gtk_widget_set_name (spinbutton3, "spinbutton3"); gtk_widget_show (spinbutton3); gtk_box_pack_start (GTK_BOX (vbox2), spinbutton3, TRUE, TRUE, 0); bdrive = gtk_button_new_with_mnemonic (_("DRIVE")); gtk_widget_set_name (bdrive, "bdrive"); gtk_widget_show (bdrive); gtk_box_pack_start (GTK_BOX (vbox2), bdrive, TRUE, TRUE, 0); label3 = gtk_label_new (_("<b>Comando Drive</b>")); gtk_widget_set_name (label3, "label3"); gtk_widget_show (label3); gtk_frame_set_label_widget (GTK_FRAME (frame3), label3); gtk_label_set_use_markup (GTK_LABEL (label3), TRUE); frame4 = gtk_frame_new (NULL); gtk_widget_set_name (frame4, "frame4"); gtk_widget_show (frame4); gtk_box_pack_start (GTK_BOX (hbox2), frame4, TRUE, TRUE, 0); gtk_container_set_border_width (GTK_CONTAINER (frame4), 3); gtk_frame_set_shadow_type (GTK_FRAME (frame4), GTK_SHADOW_OUT); alignment4 = gtk_alignment_new (0.5, 0.5, 1, 1); gtk_widget_set_name (alignment4, "alignment4"); gtk_widget_show (alignment4); gtk_container_add (GTK_CONTAINER (frame4), alignment4); 121 gtk_alignment_set_padding (GTK_ALIGNMENT (alignment4), 0, 0, 12, 0); vbox3 = gtk_vbox_new (FALSE, 0); gtk_widget_set_name (vbox3, "vbox3"); gtk_widget_show (vbox3); gtk_container_add (GTK_CONTAINER (alignment4), vbox3); entrycomando = gtk_entry_new (); gtk_widget_set_name (entrycomando, "entrycomando"); gtk_widget_show (entrycomando); gtk_box_pack_start (GTK_BOX (vbox3), entrycomando, TRUE, TRUE, 48); gtk_entry_set_text (GTK_ENTRY (entrycomando), _("Comando")); gtk_entry_set_width_chars (GTK_ENTRY (entrycomando), 0); bsimular = gtk_button_new (); gtk_widget_set_name (bsimular, "bsimular"); gtk_widget_show (bsimular); gtk_box_pack_start (GTK_BOX (vbox3), bsimular, FALSE, FALSE, 0); alignment6 = gtk_alignment_new (0.5, 0.5, 0, 0); gtk_widget_set_name (alignment6, "alignment6"); gtk_widget_show (alignment6); gtk_container_add (GTK_CONTAINER (bsimular), alignment6); hbox3 = gtk_hbox_new (FALSE, 2); gtk_widget_set_name (hbox3, "hbox3"); gtk_widget_show (hbox3); gtk_container_add (GTK_CONTAINER (alignment6), hbox3); image1 = gtk_image_new_from_stock ("gtk-network", GTK_ICON_SIZE_BUTTON); gtk_widget_set_name (image1, "image1"); gtk_widget_show (image1); gtk_box_pack_start (GTK_BOX (hbox3), image1, FALSE, FALSE, 0); label9 = gtk_label_new_with_mnemonic (_("SIMULAR")); gtk_widget_set_name (label9, "label9"); gtk_widget_show (label9); gtk_box_pack_start (GTK_BOX (hbox3), label9, FALSE, FALSE, 0); bejecutar = gtk_button_new (); gtk_widget_set_name (bejecutar, "bejecutar"); gtk_widget_show (bejecutar); gtk_box_pack_start (GTK_BOX (vbox3), bejecutar, TRUE, FALSE, 0); alignment7 = gtk_alignment_new (0.5, 0.5, 0, 0); gtk_widget_set_name (alignment7, "alignment7"); gtk_widget_show (alignment7); gtk_container_add (GTK_CONTAINER (bejecutar), alignment7); hbox4 = gtk_hbox_new (FALSE, 2); gtk_widget_set_name (hbox4, "hbox4"); gtk_widget_show (hbox4); gtk_container_add (GTK_CONTAINER (alignment7), hbox4); image2 = gtk_image_new_from_stock ("gtk-yes", GTK_ICON_SIZE_BUTTON); gtk_widget_set_name (image2, "image2"); gtk_widget_show (image2); gtk_box_pack_start (GTK_BOX (hbox4), image2, FALSE, FALSE, 0); label10 = gtk_label_new_with_mnemonic (_("EJECUTAR")); gtk_widget_set_name (label10, "label10"); gtk_widget_show (label10); gtk_box_pack_start (GTK_BOX (hbox4), label10, FALSE, FALSE, 0); 122 label4 = gtk_label_new (_("<b>Linea de Comandos</b>")); gtk_widget_set_name (label4, "label4"); gtk_widget_show (label4); gtk_frame_set_label_widget (GTK_FRAME (frame4), label4); gtk_label_set_use_markup (GTK_LABEL (label4), TRUE); frame5 = gtk_frame_new (NULL); gtk_widget_set_name (frame5, "frame5"); gtk_widget_show (frame5); gtk_box_pack_start (GTK_BOX (hbox2), frame5, TRUE, TRUE, 0); gtk_container_set_border_width (GTK_CONTAINER (frame5), 3); gtk_frame_set_shadow_type (GTK_FRAME (frame5), GTK_SHADOW_OUT); alignment5 = gtk_alignment_new (0.5, 0.5, 1, 1); gtk_widget_set_name (alignment5, "alignment5"); gtk_widget_show (alignment5); gtk_container_add (GTK_CONTAINER (frame5), alignment5); gtk_alignment_set_padding (GTK_ALIGNMENT (alignment5), 0, 0, 12, 0); vbox4 = gtk_vbox_new (FALSE, 0); gtk_widget_set_name (vbox4, "vbox4"); gtk_widget_show (vbox4); gtk_container_add (GTK_CONTAINER (alignment5), vbox4); entryrutina = gtk_entry_new (); gtk_widget_set_name (entryrutina, "entryrutina"); gtk_entry_set_max_length(GTK_ENTRY (entryrutina), 20); gtk_widget_show (entryrutina); gtk_box_pack_start (GTK_BOX (vbox4), entryrutina, TRUE, FALSE, 0); gtk_entry_set_text (GTK_ENTRY (entryrutina), _("Nombre Rutina")); brutina1 = gtk_button_new (); gtk_widget_set_name (brutina1, "brutina1"); gtk_widget_show (brutina1); gtk_box_pack_start (GTK_BOX (vbox4), brutina1, TRUE, FALSE, 0); alignment15 = gtk_alignment_new (0.5, 0.5, 0, 0); gtk_widget_set_name (alignment15, "alignment15"); gtk_widget_show (alignment15); gtk_container_add (GTK_CONTAINER (brutina1), alignment15); hbox11 = gtk_hbox_new (FALSE, 2); gtk_widget_set_name (hbox11, "hbox11"); gtk_widget_show (hbox11); gtk_container_add (GTK_CONTAINER (alignment15), hbox11); image8 = gtk_image_new_from_stock ("gtk-save-as", GTK_ICON_SIZE_BUTTON); gtk_widget_set_name (image8, "image8"); gtk_widget_show (image8); gtk_box_pack_start (GTK_BOX (hbox11), image8, FALSE, FALSE, 0); label18 = gtk_label_new_with_mnemonic (_("Crear Rutina")); gtk_widget_set_name (label18, "label18"); gtk_widget_show (label18); gtk_box_pack_start (GTK_BOX (hbox11), label18, FALSE, FALSE, 0); brutina2 = gtk_button_new (); gtk_widget_set_name (brutina2, "brutina2"); gtk_widget_show (brutina2); gtk_box_pack_start (GTK_BOX (vbox4), brutina2, TRUE, FALSE, 0); alignment16 = gtk_alignment_new (0.5, 0.5, 0, 0); gtk_widget_set_name (alignment16, "alignment16"); gtk_widget_show (alignment16); 123 gtk_container_add (GTK_CONTAINER (brutina2), alignment16); hbox12 = gtk_hbox_new (FALSE, 2); gtk_widget_set_name (hbox12, "hbox12"); gtk_widget_show (hbox12); gtk_container_add (GTK_CONTAINER (alignment16), hbox12); image9 = gtk_image_new_from_stock ("gtk-open", GTK_ICON_SIZE_BUTTON); gtk_widget_set_name (image9, "image9"); gtk_widget_show (image9); gtk_box_pack_start (GTK_BOX (hbox12), image9, FALSE, FALSE, 0); label19 = gtk_label_new_with_mnemonic (_("Cargar Rutina")); gtk_widget_set_name (label19, "label19"); gtk_widget_show (label19); gtk_box_pack_start (GTK_BOX (hbox12), label19, FALSE, FALSE, 0); brutina3 = gtk_button_new (); gtk_widget_set_name (brutina3, "brutina3"); gtk_widget_show (brutina3); gtk_box_pack_start (GTK_BOX (vbox4), brutina3, TRUE, FALSE, 0); alignment17 = gtk_alignment_new (0.5, 0.5, 0, 0); gtk_widget_set_name (alignment17, "alignment17"); gtk_widget_show (alignment17); gtk_container_add (GTK_CONTAINER (brutina3), alignment17); hbox13 = gtk_hbox_new (FALSE, 2); gtk_widget_set_name (hbox13, "hbox13"); gtk_widget_show (hbox13); gtk_container_add (GTK_CONTAINER (alignment17), hbox13); image10 = gtk_image_new_from_stock ("gtk-media-play", GTK_ICON_SIZE_BUTTON); gtk_widget_set_name (image10, "image10"); gtk_widget_show (image10); gtk_box_pack_start (GTK_BOX (hbox13), image10, FALSE, FALSE, 0); label20 = gtk_label_new_with_mnemonic (_("Ejecutar Rutina")); gtk_widget_set_name (label20, "label20"); gtk_widget_show (label20); gtk_box_pack_start (GTK_BOX (hbox13), label20, FALSE, FALSE, 0); label5 = gtk_label_new (_("<b>Rutinas</b>")); gtk_widget_set_name (label5, "label5"); gtk_widget_show (label5); gtk_frame_set_label_widget (GTK_FRAME (frame5), label5); gtk_label_set_use_markup (GTK_LABEL (label5), TRUE); /*Ligar las funciones de respuesta específicas a cada boton de la ventana*/ g_signal_connect ((gpointer) bdrive, "clicked", G_CALLBACK (on_bdrive_clicked), NULL); g_signal_connect ((gpointer) bsimular, "clicked", G_CALLBACK (on_bsimular_clicked), NULL); g_signal_connect ((gpointer) bejecutar, "clicked", G_CALLBACK (on_bejecutar_clicked), NULL); g_signal_connect ((gpointer) bejecutar, "clicked", G_CALLBACK (on_bejecutar_clicked), NULL); g_signal_connect ((gpointer) brutina1, "clicked", G_CALLBACK (on_brutina1_clicked), NULL); 124 g_signal_connect ((gpointer) brutina2, "clicked", G_CALLBACK (on_brutina2clicked), NULL); g_signal_connect ((gpointer) brutina3, "clicked", G_CALLBACK (on_brutina3_clicked), NULL); /* destroy - Se desea destruir la ventana, aqui es donde se * debe llevar a cabo la limpieza necesaria */ g_signal_connect((gpointer) window1, "destroy", G_CALLBACK(gtk_widget_destroy), GTK_OBJECT (window1)); return window1; } /*Función de creación de ventana 2*/ GtkWidget* create_window2 (void) { /*Objetos de la ventana 2*/ GtkWidget *window2; GtkWidget *frame6; GtkWidget *alignment18; GtkWidget *vbox8; GtkWidget *hbox14; GtkWidget *bsim; GtkWidget *alignment19; GtkWidget *hbox15; GtkWidget *image11; GtkWidget *label24; GtkWidget *bquit; GtkWidget *alignment20; GtkWidget *hbox16; GtkWidget *image12; GtkWidget *label25; GtkWidget *label23; GtkWidget *bacerca; GtkWidget *alignment21; GtkWidget *hbox17; GtkWidget *image13; GtkWidget *label26; /* Atributos del area GTKGL */ GdkGLConfig *glconfig; glconfig = gdk_gl_config_new_by_mode(GDK_GL_MODE_RGB | GDK_GL_MODE_DEPTH | GDK_GL_MODE_DOUBLE); window2 = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_widget_set_name (window2, "window2"); gtk_window_set_title (GTK_WINDOW (window2), _("Mundo Virtual")); frame6 = gtk_frame_new (NULL); gtk_widget_set_name (frame6, "frame6"); gtk_widget_show (frame6); gtk_container_add (GTK_CONTAINER (window2), frame6); gtk_frame_set_shadow_type (GTK_FRAME (frame6), GTK_SHADOW_NONE); alignment18 = gtk_alignment_new (0.5, 0.5, 1, 1); gtk_widget_set_name (alignment18, "alignment18"); gtk_widget_show (alignment18); gtk_container_add (GTK_CONTAINER (frame6), alignment18); 125 vbox8 = gtk_vbox_new (FALSE, 2); gtk_widget_set_name (vbox8, "vbox8"); gtk_widget_show (vbox8); gtk_container_add (GTK_CONTAINER (alignment18), vbox8); gtk_container_set_border_width (GTK_CONTAINER (vbox8), 2); /*Creación del area de despliegue OpenGL*/ glarea1 = gtk_drawing_area_new(); gtk_widget_set_gl_capability(GTK_WIDGET (glarea1), glconfig, NULL, TRUE, GDK_GL_RGBA_TYPE); gtk_widget_set_name (glarea1, "glarea1"); /*Eventos habilitados en el area OpenGL*/ gtk_widget_set_events(glarea1, GDK_EXPOSURE_MASK| GDK_BUTTON_PRESS_MASK| GDK_KEY_PRESS_MASK| GDK_POINTER_MOTION_MASK| GDK_POINTER_MOTION_HINT_MASK); gtk_widget_show (glarea1); gtk_box_pack_start (GTK_BOX (vbox8), glarea1, TRUE, TRUE, 0); gtk_widget_set_size_request (glarea1, 350, 350); hbox14 = gtk_hbox_new (FALSE, 0); gtk_widget_set_name (hbox14, "hbox14"); gtk_widget_show (hbox14); gtk_box_pack_start (GTK_BOX (vbox8), hbox14, TRUE, TRUE, 0); bsim = gtk_button_new (); gtk_widget_set_name (bsim, "bsim"); gtk_widget_show (bsim); gtk_box_pack_start (GTK_BOX (hbox14), bsim, TRUE, TRUE, 0); alignment19 = gtk_alignment_new (0.5, 0.5, 0, 0); gtk_widget_set_name (alignment19, "alignment19"); gtk_widget_show (alignment19); gtk_container_add (GTK_CONTAINER (bsim), alignment19); hbox15 = gtk_hbox_new (FALSE, 2); gtk_widget_set_name (hbox15, "hbox15"); gtk_widget_show (hbox15); gtk_container_add (GTK_CONTAINER (alignment19), hbox15); image11 = gtk_image_new_from_stock ("gtk-media-play", GTK_ICON_SIZE_BUTTON); gtk_widget_set_name (image11, "image11"); gtk_widget_show (image11); gtk_box_pack_start (GTK_BOX (hbox15), image11, FALSE, FALSE, 0); label24 = gtk_label_new_with_mnemonic (_("SIMULAR")); gtk_widget_set_name (label24, "label24"); gtk_widget_show (label24); gtk_box_pack_start (GTK_BOX (hbox15), label24, FALSE, FALSE, 0); gtk_label_set_justify (GTK_LABEL (label24), GTK_JUSTIFY_CENTER); bquit = gtk_button_new (); gtk_widget_set_name (bquit, "bquit"); gtk_widget_show (bquit); gtk_box_pack_start (GTK_BOX (hbox14), bquit, FALSE, FALSE, 0); gtk_widget_set_size_request (bquit, 133, -1); alignment20 = gtk_alignment_new (0.5, 0.5, 0, 0); gtk_widget_set_name (alignment20, "alignment20"); 126 gtk_widget_show (alignment20); gtk_container_add (GTK_CONTAINER (bquit), alignment20); hbox16 = gtk_hbox_new (FALSE, 2); gtk_widget_set_name (hbox16, "hbox16"); gtk_widget_show (hbox16); gtk_container_add (GTK_CONTAINER (alignment20), hbox16); image12 = gtk_image_new_from_stock ("gtk-cancel", GTK_ICON_SIZE_BUTTON); gtk_widget_set_name (image12, "image12"); gtk_widget_show (image12); gtk_box_pack_start (GTK_BOX (hbox16), image12, FALSE, FALSE, 0); label25 = gtk_label_new_with_mnemonic (_("QUIT")); gtk_widget_set_name (label25, "label25"); gtk_widget_show (label25); gtk_box_pack_start (GTK_BOX (hbox16), label25, FALSE, FALSE, 0); bacerca = gtk_button_new (); gtk_widget_set_name (bquit, "bacerca"); gtk_widget_show (bacerca); gtk_box_pack_start (GTK_BOX (hbox14), bacerca, FALSE, FALSE, 0); alignment21 = gtk_alignment_new (0.5, 0.5, 0, 0); gtk_widget_set_name (alignment20, "alignment21"); gtk_widget_show (alignment21); gtk_container_add (GTK_CONTAINER (bacerca), alignment21); hbox17 = gtk_hbox_new (FALSE, 2); gtk_widget_set_name (hbox16, "hbox17"); gtk_widget_show (hbox17); gtk_container_add (GTK_CONTAINER (alignment21), hbox17); image13 = gtk_image_new_from_stock ("gtk-edit", GTK_ICON_SIZE_BUTTON); gtk_widget_set_name (image13, "image13"); gtk_widget_show (image13); gtk_box_pack_start (GTK_BOX (hbox17), image13, FALSE, FALSE, 0); label26 = gtk_label_new_with_mnemonic (_("Acerca de (about)")); gtk_widget_set_name (label26, "label26"); gtk_widget_show (label26); gtk_box_pack_start (GTK_BOX (hbox17), label26, FALSE, FALSE, 0); label23 = gtk_label_new (_("<b>Simulador</b>")); gtk_widget_set_name (label23, "label23"); gtk_widget_show (label23); gtk_frame_set_label_widget (GTK_FRAME (frame6), label23); gtk_label_set_use_markup (GTK_LABEL (label23), TRUE); g_signal_connect ((gpointer) bsim, "clicked", G_CALLBACK (on_bsim_clicked), NULL); g_signal_connect((gpointer) bquit, "clicked", G_CALLBACK(gtk_main_quit), NULL); /* Esta función se encarga de monitorear cuando se presiona un botón dentro del * area GTKGL.*/ g_signal_connect ((gpointer) glarea1, "button_press_event", G_CALLBACK(on_glarea_button_press_event), NULL); g_signal_connect((gpointer) bacerca, "clicked", G_CALLBACK(on_bacerca_clicked), NULL); /* Esta función se encarga de monitorear cuando se presiona una tecla 127 * y la ventana OpenGL esté en frente*/ g_signal_connect_swapped (G_OBJECT (window2), "key_press_event", G_CALLBACK (on_glarea_key_press_event), glarea1); /* motion_notify_event - El ratón se esta moviendo dentro de la ventana */ g_signal_connect ((gpointer) glarea1, "motion_notify_event", G_CALLBACK(on_glarea_motion_notify_event), NULL); /* expose_event - La ventana se ha expuesto y el contenido debe de ser redibujado */ g_signal_connect ((gpointer) glarea1, "expose_event", G_CALLBACK(on_glarea_expose_event), NULL); /* configure_event - Se ha cambiado el tamaño de la ventana.*/ g_signal_connect ((gpointer) glarea1, "configure_event", G_CALLBACK(on_glarea_configure_event), NULL); /* realize - Se ha creado la ventana, aqui se introducen las */ /* rutinas de inicialización. */ g_signal_connect ((gpointer) glarea1, "realize", G_CALLBACK(glarea_init), NULL); /* destroy - Se desea destruir la ventana, aqui es donde se * debe llevar a cabo la limpieza necesaria */ g_signal_connect((gpointer) glarea1, "destroy", G_CALLBACK(gtk_widget_destroy), GTK_OBJECT (window2)); return window2; } /*Función de creación de la ventana 3*/ GtkWidget* create_window3 (void) { /*Objetos de la ventana 3*/ GtkWidget *window3; GtkWidget *hbox16; GtkWidget *frame2; GtkWidget *alignment2; GtkWidget *hbox5; GtkWidget *vbox5; GtkWidget *label12; GtkWidget *bposicion1; GtkWidget *alignment10; GtkWidget *hbox6; GtkWidget *image3; GtkWidget *label13; GtkWidget *bposicion2; GtkWidget *alignment11; GtkWidget *hbox7; GtkWidget *image4; GtkWidget *label14; GtkWidget *bposicion3; GtkWidget *alignment12; GtkWidget *hbox8; GtkWidget *image5; GtkWidget *label15; GtkWidget *bposicion4; GtkWidget *alignment14; GtkWidget *hbox10; GtkWidget *image7; GtkWidget *label17; GtkWidget *bposicion5; GtkWidget *alignment13; 128 GtkWidget *hbox9; GtkWidget *image6; GtkWidget *label16; GtkWidget *vseparator2; GtkWidget *vbox6; GtkWidget *label21; GtkWidget *vseparator1; GtkWidget *vbox7; GtkWidget *label22; GtkWidget *bsimulador; GtkWidget *label2; GtkWidget *hbox2; window3= gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_widget_set_name (window3, "window3"); gtk_container_set_border_width (GTK_CONTAINER (window3), 3); gtk_window_set_title (GTK_WINDOW (window3), _("Controles del Simulador (Controles de Configuración)")); hbox16 = gtk_hbox_new (FALSE, 0); gtk_widget_set_name (hbox16, "hbox16"); gtk_widget_show (hbox16); gtk_container_add (GTK_CONTAINER (window3), hbox16); frame2 = gtk_frame_new (NULL); gtk_widget_set_name (frame2, "frame2"); gtk_widget_show (frame2); gtk_box_pack_start (GTK_BOX (hbox16), frame2, TRUE, TRUE, 0); gtk_container_set_border_width (GTK_CONTAINER (frame2), 3); gtk_frame_set_shadow_type (GTK_FRAME (frame2), GTK_SHADOW_OUT); alignment2 = gtk_alignment_new (0.5, 0.5, 1, 1); gtk_widget_set_name (alignment2, "alignment2"); gtk_widget_show (alignment2); gtk_container_add (GTK_CONTAINER (frame2), alignment2); gtk_alignment_set_padding (GTK_ALIGNMENT (alignment2), 0, 0, 12, 0); hbox5 = gtk_hbox_new (FALSE, 0); gtk_widget_set_name (hbox5, "hbox5"); gtk_widget_show (hbox5); gtk_container_add (GTK_CONTAINER (alignment2), hbox5); vbox5 = gtk_vbox_new (FALSE, 0); gtk_widget_set_name (vbox5, "vbox5"); gtk_widget_show (vbox5); gtk_box_pack_start (GTK_BOX (hbox5), vbox5, TRUE, TRUE, 0); gtk_container_set_border_width (GTK_CONTAINER (vbox5), 13); label12 = gtk_label_new (_("POSICION")); gtk_widget_set_name (label12, "label12"); gtk_widget_show (label12); gtk_box_pack_start (GTK_BOX (vbox5), label12, TRUE, FALSE, 0); bposicion1 = gtk_button_new (); gtk_widget_set_name (bposicion1, "bposicion1"); gtk_widget_show (bposicion1); gtk_box_pack_start (GTK_BOX (vbox5), bposicion1, TRUE, FALSE, 0); alignment10 = gtk_alignment_new (0.5, 0.5, 0, 0); gtk_widget_set_name (alignment10, "alignment10"); gtk_widget_show (alignment10); gtk_container_add (GTK_CONTAINER (bposicion1), alignment10); hbox6 = gtk_hbox_new (FALSE, 2); gtk_widget_set_name (hbox6, "hbox6"); 129 gtk_widget_show (hbox6); gtk_container_add (GTK_CONTAINER (alignment10), hbox6); image3 = gtk_image_new_from_stock ("gtk-redo", GTK_ICON_SIZE_BUTTON); gtk_widget_set_name (image3, "image3"); gtk_widget_show (image3); gtk_box_pack_start (GTK_BOX (hbox6), image3, FALSE, FALSE, 0); label13 = gtk_label_new_with_mnemonic (_("APILAR")); gtk_widget_set_name (label13, "label13"); gtk_widget_show (label13); gtk_box_pack_start (GTK_BOX (hbox6), label13, FALSE, FALSE, 0); bposicion2 = gtk_button_new (); gtk_widget_set_name (bposicion2, "bposicion2"); gtk_widget_show (bposicion2); gtk_box_pack_start (GTK_BOX (vbox5), bposicion2, TRUE, FALSE, 0); alignment11 = gtk_alignment_new (0.5, 0.5, 0, 0); gtk_widget_set_name (alignment11, "alignment11"); gtk_widget_show (alignment11); gtk_container_add (GTK_CONTAINER (bposicion2), alignment11); hbox7 = gtk_hbox_new (FALSE, 2); gtk_widget_set_name (hbox7, "hbox7"); gtk_widget_show (hbox7); gtk_container_add (GTK_CONTAINER (alignment11), hbox7); image4 = gtk_image_new_from_stock ("gtk-undo", GTK_ICON_SIZE_BUTTON); gtk_widget_set_name (image4, "image4"); gtk_widget_show (image4); gtk_box_pack_start (GTK_BOX (hbox7), image4, FALSE, FALSE, 0); label14 = gtk_label_new_with_mnemonic (_("DESAPILAR")); gtk_widget_set_name (label14, "label14"); gtk_widget_show (label14); gtk_box_pack_start (GTK_BOX (hbox7), label14, FALSE, FALSE, 0); bposicion3 = gtk_button_new (); gtk_widget_set_name (bposicion3, "bposicion3"); gtk_widget_show (bposicion3); gtk_box_pack_start (GTK_BOX (vbox5), bposicion3, TRUE, FALSE, 0); alignment12 = gtk_alignment_new (0.5, 0.5, 0, 0); gtk_widget_set_name (alignment12, "alignment12"); gtk_widget_show (alignment12); gtk_container_add (GTK_CONTAINER (bposicion3), alignment12); hbox8 = gtk_hbox_new (FALSE, 2); gtk_widget_set_name (hbox8, "hbox8"); gtk_widget_show (hbox8); gtk_container_add (GTK_CONTAINER (alignment12), hbox8); image5 = gtk_image_new_from_stock ("gtk-refresh", GTK_ICON_SIZE_BUTTON); gtk_widget_set_name (image5, "image5"); gtk_widget_show (image5); gtk_box_pack_start (GTK_BOX (hbox8), image5, FALSE, FALSE, 0); label15 = gtk_label_new_with_mnemonic (_("Reiniciar Pila")); gtk_widget_set_name (label15, "label15"); gtk_widget_show (label15); gtk_box_pack_start (GTK_BOX (hbox8), label15, FALSE, FALSE, 0); bposicion4 = gtk_button_new (); 130 gtk_widget_set_name (bposicion4, "bposicion4"); gtk_widget_show (bposicion4); gtk_box_pack_start (GTK_BOX (vbox5), bposicion4, TRUE, FALSE, 0); alignment14 = gtk_alignment_new (0.5, 0.5, 0, 0); gtk_widget_set_name (alignment14, "alignment14"); gtk_widget_show (alignment14); gtk_container_add (GTK_CONTAINER (bposicion4), alignment14); hbox10 = gtk_hbox_new (FALSE, 2); gtk_widget_set_name (hbox10, "hbox10"); gtk_widget_show (hbox10); gtk_container_add (GTK_CONTAINER (alignment14), hbox10); image7 = gtk_image_new_from_stock ("gtk-refresh", GTK_ICON_SIZE_BUTTON); gtk_widget_set_name (image7, "image7"); gtk_widget_show (image7); gtk_box_pack_start (GTK_BOX (hbox10), image7, FALSE, FALSE, 0); label17 = gtk_label_new_with_mnemonic (_("Reiniciar Robot")); gtk_widget_set_name (label17, "label17"); gtk_widget_show (label17); gtk_box_pack_start (GTK_BOX (hbox10), label17, FALSE, FALSE, 0); bposicion5 = gtk_button_new (); gtk_widget_set_name (bposicion5, "bposicion5"); gtk_widget_show (bposicion5); gtk_box_pack_start (GTK_BOX (vbox5), bposicion5, TRUE, FALSE, 0); alignment13 = gtk_alignment_new (0.5, 0.5, 0, 0); gtk_widget_set_name (alignment13, "alignment13"); gtk_widget_show (alignment13); gtk_container_add (GTK_CONTAINER (bposicion5), alignment13); hbox9 = gtk_hbox_new (FALSE, 2); gtk_widget_set_name (hbox9, "hbox9"); gtk_widget_show (hbox9); gtk_container_add (GTK_CONTAINER (alignment13), hbox9); image6 = gtk_image_new_from_stock ("gtk-refresh", GTK_ICON_SIZE_BUTTON); gtk_widget_set_name (image6, "image6"); gtk_widget_show (image6); gtk_box_pack_start (GTK_BOX (hbox9), image6, FALSE, FALSE, 0); label16 = gtk_label_new_with_mnemonic (_("Reiniciar C\303\241mara")); gtk_widget_set_name (label16, "label16"); gtk_widget_show (label16); gtk_box_pack_start (GTK_BOX (hbox9), label16, FALSE, FALSE, 0); vseparator2 = gtk_vseparator_new (); gtk_widget_set_name (vseparator2, "vseparator2"); gtk_widget_show (vseparator2); gtk_box_pack_start (GTK_BOX (hbox5), vseparator2, TRUE, TRUE, 0); vbox6 = gtk_vbox_new (FALSE, 0); gtk_widget_set_name (vbox6, "vbox6"); gtk_widget_show (vbox6); gtk_box_pack_start (GTK_BOX (hbox5), vbox6, TRUE, TRUE, 0); gtk_container_set_border_width (GTK_CONTAINER (vbox6), 3); label21 = gtk_label_new (_("VISTA")); gtk_widget_set_name (label21, "label21"); gtk_widget_show (label21); gtk_box_pack_start (GTK_BOX (vbox6), label21, FALSE, FALSE, 18); 131 bgrid = gtk_toggle_button_new_with_mnemonic (_("GRID")); gtk_widget_set_name (bgrid, "bgrid"); gtk_widget_show (bgrid); gtk_box_pack_start (GTK_BOX (vbox6), bgrid, TRUE, FALSE, 0); btexturas = gtk_toggle_button_new_with_mnemonic (_("Fondo Blanco")); gtk_widget_set_name (btexturas, "btexturas"); gtk_widget_show (btexturas); gtk_box_pack_start (GTK_BOX (vbox6), btexturas, TRUE, FALSE, 0); vseparator1 = gtk_vseparator_new (); gtk_widget_set_name (vseparator1, "vseparator1"); gtk_widget_show (vseparator1); gtk_box_pack_start (GTK_BOX (hbox5), vseparator1, TRUE, TRUE, 0); vbox7 = gtk_vbox_new (FALSE, 0); gtk_widget_set_name (vbox7, "vbox7"); gtk_widget_show (vbox7); gtk_box_pack_start (GTK_BOX (hbox5), vbox7, TRUE, TRUE, 0); gtk_container_set_border_width (GTK_CONTAINER (vbox7), 3); label22 = gtk_label_new (_("SIMULACION")); gtk_widget_set_name (label22, "label22"); gtk_widget_show (label22); gtk_box_pack_start (GTK_BOX (vbox7), label22, TRUE, FALSE, 0); bcheckmax = gtk_toggle_button_new_with_mnemonic (_("Lim. Angulos")); gtk_widget_set_name (bcheckmax, "bcheckmax"); gtk_widget_show (bcheckmax); gtk_box_pack_start (GTK_BOX (vbox7), bcheckmax, TRUE, FALSE, 0); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (bcheckmax), TRUE); bcolision1 = gtk_toggle_button_new_with_mnemonic (_("1\302\260 Colisi\303\263n")); gtk_widget_set_name (bcolision1, "bcolision1"); gtk_widget_show (bcolision1); gtk_box_pack_start (GTK_BOX (vbox7), bcolision1, TRUE, FALSE, 0); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (bcolision1), TRUE); bsimulador = gtk_toggle_button_new_with_mnemonic (_("SIM ON")); gtk_widget_set_name (bsimulador, "bsimulador"); gtk_widget_show (bsimulador); gtk_box_pack_start (GTK_BOX (vbox7), bsimulador, TRUE, FALSE, 0); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (bsimulador), TRUE); label2 = gtk_label_new (_("<b>OPCIONES DEL SIMULADOR</b>")); gtk_widget_set_name (label2, "label2"); gtk_widget_show (label2); gtk_frame_set_label_widget (GTK_FRAME (frame2), label2); gtk_label_set_use_markup (GTK_LABEL (label2), TRUE); gtk_label_set_justify (GTK_LABEL (label2), GTK_JUSTIFY_CENTER); /*Conección de la funciones de respuestas a los botones la ventana 3*/ g_signal_connect ((gpointer) bposicion1, "clicked", G_CALLBACK (on_bposicion1_clicked), NULL); g_signal_connect ((gpointer) bposicion2, "clicked", G_CALLBACK (on_bposicion2_clicked), NULL); g_signal_connect ((gpointer) bposicion3, "clicked", G_CALLBACK (on_bposicion3_clicked), NULL); g_signal_connect ((gpointer) bposicion4, "clicked", G_CALLBACK (on_bposicion4_clicked), 132 NULL); g_signal_connect ((gpointer) bposicion5, "clicked", G_CALLBACK (on_bposicion5_clicked), NULL); g_signal_connect ((gpointer) bposicion5, "clicked", G_CALLBACK (on_bposicion5_clicked), NULL); g_signal_connect ((gpointer) bgrid, "toggled", G_CALLBACK (on_bgrid_toggled), NULL); g_signal_connect ((gpointer) btexturas, "toggled", G_CALLBACK (on_btexturas_toggled), NULL); g_signal_connect ((gpointer) bcheckmax, "toggled", G_CALLBACK (on_bcheckmax_toggled), NULL); g_signal_connect ((gpointer) bcolision1, "toggled", G_CALLBACK (on_bcolision1_toggled), NULL); g_signal_connect ((gpointer) bsimulador, "toggled", G_CALLBACK (on_bsimulador_toggled), NULL); /* destroy - Se desea destruir la ventana, aqui es donde se * debe llevar a cabo la limpieza necesaria */ g_signal_connect((gpointer) window3, "destroy", G_CALLBACK(gtk_widget_destroy), GTK_OBJECT (window3)); return window3; } /*************************************************************************************************************/ rutina.c: Funciones de creación, edición y ejecución de rutinas desde linea de comandos /*************************************************************************** * SimQNK * Simulador virtual del brazo robot Stäubli RX90 L * rutina.c: funciones de creación, edición, carga y ejecución de rutinas desde la linea de comandos * Copyright(C) 2006 David Cuenca * Email: dcuenc@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "rutina.h" int puntpos=0; 133 /*MATRIZ DE PILA POSICIONES*/ /*Espacio de memoria en el que se apilan las posiciones*/ float pilaposiciones[60][6]; /*Velocidades Maximas de giro de cada articulación*/ int velocmaximas[6]={356, 356, 296, 409,800, 1125}; //.............APILAR/DESAPILAR POSICION ACTUAL...................// void push_posicion(void){ pilaposiciones[puntpos][0]=angulo1; pilaposiciones[puntpos][1]=angulo2; pilaposiciones[puntpos][2]=angulo3; pilaposiciones[puntpos][3]=angulo4; pilaposiciones[puntpos][4]=angulo5; pilaposiciones[puntpos][5]=angulo6; if(puntpos < 60){ puntpos++; //aumenta el puntero de la pila } } void pull_posicion(void){ if(puntpos > 0){ puntpos--;} angulo1=pilaposiciones[puntpos][0]; angulo2=pilaposiciones[puntpos][1]; angulo3=pilaposiciones[puntpos][2]; angulo4=pilaposiciones[puntpos][3]; angulo5=pilaposiciones[puntpos][4]; angulo6=pilaposiciones[puntpos][5]; } /*Reiniciar la pila*/ void reiniciar_pila(void){ puntpos=0; } //.............VELOCIDADES DE CADA ARTICULACION.........// /*Se establece la velocidad del movimientos, dependiedo del * porcentaje pasado y de la velocidad máxima de la articulación*/ void set_velocidad(int porciento, int numart){ velocidad=(velocmaximas[numart-1])*porciento/100; if(velocidad==0){ velocidad=10; } } //.............CREACION DE UNA RUTINA...................// /* Utilizando la terminal (linea de comandos)*/ void crear_rutina(void){ int nru; char basura[10]; /*Se pide el nombre la rutina a crear*/ printf("\n\n----CREACIÓN DE UNA RUTINA (Presione ENTER)---"); fflush(stdin); scanf("%c",&basura); printf("\nNumero de movimientos:_"); scanf("%f", &rutina[0][0]); if(rutina[0][0] > 60){ rutina[0][0]=60; } for(nru=1; nru<=rutina[0][0];nru++){ /*Se pide caracterizar los movimientos de la rutina*/ printf("Movimiento #%i ->JOINT:_", nru); scanf("%f", &rutina[nru][0]); 134 printf("Movimiento #%i ->ANGULO:_", nru); scanf("%f", &rutina[nru][1]); printf("Movimiento #%i ->VELOCIDAD:_", nru); scanf("%f", &rutina[nru][2]); } printf("\nUtiliza la tecla >> F8 << para ejecutar la rutina"); fflush(stdout); } /*.............CRONOMETRO DE ESPERA.......................*/ /*Regula la velocidad de dibujo de los cuadro de la escena (frames)*/ void esperar(float segundos) { clock_t tiempoespera; tiempoespera = clock () + segundos * CLK_TCK ; while (clock() < tiempoespera) {} } //.............MOVER CADA ARTICULACIÓN.........// /*mueva cada articulación hasta alcanzar el agnulo deseado*/ void mover_joint(float* ang, float delta, float vel){ float angfinal; float veldelta=velocidad/50; int finwhile; int ya; ya=0; finwhile=0; choque=0; angmax=0; push_posicion(); angfinal= (*ang) + delta; //apila la posición actual; if(delta > 0){ /*realizar giro positivo*/ while((*ang) < angfinal && finwhile<1){ dibujar(); (*ang)+=veldelta; if(checkcolision==1 ){ if(choque==1){ /*si se da choque y esta habilitada la opción de terminar al * primer choque, se manda señal de terminar*/ finwhile=1; (*ang)-=veldelta; ya=1; } } if(checkmax==1 ){ if(angmax==1 && ya!=1){ /*si se llega a angulo maximo y esta habilitada la opción de limitar angulos de giro, * se manda señal de terminar*/ finwhile=1; (*ang)-=veldelta; } } //esperar(1); } if(finwhile!=1){ (*ang)=angfinal; } /*dibujar el modelo en la nueva posición*/ dibujar(); }else if(delta < 0){ 135 /*realizar giro negativo*/ while((*ang) > angfinal && finwhile<1){ dibujar(); (*ang)-=veldelta; if(checkcolision==1 ){ if(choque==1){ finwhile=1; (*ang)+=veldelta; ya=1; } } if(checkmax==1 ){ if(angmax==1 && ya!=1){ finwhile=1; (*ang)+=veldelta; } } //esperar(1); } if(finwhile!=1){ (*ang)=angfinal; } /*dibujar el modelo en la nueva posición*/ dibujar(); } } void selec_mover_joint(void){ /*Seleccionar cual articulación es la que se debe mover, segun la orden del usuario*/ float* puntang; if(joint==1){ puntang = &angulo1; mover_joint(puntang, angulodelta, velocidad); }else if(joint==2){ puntang = &angulo2; mover_joint(puntang, -1*angulodelta, velocidad); }else if(joint==3){ puntang = &angulo3; mover_joint(puntang, -1*angulodelta, velocidad); }else if(joint==4){ puntang = &angulo4; mover_joint(puntang, angulodelta, velocidad); }else if(joint==5){ puntang = &angulo5; mover_joint(puntang, -1*angulodelta, velocidad); }else if(joint==6){ puntang = &angulo6; mover_joint(puntang, angulodelta, velocidad); } } //.............EJECUTAR RUTINA..............// /*Ejecutar la rutina cargada en memoria*/ void ejecutar_rutina(void){ int nm; if(rutina[0][0]!=0){ for(nm=1; nm<=rutina[0][0]; nm++){ joint=rutina[nm][0]; 136 angulodelta=rutina[nm][1]; set_velocidad((int)rutina[nm][2], (int)joint); selec_mover_joint(); } } } //..............ABRIR y LEER ARCHIVO DE RUTINA................// int abrir_rutina(void){ FILE *archivo1; char nombrefile[15]; char letra; char articult[3]; char angt[7]; char veloct[7]; char linea[15]; char basura[10]; int p1, p2, p3; //punteros /*Pide nombre de archivo que se desea cargar*/ printf("\nLEER ARCHIVO DE RUTINA (Presione ENTER)\n"); fflush(stdin); scanf("%c", &basura); printf("\nEscriba el nombre del archivo que desea abrir:_"); scanf("%s", &nombrefile); /*abre el archivo y lee la información*/ archivo1=fopen(nombrefile,"r"); if (!archivo1){ printf("\n-ERROR-->No se pudo abrir el archivo %s", nombrefile); fflush(stdout); return 1; } p1=0; rutina[0][0]=0; do{ letra=fgetc(archivo1); if(letra=='M'){ if(p1<60){ p1++; rutina[0][0]++; } }else if(letra=='A'){ p2=0; while(letra!=','){ letra=fgetc(archivo1); angt[p2]=letra; p2++; } rutina[p1][1]=atof(angt); }else if(letra=='J'){ p2=0; while(letra!=','){ letra=fgetc(archivo1); articult[p2]=letra; p2++; } rutina[p1][0]=atof(articult); }else if(letra=='V'){ p2=0; while(letra!=','){ letra=fgetc(archivo1); veloct[p2]=letra; 137 p2++; } rutina[p1][2]=atof(veloct); } }while (letra!='f'); fclose(archivo1); } //................SALVAR RUTINA A ARCHIVO...............................// int salvar_rutina(void){ FILE *archivo1; char nombrefile[15]; char basura[10]; int p1; //punteros /*Pide nombre de archivo en que se desea guardar la rutina*/ printf("\nSALVAR RUTINA (Presione ENTER)\n"); fflush(stdin); scanf("%c", &basura); printf("\nEscriba el nombre del archivo que desea crear:_"); scanf("%s", &nombrefile); archivo1=fopen(nombrefile,"w"); if (!archivo1){ printf("\n-ERROR-->No se pudo abrir el archivo %s", nombrefile); return 1; } p1=0; fprintf(archivo1,"inicio\n"); for(p1=1;p1<=rutina[0][0];p1++){ /*imprime cada movimiento de la rutina de la memoria, en una linea nueva del archivo de texto*/ fprintf(archivo1,"M%i, J %d, A %3.1f, V %d,\n",p1,(int)rutina[p1][0],rutina[p1][1],(int)rutina[p1][2]); } fprintf(archivo1,"fin"); fclose(archivo1); } /*************************************************************************************************************/ ventanagl.c: Funciones de configuración de la ventana OpenGL /*************************************************************************** * SimQNK * Simulador virtual del brazo robot Stäubli RX90 L * ventanagl.c: funciones de configuración de la ventana de despliegue OpenGL * Copyright(C) 2006 David Cuenca * Email: dcuenc@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ 138 #include "ventanagl.h" #define SLICES 30 #define STACKS 30 /*Posición de las luces*/ GLfloat position0 [] = { 0.0, 0.0, 600.0, 1.0 }; /*Función reinicia los valores de posición y velocidad de la camara*/ void valores_iniciales_camara(void){ printf("\nObjeto Seleccionado--> CAMARA"); camara_x = 0; camara_y = 0; camara_z = 300; ancho_x=0; altura_y =0; angulo_yr=0; angulo_xr=0; zoom_z = 160; aument_cam = 1; mov_cam = 1; } /* Inicio de la ventana OpenGL --> (luces, materiales, buffers)*/ gboolean inicio(void){ GdkGLContext *glcontext = gtk_widget_get_gl_context (glarea1); GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (glarea1); /*** OpenGL BEGIN ***/ if (!gdk_gl_drawable_make_current(gldrawable, glcontext)){ printf("\nError al buscar area OpenGL"); return FALSE; } /*Vectores que definen el tipo de material*/ GLfloat ambient [] = { 0.1, 0.1, 0.1, 0.0 }; GLfloat specular []= { 1.0, 1.0, 1.0, 1.0 }; GLfloat shininess [] = { 100.0 }; glClearColor(0.0, 0.0, 0.0, 0.0); glShadeModel(GL_SMOOTH); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glEnable(GL_DEPTH_TEST); /*Definición del tipo de material de los objetos a dibujar*/ glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular); glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, shininess); glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE); glEnable(GL_COLOR_MATERIAL); /*Posición de las luces*/ glLightfv(GL_LIGHT0, GL_POSITION, position0); glLightfv(GL_LIGHT0, GL_AMBIENT, ambient); /*Se habilitan las luces*/ glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); /*Crear las diferentes partes del robot compilando las listas de despliegue*/ crear_base(SLICES, STACKS); crear_hombro(); crear_brazo1(SLICES, STACKS); 139 crear_codo(); crear_brazo2(SLICES, STACKS); crear_munieca(SLICES, STACKS); crear_mano(SLICES, STACKS); return TRUE; } /* Reajustar el tamaño de la ventana OpenGL*/ gboolean reshape(int w, int h){ /*Buscar area OpenGL*/ GdkGLContext *glcontext = gtk_widget_get_gl_context (glarea1); GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (glarea1); /*** OpenGL INICIO ***/ if (!gdk_gl_drawable_make_current(gldrawable, glcontext)){ printf("\nError al buscar area OpenGL"); return FALSE; } /*Prevenir la división por cero*/ if(h==0){h=1;}; /*Define el area de dibujo de la ventana*/ glViewport(0, 0, (GLint)w, (GLint)h); /*Se carga la Matriz de proyección*/ glMatrixMode(GL_PROJECTION); glLoadIdentity(); /*Volumen de visión de la camara*/ glOrtho(-1*zoom_z*w/h, zoom_z*w/h, -1*zoom_z, zoom_z, -1200, 1200); /*Posición de la camara en la escena*/ gluLookAt(camara_x, camara_y, camara_z, 0, 0, 0, 0, 1, 0); /*Se mueva la camara */ glRotatef(angulo_xr, 1.0, 0.0, 0.0); glTranslatef(ancho_x, altura_y, 0.0); glRotatef(angulo_yr, 0.0, 1.0, 0.0); glMatrixMode(GL_MODELVIEW); /*Mover la luces. Luces siguen a la camara*/ glPushMatrix(); glRotatef(-1*angulo_yr, 0.0, 1.0, 0.0); glTranslatef(-1*ancho_x, -1*altura_y, 0.0); glRotatef(-1*angulo_xr, 1.0, 0.0, 0.0); glLightfv(GL_LIGHT0, GL_POSITION, position0); glPopMatrix(); return TRUE; } /*************************************************************************************************************/ main.c: Función principal de la aplicación /*************************************************************************** * SimQNK * Simulador virtual del brazo robot Stäubli RX90 L * main.c: función principal del programa * Copyright(C) 2006 David Cuenca * Email: dcuenc@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or 140 * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #include <stdio.h> #include <gtk/gtk.h> #include "ventanagl.h" #include "dibujo.h" #include "rutina.h" #include "interface.h" #include "callbacks.h" int main (int argc, char *argv[]) { /* Se establecen los parametros de configuración inicial del simulador*/ checkmax=1; //Se verifican los ang. máximos checkcolision=1; //Se detiene en el primer choque mover_obj = 1; //El objeto seleccionado es el robot lim=0; grid=0; //Piso solido altpiso=0.0; //Sin base externa marg=0.0; //margen de precaución=0 /*Incializar GTK*/ gtk_set_locale (); gtk_init (&argc, &argv); /*Creary desplegar las ventanas de la aplicación*/ ventana1 = create_window1 (); gtk_widget_show (ventana1); ventana3 = create_window3 (); gtk_widget_show (ventana3); ventana2 = create_window2 (); gtk_widget_show (ventana2); /*Inicializar los valores de posición de la camara y el robot*/ inicio(); valores_iniciales_camara(); valores_iniciales_robot(); init_vortexfijos(marg); /*Crear los puntos de control de colisión*/ crear_controles(); /*Lazo infinito del que nunca se sale*/ gtk_main (); return 0; } /*************************************************************************************************************/ 141