• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 Google LLC All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.skar.examples.helloskar.app;
18 
19 import android.animation.PropertyValuesHolder;
20 import android.animation.ValueAnimator;
21 import android.graphics.Canvas;
22 import android.graphics.Color;
23 import android.graphics.PorterDuff;
24 import android.opengl.GLES20;
25 import android.opengl.GLSurfaceView;
26 import android.opengl.Matrix;
27 import android.os.Bundle;
28 import android.support.annotation.NonNull;
29 
30 import android.support.design.widget.BottomNavigationView;
31 import android.support.v7.app.AppCompatActivity;
32 import android.support.v7.widget.Toolbar;
33 import android.util.Log;
34 import android.view.Menu;
35 import android.view.MenuInflater;
36 import android.view.MenuItem;
37 import android.view.MotionEvent;
38 import android.view.SurfaceHolder;
39 import android.view.View;
40 import android.view.WindowManager;
41 import android.widget.Toast;
42 
43 import com.google.ar.core.Anchor;
44 import com.google.ar.core.ArCoreApk;
45 import com.google.ar.core.Camera;
46 import com.google.ar.core.Frame;
47 import com.google.ar.core.HitResult;
48 import com.google.ar.core.Plane;
49 import com.google.ar.core.Point;
50 import com.google.ar.core.Point.OrientationMode;
51 import com.google.ar.core.PointCloud;
52 import com.google.ar.core.Session;
53 import com.google.ar.core.Trackable;
54 import com.google.ar.core.TrackingState;
55 
56 import com.google.ar.core.examples.java.helloskar.R;
57 import com.google.skar.examples.helloskar.helpers.CameraPermissionHelper;
58 import com.google.skar.examples.helloskar.helpers.DisplayRotationHelper;
59 import com.google.skar.examples.helloskar.helpers.FullScreenHelper;
60 import com.google.skar.examples.helloskar.helpers.GestureHelper;
61 import com.google.skar.examples.helloskar.helpers.SnackbarHelper;
62 
63 import com.google.skar.examples.helloskar.rendering.BackgroundRenderer;
64 import com.google.skar.examples.helloskar.rendering.DrawManager;
65 
66 import com.google.ar.core.exceptions.CameraNotAvailableException;
67 import com.google.ar.core.exceptions.UnavailableApkTooOldException;
68 import com.google.ar.core.exceptions.UnavailableArcoreNotInstalledException;
69 import com.google.ar.core.exceptions.UnavailableDeviceNotCompatibleException;
70 import com.google.ar.core.exceptions.UnavailableSdkTooOldException;
71 import com.google.ar.core.exceptions.UnavailableUserDeclinedInstallationException;
72 
73 import java.io.IOException;
74 import java.util.ArrayList;
75 
76 import javax.microedition.khronos.egl.EGLConfig;
77 import javax.microedition.khronos.opengles.GL10;
78 
79 /**
80  * This is a simple example that shows how to create an augmented reality (AR) application using the
81  * ARCore API. The application will display any detected planes and will allow the user to tap on a
82  * plane to place 2D objects
83  */
84 
85 public class HelloCanvasAR extends AppCompatActivity implements GLSurfaceView.Renderer {
86     private static final String TAG = HelloCanvasAR.class.getSimpleName();
87     private final int MAX_NUMBER_DRAWABLES = 50; // Arbitrary limit to the # of anchors to store
88 
89     // Simple SurfaceView used to draw 2D objects on top of the GLSurfaceView
90     private CanvasARSurfaceView arSurfaceView;
91     private SurfaceHolder holder;
92 
93     // GLSurfaceView used to draw 3D objects & camera input
94     private GLSurfaceView glSurfaceView;
95 
96     // ARSession
97     private Session session;
98 
99     // OpenGL background renderer
100     private final BackgroundRenderer backgroundRenderer = new BackgroundRenderer();
101 
102     // 2D Renderer
103     private DrawManager drawManager = new DrawManager();
104 
105     private boolean installRequested;
106     private final SnackbarHelper messageSnackbarHelper = new SnackbarHelper();
107     private DisplayRotationHelper displayRotationHelper;
108     private GestureHelper tapHelper;
109 
110     // Temporary matrix allocated here to reduce number of allocations for each frame.
111     private final float[] anchorMatrix = new float[16];
112 
113     // Anchors created from taps used for object placing.
114     private final ArrayList<Anchor> anchors = new ArrayList<>();
115 
116     // Animation fields
117     float radius;
118     String PROPERTY_RADIUS = "radius";
119     ValueAnimator animator;
120 
121     @Override
onCreate(Bundle savedInstanceState)122     protected void onCreate(Bundle savedInstanceState) {
123         super.onCreate(savedInstanceState);
124         setContentView(R.layout.activity_main);
125 
126         // Menu tool bar set up
127         Toolbar toolbar = findViewById(R.id.main_toolbar);
128         setSupportActionBar(toolbar);
129 
130         // Hide notifications bar
131         getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
132                 WindowManager.LayoutParams.FLAG_FULLSCREEN);
133 
134         // Canvas Surface View set up
135         arSurfaceView = findViewById(R.id.canvas_surfaceview);
136         glSurfaceView = findViewById(R.id.gl_surfaceview);
137         arSurfaceView.bringToFront();
138         arSurfaceView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
139         holder = arSurfaceView.getHolder();
140 
141         // Set up tap listener.
142         tapHelper = new GestureHelper(this);
143 
144         glSurfaceView.setOnTouchListener(tapHelper);
145 
146         // Set up renderer.
147         glSurfaceView.setPreserveEGLContextOnPause(true);
148         glSurfaceView.setEGLContextClientVersion(2);
149         glSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0); // Alpha used for plane blending.
150         glSurfaceView.setRenderer(this);
151         glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
152         displayRotationHelper = new DisplayRotationHelper(this);
153         installRequested = false;
154 
155         // Set up finger painting palette bar
156         BottomNavigationView bottomNav = findViewById(R.id.palette);
157         bottomNav.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
158             @Override
159             public boolean onNavigationItemSelected(@NonNull MenuItem item) {
160                 switch (item.getItemId()) {
161                     case R.id.palette_green:
162                         drawManager.fingerPainting.setColor(Color.GREEN);
163                         return true;
164                     case R.id.palette_red:
165                         drawManager.fingerPainting.setColor(Color.RED);
166                         return true;
167                     case R.id.palette_reset:
168                         drawManager.fingerPainting.reset();
169                         return true;
170                     default:
171                         return true;
172                 }
173             }
174         });
175 
176         // Value Animator set up
177         PropertyValuesHolder propertyRadius = PropertyValuesHolder.ofFloat(PROPERTY_RADIUS, 0, 0.5f);
178         animator = new ValueAnimator();
179         animator.setValues(propertyRadius);
180         animator.setDuration(1000);
181         animator.setRepeatCount(ValueAnimator.INFINITE);
182         animator.setRepeatMode(ValueAnimator.REVERSE);
183         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
184             @Override
185             public void onAnimationUpdate(ValueAnimator animation) {
186                 radius = (float) animation.getAnimatedValue(PROPERTY_RADIUS);
187             }
188         });
189         animator.start();
190     }
191 
192     @Override
onResume()193     protected void onResume() {
194         super.onResume();
195 
196         if (session == null) {
197             Exception exception = null;
198             String message = null;
199             try {
200                 switch (ArCoreApk.getInstance().requestInstall(this, !installRequested)) {
201                     case INSTALL_REQUESTED:
202                         installRequested = true;
203                         return;
204                     case INSTALLED:
205                         break;
206                 }
207 
208                 // ARCore requires camera permissions to operate. If we did not yet obtain runtime
209                 // permission on Android M and above, now is a good time to ask the user for it.
210                 if (!CameraPermissionHelper.hasCameraPermission(this)) {
211                     CameraPermissionHelper.requestCameraPermission(this);
212                     return;
213                 }
214 
215                 // Create the session.
216                 session = new Session(/* context= */ this);
217 
218             } catch (UnavailableArcoreNotInstalledException
219                     | UnavailableUserDeclinedInstallationException e) {
220                 message = "Please install ARCore";
221                 exception = e;
222             } catch (UnavailableApkTooOldException e) {
223                 message = "Please update ARCore";
224                 exception = e;
225             } catch (UnavailableSdkTooOldException e) {
226                 message = "Please update this app";
227                 exception = e;
228             } catch (UnavailableDeviceNotCompatibleException e) {
229                 message = "This device does not support AR";
230                 exception = e;
231             } catch (Exception e) {
232                 message = "Failed to create AR session";
233                 exception = e;
234             }
235 
236             if (message != null) {
237                 messageSnackbarHelper.showError(this, message);
238                 Log.e(TAG, "Exception creating session", exception);
239                 return;
240             }
241         }
242 
243         // Note that order matters - see the note in onPause(), the reverse applies here.
244         try {
245             session.resume();
246         } catch (CameraNotAvailableException e) {
247             messageSnackbarHelper.showError(this, "Camera not available. Please restart the app.");
248             session = null;
249             return;
250         }
251 
252         glSurfaceView.onResume();
253         displayRotationHelper.onResume();
254         messageSnackbarHelper.showMessage(this, "Searching for surfaces...");
255     }
256 
257     @Override
onPause()258     public void onPause() {
259         super.onPause();
260         if (session != null) {
261             displayRotationHelper.onPause();
262             glSurfaceView.onPause();
263             session.pause();
264         }
265     }
266 
267     @Override
onRequestPermissionsResult(int requestCode, String[] permissions, int[] results)268     public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] results) {
269         if (!CameraPermissionHelper.hasCameraPermission(this)) {
270             Toast.makeText(this, "Camera permission is needed to run this application", Toast.LENGTH_LONG)
271                     .show();
272             if (!CameraPermissionHelper.shouldShowRequestPermissionRationale(this)) {
273                 // Permission denied with checking "Do not ask again".
274                 CameraPermissionHelper.launchPermissionSettings(this);
275             }
276             finish();
277         }
278     }
279 
280     @Override
onWindowFocusChanged(boolean hasFocus)281     public void onWindowFocusChanged(boolean hasFocus) {
282         super.onWindowFocusChanged(hasFocus);
283         FullScreenHelper.setFullScreenOnWindowFocusChanged(this, hasFocus);
284     }
285 
286     /************** GLSurfaceView Methods ****************************/
287     @Override
onSurfaceCreated(GL10 gl, EGLConfig config)288     public void onSurfaceCreated(GL10 gl, EGLConfig config) {
289         GLES20.glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
290 
291         // Prepare the rendering objects. This involves reading shaders, so may throw an IOException.
292         try {
293             // Create the texture and pass it to ARCore session to be filled during update().
294             backgroundRenderer.createOnGlThread( this);
295             drawManager.initializePlaneShader(this, "models/trigrid.png");
296         } catch (IOException e) {
297             Log.e(TAG, "Failed to read an asset file", e);
298         }
299     }
300 
301     @Override
onSurfaceChanged(GL10 gl, int width, int height)302     public void onSurfaceChanged(GL10 gl, int width, int height) {
303         displayRotationHelper.onSurfaceChanged(width, height);
304         GLES20.glViewport(0, 0, width, height);
305 
306         // Send viewport information to 2D AR drawing manager
307         drawManager.updateViewport(width, height);
308     }
309 
310     @Override
onDrawFrame(GL10 gl)311     public void onDrawFrame(GL10 gl) {
312         Canvas canvas = null;
313 
314         // Clear screen to notify driver it should not load any pixels from previous frame.
315         GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
316 
317         if (session == null) {
318             return;
319         }
320 
321         // Notify ARCore session that the view size changed so that the perspective matrix and
322         // the video background can be properly adjusted.
323         displayRotationHelper.updateSessionIfNeeded(session);
324 
325         try {
326             session.setCameraTextureName(backgroundRenderer.getTextureId());
327             Frame frame = session.update();
328             Camera camera = frame.getCamera();
329 
330             // Query information from single tap gestures to get anchors
331             handleSingleTaps(frame, camera);
332 
333             // Draw background with OpenGL.
334             // TODO: possibly find a way to extract texture and draw on Canvas
335             backgroundRenderer.draw(frame);
336 
337             // If not tracking, don't draw objects
338             if (camera.getTrackingState() == TrackingState.PAUSED) {
339                 return;
340             }
341 
342             // Get projection matrix.
343             float[] projMatrix = new float[16];
344             camera.getProjectionMatrix(projMatrix, 0, 0.1f, 100.0f);
345             drawManager.updateProjectionMatrix(projMatrix);
346 
347             // Get camera matrix and draw.
348             float[] viewMatrix = new float[16];
349             camera.getViewMatrix(viewMatrix, 0);
350             drawManager.updateViewMatrix(viewMatrix);
351 
352             final float[] colorCorrectionRgba = new float[4];
353             frame.getLightEstimate().getColorCorrection(colorCorrectionRgba, 0);
354             drawManager.updateLightColorFilter(colorCorrectionRgba);
355 
356             // Query information from scrolling gestures to build finger paintings
357             handleHoldTaps(frame, camera);
358 
359             // Drawing on Canvas (SurfaceView)
360             if (arSurfaceView.isRunning()) {
361                 // Lock canvas
362                 canvas = holder.lockHardwareCanvas();
363                 canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
364 
365                 // Draw point cloud
366                 PointCloud pointCloud = frame.acquirePointCloud();
367                 drawPointCloud(canvas, pointCloud);
368                 pointCloud.release();
369 
370                 // Draw planes
371                 // Check if we detected at least one plane. If so, hide the loading message.
372                 if (messageSnackbarHelper.isShowing()) {
373                     for (Plane plane : session.getAllTrackables(Plane.class)) {
374                         if (plane.getType() == com.google.ar.core.Plane.Type.HORIZONTAL_UPWARD_FACING
375                                 && plane.getTrackingState() == TrackingState.TRACKING) {
376                             messageSnackbarHelper.hide(this);
377                             break;
378                         }
379                     }
380                 }
381 
382                 // Draw planes
383                 drawPlanes(canvas, camera);
384 
385                 // Draw models
386                 drawModels(canvas);
387 
388                 // Draw finger painting
389                 drawFingerPainting(canvas);
390 
391                 // Unlock canvas
392                 holder.unlockCanvasAndPost(canvas);
393             }
394         } catch (Throwable t) {
395             // Avoid crashing the application due to unhandled exceptions.
396             if (canvas != null) {
397                 holder.unlockCanvasAndPost(canvas);
398             }
399             Log.e(TAG, "Exception on the OpenGL thread", t);
400         }
401     }
402 
403     /**************************** Gesture helpers ******************************/
404     /**
405      * Given a Frame and a Camera, perform hit tests on stored UI touch events. If a hit test is
406      * successful, construct an Anchor at the hit position and add it to the set of anchors.
407      * @param frame     Frame of this update() call
408      * @param camera    Camera of this update() call
409      */
handleSingleTaps(Frame frame, Camera camera)410     private void handleSingleTaps(Frame frame, Camera camera) {
411         MotionEvent tap = tapHelper.poll();
412         if (tap != null && camera.getTrackingState() == TrackingState.TRACKING) {
413             for (HitResult hit : frame.hitTest(tap)) {
414                 // Check if any plane was hit, and if it was hit inside the plane polygon
415                 Trackable trackable = hit.getTrackable();
416                 // Creates an anchor if a plane or an oriented point was hit.
417                 if ((trackable instanceof Plane
418                         && ((Plane) trackable).isPoseInPolygon(hit.getHitPose())
419                         && (DrawManager.calculateDistanceToPlane(hit.getHitPose(), camera.getPose())
420                         > 0))
421                         || (trackable instanceof Point
422                         && ((Point) trackable).getOrientationMode()
423                         == OrientationMode.ESTIMATED_SURFACE_NORMAL)) {
424                     if (anchors.size() >= MAX_NUMBER_DRAWABLES) {
425                         anchors.get(0).detach();
426                         anchors.remove(0);
427                     }
428                     anchors.add(hit.createAnchor());
429                     break;
430                 }
431             }
432         }
433     }
434 
435     /**
436      * Given a Frame and a Camera, perform hit tests on stored UI touch events. If a hit test is
437      * successful, construct an Anchor at the hit position and add it to the set of anchors.
438      * @param frame     Frame of this update() call
439      * @param camera    Camera of this update() call
440      */
handleHoldTaps(Frame frame, Camera camera)441     private void handleHoldTaps(Frame frame, Camera camera) {
442         // Building finger painting
443         GestureHelper.ScrollEvent holdTap = tapHelper.holdPoll();
444         if (holdTap != null && camera.getTrackingState() == TrackingState.TRACKING) {
445             for (HitResult hit : frame.hitTest(holdTap.event)) {
446                 // Check if any plane was hit, and if it was hit inside the plane polygon
447                 Trackable trackable = hit.getTrackable();
448                 // Creates an anchor if a plane or an oriented point was hit.
449                 if ((trackable instanceof Plane
450                         && ((Plane) trackable).isPoseInPolygon(hit.getHitPose())
451                         && (DrawManager.calculateDistanceToPlane(hit.getHitPose(), camera.getPose())
452                         > 0))
453                         || (trackable instanceof Point
454                         && ((Point) trackable).getOrientationMode()
455                         == OrientationMode.ESTIMATED_SURFACE_NORMAL)) {
456 
457                     // Get hit point transform, apply it to the origin --> point is not in hit
458                     // location on the plane
459                     float[] modelMatrix = new float[16];
460                     hit.getHitPose().toMatrix(modelMatrix, 0);
461                     float[] hitLocation = {0, 0, 0, 1};
462                     Matrix.multiplyMV(hitLocation, 0, modelMatrix, 0,
463                                       hitLocation, 0);
464 
465                     if (! drawManager.fingerPainting.computeNextPoint(hitLocation, modelMatrix, holdTap)) {
466                         // Try to add the next point to the finger painting. If return value
467                         // is false, then keep looping
468                         continue;
469                     }
470 
471                     break;
472                 }
473             }
474         }
475     }
476 
477     /**************************** Drawing helpers ******************************/
478     // Helper drawing functions that invoke drawManager
drawPlanes(Canvas canvas, Camera camera)479     private void drawPlanes(Canvas canvas, Camera camera) {
480         drawManager.drawPlanes(canvas, camera.getPose(), session.getAllTrackables(Plane.class));
481     }
482 
drawPointCloud(Canvas canvas, PointCloud cloud)483     private void drawPointCloud(Canvas canvas, PointCloud cloud) {
484         drawManager.drawPointCloud(canvas, cloud);
485     }
486 
drawModels(Canvas canvas)487     private void drawModels(Canvas canvas) {
488         for (Anchor anchor : anchors) {
489             if (anchor.getTrackingState() != TrackingState.TRACKING) {
490                 continue;
491             }
492             // Get the current pose of an Anchor in world space
493             anchor.getPose().toMatrix(anchorMatrix, 0);
494             drawManager.modelMatrices.add(0, anchorMatrix);
495 
496             switch (drawManager.currentDrawabletype) {
497                 case circle:
498                     drawManager.drawCircle(canvas);
499                     break;
500                 case rect:
501                     drawManager.drawRect(canvas);
502                     break;
503                 case animation:
504                     drawManager.drawAnimatedRoundRect(canvas, radius);
505                     break;
506                 case text:
507                     drawManager.drawText(canvas, "Android");
508                     break;
509                 default:
510                     drawManager.drawCircle(canvas);
511                     break;
512             }
513         }
514     }
515 
drawFingerPainting(Canvas canvas)516     private void drawFingerPainting(Canvas canvas) {
517         drawManager.fingerPainting.setSmoothness(drawManager.drawSmoothPainting);
518         drawManager.fingerPainting.buildPath();
519         drawManager.drawFingerPainting(canvas);
520     }
521 
522     /**************************** UI helpers ******************************/
523     // Tool bar functions
onCreateOptionsMenu(Menu menu)524     public boolean onCreateOptionsMenu(Menu menu) {
525         MenuInflater inflater = getMenuInflater();
526         inflater.inflate(R.menu.main_menu, menu);
527 
528         menu.setGroupCheckable(R.id.menu_drawables, true, true);
529         return true;
530     }
531 
onOptionsItemSelected(MenuItem item)532     public boolean onOptionsItemSelected(MenuItem item) {
533         item.setChecked(!item.isChecked());
534         switch (item.getItemId()) {
535             case R.id.smooth_paint:
536                 drawManager.drawSmoothPainting = item.isChecked();
537                 return true;
538             case R.id.draw_circle:
539                 drawManager.currentDrawabletype = DrawManager.DrawingType.circle;
540                 return true;
541             case R.id.draw_rect:
542                 drawManager.currentDrawabletype = DrawManager.DrawingType.rect;
543                 return true;
544             case R.id.draw_text:
545                 drawManager.currentDrawabletype = DrawManager.DrawingType.text;
546                 return true;
547             case R.id.draw_animation:
548                 drawManager.currentDrawabletype = DrawManager.DrawingType.animation;
549                 return true;
550             default:
551                 return super.onOptionsItemSelected(item);
552         }
553     }
554 }
555