@pirireis/webglobeplugins 1.1.13 → 1.1.15

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.15",
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
+ // last value of uint16 is 65535
17
18
  export const IndexAttributeEscapeValue = -1;
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
@@ -45,7 +46,7 @@ in uvec4 a_color;
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
 
@@ -54,6 +55,8 @@ void main() {
54
55
  float elevation = ${WORLD_RADIUS_3D};
55
56
  float altitude = 0.0;
56
57
 
58
+ // Always define v_index to avoid undefined varyings when picking is disabled.
59
+
57
60
  vec4 decodedColor = vec4(
58
61
  float(a_color.r) / 255.0,
59
62
  float(a_color.g) / 255.0,
@@ -62,7 +65,7 @@ void main() {
62
65
  );
63
66
 
64
67
  // a_color.a == 255u is reserved as "no per-vertex color" sentinel.
65
- v_color = (a_color.a == 255u || useDefaultColor == 1.0) ? defaultColor : decodedColor;
68
+ v_color = (a_color.a == 255u || useDefaultColor == true) ? defaultColor : decodedColor;
66
69
 
67
70
  if (is3D == true) {
68
71
  for (int i = 0; i < 6; i++) {
@@ -87,11 +90,13 @@ void main() {
87
90
  gl_Position = mercatorXYToGLPosition(mercatorXY);
88
91
  }
89
92
 
90
- if (private_isPickedOn == 1.0){
91
- if (a_index == private_pickedIndex) {
93
+ if (private_isPickedOn == true){
94
+ if (a_index == float(private_pickedIndex)) {
92
95
  v_color = pickedColor;
93
96
  }
94
- v_index = int(a_index + 0.5); // +0.5 to avoid rounding issues
97
+ v_index = uint(a_index);
98
+ } else {
99
+ v_index = 0u;
95
100
  }
96
101
 
97
102
 
@@ -109,19 +114,15 @@ precision highp int;
109
114
 
110
115
 
111
116
  in vec4 v_color; // color from vertex shader
112
- flat in int v_index; // index from vertex shader
117
+ flat in uint v_index; // index from vertex shader
113
118
  layout(location = 0) out vec4 outColor;
114
- layout(location = 1) out int outIndex;
119
+ layout(location = 1) out uint outIndex;
115
120
 
116
121
  void main() {
117
122
  outColor = v_color;
118
123
  outIndex = v_index;
119
124
  }
120
125
  `;
121
- function resCalculation(sourceResolution, mergeCount) {
122
- return sourceResolution + (mergeCount - 1) * (sourceResolution - 1);
123
- }
124
- const RESOLUTION = resCalculation(5, 20); // 5 is tile dimension length, 12 is merge count
125
126
  export class TextureDemTriangles {
126
127
  globe;
127
128
  gl;
@@ -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: "uint32",
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);
@@ -209,13 +208,24 @@ export class TextureDemTriangles {
209
208
  this.demTextureManager.bindData(0, uniformBindingPoints.dem);
210
209
  gl.bindVertexArray(vao);
211
210
  ubo.bind();
212
- if (this.globe.api_GetCurrentLODWithDecimal() < 14.5) {
213
- drawOnTopBegin(gl);
214
- }
211
+ // This program has two fragment outputs (location 0: color, location 1: index).
212
+ // When rendering to the default framebuffer (no picking FBO bound), only location 0
213
+ // is valid; explicitly discard location 1 to avoid GL_INVALID_OPERATION.
214
+ // const boundFbo = gl.getParameter(gl.FRAMEBUFFER_BINDING) as WebGLFramebuffer | null;
215
+ // if (boundFbo == null) {
216
+ // gl.drawBuffers([gl.BACK, gl.NONE]);
217
+ // // }
218
+ // if (this.globe.api_GetCurrentLODWithDecimal() < 14.5) {
219
+ // drawOnTopBegin(gl);
220
+ // }
215
221
  drawArrays(gl, drawOptions.drawMode ?? gl.TRIANGLES, drawOptions);
216
- if (this.globe.api_GetCurrentLODWithDecimal() < 14.5) {
217
- drawOnTopEnd(gl);
218
- }
222
+ // if (this.globe.api_GetCurrentLODWithDecimal() < 14.5) {
223
+ // drawOnTopEnd(gl);
224
+ // }
225
+ // if (boundFbo == null) {
226
+ // // Restore the default draw buffer mapping for subsequent draws.
227
+ // gl.drawBuffers([gl.BACK]);
228
+ // }
219
229
  this.demTextureManager.unbindData(0, uniformBindingPoints.dem);
220
230
  gl.bindVertexArray(null);
221
231
  gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
@@ -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,15 +110,19 @@ 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
  }
117
+ else {
118
+ this._uboHandler.updateSingle("private_isPickedOn", new Float32Array([0.0]));
119
+ }
117
120
  if (this._options.drawEdges) {
118
121
  this._uboForRealEdgeArcs = this._program.createUBO();
119
122
  this._uboForRealEdgeArcs.updateSingle("defaultColor", new Float32Array(this._options.edgeStyle.defaultColor));
120
123
  this._uboForRealEdgeArcs.updateSingle("useDefaultColor", new Float32Array([1]));
121
124
  this._uboForRealEdgeArcs.updateSingle("opacity", this._effectiveOpacity("edgeArc"));
125
+ this._uboForRealEdgeArcs.updateSingle("private_isPickedOn", new Float32Array([0.0]));
122
126
  this._drawRealEdgeArcs.elementBuffer = gl.createBuffer();
123
127
  }
124
128
  if (this._options.variativeColorsOn) {
@@ -143,7 +147,6 @@ export class TerrainPolygonSemiPlugin {
143
147
  } : undefined);
144
148
  this._drawRangeIndexParams.elementBuffer = this._elementArrayBuffer;
145
149
  this._workerContact = new WorkerContact(this.globe, this._options, (result) => {
146
- // console.log('TerrainPolygonSemiPlugin received data from worker', result);
147
150
  this.setBuffers(result);
148
151
  });
149
152
  this._drawRangeIndexParams.elementBuffer = this._elementArrayBuffer;
@@ -290,30 +293,6 @@ export class TerrainPolygonSemiPlugin {
290
293
  if (this._options.variativeColorsOn && data.variativeColors) {
291
294
  safeVertexCount = Math.min(safeVertexCount, Math.floor(data.variativeColors.length / 4));
292
295
  }
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
296
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._elementArrayBuffer);
318
297
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data.indices, gl.STREAM_DRAW);
319
298
  gl.bindBuffer(gl.ARRAY_BUFFER, this._vec3Buffer);
@@ -378,7 +357,7 @@ export class TerrainPolygonSemiPlugin {
378
357
  if (this._lastPickedPolygon !== null) {
379
358
  this._lastPickedPolygon = null;
380
359
  globe.DrawRender();
381
- this._uboHandler.updateSingle("private_pickedIndex", new Float32Array([-1]));
360
+ this._uboHandler.updateSingle("private_pickedIndex", new Float32Array([IndexAttributeEscapeValue]));
382
361
  }
383
362
  }
384
363
  });
@@ -1,27 +1,32 @@
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
+ // WebGL returns -1 for inactive attributes (optimized out). Treat as a no-op.
12
+ // This also avoids accidentally mutating global generic attribute state.
13
+ if (index < 0) {
14
+ return;
15
+ }
16
+ const typeInfo = dataType ? DATA_TYPE_MAP[dataType] : DATA_TYPE_MAP.float;
22
17
  if (bufferAndReadInfo == null || bufferAndReadInfo.buffer == null) {
23
- if (escapeValues)
24
- constantFunction(gl, index, size, escapeValues, integer, integerUnsigned);
18
+ // Disable array and set constant value
19
+ gl.disableVertexAttribArray(index);
20
+ // If the shader expects an integer attribute (in uint/int/ivec*/uvec*),
21
+ // WebGL requires the constant to be set via vertexAttribI4i/vertexAttribI4ui.
22
+ // Otherwise you'll hit: "Vertex shader input type does not match...".
23
+ if (escapeValues != null) {
24
+ setConstantAttribute(gl, index, size, escapeValues, typeInfo);
25
+ }
26
+ else if (typeInfo.useIntegerPointer) {
27
+ // Default constant for integer attributes when not provided.
28
+ setConstantAttribute(gl, index, size, [0, 0, 0, 0], typeInfo);
29
+ }
25
30
  return;
26
31
  }
27
32
  const { buffer, stride, offset } = bufferAndReadInfo;
@@ -34,10 +39,10 @@ const attributeLoader = (gl, bufferAndReadInfo, index, size, { divisor = null, t
34
39
  if (stride < 0 || offset < 0) {
35
40
  throw new Error("Stride and offset must be non-negative");
36
41
  }
37
- const attribType = type === null ? gl.FLOAT : type;
42
+ const attribType = typeInfo.getGLType(gl);
38
43
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
39
44
  gl.enableVertexAttribArray(index);
40
- if (integer) {
45
+ if (typeInfo.useIntegerPointer) {
41
46
  gl.vertexAttribIPointer(index, size, attribType, stride, offset);
42
47
  }
43
48
  else {
@@ -59,16 +64,34 @@ const createBufferAndReadInfo = (buffer, stride = 0, offset = 0) => {
59
64
  return null;
60
65
  return { buffer, stride, offset };
61
66
  };
62
- const constantFunction = (gl, index, size, escapeValues, integer, integerUnsigned) => {
63
- if (!integer) {
64
- const func = `vertexAttrib${size}f`;
67
+ const setConstantAttribute = (gl, index, size, values, typeInfo) => {
68
+ // Array should already be disabled by caller
69
+ if (size < 1 || size > 4) {
70
+ throw new Error(`Attribute size must be between 1 and 4. Got ${size}.`);
71
+ }
72
+ if (typeInfo.useIntegerPointer) {
73
+ return;
74
+ gl.vertexAttrib4f(index, 0, 0, 0, 1);
75
+ // Integer attributes: WebGL2 ONLY supports vertexAttribI4i/I4ui for constants.
76
+ const intValues = values.map((v) => Math.trunc(v));
77
+ while (intValues.length < 4)
78
+ intValues.push(0);
79
+ const func = typeInfo.isSigned ? "vertexAttribI4i" : "vertexAttribI4ui";
65
80
  // @ts-ignore
66
- gl[func](index, ...escapeValues);
81
+ gl[func](index, intValues[0], intValues[1], intValues[2], intValues[3]);
67
82
  return;
68
83
  }
69
- const values = escapeValues.map((v) => Math.trunc(v));
70
- const func = integerUnsigned ? `vertexAttribI${size}ui` : `vertexAttribI${size}i`;
71
- // @ts-ignore
72
- gl[func](index, ...values);
84
+ // Float attributes: use vertexAttrib{1..4}f.
85
+ const v = values.slice(0, 4);
86
+ while (v.length < 4)
87
+ v.push(size === 4 ? 0 : (v.length === 3 ? 1 : 0));
88
+ if (size === 1)
89
+ gl.vertexAttrib1f(index, v[0]);
90
+ else if (size === 2)
91
+ gl.vertexAttrib2f(index, v[0], v[1]);
92
+ else if (size === 3)
93
+ gl.vertexAttrib3f(index, v[0], v[1], v[2]);
94
+ else
95
+ gl.vertexAttrib4f(index, v[0], v[1], v[2], v[3]);
73
96
  };
74
97
  export { attributeLoader, createBufferAndReadInfo };
@@ -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
  }