From f27b479f957df19b44913f81509aaef7af0d19ce Mon Sep 17 00:00:00 2001 From: ziadb Date: Thu, 26 Jul 2018 15:31:01 -0400 Subject: SkAR Java: refactored project structure 1) Changed file paths 2) Added helpers that were missing previously Bug: skia: Change-Id: Idb4194fcef815a23277a17670acf46255a47451d Reviewed-on: https://skia-review.googlesource.com/143680 Reviewed-by: Mike Reed --- .../examples/java/helloskar/ARSurfaceView.java | 45 -- .../core/examples/java/helloskar/DrawManager.java | 349 ------------- .../examples/java/helloskar/HelloCanvasAR.java | 553 --------------------- .../java/com/google/skar/SkARFingerPainting.java | 155 ------ .../skar/examples/helloskar/app/ARSurfaceView.java | 45 ++ .../skar/examples/helloskar/app/HelloCanvasAR.java | 553 +++++++++++++++++++++ .../examples/helloskar/app/SkARFingerPainting.java | 155 ++++++ .../helloskar/helpers/CameraPermissionHelper.java | 65 +++ .../helloskar/helpers/DisplayRotationHelper.java | 112 +++++ .../helloskar/helpers/FullScreenHelper.java | 49 ++ .../examples/helloskar/helpers/SnackbarHelper.java | 113 +++++ .../skar/examples/helloskar/helpers/TapHelper.java | 116 +++++ .../helloskar/rendering/BackgroundRenderer.java | 190 +++++++ .../examples/helloskar/rendering/DrawManager.java | 349 +++++++++++++ .../examples/helloskar/rendering/ShaderUtil.java | 99 ++++ 15 files changed, 1846 insertions(+), 1102 deletions(-) delete mode 100644 platform_tools/android/apps/skar_java/src/main/java/com/google/ar/core/examples/java/helloskar/ARSurfaceView.java delete mode 100644 platform_tools/android/apps/skar_java/src/main/java/com/google/ar/core/examples/java/helloskar/DrawManager.java delete mode 100644 platform_tools/android/apps/skar_java/src/main/java/com/google/ar/core/examples/java/helloskar/HelloCanvasAR.java delete mode 100644 platform_tools/android/apps/skar_java/src/main/java/com/google/skar/SkARFingerPainting.java create mode 100644 platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/app/ARSurfaceView.java create mode 100644 platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/app/HelloCanvasAR.java create mode 100644 platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/app/SkARFingerPainting.java create mode 100644 platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/CameraPermissionHelper.java create mode 100644 platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/DisplayRotationHelper.java create mode 100644 platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/FullScreenHelper.java create mode 100644 platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/SnackbarHelper.java create mode 100644 platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/TapHelper.java create mode 100644 platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/rendering/BackgroundRenderer.java create mode 100644 platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/rendering/DrawManager.java create mode 100644 platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/rendering/ShaderUtil.java (limited to 'platform_tools/android') 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 deleted file mode 100644 index eaa336cc9e..0000000000 --- a/platform_tools/android/apps/skar_java/src/main/java/com/google/ar/core/examples/java/helloskar/ARSurfaceView.java +++ /dev/null @@ -1,45 +0,0 @@ -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 deleted file mode 100644 index 724ab17c02..0000000000 --- a/platform_tools/android/apps/skar_java/src/main/java/com/google/ar/core/examples/java/helloskar/DrawManager.java +++ /dev/null @@ -1,349 +0,0 @@ -package com.google.ar.core.examples.java.helloskar; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.BitmapShader; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.ColorFilter; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.graphics.RectF; -import android.graphics.Shader; -import android.opengl.Matrix; - -import com.google.ar.core.Plane; -import com.google.ar.core.PointCloud; -import com.google.ar.core.Pose; -import com.google.ar.core.TrackingState; -import com.google.skar.CanvasMatrixUtil; -import com.google.skar.PaintUtil; -import com.google.skar.SkARFingerPainting; -import java.io.IOException; -import java.nio.FloatBuffer; -import java.util.ArrayList; -import java.util.Collection; - -/** - * Sample class that handles drawing different types of geometry using the matrices provided - * by ARCore. The matrices are handled by SkARMatrix in order to be passed to the drawing - * Canvas. - */ - -public class DrawManager { - private float[] projectionMatrix = new float[16]; - private float[] viewMatrix = new float[16]; - private float viewportWidth; - private float viewportHeight; - private ColorFilter lightFilter; - private BitmapShader planeShader; - public ArrayList modelMatrices = new ArrayList<>(); - public SkARFingerPainting fingerPainting = new SkARFingerPainting(false); - - public void updateViewport(float width, float height) { - viewportWidth = width; - viewportHeight = height; - } - - public void updateProjectionMatrix(float[] projectionMatrix) { - this.projectionMatrix = projectionMatrix; - } - - public void updateViewMatrix(float[] viewMatrix) { - this.viewMatrix = viewMatrix; - } - - public void updateLightColorFilter(float[] colorCorr) { - lightFilter = PaintUtil.createLightCorrectionColorFilter(colorCorr); - } - - // Sample function for drawing a circle - public void drawCircle(Canvas canvas) { - if (modelMatrices.isEmpty()) { - return; - } - Paint p = new Paint(); - p.setColorFilter(lightFilter); - p.setARGB(180, 100, 0, 0); - - canvas.save(); - android.graphics.Matrix m = CanvasMatrixUtil.createPerspectiveMatrix(modelMatrices.get(0), - viewMatrix, projectionMatrix, viewportWidth, viewportHeight); - canvas.setMatrix(m); - - canvas.drawCircle(0, 0, 0.1f, p); - canvas.restore(); - } - - // Sample function for drawing an animated round rect - public void drawAnimatedRoundRect(Canvas canvas, float radius) { - if (modelMatrices.isEmpty()) { - return; - } - Paint p = new Paint(); - p.setColorFilter(lightFilter); - p.setARGB(180, 100, 0, 100); - - canvas.save(); - canvas.setMatrix(CanvasMatrixUtil.createPerspectiveMatrix(modelMatrices.get(0), - viewMatrix, projectionMatrix, viewportWidth, viewportHeight)); - canvas.drawRoundRect(0,0, 0.5f, 0.5f, radius, radius, p); - canvas.restore(); - } - - // Sample function for drawing a rect - 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(CanvasMatrixUtil.createPerspectiveMatrix(modelMatrices.get(0), - viewMatrix, projectionMatrix, viewportWidth, viewportHeight)); - RectF rect = new RectF(0, 0, 0.2f, 0.2f); - canvas.drawRect(rect, p); - canvas.restore(); - } - - // Sample function for drawing text on a canvas - 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 = CanvasMatrixUtil.createXYtoXZRotationMatrix(); - float[][] matrices = { scaleMatrix, rotateMatrix, modelMatrices.get(0), viewMatrix, - projectionMatrix, - CanvasMatrixUtil.createViewportMatrix(viewportWidth, viewportHeight)}; - - canvas.save(); - canvas.setMatrix(CanvasMatrixUtil.createMatrixFrom4x4(CanvasMatrixUtil.multiplyMatrices4x4(matrices))); - canvas.drawText(text, 0, 0, p); - canvas.restore(); - } - - public void drawFingerPainting(Canvas canvas) { - // Build the path before rendering - fingerPainting.buildPath(); - - // If path empty, return - if (fingerPainting.getPaths().isEmpty()) { - return; - } - - // Get finger painting model matrix - float[] model = fingerPainting.getModelMatrix(); - float[] in = new float[16]; - Matrix.setIdentityM(in, 0); - Matrix.translateM(in, 0, model[12], model[13], model[14]); - - float[] initRot = CanvasMatrixUtil.createXYtoXZRotationMatrix(); - - float[] scale = new float[16]; - float s = 0.001f; - Matrix.setIdentityM(scale, 0); - Matrix.scaleM(scale, 0, s, s, s); - - // Matrix = mvpv - float[][] matrices = {scale, initRot, in, viewMatrix, projectionMatrix, CanvasMatrixUtil.createViewportMatrix(viewportWidth, viewportHeight)}; - android.graphics.Matrix mvpv = CanvasMatrixUtil.createMatrixFrom4x4(CanvasMatrixUtil.multiplyMatrices4x4(matrices)); - - // Paint set up - Paint p = new Paint(); - p.setStyle(Paint.Style.STROKE); - p.setStrokeWidth(30f); - p.setAlpha(120); - - for (Path path : fingerPainting.getPaths()) { - if (path.isEmpty()) { - continue; - } - p.setColor(fingerPainting.getPathColor(path)); - - // Scaling issues appear to happen when drawing a Path and transforming the Canvas - // directly with a matrix on Android versions less than P. Ideally we would - // switch true to be (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) - - if (true) { - // Transform applied through canvas - canvas.save(); - canvas.setMatrix(mvpv); - canvas.drawPath(path, p); - canvas.restore(); - } else { - // Transform path directly - Path pathDst = new Path(); - path.transform(mvpv, pathDst); - - // Draw dest path - canvas.save(); - canvas.setMatrix(new android.graphics.Matrix()); - canvas.drawPath(pathDst, p); - canvas.restore(); - } - } - - } - - // Sample function for drawing the AR point cloud - public void drawPointCloud(Canvas canvas, PointCloud cloud) { - FloatBuffer points = cloud.getPoints(); - int numberOfPoints = points.remaining() / 4; - - float[][] matrices = {viewMatrix, projectionMatrix, CanvasMatrixUtil.createViewportMatrix(viewportWidth, viewportHeight)}; - float[] vpv = CanvasMatrixUtil.multiplyMatrices4x4(matrices); - - float[] pointsToDraw = new float[numberOfPoints * 2]; - for (int i = 0; i < numberOfPoints; i++) { - float[] point = {points.get(i * 4), points.get(i * 4 + 1), points.get(i * 4 + 2), 1}; - float[] result = CanvasMatrixUtil.multiplyMatrixVector(vpv, point, true); - pointsToDraw[i * 2] = result[0]; - pointsToDraw[i * 2 + 1] = result[1]; - } - - Paint p = new Paint(); - p.setARGB(220, 20, 232, 255); - p.setStrokeCap(Paint.Cap.SQUARE); - p.setStrokeWidth(6.0f); - - canvas.save(); - float[] id = new float[16]; - Matrix.setIdentityM(id, 0); - android.graphics.Matrix identity = CanvasMatrixUtil.createMatrixFrom4x4(id); - canvas.setMatrix(identity); - canvas.drawPoints(pointsToDraw, p); - canvas.restore(); - } - - - // Sample function for drawing AR planes - public void drawPlanes(Canvas canvas, Pose cameraPose, Collection allPlanes) { - if (allPlanes.size() <= 0) { - return; - } - - for (Plane plane : allPlanes) { - Plane subsumePlane = plane.getSubsumedBy(); - if (plane.getTrackingState() != TrackingState.TRACKING || subsumePlane != null) { - continue; - } - - float distance = calculateDistanceToPlane(plane.getCenterPose(), cameraPose); - if (distance < 0) { // Plane is back-facing. - continue; - } - - // Get plane model matrix - float[] model = new float[16]; - plane.getCenterPose().toMatrix(model, 0); - - // Initial rotation - float[] initRot = CanvasMatrixUtil.createXYtoXZRotationMatrix(); - - // Matrix = mvpv - float[][] matrices = {initRot, model, viewMatrix, projectionMatrix, CanvasMatrixUtil.createViewportMatrix(viewportWidth, viewportHeight)}; - android.graphics.Matrix mvpv = CanvasMatrixUtil.createMatrixFrom4x4(CanvasMatrixUtil.multiplyMatrices4x4(matrices)); - - drawPlaneAsPath(canvas, mvpv, plane); - } - } - - // Helper function that draws an AR plane using a path - private void drawPlaneAsPath(Canvas canvas, android.graphics.Matrix mvpv, Plane plane) { - int vertsSize = plane.getPolygon().limit() / 2; - FloatBuffer polygon = plane.getPolygon(); - polygon.rewind(); - - // Build source path from polygon data - Path pathSrc = new Path(); - pathSrc.moveTo(polygon.get(0), polygon.get(1)); - for (int i = 1; i < vertsSize; i++) { - pathSrc.lineTo(polygon.get(i * 2), polygon.get(i * 2 + 1)); - } - pathSrc.close(); - - // Set up paint - Paint p = new Paint(); - - if (false) { - //p.setShader(planeShader); - p.setColorFilter(new PorterDuffColorFilter(Color.argb(0.4f, 1, 0, 0), - PorterDuff.Mode.SRC_ATOP)); - } - - p.setColor(Color.RED); - p.setAlpha(100); - p.setStrokeWidth(0.01f); - p.setStyle(Paint.Style.STROKE); - - - if (true) { - // Shader local matrix - android.graphics.Matrix lm = new android.graphics.Matrix(); - lm.setScale(0.00005f, 0.00005f); - planeShader.setLocalMatrix(lm); - // Draw dest path - canvas.save(); - canvas.setMatrix(mvpv); - canvas.drawPath(pathSrc, p); - canvas.restore(); - } else { - // Build destination path by transforming source path - Path pathDst = new Path(); - pathSrc.transform(mvpv, pathDst); - - // Shader local matrix - android.graphics.Matrix lm = new android.graphics.Matrix(); - lm.setScale(0.00005f, 0.00005f); - lm.postConcat(mvpv); - planeShader.setLocalMatrix(lm); - - // Draw dest path - canvas.save(); - canvas.setMatrix(new android.graphics.Matrix()); - canvas.drawPath(pathDst, p); - canvas.restore(); - } - } - - public void initializePlaneShader(Context context, String gridDistanceTextureName) throws IOException { - // Read the texture. - Bitmap planeTexture = - BitmapFactory.decodeStream(context.getAssets().open(gridDistanceTextureName)); - // Set up the shader - planeShader = new BitmapShader(planeTexture, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); - planeShader.setLocalMatrix(new android.graphics.Matrix()); - } - - 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; - } - - public static float calculateDistanceToPlane(Pose planePose, Pose cameraPose) { - float[] normal = new float[3]; - float cameraX = cameraPose.tx(); - float cameraY = cameraPose.ty(); - float cameraZ = cameraPose.tz(); - // Get transformed Y axis of plane's coordinate system. - planePose.getTransformedAxis(1, 1.0f, normal, 0); - // Compute dot product of plane's normal with vector from camera to plane center. - return (cameraX - planePose.tx()) * normal[0] - + (cameraY - planePose.ty()) * normal[1] - + (cameraZ - planePose.tz()) * normal[2]; - } -} diff --git a/platform_tools/android/apps/skar_java/src/main/java/com/google/ar/core/examples/java/helloskar/HelloCanvasAR.java b/platform_tools/android/apps/skar_java/src/main/java/com/google/ar/core/examples/java/helloskar/HelloCanvasAR.java deleted file mode 100644 index cc4ad93b99..0000000000 --- a/platform_tools/android/apps/skar_java/src/main/java/com/google/ar/core/examples/java/helloskar/HelloCanvasAR.java +++ /dev/null @@ -1,553 +0,0 @@ -/* - * 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.animation.PropertyValuesHolder; -import android.animation.ValueAnimator; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.PointF; -import android.graphics.PorterDuff; -import android.opengl.GLES20; -import android.opengl.GLSurfaceView; -import android.opengl.Matrix; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.design.internal.BottomNavigationMenuView; -import android.support.design.widget.BottomNavigationView; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; -import android.util.Log; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.SurfaceHolder; -import android.view.View; -import android.view.WindowManager; -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.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 HelloCanvasAR extends AppCompatActivity implements GLSurfaceView.Renderer { - public enum DrawingType { - circle, rect, text, animation - } - - private static final String TAG = HelloCanvasAR.class.getSimpleName(); - - //Simple SurfaceView used to draw 2D objects on top of the GLSurfaceView - private ARSurfaceView arSurfaceView; - private Canvas canvas; - private SurfaceHolder holder; - - //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; - - // OpenGL background renderer - private final BackgroundRenderer backgroundRenderer = new BackgroundRenderer(); - - // 2D Renderer - private DrawManager drawManager = new DrawManager(); - private DrawingType currentDrawabletype = DrawingType.circle; - private boolean drawSmoothPainting = true; - - // Temporary matrix allocated here to reduce number of allocations for each frame. - private final float[] anchorMatrix = new float[16]; - - PointF previousEvent; - - // Anchors created from taps used for object placing. - private final ArrayList anchors = new ArrayList<>(); - - // Animation fields - float radius; - String PROPERTY_RADIUS = "radius"; - ValueAnimator animator; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - - Toolbar myToolbar = (Toolbar) findViewById(R.id.my_toolbar); - setSupportActionBar(myToolbar); - - - //hide notifications bar - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - - arSurfaceView = findViewById(R.id.arsurfaceview); - glSurfaceView = findViewById(R.id.glsurfaceview); - arSurfaceView.bringToFront(); - arSurfaceView.setLayerType(View.LAYER_TYPE_HARDWARE, null); - 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; - - BottomNavigationView bottomNav = findViewById(R.id.palette); - bottomNav.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() { - @Override - public boolean onNavigationItemSelected(@NonNull MenuItem item) { - switch (item.getItemId()) { - case R.id.palette_green: - drawManager.fingerPainting.setColor(Color.GREEN); - return true; - case R.id.palette_red: - drawManager.fingerPainting.setColor(Color.RED); - return true; - case R.id.palette_reset: - drawManager.fingerPainting.reset(); - return true; - default: - return true; - } - } - }); - - // Animator set up - PropertyValuesHolder propertyRadius = PropertyValuesHolder.ofFloat(PROPERTY_RADIUS, 0, 0.5f); - animator = new ValueAnimator(); - animator.setValues(propertyRadius); - animator.setDuration(1000); - animator.setRepeatCount(ValueAnimator.INFINITE); - animator.setRepeatMode(ValueAnimator.REVERSE); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - radius = (float) animation.getAnimatedValue(PROPERTY_RADIUS); - } - }); - animator.start(); - } - - @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( this); - drawManager.initializePlaneShader(this, "models/trigrid.png"); - } 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.updateViewport(width, height); - } - - @Override - public void onDrawFrame(GL10 gl) { - canvas = null; - holder = null; - - // 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()) - && (DrawManager.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 with OpenGL. - // TODO: possibly find a way to extract texture and draw on Canvas - 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); - - // Building finger painting - TapHelper.ScrollEvent holdTap = tapHelper.holdPoll(); - if (holdTap != null && camera.getTrackingState() == TrackingState.TRACKING) { - for (HitResult hit : frame.hitTest(holdTap.e)) { - // 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()) - && (DrawManager.calculateDistanceToPlane(hit.getHitPose(), camera.getPose()) - > 0)) - || (trackable instanceof Point - && ((Point) trackable).getOrientationMode() - == OrientationMode.ESTIMATED_SURFACE_NORMAL)) { - - // Get hit point transform, apply it to the origin - float[] gm = new float[16]; - hit.getHitPose().toMatrix(gm, 0); - float[] point = {0, 0, 0, 1}; - Matrix.multiplyMV(point, 0, gm, 0, point, 0); - - if (drawManager.fingerPainting.isEmpty()) { - drawManager.fingerPainting.addPoint(new PointF(0, 0), true); - - // Get model matrix of first point - float[] m = new float[16]; - hit.getHitPose().toMatrix(m, 0); - drawManager.fingerPainting.setModelMatrix(m); - } else { - float localDistanceScale = 1000; - PointF distance = new PointF(point[0] - previousEvent.x, - point[2] - previousEvent.y); - - if (distance.length() < 0.05f) { - continue; - } - - // New point is distance + old point - PointF p = new PointF(distance.x * localDistanceScale - + drawManager.fingerPainting.previousPoint.x, - distance.y * localDistanceScale - + drawManager.fingerPainting.previousPoint.y); - - drawManager.fingerPainting.addPoint(p, holdTap.isStartOfScroll); - } - - previousEvent = new PointF(point[0], point[2]); - break; - } - } - } - - // Drawing on Canvas (SurfaceView) - if (arSurfaceView.isRunning()) { - // Lock canvas - SurfaceHolder holder = arSurfaceView.getHolder(); - Canvas canvas = holder.lockHardwareCanvas(); - canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); - - // Draw point cloud - PointCloud pointCloud = frame.acquirePointCloud(); - drawPointCloud(canvas, pointCloud); - pointCloud.release(); - - // Draw planes - // 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; - } - } - } - - // Draw planes - drawPlanes(canvas, camera); - - // Draw models - drawModels(canvas); - - // Draw finger painting - drawFingerPainting(canvas); - - // Unlock canvas - holder.unlockCanvasAndPost(canvas); - } - } catch (Throwable t) { - // Avoid crashing the application due to unhandled exceptions. - if (holder != null && canvas != null) { - holder.unlockCanvasAndPost(canvas); - } - Log.e(TAG, "Exception on the OpenGL thread", t); - } - } - - // Helper drawing functions that invoke drawManager - private void drawPlanes(Canvas canvas, Camera camera) { - drawManager.drawPlanes(canvas, camera.getPose(), session.getAllTrackables(Plane.class)); - } - - private void drawPointCloud(Canvas canvas, PointCloud cloud) { - drawManager.drawPointCloud(canvas, cloud); - } - - private void drawModels(Canvas canvas) { - 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); - - switch (currentDrawabletype) { - case circle: - drawManager.drawCircle(canvas); - break; - case rect: - drawManager.drawRect(canvas); - break; - case animation: - drawManager.drawAnimatedRoundRect(canvas, radius); - break; - case text: - drawManager.drawText(canvas, "Android"); - break; - default: - drawManager.drawCircle(canvas); - break; - } - } - } - - private void drawFingerPainting(Canvas canvas) { - drawManager.fingerPainting.setSmoothness(drawSmoothPainting); - drawManager.drawFingerPainting(canvas); - } - - // Menu functions - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.main_menu, menu); - return true; - } - - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.reset_paint: - drawManager.fingerPainting.reset(); - return true; - case R.id.smooth_paint: - drawSmoothPainting = true; - return true; - case R.id.rough_paint: - drawSmoothPainting = false; - return true; - case R.id.draw_circle: - currentDrawabletype = DrawingType.circle; - return true; - case R.id.draw_rect: - currentDrawabletype = DrawingType.rect; - return true; - case R.id.draw_animation: - currentDrawabletype = DrawingType.animation; - return true; - case R.id.draw_text: - currentDrawabletype = DrawingType.text; - return true; - default: - return super.onOptionsItemSelected(item); - } - } -} diff --git a/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/SkARFingerPainting.java b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/SkARFingerPainting.java deleted file mode 100644 index d63295857d..0000000000 --- a/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/SkARFingerPainting.java +++ /dev/null @@ -1,155 +0,0 @@ -package com.google.skar; - -import android.graphics.Color; -import android.graphics.Path; -import android.graphics.PointF; -import android.util.Log; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -import java.util.ArrayList; - -public class SkARFingerPainting { - // Points obtained by touching the screen. The first point is always brough to (0,0). - // All subsequent points are translated by the same amount. - private ArrayList points = new ArrayList<>(); - private ArrayList jumpPoints = new ArrayList<>(); - private Map indexColors = new HashMap<>(); - private Map pathColors = new HashMap<>(); - private ArrayList paths = new ArrayList<>(); - private int color = Color.RED; - - // Previous point added to the path. This points belongs to the path in local space. - public PointF previousPoint; - - // Holds the model matrix of the first point added to such that the path can be drawn at the - // model location (i.e on the Plane) - private float[] modelMatrix; - - private boolean isSmooth; - - public SkARFingerPainting(boolean smooth) { - this.isSmooth = smooth; - } - - public boolean getSmoothness() { - return isSmooth; - } - - public void setSmoothness(boolean smooth) { - isSmooth = smooth; - } - - // Adds another point to the path in Local space - public void addPoint(PointF p, boolean jumpPoint) { - points.add(p); - if (jumpPoint) { - Log.i("Jumped!", Integer.toString(points.size() - 1)); - jumpPoints.add(points.size() - 1); - indexColors.put(points.size() - 1, color); - } - previousPoint = p; - } - - // Used to build a path before rendering it - public void buildPath() { - paths = new ArrayList<>(); - if (points.size() <= 1) { - return; - } - - - if (isSmooth) { - int start = 0; - for (int j = 1; j < jumpPoints.size(); j++) { - - int finish = jumpPoints.get(j); - buildSmoothFromTo(start, finish); - start = finish; - } - - buildSmoothFromTo(start, points.size()); - } else { - - int start = 0; - for (int j = 1; j < jumpPoints.size(); j++) { - int finish = jumpPoints.get(j); - buildRoughFromTo(start, finish); - start = finish; - } - - buildRoughFromTo(start, points.size()); - } - } - - private void buildRoughFromTo(int start, int finish) { - Path p = new Path(); - int c = indexColors.get(start); - p.moveTo(points.get(start).x, points.get(start).y); - for (int i = start + 1; i < finish; i++) { - p.lineTo(points.get(i).x, points.get(i).y); - } - paths.add(p); - pathColors.put(p, c); - } - - private void buildSmoothFromTo(int start, int finish) { - Path p = new Path(); - int c = indexColors.get(start); - int nbPts = finish - start; - // If less than 3 points, than draw a line between the two points - if (nbPts <= 2 && nbPts > 1) { - p.moveTo(points.get(start).x, points.get(start).y); - p.lineTo(points.get(start + 1).x, points.get(start + 1).y); - } else if (nbPts >= 3){ - // Else, essentially run deCasteljau - p.moveTo(points.get(start).x, points.get(start).y); - p.lineTo((points.get(start).x + points.get(start + 1).x) / 2, - (points.get(start).y + points.get(start + 1).y) / 2); - - for (int i = start + 1; i < finish - 1; i++) { - PointF p1 = points.get(i); - PointF p2 = points.get(i + 1); - p.quadTo(p1.x, p1.y, (p1.x + p2.x) / 2, (p1.y + p2.y) / 2); - } - - p.lineTo(points.get(finish - 1).x, points.get(finish - 1).y); - } - paths.add(p); - pathColors.put(p, c); - } - - public boolean isEmpty() { - return points.isEmpty(); - } - - public float[] getModelMatrix() { - return modelMatrix; - } - - public void setModelMatrix(float[] m) { - modelMatrix = m; - } - - public void setColor(int color) { - this.color = color; - } - - public int getPathColor(Path p) { - return pathColors.get(p); - } - - public ArrayList getPaths() { - return paths; - } - - public void reset() { - points.clear(); - jumpPoints.clear(); - paths.clear(); - pathColors.clear(); - indexColors.clear(); - } -} diff --git a/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/app/ARSurfaceView.java b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/app/ARSurfaceView.java new file mode 100644 index 0000000000..03185c2cd3 --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/app/ARSurfaceView.java @@ -0,0 +1,45 @@ +package com.google.skar.examples.helloskar.app; + +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/skar/examples/helloskar/app/HelloCanvasAR.java b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/app/HelloCanvasAR.java new file mode 100644 index 0000000000..718545e72b --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/app/HelloCanvasAR.java @@ -0,0 +1,553 @@ +/* + * 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.skar.examples.helloskar.app; + +import android.animation.PropertyValuesHolder; +import android.animation.ValueAnimator; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.PointF; +import android.graphics.PorterDuff; +import android.opengl.GLES20; +import android.opengl.GLSurfaceView; +import android.opengl.Matrix; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.design.internal.BottomNavigationMenuView; +import android.support.design.widget.BottomNavigationView; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.SurfaceHolder; +import android.view.View; +import android.view.WindowManager; +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.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 HelloCanvasAR extends AppCompatActivity implements GLSurfaceView.Renderer { + public enum DrawingType { + circle, rect, text, animation + } + + private static final String TAG = HelloCanvasAR.class.getSimpleName(); + + //Simple SurfaceView used to draw 2D objects on top of the GLSurfaceView + private ARSurfaceView arSurfaceView; + private Canvas canvas; + private SurfaceHolder holder; + + //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; + + // OpenGL background renderer + private final BackgroundRenderer backgroundRenderer = new BackgroundRenderer(); + + // 2D Renderer + private DrawManager drawManager = new DrawManager(); + private DrawingType currentDrawabletype = DrawingType.circle; + private boolean drawSmoothPainting = true; + + // Temporary matrix allocated here to reduce number of allocations for each frame. + private final float[] anchorMatrix = new float[16]; + + PointF previousEvent; + + // Anchors created from taps used for object placing. + private final ArrayList anchors = new ArrayList<>(); + + // Animation fields + float radius; + String PROPERTY_RADIUS = "radius"; + ValueAnimator animator; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + Toolbar myToolbar = (Toolbar) findViewById(R.id.my_toolbar); + setSupportActionBar(myToolbar); + + + //hide notifications bar + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + + arSurfaceView = findViewById(R.id.arsurfaceview); + glSurfaceView = findViewById(R.id.glsurfaceview); + arSurfaceView.bringToFront(); + arSurfaceView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + 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; + + BottomNavigationView bottomNav = findViewById(R.id.palette); + bottomNav.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() { + @Override + public boolean onNavigationItemSelected(@NonNull MenuItem item) { + switch (item.getItemId()) { + case R.id.palette_green: + drawManager.fingerPainting.setColor(Color.GREEN); + return true; + case R.id.palette_red: + drawManager.fingerPainting.setColor(Color.RED); + return true; + case R.id.palette_reset: + drawManager.fingerPainting.reset(); + return true; + default: + return true; + } + } + }); + + // Animator set up + PropertyValuesHolder propertyRadius = PropertyValuesHolder.ofFloat(PROPERTY_RADIUS, 0, 0.5f); + animator = new ValueAnimator(); + animator.setValues(propertyRadius); + animator.setDuration(1000); + animator.setRepeatCount(ValueAnimator.INFINITE); + animator.setRepeatMode(ValueAnimator.REVERSE); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + radius = (float) animation.getAnimatedValue(PROPERTY_RADIUS); + } + }); + animator.start(); + } + + @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( this); + drawManager.initializePlaneShader(this, "models/trigrid.png"); + } 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.updateViewport(width, height); + } + + @Override + public void onDrawFrame(GL10 gl) { + canvas = null; + holder = null; + + // 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()) + && (DrawManager.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 with OpenGL. + // TODO: possibly find a way to extract texture and draw on Canvas + 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); + + // Building finger painting + TapHelper.ScrollEvent holdTap = tapHelper.holdPoll(); + if (holdTap != null && camera.getTrackingState() == TrackingState.TRACKING) { + for (HitResult hit : frame.hitTest(holdTap.e)) { + // 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()) + && (DrawManager.calculateDistanceToPlane(hit.getHitPose(), camera.getPose()) + > 0)) + || (trackable instanceof Point + && ((Point) trackable).getOrientationMode() + == OrientationMode.ESTIMATED_SURFACE_NORMAL)) { + + // Get hit point transform, apply it to the origin + float[] gm = new float[16]; + hit.getHitPose().toMatrix(gm, 0); + float[] point = {0, 0, 0, 1}; + Matrix.multiplyMV(point, 0, gm, 0, point, 0); + + if (drawManager.fingerPainting.isEmpty()) { + drawManager.fingerPainting.addPoint(new PointF(0, 0), true); + + // Get model matrix of first point + float[] m = new float[16]; + hit.getHitPose().toMatrix(m, 0); + drawManager.fingerPainting.setModelMatrix(m); + } else { + float localDistanceScale = 1000; + PointF distance = new PointF(point[0] - previousEvent.x, + point[2] - previousEvent.y); + + if (distance.length() < 0.05f) { + continue; + } + + // New point is distance + old point + PointF p = new PointF(distance.x * localDistanceScale + + drawManager.fingerPainting.previousPoint.x, + distance.y * localDistanceScale + + drawManager.fingerPainting.previousPoint.y); + + drawManager.fingerPainting.addPoint(p, holdTap.isStartOfScroll); + } + + previousEvent = new PointF(point[0], point[2]); + break; + } + } + } + + // Drawing on Canvas (SurfaceView) + if (arSurfaceView.isRunning()) { + // Lock canvas + SurfaceHolder holder = arSurfaceView.getHolder(); + Canvas canvas = holder.lockHardwareCanvas(); + canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); + + // Draw point cloud + PointCloud pointCloud = frame.acquirePointCloud(); + drawPointCloud(canvas, pointCloud); + pointCloud.release(); + + // Draw planes + // 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; + } + } + } + + // Draw planes + drawPlanes(canvas, camera); + + // Draw models + drawModels(canvas); + + // Draw finger painting + drawFingerPainting(canvas); + + // Unlock canvas + holder.unlockCanvasAndPost(canvas); + } + } catch (Throwable t) { + // Avoid crashing the application due to unhandled exceptions. + if (holder != null && canvas != null) { + holder.unlockCanvasAndPost(canvas); + } + Log.e(TAG, "Exception on the OpenGL thread", t); + } + } + + // Helper drawing functions that invoke drawManager + private void drawPlanes(Canvas canvas, Camera camera) { + drawManager.drawPlanes(canvas, camera.getPose(), session.getAllTrackables(Plane.class)); + } + + private void drawPointCloud(Canvas canvas, PointCloud cloud) { + drawManager.drawPointCloud(canvas, cloud); + } + + private void drawModels(Canvas canvas) { + 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); + + switch (currentDrawabletype) { + case circle: + drawManager.drawCircle(canvas); + break; + case rect: + drawManager.drawRect(canvas); + break; + case animation: + drawManager.drawAnimatedRoundRect(canvas, radius); + break; + case text: + drawManager.drawText(canvas, "Android"); + break; + default: + drawManager.drawCircle(canvas); + break; + } + } + } + + private void drawFingerPainting(Canvas canvas) { + drawManager.fingerPainting.setSmoothness(drawSmoothPainting); + drawManager.drawFingerPainting(canvas); + } + + // Menu functions + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.main_menu, menu); + return true; + } + + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.reset_paint: + drawManager.fingerPainting.reset(); + return true; + case R.id.smooth_paint: + drawSmoothPainting = true; + return true; + case R.id.rough_paint: + drawSmoothPainting = false; + return true; + case R.id.draw_circle: + currentDrawabletype = DrawingType.circle; + return true; + case R.id.draw_rect: + currentDrawabletype = DrawingType.rect; + return true; + case R.id.draw_animation: + currentDrawabletype = DrawingType.animation; + return true; + case R.id.draw_text: + currentDrawabletype = DrawingType.text; + return true; + default: + return super.onOptionsItemSelected(item); + } + } +} diff --git a/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/app/SkARFingerPainting.java b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/app/SkARFingerPainting.java new file mode 100644 index 0000000000..eac2a6bb33 --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/app/SkARFingerPainting.java @@ -0,0 +1,155 @@ +package com.google.skar.examples.helloskar.app; + +import android.graphics.Color; +import android.graphics.Path; +import android.graphics.PointF; +import android.util.Log; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import java.util.ArrayList; + +public class SkARFingerPainting { + // Points obtained by touching the screen. The first point is always brough to (0,0). + // All subsequent points are translated by the same amount. + private ArrayList points = new ArrayList<>(); + private ArrayList jumpPoints = new ArrayList<>(); + private Map indexColors = new HashMap<>(); + private Map pathColors = new HashMap<>(); + private ArrayList paths = new ArrayList<>(); + private int color = Color.RED; + + // Previous point added to the path. This points belongs to the path in local space. + public PointF previousPoint; + + // Holds the model matrix of the first point added to such that the path can be drawn at the + // model location (i.e on the Plane) + private float[] modelMatrix; + + private boolean isSmooth; + + public SkARFingerPainting(boolean smooth) { + this.isSmooth = smooth; + } + + public boolean getSmoothness() { + return isSmooth; + } + + public void setSmoothness(boolean smooth) { + isSmooth = smooth; + } + + // Adds another point to the path in Local space + public void addPoint(PointF p, boolean jumpPoint) { + points.add(p); + if (jumpPoint) { + Log.i("Jumped!", Integer.toString(points.size() - 1)); + jumpPoints.add(points.size() - 1); + indexColors.put(points.size() - 1, color); + } + previousPoint = p; + } + + // Used to build a path before rendering it + public void buildPath() { + paths = new ArrayList<>(); + if (points.size() <= 1) { + return; + } + + + if (isSmooth) { + int start = 0; + for (int j = 1; j < jumpPoints.size(); j++) { + + int finish = jumpPoints.get(j); + buildSmoothFromTo(start, finish); + start = finish; + } + + buildSmoothFromTo(start, points.size()); + } else { + + int start = 0; + for (int j = 1; j < jumpPoints.size(); j++) { + int finish = jumpPoints.get(j); + buildRoughFromTo(start, finish); + start = finish; + } + + buildRoughFromTo(start, points.size()); + } + } + + private void buildRoughFromTo(int start, int finish) { + Path p = new Path(); + int c = indexColors.get(start); + p.moveTo(points.get(start).x, points.get(start).y); + for (int i = start + 1; i < finish; i++) { + p.lineTo(points.get(i).x, points.get(i).y); + } + paths.add(p); + pathColors.put(p, c); + } + + private void buildSmoothFromTo(int start, int finish) { + Path p = new Path(); + int c = indexColors.get(start); + int nbPts = finish - start; + // If less than 3 points, than draw a line between the two points + if (nbPts <= 2 && nbPts > 1) { + p.moveTo(points.get(start).x, points.get(start).y); + p.lineTo(points.get(start + 1).x, points.get(start + 1).y); + } else if (nbPts >= 3){ + // Else, essentially run deCasteljau + p.moveTo(points.get(start).x, points.get(start).y); + p.lineTo((points.get(start).x + points.get(start + 1).x) / 2, + (points.get(start).y + points.get(start + 1).y) / 2); + + for (int i = start + 1; i < finish - 1; i++) { + PointF p1 = points.get(i); + PointF p2 = points.get(i + 1); + p.quadTo(p1.x, p1.y, (p1.x + p2.x) / 2, (p1.y + p2.y) / 2); + } + + p.lineTo(points.get(finish - 1).x, points.get(finish - 1).y); + } + paths.add(p); + pathColors.put(p, c); + } + + public boolean isEmpty() { + return points.isEmpty(); + } + + public float[] getModelMatrix() { + return modelMatrix; + } + + public void setModelMatrix(float[] m) { + modelMatrix = m; + } + + public void setColor(int color) { + this.color = color; + } + + public int getPathColor(Path p) { + return pathColors.get(p); + } + + public ArrayList getPaths() { + return paths; + } + + public void reset() { + points.clear(); + jumpPoints.clear(); + paths.clear(); + pathColors.clear(); + indexColors.clear(); + } +} diff --git a/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/CameraPermissionHelper.java b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/CameraPermissionHelper.java new file mode 100644 index 0000000000..f8e9c33af3 --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/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 com.google.skar.examples.helloskar.helpers; + +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 final class CameraPermissionHelper { + private static final int CAMERA_PERMISSION_CODE = 0; + private static final String CAMERA_PERMISSION = Manifest.permission.CAMERA; + + /** + * 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/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/DisplayRotationHelper.java b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/DisplayRotationHelper.java new file mode 100644 index 0000000000..7e4ce81c29 --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/DisplayRotationHelper.java @@ -0,0 +1,112 @@ +/* + * 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.skar.examples.helloskar.helpers; + +import android.app.Activity; +import android.content.Context; +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManager.DisplayListener; +import android.view.Display; +import android.view.WindowManager; + +import com.google.ar.core.Session; + +/** + * Helper to track the display rotations. In particular, the 180 degree rotations are not notified + * by the onSurfaceChanged() callback, and thus they require listening to the android display + * events. + */ +public final class DisplayRotationHelper implements DisplayListener { + private boolean viewportChanged; + private int viewportWidth; + private int viewportHeight; + private final Context context; + private final Display display; + + /** + * Constructs the DisplayRotationHelper but does not register the listener yet. + * + * @param context the Android {@link Context}. + */ + public DisplayRotationHelper(Context context) { + this.context = context; + display = context.getSystemService(WindowManager.class).getDefaultDisplay(); + } + + /** + * Registers the display listener. Should be called from {@link Activity#onResume()}. + */ + public void onResume() { + context.getSystemService(DisplayManager.class).registerDisplayListener(this, null); + } + + /** + * Unregisters the display listener. Should be called from {@link Activity#onPause()}. + */ + public void onPause() { + context.getSystemService(DisplayManager.class).unregisterDisplayListener(this); + } + + /** + * Records a change in surface dimensions. This will be later used by {@link + * #updateSessionIfNeeded(Session)}. Should be called from {@link + * android.opengl.GLSurfaceView.Renderer + * #onSurfaceChanged(javax.microedition.khronos.opengles.GL10, int, int)}. + * + * @param width the updated width of the surface. + * @param height the updated height of the surface. + */ + public void onSurfaceChanged(int width, int height) { + viewportWidth = width; + viewportHeight = height; + viewportChanged = true; + } + + /** + * Updates the session display geometry if a change was posted either by {@link + * #onSurfaceChanged(int, int)} call or by {@link #onDisplayChanged(int)} system callback. This + * function should be called explicitly before each call to {@link Session#update()}. This + * function will also clear the 'pending update' (viewportChanged) flag. + * + * @param session the {@link Session} object to update if display geometry changed. + */ + public void updateSessionIfNeeded(Session session) { + if (viewportChanged) { + int displayRotation = display.getRotation(); + session.setDisplayGeometry(displayRotation, viewportWidth, viewportHeight); + viewportChanged = false; + } + } + + /** + * Returns the current rotation state of android display. Same as {@link Display#getRotation()}. + */ + public int getRotation() { + return display.getRotation(); + } + + @Override + public void onDisplayAdded(int displayId) { + } + + @Override + public void onDisplayRemoved(int displayId) { + } + + @Override + public void onDisplayChanged(int displayId) { + viewportChanged = true; + } +} diff --git a/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/FullScreenHelper.java b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/FullScreenHelper.java new file mode 100644 index 0000000000..3738b963dc --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/FullScreenHelper.java @@ -0,0 +1,49 @@ +/* + * 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.skar.examples.helloskar.helpers; + +import android.app.Activity; +import android.view.View; +import android.view.WindowManager; + +/** + * Helper to set up the Android full screen mode. + */ +public final class FullScreenHelper { + /** + * Sets the Android fullscreen flags. Expected to be called from {@link + * Activity#onWindowFocusChanged(boolean hasFocus)}. + * + * @param activity the Activity on which the full screen mode will be set. + * @param hasFocus the hasFocus flag passed from the {@link Activity#onWindowFocusChanged(boolean + * hasFocus)} callback. + */ + public static void setFullScreenOnWindowFocusChanged(Activity activity, boolean hasFocus) { + if (hasFocus) { + // https://developer.android.com/training/system-ui/immersive.html#sticky + activity + .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); + activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + } +} diff --git a/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/SnackbarHelper.java b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/SnackbarHelper.java new file mode 100644 index 0000000000..02393499e9 --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/SnackbarHelper.java @@ -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. + */ +package com.google.skar.examples.helloskar.helpers; + +import android.app.Activity; +import android.support.design.widget.BaseTransientBottomBar; +import android.support.design.widget.Snackbar; +import android.view.View; + +/** + * Helper to manage the sample snackbar. Hides the Android boilerplate code, and exposes simpler + * methods. + */ +public final class SnackbarHelper { + private static final int BACKGROUND_COLOR = 0xbf323232; + private Snackbar messageSnackbar; + + private enum DismissBehavior {HIDE, SHOW, FINISH} + + ; + + public boolean isShowing() { + return messageSnackbar != null; + } + + /** + * Shows a snackbar with a given message. + */ + public void showMessage(Activity activity, String message) { + show(activity, message, DismissBehavior.HIDE); + } + + /** + * Shows a snackbar with a given message, and a dismiss button. + */ + public void showMessageWithDismiss(Activity activity, String message) { + show(activity, message, DismissBehavior.SHOW); + } + + /** + * Shows a snackbar with a given error message. When dismissed, will finish the activity. Useful + * for notifying errors, where no further interaction with the activity is possible. + */ + public void showError(Activity activity, String errorMessage) { + show(activity, errorMessage, DismissBehavior.FINISH); + } + + /** + * Hides the currently showing snackbar, if there is one. Safe to call from any thread. Safe to + * call even if snackbar is not shown. + */ + public void hide(Activity activity) { + activity.runOnUiThread( + new Runnable() { + @Override + public void run() { + if (messageSnackbar != null) { + messageSnackbar.dismiss(); + } + messageSnackbar = null; + } + }); + } + + private void show( + final Activity activity, final String message, final DismissBehavior dismissBehavior) { + activity.runOnUiThread( + new Runnable() { + @Override + public void run() { + messageSnackbar = + Snackbar.make( + activity.findViewById(android.R.id.content), + message, + Snackbar.LENGTH_INDEFINITE); + messageSnackbar.getView().setBackgroundColor(BACKGROUND_COLOR); + if (dismissBehavior != DismissBehavior.HIDE) { + messageSnackbar.setAction( + "Dismiss", + new View.OnClickListener() { + @Override + public void onClick(View v) { + messageSnackbar.dismiss(); + } + }); + if (dismissBehavior == DismissBehavior.FINISH) { + messageSnackbar.addCallback( + new BaseTransientBottomBar.BaseCallback() { + @Override + public void onDismissed(Snackbar transientBottomBar, int event) { + super.onDismissed(transientBottomBar, event); + activity.finish(); + } + }); + } + } + messageSnackbar.show(); + } + }); + } +} diff --git a/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/TapHelper.java b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/TapHelper.java new file mode 100644 index 0000000000..bcbfe2aa98 --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/TapHelper.java @@ -0,0 +1,116 @@ +/* + * 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.skar.examples.helloskar.helpers; + +import android.content.Context; +import android.util.Log; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnTouchListener; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +/** + * Helper to detect taps using Android GestureDetector, and pass the taps between UI thread and + * render thread. + */ +public final class TapHelper implements OnTouchListener { + private final GestureDetector gestureDetector; + private final BlockingQueue queuedSingleTaps = new ArrayBlockingQueue<>(16); + private final BlockingQueue queuedFingerHold = new ArrayBlockingQueue<>(16); + private boolean isScrolling = false; + private boolean previousScroll = true; + + public static class ScrollEvent { + public MotionEvent e; + public boolean isStartOfScroll; + + public ScrollEvent(MotionEvent e, boolean isStartOfScroll) { + this.e = e; + this.isStartOfScroll = isStartOfScroll; + } + } + + /** + * Creates the tap helper. + * + * @param context the application's context. + */ + public TapHelper(Context context) { + gestureDetector = + new GestureDetector( + context, + new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onSingleTapUp(MotionEvent e) { + // Queue tap if there is space. Tap is lost if queue is full. + queuedSingleTaps.offer(e); + return true; + } + + @Override + public boolean onScroll (MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + // Queue motion events when scrolling + if (e2.getPointerCount() == 1 && e1.getPointerCount() == 1) { + previousScroll = isScrolling; + isScrolling = true; + queuedFingerHold.offer(new ScrollEvent(e2, startedScrolling())); + return true; + } + return false; + } + + + @Override + public boolean onDown(MotionEvent e) { + return true; + } + }); + } + + /** + * Polls for a tap. + * + * @return if a tap was queued, a MotionEvent for the tap. Otherwise null if no taps are queued. + */ + public MotionEvent poll() { + return queuedSingleTaps.poll(); + } + + public ScrollEvent holdPoll() { return queuedFingerHold.poll(); } + + public boolean startedScrolling() { + return isScrolling && !previousScroll; + } + + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + boolean val = gestureDetector.onTouchEvent(motionEvent); + + // If finger is up + is scrolling: don't scroll anymore, and empty Touch Hold queue + if (motionEvent.getAction() == MotionEvent.ACTION_UP && isScrolling) { + previousScroll = true; + isScrolling = false; + queuedFingerHold.clear(); + } + return val; + } +} diff --git a/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/rendering/BackgroundRenderer.java b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/rendering/BackgroundRenderer.java new file mode 100644 index 0000000000..b6b5a859b8 --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/rendering/BackgroundRenderer.java @@ -0,0 +1,190 @@ +/* + * 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.skar.examples.helloskar.rendering; + +import android.content.Context; +import android.opengl.GLES11Ext; +import android.opengl.GLES20; +import android.opengl.GLSurfaceView; + +import com.google.ar.core.Frame; +import com.google.ar.core.Session; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; + +/** + * This class renders the AR background from camera feed. It creates and hosts the texture given to + * ARCore to be filled with the camera image. + */ +public class BackgroundRenderer { + private static final String TAG = BackgroundRenderer.class.getSimpleName(); + + // Shader names. + private static final String VERTEX_SHADER_NAME = "shaders/screenquad.vert"; + private static final String FRAGMENT_SHADER_NAME = "shaders/screenquad.frag"; + + private static final int COORDS_PER_VERTEX = 3; + private static final int TEXCOORDS_PER_VERTEX = 2; + private static final int FLOAT_SIZE = 4; + + private FloatBuffer quadVertices; + private FloatBuffer quadTexCoord; + private FloatBuffer quadTexCoordTransformed; + + private int quadProgram; + + private int quadPositionParam; + private int quadTexCoordParam; + private int textureId = -1; + + public BackgroundRenderer() { + } + + public int getTextureId() { + return textureId; + } + + /** + * Allocates and initializes OpenGL resources needed by the background renderer. Must be called on + * the OpenGL thread, typically in {@link GLSurfaceView.Renderer#onSurfaceCreated(GL10, + * EGLConfig)}. + * + * @param context Needed to access shader source. + */ + public void createOnGlThread(Context context) throws IOException { + // Generate the background texture. + int[] textures = new int[1]; + GLES20.glGenTextures(1, textures, 0); + textureId = textures[0]; + int textureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES; + GLES20.glBindTexture(textureTarget, textureId); + GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); + GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST); + + int numVertices = 4; + if (numVertices != QUAD_COORDS.length / COORDS_PER_VERTEX) { + throw new RuntimeException("Unexpected number of vertices in BackgroundRenderer."); + } + + ByteBuffer bbVertices = ByteBuffer.allocateDirect(QUAD_COORDS.length * FLOAT_SIZE); + bbVertices.order(ByteOrder.nativeOrder()); + quadVertices = bbVertices.asFloatBuffer(); + quadVertices.put(QUAD_COORDS); + quadVertices.position(0); + + ByteBuffer bbTexCoords = + ByteBuffer.allocateDirect(numVertices * TEXCOORDS_PER_VERTEX * FLOAT_SIZE); + bbTexCoords.order(ByteOrder.nativeOrder()); + quadTexCoord = bbTexCoords.asFloatBuffer(); + quadTexCoord.put(QUAD_TEXCOORDS); + quadTexCoord.position(0); + + ByteBuffer bbTexCoordsTransformed = + ByteBuffer.allocateDirect(numVertices * TEXCOORDS_PER_VERTEX * FLOAT_SIZE); + bbTexCoordsTransformed.order(ByteOrder.nativeOrder()); + quadTexCoordTransformed = bbTexCoordsTransformed.asFloatBuffer(); + + int vertexShader = + ShaderUtil.loadGLShader(TAG, context, GLES20.GL_VERTEX_SHADER, VERTEX_SHADER_NAME); + int fragmentShader = + ShaderUtil.loadGLShader(TAG, context, GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_NAME); + + quadProgram = GLES20.glCreateProgram(); + GLES20.glAttachShader(quadProgram, vertexShader); + GLES20.glAttachShader(quadProgram, fragmentShader); + GLES20.glLinkProgram(quadProgram); + GLES20.glUseProgram(quadProgram); + + ShaderUtil.checkGLError(TAG, "Program creation"); + + quadPositionParam = GLES20.glGetAttribLocation(quadProgram, "a_Position"); + quadTexCoordParam = GLES20.glGetAttribLocation(quadProgram, "a_TexCoord"); + + ShaderUtil.checkGLError(TAG, "Program parameters"); + } + + /** + * Draws the AR background image. The image will be drawn such that virtual content rendered with + * the matrices provided by {@link com.google.ar.core.Camera#getViewMatrix(float[], int)} and + * {@link com.google.ar.core.Camera#getProjectionMatrix(float[], int, float, float)} will + * accurately follow static physical objects. This must be called before drawing virtual + * content. + * + * @param frame The last {@code Frame} returned by {@link Session#update()}. + */ + public void draw(Frame frame) { + // If display rotation changed (also includes view size change), we need to re-query the uv + // coordinates for the screen rect, as they may have changed as well. + if (frame.hasDisplayGeometryChanged()) { + frame.transformDisplayUvCoords(quadTexCoord, quadTexCoordTransformed); + } + + // No need to test or write depth, the screen quad has arbitrary depth, and is expected + // to be drawn first. + GLES20.glDisable(GLES20.GL_DEPTH_TEST); + GLES20.glDepthMask(false); + + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId); + + GLES20.glUseProgram(quadProgram); + + // Set the vertex positions. + GLES20.glVertexAttribPointer( + quadPositionParam, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0, quadVertices); + + // Set the texture coordinates. + GLES20.glVertexAttribPointer( + quadTexCoordParam, + TEXCOORDS_PER_VERTEX, + GLES20.GL_FLOAT, + false, + 0, + quadTexCoordTransformed); + + // Enable vertex arrays + GLES20.glEnableVertexAttribArray(quadPositionParam); + GLES20.glEnableVertexAttribArray(quadTexCoordParam); + + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); + + // Disable vertex arrays + GLES20.glDisableVertexAttribArray(quadPositionParam); + GLES20.glDisableVertexAttribArray(quadTexCoordParam); + + // Restore the depth state for further drawing. + GLES20.glDepthMask(true); + GLES20.glEnable(GLES20.GL_DEPTH_TEST); + + ShaderUtil.checkGLError(TAG, "Draw"); + } + + private static final float[] QUAD_COORDS = + new float[]{ + -1.0f, -1.0f, 0.0f, -1.0f, +1.0f, 0.0f, +1.0f, -1.0f, 0.0f, +1.0f, +1.0f, 0.0f, + }; + + private static final float[] QUAD_TEXCOORDS = + new float[]{ + 0.0f, 1.0f, + 0.0f, 0.0f, + 1.0f, 1.0f, + 1.0f, 0.0f, + }; +} diff --git a/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/rendering/DrawManager.java b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/rendering/DrawManager.java new file mode 100644 index 0000000000..2ef85b049e --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/rendering/DrawManager.java @@ -0,0 +1,349 @@ +package com.google.skar.examples.helloskar.rendering; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.RectF; +import android.graphics.Shader; +import android.opengl.Matrix; + +import com.google.ar.core.Plane; +import com.google.ar.core.PointCloud; +import com.google.ar.core.Pose; +import com.google.ar.core.TrackingState; +import com.google.skar.CanvasMatrixUtil; +import com.google.skar.PaintUtil; +import com.google.skar.SkARFingerPainting; +import java.io.IOException; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.Collection; + +/** + * Sample class that handles drawing different types of geometry using the matrices provided + * by ARCore. The matrices are handled by SkARMatrix in order to be passed to the drawing + * Canvas. + */ + +public class DrawManager { + private float[] projectionMatrix = new float[16]; + private float[] viewMatrix = new float[16]; + private float viewportWidth; + private float viewportHeight; + private ColorFilter lightFilter; + private BitmapShader planeShader; + public ArrayList modelMatrices = new ArrayList<>(); + public SkARFingerPainting fingerPainting = new SkARFingerPainting(false); + + public void updateViewport(float width, float height) { + viewportWidth = width; + viewportHeight = height; + } + + public void updateProjectionMatrix(float[] projectionMatrix) { + this.projectionMatrix = projectionMatrix; + } + + public void updateViewMatrix(float[] viewMatrix) { + this.viewMatrix = viewMatrix; + } + + public void updateLightColorFilter(float[] colorCorr) { + lightFilter = PaintUtil.createLightCorrectionColorFilter(colorCorr); + } + + // Sample function for drawing a circle + public void drawCircle(Canvas canvas) { + if (modelMatrices.isEmpty()) { + return; + } + Paint p = new Paint(); + p.setColorFilter(lightFilter); + p.setARGB(180, 100, 0, 0); + + canvas.save(); + android.graphics.Matrix m = CanvasMatrixUtil.createPerspectiveMatrix(modelMatrices.get(0), + viewMatrix, projectionMatrix, viewportWidth, viewportHeight); + canvas.setMatrix(m); + + canvas.drawCircle(0, 0, 0.1f, p); + canvas.restore(); + } + + // Sample function for drawing an animated round rect + public void drawAnimatedRoundRect(Canvas canvas, float radius) { + if (modelMatrices.isEmpty()) { + return; + } + Paint p = new Paint(); + p.setColorFilter(lightFilter); + p.setARGB(180, 100, 0, 100); + + canvas.save(); + canvas.setMatrix(CanvasMatrixUtil.createPerspectiveMatrix(modelMatrices.get(0), + viewMatrix, projectionMatrix, viewportWidth, viewportHeight)); + canvas.drawRoundRect(0,0, 0.5f, 0.5f, radius, radius, p); + canvas.restore(); + } + + // Sample function for drawing a rect + 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(CanvasMatrixUtil.createPerspectiveMatrix(modelMatrices.get(0), + viewMatrix, projectionMatrix, viewportWidth, viewportHeight)); + RectF rect = new RectF(0, 0, 0.2f, 0.2f); + canvas.drawRect(rect, p); + canvas.restore(); + } + + // Sample function for drawing text on a canvas + 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 = CanvasMatrixUtil.createXYtoXZRotationMatrix(); + float[][] matrices = { scaleMatrix, rotateMatrix, modelMatrices.get(0), viewMatrix, + projectionMatrix, + CanvasMatrixUtil.createViewportMatrix(viewportWidth, viewportHeight)}; + + canvas.save(); + canvas.setMatrix(CanvasMatrixUtil.createMatrixFrom4x4(CanvasMatrixUtil.multiplyMatrices4x4(matrices))); + canvas.drawText(text, 0, 0, p); + canvas.restore(); + } + + public void drawFingerPainting(Canvas canvas) { + // Build the path before rendering + fingerPainting.buildPath(); + + // If path empty, return + if (fingerPainting.getPaths().isEmpty()) { + return; + } + + // Get finger painting model matrix + float[] model = fingerPainting.getModelMatrix(); + float[] in = new float[16]; + Matrix.setIdentityM(in, 0); + Matrix.translateM(in, 0, model[12], model[13], model[14]); + + float[] initRot = CanvasMatrixUtil.createXYtoXZRotationMatrix(); + + float[] scale = new float[16]; + float s = 0.001f; + Matrix.setIdentityM(scale, 0); + Matrix.scaleM(scale, 0, s, s, s); + + // Matrix = mvpv + float[][] matrices = {scale, initRot, in, viewMatrix, projectionMatrix, CanvasMatrixUtil.createViewportMatrix(viewportWidth, viewportHeight)}; + android.graphics.Matrix mvpv = CanvasMatrixUtil.createMatrixFrom4x4(CanvasMatrixUtil.multiplyMatrices4x4(matrices)); + + // Paint set up + Paint p = new Paint(); + p.setStyle(Paint.Style.STROKE); + p.setStrokeWidth(30f); + p.setAlpha(120); + + for (Path path : fingerPainting.getPaths()) { + if (path.isEmpty()) { + continue; + } + p.setColor(fingerPainting.getPathColor(path)); + + // Scaling issues appear to happen when drawing a Path and transforming the Canvas + // directly with a matrix on Android versions less than P. Ideally we would + // switch true to be (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) + + if (true) { + // Transform applied through canvas + canvas.save(); + canvas.setMatrix(mvpv); + canvas.drawPath(path, p); + canvas.restore(); + } else { + // Transform path directly + Path pathDst = new Path(); + path.transform(mvpv, pathDst); + + // Draw dest path + canvas.save(); + canvas.setMatrix(new android.graphics.Matrix()); + canvas.drawPath(pathDst, p); + canvas.restore(); + } + } + + } + + // Sample function for drawing the AR point cloud + public void drawPointCloud(Canvas canvas, PointCloud cloud) { + FloatBuffer points = cloud.getPoints(); + int numberOfPoints = points.remaining() / 4; + + float[][] matrices = {viewMatrix, projectionMatrix, CanvasMatrixUtil.createViewportMatrix(viewportWidth, viewportHeight)}; + float[] vpv = CanvasMatrixUtil.multiplyMatrices4x4(matrices); + + float[] pointsToDraw = new float[numberOfPoints * 2]; + for (int i = 0; i < numberOfPoints; i++) { + float[] point = {points.get(i * 4), points.get(i * 4 + 1), points.get(i * 4 + 2), 1}; + float[] result = CanvasMatrixUtil.multiplyMatrixVector(vpv, point, true); + pointsToDraw[i * 2] = result[0]; + pointsToDraw[i * 2 + 1] = result[1]; + } + + Paint p = new Paint(); + p.setARGB(220, 20, 232, 255); + p.setStrokeCap(Paint.Cap.SQUARE); + p.setStrokeWidth(6.0f); + + canvas.save(); + float[] id = new float[16]; + Matrix.setIdentityM(id, 0); + android.graphics.Matrix identity = CanvasMatrixUtil.createMatrixFrom4x4(id); + canvas.setMatrix(identity); + canvas.drawPoints(pointsToDraw, p); + canvas.restore(); + } + + + // Sample function for drawing AR planes + public void drawPlanes(Canvas canvas, Pose cameraPose, Collection allPlanes) { + if (allPlanes.size() <= 0) { + return; + } + + for (Plane plane : allPlanes) { + Plane subsumePlane = plane.getSubsumedBy(); + if (plane.getTrackingState() != TrackingState.TRACKING || subsumePlane != null) { + continue; + } + + float distance = calculateDistanceToPlane(plane.getCenterPose(), cameraPose); + if (distance < 0) { // Plane is back-facing. + continue; + } + + // Get plane model matrix + float[] model = new float[16]; + plane.getCenterPose().toMatrix(model, 0); + + // Initial rotation + float[] initRot = CanvasMatrixUtil.createXYtoXZRotationMatrix(); + + // Matrix = mvpv + float[][] matrices = {initRot, model, viewMatrix, projectionMatrix, CanvasMatrixUtil.createViewportMatrix(viewportWidth, viewportHeight)}; + android.graphics.Matrix mvpv = CanvasMatrixUtil.createMatrixFrom4x4(CanvasMatrixUtil.multiplyMatrices4x4(matrices)); + + drawPlaneAsPath(canvas, mvpv, plane); + } + } + + // Helper function that draws an AR plane using a path + private void drawPlaneAsPath(Canvas canvas, android.graphics.Matrix mvpv, Plane plane) { + int vertsSize = plane.getPolygon().limit() / 2; + FloatBuffer polygon = plane.getPolygon(); + polygon.rewind(); + + // Build source path from polygon data + Path pathSrc = new Path(); + pathSrc.moveTo(polygon.get(0), polygon.get(1)); + for (int i = 1; i < vertsSize; i++) { + pathSrc.lineTo(polygon.get(i * 2), polygon.get(i * 2 + 1)); + } + pathSrc.close(); + + // Set up paint + Paint p = new Paint(); + + if (false) { + //p.setShader(planeShader); + p.setColorFilter(new PorterDuffColorFilter(Color.argb(0.4f, 1, 0, 0), + PorterDuff.Mode.SRC_ATOP)); + } + + p.setColor(Color.RED); + p.setAlpha(100); + p.setStrokeWidth(0.01f); + p.setStyle(Paint.Style.STROKE); + + + if (true) { + // Shader local matrix + android.graphics.Matrix lm = new android.graphics.Matrix(); + lm.setScale(0.00005f, 0.00005f); + planeShader.setLocalMatrix(lm); + // Draw dest path + canvas.save(); + canvas.setMatrix(mvpv); + canvas.drawPath(pathSrc, p); + canvas.restore(); + } else { + // Build destination path by transforming source path + Path pathDst = new Path(); + pathSrc.transform(mvpv, pathDst); + + // Shader local matrix + android.graphics.Matrix lm = new android.graphics.Matrix(); + lm.setScale(0.00005f, 0.00005f); + lm.postConcat(mvpv); + planeShader.setLocalMatrix(lm); + + // Draw dest path + canvas.save(); + canvas.setMatrix(new android.graphics.Matrix()); + canvas.drawPath(pathDst, p); + canvas.restore(); + } + } + + public void initializePlaneShader(Context context, String gridDistanceTextureName) throws IOException { + // Read the texture. + Bitmap planeTexture = + BitmapFactory.decodeStream(context.getAssets().open(gridDistanceTextureName)); + // Set up the shader + planeShader = new BitmapShader(planeTexture, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); + planeShader.setLocalMatrix(new android.graphics.Matrix()); + } + + 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; + } + + public static float calculateDistanceToPlane(Pose planePose, Pose cameraPose) { + float[] normal = new float[3]; + float cameraX = cameraPose.tx(); + float cameraY = cameraPose.ty(); + float cameraZ = cameraPose.tz(); + // Get transformed Y axis of plane's coordinate system. + planePose.getTransformedAxis(1, 1.0f, normal, 0); + // Compute dot product of plane's normal with vector from camera to plane center. + return (cameraX - planePose.tx()) * normal[0] + + (cameraY - planePose.ty()) * normal[1] + + (cameraZ - planePose.tz()) * normal[2]; + } +} diff --git a/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/rendering/ShaderUtil.java b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/rendering/ShaderUtil.java new file mode 100644 index 0000000000..ce33a45966 --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/rendering/ShaderUtil.java @@ -0,0 +1,99 @@ +/* + * 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.skar.examples.helloskar.rendering; + +import android.content.Context; +import android.opengl.GLES20; +import android.util.Log; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +/** + * Shader helper functions. + */ +public class ShaderUtil { + /** + * Converts a raw text file, saved as a resource, into an OpenGL ES shader. + * + * @param type The type of shader we will be creating. + * @param filename The filename of the asset file about to be turned into a shader. + * @return The shader object handler. + */ + public static int loadGLShader(String tag, Context context, int type, String filename) + throws IOException { + String code = readRawTextFileFromAssets(context, filename); + int shader = GLES20.glCreateShader(type); + GLES20.glShaderSource(shader, code); + GLES20.glCompileShader(shader); + + // Get the compilation status. + final int[] compileStatus = new int[1]; + GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0); + + // If the compilation failed, delete the shader. + if (compileStatus[0] == 0) { + Log.e(tag, "Error compiling shader: " + GLES20.glGetShaderInfoLog(shader)); + GLES20.glDeleteShader(shader); + shader = 0; + } + + if (shader == 0) { + throw new RuntimeException("Error creating shader."); + } + + return shader; + } + + /** + * Checks if we've had an error inside of OpenGL ES, and if so what that error is. + * + * @param label Label to report in case of error. + * @throws RuntimeException If an OpenGL error is detected. + */ + public static void checkGLError(String tag, String label) { + int lastError = GLES20.GL_NO_ERROR; + // Drain the queue of all errors. + int error; + while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { + Log.e(tag, label + ": glError " + error); + lastError = error; + } + if (lastError != GLES20.GL_NO_ERROR) { + throw new RuntimeException(label + ": glError " + lastError); + } + } + + /** + * Converts a raw text file into a string. + * + * @param filename The filename of the asset file about to be turned into a shader. + * @return The context of the text file, or null in case of error. + */ + private static String readRawTextFileFromAssets(Context context, String filename) + throws IOException { + try (InputStream inputStream = context.getAssets().open(filename); + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line).append("\n"); + } + return sb.toString(); + } + } +} -- cgit v1.2.3