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 帧中,它已经在这个盒子后面了。由于某种原因,在这种情况下可能存在理想的交叉点,但最后一个项目符号位置与交叉点不同。这导致爆炸被渲染在错误的位置。因此,第二种方法只有在子弹“跳跃”期间出现在一个不常见的盒子内时才有效。

问题是在这种情况下我如何重复物理系统的行为:

  1. 子弹移动得比较快
  2. 一旦子弹穿过盒子的任何一面,就会立即检测到交点,因此子弹的运动不会“跳跃”。

提前致谢。

这是尝试重新创建物理引擎计算时的常见问题。由于你的子弹太小,有时会在帧之间穿过墙,我看到两个选项:

  1. 在帧 x+1 上,您可以计算自帧 x 以来行进了多少距离,并将其用作子弹的大小。如果飞机在 x -> x1 之间的行驶距离内穿过,那么您就知道发生了碰撞。
  2. 如果碰撞点不移动,你可以使用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;
}