@pirireis/webglobeplugins 0.15.22-alpha → 0.15.25

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.
@@ -16,15 +16,16 @@ import { BufferManager } from '../../util/account/single-attribute-buffer-manage
16
16
  import { BufferOrchestrator } from '../../util/account/single-attribute-buffer-management/buffer-orchestrator';
17
17
  import { LineStripProgramCache } from '../../programs/line-on-globe/linestrip/linestrip';
18
18
  import { opacityCheck, colorCheck } from '../../util/check/typecheck';
19
+ import { FrameCounterTrigger } from '../../util/frame-counter-trigger';
19
20
  const initialCapacity = 2; // initial capacity for the buffer managers
20
21
  // const _float32Array = new Float32Array(360 * 3 * 2).fill(NaN);
21
22
  export class Padding1DegreePlugin {
22
23
  id;
23
24
  globe = null;
24
25
  bufferOrchestrator;
25
- bufferManagersMap;
26
+ bufferManagerMap;
26
27
  lineProgram = null;
27
- _innerPaddingRatio = 0.9; //
28
+ _innerPaddingRatio = 0.95; //
28
29
  _float32Array = null; // this is used to forward the data to the buffer manager
29
30
  _uboHandler = null; // this is used to forward the data to the shader program
30
31
  _bufferNames = ["position2d", "position3d"];
@@ -42,9 +43,10 @@ export class Padding1DegreePlugin {
42
43
  adaptiveOpacity: true, // if true, the opacity will be adaptive to the LOD
43
44
  isMSL: false // if true, no elevation of terrain
44
45
  };
46
+ _frameCounterTrigger = null;
45
47
  constructor(id, options = null) {
46
48
  this.id = id;
47
- this.bufferManagersMap = new Map();
49
+ this.bufferManagerMap = new Map();
48
50
  this.lineProgram = null;
49
51
  this._options = options ? { ...this._options, ...options } : this._options;
50
52
  this.bufferOrchestrator = new BufferOrchestrator();
@@ -52,6 +54,21 @@ export class Padding1DegreePlugin {
52
54
  this._bufferNames.push("color");
53
55
  }
54
56
  }
57
+ increaseSpace(amount) {
58
+ if (this._freed) {
59
+ console.warn("Plugin is freed, cannot increase space");
60
+ return;
61
+ }
62
+ if (this.globe === null) {
63
+ console.warn("Globe is not initialized.");
64
+ return;
65
+ }
66
+ if (typeof amount !== "number" || amount <= 0) {
67
+ console.warn("Invalid amount, must be a positive number");
68
+ return;
69
+ }
70
+ this.bufferOrchestrator.ensureSpace(amount, this.bufferManagerMap);
71
+ }
55
72
  insertBulk(inputs) {
56
73
  if (this.globe === null) {
57
74
  console.warn("Globe is not initialized, cannot insert input");
@@ -63,16 +80,78 @@ export class Padding1DegreePlugin {
63
80
  }
64
81
  const wrapper = [null];
65
82
  this._float32Array = new Float32Array(360 * 4 * 4); // largest float32Array
83
+ let newItemCount = 0;
84
+ for (let i = 0; i < inputs.length; i++) {
85
+ if (!this._dataMap.has(inputs[i].key)) {
86
+ newItemCount++;
87
+ }
88
+ }
89
+ this.bufferOrchestrator.ensureSpace(newItemCount, this.bufferManagerMap);
66
90
  for (let i = 0; i < inputs.length; i++) {
67
91
  const coords = preAdapter(this.globe, inputs[i], this._innerPaddingRatio, null);
68
92
  const paddingInput = { ...inputs[i], ...coords };
69
93
  this._dataMap.set(paddingInput.key, paddingInput);
70
94
  wrapper[0] = paddingInput;
71
- this.bufferOrchestrator.insertBulk(wrapper, this.bufferManagersMap, this._bufferNames);
95
+ this.bufferOrchestrator.insertBulk(wrapper, this.bufferManagerMap, this._bufferNames);
72
96
  }
73
97
  this._float32Array = null;
74
98
  this.globe?.DrawRender();
75
99
  }
100
+ updateColorRatios(key, colorRatios, drawRender = true) {
101
+ if (this._freed) {
102
+ console.warn("Plugin is freed, cannot update color");
103
+ return;
104
+ }
105
+ if (this.globe === null) {
106
+ console.warn("Globe is not initialized, cannot update color");
107
+ return;
108
+ }
109
+ if (!this._options.variativeColorsOn) {
110
+ console.warn("VariativeColors are not enabled");
111
+ return;
112
+ }
113
+ if (!this._dataMap.has(key)) {
114
+ console.warn(`Key ${key} does not exist in the data map`);
115
+ return;
116
+ }
117
+ const paddingInput = this._dataMap.get(key);
118
+ if (!paddingInput) {
119
+ console.warn(`Padding input for key ${key} is not found`);
120
+ return;
121
+ }
122
+ this._float32Array = new Float32Array(360 * 4 * 4); // largest float32Array
123
+ paddingInput.colorsRatios = colorRatios;
124
+ this.bufferOrchestrator.updateBulk([paddingInput], this.bufferManagerMap, ["color"]);
125
+ this._float32Array = null;
126
+ if (drawRender) {
127
+ this.globe.DrawRender();
128
+ }
129
+ }
130
+ updateCoordinates(items) {
131
+ if (this._freed) {
132
+ console.warn("Plugin is freed, cannot update coordinates");
133
+ return;
134
+ }
135
+ if (this.globe === null) {
136
+ console.warn("Globe is not initialized, cannot update coordinates");
137
+ return;
138
+ }
139
+ this._float32Array = new Float32Array(360 * 4 * 4); // largest float32Array
140
+ const updateKeys = [];
141
+ for (const item of items) {
142
+ const paddingInput = this._dataMap.get(item.key);
143
+ if (!paddingInput) {
144
+ console.warn(`Padding input for key ${item.key} is not found`);
145
+ continue;
146
+ }
147
+ paddingInput.center = item.center;
148
+ paddingInput.radius = item.radius;
149
+ updateKeys.push(item.key);
150
+ }
151
+ this._buildPaddings("input", updateKeys);
152
+ this._float32Array = null;
153
+ this.globe.DrawRender();
154
+ }
76
155
  deleteBulk(keys) {
77
156
  if (this.globe === null) {
78
157
  console.warn("Globe is not initialized, cannot delete data");
@@ -82,7 +161,7 @@ export class Padding1DegreePlugin {
82
161
  console.warn("Plugin is freed, cannot delete data");
83
162
  return;
84
163
  }
85
- this.bufferOrchestrator.deleteBulk(keys, this.bufferManagersMap);
164
+ this.bufferOrchestrator.deleteBulk(keys, this.bufferManagerMap);
86
165
  this.globe?.DrawRender();
87
166
  }
88
167
  setPluginOpacity(opacity, drawRender = false) {
@@ -128,10 +207,18 @@ export class Padding1DegreePlugin {
128
207
  this._buildPaddings("elevation");
129
208
  this.globe?.DrawRender();
130
209
  }
131
- init(globe) {
210
+ init(globe, gl) {
132
211
  this.globe = globe;
133
212
  this.lineProgram = LineStripProgramCache.get(globe);
134
- const bufferManagersMap = new Map([
213
+ this._frameCounterTrigger = new FrameCounterTrigger(globe, 10, 1000, (globeChanges) => {
214
+ if (this._freed)
215
+ return;
216
+ this.__build(globeChanges);
217
+ globe.DrawRender();
218
+ });
219
+ this.__updateLODRelatedParameters();
220
+ this.__updateAdaptiveOpacityMultiplier();
221
+ const bufferManagerMap = new Map([
135
222
  ["position2d", {
136
223
  bufferManager: new BufferManager(globe.gl, 360 * 2 * 3, // plus 1 to cut
137
224
  { bufferType: this._options.bufferType, initialCapacity: initialCapacity }),
@@ -148,10 +235,10 @@ export class Padding1DegreePlugin {
148
235
  if (outerCoords === null || innerCoords === null) {
149
236
  continue;
150
237
  }
151
- let coords = globe.api_GetMercator2DPoint(outerCoords[0], outerCoords[1]);
238
+ let coordsOuter = globe.api_GetMercator2DPoint(outerCoords[0], outerCoords[1]);
152
239
  // fill the second coordinate with 0
153
- this._float32Array[i * 6] = coords[0];
154
- this._float32Array[i * 6 + 1] = coords[1];
240
+ this._float32Array[i * 6] = coordsOuter[0];
241
+ this._float32Array[i * 6 + 1] = coordsOuter[1];
155
242
  let coordsInner = globe.api_GetMercator2DPoint(innerCoords[0], innerCoords[1]);
156
243
  // fill the second coordinate with 0
157
244
  this._float32Array[i * 6 + 2] = coordsInner[0];
@@ -197,7 +284,7 @@ export class Padding1DegreePlugin {
197
284
  ],
198
285
  ]);
199
286
  if (this._options.variativeColorsOn) {
200
- bufferManagersMap.set("color", {
287
+ bufferManagerMap.set("color", {
201
288
  bufferManager: new BufferManager(globe.gl, 360 * 4 * 3, // 4 for RGBA
202
289
  { bufferType: this._options.bufferType, initialCapacity: initialCapacity }),
203
290
  adaptor: (paddingInput) => {
@@ -237,7 +324,7 @@ export class Padding1DegreePlugin {
237
324
  this._float32Array[i * 12 + 4] = color[0];
238
325
  this._float32Array[i * 12 + 5] = color[1];
239
326
  this._float32Array[i * 12 + 6] = color[2];
240
- this._float32Array[i * 12 + 7] = 0; // color[3] ;
327
+ this._float32Array[i * 12 + 7] = 0.1; // color[3];
241
328
  i++;
242
329
  }
243
330
  }
@@ -247,7 +334,7 @@ export class Padding1DegreePlugin {
247
334
  });
248
335
  }
249
336
  const vaoInput = ["position3d", "position2d", "color"].map((name) => {
250
- const bufferManager = bufferManagersMap.get(name);
337
+ const bufferManager = bufferManagerMap.get(name);
251
338
  if (!bufferManager) {
252
339
  return null;
253
340
  }
@@ -258,8 +345,8 @@ export class Padding1DegreePlugin {
258
345
  };
259
346
  });
260
347
  this._vao = this.lineProgram.createVAO(vaoInput[0], vaoInput[1], vaoInput[2]);
261
- this.bufferManagersMap = bufferManagersMap;
262
- this.bufferOrchestrator.resetWithCapacity(bufferManagersMap, initialCapacity);
348
+ this.bufferManagerMap = bufferManagerMap;
349
+ this.bufferOrchestrator.resetWithCapacity(bufferManagerMap, initialCapacity);
263
350
  this._uboHandler = this.lineProgram.createUBO("STATIC_DRAW");
264
351
  this._uboHandler.updateSingle("u_color", new Float32Array(this._options.defaultColor));
265
352
  }
@@ -272,9 +359,8 @@ export class Padding1DegreePlugin {
272
359
  console.warn("Globe or LineProgram or VAO is not initialized, cannot draw");
273
360
  return;
274
361
  }
275
- if (this.globe.api_IsScreenMoving()) {
276
- this.__build();
277
- }
362
+ this._frameCounterTrigger?.trigger();
363
+ // this.__build();
278
364
  const gl = this.globe.gl;
279
365
  const drawOptions = {
280
366
  drawRange: {
@@ -296,6 +382,7 @@ export class Padding1DegreePlugin {
296
382
  return;
297
383
  }
298
384
  this._freed = true;
385
+ this._frameCounterTrigger?.free();
299
386
  LineStripProgramCache.release(this.lineProgram);
300
387
  this.lineProgram = null;
301
388
  this._uboHandler?.free();
@@ -303,68 +390,112 @@ export class Padding1DegreePlugin {
303
390
  this.globe.gl.deleteVertexArray(this._vao);
304
391
  this.globe = null;
305
392
  this._vao = null;
306
- this.bufferManagersMap.forEach((bufferManager) => {
393
+ this.bufferManagerMap.forEach((bufferManager) => {
307
394
  bufferManager.bufferManager.free();
308
395
  });
309
396
  this._float32Array = null;
310
397
  }
311
- __updateLODRelatedParameters() {
312
- const lod = this.globe?.api_GetCurrentLODWithDecimal();
313
- this._innerPaddingRatio = 1 - Math.pow(0.7, lod);
314
- }
315
- _buildPaddings(level) {
398
+ _buildPaddings(level, subSetIDs = null) {
316
399
  if (level === "input") {
317
- // Build input paddings
400
+ this.__inner(subSetIDs);
401
+ this.__elevation(subSetIDs);
318
402
  }
319
403
  else if (level === "innerCircle") {
320
404
  // Build inner circle paddings
321
- this.__innerCircle();
322
- this.__elevation();
405
+ if (this._options.adativePaddingSize) {
406
+ this.__innerCircle(subSetIDs);
407
+ }
408
+ this.__elevation(subSetIDs);
323
409
  }
324
410
  else if (level === "elevation") {
325
411
  // Build elevation paddings
326
- this.__elevation();
412
+ this.__elevation(subSetIDs);
413
+ }
414
+ }
415
+ __inner(subSetIDs = null) {
416
+ // Implement inner circle padding logic
417
+ console.log("innerCircle Level Update");
418
+ const datas = this._dataMap;
419
+ let keys;
420
+ if (subSetIDs) {
421
+ keys = subSetIDs;
422
+ }
423
+ else {
424
+ keys = Array.from(datas.keys());
425
+ }
426
+ for (const key of keys) {
427
+ const value = datas.get(key);
428
+ if (!value)
429
+ continue;
430
+ // Implement inner circle padding logic using key and value
431
+ const { innerCoords, outerCoords } = preAdapter(this.globe, value, this._innerPaddingRatio, null);
432
+ value.outerCoords = outerCoords;
433
+ value.innerCoords = innerCoords;
327
434
  }
328
435
  }
329
- __innerCircle() {
436
+ __innerCircle(subSetIDs = null) {
330
437
  // Implement inner circle padding logic
331
438
  console.log("innerCircle Level Update");
332
439
  const datas = this._dataMap;
333
- for (const [key, value] of datas) {
440
+ let keys;
441
+ if (subSetIDs) {
442
+ keys = subSetIDs;
443
+ }
444
+ else {
445
+ keys = Array.from(datas.keys());
446
+ }
447
+ for (const key of keys) {
448
+ const value = datas.get(key);
449
+ if (!value)
450
+ continue;
334
451
  // Implement inner circle padding logic using key and value
335
452
  const { innerCoords } = preAdapter(this.globe, value, this._innerPaddingRatio, value.outerCoords);
336
453
  value.innerCoords = innerCoords;
337
454
  }
338
455
  }
339
- __elevation() {
340
- this._updateAdaptiveOpacityMultiplier();
341
- const bufferToUpdate = this.globe?.api_GetCurrentGeometry() === 1 ? "position2d" : "position3d";
342
- console.log(bufferToUpdate);
456
+ __elevation(subSetIDs = null) {
457
+ this.__updateAdaptiveOpacityMultiplier();
458
+ const bufferToUpdate = [this.globe?.api_GetCurrentGeometry() === 1 ? "position2d" : "position3d"];
343
459
  const datas = this._dataMap;
344
460
  const wrapper = [null];
345
461
  this._float32Array = new Float32Array(360 * 4 * 4); // largest float32Array
346
- for (const [key, value] of datas) {
462
+ let keys;
463
+ if (subSetIDs) {
464
+ keys = subSetIDs;
465
+ }
466
+ else {
467
+ keys = Array.from(datas.keys());
468
+ }
469
+ for (const key of keys) {
470
+ const value = datas.get(key);
471
+ if (!value)
472
+ continue;
347
473
  wrapper[0] = value;
348
- this.bufferOrchestrator.updateBulk(wrapper, this.bufferManagersMap, [bufferToUpdate]);
474
+ this.bufferOrchestrator.updateBulk(wrapper, this.bufferManagerMap, bufferToUpdate);
349
475
  }
350
476
  this._float32Array = null; // reset the float32Array
351
477
  }
352
- __build() {
353
- const globeChanges = this.lineProgram?.cameraBlockTotem.getGlobeChanges();
354
- if (globeChanges.lodChanged) {
355
- console.log("lod changed");
356
- }
357
- if (globeChanges.lodChanged) {
478
+ __build(globeChanges) {
479
+ if (globeChanges.lod) {
358
480
  this.__updateLODRelatedParameters();
359
481
  this._buildPaddings("innerCircle");
360
482
  }
361
- else if (globeChanges.lookChanged || globeChanges.geometryChanged) {
483
+ else if (globeChanges.look || globeChanges.geometry) {
362
484
  this._buildPaddings("elevation");
363
485
  }
364
486
  }
365
- _updateAdaptiveOpacityMultiplier() {
366
- const currentLod = this.globe?.api_GetCurrentLODWithDecimal();
367
- this._adaptiveOpacityMultiplier = Math.max(1 - (2.9 / currentLod), 0.1);
487
+ __updateLODRelatedParameters() {
488
+ const lod = this.globe?.api_GetCurrentLODWithDecimal();
489
+ this._innerPaddingRatio = 1 - Math.pow(0.7, lod);
490
+ }
491
+ __updateAdaptiveOpacityMultiplier() {
492
+ if (this._options.adaptiveOpacity === true) {
493
+ const currentLod = this.globe?.api_GetCurrentLODWithDecimal();
494
+ this._adaptiveOpacityMultiplier = Math.max(1 - (2.9 / currentLod), 0.1);
495
+ }
496
+ else {
497
+ this._adaptiveOpacityMultiplier = 1; // TODO: set this once on adaptiveOpacity is Set to false
498
+ }
368
499
  }
369
500
  }
370
501
  function preAdapter(globe, paddingInput, paddingRatio, outerCoords) {
@@ -374,11 +505,11 @@ function preAdapter(globe, paddingInput, paddingRatio, outerCoords) {
374
505
  for (let i = 0; i < 360; i++) {
375
506
  const { long, lat } = globe.Math.FindPointByPolar(paddingInput.center[0], // center long
376
507
  paddingInput.center[1], // center lat
377
- paddingInput.outerRadius, // outer radius
508
+ paddingInput.radius, // outer radius
378
509
  i);
379
510
  const { long: endLong, lat: endLat } = globe.Math.FindPointByPolar(paddingInput.center[0], // center long
380
511
  paddingInput.center[1], // center lat
381
- paddingInput.outerRadius * paddingRatio, // inner radius
512
+ paddingInput.radius * paddingRatio, // inner radius
382
513
  i);
383
514
  const longDifference = Math.abs(long - endLong);
384
515
  const latDifference = Math.abs(lat - endLat);
@@ -399,7 +530,7 @@ function preAdapter(globe, paddingInput, paddingRatio, outerCoords) {
399
530
  innerCoords[i] = null;
400
531
  continue;
401
532
  }
402
- const { long: endLong, lat: endLat } = globe.Math.FindPointByPolar(paddingInput.center[0], paddingInput.center[1], paddingInput.outerRadius * paddingRatio, i);
533
+ const { long: endLong, lat: endLat } = globe.Math.FindPointByPolar(paddingInput.center[0], paddingInput.center[1], paddingInput.radius * paddingRatio, i);
403
534
  innerCoords[i] = [endLong, endLat];
404
535
  }
405
536
  }
@@ -188,7 +188,7 @@ export class BufferOrchestrator {
188
188
  offsets.push(offset);
189
189
  }
190
190
  else {
191
- throw new Error("updateBulk item Key does not exist");
191
+ throw new Error(`updateBulk item Key does not exist, cannot update: ${item.key}`);
192
192
  }
193
193
  }
194
194
  if (bufferKeys) {
@@ -9,21 +9,31 @@ export class StaticDynamicStrategy {
9
9
  _staticDynamicState = StaticDynamicState.DYNAMIC;
10
10
  _transitionLevel = 8; // Default transition level
11
11
  _lastStaticDynamicState = StaticDynamicState.STATIC;
12
- constructor(globe, transitionLevel = 8) {
12
+ _lastGeometry = null;
13
+ _toStatic;
14
+ constructor(globe, transitionLevel = 8, toStatic) {
13
15
  this.globe = globe;
14
16
  this._transitionLevel = transitionLevel;
17
+ this._toStatic = toStatic;
15
18
  this.updateState();
16
19
  }
17
20
  updateState() {
18
21
  const currentLOD = this.globe.api_GetCurrentLODWithDecimal();
19
22
  const state = currentLOD < this._transitionLevel ? StaticDynamicState.STATIC : StaticDynamicState.DYNAMIC;
23
+ const currentGeometry = this.globe.api_GetCurrentGeometry();
20
24
  if (this._lastStaticDynamicState === StaticDynamicState.DYNAMIC && state === StaticDynamicState.STATIC) {
21
25
  this._staticDynamicState = StaticDynamicState.TO_STATIC;
26
+ this._toStatic();
27
+ }
28
+ else if ((currentGeometry !== this._lastGeometry) && state === StaticDynamicState.STATIC) {
29
+ this._staticDynamicState = StaticDynamicState.TO_STATIC;
30
+ this._toStatic();
22
31
  }
23
32
  else {
24
33
  this._staticDynamicState = state;
25
34
  }
26
35
  this._lastStaticDynamicState = this._staticDynamicState;
36
+ this._lastGeometry = currentGeometry;
27
37
  }
28
38
  getState() {
29
39
  return this._staticDynamicState;
@@ -0,0 +1,84 @@
1
+ import { CameraUniformBlockTotemCache } from "../programs/totems/camerauniformblock";
2
+ export class FrameCounterTrigger {
3
+ globe;
4
+ count = 0;
5
+ threshold;
6
+ timeoutMs;
7
+ timeoutId = null;
8
+ accumulatedChanges = {
9
+ geometry: false,
10
+ look: false,
11
+ lod: false,
12
+ lod2DWheel: false,
13
+ elevationScale: false,
14
+ screenMoved: false
15
+ };
16
+ cameraBlockTotem;
17
+ updateCallback;
18
+ constructor(globe, threshold, timeoutMs, updateCallback) {
19
+ this.threshold = threshold;
20
+ this.timeoutMs = timeoutMs;
21
+ this.updateCallback = updateCallback;
22
+ this.globe = globe;
23
+ this.cameraBlockTotem = CameraUniformBlockTotemCache.get(globe);
24
+ }
25
+ trigger(level = null) {
26
+ const globeChanges = this.cameraBlockTotem.getGlobeChanges();
27
+ if (!globeChanges.screenMoved)
28
+ return;
29
+ this.setChanges(globeChanges);
30
+ if (globeChanges.geometry || globeChanges.elevationScale || globeChanges.lod2DWheel) {
31
+ this.triggerUpdate();
32
+ return;
33
+ }
34
+ this.count++;
35
+ if (this.count === 1) {
36
+ this.startTimeout();
37
+ }
38
+ if (this.count >= this.threshold) {
39
+ this.triggerUpdate();
40
+ }
41
+ }
42
+ startTimeout() {
43
+ this.clearTimeout();
44
+ this.timeoutId = setTimeout(() => {
45
+ this.triggerUpdate();
46
+ }, this.timeoutMs);
47
+ }
48
+ triggerUpdate() {
49
+ this.clearTimeout();
50
+ this.updateCallback(this.accumulatedChanges);
51
+ this.reset();
52
+ }
53
+ clearTimeout() {
54
+ if (this.timeoutId) {
55
+ clearTimeout(this.timeoutId);
56
+ this.timeoutId = null;
57
+ }
58
+ }
59
+ setChanges(changes) {
60
+ // accumulate true states until reset
61
+ this.accumulatedChanges.geometry = this.accumulatedChanges.geometry || changes.geometry;
62
+ this.accumulatedChanges.look = this.accumulatedChanges.look || changes.look;
63
+ this.accumulatedChanges.lod = this.accumulatedChanges.lod || changes.lod;
64
+ this.accumulatedChanges.elevationScale = this.accumulatedChanges.elevationScale || changes.elevationScale;
65
+ this.accumulatedChanges.screenMoved = this.accumulatedChanges.screenMoved || changes.screenMoved;
66
+ this.accumulatedChanges.lod2DWheel = this.accumulatedChanges.lod2DWheel || changes.lod2DWheel;
67
+ }
68
+ getLevel() {
69
+ return this.accumulatedChanges;
70
+ }
71
+ reset() {
72
+ this.count = 0;
73
+ this.accumulatedChanges.geometry = false;
74
+ this.accumulatedChanges.look = false;
75
+ this.accumulatedChanges.lod = false;
76
+ this.accumulatedChanges.lod2DWheel = false;
77
+ this.accumulatedChanges.elevationScale = false;
78
+ this.accumulatedChanges.screenMoved = false;
79
+ this.clearTimeout();
80
+ }
81
+ free() {
82
+ CameraUniformBlockTotemCache.release(this.globe);
83
+ }
84
+ }
@@ -30,3 +30,27 @@ export default function imageToMagnitude(imageData) {
30
30
  }
31
31
  return magnitudeArray;
32
32
  }
33
+ export function imageToRadianAngle(imageData) {
34
+ const { image, uMax, vMax, uMin, vMin, height, width } = imageData;
35
+ const canvas = document.createElement('canvas');
36
+ const ctx = canvas.getContext('2d');
37
+ canvas.width = width;
38
+ canvas.height = height;
39
+ ctx.drawImage(image, 0, 0, width, height);
40
+ const img = ctx.getImageData(0, 0, width, height);
41
+ const data = img.data;
42
+ const angleArray = new Float32Array(height * width);
43
+ const uDiff = uMax - uMin;
44
+ const vDiff = vMax - vMin;
45
+ for (let i = 0; i < data.length; i += 4) {
46
+ const r = data[i];
47
+ const g = data[i + 1];
48
+ const u = uMin + (uDiff * r) / 255;
49
+ const v = vMin + (vDiff * g) / 255;
50
+ // North-oriented angle: 0 = North, π/2 = East, π = South, 3π/2 = West
51
+ const angle = Math.atan2(u, v);
52
+ const index = i / 4;
53
+ angleArray[index] = angle < 0 ? (angle + 2 * Math.PI) : angle;
54
+ }
55
+ return angleArray;
56
+ }
package/wind/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import WindPlugin from "./plugin";
2
2
  import createVectorFieldImage from "./vectorfieldimage";
3
- import imageToMagnitude from "./imagetovectorfieldandmagnitude";
3
+ import imageToMagnitude, { imageToRadianAngle } from "./imagetovectorfieldandmagnitude";
4
4
  import { createImageFromBase64 } from "../util/webglobjectbuilders";
5
- export { createVectorFieldImage, imageToMagnitude, WindPlugin, createImageFromBase64 };
5
+ export { createVectorFieldImage, imageToMagnitude, WindPlugin, createImageFromBase64, imageToRadianAngle };