Updated 16 June 2023
The 3D model can in the form of GLB or GLTF format and these 3D models can be loaded at runtime without conversion. Which is an improvement in the flexibility of the models rendered in the application.
In this blog, we will be using the GLB format because the GLTF model is dependent on there .bin file which has to be saved with its corresponding file but a GLB file contains a 3D model saved in the GL Transmission Format (glTF). It stores information about a 3D model, such as node hierarchy, cameras, and materials, animations, and meshes in binary format. GLB files are the binary version of .gltf files.
Copy all the files from the model(.gltf and .bin file) and drag on this link: Click here
1 |
classpath 'com.android.tools.build:gradle:3.6.3' |
1 2 |
implementation 'com.google.ar.sceneform.ux:sceneform-ux:1.15.0' implementation 'com.google.ar.sceneform:assets:1.15.0' |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<!-- Features --> <uses-feature android:name="android.hardware.camera" android:required="false" /> <uses-feature android:name="android.hardware.camera.autofocus" android:required="false" /> <uses-feature android:name="android.hardware.camera.ar" android:required="false" /> <application android:icon="@mipmap/ic_launcher" android:label="@string/app_name" > ------- <!-- AR Core --> <meta-data android:name="com.google.ar.core" android:value="optional" /> </application> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
class MainActivity : AppCompatActivity() { private lateinit var mContentViewBinding: ActivityArBinding private var arFragment: ArFragment? = null private var objectRenderable: ModelRenderable? = null private var arModel: String? = null private var mModelStateSnackBar: Snackbar? = null private lateinit var mModelCompletableFuture: CompletableFuture<Void> var anchorNode: AnchorNode? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arModel = "AR_MODEL_URL" if (checkIsSupportedDeviceOrFinish(this)) { mContentViewBinding = DataBindingUtil.setContentView(this, R.layout.activity_ar) startInitialization() } else { ToastHelper.showToast(this, getString(R.string.the_ar_feature_is_not_supported_by_your_device)) this.finish() } } private fun startInitialization() { try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { try { arFragment = supportFragmentManager.findFragmentById(R.id.ux_fragment) as ArFragment Toast.makeText(this@ArActivity, getString(R.string.downloading_model), Toast.LENGTH_SHORT).show() // Init renderable loadModel() // Set tap listener arFragment!!.setOnTapArPlaneListener { hitResult: HitResult, plane: Plane?, motionEvent: MotionEvent? -> val anchor = hitResult.createAnchor() if (anchorNode == null) { anchorNode = AnchorNode(anchor) anchorNode?.setParent(arFragment!!.arSceneView.scene) createModel() } } } catch (e: Exception) { e.printStackTrace() } } else { val permissions = arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE) requestPermissions(permissions, RC_AR) } } } catch (e: java.lang.Exception) { Log.d("TAG", e.printStackTrace().toString() + e.message.toString()) } } private fun createModel() { try { if (anchorNode != null) { val node = TransformableNode(arFragment!!.transformationSystem) node.scaleController.maxScale = 0.02f node.scaleController.minScale = 0.01f node.setParent(anchorNode) node.renderable = objectRenderable node.select() mModelStateSnackBar = Snackbar.make(mContentViewBinding.arLayout, getString(R.string.model_ready), Snackbar.LENGTH_INDEFINITE).setAction(getString(R.string.dismiss)) { mModelStateSnackBar?.dismiss() } } } catch (e: Exception) { Toast.makeText(this@ArActivity, getString(R.string.something_went_wrong), Toast.LENGTH_SHORT).show() } } @RequiresApi(api = Build.VERSION_CODES.N) private fun loadModel() { try { ModelRenderable.builder() .setSource(this, RenderableSource.builder().setSource( this, Uri.parse(arModel), RenderableSource.SourceType.GLB)/*RenderableSource.SourceType.GLTF2)*/ .setScale(0.75f) .setRecenterMode(RenderableSource.RecenterMode.ROOT) .build()) .setRegistryId(arModel) .build() .thenAccept { renderable: ModelRenderable -> objectRenderable = renderable Toast.makeText(this@ArActivity, getString(R.string.model_ready), Toast.LENGTH_SHORT).show() } .exceptionally { throwable: Throwable? -> Log.i("Model", "cant load") mModelStateSnackBar = Snackbar.make(mContentViewBinding.arLayout, getString(R.string.model_error), Snackbar.LENGTH_INDEFINITE).setAction(getString(R.string.try_again)) { mModelStateSnackBar?.dismiss() } null } } catch (e: Exception) { e.printStackTrace() } } private fun checkIsSupportedDeviceOrFinish(activity: Activity): Boolean { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { Log.e("error", "Sceneform requires Android N or later") return false } val openGlVersionString = (activity.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager) .deviceConfigurationInfo .glEsVersion if (java.lang.Double.parseDouble(openGlVersionString) < MIN_OPENGL_VERSION) { Log.e("error", "Sceneform requires OpenGL ES 3.1 later") return false } return true } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <RelativeLayout android:id="@+id/ar_layout" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:id="@+id/ux_fragment" android:name="com.google.ar.sceneform.ux.ArFragment" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_below="@id/toolbar" android:tag="ar_fragment" /> </RelativeLayout> </layout> |
Now you just have to pass the .glb/.gltf model URL on the “arModel” string. Now just run your app.
. . . . . . . . . .
That’s it from my side for today, thanks for reading it until now.
If you have more details or questions, you can reply to the received confirmation email.
Back to Home
4 comments
Hey,
Firstly, it depends on how you have imported the 3d model.
If you are seeing only the 3D model, then most probably the animations were not added in the glb/gltf file.
To make sure that your gltf file is proper and has animations while importing, open the gltf file in text editor and then check for the tag named “animations”.
If this tag is present, then you need to check the android end code further.
Supposedly, your glb/gltf file is correct and upto the mark, then in the android code you will need to get access to animation that you want to run by calling getAnimationData Function on your ModelRenderable object.
Now you will need to create an instance of ModelAnimator and pass both animationObject and Renderable object as arguments to the constructor of ModelAnimator.
Now you need to call the start function over ModelAnimator object and it should work.
Still if you face any issue, then please feel free to reply back over here.