AFrame:如何使光线投射器与 object3D 子对象一起工作?
Posted
技术标签:
【中文标题】AFrame:如何使光线投射器与 object3D 子对象一起工作?【英文标题】:AFrame: how to make the raycaster work with an object3D child objects? 【发布时间】: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 请注意,它对鸟的作用与预期一样,但对兽人来说表现得很奇怪。此外,边界框与碰撞形状框本身也不匹配。这一点我也不清楚。
【问题讨论】:
【参考方案1】:边界框也与碰撞形状框本身不匹配。
边界框考虑了世界矩阵。当模型比例不同时,您可以看到它是如何变化的:
您还可以看到红色框也没有很好地缩放。我认为这里的大多数问题都是规模混淆的结果。
问题在于,对于某些模型它可以工作,但对于其中一些模型,它会捕获碰撞形状之外的交叉点。
在设置 object3D 之前添加线框会干扰 raycaster。不确定,但我猜这也是因为缩放问题。
Here's a glitch 在setObject3D
之后设置线框
我会从不同的方法开始。将盒子创建为场景子对象,并根据模型 worldMatrix + 骨骼偏移管理它们的变换。管理(放大/缩小、重新定位)和调试会更容易。
【讨论】:
非常感谢,伙计!像魅力一样工作。我会考虑按照您的建议将箱子移到现场。干杯!以上是关于AFrame:如何使光线投射器与 object3D 子对象一起工作?的主要内容,如果未能解决你的问题,请参考以下文章