3

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.

Community
  • 1
  • 1
Raik
  • 376
  • 3
  • 6
  • your pin elements must also be 3d elements. you can not mix 3d with 2d DOM elements like that. Use a THREE.Sprite for your labels. – gaitat Mar 28 '15 at 16:02
  • 1
    There lies my problem that I have to use DOM elements for the text labels not sprites. 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. – Raik Mar 28 '15 at 17:11

2 Answers2

8

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
  • 102,557
  • 10
  • 276
  • 276
  • It’s a bit too theoretical answer for me. Don’t know how to practically apply the formula for my use case. – Raik Mar 28 '15 at 21:54
  • 2
    Hide the label if the pin's 3D distance from the camera is greater than `L`. – WestLangley Mar 28 '15 at 23:43
  • Great conceptual explanation, especially with the formula. This was exactly the solution I needed for my problem. Tested with code and works! – Russell Strauss Mar 30 '17 at 20:03
1

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

    }
}
Yousif Al-Y
  • 196
  • 1
  • 8
  • Nice idea. The more you rotate the more calculations will go bad i think. Maybe the raycasting solution is more solid. I have gotten it work by [mouse](http://codepen.io/raik/pen/yywXmJ), but cant get it to work with constant rendering. – Raik Mar 28 '15 at 17:06
  • True, I don't understand what's going wrong. There must be something I'm missing about the way the camera is panned probably. It's weird that the raycasting doesn't work either well. – Yousif Al-Y Mar 28 '15 at 23:25