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

Add support for haptic feedback on Pico controllers #2932

Merged
merged 2 commits into from
Mar 9, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -67,6 +67,7 @@ target_sources(
PUBLIC
src/picovr/cpp/native-lib.cpp
src/picovr/cpp/DeviceDelegatePicoVR.cpp
src/picovr/cpp/VRBrowserPico.cpp
)
elseif(NOAPI)
target_sources(
23 changes: 21 additions & 2 deletions app/src/picovr/cpp/DeviceDelegatePicoVR.cpp
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@

#include "DeviceDelegatePicoVR.h"
#include "ElbowModel.h"
#include "BrowserEGLContext.h"
#include "VRBrowserPico.h"

#include <EGL/egl.h>
#include "vrb/CameraEye.h"
@@ -53,6 +53,7 @@ struct DeviceDelegatePicoVR::State {
float axisX = 0;
float axisY = 0;
ElbowModel::HandEnum hand;
int hapticFrameID = 0;
Controller()
: index(-1)
, created(false)
@@ -142,6 +143,16 @@ struct DeviceDelegatePicoVR::State {
}
}

void UpdateHaptics(Controller& aController) {
uint64_t inputFrameID = 0;
float pulseDuration = 0.0f, pulseIntensity = 0.0f;
controllerDelegate->GetHapticFeedback(aController.index, inputFrameID, pulseDuration, pulseIntensity);

if (aController.hapticFrameID != inputFrameID) {
VRBrowserPico::UpdateHaptics(aController.index, pulseIntensity, pulseDuration);
}
}

void UpdateControllers() {
for (int32_t i = 0; i < controllers.size(); ++i) {
if (!controllers[i].enabled) {
@@ -208,6 +219,10 @@ struct DeviceDelegatePicoVR::State {
}

controllerDelegate->SetTransform(i, transform);

if (controllerDelegate->GetHapticCount(i)) {
UpdateHaptics(controllers[i]);
}
}
}
};
@@ -227,6 +242,10 @@ DeviceDelegatePicoVR::SetRenderMode(const device::RenderMode aMode) {
return;
}
m.renderMode = aMode;
if (aMode == device::RenderMode::StandAlone) {
// Ensure that all haptics are cancelled when exiting WebVR
VRBrowserPico::CancelAllHaptics();
}
}

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

76 changes: 76 additions & 0 deletions app/src/picovr/cpp/VRBrowserPico.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "VRBrowserPico.h"
#include "vrb/ConcreteClass.h"
#include "vrb/Logger.h"
#include "JNIUtil.h"

namespace {

const char* sUpdateHapticsName = "updateHaptics";
const char* sUpdateHapticsSignature = "(IFF)V";
const char* sCancelAllHapticsName = "cancelAllHaptics";
const char* sCancelAllHapticsSignature = "()V";

JNIEnv* sEnv = nullptr;
jclass sBrowserClass = nullptr;
jobject sActivity = nullptr;
jmethodID sUpdateHaptics = nullptr;
jmethodID sCancelAllHaptics = nullptr;
}

namespace crow {

void
VRBrowserPico::InitializeJava(JNIEnv* aEnv, jobject aActivity) {
if (aEnv == sEnv) {
return;
}
sEnv = aEnv;
if (!sEnv) {
return;
}
sActivity = sEnv->NewGlobalRef(aActivity);
sBrowserClass = sEnv->GetObjectClass(sActivity);
if (!sBrowserClass) {
return;
}

sUpdateHaptics = FindJNIMethodID(sEnv, sBrowserClass, sUpdateHapticsName, sUpdateHapticsSignature);
sCancelAllHaptics = FindJNIMethodID(sEnv, sBrowserClass, sCancelAllHapticsName, sCancelAllHapticsSignature);
}

void
VRBrowserPico::ShutdownJava() {
if (!sEnv) {
return;
}
if (sActivity) {
sEnv->DeleteGlobalRef(sActivity);
sActivity = nullptr;
}

sBrowserClass = nullptr;
sUpdateHaptics = nullptr;
sCancelAllHaptics = nullptr;
sEnv = nullptr;
}


void
VRBrowserPico::UpdateHaptics(jint aControllerIndex, jfloat aIntensity, jfloat aDuration) {
if (!ValidateMethodID(sEnv, sActivity, sUpdateHaptics, __FUNCTION__)) { return; }
sEnv->CallVoidMethod(sActivity, sUpdateHaptics, aControllerIndex, aIntensity, aDuration);
CheckJNIException(sEnv, __FUNCTION__);
}
void
VRBrowserPico::CancelAllHaptics() {
if (!ValidateMethodID(sEnv, sActivity, sCancelAllHaptics, __FUNCTION__)) { return; }
sEnv->CallVoidMethod(sActivity, sCancelAllHaptics);
CheckJNIException(sEnv, __FUNCTION__);
}

} // namespace crow
23 changes: 23 additions & 0 deletions app/src/picovr/cpp/VRBrowserPico.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#pragma once
#include "vrb/MacroUtils.h"

#include <memory>
#include <string>
#include <jni.h>
#include <functional>

namespace crow {

namespace VRBrowserPico {
void InitializeJava(JNIEnv* aEnv, jobject aActivity);
void ShutdownJava();
void UpdateHaptics(jint aControllerIndex, jfloat aIntensity, jfloat aDuration);
void CancelAllHaptics();
} // namespace VRBrowser;

} // namespace crow
3 changes: 3 additions & 0 deletions app/src/picovr/cpp/native-lib.cpp
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@

#include "BrowserWorld.h"
#include "DeviceDelegatePicoVR.h"
#include "VRBrowserPico.h"
#include "vrb/GLError.h"
#include "vrb/Logger.h"
#include "vrb/RunnableQueue.h"
@@ -36,6 +37,7 @@ JNI_METHOD(void, nativeInitialize)
(JNIEnv* aEnv, jobject aActivity, jint width, jint height, jobject aAssetManager, jint type, jint focusInex) {
gDestroyed = false;
sQueue->AttachToThread();
VRBrowserPico::InitializeJava(aEnv, aActivity);
if (!sDevice) {
sDevice = crow::DeviceDelegatePicoVR::Create(BrowserWorld::Instance().GetRenderContext());
}
@@ -61,6 +63,7 @@ JNI_METHOD(void, nativeDestroy)
BrowserWorld::Instance().RegisterDeviceDelegate(nullptr);
sDevice = nullptr;
BrowserWorld::Destroy();
VRBrowserPico::ShutdownJava();
}

JNI_METHOD(void, nativePause)
27 changes: 27 additions & 0 deletions app/src/picovr/java/org/mozilla/vrbrowser/PlatformActivity.java
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@
import android.os.Bundle;
import android.util.Log;

import androidx.annotation.Keep;
import androidx.annotation.NonNull;

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

}

// Called by native
@Keep
@SuppressWarnings("unused")
private void updateHaptics(int aControllerIndex, float aIntensity, float aDurationSeconds) {
runOnUiThread(() -> {
if (mControllerManager != null) {
float intensity = 255.0f * Math.max(Math.min(aIntensity, 1.0f), 0.0f);
int durationMs = Math.round(aDurationSeconds * 1000);
ControllerClient.vibrateCV2ControllerStrength(intensity, durationMs, 1 - aControllerIndex);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to swap the hands to get the correct controller to vibrate.

}
});
}

// Called by native
@Keep
@SuppressWarnings("unused")
private void cancelAllHaptics() {
runOnUiThread(() -> {
if (mControllerManager != null) {
ControllerClient.vibrateCV2ControllerStrength(0, 0, 0);
ControllerClient.vibrateCV2ControllerStrength(0, 0, 1);
}
});
}

protected native void nativeOnCreate();
protected native void nativeInitialize(int width, int height, Object aAssetManager, int type, int focusIndex);
protected native void nativeShutdown();