google-ar / sceneform-android-sdk

Sceneform SDK for Android
https://developers.google.com/sceneform/develop/
Apache License 2.0
1.23k stars 604 forks source link

GLTF Model not rendering using reusable class #1062

Closed rajhraval closed 4 years ago

rajhraval commented 4 years ago

I am trying to load a .gltf model remotely using Sceneform by using a reusable class and then creating an instance of the class in MainActivity and calling loadModel() method

ModelLoader.kt

import android.app.Activity
import android.app.AlertDialog
import android.net.Uri
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.google.ar.core.Anchor
import com.google.ar.sceneform.AnchorNode
import com.google.ar.sceneform.assets.RenderableSource
import com.google.ar.sceneform.rendering.ModelRenderable
import com.google.ar.sceneform.ux.ArFragment
import com.google.ar.sceneform.ux.TransformableNode
import java.lang.ref.WeakReference

class ModelLoader internal constructor(owner: WeakReference<Activity>){

    private val owner: WeakReference<Activity>

    init {
        this.owner = owner
    }

    internal fun loadModel(fragment: ArFragment, anchor: Anchor, assetURL: String, assetSize: Float) {

        val activity = owner.get()

        if (activity == null) {
            Log.d("Model Loader", "Activity is null, cannot load the model")
        }

        val modelRenderable = ModelRenderable.builder()
            .setSource((fragment.requireContext()), RenderableSource.builder().setSource(
                (fragment.requireContext()),
                Uri.parse(assetURL),
                RenderableSource.SourceType.GLTF2)
                .setScale(assetSize)
                .setRecenterMode(RenderableSource.RecenterMode.ROOT)
                .build())
            .setRegistryId(assetURL)
            .build()

        modelRenderable.thenAccept { renderableObject ->
            val anchorNode = AnchorNode(anchor)
            val transformableNode = TransformableNode(fragment.transformationSystem)
            renderableObject.isShadowCaster = false
            renderableObject.isShadowReceiver = false
            transformableNode.renderable = renderableObject
            fragment.arSceneView.scene.addChild(anchorNode)
            transformableNode.select()
        }

        modelRenderable.exceptionally {
            val builder = AlertDialog.Builder(activity)
            builder.setMessage(it.message)
                .setTitle("Fetch Error")
            val dialog = builder.create()
            dialog.show()
            null
        }

        return

    }

}

The example of the class in use is given in the Link in Java and also it states a Weak Reference of the MainActivity. I want to use ModelLoader class for other activities too, so I gave a weak reference type of Activity instead of Main Activity.

MainActivity.kt

import android.app.Activity
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.google.ar.core.Plane
import com.google.ar.sceneform.ux.ArFragment
import java.lang.ref.WeakReference

class MainActivity : AppCompatActivity() {

    private val gltfAsset = "https://raw.githubusercontent.com/rajhraval1/AR-Assets/master/stool/Stool_01.gltf"
    private val assetSize = 0.075f

    private lateinit var modelLoader: ModelLoader
    private lateinit var arFragment: ArFragment

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        modelLoader = ModelLoader(owner = WeakReference<Activity>(this))

        arFragment = supportFragmentManager.findFragmentById(R.id.sceneform_fragment) as ArFragment
        arFragment.arSceneView.planeRenderer.isVisible = true
        arFragment.setOnTapArPlaneListener { hitResult, plane, _ ->

            if (plane.type != Plane.Type.HORIZONTAL_UPWARD_FACING) {
                return@setOnTapArPlaneListener
            }

            val anchor = hitResult.createAnchor()
            modelLoader.loadModel(arFragment, anchor, gltfAsset, assetSize)

        }

    }

}

The result is I can only see the plane renderer visible in the tracking state and the model is not getting rendered. I tried to check using Log.d it showed that the model was loaded. But yet not shown on the screen.

What is the problem?

tellypresence commented 4 years ago

Looks like your transformableNode is a local variable in the callback? You will need to make sure that the renderable is assigned to a node available in your fragment.

rajhraval commented 4 years ago

Yes, I have used transformable node locally in the thenAccept (closure: I am new in Kotlin, using my Swift Knowledge to compare that)

and the model renderable is assigned to the transformableNode renderable.

I have used the same method closures ie: builder(), thenAccept, exceptionally in Main Activity, it worked there.

rajhraval commented 4 years ago

I tried a workaround.

I replaced class ModelLoader internal constructor(owner: WeakReference<Activity>)

to

class ModelLoader (val fragment: ArFragment)

It works for me and the model is rendered.