@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 +1 -1
- package/programs/polygon-on-globe/texture-dem-triangles.js +14 -15
- package/semiplugins/shape-on-terrain/terrain-polygon/data/master-worker.js +1 -1
- package/semiplugins/shape-on-terrain/terrain-polygon/data/worker.js +1 -1
- package/semiplugins/shape-on-terrain/terrain-polygon/terrain-polygon.js +3 -28
- package/util/gl-util/buffer/attribute-loader.js +17 -27
- package/util/picking/picker-displayer-uint.js +172 -0
- package/util/picking/picker-displayer.js +76 -30
package/package.json
CHANGED
|
@@ -14,15 +14,16 @@ const uniformBindingPoints = {
|
|
|
14
14
|
dem: 2,
|
|
15
15
|
selection: 3,
|
|
16
16
|
};
|
|
17
|
-
|
|
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: "
|
|
24
|
-
{ name: "private_pickedIndex", type: "
|
|
25
|
-
{ name: "useDefaultColor", type: "
|
|
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
|
|
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
|
|
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 ==
|
|
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 ==
|
|
91
|
+
if (private_isPickedOn == true){
|
|
91
92
|
if (a_index == private_pickedIndex) {
|
|
92
93
|
v_color = pickedColor;
|
|
93
94
|
}
|
|
94
|
-
v_index =
|
|
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
|
|
113
|
+
flat in uint v_index; // index from vertex shader
|
|
113
114
|
layout(location = 0) out vec4 outColor;
|
|
114
|
-
layout(location = 1) out
|
|
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
|
-
|
|
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
|
|
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
|
|
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, "
|
|
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([
|
|
356
|
+
this._uboHandler.updateSingle("private_pickedIndex", new Float32Array([IndexAttributeEscapeValue]));
|
|
382
357
|
}
|
|
383
358
|
}
|
|
384
359
|
});
|
|
@@ -1,27 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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,
|
|
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 =
|
|
27
|
+
const attribType = typeInfo.getGLType(gl);
|
|
38
28
|
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
39
29
|
gl.enableVertexAttribArray(index);
|
|
40
|
-
if (
|
|
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,
|
|
63
|
-
if (!
|
|
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 =
|
|
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;
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
192
|
-
|
|
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
|
|
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
|
|
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 !==
|
|
272
|
+
if (id !== esc) {
|
|
227
273
|
selectedObjects.add(id);
|
|
228
274
|
}
|
|
229
275
|
}
|
|
230
276
|
return selectedObjects;
|
|
231
277
|
}
|
|
232
|
-
_initHoldBuffer(
|
|
233
|
-
if (this._pbo && this._pboSize >=
|
|
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,
|
|
285
|
+
gl.bufferData(gl.PIXEL_PACK_BUFFER, sizeInBytes, gl.STREAM_READ);
|
|
240
286
|
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
|
|
241
|
-
this._pboSize =
|
|
287
|
+
this._pboSize = sizeInBytes;
|
|
242
288
|
if (this._pbo !== undefined) {
|
|
243
289
|
gl.deleteBuffer(this._pbo);
|
|
244
290
|
}
|