import Base from '../core/Base';
import FrameBuffer from '../FrameBuffer';
import Texture2D from '../Texture2D';
import Shader from '../Shader';
import Material from '../Material';
import colorEssl from './color.glsl.js';
Shader.import(colorEssl);
/**
* Pixel picking is gpu based picking, which is fast and accurate.
* But not like ray picking, it can't get the intersection point and triangle.
* @constructor clay.picking.PixelPicking
* @extends clay.core.Base
*/
var PixelPicking = Base.extend(function() {
return /** @lends clay.picking.PixelPicking# */ {
/**
* Target renderer
* @type {clay.Renderer}
*/
renderer: null,
/**
* Downsample ratio of hidden frame buffer
* @type {number}
*/
downSampleRatio: 1,
width: 100,
height: 100,
lookupOffset: 1,
_frameBuffer: null,
_texture: null,
_shader: null,
_idMaterials: [],
_lookupTable: [],
_meshMaterials: [],
_idOffset: 0
};
}, function() {
if (this.renderer) {
this.width = this.renderer.getWidth();
this.height = this.renderer.getHeight();
}
this._init();
}, /** @lends clay.picking.PixelPicking.prototype */ {
_init: function() {
this._texture = new Texture2D({
width: this.width * this.downSampleRatio,
height: this.height * this.downSampleRatio
});
this._frameBuffer = new FrameBuffer();
this._shader = new Shader(Shader.source('clay.picking.color.vertex'), Shader.source('clay.picking.color.fragment'));
},
/**
* Set picking presision
* @param {number} ratio
*/
setPrecision: function(ratio) {
this._texture.width = this.width * ratio;
this._texture.height = this.height * ratio;
this.downSampleRatio = ratio;
},
resize: function(width, height) {
this._texture.width = width * this.downSampleRatio;
this._texture.height = height * this.downSampleRatio;
this.width = width;
this.height = height;
this._texture.dirty();
},
/**
* Update the picking framebuffer
* @param {number} ratio
*/
update: function(scene, camera) {
var renderer = this.renderer;
if (renderer.getWidth() !== this.width || renderer.getHeight() !== this.height) {
this.resize(renderer.width, renderer.height);
}
this._frameBuffer.attach(this._texture);
this._frameBuffer.bind(renderer);
this._idOffset = this.lookupOffset;
this._setMaterial(scene);
renderer.render(scene, camera);
this._restoreMaterial();
this._frameBuffer.unbind(renderer);
},
_setMaterial: function(root) {
for (var i =0; i < root._children.length; i++) {
var child = root._children[i];
if (child.geometry && child.material && child.material.shader) {
var id = this._idOffset++;
var idx = id - this.lookupOffset;
var material = this._idMaterials[idx];
if (!material) {
material = new Material({
shader: this._shader
});
var color = packID(id);
color[0] /= 255;
color[1] /= 255;
color[2] /= 255;
color[3] = 1.0;
material.set('color', color);
this._idMaterials[idx] = material;
}
this._meshMaterials[idx] = child.material;
this._lookupTable[idx] = child;
child.material = material;
}
if (child._children.length) {
this._setMaterial(child);
}
}
},
/**
* Pick the object
* @param {number} x Mouse position x
* @param {number} y Mouse position y
* @return {clay.Node}
*/
pick: function(x, y) {
var renderer = this.renderer;
var ratio = this.downSampleRatio;
x = Math.ceil(ratio * x);
y = Math.ceil(ratio * (this.height - y));
this._frameBuffer.bind(renderer);
var pixel = new Uint8Array(4);
var _gl = renderer.gl;
// TODO out of bounds ?
// preserveDrawingBuffer ?
_gl.readPixels(x, y, 1, 1, _gl.RGBA, _gl.UNSIGNED_BYTE, pixel);
this._frameBuffer.unbind(renderer);
// Skip interpolated pixel because of anti alias
if (pixel[3] === 255) {
var id = unpackID(pixel[0], pixel[1], pixel[2]);
if (id) {
var el = this._lookupTable[id - this.lookupOffset];
return el;
}
}
},
_restoreMaterial: function() {
for (var i = 0; i < this._lookupTable.length; i++) {
this._lookupTable[i].material = this._meshMaterials[i];
}
},
dispose: function(renderer) {
this._frameBuffer.dispose(renderer);
}
});
function packID(id){
var r = id >> 16;
var g = (id - (r << 8)) >> 8;
var b = id - (r << 16) - (g<<8);
return [r, g, b];
}
function unpackID(r, g, b){
return (r << 16) + (g<<8) + b;
}
export default PixelPicking;