Object.assign(pc, function () {
var _deviceDeprecationWarning = false;
var _getSelectionDeprecationWarning = false;
var _prepareDeprecationWarning = false;
/**
* @constructor
* @name pc.Picker
* @classdesc Picker object used to select mesh instances from screen coordinates.
* @description Create a new instance of a Picker object
* @param {pc.Application} app The application managing this picker instance.
* @param {Number} width The width of the pick buffer in pixels.
* @param {Number} height The height of the pick buffer in pixels.
* @property {Number} width Width of the pick buffer in pixels (read-only).
* @property {Number} height Height of the pick buffer in pixels (read-only).
* @property {pc.RenderTarget} renderTarget The render target used by the picker internally (read-only).
*/
var Picker = function (app, width, height) {
if (app instanceof pc.GraphicsDevice) {
app = pc.Application.getApplication();
if (!_deviceDeprecationWarning) {
_deviceDeprecationWarning = true;
// #ifdef DEBUG
console.warn("pc.Picker now takes pc.Application as first argument. Passing pc.GraphicsDevice is deprecated.");
// #endif
}
}
this.app = app;
this.device = app.graphicsDevice;
var device = this.device;
this.library = device.getProgramLibrary();
this.pickColor = new Float32Array(4);
this.pickColor[3] = 1;
this.scene = null;
this.drawCalls = [];
this.layer = null;
this.layerComp = null;
this.clearOptions = {
color: [1, 1, 1, 1],
depth: 1,
flags: pc.CLEARFLAG_COLOR | pc.CLEARFLAG_DEPTH
};
var self = this;
this._clearDepthOptions = {
depth: 1.0,
flags: pc.CLEARFLAG_DEPTH
};
this.clearDepthCommand = new pc.Command(0, 0, function (){
device.clear(self._clearDepthOptions);
});
this.resize(width, height);
this._ignoreOpacityFor = null; // meshInstance
};
/**
* @function
* @name pc.Picker#getSelection
* @description Return the list of mesh instances selected by the specified rectangle in the
* previously prepared pick buffer.The rectangle using top-left coordinate system.
* @param {Number} x The left edge of the rectangle
* @param {Number} y The top edge of the rectangle
* @param {Number} [width] The width of the rectangle
* @param {Number} [height] The height of the rectangle
* @returns {pc.MeshInstance[]} An array of mesh instances that are in the selection
* @example
* // Get the selection at the point (10,20)
* var selection = picker.getSelection(10, 20);
*
* // Get all models in rectangle with corners at (10,20) and (20,40)
* var selection = picker.getSelection(10, 20, 10, 20);
*/
Picker.prototype.getSelection = function (x, y, width, height) {
var device = this.device;
if (typeof x === 'object') {
// #ifdef DEBUG
if (!_prepareDeprecationWarning) {
_prepareDeprecationWarning = true;
console.warn("Picker.getSelection:param 'rect' is deprecated, use 'x, y, width, height' instead.");
}
// #endif
var rect = x;
x = rect.x;
y = rect.y;
width = rect.width;
height = rect.height;
} else {
y = this.layer.renderTarget.height - (y + (height || 1));
}
width = width || 1;
height = height || 1;
// Cache active render target
var prevRenderTarget = device.renderTarget;
// Ready the device for rendering to the pick buffer
device.setRenderTarget(this.layer.renderTarget);
device.updateBegin();
var pixels = new Uint8Array(4 * width * height);
device.readPixels(x, y, width, height, pixels);
device.updateEnd();
// Restore render target
device.setRenderTarget(prevRenderTarget);
var selection = [];
var drawCalls = this.layer.instances.visibleOpaque[0].list;
var r, g, b, index;
for (var i = 0; i < width * height; i++) {
r = pixels[4 * i + 0];
g = pixels[4 * i + 1];
b = pixels[4 * i + 2];
index = r << 16 | g << 8 | b;
// White is 'no selection'
if (index !== 0xffffff) {
var selectedMeshInstance = drawCalls[index];
if (selection.indexOf(selectedMeshInstance) === -1) {
selection.push(selectedMeshInstance);
}
}
}
return selection;
};
/**
* @function
* @name pc.Picker#prepare
* @description Primes the pick buffer with a rendering of the specified models from the point of view
* of the supplied camera. Once the pick buffer has been prepared, pc.Picker#getSelection can be
* called multiple times on the same picker object. Therefore, if the models or camera do not change
* in any way, pc.Picker#prepare does not need to be called again.
* @param {pc.CameraComponent} camera The camera component used to render the scene.
* @param {pc.Scene} scene The scene containing the pickable mesh instances.
* @param {pc.Layer|pc.RenderTarget} [arg] Layer or RenderTarget from which objects will be picked. If not supplied, all layers rendering to backbuffer before this layer will be used.
*/
Picker.prototype.prepare = function (camera, scene, arg) {
var device = this.device;
var i, j;
var self = this;
if (camera instanceof pc.Camera) {
// #ifdef DEBUG
if (!_getSelectionDeprecationWarning) {
_getSelectionDeprecationWarning = true;
console.warn("pc.Picker#prepare now takes pc.CameraComponent as first argument. Passing pc.Camera is deprecated.");
}
// #endif
camera = camera._component;
}
this.scene = scene;
var sourceLayer = null;
var sourceRt = null;
if (arg instanceof pc.Layer) {
sourceLayer = arg;
} else {
sourceRt = arg;
}
// Setup picker rendering once
if (!this.layer) {
var pickColorId = device.scope.resolve('uColor');
this.layer = new pc.Layer({
name: "Picker",
shaderPass: pc.SHADER_PICK,
opaqueSortMode: pc.SORTMODE_NONE,
onEnable: function () {
if (this.renderTarget) return;
var colorBuffer = new pc.Texture(device, {
format: pc.PIXELFORMAT_R8_G8_B8_A8,
width: self.width,
height: self.height
});
colorBuffer.name = 'pick';
colorBuffer.minFilter = pc.FILTER_NEAREST;
colorBuffer.magFilter = pc.FILTER_NEAREST;
colorBuffer.addressU = pc.ADDRESS_CLAMP_TO_EDGE;
colorBuffer.addressV = pc.ADDRESS_CLAMP_TO_EDGE;
this.renderTarget = new pc.RenderTarget(device, colorBuffer, {
depth: true
});
},
onDisable: function () {
if (!this.renderTarget) return;
this.renderTarget._colorBuffer.destroy();
this.renderTarget.destroy();
this.renderTarget = null;
},
onDrawCall: function (meshInstance, index) {
self.pickColor[0] = ((index >> 16) & 0xff) / 255;
self.pickColor[1] = ((index >> 8) & 0xff) / 255;
self.pickColor[2] = (index & 0xff) / 255;
pickColorId.setValue(self.pickColor);
device.setBlending(false);
}
});
this.layerComp = new pc.LayerComposition();
this.layerComp.pushOpaque(this.layer);
this.meshInstances = this.layer.opaqueMeshInstances;
this._instancesVersion = -1;
}
// Collect pickable mesh instances
var instanceList, instanceListLength, drawCall;
if (!sourceLayer) {
this.layer.clearMeshInstances();
var layers = scene.layers.layerList;
var subLayerEnabled = scene.layers.subLayerEnabled;
var isTransparent = scene.layers.subLayerList;
var layer;
var layerCamId, transparent;
for (i = 0; i < layers.length; i++) {
if (layers[i].overrideClear && layers[i]._clearDepthBuffer) layers[i]._pickerCleared = false;
}
for (i = 0; i < layers.length; i++) {
layer = layers[i];
if (layer.renderTarget !== sourceRt || !layer.enabled || !subLayerEnabled[i]) continue;
layerCamId = layer.cameras.indexOf(camera);
if (layerCamId < 0) continue;
if (layer.overrideClear && layer._clearDepthBuffer && !layer._pickerCleared) {
this.meshInstances.push(this.clearDepthCommand);
layer._pickerCleared = true;
}
transparent = isTransparent[i];
instanceList = transparent ? layer.instances.transparentMeshInstances : layer.instances.opaqueMeshInstances;
instanceListLength = instanceList.length;
for (j = 0; j < instanceListLength; j++) {
drawCall = instanceList[j];
if (drawCall.pick) {
this.meshInstances.push(drawCall);
}
}
}
} else {
if (this._instancesVersion !== sourceLayer._version) {
this.layer.clearMeshInstances();
instanceList = sourceLayer.instances.opaqueMeshInstances;
instanceListLength = instanceList.length;
for (j = 0; j < instanceListLength; j++) {
drawCall = instanceList[j];
if (drawCall.pick) {
this.meshInstances.push(drawCall);
}
}
instanceList = sourceLayer.instances.transparentMeshInstances;
instanceListLength = instanceList.length;
for (j = 0; j < instanceListLength; j++) {
drawCall = instanceList[j];
if (drawCall.pick) {
this.meshInstances.push(drawCall);
}
}
this._instancesVersion = sourceLayer._version;
}
}
// Setup picker camera if changed
if (this.layer.cameras[0] !== camera) {
this.layer.clearCameras();
this.layer.addCamera(camera);
}
// save old camera state
this.onLayerPreRender(this.layer, sourceLayer, sourceRt);
// Render
this.app.renderer.renderComposition(this.layerComp);
// restore old camera state
this.onLayerPostRender(this.layer);
};
Picker.prototype.onLayerPreRender = function (layer, sourceLayer, sourceRt) {
if (this.width !== layer.renderTarget.width || this.height !== layer.renderTarget.height) {
layer.onDisable();
layer.onEnable();
}
layer.oldClear = layer.cameras[0].camera._clearOptions;
layer.oldAspectMode = layer.cameras[0].aspectRatioMode;
layer.oldAspect = layer.cameras[0].aspectRatio;
layer.cameras[0].camera._clearOptions = this.clearOptions;
layer.cameras[0].aspectRatioMode = pc.ASPECT_MANUAL;
var rt = sourceRt ? sourceRt : (sourceLayer ? sourceLayer.renderTarget : null);
layer.cameras[0].aspectRatio = layer.cameras[0].calculateAspectRatio(rt);
this.app.renderer.updateCameraFrustum(layer.cameras[0].camera);
};
Picker.prototype.onLayerPostRender = function (layer) {
layer.cameras[0].camera._clearOptions = layer.oldClear;
layer.cameras[0].aspectRatioMode = layer.oldAspectMode;
layer.cameras[0].aspectRatio = layer.oldAspect;
};
/**
* @function
* @name pc.Picker#resize
* @description Sets the resolution of the pick buffer. The pick buffer resolution does not need
* to match the resolution of the corresponding frame buffer use for general rendering of the
* 3D scene. However, the lower the resolution of the pick buffer, the less accurate the selection
* results returned by pc.Picker#getSelection. On the other hand, smaller pick buffers will
* yield greater performance, so there is a trade off.
* @param {Number} width The width of the pick buffer in pixels.
* @param {Number} height The height of the pick buffer in pixels.
*/
Picker.prototype.resize = function (width, height) {
this.width = width;
this.height = height;
};
Object.defineProperty(Picker.prototype, 'renderTarget', {
get: function () {
return this.layer.renderTarget;
}
});
return {
Picker: Picker
};
}());