如何检测three.js中的碰撞?

时间:2012-07-13 15:38:49

标签: javascript three.js webgl collision-detection

我正在使用three.js。

我的场景中有两个网格几何体。

如果这些几何相交(或如果翻译则会相交)我想将其检测为碰撞。

如何使用three.js执行碰撞检测?如果three.js没有碰撞检测设施,是否还有其他库可以与three.js结合使用?

5 个答案:

答案 0 :(得分:95)

在Three.js中,似乎不再支持实用程序CollisionUtils.js和Collisions.js,并且mrdoob(three.js的创建者)自己建议更新到最新版本的three.js并使用Ray类为了这个目的而改为。接下来是一种方法。

这个想法是这样的:假设我们要检查一个名为“Player”的给定网格是否与名为“collidableMeshList”的数组中包含的任何网格相交。我们可以做的是创建一组光线,这些光线从Player网格(Player.position)的坐标开始,并向Player网格几何体中的每个顶点延伸。每个Ray都有一个名为“intersectObjects”的方法,它返回Ray与之相交的对象数组,以及每个对象的距离(从Ray的原点开始测量)。如果到交叉点的距离小于玩家位置和几何体顶点之间的距离,则碰撞发生在玩家网格的内部 - 我们可能称之为“实际”碰撞。

我在:

发布了一个工作示例

http://stemkoski.github.io/Three.js/Collision-Detection.html

您可以使用箭头键移动红色线框立方体,并使用W / A / S / D旋转它。当它与其中一个蓝色立方体相交时,如上所述,对于每个交叉点,单词“Hit”将出现在屏幕顶部一次。代码的重要部分如下。

for (var vertexIndex = 0; vertexIndex < Player.geometry.vertices.length; vertexIndex++)
{       
    var localVertex = Player.geometry.vertices[vertexIndex].clone();
    var globalVertex = Player.matrix.multiplyVector3(localVertex);
    var directionVector = globalVertex.subSelf( Player.position );

    var ray = new THREE.Ray( Player.position, directionVector.clone().normalize() );
    var collisionResults = ray.intersectObjects( collidableMeshList );
    if ( collisionResults.length > 0 && collisionResults[0].distance < directionVector.length() ) 
    {
        // a collision occurred... do something...
    }
}

这种特殊方法存在两个潜在的问题。

(1)当光线的原点在网格M内时,光线和M之间不会发生碰撞。

(2)小的物体(相对于玩家网格物体)可能在各种射线之间“滑动”,因此不会记录碰撞。减少此问题几率的两种可能方法是编写代码,以便小对象创建光线并从其视角进行碰撞检测工作,或在网格上包含更多顶点(例如使用CubeGeometry(100,100,100, 20,20,20)而不是CubeGeometry(100,100,100,1,1,1)。)后一种方法可能会导致性能下降,所以我建议谨慎使用它。

我希望其他人能够通过解决这个问题的方式为这个问题做出贡献。在开发此处描述的解决方案之前,我在这方面已经挣扎了很长时间。

答案 1 :(得分:6)

在SO问题中,这个问题实在太广泛了,但为了润滑网站的SEO,这里有几个简单的起点:

如果您想要非常简单的碰撞检测而不是完整的物理引擎,那么请查看Three.js: Simple Collision Detection

另一方面,如果你想要一些碰撞响应,而不仅仅是“做A和B碰撞吗?”,请看一下Physijs,这是一个非常容易使用的Ammo.js包装器three.js所

答案 2 :(得分:1)

由于我的其他答案有限,因此我做了一些更准确的事情,只有在发生碰撞时才返回true,而在没有碰撞时(但有时在仍然存在时)返回false 无论如何,请首先执行以下功能:

function rt(a,b) {
  let d = [b]; 
  let e = a.position.clone();
  let f = a.geometry.vertices.length;
  let g = a.position;
  let h = a.matrix;
  let i = a.geometry.vertices;
    for (var vertexIndex = f-1; vertexIndex >= 0; vertexIndex--) {      
        let localVertex = i[vertexIndex].clone();
        let globalVertex = localVertex.applyMatrix4(h);
        let directionVector = globalVertex.sub(g);
        
        let ray = new THREE.Raycaster(e,directionVector.clone().normalize());
        let collisionResults = ray.intersectObjects(d);
        if ( collisionResults.length > 0 && collisionResults[0].distance < directionVector.length() ) { 
            return true;
    }
    }
 return false;
}

以上功能与该问题的答案相同 Lee Stemkoski(我通过键入使我赞扬),但是我进行了更改,因此它运行更快,并且您无需创建网格阵列。确定步骤2:创建此函数:

function ft(a,b) {
  return rt(a,b)||rt(b,a)||(a.position.z==b.position.z&&a.position.x==b.position.x&&a.position.y==b.position.y)
}

如果网格A的中心不在网格B中,并且网格B的中心不在A中,则返回true;或者位置相等并且它们实际上在接触。如果您缩放一个或两个网格,则此方法仍然有效。 我在以下位置有一个示例:https://3d-collsion-test-r.glitch.me/

答案 3 :(得分:0)

仅适用于BoxGeometry和BoxBufferGeometry

创建以下功能:

function checkTouching(a, d) {
  let b1 = a.position.y - a.geometry.parameters.height / 2;
  let t1 = a.position.y + a.geometry.parameters.height / 2;
  let r1 = a.position.x + a.geometry.parameters.width / 2;
  let l1 = a.position.x - a.geometry.parameters.width / 2;
  let f1 = a.position.z - a.geometry.parameters.depth / 2;
  let B1 = a.position.z + a.geometry.parameters.depth / 2;
  let b2 = d.position.y - d.geometry.parameters.height / 2;
  let t2 = d.position.y + d.geometry.parameters.height / 2;
  let r2 = d.position.x + d.geometry.parameters.width / 2;
  let l2 = d.position.x - d.geometry.parameters.width / 2;
  let f2 = d.position.z - d.geometry.parameters.depth / 2;
  let B2 = d.position.z + d.geometry.parameters.depth / 2;
  if (t1 < b2 || r1 < l2 || b1 > t2 || l1 > r2 || f1 > B2 || B1 < f2) {
    return false;
  }
  return true;
}

在这样的条件语句中使用它:

if (checkTouching(cube1,cube2)) {
alert("collision!")
}

我在https://3d-collion-test.glitch.me/

有一个使用此示例

注意:如果旋转(或缩放)一个(或两个)多维数据集/基元,它将检测到好像它们没有被旋转(或缩放)

答案 4 :(得分:0)

这似乎已经解决了,但如果您不习惯使用光线投射和创建自己的物理环境,我有一个更简单的解决方案。

CANNON.js 和 AMMO.js 都是建立在 THREE.js 之上的物理库。它们创建了一个二级物理环境,然后您将对象位置与该场景联系起来以模拟物理环境。文档很简单,可以遵循 CANNON 并且它是我使用的,但它自 4 年前发布以来一直没有更新。该 repo 已经分叉,社区将其更新为 cannon-es。我会在这里留下一个代码片段,这样你就可以看到它是如何工作的

/**
* Floor
*/
const floorShape = new CANNON.Plane()
const floorBody = new CANNON.Body()
floorBody.mass = 0
floorBody.addShape(floorShape)
floorBody.quaternion.setFromAxisAngle(
    new CANNON.Vec3(-1,0,0),
    Math.PI / 2
)
world.addBody(floorBody)

const floor = new THREE.Mesh(
new THREE.PlaneGeometry(10, 10),
new THREE.MeshStandardMaterial({
    color: '#777777',
    metalness: 0.3,
    roughness: 0.4,
    envMap: environmentMapTexture
})
)
floor.receiveShadow = true
floor.rotation.x = - Math.PI * 0.5
scene.add(floor)

// THREE mesh
const mesh = new THREE.Mesh(
    sphereGeometry,
    sphereMaterial
)
mesh.scale.set(1,1,1)
mesh.castShadow = true
mesh.position.copy({x: 0, y: 3, z: 0})
scene.add(mesh)

// Cannon
const shape = new CANNON.Sphere(1)
const body = new CANNON.Body({
    mass: 1,
    shape,
    material: concretePlasticMaterial
})
body.position.copy({x: 0, y: 3, z: 0})
world.addBody(body)

这会产生一个地板和一个球,但也会在 CANNON.js 环境中创建相同的东西。

const tick = () =>
{
    const elapsedTime = clock.getElapsedTime() 
    const deltaTime = elapsedTime - oldElapsedTime
    oldElapsedTime = elapsedTime

    // Update Physics World
    mesh.position.copy(body.position)

    world.step(1/60,deltaTime,3)


    // Render
    renderer.render(scene, camera)

    // Call tick again on the next frame
    window.requestAnimationFrame(tick)
}

在此之后,您只需根据物理场景的位置更新动画函数中 THREE.js 场景的位置。

请查看文档,因为它可能看起来比实际复杂。使用物理库将是模拟碰撞的最简单方法。也看看Physi.js,我没用过,但应该是一个更友好的库,不需要你做辅助环境