diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7cee73d..09aa707 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -21,12 +21,12 @@ plugins { android { namespace = "com.example.helloandroidxr" - compileSdk = 36 + compileSdk = 37 defaultConfig { applicationId = "com.example.helloandroidxr" minSdk = 24 - targetSdk = 36 + targetSdk = 37 versionCode = 1 versionName = "1.0" diff --git a/app/src/main/java/com/example/helloandroidxr/ui/HelloAndroidXRApp.kt b/app/src/main/java/com/example/helloandroidxr/ui/HelloAndroidXRApp.kt index 3666e13..2cdea44 100644 --- a/app/src/main/java/com/example/helloandroidxr/ui/HelloAndroidXRApp.kt +++ b/app/src/main/java/com/example/helloandroidxr/ui/HelloAndroidXRApp.kt @@ -55,8 +55,8 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.window.core.layout.WindowSizeClass +import androidx.xr.compose.platform.LocalSession import androidx.xr.compose.platform.LocalSpatialCapabilities -import androidx.xr.compose.platform.LocalSpatialConfiguration import androidx.xr.compose.spatial.ContentEdge import androidx.xr.compose.spatial.Orbiter import androidx.xr.compose.spatial.Subspace @@ -69,16 +69,12 @@ import androidx.xr.compose.subspace.layout.fillMaxSize import androidx.xr.compose.subspace.layout.fillMaxWidth import androidx.xr.compose.subspace.layout.height import androidx.xr.compose.subspace.layout.movable -import androidx.xr.compose.subspace.layout.offset import androidx.xr.compose.subspace.layout.padding import androidx.xr.compose.subspace.layout.resizable -import androidx.xr.compose.subspace.layout.rotate import androidx.xr.compose.subspace.layout.size import androidx.xr.compose.subspace.layout.width -import androidx.xr.runtime.math.Quaternion import com.example.helloandroidxr.R import com.example.helloandroidxr.ui.components.BugdroidControls -import com.example.helloandroidxr.ui.components.BugdroidModel import com.example.helloandroidxr.ui.components.BugdroidSliderControls import com.example.helloandroidxr.ui.components.EnvironmentControls import com.example.helloandroidxr.ui.components.SearchBar @@ -95,7 +91,14 @@ import kotlinx.coroutines.launch @Composable fun HelloAndroidXRApp() { - val viewModel = BugdroidViewModel() + val viewModel = remember { BugdroidViewModel() } + val session = LocalSession.current + LaunchedEffect(session) { + if (session != null) { + viewModel.initSession(session) + } + } + val uiState by viewModel.uiState.collectAsState() if (LocalSpatialCapabilities.current.isSpatialUiEnabled) { SpatialLayout( @@ -344,7 +347,6 @@ private fun PrimaryContent( if (uiState.showBugdroid) R.string.hide_bugdroid else R.string.show_bugdroid val animateStringResId = if (uiState.animateBugdroid) R.string.stop_animation_bugdroid else R.string.animate_bugdroid - val modelTransform = uiState.modelTransform Surface(modifier.fillMaxSize()) { Column(modifier.padding(48.dp), horizontalAlignment = Alignment.CenterHorizontally) { Box(modifier.padding(48.dp), contentAlignment = Alignment.Center) { @@ -371,26 +373,6 @@ private fun PrimaryContent( } } } - BugdroidModel( - modelTransform = modelTransform, - showBugdroid = uiState.showBugdroid, - animateBugdroid = uiState.animateBugdroid, - modifier = SubspaceModifier - .fillMaxSize() - .rotate( - Quaternion( - x = modelTransform.rotation.x, - y = modelTransform.rotation.y, - z = modelTransform.rotation.z, - w = modelTransform.rotation.w - ) - ) - .offset( - x = modelTransform.offset.x.dp, - y = modelTransform.offset.y.dp, - z = modelTransform.offset.z.dp // Relative position from the panel - ) - ) } } } else { @@ -408,7 +390,7 @@ private fun BlockOfContentOne( onSliderGroupSelected: (SliderGroup) -> Unit, onResetModel: () -> Unit ) { - if (LocalSpatialConfiguration.current.hasXrSpatialFeature && showBugdroid) { + if (LocalSpatialCapabilities.current.isContent3dEnabled && showBugdroid) { BugdroidControls( onSliderGroupSelected = onSliderGroupSelected, onResetModel = { @@ -433,7 +415,7 @@ private fun BlockOfContentTwo( onMaterialColorChange: (ModelMaterialColor) -> Unit, onMaterialPropertiesChange: (ModelMaterialProperties) -> Unit, ) { - if (LocalSpatialConfiguration.current.hasXrSpatialFeature && showBugdroid) { + if (LocalSpatialCapabilities.current.isContent3dEnabled && showBugdroid) { BugdroidSliderControls( visibleSliderGroup = uiState.visibleSliderGroup, modelTransform = uiState.modelTransform, diff --git a/app/src/main/java/com/example/helloandroidxr/ui/components/BugdroidModel.kt b/app/src/main/java/com/example/helloandroidxr/ui/components/BugdroidModel.kt deleted file mode 100644 index e0bcd0f..0000000 --- a/app/src/main/java/com/example/helloandroidxr/ui/components/BugdroidModel.kt +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2025 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.helloandroidxr.ui.components - -import android.annotation.SuppressLint -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.platform.LocalDensity -import androidx.xr.compose.platform.LocalSession -import androidx.xr.compose.spatial.PlanarEmbeddedSubspace -import androidx.xr.compose.subspace.ExperimentalSpatialGltfAnimationApi -import androidx.xr.compose.subspace.SpatialGltfModel -import androidx.xr.compose.subspace.SpatialGltfModelAnimation -import androidx.xr.compose.subspace.SpatialGltfModelSource -import androidx.xr.compose.subspace.draw.scale -import androidx.xr.compose.subspace.layout.SubspaceModifier -import androidx.xr.compose.subspace.layout.onSizeChanged -import androidx.xr.compose.subspace.rememberSpatialGltfModelState -import androidx.xr.compose.unit.Meter -import androidx.xr.runtime.math.Vector4 -import androidx.xr.scenecore.AlphaMode -import androidx.xr.scenecore.KhronosPbrMaterial -import com.example.helloandroidxr.viewmodel.ModelTransform -import java.nio.file.Paths - -// Bugdroid glb height in meters -private const val bugdroidHeight = 2.08f - -// The desired amount of the available layout height to use for the bugdroid -private const val fillRatio = 0.5f - -@OptIn(ExperimentalSpatialGltfAnimationApi::class) -@SuppressLint("NewApi", "RestrictedApi") -@Composable -fun BugdroidModel( - modelTransform: ModelTransform, - showBugdroid: Boolean, - animateBugdroid: Boolean, - modifier: SubspaceModifier = SubspaceModifier, -) { - val xrSession = LocalSession.current - val density = LocalDensity.current - var scaleFromLayout by remember { mutableFloatStateOf(1f) } - - if (xrSession != null && showBugdroid) { - // Initialize and remember the state of the glTF model, loading it from the assets folder. - val bugdroidModelState = rememberSpatialGltfModelState( - source = SpatialGltfModelSource.fromPath( - Paths.get("models/bugdroid_animated_wave.glb") - ) - ) - - // Find a specific node by name to apply modifications, such as material overrides. - val bugdroidNode = remember(bugdroidModelState.nodes) { - bugdroidModelState.nodes.find { it.name == "Droid_Solo:Bugdroid" } - } - - // Maintain a reference to the custom material to avoid re-creating it on every recomposition. - var pbrMaterial by remember { mutableStateOf(null) } - - // Create and apply a custom PBR material to the model when the XR session or target node changes. - LaunchedEffect(xrSession, bugdroidNode) { - val material = pbrMaterial ?: KhronosPbrMaterial.create( - session = xrSession, - alphaMode = AlphaMode.OPAQUE - ).also { - pbrMaterial = it - - // Apply the initial material properties. Base Color is RGBA value - it.setBaseColorFactor( - Vector4( - x = modelTransform.materialColor.x, - y = modelTransform.materialColor.y, - z = modelTransform.materialColor.z, - w = modelTransform.materialColor.w - ) - ) - it.setMetallicFactor(modelTransform.materialProperties.metallic) - it.setRoughnessFactor(modelTransform.materialProperties.roughness) - } - - // Apply the custom PBR material to the specific node, overriding original glTF material. - bugdroidNode?.setMaterialOverride( - material = material - ) - } - - // Update the base color material properties whenever the model transform state changes. - LaunchedEffect(modelTransform.materialColor, pbrMaterial) { - pbrMaterial?.setBaseColorFactor( - Vector4( - x = modelTransform.materialColor.x, - y = modelTransform.materialColor.y, - z = modelTransform.materialColor.z, - w = modelTransform.materialColor.w - ) - ) - } - - // Update the metallic factor property whenever the model transform state changes. - LaunchedEffect(modelTransform.materialProperties.metallic, pbrMaterial) { - pbrMaterial?.setMetallicFactor(modelTransform.materialProperties.metallic) - } - - // Update the roughness property whenever the model transform state changes. - LaunchedEffect(modelTransform.materialProperties.roughness, pbrMaterial) { - pbrMaterial?.setRoughnessFactor(modelTransform.materialProperties.roughness) - } - - // Control the model's animation state based on the animateBugdroid flag. - LaunchedEffect(bugdroidModelState.getAnimations()) { - val animation = bugdroidModelState.getAnimations().find { - it.name == "Armature|Take 001|BaseLayer" - } - if (animateBugdroid) { - if (animation?.animationState != SpatialGltfModelAnimation.AnimationState.Playing) { - animation?.loop() - } - } else { - animation?.stop() - } - } - - // Use a PlanarEmbeddedSubspace to anchor the 3D model within the 2D layout. - PlanarEmbeddedSubspace { - SpatialGltfModel( - state = bugdroidModelState, - modifier = modifier - .onSizeChanged { size -> - // Calculate the scale to use for the entity based on the layout size - val scaleToFillLayoutHeight = Meter - .fromPixel(size.height.toFloat(), density).toM() / bugdroidHeight - // Limit the scale to a ratio of the available space. - scaleFromLayout = scaleToFillLayoutHeight * fillRatio - } - .scale(scaleFromLayout * modelTransform.scale) - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/helloandroidxr/viewmodel/BugdroidViewModel.kt b/app/src/main/java/com/example/helloandroidxr/viewmodel/BugdroidViewModel.kt index 0f57ed4..f52a6ac 100644 --- a/app/src/main/java/com/example/helloandroidxr/viewmodel/BugdroidViewModel.kt +++ b/app/src/main/java/com/example/helloandroidxr/viewmodel/BugdroidViewModel.kt @@ -16,20 +16,37 @@ package com.example.helloandroidxr.viewmodel +import android.annotation.SuppressLint +import android.util.Log +import androidx.core.net.toUri import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.xr.runtime.Session +import androidx.xr.runtime.math.Pose +import androidx.xr.runtime.math.Quaternion +import androidx.xr.runtime.math.Vector3 +import androidx.xr.runtime.math.Vector4 +import androidx.xr.scenecore.AlphaMode +import androidx.xr.scenecore.ExperimentalGltfAnimationApi +import androidx.xr.scenecore.GltfAnimationStartOptions +import androidx.xr.scenecore.GltfModel +import androidx.xr.scenecore.GltfModelEntity +import androidx.xr.scenecore.KhronosPbrMaterial +import androidx.xr.scenecore.scene import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch -private const val DEFAULT_SCALE = 1.0f +private const val DEFAULT_SCALE = 0.25f private const val DEFAULT_X_ROTATION = 0.0f private const val DEFAULT_Y_ROTATION = 0.0f private const val DEFAULT_Z_ROTATION = 0.0f private const val DEFAULT_W_ROTATION = 1.0f private const val DEFAULT_X_OFFSET = 0.0f private const val DEFAULT_Y_OFFSET = 0.0f -private const val DEFAULT_Z_OFFSET = 400.0f +private const val DEFAULT_Z_OFFSET = 0.25f // Start closer to the viewer private const val DEFAULT_X_MATERIAL_COLOR = 0.0f private const val DEFAULT_Y_MATERIAL_COLOR = 1.0f private const val DEFAULT_Z_MATERIAL_COLOR = 0.0f @@ -47,8 +64,8 @@ const val MIN_Z_ROTATION_VALUE = -50.0f const val MAX_Z_ROTATION_VALUE = 50.0f const val MIN_W_ROTATION_VALUE = -5.0f const val MAX_W_ROTATION_VALUE = 5.0f -const val MIN_OFFSET_VALUE = -1500.0f -const val MAX_OFFSET_VALUE = 1500.0f +const val MIN_OFFSET_VALUE = -1.5f +const val MAX_OFFSET_VALUE = 1.5f const val MIN_MATERIAL_COLOR_VALUE = 0.0f const val MAX_MATERIAL_COLOR_VALUE = 1.0f const val MIN_MATERIAL_PROP_VALUE = 0.0f @@ -113,6 +130,103 @@ class BugdroidViewModel : ViewModel() { // Public immutable state flow for the UI to observe val uiState: StateFlow = _uiState.asStateFlow() + private var xrSession: Session? = null + private var gltfModel: GltfModel? = null + private var gltfEntity: GltfModelEntity? = null + private var pbrMaterial: KhronosPbrMaterial? = null + + fun initSession(session: Session) { + if (this.xrSession == session) return + this.xrSession = session + + /** + * This demonstrates working with a 3D model directly with XR SceneCore APIs. We generally + * recommend working with Jetpack Compose for XR when possible. + * In this case, for example, we could use a SceneCoreEntity to leverage Compose for layout + */ + viewModelScope.launch { + try { + // Create the model + val model = GltfModel.create( + session, + "models/bugdroid_animated_wave.glb".toUri() + ) + gltfModel = model + + // Create the entity from the model + val entity = GltfModelEntity.create( + session = session, + model = model, + parent = session.scene.activitySpace + ) + gltfEntity = entity + + // Create the material + val material = KhronosPbrMaterial.create( + session = session, + alphaMode = AlphaMode.OPAQUE + ).also { pbrMaterial = it } + + val currentTransform = _uiState.value.modelTransform + material.setBaseColorFactor( + Vector4( + x = currentTransform.materialColor.x, + y = currentTransform.materialColor.y, + z = currentTransform.materialColor.z, + w = currentTransform.materialColor.w + ) + ) + material.setMetallicFactor(currentTransform.materialProperties.metallic) + material.setRoughnessFactor(currentTransform.materialProperties.roughness) + + // Apply the material to the correct node in the entity + val bugdroidNode = entity.nodes.find { it.name == "Droid_Solo:Bugdroid" } + bugdroidNode?.setMaterialOverride(material) + + updateEntityTransform(currentTransform) + entity.setEnabled(_uiState.value.showBugdroid) + + updateAnimationState(_uiState.value.animateBugdroid) + } catch (e: Exception) { + Log.e("BugdroidViewModel", "Failed to load Bugdroid model entity: $e") + } + } + } + + private fun updateEntityTransform(transform: ModelTransform) { + val entity = gltfEntity ?: return + entity.setScale(transform.scale) + entity.setPose( + Pose( + translation = Vector3( + x = transform.offset.x, + y = transform.offset.y, + z = transform.offset.z + ), + rotation = Quaternion( + x = transform.rotation.x, + y = transform.rotation.y, + z = transform.rotation.z, + w = transform.rotation.w + ) + ) + ) + } + + @OptIn(ExperimentalGltfAnimationApi::class) + @SuppressLint("NewApi") + private fun updateAnimationState(animate: Boolean) { + val entity = gltfEntity ?: return + val animation = entity.getAnimations().find { + it.name == "Armature|Take 001|BaseLayer" + } ?: return + + if (animate) { + animation.start(GltfAnimationStartOptions(shouldLoop = true)) + } else { + animation.stop() + } + } fun updateShownSliderGroup(group: SliderGroup) { _uiState.update { currentState -> @@ -122,111 +236,146 @@ class BugdroidViewModel : ViewModel() { fun updateShowBugdroid() { _uiState.update { currentState -> - currentState.copy(showBugdroid = !currentState.showBugdroid) + val nextState = !currentState.showBugdroid + gltfEntity?.setEnabled(nextState) + currentState.copy(showBugdroid = nextState) } } fun updateAnimateBugdroid() { _uiState.update { currentState -> - currentState.copy(animateBugdroid = !currentState.animateBugdroid) + val nextState = !currentState.animateBugdroid + updateAnimationState(nextState) + currentState.copy(animateBugdroid = nextState) } } fun updateScale(newScale: Float) { _uiState.update { currentState -> - currentState.copy( - modelTransform = currentState.modelTransform.copy( - scale = newScale.coerceIn(MIN_SCALE_VALUE, MAX_SCALE_VALUE) - ) + val updatedTransform = currentState.modelTransform.copy( + scale = newScale.coerceIn(MIN_SCALE_VALUE, MAX_SCALE_VALUE) ) + updateEntityTransform(updatedTransform) + currentState.copy(modelTransform = updatedTransform) } } fun updateRotation(newRotation: ModelRotation) { _uiState.update { currentState -> - currentState.copy( - modelTransform = currentState.modelTransform.copy( - rotation = newRotation.copy( - x = newRotation.x.coerceIn(MIN_X_ROTATION_VALUE, MAX_X_ROTATION_VALUE), - y = newRotation.y.coerceIn(MIN_Y_ROTATION_VALUE, MAX_Y_ROTATION_VALUE), - z = newRotation.z.coerceIn(MIN_Z_ROTATION_VALUE, MAX_Z_ROTATION_VALUE), - w = newRotation.w.coerceIn(MIN_X_ROTATION_VALUE, MAX_W_ROTATION_VALUE) - ) - ) + val updatedRotation = newRotation.copy( + x = newRotation.x.coerceIn(MIN_X_ROTATION_VALUE, MAX_X_ROTATION_VALUE), + y = newRotation.y.coerceIn(MIN_Y_ROTATION_VALUE, MAX_Y_ROTATION_VALUE), + z = newRotation.z.coerceIn(MIN_Z_ROTATION_VALUE, MAX_Z_ROTATION_VALUE), + w = newRotation.w.coerceIn(MIN_X_ROTATION_VALUE, MAX_W_ROTATION_VALUE) ) + val updatedTransform = currentState.modelTransform.copy(rotation = updatedRotation) + updateEntityTransform(updatedTransform) + currentState.copy(modelTransform = updatedTransform) } } fun updateOffset(newOffset: ModelOffset) { _uiState.update { currentState -> - currentState.copy( - modelTransform = currentState.modelTransform.copy( - offset = currentState.modelTransform.offset.copy( - x = newOffset.x.coerceIn(MIN_OFFSET_VALUE, MAX_OFFSET_VALUE), - y = newOffset.y.coerceIn(MIN_OFFSET_VALUE, MAX_OFFSET_VALUE), - z = newOffset.z.coerceIn(MIN_OFFSET_VALUE, MAX_OFFSET_VALUE), - ) - ) + val updatedOffset = currentState.modelTransform.offset.copy( + x = newOffset.x.coerceIn(MIN_OFFSET_VALUE, MAX_OFFSET_VALUE), + y = newOffset.y.coerceIn(MIN_OFFSET_VALUE, MAX_OFFSET_VALUE), + z = newOffset.z.coerceIn(MIN_OFFSET_VALUE, MAX_OFFSET_VALUE), ) + val updatedTransform = currentState.modelTransform.copy(offset = updatedOffset) + updateEntityTransform(updatedTransform) + currentState.copy(modelTransform = updatedTransform) } } fun updateMaterialColor(newMaterialColor: ModelMaterialColor) { _uiState.update { currentState -> - currentState.copy( - modelTransform = currentState.modelTransform.copy( - materialColor = currentState.modelTransform.materialColor.copy( - x = newMaterialColor.x.coerceIn( - MIN_MATERIAL_COLOR_VALUE, - MAX_MATERIAL_COLOR_VALUE - ), - y = newMaterialColor.y.coerceIn( - MIN_MATERIAL_COLOR_VALUE, - MAX_MATERIAL_COLOR_VALUE - ), - z = newMaterialColor.z.coerceIn( - MIN_MATERIAL_COLOR_VALUE, - MAX_MATERIAL_COLOR_VALUE - ), - w = newMaterialColor.w.coerceIn( - MIN_MATERIAL_COLOR_VALUE, - MAX_MATERIAL_COLOR_VALUE - ), - ) + val updatedColor = currentState.modelTransform.materialColor.copy( + x = newMaterialColor.x.coerceIn( + MIN_MATERIAL_COLOR_VALUE, + MAX_MATERIAL_COLOR_VALUE + ), + y = newMaterialColor.y.coerceIn( + MIN_MATERIAL_COLOR_VALUE, + MAX_MATERIAL_COLOR_VALUE + ), + z = newMaterialColor.z.coerceIn( + MIN_MATERIAL_COLOR_VALUE, + MAX_MATERIAL_COLOR_VALUE + ), + w = newMaterialColor.w.coerceIn( + MIN_MATERIAL_COLOR_VALUE, + MAX_MATERIAL_COLOR_VALUE + ), + ) + val updatedTransform = currentState.modelTransform.copy(materialColor = updatedColor) + pbrMaterial?.setBaseColorFactor( + Vector4( + x = updatedColor.x, + y = updatedColor.y, + z = updatedColor.z, + w = updatedColor.w ) ) + currentState.copy(modelTransform = updatedTransform) } } fun updateMaterialProperties(newMaterialProperties: ModelMaterialProperties) { _uiState.update { currentState -> - currentState.copy( - modelTransform = currentState.modelTransform.copy( - materialProperties = currentState.modelTransform.materialProperties.copy( - ambientOcclusion = newMaterialProperties.ambientOcclusion.coerceIn( - MIN_MATERIAL_PROP_VALUE, - MAX_MATERIAL_PROP_VALUE - ), - metallic = newMaterialProperties.metallic.coerceIn( - MIN_MATERIAL_PROP_VALUE, - MAX_MATERIAL_PROP_VALUE - ), - roughness = newMaterialProperties.roughness.coerceIn( - MIN_MATERIAL_PROP_VALUE, - MAX_MATERIAL_PROP_VALUE - ), - ) - ) + val updatedProperties = currentState.modelTransform.materialProperties.copy( + ambientOcclusion = newMaterialProperties.ambientOcclusion.coerceIn( + MIN_MATERIAL_PROP_VALUE, + MAX_MATERIAL_PROP_VALUE + ), + metallic = newMaterialProperties.metallic.coerceIn( + MIN_MATERIAL_PROP_VALUE, + MAX_MATERIAL_PROP_VALUE + ), + roughness = newMaterialProperties.roughness.coerceIn( + MIN_MATERIAL_PROP_VALUE, + MAX_MATERIAL_PROP_VALUE + ), ) + val updatedTransform = + currentState.modelTransform.copy(materialProperties = updatedProperties) + pbrMaterial?.setMetallicFactor(updatedProperties.metallic) + pbrMaterial?.setRoughnessFactor(updatedProperties.roughness) + currentState.copy(modelTransform = updatedTransform) } } fun resetModel() { _uiState.update { currentState -> + val resetTransform = ModelTransform() + updateEntityTransform(resetTransform) + pbrMaterial?.let { material -> + material.setBaseColorFactor( + Vector4( + x = resetTransform.materialColor.x, + y = resetTransform.materialColor.y, + z = resetTransform.materialColor.z, + w = resetTransform.materialColor.w + ) + ) + material.setMetallicFactor(resetTransform.materialProperties.metallic) + material.setRoughnessFactor(resetTransform.materialProperties.roughness) + } + updateAnimationState(false) currentState.copy( - modelTransform = ModelTransform(), + modelTransform = resetTransform, animateBugdroid = false ) } } + + override fun onCleared() { + super.onCleared() + gltfEntity?.parent = null + gltfEntity = null + gltfModel?.close() + gltfModel = null + pbrMaterial?.close() + pbrMaterial = null + xrSession = null + } } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0f8441c..9bfc45c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,16 +1,16 @@ [versions] -androidx-runtime = "1.11.4" +androidx-runtime = "1.12.0" agp = "9.3.1" -arcore = "1.0.0-beta01" -compose = "1.0.0-alpha16" +arcore = "1.0.0-beta02" +compose = "1.0.0-alpha17" extensionsXr = "1.3.0" -scenecore = "1.0.0-beta01" +scenecore = "1.0.0-beta02" kotlin = "2.4.10" concurrentFuturesKtx = "1.3.0" activityCompose = "1.13.0" -composeBom = "2026.06.01" +composeBom = "2026.08.00" material = "1.14.0" -adaptiveAndroid = "1.2.0" +adaptiveAndroid = "1.3.0" [libraries] androidx-arcore = { module = "androidx.xr.arcore:arcore", version.ref = "arcore" }