Thomas Fransolet 0310d28b5e Canal VR Meta Quest : projet Unity, viewer web et documentation
Premier commit du cinquième front. Trois parties :

- `unity/MyInfoMateVR/` : le projet Unity (6000.0.83f1, URP, Meta XR SDK 205),
  un APK unique pour tous les clients. Menu flottant à sélection au regard,
  appairage, chargement de scène GLB, POI, cache de contenu, télémétrie.
- `unity-overlay/` : les mêmes scripts à recopier sur un projet Unity neuf, avec
  les pièges rencontrés consignés dans son README.
- `viewer/` : viewer et éditeur de scène web autonome (Vite, TypeScript,
  three.js), partagé avec les autres fronts.
- `docs/` : état des lieux, setup Unity, décisions d'architecture et plan
  d'exécution en 9 étapes.

La scène est décrite par un `scene.json` poussé par `adb push` : l'app le
préfère à celui embarqué dans l'APK.

Les binaires (GLB, textures de l'échantillon Sponza, DLL Meta XR) passent par
Git LFS dès ce premier commit — les y faire entrer après coup demanderait de
réécrire l'historique. Les artefacts régénérés par l'éditeur et par CMake
(`Library/`, `.utmp/`, Burst debug) sont ignorés.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 15:26:07 +02:00

4.4 KiB

name, description, allowed-tools, tags
name description allowed-tools tags
hz-meta-xr-operator-grabbed-objects Aims and positions objects currently held by simulated controllers in Meta Quest and Horizon OS XR apps, including pose-offset calibration and quaternion math for grip and aim poses.
Bash(hzdb:*)
agentic-xr
openxr
unity
aiming
grip
aim
grabbed
orientation
position
offset

Meta XR Operator Grabbed Object Control

How to precisely aim grabbed objects (guns, tools, pointers) so a reference point (muzzle, tip) points at a target.

Core Concept: Pose Offset

When grabbed, the object's reference point doesn't align with the controller pose. There are two offsets — rotation and position — that are constant after a grab. Measure them once, then apply their inverses to aim or position the object.

Offsets depend on which pose type (aim vs grip) you use. Always calibrate — never skip based on assumptions about the grab code. The OpenXR-to-object mapping always has a residual offset.

Step 1: Grab the Object

Position the controller at the object and press grip (see coordinates skill for world-to-OpenXR conversion).

Step 2: Calibrate Offsets (once per grab)

Set the aim pose to identity, then read the reference point's pose:

set_controller_pose(aim, position: [0,0,0], orientation: [0,0,0,1])
get_world_pose("Controller/Anchor")    → anchor_pos
get_world_pose("Object/RefPoint")      → ref_pos, rotation (rx, ry, rz)

Rotation offset — the reference point's yaw at identity reveals the offset:

forward_openxr = (sin(ry°), 0, -cos(ry°))     // for yaw-only offset
Q_offset = math_build_quat(forward_openxr)
Q_offset_inv = [-Q.x, -Q.y, -Q.z, Q.w]        // quaternion conjugate

Position offset — vector from anchor to reference point in Unity world space:

P_offset = ref_pos - anchor_pos

Note: Recalibrate the position offset if the object's scale or model changes. The rotation offset is stable across those changes.

After computing the offsets, the controller and grabbed object should be moved to a default position in view (unless specified otherwise by the user or game). Keep held objects far enough from the camera so they don't fill the screen. In local (EyeLevel) space, recommended ranges from the camera are:

  • Z (forward): minimum -0.4, closer makes objects too large
  • Y (below eyes): -0.2 to -0.4
  • X (lateral): ±0.1 to ±0.2 (positive = right hand, negative = left hand)

Step 3: Aim at a Target

  1. Turn head to face the target (see coordinates skill).
  2. Requery positions — head movement shifts the controller in world space, so it is critical to requery positions of objects after head movement:
    get_world_pose("Object/RefPoint")  → ref_pos
    get_world_pose("TargetObject")     → target_pos
    
  3. Compute aim orientation and apply the inverse offset:
    dir = target_pos - ref_pos
    Q_desired = math_build_quat([dir.x, dir.y, -dir.z])
    Q_aim = math_multiply_quat(Q_desired, Q_offset_inv)
    set_controller_pose(aim, orientation: Q_aim)
    
  4. Refine — the rotation shifts the reference point slightly. Requery ref_pos, recompute, and re-set once for better accuracy.
  5. Act and verify:
    set_controller_input(Trigger, 1)
    openxr_capture_composited_image()           // visual check
    get_world_pose("TargetObject")              // "not found" = hit
    

Positioning the Reference Point

To place the reference point at an exact world position, subtract P_offset before converting to OpenXR:

anchor_pos = desired_pos - P_offset
openxr_pos = world_to_openxr(anchor_pos)       // see coordinates skill
set_controller_pose(aim, position: openxr_pos)

To position AND orient in one call, combine with the aim quaternion from Step 3.

Common Pitfalls

  1. Not calibrating after grab — measure offsets after each grab.
  2. Using stale positions — always requery after any head or controller movement.
  3. Not turning head first — head rotation moves the controller. Turn head → requery → compute.
  4. Mixing pose types — calibrate and aim with the same pose type (both aim or both grip).
  5. Forgetting the inverseQ_aim = Q_desired * Q_offset_inv, not Q_desired * Q_offset.
  6. Aiming from object origin instead of fire point — use the actual muzzle/fire point child, not the object's transform origin.