Avoid collision with walls. first-person control

I’ve created navigation mesh on my Scene with walkable Property in Blender. The walkable area is floor without objects (You can see in the attached image).I could load it as glb data,save floor and wall as Obejct 3d, connect it with addevent click(mouseup) action and calculate between mouse Position and my navmesh by Raycasting. The Racast Outputs is ok, but the Problem is, i don’t Know how can i avoid camera to go through the wall, because as soon as i clicked, camera moves, and raycast show position of where i clicked, not where my camera gonna land (beause as long as i hold the button, Camera keeps going) . I’ve searched everywhere and the only solution that I’ve found was Three-pathfinding library-> function clampStep but there is no example for that. Any suggestion how can i restrict movement of camera between the walls?

I’m totally beginner in both threejs and blender, would really appreciate any help or suggestion.

      this.scene = new THREE.Scene();
      this.scene.background = new THREE.Color(0xdddddd);
   
     
      // this.camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 5000);
      this.camera.rotation.y = 10 / 180 * Math.PI;
      this.camera.position.x = 10;
      this.camera.position.y = 3;
      this.camera.position.z = 20;
  
      
      this.renderer = new THREE.WebGLRenderer({ antialias: true });
      this.renderer.setSize(window.innerWidth, window.innerHeight);
      this.renderer.outputEncoding = THREE.sRGBEncoding;
      this.renderer.physicallyCorrectLights = true;
    
      document.body.appendChild(this.renderer.domElement);

      this.controls =  new FirstPersonControls(this.camera, this.renderer.domElement);
      this.controls.movementSpeed =  5;
      this.controls.lookSpeed = 0.01;
      this.controls.noFly = false;
      this.controls.lookVertical = false;
     

      this.hlight = new THREE.AmbientLight(0xffffff, 3);;
      this.scene.add(this.hlight);
      let loader = new GLTFLoader();
      loader.load("/ShowRoom121122.glb",  (gltf) => {

			
        const self = this;
				gltf.scene.traverse(function (child) {
        
    				if (child.isMesh){
      
						if (child.name=="navMesh_1"  ){
              
              self.navmesh=child;
              self.sceneMeshes.push(child);
							child.material.transparent = true;
						}
            else if(child.name=="wall_1" ||child.name=="wall_2"){
              self.navmesh=child;
              self.sceneMeshes.push(child);
            }
            else{
							child.castShadow = false;
							child.receiveShadow = true;
						}
            self.scene.add( gltf.scene );
					}
				})});
        
    },
    raycast:function(e){
    
    	// debugger	
      let mousex=0;
			this.mouse.x = ( e.clientX / window.innerWidth ) * 2 - 1;
			this.mouse.y = - ( e.clientY / window.innerHeight ) * 2 + 1;
      this.raycaster.ray.origin.copy( this.camera.position );
this.camera.getWorldDirection( this.raycaster.ray.direction );

			//2. set the picking ray from the camera position and mouse coordinates
			this.raycaster.setFromCamera( this.mouse, this.camera );    

			//3. compute intersections
			const intersects = this.raycaster.intersectObjects( this.sceneMeshes );
		
			if (intersects.length>0)
      {
       console.log(intersects[0])
      }
      else{
       
      }
  
    },
    animate:function(){
      requestAnimationFrame(this.animate)
      this.controls.update(0.01);
      this.renderer.render(this.scene, this.camera);
      this.renderer.domElement.addEventListener( 'click', this.raycast,false );
    }
  },
  mounted() {
      this.init();
      this.animate();
  }
type or paste code here

From the picture, I’m guessing that the entire room is just a rectangle, so here’s what I would do: print out the position of the camera to the console or screen, then walk the camera around the room and look at the numbers to find the minimum and maximum values for X and Z that keep the camera inside where you want it. Then, change the code so that every frame you adjust the camera position make sure it stays inside those bounds.

The general topic you’re looking for is called “collision detection,” so search for that. There are at least a couple of collision detection libraries for three.js, but it’s a big subject.

Thanks for your respond, the problem is, it’s more than one rectangle, almost 3-4. And i would prefer to do it with raycaster (if it’s possible…) and yeah i’ve searched for collision detection, but didn’t find lot related to blender and three.js navmesh concept… :worried:
because it’s first control, it doesn’t make sense somehow, that i calculate raycast between camera position and target(where i clicked), i think, maybe i should calculate camera-position while it’s moving (but i don’t know how),:face_with_diagonal_mouth:

The navmesh library looks like path planning for bots rather collision detection. I guess you could make the walkable navmesh smaller than the floor, then let the user click on the real geometry to define the goal point. Then, fire a 2d ray back from the goal point to the camera to figure out where it first intersects that distant edge of the 2d navmesh, then let the navmesh library generate a walkable path to that point on the distant edge.

That might look really interesting. If you’re in one room facing the doorway to another room and you click the far wall in the other room right next to the edge of the doorway, the camera could walk an indirect path to that goal point, passing through the middle of the doorway. This idea wouldn’t work if you want to have floors over floors like in the demo, but it could look really interesting.

If you’d rather do real collision detection, build the collision floor as a set of large 2d triangles and use a js 2d collision detection library to avoid getting too close to the outside edges.

You can do 3d collision detection with raycasting against the world, but you’ll need to cast many rays and you’ll run into irritating edge cases. Detecting collisions is not the hard part; reacting to them is where it gets bothersome. I suppose you also could cast down to the floor at an angle and stop the movement when one of the rays stops hitting the floor mesh.

For both 2d and 3d, you’ll need to handle cases where you’ll want to slide along the wall rather than stopping.

I’d recommend you copy the source code of the controls and modify it based on your needs.

You can modify the handler event that controls the movement and verify if the camera is near a wall before committing an action.

The three js documentation has the source code, after methods and properties, at the end of the section.

1 Like

Here is an example using raycasting:

// Create a list of objects you want to collide with
var collidableMeshList = [];
var wall = new THREE.Mesh(wallGeometry, wallMaterial);
wall.position.set(100, 50, -100);
scene.add(wall);
collidableMeshList.push(wall);
// etc...

// Check your player (MovingCube) for collisions in your game/animate loop:
for (var vertexIndex = 0; vertexIndex < MovingCube.geometry.vertices.length; vertexIndex++) {		
  var localVertex = MovingCube.geometry.vertices[vertexIndex].clone();
  var globalVertex = localVertex.applyMatrix4( MovingCube.matrix );
  var directionVector = globalVertex.sub( MovingCube.position );
  
  var ray = new THREE.Raycaster( originPoint, directionVector.clone().normalize() );
  var collisionResults = ray.intersectObjects( collidableMeshList );
  if (collisionResults.length > 0 && collisionResults[0].distance < directionVector.length()) 
    appendText(" Hit ");
}
1 Like

The problem is that raycasting is only done forward. To the back the raycaster is blind. so setFromCamera doesn’t help here. You need four raycasters, one for each direction. You instantiate them with the camera position and a direction vector in each cardinal direction. When checking, you sort the four intersection arrays by distance and pull the smallest value. If this value falls below a certain threshold, you move the camera in the opposite direction. That’s all.

It was a pleasure to help
Chiando

 raycastLock: function () {
     var c = this.camera.position
     var rc1 = new THREE.Raycaster(c, new THREE.Vector3(1, 0, 0)).intersectObjects(this.sceneMeshes, false)
     var rc2 = new THREE.Raycaster(c, new THREE.Vector3(0, 0, 1)).intersectObjects(this.sceneMeshes, false)
     var rc3 = new THREE.Raycaster(c, new THREE.Vector3(-1, 0, 0)).intersectObjects(this.sceneMeshes, false)
     var rc4 = new THREE.Raycaster(c, new THREE.Vector3(0, 0, -1)).intersectObjects(this.sceneMeshes, false) 
     var r1 = rc1.length > 0 ? rc1[0] : null;
     var r2 = rc2.length > 0 ? rc2[0] : null;
     var r3 = rc3.length > 0 ? rc3[0] : null;
     var r4 = rc4.length > 0 ? rc4[0] : null;
     var arr = [];
     if (r1) arr.push(r1);
     if (r2) arr.push(r2);
     if (r3) arr.push(r3);
     if (r4) arr.push(r4);
     var rSorted = arr.sort((a, b) => { return a.distance - b.distance })
     if (rSorted.length > 0 && rSorted[0].distance < 2) {
         var r = rSorted[0]
         var d = new THREE.Vector3()
         d.subVectors(c, r.point).normalize();
         console.log(r,d)
         this.camera.position.addScaledVector(d, 0.3)
     }                
 },
1 Like

I have optimized this again to get along with only one Raycaster. Now it determines the running direction and instantiates the raycaster with the corresponding direction vector.

raycastLock: function () {
    var camPosition = this.camera.position
    if (!this.oldPosition) {
        this.oldPosition = camPosition.clone();
        return;
    }
    var direction = new THREE.Vector3();
    direction.subVectors(camPosition, this.oldPosition).normalize(); 
    this.oldPosition = camPosition.clone();
    var intersectedObjects = new THREE.Raycaster(camPosition, direction).intersectObjects(this.sceneMeshes, false)
     
    if (intersectedObjects.length > 0 && intersectedObjects[0].distance < 2) {
        var obj = intersectedObjects[0]
        var moveV = new THREE.Vector3()
        moveV.subVectors(camPosition, obj.point).normalize();
        this.camera.position.addScaledVector(moveV, 0.3)
    }
},
2 Likes