I'm trying to implement "bullet and target collision" problem and create an explosion when collision occurs. I managed to do it using aframe-physics-system
which was working good: the explosion was rendering at the exact point of the collision and in the exact time. Now I decided to get rid of the physics system as I don't need such overhead - my only goal is to render an explosion.
I tried to use box.containsPoint
as well as 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);
}
But both approaches have the following flaw:
On frame X
the bullet is just in front of a box, but in frame X+1
it is already behind this box. For some reason in this case there might be desirable intersections, but the last bullet position is different than the intersection. Which causes the explosion to be rendered in a wrong position. So, the second approach works only if bullet during it's "jumps" appears inside of a box which is far from being frequent.
The question is how in this case I can repeat the behaviour I had with physics system:
Thanks in advance.
Thanks to @Marquizzo, I ended up with the following solution:
I'm casting a ray from the bullet position to the position of the gun. If there is 1 intersection, then the bullet is inside of the box, so I can render an explosion at the intersection position. But if there are two intersections I will take the second one as it will be more far from the ray origin point and hence closer to the gun. But also I had to calculate the distance between the bullet position and the intersection which as was advised should be less than the distance bullet passed between the frames:
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;
}