@@ -97,29 +97,34 @@ class Spider { |
| 97 | 97 | let branchStart = Math.min(branch.startX, branch.endX); |
| 98 | 98 | let branchEnd = Math.max(branch.startX, branch.endX); |
| 99 | 99 | |
| 100 | | - if (this.pos.x >= branchStart && this.pos.x <= branchEnd) { |
| 100 | + // Since the branch angle is very small (0.05 radians ≈ 3 degrees), |
| 101 | + // we can use a simpler approximation |
| 102 | + if (this.pos.x >= branchStart - 10 && this.pos.x <= branchEnd + 10) { |
| 101 | 103 | // Calculate position along branch (0 to 1) |
| 102 | 104 | let t = (this.pos.x - branchStart) / (branchEnd - branchStart); |
| 103 | 105 | t = constrain(t, 0, 1); |
| 104 | 106 | |
| 105 | | - // Base thickness tapers from full at start to 35% at end |
| 106 | | - let branchTopThickness = lerp(branch.thickness, branch.thickness * 0.35, t); |
| 107 | + // Branch visual thickness tapers from full at start to 35% at end |
| 108 | + // This matches exactly how it's drawn in the bezier curves |
| 109 | + let branchTopThickness = lerp(branch.thickness * 0.9, branch.thickness * 0.35, t); |
| 107 | 110 | |
| 108 | | - // Account for branch angle |
| 109 | | - let angleOffset = (this.pos.x - branchStart) * Math.tan(branch.angle); |
| 111 | + // The branch is drawn centered at branch.y |
| 112 | + // With small angle approximation: the top of the branch is at |
| 113 | + let branchSurfaceY = branch.y - branchTopThickness; |
| 110 | 114 | |
| 111 | | - // The visual top of the branch (where spider should land) |
| 112 | | - let branchTopY = branch.y - branchTopThickness + angleOffset; |
| 115 | + // Add slight angle correction (for small angles, tan ≈ sin ≈ angle in radians) |
| 116 | + let angleCorrection = (this.pos.x - branchStart) * branch.angle; |
| 117 | + branchSurfaceY += angleCorrection; |
| 113 | 118 | |
| 114 | | - // Only trigger collision if spider is actually above and close to the branch |
| 115 | | - // Previous position check to ensure we're coming from above |
| 119 | + // Check if spider is crossing the branch from above |
| 116 | 120 | let prevY = this.pos.y - this.vel.y; |
| 117 | 121 | |
| 118 | | - if (prevY <= branchTopY && // Was above the branch |
| 119 | | - this.pos.y + this.radius >= branchTopY && // Now at or below surface |
| 120 | | - this.pos.y - this.radius <= branchTopY + branch.thickness) { // But not too far below |
| 122 | + if (prevY <= branchSurfaceY && // Was above |
| 123 | + this.pos.y + this.radius >= branchSurfaceY && // Now at or below |
| 124 | + this.pos.y < branch.y + branch.thickness) { // Not too far below |
| 121 | 125 | |
| 122 | | - this.pos.y = branchTopY - this.radius; |
| 126 | + // Place spider on the branch surface |
| 127 | + this.pos.y = branchSurfaceY - this.radius; |
| 123 | 128 | this.land(); |
| 124 | 129 | } |
| 125 | 130 | } |
@@ -272,9 +277,10 @@ class Fly { |
| 272 | 277 | this.wingPhase = random(TWO_PI); |
| 273 | 278 | this.wanderAngle = random(TWO_PI); |
| 274 | 279 | this.glowIntensity = random(150, 255); |
| 275 | | - this.webTouchCount = 0; |
| 276 | | - this.requiredStrands = 3; |
| 277 | 280 | this.touchedStrands = new Set(); |
| 281 | + this.slowedBy = new Set(); // Track which strands are slowing us |
| 282 | + this.baseSpeed = 3; |
| 283 | + this.currentSpeed = this.baseSpeed; |
| 278 | 284 | } |
| 279 | 285 | |
| 280 | 286 | update() { |
@@ -295,8 +301,9 @@ class Fly { |
| 295 | 301 | wanderForce.mult(0.1); |
| 296 | 302 | this.acc.add(wanderForce); |
| 297 | 303 | |
| 304 | + // Apply current speed (which may be slowed) |
| 298 | 305 | this.vel.add(this.acc); |
| 299 | | - this.vel.limit(3); |
| 306 | + this.vel.limit(this.currentSpeed); |
| 300 | 307 | this.pos.add(this.vel); |
| 301 | 308 | this.acc.mult(0); |
| 302 | 309 | |
@@ -305,34 +312,100 @@ class Fly { |
| 305 | 312 | if (this.pos.y < -30) this.pos.y = height + 30; |
| 306 | 313 | if (this.pos.y > height + 30) this.pos.y = -30; |
| 307 | 314 | |
| 308 | | - this.touchedStrands.clear(); |
| 309 | | - for (let strand of webStrands) { |
| 310 | | - let d = this.pointToLineDistance(this.pos, strand.start, strand.end); |
| 311 | | - if (d < this.radius + 3) { |
| 312 | | - this.touchedStrands.add(strand); |
| 313 | | - } |
| 314 | | - } |
| 315 | + // Check web collisions |
| 316 | + this.checkWebCollisions(); |
| 317 | + } |
| 318 | + |
| 319 | + checkWebCollisions() { |
| 320 | + let currentlyTouching = new Set(); |
| 315 | 321 | |
| 316 | | - if (this.touchedStrands.size >= this.requiredStrands) { |
| 317 | | - this.caught = true; |
| 318 | | - for (let strand of this.touchedStrands) { |
| 319 | | - strand.vibrate(5); |
| 322 | + for (let strand of webStrands) { |
| 323 | + let touching = false; |
| 324 | + |
| 325 | + // Check collision with strand path |
| 326 | + if (strand.path && strand.path.length > 1) { |
| 327 | + for (let i = 0; i < strand.path.length - 1; i++) { |
| 328 | + let p1 = strand.path[i]; |
| 329 | + let p2 = strand.path[i + 1]; |
| 330 | + let d = this.pointToLineDistance(this.pos, p1, p2); |
| 331 | + if (d < this.radius + 3) { |
| 332 | + touching = true; |
| 333 | + break; |
| 334 | + } |
| 335 | + } |
| 336 | + } else if (strand.start && strand.end) { |
| 337 | + // Fallback for strands without path |
| 338 | + let d = this.pointToLineDistance(this.pos, strand.start, strand.end); |
| 339 | + if (d < this.radius + 3) { |
| 340 | + touching = true; |
| 341 | + } |
| 320 | 342 | } |
| 321 | | - for (let strand of webStrands) { |
| 343 | + |
| 344 | + if (touching) { |
| 345 | + currentlyTouching.add(strand); |
| 346 | + |
| 347 | + // If this is a new strand we're touching |
| 322 | 348 | if (!this.touchedStrands.has(strand)) { |
| 323 | | - for (let touched of this.touchedStrands) { |
| 324 | | - let d1 = dist(strand.start.x, strand.start.y, touched.start.x, touched.start.y); |
| 325 | | - let d2 = dist(strand.start.x, strand.start.y, touched.end.x, touched.end.y); |
| 326 | | - let d3 = dist(strand.end.x, strand.end.y, touched.start.x, touched.start.y); |
| 327 | | - let d4 = dist(strand.end.x, strand.end.y, touched.end.x, touched.end.y); |
| 328 | | - if (min(d1, d2, d3, d4) < 50) { |
| 329 | | - strand.vibrate(2); |
| 330 | | - break; |
| 349 | + this.touchedStrands.add(strand); |
| 350 | + |
| 351 | + // Vibrate the web when first touching |
| 352 | + strand.vibrate(3); |
| 353 | + |
| 354 | + // First strand slows us down |
| 355 | + if (this.touchedStrands.size === 1) { |
| 356 | + this.currentSpeed = this.baseSpeed * 0.4; // Slow to 40% speed |
| 357 | + this.slowedBy.add(strand); |
| 358 | + |
| 359 | + // Visual feedback - yellow particles for slowing |
| 360 | + for (let j = 0; j < 3; j++) { |
| 361 | + let p = new Particle(this.pos.x, this.pos.y); |
| 362 | + p.color = color(255, 255, 0, 150); |
| 363 | + p.vel = createVector(random(-1, 1), random(-1, 1)); |
| 364 | + p.size = 3; |
| 365 | + particles.push(p); |
| 366 | + } |
| 367 | + } |
| 368 | + // Second strand catches us |
| 369 | + else if (this.touchedStrands.size >= 2 && !this.caught) { |
| 370 | + this.caught = true; |
| 371 | + this.currentSpeed = 0; |
| 372 | + |
| 373 | + // Stronger vibration when caught |
| 374 | + strand.vibrate(8); |
| 375 | + |
| 376 | + // Also vibrate nearby strands |
| 377 | + for (let otherStrand of webStrands) { |
| 378 | + if (otherStrand !== strand) { |
| 379 | + for (let touchedStrand of this.touchedStrands) { |
| 380 | + let d1 = dist(otherStrand.start.x, otherStrand.start.y, touchedStrand.start.x, touchedStrand.start.y); |
| 381 | + let d2 = dist(otherStrand.start.x, otherStrand.start.y, touchedStrand.end.x, touchedStrand.end.y); |
| 382 | + let d3 = dist(otherStrand.end.x, otherStrand.end.y, touchedStrand.start.x, touchedStrand.start.y); |
| 383 | + let d4 = dist(otherStrand.end.x, otherStrand.end.y, touchedStrand.end.x, touchedStrand.end.y); |
| 384 | + if (min(d1, d2, d3, d4) < 50) { |
| 385 | + otherStrand.vibrate(2); |
| 386 | + break; |
| 387 | + } |
| 388 | + } |
| 389 | + } |
| 390 | + } |
| 391 | + |
| 392 | + // Create caught particles |
| 393 | + for (let j = 0; j < 6; j++) { |
| 394 | + let p = new Particle(this.pos.x, this.pos.y); |
| 395 | + p.color = color(255, 200, 0, 200); |
| 396 | + p.vel = createVector(random(-2, 2), random(-2, 2)); |
| 397 | + particles.push(p); |
| 331 | 398 | } |
| 332 | 399 | } |
| 333 | 400 | } |
| 334 | 401 | } |
| 335 | 402 | } |
| 403 | + |
| 404 | + // If we're no longer touching strands we were slowed by, speed back up |
| 405 | + if (this.slowedBy.size > 0 && currentlyTouching.size === 0) { |
| 406 | + this.currentSpeed = this.baseSpeed; |
| 407 | + this.slowedBy.clear(); |
| 408 | + } |
| 336 | 409 | } |
| 337 | 410 | |
| 338 | 411 | pointToLineDistance(point, lineStart, lineEnd) { |
@@ -351,7 +424,8 @@ class Fly { |
| 351 | 424 | push(); |
| 352 | 425 | translate(this.pos.x, this.pos.y); |
| 353 | 426 | |
| 354 | | - if (this.touchedStrands.size > 0 && !this.caught) { |
| 427 | + // Show slowdown effect |
| 428 | + if (this.slowedBy.size > 0 && !this.caught) { |
| 355 | 429 | stroke(255, 255, 0, 100); |
| 356 | 430 | strokeWeight(1); |
| 357 | 431 | noFill(); |
@@ -373,6 +447,9 @@ class Fly { |
| 373 | 447 | |
| 374 | 448 | if (!this.stuck) { |
| 375 | 449 | this.wingPhase += 0.5; |
| 450 | + // Wing animation slows down when slowed |
| 451 | + let wingSpeed = this.slowedBy.size > 0 ? 0.25 : 0.5; |
| 452 | + this.wingPhase += wingSpeed; |
| 376 | 453 | let wingSpread = sin(this.wingPhase) * 5; |
| 377 | 454 | |
| 378 | 455 | fill(255, 255, 255, 150); |
@@ -545,6 +622,7 @@ class Particle { |
| 545 | 622 | this.vel = createVector(random(-3, 3), random(-5, -2)); |
| 546 | 623 | this.lifetime = 255; |
| 547 | 624 | this.color = color(255, random(200, 255), random(100, 200)); |
| 625 | + this.size = 6; // Default size |
| 548 | 626 | } |
| 549 | 627 | |
| 550 | 628 | update() { |
@@ -557,7 +635,7 @@ class Particle { |
| 557 | 635 | push(); |
| 558 | 636 | noStroke(); |
| 559 | 637 | fill(red(this.color), green(this.color), blue(this.color), this.lifetime); |
| 560 | | - ellipse(this.pos.x, this.pos.y, 6); |
| 638 | + ellipse(this.pos.x, this.pos.y, this.size); |
| 561 | 639 | pop(); |
| 562 | 640 | } |
| 563 | 641 | |