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
9 changes: 3 additions & 6 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,16 @@ plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.screenshot)
}

android {
namespace = "com.example.helloandroidxr"
compileSdk = 35
compileSdk = 36

defaultConfig {
applicationId = "com.example.helloandroidxr"
minSdk = 24
targetSdk = 35
targetSdk = 36
versionCode = 1
versionName = "1.0"

Expand Down Expand Up @@ -57,7 +56,6 @@ android {
composeOptions {
kotlinCompilerExtensionVersion = "1.5.4"
}
experimentalProperties["android.experimental.enableScreenshotTest"] = true
}

dependencies {
Expand All @@ -67,6 +65,7 @@ dependencies {
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)
implementation(libs.androidx.compose.material3)
Expand All @@ -76,6 +75,4 @@ dependencies {
implementation(libs.androidx.activity.compose)

implementation(libs.androidx.compose.ui.tooling)

screenshotTestImplementation(libs.androidx.compose.ui.tooling)
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@

package com.example.helloandroidxr.environment

import android.net.Uri
import android.util.Log
import androidx.concurrent.futures.await
import androidx.xr.scenecore.GltfModel
import androidx.xr.runtime.Session
import androidx.xr.scenecore.GltfModel
import androidx.xr.scenecore.SpatialEnvironment
import androidx.xr.scenecore.scene
import kotlinx.coroutines.CoroutineScope
Expand All @@ -29,11 +29,13 @@ class EnvironmentController(private val xrSession: Session, private val coroutin
private val assetCache: HashMap<String, Any> = HashMap()
private var activeEnvironmentModelName: String? = null

fun requestHomeSpaceMode() = xrSession.scene.spatialEnvironment.requestHomeSpaceMode()
fun requestHomeSpaceMode() = xrSession.scene.requestHomeSpaceMode()

fun requestFullSpaceMode() = xrSession.scene.spatialEnvironment.requestFullSpaceMode()
fun requestFullSpaceMode() = xrSession.scene.requestFullSpaceMode()

fun requestPassthrough() = xrSession.scene.spatialEnvironment.setPassthroughOpacityPreference(1f)
fun requestPassthrough() {
xrSession.scene.spatialEnvironment.preferredPassthroughOpacity = 1f
}

/**
* Request the system load a custom Environment
Expand All @@ -51,13 +53,11 @@ class EnvironmentController(private val xrSession: Session, private val coroutin
skybox = null,
geometry = environmentModel
).let {
xrSession.scene.spatialEnvironment.setSpatialEnvironmentPreference(
it
)
xrSession.scene.spatialEnvironment.preferredSpatialEnvironment = it
}
activeEnvironmentModelName = environmentModelName
}
xrSession.scene.spatialEnvironment.setPassthroughOpacityPreference(0f)
xrSession.scene.spatialEnvironment.preferredPassthroughOpacity = 0f

} catch (e: Exception) {
Log.e(
Expand All @@ -74,7 +74,7 @@ class EnvironmentController(private val xrSession: Session, private val coroutin
if (!assetCache.containsKey(modelName)) {
try {
val gltfModel =
GltfModel.create(xrSession, modelName).await()
GltfModel.create(xrSession, Uri.parse(modelName))
assetCache[modelName] = gltfModel

} catch (e: Exception) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ import androidx.compose.ui.unit.dp
import androidx.window.core.layout.WindowSizeClass
import androidx.window.core.layout.WindowWidthSizeClass
import androidx.xr.compose.platform.LocalSpatialCapabilities
import androidx.xr.compose.spatial.ContentEdge
import androidx.xr.compose.spatial.Orbiter
import androidx.xr.compose.spatial.OrbiterEdge
import androidx.xr.compose.spatial.Subspace
import androidx.xr.compose.subspace.SpatialColumn
import androidx.xr.compose.subspace.SpatialPanel
Expand All @@ -71,6 +71,7 @@ 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.size
Expand Down Expand Up @@ -260,15 +261,15 @@ private fun TopAppBar() {
) {
Spacer(Modifier.weight(1f))
Orbiter(
position = OrbiterEdge.Top,
position = ContentEdge.Top,
offset = dimensionResource(R.dimen.top_ornament_padding),
alignment = Alignment.Start
) {
SearchBar()
}
Spacer(Modifier.weight(1f))
Orbiter(
position = OrbiterEdge.Top,
position = ContentEdge.Top,
offset = dimensionResource(R.dimen.top_ornament_padding),
alignment = Alignment.End
) {
Expand All @@ -280,22 +281,28 @@ private fun TopAppBar() {
@Composable
private fun PrimaryContent(modifier: Modifier = Modifier) {
var showBugdroid by rememberSaveable { mutableStateOf(false) }
val stringResId = if (showBugdroid) R.string.hide_bugdroid else R.string.show_bugdroid

if (LocalSpatialCapabilities.current.isSpatialUiEnabled) {
Surface(modifier.fillMaxSize()) {
Box(modifier.padding(48.dp), contentAlignment = Alignment.Center) {
Button(
onClick = {
showBugdroid = true
showBugdroid = !showBugdroid
},
modifier = modifier
) {
Text(
text = stringResource(id = R.string.show_bugdroid),
text = stringResource(id = stringResId),
style = MaterialTheme.typography.labelLarge
)
}
BugdroidModel(showBugdroid = showBugdroid)
BugdroidModel(
showBugdroid = showBugdroid,
modifier = SubspaceModifier
.fillMaxSize()
.offset(z = 400.dp) // Relative position from the panel
)
}
}
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,52 +16,113 @@

package com.example.helloandroidxr.ui.components

import android.annotation.SuppressLint
import android.content.Context
import android.util.Log
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.DisposableEffect
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.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.platform.LocalDensity
import androidx.xr.compose.platform.LocalSession
import androidx.xr.compose.spatial.Subspace
import androidx.xr.compose.subspace.Volume
import androidx.xr.compose.subspace.SceneCoreEntity
import androidx.xr.compose.subspace.SceneCoreEntitySizeAdapter
import androidx.xr.compose.subspace.layout.SubspaceModifier
import androidx.xr.compose.subspace.layout.offset
import androidx.xr.compose.subspace.layout.scale
import androidx.xr.compose.unit.Meter
import androidx.xr.runtime.Session
import androidx.xr.scenecore.GltfModel
import androidx.xr.scenecore.GltfModelEntity
import com.example.helloandroidxr.R
import kotlinx.coroutines.guava.await
import kotlinx.coroutines.launch
import java.io.InputStream

// 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

@Composable
fun BugdroidModel(showBugdroid: Boolean) {
fun BugdroidModel(showBugdroid: Boolean, modifier: SubspaceModifier = SubspaceModifier) {
if (showBugdroid) {
val xrSession = checkNotNull(LocalSession.current)
val scope = rememberCoroutineScope()
// Load the GltfModel data before creating the entity.
var gltfModel by remember { mutableStateOf<GltfModel?>(null) }
val context = LocalContext.current

Subspace {
val inputStream: InputStream =
context.resources.openRawResource(R.raw.bugdroid_animated_wave)
Volume(
SubspaceModifier.offset(z = 400.dp) // Relative position
) { parent ->
scope.launch {
val gltfModel = GltfModel.create(
session = xrSession,
assetData = inputStream.readBytes(),
assetKey = "BUGDROID"
).await()
val gltfEntity = GltfModelEntity.create(xrSession, gltfModel)
// Make this glTF a child of the Volume
gltfEntity.setParent(parent)
// Change the size of the large glTF to 10%
gltfEntity.setScale(0.1f)
gltfEntity.startAnimation(
loop = true,
animationName = "Armature|Take 001|BaseLayer"
)
}
LaunchedEffect(Unit) {
if (gltfModel == null) {
gltfModel = BugdroidGltfModelCache.getOrLoadModel(xrSession, context)
}
}
gltfModel?.let { gltfModel ->
Subspace {
val density = LocalDensity.current
var scale by remember { mutableFloatStateOf(1f) }
SceneCoreEntity(
factory = {
GltfModelEntity.create(xrSession, gltfModel).also { entity ->
entity.startAnimation(
loop = true, animationName = "Armature|Take 001|BaseLayer"
)
}
},
sizeAdapter = SceneCoreEntitySizeAdapter(onLayoutSizeChanged = { size ->
// Calculate the scale we should use for the entity based on the size the
// layout is setting on the SceneCoreEntity
val scaleToFillLayoutHeight = Meter
.fromPixel(size.height.toFloat(), density).toM() / bugdroidHeight
//Limit the scale to a ratio of the available space
scale = scaleToFillLayoutHeight * fillRatio
}),
modifier = modifier.scale(scale)
)
}
}
}
// Clean up the cache when the composable leaves the composition.
DisposableEffect(Unit) {
onDispose {
BugdroidGltfModelCache.clearCache()
}
}
}

/**
* Singleton object to cache the GltfModel.
*/
private object BugdroidGltfModelCache {
private var cachedModel: GltfModel? = null

@SuppressLint("RestrictedApi")
suspend fun getOrLoadModel(
xrCoreSession: Session, context: Context
): GltfModel? {
return if (cachedModel == null) {
try {
val inputStream: InputStream =
context.resources.openRawResource(R.raw.bugdroid_animated_wave)
cachedModel = GltfModel.create(
xrCoreSession, inputStream.readBytes(), "BUGDROID"
)
cachedModel
} catch (e: Exception) {
Log.e(TAG, "Error loading GLTF model", e)
null
}
} else {
cachedModel
}
}

fun clearCache() {
cachedModel = null
}

const val TAG = "BugdroidGltfModelCache"
}
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@
<string name="set_virtual_environment">set virtual environment</string>
<string name="set_passthrough">set passthrough</string>
<string name="show_bugdroid">Show bugdroid</string>
<string name="hide_bugdroid">Hide bugdroid</string>

</resources>

This file was deleted.

2 changes: 0 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,3 @@ kotlin.code.style=official
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true

android.experimental.enableScreenshotTest=true
Loading