From bb5b7588d67aa3ed24f1b44ded654215e1818b2f Mon Sep 17 00:00:00 2001 From: ziadb Date: Mon, 2 Jul 2018 11:12:27 -0400 Subject: [PATCH] Java-only version of SkAR: drawing on android Canvas Bug: skia: Change-Id: I3b85fac93a2854d1575f71554e2a7da66e8a6a6f Reviewed-on: https://skia-review.googlesource.com/138920 Reviewed-by: Mike Reed --- platform_tools/android/apps/settings.gradle | 1 + .../android/apps/skar_java/build.gradle | 42 +++ .../android/apps/skar_java/proguard-rules.pro | 17 + .../skar_java/src/main/AndroidManifest.xml | 50 +++ .../src/main/assets/models/trigrid.png | Bin 0 -> 37354 bytes .../src/main/assets/shaders/plane.frag | 31 ++ .../src/main/assets/shaders/plane.vert | 40 ++ .../src/main/assets/shaders/point_cloud.frag | 21 ++ .../src/main/assets/shaders/point_cloud.vert | 28 ++ .../src/main/assets/shaders/screenquad.frag | 24 ++ .../src/main/assets/shaders/screenquad.vert | 24 ++ .../java/helloskar/ARSurfaceView.java | 45 +++ .../examples/java/helloskar/DrawManager.java | 106 ++++++ .../java/helloskar/HelloSkARActivity.java | 355 ++++++++++++++++++ .../main/java/com/google/skar/SkARMatrix.java | 211 +++++++++++ .../main/java/com/google/skar/SkARUtil.java | 31 ++ .../main/res/drawable-xxhdpi/ic_launcher.png | Bin 0 -> 21226 bytes .../src/main/res/layout/activity_main.xml | 39 ++ .../skar_java/src/main/res/values/strings.xml | 18 + .../skar_java/src/main/res/values/styles.xml | 35 ++ 20 files changed, 1118 insertions(+) create mode 100644 platform_tools/android/apps/skar_java/build.gradle create mode 100644 platform_tools/android/apps/skar_java/proguard-rules.pro create mode 100644 platform_tools/android/apps/skar_java/src/main/AndroidManifest.xml create mode 100644 platform_tools/android/apps/skar_java/src/main/assets/models/trigrid.png create mode 100644 platform_tools/android/apps/skar_java/src/main/assets/shaders/plane.frag create mode 100644 platform_tools/android/apps/skar_java/src/main/assets/shaders/plane.vert create mode 100644 platform_tools/android/apps/skar_java/src/main/assets/shaders/point_cloud.frag create mode 100644 platform_tools/android/apps/skar_java/src/main/assets/shaders/point_cloud.vert create mode 100644 platform_tools/android/apps/skar_java/src/main/assets/shaders/screenquad.frag create mode 100644 platform_tools/android/apps/skar_java/src/main/assets/shaders/screenquad.vert create mode 100644 platform_tools/android/apps/skar_java/src/main/java/com/google/ar/core/examples/java/helloskar/ARSurfaceView.java create mode 100644 platform_tools/android/apps/skar_java/src/main/java/com/google/ar/core/examples/java/helloskar/DrawManager.java create mode 100644 platform_tools/android/apps/skar_java/src/main/java/com/google/ar/core/examples/java/helloskar/HelloSkARActivity.java create mode 100644 platform_tools/android/apps/skar_java/src/main/java/com/google/skar/SkARMatrix.java create mode 100644 platform_tools/android/apps/skar_java/src/main/java/com/google/skar/SkARUtil.java create mode 100644 platform_tools/android/apps/skar_java/src/main/res/drawable-xxhdpi/ic_launcher.png create mode 100644 platform_tools/android/apps/skar_java/src/main/res/layout/activity_main.xml create mode 100644 platform_tools/android/apps/skar_java/src/main/res/values/strings.xml create mode 100644 platform_tools/android/apps/skar_java/src/main/res/values/styles.xml diff --git a/platform_tools/android/apps/settings.gradle b/platform_tools/android/apps/settings.gradle index 148e1ac2af..72774bb32b 100644 --- a/platform_tools/android/apps/settings.gradle +++ b/platform_tools/android/apps/settings.gradle @@ -1,3 +1,4 @@ include ':viewer' include ':skqp' include ':arcore' //must build out directory first: bin/gn gen out/arm64 --args='ndk="NDKPATH" target_cpu="ABI" is_component_build=true' +include ':skar_java' \ No newline at end of file diff --git a/platform_tools/android/apps/skar_java/build.gradle b/platform_tools/android/apps/skar_java/build.gradle new file mode 100644 index 0000000000..fd85e24230 --- /dev/null +++ b/platform_tools/android/apps/skar_java/build.gradle @@ -0,0 +1,42 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 27 + defaultConfig { + applicationId "com.google.ar.core.examples.java.helloar" + + // 24 is the minimum since ARCore only works with 24 and higher. + minSdkVersion 26 + targetSdkVersion 27 + versionCode 1 + versionName "1.0" + } + + testOptions { + unitTests.returnDefaultValues = false + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + // ARCore library + implementation 'com.google.ar:core:1.2.0' + + // Obj - a simple Wavefront OBJ file loader + // https://github.com/javagl/Obj + implementation 'de.javagl:obj:0.2.1' + + implementation 'com.android.support:appcompat-v7:27.0.2' + implementation 'com.android.support:design:27.0.2' + + // Required -- JUnit 4 framework + testImplementation 'junit:junit:4.12' + // Optional -- Mockito framework + testImplementation 'org.mockito:mockito-core:1.10.19' +} diff --git a/platform_tools/android/apps/skar_java/proguard-rules.pro b/platform_tools/android/apps/skar_java/proguard-rules.pro new file mode 100644 index 0000000000..45dc58a590 --- /dev/null +++ b/platform_tools/android/apps/skar_java/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /opt/android-sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/platform_tools/android/apps/skar_java/src/main/AndroidManifest.xml b/platform_tools/android/apps/skar_java/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..6f93cbf7a2 --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/AndroidManifest.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/platform_tools/android/apps/skar_java/src/main/assets/models/trigrid.png b/platform_tools/android/apps/skar_java/src/main/assets/models/trigrid.png new file mode 100644 index 0000000000000000000000000000000000000000..05cbe6e52f39a5a9aa7e4e9c2b46b9b9b4d5e3b2 GIT binary patch literal 37354 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 C`Ms+E literal 0 HcmV?d00001 diff --git a/platform_tools/android/apps/skar_java/src/main/assets/shaders/plane.frag b/platform_tools/android/apps/skar_java/src/main/assets/shaders/plane.frag new file mode 100644 index 0000000000..d0a4708895 --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/assets/shaders/plane.frag @@ -0,0 +1,31 @@ +/* + * 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. + */ + +precision highp float; +uniform sampler2D u_Texture; +uniform vec4 u_dotColor; +uniform vec4 u_lineColor; +uniform vec4 u_gridControl; // dotThreshold, lineThreshold, lineFadeShrink, occlusionShrink +varying vec3 v_TexCoordAlpha; + +void main() { + vec4 control = texture2D(u_Texture, v_TexCoordAlpha.xy); + float dotScale = v_TexCoordAlpha.z; + float lineFade = max(0.0, u_gridControl.z * v_TexCoordAlpha.z - (u_gridControl.z - 1.0)); + vec3 color = (control.r * dotScale > u_gridControl.x) ? u_dotColor.rgb + : (control.g > u_gridControl.y) ? u_lineColor.rgb * lineFade + : (u_lineColor.rgb * 0.25 * lineFade) ; + gl_FragColor = vec4(color, v_TexCoordAlpha.z * u_gridControl.w); +} diff --git a/platform_tools/android/apps/skar_java/src/main/assets/shaders/plane.vert b/platform_tools/android/apps/skar_java/src/main/assets/shaders/plane.vert new file mode 100644 index 0000000000..9ac5a6db27 --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/assets/shaders/plane.vert @@ -0,0 +1,40 @@ +/* + * 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. + */ + +uniform mat4 u_Model; +uniform mat4 u_ModelViewProjection; +uniform mat2 u_PlaneUvMatrix; +uniform vec3 u_Normal; + +attribute vec3 a_XZPositionAlpha; // (x, z, alpha) + +varying vec3 v_TexCoordAlpha; + +void main() { + vec4 local_pos = vec4(a_XZPositionAlpha.x, 0.0, a_XZPositionAlpha.y, 1.0); + vec4 world_pos = u_Model * 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(u_Normal, arbitrary)); + vec3 vec_v = normalize(cross(u_Normal, vec_u)); + + // Project vertices in world frame onto vec_u and vec_v. + vec2 uv = vec2(dot(world_pos.xyz, vec_u), dot(world_pos.xyz, vec_v)); + v_TexCoordAlpha = vec3(u_PlaneUvMatrix * uv, a_XZPositionAlpha.z); + gl_Position = u_ModelViewProjection * local_pos; +} diff --git a/platform_tools/android/apps/skar_java/src/main/assets/shaders/point_cloud.frag b/platform_tools/android/apps/skar_java/src/main/assets/shaders/point_cloud.frag new file mode 100644 index 0000000000..463d0526e4 --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/assets/shaders/point_cloud.frag @@ -0,0 +1,21 @@ +/* + * 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. + */ + +precision mediump float; +varying vec4 v_Color; + +void main() { + gl_FragColor = v_Color; +} diff --git a/platform_tools/android/apps/skar_java/src/main/assets/shaders/point_cloud.vert b/platform_tools/android/apps/skar_java/src/main/assets/shaders/point_cloud.vert new file mode 100644 index 0000000000..627fc1a6f3 --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/assets/shaders/point_cloud.vert @@ -0,0 +1,28 @@ +/* + * 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. + */ + +uniform mat4 u_ModelViewProjection; +uniform vec4 u_Color; +uniform float u_PointSize; + +attribute vec4 a_Position; + +varying vec4 v_Color; + +void main() { + v_Color = u_Color; + gl_Position = u_ModelViewProjection * vec4(a_Position.xyz, 1.0); + gl_PointSize = u_PointSize; +} diff --git a/platform_tools/android/apps/skar_java/src/main/assets/shaders/screenquad.frag b/platform_tools/android/apps/skar_java/src/main/assets/shaders/screenquad.frag new file mode 100644 index 0000000000..800d723a80 --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/assets/shaders/screenquad.frag @@ -0,0 +1,24 @@ +/* + * 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. + */ +#extension GL_OES_EGL_image_external : require + +precision mediump float; +varying vec2 v_TexCoord; +uniform samplerExternalOES sTexture; + + +void main() { + gl_FragColor = texture2D(sTexture, v_TexCoord); +} diff --git a/platform_tools/android/apps/skar_java/src/main/assets/shaders/screenquad.vert b/platform_tools/android/apps/skar_java/src/main/assets/shaders/screenquad.vert new file mode 100644 index 0000000000..01c93e3d48 --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/assets/shaders/screenquad.vert @@ -0,0 +1,24 @@ +/* + * 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. + */ + +attribute vec4 a_Position; +attribute vec2 a_TexCoord; + +varying vec2 v_TexCoord; + +void main() { + gl_Position = a_Position; + v_TexCoord = a_TexCoord; +} diff --git a/platform_tools/android/apps/skar_java/src/main/java/com/google/ar/core/examples/java/helloskar/ARSurfaceView.java b/platform_tools/android/apps/skar_java/src/main/java/com/google/ar/core/examples/java/helloskar/ARSurfaceView.java new file mode 100644 index 0000000000..eaa336cc9e --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/java/com/google/ar/core/examples/java/helloskar/ARSurfaceView.java @@ -0,0 +1,45 @@ +package com.google.ar.core.examples.java.helloskar; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.util.AttributeSet; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +/** + * SurfaceView that is overlayed on top of a GLSurfaceView. All 2D drawings can be done on this + * surface. + */ +public class ARSurfaceView extends SurfaceView implements SurfaceHolder.Callback { + + boolean running; + + public ARSurfaceView(Context context, AttributeSet attrs) { + super(context, attrs); + + SurfaceHolder holder = getHolder(); + this.setBackgroundColor(Color.TRANSPARENT); + this.setZOrderOnTop(true); + holder.setFormat(PixelFormat.TRANSPARENT); + holder.addCallback(this); + } + + public boolean isRunning() { + return running; + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + running = true; + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + running = false; + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + } +} diff --git a/platform_tools/android/apps/skar_java/src/main/java/com/google/ar/core/examples/java/helloskar/DrawManager.java b/platform_tools/android/apps/skar_java/src/main/java/com/google/ar/core/examples/java/helloskar/DrawManager.java new file mode 100644 index 0000000000..bf85a767c2 --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/java/com/google/ar/core/examples/java/helloskar/DrawManager.java @@ -0,0 +1,106 @@ +package com.google.ar.core.examples.java.helloskar; + +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.RectF; + +import com.google.skar.SkARMatrix; +import com.google.skar.SkARUtil; + +import java.util.ArrayList; + +public class DrawManager { + private float[] projectionMatrix = new float[16]; + private float[] viewMatrix = new float[16]; + private float[] viewportMatrix = new float[16]; + private ColorFilter lightFilter; + public ArrayList modelMatrices = new ArrayList<>(); + + public void updateViewportMatrix(float width, float height) { + viewportMatrix = SkARMatrix.createViewportMatrix(width, height); + } + + public void updateProjectionMatrix(float[] projectionMatrix) { + this.projectionMatrix = projectionMatrix; + } + + public void updateViewMatrix(float[] viewMatrix) { + this.viewMatrix = viewMatrix; + } + + public void updateLightColorFilter(float[] colorCorr) { + lightFilter = SkARUtil.createLightCorrectionColorFilter(colorCorr); + } + + public void drawCircle(Canvas canvas) { + if (modelMatrices.isEmpty()) { + return; + } + Paint p = new Paint(); + p.setColorFilter(lightFilter); + p.setARGB(180, 100, 0, 0); + + canvas.save(); + canvas.setMatrix(SkARMatrix.createPerspectiveMatrix(modelMatrices.get(0), + viewMatrix, projectionMatrix, viewportMatrix)); + canvas.drawCircle(0, 0, 0.1f, p); + canvas.restore(); + } + + public void drawRect(Canvas canvas) { + if (modelMatrices.isEmpty()) { + return; + } + Paint p = new Paint(); + p.setColorFilter(lightFilter); + p.setARGB(180, 0, 0, 255); + canvas.save(); + canvas.setMatrix(SkARMatrix.createPerspectiveMatrix(modelMatrices.get(0), + viewMatrix, projectionMatrix, viewportMatrix)); + RectF rect = new RectF(0, 0, 0.2f, 0.2f); + canvas.drawRect(rect, p); + canvas.restore(); + } + + public void drawText(Canvas canvas, String text) { + if (modelMatrices.isEmpty()) { + return; + } + Paint p = new Paint(); + float textSize = 100; + p.setColorFilter(lightFilter); + p.setARGB(255, 0, 255, 0); + p.setTextSize(textSize); + + + float[] scaleMatrix = getTextScaleMatrix(textSize); + float[] rotateMatrix = createXYtoXZRotationMatrix(); + float[] actualModel = new float[16]; + android.opengl.Matrix.setIdentityM(actualModel, 0); + + android.opengl.Matrix.multiplyMM(actualModel, 0, scaleMatrix, 0, rotateMatrix, 0); + android.opengl.Matrix.multiplyMM(actualModel, 0, modelMatrices.get(0), 0, actualModel, 0); + + canvas.save(); + canvas.setMatrix(SkARMatrix.createPerspectiveMatrix(actualModel, + viewMatrix, projectionMatrix, viewportMatrix, false)); + canvas.drawText(text, 0, 0, p); + canvas.restore(); + } + + private float[] getTextScaleMatrix(float size) { + float scaleFactor = 1 / (size * 10); + float[] initScale = new float[16]; + android.opengl.Matrix.setIdentityM(initScale, 0); + android.opengl.Matrix.scaleM(initScale, 0, scaleFactor, scaleFactor, scaleFactor); + return initScale; + } + + private float[] createXYtoXZRotationMatrix() { + float[] skiaRotation = new float[16]; + android.opengl.Matrix.setIdentityM(skiaRotation, 0); + android.opengl.Matrix.rotateM(skiaRotation, 0, 90, 1, 0, 0); + return skiaRotation; + } +} diff --git a/platform_tools/android/apps/skar_java/src/main/java/com/google/ar/core/examples/java/helloskar/HelloSkARActivity.java b/platform_tools/android/apps/skar_java/src/main/java/com/google/ar/core/examples/java/helloskar/HelloSkARActivity.java new file mode 100644 index 0000000000..a8ccb53478 --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/java/com/google/ar/core/examples/java/helloskar/HelloSkARActivity.java @@ -0,0 +1,355 @@ +/* + * 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 com.google.ar.core.examples.java.helloskar; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.opengl.GLES20; +import android.opengl.GLSurfaceView; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.MotionEvent; +import android.view.SurfaceHolder; +import android.widget.Toast; + +import com.google.ar.core.Anchor; +import com.google.ar.core.ArCoreApk; +import com.google.ar.core.Camera; +import com.google.ar.core.Frame; +import com.google.ar.core.HitResult; +import com.google.ar.core.Plane; +import com.google.ar.core.Point; +import com.google.ar.core.Point.OrientationMode; +import com.google.ar.core.PointCloud; +import com.google.ar.core.Session; +import com.google.ar.core.Trackable; +import com.google.ar.core.TrackingState; +import com.google.ar.core.examples.java.common.helpers.CameraPermissionHelper; +import com.google.ar.core.examples.java.common.helpers.DisplayRotationHelper; +import com.google.ar.core.examples.java.common.helpers.FullScreenHelper; +import com.google.ar.core.examples.java.common.helpers.SnackbarHelper; +import com.google.ar.core.examples.java.common.helpers.TapHelper; +import com.google.ar.core.examples.java.common.rendering.BackgroundRenderer; +import com.google.ar.core.examples.java.common.rendering.PlaneRenderer; +import com.google.ar.core.examples.java.common.rendering.PointCloudRenderer; +import com.google.ar.core.exceptions.CameraNotAvailableException; +import com.google.ar.core.exceptions.UnavailableApkTooOldException; +import com.google.ar.core.exceptions.UnavailableArcoreNotInstalledException; +import com.google.ar.core.exceptions.UnavailableDeviceNotCompatibleException; +import com.google.ar.core.exceptions.UnavailableSdkTooOldException; +import com.google.ar.core.exceptions.UnavailableUserDeclinedInstallationException; + +import java.io.IOException; +import java.util.ArrayList; + +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 API. The application will display any detected planes and will allow the user to tap on a + * plane to place 2D objects + */ +public class HelloSkARActivity extends AppCompatActivity implements GLSurfaceView.Renderer { + private static final String TAG = HelloSkARActivity.class.getSimpleName(); + + //Simple SurfaceView used to draw 2D objects on top of the GLSurfaceView + private ARSurfaceView arSurfaceView; + + //GLSurfaceView used to draw 3D objects & camera input + private GLSurfaceView glSurfaceView; + + //ARSession + private Session session; + + private boolean installRequested; + private final SnackbarHelper messageSnackbarHelper = new SnackbarHelper(); + private DisplayRotationHelper displayRotationHelper; + private TapHelper tapHelper; + + //Renderers + private final BackgroundRenderer backgroundRenderer = new BackgroundRenderer(); + private final PlaneRenderer planeRenderer = new PlaneRenderer(); + private final PointCloudRenderer pointCloudRenderer = new PointCloudRenderer(); + + //2D Renderer + private DrawManager drawManager = new DrawManager(); + + // Temporary matrix allocated here to reduce number of allocations for each frame. + private final float[] anchorMatrix = new float[16]; + + // Anchors created from taps used for object placing. + private final ArrayList anchors = new ArrayList<>(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + arSurfaceView = findViewById(R.id.arsurfaceview); + glSurfaceView = findViewById(R.id.glsurfaceview); + arSurfaceView.bringToFront(); + displayRotationHelper = new DisplayRotationHelper(/*context=*/ this); + + // Set up tap listener. + tapHelper = new TapHelper(/*context=*/ this); + glSurfaceView.setOnTouchListener(tapHelper); + + // Set up renderer. + glSurfaceView.setPreserveEGLContextOnPause(true); + glSurfaceView.setEGLContextClientVersion(2); + glSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0); // Alpha used for plane blending. + glSurfaceView.setRenderer(this); + glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); + + installRequested = false; + } + + @Override + protected void onResume() { + super.onResume(); + + if (session == null) { + Exception exception = null; + String message = null; + try { + switch (ArCoreApk.getInstance().requestInstall(this, !installRequested)) { + case INSTALL_REQUESTED: + installRequested = true; + return; + case INSTALLED: + break; + } + + // 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; + } + + // Create the session. + session = new Session(/* context= */ this); + + } catch (UnavailableArcoreNotInstalledException + | UnavailableUserDeclinedInstallationException e) { + message = "Please install ARCore"; + exception = e; + } catch (UnavailableApkTooOldException e) { + message = "Please update ARCore"; + exception = e; + } catch (UnavailableSdkTooOldException e) { + message = "Please update this app"; + exception = e; + } catch (UnavailableDeviceNotCompatibleException e) { + message = "This device does not support AR"; + exception = e; + } catch (Exception e) { + message = "Failed to create AR session"; + exception = e; + } + + if (message != null) { + messageSnackbarHelper.showError(this, message); + Log.e(TAG, "Exception creating session", exception); + return; + } + } + + // Note that order matters - see the note in onPause(), the reverse applies here. + try { + session.resume(); + } catch (CameraNotAvailableException e) { + messageSnackbarHelper.showError(this, "Camera not available. Please restart the app."); + session = null; + return; + } + + glSurfaceView.onResume(); + displayRotationHelper.onResume(); + messageSnackbarHelper.showMessage(this, "Searching for surfaces..."); + } + + @Override + public void onPause() { + super.onPause(); + if (session != null) { + displayRotationHelper.onPause(); + glSurfaceView.onPause(); + session.pause(); + } + } + + @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(); + } + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + FullScreenHelper.setFullScreenOnWindowFocusChanged(this, hasFocus); + } + + /************** GLSurfaceView Methods ****************************/ + @Override + public void onSurfaceCreated(GL10 gl, EGLConfig config) { + GLES20.glClearColor(0.1f, 0.1f, 0.1f, 1.0f); + + // Prepare the rendering objects. This involves reading shaders, so may throw an IOException. + try { + // Create the texture and pass it to ARCore session to be filled during update(). + backgroundRenderer.createOnGlThread(/*context=*/ this); + planeRenderer.createOnGlThread(/*context=*/ this, "models/trigrid.png"); + pointCloudRenderer.createOnGlThread(/*context=*/ this); + } catch (IOException e) { + Log.e(TAG, "Failed to read an asset file", e); + } + } + + @Override + public void onSurfaceChanged(GL10 gl, int width, int height) { + displayRotationHelper.onSurfaceChanged(width, height); + GLES20.glViewport(0, 0, width, height); + + // Send viewport information to 2D AR drawing manager + drawManager.updateViewportMatrix(width, height); + } + + @Override + public void onDrawFrame(GL10 gl) { + // Clear screen to notify driver it should not load any pixels from previous frame. + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); + + if (session == null) { + return; + } + // Notify ARCore session that the view size changed so that the perspective matrix and + // the video background can be properly adjusted. + displayRotationHelper.updateSessionIfNeeded(session); + + try { + session.setCameraTextureName(backgroundRenderer.getTextureId()); + Frame frame = session.update(); + Camera camera = frame.getCamera(); + + MotionEvent tap = tapHelper.poll(); + if (tap != null && camera.getTrackingState() == TrackingState.TRACKING) { + for (HitResult hit : frame.hitTest(tap)) { + // Check if any plane was hit, and if it was hit inside the plane polygon + Trackable trackable = hit.getTrackable(); + // Creates an anchor if a plane or an oriented point was hit. + if ((trackable instanceof Plane + && ((Plane) trackable).isPoseInPolygon(hit.getHitPose()) + && (PlaneRenderer.calculateDistanceToPlane(hit.getHitPose(), camera.getPose()) + > 0)) + || (trackable instanceof Point + && ((Point) trackable).getOrientationMode() + == OrientationMode.ESTIMATED_SURFACE_NORMAL)) { + if (anchors.size() >= 20) { + anchors.get(0).detach(); + anchors.remove(0); + } + anchors.add(hit.createAnchor()); + break; + } + } + } + + // Draw background. + backgroundRenderer.draw(frame); + + // If not tracking, don't draw objects + if (camera.getTrackingState() == TrackingState.PAUSED) { + return; + } + + // Get projection matrix. + float[] projmtx = new float[16]; + camera.getProjectionMatrix(projmtx, 0, 0.1f, 100.0f); + drawManager.updateProjectionMatrix(projmtx); + + // Get camera matrix and draw. + float[] viewmtx = new float[16]; + camera.getViewMatrix(viewmtx, 0); + drawManager.updateViewMatrix(viewmtx); + + final float[] colorCorrectionRgba = new float[4]; + frame.getLightEstimate().getColorCorrection(colorCorrectionRgba, 0); + drawManager.updateLightColorFilter(colorCorrectionRgba); + + PointCloud pointCloud = frame.acquirePointCloud(); + pointCloudRenderer.update(pointCloud); + pointCloudRenderer.draw(viewmtx, projmtx); + pointCloud.release(); + + // Check if we detected at least one plane. If so, hide the loading message. + if (messageSnackbarHelper.isShowing()) { + for (Plane plane : session.getAllTrackables(Plane.class)) { + if (plane.getType() == com.google.ar.core.Plane.Type.HORIZONTAL_UPWARD_FACING + && plane.getTrackingState() == TrackingState.TRACKING) { + messageSnackbarHelper.hide(this); + break; + } + } + } + // Visualize planes. + planeRenderer.drawPlanes( + session.getAllTrackables(Plane.class), camera.getDisplayOrientedPose(), projmtx); + + // Draw models using Canvas + if (arSurfaceView.isRunning()) { + drawModels(); + } + + + } catch (Throwable t) { + // Avoid crashing the application due to unhandled exceptions. + Log.e(TAG, "Exception on the OpenGL thread", t); + } + } + + private void drawModels() { + SurfaceHolder holder = arSurfaceView.getHolder(); + Canvas canvas = holder.lockHardwareCanvas(); + canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); + for (Anchor anchor : anchors) { + if (anchor.getTrackingState() != TrackingState.TRACKING) { + continue; + } + // Get the current pose of an Anchor in world space. The Anchor pose is updated + // during calls to session.update() as ARCore refines its estimate of the world. + anchor.getPose().toMatrix(anchorMatrix, 0); + drawManager.modelMatrices.add(0, anchorMatrix); + + drawManager.drawRect(canvas); + drawManager.drawCircle(canvas); + drawManager.drawText(canvas, "HelloSkAR"); + } + holder.unlockCanvasAndPost(canvas); + + } +} diff --git a/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/SkARMatrix.java b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/SkARMatrix.java new file mode 100644 index 0000000000..fc9333c6df --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/SkARMatrix.java @@ -0,0 +1,211 @@ +package com.google.skar; + +import android.graphics.Matrix; + +/** + * Provides static methods for matrix manipulation. Input matrices are assumed to be 4x4 + * android.opengl.Matrix types. Output matrices are 3x3 android.graphics.Matrix types. + * The main use of this class is to be able to get a Matrix for a Canvas that applies perspective + * to 2D objects + */ + +public class SkARMatrix { + /** + * Returns an android.graphics.Matrix that can be used on a Canvas to draw a 2D object in + * perspective. Objects will be rotated towards the XZ plane. Undefined behavior when any of + * the matrices are not of size 16, or are null. + * + * @param model 4x4 model matrix of the object to be drawn (global/world) + * @param view 4x4 camera view matrix (brings objects to camera origin and orientation) + * @param projection 4x4 projection matrix + * @param viewport 4x4 viewport matrix + * @return 3x3 matrix that puts a 2D objects in perspective on a Canvas + */ + + public static Matrix createPerspectiveMatrix(float[] model, float[] view, float[] projection, + float[] viewport) { + float[] skiaRotation = createXYtoXZRotationMatrix(); + float[][] matrices = {skiaRotation, model, view, projection, viewport}; + return createMatrixFrom4x4Array(matrices); + } + + /** + * Returns an android.graphics.Matrix that can be used on a Canvas to draw a 2D object in + * perspective. Undefined behavior when any of the matrices are not of size 16, or are null. + * + * @param model 4x4 model matrix of the object to be drawn (global/world) + * @param view 4x4 camera view matrix (brings objects to camera origin and orientation) + * @param projection 4x4 projection matrix + * @param viewport 4x4 viewport matrix + * @param rotatePlane specifies if object should be from the XY plane to the XZ plane + * @return 3x3 matrix that puts a 2D objects in perspective on a Canvas + */ + + public static Matrix createPerspectiveMatrix(float[] model, float[] view, float[] projection, + float[] viewport, boolean rotatePlane) { + if (rotatePlane) { + return createPerspectiveMatrix(model, view, projection, viewport); + } else { + float[][] matrices = {model, view, projection, viewport}; + return createMatrixFrom4x4Array(matrices); + } + } + + /** + * Returns an android.graphics.Matrix that can be used on a Canvas to draw a 2D object in + * perspective. Undefined behavior when any of the matrices are not of size 16, or are null. + * + * @param model 4x4 model matrix of the object to be drawn (global/world) + * @param view 4x4 camera view matrix (brings objects to camera origin and orientation) + * @param projection 4x4 projection matrix + * @param viewPortWidth width of viewport of GLSurfaceView + * @param viewPortHeight height of viewport of GLSurfaceView + * @param rotatePlane specifies if object should be from the XY plane to the XZ plane + * @return 3x3 matrix that puts a 2D objects in perspective on a Canvas + */ + + public static Matrix createPerspectiveMatrix(float[] model, float[] view, float[] projection, + float viewPortWidth, float viewPortHeight, boolean rotatePlane) { + if (rotatePlane) { + return createPerspectiveMatrix(model, view, projection, viewPortWidth, viewPortHeight); + } else { + float[] viewPort = createViewportMatrix(viewPortWidth, viewPortHeight); + float[][] matrices = {model, view, projection, viewPort}; + return createMatrixFrom4x4Array(matrices); + } + } + + /** + * Returns an android.graphics.Matrix that can be used on a Canvas to draw a 2D object in + * perspective. Object will be rotated towards the XZ plane. Undefined behavior when any of + * the matrices are not of size 16, or are null. + * + * @param model 4x4 model matrix of the object to be drawn (global/world) + * @param view 4x4 camera view matrix (brings objects to camera origin and orientation) + * @param projection 4x4 projection matrix + * @param viewPortWidth width of viewport of GLSurfaceView + * @param viewPortHeight height of viewport of GLSurfaceView + * @return 3x3 matrix that puts a 2D objects in perspective on a Canvas + */ + + public static Matrix createPerspectiveMatrix(float[] model, float[] view, float[] projection, + float viewPortWidth, float viewPortHeight) { + float[] viewPort = createViewportMatrix(viewPortWidth, viewPortHeight); + float[] skiaRotation = createXYtoXZRotationMatrix(); + float[][] matrices = {skiaRotation, model, view, projection, viewPort}; + return createMatrixFrom4x4Array(matrices); + } + + /** + * Returns a 16-float matrix in column-major order that represents a viewport matrix given + * the width and height of the viewport. + * + * @param width width of viewport + * @param height height of viewport + */ + + public static float[] createViewportMatrix(float width, float height) { + float[] viewPort = new float[16]; + android.opengl.Matrix.setIdentityM(viewPort, 0); + android.opengl.Matrix.translateM(viewPort, 0, width / 2, height / 2, 0); + android.opengl.Matrix.scaleM(viewPort, 0, width / 2, -height / 2, 0); + return viewPort; + } + + /** + * Returns a 16-float matrix in column-major order that is used to rotate objects from the XY plane + * to the XZ plane. This is useful given that objects drawn on the Canvas are on the XY plane. + * In order to get objects to appear as if they are sticking on planes/ceilings/walls, we need + * to rotate them from the XY plane to the XZ plane. + */ + + private static float[] createXYtoXZRotationMatrix() { + float[] rotation = new float[16]; + android.opengl.Matrix.setIdentityM(rotation, 0); + android.opengl.Matrix.rotateM(rotation, 0, 90, 1, 0, 0); + return rotation; + } + + /** + * Returns an android.graphics.Matrix resulting from a 9-float matrix array in row-major order. + * Undefined behavior when the array is not of size 9 or is null. + * + * @param m3 9-float matrix array in row-major order + */ + + public static Matrix createMatrixFrom3x3(float[] m3) { + Matrix m = new Matrix(); + m.setValues(m3); + return m; + } + + /** + * Returns an android.graphics.Matrix resulting from a 16-float matrix array in column-major order + * Undefined behavior when the array is not of size 16 or is null. + * + * @param m4 + */ + + public static Matrix createMatrixFrom4x4(float[] m4) { + float[] m3 = matrix4x4ToMatrix3x3(m4); + return createMatrixFrom3x3(m3); + } + + /** + * Returns an android.graphics.Matrix resulting from the concatenation of 16-float matrices + * in column-major order from left to right. + * e.g: m4Array = {m1, m2, m3} --> returns m = m3 * m2 * m1 + * Undefined behavior when the array is empty, null, or contains arrays not of size 9 (or null) + * + * @param m4Array array of 16-float matrices in column-major order + */ + + public static Matrix createMatrixFrom4x4Array(float[][] m4Array) { + float[] result = multiplyMatrices4x4(m4Array); + return createMatrixFrom4x4(result); + } + + /** + * Returns a 9-float matrix in row-major order given a 16-float matrix in column-major order. + * This will drop the Z column and row. + * Undefined behavior when the array is not of size 9 or is null. + * + * @param m4 16-float matrix in column-major order + */ + + private static float[] matrix4x4ToMatrix3x3(float[] m4) { + float[] m3 = new float[9]; + + int j = 0; + for (int i = 0; i < 7; i = i + 3) { + if (j == 2) { + j++; //skip row #3 + } + m3[i] = m4[j]; + m3[i + 1] = m4[j + 4]; + m3[i + 2] = m4[j + 12]; + j++; + } + return m3; + } + + /** + * Returns a 16-float matrix in column-major order resulting from the multiplication of matrices. + * e.g: m4Array = {m1, m2, m3} --> returns m = m3 * m2 * m1 + * Undefined behavior when the array is empty, null, or contains arrays not of size 9 (or null) + * + * @param m4Array array of 16-float matrices in column-major order + */ + + private static float[] multiplyMatrices4x4(float[][] m4Array) { + float[] result = new float[16]; + android.opengl.Matrix.setIdentityM(result, 0); + float[] rhs = result; + for (int i = 0; i < m4Array.length; i++) { + float[] lhs = m4Array[i]; + android.opengl.Matrix.multiplyMM(result, 0, lhs, 0, rhs, 0); + rhs = result; + } + return result; + } +} diff --git a/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/SkARUtil.java b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/SkARUtil.java new file mode 100644 index 0000000000..42a221af86 --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/SkARUtil.java @@ -0,0 +1,31 @@ +package com.google.skar; + +import android.graphics.ColorFilter; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; + +import java.util.Arrays; + +public class SkARUtil { + + private static final float MIDDLE_GRAY_GAMMA = 0.466f; + + /** + * Returns a ColorFilter that can be used on a Paint to apply color correction effects + * as documented by ARCore in + * LightEstimate + * + * @param colorCorr output array of + * getColorCorrection() + * @return ColorFilter with effects applied + */ + public static ColorFilter createLightCorrectionColorFilter(float[] colorCorr) { + float[] colorCorrCopy = Arrays.copyOf(colorCorr, 4); + for (int i = 0; i < 3; i++) { + colorCorrCopy[i] *= colorCorrCopy[3] / MIDDLE_GRAY_GAMMA; + } + ColorMatrix m = new ColorMatrix(); + m.setScale(colorCorrCopy[0], colorCorrCopy[1], colorCorrCopy[2], 1); + return new ColorMatrixColorFilter(m); + } +} diff --git a/platform_tools/android/apps/skar_java/src/main/res/drawable-xxhdpi/ic_launcher.png b/platform_tools/android/apps/skar_java/src/main/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..3f691da0397b0bc5da110758f2e1a86babba700a GIT binary patch literal 21226 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 literal 0 HcmV?d00001 diff --git a/platform_tools/android/apps/skar_java/src/main/res/layout/activity_main.xml b/platform_tools/android/apps/skar_java/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000000..2c46a04e91 --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/res/layout/activity_main.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + diff --git a/platform_tools/android/apps/skar_java/src/main/res/values/strings.xml b/platform_tools/android/apps/skar_java/src/main/res/values/strings.xml new file mode 100644 index 0000000000..ed91c5d4f0 --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/res/values/strings.xml @@ -0,0 +1,18 @@ + + + HelloSkAR Java + diff --git a/platform_tools/android/apps/skar_java/src/main/res/values/styles.xml b/platform_tools/android/apps/skar_java/src/main/res/values/styles.xml new file mode 100644 index 0000000000..59cf7e9ffb --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/res/values/styles.xml @@ -0,0 +1,35 @@ + + + + + + + + + +