Three.js - 如何检查物体是否在球体后面(不可见)

3
我有一个球体(地球仪),球面上有物体(图钉),这些物体的DOM元素(标签)是根据图钉位置计算出来的,以2D世界为基础。
我的问题是当图钉被拖到球体后面时(通过鼠标拖动或动画),我需要隐藏与之关联的DOM元素(标签),使文本标签不可见。
我的逻辑是,如果我可以得到在3D世界中的图钉告诉我它是否在球体后面,那么我就可以隐藏与之关联的标签。
完整代码请参考Codepen
下面是我研究出来的函数:
function checkPinVisibility() {

    var startPoint = camera.position.clone();

    for (var i = 0; i < pins.length; i++) {

        var direction = pins[i].position.clone();
        var directionVector = direction.sub(startPoint);

        raycaster.set(startPoint, directionVector.clone().normalize());

        var intersects = raycaster.intersectObject(pins[i]);

        if (intersects.length > 0) {
            // ?
        }

    }
}

我研究了许多帖子,但并没有真正得到所需的结果: 我已经通过将鼠标XY位置作为射线使其工作,但无法获得所有引脚的常数渲染的工作解决方案。

你的引脚元素也必须是3D元素。不能将3D和2D DOM元素混合使用。使用THREE.Sprite来创建标签。 - gaitat
1
这就是我的问题,我必须使用DOM元素来作为文本标签而不是精灵。我的逻辑是,如果我可以让处于3D世界中的图钉告诉我它是否在地球仪后面,那么我就可以隐藏与该图钉相关联的标签。 - Raik
2个回答

8
你想知道摄像机可以看到球表面的哪些点。
想象一条从摄像机出发的线与球相切。设 L 为从摄像机到切点的距离。
摄像机只能看到在球上距离摄像机比 L 更近的点。 L 的公式为 L = sqrt( D^2 - R^2 ),其中 D 是摄像机到球心的距离,R 是球半径。

这对我来说有点太理论化了。不知道如何在我的用例中实际应用公式。 - Raik
2
如果针脚距离相机的3D距离大于L,则隐藏标签。 - WestLangley
很好的概念解释,特别是配合公式。这正是我需要解决问题的方案。已经使用代码测试并且有效! - Russell Strauss

1

WestLangley 的代码解决方案。如果您认为他的答案是最好的,请给他采纳。

function checkPinVisibility() {
    var cameraToEarth = earth.position.clone().sub(camera.position);
    var L = Math.sqrt(Math.pow(cameraToEarth.length(), 2) - Math.pow(earthGeometry.parameters.radius, 2));

    for (var i = 0; i < pins.length; i++) { 

        var cameraToPin = pins[i].position.clone().sub(camera.position);

        if(cameraToPin.length() > L) { 
            pins[i].domlabel.style.visibility = "hidden";
        } else { 
            pins[i].domlabel.style.visibility = "visible";
        }
    }
}

奇怪的是,它仍然容易出现相机移动错误。非常奇怪,但它仍然比我的投影到LOOKAT解决方案更好。

我的旧回答:

我本以为是这样的,但它似乎不能按预期工作。

  if (intersects.length > 0) {
       pins[i].domlabel.style.visibility = "visible";
   } else {
       pins[i].domlabel.style.visibility = "hidden";
   }

我已经接近解决方案,但仍不完美。以下代码的作用是找到沿着相机LOOKAT方向到一个标记(cameraToPinProjection)的距离,并将其与沿着LOOKAT方向到地球的距离(cameraToEarthProjection)进行比较。 如果cameraToPinProjection > cameraToEarthProjection,则表示该标记在LOOKAT方向上在地球中心后方(然后我隐藏该标记)。
您会发现我将cameraToEarth投影乘以“0.8”因子。这是为了使它稍微缩短。可以尝试进行实验。
它并不完美,因为当您旋转地球时,有时标签不会按您希望的方式工作,我不知道如何解决。
希望这可以帮助到您。
function checkPinVisibility() {
    var LOOKAT = new THREE.Vector3( 0, 0, -1 );
    LOOKAT.applyQuaternion( camera.quaternion );

    var cameraToEarth = earth.position.clone().sub(camera.position);
    var angleToEarth = LOOKAT.angleTo(cameraToEarth);

    var cameraToEarthProjection = LOOKAT.clone().normalize().multiplyScalar(0.8 * cameraToEarth.length() * Math.cos(angleToEarth));

    var startPoint = camera.position.clone();

    for (var i = 0; i < pins.length; i++) {

        var cameraToPin = pins[i].position.clone().sub(camera.position);
        var angleToPin = LOOKAT.angleTo(cameraToPin);

        var cameraToPinProjection = LOOKAT.clone().normalize().multiplyScalar(cameraToPin.length() * Math.cos(angleToPin));

        if(cameraToPinProjection.length() > cameraToEarthProjection.length()) {
            pins[i].domlabel.style.visibility = "hidden";
        } else { 
            pins[i].domlabel.style.visibility = "visible";
        }

    }
}

不错的想法。我认为你旋转得越多,计算就会出问题。也许射线跟踪解决方案更加可靠。我已经用鼠标做到了这一点,但无法在不停渲染时使其正常工作。 - Raik
确实,我不明白出了什么问题。可能是我对相机的移动方式有所遗漏。很奇怪射线投射也不能正常工作。 - Yousif Al-Y

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接