# Collide test not 100% accurate

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.

FPS Example

The related code section is encapsulated in the update method of the Bullet class.

2 Likes

thank you @Mugen87 I think I should first build a smaller debug scene and find the problem

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.

``````if( (target.position.x - bullet.position.x)^2 + (target.position.y -bullet.position.y)^2 < R^2){
collision_function()
}
``````

sounds good , I would have a try,BTW ,your english is better than me

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

Oh,thanks, I remember .It is too careless of me!

I build a small test program,here is the code , I failed to build it in fiddle. So I post here
html:

``````<!DOCTYPE html>
<html>

<title>ray intersect line</title>
<script src="/lib/three.js"></script>
<script src="/lib/TrackballControls.js"></script>
<script src="/lib/tween.js"></script>

<body>
<div class="container">
<div class="fire"></div><div class="hit"></div>
<canvas class="scene"></canvas>
</div>
<script src="/threejsExample/f06-ray/main.js"></script>
</body>

</html>
``````

css:

``````body{
margin: 0;
}
.container {
width: 640px;
height: 480px;
position: relative;
overflow: hidden;
border: solid 1px blue;
}
.fire{
position: absolute;
right: 0;
bottom: 0;
}
.hit{
position: absolute;
right: 0;
bottom: 30px;
}
``````

js:

``````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);

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);
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);
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;

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

sorry I make mistake about your approach . The correct pic should be like this. Now I can understand ,Thank you, let me continue my test

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

1 Like

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`.

A bounding box is normally of type `THREE.Box3` which has not geometry. So it canât be rendered. Do you visualize the AABB with `THREE.Box3Helper`?

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);
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 :

If the geometry of the object does not change then it should be sufficient to use Box3.applyMatrix4 with the world matrix of the respective 3D object.

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/