jsgui3-server 0.0.133 → 0.0.135
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/AGENTS.md +6 -0
- package/README.md +114 -18
- package/TODO.md +81 -0
- package/cli.js +96 -0
- package/docs/simple-server-api-design.md +702 -0
- package/examples/controls/14) window, canvas/client.js +238 -0
- package/examples/controls/14) window, canvas/server.js +21 -0
- package/examples/controls/14b) window, canvas (improved renderer)/client.js +391 -0
- package/examples/controls/14b) window, canvas (improved renderer)/server.js +21 -0
- package/examples/controls/14d) window, canvas globe/Clipping.js +0 -0
- package/examples/controls/14d) window, canvas globe/EarthGlobeRenderer.js +435 -0
- package/examples/controls/14d) window, canvas globe/RenderingPipeline.js +43 -0
- package/examples/controls/14d) window, canvas globe/client.js +95 -0
- package/examples/controls/14d) window, canvas globe/math.js +76 -0
- package/examples/controls/14d) window, canvas globe/pipeline/BaseStage.js +46 -0
- package/examples/controls/14d) window, canvas globe/pipeline/ClippingStage.js +29 -0
- package/examples/controls/14d) window, canvas globe/pipeline/ComposeStage.js +15 -0
- package/examples/controls/14d) window, canvas globe/pipeline/ContinentsStage.js +116 -0
- package/examples/controls/14d) window, canvas globe/pipeline/GridStage.js +105 -0
- package/examples/controls/14d) window, canvas globe/pipeline/HUDStage.js +10 -0
- package/examples/controls/14d) window, canvas globe/pipeline/ShadeSphereStage.js +153 -0
- package/examples/controls/14d) window, canvas globe/pipeline/TransformStage.js +23 -0
- package/examples/controls/14d) window, canvas globe/pipeline/clipping/FrontFaceStrategy.js +46 -0
- package/examples/controls/14d) window, canvas globe/pipeline/clipping/PlaneClipStrategy.js +339 -0
- package/examples/controls/14d) window, canvas globe/server.js +21 -0
- package/examples/controls/14e) window, canvas multithreaded/client.js +948 -0
- package/examples/controls/14e) window, canvas multithreaded/server.js +21 -0
- package/examples/controls/14f) window, canvas polyglobe/client.js +569 -0
- package/examples/controls/14f) window, canvas polyglobe/math.js +137 -0
- package/examples/controls/14f) window, canvas polyglobe/server.js +21 -0
- package/module.js +3 -1
- package/package.json +12 -6
- package/resources/_old_website-resource.js +10 -2
- package/resources/jsbuilder/babel/deep_iterate/deep_iterate_babel.js +37 -0
- package/serve-factory.js +221 -0
- package/serve-helpers.js +95 -0
- package/server.js +93 -7
- package/tests/cli.test.js +66 -0
- package/tests/dummy-client.js +10 -0
- package/tests/serve.test.js +210 -0
- package/.vscode/settings.json +0 -6
|
@@ -0,0 +1,948 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
const jsgui = require('jsgui3-client');
|
|
5
|
+
const {controls, Control, mixins} = jsgui;
|
|
6
|
+
const {dragable} = mixins;
|
|
7
|
+
const {Checkbox, Date_Picker, Text_Input, Text_Field, Dropdown_Menu} = controls;
|
|
8
|
+
const Active_HTML_Document = require('../../../controls/Active_HTML_Document');
|
|
9
|
+
class EarthGlobeRenderer {
|
|
10
|
+
constructor(canvasOrId, options) {
|
|
11
|
+
if (!options) options = {};
|
|
12
|
+
let cid = canvasOrId || 'globeCanvas';
|
|
13
|
+
let el = typeof cid === 'string' ? document.getElementById(cid) : cid;
|
|
14
|
+
if (!(el instanceof HTMLCanvasElement)) throw new Error("Canvas 'globeCanvas' not found.");
|
|
15
|
+
this.canvas = el;
|
|
16
|
+
this.ctx = this.canvas.getContext('2d', { alpha: true });
|
|
17
|
+
|
|
18
|
+
// ---------- Options / state ----------
|
|
19
|
+
this.opts = {
|
|
20
|
+
padding: options.padding != null ? options.padding : 8,
|
|
21
|
+
background: options.background != null ? options.background : null,
|
|
22
|
+
|
|
23
|
+
// Lighting/material
|
|
24
|
+
baseColor: options.baseColor || [0.13, 0.42, 0.86], // linear-ish RGB
|
|
25
|
+
ambient: options.ambient != null ? options.ambient : 0.08,
|
|
26
|
+
diffuse: options.diffuse != null ? options.diffuse : 1.0,
|
|
27
|
+
specular: options.specular != null ? options.specular : 0.7,
|
|
28
|
+
shininess: options.shininess != null ? options.shininess : 140.0,
|
|
29
|
+
terminatorSoftness: options.terminatorSoftness != null ? options.terminatorSoftness : 0.08,
|
|
30
|
+
atmosphere: options.atmosphere != null ? options.atmosphere : 0.5,
|
|
31
|
+
backlight: options.backlight != null ? options.backlight : 0.25,
|
|
32
|
+
|
|
33
|
+
// Quality
|
|
34
|
+
quality: Math.min(2, Math.max(0.5, options.quality != null ? options.quality : 1.15)),
|
|
35
|
+
|
|
36
|
+
// Grid
|
|
37
|
+
grid: {
|
|
38
|
+
enabled: options.grid && options.grid.enabled != null ? options.grid.enabled : false,
|
|
39
|
+
color: options.grid && options.grid.color ? options.grid.color : '#444',
|
|
40
|
+
lineWidth: options.grid && options.grid.lineWidth != null ? options.grid.lineWidth : 0.8,
|
|
41
|
+
alpha: options.grid && options.grid.alpha != null ? options.grid.alpha : 0.7,
|
|
42
|
+
stepLat: options.grid && options.grid.stepLat != null ? options.grid.stepLat : 10,
|
|
43
|
+
stepLon: options.grid && options.grid.stepLon != null ? options.grid.stepLon : 10,
|
|
44
|
+
sampleStepDeg: options.grid && options.grid.sampleStepDeg != null ? options.grid.sampleStepDeg : 2
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
// Antarctica styling
|
|
48
|
+
antarcticaFill: options.antarcticaFill || 'rgba(220,240,255,0.85)',
|
|
49
|
+
antarcticaStroke: options.antarcticaStroke || 'rgba(180,200,220,0.9)',
|
|
50
|
+
antarcticaLineWidth: options.antarcticaLineWidth != null ? options.antarcticaLineWidth : 1.0,
|
|
51
|
+
|
|
52
|
+
// Inertia
|
|
53
|
+
inertiaFriction: options.inertiaFriction != null ? options.inertiaFriction : 2.6,
|
|
54
|
+
inertiaMinSpeed: options.inertiaMinSpeed != null ? options.inertiaMinSpeed : 0.05,
|
|
55
|
+
|
|
56
|
+
// Workers/tiling
|
|
57
|
+
maxWorkers: options.maxWorkers || (typeof navigator !== 'undefined' ? navigator.hardwareConcurrency || 4 : 4),
|
|
58
|
+
|
|
59
|
+
dpr: (typeof window !== 'undefined' && window.devicePixelRatio) ? window.devicePixelRatio : 1
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Sun direction (camera frame)
|
|
63
|
+
this.sun = this._normVec([-0.45, 0.55, 0.65]);
|
|
64
|
+
|
|
65
|
+
// Orientation (arcball) quaternion
|
|
66
|
+
this.q = this._qIdentity();
|
|
67
|
+
this.R = this._mat3FromQuat(this.q);
|
|
68
|
+
this.Rt = this._mat3Transpose(this.R);
|
|
69
|
+
|
|
70
|
+
// Offscreen + internal disc
|
|
71
|
+
this._off = null;
|
|
72
|
+
this._discSize = 0;
|
|
73
|
+
this._Nx = null; this._Ny = null; this._Nz = null; this._A8 = null;
|
|
74
|
+
|
|
75
|
+
// Workers & shared buffers
|
|
76
|
+
this._useWorkers = typeof Worker !== 'undefined';
|
|
77
|
+
this._useSAB = (typeof SharedArrayBuffer !== 'undefined') && (typeof crossOriginIsolated !== 'undefined') && crossOriginIsolated;
|
|
78
|
+
this._workers = [];
|
|
79
|
+
this._rgbaSAB = null;
|
|
80
|
+
this._rgba = null;
|
|
81
|
+
this._imgData = null;
|
|
82
|
+
this._jobSeq = 0;
|
|
83
|
+
this._shadingInProgress = false;
|
|
84
|
+
this._rerenderRequested = false;
|
|
85
|
+
|
|
86
|
+
// Interaction & inertia
|
|
87
|
+
this._dragging = false;
|
|
88
|
+
this._v0 = [0,0,1];
|
|
89
|
+
this._axis = [0,1,0];
|
|
90
|
+
this._omega = 0;
|
|
91
|
+
this._lastTime = 0;
|
|
92
|
+
this._animRAF = 0;
|
|
93
|
+
this._isAnimating = false;
|
|
94
|
+
|
|
95
|
+
// Antarctica outline
|
|
96
|
+
this._ANT = [
|
|
97
|
+
[-70.0, 0.0], [-70.5, 7.5], [-70.3, 15.0], [-70.0, 22.5], [-69.5, 30.0],
|
|
98
|
+
[-69.0, 37.5], [-68.5, 45.0], [-68.0, 52.5], [-67.5, 60.0], [-67.2, 67.5],
|
|
99
|
+
[-66.8, 75.0], [-66.5, 82.5], [-66.3, 90.0], [-66.2, 100.0], [-66.5, 110.0],
|
|
100
|
+
[-66.8, 120.0], [-67.0, 130.0], [-67.5, 140.0], [-68.5, 150.0], [-70.0, 160.0],
|
|
101
|
+
[-73.0, 170.0], [-75.5, 178.0], [-77.5, -175.0], [-78.5, -170.0], [-79.0, -160.0],
|
|
102
|
+
[-78.8, -150.0], [-77.5, -140.0], [-76.5, -130.0], [-75.5, -120.0], [-74.5, -110.0],
|
|
103
|
+
[-73.5, -100.0], [-72.5, -90.0], [-71.8, -80.0], [-70.5, -72.0], [-68.8, -68.0],
|
|
104
|
+
[-66.5, -66.0], [-64.5, -64.0], [-63.0, -60.0], [-64.5, -58.0], [-66.5, -55.0],
|
|
105
|
+
[-70.0, -50.0], [-73.0, -45.0], [-76.0, -42.0], [-78.0, -40.0], [-79.0, -35.0],
|
|
106
|
+
[-78.5, -25.0], [-76.5, -15.0], [-73.5, -7.5], [-71.0, -2.5], [-70.0, 0.0]
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
this._attachPointerHandlers();
|
|
110
|
+
this.resize();
|
|
111
|
+
this.render();
|
|
112
|
+
|
|
113
|
+
this._onResize = () => { this.resize(); this.render(); };
|
|
114
|
+
window.addEventListener('resize', this._onResize, { passive: true });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
destroy() {
|
|
118
|
+
window.removeEventListener('resize', this._onResize);
|
|
119
|
+
this._detachPointerHandlers();
|
|
120
|
+
if (this._animRAF) cancelAnimationFrame(this._animRAF);
|
|
121
|
+
for (let i = 0; i < this._workers.length; i++) {
|
|
122
|
+
try { this._workers[i].terminate(); } catch(e){}
|
|
123
|
+
}
|
|
124
|
+
this._workers = [];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ---------- Public controls ----------
|
|
128
|
+
enableGrid(enabled) { this.opts.grid.enabled = !!enabled; this.render(); }
|
|
129
|
+
setGridColor(color) { this.opts.grid.color = color || '#444'; this.render(); }
|
|
130
|
+
setSunDirection(vec3) { this.sun = this._normVec(vec3); this.render(); }
|
|
131
|
+
setSunFromSpherical(lonDeg, latDeg) {
|
|
132
|
+
let toR = Math.PI / 180, lon = lonDeg * toR, lat = latDeg * toR;
|
|
133
|
+
let cx = Math.cos(lat), sx = Math.sin(lat), sz = Math.sin(lon), cz = Math.cos(lon);
|
|
134
|
+
this.setSunDirection([cx*sz, sx, cx*cz]);
|
|
135
|
+
}
|
|
136
|
+
centerOnLatLon(latDeg, lonDeg) {
|
|
137
|
+
let φ = latDeg * Math.PI / 180;
|
|
138
|
+
let λ = lonDeg * Math.PI / 180;
|
|
139
|
+
let qy = this._qFromAxisAngle(0,1,0, -λ);
|
|
140
|
+
let qx = this._qFromAxisAngle(1,0,0, φ);
|
|
141
|
+
this.q = this._qNormalize(this._qMul(qx, qy));
|
|
142
|
+
this._updateRot();
|
|
143
|
+
this.render();
|
|
144
|
+
}
|
|
145
|
+
centerOnAntarctica() { this.centerOnLatLon(-90, 0); }
|
|
146
|
+
setQuality(q) { this.opts.quality = Math.min(2, Math.max(0.5, q || 1)); this._discSize = 0; this.render(); }
|
|
147
|
+
|
|
148
|
+
resize() {
|
|
149
|
+
let dpr = (this.opts.dpr = (typeof window !== 'undefined' && window.devicePixelRatio) ? window.devicePixelRatio : 1);
|
|
150
|
+
let rect = this.canvas.getBoundingClientRect();
|
|
151
|
+
let dw = rect.width || this.canvas.width;
|
|
152
|
+
let dh = rect.height || this.canvas.height;
|
|
153
|
+
let w = Math.max(1, Math.round(dw * dpr));
|
|
154
|
+
let h = Math.max(1, Math.round(dh * dpr));
|
|
155
|
+
if (this.canvas.width !== w || this.canvas.height !== h) {
|
|
156
|
+
this.canvas.width = w; this.canvas.height = h;
|
|
157
|
+
}
|
|
158
|
+
this.ctx.setTransform(1,0,0,1,0,0);
|
|
159
|
+
this.ctx.scale(dpr, dpr);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ---------- Render ----------
|
|
163
|
+
render() {
|
|
164
|
+
if (this._shadingInProgress) { this._rerenderRequested = true; return; }
|
|
165
|
+
|
|
166
|
+
let ctx = this.ctx;
|
|
167
|
+
let rect = this.canvas.getBoundingClientRect();
|
|
168
|
+
let width = rect.width || this.canvas.width / this.opts.dpr;
|
|
169
|
+
let height = rect.height || this.canvas.height / this.opts.dpr;
|
|
170
|
+
|
|
171
|
+
ctx.clearRect(0, 0, width, height);
|
|
172
|
+
if (this.opts.background) {
|
|
173
|
+
ctx.fillStyle = this.opts.background;
|
|
174
|
+
ctx.fillRect(0, 0, width, height);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
let pad = this.opts.padding;
|
|
178
|
+
let r = Math.max(1, Math.min(width, height) / 2 - pad);
|
|
179
|
+
let cx = width / 2, cy = height / 2;
|
|
180
|
+
|
|
181
|
+
// Placeholder ocean fill so it isn't black while workers run
|
|
182
|
+
let bc = this.opts.baseColor;
|
|
183
|
+
let pr = Math.max(0, Math.min(255, Math.round(Math.pow(bc[0], 1/2.2) * 255)));
|
|
184
|
+
let pg = Math.max(0, Math.min(255, Math.round(Math.pow(bc[1], 1/2.2) * 255)));
|
|
185
|
+
let pb = Math.max(0, Math.min(255, Math.round(Math.pow(bc[2], 1/2.2) * 255)));
|
|
186
|
+
ctx.save();
|
|
187
|
+
ctx.beginPath();
|
|
188
|
+
ctx.arc(cx, cy, r, 0, Math.PI*2);
|
|
189
|
+
ctx.clip();
|
|
190
|
+
ctx.fillStyle = 'rgb(' + pr + ',' + pg + ',' + pb + ')';
|
|
191
|
+
ctx.fillRect(cx - r, cy - r, 2*r, 2*r);
|
|
192
|
+
ctx.restore();
|
|
193
|
+
|
|
194
|
+
// Internal disc size & rotation
|
|
195
|
+
let q = this.opts.quality;
|
|
196
|
+
let d = Math.max(2, Math.floor(2 * r * q));
|
|
197
|
+
this._updateRot();
|
|
198
|
+
|
|
199
|
+
// Ensure disc + workers
|
|
200
|
+
this._ensureDiscAndWorkers(d);
|
|
201
|
+
|
|
202
|
+
// Shade with workers
|
|
203
|
+
this._shadingInProgress = true;
|
|
204
|
+
let jobId = ++this._jobSeq;
|
|
205
|
+
|
|
206
|
+
this._shadeWithWorkers(jobId, {
|
|
207
|
+
Rt: this.Rt,
|
|
208
|
+
sun: this.sun,
|
|
209
|
+
amb: this.opts.ambient,
|
|
210
|
+
kd: this.opts.diffuse,
|
|
211
|
+
ks: this.opts.specular,
|
|
212
|
+
shininess: this.opts.shininess,
|
|
213
|
+
termSoft: this.opts.terminatorSoftness,
|
|
214
|
+
atm: this.opts.atmosphere,
|
|
215
|
+
back: this.opts.backlight,
|
|
216
|
+
baseColor: this.opts.baseColor
|
|
217
|
+
}).then((completedJobId) => {
|
|
218
|
+
if (completedJobId !== this._jobSeq) return; // stale result, ignore
|
|
219
|
+
|
|
220
|
+
let off = this._getOff(d, d);
|
|
221
|
+
off.ctx.putImageData(this._imgData, 0, 0);
|
|
222
|
+
|
|
223
|
+
ctx.save();
|
|
224
|
+
ctx.beginPath();
|
|
225
|
+
ctx.arc(cx, cy, r, 0, Math.PI*2);
|
|
226
|
+
ctx.clip();
|
|
227
|
+
let drawSize = d / this.opts.quality;
|
|
228
|
+
ctx.drawImage(off.canvas, cx - drawSize/2, cy - drawSize/2, drawSize, drawSize);
|
|
229
|
+
|
|
230
|
+
if (this.opts.grid.enabled) this._drawGraticule(cx, cy, r, this._isAnimating ? 2 : 1);
|
|
231
|
+
this._drawAntarctica(cx, cy, r);
|
|
232
|
+
ctx.restore();
|
|
233
|
+
|
|
234
|
+
ctx.beginPath();
|
|
235
|
+
ctx.arc(cx, cy, r, 0, Math.PI*2);
|
|
236
|
+
ctx.strokeStyle = 'rgba(0,0,0,0.14)';
|
|
237
|
+
ctx.lineWidth = 1.1;
|
|
238
|
+
ctx.stroke();
|
|
239
|
+
|
|
240
|
+
this._shadingInProgress = false;
|
|
241
|
+
if (this._rerenderRequested) {
|
|
242
|
+
this._rerenderRequested = false;
|
|
243
|
+
this.render();
|
|
244
|
+
}
|
|
245
|
+
}).catch(() => {
|
|
246
|
+
this._shadingInProgress = false;
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ---------- Workers / tiling ----------
|
|
251
|
+
_ensureDiscAndWorkers(d) {
|
|
252
|
+
if (this._discSize !== d) {
|
|
253
|
+
this._buildNormalMap(d);
|
|
254
|
+
this._discSize = d;
|
|
255
|
+
|
|
256
|
+
if (this._useSAB) {
|
|
257
|
+
this._rgbaSAB = new SharedArrayBuffer(d * d * 4);
|
|
258
|
+
this._rgba = new Uint8ClampedArray(this._rgbaSAB);
|
|
259
|
+
this._imgData = new ImageData(this._rgba, d, d);
|
|
260
|
+
} else {
|
|
261
|
+
this._rgbaSAB = null;
|
|
262
|
+
this._rgba = new Uint8ClampedArray(d * d * 4);
|
|
263
|
+
this._imgData = new ImageData(this._rgba, d, d);
|
|
264
|
+
}
|
|
265
|
+
this._initWorkerPool(d);
|
|
266
|
+
} else if (this._workers.length === 0) {
|
|
267
|
+
this._initWorkerPool(d);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
_initWorkerPool(d) {
|
|
272
|
+
for (let i = 0; i < this._workers.length; i++) {
|
|
273
|
+
try { this._workers[i].terminate(); } catch(e){}
|
|
274
|
+
}
|
|
275
|
+
this._workers = [];
|
|
276
|
+
if (!this._useWorkers) return;
|
|
277
|
+
|
|
278
|
+
let src = this._workerSource();
|
|
279
|
+
let blob = new Blob([src], { type: 'application/javascript' });
|
|
280
|
+
let url = URL.createObjectURL(blob);
|
|
281
|
+
|
|
282
|
+
let N = Math.max(1, Math.min(this.opts.maxWorkers, (typeof navigator !== 'undefined' ? navigator.hardwareConcurrency || 4 : 4)));
|
|
283
|
+
for (let i = 0; i < N; i++) {
|
|
284
|
+
let w = new Worker(url);
|
|
285
|
+
this._workers.push(w);
|
|
286
|
+
}
|
|
287
|
+
// Workers have loaded the code by now
|
|
288
|
+
URL.revokeObjectURL(url);
|
|
289
|
+
|
|
290
|
+
// Prepare and send INIT per worker
|
|
291
|
+
if (this._useSAB) {
|
|
292
|
+
let sabNx = new SharedArrayBuffer(this._Nx.byteLength);
|
|
293
|
+
let sabNy = new SharedArrayBuffer(this._Ny.byteLength);
|
|
294
|
+
let sabNz = new SharedArrayBuffer(this._Nz.byteLength);
|
|
295
|
+
let sabA8 = new SharedArrayBuffer(this._A8.byteLength);
|
|
296
|
+
new Float32Array(sabNx).set(this._Nx);
|
|
297
|
+
new Float32Array(sabNy).set(this._Ny);
|
|
298
|
+
new Float32Array(sabNz).set(this._Nz);
|
|
299
|
+
new Uint8ClampedArray(sabA8).set(this._A8);
|
|
300
|
+
|
|
301
|
+
for (let i = 0; i < this._workers.length; i++) {
|
|
302
|
+
this._workers[i].postMessage({
|
|
303
|
+
type: 'init',
|
|
304
|
+
d,
|
|
305
|
+
useSAB: true,
|
|
306
|
+
sabRGBA: this._rgbaSAB,
|
|
307
|
+
sabNx, sabNy, sabNz, sabA8
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
} else {
|
|
311
|
+
// IMPORTANT: clone **per worker**; each transfer detaches the buffer!
|
|
312
|
+
for (let i = 0; i < this._workers.length; i++) {
|
|
313
|
+
let nxBuf = this._Nx.buffer.slice(0);
|
|
314
|
+
let nyBuf = this._Ny.buffer.slice(0);
|
|
315
|
+
let nzBuf = this._Nz.buffer.slice(0);
|
|
316
|
+
let a8Buf = this._A8.buffer.slice(0);
|
|
317
|
+
this._workers[i].postMessage({
|
|
318
|
+
type: 'init',
|
|
319
|
+
d,
|
|
320
|
+
useSAB: false,
|
|
321
|
+
sabRGBA: null,
|
|
322
|
+
sabNx: nxBuf, sabNy: nyBuf, sabNz: nzBuf, sabA8: a8Buf
|
|
323
|
+
}, [nxBuf, nyBuf, nzBuf, a8Buf]);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
_shadeWithWorkers(jobId, params) {
|
|
329
|
+
if (this._workers.length === 0) {
|
|
330
|
+
return new Promise((resolve) => {
|
|
331
|
+
this._shadeTileOnMain(0, this._discSize, params);
|
|
332
|
+
resolve(jobId);
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
let d = this._discSize;
|
|
337
|
+
let W = this._workers.length;
|
|
338
|
+
let colsPer = Math.floor(d / W);
|
|
339
|
+
let rem = d - colsPer * W;
|
|
340
|
+
let promises = [];
|
|
341
|
+
let self = this;
|
|
342
|
+
|
|
343
|
+
for (let i = 0; i < W; i++) {
|
|
344
|
+
let x0 = i * colsPer + Math.min(i, rem);
|
|
345
|
+
let x1 = x0 + colsPer + (i < rem ? 1 : 0);
|
|
346
|
+
|
|
347
|
+
promises.push(new Promise(function(resolve) {
|
|
348
|
+
let w = self._workers[i];
|
|
349
|
+
|
|
350
|
+
let handler = function(ev) {
|
|
351
|
+
let m = ev.data;
|
|
352
|
+
if (!m || m.jobId !== jobId) return;
|
|
353
|
+
// Non-SAB: stitch returned tile
|
|
354
|
+
if (!self._useSAB && m.rgba) {
|
|
355
|
+
let tile = new Uint8ClampedArray(m.rgba);
|
|
356
|
+
let width = self._discSize;
|
|
357
|
+
let tileW = x1 - x0;
|
|
358
|
+
for (let y = 0; y < width; y++) {
|
|
359
|
+
let dst = (y * width + x0) * 4;
|
|
360
|
+
let src = (y * tileW) * 4;
|
|
361
|
+
self._rgba.set(tile.subarray(src, src + tileW*4), dst);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
w.removeEventListener('message', handler);
|
|
365
|
+
resolve();
|
|
366
|
+
};
|
|
367
|
+
w.addEventListener('message', handler);
|
|
368
|
+
|
|
369
|
+
w.postMessage({
|
|
370
|
+
type: 'shade',
|
|
371
|
+
jobId,
|
|
372
|
+
x0, x1,
|
|
373
|
+
Rt: params.Rt,
|
|
374
|
+
sun: params.sun,
|
|
375
|
+
amb: params.amb, kd: params.kd, ks: params.ks,
|
|
376
|
+
shininess: params.shininess,
|
|
377
|
+
termSoft: params.termSoft, atm: params.atm, back: params.back,
|
|
378
|
+
baseColor: params.baseColor
|
|
379
|
+
});
|
|
380
|
+
}));
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return Promise.all(promises).then(function(){ return jobId; });
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// ---------- Single-thread fallback ----------
|
|
387
|
+
_shadeTileOnMain(x0, x1, P) {
|
|
388
|
+
let d = this._discSize;
|
|
389
|
+
let Nx = this._Nx, Ny = this._Ny, Nz = this._Nz, A8 = this._A8;
|
|
390
|
+
let rgba = this._rgba;
|
|
391
|
+
|
|
392
|
+
let Lx = P.sun[0], Ly = P.sun[1], Lz = P.sun[2];
|
|
393
|
+
let Hx = Lx, Hy = Ly, Hz = Lz + 1.0;
|
|
394
|
+
let invH = 1 / Math.hypot(Hx, Hy, Hz);
|
|
395
|
+
Hx *= invH; Hy *= invH; Hz *= invH;
|
|
396
|
+
|
|
397
|
+
let amb = P.amb, kd = P.kd, ks = P.ks, shin = P.shininess;
|
|
398
|
+
let termSoft = P.termSoft, atm = P.atm, back = P.back;
|
|
399
|
+
let baseR = P.baseColor[0], baseG = P.baseColor[1], baseB = P.baseColor[2];
|
|
400
|
+
|
|
401
|
+
let tone = function(c){ return 1 - Math.exp(-2.6*c); };
|
|
402
|
+
let toSRGB = function(l){ if (l<0) l=0; if (l>1) l=1; return Math.pow(l, 1/2.2); };
|
|
403
|
+
|
|
404
|
+
for (let y = 0; y < d; y++) {
|
|
405
|
+
let row = y * d;
|
|
406
|
+
for (let x = x0; x < x1; x++) {
|
|
407
|
+
let i = row + x;
|
|
408
|
+
let nz = Nz[i];
|
|
409
|
+
let p = i<<2;
|
|
410
|
+
if (nz < 0) { rgba[p]=0; rgba[p+1]=0; rgba[p+2]=0; rgba[p+3]=0; continue; }
|
|
411
|
+
let nx = Nx[i], ny = Ny[i];
|
|
412
|
+
|
|
413
|
+
let NL = nx*Lx + ny*Ly + nz*Lz;
|
|
414
|
+
let NV = nz;
|
|
415
|
+
let NH = nx*Hx + ny*Hy + nz*Hz;
|
|
416
|
+
|
|
417
|
+
let t = (NL + termSoft) / (2*termSoft);
|
|
418
|
+
if (t < 0) t = 0; else if (t > 1) t = 1;
|
|
419
|
+
let dayMask = (3 - 2*t) * t * t * (NL > 0 ? NL : 0);
|
|
420
|
+
|
|
421
|
+
let diff = kd * dayMask;
|
|
422
|
+
let spec = NL > 0 ? ks * Math.pow(NH > 0 ? NH : 0, shin) : 0;
|
|
423
|
+
|
|
424
|
+
let rimDay = atm * Math.pow(1 - (NV < 0 ? 0 : (NV > 1 ? 1 : NV)), 2.4) * (NL + 0.15 > 0 ? NL + 0.15 : 0);
|
|
425
|
+
let rimBack = 0;
|
|
426
|
+
if (Lz < 0) {
|
|
427
|
+
let away = NL < 0 ? -NL : 0;
|
|
428
|
+
rimBack = back * Math.pow(1 - (NV < 0 ? 0 : (NV > 1 ? 1 : NV)), 3.2) * away;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
let rLin = baseR*(amb + diff) + spec + 0.40*rimDay + 0.25*rimBack;
|
|
432
|
+
let gLin = baseG*(amb + diff) + spec + 0.65*rimDay + 0.40*rimBack;
|
|
433
|
+
let bLin = baseB*(amb + diff) + spec + 1.00*rimDay + 0.80*rimBack;
|
|
434
|
+
|
|
435
|
+
rLin = tone(rLin); gLin = tone(gLin); bLin = tone(bLin);
|
|
436
|
+
|
|
437
|
+
rgba[p] = Math.round(toSRGB(rLin)*255);
|
|
438
|
+
rgba[p+1] = Math.round(toSRGB(gLin)*255);
|
|
439
|
+
rgba[p+2] = Math.round(toSRGB(bLin)*255);
|
|
440
|
+
rgba[p+3] = A8[i];
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// ---------- Worker source ----------
|
|
446
|
+
_workerSource() {
|
|
447
|
+
let s = '';
|
|
448
|
+
s += `
|
|
449
|
+
let d=0, useSAB=false;
|
|
450
|
+
let Nx=null, Ny=null, Nz=null, A8=null;
|
|
451
|
+
let rgba=null;
|
|
452
|
+
|
|
453
|
+
function tone(c){ return 1 - Math.exp(-2.6*c); }
|
|
454
|
+
function toSRGB(l){ if (l<0) l=0; if (l>1) l=1; return Math.pow(l, 1/2.2); }
|
|
455
|
+
|
|
456
|
+
onmessage = function(ev){
|
|
457
|
+
let m = ev.data;
|
|
458
|
+
if (m.type === 'init') {
|
|
459
|
+
d = m.d;
|
|
460
|
+
useSAB = !!m.useSAB;
|
|
461
|
+
if (useSAB) {
|
|
462
|
+
if (m.sabRGBA) rgba = new Uint8ClampedArray(m.sabRGBA);
|
|
463
|
+
Nx = new Float32Array(m.sabNx);
|
|
464
|
+
Ny = new Float32Array(m.sabNy);
|
|
465
|
+
Nz = new Float32Array(m.sabNz);
|
|
466
|
+
A8 = new Uint8ClampedArray(m.sabA8);
|
|
467
|
+
} else {
|
|
468
|
+
Nx = new Float32Array(m.sabNx);
|
|
469
|
+
Ny = new Float32Array(m.sabNy);
|
|
470
|
+
Nz = new Float32Array(m.sabNz);
|
|
471
|
+
A8 = new Uint8ClampedArray(m.sabA8);
|
|
472
|
+
}
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
if (m.type === 'shade') {
|
|
476
|
+
let jobId = m.jobId|0;
|
|
477
|
+
let x0 = m.x0|0, x1 = m.x1|0;
|
|
478
|
+
|
|
479
|
+
let Lx = m.sun[0], Ly = m.sun[1], Lz = m.sun[2];
|
|
480
|
+
let Hx = Lx, Hy = Ly, Hz = Lz + 1.0;
|
|
481
|
+
let invH = 1 / Math.hypot(Hx,Hy,Hz);
|
|
482
|
+
Hx *= invH; Hy *= invH; Hz *= invH;
|
|
483
|
+
|
|
484
|
+
let amb = m.amb, kd = m.kd, ks = m.ks, shin = m.shininess;
|
|
485
|
+
let termSoft = m.termSoft, atm = m.atm, back = m.back;
|
|
486
|
+
let baseR = m.baseColor[0], baseG = m.baseColor[1], baseB = m.baseColor[2];
|
|
487
|
+
|
|
488
|
+
if (useSAB) {
|
|
489
|
+
for (let y=0; y<d; y++) {
|
|
490
|
+
let row = y * d;
|
|
491
|
+
for (let x=x0; x<x1; x++) {
|
|
492
|
+
let i = row + x;
|
|
493
|
+
let nz = Nz[i];
|
|
494
|
+
let p = i<<2;
|
|
495
|
+
if (nz < 0) { rgba[p]=0; rgba[p+1]=0; rgba[p+2]=0; rgba[p+3]=0; continue; }
|
|
496
|
+
let nx = Nx[i], ny = Ny[i];
|
|
497
|
+
|
|
498
|
+
let NL = nx*Lx + ny*Ly + nz*Lz;
|
|
499
|
+
let NV = nz;
|
|
500
|
+
let NH = nx*Hx + ny*Hy + nz*Hz;
|
|
501
|
+
|
|
502
|
+
let t = (NL + termSoft) / (2*termSoft);
|
|
503
|
+
if (t<0) t=0; else if (t>1) t=1;
|
|
504
|
+
let dayMask = (3-2*t)*t*t*(NL>0?NL:0);
|
|
505
|
+
|
|
506
|
+
let diff = kd * dayMask;
|
|
507
|
+
let spec = NL>0 ? ks * Math.pow(NH>0?NH:0, shin) : 0;
|
|
508
|
+
|
|
509
|
+
let rimDay = atm * Math.pow(1 - (NV<0?0:(NV>1?1:NV)), 2.4) * (NL+0.15>0?NL+0.15:0);
|
|
510
|
+
let rimBack = 0;
|
|
511
|
+
if (Lz < 0) {
|
|
512
|
+
let away = NL<0 ? -NL : 0;
|
|
513
|
+
rimBack = back * Math.pow(1 - (NV<0?0:(NV>1?1:NV)), 3.2) * away;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
let rLin = baseR*(amb + diff) + spec + 0.40*rimDay + 0.25*rimBack;
|
|
517
|
+
let gLin = baseG*(amb + diff) + spec + 0.65*rimDay + 0.40*rimBack;
|
|
518
|
+
let bLin = baseB*(amb + diff) + spec + 1.00*rimDay + 0.80*rimBack;
|
|
519
|
+
|
|
520
|
+
rLin = tone(rLin); gLin = tone(gLin); bLin = tone(bLin);
|
|
521
|
+
|
|
522
|
+
rgba[p] = Math.round(toSRGB(rLin)*255);
|
|
523
|
+
rgba[p+1] = Math.round(toSRGB(gLin)*255);
|
|
524
|
+
rgba[p+2] = Math.round(toSRGB(bLin)*255);
|
|
525
|
+
rgba[p+3] = A8[i];
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
postMessage({ jobId });
|
|
529
|
+
} else {
|
|
530
|
+
let tileW = x1 - x0;
|
|
531
|
+
let out = new Uint8ClampedArray(tileW * d * 4);
|
|
532
|
+
for (let y=0; y<d; y++) {
|
|
533
|
+
let row = y * d;
|
|
534
|
+
let base = y * tileW * 4;
|
|
535
|
+
for (let x=x0; x<x1; x++) {
|
|
536
|
+
let i = row + x;
|
|
537
|
+
let nz = Nz[i];
|
|
538
|
+
let p = base + (x - x0) * 4;
|
|
539
|
+
if (nz < 0) { out[p]=0; out[p+1]=0; out[p+2]=0; out[p+3]=0; continue; }
|
|
540
|
+
let nx = Nx[i], ny = Ny[i];
|
|
541
|
+
|
|
542
|
+
let NL = nx*Lx + ny*Ly + nz*Lz;
|
|
543
|
+
let NV = nz;
|
|
544
|
+
let NH = nx*Hx + ny*Hy + nz*Hz;
|
|
545
|
+
|
|
546
|
+
let t = (NL + termSoft) / (2*termSoft);
|
|
547
|
+
if (t<0) t=0; else if (t>1) t=1;
|
|
548
|
+
let dayMask = (3-2*t)*t*t*(NL>0?NL:0);
|
|
549
|
+
|
|
550
|
+
let diff = kd * dayMask;
|
|
551
|
+
let spec = NL>0 ? ks * Math.pow(NH>0?NH:0, shin) : 0;
|
|
552
|
+
|
|
553
|
+
let rimDay = atm * Math.pow(1 - (NV<0?0:(NV>1?1:NV)), 2.4) * (NL+0.15>0?NL+0.15:0);
|
|
554
|
+
let rimBack = 0;
|
|
555
|
+
if (Lz < 0) {
|
|
556
|
+
let away = NL<0 ? -NL : 0;
|
|
557
|
+
rimBack = back * Math.pow(1 - (NV<0?0:(NV>1?1:NV)), 3.2) * away;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
let rLin = baseR*(amb + diff) + spec + 0.40*rimDay + 0.25*rimBack;
|
|
561
|
+
let gLin = baseG*(amb + diff) + spec + 0.65*rimDay + 0.40*rimBack;
|
|
562
|
+
let bLin = baseB*(amb + diff) + spec + 1.00*rimDay + 0.80*rimBack;
|
|
563
|
+
|
|
564
|
+
rLin = tone(rLin); gLin = tone(gLin); bLin = tone(bLin);
|
|
565
|
+
|
|
566
|
+
out[p] = Math.round(toSRGB(rLin)*255);
|
|
567
|
+
out[p+1] = Math.round(toSRGB(gLin)*255);
|
|
568
|
+
out[p+2] = Math.round(toSRGB(bLin)*255);
|
|
569
|
+
out[p+3] = A8[i];
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
postMessage({ jobId, rgba: out.buffer }, [out.buffer]);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
`;
|
|
577
|
+
return s;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// ---------- Graticule & Antarctica ----------
|
|
581
|
+
_drawGraticule(cx, cy, r, coarsenFactor) {
|
|
582
|
+
let g = this.opts.grid;
|
|
583
|
+
let ctx = this.ctx;
|
|
584
|
+
let stepLat = Math.max(2, g.stepLat * (coarsenFactor || 1));
|
|
585
|
+
let stepLon = Math.max(2, g.stepLon * (coarsenFactor || 1));
|
|
586
|
+
let sampleStep = Math.max(1, Math.floor(g.sampleStepDeg * (coarsenFactor || 1)));
|
|
587
|
+
|
|
588
|
+
ctx.save();
|
|
589
|
+
ctx.strokeStyle = g.color;
|
|
590
|
+
ctx.globalAlpha = g.alpha;
|
|
591
|
+
ctx.lineWidth = g.lineWidth;
|
|
592
|
+
ctx.lineCap = 'round';
|
|
593
|
+
|
|
594
|
+
for (let lat=-80; lat<=80; lat+=stepLat) this._strokeLatitude(cx, cy, r, lat, sampleStep);
|
|
595
|
+
for (let lon=-180; lon<180; lon+=stepLon) this._strokeLongitude(cx, cy, r, lon, sampleStep);
|
|
596
|
+
|
|
597
|
+
ctx.restore();
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
_drawAntarctica(cx, cy, r) {
|
|
601
|
+
let ctx = this.ctx;
|
|
602
|
+
let allFront = true;
|
|
603
|
+
for (let i=0;i<this._ANT.length;i++){
|
|
604
|
+
let w = this._latLonToXYZ(this._ANT[i][0], this._ANT[i][1]);
|
|
605
|
+
let c = this._applyR(w);
|
|
606
|
+
if (c[2] < 0) { allFront = false; break; }
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
ctx.save();
|
|
610
|
+
ctx.fillStyle = this.opts.antarcticaFill;
|
|
611
|
+
ctx.strokeStyle = this.opts.antarcticaStroke;
|
|
612
|
+
ctx.lineWidth = this.opts.antarcticaLineWidth;
|
|
613
|
+
|
|
614
|
+
if (allFront) {
|
|
615
|
+
ctx.beginPath();
|
|
616
|
+
for (let i=0;i<this._ANT.length;i++){
|
|
617
|
+
let v = this._projectLatLon(this._ANT[i][0], this._ANT[i][1]); if (!v) continue;
|
|
618
|
+
let x = cx + r*v[0], y = cy - r*v[1];
|
|
619
|
+
if (i===0) ctx.moveTo(x,y); else ctx.lineTo(x,y);
|
|
620
|
+
}
|
|
621
|
+
ctx.closePath();
|
|
622
|
+
ctx.fill(); ctx.stroke();
|
|
623
|
+
} else {
|
|
624
|
+
let runs = this._densifyAndSplitFrontRuns(this._ANT, 1.2);
|
|
625
|
+
for (let k=0;k<runs.length;k++){
|
|
626
|
+
let run = runs[k]; if (run.length<2) continue;
|
|
627
|
+
ctx.beginPath();
|
|
628
|
+
for (let i=0;i<run.length;i++){
|
|
629
|
+
let v = this._projectLatLon(run[i][0], run[i][1]); if (!v) continue;
|
|
630
|
+
let x = cx + r*v[0], y = cy - r*v[1];
|
|
631
|
+
if (i===0) ctx.moveTo(x,y); else ctx.lineTo(x,y);
|
|
632
|
+
}
|
|
633
|
+
ctx.closePath();
|
|
634
|
+
ctx.fill(); ctx.stroke();
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
ctx.restore();
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
_strokeLatitude(cx, cy, r, latDeg, stepDeg) {
|
|
641
|
+
let ctx = this.ctx;
|
|
642
|
+
ctx.beginPath();
|
|
643
|
+
let drawing = false;
|
|
644
|
+
for (let lon=-180; lon<=180; lon+=stepDeg) {
|
|
645
|
+
let v = this._projectLatLon(latDeg, lon);
|
|
646
|
+
if (!v) { drawing = false; continue; }
|
|
647
|
+
let x = cx + r*v[0], y = cy - r*v[1];
|
|
648
|
+
if (!drawing) { ctx.moveTo(x,y); drawing=true; } else ctx.lineTo(x,y);
|
|
649
|
+
}
|
|
650
|
+
ctx.stroke();
|
|
651
|
+
}
|
|
652
|
+
_strokeLongitude(cx, cy, r, lonDeg, stepDeg) {
|
|
653
|
+
let ctx = this.ctx;
|
|
654
|
+
ctx.beginPath();
|
|
655
|
+
let drawing = false;
|
|
656
|
+
for (let lat=-90; lat<=90; lat+=stepDeg) {
|
|
657
|
+
let v = this._projectLatLon(lat, lonDeg);
|
|
658
|
+
if (!v) { drawing = false; continue; }
|
|
659
|
+
let x = cx + r*v[0], y = cy - r*v[1];
|
|
660
|
+
if (!drawing) { ctx.moveTo(x,y); drawing=true; } else ctx.lineTo(x,y);
|
|
661
|
+
}
|
|
662
|
+
ctx.stroke();
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
_densifyAndSplitFrontRuns(latLonPts, stepDeg) {
|
|
666
|
+
let out = [];
|
|
667
|
+
let pts = latLonPts.slice(0);
|
|
668
|
+
if (pts[0][0] !== pts[pts.length-1][0] || pts[0][1] !== pts[pts.length-1][1]) {
|
|
669
|
+
pts.push([pts[0][0], pts[0][1]]);
|
|
670
|
+
}
|
|
671
|
+
let run = [];
|
|
672
|
+
for (let i=0;i<pts.length-1;i++){
|
|
673
|
+
let a=pts[i], b=pts[i+1];
|
|
674
|
+
let seg=this._interpLL(a,b,stepDeg);
|
|
675
|
+
for (let j=0;j<seg.length;j++){
|
|
676
|
+
let W=this._latLonToXYZ(seg[j][0], seg[j][1]);
|
|
677
|
+
let C=this._applyR(W);
|
|
678
|
+
if (C[2]>=0) run.push([seg[j][0], seg[j][1]]);
|
|
679
|
+
else { if (run.length>2) out.push(run); run=[]; }
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
if (run.length>2) out.push(run);
|
|
683
|
+
return out;
|
|
684
|
+
}
|
|
685
|
+
_interpLL(a,b,stepDeg){
|
|
686
|
+
let latA=a[0], lonA=a[1], latB=b[0], lonB=b[1];
|
|
687
|
+
let dLon = lonB - lonA; if (dLon>180) dLon-=360; if (dLon<-180) dLon+=360;
|
|
688
|
+
let dLat = latB - latA;
|
|
689
|
+
let dist = Math.hypot(dLat, dLon);
|
|
690
|
+
let steps = Math.max(1, Math.ceil(dist / stepDeg));
|
|
691
|
+
let out = [];
|
|
692
|
+
for (let i=0;i<steps;i++){
|
|
693
|
+
let t = i/steps;
|
|
694
|
+
out.push([latA + dLat*t, lonA + dLon*t]);
|
|
695
|
+
}
|
|
696
|
+
return out;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
_projectLatLon(latDeg, lonDeg) {
|
|
700
|
+
let v = this._latLonToXYZ(latDeg, lonDeg);
|
|
701
|
+
let c = this._applyR(v);
|
|
702
|
+
if (c[2] < 0) return null;
|
|
703
|
+
return [c[0], c[1]];
|
|
704
|
+
}
|
|
705
|
+
_latLonToXYZ(latDeg, lonDeg){
|
|
706
|
+
let φ = latDeg * Math.PI / 180, λ = lonDeg * Math.PI / 180;
|
|
707
|
+
let cφ = Math.cos(φ), sφ = Math.sin(φ);
|
|
708
|
+
let sλ = Math.sin(λ), cλ = Math.cos(λ);
|
|
709
|
+
return [cφ*sλ, sφ, cφ*cλ];
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// ---------- Unit-disc normal map ----------
|
|
713
|
+
_buildNormalMap(d) {
|
|
714
|
+
let rad = d * 0.5, invR = 1 / rad;
|
|
715
|
+
this._Nx = new Float32Array(d*d);
|
|
716
|
+
this._Ny = new Float32Array(d*d);
|
|
717
|
+
this._Nz = new Float32Array(d*d);
|
|
718
|
+
this._A8 = new Uint8ClampedArray(d*d);
|
|
719
|
+
|
|
720
|
+
let i = 0;
|
|
721
|
+
for (let y=0; y<d; y++) {
|
|
722
|
+
let vy_screen = (y + 0.5 - rad) * invR;
|
|
723
|
+
for (let x=0; x<d; x++) {
|
|
724
|
+
let vx = (x + 0.5 - rad) * invR;
|
|
725
|
+
let vy = -vy_screen;
|
|
726
|
+
let rr = vx*vx + vy*vy;
|
|
727
|
+
if (rr > 1.0005) {
|
|
728
|
+
this._Nz[i] = -1;
|
|
729
|
+
this._A8[i] = 0;
|
|
730
|
+
} else {
|
|
731
|
+
let dist = Math.sqrt(rr);
|
|
732
|
+
let vz = Math.sqrt(1 - (rr < 0 ? 0 : rr));
|
|
733
|
+
this._Nx[i] = vx; this._Ny[i] = vy; this._Nz[i] = vz;
|
|
734
|
+
let aa = (1 - dist) * rad; if (aa < 0) aa = 0; if (aa > 1) aa = 1;
|
|
735
|
+
this._A8[i] = Math.round(255 * aa);
|
|
736
|
+
}
|
|
737
|
+
i++;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// ---------- Offscreen ----------
|
|
743
|
+
_getOff(w, h) {
|
|
744
|
+
if (!this._off || this._off.canvas.width !== w || this._off.canvas.height !== h) {
|
|
745
|
+
let c = document.createElement('canvas'); c.width = w; c.height = h;
|
|
746
|
+
this._off = { canvas: c, ctx: c.getContext('2d') };
|
|
747
|
+
}
|
|
748
|
+
return this._off;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// ---------- Arcball + inertia ----------
|
|
752
|
+
_attachPointerHandlers() {
|
|
753
|
+
let el = this.canvas;
|
|
754
|
+
let s = getComputedStyle(el);
|
|
755
|
+
if (s.touchAction !== 'none') el.style.touchAction = 'none';
|
|
756
|
+
let onDown = (ev) => {
|
|
757
|
+
this._dragging = true; this._isAnimating = true;
|
|
758
|
+
this._v0 = this._screenToArcball(ev.clientX, ev.clientY);
|
|
759
|
+
this._omega = 0; this._lastTime = performance.now();
|
|
760
|
+
try { el.setPointerCapture(ev.pointerId); } catch(e){}
|
|
761
|
+
};
|
|
762
|
+
let onMove = (ev) => {
|
|
763
|
+
if (!this._dragging) return;
|
|
764
|
+
let v1 = this._screenToArcball(ev.clientX, ev.clientY);
|
|
765
|
+
let dq = this._qFromVectors(this._v0, v1);
|
|
766
|
+
this.q = this._qNormalize(this._qMul(dq, this.q));
|
|
767
|
+
this._v0 = v1;
|
|
768
|
+
|
|
769
|
+
let now = performance.now(), dt = Math.max(1, now - this._lastTime) / 1000;
|
|
770
|
+
this._lastTime = now;
|
|
771
|
+
|
|
772
|
+
let angle = 2 * Math.acos(Math.max(-1, Math.min(1, dq[3])));
|
|
773
|
+
if (angle > Math.PI) angle = 2*Math.PI - angle;
|
|
774
|
+
let s = Math.sqrt(Math.max(0, 1 - dq[3]*dq[3]));
|
|
775
|
+
if (s < 1e-6) { this._axis[0]=0; this._axis[1]=1; this._axis[2]=0; }
|
|
776
|
+
else { this._axis[0]=dq[0]/s; this._axis[1]=dq[1]/s; this._axis[2]=dq[2]/s; }
|
|
777
|
+
this._omega = angle / dt;
|
|
778
|
+
|
|
779
|
+
this._updateRot();
|
|
780
|
+
this.render();
|
|
781
|
+
};
|
|
782
|
+
let onUp = (ev) => {
|
|
783
|
+
this._dragging = false;
|
|
784
|
+
try { el.releasePointerCapture(ev.pointerId); } catch(e){}
|
|
785
|
+
this._startInertia();
|
|
786
|
+
};
|
|
787
|
+
el.addEventListener('pointerdown', onDown);
|
|
788
|
+
el.addEventListener('pointermove', onMove);
|
|
789
|
+
el.addEventListener('pointerup', onUp);
|
|
790
|
+
el.addEventListener('pointercancel', onUp);
|
|
791
|
+
this._ptr = { onDown, onMove, onUp };
|
|
792
|
+
}
|
|
793
|
+
_detachPointerHandlers() {
|
|
794
|
+
if (!this._ptr) return;
|
|
795
|
+
let el = this.canvas;
|
|
796
|
+
el.removeEventListener('pointerdown', this._ptr.onDown);
|
|
797
|
+
el.removeEventListener('pointermove', this._ptr.onMove);
|
|
798
|
+
el.removeEventListener('pointerup', this._ptr.onUp);
|
|
799
|
+
el.removeEventListener('pointercancel', this._ptr.onUp);
|
|
800
|
+
this._ptr = null;
|
|
801
|
+
}
|
|
802
|
+
_startInertia() {
|
|
803
|
+
if (this._omega <= this.opts.inertiaMinSpeed) { this._omega = 0; this._isAnimating = false; this.render(); return; }
|
|
804
|
+
let friction = this.opts.inertiaFriction;
|
|
805
|
+
let last = performance.now();
|
|
806
|
+
let step = (now) => {
|
|
807
|
+
let dt = Math.max(1, now - last) / 1000; last = now;
|
|
808
|
+
this._omega *= Math.exp(-friction * dt);
|
|
809
|
+
if (this._omega <= this.opts.inertiaMinSpeed) {
|
|
810
|
+
this._omega = 0; this._isAnimating = false; this._animRAF = 0; this.render(); return;
|
|
811
|
+
}
|
|
812
|
+
let dq = this._qFromAxisAngle(this._axis[0], this._axis[1], this._axis[2], this._omega * dt);
|
|
813
|
+
this.q = this._qNormalize(this._qMul(dq, this.q));
|
|
814
|
+
this._updateRot();
|
|
815
|
+
this.render();
|
|
816
|
+
this._animRAF = requestAnimationFrame(step);
|
|
817
|
+
};
|
|
818
|
+
if (this._animRAF) cancelAnimationFrame(this._animRAF);
|
|
819
|
+
this._animRAF = requestAnimationFrame(step);
|
|
820
|
+
}
|
|
821
|
+
_screenToArcball(clientX, clientY) {
|
|
822
|
+
let rect = this.canvas.getBoundingClientRect();
|
|
823
|
+
let cx = rect.left + rect.width / 2;
|
|
824
|
+
let cy = rect.top + rect.height / 2;
|
|
825
|
+
let pad = this.opts.padding;
|
|
826
|
+
let r = Math.max(1, Math.min(rect.width, rect.height)/2 - pad);
|
|
827
|
+
let x = (clientX - cx) / r;
|
|
828
|
+
let y = (cy - clientY) / r;
|
|
829
|
+
let d2 = x*x + y*y;
|
|
830
|
+
if (d2 <= 1) return [x, y, Math.sqrt(1 - d2)];
|
|
831
|
+
let inv = 1 / Math.sqrt(d2);
|
|
832
|
+
return [x*inv, y*inv, 0];
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// ---------- Math ----------
|
|
836
|
+
_updateRot() { this.R = this._mat3FromQuat(this.q); this.Rt = this._mat3Transpose(this.R); }
|
|
837
|
+
_applyR(v) {
|
|
838
|
+
let R = this.R;
|
|
839
|
+
return [ R[0]*v[0] + R[3]*v[1] + R[6]*v[2],
|
|
840
|
+
R[1]*v[0] + R[4]*v[1] + R[7]*v[2],
|
|
841
|
+
R[2]*v[0] + R[5]*v[1] + R[8]*v[2] ];
|
|
842
|
+
}
|
|
843
|
+
_qIdentity() { return [0,0,0,1]; }
|
|
844
|
+
_qNormalize(q){ let x=q[0],y=q[1],z=q[2],w=q[3]; let n=Math.hypot(x,y,z,w)||1; q[0]=x/n;q[1]=y/n;q[2]=z/n;q[3]=w/n; return q; }
|
|
845
|
+
_qMul(a,b){ return [ a[3]*b[0] + a[0]*b[3] + a[1]*b[2] - a[2]*b[1],
|
|
846
|
+
a[3]*b[1] - a[0]*b[2] + a[1]*b[3] + a[2]*b[0],
|
|
847
|
+
a[3]*b[2] + a[0]*b[1] - a[1]*b[0] + a[2]*b[3],
|
|
848
|
+
a[3]*b[3] - a[0]*b[0] - a[1]*b[1] - a[2]*b[2] ]; }
|
|
849
|
+
_qFromAxisAngle(ax,ay,az,angle){ let n=Math.hypot(ax,ay,az)||1; let s=Math.sin(angle*0.5)/n; return [ax*s,ay*s,az*s,Math.cos(angle*0.5)]; }
|
|
850
|
+
_qFromVectors(u,v){
|
|
851
|
+
let dot=u[0]*v[0]+u[1]*v[1]+u[2]*v[2];
|
|
852
|
+
if (dot>=1-1e-10) return [0,0,0,1];
|
|
853
|
+
if (dot<=-1+1e-10){ let ax=Math.abs(u[0])<0.9?1:0, ay=Math.abs(u[1])<0.9?1:0, az=Math.abs(u[2])<0.9?1:0;
|
|
854
|
+
let cx=u[1]*az-u[2]*ay, cy=u[2]*ax-u[0]*az, cz=u[0]*ay-u[1]*ax; return this._qNormalize([cx,cy,cz,0]); }
|
|
855
|
+
let cx=u[1]*v[2]-u[2]*v[1], cy=u[2]*v[0]-u[0]*v[2], cz=u[0]*v[1]-u[1]*v[0], w=1+dot;
|
|
856
|
+
return this._qNormalize([cx,cy,cz,w]);
|
|
857
|
+
}
|
|
858
|
+
_mat3FromQuat(q){
|
|
859
|
+
let x=q[0],y=q[1],z=q[2],w=q[3],x2=x+x,y2=y+y,z2=z+z,xx=x*x2,yy=y*y2,zz=z*z2,xy=x*y2,xz=x*z2,yz=y*z2,wx=w*x2,wy=w*y2,wz=w*z2;
|
|
860
|
+
return [1-(yy+zz), xy+wz, xz-wy, xy-wz, 1-(xx+zz), yz+wx, xz+wy, yz-wx, 1-(xx+yy)];
|
|
861
|
+
}
|
|
862
|
+
_mat3Transpose(m){ return [m[0],m[3],m[6], m[1],m[4],m[7], m[2],m[5],m[8]]; }
|
|
863
|
+
_normVec(v){ let n=Math.hypot(v[0],v[1],v[2])||1; return [v[0]/n, v[1]/n, v[2]/n]; }
|
|
864
|
+
}
|
|
865
|
+
class Demo_UI extends Active_HTML_Document {
|
|
866
|
+
constructor(spec = {}) {
|
|
867
|
+
spec.__type_name = spec.__type_name || 'demo_ui';
|
|
868
|
+
super(spec);
|
|
869
|
+
const {context} = this;
|
|
870
|
+
if (typeof this.body.add_class === 'function') {
|
|
871
|
+
this.body.add_class('demo-ui');
|
|
872
|
+
}
|
|
873
|
+
const compose = () => {
|
|
874
|
+
const window = new controls.Window({
|
|
875
|
+
context: context,
|
|
876
|
+
title: 'jsgui3-html Dropdown_Menu',
|
|
877
|
+
pos: [5, 5]
|
|
878
|
+
});
|
|
879
|
+
window.size = [1000, 1000];
|
|
880
|
+
const canvas = new controls.canvas({
|
|
881
|
+
context
|
|
882
|
+
});
|
|
883
|
+
canvas.dom.attributes.id = 'globeCanvas'
|
|
884
|
+
canvas.size = [900, 900];
|
|
885
|
+
window.inner.add(canvas);
|
|
886
|
+
this.body.add(window);
|
|
887
|
+
this._ctrl_fields = this._ctrl_fields || {};
|
|
888
|
+
this._ctrl_fields.canvas = this.canvas = canvas;
|
|
889
|
+
}
|
|
890
|
+
if (!spec.el) {
|
|
891
|
+
compose();
|
|
892
|
+
//this.add_change_listeners();
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
/*
|
|
896
|
+
add_change_listeners() {
|
|
897
|
+
const {select_options} = this;
|
|
898
|
+
select_options.data.model.on('change', e => {
|
|
899
|
+
console.log('select_options.data.model change e', e);
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
*/
|
|
903
|
+
activate() {
|
|
904
|
+
if (!this.__active) {
|
|
905
|
+
super.activate();
|
|
906
|
+
const {context, ti1, ti2} = this;
|
|
907
|
+
//this.add_change_listeners();
|
|
908
|
+
console.log('activate Demo_UI');
|
|
909
|
+
context.on('window-resize', e_resize => {
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
/*
|
|
913
|
+
const globe = new EarthGlobeRenderer("globeCanvas", {
|
|
914
|
+
background: "#081019",
|
|
915
|
+
quality: 1.25,
|
|
916
|
+
grid: { enabled: true, color: "#444" }, // dark grey default
|
|
917
|
+
inertiaFriction: 2.8,
|
|
918
|
+
zoom: 1
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
// Place sun front-left-up:
|
|
922
|
+
globe.setSunFromSpherical(-35, 25);
|
|
923
|
+
*/
|
|
924
|
+
|
|
925
|
+
let globe = new EarthGlobeRenderer('globeCanvas', {
|
|
926
|
+
background: '#081019',
|
|
927
|
+
quality: 1.2,
|
|
928
|
+
grid: { enabled: true, color: '#444' } // dark grey default
|
|
929
|
+
});
|
|
930
|
+
globe.setSunFromSpherical(-35, 25);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
Demo_UI.css = `
|
|
935
|
+
* {
|
|
936
|
+
margin: 0;
|
|
937
|
+
padding: 0;
|
|
938
|
+
}
|
|
939
|
+
body {
|
|
940
|
+
overflow-x: hidden;
|
|
941
|
+
overflow-y: hidden;
|
|
942
|
+
background-color: #E0E0E0;
|
|
943
|
+
}
|
|
944
|
+
.demo-ui {
|
|
945
|
+
}
|
|
946
|
+
`;
|
|
947
|
+
controls.Demo_UI = Demo_UI;
|
|
948
|
+
module.exports = jsgui;
|