@cesium/engine
Version:
CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.
168 lines (143 loc) • 4.78 kB
JavaScript
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";
/**
* @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;
}
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.
*
* @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,
});
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];
};
/**
* 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;