跳转至主要内容

设备工作空间转换教程

本教程基于 力反馈入门教程 并演示如何设置设备的位移、旋转和缩放 Inverse3 利用其空间变换特性和方法进行调整。

引言​

在设置好基础力反馈场景后,您可能会发现,对触觉原点或触觉控制器进行的调整(例如平移、缩放或旋转)并未如预期那样影响触觉反馈。 该反馈仍然对应于设备前方的一个不可见球体,且不受这些变换的影响。

工作区转换失败

出现这种偏差的原因在于,力计算使用的是光标的实际(未变换)坐标。 为了解决这个问题,我们使用由Inverse3 控制器组件提供的线程安全的缓存变换矩阵。这样,我们就可以对力反馈的计算应用世界坐标变换。

场景构图​

请从“力反馈基础教程”中的场景开始,并确保Inverse3 手柄的左右手设置与您的设备相匹配。 将 手柄旋转至Haply 标志朝向摄像头的方向,并根据需要调整的缩放比例,以扩大光标范围或调整其位置。

inverse3 右手设置

工作区转换(右手)

对力反馈脚本的修改​

复制该脚本 SphereForceFeedback.cs 摘自关于力反馈的基础教程,并在回调中 OnDeviceStateChanged 进行了以下调整:

  • 请替换 device.CursorLocalPosition durch device.CursorPosition.
  • 请替换 device.CursorLocalVelocity durch device.CursorVelocity.
  • 请替换 device.CursorSetLocalForce(force) durch device.CursorSetForce(force).
private void OnDeviceStateChanged(object sender, Inverse3EventArgs args)
{
var inverse3 = args.DeviceController;
var force = ForceCalculation(inverse3.CursorPosition, inverse3.CursorVelocity,
_cursorRadius, _ballPosition, _ballRadius);

inverse3.SetCursorForce(force);
}

游戏体验​

按住Inverse3 光标,启动播放模式。试着像上一个示例中那样触摸球体。 此时,您应该能感受到精确的触觉反馈,该反馈反映了应用于“触觉原点 ”和“触觉控制器”的变换。

光标击中弹丸,放大

源文件​

该示例的完整场景及相关文件可从 Unity 的“包管理器”中的“示例教程”中导入。

该示例场景包含用于在运行时调整触觉 控制器和触觉原点的额外脚本。

SphereForceFeedback.cs​

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

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

namespace Haply.Samples.Tutorials._3_DeviceSpaceTransform
{
public class SphereForceFeedback : MonoBehaviour
{
// must assign in inspector
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 ??= FindFirstObjectByType<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. Using 'device.Position' instead of 'device.LocalPosition'
// ensures the force calculation considers the device's offset and rotation in world space.
var force = ForceCalculation(inverse3.CursorPosition, inverse3.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.
inverse3.SetCursorForce(force);
}
}
}