디바이스 작업 공간 변환 튜토리얼
이 튜토리얼에서는 기본 강제 피드백 튜토리얼을 통해 위치, 회전, 배율을 조정하는 방법을 시연합니다. Inverse3
장치의 공간 변환 속성 및 메서드를 사용합니다.
소개
기본 힘 피드백을 위해 씬을 설정한 후 햅틱 원점 또는 햅틱 컨트롤러를이동, 크기 조정 또는 회전하는 등의조정이 예상대로 햅틱 피드백에 영향을 미치지 않는 것을 확인할 수 있습니다. 피드백은 여전히 장치 앞에 위치한 보이지 않는 구체에 해당하며 이러한 변환의 영향을 받지 않습니다.
이러한 불일치는 힘 계산이 장치 커서의 변환되지 않은 실제 좌표를 사용하기 때문에 발생합니다. 이 문제를 해결하기 위해 Inverse3 컨트롤러 컴포넌트에서 제공하는 스레드 안전 캐시된 변환 행렬을 활용하여 햅틱 피드백 계산에 월드 스페이스 변환을 적용할 수 있습니다.
장면 설정
기본 포스 피드백 튜토리얼의 장면부터 시작하여 Inverse3 컨트롤러의 손 모양이 장치와 일치하는지 확인합니다. 햅틱 컨트롤러를 회전하여 Haply 로고가 카메라를 향하도록 하고 원하는 대로 햅틱 원점 눈금을 조정하여 커서 범위를 늘리거나 위치를 조정합니다.
강제 피드백 스크립트 변경
복사 SphereForceFeedback.cs
스크립트를 실행하고 기본 강제 피드백 튜토리얼에서 다음과 같이 조정합니다. OnDeviceStateChanged
콜백:
- 교체
device.CursorLocalPosition
와 함께device.CursorPosition
. - 교체
device.CursorLocalVelocity
와 함께device.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 커서를 누른 상태에서 재생 모드로 들어갑니다. 이전 예제에서와 같이 구를 터치합니다. 이제 햅틱 원 점 및 햅틱 컨트롤러에 적용된 변환을 반영하는 정확한 햅틱 피드백을 경험할 수 있을 것입니다.
소스 파일
이 예제의 전체 씬과 관련 파일은 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.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);
}
}
}