-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathboids.html
More file actions
79 lines (76 loc) · 4.72 KB
/
Copy pathboids.html
File metadata and controls
79 lines (76 loc) · 4.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="icon" type="image/png" href="../favicon.png">
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<link rel="icon" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+CiAgPHJlY3QgZmlsbD0iIzBhMGEwYSIgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIi8+CiAgPHBhdGggZD0iTTggOCBMMjQgOCBMMjQgMjQgTDggMjQgWiBNMTIgMTIgTDIwIDEyIEwyMCAyMCBMMTIgMjAgWiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIiBzdHJva2Utd2lkdGg9IjEuNSIvPgo8L3N2Zz4K"/>
<title>Boids</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
html,body{height:100%;background:#05070b;overflow:hidden}
canvas{display:block;width:100%;height:100%;touch-action:none}
#info{position:fixed;bottom:calc(12px + env(safe-area-inset-bottom));left:50%;transform:translateX(-50%);color:rgba(255,255,255,0.9);background:rgba(0,0,0,0.6);padding:6px 12px;border-radius:6px;text-shadow:0 1px 3px rgba(0,0,0,0.9);font:13px/1.4 ui-monospace,monospace;pointer-events:none;text-align:center}
.back-btn{position:fixed;top:calc(12px + env(safe-area-inset-top));right:calc(12px + env(safe-area-inset-right));background:rgba(20,20,20,0.8);border:1px solid #444;color:#e0e0e0;padding:8px 14px;border-radius:6px;font:12px ui-monospace,monospace;cursor:pointer;text-decoration:none;backdrop-filter:blur(8px);z-index:1000;transition:border-color 0.2s}
.back-btn:hover{border-color:#888}
</style>
</head>
<body>
<a href="./" class="back-btn">← Gallery</a>
<canvas id="c"></canvas>
<div id="info">Boids (Craig Reynolds, 1986) · move mouse to attract · click to reseed</div>
<script>
const c=document.getElementById('c'),ctx=c.getContext('2d');
let W,H,px=null,py=null;
const N=120,boid=[];
function resize(){W=c.width=innerWidth;H=c.height=innerHeight}
function reset(){
boid.length=0;
for(let i=0;i<N;i++)boid.push({x:Math.random()*W,y:Math.random()*H,vx:(Math.random()*2-1)*0.8,vy:(Math.random()*2-1)*0.8});
}
resize();reset();
addEventListener('resize',()=>{resize();reset()});
addEventListener('click',reset);
addEventListener('mousemove',e=>{px=e.clientX;py=e.clientY});
addEventListener('mouseleave',()=>{px=py=null});
addEventListener('touchmove',e=>{px=e.touches[0].clientX;py=e.touches[0].clientY},{passive:true});
addEventListener('touchend',()=>{px=py=null});
const maxSpeed=2.2,maxForce=0.06;
function wrap(b){if(b.x<-20)b.x=W+20;if(b.x>W+20)b.x=-20;if(b.y<-20)b.y=H+20;if(b.y>H+20)b.y=-20}
function loop(){
ctx.fillStyle='#05070b';ctx.fillRect(0,0,W,H);
for(let i=0;i<N;i++){
const b=boid[i];
let sx=0,sy=0,sa=0,ax=0,ay=0,aa=0,cx=0,cy=0,ca=0;
for(let j=0;j<N;j++){
if(i===j)continue;const o=boid[j];
const dx=o.x-b.x,dy=o.y-b.y,d2=dx*dx+dy*dy;
if(d2<0.0001)continue;
if(d2<18*18){const d=Math.sqrt(d2);sx-=dx/d;sy-=dy/d;sa++}
if(d2<42*42){ax+=o.vx;ay+=o.vy;aa++}
if(d2<52*52){cx+=o.x;cy+=o.y;ca++}
}
let fx=0,fy=0;
if(sa){sx/=sa;sy/=sa;const m=Math.hypot(sx,sy)||1;sx=(sx/m)*maxSpeed-b.vx;sy=(sy/m)*maxSpeed-b.vy;const sm=Math.hypot(sx,sy)||1;if(sm>maxForce){sx=(sx/sm)*maxForce;sy=(sy/sm)*maxForce}fx+=sx*1.35;fy+=sy*1.35}
if(aa){ax/=aa;ay/=aa;const m=Math.hypot(ax,ay)||1;ax=(ax/m)*maxSpeed-b.vx;ay=(ay/m)*maxSpeed-b.vy;const am=Math.hypot(ax,ay)||1;if(am>maxForce){ax=(ax/am)*maxForce;ay=(ay/am)*maxForce}fx+=ax*0.85;fy+=ay*0.85}
if(ca){cx=cx/ca-b.x;cy=cy/ca-b.y;const m=Math.hypot(cx,cy)||1;cx=(cx/m)*maxSpeed-b.vx;cy=(cy/m)*maxSpeed-b.vy;const cm=Math.hypot(cx,cy)||1;if(cm>maxForce){cx=(cx/cm)*maxForce;cy=(cy/cm)*maxForce}fx+=cx*0.65;fy+=cy*0.65}
if(px!=null){const dx=px-b.x,dy=py-b.y,d=Math.hypot(dx,dy)||1;const pull=Math.min(1,180/d)*0.015;fx+=(dx/d)*pull;fy+=(dy/d)*pull}
b.vx+=fx;b.vy+=fy;const sp=Math.hypot(b.vx,b.vy)||1;if(sp>maxSpeed){b.vx=(b.vx/sp)*maxSpeed;b.vy=(b.vy/sp)*maxSpeed}
b.x+=b.vx;b.y+=b.vy;wrap(b);
}
ctx.fillStyle='rgba(192,192,192,0.7)';ctx.strokeStyle='rgba(255,255,255,0.2)';ctx.lineWidth=1;
for(const b of boid){
const a=Math.atan2(b.vy,b.vx),s=7;
ctx.beginPath();ctx.moveTo(b.x+Math.cos(a)*s,b.y+Math.sin(a)*s);
ctx.lineTo(b.x+Math.cos(a+2.4)*s*0.75,b.y+Math.sin(a+2.4)*s*0.75);
ctx.lineTo(b.x+Math.cos(a-2.4)*s*0.75,b.y+Math.sin(a-2.4)*s*0.75);
ctx.closePath();ctx.fill();ctx.stroke();
}
requestAnimationFrame(loop);
}
loop();
</script>
<script>(function(){var inFrame=false;try{inFrame=window.self!==window.top}catch(e){inFrame=true}if(inFrame)document.querySelectorAll("#info,.info,#ctrl,#buttons,#depth,.controls,#mode,button,.back-btn").forEach(function(e){e.style.display="none"})})()</script>
<script>if(window!==window.top){document.querySelectorAll("#info,#epoch,#seed,#params,#gen,#ctrl,#depth,.info,#buttons,.back-btn").forEach(function(e){e.style.display="none"})}</script>
</body>
</html>