Why can't my intersect boxes for my GLTF/FBX models detect collision?

Im new to threejs, so sorry for newbie question.

I’m not sure why my intersect for box3 bounding boxes for my GLTF model and FBX model detect a collision. I drew a helper box around to make sure my GLTF model’s bounding box is moving along. But when I crash into the FBX building’s bounding box, it still does not detect a collision. I’m unsure why this is happening and cant seem to find a google answer for it too.

const buildingboundingBox = new THREE.Box3()


fbxLoader.load( //FBX model for the building 
    one_story.href,
    (object) => {
        object.scale.set(0.25, 0.25, 0.25)
        object.position.set(60, 0, 0)
        object.rotateY(-0.5 * Math.PI)
        scene.add(object)
        buildingboundingBox.setFromObject(object);
        const buildingBBhelper = new THREE.Box3Helper(buildingboundingBox, 0x00ff00);
        scene.add(buildingBBhelper);
    }, (error) => { console.log(error) }
);

//#region CHARACTERS
//All logics for the character
const raycaster = new THREE.Raycaster();
raycaster.far = 100;
const characterboundingBox = new THREE.Box3()
gltf.load(character3.href, function (gltf) {
    model = gltf.scene;

    model.position.set(0, 0, 0)
    model.scale.set(10, 10, 10)
    container.add(model);

    mixer = new THREE.AnimationMixer(model);
    const clips = gltf.animations;

    const idleClip = THREE.AnimationClip.findByName(clips, 'Idle');
    const idleAction = mixer.clipAction(idleClip);

    const idleShootClip = THREE.AnimationClip.findByName(clips, 'Idle_Gun');
    const idleShootAction = mixer.clipAction(idleShootClip);

    const walkClip = THREE.AnimationClip.findByName(clips, 'Run');
    const walkAction = mixer.clipAction(walkClip);

    const walkShootClip = THREE.AnimationClip.findByName(clips, 'Run_Gun');
    const walkShootAction = mixer.clipAction(walkShootClip);

    idleAction.play();
    walkShootAction.stop();
    walkAction.stop();
    idleShootAction.stop();

    let newPosition = container.position.clone().add(new THREE.Vector3(0, 5, 0));

    function updateNewPosition() {
        newPosition = container.position.clone().add(new THREE.Vector3(0, 5, 0));
    }

    //setting the bounding box for the character
    characterboundingBox.setFromObject(model);
    const helper = new THREE.Box3Helper(characterboundingBox, 0xff0000);
    container.add(helper);

    // Key and mouse events
    window.addEventListener("keydown", (e) => {
        const { keyCode } = e;
        if ((keyCode === 87 || keyCode === 38) && isPlaying) {
            // baseActions.idle.weight = 0;
            // baseActions.run.weight = 5;
            // activateAction(baseActions.run.action);
            // activateAction(baseActions.idle.action);
            idleAction.stop();
            if (mousedown) { walkShootAction.play(); }
            else { walkAction.play(); }
            movingForward = true;
        }
        if (keyCode == 27 && isPlaying) {
            isPlaying = false;
            body.style.cursor = 'default';

        }
        if (keyCode == 32 && !isPlaying) {
            isPlaying = true;

            body.style.cursor = 'none';

        }
        if (characterboundingBox.intersectsBox(buildingboundingBox)) {
            console.log("collided");
        }
    });
    window.addEventListener("keyup", (e) => {
        const { keyCode } = e;
        if (keyCode === 87 || keyCode === 38) {
            // baseActions.idle.weight = 1;
            // baseActions.run.weight = 0;
            // activateAction(baseActions.run.action);
            // activateAction(baseActions.idle.action);
            walkAction.stop();
            walkShootAction.stop();
            movingForward = false;
        }
    });
    window.addEventListener("pointerdown", (e) => {
        if (!isPlaying) return;

        if (movingForward == true) {
            idleAction.stop();
            walkAction.stop();
            idleShootAction.stop();
            walkShootAction.play();
        } else {
            idleAction.stop();
            walkAction.stop();
            walkShootAction.stop();
            idleShootAction.play();
        }

        // pointer.x = (e.clientX / window.innerWidth) * 2 - 1;
        // pointer.y = - (e.clientY / window.innerHeight) * 2 + 1;
        // pointer.x = 0;
        // pointer.y = 0;
        // raycaster.setFromCamera(pointer, camera);
        updateNewPosition();

        let forward = new THREE.Vector3(0, 0, 1);
        forward.applyQuaternion(model.quaternion);

        raycaster.set(newPosition, forward);

        const intersects = raycaster.intersectObjects(scene.children);
        for (let i = 0; i < intersects.length; i++) {
            // console.log(intersects[i].object.name);
            if (intersects[i].object.name == "Zombie_Arm") {
                console.log("hit");
                zombie1Health = 0;
            }
            if (intersects[i].object.name == "Zombie") {
                console.log("hit");
                zombie2Health = 0;
            }
            if (intersects[i].object.name == "Zombie_Chubby") {
                console.log("hit");
                zombie3Health = 0;
            }
        }

        // scene.add(new THREE.ArrowHelper(raycaster.ray.direction, raycaster.ray.origin, 300, 0xff0000));

        mousedown = true;
    });
    window.addEventListener("pointerup", (e) => {
        if (movingForward == true) {
            walkShootAction.stop();
            walkAction.play();
        } else {
            idleShootAction.stop();
            idleAction.play();
        }
        mousedown = false;
    });
    window.addEventListener("pointermove", (e) => {
        if (isPlaying) {
            const { movementX, movementY } = e;
            const offset = new THREE.Spherical().setFromVector3(
                camera.position.clone().sub(cameraOrigin)
            );
            const phi = offset.phi - movementY * 0.02;
            offset.theta -= movementX * 0.02;
            offset.phi = Math.max(0.01, Math.min(0.35 * Math.PI, phi));
            camera.position.copy(
                cameraOrigin.clone().add(new THREE.Vector3().setFromSpherical(offset))
            );
            camera.lookAt(container.position.clone().add(cameraOrigin));
        }
    });



});
//#endregion

function animate() {
    // requestAnimationFrame(animate);
    var delta = clock.getDelta();


    // Check for collisions
    if (characterboundingBox.intersectsBox(buildingboundingBox)) {
        console.log("Collided with building!");
    }

    updateZombie1Animation();
    updateZombie2Animation();
    updateZombie3Animation();

    if (mixer) mixer.update(delta);
    if (zombMixer) zombMixer.update(delta);
    if (zombMixer1) zombMixer1.update(delta);
    if (zombMixer2) zombMixer2.update(delta);

    if (movingForward) {
        // Get the X-Z plane in which camera is looking to move the player
        camera.getWorldDirection(tempCameraVector);
        const cameraDirection = tempCameraVector.setY(0).normalize();

        // Get the X-Z plane in which player is looking to compare with camera
        model.getWorldDirection(tempModelVector);
        const playerDirection = tempModelVector.setY(0).normalize();

        // Get the angle to x-axis. z component is used to compare if the angle is clockwise or anticlockwise since angleTo returns a positive value
        const cameraAngle = cameraDirection.angleTo(xAxis) * (cameraDirection.z > 0 ? 1 : -1);
        const playerAngle = playerDirection.angleTo(xAxis) * (playerDirection.z > 0 ? 1 : -1);

        // Get the angle to rotate the player to face the camera. Clockwise positive
        const angleToRotate = playerAngle - cameraAngle;

        // Get the shortest angle from clockwise angle to ensure the player always rotates the shortest angle
        let sanitisedAngle = angleToRotate;
        if (angleToRotate > Math.PI) {
            sanitisedAngle = angleToRotate - 2 * Math.PI
        }
        if (angleToRotate < -Math.PI) {
            sanitisedAngle = angleToRotate + 2 * Math.PI
        }

        // Rotate the model by a tiny value towards the camera direction
        model.rotateY(
            Math.max(-0.05, Math.min(sanitisedAngle, 0.05))
        );
        container.position.add(cameraDirection.multiplyScalar(0.05));
        camera.lookAt(container.position.clone().add(cameraOrigin));
    }

    renderer.render(scene, camera)
}
renderer.setAnimationLoop(animate);

Bounding boxes are just a tool for figuring out bounding boxes. they don’t participate in raycasting or the scene/rendering. If you want a box to respond to raycasts, you’ll have to make a

let mesh = THREE.Mesh(new THREE.BoxGeometry(),new THREE.MeshStandardMaterial())
boundingBox.getCenter(mesh.position);
boundingBox.getSize(mesh.size);
scene.add(mesh);

But what is the intersectobjects function for? Shouldn’t that be used to return a true or falce for collisions?

So on top of a Box3, I still need a raycast? Or I can just use that mesh function?

intersectsObjects will only intersect with Mesh and Points, and Line iirc.
You will have to create some actual geometry to intersect with, so if you only have a Box3 you will have to construct a corresponding mesh, scaled, positioned, and rotated how you have your Box3 in order to raycast against it.

Box3 is just a mathematical datatype. a min and a max Vector3. It doesn’t have a “position” or “rotation” or “scale”.

You can however extract those properties like i showed above.

the mesh function makes a mesh from your Box3 and then you can raycast against that mesh.

Ahh i see, so i need a mesh and a box3 on my gltf model to be able to detect collisions and raycasts if im understanding this correctly?

But if so, how do i form a mesh based of my box3? because my box3 is already setfromobject of my gltf model. So I dont know how to create a mesh that is shaped like the box3 (if you get what i mean)

//#region CHARACTERS
//All logics for the character
const raycaster = new THREE.Raycaster();
raycaster.far = 100;
const characterboundingBox = new THREE.Box3()
gltf.load(character3.href, function (gltf) {
    model = gltf.scene;

    model.position.set(0, 0, 0)
    model.scale.set(10, 10, 10)
    container.add(model);

    mixer = new THREE.AnimationMixer(model);
    const clips = gltf.animations;

//animation codes...

    //setting the bounding box for the character
    characterboundingBox.setFromObject(model);
    const helper = new THREE.Box3Helper(characterboundingBox, 0xff0000);
    container.add(helper);
    // This mesh dont seem to be forming around the box3 boundingbox, because it causes the helper to get removed too.
    let mesh = new THREE.Mesh(new THREE.BoxGeometry(),new THREE.MeshStandardMaterial())
    characterboundingBox.getCenter(mesh.position);
    characterboundingBox.getSize(mesh.size);
    container.add(mesh);

    let newPosition = container.position.clone().add(new THREE.Vector3(0, 5, 0));

    function updateNewPosition() {
        newPosition = container.position.clone().add(new THREE.Vector3(0, 5, 0));
    }

// Key and mouse events codes...

});

//#endregion

Hehe I hate to be the bearer of bad news but… Animated meshes are especially tricky.
Google: “threesj animated bounding boxes”

Boundingboxes are usually computed once at startup from a Geometry, because they are a bit expensive to compute, since the system has to loop through all vertices to compute bounding boxes.

The problem is, Animated meshes, may be moving those vertices on the GPU while animating, so the bounding box is no longer accurate.

Also with animated meshes, often the artists will have a different sized character model, and during animation, use the animation itself to scale the character down or up to the correct size, so even if you compute the bounding box from the original mesh, it will not be close to correct after animation starts.

Then there is the fact that raycasting animated meshes is Also pretty expensive… so if you’re trying to raycast over an animated character, you may feel your rendering start to chug as the mouse cursor goes over the character.

So usually the practical solution is to NOT raycast a character, but instead attach some invisible box meshes to the character bones, and raycast against them instead.

This also creates problems for frustum culling animated characters where you will sometimes see a character disappear as it gets close to the screen edges or camera…

So to fight that, we often just disable frustum culling on characters.

There is no perfect solution that doesn’t impact performance, often in drastic ways.

These problems are why physics and interaction with characters are often simplified to just pretending the character is a single Cylinder, or a single Box or a Capsule shape, and raycasting against that.

check this out. I created a Box3 bounding box like that on my character which can move with the character. instead of using the mesh of the animation, can I create the mesh on the bb and detect like that?

like, it may not be as accurate but i think its at least efficient enough (considering this website is super heavy alrd, im trying to cut as much corners as possible.)

the red thin square box is the bounding box i coded. the red and orange shades are stuff i drew to visualize what could possibly be done to apply a mesh over the entire bounding box

sorry if its unclear, im trying my best to understand meshes as much as possible haha

Yeah. Try this?

let listOfBoxesToRaycast = [  ]
let boundingBox = YOUR BOX THREE HERE
let mesh = THREE.Mesh(new THREE.BoxGeometry(),new THREE.MeshStandardMaterial({transparent:true,opacity:.9}))
boundingBox.getCenter(mesh.position); // GET ITS CENTER INTO MESH POSITION
boundingBox.getSize(mesh.size); //GET ITS SIZE INTO MESH SCALE
scene.add(mesh);

YOURCHARACTER.attach(mesh); // Now .attach it to the character so character drags it around

listOfMeshesToRaycast.push( mesh );  //Add it to the list of meshes to raycast

...

when you need to raycast:

let hits = raycaster.intersectObjects( listOfMeshesToRaycast, ...

if(hits.length>0){
   let boxThatWasHit = hits[0].object;
   let hitPoint = hits[0].point;
   //Raycaster hit a box
}