One commited on
Commit
f5f6791
Β·
verified Β·
1 Parent(s): 8ed9966

Upload index.html

Browse files
Files changed (1) hide show
  1. index.html +87 -45
index.html CHANGED
@@ -645,22 +645,6 @@ class Fish {
645
  c.closePath();
646
  c.fill();
647
 
648
- // pectoral fin (flapping)
649
- const flap = Math.sin(this.phase * 1.3) * 0.45;
650
- c.save();
651
- c.translate(s * 0.05, s * 0.12);
652
- c.rotate(0.7 + flap);
653
- // v17: pectoral fin matches the body color (no more blue arms on silver fish), but
654
- // gets a thin darker outline so it stays visible sitting on top of the same-colored body
655
- c.fillStyle = this.pevFillStyle;
656
- c.beginPath();
657
- c.ellipse(0, 0, s * 0.3, s * 0.14, 0, 0, Math.PI * 2);
658
- c.fill();
659
- c.strokeStyle = this.pevStrokeStyle;
660
- c.lineWidth = Math.max(1, s * 0.03);
661
- c.stroke();
662
- c.restore();
663
-
664
  // eye
665
  c.fillStyle = "#fff";
666
  c.beginPath();
@@ -709,37 +693,71 @@ class Fish {
709
 
710
  c.restore();
711
 
712
- // v7: overhead light streak β€” a soft line of light on whichever edge faces up,
713
- // computed in screen space so it stays on top no matter how the fish turns.
714
  {
715
  const ca = Math.cos(this.angle), sa = Math.sin(this.angle);
716
- const N = 40;
717
- const pts = [];
718
- for (let i = 0; i < N; i++) {
719
- const th = (i / N) * Math.PI * 2;
720
- const lx = s * Math.cos(th); // along heading
721
- const ly = -s * 0.42 * Math.sin(th); // across body (local up)
722
- pts.push([this.x + lx*ca - ly*sa, this.y + lx*sa + ly*ca]);
723
- }
724
- let ti = 0; for (let i = 1; i < N; i++) if (pts[i][1] < pts[ti][1]) ti = i;
725
- const arc = [];
726
- for (let k = -8; k <= 8; k++) {
727
- const idx = ((ti + k) % N + N) % N;
728
- arc.push(pts[idx]);
729
- }
730
  const a = 0.35 * (1 - curNight * 0.65); // dimmer at night, still present
731
  if (a > 0.02) {
732
  c.save();
733
- c.lineCap = "round"; c.lineJoin = "round";
734
- c.strokeStyle = `rgba(255,255,255,${(a * 0.4).toFixed(3)})`;
735
- c.lineWidth = s * 0.16;
736
- strokeArc(c, arc);
737
- c.strokeStyle = `rgba(255,255,255,${a.toFixed(3)})`;
738
- c.lineWidth = Math.max(1, s * 0.05);
739
- strokeArc(c, arc);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
740
  c.restore();
741
  }
742
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
743
  }
744
  }
745
 
@@ -857,6 +875,10 @@ function buildScene() {
857
  // so fish push the stalk where it actually looks. Weighted by f β†’ root stays planted.
858
  cA1: rand(5, 12), cF1: rand(0.9, 1.8), cP1: rand(0, Math.PI * 2),
859
  cA2: rand(2, 6), cF2: rand(2.2, 3.6), cP2: rand(0, Math.PI * 2),
 
 
 
 
860
  tipW: rand(1.4, 2.6), // v15: blade tapers from width β†’ tipW up the stipe
861
  // PERF: stroke styles precomputed once (hue is fixed per stalk) β€” drawSeaweed
862
  // used to rebuild both template strings every frame for every stalk.
@@ -922,7 +944,11 @@ function buildScene() {
922
  cA1: rand(-5, 5), // primary bend amplitude
923
  cF1: rand(2.2, 4.5), // frequency along blade length
924
  cP1: rand(0, Math.PI * 2), // phase offset
925
- segs: 3 + ((Math.random() * 2) | 0) // 3-4 segments for tapered outline
 
 
 
 
926
  });
927
  }
928
  }
@@ -951,7 +977,11 @@ function buildScene() {
951
  cA1: rand(-8, 8), // primary bend amplitude (larger for foreground)
952
  cF1: rand(2, 4), // frequency along blade length
953
  cP1: rand(0, Math.PI * 2), // phase offset
954
- segs: 4 + ((Math.random() * 2) | 0) // 4-5 segments for tapered outline
 
 
 
 
955
  });
956
  }
957
  }
@@ -967,9 +997,12 @@ function buildScene() {
967
  }
968
 
969
  for (let i = 0; i < 5; i++) {
 
 
 
970
  rays.push({
971
  x: rand(0, W), w: rand(80, 210), tilt: rand(-0.35, 0.35),
972
- speed: rand(0.06, 0.14) * (Math.random() < 0.5 ? 1 : -1), alpha: rand(0.04, 0.09),
973
  // undulation β€” two traveling waves so each beam shimmers like light refracted through moving water
974
  uAmp: rand(18, 46), uLen: rand(2.2, 4.5), uSpd: rand(0.35, 0.9) * (Math.random() < 0.5 ? 1 : -1), uPh: rand(0, Math.PI * 2),
975
  vAmp: rand(6, 14), vLen: rand(5, 9), vSpd: rand(0.8, 1.6) * (Math.random() < 0.5 ? 1 : -1), vPh: rand(0, Math.PI * 2)
@@ -1079,6 +1112,8 @@ function drawRays(t, light) {
1079
  const [rr, rg, rb] = light.ray;
1080
  const segs = RAY_SEGS;
1081
  for (const r of rays) {
 
 
1082
  // slow horizontal drift of the whole beam (kept from v1)
1083
  const off = Math.sin(t * r.speed * 6 + r.x) * 55;
1084
  const x0 = r.x + off;
@@ -1130,6 +1165,7 @@ function drawRays(t, light) {
1130
  RC[i][0] = cxm + coreHalf; RC[i][1] = ry2;
1131
  }
1132
  band(LC, RC, r.alpha * 1.5, 0.65, 0);
 
1133
  }
1134
  }
1135
 
@@ -1238,7 +1274,9 @@ function updatePlants(t, dt) {
1238
  // normal sway + a fast extra flinch while being pushed
1239
  const sway = Math.sin(t * w.speed + w.phase + f * 2.6) * f * f * 24
1240
  + Math.sin(t * 7 + w.phase) * w.touch * f * 16;
1241
- const px = w.x + (w.off[i] || 0) + stalkCurveX(w, f) + sway;
 
 
1242
  const py = baseY - w.h * f;
1243
  w.points.push({ x: px, y: py });
1244
  plantPoints.push({ x: px, y: py, r: w.width * 0.55, s: w });
@@ -1383,8 +1421,10 @@ function drawGrass(t) {
1383
  const curveX = f * g.cA1 * Math.sin(f * g.cF1 + g.cP1);
1384
  // constant sway, amplified while rippling from a passing fish
1385
  const sway = Math.sin(t * g.speed + g.phase + f * 2.4) * amp * f;
 
 
1386
  pts.push({
1387
- x: g.x + g.lean * g.h * f + curveX + sway,
1388
  y: g.y - g.h * f * (1 - g.ripple * 0.06)
1389
  });
1390
  }
@@ -1441,8 +1481,10 @@ function drawFgGrass(t) {
1441
  const curveX = f * g.cA1 * Math.sin(f * g.cF1 + g.cP1);
1442
  // constant sway, weighted toward the tip
1443
  const sway = Math.sin(t * g.speed + g.phase + f * 2.4) * amp * f;
 
 
1444
  pts.push({
1445
- x: g.x + g.lean * g.h * f + curveX + sway,
1446
  y: g.y - g.h * f
1447
  });
1448
  }
 
645
  c.closePath();
646
  c.fill();
647
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
648
  // eye
649
  c.fillStyle = "#fff";
650
  c.beginPath();
 
693
 
694
  c.restore();
695
 
696
+ // v17: overhead light β€” a soft elliptical highlight matching the fish body shape,
697
+ // positioned on the upper edge and rotated with the fish's angle.
698
  {
699
  const ca = Math.cos(this.angle), sa = Math.sin(this.angle);
700
+ // Find which side faces up (screen space)
701
+ const upY = -sa; // screen-space y-component of the fish's local "up" direction
702
+ // Smooth transition instead of hard switch to prevent flickering/snapping
703
+ const sideUp = -upY / (Math.abs(upY) + 0.15);
704
+
 
 
 
 
 
 
 
 
 
705
  const a = 0.35 * (1 - curNight * 0.65); // dimmer at night, still present
706
  if (a > 0.02) {
707
  c.save();
708
+ // Position the ellipse on the upper edge of the fish body
709
+ const offsetX = sideUp * s * 0.18; // offset along the fish's local up axis
710
+ const lightX = this.x + offsetX * ca;
711
+ const lightY = this.y + offsetX * sa;
712
+
713
+ // Draw two concentric ellipses matching the fish body proportions
714
+ c.translate(lightX, lightY);
715
+ c.rotate(this.angle);
716
+
717
+ // Outer soft glow β€” larger and more transparent
718
+ c.fillStyle = `rgba(255,255,255,${(a * 0.3).toFixed(3)})`;
719
+ c.beginPath();
720
+ c.ellipse(0, 0, s * 0.65, s * 0.25, 0, 0, Math.PI * 2);
721
+ c.fill();
722
+
723
+ // Inner bright core β€” smaller and more opaque
724
+ c.fillStyle = `rgba(255,255,255,${a.toFixed(3)})`;
725
+ c.beginPath();
726
+ c.ellipse(0, 0, s * 0.35, s * 0.1, 0, 0, Math.PI * 2);
727
+ c.fill();
728
+
729
  c.restore();
730
  }
731
  }
732
+ // v19: pectoral fins drawn OVER the reflections for correct layering
733
+ {
734
+ c.save();
735
+ c.translate(this.x, this.y);
736
+ c.rotate(this.angle);
737
+ if (Math.abs(this.roll) > 0.01) {
738
+ c.scale(1, Math.max(0.42, Math.cos(this.roll)));
739
+ c.rotate(this.roll * 0.1);
740
+ }
741
+
742
+ // pectoral fin (flapping)
743
+ const flap = Math.sin(this.phase * 1.3) * 0.45;
744
+ c.save();
745
+ c.translate(s * 0.05, s * 0.12);
746
+ c.rotate(0.7 + flap);
747
+ // v17: pectoral fin matches the body color (no more blue arms on silver fish), but
748
+ // gets a thin darker outline so it stays visible sitting on top of the same-colored body
749
+ c.fillStyle = this.pevFillStyle;
750
+ c.beginPath();
751
+ c.ellipse(0, 0, s * 0.3, s * 0.14, 0, 0, Math.PI * 2);
752
+ c.fill();
753
+ c.strokeStyle = this.pevStrokeStyle;
754
+ c.lineWidth = Math.max(1, s * 0.03);
755
+ c.stroke();
756
+ c.restore();
757
+
758
+ c.restore();
759
+ }
760
+
761
  }
762
  }
763
 
 
875
  // so fish push the stalk where it actually looks. Weighted by f β†’ root stays planted.
876
  cA1: rand(5, 12), cF1: rand(0.9, 1.8), cP1: rand(0, Math.PI * 2),
877
  cA2: rand(2, 6), cF2: rand(2.2, 3.6), cP2: rand(0, Math.PI * 2),
878
+ // v17: secondary wobble - a finer inner shimmer for unique motion per stalk
879
+ sAmp: rand(2, 5), // amplitude of the secondary wave
880
+ sFreq: rand(3.5, 6.5), // higher frequency than primary sway
881
+ sPh: rand(0, Math.PI * 2), // phase offset
882
  tipW: rand(1.4, 2.6), // v15: blade tapers from width β†’ tipW up the stipe
883
  // PERF: stroke styles precomputed once (hue is fixed per stalk) β€” drawSeaweed
884
  // used to rebuild both template strings every frame for every stalk.
 
944
  cA1: rand(-5, 5), // primary bend amplitude
945
  cF1: rand(2.2, 4.5), // frequency along blade length
946
  cP1: rand(0, Math.PI * 2), // phase offset
947
+ segs: 3 + ((Math.random() * 2) | 0), // 3-4 segments for tapered outline
948
+ // v17: secondary wobble - finer inner shimmer per blade
949
+ sAmp: rand(1.5, 3.5),
950
+ sFreq: rand(4.5, 8),
951
+ sPh: rand(0, Math.PI * 2)
952
  });
953
  }
954
  }
 
977
  cA1: rand(-8, 8), // primary bend amplitude (larger for foreground)
978
  cF1: rand(2, 4), // frequency along blade length
979
  cP1: rand(0, Math.PI * 2), // phase offset
980
+ segs: 4 + ((Math.random() * 2) | 0), // 4-5 segments for tapered outline
981
+ // v17: secondary wobble - finer inner shimmer per blade (foreground)
982
+ sAmp: rand(2, 4.5),
983
+ sFreq: rand(3.8, 6.5),
984
+ sPh: rand(0, Math.PI * 2)
985
  });
986
  }
987
  }
 
997
  }
998
 
999
  for (let i = 0; i < 5; i++) {
1000
+ // random blur state: 0px (as-is), ~3px (semi-blurry), or ~7px (blurry) for visual variety
1001
+ const blurRoll = Math.random();
1002
+ const blur = blurRoll < 0.34 ? 0 : (blurRoll < 0.67 ? 3 : 7);
1003
  rays.push({
1004
  x: rand(0, W), w: rand(80, 210), tilt: rand(-0.35, 0.35),
1005
+ speed: rand(0.06, 0.14) * (Math.random() < 0.5 ? 1 : -1), alpha: rand(0.04, 0.09), blur,
1006
  // undulation β€” two traveling waves so each beam shimmers like light refracted through moving water
1007
  uAmp: rand(18, 46), uLen: rand(2.2, 4.5), uSpd: rand(0.35, 0.9) * (Math.random() < 0.5 ? 1 : -1), uPh: rand(0, Math.PI * 2),
1008
  vAmp: rand(6, 14), vLen: rand(5, 9), vSpd: rand(0.8, 1.6) * (Math.random() < 0.5 ? 1 : -1), vPh: rand(0, Math.PI * 2)
 
1112
  const [rr, rg, rb] = light.ray;
1113
  const segs = RAY_SEGS;
1114
  for (const r of rays) {
1115
+ // apply per-ray blur state: 0px (as-is), 3px (semi-blurry), or 7px (blurry)
1116
+ ctx.filter = r.blur > 0 ? `blur(${r.blur}px)` : 'none';
1117
  // slow horizontal drift of the whole beam (kept from v1)
1118
  const off = Math.sin(t * r.speed * 6 + r.x) * 55;
1119
  const x0 = r.x + off;
 
1165
  RC[i][0] = cxm + coreHalf; RC[i][1] = ry2;
1166
  }
1167
  band(LC, RC, r.alpha * 1.5, 0.65, 0);
1168
+ ctx.filter = 'none'; // reset blur before next ray or other drawing
1169
  }
1170
  }
1171
 
 
1274
  // normal sway + a fast extra flinch while being pushed
1275
  const sway = Math.sin(t * w.speed + w.phase + f * 2.6) * f * f * 24
1276
  + Math.sin(t * 7 + w.phase) * w.touch * f * 16;
1277
+ // v17: secondary wobble - a finer inner movement for unique per-stalk motion
1278
+ const sWobble = Math.sin(t * w.sFreq + w.sPh + f * 4.2) * w.sAmp * f;
1279
+ const px = w.x + (w.off[i] || 0) + stalkCurveX(w, f) + sway + sWobble;
1280
  const py = baseY - w.h * f;
1281
  w.points.push({ x: px, y: py });
1282
  plantPoints.push({ x: px, y: py, r: w.width * 0.55, s: w });
 
1421
  const curveX = f * g.cA1 * Math.sin(f * g.cF1 + g.cP1);
1422
  // constant sway, amplified while rippling from a passing fish
1423
  const sway = Math.sin(t * g.speed + g.phase + f * 2.4) * amp * f;
1424
+ // v17: secondary wobble - finer inner movement for unique per-blade motion
1425
+ const sWobble = Math.sin(t * g.sFreq + g.sPh + f * 3.8) * g.sAmp * f;
1426
  pts.push({
1427
+ x: g.x + g.lean * g.h * f + curveX + sway + sWobble,
1428
  y: g.y - g.h * f * (1 - g.ripple * 0.06)
1429
  });
1430
  }
 
1481
  const curveX = f * g.cA1 * Math.sin(f * g.cF1 + g.cP1);
1482
  // constant sway, weighted toward the tip
1483
  const sway = Math.sin(t * g.speed + g.phase + f * 2.4) * amp * f;
1484
+ // v17: secondary wobble - finer inner movement for unique per-blade motion
1485
+ const sWobble = Math.sin(t * g.sFreq + g.sPh + f * 4.5) * g.sAmp * f;
1486
  pts.push({
1487
+ x: g.x + g.lean * g.h * f + curveX + sway + sWobble,
1488
  y: g.y - g.h * f
1489
  });
1490
  }