포스 피드백 기초 튜토리얼
이 튜토리얼에서는 강성과 감쇠를 모두 고려하여, 공과 같은 정적 물체와의 접촉 시 나타나는 물리적 특성을 재현하는 간단한 햅틱 시뮬레이션을 만드는 과정을 단계별로 안내합니다. 이 튜토리얼을 마치면, 공의 존재를 느낄 수 있고 다양한 햅틱 경험을 위해 공의 강성 및 감쇠 특성을 조정할 수 있는 시뮬레이션을 완성하게 됩니다.
서론
이 튜토리얼의 핵심 과제는 강성과 감쇠 특성을 모두 지닌 구체와 접촉할 때 발생하는 힘을 계산하는 함수를 개발하는 것입니다. 이 맥락에서 강성은 스프링과 같은 역할을 하며, 압축될수록 더 큰 힘을 발생시킵니다. 반면 감쇠는 물체가 움직이는 데 대한 저항을 나타내며, 물체가 빠르게 움직일수록 이 저항은 더 커집니다.
장면 구성
먼저, 다음의 지침에 따라 햅틱 리그를 설정하는 것부터 시작하세요. “첫걸음” 사용 안내 설명된 대로, 다음의 위치, 회전 및 크기가 햅틱 오리진 auf (0, 0, 0) 또는 (1, 1, 1) 정해져 있다.
그런 다음 다음과 같은 이름의 구체를 생성하십시오. Sphere 다음과 같은 특성을 지닌:
- 위치:
(0, 0, -0.1)(기기에서 약 10cm 앞) - 스케일링:
(0.2, 0.2, 0.2)(지름 20cm의 구에 해당함)
포스 피드백 스크립트
GameObject에 다음을 추가하세요 구체 "라는 이름의 새로운 C# 스크립트 SphereForceFeedback.cs hinzu.
이 스크립트는 공과 접촉할 때 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);
}
}
}