Skip to content
This repository was archived by the owner on Jul 22, 2024. It is now read-only.

Commit 25bc08b

Browse files
Add support for haptic feedback on Pico controllers (#2932)
* Add support for haptic feedback on Pico controllers * Swap which contoller vibrates. Co-authored-by: Randall E. Barker <[email protected]>
1 parent fcec46e commit 25bc08b

File tree

6 files changed

+151
-2
lines changed

6 files changed

+151
-2
lines changed

app/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ target_sources(
6767
PUBLIC
6868
src/picovr/cpp/native-lib.cpp
6969
src/picovr/cpp/DeviceDelegatePicoVR.cpp
70+
src/picovr/cpp/VRBrowserPico.cpp
7071
)
7172
elseif(NOAPI)
7273
target_sources(

app/src/picovr/cpp/DeviceDelegatePicoVR.cpp

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
#include "DeviceDelegatePicoVR.h"
77
#include "ElbowModel.h"
8-
#include "BrowserEGLContext.h"
8+
#include "VRBrowserPico.h"
99

1010
#include <EGL/egl.h>
1111
#include "vrb/CameraEye.h"
@@ -53,6 +53,7 @@ struct DeviceDelegatePicoVR::State {
5353
float axisX = 0;
5454
float axisY = 0;
5555
ElbowModel::HandEnum hand;
56+
int hapticFrameID = 0;
5657
Controller()
5758
: index(-1)
5859
, created(false)
@@ -142,6 +143,16 @@ struct DeviceDelegatePicoVR::State {
142143
}
143144
}
144145

146+
void UpdateHaptics(Controller& aController) {
147+
uint64_t inputFrameID = 0;
148+
float pulseDuration = 0.0f, pulseIntensity = 0.0f;
149+
controllerDelegate->GetHapticFeedback(aController.index, inputFrameID, pulseDuration, pulseIntensity);
150+
151+
if (aController.hapticFrameID != inputFrameID) {
152+
VRBrowserPico::UpdateHaptics(aController.index, pulseIntensity, pulseDuration);
153+
}
154+
}
155+
145156
void UpdateControllers() {
146157
for (int32_t i = 0; i < controllers.size(); ++i) {
147158
if (!controllers[i].enabled) {
@@ -208,6 +219,10 @@ struct DeviceDelegatePicoVR::State {
208219
}
209220

210221
controllerDelegate->SetTransform(i, transform);
222+
223+
if (controllerDelegate->GetHapticCount(i)) {
224+
UpdateHaptics(controllers[i]);
225+
}
211226
}
212227
}
213228
};
@@ -227,6 +242,10 @@ DeviceDelegatePicoVR::SetRenderMode(const device::RenderMode aMode) {
227242
return;
228243
}
229244
m.renderMode = aMode;
245+
if (aMode == device::RenderMode::StandAlone) {
246+
// Ensure that all haptics are cancelled when exiting WebVR
247+
VRBrowserPico::CancelAllHaptics();
248+
}
230249
}
231250

232251
device::RenderMode
@@ -296,7 +315,7 @@ DeviceDelegatePicoVR::SetControllerDelegate(ControllerDelegatePtr& aController)
296315
beam.TranslateInPlace(vrb::Vector(0.0f, 0.012f, -0.06f));
297316
m.controllerDelegate->CreateController(index, int32_t(controller.hand), controller.IsRightHand() ? "Pico Neo 2 (Right)" : "Pico Neo 2 (LEFT)", beam);
298317
m.controllerDelegate->SetButtonCount(index, kNumButtons);
299-
m.controllerDelegate->SetHapticCount(index, 0);
318+
m.controllerDelegate->SetHapticCount(index, 1);
300319
} else {
301320
vrb::Matrix beam = vrb::Matrix::Rotation(vrb::Vector(1.0f, 0.0f, 0.0f), -vrb::PI_FLOAT / 11.5f);
302321

app/src/picovr/cpp/VRBrowserPico.cpp

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5+
6+
#include "VRBrowserPico.h"
7+
#include "vrb/ConcreteClass.h"
8+
#include "vrb/Logger.h"
9+
#include "JNIUtil.h"
10+
11+
namespace {
12+
13+
const char* sUpdateHapticsName = "updateHaptics";
14+
const char* sUpdateHapticsSignature = "(IFF)V";
15+
const char* sCancelAllHapticsName = "cancelAllHaptics";
16+
const char* sCancelAllHapticsSignature = "()V";
17+
18+
JNIEnv* sEnv = nullptr;
19+
jclass sBrowserClass = nullptr;
20+
jobject sActivity = nullptr;
21+
jmethodID sUpdateHaptics = nullptr;
22+
jmethodID sCancelAllHaptics = nullptr;
23+
}
24+
25+
namespace crow {
26+
27+
void
28+
VRBrowserPico::InitializeJava(JNIEnv* aEnv, jobject aActivity) {
29+
if (aEnv == sEnv) {
30+
return;
31+
}
32+
sEnv = aEnv;
33+
if (!sEnv) {
34+
return;
35+
}
36+
sActivity = sEnv->NewGlobalRef(aActivity);
37+
sBrowserClass = sEnv->GetObjectClass(sActivity);
38+
if (!sBrowserClass) {
39+
return;
40+
}
41+
42+
sUpdateHaptics = FindJNIMethodID(sEnv, sBrowserClass, sUpdateHapticsName, sUpdateHapticsSignature);
43+
sCancelAllHaptics = FindJNIMethodID(sEnv, sBrowserClass, sCancelAllHapticsName, sCancelAllHapticsSignature);
44+
}
45+
46+
void
47+
VRBrowserPico::ShutdownJava() {
48+
if (!sEnv) {
49+
return;
50+
}
51+
if (sActivity) {
52+
sEnv->DeleteGlobalRef(sActivity);
53+
sActivity = nullptr;
54+
}
55+
56+
sBrowserClass = nullptr;
57+
sUpdateHaptics = nullptr;
58+
sCancelAllHaptics = nullptr;
59+
sEnv = nullptr;
60+
}
61+
62+
63+
void
64+
VRBrowserPico::UpdateHaptics(jint aControllerIndex, jfloat aIntensity, jfloat aDuration) {
65+
if (!ValidateMethodID(sEnv, sActivity, sUpdateHaptics, __FUNCTION__)) { return; }
66+
sEnv->CallVoidMethod(sActivity, sUpdateHaptics, aControllerIndex, aIntensity, aDuration);
67+
CheckJNIException(sEnv, __FUNCTION__);
68+
}
69+
void
70+
VRBrowserPico::CancelAllHaptics() {
71+
if (!ValidateMethodID(sEnv, sActivity, sCancelAllHaptics, __FUNCTION__)) { return; }
72+
sEnv->CallVoidMethod(sActivity, sCancelAllHaptics);
73+
CheckJNIException(sEnv, __FUNCTION__);
74+
}
75+
76+
} // namespace crow

app/src/picovr/cpp/VRBrowserPico.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5+
6+
#pragma once
7+
#include "vrb/MacroUtils.h"
8+
9+
#include <memory>
10+
#include <string>
11+
#include <jni.h>
12+
#include <functional>
13+
14+
namespace crow {
15+
16+
namespace VRBrowserPico {
17+
void InitializeJava(JNIEnv* aEnv, jobject aActivity);
18+
void ShutdownJava();
19+
void UpdateHaptics(jint aControllerIndex, jfloat aIntensity, jfloat aDuration);
20+
void CancelAllHaptics();
21+
} // namespace VRBrowser;
22+
23+
} // namespace crow

app/src/picovr/cpp/native-lib.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "BrowserWorld.h"
1010
#include "DeviceDelegatePicoVR.h"
11+
#include "VRBrowserPico.h"
1112
#include "vrb/GLError.h"
1213
#include "vrb/Logger.h"
1314
#include "vrb/RunnableQueue.h"
@@ -36,6 +37,7 @@ JNI_METHOD(void, nativeInitialize)
3637
(JNIEnv* aEnv, jobject aActivity, jint width, jint height, jobject aAssetManager, jint type, jint focusInex) {
3738
gDestroyed = false;
3839
sQueue->AttachToThread();
40+
VRBrowserPico::InitializeJava(aEnv, aActivity);
3941
if (!sDevice) {
4042
sDevice = crow::DeviceDelegatePicoVR::Create(BrowserWorld::Instance().GetRenderContext());
4143
}
@@ -61,6 +63,7 @@ JNI_METHOD(void, nativeDestroy)
6163
BrowserWorld::Instance().RegisterDeviceDelegate(nullptr);
6264
sDevice = nullptr;
6365
BrowserWorld::Destroy();
66+
VRBrowserPico::ShutdownJava();
6467
}
6568

6669
JNI_METHOD(void, nativePause)

app/src/picovr/java/org/mozilla/vrbrowser/PlatformActivity.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import android.os.Bundle;
1010
import android.util.Log;
1111

12+
import androidx.annotation.Keep;
1213
import androidx.annotation.NonNull;
1314

1415
import com.picovr.client.HbListener;
@@ -97,6 +98,7 @@ public void onBindService() {
9798
@Override
9899
protected void onPause() {
99100
if (mControllerManager != null) {
101+
cancelAllHaptics();
100102
mControllerManager.unbindService();
101103
} else if (mHbManager != null) {
102104
mHbManager.Pause();
@@ -315,6 +317,31 @@ public void onChannelChanged(int i, int i1) {
315317

316318
}
317319

320+
// Called by native
321+
@Keep
322+
@SuppressWarnings("unused")
323+
private void updateHaptics(int aControllerIndex, float aIntensity, float aDurationSeconds) {
324+
runOnUiThread(() -> {
325+
if (mControllerManager != null) {
326+
float intensity = 255.0f * Math.max(Math.min(aIntensity, 1.0f), 0.0f);
327+
int durationMs = Math.round(aDurationSeconds * 1000);
328+
ControllerClient.vibrateCV2ControllerStrength(intensity, durationMs, 1 - aControllerIndex);
329+
}
330+
});
331+
}
332+
333+
// Called by native
334+
@Keep
335+
@SuppressWarnings("unused")
336+
private void cancelAllHaptics() {
337+
runOnUiThread(() -> {
338+
if (mControllerManager != null) {
339+
ControllerClient.vibrateCV2ControllerStrength(0, 0, 0);
340+
ControllerClient.vibrateCV2ControllerStrength(0, 0, 1);
341+
}
342+
});
343+
}
344+
318345
protected native void nativeOnCreate();
319346
protected native void nativeInitialize(int width, int height, Object aAssetManager, int type, int focusIndex);
320347
protected native void nativeShutdown();

0 commit comments

Comments
 (0)