| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255 |
- import BoundingRectangle from "../Core/BoundingRectangle.js";
- import Color from "../Core/Color.js";
- import defined from "../Core/defined.js";
- import destroyObject from "../Core/destroyObject.js";
- import FramebufferManager from "../Renderer/FramebufferManager.js";
- import PassState from "../Renderer/PassState.js";
- import PixelDatatype from "../Renderer/PixelDatatype.js";
- import PixelFormat from "../Core/PixelFormat.js";
- import Sync from "../Renderer/Sync.js";
- import RuntimeError from "../Core/RuntimeError.js";
-
- /**
- * @private
- */
- function PickFramebuffer(context) {
- // Override per-command states
- const passState = new PassState(context);
- passState.blendingEnabled = false;
- passState.scissorTest = {
- enabled: true,
- rectangle: new BoundingRectangle(),
- };
- passState.viewport = new BoundingRectangle();
-
- this._context = context;
- this._fb = new FramebufferManager({
- depthStencil: true,
- });
- this._passState = passState;
- this._width = 0;
- this._height = 0;
- }
-
- /**
- * Return the picked object(s) rendered within a given rectangle.
- *
- * @private
- * @param {object} context The active context.
- * @param {Uint8Array|Uint16Array|Float32Array|Uint32Array} pixels The pixels in the specified rectangle.
- * @param {number} width The rectangle width.
- * @param {number} height The rectangle height.
- * @param {number} [limit=1] If supplied, stop iterating after collecting this many objects.
- * @returns {object[]} A list of rendered objects, ordered by distance to the middle of the rectangle.
- */
- function pickObjectsFromPixels(context, pixels, width, height, limit = 1) {
- const max = Math.max(width, height);
- const length = max * max;
- const halfWidth = Math.floor(width * 0.5);
- const halfHeight = Math.floor(height * 0.5);
-
- let x = 0;
- let y = 0;
- let dx = 0;
- let dy = -1;
-
- // Spiral around the center pixel, this is a workaround until
- // we can access the depth buffer on all browsers.
-
- // The region does not have to square and the dimensions do not have to be odd, but
- // loop iterations would be wasted. Prefer square regions where the size is odd.
- const objects = new Set();
- for (let i = 0; i < length; ++i) {
- if (
- -halfWidth <= x &&
- x <= halfWidth &&
- -halfHeight <= y &&
- y <= halfHeight
- ) {
- const index = 4 * ((halfHeight - y) * width + x + halfWidth);
-
- const pickColor = Color.bytesToRgba(
- pixels[index],
- pixels[index + 1],
- pixels[index + 2],
- pixels[index + 3],
- );
-
- const object = context.getObjectByPickColor(pickColor);
- if (defined(object)) {
- objects.add(object);
- if (objects.size >= limit) {
- break;
- }
- }
- }
-
- // if (top right || bottom left corners) || (top left corner) || (bottom right corner + (1, 0))
- // change spiral direction
- if (x === y || (x < 0 && -x === y) || (x > 0 && x === 1 - y)) {
- const temp = dx;
- dx = -dy;
- dy = temp;
- }
-
- x += dx;
- y += dy;
- }
- return [...objects];
- }
-
- PickFramebuffer.prototype.begin = function (screenSpaceRectangle, viewport) {
- const context = this._context;
- const { width, height } = viewport;
-
- BoundingRectangle.clone(
- screenSpaceRectangle,
- this._passState.scissorTest.rectangle,
- );
-
- // Create or recreate renderbuffers and framebuffer used for picking
- this._width = width;
- this._height = height;
- this._fb.update(context, width, height);
- this._passState.framebuffer = this._fb.framebuffer;
-
- this._passState.viewport.width = width;
- this._passState.viewport.height = height;
-
- return this._passState;
- };
-
- /**
- * Return the picked objects rendered within a given rectangle using asynchronously without stalling the GPU.
- * Requires WebGL2.
- *
- * @param {BoundingRectangle} screenSpaceRectangle
- * @param {FrameState} frameState
- * @param {number} [limit=1] If supplied, stop iterating after collecting this many objects.
- * @returns {Promise<object[]>} A list of rendered objects, ordered by distance to the middle of the rectangle.
- *
- * @exception {RuntimeError} Async Picking Request Timeout.
- * @exception {DeveloperError} A WebGL 2 context is required.
- */
- PickFramebuffer.prototype.endAsync = async function (
- screenSpaceRectangle,
- frameState,
- limit = 1,
- ) {
- const width = screenSpaceRectangle.width ?? 1.0;
- const height = screenSpaceRectangle.height ?? 1.0;
-
- const context = this._context;
- const framebuffer = this._fb.framebuffer;
-
- let pixelDatatype = PixelDatatype.UNSIGNED_BYTE;
- let pixelFormat = PixelFormat.RGBA;
-
- if (defined(framebuffer) && framebuffer.numberOfColorAttachments > 0) {
- pixelDatatype = framebuffer.getColorTexture(0).pixelDatatype;
- pixelFormat = framebuffer.getColorTexture(0).pixelFormat;
- }
-
- const pbo = context.readPixelsToPBO({
- x: screenSpaceRectangle.x,
- y: screenSpaceRectangle.y,
- width: width,
- height: height,
- framebuffer: framebuffer,
- });
-
- const sync = Sync.create({
- context: context,
- });
-
- // Wait for the GPU to signal that it is ready to readback the PBO data
- try {
- await sync.waitForSignal((next) => frameState.afterRender.push(next));
- const pixels = PixelFormat.createTypedArray(
- pixelFormat,
- pixelDatatype,
- width,
- height,
- );
- pbo.getBufferData(pixels);
- const pickedObjects = pickObjectsFromPixels(
- context,
- pixels,
- width,
- height,
- limit,
- );
- return pickedObjects;
- } catch (e) {
- throw new RuntimeError("Async Picking Request Timeout");
- } finally {
- sync.destroy();
- pbo.destroy();
- }
- };
-
- /**
- * Return the picked objects rendered within a given rectangle.
- *
- * @param {BoundingRectangle} screenSpaceRectangle
- * @param {number} [limit=1] If supplied, stop iterating after collecting this many objects.
- * @returns {object[]} A list of rendered objects, ordered by distance to the middle of the rectangle.
- */
- PickFramebuffer.prototype.end = function (screenSpaceRectangle, limit = 1) {
- const width = screenSpaceRectangle.width ?? 1.0;
- const height = screenSpaceRectangle.height ?? 1.0;
-
- const context = this._context;
- const pixels = context.readPixels({
- x: screenSpaceRectangle.x,
- y: screenSpaceRectangle.y,
- width: width,
- height: height,
- framebuffer: this._fb.framebuffer,
- });
-
- return pickObjectsFromPixels(context, pixels, width, height, limit);
- };
-
- /**
- * Return a typed array containing the RGBA (byte) components of the
- * pixel that is at the center of the given rectangle.
- *
- * This may, for example, be voxel tile and sample information as rendered
- * by a pickVoxel pass, within a given rectangle. Or it may be the result
- * of a metadata picking rendering pass.
- *
- * @param {BoundingRectangle} screenSpaceRectangle
- * @returns {Uint8Array} The RGBA components
- */
- PickFramebuffer.prototype.readCenterPixel = function (screenSpaceRectangle) {
- const width = screenSpaceRectangle.width ?? 1.0;
- const height = screenSpaceRectangle.height ?? 1.0;
-
- const context = this._context;
- const pixels = context.readPixels({
- x: screenSpaceRectangle.x,
- y: screenSpaceRectangle.y,
- width: width,
- height: height,
- framebuffer: this._fb.framebuffer,
- });
-
- // Read the center pixel
- const halfWidth = Math.floor(width * 0.5);
- const halfHeight = Math.floor(height * 0.5);
- const index = 4 * (halfHeight * width + halfWidth);
-
- return pixels.slice(index, index + 4);
- };
-
- PickFramebuffer.prototype.isDestroyed = function () {
- return false;
- };
-
- PickFramebuffer.prototype.destroy = function () {
- this._fb.destroy();
- return destroyObject(this);
- };
-
- export default PickFramebuffer;
|