aboutsummaryrefslogtreecommitdiffhomepage
path: root/platform_tools/android/apps/arcore/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'platform_tools/android/apps/arcore/src/main')
-rw-r--r--platform_tools/android/apps/arcore/src/main/AndroidManifest.xml35
-rw-r--r--platform_tools/android/apps/arcore/src/main/assets/models/trigrid.pngbin0 -> 146080 bytes
-rw-r--r--platform_tools/android/apps/arcore/src/main/cpp/anchor_wrapper.cc41
-rw-r--r--platform_tools/android/apps/arcore/src/main/cpp/anchor_wrapper.h45
-rw-r--r--platform_tools/android/apps/arcore/src/main/cpp/background_renderer.cc113
-rw-r--r--platform_tools/android/apps/arcore/src/main/cpp/background_renderer.h61
-rw-r--r--platform_tools/android/apps/arcore/src/main/cpp/glm.h25
-rw-r--r--platform_tools/android/apps/arcore/src/main/cpp/hello_ar_application.cc987
-rw-r--r--platform_tools/android/apps/arcore/src/main/cpp/hello_ar_application.h158
-rw-r--r--platform_tools/android/apps/arcore/src/main/cpp/jni_interface.cc130
-rw-r--r--platform_tools/android/apps/arcore/src/main/cpp/jni_interface.h34
-rw-r--r--platform_tools/android/apps/arcore/src/main/cpp/pending_anchor.cc68
-rw-r--r--platform_tools/android/apps/arcore/src/main/cpp/pending_anchor.h50
-rw-r--r--platform_tools/android/apps/arcore/src/main/cpp/plane_renderer.cc220
-rw-r--r--platform_tools/android/apps/arcore/src/main/cpp/plane_renderer.h70
-rw-r--r--platform_tools/android/apps/arcore/src/main/cpp/point_cloud_renderer.cc77
-rw-r--r--platform_tools/android/apps/arcore/src/main/cpp/point_cloud_renderer.h57
-rw-r--r--platform_tools/android/apps/arcore/src/main/cpp/util.cc329
-rw-r--r--platform_tools/android/apps/arcore/src/main/cpp/util.h139
-rw-r--r--platform_tools/android/apps/arcore/src/main/java/org/skia/arcore/CameraPermissionHelper.java65
-rw-r--r--platform_tools/android/apps/arcore/src/main/java/org/skia/arcore/HelloArActivity.java364
-rw-r--r--platform_tools/android/apps/arcore/src/main/java/org/skia/arcore/JniInterface.java82
-rw-r--r--platform_tools/android/apps/arcore/src/main/res/drawable-xxhdpi/ic_launcher.pngbin0 -> 21226 bytes
-rw-r--r--platform_tools/android/apps/arcore/src/main/res/layout-xlarge-land/activity_main.xml41
-rw-r--r--platform_tools/android/apps/arcore/src/main/res/layout/activity_main.xml50
-rw-r--r--platform_tools/android/apps/arcore/src/main/res/layout/state_item.xml35
-rw-r--r--platform_tools/android/apps/arcore/src/main/res/menu/draw_menu.xml15
-rw-r--r--platform_tools/android/apps/arcore/src/main/res/menu/edit_menu.xml8
-rw-r--r--platform_tools/android/apps/arcore/src/main/res/menu/rotation_mode.xml11
-rw-r--r--platform_tools/android/apps/arcore/src/main/res/menu/title.xml12
-rw-r--r--platform_tools/android/apps/arcore/src/main/res/values/integers.xml4
-rw-r--r--platform_tools/android/apps/arcore/src/main/res/values/strings.xml19
-rw-r--r--platform_tools/android/apps/arcore/src/main/res/values/styles.xml35
33 files changed, 3380 insertions, 0 deletions
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
new file mode 100644
index 0000000000..d85eedf72c
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/assets/models/trigrid.png
Binary files differ
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
new file mode 100644
index 0000000000..3f691da039
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
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>