diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 456f49746..cfb2fa140 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -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( diff --git a/app/src/picovr/cpp/DeviceDelegatePicoVR.cpp b/app/src/picovr/cpp/DeviceDelegatePicoVR.cpp index 4f174c437..777798d2c 100644 --- a/app/src/picovr/cpp/DeviceDelegatePicoVR.cpp +++ b/app/src/picovr/cpp/DeviceDelegatePicoVR.cpp @@ -5,7 +5,7 @@ #include "DeviceDelegatePicoVR.h" #include "ElbowModel.h" -#include "BrowserEGLContext.h" +#include "VRBrowserPico.h" #include #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); diff --git a/app/src/picovr/cpp/VRBrowserPico.cpp b/app/src/picovr/cpp/VRBrowserPico.cpp new file mode 100644 index 000000000..4b8a69d1f --- /dev/null +++ b/app/src/picovr/cpp/VRBrowserPico.cpp @@ -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 diff --git a/app/src/picovr/cpp/VRBrowserPico.h b/app/src/picovr/cpp/VRBrowserPico.h new file mode 100644 index 000000000..9b99ab11e --- /dev/null +++ b/app/src/picovr/cpp/VRBrowserPico.h @@ -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 +#include +#include +#include + +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 diff --git a/app/src/picovr/cpp/native-lib.cpp b/app/src/picovr/cpp/native-lib.cpp index 3c889f333..377651659 100644 --- a/app/src/picovr/cpp/native-lib.cpp +++ b/app/src/picovr/cpp/native-lib.cpp @@ -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) diff --git a/app/src/picovr/java/org/mozilla/vrbrowser/PlatformActivity.java b/app/src/picovr/java/org/mozilla/vrbrowser/PlatformActivity.java index 615fd59a6..90eb060fd 100644 --- a/app/src/picovr/java/org/mozilla/vrbrowser/PlatformActivity.java +++ b/app/src/picovr/java/org/mozilla/vrbrowser/PlatformActivity.java @@ -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); + } + }); + } + + // 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();