Manual Graficacion

MANUAL PARA EL CURSO DE GRAFICACIÓN Mtra. Ana Cecilia Ruiz Calvillo Cd. Obregón Sonora, Enero 2008 1 DIRECTORIO Mtro

Views 164 Downloads 0 File size 2MB

Report DMCA / Copyright

DOWNLOAD FILE

Recommend stories

Citation preview

MANUAL PARA EL CURSO DE GRAFICACIÓN

Mtra. Ana Cecilia Ruiz Calvillo

Cd. Obregón Sonora, Enero 2008 1

DIRECTORIO Mtro. Sergio Pablo Mariscal Alvarado Director General ITESCA Lic. Clara E. Mark Corona Subdirectora Mtro. Alejandro Faccinetto Ruiz Jefe de la División Académica Mtra. Lilia B. Navarro Fragoso Jefa del Departamento de Desarrollo Académico Mtro. Leobardo Rodríguez Jefe de Carrera Ingeniería en Sistemas Computacionales Mtra. Ana Cecilia Ruiz Calvillo Elaborador del Manual

ESTE MANUAL FUE REALIZADO PARA USO EXCLUSIVO DEL INSTITUTO TECNOLÓGICO SUPERIOR DE CAJEME D.R. ITESCA, Cd.Obregón, Sonora; México. Carretera Internacional a Nogales Km. 2. Tel. (644) 410-86-50

2

INDICE

INTRODUCCIÓN

5

1. INTRODUCCIÓN A LA GRAFICACIÓN POR COMPUTADORA 1.1 Introducción. 1.2 Evolución de la programación de gráficos por computadora.

6 10

1.2.1 Nivel Hardware.

10

1.2.2 Nivel Sistema Operativo.

13

1.2.3 GKS y PHIGS.

16

1.2.4 OpenGL.

19

1.2.5 Java.

21

1.2.5.1 Java 2D.

24

1.2.5.2 Java 3D.

26

1.3 Campos relacionados con la graficación.

28

2. TRANSFORMACIONES GEOMÉTRICAS 2.1 Transformaciones bidimensionales.

30

2.2 Coordenadas homogéneas y representación matricial.

32

2.3 Tipos de transformaciones bidimensionales.

36

2.4 Composición de transformaciones bidimensionales.

45

2.5 Transformaciones de la composición general.

49

2.6 Transformación ventana-área de vista.

53

2.7 Representación matricial de transformaciones tridimensionales.

54

2.7.1 Transformaciones tridimensionales.

56

2.7.2 Matriz de transformación.

56

2.7.3 Tipos de transformaciones tridimensionales.

61

2.8 Composición de transformaciones tridimensionales

71

3. MODELADO GEOMÉTRICO 3.1 Modelos geométricos.

76

3

3.2 Proyecciones.

90

3.2.1 El modelo de vistas.

94

3.2.2 Modo de Compatibilidad.

96

3.2.3 Configuración de vista.

99

3.2.4 Creación de una vista propia. 3.3 Representación tridimensional de objetos.

102 105

3.3.1 Superficies de polígonos, curvas y cuadráticas.

105

3.3.2 Representaciones de “spline” y Curvas Bézier.

112

3.3.3 Superficies Bézier.

122

ANEXOS Anexo 1. Preguntas y Ejercicios de Unidad 1.

126

Anexo 2. Preguntas y Ejercicios de Unidad 2.

128

Anexo 3. Preguntas y Ejercicios de Unidad 3.

132

Anexo 4. Índice de figuras.

137

Anexo 5. Índice de programas.

139

Anexo 6. Índice de tablas.

141

GLOSARIO

142

BIBLIOGRAFÍA

147

4

INTRODUCCIÓN El rápido desarrollo del hardware de computadora, las aplicaciones gráficas y tecnologías de red han hecho que la graficación por computadora sea un tema crucial en estos días. El modelado y renderizado de objetos gráficos virtuales, son los principales objetivos de la graficación. Los tópicos involucrados con este proceso abarcan una gran rama de disciplinas desde las matemáticas y tecnologías de información hasta psicología, medicina, ingeniería y arte. La materia de graficación tiene por objetivo lograr que el estudiante aplique técnicas y algoritmos básicos de representación y visualización de objetos en dos y tres dimensiones para desarrollar modelos de simulación e interfaces hombre – máquina. El presente manual pretende ayudar a lograr dicho objetivo; ya que contiene ejemplos de programas que ilustran cada concepto introducido. Cuenta con un anexo de preguntas y programas a resolver por unidad, para reforzar el aprendizaje adquirido; así como un glosario de terminolgía relacionada con la graficación. Está dividido en tres secciones: • Introducción a la graficación por computadora: se presenta conceptos fundamentales de la graficación, su objetivo y aplicaciones, así como la evolución de los lenguajes de programación utilizados para su desarrollo. • Transformaciones geométricas: se dan a conocer los principales conceptos y aplicaciones de transformaciones geométricas en dos y tres dimensiones, así como la representación matricial de objetos gráficos. • Modelado geométrico: se dan a conocer las técnicas para la representación tridimensional de objetos y sus diferentes proyecciones en el área de vista. Se ha utilizado el lenguaje de programación Java ya que es un lenguaje de programación multiplataforma, es simple y orientado a objetos. Se emplean sus aplicaciones en Java 2D y Java 3D debido a que son paquetes que proporcionan grandes capacidades y poderosas interfaces para el modelado y programación de gráficos.

5

INTRODUCCIÓN A LA GRAFICACIÓN POR COMPUTADORA

1.1 Introducción. La graficación por computadora estudia la teoría y las técnicas de modelado, procesamiento y renderizado de objetos gráficos en computadoras. El objetivo básico de la graficación por computadora es construir un mundo virtual a partir de objetos gráficos y renderizar una escena del modelo virtual a un dispositivo gráfico a partir de vistas específicas.

6

Figura 1.1 Tareas principales de la graficación por computadora: modelando un mundo virtual y renderizandolo a una escena

Un sistema gráfico típicamente consiste en dos componentes: un modelador y un renderizador. El modelador es el responsable de la construcción de los modelos del mundo virtual y el renderizador realiza el renderizado de la escena. Un sistema de modo retenido mantiene un persistente modelo de los objetos gráficos y la función del modelador es explícita. Un sistema de modo inmediato renderiza los objetos inmediatamente y el modelo es mas transitorio. Esta perspectiva del paradigma de modelado-renderizado es conveniente para el estudio de sistemas gráficos, aún cuando la separación no es clara en algunos sistemas. Típicamente los objetos gráficos a modelar están en un espacio ya sea 2D o 3D. Este espacio común alberga todos los objetos gráficos y es comúnmente llamado espacio del mundo, espacio mundo o mundo espacial. En una escena renderizada del espacio mundo, la salida principal del sistema gráfico, tiene típicamente en un formato en 2D. Consecuentemente las técnicas involucradas en gráficos 2D y 3D son comúnmente tratados en temas separados. Los objetos gráficos a ser modelados en el espacio mundo son usualmente entidades geométricas tales como líneas y superficies; pero también se incluyen otros objetos especiales tales como iluminación, textos e imágenes. Los objetos gráficos pueden poseer muchas características y propiedades tales como color, trasparencia y texturas. Para modelar objetos geométricos se utilizan diversas representaciones matemáticas. Segmentos de línea recta y mallas de polígonos simples

7

proporcionan representaciones simples y compactas. Solo los vértices de las estructuras

necesitan

almacenarse

y

son

fáciles

de

implementar.

Las

representaciones más sofisticadas incluyen curvas spline y Bézier y superficies Bézier; éstas son versátiles y requieren solo del almacenamiento de ciertos puntos de control relativos. Las transformaciones geométricas son aplicadas a los objetos para alcanzar el posicionamiento propio de los mismos en un espacio virtual. Las transformaciones de este tipo son llamadas transformaciones de objetos. Las transformaciones también se utilizan para las vistas; estas son conocidas como transformaciones de visualización. Una familia de transformaciones geométricas muy útil es la AffineTransforms, la cual incluye la mayoría de las transformaciones comunes tales como traslaciones, rotaciones, escalaciones y reflecciones. Un conjunto más general de transformaciones son las transformaciones proyectivas, que son muy utilizadas para las vistas en 3D. Una vista es empleada para ver el modelo en el mundo virtual desde una perspectiva específica. Un proceso de una vista en 2D es relativamente simple; la transformación de vistas es usualmente indistinguible de la transformación del objeto y las características de renderizado tales como reglas de composición y fragmentos de trayectoria pueden ser aplicadas. Una vista en 3D es mucho más complicada; tal como los ojos o las cámaras, las vistas en 3D involucran el proceso de proyección que mapea los objetos 3D a un plano 2D. Muchos parámetros tales como la proyección, la posición de vista, orientación y un campo de vista pueden afectar el renderizado en 3D. Para poder alcanzar el renderizado realístico del mundo virtual, existen muchos temas de rendereizado que tienen que direccionarse; es decir las ubicaciones relativas de los objetos tienen que reflejarse correctamente en las imágenes renderizadas. Por ejemplo, un objeto puede estar detrás de otro, y la porción oculta no debe mostrarse en la imagen; las fuentes de iluminación deben ser consideradas ya que las propiedades de los materiales de los objetos afectarán su apariencia.

8

Las posibilidades y características de los dispositivos de hardware tienen un gran impacto en los sistemas gráficos. Los dispositivos de salida más comunes para mostrar los resultados del renderizado de gráficos son los monitores de video y las impresoras. Otros dispositivos incluyen los plotters y los proyectores holográficos. En cuanto a los dispositivos de entrada se encuentran el ratón, josticks y las tabletas digitalizadoras. La animación es también una parte importante de la graficación por computadora. En lugar de tener imágenes estáticas, la animación produce contenidos gráficos dinámicos y su renderizado. En las aplicaciones tales como renderizado de escenas de películas y juegos, la animación juega un papel muy importante. Otro aspecto dinámico de la graficación por computadora es la interacción. En respuesta a las entradas de usuario, el modelo de gráficos puede cambiar acorde a lo que se le indica. El fundamento principal de GUI (Interfaz Gráfica de Usuario) se basa en las interacciones del usuario con los sistemas gráficos. La graficación por computadora tiene una amplia gama de aplicaciones. La popularidad de los ambientes GUI ha hecho de los gráficos una parte integral de los programas de usuario. CAD (Diseño de Ayuda por Computadora) y otras aplicaciones de ingeniería dependen en gran parte de los sistemas gráficos. La visualización de datos y otras aplicaciones científicas también hacen gran uso de los gráficos. Con el rápido desarrollo de la instrumentación basada en computadoras tal como el CT (Tomografía por Computadora), PET (Tomografía de Emisión Positrónica) y MRI (Imagen de Resonancia Magnética), los sistemas médicos han abierto sus puertas a tecnólogos en graficación por computadora. Además la graficación por computadora es un ingrediente crucial para vídeo juegos y otras aplicaciones en el mundo del entretenimiento. Tradicionalmente la graficación por computadora ha lidiado con detalles de implementación, utilizando algoritmos de bajo nivel para convertir figuras primitivas tales como líneas a pixeles, para determinar superficies escondidas de una vista, para calcular los valores de color de los puntos en una superficie, etc.

9

Estos algoritmos y métodos han hecho que la graficación por computadora sea un tema considerado como técnicamente difícil y complejo.

1.2 Evolución de la programación en gráficos por computadora. La programación en gráficos por computadora ha aparecido en casi todos los niveles de la arquitectura de computadora. Hablando en términos generales se ha ido moviendo de un nivel bajo (al utilizar métodos dependientes de la plataforma utilizada) hasta ambientes abstractos, de alto nivel y portables. La siguiente tabla nos muestra ejemplos de los ambientes de programación gráfica en varios niveles de la arquitectura de computadora. Plataforma independiente (Java 2D y Java 3D) Estándares Gráficos (GKS, PHIGS, OpenGL) OS (WIN32, X, Mac OS) Hardware (registro directo / programación de buffer de video) Tabla 1.1 Programación gráfica en diferentes niveles

1.2.1 Nivel Hardware. Los programas de gráficos por computadoras dependen de dispositivos de salida con capacidades gráficas. Los dispositivos más comunes en este caso son los monitores CRT y paneles LCD. Estos son dispositivos de rasteo 2D que proporcionan una superficie de pantalla consistente de un arreglo rectangular de puntos discretos. Un dispositivo de pantalla de este tipo es comúnmente manejado por una tabla de gráficos dedicados con su propio procesador y su propia memoria. Las aplicaciones gráficas de bajo nivel comúnmente programan los gráficos directamente al hardware. En computadoras personales con MSDOS, por ejemplo, la mayoría de las aplicaciones gráficas accesan directamente a la

10

memoria de la pantalla. A pesar de que el BIOS y el DOS proporcionan cierto soporte primitivo para funciones gráficas, son considerados muy lentos para programas gráficos intensos. Tales programas están típicamente escritos en lenguaje ensamblador y manipulan los registros del hardware y los búferes de video de una manera muy dependiente del hardware. El siguiente código muestra un programa en ensamblador que demuestra la programación gráfica a bajo nivel. Utiliza Microsoft Macro Assembler y puede ser ejecutado en cualquier máquina compatible con IBM PC y con una tarjeta de gráficos VGA. El programa dibuja un círculo escribiendo directamente en las localidades de memoria del búfer de video. .model small,stdcall .stack 100h .386 .data saveMode BYTE ? ; saved video mode xc WORD ? ; center x yc WORD ? ; center y x SWORD ? ; x coordinate y SWORD ? ; y coordinate dE SWORD ? ; east delta dSE SWORD ? ; southeast delta w WORD 320 ; screen width .code main PROC mov ax,@data mov ds,ax ;Set Video Mode 320X200 mov ah,0Fh ; get current video mode int 10h mov saveMode,al ; save mode mov ah,0 ; set new video mode mov al,13h ; mode 13h int 10h push 0A000h ; video segment address pop es ; ES = A000h (video segment). ;Set Background mov dx,3c8h ; video palette port (3C8h) mov al,0 ; set palette index out dx,al ;Set screen background color to dark blue. mov dx,3c9h ; port address 3C9h mov al,0 ; red out dx,al mov al,0 ; green out dx,al mov al,32 ; blue (32/63) out dx,al ; Draw Circle ; Change color at index 1 to yellow (63,63,0) mov dx,3c8h ; video palette port (3C8h) mov al,1 ; set palette index 1

11

out dx,al mov dx,3c9h mov al,63 ; red out dx,al mov al,63 ; green out dx,al mov al,0 ; blue out dx,al

; port address 3C9h

mov xc,160 mov yc,100

; center of screen

; Calculate coordinates mov x, 0 mov y, 50 ; radius 50 mov bx, -49 ; 1-radius mov dE, 3 mov dSE, -95 DRAW: call Draw_Pixels

; Draw 8 pixels

cmp bx, 0 ; decide E or SE jns MVSE add bx, dE add dE, 2 add dSE, 2 inc x jmp NXT

; move east

add bx, dSE add dE, 2 add dSE, 4 inc x dec y

; move southeast

MVSE:

NXT: mov cx, x ; continue if x < y cmp cx, y jb DRAW ; Restore Video Mode mov ah,10h ; wait for keystroke int 16h mov ah,0 ; reset video mode mov al,saveMode ; to saved mode int 10h .EXIT main ENDP ; Draw 8 pixels symmetrical about the center Draw_Pixels PROC ; Calculate the video buffer offset of the pixel. mov ax, yc add ax, y mul w add ax, xc add ax, x mov di, ax mov BYTE PTR es:[di],1 ; store color index ; Horizontal symmetrical pixel sub di, x sub di, x mov BYTE PTR es:[di],1 ; store color index ; Vertical symmetrical pixel mov ax, yc sub ax, y mul w add ax, xc add ax, x mov di, ax mov BYTE PTR es:[di],1 ; store color index

12

; Horizontal pixel sub di, x sub di, x mov BYTE PTR es:[di],1 ; store color index ; Switch x, y to get other 4 pixels mov ax, yc add ax, x mul w add ax, xc add ax, y mov di, ax mov BYTE PTR es:[di],1 ; store color index sub di, y sub di, y mov BYTE PTR es:[di],1 ; store color index mov ax, yc sub ax, x mul w add ax, xc add ax, y mov di, ax mov BYTE PTR es:[di],1 ; store color index sub di, y sub di, y mov BYTE PTR es:[di],1 ; store color index ret Draw_Pixels ENDP END main

Programa 1.1 Código en ensamblador que muestra un círculo.

Figura 1.2 Círculo creado al ejecutar el código en ensamblador.

1.2.2 Nivel a Sistema Operativo. Las infraestructuras de gráficos a bajo nivel proporcionan facilidades básicas para programar las pantallas. Sin embargo, la programación directa a los buffers de video y a los registros del hardware no es una forma efectiva para aplicaciones generales de gráficos. El programar a nivel de hardware requiere del conocimiento a fondo de los dispositivos además que es tedioso el hacer tareas

13

simples. Los programas escritos a este nivel no son portales aun para diferentes dispositivos en la misma plataforma. La programación de interfaces a alto nivel es necesaria para facilitar la carga de la programación gráfica. Debido a las inherentes complejidades de los problemas gráficos, es deseable proporcionar una capa de abstracción para la programación de aplicaciones. Un lugar natural para agregar dicha abstracción es el sistema operativo. Con el desarrollo y la expansión de aplicaciones de interfaces gráficas de usuario (GUI) en los sistemas modernos de computadora, los gráficos soportados en los sistemas operativos se han convertido cada vez en más comunes y extensivos. Las APIs proporcionadas al nivel de sistema operativo proporcionan una interfaz uniforme para la programación gráfica en la misma plataforma. Normalmente las diferencias de hardware son complacidas al utilizar drivers específicos para los dispositivos. Un driver implementa una interface estándar con el sistema operativo para un dispositivo particular. Los programas de aplicaciones solo necesitan llamar a funciones gráficas estándares proporcionadas por el sistema operativo y se evitan la tarea de lidiar con las especificaciones del hardware. Win32 es la API para el sistema operativo de 32 bits de Windows tal como Windows 9x/ME/NT/2000/XP. El siguiente código muestra un programa de WIN32 que dibuja un círculo. Este es un ejemplo simple de un programa en Windows escrito en lenguaje C. El programa crea una ventana estándar y llama directamente al API de WIN32 para dibujar el círculo en el área del cliente de la ventana principal del programa. El círculo está centrado en la ventana y el tamaño es ajustado automáticamente si la ventana se ajusta. #include #include LRESULT CALLBACK MainWndProc (HWND hwnd, UINT nMsg, WPARAM wParam, LPARAM lParam) { HDC hdc; /* Device context used for drawing */ PAINTSTRUCT ps; /* Paint structure used during drawing */ RECT rc; /* Client area rectangle */ int cx; /* Center x-coordinate */ int cy; /* Center y-coordinate */ int r; /* Radius of circle */

14

/* Message processing.*/ switch (nMsg) { case WM_DESTROY: /* The window is being destroyed, close the application */ PostQuitMessage (0); return 0; break; case WM_PAINT: /* The window needs to be redrawn. */ hdc = BeginPaint (hwnd, &ps); GetClientRect (hwnd, &rc); /* Calculater center and radius */ cx = (rc.left + rc.right)/2; cy = (rc.top + rc.bottom)/2; if (rc.bottom - rc.top < rc.right - rc.left) r = (rc.bottom - rc.top) / 2 - 20; else r = (rc.right - rc.left) / 2 - 20; Ellipse(hdc, cx-r, cy-r, cx+r, cy+r); EndPaint (hwnd, &ps); return 0; break; } return DefWindowProc (hwnd, nMsg, wParam, lParam); } int WINAPI WinMain (HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmd, int nShow) { HWND hwndMain; /* Main window handle */ MSG msg; /* Win32 message structure */ WNDCLASSEX wndclass; /* Window class structure */ char* szMainWndClass = "WinCircle"; /* The window class name */ /* Create a window class */ /* Initialize the entire structure to zero */ memset (&wndclass, 0, sizeof(WNDCLASSEX)); /* The class Name */ wndclass.lpszClassName = szMainWndClass; /* The size of the structure. */ wndclass.cbSize = sizeof(WNDCLASSEX); /* All windows of this class redraw when resized. */ wndclass.style = CS_HREDRAW | CS_VREDRAW; /* All windows of this class use the MainWndProc window function. */ wndclass.lpfnWndProc = MainWndProc; /* This class is used with the current program instance. */ wndclass.hInstance = hInst; /* Use standard application icon and arrow cursor */ wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION); wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor (NULL, IDC_ARROW); /* Color the background white */ wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH); /* Register the window class */ RegisterClassEx (&wndclass); /* Create a window using the window class */ hwndMain = CreateWindow ( szMainWndClass, /* Class name */

15

"Circle", /* Caption */ WS_OVERLAPPEDWINDOW, /* Style */ CW_USEDEFAULT, /* Initial x (use default) */ CW_USEDEFAULT, /* Initial y (use default) */ CW_USEDEFAULT, /* Initial x size (use default) */ CW_USEDEFAULT, /* Initial y size (use default) */ NULL, /* No parent window */ NULL, /* No menu */ hInst, /* This program instance */ NULL /* Creation parameters */ ); /* Display the window */ ShowWindow (hwndMain, nShow); UpdateWindow (hwndMain); /* The message loop */ while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg); DispatchMessage (&msg); } return msg.wParam; }

Programa 1.2 Código en C que muestra un círculo.

Figura 1.3 Círculo creado al ejecutar el código en C.

1.2.3 GKS y PHIGS. La programación gráfica basada en APIS de sistemas operativos es un paso mayor desde el nivel de hardware en términos de independencia y conveniencia de dispositivos. Sin embargo, los programas gráficos que dependen de funciones de sistema operativo no son portables en diferentes plataformas. Por ejemplo Microsoft Windows y Mac Os, ambos son sistemas operativos con interfaces de usuario gráficas (GUI). Sin embargo sus APIs son diferentes e incompatibles al nivel de llamadas de sistema.

16

Es fácil ver las ventajas de una interface estándar para programación gráfica. Ya que ésta proporcionará una capa de abstracción necesaria para dispositivos e independencia de plataforma. GKS (Graphics Kernel System) es el primer estándar internacional para Graficación por computadora. GKS (ISO 7942 1985) es un estándar para gráficos en 2D. Especifica funciones gráficas básicas independientes de las plataformas de computadora. Algunos niveles están definidos para acomodar diferentes capacidades de sistemas de hardware. Una implementación de GKS en un lenguaje de programación necesitará de una definición apropiada de sintaxis para el lenguaje. Un lenguaje cubierta es utilizado para definir el formato específico del GKS en el lenguaje de programación. El lenguaje cubierta más común para GKS es el FORTRAN. Aunque también se pueden utilizar otros lenguajes como Pascal y C. GKS-3D (ISO 8805 1988) es una extensión del GKS que soporta gráficos en 3D. GKS y GKS-3D están diseñados principalmente para dibujar objetos individuales con ciertos atributos. Son útiles para primitivas gráficas no estructuradas y estáticas, pero no soportan directamente modelos gráficos más complejos. PHIGS (Programmer’s Hierarchical Interactive Graphics System, ISO 9592 1991) es un estándar gráfico similar al GKS. PHIGS y PHIGS+ incluyen las capacidades de GKS. Tienen funcionalidades adicionales para organizaciones jerárquicas de las primitivas gráficas y de edición dinámica. El siguiente código muestra la programación en GKS en el lenguaje cubierta FORTRAN. El programa dibuja un círculo rojo usando la primitiva polilínea de GKS. Los puntos del círculo son calculados con funciones trigonométricas de alto nivel proporcionadas por FORTRAN. PROGRAM CIRCLE C C Define error file, Fortran unit number, and workstation type, C and workstation ID. C PARAMETER (IERRF=6, LUNIT=2, IWTYPE=1, IWKID=1)

17

PARAMETER (ID=121) DIMENSION XP(ID),YP(ID) C C Open GKS, open and activate a workstation. C CALL GOPKS (IERRF,IDUM) CALL GOPWK (IWKID,LUNIT,IWTYPE) CALL GACWK (IWKID) C C Define colors. C CALL GSCR(IWKID,0, 1.0, 1.0, 1.0) CALL GSCR(IWKID,1, 1.0, 0.0, 0.0) C C Draw a circle. C X0 = .5 Y0 = .5 R = .3 JL = 120 RADINC = 2.*3.1415926/REAL(JL) DO 10 J=1,JL+1 X = X0+R*COS(REAL(J)*RADINC) Y = Y0+R*SIN(REAL(J)*RADINC) XP(J) = X YP(J) = Y 10 CONTINUE CALL GSPLI(1) CALL GSPLCI(1) CALL GPL(JL+1,XP,YP) C C Deactivate and close the workstation, close GKS. C CALL GDAWK (IWKID) CALL GCLWK (IWKID) CALL GCLKS C STOP END

Programa 1.3: Código en FORTRAN que muestra un círculo.

Figura 1.4. Círculo creado al ejecutar el código en FORTRAN.

18

1.2.4 OpenGL. OpenGL es una API muy popular para gráficos en 2D/3D derivada de GL (Graphics Library) de Silicon Graphics Inc. GL es la interface de programación gráfica usada en las estaciones de trabajo de SGI. OpenGL está diseñado para ser abierto y es un estándar neutral. Está disponible virtualmente en todas las plataformas; de hecho muchos vendedores de hardware ofrecen interfaces OpenGL para sus tarjetas gráficas y dispositivos. Con más de 200 funciones, proporciona una API gráfica más poderosa que los estándares de GKS. OpenGL es una API relativamente de bajo nivel con una interface orientada al procedimiento y es posible utilizar diferentes lenguajes de programación. Existe un lenguaje FORTRAN oficial y actualmente se está desarrollando un lenguaje cubierta en Java. Sin embargo, la raíz de OpenGL es el lenguaje C. OpenGL consiste en dos librerías: GL y GLU (OpenGL Utility Library). La librería GL contiene las funciones básicas de gráficos, y la librería GLU contiene funciones de más alto nivel creadas a partir de las GL. OpenGL por sí solo no tiene funciones para construir una interfaz de usuario. Es necesario un paquete portable llamado GLUT (OpenGL Utility Toolkit) que puede ser utilizado con OpenGL para construir programas gráficos completos. El siguiente código muestra un ejemplo que dibuja un círculo en OpenGL. El programa utiliza GLUT para construir la interface de usuario y también funciones GL y GLU para construir el display. #include #include void display(void) { int i; int n = 80; float a = 2*3.1415926535/n; float x; float y; glClear(GL_COLOR_BUFFER_BIT); glColor3f(1.0,0,0); glBegin(GL_LINE_LOOP); for (i = 0; i < n; i++) {

19

x = cos(i*a); y = sin(i*a); glVertex2f(x, y); } glEnd(); glFlush(); } int main(int argc, char** argv) { glutInit(&argc, argv); glutCreateWindow("Circle"); glutDisplayFunc(display); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(-1.2, 1.2, -1.2, 1.2); glClearColor(1.0, 1.0, 1.0, 0.0); glutMainLoop(); }

Programa 1.4: Código utilizando OpenGL que muestra un círculo.

Figura 1.5. Círculo creado al ejecutar el código de OpenGL.

OpenGL como una API de 3D es mucho más capaz que el dibujar un simple circulo. A continuación se muestra un ejemplo que presenta una esfera en 3D. #include GLUquadricObj* sphere; void display(void) { glClear(GL_COLOR_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); glRotatef(0.2, 0.0, 0.0, 1.0); gluSphere(sphere, 1.8, 24, 24); glutSwapBuffers(); } void idle(void) { glutPostRedisplay(); } int main(int argc, char** argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB); glutCreateWindow("Spinning Sphere"); glutDisplayFunc(display); glMatrixMode(GL_PROJECTION);

20

glLoadIdentity(); glOrtho(-2.0, 2.0, -2.0, 2.0, -2.0, 2.0); glClearColor(1.0, 1.0, 1.0, 0.0); glColor3f(1.0, 0.5, 0.5); sphere = gluNewQuadric(); gluQuadricDrawStyle(sphere, GLU_LINE); glutIdleFunc(idle); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt(1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); glutMainLoop(); }

Programa 1.5: Código utilizando OpenGL que muestra una esfera 3D.

Figura 1.6. Esfera creada al ejecutar el código de OpenGL.

1.2.5 Java. A pesar de que OpenGL ofrece un nivel de abstracción procedimental en C, no está diseñado para acomodar directamente el modelado gráfico en el paradigma orientado a objetos. Una API de nivel alto de gráficos basados en POO puede ofrecer grandes beneficios a aplicaciones. Java 2D y Java 3D son APIs gráficas asociadas con el lenguaje de programación Java. Son APIs orientadas a objetos de alto nivel con gran capacidad de portabilidad. Java 3D está típicamente implementada en la cima de otras APIs de bajo nivel tal como OpenGL. La siguiente tabla muestra una representación típica de las capas de un sistema gráfico

21

Aplicación gráfica Java APIs

Java 3D

Java VM

OpenGL

OS Driver de Display Tarjeta Gráfica Display Tabla 1.2 Capas de los sistemas gráficos.

Java es un lenguaje de programación de múltiples propósitos, capaz de desarrollar aplicaciones robustas. En los años recientes ha obtenido gran popularidad y se ha convertido en el lenguaje de programación por defecto de una amplia gama de aplicaciones. En la actualidad no solo se usa para programación web, sino también para desarrollar aplicaciones multiplataforma en servidores, computadoras de escritorio y aparatos móviles. Un programa en Java es compilado en un formato estándar y es independiente de cualquier plataforma, es conocido como “código byte” El código byte compilado puede ser ejecutado sin tener que realizar algún cambio mientas que la computadora cuente con el Java Virtual Machine. Esta independencia hace que Java sea el lenguaje ideal para aplicaciones en internet. Java está diseñado para soportar la programación orientada a objetos, consiste en clases y la interacción e instanciación de objetos constituye una de las acciones principales de un programa en Java. Además de esto, mantiene la simplicidad, elegancia y eficiencia de su predecesor, el lenguaje C. Al mismo tiempo, que evita las deficiencias y los riesgos de C y C++. Mientras que el lenguaje en sí es muy simple, la plataforma de Java proporciona un conjunto de APIs que cubren un amplio rango de tareas y aplicaciones: de archivos I/O, gráficos, multimedia, bases de datos, red, seguridad, etc. Además de que facilita la programación GUI a través de AWT y Swing.

22

Una opción para la programación de gráficos para Java es OpenGL. Hay varios proyectos en marcha para desarrollar un lenguaje cubierta de Java para OpenGL. JOGL es la implementación de JSR 231: el lenguaje cubierta Java para OpenGL. JOGL proporciona las clases GL y GLU para encapsular las funciones en GL y GLU. Los dos componentes GLCanvas y GLJPanel proporcionan las superfices de dibujo para las llamadas a OpenGL. El GLCanvas es un componente pesado que utilizará la aceleración del hardware. El GLJPanel es un componente ligero implementado en memoria. La aceleración del hardware no está disponible para GLJPanel. A continuación se muestra un ejemplo de JOGL que es el equivalente del ejemplo visto con OpenGL. import java.awt.*; import java.awt.event.*; import javax.swing.*; import net.java.games.jogl.*; public class JOGLDemo { public static void main(String[] args) { Frame frame = new Frame("JOGL Demo"); GLCapabilities cap = new GLCapabilities(); GLCanvas canvas = GLDrawableFactory.getFactory().createGLCanvas(cap); canvas.setSize(300, 300); canvas.addGLEventListener(new Renderer()); frame.add(canvas); frame.pack(); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); frame.show(); } static class Renderer implements GLEventListener { private GL gl; private GLU glu; private GLDrawable gldrawable; public void init(GLDrawable drawable) { gl = drawable.getGL(); glu = drawable.getGLU(); this.gldrawable = drawable; gl.glMatrixMode(GL.GL_PROJECTION); gl.glLoadIdentity(); glu.gluOrtho2D(-1.2, 1.2, -1.2, 1.2); gl.glClearColor(1.0f, 1.0f, 1.0f, 0.0f); } public void display(GLDrawable drawable) { int i; int n = 80; float a = (float)(2*3.1415926535/n); float x; float y; gl.glClear(GL.GL_COLOR_BUFFER_BIT); gl.glColor3f(1.0f,0,0); gl.glBegin(GL.GL_LINE_LOOP); for (i = 0; i < n; i++) { x = (float)Math.cos(i*a); y = (float)Math.sin(i*a);

23

gl.glVertex2f(x, y); } gl.glEnd(); gl.glFlush(); } public void reshape(GLDrawable drawable, int x, int y, int width, int height) {} public void displayChanged(GLDrawable drawable, boolean modeChanged, boolean deviceChanged) {} } }

Programa 1.6: Código utilizando JOGL que muestra una esfera 3D.

1.2.5.1 Java 2D. La plataforma de Java 2 proporciona mejoras en capacidades gráficas con la introducción de las APIs de Swing y Java 2D y 3D. Estas APIs proporcionan soporte a muchas de las tareas de la graficación por computadora. Juntas han hecho muy atractiva la opción de utilizar Java para la programación gráfica. Java 2D proporciona un conjunto completo de funcionalidades para manipular y renderizar gráficos en 2D. Entre lo que se incluye: • Una jerarquía de clases para objetos geométricos. • El proceso de renderización es mucho más refinado. • Presenta mucha capacidad para el procesamiento de imágenes. • Se proporcionan modelos, tipografías y otros soportes relacionados con gráficos. La clase Graphics2D, una subclase de Graphics, es el motor de renderizdo para Java 2D. Proporciona los métodos para renderizar figuras geométricas, imágenes y texto. El proceso de renderizado puede ser controlado al seleccionar transformaciones, pinturas, propiedades de líneas, composición, fragmentos de trayectoria y otras propiedades. El siguiente código muestra un ejemplo de Java 2D que utiliza ciertas capacidades como transparencia, pintura gradual, transformación y tipografía.

24

import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.awt.font.*; import java.awt.geom.*; public class Demo2D extends JApplet{ public static void main(String args[]){ JFrame frame = new JFrame(); frame.setTitle ("Demo Java 2D"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JApplet applet = new Demo2D(); applet.init(); frame.getContentPane().add(applet); frame.pack(); frame.setVisible(true); } public void init(){ JPanel panel = new Panel2D(); getContentPane().add(panel); } } class Panel2D extends JPanel{ public Panel2D(){ setPreferredSize(new Dimension(500, 400)); setBackground(Color.white); } public void paintComponent(Graphics g){ super.paintComponent(g); Graphics2D g2 = (Graphics2D)g; //dibujar elipse Shape ellipse = new Ellipse2D.Double(150, 100, 200, 200); GradientPaint paint = new GradientPaint(100, 100, Color.white, 400, 400, Color.gray); g2.setPaint(paint); g2.fill(ellipse); //establecer transparencia AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.4f); g2.setComposite(ac); g2.setColor(Color.blue); //dibujar el texto transparente Font font = new Font("Serif", Font.BOLD, 120); g2.setFont(font); g2.drawString("Java", 120, 200); //obtener el outline del texto en glyph FontRenderContext frc = g2.getFontRenderContext(); GlyphVector gv = font.createGlyphVector(frc, "2D"); Shape glyph = gv.getOutline(150, 300); //dibujar el glyph rotado g2.rotate(Math.PI/6, 200, 300); g2.fill(glyph); } }

Programa 1.7: Código utilizando Java2D que muestra un círculo y texto.

25

Figura 1.7. Gráfico creado al ejecutar el código de Java2D..

1.2.5.2 Java 3D. Java 3D proporciona un área de trabajo para gráficos en 3D, incluyendo opciones adicionales como animación, interacción 3D y vistas sofisticadas; además proporciona una interface de programación relativamente simple e intuitiva. El paradigma de programación de Java 3D es muy diferente al de Java 2D. Un modelo abstracto conocido como escena gráfica es utilizado para organizar y retener los objetos visuales y los comportamientos en la escena virtual. La escena gráfica contiene la información completa del mundo virtual de gráficos. El motor de renderización de Java 3D renderiza automáticamente la escena gráfica; esto lo hace con un objeto Canvas 3D. Canvas 3D es un componente pesado que no funciona adecuadamente con los componentes Swing. A continuación se muestra el código de una aplicación en Java 3D. Se presenta un globo terráqueo en rotación y un cadena de texto en 3D “Java 3D” situado frente al globo. import javax.vecmath.*; import java.awt.*; import java.applet.*; import java.awt.event.*; import java.net.URL; import javax.media.j3d.*; import com.sun.j3d.utils.universe.*; import com.sun.j3d.utils.geometry.*; import com.sun.j3d.utils.image.*; import com.sun.j3d.utils.applet.MainFrame; public class Demo3D extends Applet{ public static void main(String[] args){ new MainFrame(new Demo3D(), 480, 480); } private SimpleUniverse su;

26

public void init(){ GraphicsConfiguration gc = SimpleUniverse.getPreferredConfiguration(); Canvas3D cv = new Canvas3D(gc); setLayout(new BorderLayout()); add(cv); BranchGroup bg = createSceneGraph(); bg.compile(); su = new SimpleUniverse(cv); su.getViewingPlatform().setNominalViewingTransform(); su.addBranchGraph(bg); } public void destroy(){ su.cleanup(); } private BranchGroup createSceneGraph(){ BranchGroup root = new BranchGroup(); TransformGroup spin = new TransformGroup(); spin.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); root.addChild(spin); //texto 3D Appearance ap = new Appearance(); ap.setMaterial(new Material()); Font3D font = new Font3D(new Font("Helvetica", Font.PLAIN, 1), new FontExtrusion()); Text3D text = new Text3D(font, "Java 3D"); Shape3D shape = new Shape3D(text, ap); //transformación del texto Transform3D tr = new Transform3D(); tr.setScale(0.2); tr.setTranslation(new Vector3d(-0.35, -0.15, 0.75)); TransformGroup tg = new TransformGroup(tr); root.addChild(tg); tg.addChild(shape); //globo ap = createAppearance(); spin.addChild(new Sphere(0.7f, Primitive.GENERATE_TEXTURE_COORDS, 50, ap)); //rotación Alpha alpha = new Alpha(-1, 6000); RotationInterpolator rotator = new RotationInterpolator(alpha, spin); BoundingSphere bounds = new BoundingSphere(); rotator.setSchedulingBounds(bounds); spin.addChild(rotator); //fondo y luces Background background = new Background(1.0f, 1.0f, 1.0f); background.setApplicationBounds(bounds); root.addChild(background); AmbientLight light = new AmbientLight(true, new Color3f(Color.red)); light.setInfluencingBounds(bounds); root.addChild(light); PointLight ptlight = new PointLight(new Color3f(Color.white), new Point3f(3f,3f,3f), new Point3f(1f,0f,0f)); ptlight.setInfluencingBounds(bounds); root.addChild(ptlight); return root; } private Appearance createAppearance(){ Appearance ap = new Appearance(); URL filename = getClass().getClassLoader().getResource("earth.jpg"); TextureLoader loader = new TextureLoader(filename, this); ImageComponent2D image = loader.getImage(); Texture2D texture = new Texture2D(Texture.BASE_LEVEL, Texture.RGBA, image.getWidth(), image.getHeight()); texture.setImage(0, image); texture.setEnable(true); texture.setMagFilter(Texture.BASE_LEVEL_LINEAR); texture.setMinFilter(Texture.BASE_LEVEL_LINEAR); ap.setTexture(texture); return ap; } }

Programa 1.8: Código utilizando Java3D que muestra un globo terráqueo en rotación y texto.

27

Figura 1.8. Gráfico creado al ejecutar el código de Java3D.

1.3 Campos relacionados con la Graficación. La Graficación por computadora, el procesamiento de imágenes y la visión por computadora son campos relacionados con los objetos gráficos. Son diferentes en cuanto a sus objetivos y técnicas; sin embargo, existe una relación cercana y las líneas divisorias entre ellas han ido desapareciendo con el paso de los años. El procesamiento de imágenes concierne a las técnicas de procesar imágenes digitales rasterizadas. Usualmente lidea los problemas de mejoramiento de imagen, reducción de ruido, compresión de imagen y detección de bordes. El procesamiento de imágenes toma como entrada una imagen existente y realiza acciones apropiadas en ella. La Graficación por computadora, por otro lado, genera imágenes sintéticas de un mundo virtual. El procesamiento de imágenes está cercanamente relacionado con la Graficación por computadora, el resultado del renderizado de gráficos es normalmente imágenes. Las imágenes rasterizadas son usadas comúnmente en gráficos por computadora como gráficos primitivos, además de que

son utilizadas como texturas para mejorar

el

renderizado gráfico. La visión por computadora tiene la tarea de tratar de entender las imágenes del mundo real; visto de cierta manera, un sistema de visión por computadora es lo inverso a un sistema de graficación por computadora. Su principal objetivo es

28

reconstruir un mundo virtual de imágenes de un mundo real. Por lo tanto la visión por computadora y la Graficación por computadora se complementan una con otra; ambas proporcionan diferentes perspectivas de un sistema común. La teoría y la práctica de la Graficación por computadora dependen mayormente de ciertos conceptos matemáticos importantes. Las áreas de las matemáticas que se relacionan son la geometría analítica y el algebra lineal. La geometría analítica proporciona representaciones numéricas de objetos gráficos. Y el algebra lineal estudia las operaciones y transformaciones de espacios vectoriales, los cuales son importantes en muchos problemas fundamentales de la graficación por computadora.

29

TRANSFORMACIONES GEOMÉTRICAS

2.1 Transformaciones bidimensionales. Un sistema de gráficos 2D modela un mundo virtual en dos dimensiones. Comparado con los gráficos 3D, los gráficos en 2D son más simples en cuanto a modelado y renderizado. Los objetos 2D son más fáciles de crear y manipular. El renderizado en 2D normalmente no involucra proyecciones complicadas como las de los gráficos en 3D. A pesar de que un modelo en 2D no puede capturar completamente la naturaleza de un espacio en 3D, los gráficos en 2D son aplicados debido a su simplicidad y eficiencia. Esto es un ingrediente esencial de los programas modernos basados en GUI.

30

Los conceptos claves de los gráficos 2D incluyen el renderizar por un conducto, el espacio del objeto, el espacio del mundo, el espacio del dispositivo, los sistemas de coordenadas, las primitivas gráficas, las transformaciones geométricas, los colores, fragmentos, reglas de composición y otros temas. Java 2D proporciona soporte para gráficos 2D. En los gráficos 2D, el espacio del mundo virtual y el espacio de vistas son bidimensionales. El renderizar involucra la composición de varios objetos a través de transformaciones relativas. Frecuentemente un espacio del mundo no se necesita cuando se modelan explícitamente las relaciones entre objetos gráficos. Sin embargo, para alcanzar la claridad de las estructuras de los sistemas y para mantener la analogía con los gráficos 3D, la noción de un mundo virtual es fundamental. Conceptualmente un objeto gráfico puede definirse en su propio espacio y puede colocarse en un espacio de un mundo 2D a través de una transformación del mismo. El renderizado 2D toma una foto del mundo y produce una imagen representándola en una vista particular en el espacio del dispositivo.

Transformación

Vista

Figura 2.1 Un objeto gráfico en 2D es procesado para su transformación y su vista.

Los componentes esenciales de un sistema gráfico en 2D incluyen la renderización modelo del objeto en 2D, la aplicación de transformaciones geométricas al objeto, y una vista particular del mundo virtual en un dispositivo de display. Los pasos para renderizar un gráfico en un programa de gráficos 2D son: 1. Construir los objetos en 2D. 2. Aplicar las transformaciones a los objetos. 3. Aplicar color y otras propiedades de renderizado. 4. Renderizar la escena en un dispositivo para gráficos.

31

Los objetos gráficos en el modelo son de dos dimensiones. Además de los objetos geométricos construidos de primitivas básicas como líneas, polígonos y elipses, el modelo puede incluir objetos tales como texto e imágenes. La transformación involucrada en los gráficos 2D forma parte de una Affine Transformation. Las transformaciones cambian la forma y ubicación

de los

objetos visuales a los cuales se les aplica dicha transformación. Las vistas de transformación no cambian el modelo del mundo virtual, pero cambian las vistas de la escena completa en el espacio mundo. Por ejemplo, si en un modelo virtual con un círculo y un triángulo, se aplica una traslación al círculo, (transformación de objeto) solo se moverá el círculo sin afectar el triángulo. La traslación como una transformación de vista, por otro lado, moverá la vista completa. Además de la geometría, muchos otros atributos pueden afectar el renderizado de una escena como colores, transparencia, texturizado y estilos de línea. Un sistema de gráficos 2D renderizará una escena basada en la información geométrica, transformación y el contexto gráfico involucrando todos los atributos.

2.2 Coordenadas homogéneas y representación matricial. Los componentes fundamentales en un modelo gráfico son los objetos geométricos. Para poder representarlos de manera precisa y eficiente se tiene que utilizar un sistema de coordenadas. El sistema de coordenadas más utilizado en 2D emplea las coordenadas cartesianas como se muestra en la siguiente figura:

32

Figura 2.2 Sistema de coordenadas en 2D con el ejes (x, y)

Dos ejes perpendiculares son colocados en el plano. Cada eje está etiquetado por un conjunto de números reales. El eje horizontal se denomina eje

y el vertical

eje . La intersección de los ejes se identifica con el número 0, y se denomina punto de origen. Cada punto en el plano está asociado a un par de números reales

conocidos como coordenada

y coordenada

. Las coordenadas

miden la posición horizontal y vertical de un punto relativo en los ejes. Un objeto geométrico en 2D es un conjunto de puntos en el plano. El número de puntos en el conjunto que constituye el objeto geométrico es usualmente infinito. Para representar efectivamente tal objeto, se utiliza una ecuación para definir la relación de las coordenadas

y

que un punto del objeto debe satisfacer.

Por ejemplo una línea se puede representar a través de una ecuación polinomial de grado 1 o ecuación lineal:

Figura 2.3 Línea que pude ser representada por una ecuación lineal.

33

Un círculo centrado en el origen con un radio

se representa con la siguiente

ecuación:

Una elipse centrada en ( ,

) presenta la siguiente ecuación:

Otro tipo común de ecuación que se utiliza para representar una curva es la ecuación paramétrica. En lugar de una ecuación que relacione únicamente a , se utiliza una tercera variable . Ambas

y

y

son expresadas en función de .

La ventaja de las ecuaciones paramétricas es que proporcionan formas funcionales explícitas para evaluar las coordenadas. La siguiente figura muestra una elipse que también se puede expresar utilizando la ecuación paramétrica:

Figura 2.4 Una elipse representada por una ecuación cuadrática.

La colección de todos los puntos o coordenadas también se conoce como espacio. Tres tipos de espacios están normalmente relacionados con un sistema gráfico: el espacio del objeto, el espacio del mundo, y el espacio del dispositivo.

34

Cada espacio es caracterizado por su propio sistema de coordenadas. Los objetos geométricos en un espacio pueden ser mapeados en otro a través de transformaciones. Un sistema de coordenadas de un objeto, también conocido como sistema de coordenadas de modelado o sistema de coordenadas local, está asociado con la definición de un objeto gráfico particular o primitivo. Al construir tal objeto, es conveniente elegir un sistema de coordenadas que sea natural al objeto sin preocuparse por su destino final y apariencia en el espacio mundo. Por ejemplo, cuando se define un círculo primitivo, se puede elegir el tener el origen del sistema de coordenadas en el centro del círculo o simplemente definir una unidad de círculo (un círculo de radio 1). El círculo posteriormente puede ser colocado en cualquier parte del espacio mundo a través de una transformación llamada traslación. Su radio puede cambiar a cualquier valor al efectuar una escalación. También se puede transformar en una elipse al utilizar una escalación no uniforme. El sistema de coordenadas del mundo, o sistema de coordenadas del usuario, define una referencia común de todos los objetos en un modelo gráfico. Representa el mundo virtual compartido por los subsistemas modelados y renderizados. Los objetos geométricos son colocados en este espacio a través de transformaciones de objetos. El sistema de renderización toma una foto del espacio y produce una imagen renderizada a través de un dispositivo de salida. El sistema de coordenadas del dispositivo representa el espacio del display de un dispositivo de salida tal como una pantalla o una impresora. La siguiente figura muestra un ejemplo típico de tal sistema de coordenadas. El origen está localizado en la esquina superior izquierda, las posiciones positivas del eje de las apuntan hacia la derecha y las posiciones positivas del eje de las

apuntan

hacia abajo. Los valores de las coordenadas son valores enteros. Esta opción obviamente es diferente a cualquier representación matemática, pero es más natural para la mayoría de los dispositivos de pantalla.

35

x

(0,0)

y Figura 2.5 Sistema de coordenadas de Java 2D, con el eje x aumentando hacia la derecha y el eje y hacia abajo.

Por default las coordenadas del mundo de Java 2D coinciden con las coordenadas de los dispositivos. Con las transformaciones disponibles en un sistema de gráficos, es fácil definir diferentes espacios mundo que pueden ser los más apropiados para aplicaciones particulares.

2.3 Tipos de transformaciones bidimensionales. Los objetos geométricos atraviesan un estado de transformación antes de ser renderizados. Una familia general de transformaciones geométricas comúnmente utilizadas en gráficos por computadora se denomina Affine Transformation. Una transformación afin preserva las líneas paralelas; además las que

también

preservan la distancia se conocen como isométricas, movimientos euclidean o movimientos rigidos. Las transformaciones afines más comunes son: • Traslación • Rotación • Reflección • Escalación • Sesgar Una traslación mueve todos los puntos de un objeto a una cantidad establecida. La cual está especificada por la cantidad de movimientos en las direcciones

y .

Una traslación es isométrica ya que no cambia las longitudes ni los ángulos. La

36

siguiente figura muestra la traslación de un objeto de (3, -1). Es decir el objeto es movido tres unidades a la derecha y una hacia arriba.

Figura 2.6 Una traslación (3, -1)

Una rotación mueve un objeto aproximadamente un punto por ángulo; es decir está determinado por el punto y el anglo. También es isométrico, ya que cambia la orientación de la forma. La siguiente figura muestra una rotación de 45 grados del orígen.

Figura 2.7 Una rotación sobre el origen

Una reflección mapea el objeto a su imagen espejo sobre una línea; es decir es determinado por una línea. La reflección es isométrica, ya que cambia la orientación del angulo. La siguiente figura muestra una reflección de una línea de 45 grados entre los ejes

y .

37

Figura 2.8 Una reflección sobre una línea diagonal.

La escalación redimensiona el objeto por ciertos factores fijados en las direcciones

y . La escalación no es isométrica, ya que cambia las distancias y

los ángulos. Sin embargo, preserva el paralelismo. La siguiente figura muestra una escalación de factores (1.5, 2).

Figura 2.9 Escalación por los factores (1.5, 2).

El sesgar sobre una línea es mover un punto una cantidad proporcional a la distancia indicada por la línea. Los movimientos de los puntos son paralelos a la línea; es decir, los puntos en la línea no se mueven; sin embargo los puntos en el lado opuesto de la línea se mueven en dirección opuesta. Este tipo de trasnformación no es isométrica, pero preserva el paralelismo. La siguiente figura muestra el sesgar por el factor 1.0 sobre la línea horizontal y =2

38

Figura 2.10 Sesgar por el factor 1 sobre la línea horizontal punteada.

Matemáticamente una transformación afín puede ser representada por una matriz 3 x 3. Una transformación afín requiere una matriz de 3 x 3 en lugar de una de 2 x 2, debido a que las transformaciones tales como las traslaciones no son lineales en un espacio 2D. Utilizando el concepto de coordenadas homogéneas, es posible tratar todas las transformaciones afines en un ambiente lineal al agregar una dimensión al vector de representación de los puntos. Para transformaciones básicas, es comúnmente más sencillo encontrar las matrices de transformación directamente. Una rotación del angulo θ sobre el origen es representado con la matriz:

La traslación por la cantidad

La escalación por los factores

tiene la matriz:

tiene la matriz de representación:

39

La reflección en la línea

El sesgar sobre el eje

es representada por la matriz:

por el factor

se da por la matriz:

Java 2D utiliza una clase llamada AffineTransform para definir una transformación afín. Ofrece métodos para establecer la mayoría de las transformaciones afines básicas definidas anteriormente. Los siguientes métodos de AffineTransform establecen las transformaciones nombradas: •

void setToIdentity()



void setToRotation(double theta)



void setToRotation(double theta, double x, double y)



void setToScale(double sx, double sy)



void setToShear(double shx, double shy)



void setToTranslation(double tx, double ty)

Una transformación que no se encuentra entre los métodos listados es el de reflección. Sin embargo, se puede definir una reflección al establecer su matriz. La siguiente matriz define una reflección sobre el eje :

La clase AffineTransform tiene constructores y métodos que establecen directamente las primeras dos filas de la matriz de transformacion:

40



AffineTransform(double m00, double m10, double m01, double m11, double m02, double m12)



AffineTransform(float m00, float m10, float m01, float m11, float m02, float m12)



AffineTransform(double[ ] flatmatrix)



AffineTransform(float[ ] flatmatrix)



void setTransform(double m00, double m10, double m01, double m11, double m02, double m12)

Debido a que la última fila de una matriz de transformación afin siempre es (001), se omite en la lista de parámetros. La matriz de reflección definida anteriormente puede establecerse por el siguiente método: transform.setTranform(-1, 0, 0, 1, 0, 0); Debido a que la clase de AffineTransform permite la escalación con factores negativos, la reflección también puede definirse como un tipo especial de escalación: transform.setToScale(-1, 1); Un objeto AffineTransform puede ser utilizado por ambas transformaciones: de objeto y de vista. Los siguientes métodos de la clase se aplican a la transformación de objetos geométricos. •

Shape createTransformedShape(Shape shape)



void transform(double[ ] src, int src0ff, double[ ] dst, int dst0ff, int numPts)



void transform(double[ ] src, int src0ff, float[ ] dst, int dst0ff, int numPts)



void transform(float[ ] src, int src0ff, double[ ] dst, int dst0ff, int numPts)



void transform(float[ ] src, int src0ff, float[ ] dst, int dst0ff, int numPts)



Point2D transform(Point2D src, Point2D dst)



Point2D transform(Point2D[ ] src, int src0ff, Point2D[ ] dst, int dst0ff, int numPts)



void deltaTransform(double[ ] src, int src0ff, double[ ] dst, int dst0ff, int numPts)

41



Point2D deltaTransform(Point2D src, Point2D dst)

El método createTransformedShape transforma una figura completa. Los métodos de Transform realizan una transformación de un conjunto de puntos. El método deltaTransform realiza una transformación de un conjunto de vectores. Una transformación de vista puede realizarse con la transformación en un objeto Graphics2D. La clase Graphics2D tiene los siguientes métodos para manipular sus transformaciones: •

void setTansform(AffineTransform tx)



void transform(AffineTransform tx)

El método setTransform reemplaza la transformación actual con el objeto AffineTransform dado. El método transform concatena la actual transformación con el objeto AffineTransform de la derecha. El siguiente ejemplo muestra los efectos de las transformaciones afines. El usuario puede realizar una transformación de un objeto gráfico usando el mouse. Las transformaciones affines son seleccionadas a través de un menú que incluye la traslación, rotación, escalación, sesgar y reflección. import java.awt.*; import java.awt.geom.*; import java.awt.event.*; import java.util.*; import javax.swing.*; public class Transformations extends JApplet implements ActionListener { public static void main(String s[]) { JFrame frame = new JFrame(); frame.setTitle("Affine Transforms"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JApplet applet = new Transformations(); applet.init(); frame.getContentPane().add(applet); frame.pack(); frame.setVisible(true); } TransformPanel panel = null; public void init() { JMenuBar mb = new JMenuBar(); setJMenuBar(mb); JMenu menu = new JMenu("Transforms"); mb.add(menu); JMenuItem mi = new JMenuItem("Translation"); mi.addActionListener(this); menu.add(mi); mi = new JMenuItem("Rotation");

42

mi.addActionListener(this); menu.add(mi); mi = new JMenuItem("Scaling"); mi.addActionListener(this); menu.add(mi); mi = new JMenuItem("Shearing"); mi.addActionListener(this); menu.add(mi); mi = new JMenuItem("Reflection"); mi.addActionListener(this); menu.add(mi); panel = new TransformPanel(); getContentPane().add(panel); } public void actionPerformed(ActionEvent ev) { String command = ev.getActionCommand(); if ("Translation".equals(command)) { panel.transformType = panel.TRANSLATION; } else if ("Rotation".equals(command)) { panel.transformType = panel.ROTATION; } else if ("Scaling".equals(command)) { panel.transformType = panel.SCALING; } else if ("Shearing".equals(command)) { panel.transformType = panel.SHEARING; } else if ("Reflection".equals(command)) { panel.transformType = panel.REFLECTION; } } } class TransformPanel extends JPanel implements MouseListener, MouseMotionListener { static final int NONE = 0; static final int TRANSLATION = 1; static final int ROTATION = 2; static final int SCALING = 3; static final int SHEARING = 4; static final int REFLECTION = 5; int transformType = NONE; Shape drawShape = null; Shape tempShape = null; Point p = null; int x0 = 400; int y0 = 300; public TransformPanel() { super(); setPreferredSize(new Dimension(800, 600)); setBackground(Color.white); drawShape = new Rectangle(-50,-50,100,100); addMouseListener(this); addMouseMotionListener(this); } public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D)g; g2.translate(x0, y0); g2.drawLine(-200,0,200,0); g2.drawLine(0,-200,0,200); g2.draw(drawShape); } public void mouseClicked(MouseEvent ev) { } public void mouseEntered(MouseEvent ev) { } public void mouseExited(MouseEvent ev) { }

43

public void mousePressed(MouseEvent ev) { p = ev.getPoint(); } public void mouseReleased(MouseEvent ev) { Graphics g = getGraphics(); Point p1 = ev.getPoint(); AffineTransform tr = new AffineTransform(); switch (transformType) { case TRANSLATION: tr.setToTranslation(p1.x-p.x, p1.y-p.y); break; case ROTATION: double a = Math.atan2(p1.y-y0, p1.x-x0) - Math.atan2(p.y-y0, p.x-x0); tr.setToRotation(a); break; case SCALING: double sx = Math.abs((double)(p1.x-x0)/(p.x-x0)); double sy = Math.abs((double)(p1.y-y0)/(p.y-y0)); tr.setToScale(sx, sy); break; case SHEARING: double shx = ((double)(p1.x-x0)/(p.x-x0))-1; double shy = ((double)(p1.y-y0)/(p.y-y0))-1; tr.setToShear(shx, shy); break; case REFLECTION: tr.setTransform(-1,0,0,1,0,0); break; } drawShape = tr.createTransformedShape(drawShape); repaint(); } public void mouseMoved(MouseEvent ev) { } public void mouseDragged(MouseEvent ev) { Point p1 = ev.getPoint(); AffineTransform tr = new AffineTransform(); switch (transformType) { case TRANSLATION: tr.setToTranslation(p1.x-p.x, p1.y-p.y); break; case ROTATION: double a = Math.atan2(p1.y-y0, p1.x-x0) - Math.atan2(p.y-y0, p.x-x0); tr.setToRotation(a); break; case SCALING: double sx = Math.abs((double)(p1.x-x0)/(p.x-x0)); double sy = Math.abs((double)(p1.y-y0)/(p.y-y0)); tr.setToScale(sx, sy); break; case SHEARING: double shx = ((double)(p1.x-x0)/(p.x-x0))-1; double shy = ((double)(p1.y-y0)/(p.y-y0))-1; tr.setToShear(shx, shy); break; case REFLECTION: tr.setTransform(-1,0,0,1,0,0); break; } Graphics2D g = (Graphics2D)getGraphics(); g.setXORMode(Color.white); g.translate(x0, y0); if (tempShape != null) g.draw(tempShape); tempShape = tr.createTransformedShape(drawShape); g.draw(tempShape); } }

Programa 2.1: Código que muestra efectos de las transformaciones afines.

44

Figura 2.11 Salida de la ejecución del programa 2.1.

2.4 Composición de transformaciones bidimensionales. Las transformaciones pueden combinarse para formar nuevas transformaciones. Por ejemplo, se puede aplicar una traslación seguida de una rotación y esta a su vez seguida por otra traslación. Cualquier composición de transformaciones afines sigue siendo una transformación afin. Cualquier composición de movimiento rígido sigue siendo un movimiento rígido. Por lo contrario, una transformación puede descomponerse en una serie (normalmente más simples) de transformaciones. La matriz de transformación de una transformación compuesta es el producto de las matrices de las transformaciones individuales. Por ejemplo, si matrices de las transformaciones afines matriz de la composición

, es

son

, respectivamente, entonces la . Cabe mencionar que la

operación de la composición de la transformación es no conmutativa, por lo que el orden al aplicar las transformaciones es significativo. En esta notación, las transformaciones de una transformación compuesta son aplicadas de derecha a izquierda. Por ejemplo, cuando una transformación compuesta aplicada a un punto , el orden de las transformaciones es

es

:

45

Las transformaciones compuestas son utilies cuando se desean construir transformaciones complejas a partir de transformaciones simples. Si se necesita una rotación sobre el punto (3, 4) a 30 grados, primero se realiza la traslación para mover el punto (3, 4) del origen. Después se realiza una rotación de 30 grados sobre el origen. Finalmente se puede trasladar el origen de regreso al punto (3, 4). Al combinar las tres tranformaciones, obtendremos la transformación requerida. En una matriz, la traslación que mueve de (3, 4) al origen está dada por: 1

0

-3

0

1

-4

0

0

1

La rotación de 30 grados sobre el origen: √3/2

-1/2

0

1/2

√3/2

0

0

0

1

La segunda traslación sería: 1

0

3

0

1

4

0

0

1

Combinando las tres transformaciones, la rotación final tendría la matriz de transformación:

1

0

3

√3/2

-1/2

0

1

0

-3

0

1

4

½

√3/2

0

0

1

-4

0

0

1

0

0

1

0

0

1

En Java 2D, la clase AffineTransform proporciona los siguientes métodos que soportan las tranformaciones compuestas:

46



void rotate(double theta)



void rotate(double theta, double x, double y)



void scale(double sx, double sy)



void shear(double shx, double shy)



void translate(double tx, double ty)

Estos métodos no limpian las transformaciones existentes en los objetos actuales, sino que combinan las transformaciones actuales con las nuevas especificadas. Las nuevas transformaciones son añadidas a la derecha de las actuales. Además de

las

transformaciones

simples

listadas,

es

posible

combinar

las

transformaciones actuales con otro objeto de AffineTransform: •

void concatenate(AffineTransform tx)



void preConcatenate(AffineTransform tx)

El primer método concatena la segunda transformación a la derecha de la actual. El segundo método concatena la segunda transformación a la izquierda de la actual. Cabe notar que el orden de la composición de la transformación es de izquierda a derecha, y los métodos anteriores, excepto el de preConcatenate, concatena la transformación a la derecha. Si se crea una transformación de composición al llamar a los métodos mencionados anteriormente, las transformaciones son aplicadas en el orden opuesto a la secuencia del llamado. Por ejemplo: AffineTransform transform = new AffineTransform(); transform.rotate(Math.PI/3); transform.scale(2, 0.3); transform.translate(100, 200); La primera transformación a aplicar es la traslación y la última es la rotación. El siguiente código muestra el uso de las composiciones en transformaciones. El ejemplo trata de rotar una elipse sobre su centro que no está localizado en el origen, por lo que lo primero que traslada el objeto al origen, luego lo rota en el origen y finalmente trasladar la elipse rotada a su punto original.

47

import javax.swing.*; import java.awt.*; import java.awt.geom.*; public class Composition extends JApplet { public static void main(String s[]) { JFrame frame = new JFrame(); frame.setTitle("Transformation Composition"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JApplet applet = new Composition(); applet.init(); frame.getContentPane().add(applet); frame.pack(); frame.setVisible(true); } public void init() { JPanel panel = new CompositionPanel(); getContentPane().add(panel); } } class CompositionPanel extends JPanel { public CompositionPanel() { setPreferredSize(new Dimension(640, 480)); this.setBackground(Color.white); } public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D)g; g2.translate(100,100); Shape e = new Ellipse2D.Double(300, 200, 200, 100); g2.setColor(new Color(160,160,160)); g2.fill(e); AffineTransform transform = new AffineTransform(); transform.translate(-400,-250); e = transform.createTransformedShape(e); g2.setColor(new Color(220,220,220)); g2.fill(e); g2.setColor(Color.black); g2.drawLine(0, 0, 150, 0); g2.drawLine(0, 0, 0, 150); transform.setToRotation(Math.PI / 6.0); e = transform.createTransformedShape(e); g2.setColor(new Color(100,100,100)); g2.draw(e); transform.setToTranslation(400, 250); e = transform.createTransformedShape(e); g2.setColor(new Color(0,0,0)); g2.draw(e); } }

Programa 2.2: Código que aplica la composición de transformaciones.

Figura 2.12 Salida de la ejecución del programa 2.2.

48

2.5 Transformaciones de la composición general. Las reglas de composición determinan los resultados de objetos renderizados superpuestos. Al elegir reglas de composición se pueden obtener varios efectos visuales, como diferentes grados de transparencia. Para establecer las reglas de composición, el concepto de un canal necesario. El canal

es

puede ser visto como parte de las propiedades del color que

especifican la transparencia. Un valor

es un número entre 0.0 y 1.0, siendo el

0.0 la transparencia completa y el 1.0 la opacidad completa. Dando la fuente y destino del pixel de color y los valores , las reglas Porter-Duff definen el resultado del color y los valores

como una combinación lineal de la

fuente y los valores de destino:

Frecuentemente los components de color deben tener valores

premultiplicados

para acelerar el cálculo. Las diferentes elecciones de los dos coeficientes

y

en la equación definen las diferentes reglas de composición. Existen 12 reglas Porter –Duff, que tienen los coeficientes mostrados en la tabla 2.1. Las reglas Porter-Duff pueden derivarse sistemáticamente de un modelo probabilístico. El valor

de un color puede ser interpretado como la probabilidad

de que el color se mostrará, o más concretamente como la porción del área del pixel cubierto por un color específico. Al combinar el código y los colores destinos con sus respectivos valores , se deben considerar cuatro diferentes casos: solo se presenta el color fuente, solo se presenta el color destino, ambos colores se presentan o ningún color se presenta. La figura 2.13 muestra los cuatro eventos, los cuales ocurren con una probabilidad de d),

s(1-

d),

d(1-

),

s

d,

y (1-

s)(1-

respectivamente. Una regla de composición simplemente decide si retiene el

color cuando este ocurra. En el evento de solo código fuente, una regla puede

49

elegir el matener el color fuente u omitirlo. En el evento solo color destino, el color destino puede ser seleccionado u omitido, en el evento de ambos colores, una regla puede elegir el color fuente, el color destino o ninguno de ellos. En el evento ningún color, una regla solo puede no elegir color. Por lo tanto el número total de reglas basado en este modelo es 2 x 2 x 3 x 1 = 12. Ninguno

Fuente

Ambos Destino

Figura 2.13 Cuatro eventos diferentes de color que pueden ocurrir en el modelo probabilístico de composición.

Por ejemplo, la regla SrcOver elige el color fuente en el evento de solo color fuente y el evento de ambos colores. Elige el color destino en el evento solo color destino. No debe elegir ningún color en el evento de ninguno. Por consecuencia la probabilidad de que ocurra el color fuente en el color es probabilidad del color destino es

d(1-

s).

s(1-

d),

+

s

d

=

s

y la

Esto lleva a la selección del coeficiente

como se muestra en la tabla: Regla Porter-Duff Clear

0

SrcOver

1

DstOver

1-

SrcIn DstIn SrcOut

0 1d

1 0

d

0 1-

s

s d

0

DstOut

0

1-

Src

1

0

Dst

0

1

s

50

SrcAtop

1-

d

DstAtop

1-

d

Xor

1-

d

s s

1-

s

Tabla 2.1 Las 12 reglas de composición Porter –Duff

En las versiones anteriores de Java 2D se soportan las primeras ocho reglas. A partir de J2SDK 1.4, las 12 reglas son soportadas. La clase AlphaComposite encapsula dichas reglas. Una instancia para una regla de AlphaComposite puede obtenerse por un campo estático de AlphaComposite con el nombre mostrado en la tabla. Para aplicar las reglas de composición un objeto Graphics2D siemplemente llama al método setComposite. Por ejemplo, los siguientes comandos establecen una regla de composición de SrcIn: Graphics2D g2 = (Graphics2D)g; g2.setComposite(AlphaComposite.SrcIn); El siguiente código muestra una aplicación de la clase AlphaComposite que implementa las reglas Porter-Duff. El ejemplo muestra ciertos objetos visuales renderizados con las 12 reglas de composición. Las reglas son seleccionadas al dar click con el mouse en el panel de display. import java.awt.*; import java.awt.event.*; import java.awt.image.*; import javax.swing.*; import java.awt.font.*; import java.awt.geom.*; import java.io.*; import java.net.URL; import javax.imageio.*; public class Compositing extends JApplet { public static void main(String s[]) { JFrame frame = new JFrame(); frame.setTitle("Compositing Rules"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JApplet applet = new Compositing(); applet.init(); frame.getContentPane().add(applet); frame.pack(); frame.setVisible(true); } public void init() { JPanel panel = new CompositPanel(); getContentPane().add(panel); } } class CompositPanel extends JPanel implements MouseListener { BufferedImage image;

51

int[] rules = {AlphaComposite.CLEAR, AlphaComposite.SRC_OVER, AlphaComposite.DST_OVER, AlphaComposite.SRC_IN, AlphaComposite.DST_IN, AlphaComposite.SRC_OUT, AlphaComposite.DST_OUT, AlphaComposite.SRC, AlphaComposite.DST, AlphaComposite.SRC_ATOP, AlphaComposite.DST_ATOP, AlphaComposite.XOR}; int ruleIndex = 0; public CompositPanel() { setPreferredSize(new Dimension(500, 400)); setBackground(Color.white); URL url = getClass().getClassLoader().getResource("images/earth.jpg"); try { image = ImageIO.read(url); } catch (IOException ex) { ex.printStackTrace(); } addMouseListener(this); } public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D)g; g2.drawImage(image, 100, 100, this); AlphaComposite ac = AlphaComposite.getInstance(rules[ruleIndex], 0.4f); g2.setComposite(ac); Shape ellipse = new Ellipse2D.Double(50, 50, 120, 120); g2.setColor(Color.red); g2.fill(ellipse); g2.setColor(Color.orange); Font font = new Font("Serif", Font.BOLD, 144); g2.setFont(font); g2.drawString("Java", 90, 240); } public void mouseClicked(MouseEvent e) { ruleIndex++; ruleIndex %= 12; repaint(); } public void mousePressed(MouseEvent e) { } public void mouseReleased(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } }

Programa 2.3: Objetos renderizados con las 12 reglas de composición.

Figura 2.14 Salida de la ejecución del programa 2.3.

52

2.6 Transformación ventana-área de vista. Una trayectoria de recorte define una región en la cual los objetos pueden ser visibles. Graphics2D mantiene una región actual para el recorte. Cuando se dibuja objeto, este es entrecortado por la trayectoria de recorte. Las porciones del objeto que se encuentran fuera de la trayectoria no se dibujarán. El siguiente segmento de código muestra una elipse como la forma del recorte y dibuja una imagen; solo la porción de la imagen que se encuentra dentro de la elipse será visible. Graphics2D g2 = (Graphics2D)g; Shape ellipse = new Ellipse2D.Double(0,0,300,200); g2.setClip(ellipse); g2.drawImage(image, 0, 0, this); Otro metodo de Graphics2D que cambia la región de recorte es: •

void clip(Shape path)

El método cortará la región actual de recorte con la figura especificada. El siguiente ejemplo demuestra el uso de la trayectoria de recorte. En el programa se crea una figura y la cual es utilizada como la trayectoria de recorte para un objeto Graphics2D. El dibujo es entrecortado por la figura. import java.awt.*; import javax.swing.*; import java.awt.geom.*; public class TestClip extends JApplet { public static void main(String s[]) { JFrame frame = new JFrame(); frame.setTitle("Clip Path"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JApplet applet = new TestClip(); applet.init(); frame.getContentPane().add(applet); frame.pack(); frame.setVisible(true); } public void init() { JPanel panel = new ClipPanel(); getContentPane().add(panel); } } class ClipPanel extends JPanel { public ClipPanel() { setPreferredSize(new Dimension(500, 500)); setBackground(Color.white); }

53

public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D)g; GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD); path.moveTo(100,200); path.quadTo(250, 50, 400, 200); path.lineTo(400,400); path.quadTo(250,250,100,400); path.closePath(); g2.clip(path); g2.setColor(new Color(200,200,200)); g2.fill(path); g2.setColor(Color.black); g2.setFont(new Font("Serif", Font.BOLD, 60)); g2.drawString("Clip Path Demo",80,200); g2.drawOval(50, 250, 400, 100); } }

Programa 2.4: Código que presenta un ejemplo de trayectoria de recorte.

Figura 2.15 Salida de la ejecución del programa 2.4.

2.7 Representación matricial de transformaciones tridimensionales. Las transformaciones juegan un papel importante en los gráficos por computadora. A través de transformaciones geométricas apropiadas, los objetos gráficos pueden cambiar en forma, tamaño y ubicación. Las transformaciones afines son utilizadas frecuentemente en el modelado del mundo virtual. Un tipo especial de transformación es la perspectiva o proyección ortogonal, que comúnmente se utiliza en las vistas para constuir el mapeo de un espacio 3D a una imagen 2D. La animación frecuentemente involucra la aplicación de transformaciones geométricas para alcanzar los movimientos en una escena. Java 3D proporciona soporte para

transformaciones en diversos niveles. Las

clases de matrices tal como Matrix4d ofrecen la representación de datos de bajo

54

nivel

de

las

transformaciones.

La

clase

Transform3D

encapsula

las

transformaciones 3D con las facilidades necesarias para establecer y componer las transormaciones y para aplicarlas a puntos y vectores. La clase TransformGroup representa el nivel alto de los nodos de transformación en las gráficas de la escena. Con el uso de las coordenadas homogenas, todas las transformaciones proyectivas en 3D (incluyendo las transformaciones affines) están completamente determinadas por transformaciones matriciales 4 x 4. Las transformaciones con interpretaciones geométricas directas tales como las rotaciones y las traslaciones pueden ser expresadas con matrices. Sin embargo, la construcción de matrices explícitas de ciertas transformaciones tales como rotaciones generales en 3D pueden ser complicadas. Otras representaciones, como la de un vector de cuatro dimensiones para presentar una rotación 3D, pueden proporcionar formas intermedias más convenientes para construcciones y manipulaciones de objetos. Las transformaciones pueden combinarse para formar nuevas transformaciones. Una manera poderosa para construir y manipular transformaciones complejas es la composición de transformaciones simples. En Java 3D la composición puede ocurrir en el nivel mas bajo de una matriz o de un objeto Transform3D a través de la multiplicación de las transformaciones. Aunque también puede ocurrir en un nivel alto de una escena gráfica, donde la cadena de los nodos de transformaciones crea el efecto de una transformación compuesta. La aplicación principal de las transformaciones es cambiar las características geométricas de los objetos. En Java 3D los nodos de TransformGroup representan las transformaciones y aplican a su hijo la transformación definida en el nodo. Otra aplicación de las transformaciones es el ayudar a la construcción de figuras geometricas. La clase Transform3D ofrece métodos convenientes para aplicar los puntos o vectores de transformación. Para construir figuras geometricas que tienen diversas propiedades simétricas, se puede tomar la ventaja de las capacidades de la transformación al generar muchos puntos de la figura a través de transformaciones de un conjunto de puntos base.

55

2.7.1 Transformaciones tridimensionales. Una transformación es un mapeo de un espacio de un vector a si mismo o a otro espacio de vector. Ciertas familias de transformaciones que preservan algunas propiedades goemétricas tienen un significado muy especial para la graficación por computadora. Por ejemplo, las transformaciones proyectivas son cruciales en las vistas en 3D. Las transformaciones afines, son un subconjunto de las transformaciones proyectivas, y son utilizadas extensamente en el modelado visual de los objetos. Una transformación afin mapea líneas a líneas y preserva el paralelismo. Por ejemplo, una transformación afin puede no mapear un rectagulo a un rectángulo, pero siempre mapeará un rectángulo a un paralelograma. Así como en el caso de 2D,

las

transformaciones

afines

3D

incluyen

traslaciones,

rotaciones,

escalaciones, sesgaciones y reflecciones. Sin embargo, las versiones de las trasnformaciones en 3D son frecuentemente más complejas, especialmente las rotaciones. Las transformaciones afines que además preservan las distancias son conocidas como movimientos rígidos, movimientos Euclidean o movimientos isométricos. Las traslaciones, rotaciones y reflecciones son ejemplos de movimientos rígidos.

2.7.2 Matriz de transformación.

Las transformaciones afines pueden representarse en forma de matriz. Si un punto en un espacio en 3D es representado por tres coordenadas:

Entonces la transformación afin puede ser representada como una equación matriz:

56

Las rotaciones, escalaciones y sesgaciones relativas al origen pueden ser representados por la matriz de multiplicación sola. La adición de la columna vector representa una traslación. Debido a que se requiere de un tratamiento especial para las traslaciones, la transformación descrita arriba no es lineal en el espacio . Sin embargo, al utilizar coordenadas homogéneas para representar puntos 3D, todas las transformaciones afines y las transformaciones proyectivas pueden ser

representadas

como

matriz

de

multiplicaciones.

homogéneas de un punto 3D tiene cuatro componentes: es 0, corresponde a una coordenada regular 3D

Las

coordenadas . Cuando

no

. Con las

coordenadas homogéneas, las mismas transformaciones afines dadas en la parte superior pueden ser representadas equivalentemente como una transformación lineal en el espacio

.

La matriz de transformación 4 x 4 en la equación determina completamente una transformación afin. Las operaciones involucradas en la transformación pueden ser representadas como su correspondiente matriz de operaciones. El combinar diversas transformaciones afines produce otra transformación afin. La matriz de una transformación de composición es el producto de las matrices de trasnformaciones correspondientes. La matriz inversa de una transformación corresponde a la misma transformación inversa. Una trasformación proyectiva puede ser representada con una equación de matriz similar a:

Java 3D ofrece diversas clases que soportan las transformaciones. Además de clases de vector, el paquete javax.vecmath contiene clases de matrices que

57

representan matrices de 3 x 3, y de 4 x 4, y matrices generales como: Matrix3f, Matrix3d, Matrix4f, Matrix4d, GMatrix. El siguiente segmento de código contruye un objeto Matrix4d: double[ ] array = { 1.0, 2.0, 3.0, 1.0, 0.0, 1.0, -1.0, 2.0, 4.0, 0.0, 0.5, -1.0, 0.0, 0.0, 0.0, 1.0 }; Matrix4d matrix = new Matrix4d(array); La clase GMatrix puede ser utilizada para representar una matriz de tamaños arbitrarios con elementos del tipo double. Por ejemplo el siguiente código crea una matriz de 3 x 4: double[ ] array = {1.0, 2.0, 3.0, 1.0, 0.0, 1.0, -1.0, 2.0, 4.0, 0.0, 0.5, -1.0}; GMatrix matrix = new GMatrix(3, 4, array); Las operaciones básicasde las matrices tales como la suma, multiplicación, e inversión son posibles de realizar. La siguiente es una lista parcial de métodos de la clase Matrix4d para operaciones con matrices. Otras clases contienen métodos similares: • void add(Matrix4d m1): suma una matriz m1 a la matriz actual. • void sub(Matrix4d m1): resta una matriz m1 a la matriz actual. • void mul(Matrix4d m1): multiplica una matriz m1 a la matriz actual. • void invert(): invierte la matriz actual. • void add(Matrix4d m1, Matrix4d m2): establece la matriz actual a la suma de las matrices m1 y m2. • void sub(Matrix4d m1, Matrix4d m2): establece la matriz actual a la resta de las matrices m1 y m2. • void mul(Matrix4d m1, Matrix4d m2): establece la matriz actual a la multiplicación de las matrices m1 y m2. • void invert(Matrix4d m1): establece la matriz actual con la inversión de la matriz m1.

58

• void transpose(): traspone la matriz actual. • void mul(doublé scalar): multiplica la matriz actual por el numero dado en scalar. • double determinant(): regresa el determinante de la matriz actual. El siguiente código proporciona una clase que despliega una matriz. import java.awt.*; import javax.vecmath.*; public class MatrixPanel extends Panel { TextField[] fields = new TextField[16]; public MatrixPanel() { setLayout(new GridLayout(4, 4)); for (int i = 0; i < 16; i++) { fields[i] = new TextField(5); if (i/4 == i%4) fields[i].setText("1"); else fields[i].setText("0"); add(fields[i]); } } public MatrixPanel(Matrix4d m) { setLayout(new GridLayout(4, 4)); for (int i = 0; i < 16; i++) { fields[i] = new TextField(5); fields[i].setText("" + m.getElement(i/4, i%4)); add(fields[i]); } } public void set(Matrix4d m) { for (int i = 0; i < 16; i++) { fields[i].setText("" + m.getElement(i/4, i%4)); } } public void get(Matrix4d m) { for (int i = 0; i < 16; i++) { m.setElement(i/4, i%4, Double.parseDouble(fields[i].getText())); } } }

Programa 2.5: Código que proporciona una clase que despliega una matriz.

El siguiente código proporciona al usuario la vista de la matriz del programa 2.5. El usuario puede realizar ciertas operaciones con la matriz utilizando los métodos dados por la clase Matrix4d. import java.awt.*; import java.awt.event.*; import javax.vecmath.*; import java.applet.*; import com.sun.j3d.utils.applet.MainFrame; public class TestMatrix extends Applet implements ActionListener { public static void main(String[] args) { new MainFrame(new TestMatrix(), 600, 200); }

59

MatrixPanel mp; Matrix4d m = new Matrix4d(); TextField tf; public void init() { this.setLayout(new BorderLayout()); mp = new MatrixPanel(); add(mp, BorderLayout.CENTER); Panel p = new Panel(); p.setLayout(new GridLayout(6,1)); Button button = new Button("Identity"); button.addActionListener(this); p.add(button); button = new Button("Zero"); button.addActionListener(this); p.add(button); button = new Button("Negate"); button.addActionListener(this); p.add(button); button = new Button("Transpose"); button.addActionListener(this); p.add(button); button = new Button("Invert"); button.addActionListener(this); p.add(button); button = new Button("Determinant"); button.addActionListener(this); p.add(button); this.add(p, BorderLayout.EAST); tf = new TextField(); add(tf, BorderLayout.SOUTH); } public void actionPerformed(ActionEvent e) { String cmd = e.getActionCommand(); if ("Identity".equals(cmd)) { mp.get(m); m.setIdentity(); mp.set(m); } else if ("Zero".equals(cmd)) { mp.get(m); m.setZero(); mp.set(m); } else if ("Negate".equals(cmd)) { mp.get(m); m.negate(); mp.set(m); } else if ("Transpose".equals(cmd)) { mp.get(m); m.transpose(); mp.set(m); } else if ("Invert".equals(cmd)) { mp.get(m); m.invert(); mp.set(m); } else if ("Determinant".equals(cmd)) { mp.get(m); tf.setText("" + m.determinant()); } } }

Programa 2.6: Código que proporciona una interface para visualizar una matriz y realizar diversas operaciones sobre ella.

60

Figura 2.16 Salida de la ejecución del programa 2.6.

2.7.3 Tipos de transformaciones tridimensionales. Java 3D incluye la clase Transform3D que representa las transformaciones afines 3D o transformaciones proyectivas. Transform3D internamente mantiene una matriz double 4 x 4 para realizar una transformación. Para crear un objeto Transform3D se debe utilizar cualquiera de sus constructores disponibles, el constructor por default crea una matriz de identidad; aunque también se puede administrar un objeto matriz, un arreglo u otra forma de matriz de transformación. Por ejemplo el siguiente código presenta tres objetos Transform3D y sus transformaciones equivalentes: double[ ] array = {1.0, 2.0, 3.0, 1.0, 0.0, 1.0, -1.0, 2.0, 4.0, 0.0, 0.5, -1.0, 0.0, 0.0, 0.0, 1.0}; Matrix4d matrix = new Matrix4d(array); GMatrix gmatrix = new GMatrix(4, 4, array); Transform3D transform1 = new Transform3D(matrix); Transform3D transform2 = new Transform3D(gmatrix); Transform3D transform3 = new Transform3D(array); Transform3D contiene un gran número de métodos que establecen y manipulan la tansformación. Algunos de los métodos que directamente manejan la matriz de transformación son los siguientes: • void set(Matrix4d m1): establece la matriz de transformación a m1. • void set(Matrix4f m1): establece la matriz de transformación a m1.

61

• void set(GMatrix m1): establece la matriz de transformación a m1. • void set(double[ ] array): establece la matriz de transformación al arreglo. • void set(float[ ] array): establece la matriz de transformación al arreglo. • void get(Matrix4d m1): obtiene la matriz de transformación a m1. • void get(Matrix4f m1): obtiene la matriz de transformación a m1. • void get(GMatrix m1): obtiene la matriz de transformación a m1. • void get(double[ ] array): obtiene la matriz de transformación al arreglo. • void get(float[ ] array): obtiene la matriz de transformación al arreglo. Las transformaciones afines 3D más comunes son: • Traslación • Escalación • Reflección • Sesgar • Rotación Una traslación puede ser representada por la matriz de transformación:

Debajo de la traslación, cada punto se mueve por las cantidades constantes en las direcciones

, respectivamente. La forma y orientación de la

figura geométrica no cambiará debido a la traslación. El inverso de una traslación por

es una traslación por

.

Transform3D incluye los siguientes métodos para establecer traslaciones: • void set(Vector3d trans) • void set(Vector3f trans) • void setTraslation(Vector3d trans) • void setTranslation(Vector3f trans)

62

El conjunto de métodos reemplazan la transformación completa con la traslación especificada. Los métodos setTranslation modifican solo los componentes de la traslación de la transformación existente. Una escalación por los factores

en las direcciones

tiene la

siguiente matriz de representación:

Si todos los factores de escalación no son ceros, la transformación de escalación es invertible. La matriz inversa también es una escalación con los factores . Una escalación es uniforme si

.

Transform3D incluye los siguientes métodos para establecer las escalaciones: • void set(double scale) • void setScale(double scale) • void setScale(Vector3d scales) El conjunto de métodos reemplazan la transformación completa con una escalación. Los métodos setScale modifican solo los componentes a escalar de la transformación existente. Una reflección 3D es realizada sobre un plano. Una simple reflección sobre el plano

, por ejemplo, tendría la siguiente matriz:

63

Una reflección siempre es inversible, y la inversión de una reflección es la reflección misma. Una reflección sobre un plano a través del origen con el vector normal

puede expresarse como:

Una matriz de representación puede derivarse de su equación. Una sesgación 3D mueve un punto a lo largo del plano, y la cantidad del movimiento depende de la distancia del punto al plano. Una sesgación simple que solo cambia las coordenadas

Las coordenadas coordenadas

y

presenta la siguiente matriz:

no son modificadas al realizar esta transformación. Las

son movidas acorde a la ecuación:

Una sesgación más general

mueve a ambas coordenadas y se representa

como:

La sesgación es inversible. La inversión de la sesgación presentada en la matriz de arriba es otra sesgación del mismo tipo con parámetros

.

La rotación en 3D es una operación compleja. Una rotación general en 3D tiene un eje de rotación que puede ser cualquier línea en el espacio. Alrededor del eje todos los puntos son rotados por un angulo fijo. Una rotación del angulo eje

sobe el

puede ser representado por la siguiente matriz:

64

A pesar de que cualquier rotación puede ser representada como una matriz de transformación, es difícil obtener la matriz de transformación directamente de las especificaciones geométricas de una rotación general. Por ejemplo, puede ser fácil el obtener matrices para rotaciones sobre los ejes , matriz de rotación del ángulo

o ; pero ¿cuál sería la

sobre los ejes (1, 1, 1)? Una representación de

una rotación general en 3D que ofrece una conección más directa a sus propiedades geométricas involucra el uso de cuaterniones. El cuaternión es un sistema numérico que extiende los campos de números complejos. Un punto

en un espacio 3D utilizando un cuaternión puro (cuaternión con

parte real 0) se representa como: Siendo

un cuaternión fijo. Una transformación en el espacio 3D puede estár

definida por:

Si

es una unidad de cuaternión, entonces se puede ver que la transformación

definida arriba es una rotación. En este caso

La unidad vector

puede ser representado por:

define la dirección del eje de rotación, y el eje pasa a través

del origen. El ángulo de rotación está dado por . Java 3D incluye las clases Quat4f y Quat4d para representar cuaterniones. Además de las operaciones inherentes de las clases Tuple4*, las operaciones estándares para cuaterniones tales como conjugar, multiplicar, invertir y normalizar son soportadas en estas clases. Debido a la conección con rotaciones

65

dichas clases soportan además métodos convenientes para directamente establecer una rotación de cuaternión basadas en un eje y ángulo especificado. • void set(AxisAngle4d r) La clase Transform3D proporciona constructores y métodos para aceptar directamente parámetros de cuaterniones como especificaciones de rotación: • Transform3D(Quat4d q, Vector3d translation, double scale) • Transform3D(Quat4f q, Vector3d translation, double scale) • Transform3D(Quat4f q, Vector3f translation, float scale) • void set(Quat4d q) • void set(Quat4f q) Otra manera de representar rotaciones en 3D es el usar tres rotaciones de ciertos ángulos sobre las coordenadas ejes. El eje para ejes

y

es diferente de los

. Los tres angulos son conocidos como los ángulos Euler. La clase

Transform3D proporciona los métodos para establecer la rotación basada en los ángulos Euler. • void setEuler(Vector3d eulerAngles) El objeto Vector3d especifica los ángulos Euler sobre los ejes

,

y

. Los

ángulos son conocidos como elevación, azimut e inclinación. ****banco, altitud y cabecera. Sin embargo, no existe ningún método en Transform3D para recuperar los angulos Euler. Se puede utilizar un método get para recuperar un cuaternión y después convertir lo en ángulos Euler. El siguiente código muestra una conversión de un cuaternión a los ángulos Euler: public static Vector3d quat2Euler(Quat4d q1) { double sqw = q1.w*q1.w; double sqx = q1.x*q1.x; double sqy = q1.y*q1.y; double sqz = q1.z*q1.z; double heading = Math.atan2(2.0 * (q1.x*q1.y + q1.z*q1.w),(sqx - sqy - sqz + sqw)); double bank = Math.atan2(2.0 * (q1.y*q1.z + q1.x*q1.w),(-sqx - sqy + sqz + sqw)); double attitude = Math.asin(-2.0 * (q1.x*q1.z - q1.y*q1.w)); return new Vector3d(bank, attitude, heading); }

Programa 2.7: Código que muestra la conversión de un cuaternión a los ángulos Euler.

66

Transform3D contiene métodos similares a los de la clase Matrix4d para las operaciones de la matriz de transformación. También permite la aplicación directa de la transformación a puntos o vectores. Esta característica facilita la construcción de geometrías a través de transformaciones. El siguiente código proporciona una clase que despliega un conjunto de coordenadas. import javax.vecmath.*; import java.awt.*; import java.awt.event.*; import javax.media.j3d.*; import com.sun.j3d.utils.universe.*; import com.sun.j3d.utils.geometry.*; public class Axes extends Group { public Axes() { Appearance ap = new Appearance(); ap.setMaterial(new Material()); Font3D font = new Font3D(new Font("SanSerif", Font.PLAIN, 1), new FontExtrusion()); Text3D x = new Text3D(font, "x"); Shape3D xShape = new Shape3D(x, ap); Text3D y = new Text3D(font, "y"); Shape3D yShape = new Shape3D(y, ap); Text3D z = new Text3D(font, "z"); Shape3D zShape = new Shape3D(z, ap); // transform for texts Transform3D tTr = new Transform3D(); tTr.setTranslation(new Vector3d(-0.12, 0.6, -0.04)); tTr.setScale(0.5); // transform for arrows Transform3D aTr = new Transform3D(); aTr.setTranslation(new Vector3d(0, 0.5, 0)); // x axis Cylinder xAxis = new Cylinder(0.05f, 1f); Transform3D xTr = new Transform3D(); xTr.setRotation(new AxisAngle4d(0, 0, 1, -Math.PI/2)); xTr.setTranslation(new Vector3d(0.5, 0, 0)); TransformGroup xTg = new TransformGroup(xTr); xTg.addChild(xAxis); this.addChild(xTg); TransformGroup xTextTg = new TransformGroup(tTr); xTextTg.addChild(xShape); xTg.addChild(xTextTg); Cone xArrow = new Cone(0.1f, 0.2f); TransformGroup xArrowTg = new TransformGroup(aTr); xArrowTg.addChild(xArrow); xTg.addChild(xArrowTg); // y axis Cylinder yAxis = new Cylinder(0.05f, 1f); Transform3D yTr = new Transform3D(); yTr.setTranslation(new Vector3d(0, 0.5, 0)); TransformGroup yTg = new TransformGroup(yTr); yTg.addChild(yAxis); this.addChild(yTg); TransformGroup yTextTg = new TransformGroup(tTr); yTextTg.addChild(yShape); yTg.addChild(yTextTg); Cone yArrow = new Cone(0.1f, 0.2f); TransformGroup yArrowTg = new TransformGroup(aTr); yArrowTg.addChild(yArrow); yTg.addChild(yArrowTg); // z axis Cylinder zAxis = new Cylinder(0.05f, 1f); Transform3D zTr = new Transform3D();

67

zTr.setRotation(new AxisAngle4d(1, 0, 0, Math.PI/2)); zTr.setTranslation(new Vector3d(0, 0, 0.5)); TransformGroup zTg = new TransformGroup(zTr); zTg.addChild(zAxis); this.addChild(zTg); TransformGroup zTextTg = new TransformGroup(tTr); zTextTg.addChild(zShape); zTg.addChild(zTextTg); Cone zArrow = new Cone(0.1f, 0.2f); TransformGroup zArrowTg = new TransformGroup(aTr); zArrowTg.addChild(zArrow); zTg.addChild(zArrowTg); } }

Programa 2.8: Código que proporciona la clase que despliega un conjunto de coordenadas.

El siguiente código demuestra las características de la clase Transform3D gráficamente. Una matriz de transformación correspondiente a un objeto Transform3D es desplegado y puede ser editado por el usuario. Los componentes de rotación, traslación y escalación de la transformación son extraídos y desplegados separadamente. El usuario además puede especificar tres componentes y aplicarlos a la transformación. La transformación puede aplicarse a un objeto visual o un conjunto de coordenadas 3D, por lo que su efecto puede visualizarse inmediantamente. El programa proporciona una herramienta para explorar la relación entre las matrices de transformación y la interpretación geométrica de las transformaciones. import javax.vecmath.*; import java.awt.*; import java.awt.event.*; import javax.media.j3d.*; import com.sun.j3d.utils.universe.*; import com.sun.j3d.utils.geometry.*; import java.applet.*; import com.sun.j3d.utils.applet.MainFrame; public class TestTransform extends Applet implements ActionListener { public static void main(String[] args) { new MainFrame(new TestTransform(), 640, 300); } TransformGroup trGroup; Transform3D transform = new Transform3D(); MatrixPanel mp = new MatrixPanel(); TextField rx = new TextField(); TextField ry = new TextField(); TextField rz = new TextField(); TextField ra = new TextField(); TextField tx = new TextField(); TextField ty = new TextField(); TextField tz = new TextField(); TextField sx = new TextField(); TextField sy = new TextField(); TextField sz = new TextField(); public void init() { setLayout(new BorderLayout());

68

Panel eastPanel = new Panel(); eastPanel.setLayout(new BorderLayout()); eastPanel.add(mp, BorderLayout.NORTH); add(eastPanel, BorderLayout.EAST); Button button = new Button("Transform"); button.addActionListener(this); Panel p = new Panel(); p.add(button); eastPanel.add(p, BorderLayout.EAST); p = new Panel(); p.setLayout(new GridLayout(4,5)); p.add(new Label("x")); p.add(new Label("y")); p.add(new Label("z")); p.add(new Label("angle")); p.add(new Label("")); p.add(rx); p.add(ry); p.add(rz); p.add(ra); button = new Button("Rotate"); button.addActionListener(this); p.add(button); p.add(tx); p.add(ty); p.add(tz); p.add(new Panel()); button = new Button("Translate"); button.addActionListener(this); p.add(button); p.add(sx); p.add(sy); p.add(sz); p.add(new Panel()); button = new Button("Scale"); button.addActionListener(this); p.add(button); eastPanel.add(p, BorderLayout.SOUTH); GraphicsConfiguration gc = SimpleUniverse.getPreferredConfiguration(); Canvas3D cv = new Canvas3D(gc); add(cv, BorderLayout.CENTER); BranchGroup bg = createSceneGraph(); bg.compile(); SimpleUniverse su = new SimpleUniverse(cv); su.getViewingPlatform().setNominalViewingTransform(); su.addBranchGraph(bg); } private BranchGroup createSceneGraph() { BranchGroup root = new BranchGroup(); trGroup = new TransformGroup(); trGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); root.addChild(trGroup); //object Appearance ap = new Appearance(); ap.setMaterial(new Material()); Group shape = new Axes(); Transform3D tr = new Transform3D(); tr.setScale(0.5); TransformGroup tg = new TransformGroup(tr); trGroup.addChild(tg); tg.addChild(shape); //background and light BoundingSphere bounds = new BoundingSphere();

69

Background background = new Background(1.0f, 1.0f, 1.0f); background.setApplicationBounds(bounds); root.addChild(background); AmbientLight light = new AmbientLight(true, new Color3f(Color.red)); light.setInfluencingBounds(bounds); root.addChild(light); PointLight ptlight = new PointLight(new Color3f(Color.green), new Point3f(3f,3f,3f), new Point3f(1f,0f,0f)); ptlight.setInfluencingBounds(bounds); root.addChild(ptlight); PointLight ptlight2 = new PointLight(new Color3f(Color.orange), new Point3f(-2f,2f,2f), new Point3f(1f,0f,0f)); ptlight2.setInfluencingBounds(bounds); root.addChild(ptlight2); return root; } public void actionPerformed(ActionEvent e) { Matrix4d m = new Matrix4d(); mp.get(m); transform.set(m); String cmd = e.getActionCommand(); if ("Transform".equals(cmd)) { Quat4d quat = new Quat4d(); Vector3d translation = new Vector3d(); transform.get(quat, translation); Vector3d scale = new Vector3d(); transform.getScale(scale); AxisAngle4d rotation = new AxisAngle4d(); rotation.set(quat); rx.setText("" + rotation.x); ry.setText("" + rotation.y); rz.setText("" + rotation.z); ra.setText("" + rotation.angle); tx.setText("" + translation.x); ty.setText("" + translation.y); tz.setText("" + translation.z); sx.setText("" + scale.x); sy.setText("" + scale.y); sz.setText("" + scale.z); trGroup.setTransform(transform); } else { if ("Rotate".equals(cmd)) { double x = Double.parseDouble(rx.getText()); double y = Double.parseDouble(ry.getText()); double z = Double.parseDouble(rz.getText()); double a = Double.parseDouble(ra.getText()); transform.setRotation(new AxisAngle4d(x, y, z, a)); } else if ("Translate".equals(cmd)) { double x = Double.parseDouble(tx.getText()); double y = Double.parseDouble(ty.getText()); double z = Double.parseDouble(tz.getText()); transform.setTranslation(new Vector3d(x, y, z)); } else if ("Scale".equals(cmd)) { double x = Double.parseDouble(sx.getText()); double y = Double.parseDouble(sy.getText()); double z = Double.parseDouble(sz.getText()); transform.setScale(new Vector3d(x, y, z)); } transform.get(m); mp.set(m); } } }

Programa 2.9: Código que muestra las características de la clase Transform3D.

70

Figura 2.17 Salida de la ejecución del programa 2.9.

2.8 Composición de transformaciones tridimensionales.

Dos o más trasnformaciones pueden ser combinadas para formar una trasnformación compuesta. Por ejemplo si

son dos transformaciones,

entonces la transformación de composición está definida como:

Cabe notar que en general, la composición de dos transformaciones no es conmutativa:

.

En esta notación las transformaciones compuestas son aplicadas de derecha a izquierda. Por ejemplo aplica primero a

denota la transformación de composición que se

, seguido por

.

En forma de matriz, la composición de las transformaciones corresponde a la multiplicación de matriz. Si las matrices de trasnformaciones para

son

, respectivamente, entonces la matriz para la transformación compuesta es la matriz

.

La clase Transform3D incluye diversos métodos para multiplicar matrices de transformación para así poder formar transformaciones compuestas. •

void

mul(Transform3D

t):

multiplica

la

transformación

t

con

la

transformación actual a la derecha.

71



void mul(Transform3D t1, Transform3D t2): multiplica la transformación t1 por t2.



void mulInverse(Transform3D t): multiplica la transformación inversa t por la transformación actual de la derecha.



void

mulInverse(Transform3D

t1,

Transform3D

t2):

multiplica

la

transformación t1 por la transformación t2 inversa de la derecha. •

void mulTransposeBoth(Transform3D t1, Transform3D t2): multiplica la transposición de la transformación t1 por la transposición de la transformación t2 de la derecha.



void mulTransposeLeft(Transform3D t1, Transform3D t2): multiplica la transposición de la transformación t1 por la transformación t2 de la derecha.



void mulTransposeRight(Transform3D t1, Transform3D t2): multiplica la transformación t1 por la transposición de la

transformación t2 de la

derecha. Cuando se desea establecer una transformación compleja, es mucho más sencillo componerla de transformaciones simples, que construir su matriz directamente. Supongamos que queremos contruir una rotación de

sobre los ejes a través de

(1, 1, 0) y (1, 2, 1). Debido a que los ejes no pasan por el origen, no podemos aplicar la equación de cuaterniones directamente. Sin embargo, podemos primero realizar una traslación para mover los ejes al origen, realizar la rotación sobre los nuevos ejes, y finalmente aplicar la traslación inversa para enviar los ejes de regreso a su ubicación original. Supongamos que

es la traslación por (-1,-1,0).

Entonces

es la rotación de

y

.Y

sobre los

ejes a través del origen y (0, 1, 1). Por lo tanto la rotación original puede ser descompuesta en

.

Este patrón de descomposición es muy común. Si una transformación particular se localiza en una posición estándar, una transformación similiar en una posición más general puede obtenerse de la siguiente manera: primero se transforma a la

72

posición estándar,

después se realiza la transformación dada en la forma

estándar y después se transforma a su posición original. Consideremos otro ejemplo, en este caso de reflección. Una matriz general de reflección sería difícil de construir, por lo que se tratará de reducir el problema de a un plano

debido a que es mucho más fácil de construir. Supongamos que el

plano de reflección es un plano a través del origen dado por la ecuación:

En lugar de encontrar la matriz de transformación directamnte, se usará una transformación compuesta. Primero se puede construir una rotación para mapear el plano de reflección al plano

; después se realiza la reflección sobre el plano

y finalmente se completará la transformación compuesta al realizar la rotación inversa que mapea el plano

de regreso al plano de reflección original. La

transformación compuesta es el equivalente de la reflección original. Para obtener la rotación que mapea la reflección del plano descrita anteriormente al plano

, es equivalente a hacer lo siguiente: mapear el vector normal

, el vector normal para el plano

. El eje de rotación sería

a y la

ecuación de cuaternión de la rotación tendría la forma:

El ángulo de rotación apropiado debería satisfacer la condición:

Al denotar esta rotación por

y la reflección sobre el plano

por , entonces la

reflección sobre el plano puede representarse como:

El siguiente código ilustra la construcción de reflecciones utilizando este método. Un plano en una posición general actúa como un espejo para la reflección. Un texto en 3D está rotando en la escena y la imagen espejo sobre el plano también

73

se despliega. El plano se muestra en una forma semitransparente para dar un efecto de reflección en espejo para el objeto transformado. import javax.vecmath.*; import java.awt.*; import java.awt.event.*; import javax.media.j3d.*; import com.sun.j3d.utils.universe.*; import com.sun.j3d.utils.geometry.*; import java.applet.*; import com.sun.j3d.utils.applet.MainFrame; public class Mirror extends Applet { public static void main(String[] args) { new MainFrame(new Mirror(), 640, 480); } public void init() { // create canvas GraphicsConfiguration gc = SimpleUniverse.getPreferredConfiguration(); Canvas3D cv = new Canvas3D(gc); setLayout(new BorderLayout()); add(cv, BorderLayout.CENTER); BranchGroup bg = createSceneGraph(); bg.compile(); SimpleUniverse su = new SimpleUniverse(cv); su.getViewingPlatform().setNominalViewingTransform(); su.addBranchGraph(bg); } private BranchGroup createSceneGraph() { //object Appearance ap = new Appearance(); ap.setMaterial(new Material()); Font3D font = new Font3D(new Font("Serif", Font.PLAIN, 1), new FontExtrusion()); Shape3D shape = new Shape3D(new Text3D(font, "Java"), ap); //translation Transform3D trans = new Transform3D(); trans.setTranslation(new Vector3d(-0.5,0,0)); TransformGroup transg = new TransformGroup(trans); transg.addChild(shape); //rotation TransformGroup spin = new TransformGroup(); spin.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); spin.addChild(transg); //scaling, translation Transform3D tr = new Transform3D(); tr.setScale(0.25); tr.setTranslation(new Vector3d(0.5,0,0)); TransformGroup tg = new TransformGroup(tr); tg.addChild(spin); //shared group SharedGroup share = new SharedGroup(); share.addChild(tg); //link leaf nodes to shared group Link link1 = new Link(share); Link link2 = new Link(share); //reflection Transform3D reflection = getReflection(1,1,1); TransformGroup reflectionGroup = new TransformGroup(reflection); reflectionGroup.addChild(link2); //the mirror QuadArray qa = new QuadArray(4, QuadArray.COORDINATES); qa.setCoordinate(0, new Point3d(0,-0.5,0.5)); qa.setCoordinate(1, new Point3d(1,-0.5,-0.5)); qa.setCoordinate(2, new Point3d(0,0.5,-0.5)); qa.setCoordinate(3, new Point3d(-1,0.5,0.5)); ap = new Appearance(); ap.setTransparencyAttributes(

74

new TransparencyAttributes(TransparencyAttributes.BLENDED, 0.7f)); Shape3D mirror = new Shape3D(qa, ap); //rotator Alpha alpha = new Alpha(-1, 4000); RotationInterpolator rotator = new RotationInterpolator(alpha, spin); BoundingSphere bounds = new BoundingSphere(); rotator.setSchedulingBounds(bounds); //background and lights Background background = new Background(0.5f, 0.5f, 0.5f); background.setApplicationBounds(bounds); AmbientLight light = new AmbientLight(true, new Color3f(Color.red)); light.setInfluencingBounds(bounds); PointLight ptlight = new PointLight(new Color3f(Color.green), new Point3f(3f,3f,3f), new Point3f(1f,0f,0f)); ptlight.setInfluencingBounds(bounds); PointLight ptlight2 = new PointLight(new Color3f(Color.orange), new Point3f(-2f,2f,2f), new Point3f(1f,0f,0f)); ptlight2.setInfluencingBounds(bounds); //branch group BranchGroup root = new BranchGroup(); root.addChild(link1); root.addChild(reflectionGroup); root.addChild(mirror); root.addChild(rotator); root.addChild(background); root.addChild(light); root.addChild(ptlight); root.addChild(ptlight2); return root; } static Transform3D getReflection(double a, double b, double c) { Transform3D transform = new Transform3D(); double theta = Math.acos(c/Math.sqrt(a*a+b*b+c*c)); double r = Math.sqrt(a*a+b*b); Transform3D rot = new Transform3D(); rot.set(new AxisAngle4d(b/r, -a/r, 0, theta)); Transform3D ref = new Transform3D(); ref.setScale(new Vector3d(1,1,-1)); transform.mulInverse(rot); transform.mul(ref); transform.mul(rot); return transform; } }

Programa 2.10: Código que muestra la construcción de reflecciones.

Figura 2.18 Salida de la ejecución del programa 2.10.

75

MODELADO GEOMÉTRICO

3.1 Modelos geométricos. Los gráficos utilizados para construir graficos en 3D son los objetos con formas visuales. Constituyen los objetos visibles en una escena renderizada. Un objeto con forma está definido por su geometría y apariencia. La geometría proporciona la descripción matemática de la forma, tamaño y otras propiedades estructurales del objeto. La apariencia define el color, textura, material y otros atributos del objeto.

76

La geometría de un objeto visual puede ser construidoa de un conjunto de objetos simples tales como los triangulos. A otros objetos más complejos como los cubos y esferas que están preconstruidos y pueden ser reutilizados se les conoce como figuras primitivas. Las figuras primitivas proporcionan un nivel de abstracción que simplificará la construcción de muchos objetos complejos. Los objetos de texto basados en fuentes proporcionan otra fuente de objetos geométricos. Tanto los objetos de texto 2D como 3D son elementos útiles para una escena gráfica en 3D. Java 3D proporciona la clase Shape3D que representa la forma de los objetos. La geometría y apariencia de un nodo Shape3D están definidas al hacer referencia a objetos del tipo Geometry y Appearance. La clase Geometry tiene subclases que ayudan a definir varios tipos de figuras geométricas de diferentes maneras. La clase Appearance contiene referencias a varios atributos de los objetos que definen diferentes aspectos de su apariencia. Comunmente se utilizan las primitivas como cubos, esferas, cilindros y conos los cuales son proporcionados por Java 3D. Java 3D proporciona además el soporte necesario para utilizar texto en 2D y 3D como objetos geométricos. El modelado geométrico empieza con el modelado de un punto. Para representar los puntos en las computadoras, se utiliza un concepto algebraico conocido como vector y espacios de vector. Un vector de

dimensión es una tupla de

números:

La colección de todos los dimensional vector 3D un vector 4D

vectores dimensionales forma el

espacio

. En un espacio 3D, un punto puede ser representado por un ; utilizando coordenadas homogéneas un punto está asociado a .

Existe un concepto geométrico de vectores que representa cantidades con direcciones. Ejemplos de tales vectores incluyen la dirección de la línea, la fuerza, velocidad y aceleración. Un vector geométrico también es representado como una -tupla. Algebraicamente no hay distinción entre un punto geométrico y un vector

77

geométrico; la diferencia existe solo en la interpretacón general de las cantidades matemáticas. En los gráficos 3D la construcción y transformación geométricas dependen enormemente de la notación matemática de vectores. Java proporciona un gran conjunto de clases que representan puntos, vectores y matrices en el paquete javax.vecmath; este paquete contiene muchas variaciones de clases de vectores y matrices. Las figuras geométricas básicas de objetos 3D son modelados como puntos, líneas y superficies. Los puntos y líneas (incluyendo las curvas) son relativamente simples de definir debido a que son usuamente extensiones que corresponden a modelos 2D. Los modelos de superficies en 3D presentan algunos retos reales; es decir los objetos solidos 3D pueden usualmente ser modelados como superficies. Matemáticamente una superficie puede ser representada por una ecuación implícita en las coordenadas:

Alternativamente, es usualmente más conveniente utilizar una ecuación paramétrica con dos parámetros para realizar aplicaciones gráficas como se muestra a continuación:

Debido a la complejidad que involucra el representar una superficie 3D, es necesario utilizar una colección de superficies más simples. Una representación comúnmente utilizada es una malla de polígonos simples, tales como triángulos y cuadriláteros. Otra representación para las superficies 3D son las superfices polinomiales y spline. La figura 3.1 muestra un ejemplo de mallas de polígonos representando una superficie.

78

Figura 3.1: Esfera representada por mallas de triángulos de diferentes resoluciones.

Java 3D ofrece soporte directo para arreglos de puntos, líneas y triángulos o cuadriláteros como herramientas básicas para las construcciones geométicas. En Java 3D, un objeto visual es comúnmente representado por Shape3D. Los objetos de este tipo hacen referencia a objetos de Geometry los cuales definen la forma y otras características. Shape3D también hace referencia a los objetos del tipo Appearance que definen la apariencia en el renderizado. La clase Geometry es una clase abstracta con un gran número de descendientes.

Figura 3.2: Jerarquía de clases de Geometry.

A continuación se presentarán algunas de estas clases. GeometryArray es la familia de clases que proporcionan las facilidades para construir directamente figuras geométricas con arreglos de polígonos, líneas o

79

puntos. Define los vértices que especifican la relación estructural entre los vértices. En un objeto del tipo GeometryArray las definiciones para los vértices siempre incluyen coordenadas, aunque también pueden incluir otro tipo de datos como superficies normales y colores. Normalmente un objeto de la familia GeometryArray es creado al llamar al constructor apropiado con los datos especificados y los tamaños de los arreglos. Los datos del vértice son establecidos con llamadas a métodos. GeometryArray proporciona una variedad de métodos para establecer las coordenadas y otros datos. Por ejemplo, una coordenada puede establecerse individualmente o a través de un arreglo de coordenadas: • void setCoordinate(int index, Point3f coord.) • void setCoordinates(int startIndex, Point3f[ ] cords) La clase PointArray define la geometría consistente de un conjunto de puntos. Cada vértice corresponde un punto en la figura. Por ejemplo el siguiente fragmento de código define tres puntos: PointArray pa = new PointArray(3, GeometryArray.COORDINATES); pa.setCoordinate(0, new Point3f(0f, 0f, 0f)); pa.setCoordinate(1, new Point3f(1f, 0f, 0f)); pa.setCoordinate(3, new Point3f(0f, 1f, 0f));

Figura 3.3: Geometría de un PointArray.

80

La clase LineArray define segmentos de línea. Cada dos vértices especificados secuencialmente corresponden al segmento de línea como se ve en el siguiente fragmento de código: LineArray la = new LineArray(6, GeometryArray.COORDINATES); Point3f[ ] coords = new Point3f[6]; cords[0]= new Point3f(0f, 0f, 0f); cords[1]= new Point3f(1f, 1f, 0f); cords[2]= new Point3f(1f, 0f, 0f); cords[3]= new Point3f(2f, 1f, 0f); cords[4]= new Point3f(2f, 1f, 0f); cords[5]= new Point3f(3f, 0f, 0f); la.setCoordinates(0, coords);

Figura 3.4: Geometría de un LineArray.

La clase TriangleArray define la superfice consistente en arreglos de tríangulos, cada tres vértices definen un triangulo. El siguiente fragmento de código define un objeto geométrico conformado por dos triangulos: TriangleArray ta = new TriangleArray(6, GeometryArray.COORDINATES); Point3f[ ] coords = new Point3f[6]; cords[0]= new Point3f(0f, 0f, 0f); cords[1]= new Point3f(1f, 1f, 0f); cords[2]= new Point3f(1f, 0f, 0f); cords[3]= new Point3f(2f, 1f, 0f); cords[4]= new Point3f(2f, 1f, 0f); cords[5]= new Point3f(3f, 0f, 0f); ta.setCoordinates(0, coords);

81

Figura 3.5: Geometría de un TriangleArray.

La geometría de un cono puede estár definido como una serie de triángulos que utilizan TriangleArray como se muestra en el siguiente fragmento de código: int n=0; //numero de parches de triangulo TriangleArray ta = new TriangleArray(3*n, GeometryArray.COORDINATES); Point3f apex = new Point3f(0, 0, 1); Point3f p1 = new Point3f(1, 0,0); int count = 0; for (int i=1; i