schteppe / cannon.js

A lightweight 3D physics engine written in JavaScript.
http://schteppe.github.com/cannon.js
MIT License
4.67k stars 709 forks source link

Proper "isGrounded()" detection? #313

Open IceReaper opened 7 years ago

IceReaper commented 7 years ago

I'm currently trying to implement FPS controls in my prototype. In theory everything should work, but i still have a problem left which i cannot find out: Proper "isGrounded()" detection.

I'm currently using the ray-cast approach to detect whether something is directly below the player. This however only returns true if the body is beneath the player collision models center. So i need a different approach on this. some examples are using a collide event. However collision may occurm and then after some movement you might fall in a hole, resulting in the isGrounded variable being still true as its set to true on collide. So basically i need to check this at the moment the players wants to actually jump. How to do that correctly? As the players body cannot rotate at all and is a cylinder, i theoretically need to check wether something is touching the bottom plane (or planes?) on-the-fly.

measuredweighed commented 7 years ago

This is an old issue, but here's how I resolved the need for a similar check: essentially I use the cannon.js postStep event to iterate over contacts in the world, check whether any of those contacts involve my player object and update an isOnGround flag based on the contact normal.

It would be more efficient to use cannon.js' collide event listener on the player body itself, but as far as I'm aware cannon offers no 'collisionEnd' callback (which is necessary to reset the isOnGround flag.

Here's some pseudo-code:

var upVector = new CANNON.Vec3(0, 1, 0);
var contactNormal = new CANNON.Vec3(0, 0, 0);

world.addEventListener("postStep", function(e) {
    isOnGround = false;

    if(world.contacts.length > 0) {
        for(var i = 0; i < world.contacts.length; i++) {
            var contact = world.contacts[i];
            if(contact.bi.id == object.id || contact.bj.id == object.id) {
                if(contact.bi.id == object.id) {
                    contact.ni.negate(contactNormal);
                } else {
                    contact.ni.copy(contactNormal);
                }

                isOnGround = contactNormal.dot(upVector) > 0.5;
            }
        }
    }
}
Hari01V commented 2 years ago

How to use the Raycast approach to detect whether something is directly below the player? Example code would be appreciated

let DOWN_DIRECTION = new THREE.Vector3(0, -1, 0); this._rayCaster = new THREE.Raycaster(this._player._mesh.position, DOWN_DIRECTION, 0, 10); this._rayCaster.set(this._player._mesh.position, DOWN_DIRECTION); const intersect = this._rayCaster.intersectObjects(this._player._scene.children, true);

Here, Intersect is returned with an empty array. Also, the Model has a plane (ground) and a Cuboid for player mesh. Am i missing something in the code snippet for it to work?

johnnyrainbow commented 3 months ago

This is an old issue, but here's how I resolved the need for a similar check: essentially I use the cannon.js postStep event to iterate over contacts in the world, check whether any of those contacts involve my player object and update an isOnGround flag based on the contact normal.

It would be more efficient to use cannon.js' collide event listener on the player body itself, but as far as I'm aware cannon offers no 'collisionEnd' callback (which is necessary to reset the isOnGround flag.

Here's some pseudo-code:

var upVector = new CANNON.Vec3(0, 1, 0);
var contactNormal = new CANNON.Vec3(0, 0, 0);

world.addEventListener("postStep", function(e) {
    isOnGround = false;

    if(world.contacts.length > 0) {
        for(var i = 0; i < world.contacts.length; i++) {
            var contact = world.contacts[i];
            if(contact.bi.id == object.id || contact.bj.id == object.id) {
                if(contact.bi.id == object.id) {
                    contact.ni.negate(contactNormal);
                } else {
                    contact.ni.copy(contactNormal);
                }

                isOnGround = contactNormal.dot(upVector) > 0.5;
            }
        }
    }
}

Helpful snippet but just to correct you, the contact normal calculations are wrong. Here is the right way to do it

   if(contact.bi.id == object.id) {
         contact.ni.negate(contactNormal);
      } else {
          this.contactNormal.copy(contact.ni)
      }