This block experiments SimplexNoise.
Work initiated by this block from elenstar, Drawing Vector Field, and the outstanding Neonflames.
Cherry on the cake: this is my first block with canvas. Yeaheahhhhhhh
license: gpl-3.0 |
This block experiments SimplexNoise.
Work initiated by this block from elenstar, Drawing Vector Field, and the outstanding Neonflames.
Cherry on the cake: this is my first block with canvas. Yeaheahhhhhhh
<meta charset="utf-8"> | |
<style> | |
#under-construction { | |
display: none; | |
position: absolute; | |
top: 200px; | |
left: 300px; | |
font-size: 40px; | |
} | |
div.step { | |
position: relative; | |
} | |
div.require { | |
position: absolute; | |
left: 150px; | |
bottom: -10px; | |
} | |
.desc { | |
position: absolute; | |
left: 300px; | |
padding: 10px; | |
} | |
#particles .desc { | |
top: -10px | |
} | |
.desc .highlighted { | |
color: red; | |
} | |
.title { | |
font-weight: bold; | |
} | |
ul { | |
margin: 0px; | |
padding-left: 25px | |
} | |
canvas { | |
margin: 10px; | |
border: solid lightgrey 1px; | |
border-radius: 5px; | |
box-shadow: 2px 2px 6px grey; | |
} | |
</style> | |
<body> | |
<div id="under-construction"> | |
UNDER CONSTRUCTION | |
</div> | |
<div id="worms" class="step"> | |
<div class="desc"> | |
<div class="title">Worms / particles' pathes</div> | |
<ul> | |
<li>at each frame, persists each particle</li> | |
<li>the <span class="highlighted">red worm</span> traces the <span class="highlighted">red particle</span>, which behaves using the <span class="highlighted">red direction</span>, which is based on the <span class="highlighted">red noise</span>; it restarts at center</li> | |
</ul> | |
</div> | |
<canvas></canvas> | |
<div id="traces" class="require">↓ traces</div> | |
</div> | |
<div id="particles" class="step"> | |
<div class="desc"> | |
<div class="title">Particles' moves</div> | |
<ul> | |
<li>at each frame, updates each particle's position with the corresponding direction</li> | |
<li>the direction of a particle <em>p</em> is computed with <em class="highlighted">noise3D(p.x, p.y, t)</em>; the <em>t</em> param. states for time, allowing the noise to evolve among time</li> | |
<li>the underlying <em>noise3D</em> is tweaked so that:<ul> | |
<li>at a particular frame, <span class="coord-based-desc">close particles behave +/- the same way</span></li> | |
<li>particles passing a particular place <span class="time-based-desc">in close frames behave +/- the same way</span></li> | |
</ul></li> | |
</ul> | |
</div> | |
<canvas></canvas> | |
<div id="uses" class="require">↓ uses</div> | |
</div> | |
<div id="directions" class="step"> | |
<div class="desc"> | |
<div class="title">Particles' directions</div> | |
<ul> | |
<li>only drawn for the sake of understanding; shows the mapping <em>noise3D(x,y,t)→direction</em></li> | |
<li>the underlying <em>noise3D</em> is tweaked so that:<ul> | |
<li>at a particular frame, <span class="coord-based-desc">close places produce +/- the same direction</span></li> | |
<li>at a particular place, <span class="time-based-desc">close frames produce +/- the same direction</span></li> | |
</ul></li> | |
</ul> | |
</div> | |
<canvas></canvas> | |
<div id="transforms" class="require">↓ transforms</div> | |
</div> | |
<div id="noises" class="step"> | |
<div class="desc"> | |
<div class="title">SimplexNoise</div> | |
<ul> | |
<li>only drawn for the sake of understanding; shows the mapping <em>noise3D(x,y,t)→noise</em></li> | |
<li><span class="highlighted">now, let's play</span> <em>(and probably have some headaches ;-)</em><ul> | |
<li>at a particular frame, <select id="coord-based-config" onchange="updateConfigAndDesc()"> | |
<option value=100000>all places produce the same noise</option> | |
<option value=100 selected="selected">close places produce +/- the same noise</option> | |
<option value=10>close places produce different noises</option> | |
</select></li> | |
<li>at a particular place, <select id="time-based-config" onchange="updateConfigAndDesc()"> | |
<option value=20000000>all frames produce the same noise</option> | |
<option value=20000 selected="selected">close frames produce +/- the same noise</option> | |
<option value=2000>close frames produce different noises</option> | |
</select></li> | |
</ul></li> | |
</ul> | |
</div> | |
<canvas></canvas> | |
</div> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<script src="simplex-noise.min.js"></script> | |
<script> | |
var _2PI = 2*Math.PI; | |
var simplex = new SimplexNoise(), | |
timeBasedLength = 20000, //lower values make the noise evolving faster | |
coordBasedLength = 100; //lower values make close particles behave more differently | |
var particleCount = 15, | |
particleRadius = 1, | |
particles = new Array(particleCount), | |
particleSpeed = 0.5; | |
var interval = 10, //a direction/noise every interval | |
midInterval = interval/2, | |
fieldValues = []; | |
var cbw = 1, //canvas border width | |
cm = 10, //canvas margin | |
totalWidth = 300, | |
totalHeight = 500, | |
width = totalWidth-(cm+cbw)*2, | |
height = (totalHeight-((cm+cbw)*2)*4)/4; | |
initLayout(); | |
initParticles(); | |
var wormsContext = document.querySelector("#worms canvas").getContext("2d"), | |
particlesContext = document.querySelector("#particles canvas").getContext("2d"), | |
directionsContext = document.querySelector("#directions canvas").getContext("2d"), | |
noisesContext = document.querySelector("#noises canvas").getContext("2d"); | |
d3.interval(function(elapsed) { | |
updateSimplexField(elapsed); | |
updateParticles(elapsed); | |
redrawWorms(); | |
redrawParticles(); | |
redrawDirections(); | |
redrawNoises(); | |
}); | |
function initLayout() { | |
d3.selectAll("canvas") | |
.attr("width", width) | |
.attr("height", height); | |
updateConfigAndDesc(); | |
} | |
function initParticles() { | |
for (var i = 0; i < particleCount; ++i) { | |
particles[i] = { | |
x: width*Math.random(), | |
y: height*Math.random(), | |
noise: 0, | |
direction: 0 | |
}; | |
} | |
} | |
function updateSimplexField (elapsed) { | |
var timeBasedChange = elapsed/timeBasedLength; | |
//begin: compute a value every 10-x and 10-y | |
fieldValues = []; | |
for (var x = interval; x < width-midInterval; x+=interval) { | |
for (var y = interval; y < height-interval; y+=interval) { | |
var noise = simplex.noise3D(x/coordBasedLength, y/coordBasedLength, timeBasedChange); | |
var noiseBasedAngle = _2PI*noise; | |
fieldValues.push({ | |
x: x, | |
y: y, | |
noise: noise, | |
direction: noiseBasedAngle | |
}) | |
} | |
} | |
//end: compute a value every 10-x and 10-y | |
} | |
function updateParticles(elapsed) { | |
var timeBasedChange = elapsed/timeBasedLength; | |
//begin: update each particle's coord. using SimplexNoise | |
for (var i = 0; i < particleCount; ++i) { | |
var p = particles[i]; | |
var noise = simplex.noise3D(p.x/coordBasedLength, p.y/coordBasedLength, timeBasedChange); | |
var noiseBasedAngle = _2PI*noise; | |
var change = {vx: particleSpeed*Math.cos(noiseBasedAngle), | |
vy: particleSpeed*Math.sin(noiseBasedAngle)} | |
p.x += change.vx; | |
p.y += change.vy; | |
p.noise = noise; | |
p.direction = noiseBasedAngle; | |
if (p.x < -particleRadius || p.x > width+particleRadius || | |
p.y < -particleRadius || p.y > height+particleRadius) { | |
p.x = (i===0)? width/2 : width*Math.random(); | |
p.y = (i===0)? height/2 : height*Math.random(); | |
p.noise = 0; | |
p.direction = 0; | |
} | |
} | |
//end: update each particle's coord. using SimplexNoise | |
} | |
function redrawWorms() { | |
//begin: fade existing image | |
wormsContext.globalCompositeOperation = 'destination-out'; | |
wormsContext.fillStyle = 'rgba(255, 255, 255, .05)'; | |
wormsContext.fillRect(0, 0, width, height); | |
wormsContext.globalCompositeOperation = 'source-over'; | |
//begin: fade existing image | |
//begin: insert first particle (red, large) | |
wormsContext.fillStyle = 'rgba(255, 0, 0, 0.5)'; | |
wormsContext.beginPath(); | |
wormsContext.arc(particles[0].x, particles[0].y, 2*particleRadius, 0, _2PI); | |
wormsContext.fill(); | |
//end: insert first particle (red, large) | |
//begin: insert other particles (black, small) | |
wormsContext.fillStyle = 'rgba(0, 0, 0, .5)' | |
for (var i = 1; i < particleCount; ++i) { | |
wormsContext.beginPath(); | |
wormsContext.arc(particles[i].x, particles[i].y, particleRadius, 0, _2PI); | |
wormsContext.fill(); | |
} | |
//end: insert other particles (black, small) | |
} | |
function redrawParticles() { | |
//begin: delete existing image | |
particlesContext.clearRect(0, 0, width, height) | |
//begin: delete existing image | |
//begin: insert first particle (red, large) | |
particlesContext.fillStyle = 'red'; | |
particlesContext.beginPath(); | |
particlesContext.arc(particles[0].x, particles[0].y, 2*particleRadius, 0, _2PI); | |
particlesContext.fill(); | |
//end: insert first particle (red, large) | |
//begin: insert other particles (black, small) | |
particlesContext.fillStyle = 'black' | |
for (var i = 1; i < particleCount; ++i) { | |
particlesContext.beginPath(); | |
particlesContext.arc(particles[i].x, particles[i].y, particleRadius, 0, _2PI); | |
particlesContext.fill(); | |
} | |
//end: insert other particles(black, small) | |
} | |
function redrawDirections() { | |
function drawDirectionArrow(context) { | |
context.moveTo(0,0); | |
context.lineTo(midInterval, 0); | |
context.lineTo(midInterval-2, -1); | |
context.lineTo(midInterval-2, +1); | |
context.lineTo(midInterval, 0); | |
} | |
//begin: delete existing image | |
directionsContext.setTransform(1, 0, 0, 1, 0, 0); | |
directionsContext.clearRect(0, 0, width, height); | |
//begin: delete existing image | |
//begin: draw field | |
directionsContext.strokeStyle = 'black'; | |
for (var i = 0; i < fieldValues.length; ++i) { | |
directionsContext.beginPath(); | |
directionsContext.setTransform(1, 0, 0, 1, fieldValues[i].x, fieldValues[i].y); | |
directionsContext.rotate(fieldValues[i].direction); | |
drawDirectionArrow(directionsContext); | |
directionsContext.stroke(); | |
} | |
//end: draw field | |
//begin: insert 1st particle's direction (red) | |
directionsContext.strokeStyle = 'red'; | |
directionsContext.beginPath(); | |
directionsContext.translate(10, 10); | |
directionsContext.setTransform(1, 0, 0, 1, particles[0].x, particles[0].y); | |
directionsContext.rotate(particles[0].direction); | |
drawDirectionArrow(directionsContext); | |
directionsContext.stroke(); | |
//end: insert 1st particle's direction (red) | |
} | |
function redrawNoises() { | |
//begin: delete existing image | |
noisesContext.clearRect(0, 0, width, height); | |
//begin: delete existing image | |
//begin: draw field | |
noisesContext.fillStyle = 'black'; | |
for (var i = 0; i < fieldValues.length; ++i) { | |
noisesContext.beginPath(); | |
noisesContext.arc(fieldValues[i].x, fieldValues[i].y, midInterval/2*(1+fieldValues[i].noise), 0, _2PI); | |
noisesContext.fill(); | |
} | |
//end: draw field | |
//begin: insert 1st particle's noise (red) | |
noisesContext.fillStyle = 'red'; | |
noisesContext.beginPath(); | |
noisesContext.arc(particles[0].x, particles[0].y, midInterval/2*(1+particles[0].noise), 0, _2PI); | |
noisesContext.fill(); | |
//end: insert 1st particle's noise (red) | |
} | |
function updateConfigAndDesc() { | |
timeBasedLength = document.getElementById('time-based-config').value; | |
coordBasedLength = document.getElementById('coord-based-config').value; | |
coordBasedDesc = { | |
100000: [ | |
"all particles behave the same way", | |
"all places produce the same direction" | |
], | |
100: [ | |
"close particles behave +/- the same way", | |
"close places produce +/- the same direction" | |
], | |
10: [ | |
"close particles behave differently", | |
"close places produce different direction" | |
] | |
} | |
timeBasedDesc = { | |
20000000: [ | |
"will always behave the same way", | |
"close frames produce the same direction" | |
], | |
20000: [ | |
"in close frames behave +/- the same way", | |
"close frames produce +/- the same direction" | |
], | |
2000: [ | |
"in close frames behave differently", | |
"close frames produce different direction" | |
] | |
} | |
d3.selectAll(".time-based-desc").data(timeBasedDesc[timeBasedLength]) | |
.text(function(d) { return d; }); | |
d3.selectAll(".coord-based-desc").data(coordBasedDesc[coordBasedLength]) | |
.text(function(d) { return d; }); | |
} | |
</script> | |
</body> |
/*! simplex-noise.js: copyright 2012 Jonas Wagner, licensed under a MIT license. See https://github.com/jwagner/simplex-noise.js for details */ | |
(function(){function o(e){e||(e=Math.random),this.p=new Uint8Array(256),this.perm=new Uint8Array(512),this.permMod12=new Uint8Array(512);for(var t=0;t<256;t++)this.p[t]=e()*256;for(t=0;t<512;t++)this.perm[t]=this.p[t&255],this.permMod12[t]=this.perm[t]%12}var e=.5*(Math.sqrt(3)-1),t=(3-Math.sqrt(3))/6,n=1/3,r=1/6,i=(Math.sqrt(5)-1)/4,s=(5-Math.sqrt(5))/20;o.prototype={grad3:new Float32Array([1,1,0,-1,1,0,1,-1,0,-1,-1,0,1,0,1,-1,0,1,1,0,-1,-1,0,-1,0,1,1,0,-1,1,0,1,-1,0,-1,-1]),grad4:new Float32Array([0,1,1,1,0,1,1,-1,0,1,-1,1,0,1,-1,-1,0,-1,1,1,0,-1,1,-1,0,-1,-1,1,0,-1,-1,-1,1,0,1,1,1,0,1,-1,1,0,-1,1,1,0,-1,-1,-1,0,1,1,-1,0,1,-1,-1,0,-1,1,-1,0,-1,-1,1,1,0,1,1,1,0,-1,1,-1,0,1,1,-1,0,-1,-1,1,0,1,-1,1,0,-1,-1,-1,0,1,-1,-1,0,-1,1,1,1,0,1,1,-1,0,1,-1,1,0,1,-1,-1,0,-1,1,1,0,-1,1,-1,0,-1,-1,1,0,-1,-1,-1,0]),noise2D:function(n,r){var i=this.permMod12,s=this.perm,o=this.grad3,u,a,f,l=(n+r)*e,c=Math.floor(n+l),h=Math.floor(r+l),p=(c+h)*t,d=c-p,v=h-p,m=n-d,g=r-v,y,b;m>g?(y=1,b=0):(y=0,b=1);var w=m-y+t,E=g-b+t,S=m-1+2*t,x=g-1+2*t,T=c&255,N=h&255,C=.5-m*m-g*g;if(C<0)u=0;else{var k=i[T+s[N]]*3;C*=C,u=C*C*(o[k]*m+o[k+1]*g)}var L=.5-w*w-E*E;if(L<0)a=0;else{var A=i[T+y+s[N+b]]*3;L*=L,a=L*L*(o[A]*w+o[A+1]*E)}var O=.5-S*S-x*x;if(O<0)f=0;else{var M=i[T+1+s[N+1]]*3;O*=O,f=O*O*(o[M]*S+o[M+1]*x)}return 70*(u+a+f)},noise3D:function(e,t,i){var s=this.permMod12,o=this.perm,u=this.grad3,a,f,l,c,h=(e+t+i)*n,p=Math.floor(e+h),d=Math.floor(t+h),v=Math.floor(i+h),m=(p+d+v)*r,g=p-m,y=d-m,b=v-m,w=e-g,E=t-y,S=i-b,x,T,N,C,k,L;w<E?E<S?(x=0,T=0,N=1,C=0,k=1,L=1):w<S?(x=0,T=1,N=0,C=0,k=1,L=1):(x=0,T=1,N=0,C=1,k=1,L=0):E<S?w<S?(x=0,T=0,N=1,C=1,k=0,L=1):(x=1,T=0,N=0,C=1,k=0,L=1):(x=1,T=0,N=0,C=1,k=1,L=0);var A=w-x+r,O=E-T+r,M=S-N+r,_=w-C+2*r,D=E-k+2*r,P=S-L+2*r,H=w-1+3*r,B=E-1+3*r,j=S-1+3*r,F=p&255,I=d&255,q=v&255,R=.6-w*w-E*E-S*S;if(R<0)a=0;else{var U=s[F+o[I+o[q]]]*3;R*=R,a=R*R*(u[U]*w+u[U+1]*E+u[U+2]*S)}var z=.6-A*A-O*O-M*M;if(z<0)f=0;else{var W=s[F+x+o[I+T+o[q+N]]]*3;z*=z,f=z*z*(u[W]*A+u[W+1]*O+u[W+2]*M)}var X=.6-_*_-D*D-P*P;if(X<0)l=0;else{var V=s[F+C+o[I+k+o[q+L]]]*3;X*=X,l=X*X*(u[V]*_+u[V+1]*D+u[V+2]*P)}var $=.6-H*H-B*B-j*j;if($<0)c=0;else{var J=s[F+1+o[I+1+o[q+1]]]*3;$*=$,c=$*$*(u[J]*H+u[J+1]*B+u[J+2]*j)}return 32*(a+f+l+c)},noise4D:function(e,t,n,r){var o=this.permMod12,u=this.perm,a=this.grad4,f,l,c,h,p,d=(e+t+n+r)*i,v=Math.floor(e+d),m=Math.floor(t+d),g=Math.floor(n+d),y=Math.floor(r+d),b=(v+m+g+y)*s,w=v-b,E=m-b,S=g-b,x=y-b,T=e-w,N=t-E,C=n-S,k=r-x,L=0,A=0,O=0,M=0;T>N?L++:A++,T>C?L++:O++,T>k?L++:M++,N>C?A++:O++,N>k?A++:M++,C>k?O++:M++;var _,D,P,H,B,j,F,I,q,R,U,z;_=L<3?0:1,D=A<3?0:1,P=O<3?0:1,H=M<3?0:1,B=L<2?0:1,j=A<2?0:1,F=O<2?0:1,I=M<2?0:1,q=L<1?0:1,R=A<1?0:1,U=O<1?0:1,z=M<1?0:1;var W=T-_+s,X=N-D+s,V=C-P+s,$=k-H+s,J=T-B+2*s,K=N-j+2*s,Q=C-F+2*s,G=k-I+2*s,Y=T-q+3*s,Z=N-R+3*s,et=C-U+3*s,tt=k-z+3*s,nt=T-1+4*s,rt=N-1+4*s,it=C-1+4*s,st=k-1+4*s,ot=v&255,ut=m&255,at=g&255,ft=y&255,lt=.6-T*T-N*N-C*C-k*k;if(lt<0)f=0;else{var ct=u[ot+u[ut+u[at+u[ft]]]]%32*4;lt*=lt,f=lt*lt*(a[ct]*T+a[ct+1]*N+a[ct+2]*C+a[ct+3]*k)}var ht=.6-W*W-X*X-V*V-$*$;if(ht<0)l=0;else{var pt=u[ot+_+u[ut+D+u[at+P+u[ft+H]]]]%32*4;ht*=ht,l=ht*ht*(a[pt]*W+a[pt+1]*X+a[pt+2]*V+a[pt+3]*$)}var dt=.6-J*J-K*K-Q*Q-G*G;if(dt<0)c=0;else{var vt=u[ot+B+u[ut+j+u[at+F+u[ft+I]]]]%32*4;dt*=dt,c=dt*dt*(a[vt]*J+a[vt+1]*K+a[vt+2]*Q+a[vt+3]*G)}var mt=.6-Y*Y-Z*Z-et*et-tt*tt;if(mt<0)h=0;else{var gt=u[ot+q+u[ut+R+u[at+U+u[ft+z]]]]%32*4;mt*=mt,h=mt*mt*(a[gt]*Y+a[gt+1]*Z+a[gt+2]*et+a[gt+3]*tt)}var yt=.6-nt*nt-rt*rt-it*it-st*st;if(yt<0)p=0;else{var bt=u[ot+1+u[ut+1+u[at+1+u[ft+1]]]]%32*4;yt*=yt,p=yt*yt*(a[bt]*nt+a[bt+1]*rt+a[bt+2]*it+a[bt+3]*st)}return 27*(f+l+c+h+p)}},typeof define!="undefined"&&define.amd?define(function(){return o}):typeof window!="undefined"&&(window.SimplexNoise=o),typeof exports!="undefined"&&(exports.SimplexNoise=o),typeof module!="undefined"&&(module.exports=o)})(); |