【发布时间】:2022-01-20 04:37:54
【问题描述】:
我想使用 AFrame raycaster 组件来捕捉与对象的交集。我正在将我的自定义对象添加到 GLTF 模型中。我称它们为“碰撞形状”,它们被用来捕捉镀金模型和射弹之间的碰撞。用例:向敌人发射子弹。
问题在于,对于某些模型它可以工作,但对于其中一些模型,它会捕获碰撞形状之外的交叉点。
为了定位碰撞形状,我使用了碰撞对象应锚定到的骨骼名称。
我的代码如下(我删除了一些部分以使其更短):
<a-gltf-model src="#bird"
position="2 -75 -300"
animation-mixer
scale="1 1 1"
shape__Bone_38_08="bone: Bone_38_08; shape: box; halfExtents: 10 10 5"
shape__Bone_39_07="bone: Bone_39_07; shape: box; halfExtents: 15 10 10">
</a-gltf-model>
<a-gltf-model src="#orc" position="-2 0 -5" animation-mixer="clip: Orc.004" scale="2 2 2" rotation="0 180 0"
shape__hair_1="bone: hair_1; shape: box; halfExtents: 0.05 0.075 0.05"
shape__leg_L_1="bone: leg_L_1; shape: box; halfExtents: 0.05 0.125 0.05; offset: 0 -0.05 -0.1">
</a-gltf-model>
<a-entity camera look-controls position="0 1.6 0" wasd-controls>
<a-cursor color="gray" raycaster="objects: [data-raycastable]" ></a-cursor>
</a-entity>
组件:
AFRAME.registerComponent("shape", {
schema: {
bone: { default: "" },
shape: { default: "box", oneOf: ["box", "sphere", "cylinder"] },
offset: { type: "vec3", default: { x: 0, y: 0, z: 0 } },
orientation: { type: "vec4", default: { x: 0, y: 0, z: 0, w: 1 } },
// box
halfExtents: { type: "vec3", default: { x: 0.5, y: 0.5, z: 0.5 }, if: { shape: ["box"] } },
visible: { type: "boolean", default: true }
},
multiple: true,
init(){
const data = this.data;
const self = this;
const el = this.el;
el.addEventListener("model-loaded", function modelReady() {
el.removeEventListener("model-loaded", modelReady);
const boneDummy = document.createElement("a-entity");
self.setDummyShape(boneDummy, data);
self.boneObj = self.getBone(el.object3D, data.bone);
el.appendChild(boneDummy);
self.boneDummy = boneDummy;
});
},
setDummyShape(dummy, data) {
const shapeName = "collidable-shape";
const config = {
shapeName: data.bone,
shape: data.shape,
offset: data.offset,
halfExtents: data.halfExtents
};
dummy.setAttribute(shapeName, config);
},
getBone(root, boneName) {
let bone = root.getObjectByName(boneName);
if (!bone) {
root.traverse(node => {
const n = node;
if (n?.isBone && n.name.includes(boneName)) {
bone = n;
}
});
}
return bone;
},
inverseWorldMatrix: new THREE.Matrix4(),
boneMatrix: new THREE.Matrix4(),
tick() {
const el = this.el;
if (!el) { throw Error("AFRAME entity is undefined."); }
if (!this.boneObj || !this.boneDummy) return;
this.inverseWorldMatrix.copy(el.object3D.matrix).invert();
this.boneMatrix.multiplyMatrices(this.inverseWorldMatrix, this.boneObj.matrixWorld);
this.boneDummy.object3D.position.setFromMatrixPosition(this.boneMatrix);
}
})
AFRAME.registerComponent("collidable-shape", {
schema: {
shape: { default: "box", oneOf: ["box", "sphere", "cylinder"] },
offset: { type: "vec3", default: { x: 0, y: 0, z: 0 } },
orientation: { type: "vec4", default: { x: 0, y: 0, z: 0, w: 1 } },
// box
halfExtents: { type: "vec3", default: { x: 0.5, y: 0.5, z: 0.5 }, if: { shape: ["box"] } },
visible: { type: "boolean", default: true }
},
collistionObject: null ,
multiple:true,
init() {
const scene = this.el.sceneEl;
if (!scene) { throw Error("AFRAME scene is undefined."); }
if (scene.hasLoaded) {
this.initShape();
} else {
scene.addEventListener("loaded", this.initShape.bind(this));
}
},
initShape() {
const data = this.data;
this.el.setAttribute("data-raycastable", "");
this.el.addEventListener('mouseenter', evt => {
console.log("mouse enter", data.shape);
this.el.object3D.children[0].material.color.setHex(0x00ff00);
});
this.el.addEventListener('mouseleave', evt => {
console.log("mouse leave", data.shape);
this.el.object3D.children[0].material.color.setHex(0xff0000);
});
const scale = new THREE.Vector3(1, 1, 1);
this.el.object3D.getWorldScale(scale);
let shape;
let offset;
let orientation;
if (Object.prototype.hasOwnProperty.call(data, "offset")) {
offset = new THREE.Vector3(
data.offset.x * scale.x,
data.offset.y * scale.y,
data.offset.z * scale.z
);
}
if (Object.prototype.hasOwnProperty.call(data, "orientation")) {
orientation = new THREE.Quaternion();
orientation.copy(data.orientation);
}
switch (data.shape) {
case "box":
shape = new THREE.BoxGeometry(
data.halfExtents.x * 2 * scale.x,
data.halfExtents.y * 2 * scale.y,
data.halfExtents.z * 2 * scale.z
);
break;
}
this._applyShape(shape, offset, data.visible);
},
_applyShape(shape, offset, visible) {
const material = new THREE.MeshBasicMaterial({ color: 0xff0000, transparent: true, opacity: 0.3 });
const wireframe = new THREE.LineSegments(
new THREE.EdgesGeometry(shape),
new THREE.LineBasicMaterial({ color: 0xff0000, linewidth: 3 }));
this.collistionObject = new THREE.Mesh(shape, material);
this.collistionObject.add(wireframe);
if (offset) {
this.collistionObject.position.set(offset.x, offset.y, offset.z);
}
this.collistionObject.visible = visible === true;
this.el.setObject3D("mesh", this.collistionObject);
const size = new THREE.Vector3();
const box = new THREE.Box3().setFromObject(this.el.object3D);
box.getSize(size);
const bbox = new THREE.BoxGeometry(size.x, size.y, size.z);
const bboxWireframe = new THREE.LineSegments(
new THREE.EdgesGeometry(bbox),
new THREE.LineBasicMaterial({ color: 0x000000, linewidth: 10 }));
this.el.object3D.add(bboxWireframe)
}
});
示例项目可以在这里找到:https://glitch.com/edit/#!/collisons-test 请注意,它对鸟的作用与预期一样,但对兽人来说表现得很奇怪。此外,边界框与碰撞形状框本身也不匹配。这一点我也不清楚。
【问题讨论】: