Upload index.html
Browse files- index.html +55 -5
index.html
CHANGED
|
@@ -167,6 +167,30 @@ class Fish {
|
|
| 167 |
desired = lerpAngle(desired, Math.atan2(H * 0.45 - this.y, W / 2 - this.x), fp * 1.6);
|
| 168 |
}
|
| 169 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
// Limit turn rate so direction changes are smooth and fish-like
|
| 171 |
let d = desired - this.angle;
|
| 172 |
while (d > Math.PI) d -= 2 * Math.PI;
|
|
@@ -220,14 +244,15 @@ class Fish {
|
|
| 220 |
// tail fin follows the body wave at its base, amplified (same phase β no fighting)
|
| 221 |
const sway = Math.sin(-s * waveK + this.phase) * s * 0.34;
|
| 222 |
|
| 223 |
-
// tail fin
|
|
|
|
| 224 |
c.fillStyle = `hsla(${this.hue},75%,52%,0.92)`;
|
| 225 |
c.beginPath();
|
| 226 |
c.moveTo(-s * 0.5, 0);
|
| 227 |
-
c.quadraticCurveTo(-s * 1.
|
| 228 |
-
c.
|
| 229 |
-
c.
|
| 230 |
-
c.quadraticCurveTo(-s * 1.
|
| 231 |
c.fill();
|
| 232 |
|
| 233 |
// body β undulates while swimming: the ellipse flexes along a traveling wave (v3)
|
|
@@ -865,6 +890,31 @@ function frame(now) {
|
|
| 865 |
updateGrass(t, dt);
|
| 866 |
|
| 867 |
for (const f of fishes) f.update(dt);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 868 |
fishes.sort((a, b) => a.size - b.size); // small fish swim behind
|
| 869 |
for (const f of fishes) f.draw(ctx);
|
| 870 |
|
|
|
|
| 167 |
desired = lerpAngle(desired, Math.atan2(H * 0.45 - this.y, W / 2 - this.x), fp * 1.6);
|
| 168 |
}
|
| 169 |
|
| 170 |
+
// v7: separation steering β bend around nearby fish instead of ploughing into them.
|
| 171 |
+
// Only kicks in while actually swimming, so resting fish don't twitch at each other;
|
| 172 |
+
// the faster we swim, the earlier and harder we steer away.
|
| 173 |
+
const mySpeed = this.speed * this.speedFactor;
|
| 174 |
+
if (mySpeed > 0.3) {
|
| 175 |
+
let ax = 0, ay = 0, wsum = 0;
|
| 176 |
+
for (const o of fishes) {
|
| 177 |
+
if (o === this) continue;
|
| 178 |
+
const dx = o.x - this.x, dy = o.y - this.y;
|
| 179 |
+
const d2 = dx * dx + dy * dy;
|
| 180 |
+
const zone = (this.size + o.size) * 1.7; // notice radius scales with both fish
|
| 181 |
+
if (d2 < zone * zone && d2 > 0.01) {
|
| 182 |
+
const dd = Math.sqrt(d2);
|
| 183 |
+
const w = (1 - dd / zone) * Math.min(1, mySpeed); // close & fast β strong
|
| 184 |
+
ax -= (dx / dd) * w;
|
| 185 |
+
ay -= (dy / dd) * w;
|
| 186 |
+
wsum += w;
|
| 187 |
+
}
|
| 188 |
+
}
|
| 189 |
+
if (wsum > 0) {
|
| 190 |
+
desired = lerpAngle(desired, Math.atan2(ay, ax), Math.min(0.85, wsum * 0.45));
|
| 191 |
+
}
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
// Limit turn rate so direction changes are smooth and fish-like
|
| 195 |
let d = desired - this.angle;
|
| 196 |
while (d > Math.PI) d -= 2 * Math.PI;
|
|
|
|
| 244 |
// tail fin follows the body wave at its base, amplified (same phase β no fighting)
|
| 245 |
const sway = Math.sin(-s * waveK + this.phase) * s * 0.34;
|
| 246 |
|
| 247 |
+
// tail fin (v7): rounded lobes instead of two pointed spikes β each tip is swept
|
| 248 |
+
// around by a curve whose control point sits just past where the old spike was
|
| 249 |
c.fillStyle = `hsla(${this.hue},75%,52%,0.92)`;
|
| 250 |
c.beginPath();
|
| 251 |
c.moveTo(-s * 0.5, 0);
|
| 252 |
+
c.quadraticCurveTo(-s * 1.04, -s * 0.36 + sway, -s * 1.22, -s * 0.44 + sway); // upper edge out toward the tip
|
| 253 |
+
c.quadraticCurveTo(-s * 1.34, -s * 0.52 + sway, -s * 0.86, sway * 0.5); // rounded upper lobe β notch
|
| 254 |
+
c.quadraticCurveTo(-s * 1.34, s * 0.52 + sway, -s * 1.22, s * 0.44 + sway); // notch β rounded lower lobe
|
| 255 |
+
c.quadraticCurveTo(-s * 1.04, s * 0.36 + sway, -s * 0.5, 0); // lower edge back to the base
|
| 256 |
c.fill();
|
| 257 |
|
| 258 |
// body β undulates while swimming: the ellipse flexes along a traveling wave (v3)
|
|
|
|
| 890 |
updateGrass(t, dt);
|
| 891 |
|
| 892 |
for (const f of fishes) f.update(dt);
|
| 893 |
+
|
| 894 |
+
// v7: fish gently bump each other β overlapping pairs are eased apart along their
|
| 895 |
+
// center line (weighted by size), so they nudge instead of stacking on top of each other
|
| 896 |
+
for (let i = 0; i < fishes.length; i++) {
|
| 897 |
+
const a = fishes[i];
|
| 898 |
+
for (let j = i + 1; j < fishes.length; j++) {
|
| 899 |
+
const b = fishes[j];
|
| 900 |
+
let dx = b.x - a.x, dy = b.y - a.y;
|
| 901 |
+
const minD = (a.size + b.size) * 0.62; // gentle: smaller than their visual length
|
| 902 |
+
const d2 = dx * dx + dy * dy;
|
| 903 |
+
if (d2 <= 0.01) { // exactly overlapping β nudge apart randomly
|
| 904 |
+
a.x -= rand(0.5, 1.5); a.y -= rand(0.5, 1.5);
|
| 905 |
+
b.x += rand(0.5, 1.5); b.y += rand(0.5, 1.5);
|
| 906 |
+
} else if (d2 < minD * minD) {
|
| 907 |
+
const dd = Math.sqrt(d2);
|
| 908 |
+
const overlap = (minD - dd) / minD; // 0..1 how deep they are in each other
|
| 909 |
+
const push = overlap * 24 * dt; // soft: only a few px/s even at full overlap
|
| 910 |
+
dx /= dd; dy /= dd;
|
| 911 |
+
const ma = b.size / (a.size + b.size), mb = a.size / (a.size + b.size);
|
| 912 |
+
a.x -= dx * push * 2 * ma; a.y -= dy * push * 2 * ma;
|
| 913 |
+
b.x += dx * push * 2 * mb; b.y += dy * push * 2 * mb;
|
| 914 |
+
}
|
| 915 |
+
}
|
| 916 |
+
}
|
| 917 |
+
|
| 918 |
fishes.sort((a, b) => a.size - b.size); // small fish swim behind
|
| 919 |
for (const f of fishes) f.draw(ctx);
|
| 920 |
|