kopia lustrzana https://github.com/robhawkes/vizicities
Initial (untidy) picking implementation
rodzic
398db98975
commit
7ef8197306
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
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
};
|
|
@ -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;
|
|
@ -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;
|
||||
})();
|
|
@ -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;
|
|
@ -40,7 +40,7 @@ class World extends EventEmitter {
|
|||
}
|
||||
|
||||
_initEngine() {
|
||||
this._engine = Engine(this._container);
|
||||
this._engine = Engine(this._container, this);
|
||||
|
||||
// Engine events
|
||||
//
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue