@babylonjs/core 9.12.1 → 9.13.0
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/Collisions/gpuPicker.d.ts +111 -4
- package/Collisions/gpuPicker.js +673 -106
- package/Collisions/gpuPicker.js.map +1 -1
- package/Engines/Native/nativeInterfaces.d.ts +1 -0
- package/Engines/Native/nativeInterfaces.js.map +1 -1
- package/Engines/abstractEngine.pure.js +2 -2
- package/Engines/abstractEngine.pure.js.map +1 -1
- package/Engines/nullEngine.pure.js +5 -1
- package/Engines/nullEngine.pure.js.map +1 -1
- package/Engines/thinNativeEngine.pure.d.ts +3 -0
- package/Engines/thinNativeEngine.pure.js +43 -4
- package/Engines/thinNativeEngine.pure.js.map +1 -1
- package/Loading/sceneLoader.d.ts +5 -1
- package/Loading/sceneLoader.js +298 -401
- package/Loading/sceneLoader.js.map +1 -1
- package/Materials/GaussianSplatting/gaussianSplattingGpuPickingMaterialPlugin.pure.d.ts +15 -0
- package/Materials/GaussianSplatting/gaussianSplattingGpuPickingMaterialPlugin.pure.js +34 -1
- package/Materials/GaussianSplatting/gaussianSplattingGpuPickingMaterialPlugin.pure.js.map +1 -1
- package/Materials/GaussianSplatting/gaussianSplattingMaterial.pure.d.ts +9 -0
- package/Materials/GaussianSplatting/gaussianSplattingMaterial.pure.js +26 -2
- package/Materials/GaussianSplatting/gaussianSplattingMaterial.pure.js.map +1 -1
- package/Materials/Node/Blocks/GaussianSplatting/gaussianSplattingBlock.pure.js +3 -1
- package/Materials/Node/Blocks/GaussianSplatting/gaussianSplattingBlock.pure.js.map +1 -1
- package/Meshes/GaussianSplatting/gaussianSplattingMeshBase.pure.d.ts +27 -0
- package/Meshes/GaussianSplatting/gaussianSplattingMeshBase.pure.js +44 -0
- package/Meshes/GaussianSplatting/gaussianSplattingMeshBase.pure.js.map +1 -1
- package/Meshes/GaussianSplatting/gaussianSplattingSortWorker.d.ts +2 -0
- package/Meshes/GaussianSplatting/gaussianSplattingSortWorker.js +195 -47
- package/Meshes/GaussianSplatting/gaussianSplattingSortWorker.js.map +1 -1
- package/Misc/tools.pure.js +1 -1
- package/Misc/tools.pure.js.map +1 -1
- package/Shaders/ShadersInclude/gaussianSplatting.js +1 -0
- package/Shaders/ShadersInclude/gaussianSplatting.js.map +1 -1
- package/Shaders/gaussianSplatting.fragment.js +17 -1
- package/Shaders/gaussianSplatting.fragment.js.map +1 -1
- package/Shaders/gaussianSplatting.vertex.js +1 -1
- package/Shaders/gaussianSplatting.vertex.js.map +1 -1
- package/Shaders/gaussianSplattingDepth.vertex.js +1 -1
- package/Shaders/gaussianSplattingDepth.vertex.js.map +1 -1
- package/Shaders/picking.fragment.js +39 -3
- package/Shaders/picking.fragment.js.map +1 -1
- package/ShadersWGSL/ShadersInclude/gaussianSplatting.js +3 -1
- package/ShadersWGSL/ShadersInclude/gaussianSplatting.js.map +1 -1
- package/ShadersWGSL/gaussianSplatting.fragment.js +14 -1
- package/ShadersWGSL/gaussianSplatting.fragment.js.map +1 -1
- package/ShadersWGSL/gaussianSplatting.vertex.js +2 -2
- package/ShadersWGSL/gaussianSplatting.vertex.js.map +1 -1
- package/ShadersWGSL/gaussianSplattingDepth.vertex.js +2 -2
- package/ShadersWGSL/gaussianSplattingDepth.vertex.js.map +1 -1
- package/ShadersWGSL/picking.fragment.js +22 -1
- package/ShadersWGSL/picking.fragment.js.map +1 -1
- package/package.json +1 -1
package/Collisions/gpuPicker.js
CHANGED
|
@@ -1,14 +1,35 @@
|
|
|
1
1
|
|
|
2
|
+
import { MultiRenderTarget } from "../Materials/Textures/multiRenderTarget.pure.js";
|
|
2
3
|
import { RenderTargetTexture } from "../Materials/Textures/renderTargetTexture.pure.js";
|
|
3
4
|
import { ShaderMaterial } from "../Materials/shaderMaterial.pure.js";
|
|
4
5
|
import { GaussianSplattingMaterial } from "../Materials/GaussianSplatting/gaussianSplattingMaterial.pure.js";
|
|
5
6
|
import { GaussianSplattingGpuPickingMaterialPlugin } from "../Materials/GaussianSplatting/gaussianSplattingGpuPickingMaterialPlugin.pure.js";
|
|
6
7
|
import { Color4 } from "../Maths/math.color.pure.js";
|
|
8
|
+
import { Epsilon } from "../Maths/math.constants.js";
|
|
9
|
+
import { Matrix, TmpVectors, Vector3 } from "../Maths/math.vector.pure.js";
|
|
7
10
|
import { VertexBuffer } from "../Meshes/buffer.js";
|
|
8
11
|
import { Logger } from "../Misc/logger.js";
|
|
12
|
+
/**
|
|
13
|
+
* Defines how multi pick texture readbacks should be performed.
|
|
14
|
+
*/
|
|
15
|
+
export var GPUMultiPickReadbackStrategy;
|
|
16
|
+
(function (GPUMultiPickReadbackStrategy) {
|
|
17
|
+
/**
|
|
18
|
+
* Chooses between a single rectangle readback and small per-point readbacks using the thresholds in IGPUMultiPickOptions.
|
|
19
|
+
*/
|
|
20
|
+
GPUMultiPickReadbackStrategy[GPUMultiPickReadbackStrategy["Auto"] = 0] = "Auto";
|
|
21
|
+
/**
|
|
22
|
+
* Always reads the full bounding rectangle of the picked points. This minimizes readback calls and is best for dense point sets.
|
|
23
|
+
*/
|
|
24
|
+
GPUMultiPickReadbackStrategy[GPUMultiPickReadbackStrategy["Rectangle"] = 1] = "Rectangle";
|
|
25
|
+
/**
|
|
26
|
+
* Always reads each picked point independently. This minimizes transferred pixels for sparse point sets but can be slower when many points are picked.
|
|
27
|
+
*/
|
|
28
|
+
GPUMultiPickReadbackStrategy[GPUMultiPickReadbackStrategy["Individual"] = 2] = "Individual";
|
|
29
|
+
})(GPUMultiPickReadbackStrategy || (GPUMultiPickReadbackStrategy = {}));
|
|
9
30
|
/**
|
|
10
31
|
* Class used to perform a picking operation using GPU
|
|
11
|
-
*
|
|
32
|
+
* GPUPicker can pick meshes, instances and thin instances
|
|
12
33
|
*/
|
|
13
34
|
export class GPUPicker {
|
|
14
35
|
constructor() {
|
|
@@ -23,10 +44,16 @@ export class GPUPicker {
|
|
|
23
44
|
this._pickableMeshes = [];
|
|
24
45
|
this._meshMaterialMap = new Map();
|
|
25
46
|
this._readbuffer = null;
|
|
47
|
+
this._depthReadbuffer = null;
|
|
48
|
+
this._depthTextureType = 0;
|
|
49
|
+
this._isDepthTexturePacked = false;
|
|
50
|
+
this._useDepthPicking = false;
|
|
51
|
+
this._isUsingDepthPickingRenderTarget = false;
|
|
26
52
|
this._meshRenderingCount = 0;
|
|
27
53
|
this._renderWarningIssued = false;
|
|
28
54
|
this._renderPickingTexture = false;
|
|
29
55
|
this._sceneBeforeRenderObserver = null;
|
|
56
|
+
this._pickingTextureClearObserver = null;
|
|
30
57
|
this._pickingTextureAfterRenderObserver = null;
|
|
31
58
|
this._nextFreeId = 1;
|
|
32
59
|
this._gsPickingMaterials = [];
|
|
@@ -55,12 +82,68 @@ export class GPUPicker {
|
|
|
55
82
|
get defaultRenderMaterials() {
|
|
56
83
|
return this._pickingMaterialCache;
|
|
57
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Gets or sets a boolean indicating if depth-based pickedPoint and normal reconstruction should be enabled.
|
|
87
|
+
*
|
|
88
|
+
* When disabled, GPUPicker uses the original single-color render target and shader path. When enabled, GPUPicker
|
|
89
|
+
* switches to a MultiRenderTarget and compiles the default picking shader with GPUPICKER_DEPTH to output both the
|
|
90
|
+
* picking id and the depth required to reconstruct the picked point and normal.
|
|
91
|
+
*
|
|
92
|
+
* Custom picking materials and special picking material plugins should also write the depth attachment. If they do
|
|
93
|
+
* not, GPUPicker will still try to reconstruct pickedPoint and normal from the depth target, but the returned values
|
|
94
|
+
* may be missing or incorrect.
|
|
95
|
+
*/
|
|
96
|
+
get enableDepthPicking() {
|
|
97
|
+
return this._useDepthPicking;
|
|
98
|
+
}
|
|
99
|
+
set enableDepthPicking(value) {
|
|
100
|
+
if (this._useDepthPicking === value) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
this._useDepthPicking = value;
|
|
104
|
+
this._isUsingDepthPickingRenderTarget = false;
|
|
105
|
+
this._depthReadbuffer = null;
|
|
106
|
+
let pickableMeshes = [];
|
|
107
|
+
if (this._cachedScene && this._pickingTexture) {
|
|
108
|
+
pickableMeshes = this._pickableMeshes.map((mesh) => {
|
|
109
|
+
const material = this._meshMaterialMap.get(mesh);
|
|
110
|
+
const className = mesh.getClassName();
|
|
111
|
+
if (material instanceof ShaderMaterial &&
|
|
112
|
+
!this._pickingMaterialCache.includes(material) &&
|
|
113
|
+
className !== "GaussianSplattingMesh" &&
|
|
114
|
+
className !== "GaussianSplattingPartProxyMesh") {
|
|
115
|
+
return { mesh, material };
|
|
116
|
+
}
|
|
117
|
+
return mesh;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
this._clearPickingMaterials();
|
|
121
|
+
if (this._cachedScene && this._pickingTexture) {
|
|
122
|
+
this.clearPickingList();
|
|
123
|
+
this._pickingTexture.dispose();
|
|
124
|
+
this._pickingTexture = null;
|
|
125
|
+
if (pickableMeshes.length > 0) {
|
|
126
|
+
this.addPickingList(pickableMeshes);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
58
130
|
_getColorIdFromReadBuffer(offset) {
|
|
59
131
|
const r = this._readbuffer[offset];
|
|
60
132
|
const g = this._readbuffer[offset + 1];
|
|
61
133
|
const b = this._readbuffer[offset + 2];
|
|
62
134
|
return (r << 16) + (g << 8) + b;
|
|
63
135
|
}
|
|
136
|
+
_getReadBufferOffset(x, y, width, height) {
|
|
137
|
+
const bufferY = this._cachedScene?.getEngine().isWebGPU ? height - y - 1 : y;
|
|
138
|
+
return (bufferY * width + x) * 4;
|
|
139
|
+
}
|
|
140
|
+
_createColorPickingRenderTarget(scene, width, height) {
|
|
141
|
+
return new RenderTargetTexture("pickingTexture", { width: width, height: height }, scene, {
|
|
142
|
+
generateMipMaps: false,
|
|
143
|
+
type: 0,
|
|
144
|
+
samplingMode: 1,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
64
147
|
_createRenderTarget(scene, width, height) {
|
|
65
148
|
if (this._cachedScene && this._pickingTexture) {
|
|
66
149
|
const index = this._cachedScene.customRenderTargets.indexOf(this._pickingTexture);
|
|
@@ -72,11 +155,53 @@ export class GPUPicker {
|
|
|
72
155
|
if (this._pickingTexture) {
|
|
73
156
|
this._pickingTexture.dispose();
|
|
74
157
|
}
|
|
75
|
-
this.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
158
|
+
if (this._useDepthPicking) {
|
|
159
|
+
const engine = scene.getEngine();
|
|
160
|
+
const supportsDepthPickingRenderTarget = engine.isWebGPU || engine.webGLVersion !== 1;
|
|
161
|
+
if (!supportsDepthPickingRenderTarget) {
|
|
162
|
+
Logger.Warn("GPUPicker depth picking requires WebGL2, WebGPU, or Native engine support. Falling back to color-only GPU picking.");
|
|
163
|
+
this._useDepthPicking = false;
|
|
164
|
+
this._isUsingDepthPickingRenderTarget = false;
|
|
165
|
+
this._isDepthTexturePacked = false;
|
|
166
|
+
this._pickingTexture = this._createColorPickingRenderTarget(scene, width, height);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (engine.getCaps().textureFloatRender) {
|
|
170
|
+
this._depthTextureType = 1;
|
|
171
|
+
}
|
|
172
|
+
else if (engine.getCaps().textureHalfFloatRender) {
|
|
173
|
+
this._depthTextureType = 2;
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
this._depthTextureType = 0;
|
|
177
|
+
}
|
|
178
|
+
this._isDepthTexturePacked = this._depthTextureType === 0;
|
|
179
|
+
const pickingTexture = new MultiRenderTarget("pickingTexture", { width: width, height: height }, 2, scene, {
|
|
180
|
+
generateMipMaps: false,
|
|
181
|
+
generateDepthBuffer: true,
|
|
182
|
+
generateStencilBuffer: false,
|
|
183
|
+
types: [0, this._depthTextureType],
|
|
184
|
+
samplingModes: [1, 1],
|
|
185
|
+
formats: [5, 5],
|
|
186
|
+
}, ["pickingTexture_id", "pickingTexture_depth"]);
|
|
187
|
+
if (pickingTexture.isSupported) {
|
|
188
|
+
this._pickingTexture = pickingTexture;
|
|
189
|
+
this._isUsingDepthPickingRenderTarget = true;
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
Logger.Warn("GPUPicker depth picking requires MultiRenderTarget support. Falling back to color-only GPU picking.");
|
|
193
|
+
this._useDepthPicking = false;
|
|
194
|
+
this._isUsingDepthPickingRenderTarget = false;
|
|
195
|
+
this._isDepthTexturePacked = false;
|
|
196
|
+
pickingTexture.dispose();
|
|
197
|
+
this._pickingTexture = this._createColorPickingRenderTarget(scene, width, height);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
this._pickingTexture = this._createColorPickingRenderTarget(scene, width, height);
|
|
202
|
+
this._isUsingDepthPickingRenderTarget = false;
|
|
203
|
+
this._isDepthTexturePacked = false;
|
|
204
|
+
}
|
|
80
205
|
}
|
|
81
206
|
_clearPickingMaterials() {
|
|
82
207
|
for (let i = 0; i < this._pickingMaterialCache.length; i++) {
|
|
@@ -100,6 +225,12 @@ export class GPUPicker {
|
|
|
100
225
|
this._shaderLanguage = 1 /* ShaderLanguage.WGSL */;
|
|
101
226
|
}
|
|
102
227
|
const defines = [];
|
|
228
|
+
if (this._useDepthPicking) {
|
|
229
|
+
defines.push("#define GPUPICKER_DEPTH");
|
|
230
|
+
if (this._isDepthTexturePacked) {
|
|
231
|
+
defines.push("#define GPUPICKER_PACK_DEPTH");
|
|
232
|
+
}
|
|
233
|
+
}
|
|
103
234
|
const options = {
|
|
104
235
|
attributes: [VertexBuffer.PositionKind, GPUPicker._AttributeName],
|
|
105
236
|
uniforms: ["world", "viewProjection", "meshID"],
|
|
@@ -429,25 +560,28 @@ export class GPUPicker {
|
|
|
429
560
|
if (!this._pickableMeshes || this._pickableMeshes.length === 0) {
|
|
430
561
|
return null;
|
|
431
562
|
}
|
|
432
|
-
const { rttSizeW, rttSizeH,
|
|
433
|
-
const { x: adjustedX, y: adjustedY } = this._prepareForPicking(x, y,
|
|
563
|
+
const { rttSizeW, rttSizeH, scaleX, scaleY } = this._getRenderInfo();
|
|
564
|
+
const { x: adjustedX, y: adjustedY } = this._prepareForPicking(x, y, scaleX, scaleY);
|
|
434
565
|
if (adjustedX < 0 || adjustedY < 0 || adjustedX >= rttSizeW || adjustedY >= rttSizeH) {
|
|
435
566
|
return null;
|
|
436
567
|
}
|
|
437
568
|
this._pickingInProgress = true;
|
|
438
569
|
// Invert Y
|
|
439
570
|
const invertedY = rttSizeH - adjustedY - 1;
|
|
440
|
-
this.
|
|
571
|
+
const pickingRegion = this._getPickingRenderRegion(adjustedX, invertedY, rttSizeW, rttSizeH);
|
|
572
|
+
this._preparePickingBuffer(this._engine, rttSizeW, rttSizeH, pickingRegion.x, pickingRegion.y, pickingRegion.width, pickingRegion.height);
|
|
441
573
|
await this._waitForPickingMaterialsReadyAsync();
|
|
574
|
+
this._addPickingTextureToRenderTargets();
|
|
442
575
|
return await this._executePickingAsync(adjustedX, invertedY, disposeWhenDone);
|
|
443
576
|
}
|
|
444
577
|
/**
|
|
445
578
|
* Execute a picking operation on multiple coordinates
|
|
446
579
|
* @param xy defines the X,Y coordinates where to run the pick
|
|
447
580
|
* @param disposeWhenDone defines a boolean indicating we do not want to keep resources alive (false by default)
|
|
581
|
+
* @param options defines options used to tune the multi pick readback strategy
|
|
448
582
|
* @returns A promise with the picking results. Always returns an array with the same length as the number of coordinates. The mesh or null at the index where no mesh was picked.
|
|
449
583
|
*/
|
|
450
|
-
async multiPickAsync(xy, disposeWhenDone = false) {
|
|
584
|
+
async multiPickAsync(xy, disposeWhenDone = false, options) {
|
|
451
585
|
if (this._pickingInProgress) {
|
|
452
586
|
return null;
|
|
453
587
|
}
|
|
@@ -456,10 +590,15 @@ export class GPUPicker {
|
|
|
456
590
|
}
|
|
457
591
|
if (xy.length === 1) {
|
|
458
592
|
const pi = await this.pickAsync(xy[0].x, xy[0].y, disposeWhenDone);
|
|
459
|
-
|
|
593
|
+
const result = {
|
|
460
594
|
meshes: [pi?.mesh ?? null],
|
|
461
|
-
thinInstanceIndexes: pi?.thinInstanceIndex ? [pi.thinInstanceIndex] : undefined,
|
|
595
|
+
thinInstanceIndexes: pi?.thinInstanceIndex !== undefined ? [pi.thinInstanceIndex] : undefined,
|
|
462
596
|
};
|
|
597
|
+
if (this._useDepthPicking) {
|
|
598
|
+
result.pickedPoints = [pi?.pickedPoint ?? null];
|
|
599
|
+
result.normals = [pi?.normal ?? null];
|
|
600
|
+
}
|
|
601
|
+
return result;
|
|
463
602
|
}
|
|
464
603
|
this._pickingInProgress = true;
|
|
465
604
|
const processedXY = new Array(xy.length);
|
|
@@ -467,28 +606,50 @@ export class GPUPicker {
|
|
|
467
606
|
let maxX = -Infinity;
|
|
468
607
|
let minY = Infinity;
|
|
469
608
|
let maxY = -Infinity;
|
|
470
|
-
const { rttSizeW, rttSizeH,
|
|
609
|
+
const { rttSizeW, rttSizeH, scaleX, scaleY } = this._getRenderInfo();
|
|
610
|
+
let hasInBoundsPoint = false;
|
|
611
|
+
let inBoundsPointCount = 0;
|
|
471
612
|
// Process screen coordinates adjust to dpr
|
|
472
613
|
for (let i = 0; i < xy.length; i++) {
|
|
473
614
|
const item = xy[i];
|
|
474
615
|
const { x, y } = item;
|
|
475
|
-
const { x: adjustedX, y: adjustedY } = this._prepareForPicking(x, y,
|
|
616
|
+
const { x: adjustedX, y: adjustedY } = this._prepareForPicking(x, y, scaleX, scaleY);
|
|
476
617
|
processedXY[i] = {
|
|
477
618
|
...item,
|
|
478
619
|
x: adjustedX,
|
|
479
620
|
y: adjustedY,
|
|
480
621
|
};
|
|
622
|
+
if (adjustedX < 0 || adjustedY < 0 || adjustedX >= rttSizeW || adjustedY >= rttSizeH) {
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
hasInBoundsPoint = true;
|
|
626
|
+
inBoundsPointCount++;
|
|
481
627
|
minX = Math.min(minX, adjustedX);
|
|
482
628
|
maxX = Math.max(maxX, adjustedX);
|
|
483
629
|
minY = Math.min(minY, adjustedY);
|
|
484
630
|
maxY = Math.max(maxY, adjustedY);
|
|
485
631
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
632
|
+
if (!hasInBoundsPoint) {
|
|
633
|
+
this._pickingInProgress = false;
|
|
634
|
+
return null;
|
|
635
|
+
}
|
|
636
|
+
const depthPadding = this._useDepthPicking ? GPUPicker._DepthPixelRadius : 0;
|
|
637
|
+
const regionLeft = Math.max(minX - depthPadding, 0);
|
|
638
|
+
const regionRight = Math.min(maxX + depthPadding, rttSizeW - 1);
|
|
639
|
+
const regionTop = Math.max(minY - depthPadding, 0);
|
|
640
|
+
const regionBottom = Math.min(maxY + depthPadding, rttSizeH - 1);
|
|
641
|
+
if (regionLeft >= rttSizeW || regionTop >= rttSizeH || regionRight < 0 || regionBottom < 0) {
|
|
642
|
+
this._pickingInProgress = false;
|
|
643
|
+
return null;
|
|
644
|
+
}
|
|
645
|
+
const w = regionRight - regionLeft + 1;
|
|
646
|
+
const h = regionBottom - regionTop + 1;
|
|
647
|
+
const partialCutH = rttSizeH - regionBottom - 1;
|
|
648
|
+
const useIndividualReadback = this._shouldUseIndividualMultiPickReadback(inBoundsPointCount, w * h, options);
|
|
649
|
+
this._preparePickingBuffer(this._engine, rttSizeW, rttSizeH, regionLeft, partialCutH, w, h, useIndividualReadback ? 1 : w, useIndividualReadback ? 1 : h);
|
|
490
650
|
await this._waitForPickingMaterialsReadyAsync();
|
|
491
|
-
|
|
651
|
+
this._addPickingTextureToRenderTargets();
|
|
652
|
+
return await this._executeMultiPickingAsync(processedXY, regionLeft, partialCutH, rttSizeW, rttSizeH, w, h, useIndividualReadback, disposeWhenDone);
|
|
492
653
|
}
|
|
493
654
|
/**
|
|
494
655
|
* Execute a picking operation on box defined by two screen coordinates
|
|
@@ -497,7 +658,7 @@ export class GPUPicker {
|
|
|
497
658
|
* @param x2 defines the X coordinate of the opposite corner of the box where to run the pick
|
|
498
659
|
* @param y2 defines the Y coordinate of the opposite corner of the box where to run the pick
|
|
499
660
|
* @param disposeWhenDone defines a boolean indicating we do not want to keep resources alive (false by default)
|
|
500
|
-
* @returns A promise with the picking results.
|
|
661
|
+
* @returns A promise with the picking results. Contains one entry for each picked pixel in the box.
|
|
501
662
|
*/
|
|
502
663
|
async boxPickAsync(x1, y1, x2, y2, disposeWhenDone = false) {
|
|
503
664
|
if (this._pickingInProgress) {
|
|
@@ -507,9 +668,9 @@ export class GPUPicker {
|
|
|
507
668
|
return null;
|
|
508
669
|
}
|
|
509
670
|
this._pickingInProgress = true;
|
|
510
|
-
const { rttSizeW, rttSizeH,
|
|
511
|
-
const { x: adjustedX1, y: adjustedY1 } = this._prepareForPicking(x1, y1,
|
|
512
|
-
const { x: adjustedX2, y: adjustedY2 } = this._prepareForPicking(x2, y2,
|
|
671
|
+
const { rttSizeW, rttSizeH, scaleX, scaleY } = this._getRenderInfo();
|
|
672
|
+
const { x: adjustedX1, y: adjustedY1 } = this._prepareForPicking(x1, y1, scaleX, scaleY);
|
|
673
|
+
const { x: adjustedX2, y: adjustedY2 } = this._prepareForPicking(x2, y2, scaleX, scaleY);
|
|
513
674
|
const minX = Math.max(Math.min(adjustedX1, adjustedX2), 0);
|
|
514
675
|
const maxX = Math.min(Math.max(adjustedX1, adjustedX2), rttSizeW - 1);
|
|
515
676
|
const minY = Math.max(Math.min(adjustedY1, adjustedY2), 0);
|
|
@@ -518,30 +679,84 @@ export class GPUPicker {
|
|
|
518
679
|
this._pickingInProgress = false;
|
|
519
680
|
return null;
|
|
520
681
|
}
|
|
521
|
-
const
|
|
522
|
-
const
|
|
523
|
-
const
|
|
524
|
-
|
|
682
|
+
const depthPadding = this._useDepthPicking ? GPUPicker._DepthPixelRadius : 0;
|
|
683
|
+
const regionLeft = Math.max(minX - depthPadding, 0);
|
|
684
|
+
const regionRight = Math.min(maxX + depthPadding, rttSizeW - 1);
|
|
685
|
+
const regionTop = Math.max(minY - depthPadding, 0);
|
|
686
|
+
const regionBottom = Math.min(maxY + depthPadding, rttSizeH - 1);
|
|
687
|
+
const w = regionRight - regionLeft + 1;
|
|
688
|
+
const h = regionBottom - regionTop + 1;
|
|
689
|
+
const partialCutH = rttSizeH - regionBottom - 1;
|
|
690
|
+
this._preparePickingBuffer(this._engine, rttSizeW, rttSizeH, regionLeft, partialCutH, w, h);
|
|
525
691
|
await this._waitForPickingMaterialsReadyAsync();
|
|
526
|
-
|
|
692
|
+
this._addPickingTextureToRenderTargets();
|
|
693
|
+
return await this._executeBoxPickingAsync(minX, maxY, Math.max(maxX - minX, 1), Math.max(maxY - minY, 1), regionLeft, partialCutH, rttSizeW, rttSizeH, w, h, disposeWhenDone);
|
|
527
694
|
}
|
|
528
695
|
_getRenderInfo() {
|
|
529
696
|
const engine = this._cachedScene.getEngine();
|
|
530
697
|
const rttSizeW = engine.getRenderWidth();
|
|
531
698
|
const rttSizeH = engine.getRenderHeight();
|
|
532
|
-
|
|
699
|
+
// Picking coordinates are expected in input/canvas CSS pixels, matching Babylon pointer coordinates.
|
|
700
|
+
// Use the actual client rect so CSS scaling/stretching is handled, and fall back to hardware scaling
|
|
701
|
+
// for engines without a DOM input element.
|
|
702
|
+
let inputElementClientRect = null;
|
|
703
|
+
try {
|
|
704
|
+
inputElementClientRect = engine.getInputElementClientRect();
|
|
705
|
+
}
|
|
706
|
+
catch {
|
|
707
|
+
// Non-DOM or pure engine builds may not register the DOM side effect. Fall back below.
|
|
708
|
+
}
|
|
709
|
+
const scaleX = inputElementClientRect?.width ? rttSizeW / inputElementClientRect.width : 1 / engine._hardwareScalingLevel;
|
|
710
|
+
const scaleY = inputElementClientRect?.height ? rttSizeH / inputElementClientRect.height : 1 / engine._hardwareScalingLevel;
|
|
533
711
|
return {
|
|
534
712
|
rttSizeW,
|
|
535
713
|
rttSizeH,
|
|
536
|
-
|
|
714
|
+
scaleX,
|
|
715
|
+
scaleY,
|
|
537
716
|
};
|
|
538
717
|
}
|
|
539
|
-
_prepareForPicking(x, y,
|
|
540
|
-
return { x: (
|
|
718
|
+
_prepareForPicking(x, y, scaleX, scaleY) {
|
|
719
|
+
return { x: (scaleX * x) >> 0, y: (scaleY * y) >> 0 };
|
|
541
720
|
}
|
|
542
|
-
|
|
721
|
+
_getPickingRenderRegion(x, y, renderWidth, renderHeight) {
|
|
722
|
+
if (!this._useDepthPicking) {
|
|
723
|
+
return { x, y, width: 1, height: 1 };
|
|
724
|
+
}
|
|
725
|
+
const radius = GPUPicker._DepthPixelRadius;
|
|
726
|
+
const left = Math.max(x - radius, 0);
|
|
727
|
+
const bottom = Math.max(y - radius, 0);
|
|
728
|
+
const right = Math.min(x + radius, renderWidth - 1);
|
|
729
|
+
const top = Math.min(y + radius, renderHeight - 1);
|
|
730
|
+
return {
|
|
731
|
+
x: left,
|
|
732
|
+
y: bottom,
|
|
733
|
+
width: right - left + 1,
|
|
734
|
+
height: top - bottom + 1,
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
_shouldUseIndividualMultiPickReadback(inBoundsPointCount, readArea, options) {
|
|
738
|
+
const readbackStrategy = options?.readbackStrategy ?? 0 /* GPUMultiPickReadbackStrategy.Auto */;
|
|
739
|
+
if (inBoundsPointCount === 0 || readbackStrategy === 1 /* GPUMultiPickReadbackStrategy.Rectangle */) {
|
|
740
|
+
return false;
|
|
741
|
+
}
|
|
742
|
+
if (readbackStrategy === 2 /* GPUMultiPickReadbackStrategy.Individual */) {
|
|
743
|
+
return true;
|
|
744
|
+
}
|
|
745
|
+
const maxIndividualReadbackCount = options?.maxIndividualReadbackCount !== undefined && Number.isFinite(options.maxIndividualReadbackCount) && options.maxIndividualReadbackCount >= 0
|
|
746
|
+
? options.maxIndividualReadbackCount
|
|
747
|
+
: GPUPicker._MaxMultiPickIndividualReadbackCount;
|
|
748
|
+
if (inBoundsPointCount > maxIndividualReadbackCount) {
|
|
749
|
+
return false;
|
|
750
|
+
}
|
|
751
|
+
const individualReadbackAreaRatio = options?.individualReadbackAreaRatio !== undefined && Number.isFinite(options.individualReadbackAreaRatio) && options.individualReadbackAreaRatio > 0
|
|
752
|
+
? options.individualReadbackAreaRatio
|
|
753
|
+
: GPUPicker._MultiPickIndividualReadbackAreaRatio;
|
|
754
|
+
const pointReadArea = inBoundsPointCount * (this._useDepthPicking ? 1 + (GPUPicker._DepthPixelRadius * 2 + 1) ** 2 : 1);
|
|
755
|
+
return readArea > pointReadArea * individualReadbackAreaRatio;
|
|
756
|
+
}
|
|
757
|
+
_preparePickingBuffer(engine, rttSizeW, rttSizeH, x, y, w = 1, h = 1, readBufferW = w, readBufferH = h) {
|
|
543
758
|
this._meshRenderingCount = 0;
|
|
544
|
-
const requiredBufferSize = engine.isWebGPU ? (4 *
|
|
759
|
+
const requiredBufferSize = engine.isWebGPU ? (4 * readBufferW * readBufferH + 255) & ~255 : 4 * readBufferW * readBufferH;
|
|
545
760
|
if (!this._readbuffer || this._readbuffer.length < requiredBufferSize) {
|
|
546
761
|
this._readbuffer = new Uint8Array(requiredBufferSize);
|
|
547
762
|
}
|
|
@@ -552,6 +767,16 @@ export class GPUPicker {
|
|
|
552
767
|
this._updateRenderList();
|
|
553
768
|
}
|
|
554
769
|
this._pickingTexture.clearColor = new Color4(0, 0, 0, 0);
|
|
770
|
+
this._pickingTextureClearObserver?.remove();
|
|
771
|
+
this._pickingTextureClearObserver = this._pickingTexture.onClearObservable.add((engine) => {
|
|
772
|
+
if (this._isUsingDepthPickingRenderTarget) {
|
|
773
|
+
engine.bindAttachments(engine.buildTextureLayout([true, false]));
|
|
774
|
+
}
|
|
775
|
+
engine.clear(this._pickingTexture.clearColor, true, true, true);
|
|
776
|
+
if (this._isUsingDepthPickingRenderTarget) {
|
|
777
|
+
engine.bindAttachments(engine.buildTextureLayout([true, true]));
|
|
778
|
+
}
|
|
779
|
+
});
|
|
555
780
|
this._pickingTexture.onBeforeRender = () => {
|
|
556
781
|
this._enableScissor(x, y, w, h);
|
|
557
782
|
};
|
|
@@ -559,9 +784,22 @@ export class GPUPicker {
|
|
|
559
784
|
this._pickingTextureAfterRenderObserver = this._pickingTexture.onAfterRenderObservable.add(() => {
|
|
560
785
|
this._disableScissor();
|
|
561
786
|
});
|
|
787
|
+
}
|
|
788
|
+
_addPickingTextureToRenderTargets() {
|
|
789
|
+
this._removePickingTextureFromRenderTargets();
|
|
562
790
|
this._cachedScene.customRenderTargets.push(this._pickingTexture);
|
|
563
791
|
this._renderPickingTexture = true;
|
|
564
792
|
}
|
|
793
|
+
_removePickingTextureFromRenderTargets() {
|
|
794
|
+
if (!this._cachedScene || !this._pickingTexture) {
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
const index = this._cachedScene.customRenderTargets.indexOf(this._pickingTexture);
|
|
798
|
+
if (index > -1) {
|
|
799
|
+
this._cachedScene.customRenderTargets.splice(index, 1);
|
|
800
|
+
this._renderPickingTexture = false;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
565
803
|
// pick one pixel
|
|
566
804
|
async _executePickingAsync(x, y, disposeWhenDone) {
|
|
567
805
|
return await new Promise((resolve, reject) => {
|
|
@@ -573,43 +811,60 @@ export class GPUPicker {
|
|
|
573
811
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
574
812
|
this._pickingTexture.onAfterRender = async () => {
|
|
575
813
|
if (this._checkRenderStatus()) {
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
this.
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
814
|
+
try {
|
|
815
|
+
this._pickingTexture.onAfterRender = null;
|
|
816
|
+
let pickedMesh = null;
|
|
817
|
+
let thinInstanceIndex = undefined;
|
|
818
|
+
this._removePickingTextureFromRenderTargets();
|
|
819
|
+
// Do the actual picking
|
|
820
|
+
if (await this._readTexturePixelsAsync(x, y)) {
|
|
821
|
+
const colorId = this._getColorIdFromReadBuffer(0);
|
|
822
|
+
// Thin?
|
|
823
|
+
if (this._thinIdMap[colorId]) {
|
|
824
|
+
pickedMesh = this._pickableMeshes[this._thinIdMap[colorId].meshId];
|
|
825
|
+
thinInstanceIndex = this._thinIdMap[colorId].thinId;
|
|
826
|
+
}
|
|
827
|
+
else {
|
|
828
|
+
pickedMesh = this._pickableMeshes[this._idMap[colorId]];
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
// Depth reconstruction is guaranteed for GPUPicker's default picking shader. Custom picking
|
|
832
|
+
// materials and special material plugins can also work if they write the second MRT attachment;
|
|
833
|
+
// otherwise the reconstructed pickedPoint/normal may be missing or incorrect.
|
|
834
|
+
let depthPickingInfo = null;
|
|
835
|
+
if (this._useDepthPicking && pickedMesh) {
|
|
836
|
+
const camera = this._cachedScene.activeCamera;
|
|
837
|
+
if (camera) {
|
|
838
|
+
const { rttSizeW, rttSizeH } = this._getRenderInfo();
|
|
839
|
+
const viewport = camera.viewport.toGlobal(rttSizeW, rttSizeH);
|
|
840
|
+
const view = this._cachedScene.getViewMatrix().clone();
|
|
841
|
+
const projection = this._cachedScene.getProjectionMatrix().clone();
|
|
842
|
+
const cameraPosition = camera.globalPosition.clone();
|
|
843
|
+
depthPickingInfo = await this._getDepthPickingInfoAsync(x, y, rttSizeW, rttSizeH, view, projection, cameraPosition, viewport);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
if (disposeWhenDone) {
|
|
847
|
+
this.dispose();
|
|
848
|
+
}
|
|
849
|
+
this._pickingInProgress = false;
|
|
850
|
+
if (pickedMesh) {
|
|
851
|
+
resolve({ mesh: pickedMesh, thinInstanceIndex: thinInstanceIndex, ...depthPickingInfo });
|
|
592
852
|
}
|
|
593
853
|
else {
|
|
594
|
-
|
|
854
|
+
resolve(null);
|
|
595
855
|
}
|
|
596
856
|
}
|
|
597
|
-
|
|
598
|
-
this.
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
if (pickedMesh) {
|
|
602
|
-
resolve({ mesh: pickedMesh, thinInstanceIndex: thinInstanceIndex });
|
|
603
|
-
}
|
|
604
|
-
else {
|
|
605
|
-
resolve(null);
|
|
857
|
+
catch (error) {
|
|
858
|
+
this._removePickingTextureFromRenderTargets();
|
|
859
|
+
this._pickingInProgress = false;
|
|
860
|
+
reject(error instanceof Error ? error : new Error(`${error}`));
|
|
606
861
|
}
|
|
607
862
|
}
|
|
608
863
|
};
|
|
609
864
|
});
|
|
610
865
|
}
|
|
611
866
|
// pick multiple pixels
|
|
612
|
-
async _executeMultiPickingAsync(xy,
|
|
867
|
+
async _executeMultiPickingAsync(xy, readX, readY, rttSizeW, rttSizeH, w, h, useIndividualReadback, disposeWhenDone) {
|
|
613
868
|
return await new Promise((resolve, reject) => {
|
|
614
869
|
if (!this._pickingTexture) {
|
|
615
870
|
this._pickingInProgress = false;
|
|
@@ -619,27 +874,91 @@ export class GPUPicker {
|
|
|
619
874
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
620
875
|
this._pickingTexture.onAfterRender = async () => {
|
|
621
876
|
if (this._checkRenderStatus()) {
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
877
|
+
try {
|
|
878
|
+
this._pickingTexture.onAfterRender = null;
|
|
879
|
+
const pickedMeshes = new Array(xy.length).fill(null);
|
|
880
|
+
const thinInstanceIndexes = new Array(xy.length).fill(0);
|
|
881
|
+
if (useIndividualReadback) {
|
|
882
|
+
for (let i = 0; i < xy.length; i++) {
|
|
883
|
+
const pointBottomY = rttSizeH - xy[i].y - 1;
|
|
884
|
+
if (xy[i].x < 0 || pointBottomY < 0 || xy[i].x >= rttSizeW || pointBottomY >= rttSizeH) {
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
887
|
+
// eslint-disable-next-line no-await-in-loop
|
|
888
|
+
if (await this._readTexturePixelsAsync(xy[i].x, pointBottomY, 1, 1)) {
|
|
889
|
+
const { pickedMesh, thinInstanceIndex } = this._getMeshFromReadBuffer(0);
|
|
890
|
+
pickedMeshes[i] = pickedMesh;
|
|
891
|
+
thinInstanceIndexes[i] = thinInstanceIndex ?? 0;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
else {
|
|
896
|
+
if (await this._readTexturePixelsAsync(readX, readY, w, h)) {
|
|
897
|
+
for (let i = 0; i < xy.length; i++) {
|
|
898
|
+
const { pickedMesh, thinInstanceIndex } = this._getMeshFromMultiplePoints(xy[i].x, xy[i].y, readX, readY, rttSizeH, w, h);
|
|
899
|
+
pickedMeshes[i] = pickedMesh;
|
|
900
|
+
thinInstanceIndexes[i] = thinInstanceIndex ?? 0;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
let pickedPoints;
|
|
905
|
+
let normals;
|
|
906
|
+
if (this._useDepthPicking) {
|
|
907
|
+
pickedPoints = new Array(xy.length).fill(null);
|
|
908
|
+
normals = new Array(xy.length).fill(null);
|
|
630
909
|
}
|
|
910
|
+
if (pickedPoints && normals && pickedMeshes.some((mesh) => !!mesh)) {
|
|
911
|
+
const camera = this._cachedScene.activeCamera;
|
|
912
|
+
if (camera) {
|
|
913
|
+
const viewport = camera.viewport.toGlobal(rttSizeW, rttSizeH);
|
|
914
|
+
const view = this._cachedScene.getViewMatrix().clone();
|
|
915
|
+
const projection = this._cachedScene.getProjectionMatrix().clone();
|
|
916
|
+
const cameraPosition = camera.globalPosition.clone();
|
|
917
|
+
if (useIndividualReadback) {
|
|
918
|
+
for (let i = 0; i < xy.length; i++) {
|
|
919
|
+
if (!pickedMeshes[i]) {
|
|
920
|
+
continue;
|
|
921
|
+
}
|
|
922
|
+
const pointY = rttSizeH - xy[i].y - 1;
|
|
923
|
+
// eslint-disable-next-line no-await-in-loop
|
|
924
|
+
const depthPickingInfo = await this._getDepthPickingInfoAsync(xy[i].x, pointY, rttSizeW, rttSizeH, view, projection, cameraPosition, viewport);
|
|
925
|
+
pickedPoints[i] = depthPickingInfo?.pickedPoint ?? null;
|
|
926
|
+
normals[i] = depthPickingInfo?.normal ?? null;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
else {
|
|
930
|
+
const depthPixels = await this._readDepthTexturePixelsAsync(readX, readY, w, h);
|
|
931
|
+
if (depthPixels) {
|
|
932
|
+
for (let i = 0; i < xy.length; i++) {
|
|
933
|
+
if (!pickedMeshes[i]) {
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
936
|
+
const pointY = rttSizeH - xy[i].y - 1;
|
|
937
|
+
const depthPickingInfo = this._getDepthPickingInfoFromBuffer(depthPixels, xy[i].x, pointY, readX, readY, w, h, rttSizeH, view, projection, cameraPosition, viewport);
|
|
938
|
+
pickedPoints[i] = depthPickingInfo?.pickedPoint ?? null;
|
|
939
|
+
normals[i] = depthPickingInfo?.normal ?? null;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
if (disposeWhenDone) {
|
|
946
|
+
this.dispose();
|
|
947
|
+
}
|
|
948
|
+
this._pickingInProgress = false;
|
|
949
|
+
resolve({ meshes: pickedMeshes, thinInstanceIndexes: thinInstanceIndexes, pickedPoints, normals });
|
|
631
950
|
}
|
|
632
|
-
|
|
633
|
-
this.
|
|
951
|
+
catch (error) {
|
|
952
|
+
this._removePickingTextureFromRenderTargets();
|
|
953
|
+
this._pickingInProgress = false;
|
|
954
|
+
reject(error instanceof Error ? error : new Error(`${error}`));
|
|
634
955
|
}
|
|
635
|
-
this._pickingInProgress = false;
|
|
636
|
-
resolve({ meshes: pickedMeshes, thinInstanceIndexes: thinInstanceIndexes });
|
|
637
956
|
}
|
|
638
957
|
};
|
|
639
958
|
});
|
|
640
959
|
}
|
|
641
960
|
// pick box area
|
|
642
|
-
async _executeBoxPickingAsync(
|
|
961
|
+
async _executeBoxPickingAsync(scanX, scanMaxY, scanW, scanH, readX, readY, rttSizeW, rttSizeH, readW, readH, disposeWhenDone) {
|
|
643
962
|
return await new Promise((resolve, reject) => {
|
|
644
963
|
if (!this._pickingTexture) {
|
|
645
964
|
this._pickingInProgress = false;
|
|
@@ -649,42 +968,83 @@ export class GPUPicker {
|
|
|
649
968
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
650
969
|
this._pickingTexture.onAfterRender = async () => {
|
|
651
970
|
if (this._checkRenderStatus()) {
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
971
|
+
try {
|
|
972
|
+
this._pickingTexture.onAfterRender = null;
|
|
973
|
+
const pickedMeshes = [];
|
|
974
|
+
const thinInstanceIndexes = [];
|
|
975
|
+
const pickedPixelCoordinates = [];
|
|
976
|
+
const scanBufferX = scanX - readX;
|
|
977
|
+
const scanBufferY = rttSizeH - scanMaxY - 1 - readY;
|
|
978
|
+
if (await this._readTexturePixelsAsync(readX, readY, readW, readH)) {
|
|
979
|
+
for (let offsetY = 0; offsetY < scanH; ++offsetY) {
|
|
980
|
+
for (let offsetX = 0; offsetX < scanW; ++offsetX) {
|
|
981
|
+
const bufferX = scanBufferX + offsetX;
|
|
982
|
+
const bufferY = scanBufferY + offsetY;
|
|
983
|
+
if (bufferX < 0 || bufferY < 0 || bufferX >= readW || bufferY >= readH) {
|
|
984
|
+
continue;
|
|
666
985
|
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
986
|
+
const colorId = this._getColorIdFromReadBuffer(this._getReadBufferOffset(bufferX, bufferY, readW, readH));
|
|
987
|
+
if (colorId > 0) {
|
|
988
|
+
// Thin?
|
|
989
|
+
if (this._thinIdMap[colorId]) {
|
|
990
|
+
const pickedMesh = this._pickableMeshes[this._thinIdMap[colorId].meshId];
|
|
991
|
+
const thinInstanceIndex = this._thinIdMap[colorId].thinId;
|
|
992
|
+
pickedMeshes.push(pickedMesh);
|
|
993
|
+
thinInstanceIndexes.push(thinInstanceIndex);
|
|
994
|
+
pickedPixelCoordinates.push({ x: scanX + offsetX, y: scanMaxY - offsetY });
|
|
995
|
+
}
|
|
996
|
+
else {
|
|
997
|
+
const pickedMesh = this._pickableMeshes[this._idMap[colorId]];
|
|
998
|
+
pickedMeshes.push(pickedMesh);
|
|
999
|
+
thinInstanceIndexes.push(0);
|
|
1000
|
+
pickedPixelCoordinates.push({ x: scanX + offsetX, y: scanMaxY - offsetY });
|
|
1001
|
+
}
|
|
671
1002
|
}
|
|
672
1003
|
}
|
|
673
1004
|
}
|
|
674
1005
|
}
|
|
1006
|
+
let pickedPoints;
|
|
1007
|
+
let normals;
|
|
1008
|
+
if (this._useDepthPicking) {
|
|
1009
|
+
pickedPoints = new Array(pickedMeshes.length).fill(null);
|
|
1010
|
+
normals = new Array(pickedMeshes.length).fill(null);
|
|
1011
|
+
}
|
|
1012
|
+
if (pickedPoints && normals && pickedMeshes.length > 0) {
|
|
1013
|
+
const camera = this._cachedScene.activeCamera;
|
|
1014
|
+
const depthPixels = await this._readDepthTexturePixelsAsync(readX, readY, readW, readH);
|
|
1015
|
+
if (camera && depthPixels) {
|
|
1016
|
+
const viewport = camera.viewport.toGlobal(rttSizeW, rttSizeH);
|
|
1017
|
+
const view = this._cachedScene.getViewMatrix().clone();
|
|
1018
|
+
const projection = this._cachedScene.getProjectionMatrix().clone();
|
|
1019
|
+
const cameraPosition = camera.globalPosition.clone();
|
|
1020
|
+
for (let i = 0; i < pickedPixelCoordinates.length; i++) {
|
|
1021
|
+
const point = pickedPixelCoordinates[i];
|
|
1022
|
+
const pointY = rttSizeH - point.y - 1;
|
|
1023
|
+
const depthPickingInfo = this._getDepthPickingInfoFromBuffer(depthPixels, point.x, pointY, readX, readY, readW, readH, rttSizeH, view, projection, cameraPosition, viewport);
|
|
1024
|
+
pickedPoints[i] = depthPickingInfo?.pickedPoint ?? null;
|
|
1025
|
+
normals[i] = depthPickingInfo?.normal ?? null;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
if (disposeWhenDone) {
|
|
1030
|
+
this.dispose();
|
|
1031
|
+
}
|
|
1032
|
+
this._pickingInProgress = false;
|
|
1033
|
+
resolve({ meshes: pickedMeshes, thinInstanceIndexes: thinInstanceIndexes, pickedPoints, normals });
|
|
675
1034
|
}
|
|
676
|
-
|
|
677
|
-
this.
|
|
1035
|
+
catch (error) {
|
|
1036
|
+
this._removePickingTextureFromRenderTargets();
|
|
1037
|
+
this._pickingInProgress = false;
|
|
1038
|
+
reject(error instanceof Error ? error : new Error(`${error}`));
|
|
678
1039
|
}
|
|
679
|
-
this._pickingInProgress = false;
|
|
680
|
-
resolve({ meshes: pickedMeshes, thinInstanceIndexes: thinInstanceIndexes });
|
|
681
1040
|
}
|
|
682
1041
|
};
|
|
683
1042
|
});
|
|
684
1043
|
}
|
|
685
1044
|
_enableScissor(x, y, w = 1, h = 1) {
|
|
686
1045
|
if (this._engine.enableScissor) {
|
|
687
|
-
this._engine.
|
|
1046
|
+
const scissorY = this._engine?.isWebGPU && this._pickingTexture ? this._pickingTexture.getSize().height - y - h : y;
|
|
1047
|
+
this._engine.enableScissor(x, scissorY, w, h);
|
|
688
1048
|
}
|
|
689
1049
|
}
|
|
690
1050
|
_disableScissor() {
|
|
@@ -757,12 +1117,28 @@ export class GPUPicker {
|
|
|
757
1117
|
}
|
|
758
1118
|
Logger.Warn(`GPUPicker: gave up waiting for picking materials to compile after ${maxAttempts} attempts; picking results may be incorrect.`);
|
|
759
1119
|
}
|
|
760
|
-
_getMeshFromMultiplePoints(x, y,
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
1120
|
+
_getMeshFromMultiplePoints(x, y, readX, readY, renderHeight, readWidth, readHeight) {
|
|
1121
|
+
const bufferX = x - readX;
|
|
1122
|
+
const bufferY = renderHeight - y - 1 - readY;
|
|
1123
|
+
let pickedMesh = null;
|
|
1124
|
+
let thinInstanceIndex;
|
|
1125
|
+
if (bufferX < 0 || bufferY < 0 || bufferX >= readWidth || bufferY >= readHeight) {
|
|
1126
|
+
return { pickedMesh, thinInstanceIndex };
|
|
1127
|
+
}
|
|
1128
|
+
const colorId = this._getColorIdFromReadBuffer(this._getReadBufferOffset(bufferX, bufferY, readWidth, readHeight));
|
|
1129
|
+
if (colorId > 0) {
|
|
1130
|
+
if (this._thinIdMap[colorId]) {
|
|
1131
|
+
pickedMesh = this._pickableMeshes[this._thinIdMap[colorId].meshId];
|
|
1132
|
+
thinInstanceIndex = this._thinIdMap[colorId].thinId;
|
|
1133
|
+
}
|
|
1134
|
+
else {
|
|
1135
|
+
pickedMesh = this._pickableMeshes[this._idMap[colorId]];
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
return { pickedMesh, thinInstanceIndex };
|
|
1139
|
+
}
|
|
1140
|
+
_getMeshFromReadBuffer(offset) {
|
|
1141
|
+
const colorId = this._getColorIdFromReadBuffer(offset);
|
|
766
1142
|
let pickedMesh = null;
|
|
767
1143
|
let thinInstanceIndex;
|
|
768
1144
|
if (colorId > 0) {
|
|
@@ -810,17 +1186,191 @@ export class GPUPicker {
|
|
|
810
1186
|
gsPickingMaterial.needAlphaBlending = () => false;
|
|
811
1187
|
gsPickingMaterial.backFaceCulling = false;
|
|
812
1188
|
// Attach the GPU picking plugin
|
|
813
|
-
new GaussianSplattingGpuPickingMaterialPlugin(gsPickingMaterial);
|
|
1189
|
+
const plugin = new GaussianSplattingGpuPickingMaterialPlugin(gsPickingMaterial);
|
|
1190
|
+
plugin.enableDepthPicking = this._useDepthPicking;
|
|
1191
|
+
plugin.enablePackedDepthPicking = this._isDepthTexturePacked;
|
|
814
1192
|
return gsPickingMaterial;
|
|
815
1193
|
}
|
|
816
1194
|
async _readTexturePixelsAsync(x, y, w = 1, h = 1) {
|
|
817
|
-
if (!this._cachedScene || !this._pickingTexture
|
|
1195
|
+
if (!this._cachedScene || !this._pickingTexture) {
|
|
818
1196
|
return false;
|
|
819
1197
|
}
|
|
820
1198
|
const engine = this._cachedScene.getEngine();
|
|
821
|
-
|
|
1199
|
+
const texture = this._isUsingDepthPickingRenderTarget ? this._pickingTexture.textures[0]._texture : this._pickingTexture._texture;
|
|
1200
|
+
if (!texture) {
|
|
1201
|
+
return false;
|
|
1202
|
+
}
|
|
1203
|
+
const readY = engine.isWebGPU ? texture.height - y - h : y;
|
|
1204
|
+
await engine._readTexturePixels(texture, w, h, -1, 0, this._readbuffer, true, true, x, readY);
|
|
1205
|
+
return true;
|
|
1206
|
+
}
|
|
1207
|
+
async _readDepthTexturePixelsAsync(x, y, w, h) {
|
|
1208
|
+
if (!this._cachedScene || !this._isUsingDepthPickingRenderTarget || !(this._pickingTexture instanceof MultiRenderTarget)) {
|
|
1209
|
+
return null;
|
|
1210
|
+
}
|
|
1211
|
+
const texture = this._pickingTexture.textures[1]._texture;
|
|
1212
|
+
if (!texture) {
|
|
1213
|
+
return null;
|
|
1214
|
+
}
|
|
1215
|
+
const engine = this._cachedScene.getEngine();
|
|
1216
|
+
const requiredByteLength = engine.isWebGPU ? (4 * 4 * w * h + 255) & ~255 : 4 * 4 * w * h;
|
|
1217
|
+
if (!this._depthReadbuffer || this._depthReadbuffer.byteLength < requiredByteLength) {
|
|
1218
|
+
this._depthReadbuffer = this._depthTextureType === 0 ? new Uint8Array(requiredByteLength) : new Float32Array(requiredByteLength / 4);
|
|
1219
|
+
}
|
|
1220
|
+
const readY = engine.isWebGPU ? texture.height - y - h : y;
|
|
1221
|
+
return await engine._readTexturePixels(texture, w, h, -1, 0, this._depthReadbuffer, true, false, x, readY);
|
|
1222
|
+
}
|
|
1223
|
+
async _getDepthPickingInfoAsync(x, y, renderWidth, renderHeight, view, projection, cameraPosition, viewport) {
|
|
1224
|
+
const radius = GPUPicker._DepthPixelRadius;
|
|
1225
|
+
const left = Math.max(x - radius, 0);
|
|
1226
|
+
const bottom = Math.max(y - radius, 0);
|
|
1227
|
+
const right = Math.min(x + radius, renderWidth - 1);
|
|
1228
|
+
const top = Math.min(y + radius, renderHeight - 1);
|
|
1229
|
+
const width = right - left + 1;
|
|
1230
|
+
const height = top - bottom + 1;
|
|
1231
|
+
const pixels = await this._readDepthTexturePixelsAsync(left, bottom, width, height);
|
|
1232
|
+
if (!pixels) {
|
|
1233
|
+
return null;
|
|
1234
|
+
}
|
|
1235
|
+
return this._getDepthPickingInfoFromBuffer(pixels, x, y, left, bottom, width, height, renderHeight, view, projection, cameraPosition, viewport);
|
|
1236
|
+
}
|
|
1237
|
+
_getDepthPickingInfoFromBuffer(pixels, x, y, bufferLeft, bufferBottom, bufferWidth, bufferHeight, renderHeight, view, projection, cameraPosition, viewport) {
|
|
1238
|
+
let centerBufferX = x - bufferLeft;
|
|
1239
|
+
let centerBufferY = y - bufferBottom;
|
|
1240
|
+
let centerDepth = this._getDepthFromBuffer(pixels, centerBufferX, centerBufferY, bufferWidth, bufferHeight);
|
|
1241
|
+
if (centerDepth === null) {
|
|
1242
|
+
let closestDistanceSquared = Infinity;
|
|
1243
|
+
for (let yy = 0; yy < bufferHeight; yy++) {
|
|
1244
|
+
for (let xx = 0; xx < bufferWidth; xx++) {
|
|
1245
|
+
const depth = this._getDepthFromBuffer(pixels, xx, yy, bufferWidth, bufferHeight);
|
|
1246
|
+
if (depth === null) {
|
|
1247
|
+
continue;
|
|
1248
|
+
}
|
|
1249
|
+
const dx = xx - centerBufferX;
|
|
1250
|
+
const dy = yy - centerBufferY;
|
|
1251
|
+
const distanceSquared = dx * dx + dy * dy;
|
|
1252
|
+
if (distanceSquared < closestDistanceSquared) {
|
|
1253
|
+
closestDistanceSquared = distanceSquared;
|
|
1254
|
+
centerBufferX = xx;
|
|
1255
|
+
centerBufferY = yy;
|
|
1256
|
+
centerDepth = depth;
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
if (centerDepth === null) {
|
|
1262
|
+
return null;
|
|
1263
|
+
}
|
|
1264
|
+
x = bufferLeft + centerBufferX;
|
|
1265
|
+
y = bufferBottom + centerBufferY;
|
|
1266
|
+
const viewportTop = renderHeight - viewport.y - viewport.height;
|
|
1267
|
+
const centerViewportX = x - viewport.x;
|
|
1268
|
+
const centerViewportY = renderHeight - y - 1 - viewportTop;
|
|
1269
|
+
if (centerViewportX < 0 || centerViewportY < 0 || centerViewportX >= viewport.width || centerViewportY >= viewport.height) {
|
|
1270
|
+
return null;
|
|
1271
|
+
}
|
|
1272
|
+
const pickedPoint = Vector3.UnprojectFloatsToRef(centerViewportX, centerViewportY, centerDepth, viewport.width, viewport.height, Matrix.IdentityReadOnly, view, projection, new Vector3());
|
|
1273
|
+
const fallbackNormal = () => cameraPosition.subtractToRef(pickedPoint, new Vector3()).normalize();
|
|
1274
|
+
let bestOffsetA = null;
|
|
1275
|
+
let bestOffsetB = null;
|
|
1276
|
+
let bestDepthDelta = Infinity;
|
|
1277
|
+
const offsets = GPUPicker._DepthNeighborOffsets;
|
|
1278
|
+
const depthNeighbors = [];
|
|
1279
|
+
const epsilonSquared = Epsilon * Epsilon;
|
|
1280
|
+
for (let i = 0; i < offsets.length; i++) {
|
|
1281
|
+
const offset = offsets[i];
|
|
1282
|
+
const depth = this._getDepthFromBuffer(pixels, centerBufferX + offset[0], centerBufferY + offset[1], bufferWidth, bufferHeight);
|
|
1283
|
+
if (depth === null ||
|
|
1284
|
+
!this._getDepthPointFromBufferToRef(pixels, x, y, offset[0], offset[1], bufferLeft, bufferBottom, bufferWidth, bufferHeight, renderHeight, view, projection, viewport, TmpVectors.Vector3[3])) {
|
|
1285
|
+
continue;
|
|
1286
|
+
}
|
|
1287
|
+
depthNeighbors.push({ offset, depth, depthDelta: Math.abs(centerDepth - depth) });
|
|
1288
|
+
}
|
|
1289
|
+
depthNeighbors.sort((a, b) => a.depthDelta - b.depthDelta);
|
|
1290
|
+
for (let i = 0; i < depthNeighbors.length; i++) {
|
|
1291
|
+
const neighborA = depthNeighbors[i];
|
|
1292
|
+
this._getDepthPointFromBufferToRef(pixels, x, y, neighborA.offset[0], neighborA.offset[1], bufferLeft, bufferBottom, bufferWidth, bufferHeight, renderHeight, view, projection, viewport, TmpVectors.Vector3[3]);
|
|
1293
|
+
for (let j = i + 1; j < depthNeighbors.length; j++) {
|
|
1294
|
+
const neighborB = depthNeighbors[j];
|
|
1295
|
+
this._getDepthPointFromBufferToRef(pixels, x, y, neighborB.offset[0], neighborB.offset[1], bufferLeft, bufferBottom, bufferWidth, bufferHeight, renderHeight, view, projection, viewport, TmpVectors.Vector3[4]);
|
|
1296
|
+
const toA = TmpVectors.Vector3[3].subtractToRef(pickedPoint, TmpVectors.Vector3[0]);
|
|
1297
|
+
const toB = TmpVectors.Vector3[4].subtractToRef(pickedPoint, TmpVectors.Vector3[1]);
|
|
1298
|
+
if (toA.lengthSquared() < epsilonSquared || toB.lengthSquared() < epsilonSquared) {
|
|
1299
|
+
continue;
|
|
1300
|
+
}
|
|
1301
|
+
Vector3.CrossToRef(toB, toA, TmpVectors.Vector3[5]);
|
|
1302
|
+
if (TmpVectors.Vector3[5].lengthSquared() < epsilonSquared) {
|
|
1303
|
+
continue;
|
|
1304
|
+
}
|
|
1305
|
+
const depthDelta = neighborA.depthDelta + neighborB.depthDelta + Math.abs(neighborA.depth - neighborB.depth);
|
|
1306
|
+
if (depthDelta < bestDepthDelta) {
|
|
1307
|
+
bestDepthDelta = depthDelta;
|
|
1308
|
+
bestOffsetA = neighborA.offset;
|
|
1309
|
+
bestOffsetB = neighborB.offset;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
if (!bestOffsetA || !bestOffsetB) {
|
|
1314
|
+
return { pickedPoint, normal: fallbackNormal() };
|
|
1315
|
+
}
|
|
1316
|
+
this._getDepthPointFromBufferToRef(pixels, x, y, bestOffsetA[0], bestOffsetA[1], bufferLeft, bufferBottom, bufferWidth, bufferHeight, renderHeight, view, projection, viewport, TmpVectors.Vector3[3]);
|
|
1317
|
+
this._getDepthPointFromBufferToRef(pixels, x, y, bestOffsetB[0], bestOffsetB[1], bufferLeft, bufferBottom, bufferWidth, bufferHeight, renderHeight, view, projection, viewport, TmpVectors.Vector3[4]);
|
|
1318
|
+
const toA = TmpVectors.Vector3[3].subtractToRef(pickedPoint, TmpVectors.Vector3[0]);
|
|
1319
|
+
const toB = TmpVectors.Vector3[4].subtractToRef(pickedPoint, TmpVectors.Vector3[1]);
|
|
1320
|
+
if (toA.lengthSquared() < epsilonSquared || toB.lengthSquared() < epsilonSquared) {
|
|
1321
|
+
return { pickedPoint, normal: fallbackNormal() };
|
|
1322
|
+
}
|
|
1323
|
+
toA.normalize();
|
|
1324
|
+
toB.normalize();
|
|
1325
|
+
const normal = Vector3.CrossToRef(toB, toA, new Vector3());
|
|
1326
|
+
if (normal.lengthSquared() < epsilonSquared) {
|
|
1327
|
+
return { pickedPoint, normal: fallbackNormal() };
|
|
1328
|
+
}
|
|
1329
|
+
normal.normalize();
|
|
1330
|
+
const cameraDirection = pickedPoint.subtractToRef(cameraPosition, TmpVectors.Vector3[2]).normalize();
|
|
1331
|
+
if (Vector3.Dot(normal, cameraDirection) > 0) {
|
|
1332
|
+
normal.negateInPlace();
|
|
1333
|
+
}
|
|
1334
|
+
return { pickedPoint, normal };
|
|
1335
|
+
}
|
|
1336
|
+
_getDepthPointFromBufferToRef(pixels, x, y, offsetX, offsetY, left, bottom, bufferWidth, bufferHeight, renderHeight, view, projection, viewport, result) {
|
|
1337
|
+
const bufferX = x + offsetX - left;
|
|
1338
|
+
const bufferY = y + offsetY - bottom;
|
|
1339
|
+
const depth = this._getDepthFromBuffer(pixels, bufferX, bufferY, bufferWidth, bufferHeight);
|
|
1340
|
+
if (depth === null) {
|
|
1341
|
+
return false;
|
|
1342
|
+
}
|
|
1343
|
+
const viewportTop = renderHeight - viewport.y - viewport.height;
|
|
1344
|
+
const viewportX = x + offsetX - viewport.x;
|
|
1345
|
+
const viewportY = renderHeight - (y + offsetY) - 1 - viewportTop;
|
|
1346
|
+
if (viewportX < 0 || viewportY < 0 || viewportX >= viewport.width || viewportY >= viewport.height) {
|
|
1347
|
+
return false;
|
|
1348
|
+
}
|
|
1349
|
+
Vector3.UnprojectFloatsToRef(viewportX, viewportY, depth, viewport.width, viewport.height, Matrix.IdentityReadOnly, view, projection, result);
|
|
822
1350
|
return true;
|
|
823
1351
|
}
|
|
1352
|
+
_getDepthFromBuffer(pixels, x, y, width, height) {
|
|
1353
|
+
if (x < 0 || y < 0 || x >= width || y >= height) {
|
|
1354
|
+
return null;
|
|
1355
|
+
}
|
|
1356
|
+
const index = this._getReadBufferOffset(x, y, width, height);
|
|
1357
|
+
let depth;
|
|
1358
|
+
if (pixels instanceof Uint8Array || pixels instanceof Uint8ClampedArray) {
|
|
1359
|
+
if (this._isDepthTexturePacked) {
|
|
1360
|
+
depth = pixels[index] / (255 * 255 * 255 * 255) + pixels[index + 1] / (255 * 255 * 255) + pixels[index + 2] / (255 * 255) + pixels[index + 3] / 255;
|
|
1361
|
+
}
|
|
1362
|
+
else {
|
|
1363
|
+
depth = pixels[index] / 255;
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
else {
|
|
1367
|
+
depth = pixels[index];
|
|
1368
|
+
}
|
|
1369
|
+
if (!Number.isFinite(depth) || depth <= 0 || depth >= 1) {
|
|
1370
|
+
return null;
|
|
1371
|
+
}
|
|
1372
|
+
return depth;
|
|
1373
|
+
}
|
|
824
1374
|
/** Release the resources */
|
|
825
1375
|
dispose() {
|
|
826
1376
|
this.setPickingList(null);
|
|
@@ -831,8 +1381,25 @@ export class GPUPicker {
|
|
|
831
1381
|
this._clearPickingMaterials();
|
|
832
1382
|
this._sceneBeforeRenderObserver?.remove();
|
|
833
1383
|
this._sceneBeforeRenderObserver = null;
|
|
1384
|
+
this._pickingTextureClearObserver?.remove();
|
|
1385
|
+
this._pickingTextureClearObserver = null;
|
|
1386
|
+
this._pickingTextureAfterRenderObserver?.remove();
|
|
1387
|
+
this._pickingTextureAfterRenderObserver = null;
|
|
834
1388
|
}
|
|
835
1389
|
}
|
|
836
1390
|
GPUPicker._AttributeName = "instanceMeshID";
|
|
837
1391
|
GPUPicker._MaxPickingId = 0x00ffffff; // 24 bits unsigned integer max
|
|
1392
|
+
GPUPicker._DepthPixelRadius = 1;
|
|
1393
|
+
GPUPicker._MaxMultiPickIndividualReadbackCount = 32;
|
|
1394
|
+
GPUPicker._MultiPickIndividualReadbackAreaRatio = 16;
|
|
1395
|
+
GPUPicker._DepthNeighborOffsets = [
|
|
1396
|
+
[-1, 1],
|
|
1397
|
+
[0, 1],
|
|
1398
|
+
[1, 1],
|
|
1399
|
+
[1, 0],
|
|
1400
|
+
[1, -1],
|
|
1401
|
+
[0, -1],
|
|
1402
|
+
[-1, -1],
|
|
1403
|
+
[-1, 0],
|
|
1404
|
+
];
|
|
838
1405
|
//# sourceMappingURL=gpuPicker.js.map
|