reckfellENGINE

Модели XR-контроллеров: загрузка и fallback

Проверка доступности, порядок событий, фильтры рук и управление геометрией устройства.

На этой странице

Модели устройств OpenXR

Render models — визуальные модели оборудования, предоставленные XR runtime. Текущая реализация Reckfell использует XR_EXT_render_model и XR_EXT_interaction_render_model. Наличие interaction profile не подтверждает доступность модели конкретного устройства.

Сначала настройте запуск XR и action map. Включите xr/openxr/extensions/render_model до запуска приложения. Для этих классов нужна сборка с OpenXR и glTF. Сохранённая галочка и наличие классов в SDK не означают, что runtime активировал оба расширения или загрузил геометрию.

Начать с общего менеджера

Для первого варианта используйте один OpenXRRenderModelManager с Tracker=Any непосредственно под XROrigin3D. Оставьте MakeLocalToPose пустым и сохраняйте собственный fallback-визуал, пока реальная модель недоступна.

Текст
XROrigin3D
├── XRCamera3D
├── OpenXRRenderModelManager (Tracker=Any)
└── FallbackVisuals

Создавайте менеджер только после проверки runtime-синглтона и IsActive. В текущем коде менеджер обращается к native-синглтону даже при уведомлениях создания; создание при отсутствующем wrapper может дать native error. Не создавайте OpenXRRenderModelExtension через new в игровом коде: wrapper управляется системой OpenXR.

C#
using System;
using Reckfell;

public static class RenderModelGate
{
    // Returns a new child owned by origin, or null so the caller can retain fallback visuals.
    public static OpenXRRenderModelManager? CreateIfActive(XROrigin3D origin,
        Action<OpenXRRenderModel> added, Action<OpenXRRenderModel> removed)
    {
        ArgumentNullException.ThrowIfNull(origin);
        ArgumentNullException.ThrowIfNull(added);
        ArgumentNullException.ThrowIfNull(removed);
        if (!origin.IsInsideTree()) return null;
        if (!Engine.HasSingleton("OpenXRRenderModelExtension")) return null;
        var extension = Engine.GetSingleton("OpenXRRenderModelExtension") as OpenXRRenderModelExtension;
        if (extension == null || !extension.IsActive()) return null;
        var manager = new OpenXRRenderModelManager
        {
            Tracker = OpenXRRenderModelManager.RenderModelTracker.Any
        };
        // Subscribe before AddChild: existing runtime models can be reported during EnterTree.
        manager.RenderModelAdded += model => added(model);
        manager.RenderModelRemoved += model => removed(model);
        origin.AddChild(manager);
        return manager;
    }
}

Вызовите CreateIfActive после запуска XR, передав живой origin и обработчики. null оставляет fallback под управлением вашего компонента. Возвращённый менеджер уже добавлен к origin; повторно AddChild не нужен. Он и его модели освобождаются с деревом. Синглтон extension заимствован: не вызывайте у него Free/Dispose.

Подписка внутри helper происходит до AddChild: менеджер может сообщить об уже известных моделях прямо при EnterTree. Если подписываться после добавления, начальные события можно пропустить. Положительная ветка с реальным runtime этим прогоном не проверена; проверены отказ без синглтона и аргументы.

Событие добавления не равно готовой картинке

RenderModelAdded менеджера передаёт OpenXRRenderModel Entity. Оно означает добавление записи в дерево менеджера. Если render_model_data отсутствует, создание дочерней геометрии может вернуть null, но событие Entity всё равно возможно. Не скрывайте fallback только по счётчику этих событий.

Для переключения визуалов проверьте фактическую геометрию выбранной модели и текущую tracking confidence через extension. При Confidence=None встроенный OpenXRRenderModel перестаёт обновлять transform/анимируемые части, но не скрывает предыдущую геометрию. Политику скрытия устаревшей модели и возврата fallback реализуйте отдельно. IsActive тоже не является флагом «контроллер загружен».

RenderModelRemoved передаётся перед remove_child/queue_free. Объект ещё доступен в обработчике, но сохранять его как живую модель на следующий кадр нельзя. У менеджера с фильтром обработка может быть отложена: backend RID к этому моменту уже может быть отозван, поэтому не запрашивайте по нему данные extension. Не освобождайте manager-owned Entity внутри этого события: удаление выполнит менеджер. Обновите собственное состояние fallback и отсоедините свои ссылки.

Отдельно для левой и правой руки

TrackerРазмещение
AnyВсе модели; менеджер непосредственно под XROrigin3D.
NoneSetМодели без определённого top-level path; также непосредственно под origin.
LeftHand / RightHandФильтр по пути руки; origin должен быть предком, допускается промежуточный Entity.

Для размещения внутри XRController3D используйте одинаковое имя pose у контроллера и MakeLocalToPose менеджера, например grip:

Текст
XROrigin3D
└── XRController3D (Tracker=left_hand, Pose=grip)
    └── OpenXRRenderModelManager (Tracker=LeftHand, MakeLocalToPose=grip)

MakeLocalToPose применяет обратный adjusted transform pose к внутреннему контейнеру. Это компенсация уже применённой pose, а не имя файла и не смена действия ввода. Включайте её только для конкретной руки и соответствующего родителя; Any/NoneSet с этой настройкой дают configuration warning.

Tracker связывается с positional tracker и XR path в setter при изменении значения. Если тогда API/tracker ещё отсутствовали, повтор той же настройки не гарантирует повторную привязку. Настраивайте hand-менеджер после готовности provider/trackers; при смене setup пересоздавайте принадлежащий вам менеджер контролируемо. Source также проверяет существование pose, а не её HasTrackingData, поэтому потерю tracking нужно обрабатывать явно.

Top-level path /user/hand/left или /user/hand/right — связь модели с рукой, а не доказательство, что контроллер физически удерживается. Система моделей не создаёт коллизии, ограничения движения руки или игровую логику кнопок. Эти задачи используйте отдельно от визуальной геометрии.

Прямая работа с OpenXRRenderModel

RenderModel принимает RID именно этого OpenXR extension. Это не mesh RID RenderingServer и не текстура RenderingDevice. Получайте актуальные идентификаторы у provider; не придумывайте числа и не освобождайте чужие RID через FreeRid/RenderModelDestroy.

Setter RenderModel сохраняет RID и при нахождении в дереве пытается добавить геометрию. В текущем исходнике он не удаляет прежние children: повторная установка ненулевого RID может добавить ещё одну сцену, а default RID сам по себе не очищает уже загруженную. Для замены собственного прямого экземпляра создайте новый OpenXRRenderModel и удалите старый Entity; для manager-owned экземпляров оставьте lifecycle менеджеру. Эти пути с реальной геометрией сверены по исходнику, не выполнены на гарнитуре.

GetTopLevelPath возвращает строку пути или пустую строку. RenderModelTopLevelPathChanged уведомляет о смене связи для конкретной модели; это не событие загрузки mesh. Root transform и анимируемые части обновляются из provider. Не добавляйте origin/reference frame второй раз поверх уже настроенной иерархии.

Диагностика

СимптомПроверить
Классы есть, менеджер даёт native errorРеальный runtime-синглтон, IsActive и момент создания; не создавайте wrapper вручную.
IsActive=true, но геометрии нетRuntime-модели, данные mesh, action map и связь устройства; это отдельные состояния.
Дубли контроллеровНесколько менеджеров с пересекающимися фильтрами, свой fallback и повторные SetRenderModel.
Модель смещена вдвоеРодитель XRController3D, его Pose, MakeLocalToPose и отсутствие второго origin/pose transform.
После потери tracking остался старый контроллерConfidence=None не скрывает Entity автоматически; включите свою политику fallback.
Фильтр руки не обновился после позднего стартаПорядок создания trackers и настройки Tracker; одинаковый setter не является rebind.

Что проверено

На установленном Reckfell 1.0.0, Windows headless с XR off выполнено 19 проверок: реальное отсутствие зарегистрированного runtime-синглтона, отказ точного helper, null-аргументы, параметры/пустой lifecycle Entity и сигнатуры C# events. Для metadata-проверок тест временно создал собственный неактивный диагностический wrapper, не зарегистрированный в Engine; это приём теста, не игровой пример. Его освобождение исправлено в QA-коде через Free, предупреждений об утечке в итоговом прогоне нет.

События в тесте вызывались вручную: это не обнаружение контроллеров. Сигналы provider, загрузка реальной геометрии, tracking loss visuals, hand filtering, pose rebasing, анимация, изображение и экспорт не проверены. Их ограничения описаны по текущему исходнику. Исходники движка, Runtime и SDK не менялись.

Документация Reckfell EngineНаверх ↑
БЫСТРЫЙ ПЕРЕХОДНачало работыEntity и иерархияC# & .NET 10Сцены и префабы
Руководства и полный API Reckfell K
reckfellENGINE
RECKFELL ACCOUNT

Вход в аккаунт

Войдите в аккаунт Reckfell.

Один аккаунт для всей платформы Reckfell.