概要
nuxt.js × three.js × ammo.js の開発に関するメモ。
外部OBJファイル等をloadしてsceneにaddしてから、それに対して、
物理演算(自由落下と当たり判定)を物理エンジンを使って加えられるかの実験メモ。
前回さくっと作ってみたら、object同士のcollision(当たり判定)が動作しない結果になった。
悔しいから色々試して、動作したバージョンができたので、メモとして残しておく。
前回の投稿の続きになるので、前提や環境は以下を参照。
nuxt.js × three.js × ammo.js でSpeedPizzaを作った時のメモ(失敗)
原因
結論からいうと前回の投稿の、「addしたobjectからshape→bodyを作成してPhysicsWorldに追加」の「そこで、makeShape関数によって、読み込んだobjectのgeometryからshapeを形成することにした。」の部分が原因。
makeShape関数の部分を細かく言うと、geometry(BufferGeometry)から頂点情報を取り出し、頂点情報からammo.jsのbtBvhTriangleMeshShapeを作成している。
色々調べたら、btBvhTriangleMeshShapeではmesh同士のcollisionが動作しないということがわかった、、、orz
そこで、btConvexHullShapeを使って、shapeを作り直すことにした。
誤っているソース
<script type="module">
... 略
export default {
... 略
methods: {
... 略
makeShape(mesh) {
const triangleMesh = new Ammo.btTriangleMesh(true, true);
triangleMesh.setScaling(
new Ammo.btVector3(mesh.scale.x, mesh.scale.y, mesh.scale.z)
);
const geometry = mesh.geometry;
if (geometry instanceof THREE.BufferGeometry) {
const vertexPositionArray = geometry.attributes.position.array;
for (let i = 0; i * 3 < geometry.attributes.position.count; i++) {
triangleMesh.addTriangle(
new Ammo.btVector3(
vertexPositionArray[i * 9],
vertexPositionArray[i * 9 + 1],
vertexPositionArray[i * 9 + 2]
),
new Ammo.btVector3(
vertexPositionArray[i * 9 + 3],
vertexPositionArray[i * 9 + 4],
vertexPositionArray[i * 9 + 5]
),
new Ammo.btVector3(
vertexPositionArray[i * 9 + 6],
vertexPositionArray[i * 9 + 7],
vertexPositionArray[i * 9 + 8]
),
false
);
}
} else if (geometry instanceof THREE.Geometry) {
for (let i = 0; i < geometry.faces.length; i++) {
const face = geometry.faces[i];
if (face instanceof THREE.Face3) {
const vec1 = new Ammo.btVector3(0, 0, 0);
const vec2 = new Ammo.btVector3(0, 0, 0);
const vec3 = new Ammo.btVector3(0, 0, 0);
vec1.setX(face[0].x);
vec1.setY(face[0].y);
vec1.setZ(face[0].z);
vec2.setX(face[1].x);
vec2.setY(face[1].y);
vec2.setZ(face[1].z);
vec3.setX(face[2].x);
vec3.setY(face[2].y);
vec3.setZ(face[2].z);
triangleMesh.addTriangle(vec1, vec2, vec3, true);
}
}
}
const shape = new Ammo.btBvhTriangleMeshShape(triangleMesh, true, true);
return shape;
},
... 略
},
};
</script>
正解ソースはこちら
<script type="module">
... 略
export default {
... 略
methods: {
... 略
makeShape(mesh) {
const shape = new Ammo.btConvexHullShape();
const geometry = mesh.geometry;
if (geometry instanceof THREE.BufferGeometry) {
const vertexPositionArray = geometry.attributes.position.array;
for (let i = 0; i * 3 < geometry.attributes.position.count; i++) {
shape.addPoint(
new Ammo.btVector3(
vertexPositionArray[i * 9],
vertexPositionArray[i * 9 + 1],
vertexPositionArray[i * 9 + 2]
),
new Ammo.btVector3(
vertexPositionArray[i * 9 + 3],
vertexPositionArray[i * 9 + 4],
vertexPositionArray[i * 9 + 5]
),
new Ammo.btVector3(
vertexPositionArray[i * 9 + 6],
vertexPositionArray[i * 9 + 7],
vertexPositionArray[i * 9 + 8]
),
false
);
}
} else if (geometry instanceof THREE.Geometry) {
for (let i = 0; i < geometry.faces.length; i++) {
const face = geometry.faces[i];
if (face instanceof THREE.Face3) {
shape.addPoint(
new Ammo.btVector3(face[0].x, face[0].y, face[0].z),
new Ammo.btVector3(face[1].x, face[1].y, face[1].z),
new Ammo.btVector3(face[2].x, face[2].y, face[2].z),
true
);
}
}
}
shape.setLocalScaling(
new Ammo.btVector3(mesh.scale.x, mesh.scale.y, mesh.scale.z)
);
return shape;
},
... 略
},
};
</script>
おまけ
createRigidBody関数内で、bodyに色々な係数を渡すことができるので、それもメモっておこう。
<script type="module">
... 略
export default {
... 略
methods: {
... 略
createRigidBody(threeObject, physicsShape, mass, pos, quat) {
physicsShape.setMargin(0.05);
const transform = new Ammo.btTransform();
transform.setIdentity();
transform.setOrigin(new Ammo.btVector3(pos.x, pos.y, pos.z));
transform.setRotation(
new Ammo.btQuaternion(quat.x, quat.y, quat.z, quat.w)
);
const motionState = new Ammo.btDefaultMotionState(transform);
const localInertia = new Ammo.btVector3(0, 0, 0);
physicsShape.calculateLocalInertia(mass, localInertia);
const rbInfo = new Ammo.btRigidBodyConstructionInfo(
mass,
motionState,
physicsShape,
localInertia
);
const body = new Ammo.btRigidBody(rbInfo);
// この部分
body.setRestitution(1);//反発
body.setFriction(0.6);//摩擦
body.setDamping(0, 0.04);//減衰
body.setAngularFactor(new Ammo.btVector3(1, 1, 1));//回転の向き
body.setLinearFactor(new Ammo.btVector3(1, 1, 1));//平行移動の向き
threeObject.userData.physicsBody = body;
this.rigidBodies.push(threeObject);
body.setActivationState(4);
this.physicsWorld.addRigidBody(body);
return body;
},
... 略
},
};
</script>
跳ねすぎて、ほとんど落ちた
まとめ
とりあえずはうまくいった。
意外と、外部のモデルファイルからloadしたobjectで、collisionを動作させる情報が少ないということがわかった。特にammo.jsは情報が少ない。
誰かの役に立ってくれたらと願います。
細かい部分はもっと勉強しなければ。