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

기본 포스 피드백 튜토리얼

이 튜토리얼에서는 강성과 댐핑을 모두 통합하여 구와 같은 정적 물체와의 접촉 시 물리적 속성을 시뮬레이션하는 기본 햅틱 시뮬레이션을 만드는 방법을 안내합니다. 이 튜토리얼이 끝나면 구의 존재를 느끼고 다양한 햅틱 경험을 위해 강성 및 댐핑 속성을 조정할 수 있는 시뮬레이션을 만들 수 있습니다.

소개

이 튜토리얼의 핵심 과제는 강성과 감쇠를 모두 나타내는 구와의 접촉으로 인해 발생하는 힘을 계산할 수 있는 함수를 개발하는 것입니다. 여기서 강성은 스프링처럼 작용하여 압축될수록 더 많은 힘을 생성합니다. 반면에 댐핑은 물체의 움직임에 대한 저항을 나타내며, 물체를 빠르게 움직일수록 더 많은 저항을 제공합니다.

장면 설정

햅틱 릭을 설정하는 방법은 다음과 같습니다. 빠른 시작 가이드를 사용하여 햅틱 오리진의 위치, 회전 및 배율은 다음과 같이 설정됩니다. (0, 0, 0) 그리고 (1, 1, 1) 를 각각 입력합니다.

그런 다음 다음과 같은 이름의 구를 만듭니다. Sphere 를 다음과 같은 속성으로 설정합니다:

  • 위치: (0, 0, -0.1) (기기에서 약 10cm 앞)
  • 규모: (0.2, 0.2, 0.2) (지름 20cm 구에 해당)

강제 피드백 스크립트

에 새 C# 스크립트를 추가합니다. 구체 게임 오브젝트 이름 SphereForceFeedback.cs. 이 스크립트는 강성과 댐핑을 모두 고려하여 구에 닿을 때 Inverse3 커서에 가해지는 힘을 계산합니다. 다음 프로퍼티를 사용하여 스크립트를 초기화합니다:

[SerializeField]
private Inverse3 inverse3;

[Range(0, 800)]
public float stiffness = 300f;
[Range(0, 3)]
public float damping = 1f;

private Vector3 _ballPosition;
private float _ballRadius;
private float _cursorRadius;

힘 계산은 커서가 구를 관통할 때만 발생해야 하며, 강성과 댐핑이 정의된 실제 물체에 닿는 느낌을 시뮬레이션합니다. 구의 ForceCalculation 메서드가 커서의 위치와 속도를 모두 고려하여 이를 처리합니다:

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

private void OnDeviceStateChanged(Inverse3 device)
{
// Calculate the ball force
var force = ForceCalculation(device.CursorLocalPosition, device.CursorLocalVelocity,
_cursorRadius, _ballPosition, _ballRadius);

device.CursorSetLocalForce(force);
}

에서 Awake 메서드, 초기화 _ballPosition, _ballRadius_cursorRadius 를 클릭하여 씬 데이터를 설정합니다:

private void SaveSceneData()
{
var t = transform;
_ballPosition = t.position;
_ballRadius = t.lossyScale.x / 2f;

_cursorRadius = inverse3.Cursor.Model.transform.lossyScale.x / 2f;
}

private void Awake()
{
SaveSceneData();
}

등록 및 등록 취소를 확인합니다. OnDeviceStateChanged 콜백의 OnEnable 그리고 OnDisable 메서드를 각각 사용하여 상호작용 중 힘 피드백을 적절히 처리할 수 있습니다:

protected void OnEnable()
{
inverse3.DeviceStateChanged += OnDeviceStateChanged;
}

protected void OnDisable()
{
inverse3.DeviceStateChanged -= OnDeviceStateChanged;
}

게임 플레이 경험

Inverse3 커서를 누른 상태에서 플레이 모드로 전환하고 구를 터치해 봅니다. Unity 인스펙터를 통해 구의 존재감을 느끼고 강성 및 댐핑 프로퍼티를 조작하여 가상 오브젝트와 상호작용하는 듯한 느낌을 받을 수 있을 것입니다.

커서 히트 구

소스 파일

이 예제의 전체 씬과 모든 관련 파일은 Unity 패키지 관리자의 튜토리얼 샘플에서 임포트할 수 있습니다.

SphereForceFeedback.cs

/*
* Copyright 2024 Haply Robotics Inc. All rights reserved.
*/

using Haply.Inverse.Unity;
using UnityEngine;

namespace Haply.Samples.Tutorials._2_BasicForceFeedback
{
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
var force = ForceCalculation(device.CursorLocalPosition, device.CursorLocalVelocity,
_cursorRadius, _ballPosition, _ballRadius);

device.CursorSetLocalForce(force);
}
}
}