Skip to content

Commit 02f6d3f

Browse files
committed
feat: add pointer indicator
1 parent 6289dcb commit 02f6d3f

3 files changed

Lines changed: 138 additions & 2 deletions

File tree

android/sharedCode/src/main/cpp/VROInputControllerOpenXR.cpp

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
#include <android/log.h>
99
#include <cmath>
1010
#include "VROLog.h"
11-
#include "VROInputPresenterOVR.h"
11+
#include "VROInputPresenterOpenXR.h"
1212
#include "VROVector3f.h"
1313
#include "VROCamera.h"
1414
#include "VROInputType.h"
@@ -256,6 +256,7 @@ void VROInputControllerOpenXR::onProcess(XrSession session, XrSpace baseSpace,
256256
VROInputControllerBase::updateHitNode(camera, pos, forward);
257257
VROInputControllerBase::onMove(ViroOculus::Controller, pos, rot, forward);
258258
VROInputControllerBase::processGazeEvent(ViroOculus::Controller);
259+
updateLaserViz(pos, forward);
259260
}
260261
}
261262

@@ -573,6 +574,7 @@ void VROInputControllerOpenXR::processHands(XrSpace baseSpace, XrTime time,
573574
if (hand == 1) { // right hand = primary pointer
574575
VROInputControllerBase::updateHitNode(camera, pos, forward);
575576
VROInputControllerBase::processGazeEvent(source);
577+
updateLaserViz(pos, forward);
576578
}
577579
VROInputControllerBase::onMove(source, pos, rot, forward);
578580
} else {
@@ -597,6 +599,7 @@ void VROInputControllerOpenXR::processHands(XrSpace baseSpace, XrTime time,
597599
if (hand == 1) {
598600
VROInputControllerBase::updateHitNode(camera, pos, forward);
599601
VROInputControllerBase::processGazeEvent(source);
602+
updateLaserViz(pos, forward);
600603
}
601604
VROInputControllerBase::onMove(source, pos, rot, forward);
602605
}
@@ -653,5 +656,22 @@ VROVector3f VROInputControllerOpenXR::getDragForwardOffset() {
653656

654657
std::shared_ptr<VROInputPresenter>
655658
VROInputControllerOpenXR::createPresenter(std::shared_ptr<VRODriver> driver) {
656-
return std::make_shared<VROInputPresenterOVR>();
659+
return std::make_shared<VROInputPresenterOpenXR>();
660+
}
661+
662+
void VROInputControllerOpenXR::updateLaserViz(const VROVector3f &origin,
663+
const VROVector3f &forward) {
664+
auto presenter = std::dynamic_pointer_cast<VROInputPresenterOpenXR>(getPresenter());
665+
if (!presenter) return;
666+
667+
// If we have a hit, end the laser at the hit point. Otherwise, extend it
668+
// a fixed distance forward so the user always sees their aim ray.
669+
constexpr float kNoHitRange = 4.0f; // meters
670+
VROVector3f hitPoint;
671+
if (_hitResult) {
672+
hitPoint = _hitResult->getLocation();
673+
} else {
674+
hitPoint = origin + forward.scale(kNoHitRange);
675+
}
676+
presenter->updateAimRay(origin, hitPoint, true /* visible */);
657677
}

android/sharedCode/src/main/cpp/VROInputControllerOpenXR.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,15 @@ class VROInputControllerOpenXR : public VROInputControllerBase {
161161
bool createActionSpaces(XrSession session);
162162
void processHands(XrSpace baseSpace, XrTime time, const VROCamera &camera);
163163

164+
/**
165+
* Push the current aim ray to the input presenter so it can render the
166+
* visible laser line. If the most recent updateHitNode produced a hit,
167+
* the line stops at the hit point; otherwise it extends a fixed distance
168+
* along the forward direction so the user always sees where they're aiming.
169+
* Call once per frame, immediately after updateHitNode + processGazeEvent.
170+
*/
171+
void updateLaserViz(const VROVector3f &origin, const VROVector3f &forward);
172+
164173
public:
165174
/** Enable or disable hand tracking gesture processing. Thread-safe (atomic store). */
166175
void setHandTrackingEnabled(bool enabled) { _handTrackingEnabled = enabled; }
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// VROInputPresenterOpenXR.h
2+
// ViroRenderer
3+
//
4+
// Visual presenter for the OpenXR (Meta Quest) input controller. Provides:
5+
// 1. A reticle (existing VROReticle) positioned at the hit point in world
6+
// space (not headlocked — Cardboard-style fixed pointer is wrong here
7+
// because we have actual 6DOF aim from controllers / FB hand-aim).
8+
// 2. A laser line from the aim origin to the reticle.
9+
//
10+
// Both are children of the presenter's _rootNode, which VROScene attaches to
11+
// the scene root via attachInputController(). The reticle position is updated
12+
// via the existing onGazeHit → onReticleGazeHit chain. The laser endpoints
13+
// are updated each frame by VROInputControllerOpenXR via updateAimRay().
14+
//
15+
// Copyright © 2026 ReactVision. All rights reserved.
16+
// MIT License — see LICENSE file.
17+
18+
#ifndef ANDROID_VROINPUTPRESENTEROPENXR_H
19+
#define ANDROID_VROINPUTPRESENTEROPENXR_H
20+
21+
#include <memory>
22+
#include <vector>
23+
#include "VROInputPresenter.h"
24+
#include "VROReticle.h"
25+
#include "VRONode.h"
26+
#include "VROPolyline.h"
27+
#include "VROMaterial.h"
28+
29+
class VROInputPresenterOpenXR : public VROInputPresenter {
30+
public:
31+
VROInputPresenterOpenXR() {
32+
// Reticle at world-space hit point (not headlocked).
33+
auto reticle = std::make_shared<VROReticle>(nullptr);
34+
reticle->setPointerFixed(false);
35+
setReticle(reticle);
36+
37+
// Build the laser geometry once. Per-frame updates mutate the existing
38+
// polyline's points via setPaths() rather than swapping the node's
39+
// geometry — swapping caused a one-frame blink because the new
40+
// geometry isn't fully uploaded to the GPU until the next render.
41+
std::vector<VROVector3f> initialPath = { {0, 0, 0}, {0, 0, -1} };
42+
_laserGeom = VROPolyline::createPolyline(initialPath, 0.003f /* thickness */);
43+
_laserGeom->setName("AimLaserGeom");
44+
auto material = _laserGeom->getMaterials().front();
45+
material->getDiffuse().setColor({ 0.33f, 0.976f, 0.968f, 1.0f }); // cyan
46+
material->setWritesToDepthBuffer(false);
47+
material->setReadsFromDepthBuffer(false);
48+
material->setReceivesShadows(false);
49+
50+
_laserNode = std::make_shared<VRONode>();
51+
_laserNode->setName("AimLaser");
52+
_laserNode->setGeometry(_laserGeom);
53+
_laserNode->setHidden(true); // hidden until first updateAimRay()
54+
_rootNode->addChildNode(_laserNode);
55+
}
56+
57+
~VROInputPresenterOpenXR() override = default;
58+
59+
/**
60+
* Inherit the existing reticle-positioning chain. processGazeEvent() in
61+
* VROInputControllerBase calls onGazeHit() on registered delegates; we
62+
* forward to onReticleGazeHit() which moves the reticle to hit world pos.
63+
*/
64+
void onGazeHit(int source, std::shared_ptr<VRONode> node, const VROHitTestResult &hit) override {
65+
VROInputPresenter::onReticleGazeHit(hit);
66+
}
67+
68+
/**
69+
* Trigger reticle pulse animation on click for parity with OVR/Cardboard.
70+
*/
71+
void onClick(int source, std::shared_ptr<VRONode> node, ClickState clickState,
72+
std::vector<float> position) override {
73+
VROInputPresenter::onClick(source, node, clickState, position);
74+
if (clickState == ClickState::ClickUp && getReticle()) {
75+
getReticle()->trigger();
76+
}
77+
}
78+
79+
/**
80+
* Update the visible aim ray each frame.
81+
*
82+
* Mutates the persistent polyline's points in place via setPaths(). This
83+
* avoids the one-frame blink that occurs when VRONode::setGeometry()
84+
* swaps the geometry pointer and the new mesh hasn't been uploaded to
85+
* the GPU yet.
86+
*
87+
* @param origin World-space origin of the ray
88+
* @param hitPoint World-space endpoint
89+
* @param visible True to show, false to hide (no input active)
90+
*/
91+
void updateAimRay(const VROVector3f &origin, const VROVector3f &hitPoint, bool visible) {
92+
if (!visible) {
93+
_laserNode->setHidden(true);
94+
return;
95+
}
96+
_laserNode->setHidden(false);
97+
98+
std::vector<std::vector<VROVector3f>> paths = { { origin, hitPoint } };
99+
_laserGeom->setPaths(paths);
100+
}
101+
102+
private:
103+
std::shared_ptr<VRONode> _laserNode;
104+
std::shared_ptr<VROPolyline> _laserGeom;
105+
};
106+
107+
#endif // ANDROID_VROINPUTPRESENTEROPENXR_H

0 commit comments

Comments
 (0)