grechka-game/src3d/render/lighting.js

105 lines
4.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Lighting overlay at night (Minecraft-style: offscreen lightmap, wall occlusion, warm flicker)
import { state } from '../core/state.js';
import { TILE } from '../core/constants.js';
import { BLOCKS } from '../data/blocks.js';
import { getBlock } from '../world/world-storage.js';
export function renderLighting(ctx, W, H, camX, camY, lightC, lightCtx, dpr, blocks, minGX, maxGX, minGY, maxGY, now) {
// 1) Рисуем тёмный оверлей на offscreen canvas
lightC.width = W * dpr;
lightC.height = H * dpr;
lightCtx.setTransform(dpr, 0, 0, dpr, 0, 0);
lightCtx.fillStyle = 'rgba(0,0,12,0.82)';
lightCtx.fillRect(0, 0, W, H);
// 2) Вырезаем «окна света» через destination-out — свет не проходит сквозь стены
lightCtx.globalCompositeOperation = 'destination-out';
// Функция: рисуем мягкий луч света с затуханием за стенами
function castLight(sx, sy, radius) {
const flick = 0.97 + Math.sin(now / 300 + sx * 0.01) * 0.015 + Math.sin(now / 470 + sy * 0.02) * 0.015;
const r = radius * flick;
// 12 лучей — достаточно для мягкого круга
const steps = 24;
// Собираем дистанции до стен по лучам
const dists = new Float32Array(steps);
for (let i = 0; i < steps; i++) {
const angle = (i / steps) * Math.PI * 2;
const dx = Math.cos(angle);
const dy = Math.sin(angle);
let maxDist = r;
// Идём по лучу пока не упрёмся в стену
for (let step = TILE * 0.3; step < r; step += TILE * 0.35) {
const gx = Math.floor((sx + dx * step) / TILE);
const gy = Math.floor((sy + dy * step) / TILE);
const blk = getBlock(gx, gy);
if (blk && !blk.dead && BLOCKS[blk.t] && BLOCKS[blk.t].solid && step > TILE * 0.3) {
maxDist = step;
break;
}
}
dists[i] = maxDist;
}
// Рисуем сглаженный полигон по dists
const cx = sx - camX, cy = sy - camY;
// Центр: яркая точка
const maxR = Math.max(...dists);
const grad = lightCtx.createRadialGradient(cx, cy, 0, cx, cy, maxR);
grad.addColorStop(0, 'rgba(255,255,255,1)');
grad.addColorStop(0.5, 'rgba(255,255,255,0.65)');
grad.addColorStop(1, 'rgba(255,255,255,0)');
lightCtx.fillStyle = grad;
// Рисуем shape по dists (звездоподобный полигон)
lightCtx.beginPath();
for (let i = 0; i <= steps; i++) {
const idx = i % steps;
const nextIdx = (i + 1) % steps;
const avgD = (dists[idx] + dists[nextIdx]) / 2;
const angle = (idx / steps) * Math.PI * 2;
const px = cx + Math.cos(angle) * dists[idx];
const py = cy + Math.sin(angle) * dists[idx];
if (i === 0) lightCtx.moveTo(px, py);
else lightCtx.lineTo(px, py);
}
lightCtx.closePath();
lightCtx.fill();
}
// Источники света
for (const b of blocks) {
if (b.dead) continue;
if (b.gx < minGX - 5 || b.gx > maxGX + 5 || b.gy < minGY - 5 || b.gy > maxGY + 5) continue;
const def = BLOCKS[b.t];
if (def.lightRadius) {
castLight(b.gx * TILE + TILE / 2, b.gy * TILE + TILE / 2, def.lightRadius);
}
}
// 3) Накладываем lightmap на основной canvas
lightCtx.globalCompositeOperation = 'source-over';
ctx.drawImage(lightC, 0, 0, W, H);
// 4) Тёплый оверлей от источников света (additive, мягкий)
ctx.save();
ctx.globalCompositeOperation = 'lighter';
for (const b of blocks) {
if (b.dead) continue;
if (b.gx < minGX - 3 || b.gx > maxGX + 3 || b.gy < minGY - 3 || b.gy > maxGY + 3) continue;
const def = BLOCKS[b.t];
if (def.lightRadius) {
const flick = 0.9 + Math.sin(now / 350 + b.gx * 3.7) * 0.05 + Math.sin(now / 530 + b.gy * 2.3) * 0.05;
const wx = b.gx * TILE + TILE / 2 - camX;
const wy = b.gy * TILE + TILE / 2 - camY;
const r = def.lightRadius * 0.85;
const grad = ctx.createRadialGradient(wx, wy, 0, wx, wy, r);
grad.addColorStop(0, `rgba(255,180,80,0.28)`);
grad.addColorStop(0.5, `rgba(255,140,40,0.13)`);
grad.addColorStop(1, 'rgba(255,100,20,0)');
ctx.fillStyle = grad;
ctx.beginPath();
ctx.arc(wx, wy, r, 0, Math.PI * 2);
ctx.fill();
}
}
ctx.restore();
}