我正在使用three.js。
我的场景中有两个网格几何体。
如果这些几何相交(或如果翻译则会相交)我想将其检测为碰撞。
如何使用three.js执行碰撞检测?如果three.js没有碰撞检测设施,是否还有其他库可以与three.js结合使用?
答案 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,我没用过,但应该是一个更友好的库,不需要你做辅助环境