1 /*
2  * Copyright 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.xr.runtime.openxr
18 
19 import androidx.annotation.RestrictTo
20 import androidx.xr.runtime.internal.Anchor
21 import androidx.xr.runtime.internal.AnchorResourcesExhaustedException
22 import androidx.xr.runtime.internal.Hand
23 import androidx.xr.runtime.internal.HitResult
24 import androidx.xr.runtime.internal.PerceptionManager
25 import androidx.xr.runtime.internal.Plane
26 import androidx.xr.runtime.internal.Trackable
27 import androidx.xr.runtime.math.Pose
28 import androidx.xr.runtime.math.Ray
29 import androidx.xr.runtime.math.Vector3
30 import java.util.Arrays
31 import java.util.UUID
32 
33 /** Implementation of the perception capabilities of a runtime using OpenXR. */
34 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
35 public class OpenXrPerceptionManager
36 internal constructor(private val timeSource: OpenXrTimeSource) : PerceptionManager {
37 
createAnchornull38     override fun createAnchor(pose: Pose): Anchor {
39         val nativeAnchor = nativeCreateAnchor(pose, lastUpdateXrTime)
40         checkNativeAnchorIsValid(nativeAnchor)
41         val anchor = OpenXrAnchor(nativeAnchor, xrResources)
42         anchor.update(lastUpdateXrTime)
43         xrResources.addUpdatable(anchor as Updatable)
44         return anchor
45     }
46 
47     // TODO: b/345315434 - Implement this method correctly once we have the ability to conduct
48     // hit tests in the native OpenXrManager.
hitTestnull49     override fun hitTest(ray: Ray): List<HitResult> {
50         val hitData =
51             nativeHitTest(
52                 maxResults = 5,
53                 ray.origin.x,
54                 ray.origin.y,
55                 ray.origin.z,
56                 ray.direction.x,
57                 ray.direction.y,
58                 ray.direction.z,
59                 lastUpdateXrTime,
60             )
61         return Arrays.asList(*hitData).toList().map { toHitResult(it, ray.origin) }
62     }
63 
getPersistedAnchorUuidsnull64     override fun getPersistedAnchorUuids(): List<UUID> {
65         val anchorUuids = nativeGetPersistedAnchorUuids()
66         return Arrays.asList(*anchorUuids)
67             .toList()
68             .map { OpenXrAnchor.UUIDFromByteArray(it) }
69             .filterNotNull()
70     }
71 
loadAnchornull72     override fun loadAnchor(uuid: UUID): Anchor {
73         val nativeAnchor = nativeLoadAnchor(uuid)
74         when (nativeAnchor) {
75             -2L -> throw IllegalStateException("Failed to load anchor.")
76             -10L -> throw AnchorResourcesExhaustedException()
77         }
78         val anchor = OpenXrAnchor(nativeAnchor, xrResources, loadedUuid = uuid)
79         anchor.update(lastUpdateXrTime)
80         xrResources.addUpdatable(anchor as Updatable)
81         return anchor
82     }
83 
loadAnchorFromNativePointernull84     override fun loadAnchorFromNativePointer(nativePointer: Long): Anchor {
85         val anchor = OpenXrAnchor(nativePointer, xrResources)
86         anchor.update(lastUpdateXrTime)
87         xrResources.addUpdatable(anchor as Updatable)
88         return anchor
89     }
90 
unpersistAnchornull91     override fun unpersistAnchor(uuid: UUID) {
92         check(nativeUnpersistAnchor(uuid)) { "Failed to unpersist anchor." }
93     }
94 
95     internal val xrResources = XrResources()
96     override val trackables: Collection<Trackable> = xrResources.trackablesMap.values
97     override val leftHand: Hand
98         get() = xrResources.leftHand
99 
100     override val rightHand: Hand
101         get() = xrResources.rightHand
102 
103     private var lastUpdateXrTime: Long = 0L
104 
105     /**
106      * Updates the perception manager.
107      *
108      * @param xrTime the number of nanoseconds since the start of the OpenXR epoch.
109      */
updatenull110     public fun update(xrTime: Long) {
111         val planes = nativeGetPlanes()
112         // Add new planes to the list of trackables.
113         for (plane in planes) {
114             if (xrResources.trackablesMap.containsKey(plane)) continue
115 
116             val planeTypeInt = nativeGetPlaneType(plane, xrTime)
117             check(planeTypeInt >= 0) { "Failed to get plane type." }
118 
119             val trackable =
120                 OpenXrPlane(plane, Plane.Type.fromOpenXrType(planeTypeInt), timeSource, xrResources)
121             xrResources.addTrackable(plane, trackable)
122             xrResources.addUpdatable(trackable as Updatable)
123         }
124 
125         for (updatable in xrResources.updatables) {
126             updatable.update(xrTime)
127         }
128 
129         lastUpdateXrTime = xrTime
130     }
131 
clearnull132     internal fun clear() {
133         xrResources.clear()
134     }
135 
toHitResultnull136     private fun toHitResult(hitData: HitData, origin: Vector3): HitResult {
137         val trackable =
138             xrResources.trackablesMap[hitData.id]
139                 ?: throw IllegalStateException("Trackable not found.")
140 
141         return HitResult(
142             distance = (hitData.pose.translation - origin).length,
143             hitPose = hitData.pose,
144             trackable = trackable,
145         )
146     }
147 
checkNativeAnchorIsValidnull148     private fun checkNativeAnchorIsValid(nativeAnchor: Long) {
149         when (nativeAnchor) {
150             -2L -> throw IllegalStateException("Failed to create anchor.") // kErrorRuntimeFailure
151             -10L -> throw AnchorResourcesExhaustedException() // kErrorLimitReached
152         }
153     }
154 
nativeCreateAnchornull155     private external fun nativeCreateAnchor(pose: Pose, timestampNs: Long): Long
156 
157     private external fun nativeGetPlanes(): LongArray
158 
159     private external fun nativeGetPlaneType(planeId: Long, timestampNs: Long): Int
160 
161     private external fun nativeHitTest(
162         maxResults: Int,
163         originX: Float,
164         originY: Float,
165         originZ: Float,
166         directionX: Float,
167         directionY: Float,
168         directionZ: Float,
169         timestampNs: Long,
170     ): Array<HitData>
171 
172     private external fun nativeGetPersistedAnchorUuids(): Array<ByteArray>
173 
174     private external fun nativeLoadAnchor(uuid: UUID): Long
175 
176     private external fun nativeUnpersistAnchor(uuid: UUID): Boolean
177 }
178