Отслеживание рук: суставы, pinch и модель
Проверка данных руки, исполняемый пример жеста и реальные ограничения скелетной модели.
Отслеживание суставов руки и ввод контроллера — разные источники данных. XRHandTracker даёт трансформы и флаги суставов; карта действий даёт логические команды. Наличие суставов не создаёт автоматически select, захват или готовую модель руки.
Включите и проверьте данные
- Подготовьте XR-сессию и обработку фокуса.
- До инициализации включите xr/openxr/extensions/hand_tracking. Для OpenXR нужны расширение XR_EXT_hand_tracking и поддержка hand tracking устройством/runtime. Настройка запрашивает возможность, но не гарантирует её доступность.
- Получайте левую руку через XRServer.GetTracker("/user/hand_tracker/left") as XRHandTracker, правую — через /user/hand_tracker/right. Это другие имена, чем left_hand/right_hand у контроллеров.
- Проверяйте null, затем HasTrackingData руки, затем флаги конкретных суставов. Отсутствие tracker может означать незавершённую инициализацию, остановленный сеанс или недоступную возможность; оно само по себе не доказывает причину.
У XRHandTracker есть 26 суставов. HandJoint.Max — граница диапазона, не сустав. Transform сустава в OpenXR задан в пространстве tracking, а не относительно предыдущей кости. В этом пути ориентация дополнительно приведена к соглашениям Humanoid-рига. Не записывайте такие трансформы напрямую как локальные трансформы костей.
| Что нужно | Что проверять |
|---|---|
| Координата сустава | PositionValid; для строгого взаимодействия также PositionTracked |
| Ориентация | OrientationValid; требование OrientationTracked зависит от вашей политики |
| Скорость | Соответствующий LinearVelocityValid/AngularVelocityValid, а не просто ненулевой Vector3 |
| Радиус в OpenXR | PositionValid; радиус дан в метрах и без валидной позиции не определён |
VALID и TRACKED различаются: пригодная оценка не обязательно непосредственно отслеживается. Единицы радиуса и правила валидности: справочник OpenXR.
Подтверждён дефект текущего конструктора: начальные флаги суставов не инициализированы. Не считайте новый XRHandTracker готовыми данными и не включайте ему HasTrackingData вручную в игровом коде. Собственный поставщик обязан заполнить флаги и значения всех нужных суставов до публикации валидного состояния. Пример ниже предполагает данные такого поставщика; он не исправляет конструктор.
Пример жеста pinch
Сохраните HandPinch.cs и создайте отдельный экземпляр для каждой руки. Вызывайте Update с текущим tracker и состоянием фокуса из XrStartup. Это локальный детектор расстояния, не команда action map и не реализация распознавания жестов runtime.
using Reckfell;
public sealed class HandPinch
{
public bool IsPinching { get; private set; }
// OpenXR raw positions are meters. No game WorldScale is applied here.
public bool Update(XRHandTracker tracker, bool focused)
{
if (!focused || !TryDistance(tracker, out float meters))
return IsPinching = false;
IsPinching = IsPinching ? meters < 0.04f : meters <= 0.025f;
return IsPinching;
}
public static bool TryDistance(XRHandTracker tracker, out float meters)
{
meters = 0;
if (tracker is null || !tracker.HasTrackingData) return false;
const XRHandTracker.HandJointFlags required =
XRHandTracker.HandJointFlags.PositionValid | XRHandTracker.HandJointFlags.PositionTracked;
var thumb = XRHandTracker.HandJoint.ThumbTip;
var index = XRHandTracker.HandJoint.IndexFingerTip;
if ((tracker.GetHandJointFlags(thumb) & required) != required
|| (tracker.GetHandJointFlags(index) & required) != required) return false;
Vector3 a = tracker.GetHandJointTransform(thumb).Origin;
Vector3 b = tracker.GetHandJointTransform(index).Origin;
if (!a.IsFinite() || !b.IsFinite()) return false;
meters = a.DistanceTo(b);
return float.IsFinite(meters);
}
}Вход — расстояние до 0.025 м, отпускание — от 0.04 м. Гистерезис предотвращает переключение на одной границе; пороги нужно проверить на целевых руках и устройстве. Пример требует PositionValid и PositionTracked у обоих кончиков, поэтому намеренно отвергает только оценённые положения. Для OpenXR используются сырые метрические координаты; WorldScale сюда не добавляется. Для другого поставщика сначала подтвердите его соглашения.
При потере фокуса/данных состояние сбрасывается. При возврате фокуса уже сведённые пальцы могут сразу снова дать true; если меню требует нового жеста после возврата, добавьте отдельное перевооружение. Сам helper не выбирает объект, не создаёт луч или коллайдер. Для события одного нажатия сравнивайте текущее состояние с предыдущим; не выполняйте действие каждый кадр удержания.
Покажите настоящую модель руки
Понадобится своя корректно скиненная модель со скелетом. В риге используйте такую структуру:
XROrigin3D
└── XREntity3D (/user/hand_tracker/left, default)
└── Model
└── Skeleton3D
└── XRHandModifier3DУ XREntity3D сначала задайте Tracker, затем Pose=default. У XRHandModifier3D задайте HandTracker на ту же руку. Модификатор сопоставляет имена костей с префиксами Left/Right: например LeftHand, LeftThumbTip, LeftIndexProximal. Отсутствующие кости пропускаются с предупреждением; это не автоматическая починка скелета.
Режим Full меняет позиции и вращения костей, RotationOnly — вращения, сохраняя длины модели. Модификатор не создаёт skin weights, не исправляет оси костей и не добавляет коллизии пальцев. При потере данных он может сохранять предыдущую позу. Видимость ShowWhenTracked не заменяет запрет взаимодействий; ограничения tracking у XREntity3D описаны отдельно.
Читайте результат в момент модификации
Skeleton3D временно применяет модификаторы и затем восстанавливает базовую pose. Поэтому для диагностики читайте кость внутри ModificationProcessed, а не произвольно после кадра. Сохраните HandPoseProbe.cs, добавьте обычный Entity с этим скриптом, задайте Skeleton, Modifier и существующий индекс Bone. LastModified хранит копию и сам по себе не подтверждает актуальный tracking.
using Reckfell;
public partial class HandPoseProbe : Entity
{
[Export] public Skeleton3D Skeleton { get; set; } = null!;
[Export] public XRHandModifier3D Modifier { get; set; } = null!;
[Export] public int Bone { get; set; }
public Transform3D LastModified { get; private set; } = Transform3D.Identity;
public override void _Ready()
{
if (Skeleton is null || Modifier is null || Modifier.GetSkeleton() != Skeleton
|| Bone < 0 || Bone >= Skeleton.GetBoneCount())
{
ReckfellApi.PushError("HandPoseProbe: assign a matching skeleton, modifier and bone index.");
return;
}
Modifier.ModificationProcessed += Capture;
}
private void Capture() => LastModified = Skeleton.GetBonePose(Bone);
public override void _ExitTree()
{
if (Modifier is not null && ReckfellObject.IsInstanceValid(Modifier))
Modifier.ModificationProcessed -= Capture;
}
}Исполнение уточнило поведение: до первой валидной руки модификатор оставляет базовую pose; при потере HasTrackingData после валидного кадра повторяет сохранённую относительную pose. При невалидной Palm и HasTrackingData=true он пропускает обработку. HandTracker setter в дереве перестраивает карту и очищает cache даже при повторе имени — не вызывайте его каждый кадр.
Используемая иерархия суставов должна быть полностью согласована и валидна. В исходнике после проверки ORIENTATION_VALID нет отдельной защиты каждого дочернего сустава перед применением; частично невалидная иерархия не проверена как корректный сценарий. Не подставляйте флаги вручную у настоящего поставщика.
HandTrackingSource=Unknown означает неизвестный источник. Не определяйте по названию runtime, оптическая рука это или имитация от контроллера. Профили взаимодействия рук могут давать действия через карту OpenXR, но требуют своей поддержки и привязок; суставы сами по себе этого не подтверждают.
Диагностика и охват
Если руки нет, сначала проверьте запуск и наличие tracker; если tracker есть, но данные невалидны — состояние сеанса, настройки устройства и видимость рук его камерам. Если модель стоит на месте — pose default, HandTracker модификатора и имена костей. Если жест срабатывает неверно — флаги обоих суставов, пространство координат, конечность значений и пороги.
Выполнены 19 проверок искусственного XRHandTracker и ещё 14 проверок настоящего Skeleton3D/XRHandModifier3D с 26 сопоставленными костями и искусственными суставами. Точный HandPoseProbe выполнен в движке. Дефект начальных флагов сохранён как отрицательный результат; тестовый поставщик инициализирует их явно. Оптическое tracking, профиль ввода, экспорт, skin weights, качество модели/анимации и изображение не проверялись. Старые ссылки на отсутствующие демо и прогнозы поддержки платформ удалены.