@@ -97,29 +97,34 @@ class Spider { |
| 97 | let branchStart = Math.min(branch.startX, branch.endX); | 97 | let branchStart = Math.min(branch.startX, branch.endX); |
| 98 | let branchEnd = Math.max(branch.startX, branch.endX); | 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 | // Calculate position along branch (0 to 1) | 103 | // Calculate position along branch (0 to 1) |
| 102 | let t = (this.pos.x - branchStart) / (branchEnd - branchStart); | 104 | let t = (this.pos.x - branchStart) / (branchEnd - branchStart); |
| 103 | t = constrain(t, 0, 1); | 105 | t = constrain(t, 0, 1); |
| 104 | | 106 | |
| 105 | - // Base thickness tapers from full at start to 35% at end | 107 | + // Branch visual thickness tapers from full at start to 35% at end |
| 106 | - let branchTopThickness = lerp(branch.thickness, branch.thickness * 0.35, t); | 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 | 111 | + // The branch is drawn centered at branch.y |
| 109 | - let angleOffset = (this.pos.x - branchStart) * Math.tan(branch.angle); | 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) | 115 | + // Add slight angle correction (for small angles, tan ≈ sin ≈ angle in radians) |
| 112 | - let branchTopY = branch.y - branchTopThickness + angleOffset; | 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 | 119 | + // Check if spider is crossing the branch from above |
| 115 | - // Previous position check to ensure we're coming from above | | |
| 116 | let prevY = this.pos.y - this.vel.y; | 120 | let prevY = this.pos.y - this.vel.y; |
| 117 | | 121 | |
| 118 | - if (prevY <= branchTopY && // Was above the branch | 122 | + if (prevY <= branchSurfaceY && // Was above |
| 119 | - this.pos.y + this.radius >= branchTopY && // Now at or below surface | 123 | + this.pos.y + this.radius >= branchSurfaceY && // Now at or below |
| 120 | - this.pos.y - this.radius <= branchTopY + branch.thickness) { // But not too far 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 | this.land(); | 128 | this.land(); |
| 124 | } | 129 | } |
| 125 | } | 130 | } |
@@ -272,9 +277,10 @@ class Fly { |
| 272 | this.wingPhase = random(TWO_PI); | 277 | this.wingPhase = random(TWO_PI); |
| 273 | this.wanderAngle = random(TWO_PI); | 278 | this.wanderAngle = random(TWO_PI); |
| 274 | this.glowIntensity = random(150, 255); | 279 | this.glowIntensity = random(150, 255); |
| 275 | - this.webTouchCount = 0; | | |
| 276 | - this.requiredStrands = 3; | | |
| 277 | this.touchedStrands = new Set(); | 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 | update() { | 286 | update() { |
@@ -295,8 +301,9 @@ class Fly { |
| 295 | wanderForce.mult(0.1); | 301 | wanderForce.mult(0.1); |
| 296 | this.acc.add(wanderForce); | 302 | this.acc.add(wanderForce); |
| 297 | | 303 | |
| | 304 | + // Apply current speed (which may be slowed) |
| 298 | this.vel.add(this.acc); | 305 | this.vel.add(this.acc); |
| 299 | - this.vel.limit(3); | 306 | + this.vel.limit(this.currentSpeed); |
| 300 | this.pos.add(this.vel); | 307 | this.pos.add(this.vel); |
| 301 | this.acc.mult(0); | 308 | this.acc.mult(0); |
| 302 | | 309 | |
@@ -305,34 +312,100 @@ class Fly { |
| 305 | if (this.pos.y < -30) this.pos.y = height + 30; | 312 | if (this.pos.y < -30) this.pos.y = height + 30; |
| 306 | if (this.pos.y > height + 30) this.pos.y = -30; | 313 | if (this.pos.y > height + 30) this.pos.y = -30; |
| 307 | | 314 | |
| 308 | - this.touchedStrands.clear(); | 315 | + // Check web collisions |
| 309 | - for (let strand of webStrands) { | 316 | + this.checkWebCollisions(); |
| 310 | - let d = this.pointToLineDistance(this.pos, strand.start, strand.end); | 317 | + } |
| 311 | - if (d < this.radius + 3) { | 318 | + |
| 312 | - this.touchedStrands.add(strand); | 319 | + checkWebCollisions() { |
| 313 | - } | 320 | + let currentlyTouching = new Set(); |
| 314 | - } | | |
| 315 | | 321 | |
| 316 | - if (this.touchedStrands.size >= this.requiredStrands) { | 322 | + for (let strand of webStrands) { |
| 317 | - this.caught = true; | 323 | + let touching = false; |
| 318 | - for (let strand of this.touchedStrands) { | 324 | + |
| 319 | - strand.vibrate(5); | 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 | if (!this.touchedStrands.has(strand)) { | 348 | if (!this.touchedStrands.has(strand)) { |
| 323 | - for (let touched of this.touchedStrands) { | 349 | + this.touchedStrands.add(strand); |
| 324 | - let d1 = dist(strand.start.x, strand.start.y, touched.start.x, touched.start.y); | 350 | + |
| 325 | - let d2 = dist(strand.start.x, strand.start.y, touched.end.x, touched.end.y); | 351 | + // Vibrate the web when first touching |
| 326 | - let d3 = dist(strand.end.x, strand.end.y, touched.start.x, touched.start.y); | 352 | + strand.vibrate(3); |
| 327 | - let d4 = dist(strand.end.x, strand.end.y, touched.end.x, touched.end.y); | 353 | + |
| 328 | - if (min(d1, d2, d3, d4) < 50) { | 354 | + // First strand slows us down |
| 329 | - strand.vibrate(2); | 355 | + if (this.touchedStrands.size === 1) { |
| 330 | - break; | 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 | pointToLineDistance(point, lineStart, lineEnd) { | 411 | pointToLineDistance(point, lineStart, lineEnd) { |
@@ -351,7 +424,8 @@ class Fly { |
| 351 | push(); | 424 | push(); |
| 352 | translate(this.pos.x, this.pos.y); | 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 | stroke(255, 255, 0, 100); | 429 | stroke(255, 255, 0, 100); |
| 356 | strokeWeight(1); | 430 | strokeWeight(1); |
| 357 | noFill(); | 431 | noFill(); |
@@ -373,6 +447,9 @@ class Fly { |
| 373 | | 447 | |
| 374 | if (!this.stuck) { | 448 | if (!this.stuck) { |
| 375 | this.wingPhase += 0.5; | 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 | let wingSpread = sin(this.wingPhase) * 5; | 453 | let wingSpread = sin(this.wingPhase) * 5; |
| 377 | | 454 | |
| 378 | fill(255, 255, 255, 150); | 455 | fill(255, 255, 255, 150); |
@@ -545,6 +622,7 @@ class Particle { |
| 545 | this.vel = createVector(random(-3, 3), random(-5, -2)); | 622 | this.vel = createVector(random(-3, 3), random(-5, -2)); |
| 546 | this.lifetime = 255; | 623 | this.lifetime = 255; |
| 547 | this.color = color(255, random(200, 255), random(100, 200)); | 624 | this.color = color(255, random(200, 255), random(100, 200)); |
| | 625 | + this.size = 6; // Default size |
| 548 | } | 626 | } |
| 549 | | 627 | |
| 550 | update() { | 628 | update() { |
@@ -557,7 +635,7 @@ class Particle { |
| 557 | push(); | 635 | push(); |
| 558 | noStroke(); | 636 | noStroke(); |
| 559 | fill(red(this.color), green(this.color), blue(this.color), this.lifetime); | 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 | pop(); | 639 | pop(); |
| 562 | } | 640 | } |
| 563 | | 641 | |