设备工作区转换教程
本教程扩展了 基本力反馈 教程,演示如何调整"...... "的位置、旋转和缩放。 Inverse3
设备的空间转换特性和方法。
导言
为基本力反馈设置场景后,您可能会注意到,对触觉原点或触觉控制器的调整(如移动、缩放或旋转)并不会如预期那样影响触觉反馈。 反馈仍对应于设备前方的一个不可见球体,不受这些变换的影响。
之所以会出现这种差异,是因为力计算使用的是设备光标的真实、未转换的坐标。 为了解决这个问题,我们将利用Inverse3 Controller组件提供的线程安全缓存变换矩阵,从而将世界空间变换应用于触觉反馈计算。
场景设置
从 "基本力反馈 "教程中的场景开始,确保Inverse3 控制器的手柄与您的设备相匹配。 旋转触觉控制器,使 Haply 徽标面向摄像头,并根据需要调整触觉原点刻度,以增强光标范围或调整其位置。
力反馈脚本更改
复制 SphereForceFeedback.cs
基本强制反馈教程中的脚本,并在 OnDeviceStateChanged
回调:
- 更换
device.LocalPosition
与device.Position
. - 更换
device.LocalVelocity
与device.Velocity
. - 更换
device.CursorSetLocalForce(force)
与device.CursorSetForce(force)
.
private void OnDeviceStateChanged(Inverse3 device) {
var force = ForceCalculation(device.Position, device.Velocity,
_cursorRadius, _ballPosition, _ballRadius);
device.CursorSetForce(force);
}
游戏体验
按住 Inverse3 光标并进入播放模式。尝试像上一个示例那样触摸球体。 现在你应该体验到精确的触觉反馈,它反映了应用于触觉原点和触觉控制器的变换。
源文件
本示例的完整场景和相关文件可从 Unity 包管理器中的教程示例导入。
示例场景包括用于运行时调整触觉控制器和触觉原点的附加脚本。
SphereForceFeedback.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.Position' instead of 'device.LocalPosition'
// ensures the force calculation considers the device's offset and rotation in world space.
var force = ForceCalculation(device.Position, device.Velocity,
_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);
}
}
}