简体   繁体   中英

Three.js - How to check if object is behind a sphere (not visible)

I have a sphere (globe) with objects (pins) on the surface with DOM elements (labels) what are calculated from the pin position to 2d world.

My problem is that when the pins go behind the globe (with mouse dragging or animation) then I need to hide labels which are in DOM so that the text label isn't visible without the pin.

My logic is that if I can get the pin which is in 3D world to tell me if it's behind the globe then I can hide the label associated with the pin.

Codepen with whole the code.

The function that I have researched together:

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) {
            // ?
        }

    }
}

I have researched through many posts but can't really get the result needed:

I have gotten it work by mouse XY position as a ray , but can't really get a working solution with constant rendering for all the pins.

You want to know which points on the surface of a sphere are visible to the camera.

Imagine a line from the camera that is tangent to the sphere. Let L be the length of the line from the camera to the tangent point.

The camera can only see points on the sphere that are closer to the camera than L .

The formula for L is L = sqrt( D^2 - R^2 ) , where D is the distance from the camera to the sphere center, and R is the sphere radius.

WestLangley 's solution in code form. Please give him the accepted answer if you feel his answer the best.

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

Oddly enough it is still susceptible to that camera pan error. Very weird, but it's still better than my Projection-onto-LOOKAT solution.

MY OLD ANSWER:

I would have assumed its something like this, but this doesn't seem to work as expected.

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

I got close with this solution, but its still not perfect. What the code below does is it finds the distance along the LOOKAT direction of the camera to a pin (cameraToPinProjection) and compares it with the distance along the LOOKAT direction to the earth (cameraToEarthProjection). If cameraToPinProjection > cameraToEarthProjection it means the pin is behind the centre of the earth along the LOOKAT direction (and then I hide the pin).

You will realise there's a "0.8" factor I multiply the cameraToEarth projection by. This is to make it slightly shorter. Experiment with it.

Its not perfect because as you rotate the Earth around you will notice that sometimes labels don't act the way you'd like them, I'm not sure how to fix.

I hope this helps.

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

    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM