diff options
author | ziadb <ziadb@google.com> | 2018-06-19 13:57:15 -0400 |
---|---|---|
committer | Ziad Ben Hadj-Alouane <ziadb@google.com> | 2018-06-19 19:10:54 +0000 |
commit | bceddbcb72605343aca01638f64d7a8e8cb9792d (patch) | |
tree | ed4eaaec1011ca860cd2c750c2f241472543824b | |
parent | 80452bee11ebe6708ea459ea34e526a44c04bdb0 (diff) |
SkAR: drawing text, shapes, rotation modes enabled, translating objects
To run this app, you need to create an out directory as such:
bin/gn gen out/arm64 --args='ndk="NDK_PATH" target_cpu="ABI"'
For now, the only supported ABI is arm64
Change-Id: I012f0c6a0550d80a0028f42177d5ca72974d848d
Bug: skia:
Reviewed-on: https://skia-review.googlesource.com/130980
Reviewed-by: Mike Reed <reed@google.com>
Reviewed-by: Mike Klein <mtklein@google.com>
Commit-Queue: Ziad Ben Hadj-Alouane <ziadb@google.com>
45 files changed, 5886 insertions, 21 deletions
diff --git a/.gitignore b/.gitignore index 77f711db2f..0de012469b 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,10 @@ platform_tools/android/apps/build platform_tools/android/apps/*.properties platform_tools/android/apps/*/build platform_tools/android/apps/*/src/main/libs +platform_tools/android/apps/*.hprof +platform_tools/android/apps/*/.externalNativeBuild + +sampleapp_prefs.txt /skps third_party/externals tools/skp/page_sets/data/*.json @@ -1995,6 +1995,60 @@ if (skia_enable_tools) { ] } + if (is_android) { + test_app("arcore") { + is_shared_library = true + configs = [ + ":skia_public", + "gn:default", + ] + + # For internship expedience, yes, we're rebuilding Skia rather than depending on :skia. + # At the moment there's no way to use Skia and Skottie/SkShaper unless they're in the same .so. + sources = [] + sources += skia_core_sources + sources += skia_utils_sources + sources += skia_xps_sources + sources += [ + "src/android/SkAndroidFrameworkUtils.cpp", + "src/android/SkAnimatedImage.cpp", + "src/android/SkBitmapRegionCodec.cpp", + "src/android/SkBitmapRegionDecoder.cpp", + "src/codec/SkAndroidCodec.cpp", + "src/codec/SkBmpBaseCodec.cpp", + "src/codec/SkBmpCodec.cpp", + "src/codec/SkBmpMaskCodec.cpp", + "src/codec/SkBmpRLECodec.cpp", + "src/codec/SkBmpStandardCodec.cpp", + "src/codec/SkCodec.cpp", + "src/codec/SkCodecImageGenerator.cpp", + "src/codec/SkColorTable.cpp", + "src/codec/SkGifCodec.cpp", + "src/codec/SkMaskSwizzler.cpp", + "src/codec/SkMasks.cpp", + "src/codec/SkSampledCodec.cpp", + "src/codec/SkSampler.cpp", + "src/codec/SkStreamBuffer.cpp", + "src/codec/SkSwizzler.cpp", + "src/codec/SkWbmpCodec.cpp", + "src/images/SkImageEncoder.cpp", + "src/ports/SkDiscardableMemory_none.cpp", + "src/ports/SkImageGenerator_skia.cpp", + "src/ports/SkMemory_malloc.cpp", + "src/ports/SkOSFile_stdio.cpp", + "src/sfnt/SkOTTable_name.cpp", + "src/sfnt/SkOTUtils.cpp", + "src/utils/mac/SkStream_mac.cpp", + "third_party/gif/SkGifImageReader.cpp", + ] + deps = [ + ":tool_utils", + "modules/skottie", + "modules/skshaper", + ] + } + } + if (!skia_use_angle && (is_linux || is_win || is_mac)) { test_app("HelloWorld") { sources = [ diff --git a/include/utils/Sk3D.h b/include/utils/Sk3D.h new file mode 100644 index 0000000000..f18281185b --- /dev/null +++ b/include/utils/Sk3D.h @@ -0,0 +1,19 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef Sk3D_DEFINED +#define Sk3D_DEFINED + +#include "SkPoint3.h" +#include "SkMatrix44.h" + +SK_API void Sk3LookAt(SkMatrix44* dst, const SkPoint3& eye, const SkPoint3& center, const SkPoint3& up); +SK_API bool Sk3Perspective(SkMatrix44* dst, float near, float far, float angle); +SK_API void Sk3MapPts(SkPoint dst[], const SkMatrix44& m4, const SkPoint3 src[], int count); + +#endif + diff --git a/platform_tools/android/apps/arcore/CMakeLists.txt b/platform_tools/android/apps/arcore/CMakeLists.txt new file mode 100644 index 0000000000..92c5968c95 --- /dev/null +++ b/platform_tools/android/apps/arcore/CMakeLists.txt @@ -0,0 +1,74 @@ +# Copyright (C) 2018 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +## + +# Sets the minimum version of CMake required to build the native library. +cmake_minimum_required(VERSION 3.4.1) + +# Import the ARCore library. +add_library(arcore SHARED IMPORTED) +set_target_properties(arcore PROPERTIES IMPORTED_LOCATION + "${ARCORE_LIBPATH}/${ANDROID_ABI}/libarcore_sdk_c.so") + +add_library(sk_skia SHARED IMPORTED) +set_target_properties(sk_skia PROPERTIES IMPORTED_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}/src/main/libs/${ANDROID_ABI}/libarcore.so") + +# This is the main app library. +add_library(hello_ar_native SHARED + "src/main/cpp/hello_ar_application.cc" + "src/main/cpp/background_renderer.cc" + "src/main/cpp/jni_interface.cc" + "src/main/cpp/plane_renderer.cc" + "src/main/cpp/point_cloud_renderer.cc" + "src/main/cpp/util.cc" + "src/main/cpp/pending_anchor.cc" + "src/main/cpp/anchor_wrapper.cc") + +target_include_directories(hello_ar_native PRIVATE + #BASIC AR NATIVE CODE + "src/main/cpp" + + #ARCORE LIBRARY + "${ARCORE_INCLUDE}" + + #GLM + "${ANDROID_NDK}/sources/third_party/vulkan/src/libs/glm" + + #SKIA INCLUDE DIRECTORIES + "${SKIA_INCLUDE_PATH}/../modules/skshaper/include" + "${SKIA_INCLUDE_PATH}/../modules/skottie/include" + "${SKIA_INCLUDE_PATH}/../tools" + "${SKIA_INCLUDE_PATH}/../gm" + "${SKIA_INCLUDE_PATH}/core" + "${SKIA_INCLUDE_PATH}/config" + "${SKIA_INCLUDE_PATH}/gpu" + "${SKIA_INCLUDE_PATH}/android" + "${SKIA_INCLUDE_PATH}/atlastext" + "${SKIA_INCLUDE_PATH}/c" + "${SKIA_INCLUDE_PATH}/codec" + "${SKIA_INCLUDE_PATH}/effects" + "${SKIA_INCLUDE_PATH}/encode" + "${SKIA_INCLUDE_PATH}/pathops" + "${SKIA_INCLUDE_PATH}/ports" + "${SKIA_INCLUDE_PATH}/private" + "${SKIA_INCLUDE_PATH}/svg" + "${SKIA_INCLUDE_PATH}/utils" + "${SKIA_INCLUDE_PATH}/views") + +target_link_libraries(hello_ar_native + android + log + GLESv2 + arcore + sk_skia) diff --git a/platform_tools/android/apps/arcore/build.gradle b/platform_tools/android/apps/arcore/build.gradle new file mode 100644 index 0000000000..0e82ab8831 --- /dev/null +++ b/platform_tools/android/apps/arcore/build.gradle @@ -0,0 +1,92 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +apply plugin: 'com.android.application' + +/* +The arcore aar library contains the native shared libraries. These are +extracted before building to a temporary directory. + */ +def arcore_libpath = "${buildDir}/arcore-native" + +// Create a configuration to mark which aars to extract .so files from +configurations { natives } + +android { + sourceSets.main.jni.srcDirs = [] //disable automatic ndk-build call + sourceSets.main.jniLibs.srcDir "src/main/libs" + productFlavors { arm64 {} } + + setupSkiaLibraryBuild(project, applicationVariants, "libarcore") + + compileSdkVersion 27 + defaultConfig { + applicationId "org.skia.viewer" + // 24 is the minimum since ARCore only works with 24 and higher. + minSdkVersion 24 + targetSdkVersion 27 + versionCode 1 + versionName "1.0" + + externalNativeBuild { + cmake { + cppFlags "-std=c++11", "-Wall" + arguments "-DANDROID_STL=c++_static", + "-DARCORE_LIBPATH=${arcore_libpath}/jni", + "-DARCORE_INCLUDE=${project.rootDir}/../../libraries/include", + "-DSKIA_INCLUDE_PATH=${project.rootDir}/../../../include" + } + } + ndk { + abiFilters "arm64-v8a" + } + } + flavorDimensions "base" + externalNativeBuild { + cmake { + path "CMakeLists.txt" + } + } + +} + +dependencies { + // ARCore library + implementation 'com.google.ar:core:1.2.0' + natives 'com.google.ar:core:1.2.0' + + implementation 'com.android.support:appcompat-v7:27.0.2' + implementation 'com.android.support:design:27.0.2' +} + +// Extracts the shared libraries from aars in the natives configuration. +// This is done so that NDK builds can access these libraries. +task extractNativeLibraries() { + doFirst { + configurations.natives.files.each { f -> + copy { + from zipTree(f) + into arcore_libpath + include "jni/**/*" + } + } + } +} + + +tasks.whenTaskAdded { + task-> if (task.name.contains("external") && !task.name.contains("Clean")) { + task.dependsOn(extractNativeLibraries) + + //make sure skia lib is built and copied in the correct directory before building arcore + tasks.whenTaskAdded { + t-> if (t.name.contains("CopySkiaLib")) { + task.dependsOn(t) + } + } + } +} + diff --git a/platform_tools/android/apps/arcore/src/main/AndroidManifest.xml b/platform_tools/android/apps/arcore/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..975dc2592d --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/AndroidManifest.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="org.skia.arcore"> + + <uses-permission android:name="android.permission.CAMERA"/> + <!-- This tag indicates that this application requires ARCore. This results in the application + only being visible in the Google Play Store on devices that support ARCore. --> + <uses-feature android:name="android.hardware.camera.ar" android:required="true"/> + <uses-feature android:glEsVersion="0x00020000" android:required="true" /> + + <application + android:allowBackup="true" + android:icon="@drawable/ic_launcher" + android:label="@string/app_name" + android:theme="@style/Theme.AppCompat.Light.NoActionBar" + android:usesCleartextTraffic="false"> + <!-- This tag indicates that this application requires ARCore. This results in the Google Play + Store downloading and installing ARCore along with the application. --> + <meta-data android:name="com.google.ar.core" android:value="required" /> + + <activity + android:name=".HelloArActivity" + android:label="@string/app_name" + android:configChanges="orientation|screenSize" + android:exported="true" + android:theme="@style/Theme.AppCompat.DayNight.NoActionBar" + + android:screenOrientation="locked"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/platform_tools/android/apps/arcore/src/main/assets/models/trigrid.png b/platform_tools/android/apps/arcore/src/main/assets/models/trigrid.png Binary files differnew file mode 100644 index 0000000000..d85eedf72c --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/assets/models/trigrid.png diff --git a/platform_tools/android/apps/arcore/src/main/cpp/anchor_wrapper.cc b/platform_tools/android/apps/arcore/src/main/cpp/anchor_wrapper.cc new file mode 100644 index 0000000000..832a8b001b --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/cpp/anchor_wrapper.cc @@ -0,0 +1,41 @@ +/* + * Copyright 2018 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hello_ar_application.h" +#include "arcore_c_api.h" +#include "anchor_wrapper.h" + +namespace hello_ar { + + AnchorWrapper::AnchorWrapper(ArAnchor *anchor) : anchor(anchor) {} + + const ArAnchor* AnchorWrapper::GetArAnchor() { + return anchor; + } + DrawableType AnchorWrapper::GetDrawableType() { + return drawableType; + } + + void AnchorWrapper::SetArAnchor(ArAnchor* anchor) { + this->anchor = anchor; + } + void AnchorWrapper::SetDrawableType(DrawableType drawableType) { + this->drawableType = drawableType; + } + + + +} // namespace hello_ar diff --git a/platform_tools/android/apps/arcore/src/main/cpp/anchor_wrapper.h b/platform_tools/android/apps/arcore/src/main/cpp/anchor_wrapper.h new file mode 100644 index 0000000000..ff05e57010 --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/cpp/anchor_wrapper.h @@ -0,0 +1,45 @@ +/* + * Copyright 2018 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef C_ARCORE_HELLO_AR_ANCHOR_WRAPPER_H_ +#define C_ARCORE_HELLO_AR_ANCHOR_WRAPPER_H_ +#include "arcore_c_api.h" + +namespace hello_ar { + enum DrawableType { + TEXT = 0, CIRCLE = 1, RECT = 2 + }; + + class AnchorWrapper { + public: + AnchorWrapper(ArAnchor* anchor); + + const ArAnchor* GetArAnchor(); + DrawableType GetDrawableType(); + bool GetInEditMode(); + + void SetArAnchor(ArAnchor* anchor); + void SetDrawableType(DrawableType drawableType); + void SetInEditMode(bool inEditMode); + + private: + ArAnchor* anchor; + DrawableType drawableType; + bool inEditMode = false; + }; +} // namespace hello_ar + +#endif diff --git a/platform_tools/android/apps/arcore/src/main/cpp/background_renderer.cc b/platform_tools/android/apps/arcore/src/main/cpp/background_renderer.cc new file mode 100644 index 0000000000..ea7a7c7eb1 --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/cpp/background_renderer.cc @@ -0,0 +1,113 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// This modules handles drawing the passthrough camera image into the OpenGL +// scene. + +#include <type_traits> + +#include "background_renderer.h" + +namespace hello_ar { + namespace { +// Positions of the quad vertices in clip space (X, Y, Z). + const GLfloat kVertices[] = { + -1.0f, -1.0f, 0.0f, +1.0f, -1.0f, 0.0f, + -1.0f, +1.0f, 0.0f, +1.0f, +1.0f, 0.0f, + }; + +// UVs of the quad vertices (S, T) + const GLfloat kUvs[] = { + 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + }; + + constexpr char kVertexShader[] = R"( + attribute vec4 vertex; + attribute vec2 textureCoords; + varying vec2 v_textureCoords; + void main() { + v_textureCoords = textureCoords; + gl_Position = vertex; + })"; + + constexpr char kFragmentShader[] = R"( + #extension GL_OES_EGL_image_external : require + precision mediump float; + uniform samplerExternalOES texture; + varying vec2 v_textureCoords; + void main() { + gl_FragColor = texture2D(texture, v_textureCoords); + })"; + + } // namespace + + void BackgroundRenderer::InitializeGlContent() { + shader_program_ = util::CreateProgram(kVertexShader, kFragmentShader); + + if (!shader_program_) { + LOGE("Could not create program."); + } + + glGenTextures(1, &texture_id_); + glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture_id_); + glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + uniform_texture_ = glGetUniformLocation(shader_program_, "texture"); + attribute_vertices_ = glGetAttribLocation(shader_program_, "vertex"); + attribute_uvs_ = glGetAttribLocation(shader_program_, "textureCoords"); + } + + void BackgroundRenderer::Draw(const ArSession *session, const ArFrame *frame) { + static_assert(std::extent<decltype(kUvs)>::value == kNumVertices * 2, + "Incorrect kUvs length"); + static_assert(std::extent<decltype(kVertices)>::value == kNumVertices * 3, + "Incorrect kVertices length"); + + // If display rotation changed (also includes view size change), we need to + // re-query the uv coordinates for the on-screen portion of the camera image. + int32_t geometry_changed = 0; + ArFrame_getDisplayGeometryChanged(session, frame, &geometry_changed); + if (geometry_changed != 0 || !uvs_initialized_) { + ArFrame_transformDisplayUvCoords(session, frame, kNumVertices * 2, kUvs, + transformed_uvs_); + uvs_initialized_ = true; + } + glUseProgram(shader_program_); + glDepthMask(GL_FALSE); + + glUniform1i(uniform_texture_, 1); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture_id_); + + glEnableVertexAttribArray(attribute_vertices_); + glVertexAttribPointer(attribute_vertices_, 3, GL_FLOAT, GL_FALSE, 0, + kVertices); + + glEnableVertexAttribArray(attribute_uvs_); + glVertexAttribPointer(attribute_uvs_, 2, GL_FLOAT, GL_FALSE, 0, + transformed_uvs_); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glUseProgram(0); + glDepthMask(GL_TRUE); + util::CheckGlError("BackgroundRenderer::Draw() error"); + } + + GLuint BackgroundRenderer::GetTextureId() const { return texture_id_; } + +} // namespace hello_ar diff --git a/platform_tools/android/apps/arcore/src/main/cpp/background_renderer.h b/platform_tools/android/apps/arcore/src/main/cpp/background_renderer.h new file mode 100644 index 0000000000..0e4f701f44 --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/cpp/background_renderer.h @@ -0,0 +1,61 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef C_ARCORE_HELLO_AR_BACKGROUND_RENDERER_H_ +#define C_ARCORE_HELLO_AR_BACKGROUND_RENDERER_H_ + +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include <cstdlib> + +#include "arcore_c_api.h" +#include "util.h" + +namespace hello_ar { + +// This class renders the passthrough camera image into the OpenGL frame. + class BackgroundRenderer { + public: + BackgroundRenderer() = default; + + ~BackgroundRenderer() = default; + + // Sets up OpenGL state. Must be called on the OpenGL thread and before any + // other methods below. + void InitializeGlContent(); + + // Draws the background image. This methods must be called for every ArFrame + // returned by ArSession_update() to catch display geometry change events. + void Draw(const ArSession *session, const ArFrame *frame); + + // Returns the generated texture name for the GL_TEXTURE_EXTERNAL_OES target. + GLuint GetTextureId() const; + + private: + static constexpr int kNumVertices = 4; + + GLuint shader_program_; + GLuint texture_id_; + + GLuint attribute_vertices_; + GLuint attribute_uvs_; + GLuint uniform_texture_; + + float transformed_uvs_[kNumVertices * 2]; + bool uvs_initialized_ = false; + }; +} // namespace hello_ar +#endif // C_ARCORE_HELLO_AR_BACKGROUND_RENDERER_H_ diff --git a/platform_tools/android/apps/arcore/src/main/cpp/glm.h b/platform_tools/android/apps/arcore/src/main/cpp/glm.h new file mode 100644 index 0000000000..cf2a8440b8 --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/cpp/glm.h @@ -0,0 +1,25 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef C_ARCORE_HELLOE_AR_GLM_H_ +#define C_ARCORE_HELLOE_AR_GLM_H_ + +#define GLM_FORCE_RADIANS 1 +#include "glm.hpp" +#include "gtc/matrix_transform.hpp" +#include "gtc/type_ptr.hpp" +#include "gtx/quaternion.hpp" + +#endif diff --git a/platform_tools/android/apps/arcore/src/main/cpp/hello_ar_application.cc b/platform_tools/android/apps/arcore/src/main/cpp/hello_ar_application.cc new file mode 100644 index 0000000000..6e050f373c --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/cpp/hello_ar_application.cc @@ -0,0 +1,987 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hello_ar_application.h" +#include <gtx/string_cast.hpp> + +#include "anchor_wrapper.h" +#include "plane_renderer.h" +#include "pending_anchor.h" +#include "util.h" +#include "SkCanvas.h" +#include "GrContext.h" +#include "gl/GrGLTypes.h" +#include "SkSurface.h" +#include "SkTypeface.h" +#include "SkFontStyle.h" +#include "GrBackendSurface.h" +#include "SkMatrix44.h" +#include "SkMatrix.h" +#include "SkTextBlob.h" +#include "glm.h" +#include "SkPoint3.h" +#include "Sk3D.h" +#include <math.h> /* acos */ +#include "SkShaper.h" +#include "Skottie.h" +#include "SkAnimTimer.h" +#include "Resources.h" +#include "SkStream.h" + +namespace hello_ar { + namespace { + constexpr size_t kMaxNumberOfAndroidsToRender = 1; + constexpr int32_t kPlaneColorRgbaSize = 16; + + const glm::vec3 kWhite = {255, 255, 255}; + + constexpr std::array<uint32_t, kPlaneColorRgbaSize> kPlaneColorRgba = { + {0xFFFFFFFF, 0xF44336FF, 0xE91E63FF, 0x9C27B0FF, 0x673AB7FF, 0x3F51B5FF, + 0x2196F3FF, 0x03A9F4FF, 0x00BCD4FF, 0x009688FF, 0x4CAF50FF, 0x8BC34AFF, + 0xCDDC39FF, 0xFFEB3BFF, 0xFFC107FF, 0xFF9800FF}}; + + inline glm::vec3 GetRandomPlaneColor() { + const int32_t colorRgba = kPlaneColorRgba[std::rand() % kPlaneColorRgbaSize]; + return glm::vec3(((colorRgba >> 24) & 0xff) / 255.0f, + ((colorRgba >> 16) & 0xff) / 255.0f, + ((colorRgba >> 8) & 0xff) / 255.0f); + } + } // namespace + + HelloArApplication::HelloArApplication(AAssetManager *asset_manager) + : asset_manager_(asset_manager) { + LOGI("OnCreate()"); + } + + HelloArApplication::~HelloArApplication() { + if (ar_session_ != nullptr) { + ArSession_destroy(ar_session_); + ArFrame_destroy(ar_frame_); + } + } + + void HelloArApplication::OnPause() { + LOGI("OnPause()"); + if (ar_session_ != nullptr) { + ArSession_pause(ar_session_); + } + } + + void HelloArApplication::OnResume(void *env, void *context, void *activity) { + LOGI("OnResume()"); + + if (ar_session_ == nullptr) { + ArInstallStatus install_status; + // If install was not yet requested, that means that we are resuming the + // activity first time because of explicit user interaction (such as + // launching the application) + bool user_requested_install = !install_requested_; + + // === ATTENTION! ATTENTION! ATTENTION! === + // This method can and will fail in user-facing situations. Your + // application must handle these cases at least somewhat gracefully. See + // HelloAR Java sample code for reasonable behavior. + CHECK(ArCoreApk_requestInstall(env, activity, user_requested_install, + &install_status) == AR_SUCCESS); + + switch (install_status) { + case AR_INSTALL_STATUS_INSTALLED: + break; + case AR_INSTALL_STATUS_INSTALL_REQUESTED: + install_requested_ = true; + return; + } + + // === ATTENTION! ATTENTION! ATTENTION! === + // This method can and will fail in user-facing situations. Your + // application must handle these cases at least somewhat gracefully. See + // HelloAR Java sample code for reasonable behavior. + CHECK(ArSession_create(env, context, &ar_session_) == AR_SUCCESS); + CHECK(ar_session_); + + ArFrame_create(ar_session_, &ar_frame_); + CHECK(ar_frame_); + + ArSession_setDisplayGeometry(ar_session_, display_rotation_, width_, + height_); + } + + const ArStatus status = ArSession_resume(ar_session_); + CHECK(status == AR_SUCCESS); + } + + void HelloArApplication::OnSurfaceCreated() { + LOGI("OnSurfaceCreated()"); + + background_renderer_.InitializeGlContent(); + point_cloud_renderer_.InitializeGlContent(); + plane_renderer_.InitializeGlContent(asset_manager_); + } + + void HelloArApplication::OnDisplayGeometryChanged(int display_rotation, + int width, int height) { + LOGI("OnSurfaceChanged(%d, %d)", width, height); + glViewport(0, 0, width, height); + display_rotation_ = display_rotation; + width_ = width; + height_ = height; + + if (ar_session_ != nullptr) { + ArSession_setDisplayGeometry(ar_session_, display_rotation, width, height);; + } + } + + void HelloArApplication::OnObjectRotationChanged(int rotation) { + LOGI("OnObjectRotationChanged(%d)", rotation); + currentObjectRotation = rotation; + } + + void HelloArApplication::OnAction(float value) { + LOGI("OnAction(%.6f)", value); + currentValue = value; + } + + void DrawText(SkCanvas *canvas, SkPaint *paint, const char text[]) { + float spacing = 0.05; + for (int i = 0; i < sizeof(text) / sizeof(text[0]); i++) { + const char letter[] = {text[i]}; + size_t byteLength = strlen(static_cast<const char *>(letter)); + canvas->drawText(letter, byteLength, spacing * i, 0, *paint); + } + } + + void DrawAxes(SkCanvas *canvas, SkMatrix44 m) { + SkPaint p; + p.setStrokeWidth(10); + SkPoint3 src[4] = { + {0, 0, 0}, + {0.2, 0, 0}, + {0, 0.2, 0}, + {0, 0, 0.2}, + }; + SkPoint dst[4]; + Sk3MapPts(dst, m, src, 4); + + const char str[] = "XYZ"; + p.setColor(SK_ColorRED); + canvas->drawLine(dst[0], dst[1], p); + + p.setColor(SK_ColorGREEN); + canvas->drawLine(dst[0], dst[2], p); + + p.setColor(SK_ColorBLUE); + canvas->drawLine(dst[0], dst[3], p); + } + + void DrawVector(SkCanvas *canvas, SkMatrix44 m, glm::vec3 begin, glm::vec3 end, SkColor c) { + SkPaint p; + p.setStrokeWidth(15); + SkPoint3 src[2] = { + {begin.x, begin.y, begin.z}, + {end.x, end.y, end.z} + }; + SkPoint dst[2]; + Sk3MapPts(dst, m, src, 2); + + const char str[] = "XYZ"; + p.setColor(c); + canvas->drawLine(dst[0], dst[1], p); + } + + void DrawBoundingBox(SkCanvas* canvas) { + SkPaint paint; + paint.setColor(SK_ColorYELLOW); + SkIRect bounds = canvas->getDeviceClipBounds(); + SkRect b = SkRect::Make(bounds); + + canvas->drawRect(b, paint); + } + + void HelloArApplication::OnDrawFrame() { + grContext = GrContext::MakeGL(); + + GrBackendRenderTarget target; + sk_sp<SkSurface> surface = nullptr; + GrGLFramebufferInfo framebuffer_info; + framebuffer_info.fFBOID = 0; + framebuffer_info.fFormat = 0x8058; + + + glClearColor(0.9f, 0.9f, 0.9f, 1.0f); + glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); + + glEnable(GL_CULL_FACE); + glEnable(GL_DEPTH_TEST); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + if (ar_session_ == nullptr) return; + + ArSession_setCameraTextureName(ar_session_, + background_renderer_.GetTextureId()); + + // Update session to get current frame and render camera background. + if (ArSession_update(ar_session_, ar_frame_) != AR_SUCCESS) { + LOGE("HelloArApplication::OnDrawFrame ArSession_update error"); + } + + // GET CAMERA INFO + ArCamera *ar_camera; + ArFrame_acquireCamera(ar_session_, ar_frame_, &ar_camera); + + glm::mat4 view_mat; + glm::mat4 projection_mat; + ArCamera_getViewMatrix(ar_session_, ar_camera, glm::value_ptr(view_mat)); + ArCamera_getProjectionMatrix(ar_session_, ar_camera, + /*near=*/0.1f, /*far=*/100.f, + glm::value_ptr(projection_mat)); + + ArTrackingState camera_tracking_state; + ArCamera_getTrackingState(ar_session_, ar_camera, &camera_tracking_state); + ArCamera_release(ar_camera); + + background_renderer_.Draw(ar_session_, ar_frame_); + + // If the camera isn't tracking don't bother rendering other objects. + if (camera_tracking_state != AR_TRACKING_STATE_TRACKING) { + return; + } + + // Get light estimation value. + ArLightEstimate *ar_light_estimate; + ArLightEstimateState ar_light_estimate_state; + ArLightEstimate_create(ar_session_, &ar_light_estimate); + + ArFrame_getLightEstimate(ar_session_, ar_frame_, ar_light_estimate); + ArLightEstimate_getState(ar_session_, ar_light_estimate, + &ar_light_estimate_state); + + // Set light intensity to default. Intensity value ranges from 0.0f to 1.0f. + // The first three components are color scaling factors. + // The last one is the average pixel intensity in gamma space. + float color_correction[4] = {1.f, 1.f, 1.f, 1.f}; + if (ar_light_estimate_state == AR_LIGHT_ESTIMATE_STATE_VALID) { + ArLightEstimate_getColorCorrection(ar_session_, ar_light_estimate, + color_correction); + } + + ArLightEstimate_destroy(ar_light_estimate); + ar_light_estimate = nullptr; + SkMatrix44 skProj; + SkMatrix44 skView; + SkMatrix skViewport; + + skProj = util::GlmMatToSkMat(projection_mat); + skView = util::GlmMatToSkMat(view_mat); + skViewport.setScale(width_ / 2, -height_ / 2); + skViewport.postTranslate(width_ / 2, height_ / 2); + target = GrBackendRenderTarget(width_, height_, 0, 0, framebuffer_info); + surface = SkSurface::MakeFromBackendRenderTarget(grContext.get(), + target, + kBottomLeft_GrSurfaceOrigin, + kRGBA_8888_SkColorType, + nullptr, nullptr); + + // Render Andy objects. + std::vector<SkMatrix44> models; + //glm::mat4 model_mat(1.0f); + for (const auto &obj_iter : tracked_obj_set_) { + ArTrackingState tracking_state = AR_TRACKING_STATE_STOPPED; + ArAnchor_getTrackingState(ar_session_, obj_iter, &tracking_state); + if (tracking_state == AR_TRACKING_STATE_TRACKING) { + // Render object only if the tracking state is AR_TRACKING_STATE_TRACKING. + //util::GetTransformMatrixFromAnchor(ar_session_, obj_iter, &model_mat); + //DRAW ANDY + //andy_renderer_.Draw(glm::mat4(1), glm::mat4(1), model_mat, color_correction); + + //PREPARE SKIA MATS + + SkMatrix44 skModel; + + switch (currentObjectRotation) { + case 0: { + auto iter = anchor_skmat4_axis_aligned_map_.find(obj_iter); + if (iter != anchor_skmat4_axis_aligned_map_.end()) { + skModel = iter->second; + models.push_back(skModel); + } + } + break; + case 1: { + auto iter = anchor_skmat4_camera_aligned_map_.find(obj_iter); + if (iter != anchor_skmat4_camera_aligned_map_.end()) { + skModel = iter->second; + models.push_back(skModel); + } + } + break; + case 2: { + auto iter = anchor_skmat4_snap_aligned_map_.find(obj_iter); + if (iter != anchor_skmat4_snap_aligned_map_.end()) { + skModel = iter->second; + models.push_back(skModel); + } + } + break; + default: { + auto iter = anchor_skmat4_axis_aligned_map_.find(obj_iter); + if (iter != anchor_skmat4_axis_aligned_map_.end()) { + skModel = iter->second; + models.push_back(skModel); + } + } + break; + } + + } + } + + // Update and render planes. + ArTrackableList *plane_list = nullptr; + ArTrackableList_create(ar_session_, &plane_list); + CHECK(plane_list != nullptr); + + ArTrackableType plane_tracked_type = AR_TRACKABLE_PLANE; + ArSession_getAllTrackables(ar_session_, plane_tracked_type, plane_list); + + int32_t plane_list_size = 0; + ArTrackableList_getSize(ar_session_, plane_list, &plane_list_size); + plane_count_ = plane_list_size; + + for (int i = 0; i < plane_list_size; ++i) { + ArTrackable *ar_trackable = nullptr; + ArTrackableList_acquireItem(ar_session_, plane_list, i, &ar_trackable); + ArPlane *ar_plane = ArAsPlane(ar_trackable); + ArTrackingState out_tracking_state; + ArTrackable_getTrackingState(ar_session_, ar_trackable, + &out_tracking_state); + + ArPlane *subsume_plane; + ArPlane_acquireSubsumedBy(ar_session_, ar_plane, &subsume_plane); + if (subsume_plane != nullptr) { + ArTrackable_release(ArAsTrackable(subsume_plane)); + continue; + } + + if (ArTrackingState::AR_TRACKING_STATE_TRACKING != out_tracking_state) { + continue; + } + + ArTrackingState plane_tracking_state; + ArTrackable_getTrackingState(ar_session_, ArAsTrackable(ar_plane), + &plane_tracking_state); + if (plane_tracking_state == AR_TRACKING_STATE_TRACKING) { + const auto iter = plane_color_map_.find(ar_plane); + glm::vec3 color; + if (iter != plane_color_map_.end()) { + color = iter->second; + + // If this is an already observed trackable release it so it doesn't + // leave aof placing objects on surfaces (n additional reference dangling. + ArTrackable_release(ar_trackable); + } else { + // The first plane is always white. + if (!first_plane_has_been_found_) { + first_plane_has_been_found_ = true; + color = kWhite; + } else { + color = GetRandomPlaneColor(); + } + plane_color_map_.insert({ar_plane, color}); + } + + plane_renderer_.Draw(projection_mat, view_mat, ar_session_, ar_plane, + color); + } + } + + ArTrackableList_destroy(plane_list); + plane_list = nullptr; + + // Update and render point cloud. + ArPointCloud *ar_point_cloud = nullptr; + ArStatus point_cloud_status = + ArFrame_acquirePointCloud(ar_session_, ar_frame_, &ar_point_cloud); + if (point_cloud_status == AR_SUCCESS) { + point_cloud_renderer_.Draw(projection_mat * view_mat, ar_session_, + ar_point_cloud); + ArPointCloud_release(ar_point_cloud); + } + SkMatrix44 i = SkMatrix44::kIdentity_Constructor; + + if (surface != nullptr) { + SkCanvas *canvas = surface->getCanvas(); + SkAutoCanvasRestore acr(canvas, true); + SkMatrix44 vpv = skViewport * skProj * skView; + for(SkMatrix44 skModel: models) { + SkMatrix44 i = SkMatrix44::kIdentity_Constructor; + canvas->setMatrix(i); + SkMatrix44 mvpv = skViewport * skProj * skView * skModel; + + //Draw XYZ axes + DrawAxes(canvas, mvpv); + //Drawing camera orientation + /* DrawVector(canvas, vpv, begins[0], ends[0], SK_ColorMAGENTA); + DrawVector(canvas, vpv, begins[0], ends[1], SK_ColorYELLOW); + DrawVector(canvas, vpv, begins[0], ends[2], SK_ColorCYAN);*/ + + canvas->concat(mvpv); + SkPaint paint; + + //Draw Circle + paint.setColor(0x80700000); + canvas->drawCircle(0, 0, 0.1, paint); + + //Draw Text + paint.setColor(SK_ColorBLUE); + if (currentValue != 0) { + paint.setTextSize(currentValue); + } else { + paint.setTextSize(0.1); + } + + paint.setAntiAlias(true); + const char text[] = "SkAR"; + size_t byteLength = strlen(static_cast<const char *>(text)); + SkShaper shaper(nullptr); + SkTextBlobBuilder builder; + SkPoint p = SkPoint::Make(0, 0); + shaper.shape(&builder, paint, text, byteLength, true, p, 10); + canvas->drawTextBlob(builder.make(), 0, 0, paint); + + //DrawBoundingBox(canvas); + } + canvas->flush(); + } + } + + + bool HelloArApplication::OnTouchedFirst(float x, float y, int drawMode) { + LOGI("Entered OnTouchedFirst"); + if (pendingAnchor != nullptr) { + delete pendingAnchor; + } + SkPoint p = SkPoint::Make(x,y); + pendingAnchor = new PendingAnchor(p); + bool editAnchor = false; + + if (ar_frame_ != nullptr && ar_session_ != nullptr) { + ArHitResultList *hit_result_list = nullptr; + ArHitResultList_create(ar_session_, &hit_result_list); + CHECK(hit_result_list); + ArFrame_hitTest(ar_session_, ar_frame_, x, y, hit_result_list); + + int32_t hit_result_list_size = 0; + ArHitResultList_getSize(ar_session_, hit_result_list, &hit_result_list_size); + ArHitResult *ar_hit_result = nullptr; + ArPose *out_pose = nullptr; + ArPlane* hitPlane = nullptr; + for (int32_t i = 0; i < hit_result_list_size; ++i) { + ArHitResult *ar_hit = nullptr; + ArPose *created_out_pose = nullptr; + ArHitResult_create(ar_session_, &ar_hit); + ArHitResultList_getItem(ar_session_, hit_result_list, i, ar_hit); + + if (ar_hit == nullptr) { + LOGE("HelloArApplication::OnTouched ArHitResultList_getItem error"); + return editAnchor; + } + + ArTrackable *ar_trackable = nullptr; + ArHitResult_acquireTrackable(ar_session_, ar_hit, &ar_trackable); + ArTrackableType ar_trackable_type = AR_TRACKABLE_NOT_VALID; + ArTrackable_getType(ar_session_, ar_trackable, &ar_trackable_type); + // Creates an anchor if a plane or an oriented point was hit. + if (AR_TRACKABLE_PLANE == ar_trackable_type) { + ArPose *hit_pose = nullptr; + ArPose_create(ar_session_, nullptr, &hit_pose); + ArHitResult_getHitPose(ar_session_, ar_hit, hit_pose); + int32_t in_polygon = 0; + ArPlane *ar_plane = ArAsPlane(ar_trackable); + ArPlane_isPoseInPolygon(ar_session_, ar_plane, hit_pose, &in_polygon); + + { + // Use hit pose and camera pose to check if hittest is from the + // back of the plane, if it is, no need to create the anchor. + ArPose *camera_pose = nullptr; + ArPose_create(ar_session_, nullptr, &camera_pose); + ArCamera *ar_camera; + ArFrame_acquireCamera(ar_session_, ar_frame_, &ar_camera); + ArCamera_getPose(ar_session_, ar_camera, camera_pose); + float normal_distance_to_plane = util::CalculateDistanceToPlane( + ar_session_, *hit_pose, *camera_pose); + + if (!in_polygon || normal_distance_to_plane < 0) { + ArPose_destroy(camera_pose); + continue; + } + ArPose_destroy(camera_pose); + ArCamera_release(ar_camera); + } + + //Raw pose of hit location + float out_hit_raw[] = {0, 0, 0, 0, 0, 0, 0}; + ArPose_getPoseRaw(ar_session_, hit_pose, out_hit_raw); + ArPose_destroy(hit_pose); + + //Position of anchor + glm::vec4 pendingAnchorPos(out_hit_raw[4], out_hit_raw[5], out_hit_raw[6], 1); + pendingAnchor->SetContainingPlane(ar_plane); + + //Check if plane contains approx the same anchor + auto planeAnchors = plane_anchors_map_.find(ar_plane); + if (planeAnchors != plane_anchors_map_.end()) { + //other anchors existed on this plane + std::vector<ArAnchor*> anchors = planeAnchors->second; + int i = 0; + LOGI("Size of anchor list: %d", (int) anchors.size()); + for(ArAnchor* const& anchor: anchors) { + //Get anchor's pose + i++; + LOGI("CHECKING: Anchor #%d", i); + ArPose *anchor_pose = nullptr; + ArPose_create(ar_session_, nullptr, &anchor_pose); + ArAnchor_getPose(ar_session_, anchor, anchor_pose); + float out_anchor_raw[] = {0, 0, 0, 0, 0, 0, 0}; + ArPose_getPoseRaw(ar_session_, anchor_pose, out_anchor_raw); + ArPose_destroy(anchor_pose); + glm::vec4 oldAnchorPos(out_anchor_raw[4], out_anchor_raw[5], out_anchor_raw[6], 1); + oldAnchorPos = oldAnchorPos - pendingAnchorPos; + float distance = util::Magnitude(glm::vec3(oldAnchorPos)); + if (distance < 0.1f) { + LOGI("TouchFirst: Editing old anchor!"); + editAnchor = true; + pendingAnchor->SetArAnchor(anchor); + pendingAnchor->SetEditMode(true); + + ArHitResult_destroy(ar_hit); + ArHitResultList_destroy(hit_result_list); + LOGI("TouchFirst: Edit %d", editAnchor); + return editAnchor; + } + } + } + + //actual hit result, and containing plane + ar_hit_result = ar_hit; + hitPlane = ar_plane; + + //new anchor pos + float wanted_raw_pose[] = {0, 0, 0, 0, out_hit_raw[4], out_hit_raw[5], out_hit_raw[6]}; + ArPose_create(ar_session_, wanted_raw_pose, &created_out_pose); + out_pose = created_out_pose; + break; + } + } + + + if (ar_hit_result) { + LOGI("TouchFirst: Adding new anchor!"); + ArAnchor *anchor = nullptr; + pendingAnchor->SetEditMode(false); + + if (ArSession_acquireNewAnchor(ar_session_, out_pose, &anchor) != AR_SUCCESS) { + LOGE("HelloArApplication::OnTouched ArHitResult_acquireNewAnchor error"); + LOGI("TouchFirst: Failed to acquire new anchor"); + delete hitPlane; + delete pendingAnchor; + pendingAnchor = nullptr; + LOGI("TouchFirst: Edit %d", editAnchor); + return editAnchor; + } + pendingAnchor->SetArAnchor(anchor); + + ArHitResult_destroy(ar_hit_result); + ArHitResultList_destroy(hit_result_list); + ArPose_destroy(out_pose); + hit_result_list = nullptr; + LOGI("TouchFirst: Edit %d", editAnchor); + return editAnchor; + } + + LOGI("TouchFirst: didn't hit anything"); + delete hitPlane; + delete pendingAnchor; + pendingAnchor = nullptr; + LOGI("TouchFirst: Edit %d", editAnchor); + return editAnchor; + } + } + + void HelloArApplication::AddAnchor(ArAnchor* anchor, ArPlane* containingPlane) { + //delete anchor from matrices maps + //releasing the anchor if it is not tracking anymore + ArTrackingState tracking_state = AR_TRACKING_STATE_STOPPED; + ArAnchor_getTrackingState(ar_session_, anchor, &tracking_state); + if (tracking_state != AR_TRACKING_STATE_TRACKING) { + RemoveAnchor(anchor); + return; + } + + //releasing the first anchor if we exceeded maximum number of objects to be rendered + if (tracked_obj_set_.size() >= kMaxNumberOfAndroidsToRender) { + RemoveAnchor(tracked_obj_set_[0]); + } + + //updating the containing plane with a new anchor + auto planeAnchors = plane_anchors_map_.find(containingPlane); + if (planeAnchors != plane_anchors_map_.end()) { + //other anchors existed on this plane + LOGI("TouchFinal: ADDING TO OLD ANCHORS"); + std::vector<ArAnchor*> anchors = planeAnchors->second; + anchors.push_back(anchor); + plane_anchors_map_[containingPlane] = anchors; + anchor_plane_map_.insert({anchor, containingPlane}); + } else { + LOGI("TouchFinal: NEW SET OF ANCHORS"); + std::vector<ArAnchor*> anchors; + anchors.push_back(anchor); + plane_anchors_map_.insert({containingPlane, anchors}); + anchor_plane_map_.insert({anchor, containingPlane}); + } + + tracked_obj_set_.push_back(anchor); + } + + void HelloArApplication::OnTouchTranslate(float x, float y) { + LOGI("Entered On Edit Touched"); + ArAnchor *anchor = pendingAnchor->GetArAnchor(); + glm::mat4 matrix = util::SkMatToGlmMat( + anchor_skmat4_axis_aligned_map_.find(anchor)->second); + + if (ar_frame_ != nullptr && ar_session_ != nullptr) { + ArHitResultList *hit_result_list = nullptr; + ArHitResultList_create(ar_session_, &hit_result_list); + CHECK(hit_result_list); + ArFrame_hitTest(ar_session_, ar_frame_, x, y, hit_result_list); + + int32_t hit_result_list_size = 0; + ArHitResultList_getSize(ar_session_, hit_result_list, &hit_result_list_size); + ArHitResult *ar_hit_result = nullptr; + ArPose *out_pose = nullptr; + ArPlane *hitPlane = nullptr; + for (int32_t i = 0; i < hit_result_list_size; ++i) { + ArHitResult *ar_hit = nullptr; + ArPose *created_out_pose = nullptr; + ArHitResult_create(ar_session_, &ar_hit); + ArHitResultList_getItem(ar_session_, hit_result_list, i, ar_hit); + + if (ar_hit == nullptr) { + LOGE("HelloArApplication::OnTouched ArHitResultList_getItem error"); + return; + } + + ArTrackable *ar_trackable = nullptr; + ArHitResult_acquireTrackable(ar_session_, ar_hit, &ar_trackable); + ArTrackableType ar_trackable_type = AR_TRACKABLE_NOT_VALID; + ArTrackable_getType(ar_session_, ar_trackable, &ar_trackable_type); + // Creates an anchor if a plane or an oriented point was hit. + if (AR_TRACKABLE_PLANE == ar_trackable_type) { + ArPose *hit_pose = nullptr; + ArPose_create(ar_session_, nullptr, &hit_pose); + ArHitResult_getHitPose(ar_session_, ar_hit, hit_pose); + int32_t in_polygon = 0; + ArPlane *ar_plane = ArAsPlane(ar_trackable); + ArPlane_isPoseInPolygon(ar_session_, ar_plane, hit_pose, &in_polygon); + + { + // Use hit pose and camera pose to check if hittest is from the + // back of the plane, if it is, no need to create the anchor. + ArPose *camera_pose = nullptr; + ArPose_create(ar_session_, nullptr, &camera_pose); + ArCamera *ar_camera; + ArFrame_acquireCamera(ar_session_, ar_frame_, &ar_camera); + ArCamera_getPose(ar_session_, ar_camera, camera_pose); + float normal_distance_to_plane = util::CalculateDistanceToPlane( + ar_session_, *hit_pose, *camera_pose); + + if (!in_polygon || normal_distance_to_plane < 0) { + ArPose_destroy(camera_pose); + continue; + } + ArPose_destroy(camera_pose); + ArCamera_release(ar_camera); + } + + //Raw pose of hit location + float out_hit_raw[] = {0, 0, 0, 0, 0, 0, 0}; + ArPose_getPoseRaw(ar_session_, hit_pose, out_hit_raw); + ArPose_destroy(hit_pose); + + //Translate by new amount + glm::vec4 newPos(out_hit_raw[4], out_hit_raw[5], out_hit_raw[6], 1); + glm::vec4 oldPos = pendingAnchor->GetAnchorPos(ar_session_); + glm::vec3 movement = glm::vec3(newPos - oldPos); + + + //CAMERA SETTINGS + glm::mat4 backToOrigin(1); + backToOrigin = glm::translate(backToOrigin, -glm::vec3(oldPos)); + glm::mat4 backToPlane(1); + backToPlane = glm::translate(backToPlane, glm::vec3(oldPos)); + + //Axes of Skia object: start with XYZ, totate to get X(-Z)Y, paste on plane, go back to origin --> plane orientation but on origin + glm::vec3 objX = glm::normalize(glm::vec3( + backToOrigin * matrix * + glm::vec4(1, 0, 0, 1))); //X still X + glm::vec3 objY = glm::normalize(glm::vec3( + backToOrigin * matrix * + glm::vec4(0, 1, 0, 1))); //Y is now Z + glm::vec3 objZ = glm::normalize(glm::vec3( + backToOrigin * matrix * + glm::vec4(0, 0, 1, 1))); //Z is now Y + + + glm::mat4 translate(1); + translate = glm::translate(translate, movement); + matrix = translate * matrix; + RemoveAnchor(anchor); + + + + //new anchor pos + float wanted_raw_pose[] = {0, 0, 0, 0, out_hit_raw[4], out_hit_raw[5], + out_hit_raw[6]}; + ArPose_create(ar_session_, wanted_raw_pose, &created_out_pose); + out_pose = created_out_pose; + ar_hit_result = ar_hit; + break; + } + } + + if (ar_hit_result) { + LOGI("TouchFirst: Adding new anchor!"); + ArAnchor *anchor = nullptr; + pendingAnchor->SetEditMode(false); + + if (ArSession_acquireNewAnchor(ar_session_, out_pose, &anchor) != AR_SUCCESS) { + LOGE("HelloArApplication::OnTouched ArHitResult_acquireNewAnchor error"); + LOGI("TouchFirst: Failed to acquire new anchor"); + delete hitPlane; + delete pendingAnchor; + pendingAnchor = nullptr; + return; + } + pendingAnchor->SetArAnchor(anchor); + anchor_skmat4_axis_aligned_map_[anchor] = util::GlmMatToSkMat(matrix); + + //Add anchor + AddAnchor(anchor, pendingAnchor->GetContainingPlane()); + + + ArHitResult_destroy(ar_hit_result); + ArHitResultList_destroy(hit_result_list); + ArPose_destroy(out_pose); + hit_result_list = nullptr; + return; + } + } + } + + void HelloArApplication::RemoveAnchor(ArAnchor* anchor) { + //delete anchor from matrices maps + anchor_skmat4_axis_aligned_map_.erase(anchor); + anchor_skmat4_camera_aligned_map_.erase(anchor); + anchor_skmat4_snap_aligned_map_.erase(anchor); + + auto containingPlaneIter = anchor_plane_map_.find(anchor); + if (containingPlaneIter != anchor_plane_map_.end()) { + ArPlane* containingPlane = containingPlaneIter->second; + auto planeAnchors = plane_anchors_map_.find(containingPlane); + if (planeAnchors != plane_anchors_map_.end()) { + //delete this anchor from the list of anchors associated with its plane + std::vector<ArAnchor*> anchors = planeAnchors->second; + anchors.erase(std::remove(anchors.begin(), anchors.end(), anchor), anchors.end()); + plane_anchors_map_[planeAnchors->first] = anchors; + + //delete anchor from map of anchor to plane + anchor_plane_map_.erase(anchor); + } + } + //delete anchor from list of tracked objects + tracked_obj_set_.erase(std::remove(tracked_obj_set_.begin(), tracked_obj_set_.end(), anchor), tracked_obj_set_.end()); + ArAnchor_release(anchor); + } + + void HelloArApplication::UpdateMatrixMaps(ArAnchor* anchorKey, glm::mat4 aaMat, glm::mat4 caMat, glm::mat4 snapMat) { + anchor_skmat4_axis_aligned_map_.insert({anchorKey, util::GlmMatToSkMat(aaMat)}); + anchor_skmat4_camera_aligned_map_.insert({anchorKey, util::GlmMatToSkMat(caMat)}); + anchor_skmat4_snap_aligned_map_.insert({anchorKey, util::GlmMatToSkMat(snapMat)}); + } + + void SetSkiaInitialRotation(glm::mat4& initRotation) { + initRotation = glm::rotate(initRotation, SK_ScalarPI / 2, glm::vec3(1, 0, 0)); + } + + void SetSkiaObjectAxes(glm::vec3& x, glm::vec3& y, glm::vec3& z, glm::mat4 transform) { + x = glm::normalize(glm::vec3(transform * glm::vec4(1, 0, 0, 1))); //X still X + y = glm::normalize(glm::vec3(transform * glm::vec4(0, 1, 0, 1))); //Y is now Z + z = glm::normalize(glm::vec3(transform * glm::vec4(0, 0, 1, 1))); //Z is now Y + } + + void SetCameraAlignedRotation(glm::mat4& rotateTowardsCamera, float& rotationDirection, const glm::vec3& toProject, const glm::vec3& skiaY, const glm::vec3& skiaZ) { + glm::vec3 hitLookProj = -util::ProjectOntoPlane(toProject, skiaZ); + float angleRad = util::AngleRad(skiaY, hitLookProj); + glm::vec3 cross = glm::normalize(glm::cross(skiaY, hitLookProj)); + + //outs + rotationDirection = util::Dot(cross, skiaZ); + rotateTowardsCamera = glm::rotate(rotateTowardsCamera, angleRad, rotationDirection * skiaZ); + } + + struct CameraAlignmentInfo { + glm::vec3& skiaY, skiaZ; + glm::mat4& preRot, postRot; + + CameraAlignmentInfo(glm::vec3& skiaY, glm::vec3& skiaZ, glm::mat4 preRot, glm::mat4 postRot) + : skiaY(skiaY), skiaZ(skiaZ), preRot(preRot), postRot(postRot) {} + }; + + void SetCameraAlignedVertical(glm::mat4& caMat, const glm::mat4& camRot, const CameraAlignmentInfo& camAlignInfo) { + //Camera axes + glm::vec3 xCamera = glm::vec3(glm::vec4(1, 0, 0, 1) * camRot); + glm::vec3 yCamera = glm::vec3(glm::vec4(0, 1, 0, 1) * camRot); + glm::vec3 zCamera = glm::vec3(glm::vec4(0, 0, -1, 1) * camRot); + + //Get matrix that rotates object from plane towards the wanted angle + glm::mat4 rotateTowardsCamera(1); + float rotationDirection = 1; + SetCameraAlignedRotation(rotateTowardsCamera, rotationDirection, yCamera, camAlignInfo.skiaY, camAlignInfo.skiaZ); + + //LogOrientation(dot, angleRad, "Vertical/Wall"); + glm::mat4 flip(1); + flip = glm::rotate(flip, SK_ScalarPI, rotationDirection * camAlignInfo.skiaZ); + caMat = camAlignInfo.postRot * flip * rotateTowardsCamera * camAlignInfo.preRot; + } + + void SetCameraAlignedHorizontal(glm::mat4& caMat, ArPlaneType planeType, const glm::vec3 hitLook, const CameraAlignmentInfo& camAlignInfo) { + //Ceiling or Floor: follow hit location + //Get matrix that rotates object from plane towards the wanted angle + glm::mat4 rotateTowardsCamera(1); + float rotationDirection = 1; + SetCameraAlignedRotation(rotateTowardsCamera, rotationDirection, hitLook, camAlignInfo.skiaY, camAlignInfo.skiaZ); + + if (planeType == ArPlaneType::AR_PLANE_HORIZONTAL_DOWNWARD_FACING) { + //ceiling + //LogOrientation(dot, angleRad, "Ceiling"); + glm::mat4 flip(1); + flip = glm::rotate(flip, SK_ScalarPI, rotationDirection * camAlignInfo.skiaZ); + caMat = camAlignInfo.postRot * flip * rotateTowardsCamera * camAlignInfo.preRot; + } else { + //floor or tabletop + //LogOrientation(dot, angleRad, "Floor"); + caMat = camAlignInfo.postRot * rotateTowardsCamera * camAlignInfo.preRot; + } + } + + + + void HelloArApplication::SetCameraAlignedMatrix(glm::mat4& caMat, glm::vec3 hitPos, glm::mat4& planeModel, const glm::mat4& initRotation) { + //Translation matrices: from plane to origin, and from origin to plane + glm::mat4 backToOrigin(1); + backToOrigin = glm::translate(backToOrigin, -hitPos); + glm::mat4 backToPlane(1); + backToPlane = glm::translate(backToPlane, hitPos); + + //Axes of Skia object: start with XYZ, totate to get X(-Z)Y, paste on plane, go back to origin --> plane orientation but on origin + glm::vec3 skiaX, skiaY, skiaZ; + SetSkiaObjectAxes(skiaX, skiaY, skiaZ, backToOrigin * planeModel * initRotation); + + //Get camera position & rotation + glm::vec3 cameraPos; + glm::mat4 cameraRotationMatrix; + util::GetCameraInfo(ar_session_, ar_frame_, cameraPos, cameraRotationMatrix); + + //Set matrix depending on type of surface + ArPlaneType planeType = AR_PLANE_VERTICAL; + ArPlane_getType(ar_session_, pendingAnchor->GetContainingPlane(), &planeType); + + //Set CamerAlignmentInfo + CameraAlignmentInfo camAlignInfo(skiaY, skiaZ, backToOrigin * planeModel * initRotation, backToPlane); + + if (planeType == ArPlaneType::AR_PLANE_VERTICAL) { + //Wall: follow phone orientation + SetCameraAlignedVertical(caMat, cameraRotationMatrix, camAlignInfo); + } else { + //Ceiling or Floor: follow hit location + glm::vec3 hitLook(hitPos - cameraPos); + SetCameraAlignedHorizontal(caMat, planeType, hitLook, camAlignInfo); + } + } + + + void HelloArApplication::SetModelMatrices(glm::mat4& aaMat, glm::mat4& caMat, glm::mat4& snapMat, const glm::mat4& planeModel) { + //Brings Skia world to ARCore world + glm::mat4 initRotation(1); + SetSkiaInitialRotation(initRotation); + + //Copy plane model for editing + glm::mat4 copyPlaneModel(planeModel); + + //Set snap matrix + //snapMat = copyPlaneModel * initRotation; + + //Set axis-aligned matrix + glm::vec4 anchorPos = pendingAnchor->GetAnchorPos(ar_session_); + copyPlaneModel[3] = anchorPos; + aaMat = planeModel * initRotation; + + //Set camera-aligned matrix + //SetCameraAlignedMatrix(caMat, glm::vec3(anchorPos), copyPlaneModel, initRotation); + } + + void GetPlaneModelMatrix(glm::mat4& planeModel, ArSession* arSession, ArPlane* arPlane) { + ArPose *plane_pose = nullptr; + ArPose_create(arSession, nullptr, &plane_pose); + ArPlane_getCenterPose(arSession, arPlane, plane_pose); + util::GetTransformMatrixFromPose(arSession, plane_pose, &planeModel); + ArPose_destroy(plane_pose); + } + + void HelloArApplication::OnTouchedFinal(int type) { + LOGI("Entered OnTouchedFinal"); + if (pendingAnchor == nullptr) { + LOGI("WARNING: Entered OnTouchedFinal but no pending anchor.."); + return; + } + + if (pendingAnchor->GetEditMode()) { + LOGI("WARNING: Editing old anchor in OnTouchedFinal!"); + } + + //Get necessary pending anchor info + ArPlane* containingPlane = pendingAnchor->GetContainingPlane(); + glm::vec4 pendingAnchorPos = pendingAnchor->GetAnchorPos(ar_session_); + ArAnchor* actualAnchor = pendingAnchor->GetArAnchor(); + + //Plane model matrix + glm::mat4 planeModel(1); + GetPlaneModelMatrix(planeModel, ar_session_, containingPlane); + + //Setup skia object model matrices + glm::mat4 matrixAxisAligned(1); + glm::mat4 matrixCameraAligned(1); + glm::mat4 matrixSnapAligned(1); + SetModelMatrices(matrixAxisAligned, matrixCameraAligned, matrixSnapAligned, planeModel); + + //Update anchor -> model matrix datastructures + UpdateMatrixMaps(actualAnchor, matrixAxisAligned, matrixCameraAligned, matrixSnapAligned); + + //Add anchor to aux datastructures + AddAnchor(actualAnchor, containingPlane); + } + +} // namespace hello_ar diff --git a/platform_tools/android/apps/arcore/src/main/cpp/hello_ar_application.h b/platform_tools/android/apps/arcore/src/main/cpp/hello_ar_application.h new file mode 100644 index 0000000000..2ebec7ed65 --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/cpp/hello_ar_application.h @@ -0,0 +1,158 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef C_ARCORE_HELLOE_AR_HELLO_AR_APPLICATION_H_ +#define C_ARCORE_HELLOE_AR_HELLO_AR_APPLICATION_H_ + +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include <android/asset_manager.h> +#include <jni.h> +#include <memory> +#include <set> +#include <string> +#include <unordered_map> +#include <GrContext.h> +#include <gl/GrGLTypes.h> +#include <GrBackendSurface.h> +#include <SkSurface.h> +#include <Skottie.h> + +#include "arcore_c_api.h" +#include "background_renderer.h" +#include "glm.h" +#include "plane_renderer.h" +#include "point_cloud_renderer.h" +#include "util.h" +#include "pending_anchor.h" + +namespace hello_ar { + +// HelloArApplication handles all application logics. + class HelloArApplication { + public: + // Constructor and deconstructor. + HelloArApplication() = default; + + HelloArApplication(AAssetManager *asset_manager); + + ~HelloArApplication(); + + SkMatrix SkiaRenderer(const glm::mat4 &proj, const glm::mat4 &view, const glm::mat4 &model); + + // OnPause is called on the UI thread from the Activity's onPause method. + void OnPause(); + + // OnResume is called on the UI thread from the Activity's onResume method. + void OnResume(void *env, void *context, void *activity); + + // OnSurfaceCreated is called on the OpenGL thread when GLSurfaceView + // is created. + void OnSurfaceCreated(); + + // OnDisplayGeometryChanged is called on the OpenGL thread when the + // render surface size or display rotation changes. + // + // @param display_rotation: current display rotation. + // @param width: width of the changed surface view. + // @param height: height of the changed surface view. + void OnDisplayGeometryChanged(int display_rotation, int width, int height); + + void OnObjectRotationChanged(int rotation); + + void OnAction(float value); + + // OnDrawFrame is called on the OpenGL thread to render the next frame. + void OnDrawFrame(); + + bool OnTouchedFirst(float x, float y, int drawMode); + + void OnTouchTranslate(float x, float y); + + void OnEditTouched(float x, float y); + + void OnTouchedFinal(int type); + + void RemoveAnchor(ArAnchor* anchor); + + void AddAnchor(ArAnchor* anchor, ArPlane* containingPlane); + + void UpdateMatrixMaps(ArAnchor* anchorKey, glm::mat4 aaMat, glm::mat4 caMat, glm::mat4 snapMat); + + void SetModelMatrices(glm::mat4& aaMat, glm::mat4& caMat, glm::mat4& snapMat, const glm::mat4& planeModel); + + void SetCameraAlignedMatrix(glm::mat4& caMat, glm::vec3 hitPos, glm::mat4& planeModel, const glm::mat4& initRotation); + + // Returns true if any planes have been detected. Used for hiding the + // "searching for planes" snackbar. + bool HasDetectedPlanes() const { return plane_count_ > 0; } + + glm::mat4 + ComputeCameraAlignedMatrix(ArPlane *arPlane, glm::mat4 planeModel, glm::mat4 initRotation, + glm::vec4 anchorPos, + glm::vec3 cameraPos, glm::vec3 hitPos, + float cameraDisplayOutRaw[]); + + private: + ArSession *ar_session_ = nullptr; + ArFrame *ar_frame_ = nullptr; + + PendingAnchor* pendingAnchor = nullptr; + + //SKIA VARS + sk_sp<GrContext> grContext; + sk_sp<skottie::Animation> fAnim; + SkScalar fAnimT = 0; + + bool install_requested_ = false; + int width_ = 1; + int height_ = 1; + int display_rotation_ = 0; + + int currentObjectRotation = 0; + float currentValue = 0; + + std::vector<glm::vec3> begins; + std::vector<glm::vec3> ends; + + AAssetManager *const asset_manager_; + + // The anchors at which we are drawing android models + std::vector<ArAnchor *> tracked_obj_set_; + + // Stores the randomly-selected color each plane is drawn with + std::unordered_map<ArPlane *, glm::vec3> plane_color_map_; + + std::unordered_map<ArAnchor *, SkMatrix44> anchor_skmat4_axis_aligned_map_; + std::unordered_map<ArAnchor *, SkMatrix44> anchor_skmat4_camera_aligned_map_; + std::unordered_map<ArAnchor *, SkMatrix44> anchor_skmat4_snap_aligned_map_; + + std::unordered_map<ArPlane *, std::vector<ArAnchor*>> plane_anchors_map_; + std::unordered_map<ArAnchor *, ArPlane*> anchor_plane_map_; + + // The first plane is always rendered in white, if this is true then a plane + // at some point has been found. + bool first_plane_has_been_found_ = false; + + PointCloudRenderer point_cloud_renderer_; + BackgroundRenderer background_renderer_; + PlaneRenderer plane_renderer_; + + int32_t plane_count_ = 0; + }; +} // namespace hello_ar + +#endif // C_ARCORE_HELLOE_AR_HELLO_AR_APPLICATION_H_ diff --git a/platform_tools/android/apps/arcore/src/main/cpp/jni_interface.cc b/platform_tools/android/apps/arcore/src/main/cpp/jni_interface.cc new file mode 100644 index 0000000000..01cbff2236 --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/cpp/jni_interface.cc @@ -0,0 +1,130 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <android/asset_manager.h> +#include <android/asset_manager_jni.h> +#include <jni.h> + +#include "hello_ar_application.h" + +#define JNI_METHOD(return_type, method_name) \ + JNIEXPORT return_type JNICALL \ + Java_org_skia_arcore_JniInterface_##method_name + +extern "C" { + +namespace { +// maintain a reference to the JVM so we can use it later. + static JavaVM *g_vm = nullptr; + + inline jlong jptr(hello_ar::HelloArApplication *native_hello_ar_application) { + return reinterpret_cast<intptr_t>(native_hello_ar_application); + } + + inline hello_ar::HelloArApplication *native(jlong ptr) { + return reinterpret_cast<hello_ar::HelloArApplication *>(ptr); + } + +} // namespace + +jint JNI_OnLoad(JavaVM *vm, void *) { + g_vm = vm; + return JNI_VERSION_1_6; +} + +JNI_METHOD(jlong, createNativeApplication) +(JNIEnv *env, jclass, jobject j_asset_manager) { + AAssetManager *asset_manager = AAssetManager_fromJava(env, j_asset_manager); + return jptr(new hello_ar::HelloArApplication(asset_manager)); +} + +JNI_METHOD(void, destroyNativeApplication) +(JNIEnv *, jclass, jlong native_application) { + delete native(native_application); +} + +JNI_METHOD(void, onPause) +(JNIEnv *, jclass, jlong native_application) { + native(native_application)->OnPause(); +} + +JNI_METHOD(void, onResume) +(JNIEnv *env, jclass, jlong native_application, jobject context, + jobject activity) { + native(native_application)->OnResume(env, context, activity); +} + +JNI_METHOD(void, onGlSurfaceCreated) +(JNIEnv *, jclass, jlong native_application) { + native(native_application)->OnSurfaceCreated(); +} + +JNI_METHOD(void, onDisplayGeometryChanged) +(JNIEnv *, jobject, jlong native_application, int display_rotation, int width, + int height) { + native(native_application) + ->OnDisplayGeometryChanged(display_rotation, width, height); +} + +JNI_METHOD(void, onObjectRotationChanged) +(JNIEnv *, jobject, jlong native_application, int rotation) { + native(native_application) + ->OnObjectRotationChanged(rotation); +} + +JNI_METHOD(void, onAction) +(JNIEnv *, jobject, jlong native_application, jfloat value) { + native(native_application)->OnAction(value); +} + +JNI_METHOD(void, onGlSurfaceDrawFrame) +(JNIEnv *, jclass, jlong native_application) { + native(native_application)->OnDrawFrame(); +} + +JNI_METHOD(void, onTouchTranslate) +(JNIEnv *, jclass, jlong native_application, jfloat x, jfloat y) { + return native(native_application)->OnTouchTranslate(x, y); +} + +JNI_METHOD(bool, onTouchedFirst) +(JNIEnv *, jclass, jlong native_application, jfloat x, jfloat y, int drawMode) { + return native(native_application)->OnTouchedFirst(x, y, drawMode); +} + +JNI_METHOD(void, onTouchedFinal) +(JNIEnv *, jclass, jlong native_application, int type) { + native(native_application)->OnTouchedFinal(type); +} + +JNI_METHOD(jboolean, hasDetectedPlanes) +(JNIEnv *, jclass, jlong native_application) { + return static_cast<jboolean>( + native(native_application)->HasDetectedPlanes() ? JNI_TRUE : JNI_FALSE); +} + +JNIEnv *GetJniEnv() { + JNIEnv *env; + jint result = g_vm->AttachCurrentThread(&env, nullptr); + return result == JNI_OK ? env : nullptr; +} + +jclass FindClass(const char *classname) { + JNIEnv *env = GetJniEnv(); + return env->FindClass(classname); +} + +} // extern "C" diff --git a/platform_tools/android/apps/arcore/src/main/cpp/jni_interface.h b/platform_tools/android/apps/arcore/src/main/cpp/jni_interface.h new file mode 100644 index 0000000000..fe19cfcb85 --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/cpp/jni_interface.h @@ -0,0 +1,34 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef C_ARCORE_HELLOE_AR_JNI_INTERFACE_H_ +#define C_ARCORE_HELLOE_AR_JNI_INTERFACE_H_ + +#include <jni.h> +/** + * Helper functions to provide access to Java from C via JNI. + */ +extern "C" { + +// Helper function used to access the jni environment on the current thread. +// In this sample, no consideration is made for detaching the thread when the +// thread exits. This can cause memory leaks, so production applications should +// detach when the thread no longer needs access to the JVM. +JNIEnv *GetJniEnv(); + +jclass FindClass(const char *classname); +} // extern "C" +#endif diff --git a/platform_tools/android/apps/arcore/src/main/cpp/pending_anchor.cc b/platform_tools/android/apps/arcore/src/main/cpp/pending_anchor.cc new file mode 100644 index 0000000000..f6a9c67b9e --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/cpp/pending_anchor.cc @@ -0,0 +1,68 @@ +/* + * Copyright 2018 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hello_ar_application.h" +#include "plane_renderer.h" +#include "util.h" +#include "SkCanvas.h" + +namespace hello_ar { + PendingAnchor::PendingAnchor(SkPoint touchLocation) : touchLocation(touchLocation) {} + + PendingAnchor::~PendingAnchor() {} + + SkPoint PendingAnchor::GetTouchLocation() { + return touchLocation; + } + + bool PendingAnchor::GetEditMode() { + return editMode; + } + + ArPlane* PendingAnchor::GetContainingPlane() { + return containingPlane; + } + + glm::vec4 PendingAnchor::GetAnchorPos(ArSession* arSession) { + float poseRaw[] = {0, 0, 0, 0, 0, 0, 0}; + ArPose* anchorPose = nullptr; + ArPose_create(arSession, poseRaw, &anchorPose); + ArAnchor_getPose(arSession, this->anchor, anchorPose); + ArPose_getPoseRaw(arSession, anchorPose, poseRaw); + ArPose_destroy(anchorPose); + glm::vec4 anchorPos = glm::vec4(poseRaw[4], poseRaw[5], poseRaw[6], 1); + return anchorPos; + } + + ArAnchor* PendingAnchor::GetArAnchor() { + return anchor; + } + + void PendingAnchor::SetArAnchor(ArAnchor* anchor) { + this->anchor = anchor; + } + + void PendingAnchor::SetEditMode(bool editMode) { + this->editMode = editMode; + } + + void PendingAnchor::SetContainingPlane(ArPlane* plane) { + this->containingPlane = plane; + } + + + +} // namespace hello_ar diff --git a/platform_tools/android/apps/arcore/src/main/cpp/pending_anchor.h b/platform_tools/android/apps/arcore/src/main/cpp/pending_anchor.h new file mode 100644 index 0000000000..2694b678d0 --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/cpp/pending_anchor.h @@ -0,0 +1,50 @@ +/* + * Copyright 2018 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef C_ARCORE_HELLO_AR_PENDING_ANCHOR_H_ +#define C_ARCORE_HELLO_AR_PENDING_ANCHOR_H_ + +#include <gl/GrGLTypes.h> +#include <GrBackendSurface.h> + +#include "arcore_c_api.h" +#include "glm.h" + +namespace hello_ar { + class PendingAnchor { + public: + PendingAnchor(SkPoint touchLocation); + ~PendingAnchor(); + + SkPoint GetTouchLocation(); + bool GetEditMode(); + ArPlane* GetContainingPlane(); + glm::vec4 GetAnchorPos(ArSession* arSession); + ArAnchor* GetArAnchor(); + + void SetArAnchor(ArAnchor* anchor); + void SetEditMode(bool editMode); + void SetContainingPlane(ArPlane* plane); + + private: + SkPoint touchLocation; + bool editMode = false; + ArAnchor* anchor; + ArPlane* containingPlane; + }; +} // namespace hello_ar + +#endif diff --git a/platform_tools/android/apps/arcore/src/main/cpp/plane_renderer.cc b/platform_tools/android/apps/arcore/src/main/cpp/plane_renderer.cc new file mode 100644 index 0000000000..50d8f8be59 --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/cpp/plane_renderer.cc @@ -0,0 +1,220 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "plane_renderer.h" +#include "util.h" + +namespace hello_ar { + namespace { + constexpr char kVertexShader[] = R"( + precision highp float; + precision highp int; + attribute vec3 vertex; + varying vec2 v_textureCoords; + varying float v_alpha; + + uniform mat4 mvp; + uniform mat4 model_mat; + uniform vec3 normal; + + void main() { + // Vertex Z value is used as the alpha in this shader. + v_alpha = vertex.z; + + vec4 local_pos = vec4(vertex.x, 0.0, vertex.y, 1.0); + gl_Position = mvp * local_pos; + vec4 world_pos = model_mat * local_pos; + + // Construct two vectors that are orthogonal to the normal. + // This arbitrary choice is not co-linear with either horizontal + // or vertical plane normals. + const vec3 arbitrary = vec3(1.0, 1.0, 0.0); + vec3 vec_u = normalize(cross(normal, arbitrary)); + vec3 vec_v = normalize(cross(normal, vec_u)); + + // Project vertices in world frame onto vec_u and vec_v. + v_textureCoords = vec2( + dot(world_pos.xyz, vec_u), dot(world_pos.xyz, vec_v)); + })"; + + constexpr char kFragmentShader[] = R"( + precision highp float; + precision highp int; + uniform sampler2D texture; + uniform vec3 color; + varying vec2 v_textureCoords; + varying float v_alpha; + void main() { + float r = texture2D(texture, v_textureCoords).r; + gl_FragColor = vec4(color.xyz, r * v_alpha); + })"; + } // namespace + + void PlaneRenderer::InitializeGlContent(AAssetManager *asset_manager) { + shader_program_ = util::CreateProgram(kVertexShader, kFragmentShader); + + if (!shader_program_) { + LOGE("Could not create program."); + } + + uniform_mvp_mat_ = glGetUniformLocation(shader_program_, "mvp"); + uniform_texture_ = glGetUniformLocation(shader_program_, "texture"); + uniform_model_mat_ = glGetUniformLocation(shader_program_, "model_mat"); + uniform_normal_vec_ = glGetUniformLocation(shader_program_, "normal"); + uniform_color_ = glGetUniformLocation(shader_program_, "color"); + attri_vertices_ = glGetAttribLocation(shader_program_, "vertex"); + + glGenTextures(1, &texture_id_); + glBindTexture(GL_TEXTURE_2D, texture_id_); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, + GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + if (!util::LoadPngFromAssetManager(GL_TEXTURE_2D, "models/trigrid.png")) { + LOGE("Could not load png texture for planes."); + } + + glGenerateMipmap(GL_TEXTURE_2D); + + glBindTexture(GL_TEXTURE_2D, 0); + + util::CheckGlError("plane_renderer::InitializeGlContent()"); + } + + void PlaneRenderer::Draw(const glm::mat4 &projection_mat, + const glm::mat4 &view_mat, const ArSession *ar_session, + const ArPlane *ar_plane, const glm::vec3 &color) { + if (!shader_program_) { + LOGE("shader_program is null."); + return; + } + + UpdateForPlane(ar_session, ar_plane); + + glUseProgram(shader_program_); + glDepthMask(GL_FALSE); + + glActiveTexture(GL_TEXTURE0); + glUniform1i(uniform_texture_, 0); + glBindTexture(GL_TEXTURE_2D, texture_id_); + + // Compose final mvp matrix for this plane renderer. + glUniformMatrix4fv(uniform_mvp_mat_, 1, GL_FALSE, + glm::value_ptr(projection_mat * view_mat * model_mat_)); + + glUniformMatrix4fv(uniform_model_mat_, 1, GL_FALSE, + glm::value_ptr(model_mat_)); + glUniform3f(uniform_normal_vec_, normal_vec_.x, normal_vec_.y, normal_vec_.z); + glUniform3f(uniform_color_, color.x, color.y, color.z); + + glEnableVertexAttribArray(attri_vertices_); + glVertexAttribPointer(attri_vertices_, 3, GL_FLOAT, GL_FALSE, 0, + vertices_.data()); + + glDrawElements(GL_TRIANGLES, triangles_.size(), GL_UNSIGNED_SHORT, + triangles_.data()); + + glUseProgram(0); + glDepthMask(GL_TRUE); + util::CheckGlError("plane_renderer::Draw()"); + } + + void PlaneRenderer::UpdateForPlane(const ArSession *ar_session, + const ArPlane *ar_plane) { + // The following code generates a triangle mesh filling a convex polygon, + // including a feathered edge for blending. + // + // The indices shown in the diagram are used in comments below. + // _______________ 0_______________1 + // | | |4___________5| + // | | | | | | + // | | => | | | | + // | | | | | | + // | | |7-----------6| + // --------------- 3---------------2 + + vertices_.clear(); + triangles_.clear(); + + int32_t polygon_length; + ArPlane_getPolygonSize(ar_session, ar_plane, &polygon_length); + + if (polygon_length == 0) { + LOGE("PlaneRenderer::UpdatePlane, no valid plane polygon is found"); + return; + } + + const int32_t vertices_size = polygon_length / 2; + std::vector<glm::vec2> raw_vertices(vertices_size); + ArPlane_getPolygon(ar_session, ar_plane, + glm::value_ptr(raw_vertices.front())); + + // Fill vertex 0 to 3. Note that the vertex.xy are used for x and z + // position. vertex.z is used for alpha. The outter polygon's alpha + // is 0. + for (int32_t i = 0; i < vertices_size; ++i) { + vertices_.push_back(glm::vec3(raw_vertices[i].x, raw_vertices[i].y, 0.0f)); + } + + util::ScopedArPose scopedArPose(ar_session); + ArPlane_getCenterPose(ar_session, ar_plane, scopedArPose.GetArPose()); + ArPose_getMatrix(ar_session, scopedArPose.GetArPose(), + glm::value_ptr(model_mat_)); + normal_vec_ = util::GetPlaneNormal(ar_session, *scopedArPose.GetArPose()); + + // Feather distance 0.2 meters. + const float kFeatherLength = 0.2f; + // Feather scale over the distance between plane center and vertices. + const float kFeatherScale = 0.2f; + + // Fill vertex 4 to 7, with alpha set to 1. + for (int32_t i = 0; i < vertices_size; ++i) { + // Vector from plane center to current point. + glm::vec2 v = raw_vertices[i]; + const float scale = + 1.0f - std::min((kFeatherLength / glm::length(v)), kFeatherScale); + const glm::vec2 result_v = scale * v; + + vertices_.push_back(glm::vec3(result_v.x, result_v.y, 1.0f)); + } + + const int32_t vertices_length = vertices_.size(); + const int32_t half_vertices_length = vertices_length / 2; + + // Generate triangle (4, 5, 6) and (4, 6, 7). + for (int i = half_vertices_length + 1; i < vertices_length - 1; ++i) { + triangles_.push_back(half_vertices_length); + triangles_.push_back(i); + triangles_.push_back(i + 1); + } + + // Generate triangle (0, 1, 4), (4, 1, 5), (5, 1, 2), (5, 2, 6), + // (6, 2, 3), (6, 3, 7), (7, 3, 0), (7, 0, 4) + for (int i = 0; i < half_vertices_length; ++i) { + triangles_.push_back(i); + triangles_.push_back((i + 1) % half_vertices_length); + triangles_.push_back(i + half_vertices_length); + + triangles_.push_back(i + half_vertices_length); + triangles_.push_back((i + 1) % half_vertices_length); + triangles_.push_back((i + half_vertices_length + 1) % half_vertices_length + + half_vertices_length); + } + } + +} // namespace hello_ar diff --git a/platform_tools/android/apps/arcore/src/main/cpp/plane_renderer.h b/platform_tools/android/apps/arcore/src/main/cpp/plane_renderer.h new file mode 100644 index 0000000000..c1f75f2a70 --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/cpp/plane_renderer.h @@ -0,0 +1,70 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef C_ARCORE_HELLOE_AR_PLANE_RENDERER_H_ +#define C_ARCORE_HELLOE_AR_PLANE_RENDERER_H_ + +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include <android/asset_manager.h> +#include <array> +#include <cstdint> +#include <cstdlib> +#include <string> +#include <vector> + +#include "arcore_c_api.h" +#include "glm.h" + +namespace hello_ar { + +// PlaneRenderer renders ARCore plane type. + class PlaneRenderer { + public: + PlaneRenderer() = default; + + ~PlaneRenderer() = default; + + // Sets up OpenGL state used by the plane renderer. Must be called on the + // OpenGL thread. + void InitializeGlContent(AAssetManager *asset_manager); + + // Draws the provided plane. + void Draw(const glm::mat4 &projection_mat, const glm::mat4 &view_mat, + const ArSession *ar_session, const ArPlane *ar_plane, + const glm::vec3 &color); + + private: + void UpdateForPlane(const ArSession *ar_session, const ArPlane *ar_plane); + + std::vector<glm::vec3> vertices_; + std::vector<GLushort> triangles_; + glm::mat4 model_mat_ = glm::mat4(1.0f); + glm::vec3 normal_vec_ = glm::vec3(0.0f); + + GLuint texture_id_; + + GLuint shader_program_; + GLint attri_vertices_; + GLint uniform_mvp_mat_; + GLint uniform_texture_; + GLint uniform_model_mat_; + GLint uniform_normal_vec_; + GLint uniform_color_; + }; +} // namespace hello_ar + +#endif // C_ARCORE_HELLOE_AR_PLANE_RENDERER_H_ diff --git a/platform_tools/android/apps/arcore/src/main/cpp/point_cloud_renderer.cc b/platform_tools/android/apps/arcore/src/main/cpp/point_cloud_renderer.cc new file mode 100644 index 0000000000..c9ab6930d7 --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/cpp/point_cloud_renderer.cc @@ -0,0 +1,77 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "point_cloud_renderer.h" +#include "util.h" + +namespace hello_ar { + namespace { + constexpr char kVertexShader[] = R"( + attribute vec4 vertex; + uniform mat4 mvp; + void main() { + gl_PointSize = 5.0; + // Pointcloud vertex's w component is confidence value. + // Not used in renderer. + gl_Position = mvp * vec4(vertex.xyz, 1.0); + })"; + + constexpr char kFragmentShader[] = R"( + precision lowp float; + void main() { + gl_FragColor = vec4(0.1215, 0.7372, 0.8235, 1.0); + })"; + } // namespace + + void PointCloudRenderer::InitializeGlContent() { + shader_program_ = util::CreateProgram(kVertexShader, kFragmentShader); + + CHECK(shader_program_); + + attribute_vertices_ = glGetAttribLocation(shader_program_, "vertex"); + uniform_mvp_mat_ = glGetUniformLocation(shader_program_, "mvp"); + + util::CheckGlError("point_cloud_renderer::InitializeGlContent()"); + } + + void PointCloudRenderer::Draw(glm::mat4 mvp_matrix, ArSession *ar_session, + ArPointCloud *ar_point_cloud) const { + CHECK(shader_program_); + + glUseProgram(shader_program_); + + int32_t number_of_points = 0; + ArPointCloud_getNumberOfPoints(ar_session, ar_point_cloud, &number_of_points); + if (number_of_points <= 0) { + return; + } + + const float *point_cloud_data; + ArPointCloud_getData(ar_session, ar_point_cloud, &point_cloud_data); + + glUniformMatrix4fv(uniform_mvp_mat_, 1, GL_FALSE, glm::value_ptr(mvp_matrix)); + + glEnableVertexAttribArray(attribute_vertices_); + glVertexAttribPointer(attribute_vertices_, 4, GL_FLOAT, GL_FALSE, 0, + point_cloud_data); + + glDrawArrays(GL_POINTS, 0, number_of_points); + + glUseProgram(0); + util::CheckGlError("PointCloudRenderer::Draw"); + } + +} // namespace hello_ar diff --git a/platform_tools/android/apps/arcore/src/main/cpp/point_cloud_renderer.h b/platform_tools/android/apps/arcore/src/main/cpp/point_cloud_renderer.h new file mode 100644 index 0000000000..95f70e5eb7 --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/cpp/point_cloud_renderer.h @@ -0,0 +1,57 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef C_ARCORE_HELLOE_AR_POINT_CLOUD_RENDERER_H_ +#define C_ARCORE_HELLOE_AR_POINT_CLOUD_RENDERER_H_ + +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include <cstdlib> +#include <vector> + +#include "arcore_c_api.h" +#include "glm.h" + +namespace hello_ar { + + class PointCloudRenderer { + public: + // Default constructor of PointCloudRenderer. + PointCloudRenderer() = default; + + // Default deconstructor of PointCloudRenderer. + ~PointCloudRenderer() = default; + + // Initialize the GL content, needs to be called on GL thread. + void InitializeGlContent(); + + // Render the AR point cloud. + // + // @param mvp_matrix, the model view projection matrix of point cloud. + // @param ar_session, the session that is used to query point cloud points + // from ar_point_cloud. + // @param ar_point_cloud, point cloud data to for rendering. + void Draw(glm::mat4 mvp_matrix, ArSession *ar_session, + ArPointCloud *ar_point_cloud) const; + + private: + GLuint shader_program_; + GLint attribute_vertices_; + GLint uniform_mvp_mat_; + }; +} // namespace hello_ar + +#endif // C_ARCORE_HELLOE_AR_POINT_CLOUD_RENDERER_H_ diff --git a/platform_tools/android/apps/arcore/src/main/cpp/util.cc b/platform_tools/android/apps/arcore/src/main/cpp/util.cc new file mode 100644 index 0000000000..d18719fbb4 --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/cpp/util.cc @@ -0,0 +1,329 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "util.h" + +#include <unistd.h> +#include <sstream> +#include <string> +#include <SkMatrix44.h> +#include <gtx/string_cast.inl> + +#include "jni_interface.h" + +namespace hello_ar { + namespace util { + + void CheckGlError(const char *operation) { + bool anyError = false; + for (GLint error = glGetError(); error; error = glGetError()) { + LOGE("after %s() glError (0x%x)\n", operation, error); + anyError = true; + } + if (anyError) { + abort(); + } + } + + // Convenience function used in CreateProgram below. + static GLuint LoadShader(GLenum shader_type, const char *shader_source) { + GLuint shader = glCreateShader(shader_type); + if (!shader) { + return shader; + } + + glShaderSource(shader, 1, &shader_source, nullptr); + glCompileShader(shader); + GLint compiled = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + + if (!compiled) { + GLint info_len = 0; + + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &info_len); + if (!info_len) { + return shader; + } + + char *buf = reinterpret_cast<char *>(malloc(info_len)); + if (!buf) { + return shader; + } + + glGetShaderInfoLog(shader, info_len, nullptr, buf); + LOGE("hello_ar::util::Could not compile shader %d:\n%s\n", shader_type, + buf); + free(buf); + glDeleteShader(shader); + shader = 0; + } + + return shader; + } + + GLuint CreateProgram(const char *vertex_source, const char *fragment_source) { + GLuint vertexShader = LoadShader(GL_VERTEX_SHADER, vertex_source); + if (!vertexShader) { + return 0; + } + + GLuint fragment_shader = LoadShader(GL_FRAGMENT_SHADER, fragment_source); + if (!fragment_shader) { + return 0; + } + + GLuint program = glCreateProgram(); + if (program) { + glAttachShader(program, vertexShader); + CheckGlError("hello_ar::util::glAttachShader"); + glAttachShader(program, fragment_shader); + CheckGlError("hello_ar::util::glAttachShader"); + glLinkProgram(program); + GLint link_status = GL_FALSE; + glGetProgramiv(program, GL_LINK_STATUS, &link_status); + if (link_status != GL_TRUE) { + GLint buf_length = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &buf_length); + if (buf_length) { + char *buf = reinterpret_cast<char *>(malloc(buf_length)); + if (buf) { + glGetProgramInfoLog(program, buf_length, nullptr, buf); + LOGE("hello_ar::util::Could not link program:\n%s\n", buf); + free(buf); + } + } + glDeleteProgram(program); + program = 0; + } + } + return program; + } + + bool LoadPngFromAssetManager(int target, const std::string &path) { + JNIEnv *env = GetJniEnv(); + + // Put all the JNI values in a structure that is statically initalized on the + // first call to this method. This makes it thread safe in the unlikely case + // of multiple threads calling this method. + static struct JNIData { + jclass helper_class; + jmethodID load_image_method; + jmethodID load_texture_method; + } jniIds = [env]() -> JNIData { + constexpr char kHelperClassName[] = + "org/skia/arcore/JniInterface"; + constexpr char kLoadImageMethodName[] = "loadImage"; + constexpr char kLoadImageMethodSignature[] = + "(Ljava/lang/String;)Landroid/graphics/Bitmap;"; + constexpr char kLoadTextureMethodName[] = "loadTexture"; + constexpr char kLoadTextureMethodSignature[] = + "(ILandroid/graphics/Bitmap;)V"; + jclass helper_class = FindClass(kHelperClassName); + if (helper_class) { + helper_class = static_cast<jclass>(env->NewGlobalRef(helper_class)); + jmethodID load_image_method = env->GetStaticMethodID( + helper_class, kLoadImageMethodName, kLoadImageMethodSignature); + jmethodID load_texture_method = env->GetStaticMethodID( + helper_class, kLoadTextureMethodName, kLoadTextureMethodSignature); + return {helper_class, load_image_method, load_texture_method}; + } + LOGE("hello_ar::util::Could not find Java helper class %s", + kHelperClassName); + return {}; + }(); + + if (!jniIds.helper_class) { + return false; + } + + jstring j_path = env->NewStringUTF(path.c_str()); + + jobject image_obj = env->CallStaticObjectMethod( + jniIds.helper_class, jniIds.load_image_method, j_path); + + if (j_path) { + env->DeleteLocalRef(j_path); + } + + env->CallStaticVoidMethod(jniIds.helper_class, jniIds.load_texture_method, + target, image_obj); + return true; + } + + void GetTransformMatrixFromPose(ArSession *ar_session, + const ArPose *ar_pose, + glm::mat4 *out_model_mat) { + if (out_model_mat == nullptr) { + LOGE("util::GetTransformMatrixFromPose model_mat is null."); + return; + } + ArPose_getMatrix(ar_session, ar_pose, + glm::value_ptr(*out_model_mat)); + } + + glm::vec3 GetPlaneNormal(const ArSession *ar_session, + const ArPose &plane_pose) { + float plane_pose_raw[7] = {0.f}; + ArPose_getPoseRaw(ar_session, &plane_pose, plane_pose_raw); + glm::quat plane_quaternion(plane_pose_raw[3], plane_pose_raw[0], + plane_pose_raw[1], plane_pose_raw[2]); + // Get normal vector, normal is defined to be positive Y-position in local + // frame. + return glm::rotate(plane_quaternion, glm::vec3(0., 1.f, 0.)); + } + + float CalculateDistanceToPlane(const ArSession *ar_session, + const ArPose &plane_pose, + const ArPose &camera_pose) { + float plane_pose_raw[7] = {0.f}; + ArPose_getPoseRaw(ar_session, &plane_pose, plane_pose_raw); + glm::vec3 plane_position(plane_pose_raw[4], plane_pose_raw[5], + plane_pose_raw[6]); + glm::vec3 normal = GetPlaneNormal(ar_session, plane_pose); + + float camera_pose_raw[7] = {0.f}; + ArPose_getPoseRaw(ar_session, &camera_pose, camera_pose_raw); + glm::vec3 camera_P_plane(camera_pose_raw[4] - plane_position.x, + camera_pose_raw[5] - plane_position.y, + camera_pose_raw[6] - plane_position.z); + return glm::dot(normal, camera_P_plane); + } + + glm::mat4 GetCameraRotationMatrix(float cameraOutRaw[]) { + glm::mat4 cameraRotation(1); + glm::quat cameraQuat = glm::quat(cameraOutRaw[0], cameraOutRaw[1], cameraOutRaw[2], + cameraOutRaw[3]); + cameraRotation = glm::toMat4(cameraQuat); + glm::vec4 temp = cameraRotation[0]; + cameraRotation[0] = cameraRotation[2]; + cameraRotation[2] = temp; + return cameraRotation; + } + + void GetCameraInfo(ArSession* arSession, ArFrame* arFrame, glm::vec3& cameraPos, glm::mat4& cameraRotation) { + //Acquire camera + ArCamera *ar_camera; + ArFrame_acquireCamera(arSession, arFrame, &ar_camera); + + //Get camera pose + ArPose *camera_pose = nullptr; + ArPose_create(arSession, nullptr, &camera_pose); + ArCamera_getDisplayOrientedPose(arSession, ar_camera, camera_pose); + + //Get camera raw info + float outCameraRaw[] = {0, 0, 0, 0, 0, 0, 0}; + ArPose_getPoseRaw(arSession, camera_pose, outCameraRaw); + ArPose_destroy(camera_pose); + + //Write to out variables + cameraPos = glm::vec3(outCameraRaw[4], outCameraRaw[5], outCameraRaw[6]); + cameraRotation = util::GetCameraRotationMatrix(outCameraRaw); + + //Release camera + ArCamera_release(ar_camera); + } + + SkMatrix44 GlmMatToSkMat(const glm::mat4 m) { + SkMatrix44 skMat = SkMatrix44::kIdentity_Constructor; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + skMat.set(j, i, m[i][j]); + } + } + return skMat; + } + + glm::mat4 SkMatToGlmMat(const SkMatrix44 m) { + glm::mat4 glmMat(1); + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + glmMat[i][j] = m.get(j, i); + } + } + return glmMat; + } + + void Log4x4Matrix(float raw_matrix[16]) { + LOGI( + "%f, %f, %f, %f\n" + "%f, %f, %f, %f\n" + "%f, %f, %f, %f\n" + "%f, %f, %f, %f\n", + raw_matrix[0], raw_matrix[1], raw_matrix[2], raw_matrix[3], raw_matrix[4], + raw_matrix[5], raw_matrix[6], raw_matrix[7], raw_matrix[8], raw_matrix[9], + raw_matrix[10], raw_matrix[11], raw_matrix[12], raw_matrix[13], + raw_matrix[14], raw_matrix[15]); + } + + void LogGlmMat(glm::mat4 m, char *type) { + std::string str = glm::to_string(m); + LOGE("glm Matrix - %s: %s\n", type, str.c_str()); + } + + void LogSkMat44(SkMatrix44 m, char *type) { + LOGE("SkMatrix - %s: [%g, %g, %g, %g] || [%g, %g, %g, %g] || [%g, %g, %g, %g] || [%g, %g, %g, %g] \n", + type, + m.get(0, 0), m.get(1, 0), m.get(2, 0), m.get(3, 0), + m.get(0, 1), m.get(1, 1), m.get(2, 1), m.get(3, 1), + m.get(0, 2), m.get(1, 2), m.get(2, 2), m.get(3, 2), + m.get(0, 3), m.get(1, 3), m.get(2, 3), m.get(3, 3) + ); + } + + void LogSkMat(SkMatrix m, char *type) { + LOGE("SkMatrix - %s: [%g, %g, %g] || [%g, %g, %g] || [%g, %g, %g] \n", type, + m.get(0), m.get(3), m.get(6), + m.get(1), m.get(4), m.get(7), + m.get(2), m.get(5), m.get(8) + ); + } + + void LogOrientation(float rotationDirection, float angleRad, char *type) { + LOGI("Plane orientation: %s", type); + LOGI("Cross dotted with zDir:", rotationDirection); + if (rotationDirection == -1) { + LOGI("Counter Clockwise %.6f degrees rotation: ", glm::degrees(angleRad)); + } else { + LOGI("Clockwise %.6f degrees rotation: ", glm::degrees(angleRad)); + } + } + + float Dot(glm::vec3 u, glm::vec3 v) { + float result = u.x * v.x + u.y * v.y + u.z * v.z; + return result; + } + + float Magnitude(glm::vec3 u) { + float result = u.x * u.x + u.y * u.y + u.z * u.z; + return sqrt(result); + } + + float AngleRad(glm::vec3 u, glm::vec3 v) { + float dot = util::Dot(u, v); + float scale = (util::Magnitude(u) * util::Magnitude(v)); + float cosine = dot / scale; + float acosine = acos(cosine); + return acosine; + } + + glm::vec3 ProjectOntoPlane(glm::vec3 in, glm::vec3 normal) { + float dot = util::Dot(in, normal); + float multiplier = dot / (util::Magnitude(normal) * util::Magnitude(normal)); + glm::vec3 out = in - multiplier * normal; + return out; + } + + } // namespace util +} // namespace hello_ar diff --git a/platform_tools/android/apps/arcore/src/main/cpp/util.h b/platform_tools/android/apps/arcore/src/main/cpp/util.h new file mode 100644 index 0000000000..88bc2f91a8 --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/cpp/util.h @@ -0,0 +1,139 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef C_ARCORE_HELLOE_AR_UTIL_H_ +#define C_ARCORE_HELLOE_AR_UTIL_H_ + +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include <android/asset_manager.h> +#include <android/log.h> +#include <errno.h> +#include <jni.h> +#include <cstdint> +#include <cstdlib> +#include <vector> +#include <SkMatrix44.h> + +#include "arcore_c_api.h" +#include "glm.h" + +#ifndef LOGI +#define LOGI(...) \ + __android_log_print(ANDROID_LOG_INFO, "hello_ar_example_c", __VA_ARGS__) +#endif // LOGI + +#ifndef LOGE +#define LOGE(...) \ + __android_log_print(ANDROID_LOG_ERROR, "hello_ar_example_c", __VA_ARGS__) +#endif // LOGE + +#ifndef CHECK +#define CHECK(condition) \ + if (!(condition)) { \ + LOGE("*** CHECK FAILED at %s:%d: %s", __FILE__, __LINE__, #condition); \ + abort(); \ + } +#endif // CHECK + +namespace hello_ar { + // Utilities + namespace util { + + // Provides a scoped allocated instance of Anchor. + // Can be treated as an ArAnchor*. + class ScopedArPose { + public: + explicit ScopedArPose(const ArSession *session) { + ArPose_create(session, nullptr, &pose_); + } + + ~ScopedArPose() { ArPose_destroy(pose_); } + + ArPose *GetArPose() { return pose_; } + + // Delete copy constructors. + ScopedArPose(const ScopedArPose &) = delete; + + void operator=(const ScopedArPose &) = delete; + + private: + ArPose *pose_; + }; + + /* GL Utils */ + // Check GL error, and abort if an error is encountered. + // + // @param operation, the name of the GL function call. + void CheckGlError(const char *operation); + + // Create a shader program ID. + // + // @param vertex_source, the vertex shader source. + // @param fragment_source, the fragment shader source. + // @return + GLuint CreateProgram(const char *vertex_source, const char *fragment_source); + + // Load png file from assets folder and then assign it to the OpenGL target. + // This method must be called from the renderer thread since it will result in + // OpenGL calls to assign the image to the texture target. + // + // @param target, openGL texture target to load the image into. + // @param path, path to the file, relative to the assets folder. + // @return true if png is loaded correctly, otherwise false. + bool LoadPngFromAssetManager(int target, const std::string &path); + + + /* ARCore utils */ + void GetTransformMatrixFromPose(ArSession *ar_session, const ArPose *ar_pose, glm::mat4 *out_model_mat); + + // Get the plane's normal from center pose. + glm::vec3 GetPlaneNormal(const ArSession *ar_session, const ArPose &plane_pose); + + // Calculate the normal distance to plane from cameraPose, the given planePose + // should have y axis parallel to plane's normal, for example plane's center + // pose or hit test pose. + float CalculateDistanceToPlane(const ArSession *ar_session, const ArPose &plane_pose, const ArPose &camera_pose); + + // Outputs the camera rotation using display orientation + glm::mat4 GetCameraRotationMatrix(float cameraOutRaw[]); + + // Computes camera position and orientation (using GetCameraRotationMatrix) + void GetCameraInfo(ArSession* arSession, ArFrame* arFrame, glm::vec3& cameraPos, glm::mat4& cameraRotation); + + /* Matrix conversion */ + SkMatrix44 GlmMatToSkMat(const glm::mat4 m); + glm::mat4 SkMatToGlmMat(const SkMatrix44 m); + + /* Logging utils */ + //Row major output + void Log4x4Matrix(float raw_matrix[16]); + + //Column major output + void LogGlmMat(glm::mat4 m, char *type); + void LogSkMat44(SkMatrix44 m, char *type); + void LogSkMat(SkMatrix m, char *type); + void LogOrientation(float rotationDirection, float angleRad, char *type); + + /* Vector ops */ + float Dot(glm::vec3 u, glm::vec3 v); + float Magnitude(glm::vec3 u); + float AngleRad(glm::vec3 u, glm::vec3 v); + glm::vec3 ProjectOntoPlane(glm::vec3 in, glm::vec3 normal); + } // namespace util +} // namespace hello_ar + +#endif // C_ARCORE_HELLOE_AR_UTIL_H_ diff --git a/platform_tools/android/apps/arcore/src/main/java/org/skia/arcore/CameraPermissionHelper.java b/platform_tools/android/apps/arcore/src/main/java/org/skia/arcore/CameraPermissionHelper.java new file mode 100644 index 0000000000..3499ec00f3 --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/java/org/skia/arcore/CameraPermissionHelper.java @@ -0,0 +1,65 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.skia.arcore; + +import android.Manifest; +import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.provider.Settings; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; + +/** + * Helper to ask camera permission. + */ +public class CameraPermissionHelper { + private static final String CAMERA_PERMISSION = Manifest.permission.CAMERA; + private static final int CAMERA_PERMISSION_CODE = 0; + + /** + * Check to see we have the necessary permissions for this app. + */ + public static boolean hasCameraPermission(Activity activity) { + return ContextCompat.checkSelfPermission(activity, CAMERA_PERMISSION) + == PackageManager.PERMISSION_GRANTED; + } + + /** + * Check to see we have the necessary permissions for this app, and ask for them if we don't. + */ + public static void requestCameraPermission(Activity activity) { + ActivityCompat.requestPermissions( + activity, new String[]{CAMERA_PERMISSION}, CAMERA_PERMISSION_CODE); + } + + /** + * Check to see if we need to show the rationale for this permission. + */ + public static boolean shouldShowRequestPermissionRationale(Activity activity) { + return ActivityCompat.shouldShowRequestPermissionRationale(activity, CAMERA_PERMISSION); + } + + /** + * Launch Application Setting to grant permission. + */ + public static void launchPermissionSettings(Activity activity) { + Intent intent = new Intent(); + intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.setData(Uri.fromParts("package", activity.getPackageName(), null)); + activity.startActivity(intent); + } +} diff --git a/platform_tools/android/apps/arcore/src/main/java/org/skia/arcore/HelloArActivity.java b/platform_tools/android/apps/arcore/src/main/java/org/skia/arcore/HelloArActivity.java new file mode 100644 index 0000000000..73651f51c6 --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/java/org/skia/arcore/HelloArActivity.java @@ -0,0 +1,364 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.skia.arcore; + +import android.app.Activity; +import android.content.Context; +import android.hardware.display.DisplayManager; +import android.opengl.GLES20; +import android.opengl.GLSurfaceView; +import android.os.Bundle; +import android.os.Handler; +import android.support.design.widget.Snackbar; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.util.Log; +import android.view.ActionMode; +import android.view.ContextMenu; +import android.view.GestureDetector; +import android.view.Gravity; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.EditText; +import android.widget.PopupMenu; +import android.widget.Toast; + +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; + +/** + * This is a simple example that shows how to create an augmented reality (AR) application using the + * ARCore C API. + */ +public class HelloArActivity extends AppCompatActivity + implements GLSurfaceView.Renderer, DisplayManager.DisplayListener { + private static final String TAG = HelloArActivity.class.getSimpleName(); + private static final int SNACKBAR_UPDATE_INTERVAL_MILLIS = 1000; // In milliseconds. + + private GLSurfaceView mSurfaceView; + private Activity activity = null; + private boolean mViewportChanged = false; + private int mViewportWidth; + private int mViewportHeight; + private View contextView = null; + private int mCurrentObjectRotation = 0; + private float mCurrentValue = 0; + private float X = 0; + private float Y = 0; + + private boolean toEdit = false; + + // Opaque native pointer to the native application instance. + private long mNativeApplication; + private GestureDetector mGestureDetector; + + private Snackbar mLoadingMessageSnackbar; + private Handler mPlaneStatusCheckingHandler; + private final Runnable mPlaneStatusCheckingRunnable = + new Runnable() { + @Override + public void run() { + // The runnable is executed on main UI thread. + try { + if (JniInterface.hasDetectedPlanes(mNativeApplication)) { + if (mLoadingMessageSnackbar != null) { + mLoadingMessageSnackbar.dismiss(); + } + mLoadingMessageSnackbar = null; + } else { + mPlaneStatusCheckingHandler.postDelayed( + mPlaneStatusCheckingRunnable, SNACKBAR_UPDATE_INTERVAL_MILLIS); + } + } catch (Exception e) { + Log.e(TAG, e.getMessage()); + } + } + }; + private int mDrawMode = -1; + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + Toolbar myToolbar = (Toolbar) findViewById(R.id.my_toolbar); + setSupportActionBar(myToolbar); + + activity = this; + + //hide notifications bar + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + + mSurfaceView = (GLSurfaceView) findViewById(R.id.surfaceview); + + mGestureDetector = + new GestureDetector( + this, + new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onSingleTapUp(final MotionEvent e) { + toEdit = JniInterface.onTouchedFirst(mNativeApplication, e.getX(), e.getY(), mDrawMode); + + Log.i(TAG, "toEdit: " + toEdit); + X = e.getX(); + Y = e.getY(); + contextView.showContextMenu(e.getX(), e.getY()); + return true; + } + + @Override + public boolean onScroll (MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + Log.i(TAG, "Scrolling!"); + JniInterface.onTouchTranslate(mNativeApplication, e2.getX(), e2.getY()); + return true; + } + + @Override + public boolean onDown(MotionEvent e) { + return true; + } + }); + + mSurfaceView.setOnTouchListener( + new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + return mGestureDetector.onTouchEvent(event); + } + }); + + // Set up renderer. + mSurfaceView.setPreserveEGLContextOnPause(true); + mSurfaceView.setEGLContextClientVersion(2); + mSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0); // Alpha used for plane blending. + mSurfaceView.setRenderer(this); + mSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); + + JniInterface.assetManager = getAssets(); + mNativeApplication = JniInterface.createNativeApplication(getAssets()); + + mPlaneStatusCheckingHandler = new Handler(); + + //Floating context menu + contextView = findViewById(R.id.menuView); + this.registerForContextMenu(contextView); + View.OnLongClickListener listener = new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + activity.closeContextMenu(); + return false; + } + }; + contextView.setOnLongClickListener(listener); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) + { + super.onCreateContextMenu(menu, v, menuInfo); + + if (!toEdit) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.draw_menu, menu); + menu.setHeaderTitle("Draw Options"); + } + + v.setClickable(false); + v.setFocusable(false); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); + switch (item.getItemId()) { + case R.id.draw_text: + JniInterface.onTouchedFinal(mNativeApplication, 0); + return true; + case R.id.draw_circle: + JniInterface.onTouchedFinal(mNativeApplication, 1); + return true; + case R.id.draw_rect: + JniInterface.onTouchedFinal(mNativeApplication, 2); + return true; + case R.id.edit_size: + return true; + case R.id.edit_text: + return true; + default: + return super.onContextItemSelected(item); + } + } + + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.rotation_mode, menu); + return true; + } + + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.rotation_axis_aligned: + mCurrentObjectRotation = 0; + break; + case R.id.rotation_camera_aligned: + mCurrentObjectRotation = 1; + break; + case R.id.rotation_snap_aligned: + mCurrentObjectRotation = 2; + break; + case R.id.action: + mCurrentValue = 180; + JniInterface.onAction(mNativeApplication, mCurrentValue); + return true; + default: + return true; + } + JniInterface.onObjectRotationChanged(mNativeApplication, mCurrentObjectRotation); + return true; + } + + @Override + protected void onResume() { + super.onResume(); + // ARCore requires camera permissions to operate. If we did not yet obtain runtime + // permission on Android M and above, now is a good time to ask the user for it. + if (!CameraPermissionHelper.hasCameraPermission(this)) { + CameraPermissionHelper.requestCameraPermission(this); + return; + } + + JniInterface.onResume(mNativeApplication, getApplicationContext(), this); + mSurfaceView.onResume(); + + mLoadingMessageSnackbar = + Snackbar.make( + HelloArActivity.this.findViewById(android.R.id.content), + "Searching for surfaces...", + Snackbar.LENGTH_INDEFINITE); + // Set the snackbar background to light transparent black color. + mLoadingMessageSnackbar.getView().setBackgroundColor(0xbf323232); + mLoadingMessageSnackbar.show(); + mPlaneStatusCheckingHandler.postDelayed( + mPlaneStatusCheckingRunnable, SNACKBAR_UPDATE_INTERVAL_MILLIS); + + // Listen to display changed events to detect 180° rotation, which does not cause a config + // change or view resize. + getSystemService(DisplayManager.class).registerDisplayListener(this, null); + } + + @Override + public void onPause() { + super.onPause(); + mSurfaceView.onPause(); + JniInterface.onPause(mNativeApplication); + + mPlaneStatusCheckingHandler.removeCallbacks(mPlaneStatusCheckingRunnable); + + getSystemService(DisplayManager.class).unregisterDisplayListener(this); + } + + @Override + public void onDestroy() { + super.onDestroy(); + + // Synchronized to avoid racing onDrawFrame. + synchronized (this) { + JniInterface.destroyNativeApplication(mNativeApplication); + mNativeApplication = 0; + } + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (hasFocus) { + // Standard Android full-screen functionality. + getWindow() + .getDecorView() + .setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + } + + @Override + public void onSurfaceCreated(GL10 gl, EGLConfig config) { + GLES20.glClearColor(0.1f, 0.1f, 0.1f, 1.0f); + JniInterface.onGlSurfaceCreated(mNativeApplication); + } + + @Override + public void onSurfaceChanged(GL10 gl, int width, int height) { + mViewportWidth = width; + mViewportHeight = height; + mViewportChanged = true; + } + + @Override + public void onDrawFrame(GL10 gl) { + // Synchronized to avoid racing onDestroy. + synchronized (this) { + if (mNativeApplication == 0) { + return; + } + if (mViewportChanged) { + int displayRotation = getWindowManager().getDefaultDisplay().getRotation(); + JniInterface.onDisplayGeometryChanged( + mNativeApplication, displayRotation, mViewportWidth, mViewportHeight); + mViewportChanged = false; + } + JniInterface.onGlSurfaceDrawFrame(mNativeApplication); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] results) { + if (!CameraPermissionHelper.hasCameraPermission(this)) { + Toast.makeText(this, "Camera permission is needed to run this application", Toast.LENGTH_LONG) + .show(); + if (!CameraPermissionHelper.shouldShowRequestPermissionRationale(this)) { + // Permission denied with checking "Do not ask again". + CameraPermissionHelper.launchPermissionSettings(this); + } + finish(); + } + } + + // DisplayListener methods + @Override + public void onDisplayAdded(int displayId) { + } + + @Override + public void onDisplayRemoved(int displayId) { + } + + @Override + public void onDisplayChanged(int displayId) { + mViewportChanged = true; + } +} diff --git a/platform_tools/android/apps/arcore/src/main/java/org/skia/arcore/JniInterface.java b/platform_tools/android/apps/arcore/src/main/java/org/skia/arcore/JniInterface.java new file mode 100644 index 0000000000..1592bb2a4e --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/java/org/skia/arcore/JniInterface.java @@ -0,0 +1,82 @@ +package org.skia.arcore; + +import android.app.Activity; +import android.content.Context; +import android.content.res.AssetManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.opengl.GLUtils; +import android.util.Log; + +import java.io.IOException; + +/** + * JNI interface to native layer. + */ +public class JniInterface { + static { + System.loadLibrary("hello_ar_native"); + } + + private static final String TAG = "JniInterface"; + static AssetManager assetManager; + + public static native long createNativeApplication(AssetManager assetManager); + + public static native void destroyNativeApplication(long nativeApplication); + + public static native void onPause(long nativeApplication); + + public static native void onResume(long nativeApplication, Context context, Activity activity); + + /** + * Allocate OpenGL resources for rendering. + */ + public static native void onGlSurfaceCreated(long nativeApplication); + + /** + * Called on the OpenGL thread before onGlSurfaceDrawFrame when the view port width, height, or + * display rotation may have changed. + */ + public static native void onDisplayGeometryChanged( + long nativeApplication, int displayRotation, int width, int height); + + public static native void onObjectRotationChanged(long nativeApplication, int rotation); + + public static native void onAction(long nativeApplication, float value); + + /** + * Main render loop, called on the OpenGL thread. + */ + public static native void onGlSurfaceDrawFrame(long nativeApplication); + + /** + * OnTouch event, called on the OpenGL thread. + */ + + public static native void onTouchTranslate(long nativeApplication, float x, float y); + + public static native boolean onTouchedFirst(long nativeApplication, float x, float y, int drawMode); + + public static native void onTouchedFinal(long nativeApplication, int type); + + + /** + * Get plane count in current session. Used to disable the "searching for surfaces" snackbar. + */ + public static native boolean hasDetectedPlanes(long nativeApplication); + + public static Bitmap loadImage(String imageName) { + + try { + return BitmapFactory.decodeStream(assetManager.open(imageName)); + } catch (IOException e) { + Log.e(TAG, "Cannot open image " + imageName); + return null; + } + } + + public static void loadTexture(int target, Bitmap bitmap) { + GLUtils.texImage2D(target, 0, bitmap, 0); + } +} diff --git a/platform_tools/android/apps/arcore/src/main/res/drawable-xxhdpi/ic_launcher.png b/platform_tools/android/apps/arcore/src/main/res/drawable-xxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000000..3f691da039 --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/res/drawable-xxhdpi/ic_launcher.png diff --git a/platform_tools/android/apps/arcore/src/main/res/layout-xlarge-land/activity_main.xml b/platform_tools/android/apps/arcore/src/main/res/layout-xlarge-land/activity_main.xml new file mode 100644 index 0000000000..49457c9802 --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/res/layout-xlarge-land/activity_main.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="horizontal" + android:layout_width="match_parent" + android:background="@android:color/darker_gray" + android:layout_height="match_parent"> + + <!-- The navigation drawer that's always open --> + <ListView android:id="@+id/leftDrawer" + android:layout_width="240dp" + android:layout_height="match_parent" + android:layout_gravity="start" + android:choiceMode="singleChoice" + android:divider="@android:color/transparent" + android:dividerHeight="0dp" + android:layout_marginRight="5dp" + android:background="@android:color/background_light"/> + + <!-- The main content view --> + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <!-- We use mainLayout for recreating SurfaceView --> + <LinearLayout + android:id="@+id/mainLayout" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <SurfaceView + android:id="@+id/surfaceView" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_centerVertical="true" + android:layout_centerHorizontal="true" /> + </LinearLayout> + </RelativeLayout> + +</LinearLayout> + diff --git a/platform_tools/android/apps/arcore/src/main/res/layout/activity_main.xml b/platform_tools/android/apps/arcore/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000000..c3f1e2a3a2 --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/res/layout/activity_main.xml @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="utf-8"?> + +<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/drawerLayout" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <!-- The main content view --> + + <FrameLayout + android:layout_width="match_parent" + android:layout_height="match_parent"> + <View + android:layout_gravity="center" + android:id="@+id/menuView" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + <android.support.v7.widget.Toolbar + android:id="@+id/my_toolbar" + android:layout_width="match_parent" + android:layout_height="?attr/actionBarSize" + android:background="?attr/colorPrimary" + android:elevation="4dp" + android:theme="@style/ThemeOverlay.AppCompat.ActionBar" + app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/> + <EditText + android:id="@+id/text_box" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:focusable="false" + android:clickable="false" + android:visibility="gone"/> + <android.opengl.GLSurfaceView + android:id="@+id/surfaceview" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="bottom" /> + + </LinearLayout> + + + + </FrameLayout> + +</android.support.v4.widget.DrawerLayout> + diff --git a/platform_tools/android/apps/arcore/src/main/res/layout/state_item.xml b/platform_tools/android/apps/arcore/src/main/res/layout/state_item.xml new file mode 100644 index 0000000000..7a7d539d43 --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/res/layout/state_item.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" android:layout_width="match_parent" + android:layout_height="match_parent" + android:weightSum="1"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="10dp" + android:layout_marginLeft="10dp" + android:layout_marginBottom="0dp" + android:textAppearance="?android:attr/textAppearanceLarge" + android:text="Name:" + android:id="@+id/nameText" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="10dp" + android:layout_marginLeft="10dp" + android:layout_marginTop="0dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="Value" + android:id="@+id/valueText" /> + + <Spinner + android:id="@+id/optionSpinner" + android:paddingTop="0dp" + android:paddingBottom="0dp" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + </Spinner> + +</LinearLayout> diff --git a/platform_tools/android/apps/arcore/src/main/res/menu/draw_menu.xml b/platform_tools/android/apps/arcore/src/main/res/menu/draw_menu.xml new file mode 100644 index 0000000000..92851804bd --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/res/menu/draw_menu.xml @@ -0,0 +1,15 @@ +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:id="@+id/draw_text" + android:title="Draw Text" /> + <item + android:id="@+id/draw_shape" + android:title="Draw Shape" > + <menu> + <item android:id="@+id/draw_circle" + android:title="Circle" /> + <item android:id="@+id/draw_rect" + android:title="Rectangle" /> + </menu> + </item> +</menu>
\ No newline at end of file diff --git a/platform_tools/android/apps/arcore/src/main/res/menu/edit_menu.xml b/platform_tools/android/apps/arcore/src/main/res/menu/edit_menu.xml new file mode 100644 index 0000000000..25f6a1dc02 --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/res/menu/edit_menu.xml @@ -0,0 +1,8 @@ +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:id="@+id/edit_size" + android:title="Size" /> + <item + android:id="@+id/edit_text" + android:title="Text" /> +</menu>
\ No newline at end of file diff --git a/platform_tools/android/apps/arcore/src/main/res/menu/rotation_mode.xml b/platform_tools/android/apps/arcore/src/main/res/menu/rotation_mode.xml new file mode 100644 index 0000000000..bcee4f0fb9 --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/res/menu/rotation_mode.xml @@ -0,0 +1,11 @@ +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:id="@+id/rotation_axis_aligned" + android:title="Plane Axis-Aligned"/> + <item android:id="@+id/rotation_camera_aligned" + android:title="Camera-Aligned"/> + <item android:id="@+id/rotation_snap_aligned" + android:title="Snap-Aligned"/> + + <item android:id="@+id/action" + android:title="Do Action"/> +</menu>
\ No newline at end of file diff --git a/platform_tools/android/apps/arcore/src/main/res/menu/title.xml b/platform_tools/android/apps/arcore/src/main/res/menu/title.xml new file mode 100644 index 0000000000..57d04ef143 --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/res/menu/title.xml @@ -0,0 +1,12 @@ +<menu xmlns:android="http://schemas.android.com/apk/res/android" > + + <item + android:id="@+id/action_left" + android:icon="@android:drawable/ic_media_previous" + android:showAsAction="always"/> + + <item android:id="@+id/action_right" + android:icon="@android:drawable/ic_media_next" + android:showAsAction="always"/> + +</menu> diff --git a/platform_tools/android/apps/arcore/src/main/res/values/integers.xml b/platform_tools/android/apps/arcore/src/main/res/values/integers.xml new file mode 100644 index 0000000000..d3da8e7ca5 --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/res/values/integers.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <integer name="value_tag_key">1</integer> +</resources>
\ No newline at end of file diff --git a/platform_tools/android/apps/arcore/src/main/res/values/strings.xml b/platform_tools/android/apps/arcore/src/main/res/values/strings.xml new file mode 100644 index 0000000000..8b7a8ad740 --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/res/values/strings.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2017 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources> + <string name="app_name">HelloAR C</string> +</resources> diff --git a/platform_tools/android/apps/arcore/src/main/res/values/styles.xml b/platform_tools/android/apps/arcore/src/main/res/values/styles.xml new file mode 100644 index 0000000000..3a71bd32b5 --- /dev/null +++ b/platform_tools/android/apps/arcore/src/main/res/values/styles.xml @@ -0,0 +1,35 @@ +<!-- + Copyright 2017 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources> + + <!-- + Base application theme, dependent on API level. This theme is replaced + by AppBaseTheme from res/values-vXX/styles.xml on newer devices. + --> + <style name="AppBaseTheme" parent="android:Theme.Light"> + <!-- + Theme customizations available in newer API levels can go in + res/values-vXX/styles.xml, while customizations related to + backward-compatibility can go here. + --> + </style> + + <!-- Application theme. --> + <style name="AppTheme" parent="AppBaseTheme"> + <!-- All customizations that are NOT specific to a particular API-level can go here. --> + </style> + +</resources> diff --git a/platform_tools/android/apps/build.gradle b/platform_tools/android/apps/build.gradle index 9f285e5b06..64e730897f 100644 --- a/platform_tools/android/apps/build.gradle +++ b/platform_tools/android/apps/build.gradle @@ -3,10 +3,11 @@ buildscript { repositories { + google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.3' + classpath 'com.android.tools.build:gradle:3.0.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -15,6 +16,7 @@ buildscript { allprojects { repositories { + google() jcenter() } } @@ -105,5 +107,6 @@ def constructBuildCommand(project, variant, appName) { } String out_dir = getVariantOutDir(project, variant).skiaOut + return "${depotToolsDir}/ninja -C $out_dir $appName" } diff --git a/platform_tools/android/apps/gradle/wrapper/gradle-wrapper.properties b/platform_tools/android/apps/gradle/wrapper/gradle-wrapper.properties index 9326d8c394..68afb72efc 100644 --- a/platform_tools/android/apps/gradle/wrapper/gradle-wrapper.properties +++ b/platform_tools/android/apps/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
\ No newline at end of file diff --git a/platform_tools/android/apps/settings.gradle b/platform_tools/android/apps/settings.gradle index a8c2cb3fb4..148e1ac2af 100644 --- a/platform_tools/android/apps/settings.gradle +++ b/platform_tools/android/apps/settings.gradle @@ -1,2 +1,3 @@ include ':viewer' include ':skqp' +include ':arcore' //must build out directory first: bin/gn gen out/arm64 --args='ndk="NDKPATH" target_cpu="ABI" is_component_build=true' diff --git a/platform_tools/android/apps/skqp/build.gradle b/platform_tools/android/apps/skqp/build.gradle index e368a66ef6..2c54751b3a 100644 --- a/platform_tools/android/apps/skqp/build.gradle +++ b/platform_tools/android/apps/skqp/build.gradle @@ -22,6 +22,7 @@ android { versionName "1.0" signingConfig signingConfigs.debug } + flavorDimensions "base" sourceSets.main.jni.srcDirs = [] sourceSets.main.jniLibs.srcDir "src/main/libs" productFlavors { universal{}; arm {}; arm64 {}; x86 {}; x64 {}; arm64vulkan{}; } diff --git a/platform_tools/android/apps/viewer/build.gradle b/platform_tools/android/apps/viewer/build.gradle index 630544d0bb..6ec51c857d 100644 --- a/platform_tools/android/apps/viewer/build.gradle +++ b/platform_tools/android/apps/viewer/build.gradle @@ -22,6 +22,7 @@ android { versionName "1.0" signingConfig signingConfigs.debug } + flavorDimensions "base" sourceSets.main.jni.srcDirs = [] //disable automatic ndk-build call sourceSets.main.jniLibs.srcDir "src/main/libs" productFlavors { universal{}; arm {}; arm64 {}; x86 {}; x64 {}; arm64vulkan{}; } diff --git a/platform_tools/libraries/include/arcore_c_api.h b/platform_tools/libraries/include/arcore_c_api.h new file mode 100644 index 0000000000..cecd117fa3 --- /dev/null +++ b/platform_tools/libraries/include/arcore_c_api.h @@ -0,0 +1,2255 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef ARCORE_C_API_H_ +#define ARCORE_C_API_H_ + +#include <stddef.h> +#include <stdint.h> + +/// @defgroup concepts Concepts +/// High-Level concepts of ARCore +/// +/// @section ownership Object ownership +/// +/// ARCore has two categories of objects: "value types" and "reference types". +/// +/// - Value types are owned by application. They are created and destroyed using +/// the @c create / @c destroy methods, and are populated by ARCore using +/// methods with @c get in the method name. +/// +/// - Reference types are owned by ARCore. A reference is acquired by one of the +/// @c acquire methods. For each call to the @c acquire method, the +/// application must call the matching @c release method. Note that even if +/// last reference is released, ARCore may continue to hold a reference to the +/// object at ARCore's discretion. +/// +/// Reference types are further split into: +/// +/// - Long-lived objects. These objects persist across frames, possibly for the +/// life span of the application or session. Acquire may fail if ARCore is in +/// an incorrect state, e.g. not tracking. Acquire from list always succeeds, +/// as the object already exists. +/// +/// - Transient large data. These objects are usually acquired per-frame and are +/// a limited resource. The @c acquire call may fail if the resource is +/// exhausted (too many are currently held), deadline exceeded (the target of +/// the acquire was already released), or the resource is not yet available. +/// +/// Note: Lists are value types (owned by application), but can hold references +/// to long-lived objects. This means that the references held by a list are not +/// released until either the list is destroyed, or is re-populated by another +/// api call. +/// +/// For example, ::ArAnchorList, which is a value type, will hold references to +/// Anchors, which are long-lived objects. +/// +/// @section spaces Poses and Coordinate Spaces +/// +/// An @c ArPose describes an rigid transformation from one coordinate space to +/// another. As provided from all ARCore APIs, Poses always describe the +/// transformation from object's local coordinate space to the <b>world +/// coordinate space</b> (see below). That is, Poses from ARCore APIs can be +/// thought of as equivalent to OpenGL model matrices. +/// +/// The transformation is defined using a quaternion rotation about the origin +/// followed by a translation. +/// +/// The coordinate system is right-handed, like OpenGL conventions. +/// +/// Translation units are meters. +/// +/// @section worldcoordinates World Coordinate Space +/// +/// As ARCore's understanding of the environment changes, it adjusts its model +/// of the world to keep things consistent. When this happens, the numerical +/// location (coordinates) of the camera and anchors can change significantly to +/// maintain appropriate relative positions of the physical locations they +/// represent. +/// +/// These changes mean that every frame should be considered to be in a +/// completely unique world coordinate space. The numerical coordinates of +/// anchors and the camera should never be used outside the rendering frame +/// during which they were retrieved. If a position needs to be considered +/// beyond the scope of a single rendering frame, either an anchor should be +/// created or a position relative to a nearby existing anchor should be used. + +/// @defgroup common Common Definitions +/// Shared types and constants + +/// @defgroup anchor Anchor +/// Describes a fixed location and orientation in the real world. + +/// @defgroup arcoreapk ArCoreApk +/// Management of the ARCore service APK + +/// @defgroup augmented_image AugmentedImage +/// An image being detected and tracked by ARCore. + +/// @defgroup augmented_image_database AugmentedImageDatabase +/// Database containing a list of images to be detected and tracked by ARCore. + +/// @defgroup camera Camera +/// Provides information about the camera that is used to capture images. + +/// @defgroup cloud Cloud Anchors +/// The cloud state and configuration of an Anchor and the AR Session. + +/// @defgroup config Configuration +/// Session configuration. + +/// @defgroup frame Frame +/// Per-frame state. + +/// @defgroup hit HitResult +/// Defines an intersection between a ray and estimated real-world geometry. + +/// @defgroup image ImageMetadata +/// Provides access to metadata from the camera image capture result. + +/// @defgroup light LightEstimate +/// Holds information about the estimated lighting of the real scene. + +/// @defgroup plane Plane +/// Describes the current best knowledge of a real-world planar surface. + +/// @defgroup point Point +/// Represents a point in space that ARCore is tracking. + +/// @defgroup pointcloud PointCloud +/// Contains a set of observed 3D points and confidence values. + +/// @defgroup pose Pose +/// Represents an immutable rigid transformation from one coordinate +/// space to another. + +/// @defgroup session Session +/// Session management. + +/// @defgroup trackable Trackable +/// Something that can be tracked and that Anchors can be attached to. + +/// @defgroup cpp_helpers C++ helper functions + +/// @addtogroup config +/// @{ + +/// An opaque session configuration object (@ref ownership "value type"). +/// +/// Create with ArConfig_create()<br> +/// Release with ArConfig_destroy() +typedef struct ArConfig_ ArConfig; + +/// @} + +/// @addtogroup session +/// @{ + +/// The ArCore session (@ref ownership "value type"). +/// +/// Create with ArSession_create()<br> +/// Release with ArSession_destroy() +typedef struct ArSession_ ArSession; + +/// @} + +/// @addtogroup pose +/// @{ + +/// A structured rigid transformation (@ref ownership "value type"). +/// +/// Allocate with ArPose_create()<br> +/// Release with ArPose_destroy() +typedef struct ArPose_ ArPose; + +/// @} + +// Camera. + +/// @addtogroup camera +/// @{ + +/// The virtual and physical camera +/// (@ref ownership "reference type, long-lived"). +/// +/// Acquire with ArFrame_acquireCamera()<br> +/// Release with ArCamera_release() +typedef struct ArCamera_ ArCamera; + +/// @} + +// Frame and frame objects. + +/// @addtogroup frame +/// @{ + +/// The world state resulting from an update (@ref ownership "value type"). +/// +/// Allocate with ArFrame_create()<br> +/// Populate with ArSession_update()<br> +/// Release with ArFrame_destroy() +typedef struct ArFrame_ ArFrame; + +/// @} + +// LightEstimate. + +/// @addtogroup light +/// @{ + +/// An estimate of the real-world lighting (@ref ownership "value type"). +/// +/// Allocate with ArLightEstimate_create()<br> +/// Populate with ArFrame_getLightEstimate()<br> +/// Release with ArLightEstimate_destroy() +typedef struct ArLightEstimate_ ArLightEstimate; + +/// @} + +// PointCloud. + +/// @addtogroup pointcloud +/// @{ + +/// A cloud of tracked 3D visual feature points +/// (@ref ownership "reference type, large data"). +/// +/// Acquire with ArFrame_acquirePointCloud()<br> +/// Release with ArPointCloud_release() +typedef struct ArPointCloud_ ArPointCloud; + +/// @} + +// ImageMetadata. + +/// @addtogroup image +/// @{ + +/// Camera capture metadata (@ref ownership "reference type, large data"). +/// +/// Acquire with ArFrame_acquireImageMetadata()<br> +/// Release with ArImageMetadata_release() +typedef struct ArImageMetadata_ ArImageMetadata; + +/// Accessing CPU image from the tracking camera +/// (@ref ownership "reference type, large data"). +/// +/// Acquire with ArFrame_acquireCameraImage()<br> +/// Convert to NDK AImage with ArImage_getNdkImage()<br> +/// Release with ArImage_release(). +typedef struct ArImage_ ArImage; + +/// Forward declaring the AImage struct from Android NDK, which is used +/// in ArImage_getNdkImage(). +typedef struct AImage AImage; +/// @} + +// Trackables. + +/// @addtogroup trackable +/// @{ + +/// Trackable base type (@ref ownership "reference type, long-lived"). +typedef struct ArTrackable_ ArTrackable; + +/// A list of ArTrackables (@ref ownership "value type"). +/// +/// Allocate with ArTrackableList_create()<br> +/// Release with ArTrackableList_destroy() +typedef struct ArTrackableList_ ArTrackableList; + +/// @} + +// Plane + +/// @addtogroup plane +/// @{ + +/// A detected planar surface (@ref ownership "reference type, long-lived"). +/// +/// Trackable type: #AR_TRACKABLE_PLANE <br> +/// Release with: ArTrackable_release() +typedef struct ArPlane_ ArPlane; + +/// @} + +// Point + +/// @addtogroup point +/// @{ + +/// An arbitrary point in space (@ref ownership "reference type, long-lived"). +/// +/// Trackable type: #AR_TRACKABLE_POINT <br> +/// Release with: ArTrackable_release() +typedef struct ArPoint_ ArPoint; + +/// @} + +// Augmented Image + +/// @addtogroup augmented_image +/// @{ + +/// An image that has been detected and tracked (@ref ownership "reference type, +/// long-lived"). +/// +/// Trackable type: #AR_TRACKABLE_AUGMENTED_IMAGE <br> +/// Release with: ArTrackable_release() +typedef struct ArAugmentedImage_ ArAugmentedImage; + +/// @} + +// Augmented Image Database +/// @addtogroup augmented_image_database +/// @{ + +/// A database of images to be detected and tracked by ARCore (@ref ownership +/// "value type"). +/// +/// An image database supports up to 1000 images. A database can be generated by +/// the `arcoreimg` command-line database generation tool provided in the SDK, +/// or dynamically created at runtime by adding individual images. +/// +/// Only one image database can be active in a session. Any images in the +/// currently active image database that have a TRACKING/PAUSED state will +/// immediately be set to the STOPPED state if a different or null image +/// database is made active in the current session Config. +/// +/// Create with ArAugmentedImageDatabase_create() or +/// ArAugmentedImageDatabase_deserialize()<br> +/// Release with: ArAugmentedImageDatabase_destroy() +typedef struct ArAugmentedImageDatabase_ ArAugmentedImageDatabase; + +/// @} + +// Anchors. + +/// @addtogroup anchor +/// @{ + +/// A position in space attached to a trackable +/// (@ref ownership "reference type, long-lived"). +/// +/// Create with ArSession_acquireNewAnchor() or +/// ArHitResult_acquireNewAnchor()<br> +/// Release with ArAnchor_release() +typedef struct ArAnchor_ ArAnchor; + +/// A list of anchors (@ref ownership "value type"). +/// +/// Allocate with ArAnchorList_create()<br> +/// Release with ArAnchorList_destroy() +typedef struct ArAnchorList_ ArAnchorList; + +/// @} + +// Hit result functionality. + +/// @addtogroup hit +/// @{ + +/// A single trackable hit (@ref ownership "value type"). +/// +/// Allocate with ArHitResult_create()<br> +/// Populate with ArHitResultList_getItem()<br> +/// Release with ArHitResult_destroy() +typedef struct ArHitResult_ ArHitResult; + +/// A list of hit test results (@ref ownership "value type"). +/// +/// Allocate with ArHitResultList_create()<br> +/// Release with ArHitResultList_destroy()<br> +typedef struct ArHitResultList_ ArHitResultList; + +/// @} + +/// @cond EXCLUDE_FROM_DOXYGEN + +// Forward declaring the ACameraMetadata struct from Android NDK, which is used +// in ArImageMetadata_getNdkCameraMetadata +typedef struct ACameraMetadata ACameraMetadata; + +/// @endcond + +/// @addtogroup cpp_helpers +/// @{ +/// These methods expose allowable type conversions as C++ helper functions. +/// This avoids having to explicitly @c reinterpret_cast in most cases. +/// +/// Note: These methods only change the type of a pointer - they do not change +/// the reference count of the referenced objects. +/// +/// Note: There is no runtime checking that casts are correct. Call @ref +/// ArTrackable_getType() beforehand to figure out the correct cast. + +#ifdef __cplusplus +/// Upcasts to ArTrackable +inline ArTrackable *ArAsTrackable(ArPlane *plane) { + return reinterpret_cast<ArTrackable *>(plane); +} + +/// Upcasts to ArTrackable +inline ArTrackable *ArAsTrackable(ArPoint *point) { + return reinterpret_cast<ArTrackable *>(point); +} + +/// Upcasts to ArTrackable +inline ArTrackable *ArAsTrackable(ArAugmentedImage *augmented_image) { + return reinterpret_cast<ArTrackable *>(augmented_image); +} + +/// Downcasts to ArPlane. +inline ArPlane *ArAsPlane(ArTrackable *trackable) { + return reinterpret_cast<ArPlane *>(trackable); +} + +/// Downcasts to ArPoint. +inline ArPoint *ArAsPoint(ArTrackable *trackable) { + return reinterpret_cast<ArPoint *>(trackable); +} + +/// Downcasts to ArAugmentedImage. +inline ArAugmentedImage *ArAsAugmentedImage(ArTrackable *trackable) { + return reinterpret_cast<ArAugmentedImage *>(trackable); +} +#endif +/// @} + +// If compiling for C++11, use the 'enum underlying type' feature to enforce +// size for ABI compatibility. In pre-C++11, use int32_t for fixed size. +#if __cplusplus >= 201100 +#define AR_DEFINE_ENUM(_type) enum _type : int32_t +#else +#define AR_DEFINE_ENUM(_type) \ + typedef int32_t _type; \ + enum +#endif + +#if defined(__GNUC__) && !defined(AR_DEPRECATED_SUPPRESS) +#define AR_DEPRECATED(_deprecation_string) \ + __attribute__((deprecated(_deprecation_string))); +#else +#define AR_DEPRECATED(_deprecation_string) +#endif + +/// @ingroup trackable +/// Object types for heterogeneous query/update lists. +AR_DEFINE_ENUM(ArTrackableType){ + /// The base Trackable type. Can be passed to ArSession_getAllTrackables() + /// and ArFrame_getUpdatedTrackables() as the @c filter_type to get + /// all/updated Trackables of all types. + AR_TRACKABLE_BASE_TRACKABLE = 0x41520100, + + /// The ::ArPlane subtype of Trackable. + AR_TRACKABLE_PLANE = 0x41520101, + + /// The ::ArPoint subtype of Trackable. + AR_TRACKABLE_POINT = 0x41520102, + + /// The ::ArAugmentedImage subtype of Trackable. + AR_TRACKABLE_AUGMENTED_IMAGE = 0x41520104, + + /// An invalid Trackable type. + AR_TRACKABLE_NOT_VALID = 0}; + +/// @ingroup common +/// Return code indicating success or failure of a method. +AR_DEFINE_ENUM(ArStatus){ + /// The operation was successful. + AR_SUCCESS = 0, + + /// One of the arguments was invalid, either null or not appropriate for the + /// operation requested. + AR_ERROR_INVALID_ARGUMENT = -1, + + /// An internal error occurred that the application should not attempt to + /// recover from. + AR_ERROR_FATAL = -2, + + /// An operation was attempted that requires the session be running, but the + /// session was paused. + AR_ERROR_SESSION_PAUSED = -3, + + /// An operation was attempted that requires the session be paused, but the + /// session was running. + AR_ERROR_SESSION_NOT_PAUSED = -4, + + /// An operation was attempted that the session be in the TRACKING state, + /// but the session was not. + AR_ERROR_NOT_TRACKING = -5, + + /// A texture name was not set by calling ArSession_setCameraTextureName() + /// before the first call to ArSession_update() + AR_ERROR_TEXTURE_NOT_SET = -6, + + /// An operation required GL context but one was not available. + AR_ERROR_MISSING_GL_CONTEXT = -7, + + /// The configuration supplied to ArSession_configure() was unsupported. + /// To avoid this error, ensure that Session_checkSupported() returns true. + AR_ERROR_UNSUPPORTED_CONFIGURATION = -8, + + /// The android camera permission has not been granted prior to calling + /// ArSession_resume() + AR_ERROR_CAMERA_PERMISSION_NOT_GRANTED = -9, + + /// Acquire failed because the object being acquired is already released. + /// For example, this happens if the application holds an ::ArFrame beyond + /// the next call to ArSession_update(), and then tries to acquire its point + /// cloud. + AR_ERROR_DEADLINE_EXCEEDED = -10, + + /// There are no available resources to complete the operation. In cases of + /// @c acquire methods returning this error, This can be avoided by + /// releasing previously acquired objects before acquiring new ones. + AR_ERROR_RESOURCE_EXHAUSTED = -11, + + /// Acquire failed because the data isn't available yet for the current + /// frame. For example, acquire the image metadata may fail with this error + /// because the camera hasn't fully started. + AR_ERROR_NOT_YET_AVAILABLE = -12, + + /// The android camera has been reallocated to a higher priority app or is + /// otherwise unavailable. + AR_ERROR_CAMERA_NOT_AVAILABLE = -13, + + /// The host/resolve function call failed because the Session is not + /// configured for cloud anchors. + AR_ERROR_CLOUD_ANCHORS_NOT_CONFIGURED = -14, + + /// ArSession_configure() failed because the specified configuration + /// required the Android INTERNET permission, which the application did not + /// have. + AR_ERROR_INTERNET_PERMISSION_NOT_GRANTED = -15, + + /// HostCloudAnchor() failed because the anchor is not a type of anchor that + /// is currently supported for hosting. + AR_ERROR_ANCHOR_NOT_SUPPORTED_FOR_HOSTING = -16, + + /// An image with insufficient quality (e.g. too few features) was attempted + /// to be added to the image database. + AR_ERROR_IMAGE_INSUFFICIENT_QUALITY = -17, + + /// The data passed in for this operation was not in a valid format. + AR_ERROR_DATA_INVALID_FORMAT = -18, + + /// The data passed in for this operation is not supported by this version + /// of the SDK. + AR_ERROR_DATA_UNSUPPORTED_VERSION = -19, + + /// The ARCore APK is not installed on this device. + AR_UNAVAILABLE_ARCORE_NOT_INSTALLED = -100, + + /// The device is not currently compatible with ARCore. + AR_UNAVAILABLE_DEVICE_NOT_COMPATIBLE = -101, + + /// The ARCore APK currently installed on device is too old and needs to be + /// updated. + AR_UNAVAILABLE_APK_TOO_OLD = -103, + + /// The ARCore APK currently installed no longer supports the ARCore SDK + /// that the application was built with. + AR_UNAVAILABLE_SDK_TOO_OLD = -104, + + /// The user declined installation of the ARCore APK during this run of the + /// application and the current request was not marked as user-initiated. + AR_UNAVAILABLE_USER_DECLINED_INSTALLATION = -105}; + +/// @ingroup common +/// Describes the tracking state of a @c Trackable, an ::ArAnchor or the +/// ::ArCamera. +AR_DEFINE_ENUM(ArTrackingState){ + /// The object is currently tracked and its pose is current. + AR_TRACKING_STATE_TRACKING = 0, + + /// ARCore has paused tracking this object, but may resume tracking it in + /// the future. This can happen if device tracking is lost, if the user + /// enters a new space, or if the Session is currently paused. When in this + /// state, the positional properties of the object may be wildly inaccurate + /// and should not be used. + AR_TRACKING_STATE_PAUSED = 1, + + /// ARCore has stopped tracking this Trackable and will never resume + /// tracking it. + AR_TRACKING_STATE_STOPPED = 2}; + +/// @ingroup cloud +/// Describes the current cloud state of an @c Anchor. +AR_DEFINE_ENUM(ArCloudAnchorState){ + /// The anchor is purely local. It has never been hosted using + /// hostCloudAnchor, and has not been acquired using acquireCloudAnchor. + AR_CLOUD_ANCHOR_STATE_NONE = 0, + + /// A hosting/resolving task for the anchor is in progress. Once the task + /// completes in the background, the anchor will get a new cloud state after + /// the next update() call. + AR_CLOUD_ANCHOR_STATE_TASK_IN_PROGRESS = 1, + + /// A hosting/resolving task for this anchor completed successfully. + AR_CLOUD_ANCHOR_STATE_SUCCESS = 2, + + /// A hosting/resolving task for this anchor finished with an internal + /// error. The app should not attempt to recover from this error. + AR_CLOUD_ANCHOR_STATE_ERROR_INTERNAL = -1, + + /// The app cannot communicate with the ARCore Cloud because of an invalid + /// or unauthorized API key in the manifest, or because there was no API key + /// present in the manifest. + AR_CLOUD_ANCHOR_STATE_ERROR_NOT_AUTHORIZED = -2, + + /// The ARCore Cloud was unreachable. This can happen because of a number of + /// reasons. The request sent to the server could have timed out with no + /// response, there could be a bad network connection, DNS unavailability, + /// firewall issues, or anything that could affect the device's ability to + /// connect to the ARCore Cloud. + AR_CLOUD_ANCHOR_STATE_ERROR_SERVICE_UNAVAILABLE = -3, + + /// The application has exhausted the request quota allotted to the given + /// API key. The developer should request additional quota for the ARCore + /// Cloud for their API key from the Google Developers Console. + AR_CLOUD_ANCHOR_STATE_ERROR_RESOURCE_EXHAUSTED = -4, + + /// Hosting failed, because the server could not successfully process the + /// dataset for the given anchor. The developer should try again after the + /// device has gathered more data from the environment. + AR_CLOUD_ANCHOR_STATE_ERROR_HOSTING_DATASET_PROCESSING_FAILED = -5, + + /// Resolving failed, because the ARCore Cloud could not find the provided + /// cloud anchor ID. + AR_CLOUD_ANCHOR_STATE_ERROR_CLOUD_ID_NOT_FOUND = -6, + + /// The server could not match the visual features provided by ARCore + /// against the localization dataset of the requested cloud anchor ID. This + /// means that the anchor pose being requested was likely not created in the + /// user's surroundings. + AR_CLOUD_ANCHOR_STATE_ERROR_RESOLVING_LOCALIZATION_NO_MATCH = -7, + + /// The anchor could not be resolved because the SDK used to host the anchor + /// was newer than and incompatible with the version being used to acquire + /// it. + AR_CLOUD_ANCHOR_STATE_ERROR_RESOLVING_SDK_VERSION_TOO_OLD = -8, + + /// The anchor could not be acquired because the SDK used to host the anchor + /// was older than and incompatible with the version being used to acquire + /// it. + AR_CLOUD_ANCHOR_STATE_ERROR_RESOLVING_SDK_VERSION_TOO_NEW = -9}; + +/// @ingroup arcoreapk +/// Describes the current state of ARCore availability on the device. +AR_DEFINE_ENUM(ArAvailability){ + /// An internal error occurred while determining ARCore availability. + AR_AVAILABILITY_UNKNOWN_ERROR = 0, + /// ARCore is not installed, and a query has been issued to check if ARCore + /// is is supported. + AR_AVAILABILITY_UNKNOWN_CHECKING = 1, + /// ARCore is not installed, and the query to check if ARCore is supported + /// timed out. This may be due to the device being offline. + AR_AVAILABILITY_UNKNOWN_TIMED_OUT = 2, + /// ARCore is not supported on this device. + AR_AVAILABILITY_UNSUPPORTED_DEVICE_NOT_CAPABLE = 100, + /// The device and Android version are supported, but the ARCore APK is not + /// installed. + AR_AVAILABILITY_SUPPORTED_NOT_INSTALLED = 201, + /// The device and Android version are supported, and a version of the + /// ARCore APK is installed, but that ARCore APK version is too old. + AR_AVAILABILITY_SUPPORTED_APK_TOO_OLD = 202, + /// ARCore is supported, installed, and available to use. + AR_AVAILABILITY_SUPPORTED_INSTALLED = 203}; + +/// @ingroup arcoreapk +/// Indicates the outcome of a call to ArCoreApk_requestInstall(). +AR_DEFINE_ENUM(ArInstallStatus){ + /// The requested resource is already installed. + AR_INSTALL_STATUS_INSTALLED = 0, + /// Installation of the resource was requested. The current activity will be + /// paused. + AR_INSTALL_STATUS_INSTALL_REQUESTED = 1}; + +/// @ingroup arcoreapk +/// Controls the behavior of the installation UI. +AR_DEFINE_ENUM(ArInstallBehavior){ + /// Hide the Cancel button during initial prompt and prevent user from + /// exiting via tap-outside. + /// + /// Note: The BACK button or tapping outside of any marketplace-provided + /// install dialog will still decline the installation. + AR_INSTALL_BEHAVIOR_REQUIRED = 0, + /// Include Cancel button in initial prompt and allow easily backing out + /// after installation has been initiated. + AR_INSTALL_BEHAVIOR_OPTIONAL = 1}; + +/// @ingroup arcoreapk +/// Controls the message displayed by the installation UI. +AR_DEFINE_ENUM(ArInstallUserMessageType){ + /// Display a localized message like "This application requires ARCore...". + AR_INSTALL_USER_MESSAGE_TYPE_APPLICATION = 0, + /// Display a localized message like "This feature requires ARCore...". + AR_INSTALL_USER_MESSAGE_TYPE_FEATURE = 1, + /// Application has explained why ARCore is required prior to calling + /// ArCoreApk_requestInstall(), skip user education dialog. + AR_INSTALL_USER_MESSAGE_TYPE_USER_ALREADY_INFORMED = 2}; + +/// @ingroup config +/// Select the behavior of the lighting estimation subsystem. +AR_DEFINE_ENUM(ArLightEstimationMode){ + /// Lighting estimation is disabled. + AR_LIGHT_ESTIMATION_MODE_DISABLED = 0, + /// Lighting estimation is enabled, generating a single-value intensity + /// estimate. + AR_LIGHT_ESTIMATION_MODE_AMBIENT_INTENSITY = 1}; + +/// @ingroup config +/// Select the behavior of the plane detection subsystem. +AR_DEFINE_ENUM(ArPlaneFindingMode){ + /// Plane detection is disabled. + AR_PLANE_FINDING_MODE_DISABLED = 0, + /// Detection of only horizontal planes is enabled. + AR_PLANE_FINDING_MODE_HORIZONTAL = 1, + /// Detection of only vertical planes is enabled. + AR_PLANE_FINDING_MODE_VERTICAL = 2, + /// Detection of horizontal and vertical planes is enabled. + AR_PLANE_FINDING_MODE_HORIZONTAL_AND_VERTICAL = 3}; + +/// @ingroup config +/// Selects the behavior of ArSession_update(). +AR_DEFINE_ENUM(ArUpdateMode){ + /// @c update() will wait until a new camera image is available, or until + /// the built-in timeout (currently 66ms) is reached. On most + /// devices the camera is configured to capture 30 frames per second. + /// If the camera image does not arrive by the built-in timeout, then + /// @c update() will return the most recent ::ArFrame object. + AR_UPDATE_MODE_BLOCKING = 0, + /// @c update() will return immediately without blocking. If no new camera + /// image is available, then @c update() will return the most recent + /// ::ArFrame object. + AR_UPDATE_MODE_LATEST_CAMERA_IMAGE = 1}; + +/// @ingroup plane +/// Simple summary of the normal vector of a plane, for filtering purposes. +AR_DEFINE_ENUM(ArPlaneType){ + /// A horizontal plane facing upward (for example a floor or tabletop). + AR_PLANE_HORIZONTAL_UPWARD_FACING = 0, + /// A horizontal plane facing downward (for example a ceiling). + AR_PLANE_HORIZONTAL_DOWNWARD_FACING = 1, + /// A vertical plane (for example a wall). + AR_PLANE_VERTICAL = 2}; + +/// @ingroup light +/// Tracks the validity of a light estimate. +AR_DEFINE_ENUM(ArLightEstimateState){ + /// The light estimate is not valid this frame and should not be used for + /// rendering. + AR_LIGHT_ESTIMATE_STATE_NOT_VALID = 0, + /// The light estimate is valid this frame. + AR_LIGHT_ESTIMATE_STATE_VALID = 1}; + +/// @ingroup point +/// Indicates the orientation mode of the ::ArPoint. +AR_DEFINE_ENUM(ArPointOrientationMode){ + /// The orientation of the ::ArPoint is initialized to identity but may + /// adjust slightly over time. + AR_POINT_ORIENTATION_INITIALIZED_TO_IDENTITY = 0, + /// The orientation of the ::ArPoint will follow the behavior described in + /// ArHitResult_getHitPose(). + AR_POINT_ORIENTATION_ESTIMATED_SURFACE_NORMAL = 1}; + +/// @ingroup cloud +/// Indicates the cloud configuration of the ::ArSession. +AR_DEFINE_ENUM(ArCloudAnchorMode){ + /// Anchor Hosting is disabled. This is the value set in the default + /// ::ArConfig. + AR_CLOUD_ANCHOR_MODE_DISABLED = 0, + /// Anchor Hosting is enabled. Setting this value and calling @c configure() + /// will require that the application have the Android INTERNET permission. + AR_CLOUD_ANCHOR_MODE_ENABLED = 1}; + +#undef AR_DEFINE_ENUM + +#ifdef __cplusplus +extern "C" { +#endif + +// Note: destroy methods do not take ArSession* to allow late destruction in +// finalizers of garbage-collected languages such as Java. + +/// @addtogroup arcoreapk +/// @{ + +/// Determines if ARCore is supported on this device. This may initiate a query +/// with a remote service to determine if the device is compatible, in which +/// case it will return immediately with @c out_availability set to +/// #AR_AVAILABILITY_UNKNOWN_CHECKING. +/// +/// For ARCore-required apps (as indicated by the <a +/// href="https://developers.google.com/ar/develop/c/enable-arcore#ar_required">manifest +/// meta-data</a>) this method will assume device compatibility and will always +/// immediately return one of #AR_AVAILABILITY_SUPPORTED_INSTALLED, +/// #AR_AVAILABILITY_SUPPORTED_APK_TOO_OLD, or +/// #AR_AVAILABILITY_SUPPORTED_NOT_INSTALLED. +/// +/// Note: A result #AR_AVAILABILITY_SUPPORTED_INSTALLED only indicates presence +/// of a suitably versioned ARCore APK. Session creation may still fail if the +/// ARCore APK has been sideloaded onto an incompatible device. +/// +/// May be called prior to ArSession_create(). +/// +/// @param[in] env The application's @c JNIEnv object +/// @param[in] application_context A @c jobject referencing the application's +/// Android @c Context. +/// @param[out] out_availability A pointer to an ArAvailability to receive +/// the result. +void ArCoreApk_checkAvailability(void *env, + void *application_context, + ArAvailability *out_availability); + +/// Initiates installation of ARCore if needed. When your apllication launches +/// or enters an AR mode, it should call this method with @c +/// user_requested_install = 1. +/// +/// If ARCore is installed and compatible, this function will set @c +/// out_install_status to #AR_INSTALL_STATUS_INSTALLED. +/// +/// If ARCore is not currently installed or the installed version not +/// compatible, the function will set @c out_install_status to +/// #AR_INSTALL_STATUS_INSTALL_REQUESTED and return immediately. Your current +/// activity will then pause while the user is informed about the requierment of +/// ARCore and offered the opportunity to install it. +/// +/// When your activity resumes, you should call this method again, this time +/// with @c user_requested_install = 0. This will either set +/// @c out_install_status to #AR_INSTALL_STATUS_INSTALLED or return an error +/// code indicating the reason that installation could not be completed. +/// +/// ARCore-optional applications must ensure that ArCoreApk_checkAvailability() +/// returns one of the <tt>AR_AVAILABILITY_SUPPORTED_...</tt> values before +/// calling this method. +/// +/// See <A +/// href="https://github.com/google-ar/arcore-android-sdk/tree/master/samples"> +/// our sample code</A> for an example of how an ARCore-required application +/// should use this function. +/// +/// May be called prior to ArSession_create(). +/// +/// For more control over the message displayed and ease of exiting the process, +/// see ArCoreApk_requestInstallCustom(). +/// +/// <b>Caution:</b> The value of <tt>*out_install_status</tt> should only be +/// considered when #AR_SUCCESS is returned. Otherwise this value must be +/// ignored. +/// +/// @param[in] env The application's @c JNIEnv object +/// @param[in] application_activity A @c jobject referencing the application's +/// current Android @c Activity. +/// @param[in] user_requested_install if set, override the previous installation +/// failure message and always show the installation interface. +/// @param[out] out_install_status A pointer to an ArInstallStatus to receive +/// the resulting install status, if successful. Note: this value is only +/// valid with the return value is #AR_SUCCESS. +/// @return #AR_SUCCESS, or any of: +/// - #AR_ERROR_FATAL if an error occurs while checking for or requesting +/// installation +/// - #AR_UNAVAILABLE_DEVICE_NOT_COMPATIBLE if ARCore is not supported +/// on this device. +/// - #AR_UNAVAILABLE_USER_DECLINED_INSTALLATION if the user previously declined +/// installation. +ArStatus ArCoreApk_requestInstall(void *env, + void *application_activity, + bool user_requested_install, + ArInstallStatus *out_install_status); + +/// Initiates installation of ARCore if required, with configurable behavior. +/// +/// This is a more flexible version of ArCoreApk_requestInstall() allowing the +/// application control over the initial informational dialog and ease of +/// exiting or cancelling the installation. +/// +/// See ArCoreApk_requestInstall() for details of use and behavior. +/// +/// May be called prior to ArSession_create(). +/// +/// @param[in] env The application's @c JNIEnv object +/// @param[in] application_activity A @c jobject referencing the application's +/// current Android @c Activity. +/// @param[in] user_requested_install if set, override the previous installation +/// failure message and always show the installation interface. +/// @param[in] install_behavior controls the presence of the cancel button at +/// the user education screen and if tapping outside the education screen or +/// install-in-progress screen causes them to dismiss. +/// @param[in] message_type controls the text of the of message displayed +/// before showing the install prompt, or disables display of this message. +/// @param[out] out_install_status A pointer to an ArInstallStatus to receive +/// the resulting install status, if successful. Note: this value is only +/// valid with the return value is #AR_SUCCESS. +/// @return #AR_SUCCESS, or any of: +/// - #AR_ERROR_FATAL if an error occurs while checking for or requesting +/// installation +/// - #AR_UNAVAILABLE_DEVICE_NOT_COMPATIBLE if ARCore is not supported +/// on this device. +/// - #AR_UNAVAILABLE_USER_DECLINED_INSTALLATION if the user previously declined +/// installation. +ArStatus ArCoreApk_requestInstallCustom(void *env, + void *application_activity, + int32_t user_requested_install, + ArInstallBehavior install_behavior, + ArInstallUserMessageType message_type, + ArInstallStatus *out_install_status); + +/// @} +/// @addtogroup session +/// @{ + +/// Attempts to create a new ARCore session. +/// +/// This is the entry point of ARCore. This function MUST be the first ARCore +/// call made by an application. +/// +/// @param[in] env The application's @c JNIEnv object +/// @param[in] application_context A @c jobject referencing the application's +/// Android @c Context +/// @param[out] out_session_pointer A pointer to an @c ArSession* to receive +/// the address of the newly allocated session. +/// @return #AR_SUCCESS or any of: +/// - #AR_UNAVAILABLE_ARCORE_NOT_INSTALLED +/// - #AR_UNAVAILABLE_DEVICE_NOT_COMPATIBLE +/// - #AR_UNAVAILABLE_APK_TOO_OLD +/// - #AR_UNAVAILABLE_SDK_TOO_OLD +/// - #AR_ERROR_CAMERA_PERMISSION_NOT_GRANTED +ArStatus ArSession_create(void *env, + void *application_context, + ArSession **out_session_pointer); + +/// @} + +// === ArConfig methods === + +/// @addtogroup config +/// @{ + +/// Creates a new configuration object and initializes it to a sensible default +/// configuration. Plane detection and lighting estimation are enabled, and +/// blocking update is selected. This configuration is guaranteed to be +/// supported on all devices that support ARCore. +void ArConfig_create(const ArSession *session, ArConfig **out_config); + +/// Releases memory used by the provided configuration object. +void ArConfig_destroy(ArConfig *config); + +/// Stores the currently configured lighting estimation mode into +/// @c *light_estimation_mode. +void ArConfig_getLightEstimationMode( + const ArSession *session, + const ArConfig *config, + ArLightEstimationMode *light_estimation_mode); + +/// Sets the lighting estimation mode that should be used. See +/// ::ArLightEstimationMode for available options. +void ArConfig_setLightEstimationMode( + const ArSession *session, + ArConfig *config, + ArLightEstimationMode light_estimation_mode); + +/// Stores the currently configured plane finding mode into +/// @c *plane_finding_mode. +void ArConfig_getPlaneFindingMode(const ArSession *session, + const ArConfig *config, + ArPlaneFindingMode *plane_finding_mode); + +/// Sets the plane finding mode that should be used. See +/// ::ArPlaneFindingMode for available options. +void ArConfig_setPlaneFindingMode(const ArSession *session, + ArConfig *config, + ArPlaneFindingMode plane_finding_mode); + +/// Stores the currently configured behavior of @ref ArSession_update() into +/// @c *update_mode. +void ArConfig_getUpdateMode(const ArSession *session, + const ArConfig *config, + ArUpdateMode *update_mode); + +/// Sets the behavior of @ref ArSession_update(). See +/// ::ArUpdateMode for available options. +void ArConfig_setUpdateMode(const ArSession *session, + ArConfig *config, + ArUpdateMode update_mode); + +/// Gets the current cloud anchor mode from the ::ArConfig. +void ArConfig_getCloudAnchorMode(const ArSession *session, + const ArConfig *config, + ArCloudAnchorMode *out_cloud_anchor_mode); + +/// Sets the cloud configuration that should be used. See ::ArCloudAnchorMode +/// for available options. +void ArConfig_setCloudAnchorMode(const ArSession *session, + ArConfig *config, + ArCloudAnchorMode cloud_anchor_mode); + +/// Sets the image database in the session configuration. +/// +/// Any images in the currently active image database that have a +/// TRACKING/PAUSED state will immediately be set to the STOPPED state if a +/// different or null image database is set. +/// +/// This function makes a copy of the image database. +void ArConfig_setAugmentedImageDatabase( + const ArSession *session, + ArConfig *config, + const ArAugmentedImageDatabase *augmented_image_database); + +/// Returns the image database from the session configuration. +/// +/// This function returns a copy of the internally stored image database. +void ArConfig_getAugmentedImageDatabase( + const ArSession *session, + const ArConfig *config, + ArAugmentedImageDatabase *out_augmented_image_database); + +/// @} + +// === ArSession methods === + +/// @addtogroup session +/// @{ + +/// Releases resources used by an ARCore session. +void ArSession_destroy(ArSession *session); + +/// Before release 1.2.0: Checks if the provided configuration is usable on the +/// this device. If this method returns #AR_ERROR_UNSUPPORTED_CONFIGURATION, +/// calls to ArSession_configure(Config) with this configuration will fail. +/// +/// This function now always returns true. See documentation for each +/// configuration entry to know which configuration options & combinations are +/// supported. +/// +/// @param[in] session The ARCore session +/// @param[in] config The configuration to test +/// @return #AR_SUCCESS or: +/// - #AR_ERROR_INVALID_ARGUMENT if any of the arguments are null. +/// @deprecated in release 1.2.0. Please refer to the release notes +/// (<a +/// href="https://github.com/google-ar/arcore-android-sdk/releases/tag/v1.2.0">release +/// notes 1.2.0</a>) +/// +ArStatus ArSession_checkSupported(const ArSession *session, + const ArConfig *config) + AR_DEPRECATED( + "deprecated in release 1.2.0. Please see function documentation"); + +/// Configures the session with the given config. +/// Note: a session is always initially configured with the default config. +/// This should be called if a configuration different than default is needed. +/// +/// @return #AR_SUCCESS or any of: +/// - #AR_ERROR_FATAL +/// - #AR_ERROR_UNSUPPORTED_CONFIGURATION +ArStatus ArSession_configure(ArSession *session, const ArConfig *config); + +/// Starts or resumes the ARCore Session. +/// +/// Typically this should be called from <a +/// href="https://developer.android.com/reference/android/app/Activity.html#onResume()" +/// ><tt>Activity.onResume()</tt></a>. +/// +/// @returns #AR_SUCCESS or any of: +/// - #AR_ERROR_FATAL +/// - #AR_ERROR_CAMERA_PERMISSION_NOT_GRANTED +/// - #AR_ERROR_CAMERA_NOT_AVAILABLE +ArStatus ArSession_resume(ArSession *session); + +/// Pause the current session. This method will stop the camera feed and release +/// resources. The session can be restarted again by calling ArSession_resume(). +/// +/// Typically this should be called from <a +/// href="https://developer.android.com/reference/android/app/Activity.html#onPause()" +/// ><tt>Activity.onPause()</tt></a>. +/// +/// @returns #AR_SUCCESS or any of: +/// - #AR_ERROR_FATAL +ArStatus ArSession_pause(ArSession *session); + +/// Sets the OpenGL texture name (id) that will allow GPU access to the camera +/// image. The provided ID should have been created with @c glGenTextures(). The +/// resulting texture must be bound to the @c GL_TEXTURE_EXTERNAL_OES target for +/// use. Shaders accessing this texture must use a @c samplerExternalOES +/// sampler. See sample code for an example. +void ArSession_setCameraTextureName(ArSession *session, uint32_t texture_id); + +/// Sets the aspect ratio, coordinate scaling, and display rotation. This data +/// is used by UV conversion, projection matrix generation, and hit test logic. +/// +/// Note: this function doesn't fail. If given invalid input, it logs a error +/// and doesn't apply the changes. +/// +/// @param[in] session The ARCore session +/// @param[in] rotation Display rotation specified by @c android.view.Surface +/// constants: @c ROTATION_0, @c ROTATION_90, @c ROTATION_180 and +/// @c ROTATION_270 +/// @param[in] width Width of the view, in pixels +/// @param[in] height Height of the view, in pixels +void ArSession_setDisplayGeometry(ArSession *session, + int32_t rotation, + int32_t width, + int32_t height); + +/// Updates the state of the ARCore system. This includes: receiving a new +/// camera frame, updating the location of the device, updating the location of +/// tracking anchors, updating detected planes, etc. +/// +/// This call may cause off-screen OpenGL activity. Because of this, to avoid +/// unnecessary frame buffer flushes and reloads, this call should not be made +/// in the middle of rendering a frame or offscreen buffer. +/// +/// This call may update the pose of all created anchors and detected planes. +/// The set of updated objects is accessible through +/// ArFrame_getUpdatedTrackables(). +/// +/// @c update() in blocking mode (see ::ArUpdateMode) will wait until a +/// new camera image is available, or until the built-in timeout +/// (currently 66ms) is reached. +/// If the camera image does not arrive by the built-in timeout, then +/// @c update() will return the most recent ::ArFrame object. For some +/// applications it may be important to know if a new frame was actually +/// obtained (for example, to avoid redrawing if the camera did not produce a +/// new frame). To do that, compare the current frame's timestamp, obtained via +/// @c ArFrame_getTimestamp, with the previously recorded frame timestamp. If +/// they are different, this is a new frame. +/// +/// @param[in] session The ARCore session +/// @param[inout] out_frame The Frame object to populate with the updated world +/// state. This frame must have been previously created using +/// ArFrame_create(). The same ArFrame instance may be used when calling +/// this repeatedly. +/// +/// @return #AR_SUCCESS or any of: +/// - #AR_ERROR_FATAL +/// - #AR_ERROR_SESSION_PAUSED +/// - #AR_ERROR_TEXTURE_NOT_SET +/// - #AR_ERROR_MISSING_GL_CONTEXT +/// - #AR_ERROR_CAMERA_NOT_AVAILABLE - camera was removed during runtime. +ArStatus ArSession_update(ArSession *session, ArFrame *out_frame); + +/// Defines a tracked location in the physical world. +/// +/// @return #AR_SUCCESS or any of: +/// - #AR_ERROR_NOT_TRACKING +/// - #AR_ERROR_SESSION_PAUSED +/// - #AR_ERROR_RESOURCE_EXHAUSTED +ArStatus ArSession_acquireNewAnchor(ArSession *session, + const ArPose *pose, + ArAnchor **out_anchor); + +/// Returns all known anchors, including those not currently tracked. Anchors +/// forgotten by ARCore due to a call to ArAnchor_detach() or entering the +/// #AR_TRACKING_STATE_STOPPED state will not be included. +/// +/// @param[in] session The ARCore session +/// @param[inout] out_anchor_list The list to fill. This list must have already +/// been allocated with ArAnchorList_create(). If previously used, the list +/// will first be cleared. +void ArSession_getAllAnchors(const ArSession *session, + ArAnchorList *out_anchor_list); + +/// Returns the list of all known @ref trackable "trackables". This includes +/// ::ArPlane objects if plane detection is enabled, as well as ::ArPoint +/// objects created as a side effect of calls to ArSession_acquireNewAnchor() or +/// ArFrame_hitTest(). +/// +/// @param[in] session The ARCore session +/// @param[in] filter_type The type(s) of trackables to return. See +/// ::ArTrackableType for legal values. +/// @param[inout] out_trackable_list The list to fill. This list must have +/// already been allocated with ArTrackableList_create(). If previously +/// used, the list will first be cleared. +void ArSession_getAllTrackables(const ArSession *session, + ArTrackableType filter_type, + ArTrackableList *out_trackable_list); + +/// This will create a new cloud anchor using pose and other metadata from +/// @c anchor. +/// +/// If the function returns #AR_SUCCESS, the cloud state of @c out_cloud_anchor +/// will be set to #AR_CLOUD_ANCHOR_STATE_TASK_IN_PROGRESS and the initial pose +/// will be set to the pose of @c anchor. However, the new @c out_cloud_anchor +/// is completely independent of @c anchor, and the poses may diverge over time. +/// If the return value of this function is not #AR_SUCCESS, then +/// @c out_cloud_anchor will be set to null. +/// +/// @param[in] session The ARCore session +/// @param[in] anchor The anchor to be hosted +/// @param[inout] out_cloud_anchor The new cloud anchor +/// @return #AR_SUCCESS or any of: +/// - #AR_ERROR_NOT_TRACKING +/// - #AR_ERROR_SESSION_PAUSED +/// - #AR_ERROR_CLOUD_ANCHORS_NOT_CONFIGURED +/// - #AR_ERROR_RESOURCE_EXHAUSTED +/// - #AR_ERROR_ANCHOR_NOT_SUPPORTED_FOR_HOSTING +ArStatus ArSession_hostAndAcquireNewCloudAnchor(ArSession *session, + const ArAnchor *anchor, + ArAnchor **out_cloud_anchor); + +/// This will create a new cloud anchor, and schedule a resolving task to +/// resolve the anchor's pose using the given cloud anchor ID. +/// +/// If this function returns #AR_SUCCESS, the cloud state of @c out_cloud_anchor +/// will be #AR_CLOUD_STATE_TASK_IN_PROGRESS, and its tracking state will be +/// #AR_TRACKING_STATE_PAUSED. This anchor will never start tracking until its +/// pose has been successfully resolved. If the resolving task ends in an error, +/// the tracking state will be set to #AR_TRACKING_STATE_STOPPED. If the return +/// value is not #AR_SUCCESS, then @c out_cloud_anchor will be set to null. +/// +/// @param[in] session The ARCore session +/// @param[in] cloud_anchor_id The cloud ID of the anchor to be resolved +/// @param[inout] out_cloud_anchor The new cloud anchor +/// @return #AR_SUCCESS or any of: +/// - #AR_ERROR_NOT_TRACKING +/// - #AR_ERROR_SESSION_PAUSED +/// - #AR_ERROR_CLOUD_ANCHORS_NOT_CONFIGURED +/// - #AR_ERROR_RESOURCE_EXHAUSTED +ArStatus ArSession_resolveAndAcquireNewCloudAnchor(ArSession *session, + const char *cloud_anchor_id, + ArAnchor **out_cloud_anchor); + +/// @} + +// === ArPose methods === + +/// @addtogroup pose +/// @{ + +/// Allocates and initializes a new pose object. @c pose_raw points to an array +/// of 7 floats, describing the rotation (quaternion) and translation of the +/// pose in the same order as the first 7 elements of the Android +/// @c Sensor.TYPE_POSE_6DOF values documented on <a +/// href="https://developer.android.com/reference/android/hardware/SensorEvent.html#values" +/// >@c SensorEvent.values() </a> +/// +/// The order of the values is: qx, qy, qz, qw, tx, ty, tz. +/// +/// If @c pose_raw is null, initializes with the identity pose. +void ArPose_create(const ArSession *session, + const float *pose_raw, + ArPose **out_pose); + +/// Releases memory used by a pose object. +void ArPose_destroy(ArPose *pose); + +/// Extracts the quaternion rotation and translation from a pose object. +/// @param[in] session The ARCore session +/// @param[in] pose The pose to extract +/// @param[out] out_pose_raw Pointer to an array of 7 floats, to be filled with +/// the quaternion rotation and translation as described in ArPose_create(). +void ArPose_getPoseRaw(const ArSession *session, + const ArPose *pose, + float *out_pose_raw); + +/// Converts a pose into a 4x4 transformation matrix. +/// @param[in] session The ARCore session +/// @param[in] pose The pose to convert +/// @param[out] out_matrix_col_major_4x4 Pointer to an array of 16 floats, to be +/// filled with a column-major homogenous transformation matrix, as used by +/// OpenGL. +void ArPose_getMatrix(const ArSession *session, + const ArPose *pose, + float *out_matrix_col_major_4x4); + +/// @} + +// === ArCamera methods === + +/// @addtogroup camera +/// @{ + +/// Sets @c out_pose to the pose of the user's device in the world coordinate +/// space at the time of capture of the current camera texture. The position and +/// orientation of the pose follow the device's physical camera (they are not +/// affected by display orientation) and uses OpenGL camera conventions (+X +/// right, +Y up, -Z in the direction the camera is looking). +/// +/// Note: This pose is only useful when ArCamera_getTrackingState() returns +/// #AR_TRACKING_STATE_TRACKING and otherwise should not be used. +/// +/// @param[in] session The ARCore session +/// @param[in] camera The session's camera (retrieved from any frame). +/// @param[inout] out_pose An already-allocated ArPose object into which the +/// pose will be stored. +void ArCamera_getPose(const ArSession *session, + const ArCamera *camera, + ArPose *out_pose); + +/// Sets @c out_pose to the pose of the user's device in the world coordinate +/// space at the time of capture of the current camera texture. The position of +/// the pose is located at the device's camera, while the orientation +/// approximately matches the orientation of the display (considering display +/// rotation), using OpenGL camera conventions (+X right, +Y up, -Z in the +/// direction the camera is looking). +/// +/// Note: This pose is only useful when ArCamera_getTrackingState() returns +/// #AR_TRACKING_STATE_TRACKING and otherwise should not be used. +/// +/// See also: ArCamera_getViewMatrix() +/// +/// @param[in] session The ARCore session +/// @param[in] camera The session's camera (retrieved from any frame). +/// @param[inout] out_pose An already-allocated ArPose object into which the +/// pose will be stored. +void ArCamera_getDisplayOrientedPose(const ArSession *session, + const ArCamera *camera, + ArPose *out_pose); + +/// Returns the view matrix for the camera for this frame. This matrix performs +/// the inverse transfrom as the pose provided by +/// ArCamera_getDisplayOrientedPose(). +/// +/// @param[in] session The ARCore session +/// @param[in] camera The session's camera. +/// @param[inout] out_col_major_4x4 Pointer to an array of 16 floats, to be +/// filled with a column-major homogenous transformation matrix, as used by +/// OpenGL. +void ArCamera_getViewMatrix(const ArSession *session, + const ArCamera *camera, + float *out_col_major_4x4); + +/// Gets the current state of the pose of this camera. If this state is anything +/// other than #AR_TRACKING_STATE_TRACKING the Camera's pose should not be +/// considered useful. +void ArCamera_getTrackingState(const ArSession *session, + const ArCamera *camera, + ArTrackingState *out_tracking_state); + +/// Computes a projection matrix for rendering virtual content on top of the +/// camera image. Note that the projection matrix reflects the current display +/// geometry and display rotation. +/// +/// @param[in] session The ARCore session +/// @param[in] camera The session's camera. +/// @param[in] near Specifies the near clip plane, in meters +/// @param[in] far Specifies the far clip plane, in meters +/// @param[inout] dest_col_major_4x4 Pointer to an array of 16 floats, to +/// be filled with a column-major homogenous transformation matrix, as used +/// by OpenGL. +void ArCamera_getProjectionMatrix(const ArSession *session, + const ArCamera *camera, + float near, + float far, + float *dest_col_major_4x4); + +/// Releases a reference to the camera. This must match a call to +/// ArFrame_acquireCamera(). +/// +/// This method may safely be called with @c nullptr - it will do nothing. +void ArCamera_release(ArCamera *camera); + +/// @} + +// === ArFrame methods === + +/// @addtogroup frame +/// @{ + +/// Allocates a new ArFrame object, storing the pointer into @c *out_frame. +/// +/// Note: the same ArFrame can be used repeatedly when calling ArSession_update. +void ArFrame_create(const ArSession *session, ArFrame **out_frame); + +/// Releases an ArFrame and any references it holds. +void ArFrame_destroy(ArFrame *frame); + +/// Checks if the display rotation or viewport geometry changed since the +/// previous call to ArSession_update(). The application should re-query +/// ArCamera_getProjectionMatrix() and ArFrame_transformDisplayUvCoords() +/// whenever this emits non-zero. +void ArFrame_getDisplayGeometryChanged(const ArSession *session, + const ArFrame *frame, + int32_t *out_geometry_changed); + +/// Returns the timestamp in nanoseconds when this image was captured. This can +/// be used to detect dropped frames or measure the camera frame rate. The time +/// base of this value is specifically <b>not</b> defined, but it is likely +/// similar to <tt>clock_gettime(CLOCK_BOOTTIME)</tt>. +void ArFrame_getTimestamp(const ArSession *session, + const ArFrame *frame, + int64_t *out_timestamp_ns); + +/// Transform the given texture coordinates to correctly show the background +/// image. This will account for the display rotation, and any additional +/// required adjustment. For performance, this function should be called only if +/// ArFrame_hasDisplayGeometryChanged() emits true. +/// +/// @param[in] session The ARCore session +/// @param[in] frame The current frame. +/// @param[in] num_elements The number of floats to transform. Must be +/// a multiple of 2. @c uvs_in and @c uvs_out must point to arrays of at +/// least this many floats. +/// @param[in] uvs_in Input UV coordinates in normalized screen space. +/// @param[inout] uvs_out Output UV coordinates in texture coordinates. +void ArFrame_transformDisplayUvCoords(const ArSession *session, + const ArFrame *frame, + int32_t num_elements, + const float *uvs_in, + float *uvs_out); + +/// Performs a ray cast from the user's device in the direction of the given +/// location in the camera view. Intersections with detected scene geometry are +/// returned, sorted by distance from the device; the nearest intersection is +/// returned first. +/// +/// Note: Significant geometric leeway is given when returning hit results. For +/// example, a plane hit may be generated if the ray came close, but did not +/// actually hit within the plane extents or plane bounds +/// (ArPlane_isPoseInExtents() and ArPlane_isPoseInPolygon() can be used to +/// determine these cases). A point (point cloud) hit is generated when a point +/// is roughly within one finger-width of the provided screen coordinates. +/// +/// The resulting list is ordered by distance, with the nearest hit first +/// +/// Note: If not tracking, the hit_result_list will be empty. <br> +/// Note: If called on an old frame (not the latest produced by +/// ArSession_update() the hit_result_list will be empty). +/// +/// @param[in] session The ARCore session. +/// @param[in] frame The current frame. +/// @param[in] pixel_x Logical X position within the view, as from an +/// Android UI event. +/// @param[in] pixel_y Logical X position within the view. +/// @param[inout] hit_result_list The list to fill. This list must have been +/// previously allocated using ArHitResultList_create(). If the list has +/// been previously used, it will first be cleared. +void ArFrame_hitTest(const ArSession *session, + const ArFrame *frame, + float pixel_x, + float pixel_y, + ArHitResultList *hit_result_list); + +/// Gets the current ambient light estimate, if light estimation was enabled. +/// +/// @param[in] session The ARCore session. +/// @param[in] frame The current frame. +/// @param[inout] out_light_estimate The light estimate to fill. This object +/// must have been previously created with ArLightEstimate_create(). +void ArFrame_getLightEstimate(const ArSession *session, + const ArFrame *frame, + ArLightEstimate *out_light_estimate); + +/// Acquires the current set of estimated 3d points attached to real-world +/// geometry. A matching call to PointCloud_release() must be made when the +/// application is done accessing the point cloud. +/// +/// Note: This information is for visualization and debugging purposes only. Its +/// characteristics and format are subject to change in subsequent versions of +/// the API. +/// +/// @param[in] session The ARCore session. +/// @param[in] frame The current frame. +/// @param[out] out_point_cloud Pointer to an @c ArPointCloud* receive the +/// address of the point cloud. +/// @return #AR_SUCCESS or any of: +/// - #AR_ERROR_DEADLINE_EXCEEDED if @c frame is not the latest frame from +/// by ArSession_update(). +/// - #AR_ERROR_RESOURCE_EXHAUSTED if too many point clouds are currently held. +ArStatus ArFrame_acquirePointCloud(const ArSession *session, + const ArFrame *frame, + ArPointCloud **out_point_cloud); + +/// Returns the camera object for the session. Note that this Camera instance is +/// long-lived so the same instance is returned regardless of the frame object +/// this method was called on. +void ArFrame_acquireCamera(const ArSession *session, + const ArFrame *frame, + ArCamera **out_camera); + +/// Gets the camera metadata for the current camera image. +/// +/// @return #AR_SUCCESS or any of: +/// - #AR_ERROR_DEADLINE_EXCEEDED if @c frame is not the latest frame from +/// by ArSession_update(). +/// - #AR_ERROR_RESOURCE_EXHAUSTED if too many metadata objects are currently +/// held. +/// - #AR_ERROR_NOT_YET_AVAILABLE if the camera failed to produce metadata for +/// the given frame. Note: this will commonly happen for few frames right +/// after @c ArSession_resume() due to the camera stack bringup. +ArStatus ArFrame_acquireImageMetadata(const ArSession *session, + const ArFrame *frame, + ArImageMetadata **out_metadata); + +/// Gets the set of anchors that were changed by the ArSession_update() that +/// produced this Frame. +/// +/// @param[in] session The ARCore session +/// @param[in] frame The current frame. +/// @param[inout] out_anchor_list The list to fill. This list must have +/// already been allocated with ArAnchorList_create(). If previously +/// used, the list will first be cleared. +void ArFrame_getUpdatedAnchors(const ArSession *session, + const ArFrame *frame, + ArAnchorList *out_anchor_list); + +/// Gets the set of trackables of a particular type that were changed by the +/// ArSession_update() call that produced this Frame. +/// +/// @param[in] session The ARCore session +/// @param[in] frame The current frame. +/// @param[in] filter_type The type(s) of trackables to return. See +/// ::ArTrackableType for legal values. +/// @param[inout] out_trackable_list The list to fill. This list must have +/// already been allocated with ArTrackableList_create(). If previously +/// used, the list will first be cleared. +void ArFrame_getUpdatedTrackables(const ArSession *session, + const ArFrame *frame, + ArTrackableType filter_type, + ArTrackableList *out_trackable_list); +/// @} + +// === ArPointCloud methods === + +/// @addtogroup pointcloud +/// @{ + +/// Retrieves the number of points in the point cloud. +/// +void ArPointCloud_getNumberOfPoints(const ArSession *session, + const ArPointCloud *point_cloud, + int32_t *out_number_of_points); + +/// Retrieves a pointer to the point cloud data. +/// +/// Each point is represented by four consecutive values in the array; first the +/// X, Y, Z position coordinates, followed by a confidence value. This is the +/// same format as described in <a +/// href="https://developer.android.com/reference/android/graphics/ImageFormat.html#DEPTH_POINT_CLOUD" +/// >DEPTH_POINT_CLOUD</a>. +/// +/// The pointer returned by this function is valid until ArPointCloud_release() +/// is called. The application must copy the data if they wish to retain it for +/// longer. The points are in world coordinates consistent with the frame it was +/// obtained from. If the number of points is zero, then the value of +/// @c *out_point_cloud_data should is undefined. +void ArPointCloud_getData(const ArSession *session, + const ArPointCloud *point_cloud, + const float **out_point_cloud_data); + +/// Returns the timestamp in nanoseconds when this point cloud was observed. +/// This timestamp uses the same time base as ArFrame_getTimestamp(). +void ArPointCloud_getTimestamp(const ArSession *session, + const ArPointCloud *point_cloud, + int64_t *out_timestamp_ns); + +/// Releases a reference to the point cloud. This must match a call to +/// ArFrame_acquirePointCloud(). +/// +/// This method may safely be called with @c nullptr - it will do nothing. +void ArPointCloud_release(ArPointCloud *point_cloud); + +/// @} + +// === Image Metadata methods === + +/// @addtogroup image +/// @{ + +/// Retrieves the capture metadata for the current camera image. +/// +/// @c ACameraMetadata is a struct in Android NDK. Include NdkCameraMetadata.h +/// to use this type. +/// +/// Note: that the ACameraMetadata returned from this function will be invalid +/// after its ArImageMetadata object is released. +void ArImageMetadata_getNdkCameraMetadata( + const ArSession *session, + const ArImageMetadata *image_metadata, + const ACameraMetadata **out_ndk_metadata); + +/// Releases a reference to the metadata. This must match a call to +/// ArFrame_acquireImageMetadata(). +/// +/// This method may safely be called with @c nullptr - it will do nothing. +void ArImageMetadata_release(ArImageMetadata *metadata); + +// === CPU Image Access types and methods === +/// Gets the image of the tracking camera relative to the input session and +/// frame. +/// Return values: +/// @returns #AR_SUCCESS or any of: +/// - #AR_ERROR_INVALID_ARGUMENT - one more input arguments are invalid. +/// - #AR_ERROR_DEADLINE_EXCEEDED - the input frame is not the current frame. +/// - #AR_ERROR_RESOURCE_EXHAUSTED - the caller app has exceeded maximum number +/// of images that it can hold without releasing. +/// - #AR_ERROR_NOT_YET_AVAILABLE - image with the timestamp of the input frame +/// was not found within a bounded amount of time, or the camera failed to +/// produce the image +ArStatus ArFrame_acquireCameraImage(ArSession *session, + ArFrame *frame, + ArImage **out_image); + +/// Converts an ArImage object to an Android NDK AImage object. +void ArImage_getNdkImage(const ArImage *image, const AImage **out_ndk_image); + +/// Releases an instance of ArImage returned by ArFrame_acquireCameraImage(). +void ArImage_release(ArImage *image); +/// @} + +// === ArLightEstimate methods === + +/// @addtogroup light +/// @{ + +/// Allocates a light estimate object. +void ArLightEstimate_create(const ArSession *session, + ArLightEstimate **out_light_estimate); + +/// Releases the provided light estimate object. +void ArLightEstimate_destroy(ArLightEstimate *light_estimate); + +/// Retrieves the validity state of a light estimate. If the resulting value of +/// @c *out_light_estimate_state is not #AR_LIGHT_ESTIMATE_STATE_VALID, the +/// estimate should not be used for rendering. +void ArLightEstimate_getState(const ArSession *session, + const ArLightEstimate *light_estimate, + ArLightEstimateState *out_light_estimate_state); + +/// Retrieves the pixel intensity, in gamma space, of the current camera view. +/// Values are in the range (0.0, 1.0), with zero being black and one being +/// white. +/// If rendering in gamma space, divide this value by 0.466, which is middle +/// gray in gamma space, and multiply against the final calculated color after +/// rendering. +/// If rendering in linear space, first convert this value to linear space by +/// rising to the power 2.2. Normalize the result by dividing it by 0.18 which +/// is middle gray in linear space. Then multiply by the final calculated color +/// after rendering. +void ArLightEstimate_getPixelIntensity(const ArSession *session, + const ArLightEstimate *light_estimate, + float *out_pixel_intensity); + +/// Gets the color correction values that are uploaded to the fragment shader. +/// Use the RGB scale factors (components 0-2) to match the color of the light +/// in the scene. Use the pixel intensity (component 3) to match the intensity +/// of the light in the scene. +/// +/// `out_color_correction_4` components are: +/// - `[0]` Red channel scale factor. +/// - `[1]` Green channel scale factor. +/// - `[2]` Blue channel scale factor. +/// - `[3]` Pixel intensity. This is the same value as the one return from +/// ArLightEstimate_getPixelIntensity(). +/// +/// The RGB scale factors can be used independently from the pixel intensity +/// value. They are put together for the convenience of only having to upload +/// one float4 to the fragment shader. +/// +/// The RGB scale factors are not intended to brighten nor dim the scene. They +/// are only to shift the color of the virtual object towards the color of the +/// light; not intensity of the light. The pixel intensity is used to match the +/// intensity of the light in the scene. +/// +/// Color correction values are reported in gamma space. +/// If rendering in gamma space, component-wise multiply them against the final +/// calculated color after rendering. +/// If rendering in linear space, first convert the values to linear space by +/// rising to the power 2.2. Then component-wise multiply against the final +/// calculated color after rendering. +void ArLightEstimate_getColorCorrection(const ArSession *session, + const ArLightEstimate *light_estimate, + float *out_color_correction_4); + +/// @} + +// === ArAnchorList methods === + +/// @addtogroup anchor +/// @{ + +/// Creates an anchor list object. +void ArAnchorList_create(const ArSession *session, + ArAnchorList **out_anchor_list); + +/// Releases the memory used by an anchor list object, along with all the anchor +/// references it holds. +void ArAnchorList_destroy(ArAnchorList *anchor_list); + +/// Retrieves the number of anchors in this list. +void ArAnchorList_getSize(const ArSession *session, + const ArAnchorList *anchor_list, + int32_t *out_size); + +/// Acquires a reference to an indexed entry in the list. This call must +/// eventually be matched with a call to ArAnchor_release(). +void ArAnchorList_acquireItem(const ArSession *session, + const ArAnchorList *anchor_list, + int32_t index, + ArAnchor **out_anchor); + +// === ArAnchor methods === + +/// Retrieves the pose of the anchor in the world coordinate space. This pose +/// produced by this call may change each time ArSession_update() is called. +/// This pose should only be used for rendering if ArAnchor_getTrackingState() +/// returns #AR_TRACKING_STATE_TRACKING. +/// +/// @param[in] session The ARCore session. +/// @param[in] anchor The anchor to retrieve the pose of. +/// @param[inout] out_pose An already-allocated ArPose object into which the +/// pose will be stored. +void ArAnchor_getPose(const ArSession *session, + const ArAnchor *anchor, + ArPose *out_pose); + +/// Retrieves the current state of the pose of this anchor. +void ArAnchor_getTrackingState(const ArSession *session, + const ArAnchor *anchor, + ArTrackingState *out_tracking_state); + +/// Tells ARCore to stop tracking and forget this anchor. This call does not +/// release the reference to the anchor - that must be done separately using +/// ArAnchor_release(). +void ArAnchor_detach(ArSession *session, ArAnchor *anchor); + +/// Releases a reference to an anchor. This does not mean that the anchor will +/// stop tracking, as it will be obtainable from e.g. ArSession_getAllAnchors() +/// if any other references exist. +/// +/// This method may safely be called with @c nullptr - it will do nothing. +void ArAnchor_release(ArAnchor *anchor); + +/// Acquires the cloud anchor ID of the anchor. The ID acquired is an ASCII +/// null-terminated string. The acquired ID must be released after use by the +/// @c ArString_release function. For anchors with cloud state +/// #AR_CLOUD_ANCHOR_STATE_NONE or #AR_CLOUD_ANCHOR_STATE_TASK_IN_PROGRESS, this +/// will always be an empty string. +/// +/// @param[in] session The ARCore session. +/// @param[in] anchor The anchor to retrieve the cloud ID of. +/// @param[inout] out_cloud_anchor_id A pointer to the acquired ID string. +void ArAnchor_acquireCloudAnchorId(ArSession *session, + ArAnchor *anchor, + char **out_cloud_anchor_id); + +/// Gets the current cloud anchor state of the anchor. This state is guaranteed +/// not to change until update() is called. +/// +/// @param[in] session The ARCore session. +/// @param[in] anchor The anchor to retrieve the cloud state of. +/// @param[inout] out_state The current cloud state of the anchor. +void ArAnchor_getCloudAnchorState(const ArSession *session, + const ArAnchor *anchor, + ArCloudAnchorState *out_state); + +/// @} + +// === ArTrackableList methods === + +/// @addtogroup trackable +/// @{ + +/// Creates a trackable list object. +void ArTrackableList_create(const ArSession *session, + ArTrackableList **out_trackable_list); + +/// Releases the memory used by a trackable list object, along with all the +/// anchor references it holds. +void ArTrackableList_destroy(ArTrackableList *trackable_list); + +/// Retrieves the number of trackables in this list. +void ArTrackableList_getSize(const ArSession *session, + const ArTrackableList *trackable_list, + int32_t *out_size); + +/// Acquires a reference to an indexed entry in the list. This call must +/// eventually be matched with a call to ArTrackable_release(). +void ArTrackableList_acquireItem(const ArSession *session, + const ArTrackableList *trackable_list, + int32_t index, + ArTrackable **out_trackable); + +// === ArTrackable methods === + +/// Releases a reference to a trackable. This does not mean that the trackable +/// will necessarily stop tracking. The same trackable may still be included in +/// from other calls, for example ArSession_getAllTrackables(). +/// +/// This method may safely be called with @c nullptr - it will do nothing. +void ArTrackable_release(ArTrackable *trackable); + +/// Retrieves the type of the trackable. See ::ArTrackableType for valid types. +void ArTrackable_getType(const ArSession *session, + const ArTrackable *trackable, + ArTrackableType *out_trackable_type); + +/// Retrieves the current state of ARCore's knowledge of the pose of this +/// trackable. +void ArTrackable_getTrackingState(const ArSession *session, + const ArTrackable *trackable, + ArTrackingState *out_tracking_state); + +/// Creates an Anchor at the given pose in the world coordinate space, attached +/// to this Trackable, and acquires a reference to it. The type of Trackable +/// will determine the semantics of attachment and how the Anchor's pose will be +/// updated to maintain this relationship. Note that the relative offset between +/// the pose of multiple Anchors attached to a Trackable may adjust slightly +/// over time as ARCore updates its model of the world. +/// +/// @return #AR_SUCCESS or any of: +/// - #AR_ERROR_NOT_TRACKING if the trackable's tracking state was not +/// #AR_TRACKING_STATE_TRACKING +/// - #AR_ERROR_SESSION_PAUSED if the session was paused +/// - #AR_ERROR_RESOURCE_EXHAUSTED if too many anchors exist +ArStatus ArTrackable_acquireNewAnchor(ArSession *session, + ArTrackable *trackable, + ArPose *pose, + ArAnchor **out_anchor); + +/// Gets the set of anchors attached to this trackable. +/// +/// @param[in] session The ARCore session +/// @param[in] trackable The trackable to query the anchors of. +/// @param[inout] out_anchor_list The list to fill. This list must have +/// already been allocated with ArAnchorList_create(). If previously +/// used, the list will first be cleared. +void ArTrackable_getAnchors(const ArSession *session, + const ArTrackable *trackable, + ArAnchorList *out_anchor_list); + +/// @} + +// === ArPlane methods === + +/// @addtogroup plane +/// @{ + +/// Acquires a reference to the plane subsuming this plane. +/// +/// Two or more planes may be automatically merged into a single parent plane, +/// resulting in this method acquiring the parent plane when called with each +/// child plane. A subsumed plane becomes identical to the parent plane, and +/// will continue behaving as if it were independently tracked, for example +/// being included in the output of ArFrame_getUpdatedTrackables(). +/// +/// In cases where a subsuming plane is itself subsumed, this function +/// will always return the topmost non-subsumed plane. +/// +/// Note: this function will set @c *out_subsumed_by to NULL if the plane is not +/// subsumed. +void ArPlane_acquireSubsumedBy(const ArSession *session, + const ArPlane *plane, + ArPlane **out_subsumed_by); + +/// Retrieves the type (orientation) of the plane. See ::ArPlaneType. +void ArPlane_getType(const ArSession *session, + const ArPlane *plane, + ArPlaneType *out_plane_type); + +/// Returns the pose of the center of the detected plane. The pose's transformed +/// +Y axis will be point normal out of the plane, with the +X and +Z axes +/// orienting the extents of the bounding rectangle. +/// +/// @param[in] session The ARCore session. +/// @param[in] plane The plane for which to retrieve center pose. +/// @param[inout] out_pose An already-allocated ArPose object into which the +/// pose will be stored. +void ArPlane_getCenterPose(const ArSession *session, + const ArPlane *plane, + ArPose *out_pose); + +/// Retrieves the length of this plane's bounding rectangle measured along the +/// local X-axis of the coordinate space defined by the output of +/// ArPlane_getCenterPose(). +void ArPlane_getExtentX(const ArSession *session, + const ArPlane *plane, + float *out_extent_x); + +/// Retrieves the length of this plane's bounding rectangle measured along the +/// local Z-axis of the coordinate space defined by the output of +/// ArPlane_getCenterPose(). +void ArPlane_getExtentZ(const ArSession *session, + const ArPlane *plane, + float *out_extent_z); + +/// Retrieves the number of elements (not vertices) in the boundary polygon. +/// The number of vertices is 1/2 this size. +void ArPlane_getPolygonSize(const ArSession *session, + const ArPlane *plane, + int32_t *out_polygon_size); + +/// Returns the 2D vertices of a convex polygon approximating the detected +/// plane, in the form <tt>[x1, z1, x2, z2, ...]</tt>. These X-Z values are in +/// the plane's local x-z plane (y=0) and must be transformed by the pose +/// (ArPlane_getCenterPose()) to get the boundary in world coordinates. +/// +/// @param[in] session The ARCore session. +/// @param[in] plane The plane to retrieve the polygon from. +/// @param[inout] out_polygon_xz A pointer to an array of floats. The length of +/// this array must be at least that reported by ArPlane_getPolygonSize(). +void ArPlane_getPolygon(const ArSession *session, + const ArPlane *plane, + float *out_polygon_xz); + +/// Sets @c *out_pose_in_extents to non-zero if the given pose (usually obtained +/// from a HitResult) is in the plane's rectangular extents. +void ArPlane_isPoseInExtents(const ArSession *session, + const ArPlane *plane, + const ArPose *pose, + int32_t *out_pose_in_extents); + +/// Sets @c *out_pose_in_extents to non-zero if the given pose (usually obtained +/// from a HitResult) is in the plane's polygon. +void ArPlane_isPoseInPolygon(const ArSession *session, + const ArPlane *plane, + const ArPose *pose, + int32_t *out_pose_in_polygon); + +/// @} + +// === ArPoint methods === + +/// @addtogroup point +/// @{ + +/// Returns the pose of the point. +/// If ArPoint_getOrientationMode() returns ESTIMATED_SURFACE_NORMAL, the +/// orientation will follow the behavior described in ArHitResult_getHitPose(). +/// If ArPoint_getOrientationMode() returns INITIALIZED_TO_IDENTITY, then +/// returns an orientation that is identity or close to identity. +/// @param[in] session The ARCore session. +/// @param[in] point The point to retrieve the pose of. +/// @param[inout] out_pose An already-allocated ArPose object into which the +/// pose will be stored. +void ArPoint_getPose(const ArSession *session, + const ArPoint *point, + ArPose *out_pose); + +/// Returns the OrientationMode of the point. For @c Point objects created by +/// ArFrame_hitTest(). +/// If OrientationMode is ESTIMATED_SURFACE_NORMAL, then normal of the surface +/// centered around the ArPoint was estimated succesfully. +/// +/// @param[in] session The ARCore session. +/// @param[in] point The point to retrieve the pose of. +/// @param[inout] out_orientation_mode OrientationMode output result for the +/// the point. +void ArPoint_getOrientationMode(const ArSession *session, + const ArPoint *point, + ArPointOrientationMode *out_orientation_mode); + +/// @} + +// === ArAugmentedImage methods === + +/// @addtogroup augmented_image +/// @{ + +/// Returns the pose of the center of the detected image. The pose's +/// transformed +Y axis will be point normal out of the image. +/// +/// If the tracking state is PAUSED/STOPPED, this returns the pose when the +/// image state was last TRACKING, or the identity pose if the image state has +/// never been TRACKING. +void ArAugmentedImage_getCenterPose(const ArSession *session, + const ArAugmentedImage *augmented_image, + ArPose *out_pose); + +/// Retrieves the estimated width, in metres, of the corresponding physical +/// image, as measured along the local X-axis of the coordinate space with +/// origin and axes as defined by ArAugmentedImage_getCenterPose(). +/// +/// ARCore will attempt to estimate the physical image's width and continuously +/// update this estimate based on its understanding of the world. If the +/// optional physical size is specified in the image database, this estimation +/// process will happen more quickly. However, the estimated size may be +/// different from the originally specified size. +/// +/// If the tracking state is PAUSED/STOPPED, this returns the estimated width +/// when the image state was last TRACKING. If the image state has never been +/// TRACKING, this returns 0, even the image has a specified physical size in +/// the image database. +void ArAugmentedImage_getExtentX(const ArSession *session, + const ArAugmentedImage *augmented_image, + float *out_extent_x); + +/// Retrieves the estimated height, in metres, of the corresponding physical +/// image, as measured along the local Z-axis of the coordinate space with +/// origin and axes as defined by ArAugmentedImage_getCenterPose(). +/// +/// ARCore will attempt to estimate the physical image's height and continuously +/// update this estimate based on its understanding of the world. If an optional +/// physical size is specified in the image database, this estimation process +/// will happen more quickly. However, the estimated size may be different from +/// the originally specified size. +/// +/// If the tracking state is PAUSED/STOPPED, this returns the estimated height +/// when the image state was last TRACKING. If the image state has never been +/// TRACKING, this returns 0, even the image has a specified physical size in +/// the image database. +void ArAugmentedImage_getExtentZ(const ArSession *session, + const ArAugmentedImage *augmented_image, + float *out_extent_z); + +/// Returns the zero-based positional index of this image from its originating +/// image database. +/// +/// This index serves as the unique identifier for the image in the database. +void ArAugmentedImage_getIndex(const ArSession *session, + const ArAugmentedImage *augmented_image, + int32_t *out_index); + +/// Returns the name of this image. +/// +/// The image name is not guaranteed to be unique. +/// +/// This function will allocate memory for the name string, and set +/// *out_augmented_image_name to point to that string. The caller must release +/// the string using ArString_release when the string is no longer needed. +void ArAugmentedImage_acquireName(const ArSession *session, + const ArAugmentedImage *augmented_image, + char **out_augmented_image_name); + +/// @} + +// === ArAugmentedImageDatabase methods === + +/// @addtogroup augmented_image_database +/// @{ + +/// Creates a new empty image database. +void ArAugmentedImageDatabase_create( + const ArSession *session, + ArAugmentedImageDatabase **out_augmented_image_database); + +/// Creates a new image database from a byte array. The contents of the byte +/// array must have been generated by the command-line database generation tool +/// provided in the SDK, or at runtime from ArAugmentedImageDatabase_serialize. +/// +/// Note: this function takes about 10-20ms for a 5MB byte array. Run this in a +/// background thread if this affects your application. +/// +/// @return #AR_SUCCESS or any of: +/// - #AR_ERROR_DATA_INVALID_FORMAT - the bytes are in an invalid format. +/// - #AR_ERROR_DATA_UNSUPPORTED_VERSION - the database is not supported by +/// this version of the SDK. +ArStatus ArAugmentedImageDatabase_deserialize( + const ArSession *session, + const uint8_t *database_raw_bytes, + int64_t database_raw_bytes_size, + ArAugmentedImageDatabase **out_augmented_image_database); + +/// Serializes an image database to a byte array. +/// +/// This function will allocate memory for the serialized raw byte array, and +/// set *out_image_database_raw_bytes to point to that byte array. The caller is +/// expected to release the byte array using ArByteArray_release when the byte +/// array is no longer needed. +void ArAugmentedImageDatabase_serialize( + const ArSession *session, + const ArAugmentedImageDatabase *augmented_image_database, + uint8_t **out_image_database_raw_bytes, + int64_t *out_image_database_raw_bytes_size); + +/// Adds a single named image of unknown physical size to an image database, +/// from an array of grayscale pixel values. Returns the zero-based positional +/// index of the image within the image database. +/// +/// If the physical size of the image is known, use +/// ArAugmentedImageDatabase_addImageWithPhysicalSize instead, to improve image +/// detection time. +/// +/// For images added via ArAugmentedImageDatabase_addImage, ARCore estimates the +/// physical image's size and pose at runtime when the physical image is visible +/// and is being tracked. This extra estimation step will require the user to +/// move their device to view the physical image from different viewpoints +/// before the size and pose of the physical image can be estimated. +/// +/// This function takes time to perform non-trivial image processing (20ms - +/// 30ms), and should be run on a background thread. +/// +/// The image name is expected to be a null-terminated string in UTF-8 format. +/// +/// @return #AR_SUCCESS or any of: +/// - #AR_ERROR_IMAGE_INSUFFICIENT_QUALITY - image quality is insufficient, e.g. +/// because of lack of features in the image. +ArStatus ArAugmentedImageDatabase_addImage( + const ArSession *session, + ArAugmentedImageDatabase *augmented_image_database, + const char *image_name, + const uint8_t *image_grayscale_pixels, + int32_t image_width_in_pixels, + int32_t image_height_in_pixels, + int32_t image_stride_in_pixels, + int32_t *out_index); + +/// Adds a single named image to an image database, from an array of grayscale +/// pixel values, along with a positive physical width in meters for this image. +/// Returns the zero-based positional index of the image within the image +/// database. +/// +/// If the physical size of the image is not known, use +/// ArAugmentedImageDatabase_addImage instead, at the expense of an increased +/// image detection time. +/// +/// For images added via ArAugmentedImageDatabase_addImageWithPhysicalSize, +/// ARCore can estimate the pose of the physical image at runtime as soon as +/// ARCore detects the physical image, without requiring the user to move the +/// device to view the physical image from different viewpoints. Note that +/// ARCore will refine the estimated size and pose of the physical image as it +/// is viewed from different viewpoints. +/// +/// This function takes time to perform non-trivial image processing (20ms - +/// 30ms), and should be run on a background thread. +/// +/// The image name is expected to be a null-terminated string in UTF-8 format. +/// +/// @return #AR_SUCCESS or any of: +/// - #AR_ERROR_IMAGE_INSUFFICIENT_QUALITY - image quality is insufficient, e.g. +/// because of lack of features in the image. +/// - #AR_ERROR_INVALID_ARGUMENT - image_width_in_meters is <= 0. +ArStatus ArAugmentedImageDatabase_addImageWithPhysicalSize( + const ArSession *session, + ArAugmentedImageDatabase *augmented_image_database, + const char *image_name, + const uint8_t *image_grayscale_pixels, + int32_t image_width_in_pixels, + int32_t image_height_in_pixels, + int32_t image_stride_in_pixels, + float image_width_in_meters, + int32_t *out_index); + +/// Returns the number of images in the image database. +void ArAugmentedImageDatabase_getNumImages( + const ArSession *session, + const ArAugmentedImageDatabase *augmented_image_database, + int32_t *out_num_images); + +/// Releases memory used by an image database. +void ArAugmentedImageDatabase_destroy( + ArAugmentedImageDatabase *augmented_image_database); + +/// @} + +// === ArHitResultList methods === + +/// @addtogroup hit +/// @{ + +/// Creates a hit result list object. +void ArHitResultList_create(const ArSession *session, + ArHitResultList **out_hit_result_list); + +/// Releases the memory used by a hit result list object, along with all the +/// trackable references it holds. +void ArHitResultList_destroy(ArHitResultList *hit_result_list); + +/// Retrieves the number of hit results in this list. +void ArHitResultList_getSize(const ArSession *session, + const ArHitResultList *hit_result_list, + int32_t *out_size); + +/// Copies an indexed entry in the list. This acquires a reference to any +/// trackable referenced by the item, and releases any reference currently held +/// by the provided result object. +/// +/// @param[in] session The ARCore session. +/// @param[in] hit_result_list The list from which to copy an item. +/// @param[in] index Index of the entry to copy. +/// @param[inout] out_hit_result An already-allocated ArHitResult object into +/// which the result will be copied. +void ArHitResultList_getItem(const ArSession *session, + const ArHitResultList *hit_result_list, + int32_t index, + ArHitResult *out_hit_result); + +// === ArHitResult methods === + +/// Allocates an empty hit result object. +void ArHitResult_create(const ArSession *session, ArHitResult **out_hit_result); + +/// Releases the memory used by a hit result object, along with any +/// trackable reference it holds. +void ArHitResult_destroy(ArHitResult *hit_result); + +/// Returns the distance from the camera to the hit location, in meters. +void ArHitResult_getDistance(const ArSession *session, + const ArHitResult *hit_result, + float *out_distance); + +/// Returns the pose of the intersection between a ray and detected real-world +/// geometry. The position is the location in space where the ray intersected +/// the geometry. The orientation is a best effort to face the user's device, +/// and its exact definition differs depending on the Trackable that was hit. +/// +/// ::ArPlane : X+ is perpendicular to the cast ray and parallel to the plane, +/// Y+ points along the plane normal (up, for #AR_PLANE_HORIZONTAL_UPWARD_FACING +/// planes), and Z+ is parallel to the plane, pointing roughly toward the +/// user's device. +/// +/// ::ArPoint : +/// Attempt to estimate the normal of the surface centered around the hit test. +/// Surface normal estimation is most likely to succeed on textured surfaces +/// and with camera motion. +/// If ArPoint_getOrientationMode() returns ESTIMATED_SURFACE_NORMAL, +/// then X+ is perpendicular to the cast ray and parallel to the physical +/// surface centered around the hit test, Y+ points along the estimated surface +/// normal, and Z+ points roughly toward the user's device. If +/// ArPoint_getOrientationMode() returns INITIALIZED_TO_IDENTITY, then X+ is +/// perpendicular to the cast ray and points right from the perspective of the +/// user's device, Y+ points up, and Z+ points roughly toward the user's device. +/// +/// If you wish to retain the location of this pose beyond the duration of a +/// single frame, create an anchor using ArHitResult_acquireNewAnchor() to save +/// the pose in a physically consistent way. +/// +/// @param[in] session The ARCore session. +/// @param[in] hit_result The hit result to retrieve the pose of. +/// @param[inout] out_pose An already-allocated ArPose object into which the +/// pose will be stored. +void ArHitResult_getHitPose(const ArSession *session, + const ArHitResult *hit_result, + ArPose *out_pose); + +/// Acquires reference to the hit trackable. This call must be paired with a +/// call to ArTrackable_release(). +void ArHitResult_acquireTrackable(const ArSession *session, + const ArHitResult *hit_result, + ArTrackable **out_trackable); + +/// Creates a new anchor at the hit location. See ArHitResult_getHitPose() for +/// details. This is equivalent to creating an anchor on the hit trackable at +/// the hit pose. +/// +/// @return #AR_SUCCESS or any of: +/// - #AR_ERROR_NOT_TRACKING +/// - #AR_ERROR_SESSION_PAUSED +/// - #AR_ERROR_RESOURCE_EXHAUSTED +/// - #AR_ERROR_DEADLINE_EXCEEDED - hit result must be used before the next call +/// to update(). +ArStatus ArHitResult_acquireNewAnchor(ArSession *session, + ArHitResult *hit_result, + ArAnchor **out_anchor); + +/// @} + +// Utility methods for releasing data. + +/// Releases a string acquired using an ARCore API function. +/// +/// @param[in] str The string to be released. +void ArString_release(char *str); + +/// Releases a byte array created using an ARCore API function. +void ArByteArray_release(uint8_t *byte_array); + +#ifdef __cplusplus +} +#endif + +#endif // ARCORE_C_API_H_ diff --git a/src/utils/Sk3D.h b/src/utils/Sk3D.h deleted file mode 100644 index 46bb5cce80..0000000000 --- a/src/utils/Sk3D.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2018 Google Inc. - * - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ - -#ifndef Sk3D_DEFINED -#define Sk3D_DEFINED - -#include "SkPoint3.h" -#include "SkMatrix44.h" - -void Sk3LookAt(SkMatrix44* dst, const SkPoint3& eye, const SkPoint3& center, const SkPoint3& up); -bool Sk3Perspective(SkMatrix44* dst, float near, float far, float angle); -void Sk3MapPts(SkPoint dst[], const SkMatrix44& m4, const SkPoint3 src[], int count); - -#endif - |