장치의 작업 공간 변환에 대한 튜토리얼
이 튜토리얼은 다음을 바탕으로 합니다. 포스 피드백 기초 튜토리얼 을 열고 기기의 위치, 회전 및 크기를 조정하는 방법을 보여줍니다. Inverse3 공간 변환 특성과 방법을 활용하여 조정할 수 있습니다.
서론
기본 포스 피드백용 장면을 설정한 후, 햅틱 원점이나 햅틱 컨트롤러를 이동, 크기 조정 또는 회전하는 등의 조정을 해도 햅틱 피드백이 예상대로 반응하지 않는 것을 발견할 수 있습니다. 피드백은 여전히 장치 앞에 있는 보이지 않는 구체를 기준으로 하며, 이러한 변환의 영향을 받지 않습니다.

이러한 오차는 힘 계산에 커서의 실제(변환되지 않은) 좌표가 사용되기 때문에 발생합니다. 이 문제를 해결하기 위해, ‘Inverse3 ’ 컨트롤러 컴포넌트에서 제공하는 스레드 안전하고 캐시된 변환 행렬을 사용합니다. 이를 통해 햅틱 피드백 계산에 월드 좌표 변환을 적용할 수 있습니다.
장면 구성
포스 피드백(Force-Feedback) 기본 튜토리얼의 장면부터 시작하고, 햅틱 컨트롤러(Inverse3) 의 손잡이 방향이 사용 중인 기기와 일치하는지 확인하십시오. Haply 로고가 카메라를 향하도록 햅틱 컨트롤러를 회전시키고, 필요에 따라 햅틱 원점(Haptic Origin )의 배율을 조정하여 커서 범위를 넓히거나 위치를 조정하십시오.


포스 피드백 스크립트의 변경 사항
스크립트를 복사하세요 SphereForceFeedback.cs 포스 피드백에 관한 기초 튜토리얼에서 가져와 콜백에서 OnDeviceStateChanged 다음과 같은 조정을 실시합니다:
- 다음으로 대체하십시오
device.CursorLocalPositiondurchdevice.CursorPosition. - 다음으로 대체하십시오
device.CursorLocalVelocitydurchdevice.CursorVelocity. - 다음으로 대체하십시오
device.CursorSetLocalForce(force)durchdevice.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 커서를 길게 누른 상태에서 재생 모드를 시작하세요. 앞의 예시와 같이 공을 터치해 보세요. 이제 Haptic Origin과 Haptic Controller에 적용된 변환을 반영하는 정밀한 햅틱 피드백을 경험할 수 있을 것입니다.

원본 파일
이 예제의 전체 장면과 관련 파일은 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);
}
}
}