Skip to content

PICO Controller Haptic Feedback Not Working #87

@Q1an05

Description

@Q1an05

Issue Summary

Haptic feedback (vibration) does not work on PICO 4 / Neo3 controllers when using UltimateXR's default UxrPicoNeo3Input class. The issue is caused by incompatible haptic buffer implementation with PICO's wideband linear motor.

Environment

  • Device: PICO 4 / PICO Neo3
  • Unity Version: 2022.3.x LTS
  • UltimateXR Version: Latest from GitHub (commit: 985f20c)
  • PICO SDK Version: 3.3.3 (com.unity.xr.picoxr)
  • Platform: Android

Steps to Reproduce

  1. Import UltimateXR and PICO Unity Integration SDK
  2. Set up UxrAvatar with default PICO components:
    • Controller Input: UxrPicoNeo3Input
    • Controller Tracking: UxrPicoNeo3Tracking
  3. Call haptic feedback via:
    var avatar = UxrAvatar.LocalAvatar;
    avatar.ControllerInput.SendHapticFeedback(
        UxrHandSide.Right, 
        UxrHapticClipType.RumbleFreqNormal, 
        1.0f, 
        0.3f
    );
  4. Expected: Controller vibrates
  5. Actual: No vibration feedback

Root Cause Analysis

Problem Location

UxrUnityXRControllerInput.SendHapticFeedback() (line ~240)

Current Implementation

if (hapticCapabilities.supportsBuffer)
{
    // UltimateXR uses SendHapticBuffer for PICO
    byte[] samples = new byte[(int)(hapticCapabilities.bufferFrequencyHz * durationSeconds)];
    // ... sample generation logic
    inputDevice.SendHapticBuffer(channel, samples);
}
else if (hapticCapabilities.supportsImpulse)
{
    inputDevice.SendHapticImpulse(channel, amplitude, durationSeconds);
}

Why It Fails

  1. PICO devices report hapticCapabilities.supportsBuffer = true
  2. UltimateXR chooses SendHapticBuffer code path
  3. The generated buffer data is incompatible with PICO's wideband linear motor
  4. PICO's SendHapticBuffer implementation expects different data format

Verification

Using PICO's native SDK directly works correctly:

// This works perfectly
PXR_Input.SendHapticImpulse(
    PXR_Input.VibrateType.RightController, 
    amplitude: 1.0f, 
    duration: 300, 
    frequency: 100
);

Proposed Solutions

Option 1: Prefer Impulse over Buffer for PICO (Recommended)

Modify UxrPicoNeo3Input to override SendHapticFeedback and use SendHapticImpulse instead of SendHapticBuffer:

public class UxrPicoNeo3Input : UxrUnityXRControllerInput
{
    public override void SendHapticFeedback(UxrHandSide handSide, 
                                            float frequency, 
                                            float amplitude, 
                                            float durationSeconds, 
                                            UxrHapticMode hapticMode = UxrHapticMode.Mix)
    {
        // Force using SendHapticImpulse for PICO compatibility
        InputDevice inputDevice = GetInputDevice(handSide);
        if (inputDevice.isValid && 
            inputDevice.TryGetHapticCapabilities(out var caps) && 
            caps.supportsImpulse)
        {
            inputDevice.SendHapticImpulse(0, amplitude, durationSeconds);
        }
    }
}

Option 2: Use PICO Native SDK

Add conditional compilation to use PXR_Input.SendHapticImpulse when ULTIMATEXR_USE_PICOXR_SDK is defined:

#if ULTIMATEXR_USE_PICOXR_SDK
    PXR_Input.SendHapticImpulse(
        handSide == UxrHandSide.Left ? 
            PXR_Input.VibrateType.LeftController : 
            PXR_Input.VibrateType.RightController,
        amplitude,
        (int)(durationSeconds * 1000),
        (int)frequency
    );
#else
    // fallback to default implementation
#endif

Option 3: Fix Buffer Generation

Investigate and fix the buffer data format to match PICO's expectations.

Workaround (Current Solution)

We created custom classes that inherit from UltimateXR's PICO classes:

PICOInput.cs:

public class PICOInput : UxrPicoNeo3Input
{
    public override void SendHapticFeedback(UxrHandSide handSide, 
                                            float frequency, 
                                            float amplitude, 
                                            float durationSeconds, 
                                            UxrHapticMode hapticMode = UxrHapticMode.Mix)
    {
        PXR_Input.SendHapticImpulse(
            handSide == UxrHandSide.Left ? 
                PXR_Input.VibrateType.LeftController : 
                PXR_Input.VibrateType.RightController,
            amplitude,
            (int)(durationSeconds * 1000),
            Mathf.Clamp((int)frequency, 50, 500)
        );
    }
}

PICOTracking.cs:

public class PICOTracking : UxrPicoNeo3Tracking
{
    public override Type RelatedControllerInputType => typeof(PICOInput);
}

Then replace components in UxrAvatar:

  • Controller Input: PICOInput
  • Controller Tracking: PICOTracking

Additional Context

  • PICO 4 uses wideband linear motors (50-500Hz)
  • PICO SDK provides PXR_Input.SendHapticImpulse() specifically for this hardware
  • Other PICO features (tracking, buttons) work correctly with UltimateXR
  • Only haptic feedback has this compatibility issue

Related Documentation


Labels: bug, pico, haptic, android
Priority: Medium (has workaround, but affects out-of-box experience)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions