Solver — MRTK3

Solver Main

Solver sind Komponenten, welche die Berechnung der Position und Ausrichtung eines Objekts gemäß einem vordefinierten Algorithmus erleichtern. Beispiel: Platzieren eines Objekts auf der Oberfläche, mit welcher sich der Anvisieren-Raycast des Benutzers schneidet.

Das Solver-System definiert bestimmungsgemäß eine Abfolge von Vorgängen für diese Transformationsberechnungen, da es keine zuverlässige Möglichkeit gibt, Unity die Aktualisierungsreihenfolge für Komponenten anzugeben.

Solver bieten eine Reihe von Verhaltensweisen zum Anfügen von Objekten an andere Objekte oder Systeme. Ein weiteres Beispiel wäre ein folgendes („tag-along“) Objekt, das basierend auf der Kamera vor dem Benutzer schwebt. Ein Solver könnte auch an einen Controller und ein Objekt angefügt werden, damit das Objekt dem Controller folgt. Alle Solver können sicher gestapelt werden--z. B. ein Folgeverhalten plus Oberflächenmagnetismus plus Momentum.

Verwendung

Das Solver-System besteht aus drei Kategorien von Skripts:

  • Solver: Die abstrakte Basisklasse, von der alle Solver abgeleitet werden. Sie bietet Zustandsverfolgung, Glättungsparameter und Implementierung, automatische Solver-Systemintegration und Aktualisierungsreihenfolge.
  • SolverHandler: Legt das zu verfolgende Bezugsobjekt fest (z. B. die Hauptkameratransformation, Handstrahl usw.), verarbeitet das Sammeln von Solver-Komponenten und führt deren Aktualisierung in der richtigen Reihenfolge aus.

Die dritte Kategorie ist der Solver selbst. Die folgenden Solver stellen die Bausteine für das grundlegende Verhalten bereit:

  • Orbital: Wird an einer angegebenen Position und mit einem Offset vom Bezugszobjekt verankert.
  • ConstantViewSize: Wird so skaliert, dass eine konstante Größe relativ zur Ansicht des Bezugsobjekts beibehalten wird.
  • RadialView: Hält das Objekt innerhalb eines Sichtkegels, der vom Bezugsobjekt geworfen wird.
  • Follow: Hält das Objekt innerhalb einer Gruppe benutzerseitig definierter Grenzen vom Bezugsobjekt.
  • InBetween: Hält ein Objekt zwischen zwei verfolgten Objekten.
  • SurfaceMagnetism: Wirft Strahlen auf Oberflächen in der Welt und richtet das Objekt an dieser Oberfläche aus.
  • DirectionalIndicator: Bestimmt die Position und Ausrichtung eines Objekts als Richtungsindikator. Ab dem Bezugspunkt des vom SolverHandler verfolgten Ziels richtet sich dieser Indikator auf das angegebene DirectionalTarget aus.
  • Momentum: Wendet Beschleunigung/Geschwindigkeit/Reibung an, um Momentum und Elastizität für ein Objekt zu simulieren, das von anderen Solvern/Komponenten bewegt wird.
  • HandConstraint: Schränkt das Objekt auf die Verfolgung von Händen in einem Bereich ein, in dem sich das GameObject nicht mit den Händen überschneidet. Nützlich für auf Hände eingeschränkte interaktive Inhalte wie Menüs usw. Dieser Solver ist für die Verwendung mit XRNode vorgesehen.
  • HandConstraintPalmUp: Wird von HandConstraint abgeleitet, umfasst aber Logik zum Testen vor der Aktivierung, ob die Handfläche dem Benutzer zugewandt ist. Dieser Solver funktioniert nur mit XRNode-Controllern und verhält sich genau wie seine Basisklasse mit anderen Controllern.

Zum Verwenden des Solver-Systems fügen Sie einem GameObject einfach eine der oben aufgeführten Komponenten hinzu. Da alle Solver einen SolverHandler erfordern, wird einer automatisch von Unity erstellt.

Hinweis

Beispiele für die Verwendung des Solver-Systems finden Sie in der Datei SolverExamples.scene.

Ändern des Verfolgungsbezugs

Die Eigenschaft Tracked Target Type (Typ des verfolgten Ziels) der SolverHandler-Komponente definiert den Bezugspunkt, den alle Solver verwenden, um ihre Algorithmen zu berechnen. Beispielsweise führt ein Werttyp von Head mit einer einfachen SurfaceMagnetism-Komponente zu einem Raycast vom Kopf des Benutzers aus und in dessen Anvisierrichtung, um aufzulösen, welche Oberfläche getroffen wird. Potenzielle Werte für die TrackedTargetType-Eigenschaft sind:

  • *Head (Kopf): Bezugspunkt ist die Transformation der Hauptkamera.
  • ControllerRay: Bezugspunkt ist die LinePointer-Transformation auf einem Controller (d. h. Zeigerursprung auf einem Bewegungscontroller oder Handcontroller), die in Richtung des Linienstrahls zeigt.
    • Verwenden Sie die TrackedHandedness-Eigenschaft, um die bevorzugte Händigkeit auszuwählen (d. h. Links, Rechts, beide)
  • HandJoint (Handgelenk): Bezugspunkt ist die Transformation eines spezifischen Handgelenks.
    • Verwenden Sie die TrackedHandedness-Eigenschaft, um die bevorzugte Händigkeit auszuwählen (d. h. Links, Rechts, beide)
    • Verwenden Sie die TrackedHandJoint-Eigenschaft, um die zu verwendende Gelenktransformation zu bestimmen.
  • CustomOverride (benutzerdefinierte Außerkraftsetzung): Bezugspunkt von der zugewiesenen TransformOverride aus.

Hinweis

Für beide Typen, also ControllerRay und HandJoint, versucht der Solver-Handler zuerst, die linke Controller-/Handtransformation bereitzustellen, und dann die rechte, wenn die erste nicht verfügbar ist, oder wenn die TrackedHandedness-Eigenschaft etwas anderes angibt.

Wichtig

Die meisten Solver verwenden den Vorwärtsvektor des verfolgten Transformationsziels, das vom SolverHandler bereitgestellt wird. Bei Verwendung des Typs Handgelenk für ein verfolgtes Ziel kann es sein, dass der Vorwärtsvektor des Hand(flächen)gelenks durch die Finger und nicht durch die Handfläche zeigt. Dies hängt von der Plattform ab, die die Handgelenksdaten liefert. Für die Eingabesimulation und Windows Mixed Reality zeigt der Aufwärtsvektor durch die Handfläche nach oben (in anderen Worten heißt das, der grüner Vektor bedeutet aufwärts, der blaue Vektor vorwärts).

Um dies zu umgehen, aktualisieren Sie die Eigenschaft Additional Rotation (Zusätzliche Drehung) des SolverHandler auf <90, 0, 0>. Dadurch wird sichergestellt, dass der an die Solver übergebene Vorwärtsvektor durch die Handfläche und nach außen, von der Hand weg zeigt.

Alternativ können Sie den Typ Controller Ray (Controllerstrahl) für ein verfolgtes Ziel verwenden, um ein ähnliches Verhalten für das Zeigen mit den Händen zu erhalten.

Verketten von Solvern

Es ist möglich, dem selben GameObject mehrere Solver-Komponenten hinzuzufügen und so deren Algorithmen zu verketten. Die SolverHandler-Komponenten übernehmen die Aktualisierung aller Solver auf desselben GameObject. Standardmäßig ruft der SolverHandler beim Starten GetComponents<Solver>() auf, wodurch die Solver in der Reihenfolge zurückgegeben werden, in der sie im Inspektor vorkommen.

Darüber hinaus weist das Festlegen der Eigenschaft Updated Linked Transform (Aktualisierte verknüpfte Transformation) auf „true“ den Solver an, seine berechnete Position, Ausrichtung und Skalierung in einer Zwischenvariablen zu speichern, auf die alle Solver zugreifen können (d. h. GoalPosition). „false“ gibt an, dass der Solver die Transformation des GameObject direkt aktualisiert. Durch das Speichern der Transformationseigenschaften an einem Zwischenspeicherort können andere Solver ihre Berechnungen ab der Zwischenvariablen ausführen. Dies liegt daran, dass Unity nicht zulässt, dass Aktualisierungen von „gameObject.transform“ innerhalb desselben Frames gestapelt werden.

Hinweis

Entwickler können die Ausführungsreihenfolge von Solvern ändern, indem sie die SolverHandler.Solvers-Eigenschaft direkt festlegen.

Erstellen eines neuen Solvers

Alle Solver müssen von der abstrakten Basisklasse Solver erben. Die primären Anforderungen einer Solver-Erweiterung umfassen das Außerkraftsetzen der SolverUpdate-Methode. Bei dieser Methode sollten Entwickler die geerbten Eigenschaften GoalPosition, GoalRotation und GoalScale auf die gewünschten Werte aktualisieren. Darüber hinaus ist es nützlich, SolverHandler.TransformTarget als Bezugsframe zu nutzen, der vom Consumer gewünscht wird.

Der unten angegebene Code enthält ein Beispiel für eine neue Solver-Komponente namens InFront, die das angefügte Objekt 2 m vor dem SolverHandler.TransformTarget platziert. Der Consumer legt SolverHandler.TrackedTargetType als Head fest, dann ist SolverHandler.TransformTarget die Kameratransformation, weshalb dieser Solver das angefügte GameObject in jedem Frame 2 m vor dem Anvisieren des Benutzers platziert.

/// <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;
        }
    }
}

Richtlinien für die Solver-Implementierung

Allgemeine Solver-Eigenschaften

Jede Solver-Komponente verfügt über einen Kernsatz identischer Eigenschaften, die das Kernverhalten des Solvers steuern.

Wenn Smoothing (Glättung) aktiviert ist, aktualisiert der Solver die Transformation des GameObject im Laufe der Zeit schrittweise auf die berechneten Werte. Die Geschwindigkeit dieser Änderung wird durch die Eigenschaft LerpTime der jeweiligen Transformationskomponente bestimmt. Ein höherer MoveLerpTime-Wert führt beispielsweise zu langsameren Inkrementen bei der Bewegung zwischen Frames.

Wenn MaintainScale aktiviert ist, verwendet der Solver die lokale Standardskalierung des GameObject.

Orbital

Die Orbital-Klasse ist eine Komponente mit Folgeverhalten, die sich wie Planeten in einem Sonnensystem verhält. Dieser Solver stellt sicher, dass das angefügte GameObject die verfolgte Transformation umkreist. Wenn also der Tracked Target Type (Typ des verfolgten Ziels) des SolverHandler auf Head festgelegt ist, umkreist das GameObject den Kopf des Benutzers mit einem festen Offset.

Entwickler können diesen festen Offset ändern, um Menüs oder andere Szenenkomponenten auf Augen- oder Hüfthöhe usw. um einen Benutzer herum zu halten. Dies erfolgt durch Ändern der Eigenschaften Local Offset (Lokaler Offset) und World Offset (Weltoffset). Die Eigenschaft Orientation Type (Ausrichtungstyp) bestimmt bei auf das Objekt angewendeter Drehung, ob es seine ursprüngliche Drehung beibehalten oder immer der Kamera oder dem Gesicht zugewandt sein soll, egal welche Transformation seine Position bestimmt.

RadialView

RadialView ist eine weitere Komponente mit Folgeverhalten, die einen bestimmten Teil eines GameObject im Frustum des Sichtfelds des Benutzers hält.

Die Eigenschaften Min & Max View Degrees (Mindest-/Maximalsichtgrad) bestimmen die Größe des GameObject.-Anteils, der immer im Sichtfeld sein muss.

Die Eigenschaften Min & Max Distance (Mindest-/Maximalabstand) bestimmen, wie weit das GameObject vom Benutzer entfernt bleiben soll. Wenn Sie beispielsweise mit einer Min Distance (Mindestabstand) von 1 m auf das GameObject zugehen, wird das GameObject weggestoßen, um sicherzustellen, dass es dem Benutzer nie näher als 1 m kommt.

Im Allgemeinen wird die RadialView in Verbindung mit dem auf Head festgelegten Tracked Target Type (Typ des verfolgten Ziels) verwendet, damit die Komponente dem Anvisieren des Benutzers folgt. Diese Komponente kann jedoch so funktionieren, dass sie immer im "Sichtfeld" jedes Tracked Target Type (Typ des verfolgten Ziels) gehalten wird.

Follow

Die Follow-Klasse positioniert ein Element vor dem verfolgten Ziel, relativ zu seiner lokalen Vorwärtsachse. Das Element kann lose eingeschränkt werden (auch als „tag-along“ bezeichnet), sodass es erst folgt, wenn das nachverfolgte Ziel die benutzerdefinierten Grenzen überschreitet.

Es funktioniert ähnlich wie der RadialView-Solver mit zusätzlichen Steuerelementen zum Verwalten von Max Horizontal & Vertical View Degrees (Max. horizontaler/vertikaler Sichtgrad) und Mechanismen zum Ändern der Ausrichtung des Objekts.

Follow-Beispielszene (Assets/MRTK/Examples/Demos/Solvers/Scenes/FollowSolverExample.unity)

InBetween

Die InBetween-Klasse hält das angefügte GameObject zwischen zwei Transformationen. Diese zwei Transformationsendpunkte werden durch den eigenen SolverHandlerTracked Target Type (Typ des verfolgten Ziels) des GameObject und die Eigenschaft Second Tracked Target Type (Zweiter Typ des verfolgten Ziels) der InBetween-Komponente definiert. Im Allgemeinen werden beide Typen auf CustomOverride und die resultierenden SolverHandler.TransformOverride- und InBetween.SecondTransformOverride-Werte auf die zwei verfolgten Endpunkte festgelegt.

Zur Laufzeit erstellt die InBetween-Komponente eine weitere SolverHandler-Komponente, die auf den Eigenschaften Second Tracked Target Type (Zweiter Typ des verfolgten Ziels) und Second Transform Override (Zweite Transformationsaußerkraftsetzung) basiert.

Der PartwayOffset definiert, wo entlang der Linie zwischen zwei Transformationen das Objekt platziert wird, wobei 0,5 auf halber Strecke, 1,0 bei der ersten Transformation und 0,0 bei der zweiten Transformation ist.

SurfaceMagnetism

SurfaceMagnetism funktioniert durch Ausführen eines Raycasts gegen eine festgelegte LayerMask von Oberflächen und Platzieren des GameObjects an diesem Kontaktpunkt.

Der Surface Normal Offset (Offset zur Oberflächennormale) platziert das GameObject in einem festgelegten Abstand in Metern von der Oberfläche in Richtung der Normalen am Punkt des Auftreffens auf der Oberfläche.

Umgekehrt platziert der Surface Ray Offset (Offset zum Oberflächenstrahl) das GameObject in einem festgelegten Abstand in Metern von der Oberfläche weg, aber in entgegengesetzter Richtung des ausgeführten Raycasts. Wenn der Raycast also das Anvisieren des Benutzers ist, bewegt sich das GameObject näher entlang der Linie zwischen dem Auftreffpunkt auf der Oberfläche und der Kamera.

Der Orientation Mode (Ausrichtungsmodus) bestimmt den Typ der Drehung, die in Bezug auf die Normale auf der Oberfläche angewendet werden soll.

  • None (Keine): Keine Drehung angewendet.
  • TrackedTarget (Verfolgtes Ziel): Das Objekt wird der verfolgten Transformation, die den Raycast steuert, zugewandt.
  • SurfaceNormal (Oberflächennormale): Das Objekt wird auf Grundlage der Normalen am Punkt des Auftreffens auf der Oberfläche ausgerichtet.
  • Blended (Gemischt): Das Objekt wird basierend auf der Normalen am Punkt des Auftreffens auf der Oberfläche UND der verfolgten Transformation zugewandt ausgerichtet.

Um zu erzwingen, dass das zugeordnete GameObject in jedem anderen Modus als None vertikal bleibt, aktivieren Sie Keep Orientation Vertical (Ausrichtung vertikal halten).

Hinweis

Verwenden Sie die Eigenschaft Orientation Blend (Gemischte Ausrichtung), um das Gleichgewicht zwischen Drehfaktoren zu steuern, wenn der Orientation Mode (Ausrichtungsmodus) auf Blended (Gemischt) festgelegt ist. Beim Wert 0,0 wird die Ausrichtung vollständig vom TrackedTarget-Modus gesteuert, und beim Wert 1,0 wird die Ausrichtung vollständig von SurfaceNormal gesteuert.

Bestimmen, welche Oberflächen erreicht werden können

Beim Hinzufügen einer SurfaceMagnetism-Komponente zu einem GameObject ist es wichtig, die Ebene des GameObject und seine untergeordneten Elemente zu berücksichtigen, sofern diese Collider aufweisen. Die Komponente funktioniert so, dass sie verschiedene Raycasts ausführt, um zu bestimmen, an welcher Oberfläche sie sich selbst „magnetisch anheften“ soll. Angenommen, das GameObject des Solvers weist einen Collider auf einer der in der Eigenschaft MagneticSurfaces von SurfaceMagnetism aufgelisteten Ebenen auf. In diesem Fall trifft der Raycast wahrscheinlich auf sich selbst, was dazu führt, dass das GameObject an seinen eigenen Kollisionspunkt angefügt wird. Dieses seltsame Verhalten kann vermieden werden, indem das Haupt-GameObject und alle seine untergeordneten Elemente auf die Ebene Ignore Ray cast (Raycast ignorieren) festgelegt werden oder das MagneticSurfaces-LayerMask-Array entsprechend geändert wird.

Umgekehrt wird ein SurfaceMagnetism GameObject nicht mit Oberflächen auf einer Ebene kollidieren, die nicht in der MagneticSurfaces-Eigenschaft aufgeführt ist. Wir raten dazu, dass Sie alle gewünschten Oberflächen auf einer dedizierten Ebene platzieren (d. h. Oberflächen) und die MagneticSurfaces-Eigenschaft auf eben diese Ebene festlegen. Die Verwendung von default (Standard) oder everything (alles) kann dazu führen, dass Benutzeroberflächenkomponenten oder Cursor zum Solver beitragen.

Schließlich werden Oberflächen, die weiter als die Einstellung der Eigenschaft MaxRaycastDistance entfernt liegen, von den SurfaceMagnetism-Raycasts ignoriert.

DirectionalIndicator

Die DirectionalIndicator-Klasse ist eine Komponente mit Folgeverhalten, die sich selbst an der Richtung des gewünschten Punkts im Raum orientiert. Wird am häufigsten verwendet, wenn der Typ des verfolgten Ziels des SolverHandler auf Head festgelegt ist. Auf diese Weise weist eine UX-Komponente einen Benutzer mit dem DirectionalIndicator-Solver an, auf den gewünschten Punkt im Raum zu sehen. Dieser Punkt wird durch die Eigenschaft Richtungsweisendes Ziel bestimmt.

Wenn das gerichtete Ziel für den Benutzer sichtbar ist oder egal welcher Bezugsframe im SolverHandler festgelegt ist, deaktiviert dieser Solver alle darunter liegenden Renderer-Komponenten. Wenn es nicht sichtbar ist, wird alles für den Indikator aktiviert.

Der Indikator wird immer kleiner, je näher der Benutzer dem Erfassen des Directional Target in seinem Sichtfeld (FOV) kommt.

  • Min Indicator Scale (Minimale Indikatorskala): Die minimale Skalierung für das Indikatorobjekt.

  • Max Indicator Scale (Maximale Indikatorskala): Die maximale Skalierung für das Indikatorobjekt.

  • Skalierungsfaktor für Sichtbarkeit - Multiplikator zum Erhöhen oder Verringern des Sichtfelds, der bestimmt, ob der Punkt des Directional Target sichtbar ist oder nicht

  • View Offset (Sichtoffset): Vom Blickpunkt des Bezugsrahmens (d. h. möglicherweise Kamera) und in Indikatorrichtung definiert diese Eigenschaft, wie weit sich das Objekt von der Mitte des Viewports entfernt befindet.

Directional Indicator-Beispielszene (Richtungsindikator) (Assets/MRTK/Examples/Demos/Solvers/Scenes/DirectionalIndicatorSolverExample.unity)

Handmenü mit HandConstraint und HandConstraintPalmUp

Das HandConstraint-Verhalten bietet einen Solver, der das nachverfolgte Objekt auf einen Bereich beschränkt, der für auf die Hände eingeschränkte Inhalte (z. B. Handbenutzeroberfläche, Menüs usw.) sicher ist. Sichere Regionen gelten als Bereiche, die sich nicht mit der Hand überschneiden. Eine abgeleitete Klasse von HandConstraint namens HandConstraintPalmUp ist ebenfalls enthalten, um ein allgemeines Verhalten der Aktivierung des vom Solver verfolgten Objekts zu veranschaulichen, wenn die Handfläche dem Benutzer zugewandt ist.

In der Dokumentation zum Handmenü finden Sie Beispiele für die Verwendung des Handeinschränkungs-Solvers zum Erstellen von Handmenüs.