Selectionbox and orthographic camera

Hi all.

First to thank for the great support you allways have provided me.

I have modified the SelectionBox and SelectionHelper modules published in threejs in order to work with instances.

The point is that if I try to use it with an orthographic camera the selection is not accurate. It works properly with a perspective camera.

Note:
In order to enable|disable area picking just double click left mouse button.
In order to test the code with an orthographic camera just comment perspective camera initialization and uncomment orthographic camera initialization in init function.

Here the code:

<!DOCTYPE html>
<html lang="en">
	<head><base href="./three.js/examples/" target="_blank">
		<title>three.js webgl - scene animation</title>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
		<style>
			body {
				font-family: Monospace;
				background-color: #f0f0f0;
				margin: 0px;
				overflow: hidden;
			}
			
			.selectBox {
				border: 1px solid #55aaff;
				background-color: rgba(75, 160, 255, 0.3);
				position: fixed;
			}
		</style>
	</head>

	<body>

		<div id="container"></div>

		<div id="info">
		<a href="http://threejs.org" target="_blank">three.js</a> webgl - scene animation - <a href="https://clara.io/view/96106133-2e99-40cf-8abd-64defd153e61">Three Gears Scene</a> courtesy of David Sarno</div>

		<script type="text/javascript" src="https://threejs.org/build/three.js"></script>
		<script type="text/javascript" src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
		
		<script id="vertInstanced" type="x-shader/x-vertex">
			precision highp float;
			float EPSILON = 0.3421;
			in float visible;
			in float selected;
			in vec4 lodInfo;
			in vec3 mcol0;
			in vec3 mcol1;
			in vec3 mcol2;
			in vec3 mcol3;
			#ifdef PICKING
				in float pn_UUID;
			#else
				in vec4 color;
			#endif
			out vec3 vPosition;
			out float vFragDepth; 
			out float intensity;    
			out vec4 vColor;
			out float vVisible;
			uniform float logDepthBufFC;
			vec3 lightDirection;
			void main() {
				vVisible = visible;
				if (vVisible > 0.0) {
					mat4 matrix = mat4(
						vec4( mcol0, 0 ),
						vec4( mcol1, 0 ),
						vec4( mcol2, 0 ),
						vec4( mcol3, 1 )
					);
					vec3 tPosition = (matrix * vec4(position, 1.0)).xyz;
					vec3 positionEye = vec3( modelViewMatrix * vec4(tPosition, 1.0 ) ).xyz;
					//vec3 positionEye = ( modelViewMatrix * matrix * vec4( position, 1.0 ) ).xyz;
					vec3 cameraPos1 =vec3( modelViewMatrix * vec4(cameraPosition, 1.0 ) ).xyz;
					
					gl_Position = projectionMatrix * vec4( positionEye, 1.0 );
					vec3 cameraP = (projectionMatrix * vec4(cameraPos1, 1.0)).xyz;
					vFragDepth = 1.0 + gl_Position.w;
					gl_Position.z = log2(max( EPSILON, gl_Position.w + 1.0 )) * logDepthBufFC;
					lightDirection = normalize(vec3(cameraPosition.x-gl_Position.x, cameraPosition.y-gl_Position.y, cameraPosition.z-gl_Position.z));
					intensity =  pow( 1.0 - dot (normal, lightDirection), 1.0);
					vPosition = vec3(gl_Position.x, gl_Position.y, gl_Position.z);
					
					//if ( distance(cameraPos1, positionEye) > lodInfo.a ) {
					//	vVisible = 0.0;
					//}
					
					#ifdef PICKING
						vColor = vec4(tPosition, pn_UUID);
					#else
						if ( selected > 0.0 ) {
							vColor = vec4(1.0, color.g, color.b, 0.5);
						} else {
							vColor = color;
						}
					#endif
				}
			}
		</script>

		<script id="fragInstanced" type="x-shader/x-fragment">
			precision highp float;
			in float vFragDepth;
			in float intensity;
			in float vVisible;
			in vec4 vColor;
			in vec3 vPosition;
			out vec4 myColor;
			uniform float logDepthBufFC;
			void main()	{
				if ( vVisible > 0.0) {
					#if defined(USE_LOGDEPTHBUF) && defined(USE_LOGDEPTHBUF_EXT)                                    
						gl_FragDepthEXT = log2(vFragDepth) * logDepthBufFC * 0.5;                                 
					#endif
					#ifdef PICKING
						myColor = vColor;
					#else
						myColor = vColor;
						myColor.rgb *= intensity * 0.65;
					#endif
				} else {
					discard;
				}
			}
		</script>

		<script>
			var container, stats;
			var selectionBox, helper;
			var isSelecting = false;
			var camera, controls, scene, renderer;
			var objects = [];
			var materialList  = [];
			var geometryList  = [];
			var dummyVector = new THREE.Vector3();
			//var instanceCount  = 10;
			var objectCount  = 0;
			
			var vert = document.getElementById( 'vertInstanced' ).textContent;
			var frag = document.getElementById( 'fragInstanced' ).textContent;
			var material = new THREE.ShaderMaterial( {
				vertexShader: "#version 300 es\n" + vert,
				fragmentShader: "#version 300 es\n" +frag,
				transparent: true,
				side: THREE.DoubleSide,
				clipping: true,
				clipShadows: false
			} );
			materialList.push( material );
			var pickingMaterial = new THREE.ShaderMaterial( {
				vertexShader: "#version 300 es\n" +"#define PICKING\n" + vert,
				fragmentShader: "#version 300 es\n" +"#define PICKING\n" + frag,
				clipping: true,
				clipShadows: false
			} );
			materialList.push( pickingMaterial );
			console.log(materialList);

			// Internal Functions:
			 
			var SelectionHelper = function( selectionBox, renderer, cssClassName ) {
				var selectionHelper = this;
				selectionHelper.isEnabled = isSelecting;
				selectionHelper.element = document.createElement( "div" );
				selectionHelper.element.classList.add( cssClassName );
				selectionHelper.element.style.pointerEvents = "none";
				selectionHelper.renderer = renderer;
				selectionHelper.startPoint = { x: 0, y: 0 };
				selectionHelper.pointTopLeft = { x: 0, y: 0 };
				selectionHelper.pointBottomRight = { x: 0, y: 0 };
				selectionHelper.isDown = false;
				selectionHelper.renderer.domElement.addEventListener( "mousedown", function ( event ) {
					selectionHelper.isDown = true;
					selectionHelper.onSelectStart( event );
				}.bind( selectionHelper ), false );
				selectionHelper.renderer.domElement.addEventListener( "mousemove", function ( event ) {
					if ( selectionHelper.isDown ) {
						selectionHelper.onSelectMove( event );
					}
				}.bind( selectionHelper ), false );
				
				selectionHelper.renderer.domElement.addEventListener( "mouseup", function ( event ) {
					selectionHelper.isDown = false;
					selectionHelper.onSelectOver( event );
				}.bind( selectionHelper ), false );
				
				selectionHelper.onSelectStart = function ( event ) {
					if (isSelecting == true) {
						selectionHelper.renderer.domElement.parentElement.appendChild( selectionHelper.element );
						selectionHelper.element.style.left = event.clientX + "px";
						selectionHelper.element.style.top = event.clientY + "px";
						selectionHelper.element.style.width = "0px";
						selectionHelper.element.style.height = "0px";
						selectionHelper.startPoint.x = event.clientX;
						selectionHelper.startPoint.y = event.clientY;
					}
				}

				selectionHelper.onSelectMove = function ( event ) {
					if (isSelecting == true) {
						selectionHelper.pointBottomRight.x = Math.max( selectionHelper.startPoint.x, event.clientX );
						selectionHelper.pointBottomRight.y = Math.max( selectionHelper.startPoint.y, event.clientY );
						selectionHelper.pointTopLeft.x = Math.min( selectionHelper.startPoint.x, event.clientX );
						selectionHelper.pointTopLeft.y = Math.min( selectionHelper.startPoint.y, event.clientY );
						selectionHelper.element.style.left = selectionHelper.pointTopLeft.x + "px";
						selectionHelper.element.style.top = selectionHelper.pointTopLeft.y + "px";
						selectionHelper.element.style.width = ( selectionHelper.pointBottomRight.x - selectionHelper.pointTopLeft.x ) + "px";
						selectionHelper.element.style.height = ( selectionHelper.pointBottomRight.y - selectionHelper.pointTopLeft.y ) + "px";
					}
				}

				selectionHelper.onSelectOver = function ( event ) {
					if (isSelecting == true) {
						selectionHelper.element.parentElement.removeChild( selectionHelper.element );
					}
				}
			}

			var SelectionBox = function( camera, scene, deep ) {
				var selectionBox = this;
				selectionBox.camera = camera;
				selectionBox.scene = scene;
				selectionBox.startPoint = new THREE.Vector3();
				selectionBox.endPoint = new THREE.Vector3();
				selectionBox.collection = [];
				selectionBox.deep = deep || Number.MAX_VALUE;
				
				selectionBox.select = function ( startPoint, endPoint ) {
					selectionBox.startPoint = startPoint || selectionBox.startPoint;
					selectionBox.endPoint = endPoint || selectionBox.endPoint;
					selectionBox.collection = [];
					var boxSelectionFrustum = selectionBox.createFrustum( selectionBox.startPoint, selectionBox.endPoint );
					console.log(boxSelectionFrustum);
					selectionBox.searchChildInFrustum( boxSelectionFrustum, selectionBox.scene );
					console.log("selectionBox.collection:", selectionBox.collection);
					return selectionBox.collection;
				}

				selectionBox.createFrustum = function ( startPoint, endPoint ) {
					console.log("startPoint", startPoint, "endPoint", endPoint);
					startPoint = startPoint || selectionBox.startPoint;
					endPoint = endPoint || selectionBox.endPoint
					
					selectionBox.camera.updateProjectionMatrix();
					selectionBox.camera.updateMatrixWorld();
					selectionBox.camera.updateMatrix();
					
					var tmpPoint = startPoint.clone();
					tmpPoint.x = Math.min( startPoint.x, endPoint.x );
					tmpPoint.y = Math.max( startPoint.y, endPoint.y );
					endPoint.x = Math.max( startPoint.x, endPoint.x );
					endPoint.y = Math.min( startPoint.y, endPoint.y );
					
					var vecNear = selectionBox.camera.position.clone();
					var vecTopLeft = tmpPoint.clone();
					var vecTopRight = new THREE.Vector3( endPoint.x, tmpPoint.y, 0 );
					var vecDownRight = endPoint.clone();
					var vecDownLeft = new THREE.Vector3( tmpPoint.x, endPoint.y, 0 );
					vecTopLeft.unproject( selectionBox.camera );
					vecTopRight.unproject( selectionBox.camera );
					vecDownRight.unproject( selectionBox.camera );
					vecDownLeft.unproject( selectionBox.camera );
					
					var vectemp1 = vecTopLeft.clone().sub( vecNear );
					var vectemp2 = vecTopRight.clone().sub( vecNear );
					var vectemp3 = vecDownRight.clone().sub( vecNear );
					vectemp1.normalize();
					vectemp2.normalize();
					vectemp3.normalize();
					
					vectemp1.multiplyScalar( selectionBox.deep );
					vectemp2.multiplyScalar( selectionBox.deep );
					vectemp3.multiplyScalar( selectionBox.deep );
					vectemp1.add( vecNear );
					vectemp2.add( vecNear );
					vectemp3.add( vecNear );
					
					var planeTop = new THREE.Plane();
					planeTop.setFromCoplanarPoints( vecNear, vecTopLeft, vecTopRight );
					
					var planeRight = new THREE.Plane();
					planeRight.setFromCoplanarPoints( vecNear, vecTopRight, vecDownRight );
					
					var planeDown = new THREE.Plane();
					planeDown.setFromCoplanarPoints( vecDownRight, vecDownLeft, vecNear );
					
					var planeLeft = new THREE.Plane();
					planeLeft.setFromCoplanarPoints( vecDownLeft, vecTopLeft, vecNear );
					
					var planeFront = new THREE.Plane();
					planeFront.setFromCoplanarPoints( vecTopRight, vecDownRight, vecDownLeft );
					
					var planeBack = new THREE.Plane();
					planeBack.setFromCoplanarPoints( vectemp3, vectemp2, vectemp1 );
					planeBack.normal = planeBack.normal.multiplyScalar( -1 );
					
					return new THREE.Frustum( planeTop, planeRight, planeDown, planeLeft, planeFront, planeBack );
				}


				selectionBox.searchChildInFrustum = function ( frustum, scene ) {
					var geometry
					selectionBox.collection = [];
					scene.traverse(function(object) {
						if ( object.geometry != undefined ) {
							//console.log(object.geometry);
							object.geometry.name = object.geometry.uuid;
							object.geometry.computeBoundingSphere();
							object.geometry.computeBoundingBox();
							if (object.geometry.attributes.pn_UUID != undefined) {
								var uuidArray = [];
								var colorArray = [];
								var matrixArray = [];
								for (var i in object.geometry.attributes.pn_UUID.array) {
									var matrix = new THREE.Matrix4();
									matrix.elements = [
										object.geometry.attributes.mcol0.array[i*3], object.geometry.attributes.mcol0.array[(i*3)+1], object.geometry.attributes.mcol0.array[(i*3)+2], 0,
										object.geometry.attributes.mcol1.array[i*3], object.geometry.attributes.mcol1.array[(i*3)+1], object.geometry.attributes.mcol1.array[(i*3)+2], 0,
										object.geometry.attributes.mcol2.array[i*3], object.geometry.attributes.mcol2.array[(i*3)+1], object.geometry.attributes.mcol2.array[(i*3)+2], 0,
										object.geometry.attributes.mcol3.array[i*3], object.geometry.attributes.mcol3.array[(i*3)+1], object.geometry.attributes.mcol3.array[(i*3)+2], 1
									];
									var center = object.geometry.boundingSphere.center.clone().applyMatrix4( matrix );
									//console.log("i:", i, "pn_UUID:", object.geometry.attributes.pn_UUID.array[i]);
									if ( frustum.containsPoint( center ) ) {
										//console.log("frustum.containsPoint:" + object.geometry.attributes.pn_UUID.array[i]);
										uuidArray.push(object.geometry.userData.uuid2IdArray[object.geometry.attributes.pn_UUID.array[i]]);
										colorArray.push([
											object.geometry.attributes.color.array[i*4],
											object.geometry.attributes.color.array[(i*4)+1],
											object.geometry.attributes.color.array[(i*4)+2],
											object.geometry.attributes.color.array[(i*4)+3]
										]);
										var matrix = new THREE.Matrix4();
										matrix.elements = [
											object.geometry.attributes.mcol0.array[i*3], object.geometry.attributes.mcol0.array[(i*3)+1], object.geometry.attributes.mcol0.array[(i*3)+2], 0,
											object.geometry.attributes.mcol1.array[i*3], object.geometry.attributes.mcol1.array[(i*3)+1], object.geometry.attributes.mcol1.array[(i*3)+2], 0,
											object.geometry.attributes.mcol2.array[i*3], object.geometry.attributes.mcol2.array[(i*3)+1], object.geometry.attributes.mcol2.array[(i*3)+2], 0,
											object.geometry.attributes.mcol3.array[i*3], object.geometry.attributes.mcol3.array[(i*3)+1], object.geometry.attributes.mcol3.array[(i*3)+2], 0,
										];
										matrixArray.push(matrix);
										//console.log("object.geometry.attributes.pn_UUID.array[i]", object.geometry.attributes.pn_UUID.array[i]);
									}
								}
								if (uuidArray.length > 0) {
									selectionBox.collection.push( {
										geo: object.geometry,
										id:object.geometry.name,
										uuidArray: uuidArray,
										colorArray: colorArray,
										matrixArray: matrixArray
									});
									console.log(selectionBox.collection);
								}
							}
						}
					});
				}
			}

			var randomizeMatrix = function() {
				var position = new THREE.Vector3();
				var rotation = new THREE.Euler();
				var quaternion = new THREE.Quaternion();
				var scale = new THREE.Vector3();
				return function( matrix ) {
					position.x = Math.random() * 800 - 90;
					position.y = Math.random() * 800 - 90;
					position.z = Math.random() * 800 - 90;
					rotation.x = Math.random() * 2 * Math.PI;
					rotation.y = Math.random() * 2 * Math.PI;
					rotation.z = Math.random() * 2 * Math.PI;
					quaternion.setFromEuler( rotation, false );
					scale.x = scale.y = scale.z = 1;
					matrix.compose( position, quaternion, scale );
				};
			}();
			
			// BEGIN:
			init();
			animate();

			function makeInstanced( geo, instanceCount ) {
				// geometry
				geometryList.push( geo );
				geo.computeBoundingSphere();
				geo.computeBoundingBox();
				geometrySize = geo.boundingBox.getSize(dummyVector);
				console.log("geometrySize:", geometrySize);
				var sizeArray = [geometrySize.x, geometrySize.y, geometrySize.z].sort((a, b) => b - a);
				var occlussionDistance = (sizeArray[0]+ sizeArray[1]) * 0.5 * 500;
				//console.log("occlussionDistance:", occlussionDistance);
				geometryCenter = geo.boundingBox.getCenter(dummyVector);
				//console.log("geometryCenter:", geometryCenter);
				
				var igeo = new THREE.InstancedBufferGeometry();
				igeo.index = geo.index;
				igeo.attributes.position = geo.attributes.position;
				igeo.attributes.normal = geo.attributes.normal;
				igeo.attributes.uv = geo.attributes.uv;
				geometryList.push( igeo );
				
				igeo.userData = {
					uuid2IdArray: []
				}
				
				
				// Instanced Attributes Definition:
				
				// Visible:
				var visible = new THREE.InstancedBufferAttribute( new Float32Array( instanceCount * 1 ), 1, true, 1 );
				
				// Selected:
				var selected = new THREE.InstancedBufferAttribute( new Float32Array( instanceCount * 1 ), 1, true, 1 );
				
				// Absolute Transformation Matrix:
				var mcol0 = new THREE.InstancedBufferAttribute( new Float32Array( instanceCount * 3 ), 3, true, 1 );
				var mcol1 = new THREE.InstancedBufferAttribute( new Float32Array( instanceCount * 3 ), 3, true, 1 );
				var mcol2 = new THREE.InstancedBufferAttribute( new Float32Array( instanceCount * 3 ), 3, true, 1 );
				var mcol3 = new THREE.InstancedBufferAttribute( new Float32Array( instanceCount * 3 ), 3, true, 1 );
				
				// Center and Occlussion Distance of object from camera position:
				var lodInfo = new THREE.InstancedBufferAttribute( new Float32Array( instanceCount * 4 ), 4,true, 1 );
				
				// Scene Colors:
				var colors = new THREE.InstancedBufferAttribute( new Float32Array( instanceCount * 4 ), 4, true, 1 );
				
				// UUID information
				var pn_UUID = new THREE.InstancedBufferAttribute( new Float32Array( instanceCount * 1 ), 1 ,true, 1 );
				
				// Instanced Attributes Assignation:
				var matrix = new THREE.Matrix4();
				var me = matrix.elements;
				console.log(instanceCount);
				for ( var i=0;  i < instanceCount; i ++ ) {
					objectCount ++;
					visible.array[i] = 1;
					
					selected.array[i] = 0;
					
					randomizeMatrix( matrix );
					
					var instanceCenter =  geometryCenter.applyMatrix4(matrix);
					//console.log("instanceCenter:", instanceCenter);
					mcol0.setXYZ( i, me[ 0 ], me[ 1 ], me[ 2 ] );
					mcol1.setXYZ( i, me[ 4 ], me[ 5 ], me[ 6 ] );
					mcol2.setXYZ( i, me[ 8 ], me[ 9 ], me[ 10 ] );
					mcol3.setXYZ( i, me[ 12 ], me[ 13 ], me[ 14 ] );
					
					lodInfo.array[i*4] = instanceCenter.x;
					lodInfo.array[(i*4) + 1] = instanceCenter.y;
					lodInfo.array[(i*4) + 2] = instanceCenter.z;
					lodInfo.array[(i*4) + 3] = occlussionDistance;
					
					colors.array[i*4] = Math.random();
					colors.array[(i*4) + 1] = Math.random();
					colors.array[(i*4) + 2] = Math.random();
					colors.array[(i*4) + 3] = Math.random();
					
					//colors.array[i*4] = 0.0;
					//colors.array[(i*4) + 1] = 0.0;
					//colors.array[(i*4) + 2] = 1.0;
					//colors.array[(i*4) + 3] = 1.0;
					igeo.userData.uuid2IdArray[objectCount] = i;
					pn_UUID.array[i] = objectCount;
				}
				
				// Instanced Attributes addition:
				igeo.addAttribute( 'visible', visible );
				igeo.addAttribute( 'selected', selected );
				igeo.addAttribute( 'mcol0', mcol0 );
				igeo.addAttribute( 'mcol1', mcol1 );
				igeo.addAttribute( 'mcol2', mcol2 );
				igeo.addAttribute( 'mcol3', mcol3 );
				igeo.addAttribute( 'lodInfo', lodInfo );
				igeo.addAttribute( 'color', colors );
				igeo.addAttribute( 'pn_UUID', pn_UUID );
				// mesh
				mesh = new THREE.Mesh( igeo, material );
				mesh.frustumCulled = false;
				console.log(mesh);
				scene.add( mesh );
			}

			function updateInstances(geometry, uuidArray, attribute, value) {
				//console.log("geometry 1:", geometry);
				for (var i in uuidArray) {
					var uuid = uuidArray[i];
					switch (attribute) {
						case "color": // Replaces color in instanced attributes
							if (value.length == 1) {
								geometry.attributes.color.array[uuid*4] = value[0][0];
								geometry.attributes.color.array[(uuid*4)+1] = value[0][1];
								geometry.attributes.color.array[(uuid*4)+2] = value[0][2];
								geometry.attributes.color.array[(uuid*4)+3] = value[0][3];
							} else {
								geometry.attributes.color.array[uuid*4] = value[i][0];
								geometry.attributes.color.array[(uuid*4)+1] = value[i][1];
								geometry.attributes.color.array[(uuid*4)+2] = value[i][2];
								geometry.attributes.color.array[(uuid*4)+3] = value[i][3];
							}
							geometry.attributes.color.needsUpdate = true;
							break;
						case "visible": // Replaces visible in instanced attributes
							if (value.length == 1) {
								geometry.attributes.visible.array[uuid] = value[0];
							} else {
								geometry.attributes.visible.array[uuid] = value[i];
							}
							geometry.attributes.visible.needsUpdate = true;
							break;
						case "tMatrix": // multiplies absmatrix in instanced attributes by passed matrix
							break;
						case "rMatrix": // Replaces absmatrix in instanced attributes
							break;
						case "selected": // Replaces selected in instanced attributes
							if (value.length == 1) {
								geometry.attributes.selected.array[uuid] = value[0];
							} else {
								geometry.attributes.selected.array[uuid] = value[i];
							}
							geometry.attributes.selected.needsUpdate = true;
							break;
						case "pickable": // Replaces color in instanced attributes
							if (value.length == 1) {
								geometry.attributes.pickable.array[uuid] = value[0];
							} else {
								geometry.attributes.pickable.array[uuid] = value[i];
							}
							geometry.attributes.pickable.needsUpdate = true;
							break;
					}
				}
			}

			function switchCamera() {
				if (camera instanceof THREE.PerspectiveCamera) {
					camera = new THREE.OrthographicCamera(
					window.innerWidth / - 16, window.innerWidth / 16,window.innerHeight / 16, window.innerHeight / - 16, -200, 500 );
					camera.position.x = 2;
					camera.position.y = 1;
					camera.position.z = 3;
					camera.lookAt(scene.position);
					this.perspective = "Orthographic";
				} else {
					camera = new THREE.PerspectiveCamera(45,
					window.innerWidth / window.innerHeight, 0.1, 1000);
					camera.position.x = 120;
					camera.position.y = 60;
					camera.position.z = 180;
					camera.lookAt(scene.position);
					this.perspective = "Perspective";
				}
			}


			function init() {
				camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 5000 );
				
				//frustumSize = 1000;
				//var aspect = window.innerWidth / window.innerHeight;
				//camera = new THREE.OrthographicCamera( frustumSize * aspect / - 2, frustumSize * aspect / 2, frustumSize / 2, frustumSize / - 2, 0.1, 1000 );
				
				camera.position.z = 1000;
				scene = new THREE.Scene();
				scene.background = new THREE.Color( 0xf0f0f0 );
				var geometry1 = new THREE.BoxBufferGeometry( 20, 20, 20 );
				makeInstanced( geometry1, 500 )
				var geometry2 = new THREE.SphereBufferGeometry( 10, 8, 6 );
				makeInstanced( geometry2, 500 )
				var geometry3 = new THREE.CylinderBufferGeometry( 10, 5, 20, 32 );
				makeInstanced( geometry3, 500 )
				var geometry4 = new THREE.CylinderBufferGeometry( 10, 10, 20, 32 );
				makeInstanced( geometry4, 500 )
				container = document.getElementById( "container" );
				var canvas = document.createElement( 'canvas' );
				gl = canvas.getContext( 'webgl2', { antialias: true } );
				gl.getExtension('EXT_color_buffer_float');
				gl.enable(gl.SAMPLE_ALPHA_TO_COVERAGE);
				renderer = new THREE.WebGLRenderer( {  
					logarithmicDepthBuffer: true, 
					canvas: canvas, context: gl 
				} );
				
				renderer.setClearColor( 0xffffff );
				renderer.autoClear = true;
				renderer.setPixelRatio( window.devicePixelRatio );
				renderer.setSize( window.innerWidth, window.innerHeight );
				renderer.sortObjects = true;
				container.appendChild( renderer.domElement );
				
				var info = document.createElement( 'div' );
				info.style.position = 'absolute';
				info.style.top = '0px';
				info.style.width = '100%';
				info.style.textAlign = 'center';
				info.innerHTML = '<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> webgl - box selection';
				container.appendChild( info );
				

				
				
				controls = new THREE.OrbitControls( camera, renderer.domElement );
				controls.addEventListener( 'change', render );
				renderer.domElement.addEventListener('dblclick', onDblclickGl, false);
				window.addEventListener( 'resize', onWindowResize, false );
			}

			function onDblclickGl(e){
				console.log(e);
				if (isSelecting == false) {
					isSelecting =true;
					if (controls != undefined) {
						console.log("removing Listener in controls");
						controls.enabled = false;
					}
					selectionBox = new SelectionBox( camera, scene );
					helper = new SelectionHelper( selectionBox, renderer, "selectBox" );
				} else {
					isSelecting = false;
					if (selectionBox != undefined && helper != undefined) {
						console.log(selectionBox, helper);
					}
					selectionBox = undefined;
					helper = undefined;
					controls.enabled = true;
				}
			}

			function onWindowResize() {
				camera.aspect = window.innerWidth / window.innerHeight;
				camera.updateProjectionMatrix();
				renderer.setSize( window.innerWidth, window.innerHeight );
			}

			function animate() {
				requestAnimationFrame( animate );
				render();
				//stats.update();
				//rendererStats.update(renderer);
			}

			function render() {
				renderer.render( scene, camera );
			}

			// Events:
			document.addEventListener("mousedown", function ( event ) {
				console.log("mousedown",isSelecting);
				console.log("geometries:", renderer.info.memory.geometries, 
					"calls:", renderer.info.render.calls, 
					"frame:", renderer.info.render.frame, 
					"lines:", renderer.info.render.lines,
					"points:", renderer.info.render.points, 
					"triangles:", renderer.info.render.triangles 
				);
				if (isSelecting == true) {
					for ( var i in selectionBox.collection ) {
						// Change attributes
						//updateInstances(selectionBox.collection[i].geo, selectionBox.collection[i].uuidArray, "color", [[1,0,0,1]])
						updateInstances(selectionBox.collection[i].geo, selectionBox.collection[i].uuidArray, "selected", [1])
					}
					selectionBox.startPoint.set(
						( event.clientX / window.innerWidth ) * 2 - 1,
						-( event.clientY / window.innerHeight ) * 2 + 1,
						0.01 );
				}
			} );

			document.addEventListener( "mousemove", function ( event ) {
				if (isSelecting == true) {
					if ( helper.isDown ) {
						for ( var i = 0; i < selectionBox.collection.length; i++ ) {
							// Reset all objects:
							updateInstances(selectionBox.collection[i].geo, selectionBox.collection[i].uuidArray, "selected", [0])
						}
						selectionBox.endPoint.set(
							( event.clientX / window.innerWidth ) * 2 - 1,
							-( event.clientY / window.innerHeight ) * 2 + 1,
							0.5 );
						var allSelected = selectionBox.select();
						for ( var i = 0; i < allSelected.length; i++ ) {
							// Change attributes
							updateInstances(selectionBox.collection[i].geo, selectionBox.collection[i].uuidArray, "selected", [1])
						}
					}
				}
			} );

			document.addEventListener("mouseup", function ( event ) {
				if (isSelecting == true) {
					selectionBox.endPoint.set(
						(event.clientX / window.innerWidth) * 2 - 1,
						-(event.clientY / window.innerHeight) * 2 + 1,
						0.5 );
					var allSelected = selectionBox.select();
					for ( var i = 0; i < allSelected.length; i++ ) {
						// Finally change attrs
						updateInstances(selectionBox.collection[i].geo, selectionBox.collection[i].uuidArray, "selected", [1])
					}
				}
			});
		</script>
	</body>
</html>

Best regards

Hi! I have a same problem, do you have a solution now? Intersection via orthographic camera

Hi.

Not yet.

I am still fighting with this issue.

Best regards

Hi, i solved my problem.
Maybe this solution helps you too

            raycaster.ray.origin.set( raymouse.x, raymouse.y, - 1 ).unproject( cameraOrtho );

but i used GPU Picker library

could you share a working example?
In fact I am also using my own gpu picking. and tried unproject without accurate result

you can see the source code of my project. To switch the orthographic camera, please press “O”

https://alovert.ru

Hi Arkadiy.

I have had a look into your project but I cannot seen any reference to Area Selector.

Could you exaplain where is the code linked to area piking?

My goal is to be able to select all objects inside a rectangle projected in the 3d canvas.

Have a look to:
https://threejs.org/examples/?q=selec#misc_boxselection

Many thanks in advanced.

Many thanks in advanced.

Hi! I use this library https://github.com/brianxu/GPUPicker
For picking i use this code

function onMouseClick(e) {
        e.preventDefault();

        mouse.x = e.clientX;
        mouse.y = e.clientY;
        var raymouse = new THREE.Vector2();
        raymouse.x = ( e.clientX / renderer.domElement.clientWidth ) * 2 - 1;
        raymouse.y = - ( e.clientY / renderer.domElement.clientHeight ) * 2 + 1;

        raycaster.setFromCamera(raymouse, activeCamera);
        
        if (activeCamera === cameraOrtho) {
            raycaster.ray.origin.set( raymouse.x, raymouse.y, - 1 ).unproject( cameraOrtho );
        }

        
        var intersect = gpuPicker.pick(mouse, raycaster);
        
        
		if (intersect) {

But I just randomly tried this code

raycaster.ray.origin.set( raymouse.x, raymouse.y, - 1 ).unproject( cameraOrtho );

and it worked

Hi.

The problem is not the same.

I am trying to create a frustum from an area selection, not just gpu picking.

By the way, I recommend you to depvelop your own GPU picking instead using gpuPicker. It is really simple do it by programming a shader.

Another, thing, I also recommend you using instances instead using each time a cloned mesh, it will reduce the draw calls only up to 2 calls increasing the render performance.

: )

Best regards

Thanks for the advice!
I am new to programming, and I study the library of three js for the third week. I never worked with shaders, tried to copy the code from the examples, but quickly got confused and refused this idea. Could you give me some example code, how can I add objects through instances? I have been thinking about this decision for a long time. Ofcourse, if it`s not difficult for you. Thank you very much!

There are two different ways to face the problem of dynamic adding | removing instances from the scene.

The easiest way is to set a hard limit of instances for example (100000) for each asset you will have in your scene. You can control the current instances available in the scene by changing maxInstancedCount BufferGeometry property.

The only problem then , is rebuilding the typed array for each instanced Attribute each time yo make a change (add|remove)

In your case, I have only seen 2 assets, the structure and the connector.

You can set as limit for example 10000 instances for each asset.
You will hace also to set the meshes in this way:
mesh.frustumCulled = false;
in order to avoid global mesh frustum culled

Your scene will be in this way:

Scene:
- Mesh1:
- BufferGeometry
- BufferGeometryAttributes
- InstancedBufferGeometryAttributes
index
drawRange
maxInstancedCount
- material: RawShaderMaterial
- Mesh2:
- BufferGeometry
- BufferGeometryAttributes
- InstancedBufferGeometryAttributes
index
drawRange
maxInstancedCount
- material: RawShaderMaterial

Basically, the BufferAttributes are global for all instances and the InstancedAttributes are for defined differences between instances like position, rotation, color, etc.

You have a very simple example to start bwith at examples in which you can see how you can increase | reduce the number of instances:
https://threejs.org/examples/?q=instan#webgl_buffergeometry_instancing

You will have to play with maxInstancedCount and rebuild the instanced attributes each time you will add| remove a instance, but you will find it is quite easy as they are simple typed Arrays, and you will realize that typed arrays are really fast. When maxInstancedCount will be 0 then set drawRange also to 0 ans mesh.visinle to false it will increase the performance.

Another thing I recommend you is to work with GLB Draco compressed format, it is really fast.

Other recommendations:

I would load the assets only one time and save the in an array of assets to be able to clean them if you need.
The same for materials
Use always BufferGeometry compressed formats and load only one time if few assets like your case.

Build the scene as an static scene (no animation) if you don’t really need animations.

if there are no animations, control when you want that mesh.matrix will be updated by setting
mesh.matrixAutoUpdate = false;
mesh.matrixWorldNeedsUpdate = false;

When you will change anything in the global mesh, the change them again and render.

You will find lots of recommendation better explained in stackoverflow portal.

You you example, with only 2 assets I think you would be able to set a scene with 100k instances in a low level PC at 60 frames/sec without any problem.

Good luck in your project.

Wow! Very good documentation for me. Much thanks, good luck you too!

Hello, Alejandro_Insua! Maybe you can help me with problem? Do you know, how to intersect non indexed buffer geometry? I want to detect intersection between two defferent faces of two different non indexed buffergeometry. I set ray from each faces, but raycaster dont work with non indexed geometry.I think that i can try to get color of intersection face, like your shader picker, but i dont know how do it. Thank you very much!%D0%91%D0%B5%D0%B7%D1%8B%D0%BC%D1%8F%D0%BD%D0%BD%D1%8B%D0%B9-1

Hi Arkadiy.

You can convert non-indexed BufferGeometry to indexed BufferGeometry.

Just add this constructor at the beginning of your code of put it into a separate file and source it.

I don’t know if this helps you, tell me if not.

THREE.BufferGeometry.prototype.toIndexed = function() {
		let prec = 0;
		let list = [];
		let vertices = {};
		function store(x, y, z, v) {
			const id = Math.floor( x * prec ) + '_' + Math.floor( y * prec ) + '_'  + Math.floor( z * prec );
			if ( vertices[id] === undefined ) {
				vertices[id] = list.length;
				list.push(v);
			}
			return vertices[id];
		}
		function indexBufferGeometry(src, dst) {
			const position = src.attributes.position.array;
			const faceCount = ( position.length / 3 ) / 3;
			const type = faceCount * 3 > 65536 ? Uint32Array : Uint16Array;
			const indexArray = new type(faceCount * 3);
			for ( let i = 0, l = faceCount; i < l; i ++ ) {
				const offset = i * 9;
				indexArray[i * 3    ] = store(position[offset], position[offset + 1], position[offset + 2], i * 3);
				indexArray[i * 3 + 1] = store(position[offset + 3], position[offset + 4], position[offset + 5], i * 3 + 1);
				indexArray[i * 3 + 2] = store(position[offset + 6], position[offset + 7], position[offset + 8], i * 3 + 2);
			}
			dst.index = new THREE.BufferAttribute(indexArray, 1);
			const count = list.length;
			for ( let key in src.attributes ) {
				const src_attribute = src.attributes[key];
				const dst_attribute = new THREE.BufferAttribute(new src_attribute.array.constructor(count * src_attribute.itemSize), src_attribute.itemSize);
				const dst_array = dst_attribute.array;
				const src_array = src_attribute.array;
				switch ( src_attribute.itemSize ) {
					case 1:
						for ( let i = 0, l = list.length; i < l; i ++ ) {
							dst_array[i] = src_array[list[i]];
						}
						break;
					case 2:
						for ( let i = 0, l = list.length; i < l; i ++ ) {
							const index = list[i] * 2;
							const offset = i * 2;
							dst_array[offset] = src_array[index];
							dst_array[offset + 1] = src_array[index + 1];
						}
						break;
					case 3:
						for ( let i = 0, l = list.length; i < l; i ++ ) {
							const index = list[i] * 3;
							const offset = i * 3;
							dst_array[offset] = src_array[index];
							dst_array[offset + 1] = src_array[index + 1];
							dst_array[offset + 2] = src_array[index + 2];
						}
						break;
					case 4:
						for ( let i = 0, l = list.length; i < l; i ++ ) {
							const index = list[i] * 4;
							const offset = i * 4;
							dst_array[offset] = src_array[index];
							dst_array[offset+ 1] = src_array[index + 1];
							dst_array[offset + 2] = src_array[index + 2];
							dst_array[offset + 3] = src_array[index + 3];
						}
						break;
				}
				dst.attributes[key] = dst_attribute;
			}
			dst.boundingSphere = new THREE.Sphere();
			dst.computeBoundingSphere();
			dst.boundingBox = new THREE.Box3();
			dst.computeBoundingBox();
			// Release data
			vertices = {};
			list = [];
		}
		return function( precision ) {
			prec = Math.pow(10,  precision || 6 );
			const geometry = new THREE.BufferGeometry;
			indexBufferGeometry(this, geometry);
			geometry.computeBoundingSphere();
			geometry.computeBoundingBox();
			return geometry;
		}
	}();

Hi again! Thank you for replying! I create my non indexed geometry like this:
var box = new THREE.Mesh(geometry.toNonIdexed(), material);
I can just delete this function and my geometry will indexed. But in this way i have 24 count position in buffer attribute (instead 36 in non indexed) and my ray`s direction from faces will not correct. I set ray through this code:

var pos = box.geometry.attributes.position; 
var ori = new THREE.Vector3();
var dir = new THREE.Vector3();
var a = new THREE.Vector3(),
b = new THREE.Vector3(),
c = new THREE.Vector3(),
tri = new THREE.Triangle();
						var faces = pos.count / 3;
						for (let i = 0; i < faces; i++) {
							a.fromBufferAttribute(pos, i * 3 + 0);
							b.fromBufferAttribute(pos, i * 3 + 1);
							c.fromBufferAttribute(pos, i * 3 + 2);
							tri.set(a, b, c);
							tri.getMidpoint(ori);
							tri.getNormal(dir)

                            //visualize ray
    						scene.add(new THREE.ArrowHelper(raycaster.ray.direction, raycaster.ray.origin, 30, 0xff0000));


							raycaster.set(ori, dir.negate());
							intersects = raycaster.intersectObject(box, true);
							if (intersects.length > 0) {
								
								console.log(intersects[0]);
							
							}

						}

Indexed geometry ->


Non indexed (all works ok)->

And i have a question, what is better for perfomance. Indexed buffer geometry or non Indexed?

When you have an indexed buffer geometry, coordinates of vertices must be taken by indices of vertices in faces. So you have to work with .index property.

Take a look at this topic: Volume of THREE.BufferGeometry()

It works with both indexed and non-indexed buffer geometries. See, how it gets coordinates for p1 , p2 , p3 vectors in dependence on type of geometry.

Thank you, its works!

Hi Alejandro
Did you solve the problem with the SelectionBox?
I’m having the same problem in my project