I have a 3D scene in three.js in which I need to get an array of objects that are within X range of a source object. At the moment, the example I'm using is utilizing raycasting inside of a for loop that iterates an array of "collidable objects" that exist in the scene. I feel like there must be a better way to handle this because this approach is exponentially more complex if every object in the array has to raycast from itself to every other object in the array. This has massive performance impacts as the array of collidable objects grows.
//hold collidable objects
var collidableObjects = [];
var scene = new THREE.Scene();
var cubeGeo = new THREE.CubeGeometry( 10 , 10 , 10 );
var materialA = new THREE.MeshBasicMaterial( { color: 0xff0000 } );
var materialB = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
var cubeA = new THREE.Mesh( cubeGeo , materialA );
collidableObjects.push( cubeA );
scene.add( cubeA );
//Change this variable to a larger number to see the processing time explode
var range = 100;
for( var x = 0 ; x < range ; x += 20 ) {
for( var z = 0; z < range ; z += 20 ) {
if( x === 0 && z === 0 ) continue;
var cube = new THREE.Mesh( cubeGeo , materialB );
scene.add( cube );
cube.position.x = x;
cube.position.z = z;
collidableObjects.push( cube );
var cube = cube.clone();
scene.add( cube );
cube.position.x = x * -1;
cube.position.z = z;
collidableObjects.push( cube );
var cube = cube.clone();
scene.add( cube );
cube.position.x = x;
cube.position.z = z * -1;
collidableObjects.push( cube );
var cube = cube.clone();
scene.add( cube );
cube.position.x = x * -1;
cube.position.z = z * -1;
collidableObjects.push( cube );
}
}
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
camera.position.y = 200;
camera.lookAt( scene.position );
function render() {
//requestAnimationFrame(render);
renderer.render(scene, camera);
console.log( getObjectsWithinRange( cubeA , 30 ) );
}
function getObjectsWithinRange( source , range ) {
var startTime = new Date().getTime();
var inRange = [];
for( var i = 0; i < collidableObjects.length ; ++i ) {
var ray = new THREE.Raycaster( source.position , collidableObjects[i].position , 0 , range );
if( ( obj = ray.intersectObject( collidableObjects[i] ) ) && obj.length ) {
inRange.push( obj[0] );
}
}
var endTime = new Date().getTime();
console.log( 'Processing Time: ' , endTime - startTime );
return inRange;
}
render();
You can see the JSfiddle of this here.
If you change the indicated variable to a larger number say 200, then you'll see the processing time start to get out of control. I feel like there has to be a simpler way to reduce down the array of doing this so I looked at the documentation for the Raycaster of three.js and I noticed that both the near
and far
attributes say "This value indicates which objects can be discarded based on the distance." so I presume there's some internal function that is used to refine the results down based on distance before casting all the rays.
I did a little digging on this and came up with a single function inside of Ray.js
.
distanceToPoint: function () {
var v1 = new THREE.Vector3();
return function ( point ) {
var directionDistance = v1.subVectors( point, this.origin ).dot( this.direction );
// point behind the ray
if ( directionDistance < 0 ) {
return this.origin.distanceTo( point );
}
v1.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin );
return v1.distanceTo( point );
};
}(),
I guess what I'm looking for is a better way to get all of the objects in the scene that are within X radius of a source object. I don't even need to use the Raycasting because I'm not interested in mesh collision, rather just a list of the objects within X radius of the source object. I don't even need to recurse into the children of those objects because of the way the scene is set up. So I feel like there must be some internal function or something that simply uses the THREE.Vector3
objects and math to refine them by distance. That has to be a lot cheaper math to run than Raycasting in this case. If there's already a function that handles this somewhere in three.js, I don't want to recreate one from scratch. I also realize this may be a very long-winded question for what could very well be a single line answer, but I wanted to make sure I have all the details and whatnot here in case someone else looking to do this searches for it later.