Summary
When an entity has invisible child meshes (mesh.visible = false) that are not Interactable, those meshes still intercept rays fired by InputSystem's BVH raycaster. If those invisible meshes protrude closer to the viewer than a separate nearby Interactable entity's mesh, the nearby entity never receives Hovered.
Root Cause
Three.js Raycaster.intersectObject() does not check mesh.visible — invisible meshes are still tested. IWSDK's computeBoundsTreeForEntity uses object3D.traverse() (also no visibility check) to build the BVH for all descendant meshes of an Interactable entity.
A concrete example: a panel entity (entity A, Interactable) has inactive game-screen button zones as children (mesh.visible = false, zoneD = 0.10, front face at localZ = 0.11). A separate menu button entity (entity B, Interactable) is also a child of the panel, with its zone mesh at localZ = 0.08 (front face, zoneD = 0.04). When the ray is cast:
- Entity A's BVH is traversed recursively — hits the invisible child zone mesh at
localZ = 0.11 (closer to the camera than entity B's mesh at localZ = 0.08)
- Entity A receives
Hovered via that invisible mesh hit; pointer event propagation stops
- Entity B never receives
Hovered, even though its interactive zone mesh is unobstructed
Steps to Reproduce
- Create a panel entity with
Interactable + DistanceGrabbable
- Attach several button zone meshes as children — some
Interactable (menu buttons), some not (game-screen buttons, mesh.visible = false)
- The inactive zones use a deeper
BoxGeometry (zoneD = 0.10) than the active menu zones (zoneD = 0.04), so their front face protrudes further toward the viewer
- Point a controller at one of the menu button zones that overlaps in Y with an inactive zone
- The menu button entity does not receive
Hovered; the panel entity does instead
Expected Behaviour
mesh.visible = false should be sufficient to exclude a mesh from raycasting consideration, OR the BVH traversal in computeBoundsTreeForEntity / updateDescendantArrays should skip invisible meshes.
Actual Behaviour
Invisible meshes are tested by the raycaster and can block Hovered from being delivered to the correct Interactable entity.
Workaround
Park inactive zone meshes behind the panel (localZ = -0.5) rather than in front of it. This ensures they are always at a greater ray distance than active zones, so they never win the closest-hit test. Update localZ to the correct active value when enabling zones, and back to -0.5 when disabling.
// On deactivate — park behind panel so raycaster can't intercept
(entity.getVectorView(Transform, 'position') as Float32Array)[2] = -0.5;
entity.object3D!.visible = false;
// On activate — move in front of panel face
(entity.getVectorView(Transform, 'position') as Float32Array)[2] = 0.06;
entity.object3D!.visible = true;
Environment
- IWSDK
@iwsdk/core (latest)
- Three.js BVH raycasting via
three-mesh-bvh
- Emulated via IWER (Meta Quest 3 emulation)
Summary
When an entity has invisible child meshes (
mesh.visible = false) that are notInteractable, those meshes still intercept rays fired by InputSystem's BVH raycaster. If those invisible meshes protrude closer to the viewer than a separate nearbyInteractableentity's mesh, the nearby entity never receivesHovered.Root Cause
Three.js
Raycaster.intersectObject()does not checkmesh.visible— invisible meshes are still tested. IWSDK'scomputeBoundsTreeForEntityusesobject3D.traverse()(also no visibility check) to build the BVH for all descendant meshes of anInteractableentity.A concrete example: a panel entity (entity A,
Interactable) has inactive game-screen button zones as children (mesh.visible = false,zoneD = 0.10, front face atlocalZ = 0.11). A separate menu button entity (entity B,Interactable) is also a child of the panel, with its zone mesh atlocalZ = 0.08(front face,zoneD = 0.04). When the ray is cast:localZ = 0.11(closer to the camera than entity B's mesh atlocalZ = 0.08)Hoveredvia that invisible mesh hit; pointer event propagation stopsHovered, even though its interactive zone mesh is unobstructedSteps to Reproduce
Interactable+DistanceGrabbableInteractable(menu buttons), some not (game-screen buttons,mesh.visible = false)BoxGeometry(zoneD = 0.10) than the active menu zones (zoneD = 0.04), so their front face protrudes further toward the viewerHovered; the panel entity does insteadExpected Behaviour
mesh.visible = falseshould be sufficient to exclude a mesh from raycasting consideration, OR the BVH traversal incomputeBoundsTreeForEntity/updateDescendantArraysshould skip invisible meshes.Actual Behaviour
Invisible meshes are tested by the raycaster and can block
Hoveredfrom being delivered to the correctInteractableentity.Workaround
Park inactive zone meshes behind the panel (
localZ = -0.5) rather than in front of it. This ensures they are always at a greater ray distance than active zones, so they never win the closest-hit test. UpdatelocalZto the correct active value when enabling zones, and back to-0.5when disabling.Environment
@iwsdk/core(latest)three-mesh-bvh