如何在three.js中获取半径范围内的其他3D对象

5
我在three.js中有一个3D场景,我需要获取一组距离源对象X范围内的对象数组。目前,我使用的示例在场景中利用射线投射(raycasting)和for循环迭代“可碰撞物体”数组。如果每个数组中的对象都必须从自身到数组中的其他对象进行射线投射,则此方法的复杂度呈指数级增长,因此我认为一定有更好的方法来处理这个问题。随着可碰撞物体数组的增长,这会对性能产生巨大的影响。
//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();

您可以在此处查看 JSFiddle。

如果您将指定的变量更改为较大的数字,比如 200,则会发现处理时间开始失控。我觉得必须有一种更简单的方式来减少数组的大小,所以我查看了 three.js 的 Raycaster 文档,并注意到 nearfar 属性都说“此值表示可以根据距离丢弃哪些对象”,因此我推断出在投射所有光线之前,应该使用某些内部函数根据距离细化结果。

我对此进行了一些调查,并在 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 );

};

}(),

我想找到一种更好的方法,以获取场景中与源对象距离为X的所有对象。我甚至不需要使用射线投射,因为我对网格碰撞不感兴趣,只需列出源对象周围距离在X范围内的对象列表。由于场景设置的原因,我甚至不需要递归进入这些对象的子元素。因此,我认为three.js中必须有某个内部函数或类可以仅使用THREE.Vector3对象和数学来通过距离来细化它们。在这种情况下,比起射线投射,这将是更便宜的计算量。如果three.js中已经有一个处理此问题的函数,我就不想从头开始重新创建一个。我也意识到,这可能是一个非常冗长的问题,但我想确保在后来搜索此类内容的其他人能够看到所有细节和附加信息。

1
Three.js r.59有一个八叉树示例 - WestLangley
@WestLangley 哇,我甚至没有看到那个,但是根据我在渲染器中看到的内容,这个例子可能有一些有用的函数 :) 谢谢,我会去看看的。 - Brian
1个回答

12

碰撞检测是一个更普遍的问题,如果您在Three.js之外的上下文中考虑它,我认为您会更成功。有许多管理需要相互检查碰撞的大量对象的方法。以下是一些可能与您相关的优化:

第一个优化是每个对象都具有布尔属性,指示其是否自上次物理更新以来移动。如果您要比较的两个对象都没有移动,则不需要重新计算碰撞。这在稳定状态下拥有大量对象(如可以推动的板条箱)时最为相关。您可以在此基础上构建许多其他优化;例如,通常情况下,如果两个对象没有移动,则它们不会发生碰撞,因为如果它们发生碰撞,它们将会反弹(分离移动)。

第二个优化是,通常你只需要在一定距离内检查碰撞。例如,如果你知道所有的对象都小于100个单位,那么你只需要检查是否 (x1-x2)^2 + (y1-y2)^2 + (z1-z2)^2 > 100^2。如果检查为真(表示两个对象之间的距离很大),那么你就不需要计算详细的碰撞。实际上,这或多或少是Raycaster为你提供的near/far优化,但你在代码中没有利用它,因为你总是调用intersectObject方法。
第三个优化是,在每次物理更新中,你都会分配一堆新的Raycaster和相关对象。相反,你可以保留一个Raycaster池(甚至只有一个Raycaster),并更新它们的属性。这将避免大量的垃圾回收。

最常见的应对大量碰撞对象的一般化方法是称为空间划分。基本思想是将世界划分为一定数量的空间,并跟踪哪些对象在其中。然后,在需要计算碰撞时,只需要检查在同一空间中的其他对象即可。实现这一点的最常见方法是使用 八叉树(一个 8 叉树)。如 WestLangley 所述,Three.js 自 r59 开始带有 八叉树实现,还有一个 示例源代码)。这里是一个使用 2D 示例介绍空间划分概念的合理入门。

除了这些优化之外,如果您需要进行特别复杂的操作,您可能需要考虑使用外部物理库,它将为您管理这些优化。目前与Three.js一起使用最流行的是Physijs, Cannon.js, 和 Ammo.js


这是一篇措辞精妙的帖子。非常感谢。您涵盖了我甚至没有考虑过的许多要点。我曾考虑过对对象进行分区,但无法想出如何处理位于分区“边界”的对象。似乎八叉树实现正是我需要处理该优化的方法。尽管在这种情况下,我实际上不需要检查碰撞,而是距离。该系统被用作“视线”,敌对检测等的预过滤器,在我正在开发的MMO服务器中使用。 - Brian
我已经将这个标记为我的答案,因为实现Octree的性能提升比我最初想象的要好得多 :) 我甚至不知道他们在three.js中添加了一个Octree :P 再次感谢! - Brian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接