Skip to content
Draft
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ android.enableR8.fullMode=true

# Use an AndroidX snapshot build.
# https://androidx.dev/snapshots/builds
# snapshotVersion=14793336
snapshotVersion=15378830
3 changes: 2 additions & 1 deletion xr/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ val isUsingSnapshot = snapshotVersion != null
val implementationXrLibraries = listOf<Provider<MinimalExternalModuleDependency>>(
xrLibs.androidx.xr.arcore.asProvider(),
xrLibs.androidx.xr.arcore.play.services,
xrLibs.androidx.xr.glimmer,
xrLibs.androidx.xr.glimmer.asProvider(),
xrLibs.androidx.xr.glimmer.googlefonts,
xrLibs.androidx.xr.projected.asProvider(),
xrLibs.androidx.xr.scenecore,
xrLibs.androidx.xr.compose,
Expand Down
1 change: 1 addition & 0 deletions xr/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ androidx-xr-arcore = { module = "androidx.xr.arcore:arcore", version.ref = "andr
androidx-xr-arcore-play-services = { module = "androidx.xr.arcore:arcore-play-services", version.ref = "androidx-xr-arcore-play-services" }
androidx-xr-compose = { module = "androidx.xr.compose:compose", version.ref = "androidx-xr-compose" }
androidx-xr-glimmer = { module = "androidx.xr.glimmer:glimmer", version.ref = "androidx-xr-glimmer" }
androidx-xr-glimmer-googlefonts = { module = "androidx.xr.glimmer:glimmer-google-fonts", version.ref = "androidx-xr-glimmer" }
androidx-xr-projected = { module = "androidx.xr.projected:projected", version.ref = "androidx-xr-projected" }
androidx-xr-projected-testing = { module = "androidx.xr.projected:projected-testing", version.ref = "androidx-xr-projected" }
androidx-xr-scenecore = { module = "androidx.xr.scenecore:scenecore", version.ref = "androidx-xr-scenecore" }
23 changes: 20 additions & 3 deletions xr/src/main/java/com/example/xr/arcore/DepthMaps.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,14 @@

package com.example.xr.arcore

import androidx.xr.arcore.DepthMap
import android.content.Context
import androidx.xr.arcore.Depth
import androidx.xr.runtime.DepthEstimationMode
import androidx.xr.runtime.ExperimentalXrDeviceLifecycleApi
import androidx.xr.runtime.RenderingMode
import androidx.xr.runtime.Session
import androidx.xr.runtime.SessionConfigureSuccess
import androidx.xr.runtime.XrDevice

private fun configureDepthEstimation(session: Session) {
// [START androidxr_arcore_depthmaps_configure]
Expand All @@ -34,12 +38,25 @@ private fun configureDepthEstimation(session: Session) {
// [END androidxr_arcore_depthmaps_configure]
}

@OptIn(ExperimentalXrDeviceLifecycleApi::class)
private fun checkLeftDepthSupport(context: Context) {
// [START androidxr_arcore_depthmaps_check_support]
val xrDevice = XrDevice.getCurrentDevice(context)
val hasMonoDepth = xrDevice.isRenderingModeSupported(RenderingMode.MONO)
val hasStereoDepth = xrDevice.isRenderingModeSupported(RenderingMode.STEREO)
// [END androidxr_arcore_depthmaps_check_support]
}

@Suppress("UnusedVariable")
private suspend fun getDepthMap(session: Session) {
private fun getDepthMap(session: Session, hasStereoDepth: Boolean) {
// [START androidxr_arcore_depthmaps_obtain_depth_map]
val depthMap = DepthMap.left(session) ?: return
if (hasStereoDepth) {
val depthMap = Depth.left(session)
}
// [END androidxr_arcore_depthmaps_obtain_depth_map]
}

private suspend fun calculateDepthResults(depthMap: Depth) {
// [START androidxr_arcore_depthmaps_calculate_results]
depthMap.state.collect { depthMap ->
// example coordinates
Expand Down
2 changes: 1 addition & 1 deletion xr/src/main/java/com/example/xr/arcore/DevicePose.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ private fun configureDevicePose(session: Session) {
// [START androidxr_arcore_device_pose_configure]
// Define the configuration object to enable tracking device pose.
val newConfig = session.config.copy(
deviceTracking = DeviceTrackingMode.SPATIAL_LAST_KNOWN
deviceTracking = DeviceTrackingMode.SPATIAL
)
// Apply the configuration to the session.
try {
Expand Down
4 changes: 2 additions & 2 deletions xr/src/main/java/com/example/xr/arcore/FaceTracking.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ import android.annotation.SuppressLint
import androidx.xr.arcore.Face
import androidx.xr.arcore.FaceBlendShapeType
import androidx.xr.arcore.FaceConfidenceRegion
import androidx.xr.arcore.TrackingState
import androidx.xr.runtime.FaceTrackingMode
import androidx.xr.runtime.Session
import androidx.xr.runtime.SessionConfigureSuccess
import androidx.xr.runtime.TrackingState

// TODO(dbridie): Remove in next release. This was fixed in aosp/508d564ca343bfc190ecaddc07209e8a9c4bdaab.
@SuppressLint("RestrictedApi")
Expand All @@ -43,7 +43,7 @@ private fun configureFaceTracking(session: Session) {
@Suppress("UnusedVariable")
private suspend fun getUserFace(session: Session) {
// [START androidxr_arcore_faceTracking_getFace]
val face = Face.getUserFace(session) ?: return
val face = Face.getUserFace(session)
face.state.collect { state ->
if (state.trackingState != TrackingState.TRACKING) return@collect

Expand Down
23 changes: 15 additions & 8 deletions xr/src/main/java/com/example/xr/arcore/Geospatial.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,9 @@
package com.example.xr.arcore

import androidx.xr.arcore.ArDevice
import androidx.xr.arcore.CreateGeospatialPoseFromPoseErrorInternal
import androidx.xr.arcore.CreateGeospatialPoseFromPoseNotTracking
import androidx.xr.arcore.CreateGeospatialPoseFromPoseSuccess
import androidx.xr.arcore.CreatePoseFromGeospatialPoseErrorInternal
import androidx.xr.arcore.CreatePoseFromGeospatialPoseInternalError
import androidx.xr.arcore.CreatePoseFromGeospatialPoseNotTracking
import androidx.xr.arcore.CreatePoseFromGeospatialPoseSuccess
import androidx.xr.arcore.Geospatial
Expand All @@ -42,10 +41,10 @@ private fun configureGeospatialSession(session: Session) {
// [START androidxr_arcore_geospatial_configure]
// Define the configuration object to enable Geospatial features.
val newConfig = Config(
// Set the GeospatialMode to VPS_AND_GPS.
geospatial = GeospatialMode.VPS_AND_GPS,
// Set the DeviceTrackingMode to SPATIAL_LAST_KNOWN.
deviceTracking = DeviceTrackingMode.SPATIAL_LAST_KNOWN
// Set the GeospatialMode to SPATIAL.
geospatial = GeospatialMode.SPATIAL,
// Set the DeviceTrackingMode to SPATIAL.
deviceTracking = DeviceTrackingMode.SPATIAL
)
// Apply the configuration to the session.
try {
Expand Down Expand Up @@ -97,6 +96,9 @@ private suspend fun checkVpsAvailability(geospatial: Geospatial) {
is VpsAvailabilityUnavailable -> {
// VPS is not available at this location.
}
else -> {
// A newer exception was added, but your app is using an old version of the library
}
}
// [END androidxr_arcore_geospatial_check_vps]
}
Expand All @@ -120,7 +122,9 @@ private fun convertDeviceToGeospatial(session: Session, geospatial: Geospatial)
// Geospatial is not currently tracking.
}

is CreateGeospatialPoseFromPoseErrorInternal -> TODO()
else -> {
// A newer exception was added, but your app is using an old version of the library
}
}
// [END androidxr_arcore_geospatial_device_to_geospatial]
}
Expand All @@ -136,9 +140,12 @@ private fun convertGeospatialToDevice(geospatial: Geospatial, geoPose: Geospatia
is CreatePoseFromGeospatialPoseNotTracking -> {
// Geospatial is not currently tracking.
}
is CreatePoseFromGeospatialPoseErrorInternal -> {
is CreatePoseFromGeospatialPoseInternalError -> {
// An internal error occurred.
}
else -> {
// A newer exception was added, but your app is using an old version of the library
}
}
// [END androidxr_arcore_geospatial_pose_to_device]
}
6 changes: 3 additions & 3 deletions xr/src/main/java/com/example/xr/arcore/Hands.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,15 @@ fun ComponentActivity.configureSession(session: Session) {
fun ComponentActivity.collectHands(session: Session) {
lifecycleScope.launch {
// [START androidxr_arcore_hand_collect]
Hand.left(session)?.state?.collect { handState -> // or Hand.right(session)
Hand.left(session).state.collect { handState -> // or Hand.right(session)
// Hand state has been updated.
// Use the state of hand joints to update an entity's position.
renderPlanetAtHandPalm(handState)
}
// [END androidxr_arcore_hand_collect]
}
lifecycleScope.launch {
Hand.right(session)?.state?.collect { rightHandState ->
Hand.right(session).state.collect { rightHandState ->
renderPlanetAtFingerTip(rightHandState)
}
}
Expand All @@ -69,7 +69,7 @@ fun secondaryHandDetection(activity: Activity, session: Session) {
// [START androidxr_arcore_hand_handedness]
val handedness = Hand.getPrimaryHandSide(activity.contentResolver)
val secondaryHand = if (handedness == HandSide.LEFT) Hand.right(session) else Hand.left(session)
val handState = secondaryHand?.state ?: return
val handState = secondaryHand.state
detectGesture(handState)
// [END androidxr_arcore_hand_handedness]
}
Expand Down
2 changes: 1 addition & 1 deletion xr/src/main/java/com/example/xr/arcore/Mobile.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import androidx.xr.runtime.Session
fun accessMobileRuntime(session: Session) {
// [START androidxr_arcore_mobile_runtime]
val arCoreRuntime = session.runtimes.firstNotNullOfOrNull { it as? ArCoreRuntime } ?: return
val originalSession = arCoreRuntime.lifecycleManager.session()
val originalSession = arCoreRuntime.session()
val originalFrame = arCoreRuntime.perceptionManager.lastFrame()
// [END androidxr_arcore_mobile_runtime]
}
3 changes: 2 additions & 1 deletion xr/src/main/java/com/example/xr/compose/Orbiter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import androidx.xr.compose.subspace.layout.SubspaceModifier
import androidx.xr.compose.subspace.layout.height
import androidx.xr.compose.subspace.layout.movable
import androidx.xr.compose.subspace.layout.resizable
import androidx.xr.compose.subspace.layout.transformingMovable
import androidx.xr.compose.subspace.layout.width
import androidx.xr.compose.unit.DpVolumeOffset
import com.example.xr.R
Expand All @@ -62,7 +63,7 @@ private fun OrbiterExampleSubspace() {
.height(824.dp)
.width(1400.dp)
.resizable()
.movable(),
.transformingMovable(),
) {
SpatialPanelContent()
OrbiterExample()
Expand Down
88 changes: 64 additions & 24 deletions xr/src/main/java/com/example/xr/compose/SpatialGltfModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@

package com.example.xr.compose

import android.util.Log
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
Expand All @@ -40,6 +42,7 @@ import kotlin.io.path.Path
fun SpatialGltfModelExample(){
val xrSession = checkNotNull(LocalSession.current)
val degrees = 1f
val removeMaterial = false

// [START androidxr_compose_SpatialGltfModelState]
val modelState = rememberSpatialGltfModelState(
Expand All @@ -49,64 +52,101 @@ fun SpatialGltfModelExample(){
)
// [END androidxr_compose_SpatialGltfModelState]

// [START androidxr_compose_SpatialGltfModelMaterial]
// [START androidxr_compose_SpatialGltfModelNode]
// Retrieve the list of nodes (individual components/meshes) defined within the glTF model.
val entityNodes = modelState.nodes

// Find a specific node by name to apply modifications, such as material overrides.
val node = entityNodes.find { it.name == "node_name" }
// [END androidxr_compose_SpatialGltfModelNode]

// [START androidxr_compose_SpatialGltfModelIntrospection]
LaunchedEffect(node, degrees) {
val rotation = Quaternion.fromEulerAngles(degrees, 0f, degrees)
node?.let {
it.localPose = Pose(it.localPose.translation, rotation)
}
}
// [END androidxr_compose_SpatialGltfModelIntrospection]

// [START androidxr_compose_SpatialGltfModelMaterial]
// Maintain a reference to the custom material to avoid re-creating it on every recomposition.
var pbrMaterial by remember { mutableStateOf<KhronosPbrMaterial?>(null) }

// Create and apply the custom material once the session is ready and the target node is available.
LaunchedEffect(node) {
val material = pbrMaterial ?: KhronosPbrMaterial.create(
val material = 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.

medium

The material is being re-created every time this LaunchedEffect runs, bypassing the pbrMaterial cache defined at line 78. This is less efficient than the previous implementation. Consider restoring the caching logic.

Suggested change
val material = KhronosPbrMaterial.create(
val material = pbrMaterial ?: KhronosPbrMaterial.create(

session = xrSession,
alphaMode = AlphaMode.OPAQUE
).also {
pbrMaterial = it
// Apply a base color factor (RGBA) to change the color of the model.
it.setBaseColorFactor(
Vector4(
x = 0.5f,
y = 0.0f,
z = 0.5f,
w = 1.0f
)
)
}
// [END androidxr_compose_SpatialGltfModelMaterial]

// [START androidxr_compose_SpatialGltfModelMaterialOverride]
node?.setMaterialOverride(
material = material
)
// [END androidxr_compose_SpatialGltfModelMaterialOverride]

// [START androidxr_compose_SpatialGltfModelMaterialClear]
if (removeMaterial) {
node?.clearMaterialOverride()
}
// [END androidxr_compose_SpatialGltfModelMaterialClear]
}

// [START androidxr_compose_SpatialGltfModelTexture]
LaunchedEffect(node) {
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 LaunchedEffect conflicts with the one starting at line 81. Both effects use node as a key and call setMaterialOverride. Since they run concurrently, the second one will overwrite the first, making the base color factor set in the first block ineffective. Consider merging these snippets or clarifying their usage.

val material = KhronosPbrMaterial.create(
session = xrSession,
alphaMode = AlphaMode.OPAQUE
).also {
pbrMaterial = it

// Load a texture
val texture = Texture.create(
session = xrSession,
path = Path("textures/texture_name.png")
)

// Configure occlusion to define how the material surface handles ambient lighting.
// Set the texture and configure occlusion to define how the material surface handles ambient lighting.
it.setOcclusionTexture(
texture = texture,
strength = 1.0f
)

// Apply a base color factor (RGBA) to tint the model.
it.setBaseColorFactor(
Vector4(
x = 0.5f,
y = 0.0f,
z = 0.5f,
w = 1.0f
)
)
}

// Apply the custom PBR material to the specific node, overriding its original glTF material.
node?.setMaterialOverride(
material = material
)
}
// [END androidxr_compose_SpatialGltfModelMaterial]
// [END androidxr_compose_SpatialGltfModelTexture]

// [START androidxr_compose_SpatialGltfModelIntrospection]
val arrows = modelState.nodes.find {
it.name == "Arrows"
// [START androidxr_compose_SpatialGltfModelAnimation]
val animation = modelState.animations.find { it.name == "Walk" }

animation?.animationState?.let { state ->
LaunchedEffect(state) {
Log.i("SpatialGltfModelAnimationSample", "Animation State: $state")
}
}

LaunchedEffect(arrows, degrees) {
val rotation = Quaternion.fromEulerAngles(degrees, 0f, degrees)
arrows?.localPose =
Pose(arrows.localPose.translation, rotation)
DisposableEffect(animation) {
animation?.loop()
onDispose {
animation?.stop()
}
}
// [END androidxr_compose_SpatialGltfModelIntrospection]
// [END androidxr_compose_SpatialGltfModelAnimation]

// [START androidxr_compose_SpatialGltfModelLoad]
SpatialGltfModel(state = modelState, modifier = SubspaceModifier)
Expand Down
5 changes: 3 additions & 2 deletions xr/src/main/java/com/example/xr/compose/SpatialPanel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import androidx.xr.compose.subspace.SpatialPanel
import androidx.xr.compose.subspace.layout.SubspaceModifier
import androidx.xr.compose.subspace.layout.height
import androidx.xr.compose.subspace.layout.movable
import androidx.xr.compose.subspace.layout.transformingMovable
import androidx.xr.compose.subspace.layout.width

@Composable
Expand All @@ -44,7 +45,7 @@ private fun SpatialPanelExample() {
SubspaceModifier
.height(824.dp)
.width(1400.dp)
.movable(),
.transformingMovable(),
resizePolicy = ResizePolicy(),
) {
SpatialPanelContent()
Expand Down Expand Up @@ -81,7 +82,7 @@ private fun ContentInSpatialPanel() {
if (LocalSpatialCapabilities.current.isSpatialUiEnabled) {
Subspace {
SpatialPanel(
modifier = SubspaceModifier.movable(),
modifier = SubspaceModifier.transformingMovable(),
resizePolicy = ResizePolicy(),
) {
AppContent()
Expand Down
Loading
Loading