本文へスキップ
バージョン: 2.1.1

デバイス・ワークスペース変換チュートリアル

このチュートリアルは 基本的なフォース・フィードバック の位置、回転、スケーリングを調整する方法を実演する。 Inverse3 デバイスの空間変換特性と方法を使用する。

はじめに

基本的なフォースフィードバックのためにシーンを設定した後、Haptic Originや Haptic Controllerの調整移動、拡大縮小、回転など)が、予想通りハプティックフィードバックに影響しないことに気づくかもしれません。 フィードバックは、まだデバイスの前にある目に見えない球に対応し、これらの変換の影響を受けません。

ワークスペース変換失敗

この不一致は、力の計算がデバイスのカーソルの変換されていない実際の座標を使用するために発生します。 これに対処するために、Inverse3 Controllerコンポーネントによって提供されるスレッドセーフでキャッシュされた変換行列を利用し、ハプティックフィードバックの計算にワールド空間の変換を適用できるようにします。

シーン設定

チュートリアルの基本的なフォースフィードバックのシーンから始め、Inverse3コントローラーの手の大きさがあなたのデバイスに合っていることを確認します。 Haplyのロゴがカメラに向くようにHaptic Controllerを回転させ、Haptic Originのスケールを必要に応じて調整し、カーソルの範囲を広げたり、位置を調整します。

インバース3右セットアップ

ワークスペース・トランスフォーム・右手

フォース・フィードバック・スクリプトの変更

をコピーする。 SphereForceFeedback.cs チュートリアルのBasic Force-Feedbackスクリプトを参考に、以下のように調整します。 OnDeviceStateChanged コールバック:

  • 交換 device.CursorLocalPositiondevice.CursorPosition.
  • 交換 device.CursorLocalVelocitydevice.CursorVelocity.
  • 交換 device.CursorSetLocalForce(force)device.CursorSetForce(force).
private void OnDeviceStateChanged(Inverse3 device) {
var force = ForceCalculation(device.CursorPosition, device.CursorVelocity,
_cursorRadius, _ballPosition, _ballRadius);

device.CursorSetForce(force);
}

ゲーム体験

Inverse3カーソルを押したまま、プレイモードに入る。前の例と同じように、球に触れてみてください。 これで、Haptic Originと Haptic Controllerに適用された変形を反映した正確な触覚フィードバックが体験できるはずです。

カーソルヒット球拡大

ソースファイル

この例の完全なシーンと関連ファイルは、Unity Package Manager のTutorialsサンプルからインポートできます。

サンプルシーンには、Haptic Controllerと Haptic Originのランタイム調整用の追加スクリプトが含まれています。

スフィアフォースフィードバック.cs

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

using Haply.Inverse.Unity;
using UnityEngine;
using UnityEngine.Serialization;

namespace Haply.Samples.Tutorials._3_DeviceSpaceTransform
{
public class SphereForceFeedback : MonoBehaviour
{
// must assign in inspector
public Inverse3 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.Model.transform.lossyScale.x / 2f;
}

/// <summary>
/// Saves the initial scene data cache.
/// </summary>
private void Awake()
{
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;
}

/// <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="device">The Inverse3 device instance.</param>
private void OnDeviceStateChanged(Inverse3 device)
{
// Calculate the ball force. Using 'device.CursorPosition' instead of 'device.CursorLocalPosition'
// ensures the force calculation considers the device's offset and rotation in world space.
var force = ForceCalculation(device.CursorPosition, device.CursorVelocity,
_cursorRadius, _ballPosition, _ballRadius);

// Apply the calculated force to the cursor. Using 'device.CursorSetForce' instead of
// 'device.CursorSetLocalForce' ensures that the force vector is correctly converted
// from world space to the device's local space.
device.CursorSetForce(force);
}
}
}