본문으로 이동

기기 작업 공간 변환 방법 안내

이 튜토리얼은 다음 내용을 이어서 다룹니다. 기본 힘 피드백 튜토리얼 기기의 위치, 회전 및 크기 조정을 하는 방법을 보여줌으로써 Inverse3 공간 변환의 속성과 메서드를 활용하여.

소개​

기본 햅틱 피드백을 위한 씬을 설정한 후, ‘Haptic Origin’이나 ‘Haptic Controller’에 적용한 조정(이동, 크기 조정, 회전 등)이 햅틱 피드백에 예상대로 반영되지 않는 것을 발견할 수 있습니다. 햅틱 피드백은 항상 기기 앞쪽에 위치한 보이지 않는 구체를 기준으로 하며, 이러한 변환의 영향을 받지 않습니다.

작업 공간 변환 실패

이러한 차이는 힘 계산에 기기의 커서 좌표가 변환되지 않은 실제 좌표로 사용되기 때문입니다. 이 문제를 해결하기 위해, ‘Inverse3 ’ 컨트롤러 컴포넌트가 제공하는 스레드 간 안전성이 보장된 캐시된 변환 행렬을 사용할 것입니다. 이를 통해 햅틱 피드백 계산에 월드 공간 변환을 적용할 수 있게 됩니다.

장면 설정​

기본 힘 반환 튜토리얼 장면에서 시작하여, ‘Inverse3 ’ 컨트롤러의 좌우 방향이 기기의 방향과 일치하는지 확인하십시오. ‘ Haply ’ 로고가 카메라를 향하도록 ‘Haptic Controller’를 회전시킨 다음, 필요에 따라 ‘Haptic Origin’의 배율을 조정하여 커서의 범위를 개선하거나 위치를 변경하십시오.

inverse3-오른손잡이 설정

작업 공간 변환 오른손잡이

강제 복귀 스크립트 수정 사항​

스크립트를 복사하세요 SphereForceFeedback.cs 기본 피드백 튜토리얼을 참고하여 콜백 함수에 다음과 같은 수정 사항을 적용하세요. OnDeviceStateChanged :

  • Remplacez device.CursorLocalPosition par device.CursorPosition.
  • Remplacez device.CursorLocalVelocity par device.CursorVelocity.
  • Remplacez device.CursorSetLocalForce(force) par 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 의 커서를 누른 채로 플레이 모드로 전환하세요. 앞서 예시에서와 같이 구체를 터치해 보세요. 이제 Haptic Origin과 Haptic Controller에 적용된 변환을 반영하는 정밀한 햅틱 피드백을 느낄 수 있을 것입니다.

커서-히트-구-확대

소스 파일​

이 예제와 관련된 전체 씬 및 관련 파일은 Unity 패키지 관리자의 ‘Tutorials’ 예제에서 가져올 수 있습니다.

이 예제 장면에는 실행 시 ‘Haptic Controller’와 ‘Haptic Origin’을 조정할 수 있는 추가 스크립트가 포함되어 있습니다.

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);
}
}
}