5

I have a point in space express a vector in a three js viewer. Attached to this point there is an "HTML annotation"

visible annotation

that I would like to hide when the point is not visible (behind other surfaces of the same mesh or hidden by other meshes). For example in the image below it should be hidden:

should be invisible

I am using some code to check to see if the annotation is in the frustum as suggested in another question but this does not quite work as the annotation disappear only when I rotate the object quite dramatically. See picture below:

now is not visible

Can you help me to solve my problem?

Here my code so far:

const vector = new THREE.Vector3(x, y, z);

this.aCamera.updateMatrix();
this.aCamera.updateMatrixWorld(true);

let frustum = new THREE.Frustum();
frustum.setFromMatrix(new THREE.Matrix4().multiplyMatrices(this.aCamera.projectionMatrix, this.aCamera.matrixWorldInverse));

  // check if annotation is in view
  if (frustum.containsPoint(vector)) {
       anAnnotation.css({opacity: 0});
  } else {
        anAnnotation.css({opacity: 1});
  }
Dino
  • 1,307
  • 2
  • 16
  • 47

1 Answers1

4

I can think of two ways to do that.

First, you could use a raycaster (code is from memory, not entirely sure this will 100% work like this):

  • setup the raycaster with a ray pointing from the camera to your marker:

    // somewhere outside
    const raycaster = new THREE.Raycaster();
    const v = new THREE.Vector3();
    
    // in the animation-loop
    v.copy(marker.position).sub(camera.position).normalize();
    raycaster.set(camera.position, v);
    
  • get objects intersecting that ray

    // you might want to be a bit more specific
    const intersections = raycaster.intersectObjects(scene, true);
    
  • if the first intersection isn't the marker, it is at least partly occluded

    if (intersections.length > 0 && intersections[0].object !== marker) {
      // hide marker...
    }
    

This will probably work fine for a smaller number of objects / objects with limited amount of faces. For very complex objects, the raycaster is painfully slow, and you might want to resort to using a pre-rendered depth-map.

  • before rendering the scene, render just the occluders into a depth-map (you can use object.layers and camera.layers (Layer docs) to control what gets rendered)

    // outside animation-loop
    const depthMaterial =  new THREE.MeshDepthMaterial({
      depthPacking: THREE.RGBADepthPacking
    });
    
    const depthTarget = new THREE.WebGLRenderTarget(
      rendererWidth, 
      rendererHeight
    );
    
    
    // before rendering scene
    camera.layers.disable(MARKERS_LAYER);
    scene.overrideMaterial = depthMaterial;
    renderer.render(scene, camera, depthTarget);
    camera.layers.enable(MARKERS_LAYER);
    
  • now you can project coordinates of the marker and compare the depth from the depthMap at that position with the z-distance of the marker. Please see this codepen for how to read world-space coordinates from the depth-map.

Martin Schuhfuß
  • 6,814
  • 1
  • 36
  • 44
  • Hi Martin, thank you for your answer. Just a question. What kind of three object is the marker? – Dino Jan 18 '18 at 12:16
  • I was referring to that circle you have there. If you did that completely in html, forget about that. In that case you need to use the 3D-position the html is attached to (i think it's `vector` in your snippet?) instead and do depth-testing manually (just compare the depth from raycaster/depth-map with `length(camera.position - vector)`) – Martin Schuhfuß Jan 18 '18 at 12:33
  • Just a comment if somebody else comes on this answer; be sure to convert the marker.position to world Space if it is child of something else – Stormsson Apr 08 '22 at 21:38