私はthree.jsを使用しています。
シーンに2つのメッシュジオメトリがあります。
これらのジオメトリが交差している場合(または変換すると交差する場合)、これを衝突として検出します。
three.jsで衝突検出を実行するにはどうすればよいですか?three.jsに衝突検出機能がない場合、three.jsと組み合わせて使用する可能性のある他のライブラリはありますか?
私はthree.jsを使用しています。
シーンに2つのメッシュジオメトリがあります。
これらのジオメトリが交差している場合(または変換すると交差する場合)、これを衝突として検出します。
three.jsで衝突検出を実行するにはどうすればよいですか?three.jsに衝突検出機能がない場合、three.jsと組み合わせて使用する可能性のある他のライブラリはありますか?
Three.jsでは、ユーティリティCollisionUtils.jsとCollision.jsはサポートされなくなったようです。また、mrdoob(three.jsの作成者)自身が、最新バージョンのthree.jsに更新し、この目的でRayクラスを使用することを推奨しています。代わりは。以下はそれを回避するための1つの方法です。
アイデアは次のとおりです。「Player」と呼ばれる特定のメッシュが、「collidableMeshList」と呼ばれる配列に含まれるメッシュと交差するかどうかを確認するとします。私たちにできることは、Playerメッシュの座標(Player.position)で始まり、Playerメッシュのジオメトリの各頂点に向かって伸びる一連の光線を作成することです。各レイには「intersectObjects」と呼ばれるメソッドがあり、レイが交差したオブジェクトの配列と、これらの各オブジェクトまでの距離(レイの原点から測定)を返します。交差点までの距離がプレーヤーの位置とジオメトリの頂点の間の距離よりも短い場合、衝突はプレーヤーのメッシュの内部で発生しました。これはおそらく「実際の」衝突と呼ばれます。
私は実際の例を次の場所に投稿しました:
http://stemkoski.github.io/Three.js/Collision-Detection.html
矢印キーで赤いワイヤーフレームキューブを移動し、W / A / S/Dで回転させることができます。青い立方体の1つと交差すると、上記のように交差するたびに1回画面の上部に「ヒット」という単語が表示されます。コードの重要な部分は以下のとおりです。
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...
}
}
この特定のアプローチには2つの潜在的な問題があります。
(1)光線の原点がメッシュM内にある場合、光線とMの衝突は返されません。
(2)(プレーヤーメッシュに対して)小さいオブジェクトがさまざまな光線間で「スリップ」する可能性があるため、衝突は記録されません。この問題の可能性を減らすための2つの可能なアプローチは、小さなオブジェクトが光線を作成し、それらの視点から衝突検出の作業を行うようにコードを作成するか、メッシュにさらに多くの頂点を含めることです(たとえば、CubeGeometry(100、100、100、 CubeGeometry(100、100、100、1、1、1)ではなく20、20、20))後者のアプローチはパフォーマンスに影響を与える可能性があるため、慎重に使用することをお勧めします。
他の人がこの質問の解決策でこの質問に貢献することを願っています。ここで説明するソリューションを開発する前に、私はかなり長い間苦労しました。
これは実際にはトピックの範囲が広すぎてSOの質問でカバーできませんが、サイトのSEOに少し油を注ぐために、ここにいくつかの簡単な出発点があります。
完全な物理エンジンではなく、本当に単純な衝突検出が必要な場合は、チェックアウトしてください(既存のWebサイトがなくなったため、リンクが削除されました)
一方、「AとBがぶつかった」だけでなく、衝突応答が必要な場合は、Three.jsを中心に構築された非常に使いやすいAmmo.jsラッパーであるPhysijsをご覧ください。
three.jsの最新バージョンで動作するLeeの回答の更新バージョン
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...
}
}
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を返します。これは、メッシュの1つ(または両方)をスケーリングする場合でも機能します。https://3d-collsion-test-r.glitch.me/に例があります
これはすでに解決されているようですが、レイキャスティングを使用して独自の物理環境を作成することに慣れていない場合は、より簡単な解決策があります。
CANNON.jsとAMMO.jsはどちらも、THREE.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)
}
この後、物理シーンの位置に基づいて、animate関数で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;
}
}
}
}
};