Zum Hauptinhalt springen

Einführung in die Force-Feedback-Technologie

Dieses Tutorial führt Sie durch die Erstellung einer grundlegenden haptischen Simulation, die sowohl Steifigkeit als auch Dämpfung berücksichtigt, um die physikalischen Eigenschaften des Kontakts mit einem statischen Objekt, wie beispielsweise einer Kugel, zu simulieren. Am Ende dieses Tutorials verfügen Sie über eine Simulation, mit der Sie die Präsenz einer Kugel spüren und deren Steifigkeits- und Dämpfungseigenschaften für verschiedene haptische Experimente anpassen können.

Einführung​

Die größte Herausforderung dieses Tutorials besteht darin, eine Funktion zu entwickeln, mit der sich die Kräfte berechnen lassen, die beim Kontakt mit einer Kugel entstehen, die sowohl eine Steifigkeit als auch eine Dämpfung aufweist. In diesem Zusammenhang wirkt die Steifigkeit wie eine Feder: Sie erzeugt eine Kraft, die umso größer ist, je stärker das Objekt zusammengedrückt wird. Die Dämpfung hingegen stellt den Bewegungswiderstand eines Objekts dar: Sie leistet umso mehr Widerstand, je schneller das Objekt bewegt wird.

Einrichtung der Szene​

Richten Sie zunächst ein Haptic Rig ein, wie in der Anleitung „Erste Schritte“, wobei darauf zu achten ist, dass die Position, die Drehung und die Skalierung von Haptischer Ursprung so seien sie jeweils definiert als (0, 0, 0) et (1, 1, 1).

Erstellen Sie anschließend eine Kugel mit dem Namen Sphere mit folgenden Eigenschaften:

  • Position : (0, 0, -0.1) (etwa 10 cm vor der Kamera)
  • Maßstab: (0.2, 0.2, 0.2) (entspricht einer Kugel mit einem Durchmesser von 20 cm)

Skript zur erzwungenen Rückgabe​

Fügen Sie dem GameObject ein neues C#-Skript hinzu Sphäre ernannt SphereForceFeedback.cs. Dieses Skript berechnet die Kräfte, die beim Kontakt mit der Kugel auf den Cursor „ Inverse3 “ wirken, wobei sowohl die Steifigkeit als auch die Dämpfung berücksichtigt werden. Initialisieren Sie das Skript mit den folgenden Eigenschaften:

[SerializeField]
private Inverse3 inverse3;

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

private Vector3 _ballPosition;
private float _ballRadius;
private float _cursorRadius;

Die Kraftberechnung darf erst erfolgen, wenn der Cursor in die Kugel eindringt, um das Gefühl zu simulieren, einen physischen Objekt mit definierter Steifigkeit und Dämpfung zu berühren. Die Methode ForceCalculation wird diese Aufgabe unter Berücksichtigung sowohl der Position als auch der Geschwindigkeit des Cursors übernehmen:

private Vector3 ForceCalculation(Vector3 cursorPosition, Vector3 cursorVelocity, float cursorRadius,
Vector3 otherPosition, float otherRadius)
{
var force = Vector3.zero;

var distanceVector = cursorPosition - otherPosition;
var distance = distanceVector.magnitude;
var penetration = otherRadius + cursorRadius - distance;

if (penetration > 0)
{
// Normalize the distance vector to get the direction of the force
var normal = distanceVector.normalized;

// Calculate the force based on penetration
force = normal * penetration * stiffness;

// Apply damping based on the cursor velocity
force -= cursorVelocity * damping;
}

return force;
}

private void OnDeviceStateChanged(object sender, Inverse3EventArgs args)
{
var inverse3 = args.DeviceController;
// Calculate the ball force
var force = ForceCalculation(inverse3.CursorLocalPosition, inverse3.CursorLocalVelocity,
_cursorRadius, _ballPosition, _ballRadius);

inverse3.SetCursorLocalForce(force);
}

In der Methode Awake, initialisieren Sie _ballPosition, _ballRadius et _cursorRadius So konfigurieren Sie die Szenendaten:

private void SaveSceneData()
{
var t = transform;
_ballPosition = t.position;
_ballRadius = t.lossyScale.x / 2f;

_cursorRadius = inverse3.Cursor.Radius;
}

private void Awake()
{
SaveSceneData();
}

Bitte denken Sie daran, die Erinnerung anzumelden und wieder abzumelden. OnDeviceStateChanged in den Methoden OnEnable et OnDisable, um die Rückkopplung während der Interaktion korrekt zu steuern:

protected void OnEnable()
{
inverse3.DeviceStateChanged += OnDeviceStateChanged;
}

protected void OnDisable()
{
inverse3.DeviceStateChanged -= OnDeviceStateChanged;
}

Spielerlebnis​

Halten Sie den Cursor auf „ Inverse3 “, wechseln Sie in den Play-Modus und versuchen Sie, die Kugel zu berühren. Sie sollten die Kugel wahrnehmen können und ihre Steifigkeits- und Dämpfungseigenschaften über den Unity-Inspector anpassen können, was ein greifbares Gefühl der Interaktion mit einem virtuellen Objekt vermittelt.

Cursor berührt die Kugel

Quelldateien​

Die komplette Szene und alle zu diesem Beispiel gehörenden Dateien können aus dem Beispiel „Tutorials“ im Unity-Paketmanager importiert werden.

SphereForceFeedback.cs​

/*
* Copyright 2024 Haply Robotics Inc. All rights reserved.
*/

using Haply.Inverse.DeviceControllers;
using Haply.Inverse.DeviceData;
using UnityEngine;

namespace Haply.Samples.Tutorials._2_BasicForceFeedback
{
public class SphereForceFeedback : MonoBehaviour
{
public Inverse3Controller inverse3;

[Range(0, 800)]
// Stiffness of the force feedback.
public float stiffness = 300f;

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

private Vector3 _ballPosition;
private float _ballRadius;
private float _cursorRadius;

/// <summary>
/// Stores the cursor and sphere transform data for access by the haptic thread.
/// </summary>
private void SaveSceneData()
{
var t = transform;
_ballPosition = t.position;
_ballRadius = t.lossyScale.x / 2f;

_cursorRadius = inverse3.Cursor.Radius;
}

/// <summary>
/// Saves the initial scene data cache.
/// </summary>
private void Awake()
{
inverse3 ??= FindObjectOfType<Inverse3Controller>();
inverse3.Ready.AddListener((device, args) => SaveSceneData());
}

/// <summary>
/// Subscribes to the DeviceStateChanged event.
/// </summary>
private void OnEnable()
{
inverse3.DeviceStateChanged += OnDeviceStateChanged;
}

/// <summary>
/// Unsubscribes from the DeviceStateChanged event.
/// </summary>
private void OnDisable()
{
inverse3.DeviceStateChanged -= OnDeviceStateChanged;
inverse3.Release();
}

/// <summary>
/// Calculates the force based on the cursor's position and another sphere position.
/// </summary>
/// <param name="cursorPosition">The position of the cursor.</param>
/// <param name="cursorVelocity">The velocity of the cursor.</param>
/// <param name="cursorRadius">The radius of the cursor.</param>
/// <param name="otherPosition">The position of the other sphere (e.g., ball).</param>
/// <param name="otherRadius">The radius of the other sphere.</param>
/// <returns>The calculated force vector.</returns>
private Vector3 ForceCalculation(Vector3 cursorPosition, Vector3 cursorVelocity, float cursorRadius,
Vector3 otherPosition, float otherRadius)
{
var force = Vector3.zero;

var distanceVector = cursorPosition - otherPosition;
var distance = distanceVector.magnitude;
var penetration = otherRadius + cursorRadius - distance;

if (penetration > 0)
{
// Normalize the distance vector to get the direction of the force
var normal = distanceVector.normalized;

// Calculate the force based on penetration
force = normal * penetration * stiffness;

// Apply damping based on the cursor velocity
force -= cursorVelocity * damping;
}

return force;
}

/// <summary>
/// Event handler that calculates and send the force to the device when the cursor's position changes.
/// </summary>
/// <param name="sender">The Inverse3 data object.</param>
/// <param name="args">The event arguments containing the device data.</param>
private void OnDeviceStateChanged(object sender, Inverse3EventArgs args)
{
var inverse3 = args.DeviceController;
// Calculate the ball force
var force = ForceCalculation(inverse3.CursorLocalPosition, inverse3.CursorLocalVelocity,
_cursorRadius, _ballPosition, _ballRadius);

inverse3.SetCursorLocalForce(force);
}
}
}