Skip to main content
Version : la plus récente

Retour de force basé sur la physique [Expérimental]

Les exemples précédents rendaient des objets primitifs tels qu'un plan ou une sphère où les calculs de retour de force étaient codés à la main en raison de leur simplicité. étaient codés à la main en raison de leur simplicité. Au fur et à mesure que les objets deviennent plus complexes, les calculs manuels deviennent encombrants et compliqués. les calculs manuels deviennent lourds et compliqués. Les moteurs physiques tels que SOFA, IMSTK et CHAI3D sont fortement optimisés pour les applications haptiques, ce qui leur permet de s'exécuter à des fréquences supérieures à 1000 Hz créant ainsi des simulations haptiques réalistes, mais ils nécessitent une intégration dans Unity. Unity dispose d'un moteur physique intégré qui peut être utilisé pour des simulations haptiques réalistes. intégré qui peut être utilisé pour des simulations simples. Cet article montre comment construire une scène haptique et incorporer le moteur physique dans Unity. et d'incorporer le moteur physique dans une simulation haptique en utilisant des objets cinématiques et non cinématiques. cinématiques et non cinématiques.

Introduction

Unity dispose de deux types de RigidBodies : les objets cinématiques et les objets non cinématiques. Les objets cinématiques sont contrôlés par des comme le curseur dans les exemples précédents. En revanche, les corps non cinématiques sont contrôlés par les collisions et les forces qui en résultent. les collisions et les forces qui en résultent. La difficulté réside dans l'échange de données de force entre l'utilisateur et l'objet non cinématique. entre l'utilisateur et l'objet non cinématique, car ni la force d'entrée ni la force de sortie ne peuvent être mesurées directement. directement.

Pour contourner ce problème, nous utilisons ConfigurableJoints pour créer un couplage virtuel qui relie un objet cinématique et un objet non cinématique à l'aide d'une liaison virtuelle composée d'un ressort et d'un amortisseur. objet cinématique et un objet non cinématique avec une liaison virtuelle composée d'un ressort et d'un amortisseur. En effet, lorsqu'un objet non cinématique entre en collision avec un autre objet non cinématique, son mouvement est entravé, mais l'objet cinématique ne peut pas bouger. l'empêchera de se déplacer, mais l'objet cinématique continuera à se déplacer, ce qui tendra le ressort et créera une force. créant ainsi une force. Nous pouvons utiliser la force résultante directement pour effectuer le rendu des objets.

Configuration de la scène

La mise en œuvre de cette méthode repose sur l'utilisation de deux objets en tandem :

  • L'objet Curseur (cinématique), qui correspond à la position du curseur de votre appareil.
  • Le PhysicEffector (non cinématique), qui sera lié à l'objet Cursor par l'intermédiaire d'une articulation fixe.

La force rendue par votre appareil sera relative à la distance entre ces deux objets, de telle sorte que lorsque l'objet PhysicsEffector est bloqué par un autre objet de la scène, une force opposée sera appliquée. que lorsque l'objet PhysicsEffector est bloqué par un autre objet de la scène, une force opposée proportionnelle à la distance de l'objet Cursor sera générée. force opposée proportionnelle à la distance de l'objet Curseur sera générée.

  • Ajoutez un fil et un curseur haptique comme indiqué dans le guide de démarrage rapide.
  • Créez un espace de travail comme indiqué dans la section Mise à l'échelle et placement de l'espace de travail.
  • Créez une sphère appelée Physics Effector dans l'espace de travail haptique avec les mêmes valeurs de transformation que le curseur. les mêmes valeurs de transformation que le curseur.
  • Ajoutez divers objets 3D avec des collisionneurs dans la scène.
  • Ajouter un Cube avec un corps rigide, sa gravité activée et une masse de 1000.

scène

optionnel : vous pouvez régler le SpatialMappingWideframe matériel sur PhysicEffector pour voir comment la sphère tourne lorsque vous la déplacez sur la surface en raison du frottement. frottement. tapis à cadre large

Boucle haptique physique simple

Ajouter un nouveau Script C# appelé SimplePhysicsHapticEffector.cs à la PhysicEffector l'objet du jeu. La source de ce script est donnée ci-dessous.

using Haply.HardwareAPI.Unity;
using UnityEngine;

public class SimplePhysicsHapticEffector : MonoBehaviour
{
// Thread safe scene data
private struct AdditionalData
{
public Vector3 physicEffectorPosition;
}

public bool forceEnabled;
[Range(0, 800)]
public float stiffness = 400f;
[Range(0, 3)]
public float damping = 1;

private HapticThread m_hapticThread;

private void Awake ()
{
// Find the HapticThread object before the first FixedUpdate() call.
m_hapticThread = FindObjectOfType<HapticThread>();

// Create the physics link between the physic effector and the device cursor
AttachCursor( m_hapticThread.avatar.gameObject );
}

private void OnEnable ()
{
// Run haptic loop with AdditionalData method to get initial values
if (m_hapticThread.isInitialized)
m_hapticThread.Run(ForceCalculation, GetAdditionalData());
else
m_hapticThread.onInitialized.AddListener(() => m_hapticThread.Run(ForceCalculation, GetAdditionalData()) );
}

private void FixedUpdate () =>
// Update AdditionalData
m_hapticThread.SetAdditionalData( GetAdditionalData() );

// Attach the current physics effector to the device end-effector with a fixed joint
private void AttachCursor (GameObject cursor)
{
// Add a kinematic rigidbody to the cursor.
var rbCursor = cursor.GetComponent<Rigidbody>();
if ( !rbCursor )
{
rbCursor = cursor.AddComponent<Rigidbody>();
rbCursor.useGravity = false;
rbCursor.isKinematic = true;
}

// Add a non-kinematic rigidbody to self
if ( !gameObject.GetComponent<Rigidbody>() )
{
var rb = gameObject.AddComponent<Rigidbody>();
rb.useGravity = false;
}

// Connect self to the cursor rigidbody
if ( !gameObject.GetComponent<FixedJoint>() )
{
var joint = gameObject.AddComponent<FixedJoint>();
joint.connectedBody = rbCursor;
}
}

// Method used by HapticThread.Run(ForceCalculation) and HapticThread.GetAdditionalData()
// to synchronize the physic effector position information between the physics thread and the haptic thread.
private AdditionalData GetAdditionalData ()
{
AdditionalData additionalData;
additionalData.physicEffectorPosition = transform.localPosition;
return additionalData;
}

// Calculate the force to apply based on the distance between the two effectors.
private Vector3 ForceCalculation ( in Vector3 position, in Vector3 velocity, in AdditionalData additionalData )
{
if ( !forceEnabled )
{
return Vector3.zero;
}
var force = additionalData.physicEffectorPosition - position;
force *= stiffness;
force -= velocity * damping;
return force;
}
}

Avec cette configuration, vous devriez être en mesure de sentir chaque objet dans la scène, les objets plus lourds offrant plus de résistance. résistance.

Effecteur physique simple

Problèmes :

  • La sensation de friction/traînée est causée par la différence de fréquence de mise à jour entre le moteur physique de Unity (entre 60Hz et 120Hz) et le moteur haptique. entre le moteur physique de Unity (entre 60Hz et 120Hz) et le fil haptique (~1000Hz). (~1000Hz). Cette différence signifie que l'effecteur physique sera toujours retard par rapport à la position réelle du curseur, ce qui conduit à des forces qui ressemblent à une fonction en escalier au lieu d'être continues.
  • Pas de véritable haptique sur les objets en mouvement.

Solutions :

  • Diminuer la valeur de ProjectSettings.FixedTimestep aussi proche que possible de 0.001 que possible. Ce changement aura un impact significatif sur les performances des scènes complexes. complexes.
  • N'appliquer les forces qu'en cas de collision (voir l'exemple suivant).
  • Utiliser un moteur physique/haptique tiers comme (TOIA, SOFA, etc...) en tant qu'intergiciel entre le moteur physique d'unity et la boucle haptique pour simuler l'effet de la boucle haptique. intermédiaire entre le moteur physique de unity et la boucle haptique pour simuler les points de contact à une fréquence plus élevée. points de contact à une fréquence plus élevée.

Boucle haptique physique plus avancée

Dans cet exemple, nous allons :

  • Utiliser la sortie de détection des collisions pour éviter la sensation de frottement/traînée lorsque l'effecteur n'est pas en contact avec un objet.

  • Une articulation configurable avec une limite, un ressort et un amortisseur comme lien entre les deux effecteurs au lieu d'une articulation fixe. deux effecteurs au lieu d'un FixedJoint. Cela nous permettra d'utiliser les PhysicsMaterials de Unity pour définir différentes valeurs de friction sur les objets de la scène. scène et de sentir la masse d'un objet mobile à travers le retour de force.

Sur le site Effecteur physique remplacer l'objet de jeu SimplifiedPhysicsHapticsEffector (Effecteur haptique simplifié) par un nouveau composant de script Script C# appelé AdvancedPhysicsHapticEffector.cs

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

public class AdvancedPhysicsHapticEffector : MonoBehaviour
{
/// Thread-safe scene data
private struct AdditionalData
{
public Vector3 physicEffectorPosition;
public bool isTouching;
}

public bool forceEnabled;

[Range(0, 800)]
public float stiffness = 400f;
[Range(0, 3)]
public float damping = 1;

private HapticThread m_hapticThread;

// Apply forces only when we're colliding with an object which prevents feeling
// friction/drag while moving through the air.
public bool collisionDetection = true;

private List<Collider> m_Touched = new();

private void Awake ()
{
// Find the HapticThread object before the first FixedUpdate() call.
m_hapticThread = FindObjectOfType<HapticThread>();

// Create the physics link between the physic effector and the device cursor
AttachCursor( m_hapticThread.avatar.gameObject );
}

private void OnEnable ()
{
// Run haptic loop with AdditionalData method to get initial values
if (m_hapticThread.isInitialized)
m_hapticThread.Run(ForceCalculation, GetAdditionalData());
else
m_hapticThread.onInitialized.AddListener(() => m_hapticThread.Run(ForceCalculation, GetAdditionalData()) );
}

private void FixedUpdate () =>
// Update AdditionalData with the latest physics data.
m_hapticThread.SetAdditionalData( GetAdditionalData() );

/// Attach the current physics effector to the device end-effector with a joint
private void AttachCursor (GameObject cursor)
{
// Add a kinematic rigidbody to the cursor.
var rbCursor = cursor.AddComponent<Rigidbody>();
rbCursor.useGravity = false;
rbCursor.isKinematic = true;

// Add a non-kinematic rigidbody to self.
var rb = gameObject.AddComponent<Rigidbody>();
rb.useGravity = false;
rb.drag = 80f; // stabilize spring connection

// Connect self with the cursor rigidbody via a spring/damper joint and a locked rotation.
var joint = gameObject.AddComponent<ConfigurableJoint>();
joint.connectedBody = rbCursor;
joint.anchor = joint.connectedAnchor = Vector3.zero;
joint.axis = joint.secondaryAxis = Vector3.zero;

// Limit linear movements.
joint.xMotion = joint.yMotion = joint.zMotion = ConfigurableJointMotion.Limited;

// Configure the limit, spring and damper
joint.linearLimit = new SoftJointLimit()
{
limit = 0.001f
};
joint.linearLimitSpring = new SoftJointLimitSpring()
{
spring = 500000f,
damper = 10000f
};

// Lock the rotation to prevent the sphere from rolling due to friction with the material which will
// improve the force-feedback feeling.
joint.angularXMotion = joint.angularYMotion = joint.angularZMotion = ConfigurableJointMotion.Locked;


// Set the first collider which handles collisions with other game objects.
var sphereCollider = gameObject.GetComponents<SphereCollider>();
sphereCollider.material = new PhysicMaterial {
dynamicFriction = 0,
staticFriction = 0
};


// Set the second collider as a trigger that is a bit larger than our first collider. It will be used to
// detect when our effector is moving away from an object it was touching.
var trigger = gameObject.AddComponent<SphereCollider>();
trigger.isTrigger = true;
trigger.radius = sphereCollider.radius * 1.08f;
}

// Method used by HapticThread.Run(ForceCalculation) and HapticThread.GetAdditionalData()
// to synchronize the physic effector position information between the physics thread and the haptic thread
private AdditionalData GetAdditionalData ()
{
AdditionalData additionalData;
additionalData.physicEffectorPosition = transform.localPosition;
additionalData.isTouching = collisionDetection && m_Touched.Count > 0;
return additionalData;
}

// Calculate the force to apply based on the distance between the two effectors
private Vector3 ForceCalculation ( in Vector3 position, in AdditionalData additionalData )
{
if ( !forceEnabled || (collisionDetection && !additionalData.isTouching) )
{
// Don't compute forces if there are no collisions which prevents feeling drag/friction while moving through air.
return Vector3.zero;
}
var force = additionalData.physicEffectorPosition - position;
force *= stiffness;
return force;
}

private void OnCollisionEnter ( Collision collision )
{
if ( forceEnabled && collisionDetection && !m_Touched.Contains( collision.collider ) )
{
// Store the object that our effector is touching.
m_Touched.Add( collision.collider );
}
}

private void OnTriggerExit ( Collider other )
{
if ( forceEnabled && collisionDetection && m_Touched.Contains( other ) )
{
// Remove the object when our effector moves away from it.
m_Touched.Remove( other );
}
}

}

Fichiers sources

La scène finale et tous les fichiers associés utilisés dans cet exemple peuvent être importés à partir du gestionnaire de paquets Unity de Unity.

Caractéristiques supplémentaires :

  • Choisissez entre l'effecteur physique simplifié et l'effecteur physique avancé en appuyant sur la touche 1 ou 2 clé.
  • Contrôler les Taux de rafraîchissement de l'haptique avec le LEFT/RIGHT clés.
  • Contrôler les Taux de rafraîchissement de la physique avec le UP/DOWN clés.
  • Toggle Détection des collisions avec le C clé.
  • Toggle Retour d'effort avec le SPACE clé.
  • Une scène préparée avec des objets statiques et dynamiques ayant des massesdifférentes et PhysicsMaterials.
  • Une interface utilisateur pour afficher les propriétés des objets touchés (friction statique/dynamique, masse, traînée...)

Effecteur physique et interface utilisateur avancés