Upload index.html
Browse files- index.html +139 -1
index.html
CHANGED
|
@@ -83,7 +83,7 @@ function genKoiPatches(type) {
|
|
| 83 |
}
|
| 84 |
return arr;
|
| 85 |
}
|
| 86 |
-
let fishes = [], bubbles = [], seaweeds = [], backSeaweeds = [], farSeaweeds = [], motes = [], rays = [], rocks = [], grass = [], fgGrass = [];
|
| 87 |
let playerFish = null; // the fish controlled by gamepad (spawned with F key)
|
| 88 |
const GAMEPAD_DEADZONE = 0.15; // ignore small stick movements
|
| 89 |
let curNight = 0; // v7: shared night factor (dims the top-light + sheen)
|
|
@@ -894,6 +894,7 @@ function buildScene() {
|
|
| 894 |
bubbles = [];
|
| 895 |
seaweeds = [];
|
| 896 |
backSeaweeds = []; // v4: blurry background seaweed (not fish-touchable)
|
|
|
|
| 897 |
farSeaweeds = []; // v5: ultra-far pre-rendered blurry sprites (deepest layer)
|
| 898 |
motes = [];
|
| 899 |
rays = [];
|
|
@@ -1047,6 +1048,50 @@ function buildScene() {
|
|
| 1047 |
}
|
| 1048 |
}
|
| 1049 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1050 |
// v5: ultra-far pre-rendered blurry seaweed β deepest layer, heavily blurred sprites
|
| 1051 |
const nFarClusters = Math.max(6, Math.floor(W / 180)); // 50% more than previous far count
|
| 1052 |
for (let i = 0; i < nFarClusters; i++) {
|
|
@@ -1488,6 +1533,96 @@ function stalkCurveX(w, f) {
|
|
| 1488 |
// v4.3: draw background seaweed procedurally β multi-wave organic motion instead of
|
| 1489 |
// rigid sprite rotation. Far stalks get a soft low-alpha halo pass to keep their
|
| 1490 |
// "far away" look without any runtime blur filters (no slowdown).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1491 |
function drawBackSeaweeds(t) {
|
| 1492 |
// v6: no runtime canvas filter β blur() forces a CPU re-rasterize every frame
|
| 1493 |
// and was the main FPS cost. Softness now comes from the halo pass below instead.
|
|
@@ -2107,6 +2242,9 @@ function frame(now) {
|
|
| 2107 |
// v5: ultra-far pre-rendered blurry seaweed β deepest layer, behind everything else
|
| 2108 |
drawFarSeaweeds(t);
|
| 2109 |
|
|
|
|
|
|
|
|
|
|
| 2110 |
// v4: background seaweed β drawn behind sand/rocks for depth
|
| 2111 |
drawBackSeaweeds(t); // v4.3: procedural multi-wave motion (no separate update pass)
|
| 2112 |
|
|
|
|
| 83 |
}
|
| 84 |
return arr;
|
| 85 |
}
|
| 86 |
+
let fishes = [], bubbles = [], seaweeds = [], backSeaweeds = [], midSeaweeds = [], farSeaweeds = [], motes = [], rays = [], rocks = [], grass = [], fgGrass = [];
|
| 87 |
let playerFish = null; // the fish controlled by gamepad (spawned with F key)
|
| 88 |
const GAMEPAD_DEADZONE = 0.15; // ignore small stick movements
|
| 89 |
let curNight = 0; // v7: shared night factor (dims the top-light + sheen)
|
|
|
|
| 894 |
bubbles = [];
|
| 895 |
seaweeds = [];
|
| 896 |
backSeaweeds = []; // v4: blurry background seaweed (not fish-touchable)
|
| 897 |
+
midSeaweeds = []; // v6: middle-distance seaweed with halo pass (between back and far)
|
| 898 |
farSeaweeds = []; // v5: ultra-far pre-rendered blurry sprites (deepest layer)
|
| 899 |
motes = [];
|
| 900 |
rays = [];
|
|
|
|
| 1048 |
}
|
| 1049 |
}
|
| 1050 |
|
| 1051 |
+
// v6: middle-distance seaweed using halo stroke pass (median properties between back and far)
|
| 1052 |
+
const nMidClusters = Math.max(5, Math.floor(W / 190));
|
| 1053 |
+
for (let i = 0; i < nMidClusters; i++) {
|
| 1054 |
+
const cx = rand(25, W - 25);
|
| 1055 |
+
// Median hue between backSeaweeds (165-200) and farSeaweeds (170-210)
|
| 1056 |
+
const clusterHue = rand(183, 197); // midpoint of both ranges
|
| 1057 |
+
const nStalks = 2 + ((Math.random() * 3) | 0);
|
| 1058 |
+
for (let j = 0; j < nStalks; j++) {
|
| 1059 |
+
const hue = clusterHue + rand(-12, 12);
|
| 1060 |
+
// Median properties: height between backSeaweeds (0.35-0.65H) and farSeaweeds (0.4-0.7H)
|
| 1061 |
+
const st = {
|
| 1062 |
+
x: cx + rand(-22, 22),
|
| 1063 |
+
h: rand(H * 0.42, H * 0.68), // median height
|
| 1064 |
+
segs: 9,
|
| 1065 |
+
phase: rand(0, Math.PI * 2),
|
| 1066 |
+
speed: rand(0.6, 1.15), // median sway speed between back (0.7-1.3) and far (0.5-1.0)
|
| 1067 |
+
hue,
|
| 1068 |
+
width: rand(3, 6.5), // median width
|
| 1069 |
+
tipW: rand(1.8, 3.2),
|
| 1070 |
+
branches: [],
|
| 1071 |
+
// Organic curve parameters (like backSeaweeds for halo pass technique)
|
| 1072 |
+
cA1: rand(9, 16), cF1: rand(0.7, 1.5), cP1: rand(0, Math.PI * 2),
|
| 1073 |
+
cA2: rand(3.5, 9), cF2: rand(1.8, 3.0), cP2: rand(0, Math.PI * 2),
|
| 1074 |
+
// Multi-wave organic motion parameters
|
| 1075 |
+
waveA1: rand(7, 15), waveF1: rand(0.45, 1.1), wavePh1: rand(0, Math.PI * 2),
|
| 1076 |
+
waveA2: rand(3.5, 9), waveF2: rand(1.2, 2.0), wavePh2: rand(0, Math.PI * 2),
|
| 1077 |
+
waveA3: rand(2.5, 6), waveF3: rand(2.2, 3.4), wavePh3: rand(0, Math.PI * 2)
|
| 1078 |
+
};
|
| 1079 |
+
// Add branches (median count between back and far)
|
| 1080 |
+
const nBr = 1 + ((Math.random() * 3) | 0);
|
| 1081 |
+
for (let k = 0; k < nBr; k++) {
|
| 1082 |
+
st.branches.push({
|
| 1083 |
+
f: rand(0.28, 0.82),
|
| 1084 |
+
side: Math.random() < 0.5 ? -1 : 1,
|
| 1085 |
+
len: rand(11, 26),
|
| 1086 |
+
up: rand(9, 22),
|
| 1087 |
+
w: rand(1.8, 3.5),
|
| 1088 |
+
ph: rand(0, Math.PI * 2)
|
| 1089 |
+
});
|
| 1090 |
+
}
|
| 1091 |
+
midSeaweeds.push(st);
|
| 1092 |
+
}
|
| 1093 |
+
}
|
| 1094 |
+
|
| 1095 |
// v5: ultra-far pre-rendered blurry seaweed β deepest layer, heavily blurred sprites
|
| 1096 |
const nFarClusters = Math.max(6, Math.floor(W / 180)); // 50% more than previous far count
|
| 1097 |
for (let i = 0; i < nFarClusters; i++) {
|
|
|
|
| 1533 |
// v4.3: draw background seaweed procedurally β multi-wave organic motion instead of
|
| 1534 |
// rigid sprite rotation. Far stalks get a soft low-alpha halo pass to keep their
|
| 1535 |
// "far away" look without any runtime blur filters (no slowdown).
|
| 1536 |
+
// v6: middle-distance seaweed using halo stroke pass (median depth between back and far)
|
| 1537 |
+
function drawMidSeaweeds(t) {
|
| 1538 |
+
for (const w of midSeaweeds) {
|
| 1539 |
+
const baseY = sandAt(w.x) + 10;
|
| 1540 |
+
const n = w.segs;
|
| 1541 |
+
// Slightly more blue-tinted and softer than backSeaweeds
|
| 1542 |
+
const coreStyle = `hsla(${w.hue},48%,30%,0.65)`;
|
| 1543 |
+
const haloStyle = `hsla(${w.hue},42%,34%,0.16)`;
|
| 1544 |
+
|
| 1545 |
+
// stalk sway at fraction f: three overlapping waves, root planted (f^2), tip free (f)
|
| 1546 |
+
const swayAt = (f) =>
|
| 1547 |
+
Math.sin(t * w.waveF1 + w.wavePh1 + f * 2.4) * w.waveA1 * f * f +
|
| 1548 |
+
Math.sin(t * w.waveF2 + w.wavePh2 + f * 3.8) * w.waveA2 * f * f +
|
| 1549 |
+
Math.sin(t * w.waveF3 + w.wavePh3 + f * 5.4) * w.waveA3 * f;
|
| 1550 |
+
|
| 1551 |
+
// Wider halo pass for more softness (between backSeaweeds and farSeaweeds)
|
| 1552 |
+
for (let pass = 0; pass < 2; pass++) {
|
| 1553 |
+
const isHalo = pass === 0;
|
| 1554 |
+
ctx.strokeStyle = isHalo ? haloStyle : coreStyle;
|
| 1555 |
+
ctx.lineCap = 'round';
|
| 1556 |
+
ctx.lineJoin = 'round';
|
| 1557 |
+
ctx.lineWidth = (w.width + w.tipW) / 2 * (isHalo ? 2.2 : 1); // wider halo than backSeaweeds
|
| 1558 |
+
|
| 1559 |
+
// stalk outline: up one side, round tip, down the other
|
| 1560 |
+
ctx.beginPath();
|
| 1561 |
+
let prevX = 0, prevY = baseY;
|
| 1562 |
+
let lastPx = 0, lastPy = baseY;
|
| 1563 |
+
for (let i = 0; i <= n; i++) {
|
| 1564 |
+
const f = i / n;
|
| 1565 |
+
const px = w.x + stalkCurveX(w, f) + swayAt(f);
|
| 1566 |
+
const py = baseY - w.h * f;
|
| 1567 |
+
const dx = px - prevX, dy = py - prevY;
|
| 1568 |
+
const dl = Math.hypot(dx, dy) || 1;
|
| 1569 |
+
const nx = -dy / dl, ny = dx / dl;
|
| 1570 |
+
const hw = (w.width * (1 - f) + w.tipW * f) / 2;
|
| 1571 |
+
if (i === 0) ctx.moveTo(px + nx * hw, py + ny * hw);
|
| 1572 |
+
else ctx.lineTo(px + nx * hw, py + ny * hw);
|
| 1573 |
+
prevX = px; prevY = py;
|
| 1574 |
+
lastPx = px; lastPy = py;
|
| 1575 |
+
}
|
| 1576 |
+
// round tip cap
|
| 1577 |
+
const dxT = lastPx - prevX, dyT = lastPy - prevY;
|
| 1578 |
+
const dlT = Math.hypot(dxT, dyT) || 1;
|
| 1579 |
+
const nxT = -dyT / dlT, nyT = dxT / dlT;
|
| 1580 |
+
ctx.quadraticCurveTo(lastPx, lastPy, lastPx + dxT * 0.5 + nxT * (w.tipW / 2), lastPy + dyT * 0.5 + nyT * (w.tipW / 2));
|
| 1581 |
+
// down the other side
|
| 1582 |
+
for (let i = n; i >= 0; i--) {
|
| 1583 |
+
const f = i / n;
|
| 1584 |
+
const px = w.x + stalkCurveX(w, f) + swayAt(f);
|
| 1585 |
+
const py = baseY - w.h * f;
|
| 1586 |
+
let dx, dy;
|
| 1587 |
+
if (i === n) { dx = prevX - px; dy = prevY - py; }
|
| 1588 |
+
else {
|
| 1589 |
+
const f2 = (i + 1) / n;
|
| 1590 |
+
const px2 = w.x + stalkCurveX(w, f2) + swayAt(f2);
|
| 1591 |
+
const py2 = baseY - w.h * f2;
|
| 1592 |
+
dx = px2 - px; dy = py2 - py;
|
| 1593 |
+
}
|
| 1594 |
+
const dl = Math.hypot(dx, dy) || 1;
|
| 1595 |
+
const nx = -dy / dl, ny = dx / dl;
|
| 1596 |
+
const hw = (w.width * (1 - f) + w.tipW * f) / 2;
|
| 1597 |
+
ctx.lineTo(px - nx * hw, py - ny * hw);
|
| 1598 |
+
}
|
| 1599 |
+
ctx.closePath();
|
| 1600 |
+
ctx.stroke();
|
| 1601 |
+
|
| 1602 |
+
// branches flutter with the stalk
|
| 1603 |
+
for (const b of w.branches) {
|
| 1604 |
+
const idx = Math.min(n, Math.max(1, Math.round(b.f * n)));
|
| 1605 |
+
const f = idx / n;
|
| 1606 |
+
const ax = w.x + stalkCurveX(w, f) + swayAt(f);
|
| 1607 |
+
const ay = baseY - w.h * f;
|
| 1608 |
+
const flutter = Math.sin(t * w.waveF2 * 1.4 + b.ph) * (3 + w.waveA2 * 0.4);
|
| 1609 |
+
const bx = ax + b.side * b.len + flutter;
|
| 1610 |
+
const by = ay - b.up;
|
| 1611 |
+
ctx.beginPath();
|
| 1612 |
+
ctx.lineWidth = isHalo ? b.w * 2.2 : b.w;
|
| 1613 |
+
ctx.strokeStyle = isHalo ? haloStyle : `hsla(${w.hue + 6},50%,40%,0.7)`;
|
| 1614 |
+
ctx.moveTo(ax, ay);
|
| 1615 |
+
ctx.bezierCurveTo(
|
| 1616 |
+
ax + b.side * b.len * 0.3, ay - b.up * 0.75,
|
| 1617 |
+
bx - b.side * b.len * 0.35, by + b.up * 0.6,
|
| 1618 |
+
bx, by
|
| 1619 |
+
);
|
| 1620 |
+
ctx.stroke();
|
| 1621 |
+
}
|
| 1622 |
+
}
|
| 1623 |
+
}
|
| 1624 |
+
}
|
| 1625 |
+
|
| 1626 |
function drawBackSeaweeds(t) {
|
| 1627 |
// v6: no runtime canvas filter β blur() forces a CPU re-rasterize every frame
|
| 1628 |
// and was the main FPS cost. Softness now comes from the halo pass below instead.
|
|
|
|
| 2242 |
// v5: ultra-far pre-rendered blurry seaweed β deepest layer, behind everything else
|
| 2243 |
drawFarSeaweeds(t);
|
| 2244 |
|
| 2245 |
+
// v6: middle-distance seaweed with halo pass (between far and back)
|
| 2246 |
+
drawMidSeaweeds(t);
|
| 2247 |
+
|
| 2248 |
// v4: background seaweed β drawn behind sand/rocks for depth
|
| 2249 |
drawBackSeaweeds(t); // v4.3: procedural multi-wave motion (no separate update pass)
|
| 2250 |
|