diff --git a/code/mobile/android/PhoneVR/.gitignore b/code/mobile/android/PhoneVR/.gitignore
index fa0251e2..706a185d 100644
--- a/code/mobile/android/PhoneVR/.gitignore
+++ b/code/mobile/android/PhoneVR/.gitignore
@@ -11,6 +11,7 @@
/ALVR
/cardboard
/gvr-android-sdk-1.200
+/arcore-android-sdk-main
/app/libraries
/app/libraries_gvr
diff --git a/code/mobile/android/PhoneVR/app/CMakeLists.txt b/code/mobile/android/PhoneVR/app/CMakeLists.txt
index 358126a1..c6014dae 100644
--- a/code/mobile/android/PhoneVR/app/CMakeLists.txt
+++ b/code/mobile/android/PhoneVR/app/CMakeLists.txt
@@ -19,6 +19,13 @@ add_library(native-lib-alvr SHARED
${LIB_SRC}
)
+# Import the ARCore (Google Play Services for AR) library.
+add_library(arcore SHARED IMPORTED)
+set_target_properties(arcore PROPERTIES IMPORTED_LOCATION
+ ${ARCORE_LIBPATH}/${ANDROID_ABI}/libarcore_sdk_c.so
+ INTERFACE_INCLUDE_DIRECTORIES ${ARCORE_INCLUDE}
+)
+
set(libs_dir ${CMAKE_CURRENT_SOURCE_DIR}/libraries)
set(alvr_build_dir ${CMAKE_CURRENT_SOURCE_DIR}/../ALVR/build/alvr_client_core)
@@ -31,10 +38,11 @@ target_include_directories(native-lib-alvr
# ALVR Headers
PUBLIC ${alvr_build_dir}
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../cardboard
+ PUBLIC ${ARCORE_INCLUDE}
)
target_link_libraries(native-lib-alvr
- log android EGL GLESv3 mediandk
+ log android EGL GLESv3 mediandk arcore
${alvr_build_dir}/${ANDROID_ABI}/libalvr_client_core.so
${libs_dir}/jni/${ANDROID_ABI}/libGfxPluginCardboard.so
)
diff --git a/code/mobile/android/PhoneVR/app/build.gradle b/code/mobile/android/PhoneVR/app/build.gradle
index 399bcf30..c3ca0810 100644
--- a/code/mobile/android/PhoneVR/app/build.gradle
+++ b/code/mobile/android/PhoneVR/app/build.gradle
@@ -7,6 +7,15 @@ apply plugin: 'com.google.gms.google-services'
apply from: '../versioning.gradle'
apply plugin: "com.diffplug.spotless"
+/*
+The arcore aar library contains the native shared libraries. These are
+extracted before building to a temporary directory.
+ */
+def arcore_libpath = "${rootProject.layout.buildDirectory.get()}/arcore-native"
+
+// Create a configuration to mark which aars to extract .so files from
+configurations { natives }
+
def keystorePropertiesFile = rootProject.file("keystore.properties")
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
@@ -50,6 +59,8 @@ android {
externalNativeBuild {
cmake {
cppFlags "-std=c++17 -fexceptions -frtti" //-DANDROID_USE_LEGACY_TOOLCHAIN_FILE=ON"
+ arguments "-DARCORE_LIBPATH=${arcore_libpath}/jni",
+ "-DARCORE_INCLUDE=${project.rootDir}/arcore-android-sdk-main/libraries/include"
}
}
@@ -174,6 +185,9 @@ dependencies {
implementation 'androidx.camera:camera-lifecycle:1.3.4'
implementation 'androidx.camera:camera-camera2:1.3.4'
+ implementation 'com.google.ar:core:1.46.0'
+ natives 'com.google.ar:core:1.46.0'
+
def acraVersion = '5.7.0'
implementation "ch.acra:acra-mail:$acraVersion"
implementation "ch.acra:acra-dialog:$acraVersion"
@@ -199,6 +213,13 @@ task extractNdk(type: Copy) {
include "jni/**/libgvr.so"
}
}
+ configurations.natives.files.each { f ->
+ copy {
+ from zipTree(f)
+ into arcore_libpath
+ include "jni/**/*"
+ }
+ }
}
task deleteNdk(type: Delete) {
diff --git a/code/mobile/android/PhoneVR/app/src/gvr/CMakeLists.txt b/code/mobile/android/PhoneVR/app/src/gvr/CMakeLists.txt
index 344e6b1b..10cf63d4 100644
--- a/code/mobile/android/PhoneVR/app/src/gvr/CMakeLists.txt
+++ b/code/mobile/android/PhoneVR/app/src/gvr/CMakeLists.txt
@@ -9,7 +9,6 @@ set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS} -O3 -std=c++17 -g")
set(ANDROID_STL c++_static)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH)
-
set(gvr_libs_dir ${CMAKE_CURRENT_SOURCE_DIR}/../../libraries_gvr)
file(GLOB_RECURSE LIB_SRC
../../../../../../common/libs/ifaddrs/*.c
diff --git a/code/mobile/android/PhoneVR/app/src/main/AndroidManifest.xml b/code/mobile/android/PhoneVR/app/src/main/AndroidManifest.xml
index c193327f..5a43f188 100644
--- a/code/mobile/android/PhoneVR/app/src/main/AndroidManifest.xml
+++ b/code/mobile/android/PhoneVR/app/src/main/AndroidManifest.xml
@@ -75,6 +75,8 @@
android:screenOrientation="landscape"
android:theme="@style/Theme.AppCompat.NoActionBar"
android:launchMode="singleTop" />
+
+
diff --git a/code/mobile/android/PhoneVR/app/src/main/cpp/alvr_main.cpp b/code/mobile/android/PhoneVR/app/src/main/cpp/alvr_main.cpp
index c6336b01..aae6b297 100644
--- a/code/mobile/android/PhoneVR/app/src/main/cpp/alvr_main.cpp
+++ b/code/mobile/android/PhoneVR/app/src/main/cpp/alvr_main.cpp
@@ -1,6 +1,8 @@
#include "alvr_client_core.h"
+#include "arcore_c_api.h"
#include "cardboard.h"
#include
+#include
#include
#include
#include
@@ -17,6 +19,11 @@ using namespace nlohmann;
uint64_t HEAD_ID = alvr_path_string_to_id("/user/head");
+// TODO: Make this configurable.
+// Using ARCore orientation is more accurate, but causes a ~0.5 second delay,
+// which is probably nauseating for most folks. TODO.
+bool useARCoreOrientation = false;
+
// Note: the Cardboard SDK cannot estimate display time and an heuristic is used instead.
const uint64_t VSYNC_QUEUE_INTERVAL_NS = 50e6;
const float FLOOR_HEIGHT = 1.5;
@@ -30,8 +37,18 @@ struct NativeContext {
CardboardLensDistortion *lensDistortion = nullptr;
CardboardDistortionRenderer *distortionRenderer = nullptr;
+ bool arcoreEnabled = false;
+ ArSession *arSession = nullptr;
+ ArFrame *arFrame = nullptr;
+ ArAnchor *arFloorAnchor = nullptr;
+ GLuint arTexture = 0;
+
+ AlvrQuat lastOrientation = {0.f, 0.f, 0.f, 0.f};
+ float lastPosition[3] = {0.f, 0.f, 0.f};
+
int screenWidth = 0;
int screenHeight = 0;
+ int screenRotation = 0;
bool renderingParamsChanged = true;
bool glContextRecreated = false;
@@ -107,13 +124,161 @@ AlvrFov getFov(CardboardEye eye) {
AlvrPose getPose(uint64_t timestampNs) {
AlvrPose pose = {};
+ bool returnLastPosition = false;
+
+ if (!CTX.arcoreEnabled || (CTX.arcoreEnabled && !useARCoreOrientation)) {
+ float pos[3];
+ float q[4];
+ CardboardHeadTracker_getPose(CTX.headTracker, (int64_t) timestampNs, kLandscapeLeft, pos, q);
+
+ auto inverseOrientation = AlvrQuat{q[0], q[1], q[2], q[3]};
+ pose.orientation = inverseQuat(inverseOrientation);
+ CTX.lastOrientation = pose.orientation;
+ }
+
+ if (CTX.arcoreEnabled && CTX.arSession != nullptr) {
+ if (eglGetCurrentContext() == EGL_NO_CONTEXT) {
+ throw std::runtime_error("Failed to get EGL context in getPose.");
+ returnLastPosition = false;
+ goto out;
+ }
+
+ int ret = ArSession_update(CTX.arSession, CTX.arFrame);
+ if (ret != AR_SUCCESS) {
+ error("getPose: ArSession_update failed (%d), using last position", ret);
+ returnLastPosition = true;
+ goto out;
+ }
+
+ ArCamera *arCamera = nullptr;
+ ArFrame_acquireCamera(CTX.arSession, CTX.arFrame, &arCamera);
+
+ ArTrackingState arTrackingState;
+ ArCamera_getTrackingState(CTX.arSession, arCamera, &arTrackingState);
+ if (arTrackingState != AR_TRACKING_STATE_TRACKING) {
+ error("getPose: Camera is not tracking, using last position");
+ if (arTrackingState == AR_TRACKING_STATE_PAUSED) {
+ error("- AR tracking state is PAUSED");
+ ArTrackingFailureReason failureReason;
+ ArCamera_getTrackingFailureReason(CTX.arSession, arCamera, &failureReason);
+ error("- Failure reason: %d", failureReason);
+ } else if (arTrackingState == AR_TRACKING_STATE_STOPPED) {
+ error("- AR tracking state is STOPPED");
+ }
+ returnLastPosition = true;
+ ArCamera_release(arCamera);
+ goto out;
+ }
+
+ ArPose *arPose = nullptr;
+ ArPose_create(CTX.arSession, nullptr, &arPose);
+ ArCamera_getDisplayOrientedPose(CTX.arSession, arCamera, arPose);
+ // ArPose_getPoseRaw() returns a pose in {qx, qy, qz, qw, tx, ty, tz} format.
+ float arRawPose[7] = {0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f};
+ ArPose_getPoseRaw(CTX.arSession, arPose, arRawPose);
+
+ /* We determine floor position by finding the lowest detected plane. We do this by
+ * placing an anchor in the center position of the plane, and if it's lower than the
+ * currently placed anchor (CTX.floorAnchor), we replace it.
+ *
+ * (By default, ARCore's "world coordinates" space begins wherever the device is, but this
+ * can desync over time. Anchor position adapts to world space movement. */
+ ArTrackableList *trackables = nullptr;
+ ArTrackableList_create(CTX.arSession, &trackables);
+ ArFrame_getUpdatedTrackables(CTX.arSession, CTX.arFrame, AR_TRACKABLE_PLANE, trackables);
+ int32_t detectedPlaneCount;
+ ArTrackableList_getSize(CTX.arSession, trackables, &detectedPlaneCount);
+
+ for (int i = 0; i < detectedPlaneCount; i++) {
+ ArTrackable* arTrackable = nullptr;
+ ArTrackableList_acquireItem(CTX.arSession, trackables, i,
+ &arTrackable);
+ const ArPlane *plane = ArAsPlane(arTrackable);
+ ArTrackingState planeTrackingState;
+ ArTrackable_getTrackingState(CTX.arSession, arTrackable, &planeTrackingState);
+ ArPlaneType planeType;
+ ArPlane_getType(CTX.arSession, plane, &planeType);
+ if (planeTrackingState == AR_TRACKING_STATE_TRACKING &&
+ planeType == AR_PLANE_HORIZONTAL_UPWARD_FACING) {
+ ArPose *planePose = nullptr;
+ ArPose_create(CTX.arSession, nullptr, &planePose);
+ ArPlane_getCenterPose(CTX.arSession, plane, planePose);
+
+ bool reanchor = false;
+ if (CTX.arFloorAnchor == nullptr) {
+ reanchor = true;
+ } else {
+ ArPose *currentFloorPose = nullptr;
+ ArPose_create(CTX.arSession, nullptr, ¤tFloorPose);
+
+ ArAnchor_getPose(CTX.arSession, CTX.arFloorAnchor, currentFloorPose);
+ float currentFloorPoseRaw[7] = {0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f};
+ ArPose_getPoseRaw(CTX.arSession, currentFloorPose, currentFloorPoseRaw);
+
+ float planePoseRaw[7] = {0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f};
+ ArPose_getPoseRaw(CTX.arSession, planePose, planePoseRaw);
+
+ if (planePoseRaw[5] < currentFloorPoseRaw[5]) {
+ info("Found new plane lower than pose (current %f vs new %f), reanchoring",
+ currentFloorPoseRaw[5], planePoseRaw[5]);
+ reanchor = true;
+ }
+ }
+
+ if (reanchor) {
+ if (CTX.arFloorAnchor != nullptr) {
+ ArAnchor_detach(CTX.arSession, CTX.arFloorAnchor);
+ ArAnchor_release(CTX.arFloorAnchor);
+ }
+
+ ArPose *planePoseNoRotation = ArPose_extractTranslation(CTX.arSession,
+ planePose);
+ ArTrackable_acquireNewAnchor(CTX.arSession, arTrackable, planePoseNoRotation,
+ &CTX.arFloorAnchor);
+ ArPose_destroy(planePoseNoRotation);
+ }
+
+ ArPose_destroy(planePose);
+ }
+ }
+
+ ArTrackableList_destroy(trackables);
+
+ float anchorRawPose[7] = {0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f};
+ if (CTX.arFloorAnchor != nullptr) {
+ ArPose *anchorPose = nullptr;
+ ArPose_create(CTX.arSession, nullptr, &anchorPose);
+ ArAnchor_getPose(CTX.arSession, CTX.arFloorAnchor, anchorPose);
+ ArPose_getPoseRaw(CTX.arSession, anchorPose, anchorRawPose);
+ info("anchor pose %f %f %f %f %f %f %f", anchorRawPose[0], anchorRawPose[1], anchorRawPose[2], anchorRawPose[3], anchorRawPose[4], anchorRawPose[5], anchorRawPose[6]);
+ }
+
+ pose.position[0] = arRawPose[4];
+ pose.position[1] = arRawPose[5] - anchorRawPose[5];
+ pose.position[2] = arRawPose[6];
- float pos[3];
- float q[4];
- CardboardHeadTracker_getPose(CTX.headTracker, (int64_t) timestampNs, kLandscapeLeft, pos, q);
+ for (int i = 0; i < 3; i++) {
+ CTX.lastPosition[i] = arRawPose[i + 4];
+ }
- auto inverseOrientation = AlvrQuat{q[0], q[1], q[2], q[3]};
- pose.orientation = inverseQuat(inverseOrientation);
+ if (useARCoreOrientation) {
+ auto orientation = AlvrQuat{arRawPose[0], arRawPose[1], arRawPose[2],
+ arRawPose[3]};
+ pose.orientation = orientation;
+ CTX.lastOrientation = pose.orientation;
+ }
+
+ ArPose_destroy(arPose);
+ ArCamera_release(arCamera);
+ }
+
+out:
+ if (returnLastPosition) {
+ pose.orientation = CTX.lastOrientation;
+ for (int i = 0; i < 3; i++) {
+ pose.position[i] = CTX.lastPosition[i];
+ }
+ }
return pose;
}
@@ -142,6 +307,70 @@ void updateViewConfigs(uint64_t targetTimestampNs = 0) {
void inputThread() {
auto deadline = std::chrono::steady_clock::now();
+ if (CTX.arcoreEnabled) {
+ /* ARCore requires an EGL context to work. Since we're calling it from a secondary
+ * thread that is not the main GL thread, we need to provide our own context. */
+ info("inputThread: creating ARCore EGL context for input thread");
+ // 1. Initialize EGL
+ EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ if (display == EGL_NO_DISPLAY) {
+ throw std::runtime_error("Failed to get EGL display.");
+ }
+
+ if (!eglInitialize(display, nullptr, nullptr)) {
+ throw std::runtime_error("Failed to initialize EGL.");
+ }
+
+ // 2. Choose EGL configuration
+ EGLint numConfigs;
+ EGLConfig config;
+ EGLint configAttribs[] = {
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
+ EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
+ EGL_BLUE_SIZE, 8,
+ EGL_GREEN_SIZE, 8,
+ EGL_RED_SIZE, 8,
+ EGL_ALPHA_SIZE, 8,
+ EGL_NONE
+ };
+
+ if (!eglChooseConfig(display, configAttribs, &config, 1, &numConfigs)) {
+ throw std::runtime_error("Failed to choose EGL config.");
+ }
+
+ if (numConfigs == 0) {
+ throw std::runtime_error("No suitable EGL configurations found.");
+ }
+
+ // 3. Create an offscreen (pbuffer) surface
+ EGLint pbufferAttribs[] = {
+ EGL_WIDTH, 1920,
+ EGL_HEIGHT, 1920,
+ EGL_NONE
+ };
+
+ EGLSurface surface = eglCreatePbufferSurface(display, config, pbufferAttribs);
+ if (surface == EGL_NO_SURFACE) {
+ throw std::runtime_error("Failed to create EGL pbuffer surface.");
+ }
+
+ // 4. Create an EGL context
+ EGLint contextAttribs[] = {
+ EGL_CONTEXT_CLIENT_VERSION, 3, // OpenGL ES 3.0 context
+ EGL_NONE
+ };
+
+ EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs);
+ if (context == EGL_NO_CONTEXT) {
+ throw std::runtime_error("Failed to create EGL context.");
+ }
+
+ // 5. Bind the context to the current thread
+ if (!eglMakeCurrent(display, surface, surface, context)) {
+ throw std::runtime_error("Failed to make EGL context current.");
+ }
+ }
+
info("inputThread: thread staring...");
while (CTX.streaming) {
@@ -162,7 +391,7 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *) {
}
extern "C" JNIEXPORT void JNICALL Java_viritualisres_phonevr_ALVRActivity_initializeNative(
- JNIEnv *env, jobject obj, jint screenWidth, jint screenHeight, jfloat refreshRate) {
+ JNIEnv *env, jobject obj, jint screenWidth, jint screenHeight, jfloat refreshRate, jboolean enableARCore) {
CTX.javaContext = env->NewGlobalRef(obj);
uint32_t viewWidth = std::max(screenWidth, screenHeight) / 2;
@@ -188,6 +417,37 @@ extern "C" JNIEXPORT void JNICALL Java_viritualisres_phonevr_ALVRActivity_initia
Cardboard_initializeAndroid(CTX.javaVm, CTX.javaContext);
CTX.headTracker = CardboardHeadTracker_create();
+
+ CTX.arcoreEnabled = (bool) enableARCore;
+ if (CTX.arcoreEnabled) {
+ if (ArSession_create(env, CTX.javaContext, &CTX.arSession) != AR_SUCCESS) {
+ error("initializeNative: Could not create ARCore session");
+ CTX.arcoreEnabled = false;
+ return;
+ }
+
+ ArConfig* arConfig = nullptr;
+ ArConfig_create(CTX.arSession, &arConfig);
+
+ // Explicitly disable all unnecessary features to preserve CPU power.
+ ArConfig_setDepthMode(CTX.arSession, arConfig, AR_DEPTH_MODE_DISABLED);
+ ArConfig_setLightEstimationMode(CTX.arSession, arConfig, AR_LIGHT_ESTIMATION_MODE_DISABLED);
+ ArConfig_setPlaneFindingMode(CTX.arSession, arConfig, AR_PLANE_FINDING_MODE_HORIZONTAL_AND_VERTICAL);
+ ArConfig_setCloudAnchorMode(CTX.arSession, arConfig, AR_CLOUD_ANCHOR_MODE_DISABLED);
+
+ // Set "latest camera image" update mode (ArSession_update returns immediately without blocking)
+ ArConfig_setUpdateMode(CTX.arSession, arConfig, AR_UPDATE_MODE_LATEST_CAMERA_IMAGE);
+
+ // TODO: Add camera config filter:
+ // https://developers.google.com/ar/develop/c/camera-configs
+
+ if (ArSession_configure(CTX.arSession, arConfig) != AR_SUCCESS) {
+ error("initializeNative: Could not configure ARCore session");
+ return;
+ }
+
+ ArFrame_create(CTX.arSession, &CTX.arFrame);
+ }
}
extern "C" JNIEXPORT void JNICALL Java_viritualisres_phonevr_ALVRActivity_destroyNative(JNIEnv *,
@@ -201,11 +461,25 @@ extern "C" JNIEXPORT void JNICALL Java_viritualisres_phonevr_ALVRActivity_destro
CTX.lensDistortion = nullptr;
CardboardDistortionRenderer_destroy(CTX.distortionRenderer);
CTX.distortionRenderer = nullptr;
+
+ if (CTX.arcoreEnabled && CTX.arSession != nullptr) {
+ ArSession_destroy(CTX.arSession);
+ CTX.arSession = nullptr;
+
+ ArFrame_destroy(CTX.arFrame);
+ CTX.arFrame = nullptr;
+ }
}
extern "C" JNIEXPORT void JNICALL Java_viritualisres_phonevr_ALVRActivity_resumeNative(JNIEnv *,
jobject) {
CardboardHeadTracker_resume(CTX.headTracker);
+ if (CTX.arcoreEnabled && CTX.arSession != nullptr) {
+ ArStatus arSessionStatus = ArSession_resume(CTX.arSession);
+ if (arSessionStatus != AR_SUCCESS) {
+ error("Failed to resume tracking: %d", arSessionStatus);
+ }
+ }
CTX.renderingParamsChanged = true;
@@ -248,6 +522,12 @@ extern "C" JNIEXPORT void JNICALL Java_viritualisres_phonevr_ALVRActivity_setScr
CTX.renderingParamsChanged = true;
}
+extern "C" JNIEXPORT void JNICALL Java_viritualisres_phonevr_ALVRActivity_setScreenRotationNative(
+ JNIEnv *, jobject, jint rotation) {
+ CTX.screenRotation = rotation;
+ CTX.renderingParamsChanged = true;
+}
+
extern "C" JNIEXPORT void JNICALL Java_viritualisres_phonevr_ALVRActivity_sendBatteryLevel(
JNIEnv *, jobject, jfloat level, jboolean plugged) {
alvr_send_battery(HEAD_ID, level, plugged);
@@ -301,6 +581,11 @@ extern "C" JNIEXPORT void JNICALL Java_viritualisres_phonevr_ALVRActivity_render
CTX.fovArr[kLeft] = getFov(kLeft);
CTX.fovArr[kRight] = getFov(kRight);
+ if (CTX.arcoreEnabled && CTX.arSession != nullptr) {
+ ArSession_setDisplayGeometry(
+ CTX.arSession, CTX.screenRotation, CTX.screenWidth, CTX.screenHeight);
+ }
+
info("renderingParamsChanged, updating new view configs (FOV) to alvr");
// alvr_send_views_config(fovArr, CTX.eyeOffsets[0] - CTX.eyeOffsets[1]);
}
@@ -341,6 +626,20 @@ extern "C" JNIEXPORT void JNICALL Java_viritualisres_phonevr_ALVRActivity_render
(uint32_t *) &CTX.lobbyTextures[1]};
alvr_resume_opengl(CTX.screenWidth / 2, CTX.screenHeight, targetViews, 1, true);
+ if (CTX.arcoreEnabled && CTX.arSession != nullptr) {
+ GLuint arTextureIdArray[1];
+ glGenTextures(1, arTextureIdArray);
+ CTX.arTexture = arTextureIdArray[0];
+
+ GL(glBindTexture(GL_TEXTURE_2D, CTX.arTexture));
+ GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
+ GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
+ GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
+ GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
+
+ ArSession_setCameraTextureName(CTX.arSession, CTX.arTexture);
+ }
+
CTX.renderingParamsChanged = false;
CTX.glContextRecreated = false;
}
@@ -508,7 +807,7 @@ extern "C" JNIEXPORT void JNICALL Java_viritualisres_phonevr_ALVRActivity_render
// offset head pos to Eye Position
offsetPosWithQuat(pose.orientation, headToEye, viewInputs[eye].pose.position);
- viewInputs[eye].pose.orientation = pose.orientation;
+ viewInputs[eye].pose = pose;
viewInputs[eye].fov = getFov((CardboardEye) eye);
viewInputs[eye].swapchain_index = 0;
}
diff --git a/code/mobile/android/PhoneVR/app/src/main/cpp/utils.h b/code/mobile/android/PhoneVR/app/src/main/cpp/utils.h
index bc31f22b..3f1d5c63 100644
--- a/code/mobile/android/PhoneVR/app/src/main/cpp/utils.h
+++ b/code/mobile/android/PhoneVR/app/src/main/cpp/utils.h
@@ -2,6 +2,7 @@
#define PHONEVR_UTILS_H
#include "alvr_client_core.h"
+#include "arcore_c_api.h"
#include
#include
#include
@@ -12,7 +13,7 @@
const char LOG_TAG[] = "ALVR_PVR_NATIVE";
-void log(AlvrLogLevel level,
+static void log(AlvrLogLevel level,
const char *FILE_NAME,
unsigned int LINE_NO,
const char *FUNC,
@@ -87,4 +88,22 @@ static const char *GlErrorString(GLenum error) {
func; \
GLCheckErrors(__FILE__, __LINE__)
+/* Returns a pose having the translation of this pose but no rotation.
+ * The caller is responsible for freeing the original pose and the output pose.
+ * (C++ port of Pose.extractTranslation.) */
+static ArPose *ArPose_extractTranslation(ArSession *session, ArPose *in_pose) {
+ float raw_pose[7] = {0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f};
+ ArPose_getPoseRaw(session, in_pose, raw_pose);
+
+ raw_pose[0] = 0.f;
+ raw_pose[1] = 0.f;
+ raw_pose[2] = 0.f;
+ raw_pose[3] = 0.f;
+
+ ArPose *out_pose = nullptr;
+ ArPose_create(session, raw_pose, &out_pose);
+
+ return out_pose;
+}
+
#endif // PHONEVR_UTILS_H
diff --git a/code/mobile/android/PhoneVR/app/src/main/java/viritualisres/phonevr/ALVRActivity.java b/code/mobile/android/PhoneVR/app/src/main/java/viritualisres/phonevr/ALVRActivity.java
index b5b09f56..b48000d3 100644
--- a/code/mobile/android/PhoneVR/app/src/main/java/viritualisres/phonevr/ALVRActivity.java
+++ b/code/mobile/android/PhoneVR/app/src/main/java/viritualisres/phonevr/ALVRActivity.java
@@ -27,6 +27,9 @@
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
+import com.google.ar.core.ArCoreApk;
+import com.google.ar.core.exceptions.UnavailableDeviceNotCompatibleException;
+import com.google.ar.core.exceptions.UnavailableUserDeclinedInstallationException;
import java.util.Objects;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
@@ -40,6 +43,8 @@ public class ALVRActivity extends AppCompatActivity
private static final String TAG = ALVRActivity.class.getSimpleName() + "-Java";
+ private boolean surfaceChanged = false;
+
// Permission request codes
private static final int PERMISSIONS_REQUEST_CODE = 2;
@@ -105,7 +110,8 @@ public void onCreate(Bundle savedInstance) {
float refreshRate = display.getRefreshRate();
Log.i(TAG, "Refresh rate: " + refreshRate);
- initializeNative(width, height, refreshRate);
+ // TODO: config option for ARCore
+ initializeNative(width, height, refreshRate, true);
setContentView(R.layout.activity_vr);
glView = findViewById(R.id.surface_view);
@@ -163,6 +169,16 @@ protected void onResume() {
return;
}
+ // TODO check if we actually want ARCore
+ try {
+ ArCoreApk.getInstance().requestInstall(this, true);
+ } catch (UnavailableDeviceNotCompatibleException e) {
+ Toast.makeText(this, R.string.arcore_not_supported, Toast.LENGTH_LONG).show();
+ // TODO disable ARCore toggle
+ } catch (UnavailableUserDeclinedInstallationException e) {
+ // TODO disable ARCore toggle
+ }
+
glView.onResume();
resumeNative();
bMonitor.startMonitoring(this);
@@ -192,10 +208,15 @@ public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
@Override
public void onSurfaceChanged(GL10 gl10, int width, int height) {
setScreenResolutionNative(width, height);
+ surfaceChanged = true;
}
@Override
public void onDrawFrame(GL10 gl10) {
+ if (surfaceChanged) {
+ setScreenRotationNative(getWindowManager().getDefaultDisplay().getRotation());
+ surfaceChanged = false;
+ }
renderNative();
}
}
@@ -285,7 +306,7 @@ private void setImmersiveSticky() {
}
private native void initializeNative(
- int screenWidth, int screenHeight, float screenRefreshRate);
+ int screenWidth, int screenHeight, float screenRefreshRate, boolean enableARCore);
private native void destroyNative();
@@ -297,6 +318,8 @@ private native void initializeNative(
private native void setScreenResolutionNative(int width, int height);
+ private native void setScreenRotationNative(int displayRotation);
+
private native void renderNative();
private native void switchViewerNative();
diff --git a/code/mobile/android/PhoneVR/app/src/main/res/values/strings.xml b/code/mobile/android/PhoneVR/app/src/main/res/values/strings.xml
index ce5664f9..574d09cd 100644
--- a/code/mobile/android/PhoneVR/app/src/main/res/values/strings.xml
+++ b/code/mobile/android/PhoneVR/app/src/main/res/values/strings.xml
@@ -42,4 +42,5 @@
Switch PhoneVR Server
Remember choice
Max Brightness
+ Your device does not support ARCore; position tracking has been disabled
diff --git a/code/mobile/android/PhoneVR/prepare-alvr-deps.sh b/code/mobile/android/PhoneVR/prepare-alvr-deps.sh
index 524bb187..e81246bb 100644
--- a/code/mobile/android/PhoneVR/prepare-alvr-deps.sh
+++ b/code/mobile/android/PhoneVR/prepare-alvr-deps.sh
@@ -51,3 +51,7 @@ unzip download.zip
rm download.zip
fi
+rm -r "arcore-android-sdk"
+curl -sLS https://github.com/google-ar/arcore-android-sdk/archive/refs/heads/main.zip > download.zip
+unzip download.zip
+rm download.zip
\ No newline at end of file