Upload index.html
Browse files- index.html +54 -11
index.html
CHANGED
|
@@ -544,15 +544,22 @@ function resize() {
|
|
| 544 |
buildScene();
|
| 545 |
}
|
| 546 |
|
| 547 |
-
// --- slow light cycle:
|
| 548 |
-
const CYCLE =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 549 |
// [t, waterTop, waterMid, waterBot, rayTint, ambientWashRGB, ambientWashAlpha]
|
| 550 |
const LIGHT_KEYS = [
|
| 551 |
-
[0.00,
|
| 552 |
-
[
|
| 553 |
-
[
|
| 554 |
-
[
|
| 555 |
-
[
|
|
|
|
| 556 |
];
|
| 557 |
|
| 558 |
function sampleLight(t) {
|
|
@@ -572,10 +579,10 @@ function rgbStr(c) { return `rgb(${c[0]},${c[1]},${c[2]})`; }
|
|
| 572 |
// Drives the sleepier night-time behaviour. Cheap: pure math on one number.
|
| 573 |
function nightFactor(t) {
|
| 574 |
const p = ((t / CYCLE) % 1 + 1) % 1;
|
| 575 |
-
if (p <
|
| 576 |
-
if (p <
|
| 577 |
-
if (p <
|
| 578 |
-
return 1 - (p -
|
| 579 |
}
|
| 580 |
|
| 581 |
function drawBackground(light) {
|
|
@@ -860,6 +867,40 @@ function drawVignette() {
|
|
| 860 |
ctx.fillRect(0, 0, W, H);
|
| 861 |
}
|
| 862 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 863 |
// Precomputed static layers — the sand and rocks never move, so they are
|
| 864 |
// rendered once to an offscreen canvas and blitted each frame (v6) instead of
|
| 865 |
// re-drawing their gradients and paths every single frame.
|
|
@@ -936,12 +977,14 @@ function frame(now) {
|
|
| 936 |
}
|
| 937 |
|
| 938 |
drawVignette();
|
|
|
|
| 939 |
requestAnimationFrame(frame);
|
| 940 |
}
|
| 941 |
|
| 942 |
function doResize() {
|
| 943 |
resize();
|
| 944 |
buildStaticLayer();
|
|
|
|
| 945 |
}
|
| 946 |
window.addEventListener("resize", doResize);
|
| 947 |
doResize();
|
|
|
|
| 544 |
buildScene();
|
| 545 |
}
|
| 546 |
|
| 547 |
+
// --- slow light cycle: bright day -> dusk -> night -> dawn, over CYCLE seconds ---
|
| 548 |
+
const CYCLE = 180; // seconds per full day/night cycle (v9: exactly 3 minutes)
|
| 549 |
+
// Phase durations in seconds: daytime blue 90s, dusk 15s, night 60s, dawn 15s. Total = 180s.
|
| 550 |
+
// The cycle starts at the beginning of the bright daytime-blue phase.
|
| 551 |
+
const DUSK_START = 90 / CYCLE; // p where day ends and dusk begins (0.5)
|
| 552 |
+
const NIGHT_START = (90 + 15) / CYCLE; // p where dusk ends and night begins (~0.5833)
|
| 553 |
+
const DAWN_START = (90 + 15 + 60) / CYCLE; // p where night ends and dawn begins (~0.9167)
|
| 554 |
+
const MID_DUSK = (DUSK_START + NIGHT_START) / 2; // sunset-glow point, mid-dusk
|
| 555 |
// [t, waterTop, waterMid, waterBot, rayTint, ambientWashRGB, ambientWashAlpha]
|
| 556 |
const LIGHT_KEYS = [
|
| 557 |
+
[0.00, [13,88,118], [8,58,82], [3,25,43], [190,235,255], [190,235,255], 0.00],
|
| 558 |
+
[DUSK_START, [13,88,118], [8,58,82], [3,25,43], [190,235,255], [190,235,255], 0.00],
|
| 559 |
+
[MID_DUSK, [224,122,52], [128,60,36], [34,22,26], [255,176,96], [255,150,70], 0.14],
|
| 560 |
+
[NIGHT_START,[7,15,32], [4,10,22], [2,6,14], [96,146,206], [3,8,20], 0.40],
|
| 561 |
+
[DAWN_START, [7,15,32], [4,10,22], [2,6,14], [96,146,206], [3,8,20], 0.40],
|
| 562 |
+
[1.00, [13,88,118], [8,58,82], [3,25,43], [190,235,255], [190,235,255], 0.00]
|
| 563 |
];
|
| 564 |
|
| 565 |
function sampleLight(t) {
|
|
|
|
| 579 |
// Drives the sleepier night-time behaviour. Cheap: pure math on one number.
|
| 580 |
function nightFactor(t) {
|
| 581 |
const p = ((t / CYCLE) % 1 + 1) % 1;
|
| 582 |
+
if (p < DUSK_START) return 0; // bright day
|
| 583 |
+
if (p < NIGHT_START) return (p - DUSK_START) / (NIGHT_START - DUSK_START); // dusk: fade to night
|
| 584 |
+
if (p < DAWN_START) return 1; // full night
|
| 585 |
+
return 1 - (p - DAWN_START) / (1 - DAWN_START); // dawn: fade to day
|
| 586 |
}
|
| 587 |
|
| 588 |
function drawBackground(light) {
|
|
|
|
| 867 |
ctx.fillRect(0, 0, W, H);
|
| 868 |
}
|
| 869 |
|
| 870 |
+
// --- v8: mild film grain over the whole scene.
|
| 871 |
+
// Efficient: a small noise tile is generated ONCE and turned into a cached pattern.
|
| 872 |
+
// Each frame we just blit that pattern with 'overlay' blending at low opacity and a
|
| 873 |
+
// random sub-tile offset, so it shimmers like real film. Cost = one pattern fillRect.
|
| 874 |
+
let grainPattern = null;
|
| 875 |
+
const GRAIN_TILE = 160; // tile size in px (kept small so the pattern is cheap to repeat)
|
| 876 |
+
function buildGrainTile() {
|
| 877 |
+
const tile = document.createElement("canvas");
|
| 878 |
+
tile.width = GRAIN_TILE; tile.height = GRAIN_TILE;
|
| 879 |
+
const g = tile.getContext("2d");
|
| 880 |
+
const img = g.createImageData(GRAIN_TILE, GRAIN_TILE);
|
| 881 |
+
const d = img.data;
|
| 882 |
+
for (let i = 0; i < d.length; i += 4) {
|
| 883 |
+
// grayscale noise centred on mid-grey (~128): with 'overlay' blend, values near
|
| 884 |
+
// 128 are neutral while higher/lower ones gently brighten/darken -> filmic grain.
|
| 885 |
+
const v = 96 + Math.floor(Math.random() * 64); // 96..159, centred on 128
|
| 886 |
+
d[i] = v; d[i+1] = v; d[i+2] = v; d[i+3] = 255;
|
| 887 |
+
}
|
| 888 |
+
g.putImageData(img, 0, 0);
|
| 889 |
+
grainPattern = ctx.createPattern(tile, "repeat"); // cached once, reused every frame
|
| 890 |
+
}
|
| 891 |
+
function drawGrain() {
|
| 892 |
+
if (!grainPattern) return;
|
| 893 |
+
const ox = (Math.random() * GRAIN_TILE) | 0; // random offset each frame -> animated grain
|
| 894 |
+
const oy = (Math.random() * GRAIN_TILE) | 0;
|
| 895 |
+
ctx.save();
|
| 896 |
+
ctx.globalCompositeOperation = "overlay";
|
| 897 |
+
ctx.globalAlpha = 0.7; // mild: subtle shimmer, not heavy noise
|
| 898 |
+
ctx.translate(-ox, -oy);
|
| 899 |
+
ctx.fillStyle = grainPattern;
|
| 900 |
+
ctx.fillRect(0, 0, W + GRAIN_TILE, H + GRAIN_TILE); // cover full screen even with offset
|
| 901 |
+
ctx.restore();
|
| 902 |
+
}
|
| 903 |
+
|
| 904 |
// Precomputed static layers — the sand and rocks never move, so they are
|
| 905 |
// rendered once to an offscreen canvas and blitted each frame (v6) instead of
|
| 906 |
// re-drawing their gradients and paths every single frame.
|
|
|
|
| 977 |
}
|
| 978 |
|
| 979 |
drawVignette();
|
| 980 |
+
drawGrain(); // v8: mild film grain on top of everything
|
| 981 |
requestAnimationFrame(frame);
|
| 982 |
}
|
| 983 |
|
| 984 |
function doResize() {
|
| 985 |
resize();
|
| 986 |
buildStaticLayer();
|
| 987 |
+
if (!grainPattern) buildGrainTile(); // v8: grain tile is size-independent, build once
|
| 988 |
}
|
| 989 |
window.addEventListener("resize", doResize);
|
| 990 |
doResize();
|