Skip to content

Commit 83a0253

Browse files
willeastcottclaude
andcommitted
Re-aim teleport arc from grip toward the target ray's indicated point
On Apple Vision Pro the target ray originates at the head, so launching the arc from the grip position with the raw target ray direction offset the trajectory by the hand-to-head parallax - the landing depended on where the hand happened to be when the pinch registered. The aim ray now launches from the grip but points at the spot the target ray indicates (its navigation-plane intersection, or a far point along the ray), making the landing independent of the initial hand registration. For tracked-pointer controllers the grip and ray origin coincide, so this reduces exactly to the unmodified target ray. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 00a6878 commit 83a0253

1 file changed

Lines changed: 36 additions & 11 deletions

File tree

scripts/esm/xr-navigation.mjs

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ const tmpSegDir = new Vec3();
2424
const tmpToCam = new Vec3();
2525
const tmpSide = new Vec3();
2626
const tmpAimOrigin = new Vec3();
27+
const tmpAimDir = new Vec3();
28+
const tmpAimPoint = new Vec3();
2729

2830
const arcVertexGLSL = /* glsl */ `
2931
attribute vec3 vertex_position;
@@ -565,20 +567,41 @@ class XrNavigation extends Script {
565567
}
566568

567569
/**
568-
* Returns the world-space point the aim visualization should start from. The target ray
569-
* origin sits at the head for gaze/pinch-style input (e.g. Apple Vision Pro transient
570-
* pointers), so prefer the handheld grip position when the input source has one.
570+
* Computes the world-space aim ray for an input source, writing it into tmpAimOrigin and
571+
* tmpAimDir. The arc launches from the handheld grip position when the input source has
572+
* one, because the target ray origin sits at the head for gaze/pinch-style input (e.g.
573+
* Apple Vision Pro transient pointers). The direction is then re-aimed from the grip
574+
* toward the point the target ray indicates - using the raw target ray direction from the
575+
* hand would offset the trajectory by the hand-to-head parallax, making the landing depend
576+
* on where the hand happened to be when the pinch registered. For tracked-pointer
577+
* controllers the grip and ray origin coincide, so this reduces to the target ray itself.
571578
*
572579
* @param {XrInputSource} inputSource - The aiming input source.
573-
* @returns {Vec3} The world-space start point.
574580
* @private
575581
*/
576-
_getAimOrigin(inputSource) {
577-
// Copy immediately: getPosition() and getOrigin() return references to the input
578-
// source's internal vectors, which the engine reuses as scratch space on the next
579-
// getDirection()/getOrigin() call
582+
_getAimRay(inputSource) {
583+
// Copy immediately: getOrigin()/getDirection()/getPosition() return references to the
584+
// input source's internal vectors, which the engine reuses as scratch space on
585+
// subsequent calls
586+
tmpAimDir.copy(inputSource.getDirection());
587+
tmpAimOrigin.copy(inputSource.getOrigin());
588+
580589
const grip = inputSource.grip ? inputSource.getPosition() : null;
581-
return tmpAimOrigin.copy(grip || inputSource.getOrigin());
590+
if (!grip) return;
591+
592+
// Aim point: where the target ray meets the navigation plane, or a far point along
593+
// the ray when it never descends to it
594+
let t = this.maxTeleportDistance;
595+
if (tmpAimDir.y < -0.001) {
596+
const tPlane = (tmpAimOrigin.y - this.groundHeight) / -tmpAimDir.y;
597+
if (tPlane > 0 && tPlane < this.maxTeleportDistance * 2) {
598+
t = tPlane;
599+
}
600+
}
601+
tmpAimPoint.copy(tmpAimDir).mulScalar(t).add(tmpAimOrigin);
602+
603+
tmpAimOrigin.copy(grip);
604+
tmpAimDir.sub2(tmpAimPoint, tmpAimOrigin).normalize();
582605
}
583606

584607
/**
@@ -646,7 +669,8 @@ class XrNavigation extends Script {
646669
if (!rec) {
647670
rec = { point: new Vec3(), valid: false };
648671
this._arcHits.set(inputSource, rec);
649-
this._computeArcHit(this._getAimOrigin(inputSource), inputSource.getDirection(), rec);
672+
this._getAimRay(inputSource);
673+
this._computeArcHit(tmpAimOrigin, tmpAimDir, rec);
650674
}
651675
if (!rec.valid) return;
652676

@@ -840,7 +864,8 @@ class XrNavigation extends Script {
840864
rec = { point: new Vec3(), valid: false };
841865
this._arcHits.set(inputSource, rec);
842866
}
843-
this._computeArcHit(this._getAimOrigin(inputSource), inputSource.getDirection(), rec);
867+
this._getAimRay(inputSource);
868+
this._computeArcHit(tmpAimOrigin, tmpAimDir, rec);
844869

845870
const visual = this._getArcVisual(inputSource);
846871
visual.entity.enabled = true;

0 commit comments

Comments
 (0)