Friday, September 21, 2007

Dibujar Fractales.

Vercion en PDF




Dibujar
los Fractales



Con
QT y C++



Jose
Angel Espinoza Portillo



Graficación



Septiembre
2007



Preámbulo



Se
proporciono un Framework de trabajo en el cual se harían las modificaciones. El
mismo ya era auto suficiente. Entonces solo era cuestión de modificarlo y
acomodarlo a nuestras necesidades y explotarlo al máximo.



El
FrameWork consta de 5 archivos de código principales:





painter.cpp y painter.h: Es la implementación del QWidget que resulta ser la ventana
principal del sistema, donde se implementan los botones, geometría, algunos
eventos y demás elementos que se observan en la ventana de la aplicación.





canvas.cpp y canvas.h: Es la implementación del QWidget que representa a el área en
la cual se va a dibujar. Incluye funciones de trazado de lineas y la activación
del evento (SIGNAL()) que dice la posición del puntero y las
mismas funciones que se invocan cada que se dibuja un objeto en sus limites.





main.cpp:
Es el código que iniciativa la aplicación y le da la ejec
ución a el Qt
para que interprete las instrucciones especificadas en canvas.cpp.



Insertar Botones a el FrameWork proporcionado



Se
agrego el inicio del constructor de Painter::Painter() en painter.cpp



QPushButton *boton1 = new QPushButton("A");



boton1->setFont( QFont( "Times", 18, QFont::Bold ) );





QPushButton *boton2 = new QPushButton("B");



boton2->setFont( QFont( "Times", 18, QFont::Bold ) );





QPushButton *boton3 = new QPushButton("C");



boton3->setFont( QFont( "Times", 18, QFont::Bold ) );





QPushButton *boton4 = new QPushButton("D");



boton4->setFont( QFont( "Times", 18, QFont::Bold ) );







Figura 1.



Para
crear la instancia en memoria de los botones con nombres boton1, ..., boton4.



Al
final de la misma función se agrego:





leftBox->addWidget( boton1 );



leftBox->addWidget( boton2 );



leftBox->addWidget( boton3 );



leftBox->addWidget( boton4 );





Justo
antes del “setLayout(grid);”,
al compilar se vera una pantalla como en la Figura 1.



Diferentes dibujos?



En
el archivo canvas.cpp, existe una función denominada Canvas::paintEvent() la cual es llamada cada vez que el cuadro de canvas es
redibujado, es en esta función donde mandas llamar la función Canvas::testLines() en la
cual se dibuja el patrón de la Figura 1 la cual venia como único patrón
predeterminado a dibujar.



Había
que modificar Canvas::paintEvent()
para que pudiera elegir en una serie de opciones que iban a ser seleccionados
por el usuario dependiendo del dibujo que quisiera desplegar. Así que se
declararon 4 funciones, una para cada patrón posible a dibujar y una variable
kind (propiedad) de el objeto Canvas la cual iba a ser quien decidiera que
patrón iba a ser dibujado el cual seria inicializado a cero en el constructor
de Canvas.



La
función Canvas::paintEvent()
quedo así:



void Canvas::paintEvent( QPaintEvent * )



{



QString s = "Angle = " + QString::number( ang );



QPainter p( this );



p.drawText( w/2 + 140, h/2, s );





switch (kind){



case 0 :testLines( &p );break;



case 1 :dibujo1( &p );break;



case 2 :dibujo2( &p );break;



case 3 :dibujo3( &p );break;



case 4 :dibujo4( &p );break;



}



}











Comunicación entre el
Canvas y el Painter



Para
que al momento de darle click en un botón en el Painter responda el Canvas hay
que agregar las posibles respuestas de Canvas para cada botón, estas son denominadas
SLOTS() y se declaran en el archivo .h con la etiqueta “public slots:”. Para
cada posible botón se declaro un slot. Esto se hizo agregando el siguiente
código a canvas.h:



public slots:



void setKind1();



void setKind2();



void setKind3();



void setKind4();





y
al mismo tiempo agregando a canvas.cpp las siguientes funciones:



void Canvas::setKind1()



{



kind
=
1;



repaint();



}



void Canvas::setKind2()



{



kind
=
2;



repaint();



}



void Canvas::setKind3()



{



kind
=
3;



repaint();



}



void Canvas::setKind4()



{



kind
=
4;



repaint();



}



las
cuales actualizan la variable kind y mandan redibujar el Canvas.



Para
que el objeto Painter pueda hacer uso de estos nuevos SLOT()’s se tiene que
conectar el comportamiento de los botones descritos anteriormente a el SLOT() que
le corresponde quedando el código de painter.cpp de la siguiente manera:





QPushButton *boton1 = new QPushButton("A");



boton1->setFont( QFont( "Times", 18, QFont::Bold ) );



connect( boton1, SIGNAL(clicked()),
canvas, SLOT(setKind1()) );





QPushButton *boton2 = new QPushButton("B");



boton2->setFont( QFont( "Times", 18, QFont::Bold ) );



connect( boton2, SIGNAL(clicked()),
canvas, SLOT(setKind2()) );





QPushButton *boton3 = new QPushButton("C");



boton3->setFont( QFont( "Times", 18, QFont::Bold ) );



connect( boton3, SIGNAL(clicked()),
canvas, SLOT(setKind3()) );





QPushButton *boton4 = new QPushButton("D");



boton4->setFont( QFont( "Times", 18, QFont::Bold ) );



connect( boton4, SIGNAL(clicked()),
canvas, SLOT(setKind4()) );










Dibujo de patrones.



Hasta
este momento hemos conectado Painter y Canvas, y unido sus funciones para que
se modifique que patrón de dibujo con el click de cada botón. Pero toda vía no
hemos definido los patrones así que se empezara a mostrar el código usado en
cada función para desplegar el patrón en el Canvas.



dibujo1()



Utiliza
la función dibujo1Cuadro()
para dibujar un rectángulo entre sus 4 combinaciones de sus puntos puntos:



void Canvas::dibujo1Cuadro(int x, int y, int x0, int y0, QPainter *p ){





MidPointLine(
x0+x, y0+y, x0+x, y0-y, p );



MidPointLine(
x0+x, y0-y, x0-x, y0-y, p );



MidPointLine(
x0-x, y0-y, x0-x, y0+y, p );



MidPointLine(
x0-x, y0+y, x0+x, y0+y, p );



}



Seguimos
con la implementación del patrón (Figura 2.):



void Canvas::dibujo1( QPainter *p )



{



int cx = w/2; // canvas center



int cy = h/2; // canvas center



int incrang = 5;



int l = 100;





int n = (360/incrang)/4;



// int n = 19;





for ( int i=0;
i < n; i++ ) {



float ang = i*incrang;





ang
= ang * ANGTORAD;





float val = l*cos(ang);



//float val =
l*cos(ang) + cx;



int px = Round( val );





val
= l*sin(ang);



//val = l*sin(ang) +
cy;



int py = Round( val );





//MidPointLine( cx,
cy, px, py, p );



dibujo1Cuadro(px
,py ,cx ,cy, p);



}



}





Figura 2.



dibujo2()



Utiliza
la función dibujo2Cuadro()
para dibujar un cuadro entre 4 puntos desfasados en algún ángulo:



void Canvas::dibujo2Cuadro(int x, int y, int x0, int y0, QPainter *p ){





MidPointLine(
x0+x, y0+y, x0-y, y0+x, p );



MidPointLine(
x0-y, y0+x, x0-x, y0-y, p );



MidPointLine(
x0-x, y0-y, x0+y, y0-x, p );



MidPointLine(
x0+y, y0-x, x0+x, y0+y, p );



}



Seguimos
con la implementación del patrón (Figura 3.):



void Canvas::dibujo2( QPainter *p )



{



int cx = w/2; // canvas center



int cy = h/2; // canvas center



int incrang = 10;



int l = 100;





int n = (360/incrang)/4;



// int n = 19;





for ( int i=0;
i < n; i++ ) {



float ang = i*incrang;





ang
= ang * ANGTORAD;





float val = l*cos(ang);



//float val =
l*cos(ang) + cx;



int px = Round( val );





val
= l*sin(ang);



//val = l*sin(ang) +
cy;



int py = Round( val );





//MidPointLine( cx,
cy, px, py, p );



dibujo2Cuadro(px
,py ,cx ,cy, p);



}



}



Figura 3.



dibujo3() : Hilbert



Al
inicio de la función se establecen valores como el tamaño de segmento y ángulo
inicial y procede a pasarle el control a una función denominada hilbert_level() a la cual se
le envía la información de nivel, dirección y distancia del segmento, la función
se despliega a continuación:



void Canvas::dibujo3( QPainter *p )



{



int l = w < h?w/32:h/32, angulo=0;



xo
= xd =
0; yo = yd = 0;



p->setPen("White");



MidPointLine(
0, h/2, w, h/2,
p );



MidPointLine( w/2, 0, w/2, h, p );





p->setPen("Blue");



hilbert_level(5, 0, l, p);



printf("///////////////");





}



Se
desarrollo una una función move(),
que se encarga de dibujar un segmento en una dirección, se muestra a
continuación:



void Canvas::move(int a, int l, QPainter *p){



switch(a){



case 0:yd -= l; break;



case 1 : xd -= l; break;



case 2 :yd += l; break;



case 3 : xd += l; break;



}



MidPointLine(
xo, yo, xd, yd, p );



xo=
xd;



yo=
yd;



}





La
función hilbert_level
es una función recursiva la cual dibuja el fractal de Hilbert, y se desarrolla
a continuación (Figura 4.):



void Canvas::hilbert_level(int level, int direction, int l, QPainter *p)



{



if (level==1) {



switch (direction) {



case 1:



move(3, l, p); move(2, l,
p); move(1, l, p);



break;



case 3:



move(1, l, p);



move(0, l, p);



move(3, l, p);



break;



case 0:



move(2, l, p);



move(3, l, p);



move(0, l, p);



break;



case 2:



move(0, l, p);



move(1, l, p);



move(2, l, p);



break;



}



}
else {



switch (direction) {



case LEFT:



hilbert_level(level-1,0,l,p);



move(RIGHT,
l, p);



hilbert_level(level-1,1,l,p);



move(DOWN,
l, p);



hilbert_level(level-1,1,l,p);



move(LEFT,
l, p);



hilbert_level(level-1,2,l,p);



break;



case RIGHT:



hilbert_level(level-1,2,l,p);



move(LEFT,
l, p);



hilbert_level(level-1,3,l,p);



move(UP,
l, p);



hilbert_level(level-1,3,l,p);



move(RIGHT,
l, p);



hilbert_level(level-1,0,l,p);



break;



case UP:



hilbert_level(level-1,1,l,p);



move(DOWN,
l, p);



hilbert_level(level-1,0,l,p);



move(RIGHT,
l, p);



hilbert_level(level-1,0,l,p);



move(UP,
l, p);



hilbert_level(level-1,3,l,p);



break;



case DOWN:



hilbert_level(level-1,3,l,p);



move(UP,
l, p);



hilbert_level(level-1,2,l,p);



move(LEFT,
l, p);



hilbert_level(level-1,2,l,p);



move(DOWN,
l, p);



hilbert_level(level-1,1,l,p);



break;



}



}



}



Figura 4.



dibujo4(): KGen



Esta
función dibuja un fractal denominado KGen. Inicia estableciendo los valores
iniciales y de largo total del segmento a dibujar. Se muestra a continuación la
implementación:



void Canvas::dibujo4( QPainter *p )



{



xo
=
0; yo = h/2;



angulo
=
0;



int l = w;



int i;



KGen
(l,
4, p);



}



La
funcion KGen es una función recursiva la cual es la encargada de dibujar el
fractal, se le proporciona el largo en que
se de ve dibujar el segmento al que se le esta llamando y el nivel de
implementación. Se muestra a continuación la implementación (Figura 5.):



void Canvas::KGen(int l, int nivel, QPainter *p){



if (nivel==0){



dibujo3CalculaPuntos(
l, angulo, p, nivel);



}else{



nivel--;



l=Round(l/3);



KGen(l,
nivel, p);



angulo+=60;



KGen(l,
nivel, p);



angulo-=120;



KGen(l,
nivel, p);



angulo+=60;



KGen(l,
nivel, p);



}



}



Figura 5.









Labels: ,