PageSourceSearch

https://sakana.ai/sketch.js

js sakana.ai collected 2026-09-24 08:48:56 UTC 14,361 bytes, 413 lines download raw bytes

1var canvas;
2let flock;
3var boidCounter = 1;
4let fuguEl = null;     // cached reference to the floating fugu CTA element
5let fuguBounds = null; // peak-scale bounding rect (computed at setup / resize, not per frame)
6
7// Compute the floating fugu's bounding box at its peak (scale 1.10) size.
8// offsetWidth/Height give the unscaled layout box (the inner <img> breathes with scale,
9// but the parent layout doesn't change). We expand by the peak scale factor so the
10// avoidance zone is sized for the largest the fugu visually gets.
11// Only called once at setup and again on window resize — not per frame.
12function refreshFuguBounds() {
13  if (!fuguEl) fuguEl = document.querySelector('.fugu-cta');
14  if (!fuguEl) { fuguBounds = null; return; }
15  const layoutW = fuguEl.offsetWidth;
16  const layoutH = fuguEl.offsetHeight;
17  if (layoutW === 0 || layoutH === 0) { fuguBounds = null; return; }
18  const rect = fuguEl.getBoundingClientRect();
19  const centerX = rect.left + rect.width / 2;
20  const centerY = rect.top + rect.height / 2;
21  const peakScale = 1.10; // matches @keyframes fugu-breathe peak in index.html
22  const peakW = layoutW * peakScale;
23  const peakH = layoutH * peakScale;
24  fuguBounds = {
25    x: centerX - peakW / 2,
26    y: centerY - peakH / 2,
27    w: peakW,
28    h: peakH
29  };
30}
31
32function windowResized() {
33  resizeCanvas(windowWidth, windowHeight);
34  background("#fff");
35  refreshFuguBounds();
36}
37
38function setup() {
39  canvas = createCanvas(windowWidth, windowHeight);
40  canvas.position(0, 0);
41  canvas.style('z-index', '-1');
42  background("#fff");
43  boidCounter = 1;
44
45  refreshFuguBounds();
46
47  flock = new Flock();
48  // Add an initial set of boids into the system
49  for (let i = 0; i < 70; i++) {
50    let b = null;
51    if (i < 35) {
52      b = new Boid(width / 2,height / 2);
53    } else {
54      b = new Boid(random(width),random(height));
55    }    
56    flock.addBoid(b);
57  }
58}
59
60function draw() {
61  background("#fff");
62  flock.run();
63}
64
65// Add a new boid into the System
66function mouseDragged() {
67  if (boidCounter <= 250) {
68    flock.addBoid(new Boid(mouseX, mouseY));
69  }
70}
71
72// The Nature of Code
73// Daniel Shiffman
74// http://natureofcode.com
75
76// Flock object
77// Does very little, simply manages the array of all the boids
78
79function Flock() {
80  // An array for all the boids
81  this.boids = []; // Initialize the array
82}
83
84Flock.prototype.run = function() {
85  for (let i = 0; i < this.boids.length; i++) {
86    this.boids[i].run(this.boids);  // Passing the entire list of boids to each boid individually
87  }
88}
89
90Flock.prototype.addBoid = function(b) {
91  this.boids.push(b);
92}
93
94// The Nature of Code
95// Daniel Shiffman
96// http://natureofcode.com
97
98// Boid class
99// Methods for Separation, Cohesion, Alignment added
100
101function Boid(x, y) {
102  this.acceleration = createVector(0, 0);
103  this.velocity = createVector(random(-1, 1), random(-1, 1));
104  this.position = createVector(x, y);
105  this.r = 3.0;
106  this.maxspeed = 3;    // Maximum speed
107  this.maxforce = 0.05; // Maximum steering force
108  this.boidCounter = boidCounter;
109  boidCounter += 1;
110  this.noiseOffsetX = random(1000); // Random starting point in noise space
111  this.noiseOffsetY = random(1000);
112  this.noiseIncrement = 0.002; // How fast we move through noise space
113}
114
115Boid.prototype.run = function(boids) {
116  this.flock(boids);
117  this.update();
118  this.borders();
119  this.render();
120}
121
122Boid.prototype.applyForce = function(force) {
123  // We could add mass here if we want A = F / M
124  this.acceleration.add(force);
125}
126
127// We accumulate a new acceleration each time based on three rules
128Boid.prototype.flock = function(boids) {
129  // Sakana AI does it's own thing!!
130  if (this.boidCounter == boidCounter-1 && boidCounter > 2) {
131    let noiseX = noise(this.noiseOffsetX);
132    let noiseY = noise(this.noiseOffsetY);
133    
134    // Convert noise (0-1) to forces (-1 to 1)
135    let forceX = map(noiseX, 0, 1, -1, 1);
136    let forceY = map(noiseY, 0, 1, -1, 1);
137    
138    // Create smooth random force
139    let randomForce = createVector(forceX, forceY);
140    randomForce.mult(5.5); // Adjust this to control how strong the random movement is
141    
142    // Apply forces
143    this.applyForce(randomForce);
144    // this.applyForce(centerForce);
145    
146    // Increment noise offsets
147    this.noiseOffsetX += this.noiseIncrement;
148    this.noiseOffsetY += this.noiseIncrement + 0.001; // Slightly different increment for Y
149  } else {
150    let sep = this.separate(boids);   // Separation
151    let ali = this.align(boids);      // Alignment
152    let coh = this.cohesion(boids);   // Cohesion
153    // Arbitrarily weight these forces
154    sep.mult(1.5);
155    ali.mult(1.0);
156    coh.mult(1.0);
157    // Add the force vectors to acceleration
158    this.applyForce(sep);
159    this.applyForce(ali);
160    this.applyForce(coh);
161  }
162
163  // Avoid the floating fugu CTA (applies to leader boid too)
164  let avoid = this.avoid(fuguBounds);
165  if (avoid.mag() > 0) {
166    this.applyForce(avoid);
167  }
168}
169
170// Steer around the floating fugu CTA — predictive avoidance.
171// Only activates when the boid's near-future position is on track to enter the rect,
172// then nudges it sideways (perpendicular to its velocity) so it curves around.
173// This avoids the constant-repulsion oscillation that caused jittery motion before.
174Boid.prototype.avoid = function(bounds) {
175  if (!bounds) return createVector(0, 0);
176
177  // Predict position ~50 frames ahead — gives boids enough lead time to curve away
178  const future = p5.Vector.add(this.position, p5.Vector.mult(this.velocity, 50));
179  const pad = 22;
180  const futureInside =
181    future.x >= bounds.x - pad && future.x <= bounds.x + bounds.w + pad &&
182    future.y >= bounds.y - pad && future.y <= bounds.y + bounds.h + pad;
183
184  // Fallback for boids that somehow ended up close to or inside the rect (e.g. spawned via mouse drag)
185  const nearestX = Math.max(bounds.x, Math.min(this.position.x, bounds.x + bounds.w));
186  const nearestY = Math.max(bounds.y, Math.min(this.position.y, bounds.y + bounds.h));
187  const dx = this.position.x - nearestX;
188  const dy = this.position.y - nearestY;
189  const inImmediateZone = dx*dx + dy*dy < 30*30;
190
191  if (!futureInside && !inImmediateZone) return createVector(0, 0);
192
193  // Steer perpendicular to current velocity, toward the side that points away from the rect center
194  const cx = bounds.x + bounds.w / 2;
195  const cy = bounds.y + bounds.h / 2;
196  const toCenter = createVector(cx - this.position.x, cy - this.position.y);
197  const perp = createVector(-this.velocity.y, this.velocity.x);
198  if (perp.dot(toCenter) > 0) perp.mult(-1);
199  if (perp.mag() < 0.001) return createVector(0, 0);
200  perp.normalize();
201  perp.mult(this.maxspeed);
202
203  let steer = p5.Vector.sub(perp, this.velocity);
204  steer.limit(this.maxforce * 3.5);
205  return steer;
206}
207
208// Method to update location
209Boid.prototype.update = function() {
210  // Update velocity
211  this.velocity.add(this.acceleration);
212  // Limit speed
213  this.velocity.limit(this.maxspeed);
214  this.position.add(this.velocity);
215
216  // Hard constraint: if this frame's motion landed the boid inside the fugu rect,
217  // push it back out along the nearest edge and reflect its velocity component (with damping).
218  // This is the safety net behind the predictive `avoid()` steering — guarantees no passthrough.
219  if (fuguBounds) {
220    const pad = 4;
221    const left = fuguBounds.x - pad;
222    const right = fuguBounds.x + fuguBounds.w + pad;
223    const top = fuguBounds.y - pad;
224    const bottom = fuguBounds.y + fuguBounds.h + pad;
225
226    if (this.position.x > left && this.position.x < right &&
227        this.position.y > top && this.position.y < bottom) {
228      const distLeft = this.position.x - left;
229      const distRight = right - this.position.x;
230      const distTop = this.position.y - top;
231      const distBottom = bottom - this.position.y;
232
233      // Escape along whichever axis is closer
234      if (Math.min(distLeft, distRight) < Math.min(distTop, distBottom)) {
235        if (distLeft < distRight) {
236          this.position.x = left;
237          if (this.velocity.x > 0) this.velocity.x = -this.velocity.x * 0.6;
238        } else {
239          this.position.x = right;
240          if (this.velocity.x < 0) this.velocity.x = -this.velocity.x * 0.6;
241        }
242      } else {
243        if (distTop < distBottom) {
244          this.position.y = top;
245          if (this.velocity.y > 0) this.velocity.y = -this.velocity.y * 0.6;
246        } else {
247          this.position.y = bottom;
248          if (this.velocity.y < 0) this.velocity.y = -this.velocity.y * 0.6;
249        }
250      }
251    }
252  }
253
254  // Reset accelertion to 0 each cycle
255  this.acceleration.mult(0);
256}
257
258// A method that calculates and applies a steering force towards a target
259// STEER = DESIRED MINUS VELOCITY
260Boid.prototype.seek = function(target) {
261  let desired = p5.Vector.sub(target,this.position);  // A vector pointing from the location to the target
262  // Normalize desired and scale to maximum speed
263  desired.normalize();
264  desired.mult(this.maxspeed);
265  // Steering = Desired minus Velocity
266  let steer = p5.Vector.sub(desired,this.velocity);
267  steer.limit(this.maxforce);  // Limit to maximum steering force
268  return steer;
269}
270
271Boid.prototype.render = function() {
272  // Draw a triangle rotated in the direction of velocity
273  let theta = this.velocity.heading() + radians(90);
274  if (this.boidCounter == boidCounter-1) {
275    fill("#E10600");
276    stroke("#E10600");
277  } else {
278    fill(168, 200);
279    stroke(168, 200);
280  }
281  push();
282  translate(this.position.x, this.position.y);
283  rotate(theta - PI/2);
284  scale(0.02);
285  strokeWeight(0);
286  
287  // Logo Fish
288  beginShape();
289  vertex(0, 554.58227);
290  bezierVertex(0, 539.57642, 12.277536, 527.29883, 27.283415, 527.29883);
291  bezierVertex(137.78125, 527.29883, 237.36554, 570.95227, 311.03076, 641.89916);
292  bezierVertex(350.5917, 588.69652, 376.51114, 524.71648, 381.9678, 453.77966);
293  vertex(154.15112, 452.53317);
294  bezierVertex(139.14525, 452.53317, 126.86771, 440.25564, 126.86771, 425.24979);
295  bezierVertex(126.86771, 410.24393, 139.14525, 397.96639, 154.15112, 397.96639);
296  vertex(410.61548, 397.96639);
297  bezierVertex(582.50098, 397.96639, 744.83722, 464.8106, 866.24841, 586.22177);
298  vertex(941.27783, 661.25118);
299  bezierVertex(946.7345, 666.70785, 949.46289, 673.52902, 949.46289, 680.34982);
300  bezierVertex(949.46289, 687.17062, 946.7345, 693.99142, 941.27783, 699.44808);
301  vertex(866.24841, 774.47749);
302  bezierVertex(744.83722, 896.88866, 582.50098, 963.7329, 410.61548, 963.7329);
303  vertex(154.15112, 963.7329);
304  bezierVertex(139.14525, 963.7329, 126.86771, 951.45536, 126.86771, 936.44951);
305  bezierVertex(126.86771, 921.44365, 139.14525, 909.16611, 154.15112, 909.16611);
306  vertex(381.9678, 909.16611);
307  bezierVertex(376.51114, 839.69397, 350.5917, 774.21393, 311.03076, 721.01129);
308  bezierVertex(237.36554, 791.94812, 137.78125, 835.60156, 27.283415, 835.60156);
309  bezierVertex(12.277536, 835.60156, 0, 823.32398, 0, 808.31811);
310  bezierVertex(0, 793.31226, 12.277536, 781.03467, 27.283415, 781.03467);
311  bezierVertex(124.13953, 781.03467, 211.44664, 742.83795, 275.56265, 680.08612);
312  bezierVertex(211.44664, 617.33429, 124.13953, 579.13757, 27.283415, 579.13757);
313  bezierVertex(12.277536, 581.86597, 0, 569.58814, 0, 554.58227);
314  vertex(883.98248, 682.81421);
315  vertex(828.05182, 626.88355);
316  bezierVertex(723.01068, 521.84241, 585.22925, 460.45465, 436.53464, 454.99799);
317  bezierVertex(431.07794, 540.94074, 398.33786, 620.06238, 347.86352, 682.81421);
318  bezierVertex(398.33786, 745.5661, 431.07794, 824.68817, 436.53464, 910.63092);
319  bezierVertex(583.86505, 903.81006, 723.01068, 843.7865, 828.05182, 738.74476);
320  vertex(883.98248, 682.81421);
321  endShape();
322  
323  pop();
324}
325
326// Wraparound
327Boid.prototype.borders = function() {
328  if (this.position.x < -this.r)  this.position.x = width + this.r;
329  if (this.position.y < -this.r)  this.position.y = height + this.r;
330  if (this.position.x > width + this.r) this.position.x = -this.r;
331  if (this.position.y > height + this.r) this.position.y = -this.r;
332}
333
334// Separation
335// Method checks for nearby boids and steers away
336Boid.prototype.separate = function(boids) {
337  let desiredseparation = 20.0;
338  let steer = createVector(0, 0);
339  let count = 0;
340  // For every boid in the system, check if it's too close
341  for (let i = 0; i < boids.length; i++) {
342    let d = p5.Vector.dist(this.position,boids[i].position);
343    // If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself)
344    if ((d > 0) && (d < desiredseparation)) {
345      // Calculate vector pointing away from neighbor
346      let diff = p5.Vector.sub(this.position, boids[i].position);
347      diff.normalize();
348      diff.div(d);        // Weight by distance
349      steer.add(diff);
350      count++;            // Keep track of how many
351    }
352  }
353  // Average -- divide by how many
354  if (count > 0) {
355    steer.div(count);
356  }
357
358  // As long as the vector is greater than 0
359  if (steer.mag() > 0) {
360    // Implement Reynolds: Steering = Desired - Velocity
361    steer.normalize();
362    steer.mult(this.maxspeed);
363    steer.sub(this.velocity);
364    steer.limit(this.maxforce);
365  }
366  return steer;
367}
368
369// Alignment
370// For every nearby boid in the system, calculate the average velocity
371Boid.prototype.align = function(boids) {
372  let neighbordist = 40;
373  let sum = createVector(0,0);
374  let count = 0;
375  for (let i = 0; i < boids.length; i++) {
376    let d = p5.Vector.dist(this.position,boids[i].position);
377    if ((d > 0) && (d < neighbordist)) {
378      sum.add(boids[i].velocity);
379      count++;
380    }
381  }
382  if (count > 0) {
383    sum.div(count);
384    sum.normalize();
385    sum.mult(this.maxspeed);
386    let steer = p5.Vector.sub(sum, this.velocity);
387    steer.limit(this.maxforce);
388    return steer;
389  } else {
390    return createVector(0, 0);
391  }
392}
393
394// Cohesion
395// For the average location (i.e. center) of all nearby boids, calculate steering vector towards that location
396Boid.prototype.cohesion = function(boids) {
397  let neighbordist = 50;
398  let sum = createVector(0, 0);   // Start with empty vector to accumulate all locations
399  let count = 0;
400  for (let i = 0; i < boids.length; i++) {
401    let d = p5.Vector.dist(this.position,boids[i].position);
402    if ((d > 0) && (d < neighbordist)) {
403      sum.add(boids[i].position); // Add location
404      count++;
405    }
406  }
407  if (count > 0) {
408    sum.div(count);
409    return this.seek(sum);  // Steer towards the location
410  } else {
411    return createVector(0, 0);
412  }
413}

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.