Escolar Documentos
Profissional Documentos
Cultura Documentos
Este libro fue desarrollado nicamente con software libre. Entre las herramientas usadas,
AT X, L X, GNU/Linux, GNOME, KDE, XaoS, Maxima, KmPlot, se encuentran: L E Y OpenOce.org, Geany, Inkscape, GIMP, Python, GCC, SDL, PyGAME, GTK+, Qt4,
etc.
CC-BY-NC-SA
Este es un libro libre con la licencia
hedio est orFFF FE e todos los que on(ron en m @on muh freueni muho ms de lo que yo mismoA en este vije en trenD que se llm mi vida Y FE e mi redor y seor que siempre se qued onmigoD n undo yo no me qued siempre on lY FE e todos los que quieren yudr onstruir
el Otro Mundo Posible
Prlogo
Descripcin general
Este libro est siendo desarrollado con el propsito de ser libro de texto para diversas materias impartidas y a impartir para la carrera de Licenciatura en Ciencias de la Computacin y otras carreras de grado y postgrado en la Universidad Centroamericana Jos Simen Caas en El Salvador. Est pensado para estudiantes que ya han aprobado los cursos en los que se estudia clculo innitesimal, geometra analtica vectorial, estructura de datos y programacin orientada a objetos. La gracacin por computadora es una de las principales reas de estudio de las ciencias de la computacin, con aplicacin en todos los mbitos de la computacin; desde la necesidad de representar medianas o grandes cantidades de datos numricos que seran ilegibles en texto, hasta el desarrollo de sosticadas aplicaciones cientcas de simulacin de modelos matemticos del clima; desde el uso de software de edicin de fotografas, hasta el desarrollo de videojuegos. En todos los mbitos, surge la necesidad de tener conocimientos elementales de gracacin por computadora. El contendio del libro comienza con una breve introduccin a SDL (y PyGame) que es la biblioteca grca base a ser usada en los primeros captulos del libro. Se sigue con una introduccin terica a la gracacin por computador, en la que se presentan algunos conceptos elementales que son imprescindibles para la programacin de aplicaciones grcas interactivas. En el captulo siguiente se aborda mpliamente el tema de la discretizacin de lneas rectas y circunferencias de un pixel de grosor. Se aborda tambin el tema de relleno de circunferencias. Luego se procede a hacer un riguroso anlisis vectorial del tema de cambio de coordenadas o cambio de marco de referencia. All se incluye una aplicacin de ejemplo en la que se pone en prctica la teora presentada. Despus, se describe la teora matemtica matricial relacionada con las transformaciones geomtricas bidimensionales y tridimensionales. Se incluye entonces una aplicacin de corte pedaggico que permite practicar transformaciones geomtricas tridimensionales y permite ver en tiempo real sus efectos sobre un conjunto de objetos geomtricamente sencillos. El siguiente captulo, muestra la teora necesaria para comprender cmo transformar
un objeto de tres dimensiones a una imagen bidimensional y cmo implementar tal transformacin. A continuacin, se incluye una reexin sobre el diseo de la interaccin entre el humano y las aplicaciones grcas. Una vez hecha dicha reexin, se procede al estudio de la aproximacin de curvas arbitrarias por medio de segmentos cbicos paramtricos. Y luego de la presentacin de la teora matemtica implicada, se presentan cinco implementaciones sencillas. Despus se hace una breve mencin de supercies paramtricas. Hacia el nal del contenido principal del libro, se presenta una descripcin de las estructuras de datos utilizables para representar mallas poligonales, que representan la tcnica usada para modelar supercies arbitrarias en todo el software de modelacin tridimensional. Finalmente se hace una introduccin a la gracacin por medio de tcnicas fractales que incluye implementacin de aplicaciones de dibujo fractal. Por ltimo, pero no menos importante, se incluyen algunos temas no relacionados directamente con la gracacin por computadora, pero s relevantes para facilitar la lectura y comprensin de algunos temas y cdigos fuente includos. Entre ellos, instrucciones sencillas y concisas para compilar proyectos en lenguaje C estndar distribuidos en varios archivos fuente; y un brevsimo resumen de notacin UML para diagramas de clases y diagramas de objeto.
Detalles de instalacin de las herramientas en ambientes Windows Esto no signica que no sean temas interesantes para un computlogo, o para un cientco. Pero esta edicin simplemente no los contempla.
Motivacin al lector
En general, el autor desea expresar su deseo de seguir mejorando y ampliando, en la medida de lo posible, esta obra para benecio de la sociedad salvadorea. Y tambin desea invitar al apreciable lector a que le saque todo el provecho posible, que aplique sus aportes en todas las reas posibles de su trayectoria profesional; y tambin que no lo considere una serie de armaciones incuestionables, sino una contribucin al desarrollo tecnolgico salvadoreo y a la independencia tecnolgica de la regin centroamericana. Como toda obra acadmica de programacin, o como la mayora, los cdigos (y programas) includos pretenden mantener simplicidad y claridad para no dicultar innecesariamente la comprensin, pero se recomienda hacer el ejercicio de optimizarlos.
hv
pygme
para C.
10
ndice general
I. Gracacin por computadora 21
23
23 23 24 24 25 25 26 26 26 26 28 29 31 33 35 36 37 42 45 46 46 47 48 50 51 52 53 54
Instalacin en distribuciones basadas en Debian . . . . . . . . . . Instalacin en openSuSE . . . . . . . . . . . . . . . . . . . . . . . Instalacin en Fedora y derivados de RedHat Otras distribuciones . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Ejemplos bsicos en SDL . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.3.1. 1.3.2. 1.3.3. 1.3.4. 1.3.5. 1.3.6. 1.3.7. 1.3.8. Inicializacin bsica del sistema de video (el Hola Mundo de SDL): Inicializacin de los subsistemas . . . . . . . . . . . . . . . . . . . Modos de video . . . . . . . . . . . . . . . . . . . . . . . . . . . . Eventos de ratn . . . . . . . . . . . . . . . . . . . . . . . . . . . Eventos de teclado . . . . . . . . . . . . . . . . . . . . . . . . . . Redimensionamiento de la ventana . . . . . . . . . . . . . . . . .
pacilitar
la compilacin
. . . . . . . . . . . . . . . . . . . . . . .
Instalacin de PyGAME . . . . . . . . . . . . . . . . . . . . . . . . . . . Ejemplos bsicos en Python . . . . . . . . . . . . . . . . . . . . . . . . . 1.6.1. 1.6.2. 1.6.3. 1.6.4. 1.6.5. 1.6.6. Inicializacin bsica del sistema de video (el Hola Mundo de
pygme):
Inicializacin de los subsistemas . . . . . . . . . . . . . . . . . . . Modos de video . . . . . . . . . . . . . . . . . . . . . . . . . . . . Eventos de ratn . . . . . . . . . . . . . . . . . . . . . . . . . . . Eventos de teclado . . . . . . . . . . . . . . . . . . . . . . . . . . Redimensionamiento de la ventana . . . . . . . . . . . . . . . . .
1.7. 1.8.
pygme
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11
ndice general
57
57 57 58 58 59 60 61 67 69 74 74 75 75 75 76 76 77 77 78 79
Ciclo de interaccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ciclo de eventos con SDL Ciclo de juego con SDL
Paletas de colores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Paletas estndares actuales 2.5.1. 2.5.2. 2.5.3. RGB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . RGBA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . CMY(K) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.6. 2.7.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
81
81 82 83 84 90 94 95 95 99 101 102 103 105
La simetra de la circunferencia . . . . . . . . . . . . . . . . . . . . . . . Algunas ideas sobre circunferencias . . . . . . . . . . . . . . . . . . . . . Algoritmo de circunferencia de punto medio . . . . . . . . . . . . . . . . 3.7.1. 3.7.2. Versin sin multiplicaciones . . . . . . . . . . . . . . . . . . . . . Circunferencias con centro arbitrario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.8. 3.9.
3.10. Ejercicios
109
109 110 112
12
ndice general
4.3. 4.4. 4.5. Simplicacin escalar para ventana completa Transformacin de distancias . . . . . . . . . . . . . . . 114 115 115 116 118 119 121 129 130
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Aplicacin: Simulador de campo elctrico bidimensional 4.5.1. 4.5.2. 4.5.3. 4.5.4. 4.5.5. Campo Elctrico
. . . . . . . . . . . . . . . . . . . . . . . . . . .
hvgfxrimitives
. . . . . . . . . . . . . .
wkefile .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.6.
Ejercicios
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
133
133 133 134 136 137 138 139 139 140 143
Rotacin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Atencin a la eciencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . Versin matricial del cambio de coordenadas . . . . . . . . . . . . . . . . Reversin de transformaciones geomtricas . . . . . . . . . . . . . . . . . Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
145
145 145
151
151 153 153 156 158 162
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8. Interaccin
8.1. 8.2. Posicionamiento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Seleccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.2.1. 8.2.2. Seleccin por puntero . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
163
163 164 164 165
13
ndice general
8.3. 8.4. Transformacin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Conclusin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 166
9. Curvas Paramtricas
9.1. 9.2. Representacin algebrica de Curvas Paramtricas 9.2.1. 9.2.2. . . . . . . . . . . . . Continuidad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Curvas suaves . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Tipos de continuidad . . . . . . . . . . . . . . . . . . . . . . . . . Continuidad Geomtrica . . . . . . . . . . . . . . . . . . . . . . . Continuidad Paramtrica 9.2.3. 9.3. 9.3.1. 9.3.2. 9.3.3. 9.3.4. 9.3.5. Interpolacin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
167
167 169 169 170 170 170 171 172 172 172 173 173 175 175 175 176 176 176 177 178 178 194 210 210 210 211 211 214 215 215 221 239 240 240
Curvas Spline y B-Spline . . . . . . . . . . . . . . . . . . . . . . . Descripcin geomtrica . . . . . . . . . . . . . . . . . . . . . . . . Descripcin matemtica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Algoritmo de clculo de coecientes
Clculo de un slo punto . . . . . . . . . . . . . . . . . . . . . . . Clculo de todos los puntos . . . . . . . . . . . . . . . . . . . . 9.3.6. Extensin para curvas paramtricas multidimensionales El caso bidimensional El caso tridimensional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Eciencia en el clculo de un slo punto Eciencia en el clculo todos los puntos 9.3.7. 9.3.8. 9.4. 9.4.1. 9.4.2. 9.4.3. 9.4.4. 9.4.5. 9.4.6.
Ejemplo de implementacin . . . . . . . . . . . . . . . . . . . . . Aplicacin para animaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Descripcin geomtrica . . . . . . . . . . . . . . . . . . . . . . . . Descripcin matemtica Polinomios de Bernstein . . . . . . . . . . . . . . . . . . . . . . . Generalizacin de curvas de Bzier de grado Ejemplos de implementacin
Curvas de Bzier
. . . . . . . . . .
9.5. 9.6.
10.Supercies paramtricas
10.1. Representacin algebrica de Supercies paramtricas . . . . . . . . . .
243
243
14
ndice general
10.2. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245
11.Mallas Poligonales
11.1. Representacin explcita . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.2. Apuntadores a una lista de vrtices . . . . . . . . . . . . . . . . . . . . . 11.3. Apuntadores a una lista de aristas 11.5. Ejercicios . . . . . . . . . . . . . . . . . . . . . 11.4. Apuntadores slo a una lista de aristas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
247
247 249 250 253 256
257
257 262 263 263 263 271 272 272 272 275
12.4. Los Conjuntos Julia-Fatou . . . . . . . . . . . . . . . . . . . . . . . . . . 12.4.1. Denicin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.4.2. Implementacin . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.5. Conjunto de Mandelbrot . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.5.1. Denicin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.5.2. Implementacin . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.6. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
277
13.Compilacin desde Mltiples archivos fuente (en lenguaje C) 14.Diagramas de Clases y Diagramas de Objetos (una untadita de UML)
14.1. Notacin de clases 14.3. Notacin de objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14.2. Notacin de relaciones o asociaciones . . . . . . . . . . . . . . . . . . . .
279 283
283 284 287
III. Apndices
289
291 297
297 297 297 298
15
ndice general
B.3. Java Me . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . B.3.1. Sitios de recursos . . . . . . . . . . . . . . . . . . . . . . . . . . . 298 298
16
ndice de guras
1.1. 2.1. 2.2. 2.3. 2.4. 3.1. 3.2. 3.3. 3.4. 3.5. 3.6. 3.7. 3.8. 3.9. 4.1. 4.2. 4.3. 4.4. 4.5. 4.6. 5.1. 5.2. 5.3. 5.4. 5.5. 5.6. 5.7. 5.8. 6.1. Display de 7 segmentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . Modelo de color RGB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 77 78 79 80 82 85 85 92 94 96 101 104 106 110 111 112 113 114 131 134 135 135 136 137 141 141 144 146
Justicacin del algoritmo de lnea de punto medio bsico - 1 Justicacin del algoritmo de lnea de punto medio bsico - 2
Anlisis inverso de punto medio . . . . . . . . . . . . . . . . . . . . . . . Simetra de las circunferencias . . . . . . . . . . . . . . . . . . . . . . . . Algoritmo de circunferencia de punto medio . . . . . . . . . . . . . . . . Esquema para algorito de circunferencias con centro arbitrario . . . . . . Relleno de circunferencias . . . . . . . . . . . . . . . . . . . . . . . . . . Cuadrcula de prctica de primitivas grcas . . . . . . . . . . . . . . . . Un mismo punto en dos marcos de referencia diferentes . . . . . . . . . . Transformacin vectorial de coordenadas . . . . . . . . . . . . . . . . . . Cambio de escala de vectores . . . . . . . . . . . . . . . . . . . . . . . . Situacin de ejemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Caso particular de pantalla completa . . . . . . . . . . . . . . . . . . . . Diagrama para el ejercicio 1 . . . . . . . . . . . . . . . . . . . . . . . . . Ejemplo de traslacin simple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Ejemplo de rotacin compuesta . . . . . . . . . . . . . . . . . . . . . . . Cambio de coordenadas a travs de mltiples marcos de referencia Ejercicio de transformacin bidimensional Cambio de coordenadas con rotacin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17
ndice de guras
6.2. 6.3. 7.1. 7.2. 7.3. 7.4. 9.1. 9.2. 9.3. 9.4. 9.5. Sistema de referencia de mano izquierda . . . . . . . . . . . . . . . . . . . . . . . 146 147 152 154 158 159 168 169 171 212 213 244 244 245 248 248 248
Ejemplos de proyeccin ortogonal . . . . . . . . . . . . . . . . . . . . . . Ejemplos de proyeccin en perspectiva Proyeccin ortogonal y en perspectiva Deduccin de proyeccin en perspectiva . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
y,
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Grca de los Polinomios de Bernstein de grado 3 . . . . . . . . . . . . . Aporte de cada uno de los polinomios de Bernstein de grado 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11.2. Objeto tridimensional simple de ejemplo . . . . . . . . . . . . . . . . . . 11.3. Diagrama de objetos del objeto de la gura 11.2 . . . . . . . . . . . . . . 11.4. Diagrama de clases de una malla poligonal en representacin de apuntadores a una lista de vrtices . . . . . . . . . . . . . . . . . . . . . . . . . 11.5. Otro diagrama de objetos del objeto de la gura 11.2 . . . . . . . . . . . 11.6. Diagrama de clases de una malla poligonal en representacin de apuntadores a una lista de aristas . . . . . . . . . . . . . . . . . . . . . . . . . . 11.7. Objeto tridimensional de ejemplo para representacin de apuntadores a una lista de aristas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.8. Diagrama de objetos del objeto de la gura 11.7 . . . . . . . . . . . . . . 11.9. Detalles de implementacin de una malla poligonal en representacin de apuntadores a una lista de aristas . . . . . . . . . . . . . . . . . . . . . . 11.10. Diagrama de clases de las aplicaciones
249 250
251
251 252
254
trnsformionesQhFjr
perspetivQhFjr
. . . . . . . . . . . . .
255
12.1. Fractal natural: Brassica oleracea, un Romanescu fresco, cortesa del programa Botany Photo of the Day de http://www.ubcbotanicalgarden.org/. 258 12.2. Las ramas de los rboles siguen leyes fractales de distribuin volumtrica. 258 12.3. Las hojas de casi todos los helechos tienen la caracterstica de la autosimilitud nita. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.4. Helecho fractal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259 259 260
12.5. Espiral de satlites con islas de Julia, cortesa del Dr. Wolfgang Beyer
18
ndice de guras
12.6. Acercamiento del conjunto de Mandelbrot realzado por el autor de este libro con el programa XaoS. . . . . . . . . . . . . . . . . . . . . . . . . . 12.7. Otro acercamiento del conjunto de Mandelbrot realizado con la aplicacin 260 261 262 264 265 266 267 268 269 270 273 274 276 276 283 284 285 286 287 291
mndelrotFout
juliFout . . . . . 12.13. Segunda imgen del programa juliFout 12.14. Tercera imgen del programa juliFout . 12.15. Cuarta imgen del programa juliFout .
12.12. Imgen del programa
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
12.16. Forma clsica del conjunto Mandelbrot, generado con erado con la aplicacin XaoS 12.18. Fractal de ejercicio 12.19. Fractal de otro ejercicio
mndelrotFout
14.1. Representacin de una clase . . . . . . . . . . . . . . . . . . . . . . . . . 14.2. Diagrama de clase ocultando sus atributos y operaciones . . . . . . . . . 14.3. Relaciones bidireccionales entre clases 14.4. Otros tipos de relacin entre clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
19
ndice de guras
20
Parte I
21
1.1.
Al igual que con muchas otras bibliotecas de funciones de lenguaje C, la expresin Instalar SDL tiene dos signicados: El primero es instalar las bibliotecas compiladas que contienen el cdigo objeto de las funciones de SDL que los dems programas ya compilados pueden utilizar; y el segundo es, instalar los archivos de cabecera necesarios para la compilacin de cdigo nuevo que use las funciones de SDL. Los usuarios de programas hechos con SDL (juegos, emuladores, etc.) slo necesitan una instalacin del primer tipo, y se dice instalacin de la biblioteca de ejecucin o instalacin de los paquetes de ejecucin. Los programadores necesitamos de la segunda, que se dice instalacin de la biblioteca de desarrollo o instalacin de los paquetes de desarrollo.
6 sdlEonfig EEversion
Si devuelve algo como:
IFPFIP
23
sdl-gfx
sdl-image
antialiasing . Se le conoce tambin como SDL Graphics Efects Primitives. Incluye la funcionalidad de abrir un amplio grupo de formatos de imgenes
desde archivo (la biblioteca base es muy limitada en este aspecto, porque slo
sdl-sound Permite reproducir sonido en diversos formatos. sdl-mixer Permite manipular (mezclar ) sonido en diversos formatos. sdl-net Permite la programacin de aplicaciones con acceso a redes de computadoras. sdl-pango Manipulacin y renderizacin de fuentes con la biblioteca Pango (que es la sdl-ttp Manipulacin y renderizacin de fuentes TrueType. sdl-stretch Permite manipulacin bidimensional de mapas de bits.
Hay muchos otros mdulos/bibliotecas desarrollados por la comunidad, pero estos son los ms portables y populares. Los dems habr que evaluarlos a la luz de su portabilidad (si es que ese factor es relevante) y la funcionalidad que aportan. biblioteca de renderizacin de texto de GTK+).
BFmp).
24
e instalar slo aquellos que se requieran. El comando anterior muestra banderas para cada paquete, y en particular, la bandera i indica que el paquete listado ya est instalado. Si en lugar de dicha letra, aparece una p o una v signica que el paquete no est instalado.
5 ystP EEinstll 8
o
5 yst EEinstll
Hay que esperar a que se descargue y actualize la informacin de los repositorios en lnea y entonces hay que buscar en la interfaz del YaST / YaST2 los paquetes del tipo
lihvBEdevel
e instalarlos.
Tambin se pueden instalar los paquetes desde la lnea de comandos. Para hacerlo, se
e instalar slo aquellos que se requieran. El comando anterior muestra, para cada paquete, la bandera i si el paquete listado ya est instalado y no muestra nada si no lo est.
25
1.2.
Si el programa est en un slo archivo fuente (y no utiliza mdulos adicionales al base), se procede de la siguietne manera:
1.3.
A continuacin se presenta una serie de ejemplos elementales a manera de introduccin a la programacin con SDL en lenguaje C estndar. No pretenden ser exhaustivos, sino solamente introductorios. Se asume que el lector tiene buen dominio del lenguaje C estndar y que tiene buenas prcticas autodidactas.
1
Listing 1.1: Hola Mundo en SDL
Hola Mundo de
26
Hola Mundo en SDL */ # include < SDL / SDL .h > # define ANCHO 323 # define ALTO 400 # define PROFUNDIDAD_COLOR 16 int main ( void ) { SDL_Surface * pantalla = NULL ; SDL_Surface * imagen = NULL ; // Inicializar el subsistema principal de SDL , el de video if ( SDL_Init ( SDL_INIT_VIDEO ) < 0) { printf ( " Error al iniciar SDL : %s \ n " , SDL_GetError () ) ; exit (1) ; } /* * Fuerza la ejecucin de la funcin ' SDL_Quit () ' * an en caso de salida con error , al llamar * a ' exit ( int ) ' o al retornar de la funcin ' main '. * */ atexit ( SDL_Quit ) ; // Abrir la ventana para grficos pantalla = SDL_SetVideoMode ( ANCHO , ALTO , PROFUNDIDAD_COLOR , SDL_SWSURFACE ) ; if ( pantalla == NULL ) { printf ( " Error al inicializar el modo de video : ' %s '\ n " , SDL_GetError () ) ; exit (1) ; } // Cambiar el ttulo de la ventana SDL_WM_SetCaption ( " Hola mundo ! " , NULL ); // Cargar una imagen : imagen = SDL_LoadBMP ( " logo_uca . bmp " ) ; if ( imagen == NULL ) { printf ( " Error al cargar la imagen : ' %s '\ n " , SDL_GetError () ) ; exit (1) ; } // Copiar la imagen al buffer temporal en ' pantalla ': SDL_BlitSurface ( imagen , NULL , pantalla , NULL ) ; // Volcar el buffer a la memoria de video : SDL_Flip ( pantalla ) ; // Esperar un tiempo de 5000 milisegundos SDL_Delay (5000) ;
27
// y luego apagar el sistema de SDL con ' SDL_Quit () ', // en este caso , invocada automticamente con el return . return 0;
SDL_INIT_TIMER Subsistema de temporizador. SDL_INIT_AUDIO Subsistemas de carga y reproduccin de pistas de audio. SDL_INIT_VIDEO Subsistema de video. SDL_INIT_CDROM Subsistema de control y reproduccin de cdrom de audio. SDL_INIT_JOYSTICK Subsistema de interaccin con palancas de mando o controles
de juego.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
/* c01 / ejemplo -02. c Inicializacin de subsistemas en SDL */ # include < SDL / SDL .h > int main ( void ) { SDL_Surface * pantalla = NULL ; /* Los subsistemas disponibles son : * SDL_INIT_TIMER * SDL_INIT_AUDIO * SDL_INIT_VIDEO * SDL_INIT_CDROM * SDL_INIT_JOYSTICK * SDL_INIT_EVERYTHING * */ if ( SDL_Init ( SDL_INIT_VIDEO | SDL_INIT_CDROM | SDL_INIT_AUDIO ) < 0) { printf ( " Error al iniciar SDL con los subsitemas de video , de unidad ptica y de sonido : %s \ n " , SDL_GetError () ) ; exit (1) ; } atexit ( SDL_Quit ) ; // inicializar el subsistema de palanca de mandos : SDL_InitSubSystem ( SDL_INIT_JOYSTICK ) ; // apaga los subsistemas de video y de audio
28
SDL_QuitSubSystem ( SDL_INIT_VIDEO | SDL_INIT_CDROM ) ; if ( SDL_WasInit ( SDL_INIT_VIDEO ) ) printf ( " El video est encendido \ n " ) ; else printf ( " El video est apagado \ n " ) ; if ( SDL_WasInit ( SDL_INIT_CDROM ) ) printf ( " El cdrom est encendido \ n " ) ; else printf ( " El cdrom est apagado \ n " ) ; if ( SDL_WasInit ( SDL_INIT_AUDIO ) ) printf ( " El audio est encendido \ n " ) ; else printf ( " El audio est apagado \ n " ) ; if ( SDL_WasInit ( SDL_INIT_JOYSTICK ) ) printf ( " El joystick est encendido \ n " ) ; else printf ( " El joystick est apagado \ n " ) ; // Apagar todos los subsistemas de SDL automticamente return 0;
Usa la memoria de sistema Usa la memoria de video Activa doble buer en la memoria de video Crea una supercie de dibujo que ocupa toda la pantalla.
1 2 3 4 5
Crea una supercie renderizable con opengl. Crea una ventana de dibujo redimensionable.
Crea una ventana de dibujo sin borde. Listing 1.3: Modos de video
/* c01 / ejemplo -03. c Modos de video en SDL */ # include < SDL / SDL .h > # define ANCHO 400
29
# define ALTO
400
int main ( void ) { SDL_Surface * pantalla = NULL ; SDL_Surface * imagen = NULL ; if ( SDL_Init ( SDL_INIT_VIDEO ) < 0) { printf ( " Error al iniciar SDL con el subsitemas de video : %s \ n " , SDL_GetError () ) ; exit (1) ; } atexit ( SDL_Quit ) ; /* Modos disponibles : * SDL_SWSURFACE usa la memoria de sistema * SDL_HWSURFACE usa la memoria de video * SDL_DOUBLEBUF activa doble buffer en la memoria de video * SDL_FULLSCREEN * SDL_OPENGL * SDL_RESIZABLE * SDL_NOFRAME * */ // Ventana normal de tamao fijo : pantalla = SDL_SetVideoMode ( ANCHO , ALTO , 0 , // Ventana normal de tamao variable : // pantalla = SDL_SetVideoMode ( ANCHO , ALTO , 0 , SDL_RESIZABLE ) ; SDL_SWSURFACE ) ; SDL_SWSURFACE |
// Ventana normal de tamao fijo maximizada ( tamao igual a pantalla completa ) // pantalla = SDL_SetVideoMode ( 0, 0 , 0 , SDL_SWSURFACE ) ; // Ventana sin borde de tamao fijo // pantalla = SDL_SetVideoMode ( ANCHO , ALTO , 0 , SDL_NOFRAME ) ; // Pantalla completa con resolucin fija // pantalla = SDL_SetVideoMode ( ANCHO , ALTO , 0 , SDL_FULLSCREEN ) ; // Pantalla completa con resolucin mxima // pantalla = SDL_SetVideoMode ( 0, 0, 0, SDL_FULLSCREEN ) ; SDL_SWSURFACE |
SDL_SWSURFACE |
SDL_SWSURFACE |
if ( pantalla == NULL ) { printf ( " Error al inicializar el modo de video : ' %s '\ n " , SDL_GetError () ) ; exit (1) ;
30
} printf ( " Tamao de la pantalla : %dx %d \ n " , pantalla - >w , pantalla - > h ) ; // Cargar una imagen : imagen = SDL_LoadBMP ( " logo_uca . bmp " ) ; if ( imagen == NULL ) { printf ( " Error al cargar la imagen : ' %s '\ n " , SDL_GetError () ) ; exit (1) ; } // Copiar la imagen al buffer temporal en ' pantalla ': SDL_BlitSurface ( imagen , NULL , pantalla , NULL ) ; // Volcar el buffer a la memoria de video : SDL_Flip ( pantalla ) ; // Espera un tiempo de 10 segundos SDL_Delay (10000) ; // Apaga manualmente los subsistemas de SDL SDL_Quit () ; } return 0;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
Listing 1.4: Eventos de ratn
/* c01 / ejemplo -04. c Eventos del ratn en SDL */ # include < SDL / SDL .h > # define ANCHO 400 # define ALTO 400 char * nombreBotones [] = { " izquierdo " , " medio " , " derecho " , " rueda arriba " , " rueda abajo " }; char * nombreBoton ( Uint8 boton ) { switch ( boton ) { case SDL_BUTTON_LEFT : return nombreBotones [0]; case SDL_BUTTON_MIDDLE : return nombreBotones [1]; case SDL_BUTTON_RIGHT : return nombreBotones [2];
31
case SDL_BUTTON_WHEELUP : return nombreBotones [3]; case SDL_BUTTON_WHEELDOWN : return nombreBotones [4];
int main ( void ) { SDL_Surface * pantalla = NULL ; SDL_Event evento ; int corriendo = 1; if ( SDL_Init ( SDL_INIT_VIDEO ) < 0) { printf ( " Error al iniciar SDL : %s \ n " , SDL_GetError () ) ; exit (1) ; } atexit ( SDL_Quit ) ; pantalla = SDL_SetVideoMode ( ANCHO , ALTO , 0 , SDL_SWSURFACE ) ; if ( pantalla == NULL ) { printf ( " Error al inicializar el modo de video : ' %s '\ n " , SDL_GetError () ) ; exit (1) ; } SDL_WM_SetCaption ( " Prueba de ratn , mueva el ratn dentro de la ventana " , NULL ) ; while ( corriendo ) { while ( SDL_PollEvent (& evento ) ) { switch ( evento . type ) { /* Consultar el archivo : / usr / include / SDL / SDL_mouse . h All se encuentra la declaracin de los botones en forma de constantes */ case SDL_MOUSEBUTTONDOWN : printf ( " Botn de ratn ' %s ' pulsado en ( %d, %d) \ n " , nombreBoton ( evento . button . button ) , evento . button . x , evento . button . y ) ; break ; case SDL_MOUSEBUTTONUP : printf ( " Botn de ratn ' %s ' liberado en ( %d, %d ) \ n " , nombreBoton ( evento . button . button ) , evento . button . x , evento . button . y ) ; break ; case SDL_MOUSEMOTION :
32
printf ( " El ratn se movi %d, %d pixeles hasta ( %d, %d ) \n", evento . motion . xrel , evento . motion . yrel , evento . motion .x , evento . motion . y ) ; break ; case SDL_QUIT : corriendo = 0; break ;
} }
return 0;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
Listing 1.5: Eventos de teclado
/* c01 / ejemplo -05. c Eventos del teclado en SDL */ # include < SDL / SDL .h > # include < stdio .h > # define ANCHO 640 # define ALTO 480 void mostrarEstado ( SDL_KeyboardEvent * tecla ) { if ( tecla - > type == SDL_KEYUP ) printf ( " SOLTADA : " ); else // SDL_KEYDOWN printf ( " PRESIONADA : " ); } void mostrarModificadores ( SDL_KeyboardEvent * tecla ) { SDLMod modificador = tecla - > keysym . mod ; if ( modificador & KMOD_NUM ) printf ( " NUMLOCK " ) ; if ( modificador & KMOD_CAPS ) printf ( " CAPSLOCK " ) ; if ( modificador & KMOD_MODE ) printf ( " MODE " ) ; if ( modificador & KMOD_LCTRL ) printf ( " LCTRL " ) ; if ( modificador & KMOD_RCTRL ) printf ( " RCTRL " ) ; if ( modificador & KMOD_LSHIFT ) printf ( " LSHIFT " ) ; if ( modificador & KMOD_RSHIFT ) printf ( " RSHIFT " ) ; if ( modificador & KMOD_LALT ) printf ( " LALT " ) ; if ( modificador & KMOD_RALT ) printf ( " RALT " ) ; if ( modificador & KMOD_LMETA ) printf ( " LMETA " ) ;
33
} /*
*/ void mostrarTecla ( SDL_KeyboardEvent * tecla ) { printf ( " Cdigo : %d , Nombre : %s \ n " , tecla - > keysym . sym , SDL_GetKeyName ( tecla - > keysym . sym ) ) ; } int main ( void ) { SDL_Surface * pantalla = NULL ; SDL_Event evento ; int corriendo = 1; if ( SDL_Init ( SDL_INIT_VIDEO ) < 0 ) { fprintf ( stderr , " No se puede iniciar SDL : %s \ n" , SDL_GetError () ) ; exit (1) ; } atexit ( SDL_Quit ) ; pantalla = SDL_SetVideoMode ( ANCHO , ALTO , 0 , SDL_SWSURFACE ) ; if ( pantalla == NULL ) { fprintf ( stderr , " No se puede establecer el modo de video %dx %d : % s\n", ANCHO , ALTO , SDL_GetError () ) ; exit (1) ; } SDL_WM_SetCaption ( " Prueba de teclado , presione las teclas " , NULL ) ; while ( corriendo ) { while ( SDL_PollEvent (& evento ) ) { switch ( evento . type ) { case SDL_KEYDOWN : case SDL_KEYUP : mostrarEstado (& evento . key ) ; mostrarModificadores (& evento . key ) ; mostrarTecla (& evento . key ); break ; case SDL_QUIT : corriendo = 0; break ;
Consultar el archivo : / usr / include / SDL / SDL_keysym . h All se encuentra la declaracin de las teclas en forma de constantes
34
} }
return 0;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
Listing 1.6: Redimensionamiento
/* c01 / ejemplo -06. c * Redimensionamiento de la ventana en SDL */ # include < SDL / SDL .h > int main ( void ) { SDL_Surface * pantalla = NULL ; SDL_Event evento ; int ANCHO = 400 , ALTO = 400; int corriendo = 1; if ( SDL_Init ( SDL_INIT_VIDEO ) < 0) { printf ( " Error al iniciar SDL : %s \ n " , SDL_GetError () ) ; exit (1) ; } atexit ( SDL_Quit ) ; pantalla = SDL_SetVideoMode ( ANCHO , ALTO , 0 , SDL_SWSURFACE | SDL_RESIZABLE ) ; if ( pantalla == NULL ) { printf ( " Error al inicializar el modo de video : ' %s '\ n " , SDL_GetError () ) ; exit (1) ; } SDL_WM_SetCaption ( " Prueba de redimensionamiento " , NULL ) ; while ( corriendo ) { while ( SDL_PollEvent (& evento ) ) { switch ( evento . type ) { /* Consultar el archivo : / usr / include / SDL / SDL_events . h A l l se encuentra la definicin de la unin ' SDL_Event '. */ case SDL_VIDEORESIZE :
35
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
// Redimensionar la pantalla , no hace falta liberar la anterior . pantalla = SDL_SetVideoMode ( evento . resize .w , evento . resize .h , PROFUNDIDAD_COLOR , SDL_SWSURFACE | SDL_RESIZABLE ) ; if ( pantalla == NULL ) { printf ( " Error al redimensionar la pantalla : ' %s '\ n " , SDL_GetError () ) ; exit (1) ; } printf ( " La ventana se ha redimensionado de %dx %d pixeles a %dx %d \ n " , ANCHO , ALTO , evento . resize .w , evento . resize . h ) ; ANCHO = evento . resize . w ; ALTO = evento . resize . h ; // Aqu habra que redibujar lo que se estaba mostrando // ya que ' pantalla ' aparece borrada ( en negro ) . break ; case SDL_QUIT : corriendo = 0; break ;
} }
return 0;
wkefile:
1 2 3 4 5 6 7 8 9 10 11
# c01 / Makefile LDFLAGS = $ ( shell sdl - config -- cflags ) LDLIBS = $ ( shell sdl - config -- libs ) CC = gcc # Existiendo este archivo en el directorio , # se pueden compilar archivos *. c que usen SDL # con slo ejecutar : # '$ make < ejecutable > ' # ( claro que tiene que existir un archivo '< ejecutable >. c ')
36
# Si este archivo Makefile no existiera , para compilar h a b r a que ejecutar : # '$ gcc -o < ejecutable > < ejecutable >. c $ ( sdl - config -- libs -- cflags ) '
Con este archivo en el mismo directorio, podemos usar la utilera compilar un programa fuente:
mke
de los sistemas
basados en Unix. De tal manera que slo haya que ejecutar la siguiente instruccin para
6 mke ejemploEHI
Con esta instruccin y el archivo mencionado, se buscar el archivo compilar en el archivo
ejemploEHI.
ejemploEHIF
y se
mke
1 2 3 4 5
/* c01 / ejemplo -07/ otro . h * */ # include < stdio .h > # define CONSTANTE 5 void funcionOtro ( int entero , FILE *f ) ;
1 2 3 4 5 6 7
/* c01 / ejemplo -07/ otro . c * */ # include " otro . h " void funcionOtro ( int entero , FILE *f ) { fprintf (f , " Cdigo en ' otro . c ' ,\ nparmetro : %d\ nconstante : %d \ n " , entero , CONSTANTE ) ; }
A continuacin se presentan los archivos fuente que continen la implementancin de un par de funciones de primitivas grcas:
1 2 3
37
/* * Enciende el pixel (x , y ) con el color dado . * * SDL no contiene de forma nativa una funcin para * encender pixeles , a diferencia de otras bibliotecas grficas . * Esto en s mismo es un tema de discucin , pero tpicamente * el cdigo de esta funcin aplica en SDL . */ void ponerPixel ( SDL_Surface *s , int x , int y , Uint32 color ) ; /* Dibuja un cuadrado vaco dado por ' rectangulo ' */ void dibujar_rectangulo ( SDL_Surface *s , SDL_Rect rectangulo , Uint32 color );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
/* c01 / ejemplo -07/ primitivas . c * */ # include " primitivas . h " void dibujar_rectangulo ( SDL_Surface *s , SDL_Rect rectangulo , Uint32 color ){ int i; for ( i = rectangulo . x ; i < rectangulo . x + rectangulo . w ; i ++) { ponerPixel (s , i , rectangulo .y , color ) ; ponerPixel (s , i , rectangulo . y + rectangulo .h -1 , color ) ; } for ( i = rectangulo . y ; i < rectangulo . y + rectangulo . h ; i ++) { ponerPixel (s , rectangulo .x , i , color ) ; ponerPixel (s , rectangulo . x + rectangulo .w -1 , i , color ) ; } } void ponerPixel ( SDL_Surface * surface , int x , int y , Uint32 color ) { int bpp = surface - > format - > BytesPerPixel ; // A q u p es la direccin del pixel al que queremos ponerle color Uint8 * p = ( Uint8 *) surface - > pixels + y * surface - > pitch + x * bpp ; if (( x > surface - > w ) ||( y > surface - > h ) ||( x <0) ||( y <0) ) return ; switch ( bpp ) { case 1: * p = color ; break ; case 2: *( Uint16 *) p = color ;
38
break ; case 3: if ( SDL_BYTEORDER == SDL_BIG_ENDIAN ) { p [0] = ( color >> 16) & 0 xff ; p [1] = ( color >> 8) & 0 xff ; p [2] = color & 0 xff ; } else { p [0] = color & 0 xff ; p [1] = ( color >> 8) & 0 xff ; p [2] = ( color >> 16) & 0 xff ; } break ; case 4: *( Uint32 *) p = color ; break ; }
Listing 1.12: Cdigo principal del ejemplo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
/* c01 / ejemplo -07/ main . c * Programa principal * */ # include " otro . h " # include " primitivas . h " # include < stdio .h > int main ( int argc , char * argv []) { SDL_Surface * pantalla = NULL ; SDL_Event evento ; Uint32 color_fondo , color1 , color2 ; SDL_Rect rect1 , rect2 ; int ANCHO = 400 , ALTO = 400; int corriendo = 1; if ( SDL_Init ( SDL_INIT_VIDEO ) < 0) { printf ( " Error al iniciar SDL : %s \ n " , SDL_GetError () ) ; exit (1) ; } atexit ( SDL_Quit ) ; pantalla = SDL_SetVideoMode ( ANCHO , ALTO , 0 , SDL_SWSURFACE | SDL_RESIZABLE ) ; if ( pantalla == NULL ) { printf ( " Error al inicializar el modo de video : ' %s '\ n " , SDL_GetError () ) ; exit (1) ;
39
} // Esta es la forma bsica de construir un color en SDL : color_fondo = SDL_MapRGB ( pantalla - > format ,0 ,0 ,0) ; color1 = SDL_MapRGB ( pantalla - > format ,255 ,0 ,0) ; color2 = SDL_MapRGB ( pantalla - > format ,0 ,255 ,0) ; SDL_WM_SetCaption ( " Cdigo distribuido " , NULL ) ; // Hacer primer dibujo // Dibujar un rectngulo rojo y otro verde rect1 . x = 0; rect1 . y = 0; rect1 . w = ANCHO /2; rect1 . h = ALTO /2; dibujar_rectangulo ( pantalla , rect1 , color1 ) ; rect2 . x = ANCHO /2; rect2 . y = ALTO /2; rect2 . w = ANCHO /2 -1; rect2 . h = ALTO /2 -1; dibujar_rectangulo ( pantalla , rect2 , color2 ) ; // Volcar el buffer en la pantalla SDL_Flip ( pantalla ) ; // llamar una funcin que se encuentra en otro archivo de cdigo funcionOtro (10 , stdout ) ; while ( corriendo ) { while ( SDL_PollEvent (& evento ) ) { switch ( evento . type ) { case SDL_VIDEORESIZE : { ANCHO = evento . resize . w ; ALTO = evento . resize . h ; // Redimensionar la pantalla , no hace falta liberar la anterior . pantalla = SDL_SetVideoMode ( ANCHO , ALTO , 0 , SDL_SWSURFACE | SDL_RESIZABLE ) ; if ( pantalla == NULL ) { printf ( " Error al redimensionar la pantalla : ' %s '\ n " , SDL_GetError () ) ; exit (1) ; } // La pantalla nueva aparece en blanco // o mejor dicho , en negro , // por lo que hay que dibujar de nuevo : rect1 . x = 0;
40
rect1 . y = 0; rect1 . w = ANCHO /2; rect1 . h = ALTO /2; dibujar_rectangulo ( pantalla , rect1 , color1 ) ; rect2 . x = ANCHO /2; rect2 . y = ALTO /2; rect2 . w = ANCHO /2 -1; rect2 . h = ALTO /2 -1; dibujar_rectangulo ( pantalla , rect2 , color2 ) ; // Vuelca el buffer en la pantalla : SDL_Flip ( pantalla ) ;
} break ;
} }
return 0;
Finalmente, para poder compilar fcilmente todo este cdigo, requerimos de un archivo
wkefile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
como el siguiente: Listing 1.13: Archivo Makele para varios fuentes usando SDL
# c01 / ejemplo -07/ Makefile LDFLAGS = $ ( shell sdl - config -- cflags ) LDLIBS = $ ( shell sdl - config -- libs ) RM = / bin / rm -f # Esto indica que los siguientes identificadores , # no son archivos , sino comandos de make : . PHONY : limpiar . PHONY : limpiartodo . PHONY : all # Nombre del programa ejecutable : PROG = ejemplo -07 # Un '*.o ' por cada '*.c ' OBJ = otro . o main . o primitivas . o # Cuando se ejecuta '$ make ' , se ejecuta esto : all : $ ( PROG ) limpiar
41
# Esto compila todo el cdigo y lo enlaza : $ ( PROG ) : $ ( OBJ ) gcc -o $ ( PROG ) $ ( OBJ ) $ ( LDLIBS ) $ ( LDFLAGS ) # Borra todos los archivos intermedios y de copia de seguridad limpiar : $ ( RM ) *~ $ ( OBJ ) # Borra todos los archivos intermedios , de copia de seguridad # y el programa ejecutable , si es que existe limpiartodo : make limpiar $ ( RM ) $ ( PROG )
La funcin
1.4.
3
Desafortunadamente, la biblioteca base de SDL no incluye funciones para encender pixeles ni para dibujar lneas, rectngulos, crculos, polgonos, etc (es decir, primitivas grcas ). Esto es debido a que sus desarrolladores pretenden que se mantenga como una biblioteca multimedia de bajo nivel para que sea ampliada al gusto (y habilidad) del programador que la use, dando la libertad/responsabilidad de construirlas uno mismo. Pero por cuestiones prcticas, en este libro vamos a preferir dibujar primitivas grcas con la ayuda de la biblioteca gfxPrimitives del paquete subseccin 1.1.2 en la pgina 24. Su uso se ilustra en el siguiente programa:
sdlEgfx
mencionada en la
1 2 3 4 5 6 7 8 9 10 11
/* c01 / ejemplo -08/ primitivas . c * */ # include < SDL / SDL .h > # include < SDL / SDL_gfxPrimitives .h > # include < stdio .h > # define ANCHO 800 # define ALTO 640 Uint32 rojo , verde , blanco ;
3
La denicin de Primitiva Grca aparece en el captulo 3 en la pgina 81, pero no es otra cosa que lneas rectas, crculos, rectngulos, polgonos, etc.
42
/*
Las funciones ____Color de SDL_gfx , indicadas en el archivo / usr / include / SDL / SDL_gfxPrimitives .h requieren el color en un entero de 32 bis as : 0 xRRGGBBAA , no acepta el color en ningn otro formato . Adems , utiliza las transparencias por defecto , siempre hay que especificar la opacidad del color .
*/ Uint32 color ( Uint8 r , Uint8 g , Uint8 b ) { return r << 24 | g << 16 | b << 8 | 255; // este valor es la opacidad del color // y debe ser mxima para que sea slido } /* Ver todas las funciones disponibles en el archivo de cabecera : / usr / include / SDL / SDL_gfxPrimitives .h
Hay unas ____Color y otras _____RGBA . */ void dibujar ( SDL_Surface * pantalla ) { // Borrar la pantalla SDL_FillRect ( pantalla , NULL , SDL_MapRGB ( pantalla - > format ,0 ,0 ,0) ) ; /* Las operaciones fuera de SDL_gfx deben ser tratadas con ' SDL_MapRGB ' y ' SDL_MapRGBA ', no con ' Uint32 color ( Uint8 , Uint8 , Uint8 ) ' */ // Dibujar un rectngulo rojo y otro verde boxColor ( pantalla , 0 ,0 , ANCHO /2 , ALTO /2 , rojo ) ; boxColor ( pantalla , ANCHO /2 , ALTO /2 , ANCHO , ALTO , verde ) ; // Lineas ( notar la diferencia visual entre ambas ) : lineColor ( pantalla , 0 ,0 , ANCHO , ALTO , blanco ) ; aalineColor ( pantalla , 0 , ALTO , ANCHO , 0 , blanco ) ; // Lnea horizontal amarilla hlineColor ( pantalla , 0 , ANCHO , ALTO /2 , color (255 , 255 , 0) ) ; stringColor ( pantalla , ANCHO /2 , ALTO /2 , " HOLA " , color (255 ,0 ,255) ) ; stringRGBA ( pantalla , 3* ANCHO /4 , ALTO /4 , " BIENVENIDOS A SDL_gfx " , 255 ,255 ,255 ,255) ;
43
int main ( int argc , char * argv []) { SDL_Surface * pantalla = NULL ; SDL_Event evento ; int corriendo = 1; if ( SDL_Init ( SDL_INIT_VIDEO ) < 0) { printf ( " Error al iniciar SDL : %s \ n " , SDL_GetError () ) ; exit (1) ; } atexit ( SDL_Quit ) ; pantalla = SDL_SetVideoMode ( ANCHO , ALTO , 0 , SDL_SWSURFACE ) ; if ( pantalla == NULL ) { printf ( " Error al inicializar el modo de video : ' %s '\ n " , SDL_GetError () ) ; exit (2) ; } rojo = color (255 ,0 ,0) ; verde = color (0 ,255 ,0) ; // verde = SDL_MapRGBA ( pantalla - > format ,0 ,255 ,0 , 255) ; FUNCIONA blanco = color (255 , 255 , 255) ; SDL_WM_SetCaption ( " Ejemplo de gfx " , NULL ) ; // Hacer primer dibujo dibujar ( pantalla ) ; // Volcar el buffer en la pantalla SDL_Flip ( pantalla ) ; // Esperar que se cierre la ventana while ( corriendo ) { while ( SDL_PollEvent (& evento ) ) { switch ( evento . type ) { case SDL_QUIT : corriendo = 0; break ;
// AS NO
} }
return 0;
Listing 1.15: Makele necesario para usar gfxPrimitives
44
# c01 / ejemplo -08/ Makefile # Ntese que se incluye un parmetro adicional por gfx : LINEA = $ ( shell sdl - config -- cflags -- libs ) - lSDL_gfx RM = / bin / rm -f
. PHONY : limpiar . PHONY : limpiartodo . PHONY : all PROG = gfx OBJ = primitivas . o all : $ ( PROG ) limpiar $ ( PROG ) : $( OBJ ) gcc -o $ ( PROG ) $ ( OBJ ) $ ( LINEA ) limpiar : $ ( RM ) *~ $ ( OBJ )
1.5.
Instalacin de PyGAME
PyGAME es un conjunto de mdulos del lenguaje Python diseados para programar juegos y programas multimedia. PyGAME agrega funcionalidad sobre la biblioteca SDL y se puede interpretar como SDL para Python. En distribuciones basadas en Debian y openSuSE, el paquete en cuestin se llama python-
5 yst Ei pythonEpygme
o
(para la interfaz de texto) (para la interfaz grca de alto nivel) (en lnea de comandos)
5 ystP Ei pythonEpygme
o
45
in situ.
1.6.
Pygame tiene un diseo y un estilo muy parecido al tpico de los mdulos de Python. Por lo cual, hay marcadas diferencias en la forma de programar con SDL en C y con PyGAME en Python. Sin embargo, la nomenclatura de las funciones, constantes y objetos es muy parecida o igual a la de SDL en C.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
Listing 1.16: Hola Mundo en Pygame
Hola Mundo de
# coding : utf -8 ' ' ' c01 / ejemplo -01. py Hola Mundo en pygame ''' import pygame # Inicializar el Sistema de pygame pygame . init () tam = ancho , alto = 323 , 400 # Abrir la ventana para grficos pantalla = pygame . display . set_mode ( tam ) # Cambiar el ttulo de la ventana pygame . display . set_caption ( " Hola Mundo ! " ) # Cargar una imagen :
4
El principal intrprete de Python y los paquetes de
46
imagen = pygame . image . load ( " logo_uca . bmp " ) # Mostrar la imagen : pantalla . blit ( imagen , (0 ,0) ) # Volcar el buffer a la memoria de video : pygame . display . flip () # Esperar 5000 milisegundos pygame . time . delay (5000) # y terminar el programa .
pygame.cdrom Mdulo para control de cdrom de audio. pygame.display Mdulo de video. pygame.font Mdulo de renderizacin de fuentes TrueType. pygame.joystick Mdulo de interaccin con palancas de mando o controles de juego. pygame.mixer Mdulo de carga y reproduccin de pistas de audio.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
Listing 1.17: Inicializacin de subsistemas en Pygame
# coding : utf -8 ' ' ' c01 / ejemplo -02. py Inicializacin de subsistemas en pygame . Los subsistemas / mdulos disponibles son : . - pygame . cdrom . - pygame . display . - pygame . font . - pygame . joystick . - pygame . mixer . - pygame . scap ''' import pygame def mensaje ( nombre , valor_de_verdad ) : ' ' ' Esta funcin simplemente sirve para no escribir la misma cadena varias veces . La combinacin 'and - or ' en Python funciona como el operador ternario de C
47
siempre y cuando el valor entre el ' and ' y el ' or ' se evale a verdadero . ' ' ' print ( " El subsistema de " + nombre + " " + ( valor_de_verdad and " s " or " no " ) + " est encendido " ) ' ' ' La funcin ' init ' inicializa todos los subsistemas disponibles y devuelve el nmero de subsistemas que pudieron ser inicializados y los que no ' ' ' funcionando , no_funcionando = pygame . init () print ( " El nmero de submdulos de pygame funcionando es : " + str ( funcionando ) ) print ( " El nmero de submdulos de pygame que no estn funcionando es : " + str ( no_funcionando ) ) mensaje ( " palanca de mandos " , pygame . joystick . get_init () ) mensaje ( " cdrom " , pygame . cdrom . get_init () ) # Apagar el mdulo de fuentes y de video : pygame . font . quit () mensaje ( " fuentes " , pygame . font . get_init () ) pygame . display . quit () mensaje ( " video " , pygame . display . get_init () ) # Encender el mdulo de fuentes : pygame . font . init () mensaje ( " fuentes " , pygame . font . get_init () ) print ( " Apagando pygame ... " ) ' ' ' Esta funcin apaga todos los pero es llamada automticamente el programa termina , por lo que en el caso que un programa deba sin pygame . ''' pygame . quit () mdulos , cuando slo es necesario seguir corriendo
pygmeFpvvgiix
pygmeFhyfvifp Activa doble buer en la memoria de video, recomendado con rpegi y con yixqv. pygmeFrpegi
Activa aceleracin de hardware, disponible slo con
pvvgiix (en
pantalla completa).
48
pygmeFisefvi pygmeFxypewi
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
Crea una ventana de dibujo sin borde. Listing 1.18: Modos de video en pygame
# coding : utf -8 ' ' ' c01 / ejemplo -03. py Modos de video en Pygame ''' import pygame def mostrarImagen () : # Copiar la imagen al buffer temporal en ' pantalla ': pantalla . blit ( imagen , (0 ,0) ) # Volcar el buffer a la memoria de video : pygame . display . flip () # Espera un tiempo de 10 segundos pygame . time . delay (10000) # Apaga manualmente el sistema de video pygame . display . quit () print ( " Tamao de la pantalla : {0} x {1} " . format ( pantalla . get_width , pantalla . get_height ) ) pygame . init () tam = ancho , alto = 400 , 400 titulo = " Modos de video ! - " imagen = pygame . image . load ( " logo_uca . bmp " ) raw_input ( " Ventana normal de tamao fijo : " + str ( tam ) ) pantalla = pygame . display . set_mode ( tam ) pygame . display . set_caption ( titulo + " Ventana normal de tamao fijo : " + str ( tam ) ) mostrarImagen () raw_input ( " Ventana normal de tamao variable " ) pantalla = pygame . display . set_mode ( tam , pygame . RESIZABLE ) pygame . display . set_caption ( titulo + " Ventana normal de tamao variable " ) mostrarImagen () raw_input ( " Ventana normal de tamao fijo maximizada ( tamao igual a pantalla completa ) " ) pantalla = pygame . display . set_mode () mostrarImagen ()
49
raw_input ( " Ventana sin borde de tamao fijo : " + str ( tam ) ) pantalla = pygame . display . set_mode ( tam , pygame . NOFRAME ) mostrarImagen () raw_input ( " Pantalla completa con resolucin fija : " + str ( tam ) ) pantalla = pygame . display . set_mode ( tam , pygame . FULLSCREEN ) mostrarImagen () raw_input ( " Pantalla completa con resolucin mxima " ) pantalla = pygame . display . set_mode ((0 ,0) , pygame . FULLSCREEN ) mostrarImagen ()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
Listing 1.19: Eventos de ratn
# coding : utf -8 ' ' ' c01 / ejemplo -04. py Eventos del ratn en pygame ''' import pygame pygame . init () tam = ancho , alto = 400 , 400 titulo = " Eventos del ratn " botones = [ " izquierdo " , " medio " , " derecho " , " rueda arriba " , " rueda abajo " ] pantalla = pygame . display . set_mode ( tam ) pygame . display . set_caption ( titulo ) corriendo = True while corriendo : for evento in pygame . event . get () : if evento . type == pygame . MOUSEBUTTONDOWN : print ( " Botn de ratn '{0} ' presionado en {1} " . format (\ botones [ evento . button -1] , evento . pos ) ) elif evento . type == pygame . MOUSEBUTTONUP : print ( " Botn de ratn '{0} ' liberado en {1} " . format (\ botones [ evento . button -1] , evento . pos ) ) elif evento . type == pygame . MOUSEMOTION : print ( " El ratn se movi {0} pixeles hasta {1} " . format (\ evento . rel , evento . pos ) ) print ( " Los botones presionados son : {0}. " . format ( evento . buttons ) ) print ( " \ tBotn izq : {0}\ n \ tBotn cen : {1}\ n \ tBotn der : {2} " . format (\
50
( evento . buttons [0] and " s " or " no " ) , ( evento . buttons [1] and " s " or " no " ) , ( evento . buttons [2] and " s " or " no " ) ) ) elif evento . type == pygame . QUIT : corriendo = False
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
Listing 1.20: Eventos de teclado
# coding : utf -8 ' ' ' c01 / ejemplo -05. py Eventos del teclado en pygame ''' import pygame def devolverModificadores ( m ) : c = ' << ' c += m & pygame . KMOD_NUM and " NUMLOCK " or " " c += m & pygame . KMOD_CAPS and " CAPSLOCK " or " " c += m & pygame . KMOD_MODE and " MODE " or " " c += m & pygame . KMOD_LCTRL and " LCTRL " or " " c += m & pygame . KMOD_RCTRL and " RCTRL " or " " c += m & pygame . KMOD_LSHIFT and " LSHIFT " or " " c += m & pygame . KMOD_RSHIFT and " RSHIFT " or " " c += m & pygame . KMOD_LALT and " LALT " or " " c += m & pygame . KMOD_RALT and " RALT " or " " c += m & pygame . KMOD_LMETA and " LMETA " or " " c += m & pygame . KMOD_RMETA and " RMETA " or " " c += ' >> ' return c def devolverTecla ( tecla ) : return " Cdigo {0} , nombre : '{1} ' " . format ( tecla , pygame . key . name ( tecla ) ) pygame . init () tam = ancho , alto = 400 , 400 titulo = " Eventos del teclado " pantalla = pygame . display . set_mode ( tam ) pygame . display . set_caption ( titulo ) corriendo = True while corriendo :
51
for evento in pygame . event . get () : if evento . type == pygame . KEYDOWN : mensaje = " Presionada " mensaje += devolverModificadores ( evento . mod ) mensaje += devolverTecla ( evento . key ) print ( mensaje ) elif evento . type == pygame . KEYUP : mensaje = " Liberada " mensaje += devolverModificadores ( evento . mod ) mensaje += devolverTecla ( evento . key ) print ( mensaje ) elif evento . type == pygame . QUIT : corriendo = False
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
Listing 1.21: Redimensionamiento
# coding : utf -8 ' ' ' c01 / ejemplo -06. py Redimensionamiento de la ventana en pygame ''' import pygame pygame . init () tam = ancho , alto = 400 , 400 titulo = " Redimensionamiento de la ventana " pantalla = pygame . display . set_mode ( tam , pygame . RESIZABLE ) pygame . display . set_caption ( titulo ) corriendo = True while corriendo : for evento in pygame . event . get () : if evento . type == pygame . VIDEORESIZE : pantalla = pygame . display . set_mode ( evento . size , pygame . RESIZABLE ) print ( " La ventana se ha redimensionado de {0} pixeles a {1} " .\ format ( tam , evento . size ) ) tam = evento . size # Aqu habra que redibujar lo que se estaba mostrando # ya que ' pantalla ' aparece borrada . elif evento . type == pygame . QUIT :
52
corriendo = False
1.7.
En
pygme,
funciones para dibujar primitivas grcas. Este Su uso se ilustra en el siguiente programa:
contiene las
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
# coding : utf -8 ' ' ' c01 / ejemplo -08/ primitivas . py Primitivas grficas ''' import pygame , random ancho , alto = 400 , 400 titulo = " Primitivas grficas " rojo , verde , blanco , negro = (255 ,0 ,0) , (0 ,255 ,0) , (255 ,255 ,255) , (0 ,0 ,0) def dibujar () : # Borrar la pantalla pantalla . fill ( negro ) # Dibujar un rectngulo rojo y otro verde pygame . draw . rect ( pantalla , rojo , pygame . Rect (0 ,0 , ancho /2 , alto /2) ) pygame . draw . rect ( pantalla , verde , pygame . Rect ( ancho /2 , alto /2 , ancho /2 , alto /2) ) # Lineas ( notar la diferencia visual entre ambas ) : pygame . draw . line ( pantalla , blanco , (0 ,0) , ( ancho , alto ) ) pygame . draw . aaline ( pantalla , blanco , (0 , alto ) , ( ancho , 0) ) ; # Lnea horizontal amarilla pygame . draw . line ( pantalla , (255 , 255 , 0) , (0 , alto /2) , ( ancho , alto /2) ) # Inicializar la fuente TrueType por defecto fuente = pygame . font . Font ( None , alto /20) ' ' ' Las fuentes de sistema pueden encontrarse tpicamente en / usr / share / fonts /... y dependen de cada sistema / distribucin . ' '' # Crear el texto saludo = fuente . render ( " HOLA " , True , (255 ,0 ,255) )
53
bienvenida = fuente . render ( " Bienvenidos a pygame " , False , (255 ,255 ,255) , (128 ,128 ,128) ) # Copiar el texto pantalla . blit ( saludo , ( ancho /2 , alto /2) ) pantalla . blit ( bienvenida , (3* ancho /4 , alto /4) ) # Redibujar el buffer pygame . display . flip () pygame . init () pantalla = pygame . display . set_mode (( ancho , alto ) , pygame . RESIZABLE ) pygame . display . set_caption ( titulo ) corriendo = True # Hacer primer dibujo dibujar () while corriendo : for evento in pygame . event . get () : if evento . type == pygame . VIDEORESIZE : pantalla = pygame . display . set_mode ( evento . size , pygame . RESIZABLE ) print ( " La ventana se ha redimensionado de {0} pixeles a {1} " .\ format (( ancho , alto ) , evento . size ) ) ancho , alto = evento . size dibujar () elif evento . type == pygame . QUIT : corriendo = False
1.8.
Ejercicios
tos segn las teclas numricas que presione el usuario. Por ejemplo, si el usuario presiona el nmero 2 (ya sea del teclado numrico o del teclado normal) debera ver algo parecido a lo que aparece en la gura 1.1.
54
1.8 Ejercicios
55
56
2.1.1. El Modelado
Cmo se modelan las cosas en la computadora?
Tiene que describirse, de algn modo, cmo es aquello que queremos representar en la computadora y hasta qu nivel de detalle lo necesitamos representar. Esta descripcin, es el modelo. Obviamente, la forma de modelar las cosas, est supeditada al hecho que debe facilitar el proceso de dibujado de tal modelo y de su respectiva manipulacin por parte del usuario (si es que esto ltimo es parte del propsito de la aplicacin). Por ejemplo, pinsese en cmo modelar una esfera. . . esfrica
r = R (asumiendo que est centrada en el origen), (b) por su ecuacin rect2 2 2 angular: (x x0 ) + (y y0 ) + (z z0 ) = R (en este caso puede estar descentrada),
(c) por las coordenadas de la mayor cantidad posible de puntos que la conforman (para
formar una nube densa de puntos),
(d) etc.
En el primer caso, el modelo es sumamente simple (nada ms es el radio de la esfera), pero el proceso de dibujo se vuelve bastante complejo debido a que hay que recalcular los puntos o lneas a dibujar en cada ocasin que necesitemos generar una imagen de la esfera. Lo cual no es precisamente lo ms eciente si se necesita dibujar la esfera con mucha frecuencia. En el segundo caso, la situacin es igual, excepto que el modelo es ms exible, porque permite describir esferas descentradas, pero nada ms. En el tercer caso, el proceso de dibujado ser ms rpido debido a que los puntos (que se calculan slo la primera vez) ya estarn calculados, y bastar con volverlos a dibujar
57
2.1.2. La Presentacin
Cmo se dibuja el modelo?
Dado el modelo, hay que describir las tcnicas necesarias (o al menos tenerlas muy claras) para poder proyectar o representar el modelo en la pantalla de la computadora (o en algn otro tipo de dispositivo de salida grca). Usualmente esa proyeccin es hacia dos dimensiones (y eso ser as, al menos, mientras no tengamos monitores tridimensionales). Se espera que una buena presentacin describa el modelo lo sucientemente bien como para que el usuario lo comprenda y pueda manipularlo (o al menos imaginrselo) sin necesidad de entender los mecanismos internos de representacin de dicho modelo. Hay que tener en cuenta que la rapidez de los algoritmos empleados para este proceso debe ser, en general, lo ms alta posible para que la aplicacin pueda presentar una interaccin con rapidez humana. Es decir, que el proceso de dibujo del modelo no debe ser muy lento. Tampoco debe ser demasiado rpido, pero eso es un problema menor, la mayora de las veces.
2.1.3. La Interaccin
Cmo interacta el usuario con el modelo?
El modelo no necesariamente tiene una estructura idntica a lo que el usuario ve por medio de la presentacin descrita en la subseccin anterior. Por ello tambin hay que denir la forma en que el usuario interactuar con el modelo (rotar un objeto tridimensional, desplazarlo o cambiarle el tamao). Dependiendo de la naturaleza de la aplicacin, el usuario nal, no necesariamente debe comprender cmo es almacenado y manipulado el modelo internamente, sino que debera poder manipularlo nicamente a travs de lo que ve en la pantalla. Sobre este tema se hablar ms en el captulo 8 en la pgina 163.
58
2.2.
Ciclo de interaccin
En general existen dos formas de implementar el ciclo de interaccin principal de una aplicacin. Estas dos formas son por ciclo de eventos y por ciclo de juego (conocido en ingls como gameloop ). Ambos tienen un propsito diferente, ya que el ciclo de eventos permite escrutar y procesar todos los eventos relevantes para la aplicacin, mientras que el ciclo de juego hace un muestreo del estado de los dispositivos relevantes para la aplicacin en diferentes momentos. Con un ciclo por eventos, se tienen que procesar todos los eventos, teniendo que decidir si analizarlos o ignorarlos, pudiendo provocar una sobrecarga en el algoritmo de procesamiento, llegando a tener que procesar muchos eventos que sucedieron hace mucho
tiempo . Con un ciclo de juego, no hay forma de garantizar que todas las acciones del
usuario sern percibidas y procesadas por la aplicacin, pero el procesamiento tiende menos a retrasarse con respecto a las acciones del usuario. El ciclo de eventos itera a travs de la secuencia de eventos generados, pudiendo estos ocurrir en diferentes lapsos de tiempo, mientras que el ciclo de juego itera tan rpido como la computadora es capaz, o itera aproximadamente cada cierto tiempo, denido por el programador. De manera esquemtica, un ciclo de eventos tiene la siguiente estructura:
1 2 3 4 5 6 7 8 9 10
hayQueEjecutar = Verdadero . . . MIENTRAS ( hayQueEjecutar ) { MIENTRAS ( hayEventos () ) { evento = siguienteEvento () procesarEvento ( evento ) ; } }
1 2 3 4
hayQueEjecutar = Verdadero . . .
1
Hace mucho tiempo para una aplicacin grca podra signicar un par de segundos.
59
MIENTRAS ( hayQueEjecutar ) { t = ahora () ; estado = leerEstadoDeDispositivosRelevantes () ; actualizarObjetosYPosiciones ( estado ); redibujarObjetosVisibles () ; dt = ahora () - t ; SI ( dt < TiempoPorCiclo ) { hacerPausa ( TiempoPorCiclo - dt ); }
evento
1 2 3 4 5 6 7 8 9 10
hvollivent
SDL_Event evento ; int corriendo = 1; . . . while ( corriendo ) { while ( SDL_PollEvent (& evento ) ) { switch ( evento . type ) { case SDL_MOUSEBUTTONDOWN :
60
En el caso de utilizar la segunda, el ciclo de eventos debera parecerse a:
. . .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
hvitivent
SDL_Event evento ; . . . while ( SDL_WaitEvent (& evento ) ) { switch ( evento . type ) { case SDL_MOUSEBUTTONDOWN : . . . break ; case ...: . . . case SDL_QUIT : break ; } }
intQP hvqetiks@voidAY
cializacin de SDL. Esta funcin es til para el control del tiempo que dura cada
61
cada de milisegundos antes de continuar. Esta funcin esperar al menos el tiempo indicado, pero es posible que lleve ms tiempo debido al proceso de asignacin y cambio de procesos (multitarea) del Sistema Operativo en ese momento particular.
El arreglo se puede indexar por las constantes puede contener un 1 que indica que la tecla
est presionada o un 0 que indica que no est presionada. Para poder usar esta funcin, es necesario llamar primero a la funcin
para actualizar el estado del arreglo. Hay que tener en cuenta que las funciones
hvollivent
hvitivent
llaman internamente a
hvwod hvqetwodtte@voidAY
cadoras (CTRL, ALT, CAPS-LOCK, etc.). El estado (de tipo entera que contiene las banderas de la forma a nivel de bits.
uwyhB
hvfyx@vGwGAweu combinadas con disyuncin a nivel de bits. Si las direcciones x y y no son xvv, se almacena all la coordenada del ratn en el momento de la llamada. Al igual que hvqetueytte, esta funcin requiere una llamada previa a hvumpivents, que puede ser ejecutada a travs de hvollivent o hvitivent. intV hvqeteltivewousette@int BdxD int BdyAY Devuelve el estado de los botones del ratn igual que la funcin anterior, pero almacena en dx y dy (si no son xvv) los cambios de posicin del cursor desde la ltima llamada a esta
misma funcin o desde la inicializacin de SDL. La plantilla para un ciclo de eventos con SDL podra ser:
1 2 3 4 5 6 7 8 9
# define MARCOS_POR_SEGUNDO 20 { SDL_Event evento ; Uint8 * teclas ; int corriendo = 1; Uint32 t , dt ; Uint32 TiempoCiclo = ( int ) (1000.0 / MARCOS_POR_SEGUNDO ) ; . .
62
. while ( corriendo ) { t = SDL_GetTicks () ; SDL_PollEvent (& evento ) ; if ( evento . type == SDL_QUIT ) { corriendo = 0; break ; } actualizarPosiciones () ; // revisar colisiones , mover cosas , etc . teclas = SDL_GetKeyState ( NULL ) ; if ( teclas [ SDLK_ESCAPE ]) { corriendo =0; break ; } if ( teclas [ SDLK_LEFT ]) {...} . . . redibujarObjetosVisibles () ; dt = SDL_GetTicks () - t ; if ( dt < TiempoCiclo ) SDL_Delay ( TiempoCiclo - dt ) ;
A continuacin se incluye un pequeo programa de ejemplo, sencillo, sobre ciclo de juego con SDL:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/* * * Programa de ejemplo para ciclos de juego * */ # include < SDL / SDL .h > # include < stdio .h > # include < stdlib .h > # define ANCHO 640 # define ALTO 480 # define INCREMENTO_DESPLAZAMIENTO 10 # define INCREMENTO_DESPLAZAMIENTO_PELOTA 4
63
MARCOS_POR_SEGUNDO 25 FONDO " fondo - trabajar . bmp " PELOTA " pelotita . bmp " TITULO " Tenis ! - Partida %d "
Uint32 color_fondo , color1 , color2 ; SDL_Surface * imgPelota , * imgFondo ; SDL_Rect jugador1 , jugador2 , pelota ; int velx , vely ; void reiniciarPosiciones ( void ) { jugador1 . x = jugador2 . x = ANCHO /2 - jugador1 . w /2; jugador1 . y = 25; jugador2 . y = ALTO - 25 - jugador2 . h ; pelota . x = ( ANCHO /2) - ( pelota . w /2) ; pelota . y = ( ALTO /2) - ( pelota . h /2) ; velx = (( rand () % 2) ? -1: 1) * INCREMENTO_DESPLAZAMIENTO_PELOTA ; vely = (( rand () % 2) ? -1: 1) * INCREMENTO_DESPLAZAMIENTO_PELOTA ;
int puntoEnRectangulo ( int x , int y , SDL_Rect * r ) { return ( x > r - > x && x < r - > x +r - > w ) && ( y > r - >y && y < r - > y+r - > h ) ; } int main ( int argc , char * argv []) { SDL_Surface * pantalla = NULL ; SDL_Event evento ; Uint8 * teclas ; Uint32 t , dt ; Uint32 TiempoCiclo = ( int ) (1000.0 / MARCOS_POR_SEGUNDO ) ; int corriendo = 1; int marcador1 = 0 , marcador2 = 0; int partida = 1; char titulo [50]; /* Inicializacin */ { srand ( time ( NULL ) ) ; if ( SDL_Init ( SDL_INIT_VIDEO ) < 0 ) { fprintf ( stderr , " No se puede iniciar SDL : %s \n " , SDL_GetError () ) ; exit (1) ; } atexit ( SDL_Quit ) ;
64
// Cargar el fondo : imgFondo = SDL_LoadBMP ( FONDO ) ; if ( imgFondo == NULL ) { printf ( " Error al cargar el fondo : ' %s '\ n " , SDL_GetError () ) ; exit (1) ; } // Cargar la Pelota : imgPelota = SDL_LoadBMP ( PELOTA ) ; if ( imgPelota == NULL ) { printf ( " Error al cargar la pelota : ' %s '\ n " , SDL_GetError () ) ; exit (1) ; } SDL_SetColorKey ( imgPelota , SDL_SRCCOLORKEY , SDL_MapRGB ( imgPelota - > format ,255 ,255 ,255) ) ; jugador1 . w = jugador2 . w = 40; jugador1 . h = jugador2 . h = 10; pelota . w = imgPelota - > w ; pelota . h = imgPelota - > h ; reiniciarPosiciones () ; SDL_WM_SetIcon ( imgPelota , NULL ) ; pantalla = SDL_SetVideoMode ( ANCHO , ALTO , 0 , SDL_SWSURFACE ) ; if ( pantalla == NULL ) { fprintf ( stderr , " No se puede establecer el modo de video %dx % d : %s \ n " , ANCHO , ALTO , SDL_GetError () ) ; exit (1) ; } sprintf ( titulo , TITULO , partida ) ; SDL_WM_SetCaption ( titulo , NULL ) ; color_fondo = SDL_MapRGB ( pantalla - > format ,255 ,255 ,255) ; color1 = SDL_MapRGB ( pantalla - > format ,255 ,0 ,0) ; color2 = SDL_MapRGB ( pantalla - > format ,0 ,0 ,255) ;
while ( corriendo ) { t = SDL_GetTicks () ; /* Actualizacin */ { SDL_PollEvent (& evento ) ; if ( evento . type == SDL_QUIT ) { corriendo = 0; break ; } // Movimiento de la pelota
65
pelota . x += velx ; pelota . y += vely ; // Rebotes laterales if ( pelota . x < 0 || pelota . x + pelota . w > ANCHO ) velx *= -1; // Deteccin de choque vertical if ( pelota . y < 0 || pelota . y + pelota . h > ALTO ) { if ( pelota . y < 0) { printf ( " Jugador 2 gana !\ n " ) ; marcador2 ++; } else { printf ( " Jugador 1 gana !\ n " ) ; marcador1 ++; } sprintf ( titulo , TITULO , ++ partida ) ; SDL_WM_SetCaption ( titulo , NULL ) ; reiniciarPosiciones () ; continue ; } // Rebotes verticales if ( puntoEnRectangulo ( pelota . x + pelota . w /2 , pelota .y , & jugador1 ) || puntoEnRectangulo ( pelota . x + pelota . w /2 , pelota . y + pelota .h , & jugador2 ) ) { vely *= -1; continue ; } // Movimiento de los jugadores teclas = SDL_GetKeyState ( NULL ) ; if ( teclas [ SDLK_ESCAPE ]) { corriendo =0; break ; } if ( teclas [ SDLK_LEFT ]) { jugador2 . x -= INCREMENTO_DESPLAZAMIENTO ; if ( jugador2 . x < 0) jugador2 . x = 0; } if ( teclas [ SDLK_RIGHT ]) { jugador2 . x += INCREMENTO_DESPLAZAMIENTO ; if ( jugador2 . x + jugador2 . w > pantalla - > w ) jugador2 . x = pantalla - >w - jugador2 . w ; }
66
if ( teclas [ SDLK_a ]) { jugador1 . x -= INCREMENTO_DESPLAZAMIENTO ; if ( jugador1 . x < 0) jugador1 . x = 0; } if ( teclas [ SDLK_d ]) { jugador1 . x += INCREMENTO_DESPLAZAMIENTO ; if ( jugador1 . x + jugador1 . w > pantalla - > w ) jugador1 . x = pantalla - >w - jugador1 .w ; }
/* Redibujar */ { // Borra la pantalla SDL_BlitSurface ( imgFondo , NULL , pantalla , NULL ) ; // SDL_FillRect ( pantalla , NULL , color_fondo ) ; // Dibujo de los jugadores SDL_FillRect ( pantalla , & jugador1 , color1 ); SDL_FillRect ( pantalla , & jugador2 , color2 ); // Dibujo de la bola SDL_BlitSurface ( imgPelota , NULL , pantalla , & pelota ); // Vuelca el buffer en la memoria de video SDL_Flip ( pantalla ) ;
printf ( " \n < < < < < < < < < < < Marcador final : > > > > > > > > > > >\ nJugador 1: %d \ nJugador 2: %d \n \ n " , marcador1 , marcador2 ) ; } return 0;
Se juega con las teclas a y d para el jugador 1 y con los cursores izquierda y derecha para el jugador 2. Cuando un jugador gana, se inicia una nueva partida.
67
pygmeFxyiix
si no hay
pygmeFeventFwit@A
la cola de eventos y devuelve el primero, cuando llega. Si ya haba eventos en la cola al momento de la llamada, retorna inmediatamente.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
pygmeFeventFpoll@A
corriendo = True . . . while corriendo : evento = pygame . event . poll () if evento == pygame . NOEVENT : continue if evento . type == pygame . MOUSEBUTTONDOWN : . . . elif evento . type == ...: . . . elif evento . type == pygame . QUIT : corriendo = False
3
1 2 3 4 5 6 7 8
pygmeFeventFwit@A
corriendo = True . . . while corriendo : evento = pygame . event . wait () if evento . type == pygame . MOUSEBUTTONDOWN : .
2 3
puede consultarse el archivo puede consultarse el archivo
c02/ejemplo-raton-poll.py c02/ejemplo-raton-wait.py
68
elif evento . type == ...: . . . elif evento . type == pygame . QUIT : corriendo = False
. .
En todo caso, la forma usada (porque tiende ms al estilo de programacin de Python) es con la funcin
pygmeFeventFget@A.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
pygmeFeventFget@A
corriendo = True . . . while corriendo : for evento in pygame . event . get () : if evento . type == pygame . MOUSEBUTTONDOWN : . . . elif evento . type == ...: . . . elif evento . type == pygame . QUIT : corriendo = False break
pygmeFtimeFgettiks@A
izacin de pygame.
pygmeFtimeFwit@milisegundosA Realiza una espera pasiva durante un nmero de milisegundos especicados. Es ligeramente menos preciso que pygmeFtimeFdely
ya que depende del sistema operativo para ser despertado.
69
el estado (presionado o no) de cada tecla del teclado. Se usan las constantes de
pygmeFuB para indexar la secuencia. Esta funcin requiere pygmeFeventFpump, que se realiza automticamente cuando se usan otras funciones del paquete pygmeFevent, como pygmeFeventFget, pygmeFeventFwit o pygmeFeventFpoll.
una llamada previa a
pygmeFkeyFgetmods@A
al de las teclas modicadoras (CTRL, ALT, CAPS-LOCK, etc.). El estado es una variable entera que contiene las banderas de la forma con disyuncin a nivel de bits.
pygmeFuwyhB
combinadas
pygmeFmouseFgetpressed@A
el estado (presionado o no) de los tres botones del ratn. El primero siempre es el botn izquierdo, el segundo el botn central y el tercero es el botn derecho. Al igual que
pygmeFkeyFgetpressed, esta funcin requiere una llamada previa a pygmeFeventFpump, que se realiza automticamente cuando se usan las otras funciones del paquete pygmeFevent.
Devuelve una tupla de la forma
pygmeFmouseFgetpos@A
@xDyA
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
MARCOS_POR_SEGUNDO = 20 t =0; dt =0 TiempoCiclo = int (1000.0 / MARCOS_POR_SEGUNDO ) ; corriendo = True . . . while corriendo : t = pygame . time . get_ticks () for evento in pygame . event . get () : if evento . type == pygame . QUIT : pygame . display . quit () # Cierra la ventana de inmediato corriendo = False break if not corriendo : break actualizarPosiciones () # revisar colisiones , mover cosas , etc .
70
teclas = pygame . key . get_pressed () if teclas [ pygame . K_ESCAPE ]: corriendo = False break if teclas [ pygame . K_LEFT ]: ... . . . redibujarObjetosVisibles () dt = pygame . time . get_ticks () - t if dt < TiempoCiclo : pygame . time . delay ( TiempoCiclo - dt )
A continuacin se incluye un pequeo programa de ejemplo, sencillo, sobre ciclo de juego con pygame:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
# !/ usr / bin / env python # coding : utf -8 " " " c02 / tenis / tenis . py - Programa de ejemplo para ciclos de juego """ import pygame , random , sys ANCHO , ALTO = 640 , 480 INCREMENTO_DESPLAZAMIENTO = 10 INCREMENTO_DESPLAZAMIENTO_PELOTA = 4
X, Y = 0, 1 MARCOS_POR_SEGUNDO = 25 FONDO = " fondo - trabajar . bmp " PELOTA = " pelotita . bmp " TITULO = " Tenis ! - Partida {0} " def reiniciarPosiciones () : jugador1 . x = jugador2 . x = ANCHO /2 - jugador1 . w /2; jugador1 . y = 25; jugador2 . y = ALTO - 25 - jugador2 . h ; pelota . x = ( ANCHO /2) - ( pelota . w /2) ; pelota . y = ( ALTO /2) - ( pelota . h /2) ; vel [ X ] = ( random . randint (0 ,1) and -1 or 1) * INCREMENTO_DESPLAZAMIENTO_PELOTA vel [ Y ] = ( random . randint (0 ,1) and -1 or 1) * INCREMENTO_DESPLAZAMIENTO_PELOTA
71
# Inicializacin : t =0; dt =0 TiempoCiclo = int (1000.0 / MARCOS_POR_SEGUNDO ) ; marcador1 = 0; marcador2 = 0 partida = 1 corriendo = True pygame . init () # Cargar el fondo : imgFondo = pygame . image . load ( FONDO ) # Cargar la Pelota : imgPelota = pygame . image . load ( PELOTA ) imgPelota . set_colorkey ((255 ,255 ,255) ) jugador1 = pygame . Rect ((0 ,0) , (40 ,10) ) jugador2 = pygame . Rect ((0 ,0) , (40 ,10) ) pelota = imgPelota . get_rect () vel = [0 ,0] reiniciarPosiciones () pygame . display . set_icon ( imgPelota ) pantalla = pygame . display . set_mode (( ANCHO , ALTO ) ) pygame . display . set_caption ( TITULO . format ( partida ) ) color_fondo = (255 ,255 ,255) color1 = (255 ,0 ,0) color2 = (0 ,0 ,255) while corriendo : t = pygame . time . get_ticks () # Actualizacin : ###################### for evento in pygame . event . get () : if evento . type == pygame . QUIT : pygame . display . quit () corriendo = False break if not corriendo : break # Movimiento de la pelota pelota . x += vel [ X ]; pelota . y += vel [ Y ];
72
# Rebotes laterales if pelota . left < 0 or pelota . right > ANCHO : vel [ X ] *= -1; # Deteccin de choque vertical if pelota . top < 0 or pelota . bottom > ALTO : if pelota . top < 0: print ( " Jugador 2 gana !\ n " ) marcador2 += 1 else : print ( " Jugador 1 gana !\ n " ) marcador1 += 1 partida += 1 pygame . display . set_caption ( TITULO . format ( partida ) ) reiniciarPosiciones () continue # Rebotes verticales if jugador1 . collidepoint ( pelota . midtop ) or \ jugador2 . collidepoint ( pelota . midbottom ) : vel [ Y ] *= -1 continue # Movimiento de los jugadores teclas = pygame . key . get_pressed () if teclas [ pygame . K_ESCAPE ]: corriendo = False break if teclas [ pygame . K_LEFT ]: jugador2 . x -= INCREMENTO_DESPLAZAMIENTO if jugador2 . left < 0: jugador2 . left = 0; if teclas [ pygame . K_RIGHT ]: jugador2 . x += INCREMENTO_DESPLAZAMIENTO if jugador2 . right > pantalla . get_width () : jugador2 . x = pantalla . get_width () - jugador2 . w if teclas [ pygame . K_a ]: jugador1 . x -= INCREMENTO_DESPLAZAMIENTO if jugador1 . x < 0: jugador1 . x = 0 if teclas [ pygame . K_d ]: jugador1 . x += INCREMENTO_DESPLAZAMIENTO if jugador1 . x + jugador1 . w > pantalla . get_width () : jugador1 . x = pantalla . get_width () - jugador1 . w # Redibujar : #############################
73
# Borra la pantalla pantalla . blit ( imgFondo , (0 ,0) ) # pantalla . fill ( color_fondo ) # Dibujo de los jugadores pantalla . fill ( color1 , jugador1 ) pantalla . fill ( color2 , jugador2 ) # Dibujo de la bola pantalla . blit ( imgPelota , pelota ) # Vuelca el buffer en la memoria de video pygame . display . flip () # Sincronizacin de tiempo ################### dt = pygame . time . get_ticks () - t if dt < TiempoCiclo : pygame . time . delay ( TiempoCiclo - dt ) print ( " \n < << < < < < < < < < Marcador final : > > > > > > > > > > >\ nJugador 1: {0}\ nJugador 2: {1}\ n \ n " . format ( marcador1 , marcador2 ) )
Se juega con las teclas a y d para el jugador 1 y con los cursores izquierda y derecha para el jugador 2. Cuando un jugador gana, se inicia una nueva partida.
2.3.
Fundamentalmente existen dos formas de representar y almacenar objetos grcos en la memoria de una computadora:
74
2.4.
Paletas de colores
Para representar el color de un objeto grco, ya sea un pixel o un objeto vectorizado, es necesario utilizar una cantidad nita de memoria para ello. Y en los tiempos iniciales de la computacin grca, la memoria era un recurso sumamente costoso y demasiado valioso como para desperdiciarlo. Por ello surgi la necesidad de utilizar poca memoria para representar los colores a utilizar por una aplicacin grca. Por ejemplo, se puede utilizar una paleta de 8 bits (un byte), para permitirle a una aplicacin, usar hasta que el primero, el etc.
28 = 256
00H
sea negro, el
01H
sea blanco,. . ., el
09H
sea azul, el
0 AH
sea verde,
75
2.5.
Con el advenimiento de dispositivos de gracacin cada vez ms avanzados (que pueden desplegar ms colores y tienen mayor resolucin), con el abaratamiento exponencial de la memoria primaria para computadoras personales y con tarjetas grcas cada vez ms sosticadas, el uso de paletas de colores ha ido poco a poco cayendo en desuso entre los programadores grcos. En su lugar, suele especicarse (y almancenarse) el color directamente como lo entienden los dispositivos de gracacin modernos a travs de estndares internacionales. Estos nuevos estndares tienen, de hecho, la forma de paletas de colores realmente grandes, que no se pueden cambiar y que permiten variaciones de color aparentemente continuas para el ojo humano . Las ms usadas, soportadas en casi todos los lenguajes de programacin, son los de mezcla aditiva de luz (RGB y RGBA) y los de mezcla sustractiva de luz (CMY(K) ).
2.5.1. RGB
[Red, Green, Blue] Un color en este formato se suele especicar en la forma de un conjunto de tres bytes continuos (24 bits), donde los primeros ocho bits, de derecha a izquierda, representan la intensidad de luz azul. Los segundos ocho bits, de derecha a izquierda, la intensidad de luz verde, y los ltimos ocho, de derecha a izquierda, la intensidad de luz roja. En la gura 2.1 podemos ver el tpo diagrama de colores en el modelo RGB. Este modelo representa un color mediante la mezcla por adicin de los tres colores primarios de la luz, rojo, verde y azul. De tal manera que en una supercie negra, su suma a nula intensidad, resulta en negro; y su suma a mxima intensidad, resulta en blanco. Eventualmente, y dependiendo de la arquitectura del procesador, el orden en que se indican estas intensidades puede ser al revs de explicado arriba . Este es un detalle de poca o nula importancia cuando que utilizan APIs de alto nivel.
4 5
es decir, la diferencia entre un color y otro, es menor que el umbral diferencial de la luz, en los humanos. Para mayor informacin, vanse los artculos:
//en.wikipedia.org/wiki/Endianness
http://es.wikipedia.org/wiki/Big-endian
http:
76
2.5.2. RGBA
[Red, Green, Blue, Alpha] Este otro modo de especicacin de color, es similar al anterior, excepto que usa 32 bits. Los ocho que se han agregado (normalmente al nal), representan la medida de opacidad/transparencia (en el caso de SDL,
00H
es transparencia total y
F FH
es opacidad
total, pero en otras bibliotecas grcas, es al revs). E igual que en el caso del RGB, su representacin binaria vara dependiendo del hardware.
2.5.3. CMY(K)
[Cyan, Magenta, Yellow, Black (o Key)] Este modelo representa un color mediante la mezcla sustractiva de los colores cyan, magente y amarillo. De tal manera que impresos sobre un papel blanco, su mezcla a mnima intensidad no absorbe nada y la luz se reeja completamente blanca, pero si se mezclan a mxima intensidad, absorben la luz completamente, por lo que no vemos luz reejada (eso es el color negro). Esta es la forma en que se especican los colores para los dispositivos de impresin con algn tipo de tinta. Y la inclusin de tinta de color negro directamente, se debe a razones tcnicas especcas .
77
2.6.
color, es la porcin del espacio de color de la luz visible que se puede representar con ese dispositivo o proceso.
Es obvio que existen limitaciones fsicas en todos los dispositivos y procesos, que les impiden mostrar la gama completa del espacio de color de la luz visible (que es bastante grande). Por eso se habla de una porcin del espacio de color de la luz visible. Tambin se podra denir como el lugar geomtrico de los puntos del plano matiz-
78
2.7 Ejercicios
En la gura 2.4 se presentan (sin pretender ofrecer una gran precisin) las diferencias de los gamuts de RGB y de CMYK.
El gamut del modelo RGB es el tringulo y la otra gura es el gamut del modelo CMYK.
2.7.
Ejercicios
1. Describa las caractersticas de los grcos de barrido y los grcos vectoriales. 2. Suponga que una aplicacin modela dos crculos, A y B. El crculo A es modelado como grco de barrido y el B como grco vectorial. Describa cmo podra representar dicha aplicacin ambos crculos. 3. Explique con sus propias palabras, qu es el Gamut de un impresor laser. 4. Investigue cmo hacer para detectar combinaciones del tipo
li derehoCtrl,
li izquierdoCshift,
etc.
79
80
3.1.
Recordatorio bsico
y = mx + B
Recurdese la ecuacin punto-pendiente de las lneas rectas:
(3.1)
y y0 = m(x x0 )
La equacin dos puntos de las lneas rectas no verticales:
(3.2)
y y0 =
y1 y0 x1 x0
(x x0 )
(3.3)
ax + by + c = 0
(3.4)
(x x0 )2 + (y y0 )2 = R2
(3.5)
81
3.2.
Simbologa
Antes que nada, consideremos que los dispositivos principales sobre los que vamos a trabajar, son monitores cuyas pantallas estn compuestas por pixeles. Los pixeles estn dispuestos en forma de una matriz rectangular de puntos casi perfectamente cuadrados y cada uno puede brillar de un color independiente de los dems. La gura 3.1 es la imgen que se usar para esquematizar en este captulo cmo funcionan los algoritmos presentados La cuadrcula de la gura 3.1 representa un grupo de nueve pixeles de una computadora. Las lneas gruesas de guiones representan las fronteras entre dichos pixeles y las intersecciones de las lneas continuas delgadas representan los centros geomtricos de ellos. Por simplicidad, vamos a suponer, en las deducciones, que la numeracin de los pixeles se extiende desde la esquina inferior izquierda, el pixel
(0, 0)
esquina superior derecha. En realidad no es as, pero as es ms parecido a los textos bsicos de matemtica, con los cuales el lector est familiarizado. Se dice tambin que, respecto del pixel central, el pixel que est a su derecha, es su pixel ESTE o simplemente E. El pixel de la esquina superior derecha, es el pixel NOR-ESTE o NE respecto del mismo pixel central. El pixel de la esquina inferior izquierda, es el pixel SUR-OESTE o SO respecto del pixel central, etc.
82
3.3.
En esta seccin, se proceder a describir un algoritmo para dibujar lneas rectas que es sencillo y eciente desde el punto de vista matemtico. Analicemos el problema de dibujar una lnea del punto
(x0, y0 ),
al
(x1, y1 ).
la ecuacin
yi = mxi + B
y1 y0 x1 x0
que dene la linea, y los pixeles a encender (o a colorear) sern los pares ordenados:
Evidentemente,
m=
y1 y0 x1 x0 y
B = y0
x0 . xi+1
Esta idea es la base para el algoritmo que se conoce como Analizador Diferencial Digital (DDA, Digital Dierential Analyzer ). Que est restringido al caso en que Para otros valores de
1 m 1.
m,
1 2 3 4 5 6 7 8 9 10 11
void dda ( int x0 , int y0 , int x1 , int y1 ) { /* Supone que -1 <= m <= 1 , x0 < x1 . x vara de x0 a x1 en incrementos unitarios */ int x ; float y , m ; m = ( float ) ( y1 - y0 ) / ( x1 - x0 ) ; y = y0 ; for ( x = x0 ; x <= x1 , x ++) { marcarPixel (x , ( int ) y ) ;
83
A pesar de que esta solucin funciona y se puede adaptar a cualquier pendiente, tiene un detalle indeseable: Utiliza artimtica de coma otante . Por ello, en las siguientes secciones se presenta una elegante solucin que utiliza nicamente aritmtica entera.
y += m ;
3.4.
A continuacin, se proceder a describir uno de los mejores algoritmos de dibujo de lneas rectas: El algoritmo de lnea de punto medio (tambin conocido como algoritmo
de lnea de Bresenham ).
Antes de abordar la resolucin general del dibujo de segmentos de recta, se abordar un caso particular ms sencillo que permite describir la lgica del algoritmo.
(x0 , y0 ), hasta el pixel superior derecho (x1 , y1 ) usando nicamente aritmtica entera.
Considere la lnea que se representa en la gura 3.2, donde el pixel previamente seleccionado aparece sombreado y los dos pixeles candidatos a ser seleccionados en el siguiente paso, son el pixel a la derecha del marcado, el pixel ESTE del anterior, el pixel NOR-ESTE geomtricos de
N E.
El punto
y de
N E. E
y el
Acabamos de marcar el pixel en cuestin, el pixel tenemos que elegir entre el pixel pasa ms cerca del centro geomtrico Si el punto medio
M , est por encima de la lnea, el pixel E es el ms cercano a la lnea. Si el punto medio est debajo, el pixel N E es el ms cercano. En cualquier caso, el error
es siempre menor o igual a
1 2 de pixel.
En el caso de la gura 3.2, el pixel escogido como siguiente es el gura 3.3 en la pgina siguiente es el
N E,
y en el caso de la
E.
M . Para ello se ax + by + c = 0.
84
85
F (x, y ) = ax + by + c
Esta tiene la siguiente caracterstica: El valor de
(3.7)
a la lnea, positivo si el punto evaluado est debajo de la lnea, y es negativo si el punto evaluado est encima de la lnea, siempre y cuando el coeciente sea negativo. . . . . . . . . . . . . . . F (x, y )
b,
el coeciente de
Entonces, tenemos que evaluar el signo de est arriba o abajo de la recta ideal.
Para ordenar un poco el panorama, denamos una nueva variable, nuestra variable de decisin:
d=F
xp + 1, yp +
1 2
= a (xp + 1) + b yp +
1 2
+c
(3.8)
d > 0,
se elige el pixel
N E;
si
d 0,
se elige el pixel
(si
d = 0,
convencin, eligimos
E ).
si se eligi el pixel si se eligi el pixel
Una vez tomada la decisin y marcado el pixel, se pasa al siguiente punto y se hace lo mismo.
dnuevo
E, y N E.
c.
Sea
P (x) = mx + B
y sea
xi+1 = xi + 1
xi
E:
86
F (xi+1 , yi ) = a(xi + 1) + byi + c = axi + a + byi + c = (axi + byi + c) + a F (xi+1 , yi ) = F (xi , yi ) + a F (xi+1 , yi ) F (xi , yi ) = a dE nuevo dviejo = a = E
Veamos el caso en que se elige el pixel
N E:
F (xi+1 , yi+1 ) = a(xi + 1) + b(yi + 1) + c = axi + a + byi + b + c = (axi + byi + c) + a + b F (xi+1 , yi+1 ) = F (xi , yi ) + a + b F (xi+1 , yi+1 ) F (xi , yi ) = a + b
E dN nuevo dviejo = a + b = N E
A estas diferencias recin introducidas, las llamaremos Resta entonces el problema del valor inicial de paso):
N E
respectivamente.
(equivale al valor
dviejo
del primer
dinicial
= F (M ) 1 1 = F (x0 + 1, y0 + ) = a(x0 + 1) + b(y0 + ) + c 2 2 1 = ax0 + a + by0 + b + c 2 1 = ax0 + by0 + c + a + b 2 1 = (ax0 + by0 + c) + a + b 2 1 = F (x0, y0 ) + a + b; F (x0, y0 ) = 0 2 1 = a+ b 2 a
y
dinicial
b (c
y=
y x x
+ B,
as:
87
y x+B x x y = y x + x B y = 0 = y x x y + x B F (x, y ) = y x x y + x B
por lo que:
a = y b = x c = x B
Note que el coeciente necesitamos
es desconocido
(se puede calcular, porque conocemos al menos dos puntos de la recta), pero como no
c,
Tenemos entonces:
E =
F (xi+1 , yi ) F (xi , yi ) = a
= y
d.
y x+B x y 2y = 2 x + 2B x 2x y = 2y x + 2x B y = F (x, y ) = 2y x 2x y + 2x B
por lo que ahora: (3.9)
88
a = 2y b = 2x c = 2x B
y volvemos a calcular los valores que nos interesan . Llegamos a:
E = dinicial =
Con esto hemos resuelto
F (xi+1 , yi ) F (xi , yi ) = a
1 a+ 2 b
= 2y = 2y x d.
Ahora, el algoritmo usa exclusivamente artimtica entera, sin el costoso tiempo para hacer operaciones en coma otante. El cdigo en lenguaje C es:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
void linea_punto_medio_1 ( int x0 , int y0 , int x1 , int y1 ) { int delta_x , delta_y , delta_E , delta_NE , d , x , y ; /* Supone que x0 < x1 , y0 < y1 y que delta_y <= delta_x . x vara de x0 a x1 en incrementos unitarios */ delta_x = x1 - x0 ; delta_y = y1 - y0 ; d = delta_y * 2 - delta_x ; delta_E = delta_y * 2; delta_NE = ( delta_y - delta_x ) * 2; x = x0 ; y = y0 ; marcarPixel (x , y ) ; // marcar el primero while ( x < x1 ) { if ( d <= 0) { d += delta_E ; } else { d += delta_NE ; y ++; } x ++;
1 2
Basta con repetir el proceso con la nueva ecuacin Note que el valor de
89
marcarPixel (x , y ) ;
hacia atrs, para poder aceptar los puntos en cualquier orden sin tener que intercambiar
las variables de entrada. El primer problema se resuelve, averiguando la relacin de orden de a eso, se decide hacer el recorrido sobre
y .
En base
o sobre
y.
As:
Listing 3.3: Algoritmo de lnea de punto medio de un cuadrante (o dos mitades de cuad-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
rante)
void linea_punto_medio_2 ( int x0 , int y0 , int x1 , int y1 ) { int delta_x , delta_y , delta_E , delta_NE , d , x , y ; delta_x = x1 - x0 ; delta_y =y1 - y0 ; x = x0 , y = y0 ; // Cuando ' horizontal ' es verdadero , el ciclo iterar a travs de x , si no , lo har a travs de y int horizontal = delta_y < delta_x ; // Inicializaciones if ( horizontal ) { d = ( delta_y *2) - delta_x ; delta_E = delta_y *2; delta_NE = ( delta_y - delta_x ) *2; } else { d = ( delta_x *2) - delta_y ; delta_E = delta_x *2; delta_NE = ( delta_x - delta_y ) *2; } // Dibujar el primer pixel marcarPixel (x , y ) ; if ( horizontal ) while ( x < x1 ) { // Iterar a travs de x if (d <=0) d += delta_E ; else { y ++; d += delta_NE ; }
90
else
x ++; marcarPixel (x , y );
Ahora bien, el otro problema es un poco ms complicado. Consideremos la gura 3.4 y repitamos el anlisis a partir de la ecuacin 3.9, pero haciendo las modicaciones pertinentes. Llegamos a lo siguiente :
while ( y < y1 ) { // Iterar a travs de y if (d <=0) d += delta_E ; else { x ++; d += delta_NE ; } y ++; marcarPixel (x , y ); }
d = F xp 1, yp O = SO = dinicial = 2y 2y + 2x 2y + x
1 2
= a (xp 1) + b yp = E = N E = dinicial
1 2
+c
d 0,
se elige el pixel
O;
si
d < 0,
4
SO.
Aunque esto parezca bastante complicado, no hace falta darle muchas vueltas al asunto . Basta con tomar conciencia del siguiente razonamiento: Si el valor inicial de
pero de signo opuesto, y el criterio se basa en el signo de dicha variable, signica que se pueden sustituir los valores manera que quede as: Si el pixel
O , SO
dinicial
por
E , N E
(sumar
dinicial
respectivamente
sin afectar gravemente el algoritmo. Basta con invertir el criterio de incremento, de tal
d > 0,
se elige el pixel
SO
N E );
si
d 0,
se elige
(sumar
E ).
3 4
La demostracin de esto se deja como ejercicio tal como lo hizo el autor de este libro
91
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
Listing 3.4: Algoritmo de lnea de punto medio de dos mitades de cuadrantes opuestos
void linea_punto_medio_3 ( int x0 , int y0 , int x1 , int y1 ) { int delta_x , delta_y , delta_E , delta_NE , d , x , y , dir_x , dir_y ; /* x vara de x0 a x1 , o de x1 a x0 en incrementos unitarios */ delta_x = abs ( x1 - x0 ) ; delta_y = abs ( y1 - y0 ) ; x = x0 ; y = y0 ; d = delta_y * 2 - delta_x ; delta_E = delta_y * 2; delta_NE = ( delta_y - delta_x ) * 2; // Indican la direccin en que la lnea debe seguir dir_x = ( x1 - x0 ) >0 ? 1 : -1; dir_y = ( y1 - y0 ) >0 ? 1 : -1; marcarPixel (x , y ) ; // marcar el primero while ( x != x1 ) { if ( d <= 0) { d += delta_E ; } else { d += delta_NE ; y += dir_y ; }
92
Finalmente, el cdigo en lenguaje C para el algoritmo de lnea de punto medio genrico (que considera avance hacia atrs como el 3.4 y pendientes arbitrarias como el 3.3) es:
x += dir_y ; marcarPixel (x , y ) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
void linea_punto_medio ( int x0 , int y0 , int x1 , int y1 ) { int delta_x , delta_y , delta_E , delta_NE , d , x , y , dir_x , dir_y ; delta_x = abs ( x1 - x0 ) ; delta_y = abs ( y1 - y0 ) ; x = x0 , y = y0 ; // Cuando ' horizontal ' es verdadero , el ciclo iterar a travs de x , sino , lo har a travs de y int horizontal = delta_y < delta_x ; // Indican la direccin en que la lnea debe seguir dir_x = ( x1 - x0 ) >0 ? 1 : -1; dir_y = ( y1 - y0 ) >0 ? 1 : -1; // Inicializaciones if ( horizontal ) { d =( delta_y *2) - delta_x ; delta_E = delta_y *2; delta_NE =( delta_y - delta_x ) *2; } else { d =( delta_x *2) - delta_y ; delta_E = delta_x *2; delta_NE =( delta_x - delta_y ) *2; } // Dibujar el primer pixel marcarPixel (x , y ); if ( horizontal ) while ( x != x1 ) { if ( d <= 0) { d += delta_E ; } else { d += delta_NE ; y += dir_y ; } x += dir_x ; marcarPixel (x , y ) ; } else
93
39 40 41 42 43 44 45 46 47 48 49
3.5.
La simetra de la circunferencia
El crculo se divide en ocho partes iguales, y si se calculan los puntos de un octante, se pueden calcular, por simetra, los puntos de los dems octantes, como puede apreciarse en la gura 3.5 Podra implementarse un procedimiento como el siguiente para aprovechar este fenmeno:
1 2 3 4
94
3.6.
Existen varias alternativas para dibujar un crculo. Entre ellas, dibujarla a partir de la ecuacin:
x2 + y 2 = R 2 y = R 2 x2
Pero esta alternativa provoca algunos efectos no deseables cuando
(3.10)
x R,
adems del
enorme consumo de recursos para ejecutar las multiplicaciones y la raz cuadrada. Otra alternativa, es dibujarla en su forma paramtrica: cas (que se realizan con series de potencias).
3.7.
F (x, y ) = x2 + y 2 R2 = 0
El valor de
(3.11)
los coecientes de
x2
y2
sean positivos.
d:
95
Si
dviejo < 0,
E:
dE nuevo dE nuevo
= F
(xp + 1) + 1, yp
1 2
E = dE nuevo dviejo
Si
dviejo 0,
elegimos el pixel
SE :
96
= F
(xp + 1) + 1, (yp 1)
1 2
= dviejo + SE = F (MSE ) F (M ) 3 1 = F (xp + 2, yp ) F (xp + 1, yp ) 2 2 3 = (xp + 2)2 + (yp )2 R2 2 1 2 (xp + 1) (yp )2 + R2 2 = (2xp + 3) + (2yp + 2)
SE = 2xp 2yp + 5
Lo que falta es entonces, calcular la condicin inicial. Sabemos, que el punto inicial es
(0, R)
1 (1, R 2 ):
1 2 3 4 5 6 7 8 9
void circunferencia_punto_medio_1 ( int radio ) { int x , y ; float d ; x = 0; y = radio ; d = 5.0 / 4 - radio ; marcarPixel ( x , y ) ; marcarPixel ( y , x ) ;
97
marcarPixel ( y ,- x ) ; marcarPixel ( x ,- y ) ; marcarPixel ( -x , - y ); marcarPixel ( -y , - x ); marcarPixel ( -y , x ) ; marcarPixel ( -x , y ) ; while ( y > x ) { if ( d < 0) { d += x * 2.0 + 3; } else { d += ( x - y ) * 2.0 + 5; y - -; } x ++; marcarPixel ( x , y ) ; marcarPixel ( y , x ) ; marcarPixel ( y ,- x ) ; marcarPixel ( x ,- y ) ; marcarPixel ( -x , - y ); marcarPixel ( -y , - x ); marcarPixel ( -y , x ) ; marcarPixel ( -x , y ) ; }
Sin embargo, persiste el desagradable asunto de tener que operar con una variable de coma otante slo por el valor inicial de
como como
en
h es incrementada nicamente en valores enteros, se puede dejar la comparacin h < 0 sin afectar el resultado. Y por razones de consistencia, se dejar la variable lugar de h.
El algoritmo resultante, usando slo aritmtica entera para dibujar crculos en lenguaje C es:
1 2 3 4 5 6 7 8 9
Listing 3.8: Algoritmo de circunferencia de punto medio slo con aritmtica entera
void circunferencia_punto_medio_2 ( int radio ) { int x , y , d ; x = 0; y = radio ; d = 1 - radio ; marcarPixel ( x , y ) ; marcarPixel ( y , x ) ; marcarPixel ( y ,- x ) ;
98
marcarPixel ( x , - y ) ; marcarPixel ( -x , -y ) ; marcarPixel ( -y , -x ) ; marcarPixel ( -y , x ) ; marcarPixel ( -x , y ) ; while ( y > x ) { if ( d < 0) { d += x * 2 + 3; } else { d += ( x - y ) * 2 + 5; y - -; } x ++; marcarPixel ( x , y ) ; marcarPixel ( y , x ) ; marcarPixel ( y , - x ) ; marcarPixel ( x , - y ) ; marcarPixel ( -x , -y ) ; marcarPixel ( -y , -x ) ; marcarPixel ( -y , x ) ; marcarPixel ( -x , y ) ; }
d,
d,
(xp , yp )
(xp + 1, yp ).
Pero si elegimos a
(xp , yp )
99
De este anlisis, surge una tercera versin del algoritmo (mucho ms rpida que las anteriores, ya que slo contiene una multiplicacin):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
void circunferencia_punto_medio_3 ( int radio ) { int x , y , d , delta_E , delta_SE ; x = 0; y = radio ; d = 1 - radio ; delta_E = 3; delta_SE = 5 - radio * 2; marcarPixel ( x , y ) ; marcarPixel ( y , x ) ; marcarPixel ( y ,- x ) ; marcarPixel ( x ,- y ) ; marcarPixel ( -x , - y ); marcarPixel ( -y , - x ); marcarPixel ( -y , x ) ; marcarPixel ( -x , y ) ; while ( y > x ) { if ( d < 0) { d += delta_E ; delta_E += 2; delta_SE += 2; } else { d += delta_SE ; delta_E += 2; delta_SE += 4; y - -; } x ++; marcarPixel ( x , y ) ; marcarPixel ( y , x ) ; marcarPixel ( y ,- x ) ; marcarPixel ( x ,- y ) ; marcarPixel ( -x , - y ); marcarPixel ( -y , - x ); marcarPixel ( -y , x ) ;
100
37 38 39
marcarPixel ( -x , y ) ;
(xc , yc ),
es precsamente el vector
C = (xc , yc ).
(x , y )
En base a este sencillo anlisis, presentamos el siguiente algoritmo de dibujo de circunferencias con centro arbitrario, basado en el algoritmo 3.8:
101
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
Listing 3.10: Algoritmo de circunferencia de punto medio para centros arbitrarios
// Funcin auxiliar void marcarPixelesCicunferencia ( int x , int y , int xc , int yc ) { marcarPixel ( x +xc , y + yc ) ; marcarPixel ( x +xc , - y + yc ) ; marcarPixel ( - x + xc , y + yc ) ; marcarPixel ( - x + xc ,- y + yc ) ; marcarPixel ( y +xc , x + yc ) ; marcarPixel ( y +xc , - x + yc ) ; marcarPixel ( - y + xc , x + yc ) ; marcarPixel ( - y + xc ,- x + yc ) ; } void circunferencia_punto_medio ( int xc , int yc , int radio ) { int x ,y , d ; x =0; y = radio ; d =1 - radio ; marcarPixelesCicunferencia (x ,y , xc , yc ) ; while (y > x ) { if (d <0) { d += x * 2 + 3; } else { d += ( x - y ) * 2 + 5; y - -; } x ++; marcarPixelesCicunferencia (x ,y , xc , yc ) ; } }
3.8.
1 2 3 4 5 6 7 8 9 10 11 12
Relleno de rectngulos
Veamos primero un par de algoritmos de dibujo de rectngulos huecos antes de pasar al de relleno:
void dibujar_rectangulo_1 ( int x1 , int y1 , int x2 , int x2 ) { /* Se asume que x1 < x2 y y1 < y2 */ int i; if ( x1 > x2 ) { i = x1 ; x1 = x2 ; x2 = x1 ; } for ( i = x1 ; i <= x2 ; i ++) { marcarPixel (i , y1 ) ; marcarPixel (i , y2 ) ;
102
void dibujar_rectangulo_2 ( int x1 , int y1 , int ancho , int alto ) { int i ; for ( i = x1 ; i < x1 + ancho ; i ++) { marcarPixel (i , y1 , ); marcarPixel (i , y1 + alto -1) ; } for ( i = y1 ; i < y1 + alto ; i ++) { marcarPixel ( x1 , i); marcarPixel ( x1 + ancho -1 , i ) ; } }
El primer algoritmo recibe como parmetros dos puntos opuestos del rectngulo (ntese que no importa el orden en que se especiquen). El segundo recibe el punto ms cercano al origen y el ancho y alto del rectngulo (ntese que tanto positivos). Una de las alternativas ms usuales entre las bibliotecas grcas, es ofrecer al programador una funcin de relleno de rectngulos que requiere del punto inicial del rectngulo y su ancho y su alto. Esta es una propuesta de implementacin en lenguaje C:
ancho
como
alto,
deben ser
1 2 3 4 5 6
void dibujar_rectangulo_relleno ( int x1 , int y1 , int ancho , int alto ) { int i , j ; for ( i = x1 ; i < x1 + ancho ; i ++) for ( j = y1 ; j < y1 + alto ; j ++) marcarPixel (i , j ) ; }
3.9.
Relleno de circunferencias
Para el relleno de circunferencias, tambin existen diferentes propuestas. Una de ellas es aprovechar el algoritmo de punto medio para circunferencias y la simetra propia de
103
esta hermosa gura geomtrica. La idea bsica es hacer una modicacin al algoritmo de punto medio para circunferencias de la siguiente manera: Cuando se haga un avance hacia el pixel marcar todos los pixeles desde
SE , aprovechar para
(0, y )
hasta
(x, y )
marcar los pixeles correspondientes con la misma idea de la simetra de ocho octantes). A continuacin se presenta el cdigo fuente de dicha solucin : Listing 3.12: Algoritmo de relleno de circunferencias de centro arbitrario (basado en el
1 2 3 4 5 6 7 8 9 10 11 12 13 14
void circunferencia_rellena ( int x0 , int y0 , int radio ){ int r_x , r_y , r_ancho , r_alto ; int x ,y ,d , i ; x =0; y = radio ; d =1 - radio ; marcarPixelesCicunferencia (x ,y , x0 , y0 ) ; while (y > x ) { if (d <0) { d += x * 2 + 3; x ++; marcarPixelesCicunferencia (x ,y , x0 , y0 ) ; } else { d += ( x - y ) * 2 + 5;
5
comparar con el cdigo de la funcin
104
3.10 Ejercicios
15 16 17 18 19 20 21 22 23 24 25 26 27 28
3.10.
Ejercicios
1. Construya un algoritmo que use la idea del DDA (cdigo 3.1 en la pgina 83) para pendientes arbitrarias. 2. Describa cmo funciona el algoritmo de dibujo de lneas conocido como algoritmo de lnea de punto medio. 3. Implementar un programa de dibujo usando la ecuacin 3.10 en la pgina 95. 4. En la cuadrcula de la gura 3.9, marque (sombree, tache, repinte, etc.) los pixeles que encendera el algoritmo de lnea 3.5, al unir a los puntos (2,2) y (9,5) y a los puntos (0,2) y (3,9). El centro geomtrico de los pixeles est en el centro de los cuadritos negros. Las lneas grices son slo guas. 5. En la cuadrcula de la gura 3.9, marque (sombree, tache, repinte, etc.) los pixeles que encendera el algoritmo de crculo 3.10 en la pgina 102, con centro (4,4) y radio 4. El centro geomtrico de los pixeles est en el centro de los cuadritos negros. Las lneas grices son slo guas. 6. Modicar el algoritmo 3.9 en la pgina 100 para dibujar crculos con centro arbitrario. 7. Modicar el algoritmo del ejercicio anterior para implementar el dibujo de crculos rellenos. 8. Considere los cdigos siguientes para rellenar crculos y explique por qu el primero es preferible frente al segundo:
1 2
105
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
void dibujarCirculoRelleno1 ( int radio ) { int x ,y ,d , i ; x =0; y = radio ; d =1 - radio ; marcarPixelesCicunferencia (x , y ) ; while (y > x ) { if (d <0) { d += x * 2 + 3; x ++; marcarPixelesCicunferencia (x , y ) ; } else { d += (x - y ) * 2 + 5; y - -;
106
3.10 Ejercicios
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
} } dibujar_rectangulo_relleno ( -x , -y , 2* x ,2* y ) ;
void dibujarCirculoRelleno2 ( int radio ) { int i ; for ( i =0; i <= radio ; i ++) dibujarCircunferencia ( i ) ; }
9. Considere los cdigos siguientes y explique por qu el primero es ms eciente que el segundo:
1 2 3 4 5 6 7 8 9 10 11 12
void rectanguloRelleno1 ( int x , int y , int ancho , int alto ) { int i , j , xmax = x + ancho , ymax = y + alto ; for ( i= x ; i < xmax ; i ++) { for ( j= y ; j < ymax ; j ++) marcarPixel (i , j) ; }
void rectanguloRelleno2 ( int x , int y , int ancho , int alto ) { int i , xmax = x + ancho , ymax = y + alto ; for ( i= x ; i < xmax ; i ++) linea_punto_medio (i , y , i , ymax ) ; }
10. Implementar una funcin de dibujo de rectngulos que reciba uno de los vrtices del rectngulo y su ancho y alto. Pero el alto y el ancho debe aceptarlos con signo negativo, lo que signica que el rectngulo debe dibujarse hacia atrs en esa dimensin. 11. Implementar una funcin de relleno de rectngulos que reciba como parmetros, dos puntos opuestos del mismo. 12. Implemente la siguiente funcin en lenguaje C estndar
polgono cerrado, nos referimos a que el ltimo punto se une con el primero aunque
no sean iguales. 13. Construir una funcin de dibujo de elipses, dados su centro y sus dos radios.
107
108
4.1.
Notacin
Para recorrer el presente captulo con tranquilidad, vamos a establecer la siguiente nomenclatura:
Sea
(R) p
en el marco de referencia
R,
en
R.
Las componentes rectangulares de y
(R ) p
(R) px
del vector
Sea
(R)
una distancia
R,
y se lee en
R.
Recordemos que un punto cualquiera, puede existir simultaneamente en una cantidad innita de marcos de referencia. Por ejemplo, veamos la gura 4.1.
109
en el marco de referencia
V,
y en el marco de referencia
R.
En cada uno de ellos, el punto tendr sus coordenadas medidas en una escala diferente, apropiada para cada marco. Usualmente trabajaremos con dos marcos de referencia, uno Virtual, propio de la lgica de nuestra aplicacin, y uno Real, propio de la API grca que estemos utilizando (tpicamente coincidente con los pixeles fsicos del monitor). El problema a analizar en este captulo es bsicamente el siguiente:
Disponemos de claro.
(V ) p ,
y deseamos conocer
(R) p
4.2.
Habiendo hecho la respectiva presentacin de la nomenclatura, pasamos al tema que nos interesa: Cmo cambiar las coordenadas de un punto (u objeto) de un marco de
referencia a otro.
Bueno, el problema puede abordarse de muy diversas maneras. Una de ellas es la perspectiva vectorial.
110
p,
(R) p = T RV
. Veamos estos nuevos elementos:
(R ) (V ) = V + E R V p
(V ) p
T RV V
en
R. E RV
(R ) V
es
al
R.
(R) V
E R V
embargo puede denirse de la siguiente manera: Antes que nada, necesitamos las coordenadas de dos puntos en ambos marcos de referencia. Llammoslos Ntese que
B.
(R) By
(R) x = (R) (V ) Ay y y
Bx Ax , x = Bx Ax , del mismo modo que y = (V ) (V ) = By Ay . Es importante mantener los signos tal cuales. Con
(R)
(R)
(V )
(V )
(V )
(R)
E RV
(V ) p =
x x
(R)
(V )
(V ) y (V ) p x , (V ) py y
(R)
111
As, la forma completa para la ecuacin vectorial de transformacin de marcos de referencia es:
(R ) p = T R V
(R) (V ) p = V +
x x
(R)
(V )
(V ) y (V ) p x , (V ) py y
(R)
(4.1)
(V )
(V )
(4.2)
4.2.1. Ejemplo
Planteemos un escenario en el que es necesario el cambio de marco de referencia: Tenemos una aplicacin grca como en la gura 4.4. La aplicacin tiene su propio marco de referencia establecido por la API grca que estamos utilizando. Esta API establece (y la mayora lo hace de la misma manera) que el origen est en la esquina superior izquierda del interior de la ventana. Todas las coordenadas delimitadas por parntesis pertenecen al sistema de coordenadas de la API. Este marco ser
R.
Dentro de nuestra aplicacin grca tenemos un cuadro (en este caso, punteado) a travs del cual mostramos un juego que tiene su propio marco de referencia, que establece que
112
el origen est en la esquina inferior izquierda del cuadro, y que las coordenadas visibles para el usuario se extienden hacia la esquina superior derecha hasta la coordenada
[1000, 1000],
llamaremos
ordendadas delimitadas por corchetes estn en el marco de referencia del juego, que
V.
Ntese que los ejes verticales avanzan en direccin opuesta, uno del otro. Y que por convencin en este texto, las coordenadas en el marco de referencia virtual se delimitan con corchetes: con parntesis:
[500, 500].
Pero nuestra API grca no funciona con ese marco de referencia. Necesi-
(V ) p
y necesitamos
(R) p .
B.
Podemos tomar estos de las esquinas inferior izquierda y superior derecha del
B (V ) =
[1000, 1000].
Ahora sustitumos:
113
(R) p =
T RV (R) V +
(V ) p
(V ) y (V ) p x , (V ) py (V ) x y 350 100 50 300 = (100, 300) + 500, 500 1000 0 1000 0 = (100, 300) + (125, 125)
(R)
(R)
Ahora sabemos que si en el juego se necesita marcar el punto referencia interno, debemos marcar el pixel
[500, 500]
en el marco de
(225, 175)
de la aplicacin.
4.3.
Hay un caso particular que es muy usual en aplicaciones de corte acadmico: Aquel en el el que toda la pantalla (o al menos toda la ventana) de la aplicacin, se usa para el despliegue grco en un marco de referencia con origen en la esquina inferior izquierda, tal como puede verse en la gura 4.5. En este caso, tal como en el ejemplo de la seccin 4.4, los puntos a elegir como referencias, son las esquinas inferior izquierda y superior derecha del marco virtual en el marco real. Podemos ver que
A(V ) = [0, 0]
(R) V = (0, AltoR). Adems A(R) = (0, AltoR), B (R) = (AnchoR, 0), B (V ) = [XmaxV, Y maxV ] y sustituimos:
114
(R) p =
(V ) p
(R) p
( V ) y (V ) p x , (V ) py (V ) x y AnchoR 0 0 AltoR (V ) (V ) = (0, AltoR) + px , py XmaxV 0 Y maxV 0 AnchoR AltoR (V ) (V ) = p x , AltoR py XmaxV Y maxV (V ) py AnchoR (V ) p x , AltoR 1 = XmaxV Y maxV
(R )
(R)
4.4.
Transformacin de distancias
Eventualmente aparece tambin el problema de transformar no slo coordenadas, sino tambin distancias entre diferentes marcos de referencia. El problema se resuelve intuitivamente haciendo una regla de tres con las distancias as:
x (V ) x
(R)
(R)
xy
(R)
(V ) y
dy
(R)
(V ) dy
y . De hecho,
es el mismo razonamiento usado para denir la ecuacin 4.1 en la pgina 112. Es de hacer notar que de esta manera, las distancias quedan direccionadas. Es decir, no slo se traduce la magnitud de la distancia, sino tambin su sentido. Si el propsito es traducir slo la magnitud de la distancia, habr que ignorar (o eliminar) el signo resultante.
4.5.
A continuacin se expone brevemente una sencila aplicacin grca interactiva que simula el campo elctrico producido por un conjunto de cargas puntuales en un espacio
115
en el punto
Q,
en el
es:
E =
1 q QP 3 4 0 QP
(4.3)
i = 0, 1, . . . , n,
entonces el campo
con
E =
qi Qi P 3 Qi P
(4.4)
Los siguientes dos archivos proporcionan la funcionalidad bsica para manipular campo elctrico.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/* c04 / campos vectoriales / campo . h * */ # define EPSILON_0 8.854 e -12 # define _1_4PIe0 8.988 e9 /* Vector genrico bidimensional , se puede usar para posicin , velocidad , etc . */ typedef struct { double x , y ; } Vector ; /* < valor > puede ser negativo . */ typedef struct {
1
Para ms detalles del dicho fenmeno fsico, consltese cualquier texto introductorio de Electromagnetismo
116
Vector posicion ; double valor ; } CargaElectrica ; /* Calcula el vector de Campo Elctrico en el punto < punto > , por efecto de la carga < carga >. El resultado se almacena en < campo >. */ void campoElectrico ( CargaElectrica * carga , Vector * punto , Vector * campo ) ; /* * Calcula el vector de Campo Elctrico * en el punto < punto > , por efecto del * arreglo de cargas < cargas >. * El resultado se almacena en < campo > * */ void campoElectricoCargas ( CargaElectrica * cargas , int cantidad , Vector * punto , Vector * campo ) ; /* * Calcula la magnitud de un vector * */ double magnitudVector ( Vector * v ) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/* c04 / campos vectoriales / campo . c * */ # include < math .h > # include " campo . h " void campoElectrico ( CargaElectrica * carga , Vector * punto , Vector * campo ) { Vector distancia ; double magnitudDistancia3 ; distancia . x = punto - > x - carga - > posicion . x ; distancia . y = punto - > y - carga - > posicion . y ; magnitudDistancia3 = pow ( magnitudVector (& distancia ) ,3.0) ; campo - > x = _1_4PIe0 * carga - > valor * distancia . x / magnitudDistancia3 ; campo - > y = _1_4PIe0 * carga - > valor * distancia . y / magnitudDistancia3 ; } void campoElectricoCargas ( CargaElectrica * cargas , int cantidad , Vector * punto , Vector * campo ) { int k ; Vector temp ; campo - > x = campo - > y = 0.0;
117
for ( k =0; k < cantidad ; k ++) { campoElectrico ( cargas +k , punto , & temp ) ; campo - > x += temp .x ; campo - > y += temp .y ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
/* c04 / campos vectoriales / colores . h * */ # include < SDL / SDL .h > // color slido # define ALFA 0 x000000FF # define # define # define # define # define /* ROJO (0 xFF000000 | ALFA ) VERDE (0 x00FF0000 | ALFA ) AZUL (0 x0000FF00 | ALFA ) BLANCO ( ROJO | VERDE | AZUL ) NEGRO 0 x000000FF
Las funciones ____Color de SDL_gfx requieren el color en un entero de 32 bis as : 0 xRRGGBBAA , no acepta el color en ningn otro formato . Adems , utiliza las transparencias por defecto , siempre hay que especificar la opacidad del color .
118
// Borra la pantalla rellenando con el color especificado void borrarPantalla ( SDL_Surface * pantalla , Uint32 relleno ) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
/* c04 / campos vectoriales / colores . c * */ # include " colores . h " Uint32 colorGfx ( Uint8 r , Uint8 g , Uint8 b ) { return r << 24 | g << 16 | b << 8 | 255; // este valor es la opacidad del color // y debe ser mxima para que el color // sea slido } Uint32 colorGfxA ( Uint8 r , Uint8 g , Uint8 b , Uint8 a ) { return r << 24 | g << 16 | b << 8 | a; } void borrarPantalla ( SDL_Surface * pantalla , Uint32 relleno ) { SDL_FillRect ( pantalla , NULL , SDL_MapRGB ( pantalla - > format , ( relleno & ROJO ) > >24 , ( relleno & VERDE ) > >16 , ( relleno & AZUL ) > >8) ) ; }
1 2 3
/* c04 / campos vectoriales / escala . h * Esta es una versin particular del problema * de la transformacin de escalas .
119
* No es una solucin general para cualquier caso . * */ # include < stdio .h > /* * Estructura para manipular escala * de pantalla completa * */ typedef struct { int AnchoR , AltoR ; double XminV , YminV ; double XmaxV , YmaxV ; } Escala ; /* * Funciones de transformacin vectorial * entre el marco de referencia Real y el Virtual * * La primera funcin se lee : * " Transformacin a Real , dado el Virtual en x " * */ int tR_Vx ( double xV , Escala * e ) ; int tR_Vy ( double yV , Escala * e ) ; double tV_Rx ( int xR , Escala * e ) ; double tV_Ry ( int yR , Escala * e ) ; /* * Funciones de tranformacin de distancias * entre el marco de referencia Real y el Virtual * * La primera funcin se lee : * " Magnitud en Real , dado el Virtual en x " * */ int mR_Vx ( double deltaxV , Escala * e ) ; int mR_Vy ( double deltayV , Escala * e ) ; double mV_Rx ( int deltaxR , Escala * e ); double mV_Ry ( int deltayR , Escala * e );
1 2 3 4 5 6 7 8
/* c04 / campos vectoriales / escala . c * */ # include " escala . h " int tR_Vx ( double xV , Escala * e ) { return ( int ) (( xV - e - > XminV ) * e - > AnchoR / (e - > XmaxV - e - > XminV ) ) ; }
120
int tR_Vy ( double yV , Escala * e ) { return ( int ) (( yV - e - > YmaxV ) * -e - > AltoR / (e - > YmaxV - e - > YminV ) ); } double tV_Rx ( int xR , Escala * e ) { return e - > XminV + xR * (e - > XmaxV -e - > XminV ) /e - > AnchoR ; } double tV_Ry ( int yR , Escala * e ) { return e - > YmaxV - (e - > YmaxV -e - > YminV ) * yR /e - > AltoR ; } int mR_Vx ( double deltaxV , Escala * e ) { return deltaxV *e - > AnchoR /( e - > XmaxV -e - > XminV ) ; } int mR_Vy ( double deltayV , Escala * e ) { return deltayV * -e - > AltoR /( e - > YmaxV -e - > YminV ) ; } double mV_Rx ( int deltaxR , Escala * e) { return deltaxR *( e - > XmaxV -e - > XminV ) /e - > AnchoR ; } double mV_Ry ( int deltayR , Escala * e) { return deltayR *( e - > YmaxV -e - > YminV ) / -e - > AltoR ; } void imprimirEscala ( FILE * salida , Escala * e ) { fprintf ( salida , " Datos de escala :\ n " ) ; fprintf ( salida , " AnchoR :\ t %d \ nAltoR :\ t %d \ n " , e - > AnchoR , e - > AltoR ) ; fprintf ( salida , "[ %f, %f]-[ %f, %f ]\ n " , e - > XminV , e - > YminV , e - > XmaxV , e - > YmaxV ) ; }
clic izquierdo
Sobre una carga no tiene efecto. Sobre el campo tiene el efecto de imprimir
N/C .
121
Sobre una carga tiene el efecto desplazar esa carga a otro punto
del plano. Sobre el campo no tiene ningn efecto. Agrega una nueva carga elctrica puntual de
1C
geqesxsgsev)
wegeqe).
rueda del mouse arriba rueda del mouse abajo Shift + echa arriba Shift + echa abajo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
vez (el valor de incremento est denido en la macro el campo provoca un alejamiento del
10 %
cada vez (el valor de decremento est denido en la macro Sobre el campo provoca un acercamiento del
10 %
la escala del modelo ni el tamao de la ventana. Disminuye el nmero de echas de campo en la pantalla sin afectar
la escala del modelo ni el tamao de la ventana. Listing 4.7: Programa principal del simulador de campo elctrico
/* c04 / campos vectoriales / main . c * */ # include < SDL / SDL .h > # include < SDL / SDL_gfxPrimitives .h > # include < stdio .h > # include < math .h > # include " colores . h " # include " campo . h " # include " escala . h " # define # define # define # define MAX_CARGAS 20 CARGA_INICIAL 1.0 INCREMENTO_CARGA 1.0 MIN_CANT_CUADROS 5
struct configuracion { int numCuadrosH ; int numCuadrosV ; Uint32 Uint32 Uint32 Uint32 Uint32 colorFondo ; colorFlecha ; colorPuntaFlecha ; colorCargaPositiva ; colorCargaNegativa ;
122
CargaElectrica cargas [ MAX_CARGAS ]; int cantidadActualCargas ; Escala e ; double radioCarga ; // en el marco virtual } conf ; int profundidad_color ; const SDL_VideoInfo * info ; void configurar ( void ) { conf . numCuadrosH = conf . numCuadrosV = 30; conf . colorFondo = BLANCO ; conf . colorFlecha = VERDE ; conf . colorPuntaFlecha = ROJO | AZUL ; conf . colorCargaPositiva = ROJO ; conf . colorCargaNegativa = NEGRO ; conf . e . AnchoR = 600; conf . e . AltoR = 600; conf . e . XminV = conf . e . YminV = -10.0; conf . e . XmaxV = conf . e . YmaxV = 10.0; conf . radioCarga = 1.0; conf . cantidadActualCargas = 0; } // Dibuja los campos en el buffer , no en la pantalla directamente void actualizar ( SDL_Surface * pantalla ) { int i , j ; Vector campo ; Vector punto ; borrarPantalla ( pantalla , conf . colorFondo ) ; // dibujar las cargas for ( i =0; i < conf . cantidadActualCargas ; i ++) { filledEllipseColor ( pantalla , tR_Vx ( conf . cargas [ i ]. posicion .x ,& conf . e ) , tR_Vy ( conf . cargas [ i ]. posicion .y ,& conf . e ) , abs ( mR_Vx ( conf . radioCarga ,& conf . e ) ) , abs ( mR_Vy ( conf . radioCarga ,& conf . e ) ) , ( conf . cargas [ i ]. valor >0.0) ? conf . colorCargaPositiva : conf . colorCargaNegativa ) ; } // dibujar el campo if ( conf . cantidadActualCargas >0) for ( i =0; i < conf . numCuadrosV ; i ++) { // el centro de cada cuadrito punto . y = tV_Ry (( conf . e . AltoR / conf . numCuadrosV ) *(2* i +1) /2 , & conf .
123
for ( j =0; j < conf . numCuadrosH ; j ++) { punto . x = tV_Rx (( conf . e . AnchoR / conf . numCuadrosH ) *(2* j +1) /2 , & conf . e ) ; campoElectricoCargas ( conf . cargas , conf . cantidadActualCargas , & punto , & campo ) ; // dibujar las lneas aalineColor ( pantalla , tR_Vx ( punto .x , & conf . e ) , tR_Vy ( punto .y , & conf . e ) , // ********** tR_Vx ( punto .x , & conf . e ) + ( int ) (( campo . x / magnitudVector (& campo ) ) *( conf . e . AnchoR / conf . numCuadrosH -1) ) , tR_Vy ( punto .y , & conf . e ) - ( int ) (( campo . y / magnitudVector (& campo ) ) *( conf . e . AltoR / conf . numCuadrosV -1) ) , conf . colorFlecha ) ; // dibujar puntas filledCircleColor ( pantalla , tR_Vx ( punto .x , & conf . e ) + ( int ) (( campo . x / magnitudVector (& campo ) ) *( conf . e . AnchoR / conf . numCuadrosH -1) ) , tR_Vy ( punto .y , & conf . e ) - ( int ) (( campo . y / magnitudVector (& campo ) ) *( conf . e . AltoR / conf . numCuadrosV -1) ) , 1, conf . colorPuntaFlecha ) ;
void configurarVideo ( SDL_Surface ** pantalla ) { if ( SDL_Init ( SDL_INIT_VIDEO ) < 0) { printf ( " Error al iniciar SDL : %s \ n " , SDL_GetError () ) ; exit (1) ; } atexit ( SDL_Quit ) ; // Este if es importante para poder usar SDL_gfx info = SDL_GetVideoInfo () ; if ( info - > vfmt - > BitsPerPixel > 8 ) { profundidad_color = info - > vfmt - > BitsPerPixel ; // printf (" %d \ n " , profundidad_color ) ; } else { profundidad_color = 16; } * pantalla = SDL_SetVideoMode ( conf . e . AnchoR , conf .e . AltoR , profundidad_color , SDL_SWSURFACE | SDL_RESIZABLE ) ;
124
if (* pantalla == NULL ) { printf ( " Error al inicializar el modo de video : ' %s '\ n " , SDL_GetError () ) ; exit (2) ; } } SDL_WM_SetCaption ( " Simulador de campos elctricos " , NULL ) ;
/* * Dado un arreglo de cargas elctricas , * y una coordenada en el marco Real , * retorna el ndice del arreglo que se corresponde * con la carga seleccionada . * Si no coincide con ninguna , retorna -1 * * */ int seleccionarCarga ( CargaElectrica * cargas , int cantidad , int xR , int yR ){ int i ; int x1 , x2 , y1 , y2 ; for ( i =0; i < cantidad ; i ++) { x1 = tR_Vx ( cargas [ i ]. posicion .x ,& conf . e ) - mR_Vx ( conf . radioCarga ,& conf . e ) ; x2 = tR_Vx ( cargas [ i ]. posicion .x ,& conf . e ) + mR_Vx ( conf . radioCarga ,& conf . e ) ; y1 = tR_Vy ( cargas [ i ]. posicion .y ,& conf . e ) + mR_Vy ( conf . radioCarga ,& conf . e ) ; y2 = tR_Vy ( cargas [ i ]. posicion .y ,& conf . e ) - mR_Vy ( conf . radioCarga ,& conf . e ) ; if ( x1 <= xR && xR <= x2 && y1 <= yR && yR <= y2 ) return i ; } return -1; } int main ( int argc , char * argv []) { SDL_Surface * pantalla = NULL ; SDL_Event evento ; int corriendo = 1; int seleccion = -1; configurar () ; configurarVideo (& pantalla ) ; // hacer primer dibujo stringColor ( pantalla ,0 , conf . e . AltoR /2 , " Haga clic derecho para agregar una carga elctrica " , conf . colorFlecha ) ;
125
// volcar el buffer en la pantalla SDL_Flip ( pantalla ) ; while ( corriendo ) { while ( SDL_PollEvent (& evento ) ) { switch ( evento . type ) { case SDL_VIDEORESIZE : { conf . e . AnchoR = evento . resize . w ; conf . e . AltoR = evento . resize . h ; // redimensionar la pantalla , no hace falta liberar la anterior . pantalla = SDL_SetVideoMode ( conf . e . AnchoR , conf . e . AltoR , profundidad_color , SDL_SWSURFACE | SDL_RESIZABLE ) ; if ( pantalla == NULL ) { printf ( " Error al redimensionar la pantalla : ' %s '\ n " , SDL_GetError () ) ; exit (1) ; } // La pantalla nueva aparece en blanco // o mejor dicho , en negro , // por lo que hay que volver a dibujar stringColor ( pantalla , 0 , conf . e . AltoR /2 , " Haga clic derecho para agregar una carga elctrica " , conf . colorFlecha ) ; actualizar ( pantalla ) ; // vuelca el buffer en la pantalla : SDL_Flip ( pantalla ) ;
} break ;
case SDL_MOUSEBUTTONDOWN :{ // Seleccionar / evaluar campo if ( evento . button . button == SDL_BUTTON_LEFT ) { seleccion = seleccionarCarga ( conf . cargas , conf . cantidadActualCargas , evento . button .x , evento . button . y ) ; if ( seleccion == -1) { Vector punto ; Vector campo ; punto . x = tV_Rx ( evento . button .x ,& conf . e ) ; punto . y = tV_Ry ( evento . button .y ,& conf . e ) ; campoElectricoCargas ( conf . cargas , conf . cantidadActualCargas , & punto , & campo ) ; printf ( " Campo electrico : ( %f, %f ) , magnitud : %f \ n " , campo .x , campo .y , magnitudVector (& campo ) ) ; }
126
} // Agregar una carga else if ( evento . button . button == SDL_BUTTON_RIGHT ) { if ( conf . cantidadActualCargas < MAX_CARGAS ) { conf . cargas [ conf . cantidadActualCargas ]. valor = CARGA_INICIAL ; conf . cargas [ conf . cantidadActualCargas ]. posicion . x = tV_Rx ( evento . button .x ,& conf . e ) ; conf . cargas [ conf . cantidadActualCargas ]. posicion . y = tV_Ry ( evento . button .y ,& conf . e ) ; conf . cantidadActualCargas ++; actualizar ( pantalla ) ; SDL_Flip ( pantalla ) ; } else printf ( " Ya se alcanz el mximo nmero de cargas permitidas \ n" ) ; } else if ( evento . button . button == SDL_BUTTON_MIDDLE ) { int s ; if (( s = seleccionarCarga ( conf . cargas , conf . cantidadActualCargas , evento . button .x , evento . button . y ) ) > -1) { conf . cargas [ s ]. valor = - conf . cargas [ s ]. valor ; actualizar ( pantalla ) ; SDL_Flip ( pantalla ) ; } } else if ( evento . button . button == SDL_BUTTON_WHEELUP ) { int s ; if (( s = seleccionarCarga ( conf . cargas , conf . cantidadActualCargas , evento . button .x , evento . button . y ) ) > -1) { conf . cargas [ s ]. valor += INCREMENTO_CARGA ; printf ( " Nuevo valor de la carga : %fC \ n " , conf . cargas [ s ]. valor ) ; } else { // alejarse un 10 % double incrementoAncho , incrementoAlto ; incrementoAncho = abs ( conf . e . XmaxV - conf . e . XminV ) *0.10; incrementoAlto = abs ( conf . e . YmaxV - conf . e . YminV ) *0.10; conf . e . XminV -= incrementoAncho /2; conf . e . XmaxV += incrementoAncho /2; conf . e . YminV -= incrementoAlto /2; conf . e . YmaxV += incrementoAlto /2; }
230 231 232 233 234 235 236 237 238 239 240 241
127
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
else if ( evento . button . button == SDL_BUTTON_WHEELDOWN ) { int s ; if (( s = seleccionarCarga ( conf . cargas , conf . cantidadActualCargas , evento . button .x , evento . button . y ) ) > -1) { conf . cargas [ s ]. valor -= INCREMENTO_CARGA ; printf ( " Nuevo valor de la carga : %fC \ n " , conf . cargas [ s ]. valor ) ; } else { // acercarse un 10 % double decrementoAncho , decrementoAlto ; decrementoAncho = abs ( conf . e . XmaxV - conf . e . XminV ) *0.10; decrementoAlto = abs ( conf . e . YmaxV - conf . e. YminV ) *0.10; conf . e . XminV += decrementoAncho /2; conf . e . XmaxV -= decrementoAncho /2; conf . e . YminV += decrementoAlto /2; conf . e . YmaxV -= decrementoAlto /2; } actualizar ( pantalla ) ; SDL_Flip ( pantalla ) ; } } break ; // asegurar que se deselecciona la carga case SDL_MOUSEBUTTONUP :{ seleccion = -1; } break ; // desplazar la carga seleccionada case SDL_MOUSEMOTION :{ if ( seleccion > -1) { conf . cargas [ seleccion ]. posicion . x = tV_Rx ( evento . button .x ,& conf . e ) ; conf . cargas [ seleccion ]. posicion . y = tV_Ry ( evento . button .y ,& conf . e ) ; actualizar ( pantalla ) ; SDL_Flip ( pantalla ) ; } } break ; case SDL_KEYDOWN :{
128
} break ;
if ( evento . key . type == SDL_KEYDOWN && evento . key . keysym . mod & KMOD_SHIFT ) switch ( evento . key . keysym . sym ) { case SDLK_UP : { conf . numCuadrosH ++; conf . numCuadrosV ++; actualizar ( pantalla ) ; SDL_Flip ( pantalla ) ; } break ; case SDLK_DOWN : { if ( conf . numCuadrosH > MIN_CANT_CUADROS ) conf . numCuadrosH - -; if ( conf . numCuadrosV > MIN_CANT_CUADROS ) conf . numCuadrosV - -; actualizar ( pantalla ) ; SDL_Flip ( pantalla ) ; } break ; }
// cuando el usuario quiera cerrar la ventana , la aplicacin debe terminar case SDL_QUIT : corriendo = 0; break ; } // fin del switch } // fin de while PollEvent } // fin de while corriendo SDL_Quit () ; return 0;
4.5.5. El Makefile
Finalmente, este es el archivo compilacin :
wkefile
1 2 3
$ make
129
# Esto indica que los siguientes identificadores , # no son archivos , sino comandos de make : . PHONY : limpiar . PHONY : limpiartodo . PHONY : all # se puede por ejemplo ejecutar en la consola # lo siguiente : # ' make limpiartodo ' , etc . # Nombre del programa ejecutable : PROG = simulador # Un '*.o ' por cada '*.c ' OBJ = main . o escala . o colores . o campo . o # Cuando se ejecuta ' make ' , se ejecuta esto : all : limpiartodo $ ( PROG ) limpiar # Esto compila todo el cdigo y lo enlaza : $ ( PROG ) : $ ( OBJ ) $ ( RM ) $ ( PROG ) gcc -o $ ( PROG ) $ ( OBJ ) $ ( LDLIBS ) $ ( LDFLAGS ) $ ( GFX ) # Borra todos los archivos intermedios y de copia de seguridad limpiar : $ ( RM ) *~ $ ( OBJ ) $ ( PRG ) # Borra todos los archivos intermedios , de copia de seguridad # y el programa ejecutable , si es que existe limpiartodo : make limpiar $ ( RM ) $ ( PROG )
4.6.
Ejercicios
int tx@doule int ty@doule doule tx@int doule ty@int xAY yAY xAY yAY
para convertir puntos del marco de referencia virtual al real y viceversa. Asuma
130
4.6 Ejercicios
que los smbolos mostrados en la gura son constantes. 2. Modique el programa de la seccin 4.5 en la pgina 115 para representar la diferencia de intensidad del campo en cada punto. 3. Agregar la funcionalidad de eliminar cargas al programa de la seccin 4.5. 4. Modique el programa de la seccin 4.5 para que la cantidad de cargas puntuales aceptadas sea ilimitada. 5. Modique el programa de la seccin 4.5 para que el aumento/acercamiento tenga como centro la posicin del cursor del ratn en ese momento en lugar del centro del espacio visible como sucede con la versin presentada. 6. Modique el programa de la seccin 4.5 para que slo se pueda seleccionar una carga al hacer clic dentro de la supercie de la carga (ya que en la versin actual, la seleccin se logra con clic en el rectngulo circunscrito a la carga). 7. Realice todas las optimizaciones posibles al cdigo de la seccin 4.5, para lograr el mejor rendimiento . 8. Modique el programa de la seccin 4.5 para que el aumento de la cantidad de echas de campo se logre con (en la versin actual es con
el campo elctrico
9. Modique el programa de la seccin 4.5 para permitir que las conguraciones iniciales (ver la estructura
onfigurion
un buen lugar para empezar es en el archivo campo.c, y el siguiente lugar, es el programa principal
131
132
5.1.
Existen bsicamente tres operaciones o transformaciones geomtricas bsicas, a partir de las cuales se pueden realizar todas las dems. Estas transformaciones son la traslacicn, el escalamiento y la rotacin, todas respecto del origen.
(2, 3)
y la nueva es
133
5.1.2. Escalamiento
El escalamiento es la operacin que nos permite agrandar o empequeecer un conjunto de puntos o un objeto compuesto. La operacin requiere de dos parmetros: el factor de escalamiento a aplicar en
y.
La operacin
requiere adem, de un punto de referencia, tpicamente el origen del marco de referencia. En el caso de aplicar escalamiento bsico a un punto, se produce el efecto de acercarlo o alejarlo del punto de referencia. Veamos un ejemplo: En la gura 5.2, se ilustra el escalamiento de
que
y . El efecto, debido a que ambos factores 1, es que todos los puntos del cuadro P se han acercado al origen (pero en y ).
1 x, y 2 en
plo anterior) tomando como referencia a su esquina inferior derecha y con factores de
1 2 en
x,
1 2 en
y.
134
135
5.1.3. Rotacin
La rotacin es la ms compleja de las operaciones o transformaciones geomtricas bsicas. Consiste en girar un punto, un conjunto de puntos o un cuerpo compuesto, al rededor de un punto de referencia (el cetnro de rotacin), tpicamente el origen del marco de referencia. Veamos un ejemplo: En la gura 5.4, se ilustra la rotacin de rotacin es de
P,
(6, 2). La x+ . El
punto de referencia es el origen del marco de referencia. Las coordenadas del punto inferior izquierdo de Un ejemplo ms: En la gura 5.5, se ilustra la rotacin de
son
(4,1962, 4,7321)1 .
P,
ejemplo anterior. La rotacin es de 4 en el sentido de las agujas del reloj a partir del eje x . El punto de referencia es esquina superior derecha de P . Las coordenadas del punto
inferior izquierdo de
son
(5,1716, 4).
136
5.2.
Representacin matricial
Aunque existen diversas maneras de representar las coordendas de los objetos grcos y de representar las transformaciones geomtricas que operan sobre ellos, vamos a elegir aqu la ms estndar y exible. Los puntos se representan como vectores columna de tamao
3 1:
x P = y 1
La operacin de traslacin bidimensional se representa como una matriz de
(5.1)
3 3:
1 0 dx T (dx , dy ) = 0 1 dy 0 0 1
(5.2)
La operacin de escalamiento bidimensional (con el origen como punto de referencia) se representa similar:
sx 0 0 S (sx , sy ) = 0 sy 0 0 0 1
(5.3)
137
(5.4)
De este modo, para efectuar transformaciones geomtricas bsicas, esas funciones en forma de matrices, deben premultiplicarse por los puntos que se pretende transformar, as:
P = T (dx , dy ) P P = S (sx , sy ) P P = R ( ) P
P P P
es es
P P
con un desplazamiento de
(dx , dy ). sx
en
sy
en
es
movimiento de las manecillas del reloj, a partir del eje al origen del marco de referencia. -
5.3.
Con lo planteado hasta ahora, surge la necesidad de representar varias transformaciones geomtricas encadenadas, para producir resultados compuestos, que no se pueden lograr con las operaciones bsicas representadas por las ecuaciones 5.2, 5.3 y 5.4. Ejemplos de ello son las transformaciones realizadas en las guras 5.3 en la pgina 135 y 5.5 en la pgina anterior. Analicemos el caso de la gura 5.3: Para lograr tal transformacin, deberamos primero trasladar
hay que hacer el escalamiento con ambos factores a antes. Expresado de otra manera: Sea
cuadro transformado, a su posicin nal, con la esquina inferior derecha a donde estaba
formacin
Q = (8, 7) la esquina inferior derecha del cuadro original P . Si aplicamos la transT (8, 7) a todos los puntos de P , lo habremos movido de tal manera que Q 1 1 de P ha quedado en el origen. Posteriormente, aplicamos a P el escalamiento S 2, 2 para obtener P , que es un cuadro de ancho y alto igual a 1, con su esquina inferior derecha tambin en el origen. Despus, aplicamos la traslacin T (8, 7) a P , con lo que obtenemos P .
138
T (8, 7) S puntos de P Q
1 1 2, 2
como ejemplo:
Q Q Q
= T (8, 7) Q 1 1 = S Q =S , 2 2
1 1 , 2 2
T (8, 7) Q 1 1 , 2 2 T (8, 7) Q
= T (8, 7) Q = T (8, 7) S
5.4.
Atencin a la eciencia
r11 r12 tx M = r21 r22 ty 0 0 1 x P = y 1
Sucede que todas las composiciones de matrices de transformacin resultan en matrices de la siguiente forma:
Sin embargo, al realizar las operaciones, se puede apreciar que el resultado es previsible. El resultado siempre ser que
x = xr11 + yr12 + tx
y = xr21 + yr22 + ty .
Lo cual
muestra que nicamente son necesarias 4 multiplicaciones y 4 sumas. La importancia de esta consideracin es porque las transformaciones de este tipo son extensivamente usadas en la gracacin tridimensional. Y un gasto innecesario de tiempo de procesador para realizar operaciones de las que ya conocemos el resultado, hara disminuir el rendimiento sensiblemente.
5.5.
A continuacin analizaremos otra perspectiva del cambio de coordenadas entre dos marcos de referencia. Esta vez usando transformaciones matriciales. Reconsideremos la gura 4.2 en la pgina 111 y la ecuacin 4.1 en la pgina 112. De ellas podemos concluir que lo primero que hacemos para hacer un cambio de coordenadas, es un escalamiento, y luego hacemos un desplazamiento.
139
M R V = T
(R ) (R ) V x ,V y
x x
(R)
, (V )
y y
(R)
(5.5)
(V ) (V ) p
As que la transformacin que habamos realizado como bin se puede realizar como:
(R) p = T RV
, tam-
(R ) x , (V ) x (R) y (V ) y
(V ) px (V ) py 1
(5.6)
Hay un punto muy importante que recalcar en este punto, y es que as como hemos denido ms as:
MRV ,
tambin podramos denir un encadenamiento de transformaciones como en la gura 5.6. Esto proporciona la
sucesivas para pasar de un marco de referencia a otro, pasando por intermedio de otros
MDA = MDC MC B MB A ,
capacidad extra, de agregar rotaciones a dichos cambios de coordenadas o cualquier otra transformacin aplicable. En la gura 5.7 podemos ver un caso hipottico en el que el marco de referencia virtual, est inclinado referencia
(R) y x (V ) , (V ) x y (R)
(V ) px (V ) R () py 1
Desde la perspectiva vectorial, habra sido difcil ofrecer una solucin sencilla.
5.6.
En frecuentes ocaciones no slo necesitaremos efectuar transformaciones geomtricas a un conjunto de puntos, sino que tambin necesitaremos destransformarlos . Para hacer
140
141
P M
1
= M P = M 1 M P = M 1 M P = I P
M
y
= P
(A B )1 = B 1 A1
Esto signica que si tenemos
P =T
(R) (R) V x ,V y
(R) y x (V ) , (V ) x y
(R)
P,
entonces:
T x x
(R )
(R) (R) V x ,V y y y
(R) 1
x x
(R)
(V )
y y
(R )
(V ) 1
= I3 P
, (V )
(V )
(R) (R) V x ,V y
= P
y entonces basta con encontrar las respectivas matrices inversas y obtendremos la matriz de retro-transformacin para
P ).
Afortunadamente no es necesario calcular ninguna matriz inversa (ese algoritmo no es precisamente rpido ), ya que suceden los siguientes fenmenos (cuyas demostraciones se dejan como ejercicios):
142
5.7 Ejercicios
x x
(R)
, (V )
y y
(R)
(V )
T y
(V )
(R) (R) V x ,V y
P P
= P = P
(V )
(R) x
(R) y
(R) (R) V x , V y
Por lo que la idea general es que para invertir una serie de transformaciones geomtricas, basta con aplicar las transformaciones inversas (siguiendo las ecuaciones 5.7, 5.8 y 5.9) en orden inveso .
5.7.
Ejercicios
sentada en la gura 5.5?
2. Realizar la transformacin de las otras aristas del cuadro analizado en la seccin 5.3. 3. Considere la gura 5.8. Rote el tringulo -45 respecto de su esquina inferior derecha segn la regla de la mano derecha, con la ayuda de una matriz de transformacin bidimensional
M1 . M1 ?
a ) Cul es la denicin de
M2 .
a ) Cul es la denicin de
M2 ?
M3 . M3 ?
a ) Cul es la denicin de
143
6. Demuestre la ecuacin 5.7 en la pgina 142. 7. Demuestre la ecuacin 5.8. 8. Demuestre la ecuacin 5.9.
144
6.1.
Sistemas de referencia
La mayora de los curso de lgebra y clculo vectorial utilizan el sistema de referencia de la mano derecha, por lo que la mayora de los anlisis posteriores a eso, lo utilizan tambin. No seremos la excepcin, aunque conviene atender que algunas bibliotecas grcas utilizan por defecto el sistema de referencia de la mano izquierda por diversas razones. No ahondaremos ms en el asunto ya que se asume que el lector ha aprobado algn curso de lgebra y/o clculo vectorial. Baste mencionar que la matriz de transformacin para pasar del sistema de mano derecha al sistema de mano izquierda (y viceversa) es
S (1, 1, 1).
6.2.
x y P = z 1
(6.1)
145
146
4 4:
1 0 T (dx , dy , dz ) = 0 0
0 1 0 0
0 dx 0 dy 1 dz 0 1
(6.2)
La operacin de escalamiento tridimensional (con el origen como punto de referencia) se representa similar:
sx 0 0 0 sy 0 S (sx , sy , sz ) = 0 0 sz 0 0 0
0 0 0 1
(6.3)
Las operaciones de rotacin (respecto de cada eje principal, siguiendo la regla de la mano derecha) se representa as:
0 0 0 1
(6.4)
147
(6.5)
(6.6)
Rz ()
y la ecuacin de
que es para
6.3.
Las transformaciones geomtricas tridimensionales, al igual que las bidimensionales, tambin pueden componerse e invertirse . La lgica matemtica es idntica, por lo que nos limitaremos en este captulo a presentar las inversas de dichas transformaciones:
= Ry () = Ry ()
trnsformionesQhFjr1
aplicacin permite, de una manera muy simple, practicar las transformaciones geomtricas tridimensionales y ver sus efectos sobre dos objetos con volumen y un objeto plano que pueden traslaparse. Para ejecutar la aplicacin, se requiere de la mquina virtual de java (se recomienda la versin 1.6), procediendo as:
Consltese el captulo A en la pgina 291 para ms detalles sobre la compilacin y ejecucin de aplicaciones J2SE.
148
x+ , y +
z+
x-y-z y la regla de la mano derecha). Con la rueda del ratn se puede acercar y alejar la
A travs de la ventana grca no se pueden alterar los objetos mencionados, para eso es el intrprete de comandos en la consola (si escribe
Xyud
muestra una breve resea de los comandos permitidos. reinicia la aplicacin a su estado inicial por defecto. agrega un comentario sin efecto alguno sobre la aplicacin. muestra el objeto en cuestin (si ya est visible, no tiene efecto). oculta el objeto en cuestin (pero todava permite su transforma-
Xreiniir
X 7`omentriob
provoca un desplazamiento
T (dx , dy , dz )
en el objeto es-
provoca un escalamiento
X`ojetob eu `sb
do.
S (s, s, s),
Los objetos vlidos para esta versin de la aplicacin son El cubo tiene una arista de largo 4, el plano es de
43
y el ojo
3 es una pirmide de 4
todos.
2 3 4
en la subseccin 2.5.1 en la pgina 76 veremos su propsito en el siguiente captulo su utilidad no es relevante en este momento, pero se le puede considerar, por el momento, como una pirmide rectangular.
149
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
: plano ocultar : cubo t 2 0 0 : cubo eu 2 : cubo t 0 -4 -4 : cubo t -8 0 0 : cubo t 4 4 4 : cubo e 1 1 .5 : cubo e 1 .5 1 : cubo rx 30 : cubo rx -30 : cubo ry 60 : cubo ry 30 : cubo rz 45 : cubo e 1 1 .5 : cubo rz 45 : cubo ocultar :%%%%%%%%%%% : ojo mostrar : ojo ry 30 : ojo ry -30 : % prstese atencin a estas ltimas transformaciones : : ojo t 5 0 0 : ojo rz 30 : ojo rx 60 : ojo t 0 2 0 : ojo t 0 -2 0 : ojo rx -60 : ojo rz -30 : ojo t -5 0 0
Note que las ltimas cuatro transformaciones invirtieron exactamente las anteriores cuatro.
6.4.
Ejercicios
150
7.1.
Proyeccin Ortogonal
La proyeccin ortogonal consiste bsicamente en proyectar las guras sobre un plano de proyeccin en direccin ortogonal al plano. Veamos algunos ejemplos en la gura 7.1 en la pgina siguiente. En todas las imgenes podemos ver los ejes principales de colores rojo, verde y azul respectivamente . Los detalles geomtricos formales no son de nuestro inters en este momento ya que el propsito de este texto no es adentrarnos en el estudio imagenolgico de las proyecciones, sino entender su efecto visual desde la experiencia del usuario de las aplicaciones grcas tridimensionales. La principal caracterstica a resaltar, es que
x, y
NO existe
Como comentario personal del autor, es curioso que modelemos realidades tridimensionales en modelos de almacenamiento lineales (la memoria de la computadora es unidimensional) que a su vez, representamos en pantallas planas bidimensionales. igual que se explic en la seccin 6.3 en la pgina 148
151
152
1 2 3 4
ry 90 t -50 0 0 rz 25 ry -25
7.2.
Proyeccin en Perspectiva
En la proyeccin en perspectiva se proyectan las guras sobre el mismo plano de proyeccin que en la proyeccin ortogonal, pero cada punto en una direccin oblicua al plano, alineada con un foco de proyeccin (ver la gura 7.3 en la pgina 158). Veamos algunos ejemplos en la gura 7.2 en la pgina siguiente. En todas las imgenes podemos ver los ejes principales x-y-z de la misma manera que en la gura 7.1 en la pgina anterior. De nuevo, los detalles geomtricos formales no son de nuestro inters en este momento. La principal caracterstica a resaltar, es que ver en la gura. Los recuadros se corresponden con los de la gura 7.1, con la diferencia que all aparecen en proyeccin ortogonal y aqu en perspectiva, con la evidente sensacin de que hay partes de los cuerpos que estn ms cerca de nosotros que otras.
existe
7.3.
Portal de Visin
Cmo producir proyecciones de los tipos
presentados en las secciones anteriores? Y a estas alturas del libro, una parte
de la respuesta es obvia: Segursimo que se logran con transformaciones geomtricas
tridimensionales .
La pregunta consecuente es:
por que si no, no habra tenido sentido estudiar todo eso. . . verdad. . . ?
153
154
de Visin de una aplicacin grca tridimensional es una ventana, un rectngulo otante (desplazable o no) en el espacio tridimensional de la aplicacin. Este representa la pantalla bidimensional a travs de la cual, el usuario (o los usuarios) perciben esta realidad virtual tridimensional.
El portal de visin, es nuestro plano de proyeccin y su descripcin resulta fundamental para construir aplicaciones grcas tridimensionales. Habiendo aclarado eso, empecemos con las proyecciones ortogonales, que son ms fciles de comprender. A continuacin haremos el siguiente ejercicio, apoyandonos en nuestra aplicacin
trnsformionesQhFjr:
aplicacin grca tridimensional imaginaria y vamos a realizar las transformaciones geomtricas necesarias para transformar todo el escenario de tal manera que el portal de visin quede sobre el plano de visin
xy .
trnsformionesQhFjr
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
: % Ocultamos momentaneamente al cubo y al plano : % para concentrarnos en el portal de visin . : cubo ocultar : plano ocultar : % Coloquemos el " ojo " en posicin : : ojo mostrar : ojo t 0 0 5 : ojo rz 90 : ojo ry 60 : ojo rz 30 : % Ahora el ojo est en posicin de : % representar el portal de visin . : % Volvamos a visibilizar el cubo y el plano : : cubo mostrar : plano mostrar : % Agreguemos un detalle : : plano t 0 0 2 : % Veamos la imgen desde el portal : : ojo ocultar
4
asumimos que la parte de atrs es la que tiene la punta
155
: % Procedemos a hacer la alineacin con 'xy ' : ojo mostrar : todos rz -30 : todos ry -60 : todos rz -90 : todos t 0 0 -5 : % Ahora podramos ocultar el ojo : % para verlo mejor , :) : ojo ocultar
5
Ahora, si efectivamente imaginamos que el ojo es una pantalla de computadora , y nos ubicamos exactamente detrs de l, veremos la imagen de la pantalla imaginaria. Ahora revisemos qu fue exactamente lo que hicimos: Primero colocamos el portal de visin en una posicin conocida, con su centro en la coordenada
r : 5, : 6, :
(r, , ).
T (0, 0, r) Rz
Ry () Rz () 2
(7.1)
Luego podramos alinear la esquina inferior izquierda del portal de visin con el origen, agregando
Lo importante de este ejercicio, es entender cmo lograr proyecciones ortogonales a travs de transformaciones geomtricas tridimensionales.
7.4.
En este momento ya casi disponemos de los recursos tericos para proceder a disear una estrategia de sistematizacin de proyecciones ortogonales. Un detalle importante que falta es considerar que la ecuacin 7.1 no es la versin ms apropiada de las alternativas disponibles. A continuacin presentamos una secuencia de transformaciones que es ms apropiada:
M2D3D = T (0, 0, r) Rz
Ry ( ) Rz () 2
6
(7.2)
Esta transformacin es muy similar a la anteriormente presentada, pero presenta una importante mejora sin alterar signicativamente el tiempo de ejecusin : Coloca los
5 6
43
156
z+
y con el eje
y+
hacia abajo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
: % Ocultamos momentaneamente al cubo y al plano : cubo ocultar : plano ocultar : % Coloquemos el " ojo " en posicin : : ojo mostrar : ojo t 0 0 5 : ojo rz 90 : ojo ry 60 : ojo rz 30 : % Ahora el ojo est en posicin . : % Volvamos a visibilizar el cubo y el plano : : cubo mostrar : plano mostrar : plano t 0 0 2 : % Procedemos a hacer la alineacin con 'xy ' , : % pero de otra manera : : todos rz -30 : todos ry 120 : todos rz -90 : todos t 0 0 5
Luego de hacer esta transformacin, convendr hacer una transformacin extra hacia el marco de referencia real, con la transformacin 5.5 en la pgina 140 o alguna equivalente que se ajuste a las necesidades, as :
P P
(7.3)
idad de acercamiento y alejamiento. Sugerimos abrir el cdigo de dicha aplicacin para ver todo este proceso en accin. En particular, el cdigo que realiza la denicin de la matriz de transformacin est en la funcin del archivo
ortldeisionFjv.
7
recordando que el eje
y+
157
7.5.
Antes que nada, veamos ahora algunos detalles geomtricos anteriormente postergados de los tipos de proyecciones estudiados. La gura 7.3 nos muestra la diferencia fundamental entre ambos tipos de proyecciones: En las proyecciones ortogonales las guras se aplastan contra el plano en direccin siempre perpendicular a este (lo que se logra simplemente ignorando las componentes en
perspectiva, las guras se proyectan en direccin de un foco de proyeccin o centro de proyeccin. Tpicamente el centro de proyeccin est detrs del plano de proyeccin (como si fuera el ojo fsico del observador, en el cual tambin hay un foco de proyeccin) y los objetos a proyectar estn delante del plano (si los objetos estn detrs del foco, el efecto visual es bastante desagradable y confuso). Veamos a continuacin cmo implementar este fenmeno ptico: Utilicemos la gura 7.4 como referencia. En ella se presenta un punto proyectado sobre el plano ser
xy ,
(x, y, z )
que es
xper
semejantes:
158
xper d
xper =
(7.4)
yper d
yper =
y
(7.5)
zper = 0
Lo que signica que podra aplicarse la transformacin
(7.6)
d d d+z , d+z , 0
para realizar la
proyeccin, posterior a la transformacin 7.2 (recuerde las condiciones descritas al inicio de esta seccin). O sea:
Pper = S
d d d+z , d+z , 0
M2D3D P z
de cada punto a
proyectar, por lo que no es viable para un procesamiento automatizado; es decir, no tiene sentido hacer una multiplicacin matricial por cada punto a proyectar. Sera demasiado costoso, por lo que vamos a emplear otra estrategia: 1. Construir la matriz 2. Hacer
M2D3D
P = M2D3D P
proyeccin ortogonal)
159
a proyectar, hacer
Px Py Pz
d Px d + Pz d = Py d + Pz = 0 =
Pper = MRV P
Recomendamos, a manera de prctica, el uso de la aplicacin cosas que su hermana no ortogonal. Para ejecutar la aplicacin, tambin escrita en lenguaje java:
perspetivQhFjr9
que
se encuentra en el material adjunto a esta obra. Esta aplicacin permite hacer las mismas
6 jv Ejr perspetivQhFjr
Con esto se iniciar una ventana grca, idntica a la de la otra aplicacin, y se inicia un intrprete de comandos con las mismas alternativas. Recomendamos que se haga una comparacin entre el archivo la aplicacin
perspetivQhFjr
y el de
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 9
/* * Recibe el centro del portal de visin en coordenadas * esfricas (d , theta , phi ) */ public void colocarPuntodeVision ( Objeto3DSimple objetos [] , float theta , float phi , float distancia ) { this . theta = theta ; this . phi = phi ; this . distancia = distancia ; matriz . identidad () ; matriz . rotarZ ( - theta ) ; matriz . rotarY (180 - phi ) ; matriz . rotarZ ( -90) ; matriz . trasladar (0 , 0, distancia ) ;
Consltese el captulo A en la pgina 291 para ms detalles sobre la compilacin y compilacin de aplicaciones J2SE.
160
// la media del ancho y alto real entre // la media del ancho y alto virtual , por // un factor de aumento matriz . escalar (( float )( ( tamanhoR . width + tamanhoR . height ) /( AnchoV + AltoV ) / magnitud_aumento )); // traslamiento final : matriz . trasladar ( tamanhoR . width /2 , tamanhoR . height /2 , 0) ; for ( int i =0; i < objetos . length ; i ++) objetos [ i ]. transformarProyeccion ( matriz , this );
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
/* * Recibe el punto de visin en coordenadas esfricas ( distancia , theta , phi ) * * Asumimos que dicha distancia representa tanto la * distancia del origen virtual al centro del portal de visin * como a la distancia del centro de proyeccin * al centro del portal de visin . * Esto es por simplicidad en este cdigo * pero no necesariamente debe ser as siempre . */ public void colocarPuntodeVision ( Objeto3DSimple objetos [] , float theta , float phi , float distancia ) { this . theta = theta ; this . phi = phi ; this . distancia = this . distanciaProyeccion = distancia ; matriz . identidad () ; matriz . rotarZ ( - theta ) ; matriz . rotarY (180 - phi ) ; matriz . rotarZ ( -90) ; matriz . trasladar (0 , 0 , distancia ) ; for ( int i =0; i < objetos . length ; i ++) objetos [ i ]. transformarProyeccion ( matriz , this , true ) ; matriz . identidad () ; // la media del ancho y alto real entre // la media del ancho y alto virtual , por // un factor de aumento matriz . escalar (( float )(
161
( tamanhoR . width + tamanhoR . height ) /( AnchoV + AltoV ) // / 3 f / distancia )); // traslamiento final : matriz . trasladar ( tamanhoR . width /2 , tamanhoR . height /2 , 0) ; for ( int i =0; i < objetos . length ; i ++) objetos [ i ]. transformarProyeccion2 ( matriz , this ) ;
7.6.
Ejercicios
tiene una distancia ja de una unidad (ver archivo
1. En la aplicacin
quiere decir que eventualmente los objetos a proyectar se encuentran detrs del Portal de Visin de la aplicacin. Por qu la imgen no se distorciona como sucede con
perspetivQhFjr?
2. Explique qu pasa cuando el centro de proyeccin en perspectiva est muy lejano de los objetos proyectados. 3. Explique geomtricamente (o matemticamente) qu es lo que sucede cuando se realiza la proyeccin en perspectiva de un objeto que se encuentra detrs del centro de proyeccin. 4. Todo el anlisis realizado hasta ahora implica que el portal de visin ve siempre directamente al origen del universo virtual. Reexione cmo hacer para dirigirlo en direciones arbitrarias. Es decir, cmo hacer para que el usuario pueda ver en otras direcciones?
162
8 Interaccin
Este captulo no pretende ser un tratado completo sobre tcnicas de interaccin humanomquina, sino una breve reexin sobre la importancia de hacer aplicaciones grcas interactivas amigables, fciles de usar e intuitivas. La razn por la que es importante que nuestras aplicaciones sean amigables (y el grado de amigabilidad depender de los usuarios a los que est orientada) es porque gran parte del xito en la difusin de una aplicacin, ya sea un juego de video, una aplicacin educativa, de entretenimiento, un simulador de algn fenmeno fsico, social, econmico, etc. depender del hecho que el usuario logre sus objetivos y que no se frustre al intentar aprender a usarlas. El respeto de estndares ociales o de facto, es muy importante en estos das. Ya no estamos en los das en los que se podan denir arbitrariamente paradigmas de interaccin.
8.1.
Posicionamiento
En una aplicacin con varios objetos (en dos o tres dimensiones), el usuario debera poder ser capaz de abrise paso, de algn modo, para lograr posicionarse al alcance de algn objeto especco que quisiera ver o modicar. Como programadores, a veces olvidamos las reacciones lgicas de una persona normal , como por ejemplo, utilizar las teclas cursoras, la de
gin enterior
gin iguiente
ulin
y las teclas
de trabajo de las aplicaciones que hacemos. A veces se nos olvida programar esas teclas y otras combinaciones que son obvias para todo el mundo en estos das. Por ello es importante presentarle la aplicacin a usuarios no familiarizados con el desarrollo de la aplicacin (de preferencia personas normales) para evaluar objetivamente si la aplicacin no tiene un interaccin excesivamente compleja o confusa antes de darla por terminada. En el caso de aplicaciones bidimensionales, el problema de posicionamiento suele resolverse con barras de desplazamiento al rededor del espacio de trabajo. Y en las ocaciones
163
8 Interaccin
en que se operan objetos que pueden traslaparse, es conveniente disponer funciones del tipo Enviar al fondo, Enviar atrs, Traer adelante y Traer al frente. En el caso de aplicaciones tridimensionales, el problema de posicionamiento representa comunmente un problema de navegacin espacial. En donde usualmente habr que disear una estrategia de limitar el volumen visible. Usualmente no todo lo que est en el universo virtual debera ser visible por el usuario en cada posicin especca. Este fenmeno puede verse en diversos juegos de video 3D de carreras, en las que el paisaje va apareciendo a medida que el jugador se va desplazando por la pista. Esto por supuesto se debe a dos factores: El primero es que requerira demasiado procesamiento para mostrar todos los detalles lejanos y el segundo es que sera demasiada informacin para que un usuario tpico la procese. Partes importantes del problema de navegacin, son el problema del cambio de direccin y la magnitud del desplazamiento lineal. El primero se reere a cmo cambiar la direccin de movimiento o la orientacin en general del portal de visin, y el segundo se reere a la cantidad de desplazamiento a realizar en una direccin especca o en general en cualquier direccin. En el caso de nuestras aplicaciones de ejemplo de los ltimos captulos, el problema de la navegacin espacial est limitado ya que el portal de visin siempre mira jamente en direccin del origen de coordenadas del universo virtual. Sin embargo, s existe el problema del cambio de direccin, resuelto con arrastre del ratn; y tambin existe el problema de la magnitud de desplazamiento lineal, resuelto con la rueda del ratn. La aplicacin
perspetivQhFjr
itacin del volumen visible, y es que no limita el volumen visible, por lo que proyecta incluso objetos detrs del centro de proyeccin (lo cual genera una distorcin visual ).
8.2.
Seleccin
ms cerca del usuario, es decir, del portal de visin. Esto es porque no debera tener
;-)
164
8.3 Transformacin
sentido seleccionar objetos que estn detrs del portal de visin, en el caso de proyeccin ortogonal, o detrs del centro de proyeccin, en el caso de proyeccin en perspectiva. Y
(b) Averguar a qu punto del universo virtual, corresponde el punto sobre el que el
usuario hizo
li.
transformaciones geomtricas aplicadas. Ntese que si aplicamos las reglas de la seccin 7.5 para la proyeccin en perspectiva, la transformacin en conjunto se vuelve irreversible (debido a la ecuacin 7.6 en la pgina 159). Una estrategia simple es obviar ejecutar la ecuacin 7.6. Dado que las transformaciones que le siguen slo afectan las otras dos dimensiones, no habr efectos colaterales negativos, y por el contrario, la inversin ser posible (tambin como un proceso de tres faces).
8.3.
Transformacin
Las transformaciones sobre objetos bidimensionales y tridimensionales deberan incluir slo las transformaciones que tengan sentido para el propsito de la aplicacin. Estas podran incluir las transformaciones bsicas estudiadas en los captulos 5 y 6 y/u otras transformaciones compuestas, relevantes, de nuevo, para la lgica de la aplicacin. En el caso bidimensional, las transformaciones se logran resolver generalmente con cambios de marco de referencia (ver captulo 4), pero en el caso tridimensional, hay que utilizar estrategias cognitivamente ecaces para que el usuario no se sienta excesivamente perdido. A continuacin mencionamos algunos ejemplos: Para rotar una escena 3D, podra utilizarse la estrategia de asociar el arrastre del ratn en
de la
La aplicacin es as porque su propsito es practicar las tranformaciones geomtricas en trminos de multiplicaciones matriciales y no en trminos grcos.
165
8 Interaccin
coordenada del centro del portal. Esta tcnica es usada en las aplicaciones usadas en los ltimos captulos. Para realizar acercamiento o alejamiento en una escena 3D, puede usarse la ya tradicional rueda del ratn, tal vez en combinacin con la tecla
trl
shift.
Para el desplazamiento de objetos o de toda una escena 3D, o del origen del marco de referencia podra usarse arrastre del ratn combinado con alguna tecla como
trl
shift.
8.4.
Conclusin
Evidentemente, existen diversas (y tal vez ilimitadas) formas de interaccin entre los usuarios y las aplicaciones grcas interactivas, por lo que, lo que nalmente podemos decir a manera de recomendacin general, es seguir el principio que la magnitud de las acciones del usuario sea proporcional a la magnitud de las transformaciones que estas acciones realizan. Obviamente esta proporcin tambin debe ser ergonmica , apropiada para la naturaleza humana. A manera de ejemplo de esto ltimo, recomendamos examinar el archivo
gonfigQhFjv
de las aplicaciones usadas en los ltimos captulos. En ellos aparecen varias constantes que controlan el funcionamiento de las aplicaciones. Estos valores fueron denidos debido a que proporcionan un comportamiento ms cmodo que otros valores. En particular las constantes que controlan la relacin entre pixeles arrastrados con el ratn y el giro del universo virtual son:
41 42 43 44 45 46 47
// para poder rotar con el mouse : int numPixelesparaMovimiento = 1; float factorRapidezGiro = 4 f ; // aumento del zoom por movimiento de la rueda del ratn : static final float MAGNITUD_AUMENTO = 0.1 f ; static final float MAGNITUD_AUMENTO_INICIAL = 5 f ;
166
9 Curvas Paramtricas
9.1. Representacin algebrica de Curvas Paramtricas
Las curvas paramtricas suelen representarse de la siguiente manera:
x = f (t) y = g (t)
restricciones
g,
segn la notacin).
se representa as:
(x1 , y1 )
(x2 , y2 )
as:
en
en
y:
(x1 , y1 ) = (2, 1)
(x2 , y2 ) = (4, 5)
R = 5, un a=2y
b = 3.
167
9 Curvas Paramtricas
168
9.2 Continuidad
y,
eje
y:
1 2 3 4 5 6
scritp de Octave:
9.2.
Continuidad
169
9 Curvas Paramtricas
si
x c
l m f (x) = f (c)
(Lo que a su vez se cumple cuando el lmite por la izquierda y por la derecha son iguales).
Concepto de
curva suave:
f (x)
[a, b]
si
f (x)
[a, b].
en
curva suave en
[a, b]
si
r (t)
[a, b]
r =0
Continuidad Geomtrica
Se dice que hay continuidad geomtrica de grado 0, denotada por curvas suaves
G0 ,
entre dos
S1
S2
en el punto
con
t = ,
si
S1 ( ) = S2 ( ).
Es decir, si las
curvas se unen en
p. G1 ,
entre dos
Se dice que hay continuidad geomtrica de grado 1, denotada por curvas suaves
S1
S2 p
en el punto
p con t =
G1
S2
sea suave.
Continuidad Paramtrica
Se dice que hay continuidad paramtrica de grado 1 (o de primer grado), denotada por
S2
en el punto
(en
170
9.2 Continuidad
ese mismo punto y con el mismo valor de parmetro) y si las curvas se unen en
S1 ( ) = S2 ( ). G1
con
Es decir,
C1 C 1.
k = 1.
C 1 G1 , C1
pero
G1
S1
S2
es suave en todo
C n,
entre dos
S1
S2
en el punto
con
t=
S1
S2
C1
9.2.3. Interpolacin
Interpolacin segn [RAE]: Calcular el valor aproximado de una magnitud en un intervalo, cuando se conocen algunos de los valores que toma a uno intervalo.
171
9 Curvas Paramtricas
Veamos la gura 9.3. En ella, el intervalo mencionado en la denicin es que s son conocidos.
los puntos negros. Y el punto griz es un valor aproximado a partir de los puntos negros
9.3.
Spline
Se le conoce como Trazadores Interpolantes Cbicos a un conjunto de segmentos de curva polinomial de tercer grado que unen una serie de puntos en el espacio desconocidos al interior de dicha serie de puntos. Existen diversas maneras de construir trazadores interpolantes cbicos, cada una con sus peculiaridades matemticas y geomtricas. En este libro nos concentraremos en un tipo particular:
comunmente
llamados nodos entre s; y que adems, pueden ser utilizados para interpolar puntos
(1) Las curvas Spline interpolan todos los puntos (2) Todos los segmentos de las curvas Spline
dependen simultaneamente de todos los puntos de control, mientras que los segmentos de las B-spline dependen slo de los puntos de control ms cercanos. Una tercera diferencia las ltimas.
incidental si se quiere
es que
mucho ms rpido que las Spline, debido a la alta complejidad del algoritmo para calcular
C2
172
Spline
denida en
[a, b] S
es una funcin que cumple con las condiciones siguientes: es una funcin seccionada, y en todos los casos, es un polinomio cbico,
S (x)
denotado por 2. 3. 4. 5.
Sj (x),
en el subintervalo
[xj , xj +1 ]
para cada
j = 0, 1, . . . , n 1;
S (xj ) = f (xj )
para cada
j = 0, 1, . . . , n; j = 0, 1, . . . , n 2; j = 0, 1, . . . , n 2; j = 0, 1, . . . , n 2;
S (x0 ) = S (xn ) = 0 (frontera libre o natural); S (x0 ) = f (x0 ) y S (xn ) = f (xn ) (frontera sujeta). S
tiene la siguiente forma:
El trazador
S (x) =
S0 (x) S1 (x)
x0 x < x1 x1 x < x2
donde Est
j = 0, 1, . . . , n 1.
Hay varias cosas que hay que notar: Para un valor de garantizan continuidad los nodos.
ms cercano por la izquierda. Por otro lado, es de recalcar que las condiciones 3, 4 y 5
C 2.
y que satisface
la siguiente manera (algoritmo adaptado del algoritmo 3.4 de [Burden y Faires 2002, p.
Valores de entrada:
173
9 Curvas Paramtricas
i = 0, 1, ..., n 1
a)
hi = xi+1 xi i = 1, 2, ..., n 1
3 hi
3. PARA
a)
i =
1
(ai+1 ai )
3 hi1
(ai ai1 )
4. HACER :
l0 = 1 0 = 0 z0 = 0
5. PARA
i = 1, 2, ..., n 1
a ) HACER:
cn = 0
7. PARA
j = n 1, n 2, ..., 0
a ) HACER:
cj = zj j cj +1 a aj h (c +2cj ) bj = j +1 j j +1 hj 3 dj =
8. FIN
cj +1 cj 3hj
Valores de salida:
aj , bj , cj , dj
1
para
j = 0, 1, ..., n 1
Los pasos 4, 5, 6 y parte del 7 resuelven un sistema lineal tridiagonal descrito en el algoritmo 6.7 de [Burden y Faires 2002, p. 408]
174
Spline
x; n; x0 , x1 , . . . , xn
aj , bj , cj , dj
para
j = 0, 1, . . . , n 1
a)
b ) MIENTRAS
j =j+1
c ) SI
j<n y = aj + bj (x xj ) + cj (x xj )2 + dj (x xj )3
o bien con la regla de Horner:
1) HACER:
x,
S .
para obtener
(x, y ) S
ERROR!.
p+1
puntos uniformemente
[x0 , xn ]). p; n; x0 , x1 , . . . , xn
y
aj , bj , cj , dj
para
j = 0, 1, . . . , n 1
i=1
k = 0, 1, . . . , p
a ) HACER: b)
2
Este algoritmo implementa bsqueda lineal, pero podra ser ms eciente con bsqueda binaria.
175
9 Curvas Paramtricas
1) HACER:
c ) HACER d ) HACER:
i=i+1 i=i1
x0 , x1 , . . . , xp
y0 , y1 , . . . , yp .
El caso bidimensional
Para conectar los puntos
(x0, y0 ) , (x1, y1 ) , . . . , (xn , yn ), en el orden dado, an cuando no formen una funcin, se usa una nueva variable t en un intervalo [t0, tn ] (tpicamente [t0 = 0, tn = 1]) con t0 < t1 < . . . < tn (la distribucin puede ser uniforme o no).
Se recomponen los pares ordenados dados como si fueran dos problemas diferentes, as:
x = S (x) (t)
y = S (y) (t).
(x, y ) =
t0 t < t1 t1 t < t2
. . .
tn1 t tn
(x)
y
Sj (t) = aj + bj (t tj ) + cj (t tj )2 + dj (t tj )3
(y ) (y ) (y ) (y )
(x)
( x)
(x)
(x)
Sj (t) = aj + bj (t tj ) + cj (t tj )2 + dj (t tj )3
(y )
para cada
j = 0, 1, ..., n 1.
El caso tridimensional
Para conectar los puntos una nueva variable
en un intervalo
(x0, y0 , z0 ) , (x1, y1 , z1 ) , . . . , (xn , yn , zn ), en el orden dado, se usa [t0, tn ] (de nuevo, tpicamente [t0 = 0, tn = 1]) con
176
Spline
Se recomponen los pares ordenados dados como si fueran tres problemas diferentes, as:
(t0, x0 ), (t1, x1 ), ..., (tn , xn ) (t0, y0 ), (t1, y1 ), ..., (tn , yn ) (t0, z0 ), (t1, z1 ), ..., (tn , zn )
Se construyen entonces tres trazadores interpolantes cbicos,
z=
S (z ) (t). As que los valores que interpolan los puntos originales son: (x) (y ) (z ) S0 (t), S0 (t), S0 (t) S (x) (t), S (y) (t), S (z ) (t) 1 1 1
. . .
t0 t < t1 t1 t < t2
. . .
(x, y, z ) =
tn1 t tn
Sj (t) = aj + bj (t tj ) + cj (t tj )2 + dj (t tj )3 ,
(y ) (y ) (y ) (y )
y para cada
(x)
(x)
(x)
(x)
(x)
Sj (t) = aj + bj (t tj ) + cj (t tj )2 + dj (t tj )3 Sj (t) = aj + bj (t tj ) + cj (t tj )2 + dj (t tj )3
(z ) (z ) (z ) (z ) (z )
(y )
j = 0, 1, ..., n 1.
t; n; t0 , t1 , . . . , tn ; aj , bj , cj , dj ;
(x)
(x)
(x)
(x)
aj , bj , cj , dj
(y )
(y )
(y )
(y )
para
j=
0, 1, ..., n 1
1. INICIO 2. SI
a)
b ) MIENTRAS
j =j+1
c ) SI
j<n
177
9 Curvas Paramtricas
1) HACER:
x = aj + bj (t tj ) + cj (t tj )2 + dj (t tj )3 y = aj + bj (t tj ) + cj (t tj )2 + dj (t tj )3
2) FIN 3. ERROR: El valor de 4. FIN Valores de salida:
(x)
(x) (y )
( x)
(x)
(y )
(y )
(y )
t,
S .
x; y ,
para obtener
(x, y ) S
ERROR!.
p+1
(x)
[t0 , tn ]): p; n; t0 , t1 , . . . , tn ; aj , bj , cj , dj
(x) (x) (x)
y
Valores de entrada:
aj , bj , cj , dj
(y )
(y )
(y )
(y )
para
j =
0, 1, . . . , n 1
1. INICIO 2. HACER: 3. PARA
i=1 = t0 + k p (tn t0 )
k = 0, 1, . . . , p
a ) HACER: tk
b ) MIENTRAS
c)
d ) HACER: (x) (x) (x) (x) xk = ai + bi (tk ti ) + ci (tk ti )2 + di (tk ti )3 (y ) (y ) (y ) (y ) yk = ai + bi (tk ti ) + ci (tk ti )2 + di (tk ti )3
4. FIN Valores de salida:
x0 , x1 , . . . , xp
y0 , y1 , . . . , yp .
178
Spline
En los siguientes archivos se especican la estructura de datos usada y las funciones que la operarn (ntese que es de propsito general).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
// / c09 / trazadores / tic . h # include < stdio .h > # define # define # define # define # define # define # define # define # define # define # define # define # define /* TIC_COD_EXITO 0 TIC_MSG_EXITO " xito \ n" TIC_COD_ERROR_PARAMETROS -1 TIC_MSG_ERROR_PARAMETROS " Error de parmetro ( s ) \ n " TIC_COD_ERROR_DOMINIO -2 TIC_MSG_ERROR_DOMINIO " Error de dominio \ n " TIC_COD_ERROR_MEMORIA -3 TIC_MSG_ERROR_MEMORIA " Error de solicitud de memoria dinmica \ n " H ALFA L MIU Z 0 1 2 3 4
Contiene toda la informacin para calcular los trazadores . El clculo de los trazadores es el siguiente : S ( x ) = S[ j ]( x ) = a [ j ] + b[ j ]*( x - x [ j ]) + c [ j ]*( x - x [ j ]) ^2 + d [j ]*( x - x [ j ]) ^3 = a [ j ] + (x - x [ j ]) * ( b [ j] + (x - x [ j ]) * ( c [ j ] + (x - x [j ]) * d [ j ] ) ) para x [ j ] <= x <= x [ j +1] ( ntese que los clculos se hacen con el nodo ms cercano a 'x ' por la izquierda ) Se asume que : x [0] < x [1] < ... < x[ num_trazadores ]
// coordenadas de los nodos double * x ; // las imgenes 'y ' estn en el arreglo 'a ' double * y ; // coeficientes de los polinomios double * a ; double * b ; double * c ; double * d ;
179
9 Curvas Paramtricas
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
// nmero de polinomios int num_trazadores ; // siempre debe cumplirse la siguiente relacin : // num_nodos = num_trazadores + 1 } trazadores ; /* Entradas : ( ' x ','y ') define los nodos ' num_nodos ' indica el nmero de nodos Salida : Se devuelve un puntero a la estructura creada o NULL si hubo error . */ trazadores * TIC_crear_trazadores ( double x [] , double y [] , int num_nodos ) ; /* Toma el valor 'x ' y calcula su imagen en funcin del trazador que le corresponda y la deposita en 'y ' */ int TIC_calcular ( trazadores * tr , double x , double * y ) ; /* Entradas : ( ' x ','y ') define los nodos Salida : Se devuelve un codigo de resultado . Se asume que los arreglos son de la misma longitud entre s y con el valor ' num_nodos ' usado para crear a ' tr ' */ int TIC_modificar_trazador ( trazadores * tr , double x [] , double y []) ; /*
Libera la memoria dinmicamente solicitada de la estructura */ void TIC_liberar_memoria ( trazadores * tr ) ; /* Imprime en la salida especificada los valores almacenados en la estructura . */ void TIC_imprimir ( trazadores * tr , FILE * f ) ; /* ************** Paramtricos 2 D **************************** */
180
Spline
typedef struct trazadoresP { // coordenadas de los nodos double * t ; // las imgenes 'x ' estn en el arreglo ' ax ' double * x ; // coeficientes de los polinomios en x double * ax ; double * bx ; double * cx ; double * dx ; // las imgenes 'y ' estn en el arreglo ' ay ' double * y ; // coeficientes de los polinomios en y double * ay ; double * by ; double * cy ; double * dy ; // nmero de polinomios int num_trazadores ; // la siguiente relacin siempre debe cumplirse : // num_nodos = num_trazadores + 1 } trazadoresP ; trazadoresP * TIC_crear_trazadoresP ( double x [] , double y [] , int num_nodos ) ; /* Toma el valor 't ' y calcula su imagen en funcin del trazador que le corresponda y la deposita en ( ' x ','y ') */ int TIC_calcularP ( trazadoresP * tr , double t , double *x , double * y ) ; /* Se asume que los arreglos son de la misma longitud entre s y con el valor ' num_nodos ' usado para crear ' tr ' */ int TIC_modificar_trazadorP ( trazadoresP * tr , double x [] , double y []) ; /* Cuando se han actualizado los valores de los nodos pero no se han agregado o quitado . Se recalculan los coeficientes .
181
9 Curvas Paramtricas
145 146 147 148 149 150
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
// / c09 / trazadores / tic . c # include " tic . h " # include < stdio .h > # include < stdlib .h > trazadores * TIC_crear_trazadores ( double x [] , double y [] , int num_nodos ) { trazadores * tr = NULL ; int i; if (( tr = ( trazadores *) malloc ( sizeof ( trazadores ) ) ) == NULL ) { fprintf ( stderr , TIC_MSG_ERROR_MEMORIA ) ; return NULL ; } tr - > x = tr - > y = tr - > a = tr - > b = tr - > c = tr - > d = NULL ; /* 'x ', 'y ' y 'a ' requieren ' num_nodos ' elementos , 'b ', 'c ' y 'd ' slo ' num_nodos -1= num_trazadores ', pero si 'c ' tiene ' num_nodos ', se simplifica la parte final del algoritmo */ if (( tr - > x = ( double *) malloc (5 * num_nodos * sizeof ( double ) ) ) == NULL ) { fprintf ( stderr , TIC_MSG_ERROR_MEMORIA ) ; TIC_liberar_memoria ( tr ) ; free ( tr ) ; return NULL ; } for ( i =0; i < 5 * num_nodos ; i ++) tr - > x [ i ]=0.0; tr - > a = tr - > y tr - > x tr - > b = tr - > a tr - > c = tr - > b tr - > d = tr - > c = + + + + num_nodos ; num_nodos ; num_nodos ; num_nodos ;
182
Spline
int TIC_modificar_trazador ( trazadores * tr , double x [] , double y []) { int i ; int n ; double * tmp [5]; // Verificar integridad de los parmetros if ( tr == NULL || tr - > a == NULL || tr - > b == NULL || tr - > c == NULL || tr - > d == NULL || x == NULL || y == NULL || tr - > num_trazadores < 1) { fprintf ( stderr , TIC_MSG_ERROR_PARAMETROS ) ; return TIC_COD_ERROR_PARAMETROS ; } // Gestionar memoria temporal para el algoritmo if ( ( tmp [0] = ( double *) malloc (5 * tr - > num_trazadores * sizeof ( double ) )) == NULL ) { fprintf ( stderr , TIC_MSG_ERROR_MEMORIA ) ; return TIC_COD_ERROR_MEMORIA ; } for ( i =1; i <5; i ++) tmp [ i ] = tmp [0] + i * tr - > num_trazadores ; // Clculo de los trazadores n = tr - > num_trazadores ; for ( i =0; i <= n ; i ++) { tr - > x [ i ] = x [ i ]; tr - > a [ i ] = y [ i ]; // Copiar los valores } // paso 2... for ( i =0; i < n ; i ++) tmp [ H ][ i ] = tr - > x [ i +1] - tr - > x [ i ]; // paso 3... for ( i =1; i < n ; i ++) tmp [ ALFA ][ i ] = 3 * ( ( tr - > a [ i +1] - tr - > a [ i ]) / tmp [ H ][ i ] ( tr - > a [ i ] - tr - > a [i -1]) / tmp [ H ][ i -1]
183
9 Curvas Paramtricas
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
); // paso 4... tmp [ L ][0] = 1; tmp [ MIU ][0] = 0; tmp [ Z ][0] = 0; // paso 5... for ( i =1; i < n ; i ++) { tmp [ L ][ i ] = 2 * ( tr -> x [ i +1] - tr - > x [i -1]) - tmp [ H ][ i -1]* tmp [ MIU ][ i -1]; tmp [ MIU ][ i ] = tmp [ H ][ i ] / tmp [ L ][ i ]; tmp [ Z ][ i ] = ( tmp [ ALFA ][ i] - tmp [ H ][ i -1]* tmp [ Z ][ i -1]) / tmp [L ][ i ]; } // paso 6... tr - > c [ n ] = 0; // paso 7... for ( i =n -1; i >=0; i - -) { tr - > c [ i ] = tmp [ Z ][ i ] - tmp [ MIU ][ i ] * tr - > c[ i +1]; tr - > b [ i ] = ( tr - > a [ i +1] - tr - > a [ i ]) / tmp [ H ][ i ] - tmp [ H ][ i ] * (tr - > c [ i +1] + 2* tr -> c [ i ]) / 3; tr - > d [ i ] = ( tr - > c [ i +1] - tr - > c [ i ]) / (3* tmp [ H ][ i ]) ; } // Liberar memoria temporal free ( tmp [0]) ; } return TIC_COD_EXITO ;
int TIC_calcular ( trazadores * tr , double x , double * y ) { if ( tr == NULL || y == NULL ) { fprintf ( stderr , TIC_MSG_ERROR_PARAMETROS ) ; return TIC_COD_ERROR_PARAMETROS ; } if ( x >= tr - > x [0]) { /* int j =0; while ( j < tr - > num_trazadores && x > tr - > x [j +1]) j ++; if ( j < tr - > num_trazadores ) { double tmpX = x - tr - > x [ j ]; * y = tr - > a [ j ] + tmpX * ( tr - > b [ j ] + tmpX * ( tr - > c[ j ] + tmpX * tr - > d [ j ] ) ) ; return TIC_COD_EXITO ; } */
184
Spline
// Esta alternativa es ms eficiente que la de arriba : int j =1; while ( j <= tr - > num_trazadores && x > tr - > x [j ]) j ++; if ( - - j < tr - > num_trazadores ) { double tmpX = x - tr - > x [ j ]; * y = tr -> a [ j ] + tmpX * ( tr - > b [ j ] + tmpX * ( tr - > c [ j] + tmpX * tr - > d [ j] ) ) ; return TIC_COD_EXITO ; }
// fprintf ( stderr , TIC_MSG_ERROR_DOMINIO ) ; return TIC_COD_ERROR_DOMINIO ; } void TIC_liberar_memoria ( trazadores * tr ){ free ( tr - > x ) ; } void TIC_imprimir ( trazadores * tr , FILE * f ) { int i ; fprintf (f , " \ nTrazadores :\ n ") ; fprintf (f , " i \ tx_i \ ty_i \ ta_i \ tb_i \ tc_i \ td_i \ n " ) ; for ( i =0; i <= tr - > num_trazadores ; i ++) fprintf (f , " %d \ t %3.2 g \ t %3.2 g\ t %3.2 g \ t %3.2 g \ t %3.2 g \t %3.2 g \ n " , i , tr - > x [ i ] , tr - >y [ i ] , tr - > a [ i ] , tr - > b [ i ] , tr - > c [ i ] , tr - > d [ i ]) ; } /* *** Paramtricos ******************** */ trazadoresP * TIC_crear_trazadoresP ( double x [] , double y [] , int num_nodos ) { trazadoresP * tr = NULL ; int i ; if (( tr = ( trazadoresP *) malloc ( sizeof ( trazadoresP ) ) ) == NULL ) { fprintf ( stderr , TIC_MSG_ERROR_MEMORIA ) ; return NULL ; } tr - > x = tr - > y = tr - > t = tr - > ax = tr - > bx = tr -> cx = tr - > dx = tr - > ay = tr - > by = tr -> cy = tr - > dy = NULL ; /* 'x ', 'y ' y 'a ' requieren ' num_nodos ' elementos , 'b ', 'c ' y 'd ' slo ' num_nodos -1= num_trazadores ',
185
9 Curvas Paramtricas
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
pero si 'c ' tiene ' num_nodos ', se simplifica la parte final del algoritmo , por lo que todos tendrn el mismo tamao de ' num_nodos '. */ if (( tr - > t = ( double *) malloc (9 * num_nodos * sizeof ( double ) ) ) == NULL ) { fprintf ( stderr , TIC_MSG_ERROR_MEMORIA ) ; free ( tr ) ; return NULL ; } for ( i =0; i < 9 * num_nodos ; i ++) tr - > t [ i ]=0.0; tr - > ax = tr - > x = tr - > t + num_nodos ; tr - > bx = tr - > ax + num_nodos ; tr - > cx = tr - > bx + num_nodos ; tr - > dx = tr - > cx + num_nodos ; tr - > ay = tr - > y = tr - > dx + tr - > by = tr - > ay + tr - > cy = tr - > by + tr - > dy = tr - > cy + num_nodos ; num_nodos ; num_nodos ; num_nodos ;
tr - > num_trazadores = num_nodos - 1; if (! TIC_modificar_trazadorP ( tr , x , y ) ) return tr ; else { TIC_liberar_memoriaP ( tr ) ; free ( tr ) ; return NULL ; }
int TIC_calcularP ( trazadoresP * tr , double t , double *x , double * y ) { if ( tr == NULL || x == NULL || y == NULL ) { fprintf ( stderr , TIC_MSG_ERROR_PARAMETROS ) ; return TIC_COD_ERROR_PARAMETROS ; } if ( t >= tr - > t [0]) { int j =1; while ( j <= tr - > num_trazadores && t > tr - > t[ j ]) j ++; if ( - - j < tr - > num_trazadores ) { double tmpT = t - tr - > t [ j ];
186
Spline
* x = tr -> ax [ j ] + tmpT tmpT * tr - > dx [ j ] ) * y = tr -> ay [ j ] + tmpT tmpT * tr - > dy [ j ] ) return TIC_COD_EXITO ;
// fprintf ( stderr , TIC_MSG_ERROR_DOMINIO ) ; return TIC_COD_ERROR_DOMINIO ; } int TIC_actualizar_trazadorP ( trazadoresP * tr ){ return TIC_modificar_trazadorP ( tr , tr - >ax , tr - > ay ) ; } int TIC_modificar_trazadorP ( trazadoresP * tr , double x [] , double y []) { int i ; int n ; double * tmp [5]; // Verificar integridad de los parmetros if ( tr == NULL || tr - > ax == NULL || tr - > bx == NULL || tr -> cx == NULL || tr - > dx == NULL || tr - > ay == NULL || tr - > by == NULL || tr -> cy == NULL || tr - > dy == NULL || x == NULL || y == NULL || tr - > num_trazadores < 1) { fprintf ( stderr , TIC_MSG_ERROR_PARAMETROS ) ; return TIC_COD_ERROR_PARAMETROS ; } // Gestionar memoria temporal para el algoritmo if ( ( tmp [0] = ( double *) malloc (5 * tr - > num_trazadores * sizeof ( double ) )) == NULL ) { fprintf ( stderr , TIC_MSG_ERROR_MEMORIA ) ; return TIC_COD_ERROR_MEMORIA ; } for ( i =1; i <5; i ++) tmp [ i ] = tmp [0] + i * tr - > num_trazadores ; // Clculo de los trazadores n = tr - > num_trazadores ;
187
9 Curvas Paramtricas
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
// Distribuir uniformemente el parametro 't ' // y asignar los coeficientes 'a ' for ( i =0; i <= n ; i ++) { tr - > t [ i ] = i / ( float ) n ; tr - > ax [ i ] = x [ i ]; tr - > ay [ i ] = y [ i ]; } // paso 2... for ( i =0; i < n ; i ++) tmp [ H ][ i ] = tr - > t [ i +1] - tr - > t [ i ]; /* *** en x **************** */ // paso 3... for ( i =1; i < n ; i ++) tmp [ ALFA ][ i ] = 3 * ( ( tr - > ax [ i +1] - tr - > ax [ i ]) / tmp [ H ][ i ] ( tr - > ax [ i ] - tr - > ax [i -1]) / tmp [ H ][ i -1] ); // paso 4... tmp [ L ][0] = 1; tmp [ MIU ][0] = 0; tmp [ Z ][0] = 0; // paso 5... for ( i =1; i < n ; i ++) { tmp [ L ][ i ] = 2 * ( tr -> t [ i +1] - tr - > t [i -1]) - tmp [ H ][ i -1]* tmp [ MIU ][ i -1]; tmp [ MIU ][ i ] = tmp [ H ][ i ] / tmp [ L ][ i ]; tmp [ Z ][ i ] = ( tmp [ ALFA ][ i] - tmp [ H ][ i -1]* tmp [ Z ][ i -1]) / tmp [L ][ i ]; } // paso 6... // tmp [ L ][ n ] = 1; // tmp [ Z ][ n ] = 0; tr - > cx [ n ] = 0; // paso 7... for ( i =n -1; i >=0; i - -) { tr - > cx [ i ] = tmp [ Z ][ i ] - tmp [ MIU ][ i ] * tr - > cx [ i +1]; tr - > bx [ i ] = ( tr - > ax [ i +1] - tr - > ax [ i ]) / tmp [ H ][ i ] - tmp [ H ][ i ] * (tr - > cx [ i +1] + 2* tr - > cx [ i ]) / 3; tr - > dx [ i ] = ( tr - > cx [ i +1] - tr - > cx [ i ]) / (3* tmp [ H ][ i ]) ; } /* *** en y **************** */ // paso 3... for ( i =1; i < n ; i ++) tmp [ ALFA ][ i ] = 3 * ( ( tr - > ay [ i +1] - tr - > ay [ i ]) / tmp [ H ][ i ] ( tr - > ay [ i ] - tr - > ay [i -1]) / tmp [ H ][ i -1] ); // paso 4...
188
Spline
tmp [ L ][0] = 1; tmp [ MIU ][0] = 0; tmp [ Z ][0] = 0; // paso 5... for ( i =1; i < n ; i ++) { tmp [ L ][ i ] = 2 * ( tr - > t [ i +1] - tr - > t [i -1]) - tmp [ H ][ i -1]* tmp [ MIU ][ i -1]; tmp [ MIU ][ i ] = tmp [ H ][ i ] / tmp [ L ][ i ]; tmp [ Z ][ i ] = ( tmp [ ALFA ][ i ] - tmp [ H ][ i -1]* tmp [ Z ][ i -1]) / tmp [ L ][ i ]; } // paso 6... // tmp [ L ][ n ] = 1; // tmp [ Z ][ n ] = 0; tr - > cy [ n ] = 0; // paso 7... for ( i =n -1; i >=0; i - -) { tr - > cy [ i ] = tmp [ Z ][ i ] - tmp [ MIU ][ i ] * tr - > cy [ i +1]; tr - > by [ i ] = ( tr - > ay [ i +1] - tr - > ay [ i ]) / tmp [ H ][ i ] - tmp [ H ][ i ] * ( tr - > cy [ i +1] + 2* tr -> cy [ i ]) / 3; tr - > dy [ i ] = ( tr - > cy [ i +1] - tr - > cy [ i ]) / (3* tmp [ H ][ i ]) ; } /* ******************* */ // Liberar memoria temporal free ( tmp [0]) ; } return TIC_COD_EXITO ;
void TIC_liberar_memoriaP ( trazadoresP * tr ) { free ( tr - > t ) ; } void TIC_imprimirP ( trazadoresP * tr , FILE * f ) { int i ; fprintf (f , " \ nTrazadores Paramtricos :\ n " ) ; fprintf (f , " i t_i \ tx_i \ ty_i \ tax_i \ tbx_i \ tcx_i \ tdx_i \ tay_i \ tby_i \ tcy_i \ tdy_i \ n " ) ; for ( i =0; i <= tr - > num_trazadores ; i ++) fprintf (f , " %d - >\ t %3.2 g \ t %3.2 g \ t %3.2 g \ n \ t %3.2 g \ t %3.2 g \ t %3.2 g \ t %3.2 g \ n \ t %3.2 g \t %3.2 g \ t %3.2 g \ t %3.2 g \ n" , i , tr - > t [ i ] , tr - >x [ i ] , tr - > y [ i ] , tr - > ax [ i ] , tr - > bx [ i ] , tr - > cx [ i] , tr - > dx [ i ] , tr - > ay [ i ] , tr - > by [ i ] , tr - > cy [ i] , tr - > dy [ i ]) ; }
Este es el programa principal, que utiliza el cdigo de los dos archivos anteriores. No
189
9 Curvas Paramtricas
es nada espectacular, ni aplicado. Es simplemente para comprender la naturaleza geomtrica de las curvas spline.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
// / c09 / trazadores / main . c # include < SDL / SDL .h > # include " tic . h " # include < SDL / SDL_gfxPrimitives .h > # include < stdio .h > # define ANCHO 640 # define ALTO 480 # define ANCHO_RECTANGULO 10 # define # define # define # define # define XMIN 0.0 XMAX 15.0 YMIN 0.0 YMAX 3.0 TPASO 0.005
# define TAM 21 static inline int x_real ( double x_virtual ) { return ( int ) ( ANCHO * x_virtual / XMAX ) ; } static inline int y_real ( double y_virtual ) { return ( int ) ( ALTO * (1.0 - y_virtual / YMAX ) ) ; } static inline double x_virtual ( int x_real ) { return ( XMAX * x_real ) / ANCHO ; } static inline double y_virtual ( int y_real ) { return YMAX * (1.0 - y_real / ( double ) ALTO ) ; } // Variables globales double x [ TAM ] = {0.9 , 1.3 , 1.9 , 2.1 , 2.6 , 3.0 , 3.9 , 4.4 , 4.7 , 5.0 , 6.0 , 7.0 , 8.0 , 9.2 , 10.5 , 11.3 , 11.6 , 12.0 , 12.6 , 13.0 , 13.3}; double y [ TAM ] = {1.3 , 1.5 , 1.85 , 2.1 , 2.6 , 2.7 , 2.4 , 2.15 , 2.05 , 2.1 , 2.25 , 2.3 , 2.25 , 1.95 , 1.4 , 0.9 , 0.7 , 0.6 , 0.5 , 0.4 , 0.25}; trazadoresP * tr = NULL ; int i , j , k , l ; double tvar , xvar , yvar ; Uint32 color_fondo , color1 , color2 ; Uint32 gfxColor ( Uint8 r , Uint8 g , Uint8 b ) { return r << 24 | g << 16 |
190
Spline
b << 8 255; }
| // este valor es la opacidad del color // y debe ser mxima para que sea slido
void dibujo ( SDL_Surface * pantalla ) { i = j = k = l =0; SDL_Rect rect ; // Borra la pantalla SDL_FillRect ( pantalla , NULL , color_fondo ); // dibujo de los rectngulos rect . w = rect . h = ANCHO_RECTANGULO ; for ( i =0; i <= tr - > num_trazadores ; i ++) { rect . x = x_real ( tr - > x [ i ]) - ANCHO_RECTANGULO /2; rect . y = y_real ( tr - > y [ i ]) - ANCHO_RECTANGULO /2; rect . h = rect . w = ANCHO_RECTANGULO ; rectangleColor ( pantalla , rect .x , rect .y , rect . x + rect .w , rect . y + rect .h , color2 ) ; } // dibujo de la curva tvar = 0.0; TIC_calcularP ( tr , tvar , & xvar , & yvar ) ; i = x_real ( xvar ) ; j = y_real ( yvar ) ; for ( tvar = TPASO ; tvar <= 1; tvar += TPASO ) { if (! TIC_calcularP ( tr , tvar , & xvar , & yvar ) ) { aalineColor ( pantalla , i , j , k = x_real ( xvar ) , l = y_real ( yvar ) , color1 ) ; i=k; j=l; } } TIC_calcularP ( tr , 1.0 , & xvar , & yvar ) ; aalineColor ( pantalla , i , j , k = x_real ( xvar ) , l = y_real ( yvar ) , color1 ) ; // vuelca el buffer en la pantalla : SDL_Flip ( pantalla ) ;
int main ( int argc , char * argv []) { SDL_Surface * pantalla = NULL ; SDL_Event evento ; int profundidad_color ; const SDL_VideoInfo * info ; int i ; int corriendo = 1; int seleccionado = 0;
191
9 Curvas Paramtricas
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
if ( SDL_Init ( SDL_INIT_VIDEO ) < 0 ) { fprintf ( stderr , " No se puede iniciar SDL : %s \ n" , SDL_GetError () ) ; exit (1) ; } atexit ( SDL_Quit ) ; // Este if es importante para poder usar SDL_gfx info = SDL_GetVideoInfo () ; if ( info - > vfmt - > BitsPerPixel > 8 ) { profundidad_color = info - > vfmt - > BitsPerPixel ; // printf (" %d \ n " , profundidad_color ) ; } else { profundidad_color = 16; } pantalla = SDL_SetVideoMode ( ANCHO , ALTO , profundidad_color , SDL_SWSURFACE ) ; if ( pantalla == NULL ) { fprintf ( stderr , " No se puede establecer el modo de video %dx %d : % s\n", ANCHO , ALTO , SDL_GetError () ) ; exit (1) ; } SDL_WM_SetCaption ( " Trazadores Interpolantes Cbicos Paramtricos ! " , NULL ) ; color_fondo = SDL_MapRGB ( pantalla - > format ,0 ,0 ,0) ; color1 = gfxColor (255 ,255 ,255) ; color2 = gfxColor (128 ,128 ,128) ; if (( tr = TIC_crear_trazadoresP (x , y , TAM ) ) == NULL ) { fprintf ( stderr , " Error al crear los trazadores \ n " ) ; exit (1) ; } TIC_imprimirP ( tr , stderr ) ; dibujo ( pantalla ) ; while ( corriendo ) { while ( SDL_PollEvent (& evento ) ) { switch ( evento . type ) { case SDL_MOUSEMOTION :{ if ( seleccionado ) { // actualizar el punto tr - > x [ i ]= x_virtual ( evento . button .x ) ; tr - > y [ i ]= y_virtual ( evento . button .y ) ; TIC_actualizar_trazadorP ( tr ) ; dibujo ( pantalla ) ; }
192
Spline
break ; case SDL_MOUSEBUTTONDOWN :{ for ( i =0; i <= tr - > num_trazadores ; i ++) { if ( evento . button . button == SDL_BUTTON_LEFT && // si hace clic sobre un nodo ... (( evento . button . x > x_real ( tr - > x [ i ]) ANCHO_RECTANGULO /2) && ( evento . button . y > y_real ( tr - > y [ i ]) ANCHO_RECTANGULO /2) && ( evento . button . x < x_real ( tr - > x [ i ]) + ANCHO_RECTANGULO /2) && ( evento . button . y < y_real ( tr - > y [ i ]) + ANCHO_RECTANGULO /2) ) ){ // se selecciona y el ndice queda en 'i ' seleccionado = 1; // printf (" seleccionado \n ") ; break ; } } } break ; case SDL_MOUSEBUTTONUP : seleccionado = 0; break ; case SDL_QUIT : corriendo = 0; break ;
Listing 9.4: Archivo Makele para la aplicacin
SDL_Quit () ; return 0;
1 2 3 4 5 6 7 8 9 10
# / c09 / trazadores / Makefile # Esto es un comentario LDFLAGS = $ ( shell sdl - config -- cflags ) LDLIBS = $ ( shell sdl - config -- libs ) - lSDL_gfx # # RM = / bin / rm -f # Esto indica que los siguientes identificadores , # no son archivos , sino comandos de make : . PHONY : limpiar
193
9 Curvas Paramtricas
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
. PHONY : limpiartodo . PHONY : all # se puede por ejemplo ejecutar en la consola # lo siguiente : # ' make limpiartodo ' , etc . # Nombre del programa ejecutable : PROG = trazadores # Un '*.o ' por cada '*.c ' OBJ = tic . o main . o # Cuando se ejecuta ' make ' , se ejecuta esto : all : $ ( PROG ) limpiar $ ( PROG ) : $ ( OBJ ) gcc -o $ ( PROG ) $ ( OBJ ) $ ( LDLIBS ) $ ( LDFLAGS ) # Borra todos los archivos intermedios y de copia de seguridad limpiar : $ ( RM ) *~ $ ( OBJ ) # Borra todos los archivos intermedios , de copia de seguridad # y el programa ejecutable , si es que existe limpiartodo : make limpiar $ ( RM ) $ ( PROG )
194
Spline
el cual debe ocurrir, a partir del inicio de la animacin. Luego se calcula una curva Spline paramtrica para cada punto de control, como se explica en la seccin 9.3.6 en la pgina 176, donde el parmetro adicional es el tiempo. Luego, pueden crearse tantos cuadros como se desee, aplicando un algoritmo basado en el algoritmo en la pgina 178. Un ejemplo aplicado, sencillo, puede estudiarse en la aplicacin se describe a continuacin:
editorplinesFpy
que
clic derecho Sobre un punto de control, agrega un puntos ms. ruedas del ratn+CTRL Aumento/disminucin del detalle/nura de la animacin. ruedas del ratn+SHIFT Aumento/disminucin del tamao de los cuadros de control. ruedas del ratn Implementa acercamiento/alejamiento. ruedas del ratn + tecla x/y Expande/Contrae el marco virtual horizontalmente/verticalmente.
clic central Sobre un punto de control, lo borra. CTRL+tecla Izquierda Se desplaza hacia el cuadro anterior. CTRL+tecla Derecha Se desplaza hacia el cuadro siguiente. Crea un nuevo cuadro a
partir del anterior cuando est en el ltimo.
CTRL+tecla L Alterna dibujo de las lneas rectas que unen los puntos de control. CTRL+tecla C Alterna dibujo de los rectngulos que marcan los puntos de control. CTRL+tecla G Guarda el archivo (interfaz de lnea de comandos). CTRL+tecla Arriba/Abajo Aumento/disminucin del grueso de los cuadros de control. tecla Espacio Inicia la animacin desde el primer cuadro hasta el ltimo y regresa al
cuadro en el que se encontraba antes de la animacin.
1 2 3 4 5 6 7 8 9
# !/ usr / bin / env python # coding : utf -8 " " " c09 / bezier_py / editorBezier . py - Programa para hacer animaciones con curvas Splines . Los datos se guardan en archivos xml . """ import pygame
195
9 Curvas Paramtricas
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
import sys , os , random import splines PAUSA = 100 ANCHO_POR_DEFECTO = 800 ALTO_POR_DEFECTO = 500 SUPERIOR_DER = ( 10.0 , 10.0) INFERIOR_IZQ = ( -10.0 , -10.0) # valores por defecto __dibujarLineas = True __dibujarCuadros = True __dibujarCurvas = False BOTON_IZQ = 1 BOTON_CEN = 2 BOTON_DER = 3 RUEDA_ARRIBA = 4 RUEDA_ABAJO = 5 __colorLineas = (128 ,128 ,128) __colorCurva = (0 ,0 ,255) __colorCuadro = (0 ,0 ,0) __colorCuadroPrimero = (255 ,0 ,0) __colorFondo = (255 ,255 ,255) __gruesoLineas = 1 def __inicializarMatriz ( matriz ) : matriz . ponerEscala ( \ ( ANCHO_POR_DEFECTO , ALTO_POR_DEFECTO ) , \ ( -10.0 , -10.0) ,(10.0 ,10.0) ) def dibujar ( matriz , ixS , pantalla , actualizarCurvas = True ) : ' ' ' Se recibe una matriz de puntos , y se dibuja la fila ixS - sima en la pantalla especificada . ''' # Borrar la pantalla if actualizarCurvas : pantalla . fill ( __colorFondo ) # Dibujar la curva : if __dibujarCurvas and actualizarCurvas : pass # Dibujar los cuadrados : if __dibujarCuadros :
196
Spline
pygame . draw . rect ( pantalla , __colorCuadroPrimero , pygame . Rect ( matriz . e . vrx ( matriz . puntos [0][ ixS ][0]) - matriz . anchoRectangulo /2 , matriz . e . vry ( matriz . puntos [0][ ixS ][1]) - matriz . anchoRectangulo /2 , matriz . anchoRectangulo , matriz . anchoRectangulo ) , matriz . gruesoRectangulo ) for i in range (1 , len ( matriz . puntos ) ) : pygame . draw . rect ( pantalla , __colorCuadro , pygame . Rect ( matriz . e . vrx ( matriz . puntos [ i ][ ixS ][0]) - matriz . anchoRectangulo /2 , matriz . e . vry ( matriz . puntos [ i ][ ixS ][1]) - matriz . anchoRectangulo /2 , matriz . anchoRectangulo , matriz . anchoRectangulo ) , matriz . gruesoRectangulo ) # Dibujar las lneas : if __dibujarLineas and actualizarCurvas : for i in range ( len ( matriz . puntos ) -1) : pygame . draw . line ( pantalla , __colorLineas , matriz . e . vr ( matriz . puntos [ i ][ ixS ]) , matriz . e . vr ( matriz . puntos [ i +1][ ixS ]) , __gruesoLineas ) # Redibujar el buffer pygame . display . flip () def guardarMatriz ( matriz ): if len ( sys . argv ) >1: matriz . guardarArchivo ( sys . argv [1]) print ( " Archivo guardado " ) else : archivo = raw_input ( " Escriba el nombre del archivo a guardar ( si lo omite , su trabajo no se guardar ) : " ) if archivo : matriz . guardarArchivo ( archivo ) print ( " Archivo guardado " ) else : print ( " Archivo no guardado " ) def iniciarAnimacion ( matriz , pantalla ) : pygame . display . set_caption ( " Animacin en curso - Por favor espere ") listaPuntosAnimacion = [] for i in range ( matriz . numNodos () ) : listaPuntosAnimacion . append ( matriz . trazador ( i )) for j in range ( len ( listaPuntosAnimacion [0]) ) : # Borrar la pantalla
197
9 Curvas Paramtricas
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
pantalla . fill ( __colorFondo ) # Dibujar los cuadrados : for i in range ( matriz . numNodos () ) : pygame . draw . rect ( pantalla , __colorCuadro , pygame . Rect ( matriz . e . vrx ( listaPuntosAnimacion [ i ][ j ][0]) - matriz . anchoRectangulo /2 , matriz . e . vry ( listaPuntosAnimacion [ i ][ j ][1]) - matriz . anchoRectangulo /2 , matriz . anchoRectangulo , matriz . anchoRectangulo ) , matriz . gruesoRectangulo ) # Dibujar las lneas : for i in range ( matriz . numNodos () -1) : pygame . draw . line ( pantalla , __colorLineas , matriz . e . vr ( listaPuntosAnimacion [ i ][ j ]) , matriz . e . vr ( listaPuntosAnimacion [ i +1][ j ]) , __gruesoLineas ) # Redibujar el buffer pygame . display . flip () # Hacer pausa pygame . time . delay ( PAUSA ) if __name__ == " __main__ " : pygame . init () matriz = splines . MatrizSpline () if len ( sys . argv ) >1: if os . path . exists ( sys . argv [1]) : matriz . cargarArchivo ( sys . argv [1]) else : __inicializarMatriz ( matriz ) else : __inicializarMatriz ( matriz ) pantalla = pygame . display . set_mode ( matriz . dimensiones () , pygame . RESIZABLE ) pygame . display . set_caption ( \ " Editor de Secuencia de puntos - " + \ ( len ( sys . argv ) >1 and sys . argv [1] or " SinNombre . bezier " ) ) ratonPresionado = 0 # cdigo del botn del ratn que ha sido presionado punto = None ixPunto = -1 ixSecuencia = 0
198
Spline
dibujar ( matriz , ixSecuencia , pantalla ) while True : for evento in pygame . event . get () : modificadores = pygame . key . get_mods () # Movimiento del ratn if evento . type == pygame . MOUSEMOTION : # Arrastrando punto if ratonPresionado == BOTON_IZQ and punto : punto [0] = matriz . e . rvx ( evento . pos [0]) punto [1] = matriz . e . rvy ( evento . pos [1]) dibujar ( matriz , ixSecuencia , pantalla ) # Desplazar el espacio virtual elif ratonPresionado == BOTON_IZQ and not punto : dxv = matriz . e . magnitudrvx ( evento . rel [0]) dyv = matriz . e . magnitudrvy ( evento . rel [1]) matriz . e . minxv -= dxv matriz . e . maxxv -= dxv matriz . e . minyv -= dyv matriz . e . maxyv -= dyv dibujar ( matriz , ixSecuencia , pantalla ) # Clic izquierdo - Identificar el punto para moverlo elif evento . type == pygame . MOUSEBUTTONDOWN \ and evento . button == BOTON_IZQ : ratonPresionado = evento . button xclic , yclic = evento . pos for i in range ( len ( matriz . puntos ) ) : px , py = matriz . e . vr ( matriz . puntos [ i ][ ixSecuencia ]) if xclic > px - matriz . anchoRectangulo /2 and \ yclic > py - matriz . anchoRectangulo /2 and \ xclic < px + matriz . anchoRectangulo /2 and \ yclic < py + matriz . anchoRectangulo /2 : punto = matriz . puntos [ i ][ ixSecuencia ] ixPunto = i dibujar ( matriz , ixSecuencia , pantalla ) break # Clic derecho - Agregar nuevo segmento elif evento . type == pygame . MOUSEBUTTONDOWN \ and not ratonPresionado \ and evento . button == BOTON_DER : xclic , yclic = evento . pos for i in range ( len ( matriz . puntos ) ) : px , py = matriz . e . vr ( matriz . puntos [ i ][ ixSecuencia ]) if xclic > px - matriz . anchoRectangulo /2 and \ yclic > py - matriz . anchoRectangulo /2 and \ xclic < px + matriz . anchoRectangulo /2 and \
199
9 Curvas Paramtricas
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
yclic < py + matriz . anchoRectangulo /2 : l = [] if i == len ( matriz . puntos ) -1: # est al final for k in range ( len ( matriz . puntos [0]) ) : l . append ([ matriz . puntos [ i ][ k ][0] , # ponerlo sobre el anterior matriz . puntos [ i ][ k ][1] ]) else : for k in range ( len ( matriz . puntos [0]) ) : l . append ([ ( matriz . puntos [ i ][ k ][0] + matriz . puntos [ i +1][ k ][0]) /2 , ( matriz . puntos [ i ][ k ][1] + matriz . puntos [ i +1][ k ][1]) /2 ]) matriz . puntos . insert ( i +1 , l ) dibujar ( matriz , ixSecuencia , pantalla ) break # Ruedas del ratn elif evento . type == pygame . MOUSEBUTTONDOWN \ and not ratonPresionado \ and ( evento . button == RUEDA_ABAJO or evento . button == RUEDA_ARRIBA ) : # Aumentar o disminur el nmero de pasos if modificadores & pygame . KMOD_CTRL : if evento . button == RUEDA_ABAJO : np = matriz . numPasos () -1 if np >1: matriz . numPasos ( -1) else : matriz . numPasos (1) print ( " Nmero de pasos para la animacin : " + str ( matriz . numPasos () ) ) # Aumentar o disminur el tamao de los cuadros elif modificadores & pygame . KMOD_SHIFT : if evento . button == RUEDA_ABAJO : matriz . anchoRectangulo = matriz . anchoRectangulo -5 if matriz . anchoRectangulo <5: matriz . anchoRectangulo = 5 else : matriz . anchoRectangulo = matriz . anchoRectangulo +5 print ( " Ancho de los cuadrados : " + str ( matriz . anchoRectangulo ) ) # Hacer acercamiento o alejamiento
200
Spline
else : porcentajeDistanciaX = 0.05*( matriz . e . maxxv - matriz . e . minxv ) porcentajeDistanciaY = 0.05*( matriz . e . maxyv - matriz . e . minyv ) teclas = pygame . key . get_pressed () if evento . button == RUEDA_ABAJO : # acercamiento if not teclas [ pygame . K_x ] and not teclas [ pygame . K_y ]: matriz . e . minxv += porcentajeDistanciaX matriz . e . maxxv -= porcentajeDistanciaX matriz . e . minyv += porcentajeDistanciaY matriz . e . maxyv -= porcentajeDistanciaY elif teclas [ pygame . K_x ]: matriz . e . minxv += porcentajeDistanciaX matriz . e . maxxv -= porcentajeDistanciaX elif teclas [ pygame . K_y ]: matriz . e . minyv += porcentajeDistanciaY matriz . e . maxyv -= porcentajeDistanciaY else : # alejamiento if not teclas [ pygame . K_x ] and not teclas [ pygame . K_y ]: matriz . e . minxv -= porcentajeDistanciaX matriz . e . maxxv += porcentajeDistanciaX matriz . e . minyv -= porcentajeDistanciaY matriz . e . maxyv += porcentajeDistanciaY elif teclas [ pygame . K_x ]: matriz . e . minxv -= porcentajeDistanciaX matriz . e . maxxv += porcentajeDistanciaX elif teclas [ pygame . K_y ]: matriz . e . minyv -= porcentajeDistanciaY matriz . e . maxyv += porcentajeDistanciaY dibujar ( matriz , ixSecuencia , pantalla ) # Eliminar un punto elif evento . type == pygame . MOUSEBUTTONDOWN \ and not ratonPresionado \ and evento . button == BOTON_CEN : xclic , yclic = evento . pos for i in range ( len ( matriz . puntos ) ) : px , py = matriz . e . vr ( matriz . puntos [ i ][ ixSecuencia ]) if xclic > px - matriz . anchoRectangulo /2 and \ yclic > py - matriz . anchoRectangulo /2 and \ xclic < px + matriz . anchoRectangulo /2 and \ yclic < py + matriz . anchoRectangulo /2 : matriz . puntos . pop ( i ) dibujar ( matriz , ixSecuencia , pantalla ) break # Detener el movimiento
201
9 Curvas Paramtricas
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
elif evento . type == pygame . MOUSEBUTTONUP : if evento . button == ratonPresionado == BOTON_IZQ : ratonPresionado = 0 punto = None ixPunto = -1 # Cuando se redimensiona la ventana elif evento . type == pygame . VIDEORESIZE : matriz . cambiarDimensiones ( evento . size ) pantalla = pygame . display . set_mode ( matriz . dimensiones () , pygame . RESIZABLE ) dibujar ( matriz , ixSecuencia , pantalla ) # Otras acciones elif evento . type == pygame . KEYDOWN and \ ( modificadores & pygame . KMOD_CTRL ) : if evento . key == pygame . K_LEFT : if ixSecuencia > 0: ixSecuencia -= 1 print ( " Cuadro {0}/{1} " . format ( ixSecuencia +1 , len ( matriz . puntos [0]) ) ) dibujar ( matriz , ixSecuencia , pantalla ) elif evento . key == pygame . K_RIGHT : if ixSecuencia == len ( matriz . puntos [0]) -1: for l in matriz . puntos : l . append ([ l [ -1][0] , l [ -1][1] ]) ixSecuencia += 1 print ( " Cuadro {0}/{1} " . format ( ixSecuencia +1 , len ( matriz . puntos [0]) ) ) dibujar ( matriz , ixSecuencia , pantalla ) elif evento . key == pygame . K_l : __dibujarLineas = not __dibujarLineas if __dibujarLineas : print ( " Dibujo de lneas encendido " ) else : print ( " Dibujo de lneas apagado " ) dibujar ( matriz , ixSecuencia , pantalla ) elif evento . key == pygame . K_c : __dibujarCuadros = not __dibujarCuadros if __dibujarCuadros : print ( " Dibujo de cuadros encendido " ) else : print ( " Dibujo de cuadros apagado ") dibujar ( matriz , ixSecuencia , pantalla ) elif evento . key == pygame . K_g : guardarMatriz ( matriz ) # Aumentar o disminur el grueso de los cuadros
202
Spline
elif evento . key == pygame . K_DOWN : matriz . gruesoRectangulo = matriz . gruesoRectangulo -1 if matriz . gruesoRectangulo <1: matriz . gruesoRectangulo = 1 print ( " Grueso de los cuadrados : " + str ( matriz . gruesoRectangulo ) ) dibujar ( matriz , ixSecuencia , pantalla ) elif evento . key == pygame . K_UP : matriz . gruesoRectangulo = matriz . gruesoRectangulo +1 print ( " Grueso de los cuadrados : " + str ( matriz . gruesoRectangulo ) ) dibujar ( matriz , ixSecuencia , pantalla ) elif evento . type == pygame . KEYDOWN and \ evento . key == pygame . K_SPACE : titulo = pygame . display . get_caption () [0] iniciarAnimacion ( matriz , pantalla ) pygame . display . set_caption ( titulo ) dibujar ( matriz , ixSecuencia , pantalla ) elif evento . type == pygame . KEYDOWN and \ evento . key == pygame . K_F1 : print ( " Ayuda ... " ) # Pendiente ... elif evento . type == pygame . QUIT : pygame . display . quit () # Cierra la ventana y apaga el subsistema de video guardarMatriz ( matriz ) sys . exit ()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
# coding : utf -8 " " " c09 / trazadores_py / splines . py - Modulo para procesar un arreglo Spline . Los datos se guardan en archivos xml . """ import xml . dom . minidom __H __ALFA __L __MIU __Z A = 0 = = = = = 0 1 2 3 4
203
9 Curvas Paramtricas
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
B = 1 C = 2 D = 3 def coeficientesTrazador ( independiente , dependiente ) : ' ' ' Devuelve la matriz de coeficientes de los 'n ' trazadores interpolantes cbicos paramtricos para la secuencia de nodos descritos en la matriz { independiente x dependiente }. __La salida tiene la forma : [[ a1 , a2 , ... , an ] , [ b1 , b2 , ... , bn ] , [ c1 , c2 , ... , cn ] , [ d1 , d2 , ... , dn ]] donde 'n +1 ' es el nmero de nodos que representan los dos vectores , dependiente e independiente . ''' if len ( independiente ) != len ( dependiente ) : raise Exception ( " __Los arreglos deben ser de igual longitud " ) numNodos = len ( independiente ) numTrazadores = len ( independiente ) - 1 def listaCeros () : l = [] for i in range ( numNodos ) : l . append (0.0) return l coef = [ 0 , listaCeros () , listaCeros () , listaCeros () ] tmp = [ listaCeros () , listaCeros () , listaCeros () , listaCeros () , listaCeros () ] # Clculo de los trazadores n = numTrazadores coef [ A ] = dependiente [:] # Copiar todo el arreglo # paso 2... for i in range ( n ) : tmp [ __H ][ i ] = independiente [ i +1] - independiente [ i ] # paso 3... for i in range (1 , n ): tmp [ __ALFA ][ i ] = 3 * ( ( coef [ A ][ i +1] - coef [ A ][ i ]) / tmp [ __H ][ i ] - \ ( coef [ A ][ i ] - coef [ A ][ i -1]) / tmp [ __H ][ i -1] ) # paso 4... tmp [ __L ][0] = 1 tmp [ __MIU ][0] = 0
204
Spline
tmp [ __Z ][0] = 0 # paso 5... for i in range (1 , n) : tmp [ __L ][ i ] = 2 * ( independiente [ i +1] - independiente [i -1]) - tmp [ __H ][ i -1]* tmp [ __MIU ][i -1] tmp [ __MIU ][ i ] = tmp [ __H ][ i ] / tmp [ __L ][ i ] tmp [ __Z ][ i ] = ( tmp [ __ALFA ][ i ] - tmp [ __H ][ i -1]* tmp [ __Z ][ i -1]) / tmp [ __L ][ i ] # paso 6... coef [ C ][ n ] = 0.0 # paso 7... for i in range (n -1 , -1 , -1) : coef [ C ][ i ] = tmp [ __Z ][ i ] - tmp [ __MIU ][ i ] * coef [ C ][ i +1] coef [ B ][ i ] = ( coef [ A ][ i +1] - coef [ A ][ i ]) / tmp [ __H ][ i] \ - tmp [ __H ][ i ] * ( coef [ C ][ i +1] + 2* coef [ C ][ i ]) / 3 coef [ D ][ i ] = ( coef [ C ][ i +1] - coef [ C ][ i ]) / (3* tmp [ __H ][ i ]) return coef class MatrizSpline () : ' ' ' Esta clase mantiene una matriz de puntos para modelar una matriz Spline . Cada lista principal de la matriz , es una secuencia independiente . ''' def __init__ ( self , escala = None ) : if escala : self . puntos = [ [ [ escala . minxv , escala . minyv ] ] , [ [( escala . minxv + escala . maxxv ) /2.0 , ( escala . minyv + escala . maxyv ) /2.0] ] , [ [ escala . maxxv , escala . maxyv ] ] ] else : self . puntos = [ [ [0 ,0] ] ,[ [0 ,0] ] ,[ [0 ,0] ] ] self . anchoRectangulo = 20 self . gruesoRectangulo = 1 self . e = escala self . __numPasos = 25 self . __t = None def numNodos ( self ) :
205
9 Curvas Paramtricas
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
return len ( self . puntos ) def numCuadros ( self ) : return len ( self . puntos [0]) def numPasos ( self , valorIncremento =0) : ' ' ' Permite aumentar o disminur el nmero de lneas que conformarn una secuencia de segmentos de Bzier ''' if valorIncremento : self . __numPasos += valorIncremento if self . __numPasos == 0: self . __numPasos -= valorIncremento return self . __numPasos def dimensiones ( self ) : " " " Devuelve las dimensiones de la pantalla donde debe mostrarse la serie de puntos " " " return self . e . maxxr , self . e . maxyr def cambiarDimensiones ( self , tam ) : " cambiar las dimensiones de la escala real " self . e . maxxr , self . e . maxyr = tam def ponerEscala ( self , ( ANCHO , ALTO ) , ( minx , miny ) , ( maxx , maxy ) ) : " " " Configura la escala de visualizacin de esta matriz """ self . e = Escala ( \ ( ANCHO , ALTO ) , \ ( minx , miny ) , ( maxx , maxy ) ) def trazador ( self , i ) : ' ' ' Devuelve una lista de pares ordenados del tipo [x , y ]. La lista tiene self . __numPasos +1 pares que interpolan a la i - sima secuencia de esta matriz de Splines . ''' numTrazadores = self . numCuadros () -1 l = [] self . __t = [ float ( x) /( self . numCuadros () -1) for x in range ( self . numCuadros () )] coefX = coeficientesTrazador ( self . __t , \ [ p [0] for p in self . puntos [ i ]] ) coefY = coeficientesTrazador ( self . __t , \ [ p [1] for p in self . puntos [ i ]] ) ix_t = 1 for t in [ float ( x ) / self . __numPasos for x in range ( self . __numPasos +1) ]:
206
Spline
while t > self . __t [ ix_t ]: ix_t += 1 ix_t -= 1 tmpT = t - self . __t [ ix_t ] l . append ( [ \ coefX [ A ][ ix_t ] + tmpT * ( coefX [ B ][ ix_t ] + tmpT * ( coefX [ C ][ ix_t ] + tmpT * coefX [ D ][ ix_t ] ) ) , \ coefY [ A ][ ix_t ] + tmpT * ( coefY [ B ][ ix_t ] + tmpT * ( coefY [ C ][ ix_t ] + tmpT * coefY [ D ][ ix_t ] ) ) ] ) # for h in l : # print ( h ) return l def cargarArchivo ( self , nombreArchivo ) : documentoxml = xml . dom . minidom . parse ( nombreArchivo ) curvasxml = documentoxml . getElementsByTagName ( " curvas " ) [0] rectangulosxml = documentoxml . getElementsByTagName ( " rectangulos " ) [0] escalaxml = documentoxml . getElementsByTagName ( " escala " ) [0] secuenciasxml = documentoxml . getElementsByTagName ( " secuencias " ) [0] self . __numPasos = int ( curvasxml . getAttribute ( " pasos " ) ) self . __t = None self . anchoRectangulo = int ( rectangulosxml . getAttribute ( " ancho ") ) self . gruesoRectangulo = int ( rectangulosxml . getAttribute ( " grueso " ) ) self . e = Escala ( \ ( int ( escalaxml . getAttribute ( " maxxr ") ) , int ( escalaxml . getAttribute ( " maxyr " ) ) ) , \ ( float ( escalaxml . getAttribute ( " minxv " ) ) , float ( escalaxml . getAttribute ( " minyv " ) ) ) , \ ( float ( escalaxml . getAttribute ( " maxxv " ) ) , float ( escalaxml . getAttribute ( " maxyv " ) ) ) ) self . puntos = [] for nodos_xml in secuenciasxml . getElementsByTagName ( " nodos " ) : l = [] for nodoxml in nodos_xml . getElementsByTagName ( " nodo " ) : l . append ([ float ( nodoxml . getAttribute ( " x " ) ) , \ float ( nodoxml . getAttribute ( " y " ) ) ]) self . puntos . append ( l ) def guardarArchivo ( self , nombreArchivo ) : implementacionxml = xml . dom . minidom . getDOMImplementation ()
207
9 Curvas Paramtricas
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248
documentoxml = implementacionxml . createDocument ( None , " splines " , None ) raiz = documentoxml . documentElement curvas = documentoxml . createElement ( " curvas " ) curvas . setAttribute ( " pasos " , str ( self . __numPasos ) ) raiz . appendChild ( curvas ) rectangulos = documentoxml . createElement ( " rectangulos " ) rectangulos . setAttribute ( " ancho " , str ( self . anchoRectangulo ) ) rectangulos . setAttribute ( " grueso " , str ( self . gruesoRectangulo ) ) raiz . appendChild ( rectangulos ) escala = documentoxml . createElement ( " escala " ) escala . setAttribute ( " maxxr " , str ( self . e . maxxr ) ) escala . setAttribute ( " maxyr " , str ( self . e . maxyr ) ) escala . setAttribute ( " minxv " , escala . setAttribute ( " minyv " , escala . setAttribute ( " maxxv " , escala . setAttribute ( " maxyv " , raiz . appendChild ( escala ) str ( self . e . minxv ) ) str ( self . e . minyv ) ) str ( self . e . maxxv ) ) str ( self . e . maxyv ) )
secuenciasxml = documentoxml . createElement ( " secuencias " ) for lista in self . puntos : nodos_xml = documentoxml . createElement ( " nodos " ) for p in lista : nodoxml = documentoxml . createElement ( " nodo ") nodoxml . setAttribute ( " x " , str ( p [0]) ) nodoxml . setAttribute ( " y " , str ( p [1]) ) nodos_xml . appendChild ( nodoxml ) secuenciasxml . appendChild ( nodos_xml ) raiz . appendChild ( secuenciasxml ) f = open ( nombreArchivo , 'w ') f . write ( documentoxml . toprettyxml () ) f . close ()
class Escala () : " " " Clase de Escalas bidimensionales de ventana completa . Esta clase permite modelar un espacio bidimensional cuadrado con dos escalas diferentes : una Real y una Virtual . La escala Real est medida en pixeles en el dominio de los enteros . Su origen se encuentra en la esquina superior izquierda y en la esquina inferior derecha se encuentra la coordenada ( ANCHO -1 , ALTO -1) .
208
Spline
Se asume que ANCHO y ALTO son positivos . La escala Virtual tiene unidades arbitrarias en el dominio de los reales . En la esquina inferior izquierda se encuentra la coordenada [ minx , miny ] y en la esquina superior derecha [ maxx , maxy ]. Se asume que minx < maxx y que miny < maxy . """ def __init__ ( self , ( ANCHO , ALTO ) , ( minx , miny ) , ( maxx , maxy ) ) : # self . minxr = 0 # Siempre es cero # self . minyr = 0 # Siempre es cero self . maxxr = ANCHO self . maxyr = ALTO self . minxv = minx self . minyv = miny self . maxxv = maxx self . maxyv = maxy def rvx ( self , xr ) : ' ' ' Convierte un valor x Real en su correspondiente Virtual ' ' ' return ( self . maxxv - self . minxv ) * float ( xr ) / self . maxxr + self . minxv def rvy ( self , yr ) : ' ' ' Convierte un valor y Real en su correspondiente Virtual ' ' ' return ( self . minyv - self . maxyv ) * float ( yr ) / self . maxyr + self . maxyv def vrx ( self , xv ) : ' ' ' Convierte un return int ( ( xv ) def vry ( self , yv ) : ' ' ' Convierte un return int ( ( yv ) valor x Virtual en su correspondiente Real ' ' ' - self . minxv ) * self . maxxr /( self . maxxv - self . minxv ) valor y Virtual en su correspondiente Real ' ' ' - self . maxyv ) * self . maxyr /( self . minyv - self . maxyv )
def rv ( self , ( xr , yr )) : ' ' ' Convierte un par (x ,y ) Real en su correspondiente Virtual ' ' ' return self . rvx ( xr ) , self . rvy ( yr ) def vr ( self , ( xv , yv )) : ' ' ' Convierte un par (x ,y ) Virtual en su correspondiente Real ' ' ' return self . vrx ( xv ) , self . vry ( yv ) def magnitudvrx ( self , deltaxv ) : ' ' ' Convierte una ' distancia ' deltaxv en la escala Virtual a su correspondiente ' distancia ' en la escala Real ' ' ' return deltaxv * self . maxxr / ( self . maxxv - self . minxv ) def magnitudvry ( self , deltayv ) :
209
9 Curvas Paramtricas
295 296 297 298 299 300 301 302 303 304 305 306
' ' ' Convierte una ' distancia ' deltayv en la escala Virtual a su correspondiente ' distancia ' en la escala Real ' ' ' return deltayv * self . maxyr / ( self . minyv - self . maxyv ) def magnitudrvx ( self , deltaxr ) : ' ' ' Convierte una ' distancia ' deltaxr en la escala Real a su correspondiente ' distancia ' en la escala Virtual ' ' ' return deltaxr * ( self . maxxv - self . minxv ) / self . maxxr def magnitudrvy ( self , deltayr ) : ' ' ' Convierte una ' distancia ' deltayr en la escala Real a su correspondiente ' distancia ' en la escala Virtual ' ' ' return deltayr * ( self . minyv - self . maxyv ) / self . maxyr
9.4.
Curvas de
Bzier
3
Las curvas de Bzier fueron nombradas as en honor de Pierre Bzier , quien las utiliz para el diseo de carroceras de automviles en 1962 en la empresa Rnault.
P0 , P1 , P2 , P3
(9.1)
t t t
1, 1 1
y adems, (si es que aplica), etc...
t)3 y
+ 3t(1
t)2 y1
3t2 (1
t)y2 +
t3 y3
, 0
210
9.4 Curvas de
Bzier
Polinomios de Bernstein, y
b(i, n, t) =
donde
n (1 t)ni ti i t 1. i
(9.2)
n i
n! i!(ni)! ,
n N, 0
es el ndice del
Bzier de grado n
Bn (t) =
i=0
b(i, n, t)Pi
(9.3)
De modo que podemos enunciar las siguientes curvas de Bzier de primero, segundo, tercer, cuarto y quinto grado (los coecientes numricos responden al Tringulo de Pascal):
1 t 1 t 1
211
9 Curvas Paramtricas
212
9.4 Curvas de
Bzier
213
9 Curvas Paramtricas
B4 (t) = (1 t)4 P0 + 4t(1 t)3 P1 + 6t2 (1 t)2 P2 + 4t3 (1 t)P3 + t4 P4 , 0 t 1
B5 (t) = (1 t)5 P0 + 5t(1 t)4 P1 + 10t2 (1 t)3 P2 + 10t3 (1 t)2 P3 + 5t4 (1 t)P4 + t5 P5 , 0 t 1
Usar curvas de Bzier de grado muy alto no reportan signicativas ventajas respecto de los de tercer grado desde el punto de vista de la eciencia de los algoritmos necesarios para manipularlas, pero presentan un comportamiento muy interesante, como puede verse en la seccin Constructing Bzier Curves de [Wikipedia-Bzier Curve]. Debido a que en general slo se usan curvas de Bzier de tercer grado, el nombre de estas se ha generalizado, de tal forma que cuando uno se reere sin ms, a Curvas de
G0
Q,
es
Q:
P3 = Q0
Para que haya continuidad que el ltimo vector
G1 , es necesario que haya continuidad G0 , y es necesario tangente de P sea linealmente dependiente del primero de Q P2 P3 = k Q0 Q1 , k > 0
C1
G1 ,
sea
Q:
P2 P3 = Q0 Q1
Para poder, entonces, tener una secuencia de segmentos de Bzier (de tercer grado) que generen una curva suave, es necesario garantizar que se cumplan estas tres caractersticas descritas.
214
9.4 Curvas de
Bzier
ezierI y ezierP.
El primero permite manipular un solo segmento de Bzier, para familiarizarse con su naturaleza. El segundo permite manipular tres segmentos de Bzier encadenados.
bezier1
Los siguientes archivos presentan una implementacin de segmentos independientes de Bzier:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
// / c09 / bezier / bezier1 . h # include < stdio .h > # define # define # define # define # define # define # define # define n" /* BEZIER_COD_EXITO 0 BEZIER_MSG_EXITO " xito \ n " BEZIER_COD_ERROR_PARAMETROS -1 BEZIER_MSG_ERROR_PARAMETROS " Error de parmetro ( s ) \ n " BEZIER_COD_ERROR_DOMINIO -2 BEZIER_MSG_ERROR_DOMINIO " Error de dominio \ n" BEZIER_COD_ERROR_MEMORIA -3 BEZIER_MSG_ERROR_MEMORIA " Error de solicitud de memoria dinmica \
Contiene los cuatro puntos de control para los segmentos de Bezier . El clculo de lo puntos internos del segmento es el siguiente : B ( t ) = Bx ( t ) i + By ( t ) j Bz ( t ) k , Bx ( t ) = (1 - t ) ^3 * x [0] + 3 t (1 - t ) ^2 * x [1] + 3t ^2(1 - t ) * x [2] + t ^3 * x [3] By ( t ) = (1 - t ) ^3 * y [0] + 3 t (1 - t ) ^2 * y [1] + 3t ^2(1 - t ) * y [2] + t ^3 * y [3] Bz ( t ) = (1 - t ) ^3 * z [0] + 3 t (1 - t ) ^2 * z [1] + 3t ^2(1 - t ) * z [2] + t ^3 * z [3] para 0 <= t <= 1
*/ typedef struct { // coordenadas de los cuatro nodos double x [4]; double y [4]; double z [4]; } segmentoBezier ;
215
9 Curvas Paramtricas
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
/*
*/ int BEZIER_calcular ( segmentoBezier * sb , double t , double *x , double *y , double * z ) ; /* Dado un valor del parmetro 't ', devuelve los valores del punto calculado del segmento de Bzier . Esta versin asume que los puntos son coplanares en xy .
Dado un valor del parmetro 't ', devuelve los valores del punto calculado del segmento de Bzier . Asume que el segmento es tridimensional .
*/
int BEZIER_calcular2d ( segmentoBezier * sb , double t , double *x , double * y ) ; /* Imprime en la salida especificada los valores almacenados en la estructura .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// / c09 / bezier / bezier1 . c # include " bezier1 . h " int BEZIER_calcular ( segmentoBezier * sb , double t , double *x , double *y , double * z ) { if ( sb == NULL || x == NULL || y == NULL || z == NULL ) return BEZIER_COD_ERROR_PARAMETROS ; if (t <0.0 || t >1.0) return BEZIER_COD_ERROR_DOMINIO ; double double double double double *x = + *y = + *z = + _1_t1 = 1 - t ; _1_t2 = _1_t1 * _1_t1 ; _1_t3 = _1_t2 * _1_t1 ; t2 = t* t ; t3 = t2 * t ; * sb - > x [0] + 3* t * _1_t2 * sb - > x [1] + 3* t2 * _1_t1 * sb - > x [2] sb - > x [3]; * sb - > y [0] + 3* t * _1_t2 * sb - > y [1] + 3* t2 * _1_t1 * sb - > y [2] sb - > y [3]; * sb - > z [0] + 3* t * _1_t2 * sb - > z [1] + 3* t2 * _1_t1 * sb - > z [2] sb - > z [3];
216
9.4 Curvas de
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
Bzier
return BEZIER_COD_EXITO ;
int BEZIER_calcular2d ( segmentoBezier * sb , double t , double *x , double * y ) { if ( sb == NULL || x == NULL || y == NULL ) return BEZIER_COD_ERROR_PARAMETROS ; if (t <0.0 || t >1.0) return BEZIER_COD_ERROR_DOMINIO ; double double double double double _1_t1 = 1 - t ; _1_t2 = _1_t1 * _1_t1 ; _1_t3 = _1_t2 * _1_t1 ; t2 = t * t ; t3 = t2 * t ;
* x = _1_t3 * sb - > x [0] + 3* t * _1_t2 * sb -> x [1] + 3* t2 * _1_t1 * sb - > x [2] + t3 * sb - > x [3]; * y = _1_t3 * sb - > y [0] + 3* t * _1_t2 * sb -> y [1] + 3* t2 * _1_t1 * sb - > y [2] + t3 * sb - > y [3]; return BEZIER_COD_EXITO ;
void BEZIER_imprimir ( segmentoBezier * sb , FILE * f ) { int i ; fprintf (f , " \ nPuntos de control de Bzier :\ n " ) ; fprintf (f , " i \ tx_i \ ty_i \ tz_i \ n " ) ; for ( i =0; i <4; i ++) fprintf (f , " %d \ t %3.2 g \ t %3.2 g\ t %3.2 g \ n " , i +1 , sb - > x[ i ] , sb - > y [ i ] , sb -> z [ i ]) ; }
El siguiente cdigo es una sencilla aplicacin que usa el cdigo de los archivos anteriores para permitirle al usuario, como se mencion antes, manipular un solo segmento de Bzier:
1 2 3 4 5 6 7 8 9 10 11 12
// / c09 / bezier / main1 .c # include < SDL / SDL .h > # include " bezier1 . h " # include < SDL / SDL_gfxPrimitives .h > # define ANCHO 640 # define ALTO 480 # define ANCHO_RECTANGULO 15 # define XMIN 0.0 # define XMAX 15.0
217
9 Curvas Paramtricas
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
# define YMIN 0.0 # define YMAX 15.0 # define TPASO 0.01 # define TAM 4 static inline int x_real ( double x_virtual ) { return ( int ) ( ANCHO * x_virtual / XMAX ) ; } static inline int y_real ( double y_virtual ) { return ( int ) ( ALTO * (1.0 - y_virtual / YMAX ) ) ; } static inline double x_virtual ( int x_real ) { return ( XMAX * x_real ) / ANCHO ; } static inline double y_virtual ( int y_real ) { return YMAX * (1.0 - y_real / ( double ) ALTO ) ; } Uint32 gfxColor ( Uint8 r , Uint8 g , Uint8 b ) { return r << 24 | g << 16 | b << 8 | 255; // este valor es la opacidad del color // y debe ser mxima para que sea slido } // Variables globales double x [ TAM ] = {1.3 , 3.0 , 6.0 , 13.0}; double y [ TAM ] = {1.3 , 5.0 , 7.4 , 14.2}; segmentoBezier sb ; int i , j , k , l ; double tvar , xvar , yvar ; Uint32 color_fondo , color1 , color2 , color3 ; void dibujo ( SDL_Surface * pantalla ) { i = j = k = l =0; SDL_Rect rect ; // Borra la pantalla SDL_FillRect ( pantalla , NULL , color_fondo ) ; for ( i =1; i <4; i ++) { lineColor ( pantalla , x_real ( sb . x [i -1]) , y_real ( sb . y [i -1]) , x_real ( sb . x [ i ]) , y_real ( sb . y [ i ]) , color3 ) ; } tvar = 0.0;
218
9.4 Curvas de
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
Bzier
BEZIER_calcular2d (& sb , tvar , & xvar , & yvar ) ; i = x_real ( xvar ) ; j = y_real ( yvar ) ; for ( tvar = TPASO ; tvar <= 1.0; tvar += TPASO ) { if (! BEZIER_calcular2d (& sb , tvar , & xvar , & yvar ) ) { lineColor ( pantalla , i , j , k= x_real ( xvar ) , l = y_real ( yvar ) , color1 ) ; i=k; j=l; } } BEZIER_calcular2d (& sb , 1.0 , & xvar , & yvar ) ; lineColor ( pantalla , i , j , k= x_real ( xvar ) , l = y_real ( yvar ) , color1 ) ; // dibujar los rectngulos de control rect . w = rect . h = ANCHO_RECTANGULO ; for ( i =0; i <4; i ++) { rect . x = x_real ( sb . x [ i ]) - ANCHO_RECTANGULO /2; rect . y = y_real ( sb . y [ i ]) - ANCHO_RECTANGULO /2; rectangleColor ( pantalla , rect .x , rect .y , rect . x + rect .w , rect . y + rect .h , color2 ) ; } // vuelca el buffer en la pantalla : SDL_Flip ( pantalla ) ;
int main ( int argc , char * argv []) { SDL_Surface * pantalla = NULL ; SDL_Event evento ; int profundidad_color ; const SDL_VideoInfo * info ; Uint32 color ; int i ; int corriendo = 1; int seleccionado = 0; if ( SDL_Init ( SDL_INIT_VIDEO ) < 0 ) { fprintf ( stderr , " No se puede iniciar SDL : %s \ n " , SDL_GetError () ) ; exit (1) ; } atexit ( SDL_Quit ) ; // Este if es importante para poder usar SDL_gfx info = SDL_GetVideoInfo () ; if ( info - > vfmt - > BitsPerPixel > 8 ) { profundidad_color = info - > vfmt - > BitsPerPixel ; // printf (" %d \ n " , profundidad_color ) ; } else { profundidad_color = 16;
219
9 Curvas Paramtricas
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
} pantalla = SDL_SetVideoMode ( ANCHO , ALTO , profundidad_color , SDL_SWSURFACE ) ; if ( pantalla == NULL ) { fprintf ( stderr , " No se puede establecer el modo de video %dx %d : % s\n", ANCHO , ALTO , SDL_GetError () ) ; exit (1) ; } SDL_WM_SetCaption ( " Segmento de Bezier " , NULL ) ; color_fondo = SDL_MapRGB ( pantalla - > format ,0 ,0 ,0) ; color1 = gfxColor (255 ,255 ,255) ; color2 = gfxColor (255 ,0 ,0) ; color3 = gfxColor (0 ,0 ,255) ; for ( i =0; i <4; i ++) { sb . x [ i ] = x [ i ]; sb . y [ i ] = y [ i ]; } // BEZIER_imprimir (& sb , stdout ) ; dibujo ( pantalla ) ; while ( corriendo ) { while ( SDL_PollEvent (& evento ) ) { switch ( evento . type ) { case SDL_MOUSEMOTION :{ if ( seleccionado ) { // actualizar el punto sb . x [ i ]= x_virtual ( evento . button . x ) ; sb . y [ i ]= y_virtual ( evento . button . y ) ; dibujo ( pantalla ) ; } } break ; case SDL_MOUSEBUTTONDOWN :{ for ( i =0; i <4; i ++) { if ( evento . button . button == SDL_BUTTON_LEFT && // si hace clic sobre un nodo ... (( evento . button . x > x_real ( sb . x [ i ]) ANCHO_RECTANGULO /2) && ( evento . button . y > y_real ( sb .y [ i ]) ANCHO_RECTANGULO /2) && ( evento . button . x < x_real ( sb .x [ i ]) + ANCHO_RECTANGULO /2) && ( evento . button . y < y_real ( sb .y [ i ]) + ANCHO_RECTANGULO /2) )
220
9.4 Curvas de
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
Bzier
){
// se selecciona y el ndice queda en 'i ' seleccionado = 1; // printf (" seleccionado \n ") ; break ;
break ; case SDL_MOUSEBUTTONUP : seleccionado = 0; break ; case SDL_KEYDOWN : if ( evento . key . keysym . sym == SDLK_SPACE ) dibujo ( pantalla ) ; break ; case SDL_QUIT : corriendo = 0; break ;
SDL_Quit () ; return 0;
bezier2
Este programa, presenta una implementacin de una secuencia de tres segmentos de Bzier, con la funcionalidad de forzar continuidad
C1
0 slo continuidad G .
Veamos su uso:
mite transformar la secuencia, manteniendo por defecto continuidad uniones de los segmentos.
C1
en las
letra l Alterna el dibujo de las lneas guas entre los nodos de control. letra c Alterna el dibujo de los cuadros que marca la posicin de los nodos de control. letra i Imprime en consola las coordenadas de los nodos de control. letra x Alterna entre forzar continuidad C 1 entre los segmentos (por defecto) o mantener
slo continuidad
G0 .
C1
la posicin de algunos
221
9 Curvas Paramtricas
Los siguientes archivos presentan una implementacin de una secuencia de segmentos de Bzier de longitud arbitraria:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
// / c09 / bezier / bezier2 . h # include " bezier1 . h " /* ********** Unin de segmentos de Bzier ************************ */ /* Nodo para una lista lineal doble con nodo de control para modelar una secuencia de segmentos de Bzier con al menos un segmento . Pero el nodo de control no es del mismo tipo de los nodos .
*/ typedef struct nodoBezier { // coordenadas de los tres nodos , // el ltimo coincide con el primero // del siguiente segmento . double x [3]; double y [3]; double z [3]; struct nodoBezier * ant ; struct nodoBezier * sig ; } nodoBezier ; /*
*/ typedef struct { // la coordenada del primer punto // del primer segmento : double x , y , z ;
nodoBezier primero ; int numNodos ; // representa el nmero de estructuras , no de puntos int continuidad ; // indica si se garantizar la continuidad de 3 er orden } listaBezier ; /* Imprime en la salida especificada los valores almacenados en la estructura .
222
9.4 Curvas de
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
Bzier
' numNodos ' representa el nmero de segmentos de Bzier deseados */ int BEZIER_crearLista ( listaBezier * lista , int numNodos , double valorInicial ) ; /* La longitud de los arreglos de entrada 'x ' y 'y ' se asume a '3* lista - > numNodos +1 '. Tambin se asume que los puntos son coplanares en xy . Se asume que la ' lista ' ya ha sido creada .
*/ int BEZIER_modificarLista2d ( listaBezier * lista , double *x , double * y ) ; /* La longitud de los arreglos de entrada 'x ', 'y ' y 'z ' se asume a '3* lista - > numNodos +1 '. Se asume que la ' lista ' ya ha sido creada .
*/ int BEZIER_modificarLista ( listaBezier * lista , double *x , double *y , double * z ) ; /* Libera la memoria utilizada para almacenar los ' nodoBezier '
*/ void BEZIER_liberarLista ( listaBezier * lista ) ; /* Dado un valor del parmetro 't ', devuelve los valores del punto calculado del segmento de Bzier . Asume que el segmento es tridimensional .
*/ int BEZIER_calculardeLista ( listaBezier * lista , double t , double *x , double *y , double * z ) ; /* Dado un valor del parmetro 't ', devuelve los valores del punto calculado del segmento de Bzier . Esta versin asume que los puntos son coplanares en xy .
*/ int BEZIER_calculardeLista2d ( listaBezier * lista , double t , double *x , double * y ) ; /* Dado un ndice de punto , actualizar el nodo de Bzier correspondiente */ int BEZIER_actualizarNodo ( listaBezier * lista , int ix , double x , double y , double z ) ;
223
9 Curvas Paramtricas
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
/*
*/ int BEZIER_recuperarNodo2d ( listaBezier * lista , int ix , double *x , double *y); /* Dado un ndice , recuperar el punto en las direcciones de 'x ', 'y ' y 'z '.
Dado un ndice , recuperar el punto en las direcciones de 'x ' y 'y '.
*/ int BEZIER_recuperarNodo ( listaBezier * lista , int ix , double *x , double *y , double * z ) ; /* Copia los valores de todos los puntos a los arreglos 'x ', 'y ' y 'z '. Estos arreglos deben contener suficiente espacio .
*/ int BEZIER_recuperarNodos2d ( listaBezier * lista , double *x , double * y ) ; /* Garantiza que la curva sea continua , forzando algunos puntos . Tendrn prioridad los puntos previos .
Copia los valores de todos los puntos a los arreglos 'x ' y 'y '. Estos arreglos deben contener suficiente espacio . Asume que los puntos son coplanares en xy .
void BEZIER_activarContinuidad ( listaBezier * lista ) ; void BEZIER_desactivarContinuidad ( listaBezier * lista ) ; /* * Agrega un segmento nuevo de Bzier * al final de la Lista especificada * */ int BEZIER_agregarNodoNuevoaLista ( listaBezier * lista , int valorInicial ) ; /* * Agrega un segmento especificado de Bzier * al final de la Lista especificada
224
9.4 Curvas de
138 139
Bzier
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
// / c09 / bezier / bezier2 . c # include " bezier2 . h " # include < stdlib .h > void BEZIER_imprimir2 ( listaBezier * lista , FILE * f ) { nodoBezier * nb = NULL ; int i , j ; if ( lista == NULL ) return ; fprintf (f , " \ nPuntos de control de Bzier :\ n " ) ; fprintf (f , " i \ tx_i \ ty_i \ tz_i \ n " ) ; // imprimir primer nodo fprintf (f , " %d \ t %3.2 g \ t %3.2 g\ t %3.2 g \ n " , i =1 , lista - >x , lista - >y , lista - > z ) ; // ' primero ' no es un puntero for ( nb = &( lista - > primero ) ; nb != NULL ; nb =nb - > sig ) { fprintf (f , " -- -\ n " ) ; for ( j =0; j <3; j ++) fprintf (f , " %d \ t %3.2 g \ t %3.2 g \ t %3.2 g \ n " , ++ i , nb - > x [ j ] , nb - > y [ j ] , nb - > z [j ]) ; }
int BEZIER_crearLista ( listaBezier * lista , int numNodos , double valorInicial ) { if ( lista == NULL || numNodos < 1) return BEZIER_COD_ERROR_PARAMETROS ; nodoBezier * temp = NULL , * temp2 = NULL ; lista - > numNodos = numNodos ; lista - > x = lista - > y = lista - > z = lista - > primero . x [0] = lista - > primero . x [1] = lista - > primero .x [2] = lista - > primero . y [0] = lista - > primero . y [1] = lista - > primero .y [2] = valorInicial ; lista - > continuidad = 1; lista - > primero . sig = lista - > primero . ant = NULL ; for ( numNodos - -; numNodos >0; numNodos - -) { if (!( temp = ( nodoBezier *) malloc ( sizeof ( nodoBezier ) ) ) ) return BEZIER_COD_ERROR_MEMORIA ; if ( temp2 == NULL ) { // es el primero creado dinmicamente lista - > primero . sig = temp2 = temp ;
225
9 Curvas Paramtricas
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
} return BEZIER_COD_EXITO ;
temp - > sig = NULL ; temp - > ant = &( lista - > primero ) ; } else { // ya hay un nodo anterior en ' temp2 ' temp - > sig = NULL ; temp - > ant = temp2 ; temp2 - > sig = temp ; temp2 = temp ; } temp - > x [0]= temp - > x [1]= temp - > x [2]= temp - > y [0]= temp - > y [1]= temp - > y [2]= valorInicial ; temp = NULL ;
int BEZIER_modificarLista2d ( listaBezier * lista , double *x , double * y ) { if ( lista == NULL || x == NULL || y == NULL ) return BEZIER_COD_ERROR_PARAMETROS ; nodoBezier * temp = NULL ; int i =0 , j ; lista - > x = x [ i ]; lista - > y = y [ i ++]; lista - > z = 0.0; for ( temp = &( lista - > primero ) ; temp != NULL ; temp = temp - > sig ) for ( j =0; j <3; j ++) { temp - > x [ j ] = x [ i ]; temp - > y [ j ] = y [ i ++]; temp - > z [ j ] = 0.0; } if ( lista - > continuidad ) BEZIER_acomodarContinua ( lista ) ; return BEZIER_COD_EXITO ;
int BEZIER_modificarLista ( listaBezier * lista , double *x , double *y , double * z ) { if ( lista == NULL || x == NULL || y == NULL || z == NULL ) return BEZIER_COD_ERROR_PARAMETROS ; nodoBezier * temp = NULL ; int i =0 , j ; lista - > x = x [ i ]; lista - > y = y [ i ]; lista - > z = z [ i ++]; for ( temp = &( lista - > primero ) ; temp != NULL ; temp = temp - > sig ) for ( j =0; j <3; j ++) { temp - > x [ j ] = x [ i ];
226
9.4 Curvas de
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
Bzier
void BEZIER_liberarLista ( listaBezier * lista ) { if ( lista == NULL ) return ; nodoBezier * nb = lista - > primero . sig , * temp = NULL ; while ( nb ) { temp = nb - > sig ; free ( nb ) ; nb = temp ; } } int BEZIER_calculardeLista2d ( listaBezier * lista , double t , double *x , double * y ) { if ( lista == NULL || x == NULL || y == NULL ) return BEZIER_COD_ERROR_PARAMETROS ; if (t <0.0 || t >1.0) return BEZIER_COD_ERROR_DOMINIO ; nodoBezier * temp = NULL , * nb = NULL ; int i =0 , j ; // calcular el ndice del nodo ' nodoBezier ' // con el que debe calcularse este 't ' int ixBezier = ( int ) ( t * lista - > numNodos ) ; if ( ixBezier == lista - > numNodos ) ixBezier - -; t = lista - > numNodos * t - ixBezier ; double double double double double double t1 = t ; t2 = t * t ; t3 = t2 * t ; _1_t1 = 1 - t ; _1_t2 = _1_t1 * _1_t1 ; _1_t3 = _1_t1 * _1_t2 ;
nb = temp = &( lista - > primero ) ; if ( ixBezier ==0) { // es el primer ' nodoBezier ' * x = _1_t3 * lista - > x + 3* t1 * _1_t2 * nb - > x [0] + 3* t2 * _1_t1 * nb - > x [1] + t3 * nb - > x [2]; * y = _1_t3 * lista - > y + 3* t1 * _1_t2 * nb - > y [0] + 3* t2 * _1_t1 * nb - > y [1] + t3 * nb - > y [2]; } else { // no es el primer ' nodoBezier ' con el que se calcular 't '
227
9 Curvas Paramtricas
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
} }
i ++; for ( temp = temp - > sig ; temp != NULL ; temp = temp - > sig ) if ( i == ixBezier ) { nb = temp ; break ; } else i ++; // aqu se asume que siempre lo encuentra ... * x = _1_t3 * nb - > ant - > x [2] + 3* t1 * _1_t2 * nb - > x [0] + 3* t2 * _1_t1 * nb - > x [1] + t3 * nb - > x [2]; * y = _1_t3 * nb - > ant - > y [2] + 3* t1 * _1_t2 * nb - > y [0] + 3* t2 * _1_t1 * nb - > y [1] + t3 * nb - > y [2];
return BEZIER_COD_EXITO ;
int BEZIER_calculardeLista ( listaBezier * lista , double t , double *x , double *y , double * z ) { if ( lista == NULL || x == NULL || y == NULL || z == NULL ) return BEZIER_COD_ERROR_PARAMETROS ; if (t <0.0 || t >1.0) return BEZIER_COD_ERROR_DOMINIO ; nodoBezier * temp = NULL , * nb = NULL ; int i =0 , j ; // calcular el ndice del nodo ' nodoBezier ' // con el que debe calcularse este 't ' int ixBezier = ( int ) ( t * lista - > numNodos ) ; if ( ixBezier == lista - > numNodos ) ixBezier - -; t = lista - > numNodos * t - ixBezier ; double double double double double double t1 = t; t2 = t* t ; t3 = t2 * t ; _1_t1 = 1 - t ; _1_t2 = _1_t1 * _1_t1 ; _1_t3 = _1_t1 * _1_t2 ;
nb = temp = &( lista - > primero ) ; if ( ixBezier ==0) { // es el primer ' nodoBezier ' * x = _1_t3 * lista - > x + 3* t1 * _1_t2 * nb - > x [0] x [1] + t3 * nb - > x [2]; * y = _1_t3 * lista - > y + 3* t1 * _1_t2 * nb - > y [0] y [1] + t3 * nb - > y [2]; * z = _1_t3 * lista - > z + 3* t1 * _1_t2 * nb - > z [0] z [1] + t3 * nb - > z [2]; } else { // no es el primer ' nodoBezier ' con el que
228
9.4 Curvas de
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
Bzier
} }
i ++; for ( temp = temp - > sig ; temp != NULL ; temp = temp - > sig ) if ( i == ixBezier ) { nb = temp ; break ; } else i ++; // aqu se asume que siempre lo encuentra ... * x = _1_t3 * nb - > ant - > x [2] + 3* t1 * _1_t2 * nb - > x [0] + 3* t2 * _1_t1 * nb - > x [1] + t3 * nb - > x [2]; * y = _1_t3 * nb - > ant - > y [2] + 3* t1 * _1_t2 * nb - > y [0] + 3* t2 * _1_t1 * nb - > y [1] + t3 * nb - > y [2]; * z = _1_t3 * nb - > ant - > z [2] + 3* t1 * _1_t2 * nb - > z [0] + 3* t2 * _1_t1 * nb - > z [1] + t3 * nb - > z [2];
return BEZIER_COD_EXITO ;
int BEZIER_actualizarNodo2d ( listaBezier * lista , int ix , double x , double y){ return BEZIER_actualizarNodo ( lista , ix , x , y , 0.0) ; } int BEZIER_actualizarNodo ( listaBezier * lista , int ix , double x , double y , double z ) { if ( lista == NULL || ix < 0) return BEZIER_COD_ERROR_PARAMETROS ; nodoBezier * temp = NULL ; int i =0 , j ; if ( ix ==0) { lista - > x = x ; lista - > y = y ; lista - > z = z ; return BEZIER_COD_EXITO ; } i ++; for ( temp = &( lista - > primero ) ; temp != NULL ; temp = temp - > sig ) for ( j =0; j <3; j ++) if ( i == ix ) { // Mecanismo de coordinacin con los puntos adyacentes : if ( lista - > continuidad ) switch ( j ) { case 0:{ /* Desplazar el penltimo punto del nodo anterior , si hay nodo anterior . Esto es para garantizar la colinealidad
229
9 Curvas Paramtricas
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
} else i ++;
} break ; case 1:{ /* Desplazar el primer punto del nodo siguiente , si hay nodo siguiente . Esto es para garantizar la colinealidad en las uniones de los segmentos de Bzier . */ if ( temp - > sig ) { temp - > sig - > x [0] = temp - > x [2] - (x - temp - > x [2]) ; temp - > sig - > y [0] = temp - > y [2] - (y - temp - > y [2]) ; temp - > sig - > z [0] = temp - > z [2] - (z - temp - > z [2]) ; } } break ; case 2:{ /* Desplazar el punto anterior y el siguiente , si es que hay un siguiente . */ if ( temp - > sig ) { temp - > x [1] += x - temp - > x [2]; temp - > y [1] += y - temp - > y [2]; temp - > z [1] += z - temp - > z [2]; temp - > sig - > x [0] += x - temp - > x [2]; temp - > sig - > y [0] += y - temp - > y [2]; temp - > sig - > z [0] += z - temp - > z [2]; } } break ;
en las uniones de los */ if ( temp - > ant ) { temp - > ant - > x [1] = - > x [2]) ; temp - > ant - > y [1] = - > y [2]) ; temp - > ant - > z [1] = - > z [2]) ; }
segmentos de Bzier . temp - > ant - > x [2] - (x - temp - > ant temp - > ant - > y [2] - (y - temp - > ant temp - > ant - > z [2] - (z - temp - > ant
230
9.4 Curvas de
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
Bzier
return BEZIER_COD_ERROR_DOMINIO ;
int BEZIER_recuperarNodo2d ( listaBezier * lista , int ix , double *x , double *y){ if ( lista == NULL || ix < 0 || x == NULL || y == NULL ) return BEZIER_COD_ERROR_PARAMETROS ; nodoBezier * temp = NULL ; int i =0 , j ; if ( ix ==0) { * x = lista - > x ; * y = lista - > y ; return BEZIER_COD_EXITO ; } i ++; for ( temp = &( lista - > primero ) ; temp != NULL ; temp = temp - > sig ) for ( j =0; j <3; j ++) if ( i == ix ) { * x = temp - > x [ j ]; * y = temp - > y [ j ]; return BEZIER_COD_EXITO ; } else i ++; return BEZIER_COD_ERROR_DOMINIO ;
int BEZIER_recuperarNodo ( listaBezier * lista , int ix , double *x , double *y , double * z ) { if ( lista == NULL || ix < 0 || x == NULL || y == NULL || z == NULL ) return BEZIER_COD_ERROR_PARAMETROS ; nodoBezier * temp = NULL ; int i =0 , j ; if ( ix ==0) { * x = lista - > x ; * y = lista - > y ; * z = lista - > z ; return BEZIER_COD_EXITO ; } i ++; for ( temp = &( lista - > primero ) ; temp != NULL ; temp = temp - > sig ) for ( j =0; j <3; j ++) if ( i == ix ) { * x = temp - > x [ j ]; * y = temp - > y [ j ]; * z = temp - > z [ j ]; return BEZIER_COD_EXITO ;
231
9 Curvas Paramtricas
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
int BEZIER_recuperarNodos ( listaBezier * lista , double *x , double *y , double * z ) { if ( lista == NULL || x == NULL || y == NULL || z == NULL ) return BEZIER_COD_ERROR_PARAMETROS ; nodoBezier * temp = NULL ; int i =0 , j ; x [ i ] = lista - > x ; y [ i ] = lista - > y ; z [ i ++]= lista - > z ; for ( temp = &( lista - > primero ) ; temp != NULL ; temp = temp - > sig ) for ( j =0; j <3; j ++) { x [ i ] = temp - > x [ j ]; y [ i ] = temp - > y [ j ]; z [ i ++]= temp - > z [ j ]; } return BEZIER_COD_EXITO ;
int BEZIER_recuperarNodos2d ( listaBezier * lista , double *x , double * y ) { if ( lista == NULL || x == NULL || y == NULL ) return BEZIER_COD_ERROR_PARAMETROS ; nodoBezier * temp = NULL ; int i =0 , j ; x [ i ] = lista - > x ; y [ i ++]= lista - > y ; for ( temp = &( lista - > primero ) ; temp != NULL ; temp = temp - > sig ) for ( j =0; j <3; j ++) { x [ i ] = temp - > x [ j ]; y [ i ++]= temp - > y [ j ]; } return BEZIER_COD_EXITO ;
int BEZIER_acomodarContinua ( listaBezier * lista ) { if ( lista == NULL ) return BEZIER_COD_ERROR_PARAMETROS ; nodoBezier * temp = NULL ; for ( temp = (&( lista - > primero ) ) -> sig ; temp != NULL ; temp = temp - > sig ){ /*
232
9.4 Curvas de
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421
Bzier
} }
Desplazar el primer punto del nodo actual . Esto es para garantizar la colinealidad en las uniones de los segmentos de Bzier . */ temp - > x [0] = temp - > ant - > x [2] - ( temp - > ant - > x [1] - temp - > ant - >x [2]) ; temp - > y [0] = temp - > ant - > y [2] - ( temp - > ant - > y [1] - temp - > ant - >y [2]) ; temp - > z [0] = temp - > ant - > z [2] - ( temp - > ant - > z [1] - temp - > ant - >z [2]) ;
return BEZIER_COD_EXITO ;
void BEZIER_activarContinuidad ( listaBezier * lista ) { lista - > continuidad = 1; } void BEZIER_desactivarContinuidad ( listaBezier * lista ) { lista - > continuidad = 0; } int BEZIER_agregarNodoNuevoaLista ( listaBezier * lista , int valorInicial ) { if ( lista == NULL ) return BEZIER_COD_ERROR_PARAMETROS ; nodoBezier * temp = NULL , * temp2 = NULL ; if (!( temp = ( nodoBezier *) malloc ( sizeof ( nodoBezier ) ) ) ) return BEZIER_COD_ERROR_MEMORIA ; for ( temp2 =&( lista - > primero ) ; temp2 - > sig ; temp2 = temp2 - > sig ) ; temp - > ant = temp - > sig = NULL ; temp2 - > sig = temp ; temp - > ant = temp2 ; lista - > numNodos ++; temp - > x [0]= temp - > x [1]= temp - > x [2]= temp - > y [0]= temp - > y [1]= temp - > y [2]= valorInicial ; } return BEZIER_COD_EXITO ;
int BEZIER_agregarNodoaLista ( listaBezier * lista , nodoBezier * nb ) { if ( lista == NULL || nb == NULL ) return BEZIER_COD_ERROR_PARAMETROS ; nodoBezier * temp2 = NULL ;
233
9 Curvas Paramtricas
422 423 424 425 426 427 428 429 430 431
for ( temp2 =&( lista - > primero ) ; temp2 - > sig ; temp2 = temp2 - > sig ) ; nb - > ant = nb - > sig = NULL ; temp2 - > sig = nb ; nb - > ant = temp2 ; lista - > numNodos ++; } return BEZIER_COD_EXITO ;
El siguiente archivo permite la manipulacin de una unin de tres segmentos de Bzier, permitiendo alternar la continuidad en sus puntos de unin, entre
C1
G0 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
// / c09 / bezier / main2 . c # include < SDL / SDL .h > # include " bezier2 . h " # include < SDL / SDL_gfxPrimitives .h > # define ANCHO 640 # define ALTO 480 # define ANCHO_RECTANGULO 15 # define # define # define # define # define XMIN 0.0 XMAX 15.0 YMIN 0.0 YMAX 15.0 TPASO 0.01
Bzier
# define TAM 10 // Debe cumplirse la siguiente relacin : // numPuntos = 3* numNodos +1; double x [ TAM ] = {1.3 , 3.0 , 6.0 , 5.0 , 4.0 , 5.0 , 6.0 , 8.0 , 10.0 , 9.0}; double y [ TAM ] = {1.3 , 5.0 , 7.4 , 9.2 , 2.0 , 3.0 , 4.0 , 5.0 , 7.0 , 4.0}; listaBezier lista ; static inline int x_real ( double x_virtual ) { return ( int ) ( ANCHO * x_virtual / XMAX ) ; } static inline int y_real ( double y_virtual ) { return ( int ) ( ALTO * (1.0 - y_virtual / YMAX ) ) ; } static inline double x_virtual ( int x_real ) { return ( XMAX * x_real ) / ANCHO ; }
234
9.4 Curvas de
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
Bzier
static inline double y_virtual ( int y_real ) { return YMAX * (1.0 - y_real / ( double ) ALTO ) ; } Uint32 gfxColor ( Uint8 r , Uint8 g , Uint8 b ) { return r << 24 | g << 16 | b << 8 | 255; // este valor es la opacidad del color // y debe ser mxima para que sea slido } // Variables globales int i , j , k , l ; double tvar , xvar , yvar ; Uint32 color_fondo , color_curva , color_lineas , color_union_nodos , color_union ; int mostrar_lineas , mostrar_cuadrados ; void dibujo ( SDL_Surface * pantalla ) { i = j = k = l =0; SDL_Rect rect ; // Borra la pantalla SDL_FillRect ( pantalla , NULL , color_fondo ); // dibujar los rectngulos de control rect . w = rect . h = ANCHO_RECTANGULO ; if ( mostrar_cuadrados ) for ( i =0; i < TAM ; i ++) { rect . x = x_real ( x [i ]) - ANCHO_RECTANGULO /2; rect . y = y_real ( y [i ]) - ANCHO_RECTANGULO /2; if ( lista . continuidad ) switch ( i %3){ case 0: rectangleColor ( pantalla , rect .x , rect .y , rect . x + rect .w , rect . y + rect .h , color_union_nodos ) ; break ; default : rectangleColor ( pantalla , rect .x , rect .y , rect . x + rect .w , rect . y + rect .h , color_union ) ; break ; } else rectangleColor ( pantalla , rect .x , rect .y , rect . x + rect .w , rect . y + rect .h , color_union_nodos ) ; }
235
9 Curvas Paramtricas
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
// dibujar lneas entre los nodos if ( mostrar_lineas ) for ( i =1; i < TAM ; i ++) { lineColor ( pantalla , x_real ( x [i -1]) , y_real ( y [i -1]) , x_real ( x [ i ]) , y_real ( y [ i ]) , color_lineas ) ; } // calcular los puntos de la curva tvar = 0.0; BEZIER_calculardeLista2d (& lista , tvar , & xvar , & yvar ) ; i = x_real ( xvar ) ; j = y_real ( yvar ) ; for ( tvar = TPASO ; tvar <= 1.0; tvar += TPASO ) { if (! BEZIER_calculardeLista2d (& lista , tvar , & xvar , & yvar ) ) { lineColor ( pantalla , i , j , k = x_real ( xvar ) , l = y_real ( yvar ) , color_curva ) ; i=k; j=l; } } BEZIER_calculardeLista2d (& lista , tvar , & xvar , & yvar ) ; lineColor ( pantalla , i , j , k = x_real ( xvar ) , l = y_real ( yvar ) , color_curva ); // vuelca el buffer en la pantalla : SDL_Flip ( pantalla ) ;
int main ( int argc , char * argv []) { SDL_Surface * pantalla = NULL ; SDL_Event evento ; int profundidad_color ; const SDL_VideoInfo * info ; Uint32 color ; int i; int corriendo = 1; int seleccionado = 0; mostrar_cuadrados = 1; mostrar_lineas = 1; if ( SDL_Init ( SDL_INIT_VIDEO ) < 0 ) { fprintf ( stderr , " No se puede iniciar SDL : %s \n " , SDL_GetError () ) ; exit (1) ; } atexit ( SDL_Quit ) ; // Este if es importante para poder usar SDL_gfx info = SDL_GetVideoInfo () ;
236
9.4 Curvas de
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
Bzier
if ( info - > vfmt - > BitsPerPixel > 8 ) { profundidad_color = info - > vfmt - > BitsPerPixel ; // printf (" %d \ n " , profundidad_color ) ; } else { profundidad_color = 16; } pantalla = SDL_SetVideoMode ( ANCHO , ALTO , profundidad_color , SDL_SWSURFACE ) ; if ( pantalla == NULL ) { fprintf ( stderr , " No se puede establecer el modo de video %dx %d : % s\n", ANCHO , ALTO , SDL_GetError () ) ; exit (1) ; } SDL_WM_SetCaption ( " Segmentos de Bezier " , NULL ) ; color_fondo = SDL_MapRGB ( pantalla - > format ,0 ,0 ,0) ; color_curva = gfxColor (255 ,255 ,255) ; // lnea color_lineas = gfxColor (0 ,0 ,255) ; // unin puntos color_union_nodos = gfxColor (255 ,0 ,0) ; // unin de nodos color_union = gfxColor (255 ,255 ,0) ; // puntos adyacentes BEZIER_crearLista (& lista , ( TAM -1) /3 ,0.0) ; BEZIER_modificarLista2d (& lista , x , y ) ; BEZIER_recuperarNodos2d (& lista , x , y ) ; BEZIER_imprimir2 (& lista , stdout ) ; dibujo ( pantalla ) ; while ( corriendo ) { while ( SDL_PollEvent (& evento ) ) { switch ( evento . type ) { case SDL_MOUSEMOTION :{ if ( seleccionado ) { // actualizar el punto x [ i ]= x_virtual ( evento . button . x ) ; y [ i ]= y_virtual ( evento . button . y ) ; BEZIER_actualizarNodo2d (& lista , i , x [ i ] , y [ i ]) ; BEZIER_recuperarNodos2d (& lista , x , y ) ; dibujo ( pantalla ) ; } } break ; case SDL_MOUSEBUTTONDOWN :{ for ( i =0; i < TAM ; i ++) { if ( evento . button . button == SDL_BUTTON_LEFT && // si hace clic sobre un nodo ... (( evento . button . x > x_real ( x [ i ]) -
237
9 Curvas Paramtricas
ANCHO_RECTANGULO /2) && ( evento . button . y > y_real ( y [ i ]) ANCHO_RECTANGULO /2) && ( evento . button . x < x_real ( x [ i ]) + ANCHO_RECTANGULO /2) && ( evento . button . y < y_real ( y [ i ]) + ANCHO_RECTANGULO /2) ) // se selecciona y el n d i c e queda en 'i ' seleccionado = 1; // printf (" seleccionado \ n ") ; break ;
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
){
break ; case SDL_MOUSEBUTTONUP : seleccionado = 0; break ; case SDL_KEYDOWN : switch ( evento . key . keysym . sym ) { case SDLK_i : { BEZIER_imprimir2 (& lista , stdout ) ; } break ; case SDLK_l : { mostrar_lineas = ! mostrar_lineas ; dibujo ( pantalla ) ; } break ; case SDLK_c : { mostrar_cuadrados = ! mostrar_cuadrados ; dibujo ( pantalla ) ; } break ; case SDLK_r : { dibujo ( pantalla ) ; } break ; case SDLK_x : { lista . continuidad = ! lista . continuidad ; if ( lista . continuidad ) { BEZIER_acomodarContinua (& lista ) ; BEZIER_recuperarNodos2d (& lista , x , y ) ; } dibujo ( pantalla ) ; } break ; }
238
9.4 Curvas de
221 222 223 224 225 226 227 228 229 230 231 232
Bzier
SDL_Quit () ; return 0;
editorBezier.py
Este programa fue implementado con el doble propsito de servir como ejemplo para este captulo y para hacer la parte principal de la cartula de este libro. Sirve para crear una serie de Secuencias de segmentos de Bzier. Las opciones de interaccin son:
ruedas del ratn Implementa acercamiento/alejamiento. ruedas del ratn + tecla x/y Expande/Contrae el marco virtual horizontalmente/verticalmente.
CTRL+tecla L Alterna dibujo de las lneas rectas que unen los puntos de control. CTRL+tecla C Alterna dibujo de los rectngulos que marcan los puntos de control. CTRL+tecla A Agrega una nueva Secuencia de segmentos de Bzier de manera aleatoria.
CTRL+tecla X
C1
C1
la posicin
CTRL+tecla G Guarda el archivo (interfaz de lnea de comandos). clic derecho Sobre un punto de control, agrega tres puntos ms a partir de ah. clic central Sobre un punto de control, borra todo un segmento de una secuencia.
239
9 Curvas Paramtricas
ruedas del ratn+CTRL Aumento/disminucin del grosor de la curva. ruedas del ratn+SHIFT Aumento/disminucin del tamao de los cuadros de control. CTRL+tecla Arriba/Abajo Aumento/disminucin del grueso de los cuadros de control. SHIFT+tecla Arriba/Abajo Aumento/disminucin del detalle/nura de las curvas
9.5.
Las curvas de Bzier no requieren de clculos previos para el clculo de los puntos que la componen, a diferencia de las curvas splines. Adems, el clculo de cada punto no requiere una bsqueda como en el caso de splines. Esto permite que las curvas de Bzier sean ms atractivas para el uso de diseo interactivo que las splines. Aunque hay que considerar que los puntos de control de Bzier, no pertenecen todos a la curva generada, y en consecuencia, su uso es menos natural que sus contrapartes splines. Adems, el usuario tendr que distinguir cules puntos de control s interpolan la curva y cules controlan los vectores tangentes. Para poder lograr esto, habr que enriquecer ms la interfaz, lo que requiere clculos adicionales. Para que un usuario no matemtico modele algo usando splines, slo tendra que aprender a agregar y eliminar puntos de control de la curva, mientras que si usa Bzier, tendr que pasar por un proceso de aprendizaje mayor para entender los efectos de cambiar los puntos de control sobre las curvas o supercies.
9.6.
Ejercicios
2 (t) = 2t i+ 5 t 1 y sea (t) = (4t 2) i t4 + 3t2 3 2 t j para 0 2 j 5 para 1 t 2. Observe que (1) = 2, 2 = (1), de manera que ambas curvas 0 se unen con continuidad C .
a ) Graque b) c)
1. Mencione las diferencias entre las curvas paramtricas Spline y Bzier. 2. Sea
(t) y (t) para 0 t 1 y 1 t 2 respectivamente. 1 Determine si (t) y (t) cumplen con continuidad G en el punto de unin. d d Recuerde que deber evaluar dt (t = 1) y dt (t = 1). 1 Determine si (t) y (t) cumplen con continuidad C en el punto de unin.
1 4 2 3 7 2 2t 9t + 6t 2 16 sin(2t) + 9
se unen en
f (t) =
g (t) = G1 cuando
5 3 2 +1 3t i + 3t t t + 12 i cos t j , con 1 3 3
3 18 3 3
j,
con
tienen continuidad
C1
f (1) = g (1).
240
9.6 Ejercicios
4. Modique el programa presentado en la subseccin 9.3.7 en la pgina 178, para que permita al usuario agregar y eliminar nodos de control. 5. Modique el programa presentado en el apartado bezier2 de la subseccin 9.4.6 en la pgina 221, para que adems de permitir elegir entre mantener continuidad y
C1
G0
G1
(sin
C 1,
obviamente).
241
9 Curvas Paramtricas
242
10 Supercies paramtricas
10.1. Representacin algebrica de Supercies paramtricas
De manera anloga a las curvas paramtricas, las supercies paramtricas se pueden representar de las siguientes maneras:
r(s, t) = x(s, t) i + y (s, t) j + z (s, t)k r(u, v ) = x(u, v ) i + y (u, v ) j + z (u, v )k x = x(s, t) y = y (s, t) , restricciones z = z (s, t)
Veamos algunos ejemplos de supercies paramtricas: Cilindro hueco de altura
y radio
0 2 0vH
En la gura 10.1 en la pgina siguiente se presenta un cilindro con generado con el comando
HD TAY
de Maxima.
R y radio lateral r (ver gura 10.2): x = (r cos + R) cos 0 2 y = r sin , 0 2 z = (r cos + R) sin
243
10 Supercies paramtricas
244
10.2 Ejercicios
R = 5,
generada con el
1 2 3 4 5 6
script de Maxima:
A: 2$ R: 5$ x : ( A * v * sin ( u /2) + R ) * cos ( u ) $ y : ( A * v * sin ( u /2) + R ) * sin ( u ) $ z : A * v * cos ( u /2) + R$ plot3d ([ x ,y , z ] , [u , 0 ,2* % pi ] , [v , -1/2 , 1/2] , [ ' grid , 60 ,20]) ;
r(r, t) es una curva suave en {[a, b] [c, d]} si la derivada direccional Du r (s, t) es continua en todos los puntos de {[a, b] [c, d]} para cualquier direccin u .
10.2.
Ejercicios
1. Escriba la ecuacin paramtrica de un sorbete que est formado por la mitad superior de una bola con centro en el origen y radio 5, y un cono circular con la
245
10 Supercies paramtricas
base en el plano
xy
y la punta en
(0, 0, 15).
246
11 Mallas Poligonales
Una manera alternativa de modelar cuerpos tridimensionales arbitrarios, es aproximando sus superces con lo que se conoce como Mallas Poligonales. Las mallas poligonales son conjuntos de puntos, aristas (lneas) y polgonos relacionados entre s con el objetivo de aproximar un cuerpo o una supercie. Existen diversas maneras de implementar tales estructuras de datos. Algunas requieren ms memoria que otras, algunas requieren algoritmos ms sosticados para operarlas que otras, algunas posibilitan ciertos anlisis que otras no. Todo depender de las necesidades concretas de la aplicacin a desarrollar. En este punto es conveniente volver a tener frescos los conceptos de geometra analtica vectorial relacionados con planos. Tambin recomendamos leer el captulo 14 en la pgina 283 para poder comprender mejor las descripcines de las estructuras de datos. A continuacin se presentarn algunas representaciones genricas diferentes de mallas poligonales (las primeras tres, adaptadas de [Foley et al., p. 366-367]):
11.1.
Representacin explcita
En esta representacin, los objetos tridimensionales se representan como una lista de polgonos; y cada polgono se representa como una lista propia de los puntos que lo conforman. El diagrama de clases correspondiente puede apreciarse en la gura 11.1. Los objetos tridimensionales se modelan como una composicin de uno o ms polgonos. Y cada polgono se modela como una secuencia lineal de puntos. Obviamente estos son los vrtices que delimitan cada polgono. Esta representacin tiene el problema de la redundancia de informacin. Vemoslo con un ejemplo: En la gura 11.2 se presenta un objeto tridimensional de cuatro vrtices dispuestos en dos polgonos adyacentes. Su representacin en diagrama de objetos (de acuerdo al diagrama de clases de la gura 11.1) est en la gura 11.3.
247
11 Mallas Poligonales
248
Figura 11.4: Diagrama de clases de una malla poligonal en representacin de apuntadores a una lista de vrtices
ambos polgonos, provocando una redundancia innecesaria. Imagine tal redundacia en un objeto altamente complejo, como el toroide de la gura 10.2 en la pgina 244. Adems, como efecto colateral, se genera un grave problema en el caso de querer trasladar un punto, puesto que la bsqueda del punto se hara por igualdad entre tres pares de otantes, lo cual es altamente riesgoso.
11.2.
En esta representacin, los objetos tridimensionales se representan como una lista de polgonos y una lista de vrtices. Cada vrtice del objeto est slo una vez en la lista de vrtices, y cada polgono contiene una lista de apuntadores a los vrtices que lo conforman. El diagrama de clases correspondiente puede apreciarse en la gura 11.4. Los objetos tridimensionales se modelan como una composicin de uno o ms polgonos y al mismo tiempo como una composicin de varios vrtices nicos. Cada polgono se modela como una secuencia lineal de referencias a vrtices. Esta representacin resuelve el problema de la redundancia de informacin, pero presenta el problema siguiente: El proceso de dibujado sera recorrer todos los polgonos y dibujar todas las lineas de unin; pero las lineas
o aristas
polgonos, se dibujaran dos veces (en realidad tantas veces como polgonos unan un mismo par de vrtices). Entonces, para una gura compleja, habra que ejecutar el cdigo de dibujo de lneas una alta cantidad de veces sin necesidad, porque ya habran
249
11 Mallas Poligonales
sido dibujadas. De hecho, la cantidad de lneas innecesariamente dibujadas ronda la mitad de todas las lneas. Veamos en la gura 11.5 la representacin en diagrama de objetos (de acuerdo al diagrama de clases de la gura 11.4) del objeto de la gura 11.2 en la pgina 248. En este caso, al trasladar un punto actualizados.
se hace slo
una modicacin y automticamente todos los polgonos que incluyen tal punto estarn
11.3.
En esta representacin, los objetos tridimensionales se representan como una lista de polgonos, una lista de vrtices y una lista de aristas. Cada polgono se representa como una lista de referencias a las aristas que lo conforman, y cada arista es un par de referencias a los vrtices que unen. El diagrama de clases correspondiente puede apreciarse en la gura 11.6.
250
Figura 11.6: Diagrama de clases de una malla poligonal en representacin de apuntadores a una lista de aristas
Figura 11.7: Objeto tridimensional de ejemplo para representacin de apuntadores a una lista de aristas
Los objetos tridimensionales se modelan como una composicin de uno o ms polgonos, como una composicin de varias aristas nicas y como una composicin de varios vrtices nicos. Cada polgono se modela como una secuencia lineal de referencias a las aristas que lo conforman. Cada arista contiene una referencia a los dos vrtices que une y contiene una referenia a los polgonos a los que pertenece. Cada vrtice por supuesto es nico. Vemos un ejemplo: En la gura 11.7 se presenta el mismo objeto de la gura 11.2 en la pgina 248 pero con informacin sobre las aristas. Su representacin en diagrama de objetos (de acuerdo al diagrama de clases de la gura 11.6) est en la gura 11.8. En este caso, existe la manera de garantizar que cada lnea
o arista
ser dibujada
slo una vez, ya que el recorrido del algoritmo de dibujo puede hacerse sobre la lista de aristas y no sobre la de polgonos. Adems no hay redundancia de vrtices. Por otro
251
11 Mallas Poligonales
252
11.4.
Como se dijo al principio del captulo, existen diversas maneras de modelar cuerpos tridimensionales y que el modelo a implementar depende de las necesidades concretas de las aplicaciones. As, la estructura de datos usada para implementar las aplicaciones
1 2 3 4 5 6 7 8 9 10 11 12 13
/* c06 / transformaciones3d / Objeto3DSimple . java * Clases 3 D bsicas */ public class Objeto3DSimple implements Config3D { VerticeSimple puntos []; AristaSimple aristas []; boolean visible = true ; // Transformar todos los vrtices mediante una matriz // para ser proyectados en un portal de visin public void transformarProyeccion ( Matriz3d m , PortaldeVision portal ) { for ( int i =0; i < puntos . length ; i ++) { puntos [ i ]. puntoProyectado . x = m . e [0][0] * puntos [ i ]. puntoReal . x + m. e [0][1] * puntos [i ]. puntoReal . y + m . e [0][2] * puntos [ i ]. puntoReal . z + m . e [0][3]; puntos [ i ]. puntoProyectado . y = m . e [1][0] * puntos [ i ]. puntoReal . x + m. e [1][1] * puntos [i ]. puntoReal . y + m . e [1][2] * puntos [ i ]. puntoReal . z + m . e [1][3]; puntos [ i ]. puntoProyectado . z = m . e [2][0] * puntos [ i ]. puntoReal . x + m. e [2][1] * puntos [i ]. puntoReal . y + m . e [2][2] * puntos [ i ]. puntoReal . z + m . e [2][3]; }
14
15
16
253
11 Mallas Poligonales
Figura 11.9: Detalles de implementacin de una malla poligonal en representacin de apuntadores a una lista de aristas 254
trnsformionesQhFjr
perspetivQhFjr
17 18 19 20 21 22 23 24
} // Transformar todos los vrtices mediante una matriz // para ser modificados en su universo virtual public void transformar ( Matriz3d m ) { float nx , ny , nz ; for ( int i =0; i < puntos . length ; i ++) { nx = m . e [0][0] * puntos [ i ]. puntoReal . x + m . e [0][1] * ]. puntoReal . y + m. e [0][2] * puntos [i ]. puntoReal . z [0][3]; ny = m . e [1][0] * puntos [ i ]. puntoReal . x + m . e [1][1] * ]. puntoReal . y + m. e [1][2] * puntos [i ]. puntoReal . z [1][3]; nz = m . e [2][0] * puntos [ i ]. puntoReal . x + m . e [2][1] * ]. puntoReal . y + m. e [2][2] * puntos [i ]. puntoReal . z [2][3]; puntos [ i ]. puntoReal . x = nx ; puntos [ i ]. puntoReal . y = ny ; puntos [ i ]. puntoReal . z = nz ; } } } class VerticeSimple { Punto3d puntoReal , puntoProyectado ; public VerticeSimple () { puntoProyectado = new Punto3d (0 f ,0 f ,0 f ) ; } } class AristaSimple { VerticeSimple punto1 , punto2 ;
25
26
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
255
11 Mallas Poligonales
43 44
11.5.
Ejercicios
1. Construya un algoritmo que genere una supercie de malla poligonal cilndrica (Obviamente hay que elegir la representacin que tendr). 2. Construya un algoritmo que genere una supercie de malla poligonal esfrica (se sugiere considerar una divisin en meridianos y paralelos). La nura de la malla deber ser controlada por parmetros de entrada al algoritmo.
256
12.1.
En realidad no existe una denicin especca de Fractal, sino ms bien, un conjunto de caractersticas asociadas a tal denicin. De tal manera que cuando algo tiene algunas de esas caractersticas , se dice que ese algo es un fractal. Las principales caractersticas son las siguientes: Tener una intrincada (y aparentemente sosticada) geometra, tal que no puede ser descrito en trminos de geometra euclideana normal. Poseer el mismo nivel de detalle a cualquier escala. Tener una descripcin geomtrica recursiva. Tener autosimilitud, determinstica o probabilstica en su apariencia. O sea, que el todo sea igual o muy parecido a una de sus partes. Tener una dimensin de Hausdor-Besicovitch mayor que la propia dimensin topolgica. Veamos algunos ejemplos de objetos con esas caractersticas en la naturaleza, en las guras 12.1 , 12.2 y 12.3. Ahora veamos algunas guras fractales generadas por computadora. La 12.4 muestra un helecho que efectivamente parece real. Las guras 12.5 conjunto de Mandelbrot.
1 2 3 4
al menos dos de ellas Ruta completa: http://www.ubcbotanicalgarden.org/potd/2006/02/brassica_oleracea_botrytis_group_romanesco.php Fuente: http://commons.wikimedia.org/wiki/User:Wolfgangbeyer Realizado con el programa libre XaoS: http://xaos.sf.net/
257
Figura 12.1: Fractal natural: Brassica oleracea, un Romanescu fresco, cortesa del programa Botany Photo of the Day de http://www.ubcbotanicalgarden.org/.
Figura 12.2: Las ramas de los rboles siguen leyes fractales de distribuin volumtrica.
258
Figura 12.3: Las hojas de casi todos los helechos tienen la caracterstica de la autosimilitud nita.
259
Figura 12.5: Espiral de satlites con islas de Julia, cortesa del Dr. Wolfgang Beyer
Figura 12.6: Acercamiento del conjunto de Mandelbrot realzado por el autor de este libro con el programa XaoS.
260
Figura 12.7: Otro acercamiento del conjunto de Mandelbrot realizado con la aplicacin
mndelrotFout
261
12.2.
El copo de nieve de
von Koch 5
El copo de nieve de
Niels Fabian Helge von Koch, es una gura sencilla que exhibe
las caractersticas de estar geomtricamente denido por un algoritmo recursivo y por que su dimensin de Hausdor-Besicovitch es mayor que su dimensin topolgica. La explicacin formal de esto ltimo est fuera del alcance actual de esta obra, pero podemos enunciar la consecuencia directa de tal formalismo: La longitud de la curva es innita,
Podemos apreciar una representacin hecha con la aplicacin XaoS mencionada anteriormente en la gura 12.8.
262
12.3 El tringulo de
Sierpiski
12.3.
El tringulo de
Sierpiski 6
Wacaw Sierpiski, el tringulo de
Sierpiski y la carpeta de Sierpiski. Estas pueden verse en las guras 12.9 y 12.10. Las ideas bsicas de estas guras planas se pueden extender para guras tridimensionales, como vemos en la gura 12.11 en la pgina 266.
12.4.
Los Conjuntos
Julia-Fatou
12.4.1. Denicin
Llamados as, en honor de
son guras concretas, sino una familia de guras fractales, con todo el esplendor de la expresin. Se obtienen al analizar el acotamiento de ciertas funciones recursivas en el dominio de (s, de los nmeros complejos).
fc (z )
con semilla
c C,
denotado por
Jc (f ),
es el
z,
z0 = z zn+1 = fc (zn )
Tpicamente se calculan los conjuntos Por otro lado, el conjunto Fatou, Es decir,
Jc (f )
con
fc (z ) = z + c.
Fc (f ) es el complemento de Jc (f ), Fc (f ) = C Jc (f ). Fc (f ) contiene todos los z para los que la sucesin antes descrita, no es acotada. | zn | > 2
entonces
z / Jc (f ).
diverge. Y si no es acotada, no se llegar al valor de 2, por lo que debe haber un nmero mximo de iteraciones a evaluar. Si el valor de
consideraremos que dicha sucesin es acotada. Vale recalcar que mientras mayor sea el
mximo, ms nos acercaremos al conjunto real (el cual, por supuesto, es imposible de
alcanzar). A continuacin presentamos algunas imgenes del programa las ventanas aparecen los valores 12.14 y 12.15.
juliFout.
En el ttulo de
lase sierpiski
263
264
Julia-Fatou
265
266
Julia-Fatou
juliFout
267
juliFout
268
Julia-Fatou
juliFout
269
juliFout
270
Julia-Fatou
12.4.2. Implementacin
En el material adjunto a este libro se encuentra la aplicacin cionamiento se describe a continuacin:
juliFout,
cuyo fun-
El primer clic, dene una de las esquinas de un rectngulo que ser usado
como rea de aumento. El segundo clic, dene la esquina opuesta del rectngulo y se efecta el aumento correspondiente. Cambia la semilla usada para calcular el conjunto y regresa la escala a
sus valores por defecto. Actualmente el programa contiene 12 semillas diferentes (algunas no se aprecian bien con algunos esquemas de color). El valor de la semilla usada se muestra en la barra de ttulo de la ventana.
actual. Por el momento hay 6 algoritmos de coloreado. El ndice del algoritmo aparece en la barra de ttulo de la ventana grca con el formato olor
iGT.
Debido a la alta complejidad del algoritmo que decide si cada pixel pertenece o no al conjunto, la respuesta de la aplicacin no es inmediata. Dependiendo del procesador en el que se ejecute, la aplicacin puede ser un poco lenta para responder a la rueda del ratn. A continuacin se presenta una de las funciones de dibujo de la aplicacin mencionada (tomada del archivo
lulosF):
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
void dibujarFractalJulia6 ( SDL_Surface * pantalla , complejo *c , escala * e ) { int i , j ; complejo z0 , z ; int numPasos ; for ( i =0; i <e - > anchoReal ; i ++) { z0 . i = tV_Ry (e , i) ; for ( j =0; j <e - > altoReal ; j ++) { z0 . r = tV_Rx (e , j ) ; z = z0 ; numPasos = 0; while ( numPasos < MAXPASOS && ( sq ( z . r ) + sq ( z . i ) < LIMITEMODULO2 ) ) { CX_suma ( CX_cuadrado (& z ) , c ) ; numPasos ++; } if ( numPasos == MAXPASOS ) { pixelColor ( pantalla , j , i , COLOR_ADENTRO ) ; } else {
271
pixelColor ( pantalla , j , i , colorGfx ( ( MAXPASOS - numPasos ) *256/ MAXPASOS , ( MAXPASOS - numPasos ) *256/ MAXPASOS , ( MAXPASOS - numPasos ) *256/ MAXPASOS )) ;
12.5.
Conjunto de
Mandelbrot
12.5.1. Denicin
En honor a
M,
cC
z0 = 0 + 0 i zn+1 = zn + c
Y se utilizan los mismos criterios de seleccin que para los conjuntos Julia. Presentamos la forma clsica de este famoso conjunto fractal en la gura 12.16 y una versin estilizada con XaoS en la gura 12.17.
12.5.2. Implementacin
En el material adjunto a este libro se encuentra la aplicacin funcionamiento se describe a continuacin:
mndelrotFout,
cuyo
El primer clic, dene una de las esquinas de un rectngulo que ser usado
como rea de aumento. El segundo clic, dene la esquina opuesta del rectngulo y se efecta el aumento correspondiente. Cambia el algoritmo de coloreado para el conjunto actual con la escala
actual. Por el momento hay 8 algoritmos de coloreado. El ndice del algoritmo aparece en la barra de ttulo de la ventana grca con el formato olor
iGV.
272
12.5 Conjunto de
Mandelbrot
mndelrotFout
273
Figura 12.17: Conjunto Mandelbrot con suavizacin de color interna y externa, generado con la aplicacin XaoS
274
12.6 Ejercicios
El algoritmo que decide si cada pixel pertenece o no al conjunto de Mandelbrot es de la misma complejidad que el del caso de los conjuntos de Julia, por lo que la respuesta de la aplicacin tiene en general, la misma velocidad. A continuacin se presenta una de las funciones de dibujo de la aplicacin mencionada (tomada del archivo
lulosF):
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398
void dibujarFractalMandelbrot7 ( SDL_Surface * pantalla , escala * e ) { int i , j ; complejo z ={0.0 , 0.0} , c ; int numPasos ; for ( i =0; i <e - > anchoReal ; i ++) { c . i = tV_Ry (e , i ) ; for ( j =0; j <e - > altoReal ; j ++) { c . r = tV_Rx (e , j ) ; numPasos = 0; z . r = z .i = 0.0; while ( numPasos < MAXPASOS && ( sq ( z . r ) + sq ( z . i ) < LIMITEMODULO2 ) ) { CX_suma ( CX_cuadrado (& z ) , & c ) ; numPasos ++; } if ( numPasos == MAXPASOS ) { pixelColor ( pantalla , j , i , COLOR_ADENTRO ) ; } else pixelColor ( pantalla , j , i , colorGfx ( numPasos *256/ MAXPASOS , ( MAXPASOS - numPasos ) *256/ MAXPASOS , numPasos *256/ MAXPASOS ) ) ;
12.6.
Ejercicios
M
y
Jc (f ).
2. Construya un algoritmo que dibuje el fractal de la gura 12.18. 3. Construya un algoritmo que dibuje el fractal de la gura 12.19.
275
276
Parte II
Otras Yerbas
277
1 2 3 4 5 6 7 8
/* c13 / principal . c * */ # include " otro . h " int main ( int argc , char * argv []) { funcion () ; return 0; }
Listing 13.2: Cabecera de otro cdigo
1 2 3
1 2 3 4 5 6 7 8 9
/* c13 / otro . c * */ # include " otro . h " # include < stdio .h > int funcion ( void ) { printf ( " hola a todos y todas \ n " ) ; return 0; }
El programa es muy simple, su salida es completamente previsible, por lo que es perfecto para ilustrar cmo podemos apoyarnos en la utilera automtica: Debemos crear en ese mismo directorio un archivo
mke
wkefile
para orientar a
mke.
El
279
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
Listing 13.4: Makele para varios archivos fuente
# c13 / Makefile # Esto es un comentario # El comando para borrar archivos RM = / bin / rm -f # Un '*.o ' por cada '*.c ' que pertenezca al proyecto OBJS = principal . o otro . o # Nombre del programa ejecutable : PROG = programa # Esto indica que los siguientes identificadores , # no son archivos , sino comandos de make : . PHONY : limpiar . PHONY : limpiartodo . PHONY : all # se puede , por ejemplo , ejecutar en la consola # lo siguiente : # '$ make limpiartodo ' , etc . # Cuando se ejecuta '$ make ' , se evalan # las reglas '$ ( PROG ) ' y ' limpiar ': all : $ ( PROG ) limpiar # Esta regla compila todo el cdigo y lo enlaza : $ ( PROG ) : $ ( OBJS ) gcc -o $ ( PROG ) $ ( OBJS ) # Esta regla borra todos los archivos intermedios # y de copia de seguridad : limpiar : $ ( RM ) *~ $ ( OBJS ) # Esta regla borra todos los archivos intermedios # y el programa ejecutable , si es que existe limpiartodo : make limpiar $ ( RM ) $ ( PROG )
La presencia de dicho archivo y su contenido nos permiten ejecutar las siguientes rdenes en esa carpeta:
6 mke limpir
Borra los archivos de copia de seguridad que se hayan creado y tambin borra los archivos de cdigo objeto intermedios (los
BFo)
280
BF).
6 mke limpirtodo
Invoca la instruccin anterior y adems borra el programa ensamblado o ejecutable (su nombre depende de lo que hayamos puesto en la variable
yq.
6 mke
Se ejecuta lo que hayamos indicado en la regla
yq, que a su vez invoca la compilacin individual de cada archivo fuente (los BF) y posteriormente invoca el ensamblaje de estos con el comando g. Posteriormente invoca la regla limpir.
Esta pequea gua no pretende ser altamente exhaustiva. Simplemente pretende orientar para la compilacin asistida en proyectos de programacin en lenguaje C estndar de mediana escala en ambientes tipo UNIX. Si se necesita mayor detalle o explicacin, por favor rerase el lector a la documentacin apropiada. Por ejemplo:
ll.
6 mn mke
O en los sitios siguientes: http://www.chuidiang.com/clinux/herramientas/makele.php http://www.calcifer.org/documentos/make/makele.html http://atc1.aut.uah.es/lsotm/Makele.htm http://en.wikipedia.org/wiki/Make_(software) http://www.opussoftware.com/tutorial/TutMakele.htm
281
282
14.1.
Notacin de clases
En UML una clase se representa como un rectngulo con tres espacios. En el primero va el nombre de la clase, en el segundo sus atributos y en el tercero sus operaciones. Veamos un ejemplo en la gura 14.1. La clase se llama
entero, y h, un otante con valor por defecto de 4.0. El atributo i es pblico y h es privado. Eso indican los signos que preceden a los nombres. Que un atributo sea privado signica que su mbito de acceso est limitado al interior del cdigo de la clase, y si es pblico, signica que se puede acceder a l desde cualquier mbito desde el que se pueda alcanzar una instancia de esta clase. La primera operacin es privada, se llama metodo, retorna void y recibe un parmetro llamado para1 de tipo
retorna un String y recibe dos parmetros: semilla que es entero con valor por defecto de 3, y objeto de tipo Estructura.
283
Los niveles de acceso pblico y privado son los ms usuales en los lenguajes de programacin orientados a objetos, pero algunos lenguajes denen otros niveles de acceso. En el caso de Java, tambin existen los niveles de acceso protegido y de paquete, representados por los signos 5 y ~ respectivamente. Por otro lado, la informacin de los atributos y de las operaciones de las clases a veces no es relevante o no es conveniente mostrarla (generalmente por cuestiones de espacio), por lo que pueden omitirse esas secciones y mostrar nicamente un rectngulo con el nombre de la clase, como vemos en la gura 14.2.
14.2.
En la gura 14.3 se presenta un resumen de la notacin bsica de asociaciones en diagramas de clases. Veamos cada uno de ellos: 1. Indica simplemente que hay una relacin uno-a-uno entre una instancia de y una de
Clase1
2. Indica que hay una relacin uno-a-uno entre las instancias, pero agrega semntica a la relacin: indica que las instancias de
Clase2.
3. Indica que hay una relacin uno-a-uno entre las clases, y el nombre de la relacin, pero sin indicar la direccin de la relacin. 4. Indica que una instancia de muchas instancias de referencias a instancias de
Clase2 y que una instancia de Clase2 tiene forzosamente 2 Clase1. Clase1 debe tener al menos una referencia a instancias
Estos elementos que indican cantidad de referencias se llaman multiplicidades . 5. Indica que una instancia de de a instancias de
Clase2 y que una instancia de Clase2 tiene forzosamente entre 2 y 4 referencias Clase1.
6. Indica que hay una relacin uno-a-uno, pero indicando que para las instancias de
284
Clase1 deben tener una coleccin de referencias a Clase2 tienen una coleccin de 5 referencias a in-
adems, que las instancias de dichas colecciones. 8. Indica que una instancia de y que las instancias de relacionadas de
Clase1 que se llama apuntados. Indica adems el nivel de acceso de Clase1 tiene 5 referencias a instancias de Clase2, Clase2 no tienen referencias a las instancias
Clase1.
Los diagramas de clase no obligan al diseador a especicar cmo, en concreto, se implementarn tales referencias o colecciones de referencias. Se podran implementar con arreglos, con listas, rboles, grafos, etc. En la gura 14.4 se presentan ms tipos de asociaciones en diagramas de clases: 1. Indica una agregacin, en la que las instancias de estas tienen una referencia a la instancia de
286
Clase1 deja de existir, las inClase1 estn formadas (entre Clase2, y Clase1, pero no contienen
otras cosas) por una coleccin de entre 1 y 4 referencias a instancias de que estas conforman simultaneamente dos instancias de referencia a ellas.
3. Indica una composicin, que es una agregacin muy restrictiva, ya que indica que si la instancia compuesta de
Clase2 hereda de Clase1. Algunos lenguajes de programacin 1 permiten herencia mltiple , otros no. Java no lo permite directamente, Python
Hay ms tipos de relaciones en la notacin UML, pero como el ttulo de este captulo dice, es slo una untadita.
14.3.
Notacin de objetos
Las instancias de una clase se representan como rectngulos con dos partes. La primera tiene el siguiente formato:
`nomresnstnibX `nomreglseb
La otra parte est reservada para los valores de sus atributos. Al igual que en el caso de los diagramas de clases, esta parte se puede obviar cuando no es necesaria. Podemos ver un ejemplo de un diagrama de objetos en la gura 14.5, en la que hay dos instancias de la clase
Clase.
287
En los diagramas de objetos tambin se pueden incluir las referencias entre s, con o sin nombre y con o sin direccin, segn sea el caso, tal como se ve en las guras 11.3, 11.5 y 11.8.
288
Parte III
Apndices
289
trnsformionesQhFjr
perspetivQhFjr.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/* cA / Controlador . java * Objeto controlador */ import java . awt . Graphics ; import java . awt . event .*; /* * Controlador de la aplicacin */ public class Controlador { /* * * Funcin principal independiente de instancias . */ public static void main ( String args []) { new Controlador ( args ) ;
291
} Ventana vista ; Modelo modelo ; /* * Hilo principal */ public Controlador ( String args []) { vista = new Ventana ( this ) ; modelo = new Universo3D () ; // mostrar la ventana vista . setVisible ( true ) ; System . out . println ( " Iniciando aplicacin " ) ; // accin principal o // conjunto de acciones principales modelo . hacerAlgo () ; // cuando ya acabamos de hacer algo importante , // terminamos la aplicacin System . exit (0) ;
/* * * Simple delegacin del proceso * */ public void dibujar ( Graphics g ) { modelo . dibujar ( g ) ; } /* * * Implementar los eventos generados desde la vista . * Como el de cerrar la ventana : * */ public WindowAdapter AdaptadorVentana = new WindowAdapter () { public void windowClosing ( java . awt . event . WindowEvent evt ) { System . out . println () ; System . exit (0) ; } };
1 2 3
292
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
*/ import java . awt .*; public class Modelo { public Modelo () { /* * * Echar a andar todos * los mecanismos del modelo * y toda su lgica . * */ } public hacerAlgo () { /* * * Aqu debera estar el corazn * de la ejecucin del modelo . * */ } /* * * El modelo no tiene conciencia * de la procedencia del contexto * grfico en el que se le * est solicitando trabajar . * * Simplemente responde por delegacin . * */ public void dibujar ( Graphics g ) { /* * * Aqu hay que hacer lo propio . * * Aqu hay que dibujar lo que haya * que dibujar de acuerdo al estado * actual del modelo y de otros * factores relevantes . * */ }
Listing A.3: Clase vista
1 2 3 4 5 6 7 8
/* cA / Ventana . java * Objeto vista , * tpicamente la Ventana de la aplicacin */ import java . awt .*; public class Ventana extends javax . swing . JFrame implements Config3D {
293
/* * * Referencia al controlador para * delegarle la respuesta a los * eventos generados desde aqu . * */ private Controlador control ; /* * * Objeto especial para lograr * la delegacin de las solicitudes * de refrescamiento * */ private PanelEspecial panelprin ; public Ventana ( Controlador control ) { this . control = control ; inicializarComponentes () ; } /* * Inicializar los componentes de la interfaz * grfica de usuario si es que hay . * En el caso de Java con Swing siempre hay . * */ private void inicializarComponentes () { panelprin = new PanelEspecial ( control ) ; setDefaultCloseOperation ( javax . swing . WindowConstants . EXIT_ON_CLOSE ) ; setTitle ( " Ttulo de la aplicacin " ) ; /* * * Agregar todos los " escuchadores " , * que deberan estar implementados * en el controlador . * * Todas las respuestas a los eventos * diparados desde este objeto grfico * y sus includos , deberan ser respondidos * por el controlador con asistencia de * los datos del modelo . * */ addWindowListener ( control . AdaptadorVentana ) ; panelprin . setBorder ( new javax . swing . border . LineBorder ( new java . awt . Color (0 , 0 , 0) ) ) ; panelprin . setMinimumSize ( new java . awt . Dimension (400 , 300) ) ; // agregar el panel principal a la ventana
294
57 58 59 60 61 62 63 64 65 66 67 68
getContentPane () . add ( panelprin , java . awt . BorderLayout . CENTER ) ; // acomodar dinmicamente los objetos grficos pack () ; // centrar esta ventana en la pantalla : Dimension tamanjoForzado = new Dimension (400 , 300) ; java . awt . Dimension tamanjoPantalla = java . awt . Toolkit . getDefaultToolkit () . getScreenSize () ; setLocation (( tamanjoPantalla . width - tamanjoForzado . width ) /2 ,( tamanjoPantalla . height - tamanjoForzado . height ) /2) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
/* cA / PanelEspecial . java * Un componente que puede ponerse en un contenedor ... * y mostrar las figuras */ import java . awt .*; import javax . swing .*; import java . awt . event .*; public class PanelEspecial extends JPanel { Controlador control ; public PanelEspecial ( Controlador control ) { this . control = control ; } /* * * Mtodo llamado cuando es necesario * redibujar la pantalla . * La tarea se delega de la vista al * controlador y de este al modelo . * */ public void paintComponent ( Graphics g) { control . dibujar ( g ) ; }
Luego de este breve ejemplo, conviene mencionar cmo compilar el cdigo fuente y cmo ensamblarlo en un slo archivo
Fjr
Flss.
6 jv gontroldorFjv
295
min. min
gontroldorFjv
6 jv gontroldor
Pero en lugar de dejar todos los archivos archivo jar luego de la compilacin, as:
Flss,
Fjr,
La opcin f indica que el siguiente parmetro debe ser el nombre del archivo a crear (o del archivo a operar). La opcin e indica que el siguiente parmetro (en este caso, despus del nombre del archivo a operar), es el nombre de la clase con el punto de entrada (donde est el main que queremos que se ejecute primero). Finalmente listamos todos los archivos que queremos incluir en el archivo creado (en este caso, todos los archivos
Flss
jr,
ver
http://java.sun.com/docs/books/tutorial/deployment/jar/ o ejecutar:
6 mn jr
296
B Referencias y manuales
B.1. SDL Simple DirectMedia Layer
B.1.1. Sitios de recursos
http://www.libsdl.org http://www.libsdl.es http://www.javielinux.com http://www.agserrano.com/publi.html http://www.losersjuegos.com.ar/
B.1.2. Artculos
Por qu SDL? (en espaol): http://www.losersjuegos.com.ar/referencia/articulos/why_sdl/why_sdl.php http://es.wikipedia.org/wiki/Grcos_3D_por_computadora Artculo sobre juegos libres http://www.marevalo.net/creacion/unmundofeliz/1999_12_06_juegos_libres.html http://es.wikipedia.org/wiki/Desarrollo_de_videojuegos http://en.wikipedia.org/wiki/Game_programming http://www.losersjuegos.com.ar/referencia/articulos/articulos.php Game Programming Wiki http://wiki.gamedev.net/
297
B Referencias y manuales
B.2.
Python y pygame
B.3.
Java Me
298
Bibliografa
[Foley et al.] Foley, James D.; van Dam, Andries; Feiner, Steven K.; Hughes, John F.; Phillips, Richard L. 1996. [Burden y Faires 2002] Burden, Richard L.; Faires, J. Douglas.
Introduccin a la gra-
Sptima edicin, Thomson Learning, 2002. [Henrquez 1999] Henrquez, Mauro Hernn.
Curve. Edicin del 26 de Febrero de 2010, 13:20 UTC. Revisado el 4 de Marzo de 2010 a las 8:42am, hora local de El Salvador. Enlace permanente: Bzier curve - oldid=346486604.
299