@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 +1 -1
- package/programs/polygon-on-globe/texture-dem-triangles.js +34 -24
- package/semiplugins/shape-on-terrain/terrain-polygon/terrain-polygon.js +7 -28
- package/util/gl-util/buffer/attribute-loader.js +56 -33
- 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
|
+
// 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: "
|
|
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
|
|
@@ -45,7 +46,7 @@ in uvec4 a_color;
|
|
|
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
|
|
|
@@ -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 ==
|
|
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 ==
|
|
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 =
|
|
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
|
|
117
|
+
flat in uint v_index; // index from vertex shader
|
|
113
118
|
layout(location = 0) out vec4 outColor;
|
|
114
|
-
layout(location = 1) out
|
|
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
|
-
|
|
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
|
-
|
|
213
|
-
|
|
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
|
-
|
|
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, "
|
|
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([
|
|
360
|
+
this._uboHandler.updateSingle("private_pickedIndex", new Float32Array([IndexAttributeEscapeValue]));
|
|
382
361
|
}
|
|
383
362
|
}
|
|
384
363
|
});
|
|
@@ -1,27 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
24
|
-
|
|
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 =
|
|
42
|
+
const attribType = typeInfo.getGLType(gl);
|
|
38
43
|
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
39
44
|
gl.enableVertexAttribArray(index);
|
|
40
|
-
if (
|
|
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
|
|
63
|
-
|
|
64
|
-
|
|
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,
|
|
81
|
+
gl[func](index, intValues[0], intValues[1], intValues[2], intValues[3]);
|
|
67
82
|
return;
|
|
68
83
|
}
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
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;
|
|
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
|
}
|