@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.
- 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/cloud-cover.js +49 -0
- package/cloud-map.js +122 -0
- package/cloud-noise.js +229 -0
- package/clouds.js +684 -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/effect.js +64 -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 +55 -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/weather-map.js +290 -0
- package/white-model.js +105 -0
package/tilt-shift.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import {Cartesian4} from '@cesium/core';
|
|
2
|
+
import {PostProcessStage, PostProcessStageComposite} from '@cesium/engine';
|
|
3
|
+
import createBlur from './blur.js';
|
|
4
|
+
import {acquireTerrainDepth, releaseTerrainDepth} from './depth-test.js';
|
|
5
|
+
import Effect from './effect.js';
|
|
6
|
+
import {eyeFocus} from './focus.js';
|
|
7
|
+
import EyeFromDepth from './shaders/EyeFromDepth.js';
|
|
8
|
+
import TiltShiftShader from './shaders/TiltShift.js';
|
|
9
|
+
|
|
10
|
+
const focusScratch = new Cartesian4();
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Tilt-shift: a narrow depth of field around the focus, which makes the scene
|
|
14
|
+
* look like a scale model.
|
|
15
|
+
*/
|
|
16
|
+
export default class TiltShift extends Effect {
|
|
17
|
+
/**
|
|
18
|
+
* @param {import('@cesium/engine').CesiumWidget} viewer
|
|
19
|
+
* @param {{focus?: import('./focus.js').Focus, range?: number, blur?: number, saturation?: number}} [options]
|
|
20
|
+
*/
|
|
21
|
+
constructor(viewer, options = {}) {
|
|
22
|
+
super(viewer);
|
|
23
|
+
this.focus_ = options.focus;
|
|
24
|
+
this.range_ = options.range ?? 0.3;
|
|
25
|
+
this.blur_ = options.blur ?? 4;
|
|
26
|
+
this.saturation_ = options.saturation ?? 0.3;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @override
|
|
31
|
+
* @param {import('@cesium/engine').Scene} scene
|
|
32
|
+
*/
|
|
33
|
+
createStage_(scene) {
|
|
34
|
+
const blur = createBlur('czm_tilt_shift_blur');
|
|
35
|
+
const stage = new PostProcessStageComposite({
|
|
36
|
+
stages: [
|
|
37
|
+
blur,
|
|
38
|
+
new PostProcessStage({
|
|
39
|
+
fragmentShader: EyeFromDepth + TiltShiftShader,
|
|
40
|
+
uniforms: {
|
|
41
|
+
blurTexture: blur.name,
|
|
42
|
+
focus: () => eyeFocus(scene, this.focus_, focusScratch),
|
|
43
|
+
range: () => this.range_,
|
|
44
|
+
saturation: () => this.saturation_,
|
|
45
|
+
},
|
|
46
|
+
}),
|
|
47
|
+
],
|
|
48
|
+
// both read the scene
|
|
49
|
+
inputPreviousStageTexture: false,
|
|
50
|
+
uniforms: blur.uniforms,
|
|
51
|
+
});
|
|
52
|
+
stage.uniforms.sigma = this.blur_;
|
|
53
|
+
return stage;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @override
|
|
58
|
+
* @param {import('@cesium/engine').Scene} scene
|
|
59
|
+
*/
|
|
60
|
+
activated_(scene) {
|
|
61
|
+
acquireTerrainDepth(scene);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @override
|
|
66
|
+
* @param {import('@cesium/engine').Scene} scene
|
|
67
|
+
*/
|
|
68
|
+
deactivating_(scene) {
|
|
69
|
+
releaseTerrainDepth(scene);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* What to focus on: a position, a function returning one, or undefined for
|
|
74
|
+
* what is in the middle of the screen.
|
|
75
|
+
*/
|
|
76
|
+
get focus() {
|
|
77
|
+
return this.focus_;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
set focus(value) {
|
|
81
|
+
this.focus_ = value;
|
|
82
|
+
this.viewer.scene.requestRender();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Width of the sharp band, in factors of two of the focal distance; with an orthographic camera,
|
|
87
|
+
* in shares of the view's height.
|
|
88
|
+
*/
|
|
89
|
+
get range() {
|
|
90
|
+
return this.range_;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
set range(value) {
|
|
94
|
+
this.range_ = value;
|
|
95
|
+
this.viewer.scene.requestRender();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Blur away from the focus, the sigma of Cesium's blur stage.
|
|
100
|
+
*/
|
|
101
|
+
get blur() {
|
|
102
|
+
return this.blur_;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
set blur(value) {
|
|
106
|
+
this.blur_ = value;
|
|
107
|
+
if (this.stage_) {
|
|
108
|
+
this.stage_.uniforms.sigma = value;
|
|
109
|
+
}
|
|
110
|
+
this.viewer.scene.requestRender();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Color and contrast boost, 0 to 1.
|
|
115
|
+
*/
|
|
116
|
+
get saturation() {
|
|
117
|
+
return this.saturation_;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
set saturation(value) {
|
|
121
|
+
this.saturation_ = value;
|
|
122
|
+
this.viewer.scene.requestRender();
|
|
123
|
+
}
|
|
124
|
+
}
|
package/toy.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import {Cartesian3, Cartesian4} from '@cesium/core';
|
|
2
|
+
import {PostProcessStage} from '@cesium/engine';
|
|
3
|
+
import {acquireTerrainDepth, releaseTerrainDepth} from './depth-test.js';
|
|
4
|
+
import Effect from './effect.js';
|
|
5
|
+
import DepthEdges from './shaders/DepthEdges.js';
|
|
6
|
+
import EyeFromDepth from './shaders/EyeFromDepth.js';
|
|
7
|
+
import ToyShader from './shaders/Toy.js';
|
|
8
|
+
|
|
9
|
+
const NO_SKY = new Cartesian4(0, 0, 0, 0);
|
|
10
|
+
const warmScratch = new Cartesian3();
|
|
11
|
+
const skyTopScratch = new Cartesian4();
|
|
12
|
+
const skyBottomScratch = new Cartesian4();
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A toy's look, as an isometric game's: the edges lit where the surface bends
|
|
16
|
+
* outward and shaded where it bends inward, rounded close up, the silhouettes
|
|
17
|
+
* outlined in a darker shade of their color, the colors a little richer and
|
|
18
|
+
* warmer, the sky a soft gradient if asked.
|
|
19
|
+
*/
|
|
20
|
+
export default class Toy extends Effect {
|
|
21
|
+
/**
|
|
22
|
+
* @param {import('@cesium/engine').CesiumWidget} viewer
|
|
23
|
+
* @param {{light?: number, shade?: number, bevel?: number, outline?: number, jump?: number, saturation?: number, warm?: [number, number, number], sky?: [import('@cesium/core').Color, import('@cesium/core').Color]}} [options]
|
|
24
|
+
*/
|
|
25
|
+
constructor(viewer, options = {}) {
|
|
26
|
+
super(viewer);
|
|
27
|
+
this.light_ = options.light ?? 0.22;
|
|
28
|
+
this.shade_ = options.shade ?? 0.18;
|
|
29
|
+
this.bevel_ = options.bevel ?? 0;
|
|
30
|
+
this.outline_ = options.outline ?? 0.4;
|
|
31
|
+
this.jump_ = options.jump ?? 20;
|
|
32
|
+
this.saturation_ = options.saturation ?? 1.04;
|
|
33
|
+
this.warm_ = options.warm ?? [1.03, 1, 0.95];
|
|
34
|
+
this.sky_ = options.sky;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @override
|
|
39
|
+
* @param {import('@cesium/engine').Scene} _scene
|
|
40
|
+
*/
|
|
41
|
+
createStage_(_scene) {
|
|
42
|
+
return new PostProcessStage({fragmentShader: EyeFromDepth + DepthEdges + this.shader_(), uniforms: this.uniforms_()});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The look's shader, after DepthEdges.
|
|
47
|
+
* @protected
|
|
48
|
+
* @return {string}
|
|
49
|
+
*/
|
|
50
|
+
shader_() {
|
|
51
|
+
return ToyShader;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The shader's uniforms.
|
|
56
|
+
* @protected
|
|
57
|
+
* @return {Object<string, () => any>}
|
|
58
|
+
*/
|
|
59
|
+
uniforms_() {
|
|
60
|
+
const color = (/** @type {import('@cesium/core').Color} */ c, /** @type {Cartesian4} */ result) => Cartesian4.fromElements(c.red, c.green, c.blue, 1, result);
|
|
61
|
+
return {
|
|
62
|
+
light: () => this.light_,
|
|
63
|
+
shade: () => this.shade_,
|
|
64
|
+
bevel: () => this.bevel_,
|
|
65
|
+
outline: () => this.outline_,
|
|
66
|
+
jump: () => this.jump_,
|
|
67
|
+
saturation: () => this.saturation_,
|
|
68
|
+
warm: () => Cartesian3.fromArray(this.warm_, 0, warmScratch),
|
|
69
|
+
skyTop: () => this.sky_ ? color(this.sky_[0], skyTopScratch) : NO_SKY,
|
|
70
|
+
skyBottom: () => this.sky_ ? color(this.sky_[1], skyBottomScratch) : NO_SKY,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @override
|
|
76
|
+
* @param {import('@cesium/engine').Scene} scene
|
|
77
|
+
*/
|
|
78
|
+
activated_(scene) {
|
|
79
|
+
acquireTerrainDepth(scene);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @override
|
|
84
|
+
* @param {import('@cesium/engine').Scene} scene
|
|
85
|
+
*/
|
|
86
|
+
deactivating_(scene) {
|
|
87
|
+
releaseTerrainDepth(scene);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* How much brighter a convex edge gets, 0 for none.
|
|
92
|
+
*/
|
|
93
|
+
get light() {
|
|
94
|
+
return this.light_;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
set light(value) {
|
|
98
|
+
this.light_ = value;
|
|
99
|
+
this.viewer.scene.requestRender();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* How much darker a concave edge gets, 0 for none.
|
|
104
|
+
*/
|
|
105
|
+
get shade() {
|
|
106
|
+
return this.shade_;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
set shade(value) {
|
|
110
|
+
this.shade_ = value;
|
|
111
|
+
this.viewer.scene.requestRender();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The edges' bevel, in meters: close up, their light and shade fade over that width, as a molded
|
|
116
|
+
* plastic edge's; a pixel at the least, 8 at the most; 0, the default, for a pixel. Next to a small
|
|
117
|
+
* step (a frame proud of a wall), which it cannot tell from a fold, it lights a patch of the face
|
|
118
|
+
* behind.
|
|
119
|
+
*/
|
|
120
|
+
get bevel() {
|
|
121
|
+
return this.bevel_;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
set bevel(value) {
|
|
125
|
+
this.bevel_ = value;
|
|
126
|
+
this.viewer.scene.requestRender();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* How much darker the outline along the silhouettes gets, a pixel wide, a darker shade of their
|
|
131
|
+
* color; 0 for none.
|
|
132
|
+
*/
|
|
133
|
+
get outline() {
|
|
134
|
+
return this.outline_;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
set outline(value) {
|
|
138
|
+
this.outline_ = value;
|
|
139
|
+
this.viewer.scene.requestRender();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* How far the depth jumps, in pixels' sizes, to be a silhouette, outlined, rather than a crease,
|
|
144
|
+
* lit or shaded: 20 by default, for a city's buildings, their eaves lit; lower close up, where a
|
|
145
|
+
* thin thing standing on the ground (a chair's leg) would light its foot.
|
|
146
|
+
*/
|
|
147
|
+
get jump() {
|
|
148
|
+
return this.jump_;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
set jump(value) {
|
|
152
|
+
this.jump_ = value;
|
|
153
|
+
this.viewer.scene.requestRender();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* The colors' saturation, 1 to keep it.
|
|
158
|
+
*/
|
|
159
|
+
get saturation() {
|
|
160
|
+
return this.saturation_;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
set saturation(value) {
|
|
164
|
+
this.saturation_ = value;
|
|
165
|
+
this.viewer.scene.requestRender();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* A factor each of red, green and blue; [1, 1, 1] to keep the colors.
|
|
170
|
+
*/
|
|
171
|
+
get warm() {
|
|
172
|
+
return this.warm_;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
set warm(value) {
|
|
176
|
+
this.warm_ = value;
|
|
177
|
+
this.viewer.scene.requestRender();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* The sky's gradient, its color at the top of the screen and at its bottom;
|
|
182
|
+
* undefined to keep the sky.
|
|
183
|
+
*/
|
|
184
|
+
get sky() {
|
|
185
|
+
return this.sky_;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
set sky(value) {
|
|
189
|
+
this.sky_ = value;
|
|
190
|
+
this.viewer.scene.requestRender();
|
|
191
|
+
}
|
|
192
|
+
}
|
package/valley-fog.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import {Color} from '@cesium/core';
|
|
2
|
+
import {PostProcessStage} from '@cesium/engine';
|
|
3
|
+
import {acquireTerrainDepth, releaseTerrainDepth} from './depth-test.js';
|
|
4
|
+
import Effect from './effect.js';
|
|
5
|
+
import {heightUniforms} from './height.js';
|
|
6
|
+
import EyeFromDepth from './shaders/EyeFromDepth.js';
|
|
7
|
+
import Fog from './shaders/Fog.js';
|
|
8
|
+
import Height from './shaders/Height.js';
|
|
9
|
+
import ValleyFogShader from './shaders/ValleyFog.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Fog filling the valleys below an altitude: from above, a sea of fog with the
|
|
13
|
+
* peaks standing out; from inside, the visibility drops with the distance.
|
|
14
|
+
*/
|
|
15
|
+
export default class ValleyFog extends Effect {
|
|
16
|
+
/**
|
|
17
|
+
* @param {import('@cesium/engine').CesiumWidget} viewer
|
|
18
|
+
* @param {{top?: number, density?: number, softness?: number, color?: Color}} [options]
|
|
19
|
+
*/
|
|
20
|
+
constructor(viewer, options = {}) {
|
|
21
|
+
super(viewer);
|
|
22
|
+
this.top_ = options.top ?? 1500;
|
|
23
|
+
this.density_ = options.density ?? 0.005;
|
|
24
|
+
this.softness_ = options.softness ?? 100;
|
|
25
|
+
this.color_ = options.color ?? new Color(0.85, 0.88, 0.92, 1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @override
|
|
30
|
+
* @param {import('@cesium/engine').Scene} scene
|
|
31
|
+
*/
|
|
32
|
+
createStage_(scene) {
|
|
33
|
+
return new PostProcessStage({
|
|
34
|
+
fragmentShader: EyeFromDepth + Height + Fog + ValleyFogShader,
|
|
35
|
+
uniforms: {
|
|
36
|
+
...heightUniforms(scene),
|
|
37
|
+
top: () => this.top_,
|
|
38
|
+
density: () => this.density_,
|
|
39
|
+
softness: () => this.softness_,
|
|
40
|
+
color: () => this.color_,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @override
|
|
47
|
+
* @param {import('@cesium/engine').Scene} scene
|
|
48
|
+
*/
|
|
49
|
+
activated_(scene) {
|
|
50
|
+
acquireTerrainDepth(scene);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @override
|
|
55
|
+
* @param {import('@cesium/engine').Scene} scene
|
|
56
|
+
*/
|
|
57
|
+
deactivating_(scene) {
|
|
58
|
+
releaseTerrainDepth(scene);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Altitude of the top of the fog, in meters above the ellipsoid.
|
|
63
|
+
*/
|
|
64
|
+
get top() {
|
|
65
|
+
return this.top_;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
set top(value) {
|
|
69
|
+
this.top_ = value;
|
|
70
|
+
this.viewer.scene.requestRender();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Density of the fog, per meter: about 1 / visibility.
|
|
75
|
+
*/
|
|
76
|
+
get density() {
|
|
77
|
+
return this.density_;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
set density(value) {
|
|
81
|
+
this.density_ = value;
|
|
82
|
+
this.viewer.scene.requestRender();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Height of the band under the top over which the fog thins out, in meters.
|
|
87
|
+
*/
|
|
88
|
+
get softness() {
|
|
89
|
+
return this.softness_;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
set softness(value) {
|
|
93
|
+
this.softness_ = value;
|
|
94
|
+
this.viewer.scene.requestRender();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
get color() {
|
|
98
|
+
return this.color_;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
set color(value) {
|
|
102
|
+
this.color_ = value;
|
|
103
|
+
this.viewer.scene.requestRender();
|
|
104
|
+
}
|
|
105
|
+
}
|
package/weather-map.js
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import {Cartesian4} from '@cesium/core';
|
|
2
|
+
// @ts-expect-error the engine exports its textures, but does not type them
|
|
3
|
+
import {Texture} from '@cesium/engine';
|
|
4
|
+
|
|
5
|
+
const TWO_PI = 2 * Math.PI;
|
|
6
|
+
// the max map's texels are blocks of this many pixels of the map across
|
|
7
|
+
const POOL = 4;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Where it rains: an image whose red channel is the local intensity, 0 to 1,
|
|
11
|
+
* over a rectangle of longitudes and latitudes, north up, not crossing the
|
|
12
|
+
* antimeridian. Outside the rectangle there is none, so the image should
|
|
13
|
+
* fade to 0 at its edges.
|
|
14
|
+
* @typedef {{image: HTMLCanvasElement | ImageData | {width: number, height: number, data: Uint8ClampedArray}, rectangle: import('@cesium/core').Rectangle}} WeatherMapOptions
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The pixels of an image, read once.
|
|
19
|
+
* @param {WeatherMapOptions['image']} image
|
|
20
|
+
*/
|
|
21
|
+
export function imageData(image) {
|
|
22
|
+
return 'data' in image ? image : image.getContext('2d', {willReadFrequently: true})?.getImageData(0, 0, image.width, image.height);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A texture's source from an image: a canvas or an ImageData as it is, other pixels as an array,
|
|
27
|
+
* which Cesium flips as it flips an image.
|
|
28
|
+
* @param {HTMLCanvasElement | ImageData | {width: number, height: number, data: Uint8ClampedArray}} image
|
|
29
|
+
*/
|
|
30
|
+
function textureSource(image) {
|
|
31
|
+
if (!('data' in image) || (typeof ImageData !== 'undefined' && image instanceof ImageData)) {
|
|
32
|
+
return image;
|
|
33
|
+
}
|
|
34
|
+
const {width, height, data} = image;
|
|
35
|
+
return {width, height, arrayBufferView: new Uint8Array(data.buffer, data.byteOffset, data.length)};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The highest value of each channel of an image, 0 to 1.
|
|
40
|
+
* @param {{data: Uint8ClampedArray} | undefined} data
|
|
41
|
+
*/
|
|
42
|
+
function highest(data) {
|
|
43
|
+
const max = [0, 0, 0, 0];
|
|
44
|
+
const values = data?.data ?? [];
|
|
45
|
+
for (let i = 0; i < values.length; i += 4) {
|
|
46
|
+
for (let channel = 0; channel < 4; channel++) {
|
|
47
|
+
max[channel] = Math.max(max[channel], values[i + channel]);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return max.map((value) => value / 255);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The highest value of each channel of images of the same size around each block of POOL x POOL
|
|
55
|
+
* pixels: in the block and its 8 neighbors, as RGBA bytes. A ray sampled less than a block apart
|
|
56
|
+
* then finds every cell of rain it passes, however small.
|
|
57
|
+
* @param {Array<{width: number, height: number, data: Uint8ClampedArray} | undefined>} images
|
|
58
|
+
*/
|
|
59
|
+
function maxPool(images) {
|
|
60
|
+
const {width, height} = /** @type {{width: number, height: number}} */ (images[0]);
|
|
61
|
+
const w = Math.ceil(width / POOL);
|
|
62
|
+
const h = Math.ceil(height / POOL);
|
|
63
|
+
const pooled = new Uint8Array(w * h * 4);
|
|
64
|
+
for (const image of images) {
|
|
65
|
+
if (!image) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
for (let y = 0; y < height; y++) {
|
|
69
|
+
for (let x = 0; x < width; x++) {
|
|
70
|
+
const i = 4 * (Math.floor(y / POOL) * w + Math.floor(x / POOL));
|
|
71
|
+
for (let channel = 0; channel < 4; channel++) {
|
|
72
|
+
pooled[i + channel] = Math.max(pooled[i + channel], image.data[4 * (y * width + x) + channel]);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const data = new Uint8Array(w * h * 4);
|
|
78
|
+
for (let y = 0; y < h; y++) {
|
|
79
|
+
for (let x = 0; x < w; x++) {
|
|
80
|
+
for (let channel = 0; channel < 4; channel++) {
|
|
81
|
+
let max = 0;
|
|
82
|
+
for (let dy = Math.max(y - 1, 0); dy <= Math.min(y + 1, h - 1); dy++) {
|
|
83
|
+
for (let dx = Math.max(x - 1, 0); dx <= Math.min(x + 1, w - 1); dx++) {
|
|
84
|
+
max = Math.max(max, pooled[4 * (dy * w + dx) + channel]);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
data[4 * (y * w + x) + channel] = max;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return {width: w, height: h, data};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* A map of the rain for the shaders, WeatherMap.glsl's uniforms, and its
|
|
96
|
+
* intensity at a point on the CPU, bilinear as the shaders sample it. Without
|
|
97
|
+
* a map, the intensity everywhere is the empty value: 1 for rain everywhere,
|
|
98
|
+
* 0 for none. A next map, over the same rectangle and of the same size, crossfades from the map to
|
|
99
|
+
* it by the blend, as the radar from one hour to the next.
|
|
100
|
+
*/
|
|
101
|
+
export default class WeatherMap {
|
|
102
|
+
/**
|
|
103
|
+
* @param {number} empty the intensity everywhere without a map
|
|
104
|
+
*/
|
|
105
|
+
constructor(empty) {
|
|
106
|
+
this.empty_ = empty;
|
|
107
|
+
/** @type {WeatherMapOptions | undefined} */
|
|
108
|
+
this.map_ = undefined;
|
|
109
|
+
/** @type {{width: number, height: number, data: Uint8ClampedArray} | undefined} */
|
|
110
|
+
this.data_ = undefined;
|
|
111
|
+
/** @type {WeatherMapOptions | undefined} */
|
|
112
|
+
this.nextMap_ = undefined;
|
|
113
|
+
/** @type {{width: number, height: number, data: Uint8ClampedArray} | undefined} */
|
|
114
|
+
this.nextData_ = undefined;
|
|
115
|
+
/**
|
|
116
|
+
* From the map, 0, to the next one, 1.
|
|
117
|
+
*/
|
|
118
|
+
this.blend = 0;
|
|
119
|
+
/**
|
|
120
|
+
* The rectangle in the ellipsoid's texture coordinates: its west and
|
|
121
|
+
* south, and one over its width and height; the whole globe without a map.
|
|
122
|
+
*/
|
|
123
|
+
this.bounds = new Cartesian4(0, 0, 1, 1);
|
|
124
|
+
/**
|
|
125
|
+
* The highest intensity on the map, 0 to 1: the empty value without a map; and the highest
|
|
126
|
+
* value of each of its channels.
|
|
127
|
+
*/
|
|
128
|
+
this.maxIntensity = empty;
|
|
129
|
+
this.maxValues = [empty, empty, empty, empty];
|
|
130
|
+
// made when the shaders first need it: a stage given an image makes its texture a frame after
|
|
131
|
+
// it is set, and draws without one in between
|
|
132
|
+
/** @type {any} */
|
|
133
|
+
this.texture_ = undefined;
|
|
134
|
+
/** @type {any} */
|
|
135
|
+
this.nextTexture_ = undefined;
|
|
136
|
+
/** @type {{width: number, height: number, data: Uint8Array} | undefined} */
|
|
137
|
+
this.maxImage_ = undefined;
|
|
138
|
+
/** @type {any} */
|
|
139
|
+
this.maxTexture_ = undefined;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
get map() {
|
|
143
|
+
return this.map_;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
set map(value) {
|
|
147
|
+
this.map_ = value;
|
|
148
|
+
if (!value) {
|
|
149
|
+
this.data_ = undefined;
|
|
150
|
+
Cartesian4.fromElements(0, 0, 1, 1, this.bounds);
|
|
151
|
+
this.maxIntensity = this.empty_;
|
|
152
|
+
} else {
|
|
153
|
+
const {image, rectangle} = value;
|
|
154
|
+
this.data_ = imageData(image);
|
|
155
|
+
Cartesian4.fromElements(
|
|
156
|
+
rectangle.west / TWO_PI + 0.5,
|
|
157
|
+
rectangle.south / Math.PI + 0.5,
|
|
158
|
+
TWO_PI / (rectangle.east - rectangle.west),
|
|
159
|
+
Math.PI / (rectangle.north - rectangle.south),
|
|
160
|
+
this.bounds,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
this.updateMaxIntensity_();
|
|
164
|
+
// made again from the new image when the shaders next need it
|
|
165
|
+
this.texture_ = this.texture_?.destroy();
|
|
166
|
+
this.clearMaxImage_();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
get nextMap() {
|
|
170
|
+
return this.nextMap_;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
set nextMap(value) {
|
|
174
|
+
this.nextMap_ = value;
|
|
175
|
+
this.nextData_ = value && imageData(value.image);
|
|
176
|
+
this.updateMaxIntensity_();
|
|
177
|
+
this.nextTexture_ = this.nextTexture_?.destroy();
|
|
178
|
+
this.clearMaxImage_();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
clearMaxImage_() {
|
|
182
|
+
this.maxImage_ = undefined;
|
|
183
|
+
this.maxTexture_ = this.maxTexture_?.destroy();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* The highest intensity around each block of the map's pixels, over the map and the next one,
|
|
188
|
+
* made when first needed: undefined without a map.
|
|
189
|
+
*/
|
|
190
|
+
maxImage() {
|
|
191
|
+
if (this.map_ && !this.maxImage_) {
|
|
192
|
+
this.maxImage_ = maxPool([this.data_, this.nextData_]);
|
|
193
|
+
}
|
|
194
|
+
return this.maxImage_;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
updateMaxIntensity_() {
|
|
198
|
+
const map = highest(this.data_);
|
|
199
|
+
const next = highest(this.nextMap_ ? this.nextData_ : undefined);
|
|
200
|
+
this.maxValues = this.map_ ? map.map((value, channel) => Math.max(value, next[channel])) : [this.empty_, this.empty_, this.empty_, this.empty_];
|
|
201
|
+
this.maxIntensity = this.maxValues[0];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* The intensity at a point, 0 to 1, bilinear as the shaders sample it: the
|
|
206
|
+
* empty value without a map, 0 outside its rectangle.
|
|
207
|
+
* @param {number} longitude radians
|
|
208
|
+
* @param {number} latitude radians
|
|
209
|
+
*/
|
|
210
|
+
intensityAt(longitude, latitude) {
|
|
211
|
+
if (!this.map_ || !this.data_) {
|
|
212
|
+
return this.empty_;
|
|
213
|
+
}
|
|
214
|
+
const {west, south, east, north} = this.map_.rectangle;
|
|
215
|
+
const u = (longitude - west) / (east - west);
|
|
216
|
+
const v = (latitude - south) / (north - south);
|
|
217
|
+
if (u < 0 || u > 1 || v < 0 || v > 1) {
|
|
218
|
+
return 0;
|
|
219
|
+
}
|
|
220
|
+
const intensity = this.sample_(this.data_, u, v);
|
|
221
|
+
if (!this.nextData_) {
|
|
222
|
+
return intensity;
|
|
223
|
+
}
|
|
224
|
+
return intensity + (this.sample_(this.nextData_, u, v) - intensity) * this.blend;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* An image's intensity, bilinear, at a point of its rectangle, 0 to 1 from its west and south.
|
|
229
|
+
* @param {{width: number, height: number, data: Uint8ClampedArray}} image
|
|
230
|
+
* @param {number} u
|
|
231
|
+
* @param {number} v
|
|
232
|
+
*/
|
|
233
|
+
sample_(image, u, v) {
|
|
234
|
+
const {width, height, data} = image;
|
|
235
|
+
// the image's rows go down from the north
|
|
236
|
+
const x = Math.min(Math.max(u * width - 0.5, 0), width - 1);
|
|
237
|
+
const y = Math.min(Math.max((1 - v) * height - 0.5, 0), height - 1);
|
|
238
|
+
const x0 = Math.floor(x);
|
|
239
|
+
const y0 = Math.floor(y);
|
|
240
|
+
const x1 = Math.min(x0 + 1, width - 1);
|
|
241
|
+
const y1 = Math.min(y0 + 1, height - 1);
|
|
242
|
+
const red = (/** @type {number} */ column, /** @type {number} */ row) => data[4 * (row * width + column)] / 255;
|
|
243
|
+
const upper = red(x0, y0) + (red(x1, y0) - red(x0, y0)) * (x - x0);
|
|
244
|
+
const lower = red(x0, y1) + (red(x1, y1) - red(x0, y1)) * (x - x0);
|
|
245
|
+
return upper + (lower - upper) * (y - y0);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* The map's texture, a pixel of the empty value without a map.
|
|
250
|
+
* @param {any} context the scene's context
|
|
251
|
+
*/
|
|
252
|
+
texture(context) {
|
|
253
|
+
if (!this.texture_) {
|
|
254
|
+
const value = 255 * this.empty_;
|
|
255
|
+
this.texture_ = new Texture({context, source: textureSource(this.map_?.image ?? new ImageData(new Uint8ClampedArray([value, value, value, 255]), 1, 1))});
|
|
256
|
+
}
|
|
257
|
+
return this.texture_;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* The next map's texture, the map's without one.
|
|
262
|
+
* @param {any} context the scene's context
|
|
263
|
+
*/
|
|
264
|
+
nextTexture(context) {
|
|
265
|
+
if (!this.nextMap_) {
|
|
266
|
+
return this.texture(context);
|
|
267
|
+
}
|
|
268
|
+
this.nextTexture_ ??= new Texture({context, source: textureSource(this.nextMap_.image)});
|
|
269
|
+
return this.nextTexture_;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* The max map's texture, a pixel of the empty value without a map.
|
|
274
|
+
* @param {any} context the scene's context
|
|
275
|
+
*/
|
|
276
|
+
maxTexture(context) {
|
|
277
|
+
if (!this.maxTexture_) {
|
|
278
|
+
const value = 255 * this.empty_;
|
|
279
|
+
const {width, height, data} = this.maxImage() ?? {width: 1, height: 1, data: new Uint8Array([value, value, value, 255])};
|
|
280
|
+
this.maxTexture_ = new Texture({context, source: {width, height, arrayBufferView: data}});
|
|
281
|
+
}
|
|
282
|
+
return this.maxTexture_;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
destroy() {
|
|
286
|
+
this.texture_ = this.texture_?.destroy();
|
|
287
|
+
this.nextTexture_ = this.nextTexture_?.destroy();
|
|
288
|
+
this.maxTexture_ = this.maxTexture_?.destroy();
|
|
289
|
+
}
|
|
290
|
+
}
|