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.