I currently studying at collision detection. I searched on web and build a small test problem. As some website says, I use three.js raycast from object center to its vertex . I test if it intersect with the bounding box of the satellite which is a line mesh .The code is as following
let point;//vector3 (0,2,0)
let origin = new THREE.Vector3();
function test(A, B) {
let local = point.clone();
A.updateMatrixWorld();
origin.set(A.position.x, A.position.y, 2);
let global = local.applyMatrix4(A.matrix);
global.z = 2;
var direct = global.sub(origin);
var ray = new THREE.Raycaster(origin, direct.normalize());
var result = ray.intersectObjects(B);
if (result.length > 0 && result[0].distance < direct.length()) {
console.log("bang!")
}
}
I only test one point on each bullet. In order to see the lines completely , I set all the pointsâ position.z to 2 The problem is some times it test ok sometimes not.
As you can see in the video. I fire 5 bullet but sometimes it only hit 4.
Unfortunately, I donât understand what you are doing in your code so let me provide you a general guide for implementing a robust bullet collision detection.
The line of fire is usually represented as a ray.
In each simulation step, the origin of the ray is the previous position of the bullet.
You now perform an intersection test with the obstacles in your game environment.
If there is an intersection, computed the (squared) euclidian distance between the origin of the ray and the intersection point. Letâs call this distance d1.
Now compute the ânewâ position of the bullet for this simulation step and the (squared) euclidian distance between the new position and the previous one. Letâs call this distance d2.
If d1 <= d2, you have a hit.
A working example of this code is presented in the following demo of Yuka, a library of developing game AI. The API is similar to three.js, so it should be no problem use the code as a template.
After watching your video,I think if you want to check the bullet in 2D scene ,It is not an appropriate way to use ââraycasterââ,It checks all vertices in the parameterwhich your pass and cost a lot of performance.It is a good idea to use a 2D collide algorithm to check if the collision happen.
Keep in mind that the first step of a ray intersection test normally happens against a bounding volume (like a bounding sphere or an AABB) which is very fast. For an accurate collision detection against a geometry, using a ray is definitely a valid approach
function main() {
init();
animate();
}
let container;
let scene, camera, renderer;
let controls;
let clock;
let bullets = [];
bullets.remove = removeElemtFromArray;
function removeElemtFromArray(elem) {
this.splice(this.indexOf(elem), 1);
}
let bBoxs = [];
function init() {
container = document.querySelector(".container");
camera = new THREE.PerspectiveCamera(60, container.clientWidth / container.clientHeight, 0.1, 2000);
camera.position.z = 200;
camera.position.y = 50;
control3D = new THREE.TrackballControls(camera);
control3D.rotateSpeed = 1.0;
control3D.zoomSpeed = 1.2;
control3D.panSpeed = 0.8;
control3D.noZoom = false;
control3D.noPan = false;
control3D.staticMoving = true;
control3D.dynamicDampingFactor = 0.3;
control3D.target.set(0, 50, 0);
scene = new THREE.Scene();
let canvas = document.querySelector(".scene")
let context = canvas.getContext('webgl2');
renderer = new THREE.WebGLRenderer({ canvas: canvas, context: context });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(container.clientWidth, container.clientHeight);
renderer.setClearColor(0x777777, 0);
container.appendChild(renderer.domElement);
window.addEventListener('resize', onWindowResize, false);
clock = new THREE.Clock();
//bullet
new ControlGun();
//boundingBox
let wid = 50;
let hei = 50;
let hwid = wid / 2;
let hhei = hei / 2;
let topLeft = new THREE.Vector3(-hwid, hhei, 0);
let bottomLeft = new THREE.Vector3(-hwid, -hhei, 0);
let topRight = new THREE.Vector3(hwid, hhei, 0);
let bottomRight = new THREE.Vector3(hwid, -hhei, 0);
material = new THREE.LineBasicMaterial({
color: 0xff0000
});
geometry = new THREE.Geometry();
geometry.vertices.push(
bottomRight,
bottomLeft,
topLeft,
);
boundingBox = new THREE.Line(geometry, material);
boundingBox.position.set(100, 100, 0);
scene.add(boundingBox);
bBoxs.push(boundingBox);
}
function onWindowResize() {
camera.aspect = container.clientWidth / container.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(container.clientWidth, container.clientHeight);
}
function animate() {
var delta = clock.getDelta();
requestAnimationFrame(animate);
ControlGun.update(delta);
TWEEN.update();
testCollide()
control3D.update();
render();
}
function render() {
renderer.render(scene, camera);
}
let point = new THREE.Vector3(0, 2, 0);//test point on bullet
let hit = 0;
let hitinfo = document.querySelector(".hit");
function testCollide() {
for (let i = 0; i < bullets.length; i++) {
let bullet = bullets[i];
bullet.updateMatrixWorld();
let local = point.clone();
let global = local.applyMatrix4(bullet.matrix);
var direct = global.sub(bullet.position);
var ray = new THREE.Raycaster(bullet.position, direct.normalize());
var result = ray.intersectObjects(bBoxs);
if (result.length > 0 && result[0].distance < direct.length()) {
hit++;
hitinfo.textContent = "hit:" + hit;
console.log("bang!");
}
}
}
function ControlGun() {
let shotInterval = 0.3;
let count = 0;
ControlGun.update = function updateGunControl(delta) {
if (count == 0)
fire();
count += delta;
if (count >= shotInterval) {
count = 0;
}
}
function createBullet() {
let geometry = new THREE.CircleGeometry(2, 4);
let material = new THREE.MeshBasicMaterial({
color: 0x000000,
})
bullet = new THREE.Mesh(geometry, material);
scene.add(bullet);
return bullet;
}
let target = new THREE.Vector3();
let direct = new THREE.Vector3();
let initDirect = new THREE.Vector3(0, 1, 0);
let total = 0;
let info = document.querySelector(".fire");
function fire() {
let bullet = createBullet();
if (bullet == null) return;
//bullet init position
bullet.position.set(0, 0, 0);
//bullet target
target.set(450, 500, 0);
direct.subVectors(target, bullet.position);
let angle = initDirect.angleTo(direct);
bullet.rotation.z = angle;
scene.add(bullet);
bullets.push(bullet);
new TWEEN.Tween(bullet.position)
.to({ x: target.x, y: target.y }, 1000)
.start()
.onComplete(function () {
bullets.remove(bullet);
});
total++;
info.textContent = "total:" + total;
}
}
main();
you can see the problem that is the hit and fire are not same . But I will continue try methods you mentioned before
sorry about my math , I try to understand your approach , So I draw this picture. But I still donât understand why d1<d2 then hit. d1 < d2 looks impossible in my comprehension
Hi, @Mugen87. I finally did it. Your method is awesome. It is very simple and accurate (never lose a hit). I think it could handle all the collision detection in my game. Here is the result!
The code change to the following
let hit = 0;
let hitinfo = document.querySelector(".hit");
let d1;
let d2;
let d2vec = new THREE.Vector3();
function testCollide() {
let i = bullets.length;
while (i--) {
let bullet = bullets[i];
bullet.updateMatrixWorld();
var ray = new THREE.Raycaster(bullet.lastPosition, bullet.lastDirect);
var intersects = ray.intersectObjects(bBoxs);
if (intersects[0] == undefined) return;
d1 = intersects[0].distance;
d2vec.subVectors(bullet.position, bullet.lastPosition);
d2 = d2vec.length();
if (d1 <= d2) {
scene.remove(bullet);
bullets.remove(bullet);
bullet.tween.stop();
hit++;
hitinfo.textContent = "hit:" + hit;
}
bullet.lastPosition.copy(bullet.position);
bullet.lastDirect.copy(bullet.direct);
}
}
thanks again
BTW, I see you build your own library YUKA, Can I use it without three.js or combine with three.js . How to install it . I donât find document for novice
Hi , I have an additional problem. Now I can test hit well. But In real game. There should not be a bounding box displayed. How can I hide the line while still collide with the bullet. I tried set material to transparent, but it seems no effect to lineMaterial. I also try set mesh .visible =false, thus the raycast function donât test it any more . Help!
Yuka is a new project and we have not provided tutorials so far. But as mentioned at github you can download the repo and then execute npm install && npm start in order to start a local server and work with the examples locally.
Yuka is not intended to replace three.js. Itâs a pure AI engine. You still need a 3D engine like three.js or BabylonJS to visually represent your game entities. You can combine Yuka with any JavaScript 3D engine you like. The idea is to map the transformation of your game entities on their respective render components per frame. This could be for example a THREE.SkinnedMesh or any other type of THREE.Object3D.
I donât use box3 display my boundingbox. I just use a line mesh whose points just form a rectangle. And I use three.js Raycast to test the line . It successed , but with a price that the line must be displayed:sweat_smile: I current plan to change the method and use THREE.Ray and ray.distanceSqToSegment to test line segment directly. I think Box3 is for 3D game . And my mesh are all plane object that do not have a thickness
after a whole day of struggle of search web and thinking. I finally work out a solution use ray.distanceSqToSegment .
the code change to following
let d1;
let d2;
let d2vec = new THREE.Vector3();
Collide.update = function testCollide(delta) {
if (bullets.length == 0 || bBoxs.length == 0) return;
let i = bullets.length;
while (i--) {
let bullet = bullets[i];
bullet.updateMatrixWorld();
let testPoint = bullet.position.clone();
testPoint.z = 0;
let ray = new THREE.Ray(bullet.lastPosition, bullet.lastDirect);
d2vec.subVectors(testPoint, bullet.lastPosition);
d2 = d2vec.lengthSq();
if (d2 == 0) return;
intersectBboxs(ray, bBoxs, bullet);
bullet.lastPosition.copy(testPoint);
bullet.lastDirect.copy(bullet.direct);
}
}
let intersetPoint = new THREE.Vector3();
let satPosRot = new THREE.Vector3();
let quaternion = new THREE.Quaternion();
function intersectBboxs(ray, bBoxs, bullet) {
for (let i = 0; i < bBoxs.length; i++) {
let bBox = bBoxs[i];
for (let j = 0; j < bBox.length; j++) {
let segPoints = bBox[j];
let v1 = segPoints[0].clone();
let v2 = segPoints[1].clone();
satPosRot.setFromMatrixPosition(bBox.host.scene.matrixWorld);
bBox.host.scene.getWorldQuaternion(quaternion);
satPosRot.applyQuaternion(quaternion);
v1.add(satPosRot);
v2.add(satPosRot);
if (!facePoint(v1, v2, bullet.lastPosition)) continue;
let distSq = ray.distanceSqToSegment(v1, v2, intersetPoint);
console.log(distSq);
if (distSq <= 0.1) {
d1 = ray.origin.clone().sub(intersetPoint).lengthSq();
if (d1 <= d2) {
BulletsMaker.reclaim(bullet);
//SatelliteMaker.reclaim(bBox.host);
console.log("bang");
return;
}
}
}
console.log('-----------------');
}
}
function facePoint(v1, v2, p) {
let d = (p.x - v1.x) * (v2.y - v1.y) - (p.y - v1.y) * (v2.x - v1.x);
if (d <0) return true;
else return false;
}
There is a interesting fact that as you watching the above video you may notice I output the result of ray.distanceSqToSegment. I find it not accurate .it is not alway zero as expected when the ray intersect segment. I must use a threshold value. But it is more accurate when the bullet get closer to the target.
Iâm currently studying at box3 in order to max leverage the convinience of three.js . My question is : how to let the box3 follow to object. I find applyMatrix4. I originly thought It could let the box3 follow the objects transformation include translate rotate and scale. When I test this function I found its not working. So What is the proper approach that could let the box3 follow the object. and What is the applyMatrix4 used for ?
Hi, @Mugen87. I find accurate problems again. Now I am sure that It will be 100% accurate when the obj I shoot is static . But , if the target move,then there will be chance of losing hit. What is your thought?? thanks
I draw a picture :
I build a fiddle , Why the box3 fly away instead of follow the plane ? I use it in a tween . Is it mean I canât use with tween.js? https://jsfiddle.net/relaxslow/wk5x7fby/