1/** @license 2 * Copyright 2023 Google LLC. 3 * SPDX-License-Identifier: Apache-2.0 4 */ 5 6// Inspired by https://cargocollective.com/sagejenson/physarum 7class Physarum { 8 static Tags = ['2d', 'simulation']; 9 10 constructor(glsl, gui) { 11 const U = this.U = {follow:false}; 12 const controllers = []; 13 const par = (s, v, ...arg)=>{U[s]||=v;controllers.push(gui.add(U,s,...arg))}; 14 const presets = { 15 worms: {senseFlip: false, senseAng:15, senseDist:10,moveAng:7,moveDist:1}, 16 mesh:{senseFlip:false, senseAng:45, senseDist:15, moveAng:17,moveDist:1}, 17 flocks: {senseFlip: true, senseAng:40, senseDist:36,moveAng:7,moveDist:1}, 18 swirl:{senseFlip: true, senseAng:1.28, senseDist: 19, moveAng: 7, moveDist: 2}, 19 }; 20 this.preset = 'worms'; 21 gui.add(this, 'preset', Object.keys(presets)).onChange(name=>{ 22 Object.assign(U, presets[name]); 23 controllers.forEach(c=>c.updateDisplay()); 24 }); 25 par('step_n', 1, 0, 20, 1); 26 par('dt', 1, 0.3, 1.0); 27 par('density', 1, 1, 4, 1); 28 par('senseFlip', false); 29 par('senseAng', 40, 0, 180 ); 30 par('senseDist', 36, 1, 50 ); 31 par('moveAng', 7, 0, 180 ); 32 par('moveDist', 1, 0, 10 ); 33 par('zoom', 1, 1, 8); 34 gui.add(U,'follow'); 35 Object.assign(U, presets[this.preset]); 36 } 37 38 frame(glsl, {DPR}) { 39 for (let i=0; i<this.U.step_n; ++i) { 40 this.step(glsl, DPR); 41 } 42 const points = this.points[0]; 43 const common = {field:this.field[0], ...this.U, points, Inc:` 44 vec2 wld2scr(vec2 p) { 45 vec2 d = follow?points(ivec2(0)).xy : vec2(field_size())*0.5; 46 return 2.0*zoom*(p-d.xy)/vec2(field_size()); //*rot2(d.z-PI/2.) 47 }`}; 48 glsl({...common, VP:` 49 varying vec2 uv = XY*2.; 50 VPos.xy = wld2scr(uv*vec2(field_size()));`, 51 FP:`field(uv).x`}); 52 53 glsl({...this.U, ...common, Grid: [1,1,4], Blend: 's+d*(1-sa)', VP:` 54 vec4 p = points(ID.xy); 55 varying vec4 color = ID.z==0 ? vec4(0,1,0,0.5):vec4(1,0,0,0.5); 56 if (ID.z>0) {p.xy += rot2(p.z+radians(senseAng)*float(ID.z-2))[0]*senseDist;} 57 VPos.xy = wld2scr(p.xy+XY*10.0/zoom);`, 58 FP:`smoothstep(1.0, 0.0, length(XY))*color`}); 59 } 60 61 step(glsl, DPR) { 62 const U=this.U, dt=U.dt; 63 const field = this.field = glsl({dt, Inc:` 64 float unpack(float x){return exp((x-1.0)*4.0);} 65 float pack(float x){return log(x)/4.0+1.0;}`, FP:` 66 vec2 dp = Src_step(); 67 float x=UV.x, y=UV.y; 68 float l=x-dp.x, r=x+dp.x, u=y-dp.y, d=y+dp.y; 69 #define S(x,y) unpack(Src(vec2(x,y)).r) 70 float v0 = S(x,y); 71 float v1 = 0.95*(v0+S(l,y)+S(r,y)+S(x,u)+S(x,d)+S(l,u)+S(r,u)+S(l,d)+S(r,d))/9.0; 72 FOut.x = pack(clamp(mix(v0, v1, dt),1e-5,1.)); 73 `}, {story:2, scale:1/DPR, format:'rgba8', filter:'linear', tag:'field'}); 74 75 const points = this.points = glsl({field:field[0], ...this.U, 76 rotAng:(1-U.senseFlip*2)*U.moveAng/180.0*Math.PI, FP:` 77 FOut = Src(I); 78 vec2 wldSize = vec2(field_size()); 79 if (FOut.w == 0.0 || FOut.x>=wldSize.x || FOut.y>=wldSize.y) { 80 FOut = vec4(hash(ivec3(I, 123)), 1.0); 81 FOut.xyz *= vec3(wldSize, TAU); 82 return; 83 } 84 vec2 dir = vec2(cos(FOut.z), sin(FOut.z)); 85 mat2 R = rot2(radians(senseAng)); 86 vec2 sense = senseDist*dir; 87 #define F(p) field((FOut.xy+(p))/wldSize).x 88 float c=F(sense), r=F(R*sense), l=F(sense*R); 89 if (l>c && c>r) { 90 FOut.z -= rotAng*dt; 91 } else if (r>c && c>l) { 92 FOut.z += rotAng*dt; 93 } else if (c<=r && c<=l) { 94 FOut.z += sign(hash(ivec3(FOut.xyz*5039.)).x-0.5)*rotAng*sqrt(dt); 95 } 96 FOut.xy += dir*moveDist*dt; 97 FOut.xy = mod(FOut.xy, wldSize); 98 `}, {scale:this.U.density/16/DPR, story:2, format:'rgba32f', tag:'points'}); 99 100 glsl({dt, points: points[0], Grid: points[0].size, Blend: 's+d', VP:`
101 VPos.xy = 2.0 * (points(ID.xy).xy+XY*2.0)/vec2(ViewSize) - 1.0;`, FP:` 102 smoothstep(1.0, 0.0, length(XY))*dt`}, field[0]); 103 } 104}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.