pixel/js/lighting.js
2026-06-01 18:27:25 +09:00

68 lines
2.7 KiB
JavaScript

(function () {
'use strict';
const root = window.PixelIslandModules ||= {};
const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
const lerp = (a, b, t) => a + (b - a) * t;
const mod = (n, m) => ((n % m) + m) % m;
function getCelestialLightForMinute(minute) {
const isNight = minute >= 6;
const local = isNight ? (minute - 6) / 4 : minute / 6;
const t = clamp(local, 0, 1);
const eased = t * t * (3 - 2 * t);
const elevation = Math.max(.08, Math.sin(t * Math.PI));
const sourceX = isNight ? lerp(-1.05, 1.05, eased) : lerp(1.18, -1.18, eased);
const sourceY = isNight ? -.46 - elevation * .24 : -.58 - elevation * .34;
return {
x: sourceX,
y: sourceY,
elevation,
isNight,
shadeAlpha: isNight ? .18 : lerp(.20, .08, elevation)
};
}
function getShadowForMinute(minute) {
const light = getCelestialLightForMinute(minute);
const length = lerp(light.isNight ? 1.35 : 1.9, light.isNight ? .85 : .52, light.elevation);
return {
x: -light.x * 6.5 * length,
y: Math.min(-1.8, light.y * 4.2 * length),
length,
alpha: light.isNight ? .085 : lerp(.27, .12, light.elevation)
};
}
function getPhase(dayMs, now = Date.now()) {
const t = mod(now, dayMs);
const minute = t / 60000;
const stops = [
{ at: 0, key: 'morning', label: 'Morning', darkness: 0.18, tint: [255, 208, 144, 0.12] },
{ at: 1, key: 'day', label: 'Day', darkness: 0.00, tint: [255, 255, 255, 0.00] },
{ at: 5, key: 'day', label: 'Day', darkness: 0.00, tint: [255, 255, 255, 0.00] },
{ at: 6, key: 'evening', label: 'Evening', darkness: 0.18, tint: [255, 146, 114, 0.14] },
{ at: 10, key: 'night', label: 'Night', darkness: 0.48, tint: [36, 47, 96, 0.18] }
];
let a = stops[0], b = stops[1];
for (let i = 0; i < stops.length - 1; i++) {
if (minute >= stops[i].at && minute < stops[i + 1].at) { a = stops[i]; b = stops[i + 1]; break; }
if (minute >= 6) { a = stops[3]; b = stops[4]; }
}
const localT = clamp((minute - a.at) / Math.max(0.0001, b.at - a.at), 0, 1);
const eased = localT * localT * (3 - 2 * localT);
const tint = a.tint.map((v, i) => lerp(v, b.tint[i], eased));
const label = minute < 1 ? 'Morning' : minute < 5 ? 'Day' : minute < 6 ? 'Evening' : 'Night';
return {
key: label.toLowerCase(),
label,
progress: t / dayMs,
darkness: lerp(a.darkness, b.darkness, eased),
tint: `rgba(${Math.round(tint[0])}, ${Math.round(tint[1])}, ${Math.round(tint[2])}, ${tint[3].toFixed(3)})`,
light: getCelestialLightForMinute(minute),
shadow: getShadowForMinute(minute)
};
}
root.Lighting = { getPhase, getShadowForMinute, getCelestialLightForMinute };
})();