AFrame & Three.JS 检测帧间发生的移动点和框之间的碰撞
AFrame & Three.JS detecting collision between moving point and box which happens between frames
我正在尝试实现“子弹与目标碰撞”问题并在发生碰撞时制造爆炸。我设法使用 aframe-physics-system
做到了这一点,效果很好:爆炸在准确的碰撞点和准确的时间渲染。现在我决定摆脱物理系统,因为我不需要这样的开销——我唯一的目标是渲染爆炸。
我尝试使用 box.containsPoint
以及 Raycaster
:
tick(time: number, delta: number): void {
// bullet-component
// ...
// Update speed based on acceleration
this.speed = this.currentAcceleration * .01 * delta;
if (this.speed > this.data.maxSpeed) {
this.speed = this.data.maxSpeed;
}
// there is an initial position and direction set in data property.
const newBulletPosition = this.position.add(this.direction.multiplyScalar(this.speed));
// targets is an array of boxes
const found = this._detectCollision(newBulletPosition, this.targets);
if (found) {
console.log("found!");
this.resetBullet();
this.el.emit("collide", {
coordinates: newBulletPosition//found
});
return;
}
this.el.object3D.position.set(newBulletPosition.x, newBulletPosition.y, newBulletPosition.z);
},
_detectCollision(point: THREE.Vector3, obj: THREE.Object3D[]): THREE.Vector3 | null {
const ray = new THREE.Raycaster(point,
this.temps.direction.clone().multiplyScalar(-1).normalize());
const intersects = ray.intersectObjects(obj, true);
return intersects.length % 2 === 1 ? intersects[0].point : null;
},
_box: new THREE.Box3(),
_inverseWorldMatrix: new THREE.Matrix4(),
_detectCollision2(point: THREE.Vector3, obj: THREE.Object3D): THREE.Vector3 | null {
obj.updateMatrixWorld(true);
this._inverseWorldMatrix.copy(obj.matrix).invert();
this._box.setFromObject(obj);
this._inverseBulletPosition.set(point.x, point.y, point.z);
this._inverseBulletPosition.applyMatrix4(this._inverseWorldMatrix);
return this._box.containsPoint(this._inverseBulletPosition);
}
但这两种方法都存在以下缺陷:
在第 X
帧中,子弹就在一个盒子前面,但在第 X+1
帧中,它已经在这个盒子后面了。由于某种原因,在这种情况下可能存在理想的交叉点,但最后一个项目符号位置与交叉点不同。这导致爆炸被渲染在错误的位置。因此,第二种方法只有在子弹“跳跃”期间出现在一个不常见的盒子内时才有效。
问题是在这种情况下我如何重复物理系统的行为:
- 子弹移动得比较快
- 一旦子弹穿过盒子的任何一面,就会立即检测到交点,因此子弹的运动不会“跳跃”。
提前致谢。
这是尝试重新创建物理引擎计算时的常见问题。由于你的子弹太小,有时会在帧之间穿过墙,我看到两个选项:
- 在帧
x+1
上,您可以计算自帧 x
以来行进了多少距离,并将其用作子弹的大小。如果飞机在 x -> x1
之间的行驶距离内穿过,那么您就知道发生了碰撞。
- 如果碰撞点不移动,你可以使用
THREE.Raycaster
并预先计算碰撞点,这样你就会知道子弹会在之前 达到那个点:
const raycaster = new THREE.Raycaster();
shoot() {
raycaster.set(origin, direction);
const intersects = raycaster.intersectObjects(arrayOfWalls);
// No intersection took place
if (intersects[0] == undefined) return;
// How far away from origin the collision takes place.
intersects[0].distance;
// The Vector3 where the bullet crosses the wall
intersects[0].point;
}
您可以阅读更多内容about Raycasters in the docs。
感谢@Marquizzo,我最终得到了以下解决方案:
我正在从子弹位置向枪的位置投射光线。如果有 1 个交点,则子弹在盒子内部,因此我可以在交点位置渲染爆炸。但是如果有两个交叉点,我会选择第二个,因为它离射线原点更远,因此离枪更近。但我还必须计算子弹位置和交叉点之间的距离,建议应该小于帧之间子弹通过的距离:
tick(time: number, delta: number): void {
const el = this.el;
if (!el) {
console.warn("AFRAME entity is undefined.");
return;
}
this.el.object3D.lookAt(this.direction.clone().multiplyScalar(1000));
// Update acceleration based on the friction
this.temps.position.copy(this.el.object3D.position);
// Update speed based on acceleration
this.speed = this.currentAcceleration * 0.05 * delta;
if (this.speed > this.data.maxSpeed) {
this.speed = this.data.maxSpeed;
}
// Set new position
this.temps.direction.copy(this.direction);
const newBulletPosition = this.temps.position.add(this.temps.direction.multiplyScalar(this.speed));
if (newBulletPosition.length() >= FADE_DISTANCE) {
this.resetBullet();
return;
}
const found = this._detectCollision(newBulletPosition, this.targetCollisionShapes);
if (found) {
const jumpDistance = newBulletPosition.clone().sub(this.el.object3D.position).length();
const collisionDistance = newBulletPosition.clone().sub(found).length();
if (collisionDistance < jumpDistance) {
console.log("found!");
this.resetBullet();
this.el.emit("collide", {
target: this.target,
coordinates: found
} as CollisionEvent);
return;
}
this.el.object3D.position.set(newBulletPosition.x, newBulletPosition.y, newBulletPosition.z);
},
_detectCollision(point: THREE.Vector3, obj: THREE.Object3D[]): THREE.Vector3 | null {
const ray = new THREE.Raycaster(point, this.direction.clone().multiplyScalar(-1).normalize());
const intersects = ray.intersectObjects(obj, true);
return intersects.length % 2 === 1
? intersects[0].point
: intersects.length > 1 ? intersects[1].point : null;
}
我正在尝试实现“子弹与目标碰撞”问题并在发生碰撞时制造爆炸。我设法使用 aframe-physics-system
做到了这一点,效果很好:爆炸在准确的碰撞点和准确的时间渲染。现在我决定摆脱物理系统,因为我不需要这样的开销——我唯一的目标是渲染爆炸。
我尝试使用 box.containsPoint
以及 Raycaster
:
tick(time: number, delta: number): void {
// bullet-component
// ...
// Update speed based on acceleration
this.speed = this.currentAcceleration * .01 * delta;
if (this.speed > this.data.maxSpeed) {
this.speed = this.data.maxSpeed;
}
// there is an initial position and direction set in data property.
const newBulletPosition = this.position.add(this.direction.multiplyScalar(this.speed));
// targets is an array of boxes
const found = this._detectCollision(newBulletPosition, this.targets);
if (found) {
console.log("found!");
this.resetBullet();
this.el.emit("collide", {
coordinates: newBulletPosition//found
});
return;
}
this.el.object3D.position.set(newBulletPosition.x, newBulletPosition.y, newBulletPosition.z);
},
_detectCollision(point: THREE.Vector3, obj: THREE.Object3D[]): THREE.Vector3 | null {
const ray = new THREE.Raycaster(point,
this.temps.direction.clone().multiplyScalar(-1).normalize());
const intersects = ray.intersectObjects(obj, true);
return intersects.length % 2 === 1 ? intersects[0].point : null;
},
_box: new THREE.Box3(),
_inverseWorldMatrix: new THREE.Matrix4(),
_detectCollision2(point: THREE.Vector3, obj: THREE.Object3D): THREE.Vector3 | null {
obj.updateMatrixWorld(true);
this._inverseWorldMatrix.copy(obj.matrix).invert();
this._box.setFromObject(obj);
this._inverseBulletPosition.set(point.x, point.y, point.z);
this._inverseBulletPosition.applyMatrix4(this._inverseWorldMatrix);
return this._box.containsPoint(this._inverseBulletPosition);
}
但这两种方法都存在以下缺陷:
在第 X
帧中,子弹就在一个盒子前面,但在第 X+1
帧中,它已经在这个盒子后面了。由于某种原因,在这种情况下可能存在理想的交叉点,但最后一个项目符号位置与交叉点不同。这导致爆炸被渲染在错误的位置。因此,第二种方法只有在子弹“跳跃”期间出现在一个不常见的盒子内时才有效。
问题是在这种情况下我如何重复物理系统的行为:
- 子弹移动得比较快
- 一旦子弹穿过盒子的任何一面,就会立即检测到交点,因此子弹的运动不会“跳跃”。
提前致谢。
这是尝试重新创建物理引擎计算时的常见问题。由于你的子弹太小,有时会在帧之间穿过墙,我看到两个选项:
- 在帧
x+1
上,您可以计算自帧x
以来行进了多少距离,并将其用作子弹的大小。如果飞机在x -> x1
之间的行驶距离内穿过,那么您就知道发生了碰撞。 - 如果碰撞点不移动,你可以使用
THREE.Raycaster
并预先计算碰撞点,这样你就会知道子弹会在之前 达到那个点:
const raycaster = new THREE.Raycaster();
shoot() {
raycaster.set(origin, direction);
const intersects = raycaster.intersectObjects(arrayOfWalls);
// No intersection took place
if (intersects[0] == undefined) return;
// How far away from origin the collision takes place.
intersects[0].distance;
// The Vector3 where the bullet crosses the wall
intersects[0].point;
}
您可以阅读更多内容about Raycasters in the docs。
感谢@Marquizzo,我最终得到了以下解决方案:
我正在从子弹位置向枪的位置投射光线。如果有 1 个交点,则子弹在盒子内部,因此我可以在交点位置渲染爆炸。但是如果有两个交叉点,我会选择第二个,因为它离射线原点更远,因此离枪更近。但我还必须计算子弹位置和交叉点之间的距离,建议应该小于帧之间子弹通过的距离:
tick(time: number, delta: number): void {
const el = this.el;
if (!el) {
console.warn("AFRAME entity is undefined.");
return;
}
this.el.object3D.lookAt(this.direction.clone().multiplyScalar(1000));
// Update acceleration based on the friction
this.temps.position.copy(this.el.object3D.position);
// Update speed based on acceleration
this.speed = this.currentAcceleration * 0.05 * delta;
if (this.speed > this.data.maxSpeed) {
this.speed = this.data.maxSpeed;
}
// Set new position
this.temps.direction.copy(this.direction);
const newBulletPosition = this.temps.position.add(this.temps.direction.multiplyScalar(this.speed));
if (newBulletPosition.length() >= FADE_DISTANCE) {
this.resetBullet();
return;
}
const found = this._detectCollision(newBulletPosition, this.targetCollisionShapes);
if (found) {
const jumpDistance = newBulletPosition.clone().sub(this.el.object3D.position).length();
const collisionDistance = newBulletPosition.clone().sub(found).length();
if (collisionDistance < jumpDistance) {
console.log("found!");
this.resetBullet();
this.el.emit("collide", {
target: this.target,
coordinates: found
} as CollisionEvent);
return;
}
this.el.object3D.position.set(newBulletPosition.x, newBulletPosition.y, newBulletPosition.z);
},
_detectCollision(point: THREE.Vector3, obj: THREE.Object3D[]): THREE.Vector3 | null {
const ray = new THREE.Raycaster(point, this.direction.clone().multiplyScalar(-1).normalize());
const intersects = ray.intersectObjects(obj, true);
return intersects.length % 2 === 1
? intersects[0].point
: intersects.length > 1 ? intersects[1].point : null;
}