@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
 GLES20.glClearColor(0.1 f, 0.1 f, 0.1 f, 1.0 f);

 // Création de la texture à rendre et passage à la session ARCore
 mBackgroundRenderer.createOnGlThread(this);
 mSession.setCameraTextureName(mBackgroundRenderer.getTextureId());

 // Rendu du robot
 try {
   mVirtualObject.createOnGlThread(this, "andy.obj", "andy.png");
   mVirtualObject.setMaterialProperties(0.0 f, 3.5 f, 1.0 f, 6.0 f);
   mVirtualObjectShadow.createOnGlThread(this, "andy_shadow.obj", "andy_shadow.png");
   mVirtualObjectShadow.setBlendMode(BlendMode.Shadow);
   mVirtualObjectShadow.setMaterialProperties(1.0 f, 0.0 f, 0.0 f, 1.0 f);
 } catch (IOException e) {}

 // Rendu du plan
 try {
  mPlaneRenderer.createOnGlThread(this, "trigrid.png");
 } catch (IOException e) {
 }
 
 mPointCloud.createOnGlThread(this);
}

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
 GLES20.glViewport(0, 0, width, height);
 // Notification des changements de dimension de la vue à la session ARCore
 // pour recalcul de la matrice de perspective
 mSession.setDisplayGeometry(width, height);
}

@Override
public void onDrawFrame(GL10 gl) {
 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);

 try {
  // On récupère la frame courante via appel à la session ARCore
  Frame frame = mSession.update();

  // Gestion des taps à l'écran
  MotionEvent tap = mQueuedSingleTaps.poll();

  if (tap != null && frame.getTrackingState() == TrackingState.TRACKING) {
    for (HitResult hit: frame.hitTest(tap)) {
      // On vérifie sir un plan a été touché
      if (hit instanceof PlaneHitResult && ((PlaneHitResult) hit).isHitInPolygon()) {
        // On limite le nombre d'objets virtuels à 16
        if (mTouches.size() >= 16) {
          mSession.removeAnchors(Arrays.asList(mTouches.get(0).getAnchor()));
          mTouches.remove(0);
        }

        // L'ajout d'une ancre indique à ARCore que les mouvements de cet objet
        // doivent être suivis
        mTouches.add(new PlaneAttachment(((PlaneHitResult) hit).getPlane(),
        mSession.addAnchor(hit.getHitPose())));

        break;
      }
    }
  }

  // On dessine l'arrière-plan
  mBackgroundRenderer.draw(frame);

  // Si le suivi n'est pas activté, on sort
  if (frame.getTrackingState() == TrackingState.NOT_TRACKING) {
    return;
  }

  // On charge la matrice de projection
  float[] projmtx = new float[16];
  mSession.getProjectionMatrix(projmtx, 0, 0.1 f, 100.0 f);

  // On charge la matrice de la caméra
  float[] viewmtx = new float[16];
  frame.getViewMatrix(viewmtx, 0);

  // Calcul de la lumière l'éclairage
  final float lightIntensity = frame.getLightEstimate().getPixelIntensity();

  // On visualise les points suivis
  mPointCloud.update(frame.getPointCloud());
  mPointCloud.draw(frame.getPointCloudPose(), viewmtx, projmtx);

  if (mLoadingMessageSnackbar != null) {
    for (Plane plane: mSession.getAllPlanes()) {
      if (plane.getType() == com.google.ar.core.Plane.Type.HORIZONTAL_UPWARD_FACING &&
          plane.getTrackingState() == Plane.TrackingState.TRACKING) {
        hideLoadingMessage();
        break;
      }
    }
  }

  // on visualise les plans
  mPlaneRenderer.drawPlanes(mSession.getAllPlanes(), frame.getPose(), projmtx);

  // Enfin, on visualise les ancres créées
  float scaleFactor = 1.0 f;

  for (PlaneAttachment planeAttachment: mTouches) {
    if (!planeAttachment.isTracking()) {
      continue;
    }

    planeAttachment.getPose().toMatrix(mAnchorMatrix, 0);

    // Dessin du model
    mVirtualObject.updateModelMatrix(mAnchorMatrix, scaleFactor);
    mVirtualObjectShadow.updateModelMatrix(mAnchorMatrix, scaleFactor);
    mVirtualObject.draw(viewmtx, projmtx, lightIntensity);
    mVirtualObjectShadow.draw(viewmtx, projmtx, lightIntensity);
  }
 } catch (Throwable t) {
 }
}