Initial (untidy) picking implementation

master
Robin Hawkes 2016-02-29 18:49:21 +00:00
rodzic 398db98975
commit 7ef8197306
14 zmienionych plików z 779 dodań i 109 usunięć

576
dist/vizicities.js vendored

Plik diff jest za duży Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Wyświetl plik

@ -3,16 +3,21 @@ import THREE from 'three';
import Scene from './Scene';
import Renderer from './Renderer';
import Camera from './Camera';
import Picking from './Picking';
class Engine extends EventEmitter {
constructor(container) {
constructor(container, world) {
console.log('Init Engine');
super();
this._world = world;
this._scene = Scene;
this._renderer = Renderer(container);
this._camera = Camera(container);
this._picking = Picking(this._world, this._renderer, this._camera);
this.clock = new THREE.Clock();
this._frustum = new THREE.Frustum();
@ -20,7 +25,12 @@ class Engine extends EventEmitter {
update(delta) {
this.emit('preRender');
this._renderer.render(this._scene, this._camera);
// this._renderer.render(this._scene, this._camera);
// Render picking scene
this._renderer.render(this._picking._pickingScene, this._camera);
this.emit('postRender');
}
@ -53,6 +63,7 @@ class Engine extends EventEmitter {
}
};
this._world = null;
this._scene = null;
this._renderer = null;
this._camera = null;
@ -62,6 +73,6 @@ class Engine extends EventEmitter {
}
// Initialise without requiring new keyword
export default function(container) {
return new Engine(container);
export default function(container, world) {
return new Engine(container, world);
};

Wyświetl plik

@ -0,0 +1,111 @@
import THREE from 'three';
import Point from '../geo/Point';
import PickingScene from './PickingScene';
// TODO: Look into a way of setting this up without passing in a renderer and
// camera from the engine
// TODO: Don't pick up an ID when the background / white is picked
var nextId = 1;
class Picking {
constructor(world, renderer, camera) {
this._world = world;
this._renderer = renderer;
this._camera = camera;
this._pickingScene = PickingScene;
this._pickingTexture = new THREE.WebGLRenderTarget();
this._pickingTexture.texture.minFilter = THREE.LinearFilter;
this._pickingTexture.texture.generateMipmaps = false;
this._nextId = 1;
this._resizeTexture();
this._initEvents();
}
_initEvents() {
window.addEventListener('resize', this._resizeTexture.bind(this), false);
// this._renderer.domElement.addEventListener('mousemove', this._onMouseMove.bind(this), false);
this._renderer.domElement.addEventListener('mouseup', this._onMouseUp.bind(this), false);
this._world.on('move', this._onWorldMove, this);
}
_onMouseUp(event) {
// Only react to main button click
if (event.button !== 0) {
return;
}
this._pick(VIZI.Point(event.clientX, event.clientY));
}
_onWorldMove() {
this._needUpdate = true;
}
// TODO: Ensure this doesn't get out of sync issue with the renderer resize
_resizeTexture() {
var size = this._renderer.getSize();
this._pickingTexture.setSize(size.width, size.height);
this._pixelBuffer = new Uint8Array(4 * size.width * size.height);
this._needUpdate = true;
}
_update() {
if (this._needUpdate) {
var texture = this._pickingTexture;
this._renderer.render(this._pickingScene, this._camera, this._pickingTexture);
// Read the rendering texture
this._renderer.readRenderTargetPixels(texture, 0, 0, texture.width, texture.height, this._pixelBuffer);
this._needUpdate = false;
console.log('Picker updated');
}
}
_pick(point) {
this._update();
var index = point.x + (this._pickingTexture.height - point.y) * this._pickingTexture.width;
// Interpret the pixel as an ID
var id = (this._pixelBuffer[index * 4 + 2] * 255 * 255) + (this._pixelBuffer[index * 4 + 1] * 255) + (this._pixelBuffer[index * 4 + 0]);
console.log('Pick id:', id);
}
// Add object to picking scene
//
// Picking ID should already be added as an attribute for now
add(mesh) {
// console.log('Add to picking:', mesh);
this._pickingScene.add(mesh);
this._needUpdate = true;
}
remove(mesh) {
this._pickingScene.remove(mesh);
this._needUpdate = true;
}
// Returns next ID to use for picking
getNextId() {
return nextId++;
}
destroy() {}
}
// Initialise without requiring new keyword
export default function(world, renderer, camera) {
return new Picking(world, renderer, camera);
};

Wyświetl plik

@ -0,0 +1,36 @@
import THREE from 'three';
import PickingShader from './PickingShader';
// FROM: https://github.com/brianxu/GPUPicker/blob/master/GPUPicker.js
var PickingMaterial = function() {
THREE.ShaderMaterial.call(this, {
// uniforms: {
// size: {
// type: 'f',
// value: 0.01,
// },
// scale: {
// type: 'f',
// value: 400,
// }
// },
// attributes: ['position', 'id'],
vertexShader: PickingShader.vertexShader,
fragmentShader: PickingShader.fragmentShader
});
};
PickingMaterial.prototype = Object.create(THREE.ShaderMaterial.prototype);
PickingMaterial.prototype.constructor = PickingMaterial;
// PickingMaterial.prototype.setPointSize = function(size) {
// this.uniforms.size.value = size;
// };
//
// PickingMaterial.prototype.setPointScale = function(scale) {
// this.uniforms.scale.value = scale;
// };
export default PickingMaterial;

Wyświetl plik

@ -0,0 +1,9 @@
import THREE from 'three';
// This can be imported from anywhere and will still reference the same scene,
// though there is a helper reference in Engine.pickingScene
export default (function() {
var scene = new THREE.Scene();
return scene;
})();

Wyświetl plik

@ -0,0 +1,35 @@
// FROM: https://github.com/brianxu/GPUPicker/blob/master/GPUPicker.js
var PickingShader = {
vertexShader: [
'attribute float pickingId;',
// '',
// 'uniform float size;',
// 'uniform float scale;',
'',
'varying vec4 worldId;',
'',
'void main() {',
' vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );',
// ' gl_PointSize = size * ( scale / length( mvPosition.xyz ) );',
' vec3 a = fract(vec3(1.0/255.0, 1.0/(255.0*255.0), 1.0/(255.0*255.0*255.0)) * pickingId);',
' a -= a.xxy * vec3(0.0, 1.0/255.0, 1.0/255.0);',
' worldId = vec4(a,1);',
' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
'}'
].join('\n'),
fragmentShader: [
'#ifdef GL_ES\n',
'precision highp float;\n',
'#endif\n',
'',
'varying vec4 worldId;',
'',
'void main() {',
' gl_FragColor = worldId;',
'}'
].join('\n')
};
export default PickingShader;

Wyświetl plik

@ -40,7 +40,7 @@ class World extends EventEmitter {
}
_initEngine() {
this._engine = Engine(this._container);
this._engine = Engine(this._container, this);
// Engine events
//

Wyświetl plik

@ -34,6 +34,19 @@ class Layer extends EventEmitter {
this.emit('added');
}
getPickingId() {
return this._world._engine._picking.getNextId();
}
// TODO: Tidy this up and don't access so many private properties to work
addToPicking(mesh) {
this._world._engine._picking.add(mesh);
}
removeFromPicking(mesh) {
this._world._engine._picking.remove(mesh);
}
// Destroys the layer and removes it from the scene and memory
destroy() {
if (this._layer.children) {

Wyświetl plik

@ -8,6 +8,7 @@ import extend from 'lodash.assign';
// import Offset from 'polygon-offset';
import GeoJSON from '../../util/GeoJSON';
import Buffer from '../../util/Buffer';
import PickingMaterial from '../../engine/PickingMaterial';
// TODO: Make sure nothing is left behind in the heap after calling destroy()
@ -66,6 +67,7 @@ class GeoJSONTile extends Tile {
setTimeout(() => {
if (!this._mesh) {
this._mesh = this._createMesh();
this._pickingMesh = this._createPickingMesh();
// this._shadowCanvas = this._createShadowCanvas();
this._requestTile();
}
@ -114,6 +116,19 @@ class GeoJSONTile extends Tile {
return mesh;
}
_createPickingMesh() {
if (!this._center) {
return;
}
var mesh = new THREE.Object3D();
mesh.position.x = this._center[0];
mesh.position.z = this._center[1];
return mesh;
}
_createDebugMesh() {
var canvas = document.createElement('canvas');
canvas.width = 256;
@ -265,6 +280,7 @@ class GeoJSONTile extends Tile {
vertices: [],
faces: [],
colours: [],
pickingIds: [],
facesCount: 0,
allFlat: true
};
@ -375,6 +391,14 @@ class GeoJSONTile extends Tile {
polygons.faces.push(polygonAttributes.faces);
polygons.colours.push(polygonAttributes.colours);
// TODO: Make this optional
var pickingId = this._layer.getPickingId();
// Inject picking ID
//
// TODO: Perhaps handle this within the GeoJSON helper
polygons.pickingIds.push(pickingId);
if (polygons.allFlat && !polygonAttributes.flat) {
polygons.allFlat = false;
}
@ -488,6 +512,12 @@ class GeoJSONTile extends Tile {
}
this._mesh.add(mesh);
material = new PickingMaterial();
material.side = THREE.BackSide;
var pickingMesh = new THREE.Mesh(geometry, material);
this._pickingMesh.add(pickingMesh);
}
this._ready = true;

Wyświetl plik

@ -69,6 +69,10 @@ class Tile {
return this._mesh;
}
getPickingMesh() {
return this._pickingMesh;
}
// Destroys the tile and removes it from the layer and memory
//
// Ensure that this leaves no trace of the tile – no textures, no meshes,

Wyświetl plik

@ -69,9 +69,11 @@ class TileLayer extends Layer {
this._frustum = new THREE.Frustum();
this._tiles = new THREE.Object3D();
this._tilesPicking = new THREE.Object3D();
}
_onAdd(world) {
this.addToPicking(this._tilesPicking);
this.add(this._tiles);
}
@ -110,6 +112,10 @@ class TileLayer extends Layer {
// Add tile to layer (and to scene) if not already there
this._tiles.add(tile.getMesh());
if (tile.getPickingMesh()) {
this._tilesPicking.add(tile.getPickingMesh());
}
});
}
@ -274,6 +280,14 @@ class TileLayer extends Layer {
for (var i = this._tiles.children.length - 1; i >= 0; i--) {
this._tiles.remove(this._tiles.children[i]);
}
if (!this._tilesPicking || !this._tilesPicking.children) {
return;
}
for (var i = this._tilesPicking.children.length - 1; i >= 0; i--) {
this._tilesPicking.remove(this._tilesPicking.children[i]);
}
}
// Return a new tile instance
@ -314,10 +328,21 @@ class TileLayer extends Layer {
}
}
// Remove tile from picking scene
this.removeFromPicking(this._tilesPicking);
if (this._tilesPicking.children) {
// Remove all tiles
for (var i = this._tilesPicking.children.length - 1; i >= 0; i--) {
this._tilesPicking.remove(this._tilesPicking.children[i]);
}
}
this._tileCache.destroy();
this._tileCache = null;
this._tiles = null;
this._tilesPicking = null;
this._frustum = null;
super.destroy();

Wyświetl plik

@ -48,6 +48,7 @@ var Buffer = (function() {
return geometry;
};
// TODO: Make picking IDs optional
var createGeometry = function(attributes, offset) {
var geometry = new THREE.BufferGeometry();
@ -56,6 +57,9 @@ var Buffer = (function() {
var normals = new Float32Array(attributes.facesCount * 9);
var colours = new Float32Array(attributes.facesCount * 9);
// One component per vertex per face (1 x 3 = 3)
var pickingIds = new Float32Array(attributes.facesCount * 3);
var pA = new THREE.Vector3();
var pB = new THREE.Vector3();
var pC = new THREE.Vector3();
@ -67,11 +71,13 @@ var Buffer = (function() {
var _faces;
var _vertices;
var _colour;
var _pickingId;
var lastIndex = 0;
for (var i = 0; i < attributes.faces.length; i++) {
_faces = attributes.faces[i];
_vertices = attributes.vertices[i];
_colour = attributes.colours[i];
_pickingId = attributes.pickingIds[i];
for (var j = 0; j < _faces.length; j++) {
// Array of vertex indexes for the face
@ -139,6 +145,10 @@ var Buffer = (function() {
colours[lastIndex * 9 + 4] = c2[1];
colours[lastIndex * 9 + 5] = c2[2];
pickingIds[lastIndex * 9 + 3] = _pickingId;
pickingIds[lastIndex * 9 + 4] = _pickingId;
pickingIds[lastIndex * 9 + 5] = _pickingId;
vertices[lastIndex * 9 + 6] = cx;
vertices[lastIndex * 9 + 7] = cy;
vertices[lastIndex * 9 + 8] = cz;
@ -151,6 +161,10 @@ var Buffer = (function() {
colours[lastIndex * 9 + 7] = c3[1];
colours[lastIndex * 9 + 8] = c3[2];
pickingIds[lastIndex * 3 + 0] = _pickingId;
pickingIds[lastIndex * 3 + 1] = _pickingId;
pickingIds[lastIndex * 3 + 2] = _pickingId;
lastIndex++;
}
}
@ -159,6 +173,7 @@ var Buffer = (function() {
geometry.addAttribute('position', new THREE.BufferAttribute(vertices, 3));
geometry.addAttribute('normal', new THREE.BufferAttribute(normals, 3));
geometry.addAttribute('color', new THREE.BufferAttribute(colours, 3));
geometry.addAttribute('pickingId', new THREE.BufferAttribute(pickingIds, 1));
geometry.computeBoundingBox();