Raycast through multiple entities

Right now I can only use raycastFirst to return the first entity hit by a ray cast from my camera. How can I raycast and have all hit objects returned? IE not stop on first hit rigidbody?

I think there is not such function yet but the staff should confirm that

PlayCanvas uses ammo.js which is a JS complied version of Bullet physics library.

Bullet allows you to do a raycast all via AllHitsRayResultCallback http://bulletphysics.org/mediawiki-1.5.8/index.php/Using_RayTest but hasn’t been explicitly exposed in ammo.js: https://github.com/kripken/ammo.js/blob/master/ammo.idl#L138

What you can do at this point:

  • Make a pull request to expose this in either the ammo.js GitHub https://github.com/kripken/ammo.js or in PlayCanvas’s fork: https://github.com/playcanvas/ammo.js
  • Use filters for the rigidbodies so you can raycast against specific groups
  • Create your own function that does raycastFirst multiple times (each one starting slightly ahead of the hit point) until you have reached a certain length or not hit anything.

Can you tell me how to do the filtering? I really don’t see anything other than raycastFirst and it has no filter options…

You will have to look into the Bullet API and what is exposed in ammo.js.

I wrote a few scripts and functions in this game but I no longer have access to the whole source. This what I can get from the site:

Script filter

var PhysicsFilter = pc.createScript("physicsFilter");
PhysicsFilter.attributes.add("groupDefault", {
    type: "boolean",
    title: "Group Default",
    "default": !0
}),
PhysicsFilter.attributes.add("groupRagDoll", {
    type: "boolean",
    title: "Group RagDoll",
    "default": !1
}),
PhysicsFilter.attributes.add("groupEnvironment", {
    type: "boolean",
    title: "Group Environment",
    "default": !1
}),
PhysicsFilter.attributes.add("groupSummonObject", {
    type: "boolean",
    title: "Group SummonObject",
    "default": !1
}),
PhysicsFilter.attributes.add("groupSupport", {
    type: "boolean",
    title: "Group Support",
    "default": !1
}),
PhysicsFilter.attributes.add("groupTrain", {
    type: "boolean",
    title: "Group Train",
    "default": !1
}),
PhysicsFilter.attributes.add("groupMisc", {
    type: "boolean",
    title: "Group Misc",
    "default": !1
}),
PhysicsFilter.attributes.add("groupRain", {
    type: "boolean",
    title: "Group Rain",
    "default": !1
}),
PhysicsFilter.attributes.add("maskDefault", {
    type: "boolean",
    title: "Mask Default",
    "default": !0
}),
PhysicsFilter.attributes.add("maskRagDoll", {
    type: "boolean",
    title: "Mask RagDoll",
    "default": !1
}),
PhysicsFilter.attributes.add("maskEnvironment", {
    type: "boolean",
    title: "Mask Environment",
    "default": !1
}),
PhysicsFilter.attributes.add("maskSummonObject", {
    type: "boolean",
    title: "Mask SummonObject",
    "default": !1
}),
PhysicsFilter.attributes.add("maskSupport", {
    type: "boolean",
    title: "Mask Support",
    "default": !1
}),
PhysicsFilter.attributes.add("maskTrain", {
    type: "boolean",
    title: "Mask Train",
    "default": !1
}),
PhysicsFilter.attributes.add("maskMisc", {
    type: "boolean",
    title: "Mask Misc",
    "default": !1
}),
PhysicsFilter.attributes.add("maskRain", {
    type: "boolean",
    title: "Mask Rain",
    "default": !1
}),
PhysicsFilter.DEFAULT = 8,
PhysicsFilter.RAGDOLL = 16,
PhysicsFilter.ENVIRONMENT = 32,
PhysicsFilter.SUMMON_OBJECT = 64,
PhysicsFilter.SUPPORT = 128,
PhysicsFilter.TRAIN = 256,
PhysicsFilter.MISC = 512,
PhysicsFilter.RAIN = 1024,
PhysicsFilter.prototype.initialize = function() {
    this.entity.rigidbody.group = (this.groupDefault ? PhysicsFilter.DEFAULT : 0) | (this.groupRagDoll ? PhysicsFilter.RAGDOLL : 0) | (this.groupEnvironment ? PhysicsFilter.ENVIRONMENT : 0) | (this.groupSummonObject ? PhysicsFilter.SUMMON_OBJECT : 0) | (this.groupSupport ? PhysicsFilter.SUPPORT : 0) | (this.groupTrain ? PhysicsFilter.TRAIN : 0) | (this.groupMisc ? PhysicsFilter.MISC : 0) | (this.groupRain ? PhysicsFilter.RAIN : 0),
    this.entity.rigidbody.mask = (this.maskDefault ? PhysicsFilter.DEFAULT : 0) | (this.maskRagDoll ? PhysicsFilter.RAGDOLL : 0) | (this.maskEnvironment ? PhysicsFilter.ENVIRONMENT : 0) | (this.maskSummonObject ? PhysicsFilter.SUMMON_OBJECT : 0) | (this.maskSupport ? PhysicsFilter.SUPPORT : 0) | (this.maskTrain ? PhysicsFilter.TRAIN : 0) | (this.maskMisc ? PhysicsFilter.MISC : 0) | (this.maskRain ? PhysicsFilter.RAIN : 0)
}
;

Function

    var n = new Ammo.btVector3
      , t = new Ammo.btVector3
      , e = function(n, t, e) {
        this.entity = n,
        this.point = t,
        this.normal = e
    };
    pc.RigidBodyComponentSystem.prototype.raycastFiltered = function(o, i, r, a) {
        n.setValue(o.x, o.y, o.z),
        t.setValue(i.x, i.y, i.z);
        var l = new Ammo.ClosestRayResultCallback(n,t);
        l.set_m_collisionFilterGroup(r),
        l.set_m_collisionFilterMask(a),
        this.app.systems.rigidbody.dynamicsWorld.rayTest(n, t, l);
        var s = null;
        if (l.hasHit()) {
            var u = l.get_m_collisionObject()
              , c = Ammo.castObject(u, Ammo.btRigidBody)
              , m = l.get_m_hitPointWorld()
              , d = l.get_m_hitNormalWorld();
            c && (s = new e(c.entity,new pc.Vec3(m.x(),m.y(),m.z()),new pc.Vec3(d.x(),d.y(),d.z())))
        }
        return Ammo.destroy(l),
        s
    }

Thanks yaustra, champion of these forums! I have provided my implementation of the idea of re-sending the ray. This relies on the mouse event to be sent the first time. It will call recursively and build up a list of entities it hits. No performance tests done.

pickEntities = function(event, results, lastResult, firstTo) {
    results = results ? results : [];
    
    var from = 0;
    var to = 0;
    
    // previously hit entity 
    if(lastResult){
        from = lastResult.entity.getPosition();
        to = firstTo;
    }
    // first cast from camera
    else {
        from = this.cameraEntity.camera.screenToWorld(event.x, event.y, this.cameraEntity.camera.nearClip);
        to = this.cameraEntity.camera.screenToWorld(event.x, event.y, this.cameraEntity.camera.farClip);
        firstTo = to;
    }
        
    lastResult = this.app.systems.rigidbody.raycastFirst(from, to);
    if (lastResult) {
        if(lastResult.entity){
  
            // this prevents rays from bouncing off the same entities
            // in a loop causing ammojs to crash
            for(var i = 0; i < results.length; i++){
                if(results[i] === lastResult.entity){
                    return results;
                }
            }
           results.push(lastResult.entity);
           this.pickEntities(event, results, lastResult, firstTo);
        }
    }
    
    return results;
};

Use

    this.app.mouse.on(pc.EVENT_MOUSEDOWN, onMouseDown, this);

    function onMouseDown(event){
       var pickedEntities = pickEntities(event);
    }
1 Like

hmm wasn’t raycasting used in the wolfenstien 3d game

I can’t imagine many games without raycasting.

1 Like

I have seen one game engine that uses ray casting, but you have to buy it just to use it and the license
http://impactjs.com/

here is an example of one game that use impact engine and raycasting
http://phoboslab.org/xibalba/

I think you might be confused as to what raycasting is? It would be used in any 3d game that requires the user to click on something, to shoot things, etc. It’s used by any AI to detect the player, it’s used for collision and physics. It’s clearly built in to playcanvas. Maybe you mean something else?

1 Like

Hmm I must have been thinking of raycasting that was used in hovertank 3d And Wolfenstein 3d

Raycasting was used in Wolfenstein 3D to render the world. This helps explains it: https://www.youtube.com/watch?v=zb6Eo1D6VW8