From 59a5d0606eeb3c198f8eb02d49e835cc03863b3c Mon Sep 17 00:00:00 2001 From: Brian Fertig Date: Sun, 26 Jul 2026 19:26:25 -0600 Subject: [PATCH] refactor(gootower): draw strands as pinched goo shapes instead of lines Replace simple line-based strand rendering with filled polygon shapes that are thick at the ends and pinched thin in the middle, with curved edges and perpendicular sag for a drooping arc effect. Strands now connect smoothly to balls with tangent-aligned edges. --- src/games/gootower/GooTowerGame.js | 60 ++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/src/games/gootower/GooTowerGame.js b/src/games/gootower/GooTowerGame.js index 647bc50..41f5be2 100644 --- a/src/games/gootower/GooTowerGame.js +++ b/src/games/gootower/GooTowerGame.js @@ -654,7 +654,8 @@ export default class GooTowerGame extends Phaser.Scene { this.drawGears(g); - // Strands first, under the balls. + // Strands first, under the balls — drawn as pinched goo shapes (thick + // at the ends, thin in the middle with curved edges). for (const s of st.strands) { if (s.broken) continue; const a = st.balls[s.a]; @@ -662,9 +663,60 @@ export default class GooTowerGame extends Phaser.Scene { if (!a || !b || a.dead || b.dead) continue; const stress = strandStress(s); const color = lerpColor(STRAND, STRAND_HOT, stress); - const width = 7 - 3 * stress; // strands thin as they are pulled taut - g.lineStyle(width, color, 1); - g.lineBetween(a.x, a.y, b.x, b.y); + + const dx = b.x - a.x; + const dy = b.y - a.y; + const len = Math.hypot(dx, dy); + if (len < 1) continue; + + // Unit direction along the strand and perpendicular to it. + const ux = dx / len, uy = dy / len; + const nx = -uy, ny = ux; + + const baseW = 7 - 3 * stress; + const endW = baseW; + const midW = Math.max(1, baseW * 0.2); + // Perpendicular sag of the centerline at the midpoint — gives the strand + // a gentle drooping arc. + const sagAmt = baseW * 0.5; + + // Width profile: endW at both endpoints, pinched to midW at the center. + // Zero derivative at t=0 and t=1 so the strand meets each ball tangentially. + // w(t) = midW + (endW - midW) * (1 - 2·t·(1-t)) + // Sag profile: zero at endpoints, sagAmt at center. + // s(t) = 4·sagAmt·t·(1-t) + // Both are quadratics with zero derivative at the endpoints, giving + // perfectly smooth (tangent-aligned) connections into the balls. + // The second derivative of the edge offset is negative everywhere, + // so the edges curve inward — a concave "pinched" look. + + const samples = Math.max(8, Math.min(20, Math.round(len / 15))); + const verts = []; + + for (let i = 0; i <= samples; i++) { + const t = i / samples; + const tp = t * (1 - t); // peaks at 0.25 when t = 0.5 + const w = midW + (endW - midW) * (1 - 2 * tp); + const sg = 4 * sagAmt * tp; + // Center point (straight line + perpendicular sag) + const cx = a.x + t * dx + nx * sg; + const cy = a.y + t * dy + ny * sg; + // Upper edge + verts.push({ x: cx + nx * (w / 2), y: cy + ny * (w / 2) }); + } + // Bottom edge, walking back from end → start. + for (let i = samples; i >= 0; i--) { + const t = i / samples; + const tp = t * (1 - t); + const w = midW + (endW - midW) * (1 - 2 * tp); + const sg = 4 * sagAmt * tp; + const cx = a.x + t * dx + nx * sg; + const cy = a.y + t * dy + ny * sg; + verts.push({ x: cx - nx * (w / 2), y: cy - ny * (w / 2) }); + } + + g.fillStyle(color, 1); + g.fillPoints(verts, true); } // Balls.