@pirireis/webglobeplugins 1.1.13 → 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.13",
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";
@@ -110,7 +110,7 @@ export class TerrainPolygonSemiPlugin {
110
110
  this._uboHandler.updateSingle("defaultColor", new Float32Array(this._options.polygonStyle.defaultColor));
111
111
  if (this._options.pickable) {
112
112
  this._pickIndexBuffer = gl.createBuffer();
113
- this._pickerDisplayer = new PickerDisplayer(this.globe, "R32I");
113
+ this._pickerDisplayer = new PickerDisplayer(this.globe, "R16UI");
114
114
  this._uboHandler.updateSingle("private_isPickedOn", new Float32Array([1.0]));
115
115
  this._uboHandler.updateSingle("pickedColor", new Float32Array(this._options.polygonStyle.pickedColor));
116
116
  }
@@ -143,7 +143,6 @@ export class TerrainPolygonSemiPlugin {
143
143
  } : undefined);
144
144
  this._drawRangeIndexParams.elementBuffer = this._elementArrayBuffer;
145
145
  this._workerContact = new WorkerContact(this.globe, this._options, (result) => {
146
- // console.log('TerrainPolygonSemiPlugin received data from worker', result);
147
146
  this.setBuffers(result);
148
147
  });
149
148
  this._drawRangeIndexParams.elementBuffer = this._elementArrayBuffer;
@@ -290,30 +289,6 @@ export class TerrainPolygonSemiPlugin {
290
289
  if (this._options.variativeColorsOn && data.variativeColors) {
291
290
  safeVertexCount = Math.min(safeVertexCount, Math.floor(data.variativeColors.length / 4));
292
291
  }
293
- // Guard against invalid indices referencing beyond uploaded attribute buffers.
294
- // This prevents ANGLE "Vertex buffer is not big enough for the draw call" errors.
295
- // let maxTriangleIndex = -1;
296
- // // for (let i = 0; i < data.indices.length; i++) {
297
- // // const v = data.indices[i];
298
- // // if (v > maxTriangleIndex) maxTriangleIndex = v;
299
- // // }
300
- // const trianglesInvalid = safeVertexCount === 0 || maxTriangleIndex >= safeVertexCount;
301
- // if (trianglesInvalid) {
302
- // console.warn(
303
- // "TerrainPolygonSemiPlugin: skipping draw due to invalid vertex/index sizing",
304
- // {
305
- // posVertexCount,
306
- // xyVertexCount,
307
- // safeVertexCount,
308
- // indicesLength: data.indices.length,
309
- // maxTriangleIndex,
310
- // pickIndicesLength: data.pickIndices?.length ?? null,
311
- // variativeColorsLength: data.variativeColors?.length ?? null,
312
- // }
313
- // );
314
- // this._drawRangeIndexParams.drawRange.count = 0;
315
- // this._drawPointsRangeIndexParams.drawRange.count = 0;
316
- // }
317
292
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._elementArrayBuffer);
318
293
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data.indices, gl.STREAM_DRAW);
319
294
  gl.bindBuffer(gl.ARRAY_BUFFER, this._vec3Buffer);
@@ -378,7 +353,7 @@ export class TerrainPolygonSemiPlugin {
378
353
  if (this._lastPickedPolygon !== null) {
379
354
  this._lastPickedPolygon = null;
380
355
  globe.DrawRender();
381
- this._uboHandler.updateSingle("private_pickedIndex", new Float32Array([-1]));
356
+ this._uboHandler.updateSingle("private_pickedIndex", new Float32Array([IndexAttributeEscapeValue]));
382
357
  }
383
358
  }
384
359
  });
@@ -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
  }