Search code examples
javascripthtmlthree.jsaframeraycasting

How do I make my raycaster only work when intersecting with visible objects/entities?


I want to make my raycaster intersect only with objects that are visible. I have three classes, .menu_one, .menu_two, and .menu_three. When I click on one of them, I want all other menus to disappear so the viewer can see the 3d background. However, when I am walking around I can still click where the menu used to be with the raycaster when I don't want to. What do I pass thru raycaster="objects: ???" or any other method to make it work. ...

<a-cursor
      id="cursor"
      material="color: black; shader: flat"
      position="0 0 -4.5"       
      geometry="primitive: ring; radiusInner: 0.15; radiusOuter: 0.2"
      animation__click="property: scale; startEvents: click; from: 0.1 0.1 0.1; to: 1 1 1; dur: 150"
      animation__fusing="property: fusing; startEvents: fusing; from: 1 1 1; to: 0.1 0.1 0.1; dur: 1500"
      event-set__mouseenter="_event: mouseenter; color: springgreen"
      event-set__mouseleave="_event: mouseleave; color: black"
      raycaster="objects: [data-visible]">
    </a-cursor>  


<a-entity class="menu1"
            geometry="primitive: plane; height: 0.2; width: 0.5"
            material="color: black"
            text="value: ${city_name}; color: white; align: center; wrapCount: 10; font: exo2bold"
            event-set__mouseenter="scale: 1.5 1.5 1"
            event-set__mouseleave="scale: 1 1 1"
            event-set__1="_event: click; _target: #models; _delay: 300; visible: false">

...

I expect the raycaster only to be able to intersect with objects that are visible to the user.

Hi, would this be the way to make this work?

AFRAME.registerComponent('data-raycastable', {
      init: function () {
        var self = this;
        var el = this.el;
        var data = this.data;

        this.eventHandlerFn = function () {
          if (el.object3D.visible = true){
          el.setAttribute('data-raycastable', '');
          }
          else {
            el.removeAttribute('data-raycastable')
          }
        }

        update: function() {

          el.addEventListener('click', this.eventHandlerFn);
          } 
          }
        }
      })

Solution

  • You should explicitly control the raycaster objects list. For example:

    raycaster="objects: [data-raycastable]"

    Now we have an entity we want to make raycastable:

    <a-entity geometry material data-raycastable>

    Let's say we hide the entity. We also remove its raycastable attribute:

    el.object3D.visible = false;
    el.removeAttribute('data-raycastable');
    

    And let's say we add it back:

    el.object3D.visible = true;
    el.setAttribute('data-raycastable', '');
    

    In more complicated scenes, it can be more tedious on many entities. Things like aframe-state-component (https://www.npmjs.com/package/aframe-state-component) can assist by declaratively toggling off raycastability depending on scene state. Say we had <a-entity raycaster="objects: [raycastable]"> with AFRAME.registerComponent('raycastable', {}), and in the state was isMenuActive which is managed by the state component:

    <a-entity id="button" bind-toggle__raycastable="isMenuActive">
    

    And I've written my own component to keep raycastable / visibility in sync.

    <a-entity id="button" bind__visible-raycastable="isMenuActive">