How to get other 3D objects within a radius of a position in three.js
Asked Answered
B

1

5

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.

Buttonwood answered 16/7, 2013 at 1:3 Comment(2)
Three.js r.59 has an octree example.Girand
@Girand wow I didn't even see that, but yeah based on what I see in the renderer, that example likely has some functions that might prove useful :) thanks I'll take a look at thatButtonwood
P
14

Collision checking is a more general problem and I think you'll have more success if you think about it in a context outside of Three.js. There are a number of methods for managing large numbers of objects that need to check for collision with each other. Here are a few optimizations that might be relevant to you here:

The first optimization is for each object to have a boolean property indicating whether it moved since the last physics update. If both objects you're comparing haven't moved, you don't need to recalculate collision. This is mostly relevant if you have a large number of objects in a steady state (like crates you can push around). There are a number of other optimizations you can build on top of this; for example, often if two objects haven't moved, they won't be colliding, because if they were colliding they would be recoiling (moving apart).

The second optimization is that you usually only need to check collision within a certain distance. For example, if you know that all of your objects are smaller than 100 units, then you can just check whether (x1-x2)^2 + (y1-y2)^2 + (z1-z2)^2 > 100^2. If the check is true (indicating the distance between the two objects is large) then you don't need to calculate detailed collisions. In fact this is more or less the near/far optimization that Raycaster provides for you, but you are not making use of it in your code, since you are always calling the intersectObject method.

The third optimization is that you are allocating a bunch of new Raycaster and related objects in every physics update. Instead, you can keep a pool of Raycasters (or even a single Raycaster) and just update their properties. This will avoid a lot of garbage collecting.

Finally, the most common generalized approach to dealing with a large number of collideable objects is called spatial partitioning. The idea is basically that you divide your world into a given number of spaces and keep track of which space objects are in. Then, when you need to calculate collision, you only need to check other objects that are in the same space. The most common approach for doing this is to use an Octree (an 8-ary tree). As WestLangley mentioned, Three.js has an Octree implementation starting in r59, along with an example (source). Here is a reasonable introduction to the concept of spatial partitioning using 2D examples.

Outside of these optimizations, if you need to do anything particularly complicated, you may want to consider using an external physics library, which will manage optimizations like these for you. The most popular ones for use with Three.js at the moment are Physijs, Cannon.js, and Ammo.js.

Poona answered 16/7, 2013 at 4:54 Comment(2)
that is a brilliantly worded post. Thank you so much. You covered quite a few points I'd not even considered. I had considered partitioning objects but couldn't come up with a way to handle objects on the "border" of a partition. It seems as though the Octree implementation is exactly what I need to handle that optimization. Despite the function/variable names, in this case I don't actually need to check collision, but rather distance. This system is being used as a pre-filter for "line of sight", aggro detection, etc in an MMO server I'm working on.Buttonwood
I went ahead and flagged this as my answer since implementing the Octree definitely had a much better performance boost than what I was originally thinking :) I didn't even know they added an Octree to three.js :P Thanks again!Buttonwood

© 2022 - 2024 — McMap. All rights reserved.