Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ dependencies {
implementation(libs.androidx.arcore)
implementation(libs.androidx.scenecore)
implementation(libs.androidx.compose)
implementation(libs.kotlinx.coroutines.guava)
compileOnly(libs.androidx.extensions.xr) //This is necessary for Proguard minification

implementation(libs.material)
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@

package com.example.helloandroidxr.environment

import android.net.Uri
import android.annotation.SuppressLint
import android.util.Log
import androidx.core.net.toUri
import androidx.xr.runtime.Session
import androidx.xr.scenecore.ExrImage
import androidx.xr.scenecore.GltfModel
import androidx.xr.scenecore.SpatialEnvironment
import androidx.xr.scenecore.scene
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import java.nio.file.Paths

class EnvironmentController(private val xrSession: Session, private val coroutineScope: CoroutineScope) {
private val assetCache: HashMap<String, Any> = HashMap()
Expand All @@ -40,18 +43,22 @@ class EnvironmentController(private val xrSession: Session, private val coroutin
/**
* Request the system load a custom Environment
*/
@SuppressLint("NewApi") // Paths.get is API 26+, but Android XR devices are 34+
fun requestCustomEnvironment(environmentModelName: String) {
coroutineScope.launch {
try {
if (activeEnvironmentModelName == null ||
activeEnvironmentModelName != environmentModelName
) {

val lightingForSkybox = ExrImage.createFromZip(
xrSession,
Paths.get("environments/green_hills_ibl.zip")
)
Comment on lines +53 to +56
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The ExrImage is recreated from the zip file every time the environment is changed. Unlike the GltfModel, this asset is not cached, which can lead to unnecessary I/O and processing overhead (unzipping and decoding) when switching between environments. Consider caching the ExrImage to improve performance.

val environmentModel = assetCache[environmentModelName] as GltfModel
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This forced cast to GltfModel will throw a NullPointerException if the asset has not been loaded into the assetCache yet (e.g., if the user triggers the environment change before the asynchronous loadModelAsset completes). While this is caught by the surrounding try-catch block, relying on exceptions for expected control flow is suboptimal. Consider using a safe cast and handling the missing asset explicitly.

Suggested change
val environmentModel = assetCache[environmentModelName] as GltfModel
val environmentModel = assetCache[environmentModelName] as? GltfModel ?: return@launch


SpatialEnvironment.SpatialEnvironmentPreference(
skybox = null,
geometry = environmentModel
geometry = environmentModel,
skybox = lightingForSkybox,
).let {
xrSession.scene.spatialEnvironment.preferredSpatialEnvironment = it
}
Expand All @@ -74,7 +81,7 @@ class EnvironmentController(private val xrSession: Session, private val coroutin
if (!assetCache.containsKey(modelName)) {
try {
val gltfModel =
GltfModel.create(xrSession, Uri.parse(modelName))
GltfModel.create(xrSession, modelName.toUri())
assetCache[modelName] = gltfModel

} catch (e: Exception) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ fun BugdroidModel(

// Create and apply a custom PBR material to the model when the XR session or target node changes.
LaunchedEffect(xrSession, bugdroidNode) {
val material = KhronosPbrMaterial.create(
val material = pbrMaterial ?: KhronosPbrMaterial.create(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The pbrMaterial is cached in a state that is not keyed to the xrSession. If the xrSession changes, this LaunchedEffect will restart and reuse the pbrMaterial from the previous session. Since KhronosPbrMaterial objects are tied to a specific session, reusing them across sessions is invalid and can lead to crashes or rendering issues. Ensure the pbrMaterial state is reset when the session changes (e.g., by using remember(xrSession) for its definition).

session = xrSession,
alphaMode = AlphaMode.OPAQUE
).also {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ fun EnvironmentControls(modifier: Modifier = Modifier) {
EnvironmentController(session, activity.lifecycleScope)
}
//load the model early so it's in memory for when we need it
val environmentModelName = "green_hills_ktx2_mipmap.glb"
val environmentModelName = "environments/green_hills_ktx2_mipmap.glb"
environmentController.loadModelAsset(environmentModelName)

Surface(modifier.clip(CircleShape)) {
Expand Down
4 changes: 1 addition & 3 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,18 @@ arcore = "1.0.0-alpha13"
compose = "1.0.0-alpha13"
extensionsXr = "1.3.0"
scenecore = "1.0.0-alpha14"
kotlinxCoroutinesGuava = "1.10.2"
kotlin = "2.3.21"
concurrentFuturesKtx = "1.3.0"
activityCompose = "1.13.0"
composeBom = "2026.05.00"
material = "1.13.0"
material = "1.14.0"
adaptiveAndroid = "1.2.0"

[libraries]
androidx-arcore = { module = "androidx.xr.arcore:arcore", version.ref = "arcore" }
androidx-compose = { module = "androidx.xr.compose:compose", version.ref = "compose" }
androidx-scenecore = { module = "androidx.xr.scenecore:scenecore", version.ref = "scenecore" }
androidx-extensions-xr = { module = "com.android.extensions.xr:extensions-xr", version.ref = "extensionsXr" }
kotlinx-coroutines-guava = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-guava", version.ref = "kotlinxCoroutinesGuava" }
androidx-compose-material3 = { module = "androidx.compose.material3:material3" }
material = { module = "com.google.android.material:material", version.ref = "material" }
androidx-compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
Expand Down
Loading