mirror of
https://github.com/godotengine/godot.git
synced 2025-10-19 16:03:29 +00:00
1184 lines
48 KiB
C++
1184 lines
48 KiB
C++
/**************************************************************************/
|
|
/* openxr_spatial_anchor.cpp */
|
|
/**************************************************************************/
|
|
/* This file is part of: */
|
|
/* GODOT ENGINE */
|
|
/* https://godotengine.org */
|
|
/**************************************************************************/
|
|
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
|
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
|
/* */
|
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
|
/* a copy of this software and associated documentation files (the */
|
|
/* "Software"), to deal in the Software without restriction, including */
|
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
|
/* the following conditions: */
|
|
/* */
|
|
/* The above copyright notice and this permission notice shall be */
|
|
/* included in all copies or substantial portions of the Software. */
|
|
/* */
|
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
/**************************************************************************/
|
|
|
|
#include "openxr_spatial_anchor.h"
|
|
|
|
#include "../../openxr_api.h"
|
|
#include "../../openxr_util.h"
|
|
#include "../openxr_future_extension.h"
|
|
#include "core/config/project_settings.h"
|
|
#include "servers/xr/xr_server.h"
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// OpenXRSpatialCapabilityConfigurationAnchor
|
|
|
|
void OpenXRSpatialCapabilityConfigurationAnchor::_bind_methods() {
|
|
ClassDB::bind_method(D_METHOD("get_enabled_components"), &OpenXRSpatialCapabilityConfigurationAnchor::_get_enabled_components);
|
|
}
|
|
|
|
bool OpenXRSpatialCapabilityConfigurationAnchor::has_valid_configuration() const {
|
|
OpenXRSpatialAnchorCapability *capability = OpenXRSpatialAnchorCapability::get_singleton();
|
|
ERR_FAIL_NULL_V(capability, false);
|
|
|
|
return capability->is_spatial_anchor_supported();
|
|
}
|
|
|
|
XrSpatialCapabilityConfigurationBaseHeaderEXT *OpenXRSpatialCapabilityConfigurationAnchor::get_configuration() {
|
|
OpenXRSpatialAnchorCapability *capability = OpenXRSpatialAnchorCapability::get_singleton();
|
|
ERR_FAIL_NULL_V(capability, nullptr);
|
|
|
|
if (capability->is_spatial_anchor_supported()) {
|
|
OpenXRSpatialEntityExtension *se_extension = OpenXRSpatialEntityExtension::get_singleton();
|
|
ERR_FAIL_NULL_V(se_extension, nullptr);
|
|
|
|
anchor_enabled_components.clear();
|
|
|
|
// Guaranteed components:
|
|
anchor_enabled_components.push_back(XR_SPATIAL_COMPONENT_TYPE_ANCHOR_EXT);
|
|
|
|
// enable optional components
|
|
if (capability->is_spatial_persistence_supported()) {
|
|
anchor_enabled_components.push_back(XR_SPATIAL_COMPONENT_TYPE_PERSISTENCE_EXT);
|
|
}
|
|
|
|
anchor_config.enabledComponentCount = anchor_enabled_components.size();
|
|
anchor_config.enabledComponents = anchor_enabled_components.ptr();
|
|
|
|
// and return this.
|
|
return (XrSpatialCapabilityConfigurationBaseHeaderEXT *)&anchor_config;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
PackedInt64Array OpenXRSpatialCapabilityConfigurationAnchor::_get_enabled_components() const {
|
|
PackedInt64Array components;
|
|
|
|
for (const XrSpatialComponentTypeEXT &component_type : anchor_enabled_components) {
|
|
components.push_back((int64_t)component_type);
|
|
}
|
|
|
|
return components;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// OpenXRSpatialComponentAnchorList
|
|
|
|
void OpenXRSpatialComponentAnchorList::_bind_methods() {
|
|
ClassDB::bind_method(D_METHOD("get_entity_pose", "index"), &OpenXRSpatialComponentAnchorList::get_entity_pose);
|
|
}
|
|
|
|
void OpenXRSpatialComponentAnchorList::set_capacity(uint32_t p_capacity) {
|
|
entity_poses.resize(p_capacity);
|
|
|
|
anchor_list.locationCount = uint32_t(entity_poses.size());
|
|
anchor_list.locations = entity_poses.ptrw();
|
|
}
|
|
|
|
XrSpatialComponentTypeEXT OpenXRSpatialComponentAnchorList::get_component_type() const {
|
|
return XR_SPATIAL_COMPONENT_TYPE_ANCHOR_EXT;
|
|
}
|
|
|
|
void *OpenXRSpatialComponentAnchorList::get_structure_data(void *p_next) {
|
|
anchor_list.next = p_next;
|
|
return &anchor_list;
|
|
}
|
|
|
|
Transform3D OpenXRSpatialComponentAnchorList::get_entity_pose(int64_t p_index) const {
|
|
ERR_FAIL_INDEX_V(p_index, entity_poses.size(), Transform3D());
|
|
|
|
OpenXRAPI *openxr_api = OpenXRAPI::get_singleton();
|
|
ERR_FAIL_NULL_V(openxr_api, Transform3D());
|
|
|
|
return openxr_api->transform_from_pose(entity_poses[p_index]);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// OpenXRSpatialContextPersistenceConfig
|
|
|
|
void OpenXRSpatialContextPersistenceConfig::_bind_methods() {
|
|
ClassDB::bind_method(D_METHOD("add_persistence_context", "persistence_context"), &OpenXRSpatialContextPersistenceConfig::add_persistence_context);
|
|
ClassDB::bind_method(D_METHOD("remove_persistence_context", "persistence_context"), &OpenXRSpatialContextPersistenceConfig::remove_persistence_context);
|
|
}
|
|
|
|
bool OpenXRSpatialContextPersistenceConfig::has_valid_configuration() const {
|
|
OpenXRSpatialAnchorCapability *capability = OpenXRSpatialAnchorCapability::get_singleton();
|
|
ERR_FAIL_NULL_V(capability, false);
|
|
|
|
if (!capability->is_spatial_persistence_supported()) {
|
|
return false;
|
|
}
|
|
|
|
// Check if we have a valid config.
|
|
if (persistence_contexts.is_empty()) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void *OpenXRSpatialContextPersistenceConfig::get_header(void *p_next) {
|
|
void *n = p_next;
|
|
if (get_next().is_valid()) {
|
|
n = get_next()->get_header(n);
|
|
}
|
|
|
|
if (has_valid_configuration()) {
|
|
OpenXRSpatialAnchorCapability *anchor_capability = OpenXRSpatialAnchorCapability::get_singleton();
|
|
ERR_FAIL_NULL_V(anchor_capability, nullptr);
|
|
|
|
// Prepare our buffer.
|
|
context_handles.resize(persistence_contexts.size());
|
|
|
|
// Copy our handles.
|
|
XrSpatialPersistenceContextEXT *ptr = context_handles.ptrw();
|
|
int i = 0;
|
|
for (const RID &rid : persistence_contexts) {
|
|
ptr[i++] = anchor_capability->get_persistence_context_handle(rid);
|
|
}
|
|
|
|
persistence_config.next = n;
|
|
persistence_config.persistenceContextCount = (uint32_t)context_handles.size();
|
|
persistence_config.persistenceContexts = context_handles.ptr();
|
|
|
|
// and return this.
|
|
return (XrSpatialCapabilityConfigurationBaseHeaderEXT *)&persistence_config;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
XrStructureType OpenXRSpatialContextPersistenceConfig::get_structure_type() {
|
|
return XR_TYPE_SPATIAL_CONTEXT_PERSISTENCE_CONFIG_EXT;
|
|
}
|
|
|
|
void OpenXRSpatialContextPersistenceConfig::add_persistence_context(RID p_persistence_context) {
|
|
ERR_FAIL_COND(persistence_contexts.has(p_persistence_context));
|
|
|
|
persistence_contexts.push_back(p_persistence_context);
|
|
}
|
|
|
|
void OpenXRSpatialContextPersistenceConfig::remove_persistence_context(RID p_persistence_context) {
|
|
ERR_FAIL_COND(!persistence_contexts.has(p_persistence_context));
|
|
|
|
persistence_contexts.erase(p_persistence_context);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// OpenXRSpatialComponentPersistenceList
|
|
|
|
void OpenXRSpatialComponentPersistenceList::_bind_methods() {
|
|
ClassDB::bind_method(D_METHOD("get_persistent_uuid", "index"), &OpenXRSpatialComponentPersistenceList::_get_persistent_uuid);
|
|
ClassDB::bind_method(D_METHOD("get_persistent_state", "index"), &OpenXRSpatialComponentPersistenceList::_get_persistent_state);
|
|
}
|
|
|
|
void OpenXRSpatialComponentPersistenceList::set_capacity(uint32_t p_capacity) {
|
|
persist_data.resize(p_capacity);
|
|
|
|
persistence_list.persistDataCount = uint32_t(persist_data.size());
|
|
persistence_list.persistData = persist_data.ptrw();
|
|
}
|
|
|
|
XrSpatialComponentTypeEXT OpenXRSpatialComponentPersistenceList::get_component_type() const {
|
|
return XR_SPATIAL_COMPONENT_TYPE_PERSISTENCE_EXT;
|
|
}
|
|
|
|
void *OpenXRSpatialComponentPersistenceList::get_structure_data(void *p_next) {
|
|
persistence_list.next = p_next;
|
|
return &persistence_list;
|
|
}
|
|
|
|
XrUuid OpenXRSpatialComponentPersistenceList::get_persistent_uuid(int64_t p_index) const {
|
|
XrUuid null_uuid = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
|
|
|
ERR_FAIL_INDEX_V(p_index, persist_data.size(), null_uuid);
|
|
|
|
return persist_data[p_index].persistUuid;
|
|
}
|
|
|
|
String OpenXRSpatialComponentPersistenceList::_get_persistent_uuid(int64_t p_index) const {
|
|
return OpenXRUtil::string_from_xruuid(get_persistent_uuid(p_index));
|
|
}
|
|
|
|
XrSpatialPersistenceStateEXT OpenXRSpatialComponentPersistenceList::get_persistent_state(int64_t p_index) const {
|
|
ERR_FAIL_INDEX_V(p_index, persist_data.size(), XR_SPATIAL_PERSISTENCE_STATE_MAX_ENUM_EXT);
|
|
|
|
return persist_data[p_index].persistState;
|
|
}
|
|
|
|
uint64_t OpenXRSpatialComponentPersistenceList::_get_persistent_state(int64_t p_index) const {
|
|
// TODO make a Godot constant that mirrors XrSpatialPersistenceStateEXT and return that
|
|
return (uint64_t)get_persistent_state(p_index);
|
|
}
|
|
|
|
String OpenXRSpatialComponentPersistenceList::get_persistence_state_name(XrSpatialPersistenceStateEXT p_state) {
|
|
XR_ENUM_SWITCH(XrSpatialPersistenceStateEXT, p_state)
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// OpenXRAnchorTracker
|
|
|
|
void OpenXRAnchorTracker::_bind_methods() {
|
|
ClassDB::bind_method(D_METHOD("has_uuid"), &OpenXRAnchorTracker::has_uuid);
|
|
ClassDB::bind_method(D_METHOD("set_uuid", "uuid"), &OpenXRAnchorTracker::_set_uuid);
|
|
ClassDB::bind_method(D_METHOD("get_uuid"), &OpenXRAnchorTracker::_get_uuid);
|
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::STRING, "uuid"), "set_uuid", "get_uuid");
|
|
|
|
ADD_SIGNAL(MethodInfo("uuid_changed"));
|
|
}
|
|
|
|
bool OpenXRAnchorTracker::has_uuid() const {
|
|
for (int i = 0; i < XR_UUID_SIZE; i++) {
|
|
if (uuid.data[i] != 0) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
XrUuid OpenXRAnchorTracker::get_uuid() const {
|
|
return uuid;
|
|
}
|
|
|
|
void OpenXRAnchorTracker::set_uuid(const XrUuid &p_uuid) {
|
|
if (uuid_is_equal(uuid, p_uuid)) {
|
|
return;
|
|
}
|
|
|
|
uuid = p_uuid;
|
|
|
|
emit_signal(SNAME("uuid_changed"));
|
|
}
|
|
|
|
String OpenXRAnchorTracker::_get_uuid() const {
|
|
return OpenXRUtil::string_from_xruuid(uuid);
|
|
}
|
|
|
|
void OpenXRAnchorTracker::_set_uuid(const String &p_uuid) {
|
|
set_uuid(OpenXRUtil::xruuid_from_string(p_uuid));
|
|
}
|
|
|
|
bool OpenXRAnchorTracker::uuid_is_equal(const XrUuid &p_a, const XrUuid &p_b) {
|
|
for (int i = 0; i < XR_UUID_SIZE; i++) {
|
|
if (p_a.data[i] != p_b.data[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// OpenXRSpatialAnchorCapability
|
|
|
|
OpenXRSpatialAnchorCapability *OpenXRSpatialAnchorCapability::singleton = nullptr;
|
|
|
|
OpenXRSpatialAnchorCapability *OpenXRSpatialAnchorCapability::get_singleton() {
|
|
return singleton;
|
|
}
|
|
|
|
OpenXRSpatialAnchorCapability::OpenXRSpatialAnchorCapability() {
|
|
singleton = this;
|
|
}
|
|
|
|
OpenXRSpatialAnchorCapability::~OpenXRSpatialAnchorCapability() {
|
|
singleton = nullptr;
|
|
}
|
|
|
|
void OpenXRSpatialAnchorCapability::_bind_methods() {
|
|
ClassDB::bind_method(D_METHOD("is_spatial_anchor_supported"), &OpenXRSpatialAnchorCapability::is_spatial_anchor_supported);
|
|
ClassDB::bind_method(D_METHOD("is_spatial_persistence_supported"), &OpenXRSpatialAnchorCapability::is_spatial_persistence_supported);
|
|
|
|
ClassDB::bind_method(D_METHOD("is_persistence_scope_supported", "scope"), &OpenXRSpatialAnchorCapability::_is_persistence_scope_supported);
|
|
ClassDB::bind_method(D_METHOD("create_persistence_context", "scope", "user_callback"), &OpenXRSpatialAnchorCapability::_create_persistence_context, DEFVAL(Callable()));
|
|
ClassDB::bind_method(D_METHOD("get_persistence_context_handle", "persistence_context"), &OpenXRSpatialAnchorCapability::_get_persistence_context_handle);
|
|
ClassDB::bind_method(D_METHOD("free_persistence_context", "persistence_context"), &OpenXRSpatialAnchorCapability::free_persistence_context);
|
|
|
|
ClassDB::bind_method(D_METHOD("create_new_anchor", "transform", "spatial_context"), &OpenXRSpatialAnchorCapability::create_new_anchor, DEFVAL(RID()));
|
|
ClassDB::bind_method(D_METHOD("remove_anchor", "anchor_tracker"), &OpenXRSpatialAnchorCapability::remove_anchor);
|
|
ClassDB::bind_method(D_METHOD("persist_anchor", "anchor_tracker", "persistence_context", "user_callback"), &OpenXRSpatialAnchorCapability::persist_anchor, DEFVAL(RID()), DEFVAL(Callable()));
|
|
ClassDB::bind_method(D_METHOD("unpersist_anchor", "anchor_tracker", "persistence_context", "user_callback"), &OpenXRSpatialAnchorCapability::unpersist_anchor, DEFVAL(RID()), DEFVAL(Callable()));
|
|
|
|
BIND_ENUM_CONSTANT(PERSISTENCE_SCOPE_SYSTEM_MANAGED);
|
|
BIND_ENUM_CONSTANT(PERSISTENCE_SCOPE_LOCAL_ANCHORS);
|
|
}
|
|
|
|
HashMap<String, bool *> OpenXRSpatialAnchorCapability::get_requested_extensions() {
|
|
HashMap<String, bool *> request_extensions;
|
|
|
|
if (GLOBAL_GET_CACHED(bool, "xr/openxr/extensions/spatial_entity/enabled") && GLOBAL_GET_CACHED(bool, "xr/openxr/extensions/spatial_entity/enable_spatial_anchors")) {
|
|
request_extensions[XR_EXT_SPATIAL_ANCHOR_EXTENSION_NAME] = &spatial_anchor_ext;
|
|
if (GLOBAL_GET_CACHED(bool, "xr/openxr/extensions/spatial_entity/enable_persistent_anchors")) {
|
|
request_extensions[XR_EXT_SPATIAL_PERSISTENCE_EXTENSION_NAME] = &spatial_persistence_ext;
|
|
request_extensions[XR_EXT_SPATIAL_PERSISTENCE_OPERATIONS_EXTENSION_NAME] = &spatial_persistence_operations_ext;
|
|
}
|
|
}
|
|
|
|
return request_extensions;
|
|
}
|
|
|
|
void OpenXRSpatialAnchorCapability::on_instance_created(const XrInstance p_instance) {
|
|
OpenXRSpatialEntityExtension *se_extension = OpenXRSpatialEntityExtension::get_singleton();
|
|
ERR_FAIL_NULL(se_extension);
|
|
|
|
if (spatial_anchor_ext) {
|
|
EXT_INIT_XR_FUNC(xrCreateSpatialAnchorEXT);
|
|
}
|
|
|
|
if (spatial_persistence_ext) {
|
|
OpenXRAPI *openxr_api = OpenXRAPI::get_singleton();
|
|
ERR_FAIL_NULL(openxr_api);
|
|
|
|
// TODO REMOVE THIS WORKAROUND ONCE POSSIBLE
|
|
// There has been some back and forth between stores and scopes. Scopes won out.
|
|
XrResult xr_result = openxr_api->get_instance_proc_addr("xrEnumerateSpatialPersistenceScopesEXT", (PFN_xrVoidFunction *)&xrEnumerateSpatialPersistenceScopesEXT_ptr);
|
|
if (xr_result != XR_SUCCESS) {
|
|
// Check for stores for compatibility with beta runtimes.
|
|
// Lucky for us, related structs and enums are compatible.
|
|
print_verbose("OpenXR: xrEnumerateSpatialPersistenceScopesEXT is not supported, falling back to xrEnumerateSpatialPersistenceStoresEXT!");
|
|
xr_result = openxr_api->get_instance_proc_addr("xrEnumerateSpatialPersistenceStoresEXT", (PFN_xrVoidFunction *)&xrEnumerateSpatialPersistenceScopesEXT_ptr);
|
|
}
|
|
ERR_FAIL_COND(XR_FAILED(xr_result));
|
|
//EXT_INIT_XR_FUNC(xrEnumerateSpatialPersistenceScopesEXT);
|
|
|
|
EXT_INIT_XR_FUNC(xrCreateSpatialPersistenceContextAsyncEXT);
|
|
EXT_INIT_XR_FUNC(xrCreateSpatialPersistenceContextCompleteEXT);
|
|
EXT_INIT_XR_FUNC(xrDestroySpatialPersistenceContextEXT);
|
|
}
|
|
|
|
if (spatial_persistence_operations_ext) {
|
|
EXT_INIT_XR_FUNC(xrPersistSpatialEntityAsyncEXT);
|
|
EXT_INIT_XR_FUNC(xrPersistSpatialEntityCompleteEXT);
|
|
EXT_INIT_XR_FUNC(xrUnpersistSpatialEntityAsyncEXT);
|
|
EXT_INIT_XR_FUNC(xrUnpersistSpatialEntityCompleteEXT);
|
|
}
|
|
}
|
|
|
|
void OpenXRSpatialAnchorCapability::on_instance_destroyed() {
|
|
xrCreateSpatialAnchorEXT_ptr = nullptr;
|
|
|
|
xrEnumerateSpatialPersistenceScopesEXT_ptr = nullptr;
|
|
xrCreateSpatialPersistenceContextAsyncEXT_ptr = nullptr;
|
|
xrCreateSpatialPersistenceContextCompleteEXT_ptr = nullptr;
|
|
xrDestroySpatialPersistenceContextEXT_ptr = nullptr;
|
|
}
|
|
|
|
void OpenXRSpatialAnchorCapability::on_session_created(const XrSession p_session) {
|
|
OpenXRSpatialEntityExtension *se_extension = OpenXRSpatialEntityExtension::get_singleton();
|
|
ERR_FAIL_NULL(se_extension);
|
|
|
|
if (!spatial_anchor_ext) {
|
|
return;
|
|
}
|
|
|
|
spatial_anchor_supported = se_extension->supports_component_type(XR_SPATIAL_CAPABILITY_ANCHOR_EXT, XR_SPATIAL_COMPONENT_TYPE_ANCHOR_EXT);
|
|
if (!spatial_anchor_supported) {
|
|
// Supported by XR runtime but not by device? We're done.
|
|
return;
|
|
}
|
|
|
|
_load_supported_persistence_scopes();
|
|
|
|
se_extension->connect(SNAME("spatial_discovery_recommended"), callable_mp(this, &OpenXRSpatialAnchorCapability::_on_spatial_discovery_recommended));
|
|
|
|
if (GLOBAL_GET_CACHED(bool, "xr/openxr/extensions/spatial_entity/enable_builtin_anchor_detection")) {
|
|
if (spatial_persistence_ext && !supported_persistence_scopes.is_empty()) {
|
|
// TODO make something nicer to select the persistence scope we want.
|
|
// We may even want to create multiple here so we get access to all
|
|
// but then mark one that we use to create our persistent anchors on.
|
|
|
|
XrSpatialPersistenceScopeEXT scope = XR_SPATIAL_PERSISTENCE_SCOPE_MAX_ENUM_EXT;
|
|
|
|
// Lets check these in order of importance to us and find the best applicable scope.
|
|
if (supported_persistence_scopes.has(XR_SPATIAL_PERSISTENCE_SCOPE_LOCAL_ANCHORS_EXT)) {
|
|
// This scope allows for local storage and is required if we want to create our own anchors.
|
|
scope = XR_SPATIAL_PERSISTENCE_SCOPE_LOCAL_ANCHORS_EXT;
|
|
} else if (supported_persistence_scopes.has(XR_SPATIAL_PERSISTENCE_SCOPE_SYSTEM_MANAGED_EXT)) {
|
|
// The system managed scope is a read only scope with system managed anchors.
|
|
scope = XR_SPATIAL_PERSISTENCE_SCOPE_SYSTEM_MANAGED_EXT;
|
|
} else {
|
|
// Just use the first supported scope, but this will be an unknown type.
|
|
scope = supported_persistence_scopes[0];
|
|
}
|
|
|
|
// Output what we're using:
|
|
print_verbose("OpenXR: Using persistence scope " + get_spatial_persistence_scope_name(scope));
|
|
|
|
// Start by creating our persistence context.
|
|
create_persistence_context(scope, callable_mp(this, &OpenXRSpatialAnchorCapability::_on_persistence_context_completed));
|
|
} else {
|
|
// Start by creating our spatial context
|
|
_create_spatial_context();
|
|
}
|
|
}
|
|
}
|
|
|
|
void OpenXRSpatialAnchorCapability::on_session_destroyed() {
|
|
if (!spatial_anchor_supported) {
|
|
return;
|
|
}
|
|
spatial_anchor_supported = false;
|
|
|
|
OpenXRSpatialEntityExtension *se_extension = OpenXRSpatialEntityExtension::get_singleton();
|
|
ERR_FAIL_NULL(se_extension);
|
|
XRServer *xr_server = XRServer::get_singleton();
|
|
ERR_FAIL_NULL(xr_server);
|
|
|
|
// Free and unregister our anchors
|
|
for (const KeyValue<XrSpatialEntityIdEXT, Ref<OpenXRAnchorTracker>> &anchor : anchors) {
|
|
xr_server->remove_tracker(anchor.value);
|
|
}
|
|
anchors.clear();
|
|
|
|
// Free our configurations
|
|
anchor_configuration.unref();
|
|
|
|
// Free our spatial context
|
|
if (spatial_context.is_valid()) {
|
|
se_extension->free_spatial_context(spatial_context);
|
|
spatial_context = RID();
|
|
}
|
|
|
|
// Free our persistence context
|
|
if (persistence_context.is_valid()) {
|
|
free_persistence_context(persistence_context);
|
|
persistence_context = RID();
|
|
}
|
|
|
|
se_extension->disconnect(SNAME("spatial_discovery_recommended"), callable_mp(this, &OpenXRSpatialAnchorCapability::_on_spatial_discovery_recommended));
|
|
|
|
supported_persistence_scopes.clear();
|
|
|
|
// Clean up all remaining persistence context RIDs.
|
|
LocalVector<RID> persistence_context_rids = persistence_context_owner.get_owned_list();
|
|
for (const RID &rid : persistence_context_rids) {
|
|
if (is_print_verbose_enabled()) {
|
|
PersistenceContextData *persistence_context_data = persistence_context_owner.get_or_null(rid);
|
|
if (persistence_context_data) { // Should never be nullptr seeing we called get_owned_list just now, but just in case.
|
|
print_line("OpenXR: Found orphaned persistence context for scope ", get_spatial_persistence_scope_name(persistence_context_data->scope));
|
|
}
|
|
}
|
|
|
|
free_persistence_context(rid);
|
|
}
|
|
}
|
|
|
|
void OpenXRSpatialAnchorCapability::on_process() {
|
|
if (!spatial_context.is_valid()) {
|
|
return;
|
|
}
|
|
|
|
// Protection against plane discovery happening too often.
|
|
if (discovery_cooldown > 0) {
|
|
discovery_cooldown--;
|
|
}
|
|
|
|
// Check if we need to start our discovery.
|
|
if (need_discovery && discovery_cooldown == 0 && !discovery_query_result.is_valid()) {
|
|
need_discovery = false;
|
|
discovery_cooldown = 60; // Set our cooldown to 60 frames, it doesn't need to be an exact science.
|
|
|
|
_start_entity_discovery();
|
|
}
|
|
|
|
// If we have a valid spatial context, and we have anchors, we want updates!
|
|
if (spatial_context.is_valid() && !anchors.is_empty()) {
|
|
OpenXRSpatialEntityExtension *se_extension = OpenXRSpatialEntityExtension::get_singleton();
|
|
ERR_FAIL_NULL(se_extension);
|
|
|
|
// We want updates for all anchors
|
|
thread_local LocalVector<RID> entities;
|
|
entities.resize(anchors.size());
|
|
RID *entity = entities.ptr();
|
|
for (const KeyValue<XrSpatialEntityIdEXT, Ref<OpenXRAnchorTracker>> &e : anchors) {
|
|
*entity = e.value->get_entity();
|
|
entity++;
|
|
}
|
|
|
|
// We just want our anchor component
|
|
thread_local LocalVector<XrSpatialComponentTypeEXT> component_types;
|
|
component_types.push_back(XR_SPATIAL_COMPONENT_TYPE_ANCHOR_EXT);
|
|
|
|
// And we get our update snapshot, this is NOT async!
|
|
RID snapshot = se_extension->update_spatial_entities(spatial_context, entities, component_types, nullptr);
|
|
if (snapshot.is_valid()) {
|
|
_process_update_snapshot(snapshot);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool OpenXRSpatialAnchorCapability::is_spatial_anchor_supported() {
|
|
return spatial_anchor_supported;
|
|
}
|
|
|
|
bool OpenXRSpatialAnchorCapability::is_spatial_persistence_supported() {
|
|
// Need anchor support for persistence to be usable
|
|
if (!is_spatial_anchor_supported()) {
|
|
return false;
|
|
}
|
|
|
|
return spatial_persistence_ext;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// Persistence scopes
|
|
|
|
bool OpenXRSpatialAnchorCapability::_load_supported_persistence_scopes() {
|
|
ERR_FAIL_COND_V(!is_spatial_persistence_supported(), false);
|
|
|
|
if (supported_persistence_scopes.is_empty()) {
|
|
OpenXRAPI *openxr_api = OpenXRAPI::get_singleton();
|
|
ERR_FAIL_NULL_V(openxr_api, false);
|
|
|
|
uint32_t size;
|
|
XrInstance instance = openxr_api->get_instance();
|
|
XrSystemId system_id = openxr_api->get_system_id();
|
|
|
|
ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false);
|
|
|
|
XrResult result = xrEnumerateSpatialPersistenceScopesEXT(instance, system_id, 0, &size, nullptr);
|
|
if (XR_FAILED(result)) {
|
|
ERR_FAIL_V_MSG(false, "OpenXR: Failed to query persistence scope count [" + openxr_api->get_error_string(result) + "]");
|
|
}
|
|
|
|
if (size > 0) {
|
|
supported_persistence_scopes.resize(size);
|
|
result = xrEnumerateSpatialPersistenceScopesEXT(instance, system_id, supported_persistence_scopes.size(), &size, supported_persistence_scopes.ptrw());
|
|
if (XR_FAILED(result)) {
|
|
ERR_FAIL_V_MSG(false, "OpenXR: Failed to query persistence scopes [" + openxr_api->get_error_string(result) + "]");
|
|
}
|
|
}
|
|
|
|
if (is_print_verbose_enabled()) {
|
|
if (!supported_persistence_scopes.is_empty()) {
|
|
print_verbose("OpenXR: Supported spatial persistence scopes:");
|
|
for (const XrSpatialPersistenceScopeEXT &scope : supported_persistence_scopes) {
|
|
print_verbose(" - " + get_spatial_persistence_scope_name(scope));
|
|
}
|
|
} else {
|
|
WARN_PRINT("OpenXR: No persistence scopes found!");
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool OpenXRSpatialAnchorCapability::is_persistence_scope_supported(XrSpatialPersistenceScopeEXT p_scope) {
|
|
if (!is_spatial_persistence_supported()) {
|
|
return false;
|
|
}
|
|
|
|
if (!_load_supported_persistence_scopes()) {
|
|
return false;
|
|
}
|
|
|
|
return supported_persistence_scopes.has(p_scope);
|
|
}
|
|
|
|
bool OpenXRSpatialAnchorCapability::_is_persistence_scope_supported(PersistenceScope p_scope) {
|
|
return is_persistence_scope_supported((XrSpatialPersistenceScopeEXT)p_scope);
|
|
}
|
|
|
|
Ref<OpenXRFutureResult> OpenXRSpatialAnchorCapability::create_persistence_context(XrSpatialPersistenceScopeEXT p_scope, const Callable &p_user_callback) {
|
|
if (!is_spatial_persistence_supported()) {
|
|
return nullptr;
|
|
}
|
|
|
|
OpenXRAPI *openxr_api = OpenXRAPI::get_singleton();
|
|
ERR_FAIL_NULL_V(openxr_api, nullptr);
|
|
|
|
OpenXRFutureExtension *future_api = OpenXRFutureExtension::get_singleton();
|
|
ERR_FAIL_NULL_V(future_api, nullptr);
|
|
|
|
XrSpatialPersistenceContextCreateInfoEXT create_info = {
|
|
XR_TYPE_SPATIAL_PERSISTENCE_CONTEXT_CREATE_INFO_EXT, // type
|
|
nullptr, // next
|
|
p_scope // scope
|
|
};
|
|
XrFutureEXT future = XR_NULL_HANDLE;
|
|
XrResult result = xrCreateSpatialPersistenceContextAsyncEXT(openxr_api->get_session(), &create_info, &future);
|
|
if (XR_FAILED(result)) {
|
|
// Not successful? then exit.
|
|
ERR_FAIL_V_MSG(Ref<OpenXRFutureResult>(), "OpenXR: Failed to create persistence scope [" + openxr_api->get_error_string(result) + "]");
|
|
}
|
|
|
|
// Create our future result
|
|
Ref<OpenXRFutureResult> future_result = future_api->register_future(future, callable_mp(this, &OpenXRSpatialAnchorCapability::_on_persistence_context_ready).bind((uint64_t)p_scope, p_user_callback));
|
|
|
|
return future_result;
|
|
}
|
|
|
|
Ref<OpenXRFutureResult> OpenXRSpatialAnchorCapability::_create_persistence_context(PersistenceScope p_scope, Callable p_user_callback) {
|
|
return create_persistence_context((XrSpatialPersistenceScopeEXT)p_scope, p_user_callback);
|
|
}
|
|
|
|
void OpenXRSpatialAnchorCapability::_on_persistence_context_ready(Ref<OpenXRFutureResult> p_future_result, uint64_t p_scope, Callable p_user_callback) {
|
|
// Complete context creation...
|
|
OpenXRAPI *openxr_api = OpenXRAPI::get_singleton();
|
|
ERR_FAIL_NULL(openxr_api);
|
|
|
|
XrCreateSpatialPersistenceContextCompletionEXT completion = {
|
|
XR_TYPE_CREATE_SPATIAL_PERSISTENCE_CONTEXT_COMPLETION_EXT, // type
|
|
nullptr, // next
|
|
XR_RESULT_MAX_ENUM, // futureResult
|
|
XR_SPATIAL_PERSISTENCE_CONTEXT_RESULT_MAX_ENUM_EXT, // createResult
|
|
XR_NULL_HANDLE // persistenceContext
|
|
};
|
|
XrResult result = xrCreateSpatialPersistenceContextCompleteEXT(openxr_api->get_session(), p_future_result->get_future(), &completion);
|
|
if (XR_FAILED(result)) { // Did our xrCreateSpatialContextCompleteEXT call fail?
|
|
// Log issue and fail.
|
|
ERR_FAIL_MSG("OpenXR: Failed to complete persistence context create future [" + openxr_api->get_error_string(result) + "]");
|
|
}
|
|
if (XR_FAILED(completion.futureResult)) { // Did our completion fail?
|
|
// Log issue and fail.
|
|
ERR_FAIL_MSG("OpenXR: Failed to complete persistence context creation [" + openxr_api->get_error_string(completion.futureResult) + "]");
|
|
}
|
|
if (completion.createResult != XR_SPATIAL_PERSISTENCE_CONTEXT_RESULT_SUCCESS_EXT) { // Did our persist fail?
|
|
// Log issue and fail.
|
|
ERR_FAIL_MSG("OpenXR: Failed to complete persistence context creation [" + get_spatial_persistence_context_result_name(completion.createResult) + "]");
|
|
}
|
|
|
|
// Wrap our persistence context
|
|
PersistenceContextData persistence_context_data;
|
|
|
|
// Update our spatial context data
|
|
persistence_context_data.scope = (XrSpatialPersistenceScopeEXT)p_scope;
|
|
persistence_context_data.persistence_context = completion.persistenceContext;
|
|
|
|
// Store this as an RID so we keep track of it.
|
|
RID context_rid = persistence_context_owner.make_rid(persistence_context_data);
|
|
|
|
// Set our RID as our result value on our future.
|
|
p_future_result->set_result_value(context_rid);
|
|
|
|
// And perform our callback if we have one.
|
|
if (p_user_callback.is_valid()) {
|
|
p_user_callback.call(context_rid);
|
|
}
|
|
}
|
|
|
|
XrSpatialPersistenceContextEXT OpenXRSpatialAnchorCapability::get_persistence_context_handle(RID p_persistence_context) const {
|
|
PersistenceContextData *persistence_context_data = persistence_context_owner.get_or_null(p_persistence_context);
|
|
ERR_FAIL_NULL_V(persistence_context_data, XR_NULL_HANDLE);
|
|
|
|
return persistence_context_data->persistence_context;
|
|
}
|
|
|
|
uint64_t OpenXRSpatialAnchorCapability::_get_persistence_context_handle(RID p_persistence_context) const {
|
|
return (uint64_t)get_persistence_context_handle(p_persistence_context);
|
|
}
|
|
|
|
void OpenXRSpatialAnchorCapability::free_persistence_context(RID p_persistence_context) {
|
|
PersistenceContextData *persistence_context_data = persistence_context_owner.get_or_null(p_persistence_context);
|
|
ERR_FAIL_NULL(persistence_context_data);
|
|
|
|
OpenXRAPI *openxr_api = OpenXRAPI::get_singleton();
|
|
ERR_FAIL_NULL(openxr_api);
|
|
|
|
if (persistence_context_data->persistence_context != XR_NULL_HANDLE) {
|
|
// Destroy our spatial context
|
|
XrResult result = xrDestroySpatialPersistenceContextEXT(persistence_context_data->persistence_context);
|
|
if (XR_FAILED(result)) {
|
|
WARN_PRINT("OpenXR: Failed to destroy the persistence context [" + openxr_api->get_error_string(result) + "]");
|
|
}
|
|
persistence_context_data->persistence_context = XR_NULL_HANDLE;
|
|
}
|
|
|
|
// And remove our RID.
|
|
persistence_context_owner.free(p_persistence_context);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// Discovery logic
|
|
|
|
void OpenXRSpatialAnchorCapability::_on_persistence_context_completed(RID p_persistence_context) {
|
|
persistence_context = p_persistence_context;
|
|
|
|
_create_spatial_context();
|
|
}
|
|
|
|
Ref<OpenXRFutureResult> OpenXRSpatialAnchorCapability::_create_spatial_context() {
|
|
OpenXRSpatialEntityExtension *se_extension = OpenXRSpatialEntityExtension::get_singleton();
|
|
ERR_FAIL_NULL_V(se_extension, nullptr);
|
|
|
|
TypedArray<OpenXRSpatialCapabilityConfigurationBaseHeader> capability_configurations;
|
|
|
|
// Create our configuration objects.
|
|
anchor_configuration.instantiate();
|
|
capability_configurations.push_back(anchor_configuration);
|
|
|
|
if (persistence_context.is_valid()) {
|
|
persistence_configuration.instantiate();
|
|
persistence_configuration->add_persistence_context(persistence_context);
|
|
} else {
|
|
// Shouldn't be instantiated in the first place but JIC
|
|
persistence_configuration.unref();
|
|
}
|
|
|
|
return se_extension->create_spatial_context(capability_configurations, persistence_configuration, callable_mp(this, &OpenXRSpatialAnchorCapability::_on_spatial_context_created));
|
|
}
|
|
|
|
void OpenXRSpatialAnchorCapability::_on_spatial_context_created(RID p_spatial_context) {
|
|
spatial_context = p_spatial_context;
|
|
need_discovery = true;
|
|
}
|
|
|
|
void OpenXRSpatialAnchorCapability::_on_spatial_discovery_recommended(RID p_spatial_context) {
|
|
if (p_spatial_context == spatial_context) {
|
|
// Trigger new discovery.
|
|
need_discovery = true;
|
|
}
|
|
}
|
|
|
|
Ref<OpenXRFutureResult> OpenXRSpatialAnchorCapability::_start_entity_discovery() {
|
|
OpenXRSpatialEntityExtension *se_extension = OpenXRSpatialEntityExtension::get_singleton();
|
|
ERR_FAIL_NULL_V(se_extension, nullptr);
|
|
|
|
// It makes no sense to discover non persistent anchors as we'd have created them during this session.
|
|
if (!persistence_context.is_valid()) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Already running or ran discovery, cancel/clean up.
|
|
if (discovery_query_result.is_valid()) {
|
|
discovery_query_result->cancel_future();
|
|
discovery_query_result.unref();
|
|
}
|
|
|
|
// We want both our anchor and persistence component.
|
|
Vector<XrSpatialComponentTypeEXT> component_types;
|
|
component_types.push_back(XR_SPATIAL_COMPONENT_TYPE_ANCHOR_EXT);
|
|
component_types.push_back(XR_SPATIAL_COMPONENT_TYPE_PERSISTENCE_EXT);
|
|
|
|
// Start our new snapshot.
|
|
discovery_query_result = se_extension->discover_spatial_entities(spatial_context, component_types, nullptr, callable_mp(this, &OpenXRSpatialAnchorCapability::_process_discovery_snapshot));
|
|
|
|
return discovery_query_result;
|
|
}
|
|
|
|
void OpenXRSpatialAnchorCapability::_process_discovery_snapshot(RID p_snapshot) {
|
|
OpenXRSpatialEntityExtension *se_extension = OpenXRSpatialEntityExtension::get_singleton();
|
|
ERR_FAIL_NULL(se_extension);
|
|
XRServer *xr_server = XRServer::get_singleton();
|
|
ERR_FAIL_NULL(xr_server);
|
|
OpenXRAPI *openxr_api = OpenXRAPI::get_singleton();
|
|
ERR_FAIL_NULL(openxr_api);
|
|
|
|
// Build our component data.
|
|
TypedArray<OpenXRSpatialComponentData> component_data;
|
|
|
|
// We always need a query result data object.
|
|
Ref<OpenXRSpatialQueryResultData> query_result_data;
|
|
query_result_data.instantiate();
|
|
component_data.push_back(query_result_data);
|
|
|
|
// And an anchor list object.
|
|
Ref<OpenXRSpatialComponentAnchorList> anchor_list_data;
|
|
anchor_list_data.instantiate();
|
|
component_data.push_back(anchor_list_data);
|
|
|
|
// Note that adding this data object means our snapshot will only return persistent anchors!
|
|
Ref<OpenXRSpatialComponentPersistenceList> persistence_list_data;
|
|
persistence_list_data.instantiate();
|
|
component_data.push_back(persistence_list_data);
|
|
|
|
if (se_extension->query_snapshot(p_snapshot, component_data, nullptr)) {
|
|
// Now loop through our data and update our anchors.
|
|
int64_t size = query_result_data->get_capacity();
|
|
for (int64_t i = 0; i < size; i++) {
|
|
XrSpatialEntityIdEXT entity_id = query_result_data->get_entity_id(i);
|
|
XrSpatialEntityTrackingStateEXT entity_state = query_result_data->get_entity_state(i);
|
|
|
|
if (entity_state == XR_SPATIAL_ENTITY_TRACKING_STATE_STOPPED_EXT) {
|
|
// We shouldn't get stopped anchors for discovery queries, but JIC.
|
|
if (anchors.has(entity_id)) {
|
|
Ref<OpenXRAnchorTracker> anchor = anchors[entity_id];
|
|
anchor->invalidate_pose(SNAME("default"));
|
|
anchor->set_spatial_tracking_state(XR_SPATIAL_ENTITY_TRACKING_STATE_STOPPED_EXT);
|
|
}
|
|
} else {
|
|
// Process our entity.
|
|
bool add_to_xr_server = false;
|
|
Ref<OpenXRAnchorTracker> anchor;
|
|
|
|
if (anchors.has(entity_id)) {
|
|
// We know about this one already.
|
|
anchor = anchors[entity_id];
|
|
} else {
|
|
// Create a new anchor.
|
|
anchor.instantiate();
|
|
anchor->set_entity(se_extension->make_spatial_entity(se_extension->get_spatial_snapshot_context(p_snapshot), entity_id));
|
|
anchors[entity_id] = anchor;
|
|
|
|
add_to_xr_server = true;
|
|
}
|
|
|
|
// Handle component data.
|
|
if (entity_state == XR_SPATIAL_ENTITY_TRACKING_STATE_PAUSED_EXT) {
|
|
anchor->invalidate_pose(SNAME("default"));
|
|
anchor->set_spatial_tracking_state(XR_SPATIAL_ENTITY_TRACKING_STATE_PAUSED_EXT);
|
|
|
|
// No further component data will be valid in this state, we need to ignore it!
|
|
} else if (entity_state == XR_SPATIAL_ENTITY_TRACKING_STATE_TRACKING_EXT) {
|
|
Transform3D transform = anchor_list_data->get_entity_pose(i);
|
|
anchor->set_pose(SNAME("default"), transform, Vector3(), Vector3());
|
|
anchor->set_spatial_tracking_state(XR_SPATIAL_ENTITY_TRACKING_STATE_TRACKING_EXT);
|
|
}
|
|
|
|
// Persistence is the only component that will contain valid data if entity_state == XR_SPATIAL_ENTITY_TRACKING_STATE_PAUSED_EXT.
|
|
const XrSpatialPersistenceStateEXT persistent_state = persistence_list_data->get_persistent_state(i);
|
|
if (persistent_state == XR_SPATIAL_PERSISTENCE_STATE_LOADED_EXT) {
|
|
anchor->set_uuid(persistence_list_data->get_persistent_uuid(i));
|
|
}
|
|
|
|
if (add_to_xr_server) {
|
|
// Register with XR server.
|
|
xr_server->add_tracker(anchor);
|
|
}
|
|
}
|
|
}
|
|
|
|
// We don't remove trackers here, users will be removing anchors.
|
|
// Maybe at some point when shared anchors between headsets result
|
|
// in another device removing the shared anchor we need to deal with this.
|
|
}
|
|
|
|
// Now that we're done, clean up our snapshot!
|
|
se_extension->free_spatial_snapshot(p_snapshot);
|
|
|
|
// And if this was our discovery snapshot, let's reset it.
|
|
if (discovery_query_result.is_valid() && discovery_query_result->get_result_value() == p_snapshot) {
|
|
discovery_query_result.unref();
|
|
}
|
|
}
|
|
|
|
void OpenXRSpatialAnchorCapability::_process_update_snapshot(RID p_snapshot) {
|
|
OpenXRSpatialEntityExtension *se_extension = OpenXRSpatialEntityExtension::get_singleton();
|
|
ERR_FAIL_NULL(se_extension);
|
|
XRServer *xr_server = XRServer::get_singleton();
|
|
ERR_FAIL_NULL(xr_server);
|
|
OpenXRAPI *openxr_api = OpenXRAPI::get_singleton();
|
|
ERR_FAIL_NULL(openxr_api);
|
|
|
|
// Build our component data.
|
|
TypedArray<OpenXRSpatialComponentData> component_data;
|
|
|
|
// We always need a query result data object.
|
|
Ref<OpenXRSpatialQueryResultData> query_result_data;
|
|
query_result_data.instantiate();
|
|
component_data.push_back(query_result_data);
|
|
|
|
// And an anchor list object.
|
|
Ref<OpenXRSpatialComponentAnchorList> anchor_list_data;
|
|
anchor_list_data.instantiate();
|
|
component_data.push_back(anchor_list_data);
|
|
|
|
if (se_extension->query_snapshot(p_snapshot, component_data, nullptr)) {
|
|
// Now loop through our data and update our anchors.
|
|
int64_t size = query_result_data->get_capacity();
|
|
for (int64_t i = 0; i < size; i++) {
|
|
XrSpatialEntityIdEXT entity_id = query_result_data->get_entity_id(i);
|
|
XrSpatialEntityTrackingStateEXT entity_state = query_result_data->get_entity_state(i);
|
|
|
|
if (anchors.has(entity_id)) {
|
|
// Process our entity.
|
|
Ref<OpenXRAnchorTracker> anchor = anchors[entity_id];
|
|
|
|
if (entity_state == XR_SPATIAL_ENTITY_TRACKING_STATE_STOPPED_EXT) {
|
|
anchor->invalidate_pose(SNAME("default"));
|
|
anchor->set_spatial_tracking_state(XR_SPATIAL_ENTITY_TRACKING_STATE_STOPPED_EXT);
|
|
} else if (entity_state == XR_SPATIAL_ENTITY_TRACKING_STATE_PAUSED_EXT) {
|
|
anchor->invalidate_pose(SNAME("default"));
|
|
anchor->set_spatial_tracking_state(XR_SPATIAL_ENTITY_TRACKING_STATE_PAUSED_EXT);
|
|
} else if (entity_state == XR_SPATIAL_ENTITY_TRACKING_STATE_TRACKING_EXT) {
|
|
Transform3D transform = anchor_list_data->get_entity_pose(i);
|
|
anchor->set_pose(SNAME("default"), transform, Vector3(), Vector3());
|
|
anchor->set_spatial_tracking_state(XR_SPATIAL_ENTITY_TRACKING_STATE_TRACKING_EXT);
|
|
}
|
|
} else {
|
|
WARN_PRINT("OpenXR: Anchor update query returned unknown anchor with entity ID: " + String::num_int64(entity_id));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now that we're done, clean up our snapshot!
|
|
se_extension->free_spatial_snapshot(p_snapshot);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// Anchor creation
|
|
|
|
Ref<OpenXRAnchorTracker> OpenXRSpatialAnchorCapability::create_new_anchor(const Transform3D &p_transform, RID p_spatial_context) {
|
|
Ref<OpenXRAnchorTracker> tracker;
|
|
|
|
ERR_FAIL_COND_V_MSG(!is_spatial_anchor_supported(), tracker, "OpenXR: Spatial entity anchor capability is not supported on this hardware!");
|
|
|
|
OpenXRAPI *openxr_api = OpenXRAPI::get_singleton();
|
|
ERR_FAIL_NULL_V(openxr_api, tracker);
|
|
XRServer *xr_server = XRServer::get_singleton();
|
|
ERR_FAIL_NULL_V(xr_server, tracker);
|
|
OpenXRSpatialEntityExtension *se_extension = OpenXRSpatialEntityExtension::get_singleton();
|
|
ERR_FAIL_NULL_V(se_extension, tracker);
|
|
|
|
// TODO reverse apply world scale and reference frame to transform.
|
|
|
|
XrPosef pose = openxr_api->pose_from_transform(p_transform);
|
|
|
|
RID sc = p_spatial_context.is_valid() ? p_spatial_context : spatial_context;
|
|
ERR_FAIL_COND_V(sc.is_null(), tracker);
|
|
|
|
XrSpatialAnchorCreateInfoEXT create_info = {
|
|
XR_TYPE_SPATIAL_ANCHOR_CREATE_INFO_EXT, // type
|
|
nullptr, // next
|
|
openxr_api->get_play_space(), // baseSpace
|
|
openxr_api->get_predicted_display_time(), // time
|
|
pose // pose
|
|
};
|
|
XrSpatialEntityIdEXT entity_id;
|
|
XrSpatialEntityEXT entity;
|
|
XrResult result = xrCreateSpatialAnchorEXT(se_extension->get_spatial_context_handle(sc), &create_info, &entity_id, &entity);
|
|
if (XR_FAILED(result)) { // Did our xrCreateSpatialContextCompleteEXT call fail?
|
|
ERR_FAIL_V_MSG(tracker, "OpenXR: Failed to create anchor [" + openxr_api->get_error_string(result) + "]");
|
|
}
|
|
|
|
tracker.instantiate();
|
|
tracker->set_entity(se_extension->add_spatial_entity(sc, entity_id, entity));
|
|
tracker->set_tracker_desc("Anchor");
|
|
tracker->set_pose(SNAME("default"), p_transform, Vector3(), Vector3());
|
|
|
|
// Remember our tracker.
|
|
anchors[entity_id] = tracker;
|
|
xr_server->add_tracker(tracker);
|
|
|
|
return tracker;
|
|
}
|
|
|
|
void OpenXRSpatialAnchorCapability::remove_anchor(Ref<OpenXRAnchorTracker> p_anchor_tracker) {
|
|
OpenXRAPI *openxr_api = OpenXRAPI::get_singleton();
|
|
ERR_FAIL_NULL(openxr_api);
|
|
XRServer *xr_server = XRServer::get_singleton();
|
|
ERR_FAIL_NULL(xr_server);
|
|
OpenXRSpatialEntityExtension *se_extension = OpenXRSpatialEntityExtension::get_singleton();
|
|
ERR_FAIL_NULL(se_extension);
|
|
|
|
// We check for this here. We could do this asynchronous but the caller may than wrongly expect this method to be instant.
|
|
ERR_FAIL_COND_MSG(p_anchor_tracker->has_uuid(), "OpenXR: This anchor is persistent. It must first be made unpersistent.");
|
|
|
|
// Attempt to unregister it from our xr_server.
|
|
xr_server->remove_tracker(p_anchor_tracker);
|
|
|
|
// Get our entity.
|
|
RID entity = p_anchor_tracker->get_entity();
|
|
ERR_FAIL_COND(entity.is_null());
|
|
|
|
// Get our entity id.
|
|
XrSpatialEntityIdEXT entity_id = se_extension->get_spatial_entity_id(entity);
|
|
ERR_FAIL_COND(entity_id == XR_NULL_ENTITY);
|
|
|
|
// Remove it from our entity list.
|
|
if (anchors.has(entity_id)) {
|
|
anchors.erase(entity_id);
|
|
}
|
|
|
|
// Clear our entity, this will free it as well.
|
|
p_anchor_tracker->set_entity(RID());
|
|
|
|
// The anchor tracker will be cleaned up once its fully dereferenced.
|
|
}
|
|
|
|
Ref<OpenXRFutureResult> OpenXRSpatialAnchorCapability::persist_anchor(Ref<OpenXRAnchorTracker> p_anchor_tracker, RID p_persistence_context, const Callable &p_user_callback) {
|
|
ERR_FAIL_COND_V(!is_spatial_persistence_supported(), nullptr);
|
|
|
|
RID pc = p_persistence_context.is_valid() ? p_persistence_context : persistence_context;
|
|
ERR_FAIL_COND_V(pc.is_null(), nullptr);
|
|
ERR_FAIL_COND_V(p_anchor_tracker.is_null(), nullptr);
|
|
PersistenceContextData *persistence_context_data = persistence_context_owner.get_or_null(pc);
|
|
ERR_FAIL_NULL_V(persistence_context_data, nullptr);
|
|
const XrSpatialPersistenceContextEXT persistence_context_handle = persistence_context_data->persistence_context;
|
|
|
|
OpenXRAPI *openxr_api = OpenXRAPI::get_singleton();
|
|
ERR_FAIL_NULL_V(openxr_api, nullptr);
|
|
OpenXRFutureExtension *future_api = OpenXRFutureExtension::get_singleton();
|
|
ERR_FAIL_NULL_V(future_api, nullptr);
|
|
OpenXRSpatialEntityExtension *se_extension = OpenXRSpatialEntityExtension::get_singleton();
|
|
ERR_FAIL_NULL_V(se_extension, nullptr);
|
|
|
|
RID entity = p_anchor_tracker->get_entity();
|
|
ERR_FAIL_COND_V(entity.is_null(), nullptr);
|
|
|
|
XrSpatialEntityIdEXT entity_id = se_extension->get_spatial_entity_id(entity);
|
|
ERR_FAIL_COND_V(entity_id == XR_NULL_ENTITY, nullptr);
|
|
|
|
RID spatial_context_rid = se_extension->get_spatial_entity_context(entity);
|
|
const XrSpatialContextEXT spatial_context_handle = se_extension->get_spatial_context_handle(spatial_context_rid);
|
|
|
|
XrFutureEXT future = XR_NULL_HANDLE;
|
|
|
|
XrSpatialEntityPersistInfoEXT persist_info = {
|
|
XR_TYPE_SPATIAL_ENTITY_PERSIST_INFO_EXT, // type
|
|
nullptr, // next
|
|
spatial_context_handle, // spatialContext
|
|
entity_id // entityId
|
|
};
|
|
XrResult result = xrPersistSpatialEntityAsyncEXT(persistence_context_handle, &persist_info, &future);
|
|
if (XR_FAILED(result)) {
|
|
ERR_FAIL_V_MSG(nullptr, "OpenXR: Failed to start making anchor persistent [" + openxr_api->get_error_string(result) + "]");
|
|
}
|
|
|
|
// Create our future result.
|
|
Ref<OpenXRFutureResult> future_result = future_api->register_future(future, callable_mp(this, &OpenXRSpatialAnchorCapability::_on_made_anchor_persistent).bind(pc, p_anchor_tracker, p_user_callback));
|
|
|
|
return future_result;
|
|
}
|
|
|
|
void OpenXRSpatialAnchorCapability::_on_made_anchor_persistent(Ref<OpenXRFutureResult> p_future_result, RID p_persistence_context, Ref<OpenXRAnchorTracker> p_anchor_tracker, const Callable &p_user_callback) {
|
|
ERR_FAIL_COND(p_anchor_tracker.is_null());
|
|
OpenXRAPI *openxr_api = OpenXRAPI::get_singleton();
|
|
ERR_FAIL_NULL(openxr_api);
|
|
PersistenceContextData *persistence_context_data = persistence_context_owner.get_or_null(p_persistence_context);
|
|
ERR_FAIL_NULL(persistence_context_data);
|
|
|
|
XrFutureEXT future = p_future_result->get_future();
|
|
|
|
XrPersistSpatialEntityCompletionEXT completion = {
|
|
XR_TYPE_PERSIST_SPATIAL_ENTITY_COMPLETION_EXT, // type
|
|
nullptr, // next
|
|
XR_RESULT_MAX_ENUM, // futureResult
|
|
XR_SPATIAL_PERSISTENCE_CONTEXT_RESULT_MAX_ENUM_EXT, // persistResult
|
|
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } // persistUuid
|
|
};
|
|
XrResult result = xrPersistSpatialEntityCompleteEXT(persistence_context_data->persistence_context, future, &completion);
|
|
if (XR_FAILED(result)) { // Did our xrCreateSpatialContextCompleteEXT call fail?
|
|
p_future_result->set_result_value(false);
|
|
ERR_FAIL_MSG("OpenXR: Failed to complete anchor persistent future [" + openxr_api->get_error_string(result) + "]");
|
|
}
|
|
if (XR_FAILED(completion.futureResult)) { // Did our completion fail?
|
|
p_future_result->set_result_value(false);
|
|
ERR_FAIL_MSG("OpenXR: Failed to complete making anchor persistent [" + openxr_api->get_error_string(completion.futureResult) + "]");
|
|
}
|
|
if (completion.persistResult != XR_SPATIAL_PERSISTENCE_CONTEXT_RESULT_SUCCESS_EXT) { // Did our process fail?
|
|
p_future_result->set_result_value(false);
|
|
ERR_FAIL_MSG("OpenXR: Failed to make anchor persistent [" + get_spatial_persistence_context_result_name(completion.persistResult) + "]");
|
|
}
|
|
|
|
// Set our new UUID.
|
|
p_anchor_tracker->set_uuid(completion.persistUuid);
|
|
|
|
// Set true our result value on our future.
|
|
p_future_result->set_result_value(true);
|
|
|
|
// Do our callback.
|
|
p_user_callback.call(p_anchor_tracker);
|
|
}
|
|
|
|
Ref<OpenXRFutureResult> OpenXRSpatialAnchorCapability::unpersist_anchor(Ref<OpenXRAnchorTracker> p_anchor_tracker, RID p_persistence_context, const Callable &p_user_callback) {
|
|
ERR_FAIL_COND_V(!is_spatial_persistence_supported(), nullptr);
|
|
|
|
RID pc = p_persistence_context.is_valid() ? p_persistence_context : persistence_context;
|
|
ERR_FAIL_COND_V(pc.is_null(), nullptr);
|
|
ERR_FAIL_COND_V(p_anchor_tracker.is_null(), nullptr);
|
|
PersistenceContextData *persistence_context_data = persistence_context_owner.get_or_null(pc);
|
|
ERR_FAIL_NULL_V(persistence_context_data, nullptr);
|
|
|
|
OpenXRAPI *openxr_api = OpenXRAPI::get_singleton();
|
|
ERR_FAIL_NULL_V(openxr_api, nullptr);
|
|
OpenXRFutureExtension *future_api = OpenXRFutureExtension::get_singleton();
|
|
ERR_FAIL_NULL_V(future_api, nullptr);
|
|
OpenXRSpatialEntityExtension *se_extension = OpenXRSpatialEntityExtension::get_singleton();
|
|
ERR_FAIL_NULL_V(se_extension, nullptr);
|
|
|
|
XrFutureEXT future;
|
|
|
|
XrSpatialEntityUnpersistInfoEXT unpersist_info = {
|
|
XR_TYPE_SPATIAL_ENTITY_UNPERSIST_INFO_EXT, // type
|
|
nullptr, // next
|
|
p_anchor_tracker->get_uuid() // persistUuid
|
|
};
|
|
XrResult result = xrUnpersistSpatialEntityAsyncEXT(persistence_context_data->persistence_context, &unpersist_info, &future);
|
|
if (XR_FAILED(result)) {
|
|
ERR_FAIL_V_MSG(nullptr, "OpenXR: Failed to make anchor unpersistent [" + openxr_api->get_error_string(result) + "]");
|
|
}
|
|
|
|
// Create our future result.
|
|
Ref<OpenXRFutureResult> future_result = future_api->register_future(future, callable_mp(this, &OpenXRSpatialAnchorCapability::_on_made_anchor_unpersistent).bind(pc, p_anchor_tracker, p_user_callback));
|
|
|
|
return future_result;
|
|
}
|
|
|
|
void OpenXRSpatialAnchorCapability::_on_made_anchor_unpersistent(Ref<OpenXRFutureResult> p_future_result, RID p_persistence_context, Ref<OpenXRAnchorTracker> p_anchor_tracker, const Callable &p_user_callback) {
|
|
ERR_FAIL_COND(p_anchor_tracker.is_null());
|
|
OpenXRAPI *openxr_api = OpenXRAPI::get_singleton();
|
|
ERR_FAIL_NULL(openxr_api);
|
|
PersistenceContextData *persistence_context_data = persistence_context_owner.get_or_null(p_persistence_context);
|
|
ERR_FAIL_NULL(persistence_context_data);
|
|
|
|
XrFutureEXT future = p_future_result->get_future();
|
|
|
|
XrUnpersistSpatialEntityCompletionEXT completion = {
|
|
XR_TYPE_UNPERSIST_SPATIAL_ENTITY_COMPLETION_EXT, // type
|
|
nullptr, // next
|
|
XR_RESULT_MAX_ENUM, // futureResult
|
|
XR_SPATIAL_PERSISTENCE_CONTEXT_RESULT_MAX_ENUM_EXT, // unpersistResult
|
|
};
|
|
XrResult result = xrUnpersistSpatialEntityCompleteEXT(persistence_context_data->persistence_context, future, &completion);
|
|
if (XR_FAILED(result)) { // Did our xrCreateSpatialContextCompleteEXT call fail?
|
|
p_future_result->set_result_value(false);
|
|
ERR_FAIL_MSG("OpenXR: Failed to complete anchor unpersistent future [" + openxr_api->get_error_string(result) + "]");
|
|
}
|
|
if (XR_FAILED(completion.futureResult)) { // Did our completion fail?
|
|
p_future_result->set_result_value(false);
|
|
ERR_FAIL_MSG("OpenXR: Failed to complete making anchor unpersistent [" + openxr_api->get_error_string(completion.futureResult) + "]");
|
|
}
|
|
if (completion.unpersistResult != XR_SPATIAL_PERSISTENCE_CONTEXT_RESULT_SUCCESS_EXT) { // Did our process fail?
|
|
p_future_result->set_result_value(false);
|
|
ERR_FAIL_MSG("OpenXR: Failed to make anchor unpersistent [" + get_spatial_persistence_context_result_name(completion.unpersistResult) + "]");
|
|
}
|
|
|
|
// Unset our UUID.
|
|
XrUuid empty_uid = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
|
p_anchor_tracker->set_uuid(empty_uid);
|
|
|
|
// Set true our result value on our future.
|
|
p_future_result->set_result_value(true);
|
|
|
|
// Do our callback.
|
|
p_user_callback.call(p_anchor_tracker);
|
|
}
|
|
|
|
String OpenXRSpatialAnchorCapability::get_spatial_persistence_scope_name(XrSpatialPersistenceScopeEXT p_scope){
|
|
XR_ENUM_SWITCH(XrSpatialPersistenceScopeEXT, p_scope)
|
|
}
|
|
|
|
String OpenXRSpatialAnchorCapability::get_spatial_persistence_context_result_name(XrSpatialPersistenceContextResultEXT p_result) {
|
|
XR_ENUM_SWITCH(XrSpatialPersistenceContextResultEXT, p_result)
|
|
}
|