Particelle o3d

Dopo aver pubblicato il tutorial sugli effetti particellari con il tag <canvas>, abbiamo voluto tentare un esperimento, estendere l’effetto in 3d, anche per dare un’occhiata al recente plugin di google o3d. Si tratta di un plugin per browser in grado di portare la grafica 3d accelerata sul web.

I punti di contatto tra la versione 2d e quella 3d ci sono, anche se ridotti al solo linguaggio utilizzato: javascript.

Vi consigliamo di leggere il tutorial in 2d per familiarizzare con il codice per gestire le particelle.

La differenza principale tra la versione canvas e quella o3d è la gestione del rendering. Nel primo caso il rendering viene effettuato disegnando via javascript la scena ad ogni singolo frame. Nel secondo, invece, la scena viene descritta durante la fase di setup, e il codice eseguito ad ogni frame si limita ad aggiornare le posizioni delle particelle, lasciando al plugin il render della scena impostata.

Cominciamo ad inserire lo script principale di o3d ed un <div> vuoto nel codice HTML, sarà il contenitore del plugin o3d

<script type="text/javascript" src="http://o3d.googlecode.com/svn/trunk/samples/o3djs/base.js"></script>


<div id="o3d"></div>

Diamo un’occhiata al setup

var init = function() {
o3djs.util.makeClients(setup);
}

Questa funzione inserirà il codice necessario per mostrare il plugin o3d all’interno del <div> che abbiamo appena creato. Appena avrà finito richiamerà la funzione di setup:

var setup = function(clientElements) {
var o3dElement = clientElements[0];
g_o3d = o3dElement.o3d;
g_client = o3dElement.client;
hc = g_client.height;
wc = g_client.width;
g_math = o3djs.math;
g_pack = g_client.createPack();
viewInfo = o3djs.rendergraph.createBasicView(
g_pack,
g_client.root,
g_client.renderGraphRoot);
// Set up a simple perspective view.
ratio = wc/hc;
viewInfo.drawContext.projection = g_math.matrix4.perspective(
2*Math.atan(hc/(2*800)), // 30 degree fov.
ratio,
1, // Near plane.
5000); // Far plane.
// Set up our view transformation to look towards the world origin where the
// cube is located.
viewInfo.drawContext.view = g_math.matrix4.lookAt([0, 0, 800], // eye
[0, 0, 0], // target
[0, 1, 0]); // up
viewInfo.clearBuffer.clearColor = [27/255, 34/255, 9/255, 1];
g_Transform = g_pack.createObject('Transform');
g_Transform.translate(-wc/2,-hc/2,0);
creaParticelle(g_Transform);
g_Transform.parent = g_client.root;
g_client.setRenderCallback(update);
}

Il codice non è molto semplice da leggere. I passaggi effettuati creano una vista sul mondo 3d, specificando punto di vista e punto obiettivo della telecamera. Successivamente si passa alla creazione di un oggetto Transform, ovvero un oggetto grazie al quale poter manipolare la posizione degli oggetti 3d. Quindi creiamo le particelle all’interno del Transform appena creato. Infine associamo la funzione da richiamare ad ogni frame, update.

Analizziamo ora il codice per la creazione delle particelle:

var numParticelle = 100;
var colori = [[0,1,1,1],[1,1,0,1],[1,0,1,1]];
var materiale = null;
var numColori = colori.length;
var part = new Array(numParticelle);
var creaParticelle = function(t) {
var g_Primitives = o3djs.primitives;
var g_Material = o3djs.material;
materiale = g_Material.createBasicMaterial(g_pack,viewInfo,[1,1,1,1]);
var x,y = hc/2;
var s = g_Primitives.createSphere(g_pack,materiale,1,10,10);
z = 0;//Math.random()*800-400;
for(var i=0;i<numParticelle;i++) {
var g_Transform = g_pack.createObject('Transform');
var g_Transform2 = g_pack.createObject('Transform');
x = Math.random()*wc;
part[i] = new Particella(x,y,z, Math.random()*5.5+0.5, colori[Math.round(Math.random()*(numColori-1))]);
part[i].setRif(x,y,z);
g_Transform2.scale([part[i].radius,part[i].radius,part[i].radius]);
g_Transform2.createParam("diffuse","ParamFloat4").value = colori[Math.round(Math.random()*(numColori-1))];
g_Transform2.addShape(s);
g_Transform.translate(x,y,z);
g_Transform2.parent = g_Transform;
part[i].t = g_Transform;
g_Transform.parent = t;
}
}

Dopo aver definito al solito il numero di particelle, i colori da utilizzare, passiamo alla creazione dei materiali delle nostre particelle. In effetti, creiamo un unico materiale, a cui di volta in volta cambieremo colore. Poi creiamo la sfera che rappresenta la nostra particella, le assegniamo il materiale. Anche la sfera che rappresenta la forma particella sarà unica. Le particelle saranno quindi varie istanze della stessa forma e dello stesso materiale. Ciò che varia è, come abbiamo visto precedentemente, l’oggetto transform che le contiene. Per ogni particella avremo un trasform differente, con scala, traslazioni e parametro colore per la riflessione della luce diffusa differenti. Più precisamente, abbiamo creato 2 Transform per particella, una che conterrà dati specifici della particella ma costanti, come la sua grandezza ed il suo colore. L’altra conterrà la posizione variabile ad ogni frame. Così facendo, potremmo tenere separati i dati che non dobbiamo modificare. Comodo no, si risparmia memoria :) Notiamo che abbiamo memorizzato nella variabile t della particella la sua Transform, che ci sarà utile quando andremo ad aggiornare la posizione delle particelle.

Manca ora il callback richiamato ad ogni frame:

var update = function() {
for(var i=0;i<numParticelle;i++) {
part[i].update();
}
}

Non fa altro che eseguire il metodo update per ogni particella.

update: function() {
if(Math.abs(mouseX-this.x)<20 && Math.abs(mouseY-this.y)<20) {
if(this.state==0) {
this.state = 1;
this.dx = this.rifx+Math.random()*200-100;
this.dy = Math.random()*hc;
this.dz = 0;//Math.random()*800-400;
this.vel = 30;
} else if(this.state==1) {
this.vibracounter = Math.round(Math.random()*20+10);
}
}
switch(this.state) {
case 0:
this.vibraLinea();
break;
case 1:
this.vibraTutto();
break;
}
this.x += (this.dx-this.x)/this.vel;
this.y += (this.dy-this.y)/this.vel;
this.z += (this.dz-this.z)/this.vel;
this.t.identity();
this.t.translate(this.x,this.y,this.z);
}

Le ultime 2 righe sono quelle che contano, aggiornano la Transform della particella, ovvero la sua posizione.

Nella demo di seguito abbiamo anche degli elementi 2d animati, che analizzeremo in un prossimo tutorial:
DEMO