Reputation: 96937
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):
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
})
);
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:
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:
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:
Upvotes: 3
Views: 1766
Reputation: 64164
I think that the solution should be
Upvotes: 1