Need clarification about pc.Picker().getSelection()

Hi folks,

I wanna make a function, which can allow me to use a rectangle to pick multiple items in a scene at once.
For achieving this, I find Frame Buffer Picking in the tutrial on the official wesite is a good start. It’s like this


var PickerFramebuffer = pc.createScript('pickerFramebuffer');

// initialize code called once per entity
PickerFramebuffer.prototype.initialize = function() {
    // Create a frame buffer picker with a resolution of 1024x1024
    this.picker = new pc.Picker(this.app.graphicsDevice, 1024, 1024);
    this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onSelect, this);
};

PickerFramebuffer.prototype.onSelect = function (event) {
    
    // QQQ: what is canvas? Is it the screen?
    var canvas = this.app.graphicsDevice.canvas;
    var canvasWidth = parseInt(canvas.clientWidth, 10);
    var canvasHeight = parseInt(canvas.clientHeight, 10);

    var camera = this.entity.camera.camera;
    var scene = this.app.scene;
    var picker = this.picker;

    picker.prepare(camera, scene);
 
    // QQQ: why do we have to map the mouse coordinates into picker's? 
    // As far as I can think about the getSelection function is we give it the center, height and width of a rectangle area on screen, and got the meshInstances of entities, 
    // which model's screen projection can be inside of the rectangle area.
    // If I'm right, why not just pass the center, height and width of the rectangle to getSelection function?
    // What this mapping does in this case?
   
    // Map the mouse coordinates into picker coordinates and
    // query the selection
    var selected = picker.getSelection({
        x: Math.floor(event.x * (picker.width / canvasWidth)),
        y: picker.height - Math.floor(event.y * (picker.height / canvasHeight)),
        width: 1024,
        height: 1024

    });

    if (selected.length > 0) {
        // Get the graph node used by the selected mesh instance
        

        console.log(selected);
        // Bubble up the hierarchy until we find an actual Entity
        for(var i = 0; i < selected.length; i += 1){
            var entity = selected[i].node;
            while (!(entity instanceof pc.Entity) && entity !== null) {
                entity = entity.getParent();
            }
            if (entity) {
                //entity.script.pulse.pulse();
                console.log(entity.name + "!!!");
            }
        }
    }
};


When I try it with my project, the picking rectangle keeps small even I make the width and height large, so I can’t pick a lot things spreaded on the ground in the scene. I guess I missunderstand something about that script. I got two questions basically with ‘QQQ’ mark in the comment of the code above.
1.what is canvas? Is it the screen?
2.why do we have to map the mouse coordinates into picker’s?
As far as I can think about the getSelection function is we give it the center, height and width of a rectangle area on screen, and got the meshInstances of entities,
which model’s screen projection can be inside of the rectangle area.
If I’m right, why not just pass the center(event.x, event.y), height and width of the rectangle to getSelection function?
What this mapping does in this case?

Here is the link to my project https://playcanvas.com/project/482357/overview/ffff

canvas - is a <canvas> element that webgl context is bound to. It is in DOM and has width and height attributes.

Canvas resolution of a back buffer in most of times is different from screen resolution and even can be not aligned with screen texels, for example when DPI is different and other cases.
Because of this you have to provide position of a pixel to a picker within canvas resolution space.

Bear in mind, picker has been updated and now does accepts arguments in different way: https://developer.playcanvas.com/en/api/pc.Picker.html#getSelection
Clearly read on coordinate system, as old arguments system worked with bottom-left origin, and new arguments with top-left.

I’ve just tested if it does provides multiple meshInstances if width and/or height are bigger than 1, and it does.

Thanks max, but I’m still confused by something.The document about pc.Picker #getSelection tells

Return the list of mesh instances selected by the specified rectangle in the previously prepared pick buffer.The rectangle using top-left coordinate system.

But comment in the code tells

// Get the selection at the point (10,20)
var selection = picker.getSelection(10, 20);

// Get all models in rectangle with **corners** at (10,20) and (20,40)
var selection = picker.getSelection(10, 20, 10, 20);

Top left VS corner, which one is true?

Finally make it, but I don’t use pc.Picker(), cause I just find that it returns all the models inside of the rectangle, which is not necessary. Instead, I just make an array containing the selectable entities, and check if their toscreen positions are inside of the selection rectangle/box.

This is much slower approach.

pc.Picker does a rendering pass in prepare(), isn’t that more problematic than hoop22’s current solution? Or have I missed something here and prepare() actually isn’t that expensive?

prepare is not cheap indeed, but it scales pretty well with scene complexity, but is pretty expensive if rectangle of selection is large, as it has to read each pixel.

Alternative path of converting coordinates of objects to screen space taking in account their aabb’s, is not cheap either. But could scale better.

Best approach would be to have decent space clustering algorithm and make analytical selection-frustum to aabb intersection check. So to avoid too many objects iterations and utilize already available data (aabb).

1 Like