1

i am making a webgl application i want to know is there any way of making objects(3D models made using blender) inside canvas clickable. So that when i click on them a pop up comes containing data.

sharad gupta
  • 31
  • 1
  • 6

2 Answers2

1

I know (and have used) two major approaches.

The first one is to allocate a separate framebuffer and render interactive object to it with different colours. Then, upon a mouse event, you read a pixel corresponding to mouse position and find an object corresponding to the colour just read. For exapmle, it may look somewhat like this.

Textured and shaded scene:

Textured and shaded scene

Rendered for hit testing:

Rendered for hit testing

This approach is interesting due to it's simplicity. But it has some performance challenges and major ones among them are rendering the scene twice and reading pixel data back (its slow and synchronous). The first one was easy: just keep a dirty flag for the framebuffer and redraw it only upon a event and only if the flag is set (then of course reset it). The second one I've tackled by reading and caching from the framebuffer big chunks of pixels:

getPixel: function (x, y) {
    var screenSize = this._screen.getCssSize();
    x = x * HIT_TEST_BUFFER_SIZE[0] / screenSize[0] | 0;
    y = y * HIT_TEST_BUFFER_SIZE[1] / screenSize[1] | 0;
    var rx = x >> PIXEL_CACHE_BUCKET_IDX_SHIFT,
        ry = y >> PIXEL_CACHE_BUCKET_IDX_SHIFT,
        pixelCache = this._pixelCache,
        bucket = pixelCache[[rx, ry]];

    if (!bucket) {
        this._framebuffer.bind();
        bucket = pixelCache[[rx, ry]] = new Uint8Array(
            4 * PIXEL_CACHE_BUCKET_SIZE[0] * PIXEL_CACHE_BUCKET_SIZE[1]
        );
        var gl = this._gl;
        gl.readPixels(
            rx << PIXEL_CACHE_BUCKET_IDX_SHIFT,
            ry << PIXEL_CACHE_BUCKET_IDX_SHIFT,
            PIXEL_CACHE_BUCKET_SIZE[0],
            PIXEL_CACHE_BUCKET_SIZE[1],
            gl.RGBA,
            gl.UNSIGNED_BYTE,
            bucket
        );
        this._framebuffer.unbind();
    }

    var bucketOffset = 4 * (
        (y - ry * PIXEL_CACHE_BUCKET_SIZE[1]) * PIXEL_CACHE_BUCKET_SIZE[0] +
        x - rx * PIXEL_CACHE_BUCKET_SIZE[0]
    );

    return bucket.subarray(bucketOffset, bucketOffset + 3);
}

The second major approach would be casting a ray to the scene. You take mouse position, construct a ray with it and cast it from a camera position into a scene to find which object it intersects with. That object would be the one mouse cursor pointing to. There is actually a decent implementation of that approach in Three.js, you can use it or take it as a reference to implement your own algorithm. The main challenge with that approach would be algorithmic complexity of searching an object the ray intersects with. It can be tackled with spacial indices built upon you scene.

Kirill Dmitrenko
  • 3,474
  • 23
  • 31
  • If you go with method 2, I feel its is almost required to use collision mesh(es) for each model. For example, the tank can be a capsule for the barrel and an obb for the body. This vastly simplify the math and therefore computational speed. In this sense, I do not think the three Raycaster is a good reference as you generally want to avoid ray testing against each triangle in a model. If performance is not an issue, then method 1 is probably superior due to its simplicity as you mentioned. – WacławJasper Jun 15 '16 at 16:32
  • Yes, that'll make sense for performance. But it's a trade off in itself: you'll not get pixel-perfect hit test. – Kirill Dmitrenko Jun 15 '16 at 16:34
  • If pixel-perfect hit test is required, then method 1 should be used no? – WacławJasper Jun 15 '16 at 16:38
  • It may be better in some situations (e.g. I'm using it for some UI elements which are 3D, but detecting them with ray casting would be a bit cumbersome). But in regarding the end result they are interchangeable. – Kirill Dmitrenko Jun 15 '16 at 16:40
0

Canvas is a simple graphics API. It draws pixels very well and nothing more. There are ways to 'fake' event handlers via mouse positions, but that takes more work. Basically you will register the location of mouse click, than match that up with the position of your 3D models to see if you have a match. You will not be able to attach event handlers directly to the 3d blender objects in canvas. In Scalable Vector Graphics (SVG) that would work fine. Just not in canvas.

function handleMouseDown(e) {
    // tell the browser we'll handle this event
    e.preventDefault();
    e.stopPropagation();

    // save the mouse position
    // in case this becomes a drag operation
    lastX = parseInt(e.clientX - offsetX);
    lastY = parseInt(e.clientY - offsetY);

    // hit all existing FrameControlPt of your blender objects
    var hit = -1;
    for (var i = 0; i < FrameControlPt.length; i++) {
        var location = FrameControlPt[i];
        var dx = lastX - location.x;
        var dy = lastY - location.y;
        if (dx * dx + dy * dy < stdRadius * stdRadius) {
            hit = i;
        }
    }

    // hit all existing buttons in the canvas
    for (var i = 0; i < btn.length; i++) {
        if ((lastX < (btn[i].x + btn[i].width)) &&
            (lastX > btn[i].x) &&
            (lastY < (btn[i].y + btn[i].height)) &&
            (lastY > btn[i].y)) {
            console.log("Button #" + (i + 1) + " has been clicked!!");
            // execute button function
            btn[i].action(); // execute a custom function
        }
    }

    // if hit then set the isDown flag to start a drag
    if (hit < 0) {
        drawAll();
    } else {
        draggingCircle = FrameControlPt[hit];
        isDown = true;
    }
}

Obviously you'd have to handleMouseUp(event).. in this example, I was allowing the users to drag and drop elements within the canvas. You'd have to adjust your events to match your intended usage.

Code extract from this sample.

zipzit
  • 3,778
  • 4
  • 35
  • 63
  • in your snippet you forgot to tell how to get `offsetX`, and in your link, you forgot to attach a resize and scroll event to update it (even if it seems unnecessary in your page since you do show the canvas at 100% of the iframe size, since this is the question here, it may be good to add it) – Kaiido Jun 15 '16 at 07:01