Está en la página 1de 299

Departamento de Electrnica e Informtica,

Universidad Centroamericana
Jos Simen Caas

Una Humilde Introduccin


a la Gracacin por Computadora
y Otras Yerbas
por

Eduardo NAVAS

versin 1.0
2010.03.08

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

Creative Commons Attribution-Noncommercial-Share Alike 3.0.


Los detalles pueden ser consultados en:
http://creativecommons.org/licenses/by-nc-sa/3.0/deed.es
La versin digital y el material adicional puede ser descargado de:
www.aliamondano-eo.wikidot.com/libro-gracos
http://dei.uca.edu.sv/publicaciones/
ISBN: 978-99923-73-36-1
Editado y preparado desde el

Departamento de Electrnica e Infomtica de la


Universidad Centroamericana Jos Simen Caas,
El Salvador, Centroamrica.

Dedico esta obra...


.- A todos los que conaron en m (con mucha frecuencia mucho ms
de lo que yo mismo) en este viaje en tren, que se llama mi vida ;
.- A mi creador y seor que siempre se qued conmigo, an cuando
yo no me qued siempre con l;
.- A todos los que quieren ayudar a construir
el Otro Mundo Posible

.- Al tiuj kiuj ec kontra


uue kunhelpas fari pli bonan mondon

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.

Lo que queda fuera


Como se dijo antes, la gracacin por computadora es una de las grandes reas de las
ciencias de la computacin, razn por la cual, cualquier obra que pretenda cubrirla toda,
se quedar corta. En el caso de este libro, estos son los temas que no se cubren en la
versin actual de la obra:
Temas avanzados de SDL
Interfaces grcas de usuario (GUI)
Modelado de slidos
Renderizacin de texto
Operaciones de bajo nivel (en ensamblador, por ejemplo)
Manipulacin de mapas de bits
Hardware grco
Iluminacin, sombreado y texturas

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.

Lo que conviene saber


Antes de escrutar este libro, es recomendable tener una base aceptable en los siguientes
temas:
Operaciones bsicas con matrices
lgebra vectorial y nociones de clculo vectorial
Operaciones bsicas con nmeros complejos
Parametrizacin de curvas
Sistemas de coordenadas rectangulares, cilndricas y esfricas
Estructuras de datos bsicas (listas, pilas, colas, rboles, directorios de nodos,
grafos, etc.)
Nociones claras de Programacin Estructurada y Programacin Orientada a Objetos
Conocimientos slidos del lenguaje C estndar en ambiente GNU/Linux
Conocimientos intermedios en lenguaje Python en ambiente GNU/Linux
Conocimientos intermedios del lenguaje Java
Destrezas elementales del uso de sistemas operativos GNU/Linux

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.

Cambios en esta versin


En esta nueva versin (que se corresponde con la segunda edicin), se han hecho muchos
cambios a lo largo de todo el texto. Se han corregido diversos errores tipogrcos en todos
los captulos, se han revisado todos los cdigos fuente que ya estaban y las imgenes se
han revisado en su resolucin y distribucin. El tamao de las pginas tambin se ha
cambiado al tamao carta, que es el estndar de facto en El Salvador.
Todos los bloques de cdigo fuente se han resaltado para su ms cmoda y ms fcil
lectura.
El captulo 1 se ha ampliado en el tema de la instalacin de SDL. Los ejemplos bsicos
fueron revisados y reordendados.
El captulo 2 incluye ahora una seccin especial sobre el ciclo de interaccin de aplicaciones grcas interactivas.
El captulo 9 incluye mucho nuevo material, incluyendo nuevas imgenes, ms algoritmos,
ms profundidad y nuevos programas de ejemplo.
El captulo 10 tambin cuenta con nuevas imgenes, hechas con Maxima. El contenido
del 12 fue revisado y cuenta con nuevas imgenes.
Y nalmente, se ha integrado la biblioteca
texto, en paralelo con

10

SDL

para C.

pygame

para Python en la primera parte del

ndice general
I. Gracacin por computadora

21

1. Introduccin a SDL y PyGAME


1.1.

23

Instalacin de las bibliotecas para C

. . . . . . . . . . . . . . . . . . . .

23

1.1.1.

Vericar si ya estn instalados los paquetes de desarrollo . . . . .

23

1.1.2.

Diseo modular de SDL

24

1.1.3.

Instalacin en distribuciones basadas en Debian . . . . . . . . . .

24

1.1.4.

Instalacin en openSuSE . . . . . . . . . . . . . . . . . . . . . . .

25

1.1.5.

Instalacin en Fedora y derivados de RedHat

1.1.6.

Otras distribuciones

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . .

25

. . . . . . . . . . . . . . . . . . . . . . . . .

26

1.2.

Compilacin de los programas que usan SDL en C

. . . . . . . . . . . .

26

1.3.

Ejemplos bsicos en SDL . . . . . . . . . . . . . . . . . . . . . . . . . . .

26

1.3.1.

Inicializacin bsica del sistema de video (el Hola Mundo de SDL):

26

1.3.2.

Inicializacin de los subsistemas . . . . . . . . . . . . . . . . . . .

28

1.3.3.

Modos de video . . . . . . . . . . . . . . . . . . . . . . . . . . . .

29

1.3.4.

Eventos de ratn . . . . . . . . . . . . . . . . . . . . . . . . . . .

31

1.3.5.

Eventos de teclado . . . . . . . . . . . . . . . . . . . . . . . . . .

33

1.3.6.

Redimensionamiento de la ventana . . . . . . . . . . . . . . . . .

35

1.3.7.

Facilitar

36

1.3.8.

Compilacin desde varios archivos fuente . . . . . . . . . . . . . .

la compilacin

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

37

1.4.

Dibujo de primitivas grcas con SDL

1.5.

Instalacin de PyGAME . . . . . . . . . . . . . . . . . . . . . . . . . . .

45

1.6.

Ejemplos bsicos en Python . . . . . . . . . . . . . . . . . . . . . . . . .

46

pygame):

42

1.6.1.

Inicializacin bsica del sistema de video (el Hola Mundo de

1.6.2.

Inicializacin de los subsistemas . . . . . . . . . . . . . . . . . . .

47

1.6.3.

Modos de video . . . . . . . . . . . . . . . . . . . . . . . . . . . .

48

1.6.4.

Eventos de ratn . . . . . . . . . . . . . . . . . . . . . . . . . . .

50

1.6.5.

Eventos de teclado . . . . . . . . . . . . . . . . . . . . . . . . . .

51

1.6.6.

Redimensionamiento de la ventana . . . . . . . . . . . . . . . . .

1.7.

Dibujo de primitivas grcas con

1.8.

Ejercicios

pygame

46

52

. . . . . . . . . . . . . . . . . .

53

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

54

11

ndice general

2. Introduccin a la Gracacin por Computadora


2.1.

2.2.

2.3.

57

Marco conceptual para la gracacin interactiva . . . . . . . . . . . . . .

57

2.1.1.

El Modelado

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

57

2.1.2.

La Presentacin . . . . . . . . . . . . . . . . . . . . . . . . . . . .

58

2.1.3.

La Interaccin

. . . . . . . . . . . . . . . . . . . . . . . . . . . .

58

Ciclo de interaccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

59

2.2.1.

Ciclo de eventos con SDL

2.2.2.

Ciclo de juego con SDL

. . . . . . . . . . . . . . . . . . . . . .

60

. . . . . . . . . . . . . . . . . . . . . . .

61

2.2.3.

Ciclo de eventos con pygame

2.2.4.

. . . . . . . . . . . . . . . . . . . .

Ciclo de juego con pygame . . . . . . . . . . . . . . . . . . . . . .

69

Tipos de representacin de grcos . . . . . . . . . . . . . . . . . . . . .

74

2.3.1.

Grcos de barrido o raster

. . . . . . . . . . . . . . . . . . . . .

74

2.3.2.

Grcos vectoriales . . . . . . . . . . . . . . . . . . . . . . . . . .

75

2.3.3.

Representacin hbrida . . . . . . . . . . . . . . . . . . . . . . . .

75

2.4.

Paletas de colores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

75

2.5.

Paletas estndares actuales

76

. . . . . . . . . . . . . . . . . . . . . . . . .

2.5.1.

RGB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

76

2.5.2.

RGBA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

77

2.5.3.

CMY(K)

77

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

2.6.

Espacios de Colores (gamuts)

. . . . . . . . . . . . . . . . . . . . . . . .

2.7.

Ejercicios

78

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

79

3. Discretizacin de Primitivas Grcas

81

3.1.

Recordatorio bsico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

81

3.2.

Simbologa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

82

3.3.

Algoritmo incremental bsico

3.4.

Algoritmo de lnea de punto medio


3.4.1.

. . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .

Simetra del algoritmo de lnea de punto medio

83
84

. . . . . . . . . .

90

3.5.

La simetra de la circunferencia . . . . . . . . . . . . . . . . . . . . . . .

94

3.6.

Algunas ideas sobre circunferencias . . . . . . . . . . . . . . . . . . . . .

95

3.7.

Algoritmo de circunferencia de punto medio . . . . . . . . . . . . . . . .

95

3.7.1.

Versin sin multiplicaciones

. . . . . . . . . . . . . . . . . . . . .

99

3.7.2.

Circunferencias con centro arbitrario . . . . . . . . . . . . . . . .

101

3.8.

Relleno de rectngulos . . . . . . . . . . . . . . . . . . . . . . . . . . . .

102

3.9.

Relleno de circunferencias

. . . . . . . . . . . . . . . . . . . . . . . . . .

103

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

105

3.10. Ejercicios

4. Marcos de Referencia y Cambio de Coordenadas

12

67

109

4.1.

Notacin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

109

4.2.

Anlisis vectorial del cambio de coordenadas . . . . . . . . . . . . . . . .

110

4.2.1.

112

Ejemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

ndice general
4.3.

Simplicacin escalar para ventana completa

4.4.

Transformacin de distancias

4.5.

4.6.

. . . . . . . . . . . . . . .

114

. . . . . . . . . . . . . . . . . . . . . . . .

115

Aplicacin: Simulador de campo elctrico bidimensional

. . . . . . . . .

115

. . . . . . . . . . . . . . . . . . . . . . . . . . .

116

4.5.1.

Campo Elctrico

4.5.2.

Uso de colores para

. . . . . . . . . . . . . .

118

4.5.3.

Las escalas y sus conversiones . . . . . . . . . . . . . . . . . . . .

119

4.5.4.

Programa principal . . . . . . . . . . . . . . . . . . . . . . . . . .

4.5.5.

El

Ejercicios

Makefile .

SDL_gfxPrimitives

129

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

130

5. Transformaciones Geomtricas Bidimencionales


5.1.

121

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Operaciones geomtricas bsicas

133

. . . . . . . . . . . . . . . . . . . . . .

133

5.1.1.

Traslacin o Desplazamiento . . . . . . . . . . . . . . . . . . . . .

133

5.1.2.

Escalamiento

134

5.1.3.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Rotacin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

136

5.2.

Representacin matricial . . . . . . . . . . . . . . . . . . . . . . . . . . .

137

5.3.

Composicin de tranformaciones geomtricas

. . . . . . . . . . . . . . .

138

5.4.

Atencin a la eciencia . . . . . . . . . . . . . . . . . . . . . . . . . . . .

139

5.5.

Versin matricial del cambio de coordenadas . . . . . . . . . . . . . . . .

139

5.6.

Reversin de transformaciones geomtricas . . . . . . . . . . . . . . . . .

140

5.7.

Ejercicios

143

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

6. Transformaciones Geomtricas Tridimencionales

145

6.1.

Sistemas de referencia

. . . . . . . . . . . . . . . . . . . . . . . . . . . .

6.2.

Representacin Matricial de Transformaciones Geomtricas

6.3.

Composicin y reversin de Transformaciones Geomtricas Tridimensionales148

6.4.

Ejercicios

. . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

7. Vista Tridimensional (de 3D a 2D)

145
145

150

151

7.1.

Proyeccin Ortogonal . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

7.2.

Proyeccin en Perspectiva

. . . . . . . . . . . . . . . . . . . . . . . . . .

153

7.3.

Portal de Visin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

153

7.4.

Implementacin de proyecciones ortogonales . . . . . . . . . . . . . . . .

156

7.5.

Implementacin de proyecciones en perspectiva

. . . . . . . . . . . . . .

158

7.6.

Ejercicios

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

162

8. Interaccin

151

163

8.1.

Posicionamiento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

163

8.2.

Seleccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

164

8.2.1.

Seleccin por puntero

164

8.2.2.

Seleccin por nominacin

. . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .

165

13

ndice general
8.3.

Transformacin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

165

8.4.

Conclusin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

166

9. Curvas Paramtricas
9.1.
9.2.

169

Curvas suaves . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

169

9.2.2.

Tipos de continuidad . . . . . . . . . . . . . . . . . . . . . . . . .

170

Continuidad Geomtrica . . . . . . . . . . . . . . . . . . . . . . .

170

Continuidad Paramtrica

170

Interpolacin

. . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Trazadores interpolantes cbicos - Curvas Spline

. . . . . . . . . . . . .

171
172

9.3.1.

Curvas Spline y B-Spline . . . . . . . . . . . . . . . . . . . . . . .

172

9.3.2.

Descripcin geomtrica . . . . . . . . . . . . . . . . . . . . . . . .

172

9.3.3.

Descripcin matemtica

173

9.3.4.

Algoritmo de clculo de coecientes

9.3.5.

Algoritmos de clculo de puntos interpolados

. . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . .

173
175

Clculo de un slo punto . . . . . . . . . . . . . . . . . . . . . . .

175

Clculo de todos los puntos . . . . . . . . . . . . . . . . . . . .

175

Extensin para curvas paramtricas multidimensionales

176

. . . . .

El caso bidimensional

. . . . . . . . . . . . . . . . . . . . . . . .

176

El caso tridimensional

. . . . . . . . . . . . . . . . . . . . . . . .

176

Eciencia en el clculo de un slo punto

. . . . . . . . . . . . . .

177

Eciencia en el clculo todos los puntos

. . . . . . . . . . . . . .

178

9.3.7.

Ejemplo de implementacin . . . . . . . . . . . . . . . . . . . . .

178

9.3.8.

Aplicacin para animaciones . . . . . . . . . . . . . . . . . . . . .

Curvas de Bzier

194

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

210

9.4.1.

Descripcin geomtrica . . . . . . . . . . . . . . . . . . . . . . . .

210

9.4.2.

Descripcin matemtica

. . . . . . . . . . . . . . . . . . . . . . .

210

9.4.3.

Polinomios de Bernstein . . . . . . . . . . . . . . . . . . . . . . .

211

9.4.4.

Generalizacin de curvas de Bzier de grado

9.4.5.

Unin de segmentos de Bzier . . . . . . . . . . . . . . . . . . . .

214

9.4.6.

Ejemplos de implementacin

. . . . . . . . . . . . . . . . . . . .

215

bezier1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

215

bezier2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

221

editorBezier.py

. . . . . . . . . . . . . . . . . . . . . . . . . . . .

239

9.5.

Splines vs. Bzier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

240

9.6.

Ejercicios

240

. . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

10.Supercies paramtricas
10.1. Representacin algebrica de Supercies paramtricas

14

167

Continuidad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

9.3.6.

9.4.

. . . . . . . . . . . .

9.2.1.

9.2.3.
9.3.

167

Representacin algebrica de Curvas Paramtricas

211

243
. . . . . . . . . .

243

ndice general
10.2. Ejercicios

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

11.Mallas Poligonales

245

247

11.1. Representacin explcita . . . . . . . . . . . . . . . . . . . . . . . . . . .

247

11.2. Apuntadores a una lista de vrtices . . . . . . . . . . . . . . . . . . . . .

249

11.3. Apuntadores a una lista de aristas

. . . . . . . . . . . . . . . . . . . . .

250

11.4. Apuntadores slo a una lista de aristas . . . . . . . . . . . . . . . . . . .

253

11.5. Ejercicios

256

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

12.Introduccin a los Fractales

257

12.1. Caractersticas de los fractales . . . . . . . . . . . . . . . . . . . . . . . .


12.2. El copo de nieve de von Koch

257

. . . . . . . . . . . . . . . . . . . . . . . .

262

. . . . . . . . . . . . . . . . . . . . . . . . . .

263

12.4. Los Conjuntos Julia-Fatou . . . . . . . . . . . . . . . . . . . . . . . . . .

263

12.4.1. Denicin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

263

12.4.2. Implementacin . . . . . . . . . . . . . . . . . . . . . . . . . . . .

271

12.3. El tringulo de Sierpiski

12.5. Conjunto de Mandelbrot

. . . . . . . . . . . . . . . . . . . . . . . . . . .

272

12.5.1. Denicin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

272

12.5.2. Implementacin . . . . . . . . . . . . . . . . . . . . . . . . . . . .

272

12.6. Ejercicios

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

275

II. Otras Yerbas

277

13.Compilacin desde Mltiples archivos fuente (en lenguaje C)

279

14.Diagramas de Clases y Diagramas de Objetos


(una untadita de UML)

283

14.1. Notacin de clases

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

283

14.2. Notacin de relaciones o asociaciones . . . . . . . . . . . . . . . . . . . .

284

14.3. Notacin de objetos

287

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

III. Apndices

289

A. Plantilla de Aplicacin Grca en J2SE

291

B. Referencias y manuales

297

B.1. SDL  Simple DirectMedia Layer . . . . . . . . . . . . . . . . . . . . . .

297

B.1.1. Sitios de recursos . . . . . . . . . . . . . . . . . . . . . . . . . . .

297

B.1.2. Artculos

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

297

B.2. Python y pygame . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

298

15

ndice general

16

B.3. Java Me . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

298

B.3.1. Sitios de recursos . . . . . . . . . . . . . . . . . . . . . . . . . . .

298

ndice de guras
1.1.

Display de 7 segmentos . . . . . . . . . . . . . . . . . . . . . . . . . . . .

55

2.1.

Modelo de color RGB

77

2.2.

Modelo de color CMY(K)

2.3.

Plano Matiz-Saturacin de la luz visible

2.4.

Comparacin del Gamut de RGB y CMYK

3.1.

Representacin abstracta de nueve pixeles

3.2.

Justicacin del algoritmo de lnea de punto medio bsico - 1

. . . . . .

85

3.3.

Justicacin del algoritmo de lnea de punto medio bsico - 2

. . . . . .

85

3.4.

Anlisis inverso de punto medio . . . . . . . . . . . . . . . . . . . . . . .

92

3.5.

Simetra de las circunferencias . . . . . . . . . . . . . . . . . . . . . . . .

94

3.6.

Algoritmo de circunferencia de punto medio . . . . . . . . . . . . . . . .

96

3.7.

Esquema para algorito de circunferencias con centro arbitrario . . . . . .

101

3.8.

Relleno de circunferencias

. . . . . . . . . . . . . . . . . . . . . . . . . .

104

3.9.

Cuadrcula de prctica de primitivas grcas . . . . . . . . . . . . . . . .

106

4.1.

Un mismo punto en dos marcos de referencia diferentes . . . . . . . . . .

110

4.2.

Transformacin vectorial de coordenadas . . . . . . . . . . . . . . . . . .

111

4.3.

Cambio de escala de vectores

. . . . . . . . . . . . . . . . . . . . . . . .

112

4.4.

Situacin de ejemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

113

4.5.

Caso particular de pantalla completa . . . . . . . . . . . . . . . . . . . .

114

4.6.

Diagrama para el ejercicio 1 . . . . . . . . . . . . . . . . . . . . . . . . .

131

5.1.

Ejemplo de traslacin simple

. . . . . . . . . . . . . . . . . . . . . . . .

134

5.2.

Ejemplo de escalamiento simple . . . . . . . . . . . . . . . . . . . . . . .

135

5.3.

Ejemplo de escalamiento compuesto

. . . . . . . . . . . . . . . . . . . .

135

5.4.

Ejemplo de rotacin simple

. . . . . . . . . . . . . . . . . . . . . . . . .

136

5.5.

Ejemplo de rotacin compuesta . . . . . . . . . . . . . . . . . . . . . . .

137

5.6.

Cambio de coordenadas a travs de mltiples marcos de referencia

141

5.7.

Cambio de coordenadas con rotacin . . . . . . . . . . . . . . . . . . . .

141

5.8.

Ejercicio de transformacin bidimensional

. . . . . . . . . . . . . . . . .

144

6.1.

Sistema de referencia de mano derecha . . . . . . . . . . . . . . . . . . .

146

. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .

. . .

78
79
80
82

17

ndice de guras
6.2.

Sistema de referencia de mano izquierda

6.3.

Otra manera de ver el sistema de referencia de mano izquierda

7.1.

Ejemplos de proyeccin ortogonal . . . . . . . . . . . . . . . . . . . . . .

152

7.2.

Ejemplos de proyeccin en perspectiva

. . . . . . . . . . . . . . . . . . .

154

7.3.

Proyeccin ortogonal y en perspectiva

. . . . . . . . . . . . . . . . . . .

158

7.4.

Deduccin de proyeccin en perspectiva

. . . . . . . . . . . . . . . . . .

159

9.1.

Ejemplo de curvas paramtricas planas . . . . . . . . . . . . . . . . . . .

168

9.2.

Resorte en alrededor del eje

generado con Octave . . . . . . . . . . . .

169

9.3.

Interpolacin

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

171

9.4.

Grca de los Polinomios de Bernstein de grado 3 . . . . . . . . . . . . .

212

9.5.

Aporte de cada uno de los polinomios de Bernstein de grado 3 . . . . . .

213

y,

10.1. Cilindro generado con Maxima


10.2. Toroide

. . . . . . . . . . . . . . . . . .
. . . . .

146
147

. . . . . . . . . . . . . . . . . . . . . . .

244

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

244

10.3. Cinta de Mbius

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

11.1. Diagrama de clases de una malla poligonal en representacin explcita

245

248

11.2. Objeto tridimensional simple de ejemplo . . . . . . . . . . . . . . . . . .

248

11.3. Diagrama de objetos del objeto de la gura 11.2 . . . . . . . . . . . . . .

248

11.4. Diagrama de clases de una malla poligonal en representacin de apuntadores a una lista de vrtices . . . . . . . . . . . . . . . . . . . . . . . . .

249

11.5. Otro diagrama de objetos del objeto de la gura 11.2 . . . . . . . . . . .

250

11.6. Diagrama de clases de una malla poligonal en representacin de apuntadores a una lista de aristas . . . . . . . . . . . . . . . . . . . . . . . . . .

251

11.7. Objeto tridimensional de ejemplo para representacin de apuntadores a


una lista de aristas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

251

11.8. Diagrama de objetos del objeto de la gura 11.7 . . . . . . . . . . . . . .

252

11.9. Detalles de implementacin de una malla poligonal en representacin de


apuntadores a una lista de aristas . . . . . . . . . . . . . . . . . . . . . .

254

11.10.Diagrama de clases de las aplicaciones

transformaciones3D.jar

perspectiva3D.jar

. . . . . . . . . . . . .

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

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

12.5. Espiral de satlites con islas de Julia, cortesa del Dr. Wolfgang Beyer

18

259
259
260

ndice de guras
12.6. Acercamiento del conjunto de Mandelbrot realzado por el autor de este
libro con el programa XaoS. . . . . . . . . . . . . . . . . . . . . . . . . .

260

12.7. Otro acercamiento del conjunto de Mandelbrot realizado con la aplicacin

mandelbrot.out

presentada ms adelante en este captulo. . . . . . . . .

12.8. Copo de nieve de von Koch

261

. . . . . . . . . . . . . . . . . . . . . . . . .

262

12.9. Tringulo de Sierpiski . . . . . . . . . . . . . . . . . . . . . . . . . . . .

264

12.10.Carpeta de Sierpiski . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

265

12.11.Pirmide de Sierpiski . . . . . . . . . . . . . . . . . . . . . . . . . . . .

julia.out . . . . .
12.13.Segunda imgen del programa julia.out
12.14.Tercera imgen del programa julia.out .
12.15.Cuarta imgen del programa julia.out .
12.12.Imgen del programa

266

. . . . . . . . . . . . . . . . .

267

. . . . . . . . . . . . . . . . .

268

. . . . . . . . . . . . . . . . .

269

. . . . . . . . . . . . . . . . .

270

12.16.Forma clsica del conjunto Mandelbrot, generado con

mandelbrot.out

273

12.17.Conjunto Mandelbrot con suavizacin de color interna y externa, generado con la aplicacin XaoS
12.18.Fractal de ejercicio

. . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

12.19.Fractal de otro ejercicio

274
276

. . . . . . . . . . . . . . . . . . . . . . . . . . .

276

14.1. Representacin de una clase . . . . . . . . . . . . . . . . . . . . . . . . .

283

14.2. Diagrama de clase ocultando sus atributos y operaciones . . . . . . . . .

284

14.3. Relaciones bidireccionales entre clases

. . . . . . . . . . . . . . . . . . .

285

. . . . . . . . . . . . . . . . . . . . .

286

14.4. Otros tipos de relacin entre clases

14.5. Representacin de una instancia de una clase

. . . . . . . . . . . . . . .

287

A.1. Modelo de diseo Modelo-Vista-Controlador . . . . . . . . . . . . . . . .

291

19

ndice de guras

20

Parte I

Gracacin por computadora

21

1 Introduccin a SDL y PyGAME


SDL, Simple DirectMedia Layer, es una biblioteca de funciones multimedia programada
en lenguaje C estndar, disponible para usarse en UNIX / GNU/Linux, en Windows,
en MacOS y en muchas otras plataformas. Tambin existe una considerable cantidad
de bibliotecas derivadas de ella para otros lenguajes de programacin. Para tener una
descripcin ms completa, rerase al sitio ocial: www.libsdl.org.

1.1.

Instalacin de las bibliotecas para C

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.

1.1.1. Vericar si ya estn instalados los paquetes de desarrollo


SDL es una biblioteca muy utilizada en el mundo de los juegos de computadora y los
emuladores, especialmente si son Software Libre, por lo que es muy probable que en una
instalacin tpica de una distribucin de GNU/Linux para escritorio, ya estn instalados
los paquetes de ejecucin. As que es probable, que nicamente sea necesario instalar los
paquetes de desarrollo.
Para averiguar si ya se tienen instalados los paquetes de desarrollo, basta con ejecutar
la siguiente instruccin:

$ sdl-config --version
Si devuelve algo como:

1.2.12

23

1 Introduccin a SDL y PyGAME


signica que la biblioteca de desarrollo est instalada y el texto devuelto indica la versin
de la biblioteca en esa mquina. Pero si devuelve algo como:

bash: sdl-config: orden no encontrada


signica que no est instalada la biblioteca de desarrollo, aunque tal vez s la de ejecucin.

1.1.2. Diseo modular de SDL


SDL consiste de una nica biblioteca de funciones base, pero existen otras bibliotecas de
funciones adicionales, desarrolladas por la amplia comunidad de usuarios/programadores
de SDL.
Aunque la biblioteca base incluye lo necesario para desarrollar aplicaciones de todo
tipo, conviene instalar adems esas otras bibliotecas porque extienden su funcionalidad,
permitiendo desarrollar aplicaciones ms rpidamente.
Por ejemplo, se recomienda instalar las siguientes bibliotecas:

sdl-gfx

Incluye funciones de dibujo de primitivas grcas

1 huecas y rellenas, con o sin

antialiasing . Se le conoce tambin como SDL Graphics Efects Primitives.

sdl-image

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
puede abrir archivos

*.bmp).

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
biblioteca de renderizacin de texto de GTK+).

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.

1.1.3. Instalacin en distribuciones basadas en Debian


Para instalar todos los paquetes de desarrollo, ejecutar la instruccin:

# apt-get install libsdl*-dev


1
2

Ver captulo 3
http://es.wikipedia.org/wiki/Antialiasing
http://en.wikipedia.org/wiki/Anti-aliasing

24

1.1 Instalacin de las bibliotecas para C


Con esto se instalarn todos los paquetes de desarrollo (development ) que a su vez,
tienen como dependencias a los paquetes de ejecucin.
Si no se desean instalar todos los paquetes de desarrollo, se pueden consultar todos los
paquetes que contengan el texto  libsdl con el comando:

$ aptitude search libsdl

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.

1.1.4. Instalacin en openSuSE


La distribucin OpenSuSE tiene una herramienta de conguracin global llamada YaST
que incluye una interfaz para instalar paquetes. Esta aplicacin tiene interface de texto
con ncurses, pero tiene una hermana con interfaz de alto nivel, YaST2.
Para instalar SDL, invocar el mdulo de instalacin:

# yast2 --install &


o

# yast --install
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

libSDL*-devel

e instalarlos.

Tambin se pueden instalar los paquetes desde la lnea de comandos. Para hacerlo, se

zypper:
# zypper install libSDL*-devel
utiliza el comando

Si no se desean instalar todos los paquetes de desarrollo, se pueden consultar todos los
paquetes que contengan el texto  libSDL con el comando:

$ zypper search libSDL

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.

1.1.5. Instalacin en Fedora y derivados de RedHat


Para instalar todos los paquetes de desarrollo, ejecutar la instruccin:

# yum install SDL*-devel

25

1 Introduccin a SDL y PyGAME


Si no se desean instalar todos los paquetes de desarrollo, se pueden consultar todos los
paquetes que contengan el texto  libsdl con el comando:

$ yum search libsdl

e instalar slo aquellos que se requieran.

1.1.6. Otras distribuciones


Para lograr instalar con xito los paquetes de desarrollo de SDL (y sus dependencias),
se recomienda usar las interfaces (grcas o de linea de comandos) para la instalacin
desde los repositorios de cada distribucin. Alternativamente se pueden descargar los
instaladores del sitio de SDL www.libsdl.org en diversos formatos, y adems, tambin
se pueden descargar los fuentes para compilar los paquetes, in situ.

1.2.

Compilacin de los programas que usan SDL en C

Si el programa est en un slo archivo fuente (y no utiliza mdulos adicionales al base),


se procede de la siguietne manera:

$ gcc archivo_fuente.c [-o archivo_objeto] $(sdl-config --cflags --libs)


o

$ gcc archivo_fuente.c [-o archivo_objeto] sdl-config --cflags --libs


Valga la aclaracin que la instruccin

$ sdl-config --cflags --libs


imprime los parmetros necesarios para que el programa

gcc

pueda enlazar correcta-

mente el cdigo mquina del programa compilado.

1.3.

Ejemplos bsicos en SDL

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.3.1. Inicializacin bsica del sistema de video (el


SDL):

1

/* c01 / ejemplo -01. c

26

Listing 1.1: Hola Mundo en SDL

Hola Mundo de

1.3 Ejemplos bsicos en SDL


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
46
47
48
49

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

1 Introduccin a SDL y PyGAME


50
51
52
53
54

// y luego apagar el sistema de SDL con ' SDL_Quit () ',


// en este caso , invocada automticamente con el return .
return 0;

1.3.2. Inicializacin de los subsistemas


SDL cuenta de forma nativa con 5 subsistemas. Las constantes para identicarlos son:

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

Listing 1.2: Inicializacin de subsistemas en SDL

/* 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 ) ;

9
10
11
12
13
14
15
16
17
18
19
20
21
22

// inicializar el subsistema de palanca de mandos :


SDL_InitSubSystem ( SDL_INIT_JOYSTICK ) ;

23
24
25

// apaga los subsistemas de video y de audio

26

28

1.3 Ejemplos bsicos en SDL


SDL_QuitSubSystem ( SDL_INIT_VIDEO | SDL_INIT_CDROM ) ;

27
28

if ( SDL_WasInit ( SDL_INIT_VIDEO ) )
printf ( " El video est encendido \ n " ) ;
else
printf ( " El video est apagado \ n " ) ;

29
30
31
32
33

if ( SDL_WasInit ( SDL_INIT_CDROM ) )
printf ( " El cdrom est encendido \ n " ) ;
else
printf ( " El cdrom est apagado \ n " ) ;

34
35
36
37
38

if ( SDL_WasInit ( SDL_INIT_AUDIO ) )
printf ( " El audio est encendido \ n " ) ;
else
printf ( " El audio est apagado \ n " ) ;

39
40
41
42
43

if ( SDL_WasInit ( SDL_INIT_JOYSTICK ) )
printf ( " El joystick est encendido \ n " ) ;
else
printf ( " El joystick est apagado \ n " ) ;

44
45
46
47
48
49
50
51

// Apagar todos los subsistemas de SDL automticamente


return 0;

1.3.3. Modos de video


Los modos de video disponibles son:

SDL_SWSURFACE

Usa la memoria de sistema

SDL_HWSURFACE

Usa la memoria de video

SDL_DOUBLEBUF

Activa doble buer en la memoria de video

SDL_FULLSCREEN
SDL_OPENGL

Crea una supercie renderizable con opengl.

SDL_RESIZABLE
SDL_NOFRAME

1
2
3
4
5

Crea una supercie de dibujo que ocupa toda la pantalla.

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

1 Introduccin a SDL y PyGAME


6
7
8
9
10

# define ALTO

400

int main ( void ) {


SDL_Surface * pantalla = NULL ;
SDL_Surface * imagen = NULL ;

11

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 ) ;

12
13
14
15
16
17

/* 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
* */

18
19
20
21
22
23
24
25
26
27

// Ventana normal de tamao fijo :


pantalla = SDL_SetVideoMode ( ANCHO , ALTO , 0 ,

28
29
30

// Ventana normal de tamao variable :


// pantalla = SDL_SetVideoMode ( ANCHO , ALTO , 0 ,
SDL_RESIZABLE ) ;

31
32
33

SDL_SWSURFACE ) ;
SDL_SWSURFACE |

// Ventana normal de tamao fijo maximizada ( tamao igual a pantalla


completa )
// pantalla = SDL_SetVideoMode (
0,
0 , 0 , SDL_SWSURFACE ) ;

34
35
36
37
38
39
40
41
42
43
44
45

// Ventana sin borde de tamao fijo


// pantalla = SDL_SetVideoMode ( ANCHO , ALTO , 0 ,
SDL_NOFRAME ) ;

SDL_SWSURFACE |

// Pantalla completa con resolucin fija


// pantalla = SDL_SetVideoMode ( ANCHO , ALTO , 0 ,
SDL_FULLSCREEN ) ;

SDL_SWSURFACE |

// Pantalla completa con resolucin mxima


// pantalla = SDL_SetVideoMode (
0,
0, 0,
SDL_FULLSCREEN ) ;

SDL_SWSURFACE |

if ( pantalla == NULL ) {
printf ( " Error al inicializar el modo de video : ' %s '\ n " ,
SDL_GetError () ) ;
exit (1) ;

46
47
48

30

1.3 Ejemplos bsicos en SDL


}

49
50

printf ( " Tamao de la pantalla : %dx %d \ n " , pantalla - >w , pantalla - > h ) ;

51
52

// Cargar una imagen :


imagen = SDL_LoadBMP ( " logo_uca . bmp " ) ;
if ( imagen == NULL ) {
printf ( " Error al cargar la imagen : ' %s '\ n " , SDL_GetError () ) ;
exit (1) ;
}

53
54
55
56
57
58
59

// Copiar la imagen al buffer temporal en ' pantalla ':


SDL_BlitSurface ( imagen , NULL , pantalla , NULL ) ;

60
61
62

// Volcar el buffer a la memoria de video :


SDL_Flip ( pantalla ) ;

63
64
65

// Espera un tiempo de 10 segundos


SDL_Delay (10000) ;

66
67
68

// Apaga manualmente los subsistemas de SDL


SDL_Quit () ;

69
70
71
72
73

return 0;

1.3.4. Eventos de ratn



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

1 Introduccin a SDL y PyGAME


18
19
20
21
22
23
24
25
26
27
28

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;

29

if ( SDL_Init ( SDL_INIT_VIDEO ) < 0) {


printf ( " Error al iniciar SDL : %s \ n " , SDL_GetError () ) ;
exit (1) ;
}
atexit ( SDL_Quit ) ;

30
31
32
33
34
35

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) ;
}

36
37
38
39
40
41

SDL_WM_SetCaption ( " Prueba de ratn , mueva el ratn dentro de la


ventana " , NULL ) ;

42
43

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 ;

44
45
46
47
48
49
50
51
52
53
54
55
56
57

case SDL_MOUSEBUTTONUP :
printf ( " Botn de ratn ' %s ' liberado en ( %d, %d ) \ n " ,
nombreBoton ( evento . button . button ) , evento . button .
x , evento . button . y ) ;
break ;

58
59
60
61
62

case SDL_MOUSEMOTION :

63

32

1.3 Ejemplos bsicos en SDL


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 ;

64
65
66
67
68
69
70
71
72

73
74
75
76

case SDL_QUIT :
corriendo = 0;
break ;

return 0;

1.3.5. Eventos de teclado



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

1 Introduccin a SDL y PyGAME


29
30
31
32

}
/*

33
34
35
36
37
38
39
40
41
42
43
44
45
46

if ( modificador & KMOD_RMETA ) printf ( " RMETA " ) ;

Consultar el archivo :
/ usr / include / SDL / SDL_keysym . h
All se encuentra la declaracin de las teclas
en forma de constantes

*/
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;

47

if ( SDL_Init ( SDL_INIT_VIDEO ) < 0 ) {


fprintf ( stderr , " No se puede iniciar SDL : %s \ n" , SDL_GetError () ) ;
exit (1) ;
}
atexit ( SDL_Quit ) ;

48
49
50
51
52
53

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) ;
}

54
55
56
57
58
59
60

SDL_WM_SetCaption ( " Prueba de teclado , presione las teclas " , NULL ) ;

61
62
63

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 ;

64
65
66
67
68
69
70
71
72
73
74
75
76

77

34

case SDL_QUIT :
corriendo = 0;
break ;

1.3 Ejemplos bsicos en SDL


78

79
80
81
82

return 0;

1.3.6. Redimensionamiento de la ventana


Listing 1.6: Redimensionamiento


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

/* 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

1 Introduccin a SDL y PyGAME


// 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 ;

36
37

38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

54
55
56
57

case SDL_QUIT :
corriendo = 0;
break ;

return 0;

1.3.7. Facilitar la compilacin


Para facilitar el proceso de compilacin para programas que usen SDL que consistan de
un solo archivo fuente (sin contar el archivo de cabecera), puede crearse en el mismo
directorio el siguiente archivo


1
2
3
4
5
6
7
8
9
10

Makefile:

Listing 1.7: Makele para programas SDL de un archivo fuente

# 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 ')

11

36

1.3 Ejemplos bsicos en SDL


12
13

# 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

make

de los sistemas

basados en Unix. De tal manera que slo haya que ejecutar la siguiente instruccin para
compilar un programa fuente:

$ make ejemplo-01
Con esta instruccin y el archivo mencionado, se buscar el archivo
compilar en el archivo

ejemplo-01.

ejemplo-01.c

y se

1.3.8. Compilacin desde varios archivos fuente


Lo anterior no aplica para un programa que consista de varios archivos fuente (sin contar
sus archivos de cabecera). Sin embargo, siempre nos podemos apoyar en el comando

make

para automatizar el proceso de compilacin. Veamos un ejemplo sencillo (se recomienda


echarle un vistazo al captulo 13 en la pgina 279 antes de continuar aqu):


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

Listing 1.8: Archivo de cabecera de otro archivo fuente

Listing 1.9: Otro archivo fuente

/* 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

Listing 1.10: Archivo de cabecera principal

/* c01 / ejemplo -07/ primitivas . h


* */
# include < SDL / SDL .h >

37

1 Introduccin a SDL y PyGAME


4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

/*
* 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

Listing 1.11: Cdigo fuente de primitivas grcas

/* 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 ;

21

if (( x > surface - > w ) ||( y > surface - > h ) ||( x <0) ||( y <0) ) return ;

22
23

switch ( bpp ) {
case 1:
* p = color ;
break ;

24
25
26
27
28

case 2:
*( Uint16 *) p = color ;

29
30

38

1.3 Ejemplos bsicos en SDL


break ;

31
32

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 ;

33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

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

case 4:
*( Uint32 *) p = color ;
break ;
}


Listing 1.12: Cdigo principal del ejemplo

/* 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

1 Introduccin a SDL y PyGAME


}

26
27

// 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) ;

28
29
30
31
32

SDL_WM_SetCaption ( " Cdigo distribuido " , NULL ) ;

33
34

// 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 ) ;

35
36
37
38
39
40
41
42
43
44
45
46
47

// Volcar el buffer en la pantalla


SDL_Flip ( pantalla ) ;

48
49
50

// llamar una funcin que se encuentra en otro archivo de cdigo


funcionOtro (10 , stdout ) ;

51
52
53

while ( corriendo ) {
while ( SDL_PollEvent (& evento ) ) {
switch ( evento . type ) {
case SDL_VIDEORESIZE : {
ANCHO = evento . resize . w ;
ALTO = evento . resize . h ;

54
55
56
57
58
59
60

// 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) ;
}

61
62
63
64
65
66
67
68
69

// La pantalla nueva aparece en blanco


// o mejor dicho , en negro ,
// por lo que hay que dibujar de nuevo :
rect1 . x = 0;

70
71
72
73

40

1.3 Ejemplos bsicos en SDL


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 ) ;

74
75
76
77
78
79
80
81
82
83

// Vuelca el buffer en la pantalla :


SDL_Flip ( pantalla ) ;

84
85

}
break ;

86
87
88
89
90
91
92
93

94
95
96
97

case SDL_QUIT :
corriendo = 0;
break ;

return 0;

Finalmente, para poder compilar fcilmente todo este cdigo, requerimos de un archivo

Makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

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

20

41

1 Introduccin a SDL y PyGAME


21
22
23
24
25
26
27
28
29
30
31
32
33

# 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

ponerPixel implementada en el archivo 1.11 es la forma tpica de encender

o colorear pixeles en SDL.

1.4.

Dibujo de primitivas grcas con SDL

Desafortunadamente, la biblioteca base de SDL no incluye funciones para encender pixeles ni para dibujar lneas, rectngulos, crculos, polgonos, etc (es decir, primitivas gr-

cas ). 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

sdl-gfx

mencionada en la

subseccin 1.1.2 en la pgina 24.


Su uso se ilustra en el siguiente programa:
Listing 1.14: Ejemplo de uso de gfxPrimitives


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

1.4 Dibujo de primitivas grcas con SDL


12
13

/*

14
15
16
17
18
19
20

Adems , utiliza las transparencias por defecto ,


siempre hay que especificar la opacidad del color .

21
22
23
24
25
26
27
28
29
30
31
32
33

*/
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
}
/*

34
35
36
37
38
39
40
41
42
43
44

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 ) ' */

45

// 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 ) ;

46
47
48
49

// Lineas ( notar la diferencia visual entre ambas ) :


lineColor ( pantalla , 0 ,0 , ANCHO , ALTO , blanco ) ;
aalineColor ( pantalla , 0 , ALTO , ANCHO , 0 , blanco ) ;

50
51
52
53

// Lnea horizontal amarilla


hlineColor ( pantalla , 0 , ANCHO , ALTO /2 , color (255 , 255 , 0) ) ;

54
55
56
57
58
59

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 .

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

1 Introduccin a SDL y PyGAME


60
61
62
63
64

int main ( int argc , char * argv []) {


SDL_Surface * pantalla = NULL ;
SDL_Event evento ;
int corriendo = 1;

65

if ( SDL_Init ( SDL_INIT_VIDEO ) < 0) {


printf ( " Error al iniciar SDL : %s \ n " , SDL_GetError () ) ;
exit (1) ;
}
atexit ( SDL_Quit ) ;

66
67
68
69
70
71

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) ;
}

72
73
74
75
76
77

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) ;

78
79
80
81
82

SDL_WM_SetCaption ( " Ejemplo de gfx " , NULL ) ;

83
84

// Hacer primer dibujo


dibujar ( pantalla ) ;

85
86
87

// Volcar el buffer en la pantalla


SDL_Flip ( pantalla ) ;

88
89
90

// Esperar que se cierre la ventana


while ( corriendo ) {
while ( SDL_PollEvent (& evento ) ) {
switch ( evento . type ) {

91
92
93
94
95
96
97
98
99
100

101
102
103
104

// AS NO

case SDL_QUIT :
corriendo = 0;
break ;

return 0;

44


Listing 1.15: Makele necesario para usar gfxPrimitives

1.5 Instalacin de PyGAME


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# c01 / ejemplo -08/ Makefile


# Ntese que se incluye un parmetro adicional por gfx :
LINEA = $ ( shell sdl - config -- cflags -- libs ) - lSDL_gfx
RM

. PHONY : limpiar
. PHONY : limpiartodo
. PHONY : all
PROG = gfx
OBJ = primitivas . o
all : $ ( PROG ) limpiar
$ ( PROG ) : $( OBJ )
gcc -o $ ( PROG ) $ ( OBJ ) $ ( LINEA )
limpiar :

22
23
24
25
26

= / bin / rm -f

$ ( RM ) *~ $ ( OBJ )

limpiartodo :
make limpiar
$ ( RM ) $ ( PROG )

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-

pygame, por lo que basta con instalarlo, en Debian, con:

# apt-get install python-pygame


o en OpenSuSE con:

# yast -i python-pygame

(para la interfaz de texto)

# yast2 -i python-pygame

(para la interfaz grca de alto nivel)

# zypper install python-pygame

(en lnea de comandos)

En OpenSuse, existe tambin el paquete

python-pygame-doc que instala documentacin

y ejemplos de PyGAME.

45

1 Introduccin a SDL y PyGAME


En distribuciones Fedora y derivados de RedHat, el paquete se llama simplemente

pygame y se puede instalar con:

# yum install pygame


Al igual que con el caso de las bibliotecas de SDL, se recomienda usar las interfaces
(grcas o de linea de comandos) para la instalacin desde los repositorios de cada
distribucin.
Si esto no es posible o simplemente no se desea, se pueden descargar los instaladores
del sitio de PyGAME www.pygame.org en diversos formatos y diferentes versiones, y
adems, tambin se pueden descargar los archivos fuentes

4 para compilar los paquetes,

in situ.

1.6.

Ejemplos bsicos en Python

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.6.1. Inicializacin bsica del sistema de video (el


pygame):
Listing 1.16: Hola Mundo en Pygame


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

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

pygame estn programados en lenguaje C estndar.

1.6 Ejemplos bsicos en Python


19
20
21
22
23
24
25
26
27
28
29
30
31

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 .
# El cerrado de los subsistemas de pygame es automtico

1.6.2. Inicializacin de los subsistemas


Pygame cuenta con 5 subsistemas nativos. Estos son:

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

1 Introduccin a SDL y PyGAME


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 " )

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

' ' ' 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

1.6.3. Modos de video


Los modos de video disponibles son:

pygame.FULLSCREEN

Crea una supercie de dibujo que ocupa toda la pantalla.

pygame.DOUBLEBUF Activa doble buer en la memoria de video, recomendado con HWSURFACE


y con OPENGL.
pygame.HWSURFACE

Activa aceleracin de hardware, disponible slo con

pantalla completa).

48

FULLSCREEN (en

1.6 Ejemplos bsicos en Python


pygame.OPENGL

Crea una supercie renderizable con opengl.

pygame.RESIZABLE
pygame.NOFRAME

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

Crea una ventana de dibujo sin borde.


Listing 1.18: Modos de video en pygame


1

Crea una ventana de dibujo redimensionable.

# 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 ()

41

49

1 Introduccin a SDL y PyGAME


42
43
44
45
46
47
48
49
50
51
52

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.6.4. Eventos de ratn



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

1.6 Ejemplos bsicos en Python


( evento . buttons [0] and " s " or " no " ) ,
( evento . buttons [1] and " s " or " no " ) ,
( evento . buttons [2] and " s " or " no " ) ) )

31
32
33
34
35
36

elif evento . type == pygame . QUIT :


corriendo = False

1.6.5. Eventos de teclado



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

1 Introduccin a SDL y PyGAME


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 )

37
38
39
40
41
42
43
44
45
46
47
48
49
50

elif evento . type == pygame . QUIT :


corriendo = False

1.6.6. Redimensionamiento de la ventana


Listing 1.21: Redimensionamiento


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

# 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 .

26

elif evento . type == pygame . QUIT :

27

52

1.7 Dibujo de primitivas grcas con pygame


28

corriendo = False

1.7.
En

Dibujo de primitivas grcas con pygame

pygame,

pygame.display, que
mdulo es pygame.draw.

existe un mdulo asociado al mdulo

funciones para dibujar primitivas grcas. Este

contiene las

Su uso se ilustra en el siguiente programa:


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

Listing 1.22: Ejemplo de uso de pygame.draw

# 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

1 Introduccin a SDL y PyGAME


bienvenida = fuente . render ( " Bienvenidos a pygame " , False ,
(255 ,255 ,255) , (128 ,128 ,128) )

35
36

# Copiar el texto
pantalla . blit ( saludo , ( ancho /2 , alto /2) )
pantalla . blit ( bienvenida , (3* ancho /4 , alto /4) )

37
38
39
40

# Redibujar el buffer
pygame . display . flip ()

41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

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 ()

61

elif evento . type == pygame . QUIT :


corriendo = False

62
63

1.8.

Ejercicios

1. Construya una aplicacin grca que muestre en pantalla un display de 7 segmentos 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

Figura 1.1: Display de 7 segmentos

55

1 Introduccin a SDL y PyGAME

56

2 Introduccin a la Gracacin por


Computadora
2.1.

Marco conceptual para la gracacin interactiva

En general, para desarrollar aplicaciones que incluyan gracacin interactiva, se deben


atender los siguientes puntos:

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. . .

(a) Una forma es por su ecuacin

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),
esfrica

(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 Introduccin a la Gracacin por Computadora


cuando se necesite. Pero la alteracin de la posicin de la esfera, por ejemplo, ser
ms lento, debido a que habr que cambiar la posicin de cada punto. Considrese en
contraste el cambio de posicin con el segundo caso en el que basta con cambiar tres
variables.
Para que los objetos del mundo real puedan ser representados en la memoria (primaria y/o secundaria) de la computadora, necesitamos primero pensar en algn modelo
matemtico que describa esos objetos. Y adems, en el caso de necesitar representarlos
en la memoria secundaria, transformarlos a un modelo estrictamente lineal.

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

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:
Listing 2.1: Ciclo de ventos


1
2
3
4
5
6
7
8
9
10

hayQueEjecutar = Verdadero
.
.
.
MIENTRAS ( hayQueEjecutar ) {
MIENTRAS ( hayEventos () ) {
evento = siguienteEvento ()
procesarEvento ( evento ) ;
}
}

La estructura de un ciclo de juego suele parecerse al siguiente esquema:


Listing 2.2: Ciclo de juego


1
2
3
4

hayQueEjecutar = Verdadero
.
.
.
1

Hace mucho tiempo para una aplicacin grca podra signicar un par de segundos.

59

2 Introduccin a la Gracacin por Computadora


5
6

MIENTRAS ( hayQueEjecutar ) {
t = ahora () ;

estado = leerEstadoDeDispositivosRelevantes () ;
actualizarObjetosYPosiciones ( estado );
redibujarObjetosVisibles () ;

8
9
10
11
12
13
14
15
16

dt = ahora () - t ;
SI ( dt < TiempoPorCiclo ) {
hacerPausa ( TiempoPorCiclo - dt );
}

2.2.1. Ciclo de eventos con SDL


Como ya se ilustr en los ejemplos de la seccin 1.3, para procesar los eventos de los
dispositivos soportados por SDL (ratn, teclado, joystick y el manejador de ventanas),
se extraen los eventos de una cola de eventos de la aplicacin. Pueden utilizarse las
funciones:

int SDL_PollEvent(SDL_Event *evento); que verica si hay eventos pendientes


de procesar en la cola de eventos y retorna 1 si hay alguno. Retorna 0 si no hay
ninguno en el momento de ser llamada. Siempre retorna inmediatamente.

int SDL_WaitEvent(SDL_Event *evento); que espera indenidamente hasta que


haya otro evento en la cola de eventos y retornando 1 cuando llega. Retorna 0 si
hubo algn error mientras se esperaba el evento. Si ya haba eventos en la cola al
momento de la llamada, retorna inmediatamente.
Ambas colocan en la direccin

evento

el siguiente evento extrado de la cola de eventos

para esta aplicacin.


En el caso de utilizar la primera, el ciclo principal del programa, el ciclo de eventos
debera tener una forma parecida a esta:


1

2
3
4
5
6
7
8
9
10

60

Listing 2.3: Ciclo de ventos con

SDL_Event evento ;
int corriendo = 1;
.
.
.
while ( corriendo ) {
while ( SDL_PollEvent (& evento ) ) {
switch ( evento . type ) {
case SDL_MOUSEBUTTONDOWN :

SDL_PollEvent

2.2 Ciclo de interaccin


.
.
.

11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

break ;
case ...:
.
.
.
case SDL_QUIT :
corriendo = 0;
break ;

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

Listing 2.4: Ciclo de ventos con

SDL_WaitEvent

SDL_Event evento ;
.
.
.
while ( SDL_WaitEvent (& evento ) ) {
switch ( evento . type ) {
case SDL_MOUSEBUTTONDOWN :
.
.
.
break ;
case ...:
.
.
.
case SDL_QUIT :
break ;
}
}

2.2.2. Ciclo de juego con SDL


Para implementar un ciclo de juego con SDL, se dispone de las siguientes funciones:

Uint32 SDL_GetTicks(void);

Devuelve el nmero de milisegundos desde la ini-

cializacin de SDL. Esta funcin es til para el control del tiempo que dura cada

61

2 Introduccin a la Gracacin por Computadora


iteracin del ciclo de juego.

void SDL_Delay(Uint32 milisegundos);

Espera durante una cantidad especi-

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.

Uint8 *SDL_GetKeyState(int *numteclas);

Devuelve el estado instantaneo del

teclado. El estado es representado por un arreglo cuya dimensin se coloca en la


direccin

numteclas si no es NULL.
SDLK_*. Cada casilla

de la forma

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

void SDL_PumpEvents(void);

para actualizar el estado del arreglo. Hay que tener en cuenta que las funciones

SDL_PollEvent

SDL_WaitEvent

llaman internamente a

SDL_PumpEvents,

por lo

que si se utilizan las primeras, no es necesario llamarla manualmente.

SDLMod SDL_GetModState(void);

Devuelve el estado actual de las teclas modi-

cadoras (CTRL, ALT, CAPS-LOCK, etc.). El estado (de tipo


entera que contiene las banderas de la forma

KMOD_*

SDLMod) es una variable

combinadas con disyuncin

a nivel de bits.

Uint8 SDL_GetMouseState(int *x, int *y); Devuelve el estado de los botones


del ratn en una variable entera. El estado contiene las banderas

SDL_BUTTON_(L/M/R)MASK combinadas con disyuncin a nivel de bits. Si las direcciones x y y no son NULL, se almacena all la coordenada del ratn en el momento
de la llamada. Al igual que SDL_GetKeyState, esta funcin requiere una llamada
previa a SDL_PumpEvents, que puede ser ejecutada a travs de SDL_PollEvent o
SDL_WaitEvent.
Uint8 SDL_GetRelativeMouseState(int *dx, int *dy); Devuelve el estado de
los botones del ratn igual que la funcin anterior, pero almacena en dx y dy (si
no son NULL) 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

Listing 2.5: Ciclo de juego con SDL

# 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

2.2 Ciclo de interaccin


.
while ( corriendo ) {
t = SDL_GetTicks () ;

10
11
12
13

SDL_PollEvent (& evento ) ;


if ( evento . type == SDL_QUIT ) {
corriendo = 0;
break ;
}

14
15
16
17
18
19

actualizarPosiciones () ; // revisar colisiones , mover cosas , etc .

20
21

teclas = SDL_GetKeyState ( NULL ) ;


if ( teclas [ SDLK_ESCAPE ]) {
corriendo =0;
break ;
}

22
23
24
25
26
27

if ( teclas [ SDLK_LEFT ]) {...}


.
.
.

28
29
30
31
32

redibujarObjetosVisibles () ;

33
34
35
36
37
38
39

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

Listing 2.6: Juego de tenis para dos jugadores con SDL

// c02 / tenis / tenis . c

/* *
* 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

2 Introduccin a la Gracacin por Computadora


15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# define
# define
# define
# define

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 ;

30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

MARCOS_POR_SEGUNDO 25
FONDO " fondo - trabajar . bmp "
PELOTA " pelotita . bmp "
TITULO " Tenis ! - Partida %d "

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 ;

46

Uint32 t , dt ;
Uint32 TiempoCiclo = ( int ) (1000.0 / MARCOS_POR_SEGUNDO ) ;

47
48
49

int corriendo = 1;
int marcador1 = 0 , marcador2 = 0;
int partida = 1;
char titulo [50];

50
51
52
53
54

/* Inicializacin */
{
srand ( time ( NULL ) ) ;

55
56
57
58

if ( SDL_Init ( SDL_INIT_VIDEO ) < 0 ) {


fprintf ( stderr , " No se puede iniciar SDL : %s \n " , SDL_GetError
() ) ;
exit (1) ;
}
atexit ( SDL_Quit ) ;

59
60
61
62
63

64

2.2 Ciclo de interaccin


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) ) ;

65
66
67
68
69
70
71
72
73
74
75
76
77
78

jugador1 . w = jugador2 . w = 40;


jugador1 . h = jugador2 . h = 10;
pelota . w = imgPelota - > w ; pelota . h = imgPelota - > h ;

79
80
81
82

reiniciarPosiciones () ;

83
84

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 ) ;

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

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

2 Introduccin a la Gracacin por Computadora


pelota . x += velx ;
pelota . y += vely ;

112
113
114

// Rebotes laterales
if ( pelota . x < 0 || pelota . x + pelota . w > ANCHO )
velx *= -1;

115
116
117
118

// 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 ;
}

119
120
121
122
123
124
125
126
127
128
129
130
131
132
133

// 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 ;
}

134
135
136
137
138
139
140

// Movimiento de los jugadores


teclas = SDL_GetKeyState ( NULL ) ;

141
142
143

if ( teclas [ SDLK_ESCAPE ]) {
corriendo =0;
break ;
}

144
145
146
147
148

if ( teclas [ SDLK_LEFT ]) {
jugador2 . x -= INCREMENTO_DESPLAZAMIENTO ;
if ( jugador2 . x < 0) jugador2 . x = 0;
}

149
150
151
152
153

if ( teclas [ SDLK_RIGHT ]) {
jugador2 . x += INCREMENTO_DESPLAZAMIENTO ;
if ( jugador2 . x + jugador2 . w > pantalla - > w )
jugador2 . x = pantalla - >w - jugador2 . w ;
}

154
155
156
157
158
159

66

2.2 Ciclo de interaccin


if ( teclas [ SDLK_a ]) {
jugador1 . x -= INCREMENTO_DESPLAZAMIENTO ;
if ( jugador1 . x < 0) jugador1 . x = 0;
}

160
161
162
163
164
165
166
167
168
169

170
171

/* Redibujar */
{

172
173
174

// Borra la pantalla
SDL_BlitSurface ( imgFondo , NULL , pantalla , NULL ) ;
// SDL_FillRect ( pantalla , NULL , color_fondo ) ;

175
176
177
178

// Dibujo de los jugadores


SDL_FillRect ( pantalla , & jugador1 , color1 );
SDL_FillRect ( pantalla , & jugador2 , color2 );

179
180
181
182

// Dibujo de la bola
SDL_BlitSurface ( imgPelota , NULL , pantalla , & pelota );

183
184
185
186
187

188
189
190
191
192

193
194

196
198

// Vuelca el buffer en la memoria de video


SDL_Flip ( pantalla ) ;

/* Sincronizacin de tiempo */
dt = SDL_GetTicks () - t ;
if ( dt < TiempoCiclo ) SDL_Delay ( TiempoCiclo - dt ) ;

printf ( " \n < < < < < < < < < < < Marcador final : > > > > > > > > > > >\ nJugador 1: %d \
nJugador 2: %d \n \ n " , marcador1 , marcador2 ) ;

195

197

if ( teclas [ SDLK_d ]) {
jugador1 . x += INCREMENTO_DESPLAZAMIENTO ;
if ( jugador1 . x + jugador1 . w > pantalla - > w )
jugador1 . x = pantalla - >w - jugador1 .w ;
}

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.

2.2.3. Ciclo de eventos con pygame


Al igual que SDL, pygame dispone de las funciones para extraer eventos de la cola de
eventos:

67

2 Introduccin a la Gracacin por Computadora


pygame.event.poll()

que verica si hay eventos pendientes de procesar en la

cola de eventos y retorna el primero de ella. Retorna

pygame.NOEVENT

si no hay

ninguno en el momento de ser llamada. Siempre retorna inmediatamente.

pygame.event.wait()

que espera indenidamente hasta que haya otro evento en

la cola de eventos y devuelve el primero, cuando llega. Si ya haba eventos en la


cola al momento de la llamada, retorna inmediatamente.

pygame.event.get() que extrae todos los eventos existentes en la cola de eventos


en ese momento especco.
En el caso de utilizar la primera, el ciclo principal del programa, el ciclo de eventos

debera tener una forma parecida a esta :


Listing 2.7: Ciclo de ventos con


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

pygame.event.poll()

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

En el caso de utilizar la segunda, el ciclo de eventos debera parecerse a :


Listing 2.8: Ciclo de ventos con


1
2
3
4
5
6
7
8

pygame.event.wait()

corriendo = True
.
.
.
while corriendo :
evento = pygame . event . wait ()
if evento . type == pygame . MOUSEBUTTONDOWN :
.
2
3

puede consultarse el archivo


puede consultarse el archivo

68

c02/ejemplo-raton-poll.py
c02/ejemplo-raton-wait.py

2.2 Ciclo de interaccin


.
.

9
10
11
12
13
14
15
16

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

pygame.event.get().

Con esta funcin, el ciclo de eventos debera

tener una forma similar a:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Listing 2.9: Ciclo de ventos con

pygame.event.get()

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

2.2.4. Ciclo de juego con pygame


Para implementar un ciclo de juego con pygame, se dispone de las siguientes funciones:

pygame.time.get_ticks()

Devuelve el nmero de milisegundos desde la inicial-

izacin de pygame.

pygame.time.delay(milisegundos) Realiza una espera activa durante un nmero


de milisegundos especicados. Es bastante preciso ya que usa el procesador durante
ese tiempo en lugar de pasar a inactividad.

pygame.time.wait(milisegundos) Realiza una espera pasiva durante un nmero


de milisegundos especicados. Es ligeramente menos preciso que pygame.time.delay
ya que depende del sistema operativo para ser despertado.

69

2 Introduccin a la Gracacin por Computadora


pygame.key.get_pressed()

Devuelve una secuencia de booleanos representando

el estado (presionado o no) de cada tecla del teclado. Se usan las constantes de

pygame.K_* para indexar la secuencia. Esta funcin requiere


pygame.event.pump, que se realiza automticamente cuando se usan otras funciones del paquete pygame.event, como pygame.event.get,
pygame.event.wait o pygame.event.poll.
tecla de la forma

una llamada previa a

pygame.key.get_mods()

Devuelve un valor entero que representa el estado actu-

al de las teclas modicadoras (CTRL, ALT, CAPS-LOCK, etc.). El estado es una


variable entera que contiene las banderas de la forma

pygame.KMOD_*

combinadas

con disyuncin a nivel de bits.

pygame.mouse.get_pressed()

Devuelve una tupla de tres booleanos indicando

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.

pygame.key.get_pressed, esta funcin requiere una llamada previa


a pygame.event.pump, que se realiza automticamente cuando se usan las otras
funciones del paquete pygame.event.

Al igual que

pygame.mouse.get_pos()

Devuelve una tupla de la forma

(x,y)

con las coorde-

nadas del cursor (dentro de la ventana).

pygame.mouse.get_rel() Devuelve una tupla de la forma (dx,dy) con la cantidad


de movimiento del cursor desde la ltima llamada a esta misma funcin.
La plantilla para un ciclo de eventos con pygame podra ser:


1
2
3
4
5
6
7
8
9

Listing 2.10: Ciclo de juego con pygame

MARCOS_POR_SEGUNDO = 20
t =0; dt =0
TiempoCiclo = int (1000.0 / MARCOS_POR_SEGUNDO ) ;
corriendo = True
.
.
.
while corriendo :
t = pygame . time . get_ticks ()

10

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

11
12
13
14
15
16
17
18

actualizarPosiciones () # revisar colisiones , mover cosas , etc .

19
20

70

2.2 Ciclo de interaccin


teclas = pygame . key . get_pressed ()
if teclas [ pygame . K_ESCAPE ]:
corriendo = False
break

21
22
23
24
25

if teclas [ pygame . K_LEFT ]: ...


.
.
.

26
27
28
29
30

redibujarObjetosVisibles ()

31
32
33
34
35

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:
Listing 2.11: Juego de tenis para dos jugadores 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

2 Introduccin a la Gracacin por Computadora


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

# 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 ()

64

# Actualizacin : ######################
for evento in pygame . event . get () :
if evento . type == pygame . QUIT :
pygame . display . quit ()
corriendo = False
break

65
66
67
68
69
70
71

if not corriendo :
break
# Movimiento de la pelota
pelota . x += vel [ X ];
pelota . y += vel [ Y ];

72
73
74
75
76
77

72

2.2 Ciclo de interaccin


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
111
112
113
114
115
116
117
118
119
120
121
122
123
124

# 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

125
126
127

# Redibujar : #############################

73

2 Introduccin a la Gracacin por Computadora


128

# Borra la pantalla
pantalla . blit ( imgFondo , (0 ,0) )
# pantalla . fill ( color_fondo )

129
130
131
132

# Dibujo de los jugadores


pantalla . fill ( color1 , jugador1 )
pantalla . fill ( color2 , jugador2 )

133
134
135
136

# Dibujo de la bola
pantalla . blit ( imgPelota , pelota )

137
138
139

# Vuelca el buffer en la memoria de video


pygame . display . flip ()

140
141
142

# Sincronizacin de tiempo ###################


dt = pygame . time . get_ticks () - t
if dt < TiempoCiclo : pygame . time . delay ( TiempoCiclo - dt )

143
144
145
146
147

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.

Tipos de representacin de grcos

Fundamentalmente existen dos formas de representar y almacenar objetos grcos en la


memoria de una computadora:

2.3.1. Grcos de barrido o raster


Esta representacin establece la manipulacin directa de los pixeles que conforman las
imgenes. Estos objetos grcos son conocidos tpicamente como mapas de bits.
Muchas aplicaciones utilizan este esquema, principalmente usadas para la manipulacin
de imgenes a nivel de pixel.
Algunas aplicaciones tpicas que funcionan as son aquellas tipo Paint, aplicaciones de
retoque de fotos como Picasa de Google, y otras ms conocidas como PhotoShop, GIMP,
etc.
Los formatos tpicos son los .bmp, .gif, .jpg, .png, etc.

74

2.4 Paletas de colores

2.3.2. Grcos vectoriales


Esta representacin establece la manipulacin de las caractersticas descriptivas de los
objetos. Por ejemplo: Se almacenan los puntos de inicio y n de un segmento de recta
y su ancho, en lugar de los pixeles que la conformaran; Se almacena el punto de la
esquina superior izquierda de un cuadrado y su ancho y alto, en lugar de almancenar
los pixeles de sus cuatro lados; Se almacena el centro y radio de un crculo y el grueso
de su circunferencia, en lugar de almacenar los pixeles de toda la circunferencia; etc.
Las aplicaciones que utilizan este esquema generalmente estn orientados a la ingeniera y
en general al diseo asistido por computadora (Computer-Aided Design, CAD). Algunas
aplicaciones tpicas que funcionan as son: AutoCAD de AutoDesk, Google SketchUp,
OO.org Draw, Inkscape, QCad, Dia, etc.
Los formatos especcos varan mucho dependiendo de la aplicacin, pero algunos ms
conocidos son los .dia, .svg, .dwg, .dxf, .odg, etc.

2.3.3. Representacin hbrida


Existen, sin embargo, aplicaciones con funcionalidades hbridas. Por ejemplo, en aplicaciones tpicamente de barrido como GIMP (o Photoshop), el texto puede almacenarse
como imagen, pero tambin como una cadena de caracteres inmersos en un rectngulo
descrito vectorialmente, dentro del rea de trabajo.
As mismo, en aplicaciones primariamente vectoriales como OO.org Draw, se pueden
insertar archivos de imgen de barrido (png, gif, jpg) y cambiarles, por ejemplo, el
contraste a dicha imgen.

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

00H

28 = 256

colores. De estos, hay que especicar, arbitrariamente,

sea negro, el

01H

sea blanco,. . ., el

09H

sea azul, el

0AH

sea verde,

etc.

75

2 Introduccin a la Gracacin por Computadora


Esa asignacin arbitraria puede realizarse con ayuda de la biblioteca grca a usar
(generalmente traen paletas predenidas) o directamente con cdigo ensamblador, en
funcin de las capacidades del dispositivo de gracacin (monitor, impresor, etc.).

2.5.

Paletas estndares actuales

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

76

http://es.wikipedia.org/wiki/Big-endian

http:

2.5 Paletas estndares actuales

Figura 2.1: Modelo de color RGB

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 .

se les llama sistemas de impresin de cuatro tintas

77

2 Introduccin a la Gracacin por Computadora

Figura 2.2: Modelo de color CMY(K)

2.6.

Espacios de Colores (gamuts)

En la teora del color, el gamut de un dispositivo o proceso usado para la creacin de un

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-

saturacin que se pueden representar mediante el dispositivo o proceso en cuestin. (ver


gura 2.3).
El color visto en el monitor de una computadora es diferente al color del mismo objeto
en una impresin en papel, pues los modelos CMYK y RGB tienen diferentes gamuts.
Por ejemplo, el azul puro (rgb: 0,0,100 %) es imposible de reproducir en CMYK. El
equivalente ms cercano en CMYK es un tono azulviolceo.
En general, en los materiales impresos, las combinaciones de luz RGB no pueden ser
reproducidas directamente, por lo que las imgenes generadas en los ordenadores, cuando
se usa un programa de edicin, dibujo vectorial, o retoque fotogrco se debe convertir
a su equivalente en el modelo CMYK que es el adecuado cuando se usa un dispositivo
que usa tintas, como las impresoras o los ploters.

78

2.7 Ejercicios

Figura 2.3: Plano Matiz-Saturacin de la luz visible

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

clic derecho+ctrl,

clic izquierdo+shift,

etc.

79

2 Introduccin a la Gracacin por Computadora

Figura 2.4: Comparacin del Gamut de RGB y CMYK

80

3 Discretizacin de Primitivas Grcas


Antes de iniciar este captulo, vale la pena aclarar el concepto de Primitiva Grca: Se
reere a las guras bsicas de dibujo vectorial, a partir de las cuales se realizan todas
las dems. Estas guras bsicas son los puntos, los segmentos de rectas, crculos, elipses
y polgonos.
Por otro lado, antes de comenzar con este captulo, se sugiere hacer un repaso de los temas
de geometra analtica bsica. Aqu slo se mencionarn las formas de las ecuaciones
usadas en el resto del libro, sin describirlas formalmente ya que se asume que el lector
ha aprobado algn curso bsico de geometra analtica vectorial.

3.1.

Recordatorio bsico

Recurdese la ecuacin pendiente-intersecto de las lneas rectas:

y = mx + B

(3.1)

Recurdese la ecuacin punto-pendiente de las lneas rectas:

y y0 = m(x x0 )

(3.2)

La equacin dos puntos de las lneas rectas no verticales:


y y0 =

y1 y0
x1 x0


(x x0 )

(3.3)

Tambin se dispone de la ecuacin cannica o implcita de las lneas rectas:

ax + by + c = 0

(3.4)

Recurdese la ecuacin paramtrica de las circunferencias centradas en un punto dado:

(x x0 )2 + (y y0 )2 = R2

(3.5)

81

3 Discretizacin de Primitivas Grcas

Figura 3.1: Representacin abstracta de nueve pixeles

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)

de las pantallas, hasta la

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 Algoritmo incremental bsico

3.3.

Algoritmo incremental bsico

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 ),

Lo primero a realizar es la denicin del modelo: Sea

al

(x1, y1 ).

yi = mxi + B

la ecuacin

que dene la linea, y los pixeles a encender (o a colorear) sern los pares ordenados:

(xi , redondear (yi )).

Evidentemente,

m=

y1 y0
x1 x0 y

B = y0

y1 y0
x1 x0

x0 .

Replanteando el problema en trminos de un proceso iterativo, consideramos que

xi = x

xi+1

para iterar sobre el eje horizontal, de izquierda a derecha. Entonces podemos

plantear lo siguiente:

yi+1 = mxi+1 + B
= m(xi + x) + B
= mxi + mx + B
= mx + (mxi + B)
yi+1 = yi + mx
y si asumimos que

x = 1 ya que avanzamos de columna en columna de pixeles, entonces:


yi+1 = yi + m

(3.6)

xi+i = 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

m,

1 m 1.

se puede usar simetra para modicar el algoritmo.

Esta es su especicacin en lenguaje c:


1
2
3
4
5
6
7
8
9
10
11

Listing 3.1: Algoritmo Analizador Diferencial Digital

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

3 Discretizacin de Primitivas Grcas


12
13
14

y += m ;

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.

3.4.

Algoritmo de lnea de punto medio

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.

El problema consiste en dibujar un segmento de recta, desde el pixel inferior izquierdo

(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

y de

N E.

El punto

E , y el pixel que est arriba

es el punto medio entre los centros

N E.
P , en las coordenadas (x0 , y0 ) y ahora
N E . Eso lo hacemos averiguando si la recta ideal
de E o de N E .

Acabamos de marcar el pixel en cuestin, el pixel


tenemos que elegir entre el pixel

y el

pasa ms cerca del centro geomtrico

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
Si el punto medio

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.

Lo que se necesita entonces, es una manera de averiguar de qu lado de la lnea est

M . Para ello se
ax + by + c = 0.

usar la siguiente ecuacin de la lnea recta, la ecuacin implcita:

Con ella, podemos construir la siguiente funcin:

84

3.4 Algoritmo de lnea de punto medio

Figura 3.2: Justicacin del algoritmo de lnea de punto medio bsico - 1

Figura 3.3: Justicacin del algoritmo de lnea de punto medio bsico - 2

85

3 Discretizacin de Primitivas Grcas

F (x, y) = ax + by + c
Esta tiene la siguiente caracterstica: El valor de

(3.7)

es cero para los puntos que pertenecen

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

b,

el coeciente de

sea negativo.
. . . . . . . . . . . . . . F (x, y)
Entonces, tenemos

< 0 = (x, y) . . . . . . . . . . . . . . F (x, y) > 0 = (x, y) . . . . . . . . . . . . . .



1
que evaluar el signo de F (M ) = F xp + 1, yp +
2 para saber si M

est arriba o abajo de la recta ideal.


Para ordenar un poco el panorama, denamos una nueva variable, nuestra variable de
decisin:





1
1
= a (xp + 1) + b yp +
+c
d = F xp + 1, yp +
2
2

(3.8)

d > 0,

se elige el pixel

Con esta variable, podemos establecer el siguiente convenio: Si

N E;

si

d 0,

se elige el pixel

convencin, eligimos

(si

d = 0,

podramos elegir cualquiera, pero por

E ).

Una vez tomada la decisin y marcado el pixel, se pasa al siguiente punto y se hace lo


dnuevo = F (ME ) = F (xp + 1) + 1, yp + 12 si se eligi el pixel E , y
= F (MN E ) = F (xp + 1) + 1, (yp + 1) + 12 si se eligi el pixel N E .

mismo.

dnuevo

Ahora bien, desconocemos los valores de

a, b

c.

Qu hacemos?. Hagamos una pausa

y analicemos el siguiente fenmeno:

Sea

P (x) = mx + B

y sea

xi+1 = xi + 1

(ya que consideramos que los sucesivos

son las coordenadas de pixeles). Entonces:

P (xi+1 ) = mxi+1 + B
= m(xi + 1) + B
= (mxi + B) + m
P (xi+1 ) = P (xi ) + m
P (xi+1 ) P (xi ) = m
Ahora veamoslo aplicado a nuestro problema de
En el caso de elegir el pixel

86

E:

(ver la ecuacin 3.7):

xi

3.4 Algoritmo de lnea de punto medio

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

N E

respectivamente.

(equivale al valor

dviejo

del primer

paso):

dinicial

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

Se deben conseguir los parmetros

y=

y
x x

+ B,

b (c

no es necesario). Para ello, partimos de

as:

87

3 Discretizacin de Primitivas Grcas

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

b resulta negativo. Note adems que el valor de B

es desconocido

(se puede calcular, porque conocemos al menos dos puntos de la recta), pero como no
necesitamos

c,

no representa ningn problema para nuestro propsito.

Tenemos entonces:

E =

F (xi+1 , yi ) F (xi , yi ) = a

= y

NE = F (xi+1 , yi+1 ) F (xi , yi ) = a + b = y x


1
dinicial =
a + 12 b
= y x
2
Pero esto nos fuerza a usar coma otante slo por el valor inicial de

d.

Todos los dems

valores sucesivos son enteros...

Entonces, replanteamos la ecuacin inicial, de la siguiente manera:

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:

88

(3.9)

3.4 Algoritmo de lnea de punto medio

a = 2y
b = 2x
c = 2x B
1

y volvemos a calcular los valores que nos interesan . Llegamos a:

F (xi+1 , yi ) F (xi , yi ) = a

E =

= 2y

NE = F (xi+1 , yi+1 ) F (xi , yi ) = a + b = 2y 2x


a + 21 b

dinicial =

= 2y x

2 el problema del valor inicial otante para

Con esto hemos resuelto

d.

Ahora, el algoritmo usa exclusivamente artimtica entera, sin el costoso tiempo para
hacer operaciones en coma otante.
El cdigo en lenguaje C es:
Listing 3.2: Algoritmo de lnea de punto medio en la mitad de un cuadrante


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

sigue siendo negativo

89

3 Discretizacin de Primitivas Grcas


23
24
25

marcarPixel (x , y ) ;

3.4.1. Simetra del algoritmo de lnea de punto medio


El problema de generalizar el algoritmo de punto medio para pendientes arbitrarias tiene
dos componentes:

(a) permitir pendientes mayores que 1, y (b) poder hacer el recorrido

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

o sobre

y.

y .

En base

As:

Listing 3.3: Algoritmo de lnea de punto medio de un cuadrante (o dos mitades de cuad-


1

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 ;

3
4
5
6

// 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 ;

7
8
9

// 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;
}

10
11
12
13
14
15
16
17
18
19
20

// 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 ;
}

21
22
23
24
25
26
27
28
29
30

90

3.4 Algoritmo de lnea de punto medio


31
32
33

else

34
35
36
37
38
39
40
41
42
43
44
45

x ++;
marcarPixel (x , y );

while ( y < y1 ) { // Iterar a travs de y


if (d <=0)
d += delta_E ;
else {
x ++;
d += delta_NE ;
}
y ++;
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 :

d = F xp 1, yp

2y

O =
SO
0
dinicial

1
2



1
= a (xp 1) + b yp
+c
2
= E

2y + 2x

= N E

2y + x

= dinicial

Lo que signica que el convenio en este caso es: Si


se elige el pixel

d0 0,

se elige el pixel

O;

si

d0 < 0,

SO.
4

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

y todos sus incrementos posibles son del mismo valor absoluto,

pero de signo opuesto, y el criterio se basa en el signo de dicha variable, signica que se
pueden sustituir los valores

O , SO

d0inicial

por

E , N E

dinicial

respectivamente

sin afectar gravemente el algoritmo. Basta con invertir el criterio de incremento, de tal
manera que quede as: Si
el pixel

(sumar

E ).

d0 > 0,

se elige el pixel

SO

(sumar

N E );

si

d0 0,

se elige

De tal manera que con una ligera modicacin al cdigo 3.2 se

puedan considerar ambos casos, as:

3
4

La demostracin de esto se deja como ejercicio


tal como lo hizo el autor de este libro

91

3 Discretizacin de Primitivas Grcas

Figura 3.4: Anlisis inverso de punto medio

Listing 3.4: Algoritmo de lnea de punto medio de dos mitades de cuadrantes opuestos


1
2
3
4
5
6
7
8
9
10
11
12

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;

13

// Indican la direccin en que la lnea debe seguir


dir_x = ( x1 - x0 ) >0 ? 1 : -1;
dir_y = ( y1 - y0 ) >0 ? 1 : -1;

14
15
16
17

marcarPixel (x , y ) ; // marcar el primero


while ( x != x1 ) {
if ( d <= 0) {
d += delta_E ;
} else {
d += delta_NE ;
y += dir_y ;
}

18
19
20
21
22
23
24
25

92

3.4 Algoritmo de lnea de punto medio


26
27
28
29

x += dir_y ;
marcarPixel (x , y ) ;

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:


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

Listing 3.5: Algoritmo de lnea de punto medio genrico

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

3 Discretizacin de Primitivas Grcas

Figura 3.5: Simetra de las circunferencias

while ( y != y1 ) {
if ( d <= 0) {
d += delta_E ;
} else {
d += delta_NE ;
x += dir_x ;
}
y += dir_y ;
marcarPixel (x , y ) ;
}

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

Listing 3.6: Funcin de aprovechamiento de la simetra de las circunferencias

void punto_circunferencia_simetria ( int x , int y ) {


marcarPixel ( x , y ) ;
marcarPixel ( y , x ) ;
marcarPixel ( y ,- x ) ;

94

3.6 Algunas ideas sobre circunferencias


5
6
7
8
9
10

marcarPixel ( x , - y ) ;
marcarPixel ( -x , -y ) ;
marcarPixel ( -y , -x ) ;
marcarPixel ( -y , x ) ;
marcarPixel ( -x , y ) ;

3.6.

Algunas ideas sobre circunferencias

Existen varias alternativas para dibujar un crculo. Entre ellas, dibujarla a partir de la
ecuacin:

p
x2 + y 2 = R 2 y = R 2 x2

(3.10)

Pero esta alternativa provoca algunos efectos no deseables cuando

x R,

adems del

enorme consumo de recursos para ejecutar las multiplicaciones y la raz cuadrada.


Otra alternativa, es dibujarla en su forma paramtrica:

(r cos , r sin ), 0 2 . Esta

es menos ineciente, pero an demasiado, debido al clculo de las funciones trigonomtricas (que se realizan con series de potencias).

3.7.

Algoritmo de circunferencia de punto medio

La alternativa ms eciente es el algoritmo de punto medio para circunferencia o algo-

ritmo de crculo de Bresenham . A continuacin se presentar la solucin bsica en el


caso de circunferencias centradas en el origen para comprender la idea general.
En la gura 3.6 aparece un diagrama para explicar la idea, que es la misma del algortimo
de punto medio para lneas.
Para construir el cdigo se proceder de manera similar. Se usar la siguiente ecuacin
del crculo, la ecuacin implcita:

F (x, y) = x2 + y 2 R2 = 0
El valor de

(3.11)

es cero en el crculo, positivo fuera, y negativo dentro, siempre y cuando

los coecientes de

x2

y2

sean positivos.

Como se hizo con las lneas, la decisin se basa en la variable

d:

1
1
dviejo = F (M ) = F (xp + 1, yp ) = (xp + 1)2 + (yp )2 R2
2
2

95

3 Discretizacin de Primitivas Grcas

Figura 3.6: Algoritmo de circunferencia de punto medio

Si

dviejo < 0,

elegimos avanzar al pixel

E:

1
(xp + 1) + 1, yp
2

dE
nuevo

= F

dE
nuevo

= dviejo + E

E = dE
nuevo dviejo

Si

96

dviejo 0,

= F (ME ) F (M )
1
1
= F (xp + 2, yp ) F (xp + 1, yp )
2
2
1
= (xp + 2)2 + (yp )2 R2
2
1
2
(xp + 1) (yp )2 + R2
2
= 2xp + 3

elegimos el pixel

SE :

3.7 Algoritmo de circunferencia de punto medio

dSE
nuevo



1
= F (xp + 1) + 1, (yp 1)
2

dSE
nuevo

= dviejo + SE

SE = dSE
nuevo dviejo
= 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)

y por ende, el siguiente punto medio es

(1, R 21 ):

1
dinicial = F (1, R )
2
1 2
= 1 + (R ) R2
2
1
= 1 + (R2 R + ) R2
4
5
dinicial =
R
4

El algoritmo resultante en lenguaje C es:


1
2
3
4
5
6
7
8
9

Listing 3.7: Primera propuesta de algoritmo de crculo de punto medio

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

3 Discretizacin de Primitivas Grcas


marcarPixel ( y ,- x ) ;
marcarPixel ( x ,- y ) ;
marcarPixel ( -x , - y );
marcarPixel ( -y , - x );
marcarPixel ( -y , x ) ;
marcarPixel ( -x , y ) ;

10
11
12
13
14
15
16

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 ) ;
}

17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

Sin embargo, persiste el desagradable asunto de tener que operar con una variable de
coma otante slo por el valor inicial de

d. Eso puede solucionarse de la siguiente manera:

h = d 14 y sustituimos este valor en lugar de


h = 1 R y la comparacin d < 0 es h < 14 . Pero

Denimos una nueva variable de decisin:

en el cdigo 3.7. El valor es ahora

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.

como
como

en

El algoritmo resultante, usando slo aritmtica entera para dibujar crculos en lenguaje
C es:


1
2

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 ) ;

4
5
6
7
8
9

98

3.7 Algoritmo de circunferencia de punto medio


marcarPixel ( x , - y ) ;
marcarPixel ( -x , -y ) ;
marcarPixel ( -y , -x ) ;
marcarPixel ( -y , x ) ;
marcarPixel ( -x , y ) ;

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

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 ) ;
}

3.7.1. Versin sin multiplicaciones


El algoritmo 3.8 se puede agilizar an ms si se considera que as como se han calculado

diferencias parciales de primer orden para


orden para los incrementos de

d,

d,

se pueden calcular diferencias de primer

es decir, las diferencias parciales de segundo orden de

d. As:
Si elegimos

(xp + 1, yp ).

Tenemos entonces el siguiente panorama:

en la iteracin actual, el punto de evaluacin se mueve de

(xp , yp )

Eviejo = 2xp + 3
Enuevo = 2(xp + 1) + 3
Enuevo Eviejo = 2
SEviejo = 2xp 2yp + 5
y tenemos adems:

SEnuevo = 2(xp + 1) 2yp + 5.


SEnuevo SEviejo = 2

SE en la iteracin actual,
(xp + 1, yp 1). Tenemos lo siguiente:

Pero si elegimos
a

el punto de evaluacin se mueve de

(xp , yp )

99

3 Discretizacin de Primitivas Grcas


Eviejo = 2xp + 3
Enuevo = 2(xp + 1) + 3
Enuevo Eviejo = 2
SEviejo = 2xp 2yp + 5
y tenemos adems:

SEnuevo = 2(xp + 1) 2(yp 1) + 5.


SEnuevo SEviejo = 4

De este anlisis, surge una tercera versin del algoritmo (mucho ms rpida que las
anteriores, ya que slo contiene una multiplicacin):


1
2

Listing 3.9: Algoritmo de lnea de punto medio sin multiplicaciones

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 ) ;

4
5
6
7
8
9
10
11
12
13
14
15
16
17

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 ) ;

18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

100

3.7 Algoritmo de circunferencia de punto medio

Figura 3.7: Esquema para algorito de circunferencias con centro arbitrario

37
38
39

marcarPixel ( -x , y ) ;

3.7.2. Circunferencias con centro arbitrario


Para construir un algoritmo de dibujo de circunferencias con centro arbitrario, a partir
del algoritmo de punto medio de esta seccin, bsicamente hay que hacer un desplazamiento vectorial de cada uno de los puntos a marcar.
Consideremos la gura 3.7. En ella aparecen dos circunferencias del mismo radio. La
diferencia vectorial entre cada uno de los puntos de la circunferencia centrada en el
origen y la centrada en

(xc , yc ),

es precsamente el vector

Otra forma de plantearlo es as: Sean

(x0 , y 0 )

C = (xc , yc ).

los puntos de la circunferencia con

centro en

(xc , yc ). Sean (x, y) los puntos de la circunferencia con centro en el origen.

Entonces

(x0 , y 0 ) = (x + xc , y + yc ).

En base a este sencillo anlisis, presentamos el siguiente algoritmo de dibujo de circunferencias con centro arbitrario, basado en el algoritmo 3.8:

101

3 Discretizacin de Primitivas Grcas



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.

Relleno de rectngulos

Veamos primero un par de algoritmos de dibujo de rectngulos huecos antes de pasar al


de relleno:


1
2
3
4
5
6
7
8
9
10
11
12

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

3.9 Relleno de circunferencias


13
14
15
16
17
18
19
20
21
22
23
1
2
3
4
5
6
7
8
9
10
11

}
if ( y1 > y2 ) {
i = y1 ;
y1 = y2 ;
y2 = y1 ;
}
for ( i = y1 ; i <= y2 ; i ++) {
marcarPixel ( x1 , i ) ;
marcarPixel ( x2 , i ) ;
}

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

ancho

como

alto,

deben ser

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:


1
2
3
4
5
6

Listing 3.11: Relleno de rectngulos

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

3 Discretizacin de Primitivas Grcas

Figura 3.8: Relleno de circunferencias

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

(0, y)

hasta

(x, y)

SE , aprovechar para

(y al mismo tiempo aprovechar para

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

cdigo 3.10 y 3.11)

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

circunferencia_punto_medio de la subseccin en la pgina 101

3.10 Ejercicios
y - -;
x ++;
for ( i =0; i <= x ; i ++) {
marcarPixelesCicunferencia (i ,y , x0 , y0 ) ;
marcarPixelesCicunferencia (y ,i , x0 , y0 ) ;
}

15
16
17
18
19
20

}
}
r_x = x0 - x ;
r_y = y0 - y ;
r_ancho = 2* x ;
r_alto = 2* y ;
dibujar_rectangulo_relleno ( r_x , r_y , r_ancho , r_alto ) ;

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

void marcarPixelesCicunferencia ( int x , int y ) {


marcarPixel ( x , y ) ;

105

3 Discretizacin de Primitivas Grcas

Figura 3.9: Cuadrcula de prctica de primitivas grcas

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

106

marcarPixel ( x ,- y ) ;
marcarPixel ( -x , y ) ;
marcarPixel ( -x , - y) ;
marcarPixel ( y , x ) ;
marcarPixel ( y ,- x ) ;
marcarPixel ( -y , x ) ;
marcarPixel ( -y , - x) ;

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 - -;

3.10 Ejercicios
x ++;
for ( i =0; i <= x ; i ++) {
marcarPixelesCicunferencia (i , y ) ;
marcarPixelesCicunferencia (y , i ) ;
}

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

void dibujarPoligono(int x[],int y[],int nPuntos);


Esta funcin debe dibujar un polgono cerrado, formado por los puntos dados. Por

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

3 Discretizacin de Primitivas Grcas


14. Construir una funcin de relleno de elipses, dados su centro y sus dos radios.
15. Construir una funcin de dibujo de elipses, dado el rectngulo que la circunscribe.

108

4 Marcos de Referencia y Cambio de


Coordenadas
Es usual que se trabaje en los programas de aplicacin, con marcos de referencia diferentes del marco de referencia establecido por la biblioteca grca a utilizar, principalmente para proveer al cdigo la abstraccin necesaria para que la aplicacin sea portable
y coherente al modelo que se est gracando. Adems, ayuda mucho a la comprensin
por parte de otros programadores.
Este captulo contempla una introduccin al tema de cambio de coordenadas entre diversos marcos de referencia. Y la herramienta fundamental que se usar ser el lgebra
vectorial, por lo que se recomienda hacer un buen repaso de dichos temas, si es que no
se tienen ya a la mano.

4.1.

Notacin

Para recorrer el presente captulo con tranquilidad, vamos a establecer la siguiente


nomenclatura:

Sea

(R)

el vector del punto

en el marco de referencia

R,

y se lee el vector

en

R.
Las componentes rectangulares de

(R)

se representarn en este libro como

(R)

p y , y se leen la componente x del vector p en R


p en R respectivamente.

Sea

(R)

una distancia

y la componente

en la escala del marco de referencia

R,

(R)

px

del vector

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

4 Marcos de Referencia y Cambio de Coordenadas

Figura 4.1: Un mismo punto en dos marcos de referencia diferentes

En ella aparece el punto

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

(V )

p
,

y deseamos conocer

(R)

(y/o viceversa) para que nuestra

manipulacin programtica sea congruente y nuestro cdigo fuente sea ordenado y


claro.

4.2.

Anlisis vectorial del cambio de coordenadas

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

4.2 Anlisis vectorial del cambio de coordenadas

Figura 4.2: Transformacin vectorial de coordenadas

p,

Veamos la gura 4.2. En ella aparece el mismo punto

visto de otra manera:





(R)

(V )
(V )
(R)

p
= V
+ E RV
p
p
= T RV
.
Veamos estos nuevos elementos:

T RV

de entrada, del marco de referencia


el vector del origen de

en

R. E RV

es una funcin vectorial que transforma el vector


al

(esta es la que buscamos denir).

El vector

es

es una funcin vectorial que cambia la escala de

las componentes del vector de entrada, del marco de referencia

(R)
V

(R)
V

al

R.

usualmente se conoce, o puede calcularse de acuerdo a la propia distribu-

cin grca de nuestra aplicacin. La funcin

E RV

no es algo dado, normalmente, sin

embargo puede denirse de la siguiente manera:


Antes que nada, necesitamos las coordenadas de dos puntos en ambos marcos de referencia. Llammoslos

(R)
x =
(R)
(V )
Ay y y

Ntese que

(R)
By

B.

(R)

Consideremos la gura 4.3.

(R)

(V )

(V )

(V )

(R)

Bx Ax , x = Bx Ax , del mismo modo que y =


(V )
(V )
= By Ay . Es importante mantener los signos tal cuales. Con

este panorama, es fcil deducir que:




(V )

E RV
p
=

(R)

(V )

px ,
(V )
x

(R)

(V )

py
(V )
y

111

4 Marcos de Referencia y Cambio de Coordenadas

Figura 4.3: Cambio de escala de vectores

As, la forma completa para la ecuacin vectorial de transformacin de marcos de referencia es:




(R)
(R)
(V )

p
= T RV
p
= V
+

(R)

(V )

Si necesitaramos invertir el proceso, es decir, calcular

(R)

(V ) y
(V )

p x , (V )
py
y

!
(4.1)

(V )

(R) , procedemos
p
, dado un p

de forma inversa:

(V )
p

"
#
(V )
(V )


(V
)

x
y
(R)
(R)
(R)

= T V R
p
= R
+
p x , (R)
py
(R)
x
y

(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

4.2 Anlisis vectorial del cambio de coordenadas

Figura 4.4: Situacin de ejemplo

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],

independientemente del tamao en pixeles del subcuadro. Todas las co-

ordendadas delimitadas por corchetes estn en el marco de referencia del juego, que
llamaremos

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:

V [ ]. Y que las coordenadas en el marco de referencia real, se delimitan


R ( ).

con parntesis:

Se nos plantea entonces un problema: El juego necesita marcar el punto

[500, 500].

(V )

p
=

Pero nuestra API grca no funciona con ese marco de referencia. Necesi-

tamos indicarle el pixel especco que deseamos marcar. Cmo hacerlo?

El problema se resuelve de la siguiente manera: Tenemos


Podemos ver que

B.

(R)
V
= (100, 300).

(V )

y necesitamos

(R)

p
.

Procedemos entonces a buscar nuestros puntos

Podemos tomar estos de las esquinas inferior izquierda y superior derecha del

cuadro del juego. Entonces

[1000, 1000].

A(R) = (100, 300), B (R) = (350, 50), A(V ) = [0, 0]

B (V ) =

Ahora sustitumos:

113

4 Marcos de Referencia y Cambio de Coordenadas

Figura 4.5: Caso particular de pantalla completa

(R)

p
=




(V )

T RV
p

!
(R)
(V ) y
(V )

=
p x , (V ) p y
(V )
x
y


350 100
50 300
= (100, 300) +
500,
500
1000 0
1000 0
= (100, 300) + (125, 125)

(R)
V
+

(R)

(R)

p
= (225, 175)

Ahora sabemos que si en el juego se necesita marcar el punto


referencia interno, debemos marcar el pixel

4.3.

(225, 175)

[500, 500]

en el marco de

de la aplicacin.

Simplicacin escalar para ventana completa

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.

(R)
V
= (0, AltoR). Adems A(R) = (0, AltoR), B (R) = (AnchoR, 0),
B (V ) = [XmaxV, Y maxV ] y sustituimos:

Podemos ver que

A(V ) = [0, 0]

114

4.4 Transformacin de distancias

(R)

p
=

=
=
=
(R)

p
=




(V )

T RV
p
!
(R)
(V ) y
(V )

p x , (V ) p y
(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 )

p
AnchoR
y
(V
)

p x , AltoR 1
XmaxV
Y maxV

(R)
V
+

(R)

Lo que signica que:

(R)
(V )

px
px
=
AnchoR
XmaxV
y

(R)
(V )

py
py
=1
AltoR
Y maxV

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:
(R)

(R)

(R)

x
(V )
x

dx
(V ) para las magnitudes en
dx

xy

(V )
y

(R)

dy

(V )
dy

para las magnitudes en

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.

Aplicacin: Simulador de campo elctrico


bidimensional

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

4 Marcos de Referencia y Cambio de Coordenadas


1

continuo bidimensional .

4.5.1. Campo Elctrico


Si tenemos una carga puntual
punto

en el punto

Q,

entonces el campo elctrico

en el

es:

E =

1
q
3 QP

40
QP

qi en los puntos Qi respectivamente,

elctrico E en el punto P es:

Si tenemos una serie de cargas puntuales

i = 0, 1, . . . , n,

entonces el campo

E =

(4.3)

n
1 X qi
3 Qi P


40
i=0 Qi P

con

(4.4)

Los siguientes dos archivos proporcionan la funcionalidad bsica para manipular campo
elctrico.
Listing 4.1: Archivo de cabecera de funciones de 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

4.5 Aplicacin: Simulador de campo elctrico bidimensional


18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

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

Listing 4.2: Cdigo fuente de funciones de campo elctrico

/* 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

4 Marcos de Referencia y Cambio de Coordenadas


20
21
22
23
24
25
26
27
28
29

for ( k =0; k < cantidad ; k ++) {


campoElectrico ( cargas +k , punto , & temp ) ;
campo - > x += temp .x ;
campo - > y += temp .y ;
}

double magnitudVector ( Vector * v ) {


return sqrt (v - > x *v - > x + v - > y *v - > y ) ;
}

Ntese que el cdigo de este ltimo archivo es altamente suceptible de ser optimizado
para mejorar el rendimiento general de la aplicacin.

4.5.2. Uso de colores para SDL_gfxPrimitives


Los siguientes dos archivos proporcionan funcionalidad bsica para manipular colores en
el formato que la biblioteca SDL_gfxPrimitives requiere para funcionar correctamente.
Listing 4.3: Archivo de cabecera para el formato de color de gfx


1
2
3
4
5
6
7
8
9
10
11
12
13
14

/* c04 / campos vectoriales / colores . h


* */
# include < SDL / SDL .h >
// color slido
# define ALFA 0 x000000FF
# define
# define
# define
# define
# define
/*

15
16
17
18
19

22
23
24
25

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 .

20
21

ROJO (0 xFF000000 | ALFA )


VERDE (0 x00FF0000 | ALFA )
AZUL (0 x0000FF00 | ALFA )
BLANCO ( ROJO | VERDE | AZUL )
NEGRO 0 x000000FF

*/
Uint32 colorGfx ( Uint8 r , Uint8 g , Uint8 b ) ;

Uint32 colorGfxA ( Uint8 r , Uint8 g , Uint8 b , Uint8 a ) ;

26
27

118

4.5 Aplicacin: Simulador de campo elctrico bidimensional


28
29

// 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

Listing 4.4: Cdigo fuente de colores para gfx

/* 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) ) ;
}

4.5.3. Las escalas y sus conversiones


Los siguientes dos archivos proporcionan la capa de abstraccin que independiza las dos
escalas usadas: La escala interna del modelo, que un universo bidimensional continuo,
virtualmente ilimitado donde slo existen cargas elctricas puntuales jas. Y la escala
de la biblioteca grca usada, que, como suele suceder, es una matriz discreta y nita
de pixeles.


1
2
3

Listing 4.5: Archivo de cabecera de funciones de escala

/* c04 / campos vectoriales / escala . h


* Esta es una versin particular del problema
* de la transformacin de escalas .

119

4 Marcos de Referencia y Cambio de Coordenadas


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

* 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 );
void imprimirEscala ( FILE * salida , Escala * e ) ;

1
2
3
4
5
6
7

Listing 4.6: Cdigo fuente de funciones de escala

/* 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

4.5 Aplicacin: Simulador de campo elctrico bidimensional


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

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 ) ;
}

4.5.4. Programa principal


A continuacin se presenta la aplicacin principal que controla los eventos producidos
por el usuario.
El programa tiene la funcionalidad de que el tamao de la ventana grca es independiente de la escala del modelo.
Los comandos de interaccin son los siguientes:

clic izquierdo

Sobre una carga no tiene efecto. Sobre el campo tiene el efecto de imprimir

en consola el vector de campo y su magnitud medido en

N/C .

121

4 Marcos de Referencia y Cambio de Coordenadas

clic izquierdo sostenido

Sobre una carga tiene el efecto desplazar esa carga a otro punto

del plano. Sobre el campo no tiene ningn efecto.

clic derecho

Agrega una nueva carga elctrica puntual de

en la macro

CARGA_INICIAL)

de cargas permitidas (indicado en la macro

clic medio

1C

(dicho valor est indicado

si es que an no se ha alcanzado el mximo nmero

MAX_CARGAS).

Sobre una carga, tiene el efecto de cambiarle el signo a su valor. Sobre el

campo no tiene ningn efecto.

rueda del mouse arriba

el campo provoca un alejamiento del

rueda del mouse abajo

1 C cada
INCREMENTO_CARGA). Sobre

Sobre una carga tiene el efecto de aumentar su valor en

vez (el valor de incremento est denido en la macro

10 %

del plano virtual.

cada vez (el valor de decremento est denido en la macro


Sobre el campo provoca un acercamiento del

Shift + echa arriba

1C
INCREMENTO_CARGA).

Sobre una carga tiene el efecto de decrementar su valor en

10 %

del plano virtual.

Aumenta el nmero de echas de campo en la pantalla sin afectar

la escala del modelo ni el tamao de la ventana.

Shift + echa abajo

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


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

/* 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 ;

19

Uint32
Uint32
Uint32
Uint32
Uint32

20
21
22
23
24
25

122

colorFondo ;
colorFlecha ;
colorPuntaFlecha ;
colorCargaPositiva ;
colorCargaNegativa ;

4.5 Aplicacin: Simulador de campo elctrico bidimensional


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
65
66
67
68
69
70
71
72
73
74

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

4 Marcos de Referencia y Cambio de Coordenadas


e);
75

for ( j =0; j < conf . numCuadrosH ; j ++) {


punto . x = tV_Rx (( conf . e . AnchoR / conf . numCuadrosH ) *(2* j +1) /2 , &
conf . e ) ;

76
77
78

campoElectricoCargas ( conf . cargas , conf . cantidadActualCargas ,


& punto , & campo ) ;

79
80

// 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 ) ;

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

// 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 ) ;

106

// 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;
}

107
108
109
110
111
112
113
114
115

* pantalla = SDL_SetVideoMode ( conf . e . AnchoR , conf .e . AltoR ,


profundidad_color , SDL_SWSURFACE | SDL_RESIZABLE ) ;

116

124

4.5 Aplicacin: Simulador de campo elctrico bidimensional


if (* pantalla == NULL ) {
printf ( " Error al inicializar el modo de video : ' %s '\ n " ,
SDL_GetError () ) ;
exit (2) ;
}

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

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 ) ;

159

125

4 Marcos de Referencia y Cambio de Coordenadas


// volcar el buffer en la pantalla
SDL_Flip ( pantalla ) ;

160
161
162

while ( corriendo ) {
while ( SDL_PollEvent (& evento ) ) {
switch ( evento . type ) {
case SDL_VIDEORESIZE : {
conf . e . AnchoR = evento . resize . w ;
conf . e . AltoR = evento . resize . h ;

163
164
165
166
167
168
169

// 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) ;
}

170
171
172
173
174
175
176
177
178

// 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 ) ;

179
180
181
182
183
184

// vuelca el buffer en la pantalla :


SDL_Flip ( pantalla ) ;

185
186

}
break ;

187
188
189

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 ) ) ;
}

190
191
192
193

194
195
196
197
198
199
200
201

126

4.5 Aplicacin: Simulador de campo elctrico bidimensional


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

}
// 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;
}

127

4 Marcos de Referencia y Cambio de Coordenadas


242
243

244
245

actualizar ( pantalla ) ;
SDL_Flip ( pantalla ) ;

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 ) ;
}

246
247
248

249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264

}
break ;

265
266
267

// asegurar que se deselecciona la carga


case SDL_MOUSEBUTTONUP :{
seleccion = -1;
}
break ;

268
269
270
271
272
273

// 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 ;

274
275
276
277
278
279
280
281
282
283
284

case SDL_KEYDOWN :{

285

128

4.5 Aplicacin: Simulador de campo elctrico bidimensional


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 ;
}

286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304

}
break ;

305
306
307

// 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

308
309
310
311
312
313
314
315
316
317
318

SDL_Quit () ;
return 0;

4.5.5. El Makefile
Finalmente, este es el archivo

Makefile

necesario para automatizar todo el proceso de

compilacin :
Listing 4.8: Archivo Makele para el simulador


1
2
3

# c04 / campos vectoriales / Makefile


LDFLAGS = $ ( shell sdl - config -- cflags )
2

Slo se requiere ejecutar:

$ make

129

4 Marcos de Referencia y Cambio de Coordenadas


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

LDLIBS = $ ( shell sdl - config -- libs )


GFX = - lSDL_gfx
# # RM

= / bin / rm -f

# 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

1. Considerando la gura 4.6, construya las funciones

int tR_Vx(double
int tR_Vy(double
double tV_Rx(int
double tV_Ry(int

xV);
yV);
xR);
yR);

para convertir puntos del marco de referencia virtual al real y viceversa. Asuma

130

4.6 Ejercicios

Figura 4.6: Diagrama para el ejercicio 1

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

Shift+Ruedas del ratn sobre


Shift+Flecha arriba/abajo).

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

configuracion

del programa principal) sean cargadas

un buen lugar para empezar es en el archivo  campo.c, y el siguiente lugar, es el programa principal

131

4 Marcos de Referencia y Cambio de Coordenadas


desde un archivo y que las conguraciones al momento de cerrarse la aplicacin se
guarden en el mismo archivo.

132

5 Transformaciones Geomtricas
Bidimencionales
En este captulo analizaremos los procedimientos estndares para realizar transformaciones geomtricas bidimensionales sobre objetos grcos inmersos en algn marco de
referencia especco (real o virtual).
Todo el anlisis girar en torno a la representacin matricial de las coordenadas, por
lo que se recomienda hacer un concienzudo repaso de operaciones con matrices, ya que
el estudio de este captulo depende transversalmente de dichas habilidades. Adems, es
preferible recordar algunos tpicos bsicos de trigonometra analtica para poder tener
una lectura ms uda.

5.1.

Operaciones geomtricas bsicas

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.

5.1.1. Traslacin o Desplazamiento


La traslacin o desplazamiento se reere a mover un punto, un conjunto de puntos o un
objeto compuesto, de su ubicacin original hacia una nueva ubicacin en su marco de
referencia. La operacin tiene un parmetro: el vector de desplazamiento.
Veamos un ejemplo grco:
Como podemos ver en la gura 5.1, el cuadro que representaremos de manera abstracta
como

ha sido trasladado en un valor de

En su nueva ubicacin lo llamaremos


base: Su posicin original era

(2, 3)

P 0.

(3, 1) (este es el parmetro de la operacin).


Tomemos su esquina inferior izquierda como

y la nueva es

(5, 2).

El mismo desplazamiento ha

afectado a todos los dems puntos del cuadro.

133

5 Transformaciones Geomtricas Bidimencionales

Figura 5.1: Ejemplo de traslacin simple

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 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

1
con factores de escalamiento
3 en

tomando como referencia al origen y

x, y 21 en

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 ).

son menores que


ms en

que

Veamos otro ejemplo de escalamiento:


En la gura 5.3, se ilustra el escalamiento de

(en la misma posicin que en el ejem-

plo anterior) tomando como referencia a su esquina inferior derecha y con factores de
escalamiento

134

1
2 en

x,

1
2 en

y.

5.1 Operaciones geomtricas bsicas

Figura 5.2: Ejemplo de escalamiento simple

Figura 5.3: Ejemplo de escalamiento compuesto

135

5 Transformaciones Geomtricas Bidimencionales

Figura 5.4: Ejemplo de rotacin simple

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,

que est en las coordenadas

(6, 2). La
x+ . El

6 en sentido contrario al de las agujas del reloj a partir del eje

punto de referencia es el origen del marco de referencia. Las coordenadas del punto
inferior izquierdo de

P0

son

(4,1962, 4,7321)1 .

Un ejemplo ms:
En la gura 5.5, se ilustra la rotacin de

P,

que est en las mismas coordenadas del

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

P 000

son

(5,1716, 4).

Invitamos amablemente al lector a comprobarlo con comps y transportador

136

5.2 Representacin matricial

Figura 5.5: Ejemplo de rotacin compuesta

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

1 0 dx
T (dx , dy ) = 0 1 dy
0 0 1

(5.1)

3 3:

(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 Transformaciones Geomtricas Bidimencionales


La operacin de rotacin bidimensional (respecto del origen, en el sentido opuesto al de
las manecillas del reloj) se representa as:

cos sin 0
R () = sin cos 0
0
0
1

(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 0 = T (dx , dy ) P

signica que

P0

es

con un desplazamiento de

P 0 = S (sx , sy ) P

signica que

P0

es

con factores de escalamiento

(dx , dy ).
sx

en

sy

en

tomando el origen como referencia.

P 0 = R () P

signica que

P0

es

radianes en sentido opuesto al


x+ , tomando como eje de rotacin

con una rotacin de

movimiento de las manecillas del reloj, a partir del eje


al origen del marco de referencia.
-

5.3.

Composicin de tranformaciones geomtricas

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

de tal manera que su esquina inferior derecha quede en el origen. Luego,

hay que hacer el escalamiento con ambos factores a

1
2 . Luego, habra que re-desplazar el

cuadro transformado, a su posicin nal, con la esquina inferior derecha a donde estaba
antes.
Expresado de otra manera:

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 Q0
1 1
0
0
de P ha quedado en el origen. Posteriormente, aplicamos a P el escalamiento S
2, 2
00
para obtener P , que es un cuadro de ancho y alto igual a 1, con su esquina inferior
00
derecha tambin en el origen. Despus, aplicamos la traslacin T (8, 7) a P , con lo que
000
obtenemos P .

Sea

formacin

138

5.4 Atencin a la eciencia



T (8, 7) S 12 , 12 T (8, 7). Pode000 directamente, sin hacer
puntos de P

La matriz de transformacin aplicada en conjunto es


mos aplicarla a los puntos de

para obtener los

pasos intermedios.
Sigamos la secuencia de transformacin para el punto

como ejemplo:

Q0 = T (8, 7) Q




1 1
1 1
00
0
Q = S
Q =S
T (8, 7) Q
,
,
2 2
2 2


1 1
000
00
Q
= T (8, 7) Q = T (8, 7) S
T (8, 7) Q
,
2 2

5.4.

Atencin a la eciencia

Sucede que todas las composiciones de matrices de transformacin resultan en matrices


de la siguiente forma:

r11 r12 tx
M = r21 r22 ty
0
0 1

Por lo que su producto con un punto

x
P = y
1

requiere 9 multiplicaciones y 6 sumas.

Sin embargo, al realizar las operaciones, se puede apreciar que el resultado es previsible.
El resultado siempre ser que

x0 = xr11 + yr12 + tx

y 0 = 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.

Versin matricial del cambio de coordenadas

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

5 Transformaciones Geomtricas Bidimencionales


Intuitivamente concluimos que la matriz de transformacin es:


MRV = T

(R)
(R)
V x ,V y

(R)

(R)


S

(V )

As que la transformacin que habamos realizado como

!
(5.5)

(V )




(R)
(V )

p
= T RV
p
,

tam-

bin se puede realizar como:

(R)
(V )

px
px

(V )
(R)

p y = MRV
py
1
1

(R)



px
(R)
(R)

(R)
S

py = T V x ,V y
1

(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

MRV ,

tambin podramos denir un encadenamiento de transformaciones

sucesivas para pasar de un marco de referencia a otro, pasando por intermedio de otros
ms as:

MDA = MDC MCB MBA ,

como en la gura 5.6. Esto proporciona la

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

radianes en el sentido opuesto a la del movimiento de las agujas del

reloj. Signica que la versin matricial de la funcin de transformacin del marco de

V al R es:

(R)
(V )

px
px

(V )
(R)

p y = MRV
py
1
1

(R)





px
(R)
(R)

(R)
(R)


y
(R)
x
R ()
S (V

py =T V x , V y
),
(V )
x
y
1

referencia

(V )

px
(V )

py
1

Desde la perspectiva vectorial, habra sido difcil ofrecer una solucin sencilla.

5.6.

Reversin de transformaciones geomtricas

En frecuentes ocaciones no slo necesitaremos efectuar transformaciones geomtricas a


un conjunto de puntos, sino que tambin necesitaremos  destransformarlos . Para hacer

140

5.6 Reversin de transformaciones geomtricas

Figura 5.6: Cambio de coordenadas a travs de mltiples marcos de referencia

Figura 5.7: Cambio de coordenadas con rotacin

141

5 Transformaciones Geomtricas Bidimencionales


esto, recordemos las siguientes propiedades matriciales:

P0 = M P
M 1 P 0 = M 1 M P

= M 1 M P
= I P
M

= P

(A B)1 = B 1 A1
0
Esto signica que si tenemos P


T

(V )

=T

(R)
(R)
V x ,V y

(R)
(R)
V x ,V y

(R)


S

(V )
x


S

(R)

(R)

(R)
y
x
(V ) ,
(V )
x
y


P,

entonces:

!!1
P 0 = I3 P

(V )
y

!!1  
1

(R)
(R)
T V x ,V y
P0 = P
(V )
(R)

(R)

y
y

y entonces basta con encontrar las respectivas matrices inversas y obtendremos la matriz
de retro-transformacin para

P0

(o sea, para regresar de

P0

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):

T (dx , dy )1 = T (dx , dy )


1 1
1
S (sx , sy )
= S
,
sx sy
R()1 = R()
Esto signica, segn la explicacin en la que estabamos, que:

aunque no completamente inviable

142

(5.7)
(5.8)
(5.9)

5.7 Ejercicios

(R)

(V )

!!1  
1

(R)
(R)

T
V
,
V
P0 = P
x
y
(V )
(R)

y
y

(V )

(V )

(R)

(R)




(R)

(R)
T V x , V y
P0 = P

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

1. Cul es la matriz de transformacin necesaria para lograr la transformacin presentada 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 .

a ) Cul es la denicin de

M1 ?

b ) Cules son las coordenadas de los puntos transformados?


4. Considere la gura 5.8 de nuevo. Rote el tringulo -30 respecto de su esquina
inferior derecha segn la regla de la mano derecha y despus, duplique su tamao
manteniendo la coordenada de su punto inferior derecho. De nuevo haga esto con
la ayuda de una matriz de transformacin bidimensional

a ) Cul es la denicin de

M2 .

M2 ?

b ) Cules son las coordenadas de los puntos transformados?


5. Considere la gura 5.8 otra vez. Triplique el tamao del tringulo, manteniendo
la coordenada del punto inferior izquierdo, con la ayuda de una matriz de transformacin bidimensional

M3 .

a ) Cul es la denicin de

M3 ?

b ) Cules son las coordenadas de los puntos transformados?


3

es cierto, parece un juego de palabras, pero as es

143

5 Transformaciones Geomtricas Bidimencionales

Figura 5.8: Ejercicio de transformacin bidimensional

6. Demuestre la ecuacin 5.7 en la pgina 142.


7. Demuestre la ecuacin 5.8.
8. Demuestre la ecuacin 5.9.

144

6 Transformaciones Geomtricas
Tridimencionales
En este captulo se presentan los tpicos bsicos y las herramientas matemticas bsicas para realizar transformaciones geomtricas tridimensionales en marcos de referencia
tridimensionales.

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.

Representacin Matricial de Transformaciones


Geomtricas

Los puntos en tres dimensiones se representan como vectores columna de tamao

x
y

P =
z
1

4 1:

(6.1)

145

6 Transformaciones Geomtricas Tridimencionales

Figura 6.1: Sistema de referencia de mano derecha

Figura 6.2: Sistema de referencia de mano izquierda

146

6.2 Representacin Matricial de Transformaciones Geomtricas

Figura 6.3: Otra manera de ver el sistema de referencia de mano izquierda

La operacin de traslacin tridimensional se representa como una matriz de

1
0
T (dx , dy , dz ) =
0
0

0
1
0
0

0 dx
0 dy

1 dz
0 1

4 4:

(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:

1
0
0
0 cos sin
Rx () =
0 sin cos
0
0
0

0
0

0
1

(6.4)

147

6 Transformaciones Geomtricas Tridimencionales

cos

0
Ry () =
sin
0

0 sin 0
1
0
0

0 cos 0
0
0
1

(6.5)

0
0

0
1

(6.6)

cos sin
sin cos
Rz () =
0
0
0
0
Ntese la similitud entre esta ltima ecuacin

Rz ()

0
0
1
0

y la ecuacin de

R()

que es para

el caso bidimensional (vase la ecuacin 5.4 en la pgina 138).

6.3.

Composicin y reversin de Transformaciones


Geomtricas Tridimensionales

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:

T (dx , dy , dz )1 = T (dx , dy , dz )


1 1 1
1
S (sx , sy , sz )
= S
, ,
sx sy sz
Rx ()1 = Rx ()

(6.7)
(6.8)
(6.9)

Ry ()

= Ry ()

(6.10)

Rz ()

= Ry ()

(6.11)

A manera de prctica, proponemos el uso de la aplicacin

transformaciones3D.jar1

que se encuentra en el material adjunto a esta obra. Esta

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

6.3 Composicin y reversin de Transformaciones Geomtricas Tridimensionales


$ java -jar transformaciones3D.jar
Con esto se iniciar una ventana grca y se inicia un intrprete de comandos en la
consola donde se ejecut el comando anterior.
La ventana grca muestra al inicio un cubo y un rectngulo tridimensionales en el
centro de un marco de referencia tridimensional de mano derecha que se puede rotar
con arrastre del ratn. Los ejes

x+ , y +

z+

son lineas de colores rojo, verde y azul

respectivamente (es un truco mnemotcnico que relaciona el modelo RGB

2 con los ejes

x-y-z y la regla de la mano derecha). Con la rueda del ratn se puede acercar y alejar la
imagen.
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

ayuda y presiona <Enter>, se mostrar

una breve descripcin de los comandos disponibles):

:ayuda

muestra una breve resea de los comandos permitidos.

:reiniciar

reinicia la aplicacin a su estado inicial por defecto.

: %<comentario>

agrega un comentario sin efecto alguno sobre la aplicacin.

:<objeto> mostrar

muestra el objeto en cuestin (si ya est visible, no tiene efecto).

:<objeto> ocultar

oculta el objeto en cuestin (pero todava permite su transforma-

cin).

:<objeto> t <dx> <dy> <dz>

provoca un desplazamiento

T (dx , dy , dz )

en el objeto es-

pecicado.

:<objeto> e <sx> <sy> <sz>

provoca un escalamiento

S (sx , sy , sz ) en el objeto especi-

cado.

:<objeto> eu <s>

provoca un escalamiento uniforme

S (s, s, s),

en el objeto especica-

do.

:<objeto> {rx|ry|rz} <theta>

provoca una rotacin (en grados sexagesimales) en el

eje especicado para el objeto especicado.

:salir

termina la aplicacin.

Los objetos vlidos para esta versin de la aplicacin son


El cubo tiene una arista de largo 4, el plano es de

43

cubo, plano, ojo

y el ojo

todos.

3 es una pirmide de

4 3 1. Los ejes tienen una longitud de 10. Si el comando introducido no es reconocido,


4

el programa muestra lo que el usuario escribi y la ayuda. El ojo est por defecto oculto .

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

6 Transformaciones Geomtricas Tridimencionales


Podramos realizar cualquier combinacin de transformaciones, pero vamos a seguir en
este texto, las siguientes (obviamente recomendamos que despus de cada instruccin introducida en la consola se examinen las transformaciones correspondientes en la ventana
grca):


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

1. Demuestre las ecuaciones 6.7 en la pgina 148, 6.8 y 6.9.

150

7 Vista Tridimensional (de 3D a 2D)1


El acto que comunmente llamaramos convertir algo de tres dimensiones a dos dimensiones es, formalmente hablando, una proyeccin de un conjunto de objetos tridimensionales, sobre una supercie plana.
Existe una gran cantidad de tipos de proyecciones, cada una con sus aplicaciones y
particularidades matemticas; algunas aplicadas al arte digital, a la arquitectura, al
diseo de piezas de maquinaria, etc. Abordarlas todas, excede el propsito de este libro,
por lo que en el presente captulo describiremos brevemente las dos principales: las

proyecciones ortogonales y las proyecciones de perspectiva .


Recomendamos repasar en este momento a ms tardar, los sistemas de coordenadas
rectangular, cilndrico y esfrico y las transformaciones entre ellos.

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

x, y

representados por lneas

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

en este tipo de proyeccin

la reproduccin de la sensacin de profundidad o lejana .


1

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

7 Vista Tridimensional (de 3D a 2D)

Figura 7.1: Ejemplos de proyeccin ortogonal

152

7.2 Proyeccin en Perspectiva


Esto podemos verlo en el primer recuadro de la gura recin referenciada. La gura es
una instantanea de la aplicacin utilizada en el captulo anterior habiendo ejecutado los
siguientes comandos:
Listing 7.1: Alejamiento del plano


1
2
3
4

: plano
: plano
: plano
: plano

ry 90
t -50 0 0
rz 25
ry -25

de lo que se puede deducir que el plano est bastante lejos.

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

en este tipo de proyeccin

existe

la reproduccin de la sensacin de profundidad o lejana , tal como podemos


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.

7.3.

Portal de Visin

Ahora bien, la pregunta importante es:

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:

Con qu secuencia de transformaciones? En la pre-

sente seccin trataremos de responder claramente a esta pregunta.

por que si no, no habra tenido sentido estudiar todo eso. . . verdad. . . ?

153

7 Vista Tridimensional (de 3D a 2D)

Figura 7.2: Ejemplos de proyeccin en perspectiva

154

7.3 Portal de Visin

Antes de avanzar, es necesario denir el concepto de Portal de Visin: El

Portal

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

transformaciones3D.jar:

Vamos a considerar el ojo como el portal de visin de una

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

xy .

Entonces, al ver exactamente desde atrs del portal

4 es como si lo viramos ya proyectado sobre la pantalla imaginaria (ya que

de visin

habremos obviado la coordenada

de todos los puntos de las guras).

Considere la siguiente secuencia de comandos para nuestra aplicacin

transformaciones3D.jar

Listing 7.2: Proyeccin ortogonal


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

para realizar lo anteriormente descrito:

: % 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

7 Vista Tridimensional (de 3D a 2D)


20
21
22
23
24
25
26
27
28

: % 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 coor-

denada

r : 5, : 6 , :

(r, , ).

Luego aplicamos la siguiente transformacin (en las lneas 22 a 25) al objeto a

(en las lneas 7 a 10 del cdigo 7.2). Llammosla simplemente

proyectar (en este caso el cubo y el plano):

 
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

T (2, 1,5, 0).

Adems, podramos agregar escalamiento, etc.. . .

Lo importante de este ejercicio, es entender cmo lograr proyecciones ortogonales a travs


de transformaciones geomtricas tridimensionales.

7.4.

Implementacin de proyecciones ortogonales

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

(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

por eso su tamao es

43

Tiene una inversin de signo menos y una suma ms

156

7.4 Implementacin de proyecciones ortogonales


objetos que estn delante del portal de visin en el eje

z+

y con el eje

y+

hacia abajo

(lo que es usual en gracacin en computadoras).


Veamos el mismo caso de proyeccin del cdigo 7.2, pero con la transformacin 7.2:
Listing 7.3: Proyeccin ortogonal preferible


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 0 = MRV M2D3D P


AnchoR AltoR
0
P = S(sx , sy , sz ) T
,
, 0 M2D3D P
2
2
Es en este punto donde la aplicacin

(7.3)

transformaciones3D.jar implementa su funcional-

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

PortaldeVision.java.
7

recordando que el eje

y+

colocarPuntodeVision

del archivo

ya est hacia abajo

157

7 Vista Tridimensional (de 3D a 2D)

Figura 7.3: Proyeccin ortogonal y en perspectiva

7.5.

Implementacin de proyecciones en perspectiva

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

despus de haber hecho la transformacin 7.3), mientras que en las proyecciones 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

xy ,

proyeccin est a una distancia


ser

xper

esto por simplicidad

158

que es

del plano. El valor en

del punto proyectado debe

sobre el plano. Para encontrar ese valor, hacemos un anlisis de tringulos

semejantes:

(x, y, z)

que adems es el plano de proyeccin . El centro de

7.5 Implementacin de proyecciones en perspectiva

Figura 7.4: Deduccin de proyeccin en perspectiva

xper
d

x
d+z
d
dx
=
x
d+z
d+z

xper =

de la misma manera (ntese que

(7.4)

d 6= 0):

yper
d

yper =

y
d+z
dx
d
=
y
d+z
d+z

(7.5)

zper = 0

(7.6)

Lo que signica que podra aplicarse la transformacin

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:

0
Pper
=S

d
d
d+z , d+z , 0

M2D3D P

El problema de este ltimo escalamiento es que depende del valor de

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 0 = M2D3D P

(la matriz de transformacin ortogonal)

para todos los puntos

a proyectar (esto equivale a hacer

proyeccin ortogonal)

159

7 Vista Tridimensional (de 3D a 2D)


3. Para cada punto

a proyectar, hacer

d
Px0
d + Pz0
d
=
Py0
d + Pz0
= 0

Px00 =
Py00
Pz00

4. Y despus, aplicar el escalamiento y traslacin nal para cada punto:

Pper = MRV P 00
Recomendamos, a manera de prctica, el uso de la aplicacin

perspectiva3D.jar9

que

se encuentra en el material adjunto a esta obra. Esta aplicacin permite hacer las mismas
cosas que su hermana

transformaciones3d.jar, pero la proyeccin es en perspectiva y

no ortogonal.
Para ejecutar la aplicacin, tambin escrita en lenguaje java:

$ java -jar perspectiva3D.jar


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.

PortaldeVision.java de
transformaciones3d.jar, en ellos podemos

Recomendamos que se haga una comparacin entre el archivo


la aplicacin

perspectiva3D.jar

y el de

ver una implementacin de estas tcnicas de proyeccin:


Listing 7.4: Fragmento del archivo PortaldeVision.java de transformaciones3d.jar

/*
* 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 ;

38
39
40
41
42
43
44
45
46

matriz . identidad () ;

47
48

matriz . rotarZ ( - theta ) ;


matriz . rotarY (180 - phi ) ;
matriz . rotarZ ( -90) ;
matriz . trasladar (0 , 0, distancia ) ;

49
50
51
52
53
9

Consltese el captulo A en la pgina 291 para ms detalles sobre la compilacin y compilacin de


aplicaciones J2SE.

160

7.5 Implementacin de proyecciones en perspectiva


// 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
));

54
55
56
57
58
59
60
61
62

// traslamiento final :
matriz . trasladar ( tamanhoR . width /2 , tamanhoR . height /2 , 0) ;

63
64
65
66
67
68

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

for ( int i =0; i < objetos . length ; i ++)


objetos [ i ]. transformarProyeccion ( matriz , this );

Listing 7.5: Fragmento del archivo PortaldeVision.java de perspectiva3D.jar

/*
* 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

7 Vista Tridimensional (de 3D a 2D)


( tamanhoR . width + tamanhoR . height ) /( AnchoV + AltoV )
// / 3 f
/ distancia
));

75
76
77
78
79

// traslamiento final :
matriz . trasladar ( tamanhoR . width /2 , tamanhoR . height /2 , 0) ;

80
81
82
83
84
85

7.6.

for ( int i =0; i < objetos . length ; i ++)


objetos [ i ]. transformarProyeccion2 ( matriz , this ) ;

Ejercicios

1. En la aplicacin

transformaciones3d.jar el portal de visin (es decir, su centro)


PortaldeVision.java), lo que

tiene una distancia ja de una unidad (ver archivo

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

perspectiva3D.jar?

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 nor-

mal , como por ejemplo, utilizar las teclas cursoras, la de

Pgina Anterior

Pgina Siguiente

Tabulacin

y las teclas

para avanzar en una u otra direccin del espacio

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

entindase por persona normal a una persona no informtica

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

perspectiva3D.jar

tiene un evidente problema con respecto a la lim-

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

8.2.1. Seleccin por puntero


Una vez que el usuario ha logrado posicionarse al alcance del objeto de su inters, debera
poder indicar que quiere operar con l y no slo verlo. En estos das, lo ms usual es
hacer clic sobre el objeto. Cualquier aplicacin que no siga dicho paradigma, resulta
extraa para un usuario nicamente acostumbrado a operaciones visuales.
En el caso particular de aplicaciones tridimensionales, la seleccin directa por ratn
implica resolver, entre otros, los problemas siguientes:

(a) Averiguar qu objeto est

ms cerca del usuario, es decir, del portal de visin. Esto es porque no debera tener

Recomendamos al lector vericar esto e intentar no marearse ni asustarse en el proceso

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

clic.

Esto ltimo requiere al menos, de lograr la inversin de todas las

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.2.2. Seleccin por nominacin


Otra manera de seleccionar objetos, es haciendo referencia a ellos por su nombre, seleccionndolos de una lista o escribindolos directamente en una lnea de comandos.
Ese es el caso de varias aplicaciones especializadas en simulacin que permiten asignarle
nombre a los objetos grcos (y tal vez tambin a los no grcos), como nuestras aplicaciones de estudio de los ltimos captulos. En ellas, la operacin de seleccin de objetos

es a travs de lnea de comandos, amarrada a la operacin de transformacin .

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

con un cambio del valor

de la coordenada del centro del portal

de visin, y asociar el arrastre del ratn en

con un cambio del valor

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

ctrl

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

ctrl

8.4.

shift.

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

Config3D.java

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

Listing 8.1: Fragmento de cdigo que especica constantes ergonmicas

// 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 ;

En otras combinaciones, no resulta fcil manipular la aplicacin.

166

9 Curvas Paramtricas
9.1.

Representacin algebrica de Curvas Paramtricas

Las curvas paramtricas suelen representarse de la siguiente manera:

~r(t) = f (t)i + g(t)j


~r(t) = x(t)i + y(t)j

~r(t) = (x(t), y(t))


de esta otra:

(
x = f (t)
y = g(t)

restricciones

Obviamente, en cada caso se debern especicar las funciones paramtricas (f y


y

g,

segn la notacin).

Veamos algunos ejemplos de curvas paramtricas:


Una circunferencia con radio

se representa as:

Un segmento de lnea recta, desde el punto

~r() = R cos i + R sin j, 0 2

(x1 , y1 )

al

(x2 , y2 )

as:

~r(t) = ((1 t)x1 + tx2 ) i + ((1 t)y1 + ty2 ) j, 0 t 1


Una elipse con radios

en

en

y:

~r() = a cos i + b sin j, 0 2


En la gura 9.1 en la pgina siguiente se presentan una circunferencia con
segmento de lnea recta con

(x1 , y1 ) = (2, 1)

(x2 , y2 ) = (4, 5)

R = 5, un
a=2y

y una elipse con

b = 3.

167

9 Curvas Paramtricas

Figura 9.1: Ejemplo de curvas paramtricas planas

168

9.2 Continuidad

Figura 9.2: Resorte en alrededor del eje

y,

R alrededor del

x = R cos
z = R sin

y=

Este es un resorte innito con radio

En la gura 9.2 se presenta un resorte con

generado con Octave

eje

y:

R = 3 y 0 8 . Se realiz con el siguiente

scritp de Octave:


1
2
3
4
5
6

theta = [0: pi /100:8* pi ];


R = 3;
x = R * cos ( theta ) ;
z = R * sin ( theta ) ;
y = theta ;
plot3 (x ,y , z )

9.2.

Continuidad

9.2.1. Curvas suaves


Concepto de

curva continua segn [Henrquez 2001, p. 78]:

169

9 Curvas Paramtricas

Se dice que una funcin es continua en un nmero

si

lm f (x) = f (c)

xc

(Lo que a su vez se cumple cuando el lmite por la izquierda y por la derecha son
iguales).

Concepto de

f (x)

curva suave:

es una curva suave en

[a, b]

si

f (x)

es continua en todos los puntos de

[a, b].

Curva paramtrica suave [Henrquez 2001, p. 229]:

~r(t) es una
en ]a, b[.

curva suave en

[a, b]

si

r~0 (t)

es continua en todos los puntos de

[a, b]

r~0 6= ~0

9.2.2. Tipos de continuidad


A continuacin presentamos un conjunto de conceptos importantes de referencia para
conceptos posteriores, adaptados de [Foley et al., p. 374]:

Continuidad Geomtrica
Se dice que hay continuidad geomtrica de grado 0, denotada por
curvas suaves

S~1

curvas se unen en

S~2

en el punto

con

t = ,

si

S~1 ( ) = S~2 ( ).

entre dos

Es decir, si las

p.

Se dice que hay continuidad geomtrica de grado 1, denotada por

~1
curvas suaves S

G0 ,

G1 ,

entre dos

~2
y S

0
en el punto p con t = , si hay continuidad G (en ese mismo
~0 ( ) = k S~0 ( ), k > 0. Es decir, si las
punto y con el mismo valor de parmetro) y S
1
2
curvas se unen en p y los vectores tangentes tienen la misma direccin y sentido.
Ntese que

G1

no implica que la curva formada por la unin de

S~1

S~2

sea suave.

Continuidad Paramtrica
Se dice que hay continuidad paramtrica de grado 1 (o de primer grado), denotada
por

170

C 1 , entre dos curvas S~1

S~2

en el punto

p con t = , si hay continuidad G0

(en

9.2 Continuidad

Figura 9.3: Interpolacin

ese mismo punto y con el mismo valor de parmetro) y


si las curvas se unen en

S~10 ( ) = S~20 ( ).

y los vectores tangentes son iguales. Tambin se puede


cuando hay continuidad

G1

con

implica que la curva formada por la unin de

S~1

denir que hay continuidad


Ntese que

C 1 G1 ,

Ntese que

C1

pero

Es decir,

C1

k = 1.

G1 ; C 1 .
S~2

es suave en todo

su trayecto.
Se dice que hay continuidad paramtrica de grado n, denotada por
curvas

S~1

S~2

en el punto

con

t=

punto y con el mismo valor de parmetro, y si


Ntese que si los vectores

S~1

S~2

C n,

entre dos

, si hay continuidad C n1 , en ese mismo

(n)
(n)
S1 ( ) = S2 ( ).

representan (como funcin seccionada) la posicin de

un mismo objeto en funcin del tiempo,

C1

en su punto de unin, garantiza movimiento

2
suave y velocidad continua, y C all mismo, garantiza velocidad suave y aceleracin
continua. Esto es muy deseable para representar desplazamientos sin irregularidades.

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

y otro lado de dicho

intervalo.

171

9 Curvas Paramtricas
Veamos la gura 9.3. En ella, el intervalo mencionado en la denicin es

[a, b]. Se conocen

los puntos negros. Y el punto griz es un valor aproximado a partir de los puntos negros
que s son conocidos.

9.3.

Trazadores interpolantes cbicos - Curvas

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

comunmente

llamados nodos entre s; y que adems, pueden ser utilizados para interpolar puntos
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:

Las curvas Spline con frontera natural.

9.3.1. Curvas Spline y B-Spline


Con frecuencia, aparece en la literatura el trmino B-spline. Estas y las curvas Spline
no son las mismas. Ambas consisten de segmentos de curvas polinomiales cbicas, pero
la diferencia radica en dos detalles:

(1) Las curvas Spline interpolan todos los puntos

de control o nodos y las B-spline no.

(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

incidental si se quiere

es que

(3) las B-spline, se calculan

mucho ms rpido que las Spline, debido a la alta complejidad del algoritmo para calcular
las ltimas.

9.3.2. Descripcin geomtrica


El trmino Spline proviene de las largas tiras exibles de metal que usaban los constructores para establecer las supercies de aeroplanos, automviles y barcos. A estas tiras
se le colocaban pesos o prensas que servan para jalarlas en determinadas direcciones
para forzar la forma que adoptaran las tiras durante su vida til. Y a menos que se
sometan a grandes tensiones, estas tiras mantienen continuidad

C2

una vez liberadas de

las prensas.
En la actualidad, los dibujantes y arquitectos usan tiras semirgidas de plstico para
dibujar curvas suaves, necesitando a veces, que interpolen ciertos puntos preestablecidos.
Es el mismo principio que con los fuselajes.

172

9.3 Trazadores interpolantes cbicos - Curvas

Spline

9.3.3. Descripcin matemtica


Denicin (adaptado de [Burden y Faires 2002]): Dada una funcin
y un conjunto de nodos
para

denida en

[a, b]
S

un trazador interpolante cbico

es una funcin que cumple con las condiciones siguientes:

S(x)

1.

a = x0 < x1 < . . . < xn = b,

es una funcin seccionada, y en todos los casos, es un polinomio cbico,

Sj (x),

denotado por

en el subintervalo

2.

S(xj ) = f (xj )

3.

Sj+1 (xj+1 ) = Sj (xj+1 )

0
4. Sj+1 (xj+1 )
00
5. Sj+1 (xj+1 )

=
=

para cada

[xj , xj+1 ]

para cada

j = 0, 1, . . . , n 1;

j = 0, 1, . . . , n;

para cada

j = 0, 1, . . . , n 2;

Sj0 (xj+1 ) para cada j = 0, 1, . . . , n 2;


Sj00 (xj+1 ) para cada j = 0, 1, . . . , n 2;

6. Una de las siguientes condiciones de frontera se satisface:

a)
b)

S 00 (x0 ) = S 00 (xn ) = 0 (frontera libre o natural);


S 0 (x0 ) = f 0 (x0 ) y S 0 (xn ) = f 0 (xn ) (frontera sujeta).

El trazador

tiene la siguiente forma:

S(x) =

S0 (x)

S1 (x)

x0 x < x1
x1 x < x2

.
.
.
.

.
.

Sn1 (x) xn1 x xn

Sj (x) = aj + bj (x xj ) + cj (x xj )2 + dj (x xj )3
claro que Sj (xj ) = aj = f (xj ).

donde
Est

Hay varias cosas que hay que notar: Para un valor de

para cada

j = 0, 1, . . . , n 1.

concreto, se opera con el nodo

ms cercano por la izquierda. Por otro lado, es de recalcar que las condiciones 3, 4 y 5
garantizan continuidad

C 2.

Adems, la condicin 2 implica que la curva interpola todos

los nodos.

9.3.4. Algoritmo de clculo de coecientes


S de la funcin f , que se dene
S 00 (x0 ) = S 00 (xn ) = 0 se procede de

Para construir el trazador interpolante cbico natural


en los nmeros

x0 < x 1 < . . . < x n

y que satisface

la siguiente manera (algoritmo adaptado del algoritmo 3.4 de [Burden y Faires 2002, p.
146]):

Valores de entrada:

173

9 Curvas Paramtricas

n; x0 , x1 , . . . , xn ; a0 = f (x0 ), a1 = f (x1 ), . . . , an = f (xn )


Algoritmo:
1. INICIO
2. PARA

a)

hi = xi+1 xi

3. PARA

a)

i = 0, 1, ..., n 1

i = 1, 2, ..., n 1

i =

3
hi

(ai+1 ai )

3
hi1

(ai ai1 )

4. HACER :

l0 = 1
0 = 0
z0 = 0
5. PARA

i = 1, 2, ..., n 1

a ) HACER:

li = 2 (xi+1 xi1 ) hi1 i1


i = hlii
zi1
zi = i hi1
li
6. HACER:

cn = 0
7. PARA

j = n 1, n 2, ..., 0

a ) HACER:

cj = zj j cj+1
a
a
h (c
+2c )
bj = j+1hj j j j+13 j
dj =

cj+1 cj
3hj

8. FIN

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

9.3 Trazadores interpolantes cbicos - Curvas

Spline

9.3.5. Algoritmos de clculo de puntos interpolados

Clculo de un slo punto2


Este algoritmo recibe un valor de la variable independiente y calcula su imagen aproximada utilizando los trazadores.
Valores de entrada:

x; n; x0 , x1 , . . . , xn

aj , bj , cj , dj

para

j = 0, 1, . . . , n 1

1. INICIO
2. SI

x x0

a)

j=0

b ) MIENTRAS
1)

(j < n) (x > xj+1 )

j =j+1

j<n

c ) SI

1) HACER:

y = aj + bj (x xj ) + cj (x xj )2 + dj (x xj )3
o bien con la regla de Horner:

y = aj + (x xj ) (bj + (x xj ) (cj + (x xj )dj ))


2) FIN
3. ERROR: El valor de

x,

est fuera del dominio de

S .

4. FIN
Valores de salida:

para obtener

(x, y) S

ERROR!.

Clculo de todos los puntos


Este algoritmo recibe los trazadores y un valor

que indica el nmero de bloques in-

terpolados que se generarn (es decir que se calcularn


espaciados, en

p+1

puntos uniformemente

[x0 , xn ]).

Valores de entrada:

p; n; x0 , x1 , . . . , xn

aj , bj , cj , dj

para

j = 0, 1, . . . , n 1

1. INICIO
2. HACER:
3. PARA

i=1

k = 0, 1, . . . , p

x
ek = x0 + kp (xn x0 )
MIENTRAS (e
xk > xi )

a ) HACER:
b)
2

Este algoritmo implementa bsqueda lineal, pero podra ser ms eciente con bsqueda binaria.

175

9 Curvas Paramtricas
i=i+1
i=i1

1) HACER:

c ) HACER
d ) HACER:

yek = ai + bi (e
xk xi ) + ci (e
xk xi )2 + di (e
xk xi )3
o bien con la regla de Horner:

yek = ai + (e
xk xi ) (bi + (e
xk xi ) (ci + (e
xk xi )di ))
4. FIN
Valores de salida:

x
e0 , x
e1 , . . . , x
ep

ye0 , ye1 , . . . , yep .

9.3.6. Extensin para curvas paramtricas multidimensionales

El caso bidimensional
(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).

Para conectar los puntos

Se recomponen los pares ordenados dados como si fueran dos problemas diferentes, as:

(t0, x0 ), (t1, x1 ), ..., (tn , xn )


(t0, y0 ), (t1, y1 ), ..., (tn , yn )
Se construyen entonces dos trazadores interpolantes cbicos,

x = S (x) (t)

y = S (y) (t).

As que los valores que interpolan los puntos originales son:

(x, y) =



(x)
(y)

S
(t),
S
(t)

 0


S (x) (t), S (y) (t)


1
1
.

..

t0 t < t1
t1 t < t2
.
.
.




S (x) (t), S (y) (t)


tn1 t tn
n1
n1
donde

(y)

(x)

(x)

(x)

(x)

(x)

Sj (t) = aj + bj (t tj ) + cj (t tj )2 + dj (t tj )3
(y)

(y)

(y)

(y)

Sj (t) = aj + bj (t tj ) + cj (t tj )2 + dj (t tj )3

para cada

j = 0, 1, ..., n 1.

El caso tridimensional
Para conectar los puntos
una nueva variable

t0 < t1 < ... < tn .

176

(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

en un intervalo

9.3 Trazadores interpolantes cbicos - Curvas

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=

x = S (x) (t), y = S (y) (t)

S (z) (t). As que los valores que interpolan los puntos originales son:

(x, y, z) =



(x)
(y)
(z)

S
(t),
S
(t),
S
(t)

0
0
0




S (x) (t), S (y) (t), S (z) (t)


1
1
1

t0 t < t1
t1 t < t2

..

.
.


 .

(x)
(y)
(z)
S
tn1 t tn
n1 (t), Sn1 (t), Sn1 (t)
donde

(x)

(x)

(x)

(x)

(x)

Sj (t) = aj + bj (t tj ) + cj (t tj )2 + dj (t tj )3 ,

(y)

(y)

(y)

(y)

(y)

(z)

(z)

(z)

(z)

(z)

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

y
para cada

j = 0, 1, ..., n 1.

Eciencia en el clculo de un slo punto


Los algoritmos usados para construir y calcular estos trazadores interpolantes cbicos
paramtricos no deben, por razones de eciencia, realizarse de forma completamente
independiente. En lugar de eso, se debe procurar aprovechar los clculos para maximizar
la eciencia de tales procesos.
Como ejemplo de ello, considere la siguiente versin del algorito de clculo de un punto
interpolado, para el caso paramtrico con dos dimensiones:
Valores de entrada:

(x)

(x)

(x)

(x)

t; n; t0 , t1 , . . . , tn ; aj , bj , cj , dj ;

(y)

(y)

(y)

(y)

aj , bj , cj , dj

para

j=

0, 1, ..., n 1
1. INICIO
2. SI

t t0

a)

j=0

b ) MIENTRAS
1)

c ) SI

(j < n) (t > tj+1 )

j =j+1

j<n

177

9 Curvas Paramtricas
1) HACER:

(x)

(x)

(x)

(x)

(y)

(y)

(y)

(y)

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

t,

est fuera del dominio de

S .

4. FIN
Valores de salida:

x; y ,

para obtener

(x, y) S

ERROR!.

Eciencia en el clculo todos los puntos


As mismo, se muestra el algoritmo que calcula
en

p+1

puntos uniformemente espaciados,

[t0 , tn ]):

Valores de entrada:

(x)

(x)

(x)

(x)

p; n; t0 , t1 , . . . , tn ; aj , bj , cj , dj

(y)

(y)

(y)

(y)

aj , bj , cj , dj

para

j =

0, 1, . . . , n 1
1. INICIO
2. HACER:
3. PARA

i=1

k = 0, 1, . . . , p

= t0 + kp (tn t0 )

tk > ti
MIENTRAS e

a ) HACER: e
tk
b)

i=i+1
i=i1

1) HACER:

c ) HACER

d ) HACER:
(x)
(x)
(x)
(x)
x
ek = ai + bi (e
tk ti ) + ci (e
tk ti )2 + di (e
tk ti )3
(y)
(y)
(y)
(y)
yek = ai + bi (e
tk ti ) + ci (e
tk ti )2 + di (e
tk ti )3
4. FIN
Valores de salida:

x
e0 , x
e1 , . . . , x
ep

ye0 , ye1 , . . . , yep .

9.3.7. Ejemplo de implementacin


A continucacin presentamos un sencillo programa en lenguaje c estndar que implementa la manipulacin de trazadores interpolantes cbicos paramtricos de dos dimensiones.
La aplicacin simplemente muestra una pantalla con una serie de nodos conectados por
una curva spline y permite mover los puntos (los nodos) al tiempo que la curva se adapta
a la forma que los puntos exigen.

178

9.3 Trazadores interpolantes cbicos - Curvas

Spline

En los siguientes archivos se especican la estructura de datos usada y las funciones que
la operarn (ntese que es de propsito general).
Listing 9.1: Archivo de cabecera de funciones de Trazadores


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

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 "

# define
# define
# define
# define
# define

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 ]

*/
typedef struct 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

// nmero de polinomios
int num_trazadores ;

47
48
49

// siempre debe cumplirse la siguiente relacin :


// num_nodos = num_trazadores + 1

50
51
52
53

} trazadores ;

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

/*
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 []) ;

82
83
84
85
86
87
88
89
90
91
92
93
94
95

/*

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

9.3 Trazadores interpolantes cbicos - Curvas


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
140
141
142
143
144

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

*/
int TIC_actualizar_trazadorP ( trazadoresP * tr ) ;
void TIC_liberar_memoriaP ( trazadoresP * tr ) ;
void TIC_imprimirP ( trazadoresP * tr , FILE * f ) ;

1
2
3
4
5
6
7
8

Listing 9.2: Cdigo fuente de funciones de trazadores

// / 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 ;

10
11
12
13
14
15

/*
'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 ;
}

16
17
18
19
20
21
22
23
24
25
26
27

for ( i =0; i < 5 * num_nodos ; i ++)


tr - > x [ i ]=0.0;

28
29
30

tr - > a = tr - > y
tr - > x
tr - > b = tr - > a
tr - > c = tr - > b
tr - > d = tr - > c

31
32
33
34
35
36

=
+
+
+
+

num_nodos ;
num_nodos ;
num_nodos ;
num_nodos ;

tr - > num_trazadores = num_nodos - 1;

37
38

if (! TIC_modificar_trazador ( tr , x , y ) )
return tr ;

39
40

182

9.3 Trazadores interpolantes cbicos - Curvas


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

Spline

else {
TIC_liberar_memoria ( tr ) ;
free ( tr ) ;
return NULL ;
}

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 ;

73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88

// 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
);
// paso 4...
tmp [ L ][0] = 1;
tmp [ MIU ][0] = 0;
tmp [ Z ][0] = 0;

89
90
91
92
93
94

// 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;

95
96
97
98
99
100
101
102
103

// 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 ]) ;
}

104
105
106
107
108
109
110
111

// Liberar memoria temporal


free ( tmp [0]) ;

112
113
114
115
116
117
118

return TIC_COD_EXITO ;

int TIC_calcular ( trazadores * tr , double x , double * y ) {

119

if ( tr == NULL || y == NULL ) {
fprintf ( stderr , TIC_MSG_ERROR_PARAMETROS ) ;
return TIC_COD_ERROR_PARAMETROS ;
}

120
121
122
123
124

if ( x >= tr - > x [0]) {


/*
int j =0;

125
126
127
128

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 ;
}
*/

129
130
131
132
133
134
135
136

184

9.3 Trazadores interpolantes cbicos - Curvas


137

// Esta alternativa es ms eficiente que la de arriba :


int j =1;

138
139
140
141
142
143
144
145
146
147

148
149
151
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
183
184

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 ;

150
152

Spline

}
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
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 ;
}

185
186
187
188
189
190
191
192
193

for ( i =0; i < 9 * num_nodos ; i ++)


tr - > t [ i ]=0.0;

194
195
196

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 ;

197
198
199
200
201
202

tr - > ay = tr - > y =
tr - > dx +
tr - > by = tr - > ay +
tr - > cy = tr - > by +
tr - > dy = tr - > cy +

203
204
205
206
207
208

tr - > num_trazadores = num_nodos - 1;

209
210
211
212
213
214
215
216
217
218
219
220

num_nodos ;
num_nodos ;
num_nodos ;
num_nodos ;

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 ) {

221

if ( tr == NULL || x == NULL || y == NULL ) {


fprintf ( stderr , TIC_MSG_ERROR_PARAMETROS ) ;
return TIC_COD_ERROR_PARAMETROS ;
}

222
223
224
225
226

if ( t >= tr - > t [0]) {


int j =1;

227
228
229

while ( j <= tr - > num_trazadores && t > tr - > t[ j ])


j ++;
if ( - - j < tr - > num_trazadores ) {
double tmpT = t - tr - > t [ j ];

230
231
232
233

186

9.3 Trazadores interpolantes cbicos - Curvas


234
235
236
237

238
239
241
243

* ( tr - > bx [ j ] + tmpT * ( tr - > cx [ j ] +


);
* ( tr - > by [ j ] + tmpT * ( tr - > cy [ j ] +
);

// fprintf ( stderr , TIC_MSG_ERROR_DOMINIO ) ;


return TIC_COD_ERROR_DOMINIO ;

240
242

* x = tr -> ax [ j ] + tmpT
tmpT * tr - > dx [ j ] )
* y = tr -> ay [ j ] + tmpT
tmpT * tr - > dy [ j ] )
return TIC_COD_EXITO ;

Spline

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
275
276
277
278

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

// 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;

301

// 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;

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

// 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

9.3 Trazadores interpolantes cbicos - Curvas


tmp [ L ][0] = 1;
tmp [ MIU ][0] = 0;
tmp [ Z ][0] = 0;

328
329
330
331

// 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;

332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350

// 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 ]) ;
}
/* ******************* */

351

// Liberar memoria temporal


free ( tmp [0]) ;

352
353
354
355
356

Spline

return TIC_COD_EXITO ;

357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372

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.
Listing 9.3: Programa principal


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

9.3 Trazadores interpolantes cbicos - Curvas


b << 8
255;

45
46
47
48
49
50
51
52

// Borra la pantalla
SDL_FillRect ( pantalla , NULL , color_fondo );

54
55
56

// 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 ) ;
}

57
58
59
60
61
62
63
64
65

// 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 ) ;

66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
82
83
84
85
86
87
88
89
90
91
92

|
// 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 ;

53

81

Spline

// 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

if ( SDL_Init ( SDL_INIT_VIDEO ) < 0 ) {


fprintf ( stderr , " No se puede iniciar SDL : %s \ n" , SDL_GetError () ) ;
exit (1) ;
}
atexit ( SDL_Quit ) ;

94
95
96
97
98
99

// 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;
}

100
101
102
103
104
105
106
107
108

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 ) ;

109
110
111
112
113
114
115
116
117

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 ) ;

118
119
120
121
122
123
124
125
126

dibujo ( pantalla ) ;

127
128

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 ) ;
}

129
130
131
132
133
134
135
136
137
138
139

192

9.3 Trazadores interpolantes cbicos - Curvas


}

140

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 ;

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

2
3
4
5
6
7
8
9
10

case SDL_QUIT :
corriendo = 0;
break ;

SDL_Quit () ;
return 0;

Spline


Listing 9.4: Archivo Makele para la aplicacin

# / 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 )

9.3.8. Aplicacin para animaciones


Como se sabe, para realizar una secuencia animada, se necesita una serie de cuadros
con pequeas variaciones entre s y tienen que mostrarse a una velocidad sucientemente alta como para que el ojo humano no note el cambio y el cerebro (tambin
humano) reconstruya las partes faltantes.
Las computadoras pueden usarse para que el dibujante/artista no deba hacer tantos
cuadros sino slo unos cuantos, y sean estas quienes ayuden a completar los cuadros
necesarios para que el ojo humano no note el cambio.
La idea general es que el dibujante/artista slo realice algunos cuadros (mientras ms,
mejor) y estos cuadros se unan por curvas Splines a travs del tiempo.
El mecanismo es el siguiente: Todos los cuadros a fundir en una sla animacin deben
tener la misma cantidad de puntos de control (por puntos de control nos referimos
a puntos a partir de los cuales se puede construir toda la escena). Estos puntos deben
ser coherentes entre cada cuadro. A cada cuadro se le asigna un valor de tiempo en

194

9.3 Trazadores interpolantes cbicos - Curvas

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

editorSplines.py

que

se describe a continuacin:

arrastre con clic izquierdo

Sobre los cuadros que representan los puntos de control, per-

mite moverlos.
Sobre el fondo blanco, mueve el marco virtual.

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

Listing 9.5: Programa para generar animaciones sencillas en Python

# !/ 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

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) )

43
44
45
46
47
48

def dibujar ( matriz , ixS , pantalla , actualizarCurvas = True ) :


' ' ' Se recibe una matriz de puntos ,
y se dibuja la fila ixS - sima en la pantalla especificada .
'''

49

# Borrar la pantalla
if actualizarCurvas :
pantalla . fill ( __colorFondo )

50
51
52
53

# Dibujar la curva :
if __dibujarCurvas and actualizarCurvas :
pass

54
55
56
57

# Dibujar los cuadrados :


if __dibujarCuadros :

58
59

196

9.3 Trazadores interpolantes cbicos - Curvas


60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

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 ()

81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102

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
pantalla . fill ( __colorFondo )

103
104

# 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 )

105
106
107
108
109
110
111
112

# 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
)

113
114
115
116
117
118

# Redibujar el buffer
pygame . display . flip ()

119
120
121

# Hacer pausa
pygame . time . delay ( PAUSA )

122
123
124
125
126
127

if __name__ == " __main__ " :


pygame . init ()

128

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 " ) )

129
130
131
132
133
134
135
136
137
138
139
140
141

ratonPresionado = 0 # cdigo del botn del ratn que ha sido


presionado
punto = None
ixPunto = -1
ixSecuencia = 0

142
143
144
145
146

198

9.3 Trazadores interpolantes cbicos - Curvas


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
183
184
185
186
187
188
189
190
191
192
193
194
195
196

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
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

197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214

# Ruedas del ratn


elif evento . type == pygame . MOUSEBUTTONDOWN \
and not ratonPresionado \
and ( evento . button == RUEDA_ABAJO or evento . button ==
RUEDA_ARRIBA ) :

215
216
217
218
219

# 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 () ) )

220
221
222
223
224
225
226
227
228
229

# 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 ) )

230
231
232
233
234
235
236
237
238

# Hacer acercamiento o alejamiento

239

200

9.3 Trazadores interpolantes cbicos - Curvas


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
275
276
277
278
279
280
281
282
283
284
285

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
elif evento . type == pygame . MOUSEBUTTONUP :
if evento . button == ratonPresionado == BOTON_IZQ :
ratonPresionado = 0
punto = None
ixPunto = -1

286
287
288
289
290
291

# 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 )

292
293
294
295
296
297

# 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 )

298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320

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 )

321
322
323
324
325
326
327
328

elif evento . key == pygame . K_g :


guardarMatriz ( matriz )

329
330
331

# Aumentar o disminur el grueso de los cuadros

332

202

9.3 Trazadores interpolantes cbicos - Curvas

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 )

333
334
335
336
337
338
339
340
341
342

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 )

343
344
345
346
347
348
349

elif evento . type == pygame . KEYDOWN and \


evento . key == pygame . K_F1 :
print ( " Ayuda ... " ) # Pendiente ...

350
351
352
353

elif evento . type == pygame . QUIT :


pygame . display . quit () # Cierra la ventana y apaga el
subsistema de video
guardarMatriz ( matriz )
sys . exit ()

354
355
356
357

Spline

La biblioteca principal, que contiene la implementacin de algunos algoritmos es esta:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Listing 9.6: Biblioteca para Curvas Splines Bidimensionales en Python

# 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

=
=
=
=
=

0
1
2
3
4

A = 0

203

9 Curvas Paramtricas
16
17
18
19
20
21
22
23
24

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 }.

25

__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

26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

coef = [ 0 , listaCeros () , listaCeros () , listaCeros () ]


tmp = [ listaCeros () , listaCeros () , listaCeros () , listaCeros () ,
listaCeros () ]

44
45
46

# Clculo de los trazadores


n = numTrazadores
coef [ A ] = dependiente [:] # Copiar todo el arreglo

47
48
49
50

# paso 2...
for i in range ( n ) :
tmp [ __H ][ i ] = independiente [ i +1] - independiente [ i ]

51
52
53
54

# 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]
)

55
56
57
58
59
60
61

# paso 4...
tmp [ __L ][0] = 1
tmp [ __MIU ][0] = 0

62
63
64

204

9.3 Trazadores interpolantes cbicos - Curvas


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

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
return len ( self . puntos )
def numCuadros ( self ) :
return len ( self . puntos [0])

111
112
113
114

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

115
116
117
118
119
120
121
122
123
124

def dimensiones ( self ) :


" " " Devuelve las dimensiones de la pantalla donde debe mostrarse
la serie de puntos " " "
return self . e . maxxr , self . e . maxyr

125
126
127
128
129

def cambiarDimensiones ( self , tam ) :


" cambiar las dimensiones de la escala real "
self . e . maxxr , self . e . maxyr = tam

130
131
132
133

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 ) )

134
135
136
137
138
139
140

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 ]]
)

141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156

ix_t = 1
for t in [ float ( x ) / self . __numPasos for x in range ( self . __numPasos
+1) ]:

157
158

206

9.3 Trazadores interpolantes cbicos - Curvas


159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
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

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
documentoxml = implementacionxml . createDocument ( None , " splines " ,
None )
raiz = documentoxml . documentElement

201
202
203

curvas = documentoxml . createElement ( " curvas " )


curvas . setAttribute ( " pasos " , str ( self . __numPasos ) )
raiz . appendChild ( curvas )

204
205
206
207

rectangulos = documentoxml . createElement ( " rectangulos " )


rectangulos . setAttribute ( " ancho " , str ( self . anchoRectangulo ) )
rectangulos . setAttribute ( " grueso " , str ( self . gruesoRectangulo ) )
raiz . appendChild ( rectangulos )

208
209
210
211
212

escala = documentoxml . createElement ( " escala " )


escala . setAttribute ( " maxxr " , str ( self . e . maxxr ) )
escala . setAttribute ( " maxyr " , str ( self . e . maxyr ) )

213
214
215
216

escala . setAttribute ( " minxv " ,


escala . setAttribute ( " minyv " ,
escala . setAttribute ( " maxxv " ,
escala . setAttribute ( " maxyv " ,
raiz . appendChild ( escala )

217
218
219
220
221
222

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 )

223
224
225
226
227
228
229
230
231
232
233

f = open ( nombreArchivo , 'w ')


f . write ( documentoxml . toprettyxml () )
f . close ()

234
235
236
237
238
239
240
241

class Escala () :
" " " Clase de Escalas bidimensionales de ventana completa .

242

Esta clase permite modelar un espacio bidimensional cuadrado


con dos escalas diferentes : una Real y una Virtual .

243
244
245

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) .

246
247
248

208

9.3 Trazadores interpolantes cbicos - Curvas


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
286
287
288
289
290
291
292
293
294

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
' ' ' Convierte una ' distancia ' deltayv en la escala Virtual
a su correspondiente ' distancia ' en la escala Real ' ' '
return deltayv * self . maxyr / ( self . minyv - self . maxyv )

295
296
297
298

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

299
300
301
302
303
304
305
306

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.

9.4.1. Descripcin geomtrica


Estas curvas estn determinadas tpicamente por cuatro puntos ordenados, de los cuales
el primero y el ltimo determinan el inicio y n de la curva, y los otros dos describen los
vectores tangentes inicial y nal que controlan la trayectoria de la curva entre los puntos
inicial y nal. Es importante recalcar que estas curvas

no sirven para interpolar, ya

que no pasan por todos los puntos de control.

9.4.2. Descripcin matemtica


Dados cuatro puntos

P0 , P1 , P2 , P3

(que pueden ser unidimensionales, bidimensionales,

tridimensionales, etc.), llamados puntos de control, se dene la curva de Bzier de la


siguiente manera:

~
B(t)
= (1 t)3 P~0 + 3t(1 t)2 P~1 + 3t2 (1 t)P~2 + t3 P~3 , 0 6 t 6 1

(9.1)

es decir:

x(t) = (1 t)3 x0 + 3t(1 t)2 x1 + 3t2 (1 t)x2 + t3 x3 , 0 6 t 6 1,


y(t) = (1 t)3 y0 + 3t(1 t)2 y1 + 3t2 (1 t)y2 + t3 y3 , 0 6 t 6 1
z(t) = (1 t)3 z0 + 3t(1 t)2 z1 + 3t2 (1 t)z2 + t3 z3 , 0 6 t 6 1
3

aunque fueron inventadas por Paul de Casteljau en 1959

210

y adems,
(si es que aplica), etc...

9.4 Curvas de

Bzier

9.4.3. Polinomios de Bernstein


La teora bsica para calcular las curvas de Bzier se basa en la idea que cada punto

de la curva es un promedio ponderado de los puntos de control. Esto se consigue con


coecientes especialmente diseados para eso. Estos coecientes determinan el polinomio
que forma la ecuacin de Bzier, y son conocidos como

Polinomios de Bernstein, y

se denen as:

 
n
b(i, n, t) =
(1 t)ni ti
i
donde

n
i

n!
i!(ni)! ,

(9.2)

0 6 i 6 n N, 0 6 t 6 1.

es el grado del polinomio (para las curvas de Bzier normales, es 3.

es el ndice del

polinomio. As, los polinomios de Bernstein de grado 3 son:

b(0, 3, t) = (1 t)3
b(1, 3, t) = 3t(1 t)2
b(2, 3, t) = 3t2 (1 t)
b(3, 3, t) = t3
Sus grcas pueden apreciarse en la gura 9.4 en la pgina siguiente. Pero su caracterstica ms importante, servir para generar un promedio ponderado puede apreciarse en
la gura 9.5 en la pgina 213: El hecho de que su suma siempre resulte en 1 es lo que
permite usarlos para clculos de promedios ponderados uniformemente espaciados.

9.4.4. Generalizacin de curvas de

Bzier de grado n

En funcin de los Polinomios de Bernstein, se denen las curvas de Bzier de n-grado


como:

~ n (t) =
B

n
X

b(i, n, t)P~i

(9.3)

i=0
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):

B1 (t) = (1 t)P0 + tP1 , 0 6 t 6 1


B2 (t) = (1 t)2 P0 + 2t(1 t)P1 + t2 P2 , 0 6 t 6 1
B3 (t) = (1 t)3 P0 + 3t(1 t)2 P1 + 3t2 (1 t)P2 + t3 P3 , 0 6 t 6 1

211

9 Curvas Paramtricas

Figura 9.4: Grca de los Polinomios de Bernstein de grado 3

212

9.4 Curvas de

Bzier

Figura 9.5: Aporte de cada uno de los polinomios de Bernstein de grado 3

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 6 t 6 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 ,
06t61
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

Bzier, se reere implcitamente a Curvas de Bzier de Tercer Grado (a menos que se


especique lo contrario, por supuesto).

9.4.5. Unin de segmentos de Bzier


La gran utilidad de las curvas de Bzier es que son rpidas de calcular, pero tienen
la limitante que tienen un nmero jo de puntos de control. A este grupo de puntos
se les llama Segmentos de Curva de Bzier o simplemente Segmentos de Bzier. El
problema del tamao jo de los segmentos de Bzier, se resuelve ensamblando una serie
de segmentos para construir una secuencia de segmentos de Bzier. Obviamente es de
nuestro inters, construir una serie de segmentos de Bzier que no slo sea continua, sino
tambin suave. Para lograr esto, analicemos lo siguiente:
Para que haya continuidad

G0

en la unin de dos curvas de Bzier,

necesario que el ltimo punto de

coincida con el primero de

Q,

es

Q:

P3 = Q0
G1 , es necesario que haya continuidad G0 , y es necesario
tangente de P sea linealmente dependiente del primero de Q

Para que haya continuidad


que el ltimo vector

y adems tener la misma direccin:

P2 P3 = k Q0 Q1 , k > 0
Para que haya continuidad

C1

(muy deseable para la mayora de las aplicaciones),

es necesario adems de continuidad


igual al primero de

Q:

G1 ,

que el ltimo vector tangente de

sea


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

9.4.6. Ejemplos de implementacin


A continuacin se presentan dos aplicaciones sencillas, ilustrativas de manipulacin de
curvas de Bzier que se incluyen en el material adjunto a este libro:

bezier1 y bezier2.

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

Listing 9.7: Archivo de cabecera de operaciones de segmentos de Bzier

// / 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

*/
int BEZIER_calcular ( segmentoBezier * sb , double t , double *x , double *y ,
double * z ) ;
/*

41
42
43
44
45
46
47
48
49

*/

52
53

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_calcular2d ( segmentoBezier * sb , double t , double *x , double * y )


;
/*

50
51

Dado un valor del parmetro 't ',


devuelve los valores del punto calculado
del segmento de Bzier .
Asume que el segmento es tridimensional .

Imprime en la salida especificada los valores


almacenados en la estructura .

*/
void BEZIER_imprimir ( segmentoBezier * sb , FILE * f ) ;

Listing 9.8: Cdigo fuente de funciones de segmentos de Bzier


1
2
3
4
5
6
7
8

// / 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

10
11
12
13
14
15

*x =
+
*y =
+
*z =
+

16
17
18

216

_1_t1 = 1 - t ;
_1_t2 = _1_t1 * _1_t1 ;
_1_t3 = _1_t2 * _1_t1 ;
t2 = t* t ;
t3 = t2 * t ;

_1_t3
t3 *
_1_t3
t3 *
_1_t3
t3 *

* 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];

9.4 Curvas de
19
20
21
22
23
24
25
26

double
double
double
double
double

28
29
30
31
32
33
34
35

37
38
39
40
41
42
43
44
45
46

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 ;

27

36

Bzier

_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

Listing 9.9: Programa principal de manipulacin de un segmento de Bzier

// / 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

# 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 ;

52

// Borra la pantalla
SDL_FillRect ( pantalla , NULL , color_fondo ) ;

53
54
55

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 ) ;
}

56
57
58
59
60
61

tvar = 0.0;

62

218

9.4 Curvas de

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 ) ;

63
64
65
66
67
68
69
70
71
72
73
74

// 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 ) ;
}

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

// 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

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 ) ;

113
114
115
116
117
118
119
120
121

color_fondo = SDL_MapRGB ( pantalla - > format ,0 ,0 ,0) ;


color1 = gfxColor (255 ,255 ,255) ;
color2 = gfxColor (255 ,0 ,0) ;
color3 = gfxColor (0 ,0 ,255) ;

122
123
124
125
126

for ( i =0; i <4; i ++) {


sb . x [ i ] = x [ i ];
sb . y [ i ] = y [ i ];
}
// BEZIER_imprimir (& sb , stdout ) ;

127
128
129
130
131
132

dibujo ( pantalla ) ;

133
134

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) )

135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153

220

9.4 Curvas de
){

154
155
156
157
158
159
160

161
163
164
165
166
167
168
169
170
171
172
173
174
175

176
177
178
180

// se selecciona y el ndice queda en 'i '


seleccionado = 1;
// printf (" seleccionado \n ") ;
break ;

break ;
case SDL_MOUSEBUTTONUP :
seleccionado = 0;
break ;

162

179

Bzier

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

entre los segmentos o mantener

0
slo continuidad G .
Veamos su uso:

arrastre con clic izquierdo

Sobre los cuadros que representan los nodos de control, per-

mite transformar la secuencia, manteniendo por defecto continuidad

C1

en las

uniones de los segmentos.

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 .

Cuando se activa la continuidad

C1

la posicin de algunos

nodos es forzada para garantizarla.

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

Listing 9.10: Archivo de cabecera para operar secuencias de segmentos de Bzier

// / c09 / bezier / bezier2 . h


# include " bezier1 . h "
/* ********** Unin de segmentos de Bzier ************************ */
/*

7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

*/
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 ;
/*

26
27
28
29
30
31
32
33
34
35
36
37
38
39

42
43
44
45

Nodo de control para una secuencia de


segmentos de Bzier .

*/
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 ;
/*

40
41

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 .

Imprime en la salida especificada los valores


almacenados en la estructura .

*/
void BEZIER_imprimir2 ( listaBezier * lista , FILE * f ) ;
/*

222

9.4 Curvas de
46
47
48
49
50

' numNodos ' representa el nmero de segmentos de Bzier deseados


*/
int BEZIER_crearLista ( listaBezier * lista , int numNodos , double
valorInicial ) ;
/*

51
52
53
54
55
56
57
58

/*

60
62
63
64
65

68
69
70
71

/*

/*

73
74
76
77
78
79

/*

81
82
84
85
86
87
88
89
90

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 ) ;

80

83

Libera la memoria utilizada para


almacenar los ' nodoBezier '

*/
void BEZIER_liberarLista ( listaBezier * lista ) ;

72

75

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 ) ;

66
67

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 ) ;

59
61

Bzier

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

*/
int BEZIER_recuperarNodo2d ( listaBezier * lista , int ix , double *x , double
*y);
/*

99
100
101
102
103
104

107
108
109
110

/*

/*

112
114
115
116
117

/*

119
121
122
123
124
125
126

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 .

*/
int BEZIER_recuperarNodos2d ( listaBezier * lista , double *x , double * y ) ;

118
120

Copia los valores de todos los puntos a los arreglos 'x ', 'y ' y 'z '.
Estos arreglos deben contener suficiente espacio .

*/
int BEZIER_recuperarNodos ( listaBezier * lista , double *x , double *y ,
double * z ) ;

111
113

Dado un ndice , recuperar el punto


en las direcciones de 'x ', 'y ' y 'z '.

*/
int BEZIER_recuperarNodo ( listaBezier * lista , int ix , double *x , double *y
, double * z ) ;

105
106

Dado un ndice , recuperar el punto


en las direcciones de 'x ' y 'y '.

Garantiza que la curva sea continua , forzando


algunos puntos .
Tendrn prioridad los puntos previos .

*/
int BEZIER_acomodarContinua ( listaBezier * lista ) ;

void BEZIER_activarContinuidad ( listaBezier * lista ) ;


void BEZIER_desactivarContinuidad ( listaBezier * lista ) ;

127
128
129
130
131
132
133
134
135
136
137

/*
* 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

* */
int BEZIER_agregarNodoaLista ( listaBezier * lista , nodoBezier * nb ) ;

1
2
3
4
5
6
7
8
9
10
11

// 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 ) ;

14
15
16
17
18
19
20
21

24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

Listing 9.11: Cdigo fuente de funciones de secuencias de segmentos de Bzier

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 " ) ;

13

23

// / c09 / bezier / bezier2 . c


# include " bezier2 . h "
# include < stdlib .h >

12

22

Bzier

// ' 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
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 ;

43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

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 ;

63

lista - > x = x [ i ];
lista - > y = y [ i ++];
lista - > z = 0.0;

64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

}
return BEZIER_COD_EXITO ;

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 ;

84

lista - > x = x [ i ];
lista - > y = y [ i ];
lista - > z = z [ i ++];

85
86
87
88

for ( temp = &( lista - > primero ) ; temp != NULL ; temp = temp - > sig )
for ( j =0; j <3; j ++) {
temp - > x [ j ] = x [ i ];

89
90
91

226

9.4 Curvas de
temp - > y [ j ] = y [ i ];
temp - > z [ j ] = z [ i ++];

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

}
if ( lista - > continuidad )
BEZIER_acomodarContinua ( lista ) ;
return BEZIER_COD_EXITO ;

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

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 ;

159

nodoBezier * temp = NULL , * nb = NULL ;


int i =0 , j ;

160
161
162

// calcular el ndice del nodo ' nodoBezier '


// con el que debe calcularse este 't '
int ixBezier = ( int ) ( t * lista - > numNodos ) ;
if ( ixBezier == lista - > numNodos ) ixBezier - -;

163
164
165
166
167

t = lista - > numNodos * t - ixBezier ;

168
169

double
double
double
double
double
double

170
171
172
173
174
175
176

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

177
178
179
180
181
182

228

+ 3* t2 * _1_t1 * nb - >
+ 3* t2 * _1_t1 * nb - >
+ 3* t2 * _1_t1 * nb - >
se calcular 't '

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
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]) ;
}

228
229
230
231
232
233
234

segmentos de Bzier .
temp - > ant - > x [2] - (x - temp - > ant
temp - > ant - > y [2] - (y - temp - > ant
temp - > ant - > z [2] - (z - temp - > ant

}
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 ;

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

}
temp - > x [ j ]= x ;
temp - > y [ j ]= y ;
temp - > z [ j ]= z ;
return BEZIER_COD_EXITO ;

268
269
270
271
272

}
else i ++;

273
274

230

9.4 Curvas de
275
276
277
278
279
280

nodoBezier * temp = NULL ;


int i =0 , j ;

282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322

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 ;

281

299

Bzier

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

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 ;

331

nodoBezier * temp = NULL ;


int i =0 , j ;

332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349

nodoBezier * temp = NULL ;


int i =0 , j ;

351
352
353
354
355
356
357
358
359
360
362
363
364
365
366

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 ;

350

361

}
else i ++;
return BEZIER_COD_ERROR_DOMINIO ;

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 ;

367

nodoBezier * temp = NULL ;

368
369

for ( temp = (&( lista - > primero ) ) -> sig ; temp != NULL ; temp = temp - > sig ){
/*

370
371

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

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;
}

392
393
394
395
396

int BEZIER_agregarNodoNuevoaLista ( listaBezier * lista , int valorInicial ) {


if ( lista == NULL )
return BEZIER_COD_ERROR_PARAMETROS ;

397

nodoBezier * temp = NULL , * temp2 = NULL ;

398
399

if (!( temp = ( nodoBezier *) malloc ( sizeof ( nodoBezier ) ) ) )


return BEZIER_COD_ERROR_MEMORIA ;
for ( temp2 =&( lista - > primero ) ; temp2 - > sig ; temp2 = temp2 - > sig ) ;

400
401
402
403

temp - > ant = temp - > sig = NULL ;

404
405

temp2 - > sig = temp ;


temp - > ant = temp2 ;
lista - > numNodos ++;

406
407
408
409

temp - > x [0]= temp - > x [1]= temp - > x [2]=


temp - > y [0]= temp - > y [1]= temp - > y [2]= valorInicial ;

410
411
412
413
414
415
416
417
418
419
420

return BEZIER_COD_EXITO ;

int BEZIER_agregarNodoaLista ( listaBezier * lista , nodoBezier * nb ) {


if ( lista == NULL || nb == NULL )
return BEZIER_COD_ERROR_PARAMETROS ;
nodoBezier * temp2 = NULL ;

421

233

9 Curvas Paramtricas
for ( temp2 =&( lista - > primero ) ; temp2 - > sig ; temp2 = temp2 - > sig ) ;

422
423

nb - > ant = nb - > sig = NULL ;

424
425

temp2 - > sig = nb ;


nb - > ant = temp2 ;
lista - > numNodos ++;

426
427
428
429
430
431

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 :

Listing 9.12: Programa principal de manipulacin de una secuencia de segmentos de


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

Bzier

// / 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

# 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 ;

23
24
25
26
27
28
29
30
31
32
33

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

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 ) ;
}

79

235

9 Curvas Paramtricas
// 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 ) ;
}

80
81
82
83
84
85
86
87

// 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
);

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

// 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;

113

int corriendo = 1;
int seleccionado = 0;

114
115
116

mostrar_cuadrados = 1;
mostrar_lineas = 1;

117
118
119

if ( SDL_Init ( SDL_INIT_VIDEO ) < 0 ) {


fprintf ( stderr , " No se puede iniciar SDL : %s \n " , SDL_GetError () ) ;
exit (1) ;
}
atexit ( SDL_Quit ) ;

120
121
122
123
124
125

// Este if es importante para poder usar SDL_gfx


info = SDL_GetVideoInfo () ;

126
127

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

175
176
177

){

178
179
180
181
182
183
184

185

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 ;

break ;
case SDL_MOUSEBUTTONUP :
seleccionado = 0;
break ;

186
187
188
189
190

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 ;
}

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

238

9.4 Curvas de
break ;

221
222

case SDL_QUIT :
corriendo = 0;
break ;

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:

arrastre con clic izquierdo

Sobre los cuadros que representan los puntos de control, per-

mite moverlos.
Sobre el fondo blanco, mueve el marco virtual de todas las secuencias.

arrastre con clic izquierdo+CTRL

Sobre algn punto de control, mueve toda la secuen-

cia a la que pertenece el punto.

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

Alterna entre forzar continuidad

C1

entre los segmentos (por defecto)

0
o mantener slo continuidad G . Cuando se activa la continuidad

C1

la posicin

de algunos nodos es forzada para garantizarla. Igual que en la aplicacin anterior.

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.

Splines vs. Bzier

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

1. Mencione las diferencias entre las curvas paramtricas Spline y Bzier.


(t) = 2ti + 52 t2 j para 0 6 t 6 1 y sea (t) = (4t 2) i t4 + 3t2 32 j
5
para 1 6 t 6 2. Observe que (1) = 2,
2 = (1), de manera que ambas curvas
0
se unen con continuidad C .

2. Sea

(t) y (t) para 0 6 t 6 1 y 1 6 t 6 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.

a ) Graque
b)
c)

3. Muestre que las dos curvas

f (t) =





1 4
2 3
7 2
1
5 3
2 t + 318
j , con 0 6 t 6 1 y

t
+
t
+
t
i
+
t

t
2
9
6
3
3
3 3


2
16

12
sin(2t) + 9 i 3 cos 3 t j , con 1 6 t 6 2 tienen continuidad

g(t) =
G1 cuando

240

se unen en

f (1) = g(1).

C1

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

G0

tambin permita mantener continuidad

G1

(sin

C 1,

C1

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

R:

x = R cos
z = R sin

y=v

y radio

0 2
0vH

R = 4 y H = 6,
plot3d([4*cos(th),v,4*sin(th)], [th, 0, 2* %pi], [v,

En la gura 10.1 en la pgina siguiente se presenta un cilindro con


generado con el comando

0, 6]);

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

Toroide con radio central

243

10 Supercies paramtricas

Figura 10.1: Cilindro generado con Maxima

Figura 10.2: Toroide

244

10.2 Ejercicios

Figura 10.3: Cinta de Mbius

R y un ancho de la cinta A:


u

x = Av sin 2 + R cos u
0 u 2
y = Av sin u2 + R sin u ,
1
1

2 v 2
u
z = Av cos 2 + R

Cinta de Mbius con radio de la cinta

En la gura 10.3 se presenta una cinta de Mbius con

A=2

R = 5,

generada con el

script de Maxima:


1
2
3
4
5
6

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]) ;

Supercie paramtrica suave

~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).

Sugerencia 1: Hacer la parametrizacin en coordenadas cilndricas.


Sugerencia 2: Es una ecuacin seccionada.

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

Figura 11.1: Diagrama de clases de una malla poligonal en representacin explcita

Figura 11.2: Objeto tridimensional simple de ejemplo

Figura 11.3: Diagrama de objetos del objeto de la gura 11.2

248

11.2 Apuntadores a una lista de vrtices

Figura 11.4: Diagrama de clases de una malla poligonal en representacin de apuntadores


a una lista de vrtices

Es de notar que las coordenadas de los puntos

o vrtices v2 y v4 estn repetidos en

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.

Apuntadores a una lista de vrtices

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

que son compartidas por dos

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

Figura 11.5: Otro diagrama de objetos del objeto de la gura 11.2

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

o alterarlo de cualquier manera

se hace slo

una modicacin y automticamente todos los polgonos que incluyen tal punto estarn
actualizados.

11.3.

Apuntadores a una lista de aristas

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

11.3 Apuntadores a una lista de aristas

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

Figura 11.8: Diagrama de objetos del objeto de la gura 11.7

252

11.4 Apuntadores slo a una lista de aristas


lado, gracias a la referencia a los polgonos a los que pertenecen las aristas, es posible
discriminar algunos polgonos para que no sean dibujados, haciendo siempre el recorrido
sobre la lista de aristas.
En la gura en la pgina siguiente se presenta un ejemplo con los detalles de implementacin de este tipo de representacin. En ese caso, las listas se implementan como
listas circulares dobles con nodo de control.

11.4.

Apuntadores slo a una lista de aristas

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

transformaciones3D.jar y perspectiva3D.jar de los captulos 6 y 7 no es de ninguno


de los tipos presentados anteriormente.
Dado que para esas aplicaciones no es relevante el concepto de polgono (ya que cada
objeto tridimensional es simplemente una agrupacin de vrtices y aristas), tal clase de
objetos no existe. En la gura 11.10 se presenta la estructura de datos usada en ese caso.
A continuacin se presenta el cdigo (en lenguaje java) que implementa dicha estructura
de datos:


1
2
3
4
5
6
7
8
9
10
11
12
13

14

15

16

Listing 11.1: Cdigo de Objeto3DSimple.java

/* 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];
}

253

11 Mallas Poligonales

Figura 11.9: Detalles de implementacin de una malla poligonal en representacin de


apuntadores a una lista de aristas
254

11.4 Apuntadores slo a una lista de aristas

Figura 11.10: Diagrama de clases de las aplicaciones

transformaciones3D.jar

18

// 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 ;
}
}

19
20
21
22
23
24

25

26

27
28
29
30
31
33
34
35
36
37
38
39
40
41
42

perspectiva3D.jar

17

32

puntos [ i
+ m.e
puntos [ i
+ m.e
puntos [ i
+ m.e

}
class VerticeSimple {
Punto3d puntoReal , puntoProyectado ;
public VerticeSimple () {
puntoProyectado = new Punto3d (0 f ,0 f ,0 f ) ;
}
}
class AristaSimple {
VerticeSimple punto1 , punto2 ;

255

11 Mallas Poligonales
43
44

java . awt . Color color ;

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 Introduccin a los Fractales


En este captulo no haremos un estudio formal de la matemtica involucrada con los
fractales, sino ms bien un breve recorrido por algunas familias de fractales que son

relativamente fciles de gracar.

12.1.

Caractersticas de los fractales

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

3 y 12.64 muestran perfectamente

helecho que efectivamente parece real. Las guras 12.5

la caracterstica de la autosimilitud. La 12.7 muestra relmpagos fractales a partir del


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

12 Introduccin a los Fractales

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

12.1 Caractersticas de los fractales

Figura 12.3: Las hojas de casi todos los helechos tienen la caracterstica de la autosimilitud nita.

Figura 12.4: Helecho fractal

259

12 Introduccin a los Fractales

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

12.1 Caractersticas de los fractales

Figura 12.7: Otro acercamiento del conjunto de Mandelbrot realizado con la aplicacin

mandelbrot.out

presentada ms adelante en este captulo.

261

12 Introduccin a los Fractales

Figura 12.8: Copo de nieve de von Koch

12.2.

El copo de nieve de

El copo de nieve de

von Koch 5

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,

pero el rea que encierra es nita.

Podemos apreciar una representacin hecha con la aplicacin XaoS mencionada anteriormente en la gura 12.8.

lase fon koj

262

12.3 El tringulo de

12.3.

El tringulo de

Sierpiski

Sierpiski 6

Presentamos brevemente las famosas guras de

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

Gaston Julia y Pierre Fatou, los conjuntos Julia no

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).

El conjunto Julia de una funcin


conjunto de todos los valores

z,

fc (z)

con semilla

c C,

denotado por

Jc (f ),

es el

tales que la siguiente sucesin sea acotada:

z0 = z
zn+1 = fc (zn )

Tpicamente se calculan los conjuntos

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.

Por otro lado, el conjunto Fatou,


Es decir,

Ya encaminndonos a la implementacin, se puede demostrar que si


la sucesin no es acotada y

z
/ Jc (f ).

|zn | > 2

entonces

Ese es el critero a usar para saber si la sucesin

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

llega a un cierto lmite sin pasar de 2,

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

julia.out.

En el ttulo de

usados como semilla. Son las guras 12.12, 12.13,

12.14 y 12.15.

lase sierpiski

263

12 Introduccin a los Fractales

Figura 12.9: Tringulo de Sierpiski

264

12.4 Los Conjuntos

Julia-Fatou

Figura 12.10: Carpeta de Sierpiski

265

12 Introduccin a los Fractales

Figura 12.11: Pirmide de Sierpiski

266

12.4 Los Conjuntos

Figura 12.12: Imgen del programa

Julia-Fatou

julia.out

267

12 Introduccin a los Fractales

Figura 12.13: Segunda imgen del programa

268

julia.out

12.4 Los Conjuntos

Figura 12.14: Tercera imgen del programa

Julia-Fatou

julia.out

269

12 Introduccin a los Fractales

Figura 12.15: Cuarta imgen del programa

270

julia.out

12.4 Los Conjuntos

Julia-Fatou

12.4.2. Implementacin
En el material adjunto a este libro se encuentra la aplicacin

julia.out,

cuyo fun-

cionamiento se describe a continuacin:

clic izquerdo

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.

clic derecho

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.

rueda del ratn

Cambia el algoritmo de coloreado para el conjunto actual con la escala

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  color

i/6.

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


165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185

calculos.c):

Listing 12.1: Funcin de dibujo de Conjunto de Julia

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

12 Introduccin a los Fractales


186
187
188
189
190
191
192
193

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

Benot Mandelbrot, es un conjunto especco, bien denido, estrechamente

relacionado con los conjuntos Julia.

El conjunto Mandelbrot, denotado por

M,

es el conjunto de todos los valores

cC

tales que la siguiente sucesin sea acotada:

z0 = 0 + 0i
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

mandelbrot.out,

cuyo

funcionamiento se describe a continuacin:

clic izquerdo

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.

clic derecho

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  color

272

i/8.

12.5 Conjunto de

Figura 12.16: Forma clsica del conjunto Mandelbrot, generado con

Mandelbrot

mandelbrot.out

273

12 Introduccin a los Fractales

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

Listing 12.2: Funcin de dibujo de Conjunto de Mandelbrot


371
372
373
374

void dibujarFractalMandelbrot7 ( SDL_Surface * pantalla , escala * e ) {


int i , j ;
complejo z ={0.0 , 0.0} , c ;
int numPasos ;

375

for ( i =0; i <e - > anchoReal ; i ++) {


c . i = tV_Ry (e , i ) ;

376
377
378

for ( j =0; j <e - > altoReal ; j ++) {


c . r = tV_Rx (e , j ) ;

379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398

calculos.c):

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

1. Investigue cul es la relacin entre

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

12 Introduccin a los Fractales

Figura 12.18: Fractal de ejercicio

Figura 12.19: Fractal de otro ejercicio

276

Parte II

Otras Yerbas

277

13 Compilacin desde Mltiples archivos


fuente (en lenguaje C)
Supongamos que tenemos nuestro cdigo separado en diversos archivos fuente, tal como
el siguiente ejemplo:


1
2
3
4
5
6
7
8

/* c13 / principal . c
* */
# include " otro . h "
int main ( int argc , char * argv []) {
funcion () ;
return 0;
}

1
2
3

2
3
4
5
6
7
8
9

Listing 13.2: Cabecera de otro cdigo

/* c13 / otro . h
* */
int funcion ( void ) ;

Listing 13.1: Programa principal


Listing 13.3: Otro cdigo fuente

/* 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

make

para compilarlo de forma

automtica:
Debemos crear en ese mismo directorio un archivo

Makefile

para orientar a

make.

El

archivo de ayuda a la compilacin debera contener algo parecido a lo siguiente:

279

13 Compilacin desde Mltiples archivos fuente (en lenguaje C)


Listing 13.4: Makele para varios archivos fuente


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

# 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

26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# 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:

$ make limpiar
Borra los archivos de copia de seguridad que se hayan creado y tambin borra
los archivos de cdigo objeto intermedios (los

280

*.o)

que se crean al compilar los

respectivos archivos de cdigo fuente (los

*.c).

$ make limpiartodo
Invoca la instruccin anterior y adems borra el programa ensamblado o ejecutable
(su nombre depende de lo que hayamos puesto en la variable

$ make
Se ejecuta lo que hayamos indicado en la regla

all.

PROG.

En este caso, invoca la regla

PROG, que a su vez invoca la compilacin individual de cada archivo fuente (los *.c)
y posteriormente invoca el ensamblaje de estos con el comando gcc. Posteriormente
invoca la regla limpiar.
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:

$ man make
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

13 Compilacin desde Mltiples archivos fuente (en lenguaje C)

282

14 Diagramas de Clases y Diagramas de


Objetos
(una untadita de UML)
Haremos en este captulo un breve resumen de la notacin UML (Unied Modeling
Language) para diagramas de clases y objetos.

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

Clase, tiene dos atributos y dos operaciones. Los atributos son i, un

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

Clase (s, del mismo tipo). La segunda funcin se llama funcion,

retorna un String y recibe dos parmetros: semilla que es entero con valor por defecto
de 3, y objeto de tipo Estructura.

Figura 14.1: Representacin de una clase

283

14 Diagramas de Clases y Diagramas de Objetos

(una untadita de UML)

Figura 14.2: Diagrama de clase ocultando sus atributos y operaciones

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  # 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.

Notacin de relaciones o asociaciones

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

Clase2.

2. Indica que hay una relacin uno-a-uno entre las instancias, pero agrega semntica
a la relacin: indica que las instancias de

Clase1 tienen una instancia de la clase

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

Clase1 puede tener referencias a ningna, una o

Clase2 y que una instancia de Clase2 tiene forzosamente 2

referencias a instancias de

Clase1.

Estos elementos que indican cantidad de referencias se llaman  multiplicidades .


5. Indica que una instancia de
de

Clase1 debe tener al menos una referencia a instancias

Clase2 y que una instancia de Clase2 tiene forzosamente entre 2 y 4 referencias

a instancias de

Clase1.

6. Indica que hay una relacin uno-a-uno, pero indicando que para las instancias de

Clase1, la referencia a la instancia de Clase2 se llama objetoUtil y es privada.


Dice adems, que para las instancias de

Clase1 se llama apuntado y es pblica.


Estos nombres se conocen como  roles .

284

Clase2, la referencia a la instancia de

14.2 Notacin de relaciones o asociaciones

Figura 14.3: Relaciones bidireccionales entre clases


285

14 Diagramas de Clases y Diagramas de Objetos

(una untadita de UML)

Figura 14.4: Otros tipos de relacin entre clases

7. Indica que las instancias de


al menos 2 instancias de

Clase1 deben tener una coleccin de referencias a

Clase2 y que esa coleccin se llama objetosUtiles. Dice

adems, que las instancias de


stancias de

Clase2 tienen una coleccin de 5 referencias a in-

Clase1 que se llama apuntados. Indica adems el nivel de acceso de

dichas colecciones.
8. Indica que una instancia de
y que las instancias de

Clase1 tiene 5 referencias a instancias de Clase2,

Clase2 son referenciadas por 3 instancias de Clase1.

Tambin dice que las instancias de


relacionadas de

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

Clase1 estn formadas (entre

otras cosas) por una coleccin de referencias a algunas instancias de


estas tienen una referencia a la instancia de

286

Clase2, y

Clase1 a la cual estn agregadas.

14.3 Notacin de objetos

Figura 14.5: Representacin de una instancia de una clase

Tambin indica que si la instancia agregada de


stancias de

Clase1 deja de existir, las in-

Clase2 de las cuales estaba fomada, no tienen por qu dejar de existir.

2. Indica una agregacin en la que las instancias de

Clase1 estn formadas (entre

otras cosas) por una coleccin de entre 1 y 4 referencias a instancias de


que estas conforman simultaneamente dos instancias de

Clase2, y

Clase1, pero no contienen

referencia a ellas.
3. Indica una composicin, que es una agregacin muy restrictiva, ya que indica que
si la instancia compuesta de

Clase1 deja de existir, las instancias de Clase2 que

la componan, deben tambin dejar de existir. En consecuencia, las instancias de

Clase2 slo pueden componer una instancia de Clase1.


4. Esta es una composicin en la que las instancias de
a la instancia de

Clase2 no tienen referencia

Clase1 que componen.

Clase2 hereda de Clase1. Algunos lenguajes de programacin


1
permiten herencia mltiple , otros no. Java no lo permite directamente, Python

5. Indica que la clase


s.

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:

<nombreInstancia>: <nombreClase>
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.

es decir, que una clase pueda heredar de varias clases simultaneamente

287

14 Diagramas de Clases y Diagramas de Objetos

(una untadita de UML)

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

A Plantilla de Aplicacin Grca en


J2SE
A continuacin se presenta el esqueleto de una aplicacin grca en lenguaje java. Es la
idea usada para las aplicaciones

transformaciones3D.jar

perspectiva3D.jar.

Se sigue la idea de un diseo Modelo-Vista-Controlador como en la gura A.1.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Listing A.1: Clase controladora

/* 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 ) ;

Figura A.1: Modelo de diseo Modelo-Vista-Controlador

291

A Plantilla de Aplicacin Grca en J2SE


}

17
18

Ventana vista ;
Modelo modelo ;

19
20
21

/* *

22

Hilo principal
*/
public Controlador ( String args []) {
vista = new Ventana ( this ) ;
modelo = new Universo3D () ;

23
24
25
26
27
28

// mostrar la ventana
vista . setVisible ( true ) ;
System . out . println ( " Iniciando aplicacin " ) ;

29
30
31
32

// accin principal o
// conjunto de acciones principales
modelo . hacerAlgo () ;

33
34
35
36
37
38
39

40
41

/* *
* Simple delegacin del proceso
* */
public void dibujar ( Graphics g ) {
modelo . dibujar ( g ) ;
}

42
43
44
45
46
47
48

/* *
* 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) ;
}
};

49
50
51
52
53
54
55
56
57
58
59
60

}
// fin de la clase Controlador

1
2
3

// cuando ya acabamos de hacer algo importante ,


// terminamos la aplicacin
System . exit (0) ;

Listing A.2: Clase del modelo

/* cA / Modelo . java
* Objeto principal de la lgica
* de la aplicacin

292

4
5
6
7

*/
import java . awt .*;
public class Modelo {

public Modelo () {
/* *
* Echar a andar todos
* los mecanismos del modelo
* y toda su lgica .
* */
}

9
10
11
12
13
14
15
16

public hacerAlgo () {
/* *
* Aqu debera estar el corazn
* de la ejecucin del modelo .
* */
}

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

1
2
3
4
5
6
7

/* *
* 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

/* cA / Ventana . java
* Objeto vista ,
* tpicamente la Ventana de la aplicacin
*/
import java . awt .*;
public class Ventana extends javax . swing . JFrame implements Config3D {

293

A Plantilla de Aplicacin Grca en J2SE


/* *
* Referencia al controlador para
* delegarle la respuesta a los
* eventos generados desde aqu .
* */
private Controlador control ;

9
10
11
12
13
14
15

/* *
* Objeto especial para lograr
* la delegacin de las solicitudes
* de refrescamiento
* */
private PanelEspecial panelprin ;

16
17
18
19
20
21
22
23

public Ventana ( Controlador control ) {


this . control = control ;
inicializarComponentes () ;
}

24
25
26
27
28

/* * 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 () {

29
30
31
32
33
34

panelprin = new PanelEspecial ( control ) ;

35
36

setDefaultCloseOperation ( javax . swing . WindowConstants .


EXIT_ON_CLOSE ) ;
setTitle ( " Ttulo de la aplicacin " ) ;

37
38
39

/* *
* 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 ) ;

40
41
42
43
44
45
46
47
48
49
50
51
52

panelprin . setBorder ( new javax . swing . border . LineBorder ( new java .


awt . Color (0 , 0 , 0) ) ) ;
panelprin . setMinimumSize ( new java . awt . Dimension (400 , 300) ) ;

53
54
55

// agregar el panel principal a la ventana

56

294

getContentPane () . add ( panelprin , java . awt . BorderLayout . CENTER ) ;

57
58

// acomodar dinmicamente los objetos grficos


pack () ;

59
60
61

// 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) ;

62
63
64
65
66
67
68

}
}
// fin de la clase vista

1
2
3
4
5
6
7
8
9

Controlador control ;

11

public PanelEspecial ( Controlador control ) {


this . control = control ;
}

12
13
14
15

/* *
* 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 ) ;
}

16
17
18
19
20
21
22
23
25
26

Listing A.4: Clase especial para delegar el refrescamiento

/* 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 {

10

24

}
// fin de la clase PanelEspecial

Luego de este breve ejemplo, conviene mencionar cmo compilar el cdigo fuente y cmo
ensamblarlo en un slo archivo

.jar

en lugar de ejecutarlo desde los archivos

.class.

Bueno, la compilacin se realiza as:

$ javac Controlador.java

295

A Plantilla de Aplicacin Grca en J2SE


Se sobreentiende que el archivo de clase indicado es el que debe contener la funcin

main.

Esto provocar la compilacin en cascada de todos las clases necesarias para que el

main

de

Controlador.java

se ejecute sin problemas.

Para ejecutar el programa se puede hacer:

$ java Controlador
Pero en lugar de dejar todos los archivos

.class,

podra realizarse la generacin de un

archivo jar luego de la compilacin, as:

$ jar -cfe aplicacion.jar Controlador *.class


La opcin  c indica que se desea crear un archivo
archivos

.jar,

.jar (el comando sirve para manipular

no slo para crearlos).

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

.class

generados en el paso de compilacin.

Para mayor informacin sobre el comando

jar,

ver

http://java.sun.com/docs/books/tutorial/deployment/jar/ o ejecutar:

$ man jar

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

http://inventwithpython.com/
http://en.wikibooks.org/wiki/Python_Programming
http://openbookproject.net//thinkCSpy/
http://pyspanishdoc.sourceforge.net/
http://www.pygame.org/
http://vpython.wikidot.com/
http://www.vpython.org/
http://www.diveintopython.org/

B.3.

Java Me

B.3.1. Sitios de recursos


http://java.sun.com/javame/
http://www.agserrano.com/publi.html
http://programacion.com/java/tutorial/ags_j2me/

298

Bibliografa
[Foley et al.]

Foley, James D.; van Dam, Andries; Feiner, Steven K.; Hughes, John F.; Phillips, Richard L.

Introduccin a la gra-

cacin por computador. Addison-Wesley Iberoamericana,


1996.
[Burden y Faires 2002]

Burden, Richard L.; Faires, J. Douglas.

Anlisis Numrico.

Sptima edicin, Thomson Learning, 2002.


[Henrquez 1999]

Henrquez, Mauro Hernn.

Clculo integral en una vari-

able real. UCA editores, 1999.


[Henrquez 2001]

Henrquez, Mauro Hernn.

Clculo diferencial en una

variable real. UCA editores, 2001.


[RAE]

RAE,

Diccionario de la real academia de la lengua es-

paola. Vigsima segunda edicin 2001.


[Wikipedia-Bzier Curve] Comunidad de Wikipedia en Ingls.

Wikipedia - Bzier

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

También podría gustarte