如何在three.js中检测冲突
How to detect collision in three.js?
我使用的是three.js.
我的场景中有两个网格几何体。
如果这些几何体相交(或者如果平移,将相交),我想将其检测为碰撞。
如何使用three.js执行碰撞检测?如果three.js没有冲突检测功能,那么我是否可以将其他库与three.js一起使用?
在Three.js中,似乎不再支持实用程序CollisionUtils.js和Collisions.js,mrdoob(Three.js的创建者)自己建议更新到Three.js的最新版本,并为此使用Ray类。以下是一种方法。
其想法是:假设我们想检查一个名为"Player"的给定网格是否与名为"collidableMeshList"的数组中包含的任何网格相交。我们可以做的是创建一组光线,这些光线从Player网格的坐标(Player.position)开始,并朝着Player网格几何体中的每个顶点延伸。每个射线都有一个名为"intersectObjects"的方法,该方法返回射线与之相交的对象阵列,以及到每个对象的距离(从射线原点开始测量)。如果到交点的距离小于玩家位置和几何体顶点之间的距离,则碰撞发生在玩家网格的内部,我们可能称之为"实际"碰撞。
我在上发布了一个工作示例
http://stemkoski.github.io/Three.js/Collision-Detection.html
可以使用箭头键移动红色线框立方体,并使用W/A/S/D旋转它。当它与其中一个蓝色立方体相交时,如上所述,每个相交处的屏幕顶部都会出现一次"命中"一词。代码的重要部分如下。
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) 较小的对象(相对于Player网格)可能在各种光线之间"滑动",因此不会记录碰撞。减少出现此问题的可能性的两种可能方法是编写代码,以便小对象创建射线并从它们的角度进行碰撞检测,或者在网格上包括更多顶点(例如,使用CubeGeometry(100、100、20、20)而不是CubeGeometrics(100、10、100、1、1)。)后一种方法可能会影响性能,所以我建议谨慎使用它。
我希望其他人将为这个问题作出贡献,提出解决这个问题的办法。在开发这里描述的解决方案之前,我自己也为此奋斗了很长一段时间。
Lee答案的更新版本,适用于最新版本的three.js
for (var vertexIndex = 0; vertexIndex < Player.geometry.attributes.position.array.length; vertexIndex++)
{
var localVertex = new THREE.Vector3().fromBufferAttribute(Player.geometry.attributes.position, vertexIndex).clone();
var globalVertex = localVertex.applyMatrix4(Player.matrix);
var directionVector = globalVertex.sub( Player.position );
var ray = new THREE.Raycaster( Player.position, directionVector.clone().normalize() );
var collisionResults = ray.intersectObjects( collidableMeshList );
if ( collisionResults.length > 0 && collisionResults[0].distance < directionVector.length() )
{
// a collision occurred... do something...
}
}
这确实是一个太宽泛的话题,无法在SO问题中涵盖,但为了稍微润滑网站的SEO,这里有几个简单的起点:
如果你想要真正简单的碰撞检测,而不是一个完整的物理引擎,那么请查看(由于没有更多现有网站,链接被删除)
另一方面,如果你确实想要一些碰撞响应,而不仅仅是";A和B撞了吗&";,看看(由于没有现有网站,链接被删除),这是一个超级容易使用的Ammo.js包装器,它是围绕Three.js 构建的
仅适用于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/
注意:如果你旋转(或缩放)其中一个(或两个)立方体/棱镜,它会检测到它们好像还没有被旋转(或被缩放)
因为我的另一个答案是有限的,我做了其他更准确的东西,只在发生碰撞时返回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/
这似乎已经解决了,但如果您不喜欢使用光线投射和创建自己的物理环境,我有一个更简单的解决方案。
CANNON.js和AMMO.js都是建立在THRE.js之上的物理库。它们创建了一个辅助物理环境,您可以将对象位置绑定到该场景以模拟物理环境。CANNON的文档很简单,我也使用它,但自4年前发布以来,它一直没有更新。回购协议已经分叉,一个社区不断更新。我会在这里留下一个代码片段,这样你就可以看到它是如何工作的
/**
* 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,我从未使用过它,但它应该是一个更友好的库,不需要您制作辅助环境
在我的threejs版本中,我只有geometry.attributes.position.array
,没有geometry.vertices
。要将其转换为顶点,我使用以下TS函数:
export const getVerticesForObject = (obj: THREE.Mesh): THREE.Vector3[] => {
const bufferVertices = obj.geometry.attributes.position.array;
const vertices: THREE.Vector3[] = [];
for (let i = 0; i < bufferVertices.length; i += 3) {
vertices.push(
new THREE.Vector3(
bufferVertices[i] + obj.position.x,
bufferVertices[i + 1] + obj.position.y,
bufferVertices[i + 2] + obj.position.z
)
);
}
return vertices;
};
我为每个维度传递对象的位置,因为默认情况下bufferVertices是相对于对象的中心的,出于我的目的,我希望它们是全局的。
我还编写了一个基于顶点检测碰撞的小函数。它可以选择对非常涉及的对象的顶点进行采样,或者检查所有顶点与其他对象顶点的接近程度:
const COLLISION_DISTANCE = 0.025;
const SAMPLE_SIZE = 50;
export const detectCollision = ({
collider,
collidables,
method,
}: DetectCollisionParams): GameObject | undefined => {
const { geometry, position } = collider.obj;
if (!geometry.boundingSphere) return;
const colliderCenter = new THREE.Vector3(position.x, position.y, position.z);
const colliderSampleVertices =
method === "sample"
? _.sampleSize(getVerticesForObject(collider.obj), SAMPLE_SIZE)
: getVerticesForObject(collider.obj);
for (const collidable of collidables) {
// First, detect if it's within the bounding box
const { geometry: colGeometry, position: colPosition } = collidable.obj;
if (!colGeometry.boundingSphere) continue;
const colCenter = new THREE.Vector3(
colPosition.x,
colPosition.y,
colPosition.z
);
const bothRadiuses =
geometry.boundingSphere.radius + colGeometry.boundingSphere.radius;
const distance = colliderCenter.distanceTo(colCenter);
if (distance > bothRadiuses) continue;
// Then, detect if there are overlapping vectors
const colSampleVertices =
method === "sample"
? _.sampleSize(getVerticesForObject(collidable.obj), SAMPLE_SIZE)
: getVerticesForObject(collidable.obj);
for (const v1 of colliderSampleVertices) {
for (const v2 of colSampleVertices) {
if (v1.distanceTo(v2) < COLLISION_DISTANCE) {
return collidable;
}
}
}
}
};
您可以尝试canon.js。它使碰撞和我最喜欢的碰撞检测库变得容易。还有ammon.js。
试试这个:
let hasCollided = false
// listener for collision
thing1.addEventListener('collide', (e) => {
// make sure it only fires once
if (!hasCollided) {
// make sure its colliding with the right target
if (e.detail.body.el.id === 'thing2') {
hasCollided = true
doThing()
}
}
})
- 如何检测是否有溢出
- 使用JavaScript和HTML5画布进行冲突检测
- 如何在three.js中检测冲突
- Javascript/JQuery中的多DIV冲突检测
- three.js服务器端冲突检测错误
- 从数组绘制级别后的冲突检测
- 如何在 javascript 中检测两个图像之间的冲突
- 在 html canvas 元素上使用 javascript 进行冲突检测,而无需使用 jquery
- 关于不同屏幕尺寸的 Phonegap 游戏开发冲突检测
- 用于检测颜色冲突的 getImageData 不起作用
- 如何获取我的 x 和 y 值以检测 html5 画布中两个图像之间的冲突
- 如何检测与JS的冲突
- JavaScript中一帧的冲突检测
- Javascript 2阵列冲突检测
- Javascript画布,通过数组进行冲突检测不起作用
- 在jquery中检测两个图像之间的冲突
- 在html/css/javascript中移动对象检测与其他对象的冲突
- gameQuery冲突检测
- 在调整窗口大小时检测表单元格冲突
- jQuery 拖动与冲突检测