주요 콘텐츠로 건너뛰기
버전: 2.1.1

디바이스 작업 공간 변환 튜토리얼

이 튜토리얼에서는 기본 강제 피드백 튜토리얼을 통해 위치, 회전, 배율을 조정하는 방법을 시연합니다. Inverse3 장치의 공간 변환 속성 및 메서드를 사용합니다.

소개

기본 힘 피드백을 위해 씬을 설정한 후 햅틱 원점 또는 햅틱 컨트롤러를이동, 크기 조정 또는 회전하는 등의조정이 예상대로 햅틱 피드백에 영향을 미치지 않는 것을 확인할 수 있습니다. 피드백은 여전히 장치 앞에 위치한 보이지 않는 구체에 해당하며 이러한 변환의 영향을 받지 않습니다.

작업 공간 변환 실패

이러한 불일치는 힘 계산이 장치 커서의 변환되지 않은 실제 좌표를 사용하기 때문에 발생합니다. 이 문제를 해결하기 위해 Inverse3 컨트롤러 컴포넌트에서 제공하는 스레드 안전 캐시된 변환 행렬을 활용하여 햅틱 피드백 계산에 월드 스페이스 변환을 적용할 수 있습니다.

장면 설정

기본 포스 피드백 튜토리얼의 장면부터 시작하여 Inverse3 컨트롤러의 손 모양이 장치와 일치하는지 확인합니다. 햅틱 컨트롤러를 회전하여 Haply 로고가 카메라를 향하도록 하고 원하는 대로 햅틱 원점 눈금을 조정하여 커서 범위를 늘리거나 위치를 조정합니다.

인버스3- 오른손잡이 설정

작업 공간 변환 오른손잡이

강제 피드백 스크립트 변경

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