roxy-cobewebgl 1.0.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,579 @@
1
+ /**
2
+ * 主线程 WebGL 回退渲染(无 OffscreenCanvas / Worker 时使用)
3
+ */
4
+
5
+ // ───────────────────────────────────────────────
6
+ // Vertex Shader
7
+ // ───────────────────────────────────────────────
8
+ const VERTEX_SHADER = `
9
+ attribute vec2 a_position;
10
+ void main() {
11
+ gl_Position = vec4(a_position, 0.0, 1.0);
12
+ }
13
+ `;
14
+
15
+ // ───────────────────────────────────────────────
16
+ // Fragment Shader
17
+ // ───────────────────────────────────────────────
18
+ const FRAGMENT_SHADER = `
19
+ precision highp float;
20
+
21
+ uniform vec2 u_resolution;
22
+ uniform float u_time;
23
+ uniform sampler2D u_texture;
24
+
25
+ uniform float u_phi;
26
+ uniform float u_theta;
27
+
28
+ uniform vec3 u_baseColor;
29
+ uniform vec3 u_glowColor;
30
+ uniform vec3 u_dotColor;
31
+ uniform float u_opacity;
32
+ uniform float u_glowOn;
33
+ uniform float u_debug;
34
+
35
+ uniform float u_dots;
36
+ uniform float u_dotSize;
37
+ uniform float u_globeRadius;
38
+
39
+ const float PI = 3.14159265359;
40
+ const float kPhi = 1.618033988749895;
41
+ const float phiMinusOne = 0.618033988749895;
42
+ const float sqrt5 = 2.23606797749979;
43
+ const float kTau = 6.283185307179586;
44
+ const float twoPiOnPhi = 3.883222077450933;
45
+ const float byLogPhiPlusOne = 1.0 / log2(1.618033988749895 + 1.0);
46
+
47
+ mat3 rotateX(float a) {
48
+ float c = cos(a), s = sin(a);
49
+ return mat3(1., 0., 0.,
50
+ 0., c, -s,
51
+ 0., s, c);
52
+ }
53
+ mat3 rotateY(float a) {
54
+ float c = cos(a), s = sin(a);
55
+ return mat3( c, 0., s,
56
+ 0., 1., 0.,
57
+ -s, 0., c);
58
+ }
59
+
60
+ vec3 nearestFibonacciLattice(vec3 p, out float m) {
61
+ float dots = u_dots;
62
+ float byDots = 1.0 / dots;
63
+ p = p.xzy;
64
+ float k = max(2., floor(
65
+ log2(sqrt5 * dots * PI * (1. - p.z * p.z)) * byLogPhiPlusOne
66
+ ));
67
+ vec2 f = floor(pow(kPhi, k) / sqrt5 * vec2(1., kPhi) + .5);
68
+ vec2 br1 = fract((f + 1.) * phiMinusOne) * kTau - twoPiOnPhi;
69
+ vec2 br2 = -2. * f;
70
+ vec2 sp = vec2(atan(p.y, p.x), p.z - 1.);
71
+ float denom = br1.x * br2.y - br2.x * br1.y;
72
+ vec2 c = floor(vec2(
73
+ br2.y * sp.x - br1.y * (sp.y * dots + 1.),
74
+ -br2.x * sp.x + br1.x * (sp.y * dots + 1.)
75
+ ) / denom);
76
+ float mindist = PI;
77
+ vec3 minip = vec3(0., 0., 1.);
78
+
79
+ for (float s = 0.; s < 4.; s += 1.) {
80
+ vec2 o = vec2(mod(s, 2.), floor(s * .5));
81
+ float idx = dot(f, c + o);
82
+ if (idx > dots || idx < 0.) continue;
83
+ float fracV = fract(idx * phiMinusOne);
84
+ float theta = fract(fracV) * kTau;
85
+ float cosphi = 1. - 2. * idx * byDots;
86
+ float sinphi = sqrt(1. - cosphi * cosphi);
87
+ vec3 sample2 = vec3(cos(theta) * sinphi, sin(theta) * sinphi, cosphi);
88
+ float dist = length(p - sample2);
89
+ if (dist < mindist) {
90
+ mindist = dist;
91
+ minip = sample2;
92
+ }
93
+ }
94
+ m = mindist;
95
+ return minip.xzy;
96
+ }
97
+
98
+ void main() {
99
+ vec2 uv = gl_FragCoord.xy / u_resolution.xy;
100
+ float aspect = u_resolution.x / u_resolution.y;
101
+ vec2 p = uv * 2. - 1.;
102
+ p.x *= aspect;
103
+
104
+ float r = u_globeRadius;
105
+ float d2 = dot(p, p);
106
+ float rSquared = r * r;
107
+ float l = d2;
108
+
109
+ float glowFactor = 0.;
110
+ vec4 color = vec4(0.);
111
+
112
+ if (d2 < rSquared) {
113
+ float z = sqrt(rSquared - d2);
114
+ vec3 nor = normalize(vec3(p.x, p.y, z));
115
+ nor = rotateY(u_phi) * rotateX(u_theta) * nor;
116
+ float dis;
117
+ vec3 gP = nearestFibonacciLattice(nor, dis);
118
+ float gLat = asin(gP.y);
119
+ float gLng = PI * 0.5 - atan(gP.z, gP.x);
120
+ vec2 dotUV = vec2(fract(gLng / kTau + 0.5), gLat / PI + 0.5);
121
+ float texSample = texture2D(u_texture, dotUV).r;
122
+ float mapColor = step(0.5, texSample);
123
+ float v = step(dis, u_dotSize);
124
+
125
+ if (u_debug > 0.5 && u_debug < 1.5) {
126
+ float dotMask = v;
127
+ vec3 surfaceColor = mix(u_baseColor, u_dotColor, dotMask);
128
+ color = vec4(surfaceColor * u_opacity, 1.);
129
+ } else if (u_debug > 1.5 && u_debug < 2.5) {
130
+ color = vec4(dotUV.x, dotUV.y, 0.0, 1.0);
131
+ } else if (u_debug > 2.5 && u_debug < 3.5) {
132
+ float dotMask = v * mapColor;
133
+ vec3 surfaceColor = mix(vec3(texSample * 0.3), u_dotColor, dotMask);
134
+ color = vec4(surfaceColor, 1.);
135
+ } else {
136
+ float dotMask = mapColor * v;
137
+ vec3 surfaceColor = mix(u_baseColor, u_dotColor, dotMask);
138
+ color = vec4(surfaceColor * u_opacity, 1.);
139
+ }
140
+
141
+ float edgeFade = sqrt(1.0 - d2 / rSquared);
142
+ color.rgb *= mix(0.3, 1.0, edgeFade);
143
+
144
+ glowFactor = u_glowOn * pow(
145
+ dot(normalize(vec3(-uv, sqrt(1. - l))), vec3(0., 0., 1.)),
146
+ 4.
147
+ ) * smoothstep(0., 1., .2 / (l - rSquared));
148
+
149
+ } else {
150
+ float outD = sqrt(0.2 / (l - rSquared));
151
+ glowFactor = u_glowOn * smoothstep(0.5, 1., outD / (outD + 1.));
152
+ }
153
+
154
+ gl_FragColor = color + vec4(glowFactor * u_glowColor, glowFactor);
155
+ }
156
+ `;
157
+
158
+ const ARC_VERTEX_SHADER = `
159
+ attribute vec3 a_arcPos;
160
+ uniform float u_phi;
161
+ uniform float u_theta;
162
+ uniform float u_aspect;
163
+ uniform float u_globeRadius;
164
+ varying float v_z;
165
+
166
+ mat3 rotateX(float a) {
167
+ float c = cos(a), s = sin(a);
168
+ return mat3(1., 0., 0., 0., c, -s, 0., s, c);
169
+ }
170
+ mat3 rotateY(float a) {
171
+ float c = cos(a), s = sin(a);
172
+ return mat3(c, 0., s, 0., 1., 0., -s, 0., c);
173
+ }
174
+
175
+ void main() {
176
+ vec3 viewPos = rotateX(-u_theta) * rotateY(-u_phi) * a_arcPos;
177
+ v_z = viewPos.z;
178
+ gl_Position = vec4(viewPos.x * u_globeRadius / u_aspect, viewPos.y * u_globeRadius, 0.0, 1.0);
179
+ }
180
+ `;
181
+
182
+ const ARC_FRAGMENT_SHADER = `
183
+ precision highp float;
184
+ uniform vec3 u_arcColor;
185
+ uniform float u_arcAlpha;
186
+ uniform float u_globeRadius;
187
+ uniform vec2 u_resolution;
188
+ varying float v_z;
189
+
190
+ void main() {
191
+ vec2 ndc = gl_FragCoord.xy / u_resolution * 2.0 - 1.0;
192
+ float aspect = u_resolution.x / u_resolution.y;
193
+ vec2 p = vec2(ndc.x * aspect, ndc.y);
194
+ float d2 = dot(p, p);
195
+ float rSq = u_globeRadius * u_globeRadius;
196
+
197
+ if (d2 < rSq) {
198
+ float sphereFrontZ = sqrt(1.0 - d2 / rSq);
199
+ if (v_z < sphereFrontZ) discard;
200
+ }
201
+
202
+ gl_FragColor = vec4(u_arcColor, u_arcAlpha);
203
+ }
204
+ `;
205
+
206
+ function createProgram(gl, vsSrc, fsSrc) {
207
+ const vs = gl.createShader(gl.VERTEX_SHADER);
208
+ gl.shaderSource(vs, vsSrc);
209
+ gl.compileShader(vs);
210
+ if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) {
211
+ console.error('Vertex shader error:', gl.getShaderInfoLog(vs));
212
+ gl.deleteShader(vs);
213
+ return null;
214
+ }
215
+
216
+ const fs = gl.createShader(gl.FRAGMENT_SHADER);
217
+ gl.shaderSource(fs, fsSrc);
218
+ gl.compileShader(fs);
219
+ if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) {
220
+ console.error('Fragment shader error:', gl.getShaderInfoLog(fs));
221
+ gl.deleteShader(fs);
222
+ gl.deleteShader(vs);
223
+ return null;
224
+ }
225
+
226
+ const prog = gl.createProgram();
227
+ gl.attachShader(prog, vs);
228
+ gl.attachShader(prog, fs);
229
+ gl.linkProgram(prog);
230
+ if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
231
+ console.error('Link error:', gl.getProgramInfoLog(prog));
232
+ gl.deleteProgram(prog);
233
+ gl.deleteShader(vs);
234
+ gl.deleteShader(fs);
235
+ return null;
236
+ }
237
+ gl.deleteShader(vs);
238
+ gl.deleteShader(fs);
239
+ return prog;
240
+ }
241
+
242
+ function createWorldTextureFromGeoJSON(gl, geojson, opts = {}) {
243
+ const W = 720, H = 360;
244
+ const c = document.createElement('canvas');
245
+ c.width = W;
246
+ c.height = H;
247
+ const ctx = c.getContext('2d');
248
+
249
+ ctx.fillStyle = '#000';
250
+ ctx.fillRect(0, 0, W, H);
251
+ ctx.fillStyle = '#fff';
252
+
253
+ for (const feature of geojson.features) {
254
+ const geom = feature.geometry;
255
+ if (!geom) continue;
256
+ let polys = [];
257
+ if (geom.type === 'Polygon') polys = [geom.coordinates];
258
+ else if (geom.type === 'MultiPolygon') polys = geom.coordinates;
259
+
260
+ for (const poly of polys) {
261
+ ctx.beginPath();
262
+ const ring = poly[0];
263
+ for (let i = 0; i < ring.length; i++) {
264
+ const [lng, lat] = ring[i];
265
+ const x = (lng + 180) / 360 * W;
266
+ const y = (90 - lat) / 180 * H;
267
+ if (i === 0) ctx.moveTo(x, y);
268
+ else ctx.lineTo(x, y);
269
+ }
270
+ ctx.closePath();
271
+ ctx.fill();
272
+ }
273
+ }
274
+
275
+ if (opts.debugShowTexture) {
276
+ c.style.cssText = 'position:fixed;left:10px;bottom:10px;width:200px;height:100px;border:1px solid #0f0;z-index:999;image-rendering:pixelated;';
277
+ document.body.appendChild(c);
278
+ }
279
+
280
+ const tex = gl.createTexture();
281
+ gl.bindTexture(gl.TEXTURE_2D, tex);
282
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
283
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, c);
284
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
285
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
286
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
287
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
288
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
289
+ gl.bindTexture(gl.TEXTURE_2D, null);
290
+ return tex;
291
+ }
292
+
293
+ const DEG2RAD = Math.PI / 180;
294
+
295
+ function latLngToVec3(lat, lng) {
296
+ const latR = lat * DEG2RAD;
297
+ const lngR = lng * DEG2RAD;
298
+ return [
299
+ Math.cos(latR) * Math.sin(lngR),
300
+ Math.sin(latR),
301
+ Math.cos(latR) * Math.cos(lngR)
302
+ ];
303
+ }
304
+
305
+ function vec3Dot(a, b) { return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]; }
306
+ function vec3Length(a) { return Math.sqrt(vec3Dot(a, a)); }
307
+ function vec3Normalize(a) {
308
+ const l = vec3Length(a);
309
+ return l > 1e-8 ? [a[0]/l, a[1]/l, a[2]/l] : [0, 0, 1];
310
+ }
311
+
312
+ function slerp(a, b, t) {
313
+ const d = Math.max(-1, Math.min(1, vec3Dot(a, b)));
314
+ const angle = Math.acos(d);
315
+ if (angle < 0.001) {
316
+ const x = a[0]*(1-t) + b[0]*t;
317
+ const y = a[1]*(1-t) + b[1]*t;
318
+ const z = a[2]*(1-t) + b[2]*t;
319
+ return vec3Normalize([x, y, z]);
320
+ }
321
+ const sinA = Math.sin(angle);
322
+ const wa = Math.sin((1 - t) * angle) / sinA;
323
+ const wb = Math.sin(t * angle) / sinA;
324
+ return [a[0]*wa + b[0]*wb, a[1]*wa + b[1]*wb, a[2]*wa + b[2]*wb];
325
+ }
326
+
327
+ function generateArcPoints(startVec, endVec, numSegments) {
328
+ numSegments = numSegments || 64;
329
+ const d = Math.max(-1, Math.min(1, vec3Dot(startVec, endVec)));
330
+ const angularDist = Math.acos(d);
331
+ const arcHeight = Math.min(0.4, 0.1 + 0.2 * angularDist);
332
+ const points = [];
333
+ for (let i = 0; i <= numSegments; i++) {
334
+ const t = i / numSegments;
335
+ const p = slerp(startVec, endVec, t);
336
+ const elevation = 1.0 + arcHeight * Math.sin(Math.PI * t);
337
+ points.push(p[0] * elevation, p[1] * elevation, p[2] * elevation);
338
+ }
339
+ return new Float32Array(points);
340
+ }
341
+
342
+ function parseHexColor(hex) {
343
+ const n = parseInt(hex.replace('#', ''), 16);
344
+ return [(n >> 16 & 255) / 255, (n >> 8 & 255) / 255, (n & 255) / 255];
345
+ }
346
+
347
+ class ArcAnimator {
348
+ constructor(arcsData, gl) {
349
+ this.gl = gl;
350
+ this.arcs = arcsData
351
+ .slice()
352
+ .sort((a, b) => a.order - b.order)
353
+ .map(arc => {
354
+ const startVec = latLngToVec3(arc.startLat, arc.startLng);
355
+ const endVec = latLngToVec3(arc.endLat, arc.endLng);
356
+ const points = generateArcPoints(startVec, endVec, 64);
357
+ const buffer = gl.createBuffer();
358
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
359
+ gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);
360
+ const numPoints = points.length / 3;
361
+ const d = Math.max(-1, Math.min(1, vec3Dot(startVec, endVec)));
362
+ const angularDist = Math.acos(d);
363
+ const color = arc.color ? parseHexColor(arc.color) : null;
364
+ const tailLen = Math.max(5, Math.floor(numPoints * 0.3));
365
+ const headEnd = numPoints - 1;
366
+ const totalDuration = 1.2 + 1.5 * angularDist;
367
+ const travelRatio = headEnd / (headEnd + tailLen);
368
+ const travelDuration = totalDuration * travelRatio;
369
+ const drainDuration = totalDuration * (1 - travelRatio);
370
+ return { buffer, numPoints, color, travelDuration, drainDuration, totalDuration, tailLen, headEnd };
371
+ });
372
+ this.arcDelay = 1.0;
373
+ const maxDur = Math.max(...this.arcs.map(a => a.totalDuration));
374
+ this.cycleDuration = Math.max(1, this.arcs.length - 1) * this.arcDelay + maxDur + 1.0;
375
+ }
376
+
377
+ getState(arcIndex, timeSec) {
378
+ const arc = this.arcs[arcIndex];
379
+ const cycleTime = timeSec % this.cycleDuration;
380
+ const arcStart = arcIndex * this.arcDelay;
381
+ const local = cycleTime - arcStart;
382
+ if (local < 0 || local > arc.totalDuration) return null;
383
+ const tailLen = arc.tailLen;
384
+ const headEnd = arc.headEnd;
385
+ if (local < arc.travelDuration) {
386
+ const p = local / arc.travelDuration;
387
+ const head = Math.min(headEnd, Math.floor(p * (headEnd + 0.9999)));
388
+ const tail = Math.max(0, head - tailLen);
389
+ const count = head - tail + 1;
390
+ return { startIdx: tail, drawCount: Math.max(2, count), alpha: 1.0 };
391
+ }
392
+ const drainP = (local - arc.travelDuration) / arc.drainDuration;
393
+ const tail = Math.floor((headEnd - tailLen) + tailLen * drainP);
394
+ const count = headEnd - tail + 1;
395
+ if (count < 2) return null;
396
+ return { startIdx: Math.min(tail, headEnd - 1), drawCount: Math.max(2, count), alpha: 1.0 };
397
+ }
398
+ }
399
+
400
+ function normalizeArcs(arcsData) {
401
+ return (arcsData || []).map((a, i) => ({
402
+ order: a.order != null ? a.order : i + 1,
403
+ startLat: a.startLat,
404
+ startLng: a.startLng,
405
+ endLat: a.endLat,
406
+ endLng: a.endLng,
407
+ color: a.color,
408
+ }));
409
+ }
410
+
411
+ /**
412
+ * @param {HTMLCanvasElement} canvas
413
+ * @param {object} config
414
+ * @param {object} [config.map]
415
+ * @param {string} [config.mapUrl]
416
+ * @param {object[]} [config.arcsData]
417
+ * @param {object} [config.params]
418
+ * @param {boolean} [config.debugShowTexture]
419
+ * @param {function(Error|string):void} [config.onError]
420
+ * @returns {Promise<{ setState: Function, destroy: Function, resize: Function }|null>}
421
+ */
422
+ export async function createFallbackGlobe(canvas, config) {
423
+ const onError = config.onError || ((e) => console.error('[cobewebgl-fallback]', e));
424
+ const gl = canvas.getContext('webgl', { antialias: false, alpha: false });
425
+ if (!gl) {
426
+ onError(new Error('WebGL 不可用'));
427
+ return null;
428
+ }
429
+
430
+ const program = createProgram(gl, VERTEX_SHADER, FRAGMENT_SHADER);
431
+ if (!program) {
432
+ onError(new Error('主着色器编译失败'));
433
+ return null;
434
+ }
435
+
436
+ const loc = {};
437
+ loc.a_position = gl.getAttribLocation(program, 'a_position');
438
+ ['u_resolution','u_time','u_texture','u_phi','u_theta','u_baseColor','u_glowColor','u_dotColor','u_opacity','u_glowOn','u_dots','u_dotSize','u_globeRadius','u_debug'].forEach(
439
+ n => loc[n] = gl.getUniformLocation(program, n)
440
+ );
441
+
442
+ const buffer = gl.createBuffer();
443
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
444
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1,1,-1,-1,1,-1,1,1,-1,1,1]), gl.STATIC_DRAW);
445
+
446
+ let geojson;
447
+ try {
448
+ if (config.map && typeof config.map === 'object') {
449
+ geojson = config.map;
450
+ } else if (config.mapUrl) {
451
+ const resp = await fetch(String(config.mapUrl));
452
+ if (!resp.ok) throw new Error('fetch map failed: ' + resp.status);
453
+ geojson = await resp.json();
454
+ } else {
455
+ throw new Error('需要 map 或 mapUrl');
456
+ }
457
+ } catch (e) {
458
+ onError(e);
459
+ return null;
460
+ }
461
+
462
+ const worldTex = createWorldTextureFromGeoJSON(gl, geojson, {
463
+ debugShowTexture: !!config.debugShowTexture,
464
+ });
465
+
466
+ const arcsNorm = normalizeArcs(config.arcsData);
467
+ let arcProgram = null;
468
+ let arcLoc = {};
469
+ let arcAnimator = null;
470
+ if (arcsNorm.length > 0) {
471
+ arcProgram = createProgram(gl, ARC_VERTEX_SHADER, ARC_FRAGMENT_SHADER);
472
+ if (arcProgram) {
473
+ arcLoc.a_arcPos = gl.getAttribLocation(arcProgram, 'a_arcPos');
474
+ ['u_phi','u_theta','u_aspect','u_globeRadius','u_arcColor','u_arcAlpha','u_resolution'].forEach(
475
+ n => arcLoc[n] = gl.getUniformLocation(arcProgram, n)
476
+ );
477
+ arcAnimator = new ArcAnimator(arcsNorm, gl);
478
+ }
479
+ }
480
+
481
+ const defaults = {
482
+ phi: 0.3, theta: 0.15,
483
+ baseColor: [0.06, 0.1, 0.2],
484
+ glowColor: [0.15, 0.4, 0.85],
485
+ dotColor: [0.4, 0.8, 1.0],
486
+ arcColor: [1.0, 0.4, 0.2],
487
+ dots: 800, dotSize: 0.008, globeRadius: 0.55, glowOn: 1.0, debug: 0.0,
488
+ opacity: 0.9, autoRotate: true, rotationSpeed: 0.003,
489
+ };
490
+ const state = { ...defaults, ...config.params };
491
+
492
+ let running = true;
493
+ let rafId = 0;
494
+
495
+ function resizeCanvas() {
496
+ const dpr = Math.min(2, window.devicePixelRatio || 1);
497
+ const w = Math.floor(canvas.clientWidth * dpr);
498
+ const h = Math.floor(canvas.clientHeight * dpr);
499
+ if (canvas.width !== w || canvas.height !== h) {
500
+ canvas.width = w;
501
+ canvas.height = h;
502
+ gl.viewport(0, 0, w, h);
503
+ }
504
+ }
505
+
506
+ function render(t) {
507
+ if (!running) return;
508
+ resizeCanvas();
509
+ const timeSec = t * 0.001;
510
+ if (state.autoRotate) state.phi += state.rotationSpeed || 0.003;
511
+
512
+ const debugVal = typeof state.debug === 'number' ? state.debug : (typeof window !== 'undefined' && window.__GLOBE_DEBUG__) || 0;
513
+
514
+ gl.useProgram(program);
515
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
516
+ gl.enableVertexAttribArray(loc.a_position);
517
+ gl.vertexAttribPointer(loc.a_position, 2, gl.FLOAT, false, 0, 0);
518
+ gl.activeTexture(gl.TEXTURE0);
519
+ gl.bindTexture(gl.TEXTURE_2D, worldTex);
520
+ gl.uniform2f(loc.u_resolution, canvas.width, canvas.height);
521
+ gl.uniform1f(loc.u_time, timeSec);
522
+ gl.uniform1i(loc.u_texture, 0);
523
+ gl.uniform1f(loc.u_phi, state.phi);
524
+ gl.uniform1f(loc.u_theta, state.theta);
525
+ gl.uniform3fv(loc.u_baseColor, state.baseColor);
526
+ gl.uniform3fv(loc.u_glowColor, state.glowColor);
527
+ gl.uniform3fv(loc.u_dotColor, state.dotColor);
528
+ gl.uniform1f(loc.u_opacity, state.opacity != null ? state.opacity : 0.9);
529
+ gl.uniform1f(loc.u_glowOn, state.glowOn);
530
+ gl.uniform1f(loc.u_dots, state.dots);
531
+ gl.uniform1f(loc.u_dotSize, state.dotSize);
532
+ gl.uniform1f(loc.u_globeRadius, state.globeRadius);
533
+ gl.uniform1f(loc.u_debug, debugVal);
534
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
535
+
536
+ if (arcProgram && arcAnimator) {
537
+ gl.enable(gl.BLEND);
538
+ gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
539
+ gl.useProgram(arcProgram);
540
+ const aspect = canvas.width / canvas.height;
541
+ gl.uniform1f(arcLoc.u_phi, state.phi);
542
+ gl.uniform1f(arcLoc.u_theta, state.theta);
543
+ gl.uniform1f(arcLoc.u_aspect, aspect);
544
+ gl.uniform1f(arcLoc.u_globeRadius, state.globeRadius);
545
+ gl.uniform2f(arcLoc.u_resolution, canvas.width, canvas.height);
546
+ try { gl.lineWidth(2.0); } catch (e) { /* ignore */ }
547
+ for (let i = 0; i < arcAnimator.arcs.length; i++) {
548
+ const st = arcAnimator.getState(i, timeSec);
549
+ if (!st) continue;
550
+ const color = arcAnimator.arcs[i].color || state.arcColor;
551
+ gl.uniform3fv(arcLoc.u_arcColor, color);
552
+ gl.uniform1f(arcLoc.u_arcAlpha, st.alpha);
553
+ gl.bindBuffer(gl.ARRAY_BUFFER, arcAnimator.arcs[i].buffer);
554
+ gl.enableVertexAttribArray(arcLoc.a_arcPos);
555
+ gl.vertexAttribPointer(arcLoc.a_arcPos, 3, gl.FLOAT, false, 0, 0);
556
+ gl.drawArrays(gl.LINE_STRIP, st.startIdx, st.drawCount);
557
+ }
558
+ gl.disable(gl.BLEND);
559
+ }
560
+ rafId = requestAnimationFrame(render);
561
+ }
562
+
563
+ resizeCanvas();
564
+ rafId = requestAnimationFrame(render);
565
+
566
+ return {
567
+ setState(partial) {
568
+ Object.assign(state, partial);
569
+ },
570
+ resize() {
571
+ resizeCanvas();
572
+ },
573
+ destroy() {
574
+ running = false;
575
+ cancelAnimationFrame(rafId);
576
+ rafId = 0;
577
+ },
578
+ };
579
+ }