Cursor sinks into the surface after the player turns

Open Beginner friendly
#53 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
90/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
three.js, typescript

Research direction

Start in packages/xr-input/src/pointer/cursor-visual.ts, focusing on updateFromIntersection and its fallback branch. Reproduce the cursor behavior in a Meta Quest 3 WebXR session with snap turning, then verify that the cursor remains visible on object surfaces after rotation and that both intersection paths behave consistently.

Written by the indexing model from the issue text.

Description

Cursor sinks into the surface after the player turns

Environment

  • @iwsdk/xr-input 0.5.3 (also reachable through @iwsdk/core 0.5.3)
  • Meta Quest 3, browser-based WebXR session
  • Locomotion enabled with turning (snap turn on the thumbstick)

Symptom

The ray cursor (the white dot drawn at the intersection point) disappears once the
player has turned.

  • Enter XR and point at any RayInteractable / DistanceGrabbable object: the
    cursor is drawn correctly on the surface.
  • Turn with the thumbstick, then point at the same object from the same distance:
    the cursor is gone. It reappears only when the ray grazes an edge of the object,
    which suggests it is being drawn behind the surface and depth-tested away.

Not reproducible before the first turn. In a desktop (non-immersive) session where
nothing rotates the player, it never happens.

Cause

packages/xr-input/src/pointer/cursor-visual.ts, in updateFromIntersection
(dist lines 78-86, and the same shape again in the fallback branch at 87-95):

// Build world-space orientation from +Z to world normal
this.cursor.quaternion.setFromUnitVectors(ZAxis, scratchNormal);
// Convert world orientation to xrOrigin local space
quaternionHelper.copy(this.xrOrigin.quaternion).invert();
this.cursor.quaternion.multiply(quaternionHelper);
// Offset slightly along the oriented normal to avoid z-fighting
offsetHelper.set(0, 0, this.zOffset);
offsetHelper.applyQuaternion(this.cursor.quaternion);
cursorPosition.add(offsetHelper);

Two problems, both invisible while xrOrigin.quaternion is identity:

  1. The world-to-local conversion multiplies in the wrong order. Expressing a
    world rotation in a parent frame is parent⁻¹ * world, so this needs
    premultiply(quaternionHelper). multiply(quaternionHelper) computes
    world * parent⁻¹.

  2. The offset is applied with a local-space quaternion but added to a
    world-space position.
    cursorPosition still holds
    intersection.pointOnFace (world) at that point; xrOrigin.worldToLocal is
    only called afterwards. So the offset has to be built from the world normal.

With the player rotated, the offset therefore points somewhere other than out of
the surface, and for most orientations it pushes the cursor behind the geometry.
zOffset is only 0.004, so any error of more than a few degrees is enough.

Suggested fix

// Offset along the world-space normal, while the position is still world-space
offsetHelper.copy(scratchNormal).multiplyScalar(this.zOffset);
cursorPosition.add(offsetHelper);

// Then express the orientation in xrOrigin local space
this.cursor.quaternion.setFromUnitVectors(ZAxis, scratchNormal);
quaternionHelper.copy(this.xrOrigin.quaternion).invert();
this.cursor.quaternion.premultiply(quaternionHelper);

The fallback branch needs the same treatment: derive the offset direction from
intersection.pointerQuaternion (world) before converting the orientation, and
use premultiply for the conversion.

Dominant language
TypeScript
Stars
357
Forks
71
PR merge metrics
No merged PRs in 30d

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from facebook/immersive-web-sdk

All issues in facebook/immersive-web-sdk

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.