我正在使用three.js。
我的场景中有两个网格几何体。
如果这些几何体相交(或者在平移时将相交),我想将其检测为碰撞。
如何使用three.js执行碰撞检测? 如果three.js没有碰撞检测功能,那么是否有其他库可以与three.js一起使用?
我正在使用three.js。
我的场景中有两个网格几何体。
如果这些几何体相交(或者在平移时将相交),我想将其检测为碰撞。
如何使用three.js执行碰撞检测? 如果three.js没有碰撞检测功能,那么是否有其他库可以与three.js一起使用?
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...
}
}
geometry.vertices
数据,那该怎么办?在OBJ模型中,有geometry.attributes.position.array
,但没有geometry.vertices
。 - XIMRXfor (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...
}
}
仅适用于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/ 上查看。
注意:如果您旋转(或缩放)一个(或两个)立方体/棱柱,它将被检测为未经转动(或缩放)。
看起来这个问题已经解决了,但如果你不太喜欢使用光线投射和创建自己的物理环境,我有一个更简单的解决方案。
CANNON.js 和 AMMO.js 都是基于 THREE.js 构建的物理库。它们创建了一个次要的物理环境,并将你的对象位置绑定到该场景以模拟物理环境。CANNON 的文档足够简单,我使用它,但自从四年前发布以来就没有更新过。这个仓库已经被 fork 并且社区一直在更新它作为 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)
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)
}
由于我的其他答案有限,我制作了另一种更准确的东西,只有在发生碰撞时才返回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在这个问题中给出的答案相同(我通过打字给他信用),但我进行了更改,使其运行更快,并且您不需要创建网格数组。好的,第二步:创建此函数:
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。即使缩放其中一个(或两个)网格,这仍然有效。
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;
};
我传递每个维度的对象位置,因为缓冲区顶点默认相对于对象中心,而出于我的目的,我希望它们是全局的。
我还编写了一个基于顶点检测碰撞的小函数。它可以选择对非常复杂的对象进行顶点采样,或检查所有顶点与其他对象的顶点之间的接近程度:
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;
}
}
}
}
};
this.segments
是一个Wall
对象的数组。this.segments.forEach(elem => {
var pos = elem.position;
if ((Math.round(this.camera.position.x) === (pos.x | pos.z)) || (Math.round(this.camera.position.z) === (pos.x | pos.z))) {
console.log(Math.round(this.camera.position.x) + "x" + pos.x + " " + pos.z);
console.log(Math.round(this.camera.position.z) + "z" + pos.x + " " + pos.z);
if ((Math.round(this.camera.position.z) === (pos.z)) && this.hid.moveForward) {
this.hid.controls.moveForward(-moveSpeed);
} else if ((Math.round(this.camera.position.z) !== (pos.z)) && this.hid.moveForward) {
this.hid.controls.moveForward(moveSpeed);
}
else if ((Math.round(this.camera.position.z) === (pos.z)) && this.hid.moveBackward) {
this.hid.controls.moveForward(-moveSpeed);
}
else if ((Math.round(this.camera.position.z) !== (pos.z)) && this.hid.moveBackward) {
this.hid.controls.moveForward(moveSpeed);
}
if (Math.round(this.camera.position.x) === (pos.x) && this.hid.moveLeft) {
this.hid.controls.moveRight(moveSpeed);
}
else if (Math.round(this.camera.position.x) !== (pos.x) && this.hid.moveLeft) {
this.hid.controls.moveRight(-moveSpeed);
}
else if (Math.round(this.camera.position.x) === (pos.x) && this.hid.moveRight) {
this.hid.controls.moveRight(-moveSpeed);
}
else if (Math.round(this.camera.position.x) !== (pos.x) && this.hid.moveRight) {
this.hid.controls.moveRight(moveSpeed);
}
if (Math.round(this.camera.position.x) === (pos.z) && this.hid.moveLeft) {
this.hid.controls.moveRight(moveSpeed);
}
else if (Math.round(this.camera.position.x) !== (pos.z) && this.hid.moveLeft) {
this.hid.controls.moveRight(-moveSpeed);
}
else if (Math.round(this.camera.position.x) === (pos.z) && this.hid.moveRight) {
this.hid.controls.moveRight(-moveSpeed);
}
else if (Math.round(this.camera.position.x) !== (pos.z) && this.hid.moveRight) {
this.hid.controls.moveRight(moveSpeed);
}
}
});
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()
}
}
})