주요 내용으로 이동

기본 힘 피드백 튜토리얼

이 튜토리얼에서는 정적 물체(예: 공)와 접촉할 때의 물리적 특성을 시뮬레이션하기 위해 강성과 감쇠를 포함한 기본적인 촉각 시뮬레이션을 만드는 방법을 안내합니다. 이 튜토리얼을 마치면, 공의 존재를 감지하고 강성 및 감쇠 속성을 조정하여 다양한 촉각 경험을 얻을 수 있는 시뮬레이션 프로그램을 완성하게 됩니다.

소개​

이 튜토리얼의 핵심 과제는 강성과 감쇠를 모두 갖는 구체와 접촉할 때 발생하는 힘을 계산하는 함수를 개발하는 것입니다. 이 맥락에서 강성은 스프링과 유사한 역할을 하며, 압축이 심할수록 발생하는 힘도 커집니다. 반면, 감쇠는 물체의 운동에 대한 저항을 나타내며, 물체가 빠르게 움직일수록 받는 저항도 커집니다.

장면 설정​

먼저, 다음을 따라 입문 가이드 설명에 따라 Haptic Rig를 설정하고, 다음 사항을 확인하십시오. 햅틱 오리진 위치, 회전 및 확대/축소 값을 각각 다음과 같이 설정합니다. (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(object sender, Inverse3EventArgs args)
{
var inverse3 = args.DeviceController;
// Calculate the ball force
var force = ForceCalculation(inverse3.CursorLocalPosition, inverse3.CursorLocalVelocity,
_cursorRadius, _ballPosition, _ballRadius);

inverse3.SetCursorLocalForce(force);
}

에서 Awake 메서드에서, 초기화 _ballPosition、_ballRadius 와 _cursorRadius,장면 데이터를 설정하려면:

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

_cursorRadius = inverse3.Cursor.Radius;
}

private void Awake()
{
SaveSceneData();
}

다음 위치에서 각각 다음을 확인하십시오. OnEnable 와 OnDisable 메서드 내 등록 및 등록 해제 OnDeviceStateChanged 인터랙션 과정에서 힘 피드백을 올바르게 처리하기 위해 콜백을 호출합니다:

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

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

게임 체험​

Inverse3 의 커서를 잡으면 재생 모드로 전환되며, 구체를 터치해 보세요. 구체의 존재감을 느낄 수 있을 것이며, Unity 인스펙터를 통해 강성과 감쇠 속성을 조정하여 가상 물체와 상호작용할 때 실제와 같은 촉감을 경험할 수 있습니다.

커서가 구체에 닿음

원본 파일​

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

SphereForceFeedback.cs​

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

using Haply.Inverse.DeviceControllers;
using Haply.Inverse.DeviceData;
using UnityEngine;

namespace Haply.Samples.Tutorials._2_BasicForceFeedback
{
public class SphereForceFeedback : MonoBehaviour
{
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 ??= FindObjectOfType<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
var force = ForceCalculation(inverse3.CursorLocalPosition, inverse3.CursorLocalVelocity,
_cursorRadius, _ballPosition, _ballRadius);

inverse3.SetCursorLocalForce(force);
}
}
}