Curvas de Bézier Aplicación práctica en Java
Jorge Torregrosa Lloret Escuela Politécnica Superior Grado Ing. Multimedia
1
2
Curvas de Bézier Aplicación práctica en Java
Jorge Torregrosa Lloret Escuela Politécnica Superior Universidad Alicante
3
4
Índice 1. Introduc ción………………………………………………………
7
2. Polinomios de Bernstein ………………………………………….
9
2.1 Introducción……………………………………………………………….. Introducción……………………………………………………………….. 2.2 Definición………………………………………………………………….. Definición………………………………………………………………….. 2.3 Ejemplos de polinomios de Bernstein ……………………………………… 2.4 Propiedades polinomios de Bernstein ……………………………………… 2.5 Anexo de programación……………………………………………………. programación……………………………………………………. 2.6 Sabías que… ……………………………………………………………….
3. Curvas de Bézier …………………………………………………. 3.1 Introducción……………………………………………………………….. Introducción……………………………………………………………….. 3.2 Definición………………………………………………………………….. Definición………………………………………………………………….. 3.3 Ejemplos de curvas de Bézier……………………………………………… Bézier ……………………………………………… 3.4 Propiedades curvas de Bézier ……………………………………………… 3.5 Propiedad recursiva de las curvas de Bézier ………………………………. 3.6 Derivadas de curvas de Bézier …………………………………………….. 3.7 Elevación de grado………………………………………………………… grado ………………………………………………………… 3.8 Curvas de Bézier racionales………………………………………………... racionales ………………………………………………... 3.9 Anexo de programación……………………………………………………. programación……………………………………………………. 3.10 Sabías que… ……………………………………………………………..
4. Algoritmo de Casteljau …………………………………………... 4.1 Introducción………………………………………………………………... Introducción………………………………………………………………... 4.2 Definición………………………………………………………………….. Definición………………………………………………………………….. 4.3 Ejemplos Casteljau…………………………………………………………. Casteljau …………………………………………………………. 4.4 Anexo de programación……………………………………………………. programación……………………………………………………. 4.5 Sabías que… ……………………………………………………………….
5
9 9 10 13 17 19
21 21 21 22 29 31 32 34 36 37 45
48 48 48 50 52 61
6
1. Introducción A lo largo de estos apartados trataremos de explicar las características básicas de las curvas de Bézier, así como los métodos para obtenerlas. Pero claro, ahora surgirá típica pregunta, ¿curvas de Bezi qué? Las curvas de Bézier son un sistema que se desarrollo sobre 1960 para realizar dibujos técnicos en el ámbito profesional. Su nombre es en honor de quién ideó un método de descripción matemática de las curvas, Pierre Bézier. A continuación una ilustración de lo que es una curva de Bézier:
Como podemos observar, las partes más importantes son los nodos de inicio y final (b 0 y b3) y los puntos de control mediante los cuales podemos modificar la forma de la curva. Las curvas de Bézier han sido abundantemente usadas en gráficos para ordenador, CAD, dibujo técnico, etc. Como la curva está completamente contenida en la envolvente convexa de los puntos de control, estos puntos pueden visualizarse gráficamente sobre un área de trabajo. Podemos observar como muchos de los programas de diseño incorporan estas curvas como “Pluma”, “Pluma”, ”Lápiz”, Lápiz”, “Trazados Bézier”, etc. en su interfaz. Adobe Illustrator, Inkscape o Gimo, entre otros, las incorporan. Vemos en la imagen de al lado como una sucesión de curvas de Bézier pueden definir el contorno de una figura.
7
Al final de algunos apartados incluiremos algún ejemplo de código útil en java, con el fin de añadir una parte más práctica a toda la teoría de Bézier. Aunque hay que añadir que estos códigos pueden estar más o menos depurados, eficientes y elegantes. La perfección de este código se amoldará a los conocimientos de 1º de Grado en Ing. Multimedia.
8
2. Polinomios de Bernstein 2.1 Introducción Sergéi Natánovich Bernstéin (n. 5 de marzo de 1880 en Odessa - Ucrania, Imperio ruso - 26 de octubre de 1968 en Moscú) era un importante matemático. Estudió de 1899 a 1904 en la Sorbonne (Sorbona) de París y en la École supérieure d'électricité (Escuela superior de electricidad) obteniendo el doctorado en 1904. En Rusia realizó un curso para revalidar los estudios en Francia y para obtener los diplomas que le daban derecho a enseñar. A partir de 1907, dio clases en la universidad de la ciudad de Járkov en Ucrania, allí se recibió de doctor en matemáticas en el año 1913. Seguidamente dio enseñanza en Leningrado y en en Moscú. Su tesis de doctorado presentada en la Sorbona en 1904 resolvía el 19° problema de Hilbert. Los trabajos de S. Bernstéin llevaron a la aproximación de las l as funciones y la teoría de las probabilidades. es.wikipedia.org
2.2 Definición Los polinomios de Bernstein son una clase especial de polinomios en el campo de los números reales. Son bastante utilizados en el ámbito del análisis numérico. El algoritmo de evaluación más numéricamente estable es el de Casteljau, el cual estudiaremos en posteriores capítulos. Estos polinomios tienen un papel clave en la definición de una curva de Bézier, puesto que estos forman parte de la fórmula que la compone. Definimos los polinomios de Bernstein como:
9
Vemos como esta definición está compuesta por números combinatorios. Como recordatorio, añadiremos que o número combinatorio se representa como:
Y se desarrolla de la siguiente manera:
Estos peculiares números se caracterizan por seguir una serie de propiedades que enunciaremos a continuación:
Los números de este tipo se llaman complementarios
A continuación visualizaremos gráficamente dichos polinomios. Mostraremos como ejemplo los polinomios de grado n=2, n=3 y n=4.
2.3 Ejemplos de polinomios de Bernstein Polinomios de Bernstein de grado 2 Nos ayudaremos de un software de representación gráfica como puede ser Geogebra para ilustrar los ejemplos.
10
Polinomios de Bernstein de grado 3 Sin duda se tratan de los polinomios más utilizados. Nos ayudaremos de un software de representación gráfica como puede ser Geogebra para ilustrar los ejemplos.
11
Polinomios de Bernstein de grado 4
12
2.4 Propiedades polinomios de Bernstein Para un grado n, existen n+1 polinomios de Bernstein definidos sobre el intervalo [a,b], por:
Estos polinomios presentan estas propiedades importantes, que cumplen cualquier valor de u en el intervalo [a,b]
Partición de la unidad:
Comprobación: Supongamos Supongamos que tenemos los polinomios de Bernstein de grado 2:
Ahora los sumamos todos:
Positividad:
Comprobación: Supongamos Supongamos que tenemos los polinomios de Bernstein de grado 2:
Visualizando la gráfica de los polinomios, observamos que en el intervalo [0,1], siempre son positivos:
13
Simetría:
Demostración: Geogebra nos permite visualizar de manera gráfica los polinomios. Poniendo como ejemplo dos polinomios, observamos que :
Son simétricos respecto x=1/2:
14
Son simétricos x=1/2:
Al cumplirse las propiedades de “Partición de la unidad” y “Positividad”, podemos decir que forman una combinación convexa. Una combinación convexa es una combinación lineal de puntos donde todos los coeficientes son no-negativos y suman 1.
Gran semejanza de estos polinomios con la distribución binomial.
La distribución binomial es una distribución de probabilidad discreta que mide el número de éxitos en una secuencia de n ensayos independientes de Bernoulli con una probabilidad fija p de ocurrencia del éxito entre los ensayos. La binomial posee la siguiente función de probabilidad:
Comparada con la de los polinomios de Bernstein:
Sus únicas raíces son 0 y 1. Comprobación: 15
Pondremos como ejemplo los polinomios de grado 2 por su sencillez:
Son linealmente independientes. independientes. Comprobación: Pondremos como ejemplo los polinomios de grado 2 por su sencillez:
Son linealmente independientes independientes
Satisfacen la relación de recurrencia:
Cada polinomio de Bernstein tiene un único máximo, que se alcanza en
.
Así que si se modifica el valor de b i, la curva de Bézier sufre su mayor variación en
.
16
3.5 Anexo de programación Como bien ya se ha descrito anteriormente, el lenguaje a utilizar será Java. A continuación describiremos la implementación del algoritmo para mostrar los resultados de los polinomios de Bernstein.
Clase Bernstein import java.io.BufferedReader; import java.io.InputStreamReader; public class Bernstein
{ //Devuelve un array de Strings, cada posición es un polinomio. public static String[] CalculoBernstein( int grado) { if (grado != 0) { int m = grado, n = 0; String[] resultado = new String[grado + 1]; for (int i = 0; i < grado + 1; i++)
{ resultado[i] = "B(" + m + "," + n + "): long comb = comb (m, n); if (comb != 1) resultado[i] += "(" + comb; if (n != 0) { if (n == 1) resultado[i] += "u" "u"; ; else
resultado[i] += "u^" + n; } if (m - n != 0)
{
17
"; ";
if (m - n == 1) if (comb == 1)
resultado[i] += "(1-u)" "(1-u)"; ; else
resultado[i] += ")*(1-u)" ")*(1-u)"; ; else if (n == 0)
resultado[i] += "(1-u)^" + (m - n); else
resultado[i] += ")*(1-u)^" + (m - n); } n++; } return resultado;
} else return null;
} //Devuelve el resultado del numero combinatorio indicado public static long comb(int n, int i) { return fact(n) / ( fact(i) * fact(n - i)); } //Realiza el factorial de un numero de forma recursiva public static long fact(int n) { if (n == 0) return 1; else return n * fact(n - 1);
} public static void main(String[] args)
{ String[] aux = new String[0]; String s = "" ""; ; try
{ BufferedReader br = new BufferedReader( new
InputStreamReader(System. in));
System.out.println("Inserta .println( "Inserta el grado de Polinomio de Bernstein deseado(<0):" ); s = br.readLine(); } catch (Exception e) { e.printStackTrace(); } try
{ parseInt (s)<=20) if(Integer. parseInt(s)>0 && Integer. parseInt
{ aux = CalculoBernstein (Integer. parseInt(s)); if(aux!= null) { aux.length; ; i++) for (int i = 0; i < aux.length { System.out.println(aux[i]); } } } else
System.out.println("Inserte .println( "Inserte grado más pequeño debido a problemas de precision" ); }catch(NumberFormatException ex){System. out.println("Entrada .println( "Entrada Incorrecta");} Incorrecta" );} } }
18
Debido a el tipo de dato “long” tiene un rango de representación: (-9223372036854775808, 9223372036854775807) Y 21! Se sale del rango, hemos restringido el máximo grado a 20. Una salida ejemplo de la aplicación sería:
as que… Sabí as
Los polinomios de Bernstein son utilizados para demostrar el teorema de aproximación de Weierstrass y por esto son también utilizados para efectuar aproximaciones e interpolaciones de funciones como, por ejemplo, la curva de Bézier, así como para la estimación de las funciones de densidad de probabilidad.
as que… Sabí as
Bernstein solucionó el problema número 19 de los Problemas de Hilbert en su tesis de doctorado. Dicho problema decía así: ¿Son siempre analíticas las soluciones de los Lagrangianos? A lo que Bernstein dijo que sí. Los problemas de Hilbert conforman una lista l ista de 23 problemas matemáticos compilados por el matemático alemán David Hilbert para la conferencia en París del Congreso Internacional de Matemáticos de 1900. Los problemas estaban todos por resolver en aquel momento, y varios resultaron ser muy influyentes en la matemática del siglo XX. 19
as que… Sabí as
La representación de los polinomios de Bernstein de grado 10 es la siguiente:
Para la representación representación nos hemos ayudado ayudado del programa programa creado anteriormente anteriormente para calcular los polinomios que posteriormente insertamos en Geogebra.
20
3. Curvas de Bézier 3.1 Introducción Pierre Étienne Bézier , (1 de septiembre de 1910 - 25 de noviembre de 1999), fue un eminente ingeniero y el creador de las llamadas curvas y superficies de Bézier que lógicamente llevan su nombre. En la actualidad se usan de manera corriente en la mayoría de los programas de diseño diseño gráfico y de diseño diseño CAD. Trayectoria Nace en París el 1 de septiembre de 1910. Obtiene el título de ingeniero mecánico de la École nationale supérieure d'arts et métiers en 1930. Consigue, un año más tarde, el título de ingeniero eléctrico de la École supérieure d'électricité, y se doctora en matemáticas por la Universidad de de París en 1977. Trabajó para la Renault en el periodo 1933 a 1975, donde desarrolló un sistema de diseño asistido por ordenador denominado UNISURF CAD. Hizo toda su carrera en el puesto de director de métodos mecánicos. mecánicos. Al final de su vida, por discordancias con sus superiores, pudo concentrarse en la modelización de superficies. Desde 1968 a 1979 fue profesor de ingeniería de producción en el Conservatoire National des Arts et Métiers. En 1985 fue reconocido por la ACM SIGGRAPH con un premio Steven Anson Coons por sus contribuciones, a lo largo de su vida, en los gráficos generados por ordenador y en las técnicas técnicas interactivas. www.es.wikipedia.org
3.2 Definición Una curva de Bézier de grado n se puede generalizar de la siguiente manera: Suponemos que tenemos un conjunto de puntos que definen la curva:
Donde es un polinomio de Bernstein:
21
Los coeficientes b i de la expresión anterior que utilizamos para definir una curva de Bézier son los llamados puntos de Bézier. Estos puntos son los vértices de lo que denominamos Polígono de Bézier de b(u). Un punto importante que y que hay que destacar, es que la representación de Bézier hereda las propiedades de los polinomios de Bernstein que veíamos en el capítulo anterior.
3.3 Ejemplos curvas de Bézier Curva de Bézier de grado 1 Para n=1 tenemos los siguientes polinomios de Bernstein:
A partir de ellos definimos la curva de Bézier:
Observaciones:
La curva de Bézier de grado 1 dibuja una línea recta que va desde b 0 hasta b1 .
Curva de Bézier de grado 2 Para n=2 tenemos los siguientes polinomios de Bernstein:
22
A partir de ellos definimos la curva de Bézier:
Observaciones:
La curva de Bézier de grado grado 2 dibuja una curva con su inicio en b 0 y final en b 2 . La curva se encuentra contenida en el polígono (triángulo) formado por los puntos b0 , b1 y b2 . Dicho polígono se denomina polígono de control. Cualquier modificación en alguno de sus puntos supone la modificación de la curva. Nos resulta imposible crear bucles en la curva solo con 3 puntos.
Curva de Bézier de grado 3 (cúbicas) Sin lugar a dudas, son las curvas de Bezier más utilizadas. Para n=3 tenemos los siguientes polinomios de Bernstein:
A partir de ellos definimos la curva de Bézier:
23
Observaciones:
La curva de Bézier de grado grado 3 dibuja una curva con su inicio en b 0 y final en b 3 . La curva se encuentra contenida en el polígono (triángulo) formado por los puntos b0 , b1 , b2 y b3 . Dicho polígono se denomina polígono de control. Cualquier modificación en alguno de sus puntos supone la modificación de la curva. Si nos fijamos, su manera de definir la curva mediante puntos de control se aproxima a las herramientas de creación de curvas en aplicaciones de diseño vectorial. Con dos puntos de control si que es posible realizar bucles con la curva. La única restricción para que esto ocurra es que se produzca un bucle en el polígono de control y que la distancia entre b 0 y b3 sea menor que la distancia entre b 1 y final en b2 . Sin bucle:
24
Sin bucle (límite):
Con bucle:
Visto lo anterior, podemos afirmar que un bucle en el polígono de control no significa un bucle en la curva. Ponemos posicionar b 0 y b3 con las mismas coordenadas, entonces la curva se sigue definiendo.
25
Curva de Bézier de grado 4 Para n=4 tenemos los siguientes polinomios de Bernstein:
A partir de ellos definimos la curva de Bézier:
Observaciones:
La curva de Bézier de grado 4 dibuja una curva con su inicio en b 0 y final en b 4 . La curva se encuentra contenida en el polígono (triángulo) formado por los puntos b 0 , b1 , b2 , b3 y b4 . Dicho polígono se denomina polígono de control. Cualquier modificación en alguno de sus puntos supone la modificación de la curva. Con tres puntos de control también es posible realizar bucles con la curva. La única restricción para que esto ocurra es que se produzca un bucle en el polígono de control y que la distancia entre b 0 y b4 sea menor que dos veces la distancia entre b 1 y final en b 3 .
26
Sin bucle:
Sin bucle (límite):
Con bucle:
27
Visto lo anterior, podemos afirmar que un bucle en el polígono de control no significa un bucle en la curva. Ponemos posicionar b 0 y b3 con las mismas coordenadas, entonces la curva se sigue definiendo.
28
3.4 Propiedades de curvas de Bézier
La curva de Bézier se encuentra en el interior de la envolvente convexa de los puntos de control. Demostración:
La curva de Bézier es infinitamente i nfinitamente derivable. El control de la curva es global. Modificar un punto de control implica i mplica modificar completamente la curva.
29
Demostración: Pongamos como ejemplo la curva de Bézier de grado 2.
Supongamos Supongamos ahora las coordenadas coordenadas de los puntos.
Ahora establecemos u=2:
Si modificamos una coordenada, cambia el punto de Bézier:
Para efectuar una transformación afín de la curva es suficiente con efectuar la transformación sobre todos los puntos de control. La curva comienza en el punto P0 y termina en el llamada interpolación del punto final .
Pn. Esta peculiaridad es
La curva es un segmento recto si, y sólo si, todos los puntos de control están alineados.
El comienzo (final) de la curva es tangente a la primera (última) sección del polígono de Bézier. Una curva puede ser desdoblada en algunos puntos en dos curvas, o de manera arbitraria en tantas curvas como se quieran, cada una de las cuales es una nueva curva de Bézier. Una de las propiedades más destacadas de los polinomios de Bernstein y que las curvas de Bézier heredan es la llamada propiedad recursiva, de la que hablaremos en el siguiente punto.
30
3.5 Propiedad recursiva de curvas de Bézier Como vimos anteriormente en los polinomios de Bernstein, su propiedad recursiva es heredada por las curvas de Bézier. Para recordar, dicha propiedad es la siguiente:
Se ha explicado esta propiedad en un punto aparte con el fin de ilustrarla mediante diferentes ejemplos. Podemos obtener los polinomios de Bernstein utilizando la recurrencia.
n = 1 → i = 0,1 →
n=2 → i = 0,1,2 →
n=3 → i = 0,1,2,3 →
n=4 → i = 0,1,2,3,4 →
31
3.6 Derivadas de curvas de Bézier Antes de seguir con las derivadas de curvas de Bézier, definiremos como derivar los polinomios de Bernstein:
Para ilustrar lo anteriormente descrito, mostraremos como son las derivadas de los polinomios de Bernstein de grado 4:
Y su representación gráfica es la siguiente:
32
Ahora que ya hemos mostrado como derivar los polinomios de Bernstein, daremos un paso más y nos centraremos en la derivada de una curva de Bézier:
Y como antes se hizo, ahora para ilustrar lo anterior, procederemos a derivar la curva de Bézier de grado 3:
33
Si comparamos la expresión resultante con la curva de Bézier de grado n-1:
Nos damos cuenta de que la derivada de una curva de Bézier de grado n es otra curva de Bézier de grado n-1, donde los nuevos puntos de control vienen definidos por:
3.7 Elevación de grado Si tenemos varias curvas de Bézier calculadas de modo independiente, podemos querer tratarlas al mismo tiempo ti empo y nos puede interesar que todas tengan el mismo grado. Claro está que cuando buscamos modificar el grado, queremos que la curva mantenga la misma forma, solo que esté definida por más puntos de control. Estos nuevos puntos están definidos por:
Donde ci son los nuevos puntos, b i los puntos de la curva original y n el grado de destino. 34
Podemos observar en la representación gráfica como estas dos curvas, aún teniendo una de ellas más puntos de control, son idénticas:
Si iteramos el proceso de elevación del grado, esencialmente estamos “redondeando” el polígono de control, recortándole las esquinas. En el límite de infinitas elevaciones de grado, el polígono tiende a la forma de la curva, aunque no es un procedimiento eficiente para trazarla.
35
3.8 Curvas de Bézier racionales Son un tipo de curvas que ajustan automáticamente los coeficientes de las ecuaciones paramétricas para obtener una aproximación a una figura arbitraria. El numerador es una ponderación de una curva Bézier en su forma de Berstein y el denominador es una suma ponderada de polinomios de Berstein.
Se pueden definir de la siguiente manera:
Este es un ejemplo de curva de Bézier racional de grado 3, en el cual hemos ido modificando su valor w 2 a 1, 2 y 10
A continuación mostraremos el proceso para obtener la curva racional de grado 3. Desarrollando el sumatorio, llegamos a la siguiente definición:
36
3.9 Anexo de programación En este apartado crearemos una aplicación Java con interfaz gráfica AWT para la visualización de curvas de Bézier cúbicas. Podremos crear hasta un máximo de 19 curvas enlazadas, modificar sus puntos a tiempo real y ver como esta modificación afecta a la curva. Hay que decir que el programa esta creado con los conocimientos de un alumno de 1º de Grado en Ing. Multimedia, por lo tanto pueden haber fallos menores. Aunque las pruebas realizadas permiten un uso fluido de la aplicación. El método que utiliza para dibujar la curva quizás no sea del todo eficiente, pero no sirve para hacernos una idea de cómo se muestra la curva. Se guardan los puntos de control en un vector. Cuando esos puntos llegan a 4, se realiza una llamada al método bezier(), el cual se encarga de almacenar en otro vector los puntos de la curva. Estos se obtienen sustituyendo en la fórmula de una curva de Bézier cúbica los puntos de control y iterando esta operación para un incremento de t pequeño. Luego se llama a repaint() r epaint() y nos dibuja todos los puntos.
Clase Bezier import java.awt.*; import java.awt.event.*; public class Bezier extends Frame implements ItemListener
{ private Panel panel panel; ; private Panel panelc; panelc; panel2; ; private Panel panel2 private private private private private
MyCanvas canvas canvas; ; Button btnLimpiar btnLimpiar; ; Checkbox calidad calidad; ; Checkbox desliz desliz; ; Checkbox pol pol; ;
private TextField px px; ; private TextField py; py; btnAnadir; ; private Button btnAnadir private static TextArea pantalla; public Bezier()
{ initComponents(); } private void initComponents()
{ panel= new Panel(); panel= panelc= panelc = new Panel(); panel2= panel2 = new Panel(); canvas =new MyCanvas(); canvas= btnLimpiar = new Button("Limpiar" Button("Limpiar"); ); pantalla = new TextArea("" TextArea( "", , 30, 15); calidad = new Checkbox("Curva Checkbox( "Curva alta calidad", calidad",false); desliz = new Checkbox("Mostrar/Ocultar Checkbox( "Mostrar/Ocultar puntos", puntos" ,true); pol = new Checkbox("Mostrar/Ocultar Checkbox( "Mostrar/Ocultar poligono Bezier", Bezier" ,false);
37
btnAnadir = new Button("Añadir Button( "Añadir (MAX 450,450)"); 450,450)" ); px= px =new TextField("X" TextField( "X",2); ,2); py= py =new TextField("Y" TextField( "Y",2); ,2); panel.setBackground(Color. lightGray ); panel.setBackground(Color. panel2.setBackground(Color. panel2 .setBackground(Color. gray); canvas.setBackground(Color. canvas .setBackground(Color. white ); canvas.setBounds(0,0,450,450); canvas .setBounds(0,0,450,450); pantalla.setEditable( false );
addWindowListener( new java.awt.event.WindowAdapter() { public void windowClosing(java.awt.event.WindowEvent evt) { exitForm(evt); } }); btnLimpiar.addActionListener( btnLimpiar .addActionListener( new ActionListener() { public void actionPerformed(ActionEvent evt) { btnLimpiarActionPerformed(evt); } }); btnAnadir.addActionListener( btnAnadir .addActionListener( new ActionListener() { public void actionPerformed(ActionEvent evt) { btnAnadirActionPerformed(evt); } }); calidad.addItemListener( calidad .addItemListener( this); desliz.addItemListener( desliz .addItemListener( this); pol.addItemListener( pol .addItemListener( this); add(btnLimpiar add( btnLimpiar, , BorderLayout.SOUTH ); ); panel.add( pantalla); panel.add( panel2.add( panel2 .add(px px); ); panel2.add( panel2 .add(py py); ); panel2.add( panel2 .add(btnAnadir btnAnadir); ); panel2.add( panel2 .add(calidad calidad); ); panel2.add( panel2 .add(desliz desliz); ); panel2.add( panel2 .add(pol pol); ); panel.setBounds(0,70,800,600); panel .setBounds(0,70,800,600); pantalla.setBounds(0,110,200,200);
panelc .add(canvas panelc.add( canvas); ); panelc.setBounds(200,0,200,500); panelc .setBounds(200,0,200,500); add(panel); add(panel ); add(panelc add( panelc); ); add(panel2 add( panel2); ); panel.add( panel .add(panelc panelc); ); pack(); } public void itemStateChanged(ItemEvent e)
{ checkHandler(); }
public void checkHandler()
{ calidad.getState()) .getState()) if(calidad canvas.modificarCalidad(0.001f); canvas .modificarCalidad(0.001f); else
canvas.modificarCalidad(0.01f); canvas .modificarCalidad(0.01f);
38
desliz.getState()) .getState()) if(desliz canvas.setMostrarAux( canvas .setMostrarAux( true ); else
canvas.setMostrarAux( canvas .setMostrarAux( false); if(pol pol.getState()) .getState())
canvas.setMostrarPol( canvas .setMostrarPol( true ); else
canvas.setMostrarPol( canvas .setMostrarPol( false); } private void btnLimpiarActionPerformed(ActionEvent evt) {
canvas.limpiar(); canvas .limpiar(); } private void btnAnadirActionPerformed(ActionEvent evt) {
String x=px x=px.getText(); .getText(); String y=py y=py.getText(); .getText(); parseInt (x),Integer. parseInt(y)); Point p=new Point(Integer. parseInt canvas.anadirPunto(p); canvas .anadirPunto(p);
} private void exitForm(java.awt.event.WindowEvent evt)
{ System.exit (0); } static public void modificarTexto(String s)
{ pantalla.setText(s);
} public static void main(String[] args)
{ Bezier marco = new Bezier(); marco.setSize(800,600); marco.setTitle( "Editor Curvas Bezier"); Bezier" ); marco.setVisible( true); } }
Clase MyCanvas import import import import import import import import
java.awt.Canvas; java.awt.Color; java.awt.Graphics; java.awt.Point; java.awt.event.MouseAdapter; java.awt.event.MouseEvent; java.awt.event.MouseMotionAdapter; java.util.Vector;
class MyCanvas extends Canvas
{ private private private private private private
Vector[] puntos puntos, , curva curva; ; Point puntoSeleccionado ; numCurvas; ; int numCurvas float calidad calidad; ; boolean mostrarAux mostrarAux; ; boolean mostrarPol mostrarPol; ;
39
public MyCanvas()
{ super();
puntos = new Vector[20]; curva = new Vector[20]; for (int i = 0; i < 20; i++) { puntos[i] puntos [i] = new Vector
(); curva[i] curva [i] = new Vector(); } puntoSeleccionado = null; numCurvas = 0; calidad = 0.01f; mostrarAux = true ; mostrarPol = false ; addMouseListener( new MouseHandler()); addMouseMotionListener( new MouseMotionHandler()); } public void paint(Graphics g)
{ numCurvas; ; pos++) for (int pos = 0; pos <= numCurvas { puntos[pos].size(); [pos].size(); int n = puntos mostrarAux) ) if (mostrarAux { for (int i = 0; i < n; i++)
{ Point p = (Point) puntos puntos[pos].elementAt(i); [pos].elementAt(i); g.drawOval(( int) p.getX() - 2, ( int) p.getY() - 2, 6, 6); } } curva[pos].size(); [pos].size(); int nc = curva for (int i = 0; i < nc; i++) { Point p = (Point) curva curva[pos].elementAt(i); [pos].elementAt(i); g.setColor(Color. red ); ); g.fillOval(( int) p.getX(), (int) p.getY(), 2, 2); g.setColor(Color. black ); } if (n >= 2 && mostrarAux mostrarAux) )
{ g.drawLine(( int) ((Point) puntos puntos[pos].elementAt(0)).getX(), [pos].elementAt(0)).getX(), (int) ((Point) puntos puntos[pos].elementAt(0)).getY(), [pos].elementAt(0)).getY(), (int) ((Point) puntos puntos[pos].elementAt(1)).getX(), [pos].elementAt(1)).getX(), (int) ((Point) puntos puntos[pos].elementAt(1)).getY()); [pos].elementAt(1)).getY()); } if (n == 4)
{ if(mostrarAux mostrarAux) ) { g.drawLine(( int) ((Point) puntos puntos[pos].elementAt(2)).getX(), [pos].elementAt(2)).getX(), (int) ((Point) puntos puntos[pos].elementAt(2)).getY(), [pos].elementAt(2)).getY(), (int) ((Point) puntos puntos[pos].elementAt(3)).getX(), [pos].elementAt(3)).getX(), (int) ((Point) puntos puntos[pos].elementAt(3)).getY()); [pos].elementAt(3)).getY()); } mostrarPol) ) if (mostrarPol {
g.setColor(Color. blue ); g.drawLine(( int) ((Point) puntos puntos[pos].elementAt(0)).getX(), [pos].elementAt(0)).getX(), (int) ((Point) puntos puntos[pos].elementAt(0)).getY(), [pos].elementAt(0)).getY(), (int) ((Point) puntos puntos[pos].elementAt(1)).getX(), [pos].elementAt(1)).getX(), (int) ((Point) puntos puntos[pos].elementAt(1)).getY()); [pos].elementAt(1)).getY()); g.drawLine(( int) ((Point) puntos puntos[pos].elementAt(2)).getX(), [pos].elementAt(2)).getX(), (int) ((Point) puntos puntos[pos].elementAt(2)).getY(), [pos].elementAt(2)).getY(), (int) ((Point) puntos puntos[pos].elementAt(3)).getX(), [pos].elementAt(3)).getX(), (int) ((Point) puntos puntos[pos].elementAt(3)).getY()); [pos].elementAt(3)).getY()); g.drawLine(( int) ((Point) puntos puntos[pos].elementAt(1)).getX(), [pos].elementAt(1)).getX(),
40
(int) ((Point) puntos[pos].elementAt(1)).getY(), puntos[pos].elementAt(1)).getY(), (int) ((Point) puntos puntos[pos].elementAt(2)).getX(), [pos].elementAt(2)).getX(), (int) ((Point) puntos puntos[pos].elementAt(2)).getY()); [pos].elementAt(2)).getY()); g.setColor(Color. black ); } } } } public void bezier()
{ mandarTexto(); numCurvas; ; pos++) for (int pos = 0; pos < numCurvas { float p = 0.0f, x0 = 0.0f, y0 = 0.0f, x1 = 0.0f, y1 = 0.0f, x2 = 0.0f, y2 = 0.0f, x3 = 0.0f, y3 = 0.0f; float tx = 0.0f, ty = 0.0f; int n = puntos puntos[pos].size(); [pos].size(); if (n <= 1) return ;
Point pto = (Point) puntos puntos[pos].elementAt(0); [pos].elementAt(0); x0 += pto.getX(); y0 += pto.getY(); pto = (Point) puntos puntos[pos].elementAt(1); [pos].elementAt(1); x1 += pto.getX(); y1 += pto.getY(); pto = (Point) puntos puntos[pos].elementAt(2); [pos].elementAt(2); x2 += pto.getX(); y2 += pto.getY(); pto = (Point) puntos puntos[pos].elementAt(3); [pos].elementAt(3); x3 += pto.getX(); y3 += pto.getY(); while (p <= 1)
{ tx = exp((1 - p), 3) * x0 + 3 * p * exp((1 - p), 2) * x1 + 3 * exp(p, 2) * (1 - p) * x2 + exp(p, 3) * x3; ty = exp((1 - p), 3) * y0 + 3 * p * exp((1 - p), 2) * y1 + 3 * exp(p, 2) * (1 - p) * y2 + exp(p, 3) * y3; curva[pos].addElement( new Point(( int) tx, (int) ty)); curva[pos].addElement( p += calidad calidad; ; } repaint(); } } private float exp(float base, int exp)
{ float r = 1.0f; int i = 0; for (; i < exp; i++)
r *= base; return r;
} public void limpiar()
{ for (int i = 0; i <= numCurvas numCurvas; ; i++)
{ puntos[i] [i] != null) if (puntos puntos[i].removeAllElements(); puntos [i].removeAllElements(); curva[i] [i] != null) if (curva curva[i].removeAllElements(); curva [i].removeAllElements(); } numCurvas = 0;
41
repaint(); } public void limpiarCurva()
{ for (int i = 0; i < curva curva. .length length; ; i++)
{ curva[i].removeAllElements(); curva [i].removeAllElements(); } repaint(); } public void modificarCalidad( float cal)
{ calidad = cal; limpiarCurva(); bezier(); } public void mandarTexto()
{ String textoPantalla = "" ""; ; numCurvas; ; i++) for (int i = 0; i < numCurvas { textoPantalla += "La curva " + (i + 1) + " :\n"; :\n"; textoPantalla += "P1: ("; ("; textoPantalla += ((Point) puntos puntos[i].elementAt(0)).getX(); [i].elementAt(0)).getX(); textoPantalla += "," ","; ; textoPantalla += ((Point) puntos puntos[i].elementAt(0)).getY(); [i].elementAt(0)).getY(); textoPantalla += ")\nP2: ("; ("; textoPantalla += ((Point) puntos puntos[i].elementAt(1)).getX(); [i].elementAt(1)).getX(); textoPantalla += "," ","; ; textoPantalla += ((Point) puntos puntos[i].elementAt(1)).getY(); [i].elementAt(1)).getY(); textoPantalla += ")\nP3: ("; ("; textoPantalla += ((Point) puntos puntos[i].elementAt(2)).getX(); [i].elementAt(2)).getX(); textoPantalla += "," ","; ; textoPantalla += ((Point) puntos puntos[i].elementAt(2)).getY(); [i].elementAt(2)).getY(); textoPantalla += ")\nP4: ("; ("; textoPantalla += ((Point) puntos puntos[i].elementAt(3)).getX(); [i].elementAt(3)).getX(); textoPantalla += "," ","; ; textoPantalla += ((Point) puntos puntos[i].elementAt(3)).getY(); [i].elementAt(3)).getY(); textoPantalla += ")\n\n" ")\n\n"; ; } Bezier. modificarTexto modificarTexto (textoPantalla); } public void anadirPunto(Point p)
{ if (numCurvas < 19)
{ puntos[ puntos [numCurvas numCurvas].addElement(p); ].addElement(p); repaint(); puntos[ [numCurvas numCurvas].size() ].size() == 4) if (puntos { numCurvas++; numCurvas ++; bezier(); puntos[ puntos [numCurvas numCurvas] ] .addElement(puntos .addElement( puntos[ [numCurvas - 1].elementAt(3)); } } } public void setMostrarAux( boolean b)
{ mostrarAux = b; bezier(); } public void setMostrarPol( boolean b)
{
42
mostrarPol = b; bezier(); } class MouseHandler extends MouseAdapter
{ public void mousePressed(MouseEvent e)
{ puntoSeleccionado = dentroPunto(e.getX(), e.getY()); } public void mouseReleased(MouseEvent e)
{ puntoSeleccionado = null; } public void mouseClicked(MouseEvent e)
{ if (numCurvas < 19)
{ puntos[ puntos [numCurvas numCurvas].addElement( ].addElement( new Point(e.getX(), e.getY())); repaint(); puntos[ [numCurvas numCurvas].size() ].size() == 4) if (puntos { numCurvas++; numCurvas ++; bezier(); puntos[ puntos [numCurvas numCurvas].addElement( ].addElement(puntos puntos[ [numCurvas - 1] .elementAt(3)); } } } } class MouseMotionHandler extends MouseMotionAdapter
{ public void mouseDragged(MouseEvent _event)
{ // Si hay algún punto seleccionado lo arrastro modificándole // las coordenadas igual a las del ratón y repintando la pantalla if (puntoSeleccionado != null ) { puntoSeleccionado .setLocation(_event.getX(), _event.getY()); repaint(); limpiarCurva(); bezier(); } } } private Point dentroPunto( int x, int y)
{ numCurvas; ; pos++) for (int pos = 0; pos < numCurvas { puntos[pos].size(); [pos].size(); int n = puntos int _x = 0, _y = 0; for (int i = 0; i < n; i++)
{ Point pto = (Point) puntos puntos[pos].elementAt(i); [pos].elementAt(i); _x = (int) pto.getX(); _y = (int) pto.getY(); if ((x > (_x - 3)) && (x < _x + 3) && (y > (_y - 3)) && (y < _y + 3)) return pto; } } return null ;
} }
43
Esta aplicación que acabamos de crear tiene la siguiente GUI:
Podemos insertar puntos manualmente, manualmente, limpiar el área de dibujo, mostrar/ocultar puntos de control, mostrar/ocultar el polígono de Bézier y definir la curvas con más o menos calidad. También posee un área donde se nos muestras las coordenadas de los puntos de control, tomando como origen el vértice superior izquierdo del área de dibujo
44
as que… Sabí as
Algunas curvas que parecen simples, tales como una circunferencia, no pueden ser descritas de manera exacta mediante curvas de Bézier o segmentos de esta clase de curvas (por raro que parezca una curva formada a su vez por cuatro segmentos de curva puede aproximarse a un círculo, con un error radial máximo menor de una parte por mil, en cada punto de control interno la distancia es
de manera horizontal o
vertical de un punto de control del exterior sobre el círculo unidad).
Sabías que…
En tipografía digital es necesario un sistema de codificación de la información geométrica que permita que los tipos se impriman perfectamente a cualquier tamaño. Para ello también se usa el sistema de Bézier que fue adoptado como sistema estándar de codificación de curvas para el lenguaje Postscript. Es pues el sistema usado en las fuentes True Type (con curvas de segundo orden) y Postscript Tipo 1 (con curvas de tercer orden).
Sabías que… La síntesis de Bézier es un nuevo método de síntesis de ondas sonoras que se puede usar para crear sonidos usando curvas de Bézier.
Una curva de Bézier puede ser usada como una única forma de onda:
El punto de inicio y el de final se encuentran en y=0 Los puntos de inicio y final definen la forma de la onda Un punto de control se encuentra sobre el eje x. Un punto de control se encuentra bajo el eje x.
45
Síntesis simple (estática)
Divide el eje x en n puntos igualmente espaciados. Realiza un barrido de izquierda a derecha generando una nueva salida en cada punto. Asegúrate de que el tiempo entre cada muestra sucesiva es constante a 1/fs segundos.
La frecuencia de salida es dada por fs/n
46
Sabías que… Existe un tipo de arte llamado string art. Un arte que basa su concepto en formar patrones abstractos que se asemejan a cualquier objeto, a partir de lazos o cables. La imagen siguiente muestra como contruir una curva de Bézier de grado 2 siguiendo este tipo de arte tan peculiar:
47
4. Algoritmo de Casteljau 4.1 Introducción Paul De Casteljau (nacido en 1930 en Besançon, Francia), un físico y matemático en Citroën, desarrolló un algoritmo para el cálculo de una curva Bézier, en 1959. Autor del libro Mathématiques et CAO. Vol. 2: Formes los polos de la de Hermes. El algoritmo de Casteljau es ampliamente utilizado aunque podría haber pasado por algunas modificaciones. El algoritmo de Casteljau es el método más robusto y estable numéricamente para la evaluación de polinomios, aunque es más lento para el cálculo de un único punto que otros métodos, como el método de Horner (más rápido, menos robusto). Sin embargo, el algoritmo de Casteljau sigue siendo muy rápido para subdividir una curva Bézier en dos segmentos de la curva en una localización paramétrica arbitraria. www.en.wikipedia.org
4.2 Definición El algoritmo de de Casteljau es, en el campo del análisis numérico de la matemática, un método recursivo para calcular polinomios en la forma de Bernstein o base de Bernstein, o en las curvas de Bézier. Toma su nombre del ingeniero Paul de Casteljau. Este algoritmo es un método numéricamente estable para evaluar las curvas de Bézier. Aunque el algoritmo de De Casteljau es relativamente lento en las configuraciones, si se compara con otros es numéricamente más estable. Los polinomios de Bernstein son útiles para deducir las propiedades fundamentales de las curvas de Bézier, pero no proporcionan una manera eficiente de construirlas. La manera tradicional de trazarlas está basada en el algoritmo de De Casteljau. Dada una curva de Bézier de grado n:
Podemos calcular el punto de la siguiente forma:
48
Para agilizar un poco los cálculos y no liarse, es aconsejable realizar un triángulo como el siguiente, indicando todos los puntos a calcular:
49
4.3 Ejemplos Casteljau Curva de Bézier grado 2 (Casteljau)
Curva de Bézier grado 3 (Casteljau)
50
Curva de Bézier grado 4 (Casteljau)
51
4.4 Anexo de programación Para realizar la siguiente aplicación nos hemos basado en el código fuente de la aplicación que creamos en el anexo de programación del apartado “3. Curvas de Bézier”. Nos hemos dedicado a modificar el algoritmo de dibujo, empleando una adaptación del de Casteljau. También hemos añadido graficos al algoritmo para que nos muestre a tiempo real como se realizan los cálculos. A continuación el código fuente de las dos clases:
Clase Casteljau import java.awt.*; import java.awt.event.*; public class Casteljau extends Frame implements ItemListener, AdjustmentListener
{ private Panel panel panel; ; private Panel panelc; panelc; private Panel panel2 panel2; ; private private private private
MyCanvas canvas canvas; ; Button btnLimpiar btnLimpiar; ; Checkbox calidad calidad; ; Checkbox desliz desliz; ;
private TextField px px; ; private TextField py; py; private Button btnAnadir btnAnadir; ;
slider; ; private Scrollbar slider private static TextArea pantalla; public Casteljau()
{ initComponents(); } private void initComponents()
{ panel = new Panel(); panelc = new Panel(); panel2 = new Panel(); canvas = new MyCanvas(); btnLimpiar = new Button("Limpiar" Button("Limpiar"); ); pantalla = new TextArea("" TextArea( "", , 30, 15); calidad = new Checkbox("Curva Checkbox( "Curva alta calidad", calidad" , false ); desliz = new Checkbox("Mostrar/Ocultar Checkbox( "Mostrar/Ocultar puntos", puntos" , true ); slider = new Scrollbar(Scrollbar. HORIZONTAL , 0, 1, 0, 101); btnAnadir = new Button("Añadir Button( "Añadir (MAX 450,450)"); 450,450)" ); px = new TextField("X" TextField( "X", , 2); py = new TextField("Y" TextField( "Y", , 2); panel.setBackground(Color. lightGray ); panel.setBackground(Color. panel2.setBackground(Color. panel2 .setBackground(Color. gray); canvas.setBackground(Color. canvas .setBackground(Color. white ); canvas.setBounds(0, canvas .setBounds(0, 0, 450, 450); slider.setValue(0); slider .setValue(0); pantalla.setEditable( false );
addWindowListener( new java.awt.event.WindowAdapter() { public void windowClosing(java.awt.event.WindowEvent evt) {
52
exitForm(evt); } }); btnLimpiar.addActionListener( btnLimpiar .addActionListener( new ActionListener() { public void actionPerformed(ActionEvent evt) { btnLimpiarActionPerformed(evt); } }); btnAnadir.addActionListener( btnAnadir .addActionListener( new ActionListener() { public void actionPerformed(ActionEvent evt) { btnAnadirActionPerformed(evt); } }); calidad.addItemListener( calidad .addItemListener( this); desliz.addItemListener( desliz .addItemListener( this); slider.addAdjustmentListener( slider .addAdjustmentListener( this ); add(btnLimpiar add( btnLimpiar, , BorderLayout.EAST ); ); panel.add( pantalla); panel.add( panel2.add( panel2 .add(px px); ); panel2.add( panel2 .add(py py); ); panel2.add( panel2 .add(btnAnadir btnAnadir); ); panel2.add( panel2 .add(calidad calidad); ); panel2.add( panel2 .add(desliz desliz); ); add(slider add( slider, , BorderLayout.SOUTH ); ); panel.setBounds(0, panel .setBounds(0, 70, 800, 600); pantalla.setBounds(0, 110, 200, 200); panelc.add(canvas panelc.add( canvas); ); panelc.setBounds(200, panelc .setBounds(200, 0, 200, 500); add(panel); add(panel ); add(panelc add( panelc); ); add(panel2 add( panel2); ); panel.add( panel .add(panelc panelc); ); pack(); } public void itemStateChanged(ItemEvent e)
{ checkHandler(); } public void adjustmentValueChanged(AdjustmentEvent e)
{ int val = slider slider.getValue(); .getValue();
canvas.representarCasteljau(val); canvas .representarCasteljau(val); } public void checkHandler()
{ calidad.getState()) .getState()) if (calidad canvas.modificarCalidad(0.001f); canvas .modificarCalidad(0.001f); else
canvas.modificarCalidad(0.01f); canvas .modificarCalidad(0.01f); if (desliz desliz.getState()) .getState())
canvas.setMostrarAux( canvas .setMostrarAux( true ); else
canvas.setMostrarAux( canvas .setMostrarAux( false ); } private void btnLimpiarActionPerformed(ActionEvent evt)
{ canvas.limpiar(); canvas .limpiar(); }
53
private void btnAnadirActionPerformed(ActionEvent evt)
{ String x = px px.getText(); .getText(); String y = py py.getText(); .getText(); Point p = new Point(Integer. parseInt(x), Integer. parseInt(y)); canvas.anadirPunto(p); canvas .anadirPunto(p); } private void exitForm(java.awt.event.WindowEvent evt)
{ System.exit (0); } static public void modificarTexto(String s)
{ pantalla.setText(s);
} public static void main(String[] args)
{ Casteljau marco = new Casteljau(); marco.setSize(800, 600); marco.setTitle( "Editor Curvas Bezier"); Bezier" ); marco.setVisible( true); } }
Clase MyCanvas import import import import import import import import
java.awt.Canvas; java.awt.Color; java.awt.Graphics; java.awt.Point; java.awt.event.MouseAdapter; java.awt.event.MouseEvent; java.awt.event.MouseMotionAdapter; java.util.Vector;
class MyCanvas extends Canvas
{ private private private private private
Vector[] puntos puntos, , puntosc puntosc, , curva curva; ; Point puntoSeleccionado ; numCurvas; ; int numCurvas calidad; ; float calidad mostrarAux; ; boolean mostrarAux
public MyCanvas()
{ super();
puntos = new Vector[20]; puntosc = new Vector[20]; curva = new Vector[20]; for (int i = 0; i < 20; i++)
{ puntos[i] = new Vector(); puntos[i] puntosc[i] puntosc [i] = new Vector(); curva[i] curva [i] = new Vector(); } puntoSeleccionado = null ; numCurvas = 0; calidad = 0.01f; mostrarAux = true ; addMouseListener( new MouseHandler()); addMouseMotionListener( new MouseMotionHandler());
54
} public void paint(Graphics g)
{ for (int pos = 0; pos <= numCurvas numCurvas; ; pos++)
{ int n = puntos puntos[pos].size(); [pos].size(); int n2 = puntosc puntosc[pos].size(); [pos].size(); int nc = curva curva[pos].size(); [pos].size();
mostrarAux) ) if (mostrarAux { for (int i = 0; i < n; i++)
{ Point p = (Point) puntos puntos[pos].elementAt(i); [pos].elementAt(i); g.drawOval(( int) p.getX() - 2, ( int) p.getY() - 2, 6, 6); } for (int i = 0; i < n2; i++)
{ Point p = (Point) puntosc puntosc[pos].elementAt(i); [pos].elementAt(i); g.drawOval(( int) p.getX() - 2, ( int) p.getY() - 2, 6, 6); } } for (int i = 0; i < nc; i++)
{ Point p = (Point) curva curva[pos].elementAt(i); [pos].elementAt(i); g.setColor(Color. red ); ); g.fillOval(( int) p.getX(), (int) p.getY(), 2, 2); g.setColor(Color. black ); } if (n == 4)
{ if (mostrarAux mostrarAux) )
{ g.drawLine(( int) ((Point) puntos puntos[pos].elementAt(0)).getX(), [pos].elementAt(0)).getX(), (int) ((Point) puntos puntos[pos].elementAt(0)).getY(), [pos].elementAt(0)).getY(), (int) ((Point) puntos puntos[pos].elementAt(1)).getX(), [pos].elementAt(1)).getX(), (int) ((Point) puntos puntos[pos].elementAt(1)).getY()); [pos].elementAt(1)).getY()); g.drawLine(( int) ((Point) puntos puntos[pos].elementAt(1)).getX(), [pos].elementAt(1)).getX(), (int) ((Point) puntos puntos[pos].elementAt(1)).getY(), [pos].elementAt(1)).getY(), (int) ((Point) puntos puntos[pos].elementAt(2)).getX(), [pos].elementAt(2)).getX(), (int) ((Point) puntos puntos[pos].elementAt(2)).getY()); [pos].elementAt(2)).getY()); g.drawLine(( int) ((Point) puntos puntos[pos].elementAt(2)).getX(), [pos].elementAt(2)).getX(), (int) ((Point) puntos puntos[pos].elementAt(2)).getY(), [pos].elementAt(2)).getY(), (int) ((Point) puntos puntos[pos].elementAt(3)).getX(), [pos].elementAt(3)).getX(), (int) ((Point) puntos puntos[pos].elementAt(3)).getY()); [pos].elementAt(3)).getY()); g.drawLine( (int) ((Point) puntosc puntosc[pos].elementAt(0)).getX(), [pos].elementAt(0)).getX(), (int) ((Point) puntosc puntosc[pos].elementAt(0)).getY(), [pos].elementAt(0)).getY(), (int) ((Point) puntosc puntosc[pos].elementAt(1)).getX(), [pos].elementAt(1)).getX(), (int) ((Point) puntosc puntosc[pos].elementAt(1)).getY()); [pos].elementAt(1)).getY()); g.drawLine( (int) (int) (int) (int)
((Point) ((Point) ((Point) ((Point)
puntosc [pos].elementAt(1)).getX(), puntosc[pos].elementAt(1)).getX(), puntosc[pos].elementAt(1)).getY(), puntosc [pos].elementAt(1)).getY(), puntosc[pos].elementAt(2)).getX(), puntosc [pos].elementAt(2)).getX(), puntosc[pos].elementAt(2)).getY()); puntosc [pos].elementAt(2)).getY());
g.drawLine( (int) (int) (int) (int)
((Point) ((Point) ((Point) ((Point)
puntosc [pos].elementAt(3)).getX(), puntosc[pos].elementAt(3)).getX(), puntosc[pos].elementAt(3)).getY(), puntosc [pos].elementAt(3)).getY(), puntosc[pos].elementAt(4)).getX(), puntosc [pos].elementAt(4)).getX(), puntosc[pos].elementAt(4)).getY()); puntosc [pos].elementAt(4)).getY());
} } }
55
} public void bezier()
{ mandarTexto(); for (int pos = 0; pos < numCurvas numCurvas; ; pos++)
{ float p = 0.0f, x0 = 0.0f, y0 = 0.0f, x1 = 0.0f, y1 = 0.0f, x2 = 0.0f, y2 = 0.0f, x3 = 0.0f, y3 = 0.0f; float ax = 0.0f, bx = 0.0f, cx = 0.0f, dx = 0.0f, ex = 0.0f, fx = 0.0f; float ay = 0.0f, by = 0.0f, cy = 0.0f, dy = 0.0f, ey = 0.0f, fy = 0.0f;
puntos[pos].size(); [pos].size(); int n = puntos if (n <= 1) return ;
Point pto = (Point) puntos puntos[pos].elementAt(0); [pos].elementAt(0); x0 += pto.getX(); y0 += pto.getY(); pto = (Point) puntos puntos[pos].elementAt(1); [pos].elementAt(1); x1 += pto.getX(); y1 += pto.getY(); pto = (Point) puntos puntos[pos].elementAt(2); [pos].elementAt(2); x2 += pto.getX(); y2 += pto.getY(); pto = (Point) puntos puntos[pos].elementAt(3); [pos].elementAt(3); x3 += pto.getX(); y3 += pto.getY(); while (p <= 1.0)
{ ax = (1 - p) * x0 + p * x1; ay = (1 - p) * y0 + p * y1; bx = (1 - p) * x1 + p * x2; by = (1 - p) * y1 + p * y2; cx = (1 - p) * x2 + p * x3; cy = (1 - p) * y2 + p * y3; dx = (1 - p) * ax + p * bx; dy = (1 - p) * ay + p * by; ex = (1 - p) * bx + p * cx; ey = (1 - p) * by + p * cy; fx = (1 - p) * dx + p * ex; fy = (1 - p) * dy + p * ey; curva[pos].addElement( new Point((int) fx, (int) fy)); curva[pos].addElement( p += calidad calidad; ; } puntosc[pos].addElement( puntosc [pos].addElement( new puntosc[pos].addElement( puntosc [pos].addElement( new puntosc[pos].addElement( puntosc [pos].addElement( new puntosc[pos].addElement( puntosc [pos].addElement( new puntosc[pos].addElement( puntosc [pos].addElement( new puntosc[pos].addElement( puntosc [pos].addElement( new repaint();
Point(( int) Point(( int) Point(( int) Point(( int) Point(( int) Point(( int)
ax, bx, cx, dx, ex, fx,
(int) (int) (int) (int) (int) (int)
ay)); by)); cy)); dy)); ey)); fy));
} } public void representarCasteljau( int paso)
{ limpiarCas(); float p = paso * 0.01f; numCurvas; ; pos++) for (int pos = 0; pos < numCurvas { float x0 = 0.0f, y0 = 0.0f, x1 = 0.0f, y1 = 0.0f, x2 = 0.0f, y2 = 0.0f, x3 = 0.0f,
56
y3 = 0.0f; float ax = 0.0f, bx = 0.0f, cx = 0.0f, dx = 0.0f, ex = 0.0f, fx = 0.0f; float ay = 0.0f, by = 0.0f, cy = 0.0f, dy = 0.0f, ey = 0.0f, fy = 0.0f; int n = puntos puntos[pos].size(); [pos].size(); if (n <= 1) return ;
Point pto = (Point) puntos puntos[pos].elementAt(0); [pos].elementAt(0); x0 += pto.getX(); y0 += pto.getY(); pto = (Point) puntos puntos[pos].elementAt(1); [pos].elementAt(1); x1 += pto.getX(); y1 += pto.getY(); pto = (Point) puntos puntos[pos].elementAt(2); [pos].elementAt(2); x2 += pto.getX(); y2 += pto.getY(); pto = (Point) puntos puntos[pos].elementAt(3); [pos].elementAt(3); x3 += pto.getX(); y3 += pto.getY(); ax = (1 - p) * x0 + p * x1; ay = (1 - p) * y0 + p * y1; bx = (1 - p) * x1 + p * x2; by = (1 - p) * y1 + p * y2; cx = (1 - p) * x2 + p * x3; cy = (1 - p) * y2 + p * y3; dx = (1 - p) * ax + p * bx; dy = (1 - p) * ay + p * by; ex = (1 - p) * bx + p * cx; ey = (1 - p) * by + p * cy; fx = (1 - p) * dx + p * ex; fy = (1 - p) * dy + p * ey; puntosc[pos].addElement( new Point(( int) ax, (int) ay)); puntosc[pos].addElement( puntosc[pos].addElement( puntosc [pos].addElement( new Point(( int) bx, (int) by)); puntosc[pos].addElement( puntosc [pos].addElement( new Point(( int) cx, (int) cy)); puntosc[pos].addElement( puntosc [pos].addElement( new Point(( int) dx, (int) dy)); puntosc[pos].addElement( puntosc [pos].addElement( new Point(( int) ex, (int) ey)); puntosc[pos].addElement( puntosc [pos].addElement( new Point(( int) fx, (int) fy)); curva[pos].addElement( curva [pos].addElement( new Point((int) fx, (int) fy)); } repaint(); } public void limpiar()
{ numCurvas; ; i++) for (int i = 0; i <= numCurvas { puntos[i] [i] != null) if (puntos puntos[i].removeAllElements(); puntos [i].removeAllElements(); if (puntosc puntosc[i] [i] != null)
puntosc[i].removeAllElements(); puntosc [i].removeAllElements(); if (curva curva[i] [i] != null )
curva[i].removeAllElements(); curva [i].removeAllElements(); } numCurvas = 0; repaint(); } public void limpiarCurva()
{
57
curva. .length length; ; i++) for (int i = 0; i < curva { curva[i].removeAllElements(); curva [i].removeAllElements(); } repaint(); } public void limpiarCas()
{ puntosc. .length length; ; i++) for (int i = 0; i < puntosc { puntosc[i].removeAllElements(); puntosc [i].removeAllElements(); } repaint(); } public void modificarCalidad( float cal)
{ calidad = cal; limpiarCurva(); bezier(); } public void mandarTexto()
{ String textoPantalla = "" ""; ; for (int i = 0; i < numCurvas numCurvas; ; i++) { textoPantalla += "La curva " + (i + 1) + " :\n"; :\n"; textoPantalla += "P1: ("; ("; textoPantalla += ((Point) puntos puntos[i].elementAt(0)).getX(); [i].elementAt(0)).getX(); textoPantalla += "," ","; ; textoPantalla += ((Point) puntos puntos[i].elementAt(0)).getY(); [i].elementAt(0)).getY(); textoPantalla += ")\nP2: ("; ("; textoPantalla += ((Point) puntos puntos[i].elementAt(1)).getX(); [i].elementAt(1)).getX(); textoPantalla += "," ","; ; textoPantalla += ((Point) puntos puntos[i].elementAt(1)).getY(); [i].elementAt(1)).getY(); textoPantalla += ")\nP3: ("; ("; textoPantalla += ((Point) puntos puntos[i].elementAt(2)).getX(); [i].elementAt(2)).getX(); textoPantalla += "," ","; ; textoPantalla += ((Point) puntos puntos[i].elementAt(2)).getY(); [i].elementAt(2)).getY(); textoPantalla += ")\nP4: ("; ("; textoPantalla += ((Point) puntos puntos[i].elementAt(3)).getX(); [i].elementAt(3)).getX(); textoPantalla += "," ","; ; textoPantalla += ((Point) puntos puntos[i].elementAt(3)).getY(); [i].elementAt(3)).getY(); textoPantalla += ")\n\n" ")\n\n"; ; } modificarTexto (textoPantalla); Casteljau. modificarTexto } public void anadirPunto(Point p)
{ if (numCurvas < 19)
{ puntos[ puntos [numCurvas numCurvas].addElement(p); ].addElement(p); repaint(); puntos[ [numCurvas numCurvas].size() ].size() == 4) if (puntos { numCurvas++; numCurvas ++; bezier(); puntos[ puntos [numCurvas numCurvas] ] .addElement(puntos .addElement( puntos[ [numCurvas - 1].elementAt(3)); } } } public void setMostrarAux( boolean b)
{ mostrarAux = b; bezier();
58
} class MouseHandler extends MouseAdapter
{ public void mousePressed(MouseEvent e)
{ puntoSeleccionado = dentroPunto(e.getX(), e.getY()); } public void mouseReleased(MouseEvent e)
{ puntoSeleccionado = null; } public void mouseClicked(MouseEvent e)
{ if (numCurvas < 19)
{ puntos[ puntos [numCurvas numCurvas].addElement( ].addElement( new Point(e.getX(), e.getY())); repaint(); puntos[ [numCurvas numCurvas].size() ].size() == 4) if (puntos { numCurvas++; numCurvas ++; bezier(); puntos[ puntos [numCurvas numCurvas].addElement( ].addElement(puntos puntos[ [numCurvas - 1] .elementAt(3)); } } } } class MouseMotionHandler extends MouseMotionAdapter
{ public void mouseDragged(MouseEvent _event)
{ // Si hay algún punto seleccionado lo arrastro modificándole // las coordenadas igual a las del ratón y repintando la pantalla if (puntoSeleccionado != null ) { puntoSeleccionado .setLocation(_event.getX(), _event.getY()); repaint(); limpiarCurva(); limpiarCas(); bezier(); } } } private Point dentroPunto( int x, int y)
{ numCurvas; ; pos++) for (int pos = 0; pos < numCurvas { puntos[pos].size(); [pos].size(); int n = puntos int _x = 0, _y = 0; for (int i = 0; i < n; i++)
{ Point pto = (Point) puntos puntos[pos].elementAt(i); [pos].elementAt(i); _x = (int) pto.getX(); _y = (int) pto.getY(); if ((x > (_x - 3)) && (x < _x + 3) && (y > (_y - 3)) && (y < _y + 3)) return pto; } } return null;
} }
59
Como podemos apreciar en la imagen inferior, hemos añadido nuevos elementos a la interfaz gráfica:
Deslizador para visualizar la progresión del algoritmo.
60
Sabías que… Si marcamos el trazo de los puntos intermedios utilizados en el algoritmo de Casteljau, se forman otras curvas de Bézier. .
Sabías que... El algoritmo de Casteljau puede ser implementado de la siguiente manera: function deCasteljau deCasteljau((i j , j) begin if i = 0 then return P0, j j else j) + u* deCasteljau j+1) return (1-u)* deCasteljau deCasteljau((i-1, j deCasteljau((i-1, j end 61
Aunque pueda parecer una forma elegante y simple de implementarlo, esta forma es extremadamente ineficiente. El porqué reside en que cuando llamamos a la función, esta a su vez realiza dos llamadas recursivas, las cuales realizan dos más cada una,… Esta forma resulta muy ineficiente.
62
Curvas de Bézier - Aplicación práctica en Java by Jorge Torregrosa Lloret i s licensed under a Creative Commons Reconocimiento-NoComercial-CompartirIgual 3.0 España License. License . 63