mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-11 13:52:19 +00:00
Drive the visual styles, bloom and sharpen through the effects port
VisualEffects defines each preset on the renderer and keeps its own records of intensities and parameters, written through to the port, so crossfades, the anime sky fade, bloom and sharpen behave as before. The renderer animates the animated styles. Sensor picks read the style records instead of the renderer's stages. The application renderer now has the effects port and its capabilities.
This commit is contained in:
1 parent
d6b553646e
commit
d0ef3a902b
18 files changed
+375
-186
No files matched your search
@@ -1473,11 +1473,14 @@ remain unchanged.
|
|||||||
|
|
||||||
## Visual effects ownership
|
## Visual effects ownership
|
||||||
|
|
||||||
VisualEffects owns style-stage creation, crossfades, animation scheduling, bloom
|
VisualEffects keeps the records of the styles, their crossfades, bloom and
|
||||||
and sharpen. Display supplies actions and renders settings; effect execution has
|
sharpen, and drives them through the renderer's effects port: it defines each
|
||||||
no DOM dependency. Existing presets and the 500 ms transition remain unchanged.
|
preset there as a style, and the renderer runs the passes and animates the
|
||||||
Stopping the controller revokes animation before asynchronous UI teardown; final
|
animated ones. Display supplies actions and renders settings; effect execution
|
||||||
destruction removes its stages and restores the borrowed bloom configuration.
|
has no DOM dependency. Existing presets and the 500 ms transition remain
|
||||||
|
unchanged. Stopping the controller revokes animation before asynchronous UI
|
||||||
|
teardown; final destruction removes its styles and turns bloom and sharpen off,
|
||||||
|
and the renderer restores the scene's bloom configuration when it goes.
|
||||||
Snow is no longer a visual preset. A share link that names it, in either the
|
Snow is no longer a visual preset. A share link that names it, in either the
|
||||||
long or compact form, opens on Normal and drops the Snow shader parameters. A
|
long or compact form, opens on Normal and drops the Snow shader parameters. A
|
||||||
view passed to embed mode or `show_in_gods_eye_view`, and a saved scene shot,
|
view passed to embed mode or `show_in_gods_eye_view`, and a saved scene shot,
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"src/render/cesium/ctrlWheelZoom.js",
|
"src/render/cesium/ctrlWheelZoom.js",
|
||||||
"src/render/input/router.js",
|
"src/render/input/router.js",
|
||||||
"src/input/sensorPick.js",
|
"src/input/sensorPick.js",
|
||||||
|
"src/styles/styleStages.js",
|
||||||
"src/data/infrastructureOverlayEntry.js",
|
"src/data/infrastructureOverlayEntry.js",
|
||||||
"src/sources/infrastructureData.js",
|
"src/sources/infrastructureData.js",
|
||||||
"src/queries/heights.js"
|
"src/queries/heights.js"
|
||||||
@@ -640,6 +641,7 @@
|
|||||||
"modules": [
|
"modules": [
|
||||||
"src/ui/effects.js",
|
"src/ui/effects.js",
|
||||||
"src/ui/visualEffects.js",
|
"src/ui/visualEffects.js",
|
||||||
|
"src/styles/styleStages.js",
|
||||||
"src/ui/visualPresets.js",
|
"src/ui/visualPresets.js",
|
||||||
"src/bloom.js",
|
"src/bloom.js",
|
||||||
"src/styles/retro.js",
|
"src/styles/retro.js",
|
||||||
@@ -915,6 +917,7 @@
|
|||||||
"src/data/scenePick.js",
|
"src/data/scenePick.js",
|
||||||
"src/data/pickRegistry.js",
|
"src/data/pickRegistry.js",
|
||||||
"src/input/sensorPick.js",
|
"src/input/sensorPick.js",
|
||||||
|
"src/styles/styleStages.js",
|
||||||
"src/input/dispatcher.js",
|
"src/input/dispatcher.js",
|
||||||
"src/render/cesium/cameraInput.js",
|
"src/render/cesium/cameraInput.js",
|
||||||
"src/render/cesium/input.js",
|
"src/render/cesium/input.js",
|
||||||
@@ -1097,6 +1100,7 @@
|
|||||||
"src/ui/surfaceKeyboard.js",
|
"src/ui/surfaceKeyboard.js",
|
||||||
"src/ui/uiLifetime.js",
|
"src/ui/uiLifetime.js",
|
||||||
"src/ui/visualEffects.js",
|
"src/ui/visualEffects.js",
|
||||||
|
"src/styles/styleStages.js",
|
||||||
"src/ui/visualInput.js",
|
"src/ui/visualInput.js",
|
||||||
"src/ui/visualPresets.js",
|
"src/ui/visualPresets.js",
|
||||||
"src/ui/visualSettings.js",
|
"src/ui/visualSettings.js",
|
||||||
@@ -1348,6 +1352,7 @@
|
|||||||
"src/ui/stores/dataPacks.js",
|
"src/ui/stores/dataPacks.js",
|
||||||
"src/ui/i18n.js",
|
"src/ui/i18n.js",
|
||||||
"src/input/sensorPick.js",
|
"src/input/sensorPick.js",
|
||||||
|
"src/styles/styleStages.js",
|
||||||
"src/data/scenePick.js",
|
"src/data/scenePick.js",
|
||||||
"src/data/pickRegistry.js",
|
"src/data/pickRegistry.js",
|
||||||
"src/input/dispatcher.js",
|
"src/input/dispatcher.js",
|
||||||
@@ -1447,6 +1452,7 @@
|
|||||||
"src/ui/unitLabels.js",
|
"src/ui/unitLabels.js",
|
||||||
"src/data/entityRecords.js",
|
"src/data/entityRecords.js",
|
||||||
"src/input/sensorPick.js",
|
"src/input/sensorPick.js",
|
||||||
|
"src/styles/styleStages.js",
|
||||||
"src/data/scenePick.js",
|
"src/data/scenePick.js",
|
||||||
"src/data/pickRegistry.js",
|
"src/data/pickRegistry.js",
|
||||||
"src/input/dispatcher.js",
|
"src/input/dispatcher.js",
|
||||||
@@ -1536,6 +1542,7 @@
|
|||||||
"src/data/entityRecords.js",
|
"src/data/entityRecords.js",
|
||||||
"src/data/scenePick.js",
|
"src/data/scenePick.js",
|
||||||
"src/input/sensorPick.js",
|
"src/input/sensorPick.js",
|
||||||
|
"src/styles/styleStages.js",
|
||||||
"src/data/pickRegistry.js",
|
"src/data/pickRegistry.js",
|
||||||
"src/input/dispatcher.js",
|
"src/input/dispatcher.js",
|
||||||
"src/render/cesium/follow.js",
|
"src/render/cesium/follow.js",
|
||||||
@@ -1636,6 +1643,7 @@
|
|||||||
"src/data/scenePick.js",
|
"src/data/scenePick.js",
|
||||||
"src/data/pickRegistry.js",
|
"src/data/pickRegistry.js",
|
||||||
"src/input/sensorPick.js",
|
"src/input/sensorPick.js",
|
||||||
|
"src/styles/styleStages.js",
|
||||||
"src/input/dispatcher.js",
|
"src/input/dispatcher.js",
|
||||||
"src/render/cesium/follow.js",
|
"src/render/cesium/follow.js",
|
||||||
"src/render/cesium/cameraInput.js",
|
"src/render/cesium/cameraInput.js",
|
||||||
@@ -1729,6 +1737,7 @@
|
|||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
"src/i18n/locales/zh-Hant.json",
|
"src/i18n/locales/zh-Hant.json",
|
||||||
"src/input/sensorPick.js",
|
"src/input/sensorPick.js",
|
||||||
|
"src/styles/styleStages.js",
|
||||||
"src/data/scenePick.js",
|
"src/data/scenePick.js",
|
||||||
"src/data/pickRegistry.js",
|
"src/data/pickRegistry.js",
|
||||||
"src/input/dispatcher.js",
|
"src/input/dispatcher.js",
|
||||||
@@ -1783,6 +1792,7 @@
|
|||||||
"src/ui/unitLabels.js",
|
"src/ui/unitLabels.js",
|
||||||
"src/data/entityRecords.js",
|
"src/data/entityRecords.js",
|
||||||
"src/input/sensorPick.js",
|
"src/input/sensorPick.js",
|
||||||
|
"src/styles/styleStages.js",
|
||||||
"src/data/scenePick.js",
|
"src/data/scenePick.js",
|
||||||
"src/data/pickRegistry.js",
|
"src/data/pickRegistry.js",
|
||||||
"src/input/dispatcher.js",
|
"src/input/dispatcher.js",
|
||||||
@@ -1859,6 +1869,7 @@
|
|||||||
"src/data/scenePick.js",
|
"src/data/scenePick.js",
|
||||||
"src/data/pickRegistry.js",
|
"src/data/pickRegistry.js",
|
||||||
"src/input/sensorPick.js",
|
"src/input/sensorPick.js",
|
||||||
|
"src/styles/styleStages.js",
|
||||||
"src/input/dispatcher.js",
|
"src/input/dispatcher.js",
|
||||||
"src/render/cesium/cameraInput.js",
|
"src/render/cesium/cameraInput.js",
|
||||||
"src/render/cesium/input.js",
|
"src/render/cesium/input.js",
|
||||||
@@ -1932,6 +1943,7 @@
|
|||||||
"src/layers/installations/classLabel.js",
|
"src/layers/installations/classLabel.js",
|
||||||
"src/data/entityRecords.js",
|
"src/data/entityRecords.js",
|
||||||
"src/input/sensorPick.js",
|
"src/input/sensorPick.js",
|
||||||
|
"src/styles/styleStages.js",
|
||||||
"src/data/scenePick.js",
|
"src/data/scenePick.js",
|
||||||
"src/data/pickRegistry.js",
|
"src/data/pickRegistry.js",
|
||||||
"src/input/dispatcher.js",
|
"src/input/dispatcher.js",
|
||||||
@@ -2097,6 +2109,7 @@
|
|||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
"src/i18n/locales/zh-Hant.json",
|
"src/i18n/locales/zh-Hant.json",
|
||||||
"src/input/sensorPick.js",
|
"src/input/sensorPick.js",
|
||||||
|
"src/styles/styleStages.js",
|
||||||
"src/data/scenePick.js",
|
"src/data/scenePick.js",
|
||||||
"src/data/pickRegistry.js",
|
"src/data/pickRegistry.js",
|
||||||
"src/input/dispatcher.js",
|
"src/input/dispatcher.js",
|
||||||
@@ -2191,6 +2204,7 @@
|
|||||||
"src/data/scenePick.js",
|
"src/data/scenePick.js",
|
||||||
"src/data/pickRegistry.js",
|
"src/data/pickRegistry.js",
|
||||||
"src/input/sensorPick.js",
|
"src/input/sensorPick.js",
|
||||||
|
"src/styles/styleStages.js",
|
||||||
"src/input/dispatcher.js",
|
"src/input/dispatcher.js",
|
||||||
"src/render/cesium/cameraInput.js",
|
"src/render/cesium/cameraInput.js",
|
||||||
"src/render/cesium/input.js",
|
"src/render/cesium/input.js",
|
||||||
@@ -2285,6 +2299,7 @@
|
|||||||
"src/data/inputOwnership.js",
|
"src/data/inputOwnership.js",
|
||||||
"src/data/scenePick.js",
|
"src/data/scenePick.js",
|
||||||
"src/input/sensorPick.js",
|
"src/input/sensorPick.js",
|
||||||
|
"src/styles/styleStages.js",
|
||||||
"src/data/pickRegistry.js",
|
"src/data/pickRegistry.js",
|
||||||
"src/input/dispatcher.js",
|
"src/input/dispatcher.js",
|
||||||
"src/render/cesium/cameraInput.js",
|
"src/render/cesium/cameraInput.js",
|
||||||
@@ -2347,6 +2362,7 @@
|
|||||||
"src/i18n/locales/zh-Hant.json",
|
"src/i18n/locales/zh-Hant.json",
|
||||||
"src/data/entityRecords.js",
|
"src/data/entityRecords.js",
|
||||||
"src/input/sensorPick.js",
|
"src/input/sensorPick.js",
|
||||||
|
"src/styles/styleStages.js",
|
||||||
"src/data/scenePick.js",
|
"src/data/scenePick.js",
|
||||||
"src/data/pickRegistry.js",
|
"src/data/pickRegistry.js",
|
||||||
"src/input/dispatcher.js",
|
"src/input/dispatcher.js",
|
||||||
@@ -2420,6 +2436,7 @@
|
|||||||
"src/i18n/locales/zh-Hant.json",
|
"src/i18n/locales/zh-Hant.json",
|
||||||
"src/data/scenePick.js",
|
"src/data/scenePick.js",
|
||||||
"src/input/sensorPick.js",
|
"src/input/sensorPick.js",
|
||||||
|
"src/styles/styleStages.js",
|
||||||
"src/data/pickRegistry.js",
|
"src/data/pickRegistry.js",
|
||||||
"src/input/dispatcher.js",
|
"src/input/dispatcher.js",
|
||||||
"src/render/cesium/cameraInput.js",
|
"src/render/cesium/cameraInput.js",
|
||||||
@@ -2475,6 +2492,7 @@
|
|||||||
"src/i18n/locales/zh-Hant.json",
|
"src/i18n/locales/zh-Hant.json",
|
||||||
"src/data/scenePick.js",
|
"src/data/scenePick.js",
|
||||||
"src/input/sensorPick.js",
|
"src/input/sensorPick.js",
|
||||||
|
"src/styles/styleStages.js",
|
||||||
"src/data/pickRegistry.js",
|
"src/data/pickRegistry.js",
|
||||||
"src/input/dispatcher.js",
|
"src/input/dispatcher.js",
|
||||||
"src/render/cesium/cameraInput.js",
|
"src/render/cesium/cameraInput.js",
|
||||||
@@ -3279,6 +3297,7 @@
|
|||||||
"src/ui/surfaceKeyboard.js",
|
"src/ui/surfaceKeyboard.js",
|
||||||
"src/ui/uiLifetime.js",
|
"src/ui/uiLifetime.js",
|
||||||
"src/ui/visualEffects.js",
|
"src/ui/visualEffects.js",
|
||||||
|
"src/styles/styleStages.js",
|
||||||
"src/ui/visualInput.js",
|
"src/ui/visualInput.js",
|
||||||
"src/ui/visualPresets.js",
|
"src/ui/visualPresets.js",
|
||||||
"src/ui/visualSettings.js",
|
"src/ui/visualSettings.js",
|
||||||
@@ -4308,6 +4327,7 @@
|
|||||||
"src/data/local_data/wof_neighborhoods/files.js",
|
"src/data/local_data/wof_neighborhoods/files.js",
|
||||||
"src/data/entityRecords.js",
|
"src/data/entityRecords.js",
|
||||||
"src/input/sensorPick.js",
|
"src/input/sensorPick.js",
|
||||||
|
"src/styles/styleStages.js",
|
||||||
"src/input/dispatcher.js",
|
"src/input/dispatcher.js",
|
||||||
"src/render/cesium/cameraInput.js",
|
"src/render/cesium/cameraInput.js",
|
||||||
"src/render/cesium/input.js",
|
"src/render/cesium/input.js",
|
||||||
@@ -4447,6 +4467,7 @@
|
|||||||
"src/render/cesium/ctrlWheelZoom.js",
|
"src/render/cesium/ctrlWheelZoom.js",
|
||||||
"src/render/input/router.js",
|
"src/render/input/router.js",
|
||||||
"src/input/sensorPick.js",
|
"src/input/sensorPick.js",
|
||||||
|
"src/styles/styleStages.js",
|
||||||
"src/data/inputOwnership.js",
|
"src/data/inputOwnership.js",
|
||||||
"src/data/pickRegistry.js",
|
"src/data/pickRegistry.js",
|
||||||
"src/data/scenePick.js"
|
"src/data/scenePick.js"
|
||||||
@@ -4552,9 +4573,11 @@
|
|||||||
"src/ui/rail/rangeValue.js",
|
"src/ui/rail/rangeValue.js",
|
||||||
"src/ui/uiLifetime.js",
|
"src/ui/uiLifetime.js",
|
||||||
"src/ui/visualEffects.js",
|
"src/ui/visualEffects.js",
|
||||||
|
"src/styles/styleStages.js",
|
||||||
"src/ui/visualInput.js",
|
"src/ui/visualInput.js",
|
||||||
"src/ui/visualPresets.js",
|
"src/ui/visualPresets.js",
|
||||||
"src/ui/visualSettings.js",
|
"src/ui/visualSettings.js",
|
||||||
|
"src/render/rendererOf.js",
|
||||||
"src/ui/stores/display.js",
|
"src/ui/stores/display.js",
|
||||||
"src/ui/stores/owners.js",
|
"src/ui/stores/owners.js",
|
||||||
"src/ui/vueRuntime.js",
|
"src/ui/vueRuntime.js",
|
||||||
@@ -4720,6 +4743,7 @@
|
|||||||
"src/data/inputOwnership.js",
|
"src/data/inputOwnership.js",
|
||||||
"src/data/scenePick.js",
|
"src/data/scenePick.js",
|
||||||
"src/input/sensorPick.js",
|
"src/input/sensorPick.js",
|
||||||
|
"src/styles/styleStages.js",
|
||||||
"src/data/pickRegistry.js",
|
"src/data/pickRegistry.js",
|
||||||
"src/input/dispatcher.js",
|
"src/input/dispatcher.js",
|
||||||
"src/render/cesium/follow.js",
|
"src/render/cesium/follow.js",
|
||||||
@@ -5072,6 +5096,7 @@
|
|||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
"src/i18n/locales/zh-Hant.json",
|
"src/i18n/locales/zh-Hant.json",
|
||||||
"src/input/sensorPick.js",
|
"src/input/sensorPick.js",
|
||||||
|
"src/styles/styleStages.js",
|
||||||
"src/input/dispatcher.js",
|
"src/input/dispatcher.js",
|
||||||
"src/render/cesium/cameraInput.js",
|
"src/render/cesium/cameraInput.js",
|
||||||
"src/render/cesium/input.js",
|
"src/render/cesium/input.js",
|
||||||
@@ -5132,6 +5157,7 @@
|
|||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
"src/i18n/locales/zh-Hant.json",
|
"src/i18n/locales/zh-Hant.json",
|
||||||
"src/input/sensorPick.js",
|
"src/input/sensorPick.js",
|
||||||
|
"src/styles/styleStages.js",
|
||||||
"src/data/scenePick.js",
|
"src/data/scenePick.js",
|
||||||
"src/data/pickRegistry.js",
|
"src/data/pickRegistry.js",
|
||||||
"src/input/dispatcher.js",
|
"src/input/dispatcher.js",
|
||||||
@@ -5206,6 +5232,7 @@
|
|||||||
"src/i18n/locales/zh-Hant.json",
|
"src/i18n/locales/zh-Hant.json",
|
||||||
"src/data/inputOwnership.js",
|
"src/data/inputOwnership.js",
|
||||||
"src/input/sensorPick.js",
|
"src/input/sensorPick.js",
|
||||||
|
"src/styles/styleStages.js",
|
||||||
"src/data/scenePick.js",
|
"src/data/scenePick.js",
|
||||||
"src/data/pickRegistry.js",
|
"src/data/pickRegistry.js",
|
||||||
"src/input/dispatcher.js",
|
"src/input/dispatcher.js",
|
||||||
@@ -5315,6 +5342,7 @@
|
|||||||
"src/data/inputOwnership.js",
|
"src/data/inputOwnership.js",
|
||||||
"src/data/scenePick.js",
|
"src/data/scenePick.js",
|
||||||
"src/input/sensorPick.js",
|
"src/input/sensorPick.js",
|
||||||
|
"src/styles/styleStages.js",
|
||||||
"src/data/pickRegistry.js",
|
"src/data/pickRegistry.js",
|
||||||
"src/input/dispatcher.js",
|
"src/input/dispatcher.js",
|
||||||
"src/render/cesium/follow.js",
|
"src/render/cesium/follow.js",
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ try {
|
|||||||
let animeOn = false;
|
let animeOn = false;
|
||||||
for (let i = 0; i < s.postProcessStages.length; i++) {
|
for (let i = 0; i < s.postProcessStages.length; i++) {
|
||||||
const stage = s.postProcessStages.get(i);
|
const stage = s.postProcessStages.get(i);
|
||||||
if (stage.name === 'godsEyeView_anime')
|
if (stage.name === 'effects_style_anime')
|
||||||
animeOn = stage.enabled && stage.uniforms.intensity > 0.99;
|
animeOn = stage.enabled && stage.uniforms.intensity > 0.99;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ try {
|
|||||||
result.bloom.intensityPct === 98 &&
|
result.bloom.intensityPct === 98 &&
|
||||||
snapshot.bloom.intensity === 98 &&
|
snapshot.bloom.intensity === 98 &&
|
||||||
manager._visualEffects.bloomIntensity === 98 &&
|
manager._visualEffects.bloomIntensity === 98 &&
|
||||||
manager._bloomStage.enabled
|
manager.viewer.scene.postProcessStages.bloom.enabled
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -183,7 +183,11 @@ try {
|
|||||||
result.sharpen.intensityPct === 61 &&
|
result.sharpen.intensityPct === 61 &&
|
||||||
manager.getVisualState().sharpen.intensity === 61 &&
|
manager.getVisualState().sharpen.intensity === 61 &&
|
||||||
manager._visualEffects.sharpenIntensity === 0.61 &&
|
manager._visualEffects.sharpenIntensity === 0.61 &&
|
||||||
Math.abs(manager._sharpenStage.uniforms.amount - 1.32) < 1e-9
|
Math.abs(
|
||||||
|
manager.viewer.scene.postProcessStages.getStageByName(
|
||||||
|
'effects_sharpen',
|
||||||
|
).uniforms.amount - 1.32,
|
||||||
|
) < 1e-9
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -199,9 +203,10 @@ try {
|
|||||||
const manager = window.__godsEyeView.styleManager;
|
const manager = window.__godsEyeView.styleManager;
|
||||||
const effects = manager._visualEffects;
|
const effects = manager._visualEffects;
|
||||||
manager.setStyle('retro');
|
manager.setStyle('retro');
|
||||||
const owned = [...Object.values(effects.stages), effects.sharpenStage];
|
|
||||||
const collection = manager.viewer.scene.postProcessStages;
|
const collection = manager.viewer.scene.postProcessStages;
|
||||||
const previousBloom = effects.previousBloom;
|
const owned = Object.keys(effects.stages).map((name) =>
|
||||||
|
collection.getStageByName(`effects_style_${name}`),
|
||||||
|
);
|
||||||
const pending = manager.dispose();
|
const pending = manager.dispose();
|
||||||
const stoppedBeforeAwait =
|
const stoppedBeforeAwait =
|
||||||
effects.stopped &&
|
effects.stopped &&
|
||||||
@@ -210,12 +215,15 @@ try {
|
|||||||
await pending;
|
await pending;
|
||||||
return {
|
return {
|
||||||
stoppedBeforeAwait,
|
stoppedBeforeAwait,
|
||||||
removed: owned.every((stage) => !collection.contains(stage)),
|
removed:
|
||||||
restored:
|
owned.every(Boolean) &&
|
||||||
effects.bloomStage.enabled === previousBloom.enabled &&
|
Object.keys(effects.stages).every(
|
||||||
Object.entries(previousBloom.uniforms).every(
|
(name) => !collection.getStageByName(`effects_style_${name}`),
|
||||||
([name, value]) => effects.bloomStage.uniforms[name] === value,
|
|
||||||
),
|
),
|
||||||
|
// The renderer gives the scene's bloom back when it goes.
|
||||||
|
restored:
|
||||||
|
!collection.bloom.enabled &&
|
||||||
|
!collection.getStageByName('effects_sharpen')?.enabled,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
check(
|
check(
|
||||||
@@ -223,7 +231,7 @@ try {
|
|||||||
teardown.stoppedBeforeAwait,
|
teardown.stoppedBeforeAwait,
|
||||||
);
|
);
|
||||||
check(
|
check(
|
||||||
'UI teardown removes owned stages and restores borrowed bloom',
|
'UI teardown removes its styles and turns bloom and sharpen off',
|
||||||
teardown.removed && teardown.restored,
|
teardown.removed && teardown.restored,
|
||||||
);
|
);
|
||||||
check(
|
check(
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ try {
|
|||||||
manager._bloomSlider.focus();
|
manager._bloomSlider.focus();
|
||||||
return {
|
return {
|
||||||
value: Number(manager._bloomSlider.value),
|
value: Number(manager._bloomSlider.value),
|
||||||
contrast: manager._bloomStage.uniforms.contrast,
|
contrast: manager.viewer.scene.postProcessStages.bloom.uniforms.contrast,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
await page.keyboard.press('ArrowRight');
|
await page.keyboard.press('ArrowRight');
|
||||||
@@ -160,7 +160,8 @@ try {
|
|||||||
const manager = window.__godsEyeView.styleManager;
|
const manager = window.__godsEyeView.styleManager;
|
||||||
return (
|
return (
|
||||||
Number(manager._bloomSlider.value) > before.value &&
|
Number(manager._bloomSlider.value) > before.value &&
|
||||||
manager._bloomStage.uniforms.contrast < before.contrast
|
manager.viewer.scene.postProcessStages.bloom.uniforms.contrast <
|
||||||
|
before.contrast
|
||||||
);
|
);
|
||||||
}, displayBefore),
|
}, displayBefore),
|
||||||
);
|
);
|
||||||
@@ -267,13 +268,15 @@ try {
|
|||||||
const manager = window.__godsEyeView.styleManager;
|
const manager = window.__godsEyeView.styleManager;
|
||||||
manager._displayBindings._detachDisplayControls();
|
manager._displayBindings._detachDisplayControls();
|
||||||
const before = manager.bloomEnabled;
|
const before = manager.bloomEnabled;
|
||||||
const contrast = manager._bloomStage.uniforms.contrast;
|
const contrast =
|
||||||
|
manager.viewer.scene.postProcessStages.bloom.uniforms.contrast;
|
||||||
manager._bloomBtn.click();
|
manager._bloomBtn.click();
|
||||||
manager._bloomSlider.value = '15';
|
manager._bloomSlider.value = '15';
|
||||||
manager._bloomSlider.dispatchEvent(new Event('input', { bubbles: true }));
|
manager._bloomSlider.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
return (
|
return (
|
||||||
manager.bloomEnabled === before &&
|
manager.bloomEnabled === before &&
|
||||||
manager._bloomStage.uniforms.contrast === contrast
|
manager.viewer.scene.postProcessStages.bloom.uniforms.contrast ===
|
||||||
|
contrast
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -208,6 +208,7 @@ test('consumer build includes only infrastructure code and resolves assets under
|
|||||||
'router.js',
|
'router.js',
|
||||||
'scenePick.js',
|
'scenePick.js',
|
||||||
'sensorPick.js',
|
'sensorPick.js',
|
||||||
|
'styleStages.js',
|
||||||
'vector.js',
|
'vector.js',
|
||||||
]);
|
]);
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
fakeInput,
|
fakeInput,
|
||||||
} from './testDoubles.mjs';
|
} from './testDoubles.mjs';
|
||||||
import { sensorPickPosition } from './sensorPick.js';
|
import { sensorPickPosition } from './sensorPick.js';
|
||||||
|
import { registerStyleStages } from '../styles/styleStages.js';
|
||||||
import { claimPointer, releasePointer } from '../data/inputOwnership.js';
|
import { claimPointer, releasePointer } from '../data/inputOwnership.js';
|
||||||
import {
|
import {
|
||||||
registerPickOwner,
|
registerPickOwner,
|
||||||
@@ -376,11 +377,8 @@ test('sensor styles move the pick to where the picture came from', () => {
|
|||||||
const stage = { enabled: true, uniforms: { intensity: 1, pixelation: 4 } };
|
const stage = { enabled: true, uniforms: { intensity: 1, pixelation: 4 } };
|
||||||
const scene = {
|
const scene = {
|
||||||
canvas: { clientWidth: 400, clientHeight: 200, width: 400, height: 200 },
|
canvas: { clientWidth: 400, clientHeight: 200, width: 400, height: 200 },
|
||||||
postProcessStages: {
|
|
||||||
getStageByName: (name) =>
|
|
||||||
name === 'godsEyeView_surveillance' ? stage : undefined,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
registerStyleStages(scene, { surveillance: stage });
|
||||||
const click = { x: 300, y: 50 };
|
const click = { x: 300, y: 50 };
|
||||||
const mapped = sensorPickPosition(scene, click);
|
const mapped = sensorPickPosition(scene, click);
|
||||||
assert.notDeepEqual([mapped.x, mapped.y], [300, 50]);
|
assert.notDeepEqual([mapped.x, mapped.y], [300, 50]);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
* distortion and quantization first.
|
* distortion and quantization first.
|
||||||
*/
|
*/
|
||||||
import * as Cesium from 'cesium';
|
import * as Cesium from 'cesium';
|
||||||
|
import { styleStagesOf } from '../styles/styleStages.js';
|
||||||
|
|
||||||
const SENSOR_STYLES = ['thermal', 'surveillance'];
|
const SENSOR_STYLES = ['thermal', 'surveillance'];
|
||||||
|
|
||||||
@@ -18,10 +19,10 @@ const SENSOR_STYLES = ['thermal', 'surveillance'];
|
|||||||
*/
|
*/
|
||||||
export function sensorPickPosition(scene, position) {
|
export function sensorPickPosition(scene, position) {
|
||||||
const out = new Cesium.Cartesian2(position.x, position.y);
|
const out = new Cesium.Cartesian2(position.x, position.y);
|
||||||
const stages = scene.postProcessStages;
|
const stages = styleStagesOf(scene);
|
||||||
if (typeof stages?.getStageByName !== 'function') return out;
|
if (!stages) return out;
|
||||||
for (const style of SENSOR_STYLES) {
|
for (const style of SENSOR_STYLES) {
|
||||||
const stage = stages.getStageByName(`godsEyeView_${style}`);
|
const stage = stages[style];
|
||||||
if (!stage?.enabled) continue;
|
if (!stage?.enabled) continue;
|
||||||
const intensity = stage.uniforms.intensity;
|
const intensity = stage.uniforms.intensity;
|
||||||
const canvas = scene.canvas,
|
const canvas = scene.canvas,
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import {
|
|||||||
} from '../../renderGovernor.js';
|
} from '../../renderGovernor.js';
|
||||||
import { fakeInput } from '../../input/testDoubles.mjs';
|
import { fakeInput } from '../../input/testDoubles.mjs';
|
||||||
import { framePortFor } from '../../testSupport/fakeFramePort.mjs';
|
import { framePortFor } from '../../testSupport/fakeFramePort.mjs';
|
||||||
|
import { registerStyleStages } from '../../styles/styleStages.js';
|
||||||
|
|
||||||
const BOSTON = { lat: 42.3601, lon: -71.0589 };
|
const BOSTON = { lat: 42.3601, lon: -71.0589 };
|
||||||
|
|
||||||
@@ -3446,12 +3447,16 @@ test('sensor clicks map displayed pixels into the pre-shader pick buffer', async
|
|||||||
};
|
};
|
||||||
const uniforms = { intensity: 1, pixelation: 1 };
|
const uniforms = { intensity: 1, pixelation: 1 };
|
||||||
let preset = 'surveillance';
|
let preset = 'surveillance';
|
||||||
scene.postProcessStages = {
|
const stage = (name) =>
|
||||||
getStageByName: (name) =>
|
name === preset ? { enabled: true, uniforms } : undefined;
|
||||||
name === `godsEyeView_${preset}`
|
registerStyleStages(scene, {
|
||||||
? { enabled: true, uniforms }
|
get surveillance() {
|
||||||
: undefined,
|
return stage('surveillance');
|
||||||
};
|
},
|
||||||
|
get thermal() {
|
||||||
|
return stage('thermal');
|
||||||
|
},
|
||||||
|
});
|
||||||
const click = (position) => app.input.click(position);
|
const click = (position) => app.input.click(position);
|
||||||
click(new Cesium.Cartesian2(1440, 540));
|
click(new Cesium.Cartesian2(1440, 540));
|
||||||
assert.ok(Math.abs(actual.x - 1472) < 1e-6);
|
assert.ok(Math.abs(actual.x - 1472) < 1e-6);
|
||||||
|
|||||||
@@ -27,16 +27,13 @@ import { cesiumCapabilities, createCesiumPorts } from './ports.js';
|
|||||||
* The members a transitional renderer has today.
|
* The members a transitional renderer has today.
|
||||||
* @typedef {Pick<import('../contract/renderer').Renderer,
|
* @typedef {Pick<import('../contract/renderer').Renderer,
|
||||||
* 'id' | 'contractVersion' | 'capabilities' | 'canvas' | 'frame' | 'draw'
|
* 'id' | 'contractVersion' | 'capabilities' | 'canvas' | 'frame' | 'draw'
|
||||||
* | 'camera' | 'query' | 'input' | 'capture' | 'credits' | 'on'
|
* | 'camera' | 'query' | 'input' | 'effects' | 'capture' | 'credits'
|
||||||
* | 'resize' | 'destroy'>}
|
* | 'on' | 'resize' | 'destroy'>}
|
||||||
* TransitionalRenderer
|
* TransitionalRenderer
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const viewers = new WeakMap();
|
const viewers = new WeakMap();
|
||||||
|
|
||||||
/** Capabilities of ports the application renderer does not have yet. */
|
|
||||||
const NOT_YET = ['styles', 'bloom', 'sharpen'];
|
|
||||||
|
|
||||||
/** A Set no one can change once built, so `capabilities` stays true. */
|
/** A Set no one can change once built, so `capabilities` stays true. */
|
||||||
export class ReadonlySet extends Set {
|
export class ReadonlySet extends Set {
|
||||||
#sealed;
|
#sealed;
|
||||||
@@ -94,11 +91,7 @@ export function createCesiumRendererPlugin({
|
|||||||
let capabilities;
|
let capabilities;
|
||||||
try {
|
try {
|
||||||
viewer = createViewer({ container, creditContainer });
|
viewer = createViewer({ container, creditContainer });
|
||||||
// Effects are not the application's yet: its styles still run
|
capabilities = new ReadonlySet(cesiumCapabilities(viewer.scene));
|
||||||
// through stages of their own.
|
|
||||||
capabilities = new ReadonlySet(
|
|
||||||
cesiumCapabilities(viewer.scene).filter((c) => !NOT_YET.includes(c)),
|
|
||||||
);
|
|
||||||
ports = createCesiumPorts(viewer, { capabilities });
|
ports = createCesiumPorts(viewer, { capabilities });
|
||||||
input = createCesiumInput(viewer, {
|
input = createCesiumInput(viewer, {
|
||||||
query: ports.query,
|
query: ports.query,
|
||||||
@@ -121,6 +114,7 @@ export function createCesiumRendererPlugin({
|
|||||||
camera: ports.camera,
|
camera: ports.camera,
|
||||||
query: ports.query,
|
query: ports.query,
|
||||||
input: input.port,
|
input: input.port,
|
||||||
|
effects: ports.effects,
|
||||||
capture: ports.capture,
|
capture: ports.capture,
|
||||||
credits: ports.credits,
|
credits: ports.credits,
|
||||||
on: ports.on,
|
on: ports.on,
|
||||||
|
|||||||
@@ -337,7 +337,6 @@ const EXPECTED_MISSING = [
|
|||||||
'draw.clampState',
|
'draw.clampState',
|
||||||
'draw.on',
|
'draw.on',
|
||||||
'frame.setTime',
|
'frame.setTime',
|
||||||
'renderer.effects',
|
|
||||||
'renderer.globe',
|
'renderer.globe',
|
||||||
'renderer.quality',
|
'renderer.quality',
|
||||||
'renderer.readPixels',
|
'renderer.readPixels',
|
||||||
|
|||||||
@@ -224,7 +224,6 @@
|
|||||||
"src/ui/locationNavigation.js",
|
"src/ui/locationNavigation.js",
|
||||||
"src/ui/navigationController.js",
|
"src/ui/navigationController.js",
|
||||||
"src/ui/shareFollow.js",
|
"src/ui/shareFollow.js",
|
||||||
"src/ui/visualEffects.js",
|
|
||||||
"src/voice/gevActions.js",
|
"src/voice/gevActions.js",
|
||||||
"src/voice/pointerPick.js"
|
"src/voice/pointerPick.js"
|
||||||
]
|
]
|
||||||
@@ -37,9 +37,13 @@ const INJECTED = {
|
|||||||
/**
|
/**
|
||||||
* Modules whose use of the bridge holds up without the factory: with no
|
* Modules whose use of the bridge holds up without the factory: with no
|
||||||
* port they read Cesium's `trackedEntity` instead, which is all an unbound
|
* port they read Cesium's `trackedEntity` instead, which is all an unbound
|
||||||
* viewer can follow by.
|
* viewer can follow by; visual settings ask for the renderer's effects
|
||||||
|
* only, never a camera, and keep their own records without one.
|
||||||
*/
|
*/
|
||||||
const FALLS_BACK = new Set(['src/data/followed.js']);
|
const FALLS_BACK = new Set([
|
||||||
|
'src/data/followed.js',
|
||||||
|
'src/ui/visualSettings.js',
|
||||||
|
]);
|
||||||
|
|
||||||
const pkg = JSON.parse(readFileSync(path.join(root, 'package.json'), 'utf8'));
|
const pkg = JSON.parse(readFileSync(path.join(root, 'package.json'), 'utf8'));
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* @module styles/styleStages
|
||||||
|
* @description Core's record of each visual style on a scene: its
|
||||||
|
* intensity, parameter values and whether it draws, as the application set
|
||||||
|
* them. Code that must follow a style's picture (sensor picks) reads it here
|
||||||
|
* rather than asking the renderer.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const records = new WeakMap();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} scene
|
||||||
|
* @param {Record<string, {enabled: boolean, uniforms: Record<string, number>}>} stages
|
||||||
|
* @returns {() => void} Forgets them, if they are still the scene's.
|
||||||
|
*/
|
||||||
|
export function registerStyleStages(scene, stages) {
|
||||||
|
if (!scene || typeof scene !== 'object') return () => {};
|
||||||
|
records.set(scene, stages);
|
||||||
|
return () => {
|
||||||
|
if (records.get(scene) === stages) records.delete(scene);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} scene
|
||||||
|
* @returns {Record<string, {enabled: boolean, uniforms: Record<string, number>}> | null}
|
||||||
|
*/
|
||||||
|
export const styleStagesOf = (scene) =>
|
||||||
|
(scene && typeof scene === 'object' && records.get(scene)) || null;
|
||||||
@@ -501,14 +501,6 @@ export class ShellFacade {
|
|||||||
return this._visualSettings.sharpenEnabled;
|
return this._visualSettings.sharpenEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
get _bloomStage() {
|
|
||||||
return this._visualEffects.bloomStage;
|
|
||||||
}
|
|
||||||
|
|
||||||
get _sharpenStage() {
|
|
||||||
return this._visualEffects.sharpenStage;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Settle only the search generation that still owns the shared input UI. */
|
/** Settle only the search generation that still owns the shared input UI. */
|
||||||
_settleLocationSearchUi(generation, options) {
|
_settleLocationSearchUi(generation, options) {
|
||||||
return this._navigation._settleLocationSearchUi(...arguments);
|
return this._navigation._settleLocationSearchUi(...arguments);
|
||||||
|
|||||||
+135
-103
@@ -1,22 +1,51 @@
|
|||||||
import { PostProcessStage } from 'cesium';
|
|
||||||
import {
|
import {
|
||||||
bloomStrengthFromIntensity,
|
bloomStrengthFromIntensity,
|
||||||
clampBloomIntensity,
|
clampBloomIntensity,
|
||||||
BLOOM_INTENSITY_DEFAULT,
|
BLOOM_INTENSITY_DEFAULT,
|
||||||
} from '../bloom.js';
|
} from '../bloom.js';
|
||||||
import {
|
import { registerStyleStages } from '../styles/styleStages.js';
|
||||||
STYLES,
|
import { STYLES, TRANSITION_DURATION_MS } from './visualPresets.js';
|
||||||
SHARPEN_SHADER,
|
|
||||||
TRANSITION_DURATION_MS,
|
|
||||||
} from './visualPresets.js';
|
|
||||||
|
|
||||||
/** Own the post-process stages and their animation, without DOM dependencies. */
|
/** Below this a style draws nothing, so it runs no pass. */
|
||||||
|
const OFF = 0.001;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A style as the renderer contract defines it
|
||||||
|
* (`src/render/contract/effects.d.ts`).
|
||||||
|
* @param {string} name
|
||||||
|
* @param {{fragmentShader: string, uniforms?: Record<string, {default: number, min: number, max: number}>}} shader
|
||||||
|
*/
|
||||||
|
export function styleDefinition(name, shader) {
|
||||||
|
return {
|
||||||
|
id: name,
|
||||||
|
params: Object.entries(shader.uniforms || {}).map(([key, meta]) => ({
|
||||||
|
key,
|
||||||
|
min: meta.min,
|
||||||
|
max: meta.max,
|
||||||
|
default: meta.default,
|
||||||
|
})),
|
||||||
|
animated: shader.fragmentShader.includes('uniform float time'),
|
||||||
|
shader: { glsl: shader.fragmentShader },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Core's records of the visual styles, bloom and sharpen, and their
|
||||||
|
* crossfades, driven onto the renderer's effects port. Each style's record
|
||||||
|
* (`stages[name]`) holds its intensity and parameter values in `uniforms`,
|
||||||
|
* written through to the renderer, and `enabled`, whether it draws. The
|
||||||
|
* renderer animates `time` for an animated style; this keeps only the
|
||||||
|
* crossfades' frames. No DOM dependencies.
|
||||||
|
*/
|
||||||
export class VisualEffects {
|
export class VisualEffects {
|
||||||
/**
|
/**
|
||||||
* @param {object} options - Viewer, render ownership callbacks and optional clocks.
|
* @param {object} options - The renderer's effects port (null: records
|
||||||
* Construction is inert; initStyles/initPostProcess retain startup ordering.
|
* only), the viewer whose sky the anime style dims, render ownership
|
||||||
|
* callbacks and optional clocks. Construction is inert;
|
||||||
|
* initStyles/initPostProcess retain startup ordering.
|
||||||
*/
|
*/
|
||||||
constructor({
|
constructor({
|
||||||
|
effects = null,
|
||||||
viewer,
|
viewer,
|
||||||
requestRender,
|
requestRender,
|
||||||
holdRender,
|
holdRender,
|
||||||
@@ -24,9 +53,8 @@ export class VisualEffects {
|
|||||||
requestFrame = (callback) => requestAnimationFrame(callback),
|
requestFrame = (callback) => requestAnimationFrame(callback),
|
||||||
cancelFrame = (id) => cancelAnimationFrame(id),
|
cancelFrame = (id) => cancelAnimationFrame(id),
|
||||||
now = () => performance.now(),
|
now = () => performance.now(),
|
||||||
wallNow = () => Date.now(),
|
|
||||||
createStage = (options) => new PostProcessStage(options),
|
|
||||||
}) {
|
}) {
|
||||||
|
this.effects = effects;
|
||||||
this.viewer = viewer;
|
this.viewer = viewer;
|
||||||
this.requestRender = requestRender;
|
this.requestRender = requestRender;
|
||||||
this.holdRender = holdRender;
|
this.holdRender = holdRender;
|
||||||
@@ -34,93 +62,113 @@ export class VisualEffects {
|
|||||||
this.requestFrame = requestFrame;
|
this.requestFrame = requestFrame;
|
||||||
this.cancelFrame = cancelFrame;
|
this.cancelFrame = cancelFrame;
|
||||||
this.now = now;
|
this.now = now;
|
||||||
this.wallNow = wallNow;
|
|
||||||
this.createStage = createStage;
|
|
||||||
this.stages = {};
|
this.stages = {};
|
||||||
this.transitions = new Map();
|
this.transitions = new Map();
|
||||||
this.startTime = wallNow();
|
|
||||||
this.bloomEnabled = false;
|
this.bloomEnabled = false;
|
||||||
this.sharpenEnabled = false;
|
this.sharpenEnabled = false;
|
||||||
this.bloomIntensity = BLOOM_INTENSITY_DEFAULT;
|
this.bloomIntensity = BLOOM_INTENSITY_DEFAULT;
|
||||||
this.sharpenIntensity = 0.49;
|
this.sharpenIntensity = 0.49;
|
||||||
this.bloomStage = null;
|
this.postProcessReady = false;
|
||||||
this.sharpenStage = null;
|
|
||||||
this.frameId = null;
|
this.frameId = null;
|
||||||
this.stopped = false;
|
this.stopped = false;
|
||||||
this.destroyed = false;
|
this.destroyed = false;
|
||||||
this.stageEntries = [];
|
this.stageEntries = [];
|
||||||
this.previousBloom = null;
|
// What the renderer draws: its style, and the styles it replaced while
|
||||||
|
// they fade out (`EffectsPort.setStyle`).
|
||||||
|
this.rendererStyle = null;
|
||||||
|
this.drawn = new Set();
|
||||||
|
this.offs = [];
|
||||||
|
this.forgetStages = () => {};
|
||||||
}
|
}
|
||||||
|
|
||||||
initStyles() {
|
initStyles() {
|
||||||
if (this.stopped || this.stageEntries.length) return;
|
if (this.stopped || this.stageEntries.length) return;
|
||||||
for (const [name, shader] of Object.entries(STYLES)) {
|
for (const [name, shader] of Object.entries(STYLES)) {
|
||||||
const uniforms = { intensity: 0.0 };
|
const definition = styleDefinition(name, shader);
|
||||||
if (shader.fragmentShader.includes('uniform float time'))
|
const off = this.effects?.defineStyle(definition);
|
||||||
uniforms.time = 0.0;
|
if (off) this.offs.push(off);
|
||||||
for (const [name, meta] of Object.entries(shader.uniforms || {}))
|
this.effects?.setStyleIntensity(name, 0);
|
||||||
uniforms[name] = meta.default;
|
this.stages[name] = this.styleRecord(name, definition);
|
||||||
const stage = this.createStage({
|
|
||||||
name: `godsEyeView_${name}`,
|
|
||||||
fragmentShader: shader.fragmentShader,
|
|
||||||
uniforms,
|
|
||||||
});
|
|
||||||
stage.enabled = false;
|
|
||||||
this.viewer.scene.postProcessStages.add(stage);
|
|
||||||
this.stages[name] = stage;
|
|
||||||
}
|
}
|
||||||
this.stageEntries = Object.entries(this.stages);
|
this.stageEntries = Object.entries(this.stages);
|
||||||
|
this.forgetStages = registerStyleStages(this.viewer?.scene, this.stages);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A style's record, its uniforms written through to the renderer. */
|
||||||
|
styleRecord(name, definition) {
|
||||||
|
let intensity = 0;
|
||||||
|
const values = Object.fromEntries(
|
||||||
|
definition.params.map((p) => [p.key, p.default]),
|
||||||
|
);
|
||||||
|
const uniforms = {};
|
||||||
|
Object.defineProperty(uniforms, 'intensity', {
|
||||||
|
enumerable: true,
|
||||||
|
get: () => intensity,
|
||||||
|
set: (value) => {
|
||||||
|
intensity = value;
|
||||||
|
this.pushIntensity(name, value);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
for (const { key } of definition.params)
|
||||||
|
Object.defineProperty(uniforms, key, {
|
||||||
|
enumerable: true,
|
||||||
|
get: () => values[key],
|
||||||
|
set: (value) => {
|
||||||
|
values[key] = value;
|
||||||
|
if (!this.destroyed)
|
||||||
|
this.effects?.setStyleParams(name, { [key]: value });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return { name, enabled: false, uniforms };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A style's intensity on the renderer. Raised from nothing, it becomes
|
||||||
|
* the renderer's style, so it draws; the style it replaces fades out
|
||||||
|
* there at the intensity this keeps writing, as every stage with an
|
||||||
|
* intensity drew before.
|
||||||
|
*/
|
||||||
|
pushIntensity(name, value) {
|
||||||
|
if (this.destroyed || !this.effects) return;
|
||||||
|
this.effects.setStyleIntensity(name, value);
|
||||||
|
// Once stopped, a write only tunes what already draws.
|
||||||
|
if (this.stopped) return;
|
||||||
|
if (value > OFF) {
|
||||||
|
if (!this.drawn.has(name)) {
|
||||||
|
const previous = this.rendererStyle;
|
||||||
|
if (previous && !(this.stages[previous]?.uniforms.intensity > OFF))
|
||||||
|
this.drawn.delete(previous);
|
||||||
|
this.effects.setStyle(name);
|
||||||
|
this.rendererStyle = name;
|
||||||
|
this.drawn.add(name);
|
||||||
|
}
|
||||||
|
} else if (name !== this.rendererStyle) {
|
||||||
|
this.drawn.delete(name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initPostProcess(sharpenIntensity = this.sharpenIntensity) {
|
initPostProcess(sharpenIntensity = this.sharpenIntensity) {
|
||||||
if (this.stopped || this.sharpenStage) return;
|
if (this.stopped || this.postProcessReady) return;
|
||||||
this.bloomStage = this.viewer.scene.postProcessStages.bloom;
|
this.postProcessReady = true;
|
||||||
const names = [
|
this.effects?.bloom({
|
||||||
'glowOnly',
|
enabled: false,
|
||||||
'contrast',
|
strength: bloomStrengthFromIntensity(this.bloomIntensity),
|
||||||
'brightness',
|
|
||||||
'delta',
|
|
||||||
'sigma',
|
|
||||||
'stepSize',
|
|
||||||
];
|
|
||||||
this.previousBloom = {
|
|
||||||
enabled: this.bloomStage.enabled,
|
|
||||||
uniforms: Object.fromEntries(
|
|
||||||
names.map((name) => [name, this.bloomStage.uniforms[name]]),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
this.bloomStage.enabled = false;
|
|
||||||
Object.assign(this.bloomStage.uniforms, {
|
|
||||||
glowOnly: false,
|
|
||||||
contrast: 256.0,
|
|
||||||
brightness: -0.35,
|
|
||||||
delta: 0.25,
|
|
||||||
sigma: 0.35,
|
|
||||||
stepSize: 1.0,
|
|
||||||
});
|
});
|
||||||
this.sharpenStage = this.createStage({
|
this.effects?.sharpen({ enabled: false, amount: sharpenIntensity });
|
||||||
name: 'godsEyeView_sharpen',
|
|
||||||
fragmentShader: SHARPEN_SHADER,
|
|
||||||
uniforms: { amount: 1.3 },
|
|
||||||
});
|
|
||||||
this.sharpenStage.enabled = false;
|
|
||||||
this.viewer.scene.postProcessStages.add(this.sharpenStage);
|
|
||||||
this.applySharpenIntensity(sharpenIntensity);
|
this.applySharpenIntensity(sharpenIntensity);
|
||||||
}
|
}
|
||||||
|
|
||||||
setStageIntensity(stage, value) {
|
setStageIntensity(stage, value) {
|
||||||
if (this.stopped || !stage) return;
|
if (this.stopped || !stage) return;
|
||||||
stage.uniforms.intensity = value;
|
stage.uniforms.intensity = value;
|
||||||
stage.enabled = value > 0.001;
|
stage.enabled = value > OFF;
|
||||||
// Anime's bands turn the faint Milky Way into blotches, and depth cannot
|
// Anime's bands turn the faint Milky Way into blotches, and depth cannot
|
||||||
// tell the sky from far geometry, so the glow fades with the style.
|
// tell the sky from far geometry, so the glow fades with the style.
|
||||||
if (
|
if (
|
||||||
stage === this.stages.anime &&
|
stage === this.stages.anime &&
|
||||||
this.viewer.scene.skyBox?.milkyWayVisibility !== undefined
|
this.viewer?.scene?.skyBox?.milkyWayVisibility !== undefined
|
||||||
)
|
)
|
||||||
this.viewer.scene.skyBox.milkyWayVisibility = 1 - value;
|
this.viewer.scene.skyBox.milkyWayVisibility = 1 - value;
|
||||||
if (stage.enabled && stage.uniforms.time !== undefined)
|
|
||||||
this.startAnimationLoop();
|
|
||||||
this.requestRender('style-stage');
|
this.requestRender('style-stage');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,26 +178,19 @@ export class VisualEffects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
syncBloomEnabled() {
|
syncBloomEnabled() {
|
||||||
if (this.stopped || !this.bloomStage) return;
|
if (this.stopped || !this.postProcessReady) return;
|
||||||
this.bloomStage.enabled =
|
this.effects?.bloom({ enabled: this.bloomEnabled });
|
||||||
this.bloomEnabled &&
|
|
||||||
bloomStrengthFromIntensity(this.bloomIntensity) > 0.06;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
applyBloomIntensity(intensity) {
|
applyBloomIntensity(intensity) {
|
||||||
if (this.stopped) return;
|
if (this.stopped) return;
|
||||||
this.bloomIntensity = clampBloomIntensity(intensity);
|
this.bloomIntensity = clampBloomIntensity(intensity);
|
||||||
this.requestRender('bloom');
|
this.requestRender('bloom');
|
||||||
if (!this.bloomStage) return;
|
if (!this.postProcessReady) return;
|
||||||
const rawStrength = bloomStrengthFromIntensity(this.bloomIntensity);
|
this.effects?.bloom({
|
||||||
const strength = rawStrength <= 0.06 ? 0.0 : (rawStrength - 0.06) / 0.94;
|
enabled: this.bloomEnabled,
|
||||||
const eased = strength * strength * (3.0 - 2.0 * strength);
|
strength: bloomStrengthFromIntensity(this.bloomIntensity),
|
||||||
this.bloomStage.uniforms.contrast = 255.0 - eased * 168.0;
|
});
|
||||||
this.bloomStage.uniforms.brightness = -0.5 + eased * 0.36;
|
|
||||||
this.bloomStage.uniforms.sigma = 0.28 + eased * 6.3;
|
|
||||||
this.bloomStage.uniforms.delta = 0.2 + eased * 2.25;
|
|
||||||
this.bloomStage.uniforms.stepSize = 1.0 + eased * 1.25;
|
|
||||||
this.syncBloomEnabled();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setBloomEnabled(enabled) {
|
setBloomEnabled(enabled) {
|
||||||
@@ -162,20 +203,18 @@ export class VisualEffects {
|
|||||||
/**
|
/**
|
||||||
* Sharpen runs only when it is on and its slider is above 0, as the style
|
* Sharpen runs only when it is on and its slider is above 0, as the style
|
||||||
* stages do: the pass costs the same at any amount (~1.5 ms GPU per frame
|
* stages do: the pass costs the same at any amount (~1.5 ms GPU per frame
|
||||||
* on the Arc, 2026-10-24), and `0.1 + value * 2` still sharpened at 0.
|
* on the Arc, 2026-10-24), and its gain still sharpened at 0.
|
||||||
*/
|
*/
|
||||||
syncSharpenEnabled() {
|
syncSharpenEnabled() {
|
||||||
if (this.stopped || !this.sharpenStage) return;
|
if (this.stopped || !this.postProcessReady) return;
|
||||||
this.sharpenStage.enabled =
|
this.effects?.sharpen({ enabled: this.sharpenEnabled });
|
||||||
this.sharpenEnabled && this.sharpenIntensity > 0.001;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
applySharpenIntensity(value) {
|
applySharpenIntensity(value) {
|
||||||
if (this.stopped) return;
|
if (this.stopped) return;
|
||||||
this.sharpenIntensity = value;
|
this.sharpenIntensity = value;
|
||||||
if (this.sharpenStage)
|
if (this.postProcessReady)
|
||||||
this.sharpenStage.uniforms.amount = 0.1 + value * 2.0;
|
this.effects?.sharpen({ enabled: this.sharpenEnabled, amount: value });
|
||||||
this.syncSharpenEnabled();
|
|
||||||
this.requestRender('sharpen');
|
this.requestRender('sharpen');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,12 +231,12 @@ export class VisualEffects {
|
|||||||
this.startAnimationLoop();
|
this.startAnimationLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Steps the crossfades each frame, holding frames while one runs. */
|
||||||
startAnimationLoop() {
|
startAnimationLoop() {
|
||||||
if (this.stopped || this.frameId !== null) return;
|
if (this.stopped || this.frameId !== null) return;
|
||||||
const update = () => {
|
const update = () => {
|
||||||
if (this.stopped) return;
|
if (this.stopped) return;
|
||||||
const now = this.now();
|
const now = this.now();
|
||||||
const elapsedSec = (this.wallNow() - this.startTime) / 1000.0;
|
|
||||||
for (const [name, transition] of this.transitions) {
|
for (const [name, transition] of this.transitions) {
|
||||||
const t = Math.min(
|
const t = Math.min(
|
||||||
(now - transition.start) / TRANSITION_DURATION_MS,
|
(now - transition.start) / TRANSITION_DURATION_MS,
|
||||||
@@ -213,14 +252,7 @@ export class VisualEffects {
|
|||||||
this.transitions.delete(name);
|
this.transitions.delete(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let animatedStageVisible = false;
|
const needed = this.transitions.size > 0;
|
||||||
for (const [, stage] of this.stageEntries) {
|
|
||||||
if (stage.enabled && stage.uniforms.time !== undefined) {
|
|
||||||
stage.uniforms.time = elapsedSec;
|
|
||||||
if (stage.uniforms.intensity > 0.001) animatedStageVisible = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const needed = this.transitions.size > 0 || animatedStageVisible;
|
|
||||||
if (needed) this.holdRender('style-anim');
|
if (needed) this.holdRender('style-anim');
|
||||||
else this.releaseRender('style-anim');
|
else this.releaseRender('style-anim');
|
||||||
this.frameId = needed ? this.requestFrame(update) : null;
|
this.frameId = needed ? this.requestFrame(update) : null;
|
||||||
@@ -228,7 +260,7 @@ export class VisualEffects {
|
|||||||
this.frameId = this.requestFrame(update);
|
this.frameId = this.requestFrame(update);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Revoke animation synchronously while callers finish releasing the stages. */
|
/** Revoke animation synchronously while callers finish releasing the styles. */
|
||||||
stop() {
|
stop() {
|
||||||
if (this.stopped) return;
|
if (this.stopped) return;
|
||||||
this.stopped = true;
|
this.stopped = true;
|
||||||
@@ -238,17 +270,17 @@ export class VisualEffects {
|
|||||||
this.transitions.clear();
|
this.transitions.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Remove owned stages and restore the borrowed bloom stage after callers release it. */
|
/** Remove the styles from the renderer and turn bloom and sharpen off. */
|
||||||
destroy() {
|
destroy() {
|
||||||
if (this.destroyed) return;
|
if (this.destroyed) return;
|
||||||
this.stop();
|
this.stop();
|
||||||
this.destroyed = true;
|
this.destroyed = true;
|
||||||
const stages = this.viewer.scene.postProcessStages;
|
this.forgetStages();
|
||||||
for (const [, stage] of this.stageEntries) stages.remove(stage);
|
for (const off of this.offs) off();
|
||||||
if (this.sharpenStage) stages.remove(this.sharpenStage);
|
this.offs = [];
|
||||||
if (this.bloomStage && this.previousBloom) {
|
if (this.postProcessReady) {
|
||||||
Object.assign(this.bloomStage.uniforms, this.previousBloom.uniforms);
|
this.effects?.bloom({ enabled: false });
|
||||||
this.bloomStage.enabled = this.previousBloom.enabled;
|
this.effects?.sharpen({ enabled: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+119
-28
@@ -1,6 +1,8 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
import { VisualEffects } from './visualEffects.js';
|
import { VisualEffects } from './visualEffects.js';
|
||||||
|
import { createCesiumEffects } from '../render/cesium/effects.js';
|
||||||
|
import { styleStagesOf } from '../styles/styleStages.js';
|
||||||
import { MILKY_WAY_INTENSITY, StarrySky } from '../sky/starrySky.js';
|
import { MILKY_WAY_INTENSITY, StarrySky } from '../sky/starrySky.js';
|
||||||
import {
|
import {
|
||||||
GLOBAL_POST_DEFAULTS,
|
GLOBAL_POST_DEFAULTS,
|
||||||
@@ -14,9 +16,10 @@ function fixture() {
|
|||||||
const frames = new Map();
|
const frames = new Map();
|
||||||
const stages = new Set();
|
const stages = new Set();
|
||||||
const holds = new Set();
|
const holds = new Set();
|
||||||
|
const portHolds = new Set();
|
||||||
const requests = [];
|
const requests = [];
|
||||||
const bloom = {
|
const bloom = {
|
||||||
enabled: true,
|
enabled: false,
|
||||||
uniforms: {
|
uniforms: {
|
||||||
glowOnly: true,
|
glowOnly: true,
|
||||||
contrast: 3,
|
contrast: 3,
|
||||||
@@ -36,10 +39,33 @@ function fixture() {
|
|||||||
return stages.delete(stage);
|
return stages.delete(stage);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const effects = new VisualEffects({
|
const scene = {
|
||||||
viewer: {
|
postProcessStages: pipeline,
|
||||||
scene: { postProcessStages: pipeline, skyBox: { milkyWayVisibility: 1 } },
|
skyBox: { milkyWayVisibility: 1 },
|
||||||
|
};
|
||||||
|
const preRender = new Set();
|
||||||
|
const renderer = createCesiumEffects(scene, {
|
||||||
|
capabilities: new Set(['styles', 'bloom', 'sharpen']),
|
||||||
|
frame: {
|
||||||
|
on(phase, cb) {
|
||||||
|
preRender.add(cb);
|
||||||
|
return () => preRender.delete(cb);
|
||||||
|
},
|
||||||
|
holdContinuous(reason) {
|
||||||
|
const hold = {};
|
||||||
|
portHolds.add(hold);
|
||||||
|
return () => portHolds.delete(hold);
|
||||||
|
},
|
||||||
|
requestRender() {},
|
||||||
},
|
},
|
||||||
|
report: (error) => {
|
||||||
|
throw error;
|
||||||
|
},
|
||||||
|
createStage: (options) => ({ ...options, enabled: true }),
|
||||||
|
});
|
||||||
|
const effects = new VisualEffects({
|
||||||
|
effects: renderer.port,
|
||||||
|
viewer: { scene },
|
||||||
requestRender: (reason) => requests.push(reason),
|
requestRender: (reason) => requests.push(reason),
|
||||||
holdRender: (reason) => holds.add(reason),
|
holdRender: (reason) => holds.add(reason),
|
||||||
releaseRender: (reason) => holds.delete(reason),
|
releaseRender: (reason) => holds.delete(reason),
|
||||||
@@ -50,8 +76,6 @@ function fixture() {
|
|||||||
},
|
},
|
||||||
cancelFrame: (id) => frames.delete(id),
|
cancelFrame: (id) => frames.delete(id),
|
||||||
now: () => time,
|
now: () => time,
|
||||||
wallNow: () => 10_000 + time,
|
|
||||||
createStage: (options) => ({ ...options, enabled: true }),
|
|
||||||
});
|
});
|
||||||
function tick(value) {
|
function tick(value) {
|
||||||
time = value;
|
time = value;
|
||||||
@@ -59,11 +83,20 @@ function fixture() {
|
|||||||
frames.clear();
|
frames.clear();
|
||||||
for (const callback of pending) callback();
|
for (const callback of pending) callback();
|
||||||
}
|
}
|
||||||
|
/** The renderer's stage for a style, or sharpen. */
|
||||||
|
const stage = (name) =>
|
||||||
|
[...stages].find(
|
||||||
|
(s) => s.name === `effects_style_${name}` || s.name === `effects_${name}`,
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
effects,
|
effects,
|
||||||
|
renderer,
|
||||||
frames,
|
frames,
|
||||||
stages,
|
stages,
|
||||||
|
stage,
|
||||||
holds,
|
holds,
|
||||||
|
portHolds,
|
||||||
|
preRender,
|
||||||
requests,
|
requests,
|
||||||
bloom,
|
bloom,
|
||||||
originalBloom,
|
originalBloom,
|
||||||
@@ -86,7 +119,10 @@ test('construction is inert and initialization creates one owned pipeline', () =
|
|||||||
f.effects.initStyles();
|
f.effects.initStyles();
|
||||||
f.effects.initPostProcess();
|
f.effects.initPostProcess();
|
||||||
assert.equal(f.stages.size, 6);
|
assert.equal(f.stages.size, 6);
|
||||||
|
assert.ok([...f.stages].every((stage) => !stage.enabled));
|
||||||
|
assert.equal(styleStagesOf(f.effects.viewer.scene), f.effects.stages);
|
||||||
f.effects.destroy();
|
f.effects.destroy();
|
||||||
|
assert.equal(styleStagesOf(f.effects.viewer.scene), null);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('crossfade uses the established 500ms easing and settles without idle frames', () => {
|
test('crossfade uses the established 500ms easing and settles without idle frames', () => {
|
||||||
@@ -104,6 +140,8 @@ test('crossfade uses the established 500ms easing and settles without idle frame
|
|||||||
assert.equal(f.effects.stages.noir.uniforms.intensity, 0.125);
|
assert.equal(f.effects.stages.noir.uniforms.intensity, 0.125);
|
||||||
f.tick(250);
|
f.tick(250);
|
||||||
assert.equal(f.effects.stages.noir.uniforms.intensity, 0.5);
|
assert.equal(f.effects.stages.noir.uniforms.intensity, 0.5);
|
||||||
|
assert.equal(f.stage('noir').uniforms.intensity, 0.5);
|
||||||
|
assert.equal(f.stage('noir').enabled, true);
|
||||||
f.tick(500);
|
f.tick(500);
|
||||||
assert.equal(f.effects.stages.noir.uniforms.intensity, 1);
|
assert.equal(f.effects.stages.noir.uniforms.intensity, 1);
|
||||||
assert.equal(f.effects.transitions.size, 0);
|
assert.equal(f.effects.transitions.size, 0);
|
||||||
@@ -132,32 +170,82 @@ test('a superseding transition starts from the current rendered intensity', () =
|
|||||||
f.effects.destroy();
|
f.effects.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('only visible animated stages keep the animation clock and render hold alive', () => {
|
test('only visible animated stages keep the renderer’s clock and hold alive', () => {
|
||||||
const f = fixture();
|
const f = fixture();
|
||||||
f.effects.initStyles();
|
f.effects.initStyles();
|
||||||
const animated = f.effects.stages.retro;
|
const animated = f.effects.stages.retro;
|
||||||
assert.notEqual(animated.uniforms.time, undefined);
|
|
||||||
f.effects.setStageIntensity(animated, 1);
|
f.effects.setStageIntensity(animated, 1);
|
||||||
f.tick(1000);
|
assert.equal(f.portHolds.size, 1);
|
||||||
assert.equal(animated.uniforms.time, 1);
|
for (const cb of f.preRender) cb({ now: performance.now() + 1000 });
|
||||||
assert.equal(f.frames.size, 1);
|
assert.ok(f.stage('retro').uniforms.time > 0.9);
|
||||||
assert.equal(f.holds.size, 1);
|
assert.equal(f.frames.size, 0, 'no frames of its own');
|
||||||
f.effects.setStageIntensity(animated, 0);
|
f.effects.setStageIntensity(animated, 0);
|
||||||
f.tick(1100);
|
assert.equal(f.portHolds.size, 0);
|
||||||
assert.equal(f.frames.size, 0);
|
|
||||||
assert.equal(f.holds.size, 0);
|
assert.equal(f.holds.size, 0);
|
||||||
f.effects.destroy();
|
f.effects.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('a crossfade draws both styles until the old one reaches 0', () => {
|
||||||
|
const f = fixture();
|
||||||
|
f.effects.initStyles();
|
||||||
|
f.effects.setStageIntensity(f.effects.stages.noir, 1);
|
||||||
|
f.effects.startTransition('noir', 1, 0);
|
||||||
|
f.effects.startTransition('thermal', 0, 1);
|
||||||
|
f.tick(250);
|
||||||
|
assert.equal(f.stage('noir').enabled, true);
|
||||||
|
assert.equal(f.stage('thermal').enabled, true);
|
||||||
|
assert.equal(f.stage('noir').uniforms.intensity, 0.5);
|
||||||
|
assert.equal(f.stage('thermal').uniforms.intensity, 0.5);
|
||||||
|
f.tick(500);
|
||||||
|
assert.equal(f.stage('noir').enabled, false);
|
||||||
|
assert.equal(f.stage('thermal').enabled, true);
|
||||||
|
// Back again before the first fade-in ends: all three may draw at once.
|
||||||
|
f.effects.startTransition('thermal', 1, 0);
|
||||||
|
f.effects.startTransition('noir', 0, 1);
|
||||||
|
f.tick(600);
|
||||||
|
f.effects.startTransition('retro', 0, 1);
|
||||||
|
f.tick(700);
|
||||||
|
assert.deepEqual(
|
||||||
|
['retro', 'thermal', 'noir'].map((name) => f.stage(name).enabled),
|
||||||
|
[true, true, true],
|
||||||
|
);
|
||||||
|
f.tick(1300);
|
||||||
|
assert.deepEqual(
|
||||||
|
Object.entries(f.effects.stages)
|
||||||
|
.filter(([, stage]) => stage.enabled)
|
||||||
|
.map(([name]) => name)
|
||||||
|
.sort(),
|
||||||
|
['noir', 'retro'],
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
['retro', 'thermal', 'noir'].map((name) => f.stage(name).enabled),
|
||||||
|
[true, false, true],
|
||||||
|
);
|
||||||
|
f.effects.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('style parameters are written through to the renderer', () => {
|
||||||
|
const f = fixture();
|
||||||
|
f.effects.initStyles();
|
||||||
|
assert.equal(f.effects.stages.thermal.uniforms.pixelation, 1.5);
|
||||||
|
f.effects.stages.thermal.uniforms.pixelation = 4;
|
||||||
|
assert.equal(f.stage('thermal').uniforms.pixelation, 4);
|
||||||
|
assert.equal(f.effects.stages.thermal.uniforms.pixelation, 4);
|
||||||
|
assert.equal(f.effects.stages.thermal.uniforms.unknown, undefined);
|
||||||
|
f.effects.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
test('direct Cockpit intensity writes are reconciled before rendering', () => {
|
test('direct Cockpit intensity writes are reconciled before rendering', () => {
|
||||||
const f = fixture();
|
const f = fixture();
|
||||||
f.effects.initStyles();
|
f.effects.initStyles();
|
||||||
f.effects.stages.thermal.uniforms.intensity = 1;
|
f.effects.stages.thermal.uniforms.intensity = 1;
|
||||||
f.effects.syncStagesEnabledFromIntensity();
|
f.effects.syncStagesEnabledFromIntensity();
|
||||||
assert.equal(f.effects.stages.thermal.enabled, true);
|
assert.equal(f.effects.stages.thermal.enabled, true);
|
||||||
|
assert.equal(f.stage('thermal').enabled, true);
|
||||||
f.effects.stages.thermal.uniforms.intensity = 0;
|
f.effects.stages.thermal.uniforms.intensity = 0;
|
||||||
f.effects.syncStagesEnabledFromIntensity();
|
f.effects.syncStagesEnabledFromIntensity();
|
||||||
assert.equal(f.effects.stages.thermal.enabled, false);
|
assert.equal(f.effects.stages.thermal.enabled, false);
|
||||||
|
assert.equal(f.stage('thermal').enabled, false);
|
||||||
f.effects.destroy();
|
f.effects.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -199,8 +287,11 @@ test('bloom retains its dead zone, normalized intensity and independent toggle',
|
|||||||
f.effects.setBloomEnabled(true);
|
f.effects.setBloomEnabled(true);
|
||||||
f.effects.applyBloomIntensity(0);
|
f.effects.applyBloomIntensity(0);
|
||||||
assert.equal(f.bloom.enabled, false);
|
assert.equal(f.bloom.enabled, false);
|
||||||
|
f.effects.applyBloomIntensity(12);
|
||||||
|
assert.equal(f.bloom.enabled, false, 'the dead zone');
|
||||||
f.effects.applyBloomIntensity(200);
|
f.effects.applyBloomIntensity(200);
|
||||||
assert.equal(f.bloom.enabled, true);
|
assert.equal(f.bloom.enabled, true);
|
||||||
|
assert.equal(f.bloom.uniforms.glowOnly, false);
|
||||||
assert.equal(f.bloom.uniforms.contrast, 87);
|
assert.equal(f.bloom.uniforms.contrast, 87);
|
||||||
f.effects.setBloomEnabled(false);
|
f.effects.setBloomEnabled(false);
|
||||||
assert.equal(f.effects.bloomIntensity, 200);
|
assert.equal(f.effects.bloomIntensity, 200);
|
||||||
@@ -213,14 +304,14 @@ test('bloom retains its dead zone, normalized intensity and independent toggle',
|
|||||||
test('sharpen uses the existing uniform scale and keeps values while disabled', () => {
|
test('sharpen uses the existing uniform scale and keeps values while disabled', () => {
|
||||||
const f = fixture();
|
const f = fixture();
|
||||||
f.effects.initPostProcess(0.49);
|
f.effects.initPostProcess(0.49);
|
||||||
assert.equal(f.effects.sharpenStage.uniforms.amount, 1.08);
|
assert.equal(f.stage('sharpen').uniforms.amount, 1.08);
|
||||||
f.effects.applySharpenIntensity(1);
|
f.effects.applySharpenIntensity(1);
|
||||||
f.effects.setSharpenEnabled(true);
|
f.effects.setSharpenEnabled(true);
|
||||||
assert.equal(f.effects.sharpenStage.uniforms.amount, 2.1);
|
assert.equal(f.stage('sharpen').uniforms.amount, 2.1);
|
||||||
assert.equal(f.effects.sharpenStage.enabled, true);
|
assert.equal(f.stage('sharpen').enabled, true);
|
||||||
f.effects.setSharpenEnabled(false);
|
f.effects.setSharpenEnabled(false);
|
||||||
assert.equal(f.effects.sharpenIntensity, 1);
|
assert.equal(f.effects.sharpenIntensity, 1);
|
||||||
assert.equal(f.effects.sharpenStage.enabled, false);
|
assert.equal(f.stage('sharpen').enabled, false);
|
||||||
f.effects.destroy();
|
f.effects.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -228,17 +319,13 @@ test('sharpen at 0 runs no pass, and comes back with the slider', () => {
|
|||||||
const f = fixture();
|
const f = fixture();
|
||||||
f.effects.initPostProcess(0.49);
|
f.effects.initPostProcess(0.49);
|
||||||
f.effects.setSharpenEnabled(true);
|
f.effects.setSharpenEnabled(true);
|
||||||
assert.equal(f.effects.sharpenStage.enabled, true);
|
assert.equal(f.stage('sharpen').enabled, true);
|
||||||
f.effects.applySharpenIntensity(0);
|
f.effects.applySharpenIntensity(0);
|
||||||
assert.equal(
|
assert.equal(f.stage('sharpen').enabled, false, 'no residual sharpen at 0');
|
||||||
f.effects.sharpenStage.enabled,
|
|
||||||
false,
|
|
||||||
'no residual sharpen at 0',
|
|
||||||
);
|
|
||||||
f.effects.setSharpenEnabled(true);
|
f.effects.setSharpenEnabled(true);
|
||||||
assert.equal(f.effects.sharpenStage.enabled, false, 'on at 0 is still off');
|
assert.equal(f.stage('sharpen').enabled, false, 'on at 0 is still off');
|
||||||
f.effects.applySharpenIntensity(0.2);
|
f.effects.applySharpenIntensity(0.2);
|
||||||
assert.equal(f.effects.sharpenStage.enabled, true);
|
assert.equal(f.stage('sharpen').enabled, true);
|
||||||
f.effects.destroy();
|
f.effects.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -262,13 +349,17 @@ test('stop revokes pending work immediately but retains stages until final destr
|
|||||||
assert.equal(f.frames.size, 0);
|
assert.equal(f.frames.size, 0);
|
||||||
assert.equal(f.effects.stages.retro.uniforms.intensity, intensity);
|
assert.equal(f.effects.stages.retro.uniforms.intensity, intensity);
|
||||||
f.effects.destroy();
|
f.effects.destroy();
|
||||||
|
assert.equal(f.stages.size, 1, 'the styles go; sharpen is the renderer’s');
|
||||||
|
assert.equal(f.stage('sharpen').enabled, false);
|
||||||
|
assert.equal(f.bloom.enabled, false);
|
||||||
|
f.effects.destroy();
|
||||||
|
f.renderer.dispose();
|
||||||
assert.equal(f.stages.size, 0);
|
assert.equal(f.stages.size, 0);
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
f.bloom,
|
f.bloom,
|
||||||
f.originalBloom,
|
f.originalBloom,
|
||||||
'borrowed bloom configuration is restored',
|
'the renderer gives the bloom configuration back',
|
||||||
);
|
);
|
||||||
f.effects.destroy();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('destroying one instance does not remove another pipeline or clock', () => {
|
test('destroying one instance does not remove another pipeline or clock', () => {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useDisplayStore } from './stores/display.js';
|
|||||||
import { applicationPinia } from './vueRuntime.js';
|
import { applicationPinia } from './vueRuntime.js';
|
||||||
import { onLocaleChange, t } from '../i18n/index.js';
|
import { onLocaleChange, t } from '../i18n/index.js';
|
||||||
import { UiLifetime } from './uiLifetime.js';
|
import { UiLifetime } from './uiLifetime.js';
|
||||||
|
import { rendererOf } from '../render/rendererOf.js';
|
||||||
import {
|
import {
|
||||||
VisualEffects,
|
VisualEffects,
|
||||||
STYLES,
|
STYLES,
|
||||||
@@ -195,6 +196,7 @@ export class VisualSettings {
|
|||||||
setStyleParameter: (name, value) => this.setStyleParameter(name, value),
|
setStyleParameter: (name, value) => this.setStyleParameter(name, value),
|
||||||
});
|
});
|
||||||
this._visualEffects = new VisualEffects({
|
this._visualEffects = new VisualEffects({
|
||||||
|
effects: rendererOf(viewer)?.effects ?? null,
|
||||||
viewer,
|
viewer,
|
||||||
requestRender: services.governorRequestRender,
|
requestRender: services.governorRequestRender,
|
||||||
holdRender: services.holdContinuousRender,
|
holdRender: services.holdContinuousRender,
|
||||||
|
|||||||
Reference in new issue
Block a user