Skip to main content
Version: 2.2.0

VerseGrip Position Control Tutorial

This tutorial demonstrates how to use the VerseGrip's rotation to directly control the position of an Inverse3 device's cursor, bypassing the Unity main thread for high-frequency updates.

Introduction

Unlike the method shown in the Quick Start Guide, which operates at a standard update frequency (60Hz), this tutorial aims to dynamically adjust the Inverse3 cursor's position based on the VerseGrip device's rotation at a higher frequency (1~4kHz). This is achieved by leveraging the DeviceStateChanged event, triggered by the haptic thread.

Scene Setup

Begin by creating a Haptic Rig: GameObject > Haply > Haptic Rig (one hand), as detailed in the Quick Start Guide.

VerseGripPositionControl Component

Create a new C# script named VerseGripPositionControl.cs and attach it to the Haptic Origin GameObject. Define the following properties within the VerseGripPositionControl class:

public Inverse3 inverse3;
public VerseGrip verseGrip;

[Range(0, 1)]
public float speed = 0.5f;

[Range(0, 0.2f)]
public float movementLimitRadius = 0.2f;

private Vector3 _targetPosition;
  • inverse3: Reference to the Inverse3 device, set via the inspector.
  • verseGrip: Reference to the VerseGrip device used for cursor control.
  • speed: Speed at which the cursor moves.
  • movementLimitRadius: Maximum distance the cursor can move from its initial position.
  • _targetPosition: Target position towards which the cursor moves.

Implement the OnDeviceStateChanged method to calculate the cursor's target position based on the VerseGrip's rotation and button input:

private void OnDeviceStateChanged(VerseGrip grip)
{
// Calculate the direction based on the VerseGrip's rotation
var direction = grip.Orientation * Vector3.forward;

// Check if the VerseGrip button is pressed down
if (grip.GetButtonDown())
{
// Initialize target position
_targetPosition = inverse3.CursorLocalPosition;
}

// Check if the VerseGrip button is being held down
if (grip.GetButton())
{
// Move the target position toward the grip direction
_targetPosition += direction * (0.0025f * speed);

// Clamp the target position within the movement limit radius
var workspaceCenter = inverse3.WorkspaceCenter;
_targetPosition = Vector3.ClampMagnitude(_targetPosition - workspaceCenter, movementLimitRadius)
+ workspaceCenter;

// Move cursor to new position
inverse3.CursorSetLocalPosition(_targetPosition);
}
}

Register and deregister DeviceStateChanged event in OnEnable and OnDisable.

/// Subscribes to the DeviceStateChanged event.
private void OnEnable()
{
verseGrip.DeviceStateChanged += OnDeviceStateChanged;
}

/// Unsubscribes from the DeviceStateChanged event.
private void OnDisable()
{
verseGrip.DeviceStateChanged -= OnDeviceStateChanged;
}

Optional: In the Update method, let's enable user to reset the force for the Inverse3.

private void Update()
{
// Check for space key to disable position control
if (Input.GetKeyDown(KeyCode.Space))
{
// Reset cursor force to disable position control
inverse3.Release();
}
}

Gameplay

  • Secure the Inverse3 device and ensure it has sufficient space for movement.
  • Enter Play Mode and hold the Inverse3 cursor.
  • Rotate the VerseGrip to observe the cursor's movement in the Unity scene, which directly corresponds to the VerseGrip's orientation.
  • Pressing the VerseGrip's button allows the Inverse3 cursor to move in the direction of the VerseGrip's rotation, showcasing real-time control.

versegrip moving ball

The image illustrates the Cursor Model with its forward axis highlighted for clarity. For more details on customizing the Cursor Model, refer to the Cursor documentation.

Source files

The final scene and all associated files used by this example can be imported from the Tutorials sample in Unity's package manager.

VerseGripPositionControl.cs

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

using Haply.Inverse.Unity;
using UnityEngine;

namespace Haply.Samples.Tutorials._6_VerseGripPositionControl
{
/// <summary>
/// Demonstrates how to control the device cursor position using the VerseGrip.
/// </summary>
public class VerseGripPositionControl : MonoBehaviour
{
// Must be assigned in inspector
public Inverse3 inverse3;
public VerseGrip verseGrip;

[Tooltip("Cursor moving speed")]
[Range(0, 1)]
public float speed = 0.5f;

[Tooltip("Maximum radius for cursor movement")]
[Range(0, 0.2f)]
public float movementLimitRadius = 0.2f;

private Vector3 _targetPosition; // Target position for the cursor

/// <summary>
/// Subscribes to the DeviceStateChanged event.
/// </summary>
private void OnEnable()
{
verseGrip.DeviceStateChanged += OnDeviceStateChanged;
}

/// <summary>
/// Unsubscribes from the DeviceStateChanged event.
/// </summary>
private void OnDisable()
{
verseGrip.DeviceStateChanged -= OnDeviceStateChanged;
}

private void Update()
{
// Check for space key to disable position control
if (Input.GetKeyDown(KeyCode.Space))
{
// Reset cursor force to disable position control
inverse3.Release();
}
}

private void OnDeviceStateChanged(VerseGrip grip)
{
// Calculate the direction based on the VerseGrip's rotation
var direction = grip.Orientation * Vector3.forward;

// Check if the VerseGrip button is pressed down
if (grip.GetButtonDown())
{
// Initialize target position
_targetPosition = inverse3.CursorLocalPosition;
}

// Check if the VerseGrip button is being held down
if (grip.GetButton())
{
// Move the target position toward the grip direction
_targetPosition += direction * (0.0025f * speed);

// Clamp the target position within the movement limit radius
var workspaceCenter = inverse3.WorkspaceCenter;
_targetPosition = Vector3.ClampMagnitude(_targetPosition - workspaceCenter, movementLimitRadius)
+ workspaceCenter;

// Move cursor to new position
inverse3.CursorSetLocalPosition(_targetPosition);
}
}
}