diff --git a/v2/CLAUDE.md b/v2/CLAUDE.md index 13b4424..c0d4a10 100644 --- a/v2/CLAUDE.md +++ b/v2/CLAUDE.md @@ -1636,7 +1636,12 @@ наклоном (строго вертикальный свет прячет тени под объектом), фрустум как у зенитного, `k·LIGHTNING_FLASH(5)·(1+nightK)` — днём вспышка тонет в солнце, ночью главная (ночной кадр 13 → 153 яркости). НЕ трогает - setSun (тот зовётся каждым тиком света). Мост: `makeLightning(at?)`, + setSun (тот зовётся каждым тиком света). Lit-частицы (дождь/снег) и + туман вспышку НЕ видели (их env-свет — только ambient+солнце; фидбек + «молния не подсвечивает дождь») — теперь `lightningEnvLight(k, nightK)` + добавляет долю вспышки (LIGHTNING_ENV_SHARE 0.2) в uEnv/uFogColor в + setSun: ночью дождь невидим и проявляется во вспышку. Мост: + `makeLightning(at?)`, `lightningInfo()`; кнопка «⚡ Молния» в секции погоды панели. - **Гейт weather.mjs** (+6 чеков молнии, 25 всего): вспышка активна и ярче (кадр ×1.08), гаснет за 0.55 с (кадр вернулся), ночью вспышка diff --git a/v2/packages/engine/src/index.ts b/v2/packages/engine/src/index.ts index 705bd42..58ee723 100644 --- a/v2/packages/engine/src/index.ts +++ b/v2/packages/engine/src/index.ts @@ -109,7 +109,7 @@ export type { DayNightSpec } from './render/daynight'; export { DEFAULT_WEATHER, WeatherSystem, clampWeather, snowPoints, windVector, rainPoints } from './render/weather'; export type { WeatherSpec } from './render/weather'; -export { LightningSystem, lightningEnvelope, boltPoints, LIGHTNING_TOP, LIGHTNING_DURATION, LIGHTNING_FLASH } from './render/lightning'; +export { LightningSystem, lightningEnvelope, boltPoints, lightningEnvLight, LIGHTNING_TOP, LIGHTNING_DURATION, LIGHTNING_FLASH, LIGHTNING_ENV_SHARE } from './render/lightning'; export type { LightningInfo, BoltPoint } from './render/lightning'; export type { PointLightOptions, PointLightHandle, SpotLightOptions, SpotLightHandle } from './render/voxelRenderer'; // setDayAmbient/setNight/addPointLight/addSpotLight — методы класса VoxelRenderer diff --git a/v2/packages/engine/src/render/__tests__/lightning.test.ts b/v2/packages/engine/src/render/__tests__/lightning.test.ts index 2ca0408..44c6657 100644 --- a/v2/packages/engine/src/render/__tests__/lightning.test.ts +++ b/v2/packages/engine/src/render/__tests__/lightning.test.ts @@ -2,9 +2,20 @@ * жизненный цикл вспышки системы (без WebGL — стаб рендера). */ import { describe, expect, it } from 'vitest'; import { - LIGHTNING_DURATION, LIGHTNING_TOP, LightningSystem, boltPoints, lightningEnvelope, + LIGHTNING_DURATION, LIGHTNING_FLASH, LIGHTNING_TOP, LightningSystem, + boltPoints, lightningEnvelope, lightningEnvLight, } from '../lightning'; +describe('lightningEnvLight (подсветка дождя/тумана)', () => { + it('ноль без вспышки, растёт с k и ночью сильнее', () => { + expect(lightningEnvLight(0, 0)).toEqual([0, 0, 0]); + const day = lightningEnvLight(1, 0); + const night = lightningEnvLight(1, 1); + expect(day[2]).toBeCloseTo(LIGHTNING_FLASH * 0.2); // доля вспышки в env + expect(night[2]).toBeCloseTo(day[2] * 2); // ночью ×(1+nightK) + }); +}); + describe('lightningEnvelope', () => { it('пик в момент удара, всё в 0..1', () => { expect(lightningEnvelope(0)).toBeCloseTo(1); diff --git a/v2/packages/engine/src/render/lightning.ts b/v2/packages/engine/src/render/lightning.ts index 3d4fb22..ba0475f 100644 --- a/v2/packages/engine/src/render/lightning.ts +++ b/v2/packages/engine/src/render/lightning.ts @@ -5,7 +5,9 @@ * отдельный направленный свет рендера (VoxelRenderer.setLightning): свет * бьёт из разряда вниз с лёгким наклоном — тени от кастеров качаются * (та же shadow map-механика, что у солнца); ночью вспышка читается ярче — - * днём она тонет в солнечном свете. Огибающая мерцания и геометрия разряда + * днём она тонет в солнечном свете. Lit-частицы (дождь/снег) и туман + * подсвечиваются вспышкой через env-свет (lightningEnvLight — вклад в + * uEnv/uFogColor в setSun). Огибающая мерцания и геометрия разряда * — чистые функции (тесты без WebGL). Пока удар по кнопке дев-панели; * позже из этого собирается гроза (серия ударов) при необходимости в игре. */ @@ -25,6 +27,20 @@ /** Цвет канала разряда (холодный бело-голубой). */ const BOLT_COLOR = '#eef3ff'; +/** Цвет света вспышки (совпадает со светом рендера, 0xdfe8ff). */ +export const LIGHTNING_COLOR: readonly [number, number, number] = [0.874, 0.91, 1.0]; + +/** Доля вспышки в env-свете частиц/тумана: билборд ловит свет со всех + * сторон, а вспышка направленная — берём часть её силы. */ +export const LIGHTNING_ENV_SHARE = 0.2; + +/** Вклад вспышки в env-свет частиц и тумана: дождь/снег (lit-слои) и + * туман подсвечиваются разрядом вместе с миром. Чистая функция. */ +export function lightningEnvLight(k: number, nightK: number): [number, number, number] { + const f = Math.max(0, k) * LIGHTNING_FLASH * (1 + nightK) * LIGHTNING_ENV_SHARE; + return [LIGHTNING_COLOR[0] * f, LIGHTNING_COLOR[1] * f, LIGHTNING_COLOR[2] * f]; +} + /** Псевдослучай 0..1 от пары чисел (sin-хэш): детерминирован, без состояния. */ function boltHash(a: number, b: number): number { const s = Math.sin(a * 127.1 + b * 311.7) * 43758.5453; diff --git a/v2/packages/engine/src/render/voxelRenderer.ts b/v2/packages/engine/src/render/voxelRenderer.ts index 29fa679..ebe2256 100644 --- a/v2/packages/engine/src/render/voxelRenderer.ts +++ b/v2/packages/engine/src/render/voxelRenderer.ts @@ -15,7 +15,7 @@ import { createSpriteLayer, createQuadLayer, makeParticleLightUniforms, packParticleLights, particleEnv } from './particles'; import type { ParticleLayer, ParticleLight, ParticleSpot, ParticleLightUniforms } from './particles'; import { createFogComposite, makeFogUniforms, fogColor } from './fog'; -import { LIGHTNING_FLASH, LIGHTNING_TOP } from './lightning'; +import { LIGHTNING_FLASH, LIGHTNING_TOP, lightningEnvLight } from './lightning'; import { particleWindow } from './polyWindow'; /** Облако деформированных вокселей (позы рига — не выровнены по сетке). */ @@ -586,19 +586,21 @@ } this.zenith.shadow.radius = shadowBlurTexels(this.shadowSoftK, top, this.zenith.shadow.mapSize.x); // env-свет освещаемых частиц: ambient + доля солнца — ночь темнит - // их без ручных tint'ов (setSun зовётся при любом изменении света) + // их без ручных tint'ов (setSun зовётся при любом изменении света). + // Плюс вклад вспышки молнии: дождь/снег ловят свет разряда + const bolt = lightningEnvLight(this.flashK, this.nightK); const e = particleEnv( { r: this.ambient.color.r, g: this.ambient.color.g, b: this.ambient.color.b, intensity: this.ambient.intensity }, { r: this.sun.color.r, g: this.sun.color.g, b: this.sun.color.b, intensity: this.sun.intensity }, ); - (this.particleLightsU.uEnv!.value as THREE.Vector3).set(e[0], e[1], e[2]); - // цвет тумана: тот же env-свет с яркой долей солнца — туман освещается - // вместе с миром (setSun зовётся при любом изменении света) + (this.particleLightsU.uEnv!.value as THREE.Vector3).set(e[0] + bolt[0], e[1] + bolt[1], e[2] + bolt[2]); + // цвет тумана: тот же env-свет с яркой долей солнца (и вспышкой) — + // туман освещается вместе с миром const fc = fogColor( { r: this.ambient.color.r, g: this.ambient.color.g, b: this.ambient.color.b, intensity: this.ambient.intensity }, { r: this.sun.color.r, g: this.sun.color.g, b: this.sun.color.b, intensity: this.sun.intensity }, ); - (this.fogU.uFogColor!.value as THREE.Vector3).set(fc[0], fc[1], fc[2]); + (this.fogU.uFogColor!.value as THREE.Vector3).set(fc[0] + bolt[0], fc[1] + bolt[1], fc[2] + bolt[2]); } /**