From d88a214912c90f0054fb29e8fb0243c3f36ffba6 Mon Sep 17 00:00:00 2001 From: David Snopek Date: Tue, 12 Mar 2024 14:40:49 -0500 Subject: [PATCH] Add support for developer-created spatial anchors via XR_FB_spatial_entity --- CHANGES.md | 1 + .../openxr_fb_spatial_anchor_manager.cpp | 505 ++++++++++++++++++ .../cpp/classes/openxr_fb_spatial_entity.cpp | 136 ++++- ...xr_fb_spatial_entity_extension_wrapper.cpp | 51 ++ ...atial_entity_storage_extension_wrapper.cpp | 20 +- .../openxr_fb_spatial_anchor_manager.h | 135 +++++ .../classes/openxr_fb_spatial_entity.h | 17 +- ...enxr_fb_spatial_entity_extension_wrapper.h | 21 + ...spatial_entity_storage_extension_wrapper.h | 6 +- common/src/main/cpp/register_types.cpp | 2 + demo/main.gd | 78 +++ demo/main.tscn | 32 +- demo/project.godot | 7 + demo/scene_anchor.tscn | 1 + demo/spatial_anchor.gd | 25 + demo/spatial_anchor.tscn | 30 ++ 16 files changed, 1050 insertions(+), 17 deletions(-) create mode 100644 common/src/main/cpp/classes/openxr_fb_spatial_anchor_manager.cpp create mode 100644 common/src/main/cpp/include/classes/openxr_fb_spatial_anchor_manager.h create mode 100644 demo/spatial_anchor.gd create mode 100644 demo/spatial_anchor.tscn diff --git a/CHANGES.md b/CHANGES.md index 3fead0b7..072b1a79 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,7 @@ - Update Meta OpenXR mobile SDK to version 62 - Add a developer-facing API for interacting with scene anchors - Add XR_FB_hand_tracking_capsules extension wrapper +- Add support for developer-created spatial anchors via XR_FB_spatial_entity ## 2.0.3 - Migrate the export scripts from gdscript to C++ via gdextension diff --git a/common/src/main/cpp/classes/openxr_fb_spatial_anchor_manager.cpp b/common/src/main/cpp/classes/openxr_fb_spatial_anchor_manager.cpp new file mode 100644 index 00000000..b76463e1 --- /dev/null +++ b/common/src/main/cpp/classes/openxr_fb_spatial_anchor_manager.cpp @@ -0,0 +1,505 @@ +/**************************************************************************/ +/* openxr_fb_spatial_anchor_manager.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT XR */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2022-present Godot XR contributors (see CONTRIBUTORS.md) */ +/* */ +/* 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 "classes/openxr_fb_spatial_anchor_manager.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "classes/openxr_fb_spatial_entity_query.h" +#include "extensions/openxr_fb_spatial_entity_extension_wrapper.h" + +using namespace godot; + +void OpenXRFbSpatialAnchorManager::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_scene", "scene"), &OpenXRFbSpatialAnchorManager::set_scene); + ClassDB::bind_method(D_METHOD("get_scene"), &OpenXRFbSpatialAnchorManager::get_scene); + + ClassDB::bind_method(D_METHOD("set_scene_setup_method", "method_name"), &OpenXRFbSpatialAnchorManager::set_scene_setup_method); + ClassDB::bind_method(D_METHOD("get_scene_setup_method"), &OpenXRFbSpatialAnchorManager::get_scene_setup_method); + + ClassDB::bind_method(D_METHOD("set_persist_in_local_file", "enable"), &OpenXRFbSpatialAnchorManager::set_persist_in_local_file); + ClassDB::bind_method(D_METHOD("get_persist_in_local_file"), &OpenXRFbSpatialAnchorManager::get_persist_in_local_file); + + ClassDB::bind_method(D_METHOD("set_local_file_path", "path"), &OpenXRFbSpatialAnchorManager::set_local_file_path); + ClassDB::bind_method(D_METHOD("get_local_file_path"), &OpenXRFbSpatialAnchorManager::get_local_file_path); + + ClassDB::bind_method(D_METHOD("set_auto_load", "enable"), &OpenXRFbSpatialAnchorManager::set_auto_load); + ClassDB::bind_method(D_METHOD("get_auto_load"), &OpenXRFbSpatialAnchorManager::get_auto_load); + + ClassDB::bind_method(D_METHOD("set_erase_unknown_anchors_on_load", "enable"), &OpenXRFbSpatialAnchorManager::set_erase_unknown_anchors_on_load); + ClassDB::bind_method(D_METHOD("get_erase_unknown_anchors_on_load"), &OpenXRFbSpatialAnchorManager::get_erase_unknown_anchors_on_load); + + ClassDB::bind_method(D_METHOD("set_visible", "visible"), &OpenXRFbSpatialAnchorManager::set_visible); + ClassDB::bind_method(D_METHOD("get_visible"), &OpenXRFbSpatialAnchorManager::get_visible); + ClassDB::bind_method(D_METHOD("show"), &OpenXRFbSpatialAnchorManager::show); + ClassDB::bind_method(D_METHOD("hide"), &OpenXRFbSpatialAnchorManager::hide); + + ClassDB::bind_method(D_METHOD("create_anchor", "transform", "custom_data"), &OpenXRFbSpatialAnchorManager::create_anchor, DEFVAL(Dictionary())); + ClassDB::bind_method(D_METHOD("load_anchor", "uuid", "custom_data", "location"), &OpenXRFbSpatialAnchorManager::load_anchor, DEFVAL(Dictionary()), DEFVAL(OpenXRFbSpatialEntity::STORAGE_LOCAL)); + ClassDB::bind_method(D_METHOD("track_anchor", "spatial_entity"), &OpenXRFbSpatialAnchorManager::track_anchor); + ClassDB::bind_method(D_METHOD("untrack_anchor", "spatial_entity_or_uuid"), &OpenXRFbSpatialAnchorManager::untrack_anchor); + + ClassDB::bind_method(D_METHOD("get_anchor_uuids"), &OpenXRFbSpatialAnchorManager::get_anchor_uuids); + ClassDB::bind_method(D_METHOD("get_anchor_node", "uuid"), &OpenXRFbSpatialAnchorManager::get_anchor_node); + ClassDB::bind_method(D_METHOD("get_spatial_entity", "uuid"), &OpenXRFbSpatialAnchorManager::get_spatial_entity); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "scene", PROPERTY_HINT_RESOURCE_TYPE, "PackedScene"), "set_scene", "get_scene"); + ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "scene_setup_method", PROPERTY_HINT_NONE, ""), "set_scene_setup_method", "get_scene_setup_method"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "persist_in_local_file", PROPERTY_HINT_NONE, ""), "set_persist_in_local_file", "get_persist_in_local_file"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "local_file_path", PROPERTY_HINT_NONE, ""), "set_local_file_path", "get_local_file_path"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_load", PROPERTY_HINT_NONE, ""), "set_auto_load", "get_auto_load"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "erase_unknown_anchors_on_load", PROPERTY_HINT_NONE, ""), "set_erase_unknown_anchors_on_load", "get_erase_unknown_anchors_on_load"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible", PROPERTY_HINT_NONE, ""), "set_visible", "get_visible"); + + ADD_SIGNAL(MethodInfo("openxr_fb_spatial_anchor_tracked", PropertyInfo(Variant::Type::OBJECT, "anchor_node"), PropertyInfo(Variant::Type::OBJECT, "spatial_entity"))); + ADD_SIGNAL(MethodInfo("openxr_fb_spatial_anchor_untracked", PropertyInfo(Variant::Type::OBJECT, "anchor_node"), PropertyInfo(Variant::Type::OBJECT, "spatial_entity"))); + ADD_SIGNAL(MethodInfo("openxr_fb_spatial_anchor_create_failed", PropertyInfo(Variant::Type::TRANSFORM3D, "transform"), PropertyInfo(Variant::Type::DICTIONARY, "custom_data"))); + ADD_SIGNAL(MethodInfo("openxr_fb_spatial_anchor_load_failed", PropertyInfo(Variant::Type::STRING_NAME, "uuid"), PropertyInfo(Variant::Type::DICTIONARY, "custom_data"), PropertyInfo(Variant::Type::INT, "location"))); + ADD_SIGNAL(MethodInfo("openxr_fb_spatial_anchor_track_failed", PropertyInfo(Variant::Type::OBJECT, "spatial_entity"))); +} + +void OpenXRFbSpatialAnchorManager::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + Ref openxr_interface = XRServer::get_singleton()->find_interface("OpenXR"); + if (openxr_interface.is_valid()) { + openxr_interface->connect("session_begun", callable_mp(this, &OpenXRFbSpatialAnchorManager::_on_openxr_session_begun)); + openxr_interface->connect("session_stopping", callable_mp(this, &OpenXRFbSpatialAnchorManager::_on_openxr_session_stopping)); + } + + + xr_origin = Object::cast_to(get_parent()); + if (xr_origin && auto_load && persist_in_local_file && anchors.size() == 0) { + // We need to make sure the session is actually running. Just checking if the interface is + // initialized isn't enough, and we don't want to accidentally load from the file twice. + OpenXRFbSpatialEntityExtensionWrapper *ext = OpenXRFbSpatialEntityExtensionWrapper::get_singleton(); + if (ext) { + Ref openxr_api = ext->get_openxr_api(); + if (openxr_api.is_valid() && openxr_api->is_running()) { + load_anchors_from_local_file(); + } + } + } + + } break; + case NOTIFICATION_EXIT_TREE: { + Ref openxr_interface = XRServer::get_singleton()->find_interface("OpenXR"); + if (openxr_interface.is_valid()) { + openxr_interface->disconnect("session_begun", callable_mp(this, &OpenXRFbSpatialAnchorManager::_on_openxr_session_begun)); + openxr_interface->disconnect("session_stopping", callable_mp(this, &OpenXRFbSpatialAnchorManager::_on_openxr_session_stopping)); + } + + xr_origin = nullptr; + _cleanup_anchors(); + } break; + } +} + +void OpenXRFbSpatialAnchorManager::_on_openxr_session_begun() { + if (xr_origin && auto_load && persist_in_local_file && anchors.size() == 0) { + load_anchors_from_local_file(); + } +} + +void OpenXRFbSpatialAnchorManager::_on_openxr_session_stopping() { + _cleanup_anchors(); +} + +// Removes anchor nodes and clears the anchor list - but doesn't change the local file. +void OpenXRFbSpatialAnchorManager::_cleanup_anchors() { + for (KeyValue &E : anchors) { + Node3D *node = Object::cast_to(ObjectDB::get_instance(E.value.node)); + if (node) { + Node *parent = node->get_parent(); + if (parent) { + parent->remove_child(node); + } + node->queue_free(); + } + + E.value.entity->untrack(); + } + anchors.clear(); +} + +PackedStringArray OpenXRFbSpatialAnchorManager::_get_configuration_warnings() const { + PackedStringArray warnings = Node::_get_configuration_warnings(); + + if (is_inside_tree()) { + XROrigin3D *origin = Object::cast_to(get_parent()); + if (origin == nullptr) { + warnings.push_back("Must be a child of XROrigin3D"); + } + } + + return warnings; +} + +void OpenXRFbSpatialAnchorManager::set_scene(const Ref &p_scene) { + scene = p_scene; +} + +Ref OpenXRFbSpatialAnchorManager::get_scene() const { + return scene; +} + +void OpenXRFbSpatialAnchorManager::set_scene_setup_method(const StringName &p_method) { + scene_setup_method = p_method; +} + +StringName OpenXRFbSpatialAnchorManager::get_scene_setup_method() const { + return scene_setup_method; +} + +void OpenXRFbSpatialAnchorManager::set_persist_in_local_file(bool p_enable) { + persist_in_local_file = p_enable; +} + +bool OpenXRFbSpatialAnchorManager::get_persist_in_local_file() const { + return persist_in_local_file; +} + +void OpenXRFbSpatialAnchorManager::set_local_file_path(const String &p_local_file) { + local_file_path = p_local_file; +} + +String OpenXRFbSpatialAnchorManager::get_local_file_path() const { + return local_file_path; +} + +void OpenXRFbSpatialAnchorManager::set_auto_load(bool p_enable) { + auto_load = p_enable; +} + +bool OpenXRFbSpatialAnchorManager::get_auto_load() const { + return auto_load; +} + +void OpenXRFbSpatialAnchorManager::set_erase_unknown_anchors_on_load(bool p_enable) { + erase_unknown_anchors_on_load = p_enable; +} + +bool OpenXRFbSpatialAnchorManager::get_erase_unknown_anchors_on_load() const { + return erase_unknown_anchors_on_load; +} + +void OpenXRFbSpatialAnchorManager::set_visible(bool p_visible) { + visible = p_visible; + + for (KeyValue &E : anchors) { + Node3D *node = Object::cast_to(ObjectDB::get_instance(E.value.node)); + ERR_CONTINUE_MSG(!node, vformat("Cannot find node for anchor %s.", E.key)); + if (node) { + node->set_visible(p_visible); + } + } +} + +bool OpenXRFbSpatialAnchorManager::get_visible() const { + return visible; +} + +void OpenXRFbSpatialAnchorManager::show() { + set_visible(true); +} + +void OpenXRFbSpatialAnchorManager::hide() { + set_visible(false); +} + +void OpenXRFbSpatialAnchorManager::create_anchor(const Transform3D &p_transform, const Dictionary &p_custom_data) { + ERR_FAIL_COND(!xr_origin); + + Ref spatial_entity = OpenXRFbSpatialEntity::create_spatial_anchor(p_transform); + spatial_entity->set_custom_data(p_custom_data); + spatial_entity->connect("openxr_fb_spatial_entity_created", callable_mp(this, &OpenXRFbSpatialAnchorManager::_on_anchor_created).bind(p_transform, spatial_entity)); +} + +void OpenXRFbSpatialAnchorManager::_on_anchor_created(bool p_success, const Transform3D &p_transform, const Ref &p_spatial_entity) { + if (p_success) { + _track_anchor(p_spatial_entity, true); + } else { + emit_signal("openxr_fb_spatial_anchor_create_failed", p_transform, p_spatial_entity->get_custom_data()); + } +} + +void OpenXRFbSpatialAnchorManager::load_anchor(const StringName &p_uuid, const Dictionary &p_custom_data, OpenXRFbSpatialEntity::StorageLocation p_location) { + ERR_FAIL_COND(!xr_origin); + + Array uuids; + uuids.push_back(p_uuid); + + Dictionary data; + data[p_uuid] = p_custom_data; + + Ref query; + query.instantiate(); + query->query_by_uuid(uuids, p_location); + query->connect("openxr_fb_spatial_entity_query_completed", callable_mp(this, &OpenXRFbSpatialAnchorManager::_on_anchor_load_query_completed).bind(data, p_location, true)); + query->execute(); +} + +void OpenXRFbSpatialAnchorManager::_on_anchor_load_query_completed(const Array &p_results, const Dictionary &p_anchors_custom_data, OpenXRFbSpatialEntity::StorageLocation p_location, bool p_save_file) { + Dictionary anchors_custom_data = p_anchors_custom_data.duplicate(); + for (int i = 0; i < p_results.size(); i++) { + Ref spatial_entity = p_results[i]; + if (spatial_entity.is_valid()) { + StringName uuid = spatial_entity->get_uuid(); + if (anchors_custom_data.has(uuid)) { + spatial_entity->set_custom_data(anchors_custom_data[uuid]); + anchors_custom_data.erase(uuid); + + _track_anchor(spatial_entity, p_save_file); + } else if (erase_unknown_anchors_on_load && spatial_entity->is_component_supported(OpenXRFbSpatialEntity::COMPONENT_TYPE_STORABLE)) { + _untrack_anchor(spatial_entity); + } + } + } + + Array failed_uuids = anchors_custom_data.keys(); + for (int i = 0; i < failed_uuids.size(); i++) { + StringName uuid = failed_uuids[i]; + emit_signal("openxr_fb_spatial_anchor_load_failed", uuid, anchors_custom_data[uuid], p_location); + } +} + +void OpenXRFbSpatialAnchorManager::track_anchor(const Ref &p_spatial_entity) { + ERR_FAIL_COND(!xr_origin); + + _track_anchor(p_spatial_entity, true); +} + +void OpenXRFbSpatialAnchorManager::_track_anchor(const Ref &p_spatial_entity, bool p_save_file) { + if (p_spatial_entity->is_component_enabled(OpenXRFbSpatialEntity::COMPONENT_TYPE_LOCATABLE)) { + _on_anchor_track_enable_locatable_completed(true, OpenXRFbSpatialEntity::COMPONENT_TYPE_LOCATABLE, true, p_spatial_entity, p_save_file); + } else { + p_spatial_entity->connect("openxr_fb_spatial_entity_set_component_enabled_completed", callable_mp(this, &OpenXRFbSpatialAnchorManager::_on_anchor_track_enable_locatable_completed).bind(p_spatial_entity, p_save_file), CONNECT_ONE_SHOT); + p_spatial_entity->set_component_enabled(OpenXRFbSpatialEntity::COMPONENT_TYPE_LOCATABLE, true); + } +} + +void OpenXRFbSpatialAnchorManager::_on_anchor_track_enable_locatable_completed(bool p_succeeded, OpenXRFbSpatialEntity::ComponentType p_component, bool p_enabled, const Ref &p_spatial_entity, bool p_save_file) { + ERR_FAIL_COND_MSG(!p_succeeded, vformat("Unable to make spatial anchor %s locatable.", p_spatial_entity->get_uuid())); + + if (p_spatial_entity->is_component_enabled(OpenXRFbSpatialEntity::COMPONENT_TYPE_STORABLE)) { + _on_anchor_track_enable_storable_completed(true, OpenXRFbSpatialEntity::COMPONENT_TYPE_STORABLE, true, p_spatial_entity, p_save_file); + } else { + p_spatial_entity->connect("openxr_fb_spatial_entity_set_component_enabled_completed", callable_mp(this, &OpenXRFbSpatialAnchorManager::_on_anchor_track_enable_storable_completed).bind(p_spatial_entity, p_save_file), CONNECT_ONE_SHOT); + p_spatial_entity->set_component_enabled(OpenXRFbSpatialEntity::COMPONENT_TYPE_STORABLE, true); + } +} + +void OpenXRFbSpatialAnchorManager::_on_anchor_track_enable_storable_completed(bool p_succeeded, OpenXRFbSpatialEntity::ComponentType p_component, bool p_enabled, const Ref &p_spatial_entity, bool p_save_file) { + ERR_FAIL_COND_MSG(!p_succeeded, vformat("Unable to make spatial anchor %s storable.", p_spatial_entity->get_uuid())); + + p_spatial_entity->connect("openxr_fb_spatial_entity_saved", callable_mp(this, &OpenXRFbSpatialAnchorManager::_on_anchor_saved).bind(p_spatial_entity, p_save_file), CONNECT_ONE_SHOT); + p_spatial_entity->save_to_storage(OpenXRFbSpatialEntity::STORAGE_LOCAL); +} + +void OpenXRFbSpatialAnchorManager::_on_anchor_saved(bool p_succeeded, OpenXRFbSpatialEntity::StorageLocation p_location, const Ref &p_spatial_entity, bool p_save_file) { + ERR_FAIL_COND_MSG(!p_succeeded, vformat("Unable to save spatial anchor %s to local storage.", p_spatial_entity->get_uuid())); + _complete_anchor_setup(p_spatial_entity, p_save_file); +} + +void OpenXRFbSpatialAnchorManager::_complete_anchor_setup(const Ref &p_entity, bool p_save_file) { + ERR_FAIL_COND(!xr_origin); + ERR_FAIL_COND(anchors.has(p_entity->get_uuid())); + + p_entity->track(); + + XRAnchor3D *node = memnew(XRAnchor3D); + node->set_name(p_entity->get_uuid()); + node->set_tracker(p_entity->get_uuid()); + node->set_visible(visible); + xr_origin->add_child(node); + + anchors[p_entity->get_uuid()] = Anchor(node, p_entity); + + if (persist_in_local_file && p_save_file) { + save_anchors_to_local_file(); + } + + if (scene.is_valid()) { + Node *scene_node = scene->instantiate(); + node->add_child(scene_node); + scene_node->call(scene_setup_method, p_entity); + } + + emit_signal("openxr_fb_spatial_anchor_tracked", node, p_entity); +} + +void OpenXRFbSpatialAnchorManager::untrack_anchor(const Variant &p_spatial_entity_or_uuid) { + StringName uuid; + + if (p_spatial_entity_or_uuid.get_type() == Variant::OBJECT) { + Ref spatial_entity = p_spatial_entity_or_uuid; + ERR_FAIL_COND(spatial_entity.is_null()); + uuid = spatial_entity->get_uuid(); + } else if (p_spatial_entity_or_uuid.get_type() == Variant::STRING || p_spatial_entity_or_uuid.get_type() == Variant::STRING_NAME) { + uuid = p_spatial_entity_or_uuid; + } else { + ERR_FAIL_MSG("Invalid argument passed to OpenXRFbSpatialAnchorManager::untrack_anchor()."); + } + + Anchor *anchor = anchors.getptr(uuid); + ERR_FAIL_COND(!anchor); + + Node3D *node = Object::cast_to(ObjectDB::get_instance(anchor->node)); + if (node) { + Node *parent = node->get_parent(); + if (parent) { + parent->remove_child(node); + } + node->queue_free(); + } + + Ref spatial_entity = anchor->entity; + spatial_entity->untrack(); + + anchors.erase(uuid); + + if (persist_in_local_file) { + save_anchors_to_local_file(); + } + + _untrack_anchor(spatial_entity); + + emit_signal("openxr_fb_spatial_anchor_untracked", node, spatial_entity); +} + +void OpenXRFbSpatialAnchorManager::_untrack_anchor(const Ref &p_spatial_entity) { + if (p_spatial_entity->is_component_enabled(OpenXRFbSpatialEntity::COMPONENT_TYPE_STORABLE)) { + _on_anchor_untrack_enable_storable_completed(true, OpenXRFbSpatialEntity::COMPONENT_TYPE_STORABLE, true, p_spatial_entity); + } else { + p_spatial_entity->connect("openxr_fb_spatial_entity_set_component_enabled_completed", callable_mp(this, &OpenXRFbSpatialAnchorManager::_on_anchor_untrack_enable_storable_completed).bind(p_spatial_entity), CONNECT_ONE_SHOT); + p_spatial_entity->set_component_enabled(OpenXRFbSpatialEntity::COMPONENT_TYPE_STORABLE, true); + } +} + +void OpenXRFbSpatialAnchorManager::_on_anchor_untrack_enable_storable_completed(bool p_succeeded, OpenXRFbSpatialEntity::ComponentType p_component, bool p_enabled, const Ref &p_spatial_entity) { + if (!p_succeeded) { + // If we couldn't make it storable, just exit silently since we were trying to remove it anyway. + return; + } + + p_spatial_entity->connect("openxr_fb_spatial_entity_erased", callable_mp(this, &OpenXRFbSpatialAnchorManager::_on_anchor_erase_completed).bind(p_spatial_entity), CONNECT_ONE_SHOT); + p_spatial_entity->erase_from_storage(OpenXRFbSpatialEntity::STORAGE_LOCAL); +} + +void OpenXRFbSpatialAnchorManager::_on_anchor_erase_completed(bool p_succeeded, OpenXRFbSpatialEntity::StorageLocation p_location, const Ref &p_spatial_entity) { + ERR_FAIL_COND_MSG(!p_succeeded, vformat("Unable to erase spatial anchor %s.", p_spatial_entity->get_uuid())); +} + +Error OpenXRFbSpatialAnchorManager::save_anchors_to_local_file() { + Ref file = FileAccess::open(local_file_path, FileAccess::WRITE); + if (file.is_null()) { + return FileAccess::get_open_error(); + } + + Dictionary anchor_data; + for (const KeyValue &E : anchors) { + anchor_data[E.key] = E.value.entity->get_custom_data(); + } + + file->store_string(JSON::stringify(anchor_data)); + + return OK; +} + +Error OpenXRFbSpatialAnchorManager::load_anchors_from_local_file() { + ERR_FAIL_COND_V(!xr_origin, FAILED); + ERR_FAIL_COND_V(anchors.size() > 0, FAILED); + + Ref file = FileAccess::open(local_file_path, FileAccess::READ); + if (file.is_null()) { + return FileAccess::get_open_error(); + } + + Ref json; + json.instantiate(); + Error parse_error = json->parse(file->get_as_text()); + if (parse_error != OK) { + return parse_error; + } + + Dictionary anchor_data = json->get_data(); + + Ref query; + query.instantiate(); + if (erase_unknown_anchors_on_load) { + // If we want to clear out unknown anchors, then we need to query everything. + query->query_all(); + query->set_max_results(1000); + } else { + // Otherwise, we just query the specific anchors we know about. + query->query_by_uuid(anchor_data.keys(), OpenXRFbSpatialEntity::STORAGE_LOCAL); + query->set_max_results(anchor_data.size()); + } + query->connect("openxr_fb_spatial_entity_query_completed", callable_mp(this, &OpenXRFbSpatialAnchorManager::_on_anchor_load_query_completed).bind(anchor_data, OpenXRFbSpatialEntity::STORAGE_LOCAL, false)); + query->execute(); + + return OK; +} + +Array OpenXRFbSpatialAnchorManager::get_anchor_uuids() const { + Array ret; + ret.resize(anchors.size()); + int i = 0; + for (const KeyValue &E : anchors) { + ret[i++] = E.key; + } + return ret; +} + +XRAnchor3D *OpenXRFbSpatialAnchorManager::get_anchor_node(const StringName &p_uuid) const { + const Anchor *anchor = anchors.getptr(p_uuid); + if (anchor) { + return Object::cast_to(ObjectDB::get_instance(anchor->node)); + } + + return nullptr; +} + +Ref OpenXRFbSpatialAnchorManager::get_spatial_entity(const StringName &p_uuid) const { + const Anchor *anchor = anchors.getptr(p_uuid); + if (anchor) { + return anchor->entity; + } + + return Ref(); +} diff --git a/common/src/main/cpp/classes/openxr_fb_spatial_entity.cpp b/common/src/main/cpp/classes/openxr_fb_spatial_entity.cpp index 4e717c19..339453e8 100644 --- a/common/src/main/cpp/classes/openxr_fb_spatial_entity.cpp +++ b/common/src/main/cpp/classes/openxr_fb_spatial_entity.cpp @@ -37,12 +37,15 @@ #include "extensions/openxr_fb_spatial_entity_extension_wrapper.h" #include "extensions/openxr_fb_spatial_entity_container_extension_wrapper.h" +#include "extensions/openxr_fb_spatial_entity_storage_extension_wrapper.h" #include "extensions/openxr_fb_scene_extension_wrapper.h" using namespace godot; void OpenXRFbSpatialEntity::_bind_methods() { ClassDB::bind_method(D_METHOD("get_uuid"), &OpenXRFbSpatialEntity::get_uuid); + ClassDB::bind_method(D_METHOD("set_custom_data"), &OpenXRFbSpatialEntity::set_custom_data); + ClassDB::bind_method(D_METHOD("get_custom_data"), &OpenXRFbSpatialEntity::get_custom_data); ClassDB::bind_method(D_METHOD("get_supported_components"), &OpenXRFbSpatialEntity::get_supported_components); ClassDB::bind_method(D_METHOD("is_component_supported", "component"), &OpenXRFbSpatialEntity::is_component_supported); @@ -63,7 +66,14 @@ void OpenXRFbSpatialEntity::_bind_methods() { ClassDB::bind_method(D_METHOD("create_mesh_instance"), &OpenXRFbSpatialEntity::create_mesh_instance); ClassDB::bind_method(D_METHOD("create_collision_shape"), &OpenXRFbSpatialEntity::create_collision_shape); + ClassDB::bind_static_method("OpenXRFbSpatialEntity", D_METHOD("create_spatial_anchor", "transform"), &OpenXRFbSpatialEntity::create_spatial_anchor); + + ClassDB::bind_method(D_METHOD("save_to_storage", "location"), &OpenXRFbSpatialEntity::save_to_storage, DEFVAL(STORAGE_LOCAL)); + ClassDB::bind_method(D_METHOD("erase_from_storage", "location"), &OpenXRFbSpatialEntity::erase_from_storage, DEFVAL(STORAGE_LOCAL)); + ClassDB::bind_method(D_METHOD("destroy"), &OpenXRFbSpatialEntity::destroy); + ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "uuid", PROPERTY_HINT_NONE, ""), "", "get_uuid"); + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "custom_data", PROPERTY_HINT_NONE, ""), "set_custom_data", "get_custom_data"); BIND_ENUM_CONSTANT(STORAGE_LOCAL); BIND_ENUM_CONSTANT(STORAGE_CLOUD); @@ -79,6 +89,9 @@ void OpenXRFbSpatialEntity::_bind_methods() { BIND_ENUM_CONSTANT(COMPONENT_TYPE_TRIANGLE_MESH); ADD_SIGNAL(MethodInfo("openxr_fb_spatial_entity_set_component_enabled_completed", PropertyInfo(Variant::Type::BOOL, "succeeded"), PropertyInfo(Variant::Type::INT, "component"), PropertyInfo(Variant::Type::BOOL, "enabled"))); + ADD_SIGNAL(MethodInfo("openxr_fb_spatial_entity_created", PropertyInfo(Variant::Type::BOOL, "succeeded"))); + ADD_SIGNAL(MethodInfo("openxr_fb_spatial_entity_saved", PropertyInfo(Variant::Type::BOOL, "succeeded"), PropertyInfo(Variant::Type::INT, "location"))); + ADD_SIGNAL(MethodInfo("openxr_fb_spatial_entity_erased", PropertyInfo(Variant::Type::BOOL, "succeeded"), PropertyInfo(Variant::Type::INT, "location"))); } String OpenXRFbSpatialEntity::_to_string() const { @@ -86,12 +99,23 @@ String OpenXRFbSpatialEntity::_to_string() const { } StringName OpenXRFbSpatialEntity::get_uuid() const { + ERR_FAIL_COND_V_MSG(space == XR_NULL_HANDLE, StringName(), "Underlying spatial entity doesn't exist (yet) or has been destroyed."); return uuid; } +void OpenXRFbSpatialEntity::set_custom_data(const Dictionary &p_custom_data) { + custom_data = p_custom_data; +} + +Dictionary OpenXRFbSpatialEntity::get_custom_data() const { + return custom_data; +} + Array OpenXRFbSpatialEntity::get_supported_components() const { Array ret; + ERR_FAIL_COND_V_MSG(space == XR_NULL_HANDLE, ret, "Underlying spatial entity doesn't exist (yet) or has been destroyed."); + Vector components = OpenXRFbSpatialEntityExtensionWrapper::get_singleton()->get_support_components(space); ret.resize(components.size()); for (int i = 0; i < components.size(); i++) { @@ -102,16 +126,22 @@ Array OpenXRFbSpatialEntity::get_supported_components() const { } bool OpenXRFbSpatialEntity::is_component_supported(ComponentType p_component) const { + ERR_FAIL_COND_V_MSG(space == XR_NULL_HANDLE, false, "Underlying spatial entity doesn't exist (yet) or has been destroyed."); + ERR_FAIL_COND_V(p_component == COMPONENT_TYPE_UNKNOWN, false); return get_supported_components().has(p_component); } bool OpenXRFbSpatialEntity::is_component_enabled(ComponentType p_component) const { + ERR_FAIL_COND_V_MSG(space == XR_NULL_HANDLE, false, "Underlying spatial entity doesn't exist (yet) or has been destroyed."); + ERR_FAIL_COND_V(p_component == COMPONENT_TYPE_UNKNOWN, false); return OpenXRFbSpatialEntityExtensionWrapper::get_singleton()->is_component_enabled(space, to_openxr_component_type(p_component)); } void OpenXRFbSpatialEntity::set_component_enabled(ComponentType p_component, bool p_enabled) { + ERR_FAIL_COND_MSG(space == XR_NULL_HANDLE, "Underlying spatial entity doesn't exist (yet) or has been destroyed."); + ERR_FAIL_COND(p_component == COMPONENT_TYPE_UNKNOWN); Ref *userdata = memnew(Ref(this)); - XrAsyncRequestIdFB request_id = OpenXRFbSpatialEntityExtensionWrapper::get_singleton()->set_component_enabled(space, to_openxr_component_type(p_component), p_enabled, OpenXRFbSpatialEntity::_on_set_component_enabled_completed, userdata); + OpenXRFbSpatialEntityExtensionWrapper::get_singleton()->set_component_enabled(space, to_openxr_component_type(p_component), p_enabled, OpenXRFbSpatialEntity::_on_set_component_enabled_completed, userdata); } void OpenXRFbSpatialEntity::_on_set_component_enabled_completed(XrResult p_result, XrSpaceComponentTypeFB p_component, bool p_enabled, void *p_userdata) { @@ -121,10 +151,13 @@ void OpenXRFbSpatialEntity::_on_set_component_enabled_completed(XrResult p_resul } PackedStringArray OpenXRFbSpatialEntity::get_semantic_labels() const { + ERR_FAIL_COND_V_MSG(space == XR_NULL_HANDLE, PackedStringArray(), "Underlying spatial entity doesn't exist (yet) or has been destroyed."); return OpenXRFbSceneExtensionWrapper::get_singleton()->get_semantic_labels(space); } Dictionary OpenXRFbSpatialEntity::get_room_layout() const { + ERR_FAIL_COND_V_MSG(space == XR_NULL_HANDLE, Dictionary(), "Underlying spatial entity doesn't exist (yet) or has been destroyed."); + OpenXRFbSceneExtensionWrapper::RoomLayout room_layout; if (!OpenXRFbSceneExtensionWrapper::get_singleton()->get_room_layout(space, room_layout)) { return Dictionary(); @@ -145,6 +178,8 @@ Dictionary OpenXRFbSpatialEntity::get_room_layout() const { } Array OpenXRFbSpatialEntity::get_contained_uuids() const { + ERR_FAIL_COND_V_MSG(space == XR_NULL_HANDLE, Array(), "Underlying spatial entity doesn't exist (yet) or has been destroyed."); + Vector uuids = OpenXRFbSpatialEntityContainerExtensionWrapper::get_singleton()->get_contained_uuids(space); Array ret; @@ -156,31 +191,39 @@ Array OpenXRFbSpatialEntity::get_contained_uuids() const { } Rect2 OpenXRFbSpatialEntity::get_bounding_box_2d() const { + ERR_FAIL_COND_V_MSG(space == XR_NULL_HANDLE, Rect2(), "Underlying spatial entity doesn't exist (yet) or has been destroyed."); return OpenXRFbSceneExtensionWrapper::get_singleton()->get_bounding_box_2d(space); } AABB OpenXRFbSpatialEntity::get_bounding_box_3d() const { + ERR_FAIL_COND_V_MSG(space == XR_NULL_HANDLE, AABB(), "Underlying spatial entity doesn't exist (yet) or has been destroyed."); return OpenXRFbSceneExtensionWrapper::get_singleton()->get_bounding_box_3d(space); } PackedVector2Array OpenXRFbSpatialEntity::get_boundary_2d() const { + ERR_FAIL_COND_V_MSG(space == XR_NULL_HANDLE, PackedVector2Array(), "Underlying spatial entity doesn't exist (yet) or has been destroyed."); return OpenXRFbSceneExtensionWrapper::get_singleton()->get_boundary_2d(space); } void OpenXRFbSpatialEntity::track() { + ERR_FAIL_COND_MSG(space == XR_NULL_HANDLE, "Underlying spatial entity doesn't exist (yet) or has been destroyed."); ERR_FAIL_COND_MSG(!is_component_enabled(COMPONENT_TYPE_LOCATABLE), vformat("Cannot track spatial entity %s because COMPONENT_TYPE_LOCATABLE isn't enabled.", uuid)); OpenXRFbSpatialEntityExtensionWrapper::get_singleton()->track_entity(uuid, space); } void OpenXRFbSpatialEntity::untrack() { + ERR_FAIL_COND_MSG(space == XR_NULL_HANDLE, "Underlying spatial entity doesn't exist (yet) or has been destroyed."); OpenXRFbSpatialEntityExtensionWrapper::get_singleton()->untrack_entity(uuid); } bool OpenXRFbSpatialEntity::is_tracked() const { + ERR_FAIL_COND_V_MSG(space == XR_NULL_HANDLE, false, "Underlying spatial entity doesn't exist (yet) or has been destroyed."); return OpenXRFbSpatialEntityExtensionWrapper::get_singleton()->is_entity_tracked(uuid); } MeshInstance3D *OpenXRFbSpatialEntity::create_mesh_instance() const { + ERR_FAIL_COND_V_MSG(space == XR_NULL_HANDLE, nullptr, "Underlying spatial entity doesn't exist (yet) or has been destroyed."); + MeshInstance3D *mesh_instance = nullptr; if (is_component_enabled(COMPONENT_TYPE_BOUNDED_3D)) { @@ -213,6 +256,8 @@ MeshInstance3D *OpenXRFbSpatialEntity::create_mesh_instance() const { } Node3D *OpenXRFbSpatialEntity::create_collision_shape() const { + ERR_FAIL_COND_V_MSG(space == XR_NULL_HANDLE, nullptr, "Underlying spatial entity doesn't exist (yet) or has been destroyed."); + if (is_component_enabled(COMPONENT_TYPE_BOUNDED_3D)) { Ref box_shape; box_shape.instantiate(); @@ -245,6 +290,75 @@ Node3D *OpenXRFbSpatialEntity::create_collision_shape() const { return nullptr; } +Ref OpenXRFbSpatialEntity::create_spatial_anchor(const Transform3D &p_transform) { + Ref *userdata = memnew(Ref()); + (*userdata).instantiate(); + OpenXRFbSpatialEntityExtensionWrapper::get_singleton()->create_spatial_anchor(p_transform, &OpenXRFbSpatialEntity::_on_spatial_anchor_created, userdata); + return *userdata; +} + +void OpenXRFbSpatialEntity::_on_spatial_anchor_created(XrResult p_result, XrSpace p_space, const XrUuidEXT *p_uuid, void *p_userdata) { + Ref *userdata = (Ref *)p_userdata; + bool success = XR_SUCCEEDED(p_result); + if (success) { + (*userdata)->space = p_space; + (*userdata)->uuid = OpenXRUtilities::uuid_to_string_name(*p_uuid); + } + (*userdata)->emit_signal("openxr_fb_spatial_entity_created", success); + memdelete(userdata); +} + +void OpenXRFbSpatialEntity::save_to_storage(StorageLocation p_location) { + ERR_FAIL_COND_MSG(space == XR_NULL_HANDLE, "Underlying spatial entity doesn't exist (yet) or has been destroyed."); + + XrSpaceSaveInfoFB save_info = { + XR_TYPE_SPACE_SAVE_INFO_FB, // type + nullptr, // next + space, // space + to_openxr_storage_location(p_location), // location + XR_SPACE_PERSISTENCE_MODE_INDEFINITE_FB, // persistenceMode + }; + + Ref *userdata = memnew(Ref(this)); + OpenXRFbSpatialEntityStorageExtensionWrapper::get_singleton()->save_space(&save_info, OpenXRFbSpatialEntity::_on_save_to_storage, userdata); +} + +void OpenXRFbSpatialEntity::_on_save_to_storage(XrResult p_result, XrSpaceStorageLocationFB p_location, void *p_userdata) { + Ref *userdata = (Ref *)p_userdata; + (*userdata)->emit_signal("openxr_fb_spatial_entity_saved", XR_SUCCEEDED(p_result), from_openxr_storage_location(p_location)); + memdelete(userdata); +} + +void OpenXRFbSpatialEntity::erase_from_storage(StorageLocation p_location) { + ERR_FAIL_COND_MSG(space == XR_NULL_HANDLE, "Underlying spatial entity doesn't exist (yet) or has been destroyed."); + + XrSpaceEraseInfoFB erase_info = { + XR_TYPE_SPACE_ERASE_INFO_FB, // type + nullptr, // next + space, // space + to_openxr_storage_location(p_location), // location + }; + + Ref *userdata = memnew(Ref(this)); + OpenXRFbSpatialEntityStorageExtensionWrapper::get_singleton()->erase_space(&erase_info, OpenXRFbSpatialEntity::_on_save_to_storage, userdata); +} + +void OpenXRFbSpatialEntity::_on_erase_from_storage(XrResult p_result, XrSpaceStorageLocationFB p_location, void *p_userdata) { + Ref *userdata = (Ref *)p_userdata; + (*userdata)->emit_signal("openxr_fb_spatial_entity_erased", XR_SUCCEEDED(p_result), from_openxr_storage_location(p_location)); + memdelete(userdata); +} + +void OpenXRFbSpatialEntity::destroy() { + ERR_FAIL_COND_MSG(space == XR_NULL_HANDLE, "Underlying spatial entity doesn't exist (yet) or has been destroyed."); + OpenXRFbSpatialEntityExtensionWrapper *spatial_entity_extension_wrapper = OpenXRFbSpatialEntityExtensionWrapper::get_singleton(); + if (spatial_entity_extension_wrapper) { + spatial_entity_extension_wrapper->untrack_entity(uuid); + spatial_entity_extension_wrapper->destroy_space(space); + space = XR_NULL_HANDLE; + } +} + XrSpaceStorageLocationFB OpenXRFbSpatialEntity::to_openxr_storage_location(StorageLocation p_location) { switch (p_location) { case OpenXRFbSpatialEntity::STORAGE_LOCAL: { @@ -259,6 +373,23 @@ XrSpaceStorageLocationFB OpenXRFbSpatialEntity::to_openxr_storage_location(Stora } } +OpenXRFbSpatialEntity::StorageLocation OpenXRFbSpatialEntity::from_openxr_storage_location(XrSpaceStorageLocationFB p_location) { + switch (p_location) { + case XR_SPACE_STORAGE_LOCATION_LOCAL_FB: { + return STORAGE_LOCAL; + } break; + case XR_SPACE_STORAGE_LOCATION_CLOUD_FB: { + return STORAGE_CLOUD; + } break; + case XR_SPACE_STORAGE_LOCATION_INVALID_FB: + case XR_SPACE_STORAGE_LOCATION_MAX_ENUM_FB: + default: { + WARN_PRINT_ONCE(vformat("Received invalid XrSpaceStorageLocationFB: %s.", (int)p_location)); + return STORAGE_LOCAL; + } + } +} + XrSpaceComponentTypeFB OpenXRFbSpatialEntity::to_openxr_component_type(ComponentType p_component) { switch (p_component) { case COMPONENT_TYPE_LOCATABLE: { @@ -288,6 +419,7 @@ XrSpaceComponentTypeFB OpenXRFbSpatialEntity::to_openxr_component_type(Component case COMPONENT_TYPE_TRIANGLE_MESH: { return XR_SPACE_COMPONENT_TYPE_TRIANGLE_MESH_META; } break; + case COMPONENT_TYPE_UNKNOWN: default: { ERR_FAIL_V_MSG(XR_SPACE_COMPONENT_TYPE_LOCATABLE_FB, vformat("Unknown component type: %s", p_component)); } @@ -325,7 +457,7 @@ OpenXRFbSpatialEntity::ComponentType OpenXRFbSpatialEntity::from_openxr_componen } break; case XR_SPACE_COMPONENT_TYPE_MAX_ENUM_FB: default: { - ERR_FAIL_V_MSG(COMPONENT_TYPE_LOCATABLE, vformat("Unknown OpenXR component type: %s", p_component)); + ERR_FAIL_V_MSG(COMPONENT_TYPE_UNKNOWN, vformat("Unknown OpenXR component type: %s", p_component)); } } } diff --git a/common/src/main/cpp/extensions/openxr_fb_spatial_entity_extension_wrapper.cpp b/common/src/main/cpp/extensions/openxr_fb_spatial_entity_extension_wrapper.cpp index b77950eb..cffd53f6 100644 --- a/common/src/main/cpp/extensions/openxr_fb_spatial_entity_extension_wrapper.cpp +++ b/common/src/main/cpp/extensions/openxr_fb_spatial_entity_extension_wrapper.cpp @@ -141,12 +141,18 @@ bool OpenXRFbSpatialEntityExtensionWrapper::initialize_fb_spatial_entity_extensi GDEXTENSION_INIT_XR_FUNC_V(xrEnumerateSpaceSupportedComponentsFB); GDEXTENSION_INIT_XR_FUNC_V(xrSetSpaceComponentStatusFB); GDEXTENSION_INIT_XR_FUNC_V(xrGetSpaceComponentStatusFB); + GDEXTENSION_INIT_XR_FUNC_V(xrDestroySpace); GDEXTENSION_INIT_XR_FUNC_V(xrLocateSpace); return true; } bool OpenXRFbSpatialEntityExtensionWrapper::_on_event_polled(const void *event) { + if (static_cast(event)->type == XR_TYPE_EVENT_DATA_SPATIAL_ANCHOR_CREATE_COMPLETE_FB) { + on_spatial_anchor_created((const XrEventDataSpatialAnchorCreateCompleteFB *)event); + return true; + } + if (static_cast(event)->type == XR_TYPE_EVENT_DATA_SPACE_SET_STATUS_COMPLETE_FB) { on_set_component_enabled_complete((const XrEventDataSpaceSetStatusCompleteFB *)event); return true; @@ -155,6 +161,51 @@ bool OpenXRFbSpatialEntityExtensionWrapper::_on_event_polled(const void *event) return false; } +bool OpenXRFbSpatialEntityExtensionWrapper::create_spatial_anchor(const Transform3D &p_transform, SpatialAnchorCreatedCallback p_callback, void *p_userdata) { + XrAsyncRequestIdFB request_id = 0; + + Quaternion quat = Quaternion(p_transform.basis); + Vector3 pos = p_transform.origin; + XrPosef pose = { + { quat.x, quat.y, quat.z, quat.w }, // orientation + { pos.x, pos.y, pos.z }, // position + }; + + XrSpatialAnchorCreateInfoFB info = { + XR_TYPE_SPATIAL_ANCHOR_CREATE_INFO_FB, // type + nullptr, // next + reinterpret_cast(get_openxr_api()->get_play_space()), // space + pose, // poseInSpace + get_openxr_api()->get_next_frame_time(), // time + }; + + const XrResult result = xrCreateSpatialAnchorFB(SESSION, &info, &request_id); + if (!XR_SUCCEEDED(result)) { + WARN_PRINT("xrCreateSpatialAnchorFB failed!"); + WARN_PRINT(get_openxr_api()->get_error_string(result)); + p_callback(result, nullptr, nullptr, p_userdata); + return false; + } + + spatial_anchor_creation_info[request_id] = SpatialAnchorCreationInfo(p_callback, p_userdata); + return true; +} + +void OpenXRFbSpatialEntityExtensionWrapper::on_spatial_anchor_created(const XrEventDataSpatialAnchorCreateCompleteFB *event) { + if (!spatial_anchor_creation_info.has(event->requestId)) { + WARN_PRINT("Received unexpected XR_TYPE_EVENT_DATA_SPATIAL_ANCHOR_CREATE_COMPLETE_FB"); + return; + } + + SpatialAnchorCreationInfo *info = spatial_anchor_creation_info.getptr(event->requestId); + info->callback(event->result, event->space, &event->uuid, info->userdata); + spatial_anchor_creation_info.erase(event->requestId); +} + +bool OpenXRFbSpatialEntityExtensionWrapper::destroy_space(const XrSpace &p_space) { + return XR_SUCCEEDED(xrDestroySpace(p_space)); +} + Vector OpenXRFbSpatialEntityExtensionWrapper::get_support_components(const XrSpace &space) { Vector components; diff --git a/common/src/main/cpp/extensions/openxr_fb_spatial_entity_storage_extension_wrapper.cpp b/common/src/main/cpp/extensions/openxr_fb_spatial_entity_storage_extension_wrapper.cpp index 914441b9..773dc9bf 100644 --- a/common/src/main/cpp/extensions/openxr_fb_spatial_entity_storage_extension_wrapper.cpp +++ b/common/src/main/cpp/extensions/openxr_fb_spatial_entity_storage_extension_wrapper.cpp @@ -108,34 +108,34 @@ bool OpenXRFbSpatialEntityStorageExtensionWrapper::_on_event_polled(const void * return false; } -XrAsyncRequestIdFB OpenXRFbSpatialEntityStorageExtensionWrapper::save_space(const XrSpaceSaveInfoFB *p_info, StorageRequestCompleteCallback p_callback, void *p_userdata) { +bool OpenXRFbSpatialEntityStorageExtensionWrapper::save_space(const XrSpaceSaveInfoFB *p_info, StorageRequestCompleteCallback p_callback, void *p_userdata) { XrAsyncRequestIdFB request_id; const XrResult result = xrSaveSpaceFB(SESSION, p_info, &request_id); if (!XR_SUCCEEDED(result)) { WARN_PRINT("xrSaveSpaceFB failed!"); WARN_PRINT(get_openxr_api()->get_error_string(result)); - p_callback(result, p_userdata); - return 0; + p_callback(result, p_info->location, p_userdata); + return false; } requests[request_id] = RequestInfo(p_callback, p_userdata); - return request_id; + return true; } -XrAsyncRequestIdFB OpenXRFbSpatialEntityStorageExtensionWrapper::erase_space(const XrSpaceEraseInfoFB *p_info, StorageRequestCompleteCallback p_callback, void *p_userdata) { +bool OpenXRFbSpatialEntityStorageExtensionWrapper::erase_space(const XrSpaceEraseInfoFB *p_info, StorageRequestCompleteCallback p_callback, void *p_userdata) { XrAsyncRequestIdFB request_id; const XrResult result = xrEraseSpaceFB(SESSION, p_info, &request_id); if (!XR_SUCCEEDED(result)) { WARN_PRINT("xrEraseSpaceFB failed!"); WARN_PRINT(get_openxr_api()->get_error_string(result)); - p_callback(result, p_userdata); - return 0; + p_callback(result, p_info->location, p_userdata); + return false; } requests[request_id] = RequestInfo(p_callback, p_userdata); - return request_id; + return true; } void OpenXRFbSpatialEntityStorageExtensionWrapper::on_space_save_complete(const XrEventDataSpaceSaveCompleteFB *event) { @@ -145,7 +145,7 @@ void OpenXRFbSpatialEntityStorageExtensionWrapper::on_space_save_complete(const } RequestInfo *request = requests.getptr(event->requestId); - request->callback(event->result, request->userdata); + request->callback(event->result, event->location, request->userdata); requests.erase(event->requestId); } @@ -156,6 +156,6 @@ void OpenXRFbSpatialEntityStorageExtensionWrapper::on_space_erase_complete(const } RequestInfo *request = requests.getptr(event->requestId); - request->callback(event->result, request->userdata); + request->callback(event->result, event->location, request->userdata); requests.erase(event->requestId); } diff --git a/common/src/main/cpp/include/classes/openxr_fb_spatial_anchor_manager.h b/common/src/main/cpp/include/classes/openxr_fb_spatial_anchor_manager.h new file mode 100644 index 00000000..cc9c3d10 --- /dev/null +++ b/common/src/main/cpp/include/classes/openxr_fb_spatial_anchor_manager.h @@ -0,0 +1,135 @@ +/**************************************************************************/ +/* openxr_fb_spatial_anchor_manager.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT XR */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2022-present Godot XR contributors (see CONTRIBUTORS.md) */ +/* */ +/* 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. */ +/**************************************************************************/ + +#ifndef OPENXR_FB_SPATIAL_ANCHOR_MANAGER_H +#define OPENXR_FB_SPATIAL_ANCHOR_MANAGER_H + +#include + +#include +#include + +#include "classes/openxr_fb_spatial_entity.h" + +namespace godot { +class PackedScene; +class XROrigin3D; +class XRAnchor3D; + +class OpenXRFbSpatialAnchorManager : public Node { + GDCLASS(OpenXRFbSpatialAnchorManager, Node); + + Ref scene; + StringName scene_setup_method = "setup_scene"; + bool persist_in_local_file = true; + String local_file_path = "user://openxr_fb_spatial_anchors.json"; + bool auto_load = true; + bool erase_unknown_anchors_on_load = true; + bool visible = true; + + XROrigin3D *xr_origin = nullptr; + + struct Anchor { + ObjectID node; + Ref entity; + + Anchor(Node *p_node, const Ref &p_entity) { + node = p_node->get_instance_id(); + entity = p_entity; + } + Anchor() { } + }; + HashMap anchors; + + void _cleanup_anchors(); + + void _load_anchor(const StringName &p_uuid, const Dictionary &p_custom_data, OpenXRFbSpatialEntity::StorageLocation p_location, bool p_save_file); + void _track_anchor(const Ref &p_spatial_entity, bool p_save_file); + + void _on_anchor_created(bool p_success, const Transform3D &p_transform, const Ref &p_spatial_entity); + void _on_anchor_load_query_completed(const Array &p_results, const Dictionary &p_anchors_custom_data, OpenXRFbSpatialEntity::StorageLocation p_location, bool p_save_file); + void _on_anchor_track_enable_locatable_completed(bool p_succeeded, OpenXRFbSpatialEntity::ComponentType p_component, bool p_enabled, const Ref &p_entity, bool p_save_file); + void _on_anchor_track_enable_storable_completed(bool p_succeeded, OpenXRFbSpatialEntity::ComponentType p_component, bool p_enabled, const Ref &p_entity, bool p_save_file); + void _on_anchor_saved(bool p_succeeded, OpenXRFbSpatialEntity::StorageLocation p_location, const Ref &p_spatial_entity, bool p_save_file); + void _complete_anchor_setup(const Ref &p_spatial_entity, bool p_save_file); + + void _untrack_anchor(const Ref &p_spatial_entity); + + void _on_anchor_untrack_enable_storable_completed(bool p_succeeded, OpenXRFbSpatialEntity::ComponentType p_component, bool p_enabled, const Ref &p_entity); + void _on_anchor_erase_completed(bool p_succeeded, OpenXRFbSpatialEntity::StorageLocation p_location, const Ref &p_entity); + +protected: + void _notification(int p_what); + + void _on_openxr_session_begun(); + void _on_openxr_session_stopping(); + + static void _bind_methods(); + +public: + PackedStringArray _get_configuration_warnings() const override; + + void set_scene(const Ref &p_scene); + Ref get_scene() const; + + void set_scene_setup_method(const StringName &p_method); + StringName get_scene_setup_method() const; + + void set_persist_in_local_file(bool p_enable); + bool get_persist_in_local_file() const; + + void set_local_file_path(const String &p_local_file); + String get_local_file_path() const; + + void set_auto_load(bool p_enable); + bool get_auto_load() const; + + void set_erase_unknown_anchors_on_load(bool p_enable); + bool get_erase_unknown_anchors_on_load() const; + + void set_visible(bool p_visible); + bool get_visible() const; + void show(); + void hide(); + + void create_anchor(const Transform3D &p_transform, const Dictionary &p_custom_data); + void load_anchor(const StringName &p_uuid, const Dictionary &p_custom_data, OpenXRFbSpatialEntity::StorageLocation p_location); + void track_anchor(const Ref &p_spatial_entity); + void untrack_anchor(const Variant &p_spatial_entity_or_uuid); + + Error save_anchors_to_local_file(); + Error load_anchors_from_local_file(); + + Array get_anchor_uuids() const; + XRAnchor3D *get_anchor_node(const StringName &p_uuid) const; + Ref get_spatial_entity(const StringName &p_uuids) const; +}; +} // namespace godot + +#endif diff --git a/common/src/main/cpp/include/classes/openxr_fb_spatial_entity.h b/common/src/main/cpp/include/classes/openxr_fb_spatial_entity.h index 7c7df38e..7b44947c 100644 --- a/common/src/main/cpp/include/classes/openxr_fb_spatial_entity.h +++ b/common/src/main/cpp/include/classes/openxr_fb_spatial_entity.h @@ -50,6 +50,7 @@ class OpenXRFbSpatialEntity : public RefCounted { }; enum ComponentType { + COMPONENT_TYPE_UNKNOWN = -1, COMPONENT_TYPE_LOCATABLE, COMPONENT_TYPE_STORABLE, COMPONENT_TYPE_SHARABLE, @@ -64,17 +65,24 @@ class OpenXRFbSpatialEntity : public RefCounted { private: XrSpace space = XR_NULL_HANDLE; StringName uuid; + Dictionary custom_data; protected: static void _bind_methods(); - static void _on_set_component_enabled_completed(XrResult p_result, XrSpaceComponentTypeFB p_component, bool p_enabled, void *userdata); + static void _on_spatial_anchor_created(XrResult p_result, XrSpace p_space, const XrUuidEXT *p_uuid, void *p_userdata); + static void _on_set_component_enabled_completed(XrResult p_result, XrSpaceComponentTypeFB p_component, bool p_enabled, void *p_userdata); + static void _on_save_to_storage(XrResult p_result, XrSpaceStorageLocationFB p_location, void *p_userdata); + static void _on_erase_from_storage(XrResult p_result, XrSpaceStorageLocationFB p_location, void *p_userdata); String _to_string() const; public: StringName get_uuid() const; + void set_custom_data(const Dictionary &p_custom_data); + Dictionary get_custom_data() const; + Array get_supported_components() const; bool is_component_supported(ComponentType p_component) const; bool is_component_enabled(ComponentType p_component) const; @@ -94,7 +102,14 @@ class OpenXRFbSpatialEntity : public RefCounted { MeshInstance3D *create_mesh_instance() const; Node3D *create_collision_shape() const; + static Ref create_spatial_anchor(const Transform3D &p_transform); + + void save_to_storage(StorageLocation p_location = STORAGE_LOCAL); + void erase_from_storage(StorageLocation p_location = STORAGE_LOCAL); + void destroy(); + static XrSpaceStorageLocationFB to_openxr_storage_location(StorageLocation p_location); + static StorageLocation from_openxr_storage_location(XrSpaceStorageLocationFB p_location); static XrSpaceComponentTypeFB to_openxr_component_type(ComponentType p_component); static ComponentType from_openxr_component_type(XrSpaceComponentTypeFB p_component); diff --git a/common/src/main/cpp/include/extensions/openxr_fb_spatial_entity_extension_wrapper.h b/common/src/main/cpp/include/extensions/openxr_fb_spatial_entity_extension_wrapper.h index 9d19c310..c6994eb8 100644 --- a/common/src/main/cpp/include/extensions/openxr_fb_spatial_entity_extension_wrapper.h +++ b/common/src/main/cpp/include/extensions/openxr_fb_spatial_entity_extension_wrapper.h @@ -53,8 +53,12 @@ class OpenXRFbSpatialEntityExtensionWrapper : public OpenXRExtensionWrapperExten return fb_spatial_entity_ext; } + typedef void (*SpatialAnchorCreatedCallback)(XrResult p_result, XrSpace p_space, const XrUuidEXT *p_uuid, void *p_userdate); typedef void (*SetComponentEnabledCallback)(XrResult p_result, XrSpaceComponentTypeFB p_component, bool p_enabled, void *p_userdata); + bool create_spatial_anchor(const Transform3D &p_transform, SpatialAnchorCreatedCallback p_callback, void *p_userdata); + bool destroy_space(const XrSpace &p_space); + Vector get_support_components(const XrSpace &p_space); bool is_component_enabled(const XrSpace &p_space, XrSpaceComponentTypeFB p_component); bool set_component_enabled(const XrSpace &p_space, XrSpaceComponentTypeFB p_component, bool p_enabled, SetComponentEnabledCallback p_callback, void *p_userdata); @@ -99,6 +103,9 @@ class OpenXRFbSpatialEntityExtensionWrapper : public OpenXRExtensionWrapperExten (XrSpaceComponentTypeFB), componentType, (XrSpaceComponentStatusFB *), status) + EXT_PROTO_XRRESULT_FUNC1(xrDestroySpace, + (XrSpace), space); + EXT_PROTO_XRRESULT_FUNC4(xrLocateSpace, (XrSpace), space, (XrSpace), baseSpace, @@ -106,10 +113,24 @@ class OpenXRFbSpatialEntityExtensionWrapper : public OpenXRExtensionWrapperExten (XrSpaceLocation *), location) bool initialize_fb_spatial_entity_extension(const XrInstance &instance); + void on_spatial_anchor_created(const XrEventDataSpatialAnchorCreateCompleteFB *event); void on_set_component_enabled_complete(const XrEventDataSpaceSetStatusCompleteFB *event); HashMap request_extensions; + struct SpatialAnchorCreationInfo { + SpatialAnchorCreatedCallback callback = nullptr; + void *userdata = nullptr; + + SpatialAnchorCreationInfo() { } + + SpatialAnchorCreationInfo(SpatialAnchorCreatedCallback p_callback, void *p_userdata) { + callback = p_callback; + userdata = p_userdata; + } + }; + HashMap spatial_anchor_creation_info; + struct SetComponentEnabledInfo { SetComponentEnabledCallback callback = nullptr; void *userdata = nullptr; diff --git a/common/src/main/cpp/include/extensions/openxr_fb_spatial_entity_storage_extension_wrapper.h b/common/src/main/cpp/include/extensions/openxr_fb_spatial_entity_storage_extension_wrapper.h index 84a00207..e09d5a70 100644 --- a/common/src/main/cpp/include/extensions/openxr_fb_spatial_entity_storage_extension_wrapper.h +++ b/common/src/main/cpp/include/extensions/openxr_fb_spatial_entity_storage_extension_wrapper.h @@ -54,10 +54,10 @@ class OpenXRFbSpatialEntityStorageExtensionWrapper : public OpenXRExtensionWrapp static OpenXRFbSpatialEntityStorageExtensionWrapper *get_singleton(); - typedef void (*StorageRequestCompleteCallback)(XrResult p_result, void *p_userdata); + typedef void (*StorageRequestCompleteCallback)(XrResult p_result, XrSpaceStorageLocationFB p_location, void *p_userdata); - XrAsyncRequestIdFB save_space(const XrSpaceSaveInfoFB *p_info, StorageRequestCompleteCallback p_callback, void *p_userdata); - XrAsyncRequestIdFB erase_space(const XrSpaceEraseInfoFB *p_info, StorageRequestCompleteCallback p_callback, void *p_userdata); + bool save_space(const XrSpaceSaveInfoFB *p_info, StorageRequestCompleteCallback p_callback, void *p_userdata); + bool erase_space(const XrSpaceEraseInfoFB *p_info, StorageRequestCompleteCallback p_callback, void *p_userdata); OpenXRFbSpatialEntityStorageExtensionWrapper(); ~OpenXRFbSpatialEntityStorageExtensionWrapper(); diff --git a/common/src/main/cpp/register_types.cpp b/common/src/main/cpp/register_types.cpp index c81fd345..0be6bc30 100644 --- a/common/src/main/cpp/register_types.cpp +++ b/common/src/main/cpp/register_types.cpp @@ -59,6 +59,7 @@ #include "classes/openxr_fb_hand_tracking_mesh.h" #include "classes/openxr_fb_render_model.h" #include "classes/openxr_fb_scene_manager.h" +#include "classes/openxr_fb_spatial_anchor_manager.h" #include "classes/openxr_fb_spatial_entity.h" #include "classes/openxr_fb_spatial_entity_query.h" @@ -123,6 +124,7 @@ void initialize_plugin_module(ModuleInitializationLevel p_level) { ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); + ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); diff --git a/demo/main.gd b/demo/main.gd index 46a5b51d..5ca1ef90 100644 --- a/demo/main.gd +++ b/demo/main.gd @@ -6,13 +6,32 @@ extends Node3D @onready var right_hand_mesh: MeshInstance3D = $XROrigin3D/RightHand/RightHandMesh @onready var left_controller_model: OpenXRFbRenderModel = $XROrigin3D/LeftHand/LeftControllerFbRenderModel @onready var right_controller_model: OpenXRFbRenderModel = $XROrigin3D/RightHand/RightControllerFbRenderModel +@onready var left_hand_pointer: XRController3D = $XROrigin3D/LeftHandPointer +@onready var left_hand_pointer_raycast: RayCast3D = $XROrigin3D/LeftHandPointer/RayCast3D +@onready var scene_pointer_mesh: MeshInstance3D = $XROrigin3D/LeftHandPointer/ScenePointerMesh +@onready var scene_colliding_mesh: MeshInstance3D = $XROrigin3D/LeftHandPointer/SceneCollidingMesh @onready var floor_mesh: MeshInstance3D = $Floor @onready var world_environment: WorldEnvironment = $WorldEnvironment @onready var scene_manager: OpenXRFbSceneManager = $XROrigin3D/OpenXRFbSceneManager +@onready var spatial_anchor_manager: OpenXRFbSpatialAnchorManager = $XROrigin3D/OpenXRFbSpatialAnchorManager + +const SpatialAnchor = preload("res://spatial_anchor.tscn") + +const COLORS = [ + "#FF0000", # Red + "#00FF00", # Green + "#0000FF", # Blue + "#FFFF00", # Yellow + "#00FFFF", # Cyan + "#FF00FF", # Magenta + "#FF8000", # Orange + "#800080", # Purple +] var xr_interface : XRInterface = null var hand_tracking_source: Array[OpenXRInterface.HandTrackedSource] var passthrough_enabled: bool = false +var selected_spatial_anchor_node: Node3D = null # Called when the node enters the scene tree for the first time. func _ready(): @@ -25,6 +44,8 @@ func _ready(): for hand in OpenXRInterface.HAND_MAX: hand_tracking_source[hand] = xr_interface.get_hand_tracking_source(hand) + randomize() + func enable_passthrough(enable: bool) -> void: if passthrough_enabled == enable: @@ -42,6 +63,9 @@ func enable_passthrough(enable: bool) -> void: world_environment.environment.background_color = Color(0.0, 0.0, 0.0, 0.0) floor_mesh.visible = false scene_manager.visible = true + spatial_anchor_manager.visible = true + left_hand_pointer.visible = true + left_hand_pointer_raycast.enabled = true if not scene_manager.are_scene_anchors_created(): scene_manager.create_scene_anchors() else: @@ -51,6 +75,9 @@ func enable_passthrough(enable: bool) -> void: world_environment.environment.background_mode = Environment.BG_SKY floor_mesh.visible = true scene_manager.visible = false + spatial_anchor_manager.visible = false + left_hand_pointer.visible = false + left_hand_pointer_raycast.enabled = false passthrough_enabled = enable else: print("Switching to/from passthrough not supported.") @@ -82,6 +109,36 @@ func _physics_process(_delta: float) -> void: hand_tracking_source[hand] = source + if left_hand_pointer.visible: + var previous_selected_spatial_anchor_node = selected_spatial_anchor_node + + if left_hand_pointer_raycast.is_colliding(): + var collision_point: Vector3 = left_hand_pointer_raycast.get_collision_point() + scene_colliding_mesh.global_position = collision_point + + var pointer_length: float = (collision_point - left_hand_pointer.global_position).length() + scene_pointer_mesh.mesh.size.z = pointer_length + scene_pointer_mesh.position.z = -pointer_length / 2.0 + + var collider: CollisionObject3D = left_hand_pointer_raycast.get_collider() + if collider.get_collision_layer_value(3): + selected_spatial_anchor_node = collider + else: + selected_spatial_anchor_node = null + else: + scene_pointer_mesh.mesh.size.z = 5 + scene_pointer_mesh.position.z = -2.5 + selected_spatial_anchor_node = null + + if previous_selected_spatial_anchor_node != selected_spatial_anchor_node: + if previous_selected_spatial_anchor_node: + previous_selected_spatial_anchor_node.set_selected(false) + if selected_spatial_anchor_node: + selected_spatial_anchor_node.set_selected(true) + scene_colliding_mesh.visible = false + else: + scene_colliding_mesh.visible = true + func _on_left_hand_button_pressed(name): if name == "menu_button": @@ -91,6 +148,27 @@ func _on_left_hand_button_pressed(name): elif name == "by_button": enable_passthrough(not passthrough_enabled) + elif name == "trigger_click" and left_hand_pointer.visible: + if left_hand_pointer_raycast.is_colliding(): + if selected_spatial_anchor_node: + var anchor_parent = selected_spatial_anchor_node.get_parent() + if anchor_parent is XRAnchor3D: + spatial_anchor_manager.untrack_anchor(anchor_parent.tracker) + else: + var anchor_transform := Transform3D() + anchor_transform.origin = left_hand_pointer_raycast.get_collision_point() + + var collision_normal: Vector3 = left_hand_pointer_raycast.get_collision_normal() + if collision_normal.is_equal_approx(Vector3.UP): + anchor_transform.basis = anchor_transform.basis.rotated(Vector3(1.0, 0.0, 0.0), PI / 2.0) + elif collision_normal.is_equal_approx(Vector3.DOWN): + anchor_transform.basis = anchor_transform.basis.rotated(Vector3(1.0, 0.0, 0.0), -PI / 2.0) + else: + anchor_transform.basis = Basis.looking_at(left_hand_pointer_raycast.get_collision_normal()) + + print ("Attempting to create spatial anchor at: ", anchor_transform) + spatial_anchor_manager.create_anchor(anchor_transform, { color = COLORS[randi() % COLORS.size()] }) + func _on_left_controller_fb_render_model_render_model_loaded() -> void: left_hand_mesh.hide() diff --git a/demo/main.tscn b/demo/main.tscn index d04c7ab4..da7366cc 100644 --- a/demo/main.tscn +++ b/demo/main.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=17 format=3 uid="uid://cqsodpswgup8w"] +[gd_scene load_steps=20 format=3 uid="uid://cqsodpswgup8w"] [ext_resource type="Script" path="res://main.gd" id="1_fsva1"] [ext_resource type="PackedScene" uid="uid://c0uv4eu2yjm3b" path="res://viewport_2d_in_3d.tscn" id="2_7whgo"] @@ -6,6 +6,7 @@ [ext_resource type="PackedScene" uid="uid://bcjp8kcgde4cs" path="res://scene_anchor.tscn" id="4_3u3ah"] [ext_resource type="PackedScene" uid="uid://ikxieb2fyavg" path="res://assets/face/Face.gltf" id="4_wrwst"] [ext_resource type="PackedScene" uid="uid://bwfyi8pgigune" path="res://xr_fb_hand_tracking_aim_demo.tscn" id="5_6bxyh"] +[ext_resource type="PackedScene" uid="uid://dsfd7xrm6o50p" path="res://spatial_anchor.tscn" id="5_g7mio"] [sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_0x6cv"] sky_horizon_color = Color(0.64625, 0.65575, 0.67075, 1) @@ -38,6 +39,13 @@ size = Vector3(0.1, 0.1, 0.1) radius = 0.025 height = 0.05 +[sub_resource type="BoxMesh" id="BoxMesh_d27jm"] +size = Vector3(0.01, 0.01, 5) + +[sub_resource type="SphereMesh" id="SphereMesh_kdpqm"] +radius = 0.01 +height = 0.02 + [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_k604q"] [sub_resource type="PlaneMesh" id="PlaneMesh_mjcgt"] @@ -102,6 +110,24 @@ tracker = &"/user/eyes_ext" transform = Transform3D(1, 0, 0, 0, -0.0133513, 0.999911, 0, -0.999911, -0.0133513, 0, 0, -1.18886) mesh = SubResource("SphereMesh_5gcab") +[node name="LeftHandPointer" type="XRController3D" parent="XROrigin3D"] +visible = false +tracker = &"left_hand" +pose = &"aim" + +[node name="ScenePointerMesh" type="MeshInstance3D" parent="XROrigin3D/LeftHandPointer"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -2.5) +mesh = SubResource("BoxMesh_d27jm") + +[node name="SceneCollidingMesh" type="MeshInstance3D" parent="XROrigin3D/LeftHandPointer"] +mesh = SubResource("SphereMesh_kdpqm") + +[node name="RayCast3D" type="RayCast3D" parent="XROrigin3D/LeftHandPointer"] +enabled = false +target_position = Vector3(0, 0, -10) +collision_mask = 6 +collide_with_areas = true + [node name="LeftXRHandModifier3D" type="XRHandModifier3D" parent="XROrigin3D"] [node name="LeftHandModel" type="OpenXRFbHandTrackingMesh" parent="XROrigin3D/LeftXRHandModifier3D"] @@ -118,6 +144,10 @@ default_scene = ExtResource("4_3u3ah") auto_create = false visible = false +[node name="OpenXRFbSpatialAnchorManager" type="OpenXRFbSpatialAnchorManager" parent="XROrigin3D"] +scene = ExtResource("5_g7mio") +visible = false + [node name="Floor" type="MeshInstance3D" parent="."] mesh = SubResource("PlaneMesh_mjcgt") diff --git a/demo/project.godot b/demo/project.godot index 6052071c..70deb7b6 100644 --- a/demo/project.godot +++ b/demo/project.godot @@ -19,6 +19,12 @@ config/icon="res://icon.svg" settings/stdout/verbose_stdout=true +[layer_names] + +3d_physics/layer_1="Virtual Environment" +3d_physics/layer_2="Scene Understanding" +3d_physics/layer_3="Spatial Anchors" + [rendering] renderer/rendering_method="gl_compatibility" @@ -28,6 +34,7 @@ textures/vram_compression/import_etc2_astc=true [xr] openxr/enabled=true +openxr/reference_space=1 openxr/extensions/eye_gaze_interaction=true shaders/enabled=true openxr/extensions/hand_tracking_aim=true diff --git a/demo/scene_anchor.tscn b/demo/scene_anchor.tscn index 62c3e925..b317f36e 100644 --- a/demo/scene_anchor.tscn +++ b/demo/scene_anchor.tscn @@ -10,3 +10,4 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.01) text = "Label" [node name="StaticBody3D" type="StaticBody3D" parent="."] +collision_layer = 2 diff --git a/demo/spatial_anchor.gd b/demo/spatial_anchor.gd new file mode 100644 index 00000000..88cf2c35 --- /dev/null +++ b/demo/spatial_anchor.gd @@ -0,0 +1,25 @@ +extends Area3D + +@onready var mesh_instance: MeshInstance3D = $MeshInstance3D + +var color: Color +var selected := false + +func setup_scene(spatial_entity: OpenXRFbSpatialEntity) -> void: + var data: Dictionary = spatial_entity.custom_data + + color = Color(data.get('color', '#FFFFFF')) + + var material: StandardMaterial3D = mesh_instance.get_surface_override_material(0) + material.albedo_color = color + mesh_instance.set_surface_override_material(0, material) + +func set_selected(p_selected: bool) -> void: + selected = p_selected + + var material: StandardMaterial3D = mesh_instance.get_surface_override_material(0) + if selected: + material.albedo_color = Color(0.5, 0.5, 0.5) + else: + material.albedo_color = color + mesh_instance.set_surface_override_material(0, material) diff --git a/demo/spatial_anchor.tscn b/demo/spatial_anchor.tscn new file mode 100644 index 00000000..9faf3337 --- /dev/null +++ b/demo/spatial_anchor.tscn @@ -0,0 +1,30 @@ +[gd_scene load_steps=5 format=3 uid="uid://dsfd7xrm6o50p"] + +[ext_resource type="Script" path="res://spatial_anchor.gd" id="1_dengx"] + +[sub_resource type="CylinderMesh" id="CylinderMesh_gayi3"] +top_radius = 0.0 +bottom_radius = 0.1 +height = 0.2 + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_bat7g"] +resource_local_to_scene = true +albedo_color = Color(0, 0, 1, 1) + +[sub_resource type="CylinderShape3D" id="CylinderShape3D_d2ows"] +height = 0.2 +radius = 0.1 + +[node name="SpatialAnchor" type="Area3D"] +collision_layer = 4 +collision_mask = 0 +script = ExtResource("1_dengx") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="."] +transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 0, -0.1) +mesh = SubResource("CylinderMesh_gayi3") +surface_material_override/0 = SubResource("StandardMaterial3D_bat7g") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="."] +transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 0, -0.1) +shape = SubResource("CylinderShape3D_d2ows")