@@ -110,7 +110,21 @@ function setup() { |
| 110 | let spiderStartX = homeBranchSide === 'left' ? | 110 | let spiderStartX = homeBranchSide === 'left' ? |
| 111 | homeBranchLength * 0.8 : | 111 | homeBranchLength * 0.8 : |
| 112 | width - homeBranchLength * 0.8; | 112 | width - homeBranchLength * 0.8; |
| 113 | - spider = new Spider(spiderStartX, homeBranchY - 15); | 113 | + |
| | 114 | + // Calculate the visual top of the branch at spider's position |
| | 115 | + let branchStart = Math.min(branchStartX, branchEndX); |
| | 116 | + let branchEnd = Math.max(branchStartX, branchEndX); |
| | 117 | + let t = (spiderStartX - branchStart) / (branchEnd - branchStart); |
| | 118 | + t = constrain(t, 0, 1); |
| | 119 | + |
| | 120 | + // Branch thickness tapers from full to 35% |
| | 121 | + let branchTopThickness = lerp(homeBranchThickness, homeBranchThickness * 0.35, t); |
| | 122 | + |
| | 123 | + // Account for branch angle |
| | 124 | + let angleOffset = (spiderStartX - branchStart) * Math.tan(homeBranchSide === 'left' ? 0.05 : -0.05); |
| | 125 | + |
| | 126 | + // Place spider on top of the visual branch |
| | 127 | + spider = new Spider(spiderStartX, homeBranchY - branchTopThickness + angleOffset); |
| 114 | | 128 | |
| 115 | // Add invisible obstacles along the branch for web anchor points | 129 | // Add invisible obstacles along the branch for web anchor points |
| 116 | let numBranchAnchors = 3; | 130 | let numBranchAnchors = 3; |
@@ -376,113 +390,90 @@ function drawSkyGradient() { |
| 376 | endShape(CLOSE); | 390 | endShape(CLOSE); |
| 377 | pop(); | 391 | pop(); |
| 378 | | 392 | |
| 379 | - // Main branch with organic shape and taper (gnarlier) | 393 | + // Main branch with organic shape and taper |
| 380 | push(); | 394 | push(); |
| 381 | translate(0, branch.y); | 395 | translate(0, branch.y); |
| 382 | rotate(branch.angle); | 396 | rotate(branch.angle); |
| 383 | - | 397 | + |
| 384 | - // Base color varies by phase | | |
| 385 | noStroke(); | 398 | noStroke(); |
| | 399 | + |
| | 400 | + // Base color |
| 386 | if (gamePhase === 'NIGHT') { | 401 | if (gamePhase === 'NIGHT') { |
| 387 | fill(30, 15, 5); | 402 | fill(30, 15, 5); |
| 388 | } else { | 403 | } else { |
| 389 | fill(92, 51, 23); | 404 | fill(92, 51, 23); |
| 390 | } | 405 | } |
| 391 | - | 406 | + |
| 392 | - // Build an irregular, tapered hull using noise to perturb the top/bottom edges | 407 | + // Branch body with taper |
| 393 | - let segs = 24; // more segments for detail | | |
| 394 | - let topPts = []; | | |
| 395 | - let botPts = []; | | |
| 396 | - let len = branch.endX - branch.startX; | | |
| 397 | - for (let i = 0; i <= segs; i++) { | | |
| 398 | - let t = i / segs; | | |
| 399 | - let x = branch.startX + len * t; | | |
| 400 | - // base radius tapers along the branch | | |
| 401 | - let r = lerp(branch.thickness, branch.thickness * 0.35, t); | | |
| 402 | - // add subtle bumpiness using noise keyed by x so it stays stable | | |
| 403 | - let n = noise(x * 0.02, 3.1) - 0.5; // [-0.5, 0.5] | | |
| 404 | - let bump = n * (4 + 6 * (1 - t)); // larger bumps near base | | |
| 405 | - // slight sine undulation so it feels woody | | |
| 406 | - let und = sin(t * TWO_PI * 1.5) * 2 * (1 - t); | | |
| 407 | - let yTop = -(r + bump + und); | | |
| 408 | - let yBot = (r + bump * 0.6 + und * 0.4); | | |
| 409 | - topPts.push({x, y: yTop}); | | |
| 410 | - botPts.push({x, y: yBot}); | | |
| 411 | - } | | |
| 412 | - | | |
| 413 | beginShape(); | 408 | beginShape(); |
| 414 | - // top edge forward | 409 | + vertex(branch.startX, -branch.thickness); |
| 415 | - for (let p of topPts) { | 410 | + bezierVertex( |
| 416 | - vertex(p.x, p.y); | 411 | + branch.startX + (branch.endX - branch.startX) * 0.3, -branch.thickness * 0.9, |
| 417 | - } | 412 | + branch.startX + (branch.endX - branch.startX) * 0.7, -branch.thickness * 0.6, |
| 418 | - // bottom edge back | 413 | + branch.endX, -branch.thickness * 0.35 |
| 419 | - for (let i = botPts.length - 1; i >= 0; i--) { | 414 | + ); |
| 420 | - vertex(botPts[i].x, botPts[i].y); | 415 | + vertex(branch.endX, branch.thickness * 0.35); |
| 421 | - } | 416 | + bezierVertex( |
| | 417 | + branch.startX + (branch.endX - branch.startX) * 0.7, branch.thickness * 0.6, |
| | 418 | + branch.startX + (branch.endX - branch.startX) * 0.3, branch.thickness * 0.9, |
| | 419 | + branch.startX, branch.thickness |
| | 420 | + ); |
| 422 | endShape(CLOSE); | 421 | endShape(CLOSE); |
| 423 | - | 422 | + |
| 424 | - // Add a secondary offshoot (fork) around 60% along the branch | 423 | + // Add a fork around 70% down the branch |
| 425 | - let forkT = 0.6; | | |
| 426 | - let forkX = branch.startX + len * forkT; | | |
| 427 | - let forkLen = min(70, len * 0.18); | | |
| 428 | - let forkAngle = (branch.side === 'right' ? -1 : 1) * (-PI/6 + noise(7.7) * PI/12); | | |
| 429 | - | | |
| 430 | - // draw the fork as a tapered curved limb | | |
| 431 | push(); | 424 | push(); |
| 432 | - translate(forkX, lerp(topPts[Math.floor(segs * forkT)].y, botPts[Math.floor(segs * forkT)].y, 0.15)); | 425 | + let forkX = branch.startX + (branch.endX - branch.startX) * 0.7; |
| 433 | - rotate(forkAngle); | 426 | + let forkY = 0; |
| 434 | - noStroke(); | 427 | + translate(forkX, forkY); |
| | 428 | + rotate((branch.side === 'right' ? -1 : 1) * PI/6); |
| | 429 | + |
| | 430 | + // Fork branch |
| 435 | if (gamePhase === 'NIGHT') { | 431 | if (gamePhase === 'NIGHT') { |
| 436 | fill(35, 18, 6); | 432 | fill(35, 18, 6); |
| 437 | } else { | 433 | } else { |
| 438 | fill(102, 58, 28); | 434 | fill(102, 58, 28); |
| 439 | } | 435 | } |
| | 436 | + |
| 440 | beginShape(); | 437 | beginShape(); |
| 441 | - vertex(0, -6); | 438 | + vertex(0, -8); |
| 442 | - bezierVertex(forkLen * 0.25, -8, forkLen * 0.55, -4, forkLen, 0); | 439 | + bezierVertex(20, -7, 35, -5, 50, -3); |
| 443 | - vertex(forkLen, 3); | 440 | + vertex(50, 3); |
| 444 | - bezierVertex(forkLen * 0.55, 0, forkLen * 0.25, 4, 0, 6); | 441 | + bezierVertex(35, 5, 20, 7, 0, 8); |
| 445 | endShape(CLOSE); | 442 | endShape(CLOSE); |
| 446 | - // tiny side twig on the fork | | |
| 447 | - stroke(gamePhase === 'NIGHT' ? color(40, 20, 0) : color(101, 67, 33)); | | |
| 448 | - strokeWeight(3); | | |
| 449 | - line(forkLen * 0.4, 0, forkLen * 0.4 + 14, -10); | | |
| 450 | pop(); | 443 | pop(); |
| 451 | - | 444 | + |
| 452 | - // Lighter highlights along the crown ridge | 445 | + // Add lighter highlights |
| 453 | if (gamePhase === 'NIGHT') { | 446 | if (gamePhase === 'NIGHT') { |
| 454 | - fill(50, 25, 10, 140); | 447 | + fill(50, 25, 10, 150); |
| 455 | } else { | 448 | } else { |
| 456 | - fill(139, 90, 43, 160); | 449 | + fill(139, 90, 43, 180); |
| 457 | } | 450 | } |
| | 451 | + |
| | 452 | + // Highlight on top ridge |
| 458 | beginShape(); | 453 | beginShape(); |
| 459 | - for (let i = 2; i <= segs - 2; i++) { | 454 | + vertex(branch.startX + 20, -branch.thickness * 0.8); |
| 460 | - let p = topPts[i]; | 455 | + bezierVertex( |
| 461 | - vertex(p.x, p.y + 3); | 456 | + branch.startX + (branch.endX - branch.startX) * 0.4, -branch.thickness * 0.7, |
| 462 | - } | 457 | + branch.startX + (branch.endX - branch.startX) * 0.6, -branch.thickness * 0.5, |
| 463 | - for (let i = segs - 2; i >= 2; i--) { | 458 | + branch.endX - 20, -branch.thickness * 0.25 |
| 464 | - let p = topPts[i]; | 459 | + ); |
| 465 | - vertex(p.x, p.y + 7); | 460 | + vertex(branch.endX - 20, -branch.thickness * 0.15); |
| 466 | - } | 461 | + bezierVertex( |
| | 462 | + branch.startX + (branch.endX - branch.startX) * 0.6, -branch.thickness * 0.4, |
| | 463 | + branch.startX + (branch.endX - branch.startX) * 0.4, -branch.thickness * 0.6, |
| | 464 | + branch.startX + 20, -branch.thickness * 0.7 |
| | 465 | + ); |
| 467 | endShape(CLOSE); | 466 | endShape(CLOSE); |
| 468 | - | 467 | + |
| 469 | - // Bark grooves: short diagonal strokes with slight randomness | 468 | + // Bark texture lines |
| 470 | - stroke(60, 30, 10, 110); | 469 | + stroke(60, 30, 10, 100); |
| 471 | strokeWeight(1); | 470 | strokeWeight(1); |
| 472 | - for (let i = 0; i < branch.barkTextures.length; i++) { | 471 | + for (let texture of branch.barkTextures) { |
| 473 | - let bx = branch.barkTextures[i].x; | 472 | + if (texture.x % 20 < 10) { |
| 474 | - // only draw inside the branch span | 473 | + line(texture.x, texture.yOff, texture.x + 3, texture.endYOff); |
| 475 | - if (bx < min(branch.startX, branch.endX) || bx > max(branch.startX, branch.endX)) continue; | | |
| 476 | - let t = (bx - branch.startX) / (len || 1); | | |
| 477 | - let r = lerp(branch.thickness, branch.thickness * 0.35, t); | | |
| 478 | - let ny = (noise(bx * 0.03, 9.2) - 0.5) * 10; | | |
| 479 | - let y = ny; | | |
| 480 | - line(bx - 3, y - r * 0.2, bx + 4, y + r * 0.25); | | |
| 481 | - if (i % 3 === 0) { | | |
| 482 | - line(bx - 5, y + 1, bx + 9, y + 3); | | |
| 483 | } | 474 | } |
| 484 | } | 475 | } |
| 485 | - | 476 | + |
| 486 | // Knots | 477 | // Knots |
| 487 | noStroke(); | 478 | noStroke(); |
| 488 | if (gamePhase === 'NIGHT') { | 479 | if (gamePhase === 'NIGHT') { |
@@ -490,39 +481,38 @@ function drawSkyGradient() { |
| 490 | } else { | 481 | } else { |
| 491 | fill(80, 40, 15); | 482 | fill(80, 40, 15); |
| 492 | } | 483 | } |
| 493 | - ellipse(branch.startX + len * 0.28, -2, 14, 11); | 484 | + ellipse(branch.startX + (branch.endX - branch.startX) * 0.3, -5, 12, 8); |
| 494 | - ellipse(branch.startX + len * 0.73, 3, 11, 9); | 485 | + ellipse(branch.startX + (branch.endX - branch.startX) * 0.65, 3, 8, 10); |
| 495 | - | 486 | + |
| 496 | pop(); | 487 | pop(); |
| 497 | | 488 | |
| 498 | - // Small twigs with organic angles | 489 | + // Small twigs |
| 499 | stroke(gamePhase === 'NIGHT' ? color(40, 20, 0) : color(101, 67, 33)); | 490 | stroke(gamePhase === 'NIGHT' ? color(40, 20, 0) : color(101, 67, 33)); |
| 500 | for (let twig of branch.twigs) { | 491 | for (let twig of branch.twigs) { |
| 501 | push(); | 492 | push(); |
| 502 | - translate(twig.x, 0); | 493 | + translate(twig.x, branch.y); |
| 503 | - | 494 | + rotate(twig.angle); |
| 504 | - // Make twigs thicker at base, drawn as a gentle curve | 495 | + |
| 505 | - strokeWeight(4); | 496 | + // Main twig |
| 506 | - bezier(0, 0, twig.length * 0.18, twig.length * 0.05, twig.length * 0.42, twig.length * 0.12, twig.length * 0.62, twig.length * 0.16); | | |
| 507 | strokeWeight(3); | 497 | strokeWeight(3); |
| 508 | - bezier(twig.length * 0.62, twig.length * 0.16, twig.length * 0.74, twig.length * 0.18, twig.length * 0.86, twig.length * 0.2, twig.length, twig.length * 0.22); | 498 | + line(0, 0, twig.length, 0); |
| 509 | - strokeWeight(2); | 499 | + |
| 510 | - line(twig.length * 0.82, twig.length * 0.2, twig.length * 1.05, twig.length * 0.28); | 500 | + // Sub twigs |
| 511 | - | | |
| 512 | - // Tiny sub-twigs | | |
| 513 | strokeWeight(1); | 501 | strokeWeight(1); |
| 514 | for (let subTwig of twig.subTwigs) { | 502 | for (let subTwig of twig.subTwigs) { |
| 515 | - line(twig.length * subTwig.pos, twig.length * 0.15, | 503 | + push(); |
| 516 | - twig.length * subTwig.pos + subTwig.length, | 504 | + translate(twig.length * subTwig.pos, 0); |
| 517 | - twig.length * 0.15 + subTwig.angle); | 505 | + rotate(subTwig.angle * 0.1); |
| | 506 | + line(0, 0, subTwig.length, -subTwig.angle); |
| | 507 | + pop(); |
| 518 | } | 508 | } |
| 519 | pop(); | 509 | pop(); |
| 520 | } | 510 | } |
| 521 | | 511 | |
| 522 | - // Add leaves with more natural placement | 512 | + // Add leaves |
| 523 | for (let leaf of branch.leaves) { | 513 | for (let leaf of branch.leaves) { |
| 524 | push(); | 514 | push(); |
| 525 | - translate(leaf.x, leaf.yOffset); | 515 | + translate(leaf.x, branch.y + leaf.yOffset); |
| 526 | rotate(leaf.rotation); | 516 | rotate(leaf.rotation); |
| 527 | | 517 | |
| 528 | // Leaf shadow | 518 | // Leaf shadow |
@@ -546,7 +536,6 @@ function drawSkyGradient() { |
| 546 | } | 536 | } |
| 547 | | 537 | |
| 548 | pop(); | 538 | pop(); |
| 549 | - pop(); | | |
| 550 | } | 539 | } |
| 551 | } | 540 | } |
| 552 | | 541 | |