/*
title: Random Cilia Creatures
category: animation
files:
head
../point_src/point.js
../point_src/stage.js
---
Random blob, 1 to 6 patches with 2 to 15 articulated cilia each.
Arrow keys control propulsion and turning. R regenerates the creature.
*/
class MainStage extends Stage {
canvas="playspace"
init(w,h) {
this.x=w/2;this.y=h/2;this.vx=0;this.vy=0;this.angle=0;this.omega=0;this.t=0;
this.keys={};this.last=performance.now();this.randomise();
if(!this.bound){
this.down=e=>{if(e.key==="r"||e.key==="R"){this.randomise();e.preventDefault()}
if(e.key.startsWith("Arrow")){this.keys[e.key]=true;e.preventDefault()}};
this.up=e=>{if(e.key.startsWith("Arrow")){this.keys[e.key]=false;e.preventDefault()}};
window.addEventListener("keydown",this.down);window.addEventListener("keyup",this.up);this.bound=true;
}
}
randomise(){
this.lobes=Array.from({length:7},()=>Math.random()*2-1);
// Different silhouettes: round, slim, bean, kidney, irregular.
this.bodyType=Math.floor(Math.random()*5);
this.scaleX=[1,1.45,1.4,1.35,.85][this.bodyType]*(0.95+Math.random()*.15);
this.scaleY=[1,.48,.66,.7,1.1][this.bodyType]*(0.92+Math.random()*.16);
this.concavity=this.bodyType===3?.30:this.bodyType===2?.13:0;
const n=1+Math.floor(Math.random()*6);
this.patches=Array.from({length:n},(_,p)=>{
const count=2+Math.floor(Math.random()*14),a=Math.random()*Math.PI*2,spacing=.025+Math.random()*.11;
return {strength:.65+Math.random()*.35,hairs:Array.from({length:count},(_,i)=>({
a:a+(i-(count-1)/2)*spacing,phase:i*.42,len:29+Math.random()*22,bias:(Math.random()-.5)*1.8,curve:(Math.random()-.5)*1.3,
act:0,nodes:Array.from({length:6},()=>({x:0,y:0}))
}))};
});
}
shape(a) {
let r=66;
for(let j=0;j2.5){this.vx*=2.5/speed;this.vy*=2.5/speed}
this.x=Math.max(25,Math.min(w-25,this.x+this.vx*dt*.2));
this.y=Math.max(25,Math.min(h-25,this.y+this.vy*dt*.2));
ctx.font="13px sans-serif";ctx.fillStyle="#d8f3ed";
ctx.fillText(this.patches.length+" patches, "+count+" cilia | Up/Down: swim | Left/Right: turn | R: regenerate",15,24);
}
}
const stage=MainStage.go();