Skip to content

Instantly share code, notes, and snippets.

@Kcnarf
Last active December 3, 2018 08:28
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Kcnarf/340e82272c3da86533f1ef254cd2bde0 to your computer and use it in GitHub Desktop.
Save Kcnarf/340e82272c3da86533f1ef254cd2bde0 to your computer and use it in GitHub Desktop.
Simplex Flurry
license: gpl-3.0
border: no

This block experiments SimplexNoise (see also 1). More particularly, how to produce distincts noise values from the same coordinates.

Work initiated by this block from elenstar, Drawing Vector Field, and the outstanding Neonflames.

In this block, I use SimplexNoise to produce each tentacle's path. The challenge was to compute distincts tentacles from one unique non-changing central point. It was challenging (for me) because all the others experiments I've done with SimplexNoise use x- and y- coords to produce distincts behaviours. The way I've used SimplexNoise doesn't suit my needs because all tentacles start from the central point, wich eventually produces the same path again and again. To overcome this issue, I influence the noise production with each tentacle's rank (see loc. /*0*/, where the 3rd param.'s value depends on it). Conceptually speaking, I imagine that each tentacle has its own rank-based shift, which uses a particular region of the noise field. What is important is that the shift is high enought to use far enought regions of the noise field. If the shift is too small, then the noises/tentacles will still remain more or less the same.

Another thing I've learnt is that the produced noise values are not uniformaly distributed. This is obvious, but I missed it. The SimplexNoise is an efficient way to produce white noise, which, by definition, is centered on 0. This impacts the code at line /*1*/, where I multiply by 2, and leads to an angle within the too large [-2PI, 2PI] interval. With this tweak, the noise field makes tentacles spreading in all directions. Without this tweak, the noise field tends to go the the right (around the 0° direction), and tentacles tend to spread only toward the right side of the root.

Acknowledgments to:

<meta charset="utf-8">
<style>
#under-construction {
display: none;
position: absolute;
top: 200px;
left: 300px;
font-size: 40px;
}
#flurry {
margin: 1px;
border-radius: 1000px;
box-shadow: 2px 2px 6px grey;
cursor: crosshair;
}
</style>
<body>
<div id="under-construction">
UNDER CONSTRUCTION
</div>
<canvas id="flurry"></canvas>
<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();
coordBasedLength= 100, //lower values make more heratic tentacles
timeBasedLength= 20000; //lower values make tentacles evolving faster
var tentacleCount = 12,
segmentCount = 50, //segments par tentacle
segmentLength = 5,
root = {}, // root coord. of all tentacles
rootObjective = {}, // allows root to move toward mouse pointer
tentacles = [],
target = {}, // allows tentacles to target mouse pointer
homingStrength = 0, // 1: tentacle are free; 0: tentacle target mouse pointer
homingStrengthObjective = 0; // allow damping of tentacles'targeting
var cbw = 1, //canvas border width
cm = 10, //canvas margintotalWidth = 960,
totalWidth = 960,
totalHeight = 500;
width = totalHeight-(cm+cbw)*2,
height = totalHeight-(cm+cbw)*2;
initLayout();
initTentacles();
var flurryContext = document.querySelector("#flurry").getContext("2d");
d3.interval(function(elapsed) {
updateTentacles(elapsed);
redrawTentacles();
});
function initLayout() {
d3.select("#flurry")
.attr("width", width)
.attr("height", height)
.on("mouseenter", entered)
.on("touchmove mousemove", moved)
.on("mouseout", exited);
}
function entered() {
homingStrengthObjective = 0.5;
}
function moved() {
var coords = d3.mouse(this);
rootObjective = {x:coords[0], y: coords[1]};
target = {x:coords[0], y: coords[1]};
}
function exited() {
rootObjective = {x:width/2, y: height/2};
homingStrengthObjective = 0;
}
function initTentacles() {
root = {x: width/2, y: height/2};
rootObjective = {x: width/2, y: height/2};
target = {x: width/2, y: height/2};
var tentacle;
for (var t=0; t<tentacleCount; t++) {
tentacle = tentacles[t] = new Array(segmentCount);
tentacle[0] = {
x: root.x,
y: root.y,
width: 5
};
for (var s=1; s<segmentCount; s++) {
tentacle[s] = {
x: root.x,
y: root.y,
width: 5*(1-s/segmentCount) //decrease segment's width as segment is far from root
};
}
}
}
function updateTentacles(elapsed) {
var timeBasedChange = elapsed/timeBasedLength;
var tentacle,
prevSeg, seg,
noise, noiseBasedAngle, targetBasedAngle, composedAngle;
//begin: damping tentacles's targeting
homingStrength = 0.99*homingStrength + 0.01*homingStrengthObjective;
//end: damping tentacles's targeting
//begin: damping root's movement toward target
root.x = 0.995*root.x + 0.005*rootObjective.x;
root.y = 0.995*root.y + 0.005*rootObjective.y;
//end: damping root's movement toward target
for (var t=0; t<tentacleCount; t++) {
tentacle = tentacles[t];
tentacle[0].x = root.x;
tentacle[0].y = root.y;
//begin: update each-but-1st segment's coord. using SimplexNoise
for (var s=1; s<segmentCount; s++) {
//segments are chained: coord. of a segment depends on coord. & noise of previous segment
prevSeg = tentacle[s-1];
seg = tentacle[s];
/*0*/ noise = simplex.noise3D(prevSeg.x/coordBasedLength, prevSeg.y/coordBasedLength, timeBasedChange+t);
/*1*/ noiseBasedAngle = 2*Math.PI*noise;
targetBasedAngle = 2*Math.atan2(target.y-prevSeg.y, target.x-prevSeg.x);
composedAngle = (homingStrength*targetBasedAngle+(1-homingStrength)*noiseBasedAngle);
seg.x = prevSeg.x + segmentLength*Math.cos(composedAngle);
seg.y = prevSeg.y + segmentLength*Math.sin(composedAngle);
}
//end: update each-but-1st segment's coord. using SimplexNoise
}
}
function redrawTentacles() {
var tentacle;
flurryContext.strokeStyle = 'rgba(0, 0, 0, 0.05)';
//begin: fade existing image
flurryContext.fillStyle = 'rgba(255, 255, 255, 0.05)';
flurryContext.fillRect(0, 0, width, height);
//begin: fade existing image
//begin: insert tentacles (black)
for (var t=0; t<tentacleCount; t++) {
tentacle = tentacles[t];
for (var s=1; s<segmentCount; s++) {
flurryContext.lineWidth = tentacle[s].width;
flurryContext.beginPath();
flurryContext.moveTo(tentacle[s-1].x, tentacle[s-1].y);
flurryContext.lineTo(tentacle[s].x, tentacle[s].y);
flurryContext.stroke();
}
}
//end: insert tentacles (black)
//begin: insert central root (red)
flurryContext.fillStyle = 'rgba(255, 0, 0, 0.3)';
flurryContext.beginPath();
flurryContext.arc(root.x, root.y, 2, 0, _2PI);
flurryContext.fill();
//end: insert central root (red)
}
</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)})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment