Skip to content

Commit 9d9c0a8

Browse files
Merge pull request #14 from fuentespatrick/alpha06-update
Update dependencies and add visibility toggling and layout-based scale resizing to the glTF
2 parents dbb8fe8 + 3b9dab5 commit 9d9c0a8

13 files changed

Lines changed: 130 additions & 111 deletions

File tree

app/build.gradle.kts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,16 @@ plugins {
1818
alias(libs.plugins.android.application)
1919
alias(libs.plugins.kotlin.android)
2020
alias(libs.plugins.kotlin.compose)
21-
alias(libs.plugins.screenshot)
2221
}
2322

2423
android {
2524
namespace = "com.example.helloandroidxr"
26-
compileSdk = 35
25+
compileSdk = 36
2726

2827
defaultConfig {
2928
applicationId = "com.example.helloandroidxr"
3029
minSdk = 24
31-
targetSdk = 35
30+
targetSdk = 36
3231
versionCode = 1
3332
versionName = "1.0"
3433

@@ -57,7 +56,6 @@ android {
5756
composeOptions {
5857
kotlinCompilerExtensionVersion = "1.5.4"
5958
}
60-
experimentalProperties["android.experimental.enableScreenshotTest"] = true
6159
}
6260

6361
dependencies {
@@ -67,6 +65,7 @@ dependencies {
6765
implementation(libs.androidx.scenecore)
6866
implementation(libs.androidx.compose)
6967
implementation(libs.kotlinx.coroutines.guava)
68+
compileOnly(libs.androidx.extensions.xr) //This is necessary for Proguard minification
7069

7170
implementation(libs.material)
7271
implementation(libs.androidx.compose.material3)
@@ -76,6 +75,4 @@ dependencies {
7675
implementation(libs.androidx.activity.compose)
7776

7877
implementation(libs.androidx.compose.ui.tooling)
79-
80-
screenshotTestImplementation(libs.androidx.compose.ui.tooling)
8178
}

app/src/main/java/com/example/helloandroidxr/environment/EnvironmentController.kt

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@
1616

1717
package com.example.helloandroidxr.environment
1818

19+
import android.net.Uri
1920
import android.util.Log
20-
import androidx.concurrent.futures.await
21-
import androidx.xr.scenecore.GltfModel
2221
import androidx.xr.runtime.Session
22+
import androidx.xr.scenecore.GltfModel
2323
import androidx.xr.scenecore.SpatialEnvironment
2424
import androidx.xr.scenecore.scene
2525
import kotlinx.coroutines.CoroutineScope
@@ -29,11 +29,13 @@ class EnvironmentController(private val xrSession: Session, private val coroutin
2929
private val assetCache: HashMap<String, Any> = HashMap()
3030
private var activeEnvironmentModelName: String? = null
3131

32-
fun requestHomeSpaceMode() = xrSession.scene.spatialEnvironment.requestHomeSpaceMode()
32+
fun requestHomeSpaceMode() = xrSession.scene.requestHomeSpaceMode()
3333

34-
fun requestFullSpaceMode() = xrSession.scene.spatialEnvironment.requestFullSpaceMode()
34+
fun requestFullSpaceMode() = xrSession.scene.requestFullSpaceMode()
3535

36-
fun requestPassthrough() = xrSession.scene.spatialEnvironment.setPassthroughOpacityPreference(1f)
36+
fun requestPassthrough() {
37+
xrSession.scene.spatialEnvironment.preferredPassthroughOpacity = 1f
38+
}
3739

3840
/**
3941
* Request the system load a custom Environment
@@ -51,13 +53,11 @@ class EnvironmentController(private val xrSession: Session, private val coroutin
5153
skybox = null,
5254
geometry = environmentModel
5355
).let {
54-
xrSession.scene.spatialEnvironment.setSpatialEnvironmentPreference(
55-
it
56-
)
56+
xrSession.scene.spatialEnvironment.preferredSpatialEnvironment = it
5757
}
5858
activeEnvironmentModelName = environmentModelName
5959
}
60-
xrSession.scene.spatialEnvironment.setPassthroughOpacityPreference(0f)
60+
xrSession.scene.spatialEnvironment.preferredPassthroughOpacity = 0f
6161

6262
} catch (e: Exception) {
6363
Log.e(
@@ -74,7 +74,7 @@ class EnvironmentController(private val xrSession: Session, private val coroutin
7474
if (!assetCache.containsKey(modelName)) {
7575
try {
7676
val gltfModel =
77-
GltfModel.create(xrSession, modelName).await()
77+
GltfModel.create(xrSession, Uri.parse(modelName))
7878
assetCache[modelName] = gltfModel
7979

8080
} catch (e: Exception) {

app/src/main/java/com/example/helloandroidxr/ui/HelloAndroidXRApp.kt

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ import androidx.compose.ui.unit.dp
5959
import androidx.window.core.layout.WindowSizeClass
6060
import androidx.window.core.layout.WindowWidthSizeClass
6161
import androidx.xr.compose.platform.LocalSpatialCapabilities
62+
import androidx.xr.compose.spatial.ContentEdge
6263
import androidx.xr.compose.spatial.Orbiter
63-
import androidx.xr.compose.spatial.OrbiterEdge
6464
import androidx.xr.compose.spatial.Subspace
6565
import androidx.xr.compose.subspace.SpatialColumn
6666
import androidx.xr.compose.subspace.SpatialPanel
@@ -71,6 +71,7 @@ import androidx.xr.compose.subspace.layout.fillMaxSize
7171
import androidx.xr.compose.subspace.layout.fillMaxWidth
7272
import androidx.xr.compose.subspace.layout.height
7373
import androidx.xr.compose.subspace.layout.movable
74+
import androidx.xr.compose.subspace.layout.offset
7475
import androidx.xr.compose.subspace.layout.padding
7576
import androidx.xr.compose.subspace.layout.resizable
7677
import androidx.xr.compose.subspace.layout.size
@@ -260,15 +261,15 @@ private fun TopAppBar() {
260261
) {
261262
Spacer(Modifier.weight(1f))
262263
Orbiter(
263-
position = OrbiterEdge.Top,
264+
position = ContentEdge.Top,
264265
offset = dimensionResource(R.dimen.top_ornament_padding),
265266
alignment = Alignment.Start
266267
) {
267268
SearchBar()
268269
}
269270
Spacer(Modifier.weight(1f))
270271
Orbiter(
271-
position = OrbiterEdge.Top,
272+
position = ContentEdge.Top,
272273
offset = dimensionResource(R.dimen.top_ornament_padding),
273274
alignment = Alignment.End
274275
) {
@@ -280,22 +281,28 @@ private fun TopAppBar() {
280281
@Composable
281282
private fun PrimaryContent(modifier: Modifier = Modifier) {
282283
var showBugdroid by rememberSaveable { mutableStateOf(false) }
284+
val stringResId = if (showBugdroid) R.string.hide_bugdroid else R.string.show_bugdroid
283285

284286
if (LocalSpatialCapabilities.current.isSpatialUiEnabled) {
285287
Surface(modifier.fillMaxSize()) {
286288
Box(modifier.padding(48.dp), contentAlignment = Alignment.Center) {
287289
Button(
288290
onClick = {
289-
showBugdroid = true
291+
showBugdroid = !showBugdroid
290292
},
291293
modifier = modifier
292294
) {
293295
Text(
294-
text = stringResource(id = R.string.show_bugdroid),
296+
text = stringResource(id = stringResId),
295297
style = MaterialTheme.typography.labelLarge
296298
)
297299
}
298-
BugdroidModel(showBugdroid = showBugdroid)
300+
BugdroidModel(
301+
showBugdroid = showBugdroid,
302+
modifier = SubspaceModifier
303+
.fillMaxSize()
304+
.offset(z = 400.dp) // Relative position from the panel
305+
)
299306
}
300307
}
301308
} else {

app/src/main/java/com/example/helloandroidxr/ui/components/BugdroidModel.kt

Lines changed: 91 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,52 +16,113 @@
1616

1717
package com.example.helloandroidxr.ui.components
1818

19+
import android.annotation.SuppressLint
20+
import android.content.Context
21+
import android.util.Log
1922
import androidx.compose.runtime.Composable
20-
import androidx.compose.runtime.rememberCoroutineScope
23+
import androidx.compose.runtime.DisposableEffect
24+
import androidx.compose.runtime.LaunchedEffect
25+
import androidx.compose.runtime.getValue
26+
import androidx.compose.runtime.mutableFloatStateOf
27+
import androidx.compose.runtime.mutableStateOf
28+
import androidx.compose.runtime.remember
29+
import androidx.compose.runtime.setValue
2130
import androidx.compose.ui.platform.LocalContext
22-
import androidx.compose.ui.unit.dp
31+
import androidx.compose.ui.platform.LocalDensity
2332
import androidx.xr.compose.platform.LocalSession
2433
import androidx.xr.compose.spatial.Subspace
25-
import androidx.xr.compose.subspace.Volume
34+
import androidx.xr.compose.subspace.SceneCoreEntity
35+
import androidx.xr.compose.subspace.SceneCoreEntitySizeAdapter
2636
import androidx.xr.compose.subspace.layout.SubspaceModifier
27-
import androidx.xr.compose.subspace.layout.offset
37+
import androidx.xr.compose.subspace.layout.scale
38+
import androidx.xr.compose.unit.Meter
39+
import androidx.xr.runtime.Session
2840
import androidx.xr.scenecore.GltfModel
2941
import androidx.xr.scenecore.GltfModelEntity
3042
import com.example.helloandroidxr.R
31-
import kotlinx.coroutines.guava.await
32-
import kotlinx.coroutines.launch
3343
import java.io.InputStream
3444

45+
// Bugdroid glb height in meters
46+
private const val bugdroidHeight = 2.08f
47+
// The desired amount of the available layout height to use for the bugdroid
48+
private const val fillRatio = 0.5f
49+
3550
@Composable
36-
fun BugdroidModel(showBugdroid: Boolean) {
51+
fun BugdroidModel(showBugdroid: Boolean, modifier: SubspaceModifier = SubspaceModifier) {
3752
if (showBugdroid) {
3853
val xrSession = checkNotNull(LocalSession.current)
39-
val scope = rememberCoroutineScope()
54+
// Load the GltfModel data before creating the entity.
55+
var gltfModel by remember { mutableStateOf<GltfModel?>(null) }
4056
val context = LocalContext.current
4157

42-
Subspace {
43-
val inputStream: InputStream =
44-
context.resources.openRawResource(R.raw.bugdroid_animated_wave)
45-
Volume(
46-
SubspaceModifier.offset(z = 400.dp) // Relative position
47-
) { parent ->
48-
scope.launch {
49-
val gltfModel = GltfModel.create(
50-
session = xrSession,
51-
assetData = inputStream.readBytes(),
52-
assetKey = "BUGDROID"
53-
).await()
54-
val gltfEntity = GltfModelEntity.create(xrSession, gltfModel)
55-
// Make this glTF a child of the Volume
56-
gltfEntity.setParent(parent)
57-
// Change the size of the large glTF to 10%
58-
gltfEntity.setScale(0.1f)
59-
gltfEntity.startAnimation(
60-
loop = true,
61-
animationName = "Armature|Take 001|BaseLayer"
62-
)
63-
}
58+
LaunchedEffect(Unit) {
59+
if (gltfModel == null) {
60+
gltfModel = BugdroidGltfModelCache.getOrLoadModel(xrSession, context)
61+
}
62+
}
63+
gltfModel?.let { gltfModel ->
64+
Subspace {
65+
val density = LocalDensity.current
66+
var scale by remember { mutableFloatStateOf(1f) }
67+
SceneCoreEntity(
68+
factory = {
69+
GltfModelEntity.create(xrSession, gltfModel).also { entity ->
70+
entity.startAnimation(
71+
loop = true, animationName = "Armature|Take 001|BaseLayer"
72+
)
73+
}
74+
},
75+
sizeAdapter = SceneCoreEntitySizeAdapter(onLayoutSizeChanged = { size ->
76+
// Calculate the scale we should use for the entity based on the size the
77+
// layout is setting on the SceneCoreEntity
78+
val scaleToFillLayoutHeight = Meter
79+
.fromPixel(size.height.toFloat(), density).toM() / bugdroidHeight
80+
//Limit the scale to a ratio of the available space
81+
scale = scaleToFillLayoutHeight * fillRatio
82+
}),
83+
modifier = modifier.scale(scale)
84+
)
85+
}
86+
}
87+
}
88+
// Clean up the cache when the composable leaves the composition.
89+
DisposableEffect(Unit) {
90+
onDispose {
91+
BugdroidGltfModelCache.clearCache()
92+
}
93+
}
94+
}
95+
96+
/**
97+
* Singleton object to cache the GltfModel.
98+
*/
99+
private object BugdroidGltfModelCache {
100+
private var cachedModel: GltfModel? = null
101+
102+
@SuppressLint("RestrictedApi")
103+
suspend fun getOrLoadModel(
104+
xrCoreSession: Session, context: Context
105+
): GltfModel? {
106+
return if (cachedModel == null) {
107+
try {
108+
val inputStream: InputStream =
109+
context.resources.openRawResource(R.raw.bugdroid_animated_wave)
110+
cachedModel = GltfModel.create(
111+
xrCoreSession, inputStream.readBytes(), "BUGDROID"
112+
)
113+
cachedModel
114+
} catch (e: Exception) {
115+
Log.e(TAG, "Error loading GLTF model", e)
116+
null
64117
}
118+
} else {
119+
cachedModel
65120
}
66121
}
122+
123+
fun clearCache() {
124+
cachedModel = null
125+
}
126+
127+
const val TAG = "BugdroidGltfModelCache"
67128
}

app/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@
2626
<string name="set_virtual_environment">set virtual environment</string>
2727
<string name="set_passthrough">set passthrough</string>
2828
<string name="show_bugdroid">Show bugdroid</string>
29+
<string name="hide_bugdroid">Hide bugdroid</string>
2930

3031
</resources>

app/src/screenshotTest/kotlin/com/example/helloandroidxr/ui/AppPreviewScreenshots.kt

Lines changed: 0 additions & 45 deletions
This file was deleted.

0 commit comments

Comments
 (0)