@pirireis/webglobeplugins 1.1.12 → 1.1.14

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pirireis/webglobeplugins",
3
- "version": "1.1.12",
3
+ "version": "1.1.14",
4
4
  "main": "index.js",
5
5
  "author": "Toprak Nihat Deniz Ozturk",
6
6
  "license": "MIT",
@@ -14,15 +14,16 @@ const uniformBindingPoints = {
14
14
  dem: 2,
15
15
  selection: 3,
16
16
  };
17
- export const IndexAttributeEscapeValue = -1;
17
+ // last value of uint16 is 65535
18
+ export const IndexAttributeEscapeValue = 65535;
18
19
  const styleBlockManager = new UniformBlockManager('Style', [
19
20
  { name: "defaultColor", type: "vec4", value: new Float32Array([0.0, 0.0, 0.0, 0.0]) },
20
21
  { name: "pickedColor", type: "vec4", value: new Float32Array([0, 0, 0, 0]) },
21
22
  { name: "u_pointSize", type: "float", value: new Float32Array([6.0]) },
22
23
  { name: "opacity", type: "float", value: new Float32Array([1.0]) },
23
- { name: "private_isPickedOn", type: "float", value: new Float32Array([0]) },
24
- { name: "private_pickedIndex", type: "float", value: new Float32Array([0]) },
25
- { name: "useDefaultColor", type: "float", value: new Float32Array([0]) },
24
+ { name: "private_isPickedOn", type: "bool", value: new Float32Array([0]) },
25
+ { name: "private_pickedIndex", type: "uint", value: new Uint32Array([0]) },
26
+ { name: "useDefaultColor", type: "bool", value: new Float32Array([0]) },
26
27
  ], uniformBindingPoints.style);
27
28
  const vertexShaderSource = `#version 300 es
28
29
  #pragma vscode_glsllint_stage : vert
@@ -39,13 +40,13 @@ ${DEM_TEXTURE_BLOCK_STRING}
39
40
 
40
41
  in vec3 a_position;
41
42
  in vec2 a_xy;
42
- in float a_index;
43
+ in uint a_index;
43
44
  in uvec4 a_color;
44
45
 
45
46
  ${styleBlockManager.glslCode()}
46
47
 
47
48
  out vec4 v_color;
48
- flat out int v_index;
49
+ flat out uint v_index;
49
50
 
50
51
  ${relativeBBoxPositionRadian}
51
52
 
@@ -62,7 +63,7 @@ void main() {
62
63
  );
63
64
 
64
65
  // a_color.a == 255u is reserved as "no per-vertex color" sentinel.
65
- v_color = (a_color.a == 255u || useDefaultColor == 1.0) ? defaultColor : decodedColor;
66
+ v_color = (a_color.a == 255u || useDefaultColor == true) ? defaultColor : decodedColor;
66
67
 
67
68
  if (is3D == true) {
68
69
  for (int i = 0; i < 6; i++) {
@@ -87,11 +88,11 @@ void main() {
87
88
  gl_Position = mercatorXYToGLPosition(mercatorXY);
88
89
  }
89
90
 
90
- if (private_isPickedOn == 1.0){
91
+ if (private_isPickedOn == true){
91
92
  if (a_index == private_pickedIndex) {
92
93
  v_color = pickedColor;
93
94
  }
94
- v_index = int(a_index + 0.5); // +0.5 to avoid rounding issues
95
+ v_index = a_index;
95
96
  }
96
97
 
97
98
 
@@ -109,9 +110,9 @@ precision highp int;
109
110
 
110
111
 
111
112
  in vec4 v_color; // color from vertex shader
112
- flat in int v_index; // index from vertex shader
113
+ flat in uint v_index; // index from vertex shader
113
114
  layout(location = 0) out vec4 outColor;
114
- layout(location = 1) out int outIndex;
115
+ layout(location = 1) out uint outIndex;
115
116
 
116
117
  void main() {
117
118
  outColor = v_color;
@@ -177,13 +178,11 @@ export class TextureDemTriangles {
177
178
  attributeLoader(this.gl, pos3dBufferInfo, this.locations.attributes.a_position, 3);
178
179
  attributeLoader(this.gl, longLatBufferInfo, this.locations.attributes.a_xy, 2);
179
180
  attributeLoader(this.gl, indexBufferInfo, this.locations.attributes.a_index, 1, {
181
+ dataType: "uint16",
180
182
  escapeValues: [IndexAttributeEscapeValue],
181
- normalized: false,
182
183
  });
183
184
  attributeLoader(this.gl, variativeColorBuffer, this.locations.attributes.a_color, 4, {
184
- type: this.gl.UNSIGNED_BYTE,
185
- integer: true,
186
- integerUnsigned: true,
185
+ dataType: "uint8",
187
186
  escapeValues: [0, 0, 0, 255]
188
187
  });
189
188
  this.gl.bindVertexArray(null);
@@ -95,7 +95,7 @@ function mergeAndSendResults() {
95
95
  indices: new Uint32Array(totalIndices),
96
96
  longLats: new Float32Array(totalLongLats),
97
97
  realEdgeArcIndices: _arcState && totalRealEdgeArcIndices > 0 ? new Uint32Array(totalRealEdgeArcIndices) : null,
98
- pickIndices: totalPickIndices > 0 ? new Float32Array(totalPickIndices) : null,
98
+ pickIndices: totalPickIndices > 0 ? new Uint16Array(totalPickIndices) : null,
99
99
  variativeColors: totalVariativeColors > 0 ? new Uint8Array(totalVariativeColors) : null,
100
100
  };
101
101
  // Sentinel/header (as in master-worker.js)
@@ -106,7 +106,7 @@ self.onmessage = (event) => {
106
106
  longLats: new Float32Array((counter / 3) * 2),
107
107
  indices: new Uint32Array(indexCounter),
108
108
  realEdgeArcIndices: _arcState ? new Uint32Array(totalArcCount) : null,
109
- pickIndices: _pickableState ? new Float32Array(counter / 3) : null,
109
+ pickIndices: _pickableState ? new Uint16Array(counter / 3) : null,
110
110
  variativeColors: _variativeColorsOnState ? new Uint8Array((counter / 3) * 4) : null,
111
111
  };
112
112
  let currentVertexOffset = 0;
@@ -1,5 +1,5 @@
1
1
  import { WorkerContact } from "./data/worker-contact";
2
- import { TextureDemTriangles } from "../../../programs/polygon-on-globe/texture-dem-triangles";
2
+ import { TextureDemTriangles, IndexAttributeEscapeValue } from "../../../programs/polygon-on-globe/texture-dem-triangles";
3
3
  import { noRegisterGlobeProgramCache } from "../../../programs/programcache";
4
4
  import { PickerDisplayer } from "../../../util/picking/picker-displayer";
5
5
  import { IndexPolygonMap } from "./data/index-polygon-map";
@@ -11,16 +11,19 @@ export class TerrainPolygonSemiPlugin {
11
11
  _options = {
12
12
  drawEdges: true,
13
13
  pickable: false,
14
+ pickablePause: false,
14
15
  variativeColorsOn: false,
15
16
  polygonStyle: {
16
17
  defaultColor: [0.5, 0.5, 1, 1],
17
18
  pickedColor: [1, 0, 0, 1],
18
19
  opacity: 1.0,
20
+ useDefaultColor: false,
19
21
  },
20
22
  edgeStyle: {
21
23
  defaultColor: [1, 1, 0, 1],
22
24
  pickedColor: [1, 0, 0, 1],
23
25
  opacity: 1.0,
26
+ useDefaultColor: true,
24
27
  },
25
28
  // Global opacity multiplier applied on top of style opacities
26
29
  opacity: 1.0,
@@ -83,11 +86,16 @@ export class TerrainPolygonSemiPlugin {
83
86
  console.log("TerrainPolygonSemiPlugin options:", this._options);
84
87
  }
85
88
  ;
86
- _effectivePolygonOpacity() {
87
- return this._options.opacity * this._options.polygonStyle.opacity;
88
- }
89
- _effectiveEdgeOpacity() {
90
- return this._options.opacity * this._options.edgeStyle.opacity;
89
+ _effectiveOpacity(target) {
90
+ if (target === "polygon") {
91
+ return new Float32Array([this._options.opacity * this._options.polygonStyle.opacity]);
92
+ }
93
+ else if (target === "edgeArc") {
94
+ return new Float32Array([this._options.opacity * this._options.edgeStyle.opacity]);
95
+ }
96
+ else {
97
+ throw new Error(`Unknown target ${target} for _effectiveOpacity, must be "polygon" or "edgeArc"`);
98
+ }
91
99
  }
92
100
  init(globe, gl) {
93
101
  this.globe = globe;
@@ -98,11 +106,11 @@ export class TerrainPolygonSemiPlugin {
98
106
  this._vec3Buffer = gl.createBuffer();
99
107
  this._mercatorXYBuffer = gl.createBuffer();
100
108
  this._uboHandler = this._program.createUBO();
101
- this._uboHandler.updateSingle("opacity", new Float32Array([this._effectivePolygonOpacity()]));
109
+ this._uboHandler.updateSingle("opacity", this._effectiveOpacity("polygon"));
102
110
  this._uboHandler.updateSingle("defaultColor", new Float32Array(this._options.polygonStyle.defaultColor));
103
111
  if (this._options.pickable) {
104
112
  this._pickIndexBuffer = gl.createBuffer();
105
- this._pickerDisplayer = new PickerDisplayer(this.globe, "R32I");
113
+ this._pickerDisplayer = new PickerDisplayer(this.globe, "R16UI");
106
114
  this._uboHandler.updateSingle("private_isPickedOn", new Float32Array([1.0]));
107
115
  this._uboHandler.updateSingle("pickedColor", new Float32Array(this._options.polygonStyle.pickedColor));
108
116
  }
@@ -110,7 +118,7 @@ export class TerrainPolygonSemiPlugin {
110
118
  this._uboForRealEdgeArcs = this._program.createUBO();
111
119
  this._uboForRealEdgeArcs.updateSingle("defaultColor", new Float32Array(this._options.edgeStyle.defaultColor));
112
120
  this._uboForRealEdgeArcs.updateSingle("useDefaultColor", new Float32Array([1]));
113
- this._uboForRealEdgeArcs.updateSingle("opacity", new Float32Array([this._effectiveEdgeOpacity()]));
121
+ this._uboForRealEdgeArcs.updateSingle("opacity", this._effectiveOpacity("edgeArc"));
114
122
  this._drawRealEdgeArcs.elementBuffer = gl.createBuffer();
115
123
  }
116
124
  if (this._options.variativeColorsOn) {
@@ -135,7 +143,6 @@ export class TerrainPolygonSemiPlugin {
135
143
  } : undefined);
136
144
  this._drawRangeIndexParams.elementBuffer = this._elementArrayBuffer;
137
145
  this._workerContact = new WorkerContact(this.globe, this._options, (result) => {
138
- // console.log('TerrainPolygonSemiPlugin received data from worker', result);
139
146
  this.setBuffers(result);
140
147
  });
141
148
  this._drawRangeIndexParams.elementBuffer = this._elementArrayBuffer;
@@ -164,64 +171,45 @@ export class TerrainPolygonSemiPlugin {
164
171
  if (name.startsWith("private_")) {
165
172
  throw new Error(`Cannot set private uniform ${name}`);
166
173
  }
167
- const isStyleKey = name === "defaultColor" || name === "pickedColor" || name === "opacity";
174
+ const isStyleKey = name === "defaultColor" || name === "pickedColor" || name === "opacity" || name === "useDefaultColor";
175
+ let targetUBO;
168
176
  if (target === "polygon") {
169
- if (isStyleKey) {
170
- const prev = this._options.polygonStyle[name];
171
- if (prev === value)
172
- return;
173
- this._options.polygonStyle[name] = value;
174
- if (name === "opacity") {
175
- this._uboHandler.updateSingle("opacity", new Float32Array([this._effectivePolygonOpacity()]));
176
- }
177
- else {
178
- this._uboHandler.updateSingle(name, value);
179
- }
180
- }
181
- else {
182
- this._uboHandler.updateSingle(name, value);
183
- }
177
+ targetUBO = this._uboHandler;
184
178
  }
185
179
  else if (target === "edgeArc") {
186
- if (isStyleKey) {
187
- const prev = this._options.edgeStyle[name];
188
- if (prev === value)
189
- return;
190
- this._options.edgeStyle[name] = value;
191
- if (name === "opacity") {
192
- this._uboForRealEdgeArcs?.updateSingle("opacity", new Float32Array([this._effectiveEdgeOpacity()]));
193
- }
194
- else {
195
- this._uboForRealEdgeArcs?.updateSingle(name, value);
196
- }
180
+ targetUBO = this._uboForRealEdgeArcs;
181
+ }
182
+ else {
183
+ console.warn(`Unknown target ${target} for setUniform, must be "polygon" or "edgeArc"`);
184
+ return;
185
+ }
186
+ if (isStyleKey) {
187
+ const prev = this._options.polygonStyle[name];
188
+ if (prev === value)
189
+ return;
190
+ this._options.polygonStyle[name] = value;
191
+ if (name === "opacity") {
192
+ targetUBO.updateSingle("opacity", this._effectiveOpacity(target));
197
193
  }
198
194
  else {
199
- this._uboForRealEdgeArcs?.updateSingle(name, value);
195
+ targetUBO.updateSingle(name, value);
200
196
  }
201
197
  }
202
198
  else {
203
- console.warn(`Unknown target ${target} for setUniform, must be "polygon" or "edgeArc"`);
199
+ targetUBO.updateSingle(name, value);
204
200
  }
205
201
  this.globe.DrawRender();
206
202
  }
207
- setDrawEdgesState(state, options = {}) {
203
+ setDrawEdgesState(state) {
208
204
  const oldState = this._options.drawEdges;
209
205
  if (oldState === state)
210
206
  return;
211
- if (options?.defaultColor) {
212
- this._options.edgeStyle.defaultColor = options.defaultColor;
213
- }
214
- if (options?.pickedColor) {
215
- this._options.edgeStyle.pickedColor = options.pickedColor;
216
- }
217
- if (options?.opacity !== undefined) {
218
- this._options.edgeStyle.opacity = options.opacity;
219
- }
220
207
  if (oldState === false && state === true) {
221
208
  this._uboForRealEdgeArcs = this._program.createUBO();
222
209
  this._uboForRealEdgeArcs.updateSingle("defaultColor", new Float32Array(this._options.edgeStyle.defaultColor));
223
- this._uboForRealEdgeArcs.updateSingle("useDefaultColor", new Float32Array([1]));
224
- this._uboForRealEdgeArcs.updateSingle("opacity", new Float32Array([this._effectiveEdgeOpacity()]));
210
+ this._uboForRealEdgeArcs.updateSingle("useDefaultColor", new Float32Array([this._options.edgeStyle.useDefaultColor ? 1 : 0]));
211
+ this._uboForRealEdgeArcs.updateSingle("pickedColor", new Float32Array(this._options.edgeStyle.pickedColor));
212
+ this._uboForRealEdgeArcs.updateSingle("opacity", this._effectiveOpacity("edgeArc"));
225
213
  this._drawRealEdgeArcs.elementBuffer = this.globe.gl.createBuffer();
226
214
  }
227
215
  else if (oldState === true && state === false) {
@@ -237,28 +225,42 @@ export class TerrainPolygonSemiPlugin {
237
225
  setOpacity(opacity) {
238
226
  if (this._options.opacity !== opacity) {
239
227
  this._options.opacity = opacity;
240
- this._uboHandler.updateSingle("opacity", new Float32Array([this._effectivePolygonOpacity()]));
241
- this._uboForRealEdgeArcs?.updateSingle("opacity", new Float32Array([this._effectiveEdgeOpacity()]));
228
+ this._uboHandler.updateSingle("opacity", this._effectiveOpacity("polygon"));
229
+ this._uboForRealEdgeArcs?.updateSingle("opacity", this._effectiveOpacity("edgeArc"));
242
230
  this.globe.DrawRender();
243
231
  }
244
232
  }
233
+ setPausePickableState(state) {
234
+ if (this._options.pickable === false) {
235
+ throw new Error("TerrainPolygonSemiPlugin is not pickable. Set pickable option to true on construction to enable picking.");
236
+ }
237
+ const oldState = this._options.pickablePause;
238
+ if (oldState === state)
239
+ return;
240
+ this._options.pickablePause = state;
241
+ // dont initialize anything. just set private_isPickedOn uniform
242
+ this._uboHandler.updateSingle("private_isPickedOn", new Float32Array([state ? 0.0 : 1.0]));
243
+ this.globe.DrawRender();
244
+ }
245
245
  setPickableState(state) {
246
+ throw new Error("setPickableState is buggy. use setPausePickableState instead.");
246
247
  const oldState = this._options.pickable;
247
248
  if (oldState === state)
248
249
  return;
249
250
  if (oldState === false && state === true) {
250
- // this._pickIndexBuffer = this.globe.gl.createBuffer();
251
+ this._pickIndexBuffer = this.globe.gl.createBuffer();
251
252
  this._uboHandler.updateSingle("private_isPickedOn", new Float32Array([1.0]));
252
253
  this._uboHandler.updateSingle("pickedColor", new Float32Array(this._options.polygonStyle.pickedColor));
253
- // this._pickerDisplayer = new PickerDisplayer(this.globe, "R32I");
254
+ this._pickerDisplayer = new PickerDisplayer(this.globe, "R32I");
254
255
  }
255
256
  else if (oldState === true && state === false) {
256
- // this.globe.gl.deleteBuffer(this._pickIndexBuffer!);
257
- // this._pickIndexBuffer = null;
257
+ this.globe.gl.deleteBuffer(this._pickIndexBuffer);
258
+ this._pickIndexBuffer = null;
258
259
  this._uboHandler.updateSingle("private_isPickedOn", new Float32Array([0.0]));
259
- // this._pickerDisplayer?.free();
260
- // this._pickerDisplayer = null;
261
- // this._lastPickedPolygon = null;
260
+ this._uboHandler.updateSingle("private_pickedIndex", new Float32Array([-1]));
261
+ this._pickerDisplayer?.free();
262
+ this._pickerDisplayer = null;
263
+ this._lastPickedPolygon = null;
262
264
  }
263
265
  this._options.pickable = state;
264
266
  this._workerContact.setPickableState(state);
@@ -287,30 +289,6 @@ export class TerrainPolygonSemiPlugin {
287
289
  if (this._options.variativeColorsOn && data.variativeColors) {
288
290
  safeVertexCount = Math.min(safeVertexCount, Math.floor(data.variativeColors.length / 4));
289
291
  }
290
- // Guard against invalid indices referencing beyond uploaded attribute buffers.
291
- // This prevents ANGLE "Vertex buffer is not big enough for the draw call" errors.
292
- // let maxTriangleIndex = -1;
293
- // // for (let i = 0; i < data.indices.length; i++) {
294
- // // const v = data.indices[i];
295
- // // if (v > maxTriangleIndex) maxTriangleIndex = v;
296
- // // }
297
- // const trianglesInvalid = safeVertexCount === 0 || maxTriangleIndex >= safeVertexCount;
298
- // if (trianglesInvalid) {
299
- // console.warn(
300
- // "TerrainPolygonSemiPlugin: skipping draw due to invalid vertex/index sizing",
301
- // {
302
- // posVertexCount,
303
- // xyVertexCount,
304
- // safeVertexCount,
305
- // indicesLength: data.indices.length,
306
- // maxTriangleIndex,
307
- // pickIndicesLength: data.pickIndices?.length ?? null,
308
- // variativeColorsLength: data.variativeColors?.length ?? null,
309
- // }
310
- // );
311
- // this._drawRangeIndexParams.drawRange.count = 0;
312
- // this._drawPointsRangeIndexParams.drawRange.count = 0;
313
- // }
314
292
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._elementArrayBuffer);
315
293
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data.indices, gl.STREAM_DRAW);
316
294
  gl.bindBuffer(gl.ARRAY_BUFFER, this._vec3Buffer);
@@ -375,7 +353,7 @@ export class TerrainPolygonSemiPlugin {
375
353
  if (this._lastPickedPolygon !== null) {
376
354
  this._lastPickedPolygon = null;
377
355
  globe.DrawRender();
378
- this._uboHandler.updateSingle("private_pickedIndex", new Float32Array([-1]));
356
+ this._uboHandler.updateSingle("private_pickedIndex", new Float32Array([IndexAttributeEscapeValue]));
379
357
  }
380
358
  }
381
359
  });
@@ -399,7 +377,7 @@ export class TerrainPolygonSemiPlugin {
399
377
  if (this._options.showTesselationPoints) {
400
378
  this._program.draw(this._vao, this._drawPointsRangeIndexParams, this._uboHandler);
401
379
  }
402
- if (this._pickerDisplayer && this._options.pickable) {
380
+ if (this._pickerDisplayer && !this._options.pickablePause) {
403
381
  this._pickerDisplayer.bindFBO();
404
382
  this._pickerDisplayer.clearTextures();
405
383
  // gl.enable(gl.DEPTH_TEST);
@@ -407,7 +385,7 @@ export class TerrainPolygonSemiPlugin {
407
385
  gl.frontFace(gl.CW);
408
386
  this._program.draw(this._vao, this._drawRangeIndexParams, this._uboHandler);
409
387
  gl.frontFace(gl.CCW);
410
- if (this._pickerDisplayer && this._options.pickable) {
388
+ if (this._pickerDisplayer && !this._options.pickablePause) {
411
389
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
412
390
  this._pickerDisplayer.drawColorTexture();
413
391
  this._selfSelect();
@@ -1,27 +1,17 @@
1
- /**
2
- * @typedef BufferAndReadInfo Buffers can be intertwined or interleaved.
3
- * This object forces user to adapt generic convention of buffer and read information.
4
- * @type {Object}
5
- * @property {WebGLBuffer} buffer
6
- * @property {number} stride
7
- * @property {number} offset
8
- */
9
- /**
10
- *
11
- * @param {WebGLBuffer} gl
12
- * @param {BufferAndReadInfo} bufferAndReadInfo
13
- * @param {number} index
14
- * @param {number} size
15
- * @param {Object} options
16
- * @param {*} options.type | default gl.FLOAT, gl.UNSIGNED_BYTE, gl.SHORT, gl.UNSIGNED_SHORT, gl.INT, gl.UNSIGNED_INT
17
- * @param {number} options.divisor
18
- * @param {Array<number>} options.escapeValues
19
- * @returns
20
- */
21
- const attributeLoader = (gl, bufferAndReadInfo, index, size, { divisor = null, type = null, escapeValues = null, normalized = false, integer = false, integerUnsigned = true, } = {}) => {
1
+ const DATA_TYPE_MAP = {
2
+ float: { getGLType: (gl) => gl.FLOAT, useIntegerPointer: false, isSigned: true },
3
+ int8: { getGLType: (gl) => gl.BYTE, useIntegerPointer: true, isSigned: true },
4
+ uint8: { getGLType: (gl) => gl.UNSIGNED_BYTE, useIntegerPointer: true, isSigned: false },
5
+ int16: { getGLType: (gl) => gl.SHORT, useIntegerPointer: true, isSigned: true },
6
+ uint16: { getGLType: (gl) => gl.UNSIGNED_SHORT, useIntegerPointer: true, isSigned: false },
7
+ int32: { getGLType: (gl) => gl.INT, useIntegerPointer: true, isSigned: true },
8
+ uint32: { getGLType: (gl) => gl.UNSIGNED_INT, useIntegerPointer: true, isSigned: false },
9
+ };
10
+ const attributeLoader = (gl, bufferAndReadInfo, index, size, { divisor = null, dataType = null, escapeValues = null, normalized = false, } = {}) => {
11
+ const typeInfo = dataType ? DATA_TYPE_MAP[dataType] : DATA_TYPE_MAP.float;
22
12
  if (bufferAndReadInfo == null || bufferAndReadInfo.buffer == null) {
23
13
  if (escapeValues)
24
- constantFunction(gl, index, size, escapeValues, integer, integerUnsigned);
14
+ constantFunction(gl, index, size, escapeValues, typeInfo);
25
15
  return;
26
16
  }
27
17
  const { buffer, stride, offset } = bufferAndReadInfo;
@@ -34,10 +24,10 @@ const attributeLoader = (gl, bufferAndReadInfo, index, size, { divisor = null, t
34
24
  if (stride < 0 || offset < 0) {
35
25
  throw new Error("Stride and offset must be non-negative");
36
26
  }
37
- const attribType = type === null ? gl.FLOAT : type;
27
+ const attribType = typeInfo.getGLType(gl);
38
28
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
39
29
  gl.enableVertexAttribArray(index);
40
- if (integer) {
30
+ if (typeInfo.useIntegerPointer) {
41
31
  gl.vertexAttribIPointer(index, size, attribType, stride, offset);
42
32
  }
43
33
  else {
@@ -59,15 +49,15 @@ const createBufferAndReadInfo = (buffer, stride = 0, offset = 0) => {
59
49
  return null;
60
50
  return { buffer, stride, offset };
61
51
  };
62
- const constantFunction = (gl, index, size, escapeValues, integer, integerUnsigned) => {
63
- if (!integer) {
52
+ const constantFunction = (gl, index, size, escapeValues, typeInfo) => {
53
+ if (!typeInfo.useIntegerPointer) {
64
54
  const func = `vertexAttrib${size}f`;
65
55
  // @ts-ignore
66
56
  gl[func](index, ...escapeValues);
67
57
  return;
68
58
  }
69
59
  const values = escapeValues.map((v) => Math.trunc(v));
70
- const func = integerUnsigned ? `vertexAttribI${size}ui` : `vertexAttribI${size}i`;
60
+ const func = typeInfo.isSigned ? `vertexAttribI${size}i` : `vertexAttribI${size}ui`;
71
61
  // @ts-ignore
72
62
  gl[func](index, ...values);
73
63
  };
@@ -0,0 +1,172 @@
1
+ import { textureOnCanvasProgramCache } from "../programs/draw-texture-on-canvas";
2
+ import { fence } from "./fence";
3
+ /** * Use 0xFFFFFFFF (4,294,967,295) as the "no-object" value.
4
+ * This allows 0 to be a valid Object ID.
5
+ */
6
+ const ESCAPE_VALUE = 4294967295;
7
+ class PickerDisplayer {
8
+ globe;
9
+ gl;
10
+ colorTexture;
11
+ indexTexture;
12
+ fbo;
13
+ _pbo = null;
14
+ _pboSize = 0;
15
+ displayer;
16
+ _inProgress = false;
17
+ _lastWidthHeight = null;
18
+ constructor(globe) {
19
+ this.globe = globe;
20
+ this.gl = globe.gl;
21
+ const gl = this.gl;
22
+ this.colorTexture = gl.createTexture();
23
+ this.indexTexture = gl.createTexture();
24
+ this.fbo = gl.createFramebuffer();
25
+ this.resize();
26
+ this.displayer = textureOnCanvasProgramCache.get(this.gl);
27
+ }
28
+ _saveState() {
29
+ const gl = this.gl;
30
+ return {
31
+ framebuffer: gl.getParameter(gl.FRAMEBUFFER_BINDING),
32
+ pixelPackBuffer: gl.getParameter(gl.PIXEL_PACK_BUFFER_BINDING),
33
+ readBuffer: gl.getParameter(gl.READ_BUFFER),
34
+ depthTestEnabled: gl.isEnabled(gl.DEPTH_TEST),
35
+ activeTexture: gl.getParameter(gl.ACTIVE_TEXTURE),
36
+ tex2D_unit0: (gl.activeTexture(gl.TEXTURE0), gl.getParameter(gl.TEXTURE_BINDING_2D)),
37
+ drawBuffers: [gl.getParameter(gl.DRAW_BUFFER0), gl.getParameter(gl.DRAW_BUFFER1)]
38
+ };
39
+ }
40
+ _restoreState(s) {
41
+ const gl = this.gl;
42
+ gl.bindFramebuffer(gl.FRAMEBUFFER, s.framebuffer);
43
+ gl.bindBuffer(gl.PIXEL_PACK_BUFFER, s.pixelPackBuffer);
44
+ gl.readBuffer(s.readBuffer);
45
+ if (s.depthTestEnabled)
46
+ gl.enable(gl.DEPTH_TEST);
47
+ else
48
+ gl.disable(gl.DEPTH_TEST);
49
+ gl.activeTexture(gl.TEXTURE0);
50
+ gl.bindTexture(gl.TEXTURE_2D, s.tex2D_unit0);
51
+ gl.activeTexture(s.activeTexture);
52
+ gl.drawBuffers(s.drawBuffers.filter(b => b !== gl.NONE));
53
+ }
54
+ resize() {
55
+ const width = this.globe.api_ScrW();
56
+ const height = this.globe.api_ScrH();
57
+ if (this._lastWidthHeight?.width === width && this._lastWidthHeight?.height === height)
58
+ return;
59
+ this._lastWidthHeight = { width, height };
60
+ const gl = this.gl;
61
+ // Attachment 0: Visual Color (RGBA8)
62
+ gl.bindTexture(gl.TEXTURE_2D, this.colorTexture);
63
+ gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA8, width, height);
64
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
65
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
66
+ // Attachment 1: Picking ID (R32UI - Unsigned Integer)
67
+ gl.bindTexture(gl.TEXTURE_2D, this.indexTexture);
68
+ gl.texStorage2D(gl.TEXTURE_2D, 1, gl.R32UI, width, height);
69
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
70
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
71
+ gl.bindTexture(gl.TEXTURE_2D, null);
72
+ }
73
+ bindFBO() {
74
+ const gl = this.gl;
75
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.fbo);
76
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.colorTexture, 0);
77
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.TEXTURE_2D, this.indexTexture, 0);
78
+ gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1]);
79
+ if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
80
+ throw new Error("Framebuffer incomplete");
81
+ }
82
+ }
83
+ clearTextures() {
84
+ const gl = this.gl;
85
+ const saved = this._saveState();
86
+ this.bindFBO();
87
+ // Clear Color to transparent black
88
+ gl.clearBufferfv(gl.COLOR, 0, [0, 0, 0, 0]);
89
+ // Clear Index to Max Uint32 (ESCAPE_VALUE)
90
+ gl.clearBufferuiv(gl.COLOR, 1, [ESCAPE_VALUE, 0, 0, 0]);
91
+ this._restoreState(saved);
92
+ }
93
+ pickXY(x, y, radius = 0, callback) {
94
+ const side = 1 + radius * 2;
95
+ // Invert Y because WebGL screen space starts bottom-left
96
+ const webglY = this.globe.api_ScrH() - y - radius;
97
+ return this._pick(x - radius, webglY, side, side, callback);
98
+ }
99
+ getEscapeValue() {
100
+ return ESCAPE_VALUE;
101
+ }
102
+ _pick(x, y, w, h, callback) {
103
+ if (this._inProgress)
104
+ return false;
105
+ this._inProgress = true;
106
+ const gl = this.gl;
107
+ const saved = this._saveState();
108
+ const pixelCount = w * h;
109
+ const byteSize = pixelCount * 4;
110
+ try {
111
+ this._initPBO(byteSize);
112
+ this.bindFBO();
113
+ gl.readBuffer(gl.COLOR_ATTACHMENT1);
114
+ gl.bindBuffer(gl.PIXEL_PACK_BUFFER, this._pbo);
115
+ // CRITICAL: Format must be RED_INTEGER and type UNSIGNED_INT for UI
116
+ gl.readPixels(x, y, w, h, gl.RED_INTEGER, gl.UNSIGNED_INT, 0);
117
+ gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
118
+ this._restoreState(saved);
119
+ fence(gl).then(() => {
120
+ gl.bindBuffer(gl.PIXEL_PACK_BUFFER, this._pbo);
121
+ const data = new Uint32Array(pixelCount);
122
+ gl.getBufferSubData(gl.PIXEL_PACK_BUFFER, 0, data);
123
+ gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
124
+ const resultSet = new Set();
125
+ for (let i = 0; i < data.length; i++) {
126
+ if (data[i] !== ESCAPE_VALUE)
127
+ resultSet.add(data[i]);
128
+ }
129
+ callback(resultSet);
130
+ }).finally(() => {
131
+ this._inProgress = false;
132
+ });
133
+ return true;
134
+ }
135
+ catch (e) {
136
+ console.error("Picking Error:", e);
137
+ this._restoreState(saved);
138
+ this._inProgress = false;
139
+ return false;
140
+ }
141
+ }
142
+ _initPBO(size) {
143
+ const gl = this.gl;
144
+ if (this._pbo && this._pboSize >= size)
145
+ return;
146
+ if (this._pbo)
147
+ gl.deleteBuffer(this._pbo);
148
+ this._pbo = gl.createBuffer();
149
+ this._pboSize = size;
150
+ gl.bindBuffer(gl.PIXEL_PACK_BUFFER, this._pbo);
151
+ gl.bufferData(gl.PIXEL_PACK_BUFFER, size, gl.STREAM_READ);
152
+ gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
153
+ }
154
+ drawColorTexture() {
155
+ const gl = this.gl;
156
+ const saved = this._saveState();
157
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
158
+ gl.disable(gl.DEPTH_TEST);
159
+ this.displayer.draw(this.colorTexture);
160
+ this._restoreState(saved);
161
+ }
162
+ free() {
163
+ const gl = this.gl;
164
+ gl.deleteTexture(this.colorTexture);
165
+ gl.deleteTexture(this.indexTexture);
166
+ gl.deleteFramebuffer(this.fbo);
167
+ if (this._pbo)
168
+ gl.deleteBuffer(this._pbo);
169
+ textureOnCanvasProgramCache.release(this.gl);
170
+ }
171
+ }
172
+ export { PickerDisplayer };
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * add implicit texture display program for color
3
3
  * add fence on query return and return id.
4
- */
4
+ * support R32I, R32F, R16UI, R32UI
5
+ */
5
6
  import { textureOnCanvasProgramCache } from "../programs/draw-texture-on-canvas";
6
7
  import { fence } from "./fence";
7
8
  const ESCAPE_VALUE = -1;
@@ -15,8 +16,10 @@ class PickerDisplayer {
15
16
  _pboSize;
16
17
  displayer;
17
18
  _inProgress;
18
- _indexType; // R32I for Integer, R32F for Float
19
+ _indexType;
19
20
  _typedArrayConstructor;
21
+ // The value representing "nothing selected" in the current type format
22
+ _escapeValue;
20
23
  _lastWidthHeight = null;
21
24
  constructor(globe, indexType = "R32I") {
22
25
  this.globe = globe;
@@ -29,15 +32,25 @@ class PickerDisplayer {
29
32
  throw new Error("EXT_color_buffer_float extension is required for R32F index type.");
30
33
  }
31
34
  this._indexType = indexType;
32
- if (indexType === "R32I") {
33
- this._typedArrayConstructor = Int32Array;
34
- }
35
- else if (indexType === "R32F") {
36
- this._typedArrayConstructor = Float32Array;
37
- }
38
- else {
39
- throw new Error("Invalid index type. Must be 'R32I' or 'R32F'.");
35
+ // Assign TypedArray constructor and calculate the specific Escape Value
36
+ switch (indexType) {
37
+ case "R32I":
38
+ this._typedArrayConstructor = Int32Array;
39
+ break;
40
+ case "R32F":
41
+ this._typedArrayConstructor = Float32Array;
42
+ break;
43
+ case "R16UI":
44
+ this._typedArrayConstructor = Uint16Array;
45
+ break;
46
+ case "R32UI":
47
+ this._typedArrayConstructor = Uint32Array;
48
+ break;
49
+ default:
50
+ throw new Error("Invalid index type. Must be 'R32I', 'R32F', 'R16UI', or 'R32UI'.");
40
51
  }
52
+ // Calculate what -1 looks like in the target type (e.g., 0xFFFF for Uint16)
53
+ this._escapeValue = (new this._typedArrayConstructor([ESCAPE_VALUE]))[0];
41
54
  this._pbo = undefined;
42
55
  this._pboSize = 0;
43
56
  this._inProgress = false;
@@ -85,8 +98,6 @@ class PickerDisplayer {
85
98
  gl.activeTexture(gl.TEXTURE1);
86
99
  gl.bindTexture(gl.TEXTURE_2D, s.tex2D_unit1);
87
100
  gl.activeTexture(s.activeTexture);
88
- // drawBuffers restore: default framebuffer expects BACK/NONE (implementation details vary),
89
- // so only restore the number of buffers that makes sense for the bound FB.
90
101
  if (s.framebuffer === null) {
91
102
  gl.drawBuffers([s.drawBuffer0]);
92
103
  }
@@ -109,14 +120,18 @@ class PickerDisplayer {
109
120
  const { colorTexture, indexTexture } = this;
110
121
  gl.deleteTexture(colorTexture);
111
122
  gl.deleteTexture(indexTexture);
123
+ // 1. Color Texture (Standard RGBA8)
112
124
  const colorTextureNew = gl.createTexture();
113
125
  gl.bindTexture(gl.TEXTURE_2D, colorTextureNew);
114
126
  gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA8, width, height);
115
127
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
116
128
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
129
+ // 2. Index Texture (Varies based on this._indexType)
117
130
  const indexTextureNew = gl.createTexture();
118
131
  gl.bindTexture(gl.TEXTURE_2D, indexTextureNew);
119
- gl.texStorage2D(gl.TEXTURE_2D, 1, gl[this._indexType], width, height); // gl.R32I or gl.R32F
132
+ // Map string type to GL constant
133
+ const internalFormat = gl[this._indexType];
134
+ gl.texStorage2D(gl.TEXTURE_2D, 1, internalFormat, width, height);
120
135
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
121
136
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
122
137
  gl.bindTexture(gl.TEXTURE_2D, null);
@@ -127,14 +142,24 @@ class PickerDisplayer {
127
142
  const gl = this.gl;
128
143
  const saved = this._saveState();
129
144
  try {
130
- // Must clear the *FBO attachments*, not "textures".
131
145
  this.bindFBO();
146
+ // Clear Index Attachment (Attachment 1)
132
147
  if (this._indexType === "R32I") {
133
- gl.clearBufferiv(gl.COLOR, 1, new Int32Array([-1, -1, -1, -1]));
148
+ // Signed Integer
149
+ gl.clearBufferiv(gl.COLOR, 1, new Int32Array([ESCAPE_VALUE, ESCAPE_VALUE, ESCAPE_VALUE, ESCAPE_VALUE]));
150
+ }
151
+ else if (this._indexType === "R32F") {
152
+ // Float
153
+ gl.clearBufferfv(gl.COLOR, 1, new Float32Array([ESCAPE_VALUE, ESCAPE_VALUE, ESCAPE_VALUE, ESCAPE_VALUE]));
134
154
  }
135
155
  else {
136
- gl.clearBufferfv(gl.COLOR, 1, new Float32Array([-1, -1, -1, -1]));
156
+ // Unsigned Integer (R16UI, R32UI)
157
+ // We use ESCAPE_VALUE cast to uint. For -1 this is MaxUint (all 1s).
158
+ // clearBufferuiv expects Uint32Array even for 16-bit buffers.
159
+ const clearVal = this._escapeValue;
160
+ gl.clearBufferuiv(gl.COLOR, 1, new Uint32Array([clearVal, clearVal, clearVal, clearVal]));
137
161
  }
162
+ // Clear Color Attachment (Attachment 0)
138
163
  gl.clearBufferfv(gl.COLOR, 0, new Float32Array([0, 0, 0, 0]));
139
164
  }
140
165
  finally {
@@ -146,7 +171,6 @@ class PickerDisplayer {
146
171
  const { gl, fbo } = this;
147
172
  this.resize();
148
173
  gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
149
- // No need to bind textures or change ACTIVE_TEXTURE for framebufferTexture2D.
150
174
  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.colorTexture, 0);
151
175
  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.TEXTURE_2D, this.indexTexture, 0);
152
176
  gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1]);
@@ -183,15 +207,39 @@ class PickerDisplayer {
183
207
  const gl = this.gl;
184
208
  const saved = this._saveState();
185
209
  try {
186
- this._initHoldBuffer(size * 4);
210
+ // Allocate PBO with enough size for the specific type (2 bytes or 4 bytes per pixel)
211
+ const bytesPerPixel = this._typedArrayConstructor.BYTES_PER_ELEMENT;
212
+ this._initHoldBuffer(size * bytesPerPixel);
187
213
  const { _pbo } = this;
188
214
  gl.bindBuffer(gl.PIXEL_PACK_BUFFER, _pbo);
189
215
  this.bindFBO();
190
216
  gl.readBuffer(gl.COLOR_ATTACHMENT1);
191
- const format = this._indexType === "R32I" ? gl.RED_INTEGER : gl.RED;
192
- const type = this._indexType === "R32I" ? gl.INT : gl.FLOAT;
217
+ let format;
218
+ let type;
219
+ switch (this._indexType) {
220
+ case "R32I":
221
+ format = gl.RED_INTEGER;
222
+ type = gl.INT;
223
+ break;
224
+ case "R32F":
225
+ format = gl.RED;
226
+ type = gl.FLOAT;
227
+ break;
228
+ case "R16UI":
229
+ format = gl.RED_INTEGER;
230
+ type = gl.UNSIGNED_SHORT;
231
+ break;
232
+ case "R32UI":
233
+ format = gl.RED_INTEGER;
234
+ type = gl.UNSIGNED_INT;
235
+ break;
236
+ default:
237
+ // Fallback
238
+ format = gl.RED_INTEGER;
239
+ type = gl.INT;
240
+ }
193
241
  gl.readPixels(startX, startY, lengthX, lengthY, format, type, 0);
194
- // Restore state immediately after issuing readPixels (PBO keeps data for later getBufferSubData)
242
+ // Restore state immediately after issuing readPixels
195
243
  this._restoreState(saved);
196
244
  fence(this.gl)
197
245
  .then(() => {
@@ -203,7 +251,7 @@ class PickerDisplayer {
203
251
  callback(result);
204
252
  })
205
253
  .catch(() => {
206
- // Swallow or rethrow based on your app needs; important is not to wedge _inProgress.
254
+ // Swallow or rethrow based on your app needs
207
255
  })
208
256
  .finally(() => {
209
257
  this._inProgress = false;
@@ -215,30 +263,28 @@ class PickerDisplayer {
215
263
  this._inProgress = false;
216
264
  throw new Error("Picking failed.");
217
265
  }
218
- finally {
219
- this._restoreState(saved);
220
- }
221
266
  }
222
267
  _pickFromBuffer(array, size) {
223
268
  const selectedObjects = new Set();
269
+ const esc = this._escapeValue;
224
270
  for (let i = 0; i < size; i += 1) {
225
271
  const id = array[i];
226
- if (id !== ESCAPE_VALUE) {
272
+ if (id !== esc) {
227
273
  selectedObjects.add(id);
228
274
  }
229
275
  }
230
276
  return selectedObjects;
231
277
  }
232
- _initHoldBuffer(size) {
233
- if (this._pbo && this._pboSize >= size) {
278
+ _initHoldBuffer(sizeInBytes) {
279
+ if (this._pbo && this._pboSize >= sizeInBytes) {
234
280
  return;
235
281
  }
236
282
  const { gl } = this;
237
283
  const pbo = gl.createBuffer();
238
284
  gl.bindBuffer(gl.PIXEL_PACK_BUFFER, pbo);
239
- gl.bufferData(gl.PIXEL_PACK_BUFFER, size, gl.STREAM_READ);
285
+ gl.bufferData(gl.PIXEL_PACK_BUFFER, sizeInBytes, gl.STREAM_READ);
240
286
  gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
241
- this._pboSize = size;
287
+ this._pboSize = sizeInBytes;
242
288
  if (this._pbo !== undefined) {
243
289
  gl.deleteBuffer(this._pbo);
244
290
  }