@geoblocks/cesium-post-process 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +235 -2
  3. package/analog-video.js +138 -0
  4. package/blur.js +39 -0
  5. package/cesium-post-process.js +23 -0
  6. package/cloud-cover.js +49 -0
  7. package/cloud-map.js +122 -0
  8. package/cloud-noise.js +229 -0
  9. package/clouds.js +684 -0
  10. package/color-isolation.js +69 -0
  11. package/depth-test.js +37 -0
  12. package/digital-video.js +108 -0
  13. package/drone-display.js +170 -0
  14. package/effect.js +64 -0
  15. package/focus.js +25 -0
  16. package/frame-clock.js +61 -0
  17. package/height.js +23 -0
  18. package/infrared.js +40 -0
  19. package/jello.js +146 -0
  20. package/lens-distortion.js +68 -0
  21. package/lightning.js +321 -0
  22. package/motion-blur.js +117 -0
  23. package/package.json +55 -4
  24. package/precipitation.js +493 -0
  25. package/shaders/Alpenglow.js +2 -0
  26. package/shaders/AnalogVideo.js +2 -0
  27. package/shaders/Beam.js +2 -0
  28. package/shaders/CloudsBlur.js +2 -0
  29. package/shaders/CloudsComposite.js +2 -0
  30. package/shaders/CloudsDensity.js +2 -0
  31. package/shaders/CloudsDepth.js +2 -0
  32. package/shaders/CloudsMarch.js +2 -0
  33. package/shaders/CloudsResolve.js +2 -0
  34. package/shaders/CloudsShadow.js +2 -0
  35. package/shaders/CloudsShadowMap.js +2 -0
  36. package/shaders/CloudsSlab.js +2 -0
  37. package/shaders/ColorIsolation.js +2 -0
  38. package/shaders/DepthEdges.js +2 -0
  39. package/shaders/DigitalVideo.js +2 -0
  40. package/shaders/DroneDisplay.js +2 -0
  41. package/shaders/EyeFromDepth.js +2 -0
  42. package/shaders/Filmic.js +2 -0
  43. package/shaders/Flashlight.js +2 -0
  44. package/shaders/Fog.js +2 -0
  45. package/shaders/Frame.js +2 -0
  46. package/shaders/Hash.js +2 -0
  47. package/shaders/Height.js +2 -0
  48. package/shaders/Hsl.js +2 -0
  49. package/shaders/Infrared.js +2 -0
  50. package/shaders/Jello.js +2 -0
  51. package/shaders/LensDistortion.js +2 -0
  52. package/shaders/Lightning.js +2 -0
  53. package/shaders/MotionBlur.js +2 -0
  54. package/shaders/MotionBlurVelocity.js +2 -0
  55. package/shaders/Noise.js +2 -0
  56. package/shaders/Normal.js +2 -0
  57. package/shaders/Phase.js +2 -0
  58. package/shaders/Precipitation.js +2 -0
  59. package/shaders/PrecipitationShaftBlur.js +2 -0
  60. package/shaders/PrecipitationShafts.js +2 -0
  61. package/shaders/PrecipitationWind.js +2 -0
  62. package/shaders/RainHaze.js +2 -0
  63. package/shaders/Shockwave.js +2 -0
  64. package/shaders/SkyTable.js +2 -0
  65. package/shaders/SnowLine.js +2 -0
  66. package/shaders/SpeedLines.js +2 -0
  67. package/shaders/Spotlight.js +2 -0
  68. package/shaders/SpotlightBeam.js +2 -0
  69. package/shaders/SpotlightBeamBlur.js +2 -0
  70. package/shaders/SpotlightCone.js +2 -0
  71. package/shaders/Super8.js +2 -0
  72. package/shaders/Technicolor.js +2 -0
  73. package/shaders/TiltShift.js +2 -0
  74. package/shaders/Toy.js +2 -0
  75. package/shaders/Trails.js +2 -0
  76. package/shaders/ValleyFog.js +2 -0
  77. package/shaders/WeatherMap.js +2 -0
  78. package/shaders/WhiteModel.js +2 -0
  79. package/snow-line.js +108 -0
  80. package/speed-lines.js +71 -0
  81. package/spotlight.js +193 -0
  82. package/strikes.js +30 -0
  83. package/super8.js +164 -0
  84. package/technicolor.js +56 -0
  85. package/tilt-shift.js +124 -0
  86. package/toy.js +192 -0
  87. package/valley-fog.js +105 -0
  88. package/weather-map.js +290 -0
  89. package/white-model.js +105 -0
@@ -0,0 +1,69 @@
1
+ import {PostProcessStage} from '@cesium/engine';
2
+ import Effect from './effect.js';
3
+ import ColorIsolationShader from './shaders/ColorIsolation.js';
4
+ import Hsl from './shaders/Hsl.js';
5
+
6
+ /**
7
+ * Color isolation: one hue keeps its color, the rest of the scene turns gray.
8
+ */
9
+ export default class ColorIsolation extends Effect {
10
+ /**
11
+ * @param {import('@cesium/engine').CesiumWidget} viewer
12
+ * @param {{hue?: number, range?: number, strength?: number}} [options]
13
+ */
14
+ constructor(viewer, options = {}) {
15
+ super(viewer);
16
+ this.hue_ = options.hue ?? 0;
17
+ this.range_ = options.range ?? 60;
18
+ this.strength_ = options.strength ?? 1;
19
+ }
20
+
21
+ /** @override */
22
+ createStage_() {
23
+ return new PostProcessStage({
24
+ fragmentShader: Hsl + ColorIsolationShader,
25
+ uniforms: {
26
+ hue: () => this.hue_ / 360,
27
+ range: () => this.range_ / 360,
28
+ strength: () => this.strength_,
29
+ },
30
+ });
31
+ }
32
+
33
+ /**
34
+ * Hue kept in color, in degrees: 0 red, 60 yellow, 120 green, 180 cyan,
35
+ * 240 blue, 300 magenta.
36
+ */
37
+ get hue() {
38
+ return this.hue_;
39
+ }
40
+
41
+ set hue(value) {
42
+ this.hue_ = value;
43
+ this.viewer.scene.requestRender();
44
+ }
45
+
46
+ /**
47
+ * Half width of the hues kept, in degrees.
48
+ */
49
+ get range() {
50
+ return this.range_;
51
+ }
52
+
53
+ set range(value) {
54
+ this.range_ = value;
55
+ this.viewer.scene.requestRender();
56
+ }
57
+
58
+ /**
59
+ * How gray the rest turns, 0 to 1.
60
+ */
61
+ get strength() {
62
+ return this.strength_;
63
+ }
64
+
65
+ set strength(value) {
66
+ this.strength_ = value;
67
+ this.viewer.scene.requestRender();
68
+ }
69
+ }
package/depth-test.js ADDED
@@ -0,0 +1,37 @@
1
+ // The post-process stages only get the depth of the terrain when it is depth tested. Several
2
+ // effects may need it at once: the first one turns depth testing on, the last one restores it.
3
+
4
+ /** @type {WeakMap<import('@cesium/engine').Globe, {count: number, previous: boolean}>} */
5
+ const users = new WeakMap();
6
+
7
+ /**
8
+ * @param {import('@cesium/engine').Scene} scene
9
+ */
10
+ export function acquireTerrainDepth(scene) {
11
+ const globe = scene.globe;
12
+ if (!globe) {
13
+ return;
14
+ }
15
+ const user = users.get(globe);
16
+ if (user) {
17
+ user.count++;
18
+ return;
19
+ }
20
+ users.set(globe, {count: 1, previous: globe.depthTestAgainstTerrain});
21
+ globe.depthTestAgainstTerrain = true;
22
+ }
23
+
24
+ /**
25
+ * @param {import('@cesium/engine').Scene} scene
26
+ */
27
+ export function releaseTerrainDepth(scene) {
28
+ const globe = scene.globe;
29
+ const user = globe && users.get(globe);
30
+ if (!globe || !user) {
31
+ return;
32
+ }
33
+ if (--user.count === 0) {
34
+ globe.depthTestAgainstTerrain = user.previous;
35
+ users.delete(globe);
36
+ }
37
+ }
@@ -0,0 +1,108 @@
1
+ import {PostProcessStage} from '@cesium/engine';
2
+ import Effect from './effect.js';
3
+ import {acquireFrames, releaseFrames} from './frame-clock.js';
4
+ import DigitalVideoShader from './shaders/DigitalVideo.js';
5
+ import Hash from './shaders/Hash.js';
6
+
7
+ // digital FPV video runs at this rate
8
+ const FRAME_RATE = 30;
9
+ // at full breakup, a breakup starts on average every this many seconds
10
+ const MIN_INTERVAL = 2;
11
+ // length of a breakup, in seconds, and of its black screen
12
+ const DURATION = [0.4, 1.5];
13
+ const BLACKOUT = 0.25;
14
+
15
+ /**
16
+ * A digital FPV video feed: clean, then at times broken by a weak signal
17
+ * into macroblocks, displaced blocks and strips and split colors, sometimes
18
+ * with a short black screen. While active the scene renders at 30 frames per
19
+ * second, also in requestRenderMode.
20
+ */
21
+ export default class DigitalVideo extends Effect {
22
+ /**
23
+ * @param {import('@cesium/engine').CesiumWidget} viewer
24
+ * @param {{breakup?: number}} [options]
25
+ */
26
+ constructor(viewer, options = {}) {
27
+ super(viewer);
28
+ this.breakup_ = options.breakup ?? 0.5;
29
+ // the current breakup: start and length in seconds, and whether it goes black
30
+ /** @type {{start: number, duration: number, blackout: boolean} | undefined} */
31
+ this.episode_ = undefined;
32
+ // when the breakups were last updated, in seconds
33
+ this.updated_ = 0;
34
+ // the uniforms, updated once per frame
35
+ this.severity_ = 0;
36
+ this.blackout_ = 0;
37
+ this.onPreRender_ = () => this.update_();
38
+ }
39
+
40
+ /** @override */
41
+ createStage_() {
42
+ return new PostProcessStage({
43
+ fragmentShader: Hash + DigitalVideoShader,
44
+ uniforms: {
45
+ severity: () => this.severity_,
46
+ blackout: () => this.blackout_,
47
+ frame: () => Math.floor((performance.now() / 1000) * FRAME_RATE) % 100000,
48
+ },
49
+ });
50
+ }
51
+
52
+ /**
53
+ * @override
54
+ * @param {import('@cesium/engine').Scene} scene
55
+ */
56
+ activated_(scene) {
57
+ this.updated_ = performance.now() / 1000;
58
+ scene.preRender.addEventListener(this.onPreRender_);
59
+ acquireFrames(scene, FRAME_RATE);
60
+ }
61
+
62
+ /**
63
+ * @override
64
+ * @param {import('@cesium/engine').Scene} scene
65
+ */
66
+ deactivating_(scene) {
67
+ releaseFrames(scene, FRAME_RATE);
68
+ scene.preRender.removeEventListener(this.onPreRender_);
69
+ this.episode_ = undefined;
70
+ }
71
+
72
+ // before each render: end the finished breakup, start one with the chance of the time since the
73
+ // last update, so that breakups start at the same rate whatever the frame rate (at most a second,
74
+ // as a hidden page does not render), then how broken the picture is now: rising and falling over
75
+ // the breakup, black around its middle
76
+ update_() {
77
+ const now = performance.now() / 1000;
78
+ const elapsed = Math.min(now - this.updated_, 1);
79
+ this.updated_ = now;
80
+ if (this.episode_ && now > this.episode_.start + this.episode_.duration) {
81
+ this.episode_ = undefined;
82
+ }
83
+ if (!this.episode_ && Math.random() < 1 - Math.exp((-this.breakup_ / MIN_INTERVAL) * elapsed)) {
84
+ const duration = DURATION[0] + Math.random() * (DURATION[1] - DURATION[0]);
85
+ this.episode_ = {start: now, duration, blackout: Math.random() < 0.5 * this.breakup_};
86
+ }
87
+ const episode = this.episode_;
88
+ if (!episode) {
89
+ this.severity_ = 0;
90
+ this.blackout_ = 0;
91
+ return;
92
+ }
93
+ const t = (now - episode.start) / episode.duration;
94
+ this.severity_ = Math.max(0, Math.sin(Math.PI * Math.min(t, 1))) ** 0.7 * (0.4 + 0.6 * this.breakup_);
95
+ this.blackout_ = episode.blackout && Math.abs(t - 0.5) * episode.duration < BLACKOUT / 2 ? 1 : 0;
96
+ }
97
+
98
+ /**
99
+ * How often and how badly the signal breaks up, 0 to 1.
100
+ */
101
+ get breakup() {
102
+ return this.breakup_;
103
+ }
104
+
105
+ set breakup(value) {
106
+ this.breakup_ = value;
107
+ }
108
+ }
@@ -0,0 +1,170 @@
1
+ import {Cartesian3, Math as CesiumMath} from '@cesium/core';
2
+ import {PostProcessStage} from '@cesium/engine';
3
+ import Effect from './effect.js';
4
+ import DroneDisplayShader from './shaders/DroneDisplay.js';
5
+
6
+ // a 3S battery, full, empty, the seconds it takes to drain between them at the cruise throttle,
7
+ // and how far its voltage sags at full throttle
8
+ const FULL_VOLTS = 12.6;
9
+ const EMPTY_VOLTS = 10.5;
10
+ const DRAIN_SECONDS = 2400;
11
+ const SAG_VOLTS = 0.4;
12
+ // the throttle in percent at cruise, and more of it per m/s of climb
13
+ const CRUISE_THROTTLE = 70;
14
+ const CLIMB_THROTTLE = 5;
15
+ // seconds for the speeds to get two thirds of the way to the camera's; after a pause longer than
16
+ // the gap, as in requestRenderMode, the motion is not measured across it
17
+ const SMOOTHING = 0.5;
18
+ const GAP = 0.5;
19
+
20
+ /**
21
+ * A fixed-wing plane's OSD as ArduPilot draws it on an HD video system, in the
22
+ * blocky white glyphs of an analog chip with some in color: an artificial
23
+ * horizon of dots following the camera's pitch and roll as ArduPilot's does,
24
+ * the flight mode, the heading, the altitude since the display turned on, the
25
+ * vertical speed and the air speed of the camera, the throttle following its
26
+ * climb, the battery voltage draining with the throttle, and a made-up link
27
+ * quality; and in failsafe while the control link is lost, the RTL mode and the
28
+ * FAILSAFE warning.
29
+ */
30
+ export default class DroneDisplay extends Effect {
31
+ /**
32
+ * @param {import('@cesium/engine').CesiumWidget} viewer
33
+ * @param {{opacity?: number}} [options]
34
+ */
35
+ constructor(viewer, options = {}) {
36
+ super(viewer);
37
+ this.opacity_ = options.opacity ?? 1;
38
+ this.rxLoss_ = false;
39
+ // when the display turned on, and the camera's height then
40
+ this.startTime_ = 0;
41
+ this.startHeight_ = 0;
42
+ // the camera's speed and vertical speed in m/s, smoothed, from its position and height at the
43
+ // previous frame
44
+ this.speed_ = 0;
45
+ this.verticalSpeed_ = 0;
46
+ this.previousPosition_ = new Cartesian3();
47
+ this.previousHeight_ = 0;
48
+ this.previousTime_ = 0;
49
+ // the share of the battery drained
50
+ this.drained_ = 0;
51
+ this.onPostRender_ = () => this.measure_(false);
52
+ }
53
+
54
+ /**
55
+ * @override
56
+ * @param {import('@cesium/engine').Scene} scene
57
+ */
58
+ createStage_(scene) {
59
+ return new PostProcessStage({
60
+ fragmentShader: DroneDisplayShader,
61
+ uniforms: {
62
+ opacity: () => this.opacity_,
63
+ pitch: () => CesiumMath.toDegrees(scene.camera.pitch),
64
+ roll: () => CesiumMath.toDegrees(CesiumMath.negativePiToPi(scene.camera.roll)),
65
+ heading: () => CesiumMath.toDegrees(scene.camera.heading),
66
+ altitude: () => scene.camera.positionCartographic.height - this.startHeight_,
67
+ verticalSpeed: () => this.verticalSpeed_,
68
+ speed: () => this.speed_ * 3.6,
69
+ voltage: () => this.voltage_(),
70
+ linkQuality: () => this.linkQuality_(),
71
+ throttle: () => this.throttle_(),
72
+ failsafe: () => (this.rxLoss_ ? 1 : 0),
73
+ // twice per second, as ArduPilot's blinking
74
+ blink: () => (Math.floor(performance.now() / 250) % 2 === 0 ? 1 : 0),
75
+ },
76
+ });
77
+ }
78
+
79
+ /**
80
+ * @override
81
+ * @param {import('@cesium/engine').Scene} scene
82
+ */
83
+ activated_(scene) {
84
+ this.startTime_ = performance.now() / 1000;
85
+ this.startHeight_ = scene.camera.positionCartographic.height;
86
+ this.speed_ = 0;
87
+ this.verticalSpeed_ = 0;
88
+ this.drained_ = 0;
89
+ this.measure_(true);
90
+ scene.postRender.addEventListener(this.onPostRender_);
91
+ }
92
+
93
+ /**
94
+ * @override
95
+ * @param {import('@cesium/engine').Scene} scene
96
+ */
97
+ deactivating_(scene) {
98
+ scene.postRender.removeEventListener(this.onPostRender_);
99
+ }
100
+
101
+ /**
102
+ * Eases the speeds toward the camera's motion since the previous frame, and
103
+ * drains the battery at the throttle.
104
+ * @param {boolean} first whether there is no previous frame
105
+ */
106
+ measure_(first) {
107
+ const camera = this.viewer.scene.camera;
108
+ const time = performance.now() / 1000;
109
+ const height = camera.positionCartographic.height;
110
+ const dt = time - this.previousTime_;
111
+ if (!first && dt > 0 && dt < GAP) {
112
+ const ease = 1 - Math.exp(-dt / SMOOTHING);
113
+ this.speed_ += (Cartesian3.distance(camera.positionWC, this.previousPosition_) / dt - this.speed_) * ease;
114
+ this.verticalSpeed_ += ((height - this.previousHeight_) / dt - this.verticalSpeed_) * ease;
115
+ }
116
+ if (!first) {
117
+ this.drained_ += (dt / DRAIN_SECONDS) * (this.throttle_() / CRUISE_THROTTLE);
118
+ }
119
+ Cartesian3.clone(camera.positionWC, this.previousPosition_);
120
+ this.previousHeight_ = height;
121
+ this.previousTime_ = time;
122
+ }
123
+
124
+ // the battery drains over the flight, and sags under the throttle's load
125
+ voltage_() {
126
+ const drained = (FULL_VOLTS - EMPTY_VOLTS) * Math.min(this.drained_, 1);
127
+ return FULL_VOLTS - drained - (SAG_VOLTS * this.throttle_()) / 100;
128
+ }
129
+
130
+ // the link hovers in the nineties, with a deep dip once in a while
131
+ linkQuality_() {
132
+ const seconds = performance.now() / 1000 - this.startTime_;
133
+ const wander = 4 * Math.abs(Math.sin(seconds * 0.9) * Math.sin(seconds * 0.37));
134
+ const dip = Math.sin(seconds * 0.11) > 0.97 ? 35 : 0;
135
+ return Math.round(99 - wander - dip);
136
+ }
137
+
138
+ // the throttle holds the cruise, more of it climbing and less diving, wandering a little
139
+ throttle_() {
140
+ const seconds = performance.now() / 1000 - this.startTime_;
141
+ const wander = 2 * Math.sin(seconds * 0.3) * Math.sin(seconds * 0.13 + 1);
142
+ return Math.round(Math.min(Math.max(CRUISE_THROTTLE + CLIMB_THROTTLE * this.verticalSpeed_ + wander, 0), 100));
143
+ }
144
+
145
+ /**
146
+ * Opacity of the display, 0 to 1.
147
+ */
148
+ get opacity() {
149
+ return this.opacity_;
150
+ }
151
+
152
+ set opacity(value) {
153
+ this.opacity_ = value;
154
+ this.viewer.scene.requestRender();
155
+ }
156
+
157
+ /**
158
+ * Whether the control link is lost: the mode turns to RTL, the link bar to
159
+ * red, and the FAILSAFE warning blinks below the middle. It blinks only while
160
+ * the scene renders.
161
+ */
162
+ get rxLoss() {
163
+ return this.rxLoss_;
164
+ }
165
+
166
+ set rxLoss(value) {
167
+ this.rxLoss_ = value;
168
+ this.viewer.scene.requestRender();
169
+ }
170
+ }
package/effect.js ADDED
@@ -0,0 +1,64 @@
1
+ /**
2
+ * A screen-space effect: a post-process stage on a scene while active.
3
+ * Subclasses create their stage in `createStage_` and take their side effects
4
+ * in `activated_` and `deactivating_`.
5
+ */
6
+ export default class Effect {
7
+ /**
8
+ * @param {import('@cesium/engine').CesiumWidget} viewer
9
+ */
10
+ constructor(viewer) {
11
+ this.viewer = viewer;
12
+ /** @type {import('@cesium/engine').PostProcessStage | import('@cesium/engine').PostProcessStageComposite | undefined} */
13
+ this.stage_ = undefined;
14
+ }
15
+
16
+ get active() {
17
+ return this.stage_ !== undefined;
18
+ }
19
+
20
+ set active(active) {
21
+ const scene = this.viewer.scene;
22
+ if (active) {
23
+ if (this.stage_) {
24
+ return;
25
+ }
26
+ this.stage_ = this.createStage_(scene);
27
+ scene.postProcessStages.add(this.stage_);
28
+ this.activated_(scene);
29
+ } else {
30
+ if (!this.stage_) {
31
+ return;
32
+ }
33
+ this.deactivating_(scene);
34
+ // removing a stage also destroys it
35
+ scene.postProcessStages.remove(this.stage_);
36
+ this.stage_ = undefined;
37
+ }
38
+ scene.requestRender();
39
+ }
40
+
41
+ /**
42
+ * @param {import('@cesium/engine').Scene} _scene
43
+ * @return {import('@cesium/engine').PostProcessStage | import('@cesium/engine').PostProcessStageComposite}
44
+ */
45
+ createStage_(_scene) {
46
+ throw new Error(`${this.constructor.name} does not implement createStage_`);
47
+ }
48
+
49
+ /**
50
+ * Called once the stage is added to the scene.
51
+ * @param {import('@cesium/engine').Scene} _scene
52
+ */
53
+ activated_(_scene) {}
54
+
55
+ /**
56
+ * Called before the stage is removed from the scene.
57
+ * @param {import('@cesium/engine').Scene} _scene
58
+ */
59
+ deactivating_(_scene) {}
60
+
61
+ destroy() {
62
+ this.active = false;
63
+ }
64
+ }
package/focus.js ADDED
@@ -0,0 +1,25 @@
1
+ import {Cartesian3, Cartesian4, Matrix4} from '@cesium/core';
2
+
3
+ /**
4
+ * What an effect focuses on: a position, a function called every frame for a moving target, or
5
+ * undefined for what is in the middle of the screen.
6
+ * @typedef {Cartesian3 | (() => Cartesian3 | undefined) | undefined} Focus
7
+ */
8
+
9
+ const eyeScratch = new Cartesian3();
10
+
11
+ /**
12
+ * The focus in eye coordinates, computed in double precision; w is 0 for the middle of the
13
+ * screen, which the shaders read from the depth texture.
14
+ * @param {import('@cesium/engine').Scene} scene
15
+ * @param {Focus} focus
16
+ * @param {Cartesian4} result
17
+ */
18
+ export function eyeFocus(scene, focus, result) {
19
+ const position = typeof focus === 'function' ? focus() : focus;
20
+ if (!position) {
21
+ return Cartesian4.fromElements(0, 0, 0, 0, result);
22
+ }
23
+ const eye = Matrix4.multiplyByPoint(scene.camera.viewMatrix, position, eyeScratch);
24
+ return Cartesian4.fromElements(eye.x, eye.y, eye.z, 1, result);
25
+ }
package/frame-clock.js ADDED
@@ -0,0 +1,61 @@
1
+ // Animated effects need the scene to render at their frame rate, also in requestRenderMode. With a
2
+ // timer each, their render requests would not line up and would add up: one timer per scene
3
+ // requests renders at the highest rate the active effects need.
4
+
5
+ /** @type {WeakMap<import('@cesium/engine').Scene, {rates: number[], interval: ReturnType<typeof setInterval> | undefined}>} */
6
+ const clocks = new WeakMap();
7
+
8
+ /**
9
+ * @param {import('@cesium/engine').Scene} scene
10
+ * @param {{rates: number[], interval: ReturnType<typeof setInterval> | undefined}} clock
11
+ */
12
+ function restart(scene, clock) {
13
+ clearInterval(clock.interval);
14
+ clock.interval = clock.rates.length > 0 ? setInterval(() => tick(scene, clock), 1000 / Math.max(...clock.rates)) : undefined;
15
+ }
16
+
17
+ /**
18
+ * A render request; once the scene is destroyed, with effects left active, the clock stops: a
19
+ * destroyed scene throws on every call.
20
+ * @param {import('@cesium/engine').Scene} scene
21
+ * @param {{rates: number[], interval: ReturnType<typeof setInterval> | undefined}} clock
22
+ */
23
+ function tick(scene, clock) {
24
+ if (scene.isDestroyed()) {
25
+ clearInterval(clock.interval);
26
+ clocks.delete(scene);
27
+ return;
28
+ }
29
+ scene.requestRender();
30
+ }
31
+
32
+ /**
33
+ * @param {import('@cesium/engine').Scene} scene
34
+ * @param {number} rate frames per second
35
+ */
36
+ export function acquireFrames(scene, rate) {
37
+ let clock = clocks.get(scene);
38
+ if (!clock) {
39
+ clock = {rates: [], interval: undefined};
40
+ clocks.set(scene, clock);
41
+ }
42
+ clock.rates.push(rate);
43
+ restart(scene, clock);
44
+ }
45
+
46
+ /**
47
+ * @param {import('@cesium/engine').Scene} scene
48
+ * @param {number} rate the rate given to acquireFrames
49
+ */
50
+ export function releaseFrames(scene, rate) {
51
+ const clock = clocks.get(scene);
52
+ const index = clock ? clock.rates.indexOf(rate) : -1;
53
+ if (!clock || index < 0) {
54
+ return;
55
+ }
56
+ clock.rates.splice(index, 1);
57
+ restart(scene, clock);
58
+ if (clock.rates.length === 0) {
59
+ clocks.delete(scene);
60
+ }
61
+ }
package/height.js ADDED
@@ -0,0 +1,23 @@
1
+ import {Cartesian3, Matrix4} from '@cesium/core';
2
+
3
+ const upScratch = new Cartesian3();
4
+ const surfaceScratch = new Cartesian3();
5
+
6
+ /**
7
+ * The uniforms of Height.glsl: the camera's height, the local up direction in
8
+ * eye coordinates and the Earth's radius below the camera, in double precision.
9
+ * @param {import('@cesium/engine').Scene} scene
10
+ */
11
+ export function heightUniforms(scene) {
12
+ return {
13
+ cameraHeight: () => scene.camera.positionCartographic.height,
14
+ up: () => {
15
+ const up = scene.ellipsoid.geodeticSurfaceNormal(scene.camera.positionWC, upScratch);
16
+ return Matrix4.multiplyByPointAsVector(scene.camera.viewMatrix, up, up);
17
+ },
18
+ radius: () => {
19
+ const surface = scene.ellipsoid.scaleToGeodeticSurface(scene.camera.positionWC, surfaceScratch);
20
+ return surface ? Cartesian3.magnitude(surface) : scene.ellipsoid.maximumRadius;
21
+ },
22
+ };
23
+ }
package/infrared.js ADDED
@@ -0,0 +1,40 @@
1
+ import {PostProcessStage} from '@cesium/engine';
2
+ import Effect from './effect.js';
3
+ import Hsl from './shaders/Hsl.js';
4
+ import InfraredShader from './shaders/Infrared.js';
5
+
6
+ /**
7
+ * Black and white infrared film: bright foliage, dark sky and water.
8
+ */
9
+ export default class Infrared extends Effect {
10
+ /**
11
+ * @param {import('@cesium/engine').CesiumWidget} viewer
12
+ * @param {{strength?: number}} [options]
13
+ */
14
+ constructor(viewer, options = {}) {
15
+ super(viewer);
16
+ this.strength_ = options.strength ?? 1;
17
+ }
18
+
19
+ /** @override */
20
+ createStage_() {
21
+ return new PostProcessStage({
22
+ fragmentShader: Hsl + InfraredShader,
23
+ uniforms: {
24
+ strength: () => this.strength_,
25
+ },
26
+ });
27
+ }
28
+
29
+ /**
30
+ * How far from the original colors, 0 to 1.
31
+ */
32
+ get strength() {
33
+ return this.strength_;
34
+ }
35
+
36
+ set strength(value) {
37
+ this.strength_ = value;
38
+ this.viewer.scene.requestRender();
39
+ }
40
+ }