@geoblocks/cesium-post-process 0.0.0-stage → 0.1.0
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.
- package/LICENSE +29 -0
- package/README.md +235 -2
- package/analog-video.js +138 -0
- package/blur.js +39 -0
- package/cesium-post-process.js +23 -0
- package/color-isolation.js +69 -0
- package/depth-test.js +37 -0
- package/digital-video.js +108 -0
- package/drone-display.js +170 -0
- package/focus.js +25 -0
- package/frame-clock.js +61 -0
- package/height.js +23 -0
- package/infrared.js +40 -0
- package/jello.js +146 -0
- package/lens-distortion.js +68 -0
- package/lightning.js +321 -0
- package/motion-blur.js +117 -0
- package/package.json +49 -4
- package/precipitation.js +493 -0
- package/shaders/Alpenglow.js +2 -0
- package/shaders/AnalogVideo.js +2 -0
- package/shaders/Beam.js +2 -0
- package/shaders/CloudsBlur.js +2 -0
- package/shaders/CloudsComposite.js +2 -0
- package/shaders/CloudsDensity.js +2 -0
- package/shaders/CloudsDepth.js +2 -0
- package/shaders/CloudsMarch.js +2 -0
- package/shaders/CloudsResolve.js +2 -0
- package/shaders/CloudsShadow.js +2 -0
- package/shaders/CloudsShadowMap.js +2 -0
- package/shaders/CloudsSlab.js +2 -0
- package/shaders/ColorIsolation.js +2 -0
- package/shaders/DepthEdges.js +2 -0
- package/shaders/DigitalVideo.js +2 -0
- package/shaders/DroneDisplay.js +2 -0
- package/shaders/EyeFromDepth.js +2 -0
- package/shaders/Filmic.js +2 -0
- package/shaders/Flashlight.js +2 -0
- package/shaders/Fog.js +2 -0
- package/shaders/Frame.js +2 -0
- package/shaders/Hash.js +2 -0
- package/shaders/Height.js +2 -0
- package/shaders/Hsl.js +2 -0
- package/shaders/Infrared.js +2 -0
- package/shaders/Jello.js +2 -0
- package/shaders/LensDistortion.js +2 -0
- package/shaders/Lightning.js +2 -0
- package/shaders/MotionBlur.js +2 -0
- package/shaders/MotionBlurVelocity.js +2 -0
- package/shaders/Noise.js +2 -0
- package/shaders/Normal.js +2 -0
- package/shaders/Phase.js +2 -0
- package/shaders/Precipitation.js +2 -0
- package/shaders/PrecipitationShaftBlur.js +2 -0
- package/shaders/PrecipitationShafts.js +2 -0
- package/shaders/PrecipitationWind.js +2 -0
- package/shaders/RainHaze.js +2 -0
- package/shaders/Shockwave.js +2 -0
- package/shaders/SkyTable.js +2 -0
- package/shaders/SnowLine.js +2 -0
- package/shaders/SpeedLines.js +2 -0
- package/shaders/Spotlight.js +2 -0
- package/shaders/SpotlightBeam.js +2 -0
- package/shaders/SpotlightBeamBlur.js +2 -0
- package/shaders/SpotlightCone.js +2 -0
- package/shaders/Super8.js +2 -0
- package/shaders/Technicolor.js +2 -0
- package/shaders/TiltShift.js +2 -0
- package/shaders/Toy.js +2 -0
- package/shaders/Trails.js +2 -0
- package/shaders/ValleyFog.js +2 -0
- package/shaders/WeatherMap.js +2 -0
- package/shaders/WhiteModel.js +2 -0
- package/snow-line.js +108 -0
- package/speed-lines.js +71 -0
- package/spotlight.js +193 -0
- package/strikes.js +30 -0
- package/super8.js +164 -0
- package/technicolor.js +56 -0
- package/tilt-shift.js +124 -0
- package/toy.js +192 -0
- package/valley-fog.js +105 -0
- package/white-model.js +105 -0
package/digital-video.js
ADDED
|
@@ -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
|
+
}
|
package/drone-display.js
ADDED
|
@@ -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/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
|
+
}
|
package/jello.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import {Cartesian4} from '@cesium/core';
|
|
2
|
+
import {PostProcessStage} from '@cesium/engine';
|
|
3
|
+
import Effect from './effect.js';
|
|
4
|
+
import {acquireFrames, releaseFrames} from './frame-clock.js';
|
|
5
|
+
import JelloShader from './shaders/Jello.js';
|
|
6
|
+
|
|
7
|
+
// the shake renders at this rate, like a video camera
|
|
8
|
+
const FRAME_RATE = 30;
|
|
9
|
+
// the shake's sinusoids, as multiples of the frequency, and their share of the amount, so that
|
|
10
|
+
// it looks like noise rather than a single wave
|
|
11
|
+
const HARMONICS = [1, 1.37, 2.11, 3.07];
|
|
12
|
+
const WEIGHTS = [1, 0.6, 0.35, 0.2];
|
|
13
|
+
// the most the sinusoids reach together, as multiples of the first one's amplitude
|
|
14
|
+
const PEAK = WEIGHTS.reduce((sum, weight) => sum + weight, 0);
|
|
15
|
+
// at full amount: shift in picture heights, rotation in radians
|
|
16
|
+
const MAX_SHIFT = 0.015;
|
|
17
|
+
const MAX_ROTATION = 0.008;
|
|
18
|
+
// amplitude of the first sinusoid at full amount, per axis: x, y, rotation
|
|
19
|
+
const MAX_AMPLITUDES = [MAX_SHIFT, 0.5 * MAX_SHIFT, MAX_ROTATION];
|
|
20
|
+
|
|
21
|
+
const TAU = 2 * Math.PI;
|
|
22
|
+
/** @type {('x' | 'y' | 'z' | 'w')[]} one sinusoid per component */
|
|
23
|
+
const COMPONENTS = ['x', 'y', 'z', 'w'];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The jello of a vibrating camera with a rolling shutter: straight edges
|
|
27
|
+
* wobble as each row of the sensor is read at a different moment of the
|
|
28
|
+
* shake. The camera moves, so while active with an amount the scene renders
|
|
29
|
+
* at 30 frames per second, also in requestRenderMode.
|
|
30
|
+
*/
|
|
31
|
+
export default class Jello extends Effect {
|
|
32
|
+
/**
|
|
33
|
+
* @param {import('@cesium/engine').CesiumWidget} viewer
|
|
34
|
+
* @param {{amount?: number, frequency?: number}} [options]
|
|
35
|
+
*/
|
|
36
|
+
constructor(viewer, options = {}) {
|
|
37
|
+
super(viewer);
|
|
38
|
+
this.amount_ = options.amount ?? 0.1;
|
|
39
|
+
this.frequency_ = options.frequency ?? 30;
|
|
40
|
+
// a random starting phase for each sinusoid and axis
|
|
41
|
+
this.offsets_ = HARMONICS.map(() => [Math.random(), Math.random(), Math.random()].map((r) => r * TAU));
|
|
42
|
+
// the uniforms, updated when the amount or the frequency change (the shape) and
|
|
43
|
+
// once per frame (the phases)
|
|
44
|
+
this.shakeHz_ = new Cartesian4();
|
|
45
|
+
this.amplitudes_ = [new Cartesian4(), new Cartesian4(), new Cartesian4()];
|
|
46
|
+
this.zoom_ = 1;
|
|
47
|
+
this.phases_ = [new Cartesian4(), new Cartesian4(), new Cartesian4()];
|
|
48
|
+
this.updateShape_();
|
|
49
|
+
// the phases of the sinusoids at the frame's time, reduced in double precision:
|
|
50
|
+
// a float would lose the time's fraction of a second
|
|
51
|
+
this.onPreRender_ = () => {
|
|
52
|
+
const seconds = performance.now() / 1000;
|
|
53
|
+
HARMONICS.forEach((h, i) => {
|
|
54
|
+
const phase = TAU * ((h * this.frequency_ * seconds) % 1);
|
|
55
|
+
this.phases_.forEach((phases, axis) => {
|
|
56
|
+
phases[COMPONENTS[i]] = (phase + this.offsets_[i][axis]) % TAU;
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** @override */
|
|
63
|
+
createStage_() {
|
|
64
|
+
const stage = new PostProcessStage({
|
|
65
|
+
fragmentShader: JelloShader,
|
|
66
|
+
uniforms: {
|
|
67
|
+
shakeHz: () => this.shakeHz_,
|
|
68
|
+
phaseX: () => this.phases_[0],
|
|
69
|
+
phaseY: () => this.phases_[1],
|
|
70
|
+
phaseRotation: () => this.phases_[2],
|
|
71
|
+
amplitudeX: () => this.amplitudes_[0],
|
|
72
|
+
amplitudeY: () => this.amplitudes_[1],
|
|
73
|
+
amplitudeRotation: () => this.amplitudes_[2],
|
|
74
|
+
zoom: () => this.zoom_,
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
// no pass, and no frames, without an amount
|
|
78
|
+
stage.enabled = this.amount_ > 0;
|
|
79
|
+
return stage;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @override
|
|
84
|
+
* @param {import('@cesium/engine').Scene} scene
|
|
85
|
+
*/
|
|
86
|
+
activated_(scene) {
|
|
87
|
+
scene.preRender.addEventListener(this.onPreRender_);
|
|
88
|
+
if (this.amount_ > 0) {
|
|
89
|
+
acquireFrames(scene, FRAME_RATE);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @override
|
|
95
|
+
* @param {import('@cesium/engine').Scene} scene
|
|
96
|
+
*/
|
|
97
|
+
deactivating_(scene) {
|
|
98
|
+
if (this.amount_ > 0) {
|
|
99
|
+
releaseFrames(scene, FRAME_RATE);
|
|
100
|
+
}
|
|
101
|
+
scene.preRender.removeEventListener(this.onPreRender_);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
updateShape_() {
|
|
105
|
+
HARMONICS.forEach((h, i) => {
|
|
106
|
+
this.shakeHz_[COMPONENTS[i]] = h * this.frequency_;
|
|
107
|
+
this.amplitudes_.forEach((amplitudes, axis) => {
|
|
108
|
+
amplitudes[COMPONENTS[i]] = WEIGHTS[i] * MAX_AMPLITUDES[axis] * this.amount_;
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
// enough to hide the edges when all the sinusoids peak together: the shift, and the
|
|
112
|
+
// rotation of the corners, about a picture height from the middle
|
|
113
|
+
this.zoom_ = 1 + 2 * PEAK * this.amount_ * (MAX_SHIFT + MAX_ROTATION);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Strength of the shake, 0 to 1.
|
|
118
|
+
*/
|
|
119
|
+
get amount() {
|
|
120
|
+
return this.amount_;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
set amount(value) {
|
|
124
|
+
const shaking = this.amount_ > 0;
|
|
125
|
+
this.amount_ = value;
|
|
126
|
+
this.updateShape_();
|
|
127
|
+
if (this.stage_ && shaking !== value > 0) {
|
|
128
|
+
this.stage_.enabled = value > 0;
|
|
129
|
+
(value > 0 ? acquireFrames : releaseFrames)(this.viewer.scene, FRAME_RATE);
|
|
130
|
+
}
|
|
131
|
+
this.viewer.scene.requestRender();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Frequency of the vibration, in Hz: the rows read over the 20 ms readout
|
|
136
|
+
* see a fifth of a wave at 10 Hz, more than a whole one at 60 Hz.
|
|
137
|
+
*/
|
|
138
|
+
get frequency() {
|
|
139
|
+
return this.frequency_;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
set frequency(value) {
|
|
143
|
+
this.frequency_ = value;
|
|
144
|
+
this.updateShape_();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {PostProcessStage} from '@cesium/engine';
|
|
2
|
+
import Effect from './effect.js';
|
|
3
|
+
import LensDistortionShader from './shaders/LensDistortion.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The barrel distortion and dark corners of a wide-angle lens, like those of
|
|
7
|
+
* FPV cameras.
|
|
8
|
+
*/
|
|
9
|
+
export default class LensDistortion extends Effect {
|
|
10
|
+
/**
|
|
11
|
+
* @param {import('@cesium/engine').CesiumWidget} viewer
|
|
12
|
+
* @param {{distortion?: number, vignette?: number}} [options]
|
|
13
|
+
*/
|
|
14
|
+
constructor(viewer, options = {}) {
|
|
15
|
+
super(viewer);
|
|
16
|
+
this.distortion_ = options.distortion ?? 0.5;
|
|
17
|
+
this.vignette_ = options.vignette ?? 0.5;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** @override */
|
|
21
|
+
createStage_() {
|
|
22
|
+
const stage = new PostProcessStage({
|
|
23
|
+
fragmentShader: LensDistortionShader,
|
|
24
|
+
uniforms: {
|
|
25
|
+
distortion: () => this.distortion_,
|
|
26
|
+
vignette: () => this.vignette_,
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
// no pass at all without distortion or vignette
|
|
30
|
+
stage.enabled = this.enabled_();
|
|
31
|
+
return stage;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
enabled_() {
|
|
35
|
+
return this.distortion_ > 0 || this.vignette_ > 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Bending of straight lines toward the edges, 0 to 1.
|
|
40
|
+
*/
|
|
41
|
+
get distortion() {
|
|
42
|
+
return this.distortion_;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
set distortion(value) {
|
|
46
|
+
this.distortion_ = value;
|
|
47
|
+
this.update_();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Darkening of the corners, 0 to 1.
|
|
52
|
+
*/
|
|
53
|
+
get vignette() {
|
|
54
|
+
return this.vignette_;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
set vignette(value) {
|
|
58
|
+
this.vignette_ = value;
|
|
59
|
+
this.update_();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
update_() {
|
|
63
|
+
if (this.stage_) {
|
|
64
|
+
this.stage_.enabled = this.enabled_();
|
|
65
|
+
}
|
|
66
|
+
this.viewer.scene.requestRender();
|
|
67
|
+
}
|
|
68
|
+
}
|