Zum Hauptinhalt springen

Einführungs-Tutorial zum Thema Force-Feedback

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

Einführung​

Die zentrale Herausforderung dieses Tutorials besteht darin, eine Funktion zu entwickeln, die die Kräfte berechnet, die beim Kontakt mit einer Kugel entstehen, die sowohl Steifigkeit als auch Dämpfung aufweist. Steifigkeit wirkt in diesem Zusammenhang wie eine Feder und erzeugt umso mehr Kraft, je stärker sie zusammengedrückt wird. Dämpfung hingegen beschreibt den Widerstand eines Objekts gegen Bewegung: Je schneller es bewegt wird, desto größer ist dieser Widerstand.

Szenenaufbau​

Beginnen Sie damit, ein Haptic Rig einzurichten, wie in der Anleitung „Erste Schritte“ beschrieben, und stellen Sie sicher, dass Position, Drehung und Skalierung des Haptischer Ursprung auf (0, 0, 0) bzw. (1, 1, 1) festgelegt sind.

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

  • Position: (0, 0, -0.1) (etwa 10 cm vor dem Gerät)
  • Skalierung: (0.2, 0.2, 0.2) (entspricht einer Kugel mit einem Durchmesser von 20 cm)

Force-Feedback-Skript​

Fügen Sie dem GameObject Sphäre ein neues C#-Skript mit dem Namen SphereForceFeedback.cs hinzu. Dieses Skript berechnet die Kräfte, die beim Kontakt mit der Kugel auf den Inverse3-Cursor wirken, und berücksichtigt dabei Steifigkeit und Dämpfung. Legen Sie für das Skript die folgenden Eigenschaften fest:

[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 sollte nur dann erfolgen, wenn der Cursor die Kugel durchdringt, um so das Gefühl zu simulieren, ein physikalisches Objekt mit definierter Steifigkeit und Dämpfung zu berühren. Die Methode ForceCalculation ist dafür zuständig und berücksichtigt sowohl die Position als auch die Geschwindigkeit des Cursors:

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

Initialisieren Sie in der Methode Awake _ballPosition, _ballRadius und _cursorRadius, um die Szenendaten einzurichten:

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

_cursorRadius = inverse3.Cursor.Radius;
}

private void Awake()
{
SaveSceneData();
}

Stellen Sie sicher, dass der Callback OnDeviceStateChanged in OnEnable registriert und in OnDisable wieder abgemeldet wird, damit das Force-Feedback während der Interaktion korrekt verarbeitet wird:

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

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

Spielerlebnis​

Halten Sie den „ Inverse3 “-Cursor gedrückt, starten Sie den Play-Modus und versuchen Sie, die Kugel zu berühren. Sie sollten die Präsenz der Kugel spüren und ihre Steifigkeits- und Dämpfungseigenschaften über den Unity-Inspector anpassen können. So entsteht ein greifbares Gefühl der Interaktion mit einem virtuellen Objekt.

Cursor trifft Kugel

Quelldateien​

Die vollständige Szene und alle zugehörigen Dateien für dieses Beispiel können aus den Beispiel-Tutorials im Package Manager von Unity 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);
}
}
}