Game Design

Studi ed Articoli con eseguibili e codice sorgente

Informativa privacy e cookie

Reaction Diffusion NeonCyberCorallo

Stella inattivaStella inattivaStella inattivaStella inattivaStella inattiva
 

 

Per usare la simulazione vai nel seguito di questo post e usa il mouse per seminare la sostanza B nella sostanza A.

Intro

Questo Programma simula la reazione di tre sostanze chimiche A,B,e C che interagiscono tra loro in modi differenti. Rientra nel genere software della "GENERATIVE ART" che spesso si avvale di modelli matematici e rientra nei software si simulazione scientifica che generano immagini in un certo senso "artistiche" o anche definite come "arte procedurale". L'ho scritto usanto un linguaggio  p5js e lo potete usare qui on line. Nel seguioto del post trovate il modello matematico e il codice velocemente commentato. Potete lasciare un commento inviando email a Questo indirizzo email è protetto dagli spambots. È necessario abilitare JavaScript per vederlo.

 

 

Usa il mouse (pulsante sinistro e trascina) sulla seguente immaggine per seminare la sostanza B (sarà di colore blu elettrico) dento la sostanza A (che è lo sfondo) e osserva la simulazione.

Scritto da Giuseppe Gi.

Il modello matematico originale della "REACTION DIFFUSION" utilizza solo due sostanze la A e la B. In pratica una matrice piena di sostanza A (in cui ogni elemento della matrice contiene valori che vanno da 0.0 a 1.0 per indicare che percentuale di A è presente nel singolo elemento della matrice) viene fatta interagire con una matrice di sotanza B inizialmente vuota. Seminando alcuni elementi della matrice B l'algoritmo calcola l'espansione della Sostanza B dentro la sostanza A. Tale espansione però è sfumata e dinamica in quanto per ogni elemento della matrice viene calcolata la percentuale di sostanza B che si espande (usando il laplaciano degli elementi adiacenti e una velocita di espansione) e la percentuale di A (che si contrare opponendo una certa resistenza e usando il laplace dei suoi adiacenti (per dettagli sulla convoluzione puoi vedere questo mio artico sul trattamento immagini).

A questo ho aggiunto una terza sostanza C che interaggisce ritraendosi in presenza di B e dissolvendosi lentamente in presenza di A. Ho inoltre creato delle zone in cui la "velocità di espansione" e la "resistenza di contrazione" non sono uniformi ma variano sia nello spazio della matrice sia nel tempo tra i fotogrammi generando disegni più complessi. Tale variazione l'ho generata usando il Perlin Noise che assegna valori alle matrici di espansione e contrazione, e aggiornando tali matrici ogni n secondi in modo che gli schemi dei disegni continuino a modificarsi anche quando tutta la matrice è riempita.

Riferimenti bibliografici e articoli sul modello matematico e formule da cui questo programma trae ispirazione:

https://en.wikipedia.org/wiki/Reaction%E2%80%93diffusion_system 

http://www.karlsims.com/rd.html

 

Commento del codice sorgente:

Le matrici che contengono, per ogni elemento_ij, la percentuale di sostanza presente al tempo t sono innestate nell'oggetto grigliaAttuale[][] che contiene tre puntatori a tre matrici di sostanze chiamate a,b,c;

var grigliaAttuale=[];

Al tempo t+1 verrà calcolata la nuova configurazione delle sostanze che tra loro hanno reagito. Tali matrici sono innestate nell'oggetto grigliaProssima[][].

var grigliaProssima=[];

Ogni sostanza si espande o si contrare dal tempo t al tempo t+1 con una certa velocità di espansione e un certo valore di resistenza che chiamiamo feed e k e che, nel modello matematico originale sono delle costanti che si applicano o a tutti gli elementi della matrice o che si applicano solo a certe zone della matrice.

Nella mia implementazione invece ogni elemento della matrice ha un feed e un k dedicato ed esso è calcolato utilizzando l'algoritmo di "perlin noise" che permette di transitare da un valore a uno adiacente in modo casuale ma morbido ovvero senza una transisione brusca. Ciò consente nel mio programma di avere delle zone con rapida espansione che "sfumano" in zone con maggiore resistenza all'espansione. Tale matrice di Velocità e resistenza poi viene aggiornata ogni n fotogrammi in modo da rendere ancora più dinamica l'animazione delle sostanze che continuano sempre a "mescolarsi" tra loro, come se lo spazio in cui sono immerse avesse correnti o meglio avvallamenti che implodono ogni n fotogrammi.

Quindi nel codice dichiaro un puntatore alle matrici feed e k che vanno viste come la velocità di espansione della sostanza A e la velocità di rimozione della sostanza B.

var grigliaVelocita=[];

Il fattore di diffusione della sostanza A e B non sono uguali e li dichiaro come segue:
var dA=1;
var dB=0.5;

Definisco alcuni oggetti per gestire il perlin noise che mi permetteranno sia di riempire le matrici feed e k di valori che vanno da 0.0 a 1.0 e sia variabili che mi permettono di animare tale perlin noise a intervalli abbastanza lunghi da poter apprezzare le animazioni delle sostanze diffondersi tra loro.

var timerRicreazioneNoise=[];
var xoff=0.0;
var yoff=0.0;
var noiseScale=0.004;

 

La funzione setup inizializza sia le variabili che le matrici che il contesto grafico

 

function setup() {
createCanvas(400, 400);
pixelDensity(1);
background(100);

definisco due costanti per il feed e per il k che indicano il valore minimo e massimo che il perlin noise potrà usare per generare le griglie di velocità.

feed={ min:0.01,max:0.09};
k={min:0.052,max:0.07};
timerRicreazioneNoise={counter:0,time:1000};

inizializzo le matrici

for (var x=0;x<width;x++){
grigliaAttuale[x]=[];
grigliaProssima[x]=[];
grigliaVelocita[x]=[];
for(var y=0;y<height;y++){

 

//ogni elemento delle griglie delle sostanze punta a tre sostanze differenti la A, la B e la C

grigliaAttuale[x][y]={ a: 1 , b:0, c:1};
grigliaProssima[x][y]={ a:0 , b:0, c: 0};

/*
la griglia delle velocità viene riempita con valori generati dal perlin noise e mappati tra il minimo e massimo valore di feed e di k in base al valore che il perlin noise restituisce che è compreso tra 0.0 e 1.0
*/

grigliaVelocita[x][y]={feed:map(noise(x*noiseScale,y*noiseScale),0.0,1.0,feed.min,feed.max) , k:map(noise(x*noiseScale,y*noiseScale),0.0,1.0,k.min,k.max)};

se volessi visualizzare la griglia delle velocità dove i valori più scuri sono i minimi e i valori più chiari sono i massimi assomiglierebbe alla seguente immagine. La scala per perlin noise può essere variata, in questo caso abbiamo una scala abbastanza grande ovvero 0.02. La seconda immagine è quella con una scala più piccola quindi appare come ingrandita ed è 0.004

perlinNoise1perlinoise2

 Le zone più chiare applicheranno una differente resistenza e espansione rispetto alle zone più scure quindi ogni elemento delle matrici avrà la sua personale resistenza e espansione da appicare alle sostanze che interaggiscono tra di loro ma le variazioni di tale resistenza e espansione sono sfumate tra le varie zone.

 

 

 

 

 

 

 

 

 

}

 

...
}

}
/*
Il programma ha la funzione per disegnare la sostanza B dentro la sostanza A e aggiungere anche la sostanza C con il tasto centrale e quello Destro.
*/

function mouseDragged(){
if(mouseButton==LEFT){
for (var x=mouseX;x<mouseX+1;x++){
for(var y=mouseY;y<mouseY+1;y++){
grigliaAttuale[x][y].b=1;
}
}
}else if(mouseButton==RIGHT){
for (var x=mouseX-20;x<mouseX+20;x++){
for(var y=mouseY-20;y<mouseY+20;y++){
grigliaAttuale[x][y].c=1;
grigliaVelocita[x][y].feed-=0.001;//random(feed.min,feed.max);
grigliaVelocita[x][y].k+=0.001;//random(k.min,k.max);
}
}
}else if(mouseButton==CENTER){
for (var x=mouseX-20;x<mouseX+20;x++){
for(var y=mouseY-20;y<mouseY+20;y++){
grigliaAttuale[x][y].c=1;
grigliaVelocita[x][y].feed+=0.001;//random(feed.min,feed.max);
grigliaVelocita[x][y].k-=0.001;//random(k.min,k.max);
}
}
}

}

...

La funzione di disegno si occupa anche di calcolare la reazione chimica e di scambiare la griglia attuale con la griglia successiva che al tempo t diventa quella attuale e viene visualizzata.

Ogni sostanza contrubuisce a generare il colore del pixel su cui si trova e ho scelto di miscelare i colori e quindi le sostanze in modo tale che si abbiano effetti tipo neon blu su fondali di noise verdini

function draw() {
var velRitiroA;
var velEspansioneB;
timerRicreazioneNoise.counter++;
if(timerRicreazioneNoise.counter>=timerRicreazioneNoise.time){
xoff+=timerRicreazioneNoise.time;//0.5;
yoff+=timerRicreazioneNoise.time;//0.5;
for (var x=1;x<width-1;x++){
for(var y=1;y<height-1;y++){
grigliaVelocita[x][y].feed=map(noise((x+xoff)*noiseScale,(y+yoff)*noiseScale),0.0,1.0,feed.min,feed.max) ;
grigliaVelocita[x][y].k=map(noise((x+xoff)*noiseScale,(y+yoff)*noiseScale),0.0,1.0,k.min,k.max);
}
}
timerRicreazioneNoise.counter=0;
}

for (var x=1;x<width-1;x++){
for(var y=1;y<height-1;y++){

velRitiroA= grigliaVelocita[x][y].feed;
velEspansioneB=grigliaVelocita[x][y].k;


var a=grigliaAttuale[x][y].a;
var b=grigliaAttuale[x][y].b;
var c=grigliaAttuale[x][y].c;
grigliaProssima[x][y].a= a+
(dA * laplaceA(x,y)) -
(a * b * b) +
(velRitiroA * (1-a)) ;
grigliaProssima[x][y].b= b+
(dB * laplaceB(x,y)) +
(a * b * b) -
((velEspansioneB + velRitiroA) * b);
grigliaProssima[x][y].c=c+
((laplaceC(x,y))-
(b)+
(velRitiroA + velEspansioneB)*(1-c)* (1-c) *(1-b)*(1-a));




}
}
var i;
loadPixels();
var coloreA,coloreB,coloreC;
var coloreFeed,coloreK;

for (var x=0;x<width;x++){
for(var y=0;y<height;y++){
coloreA=grigliaProssima[x][y].a;
coloreB=grigliaProssima[x][y].b;
coloreC=grigliaProssima[x][y].c;
coloreFeed=grigliaVelocita[x][y].feed*1.5;
coloreK=grigliaVelocita[x][y].k*1.5;

i=(x+y*width)*4;
pixels[i]=floor((coloreB+(coloreK+coloreFeed))*255);
pixels[i+1]=floor((coloreB+(coloreK+coloreFeed))*255);
pixels[i+2]=floor((((coloreA-coloreC)+coloreB))*255);
pixels[i+3]=255;//floor(coloreFeed*255);//floor(coloreA*255);

}
}
updatePixels();
swap();

esempi di immagini generate:

}

function swap(){
var t=grigliaAttuale;
gigliaAttuale=grigliaProssima;
grigliaProssima=t;
}

function laplaceA(x,y){
var sum=0;
sum += grigliaAttuale[x][y].a * -1;
sum += grigliaAttuale[x-1][y].a * 0.2;
sum += grigliaAttuale[x+1][y].a * 0.2;
sum += grigliaAttuale[x][y+1].a * 0.2;
sum += grigliaAttuale[x][y-1].a * 0.2;
sum += grigliaAttuale[x-1][y-1].a * 0.05;
sum += grigliaAttuale[x+1][y-1].a * 0.05;
sum += grigliaAttuale[x+1][y+1].a * 0.05;
sum += grigliaAttuale[x-1][y+1].a * 0.05;
return sum;
}

function laplaceB(x,y){
var sum=0;
sum += grigliaAttuale[x][y].b * -1;
sum += grigliaAttuale[x-1][y].b * 0.2;
sum += grigliaAttuale[x+1][y].b * 0.2;
sum += grigliaAttuale[x][y+1].b * 0.2;
sum += grigliaAttuale[x][y-1].b * 0.2;
sum += grigliaAttuale[x-1][y-1].b * 0.05;
sum += grigliaAttuale[x+1][y-1].b * 0.05;
sum += grigliaAttuale[x+1][y+1].b * 0.05;
sum += grigliaAttuale[x-1][y+1].b * 0.05;
return sum;
}

function laplaceC(x,y){
var sum=0;
sum += grigliaAttuale[x][y].c * -1;
sum += grigliaAttuale[x-1][y].c * 0.2;
sum += grigliaAttuale[x+1][y].c * 0.2;
sum += grigliaAttuale[x][y+1].c * 0.2;
sum += grigliaAttuale[x][y-1].c * 0.2;
sum += grigliaAttuale[x-1][y-1].c * 0.05;
sum += grigliaAttuale[x+1][y-1].c * 0.05;
sum += grigliaAttuale[x+1][y+1].c * 0.05;
sum += grigliaAttuale[x-1][y+1].c * 0.05;
return sum;
}



Comments powered by CComment

© 2018 sito prototipale studio di GiuseppeGi