Download La geometría de la Tortuga - Docencia FCA-UNAM

Document related concepts
no text concepts found
Transcript
UNIVERSIDAD NACIONAL AUTONOMA DE MEXICO
DIPLOMADO DE DESARROLLO DE SISTEMAS CON EL PARADIGMA DE
ORIENTACIÓN A OBJETOS
MODULO III: Programación de aplicaciones con Java 2EE
Profesor.- Ramón Castro Liceaga
APLICACIÓN # 2 : Funciones Recursivas (Fractales)
Se denominan funciones recursivas a aquellas que se llaman a sí mismas. Un ejemplo típico es el método de
ordenación quick-sort, el juego denominado Torres de Hanoi, etc.
Empezaremos por distinguir un método iterativo frente a un método recursivo mediante el ejemplo del cálculo del
factorial de un número n.
n!=n·(n-1)·(n-2)...2·1
El resultado del factorial de un número entero es un número entero mucho más grande (long), que puede sobrepasar el
rango de los enteros (int) si n es grande.

Método iterativo
long factorial(int n){
long resultado=1;
for(int i=1; i<=n; i++){
resultado*=i;
}
return resultado;
}

Método recursivo
long factorial(int n){
if(n==0) return 1;
return (n*factorial(n-1));
}
La geometría de la Tortuga
En primer lugar, creamos una clase que denominaremos Tortuga, por su similitud con la tortuga del lenguaje LOGO,
tan popular a mediados de los años 80, en la que definiremos varios métodos que ejecutan movimientos o trazados
simples. Una vez definidos los movimientos básicos en la clase Tortuga es posible crear rutinas recursivas que dibujen
distintos motivos a escalas diferentes.
La clase Tortuga
La clase Tortuga tiene los siguientes miembros datos.




La posición del objeto tortuga x e y.
Su orientación, angulo.
El color del lápiz con el que pinta su trayectoria.
El movimiento de la tortuga se representa mediante trazos rectilíneos en el contexto gráfico g.
Los constructores inicializan los miembros dato, con valores por defecto o explícitos
public class Tortuga {
double x;
double y;
double angulo; //radianes
Color color;
Graphics g;
public Tortuga(double x, double y, double angulo, Color color) {
this.x=x;
this.y=y;
this.angulo=angulo*Math.PI/180;
this.color=color;
}
public Tortuga() {
this.x=0.0;
this.y=0.0;
this.angulo=0.0;
this.color=Color.black;
}
La función miembro inicia, sitúa la tortuga en el origen, orientada hacia el Este (ángulo 0) y establece como color del
lápiz el negro. Esta función proporciona, además, el contexto gráfico g, en el que se representan los movimientos de la
tortuga.
public void inicia(Graphics g){
x=0.0;
y=0.0;
angulo=0.0;
color=Color.black;
this.g=g;
}
La función colorea cambia el color de dicho lápiz
public void colorea(Color color){
this.color=color;
}
La función miembro gira, cambia la orientación de la tortuga, sumándole a su orientación actual un determinado ángulo
expresado en grados.
public void gira(double angulo){
this.angulo+=angulo*Math.PI/180;
}
La función miembro salta, cambia la posición actual de la tortuga, a otra especificada por las coordenadas x e y.
public void salta(double x, double y){
this.x=x;
this.y=y;
}
La tortuga también puede cambiar de posición saltando a lo largo de su dirección (angulo) una distancia determinada.
public void salta(double distancia){
double xx=x+distancia*Math.cos(angulo);
double yy=y-distancia*Math.sin(angulo);
salta(xx, yy);
}
La función traza es similar a salta, la única diferencia es que la tortuga dibuja con su lápiz el camino (segmento) que
sigue para moverse de un punto a otro a lo largo de su dirección (angulo) una distancia especificada. Cuando llega al
final del camino la tortuga actualiza su posición.
public void traza(double distancia){
double xx=x+distancia*Math.cos(angulo);
double yy=y-distancia*Math.sin(angulo);
g.setColor(color);
g.drawLine((int)xx, (int)yy, (int)x, (int)y);
salta(xx, yy);
}
Fractales
El término fractal proviene de la palabra latina "fractus" y que se acuñó a finales de la década de los 70 por el
matemático Benoit Mandelbrot.
Un fractal consta de fragmentos geométricos de orientación y tamaño variable, pero de aspecto similar. Si lo ampliamos
nos irá mostrando una serie repetitiva de niveles de detalle, de modo que a todas las escalas que se examine, la
estructura presentada será similar.
Para representar un fractal basta con crear una rutina que tome una forma geométrica simple y la dibuje a una
determinada escala. Se repite varias veces la llamada a esta rutina de forma recursiva y a diferentes escalas.
Curva de Koch
En la generación del copo de nieve de Koch podemos ver que cada segmento es sustituido por cuatro segmentos cada
uno de ellos de un tercio de la longitud del anterior.
El código de la función recursiva generaKoch que nos traza la curva de Koch es la siguiente
void generaKoch(int nivel, double distancia){
if(nivel==0){
tortuga.traza(distancia);
}else{
generaKoch(nivel-1, distancia/3);
tortuga.gira(60.0);
generaKoch(nivel-1, distancia/3);
tortuga.gira(-120.0);
generaKoch(nivel-1, distancia/3);
tortuga.gira(60.0);
generaKoch(nivel-1, distancia/3);
}
}
Para obtener la figura cerrada, se ejecuta el procedimiento generaKoch en cada uno de los tres segmentos de longitud
d que forman un triángulo equilátero tal como se ve en la figura.
void curvaKoch(){
double distancia=(double)(3*wAlto-30)/(2*1.732);
double x=wAncho/2-distancia/2;
double y=(distancia/3)*1.732/2+5;
tortuga.salta(x, y);
generaKoch(orden, distancia);
tortuga.gira(-120);
generaKoch(orden, distancia);
tortuga.gira(-120);
generaKoch(orden, distancia);
}
En las siguientes figuras, se muestra las primeras etapas en la generación de la curva de Koch.
Curva de Peano
La función recursiva que genera la curva de Peano la podemos ver en la figura para el nivel 0 (en la parte inferior) y
para el nivel 1 (en la parte superior), y la podemos dibujar en el applet de la geometría de la tortuga. Recordar que para
empezar un dibujo se pulsa el botón titulado Nuevo, y para ejecutar un comando hay que pulsar el botón Aplicar.
Inicia X:-120, Y: 0
Mueve 80
Gira -90º
Mueve 80
Gira 90º
Mueve 80
Gira 90º
Mueve 80
Mueve 80
Gira 90º
Mueve 80
Gira 90º
Mueve 80
Gira 90º
Mueve 80
Mueve 80
El código de la función recursiva generaPeano que nos traza la curva de Peano es la siguiente
void generaPeano(int nivel, double distancia){
if(nivel==0)
tortuga.traza(distancia);
else{
generaPeano(nivel-1, distancia/3);
tortuga.gira(-90.0);
generaPeano(nivel-1, distancia/3);
tortuga.gira(90.0);
generaPeano(nivel-1, distancia/3);
tortuga.gira(90.0);
generaPeano(nivel-1, distancia/3);
generaPeano(nivel-1, distancia/3);
tortuga.gira(90.0);
generaPeano(nivel-1, distancia/3);
tortuga.gira(90.0);
generaPeano(nivel-1, distancia/3);
tortuga.gira(90.0);
generaPeano(nivel-1, distancia/3);
generaPeano(nivel-1, distancia/3);
}
}
La llamada a esta función generaPeano comienza situando la tortuga en el punto inicial x, y, mediante la llamada a la
función miembro salta.
void curvaPeano(){
double distancia=wAlto;
double x=wAncho/2-wAlto/2;
double y=wAlto/2;
tortuga.salta(x, y);
generaPeano(orden, distancia);
}
En las siguientes figuras, se muestra las primeras etapas en la generación de la curva de Peano
Curva de Hilbert
Curva de Hilbert de orden cero la podemos generar como sigue en el applet de la geometría de la tortuga. Recordar
que para empezar un dibujo se pulsa el botón titulado Nuevo, y para ejecutar un comando hay que pulsar el botón
Aplicar
Inicia X:-50, Y: 50
Gira -90º
Mueve 100
Gira 90º
Mueve 100
Gira 90º
Mueve 100
Gira –90º
Como podemos ver en las figuras, cada segmento se sustituye por otros cuatro de longitud mitad. El procedimiento
recursivo es sin embargo, mucho más complejo que en los casos anteriores.
void generaHilbert(int nivel, int direccion, double distancia){
if(nivel>0){
tortuga.gira(-90*direccion);
generaHilbert(nivel-1, -direccion, distancia);
tortuga.traza(distancia);
tortuga.gira(90*direccion);
generaHilbert(nivel-1, direccion, distancia);
tortuga.traza(distancia);
generaHilbert(nivel-1, direccion, distancia);
tortuga.gira(90*direccion);
tortuga.traza(distancia);
generaHilbert(nivel-1, -direccion, distancia);
tortuga.gira(-90*direccion);
}
}
La llamada a la función recursiva generaHilbert comienza situando la tortuga en un punto inicial x, y, mediante la
función miembro salta.
void curvaHilbert(){
double distancia=wAlto-10;
double x=wAncho/2-wAlto/2;
double y=5.0; // 5 de margen
tortuga.salta(x, y);
generaHilbert(orden+1, 1, distancia/(potencia2(orden+1)-1));
}
La función auxiliar potencia2, halla la potencia n de 2 y es muy simple de programar
private long potencia2(int n){
long resultado=1;
for(int i=1; i<=n; i++){
resultado*=2;
}
return resultado;
}
Lo que observamos al final es una curva cuya longitud se hace cada vez más grande (tiende a infinito) en un recinto
limitado. Tras un número suficientemente grande de iteraciones la curva pasará por cualquier punto del plano.
En las siguientes figuras, se muestra las primeras etapas en la generación de la curva de Hilbert.
En el siguiente applet, se visualizan las primeras etapas de la generación de las curvas de Koch, Peano y Hilbert. Se
selecciona el nombre de la curva en el control selección y a continuación se pulsa la barra de desplazamiento para
elegir el nivel deseado
HACER:
Capturar el programa KochApplet.java
package koch;
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class KochApplet extends Applet {
Panel bevelPanel1 = new Panel();
Button btnDibuja = new Button();
Panel bevelPanel2 = new Panel();
Panel bevelPanel3 = new Panel();
FlowLayout flowLayout1 = new FlowLayout();
FlowLayout flowLayout2 = new FlowLayout();
TextField textOrden = new TextField();
Panel bevelPanel4 = new Panel();
Label label2 = new Label();
Choice chCurvas = new Choice();
FlowLayout flowLayout3 = new FlowLayout();
BorderLayout borderLayout1 = new BorderLayout();
BorderLayout borderLayout2 = new BorderLayout();
Button btnOrden = new Button();
int orden=0;
MiCanvas canvas;
//Initialize the applet
public void init() {
try {
jbInit();
}
catch (Exception e) {
e.printStackTrace();
}
}
//Component initialization
private void jbInit() throws Exception {
int ancho = Integer.parseInt(this.getParameter("WIDTH"));
int alto = Integer.parseInt(this.getParameter("HEIGHT"));
this.setSize(new Dimension(ancho, alto));
canvas=new MiCanvas();
chCurvas.addItem("Koch");
chCurvas.addItem("Peano");
chCurvas.addItem("Hilbert");
bevelPanel1.setBackground(Color.lightGray);
bevelPanel1.setLayout(borderLayout1);
btnDibuja.setLabel("Dibuja");
bevelPanel2.setLayout(flowLayout2);
bevelPanel3.setBackground(Color.gray);
bevelPanel3.setLayout(flowLayout1);
flowLayout1.setAlignment(2);
flowLayout1.setVgap(1);
flowLayout2.setAlignment(0);
flowLayout2.setVgap(1);
textOrden.setText("0");
textOrden.setColumns(2);
textOrden.setEditable(false);
bevelPanel4.setLayout(flowLayout3);
label2.setText("Curvas");
chCurvas.addItemListener(new java.awt.event.ItemListener() {
public void itemStateChanged(ItemEvent e) {
chCurvas_itemStateChanged(e);
}
});
flowLayout3.setAlignment(0);
flowLayout3.setVgap(1);
btnOrden.setLabel("Orden >>");
btnOrden.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(ActionEvent e) {
btnOrden_actionPerformed(e);
}
});
btnDibuja.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(ActionEvent e) {
btnDibuja_actionPerformed(e);
}
});
this.setLayout(borderLayout2);
this.add(bevelPanel1, BorderLayout.SOUTH);
bevelPanel1.add(bevelPanel2, BorderLayout.CENTER);
bevelPanel2.add(btnOrden, null);
bevelPanel2.add(textOrden, null);
bevelPanel1.add(bevelPanel3, BorderLayout.EAST);
bevelPanel3.add(btnDibuja, null);
bevelPanel1.add(bevelPanel4, BorderLayout.WEST);
bevelPanel4.add(label2, null);
bevelPanel4.add(chCurvas, null);
this.add(canvas, BorderLayout.CENTER);
textOrden.setEditable(false);
}
void btnDibuja_actionPerformed(ActionEvent e) {
int nCurva=chCurvas.getSelectedIndex();
canvas.setNuevo(orden, nCurva);
}
void chCurvas_itemStateChanged(ItemEvent e) {
orden=0;
textOrden.setText("0");
}
void btnOrden_actionPerformed(ActionEvent e) {
orden++;
if(orden>5)
orden=0;
textOrden.setText(String.valueOf(orden));
}
}
Capturar el programa MiCanvas.java
package koch;
import java.awt.*;
public class MiCanvas extends Canvas {
//anchura y altura del canvas
int wAncho, wAlto;
//parámetros
int orden=0;
int nCurva=0;
Tortuga tortuga=new Tortuga();
public MiCanvas() {
setBackground(Color.white);
}
void origenEscalas(Graphics g){
wAncho=getSize().width;
wAlto=getSize().height;
}
void setNuevo(int orden, int nCurva){
this.orden=orden;
this.nCurva=nCurva;
borra();
repaint();
}
void borra(){
Graphics g=getGraphics();
g.setColor(getBackground());
g.fillRect(0,0, wAncho, wAlto);
g.dispose();
}
void titulo(Graphics g){
//título
String s = "Fractales";
Font oldFont=g.getFont();
g.setFont(new Font("Helvetica", Font.BOLD, 20));
int myx = (wAncho - (g.getFontMetrics()).stringWidth(s))/2;
int myy = g.getFontMetrics().getHeight()+12;
g.setColor(Color.blue);
g.drawString(s, myx - 1, myy - 1);
g.drawString(s, myx - 1, myy + 1);
g.drawString(s, myx + 1, myy - 1);
g.drawString(s, myx + 1, myy + 1);
g.setColor(Color.cyan);
g.drawString(s, myx, myy);
g.setFont(oldFont);
}
void generaKoch(int nivel, double distancia){
if(nivel==0){
tortuga.traza(distancia);
}else{
generaKoch(nivel-1, distancia/3);
tortuga.gira(60.0);
generaKoch(nivel-1, distancia/3);
tortuga.gira(-120.0);
generaKoch(nivel-1, distancia/3);
tortuga.gira(60.0);
generaKoch(nivel-1, distancia/3);
}
}
void curvaKoch(){
double distancia=(double)(3*wAlto-30)/(2*1.732);
//raíz de tres
double x=wAncho/2-distancia/2;
double y=(distancia/3)*1.732/2+5; // 5 de margen
tortuga.salta(x, y);
generaKoch(orden, distancia);
tortuga.gira(-120);
generaKoch(orden, distancia);
tortuga.gira(-120);
generaKoch(orden, distancia);
}
void generaHilbert(int nivel, int direccion, double distancia){
if(nivel>0){
tortuga.gira(-90*direccion);
generaHilbert(nivel-1, -direccion, distancia);
tortuga.traza(distancia);
tortuga.gira(90*direccion);
generaHilbert(nivel-1, direccion, distancia);
tortuga.traza(distancia);
generaHilbert(nivel-1, direccion, distancia);
tortuga.gira(90*direccion);
tortuga.traza(distancia);
generaHilbert(nivel-1, -direccion, distancia);
tortuga.gira(-90*direccion);
}
}
void curvaHilbert(){
double distancia=wAlto-10;
double x=wAncho/2-wAlto/2;
double y=5.0; // 5 de margen
tortuga.salta(x, y);
generaHilbert(orden+1, 1, distancia/(potencia2(orden+1)-1));
}
private long potencia2(int n){
long resultado=1;
for(int i=1; i<=n; i++){
resultado*=2;
}
return resultado;
}
void generaPeano(int nivel, double distancia){
if(nivel==0)
tortuga.traza(distancia);
else{
generaPeano(nivel-1, distancia/3);
tortuga.gira(-90.0);
//10 de margen
generaPeano(nivel-1, distancia/3);
tortuga.gira(90.0);
generaPeano(nivel-1, distancia/3);
tortuga.gira(90.0);
generaPeano(nivel-1, distancia/3);
generaPeano(nivel-1, distancia/3);
tortuga.gira(90.0);
generaPeano(nivel-1, distancia/3);
tortuga.gira(90.0);
generaPeano(nivel-1, distancia/3);
tortuga.gira(90.0);
generaPeano(nivel-1, distancia/3);
generaPeano(nivel-1, distancia/3);
}
}
void curvaPeano(){
double distancia=wAlto;
double x=wAncho/2-wAlto/2;
double y=wAlto/2;
tortuga.salta(x, y);
generaPeano(orden, distancia);
}
public void paint(Graphics g){
origenEscalas(g);
g.setColor(getBackground());
g.fillRect(0,0, wAncho, wAlto);
titulo(g);
}
public void update(Graphics g){
tortuga.inicia(g);
switch(nCurva){
case 0:
curvaKoch();
break;
case 1:
curvaPeano();
break;
case 2:
curvaHilbert();
break;
default:
break;
}
}
}
Capturar el programa Tortuga.java
package koch;
import java.awt.*;
public class Tortuga {
double x;
double y;
double angulo; //radianes
Color color;
Graphics g;
public Tortuga(double x, double y, double angulo, Color color) {
this.x=x;
this.y=y;
this.angulo=angulo*Math.PI/180;
this.color=color;
}
public Tortuga() {
this.x=0.0;
this.y=0.0;
this.angulo=0.0;
this.color=Color.black;
}
public void inicia(Graphics g){
x=0.0;
y=0.0;
angulo=0.0;
color=Color.black;
this.g=g;
}
public void colorea(Color color){
this.color=color;
}
public void gira(double angulo){
this.angulo+=angulo*Math.PI/180;
}
public void salta(double x, double y){
this.x=x;
this.y=y;
}
public void salta(double distancia){
double xx=x+distancia*Math.cos(angulo);
double yy=y-distancia*Math.sin(angulo);
salta(xx, yy);
}
public void traza(double distancia){
double xx=x+distancia*Math.cos(angulo);
double yy=y-distancia*Math.sin(angulo);
g.setColor(color);
g.drawLine((int)xx, (int)yy, (int)x, (int)y);
salta(xx, yy);
}
}
Capturar el programa pagina5.html
<HTML>
<HEAD>
<TITLE></TITLE>
</HEAD>
<BODY>
<P>&nbsp;</P>
<applet CODE="koch.KochApplet.class"
ARCHIVE="koch.jar" WIDTH="600"
HEIGHT="405" HSPACE="0" VSPACE="0" ALIGN="middle">
stokesApplet aparecerá en un explorador compatible con JDK 1.1.
</applet>
</BODY>
</HTML>
-
Crear un directorio llamado ‘koch’
Compilar la aplicación:
Javac KochApplet.java MiCanvas.java Tortuga.java
Meter los archivos de clase que genera en la carpeta koch
Ejecutar desde Explorer: pagina5.html