Raycaster.intersect can not work with mesh , which is imported from OBJLoader

Hi, I am new to Three.js.
I have a question about raycaster.intersect.
The question is why console shows an error saying “Uncaught TypeError: Cannot read properties of undefined (reading ‘visible’)”

I want to use raycaster.intersect to find out the face of the mesh that my mouse is pointing to.

picture below is my bowser

Here is HTML code

<html>
	<head>
		<title>Three.js Boilerplate</title>
		<style>
			body { margin: 0; }
			canvas { width: 100%; height: 100% }
		</style>
	</head>
	<body>
		<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/87/three.min.js"></script>
		<script src="bower_components/numjs/dist/numjs.min.js"></script>
		<script src="/assets/OrbitControls.js"></script>
		<script src="/examples/local-deformation/assets/OBJLoader.js"></script>
		<script src="/examples/local-deformation/assets/MTLLoader.js"></script>
		<script src="/examples/local-deformation/scripts.js"></script>
		
	</body>
</html>

Here is Javascript code

const mouse = {
    x: undefined,
    y: undefined
  }

const raycaster = new THREE.Raycaster()
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 75,
     window.innerWidth/window.innerHeight, 0.1, 1000 );
camera.position.z = 200;

var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );

var controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.25;
controls.enableZoom = true;

var keyLight = new THREE.DirectionalLight(new THREE.Color('hsl(30, 100%, 75%)'), 1.0);
keyLight.position.set(-100, 0, 100);

var fillLight = new THREE.DirectionalLight(new THREE.Color('hsl(240, 100%, 75%)'), 0.75);
fillLight.position.set(100, 0, 100);

var backLight = new THREE.DirectionalLight(0xffffff, 1.0);
backLight.position.set(100, 0, -100).normalize();

scene.add(keyLight);
scene.add(fillLight);
scene.add(backLight);

let mesh
var mtlLoader = new THREE.MTLLoader();
mtlLoader.setTexturePath('/examples/local-deformation/assets/');
mtlLoader.setPath('/examples/local-deformation/assets/');
mtlLoader.load('head.mtl', function (materials) {

    materials.preload();
    var objLoader = new THREE.OBJLoader();
    objLoader.setMaterials(materials);
    objLoader.setPath('/examples/local-deformation/assets/');
    objLoader.load('head.obj', function (object) {
    object.traverse(function (node) {
        if (node.type === 'Mesh'){ mesh = node};
        
    });
    this.mesh = mesh
    scene.add(mesh);
    });
});

var animate = function () {
	requestAnimationFrame( animate );
	renderer.render(scene, camera);
    raycaster.setFromCamera(mouse, camera)
    const intersects = raycaster.intersectObject(mesh)
    if (intersects.length> 0) {
      console.log(intersects[0])}

};


animate();

addEventListener('mousemove',(event) =>
{
  mouse.x = (event.clientX / innerWidth)*2 -1 
  mouse.y = -(event.clientY / innerHeight)*2 +1 

//   console.log(mouse)
})

in this line,
const intersects = raycaster.intersectObject(mesh)
mesh is undefined, because the animation loop has started before you’ve finished loading the model and you are trying to use mesh, which hasn’t yet been set until the OBJLoader.load success callback completes.

Yes, I noticed that when I console.log(mesh), my Browser will will say “undefined”. But I have no idea how to fix it.I think it is related to what you mentioned.

How can I make sure that OBJLoader.load.success callback completes and then start animation loop.Because I have place animate() in the end part of script and I think the OBJLoader in the upper part of script that will be compiled first.

Can you give me some tips to achieve what you said??
Thanks in advance.

Start your loop when the model has completed. What you have there above is a classic race condition and you’ll lose the race every time.

objLoader.load(url, data => {
  ...
  animate()

This will fix it, but it’s not ideal because what if you have to load multiple assets. Generally, you may want to read up a little on promises and async/await, without these you won’t come far.

1 Like

there are many ways to solve this issue. The easiest way is to check if mesh exists before trying to use it.

if(mesh){
  raycaster.setFromCamera(mouse, camera)
  const intersects = raycaster.intersectObject(mesh)
  if (intersects.length> 0) {
    console.log(intersects[0])
  }
}

OR,

you could declare and define mesh as an empty array

const mesh = []

then push into it after your model is loaded

if (node.type === 'Mesh'){ mesh.push(node)};

and then use the raycasters intersectObjects method. That way you can dynamically push/pop meshes at runtime while still animating regardless of if the mesh array contained any meshes or not.

This example (Raycaster - sbcode.net) uses the array technique, but also does the intersectObjects check in the mousemove event callback rather than the animation loop.

Note that your problem is not related to the OBJLoader, but is JavaScript related because you were tying to use an object before is was defined.

OR,
and many more ways

1 Like

Thank you so much it is very appreciated.
I implemented your way and success to find the mesh that I imported.