|
@@ -54,6 +54,7 @@
|
|
|
|
|
|
const pointer = new THREE.Vector2();
|
|
|
const offset = new THREE.Vector3( 10, 10, 10 );
|
|
|
+ const clearColor = new THREE.Color();
|
|
|
|
|
|
init();
|
|
|
animate();
|
|
@@ -68,17 +69,66 @@
|
|
|
scene = new THREE.Scene();
|
|
|
scene.background = new THREE.Color( 0xffffff );
|
|
|
|
|
|
- pickingScene = new THREE.Scene();
|
|
|
- pickingTexture = new THREE.WebGLRenderTarget( 1, 1 );
|
|
|
-
|
|
|
scene.add( new THREE.AmbientLight( 0x555555 ) );
|
|
|
|
|
|
const light = new THREE.SpotLight( 0xffffff, 1.5 );
|
|
|
light.position.set( 0, 500, 2000 );
|
|
|
scene.add( light );
|
|
|
|
|
|
- const pickingMaterial = new THREE.MeshBasicMaterial( { vertexColors: true } );
|
|
|
- const defaultMaterial = new THREE.MeshPhongMaterial( { color: 0xffffff, flatShading: true, vertexColors: true, shininess: 0 } );
|
|
|
+ const defaultMaterial = new THREE.MeshPhongMaterial( {
|
|
|
+ color: 0xffffff,
|
|
|
+ flatShading: true,
|
|
|
+ vertexColors: true,
|
|
|
+ shininess: 0
|
|
|
+ } );
|
|
|
+
|
|
|
+ // set up the picking texture to use a 32 bit integer so we can write and read integer ids from it
|
|
|
+ pickingScene = new THREE.Scene();
|
|
|
+ pickingTexture = new THREE.WebGLRenderTarget( 1, 1, {
|
|
|
+
|
|
|
+ type: THREE.IntType,
|
|
|
+ format: THREE.RedIntegerFormat,
|
|
|
+ internalFormat: 'R32I',
|
|
|
+
|
|
|
+ } );
|
|
|
+ const pickingMaterial = new THREE.ShaderMaterial( {
|
|
|
+
|
|
|
+ glslVersion: THREE.GLSL3,
|
|
|
+
|
|
|
+ vertexShader: /* glsl */`
|
|
|
+ attribute int id;
|
|
|
+ flat varying int vid;
|
|
|
+ void main() {
|
|
|
+
|
|
|
+ vid = id;
|
|
|
+ gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
|
|
|
+
|
|
|
+ }
|
|
|
+ `,
|
|
|
+
|
|
|
+ fragmentShader: /* glsl */`
|
|
|
+ layout(location = 0) out int out_id;
|
|
|
+ flat varying int vid;
|
|
|
+
|
|
|
+ void main() {
|
|
|
+
|
|
|
+ out_id = vid;
|
|
|
+
|
|
|
+ }
|
|
|
+ `,
|
|
|
+
|
|
|
+ } );
|
|
|
+
|
|
|
+ function applyId( geometry, id ) {
|
|
|
+
|
|
|
+ const position = geometry.attributes.position;
|
|
|
+ const array = new Int32Array( position.count );
|
|
|
+ array.fill( id );
|
|
|
+
|
|
|
+ const bufferAttribute = new THREE.BufferAttribute( array, 1, false );
|
|
|
+ geometry.setAttribute( 'id', bufferAttribute );
|
|
|
+
|
|
|
+ }
|
|
|
|
|
|
function applyVertexColors( geometry, color ) {
|
|
|
|
|
@@ -95,16 +145,14 @@
|
|
|
|
|
|
}
|
|
|
|
|
|
- const geometriesDrawn = [];
|
|
|
- const geometriesPicking = [];
|
|
|
-
|
|
|
+ const geometries = [];
|
|
|
const matrix = new THREE.Matrix4();
|
|
|
const quaternion = new THREE.Quaternion();
|
|
|
const color = new THREE.Color();
|
|
|
|
|
|
for ( let i = 0; i < 5000; i ++ ) {
|
|
|
|
|
|
- let geometry = new THREE.BoxGeometry();
|
|
|
+ const geometry = new THREE.BoxGeometry();
|
|
|
|
|
|
const position = new THREE.Vector3();
|
|
|
position.x = Math.random() * 10000 - 5000;
|
|
@@ -126,19 +174,12 @@
|
|
|
|
|
|
geometry.applyMatrix4( matrix );
|
|
|
|
|
|
- // give the geometry's vertices a random color, to be displayed
|
|
|
-
|
|
|
+ // give the geometry's vertices a random color to be displayed and an integer
|
|
|
+ // identifier as a vertex attribute so boxes can be identified after being merged.
|
|
|
applyVertexColors( geometry, color.setHex( Math.random() * 0xffffff ) );
|
|
|
+ applyId( geometry, i );
|
|
|
|
|
|
- geometriesDrawn.push( geometry );
|
|
|
-
|
|
|
- geometry = geometry.clone();
|
|
|
-
|
|
|
- // give the geometry's vertices a color corresponding to the "id"
|
|
|
-
|
|
|
- applyVertexColors( geometry, color.setHex( i, THREE.NoColorSpace ) );
|
|
|
-
|
|
|
- geometriesPicking.push( geometry );
|
|
|
+ geometries.push( geometry );
|
|
|
|
|
|
pickingData[ i ] = {
|
|
|
|
|
@@ -150,15 +191,14 @@
|
|
|
|
|
|
}
|
|
|
|
|
|
- const objects = new THREE.Mesh( BufferGeometryUtils.mergeGeometries( geometriesDrawn ), defaultMaterial );
|
|
|
- scene.add( objects );
|
|
|
-
|
|
|
- pickingScene.add( new THREE.Mesh( BufferGeometryUtils.mergeGeometries( geometriesPicking ), pickingMaterial ) );
|
|
|
+ const mergedGeometry = BufferGeometryUtils.mergeGeometries( geometries );
|
|
|
+ scene.add( new THREE.Mesh( mergedGeometry, defaultMaterial ) );
|
|
|
+ pickingScene.add( new THREE.Mesh( mergedGeometry, pickingMaterial ) );
|
|
|
|
|
|
highlightBox = new THREE.Mesh(
|
|
|
new THREE.BoxGeometry(),
|
|
|
- new THREE.MeshLambertMaterial( { color: 0xffff00 }
|
|
|
- ) );
|
|
|
+ new THREE.MeshLambertMaterial( { color: 0xffff00 } )
|
|
|
+ );
|
|
|
scene.add( highlightBox );
|
|
|
|
|
|
renderer = new THREE.WebGLRenderer( { antialias: true } );
|
|
@@ -202,46 +242,41 @@
|
|
|
|
|
|
function pick() {
|
|
|
|
|
|
- //render the picking scene off-screen
|
|
|
-
|
|
|
+ // render the picking scene off-screen
|
|
|
// set the view offset to represent just a single pixel under the mouse
|
|
|
-
|
|
|
- camera.setViewOffset( renderer.domElement.width, renderer.domElement.height, pointer.x * window.devicePixelRatio | 0, pointer.y * window.devicePixelRatio | 0, 1, 1 );
|
|
|
+ const dpr = window.devicePixelRatio;
|
|
|
+ camera.setViewOffset(
|
|
|
+ renderer.domElement.width, renderer.domElement.height,
|
|
|
+ Math.floor( pointer.x * dpr ), Math.floor( pointer.y * dpr ),
|
|
|
+ 1, 1
|
|
|
+ );
|
|
|
|
|
|
// render the scene
|
|
|
-
|
|
|
renderer.setRenderTarget( pickingTexture );
|
|
|
+
|
|
|
+ // clear the background to - 1 meaning no item was hit
|
|
|
+ clearColor.setRGB( - 1, - 1, - 1 );
|
|
|
+ renderer.setClearColor( clearColor );
|
|
|
renderer.render( pickingScene, camera );
|
|
|
|
|
|
// clear the view offset so rendering returns to normal
|
|
|
-
|
|
|
camera.clearViewOffset();
|
|
|
|
|
|
- //create buffer for reading single pixel
|
|
|
-
|
|
|
- const pixelBuffer = new Uint8Array( 4 );
|
|
|
-
|
|
|
- //read the pixel
|
|
|
+ // create buffer for reading single pixel
|
|
|
+ const pixelBuffer = new Int32Array( 1 );
|
|
|
|
|
|
+ // read the pixel
|
|
|
renderer.readRenderTargetPixels( pickingTexture, 0, 0, 1, 1, pixelBuffer );
|
|
|
|
|
|
- //interpret the pixel as an ID
|
|
|
-
|
|
|
- const id = ( pixelBuffer[ 0 ] << 16 ) | ( pixelBuffer[ 1 ] << 8 ) | ( pixelBuffer[ 2 ] );
|
|
|
- const data = pickingData[ id ];
|
|
|
+ const id = pixelBuffer[ 0 ];
|
|
|
+ if ( id !== - 1 ) {
|
|
|
|
|
|
- if ( data ) {
|
|
|
-
|
|
|
- //move our highlightBox so that it surrounds the picked object
|
|
|
-
|
|
|
- if ( data.position && data.rotation && data.scale ) {
|
|
|
-
|
|
|
- highlightBox.position.copy( data.position );
|
|
|
- highlightBox.rotation.copy( data.rotation );
|
|
|
- highlightBox.scale.copy( data.scale ).add( offset );
|
|
|
- highlightBox.visible = true;
|
|
|
-
|
|
|
- }
|
|
|
+ // move our highlightBox so that it surrounds the picked object
|
|
|
+ const data = pickingData[ id ];
|
|
|
+ highlightBox.position.copy( data.position );
|
|
|
+ highlightBox.rotation.copy( data.rotation );
|
|
|
+ highlightBox.scale.copy( data.scale ).add( offset );
|
|
|
+ highlightBox.visible = true;
|
|
|
|
|
|
} else {
|
|
|
|