sceneview-android icon indicating copy to clipboard operation
sceneview-android copied to clipboard

ViewNode - Rendering problem for the second time

Open konrad-thoc opened this issue 1 year ago • 8 comments

val viewNode = ViewNode(
            engine = sceneView.engine,
            modelLoader = sceneView.modelLoader,
            viewAttachmentManager = viewAttachmentManager,
        ).apply {
            disable()
            scale = Scale(-5f, 5f, 1f)
        }

        viewNode
            .loadView(
                context = this@ARActivity,
                layoutResId = R.layout.tracker_ar_view,
                onLoaded = { _, view ->
            
                    anchorNode.addChildNode(viewNode)

                    // Remove duplicates
                    val index = sceneView.childNodes.indexOfFirst { it.name == DESTINATION_NODE_NAME }
                    if (index != -1) sceneView.removeChildNode(sceneView.childNodes[index])

                    sceneView.addChildNode(anchorNode)
                    destinationNode = anchorNode
                },
                onError = {
                 
                }
            )

I have this code for rendering XML view, when I enter this screen for the first time everything works ok, however after the 2nd time I enter the screen I get an error.

Group 20764

konrad-thoc avatar Aug 16 '24 15:08 konrad-thoc

hi, I tried to reproduce your error, created an application using ARSceneView, restarted the scene many times, but apart from the Filament double free error, I got nothing else. Can I ask you to provide me with more data to reproduce the error?

rawello avatar Sep 05 '24 16:09 rawello

Yeah sure, the problem is when navigating back to a Fragment/Activity where you previously loaded an XML view using ViewNode.

Here is Initial App Activity, when we pressed the button we are redirected to MainActivity. After the ViewNode is loaded in MainActivity, you need to navigate back (in the example you should use the system navigation) and then navigate to MainActivity again, this causes an error

class SecondActivity : AppCompatActivity() {

    private lateinit var binding: ActivitySecondBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        binding = ActivitySecondBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.apply {
            button.setOnClickListener {
                val intent = Intent(this@SecondActivity, MainActivity::class.java)
                startActivity(intent)
            }
        }
    }
}

Here is MainActivity

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    private lateinit var viewAttachmentManager: ViewAttachmentManager

    private var trackerViewBinding: TrackerViewBinding? = null

    private var destinationNode: AnchorNode? = null
    private var directionArrow: AnchorNode? = null

    private var trackerLat: Double = 0.0 // REPLACE WITH YOUR COORDINATES
    private var trackerLng: Double = 0.0 // // REPLACE WITH YOUR COORDINATES
    private val distanceState = MutableStateFlow<Double>(0.0)
    private var lastDestinationRotation: Rotation? = null

    override fun onResume() {
        super.onResume()
        viewAttachmentManager.onResume()
    }

    override fun onPause() {
        super.onPause()
        viewAttachmentManager.onPause()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        setupView()
    }

    private fun setupView() {
        binding.apply {
            sceneView.apply {
                lifecycle = [email protected]
                planeRenderer.isEnabled = true
                this.mainLightNode
                // Set rendering distance to 200m
                cameraNode.far = 200f
                configureSession { session, config ->
                    config.focusMode = Config.FocusMode.AUTO
                    config.lightEstimationMode = Config.LightEstimationMode.DISABLED
                    config.depthMode = Config.DepthMode.DISABLED
                    config.planeFindingMode = Config.PlaneFindingMode.DISABLED
                    config.updateMode = Config.UpdateMode.LATEST_CAMERA_IMAGE
                    if (session.isGeospatialModeSupported(Config.GeospatialMode.ENABLED)) {
                        config.geospatialMode = Config.GeospatialMode.ENABLED
                    } else {
                        showErrorMessage("Geospital mode not supported on this device")
                    }
                }
                onSessionUpdated = { session, frame ->
                    updateStatusText(session)

                    drawDestinationTracker(session)
                    drawDirectionArrow(session, frame)
                }
                onTrackingFailureChanged = { reason ->
                    updateInstructions(reason)
                }
                viewAttachmentManager = ViewAttachmentManager(this@MainActivity, this)
            }
        }
    }


    private fun drawDestinationTracker(session: Session) {
        val sceneView = binding.sceneView

        if (destinationNode != null) {
            // Rotate node to camera
            val cameraNode = sceneView.cameraNode
            if (distanceState.value < 1 && lastDestinationRotation != null) {
                destinationNode!!.rotation = lastDestinationRotation!!
            } else {
                destinationNode!!.lookAt(cameraNode)
            }
            lastDestinationRotation = destinationNode!!.rotation
            return
        }

        val anchor = createEarthAnchor(session) ?: return
        val anchorNode = AnchorNode(sceneView.engine, anchor)
        // Set the node identifier
        anchorNode.name = Constants.DESTINATION_NODE_NAME
        ViewRenderable.builder()
            .setView(sceneView.context, R.layout.tracker_view)
            .build(sceneView.engine)
            .thenAccept { renderable ->
                trackerViewBinding = TrackerViewBinding.bind(renderable.view)

                val viewNode = ViewNode(
                    engine = sceneView.engine,
                    modelLoader = sceneView.modelLoader,
                    viewAttachmentManager = viewAttachmentManager
                ).apply {
                    setRenderable(renderable)
                    disable()
                    scale = Scale(-10f, 10f, 1f)
                }

                anchorNode.addChildNode(viewNode)

                // Remove duplicates
                val index = sceneView.childNodes.indexOfFirst { it.name == Constants.DESTINATION_NODE_NAME }
                if (index != -1) sceneView.removeChildNode(sceneView.childNodes[index])

                sceneView.addChildNode(anchorNode)
                destinationNode = anchorNode
            }
    }

    private fun createEarthAnchor(session: Session): Anchor? {
        val earth = session.earth ?: return null
        if (earth.trackingState != TrackingState.TRACKING) return null

        val altitude = earth.cameraGeospatialPose.altitude - 1

        return earth.createAnchor(
            trackerLat,
            trackerLng,
            altitude,
            0f, 0f, 0f, 1f,
        )
    }

    private fun drawDirectionArrow(session: Session, frame: Frame) {
        if (destinationNode == null) return
        if (frame.camera.trackingState == TrackingState.TRACKING) {
            val pose = frame.camera.pose
                .compose(Pose.makeTranslation(0.3f, 0f, -1f))
                .extractTranslation()
            val anchor = session.createAnchor(pose)

            if (directionArrow != null) {
                directionArrow!!.anchor = anchor
                // Rotate node to destination
                directionArrow!!.lookAt(destinationNode!!)
                return
            }

            val sceneView = binding.sceneView
            val anchorNode = AnchorNode(sceneView.engine, anchor)
            // Set the node identifier
            anchorNode.name = Constants.DIRECTION_ARROW_NODE_NAME
            lifecycleScope.launch {
                val modelInstance =
                    sceneView.modelLoader.loadModelInstance(Constants.DIRECTION_ARROW_FILE)
                if (modelInstance != null) {
                    val modelNode = ModelNode(
                        modelInstance = modelInstance,
                        scaleToUnits = 0.3f,
                        centerOrigin = Position(y = -0.5f),
                    )
                    modelNode.disable()
                    anchorNode.addChildNode(modelNode)

                    // Remove duplicates
                    val index = sceneView.childNodes.indexOfFirst { it.name == Constants.DIRECTION_ARROW_NODE_NAME }
                    if (index != -1) sceneView.removeChildNode(sceneView.childNodes[index])

                    sceneView.addChildNode(anchorNode)
                    anchorNode.lookAt(destinationNode!!)
                    directionArrow = anchorNode
                } else {
                    showErrorMessage("Unable to load model")
                }
            }
        }
    }

    private fun updateStatusText(session: Session) {
        val earth = session.earth
        if (earth?.trackingState == TrackingState.TRACKING) {
            val cameraGeospatialPose = earth.cameraGeospatialPose
            val geospitalPose = getString(
                R.string.geospatial_pose,
                cameraGeospatialPose.latitude,
                cameraGeospatialPose.longitude,
                cameraGeospatialPose.horizontalAccuracy,
                cameraGeospatialPose.altitude,
                cameraGeospatialPose.verticalAccuracy,
                cameraGeospatialPose.heading,
                cameraGeospatialPose.headingAccuracy,
            )
            binding.statusText.text = resources.getString(
                R.string.earth_state,
                earth.earthState.toString(),
                earth.trackingState.toString(),
                geospitalPose,
            )

//            val distanceToTracker = DistanceCalculator.calculateDistance(
//                cameraGeospatialPose.latitude,
//                cameraGeospatialPose.longitude,
//                trackerLat,
//                trackerLng,
//            )
//            distanceState.value = distanceToTracker
//
//            binding.accuracyText.text = "Accuracy: ${cameraGeospatialPose.getAccuracy()}"
//            trackerViewBinding?.trackerDistance?.text = "${String.format("%.1f", distanceToTracker)} m"
        }
    }

    private fun updateInstructions(reason: TrackingFailureReason?) {
        binding.instructionText.text = reason?.getDescription(this) ?: ""
    }

    private fun showErrorMessage(error: String) {
        Toast.makeText(this, error, Toast.LENGTH_SHORT).show()
    }
}


konrad-thoc avatar Sep 05 '24 18:09 konrad-thoc

Sorry for late answer, did you try full debug step by step drawDestinationTracker in ViewRenderable builder part?

rawello avatar Sep 07 '24 19:09 rawello

No, but I'm sure there is a problem with ViewRenderable, because after replacing it and loading the 3D model, everything works fine. It is also easy to reproduce, you have to create 2 activities, and on the second one you have to load the view node using ViewRenderable, then you have to press the system back button, and then you have to go to the second activity again. Then there is a crash which cannot even be caught

konrad-thoc avatar Sep 19 '24 07:09 konrad-thoc

I have the same issue.Any news?

Lorenzoarc avatar Sep 24 '24 15:09 Lorenzoarc

no new information so far

konrad-thoc avatar Sep 24 '24 16:09 konrad-thoc

@rawello are you able to tell if it can be fixed?

konrad-thoc avatar Oct 11 '24 09:10 konrad-thoc

@rawello are you able to tell if it can be fixed?

Hi man, I have deadlines at work, last time when I tested this code, I was unable to reproduce the error, a little later I will try to solve your problem if this will still relevant, I really want to help and do PR but so much work

rawello avatar Oct 14 '24 17:10 rawello

@rawello are you able to tell if it can be fixed?

@KonradTHOC Hello man, I downloaded the sources, installed them in my project, there was an error before that when swiping back in “Compose” ARScene you could get a crash, I looked through the code and came to strange solutions, maybe I missed the point somewhere, but, to begin with updated all libraries except "Filament" and in some cases in ARScene change LocalLifecycleOwner to androidx.lifecycle.compose.LocalLifecycleOwner . Actually, I uncommented one line, wrapped part of the code in “Try-catch” and added a couple of lines to “ARScene”. So in ARScene.kt am added

onRelease = { sceneView ->
                try {
                    Log.d("ARScene", "Releasing ARSceneView")
                    sceneView.arCore.destroy()
                    sceneView.session?.close()
                    sceneView.scene.removeEntities(sceneView.scene.entities)
                    sceneView.destroy()
                }
                catch (e:Exception){
                    e.message?.let { Log.e("error arscene", it) }
                }
            }

And in SceneView.kt im uncommented runCatching { ResourceManager.getInstance().destroyAllResources() }

ps, sorry for my leg in video xd)

https://github.com/user-attachments/assets/8a0d8a6b-4f89-4fcc-b897-8b0c8e33e4bd

upd: in video 6 modelnodes connected each others with a lot of ViewNode

rawello avatar Nov 17 '24 15:11 rawello

@rawello I am using XML views so I don't have such a method. What method should I use instead of that? image

konrad-thoc avatar Nov 22 '24 14:11 konrad-thoc

@konrad-thoc oh I'm using compose, try adding similar code to onDestroy() with the order of destroy as I presented

rawello avatar Nov 22 '24 14:11 rawello

@rawello yeah it seems to be working with onDestroy method. Thanks!!

    private fun destroySceneView() {
        try {
            val sceneView = binding.arSceneView
            sceneView.arCore.destroy()
            sceneView.session?.close()
            sceneView.scene.removeEntities(sceneView.scene.entities)
            sceneView.destroy()
        } catch (e: Exception) {
            Timber.d("$this - cannot destroy SceneView")
        }
    }

konrad-thoc avatar Nov 22 '24 14:11 konrad-thoc

@konrad-thoc if its will work, notify me, i will to PR

rawello avatar Nov 22 '24 14:11 rawello

problem solved, thanks

konrad-thoc avatar Nov 22 '24 16:11 konrad-thoc

hi @rawello , I use compose and happen error when navigate popBack, for onRelease callback, should I download source and modify?

hi, yeap, you should download source, add it to your project as module and implement local copy with fix to your main project(via build.gradle and android studio UI - project structure you can add module)

sorry i havent enough time for PR that(

rawello avatar Dec 22 '24 10:12 rawello