Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

need to update redndered glb model dynamically #590

Open
arun-ibosoninnov opened this issue Jan 16, 2025 · 0 comments
Open

need to update redndered glb model dynamically #590

arun-ibosoninnov opened this issue Jan 16, 2025 · 0 comments

Comments

@arun-ibosoninnov
Copy link

i have three models moveleft, moveright and movefoward. am rendering a model directly in front of camera. this model muct change dynamically based of values that getting changed in

if (step1Details != null && step2Details != null) {
val currentDistance = step1Details.distance
val nextManeuver = step2Details.maneuver

        // Determine the instruction based on the current distance and next maneuver
        instructionMessage = when {
            currentDistance.contains("m") && nextManeuver.equals("turn-left", ignoreCase = true) -> {
                "Take Left turn in $currentDistance"
            }
            currentDistance.contains("m") && nextManeuver.equals("turn-right", ignoreCase = true) -> {
                "Take Right turn in $currentDistance"
            }
            else -> {
                "Go Straight!"
            }
        }

        // Log the instruction to console (optional)
        Log.d("NavigationInstruction", instructionMessage)
    }

moveForward , moveRight , moveLeft these values will changes in this method dynamically at that time I need to replace my gib model that's being rendered. my question is that I need to delete the model node or can I update the exsn g nod itself. The complete source code is given below

private const val kAperture = 16f
private const val kShutterSpeed = 1f / 125f
private const val kSensitivity = 100f
private const val moveForward = "models/map_way.glb"

private const val moveLeft = "models/left_arrow.glb"

private const val moveRight = "models/right_arrow.glb"

 var currentModel = moveForward



var isObjectRendered = false

class MainActivity : ComponentActivity() {

    private lateinit var fusedLocationClient: FusedLocationProviderClient
    private lateinit var locationCallback: LocationCallback
    private var locationRequired: Boolean = false
    private val permissions = arrayOf(
        android.Manifest.permission.ACCESS_FINE_LOCATION,
        android.Manifest.permission.ACCESS_COARSE_LOCATION,
    )

    override fun onResume() {
        super.onResume()
        if (locationRequired) {
            startLocationUpdates()
        }
    }
    override fun onPause() {
        super.onPause()
        locationCallback?.let {
            fusedLocationClient?.removeLocationUpdates(it)
        }
    }

    @SuppressLint("MissingPermission")
    private fun startLocationUpdates() {
        locationCallback?.let {
            val locationRequest = LocationRequest.Builder(
                Priority.PRIORITY_HIGH_ACCURACY, 100
            )
                .setWaitForAccurateLocation(false)
                .setMinUpdateIntervalMillis(3000)
                .setMaxUpdateDelayMillis(100)
                .build()

            fusedLocationClient?.requestLocationUpdates(
                locationRequest,
                it,
                Looper.getMainLooper()
            )
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        MapsInitializer.initialize(this, MapsInitializer.Renderer.LATEST) {

        }
        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
        setContent {

            var currentLocation by remember { mutableStateOf(LatLng(0.toDouble(), 0.toDouble())) }
            val cameraPosition = rememberCameraPositionState {
                position = CameraPosition.fromLatLngZoom(
                    currentLocation, 10f
                )
            }
            val origin = LatLng(
                8.4999486,76.6122943
            )//You can add your area location it's for camera position

            val cameraPositionStateNew = rememberCameraPositionState {
                position = CameraPosition.fromLatLngZoom(origin, 10f)
            }


            var cameraPositionState by remember {
                mutableStateOf(cameraPosition)
            }
            locationCallback = object : LocationCallback() {
                override fun onLocationResult(p0: LocationResult) {
                    super.onLocationResult(p0)
                    for (location in p0.locations) {
                        currentLocation = LatLng(location.latitude, location.longitude)
                        // Get the accuracy radius from the location object
                        cameraPositionState = CameraPositionState(
                            position = CameraPosition.fromLatLngZoom(
                                currentLocation, 20f
                            )
                        )
                    }
                }
            }
            GoogleNavigationAppTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    LocationScreen(this@MainActivity, currentLocation, cameraPositionStateNew)
                }
            }
        }
    }


    @OptIn(ExperimentalMaterial3Api::class)
    @Composable
    private fun LocationScreen(
        context: Context,
        currentLocation: LatLng,
        cameraPositionState: CameraPositionState,
        viewModel: LocationViewModel = viewModel()
    ) {


        val latLngList = remember { mutableStateOf<List<LatLng>>(emptyList()) }
        val destination = remember { mutableStateOf(LatLng(8.4814699,76.941563)) }
        val circlePoints = viewModel.generateCirclePoints(currentLocation, 20.00)
        val routeSteps = remember { mutableStateOf<List<RouteStep>>(emptyList()) }

        val transparentSkyBlue = Color(0x3F00BFFF)


        var orientationAngle by remember { mutableStateOf(0f) } // Initial orientation angle
        var azimuthDegrees by remember { mutableStateOf(0f) }
        val sensorManager = remember { context.getSystemService(Context.SENSOR_SERVICE) as SensorManager }
        val accelerometerSensor = remember { sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) }
        val magnetometerSensor = remember { sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) }

        val originalBitmap: Bitmap = remember { BitmapFactory.decodeResource(context.resources, R.drawable.navigation) }
        val desiredWidth = 150 // Specify the desired width in pixels
        val desiredHeight = 150 // Specify the desired height in pixels
        val resizedBitmap: Bitmap = remember { Bitmap.createScaledBitmap(originalBitmap, desiredWidth, desiredHeight, true) }
        val rotatedBitmap: Bitmap = remember(orientationAngle) { viewModel.rotateBitmap(resizedBitmap, orientationAngle) }
        val bitmapDescriptor: BitmapDescriptor = remember(rotatedBitmap) { BitmapDescriptorFactory.fromBitmap(rotatedBitmap) }
        val step1Details = routeSteps.value.getOrNull(0)
        val step2Details = routeSteps.value.getOrNull(1)

        val sensorEventListener = remember {
            object : SensorEventListener {
                private val gravity = FloatArray(3)
                private val geomagnetic = FloatArray(3)
                private var lastUpdateTime = 0L

                override fun onSensorChanged(event: SensorEvent) {
                    val currentTime = System.currentTimeMillis()
                    if (currentTime - lastUpdateTime < 100) { // Throttle updates to occur every 100 milliseconds
                        return
                    }

                    lastUpdateTime = currentTime

                    when (event.sensor.type) {
                        Sensor.TYPE_ACCELEROMETER -> gravity.apply { event.values.copyInto(this) }
                        Sensor.TYPE_MAGNETIC_FIELD -> geomagnetic.apply { event.values.copyInto(this) }
                    }

                    val rotationMatrix = FloatArray(9)
                    val success = SensorManager.getRotationMatrix(rotationMatrix, null, gravity, geomagnetic)
                    if (success) {
                        val orientation = FloatArray(3)
                        SensorManager.getOrientation(rotationMatrix, orientation)
                        val azimuthRadians = orientation[0].toDouble()
                        azimuthDegrees = Math.toDegrees(azimuthRadians).toFloat()
                        orientationAngle = azimuthDegrees // Update the orientation angle
                    }
                }

                override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
            }
        }

        val launchMultiplePermissions =
            rememberLauncherForActivityResult(ActivityResultContracts.RequestMultiplePermissions())
            { permissionMaps ->
                val areGranted = permissionMaps.values.reduce { acc, next -> acc && next }
                if (areGranted) {
                    locationRequired = true
                    startLocationUpdates()
                    Toast.makeText(context, "Permission Granted", Toast.LENGTH_SHORT).show()
                } else {
                    Toast.makeText(context, "Permission Denied", Toast.LENGTH_SHORT).show()
                }
            }

        // Define a function to calculate and display the route to the clicked marker
        suspend fun showRouteToMarker(markerLocation: LatLng) {

            startLocationUpdates()
            // Fetch route from current location to clicked marker's location
            latLngList.value = viewModel.fetchRoute(
                "${currentLocation.latitude},${currentLocation.longitude}",
                "${markerLocation.latitude},${markerLocation.longitude}"
            )!!

            //get steps
            routeSteps.value= viewModel.fetchSteps(
                "${currentLocation.latitude},${currentLocation.longitude}",
                "${destination.value.latitude},${destination.value.longitude}"
            )!!
        }


        LaunchedEffect(currentLocation) {
            if (permissions.all {
                    ContextCompat.checkSelfPermission(
                        context,
                        it
                    ) == PackageManager.PERMISSION_GRANTED
                }) {

                //get location
                startLocationUpdates()
                //get route
                latLngList.value=viewModel.fetchRoute(
                    "${currentLocation.latitude},${currentLocation.longitude}",
                    "${destination.value.latitude},${destination.value.longitude}"
                )!!
                //get steps
                routeSteps.value= viewModel.fetchSteps(
                    "${currentLocation.latitude},${currentLocation.longitude}",
                    "${destination.value.latitude},${destination.value.longitude}"
                )!!
            } else {
                launchMultiplePermissions.launch(permissions)
            }
        }

        LaunchedEffect(destination.value) {
            destination.value?.let { markerLocation ->
                showRouteToMarker(markerLocation)
            }
        }

        LaunchedEffect(orientationAngle) {
            orientationAngle = azimuthDegrees // Update the orientation angle
            // Delay to throttle updates
            delay(100) // Adjust the delay time as needed
        }

        DisposableEffect(Unit) {
            // Register sensor listeners with optimized sampling rate
            sensorManager.registerListener(sensorEventListener, accelerometerSensor, SensorManager.SENSOR_DELAY_UI)
            sensorManager.registerListener(sensorEventListener, magnetometerSensor, SensorManager.SENSOR_DELAY_UI)

            onDispose {
                // Unregister sensor listeners
                sensorManager.unregisterListener(sensorEventListener)
            }
        }


        val engine = rememberEngine()
        val modelLoader = rememberModelLoader(engine)
        val materialLoader = rememberMaterialLoader(engine)
        val cameraNode = rememberARCameraNode(engine)
        val childNodes = rememberNodes()
        val view = rememberView(engine)
        val collisionSystem = rememberCollisionSystem(view)

        var planeRenderer by remember { mutableStateOf(true) }

        var trackingFailureReason by remember { mutableStateOf<TrackingFailureReason?>(null) }
        var frame by remember { mutableStateOf<Frame?>(null) }

        // Variable to hold the navigation instruction
        var instructionMessage by remember { mutableStateOf("") }

        if (step1Details != null && step2Details != null) {
            val currentDistance = step1Details.distance
            val nextManeuver = step2Details.maneuver

            // Determine the instruction based on the current distance and next maneuver
            instructionMessage = when {
                currentDistance.contains("m") && nextManeuver.equals("turn-left", ignoreCase = true) -> {
                    "Take Left turn in $currentDistance"
                }
                currentDistance.contains("m") && nextManeuver.equals("turn-right", ignoreCase = true) -> {
                    "Take Right turn in $currentDistance"
                }
                else -> {
                    "Go Straight!"
                }
            }

            // Log the instruction to console (optional)
            Log.d("NavigationInstruction", instructionMessage)
        }

        Box(modifier = Modifier.fillMaxSize()) {

            ARScene(
                modifier = Modifier.fillMaxSize(),
                childNodes = childNodes,
                engine = engine,
                view = view,
                modelLoader = modelLoader,
                collisionSystem = collisionSystem,
                sessionConfiguration = { session, config ->
                    config.depthMode =
                        when (session.isDepthModeSupported(Config.DepthMode.AUTOMATIC)) {
                            true -> Config.DepthMode.AUTOMATIC
                            else -> Config.DepthMode.DISABLED
                        }
                    config.instantPlacementMode = Config.InstantPlacementMode.LOCAL_Y_UP
                    config.lightEstimationMode = Config.LightEstimationMode.ENVIRONMENTAL_HDR
                },
                cameraNode = cameraNode,
                planeRenderer = false,
                onTrackingFailureChanged = {
                    trackingFailureReason = it
                },
                onSessionUpdated = { session, updatedFrame ->
                    frame = updatedFrame

                    if (updatedFrame.camera.trackingState == TrackingState.TRACKING && !isObjectRendered) {
                        // Create the pose for placing the object
                        val cameraPose = updatedFrame.camera.pose
                        val translation = FloatArray(3).apply { cameraPose.getTranslation(this, 0) }
                        val rotation = FloatArray(4).apply { cameraPose.getRotationQuaternion(this, 0) }
                        translation[2] -= 1.0f // Place 1 meter in front of the camera

                        val anchorPose = Pose(translation, rotation)
                        val anchor = session.createAnchor(anchorPose)

                        childNodes += createAnchorNode(
                            engine = engine,
                            modelLoader = modelLoader,
                            materialLoader = materialLoader,
                            anchor = anchor
                        )

                        isObjectRendered = true
                    } else {
                        Log.w("ARScene", "Camera is not tracking. Unable to place object.")
                    }

                },
            )


            androidx.compose.material.Text(
                modifier = Modifier
                    .systemBarsPadding()
                    .fillMaxWidth()
                    .align(Alignment.TopCenter)
                    .padding(top = 16.dp, start = 32.dp, end = 32.dp),
                textAlign = TextAlign.Center,
                fontSize = 28.sp,
                color = Color.White,
                text = if (instructionMessage.isNotEmpty()) {
                    instructionMessage // Display navigation instruction here
                } else {
                    trackingFailureReason?.let {
                        it.getDescription(LocalContext.current)
                    } ?: if (childNodes.isEmpty()) {
                        stringResource(R.string.point_your_phone_down)
                    } else {
                        stringResource(R.string.tap_anywhere_to_add_model)
                    }
                }
            )

}
    }
}

fun createAnchorNode(
    engine: Engine,
    modelLoader: ModelLoader,
    materialLoader: MaterialLoader,
    anchor: Anchor
): AnchorNode {
    val anchorNode = AnchorNode(engine = engine, anchor = anchor)
    val modelNode = ModelNode(
        modelInstance = modelLoader.createModelInstance(currentModel),
        // Scale to fit in a 0.5 meters cube
        scaleToUnits = 0.5f
    ).apply {
        // Model Node needs to be editable for independent rotation from the anchor rotation
        isEditable = true
        editableScaleRange = 0.2f..0.75f
    }

    modelNode.rotation = Rotation(x = 0.0f, y = 0.0f, z = -90.0f) // Adjust as needed

    anchorNode.addChildNode(modelNode)


    return anchorNode
}

@OptIn(ExperimentalLayoutApi::class, ExperimentalMaterial3Api::class)
@Composable
fun BoxScope.ChipsGroup(labels: List<String>, onSelected: (index: Int) -> Unit) {
    var selectedIndex by remember { mutableIntStateOf(0) }
    FlowRow(
        modifier = Modifier
            .fillMaxWidth()
            .align(Alignment.BottomEnd)
            .background(MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.5f))
            .navigationBarsPadding()
            .padding(8.dp)
    ) {

        labels.forEachIndexed { index, label ->
            val selected = selectedIndex == index
            FilterChip(
                label = {
                    Text(
                        style = MaterialTheme.typography.bodyLarge.copy(),
                        text = label
                    )
                },
                modifier = Modifier.padding(4.dp),
                selected = selected,
                onClick = {
                    selectedIndex = index
                    onSelected(index)
                },
                leadingIcon = if (selected) {
                    {
                        Icon(
                            imageVector = Icons.Filled.Done,
                            contentDescription = "Done icon",
                            modifier = Modifier.size(FilterChipDefaults.IconSize)
                        )
                    }
                } else {
                    null
                }
            )
        }
    }
}



Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant