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