Shape Picker options

Hi, I’m using the shapePicker method to detect clicks to my hotspots. It’s described here (Entity picking without physics | Learn PlayCanvas) and is using the intersectsRay method on BoundingSpheres. It’s been working ok, and I don’t have to include the physics system. However, I do have some problems where hotspots not visible behind other geometry are sometimes accidentally picked. I’ve understood there are two more methods described for this kind of system:

  1. The physics-based raycast, where all my hotspots would have collision components and react to ray casts.
  2. The framebuffer method using the pixels instead. I’ve read it might be bad for performance on mobile GPU, which makes it a no-go for me.

(both described here: Entity Picking | Learn PlayCanvas)

If I’m to remedy this issue, where ideally I would like hotspots hidden behind other geometry not reacting to my shapepicking at all. Do I have to use the physics-based method? and if so, do I have to add a mesh collider to all my 3D geometry? is it going to affect my app performance?

Example of a hotspot I want to be hidden from shapepicking:

Hi @bjorn.syse,

Apart from the other two picking solutions, you could also do the following to remedy this situation:

  • Add shape collision boxes to non pickable geometry as well and tag it accordingly. The shape picker will pick that first and you can discard the pick if it’s not supposed to provide any user interaction, successfully occluding shapes behind it.

Hi

aha! Perhaps that’s a good start actually. I tried implementing the physics raycast solution in a separate branch and it was simple enough. however, I’m worried I’m introducing some complexity that might reduce performance and add size (ammo.js). Also my collision meshes are quite big:

I tried your suggestion instead, added an empty bounding Box behind a group of hotspots to prevent me picking hotspots even further behind. However, It seems the shapepicker is set to loop through all pickableentities and see if the ray hits any of them. Even if it does hit a non-pickable entity, I’m not sure how I would determine if that non-pickable is in front of a pickable and thus ignore the rest of the hits if you know what I mean.

So that’s easy to fix, before looping through your active shapes list sort them based on their distance from the camera. Closest to furthest.

That way when the first hit happens it will always be the closest object to the camera, and that would be the occluder object in this case.

// example code
PickerManager.prototype.sortListByDistance = function (cameraPos) {
    for (var i = 0; i < this.pickableShapes.length; i++) {
        var entry = this.pickableShapes[i];
        entry.vec.copy(entry.shape.center);
        entry.distance = entry.vec.sub(cameraPos).length();
    }
    this.pickableShapes.sort(function (a, b) {
        return a.distance > b.distance ? 1 : -1;
    });
};
1 Like

ah, obviously. It’s just that its matched with another array carrying the Entities, and the pickableShapes only contains the BoundingSphere. They are matched by assuming and taking care they carry the same index.

Like this:

ShapePicker.prototype.addItem = function (entity, shape) {
    
    if (entity && this.pickableEntities.indexOf(entity) < 0) {
        this.pickableEntities.push(entity);
        this.pickableShapes.push(shape);
    }
};
        
ShapePicker.prototype.removeItem = function (entity) {
    var i = this.pickableEntities.indexOf(entity);
    if (i >= 0) {
        this.pickableEntities.splice(i, 1);
        this.pickableShapes.splice(i, 1);
    }
};

Perhaps there is actually a better data structure I should use for this instead. Or maybe I can just sort that that pickableEntities-array in the same manner, adding some overhead I suppose.

also, that vec property, where is that defined?

In that codebase I’ve used the following method to pre-create all required objects/properties and avoid any runtime cost. Here is my method below, of course no need to create a pc.Vec3 per object (that was needed for that project), you can reuse a global vector and calculate the distance each time:

PickerManager.prototype.addItem = function (entity, shape) {
    if (entity) {
        this.pickableShapes.push({
            entity: entity,
            shape: shape,
            vec: new pc.Vec3(),
            distance: 0,
        });
    }
};

ah that makes sense. I would have to refactor my shapePicker entry system in order to get that to work. See If I have the mindpower tomorrow for that. Thanks for the input!

1 Like