@pirireis/webglobeplugins 1.3.0 → 1.4.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.
- package/Math/methods.js +7 -0
- package/constants.js +1 -0
- package/package.json +1 -1
- package/programs/point-on-globe/square-pixel-point.js +129 -59
- package/programs/programcache.js +16 -13
- package/programs/totems/attachments/dem-textures-manager.js +1 -0
- package/tracks/point-tracks/adapters.js +1 -0
- package/tracks/point-tracks/plugin.js +122 -68
- package/tracks/point-tracks/pluginold.js +437 -0
- package/util/account/single-attribute-buffer-management/object-store.js +3 -2
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
package/package.json
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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,
|
|
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
|
-
|
|
126
|
-
|
|
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
|
|
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(
|
|
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.
|
|
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
|
|
170
|
-
|
|
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 };
|
package/programs/programcache.js
CHANGED
|
@@ -89,32 +89,35 @@ const glProgramCache = (function () {
|
|
|
89
89
|
})();
|
|
90
90
|
const noRegisterGlobeProgramCache = (function () {
|
|
91
91
|
const cache = new Map();
|
|
92
|
-
|
|
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(
|
|
98
|
-
cache.get(globe).set(
|
|
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(
|
|
106
|
+
cache.get(globe).get(key).count++;
|
|
105
107
|
}
|
|
106
|
-
return cache.get(globe).get(
|
|
108
|
+
return cache.get(globe).get(key).program;
|
|
107
109
|
}
|
|
108
110
|
;
|
|
109
|
-
function releaseProgram(globe, ProgramClass) {
|
|
110
|
-
|
|
111
|
-
|
|
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(
|
|
115
|
-
if (cache.get(globe).get(
|
|
116
|
-
cache.get(globe).get(
|
|
117
|
-
cache.get(globe).delete(
|
|
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 {
|
|
5
|
-
import {
|
|
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
|
-
|
|
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();
|
|
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.
|
|
83
|
+
this._StorageManagersMap = new Map([
|
|
57
84
|
["pos3D", {
|
|
58
85
|
bufferManager: new BufferManager(gl, 3, { bufferType, initialCapacity }),
|
|
59
|
-
adaptor:
|
|
60
|
-
|
|
61
|
-
|
|
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) =>
|
|
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,38 +124,48 @@ class PointTracksPlugin {
|
|
|
92
124
|
}
|
|
93
125
|
}]
|
|
94
126
|
]);
|
|
95
|
-
|
|
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
|
|
121
|
-
* @param
|
|
122
|
-
* @param
|
|
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.
|
|
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) {
|
|
@@ -147,8 +189,8 @@ class PointTracksPlugin {
|
|
|
147
189
|
_pickerDisplayer.pickXY(x, y, pointSizes.selectionPointFilling, wrapper);
|
|
148
190
|
}
|
|
149
191
|
/**
|
|
150
|
-
*
|
|
151
|
-
* @
|
|
192
|
+
* Insert multiple tracks at once
|
|
193
|
+
* @param tracks Array of tracks to insert
|
|
152
194
|
*/
|
|
153
195
|
insertBulk(tracks) {
|
|
154
196
|
this._fillTracksToPointsMap(tracks);
|
|
@@ -157,21 +199,22 @@ class PointTracksPlugin {
|
|
|
157
199
|
if (currentLoad + flattenedPoints.length >= 2147483647) {
|
|
158
200
|
throw new Error("Too many points, Point count cannot exceed 2147483647");
|
|
159
201
|
}
|
|
160
|
-
const {
|
|
161
|
-
_bufferOrchestrator.insertBulk(flattenedPoints,
|
|
202
|
+
const { _StorageManagersMap, _bufferOrchestrator } = this;
|
|
203
|
+
_bufferOrchestrator.insertBulk(flattenedPoints, _StorageManagersMap);
|
|
162
204
|
this._refillFocus();
|
|
163
205
|
this.globe?.DrawRender();
|
|
164
206
|
}
|
|
165
207
|
flush() {
|
|
166
208
|
const capacity = 100;
|
|
167
209
|
this._bufferOrchestrator?.flush({ capacity });
|
|
168
|
-
this.
|
|
210
|
+
this._StorageManagersMap.forEach(({ bufferManager }) => bufferManager.free());
|
|
169
211
|
this._turnOffFocus();
|
|
170
212
|
this._tracksToPointsMap.clear();
|
|
171
213
|
this.globe?.DrawRender();
|
|
172
214
|
}
|
|
173
215
|
/**
|
|
174
|
-
*
|
|
216
|
+
* Delete a track by ID
|
|
217
|
+
* @param trackID The track ID to delete
|
|
175
218
|
*/
|
|
176
219
|
deleteTrack(trackID) {
|
|
177
220
|
const pointList = this._tracksToPointsMap.get(trackID);
|
|
@@ -179,26 +222,29 @@ class PointTracksPlugin {
|
|
|
179
222
|
console.warn(`Track with ID ${trackID} does not exist.`);
|
|
180
223
|
return;
|
|
181
224
|
}
|
|
182
|
-
;
|
|
183
225
|
const points = Array.from(pointList);
|
|
184
|
-
const { _bufferOrchestrator,
|
|
185
|
-
_bufferOrchestrator.deleteBulk(points.map((pointID) => keyMethod(trackID, pointID)),
|
|
226
|
+
const { _bufferOrchestrator, _StorageManagersMap } = this;
|
|
227
|
+
_bufferOrchestrator.deleteBulk(points.map((pointID) => keyMethod(trackID, pointID)), _StorageManagersMap);
|
|
186
228
|
this._tracksToPointsMap.delete(trackID);
|
|
187
|
-
this._redraw = true;
|
|
188
229
|
this._refillFocus();
|
|
189
230
|
this.globe?.DrawRender();
|
|
190
231
|
}
|
|
191
232
|
/**
|
|
192
|
-
*
|
|
193
|
-
* @param
|
|
233
|
+
* Delete specific points from a track
|
|
234
|
+
* @param trackID The track ID
|
|
235
|
+
* @param pointIDs Array of point IDs to delete
|
|
194
236
|
*/
|
|
195
237
|
deletePoints(trackID, pointIDs) {
|
|
196
|
-
const { _bufferOrchestrator,
|
|
197
|
-
_bufferOrchestrator.deleteBulk(pointIDs.map((pointID) => keyMethod(trackID, pointID)),
|
|
238
|
+
const { _bufferOrchestrator, _StorageManagersMap } = this;
|
|
239
|
+
_bufferOrchestrator.deleteBulk(pointIDs.map((pointID) => keyMethod(trackID, pointID)), _StorageManagersMap);
|
|
198
240
|
this._deletePointsFromTracksMap(trackID, pointIDs);
|
|
199
241
|
this._refillFocus();
|
|
200
242
|
this.globe?.DrawRender();
|
|
201
243
|
}
|
|
244
|
+
/**
|
|
245
|
+
* Focus on specific tracks (or disable focus with null)
|
|
246
|
+
* @param trackIDs Array of track IDs to focus on, or null to disable focus
|
|
247
|
+
*/
|
|
202
248
|
focusTracks(trackIDs = null) {
|
|
203
249
|
if (!this.globe)
|
|
204
250
|
return;
|
|
@@ -214,6 +260,11 @@ class PointTracksPlugin {
|
|
|
214
260
|
}
|
|
215
261
|
this.globe.DrawRender();
|
|
216
262
|
}
|
|
263
|
+
/**
|
|
264
|
+
* Update the color of a track
|
|
265
|
+
* @param trackID The track ID
|
|
266
|
+
* @param rgba The new RGBA color values (0-1)
|
|
267
|
+
*/
|
|
217
268
|
updateTrackColor(trackID, rgba) {
|
|
218
269
|
if (!this._tracksToPointsMap.has(trackID)) {
|
|
219
270
|
console.warn(`Track with ID ${trackID} does not exist.`);
|
|
@@ -221,13 +272,13 @@ class PointTracksPlugin {
|
|
|
221
272
|
}
|
|
222
273
|
const pointList = this._tracksToPointsMap.get(trackID);
|
|
223
274
|
const points = Array.from(pointList);
|
|
224
|
-
const { _bufferOrchestrator,
|
|
275
|
+
const { _bufferOrchestrator, _StorageManagersMap } = this;
|
|
225
276
|
_bufferOrchestrator.updateBulk(points.map((pointID) => {
|
|
226
277
|
return {
|
|
227
278
|
key: keyMethod(trackID, pointID),
|
|
228
279
|
rgba: rgba
|
|
229
280
|
};
|
|
230
|
-
}),
|
|
281
|
+
}), _StorageManagersMap, ["rgba"]);
|
|
231
282
|
this._refillFocus();
|
|
232
283
|
this.globe?.DrawRender();
|
|
233
284
|
}
|
|
@@ -237,9 +288,9 @@ class PointTracksPlugin {
|
|
|
237
288
|
return;
|
|
238
289
|
this._isFreed = true;
|
|
239
290
|
this._pickerDisplayer.free();
|
|
240
|
-
PointOnGlobeProgramCache.release(this.globe);
|
|
291
|
+
PointOnGlobeProgramCache.release(this.globe, this._terrainFitMode);
|
|
241
292
|
this.gl.deleteBuffer(this._focusParams.elementBuffer);
|
|
242
|
-
this.
|
|
293
|
+
this._StorageManagersMap.forEach(({ bufferManager }) => bufferManager.free());
|
|
243
294
|
}
|
|
244
295
|
draw3D() {
|
|
245
296
|
const { gl, _pointProgram, _pickerDisplayer, _bufferOrchestrator, _vao } = this;
|
|
@@ -253,21 +304,10 @@ class PointTracksPlugin {
|
|
|
253
304
|
_pickerDisplayer.bindFBO();
|
|
254
305
|
_pickerDisplayer.clearTextures();
|
|
255
306
|
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
|
-
});
|
|
307
|
+
_pointProgram.draw(_vao, this._focusParams.length, this._drawOptionsUBO, this._focusParams.elementBuffer);
|
|
263
308
|
}
|
|
264
309
|
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
|
-
});
|
|
310
|
+
_pointProgram.draw(_vao, _bufferOrchestrator.length, this._drawOptionsUBO, null);
|
|
271
311
|
}
|
|
272
312
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
273
313
|
_pickerDisplayer.drawColorTexture();
|
|
@@ -332,6 +372,20 @@ class PointTracksPlugin {
|
|
|
332
372
|
const x = pos.canvasX;
|
|
333
373
|
const y = globe.api_ScrH() - pos.canvasY;
|
|
334
374
|
this.screenSelection(x, y, (selectedPoints) => {
|
|
375
|
+
if (selectedPoints.length > 0) {
|
|
376
|
+
if (this._lastSelectedID !== this._selectedID) {
|
|
377
|
+
this._lastSelectedID = this._selectedID;
|
|
378
|
+
this._drawOptionsUBO.updateSingle("hovered_vertexID", this._selectedID);
|
|
379
|
+
this.globe.DrawRender();
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
if (this._lastSelectedID !== -1) {
|
|
384
|
+
this._lastSelectedID = -1;
|
|
385
|
+
this._drawOptionsUBO.updateSingle("hovered_vertexID", -1);
|
|
386
|
+
this.globe.DrawRender();
|
|
387
|
+
}
|
|
388
|
+
}
|
|
335
389
|
});
|
|
336
390
|
}
|
|
337
391
|
_turnOffFocus() {
|
|
@@ -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
|
-
|
|
4
|
-
|
|
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);
|