Решатели — MRTK3

Solver Main

Решатели — это компоненты, которые упрощают вычисление положения и ориентации объекта в соответствии с предопределенным алгоритмом. Пример: размещение объекта на поверхности, с которой пересекается взгляд пользователя.

Система решателей детерминированно определяет порядок операций для этих вычислений преобразования, так как другого надежного способа указать для Unity порядок обновления компонентов не существует.

Решатели предлагают ряд вариантов поведения для присоединения объектов к другим объектам или системам. Еще один пример — закрепленный объект, который парит перед пользователем (с учетом положения камеры). Решатель можно также подключить к контроллеру и объекту, чтобы закрепить объект у контроллера. Все решатели можно безопасно собрать вместе, например закрепление + поверхностный магнетизм + импульс.

Использование

Система решателей состоит из трех категорий сценариев:

  • Solver: базовый абстрактный класс, от которого наследуются все решатели. Он обеспечивает отслеживание состояния, параметры сглаживания и их реализацию, автоматическую интеграцию системы решателей и порядок обновления.
  • SolverHandler — задает опорный объект для отслеживания (например, трансформация основной камеры, телекинез и т. д.), обеспечивает сбор компонентов решателя и выполняет их обновление в нужном порядке.

Третья категория — это сам решатель. Стандартные блоки для основного поведения предоставляют следующие решатели:

  • Orbital: фиксирует определенную позицию и смещение относительно объекта, на который ссылается.
  • ConstantViewSize: масштабируется, чтобы поддерживать постоянный размер относительно вида с объекта, на который ссылается.
  • RadialView: удерживает объект в конусе просмотра объекта, на который ссылается.
  • Follow: удерживает объект внутри границ, заданных пользователем, объекта, на который ссылается.
  • InBetween: удерживает объект между двумя отслеживаемыми объектами.
  • SurfaceMagnetism: направляет лучи на поверхности в мире и выравнивает объект по этой поверхности.
  • DirectionalIndicator: определяет положение и ориентацию объекта как индикатор направления. С исходной позиции отслеживаемой цели SolverHandler этот индикатор будет ориентироваться на указанное целевое направление DirectionalTarget.
  • Momentum: применяет ускорение, скорость или трение для моделирования импульса и упругости для объекта, перемещаемого другими решателями или компонентами.
  • HandConstraint: определяет, что объект должен следовать за руками в области, в которой GameObject и руки не пересекаются. Это может быть полезно для ограниченного интерактивного содержимого, такого как меню и т. д. Этот решатель предназначен для работы с XRNode.
  • HandConstraintPalmUp: является производным от HandConstraint, но включает логику для проверки того, находится ли ладонь напротив пользователя перед активацией. Этот решатель работает только с контроллерами XRNode. С другими типами контроллеров этот решатель будет вести себя так же, как и его базовый класс.

Чтобы использовать систему решателей, просто добавьте один из перечисленных выше компонентов в GameObject. Так как для всех решателей требуется сценарий SolverHandler, он будет создан Unity автоматически.

Примечание

Примеры использования системы решателей можно найти в файле SolverExamples.scene.

Изменение ссылки на отслеживание

Свойство Tracked Target Type (Тип отслеживаемой цели) компонента SolverHandler определяет позицию, которую будут использовать все решатели для вычисления своих алгоритмов. Например, тип значения Head с простым компонентом SurfaceMagnetism приведет к созданию луча от головы по направлению взгляда пользователя для решения того, на какую поверхность попал луч. Возможные значения свойства TrackedTargetType:

  • *Head: исходная позиция — преобразование основной камеры
  • ControllerRay: исходной позицией является преобразование LinePointer на контроллере (т. е. источник указателя на контроллере движения или руки), указывающее в направлении линии луча
    • Используйте свойство TrackedHandedness для выбора руки (т. е. левая, правая, обе)
  • HandJoint: исходной позицией является преобразование определенного сустава руки
    • Используйте свойство TrackedHandedness для выбора руки (т. е. левая, правая, обе)
    • Используйте свойство TrackedHandJoint, чтобы определить, какое преобразование сустава нужно использовать.
  • CustomOverride: исходная позиция из назначенного свойства TransformOverride.

Примечание

Для типов ControllerRay и HandJoint решатель сначала попытается обратиться к левому контроллеру или преобразованию руки, а затем к правому, если левая сторона недоступна или если в свойстве TrackedHandedness не указано иное.

Важно!

Большинство решателей используют вектор, направленный вперед, для отслеживаемой цели преобразования, который предоставляется SolverHandler. При использовании типа отслеживаемой цели Hand Joint вектор суставов ладони может указывать на пальцы, а не сквозь ладонь. Это зависит от платформы, предоставляющей данные о суставах руки. В имитации входных данных и Windows Mixed Reality вектор вверх проходит сквозь ладонь (т. е. зеленый вектор — вверх, синий вектор — вперед).

Чтобы устранить это, обновите свойство Additional Rotation (Дополнительное вращение), указав для SolverHandler значение <90, 0, 0>. Это обеспечит направление вектора, подаваемого решателям, вперед через ладонь и обратно от руки.

Кроме того, можно использовать тип отслеживаемой цели Controller Ray, чтобы получить аналогичное поведение при указании направления с помощью рук.

Создание цепочки решателей

Можно добавить несколько компонентов Solver в один и тот же GameObject, чтобы связать их алгоритмы. Компоненты SolverHandler обрабатывают обновление всех решателей в одном GameObject. По умолчанию SolverHandler вызывает GetComponents<Solver>() при запуске, что будет возвращать решатели в том порядке, в котором они отображаются в инспекторе.

Более того, значение True для свойства преобразования Updated Linked Transform укажет Solver сохранить его вычисленное положение, ориентацию и масштаб в промежуточную переменную, доступную всем решателям (т. е. GoalPosition). Если значение равно False, Solver обновляет преобразование GameObject напрямую. При сохранении свойств преобразования в промежуточном расположении другие решатели могут выполнять вычисления, начиная с промежуточной переменной. Это обусловлено тем, что Unity не допускает обновления gameObject.Transform в стеке внутри одного кадра.

Примечание

Разработчики могут изменить порядок выполнения решателей, задав свойство SolverHandler.Solvers напрямую.

Создание решателя

Все решатели должны наследоваться от абстрактного базового класса Solver. Основные требования расширения решателей включают переопределение метода SolverUpdate. В этом методе разработчики должны обновить унаследованные свойства GoalPosition, GoalRotation и GoalScale до нужных значений. Более того, обычно полезно использовать SolverHandler.TransformTarget в качестве системы отсчета, необходимой клиенту.

Приведенный ниже код содержит пример нового компонента решателя InFront, который помещает присоединенный объект на 2 метра вперед от SolverHandler.TransformTarget. Если объект SolverHandler.TrackedTargetType задан клиентом как Head, тогда SolverHandler.TransformTarget будет преобразованием камеры, а этот решатель разместит присоединенный GameObject на расстоянии 2 м перед каждым кадром.

/// <summary>
/// InFront solver positions an object 2m in front of the tracked transform target
/// </summary>
public class InFront : Solver
{
    ...

    public override void SolverUpdate()
    {
        if (SolverHandler != null && SolverHandler.TransformTarget != null)
        {
            var target = SolverHandler.TransformTarget;
            GoalPosition = target.position + target.forward * 2.0f;
        }
    }
}

Руководства по реализации решателей

Общие свойства решателей

Каждый компонент решателя имеет основной набор идентичных свойств, управляющих основным поведением решателя.

Если включен параметр Сглаживание, решатель со временем обновит преобразование GameObject с учетом вычисленных значений. Скорость этого изменения определяется свойством LerpTime каждого компонента преобразования. Например, более высокое значение MoveLerpTime приведет к более медленному приращению движения между кадрами.

Если включен параметр MaintainScale (Соблюдать масштаб), то решатель будет использовать локальный масштаб GameObject по умолчанию.

Orbital

Класс Orbital является компонентом закрепления, который ведет себя как планеты в солнечной системе. Этот решатель обеспечит для присоединенных GameObject перемещение по орбите вокруг отслеживаемых преобразований. Таким образом, если для типа отслеживаемой целиSolverHandler задано значение Head, то GameObject будет перемещаться по орбите вокруг головы пользователя с фиксированным смещением.

Разработчики могут изменить это фиксированное смещение для сохранения меню и других компонентов сцены на уровне взгляда или пояса и т. д. вокруг пользователя. Это можно сделать, изменив свойства локального смещения Local Offset и смещения мира World Offset. Свойство типа ориентации Orientation Type определяет поворот, применяемый к объекту: должен ли всегда сохраняться начальный поворот, поворот в камеру либо поворот на любой объект, определяемый преобразованием.

RadialView

RadialView — это еще один компонент с закреплением, который хранит определенную часть GameObject в усеченном конусе обзора пользователя.

Свойства Min & Max View Degrees (Минимальный и максимальный углы обзора) определяют, какая часть GameObject должна всегда отображаться.

Свойства Min & Max Distance (минимальное и максимальное расстояние) определяют, насколько далеко должен находиться объект GameObject от пользователя. Например, если подойти к объекту GameObject на расстояние Min Distance в 1 м, то GameObject будет отталкиваться от камеры, потому что он не должен приближаться к пользователю ближе чем на 1 м.

Как правило, RadialView используется в сочетании с типом отслеживаемой цели со значением Head, чтобы компонент следовал за взглядом пользователя. Однако этот компонент может сохраняться в представлении любого типа отслеживаемой цели.

Follow

Класс Follow размещает элемент перед отслеживаемой целью относительно ее локальной оси вперед. Элемент может быть слабо ограничен (то есть закреплен), чтобы он не следовал за отслеживаемой целью, пока она не переместится за пределы, указанные пользователем.

Он работает аналогично решателю RadialView и обладает дополнительными элементами для управления максимальным горизонтальным& и вертикальным углом и механизмами изменения ориентации объекта.

Сцена с примером следования (Assets/MRTK/Examples/Demos/Solvers/Scenes/FollowSolverExample.unity)

InBetween

Класс InBetween удерживает присоединенный GameObject между двумя преобразованиями. SolverHandlerТип отслеживаемой цели объекта GameObject и свойство компонента InBetweenтипа второй отслеживаемой цели определяют эти конечные точки изменения. Как правило, оба типа получают значение CustomOverride, а результирующие SolverHandler.TransformOverride и значения InBetween.SecondTransformOverride задаются для двух отслеживаемых конечных точек.

Во время выполнения компонент InBetween создаст другой компонент SolverHandler на основе типа второй отслеживаемой цели и свойства Переназначение второго преобразования (Second Transform Override).

PartwayOffset определяет, где на линии между двумя преобразованиями должен располагаться объект: 0,5 — на половине пути, 1,0 — на первом преобразовании, а 0,0 — на втором преобразовании.

SurfaceMagnetism

SurfaceMagnetism работает путем создания луча к заданной маске LayerMask поверхностей и размещения GameObject в точке контакта.

Surface Normal Offset (Смещение нормали поверхности) помещает GameObject на заданное расстояние на расстоянии в метрах от поверхности в направлении нормали в точке попадания на поверхности.

И наоборот, Surface Ray Offset (Смещение луча поверхности) помещает GameObject на заданное расстояние в метрах от поверхности, но в противоположном направлении создания луча. Таким образом, если луч создается по взгляду пользователя, GameObject будет перемещен ближе по линии от точки попадания на поверхности до камеры.

Orientation Mode (Режим ориентации) определяет тип поворота, который необходимо применить к нормали на поверхности.

  • None — поворот не применяется.
  • TrackedTarget — объект будет обращен к отслеживаемому преобразованию, движущему луч.
  • SurfaceNormal — объект будет выравниваться по нормали в точке попадания на поверхности.
  • Blended — объект будет выравниваться на основе нормали в точке попадания на поверхности и на основе обращения к отслеживаемому преобразованию.

Чтобы заставить связанный объект GameObject оставаться в вертикальном положении в любом режиме, кроме None, включите параметр Keep Orientation Vertical (Сохранять вертикальную ориентацию).

Примечание

Свойство Orientation Blend (Смешение ориентации) используется для управления балансом между факторами поворота, если Orientation Mode (Режим ориентации) установлен в значение Blended (Смешанная). При значении 0,0 ориентация будет полностью управляться режимом TrackedTarget, а при значении 1,0 — режимом SurfaceNormal.

Определение достигаемых поверхностей

При добавлении компонента SurfaceMagnetism в GameObject важно рассмотреть слой GameObject и его дочерних элементов, если они есть. Компонент работает, выполняя оправку различных типов лучей, чтобы определить, к какой поверхности они должны "примагнититься". Предположим, у решателя GameObject есть коллайдер на одном из слоев, указанных в свойстве MagneticSurfacesSurfaceMagnetism. В этом случае луч, скорее всего, попадет в себя, в результате чего GameObject прикрепится к своей собственной точке коллайдера. Такого поведения можно избежать, задав для основного GameObject и всех дочерних элементов слой Ignore Ray cast (Игнорирование луча) или изменив массив MagneticSurfaces в LayerMask соответствующим образом.

И наоборот, GameObject SurfaceMagnetism не будет конфликтовать с областями на слое, не перечисленными в свойстве MagneticSurfaces. Мы рекомендуем размещать все нужные поверхности на выделенном слое (Поверхности) и присваивать свойству MagneticSurfaces значения только этого слоя. Использование значений default (по умолчанию) или everything (все) может привести к тому, что компоненты пользовательского интерфейса или курсоры будут влиять на решатель.

Наконец, поверхности, расположенные дальше, чем указано в значении свойства MaxRaycastDistance, будут игнорироваться лучами SurfaceMagnetism.

DirectionalIndicator

Класс DirectionalIndicator является компонентом закрепления, который ориентируется в направлении нужной точки в пространстве. Он чаще всего используется, если для типа отслеживаемой целиSolverHandler задано значение Head. Таким образом, компонент UX с решателем DirectionalIndicator будет направлять пользователя на нужную точку в пространстве. Эта точка определяется свойством Directional Target (Целевое направление).

Если целевое направление доступно для просмотра пользователю или в SolverHandler установлена какая-либо система координат, этот решатель отключит все компоненты Renderer под ним. Если направление не отображается, на индикаторе будет включено все.

Чем ближе пользователь будет к появлению точки целевого направления в поле зрения, тем меньше будет индикатор.

  • Min Indicator Scale — минимальный масштаб для объекта индикатора.

  • Max Indicator Scale — максимальный масштаб для объекта индикатора.

  • Visibility Scale Factor (Коэффициент шкалы видимости) — это множитель для увеличения или уменьшения поля зрения, который определяет, где становится видна точка целевого направления.

  • Свойство View Offset (смещение представления) с точки зрения системы координат (т. е. возможно, самой камеры) определяет, насколько далеко в направлении индикатора находится объект из центра окна просмотра.

Сцена с примером индикатора направления (Assets/MRTK/Examples/Demos/Solvers/Scenes/DirectionalIndicatorSolverExample.unity)

Меню руки с HandConstraint и HandConstraintPalmUp

Поведение HandConstraint предоставляет решателя, который ограничивает отслеживаемый объект регионом, безопасным для содержимого, ограничиваемого руками (например, пользовательского интерфейса руки, меню и т. д.). Безопасные регионы считаются областями, которые не пересекаются с рукой. Производный класс HandConstraint, вызываемый HandConstraintPalmUp, также включен для демонстрации общего поведения активации объекта, отслеживаемого решателем, когда ладонь обращена к пользователю.

См. страницу меню руки Hand Menu, на которой приведены примеры использования решателей для ограничения руки (Hand Constraint) для создания меню.