block by Kcnarf 340e82272c3da86533f1ef254cd2bde0

Simplex Flurry

Full Screen

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:

index.html

<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.min.js

/*! 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)})();