기본 포스 피드백 튜토리얼
이 튜토리얼에서는 강성과 댐핑을 모두 통합하여 구와 같은 정적 물체와의 접촉 시 물리적 속성을 시뮬레이션하는 기본 햅틱 시뮬레이션을 만드는 방법을 안내합니다. 이 튜토리얼이 끝나면 구의 존재를 느끼고 다양한 햅틱 경험을 위해 강성 및 댐핑 속성을 조정할 수 있는 시뮬레이션을 만들 수 있습니다.
소개
이 튜토리얼의 핵심 과제는 강성과 감쇠를 모두 나타내는 구와의 접촉으로 인해 발생하는 힘을 계산할 수 있는 함수를 개발하는 것입니다. 여기서 강성은 스프링처럼 작용하여 압축될수록 더 많은 힘을 생성합니다. 반면에 댐핑은 물체의 움직임에 대한 저항을 나타내며, 물체를 빠르게 움직일수록 더 많은 저항을 제공합니다.
장면 설정
햅틱 릭을 설정하는 방법은 다음과 같습니다. 빠른 시작 가이드를 사용하여 햅틱 오리진의 위치, 회전 및 배율은 다음과 같이 설정됩니다. (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();
}
등록 및 등록 취소를 확인합니다. 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.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);
        }
    }
}