«

»

mag 15

Dart, HTML5 e il frattale di Koch (2/2)

<– parte precedente

Conclusa la descrizione geometrica cominciamo a dare uno sguardo alle applicazioni Dart

Dart, struttura generale delle applicazioni

Un applicazione in Dart è concettualmente molto simile a un applicazione nei linguaggi visuali che usano elementi grafici ( un applet, un applicazione visual studio con le form etc etc) in particolare una cosa che salta all’occhio e la presenza di un entry point obbligatoria nella forma di un main() che viene eseguito al caricamento della pagina.
Tipicamente all’inizio dell’applicazione si trovano anche le import delle library e le risorse dichiarate per esempio la mia applicazione ha questa forma:

#library('koch');
#import('dart:html');
#resource('koch.css');

main() {
new Koch();
}

All’avvio mi limito ad allocare un oggetto Kock che materialmente gestirà il disegno, per quanto riguarda l’html la forma per collegarsi è la seguente:

&lt;!DOCTYPE html&gt;
&lt;html&gt;
[…]
&lt;canvas id="canvas" width="640" height="640" style="background-color: black"&gt;&lt;/canvas&gt;
[…]
&lt;div class="rangeOption"&gt;
&lt;input id="slider" type="range" max="10" value="2" /&gt;
&lt;/div&gt;
[…]
&lt;script type="application/dart" src="koch.dart"&gt;&lt;/script&gt;
&lt;script src="koch.dart.js"&gt;&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;

Lo script viene sempre importato due volte, una volta come file dart ( che viene eseguito nel caso sia presente la macchina virtuale) e una volta nella sua versione javascript, l’importazione avviene alla fine dell’html,

La gestione degli eventi

Una peculiarità del dart è che gli handler per gli eventi non devono ( e probabilmente neanche possono) essere dichiarati nell’html ad esempio la input con id slider nell’html precedente viene usata per controllare i livelli di profondità da disegnare ed ha quindi un metodo associato all’evento change. L’associazione avviene in questo modo:

InputElement slider = document.query("#slider");
slider.on.change.add((Event e) {
passi = Math.parseInt(slider.value);
drawFrame();
}, true);

su un elemento che supporta gli eventi col metodo on si accede alla lista degli eventi e col metodo add si aggiungono i listener, il prototipo della funzione è questo

EventListenerList add(void handler(Event event), [bool useCapture])

L’oggetto event in questo caso viene allocato come classe anonima, il flag finale (opzionale) ha un significato abbastanza oscuro:

Immaginate il DOM della pagina come un albero, gli eventi nel DOM vengono propagati prima dai livelli superiori verso il basso ( contenitore verso il contenuto ) e successivamente dal contenuto in alto verso il contenitore, è possibile associare l’handler a una delle due fasi per avere più controllo sull’ordine di esecuzione di eventuali handler multipli, il valore di default è false e quindi gli handler vengono invocati in fase di risalita. ( vi rimando a un ticket che ho aperto nel gruppo per ulteriori informazioni ).

Le Canvas

L’unico oggetto HTML5 che ho usato nel progetto sono le canvas. Le canvas sono degli ogetti al cui interno è possibile disegnare, i disegni sono persistenti finchè la pagina non viene chiusa e possono indirettamente essere salvati, dart ci permette di accedere alle canvas attraverso l’oggetto CanvasElement, e in particolare attraverso il CanvasRenderingContext2D ad esso associato

Il salvataggio di una canvas

La procedura per salvare una canvas è attualmente abbastanza macchinosa, si estrae l’immagine corrente dalla canvas ( come png, gif o jpeg) , la si setta come contenuto di un tag img e poi la si salva dal browser come un immagine normale.

CanvasElement canvas = document.query("#canvas");
var str=canvas.toDataURL("image/png");
ImageElement e=document.query("#canvasImg");
e.src=str;

Quindi torniamo al frattale di Koch

Lo script seguente disegna il frattale partendo da un triangolo ( 3 segmenti) creando una struttura simile a un fiocco di neve, ho fatto in modo che i colori fossero dipendenti dalla posizione e dalla distanza dal centro ( algoritmo assolutamente empirico, creato con i colori Y’UV per avere più controllo sulla luminanza, così empirico che non lo descrivo nemmeno xD ) uno slider permette di scegliere il livello di profondità con cui dovrà essere disegnato il frattale, la fase di plot viene comunque effettuata solo all’ultimo livello.

#library('koch');
#import('dart:html');
#resource('koch.css');

main() {
new Koch();
}

class Koch{
CanvasRenderingContext2D ctx;
Element console;
num xc, yc;
num passi = 0;
num r2;
num r3_4;
num id=0;

Koch(){

CanvasElement canvas = document.query("#canvas");
console=document.query("#console");
r2=Math.sqrt(2);
r3_4=Math.sqrt(3.0/4);
ctx = canvas.getContext("2d");
xc = 0.5*ctx.canvas.width;
yc = 0.5*ctx.canvas.height;

ButtonElement salva=document.query("#save");

salva.on.click.add((Event e) {
saveFrame();
}, true);

InputElement slider = document.query("#slider");
slider.on.change.add((Event e) {
passi = Math.parseInt(slider.value);
drawFrame();
}, true);
}

void cls(){
console.innerHTML="";
}
void puts(String testo){
console.innerHTML=console.innerHTML+testo;
}

String getRGBfromYUV(num Y, num U, num V){
//è una conversione abbastanza vastasa,
//mi serve solo per generare colori a luminanza definita
num B = 1.164*(Y - 16)                   + 2.018*(U - 128);
num G = 1.164*(Y - 16) - 0.813*(V - 128) - 0.391*(U - 128);
num R = 1.164*(Y - 16) + 1.596*(V - 128)    ;
return  getRGB(R.toInt(), G.toInt(), B.toInt());
}

String getRGB(num r,num g ,num b){

return 'rgb(' + r.toString() + ',' +   g.toString() +',' +   g.toString()+ ')';

}

List trovatriangolo(num centrox, num centroy, num lato){
num h=Math.sqrt(Math.pow(lato, 2)-Math.pow(0.5*lato, 2));
var p1=[centrox+0.5*lato,centroy+0.5*h];
var p2=[centrox-0.5*lato,centroy+0.5*h];
var p3=[centrox,centroy-0.5*h];
return [p1,p2,p3];
}

List trovaramo(num x1, num y1, num x2, num y2){
var p2=[x1+(x2-x1)/3,y1+(y2-y1)/3]; // un terzo del segmento
var p3=[x1+(x2-x1)/2,y1+(y2-y1)/2];//il centro del segmento
var p4=[x1+(x2-x1)*2/3,y1+(y2-y1)*2/3]; // due terzi del segmento
var v=[(p2[0]-x1)*r3_4,(p2[1]-y1)*r3_4];//un vettore allineato al segmento e lungo quanto l'altezza del triangolo
var vr=[v[1],-v[0]]; //ruoto il vettore di 90 gradi
p3=[p3[0]+vr[0],p3[1]+vr[1]];// applico il vettore al punto medio del segmento per ottenere la punta del triangolo
return [[x1,y1],p2,p3,p4,[x2,y2]];
}

num distanza(var p1, var p2){// la distanza euclidea tra due punti
return Math.sqrt(Math.pow(p1[0]-p2[0], 2)+Math.pow(p1[1]-p2[1], 2));
}

List meta(List p1, List p2){
return [0.5*(p1[0]+p2[0]),0.5*(p1[1]+p2[1])] ;
}

void kock(var p1, var p2, num npassi){
num len=distanza(p1, p2);
if(npassi&gt;0 &amp;&amp; len&gt;1){
var puntiramo=trovaramo(p1[0], p1[1], p2[0], p2[1]);
ctx.beginPath();
ctx.lineWidth = 1;
num d=distanza(p1,[xc,yc]);
num u=128+128*Math.sin((p1[0]-xc)/20);
num v=128+128*Math.sin((p1[1]-yc)/50);
//num y=156+100*Math.sin(d/5);
num y=150+Math.sin(d/5)*(d/3);
ctx.strokeStyle=getRGBfromYUV(y, u, v);
if(npassi==1 || len&lt;=3.1){// plotto solo se mi trovo all'ultimo livello, visto che le linee che costituiscono il frattale si sovrascrivono ottengo lo stesso risultato plottando una sola volta
ctx.moveTo(puntiramo[0][0],puntiramo[0][1]);
ctx.lineTo(puntiramo[1][0],puntiramo[1][1]);
ctx.lineTo(puntiramo[2][0],puntiramo[2][1]);
ctx.lineTo(puntiramo[3][0],puntiramo[3][1]);
ctx.lineTo(puntiramo[4][0],puntiramo[4][1]);
ctx.closePath();
ctx.stroke();
}
kock(puntiramo[0],puntiramo[1],npassi-1);
kock(puntiramo[1],puntiramo[2],npassi-1);
kock(puntiramo[2],puntiramo[3],npassi-1);
kock(puntiramo[3],puntiramo[4],npassi-1);
}

}

List triangolointerno(var p0, var p1, var p2){
var a=meta(p0,p1);
var b=meta(p1,p2);
var c=meta(p2,p0);
return [a,b,c];
}

void saveFrame(){
CanvasElement canvas = document.query("#canvas");
var str=canvas.toDataURL("image/png");
ImageElement e=document.query("#canvasImg");
e.src=str;
}
void drawFrame() {
num a;
ctx.clearRect(0, 0, xc*2, yc*2);
cls();

var punti=trovatriangolo(xc, yc*0.8, yc*1.6);

for(a=0;a&lt;passi;a++){
kock(punti[0],punti[1],passi);
kock(punti[1],punti[2],passi);
kock(punti[2],punti[0],passi);
punti=triangolointerno(punti[0],punti[1],punti[2]);

}
puts("Done! ("+passi.toString()+" iterazioni) \n");
}
}

Il resto (HTML associato e CSS associato) lo potete tirare fuori dalla pagina http://www.thedarshan.com/koch/koch.html

Share Button