In the previous article, I already explained what ARCore is and how it helps developers create amazing augmented reality applications without having to understand math and OpenGL.
If you haven’t read it yet, I highly recommend it before turning to this article and start developing ARCore applications.
To start developing ARCore applications, you first need to add ARCore support to your project. This is very simple, as we will use the Android Studio and Sceneform SDK. There are two basic operations that, thanks to the Sceneform, are performed automatically:
You do not need to worry about these two steps when creating an ARCore application using the Sceneform SDK. You just need to add the Sceneform SDK to your project.
Create a new Android Studio project with an empty Activity.
Add the following dependency to the project level build.gradle
file:
dependencies { classpath 'com.google.ar.sceneform:plugin:1.5.0' }
Add this dependency to the application level in the build.gradle
file:
implementation "com.google.ar.sceneform.ux:sceneform-ux:1.5.0"
Now synchronize the project with the Gradle files and wait for the build to complete. Thus, the Sceneform SDK and the Sceneform plugin for Android Studio will be added to the project. This will allow you to view .sfb
, which are 3D models that will be rendered in your camera, as well as help you import, view, and create 3D resources.
Now that the Android Studio setup is complete and the Sceneform SDK is installed, we can start creating our first ARCore application.
First, we need to add the Sceneform fragment to our layout. This is the so-called scene where all our 3D models will be placed. The fragment will independently take care of the camera initialization and resolution processing.
Navigate to your main layout file. In my case, this is the activity_main.xml
file. And add the Sceneform fragment there:
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <fragment android:name="com.google.ar.sceneform.ux.ArFragment" android:id="@+id/ux_fragment" android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout>
I set the width and height of match_parent
so that the scene match_parent
entire screen. You can choose the sizes according to your requirements.
This is all you need to do in the layout file. Now we turn to Activity, in my case this is MainActivity
. Add a method to the Activity:
public static boolean checkIsSupportedDeviceOrFinish(final Activity activity) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { Log.e(TAG, "Sceneform requires Android N or later"); Toast.makeText(activity, "Sceneform requires Android N or later", Toast.LENGTH_LONG).show(); activity.finish(); return false; } String openGlVersionString = ((ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE)) .getDeviceConfigurationInfo() .getGlEsVersion(); if (Double.parseDouble(openGlVersionString) < MIN_OPENGL_VERSION) { Log.e(TAG, "Sceneform requires OpenGL ES 3.0 later"); Toast.makeText(activity, "Sceneform requires OpenGL ES 3.0 or later", Toast.LENGTH_LONG) .show(); activity.finish(); return false; } return true; }
This method checks whether your device supports the Sceneform SDK or not. The SDK requires Android API level 27 or higher and OpenGL ES version 3.0 or higher. If the device does not support these two parameters, the scene will not be loaded, and your application will display a blank screen.
However, you can still implement all the other functions of your application that do not require the Sceneform SDK.
After checking the compatibility, we can create our 3D model and attach it to the scene.
Now you need to add to the project a 3D model that will be displayed on your screen. You can create these models yourself if you are familiar with the process of creating them. Or you can go to the Poly .
There you will find a huge repository of 3D resources to choose from. In addition, they are free to download.
In Android Studio, open your application folder in the left pane. You need a sampledata folder. This folder will contain all your 3D models. Inside this folder, create a folder with the name of your model.
In the archive that you download from Poly, you will most likely find 3 files:
.mtl
file.obj
file.png
fileThe most important of these three files is the .obj
file. This is your model. Place all 3 files in sampledata -> “your model folder” .
Now right-click on the .obj
file. The first option would be Import Sceneform Asset . Click on it, do not change the default settings, just click Finish in the next window. After that, synchronize the project with the Gradle files.
Import of the 3D resource that will be used in your project is completed. Next, let's use the 3D model in our code and include it in the scene.
Add the following code to your Activity, and I will explain it line by line:
private static final String TAG = MainActivity.class.getSimpleName(); private static final double MIN_OPENGL_VERSION = 3.0; ArFragment arFragment; ModelRenderable lampPostRenderable; @Override @SuppressWarnings({"AndroidApiChecker", "FutureReturnValueIgnored"}) protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (!checkIsSupportedDeviceOrFinish(this)) { return; } setContentView(R.layout.activity_main); arFragment = (ArFragment) getSupportFragmentManager().findFragmentById(R.id.ux_fragment); ModelRenderable.builder() .setSource(this, Uri.parse("LampPost.sfb")) .build() .thenAccept(renderable -> lampPostRenderable = renderable) .exceptionally(throwable -> { Toast toast = Toast.makeText(this, "Unable to load andy renderable", Toast.LENGTH_LONG); toast.setGravity(Gravity.CENTER, 0, 0); toast.show(); return null; }); }
First we find the arFragment
that we previously added to the layout. This fragment is responsible for the storage and operation of the scene. You can imagine it as a container for our scene.
Next, we use the ModelRenderable
class to build our model. Using the setSource
method setSource
we load our model from an .sfb
file that was generated when importing resources. The thenAccept
method gets the model after it is created, and we set the loaded model to our lampPostRenderable
variable.
For error handling, we have the exceptionally
method, which is called when an exception occurs.
All this happens asynchronously, so you don’t need to worry about multithreading.
Now that the model has been loaded and stored in the lampPostRenderable
variable, we will add it to our scene.
The arFragment
is our scene, and it will receive user touch events. Therefore, we need to install an onTap
listener for our fragment in order to handle the touches and place objects where it is needed. Add the following code to the onCreate
method:
arFragment.setOnTapArPlaneListener( (HitResult hitresult, Plane plane, MotionEvent motionevent) -> { if (lampPostRenderable == null){ return; } Anchor anchor = hitresult.createAnchor(); AnchorNode anchorNode = new AnchorNode(anchor); anchorNode.setParent(arFragment.getArSceneView().getScene()); TransformableNode lamp = new TransformableNode(arFragment.getTransformationSystem()); lamp.setParent(anchorNode); lamp.setRenderable(lampPostRenderable); lamp.select(); } );
We set the onTapArPlaneListener
listener for our AR fragment. The following is the lambda expression syntax. If you are not familiar with it, then read this small guide on this topic.
First we create an anchor from HitResult
using hitresult.createAnchor()
and save it in the Anchor
object.
Then we create a knot from this anchor. It will be called AnchorNode
and will be attached to the scene using the setParent
method.
Next, we create a TransformableNode
, which will be our model, and bind it to our node. TransformableNode
still does not have any information about the object it should display. We will pass this object to it using the setRenderable
method, which takes as an argument an object of type ModelRenderable (remember, we obtained such an object and called it lampPostRenderable
?). And finally, call the method lamp.select()
;
Oh! Too much terminology. Do not worry, now I will explain everything:
Scene : this is the place where all your 3D objects will be displayed. This scene is located in the AR fragment that we added to the layout.
HitResult : this is an imaginary line (or ray) coming from infinity, which gives the intersection point of itself with the object of the real world.
Anchor : This is a fixed location and orientation in the real world. It can be understood as coordinates (x, y, z) in three-dimensional space. Pose is the position and orientation of an object on the stage. It is used to transform the local coordinate space of an object into real coordinate space.
Anchor node : this is a node that automatically positions itself in the real world. This is the first node that is installed when a plane is detected.
TransformableNode : This is a node with which to interact. It can be moved, scaled, rotated and so on. In this example, we can scale our object and rotate it. Hence the name Transformable.
There is no rocket science here. It is really relatively simple. The entire scene can be viewed as a graph, in which the parent object is the scene, and the child - anchor nodes, which then branch out into various other nodes and objects that will be displayed on the screen.
As a result, your Activity should look like this:
package com.ayusch.arcorefirst; import android.app.Activity; import android.app.ActivityManager; import android.content.Context; import android.net.Uri; import android.os.Build; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.Gravity; import android.view.MotionEvent; import android.widget.Toast; import com.google.ar.core.Anchor; import com.google.ar.core.HitResult; import com.google.ar.core.Plane; import com.google.ar.sceneform.AnchorNode; import com.google.ar.sceneform.rendering.ModelRenderable; import com.google.ar.sceneform.ux.ArFragment; import com.google.ar.sceneform.ux.TransformableNode; public class MainActivity extends AppCompatActivity { private static final String TAG = MainActivity.class.getSimpleName(); private static final double MIN_OPENGL_VERSION = 3.0; ArFragment arFragment; ModelRenderable lampPostRenderable; @Override @SuppressWarnings({"AndroidApiChecker", "FutureReturnValueIgnored"}) protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (!checkIsSupportedDeviceOrFinish(this)) { return; } setContentView(R.layout.activity_main); arFragment = (ArFragment) getSupportFragmentManager().findFragmentById(R.id.ux_fragment); ModelRenderable.builder() .setSource(this, Uri.parse("LampPost.sfb")) .build() .thenAccept(renderable -> lampPostRenderable = renderable) .exceptionally(throwable -> { Toast toast = Toast.makeText(this, "Unable to load andy renderable", Toast.LENGTH_LONG); toast.setGravity(Gravity.CENTER, 0, 0); toast.show(); return null; }); arFragment.setOnTapArPlaneListener( (HitResult hitresult, Plane plane, MotionEvent motionevent) -> { if (lampPostRenderable == null){ return; } Anchor anchor = hitresult.createAnchor(); AnchorNode anchorNode = new AnchorNode(anchor); anchorNode.setParent(arFragment.getArSceneView().getScene()); TransformableNode lamp = new TransformableNode(arFragment.getTransformationSystem()); lamp.setParent(anchorNode); lamp.setRenderable(lampPostRenderable); lamp.select(); } ); } public static boolean checkIsSupportedDeviceOrFinish(final Activity activity) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { Log.e(TAG, "Sceneform requires Android N or later"); Toast.makeText(activity, "Sceneform requires Android N or later", Toast.LENGTH_LONG).show(); activity.finish(); return false; } String openGlVersionString = ((ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE)) .getDeviceConfigurationInfo() .getGlEsVersion(); if (Double.parseDouble(openGlVersionString) < MIN_OPENGL_VERSION) { Log.e(TAG, "Sceneform requires OpenGL ES 3.0 later"); Toast.makeText(activity, "Sceneform requires OpenGL ES 3.0 or later", Toast.LENGTH_LONG) .show(); activity.finish(); return false; } return true; } }
Congratulations! You have just completed creating your first ARCore application. Start adding objects to it and see how they start to come to life in the real world.
This was your first look at how to create a simple ARCore application from scratch in Android Studio. In the next tutorial, I will delve into ARCore and add more functionality to the application.
Source: https://habr.com/ru/post/438178/