three.js 计算边界框水平边缘的屏幕坐标

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

我有一个呈现一组粒子的 three.js 页面(可运行的演示和代码位于:https://dl.dropboxusercontent.com/u/31495717/cubemaker/index.html)。

这是默认视图的快照,我们基本上是在查看以 (0, 0, 0) 为中心的单位立方体:

我有透明的边界框,以这个单位立方体中的每个粒子为中心。如果我修改不透明度,我们就可以看到每个粒子周围的框:

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

我使用THREE.Raycaster()从相机追踪一个矢量到当前鼠标位置的世界坐标:

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);

如果有任何bounding_box_intersections,我检索相交边界框的屏幕坐标并将其存储在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;

一旦我有了 screen_object_center——相交对象或边界框中心的屏幕坐标——我就可以在鼠标悬停的任何地方放置一个 offset-ed 文本标签。

例如,下面是鼠标指针悬停在最中心的粒子边界框上时文本标签显示方式的快照:

我使用id_label边界框来调整标签的垂直偏移:

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

这没问题。

我的问题是如何设置标签相对于粒子位置和深度的水平偏移。

复杂的是较深的背景粒子比前景中的粒子小,因此我需要较小的水平偏移来使标签与其边缘保持恒定距离。

我失败的尝试涉及计算一个名为 screen_object_edgeTHREE.Vector3,它试图获取粒子边界框(INTERSECTED 对象)的边缘,而不是其中心点。

通过检索对应于粒子边缘的屏幕坐标,我希望在粒子边缘和标签之间设置一个恒定的水平间隙,而不管粒子的深度如何。

我通过复制 INTERSECTED 对象的矩阵,设置位于边界框角的新位置,然后投影一个名为 screen_object_edge 的向量来尝试此操作将矩阵位置复制到相机:

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;

一旦我旋转场景,事情就崩溃了:标签在垂直位置设置正确,但在水平位置设置不正确,因为有些标签现在与它们的粒子边界框重叠。

这里是粒子云旋转后突出显示的相同标签的示例,与前面的示例相同:

如果一切正常,标签将位于与旋转场景中的右粒子边缘相同的水平距离,与原始 non-rotated 默认场景中的一样。在这里,标签与粒子重叠。

我的问题是如何计算screen_object_edge这样我就可以得到粒子边界框边缘的正确屏幕坐标,然后正确偏移文本标签的水平位置,相对于左边或其各自粒子的右边缘。

编辑

感谢 vals 的正确答案。这是我修改后的 render() 函数的片段:

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';
        }
    }
}

当我旋转场景时,标签位置现在与边界框边缘保持恒定距离,而不管边界框的z-depth:

我认为解决方案应该是

  1. 你有边界框的中心
  2. 你计算光线投射向量和相机中向上向量的叉积。此矢量应指向场景的右侧。
  3. 您将此矢量长度设置为边界框大小的一半
  4. 您将此向量添加到边界框的中心。