the plane divided, and the picture that divides it
One tessellation, in four geometries. Wallpaper folds the plane by one of the seventeen periodic symmetry groups. Kaleido folds it by a triangle of mirrors, and the three angles decide whether the tiling lives on a sphere, on a plane or in Escher's circle limit — sweep them and the curvature sweeps with them. Quasi lays n grids across each other: two or three of them cross into a lattice, and five never repeat. Mosaic hands the picture its own cells and moves them, one relaxation step a frame, until they sit where the edges, the colours or a wired guide say they should.
Escher is the page that turns a division into a creature: a displacement field the group carries with it, so the tiles interlock however far they are pushed. Fit blends that field into the picture's own gradient, so the outline learns the image. Morph makes the field drift across the frame and the tiles change shape as they go, still interlocking, which is what a parquet deformation is. Gestalt is where the pattern stops being geometry: figure trades the two hands of a tile, flatten takes each tile's one colour, contour draws the seam and tint tells the tiles apart.
Amount holds the tiling exact on one harmonic however far it is pushed. On several, past about three quarters it begins to fold tiles through each other, which is a look and not an error.
Texture size in pixels; 0 follows the first wired texture input. A node that makes its own frames follows the patch's default instead.
height
int
0
0 … 8192
Texture size in pixels; 0 follows the first wired texture input. A node that makes its own frames follows the patch's default instead.
Source
The current source of this node, as it stands in the goofi repository at node-bundles/graphics/Tessellate.wgsl.
node-bundles/graphics/Tessellate.wgsl
/*goofi{"doc":"the plane divided, and the picture that divides it\nOne tessellation, in four geometries. Wallpaper folds the plane by one of the seventeen periodic symmetry groups. Kaleido folds it by a triangle of mirrors, and the three angles decide whether the tiling lives on a sphere, on a plane or in Escher's circle limit — sweep them and the curvature sweeps with them. Quasi lays n grids across each other: two or three of them cross into a lattice, and five never repeat. Mosaic hands the picture its own cells and moves them, one relaxation step a frame, until they sit where the edges, the colours or a wired guide say they should.\nEscher is the page that turns a division into a creature: a displacement field the group carries with it, so the tiles interlock however far they are pushed. Fit blends that field into the picture's own gradient, so the outline learns the image. Morph makes the field drift across the frame and the tiles change shape as they go, still interlocking, which is what a parquet deformation is. Gestalt is where the pattern stops being geometry: figure trades the two hands of a tile, flatten takes each tile's one colour, contour draws the seam and tint tells the tiles apart.\nAmount holds the tiling exact on one harmonic however far it is pushed. On several, past about three quarters it begins to fold tiles through each other, which is a look and not an error.","tags":["image","transform"],"inputs":[{"name":"input","kind":"TEXTURE"},{"name":"guide","kind":"TEXTURE"}],"state":["sites"],"params":[{"group":"tile","name":"kind","kind":"str","default":"wallpaper","options":["wallpaper","kaleido","quasi","mosaic"]},{"group":"tile","name":"source","kind":"str","default":"whole","options":["whole","window"]},{"group":"tile","name":"scale","kind":"float","default":4.0,"min":0.2,"max":64.0},{"group":"tile","name":"turn","kind":"float","default":0.0,"min":-360.0,"max":360.0},{"group":"tile","name":"x","kind":"float","default":0.0,"min":-2.0,"max":2.0},{"group":"tile","name":"y","kind":"float","default":0.0,"min":-2.0,"max":2.0},{"group":"wallpaper","name":"group","kind":"str","default":"p4m","options":["p1","p2","pm","pg","cm","pmm","pmg","pgg","cmm","p4","p4m","p4g","p3","p3m1","p31m","p6","p6m"]},{"group":"wallpaper","name":"aspect","kind":"float","default":1.0,"min":0.2,"max":5.0},{"group":"wallpaper","name":"skew","kind":"float","default":0.0,"min":-0.5,"max":0.5},{"group":"kaleido","name":"sides","kind":"float","default":7.0,"min":2.0,"max":12.0},{"group":"kaleido","name":"meet","kind":"float","default":3.0,"min":2.0,"max":12.0},{"group":"kaleido","name":"hinge","kind":"float","default":2.0,"min":2.0,"max":12.0},{"group":"quasi","name":"fold","kind":"int","default":5,"min":2,"max":12},{"group":"quasi","name":"shift","kind":"float","default":0.2,"min":-4.0,"max":4.0},{"group":"quasi","name":"spread","kind":"float","default":0.0,"min":-2.0,"max":2.0},{"group":"mosaic","name":"cells","kind":"int","default":24,"min":2,"max":96},{"group":"mosaic","name":"density","kind":"str","default":"edges","options":["flat","luma","dark","edges","guide"]},{"group":"mosaic","name":"affinity","kind":"float","default":0.0,"min":0.0,"max":4.0},{"group":"mosaic","name":"rate","kind":"float","default":0.25,"min":0.0,"max":1.0},{"group":"escher","name":"amount","kind":"float","default":0.0,"min":0.0,"max":1.0},{"group":"escher","name":"harmonics","kind":"int","default":2,"min":1,"max":4},{"group":"escher","name":"phase","kind":"float","default":0.0,"min":-12.0,"max":12.0},{"group":"escher","name":"seed","kind":"float","default":0.0,"min":0.0,"max":1000.0},{"group":"escher","name":"fit","kind":"float","default":0.0,"min":0.0,"max":1.0},{"group":"escher","name":"follow","kind":"str","default":"input","options":["input","guide"]},{"group":"morph","name":"drift","kind":"float","default":0.0,"min":-4.0,"max":4.0},{"group":"morph","name":"swirl","kind":"float","default":0.0,"min":-12.0,"max":12.0},{"group":"morph","name":"angle","kind":"float","default":0.0,"min":-360.0,"max":360.0},{"group":"gestalt","name":"figure","kind":"float","default":0.0,"min":0.0,"max":1.0},{"group":"gestalt","name":"flatten","kind":"float","default":0.0,"min":0.0,"max":1.0},{"group":"gestalt","name":"contour","kind":"float","default":0.0,"min":0.0,"max":1.0},{"group":"gestalt","name":"tint","kind":"float","default":0.0,"min":0.0,"max":1.0}]}*/constTAU:f32=6.28318530718;// Reflections a kaleido fold may take before the point is called unreachable, and how many of them// are remembered so the tile's own seat can be replayed. Two bits a mirror fills the word exactly.constKMAX:i32=32;constREPLAY:u32=16u;// Samples a side in the Lloyd quadrature, and the widest point group any of the seventeen has.constQUAD:i32=10;constOPS:i32=12;// What a division answers about the point it was asked. Every geometry fills the same five, and// everything after the fold reads these and never the geometry. A negative id is the one answer// that is not a tile: the point lies outside a geometry that ENDS, which only the hyperbolic one does.structCell{local:vec2f,seat:vec2f,edge:f32,parity:f32,id:f32,}fnhash21(v:vec2f)->f32{letq=vec2u(vec2i(floor(v))+65536);varh=q.x*1597334673u^q.y*3812015801u;h=(h^(h>>16u))*2246822519u;h=(h^(h>>13u))*3266489917u;returnf32(h^(h>>16u))*(1.0/4294967296.0);}fnhash11(x:f32)->f32{varh=bitcast<u32>(x+3.7)*2654435761u;h=(h^(h>>15u))*2246822519u;h=(h^(h>>13u))*3266489917u;returnf32(h^(h>>16u))*(1.0/4294967296.0);}fnrot(a:f32)->mat2x2f{lets=sin(a);letc=cos(a);returnmat2x2f(vec2f(c,s),vec2f(-s,c));}fninv2(m:mat2x2f)->mat2x2f{letdet=m[0].x*m[1].y-m[1].x*m[0].y;letsafe=select(det,1e-5,abs(det)<1e-5);returnmat2x2f(vec2f(m[1].y,-m[0].y),vec2f(-m[1].x,m[0].x))*(1.0/safe);}fnasp()->vec2f{returnresolution/min(resolution.x,resolution.y);}fntoPattern(uv:vec2f)->vec2f{return(rot(-radians(p.turn))*((uv-vec2f(0.5))*asp()-vec2f(p.x,p.y)))*p.scale;}fntoUv(x:vec2f)->vec2f{return((rot(radians(p.turn))*(x/p.scale))+vec2f(p.x,p.y))/asp()+vec2f(0.5);}// The mosaic keeps the frame's own size, because its cells are counted and not scaled.fntoMosaic(uv:vec2f)->vec2f{returnrot(-radians(p.turn))*((uv-vec2f(0.5))*asp()-vec2f(p.x,p.y))+vec2f(0.5);}fnmosaicToUv(m:vec2f)->vec2f{return((rot(radians(p.turn))*(m-vec2f(0.5)))+vec2f(p.x,p.y))/asp()+vec2f(0.5);}fnpic(uv:vec2f)->vec4f{returntextureSampleLevel(input,samp,uv,0.0);}fnhint(uv:vec2f)->vec4f{returntextureSampleLevel(guide,samp,uv,0.0);}fnluma(c:vec4f)->f32{returndot(c.rgb,vec3f(0.299,0.587,0.114));}// 0 oblique, 1 rectangular, 2 rhombic, 3 square, 4 hexagonal.fnlatKind()->i32{switchp.group{case0u,1u:{return0;}case2u,3u,5u,6u,7u:{return1;}case4u,8u:{return2;}case9u,10u,11u:{return3;}default:{return4;}}}fnbasis()->mat2x2f{leth=p.aspect;switchlatKind(){case0:{returnmat2x2f(vec2f(1.0,0.0),vec2f(p.skew,h));}case1:{returnmat2x2f(vec2f(1.0,0.0),vec2f(0.0,h));}case2:{returnmat2x2f(vec2f(0.5,h*0.5),vec2f(0.5,-h*0.5));}case3:{returnmat2x2f(vec2f(1.0,0.0),vec2f(0.0,1.0));}default:{returnmat2x2f(vec2f(1.0,0.0),vec2f(0.5,0.86602540));}}}fnspin()->i32{switchp.group{case1u,5u,6u,7u,8u:{return2;}case12u,13u,14u:{return3;}case9u,10u,11u:{return4;}case15u,16u:{return6;}default:{return1;}}}fnmirrored()->bool{switchp.group{case0u,1u,9u,12u,15u:{returnfalse;}default:{returntrue;}}}// The mirror's own line, as the angle it makes with x. p3m1 lies along the lattice and p31m across// it, which is the whole difference between the two.fnmirrorAngle()->f32{switchp.group{case2u,3u,4u,6u:{returnTAU*0.25;}case11u:{return-TAU*0.125;}case14u:{returnTAU/12.0;}default:{return0.0;}}}// What the mirror carries with it. Along the line it is a glide; across it, an offset mirror.fnglideStep()->vec2f{letb=basis();switchp.group{case3u:{returnb[1]*0.5;}case6u:{returnb[0]*0.5;}case7u,11u:{return(b[0]+b[1])*0.5;}default:{returnvec2f(0.0);}}}fnpointOrder()->i32{returnspin()*select(1,2,mirrored());}structOp{m:mat2x2f,t:vec2f}fnpointOp(k:i32)->Op{letn=spin();letturn=rot(TAU*f32(k%n)/f32(n));varo:Op;o.m=turn;o.t=vec2f(0.0);ifk>=n{letd=2.0*mirrorAngle();o.m=turn*mat2x2f(vec2f(cos(d),sin(d)),vec2f(sin(d),-cos(d)));o.t=turn*glideStep();}returno;}// One cell of the lattice sweeps the whole picture and comes back the way it went, so a field read// off the picture stays continuous where two cells meet.fncellUv(c:vec2f)->vec2f{lets=sin(TAU*0.5*c);returns*s;}fnsteer(uv:vec2f)->f32{returnselect(luma(pic(uv)),luma(hint(uv)),p.follow==1u);}// The picture's own gradient, turned around: the seam is pulled down the slope and settles where// the picture is dark. Bounded, because an unbounded field folds tiles through each other.fnimageField(c:vec2f)->vec2f{lete=0.01;letgx=steer(cellUv(c+vec2f(e,0.0)))-steer(cellUv(c-vec2f(e,0.0)));letgy=steer(cellUv(c+vec2f(0.0,e)))-steer(cellUv(c-vec2f(0.0,e)));letv=-vec2f(gx,gy)/(2.0*e);returnv/(length(v)+4.0);}// A lattice-periodic scalar's gradient, turned a quarter turn. Turned, because a gradient field// SQUEEZES the plane and a boundary pushed by one thickens rather than bends; its perpendicular// shears instead, which is the motion an interlocking arm is made of, and it holds the area of// every tile it moves.fnperiodicField(c:vec2f,phase:f32)->vec2f{vark=array<vec2f,4>(vec2f(1.0,0.0),vec2f(0.0,1.0),vec2f(1.0,1.0),vec2f(1.0,-1.0));letm=clamp(p.harmonics,1,4);varg=vec2f(0.0);varnorm=1e-5;for(vari=0;i<4;i++){ifi>=m{break;}letw=1.0/f32(i+1);letph=TAU*dot(k[i],c)+phase+hash11(p.seed+f32(i)*7.0)*TAU;g=g-k[i]*(w*TAU*sin(ph));norm=norm+w*TAU*length(k[i]);}returnvec2f(-g.y,g.x)/norm;}// The displacement the group carries: u(gx) = g u(x), by averaging one periodic field over the// point group. That identity is the whole reason a deformed tile still interlocks — push a boundary// out here and the same push arrives as a dent on every tile that meets it.fnshiftWith(x:vec2f,bi:mat2x2f,ops:i32,amp:f32,phase:f32)->vec2f{ifamp<=0.0{returnvec2f(0.0);}varu=vec2f(0.0);for(vark=0;k<OPS;k++){ifk>=ops{break;}leto=pointOp(k);letc=bi*(o.m*x+o.t);varf=periodicField(c,phase);ifp.fit>0.0{f=mix(f,imageField(c),p.fit);}u=u+transpose(o.m)*(transpose(bi)*f);}// The terms point every which way, so their sum grows as the root of their count rather than// with it. Dividing by that root is what makes `amount` mean one thing in all seventeen groups.returnu*(amp/sqrt(f32(ops)));}// A parquet deformation: the field's strength and its phase are read off where in the FRAME the// point stands, so the tiles change shape as they march and still fit together. The reading is// frame-relative and never pattern-relative, or the drift would move whenever the scale did.fnmorphAt(seen:vec2f)->vec2f{letdir=vec2f(cos(radians(p.angle)),sin(radians(p.angle)));letg=dot(seen,dir);returnvec2f(clamp(1.0+p.drift*g,0.0,2.0),p.swirl*g);}// The lattice point this one belongs to: the parallelogram first, then two sweeps of the// neighbours, which is what turns it into the cell that is actually nearest.fnlatticeIndex(x:vec2f,b:mat2x2f,bi:mat2x2f)->vec2f{varn=round(bi*x);for(varsweep=0;sweep<2;sweep++){letd=x-b*n;varbest=dot(d,d);varpick=vec2f(0.0);for(vari=-1;i<=1;i++){for(varj=-1;j<=1;j++){letv=vec2f(f32(i),f32(j));lete=d-b*v;letq=dot(e,e);ifq<best-1e-7{best=q;pick=v;}}}n=n+pick;}returnn;}fnvoronoiEdge(d:vec2f,b:mat2x2f)->f32{varbest=1e9;for(vari=-1;i<=1;i++){for(varj=-1;j<=1;j++){ifi==0&&j==0{continue;}letv=b*vec2f(f32(i),f32(j));letl=max(length(v),1e-5);best=min(best,0.5*l-dot(d,v)/l);}}returnbest;}fnwallpaperCell(x:vec2f)->Cell{letb=basis();letbi=inv2(b);varout:Cell;varpar=0.0;letg=p.group;// pg, pmg and pgg fold along their own glides, which no rotation about a lattice point reaches.ifg==3u||g==6u||g==7u{letraw=bi*x;letcell=floor(raw);varc=raw-cell;varhi=vec2f(0.5,1.0);ifg==3u{ifc.x>0.5{c=vec2f(1.0-c.x,fract(c.y+0.5));par=1.0;}}elseifg==6u{ifc.x>=0.5{c=vec2f(c.x-0.5,1.0-c.y);par=1.0-par;}ifc.x>0.25{c.x=0.5-c.x;par=1.0-par;}hi=vec2f(0.25,1.0);}else{ifc.x>=0.5{c=vec2f(c.x-0.5,fract(0.5-c.y));par=1.0-par;}ifc.y>=0.5{c=vec2f(fract(0.5-c.x),c.y-0.5);par=1.0-par;}hi=vec2f(0.5,0.5);}letm=min(min(c.x,hi.x-c.x)*length(b[0]),min(c.y,hi.y-c.y)*length(b[1]));out.local=select(toUv(b*(c-vec2f(0.5))),c,p.source==0u);out.seat=toUv(b*(cell+vec2f(0.5)));out.edge=m/p.scale;out.parity=par;out.id=hash11(hash21(cell)+par*0.517);returnout;}letn=latticeIndex(x,b,bi);vard=x-b*n;vare=voronoiEdge(d,b);// p4g's mirror is the one that misses the four-fold centre, so its wedge is the plain quarter// and the mirror's own angle belongs to the displacement field rather than to this fold.letcentred=mirrored()&&g!=11u;letorder=spin();letmu=select(mirrorAngle(),0.0,g==11u);letw=TAU/f32(order);letr=length(d);leta=atan2(d.y,d.x)-mu;letk=floor(a/w);vart=a-k*w;ifcentred&&t>w*0.5{t=w-t;par=1.0;}iforder>1||centred{lettop=select(w,w*0.5,centred);e=min(e,r*min(sin(t),sin(max(top-t,0.0))));}d=vec2f(cos(mu+t),sin(mu+t))*r;ifg==11u{lets=0.5*length(b[0]);e=min(e,abs(s-d.x-d.y)*0.70710678);ifd.x+d.y>s{d=vec2f(s-d.y,s-d.x);par=1.0-par;}}out.local=select(toUv(d),bi*d+vec2f(0.5),p.source==0u);out.seat=toUv(b*n);out.edge=max(e,0.0)/p.scale;out.parity=par;out.id=hash11(hash21(n)+f32(k)*0.131+par*0.517);returnout;}// A mirror as one curve: k|x|^2 - 2 n.x + d = 0, which is a circle when k is not zero and a line// when it is. Normalised so |n|^2 - k d = 1, the domain is where the power is negative, and the// Euclidean case falls out as a straight line with no arm of its own.structArc{k:f32,n:vec2f,d:f32}fnarcs(i:i32)->Arc{leta=TAU*0.5/max(p.sides,1.001);letb=TAU*0.5/max(p.meet,1.001);letc=TAU*0.5/max(p.hinge,1.001);varm:Arc;m.d=0.0;ifi==0{m.k=0.0;m.n=vec2f(0.0,1.0);returnm;}ifi==1{m.k=0.0;m.n=vec2f(sin(a),-cos(a));returnm;}letny=-cos(b);letnx=(-cos(c)-cos(a)*cos(b))/max(sin(a),1e-4);m.n=vec2f(nx,ny);m.k=nx+sin(b);m.d=nx-sin(b);returnm;}fnarcDist(m:Arc,x:vec2f)->f32{letpower=m.k*dot(x,x)-2.0*dot(m.n,x)+m.d;returnpower/max(2.0*length(m.k*x-m.n),1e-6);}fnarcReflect(m:Arc,x:vec2f)->vec2f{ifabs(m.k)<1e-5{returnx-2.0*(dot(m.n,x)-0.5*m.d)*m.n;}leto=m.n/m.k;lete=x-o;returno+e/max(m.k*m.k*dot(e,e),1e-9);}// Where the third mirror crosses the second. With the first crossing it at (1, 0) by construction,// that is the triangle.fntriCorner()->vec2f{leta=TAU*0.5/max(p.sides,1.001);lete=vec2f(cos(a),sin(a));letm=arcs(2);letb=dot(m.n,e);ifabs(m.k)<1e-5{returne*(m.d/(2.0*b));}letroot=sqrt(max(b*b-m.k*m.d,0.0));lets1=(b-root)/m.k;lets2=(b+root)/m.k;returne*select(max(s1,s2),min(s1,s2),min(s1,s2)>1e-4);}fnkaleidoCell(x:vec2f,amp:f32,phase:f32)->Cell{varout:Cell;// A hyperbolic plane ENDS. Its limit circle is where the third mirror is orthogonal to, and// the three domain tests alone do not name the outside of it — they answer yes out there too.letthird=arcs(2);letlimit=select(third.d/third.k,-1.0,abs(third.k)<1e-5);iflimit>0.0&&dot(x,x)>=limit{out.id=-1.0;returnout;}vary=x;varpar=0.0;varword=0u;varsteps=0u;varhome=false;for(vars=0;s<KMAX;s++){varworst=-1;varfar=1e-6;for(vari=0;i<3;i++){letv=arcDist(arcs(i),y);ifv>far{far=v;worst=i;}}ifworst<0{home=true;break;}y=arcReflect(arcs(worst),y);par=1.0-par;ifsteps<REPLAY{word=word|(u32(worst+1)<<(2u*steps));steps=steps+1u;}}if!home{out.id=-1.0;returnout;}vare=1e9;for(vari=0;i<3;i++){e=min(e,-arcDist(arcs(i),y));}// The tile's own place: the same mirrors, walked backwards off the triangle's centre.letcorner=triCorner();varseat=(vec2f(1.0,0.0)+corner)/3.0;for(vars=i32(steps)-1;s>=0;s--){seat=arcReflect(arcs(i32((word>>(2u*u32(s)))&3u)-1),seat);}ifamp>0.0{varbump=1.0;for(vari=0;i<3;i++){bump=bump*clamp(-arcDist(arcs(i),y)*6.0,0.0,1.0);}varf=periodicField(y*2.0,phase);ifp.fit>0.0{f=mix(f,imageField(y*2.0),p.fit);}y=y+f*(amp*bump*0.5);}letlo=min(vec2f(0.0),min(vec2f(1.0,0.0),corner));letspan=max(max(vec2f(1.0,0.0),corner)-lo,vec2f(1e-3));out.local=select(toUv(y),(y-lo)/span,p.source==0u);out.seat=toUv(seat);out.edge=max(e,0.0)/p.scale;out.parity=par;out.id=hash11(f32(word)*1e-3+f32(steps));returnout;}// De Bruijn's multigrid: n families of parallel lines, and the region a point falls in names one// vertex of the tiling that is dual to them. Five families is Penrose. Shift slides the whole// pattern, spread moves the families against each other, which rearranges tiles rather than// carrying them — the phason of a real quasicrystal.fnquasiCell(x:vec2f)->Cell{letn=clamp(p.fold,2,12);varv=vec2f(0.0);vare=1e9;varid=0.0;for(varj=0;j<12;j++){ifj>=n{break;}leta=TAU*0.5*f32(j)/f32(n);letdir=vec2f(cos(a),sin(a));letoff=fract(f32(j+1)*0.7548776662);lett=dot(x,dir)+p.shift+off+p.spread*hash11(f32(j)*3.1+0.5);letkf=floor(t);v=v+dir*kf;e=min(e,min(t-kf,1.0-(t-kf)));id=id+kf*(1.0+f32(j)*0.618);}letseat=v*(2.0/f32(n));varout:Cell;out.local=select(toUv(x),(x-seat)*0.5+vec2f(0.5),p.source==0u);out.seat=toUv(seat);out.edge=e/p.scale;out.parity=f32(u32(abs(id))&1u);out.id=hash11(id);returnout;}// A site wanders at most REACH cells from where it was born, which is what bounds the search for// the nearest one to a block around the point and keeps a full walk of the set out of every texel.// It has to be allowed to wander at all: a site pinned to its own square cannot crowd anywhere,// and crowding is the whole of what a weighted relaxation has to say. BLOCK is then not a taste:// a site outside it must be further off than the worst site inside, which needs 2 REACH + 1 cells.constREACH:f32=1.0;constBLOCK:i32=3;fnsiteAt(idx:vec2i,n:i32)->vec2f{letw=((idx%vec2i(n))+vec2i(n))%vec2i(n);lethome=(vec2f(w)+vec2f(0.5))/f32(n);letheld=textureLoad(sites,w,0).xy;letkept=all(abs(held-home)<=vec2f((REACH+0.01)/f32(n)));returnselect(home,held,kept)+vec2f(idx-w)/f32(n);}structNear{idx:vec2i,best:f32,second:f32,site:vec2f}fnnearest(q:vec2f,n:i32)->Near{letbase=vec2i(floor(q*f32(n)));letcq=select(vec3f(0.0),pic(mosaicToUv(q)).rgb,p.affinity>0.0);varr:Near;r.idx=base;r.site=q;r.best=1e9;r.second=1e9;for(vari=-BLOCK;i<=BLOCK;i++){for(varj=-BLOCK;j<=BLOCK;j++){letidx=base+vec2i(i,j);lets=siteAt(idx,n);letd=(q-s)*f32(n);varm=dot(d,d);ifp.affinity>0.0{letdc=cq-pic(mosaicToUv(s)).rgb;m=m+p.affinity*p.affinity*dot(dc,dc);}ifm<r.best{r.second=r.best;r.best=m;r.idx=((idx%vec2i(n))+vec2i(n))%vec2i(n);r.site=s;}elseifm<r.second{r.second=m;}}}returnr;}fndensity(q:vec2f)->f32{letuvq=mosaicToUv(q);switchp.density{case1u:{returnluma(pic(uvq))+0.02;}case2u:{return1.02-luma(pic(uvq));}case3u:{// A cell can only walk towards structure its own size, so the edge is measured over// half of one. A one-texel tap reads noise and under-samples the quadrature entirely.lete=0.5/f32(clamp(p.cells,2,96));letgx=luma(pic(uvq+vec2f(e,0.0)))-luma(pic(uvq-vec2f(e,0.0)));letgy=luma(pic(uvq+vec2f(0.0,e)))-luma(pic(uvq-vec2f(0.0,e)));returnsqrt(gx*gx+gy*gy)*8.0+0.05;}case4u:{returnluma(hint(uvq))+0.02;}default:{return1.0;}}}fnmosaicCell(m:vec2f)->Cell{letn=clamp(p.cells,2,96);letq=fract(m);letr=nearest(q,n);varout:Cell;out.local=select(mosaicToUv(m),(q-r.site)*f32(n)+vec2f(0.5),p.source==0u);out.seat=mosaicToUv(r.site);out.edge=(sqrt(r.second)-sqrt(r.best))*0.5/f32(n);out.parity=f32((r.idx.x+r.idx.y)&1);out.id=hash21(vec2f(r.idx));returnout;}fnhueRot(c:vec3f,a:f32)->vec3f{letk=vec3f(0.57735027);letca=cos(a);returnc*ca+cross(k,c)*sin(a)+k*dot(k,c)*(1.0-ca);}fnshade(uv:vec2f)->vec4f{letone=mat2x2f(vec2f(1.0,0.0),vec2f(0.0,1.0));varc:Cell;ifp.kind==3u{// The site grid is the mosaic's own lattice, so the displacement is periodic on THAT and// bends a cell rather than carrying it — a walk of at most a third of a cell, which the// search block was sized to cover.letm=toMosaic(uv);letn=f32(clamp(p.cells,2,96));letmo=morphAt(m-vec2f(0.5));letamp=max(p.amount*mo.x,0.0)*0.35;c=mosaicCell(m+shiftWith(m*n,one,1,amp,p.phase+mo.y)/n);}else{letx=toPattern(uv);letmo=morphAt(x/max(p.scale,1e-3));letamp=max(p.amount*mo.x,0.0)*0.35;letph=p.phase+mo.y;ifp.kind==0u{letbi=inv2(basis());c=wallpaperCell(x+shiftWith(x,bi,pointOrder(),amp,ph));}elseifp.kind==1u{c=kaleidoCell(x,amp,ph);}else{c=quasiCell(x+shiftWith(x,one,1,amp,ph));}}ifc.id<0.0{returnvec4f(0.0);}varcol=pic(mix(c.local,c.seat,p.flatten));col=mix(col,vec4f(vec3f(1.0)-col.rgb,col.a),p.figure*c.parity);col=vec4f(hueRot(col.rgb,hash11(c.id)*TAU*p.tint),col.a);letpx=max(fwidth(c.edge),1e-7);letline=1.0-smoothstep(px*0.5,px*1.5,c.edge);returnmix(col,vec4f(0.0,0.0,0.0,1.0),line*p.contour);}// One Lloyd step a frame, weighted by whatever density says. The sites walk to the centroids of// their own cells and the division settles onto the picture while it is being watched.fnnext_sites(uv:vec2f)->vec4f{letn=clamp(p.cells,2,96);letat=vec2i(floor(uv*resolution));ifp.kind!=3u||at.x>=n||at.y>=n{returnvec4f(0.0);}lethome=(vec2f(at)+vec2f(0.5))/f32(n);letheld=textureLoad(sites,at,0).xy;vars=home;ifframe>0u&&all(abs(held-home)<=vec2f((REACH+0.01)/f32(n))){s=held;}else{s=home+(vec2f(hash21(vec2f(at)),hash21(vec2f(at)+vec2f(0.0,77.0)))-0.5)*(0.4/f32(n));}varacc=vec2f(0.0);varmass=0.0;letstep=2.0/(f32(n)*f32(QUAD));for(vari=0;i<QUAD;i++){for(varj=0;j<QUAD;j++){letq=s+(vec2f(f32(i),f32(j))+vec2f(0.5)-f32(QUAD)*0.5)*step;ifall(nearest(fract(q),n).idx==at){letw=max(density(q),0.0);acc=acc+q*w;mass=mass+w;}}}ifmass>1e-5{s=mix(s,acc/mass,p.rate);}returnvec4f(clamp(s,home-vec2f(REACH/f32(n)),home+vec2f(REACH/f32(n))),0.0,1.0);}