2
0

openxr_spatial_entities.rst 75 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771
  1. .. _doc_openxr_spatial_entities:
  2. OpenXR spatial entities
  3. =======================
  4. For any sort of augmented reality application you need to access real world information, and be able to
  5. track real world locations. OpenXR's spatial entities API was introduced for this exact purpose.
  6. It has a very modular design. The core of the API defines how real world entities are structured,
  7. how they are found, and how information about them is stored and accessed.
  8. Various extensions are added on top, which implement specific systems such as marker tracking,
  9. plane tracking, and anchors. These are referred to as spatial capabilities.
  10. Each entity that can be handled by the system is broken up into smaller components, which makes it easy
  11. to extend the system and add new capabilities.
  12. Vendors have the ability to implement and expose additional capabilities and component types that can be
  13. used with the core API. For Godot these can be implemented in extensions. These implementations
  14. however fall outside of the scope of this manual.
  15. Finally it is important to note that the spatial entity system makes use of asynchronous functions.
  16. This means that you can start a process, and then get informed of it finishing later on.
  17. Setup
  18. -----
  19. In order to use spatial entities you need to enable the related project settings.
  20. You can find these in the OpenXR section:
  21. .. image:: img/openxr_spatial_entities_project_settings.webp
  22. .. list-table:: Spatial entity settings
  23. :header-rows: 1
  24. * - Setting
  25. - Description
  26. * - Enabled
  27. - Enables the core of the spatial entities system. This must be enabled for any of the spatial
  28. entities systems to work.
  29. * - Enable spatial anchors
  30. - Enables the spatial anchors capability that allow creating and tracking spatial anchors.
  31. * - Enable persistent anchors
  32. - Enables the ability to make spatial anchors persistent. This means that their location is stored
  33. and can be retrieved in subsequent sessions.
  34. * - Enable built-in anchor detection
  35. - Enables our built-in anchor detection logic, this will automatically retrieve persistent anchors
  36. and adjust the positioning of anchors when tracking is updated.
  37. * - Enable plane tracking
  38. - Enables the plane tracking capability that allows detection of surfaces such as floors, walls,
  39. ceilings, and tables.
  40. * - Enable built-in plane detection
  41. - Enables our built-in plane detection logic, this will automatically react to new plane data
  42. becoming available.
  43. * - Enable marker tracking
  44. - Enables our marker tracking capability that allows detection of markers such as QR codes,
  45. Aruco markers, and April tags.
  46. * - Enable built-in marker tracking
  47. - Enables our built-in marker detection logic, this will automatically react to new markers being
  48. found or markers being moved around the player's space.
  49. .. note::
  50. Note that various XR devices also require permission flags to be set. These will need to be
  51. enabled in the export preset settings.
  52. Enabling the different capabilities activates the related OpenXR APIs, but additional logic is needed
  53. to interact with this data.
  54. For each core system we have built-in logic that can be enabled that will do this for you.
  55. We'll discuss the spatial entities system under the assumption that the built-in logic is enabled first.
  56. We will then take a look at the underlying APIs and how you can implement this yourself, however it
  57. should be noted that this is often overkill and that the underlying APIs are mostly exposed to allow
  58. GDExtension plugins to implement additional capabilities.
  59. Creating our spatial manager
  60. ----------------------------
  61. When spatial entities are detected or created an
  62. :ref:`OpenXRSpatialEntityTracker<class_OpenXRSpatialEntityTracker>`
  63. object is instantiated and registered with the :ref:`XRServer<class_XRServer>`.
  64. Each type of spatial entity will implement its own subclass and we can thus react differently to
  65. each type of entity.
  66. Generally speaking we will instance different subscenes for each type of entity.
  67. As the tracker objects can be used with :ref:`XRAnchor3D<class_XRAnchor3D>` nodes, these subscenes
  68. should have such a node as their root node.
  69. All entity trackers will expose their location through the ``default`` pose.
  70. We can automate creating these subscenes and adding them to our scene tree by creating a manager
  71. object. As all locations are local to the :ref:`XROrigin3D<class_XROrigin3D>` node, we should create
  72. our manager as a child node of our origin node.
  73. Below is the basis of the script that implements our manager logic:
  74. .. code-block:: gdscript
  75. class_name SpatialEntitiesManager
  76. extends Node3D
  77. ## Signals a new spatial entity node was added.
  78. signal added_spatial_entity(node: XRNode3D)
  79. ## Signals a spatial entity node is about to be removed.
  80. signal removed_spatial_entity(node: XRNode3D)
  81. ## Scene to instantiate for spatial anchor entities.
  82. @export var spatial_anchor_scene: PackedScene
  83. ## Scene to instantiate for plane tracking spatial entities.
  84. @export var plane_tracker_scene: PackedScene
  85. ## Scene to instantiate for marker tracking spatial entities.
  86. @export var marker_tracker_scene: PackedScene
  87. # Trackers we manage nodes for.
  88. var _managed_nodes: Dictionary[OpenXRSpatialEntityTracker, XRAnchor3D]
  89. # Enter tree is called whenever our node is added to our scene.
  90. func _enter_tree():
  91. # Connect to signals that inform us about tracker changes.
  92. XRServer.tracker_added.connect(_on_tracker_added)
  93. XRServer.tracker_updated.connect(_on_tracker_updated)
  94. XRServer.tracker_removed.connect(_on_tracker_removed)
  95. # Set up existing trackers.
  96. var trackers : Dictionary = XRServer.get_trackers(XRServer.TRACKER_ANCHOR)
  97. for tracker_name in trackers:
  98. var tracker: XRTracker = trackers[tracker_name]
  99. if tracker and tracker is OpenXRSpatialEntityTracker:
  100. _add_tracker(tracker)
  101. # Exit tree is called whenever our node is removed from our scene.
  102. func _exit_tree():
  103. # Clean up our signals.
  104. XRServer.tracker_added.disconnect(_on_tracker_added)
  105. XRServer.tracker_updated.disconnect(_on_tracker_updated)
  106. XRServer.tracker_removed.disconnect(_on_tracker_removed)
  107. # Clean up trackers.
  108. for tracker in _managed_nodes:
  109. removed_spatial_entity.emit(_managed_nodes[tracker])
  110. remove_child(_managed_nodes[tracker])
  111. _managed_nodes[tracker].queue_free()
  112. _managed_nodes.clear()
  113. # See if this tracker should be managed by us and add it.
  114. func _add_tracker(tracker: OpenXRSpatialEntityTracker):
  115. var new_node: XRAnchor3D
  116. if _managed_nodes.has(tracker):
  117. # Already being managed by us!
  118. return
  119. if tracker is OpenXRAnchorTracker:
  120. # Note: Generally spatial anchors are controlled by the developer and
  121. # are unlikely to be handled by our manager.
  122. # But just for completeness we'll add it in.
  123. if spatial_anchor_scene:
  124. var new_scene = spatial_anchor_scene.instantiate()
  125. if new_scene is XRAnchor3D:
  126. new_node = new_scene
  127. else:
  128. push_error("Spatial anchor scene doesn't have an XRAnchor3D as a root node and can't be used!")
  129. new_scene.free()
  130. elif tracker is OpenXRPlaneTracker:
  131. if plane_tracker_scene:
  132. var new_scene = plane_tracker_scene.instantiate()
  133. if new_scene is XRAnchor3D:
  134. new_node = new_scene
  135. else:
  136. push_error("Plane tracking scene doesn't have an XRAnchor3D as a root node and can't be used!")
  137. new_scene.free()
  138. elif tracker is OpenXRMarkerTracker:
  139. if marker_tracker_scene:
  140. var new_scene = marker_tracker_scene.instantiate()
  141. if new_scene is XRAnchor3D:
  142. new_node = new_scene
  143. else:
  144. push_error("Marker tracking scene doesn't have an XRAnchor3D as a root node and can't be used!")
  145. new_scene.free()
  146. else:
  147. # Type of spatial entity tracker we're not supporting?
  148. push_warning("OpenXR Spatial Entities: Unsupported anchor tracker " + tracker.get_name() + " of type " + tracker.get_class())
  149. if not new_node:
  150. # No scene defined or able to be instantiated? We're done!
  151. return
  152. # Set up and add to our scene.
  153. new_node.tracker = tracker.name
  154. new_node.pose = "default"
  155. _managed_nodes[tracker] = new_node
  156. add_child(new_node)
  157. added_spatial_entity.emit(new_node)
  158. # A new tracker was added to our XRServer.
  159. func _on_tracker_added(tracker_name: StringName, type: int):
  160. if type == XRServer.TRACKER_ANCHOR:
  161. var tracker: XRTracker = XRServer.get_tracker(tracker_name)
  162. if tracker and tracker is OpenXRSpatialEntityTracker:
  163. _add_tracker(tracker)
  164. # A tracked managed by XRServer was changed.
  165. func _on_tracker_updated(_tracker_name: StringName, _type: int):
  166. # For now we ignore this, there aren't any changes here we need to react
  167. # to and the instanced scene can react to this itself if needed.
  168. pass
  169. # A tracker was removed from our XRServer.
  170. func _on_tracker_removed(tracker_name: StringName, type: int):
  171. if type == XRServer.TRACKER_ANCHOR:
  172. var tracker: XRTracker = XRServer.get_tracker(tracker_name)
  173. if _managed_nodes.has(tracker):
  174. # We emit this right before we remove it!
  175. removed_spatial_entity.emit(_managed_nodes[tracker])
  176. # Remove the node.
  177. remove_child(_managed_nodes[tracker])
  178. # Queue free the node.
  179. _managed_nodes[tracker].queue_free()
  180. # And remove from our managed nodes.
  181. _managed_nodes.erase(tracker)
  182. Spatial anchors
  183. ---------------
  184. Spatial anchors allow us to map real world locations in our virtual world in such a way that the
  185. XR runtime will keep track of these locations and adjust them as needed.
  186. If supported, anchors can be made persistent which means the anchors will be recreated in the correct
  187. location when your application starts again.
  188. You can think of use cases such as:
  189. - placing virtual windows around your space that are recreated when your application restarts
  190. - placing virtual objects on your table or on your walls and have them recreated
  191. Spatial anchors are tracked using :ref:`OpenXRAnchorTracker<class_OpenXRAnchorTracker>` objects
  192. registered with the XRServer.
  193. When needed, the location of the spatial anchor will be updated automatically; the pose on the
  194. related tracker will be updated and thus the :ref:`XRAnchor3D<class_XRAnchor3D>` node will
  195. reposition.
  196. When a spatial anchor has been made persistent, a Universally Unique Identifier (or UUID) is
  197. assigned to the anchor. You will need to store this with whatever information you need to
  198. reconstruct the scene.
  199. In our example code below we'll simply call ``set_scene_path`` and ``get_scene_path``, but you
  200. will need to supply your own implementations for these functions.
  201. In order to create a persistent anchor you need to follow a specific flow:
  202. - Create the spatial anchor
  203. - Wait until the tracking status changes to ``ENTITY_TRACKING_STATE_TRACKING``
  204. - Make the anchor persistent
  205. - Obtain the UUID and save it
  206. When an existing persistent anchor is found a new tracker is added that has the UUID already
  207. set. It is this difference in workflow that allows us to correctly react to new and existing
  208. persistent anchors.
  209. .. note::
  210. If you unpersist an anchor, the UUID is destroyed but the anchor is not
  211. removed automatically.
  212. You will need to react to the completion of unpersisting an anchor and then clean it up.
  213. Also you will get an error if you try to destroy an anchor that is still persistent.
  214. To complete our anchor system we start by creating a scene that we'll set as the scene
  215. to instantiate for anchors on our spatial manager node.
  216. This scene should have an :ref:`XRAnchor3D<class_XRAnchor3D>` node as the root but nothing
  217. else. We will add a script to it that will load a subscene that contains the actual visual
  218. aspect of our anchor so we can create different anchors in our scene.
  219. We'll assume the intention is to make these anchors persistent and save the path to this
  220. subscene as metadata for our UUID.
  221. .. code-block:: gdscript
  222. class_name OpenXRSpatialAnchor3D
  223. extends XRAnchor3D
  224. var anchor_tracker: OpenXRAnchorTracker
  225. var child_scene: Node
  226. var made_persistent: bool = false
  227. ## Return the scene path for our UUID.
  228. func get_scene_path(p_uuid: String) -> String:
  229. # Placeholder, implement this.
  230. return ""
  231. ## Store our scene path for our UUID.
  232. func set_scene_path(p_uuid: String, p_scene_path: String):
  233. # Placeholder, implement this.
  234. pass
  235. ## Remove info related to our UUID.
  236. func remove_uuid(p_uuid: String):
  237. # Placeholder, implement this.
  238. pass
  239. ## Set our child scene for this anchor, call this when creating a new anchor.
  240. func set_child_scene(p_child_scene_path: String):
  241. var packed_scene: PackedScene = load(p_child_scene_path)
  242. if not packed_scene:
  243. return
  244. child_scene = packed_scene.instantiate()
  245. if not child_scene:
  246. return
  247. add_child(child_scene)
  248. # Called when our tracking state changes.
  249. func _on_spatial_tracking_state_changed(new_state) -> void:
  250. if new_state == OpenXRSpatialEntityTracker.ENTITY_TRACKING_STATE_TRACKING and not made_persistent:
  251. # Only attempt to do this once.
  252. made_persistent = true
  253. # This warning is optional if you don't want to rely on persistence.
  254. if not OpenXRSpatialAnchorCapability.is_spatial_persistence_supported():
  255. push_warning("Persistent spatial anchors are not supported on this device!")
  256. return
  257. # Make this persistent, this will notify that the UUID changed on the anchor,
  258. # we can then store our scene path which we've already applied to our
  259. # tracked scene.
  260. OpenXRSpatialAnchorCapability.persist_anchor(anchor_tracker, RID(), Callable())
  261. func _on_uuid_changed() -> void:
  262. if anchor_tracker.uuid != "":
  263. made_persistent = true
  264. if child_scene:
  265. # If we already have a subscene, save that with the UUID.
  266. set_scene_path(anchor_tracker.uuid, child_scene.scene_file_path)
  267. else:
  268. # If we do not, look up the UUID in our stored cache.
  269. var scene_path: String = get_scene_path(anchor_tracker.uuid)
  270. if scene_path.is_empty():
  271. # Give a warning that we don't have a scene file stored for this UUID.
  272. push_warning("Unknown UUID given, can't determine child scene.")
  273. # Load a default scene so we can at least see something.
  274. set_child_scene("res://unknown_anchor.tscn")
  275. return
  276. set_child_scene(scene_path)
  277. func _ready():
  278. anchor_tracker = XRServer.get_tracker(tracker)
  279. if anchor_tracker:
  280. _on_uuid_changed()
  281. anchor_tracker.spatial_tracking_state_changed.connect(_on_spatial_tracking_state_changed)
  282. anchor_tracker.uuid_changed.connect(_on_uuid_changed)
  283. With our anchor scene in place we can add a couple of functions to our spatial manager script
  284. to create or remove anchors:
  285. .. code-block:: gdscript
  286. ...
  287. ## Create a new spatial anchor with the associated child scene.
  288. ## If persistent anchors are supported, this will be created as a persistent node
  289. ## and we will store the child scene path with the anchor's UUID for future recreation.
  290. func create_spatial_anchor(p_transform: Transform3D, p_child_scene_path: String):
  291. # Do we have anchor support?
  292. if not OpenXRSpatialAnchorCapability.is_spatial_anchor_supported():
  293. push_error("Spatial anchors are not supported on this device!")
  294. return
  295. # Adjust our transform to local space.
  296. var t: Transform3D = global_transform.inverse() * p_transform
  297. # Create anchor on our current manager.
  298. var new_anchor = OpenXRSpatialAnchorCapability.create_new_anchor(t, RID())
  299. if not new_anchor:
  300. push_error("Couldn't create an anchor for %s." % [ p_child_scene_path ])
  301. return
  302. # Creating a new anchor should have resulted in an XRAnchor being added to the scene
  303. # by our manager. We can thus continue assuming this has happened.
  304. var anchor_scene = get_tracked_scene(new_anchor)
  305. if not anchor_scene:
  306. push_error("Couldn't locate anchor scene for %s, has the manager been configured with an applicable anchor scene?" % [ new_anchor.name ])
  307. return
  308. if not anchor_scene is OpenXRSpatialAnchor3D:
  309. push_error("Anchor scene for %s is not an OpenXRSpatialAnchor3D scene, has the manager been configured with an applicable anchor scene?" % [ new_anchor.name ])
  310. return
  311. anchor_scene.set_child_scene(p_child_scene_path)
  312. ## Removes this spatial anchor from our scene.
  313. ## If the spatial anchor is persistent, the associated UUID will be cleared.
  314. func remove_spatial_anchor(p_anchor: XRAnchor3D):
  315. # Do we have anchor support?
  316. if not OpenXRSpatialAnchorCapability.is_spatial_anchor_supported():
  317. push_error("Spatial anchors are not supported on this device!")
  318. return
  319. var tracker: XRTracker = XRServer.get_tracker(p_anchor.tracker)
  320. if tracker and tracker is OpenXRAnchorTracker:
  321. var anchor_tracker: OpenXRAnchorTracker = tracker
  322. if anchor_tracker.has_uuid() and OpenXRSpatialAnchorCapability.is_spatial_persistence_supported():
  323. # If we have a UUID we should first make the anchor unpersistent
  324. # and then remove it on its callback.
  325. remove_uuid(anchor_tracker.uuid)
  326. OpenXRSpatialAnchorCapability.unpersist_anchor(anchor_tracker, RID(), _on_unpersist_complete)
  327. else:
  328. # Otherwise we can just remove it.
  329. # This will remove it from the XRServer, which in turn will trigger cleaning up our node.
  330. OpenXRSpatialAnchorCapability.remove_anchor(tracker)
  331. func _on_unpersist_complete(p_tracker: XRTracker):
  332. # Our tracker is now no longer persistent, we can remove it.
  333. OpenXRSpatialAnchorCapability.remove_anchor(p_tracker)
  334. ## Retrieve the scene we've added for a given tracker (if any).
  335. func get_tracked_scene(p_tracker: XRTracker) -> XRNode3D:
  336. for node in get_children():
  337. if node is XRNode3D and node.tracker == p_tracker.name:
  338. return node
  339. return null
  340. .. note::
  341. There seems to be a bit of magic going on in the code above.
  342. Whenever a spatial anchor is created or removed on our anchor capability,
  343. the related tracker object is created or destroyed.
  344. This results in the spatial manager adding or removing the child scene for this
  345. anchor. Hence we can rely on this here.
  346. Plane tracking
  347. --------------
  348. Plane tracking allows us to detect surfaces such as walls, floors, ceilings, and tables in
  349. the player's vicinity. This data could come from a room capture performed by the user at
  350. any time in the past, or detected live by optical sensors.
  351. The plane tracking extension doesn't make a distinction here.
  352. .. note::
  353. Some XR runtimes do require vendor extensions to enable and/or configure this process
  354. but the data will be exposed through this extension.
  355. The code we wrote up above for the spatial manager will already detect our new planes.
  356. We do need to set up a new scene and assign that scene to the spatial manager.
  357. The root node for this scene must be an :ref:`XRAnchor3D<class_XRAnchor3D>` node.
  358. We'll add a :ref:`StaticBody3D<class_StaticBody3D>` node as a child and add a
  359. :ref:`CollisionShape3D<class_CollisionShape3D>` and :ref:`MeshInstance3D<class_MeshInstance3D>`
  360. node as children of the static body.
  361. .. image:: img/openxr_plane_anchor.webp
  362. The static body and collision shape will allow us to make the plane interactable.
  363. The mesh instance node allows us to apply a "hole punch" material to the plane,
  364. when combined with passthrough this turns our plane into a visual occluder.
  365. Alternatively we can assign a material that will visualize the plane for debugging.
  366. We configure this material as the ``material_override`` material on our MeshInstance3D.
  367. For our "hole punch" material, create a :ref:`ShaderMaterial<class_ShaderMaterial>`
  368. and use the following code as the shader code:
  369. .. code-block:: glsl
  370. shader_type spatial;
  371. render_mode unshaded, shadow_to_opacity;
  372. void fragment() {
  373. ALBEDO = vec3(0.0, 0.0, 0.0);
  374. }
  375. We also need to add a script to our scene to ensure our collision and mesh are applied.
  376. .. code-block:: gdscript
  377. extends XRAnchor3D
  378. var plane_tracker: OpenXRPlaneTracker
  379. func _update_mesh_and_collision():
  380. if plane_tracker:
  381. # Place our static body using our offset so both collision
  382. # and mesh are positioned correctly.
  383. $StaticBody3D.transform = plane_tracker.get_mesh_offset()
  384. # Set our mesh so we can occlude the surface.
  385. $StaticBody3D/MeshInstance3D.mesh = plane_tracker.get_mesh()
  386. # And set our shape so we can have things collide things with our surface.
  387. $StaticBody3D/CollisionShape3D.shape = plane_tracker.get_shape()
  388. func _ready():
  389. plane_tracker = XRServer.get_tracker(tracker)
  390. if plane_tracker:
  391. _update_mesh_and_collision()
  392. plane_tracker.mesh_changed.connect(_update_mesh_and_collision)
  393. If supported by the XR runtime there is additional metadata you can query on the plane tracker
  394. object.
  395. Of specific note is the ``plane_label`` property that, if available, identifies the type of surface.
  396. Please consult the :ref:`OpenXRPlaneTracker<class_OpenXRPlaneTracker>` class documentation for
  397. further information.
  398. Marker tracking
  399. ---------------
  400. Marker tracking detects specific markers in the real world. These are usually printed images such
  401. as QR codes.
  402. The API exposes support for 4 different codes, QR codes, Micro QR codes, Aruco codes, and April tags,
  403. however XR runtimes are not required to support them all.
  404. When markers are detected, :ref:`OpenXRMarkerTracker<class_OpenXRMarkerTracker>` objects are
  405. instantiated and registered with the XRServer.
  406. Our existing spatial manager code already detects these, all we need to do is create a scene
  407. with an :ref:`XRAnchor3D<class_XRAnchor3D>` node at the root, save this, and assign it to the
  408. spatial manager as the scene to instantiate for markers.
  409. The marker tracker should be fully configured when assigned, so all that is needed is a
  410. ``_ready`` function that reacts to the marker data. Below is a template for the
  411. required code:
  412. .. code-block:: gdscript
  413. extends XRAnchor3D
  414. var marker_tracker: OpenXRMarkerTracker
  415. func _ready():
  416. marker_tracker = XRServer.get_tracker(tracker)
  417. if marker_tracker:
  418. match marker_tracker.marker_type:
  419. OpenXRSpatialComponentMarkerList.MARKER_TYPE_QRCODE:
  420. var data = marker_tracker.get_marker_data()
  421. if data.type_of() == TYPE_STRING:
  422. # Data is a QR code as a string, usually a URL.
  423. pass
  424. elif data.type_of() == TYPE_PACKED_BYTE_ARRAY:
  425. # Data is binary, can be anything.
  426. pass
  427. OpenXRSpatialComponentMarkerList.MARKER_TYPE_MICRO_QRCODE:
  428. var data = marker_tracker.get_marker_data()
  429. if data.type_of() == TYPE_STRING:
  430. # Data is a QR code as a string, usually a URL.
  431. pass
  432. elif data.type_of() == TYPE_PACKED_BYTE_ARRAY:
  433. # Data is binary, can be anything.
  434. pass
  435. OpenXRSpatialComponentMarkerList.MARKER_TYPE_ARUCO:
  436. # Use marker_tracker.marker_id to identify the marker.
  437. pass
  438. OpenXRSpatialComponentMarkerList.MARKER_TYPE_APRIL_TAG:
  439. # Use marker_tracker.marker_id to identify the marker.
  440. pass
  441. As we can see, QR Codes provide a data block that is either a string or a byte array.
  442. Aruco and April tags provide an ID that is read from the code.
  443. It's up to your use case how best to link the marker data to the scene that needs to be loaded.
  444. An example would be to encode the name of the asset you wish to display in a QR code.
  445. Backend access
  446. --------------
  447. For most purposes the core system, along with any vendor extensions, should be what most
  448. users would use as provided.
  449. For those who are implementing vendor extensions, or those for whom the built-in logic doesn't
  450. suffice, backend access is provided through a set of singleton objects.
  451. These objects can also be used to query what capabilities are supported by the headset in use.
  452. We've already added code that checks for these in our spatial manager and spatial anchor code
  453. in the sections above.
  454. .. note::
  455. The spatial entities system will encapsulate many OpenXR entities in resources that are
  456. returned as RIDs.
  457. Spatial entity core
  458. ~~~~~~~~~~~~~~~~~~~
  459. The core spatial entity functionality is exposed through the
  460. :ref:`OpenXRSpatialEntityExtension<class_OpenXRSpatialEntityExtension>` singleton.
  461. Specific logic is exposed through capabilities that introduce specialised component types,
  462. and give access to specific types of entities, however they all use the same mechanisms
  463. for accessing the entity data managed by the spatial entity system.
  464. We'll start by having a look at the individual components that make up the core system.
  465. Spatial contexts
  466. """"""""""""""""
  467. A spatial context is the main object through which we query the spatial entities system.
  468. Spatial contexts allow us to configure how we interact with one or more capabilities.
  469. It's recommended to create a spatial context for each capability that you wish to interact
  470. with, in fact, this is what Godot does for its built-in logic.
  471. We start by setting the capability configuration objects for the capabilities we wish to
  472. access.
  473. Each capability will enable the components we support for that capability.
  474. Settings can determine which components will be enabled.
  475. We'll look at these configuration objects in more detail as we look at each supported capability.
  476. Creating a spatial context is an asynchronous action. This means we ask the XR runtime to
  477. create a spatial context, and at a point in the future the XR runtime will provide us
  478. with the result.
  479. The following script is the start of our example and can be added as a node to your scene.
  480. It shows the creation of a spatial context for plane tracking,
  481. and sets up our entity discovery.
  482. .. code-block:: gdscript
  483. extends Node
  484. var spatial_context: RID
  485. func _set_up_spatial_context():
  486. # Already set up?
  487. if spatial_context:
  488. return
  489. # Not supported or we're not yet ready?
  490. if not OpenXRSpatialPlaneTrackingCapability.is_supported():
  491. return
  492. # We'll use plane tracking as an example here, our configuration object
  493. # here does not have any additional configuration. It just needs to exist.
  494. var plane_capability : OpenXRSpatialCapabilityConfigurationPlaneTracking = OpenXRSpatialCapabilityConfigurationPlaneTracking.new()
  495. var future_result : OpenXRFutureResult = OpenXRSpatialEntityExtension.create_spatial_context([ plane_capability ])
  496. # Wait for async completion.
  497. await future_result.completed
  498. # Obtain our result.
  499. spatial_context = future_result.get_spatial_context()
  500. if spatial_context:
  501. # Connect to our discovery signal.
  502. OpenXRSpatialEntityExtension.spatial_discovery_recommended.connect(_on_perform_discovery)
  503. # Perform our initial discovery.
  504. _on_perform_discovery(spatial_context)
  505. func _enter_tree():
  506. var openxr_interface : OpenXRInterface = XRServer.find_interface("OpenXR")
  507. if openxr_interface and openxr_interface.is_initialized():
  508. # Just in case our session hasn't started yet,
  509. # call our spatial context creation on start.
  510. openxr_interface.session_begun.connect(_set_up_spatial_context)
  511. # And in case it is already up and running, call it already,
  512. # it will exit if we've called it too early.
  513. _set_up_spatial_context()
  514. func _exit_tree():
  515. if spatial_context:
  516. # Disconnect from our discovery signal.
  517. OpenXRSpatialEntityExtension.spatial_discovery_recommended.disconnect(_on_perform_discovery)
  518. # Free our spatial context, this will clean it up.
  519. OpenXRSpatialEntityExtension.free_spatial_context(spatial_context)
  520. spatial_context = RID()
  521. var openxr_interface : OpenXRInterface = XRServer.find_interface("OpenXR")
  522. if openxr_interface and openxr_interface.is_initialized():
  523. openxr_interface.session_begun.disconnect(_set_up_spatial_context)
  524. func _on_perform_discovery(p_spatial_context):
  525. # See next section.
  526. pass
  527. Discovery snapshots
  528. """""""""""""""""""
  529. Once our spatial context has been created the XR runtime will start managing spatial entities
  530. according to the configuration of the specified capabilities.
  531. In order to find new entities, or to get information about our current entities, we can create
  532. a discovery snapshot. This will tell the XR runtime to gather specific data related to all
  533. the spatial entities currently managed by the spatial context.
  534. This function is asynchronous as it may take some time to gather this data and offer its results.
  535. Generally speaking you will want to perform a discovery snapshot when new entities are found.
  536. OpenXR emits an event when there are new entities to be processed, this results in the
  537. ``spatial_discovery_recommended`` signal being emitted by our
  538. :ref:`OpenXRSpatialEntityExtension<class_OpenXRSpatialEntityExtension>` singleton.
  539. Note in the example code shown above, we're already connecting to this signal and calling the
  540. ``_on_perform_discovery`` method on our node. Let's implement this:
  541. .. code-block:: gdscript
  542. ...
  543. var discovery_result : OpenXRFutureResult
  544. func _on_perform_discovery(p_spatial_context):
  545. # We get this signal for all spatial contexts, so exit if this is not for us.
  546. if p_spatial_context != spatial_context:
  547. return
  548. # If we currently have an ongoing discovery result, cancel it.
  549. if discovery_result:
  550. discovery_result.cancel_discovery()
  551. # Perform our discovery.
  552. discovery_result = OpenXRSpatialEntityExtension.discover_spatial_entities(spatial_context, [ \
  553. OpenXRSpatialEntityExtension.COMPONENT_TYPE_BOUNDED_2D, \
  554. OpenXRSpatialEntityExtension.COMPONENT_TYPE_PLANE_ALIGNMENT \
  555. ])
  556. # Wait for async completion.
  557. await discovery_result.completed
  558. var snapshot : RID = discovery_result.get_spatial_snapshot()
  559. if snapshot:
  560. # Process our snapshot result.
  561. _process_snapshot(snapshot)
  562. # And clean up our snapshot.
  563. OpenXRSpatialEntityExtension.free_spatial_snapshot(snapshot)
  564. func _process_snapshot(p_snapshot):
  565. # See further down.
  566. pass
  567. Note that when calling ``discover_spatial_entities`` we specify a list of components.
  568. The discovery query will find any entity that is managed by the spatial context and has
  569. at least one of the specified components.
  570. Update snapshots
  571. """"""""""""""""
  572. Performing an update snapshot allows us to get updated information about entities
  573. we already found previously with our discovery snapshot.
  574. This function is synchronous, and is mainly meant to obtain status and positioning data
  575. and can be run every frame.
  576. Generally speaking you would only perform update snapshots when it's likely entities
  577. change or have a lifetime process. A good example of this are persistent anchors and
  578. markers. Consult the documentation about a capability to determine if this is needed.
  579. It is not needed for plane tracking however to complete our example, here is an example
  580. of what an update snapshot would look like for plane tracking if we needed one:
  581. .. code-block:: gdscript
  582. ...
  583. func _process(_delta):
  584. if not spatial_context:
  585. return
  586. if entities.is_empty():
  587. return
  588. var entity_rids: Array[RID]
  589. for entity_id in entities:
  590. entity_rids.push_back(entities[entity_id].entity)
  591. var snapshot : RID = OpenXRSpatialEntityExtension.update_spatial_entities(spatial_context, entity_rids, [ \
  592. OpenXRSpatialEntityExtension.COMPONENT_TYPE_BOUNDED_2D, \
  593. OpenXRSpatialEntityExtension.COMPONENT_TYPE_PLANE_ALIGNMENT \
  594. ])
  595. if snapshot:
  596. # Process our snapshot.
  597. _process_snapshot(snapshot)
  598. # And clean up our snapshot.
  599. OpenXRSpatialEntityExtension.free_spatial_snapshot(snapshot)
  600. Note that in our example here we're using the same ``_process_snapshot`` function to process the snapshot.
  601. This makes sense in most situations. However if the components you've specified when creating the snapshot
  602. are different between your discovery snapshot and your update snapshot,
  603. you have to take the different components into account.
  604. Querying snapshots
  605. """"""""""""""""""
  606. Once we have a snapshot we can run queries over that snapshot to obtain the data held within.
  607. The snapshot is guaranteed to remain unchanged until you free it.
  608. For each component we've added to our snapshot we have an accompanying data object.
  609. This data object has a double function, adding it to your query ensures we query that component type,
  610. and it is the object into which the queried data is loaded.
  611. There is one special data object that must always be added to our request list as the very first
  612. entry and that is :ref:`OpenXRSpatialQueryResultData<class_OpenXRSpatialQueryResultData>`.
  613. This object will hold an entry for every returned entity with its unique ID and the current state
  614. of the entity.
  615. Completing our discovery logic we add the following:
  616. .. code-block:: gdscript
  617. ...
  618. var entities : Dictionary[int, OpenXRSpatialEntityTracker]
  619. func _process_snapshot(p_snapshot):
  620. # Always include our query result data.
  621. var query_result_data : OpenXRSpatialQueryResultData = OpenXRSpatialQueryResultData.new()
  622. # Add our bounded 2D component data.
  623. var bounded2d_list : OpenXRSpatialComponentBounded2DList = OpenXRSpatialComponentBounded2DList.new()
  624. # And our plane alignment component data.
  625. var alignment_list : OpenXRSpatialComponentPlaneAlignmentList = OpenXRSpatialComponentPlaneAlignmentList.new()
  626. if OpenXRSpatialEntityExtension.query_snapshot(p_snapshot, [ query_result_data, bounded2d_list, alignment_list]):
  627. for i in query_result_data.get_entity_id_size():
  628. var entity_id = query_result_data.get_entity_id(i)
  629. var entity_state = query_result_data.get_entity_state(i)
  630. if entity_state == OpenXRSpatialEntityTracker.ENTITY_TRACKING_STATE_STOPPED:
  631. # This state should only appear when doing an update snapshot
  632. # and tells us this entity is no longer tracked.
  633. # We thus remove it from our dictionary which should result
  634. # in the entity being cleaned up.
  635. if entities.has(entity_id):
  636. var entity_tracker : OpenXRSpatialEntityTracker = entities[entity_id]
  637. entity_tracker.spatial_tracking_state = entity_state
  638. XRServer.remove_tracker(entity_tracker)
  639. entities.erase(entity_id)
  640. else:
  641. var entity_tracker : OpenXRSpatialEntityTracker
  642. var register_with_xr_server : bool = false
  643. if entities.has(entity_id):
  644. entity_tracker = entities[entity_id]
  645. else:
  646. entity_tracker = OpenXRSpatialEntityTracker.new()
  647. entity_tracker.entity = OpenXRSpatialEntityExtension.make_spatial_entity(spatial_context, entity_id)
  648. entities[entity_id] = entity_tracker
  649. register_with_xr_server = true
  650. # Copy the state.
  651. entity_tracker.spatial_tracking_state = entity_state
  652. # If we're tracking, we should query the rest of our components.
  653. if entity_state == OpenXRSpatialEntityTracker.ENTITY_TRACKING_STATE_TRACKING:
  654. var center_pose : Transform3D = bounded2d_list.get_center_pose(i)
  655. entity_tracker.set_pose("default", center_pose, Vector3(), Vector3(), XRPose.XR_TRACKING_CONFIDENCE_HIGH)
  656. # For this example I'm using OpenXRSpatialEntityTracker which does not
  657. # hold further data. You should extend this class to store the additional
  658. # state retrieved. For plane tracking this would be OpenXRPlaneTracker
  659. # and we can store the following data in the tracker:
  660. var size : Vector2 = bounded2d_list.get_size(i)
  661. var alignment = alignment_list.get_plane_alignment(i)
  662. else:
  663. entity_tracker.invalidate_pose("default")
  664. # We don't register our tracker until after we've set our initial data.
  665. if register_with_xr_server:
  666. XRServer.add_tracker(entity_tracker)
  667. .. note::
  668. In the above example we're relying on ``ENTITY_TRACKING_STATE_STOPPED`` to clean up
  669. spatial entities that are no longer being tracked. This is only available with update snapshots.
  670. For capabilities that only rely on discovery snapshots you may wish to do a cleanup based on
  671. entities that are no longer part of the snapshot instead of relying on the state change.
  672. Spatial entities
  673. """"""""""""""""
  674. With the above information we now know how to query our spatial entities and get information about
  675. them, but there is a little more we need to look at when it comes to the entities themselves.
  676. In theory we're getting all our data from our snapshots, however OpenXR has an extra API
  677. where we create a spatial entity object from our entity ID.
  678. While this object exists the XR runtime knows that we are using this entity and that the
  679. entity is not cleaned up early. This is a prerequisite for performing an update query on
  680. this entity.
  681. In our example code we do so by calling ``OpenXRSpatialEntityExtension.make_spatial_entity``.
  682. Some spatial entity APIs will automatically create the object for us.
  683. In this case we need to call ``OpenXRSpatialEntityExtension.add_spatial_entity`` to register
  684. the created object with our implementation.
  685. Both functions return an RID that we can use in further functions that require our entity object.
  686. When we're done we can call ``OpenXRSpatialEntityExtension.free_spatial_entity``.
  687. Note that we didn't do so in our example code. This is automatically handled when our
  688. :ref:`OpenXRSpatialEntityTracker<class_OpenXRSpatialEntityTracker>` instance is destroyed.
  689. Spatial anchor capability
  690. ~~~~~~~~~~~~~~~~~~~~~~~~~
  691. Spatial anchors are managed by our :ref:`OpenXRSpatialAnchorCapability<class_OpenXRSpatialAnchorCapability>`
  692. singleton object.
  693. After the OpenXR session has been created you can call ``OpenXRSpatialAnchorCapability.is_spatial_anchor_supported``
  694. to check if the spatial anchor feature is supported on your hardware.
  695. The spatial anchor capability breaks the mold a little from what we've shown above.
  696. The spatial anchor system allows us to identify, track, persist, and share a physical location.
  697. What makes this different is that we're creating and destroying the anchor and are thus
  698. managing its lifecycle.
  699. We thus only use the discovery system to discover anchors created and persisted in previous sessions,
  700. or anchors shared with us.
  701. .. note::
  702. Sharing of anchors is currently not supported in the spatial entities specification.
  703. As we showed in our example before we always start with creating a spatial context but now using the
  704. :ref:`OpenXRSpatialCapabilityConfigurationAnchor<class_OpenXRSpatialCapabilityConfigurationAnchor>`
  705. configuration object.
  706. We'll show an example of this code after we discuss persistence scopes.
  707. First we'll look at managing local anchors.
  708. There is no difference in creating spatial anchors from what we've discussed around the built-in
  709. logic. The only important thing is to pass your own spatial context as a parameter to
  710. ``OpenXRSpatialAnchorCapability.create_new_anchor``.
  711. Making an anchor persistent requires you to wait until the anchor is tracking, this means that you
  712. must perform update queries for any anchor you create so you can process state changes.
  713. In order to enable making anchors persistent you also have to set up a persistence scope.
  714. In the core of OpenXR two types of persistence scopes are supported:
  715. .. list-table:: Persistence scopes
  716. :header-rows: 1
  717. * - Enum
  718. - Description
  719. * - PERSISTENCE_SCOPE_SYSTEM_MANAGED
  720. - Provides the application with read-only access (i.e. applications cannot modify this store)
  721. to spatial entities persisted and managed by the system.
  722. The application can use the UUID in the persistence component for this store to correlate
  723. entities across spatial contexts and device reboots.
  724. * - PERSISTENCE_SCOPE_LOCAL_ANCHORS
  725. - Persistence operations and data access is limited to spatial anchors, on the same device,
  726. for the same user and app (using `persist_anchor` and
  727. `unpersist_anchor` functions)
  728. We'll start with a new script that handles our spatial anchors. It will be similar to the
  729. script presented earlier but with a few differences.
  730. The first being the creation of our persistence scope.
  731. .. code-block:: gdscript
  732. extends Node
  733. var persistence_context : RID
  734. func _set_up_persistence_context():
  735. # Already set up?
  736. if persistence_context:
  737. # Check our spatial context.
  738. _set_up_spatial_context()
  739. return
  740. # Not supported or we're not yet ready? Just exit.
  741. if not OpenXRSpatialAnchorCapability.is_spatial_anchor_supported():
  742. return
  743. # If we can't use a persistence scope, just create our spatial context without one.
  744. if not OpenXRSpatialAnchorCapability.is_spatial_persistence_supported():
  745. _set_up_spatial_context()
  746. return
  747. var scope : int = 0
  748. if OpenXRSpatialAnchorCapability.is_persistence_scope_supported(OpenXRSpatialAnchorCapability.PERSISTENCE_SCOPE_LOCAL_ANCHORS):
  749. scope = OpenXRSpatialAnchorCapability.PERSISTENCE_SCOPE_LOCAL_ANCHORS
  750. elif OpenXRSpatialAnchorCapability.is_persistence_scope_supported(OpenXRSpatialAnchorCapability.PERSISTENCE_SCOPE_SYSTEM_MANAGED):
  751. scope = OpenXRSpatialAnchorCapability.PERSISTENCE_SCOPE_SYSTEM_MANAGED
  752. else:
  753. # Don't have a known persistence scope, report and just set up without it.
  754. push_error("No known persistence scope is supported.")
  755. _set_up_spatial_context()
  756. return
  757. # Create our persistence scope.
  758. var future_result : OpenXRFutureResult = OpenXRSpatialAnchorCapability.create_persistence_context(scope)
  759. if not future:
  760. # Couldn't create persistence scope? Just set up without it.
  761. _set_up_spatial_context()
  762. return
  763. # Now wait for our process to complete.
  764. await future_result.completed
  765. # Get our result.
  766. persistence_context = future_result.get_result()
  767. if persistence_context:
  768. # Now set up our spatial context.
  769. _set_up_spatial_context()
  770. func _enter_tree():
  771. var openxr_interface : OpenXRInterface = XRServer.find_interface("OpenXR")
  772. if openxr_interface and openxr_interface.is_initialized():
  773. # Just in case our session hasn't started yet,
  774. # call our context creation on start beginning with our persistence scope.
  775. openxr_interface.session_begun.connect(_set_up_persistence_context)
  776. # And in case it is already up and running, call it already,
  777. # it will exit if we've called it too early.
  778. _set_up_persistence_context()
  779. func _exit_tree():
  780. if spatial_context:
  781. # Disconnect from our discovery signal.
  782. OpenXRSpatialEntityExtension.spatial_discovery_recommended.disconnect(_on_perform_discovery)
  783. # Free our spatial context, this will clean it up.
  784. OpenXRSpatialEntityExtension.free_spatial_context(spatial_context)
  785. spatial_context = RID()
  786. if persistence_context:
  787. # Free our persistence context...
  788. OpenXRSpatialAnchorCapability.free_persistence_context(persistence_context)
  789. persistence_context = RID()
  790. var openxr_interface : OpenXRInterface = XRServer.find_interface("OpenXR")
  791. if openxr_interface and openxr_interface.is_initialized():
  792. openxr_interface.session_begun.disconnect(_set_up_persistence_context)
  793. With our persistence scope created, we can now create our spatial context.
  794. .. code-block:: gdscript
  795. ...
  796. var spatial_context: RID
  797. func _set_up_spatial_context():
  798. # Already set up?
  799. if spatial_context:
  800. return
  801. # Not supported or we're not yet set up.
  802. if not OpenXRSpatialAnchorCapability.is_spatial_anchor_supported():
  803. return
  804. # Create our anchor capability.
  805. var anchor_capability : OpenXRSpatialCapabilityConfigurationAnchor = OpenXRSpatialCapabilityConfigurationAnchor.new()
  806. # And set up our persistence configuration object (if needed).
  807. var persistence_config : OpenXRSpatialContextPersistenceConfig
  808. if persistence_context:
  809. persistence_config = OpenXRSpatialContextPersistenceConfig.new()
  810. persistence_config.add_persistence_context(persistence_context)
  811. var future_result : OpenXRFutureResultg = OpenXRSpatialEntityExtension.create_spatial_context([ anchor_capability ], persistence_config)
  812. # Wait for async completion.
  813. await future_result.completed
  814. # Obtain our result.
  815. spatial_context = future_result.get_spatial_context()
  816. if spatial_context:
  817. # Connect to our discovery signal.
  818. OpenXRSpatialEntityExtension.spatial_discovery_recommended.connect(_on_perform_discovery)
  819. # Perform our initial discovery.
  820. _on_perform_discovery(spatial_context)
  821. Creating our discovery snapshot for our anchors is nearly the same as we did before, however it only makes sense
  822. to create our snapshot for persistent anchors. We already know the anchors we created during our session, we
  823. just want access to those coming from the XR runtime.
  824. We also want to perform regular update queries, here we are only interested in the state so we do want to
  825. process our snapshot slightly differently.
  826. The anchor system gives us access to two components:
  827. .. list-table:: Anchor components
  828. :header-rows: 1
  829. * - Component
  830. - Data class
  831. - Description
  832. * - COMPONENT_TYPE_ANCHOR
  833. - :ref:`OpenXRSpatialComponentAnchorList<class_OpenXRSpatialComponentAnchorList>`
  834. - Provides us with the pose (location + orientation) of each anchor
  835. * - COMPONENT_TYPE_PERSISTENCE
  836. - :ref:`OpenXRSpatialComponentPersistenceList<class_OpenXRSpatialComponentPersistenceList>`
  837. - Provides us with the persistence state and UUID of each anchor
  838. .. code-block:: gdscript
  839. ...
  840. var discovery_result : OpenXRFutureResult
  841. var entities : Dictionary[int, OpenXRAnchorTracker]
  842. func _on_perform_discovery(p_spatial_context):
  843. # We get this signal for all spatial contexts, so exit if this is not for us.
  844. if p_spatial_context != spatial_context:
  845. return
  846. # Skip this if we don't have a persistence context.
  847. if not persistence_context:
  848. return
  849. # If we currently have an ongoing discovery result, cancel it.
  850. if discovery_result:
  851. discovery_result.cancel_discovery()
  852. # Perform our discovery.
  853. discovery_result = OpenXRSpatialEntityExtension.discover_spatial_entities(spatial_context, [ \
  854. OpenXRSpatialEntityExtension.COMPONENT_TYPE_ANCHOR, \
  855. OpenXRSpatialEntityExtension.COMPONENT_TYPE_PERSISTENCE \
  856. ])
  857. # Wait for async completion.
  858. await discovery_result.completed
  859. var snapshot : RID = discovery_result.get_spatial_snapshot()
  860. if snapshot:
  861. # Process our snapshot result.
  862. _process_snapshot(snapshot, true)
  863. # And clean up our snapshot.
  864. OpenXRSpatialEntityExtension.free_spatial_snapshot(snapshot)
  865. func _process(_delta):
  866. if not spatial_context:
  867. return
  868. if entities.is_empty():
  869. return
  870. var entity_rids: Array[RID]
  871. for entity_id in entities:
  872. entity_rids.push_back(entities[entity_id].entity)
  873. # We just want our anchor component here.
  874. var snapshot : RID = OpenXRSpatialEntityExtension.update_spatial_entities(spatial_context, entity_rids, [ \
  875. OpenXRSpatialEntityExtension.COMPONENT_TYPE_ANCHOR, \
  876. ])
  877. if snapshot:
  878. # Process our snapshot.
  879. _process_snapshot(snapshot)
  880. # And clean up our snapshot.
  881. OpenXRSpatialEntityExtension.free_spatial_snapshot(snapshot)
  882. func _process_snapshot(p_snapshot, p_get_uuids):
  883. pass
  884. Finally we can process our snapshot. Note that we are using :ref:`OpenXRAnchorTracker<class_OpenXRAnchorTracker>`
  885. as our tracker class as this already has all the support for anchors built in.
  886. .. code-block:: gdscript
  887. ...
  888. func _process_snapshot(p_snapshot, p_get_uuids):
  889. var result_data : Array
  890. # Always include our query result data.
  891. var query_result_data : OpenXRSpatialQueryResultData = OpenXRSpatialQueryResultData.new()
  892. result_data.push_back(query_result_data)
  893. # Add our anchor component data.
  894. var anchor_list : OpenXRSpatialComponentAnchorList = OpenXRSpatialComponentAnchorList.new()
  895. result_data.push_back(anchor_list)
  896. # And our persistent component data.
  897. var persistent_list : OpenXRSpatialComponentPersistenceList
  898. if p_get_uuids:
  899. # Only add this when we need it.
  900. persistent_list = OpenXRSpatialComponentPersistenceList.new()
  901. result_data.push_back(persistent_list)
  902. if OpenXRSpatialEntityExtension.query_snapshot(p_snapshot, result_data):
  903. for i in query_result_data.get_entity_id_size():
  904. var entity_id = query_result_data.get_entity_id(i)
  905. var entity_state = query_result_data.get_entity_state(i)
  906. if entity_state == OpenXRSpatialEntityTracker.ENTITY_TRACKING_STATE_STOPPED:
  907. # This state should only appear when doing an update snapshot
  908. # and tells us this entity is no longer tracked.
  909. # We thus remove it from our dictionary which should result
  910. # in the entity being cleaned up.
  911. if entities.has(entity_id):
  912. var entity_tracker : OpenXRAnchorTracker = entities[entity_id]
  913. entity_tracker.spatial_tracking_state = entity_state
  914. XRServer.remove_tracker(entity_tracker)
  915. entities.erase(entity_id)
  916. else:
  917. var entity_tracker : OpenXRAnchorTracker
  918. var register_with_xr_server : bool = false
  919. if entities.has(entity_id):
  920. entity_tracker = entities[entity_id]
  921. else:
  922. entity_tracker = OpenXRAnchorTracker.new()
  923. entity_tracker.entity = OpenXRSpatialEntityExtension.make_spatial_entity(spatial_context, entity_id)
  924. entities[entity_id] = entity_tracker
  925. register_with_xr_server = true
  926. # Copy the state.
  927. entity_tracker.spatial_tracking_state = entity_state
  928. # If we're tracking, we update our position.
  929. if entity_state == OpenXRSpatialEntityTracker.ENTITY_TRACKING_STATE_TRACKING:
  930. var anchor_transform = anchor_list.get_entity_pose(i)
  931. entity_tracker.set_pose("default", anchor_transform, Vector3(), Vector3(), XRPose.XR_TRACKING_CONFIDENCE_HIGH)
  932. else:
  933. entity_tracker.invalidate_pose("default")
  934. # But persistence data is a big exception, it can be provided even if we're not tracking.
  935. if p_get_uuids:
  936. var persistent_state = persistent_list.get_persistent_state(i)
  937. if persistent_state == 1:
  938. entity_tracker.uuid = persistent_list.get_persistent_uuid(i)
  939. # We don't register our tracker until after we've set our initial data.
  940. if register_with_xr_server:
  941. XRServer.add_tracker(entity_tracker)
  942. Plane tracking capability
  943. ~~~~~~~~~~~~~~~~~~~~~~~~~
  944. Plane tracking is handled by the
  945. :ref:`OpenXRSpatialPlaneTrackingCapability<class_OpenXRSpatialPlaneTrackingCapability>`
  946. singleton class.
  947. After the OpenXR session has been created you can call ``OpenXRSpatialPlaneTrackingCapability.is_supported``
  948. to check if the plane tracking feature is supported on your hardware.
  949. While we've provided most of the code for plane tracking up above, we'll present the full implementation below
  950. as it has a few small tweaks.
  951. There is no need to update snapshots here, we just do our discovery snapshot and implement our process function.
  952. Plane tracking gives access to two components that are guaranteed to be supported, and three optional components.
  953. .. list-table:: Plane tracking components
  954. :header-rows: 1
  955. * - Component
  956. - Data class
  957. - Description
  958. * - COMPONENT_TYPE_BOUNDED_2D
  959. - :ref:`OpenXRSpatialComponentBounded2DList<class_OpenXRSpatialComponentBounded2DList>`
  960. - Provides us with the center pose and bounding rectangle for each plane.
  961. * - COMPONENT_TYPE_PLANE_ALIGNMENT
  962. - :ref:`OpenXRSpatialComponentPlaneAlignmentList<class_OpenXRSpatialComponentPlaneAlignmentList>`
  963. - Provides us with the alignment of each plane
  964. * - COMPONENT_TYPE_MESH_2D
  965. - :ref:`OpenXRSpatialComponentMesh2DList<class_OpenXRSpatialComponentMesh2DList>`
  966. - Provides us with a 2D mesh that shapes each plane
  967. * - COMPONENT_TYPE_POLYGON_2D
  968. - :ref:`OpenXRSpatialComponentPolygon2DList<class_OpenXRSpatialComponentPolygon2DList>`
  969. - Provides us with a 2D polygon that shapes each plane
  970. * - COMPONENT_TYPE_PLANE_SEMANTIC_LABEL
  971. - :ref:`OpenXRSpatialComponentPlaneSemanticLabelList<class_OpenXRSpatialComponentPlaneSemanticLabelList>`
  972. - Provides us with a type identification of each plane
  973. Our plane tracking configuration object already enables all supported components, but we'll need to interrogate
  974. it so we'll store our instance in a member variable.
  975. We can use our :ref:`OpenXRPlaneTracker<class_OpenXRPlaneTracker>` tracker object to store our component data.
  976. .. code-block:: gdscript
  977. extends Node
  978. var plane_capability : OpenXRSpatialCapabilityConfigurationPlaneTracking
  979. var spatial_context: RID
  980. var discovery_result : OpenXRFutureResult
  981. var entities : Dictionary[int, OpenXRPlaneTracker]
  982. func _set_up_spatial_context():
  983. # Already set up?
  984. if spatial_context:
  985. return
  986. # Not supported or we're not yet ready?
  987. if not OpenXRSpatialPlaneTrackingCapability.is_supported():
  988. return
  989. # We'll use plane tracking as an example here, our configuration object
  990. # here does not have any additional configuration. It just needs to exist.
  991. plane_capability = OpenXRSpatialCapabilityConfigurationPlaneTracking.new()
  992. var future_result : OpenXRFutureResult = OpenXRSpatialEntityExtension.create_spatial_context([ plane_capability ])
  993. # Wait for async completion.
  994. await future_result.completed
  995. # Obtain our result.
  996. spatial_context = future_result.get_spatial_context()
  997. if spatial_context:
  998. # Connect to our discovery signal.
  999. OpenXRSpatialEntityExtension.spatial_discovery_recommended.connect(_on_perform_discovery)
  1000. # Perform our initial discovery.
  1001. _on_perform_discovery(spatial_context)
  1002. func _enter_tree():
  1003. var openxr_interface : OpenXRInterface = XRServer.find_interface("OpenXR")
  1004. if openxr_interface and openxr_interface.is_initialized():
  1005. # Just in case our session hasn't started yet,
  1006. # call our spatial context creation on start.
  1007. openxr_interface.session_begun.connect(_set_up_spatial_context)
  1008. # And in case it is already up and running, call it already,
  1009. # it will exit if we've called it too early.
  1010. _set_up_spatial_context()
  1011. func _exit_tree():
  1012. if spatial_context:
  1013. # Disconnect from our discovery signal.
  1014. OpenXRSpatialEntityExtension.spatial_discovery_recommended.disconnect(_on_perform_discovery)
  1015. # Free our spatial context, this will clean it up.
  1016. OpenXRSpatialEntityExtension.free_spatial_context(spatial_context)
  1017. spatial_context = RID()
  1018. var openxr_interface : OpenXRInterface = XRServer.find_interface("OpenXR")
  1019. if openxr_interface and openxr_interface.is_initialized():
  1020. openxr_interface.session_begun.disconnect(_set_up_spatial_context)
  1021. func _on_perform_discovery(p_spatial_context):
  1022. # We get this signal for all spatial contexts, so exit if this is not for us.
  1023. if p_spatial_context != spatial_context:
  1024. return
  1025. # If we currently have an ongoing discovery result, cancel it.
  1026. if discovery_result:
  1027. discovery_result.cancel_discovery()
  1028. # Perform our discovery.
  1029. discovery_result = OpenXRSpatialEntityExtension.discover_spatial_entities(spatial_context, \
  1030. plane_capability.get_enabled_components())
  1031. # Wait for async completion.
  1032. await discovery_result.completed
  1033. var snapshot : RID = discovery_result.get_spatial_snapshot()
  1034. if snapshot:
  1035. # Process our snapshot result.
  1036. _process_snapshot(snapshot)
  1037. # And clean up our snapshot.
  1038. OpenXRSpatialEntityExtension.free_spatial_snapshot(snapshot)
  1039. func _process_snapshot(p_snapshot):
  1040. var result_data : Array
  1041. # Make a copy of the entities we've currently found.
  1042. var org_entities : PackedInt64Array
  1043. for entity_id in entities:
  1044. org_entities.push_back(entity_id)
  1045. # Always include our query result data.
  1046. var query_result_data : OpenXRSpatialQueryResultData = OpenXRSpatialQueryResultData.new()
  1047. result_data.push_back(query_result_data)
  1048. # Add our bounded 2D component data.
  1049. var bounded2d_list : OpenXRSpatialComponentBounded2DList = OpenXRSpatialComponentBounded2DList.new()
  1050. result_data.push_back(bounded2d_list)
  1051. # And our plane alignment component data.
  1052. var alignment_list : OpenXRSpatialComponentPlaneAlignmentList = OpenXRSpatialComponentPlaneAlignmentList.new()
  1053. result_data.push_back(alignment_list)
  1054. # We need either a Mesh2D or a Polygon2D, we don't need both.
  1055. var mesh2d_list : OpenXRSpatialComponentMesh2DList
  1056. var polygon2d_list : OpenXRSpatialComponentPolygon2DList
  1057. if plane_capability.get_supports_mesh_2d():
  1058. mesh2d_list = OpenXRSpatialComponentMesh2DList.new()
  1059. result_data.push_back(mesh2d_list)
  1060. elif plane_capability.get_supports_polygons():
  1061. polygon2d_list = OpenXRSpatialComponentPolygon2DList.new()
  1062. result_data.push_back(polygon2d_list)
  1063. # And add our semantic labels if supported.
  1064. var label_list : OpenXRSpatialComponentPlaneSemanticLabelList
  1065. if plane_capability.get_supports_labels():
  1066. label_list = OpenXRSpatialComponentPlaneSemanticLabelList.new()
  1067. result_data.push_back(label_list)
  1068. if OpenXRSpatialEntityExtension.query_snapshot(p_snapshot, result_data):
  1069. for i in query_result_data.get_entity_id_size():
  1070. var entity_id = query_result_data.get_entity_id(i)
  1071. var entity_state = query_result_data.get_entity_state(i)
  1072. # Remove the entity from our original list.
  1073. if org_entities.has(entity_id):
  1074. org_entities.erase(entity_id)
  1075. if entity_state == OpenXRSpatialEntityTracker.ENTITY_TRACKING_STATE_STOPPED:
  1076. # We're not doing update snapshots so we shouldn't get this,
  1077. # but just to future proof:
  1078. if entities.has(entity_id):
  1079. var entity_tracker : OpenXRPlaneTracker = entities[entity_id]
  1080. entity_tracker.spatial_tracking_state = entity_state
  1081. XRServer.remove_tracker(entity_tracker)
  1082. entities.erase(entity_id)
  1083. else:
  1084. var entity_tracker : OpenXRPlaneTracker
  1085. var register_with_xr_server : bool = false
  1086. if entities.has(entity_id):
  1087. entity_tracker = entities[entity_id]
  1088. else:
  1089. entity_tracker = OpenXRPlaneTracker.new()
  1090. entity_tracker.entity = OpenXRSpatialEntityExtension.make_spatial_entity(spatial_context, entity_id)
  1091. entities[entity_id] = entity_tracker
  1092. register_with_xr_server = true
  1093. # Copy the state.
  1094. entity_tracker.spatial_tracking_state = entity_state
  1095. # If we're tracking, we should query the rest of our components.
  1096. if entity_state == OpenXRSpatialEntityTracker.ENTITY_TRACKING_STATE_TRACKING:
  1097. var center_pose : Transform3D = bounded2d_list.get_center_pose(i)
  1098. entity_tracker.set_pose("default", center_pose, Vector3(), Vector3(), XRPose.XR_TRACKING_CONFIDENCE_HIGH)
  1099. entity_tracker.bounds_size = bounded2d_list.get_size(i)
  1100. entity_tracker.plane_alignment = alignment_list.get_plane_alignment(i)
  1101. if mesh2d_list:
  1102. entity_tracker.set_mesh_data( \
  1103. mesh2d_list.get_transform(i), \
  1104. mesh2d_list.get_vertices(p_snapshot, i), \
  1105. mesh2d_list.get_indices(p_snapshot, i))
  1106. elif polygon2d_list:
  1107. # The logic in our tracker will convert the polygon to a mesh.
  1108. entity_tracker.set_mesh_data( \
  1109. polygon2d_list.get_transform(i), \
  1110. polygon2d_list.get_vertices(p_snapshot, i))
  1111. else:
  1112. entity_tracker.clear_mesh_data()
  1113. if label_list:
  1114. entity_tracker.plane_label = label_list.get_plane_semantic_label(i)
  1115. else:
  1116. entity_tracker.invalidate_pose("default")
  1117. # We don't register our tracker until after we've set our initial data.
  1118. if register_with_xr_server:
  1119. XRServer.add_tracker(entity_tracker)
  1120. # Any entities we've got left over, we can remove.
  1121. for entity_id in org_entities:
  1122. var entity_tracker : OpenXRPlaneTracker = entities[entity_id]
  1123. entity_tracker.spatial_tracking_state = OpenXRSpatialEntityTracker.ENTITY_TRACKING_STATE_STOPPED
  1124. XRServer.remove_tracker(entity_tracker)
  1125. entities.erase(entity_id)
  1126. Marker tracking capability
  1127. ~~~~~~~~~~~~~~~~~~~~~~~~~~
  1128. Marker tracking is handled by the
  1129. :ref:`OpenXRSpatialMarkerTrackingCapability<class_OpenXRSpatialMarkerTrackingCapability>`
  1130. singleton class.
  1131. Marker tracking works similarly to plane tracking, however we're now tracking specific entities in
  1132. the real world based on some code printed on an object like a piece of paper.
  1133. There are various different marker tracking options. OpenXR supports 4 out of the box, the following
  1134. table provides more information and the function name with which to check if your headset supports
  1135. a given option:
  1136. .. list-table:: Marker tracking options
  1137. :header-rows: 1
  1138. * - Option
  1139. - Check for support
  1140. - Configuration object
  1141. * - April tag
  1142. - ``april_tag_is_supported``
  1143. - :ref:`OpenXRSpatialCapabilityConfigurationAprilTag<class_OpenXRSpatialCapabilityConfigurationAprilTag>`
  1144. * - Aruco
  1145. - ``aruco_is_supported``
  1146. - :ref:`OpenXRSpatialCapabilityConfigurationAruco<class_OpenXRSpatialCapabilityConfigurationAruco>`
  1147. * - QR code
  1148. - ``qrcode_is_supported``
  1149. - :ref:`OpenXRSpatialCapabilityConfigurationQrCode<class_OpenXRSpatialCapabilityConfigurationQrCode>`
  1150. * - Micro QR code
  1151. - ``micro_qrcode_is_supported``
  1152. - :ref:`OpenXRSpatialCapabilityConfigurationMicroQrCode<class_OpenXRSpatialCapabilityConfigurationMicroQrCode>`
  1153. Each option has its own configuration object that you can use when creating a spatial entity.
  1154. QR codes allow you to encode a string which is decoded by the XR runtime and accessible when a marker is found.
  1155. With April tags and Aruco markers, binary data is encoded which you again can access when a marker is found,
  1156. however you need to configure the detection with the correct decoding format.
  1157. As an example we'll create a spatial context that will find QR codes and Aruco markers.
  1158. .. code-block:: gdscript
  1159. extends Node
  1160. var qrcode_config : OpenXRSpatialCapabilityConfigurationQrCode
  1161. var aruco_config : OpenXRSpatialCapabilityConfigurationAruco
  1162. var spatial_context: RID
  1163. func _set_up_spatial_context():
  1164. # Already set up?
  1165. if spatial_context:
  1166. return
  1167. var configurations : Array
  1168. # Add our QR code configuration.
  1169. if not OpenXRSpatialMarkerTrackingCapability.qrcode_is_supported():
  1170. qrcode_config = OpenXRSpatialCapabilityConfigurationQrCode.new()
  1171. configurations.push_back(qrcode_config)
  1172. # Add our Aruco marker configuration.
  1173. if not OpenXRSpatialMarkerTrackingCapability.aruco_is_supported():
  1174. aruco_config = OpenXRSpatialCapabilityConfigurationAruco.new()
  1175. aruco_config.aruco_dict = OpenXRSpatialCapabilityConfigurationAruco.ARUCO_DICT_7X7_1000
  1176. configurations.push_back(aruco_config)
  1177. # Nothing supported?
  1178. if configurations.is_empty():
  1179. return
  1180. var future_result : OpenXRFutureResult = OpenXRSpatialEntityExtension.create_spatial_context(configurations)
  1181. # Wait for async completion.
  1182. await future_result.completed
  1183. # Obtain our result.
  1184. spatial_context = future_result.get_spatial_context()
  1185. if spatial_context:
  1186. # Connect to our discovery signal.
  1187. OpenXRSpatialEntityExtension.spatial_discovery_recommended.connect(_on_perform_discovery)
  1188. # Perform our initial discovery.
  1189. _on_perform_discovery(spatial_context)
  1190. func _enter_tree():
  1191. var openxr_interface : OpenXRInterface = XRServer.find_interface("OpenXR")
  1192. if openxr_interface and openxr_interface.is_initialized():
  1193. # Just in case our session hasn't started yet,
  1194. # call our spatial context creation on start.
  1195. openxr_interface.session_begun.connect(_set_up_spatial_context)
  1196. # And in case it is already up and running, call it already,
  1197. # it will exit if we've called it too early.
  1198. _set_up_spatial_context()
  1199. func _exit_tree():
  1200. if spatial_context:
  1201. # Disconnect from our discovery signal.
  1202. OpenXRSpatialEntityExtension.spatial_discovery_recommended.disconnect(_on_perform_discovery)
  1203. # Free our spatial context, this will clean it up.
  1204. OpenXRSpatialEntityExtension.free_spatial_context(spatial_context)
  1205. spatial_context = RID()
  1206. var openxr_interface : OpenXRInterface = XRServer.find_interface("OpenXR")
  1207. if openxr_interface and openxr_interface.is_initialized():
  1208. openxr_interface.session_begun.disconnect(_set_up_spatial_context)
  1209. Every marker regardless of typer will consist of two components:
  1210. .. list-table:: Marker tracking components
  1211. :header-rows: 1
  1212. * - Component
  1213. - Data class
  1214. - Description
  1215. * - COMPONENT_TYPE_MARKER
  1216. - :ref:`OpenXRSpatialComponentMarkerList<class_OpenXRSpatialComponentMarkerList>`
  1217. - Provides us with the type, ID (Aruco and April Tag), and/or data (QR Code) for each marker.
  1218. * - COMPONENT_TYPE_BOUNDED_2D
  1219. - :ref:`OpenXRSpatialComponentBounded2DList<class_OpenXRSpatialComponentBounded2DList>`
  1220. - Provides us with the center pose and bounding rectangle for each plane.
  1221. We add our discovery implementation:
  1222. .. code-block:: gdscript
  1223. ...
  1224. var discovery_result : OpenXRFutureResult
  1225. var entities : Dictionary[int, OpenXRMarkerTracker]
  1226. func _on_perform_discovery(p_spatial_context):
  1227. # We get this signal for all spatial contexts, so exit if this is not for us.
  1228. if p_spatial_context != spatial_context:
  1229. return
  1230. # If we currently have an ongoing discovery result, cancel it.
  1231. if discovery_result:
  1232. discovery_result.cancel_discovery()
  1233. # Perform our discovery.
  1234. discovery_result = OpenXRSpatialEntityExtension.discover_spatial_entities(spatial_context, [\
  1235. OpenXRSpatialEntityExtension.COMPONENT_TYPE_MARKER, \
  1236. OpenXRSpatialEntityExtension.COMPONENT_TYPE_BOUNDED_2D \
  1237. ])
  1238. # Wait for async completion.
  1239. await discovery_result.completed
  1240. var snapshot : RID = discovery_result.get_spatial_snapshot()
  1241. if snapshot:
  1242. # Process our snapshot result.
  1243. _process_snapshot(snapshot, true)
  1244. # And clean up our snapshot.
  1245. OpenXRSpatialEntityExtension.free_spatial_snapshot(snapshot)
  1246. func _process_snapshot(p_snapshot, bool p_is_discovery):
  1247. var result_data : Array
  1248. # Make a copy of the entities we've currently found.
  1249. var org_entities : PackedInt64Array
  1250. if p_is_discovery:
  1251. # Only on discovery will we check if we have untracked entities to clean up.
  1252. for entity_id in entities:
  1253. org_entities.push_back(entity_id)
  1254. # Always include our query result data.
  1255. var query_result_data : OpenXRSpatialQueryResultData = OpenXRSpatialQueryResultData.new()
  1256. result_data.push_back(query_result_data)
  1257. # And our marker component data.
  1258. var marker_list : OpenXRSpatialComponentMarkerList
  1259. if p_is_discovery:
  1260. # Only on discovery do we check our marker data
  1261. marker_list = OpenXRSpatialComponentMarkerList.new()
  1262. result_data.push_back(marker_list)
  1263. # Add our bounded 2D component data.
  1264. var bounded2d_list : OpenXRSpatialComponentBounded2DList = OpenXRSpatialComponentBounded2DList.new()
  1265. result_data.push_back(bounded2d_list)
  1266. if OpenXRSpatialEntityExtension.query_snapshot(p_snapshot, result_data):
  1267. for i in query_result_data.get_entity_id_size():
  1268. var entity_id = query_result_data.get_entity_id(i)
  1269. var entity_state = query_result_data.get_entity_state(i)
  1270. # Remove the entity from our original list.
  1271. if org_entities.has(entity_id):
  1272. org_entities.erase(entity_id)
  1273. if entity_state == OpenXRSpatialEntityTracker.ENTITY_TRACKING_STATE_STOPPED:
  1274. # We should only get this when doing an update,
  1275. # and we'll remove our marker in that case.
  1276. if entities.has(entity_id):
  1277. var entity_tracker : OpenXRMarkerTracker = entities[entity_id]
  1278. entity_tracker.spatial_tracking_state = entity_state
  1279. XRServer.remove_tracker(entity_tracker)
  1280. entities.erase(entity_id)
  1281. else:
  1282. var entity_tracker : OpenXRMarkerTracker
  1283. var register_with_xr_server : bool = false
  1284. if entities.has(entity_id):
  1285. entity_tracker = entities[entity_id]
  1286. else:
  1287. entity_tracker = OpenXRMarkerTracker.new()
  1288. entity_tracker.entity = OpenXRSpatialEntityExtension.make_spatial_entity(spatial_context, entity_id)
  1289. entities[entity_id] = entity_tracker
  1290. register_with_xr_server = true
  1291. # Copy the state.
  1292. entity_tracker.spatial_tracking_state = entity_state
  1293. # If we're tracking, we should query the rest of our components.
  1294. if entity_state == OpenXRSpatialEntityTracker.ENTITY_TRACKING_STATE_TRACKING:
  1295. var center_pose : Transform3D = bounded2d_list.get_center_pose(i)
  1296. entity_tracker.set_pose("default", center_pose, Vector3(), Vector3(), XRPose.XR_TRACKING_CONFIDENCE_HIGH)
  1297. entity_tracker.bounds_size = bounded2d_list.get_size(i)
  1298. if p_is_discovery:
  1299. entity_tracker.marker_type = marker_list.get_marker_type(i)
  1300. entity_tracker.marker_id = marker_list.get_marker_id(i)
  1301. entity_tracker.marker_data = marker_list.get_marker_data(p_snapshot, i)
  1302. else:
  1303. entity_tracker.invalidate_pose("default")
  1304. # We don't register our tracker until after we've set our initial data.
  1305. if register_with_xr_server:
  1306. XRServer.add_tracker(entity_tracker)
  1307. if p_is_discovery:
  1308. # Any entities we've got left over, we can remove.
  1309. for entity_id in org_entities:
  1310. var entity_tracker : OpenXRMarkerTracker = entities[entity_id]
  1311. entity_tracker.spatial_tracking_state = OpenXRSpatialEntityTracker.ENTITY_TRACKING_STATE_STOPPED
  1312. XRServer.remove_tracker(entity_tracker)
  1313. entities.erase(entity_id)
  1314. And we add our update functionality:
  1315. .. code-block:: gdscript
  1316. ...
  1317. func _process(_delta):
  1318. if not spatial_context:
  1319. return
  1320. if entities.is_empty():
  1321. return
  1322. var entity_rids: Array[RID]
  1323. for entity_id in entities:
  1324. entity_rids.push_back(entities[entity_id].entity)
  1325. # We just want our anchor component here.
  1326. var snapshot : RID = OpenXRSpatialEntityExtension.update_spatial_entities(spatial_context, entity_rids, [ \
  1327. OpenXRSpatialEntityExtension.COMPONENT_TYPE_BOUNDED_2D, \
  1328. ])
  1329. if snapshot:
  1330. # Process our snapshot.
  1331. _process_snapshot(snapshot, false)
  1332. # And clean up our snapshot.
  1333. OpenXRSpatialEntityExtension.free_spatial_snapshot(snapshot)