Alex Reynolds
Alex Reynolds

Reputation: 96937

Calculating screen coordinates of horizontal edge of bounding box in three.js

I have a three.js page that renders a set of particles (runnable demo and code at: https://dl.dropboxusercontent.com/u/31495717/cubemaker/index.html).

Here is a snapshot of the default view, where we are basically looking into a unit cube centered at (0, 0, 0):

Default view

I have transparent bounding boxes centered at each particle inside this unit cube. If I modify the opacity, we can then see the boxes surrounding each particle:

var bounding_box = new THREE.Mesh(
    bounding_box_geometry, 
    new THREE.MeshLambertMaterial({ 
        opacity: 0.5, 
        transparent: true,
        alphaTest: 0.5
    })
);

Default view with bounding boxes

I use the THREE.Raycaster() to trace a vector from the camera to the world coordinate of the current mouse position:

var mouse_vector = new THREE.Vector3(mouse.x, mouse.y, 1).unproject(camera);
raycaster.set(camera.position, mouse_vector.sub(camera.position).normalize());
var bounding_box_intersections = raycaster.intersectObjects(bounding_boxes);

If there are any bounding_box_intersections, I retrieve the screen coordinates of the intersected bounding box and store it in screen_object_center:

var width = window.innerWidth, height = window.innerHeight;
var widthHalf = width / 2, heightHalf = height / 2;
var screen_object_center = new THREE.Vector3();
var projector = new THREE.Projector();
projector.projectVector(screen_object_center.setFromMatrixPosition(INTERSECTED.matrixWorld), camera);
screen_object_center.x = (screen_object_center.x * widthHalf) + widthHalf;
screen_object_center.y = -(screen_object_center.y * heightHalf) + heightHalf;

Once I have screen_object_center — the screen coordinates of the center of the intersected object or bounding box — I can place an offset-ed text label wherever the mouse hovers.

As an example, here is a snapshot of how the text label shows up with mouse pointer hovered over the centermost particle's bounding box:

Default view with label

I use the id_label bounding box to adjust the vertical offset of the label:

id_label.style.top = (screen_object_center.y - 0.85 * (id_label_rect.height / 2)) + 'px';

This works okay.

My question is how to set the horizontal offset of the label, relative to the position and depth of the particle.

The complication is that deeper background particles are smaller than particles in the foreground, and so I need a smaller horizontal offset to keep the label at a constant distance away from its edge.

My failed attempt involves calculating a THREE.Vector3 called screen_object_edge that tries to get the edge of the particle's bounding box (the INTERSECTED object), instead of its center point.

By retrieving the screen coordinates that correspond to the particle's edge, I am hoping to set a constant horizontal gap between the particle's edge and the label, regardless of the depth of the particle.

I try this by copying the matrix of the INTERSECTED object, setting a new position that lies at the corner of the bounding box, and then projecting a vector called screen_object_edge that lies from that copied matrix position to the camera:

var INTERSECTED_matrix_copy = new THREE.Matrix4().copy(INTERSECTED.matrixWorld);
INTERSECTED_matrix_copy.setPosition(new THREE.Vector3(INTERSECTED.position.x + 0.0375, INTERSECTED.position.y + 0.0375, INTERSECTED.position.z + 0.0375));
var screen_object_edge = new THREE.Vector3();
projector.projectVector(screen_object_edge.setFromMatrixPosition(INTERSECTED_matrix_copy), camera);
screen_object_edge.x = (screen_object_edge.x * widthHalf) + widthHalf;
screen_object_edge.y = -(screen_object_edge.y * heightHalf) + heightHalf;

Things break once I rotate the scene: the labels are set correctly in the vertical position, but they are set incorrectly in the horizontal position, in that some now overlap their particles' bounding box.

Here is an example of the same label highlighted, as in the previous example, after rotation of the particle cloud:

Default view, rotated and showing label

If things worked correctly, the label would be positioned at the same horizontal distance away from the right particle edge in the rotated scene, as in the original, non-rotated default scene. Here, the label overlaps the particle.

My question is how to calculate screen_object_edge so that I can get the correct screen coordinates of the edge of the particle's bounding box, and then correctly offset the horizontal position of the text label, relative to the left or right edge of its respective particle.

EDIT

Thanks to vals for the correct answer. Here's a snippet of my modified render() function:

function render() {
    // ...
    var scene_right = new THREE.Vector3().crossVectors(raycaster.ray.origin, camera.up).setLength(0.0375);
    var bounding_box_intersections = raycaster.intersectObjects(bounding_boxes);
    if (bounding_box_intersections.length > 0) { 
        // ...
        var INTERSECTED_matrix_copy = new THREE.Matrix4().copy(INTERSECTED.matrixWorld);
        INTERSECTED_matrix_copy.setPosition(new THREE.Vector3(INTERSECTED.position.x + scene_right.x, INTERSECTED.position.y + scene_right.y, INTERSECTED.position.z + scene_right.z));
        var screen_object_edge = new THREE.Vector3();
        projector.projectVector(screen_object_edge.setFromMatrixPosition(INTERSECTED_matrix_copy), camera);
        screen_object_edge.x = (screen_object_edge.x * widthHalf) + widthHalf;
        screen_object_edge.y = -(screen_object_edge.y * heightHalf) + heightHalf;
        // ...
        if (mouse.x < 0)
            id_label.style.left = (screen_object_center.x - (screen_object_edge.x - screen_object_center.x)) + 'px';
        else {
            id_label.style.left = (screen_object_center.x + (screen_object_edge.x - screen_object_center.x) - id_label_rect.width) + 'px';
            id_label.style.textAlign = 'right';
        }
    }
}

When I rotate the scene, the label position is now a constant distance from the bounding box edge, regardless of the z-depth of the bounding box:

Revised edge vector, default position

Revised edge vector, rotated position

Upvotes: 3

Views: 1766

Answers (1)

vals
vals

Reputation: 64164

I think that the solution should be

  1. You have the center of the bounding box
  2. You calculate the cross product of the raycaster vector and the up vector in the camera. This vector should point to the right of the scene.
  3. you set this vector length to half the size of the bounding box
  4. you add this vector to the center of the bounding box.

Upvotes: 1

Related Questions