Skip to main content
Version : la plus récente

Tutoriel sur la navigation dans l'espace de travail

Ce tutoriel montre comment ajuster l'échelle du curseur à l'aide de la poignée, ce qui permet à l'utilisateur de contrôler la précision et la taille de la construction de l'espace de travail navigable. sur la précision et la taille de la construction de l'espace de travail navigable.

Cet exemple s'appuie sur la scène créée dans la section Mise à l'échelle et placement de l'espace de travail en fournissant un contrôle basé sur les poignées sur l'espace de travail haptique. sur l'espace de travail haptique. L'objectif est d'utiliser le bouton de la poignée pour déclencher le décalage de l'espace de travail et d'utiliser la position du curseur pour le modifier, et le second pour déclencher le redimensionnement de l'espace de travail et utiliser l'enroulement de la poignée pour le modifier. l'espace de travail et d'utiliser l'enroulement de la poignée pour le modifier.

À cette fin, lorsque l'utilisateur appuie sur un bouton, le script WorkspaceOffsetController enregistre la dernière position connue du curseur et cesse de mettre à jour l'emplacement de l'avatar. position connue du curseur et arrête la mise à jour de l'emplacement de l'avatar. Alternativement, le WorkspaceScaleController enregistre l'échelle de l'espace de travail et l'orientation actuelle de la poignée lorsqu'un bouton est enfoncé. Grâce à l'utilisation conjointe des deux scripts, le déplacement du curseur créera un nouveau décalage du curseur, tandis que la rotation de la poignée créera un nouveau décalage du curseur. tandis que la rotation de la poignée modifie l'échelle de l'espace de travail. Dans cette implémentation, une rotation CCW autour de l'axe Z correspond à une réduction de l'échelle de l'espace de travail et vice versa. Lorsque l'utilisateur relâche le bouton, les deux décalages cessent de changer et l'avatar du curseur se déplace à nouveau. une fois de plus.

Configuration de la scène

Commencez par ajouter le composant HandleTread au GameObject de l 'espace de travail haptique. Dans la vue inspecteur, définissez le curseur comme HandleThread Avatar, puis ajoutez un cube comme enfant du curseur pour visualiser sa rotation (voir le Guide de démarrage rapide pour plus de détails). pour visualiser sa rotation (voir le Guide de démarrage rapide pour plus de détails).

filetage de la poignée

Le composant WorkspaceOffsetController

Créer un nouveau script WorkspaceOffsetController.cs et l'ajouter au Espace de travail haptique GameObject. Ajoutez ensuite une référence au curseur contrôlé par la fonction Fil haptique

    private Transform m_cursor;

private void Awake()
{
m_cursor = GetComponent<HapticThread>().avatar;
}

Les propriétés suivantes s'appliquent alors :

private Vector3 m_basePosition;
private Vector3 m_cursorBasePosition;

m_basePosition sauvegarde la position de l'espace de travail avant toute modification, tandis que m_cursorBasePosition enregistre la position du curseur.

Ensuite, ajoutez l'élément active champ encapsulé correspondant à l'état du bouton qui initialise les champs précédents et permet le décalage de l'espace de travail à chaque mise à jour. champs précédents et active le décalage de l'espace de travail à chaque mise à jour :

public bool active
{
get => m_active;
set
{
if (value)
{
m_basePosition = transform.localPosition;
m_cursorBasePosition = m_cursor.localPosition;
}
m_active = value;
}
}
private bool m_active;

Enfin, ajouter Update qui, lorsqu'elle est active, modifie le décalage de l'espace de travail en calculant le changement de la position du curseur par rapport à la position de base. position du curseur par rapport à la position de base. Notez que le changement de position doit être mis à l'échelle par la transformation de l'espace de travail pour rester précis. la transformation de l'espace de travail pour rester précis.

private void Update()
{
if (active)
{
// Move cursor offset relative to cursor position
transform.position = m_basePosition - Vector3.Scale(m_cursor.localPosition - m_cursorBasePosition, transform.lossyScale);
}
}

Pour lier le bouton de la poignée au champ actif, sélectionnez l'option Espace de travail haptique et dans le panneau de l'inspecteur ajouter (Espace de travail haptique)WorkspaceOffsetController.active = false à OnButtonUp() et (Espace de travail haptique)WorkspaceOffsetController.active = true à OnButtonDown().

événements de gestion du décalage de l'espace de travail

Optionnel : Lier le mouvement de la caméra

La modification du décalage du curseur peut être rendue intuitive en déplaçant la caméra le long de l'espace de travail. Ainsi, même si le décalage est important, le curseur ne sortira jamais du cadre. Ainsi, même si le décalage est important, le curseur ne sortira jamais du cadre.

pas de contrainte de caméra avec la contrainte de la caméra

Pour ce faire, ajoutez un composant de contrainte de position à la caméra principale, définissez le paramètre * Espace de travail haptique* comme source, et appuyez sur le bouton Activer.

contrainte de la caméra

Le composant WorkspaceScaleController

Créer un nouveau script WorkspaceScaleController.cs et l'ajouter au Espace de travail haptique GameObject. Ajoutez ensuite une référence au curseur contrôlé par la fonction HandleThread

private Transform m_cursor;

private void Awake()
{
m_cursor = GetComponent<HandleThread>().avatar;
}

Ajoutez ensuite les paramètres suivants :

public float scalingFactor = 0.25f;
public float minimumScale = 1f;
public float maximumScale = 20f;

contrainte de la caméra

scalingFactor convertit la rotation de la poignée en degrés en une échelle numérique qui est délimitée par l'échelle des degrés. par la minimumScale et le maximumScale.

Ajoutez ensuite les champs suivants :

private float m_baseScale;
private float m_cursorBaseAngle;

m_baseScale sauvegarde l'échelle du paysage avant toute modification, tandis que m_cursorBaseAngle enregistre l'orientation du curseur sur l'axe Y.

Ajouter ensuite GetTotalDegrees méthode :

private float m_cursorPreviousAngle;
private int m_rotationCount;

private float GetTotalDegrees(float currentAngle, float baseAngle)
{
if (currentAngle - m_cursorPreviousAngle > 330)
m_rotationCount--;
else if (m_cursorPreviousAngle - currentAngle > 330)
m_rotationCount++;

m_cursorPreviousAngle = currentAngle;

return 360f * m_rotationCount + (currentAngle - baseAngle);
}

La poignée peut tourner plus d'une fois autour de son propre axe, ce qui peut entraîner des sauts soudains dans le déplacement lors du franchissement de 0°. déplacement lors du franchissement de 0°. Cette fonction vérifie l'angle actuel par rapport à l'angle précédent et détecte quand 0° a été franchi et dans quelle direction, ce qui garantit que le décalage d'angle renvoyé est exact, même si plus d'une rotation complète a eu lieu. est exact, même si plus d'une rotation complète a eu lieu.

Ensuite, le active champ encapsulé, correspondant à l'état du bouton, qui initialise les champs précédents et permet la mise à l'échelle de l'espace de travail à chaque mise à jour. et active la mise à l'échelle de l'espace de travail à chaque mise à jour :

public bool active
{
get => m_active;
set
{
if (value)
{
m_rotationCount = 0;
m_baseScale = transform.localScale.z;
m_cursorPreviousAngle = m_cursorBaseAngle = m_cursor.localEulerAngles.z;

}
m_active = value;
}
}
private bool m_active;

Enfin, ajouter Update qui, s'il est actif, modifie les transformées de l'élément Espace de travail haptique en fonction de l'angle de la poignée.

private void Update()
{
if (active)
{
// Calculate scale relative to cursor roll on Z-axis rotation
var totalDegrees = GetTotalDegrees(m_cursor.localEulerAngles.z, m_cursorBaseAngle);
var scale = m_baseScale - totalDegrees * scalingFactor / 100f;

// Limit between minimumScale and maximumScale
scale = Mathf.Clamp(scale, minimumScale, maximumScale);

// Set cursor offset scale (same on each axis)
transform.localScale = Vector3.one * scale;
// Invert cursor scale to keep its original size
m_cursor.localScale = Vector3.one / scale;
}
}

Bouton de la poignée de reliure

Comme précédemment, pour lier le bouton de la poignée à l'icône active sélectionner le champ Espace de travail haptique et dans le dans le panneau de l'inspecteur, ajoutez (Espace de travail haptique)WorkspaceScaleController.active = false à OnButtonUp() et (Espace de travail haptique)WorkspaceScaleController.active = true à OnButtonDown().

la gestion des événements de l'échelle de l'espace de travail

Résultat

Vous pouvez maintenant naviguer facilement dans la scène en mettant à l'échelle et en déplaçant l'espace de travail en appuyant sur le bouton Handle en appuyant sur le bouton "Handle".

la gestion des événements de l'échelle de l'espace de travail

Fichiers sources

La scène finale et tous les fichiers associés utilisés dans cet exemple peuvent être importés depuis l'échantillon Basic Force Feedback and Workspace Control dans le gestionnaire de paquets de Unity. L'exemple Unity contient des améliorations de la qualité de vie qui sortent du cadre de ce tutoriel :

  • Une bulle transparente visualisant la taille de l'espace de travail
  • Raccourcis clavier
    • Appuyer sur M La touche permet de déplacer l'espace de travail uniquement
    • Appuyer sur S La touche permet de mettre l'espace de travail à l'échelle uniquement
  • Une interface utilisateur pour afficher les valeurs actuelles de décalage et d'échelle

WorkspaceOffsetController.cs

using Haply.HardwareAPI.Unity;
using UnityEngine;

public class WorkspaceOffsetController : MonoBehaviour
{
// Movable cursor with position controlled by Haptic Thread
private Transform m_cursor;

// Saved workspace and cursor values at transformation beginning
private Vector3 m_basePosition;
private Vector3 m_cursorBasePosition;

// If true, the workspace offset is set relatively to the cursor position on each Update() loop
public bool active
{
get => m_active;
set
{
if (value)
{
m_basePosition = transform.localPosition;
m_cursorBasePosition = m_cursor.localPosition;
}
m_active = value;
}
}

private bool m_active;

private void Awake()
{
// Get the moving cursor from the HapticThread
m_cursor = GetComponent<HapticThread>().avatar;
}

private void Update()
{
if (active)
{
// Update the workspace offset relative to cursor position
transform.position = m_basePosition - Vector3.Scale(m_cursor.localPosition - m_cursorBasePosition, transform.lossyScale);
}
}
}

WorkspaceScaleController.cs

using System;
using Haply.HardwareAPI.Unity;
using UnityEngine;

public class WorkspaceScaleController : MonoBehaviour
{
// Movable cursor with rotation controlled by Handle Thread
private Transform m_cursor;

[Tooltip("Sensitivity of scaling on handle rotation")]
public float scalingFactor = 3f;
public float minimumScale = 1f;
public float maximumScale = 5f;

// Saved workspace and cursor values at transformation beginning
private float m_baseScale;
private float m_cursorBaseAngle;
private float m_cursorPreviousAngle;
private int m_rotationCount;

// If enabled the workspace will be uniformly scaled relatively to cursor roll (Z-axis rotation) on each Update() loop
public bool active
{
get => m_active;
set
{
if (value)
{
m_rotationCount = 0;
m_baseScale = transform.localScale.z;
m_cursorPreviousAngle = m_cursorBaseAngle = m_cursor.localEulerAngles.z;
}
m_active = value;
}
}
private bool m_active;

private void Awake()
{
// Get the rotating cursor from the HandleThread
m_cursor = GetComponent<HandleThread>().avatar;
}

private void Update()
{
if (active)
{
// Calculate scale relative to cursor roll on Z-axis rotation
var totalDegrees = GetTotalDegrees(m_cursor.localEulerAngles.z, m_cursorBaseAngle);
var scale = m_baseScale - totalDegrees * scalingFactor / 100f;

// Limit between minimumScale and maximumScale
scale = Mathf.Clamp(scale, minimumScale, maximumScale);

// Set cursor offset scale (same on each axis)
transform.localScale = Vector3.one * scale;
// Invert cursor scale to keep its original size
m_cursor.localScale = Vector3.one / scale;
}
}

// Return the total degrees between baseAngle and currentAngle over the 360 degrees limitation
private float GetTotalDegrees(float currentAngle, float baseAngle)
{
if (currentAngle - m_cursorPreviousAngle > 330)
m_rotationCount--;
else if (m_cursorPreviousAngle - currentAngle > 330)
m_rotationCount++;

m_cursorPreviousAngle = currentAngle;

return 360f * m_rotationCount + (currentAngle - baseAngle);
}
}