Spaces:
Running
Running
Download index.html from HBB-Community/dots: direct link, hf CLI and curl.
- Browser
- Download file 13.7 kB
-
https://huggingface.co/spaces/HBB-Community/dots/resolve/main/index.html
- Command line
-
hf download hf://spaces/HBB-Community/dots/index.html
-
curl -L -o index.html https://huggingface.co/spaces/HBB-Community/dots/resolve/main/index.html
13.7 kB
| <html lang="ru"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>Нейроны</title> | |
| <style> | |
| body{margin:0;background:#000;overflow:hidden;font-family:monospace;color:#eee} | |
| canvas{display:block} | |
| .ui{position:fixed;top:10px;left:10px;z-index:10;font-size:12px;background:rgba(0,0,0,.8);padding:10px 14px;border-radius:6px;line-height:1.7} | |
| .ui button{background:#4f46e5;color:#fff;border:none;padding:4px 8px;border-radius:4px;cursor:pointer;font-family:monospace;font-size:11px;margin:2px} | |
| .ui button:hover{background:#4338ca} | |
| .ui button.active{background:#dc2626} | |
| .stat{color:#7ec8ff} | |
| </style> | |
| </head> | |
| <body> | |
| <div class="ui"> | |
| N: <span class="stat" id="count">30k</span> · | |
| Связей: <span class="stat" id="edges">0</span> · | |
| Ср.связ/нейрон: <span class="stat" id="avg">0</span> · | |
| FPS: <span class="stat" id="fps">0</span><br> | |
| <b>Форма:</b> | |
| <button id="btnRandom" onclick="setShape('random')">Рандом</button> | |
| <button id="btnSphere" onclick="setShape('sphere')">Шар</button> | |
| <button id="btnPlane" onclick="setShape('plane')">Плоскость</button> | |
| <button id="btnHalf" onclick="setShape('half')">Полукруг</button> | |
| <button onclick="setN(30000)">30k</button> | |
| <button onclick="setN(60000)">60k</button> | |
| <button onclick="setN(100000)">100k</button><br> | |
| <span style="opacity:.6">тяни — вращать · колесо — зум</span> | |
| </div> | |
| <canvas id="c"></canvas> | |
| <script> | |
| const canvas=document.getElementById('c'); | |
| const gl=canvas.getContext('webgl2')||canvas.getContext('webgl'); | |
| if(!gl){document.body.innerHTML='<h1>Нет WebGL</h1>';throw 0} | |
| let W=canvas.width=innerWidth,H=canvas.height=innerHeight; | |
| addEventListener('resize',()=>{W=canvas.width=innerWidth;H=canvas.height=innerHeight}); | |
| const BOX=1000; | |
| const CONNECT_D=60; | |
| const BREAK_D=CONNECT_D*1.5; | |
| const MAX_CONN=10; | |
| const MIN_DIST=22; | |
| const DAMPING=0.93; | |
| const DT=0.06; | |
| const GRID_CELL=CONNECT_D; | |
| const REBUILD=3; | |
| const ROOT={x:BOX/2,y:15,z:BOX/2}; | |
| const ROOT_ATTRACT=650; | |
| const ROOT_FORCE=25000; | |
| const VS_P=`attribute vec3 aPos;uniform mat4 uProj,uView;uniform float uSize; | |
| void main(){gl_Position=uProj*uView*vec4(aPos,1.0);gl_PointSize=uSize;}`; | |
| const FS_P=`precision mediump float;uniform vec3 uColor; | |
| void main(){vec2 d=gl_PointCoord-0.5;float r2=dot(d,d); | |
| if(r2>0.25)discard;gl_FragColor=vec4(uColor,(1.0-r2*4.0)*0.85);}`; | |
| const VS_L=`attribute vec3 aPos;uniform mat4 uProj,uView; | |
| void main(){gl_Position=uProj*uView*vec4(aPos,1.0);}`; | |
| const FS_L=`precision mediump float;uniform vec4 uColor; | |
| void main(){gl_FragColor=uColor;}`; | |
| function sh(t,s){const o=gl.createShader(t);gl.shaderSource(o,s);gl.compileShader(o); | |
| if(!gl.getShaderParameter(o,gl.COMPILE_STATUS))console.error(gl.getShaderInfoLog(o));return o} | |
| function mk(vs,fs){const p=gl.createProgram();gl.attachShader(p,sh(gl.VERTEX_SHADER,vs)); | |
| gl.attachShader(p,sh(gl.FRAGMENT_SHADER,fs));gl.linkProgram(p);return p} | |
| const progP=mk(VS_P,FS_P),progL=mk(VS_L,FS_L); | |
| function persp(f,a,n,fa){const t=1/Math.tan(f/2);return new Float32Array( | |
| [t/a,0,0,0,0,t,0,0,0,0,(fa+n)/(n-fa),-1,0,0,(2*fa*n)/(n-fa),0])} | |
| function lookAt(ex,ey,ez,cx,cy,cz,ux,uy,uz){ | |
| let zx=ex-cx,zy=ey-cy,zz=ez-cz;const zl=Math.hypot(zx,zy,zz);zx/=zl;zy/=zl;zz/=zl; | |
| let xx=uy*zz-uz*zy,xy=uz*zx-ux*zz,xz=ux*zy-uy*zx; | |
| const xl=Math.hypot(xx,xy,xz);xx/=xl;xy/=xl;xz/=xl; | |
| const yx=zy*xz-zz*xy,yy=zz*xx-zx*xz,yz=zx*xy-zy*xx; | |
| return new Float32Array([xx,yx,zx,0,xy,yy,zy,0,xz,yz,zz,0, | |
| -(xx*ex+xy*ey+xz*ez),-(yx*ex+yy*ey+yz*ez),-(zx*ex+zy*ey+zz*ez),1])} | |
| let N=30000; | |
| let pos,vel,connCount,connList; | |
| let edges=new Float32Array(0),edgeCount=0; | |
| let posBuf,edgeBuf,rootBuf,boxBuf; | |
| let curShape='random'; | |
| let forceBuf=null; | |
| function initShape(shape){ | |
| curShape=shape; | |
| forceBuf=null; | |
| pos=new Float32Array(N*3);vel=new Float32Array(N*3); | |
| connCount=new Int32Array(N); | |
| connList=new Int32Array(N*MAX_CONN).fill(-1); | |
| const cx=BOX/2,cy=BOX/2,cz=BOX/2; | |
| for(let i=0;i<N;i++){ | |
| let x,y,z; | |
| if(shape==='sphere'){ | |
| const u=Math.random()*2-1,phi=Math.random()*Math.PI*2,r=180*Math.cbrt(Math.random()); | |
| const s=Math.sqrt(1-u*u); | |
| x=cx+r*s*Math.cos(phi);y=cy+r*u;z=cz+r*s*Math.sin(phi); | |
| }else if(shape==='plane'){ | |
| x=cx+(Math.random()-0.5)*400;y=cy+(Math.random()-0.5)*400;z=cz+(Math.random()-0.5)*15; | |
| }else if(shape==='half'){ | |
| const a=Math.random()*Math.PI,r=250*Math.sqrt(Math.random()); | |
| x=cx+Math.cos(a)*r;y=cy-100+Math.random()*20;z=cz+Math.sin(a)*r; | |
| }else{ | |
| x=Math.random()*BOX;y=Math.random()*BOX;z=Math.random()*BOX; | |
| } | |
| pos[i*3]=x;pos[i*3+1]=y;pos[i*3+2]=z; | |
| } | |
| if(!posBuf)posBuf=gl.createBuffer(); | |
| gl.bindBuffer(gl.ARRAY_BUFFER,posBuf); | |
| gl.bufferData(gl.ARRAY_BUFFER,pos,gl.DYNAMIC_DRAW); | |
| if(!edgeBuf)edgeBuf=gl.createBuffer(); | |
| edges=new Float32Array(N*MAX_CONN*6);edgeCount=0; | |
| if(rootBuf)gl.deleteBuffer(rootBuf); | |
| rootBuf=gl.createBuffer(); | |
| gl.bindBuffer(gl.ARRAY_BUFFER,rootBuf); | |
| gl.bufferData(gl.ARRAY_BUFFER,new Float32Array([ROOT.x,ROOT.y,ROOT.z]),gl.STATIC_DRAW); | |
| if(!boxBuf)boxBuf=gl.createBuffer(); | |
| const b=BOX; | |
| const bv=new Float32Array([ | |
| 0,0,0, b,0,0, b,0,0, b,0,b, b,0,b, 0,0,b, 0,0,b, 0,0,0, | |
| 0,b,0, b,b,0, b,b,0, b,b,b, b,b,b, 0,b,b, 0,b,b, 0,b,0, | |
| 0,0,0, 0,b,0, b,0,0, b,b,0, b,0,b, b,b,b, 0,0,b, 0,b,b | |
| ]); | |
| gl.bindBuffer(gl.ARRAY_BUFFER,boxBuf); | |
| gl.bufferData(gl.ARRAY_BUFFER,bv,gl.STATIC_DRAW); | |
| } | |
| function setShape(s){ | |
| document.querySelectorAll('.ui button').forEach(b=>b.classList.remove('active')); | |
| const btn=document.getElementById('btn'+s[0].toUpperCase()+s.slice(1)); | |
| if(btn)btn.classList.add('active'); | |
| initShape(s); | |
| } | |
| function setN(n){ | |
| N=n; | |
| document.getElementById('count').textContent=(n/1000).toFixed(0)+'k'; | |
| initShape(curShape); | |
| } | |
| let grid,gridDims; | |
| function buildGrid(){ | |
| const cells=Math.ceil(BOX/GRID_CELL)+1; | |
| gridDims=cells; | |
| grid=new Array(cells*cells*cells); | |
| for(let i=0;i<N;i++){ | |
| const gx=Math.floor(pos[i*3]/GRID_CELL); | |
| const gy=Math.floor(pos[i*3+1]/GRID_CELL); | |
| const gz=Math.floor(pos[i*3+2]/GRID_CELL); | |
| const k=gx+cells*(gy+cells*gz); | |
| if(!grid[k])grid[k]=[]; | |
| grid[k].push(i); | |
| } | |
| } | |
| function key(x,y,z){return x+gridDims*(y+gridDims*z)} | |
| function addConn(i,j){ | |
| if(i===j)return false; | |
| if(connCount[i]>=MAX_CONN||connCount[j]>=MAX_CONN)return false; | |
| for(let c=0;c<connCount[i];c++){ | |
| if(connList[i*MAX_CONN+c]===j)return false; | |
| } | |
| connList[i*MAX_CONN+connCount[i]]=j;connCount[i]++; | |
| connList[j*MAX_CONN+connCount[j]]=i;connCount[j]++; | |
| return true; | |
| } | |
| function physics(){ | |
| if(!forceBuf||forceBuf.length!==N*3)forceBuf=new Float32Array(N*3); | |
| const force=forceBuf; | |
| force.fill(0); | |
| for(let i=0;i<N;i++){ | |
| const dx=ROOT.x-pos[i*3],dy=ROOT.y-pos[i*3+1],dz=ROOT.z-pos[i*3+2]; | |
| const d2=dx*dx+dy*dy+dz*dz+2000; | |
| const d=Math.sqrt(d2); | |
| if(d<ROOT_ATTRACT){ | |
| const f=ROOT_FORCE/d2; | |
| force[i*3]+=dx*f;force[i*3+1]+=dy*f;force[i*3+2]+=dz*f; | |
| } | |
| } | |
| for(let i=0;i<N;i++){ | |
| const px=pos[i*3],py=pos[i*3+1],pz=pos[i*3+2]; | |
| let fx=0,fy=0,fz=0; | |
| for(let c=0;c<connCount[i];c++){ | |
| const j=connList[i*MAX_CONN+c]; | |
| const dx=pos[j*3]-px,dy=pos[j*3+1]-py,dz=pos[j*3+2]-pz; | |
| const d=Math.sqrt(dx*dx+dy*dy+dz*dz)+0.001; | |
| if(d>=BREAK_D)continue; | |
| const diff=d-MIN_DIST; | |
| const w=1.0+connCount[j]*0.1; | |
| const pull=w*0.5*diff; | |
| fx+=(dx/d)*pull;fy+=(dy/d)*pull;fz+=(dz/d)*pull; | |
| } | |
| const speedMul=1+connCount[i]*0.15; | |
| force[i*3]+=fx*speedMul;force[i*3+1]+=fy*speedMul;force[i*3+2]+=fz*speedMul; | |
| } | |
| buildGrid(); | |
| for(let i=0;i<N;i++){ | |
| const px=pos[i*3],py=pos[i*3+1],pz=pos[i*3+2]; | |
| const gx=Math.floor(px/GRID_CELL),gy=Math.floor(py/GRID_CELL),gz=Math.floor(pz/GRID_CELL); | |
| let checked=0; | |
| for(let dx=-1;dx<=1&&checked<50;dx++) | |
| for(let dy=-1;dy<=1&&checked<50;dy++) | |
| for(let dz=-1;dz<=1&&checked<50;dz++){ | |
| const nx=gx+dx,ny=gy+dy,nz=gz+dz; | |
| if(nx<0||ny<0||nz<0||nx>=gridDims||ny>=gridDims||nz>=gridDims)continue; | |
| const bucket=grid[key(nx,ny,nz)]; | |
| if(!bucket)continue; | |
| for(let k=0;k<bucket.length;k++){ | |
| const j=bucket[k]; | |
| if(j===i)continue; | |
| const ddx=pos[j*3]-px,ddy=pos[j*3+1]-py,ddz=pos[j*3+2]-pz; | |
| const d2=ddx*ddx+ddy*ddy+ddz*ddz; | |
| if(d2>MIN_DIST*MIN_DIST)continue; | |
| const d=Math.sqrt(d2)+0.001; | |
| const push=(MIN_DIST-d)*3; | |
| force[i*3]-=(ddx/d)*push;force[i*3+1]-=(ddy/d)*push;force[i*3+2]-=(ddz/d)*push; | |
| checked++; | |
| if(checked>50)break; | |
| } | |
| } | |
| } | |
| for(let i=0;i<N;i++){ | |
| let vx=vel[i*3]+force[i*3]*DT; | |
| let vy=vel[i*3+1]+force[i*3+1]*DT; | |
| let vz=vel[i*3+2]+force[i*3+2]*DT; | |
| vx*=DAMPING;vy*=DAMPING;vz*=DAMPING; | |
| let nx=pos[i*3]+vx*DT,ny=pos[i*3+1]+vy*DT,nz=pos[i*3+2]+vz*DT; | |
| if(nx<0){nx=0;vx*=-0.5}if(nx>BOX){nx=BOX;vx*=-0.5} | |
| if(ny<0){ny=0;vy*=-0.5}if(ny>BOX){ny=BOX;vy*=-0.5} | |
| if(nz<0){nz=0;vz*=-0.5}if(nz>BOX){nz=BOX;vz*=-0.5} | |
| pos[i*3]=nx;pos[i*3+1]=ny;pos[i*3+2]=nz; | |
| vel[i*3]=vx;vel[i*3+1]=vy;vel[i*3+2]=vz; | |
| } | |
| } | |
| function breakLongEdges(){ | |
| const B2=BREAK_D*BREAK_D; | |
| for(let i=0;i<N;i++){ | |
| let w=0; | |
| for(let c=0;c<connCount[i];c++){ | |
| const j=connList[i*MAX_CONN+c]; | |
| const dx=pos[j*3]-pos[i*3],dy=pos[j*3+1]-pos[i*3+1],dz=pos[j*3+2]-pos[i*3+2]; | |
| if(dx*dx+dy*dy+dz*dz>B2)continue; | |
| connList[i*MAX_CONN+w]=j;w++; | |
| } | |
| for(let c=w;c<MAX_CONN;c++)connList[i*MAX_CONN+c]=-1; | |
| connCount[i]=w; | |
| } | |
| } | |
| function findNewEdges(){ | |
| buildGrid(); | |
| for(let i=0;i<N;i++){ | |
| if(connCount[i]>=MAX_CONN)continue; | |
| const px=pos[i*3],py=pos[i*3+1],pz=pos[i*3+2]; | |
| const gx=Math.floor(px/GRID_CELL),gy=Math.floor(py/GRID_CELL),gz=Math.floor(pz/GRID_CELL); | |
| outer: | |
| for(let dx=-1;dx<=1;dx++) | |
| for(let dy=-1;dy<=1;dy++) | |
| for(let dz=-1;dz<=1;dz++){ | |
| if(connCount[i]>=MAX_CONN)break outer; | |
| const nx=gx+dx,ny=gy+dy,nz=gz+dz; | |
| if(nx<0||ny<0||nz<0||nx>=gridDims||ny>=gridDims||nz>=gridDims)continue; | |
| const bucket=grid[key(nx,ny,nz)]; | |
| if(!bucket)continue; | |
| for(let k=0;k<bucket.length;k++){ | |
| if(connCount[i]>=MAX_CONN)break outer; | |
| const j=bucket[k]; | |
| if(j<=i)continue; | |
| if(connCount[j]>=MAX_CONN)continue; | |
| const ddx=pos[j*3]-px,ddy=pos[j*3+1]-py,ddz=pos[j*3+2]-pz; | |
| if(ddx*ddx+ddy*ddy+ddz*ddz>CONNECT_D*CONNECT_D)continue; | |
| addConn(i,j); | |
| } | |
| } | |
| } | |
| } | |
| function rebuildEdges(){ | |
| edgeCount=0; | |
| let ev=0; | |
| const maxE=edges.length/6; | |
| for(let i=0;i<N&&ev<maxE;i++){ | |
| for(let c=0;c<connCount[i];c++){ | |
| const j=connList[i*MAX_CONN+c]; | |
| if(j<i)continue; | |
| edges[ev++]=pos[i*3];edges[ev++]=pos[i*3+1];edges[ev++]=pos[i*3+2]; | |
| edges[ev++]=pos[j*3];edges[ev++]=pos[j*3+1];edges[ev++]=pos[j*3+2]; | |
| edgeCount++; | |
| if(ev>=maxE)break; | |
| } | |
| if(ev>=maxE)break; | |
| } | |
| gl.bindBuffer(gl.ARRAY_BUFFER,edgeBuf); | |
| gl.bufferSubData(gl.ARRAY_BUFFER,0,edges.subarray(0,ev)); | |
| } | |
| let camAngle=0.7,camElev=0.4,camDist=2200; | |
| let drag=false,lx=0,ly=0; | |
| canvas.addEventListener('mousedown',e=>{drag=true;lx=e.clientX;ly=e.clientY}); | |
| canvas.addEventListener('mouseup',()=>drag=false); | |
| canvas.addEventListener('mousemove',e=>{ | |
| if(!drag)return; | |
| camAngle+=(e.clientX-lx)*0.006; | |
| camElev=Math.max(-1.4,Math.min(1.4,camElev+(e.clientY-ly)*0.006)); | |
| lx=e.clientX;ly=e.clientY; | |
| }); | |
| canvas.addEventListener('wheel',e=>{ | |
| camDist=Math.max(400,Math.min(5000,camDist+e.deltaY*1.5)); | |
| },{passive:true}); | |
| let frames=0,lastFps=performance.now(); | |
| function draw(){ | |
| gl.viewport(0,0,W,H); | |
| gl.clearColor(0,0,0,1); | |
| gl.clear(gl.COLOR_BUFFER_BIT); | |
| gl.enable(gl.BLEND); | |
| gl.blendFunc(gl.SRC_ALPHA,gl.ONE); | |
| const proj=persp(Math.PI/3,W/H,1,8000); | |
| const cx=BOX/2,cy=BOX/2,cz=BOX/2; | |
| const ex=cx+Math.cos(camAngle)*Math.cos(camElev)*camDist; | |
| const ey=cy+Math.sin(camElev)*camDist; | |
| const ez=cz+Math.sin(camAngle)*Math.cos(camElev)*camDist; | |
| const view=lookAt(ex,ey,ez,cx,cy,cz,0,1,0); | |
| // Коробка | |
| gl.useProgram(progL); | |
| gl.uniformMatrix4fv(gl.getUniformLocation(progL,'uProj'),false,proj); | |
| gl.uniformMatrix4fv(gl.getUniformLocation(progL,'uView'),false,view); | |
| gl.uniform4f(gl.getUniformLocation(progL,'uColor'),0.3,0.5,0.8,0.35); | |
| gl.bindBuffer(gl.ARRAY_BUFFER,boxBuf); | |
| const aPL=gl.getAttribLocation(progL,'aPos'); | |
| gl.enableVertexAttribArray(aPL); | |
| gl.vertexAttribPointer(aPL,3,gl.FLOAT,false,0,0); | |
| gl.drawArrays(gl.LINES,0,24); | |
| // Связи | |
| if(edgeCount>0){ | |
| gl.uniform4f(gl.getUniformLocation(progL,'uColor'),0.35,0.55,1.0,0.12); | |
| gl.bindBuffer(gl.ARRAY_BUFFER,edgeBuf); | |
| gl.vertexAttribPointer(aPL,3,gl.FLOAT,false,0,0); | |
| gl.drawArrays(gl.LINES,0,edgeCount*2); | |
| } | |
| // Точки | |
| gl.useProgram(progP); | |
| gl.uniformMatrix4fv(gl.getUniformLocation(progP,'uProj'),false,proj); | |
| gl.uniformMatrix4fv(gl.getUniformLocation(progP,'uView'),false,view); | |
| gl.uniform1f(gl.getUniformLocation(progP,'uSize'),N>60000?1.2:2.2); | |
| gl.uniform3f(gl.getUniformLocation(progP,'uColor'),0.55,0.78,1.0); | |
| gl.bindBuffer(gl.ARRAY_BUFFER,posBuf); | |
| gl.bufferSubData(gl.ARRAY_BUFFER,0,pos); | |
| const aP2=gl.getAttribLocation(progP,'aPos'); | |
| gl.enableVertexAttribArray(aP2); | |
| gl.vertexAttribPointer(aP2,3,gl.FLOAT,false,0,0); | |
| gl.drawArrays(gl.POINTS,0,N); | |
| // Ствол | |
| gl.uniform1f(gl.getUniformLocation(progP,'uSize'),20.0); | |
| gl.uniform3f(gl.getUniformLocation(progP,'uColor'),1.0,0.3,0.4); | |
| gl.bindBuffer(gl.ARRAY_BUFFER,rootBuf); | |
| gl.vertexAttribPointer(aP2,3,gl.FLOAT,false,0,0); | |
| gl.drawArrays(gl.POINTS,0,1); | |
| frames++; | |
| const now=performance.now(); | |
| if(now-lastFps>500){ | |
| document.getElementById('fps').textContent=Math.round(frames*1000/(now-lastFps)); | |
| document.getElementById('edges').textContent=edgeCount.toLocaleString(); | |
| let total=0;for(let i=0;i<N;i++)total+=connCount[i]; | |
| document.getElementById('avg').textContent=(total/N).toFixed(1); | |
| frames=0;lastFps=now; | |
| } | |
| } | |
| initShape('random'); | |
| document.getElementById('btnRandom').classList.add('active'); | |
| let frameCount=0; | |
| (function loop(){ | |
| physics(); | |
| breakLongEdges(); | |
| if(frameCount++%REBUILD===0)findNewEdges(); | |
| rebuildEdges(); | |
| draw(); | |
| requestAnimationFrame(loop); | |
| })(); | |
| </script> | |
| </body> | |
| </html> |