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
- Import UltimateXR and PICO Unity Integration SDK
- Set up UxrAvatar with default PICO components:
- Controller Input:
UxrPicoNeo3Input
- Controller Tracking:
UxrPicoNeo3Tracking
- Call haptic feedback via:
var avatar = UxrAvatar.LocalAvatar;
avatar.ControllerInput.SendHapticFeedback(
UxrHandSide.Right,
UxrHapticClipType.RumbleFreqNormal,
1.0f,
0.3f
);
- Expected: Controller vibrates
- 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
- PICO devices report
hapticCapabilities.supportsBuffer = true
- UltimateXR chooses
SendHapticBuffer code path
- The generated buffer data is incompatible with PICO's wideband linear motor
- 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)
Issue Summary
Haptic feedback (vibration) does not work on PICO 4 / Neo3 controllers when using UltimateXR's default
UxrPicoNeo3Inputclass. The issue is caused by incompatible haptic buffer implementation with PICO's wideband linear motor.Environment
Steps to Reproduce
UxrPicoNeo3InputUxrPicoNeo3TrackingRoot Cause Analysis
Problem Location
UxrUnityXRControllerInput.SendHapticFeedback()(line ~240)Current Implementation
Why It Fails
hapticCapabilities.supportsBuffer = trueSendHapticBuffercode pathSendHapticBufferimplementation expects different data formatVerification
Using PICO's native SDK directly works correctly:
Proposed Solutions
Option 1: Prefer Impulse over Buffer for PICO (Recommended)
Modify
UxrPicoNeo3Inputto overrideSendHapticFeedbackand useSendHapticImpulseinstead ofSendHapticBuffer:Option 2: Use PICO Native SDK
Add conditional compilation to use
PXR_Input.SendHapticImpulsewhenULTIMATEXR_USE_PICOXR_SDKis defined: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:
PICOTracking.cs:
Then replace components in UxrAvatar:
PICOInputPICOTrackingAdditional Context
PXR_Input.SendHapticImpulse()specifically for this hardwareRelated Documentation
PXR_Input.SendHapticImpulse(VibrateType, float amplitude, int durationMs, int frequency)Labels: bug, pico, haptic, android
Priority: Medium (has workaround, but affects out-of-box experience)