diff --git a/packages/dev/core/src/Collisions/gpuPicker.ts b/packages/dev/core/src/Collisions/gpuPicker.ts index 49e512d5f09..bc523b2d4b3 100644 --- a/packages/dev/core/src/Collisions/gpuPicker.ts +++ b/packages/dev/core/src/Collisions/gpuPicker.ts @@ -6,8 +6,8 @@ import { RenderTargetTexture } from "core/Materials/Textures/renderTargetTexture import { ShaderLanguage } from "core/Materials/shaderLanguage"; import type { IShaderMaterialOptions } from "core/Materials/shaderMaterial"; import { ShaderMaterial } from "core/Materials/shaderMaterial"; -import { Color3, Color4 } from "core/Maths/math.color"; -import type { IColor3Like, IVector2Like } from "core/Maths/math.like"; +import { Color4 } from "core/Maths/math.color"; +import type { IVector2Like } from "core/Maths/math.like"; import type { AbstractMesh } from "core/Meshes/abstractMesh"; import { VertexBuffer } from "core/Meshes/buffer"; import type { Mesh } from "core/Meshes/mesh"; @@ -50,32 +50,37 @@ export interface IGPUMultiPickingInfo { * GPUPIcker can pick meshes, instances and thin instances */ export class GPUPicker { + private static readonly _AttributeName = "instanceMeshID"; + private static readonly _MaxPickingId = 0x00ffffff; // 24 bits unsigned integer max + private _pickingTexture: Nullable = null; - private _idMap: Array = []; - private _thinIdMap: Array<{ meshId: number; thinId: number }> = []; - private _idColors: Array = []; - private _cachedScene: Nullable; - private _engine: Nullable; - private _defaultRenderMaterial: Nullable; - private _pickableMeshes: Array; - private _meshMaterialMap: Map = new Map(); - private _readbuffer: Uint8Array; + + private readonly _idMap: Array = []; + private readonly _thinIdMap: Array<{ meshId: number; thinId: number }> = []; + private readonly _meshUniqueIdToPickerId: Array = []; + private _idWarningIssued = false; + + private _cachedScene: Nullable = null; + private _engine: Nullable = null; + + private readonly _pickingMaterialCache: Nullable[] = new Array(9).fill(null); + + private _pickableMeshes: Array = []; + private readonly _meshMaterialMap: Map = new Map(); + private _readbuffer: Nullable = null; + private _meshRenderingCount: number = 0; - private readonly _attributeName = "instanceMeshID"; - private _warningIssued = false; + private _renderWarningIssued = false; private _renderPickingTexture = false; + private _sceneBeforeRenderObserver: Nullable> = null; - private _pickingTextureAfterRenderObservable: Nullable> = null; + private _pickingTextureAfterRenderObserver: Nullable> = null; + + private _nextFreeId = 1; /** Shader language used by the generator */ protected _shaderLanguage = ShaderLanguage.GLSL; - private static _TempColor: IColor3Like = { - r: 0, - g: 0, - b: 0, - }; - /** * Gets the shader language used in this generator. */ @@ -88,38 +93,27 @@ export class GPUPicker { /** * Gets a boolean indicating if the picking is in progress */ - public get pickingInProgress() { + public get pickingInProgress(): boolean { return this._pickingInProgress; } /** - * Gets the default render material used by the picker. + * Gets the default render materials used by the picker. + * + * index is Material filling mode */ - public get defaultRenderMaterial(): Nullable { - return this._defaultRenderMaterial; - } - - private static _IdToRgb(id: number) { - GPUPicker._TempColor.r = (id & 0xff0000) >> 16; - GPUPicker._TempColor.g = (id & 0x00ff00) >> 8; - GPUPicker._TempColor.b = (id & 0x0000ff) >> 0; + public get defaultRenderMaterials(): readonly Nullable[] { + return this._pickingMaterialCache; } - private _getColorIdFromReadBuffer(offset: number) { - const r = this._readbuffer[offset]; - const g = this._readbuffer[offset + 1]; - const b = this._readbuffer[offset + 2]; + private _getColorIdFromReadBuffer(offset: number): number { + const r = this._readbuffer![offset]; + const g = this._readbuffer![offset + 1]; + const b = this._readbuffer![offset + 2]; return (r << 16) + (g << 8) + b; } - private static _SetColorData(buffer: Float32Array, i: number, r: number, g: number, b: number) { - buffer[i] = r / 255.0; - buffer[i + 1] = g / 255.0; - buffer[i + 2] = b / 255.0; - buffer[i + 3] = 1.0; - } - - private _createRenderTarget(scene: Scene, width: number, height: number) { + private _createRenderTarget(scene: Scene, width: number, height: number): void { if (this._cachedScene && this._pickingTexture) { const index = this._cachedScene.customRenderTargets.indexOf(this._pickingTexture); if (index > -1) { @@ -130,25 +124,32 @@ export class GPUPicker { if (this._pickingTexture) { this._pickingTexture.dispose(); } - this._pickingTexture = new RenderTargetTexture( - "pickingTexure", - { width: width, height: height }, - scene, - false, - undefined, - Constants.TEXTURETYPE_UNSIGNED_BYTE, - false, - Constants.TEXTURE_NEAREST_NEAREST - ); + this._pickingTexture = new RenderTargetTexture("pickingTexure", { width: width, height: height }, scene, { + generateMipMaps: false, + type: Constants.TEXTURETYPE_UNSIGNED_BYTE, + samplingMode: Constants.TEXTURE_NEAREST_NEAREST, + }); } - // eslint-disable-next-line @typescript-eslint/require-await - private async _createColorMaterialAsync(scene: Scene) { - if (this._defaultRenderMaterial) { - this._defaultRenderMaterial.dispose(); + private _clearPickingMaterials(): void { + for (let i = 0; i < this._pickingMaterialCache.length; i++) { + const material = this._pickingMaterialCache[i]; + if (material !== null) { + material.dispose(); + this._pickingMaterialCache[i] = null; + } } + } - this._defaultRenderMaterial = null; + private _getPickingMaterial(scene: Scene, fillMode: number): ShaderMaterial { + if (fillMode < 0 || 8 < fillMode) { + fillMode = Constants.MATERIAL_TriangleFillMode; + } + + const cachedMaterial = this._pickingMaterialCache[fillMode]; + if (cachedMaterial) { + return cachedMaterial; + } const engine = scene.getEngine(); @@ -158,7 +159,7 @@ export class GPUPicker { const defines: string[] = []; const options: Partial = { - attributes: [VertexBuffer.PositionKind, this._attributeName, "bakedVertexAnimationSettingsInstanced"], + attributes: [VertexBuffer.PositionKind, GPUPicker._AttributeName], uniforms: ["world", "viewProjection", "meshID"], needAlphaBlending: false, defines: defines, @@ -173,12 +174,15 @@ export class GPUPicker { }, }; - this._defaultRenderMaterial = new ShaderMaterial("pickingShader", scene, "picking", options, false); + const newMaterial = new ShaderMaterial("pickingShader", scene, "picking", options, false); + newMaterial.fillMode = fillMode; + newMaterial.onBindObservable.add(this._materialBindCallback, undefined, undefined, this); - this._defaultRenderMaterial.onBindObservable.add(this._materialBindCallback, undefined, undefined, this); + this._pickingMaterialCache[fillMode] = newMaterial; + return newMaterial; } - private _materialBindCallback(mesh: AbstractMesh | undefined) { + private _materialBindCallback(mesh: AbstractMesh | undefined): void { if (!mesh) { return; } @@ -186,8 +190,8 @@ export class GPUPicker { const material = this._meshMaterialMap.get(mesh)!; if (!material) { - if (!this._warningIssued) { - this._warningIssued = true; + if (!this._renderWarningIssued) { + this._renderWarningIssued = true; Logger.Warn("GPUPicker issue: Mesh not found in the material map. This may happen when the root mesh of an instance is not in the picking list."); } return; @@ -195,67 +199,57 @@ export class GPUPicker { const effect = material.getEffect(); - if (!mesh.hasInstances && !mesh.isAnInstance && !mesh.hasThinInstances && this._idColors[mesh.uniqueId] !== undefined) { - effect.setColor4("meshID", this._idColors[mesh.uniqueId], 1); + if (!mesh.hasInstances && !mesh.isAnInstance && !mesh.hasThinInstances && this._meshUniqueIdToPickerId[mesh.uniqueId] !== undefined) { + effect.setFloat("meshID", this._meshUniqueIdToPickerId[mesh.uniqueId]); } this._meshRenderingCount++; } - private _generateColorData(instanceCount: number, id: number, r: number, g: number, b: number, onInstance: (i: number, id: number) => void) { - const colorData = new Float32Array(4 * (instanceCount + 1)); - - GPUPicker._SetColorData(colorData, 0, r, g, b); - - for (let i = 0; i < instanceCount; i++) { - GPUPicker._IdToRgb(id); + /** + * Set the list of meshes to pick from + * Set that value to null to clear the list (and avoid leaks) + * The module will read and delete from the array provided by reference. Disposing the module or setting the value to null will clear the array. + * @param list defines the list of meshes to pick from + */ + public setPickingList(list: Nullable>): void { + this.clearPickingList(); - onInstance(i, id); - GPUPicker._SetColorData(colorData, (i + 1) * 4, GPUPicker._TempColor.r, GPUPicker._TempColor.g, GPUPicker._TempColor.b); - id++; + if (!list || list.length === 0) { + return; } - return colorData; - } - - private _generateThinInstanceColorData(instanceCount: number, id: number, onInstance: (i: number, id: number) => void) { - const colorData = new Float32Array(4 * instanceCount); - - for (let i = 0; i < instanceCount; i++) { - GPUPicker._IdToRgb(id); - - onInstance(i, id); - GPUPicker._SetColorData(colorData, i * 4, GPUPicker._TempColor.r, GPUPicker._TempColor.g, GPUPicker._TempColor.b); + this._pickableMeshes = list as Array; - id++; + // Prepare target + const scene = ("mesh" in list[0] ? list[0].mesh : list[0]).getScene(); + if (!this._cachedScene || this._cachedScene !== scene) { + this._clearPickingMaterials(); } - return colorData; + this.addPickingList(list); } /** - * Set the list of meshes to pick from - * Set that value to null to clear the list (and avoid leaks) - * The module will read and delete from the array provided by reference. Disposing the module or setting the value to null will clear the array. - * @param list defines the list of meshes to pick from + * Clear the current picking list and free resources */ - public setPickingList(list: Nullable>) { + public clearPickingList(): void { if (this._pickableMeshes) { // Cleanup for (let index = 0; index < this._pickableMeshes.length; index++) { const mesh = this._pickableMeshes[index]; if (mesh.hasInstances) { - (mesh as Mesh).removeVerticesData(this._attributeName); + (mesh as Mesh).removeVerticesData(GPUPicker._AttributeName); } if (mesh.hasThinInstances) { - (mesh as Mesh).thinInstanceSetBuffer(this._attributeName, null); + (mesh as Mesh).thinInstanceSetBuffer(GPUPicker._AttributeName, null); } if (this._pickingTexture) { this._pickingTexture.setMaterialForRendering(mesh, undefined); } const material = this._meshMaterialMap.get(mesh)!; - if (material !== this._defaultRenderMaterial) { + if (!this._pickingMaterialCache.includes(material)) { material.onBindObservable.removeCallback(this._materialBindCallback); } } @@ -263,17 +257,24 @@ export class GPUPicker { this._meshMaterialMap.clear(); this._idMap.length = 0; this._thinIdMap.length = 0; - this._idColors.length = 0; + this._meshUniqueIdToPickerId.length = 0; if (this._pickingTexture) { this._pickingTexture.renderList = []; } } + + this._nextFreeId = 1; + } + + /** + * Add array of meshes to the current picking list + * @param list defines the array of meshes to add to the current picking list + */ + public addPickingList(list: Array): void { if (!list || list.length === 0) { return; } - this._pickableMeshes = list as Array; - // Prepare target const scene = ("mesh" in list[0] ? list[0].mesh : list[0]).getScene(); const engine = scene.getEngine(); @@ -297,11 +298,15 @@ export class GPUPicker { } }); - if (!this._cachedScene || this._cachedScene !== scene) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this._createColorMaterialAsync(scene); + this._cachedScene = scene; + this._engine = scene.getEngine(); + if (!this._pickingTexture!.renderList) { + this._pickingTexture!.renderList = []; } + const newPickableMeshes: AbstractMesh[] = new Array(list.length); + const pickableMeshOffset = this._pickableMeshes?.length ?? 0; + this._cachedScene = scene; this._engine = scene.getEngine(); @@ -309,21 +314,27 @@ export class GPUPicker { const item = list[i]; if ("mesh" in item) { this._meshMaterialMap.set(item.mesh, item.material); - list[i] = item.mesh; + newPickableMeshes[i] = item.mesh; } else { - this._meshMaterialMap.set(item, this._defaultRenderMaterial!); + const material = this._getPickingMaterial(scene, item.material?.fillMode ?? Constants.MATERIAL_TriangleFillMode); + this._meshMaterialMap.set(item, material); + newPickableMeshes[i] = item; } } - this._pickingTexture!.renderList = []; + if (this._pickableMeshes !== null) { + this._pickableMeshes = [...this._pickableMeshes, ...newPickableMeshes]; + } else { + this._pickableMeshes = newPickableMeshes; + } // We will affect colors and create vertex color buffers - let id = 1; - for (let index = 0; index < this._pickableMeshes.length; index++) { - const mesh = this._pickableMeshes[index]; + let nextFreeid = this._nextFreeId; + for (let index = 0; index < newPickableMeshes.length; index++) { + const mesh = newPickableMeshes[index]; const material = this._meshMaterialMap.get(mesh)!; - if (material !== this._defaultRenderMaterial) { + if (!this._pickingMaterialCache.includes(material)) { material.onBindObservable.add(this._materialBindCallback, undefined, undefined, this); } this._pickingTexture!.setMaterialForRendering(mesh, material); @@ -333,35 +344,58 @@ export class GPUPicker { continue; // This will be handled by the source mesh } - GPUPicker._IdToRgb(id); + const globalIndex = index + pickableMeshOffset; if (mesh.hasThinInstances) { - const colorData = this._generateThinInstanceColorData((mesh as Mesh).thinInstanceCount, id, (i, id) => { - this._thinIdMap[id] = { meshId: index, thinId: i }; - }); - id += (mesh as Mesh).thinInstanceCount; - (mesh as Mesh).thinInstanceSetBuffer(this._attributeName, colorData, 4); + const thinInstanceCount = (mesh as Mesh).thinInstanceCount; + const instanceIdData = new Float32Array(thinInstanceCount); + for (let i = 0; i < thinInstanceCount; i++) { + instanceIdData[i] = nextFreeid; + this._thinIdMap[nextFreeid] = { meshId: globalIndex, thinId: i }; + nextFreeid++; + } + (mesh as Mesh).thinInstanceSetBuffer(GPUPicker._AttributeName, instanceIdData, 1); } else { - this._idMap[id] = index; - id++; + const currentMeshId = nextFreeid; + this._idMap[currentMeshId] = globalIndex; + nextFreeid++; if (mesh.hasInstances) { - const instancesForPick = this._pickableMeshes.filter((m) => m.isAnInstance && (m as InstancedMesh).sourceMesh === mesh); - const colorData = this._generateColorData(instancesForPick.length, id, GPUPicker._TempColor.r, GPUPicker._TempColor.g, GPUPicker._TempColor.b, (i, id) => { - const instance = instancesForPick[i]; - this._idMap[id] = this._pickableMeshes.indexOf(instance); - }); + // find index of instances of that mesh + const instancesForPick: number[] = []; + for (let pickableMeshIndex = 0; pickableMeshIndex < newPickableMeshes.length; ++pickableMeshIndex) { + const m = newPickableMeshes[pickableMeshIndex]; + if (m.isAnInstance && (m as InstancedMesh).sourceMesh === mesh) { + instancesForPick.push(pickableMeshIndex); + } + } + const instanceIdData = new Float32Array(instancesForPick.length + 1); // +1 for the source mesh + + instanceIdData[0] = currentMeshId; + for (let i = 0; i < instancesForPick.length; i++) { + instanceIdData[i + 1] = nextFreeid; + const globalInstanceIndex = instancesForPick[i] + pickableMeshOffset; + this._idMap[nextFreeid] = globalInstanceIndex; + nextFreeid++; + } - id += instancesForPick.length; const engine = mesh.getEngine(); - - const buffer = new VertexBuffer(engine, colorData, this._attributeName, false, false, 4, true); + const buffer = new VertexBuffer(engine, instanceIdData, GPUPicker._AttributeName, false, false, 1, true); (mesh as Mesh).setVerticesBuffer(buffer, true); } else { - this._idColors[mesh.uniqueId] = Color3.FromInts(GPUPicker._TempColor.r, GPUPicker._TempColor.g, GPUPicker._TempColor.b); + this._meshUniqueIdToPickerId[mesh.uniqueId] = currentMeshId; } } } + + if (GPUPicker._MaxPickingId < nextFreeid - 1) { + if (!this._idWarningIssued) { + this._idWarningIssued = true; + Logger.Warn(`GPUPicker maximum number of pickable meshes and instances is ${GPUPicker._MaxPickingId}. Some meshes or instances won't be pickable.`); + } + } + + this._nextFreeId = nextFreeid; } /** @@ -458,7 +492,51 @@ export class GPUPicker { return await this._executeMultiPickingAsync(processedXY, minX, maxY, rttSizeH, w, h, disposeWhenDone); } - private _getRenderInfo() { + /** + * Execute a picking operation on box defined by two screen coordinates + * @param x1 defines the X coordinate of the first corner of the box where to run the pick + * @param y1 defines the Y coordinate of the first corner of the box where to run the pick + * @param x2 defines the X coordinate of the opposite corner of the box where to run the pick + * @param y2 defines the Y coordinate of the opposite corner of the box where to run the pick + * @param disposeWhenDone defines a boolean indicating we do not want to keep resources alive (false by default) + * @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. + */ + public async boxPickAsync(x1: number, y1: number, x2: number, y2: number, disposeWhenDone = false): Promise> { + if (this._pickingInProgress) { + return null; + } + + if (!this._pickableMeshes || this._pickableMeshes.length === 0) { + return null; + } + + this._pickingInProgress = true; + + const { rttSizeW, rttSizeH, devicePixelRatio } = this._getRenderInfo(); + + const { x: adjustedX1, y: adjustedY1 } = this._prepareForPicking(x1, y1, devicePixelRatio); + const { x: adjustedX2, y: adjustedY2 } = this._prepareForPicking(x2, y2, devicePixelRatio); + + const minX = Math.max(Math.min(adjustedX1, adjustedX2), 0); + const maxX = Math.min(Math.max(adjustedX1, adjustedX2), rttSizeW - 1); + const minY = Math.max(Math.min(adjustedY1, adjustedY2), 0); + const maxY = Math.min(Math.max(adjustedY1, adjustedY2), rttSizeH - 1); + + if (minX >= rttSizeW || minY >= rttSizeH || maxX < 0 || maxY < 0) { + this._pickingInProgress = false; + return null; + } + + const w = Math.max(maxX - minX, 1); + const h = Math.max(maxY - minY, 1); + const partialCutH = rttSizeH - maxY - 1; + + this._preparePickingBuffer(this._engine!, rttSizeW, rttSizeH, minX, partialCutH, w, h); + + return await this._executeBoxPickingAsync(minX, partialCutH, w, h, disposeWhenDone); + } + + private _getRenderInfo(): { rttSizeW: number; rttSizeH: number; devicePixelRatio: number } { const engine = this._cachedScene!.getEngine(); const rttSizeW = engine.getRenderWidth(); const rttSizeH = engine.getRenderHeight(); @@ -471,11 +549,11 @@ export class GPUPicker { }; } - private _prepareForPicking(x: number, y: number, devicePixelRatio: number) { + private _prepareForPicking(x: number, y: number, devicePixelRatio: number): IVector2Like { return { x: (devicePixelRatio * x) >> 0, y: (devicePixelRatio * y) >> 0 }; } - private _preparePickingBuffer(engine: AbstractEngine, rttSizeW: number, rttSizeH: number, x: number, y: number, w = 1, h = 1) { + private _preparePickingBuffer(engine: AbstractEngine, rttSizeW: number, rttSizeH: number, x: number, y: number, w = 1, h = 1): void { this._meshRenderingCount = 0; const requiredBufferSize = engine.isWebGPU ? (4 * w * h + 255) & ~255 : 4 * w * h; @@ -492,12 +570,12 @@ export class GPUPicker { this._pickingTexture!.clearColor = new Color4(0, 0, 0, 0); - this._pickingTexture!.onBeforeRender = () => { + this._pickingTexture!.onBeforeRender = (): void => { this._enableScissor(x, y, w, h); }; - this._pickingTextureAfterRenderObservable?.remove(); - this._pickingTextureAfterRenderObservable = this._pickingTexture!.onAfterRenderObservable.add(() => { + this._pickingTextureAfterRenderObserver?.remove(); + this._pickingTextureAfterRenderObserver = this._pickingTexture!.onAfterRenderObservable.add(() => { this._disableScissor(); }); @@ -515,7 +593,7 @@ export class GPUPicker { } // eslint-disable-next-line @typescript-eslint/no-misused-promises - this._pickingTexture.onAfterRender = async () => { + this._pickingTexture.onAfterRender = async (): Promise => { if (this._checkRenderStatus()) { this._pickingTexture!.onAfterRender = null as any; let pickedMesh: Nullable = null; @@ -531,7 +609,6 @@ export class GPUPicker { // Do the actual picking if (await this._readTexturePixelsAsync(x, y)) { const colorId = this._getColorIdFromReadBuffer(0); - // Thin? if (this._thinIdMap[colorId]) { pickedMesh = this._pickableMeshes[this._thinIdMap[colorId].meshId]; @@ -574,7 +651,7 @@ export class GPUPicker { } // eslint-disable-next-line @typescript-eslint/no-misused-promises - this._pickingTexture.onAfterRender = async () => { + this._pickingTexture.onAfterRender = async (): Promise => { if (this._checkRenderStatus()) { this._pickingTexture!.onAfterRender = null as any; const pickedMeshes: Nullable[] = []; @@ -599,12 +676,60 @@ export class GPUPicker { }); } - private _enableScissor(x: number, y: number, w = 1, h = 1) { + // pick box area + private async _executeBoxPickingAsync(x: number, y: number, w: number, h: number, disposeWhenDone: boolean): Promise { + return await new Promise((resolve, reject) => { + if (!this._pickingTexture) { + this._pickingInProgress = false; + reject(new Error("Picking texture not created")); + return; + } + + // eslint-disable-next-line @typescript-eslint/no-misused-promises + this._pickingTexture.onAfterRender = async (): Promise => { + if (this._checkRenderStatus()) { + this._pickingTexture!.onAfterRender = null as any; + const pickedMeshes: Nullable[] = []; + const thinInstanceIndexes: number[] = []; + + if (await this._readTexturePixelsAsync(x, y, w, h)) { + for (let offsetY = 0; offsetY < h; ++offsetY) { + for (let offsetX = 0; offsetX < w; ++offsetX) { + const colorId = this._getColorIdFromReadBuffer((offsetY * w + offsetX) * 4); + if (colorId > 0) { + // Thin? + if (this._thinIdMap[colorId]) { + const pickedMesh = this._pickableMeshes[this._thinIdMap[colorId].meshId]; + const thinInstanceIndex = this._thinIdMap[colorId].thinId; + pickedMeshes.push(pickedMesh); + thinInstanceIndexes.push(thinInstanceIndex); + } else { + const pickedMesh = this._pickableMeshes[this._idMap[colorId]]; + pickedMeshes.push(pickedMesh); + thinInstanceIndexes.push(0); + } + } + } + } + } + + if (disposeWhenDone) { + this.dispose(); + } + + this._pickingInProgress = false; + resolve({ meshes: pickedMeshes, thinInstanceIndexes: thinInstanceIndexes }); + } + }; + }); + } + + private _enableScissor(x: number, y: number, w = 1, h = 1): void { if ((this._engine as WebGPUEngine | Engine).enableScissor) { (this._engine as WebGPUEngine | Engine).enableScissor(x, y, w, h); } } - private _disableScissor() { + private _disableScissor(): void { if ((this._engine as WebGPUEngine | Engine).disableScissor) { (this._engine as WebGPUEngine | Engine).disableScissor(); } @@ -614,8 +739,8 @@ export class GPUPicker { * @returns true if rendering if the picking texture has finished, otherwise false */ private _checkRenderStatus(): boolean { - const wasSuccessfull = this._meshRenderingCount > 0; - if (wasSuccessfull) { + const wasSuccessful = this._meshRenderingCount > 0; + if (wasSuccessful) { // Remove from the active RTTs const index = this._cachedScene!.customRenderTargets.indexOf(this._pickingTexture!); if (index > -1) { @@ -656,7 +781,7 @@ export class GPUPicker { /** * Updates the render list with the current pickable meshes. */ - private _updateRenderList() { + private _updateRenderList(): void { this._pickingTexture!.renderList = []; for (const mesh of this._pickableMeshes) { this._pickingTexture!.setMaterialForRendering(mesh, this._meshMaterialMap.get(mesh)); @@ -664,7 +789,7 @@ export class GPUPicker { } } - private async _readTexturePixelsAsync(x: number, y: number, w = 1, h = 1) { + private async _readTexturePixelsAsync(x: number, y: number, w = 1, h = 1): Promise { if (!this._cachedScene || !this._pickingTexture?._texture) { return false; } @@ -675,15 +800,14 @@ export class GPUPicker { } /** Release the resources */ - public dispose() { + public dispose(): void { this.setPickingList(null); this._cachedScene = null; // Cleaning up this._pickingTexture?.dispose(); this._pickingTexture = null; - this._defaultRenderMaterial?.dispose(); - this._defaultRenderMaterial = null; + this._clearPickingMaterials(); this._sceneBeforeRenderObserver?.remove(); this._sceneBeforeRenderObserver = null; } diff --git a/packages/dev/core/src/Shaders/picking.fragment.fx b/packages/dev/core/src/Shaders/picking.fragment.fx index d2452bc5e41..798024bc86d 100644 --- a/packages/dev/core/src/Shaders/picking.fragment.fx +++ b/packages/dev/core/src/Shaders/picking.fragment.fx @@ -1,16 +1,36 @@  #if defined(INSTANCES) -varying vec4 vMeshID; +varying float vMeshID; #else -uniform vec4 meshID; +uniform float meshID; #endif void main(void) { - + // decode mesh ID into int32 + float id; #if defined(INSTANCES) - gl_FragColor = vMeshID; + id = vMeshID; #else - gl_FragColor = meshID; + id = meshID; #endif -} \ No newline at end of file +#if defined(WEBGL2) || defined(WEBGPU) || defined(NATIVE) + int castedId = int(id); + //decompose it into 3 bytes + vec3 color = vec3( + float((castedId >> 16) & 0xFF), + float((castedId >> 8) & 0xFF), + float(castedId & 0xFF) + ) / 255.0; + gl_FragColor = vec4(color, 1.0); +#else + float castedId = floor(id + 0.5); + // for webgl1, we need to emulate bitwise operations + vec3 color = vec3( + floor(mod(castedId, 16777216.0) / 65536.0), + floor(mod(castedId, 65536.0) / 256.0), + mod(castedId, 256.0) + ) / 255.0; + gl_FragColor = vec4(color, 1.0); +#endif +} diff --git a/packages/dev/core/src/Shaders/picking.vertex.fx b/packages/dev/core/src/Shaders/picking.vertex.fx index ded2457732f..c792eef889b 100644 --- a/packages/dev/core/src/Shaders/picking.vertex.fx +++ b/packages/dev/core/src/Shaders/picking.vertex.fx @@ -1,7 +1,7 @@ // Attributes attribute vec3 position; #if defined(INSTANCES) -attribute vec4 instanceMeshID; +attribute float instanceMeshID; #endif #include @@ -16,7 +16,7 @@ uniform mat4 viewProjection; // Output #if defined(INSTANCES) -varying vec4 vMeshID; +varying float vMeshID; #endif void main(void) { @@ -32,4 +32,4 @@ void main(void) { #if defined(INSTANCES) vMeshID = instanceMeshID; #endif -} \ No newline at end of file +} diff --git a/packages/dev/core/src/ShadersWGSL/picking.fragment.fx b/packages/dev/core/src/ShadersWGSL/picking.fragment.fx index 17a17552a5d..9f7176bb9a1 100644 --- a/packages/dev/core/src/ShadersWGSL/picking.fragment.fx +++ b/packages/dev/core/src/ShadersWGSL/picking.fragment.fx @@ -1,17 +1,22 @@ #if defined(INSTANCES) -varying vMeshID: vec4f; +varying vMeshID: f32; #else -uniform meshID: vec4f; +uniform meshID: f32; #endif @fragment fn main(input: FragmentInputs) -> FragmentOutputs { - + var id: i32; #if defined(INSTANCES) - fragmentOutputs.color = input.vMeshID; + id = i32(input.vMeshID); #else - fragmentOutputs.color = uniforms.meshID; + id = i32(uniforms.meshID); #endif - -} \ No newline at end of file + var color = vec3f( + f32((id >> 16) & 0xFF), + f32((id >> 8) & 0xFF), + f32(id & 0xFF), + ) / 255.0; + fragmentOutputs.color = vec4f(color, 1.0); +} diff --git a/packages/dev/core/src/ShadersWGSL/picking.vertex.fx b/packages/dev/core/src/ShadersWGSL/picking.vertex.fx index 92679149eab..d9ea7b8096f 100644 --- a/packages/dev/core/src/ShadersWGSL/picking.vertex.fx +++ b/packages/dev/core/src/ShadersWGSL/picking.vertex.fx @@ -1,7 +1,7 @@ // Attributes attribute position: vec3f; #if defined(INSTANCES) -attribute instanceMeshID: vec4f; +attribute instanceMeshID: f32; #endif #include @@ -16,7 +16,7 @@ uniform viewProjection: mat4x4f; // Output #if defined(INSTANCES) -varying vMeshID: vec4f; +varying vMeshID: f32; #endif @vertex @@ -33,4 +33,4 @@ fn main(input : VertexInputs) -> FragmentInputs { #if defined(INSTANCES) vertexOutputs.vMeshID = input.instanceMeshID; #endif -} \ No newline at end of file +}