UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

137 lines (134 loc) 4.43 kB
import { Color } from '../../core/math/color.js'; import { ADDRESS_CLAMP_TO_EDGE, FILTER_NEAREST, PIXELFORMAT_RGBA8 } from '../../platform/graphics/constants.js'; import { RenderTarget } from '../../platform/graphics/render-target.js'; import { Texture } from '../../platform/graphics/texture.js'; import { Layer } from '../../scene/layer.js'; import { RenderPassPicker } from './render-pass-picker.js'; import { math } from '../../core/math/math.js'; import { Vec4 } from '../../core/math/vec4.js'; const tempSet = new Set(); const _rect = new Vec4(); class Picker { constructor(app, width, height){ this.renderTarget = null; this.mapping = new Map(); this.deviceValid = true; this.renderer = app.renderer; this.device = app.graphicsDevice; this.renderPass = new RenderPassPicker(this.device, app.renderer); this.width = 0; this.height = 0; this.resize(width, height); this.device.on('destroy', ()=>{ this.deviceValid = false; }); } getSelection(x, y, width = 1, height = 1) { const device = this.device; if (device.isWebGPU) { return []; } y = this.renderTarget.height - (y + height); const rect = this.sanitizeRect(x, y, width, height); device.setRenderTarget(this.renderTarget); device.updateBegin(); const pixels = new Uint8Array(4 * rect.z * rect.w); device.readPixels(rect.x, rect.y, rect.z, rect.w, pixels); device.updateEnd(); return this.decodePixels(pixels, this.mapping); } getSelectionAsync(x, y, width = 1, height = 1) { if (this.device?.isWebGL2) { y = this.renderTarget.height - (y + height); } const rect = this.sanitizeRect(x, y, width, height); return this.renderTarget.colorBuffer.read(rect.x, rect.y, rect.z, rect.w, { renderTarget: this.renderTarget, immediate: true }).then((pixels)=>{ return this.decodePixels(pixels, this.mapping); }); } sanitizeRect(x, y, width, height) { const maxWidth = this.renderTarget.width; const maxHeight = this.renderTarget.height; x = math.clamp(Math.floor(x), 0, maxWidth - 1); y = math.clamp(Math.floor(y), 0, maxHeight - 1); width = Math.floor(Math.max(width, 1)); width = Math.min(width, maxWidth - x); height = Math.floor(Math.max(height, 1)); height = Math.min(height, maxHeight - y); return _rect.set(x, y, width, height); } decodePixels(pixels, mapping) { const selection = []; if (this.deviceValid) { const count = pixels.length; for(let i = 0; i < count; i += 4){ const r = pixels[i + 0]; const g = pixels[i + 1]; const b = pixels[i + 2]; const a = pixels[i + 3]; const index = (a << 24 | r << 16 | g << 8 | b) >>> 0; if (index !== 0xFFFFFFFF) { tempSet.add(mapping.get(index)); } } tempSet.forEach((meshInstance)=>{ if (meshInstance) { selection.push(meshInstance); } }); tempSet.clear(); } return selection; } allocateRenderTarget() { const colorBuffer = new Texture(this.device, { format: PIXELFORMAT_RGBA8, width: this.width, height: this.height, mipmaps: false, minFilter: FILTER_NEAREST, magFilter: FILTER_NEAREST, addressU: ADDRESS_CLAMP_TO_EDGE, addressV: ADDRESS_CLAMP_TO_EDGE, name: 'pick' }); this.renderTarget = new RenderTarget({ colorBuffer: colorBuffer, depth: true }); } releaseRenderTarget() { if (this.renderTarget) { this.renderTarget.destroyTextureBuffers(); this.renderTarget.destroy(); this.renderTarget = null; } } prepare(camera, scene, layers) { if (layers instanceof Layer) { layers = [ layers ]; } if (!this.renderTarget || this.width !== this.renderTarget.width || this.height !== this.renderTarget.height) { this.releaseRenderTarget(); this.allocateRenderTarget(); } this.mapping.clear(); const renderPass = this.renderPass; renderPass.init(this.renderTarget); renderPass.colorOps.clearValue = Color.WHITE; renderPass.colorOps.clear = true; renderPass.depthStencilOps.clearDepth = true; renderPass.update(camera, scene, layers, this.mapping); renderPass.render(); } resize(width, height) { this.width = Math.floor(width); this.height = Math.floor(height); } } export { Picker };