@pirireis/webglobeplugins 1.3.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Math/methods.js CHANGED
@@ -76,6 +76,13 @@ export const wgs84ToCartesian3d = (output, long, lat, height) => {
76
76
  output[1] = y * radius;
77
77
  output[2] = z * radius;
78
78
  };
79
+ export const unitVectorFromLongLat = (output, long, lat) => {
80
+ const longRad = long * RADIAN;
81
+ const latRad = lat * RADIAN;
82
+ output[0] = Math.cos(latRad) * Math.cos(longRad);
83
+ output[1] = Math.cos(latRad) * Math.sin(longRad);
84
+ output[2] = Math.sin(latRad);
85
+ };
79
86
  export const wgs84ToMercator = (long, lat) => {
80
87
  return [
81
88
  WORLD_RADIUS_MERCATOR * long * RADIAN,
package/constants.js CHANGED
@@ -4,3 +4,4 @@ export const CSRenderPassPluginKeys = ["supportNormalPass",
4
4
  export const GLOBE_DEFAULT_MIN_LOD = 2;
5
5
  export const GLOBE_DEFAULT_MAX_LOD = 25;
6
6
  export const POLE2 = 12756274.0;
7
+ export const POLE_BY_PI = 6378136.99911;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pirireis/webglobeplugins",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
4
4
  "main": "index.js",
5
5
  "author": "Toprak Nihat Deniz Ozturk",
6
6
  "license": "MIT",
@@ -1,36 +1,92 @@
1
1
  import { createProgram } from "../../util/webglobjectbuilders";
2
- import { CameraUniformBlockTotemCache, CameraUniformBlockString } from "../totems";
3
- import { mercatorXYToGLPosition, cartesian3DToGLPosition } from "../../util/shaderfunctions/geometrytransformations";
2
+ import { CameraUniformBlockTotemCache, CameraUniformBlockString } from "../totems/camerauniformblock";
3
+ import { mercatorXYToGLPosition, cartesian3DToGLPosition, POLE_BY_PI, relativeBBoxPositionRadian } from "../../util/shaderfunctions/geometrytransformations";
4
4
  import { noRegisterGlobeProgramCache } from "../programcache";
5
- const vs = `#version 300 es
5
+ import { DEM_TEXTURE_BLOCK_STRING, DemTextureManagerCache } from "../totems/attachments/dem-textures-manager";
6
+ import { UniformBlockManager } from "../../util/gl-util/uniform-block/manager";
7
+ import { WORLD_RADIUS_3D } from "../../Math/constants";
8
+ const uniformBindingPoints = Object.freeze({
9
+ camera: 0,
10
+ style: 1,
11
+ dem: 2,
12
+ });
13
+ const styleBlockManager = new UniformBlockManager('StyleBlock', [
14
+ { name: "opacity", type: "float", value: new Float32Array([1.0]) },
15
+ { name: "pointSize", type: "float", value: new Float32Array([2.0]) },
16
+ { name: "hoveredPointSize", type: "float", value: new Float32Array([4.0]) },
17
+ { name: "hovered_vertexID", type: "int", value: new Int32Array([-1]) },
18
+ ], uniformBindingPoints.style);
19
+ const DEM_TEXTURE_GLPOSITION_SCRIPT = `
20
+ const float elevation = ${WORLD_RADIUS_3D};
21
+ // altitude = 0.0 / 0.0; can raise "invalid operation" before the calculation,
22
+ // so altitude is 0.0 and hasDemSample is used to determine validity.
23
+ float altitude = 0.0;
24
+ bool hasDemSample = false;
25
+ if (is3D == true) {
26
+ for (int i = 0; i < 6; i++) {
27
+ if( i == u_breakLoopIndex - 1 ) {
28
+ break;
29
+ }
30
+ vec2 uv = relativeBBoxPositionRadian(pos2D, u_demTextureBBOX[i]);
31
+ if (uv.x != -1.0) {
32
+ uv.y = 1.0 - uv.y; // flip y coordinate
33
+ // rescale and transform uv to fit in the texture data cover bbox
34
+ uv *= u_textureDataCoverRatio[i].xy;
35
+
36
+ //
37
+ altitude = texture(u_demTexture, vec3(uv, float(i))).r;
38
+ hasDemSample = true;
39
+ break;
40
+ }
41
+ }
42
+
43
+ // If the geometry is outside all given bboxes, force NaN so the primitive gets discarded.
44
+ if (!hasDemSample) {
45
+ altitude = 0.0 / 0.0;
46
+ }
47
+ vec3 position = pos3D * (elevation + (altitude + height) * elevation_scale);
48
+ gl_Position = cartesian3DToGLPosition(position);
49
+ } else {
50
+ vec2 mercatorXY = pos2D * POLE_BY_PI;
51
+ gl_Position = mercatorXYToGLPosition(mercatorXY);
52
+ }
53
+ `;
54
+ const vsF = (fitMode) => `#version 300 es
55
+ #pragma vscode_glsllint_stage : vert
56
+
57
+ precision highp float;
58
+ precision highp sampler2DArray;
59
+
60
+ ${fitMode ? DEM_TEXTURE_BLOCK_STRING : ""}
6
61
  ${CameraUniformBlockString}
62
+ ${styleBlockManager.glslCode()}
7
63
  ${mercatorXYToGLPosition}
8
64
  ${cartesian3DToGLPosition}
65
+ ${relativeBBoxPositionRadian}
66
+ ${POLE_BY_PI}
67
+
68
+
9
69
  precision highp float;
10
70
  precision highp int;
11
71
 
12
- uniform int hovered_vertexID; // can be removed
13
-
14
72
  in vec3 pos3D;
15
73
  in vec2 pos2D;
16
74
  in vec4 rgba;
17
-
18
- uniform float pointSize;
19
- uniform float hoveredPointSize;
75
+ ${fitMode ? "in float height;" : ""}
20
76
 
21
77
  flat out highp int vVertexID;
22
78
 
23
-
24
79
  out vec4 v_rgba;
25
80
 
26
81
  void main() {
27
82
 
83
+ ${fitMode ? DEM_TEXTURE_GLPOSITION_SCRIPT : `
28
84
  if(is3D){
29
85
  gl_Position = cartesian3DToGLPosition(pos3D);
30
- }
31
- else{
86
+ } else {
32
87
  gl_Position = mercatorXYToGLPosition(pos2D);
33
88
  }
89
+ `}
34
90
  if (hovered_vertexID == gl_VertexID) {
35
91
  gl_PointSize = hoveredPointSize;
36
92
  } else {
@@ -40,9 +96,12 @@ void main() {
40
96
  vVertexID = gl_VertexID;
41
97
  }`;
42
98
  const fs = `#version 300 es
99
+ #pragma vscode_glsllint_stage : frag
100
+
43
101
  precision highp float;
102
+ precision highp int;
44
103
 
45
- uniform float opacity;
104
+ ${styleBlockManager.glslCode()}
46
105
 
47
106
  flat in highp int vVertexID;
48
107
  in vec4 v_rgba;
@@ -56,46 +115,46 @@ void main() {
56
115
  fragColor.a *= opacity;
57
116
  }`;
58
117
  class PointOnGlobeProgram {
59
- constructor(globe) {
118
+ globe;
119
+ gl;
120
+ program;
121
+ demTextureManager = null; // assigned in constructor if fitMode is Dynamic, otherwise remains undefined
122
+ cameraBlockTotem;
123
+ fitMode;
124
+ _publishedUBOs = [];
125
+ _isFreed = false;
126
+ constructor(globe, fitMode) {
60
127
  this.globe = globe;
61
128
  this.gl = globe.gl;
129
+ if (fitMode != "GroundElevation" && fitMode != "SeaLevel") {
130
+ throw new Error(`Invalid fitMode: ${fitMode}. Supported values are "GroundElevation" and "SeaLevel".`);
131
+ }
132
+ this.fitMode = (fitMode === "GroundElevation");
133
+ const vs = vsF(this.fitMode);
62
134
  this.program = createProgram(this.gl, vs, fs);
63
- const { gl, program } = this;
64
- this.uniforms = {
65
- opacity: gl.getUniformLocation(program, "opacity"),
66
- hovered_vertexID: gl.getUniformLocation(program, "hovered_vertexID"),
67
- hoveredPointSize: gl.getUniformLocation(program, "hoveredPointSize"),
68
- pointSize: gl.getUniformLocation(program, "pointSize"),
69
- };
70
- // eslint-disable-next-line
71
- { // assign opacity
72
- this._lastOpacity = 1.0;
73
- this._lastPointSize = 2.0;
74
- this._lastHoveredPointSize = 4.0;
75
- this._lastHoveredID = -1;
76
- const currentProgram = gl.getParameter(gl.CURRENT_PROGRAM);
77
- gl.useProgram(program);
78
- gl.uniform1f(this.uniforms.opacity, this._lastOpacity);
79
- gl.uniform1f(this.uniforms.pointSize, this._lastPointSize);
80
- gl.uniform1f(this.uniforms.hoveredPointSize, this._lastHoveredPointSize);
81
- gl.uniform1i(this.uniforms.hovered_vertexID, this._lastHoveredID);
82
- gl.useProgram(currentProgram);
135
+ if (this.fitMode) {
136
+ this.demTextureManager = DemTextureManagerCache.get(globe);
137
+ this.demTextureManager.assignBindingPoint(this.program, uniformBindingPoints.dem);
83
138
  }
139
+ const { gl, program } = this;
140
+ styleBlockManager.assignBindingPoint(this.gl, this.program);
141
+ const styleBlockIndex = gl.getUniformBlockIndex(program, 'StyleBlock');
142
+ gl.uniformBlockBinding(program, styleBlockIndex, uniformBindingPoints.style);
84
143
  // eslint-disable-next-line
85
144
  { // assign attribute locations
86
145
  gl.bindAttribLocation(program, 0, "pos3D");
87
146
  gl.bindAttribLocation(program, 1, "pos2D");
88
147
  gl.bindAttribLocation(program, 2, "rgba");
148
+ this.fitMode && gl.bindAttribLocation(program, 3, "height");
89
149
  }
90
150
  // eslint-disable-next-line
91
151
  { // arrange camera uniform block
92
- this.cameraBlockBingingPoint = 0;
93
152
  this.cameraBlockTotem = CameraUniformBlockTotemCache.get(globe);
94
153
  const cameraBlockIndex = gl.getUniformBlockIndex(program, "CameraUniformBlock");
95
- gl.uniformBlockBinding(program, cameraBlockIndex, this.cameraBlockBingingPoint);
154
+ gl.uniformBlockBinding(program, cameraBlockIndex, uniformBindingPoints.camera);
96
155
  }
97
156
  }
98
- createVAO(pos3DBuffer, pos2DBuffer, rgbaBuffer) {
157
+ createVAO(pos3DBuffer, pos2DBuffer, rgbaBuffer, heightBuffer) {
99
158
  const { gl } = this;
100
159
  const vao = gl.createVertexArray();
101
160
  gl.bindVertexArray(vao);
@@ -118,33 +177,35 @@ class PointOnGlobeProgram {
118
177
  gl.vertexAttribPointer(2, 4, gl.FLOAT, false, 0, 0);
119
178
  // gl.vertexAttribPointer(2, 4, gl.UNSIGNED_INT, true, 0, 0);
120
179
  }
180
+ if (this.fitMode) {
181
+ if (heightBuffer) {
182
+ gl.bindBuffer(gl.ARRAY_BUFFER, heightBuffer || null);
183
+ gl.enableVertexAttribArray(3);
184
+ gl.vertexAttribPointer(3, 1, gl.FLOAT, false, 0, 0);
185
+ }
186
+ else {
187
+ gl.disableVertexAttribArray(3);
188
+ gl.vertexAttrib4f(3, 0, 0, 0, 1);
189
+ }
190
+ }
121
191
  gl.bindVertexArray(null);
122
192
  gl.bindBuffer(gl.ARRAY_BUFFER, null);
123
193
  return vao;
124
194
  }
125
- draw(vao, length, { opacity = 1.0, hoveredID = -1, pointSize = 2.0, hoveredPointSize = 4.0, elementBuffer = null } = {}) {
126
- if (length === 0 || opacity === 0)
195
+ createUBO(bufferReadType = "DYNAMIC_DRAW") {
196
+ const ubo = styleBlockManager.createUBO(this.gl, bufferReadType);
197
+ this._publishedUBOs.push(ubo);
198
+ return ubo;
199
+ }
200
+ draw(vao, length, drawOptionsUBO, elementBuffer = null) {
201
+ if (length === 0)
127
202
  return;
128
- const { gl, program, uniforms } = this;
203
+ const { gl, program } = this;
129
204
  gl.useProgram(program);
130
- if (this._lastOpacity !== opacity) {
131
- gl.uniform1f(uniforms.opacity, opacity);
132
- this._lastOpacity = opacity;
133
- }
134
- if (this._lastPointSize !== pointSize) {
135
- gl.uniform1f(uniforms.pointSize, pointSize);
136
- this._lastPointSize = pointSize;
137
- }
138
- if (this._lastHoveredPointSize !== hoveredPointSize) {
139
- gl.uniform1f(uniforms.hoveredPointSize, hoveredPointSize);
140
- this._lastHoveredPointSize = hoveredPointSize;
141
- }
142
- if (this._lastHoveredID !== hoveredID) {
143
- gl.uniform1i(uniforms.hovered_vertexID, hoveredID);
144
- this._lastHoveredID = hoveredID;
145
- }
146
205
  gl.bindVertexArray(vao);
147
- this.cameraBlockTotem.bind(this.cameraBlockBingingPoint);
206
+ this.cameraBlockTotem.bind(uniformBindingPoints.camera);
207
+ drawOptionsUBO.bind();
208
+ this.fitMode && this.demTextureManager.bindData(0, uniformBindingPoints.dem);
148
209
  if (elementBuffer) {
149
210
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elementBuffer);
150
211
  gl.drawElements(gl.POINTS, length, gl.UNSIGNED_INT, 0);
@@ -153,20 +214,29 @@ class PointOnGlobeProgram {
153
214
  else {
154
215
  gl.drawArrays(gl.POINTS, 0, length);
155
216
  }
156
- this.cameraBlockTotem.unbind(this.cameraBlockBingingPoint);
217
+ this.fitMode && this.demTextureManager.unbindData(0, uniformBindingPoints.dem);
218
+ drawOptionsUBO.unbind();
219
+ this.cameraBlockTotem.unbind(uniformBindingPoints.camera);
157
220
  gl.bindVertexArray(null);
158
221
  }
159
222
  free() {
160
223
  if (this._isFreed)
161
224
  return;
162
225
  const { gl, globe } = this;
226
+ this._publishedUBOs.forEach(ubo => ubo.free());
227
+ this._publishedUBOs = [];
163
228
  CameraUniformBlockTotemCache.release(globe);
229
+ this.fitMode && DemTextureManagerCache.release(globe);
164
230
  gl.deleteProgram(this.program);
165
231
  this._isFreed = true;
166
232
  }
167
233
  }
168
234
  const PointOnGlobeProgramCache = Object.freeze({
169
- get: (globe) => noRegisterGlobeProgramCache.getProgram(globe, PointOnGlobeProgram),
170
- release: (globe) => noRegisterGlobeProgramCache.releaseProgram(globe, PointOnGlobeProgram)
235
+ get(globe, fitMode) {
236
+ return noRegisterGlobeProgramCache.getProgram(globe, PointOnGlobeProgram, fitMode);
237
+ },
238
+ release(globe, fitMode) {
239
+ noRegisterGlobeProgramCache.releaseProgram(globe, PointOnGlobeProgram, fitMode);
240
+ }
171
241
  });
172
242
  export { PointOnGlobeProgramCache };
@@ -89,32 +89,35 @@ const glProgramCache = (function () {
89
89
  })();
90
90
  const noRegisterGlobeProgramCache = (function () {
91
91
  const cache = new Map();
92
- function getProgram(globe, ProgramClass) {
92
+ const keyFunction = (globe, ProgramClass, mode) => `${globe.id}_${ProgramClass.name}_${mode}`;
93
+ function getProgram(globe, ProgramClass, mode) {
94
+ const key = keyFunction(globe, ProgramClass, mode);
93
95
  if (!cache.has(globe)) {
94
96
  cache.set(globe, new Map());
95
97
  }
96
98
  ;
97
- if (!cache.get(globe).has(ProgramClass)) {
98
- cache.get(globe).set(ProgramClass, {
99
- program: new ProgramClass(globe),
99
+ if (!cache.get(globe).has(key)) {
100
+ cache.get(globe).set(key, {
101
+ program: new ProgramClass(globe, mode),
100
102
  count: 1
101
103
  });
102
104
  }
103
105
  else {
104
- cache.get(globe).get(ProgramClass).count++;
106
+ cache.get(globe).get(key).count++;
105
107
  }
106
- return cache.get(globe).get(ProgramClass).program;
108
+ return cache.get(globe).get(key).program;
107
109
  }
108
110
  ;
109
- function releaseProgram(globe, ProgramClass) {
110
- if (cache.has(globe) && cache.get(globe).has(ProgramClass)) {
111
- if (cache.get(globe).get(ProgramClass).count === 0) {
111
+ function releaseProgram(globe, ProgramClass, mode) {
112
+ const key = keyFunction(globe, ProgramClass, mode);
113
+ if (cache.has(globe) && cache.get(globe).has(key)) {
114
+ if (cache.get(globe).get(key).count === 0) {
112
115
  throw new Error("The program counter is already 0, cannot release program");
113
116
  }
114
- cache.get(globe).get(ProgramClass).count--;
115
- if (cache.get(globe).get(ProgramClass).count === 0) {
116
- cache.get(globe).get(ProgramClass).program.free();
117
- cache.get(globe).delete(ProgramClass);
117
+ cache.get(globe).get(key).count--;
118
+ if (cache.get(globe).get(key).count === 0) {
119
+ cache.get(globe).get(key).program.free();
120
+ cache.get(globe).delete(key);
118
121
  }
119
122
  }
120
123
  }
@@ -174,6 +174,7 @@ export class DemTextureManager {
174
174
  for (const obj of this.registry) {
175
175
  obj.setMergedTiles(this.mergedData);
176
176
  }
177
+ console.log("DEM Texture Manager updated with new merged tiles data");
177
178
  }
178
179
  bindData(textureUnit, bindingPoint) {
179
180
  const gl = this._globe.gl;
@@ -0,0 +1 @@
1
+ "use strict";
@@ -1,31 +1,51 @@
1
- import { BufferOrchestrator, BufferManager, ObjectStore } from "../../util/account/index";
1
+ import { BufferOrchestrator, BufferManager, ObjectStore } from "../../util/account/single-attribute-buffer-management/index";
2
2
  import { PickerDisplayer } from "../../util/picking/picker-displayer";
3
- import { PointOnGlobeProgramCache } from "../../programs/point-on-globe/square-pixel-point";
4
- import { defaultblendfunction } from "../../util/globe-default-gl-states";
5
- import { wgs84ToCartesian3d, wgs84ToMercator } from "../../Math/methods";
3
+ import { PointOnGlobeProgramCache, } from "../../programs/point-on-globe/square-pixel-point";
4
+ import { wgs84ToCartesian3d, wgs84ToMercator, unitVectorFromLongLat } from "../../Math/methods";
5
+ import { POLE_BY_PI } from "../../constants";
6
6
  const _0vec3 = /* @__PURE__ */ [0, 0, 0];
7
- /**
8
- * @typedef {number} long
9
- * @typedef {number} lat
10
- * @typedef {number} height
11
- * @typedef {string} ID
12
- * @typedef {string} trackID
13
- * @typedef {[number, number, number, number]} rgba 0-1
14
- * @typedef { long ,lat, height, ID } Point
15
- * @typedef {Array<Point>, rgba, trackID} Track
16
- */
17
7
  class PointTracksPlugin {
18
- constructor(id, { pointSize = 2, hoveredPointSize = 4, selectionPointFilling = 4, opacity = 1.0, objectStoreExtraParameters = ["long", "lat", "height"], initialCapacity = 1000 } = {}) {
8
+ id;
9
+ globe = null;
10
+ gl = null;
11
+ program;
12
+ pointSizes;
13
+ _isFreed;
14
+ _useElevationWithTerrainFit;
15
+ _pointProgram;
16
+ _vao;
17
+ _StorageManagersMap = null;
18
+ _bufferOrchestrator = null;
19
+ _pickerDisplayer;
20
+ _selectedID;
21
+ _terrainFitMode;
22
+ _terrainFit;
23
+ _selectedObj;
24
+ _tracksToPointsMap;
25
+ _opacity;
26
+ _lastWH;
27
+ _focusParams;
28
+ _objectStoreExtraParameters;
29
+ _objectStoreExtraParametersFiltered;
30
+ _drawOptionsUBO;
31
+ _lastSelectedID = -1;
32
+ constructor(id, { terrainFitMode = "SeaLevel", useElevationWithTerrainFit = false, pointSize = 2, hoveredPointSize = 4, selectionPointFilling = 4, opacity = 1.0, objectStoreExtraParameters = ["long", "lat", "height"], initialCapacity = 1000 } = {}) {
19
33
  this.id = id;
20
34
  this._isFreed = false;
35
+ this._useElevationWithTerrainFit = useElevationWithTerrainFit;
21
36
  this._pointProgram = null;
22
37
  this._vao = null;
23
- this._bufferManagersMap = null;
24
- this._bufferOrchestrator = null;
25
38
  this._pickerDisplayer = null;
26
39
  this._selectedID = -1;
40
+ // check _terrainFitMode
41
+ if (terrainFitMode !== "SeaLevel" && terrainFitMode !== "GroundElevation") {
42
+ console.warn(`Invalid terrainFitMode: ${terrainFitMode}. Falling back to "SeaLevel".`);
43
+ throw new Error(`Invalid terrainFitMode: ${terrainFitMode}. Valid options are "SeaLevel" or "GroundElevation".`);
44
+ }
45
+ this._terrainFitMode = terrainFitMode;
46
+ this._terrainFit = terrainFitMode === "GroundElevation";
27
47
  this._selectedObj = null;
28
- this._tracksToPointsMap = new Map(); // one to many
48
+ this._tracksToPointsMap = new Map();
29
49
  this._opacity = opacity ? opacity : 1.0;
30
50
  this.program = null;
31
51
  this._lastWH = { w: 0, h: 0 };
@@ -45,7 +65,14 @@ class PointTracksPlugin {
45
65
  this.globe = globe;
46
66
  this.gl = gl;
47
67
  this._pickerDisplayer = new PickerDisplayer(globe);
48
- this._pointProgram = PointOnGlobeProgramCache.get(globe);
68
+ this._pointProgram = PointOnGlobeProgramCache.get(globe, this._terrainFitMode);
69
+ this._drawOptionsUBO = this._pointProgram.createUBO();
70
+ this._lastSelectedID = -1;
71
+ this._drawOptionsUBO.update(new Map([
72
+ ["pointSize", this.pointSizes.pointSize],
73
+ ["hoveredPointSize", this.pointSizes.hoveredPointSize],
74
+ ["opacity", this._opacity],
75
+ ]));
49
76
  this._focusParams.elementBuffer = gl.createBuffer();
50
77
  this._initBufferManagers();
51
78
  }
@@ -53,26 +80,31 @@ class PointTracksPlugin {
53
80
  const { gl } = this;
54
81
  const initialCapacity = this._bufferOrchestrator.capacity;
55
82
  const bufferType = "DYNAMIC_DRAW";
56
- this._bufferManagersMap = new Map([
83
+ this._StorageManagersMap = new Map([
57
84
  ["pos3D", {
58
85
  bufferManager: new BufferManager(gl, 3, { bufferType, initialCapacity }),
59
- adaptor: (item) => {
60
- wgs84ToCartesian3d(_0vec3, item.long, item.lat, item.height / 1000); // height is in meters
61
- return new Float32Array(_0vec3); // height is in meters
62
- }
86
+ adaptor: this._terrainFit ?
87
+ (item) => {
88
+ unitVectorFromLongLat(_0vec3, item.long, item.lat);
89
+ return new Float32Array(_0vec3);
90
+ } :
91
+ (item) => {
92
+ wgs84ToCartesian3d(_0vec3, item.long, item.lat, item.height / 1000);
93
+ return new Float32Array(_0vec3);
94
+ }
63
95
  }],
64
96
  ["pos2D", {
65
97
  bufferManager: new BufferManager(gl, 2, { bufferType, initialCapacity }),
66
- adaptor: (item) => new Float32Array(wgs84ToMercator(item.long, item.lat))
98
+ adaptor: this._terrainFit ? (item) => {
99
+ const xy = new Float32Array(wgs84ToMercator(item.long, item.lat));
100
+ xy[0] /= POLE_BY_PI;
101
+ xy[1] /= POLE_BY_PI;
102
+ return xy;
103
+ } :
104
+ (item) => new Float32Array(wgs84ToMercator(item.long, item.lat))
67
105
  }],
68
106
  ["rgba", {
69
107
  bufferManager: new BufferManager(gl, 4, { bufferType, initialCapacity, typedArrayConstructor: Float32Array }),
70
- // adaptor: (item) => new Uint8Array([
71
- // item.rgba[0] * 255,
72
- // item.rgba[1] * 255,
73
- // item.rgba[2] * 255,
74
- // item.rgba[3] * 255
75
- // ])
76
108
  adaptor: (item) => new Float32Array(item.rgba)
77
109
  }],
78
110
  ["objectStore", {
@@ -92,53 +124,57 @@ class PointTracksPlugin {
92
124
  }
93
125
  }]
94
126
  ]);
95
- this._vao = this._pointProgram.createVAO(this._bufferManagersMap.get("pos3D").bufferManager.buffer, this._bufferManagersMap.get("pos2D").bufferManager.buffer, this._bufferManagersMap.get("rgba").bufferManager.buffer);
127
+ if (this._terrainFit && this._useElevationWithTerrainFit) {
128
+ this._StorageManagersMap.set("height", {
129
+ bufferManager: new BufferManager(gl, 1, { bufferType, initialCapacity }),
130
+ adaptor: (item) => new Float32Array([item.height / 1000])
131
+ });
132
+ }
133
+ this._vao = this._pointProgram.createVAO(this._StorageManagersMap.get("pos3D").bufferManager.buffer, this._StorageManagersMap.get("pos2D").bufferManager.buffer, this._StorageManagersMap.get("rgba").bufferManager.buffer, (this._terrainFit && this._useElevationWithTerrainFit) ? this._StorageManagersMap.get("height").bufferManager.buffer : undefined);
96
134
  }
97
135
  setPointSize(size) {
98
136
  this.pointSizes.pointSize = size;
137
+ this._drawOptionsUBO.update({ pointSize: size });
99
138
  this.globe?.DrawRender();
100
139
  }
101
140
  setSelectionPointFilling(size) {
102
141
  this.pointSizes.selectionPointFilling = size;
142
+ this._drawOptionsUBO.update({ selectionPointFilling: size });
103
143
  this.globe?.DrawRender();
104
144
  }
105
145
  setHoveredPointSize(size) {
106
146
  this.pointSizes.hoveredPointSize = size;
147
+ this._drawOptionsUBO.update({ hoveredPointSize: size });
107
148
  this.globe?.DrawRender();
108
149
  }
109
150
  setOpacity(opacity) {
110
151
  if (opacity < 0 || opacity > 1)
111
152
  return;
112
153
  this._opacity = opacity;
154
+ this._drawOptionsUBO.update({ opacity });
113
155
  this.globe?.DrawRender();
114
156
  }
115
157
  getCurrentSelection() {
116
158
  return this._selectedObj;
117
159
  }
118
160
  /**
119
- *
120
- * @param {*} x screen x
121
- * @param {*} y screen y
122
- * @param {*} callback callback on selection
161
+ * Select points at screen coordinates
162
+ * @param x screen x coordinate
163
+ * @param y screen y coordinate
164
+ * @param callback callback on selection
123
165
  */
124
166
  screenSelection(x, y, callback) {
125
167
  const { pointSizes, _pickerDisplayer } = this;
126
- const objectStore = this._bufferManagersMap.get("objectStore").bufferManager;
168
+ const objectStore = this._StorageManagersMap.get("objectStore").bufferManager;
127
169
  const wrapper = (selectedIDsSet) => {
128
170
  const selectedIDs = Array.from(selectedIDsSet);
129
171
  if (selectedIDs.length === 0) {
130
- this._selectedObj = null;
131
- this._selectedID = -1;
132
172
  callback([]);
133
173
  return;
134
174
  }
135
175
  const selectedPoints = [];
136
176
  for (let i = 0; i < selectedIDs.length; i++) {
137
177
  const id = selectedIDs[i];
138
- if (i === 0) {
139
- this._selectedID = id;
140
- this._selectedObj = objectStore.get(id);
141
- }
142
178
  const obj = objectStore.get(id);
143
179
  selectedPoints.push(obj);
144
180
  }
@@ -147,8 +183,8 @@ class PointTracksPlugin {
147
183
  _pickerDisplayer.pickXY(x, y, pointSizes.selectionPointFilling, wrapper);
148
184
  }
149
185
  /**
150
- * @param {Array<Track>} tracks
151
- * @returns
186
+ * Insert multiple tracks at once
187
+ * @param tracks Array of tracks to insert
152
188
  */
153
189
  insertBulk(tracks) {
154
190
  this._fillTracksToPointsMap(tracks);
@@ -157,21 +193,22 @@ class PointTracksPlugin {
157
193
  if (currentLoad + flattenedPoints.length >= 2147483647) {
158
194
  throw new Error("Too many points, Point count cannot exceed 2147483647");
159
195
  }
160
- const { _bufferManagersMap, _bufferOrchestrator } = this;
161
- _bufferOrchestrator.insertBulk(flattenedPoints, _bufferManagersMap);
196
+ const { _StorageManagersMap, _bufferOrchestrator } = this;
197
+ _bufferOrchestrator.insertBulk(flattenedPoints, _StorageManagersMap);
162
198
  this._refillFocus();
163
199
  this.globe?.DrawRender();
164
200
  }
165
201
  flush() {
166
202
  const capacity = 100;
167
203
  this._bufferOrchestrator?.flush({ capacity });
168
- this._bufferManagersMap.forEach(({ bufferManager }) => bufferManager.resetWithCapacity(capacity));
204
+ this._StorageManagersMap.forEach(({ bufferManager }) => bufferManager.free());
169
205
  this._turnOffFocus();
170
206
  this._tracksToPointsMap.clear();
171
207
  this.globe?.DrawRender();
172
208
  }
173
209
  /**
174
- * @param {string} trackID
210
+ * Delete a track by ID
211
+ * @param trackID The track ID to delete
175
212
  */
176
213
  deleteTrack(trackID) {
177
214
  const pointList = this._tracksToPointsMap.get(trackID);
@@ -179,26 +216,29 @@ class PointTracksPlugin {
179
216
  console.warn(`Track with ID ${trackID} does not exist.`);
180
217
  return;
181
218
  }
182
- ;
183
219
  const points = Array.from(pointList);
184
- const { _bufferOrchestrator, _bufferManagersMap } = this;
185
- _bufferOrchestrator.deleteBulk(points.map((pointID) => keyMethod(trackID, pointID)), _bufferManagersMap);
220
+ const { _bufferOrchestrator, _StorageManagersMap } = this;
221
+ _bufferOrchestrator.deleteBulk(points.map((pointID) => keyMethod(trackID, pointID)), _StorageManagersMap);
186
222
  this._tracksToPointsMap.delete(trackID);
187
- this._redraw = true;
188
223
  this._refillFocus();
189
224
  this.globe?.DrawRender();
190
225
  }
191
226
  /**
192
- * @param {string} trackID
193
- * @param {Array<string>} pointIDs
227
+ * Delete specific points from a track
228
+ * @param trackID The track ID
229
+ * @param pointIDs Array of point IDs to delete
194
230
  */
195
231
  deletePoints(trackID, pointIDs) {
196
- const { _bufferOrchestrator, _bufferManagersMap } = this;
197
- _bufferOrchestrator.deleteBulk(pointIDs.map((pointID) => keyMethod(trackID, pointID)), _bufferManagersMap);
232
+ const { _bufferOrchestrator, _StorageManagersMap } = this;
233
+ _bufferOrchestrator.deleteBulk(pointIDs.map((pointID) => keyMethod(trackID, pointID)), _StorageManagersMap);
198
234
  this._deletePointsFromTracksMap(trackID, pointIDs);
199
235
  this._refillFocus();
200
236
  this.globe?.DrawRender();
201
237
  }
238
+ /**
239
+ * Focus on specific tracks (or disable focus with null)
240
+ * @param trackIDs Array of track IDs to focus on, or null to disable focus
241
+ */
202
242
  focusTracks(trackIDs = null) {
203
243
  if (!this.globe)
204
244
  return;
@@ -214,6 +254,11 @@ class PointTracksPlugin {
214
254
  }
215
255
  this.globe.DrawRender();
216
256
  }
257
+ /**
258
+ * Update the color of a track
259
+ * @param trackID The track ID
260
+ * @param rgba The new RGBA color values (0-1)
261
+ */
217
262
  updateTrackColor(trackID, rgba) {
218
263
  if (!this._tracksToPointsMap.has(trackID)) {
219
264
  console.warn(`Track with ID ${trackID} does not exist.`);
@@ -221,13 +266,13 @@ class PointTracksPlugin {
221
266
  }
222
267
  const pointList = this._tracksToPointsMap.get(trackID);
223
268
  const points = Array.from(pointList);
224
- const { _bufferOrchestrator, _bufferManagersMap } = this;
269
+ const { _bufferOrchestrator, _StorageManagersMap } = this;
225
270
  _bufferOrchestrator.updateBulk(points.map((pointID) => {
226
271
  return {
227
272
  key: keyMethod(trackID, pointID),
228
273
  rgba: rgba
229
274
  };
230
- }), _bufferManagersMap, ["rgba"]);
275
+ }), _StorageManagersMap, ["rgba"]);
231
276
  this._refillFocus();
232
277
  this.globe?.DrawRender();
233
278
  }
@@ -237,9 +282,9 @@ class PointTracksPlugin {
237
282
  return;
238
283
  this._isFreed = true;
239
284
  this._pickerDisplayer.free();
240
- PointOnGlobeProgramCache.release(this.globe);
285
+ PointOnGlobeProgramCache.release(this.globe, this._terrainFitMode);
241
286
  this.gl.deleteBuffer(this._focusParams.elementBuffer);
242
- this._bufferManagersMap.forEach(({ bufferManager, adaptor }) => bufferManager.free());
287
+ this._StorageManagersMap.forEach(({ bufferManager }) => bufferManager.free());
243
288
  }
244
289
  draw3D() {
245
290
  const { gl, _pointProgram, _pickerDisplayer, _bufferOrchestrator, _vao } = this;
@@ -253,21 +298,10 @@ class PointTracksPlugin {
253
298
  _pickerDisplayer.bindFBO();
254
299
  _pickerDisplayer.clearTextures();
255
300
  if (this._focusParams.on) {
256
- _pointProgram.draw(_vao, this._focusParams.length, {
257
- hoveredID: this._selectedID,
258
- opacity: this._opacity,
259
- pointSize: this.pointSizes.pointSize,
260
- hoveredPointSize: this.pointSizes.hoveredPointSize,
261
- elementBuffer: this._focusParams.elementBuffer
262
- });
301
+ _pointProgram.draw(_vao, this._focusParams.length, this._drawOptionsUBO, this._focusParams.elementBuffer);
263
302
  }
264
303
  else {
265
- _pointProgram.draw(_vao, _bufferOrchestrator.length, {
266
- hoveredID: this._selectedID,
267
- opacity: this._opacity,
268
- pointSize: this.pointSizes.pointSize,
269
- hoveredPointSize: this.pointSizes.hoveredPointSize
270
- });
304
+ _pointProgram.draw(_vao, _bufferOrchestrator.length, this._drawOptionsUBO, null);
271
305
  }
272
306
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
273
307
  _pickerDisplayer.drawColorTexture();
@@ -326,13 +360,38 @@ class PointTracksPlugin {
326
360
  this._tracksToPointsMap.delete(trackID);
327
361
  }
328
362
  }
363
+ _updateHoverState(x, y) {
364
+ const { pointSizes, _pickerDisplayer } = this;
365
+ const objectStore = this._StorageManagersMap.get("objectStore").bufferManager;
366
+ const wrapper = (selectedIDsSet) => {
367
+ const selectedIDs = Array.from(selectedIDsSet);
368
+ if (selectedIDs.length === 0) {
369
+ this._selectedObj = null;
370
+ this._selectedID = -1;
371
+ if (this._lastSelectedID !== -1) {
372
+ this._lastSelectedID = -1;
373
+ this._drawOptionsUBO.updateSingle("hovered_vertexID", -1);
374
+ this.globe.DrawRender();
375
+ }
376
+ return;
377
+ }
378
+ const id = selectedIDs[0];
379
+ this._selectedID = id;
380
+ this._selectedObj = objectStore.get(id);
381
+ if (this._lastSelectedID !== this._selectedID) {
382
+ this._lastSelectedID = this._selectedID;
383
+ this._drawOptionsUBO.updateSingle("hovered_vertexID", this._selectedID);
384
+ this.globe.DrawRender();
385
+ }
386
+ };
387
+ _pickerDisplayer.pickXY(x, y, pointSizes.selectionPointFilling, wrapper);
388
+ }
329
389
  _selfSelect() {
330
390
  const { globe } = this;
331
391
  const pos = globe.api_GetMousePos();
332
392
  const x = pos.canvasX;
333
393
  const y = globe.api_ScrH() - pos.canvasY;
334
- this.screenSelection(x, y, (selectedPoints) => {
335
- });
394
+ this._updateHoverState(x, y);
336
395
  }
337
396
  _turnOffFocus() {
338
397
  const { gl } = this;
@@ -0,0 +1,437 @@
1
+ import { BufferOrchestrator, BufferManager, ObjectStore } from "../../util/account/index";
2
+ import { PickerDisplayer } from "../../util/picking/picker-displayer";
3
+ import { PointOnGlobeProgramCache, } from "../../programs/point-on-globe/square-pixel-point";
4
+ import { defaultblendfunction } from "../../util/globe-default-gl-states";
5
+ import { wgs84ToCartesian3d, wgs84ToMercator, unitVectorFromLongLat } from "../../Math/methods";
6
+ import { POLE_BY_PI } from "../../constants";
7
+ const _0vec3 = /* @__PURE__ */ [0, 0, 0];
8
+ /**
9
+ * @typedef {number} long
10
+ * @typedef {number} lat
11
+ * @typedef {number} height
12
+ * @typedef {string} ID
13
+ * @typedef {string} trackID
14
+ * @typedef {[number, number, number, number]} rgba 0-1
15
+ * @typedef { long ,lat, height, ID } Point
16
+ * @typedef {Array<Point>, rgba, trackID} Track
17
+ */
18
+ class PointTracksPlugin {
19
+ constructor(id, { terrainFitMode = "SeaLevel", // "SeaLevel" or "GroundElevation"
20
+ useElevationWithTerrainFit = false, // opens a buffer
21
+ pointSize = 2, hoveredPointSize = 4, selectionPointFilling = 4, opacity = 1.0, objectStoreExtraParameters = ["long", "lat", "height"], initialCapacity = 1000 } = {}) {
22
+ this.id = id;
23
+ this._isFreed = false;
24
+ this._useElevationWithTerrainFit = useElevationWithTerrainFit;
25
+ this._pointProgram = null;
26
+ this._vao = null;
27
+ this._bufferManagersMap = null;
28
+ this._bufferOrchestrator = null;
29
+ this._pickerDisplayer = null;
30
+ this._selectedID = -1;
31
+ // check _terrainFitMode
32
+ if (terrainFitMode !== "SeaLevel" && terrainFitMode !== "GroundElevation") {
33
+ console.warn(`Invalid terrainFitMode: ${terrainFitMode}. Falling back to "SeaLevel".`);
34
+ throw new Error(`Invalid terrainFitMode: ${terrainFitMode}. Valid options are "SeaLevel" or "GroundElevation".`);
35
+ }
36
+ this._terrainFitMode = terrainFitMode;
37
+ this._terrainFit = terrainFitMode === "GroundElevation";
38
+ this._selectedObj = null;
39
+ this._tracksToPointsMap = new Map(); // one to many
40
+ this._opacity = opacity ? opacity : 1.0;
41
+ this.program = null;
42
+ this._lastWH = { w: 0, h: 0 };
43
+ this._focusParams = { on: false, length: 0, elementBuffer: null, trackIDs: [] };
44
+ this.pointSizes = {
45
+ pointSize: pointSize ? pointSize : 2,
46
+ selectionPointFilling: selectionPointFilling ? selectionPointFilling : 4,
47
+ hoveredPointSize: hoveredPointSize ? hoveredPointSize : 4
48
+ };
49
+ this._objectStoreExtraParameters = objectStoreExtraParameters ? objectStoreExtraParameters : [];
50
+ this._objectStoreExtraParametersFiltered = objectStoreExtraParameters.filter(param => {
51
+ return !["long", "lat", "height", "ID", "trackID"].includes(param);
52
+ });
53
+ this._bufferOrchestrator = new BufferOrchestrator({ capacity: initialCapacity ? initialCapacity : 1000 });
54
+ }
55
+ init(globe, gl) {
56
+ this.globe = globe;
57
+ this.gl = gl;
58
+ this._pickerDisplayer = new PickerDisplayer(globe);
59
+ this._pointProgram = PointOnGlobeProgramCache.get(globe, this._terrainFitMode);
60
+ this._drawOptionsUBO = this._pointProgram.createUBO();
61
+ this._lastSelectedID = -1;
62
+ this._drawOptionsUBO.update(new Map([
63
+ ["pointSize", this.pointSizes.pointSize],
64
+ ["hoveredPointSize", this.pointSizes.hoveredPointSize],
65
+ ["opacity", this._opacity],
66
+ ]));
67
+ this._focusParams.elementBuffer = gl.createBuffer();
68
+ this._initBufferManagers();
69
+ }
70
+ _initBufferManagers() {
71
+ const { gl } = this;
72
+ const initialCapacity = this._bufferOrchestrator.capacity;
73
+ const bufferType = "DYNAMIC_DRAW";
74
+ this._bufferManagersMap = new Map([
75
+ ["pos3D", {
76
+ bufferManager: new BufferManager(gl, 3, { bufferType, initialCapacity }),
77
+ adaptor: this._terrainFit ?
78
+ (item) => {
79
+ unitVectorFromLongLat(_0vec3, item.long, item.lat); // height is in meters
80
+ return new Float32Array(_0vec3); // height is in meters
81
+ } :
82
+ (item) => {
83
+ wgs84ToCartesian3d(_0vec3, item.long, item.lat, item.height / 1000); // height is in meters
84
+ return new Float32Array(_0vec3); // height is in meters
85
+ }
86
+ }],
87
+ ["pos2D", {
88
+ bufferManager: new BufferManager(gl, 2, { bufferType, initialCapacity }),
89
+ adaptor: this._terrainFit ? (item) => {
90
+ const xy = new Float32Array(wgs84ToMercator(item.long, item.lat));
91
+ xy[0] /= POLE_BY_PI;
92
+ xy[1] /= POLE_BY_PI;
93
+ return xy;
94
+ } :
95
+ (item) => new Float32Array(wgs84ToMercator(item.long, item.lat))
96
+ }],
97
+ ["rgba", {
98
+ bufferManager: new BufferManager(gl, 4, { bufferType, initialCapacity, typedArrayConstructor: Float32Array }),
99
+ adaptor: (item) => new Float32Array(item.rgba)
100
+ }],
101
+ ["objectStore", {
102
+ bufferManager: new ObjectStore({ initialCapacity }),
103
+ adaptor: (item) => {
104
+ const result = {
105
+ trackID: item.trackID,
106
+ pointID: item.pointID,
107
+ };
108
+ // Add extra parameters specified in _objectStoreExtraParameters
109
+ this._objectStoreExtraParameters.forEach(param => {
110
+ if (item.hasOwnProperty(param)) {
111
+ result[param] = item[param];
112
+ }
113
+ });
114
+ return result;
115
+ }
116
+ }]
117
+ ]);
118
+ if (this._terrainFit && this._useElevationWithTerrainFit) {
119
+ this._bufferManagersMap.set("height", {
120
+ bufferManager: new BufferManager(gl, 1, { bufferType, initialCapacity }),
121
+ adaptor: (item) => new Float32Array([item.height / 1000])
122
+ });
123
+ }
124
+ this._vao = this._pointProgram.createVAO(this._bufferManagersMap.get("pos3D").bufferManager.buffer, this._bufferManagersMap.get("pos2D").bufferManager.buffer, this._bufferManagersMap.get("rgba").bufferManager.buffer, (this._terrainFit && this._useElevationWithTerrainFit) ? this._bufferManagersMap.get("height").bufferManager.buffer : undefined);
125
+ }
126
+ setPointSize(size) {
127
+ this.pointSizes.pointSize = size;
128
+ this._drawOptionsUBO.update({ pointSize: size });
129
+ this.globe?.DrawRender();
130
+ }
131
+ setSelectionPointFilling(size) {
132
+ this.pointSizes.selectionPointFilling = size;
133
+ this._drawOptionsUBO.update({ selectionPointFilling: size });
134
+ this.globe?.DrawRender();
135
+ }
136
+ setHoveredPointSize(size) {
137
+ this.pointSizes.hoveredPointSize = size;
138
+ this._drawOptionsUBO.update({ hoveredPointSize: size });
139
+ this.globe?.DrawRender();
140
+ }
141
+ setOpacity(opacity) {
142
+ if (opacity < 0 || opacity > 1)
143
+ return;
144
+ this._opacity = opacity;
145
+ this._drawOptionsUBO.update({ opacity });
146
+ this.globe?.DrawRender();
147
+ }
148
+ getCurrentSelection() {
149
+ return this._selectedObj;
150
+ }
151
+ /**
152
+ *
153
+ * @param {*} x screen x
154
+ * @param {*} y screen y
155
+ * @param {*} callback callback on selection
156
+ */
157
+ screenSelection(x, y, callback) {
158
+ const { pointSizes, _pickerDisplayer } = this;
159
+ const objectStore = this._bufferManagersMap.get("objectStore").bufferManager;
160
+ const wrapper = (selectedIDsSet) => {
161
+ const selectedIDs = Array.from(selectedIDsSet);
162
+ if (selectedIDs.length === 0) {
163
+ this._selectedObj = null;
164
+ this._selectedID = -1;
165
+ callback([]);
166
+ return;
167
+ }
168
+ const selectedPoints = [];
169
+ for (let i = 0; i < selectedIDs.length; i++) {
170
+ const id = selectedIDs[i];
171
+ if (i === 0) {
172
+ this._selectedID = id;
173
+ this._selectedObj = objectStore.get(id);
174
+ }
175
+ const obj = objectStore.get(id);
176
+ selectedPoints.push(obj);
177
+ }
178
+ callback(selectedPoints);
179
+ };
180
+ _pickerDisplayer.pickXY(x, y, pointSizes.selectionPointFilling, wrapper);
181
+ }
182
+ /**
183
+ * @param {Array<Track>} tracks
184
+ * @returns
185
+ */
186
+ insertBulk(tracks) {
187
+ this._fillTracksToPointsMap(tracks);
188
+ const flattenedPoints = tracks.map(track => trackToFlatPoints(track, this._objectStoreExtraParametersFiltered)).flat();
189
+ const currentLoad = this._bufferOrchestrator.length;
190
+ if (currentLoad + flattenedPoints.length >= 2147483647) {
191
+ throw new Error("Too many points, Point count cannot exceed 2147483647");
192
+ }
193
+ const { _bufferManagersMap, _bufferOrchestrator } = this;
194
+ _bufferOrchestrator.insertBulk(flattenedPoints, _bufferManagersMap);
195
+ this._refillFocus();
196
+ this.globe?.DrawRender();
197
+ }
198
+ flush() {
199
+ const capacity = 100;
200
+ this._bufferOrchestrator?.flush({ capacity });
201
+ this._bufferManagersMap.forEach(({ bufferManager }) => bufferManager.resetWithCapacity(capacity));
202
+ this._turnOffFocus();
203
+ this._tracksToPointsMap.clear();
204
+ this.globe?.DrawRender();
205
+ }
206
+ /**
207
+ * @param {string} trackID
208
+ */
209
+ deleteTrack(trackID) {
210
+ const pointList = this._tracksToPointsMap.get(trackID);
211
+ if (!pointList) {
212
+ console.warn(`Track with ID ${trackID} does not exist.`);
213
+ return;
214
+ }
215
+ ;
216
+ const points = Array.from(pointList);
217
+ const { _bufferOrchestrator, _bufferManagersMap } = this;
218
+ _bufferOrchestrator.deleteBulk(points.map((pointID) => keyMethod(trackID, pointID)), _bufferManagersMap);
219
+ this._tracksToPointsMap.delete(trackID);
220
+ this._redraw = true;
221
+ this._refillFocus();
222
+ this.globe?.DrawRender();
223
+ }
224
+ /**
225
+ * @param {string} trackID
226
+ * @param {Array<string>} pointIDs
227
+ */
228
+ deletePoints(trackID, pointIDs) {
229
+ const { _bufferOrchestrator, _bufferManagersMap } = this;
230
+ _bufferOrchestrator.deleteBulk(pointIDs.map((pointID) => keyMethod(trackID, pointID)), _bufferManagersMap);
231
+ this._deletePointsFromTracksMap(trackID, pointIDs);
232
+ this._refillFocus();
233
+ this.globe?.DrawRender();
234
+ }
235
+ focusTracks(trackIDs = null) {
236
+ if (!this.globe)
237
+ return;
238
+ if (trackIDs === null) {
239
+ this._focusParams.on = false;
240
+ this.globe?.DrawRender();
241
+ return;
242
+ }
243
+ this._focusParams.on = true;
244
+ this._fillElementBuffer(trackIDs);
245
+ if (!this._focusParams.on) {
246
+ this._turnOffFocus();
247
+ }
248
+ this.globe.DrawRender();
249
+ }
250
+ updateTrackColor(trackID, rgba) {
251
+ if (!this._tracksToPointsMap.has(trackID)) {
252
+ console.warn(`Track with ID ${trackID} does not exist.`);
253
+ return;
254
+ }
255
+ const pointList = this._tracksToPointsMap.get(trackID);
256
+ const points = Array.from(pointList);
257
+ const { _bufferOrchestrator, _bufferManagersMap } = this;
258
+ _bufferOrchestrator.updateBulk(points.map((pointID) => {
259
+ return {
260
+ key: keyMethod(trackID, pointID),
261
+ rgba: rgba
262
+ };
263
+ }), _bufferManagersMap, ["rgba"]);
264
+ this._refillFocus();
265
+ this.globe?.DrawRender();
266
+ }
267
+ // GLOBE API METHODS
268
+ free() {
269
+ if (this._isFreed)
270
+ return;
271
+ this._isFreed = true;
272
+ this._pickerDisplayer.free();
273
+ PointOnGlobeProgramCache.release(this.globe, this._terrainFitMode);
274
+ this.gl.deleteBuffer(this._focusParams.elementBuffer);
275
+ this._bufferManagersMap.forEach(({ bufferManager, adaptor }) => bufferManager.free());
276
+ }
277
+ draw3D() {
278
+ const { gl, _pointProgram, _pickerDisplayer, _bufferOrchestrator, _vao } = this;
279
+ if (!gl) {
280
+ throw new Error("GL is not loaded, PointTracks Plugin");
281
+ }
282
+ if (this._isFreed) {
283
+ throw new Error("Plugin is unregistered, PointTracks Plugin");
284
+ }
285
+ this.resize();
286
+ _pickerDisplayer.bindFBO();
287
+ _pickerDisplayer.clearTextures();
288
+ if (this._focusParams.on) {
289
+ _pointProgram.draw(_vao, this._focusParams.length, this._drawOptionsUBO, this._focusParams.elementBuffer);
290
+ }
291
+ else {
292
+ _pointProgram.draw(_vao, _bufferOrchestrator.length, this._drawOptionsUBO, null);
293
+ }
294
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
295
+ _pickerDisplayer.drawColorTexture();
296
+ this._selfSelect();
297
+ }
298
+ resize() {
299
+ const w = this.globe.api_ScrW();
300
+ const h = this.globe.api_ScrH();
301
+ if (w === this._lastWH.w && h === this._lastWH.h)
302
+ return;
303
+ this._lastWH.w = w;
304
+ this._lastWH.h = h;
305
+ this._pickerDisplayer.resize();
306
+ this.globe?.DrawRender();
307
+ }
308
+ // IMPLICIT METHODS
309
+ _fillTracksToPointsMap(tracks) {
310
+ for (const track of tracks) {
311
+ const trackID = track.trackID;
312
+ const points = track.points;
313
+ if (!this._tracksToPointsMap.has(trackID)) {
314
+ this._tracksToPointsMap.set(trackID, new Set());
315
+ }
316
+ const pointSet = this._tracksToPointsMap.get(trackID);
317
+ for (let p = 0; p < points.length; p++) {
318
+ const pointID = points[p].ID;
319
+ if (!pointSet.has(pointID)) {
320
+ pointSet.add(pointID);
321
+ }
322
+ else {
323
+ console.warn(`Point with ID ${pointID} already exists in track ${trackID}. Skipping duplicate.`);
324
+ }
325
+ }
326
+ }
327
+ }
328
+ _refillFocus() {
329
+ if (this._focusParams.on) {
330
+ this.focusTracks(this._focusParams.trackIDs);
331
+ }
332
+ }
333
+ _deletePointsFromTracksMap(trackID, pointIDs) {
334
+ const trackIDs = this._tracksToPointsMap.get(trackID);
335
+ if (trackIDs === undefined) {
336
+ console.warn(`Track with ID ${trackID} not found.`);
337
+ return;
338
+ }
339
+ for (const pointID of pointIDs) {
340
+ if (!trackIDs.has(pointID)) {
341
+ console.warn(`Point with ID ${pointID} not found in track ${trackID}.`);
342
+ }
343
+ else {
344
+ trackIDs.delete(pointID);
345
+ }
346
+ }
347
+ if (trackIDs.size === 0) {
348
+ this._tracksToPointsMap.delete(trackID);
349
+ }
350
+ }
351
+ _selfSelect() {
352
+ const { globe } = this;
353
+ const pos = globe.api_GetMousePos();
354
+ const x = pos.canvasX;
355
+ const y = globe.api_ScrH() - pos.canvasY;
356
+ this.screenSelection(x, y, (selectedPoints) => {
357
+ if (selectedPoints.length > 0) {
358
+ if (this._lastSelectedID !== this._selectedID) {
359
+ this._lastSelectedID = this._selectedID;
360
+ this._drawOptionsUBO.updateSingle("hovered_vertexID", this._selectedID);
361
+ this.globe.DrawRender();
362
+ }
363
+ }
364
+ else {
365
+ if (this._lastSelectedID !== -1) {
366
+ this._lastSelectedID = -1;
367
+ this._drawOptionsUBO.updateSingle("hovered_vertexID", -1);
368
+ this.globe.DrawRender();
369
+ }
370
+ }
371
+ });
372
+ }
373
+ _turnOffFocus() {
374
+ const { gl } = this;
375
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._focusParams.elementBuffer);
376
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint32Array([]), gl.STATIC_DRAW);
377
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
378
+ this._focusParams.length = 0;
379
+ this._focusParams.on = false;
380
+ }
381
+ _fillElementBuffer(trackIDs) {
382
+ let length = 0;
383
+ const indexes = [];
384
+ const foundTracks = [];
385
+ for (const trackID of trackIDs) {
386
+ const pointList = this._tracksToPointsMap.get(trackID);
387
+ if (!pointList)
388
+ continue;
389
+ foundTracks.push(trackID);
390
+ for (const pointID of pointList) {
391
+ const key = keyMethod(trackID, pointID);
392
+ const index = this._bufferOrchestrator.offsetMap.get(key);
393
+ if (index === undefined) {
394
+ throw new Error(`Point with key ${key} not found in buffer orchestrator.`);
395
+ }
396
+ indexes.push(index);
397
+ length++;
398
+ }
399
+ }
400
+ this._focusParams.trackIDs = foundTracks;
401
+ this._focusParams.length = length;
402
+ if (length === 0) {
403
+ return false;
404
+ }
405
+ const { gl } = this;
406
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._focusParams.elementBuffer);
407
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint32Array(indexes), gl.STATIC_DRAW);
408
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
409
+ return true;
410
+ }
411
+ }
412
+ const trackToFlatPoints = (track, extraParameters) => {
413
+ const trackID = track.trackID;
414
+ const points = track.points;
415
+ const rgba = new Float32Array(track.rgba);
416
+ const flatPoints = [];
417
+ for (const point of points) {
418
+ flatPoints.push({
419
+ key: keyMethod(trackID, point.ID),
420
+ long: point.long,
421
+ lat: point.lat,
422
+ height: point.height,
423
+ pointID: point.ID,
424
+ rgba,
425
+ trackID
426
+ });
427
+ for (let i = 0; i < extraParameters.length; i++) {
428
+ const param = extraParameters[i];
429
+ if (point.hasOwnProperty(param)) {
430
+ flatPoints[flatPoints.length - 1][param] = point[param];
431
+ }
432
+ }
433
+ }
434
+ return flatPoints;
435
+ };
436
+ const keyMethod = (trackID, pointID) => `${trackID}_${pointID}`;
437
+ export { PointTracksPlugin, keyMethod };
@@ -1,7 +1,8 @@
1
1
  export class ObjectStore {
2
2
  _container;
3
- constructor({ initialCapcity = 10 } = {}) {
4
- this._container = this._createEmptyList(initialCapcity);
3
+ buffer = undefined; // For compatibility with BufferManager interface
4
+ constructor({ initialCapacity = 10 } = {}) {
5
+ this._container = this._createEmptyList(initialCapacity);
5
6
  }
6
7
  resetWithCapacity(capacity) {
7
8
  this._container = this._createEmptyList(capacity);