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 Computacionales Mtra. Ana Cecilia Ruiz Calvillo Elaborador del Manual
ESTE MANUAL FUE REALIZADO PARA USO EXCLUSIVO DEL INST ITUTO 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 Transforma ciones bidimensionales. bidimensionales .
30
2.2 Coordenadas homogéneas y representación representaci ón matricial.
32
2.3 Tipos de transformaciones transfor maciones bidimensionales.
36
2.4 Composición de transformaciones transforma ciones bidimensionales.
45
2.5 Transformaciones Transforma ciones de la composición general.
49
2.6 Transformación Transformación ventana-área ventana-área de vista.
53
2.7 Representación Representació n matricial de transformaciones transforma ciones tridimensionales. tridimensio nales.
54
2.7.1 Transformaciones Transformaciones tridimensionales. tridimensionales.
56
2.7.2 Matriz de transformación. transformación.
56
2.7.3 Tipos de transformaciones transformaciones tridimensionales. tridimensionales.
61
2.8 Composición de transformaciones transforma ciones tridimensionales tridimensio nales
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 Intro du cc ió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 Evoluc ión de la prog ram ación en gráfico s po r com put adora.
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
MVSE:
NXT:
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
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 cl ass */ 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 FORTRA N.
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 j ava.awt.event.*; ava.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); GLDrawableFactory.getFactory().createGLCanvas(cap); canvas.setSize(300, 300); canvas.addGLEventListener(new canvas.addGLEventListene r(new Renderer()); frame.add(canvas); frame.pack(); frame.addWindowListener(new frame.addWindowListener( new Wi ndowAdapter() { 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); (float)Math.cos(i*a); y = (float)Math.sin(i*a); (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 renderizar gráficos en 2D. Entre lo que se incluye: •
Una jerarquía de clases para objetos geométricos. 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. propiedades. El siguiente código muestra un ejemplo de Java 2D que utiliza ciertas capacidades capacidades como transparencia, transparencia, pintura gradual, transformación transformación y tipografía.
24
import java.awt.*; import j ava.awt.event.*; ava.awt.event.*; import javax.swing.*; import j ava.awt.font.*; import j ava.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 Elli pse2D.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(A AlphaComposite.getInstance(AlphaComposite.SRC_OVER, lphaComposite.SRC_OVER, 0.4f); 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", g2.drawString("Java", 120, 200); //obtener el outline del texto en glyph FontRenderContext frc = g2.getFontRenderContext g2.getFontRenderContext(); (); GlyphVector gv = font.createGlyphVector(frc, font.createGlyphVector(frc, "2D"); Shape glyph = gv.getOutline(150, 300); //dibujar el glyph rotado g2.rotate(Math.PI/6, 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 j ava.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 = c reateSceneGraph(); bg.compile(); su = new SimpleUni verse(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 B oundingSphere(); 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 relacion ados c on 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 Transfor macion es bidim ensionales.
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 Co ord enadas hom ogé neas y repres entación m atricial.
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 transform aciones bidimension ales.
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
es representada por la matriz:
El sesgar sobre el eje por el factor
se da por la matriz:
Java 2D utiliza una clase llamada AffineTransform para definir una t ransformació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 j ava.awt.geom.*; import j ava.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 Compos ición de transform aciones bidim ensionales.
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
1
0
3
0
1
4
0
0
1
La segunda traslación sería:
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 j ava.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 l a composición de transformaciones.
Figura 2.12 Salida de la ejecución del programa 2.2.
48
2.5 Transfor macion es de la com pos ició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
0
SrcOver
1
1-
DstOver
1-
d
s
1
SrcIn
d
0
DstIn
0
s
SrcOut
1-
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 j ava.awt.event.*; import j ava.awt.image.*; import javax.swing.*; import j ava.awt.font.*; import j ava.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 Elli pse2D.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 Trans fo rm ación v entan a-área de vis ta.
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 j ava.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 m atricial de transform aciones tridim ensionales.
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 j ava.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
sobe el
eje 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 j ava.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 j ava.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 Compos ición de transform aciones tridimens ionales.
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 j ava.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 l a construcción de reflecciones.
Figura 2.18 Salida de la ejecución del programa 2.10.
75
MODELADO GEOMÉTRICO
3.1 Mod elo s g eom é tr ic os .
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<=n; i++){ float x = (float)Math.cos(i*2*Math.PI/n); float y = (float)Math.sin(i*2*Math.PI/n); Point3f p2 = new Point3f(x, y, 0); ta.setCoordinate(count++, apex); ta.setCoordinate(count++, p1); ta.setCoordinate(count++, p2); p1=p2; } La base circular del cono está dividida en
segmentos. Los dos puntos de cada
segmento son las puntas que forman el triangulo. Debido a que TriangleArray requiere de especificaciones explícitas de cada vértice en cada triangulo, es necesario definir
coordenadas, aunque haya solo
puntos distintivos.
La clase QuadArray define la superficie de arreglos de cuadriláteros. Cada cuatro secuencias de vértices definen el cuadrilátero. Los cuatro vértices son necesarios
82
que se encuentren en el plano. El siguiente fragmento de código consiste en dos cuadrados que no están en el mismo plano, pero los vértices de cada uno se encuentran en el mismo plano: QuadArray qa = new QuadArray(8, GeometryArray.COORDINATES); Point3f[ ] coords = new Point3f[8]; cords[0]= new Point3f(0f, 0f, 0f); cords[1]= new Point3f(1f, 0f, 0f); cords[2]= new Point3f(1f, 1f, 0f); cords[3]= new Point3f(0f, 1f, 0f); cords[4]= new Point3f(1f, 1f, 0f); cords[5]= new Point3f(0f, 1f, 0f); cords[6]= new Point3f(0f, 1f, 1f); cords[7]= new Point3f(1f, 1f, 1f); qa.setCoordinates(0, coords);
Figura 3.6: Geometría de un QuadArray.
Cabe mencionar que aunque solo hay seis puntos distintivos en la figura, se necesitan definir los ocho vértices del objeto debido a que es necesario especificar cada figura con cuatro vértices. Comúnmente los vértices en un arreglo son compartidos por diversos polígonos diferentes. Utilizando el TriangleArray o el QuadArray se pueden agregar los vértices compartidos múltiples veces. La clase GeometryStripArray usa la idea de “franjas” para permitir compartir los vértices adyacentes. Para definir franjas separadas, el número de vértices de cada una de ellas debe estár especificado por un arreglo de enteros: •
void setStripVertexCounts(int[ ] stripVertexCounts);
83
La longitud de cada arreglo es el número de franjas; y la cantidad introducida en cada posición del arreglo representa el número de vértices de la franja. GeometryStripArray tiene tres subclases. LineStripArray que define una franja como polilínea. Se utiliza una secuencia de puntos para especificar las franjas sin duplicar los puntos internos. El siguiente fragmento de código define la misma figura presentada con LineArray en la figura 3.3,
pero utilizando el objeto
LineStripArray: int[ ] stripVertexCounts = {2, 3}; LineStripArray lsa = new LineStripArray(5, GeometryArray.COORDINATES, stripVertexCounts); Point3f[ ] coords = new Point3f[5]; 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(3f, 0f, 0f); lsa.setCoordinates(0, coords); La clase TriangleStripArray define franjas de triángulos. En cada franja, cada tres vértices consecutivos definen el triángulo, la siguiente figura ilustra una figura construida utilizando un objeto TriangleStripArray con conteo de franjas de vértices (5, 4):
Figura 3.7: Representación de un Tri angleStripArray.
La clase TriangleFanArray ofrece una alternativa para definir franjas de tirangulos. En cada franja el primer vértice con dos vértices consecutivos definen el tríangulo
84
La siguiente figura muestra un objeto de esta clase con conteo de franjas de vértices (5, 4):
Figura 3.8: Representación de un Tri angleFanArray.
Un cono puede estár denifido por varios TriangleFanArray como se muestra en el código: int n=0; //numero de parches; int[ ] stripVertexCounts = {n+2} //un strip TriangleFanArray tfa = new TriangleFanArray(n+2, GeometryArray.COORDINATES, stripVertexCounts); Point3f apex = new Point3f(0, 0, 1); tfa.setCoordinate(0, apex); for (int i=1; i<=n; i++){ float x = (float)Math.cos(i*2*Math.PI/n); float y = (float)Math.sin(i*2*Math.PI/n); Point3f p = new Point3f(x, y, 0); tfa.setCoordinate(i+1, p); } Los
arreglos de triángulos están definidos por
puntos en cada franja.
Para evitar los vértices duplicados se utiliza IndexedGeometryArray. En lugar de definir un polígono especificando los vértices directamente, un objeto de este tipo especifica los índices de los vértices en el arreglo de puntos. Consecuentemente los vértices necesitan definirse solo una vez, pero pueden ser referenciados varias veces a través de sus índices. Por ejemplo, el siguiente segmento de cóidgo define una figura de dos cuadros. Solo utiliza seis vértices en lugar de
85
ocho como se ocupaba con el GeometryArray. Cada figura está especificada por sus cuatro índices correspondientes a los vértices de sus esquinas: IndexedQuadArray iqa = new IndexedQuadArray(6, GeometryArray.COORDINATES, 8); Point3f[ ] coords = new Point3f[6]; cords[0]= new Point3f(0f, 0f, 0f); cords[1]= new Point3f(1f, 0f, 0f); cords[2]= new Point3f(1f, 1f, 0f); cords[ 3]= new Point3f(0f, 1f, 0f); cords[4]= new Point3f(0f, 1f, 1f); cords[5]= new Point3f(1f, 1f, 1f); iqa.setCoordinates(0, coords); int[ ] indices = {0, 1, 2, 3, 4, 5}; iqa.setCoordinateIndices(0, indices); Tambien existe la clase IndexedGeometryStripArray con sus subclases IndexedLineStripArray, IndexedTriangleStripArray, e IndexedTriangleFanArray. Estas clases agregan índices al arreglo de franjas y combinan sus características. Para definir franjas separadas, necesita especificarse en el constructor un arrego stripIndex a través del siguiente método: •
void setStripIndexCounts(int[ ] stripIndexCounts)
El siguiente código construye un tetraedro regular utilizando la clase IndexedTriangleArray. El tetraedro es uno de los cinco poliedros conocidos como solidos Platónicos. Un tetraedro consiste en cuatro caras de triángulos equiláteros. import javax.vecmath.*; import javax.media.j3d.*; public class Tetrahedron extends IndexedTriangleArray { public Tetrahedron() { super(4, TriangleArray.COORDINATES | TriangleArray.NORMALS, 12); setCoordinate(0, new Point3f(1f,1f,1f)); setCoordinate(1, new Point3f(1f,-1,-1f)); setCoordinate(2, new Point3f(-1f,1f,-1f)); setCoordinate(3, new Point3f(-1f,-1f,1f)); int[] coords = {0,1,2,0,3,1,1,3,2,2,3,0}; float n = (float)(1.0/Math.sqrt(3)); setNormal(0, new Vector3f(n,n,-n)); setNormal(1, new Vector3f(n,-n,n)); setNormal(2, new Vector3f(-n,-n,-n));
86
setNormal(3, new Vector3f(-n,n,n)); Vector3f(-n,n,n)); int[] norms = {0,0,0,1,1,1,2,2,2,3,3,3}; setCoordinateIndices(0, coords); setNormalIndices(0, norms); }
}
Programa 3.1: Código que construye un t etraedro regular.
El siguiente programa despliega el tetraedro creado en el código anterior rotando en el espacio para que sea visto desde diferentes ángulos: import javax.vecmath.*; javax.vecmath.*; import java.awt.*; import j ava.awt.event.*; ava.awt.event.*; import javax.media.j3d.*; import com.sun.j3d.utils.universe.*; com.sun.j3d.utils.universe.*; import com.sun.j3d.utils.geometry.*; com.sun.j3d.utils.geometry.*; import java.applet.*; import com.sun.j3d.utils.applet.MainFrame; com.sun.j3d.utils.applet.MainFrame; public class TestTetrahedron extends Applet { public static void main(String[] args) { new MainFrame(new TestTetrahedron(), 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() { BranchGroup root = new BranchGroup(); TransformGroup spin = new TransformGroup(); spin.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); root.addChild(spin); //object Appearance ap = new Appearance(); Appearance(); ap.setMaterial(new Material()); Shape3D shape = new Shape3D(new Tetrahedron(), ap); //rotating object Transform3D tr = new Transform3D(); tr.setScale(0.25); TransformGroup tg = new TransformGroup(tr); TransformGroup(tr); spin.addChild(tg); tg.addChild(shape); Alpha alpha = new Alpha(-1, 4000); RotationInterpolator rotator = new RotationInterpolator(alpha RotationInterpolator(alpha,, spin); BoundingSphere bounds = new BoundingSphere(); rotator.setSchedulingBounds(bounds); spin.addChild(rotator); //light and background Background background = new Background(1.0f, 1.0f, 1.0f); background.setApplicationBounds(bounds); root.addChild(background); AmbientLight light = new AmbientLight(true, 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)); Point3f(1f,0f,0f)); ptlight.setInfluencingBounds(bounds);
87
root.addChild(ptlight); PointLight ptlight2 = new PointLight(new Color3f(Color.orange), new Point3f(-2f,2f,2f), new Point3f(1f,0f,0f)); Point3f(1f,0f,0f)); ptlight2.setInfluencingBounds(bounds); root.addChild(ptlight2); return root; } }
Programa 3.2: Código que despliega un tetraedro en rotación.
Figura 3.9: Salida de la ejecución del programa 3.2.
Java 3D ofrece clases útiles para las primitivas geométricas más utilizadas. La clase abstracta Primitive es una subclase de Group y encapsula los atributos geométricos geométricos predefinidos. predefinidos. La siguiente figura muestra la jerarquía de clase de las primitivas definidas en el paquete com.sun.j3d.utils.geometry. La apariencia de la primitiva puede establecerse establecerse a través de los siguientes métodos: •
void setAppearance(); setAppearance();
•
void setAppareance(Appearance setAppareance (Appearance appearance)
•
void setAppearance(int setAppearance (int subpart, Appearance appearance)
Figura 3.10: Subclases de la clase Primitive.
88
El tamaño de las primitivas puede establecerse con algunos de sus constructores por ejemplo: •
Box(float xdim, float ydim, float zdim, Appearance appearance) appearance)
•
Cone(float radius, float height)
•
Cylinder(float Cylinder(float radius, float height)
•
Sphere(float radius)
La clase Primitive es una subclase de Group, por lo tanto un objeto primitivo puede agregarse directamente a una escena. El siguiente código muestra una aplicación de primitivas. El programa despliega instancias de las cuatro primitivas básicas proporcionadas por Java 3D. import j avax.vecmath.*; import java.awt.*; import j ava.awt.event.*; import javax.media.j3d.*; import com.sun.j3d.utils.universe.*; com.sun.j3d.utils.universe.*; import com.sun.j3d.utils.geometry.*; com.sun.j3d.utils.geometry.*; import java.applet.*; import com.sun.j3d.utils.applet.MainFrame; com.sun.j3d.utils.applet.MainFrame; public class TestPrimitives extends Applet { public static void main(String[] args) { new MainFrame(new TestPrimitives(), 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() { BranchGroup root = new BranchGroup(); TransformGroup spin = new TransformGroup(); spin.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); root.addChild(spin); //primitives Appearance ap = new Appearance(); Appearance(); ap.setMaterial(new Material()); Box box = new Box(1.2f, 0.3f, 0.8f, ap); Sphere sphere = new Sphere(); Cylinder cylinder = new Cylinder(); Cone cone = new Cone(); Transform3D tr = new Transform3D(); tr.setScale(0.2); TransformGroup tg = new TransformGroup(tr); spin.addChild(tg); tg.addChild(box); tr.setIdentity();
89
tr.setTranslation(new Vector3f(0f,1.5f,0f)); TransformGroup tgSphere = new TransformGroup(tr); tg.addChild(tgSphere); tgSphere.addChild(sphere); tr.setTranslation(new Vector3f(-1f,-1.5f,0f)); TransformGroup tgCylinder = new TransformGroup(tr); tg.addChild(tgCylinder); tgCylinder.addChild(cylinder); tr.setTranslation(new Vector3f(1f,-1.5f,0f)); TransformGroup tgCone = new TransformGroup(tr); tg.addChild(tgCone); tgCone.addChild(cone); Alpha alpha = new Alpha(-1, 4000); RotationInterpolator rotator = new RotationInterpolator(alpha, spin); BoundingSphere bounds = new BoundingSphere(); rotator.setSchedulingBounds(bounds); spin.addChild(rotator); //background and light 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; } }
Programa 3.3: Código que despliega las cuatro primitivas básicas en rotación.
Figura 3.11: Salida de ejecución del programa 3.3.
3.2 Pro yeccion es.
Es posible realizar vistas de una escena 3D con una imagen 2D a través de una transformación llamada proyección. Existen dos tipos de proyecciones: la 90
proyección paralela y la proyección de perspectiva. En cada caso un plano conocido como plano vista es colocado en un mundo virtual. Las proyecciones mapean los puntos en el mundo virtual del plano. Solo una ventana finita (usualmente rectangular) en el plano de vista será utilizada para imágenes renderizadas. A esta ventana se le llama placa de vista y es análoga a un fotograma de una película en una cámara ordinaria. Solo necesitan calcularse los puntos del espacio 3D que proyectan la vista de la placa. Los puntos muy cercanos o muy alejados del usuario también se excluyen; lo que da como resultado un volumen finito en un espacio 3D que participará en la proyección. Dicho volumen es conocido como vista frustum. Una proyección paralela proyecta los puntos 3D en el plano vista a través de líneas paralelas en una dirección fija. Cuando las líneas de la proyección son perpendiculares a la vista del plano, la proyección paralela se conoce como ortográfica. Tres proyecciones ortográficas sobre los tres ejes son conocidas como elevación frontal, elevación superior y elevación lateral. La vista del volumen de una proyección paralela es un paralelograma. La siguiente figura muestra una proyección paralela:
Figura 3.12: Proyección paralela.
Las formulas para las proyecciones paralelas son relativamente simples. Supongamos que la vista del plano es sobre el plano dirección del eje . Entonces un punto mapeado al punto
y la proyección es en
en el espacio es simplemente
en un plano de vista 2D. La transformación está dada por
la matriz:
91
Comunmente es recomendable retener la información de las coordenadas mientras se realiza la proyección. Por lo tanto la proyección paralela es esencialmente la identidad de la transformación:
En la proyección de perspectiva, todas las líneas de proyección convergen en el punto de vista (la posición del ojo del espectador). Los objetos que están más cercanos a el aparecerán más grandes que los que están más alejados. Este modo asemeja la proyección del ojo humano en cámaras regulares.
Figura 3.13: Proyección perspectiva.
Las fórmulas matemáticas de la proyección perspectiva son más complejas. Asumamos que el plano de vista es el plano
y el ojo está en
viendo
hacia abajo en el eje de las , siendo el eje
la vista desde arriba. Como se
muestra en la siguiente figura:
92
Figura 3.14: Ejemplo de proyección perspectiva.
Considerando los dos triangulos similares, se tiene que:
O lo que es lo mismo:
Similarmente, en la dirección de :
Por lo tanto la transformación no es lineal en el espacio 3D. Sin embargo, puede expresarsen en una forma lineal con las coordenadas homogéneas como se muestra a continuación:
Si la coordenada es retenida, entonces la transformación se convierte en:
Al utilizar coordenadas homogéneas y matrices de transformación 4 x 4, las proyecciones paralelas y de perspectiva pueden ser tratadas uniformemente.
93
Ambos tipos son transformaciones proyectivas y pueden ser representadas por matrices 4 x 4 si
Esta matriz se
se aproxima al infinito como se muestra a continuación:
convierte en la matriz de proyección paralela descrita
anteriormente. Una proyección paralela puede ser resguardada como un caso especial de proyección de perspectiva con los puntos de vista al infinito. Los puntos de vista de la proyección de perspectiva tienen coordenadas cordenadas homogéneas aproxima a
. Mientras que
o
, el punto de vista se
un punto al infinito en una coordenada homogénea.
3.2.1 El modelo de vistas.
Java 3D proporciona un sistema de vistas muy versátil que soporta ajustes dinámicos basados en los cambios del ambiente así como la tradicional cámara estática basada en vistas. Las clases que están directamente relacionadas con las vistas son: ViewPlatform, View, PhysicalBody, PhysicalEnvironment, Canvas3D, Screen3D. Un objeto ViewPlatform define la presencia de la vista en un mundo virtual. Este objeto puede atravesar una cadena de transformaciones y eventualmente ser adjuntado al objeto del tipo Locale. El TransformGroup define la transformación de vista de la plataforma que determina principalmente la posición y orientación de la vista, o la matriz de vista. La clase Transform3D tiene un método que ayuda a construir la transformación: •
void lookAt(Point3d eye, Point3d look, Vector3d up)
La transformación es contruida para posicionar el ViewPlatform para que el ojo se coloque en el punto dado, viendo hacia el centro especificado y con la dirección dada hacia arriba en la placa de vista. La inversión de la transformación definida
94
por el método puede ser colocada en TransformGroup sobre ViewPlatform para realizar el posicionamiento especificado en ViewPlatform. El objeto View es el corazón del sistema de vista; define la configuración principal de la vista con propiedades como el tipo de proyección y el volumen de vista (la matriz de proyección de la vista). También puede acomodar cambios dinámicos en los parámetros de la vista a través de sus conecciones con los objetos PhysicalBody y PhysicalEnvironment. Los métodos de la clase View para establecer el volumen de vista o la matriz de proyección son: •
void setFieldOfView(double fov)
•
void setFrontClipDistance(double d)
•
void setBackClipDistance(double d)
•
void setProjectionPolicy(int projection)
Los objetos PhysicalBody y PhysicalEnvironment proporcionan soporte para calibraciones automáticas con sistemas dinámicos de vista. El PhysicalBody describe las características físicas del cuerpo o cabeza del usuario.
El
PhysicalEnvirontment contiene la información sobre el ambiente físico del usuario. La clase Canvas3D es una subclase de la clase AWT Canvas y puede colocarse en un contenedor AWT. Un objeto de este tipo representa la superficie renderizada. El siguiente constructor es típicamente utilizado para construir un objeto Canvas3D: •
public Canvas3D(GraphicsConfiguration gc)
Se debe definir una clase separada Screen3D para describir los dispositivos de visualización, ésta clase es referenciada por un objeto del tipo Canvas3D. La separación de la descripción de dispositivos evita la duplicación de la misma información en múltiples objetos Canvas3D. La clase Screen3D no tiene constructor público, y sus instancias pueden llamarse a través del método getScreen3D de la clase Canvas3D.
95
3.2.2 Modo de Compatibilidad.
Java 3D también ofrece un modo de vista de compatibilidad; para permitir este modo, se puede utilizar el siguiente objeto del tipo View: •
public void setCompatibilityModeEnable(boolean enabled)
La clase Transform3D contiene métodos para construir la matriz de vista y la matriz de proyección. La matriz de vista puede construirse a partir del siguiente método: •
public void lookAt(Point3d eye, Point3d look, Vector3d up)
La matriz de proyección puede construirse con uno de los siguientes métodos: •
public void perspective(double fov, double aspect, double near, double far).
•
Public void frustum(double left, double right, double bottom, double top, double near, double far)
•
Public void ortho(double left, double right, double bottom, double top, double near, double far)
Las coordenadas en estos métodos son relativas a la posición del ojo. El método perspective define una matriz de proyección de perspectiva al especificar el campo horizontal de la vista, la relación de aspecto, y los planos cercanos y lejanos. Los
métodos frustum y ortho definen la matriz de proyección
al
especificar las coordenadas de las esquinas de la vista del volumen. El método frustum define una proyección de perspectiva y el ortho una de paralela. Por ejemplo las siguientes llamadas definen la misma proyección de perspectiva: perspective(Math.PI/2, 2, 1, 2); frustum(-1, 1, -0.5, 0.5, 1, 2) El volumen de vista tiene un campo horizontal de vista de
y una relación de
aspecto de 2.0. La distancia del plano más cercano es 1 y del más lejano 2. El plano cercano es de 2 x 1 y el lejano de 4 x 2. Asumiendo que el plano cercano es la placa de vista la proyección puede representarse como: 96
La matriz de proyección para dicha proyección está dada por:
La matriz de proyección construida apropiadamente y la matriz de vista pueden establecerse con los siguientes métodos de View: •
public void setVpcToEc(Transform3D viewingMatrix)
•
public void setLeftProjection(Transform3D projectionMatrix)
•
public void setRightProjection(Transform3D projectionMatrix)
En el modo de compatibilidad, virtualmente todos los parámetros son establecidos por un objeto View. Por otro lado en el modo de vista estándar (sin compatibilidad), las especificaciones de la vista final pueden ser influenciadas por otros objetos tales como PhysicalBody y PhysicalEnvironment. Los métodos descritos anteriormente no son válidos para el modo de incompatibilidad. El modo de compatibilidad es un modo restringido y no presenta todas las características de la vista de Java 3D. El siguiente código muestra un tetraedro y utiliza vistas de modo de compatibilidad construidas manualmente. import javax.vecmath.*; import java.awt.*; import j ava.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;
97
public class CompatibilityMode extends Applet { public static void main(String[] args) { new MainFrame(new CompatibilityMode(), 640, 650); } public void init() { // create canvas GraphicsConfiguration gc = SimpleUniverse.getPreferredConfiguration(); Canvas3D cv = new Canvas3D(gc); setLayout(new BorderLayout()); add(cv, BorderLayout.CENTER); VirtualUniverse universe = new VirtualUniverse(); Locale locale = new Locale(universe); BranchGroup bg = createView(cv); locale.addBranchGraph(bg); bg = createContent(); bg.compile(); locale.addBranchGraph(bg); } private BranchGroup createView(Canvas3D cv) { BranchGroup bg = new BranchGroup(); ViewPlatform platform = new ViewPlatform(); bg.addChild(platform); View view = new View(); view.addCanvas3D(cv); view.setCompatibilityModeEnable(true); view.attachViewPlatform(platform); Transform3D projection = new Transform3D(); projection.frustum(-0.1, 0.1, -0.1, 0.1, 0.2, 10); view.setLeftProjection(projection); Transform3D viewing = new Transform3D(); Point3d eye = new Point3d(0,0,1); Point3d look = new Point3d(0,0,-1); Vector3d up = new Vector3d(0,1,0); viewing.lookAt(eye, look, up); view.setVpcToEc(viewing); PhysicalBody body = new PhysicalBody(); view.setPhysicalBody(body); PhysicalEnvironment env = new PhysicalEnvironment(); view.setPhysicalEnvironment(env); return bg; } private BranchGroup createContent() { BranchGroup root = new BranchGroup(); TransformGroup spin = new TransformGroup(); spin.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); root.addChild(spin); //object Appearance ap = new Appearance(); ap.setMaterial(new Material()); Shape3D shape = new Shape3D(new Tetrahedron(), ap); //rotating object Transform3D tr = new Transform3D(); tr.setScale(0.25); TransformGroup tg = new TransformGroup(tr); spin.addChild(tg); tg.addChild(shape); Alpha alpha = new Alpha(-1, 4000); RotationInterpolator rotator = new RotationInterpolator(alpha, spin); BoundingSphere bounds = new BoundingSphere(); rotator.setSchedulingBounds(bounds); spin.addChild(rotator); //light and background 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);
98
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; } }
Programa 3.4: Código que muestra un tetraedro y utiliza vistas en modo de compatibilidad.
Figura 3.15: Salida de la ejecución del programa 3.4.
3.2.3 Configuración de vista.
La clase SimpleUniverse divide los componentes de los objetos relacionados en dos clases: la clase Viewer y la ViewingPlatform. Un objeto Viewer consiste en un objeto
View,
un
PhysicalEnvironment
objeto y
un
ViewAvatar, conjunto
de
un
objeto
objetos
PhysicalBody,
Canvas3D.
El
un
objeto
ViewingPlatform contiene un objeto ViewPlatform y un objeto MultiTransform que contiene una serie de nodos del tipo TransformGroup que están relacionados. Los constructores de SimpleUniverse son: •
SimpleUniverse(int numTrans)
•
SimpleUniverse(Canvas3D canvas, int numTrans)
Se puede obtener el View de un objeto SimpleUniverse con la siguiente sentencia: View view = su.getViewer().getView();
99
Así mismo, para obtener el nodo TransformGroup sobre el ViewPlatform, se deben realizar las siguientes llamadas a métodos: TransformGroup tg = su.getViewingPlatform().getViewPlatformTransform(); Para obtener un TransformGroup específico en el objeto MultiTransformGroup, se hace la siguiente llamada: TransformGroup tg = su.getViewingPlatform().getMultiTransformGroup(). getTransformGroup(idx); El siguiente código muestra una aplicación de SimpleUniverse y sus efectos al controlar vistas estándares de Java3D. Este ejemplo despliega un dodecaedro, en su posición fija. Sin embargo la vista se establece en el eje de rotación
,
permitiendo al usuario ver los diferentes lados de la figura. import javax.vecmath.*; import java.awt.*; import j ava.awt.event.*; import javax.media.j3d.*; import com.sun.j3d.utils.geometry.*; public class Dodecahedron extends Shape3D{ public Dodecahedron() { GeometryInfo gi = new GeometryInfo(GeometryInfo.POLYGON_ARRAY); double phi = 0.5*(Math.sqrt(5)+1); Point3d[] vertices = {new Point3d(1,1,1), new Point3d(0,1/phi,phi),new Point3d(phi,0,1/phi),new Point3d(1/phi,phi,0), new Point3d(-1,1,1),new Point3d(0,-1/phi,phi),new Point3d(1,-1,1), new Point3d(phi,0,-1/phi),new Point3d(1,1,-1),new Point3d(-1/phi,phi,0), new Point3d(-phi,0,1/phi),new Point3d(-1,-1,1),new Point3d(1/phi,-phi,0), new Point3d(1,-1,-1),new Point3d(0,1/phi,-phi),new Poi nt3d(-1,1,-1), new Point3d(-1/phi,-phi,0),new Point3d(-phi,0,-1/phi),new Point3d(0,-1/phi,-phi), new Point3d(-1,-1,-1)}; int[] indices = {0,1,5,6,2, 0,2,7,8,3, 0,3,9,4,1, 1,4,10,11,5, 2,6,12,13,7, 3,8,14,15,9, 5,11,16,12,6, 7,13,18,14,8, 9,15,17,10,4, 19,16,11,10,17, 19,17,15,14,18, 19,18,13,12,16}; gi.setCoordinates(vertices); gi.setCoordinateIndices(indices); int[] stripCounts = {5,5,5,5,5,5,5,5,5,5,5,5}; gi.setStripCounts(stripCounts); NormalGenerator ng = new NormalGenerator(); ng.generateNormals(gi); this.setGeometry(gi.getGeometryArray()); } } +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ import javax.vecmath.*; import java.awt.*; import j ava.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 RotateView extends Applet {
100
public static void main(String[] args) { new MainFrame(new RotateView(), 640, 480); } public void init() { // create canvas GraphicsConfiguration gc = SimpleUniverse.getPreferredConfiguration(); Canvas3D cv = new Canvas3D(gc); setLayout(new BorderLayout()); add(cv, BorderLayout.CENTER); SimpleUniverse su = new SimpleUniverse(cv, 2); su.getViewingPlatform().setNominalViewingTransform(); BranchGroup bg = createSceneGraph(su.getViewingPlatform(). getMultiTransformGroup().getTransformGroup(0)); bg.compile(); su.addBranchGraph(bg); } private BranchGroup createSceneGraph(TransformGroup vtg) { BranchGroup root = new BranchGroup(); //object Appearance ap = new Appearance(); ap.setMaterial(new Material()); Shape3D shape = new Dodecahedron(); shape.setAppearance(ap); Transform3D tr = new Transform3D(); tr.setScale(0.25); TransformGroup tg = new TransformGroup(tr); root.addChild(tg); tg.addChild(shape); //view rotator Alpha alpha = new Alpha(-1, 4000); RotationInterpolator rotator = new RotationInterpolator(alpha, vtg); BoundingSphere bounds = new BoundingSphere(); rotator.setSchedulingBounds(bounds); root.addChild(rotator); //lights 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; } }
Programa 3.5: Código que muestra un dodecaedro.
101
Figura 3.16: Salida de la ejecución del programa 3.5.
3.2.4 Creación de una vista propia.
A pesar de que SimpleUniverse proporciona maneras convenientes para construir una amplia rama de aplicaciones en Java 3D, puede que éstas no sean adecuadas en algunos casos. Es posible crear vistas manualmente al establecer los objetos apropiados relacionados con la vista, tales como View, ViewPlatform, PhysicalBody, PhysicalEnvironment, etc. El siguiente fragmento de código ilustra el procedimiento para crear una vista manualmente: View view = new View(); view.setProjectionPolicy(View.PARALLEL_PROYECTION); ViewPlatform vp = new ViewPlatform(); view.addCanvas3D(cv); view.attachViewPlatform(vp); view.setPhysicalBody(new PhysicalBody()); view.setPlysicalEnvironment(new PhysicalEnvironment()); Transform3D trans = new Transform3D(); trans.lookAt(eye, center, vup); trans.invert(); TransformGroup tg = new TransformGroup(trans); tg.addChild(vp); BranchGroup bgView = new BranchGoup(); bgView.addChild(tg); 102
El siguiente código muestra una aplicación con multiples vistas. El objeto, un texto 3D en rotación, es renderizado de cuatro diferentes vistas. Una es la vista de perspectiva estándar poporcionada por SimpleUniverse. Las otras tres son proyecciones paralelas en las direcciones ,
y .
import javax.vecmath.*; import java.awt.*; import j ava.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 MultipleViews extends Applet { public static void main(String[] args) { new MainFrame(new MultipleViews(), 640, 480); } public void init() { // create 4 Canvas3D objects this.setLayout(new GridLayout(2,2)); GraphicsConfiguration gc = SimpleUniverse.getPreferredConfiguration(); // first view: standard Canvas3D cv = new Canvas3D(gc); add(cv); SimpleUniverse su = new SimpleUniverse(cv); su.getViewingPlatform().setNominalViewingTransform(); // second view: x direction cv = new Canvas3D(gc); add(cv); BranchGroup bgView = createView(cv, new Point3d(2.7,0,0), new Point3d(0,0,0), new Vector3d(0,1,0)); su.addBranchGraph(bgView); // third view: z direction cv = new Canvas3D(gc); add(cv); bgView = createView(cv, new Point3d(0, 0, 2.7), new Point3d(0,0,0), new Vector3d(0,1,0)); su.addBranchGraph(bgView); // fourth view: y direction cv = new Canvas3D(gc); add(cv); bgView = createView(cv, new Point3d(0,2.7,0), new Point3d(0,0,0), new Vector3d(0,0,1)); su.addBranchGraph(bgView); // content branch BranchGroup bg = createSceneGraph(); bg.compile(); su.addBranchGraph(bg); } private BranchGroup createView(Canvas3D cv, Point3d eye, Point3d center, Vector3d vup) { View view = new View(); view.setProjectionPolicy(View.PARALLEL_PROJECTION); ViewPlatform vp = new ViewPlatform(); view.addCanvas3D(cv); view.attachViewPlatform(vp); view.setPhysicalBody(new PhysicalBody()); view.setPhysicalEnvironment(new PhysicalEnvironment()); Transform3D trans = new Transform3D(); trans.lookAt(eye, center, vup); trans.invert(); TransformGroup tg = new TransformGroup(trans); tg.addChild(vp); BranchGroup bgView = new BranchGroup(); bgView.addChild(tg);
103
}
return bgView;
private BranchGroup createSceneGraph() { BranchGroup root = new BranchGroup(); TransformGroup spin = new TransformGroup(); spin.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); root.addChild(spin); // object Font3D font = new Font3D(new Font("Serif", Font.PLAIN, 1), new FontExtrusion()); Text3D text = new Text3D(font, "Java"); Appearance ap = new Appearance(); ap.setMaterial(new Material()); Shape3D shape = new Shape3D(text, ap); Transform3D tr = new Transform3D(); tr.setTranslation(new Vector3f(-1f, -0.25f, 0f)); TransformGroup tg = new TransformGroup(tr); spin.addChild(tg); tg.addChild(shape); // rotator Alpha alpha = new Alpha(-1, 24000); RotationInterpolator rotator = new R otationInterpolator(alpha, spin); BoundingSphere bounds = new BoundingSphere(); rotator.setSchedulingBounds(bounds); spin.addChild(rotator); // background and light 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; } }
Programa 3.6: Código que muestra un texto 3D en 4 vistas.
Figura 3.17: Salida de la ejecución del programa 3.6.
104
3.3 Representación tridimens ional de objetos.
La transformación es útil no solo para trasnformar objetos completos, sino que además sirve para construir primitivas. Las primitivas geométricas frecuetemente presentan ciertos grados de simetría. Como resultado, algunos vértices de las primitivas pueden obtenerse al transformar otros vértices. La clase Transform3D proporciona los métodos para aplicar la transformación representada por el objeto en puntos o vectores. •
void transform(Point3d p)
•
void transform(Point3d p, Point3d pOut)
•
void transform(Point3f p)
•
void transform(Point3f p, Point3f pOut)
•
void transform(Vector3d v)
•
void transform(Vector3d v, Vector3d vOut)
•
void transform(Vector3f v)
•
void transform(Vector3f v, Vector3f vOut)
•
void transform(Vector4d v)
•
void transform(Vector4d v, Vector4d vOut)
•
void transform(Vector4f v)
•
void transform(Vector4f v, Vectorfd vOut)
Los métodos con un parámetro transforman al punto o vector en su lugar. Los métodos con dos parámetros realizan transformaciones en el primero, siendo esta una tranformación no destructiva y guarda el resultado en el segundo parámetro. 3.3.1 Superficies de polígonos, curvas y cuadráticas.
Una forma de crear superficies 3D es el extruir o (barrer) una curva 2D a través del espacio. La extrusión puede ser implementada con las traslaciones. Empezando en un punto de la curva se aplica la traslación sobre la dirección de la
105
extrusión para generar otros puntos. Por ejemplo el siguiente código de un método toma una figura 2D y realiza una extrusión sobre el eje . // se assume que la fugura tiene una única curva continua Geometry extrudeShape(Shape curve, float depth) { PathIterator iter = curve.getPathIterator(new AffineTransform()); Vector ptsList = new Vector(); float[] seg = new float[6]; float x = 0, y = 0; float x0 = 0, y0 = 0; while (!iter.isDone()) { int segType = iter.currentSegment(seg); switch (segType) { case PathIterator.SEG_MOVETO: x = x0 = seg[0]; y = y0 = seg[1]; ptsList.add(new Point3f(x,y,0)); break; case PathIterator.SEG_LINETO: x = seg[0]; y = seg[1]; ptsList.add(new Point3f(x,y,0)); break; case PathIterator.SEG_QUADTO: for (int i = 1; i < 10; i++) { float t = (float)i/10f; float xi = (1-t)*(1-t)*x + 2*t*(1-t)*seg[0] + t*t*seg[2]; float yi = (1-t)*(1-t)*y + 2*t*(1-t)*seg[1] + t*t*seg[3]; ptsList.add(new Point3f(xi,yi,0)); } x = seg[2]; y = seg[3]; ptsList.add(new Point3f(x,y,0)); break; case PathIterator.SEG_CUBICTO: for (int i = 1; i < 20; i++) { float t = (float)i/20f; float xi = (1-t)*(1-t)*(1-t)*x + 3*t*(1-t)*(1-t)*seg[0] + 3*t*t*(1-t)*seg[2] + t*t*t*seg[4]; float yi = (1-t)*(1-t)*(1-t)*y + 3*t*(1-t)*(1-t)*seg[1] + 3*t*t*(1-t)*seg[3] + t*t*t*seg[5]; ptsList.add(new Point3f(xi,yi,0)); } x = seg[2]; y = seg[3]; ptsList.add(new Point3f(x,y,0)); break; case PathIterator.SEG_CLOSE: x = x0; y = y0; ptsList.add(new Point3f(x,y,0)); break; } iter.next(); } int n = ptsList.size(); IndexedQuadArray qa = new IndexedQuadArray(2*n, IndexedQuadArray.COORDINATES, 4*(n-1)); Transform3D trans = new Transform3D(); trans.setTranslation(new Vector3f(0,0,depth)); for (int i = 0; i < n; i++) { Point3f pt = (Point3f)ptsList.get(i); qa.setCoordinate(2*i, pt); trans.transform(pt); qa.setCoordinate(2*i+1, pt); } int quadIndex = 0; for (int i = 0; i < n-1; i++) { qa.setCoordinateIndex(quadIndex++, 2*i); qa.setCoordinateIndex(quadIndex++, 2*i+1); qa.setCoordinateIndex(quadIndex++, 2*(i+1)+1); qa.setCoordinateIndex(quadIndex++, 2*(i+1));
106
}
} GeometryInfo gi = new GeometryInfo(qa); NormalGenerator ng = new NormalGenerator(); ng.generateNormals(gi); return gi.getGeometryArray();
Programa 3.7: Código que muestra un método que toma un objeto 2D y le aplica una extrusión.
Muchas superficies pueden obtenerse a través de las rotaciones. Por ejemplo un cilindro puede obtenerse al rotar una línea, y una esfera es el resultado de rotar un semicírculo. Un toro puede construirse al rotar un círculo sobre un eje fuera del mismo círculo. El círculo puede obtenerse al rotar un punto. El rotar un punto repetidamente con diferentes ángulos genera un conjunto de puntos para el círculo. El rotar los puntos del círculo sobre diferentes ejes genera los vértices para el toro. Este procedimiento se ilustra en la siguiente figura:
Figura 3.18: Construcción de un toro a través de rotaciones.
Dados un cojunto de puntos para la curva; para crear la superficie en rotación con franjas, se puede rotar a un ángulo
. Al aplicar sucesivamente la rotación
de los puntos de la curva se pueden generar los vértices para un arreglo. El siguiente código muestra la construcción de un toro via dos rotaciones diferentes. import javax.vecmath.*; import java.awt.*; import j ava.awt.event.*; import javax.media.j3d.*; import com.sun.j3d.utils.universe.*; import com.sun.j3d.utils.geometry.*; public class Torus extends Shape3D { public Torus(double r1, double r2) { int m = 20; int n = 40; Point3d[] pts = new Point3d[m]; pts[0] = new Point3d(r1+r2, 0, 0); double theta = 2.0 * Math.PI / m; double c = Math.cos(theta);
107
double s = Math.sin(theta); double[] mat = {c, -s, 0, r2*(1-c), s, c, 0, -r2*s, 0, 0, 1, 0, 0, 0, 0, 1}; Transform3D rot1 = new Transform3D(mat); for (int i = 1; i < m; i++) { pts[i] = new Point3d(); rot1.transform(pts[i-1], pts[i]); } Transform3D rot2 = new Transform3D(); rot2.rotY(2.0*Math.PI/n); IndexedQuadArray qa = new IndexedQuadArray(m*n, IndexedQuadArray.COORDINATES, 4*m*n); int quadIndex = 0; for (int i = 0; i < n; i++) { qa.setCoordinates(i*m, pts); for (int j = 0; j < m; j++) { rot2.transform(pts[j]); int[] quadCoords = {i*m+j, ((i+1)%n)*m+j, ((i+1)%n)*m+((j+1)%m), i*m+((j+1)%m)}; qa.setCoordinateIndices(quadIndex, quadCoords); quadIndex += 4; } } GeometryInfo gi = new GeometryInfo(qa); NormalGenerator ng = new NormalGenerator(); ng.generateNormals(gi); this.setGeometry(gi.getGeometryArray()); }
}
Programa 3.8: Código que muestra la construcción de un toro vía dos rotaciones diferentes.
El siguiente código despliega dos toros. Los vértices de los toros son generados con transformaciones y se encuentran rotando en el espacio. import javax.vecmath.*; import java.awt.*; import j ava.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 TestTorus extends Applet { public static void main(String[] args) { new MainFrame(new TestTorus(), 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() { BranchGroup root = new BranchGroup(); TransformGroup spin = new TransformGroup(); spin.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); root.addChild(spin);
108
Transform3D tr = new Transform3D(); tr.setScale(0.8); tr.setRotation(new AxisAngle4d(1, 0, 0, Math.PI/6)); TransformGroup tg = new TransformGroup(tr); spin.addChild(tg); //object Shape3D torus1 = new Torus(0.2, 0.5); Appearance ap = new Appearance(); ap.setMaterial(new Material()); torus1.setAppearance(ap); tg.addChild(torus1); Shape3D torus2 = new Torus(0.2, 0.5); ap = new Appearance(); ap.setMaterial(new Material()); ap.setTransparencyAttributes( new TransparencyAttributes(TransparencyAttributes.BLENDED, 0.5f)); torus2.setAppearance(ap); Transform3D tr2 = new Transform3D(); tr2.setRotation(new AxisAngle4d(1, 0, 0, Math.PI/2)); tr2.setTranslation(new Vector3d(0.5,0,0)); TransformGroup tg2 = new TransformGroup(tr2); tg.addChild(tg2); tg2.addChild(torus2); Alpha alpha = new Alpha(-1, 8000); RotationInterpolator rotator = new RotationInterpolator(alpha, spin); BoundingSphere bounds = new BoundingSphere(); rotator.setSchedulingBounds(bounds); spin.addChild(rotator); //background and lights Background background = new Background(1.0f, 1.0f, 1.0f); background.setApplicationBounds(bounds); root.addChild(background); AmbientLight light = new AmbientLight(true, new Color3f(Color.blue)); 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; } }
Programa 3.9: Código que despliega dos toros generados con transformaciones.
Figura 3.19: Salida de la ejecución del programa 3.9.
109
La construcción de contenidos visuales complejos normalmente involucra transformaciones en diferentes niveles. El siguiente ejemplo despliega una flecha. Ilustra la combinación de transformaciones para reusar subestructuras simétricas. import javax.vecmath.*; import javax.media.j3d.*; public class Arrow extends IndexedTriangleArray { float w = 1f; float h = 0.15f; float d = 0.1f; public Arrow() { super(5, TriangleArray.COORDINATES | TriangleArray.NORMALS, 12); Point3f[] pts = {new Point3f(0f,0f,d), new Point3f(w,0f,0f), new Point3f(h,h,0f), new Point3f(h,-h,0f), new Point3f(0f,0f,-d)}; setCoordinates(0, pts); int[] coords = {0,1,2,0,3,1,4,1,3,4,2,1}; setCoordinateIndices(0, coords); Vector3f v1 = new Vector3f(); v1.sub(pts[1], pts[0]); v1.normalize(); Vector3f v2 = new Vector3f(); v2.sub(pts[2], pts[0]); v2.normalize(); Vector3f v = new Vector3f(); v.cross(v1, v2); setNormal(0, v); v.y = -v.y; setNormal(1, v); v.z = -v.z; setNormal(2, v); v.y = -v.y; setNormal(3, v); int[] norms = {0,0,0,1,1,1,2,2,2,3,3,3}; setNormalIndices(0, norms); } }
Programa 3.10: Código que despliega una fl echa ilustrando la combinación de transformaciones.
En el siguiente código se muestra un logo en 3D con 16 flechas y un anillo alrededor de ellas. import javax.vecmath.*; import java.awt.*; import j ava.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 Logo extends Applet { public static void main(String[] args) { new MainFrame(new Logo(), 640, 480); } public void init() { // create canvas GraphicsConfiguration gc = SimpleUniverse.getPreferredConfiguration();
110
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() { BranchGroup root = new BranchGroup(); TransformGroup spin = new TransformGroup(); spin.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); root.addChild(spin); Transform3D tr = new Transform3D(); tr.setScale(0.9); tr.setRotation(new AxisAngle4d(1, 0, 0, Math.PI/2)); TransformGroup tg = new TransformGroup(tr); spin.addChild(tg); //torus Shape3D torus = new Torus(0.04, 0.6); Appearance ap = new Appearance(); ap.setMaterial(new Material()); torus.setAppearance(ap); tg.addChild(torus); // shared group of 4 arrows SharedGroup sg = new SharedGroup(); Shape3D arrow; Transform3D tra; TransformGroup tga; for (int i = 0; i < 4; i++) { arrow = new Shape3D(new Arrow(), ap); tra = new Transform3D(); tra.setRotation(new AxisAngle4d(0, 0, 1, i*Math.PI/2)); tga = new TransformGroup(tra); sg.addChild(tga); tga.addChild(arrow); } // four links to shared group Link link = new Link(); link.setSharedGroup(sg); tr = new Transform3D(); tr.setScale(0.675); tg = new TransformGroup(tr); tg.addChild(link); spin.addChild(tg); link = new Link(); link.setSharedGroup(sg); tr = new Transform3D(); tr.setScale(0.55); tr.setRotation(new AxisAngle4d(0, 0, 1, Math.PI/4)); tg = new TransformGroup(tr); tg.addChild(link); spin.addChild(tg); link = new Link(); link.setSharedGroup(sg); tr = new Transform3D(); tr.setScale(0.4); tr.setRotation(new AxisAngle4d(0, 0, 1, Math.PI/8)); tg = new TransformGroup(tr); tg.addChild(link); spin.addChild(tg); link = new Link(); link.setSharedGroup(sg); tr = new Transform3D(); tr.setScale(0.4); tr.setRotation(new AxisAngle4d(0, 0, 1, 3*Math.PI/8)); tg = new TransformGroup(tr); tg.addChild(link);
111
}
spin.addChild(tg); //rotation Alpha alpha = new Alpha(-1, 8000); RotationInterpolator rotator = new RotationInterpolator(alpha, spin); BoundingSphere bounds = new BoundingSphere(); rotator.setSchedulingBounds(bounds); spin.addChild(rotator); //background and lights 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(2f,2f,2f), new Point3f(1f,0f,0f)); ptlight.setInfluencingBounds(bounds); root.addChild(ptlight); return root; }
Programa 3.11: Código que despliega una figura en 3D.
Figura 3.20: Salida de la ejecución del programa 3.11.
3.3.2 Representaciones de “spline” y curvas Bézier.
Una curva spline consiste en una secuencia de curvas de polinomios unidas suavemente. Las curvas B-spline son comúnmente utilizadas en gráficos por computadora. Las curvas cuadráticas y curvas cúbicas soportadas en Java 2D son casos especiales de curvas Bézier con grados de
y
, repectivamente. Sus
ecuaciones pueden expresarse como:
112
Una curva B-spline está definida por una secuencia de puntos de control; sigue direcciones generales de dichos puntos, pero no necesariamente necesita interpolar los puntos. Una curva general B-spline con grado con puntos de control
está definida por
y una secuencia de parámetros de
conocidos como nodos:
. La ecuación paramétrica
de una curva B-spline puede expresarse como:
La curva está definida no solo sobre los intervalos
. Las funciones
son conocidas como funciones normalizadas B-spline combinadas y pueden definirse recursivamente por:
De otra manera:
Las curvas B-spline son herramientas de modelado muy versátiles. La suavidad y continuidad de la curva puede controlarse por los nodos. Cuando las diferencias entre los valores de nodos adjacentes son constantes:
, la curva se
conoce como B-spline uniforme; en general, las curvas B-spline no son uniformes. La formulación de B-spline puede también aplicarse a coordenadas homogéneas en la misma combinación de funciones para los componentes . Cuando un punto de control está representado en coordenadas homogéneas, la curva se conoce como B-spline racional. Por lo tanto la familia de curvas B-spline más general es conocida como NURBS (B-spline racionales no uniformes).
113
En las curvas B-spline los nodos están uniformemente distribuídos, excepto los primeros cuatro y los últimos cuatro nodos que se establecen para que sean iguales:
Los nodos duplicados ocasionarán que los primeros y últimos puntos de control sean interpolados por la curva, similar a lo que ocurre con una curva Bézier. De hecho si
, una B-spline cúbica de éste tipo es exactamente una curva cúbica
regular Bézier:
denota las características del intervalo de la función
. La ecuación
paramétrica para el B-spline es:
Ésta es exactamente la ecuación de una curva cúbica Bézier. Cuando
, la
curva B-spline tiene más de un segmento polinomial y más puntos de control que una curva Bézier. Java 2D no soporta directamente curvas B-spline; sin embargo una curva cúbica B-spline puede convertirse a varias series de curvas Bézier, que pueden ser renderizadas. Si los puntos
son los puntos de control de una B-spline,
cada segmento puede ser convertido a una curva cúbica Bézier. Si los puntos
114
, son los puntos de control de una curva Bézier, entonces, a excepción del primer y último segmento, la conversión está dada por las siguientes fórmulas:
Tanto el primer como el último segmento son manejados de forma diferente, debido a que el primer y último punto de control son los puntos finales de la curva, la conversión del primer segmento está dado por las siguientes fórmulas:
El último segmento utiliza las siguientes fórmulas:
El siguiente código ilustra la conversión y renderizado de una curva B-spline. En el ejemplo la curva es convertida a una serie de curvas cúbicas Bézier. El programa permite al usuario introducir los puntos de control con un mouse. import java.awt.*; import j ava.awt.geom.*; import j ava.awt.event.*; import java.util.*; import javax.swing.*; public class BSpline extends JApplet { public static void main(String s[]) { JFrame frame = new JFrame(); frame.setTitle("B-Spline");
115
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JApplet applet = new BSpline(); applet.init(); frame.getContentPane().add(applet); frame.pack(); frame.setVisible(true); } public void init() { JPanel panel = new BSplinePanel(); getContentPane().add(panel); } } class BSplinePanel extends JPanel implements MouseListener, MouseMotionListener { Vector points = null; boolean completed = true; public BSplinePanel() { setPreferredSize(new Dimension(640, 480)); setBackground(Color.white); addMouseListener(this); addMouseMotionListener(this); points = new Vector(); } public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D)g; Point p0 = null; Point p1 = null; Point p2 = null; Point p3 = null; float x1,y1,x2,y2,x3,y3, x4, y4; Iterator it = points.iterator(); if (it.hasNext()) { p1 = (Point)(it.next()); } while (it.hasNext()) { p2 = (Point)(it.next()); g2.drawLine(p1.x, p1.y, p2.x, p2.y); p1 = p2; } GeneralPath spline = new GeneralPath(); int n = points.size(); if (n == 0) return; p1 = (Point)points.get(0); spline.moveTo(p1.x, p1.y); g2.drawRect(p1.x-3, p1.y-3, 6, 6); p1 = (Point)points.get(1); p2 = (Point)points.get(2); p3 = (Point)points.get(3); x1 = p1.x; y1 = p1.y; x2 = (p1.x + p2.x)/2.0f; y2 = (p1.y + p2.y)/2.0f; x4 = (2.0f*p2.x+p3.x)/3.0f; y4 = (2.0f*p2.y+p3.y)/3.0f; x3 = (x2+x4)/2.0f; y3 = (y2+y4)/2.0f; spline.curveTo(x1, y1, x2, y2, x3, y3); g2.drawRect((int)x1-3, (int)y1-3, 6, 6); g2.drawRect((int)x2-3, (int)y2-3, 6, 6); g2.drawRect((int)x3-3, (int)y3-3, 6, 6); for (int i = 2; i < n - 4; i++) { p1 = p2; p2 = p3; p3 = (Point)points.get(i+2); x1 = x4; y1 = y4; x2 = (p1.x+2.0f*p2.x)/3.0f; y2 = (p1.y+2.0f*p2.y)/3.0f; x4 = (2.0f*p2.x+p3.x)/3.0f;
116
y4 = (2.0f*p2.y+p3.y)/3.0f; x3 = (x2+x4)/2.0f; y3 = (y2+y4)/2.0f; spline.curveTo(x1,y1,x2,y2,x3,y3); g2.drawRect((int)x1-3, (int)y1-3, 6, 6); g2.drawRect((int)x2-3, (int)y2-3, 6, 6); g2.drawRect((int)x3-3, (int)y3-3, 6, 6); } p1 = p2; p2 = p3; p3 = (Point)points.get(n-2); x1 = x4; y1 = y4; x2 = (p1.x+2.0f*p2.x)/3.0f; y2 = (p1.y+2.0f*p2.y)/3.0f; x4 = (p2.x+p3.x)/2.0f; y4 = (p2.y+p3.y)/2.0f; x3 = (x2+x4)/2.0f; y3 = (y2+y4)/2.0f; spline.curveTo(x1,y1,x2,y2,x3,y3); g2.drawRect((int)x1-3, (int)y1-3, 6, 6); g2.drawRect((int)x2-3, (int)y2-3, 6, 6); g2.drawRect((int)x3-3, (int)y3-3, 6, 6); p2 = p3; p3 = (Point)points.get(n-1); x1 = x4; y1 = y4; x2 = p2.x; y2 = p2.y; x3 = p3.x; y3 = p3.y; spline.curveTo(x1,y1,x2,y2,x3,y3); g2.drawRect((int)x1-3, (int)y1-3, 6, 6); g2.drawRect((int)x2-3, (int)y2-3, 6, 6); g2.drawRect((int)x3-3, (int)y3-3, 6, 6); g2.draw(spline); } public void mouseClicked(MouseEvent ev) { } public void mouseEntered(MouseEvent ev) { } public void mouseExited(MouseEvent ev) { } public void mousePressed(MouseEvent ev) { Graphics g = getGraphics(); if (completed) { points.clear(); completed = false; } if (ev.getClickCount() == 1) { Point p =ev.getPoint(); points.add(p); g.fillOval(p.x-3, p.y-3, 6, 6); } } public void mouseReleased(MouseEvent ev) { if (ev.getClickCount() > 1) { completed = true; repaint(); } } public void mouseMoved(MouseEvent ev) { } public void mouseDragged(MouseEvent ev) { } }
Programa 3.12: Código que muestra una curva B-spline.
117
Figura 3.21: Salida de la ejecución del programa 3.12.
La ecuación paramétrica para una curva Bézier general de un grado de control
Donde
con puntos
está dada por:
es conocido como un polinomio Bernstein o base Bernstein:
Una curva Bézier tiene un grado de
y está definida por cuatro puntos de
control:
El algoritmo deCasteljau proporciona un método para calcular el punto de la curva desde los puntos de control utilizando interpolaciones lineales. El algoritmo construye un esquema triangular de los puntos, similar al triángulo de Pascal. Para la curva Bézier, se utiliza el el siguiente esquema triangular:
118
La primera fila contiene los puntos originales de control de la curva Bézier. Las filas subsecuentes son calculadas con la fórmula:
La última entrada da el valor del punto de la curva Bézier en el punto :
El proceso se ilustra en la siguiente figura, todos los cálculos son simples interpolaciones lineales:
Figura 3.22: Algoritmo deCasteljau
Una curva Bézier puede ser subdividida en dos curvas Bézier. El algoritmo deCasteljau también ofrece un método para la subdivisión. En la figura anterior, dos curvas Bézier subdivididas tienen los puntos de control
y
respectivamente. Versiones actuales de la API Java 3D no incluye soporte directo para curvas y superficies de curvas. Se implementarán las propias versiones de curvas Bézier y superficies utilizando los arreglos de línea y polígonos porporcionados por Java 3D. Otras curvas spline y superficies pueden ser implementadoas como series de curvas y superficies Bézier. El siguiente ejemplo muestra una curva cúbica Bézier con sus subdivisiones recursivas.
119
import javax.vecmath.*; javax.vecmath.*; import java.awt.*; import j ava.awt.event.*; ava.awt.event.*; import javax.media.j3d.*; import com.sun.j3d.utils.universe.*; com.sun.j3d.utils.universe.*; import com.sun.j3d.utils.geometry.*; com.sun.j3d.utils.geometry.*; public class BezierCurve extends Li neStripArray { static int level = 4; static int[] vCnts = {(1<= level){ setCoordinate(index, p3); index++; } else { Point3d p10 = new Point3d(); p10.add(p0,p1); p10.scale(0.5); Point3d p11 = new Point3d(); p11.add(p1,p2); p11.scale(0.5); Point3d p12 = new Point3d(); p12.add(p2,p3); p12.scale(0.5); Point3d p20 = new Point3d(); p20.add(p10,p11); p20.scale(0.5); Point3d p21 = new Point3d(); p21.add(p11,p12); p21.scale(0.5); Point3d p30 = new Point3d(); p30.add(p20,p21); p30.scale(0.5); subdivide(lev+1,p0,p10,p20,p30); subdivide(lev+1,p30,p21,p12,p3); } } }
Programa 3.13: Código que muestra una curva Bézier con sus divisiones recursivas.
El siguiente código despliega la curva Bézier creada en el código anterior. La clase BezierCurve está implementada como una subclase de LinesStripArray. import javax.vecmath.*; javax.vecmath.*; import java.awt.*; import j ava.awt.event.*; ava.awt.event.*; import javax.media.j3d.*; import com.sun.j3d.utils.universe.*; com.sun.j3d.utils.universe.*; import com.sun.j3d.utils.geometry.*; com.sun.j3d.utils.geometry.*; import java.applet.*; import com.sun.j3d.utils.applet.MainFrame; com.sun.j3d.utils.applet.MainFrame; public class TestBezierCurve extends Applet { public static void main(String[] args) { new MainFrame(new TestBezierCurve(), 640, 480); } public void init() { // create canvas GraphicsConfiguration gc = SimpleUniverse.getPreferredConfiguration();
120
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() { BranchGroup root = new BranchGroup(); TransformGroup spin = new TransformGroup(); spin.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); root.addChild(spin); // object Point3d p0 = new Point3d(-1,0,0.5); Point3d p1 = new Point3d(-0.2,0.6,-0.2); Point3d p2 = new Point3d(0.3,-0.8,0.3); Point3d p3 = new Point3d(0.9,0.1,0.6); Appearance ap = new Appearance(); Appearance(); ap.setColoringAttributes(new ColoringAttributes(0f, 0f, 0f, ColoringAttributes.FASTEST)); Shape3D shape = new Shape3D(new BezierCurve(p0,p1,p2,p3), ap); spin.addChild(shape); // rotation interpolator Alpha alpha = new Alpha(-1, 10000); RotationInterpolator rotator = new RotationInterpolator(alpha RotationInterpolator(alpha,, spin); BoundingSphere bounds = new BoundingSphere(); rotator.setSchedulingBounds(bounds); spin.addChild(rotator); // background Background background = new Background(1f, 1f, 1f); background.setApplicationBounds(bounds); root.addChild(background); return root; }
Programa 3.14: Código que despliega una curva Bézier.
Figura 3.23: Salida de la ejecución del programa 3.14.
121
3.3.3 Superficies Bézier.
Una superficie Bézier está formada al extender una curva Bézier a una superficie sobre otra curva Bézier utilizando el método llamado cálculo tensorial.
La
ecuación paramétrica de una superfice de una superficie Bézier está dada por:
El tipo más común utilizado para las superficies Bézier es la superfice bicúbica dada por
, la cual está definida por 16 puntos de control.
La evaluación de un punto
en una superficie Bézier puede también ser
obtenida del algoritmo deCasteljau. Una superficie Bézier puede ser vista como una familia de curvas Bézier. Para una
fija, la curva
es una curva
Bézier con los puntos de control:
Los cuatro puntos de control son cada uno otra curva Bézier. Consecuentemente estos puntos pueden ser calculados a través de cuatro aplicaciones del algoritmo deCasteljau con
. Después de haber obtenido los cuatro puntos de control
para la curva Bézier, se pueden calcular los puntos aplicación del algoritmo de deCasteljau con
, de otra
en la curva.
El siguiente código muestra una superficie bicúbica Bézier: import javax.vecmath.*; import java.awt.*; import j ava.awt.event.*; import javax.media.j3d.*; import com.sun.j3d.utils.geometry.*; public class BezierSurface extends Shape3D{ public BezierSurface(Point3d[][] ctrlPts) { int m = 17; int n = 17; Point3d[] pts = new Point3d[m*n]; int idx = 0; Point3d[] p = new Point3d[4]; double du = 1.0/(m-1); double dv = 1.0/(n-1); double u = 0;
122
double v = 0; for (int i = 0; i < m; i++) { for (int k = 0; k < 4; k++) { p[k] = deCasteljau(u, ctrlPts[k]); } v = 0; for (int j = 0; j < n; j++) { pts[idx++] = deCasteljau(v, p); v += dv; } u += du; } int[] coords = new int[2*n*(m-1)]; idx = 0; for (int i = 1; i < m; i++) { for (int j = 0; j < n; j++) { coords[idx++] = i*n + j; coords[idx++] = (i-1)*n + j; } } int[] stripCounts = new int[m-1]; for (int i = 0; i < m-1; i++) stripCounts[i] = 2*n; GeometryInfo gi = new GeometryInfo(GeometryInfo.TRIANGLE_STRIP_ARRAY); gi.setCoordinates(pts); gi.setCoordinateIndices(coords); gi.setStripCounts(stripCounts);
}
NormalGenerator ng = new NormalGenerator(); ng.generateNormals(gi); this.setGeometry(gi.getGeometryArray());
Point3d deCasteljau(double t, Point3d[] p) { Point3d[] pt = {new Point3d(p[0]),new Point3d(p[1]),new Point3d(p[2]), new Point3d(p[3])}; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3-i; j++) { pt[j].interpolate(pt[j+1], t); } } return pt[0]; } }
Programa 3.15: Código que muestra una superficie bicúbica Bézier.
El siguiente código despliega una superfice Bézier generada aleatoriamente: import javax.vecmath.*; import java.awt.*; import j ava.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 TestBezierSurface extends Applet { public static void main(String[] args) { new MainFrame(new TestBezierSurface(), 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();
123
SimpleUniverse su = new SimpleUniverse(cv); su.getViewingPlatform().setNominalViewingTransform(); su.addBranchGraph(bg); } private BranchGroup createSceneGraph() { BranchGroup root = new BranchGroup(); TransformGroup spin = new TransformGroup(); spin.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); root.addChild(spin); // surface Point3d[][] ctrlPts = new Point3d[4][4]; for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { ctrlPts[i][j] = new Point3d(2-i, 3*(Math.random()-0.5), j-2); } } Shape3D shape = new BezierSurface(ctrlPts); Appearance ap = new Appearance(); ap.setMaterial(new Material()); shape.setAppearance(ap); Transform3D tr = new Transform3D(); tr.setScale(0.25); TransformGroup tg = new TransformGroup(tr); spin.addChild(tg); tg.addChild(shape); // rotation interpolator Alpha alpha = new Alpha(-1, 10000); RotationInterpolator rotator = new RotationInterpolator(alpha, spin); BoundingSphere bounds = new BoundingSphere(); rotator.setSchedulingBounds(bounds); spin.addChild(rotator); // background and lights Background background = new Background(1f, 1f, 1f); 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.lightGray), new Point3f(1f,1f,1f), new Point3f(1f,0f,0f)); ptlight.setInfluencingBounds(bounds); root.addChild(ptlight); return root; } }
Programa 3.16: Código que muestra una superficie Bézier generada aleatoriamente.
Figura 3.24: Salida de la ejecución del programa 3.16.
124
ANEXOS
125
Anexo 1. Preguntas y Ejercicios de Unidad 1. Preguntas de Unidad 1: 1. Lista tres aplicaciones que utilicen gráficos por computadora en 2D. 2. Nombra una aplicación (que no sea un juego) que utilice gráficos por computadora en 3D. 3. Busca en internet y escribe una lista de películas que han aplicado la graficación por computadora. 4. Identifica los campos (graficación por computadora, procesamiento de imágenes, y visión por computadora) que se relacionan con las siguientes aplicaciones: a. Localización de puntos brillantes en una imagen de una mamografía. b. Construir un modelo en 3D de un edificio a través un conjunto de sus imágenes. c. Mostrar una simulación del sistema solar con el sol y los planetas en movimiento. d. Reconocer una parte del cerebro en un escaneo por MRI y mostrar un modelo en 3D del cerebro. e. Utilizar computadoras para generar una escena de un choque automovilístico. f. Realizar una identificación por computadora de una persona a partir de su fotografía. 5. Busca en internet un ejemplo de un programa que utilice GKS. 6. Busca en internet un ejemplo de un programa que utilice PHIGS. 7. Compara OpenGL con Java 3D y haz una lista de ventajas para cada API. 8. Discute las ventajas y desventajas de incluir soporte gráfico en una plataforma de lenguaje estándar como Java. 9. Haz una lista de los componentes GUI más importantes de AWT y encuentra sus equivalentes (más aproximados) en Swing. 10. Lee la documentación de Java 3D y haz una lista de paquetes de Java en Java 3D.
126
Ejercicios de Unidad 1: 1. Escribe un programa en Java (por consola) que llene un arreglo double con 100 números aleatorios e imprima la media y la desviación estándar. 2. Escribe un programa en AWT que dibuje un círculo en medio de la ventana. 3. Escribe un programa en Swing que dibuje un círculo en medio de la ventana. 4. Escribe un programa que permita al usuario arrastrar un círculo relleno en determinada posición indicada por el mouse. 5. Escribe, compila y corre el programa en Java 2D presentado en la página 25. 6. Escribe, compila y corre el programa en Java 3D presentado en la página 26.
Anexo 2. Preguntas y Ejercicios de Unidad 2.
127
Preguntas de Unidad 2: 1. ¿Cuáles son las diferencias entre transformaciones de objeto y transformaciones de vista? 2. Grafica los siguientes puntos en un sistema de coordenadas 2D: (1, 3), (-2, 1.5), (0,-2), (0, 0) 3. Encuentra las coordenadas de los vértices del siguiente triángulo:
4. Escribe un código en Java que dibuje una elipse de 80 de ancho y 100 de alto centrado en (100, 300). 5. Encuentra el ángulo actual que abarca el siguiente arco: new Arc2D.Float(0, 0, 100, 200, 0, 45); 6. ¿Cuál es la matriz de transformación de una rotación de 45° sobre el origen? 7. Construye un objeto AffineTransform para una reflección general sobre una línea que atraviesa el origen. 8. ¿Es
posible
transformar
una
elipse
en
un
círculo
utilizando
transformaciones afines? 9. ¿Es posible transformar un trapezoide a un cuadrado utilizando transformaciones afines? 10. ¿Cuál es la inversa de una rotación del un ángulo
sobre el origen?
11. Encuentra la transformación afin que mapee un eje
a un eje
y uno
a
uno . 12. Encuentra la matriz de transformación para la rotación de un ángulo sobre los puntos
.
13. Encuentra la matriz de transformación para la reflección sobre la línea .
128
14. Encuentra la matriz de transforación para la reflección sobre la línea . 15. Encuentra la matriz de transformación para sesgar sobre el eje
con un
factor de 0.5. 16. Encuentra la matriz de transformación para la composición de rotación de sobre el origen y una reflección sobre la línea 17. Si los valores RGB y
.
de la fuente de un pixel son 0.5, 0.0, 0.8 y 0.4, y los
valores del pixel destino son 0.2, 1.0, 0.5 y 0.6, encuentra el valor RGB y el valor de
para la composición utilizando cada una de las reglas: SrcOver,
DstOver, Src, Dst, SrcOut y DstOut. 18. Si el color destino tiene un valor
de 1.0, ¿cuáles reglas de composición
no serán afectadas por el color de la fuente? 19. Encuentra la matriz de transformación de la traslación que mapea el punto (3, -1, 2) a (0, 5, -1). 20. Encuentra la matriz de transformación de una rotación sobre el eje
por
30°. 21. Encuentra la matriz de transformación de la reflección sobre el plano a través del origen con el vector normal (1, 1, 1). 22. Deriva la matriz de transformación de la reflección sobre el plano a través del origen con un vector normal
como se define a continuación:
23. Calcula el producto del cuaternión: 24. Si
y
son dos vectores de unidad ortogonales,
encuentra la ecuación que mapea 25. Una sesgación de
al eje
;
al eje ; y
x
al el eje .
está definida por la matriz:
Proporcionando los vectores ortogonales de unidad
y
, deriva la
transformación de composición que realiza una sesgación a
por los
factores
.
129
26. Encuentra la matriz de transformación para la rotación 3D representada por el cuaternión: 27. Si eje
es una rotación de 30° sobre el eje
y
una rotación de 60° sobre el
, describe la rotación de composición
en términos de su eje y
ángulo. Ejercicios de Unidad 2: 1. Escribe un programa que grafique las siguientes ecuaciones paramétricas:
2. Escribe un programa que grafique las siguientes ecuaciones paramétricas:
3. Escribe un programa que despliegue un cuadrado centrado en el origen y que presente una rotación de 45°. 4. Escribe un programa que muestra un cuadro centrado en el origen con una rotación de 45°, aplicando una AffineTransform a un objeto Rectangle2D. 5. Escribe un programa que despliegue una imagen espejo de la cadena “Hola 2D” Nota: utiliza una reflección. 6. Escribe un programa que realice una reflección sobre la línea
. El
programa deberá dibujar un rectángulo original de 100 x 50 en (0, 100), aplicando una reflección al rectángulo y presentar la figura transformada en un color diferente. 7. Escribe un programa que dibuje un texto circular alrededor del punto (300, 300). Nota: Utiliza el método drawString para cada carácter y aplica una rotación repetidamente. 8. Escribe un programa que realice una escalación de factor 3 sobre la línea . El programa deberá dibujar un cuadrado original de 100 en (0, 0), aplicando la escalación al cuadrado y deberá dibujar la figura transformada en un color diferente. Nota: Descompone la transformación para realizar una escalación estándar y dos rotaciones. 130
9. Utilizando una figura circular como trayectoria de recorte, escribe un programa que dibuje una cadena de caracteres “Java 2D” con tamaño de fuente grande. 10. Escribe un programa que cargue una imagen y despliegue solo una región elíptica de la misma. Nota: utiliza la trayectoria de recorte para realizar el efecto. 11. Escribe un programa en 3D que despliegue dos tetraedros con una de las caras de uno de sus triángulos en común. 12. Escribe un programa que despliegue dos conos con sus vértices apuntándose uno con otro. 13. Escribe un programa que despliegue un cono con sus vérties apuntando en la dirección (1, 1, 1). 14. Modifica el código del programa de la página 68, para incluir una operación de sesgado
. El programa deberá permitir al usuario especificar los
factores
.
15. Modifica el código del programa de la página 68, para incluir una rotación con ángulos Euler. 16. Modifica el código del programa de la página 74, para construir una matriz de reflección directamente de la siguiente fórmula:
131
Anexo 3. Preguntas y Ejercicios de Unidad 3. Preguntas de Unidad 3: 1. Construye un objeto PointArray para los vértices de un cubo centrados en el origen. 2. Construye un objeto LineArray para las orillas de un tetraedro. 3. Utiliza un objeto TriangleArray para representar un tetraedro. 4. Construye un objeto QuadArray para representar un cubo. 5. Construye un objeto LineStripArray para las orillas de un tetraedro 6. Construye un objeto TriangleStripArray para representar un tetraedro. 7. Construye un objeto TriangleFanArray para representar un tetraedro. 8. Utiliza un objeto IndexedQuadArray para representar un cubo. 9. Dibuja la figura correspondiente al siguiente objeto TriangleStripArray: int[ ] stripVertexCounts = {5, 3}; TriangleStripArray tsa = new TriangleStripArray (8, GeometryArray.COORDINATES, stripVertexCounts); Point3f[ ] coords = new Point3f[8]; cords[0]= new Point3f(0f, 0f, 0f); cords[1]= new Point3f(0f, 1f, 0f); cords[2]= new Point3f(0.5f, 0.866f, 0f); cords[3]= new Point3f(1.5f, 0.866f, 0f); cords[4]= new Point3f(1f, 1.73f, 0f); cords[5]= new Point3f(0f, 1f, 0f); cords[6]= new Point3f(1.5f, 0.866f, 0f); cords[7]= new Point3f(2f, 0f, 0f); tsa.setCoordinates(0, coords);
10. Dibuja la figura que corresponda al siguiente objeto TriangleFanArray: int[ ] stripVertexCounts = {4, 4}; TriangleFanArray tfa = new TriangleFanArray (8, GeometryArray.COORDINATES, stripVertexCounts); Point3f[ ] coords = new Point3f[8]; cords[0]= new Point3f(0f, 0f, 0f); cords[1]= new Point3f(1f, 0f, 0f); cords[2]= new Point3f(0.866f, 0.5f, 0f); cords[3]= new Point3f(0.5f, 0.866f, 0f); cords[4]= new Point3f(0f, 0f, 0f); cords[5]= new Point3f(-1f, 0f, 0f); cords[6]= new Point3f(-0.866f, -0.5f, 0f); cords[7]= new Point3f(-0.5f, -0.866f, 0f); tfa.setCoordinates(0, coords);
11. Dibuja
una
figura
que
corresponda
al
siguiente
objeto
de
IndexedTriangleStripArray: int[ ] stripIndexCounts = {5, 3};
132
IndexedTriangleStripArray itsa = new IndexedTriangleStripArray (6, GeometryArray.COORDINATES, 8, stripIndexCounts); Point3f[ ] coords = new Point3f[6]; cords[0]= new Point3f(0f, 0f, 0f); cords[1]= new Point3f(0f, 1f, 0f); cords[2]= new Point3f(0.5f, 0.866f, 0f); cords[3]= new Point3f(1.5f, 0.866f, 0f); cords[4]= new Point3f(1f, 1.73f, 0f); cords[5]= new Point3f(2f, 0f, 0f); itsa.setCoordinates(0, coords); int[ ] índices = {0, 1, 2, 3, 4, 1, 3, 5}; itsa.setCoordinateIndices(0, indices);
12. Si una vista de frustum tiene un campo de vista de y vista.
NOTA:
, y los dos puntos
son sus dos límites horizontales, encuentra el punto de esta
es
la
transformación
exacta
realizada
por
setNominalViewTransform(). 13. Deriva la matriz de la proyección paralela sobre la dirección (1, 0, 1) en el plano
.
14. Deriva la matriz de la proyección perspectiva con vrp en el origen y la placa vista perpendicular al eje centrado en (0, 0, 1). 15. Encuentra la matriz de proyección para la vista de volumen especificada por la siguiente llamada al método: frustum(-3, 3, -2, 2, 1, 10). 16. Encuentra la matriz de proyección de la vista de volumen especificada por la siguiente llamada: frustum(-2, 2, -1, 1, 2, 4). 17. Encuentra la matriz de proyección de la vista de volumen especificada por la siguiente llamada: perspective(Math.PI/3, 1.5, 1, 10). 18. Encuentra la matriz de proyección de la vista de volumen especificada por la siguiente llamada: ortho(-3, 3, -2, 2, 1, 10). 19. Encuentra la matriz de proyección de la vista de volumen especificada por la siguiente llamada: ortho(-2, 2, -1, 1, 2, 4). 20. Encuentra la matriz de vista al efectuar un cambio de dirección hacia arriba de la misma a (1, 1, 0). 21. Encuentra la matriz de vista especificada por las siguientes llamadas: Point3d eye= new Point3d(0, 0, 0); Point3d look = new Point3d(0, 0, 1); Vector3d up = new Vector3d(0, -1, 0); lookAt(eye, look, up);
22. Encuentra la matriz de vista especificada por las siguientes llamadas:
133
Point3d eye= new Point3d(0, 0, -1); Point3d look = new Point3d(0, 0, 0); Vector3d up = new Vector3d(1, 0, 0); lookAt(eye, look, up);
23. Dada una curva cúbica B-spline definida con
puntos de control, ¿cuántas
curvas Bézier pueden generarse de la conversión? 24. Existe un tipo de curvas B-spline que no imponen restricciones especiales a los puntos finales de las mismas. Tanto el primero como último segmento son tratados de la misma manera, así como otros segmentos; es decir utiliza la misma formula para la conversión a curvas Bézier en cada segmento. Consecuentemente, los primeros y últimos puntos de control, no necesariamente necesitan ser interpolados por la curva. Encuentra la formula de conversión para este tipo de cuarva B-spline. 25. Una curva cúbica Bézier está definida por los siguientes puntos de control: Utiliza un algoritmo de deCasteljau para evaluar los puntos de la curva en: a. b. c. 26. Subdivide en dos las curvas Bézier definidas en la pregunta anterior, utilizando el algoritmo deCasteljau en los puntos: a. b. c. 27. Deriva la formula para la superficie normal de un bicubo Bézier en un punto dado.
Ejercicios de Unidad 3: 1. El octaedro es uno de los cinco sólidos platónicos. Tiene ocho caras en forma de triángulos y seis vértices:
134
Escribe un programa que implemente una clase de tipo Octahedron con su Geometría desendiente. 2. Escribe un programa para la clase Octrahedron que muestre un octaedro en forma de malla.
3.
Escribe un programa que cree una rejilla/cuadrícula 3D de 4 x 4 x 4 utilizando la clase LineArray.
4. Escrique un programa que despliegue un frustum en el espacio virtual, utilizando para ello un objeto IndexedQuadArray para definirlo con un cuadro superior del tamaño 1 x 1, un cuadro inferior de 2 x 2, y que presente una altura de 1. 5. Escribe un programa en 3D que despliegue un objeto ColorCube en rotación utilizando un objeto SimpleUniverse. Modifica la vista de la plataforma para mover ViewPlatform a (0, 0, 3). 6. Modifica el listado del programa de la página 97, para utilizarlo con una vista de proyección paralela.
135
7. Escribe un programa que despliegue una esfera y la mueva cerca de la vista hasta que se trunque parcialmente con el plano frontal. Utiliza para ello un objeto SimpleUniverse. 8. Escribe un programa que examine la matriz generada por el método lookAt de la clase Transform3D. El programa deberá permitir al usuario introducir los parámetros para el método e imprimir la matriz resultante. 9. Escribe un programa como el anterior pero en este caso utilizando los métodos: perspective, frustum y ortho. 10. Escribe un programa con vista de modo de compatibilidad. Utiliza una proyección de perspectiva para el campo de vista
. Coloca la vista en
(0, 0, 1) viendo hacia abajo en el eje . Despliega un objeto de la clase Axes definida en la página 67. 11. Escribe un programa con dos diferentes vistas. Una posicionada en (0, 0, 2) con vista hacia el eje negativo en
y con el eje
en posición hacia
arriba. La otra vista estará posicionada en (0, 0, -2) viendo sobre el eje con el eje negativo en
y
en dirección hacia arriba. Coloca un texto 3D en
rotación cerca del origen. 12. Escribe un programa con las cuatro vistas de los diferentes campos de vista. Todas las vistas están posicionadas en (0, 0, 2) viendo hacia el eje negativo
y con el eje
como dirección hacia arriba. Los campos de las
vistas están establecidas a
,
,
,y
. Coloca un texto en 3D y
un objeto de la clase Axes (página 67) en la escena. 13. Escribe un programa que genere una figura que tenga un cuadrado y cuatro objetos cilíndricos. Utiliza primitivas predefinidas y transformaciones para crearlo. Rota el objeto continuamente en la escena. 14. Escribe un programa que cree y despliegue una figura geométrica que esté formada al rotar la siguiente figura sobre el eje .
136
15. Escribe un programa que implementa la curva B-spline definida en la pregunta de unidad 24. 16. Escribe un programa que implemente una curva B-spline en 3D al convertir una serie de curvas Bézier, similarmente a las curvas en 2D. 17. Escribe un programa que muestre un editor de superficie Bézier. El programa deberá desplegar los puntos de control de la superficie como puntos cuadrados y permitir al usuario seleccionar y mover los puntos de control con el mouse y actualizar la superficie acorde a los movimientos.
Anexo 4. Índice de figuras. UNIDAD 1 Figura 1.1: Tareas principales de la graficación por computadora:
7
modelando un mundo virtual y renderizandolo a una escena Figura 1.2. Círculo creado al ejecutar el código en ensamblador.
13
Figura 1.3. Círculo creado al ejecutar el código en C.
16
Figura 1.4. Círculo creado al ejecutar el código en FORTRAN.
18
Figura 1.5. Círculo creado al ejecutar el código de OpenGL.
20
Figura 1.6. Esfera creada al ejecutar el código de OpenGL.
21
Figura 1.7. Gráfico creado al ejecutar el código de Java2D.
26
Figura 1.8. Gráfico creado al ejecutar el código de Java3D.
28
UNIDAD 2 Figura 2.1 Un objeto gráfico en 2D es procesado para su transformación
31
y su vista.
137
Figura 2.2 Sistema de coordenadas en 2D con el ejes (x, y)
33
Figura 2.3 Línea que pude ser representada por una ecuación lineal.
33
Figura 2.4 Una elipse representada por una ecuación cuadrática.
34
Figura 2.5 Sistema de coordenadas de Java 2D, con el eje x
36
aumentando hacia la derecha y el eje y hacia abajo. Figura 2.6 Una traslación (3, -1)
37
Figura 2.7 Una rotación sobre el origen
37
Figura 2.8 Una reflección sobre una línea diagonal.
38
Figura 2.9 Escalación por los factores (1.5, 2).
38
Figura 2.10 Sesgar por el factor 1 sobre la línea horizontal punteada.
39
Figura 2.11 Salida de la ejecución del programa 2.1.
45
Figura 2.12 Salida de la ejecución del programa 2.2.
48
Figura 2.13 Cuatro eventos diferentes de color que pueden ocurrir en el
50
modelo probabilístico de composición. Figura 2.14 Salida de la ejecución del programa 2.3.
52
Figura 2.15 Salida de la ejecución del programa 2.4.
54
Figura 2.16 Salida de la ejecución del programa 2.6.
61
Figura 2.17 Salida de la ejecución del programa 2.9.
71
Figura 2.18 Salida de la ejecución del programa 2.10.
75
UNIDAD 3 Figura 3.1: Esfera representada por mallas de triángulos de diferentes
79
resoluciones. Figura 3.2: Jerarquía de clases de Geometry.
79
Figura 3.3: Geometría de un PointArray.
80
Figura 3.4: Geometría de un LineArray.
81
Figura 3.5: Geometría de un TriangleArray.
82
Figura 3.6: Geometría de un QuadArray.
83
Figura 3.7: Representación de un TriangleStripArray.
84
Figura 3.8: Representación de un TriangleFanArray.
85
Figura 3.9: Salida de la ejecución del programa 3.2.
88
Figura 3.10: Subclases de la clase Primitive.
88
Figura 3.11: Salida de ejecución del programa 3.3.
90
138
Figura 3.12: Proyección paralela.
91
Figura 3.13: Proyección de perspectiva.
92
Figura 3.14: Ejemplo de proyección perspectiva.
93
Figura 3.15: Salida de la ejecución del programa 3.4.
99
Figura 3.16: Salida de la ejecución del programa 3.5.
102
Figura 3.17: Salida de la ejecución del programa 3.6.
104
Figura 3.18: Construcción de un toro a través de rotaciones.
107
Figura 3.19: Salida de la ejecución del programa 3.9.
109
Figura 3.20: Salida de la ejecución del programa 3.11.
112
Figura 3.21: Salida de la ejecución del programa 3.12.
118
Figura 3.22: Algoritmo deCasteljau
119
Figura 3.23: Salida de la ejecución del programa 3.14.
121
Figura 3.24: Salida de la ejecución del programa 3.16.
124
Anexo 5. Índice de programas. UNIDAD 1 Programa 1.1: Código en ensamblador que muestra un círculo.
11
Programa 1.2: Código en C que muestra un círculo.
14
Programa 1.3: Código en FORTRAN que muestra un círculo.
17
Programa 1.4: Código utilizando OpenGL que muestra un círculo.
19
Programa 1.5: Código utilizando OpenGL que muestra una esfera 3D.
20
Programa 1.6: Código utilizando JOGL que muestra una esfera 3D.
23
Programa 1.7: Código utilizando Java2D que muestra un círculo y texto.
25
Programa 1.8: Código utilizando Java3D que muestra un globo terráqueo
26
en rotación y texto. UNIDAD 2 Programa 2.1: Código que muestra efectos de las transformaciones
42
afines.
139
Programa 2.2: Código que aplica la composición de transformaciones.
48
Programa 2.3: Objetos renderizados con las 12 reglas de composición.
51
Programa 2.4: Código que presenta un ejemplo de trayectoria de recorte.
53
Programa 2.5: Código que proporciona una clase que despliega una
59
matriz. Programa 2.6: Código que proporciona una interface para visualizar una
59
matriz y realizar diversas operaciones sobre ella. Programa 2.7: Código que muestra la conversión de un cuaternión a los
66
ángulos Euler. Programa 2.8: Código que proporciona la clase que despliega un
67
conjunto de coordenadas. Programa 2.9: Código que muestra las características de la clase
68
Transform3D. Programa 2.10: Código que muestra la construcción de reflecciones.
74
UNIDAD 3 Programa 3.1: Código que construye un tetraedro regular.
86
Programa 3.2: Código que despliega un tetraedro en rotación.
87
Programa 3.3: Código que despliega las cuatro primitivas básicas en
90
rotación. Programa 3.4: Código que muestra un tetraedro y utiliza vistas en modo
97
de compatibilidad. Programa 3.5: Código que muestra un dodecaedro.
100
Programa 3.6: Código que muestra un texto 3D en 4 vistas.
103
Programa 3.7: Código que muestra un método que toma un objeto 2D y
106
le aplica una extrusión. Programa 3.8: Código que muestra la construcción de un toro vía dos
107
rotaciones diferentes. Programa 3.9: Código que despliega dos toros generados con
108
transformaciones. Programa 3.10: Código que despliega una flecha ilustrando la
110
combinación de transformaciones. Programa 3.11: Código que despliega una figura en 3D.
110
140
Programa 3.12: Código que muestra una curva B-spline.
115
Programa 3.13: Código que muestra una curva Bézier con sus divisiones
120
recursivas. Programa 3.14: Código que despliega una curva Bézier.
120
Programa 3.15: Código que muestra una superficie bicúbica Bézier.
122
Programa 3.16: Código que muestra una superficie Bézier generada
123
aleatoriamente.
Anexo 6. Índice de tablas. UNIDAD 1 Tabla 1.1: Programación gráfica en diferentes niveles.
10
Tabla 1.2 Capas de los sistemas gráficos.
22
UNIDAD 2 Tabla 2.1 Las 12 reglas de composición Porter –Duff
50
141
GLOSARIO A •
Algoritmo deCasteljau: método para calcular un punto en una curva Bézier a través de una serie de interpolaciones lineales.
•
Ángulos Euler: método que especifica rotaciones 3D con tres rotaciones sobre los ejes de las coordenadas principales.
•
API: Interface de Aplicación del Programador. Una interface de software estandarizada que especifica el uso de funcionalidades proporcionadas por un paquete de software.
•
Avatar: identidad representada gráficamente que adopta un usuario.
•
AWT: Abstract Window Toolkt. Un paquete gráfico de Java existente desde las primeras versions del API de Java.
•
Azimut: arco del horizonte medido entre un punto fijo y el círculo vertical que pasa por el centro de un objeto en el cielo o en la tierra.
142
C •
Centro de vista: una ubicación centrada que define la dirección en donde el ojo está posicionado.
•
Cuaternión: es una extensión de los números reales, similar a la de los números complejos. Mientras que los números complejos son una extensión de los reales por la adición de la unidad imaginaria , tal que , los cuaterniones son una extensión generada de manera análoga añadiendo las unidades imaginarias: , y tal que
•
a los números reales y
.
Curva Bézier: curva definida por una ecuación paramétrica de una combinación de polinomios con puntos de control.
•
Curva B-spline: curva definida por una ecuación paramétrica de un conjunto de variables y combinación de polinomios con una secuencia de puntos de control.
•
Curva 3D Bézier: curva paramétrica de un polinomio 3D definida por un conjunto de puntos de control.
E •
Escalación: transformación geométrica que escala las coordenadas por factores constantes. La escalación es uniforme si los tres factores de los componentes
•
son los mismos.
Espacio de dispositivo: espacio de coordenadas utilizadas por un dispositivo de salida específico.
•
Espacio mundo: referencia común de un espacio con coordenadas para modelos gráficos.
•
Espacio objeto: espacio de coordenadas locales asociadas con un objeto individual.
•
Ecuación paramétrica: conjunto de ecuaciones que expresan variables coordinadas como funciones de parámetros.
F •
FOV: campo de vista. Un ángulo para la porción visible de una vista.
143
•
Frustum: es el volumen de visualización de una cámara en un punto determinado, el cual está generado a partir de una matriz de proyección en perspectiva. Tiene forma de pirámide con la punta recortada. Este volumen de visualización está delimitado por seis planos: plano de recorte lejano, plano de recorte cercano, plano superior, plano inferior, plano izquierdo y plano derecho.
G •
GKS: Sistema de Kernel Gráfico (Graphics Kernel System). Una API de estándar gráfico.
J •
JOGL: Un lenguaje de cubierta en Java para OpenGL.
•
Malla de polígono: conjunto de polígonos simples como los triángulos que
M representan una aproximación de una superficie. •
Matriz de proyección: matriz que define un volumen de vista.
•
Matriz de transformación 3D: matriz que representa una transformación proyectiva 3D.
•
Matriz de vista: matriz que define la ubicación y orientación de una vista en un espacio virtual.
•
Modelado: el proceso de construir un modelo gráfico.
•
Modelo de compatibilidad: un modo de vista especial de Java 3D que es compatible con las vistas tradicionales de OpenGL.
•
Mundo virtual: modelo gráfico construido en una computadora.
•
NURBS: B-spline no uniforme y racional.
N
O
144
•
OpenGL: Una API gráfica popular derivada del GL (Graphics Library) de Silicon Graphics. Tiene una interface de programación típicamente asociada al lenguaje C.
P •
PHIGS: Programmer’s Hierarchical Interactive Graphics System. Una API gráfica.
•
Plano frontal de recorte: el plano fontral de una vista frustum.
•
Plano posterior de recorte: plano posterior de una vista frustum.
•
Polinomio Bernstein: (base de Bernstein) un polinomio especial frecuentemente utilizado en aproximación.
•
POO: Programación Orientada a Objetos. Un paradigma de ingeniería de software que
visualiza a un programa como un sistema de objetos
interrelacionados. •
Primitiva: objeto visual que puede ser utilizado para construir un modelo.
•
Procesamiento de imágenes: campo de la ingeniería eléctrica y las ciencias de la computación para estudiar el procesamiento por computadora de imágenes digitales.
•
Proyección de perspectiva: proyección en donde todos los proyectores pasan a través de un punto fijo.
•
Proyección paralela: proyección en donde todos los proyectores son paralelos.
•
Punto: elemento básico que representa una posición en un espacio.
•
Reflección: transformación geométrica que voltea los puntos sobre un
R plano fijo. •
Reglas Porter-Duff: método que crea nuevas figuras geométricas al utilizar un conjunto de operaciones tales como unión e intersección de las áreas de las figuras existentes.
•
Relación de aspecto: es la proporción de una imagen entre su anchura y su altura.
145
•
Representación de cuaterniones: método que representa una rotación 3D con un cuaternión.
•
Renderizado: proceso de construir una imagen de una escena de un modelo gráfico.
•
Rotación: transformación geométrica que rota un punto alrededor de un eje fijo por un ángulo constante.
S •
Sesgación: transformación
geométrica
que
mueve
los
puntos
paralelamente a un plano. •
Sistema de coordenadas: método para asociar puntos geométricos con cantidades algebraicas de tuplas ordenadas por números.
•
Sólidos platónicos: representa los cinco poliedros regulares: tetraedro, cubo, octaedro, dodecaedro e icosaedro.
•
Superficie Bézier: superficie de polinomio paramétrico definido por una rejilla o cuadrícula de puntos de control.
•
Swing: paquete de gráficos para Java relativamente nuevo y mejorado.
•
Toro: es la superficie de revolución engendrada por una circunferencia que
T gira alrededor de una recta fija de su plano, que no la corta. Proviene del vocablo en latín torus, el cual significa "bulto", ya sea "volumen o tamaño de una cosa" o "elevación de una superficie causada por una protuberancia". Un toro sólido es un objeto tridimensional construido mediante el producto cartesiano de un disco y un círculo. •
Transformación afín: transformación que preserva el paralelismo.
•
Transformación de composición: combinación de dos o más transformaciones para formar una nueva.
•
Transformación proyectiva: transformación geométrica que preserva los puntos, líneas y sus relaciones.
•
Traslación: transformación geométrica que mueve todos los puntos por un valor constante.
146
•
Trayectoria de recorte: figura que define una región para limitar el renderizado.
•
Tupla: es una secuencia ordenada de objetos, esto es, una lista con un número limitado de objetos.
V •
Vector: un elemento geométrico para una cantidad direccional.
•
Visión por computadora: campo de la ingeniería y la computación que estudia la percepción y reconstrucción de una escena de imágenes capturadas.
•
Vista en dirección hacia arriba: dirección que es considerada hacia arriba de la perspectiva del espectador.
•
Volumen de vista: porción de un espacio virtual que presenta la visibilidad de una vista.
•
VRP: punto de referencia, ojo o punto de vista. Es un punto que representa la posición del ojo o el centro de una proyección.
BIBLIOGRAFÍA H. Sowizral, K.Rushforth, M. Deering, The Java 3D API Specification. (2nda Ed.) (2000). Reading, EEUU: Addison-Wesley. H. Zhang, D. Liang. Computer Graphics using JAVA 2D and 3D. (2007). Upper Saddle River, EEUU: Pearson Prentice Hall. J.Foley, A. Van Dam, S. Feiner, J.Hughs, R. Phillips. Introduction to Computer Graphics. (1994). Reading, EEUU: Addison-Wesley. L. Ammeraal. Computer Graphics for JAVA programmers. (1998). New York, EEUU: Wiley. R. Parent. Computer Animation Algorithms and Techniques. (2002). San Francisco, EEUU: Morgan Kaufmann. Enlaces de Internet: 147