@geoblocks/cesium-binoculars 0.1.0 → 0.2.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.
@@ -0,0 +1,64 @@
1
+ import { PostProcessStageComposite } from "@cesium/engine";
2
+ export default class CesiumBinoculars {
3
+ viewer: import("@cesium/engine").CesiumWidget;
4
+ zoomFactor_: number;
5
+ maxMagnification: number;
6
+ originalFov_: number | undefined;
7
+ active_: boolean;
8
+ /**
9
+ * @type {Pick<import('@cesium/engine').ScreenSpaceCameraController, 'enableZoom' | 'enableRotate' | 'enableTilt' | 'lookEventTypes'> | undefined}
10
+ */
11
+ previousController_: Pick<import('@cesium/engine').ScreenSpaceCameraController, 'enableZoom' | 'enableRotate' | 'enableTilt' | 'lookEventTypes'> | undefined;
12
+ /**
13
+ * @type {ReturnType<import('@cesium/engine').ScreenSpaceEventHandler['getInputAction']> | undefined}
14
+ */
15
+ previousAction_: ReturnType<import('@cesium/engine').ScreenSpaceEventHandler['getInputAction']> | undefined;
16
+ /** @type {PostProcessStageComposite | undefined} */
17
+ lens_: PostProcessStageComposite | undefined;
18
+ previousDepthTestAgainstTerrain_: boolean;
19
+ reticle_: boolean;
20
+ onPreRender_: () => void;
21
+ onMouseWheel_: (delta: number) => void;
22
+ /**
23
+ * @param {import('@cesium/engine').CesiumWidget} viewer
24
+ * @param {number} [zoomFactor] magnification per mouse wheel notch
25
+ * @param {number} [maxMagnification]
26
+ */
27
+ constructor(viewer: import('@cesium/engine').CesiumWidget, zoomFactor?: number, maxMagnification?: number);
28
+ get active(): boolean;
29
+ set active(active: boolean);
30
+ addLens_(): void;
31
+ removeLens_(): void;
32
+ /**
33
+ * Whether to draw a rangefinder reticle: a mil scale (milliradians) that
34
+ * grows with the magnification, so an object's size is its distance times
35
+ * the mils it covers, divided by 1000.
36
+ */
37
+ get reticle(): boolean;
38
+ set reticle(value: boolean);
39
+ /**
40
+ * Distance in meters from the camera to what is in the middle of the view,
41
+ * terrain or 3D Tiles; undefined for the sky, when inactive or when picking
42
+ * positions is not supported. Picked when read.
43
+ */
44
+ get distance(): number | undefined;
45
+ get frustum_(): import('@cesium/core').PerspectiveFrustum;
46
+ /**
47
+ * Magnification relative to the field of view when the binoculars were activated, 1 when inactive.
48
+ * The magnification is proportional to 1 / tan(fov / 2).
49
+ */
50
+ get magnification(): number;
51
+ /**
52
+ * Only applies while active; clamped between 1 and maxMagnification.
53
+ */
54
+ set magnification(magnification: number);
55
+ /**
56
+ * In 3D, Cesium's look turns the camera around its own axes, which rolls the view: keep the horizon level.
57
+ */
58
+ onPreRender(): void;
59
+ /**
60
+ * @param {number} delta 120 per mouse wheel notch, positive to zoom in; trackpads send smaller values
61
+ */
62
+ onMouseWheel(delta: number): void;
63
+ }
64
+ //# sourceMappingURL=cesium-binoculars.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cesium-binoculars.d.ts","sourceRoot":"","sources":["../src/cesium-binoculars.js"],"names":[],"mappings":"AAKA,OAAO,EAGL,yBAAyB,EAG1B,MAAM,gBAAgB,CAAC;AASxB,MAAM,CAAC,OAAO,OAAO,gBAAgB;IAO5B,MAAM;IACN,WAAW;IACX,gBAAgB;IAChB,YAAY;IACZ,OAAO;IAEZ;;OAEG;IACE,mBAAmB,EAFd,IAAI,CAAC,OAAO,gBAAgB,EAAE,2BAA2B,EAAE,YAAY,GAAG,cAAc,GAAG,YAAY,GAAG,gBAAgB,CAAC,GAAG,SAAS;IAGjJ;;OAEG;IACE,eAAe,EAFV,UAAU,CAAC,OAAO,gBAAgB,EAAE,uBAAuB,CAAC,gBAAgB,CAAC,CAAC,GAAG,SAAS;IAGpG,oDAAoD;IAC/C,KAAK,EADC,yBAAyB,GAAG,SAAS;IAE3C,gCAAgC;IAChC,QAAQ;IACR,YAAY;IACZ,aAAa,UAoJT,MAAM;IA7KjB;;;;OAIG;IACH,YAAY,MAAM,EAJP,OAAO,gBAAgB,EAAE,YAIlB,EAAE,UAAU,AAH3B,CACA,EADQ,MAG0B,EAAE,gBAAgB,AAFpD,CAAkC,EAA1B,MAEiD,EAqB3D;IAED,IAAI,MAAM,YAET;IAED,IAAI,MAAM,CAAC,MAAM,SAAA,EAiChB;IAED,QAAQ,SA2BP;IAED,WAAW,SAQV;IAED;;;;OAIG;IACH,IAAI,OAAO,YAEV;IAED,IAAI,OAAO,CAAC,KAAK,SAAA,EAGhB;IAED;;;;OAIG;IACH,IAAI,QAAQ,uBASX;IAED,IAAI,QAAQ,IACQ,OAAO,cAAc,EAAE,kBAAkB,CAC5D;IAED;;;OAGG;IACH,IAAI,aAAa,WAKhB;IAED;;OAEG;IACH,IAAI,aAAa,CAAC,aAAa,QAAA,EAM9B;IAED;;OAEG;IACH,WAAW,SAKV;IAED;;OAEG;IACH,YAAY,CAAC,KAAK,EAFP,MAEO,QAEjB;CACF"}
@@ -0,0 +1,177 @@
1
+ import { Cartesian2, Cartesian3, Math as CesiumMath, } from "@cesium/core";
2
+ import { CameraEventType, PostProcessStage, PostProcessStageComposite, PostProcessStageLibrary, ScreenSpaceEventType, } from "@cesium/engine";
3
+ import EyeFromDepth from "./shaders/EyeFromDepth.js";
4
+ import Lens from "./shaders/Lens.js";
5
+ // blur of the depth of field and of the rim, the sigma of Cesium's blur stage
6
+ const LENS_BLUR = 3;
7
+ const middleScratch = new Cartesian2();
8
+ export default class CesiumBinoculars {
9
+ /**
10
+ * @param {import('@cesium/engine').CesiumWidget} viewer
11
+ * @param {number} [zoomFactor] magnification per mouse wheel notch
12
+ * @param {number} [maxMagnification]
13
+ */
14
+ constructor(viewer, zoomFactor = 1.25, maxMagnification = 20) {
15
+ this.viewer = viewer;
16
+ this.zoomFactor_ = zoomFactor;
17
+ this.maxMagnification = maxMagnification;
18
+ this.originalFov_ = undefined;
19
+ this.active_ = false;
20
+ /**
21
+ * @type {Pick<import('@cesium/engine').ScreenSpaceCameraController, 'enableZoom' | 'enableRotate' | 'enableTilt' | 'lookEventTypes'> | undefined}
22
+ */
23
+ this.previousController_ = undefined;
24
+ /**
25
+ * @type {ReturnType<import('@cesium/engine').ScreenSpaceEventHandler['getInputAction']> | undefined}
26
+ */
27
+ this.previousAction_ = undefined;
28
+ /** @type {PostProcessStageComposite | undefined} */
29
+ this.lens_ = undefined;
30
+ this.previousDepthTestAgainstTerrain_ = false;
31
+ this.reticle_ = false;
32
+ this.onPreRender_ = this.onPreRender.bind(this);
33
+ this.onMouseWheel_ = this.onMouseWheel.bind(this);
34
+ }
35
+ get active() {
36
+ return this.active_;
37
+ }
38
+ set active(active) {
39
+ if (active === this.active_) {
40
+ return;
41
+ }
42
+ this.active_ = active;
43
+ const controller = this.viewer.scene.screenSpaceCameraController;
44
+ const handler = this.viewer.screenSpaceEventHandler;
45
+ if (active) {
46
+ this.originalFov_ = this.frustum_.fov;
47
+ const { enableZoom, enableRotate, enableTilt, lookEventTypes } = controller;
48
+ this.previousController_ = { enableZoom, enableRotate, enableTilt, lookEventTypes };
49
+ // stand still: a drag turns the camera in place instead of moving it around the globe
50
+ controller.enableZoom = false;
51
+ controller.enableRotate = false;
52
+ controller.enableTilt = false;
53
+ controller.lookEventTypes = CameraEventType.LEFT_DRAG;
54
+ this.previousAction_ = handler.getInputAction(ScreenSpaceEventType.WHEEL);
55
+ handler.setInputAction(this.onMouseWheel_, ScreenSpaceEventType.WHEEL);
56
+ this.addLens_();
57
+ this.viewer.scene.preRender.addEventListener(this.onPreRender_);
58
+ }
59
+ else {
60
+ this.viewer.scene.preRender.removeEventListener(this.onPreRender_);
61
+ this.removeLens_();
62
+ this.frustum_.fov = this.originalFov_;
63
+ Object.assign(controller, this.previousController_);
64
+ if (this.previousAction_) {
65
+ handler.setInputAction(this.previousAction_, ScreenSpaceEventType.WHEEL);
66
+ }
67
+ else {
68
+ handler.removeInputAction(ScreenSpaceEventType.WHEEL);
69
+ }
70
+ }
71
+ // requestRenderMode: adding or removing the lens stage does not request a render
72
+ this.viewer.scene.requestRender();
73
+ }
74
+ addLens_() {
75
+ const scene = this.viewer.scene;
76
+ // the post-process stages only get the depth of the terrain when it is depth tested
77
+ if (scene.globe) {
78
+ this.previousDepthTestAgainstTerrain_ = scene.globe.depthTestAgainstTerrain;
79
+ scene.globe.depthTestAgainstTerrain = true;
80
+ }
81
+ const blur = PostProcessStageLibrary.createBlurStage();
82
+ this.lens_ = new PostProcessStageComposite({
83
+ stages: [
84
+ blur,
85
+ new PostProcessStage({
86
+ fragmentShader: EyeFromDepth + Lens,
87
+ uniforms: {
88
+ blurTexture: blur.name,
89
+ magnification: () => this.magnification,
90
+ reticle: () => (this.reticle_ ? 1 : 0),
91
+ pixelsPerMil: () => (scene.drawingBufferHeight / 2 / Math.tan(/** @type {number} */ (this.frustum_.fovy) / 2)) * 0.001,
92
+ },
93
+ }),
94
+ ],
95
+ // both read the scene
96
+ inputPreviousStageTexture: false,
97
+ uniforms: blur.uniforms,
98
+ });
99
+ this.lens_.uniforms.sigma = LENS_BLUR;
100
+ scene.postProcessStages.add(this.lens_);
101
+ }
102
+ removeLens_() {
103
+ const scene = this.viewer.scene;
104
+ // removing a stage also destroys it
105
+ scene.postProcessStages.remove(/** @type {PostProcessStageComposite} */ (this.lens_));
106
+ this.lens_ = undefined;
107
+ if (scene.globe) {
108
+ scene.globe.depthTestAgainstTerrain = this.previousDepthTestAgainstTerrain_;
109
+ }
110
+ }
111
+ /**
112
+ * Whether to draw a rangefinder reticle: a mil scale (milliradians) that
113
+ * grows with the magnification, so an object's size is its distance times
114
+ * the mils it covers, divided by 1000.
115
+ */
116
+ get reticle() {
117
+ return this.reticle_;
118
+ }
119
+ set reticle(value) {
120
+ this.reticle_ = value;
121
+ this.viewer.scene.requestRender();
122
+ }
123
+ /**
124
+ * Distance in meters from the camera to what is in the middle of the view,
125
+ * terrain or 3D Tiles; undefined for the sky, when inactive or when picking
126
+ * positions is not supported. Picked when read.
127
+ */
128
+ get distance() {
129
+ const scene = this.viewer.scene;
130
+ if (!this.active_ || !scene.pickPositionSupported) {
131
+ return undefined;
132
+ }
133
+ const canvas = scene.canvas;
134
+ const middle = Cartesian2.fromElements(canvas.clientWidth / 2, canvas.clientHeight / 2, middleScratch);
135
+ const position = scene.pickPosition(middle);
136
+ return position && Cartesian3.distance(scene.camera.positionWC, position);
137
+ }
138
+ get frustum_() {
139
+ return /** @type {import('@cesium/core').PerspectiveFrustum} */ (this.viewer.scene.camera.frustum);
140
+ }
141
+ /**
142
+ * Magnification relative to the field of view when the binoculars were activated, 1 when inactive.
143
+ * The magnification is proportional to 1 / tan(fov / 2).
144
+ */
145
+ get magnification() {
146
+ if (!this.active_) {
147
+ return 1;
148
+ }
149
+ return Math.tan(/** @type {number} */ (this.originalFov_) / 2) / Math.tan(/** @type {number} */ (this.frustum_.fov) / 2);
150
+ }
151
+ /**
152
+ * Only applies while active; clamped between 1 and maxMagnification.
153
+ */
154
+ set magnification(magnification) {
155
+ if (!this.active_) {
156
+ return;
157
+ }
158
+ const clamped = CesiumMath.clamp(magnification, 1, this.maxMagnification);
159
+ this.frustum_.fov = 2 * Math.atan(Math.tan(/** @type {number} */ (this.originalFov_) / 2) / clamped);
160
+ }
161
+ /**
162
+ * In 3D, Cesium's look turns the camera around its own axes, which rolls the view: keep the horizon level.
163
+ */
164
+ onPreRender() {
165
+ const camera = this.viewer.scene.camera;
166
+ if (Math.abs(CesiumMath.negativePiToPi(camera.roll)) > CesiumMath.EPSILON6) {
167
+ camera.setView({ orientation: { heading: camera.heading, pitch: camera.pitch, roll: 0 } });
168
+ }
169
+ }
170
+ /**
171
+ * @param {number} delta 120 per mouse wheel notch, positive to zoom in; trackpads send smaller values
172
+ */
173
+ onMouseWheel(delta) {
174
+ this.magnification *= Math.pow(this.zoomFactor_, delta / 120);
175
+ }
176
+ }
177
+ //# sourceMappingURL=cesium-binoculars.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cesium-binoculars.js","sourceRoot":"","sources":["../src/cesium-binoculars.js"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ declare const _default = "// Eye coordinates of the pixel at uv, from the depth texture; w is 0 for the sky. With a\n// logarithmic depth buffer, czm_readDepth's perspective depth is about 1 beyond a few meters,\n// so the distance is decoded from the logarithmic depth directly.\nprecision highp sampler2D;\nvec4 eyeAt(sampler2D depthTexture, vec2 uv) {\n float raw = texture(depthTexture, uv).r;\n // the pixel's view ray, through the near plane\n vec4 ray = czm_inverseProjection * vec4(2.0 * uv - 1.0, -1.0, 1.0);\n ray.xyz /= ray.w;\n if (raw >= 1.0) {\n return vec4(normalize(ray.xyz), 0.0);\n }\n#ifdef LOG_DEPTH\n float viewDepth = exp2(raw * czm_log2FarDepthFromNearPlusOne) - 1.0 + czm_currentFrustum.x;\n#else\n vec4 eye = czm_inverseProjection * vec4(2.0 * uv - 1.0, 2.0 * raw - 1.0, 1.0);\n float viewDepth = -eye.z / eye.w;\n#endif\n return vec4(ray.xyz * (viewDepth / -ray.z), 1.0);\n}\n";
2
+ export default _default;
3
+ //# sourceMappingURL=EyeFromDepth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EyeFromDepth.d.ts","sourceRoot":"","sources":["../../src/shaders/EyeFromDepth.js"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ // This file is automatically rebuilt from EyeFromDepth.glsl.
2
+ export default "// Eye coordinates of the pixel at uv, from the depth texture; w is 0 for the sky. With a\n// logarithmic depth buffer, czm_readDepth's perspective depth is about 1 beyond a few meters,\n// so the distance is decoded from the logarithmic depth directly.\nprecision highp sampler2D;\nvec4 eyeAt(sampler2D depthTexture, vec2 uv) {\n float raw = texture(depthTexture, uv).r;\n // the pixel's view ray, through the near plane\n vec4 ray = czm_inverseProjection * vec4(2.0 * uv - 1.0, -1.0, 1.0);\n ray.xyz /= ray.w;\n if (raw >= 1.0) {\n return vec4(normalize(ray.xyz), 0.0);\n }\n#ifdef LOG_DEPTH\n float viewDepth = exp2(raw * czm_log2FarDepthFromNearPlusOne) - 1.0 + czm_currentFrustum.x;\n#else\n vec4 eye = czm_inverseProjection * vec4(2.0 * uv - 1.0, 2.0 * raw - 1.0, 1.0);\n float viewDepth = -eye.z / eye.w;\n#endif\n return vec4(ray.xyz * (viewDepth / -ray.z), 1.0);\n}\n";
3
+ //# sourceMappingURL=EyeFromDepth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EyeFromDepth.js","sourceRoot":"","sources":["../../src/shaders/EyeFromDepth.js"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,eAAe,23BAA23B,CAAC"}
@@ -0,0 +1,3 @@
1
+ declare const _default = "// The eyepieces, two overlapping circles merged into one wide opening in a 160x90 box fitted to\n// the canvas, near black around them, with an uneven edge. Inside, lens effects: a slight fisheye\n// and color fringes toward the rim, both stronger at high magnification, the light falling off and\n// the image softening toward the rim, a violet and orange edge, specks of dirt on the glass, and\n// a depth of field focused on the middle of the view that gets shallower as the magnification\n// grows. Optionally, a rangefinder reticle, a mil scale that grows with the magnification. Picking\n// ignores the fisheye, so near the rim the picked point is a little off what is shown.\nprecision highp sampler2D;\nuniform sampler2D colorTexture;\nuniform sampler2D blurTexture;\nuniform sampler2D depthTexture;\nuniform float magnification;\n// 1 to draw the reticle, 0 not to\nuniform float reticle;\n// device pixels per milliradian, from the field of view\nuniform float pixelsPerMil;\nin vec2 v_textureCoordinates;\n\nconst vec2 BOX = vec2(160.0, 90.0);\nconst vec2 LEFT = vec2(55.2, 45.0);\nconst vec2 RIGHT = vec2(104.8, 45.0);\nconst float RADIUS = 43.3;\n// how much the notches between the eyepieces fill in, in box units\nconst float MERGE = 6.0;\n// half width of the soft edge of the eyepieces, in box units\nconst float EDGE = 3.5;\n// around the eyepieces\nconst vec3 SURROUND = vec3(0.02);\n// the edge of the opening for red is this much further right, and for blue further left, in box\n// units, so it fades through violet on the left and orange on the right\nconst float EDGE_SHIFT = 2.0;\n// the edge wanders in and out by up to this much, in box units, with bumps about BUMP apart\nconst float UNEVEN = 1.2;\nconst float BUMP = 9.0;\n// specks of dirt on the glass: cells per canvas height, share of cells with a speck, how dark\nconst float DIRT_CELLS = 45.0;\nconst float DIRT_DENSITY = 0.015;\nconst float DIRT = 0.55;\n// the effects only apply within this distance of the rim, in box units, so the middle of the\n// view, where the eyepieces overlap, stays clean\nconst float BAND = 20.0;\n// fisheye: on the rim, sample up to this fraction further from the center, at 5x\nconst float FISHEYE = 0.07;\n// color fringe on the rim, in box units, at 5x\nconst float FRINGE = 1.0;\n// light lost on the rim of an eyepiece\nconst float VIGNETTE = 0.25;\n// a ring this wide inside the edge, in box units, where the glass bends the light more: it starts\n// abruptly and shows the image from RING_PULL further in, blurred, with a wider color fringe\nconst float RING = 4.5;\nconst float RING_PULL = 1.5;\nconst float RING_FRINGE = 0.15;\n// blur on the rim, off the optical axis\nconst float RIM_BLUR = 1.0;\n// sharp within this many factors of two of the focal distance at 1x, narrower as 1 / magnification\nconst float FOCUS_RANGE = 2.0;\n\n// ticks of the reticle closer than this, in CSS pixels, are left out for every 5th or 10th\nconst float MIN_TICK_SPACING = 6.0;\n// ticks on each side of the middle, and the empty gap in the middle, in ticks\nconst float TICKS = 10.0;\nconst float GAP = 1.0;\n// dark lines in a light halo, readable on dark forest as on snow\nconst vec4 RETICLE = vec4(0.05, 0.05, 0.05, 0.9);\nconst vec4 HALO = vec4(0.9, 0.9, 0.85, 0.6);\n\n// coverage of a line this far from the pixel, width pixels wide\nfloat line(float distance, float width) {\n return 1.0 - smoothstep(0.5 * width - 0.5, 0.5 * width + 0.5, distance);\n}\n\n// coverage of the reticle at a pixel, in device pixels from the middle of the view, with lines\n// width device pixels wide: a cross of mil scales, ticks every 1, 5 or 10 mils, longer every 5th\nfloat reticleAt(vec2 p, float width) {\n float minSpacing = MIN_TICK_SPACING * czm_pixelRatio;\n float spacing = pixelsPerMil;\n if (spacing < minSpacing) {\n spacing *= 5.0;\n }\n if (spacing < minSpacing) {\n spacing *= 2.0;\n }\n float coverage = 0.0;\n // one axis at a time: along is the distance along the scale, across the distance from it\n for (int axis = 0; axis < 2; axis++) {\n float along = axis == 0 ? p.x : p.y;\n float across = axis == 0 ? p.y : p.x;\n float fromMiddle = abs(along);\n if (fromMiddle < GAP * spacing || fromMiddle > TICKS * spacing + width) {\n continue;\n }\n float tick = floor(fromMiddle / spacing + 0.5);\n float tickLength = (mod(tick, 5.0) == 0.0 ? 6.0 : 3.0) * czm_pixelRatio + 0.5 * width;\n coverage = max(coverage, line(abs(across), width));\n if (abs(across) < tickLength) {\n coverage = max(coverage, line(abs(fromMiddle - tick * spacing), width));\n }\n }\n return coverage;\n}\n\nfloat hash(vec2 p) {\n return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);\n}\n\n// coverage of the dirt at a pixel, in canvas heights: at most one small speck per cell, some\n// with a smaller one next to it, so they are not all round\nfloat dirtAt(vec2 p) {\n // before the branch: derivatives are undefined where neighboring pixels take different ones,\n // and not from inCell, which jumps at the cell borders\n float soft = 0.5 * fwidth(p.x * DIRT_CELLS);\n vec2 cell = floor(p * DIRT_CELLS);\n if (hash(cell) > DIRT_DENSITY) {\n return 0.0;\n }\n vec2 inCell = fract(p * DIRT_CELLS);\n vec2 center = 0.35 + 0.3 * vec2(hash(cell + 17.0), hash(cell + 31.0));\n float radius = 0.04 + 0.12 * hash(cell + 47.0) * hash(cell + 53.0);\n vec2 satellite = center + (vec2(hash(cell + 61.0), hash(cell + 67.0)) - 0.5) * 3.0 * radius;\n float speck = 1.0 - smoothstep(radius - soft, radius + soft, length(inCell - center));\n float small = 1.0 - smoothstep(0.6 * radius - soft, 0.6 * radius + soft, length(inCell - satellite));\n // some specks darker than others\n return (0.4 + 0.6 * hash(cell + 79.0)) * max(speck, small * step(0.5, hash(cell + 71.0)));\n}\n\n// smooth noise between -1 and 1, one bump per unit\nfloat noise(vec2 p) {\n vec2 i = floor(p);\n vec2 f = fract(p);\n f = f * f * (3.0 - 2.0 * f);\n float a = hash(i);\n float b = hash(i + vec2(1.0, 0.0));\n float c = hash(i + vec2(0.0, 1.0));\n float d = hash(i + vec2(1.0, 1.0));\n return 2.0 * mix(mix(a, b, f.x), mix(c, d, f.x), f.y) - 1.0;\n}\n\n// distance in box units to the edge of the opening, negative inside: a smooth union of the two\n// circles, which fills in the notches between them, made uneven\nfloat toRimAt(vec2 position) {\n float toLeftRim = length(position - LEFT) - RADIUS;\n float toRightRim = length(position - RIGHT) - RADIUS;\n float h = clamp(0.5 + 0.5 * (toRightRim - toLeftRim) / MERGE, 0.0, 1.0);\n float toRim = mix(toRightRim, toLeftRim, h) - MERGE * h * (1.0 - h);\n vec2 p = position / BUMP;\n return toRim + UNEVEN * (0.7 * noise(p) + 0.3 * noise(2.7 * p + 5.0));\n}\n\n// offsets in box units for one eyepiece: xy the fisheye, zw the color fringe\nvec4 lensOffsets(vec2 toCenter, float strength) {\n float fromCenter = length(toCenter);\n // 0 up to the band, 1 on the rim\n float rim = 1.0 - clamp((RADIUS - fromCenter) / BAND, 0.0, 1.0);\n vec2 direction = toCenter / max(fromCenter, 1e-3);\n return strength * vec4(toCenter * FISHEYE * rim * rim, direction * FRINGE * rim * rim * rim);\n}\n\n// the scene at uv, blurred by the depth of field around the focal distance and toward the rim\nvec4 sampleAt(vec2 uv, float focalDistance, float rimBlur) {\n vec4 eye = eyeAt(depthTexture, uv);\n float fromCamera = eye.w == 0.0 ? 1e30 : length(eye.xyz);\n float range = FOCUS_RANGE / magnification;\n float defocus = focalDistance > 0.0\n ? smoothstep(range, 2.0 * range, abs(log2(fromCamera / focalDistance)))\n : 0.0;\n // no depth of field at 1x, fully from 3x\n defocus *= smoothstep(1.0, 3.0, magnification);\n return mix(texture(colorTexture, uv), texture(blurTexture, uv), max(defocus, rimBlur));\n}\n\nvoid main() {\n vec2 size = czm_viewport.zw;\n float scale = min(size.x / BOX.x, size.y / BOX.y);\n vec2 position = (v_textureCoordinates * size - 0.5 * (size - scale * BOX)) / scale;\n vec2 toLeft = position - LEFT;\n vec2 toRight = position - RIGHT;\n\n // cheap optics show their flaws more when zoomed in: 0.6 at 1x, 1 at 5x, 2 at 20x and beyond\n float strength = clamp(0.5 + magnification / 10.0, 0.6, 2.0);\n vec4 left = lensOffsets(toLeft, strength);\n vec4 right = lensOffsets(toRight, strength);\n // blend the two eyepieces where they overlap, so there is no seam between them\n float weight = smoothstep(-6.0, 6.0, length(toRight) - length(toLeft));\n float toRim = toRimAt(position);\n float ring = smoothstep(-RING, -RING + 1.5, toRim);\n vec2 outward = normalize(mix(toRight, toLeft, weight));\n vec4 offsets = (mix(right, left, weight) + ring * vec4(-RING_PULL * outward, RING_FRINGE * outward)) * scale / vec4(size, size);\n\n // focused on what is in the middle of the view; the sky there is at infinity\n vec4 target = eyeAt(depthTexture, vec2(0.5));\n float focalDistance = target.w == 0.0 ? 0.0 : length(target.xyz);\n // 0 in the middle of an eyepiece, 1 on the rim; from the edge of the opening, so the middle of\n // the view, where the eyepieces meet, is not dimmed\n float fromAxis = 1.0 + toRim / RADIUS;\n float rimBlur = max(RIM_BLUR * smoothstep(0.5, 0.95, fromAxis), ring);\n\n vec2 uv = v_textureCoordinates + offsets.xy;\n vec4 center = sampleAt(uv, focalDistance, rimBlur);\n vec3 color = vec3(\n sampleAt(uv + offsets.zw, focalDistance, rimBlur).r,\n center.g,\n sampleAt(uv - offsets.zw, focalDistance, rimBlur).b\n );\n // the light falls off toward the field stop\n color *= 1.0 - VIGNETTE * smoothstep(0.5, 1.0, fromAxis);\n color *= 1.0 - DIRT * dirtAt(gl_FragCoord.xy / size.y);\n if (reticle > 0.0) {\n vec2 fromMiddle = gl_FragCoord.xy - 0.5 * size;\n color = mix(color, HALO.rgb, HALO.a * reticleAt(fromMiddle, 3.0 * czm_pixelRatio));\n color = mix(color, RETICLE.rgb, RETICLE.a * reticleAt(fromMiddle, czm_pixelRatio));\n }\n vec2 shift = vec2(EDGE_SHIFT, 0.0);\n vec3 open = 1.0 - smoothstep(-EDGE, EDGE, vec3(toRimAt(position - shift), toRim, toRimAt(position + shift)));\n out_FragColor = vec4(mix(SURROUND, color, open), center.a);\n}\n";
2
+ export default _default;
3
+ //# sourceMappingURL=Lens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Lens.d.ts","sourceRoot":"","sources":["../../src/shaders/Lens.js"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ // This file is automatically rebuilt from Lens.glsl.
2
+ export default "// The eyepieces, two overlapping circles merged into one wide opening in a 160x90 box fitted to\n// the canvas, near black around them, with an uneven edge. Inside, lens effects: a slight fisheye\n// and color fringes toward the rim, both stronger at high magnification, the light falling off and\n// the image softening toward the rim, a violet and orange edge, specks of dirt on the glass, and\n// a depth of field focused on the middle of the view that gets shallower as the magnification\n// grows. Optionally, a rangefinder reticle, a mil scale that grows with the magnification. Picking\n// ignores the fisheye, so near the rim the picked point is a little off what is shown.\nprecision highp sampler2D;\nuniform sampler2D colorTexture;\nuniform sampler2D blurTexture;\nuniform sampler2D depthTexture;\nuniform float magnification;\n// 1 to draw the reticle, 0 not to\nuniform float reticle;\n// device pixels per milliradian, from the field of view\nuniform float pixelsPerMil;\nin vec2 v_textureCoordinates;\n\nconst vec2 BOX = vec2(160.0, 90.0);\nconst vec2 LEFT = vec2(55.2, 45.0);\nconst vec2 RIGHT = vec2(104.8, 45.0);\nconst float RADIUS = 43.3;\n// how much the notches between the eyepieces fill in, in box units\nconst float MERGE = 6.0;\n// half width of the soft edge of the eyepieces, in box units\nconst float EDGE = 3.5;\n// around the eyepieces\nconst vec3 SURROUND = vec3(0.02);\n// the edge of the opening for red is this much further right, and for blue further left, in box\n// units, so it fades through violet on the left and orange on the right\nconst float EDGE_SHIFT = 2.0;\n// the edge wanders in and out by up to this much, in box units, with bumps about BUMP apart\nconst float UNEVEN = 1.2;\nconst float BUMP = 9.0;\n// specks of dirt on the glass: cells per canvas height, share of cells with a speck, how dark\nconst float DIRT_CELLS = 45.0;\nconst float DIRT_DENSITY = 0.015;\nconst float DIRT = 0.55;\n// the effects only apply within this distance of the rim, in box units, so the middle of the\n// view, where the eyepieces overlap, stays clean\nconst float BAND = 20.0;\n// fisheye: on the rim, sample up to this fraction further from the center, at 5x\nconst float FISHEYE = 0.07;\n// color fringe on the rim, in box units, at 5x\nconst float FRINGE = 1.0;\n// light lost on the rim of an eyepiece\nconst float VIGNETTE = 0.25;\n// a ring this wide inside the edge, in box units, where the glass bends the light more: it starts\n// abruptly and shows the image from RING_PULL further in, blurred, with a wider color fringe\nconst float RING = 4.5;\nconst float RING_PULL = 1.5;\nconst float RING_FRINGE = 0.15;\n// blur on the rim, off the optical axis\nconst float RIM_BLUR = 1.0;\n// sharp within this many factors of two of the focal distance at 1x, narrower as 1 / magnification\nconst float FOCUS_RANGE = 2.0;\n\n// ticks of the reticle closer than this, in CSS pixels, are left out for every 5th or 10th\nconst float MIN_TICK_SPACING = 6.0;\n// ticks on each side of the middle, and the empty gap in the middle, in ticks\nconst float TICKS = 10.0;\nconst float GAP = 1.0;\n// dark lines in a light halo, readable on dark forest as on snow\nconst vec4 RETICLE = vec4(0.05, 0.05, 0.05, 0.9);\nconst vec4 HALO = vec4(0.9, 0.9, 0.85, 0.6);\n\n// coverage of a line this far from the pixel, width pixels wide\nfloat line(float distance, float width) {\n return 1.0 - smoothstep(0.5 * width - 0.5, 0.5 * width + 0.5, distance);\n}\n\n// coverage of the reticle at a pixel, in device pixels from the middle of the view, with lines\n// width device pixels wide: a cross of mil scales, ticks every 1, 5 or 10 mils, longer every 5th\nfloat reticleAt(vec2 p, float width) {\n float minSpacing = MIN_TICK_SPACING * czm_pixelRatio;\n float spacing = pixelsPerMil;\n if (spacing < minSpacing) {\n spacing *= 5.0;\n }\n if (spacing < minSpacing) {\n spacing *= 2.0;\n }\n float coverage = 0.0;\n // one axis at a time: along is the distance along the scale, across the distance from it\n for (int axis = 0; axis < 2; axis++) {\n float along = axis == 0 ? p.x : p.y;\n float across = axis == 0 ? p.y : p.x;\n float fromMiddle = abs(along);\n if (fromMiddle < GAP * spacing || fromMiddle > TICKS * spacing + width) {\n continue;\n }\n float tick = floor(fromMiddle / spacing + 0.5);\n float tickLength = (mod(tick, 5.0) == 0.0 ? 6.0 : 3.0) * czm_pixelRatio + 0.5 * width;\n coverage = max(coverage, line(abs(across), width));\n if (abs(across) < tickLength) {\n coverage = max(coverage, line(abs(fromMiddle - tick * spacing), width));\n }\n }\n return coverage;\n}\n\nfloat hash(vec2 p) {\n return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);\n}\n\n// coverage of the dirt at a pixel, in canvas heights: at most one small speck per cell, some\n// with a smaller one next to it, so they are not all round\nfloat dirtAt(vec2 p) {\n // before the branch: derivatives are undefined where neighboring pixels take different ones,\n // and not from inCell, which jumps at the cell borders\n float soft = 0.5 * fwidth(p.x * DIRT_CELLS);\n vec2 cell = floor(p * DIRT_CELLS);\n if (hash(cell) > DIRT_DENSITY) {\n return 0.0;\n }\n vec2 inCell = fract(p * DIRT_CELLS);\n vec2 center = 0.35 + 0.3 * vec2(hash(cell + 17.0), hash(cell + 31.0));\n float radius = 0.04 + 0.12 * hash(cell + 47.0) * hash(cell + 53.0);\n vec2 satellite = center + (vec2(hash(cell + 61.0), hash(cell + 67.0)) - 0.5) * 3.0 * radius;\n float speck = 1.0 - smoothstep(radius - soft, radius + soft, length(inCell - center));\n float small = 1.0 - smoothstep(0.6 * radius - soft, 0.6 * radius + soft, length(inCell - satellite));\n // some specks darker than others\n return (0.4 + 0.6 * hash(cell + 79.0)) * max(speck, small * step(0.5, hash(cell + 71.0)));\n}\n\n// smooth noise between -1 and 1, one bump per unit\nfloat noise(vec2 p) {\n vec2 i = floor(p);\n vec2 f = fract(p);\n f = f * f * (3.0 - 2.0 * f);\n float a = hash(i);\n float b = hash(i + vec2(1.0, 0.0));\n float c = hash(i + vec2(0.0, 1.0));\n float d = hash(i + vec2(1.0, 1.0));\n return 2.0 * mix(mix(a, b, f.x), mix(c, d, f.x), f.y) - 1.0;\n}\n\n// distance in box units to the edge of the opening, negative inside: a smooth union of the two\n// circles, which fills in the notches between them, made uneven\nfloat toRimAt(vec2 position) {\n float toLeftRim = length(position - LEFT) - RADIUS;\n float toRightRim = length(position - RIGHT) - RADIUS;\n float h = clamp(0.5 + 0.5 * (toRightRim - toLeftRim) / MERGE, 0.0, 1.0);\n float toRim = mix(toRightRim, toLeftRim, h) - MERGE * h * (1.0 - h);\n vec2 p = position / BUMP;\n return toRim + UNEVEN * (0.7 * noise(p) + 0.3 * noise(2.7 * p + 5.0));\n}\n\n// offsets in box units for one eyepiece: xy the fisheye, zw the color fringe\nvec4 lensOffsets(vec2 toCenter, float strength) {\n float fromCenter = length(toCenter);\n // 0 up to the band, 1 on the rim\n float rim = 1.0 - clamp((RADIUS - fromCenter) / BAND, 0.0, 1.0);\n vec2 direction = toCenter / max(fromCenter, 1e-3);\n return strength * vec4(toCenter * FISHEYE * rim * rim, direction * FRINGE * rim * rim * rim);\n}\n\n// the scene at uv, blurred by the depth of field around the focal distance and toward the rim\nvec4 sampleAt(vec2 uv, float focalDistance, float rimBlur) {\n vec4 eye = eyeAt(depthTexture, uv);\n float fromCamera = eye.w == 0.0 ? 1e30 : length(eye.xyz);\n float range = FOCUS_RANGE / magnification;\n float defocus = focalDistance > 0.0\n ? smoothstep(range, 2.0 * range, abs(log2(fromCamera / focalDistance)))\n : 0.0;\n // no depth of field at 1x, fully from 3x\n defocus *= smoothstep(1.0, 3.0, magnification);\n return mix(texture(colorTexture, uv), texture(blurTexture, uv), max(defocus, rimBlur));\n}\n\nvoid main() {\n vec2 size = czm_viewport.zw;\n float scale = min(size.x / BOX.x, size.y / BOX.y);\n vec2 position = (v_textureCoordinates * size - 0.5 * (size - scale * BOX)) / scale;\n vec2 toLeft = position - LEFT;\n vec2 toRight = position - RIGHT;\n\n // cheap optics show their flaws more when zoomed in: 0.6 at 1x, 1 at 5x, 2 at 20x and beyond\n float strength = clamp(0.5 + magnification / 10.0, 0.6, 2.0);\n vec4 left = lensOffsets(toLeft, strength);\n vec4 right = lensOffsets(toRight, strength);\n // blend the two eyepieces where they overlap, so there is no seam between them\n float weight = smoothstep(-6.0, 6.0, length(toRight) - length(toLeft));\n float toRim = toRimAt(position);\n float ring = smoothstep(-RING, -RING + 1.5, toRim);\n vec2 outward = normalize(mix(toRight, toLeft, weight));\n vec4 offsets = (mix(right, left, weight) + ring * vec4(-RING_PULL * outward, RING_FRINGE * outward)) * scale / vec4(size, size);\n\n // focused on what is in the middle of the view; the sky there is at infinity\n vec4 target = eyeAt(depthTexture, vec2(0.5));\n float focalDistance = target.w == 0.0 ? 0.0 : length(target.xyz);\n // 0 in the middle of an eyepiece, 1 on the rim; from the edge of the opening, so the middle of\n // the view, where the eyepieces meet, is not dimmed\n float fromAxis = 1.0 + toRim / RADIUS;\n float rimBlur = max(RIM_BLUR * smoothstep(0.5, 0.95, fromAxis), ring);\n\n vec2 uv = v_textureCoordinates + offsets.xy;\n vec4 center = sampleAt(uv, focalDistance, rimBlur);\n vec3 color = vec3(\n sampleAt(uv + offsets.zw, focalDistance, rimBlur).r,\n center.g,\n sampleAt(uv - offsets.zw, focalDistance, rimBlur).b\n );\n // the light falls off toward the field stop\n color *= 1.0 - VIGNETTE * smoothstep(0.5, 1.0, fromAxis);\n color *= 1.0 - DIRT * dirtAt(gl_FragCoord.xy / size.y);\n if (reticle > 0.0) {\n vec2 fromMiddle = gl_FragCoord.xy - 0.5 * size;\n color = mix(color, HALO.rgb, HALO.a * reticleAt(fromMiddle, 3.0 * czm_pixelRatio));\n color = mix(color, RETICLE.rgb, RETICLE.a * reticleAt(fromMiddle, czm_pixelRatio));\n }\n vec2 shift = vec2(EDGE_SHIFT, 0.0);\n vec3 open = 1.0 - smoothstep(-EDGE, EDGE, vec3(toRimAt(position - shift), toRim, toRimAt(position + shift)));\n out_FragColor = vec4(mix(SURROUND, color, open), center.a);\n}\n";
3
+ //# sourceMappingURL=Lens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Lens.js","sourceRoot":"","sources":["../../src/shaders/Lens.js"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,eAAe,86TAA86T,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geoblocks/cesium-binoculars",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "license": "BSD-3-Clause",
5
5
  "type": "module",
6
6
  "repository": {
@@ -8,13 +8,26 @@
8
8
  "url": "https://github.com/geoblocks/cesium-helpers/",
9
9
  "directory": "packages/cesium-binoculars"
10
10
  },
11
- "main": "cesium-binoculars.js",
12
- "module": "cesium-binoculars.js",
11
+ "main": "dist/cesium-binoculars.js",
12
+ "module": "dist/cesium-binoculars.js",
13
+ "types": "dist/cesium-binoculars.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "source": "./src/cesium-binoculars.js",
17
+ "types": "./dist/cesium-binoculars.d.ts",
18
+ "default": "./dist/cesium-binoculars.js"
19
+ }
20
+ },
13
21
  "sideEffects": false,
14
22
  "files": [
15
- "cesium-binoculars.js",
16
- "shaders/*.js"
23
+ "dist",
24
+ "src",
25
+ "!src/**/*.test.js"
17
26
  ],
27
+ "scripts": {
28
+ "build": "tsc -p .",
29
+ "prepack": "npm run build"
30
+ },
18
31
  "publishConfig": {
19
32
  "access": "public"
20
33
  },
@@ -22,5 +35,5 @@
22
35
  "@cesium/core": ">=0.1.0",
23
36
  "@cesium/engine": ">=10"
24
37
  },
25
- "gitHead": "430bbbb63bf3d60221d670f65d5e51fdb6d5b944"
38
+ "gitHead": "a6d8fdfe6b9ad266fd8a4593c38efd24e9a5546b"
26
39
  }
@@ -0,0 +1,20 @@
1
+ // Eye coordinates of the pixel at uv, from the depth texture; w is 0 for the sky. With a
2
+ // logarithmic depth buffer, czm_readDepth's perspective depth is about 1 beyond a few meters,
3
+ // so the distance is decoded from the logarithmic depth directly.
4
+ precision highp sampler2D;
5
+ vec4 eyeAt(sampler2D depthTexture, vec2 uv) {
6
+ float raw = texture(depthTexture, uv).r;
7
+ // the pixel's view ray, through the near plane
8
+ vec4 ray = czm_inverseProjection * vec4(2.0 * uv - 1.0, -1.0, 1.0);
9
+ ray.xyz /= ray.w;
10
+ if (raw >= 1.0) {
11
+ return vec4(normalize(ray.xyz), 0.0);
12
+ }
13
+ #ifdef LOG_DEPTH
14
+ float viewDepth = exp2(raw * czm_log2FarDepthFromNearPlusOne) - 1.0 + czm_currentFrustum.x;
15
+ #else
16
+ vec4 eye = czm_inverseProjection * vec4(2.0 * uv - 1.0, 2.0 * raw - 1.0, 1.0);
17
+ float viewDepth = -eye.z / eye.w;
18
+ #endif
19
+ return vec4(ray.xyz * (viewDepth / -ray.z), 1.0);
20
+ }
@@ -0,0 +1,215 @@
1
+ // The eyepieces, two overlapping circles merged into one wide opening in a 160x90 box fitted to
2
+ // the canvas, near black around them, with an uneven edge. Inside, lens effects: a slight fisheye
3
+ // and color fringes toward the rim, both stronger at high magnification, the light falling off and
4
+ // the image softening toward the rim, a violet and orange edge, specks of dirt on the glass, and
5
+ // a depth of field focused on the middle of the view that gets shallower as the magnification
6
+ // grows. Optionally, a rangefinder reticle, a mil scale that grows with the magnification. Picking
7
+ // ignores the fisheye, so near the rim the picked point is a little off what is shown.
8
+ precision highp sampler2D;
9
+ uniform sampler2D colorTexture;
10
+ uniform sampler2D blurTexture;
11
+ uniform sampler2D depthTexture;
12
+ uniform float magnification;
13
+ // 1 to draw the reticle, 0 not to
14
+ uniform float reticle;
15
+ // device pixels per milliradian, from the field of view
16
+ uniform float pixelsPerMil;
17
+ in vec2 v_textureCoordinates;
18
+
19
+ const vec2 BOX = vec2(160.0, 90.0);
20
+ const vec2 LEFT = vec2(55.2, 45.0);
21
+ const vec2 RIGHT = vec2(104.8, 45.0);
22
+ const float RADIUS = 43.3;
23
+ // how much the notches between the eyepieces fill in, in box units
24
+ const float MERGE = 6.0;
25
+ // half width of the soft edge of the eyepieces, in box units
26
+ const float EDGE = 3.5;
27
+ // around the eyepieces
28
+ const vec3 SURROUND = vec3(0.02);
29
+ // the edge of the opening for red is this much further right, and for blue further left, in box
30
+ // units, so it fades through violet on the left and orange on the right
31
+ const float EDGE_SHIFT = 2.0;
32
+ // the edge wanders in and out by up to this much, in box units, with bumps about BUMP apart
33
+ const float UNEVEN = 1.2;
34
+ const float BUMP = 9.0;
35
+ // specks of dirt on the glass: cells per canvas height, share of cells with a speck, how dark
36
+ const float DIRT_CELLS = 45.0;
37
+ const float DIRT_DENSITY = 0.015;
38
+ const float DIRT = 0.55;
39
+ // the effects only apply within this distance of the rim, in box units, so the middle of the
40
+ // view, where the eyepieces overlap, stays clean
41
+ const float BAND = 20.0;
42
+ // fisheye: on the rim, sample up to this fraction further from the center, at 5x
43
+ const float FISHEYE = 0.07;
44
+ // color fringe on the rim, in box units, at 5x
45
+ const float FRINGE = 1.0;
46
+ // light lost on the rim of an eyepiece
47
+ const float VIGNETTE = 0.25;
48
+ // a ring this wide inside the edge, in box units, where the glass bends the light more: it starts
49
+ // abruptly and shows the image from RING_PULL further in, blurred, with a wider color fringe
50
+ const float RING = 4.5;
51
+ const float RING_PULL = 1.5;
52
+ const float RING_FRINGE = 0.15;
53
+ // blur on the rim, off the optical axis
54
+ const float RIM_BLUR = 1.0;
55
+ // sharp within this many factors of two of the focal distance at 1x, narrower as 1 / magnification
56
+ const float FOCUS_RANGE = 2.0;
57
+
58
+ // ticks of the reticle closer than this, in CSS pixels, are left out for every 5th or 10th
59
+ const float MIN_TICK_SPACING = 6.0;
60
+ // ticks on each side of the middle, and the empty gap in the middle, in ticks
61
+ const float TICKS = 10.0;
62
+ const float GAP = 1.0;
63
+ // dark lines in a light halo, readable on dark forest as on snow
64
+ const vec4 RETICLE = vec4(0.05, 0.05, 0.05, 0.9);
65
+ const vec4 HALO = vec4(0.9, 0.9, 0.85, 0.6);
66
+
67
+ // coverage of a line this far from the pixel, width pixels wide
68
+ float line(float distance, float width) {
69
+ return 1.0 - smoothstep(0.5 * width - 0.5, 0.5 * width + 0.5, distance);
70
+ }
71
+
72
+ // coverage of the reticle at a pixel, in device pixels from the middle of the view, with lines
73
+ // width device pixels wide: a cross of mil scales, ticks every 1, 5 or 10 mils, longer every 5th
74
+ float reticleAt(vec2 p, float width) {
75
+ float minSpacing = MIN_TICK_SPACING * czm_pixelRatio;
76
+ float spacing = pixelsPerMil;
77
+ if (spacing < minSpacing) {
78
+ spacing *= 5.0;
79
+ }
80
+ if (spacing < minSpacing) {
81
+ spacing *= 2.0;
82
+ }
83
+ float coverage = 0.0;
84
+ // one axis at a time: along is the distance along the scale, across the distance from it
85
+ for (int axis = 0; axis < 2; axis++) {
86
+ float along = axis == 0 ? p.x : p.y;
87
+ float across = axis == 0 ? p.y : p.x;
88
+ float fromMiddle = abs(along);
89
+ if (fromMiddle < GAP * spacing || fromMiddle > TICKS * spacing + width) {
90
+ continue;
91
+ }
92
+ float tick = floor(fromMiddle / spacing + 0.5);
93
+ float tickLength = (mod(tick, 5.0) == 0.0 ? 6.0 : 3.0) * czm_pixelRatio + 0.5 * width;
94
+ coverage = max(coverage, line(abs(across), width));
95
+ if (abs(across) < tickLength) {
96
+ coverage = max(coverage, line(abs(fromMiddle - tick * spacing), width));
97
+ }
98
+ }
99
+ return coverage;
100
+ }
101
+
102
+ float hash(vec2 p) {
103
+ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
104
+ }
105
+
106
+ // coverage of the dirt at a pixel, in canvas heights: at most one small speck per cell, some
107
+ // with a smaller one next to it, so they are not all round
108
+ float dirtAt(vec2 p) {
109
+ // before the branch: derivatives are undefined where neighboring pixels take different ones,
110
+ // and not from inCell, which jumps at the cell borders
111
+ float soft = 0.5 * fwidth(p.x * DIRT_CELLS);
112
+ vec2 cell = floor(p * DIRT_CELLS);
113
+ if (hash(cell) > DIRT_DENSITY) {
114
+ return 0.0;
115
+ }
116
+ vec2 inCell = fract(p * DIRT_CELLS);
117
+ vec2 center = 0.35 + 0.3 * vec2(hash(cell + 17.0), hash(cell + 31.0));
118
+ float radius = 0.04 + 0.12 * hash(cell + 47.0) * hash(cell + 53.0);
119
+ vec2 satellite = center + (vec2(hash(cell + 61.0), hash(cell + 67.0)) - 0.5) * 3.0 * radius;
120
+ float speck = 1.0 - smoothstep(radius - soft, radius + soft, length(inCell - center));
121
+ float small = 1.0 - smoothstep(0.6 * radius - soft, 0.6 * radius + soft, length(inCell - satellite));
122
+ // some specks darker than others
123
+ return (0.4 + 0.6 * hash(cell + 79.0)) * max(speck, small * step(0.5, hash(cell + 71.0)));
124
+ }
125
+
126
+ // smooth noise between -1 and 1, one bump per unit
127
+ float noise(vec2 p) {
128
+ vec2 i = floor(p);
129
+ vec2 f = fract(p);
130
+ f = f * f * (3.0 - 2.0 * f);
131
+ float a = hash(i);
132
+ float b = hash(i + vec2(1.0, 0.0));
133
+ float c = hash(i + vec2(0.0, 1.0));
134
+ float d = hash(i + vec2(1.0, 1.0));
135
+ return 2.0 * mix(mix(a, b, f.x), mix(c, d, f.x), f.y) - 1.0;
136
+ }
137
+
138
+ // distance in box units to the edge of the opening, negative inside: a smooth union of the two
139
+ // circles, which fills in the notches between them, made uneven
140
+ float toRimAt(vec2 position) {
141
+ float toLeftRim = length(position - LEFT) - RADIUS;
142
+ float toRightRim = length(position - RIGHT) - RADIUS;
143
+ float h = clamp(0.5 + 0.5 * (toRightRim - toLeftRim) / MERGE, 0.0, 1.0);
144
+ float toRim = mix(toRightRim, toLeftRim, h) - MERGE * h * (1.0 - h);
145
+ vec2 p = position / BUMP;
146
+ return toRim + UNEVEN * (0.7 * noise(p) + 0.3 * noise(2.7 * p + 5.0));
147
+ }
148
+
149
+ // offsets in box units for one eyepiece: xy the fisheye, zw the color fringe
150
+ vec4 lensOffsets(vec2 toCenter, float strength) {
151
+ float fromCenter = length(toCenter);
152
+ // 0 up to the band, 1 on the rim
153
+ float rim = 1.0 - clamp((RADIUS - fromCenter) / BAND, 0.0, 1.0);
154
+ vec2 direction = toCenter / max(fromCenter, 1e-3);
155
+ return strength * vec4(toCenter * FISHEYE * rim * rim, direction * FRINGE * rim * rim * rim);
156
+ }
157
+
158
+ // the scene at uv, blurred by the depth of field around the focal distance and toward the rim
159
+ vec4 sampleAt(vec2 uv, float focalDistance, float rimBlur) {
160
+ vec4 eye = eyeAt(depthTexture, uv);
161
+ float fromCamera = eye.w == 0.0 ? 1e30 : length(eye.xyz);
162
+ float range = FOCUS_RANGE / magnification;
163
+ float defocus = focalDistance > 0.0
164
+ ? smoothstep(range, 2.0 * range, abs(log2(fromCamera / focalDistance)))
165
+ : 0.0;
166
+ // no depth of field at 1x, fully from 3x
167
+ defocus *= smoothstep(1.0, 3.0, magnification);
168
+ return mix(texture(colorTexture, uv), texture(blurTexture, uv), max(defocus, rimBlur));
169
+ }
170
+
171
+ void main() {
172
+ vec2 size = czm_viewport.zw;
173
+ float scale = min(size.x / BOX.x, size.y / BOX.y);
174
+ vec2 position = (v_textureCoordinates * size - 0.5 * (size - scale * BOX)) / scale;
175
+ vec2 toLeft = position - LEFT;
176
+ vec2 toRight = position - RIGHT;
177
+
178
+ // cheap optics show their flaws more when zoomed in: 0.6 at 1x, 1 at 5x, 2 at 20x and beyond
179
+ float strength = clamp(0.5 + magnification / 10.0, 0.6, 2.0);
180
+ vec4 left = lensOffsets(toLeft, strength);
181
+ vec4 right = lensOffsets(toRight, strength);
182
+ // blend the two eyepieces where they overlap, so there is no seam between them
183
+ float weight = smoothstep(-6.0, 6.0, length(toRight) - length(toLeft));
184
+ float toRim = toRimAt(position);
185
+ float ring = smoothstep(-RING, -RING + 1.5, toRim);
186
+ vec2 outward = normalize(mix(toRight, toLeft, weight));
187
+ vec4 offsets = (mix(right, left, weight) + ring * vec4(-RING_PULL * outward, RING_FRINGE * outward)) * scale / vec4(size, size);
188
+
189
+ // focused on what is in the middle of the view; the sky there is at infinity
190
+ vec4 target = eyeAt(depthTexture, vec2(0.5));
191
+ float focalDistance = target.w == 0.0 ? 0.0 : length(target.xyz);
192
+ // 0 in the middle of an eyepiece, 1 on the rim; from the edge of the opening, so the middle of
193
+ // the view, where the eyepieces meet, is not dimmed
194
+ float fromAxis = 1.0 + toRim / RADIUS;
195
+ float rimBlur = max(RIM_BLUR * smoothstep(0.5, 0.95, fromAxis), ring);
196
+
197
+ vec2 uv = v_textureCoordinates + offsets.xy;
198
+ vec4 center = sampleAt(uv, focalDistance, rimBlur);
199
+ vec3 color = vec3(
200
+ sampleAt(uv + offsets.zw, focalDistance, rimBlur).r,
201
+ center.g,
202
+ sampleAt(uv - offsets.zw, focalDistance, rimBlur).b
203
+ );
204
+ // the light falls off toward the field stop
205
+ color *= 1.0 - VIGNETTE * smoothstep(0.5, 1.0, fromAxis);
206
+ color *= 1.0 - DIRT * dirtAt(gl_FragCoord.xy / size.y);
207
+ if (reticle > 0.0) {
208
+ vec2 fromMiddle = gl_FragCoord.xy - 0.5 * size;
209
+ color = mix(color, HALO.rgb, HALO.a * reticleAt(fromMiddle, 3.0 * czm_pixelRatio));
210
+ color = mix(color, RETICLE.rgb, RETICLE.a * reticleAt(fromMiddle, czm_pixelRatio));
211
+ }
212
+ vec2 shift = vec2(EDGE_SHIFT, 0.0);
213
+ vec3 open = 1.0 - smoothstep(-EDGE, EDGE, vec3(toRimAt(position - shift), toRim, toRimAt(position + shift)));
214
+ out_FragColor = vec4(mix(SURROUND, color, open), center.a);
215
+ }
File without changes
File without changes