From 5ddbb99ced06052484af058f846d5fc26d9627aa Mon Sep 17 00:00:00 2001 From: Riccardo Zaglia Date: Thu, 4 Dec 2025 01:39:56 +1000 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=E2=9C=A8=20Implement=20co-location?= =?UTF-8?q?=20using=20markers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 181 ++++++----- Cargo.toml | 2 +- alvr/client_core/src/c_api.rs | 3 +- alvr/client_mock/src/main.rs | 1 + alvr/client_openxr/Cargo.toml | 15 +- .../client_openxr/src/extra_extensions/mod.rs | 31 +- .../spatial_marker_tracking.rs | 299 ++++++++++++++++++ alvr/client_openxr/src/interaction.rs | 56 +++- alvr/client_openxr/src/lib.rs | 23 +- alvr/client_openxr/src/lobby.rs | 26 +- alvr/client_openxr/src/stream.rs | 16 +- alvr/common/src/average.rs | 38 ++- alvr/common/src/lib.rs | 2 +- alvr/graphics/src/lobby.rs | 20 +- alvr/packets/src/lib.rs | 12 +- alvr/server_core/src/bitrate.rs | 4 +- alvr/server_core/src/connection.rs | 11 +- alvr/server_core/src/tracking/mod.rs | 169 +++++++--- alvr/session/src/settings.rs | 75 +++-- 19 files changed, 765 insertions(+), 219 deletions(-) create mode 100644 alvr/client_openxr/src/extra_extensions/spatial_marker_tracking.rs diff --git a/Cargo.lock b/Cargo.lock index 0a5ab0efc5..280b48ca72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -180,7 +180,7 @@ dependencies = [ [[package]] name = "alvr_adb" -version = "21.0.0-dev12" +version = "21.0.0-dev13" dependencies = [ "alvr_common", "alvr_filesystem", @@ -193,7 +193,7 @@ dependencies = [ [[package]] name = "alvr_audio" -version = "21.0.0-dev12" +version = "21.0.0-dev13" dependencies = [ "alvr_common", "alvr_session", @@ -209,7 +209,7 @@ dependencies = [ [[package]] name = "alvr_client_core" -version = "21.0.0-dev12" +version = "21.0.0-dev13" dependencies = [ "alvr_audio", "alvr_common", @@ -231,7 +231,7 @@ dependencies = [ [[package]] name = "alvr_client_mock" -version = "21.0.0-dev12" +version = "21.0.0-dev13" dependencies = [ "alvr_client_core", "alvr_common", @@ -245,7 +245,7 @@ dependencies = [ [[package]] name = "alvr_client_openxr" -version = "21.0.0-dev12" +version = "21.0.0-dev13" dependencies = [ "alvr_client_core", "alvr_common", @@ -262,7 +262,7 @@ dependencies = [ [[package]] name = "alvr_common" -version = "21.0.0-dev12" +version = "21.0.0-dev13" dependencies = [ "anyhow", "backtrace", @@ -277,7 +277,7 @@ dependencies = [ [[package]] name = "alvr_dashboard" -version = "21.0.0-dev12" +version = "21.0.0-dev13" dependencies = [ "alvr_adb", "alvr_audio", @@ -309,7 +309,7 @@ dependencies = [ [[package]] name = "alvr_events" -version = "21.0.0-dev12" +version = "21.0.0-dev13" dependencies = [ "alvr_common", "alvr_packets", @@ -320,14 +320,14 @@ dependencies = [ [[package]] name = "alvr_filesystem" -version = "21.0.0-dev12" +version = "21.0.0-dev13" dependencies = [ "dirs", ] [[package]] name = "alvr_graphics" -version = "21.0.0-dev12" +version = "21.0.0-dev13" dependencies = [ "alvr_common", "alvr_session", @@ -340,7 +340,7 @@ dependencies = [ [[package]] name = "alvr_gui_common" -version = "21.0.0-dev12" +version = "21.0.0-dev13" dependencies = [ "alvr_common", "egui", @@ -348,7 +348,7 @@ dependencies = [ [[package]] name = "alvr_launcher" -version = "21.0.0-dev12" +version = "21.0.0-dev13" dependencies = [ "alvr_adb", "alvr_common", @@ -371,7 +371,7 @@ dependencies = [ [[package]] name = "alvr_packets" -version = "21.0.0-dev12" +version = "21.0.0-dev13" dependencies = [ "alvr_common", "alvr_session", @@ -381,7 +381,7 @@ dependencies = [ [[package]] name = "alvr_server_core" -version = "21.0.0-dev12" +version = "21.0.0-dev13" dependencies = [ "alvr_adb", "alvr_audio", @@ -410,7 +410,7 @@ dependencies = [ [[package]] name = "alvr_server_io" -version = "21.0.0-dev12" +version = "21.0.0-dev13" dependencies = [ "alvr_common", "alvr_events", @@ -425,7 +425,7 @@ dependencies = [ [[package]] name = "alvr_server_openvr" -version = "21.0.0-dev12" +version = "21.0.0-dev13" dependencies = [ "alvr_common", "alvr_filesystem", @@ -443,7 +443,7 @@ dependencies = [ [[package]] name = "alvr_session" -version = "21.0.0-dev12" +version = "21.0.0-dev13" dependencies = [ "alvr_common", "alvr_filesystem", @@ -457,7 +457,7 @@ dependencies = [ [[package]] name = "alvr_sockets" -version = "21.0.0-dev12" +version = "21.0.0-dev13" dependencies = [ "alvr_common", "alvr_session", @@ -470,7 +470,7 @@ dependencies = [ [[package]] name = "alvr_system_info" -version = "21.0.0-dev12" +version = "21.0.0-dev13" dependencies = [ "alvr_common", "jni", @@ -484,7 +484,7 @@ dependencies = [ [[package]] name = "alvr_vrcompositor_wrapper" -version = "21.0.0-dev12" +version = "21.0.0-dev13" dependencies = [ "alvr_common", "alvr_filesystem", @@ -494,7 +494,7 @@ dependencies = [ [[package]] name = "alvr_vulkan_layer" -version = "21.0.0-dev12" +version = "21.0.0-dev13" dependencies = [ "alvr_common", "alvr_filesystem", @@ -506,7 +506,7 @@ dependencies = [ [[package]] name = "alvr_xtask" -version = "21.0.0-dev12" +version = "21.0.0-dev13" dependencies = [ "alvr_filesystem", "pico-args", @@ -853,7 +853,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -888,7 +888,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -1067,7 +1067,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -1087,7 +1087,7 @@ dependencies = [ "regex", "rustc-hash 2.1.1", "shlex", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -1189,7 +1189,7 @@ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -1272,9 +1272,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.46" +version = "1.2.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" +checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07" dependencies = [ "find-msvc-tools", "jobserver", @@ -1629,7 +1629,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -1640,7 +1640,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -1678,7 +1678,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -1739,7 +1739,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -1951,7 +1951,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -2104,7 +2104,7 @@ checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -2196,7 +2196,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -2283,7 +2283,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -2591,9 +2591,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "heck" @@ -2639,12 +2639,11 @@ dependencies = [ [[package]] name = "http" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "fnv", "itoa", ] @@ -2915,12 +2914,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.16.1", ] [[package]] @@ -3018,7 +3017,7 @@ checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -3605,7 +3604,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -3657,7 +3656,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -4034,9 +4033,9 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "open" -version = "5.3.2" +version = "5.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95" +checksum = "43bb73a7fa3799b198970490a51174027ba0d4ec504b03cd08caf513d40024bc" dependencies = [ "is-wsl", "libc", @@ -4046,7 +4045,7 @@ dependencies = [ [[package]] name = "openxr" version = "0.19.0" -source = "git+https://github.com/zmerp/openxrs?rev=e7c1b155e79ff8b58c2f6558d28e1398ebe08d2d#e7c1b155e79ff8b58c2f6558d28e1398ebe08d2d" +source = "git+https://github.com/zmerp/openxrs?rev=1da16a72829a2caba68aebdca44fa8605d51554d#1da16a72829a2caba68aebdca44fa8605d51554d" dependencies = [ "libc", "libloading", @@ -4057,7 +4056,7 @@ dependencies = [ [[package]] name = "openxr-sys" version = "0.11.0" -source = "git+https://github.com/zmerp/openxrs?rev=e7c1b155e79ff8b58c2f6558d28e1398ebe08d2d#e7c1b155e79ff8b58c2f6558d28e1398ebe08d2d" +source = "git+https://github.com/zmerp/openxrs?rev=1da16a72829a2caba68aebdca44fa8605d51554d#1da16a72829a2caba68aebdca44fa8605d51554d" dependencies = [ "libc", ] @@ -4185,7 +4184,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -4349,7 +4348,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -4387,7 +4386,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" dependencies = [ "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -4942,7 +4941,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -4977,7 +4976,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -5019,7 +5018,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -5050,9 +5049,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.6" +version = "1.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" dependencies = [ "libc", ] @@ -5263,7 +5262,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -5432,9 +5431,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.110" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -5458,7 +5457,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -5553,7 +5552,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -5564,7 +5563,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -5695,7 +5694,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -5824,9 +5823,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +checksum = "9cf146f99d442e8e68e585f5d798ccd3cad9a7835b917e09728880a862706456" dependencies = [ "bitflags 2.10.0", "bytes", @@ -5872,7 +5871,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -6065,9 +6064,9 @@ dependencies = [ [[package]] name = "ureq-proto" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b4531c118335662134346048ddb0e54cc86bd7e81866757873055f0e38f5d2" +checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f" dependencies = [ "base64", "http", @@ -6225,7 +6224,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", "wasm-bindgen-shared", ] @@ -6753,7 +6752,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -6764,7 +6763,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -6775,7 +6774,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -6786,7 +6785,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -7266,7 +7265,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -7403,7 +7402,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", "synstructure", ] @@ -7459,7 +7458,7 @@ checksum = "dc6821851fa840b708b4cbbaf6241868cabc85a2dc22f426361b0292bfc0b836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", "zbus-lockstep", "zbus_xml", "zvariant", @@ -7474,7 +7473,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", "zbus_names", "zvariant", "zvariant_utils", @@ -7507,22 +7506,22 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -7542,7 +7541,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", "synstructure", ] @@ -7563,7 +7562,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -7596,7 +7595,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -7711,7 +7710,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", "zvariant_utils", ] @@ -7724,6 +7723,6 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.110", + "syn 2.0.111", "winnow", ] diff --git a/Cargo.toml b/Cargo.toml index d63fa577e1..40ab77b448 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "2" members = ["alvr/*"] [workspace.package] -version = "21.0.0-dev12" +version = "21.0.0-dev13" edition = "2024" rust-version = "1.88" authors = ["alvr-org"] diff --git a/alvr/client_core/src/c_api.rs b/alvr/client_core/src/c_api.rs index dfd76f722f..b9714fbc24 100644 --- a/alvr/client_core/src/c_api.rs +++ b/alvr/client_core/src/c_api.rs @@ -498,6 +498,7 @@ pub extern "C" fn alvr_send_tracking( ..Default::default() }, body: None, + markers: Vec::new(), }); } } @@ -750,7 +751,7 @@ pub extern "C" fn alvr_render_lobby_opengl( }, ], None, - None, + vec![], render_background, false, ); diff --git a/alvr/client_mock/src/main.rs b/alvr/client_mock/src/main.rs index e05a4bbbc2..b24f9e9a38 100644 --- a/alvr/client_mock/src/main.rs +++ b/alvr/client_mock/src/main.rs @@ -176,6 +176,7 @@ fn tracking_thread( hand_skeletons: [None, None], face: FaceData::default(), body: None, + markers: vec![], }); drop(input_lock); diff --git a/alvr/client_openxr/Cargo.toml b/alvr/client_openxr/Cargo.toml index 033f121ae2..00e99866d6 100644 --- a/alvr/client_openxr/Cargo.toml +++ b/alvr/client_openxr/Cargo.toml @@ -17,7 +17,7 @@ alvr_packets.workspace = true alvr_session.workspace = true alvr_system_info.workspace = true -openxr = { git = "https://github.com/zmerp/openxrs", rev = "e7c1b155e79ff8b58c2f6558d28e1398ebe08d2d" } +openxr = { git = "https://github.com/zmerp/openxrs", rev = "1da16a72829a2caba68aebdca44fa8605d51554d" } [target.'cfg(target_os = "android")'.dependencies] android-activity = { version = "0.6", features = ["native-activity"] } @@ -125,6 +125,10 @@ name = "com.oculus.permission.FACE_TRACKING" [[package.metadata.android.uses_permission]] name = "com.oculus.permission.HAND_TRACKING" [[package.metadata.android.uses_permission]] +name = "com.oculus.permission.USE_ANCHOR_API" +[[package.metadata.android.uses_permission]] +name = "com.oculus.permission.USE_SCENE" +[[package.metadata.android.uses_permission]] name = "com.oculus.permission.WIFI_LOCK" [[package.metadata.android.application.meta_data]] name = "com.oculus.intent.category.VR" @@ -212,23 +216,24 @@ name = "com.ultraleap.openxr.api_layer" [[package.metadata.android.uses_feature]] name = "android.software.xr.api.spatial" required = true - [[package.metadata.android.uses_feature]] name = "android.software.xr.api.openxr" required = true - [[package.metadata.android.uses_feature]] name = "android.software.xr.input.controller" required = false - [[package.metadata.android.uses_feature]] name = "android.software.xr.input.hand_tracking" required = false - [[package.metadata.android.uses_feature]] name = "android.software.xr.input.eye_tracking" required = false +[[package.metadata.android.uses_permission]] +name = "android.permission.SCENE_UNDERSTANDING" +[[package.metadata.android.uses_permission]] +name = "android.permission.SCENE_UNDERSTANDING_COARSE" + [[package.metadata.android.application.uses_native_library]] name = "libopenxr.google.so" required = false diff --git a/alvr/client_openxr/src/extra_extensions/mod.rs b/alvr/client_openxr/src/extra_extensions/mod.rs index d0b833b3dc..b2022337d0 100644 --- a/alvr/client_openxr/src/extra_extensions/mod.rs +++ b/alvr/client_openxr/src/extra_extensions/mod.rs @@ -9,6 +9,7 @@ mod motion_tracking_bd; mod multimodal_input; mod passthrough_fb; mod passthrough_htc; +mod spatial_marker_tracking; pub use body_tracking_bd::*; pub use body_tracking_fb::*; @@ -21,10 +22,11 @@ pub use motion_tracking_bd::*; pub use multimodal_input::*; pub use passthrough_fb::*; pub use passthrough_htc::*; -use std::ffi::CString; -use std::mem; +pub use spatial_marker_tracking::*; -use openxr::{self as xr, sys}; +use openxr::{self as xr, AsHandle, sys}; +use std::ffi::CString; +use std::{mem, ptr}; fn xr_res(result: sys::Result) -> xr::Result<()> { if result.into_raw() >= 0 { @@ -70,3 +72,26 @@ fn get_instance_proc(session: &xr::Session, method_name: &str) -> xr .ok_or(sys::Result::ERROR_EXTENSION_NOT_PRESENT) } } + +fn check_future(instance: &xr::Instance, future: sys::FutureEXT) -> xr::Result { + let future_ext = instance + .exts() + .ext_future + .ok_or(sys::Result::ERROR_EXTENSION_NOT_PRESENT)?; + + let future_poll_info = sys::FuturePollInfoEXT { + ty: xr::StructureType::FUTURE_POLL_INFO_EXT, + next: ptr::null(), + future, + }; + let mut future_poll_result = sys::FuturePollResultEXT::out(ptr::null_mut()); + unsafe { + xr_res((future_ext.poll_future)( + instance.as_handle(), + &future_poll_info, + future_poll_result.as_mut_ptr(), + ))?; + + Ok(future_poll_result.assume_init().state == xr::FutureStateEXT::READY) + } +} diff --git a/alvr/client_openxr/src/extra_extensions/spatial_marker_tracking.rs b/alvr/client_openxr/src/extra_extensions/spatial_marker_tracking.rs new file mode 100644 index 0000000000..76d91b2c90 --- /dev/null +++ b/alvr/client_openxr/src/extra_extensions/spatial_marker_tracking.rs @@ -0,0 +1,299 @@ +use alvr_common::RelaxedAtomic; +use openxr::{ + self as xr, AsHandle, raw, + sys::{self, Handle}, +}; +use std::{ + ffi::{CStr, c_char}, + ptr, thread, + time::{Duration, Instant}, +}; + +const CAPABILITY: sys::SpatialCapabilityEXT = sys::SpatialCapabilityEXT::MARKER_TRACKING_QR_CODE; +// Note: The Meta implementation is currently bugged and the buffer capacity cannot be changed once +// the fist call of query_spatial_component_data is made. +const MAX_MARKERS_COUNT: usize = 32; +const DISCOVERY_TIMEOUT: Duration = Duration::from_secs(1); + +pub struct QRCodesSpatialContext { + instance: xr::Instance, + spatial_entity_fns: raw::SpatialEntityEXT, + inner: sys::SpatialContextEXT, + enabled_components: Vec, + entity_ids: [sys::SpatialEntityIdEXT; MAX_MARKERS_COUNT], + entity_states: [sys::SpatialEntityTrackingStateEXT; MAX_MARKERS_COUNT], + bounded_2d_arr: [sys::SpatialBounded2DDataEXT; MAX_MARKERS_COUNT], + marker_arr: [sys::SpatialMarkerDataEXT; MAX_MARKERS_COUNT], + string_buffer: [c_char; 256], + discovery_enabled: RelaxedAtomic, + discovery_timeout_deadline: Instant, + snapshot_future: Option, +} + +impl QRCodesSpatialContext { + pub fn new(session: &xr::Session, initial_discovery_state: bool) -> xr::Result { + let spatial_entity_fns = session + .instance() + .exts() + .ext_spatial_entity + .ok_or(sys::Result::ERROR_EXTENSION_NOT_PRESENT)?; + if session + .instance() + .exts() + .ext_spatial_marker_tracking + .is_none() + { + return Err(sys::Result::ERROR_EXTENSION_NOT_PRESENT); + } + alvr_common::error!("MarkerSpatialContext 1"); + + let enabled_components = vec![ + sys::SpatialComponentTypeEXT::BOUNDED_2D, + sys::SpatialComponentTypeEXT::MARKER, + ]; + + let qr_code_capability_configuration = sys::SpatialCapabilityConfigurationQrCodeEXT { + ty: sys::SpatialCapabilityConfigurationQrCodeEXT::TYPE, + next: ptr::null(), + capability: CAPABILITY, + enabled_component_count: enabled_components.len() as u32, + enabled_components: enabled_components.as_ptr(), + }; + + let base_capability_configuration = ptr::from_ref(&qr_code_capability_configuration).cast(); + let spatial_context_create_info = sys::SpatialContextCreateInfoEXT { + ty: sys::SpatialContextCreateInfoEXT::TYPE, + next: ptr::null(), + capability_config_count: 1, + capability_configs: &raw const base_capability_configuration, + }; + + let mut create_context_future: sys::FutureEXT = 0; + unsafe { + super::xr_res((spatial_entity_fns.create_spatial_context_async)( + session.as_handle(), + &spatial_context_create_info, + &mut create_context_future, + ))?; + } + + loop { + if super::check_future(session.instance(), create_context_future)? { + break; + } + thread::sleep(std::time::Duration::from_millis(1)); + } + + let completion = unsafe { + let mut completion = sys::CreateSpatialContextCompletionEXT::out(ptr::null_mut()); + super::xr_res((spatial_entity_fns.create_spatial_context_complete)( + session.as_handle(), + create_context_future, + completion.as_mut_ptr(), + ))?; + completion.assume_init() + }; + if completion.future_result != sys::Result::SUCCESS { + return Err(completion.future_result); + } + + Ok(QRCodesSpatialContext { + instance: session.instance().clone(), + spatial_entity_fns, + inner: completion.spatial_context, + enabled_components, + entity_ids: [xr::sys::SpatialEntityIdEXT::default(); MAX_MARKERS_COUNT], + entity_states: [xr::sys::SpatialEntityTrackingStateEXT::STOPPED; MAX_MARKERS_COUNT], + bounded_2d_arr: [xr::sys::SpatialBounded2DDataEXT::default(); MAX_MARKERS_COUNT], + marker_arr: [sys::SpatialMarkerDataEXT { + capability: CAPABILITY, + marker_id: 0, + data: sys::SpatialBufferEXT { + buffer_id: sys::SpatialBufferIdEXT::NULL, + buffer_type: sys::SpatialBufferTypeEXT::UNKNOWN, + }, + }; MAX_MARKERS_COUNT], + string_buffer: [0; 256], + discovery_enabled: RelaxedAtomic::new(initial_discovery_state), + discovery_timeout_deadline: Instant::now(), + snapshot_future: None, + }) + } + + pub fn set_discovery_enabled(&self, enabled: bool) { + self.discovery_enabled.set(enabled); + } + + pub fn poll( + &mut self, + base_space: &xr::Space, + time: xr::Time, + ) -> xr::Result>> { + let new = Instant::now(); + + if self.snapshot_future.is_none() + && self.discovery_enabled.value() + && new > self.discovery_timeout_deadline + { + let snapshot_create_info = sys::SpatialDiscoverySnapshotCreateInfoEXT { + ty: sys::SpatialDiscoverySnapshotCreateInfoEXT::TYPE, + next: ptr::null(), + component_type_count: self.enabled_components.len() as u32, + component_types: self.enabled_components.as_ptr(), + }; + + let mut create_snapshot_future: sys::FutureEXT = 0; + unsafe { + super::xr_res((self + .spatial_entity_fns + .create_spatial_discovery_snapshot_async)( + self.inner, + &snapshot_create_info, + &mut create_snapshot_future, + ))?; + } + + self.snapshot_future = Some(create_snapshot_future); + self.discovery_timeout_deadline = new + DISCOVERY_TIMEOUT; + }; + + let &Some(future) = &self.snapshot_future else { + return Ok(None); + }; + + // Return if the future is not completed + if !super::check_future(&self.instance, future)? { + return Ok(None); + } + + self.snapshot_future = None; + + let completion_info = sys::CreateSpatialDiscoverySnapshotCompletionInfoEXT { + ty: sys::CreateSpatialDiscoverySnapshotCompletionInfoEXT::TYPE, + next: ptr::null(), + base_space: base_space.as_handle(), + time, + future, + }; + + let completion = unsafe { + let mut completion = + sys::CreateSpatialDiscoverySnapshotCompletionEXT::out(ptr::null_mut()); + super::xr_res((self + .spatial_entity_fns + .create_spatial_discovery_snapshot_complete)( + self.inner, + &completion_info, + completion.as_mut_ptr(), + ))?; + completion.assume_init() + }; + if completion.future_result != sys::Result::SUCCESS { + return Err(completion.future_result); + } + + let query_contition = sys::SpatialComponentDataQueryConditionEXT { + ty: sys::SpatialComponentDataQueryConditionEXT::TYPE, + next: ptr::null(), + component_type_count: self.enabled_components.len() as u32, + component_types: self.enabled_components.as_ptr(), + }; + + let mut query_result = sys::SpatialComponentDataQueryResultEXT { + ty: sys::SpatialComponentDataQueryResultEXT::TYPE, + next: ptr::null_mut(), + entity_id_capacity_input: MAX_MARKERS_COUNT as u32, + entity_id_count_output: 0, + entity_ids: self.entity_ids.as_mut_ptr(), + entity_state_capacity_input: MAX_MARKERS_COUNT as u32, + entity_state_count_output: 0, + entity_states: self.entity_states.as_mut_ptr(), + }; + unsafe { + super::xr_res((self.spatial_entity_fns.query_spatial_component_data)( + completion.snapshot, + &query_contition, + &mut query_result, + ))?; + } + let marker_count = query_result.entity_id_count_output; + + let mut bounded_2d_list = sys::SpatialComponentBounded2DListEXT { + ty: sys::SpatialComponentBounded2DListEXT::TYPE, + next: ptr::null_mut(), + bound_count: marker_count, + bounds: self.bounded_2d_arr.as_mut_ptr(), + }; + query_result.next = (&raw mut bounded_2d_list).cast(); + + let mut marker_list = sys::SpatialComponentMarkerListEXT { + ty: sys::SpatialComponentMarkerListEXT::TYPE, + next: ptr::null_mut(), + marker_count, + markers: self.marker_arr.as_mut_ptr(), + }; + bounded_2d_list.next = (&raw mut marker_list).cast(); + + unsafe { + super::xr_res((self.spatial_entity_fns.query_spatial_component_data)( + completion.snapshot, + &query_contition, + &mut query_result, + ))?; + } + + let mut out_markers = vec![]; + for idx in 0..query_result.entity_id_count_output as usize { + if self.entity_states[idx] != sys::SpatialEntityTrackingStateEXT::TRACKING + || self.marker_arr[idx].capability != CAPABILITY + || self.marker_arr[idx].data.buffer_id == sys::SpatialBufferIdEXT::NULL + || self.marker_arr[idx].data.buffer_type != sys::SpatialBufferTypeEXT::STRING + { + alvr_common::debug!( + "Parsing marker failed! {:?} {:?} {:?} {:?}", + self.entity_states[idx], + self.marker_arr[idx].capability, + self.marker_arr[idx].data.buffer_id, + self.marker_arr[idx].data.buffer_type + ); + continue; + } + + let get_info = sys::SpatialBufferGetInfoEXT { + ty: sys::SpatialBufferGetInfoEXT::TYPE, + next: ptr::null(), + buffer_id: self.marker_arr[idx].data.buffer_id, + }; + + let string = unsafe { + let mut _string_lenght = 0; + super::xr_res((self.spatial_entity_fns.get_spatial_buffer_string)( + completion.snapshot, + &get_info, + self.string_buffer.len() as u32, + &mut _string_lenght, + self.string_buffer.as_mut_ptr(), + ))?; + + CStr::from_ptr(self.string_buffer.as_ptr()) + .to_str() + .map_err(|_| sys::Result::ERROR_SPATIAL_BUFFER_ID_INVALID_EXT)? + .to_owned() + }; + + let pose = self.bounded_2d_arr[idx].center; + + out_markers.push((string, pose)); + } + + Ok(Some(out_markers)) + } +} + +impl Drop for QRCodesSpatialContext { + fn drop(&mut self) { + unsafe { + (self.spatial_entity_fns.destroy_spatial_context)(self.inner); + } + } +} diff --git a/alvr/client_openxr/src/interaction.rs b/alvr/client_openxr/src/interaction.rs index 06cfb01c5c..28e14067b5 100644 --- a/alvr/client_openxr/src/interaction.rs +++ b/alvr/client_openxr/src/interaction.rs @@ -3,16 +3,19 @@ use crate::{ extra_extensions::{ self, BODY_JOINT_SET_FULL_BODY_META, BodyJointSetBD, BodyTrackerBD, BodyTrackerFB, EyeTrackerSocial, FULL_BODY_JOINT_COUNT_META, FaceTracker2FB, FaceTrackerPico, - FacialTrackerHTC, MotionTrackerBD, MultimodalMeta, + FacialTrackerHTC, MotionTrackerBD, MultimodalMeta, QRCodesSpatialContext, }, }; use alvr_common::{ glam::{Quat, Vec3}, + settings_schema::Switch, *, }; use alvr_graphics::HandData; use alvr_packets::{ButtonEntry, ButtonValue, FaceData, FaceExpressions, StreamConfig}; -use alvr_session::{BodyTrackingBDConfig, BodyTrackingSourcesConfig, FaceTrackingSourcesConfig}; +use alvr_session::{ + BodyTrackingBDConfig, BodyTrackingSourcesConfig, FaceTrackingSourcesConfig, RecenteringMode, +}; use openxr as xr; use std::{ collections::{HashMap, HashSet}, @@ -145,6 +148,7 @@ pub struct InteractionSourcesConfig { pub face_tracking: Option, pub body_tracking: Option, pub prefers_multimodal_input: bool, + pub initial_marker_discovery_state: bool, } impl InteractionSourcesConfig { @@ -168,6 +172,12 @@ impl InteractionSourcesConfig { .multimodal_tracking .as_option() .is_some_and(|c| c.enabled), + initial_marker_discovery_state: matches!( + config.settings.headset.recentering_mode, + RecenteringMode::Stage { + marker_based_colocation: Switch::Enabled(_) + } + ), } } } @@ -184,6 +194,7 @@ pub struct InteractionContext { pub multimodal_hands_enabled: bool, pub face_sources: FaceSources, pub body_source: Option, + pub marker_spatial_context: Option, } impl InteractionContext { @@ -497,6 +508,7 @@ impl InteractionContext { face_expressions_tracker, }, body_source: None, + marker_spatial_context: None, } } @@ -524,6 +536,7 @@ impl InteractionContext { } self.body_source = None; + self.marker_spatial_context = None; if let Some(config) = &config.face_tracking { if matches!(self.platform, Platform::QuestPro) @@ -549,6 +562,21 @@ impl InteractionContext { } } } + if self.platform.is_quest() { + #[cfg(target_os = "android")] + { + alvr_system_info::try_get_permission("com.oculus.permission.USE_ANCHOR_API"); + alvr_system_info::try_get_permission("com.oculus.permission.USE_SCENE") + } + } else if matches!(self.platform, Platform::SamsungGalaxyXR) { + #[cfg(target_os = "android")] + { + alvr_system_info::try_get_permission("android.permission.SCENE_UNDERSTANDING"); + alvr_system_info::try_get_permission( + "android.permission.SCENE_UNDERSTANDING_COARSE", + ); + } + } if config.body_tracking.is_some() && self.platform.is_quest() @@ -668,6 +696,11 @@ impl InteractionContext { } } } + + self.marker_spatial_context = check_ext_object( + "MarkerSpatialContext", + QRCodesSpatialContext::new(&self.xr_session, config.initial_marker_discovery_state), + ); } } @@ -1136,3 +1169,22 @@ pub fn get_bd_motion_trackers(source: &BodyTracker, time: Duration) -> Vec<(u64, Vec::new() } + +pub fn get_marker_poses( + context: &mut QRCodesSpatialContext, + reference_space: &xr::Space, + time: Duration, +) -> Option> { + let xr_time = crate::to_xr_time(time); + + context + .poll(reference_space, xr_time) + .ok() + .flatten() + .map(|markers| { + markers + .into_iter() + .map(|(id, pose)| (id, crate::from_xr_pose(pose))) + .collect() + }) +} diff --git a/alvr/client_openxr/src/lib.rs b/alvr/client_openxr/src/lib.rs index 899fe42776..cf8d5bc415 100644 --- a/alvr/client_openxr/src/lib.rs +++ b/alvr/client_openxr/src/lib.rs @@ -19,8 +19,7 @@ use alvr_session::{BodyTrackingBDConfig, BodyTrackingSourcesConfig}; use alvr_system_info::Platform; use extra_extensions::{ BD_BODY_TRACKING_EXTENSION_NAME, BD_MOTION_TRACKING_EXTENSION_NAME, - META_BODY_TRACKING_FIDELITY_EXTENSION_NAME, META_BODY_TRACKING_FULL_BODY_EXTENSION_NAME, - META_DETACHED_CONTROLLERS_EXTENSION_NAME, + META_BODY_TRACKING_FIDELITY_EXTENSION_NAME, META_DETACHED_CONTROLLERS_EXTENSION_NAME, META_SIMULTANEOUS_HANDS_AND_CONTROLLERS_EXTENSION_NAME, PICO_CONFIGURATION_EXTENSION_NAME, }; use interaction::{InteractionContext, InteractionSourcesConfig}; @@ -159,11 +158,10 @@ pub fn entry_point() { xr_entry.initialize_android_loader().unwrap(); let available_extensions = xr_entry.enumerate_extensions().unwrap(); - info!("OpenXR available extensions: {available_extensions:#?}"); info!( - "Extra available extensions: {:#?}", + "OpenXR available extensions: {:#?}", available_extensions - .other + .names() .iter() .map(|vec| CStr::from_bytes_with_nul(vec) .unwrap() @@ -179,8 +177,11 @@ pub fn entry_point() { let mut exts = xr::ExtensionSet::default(); exts.bd_controller_interaction = available_extensions.bd_controller_interaction; exts.ext_eye_gaze_interaction = available_extensions.ext_eye_gaze_interaction; + exts.ext_future = available_extensions.ext_future; exts.ext_hand_tracking = available_extensions.ext_hand_tracking; exts.ext_local_floor = available_extensions.ext_local_floor; + exts.ext_spatial_entity = available_extensions.ext_spatial_entity; + exts.ext_spatial_marker_tracking = available_extensions.ext_spatial_marker_tracking; exts.ext_user_presence = available_extensions.ext_user_presence; exts.fb_body_tracking = available_extensions.fb_body_tracking; exts.fb_color_space = available_extensions.fb_color_space; @@ -202,12 +203,12 @@ pub fn entry_point() { } exts.khr_convert_timespec_time = true; exts.khr_opengl_es_enable = true; + exts.meta_body_tracking_full_body = available_extensions.meta_body_tracking_full_body; exts.other = available_extensions .other .into_iter() .filter(|ext| { [ - META_BODY_TRACKING_FULL_BODY_EXTENSION_NAME, META_BODY_TRACKING_FIDELITY_EXTENSION_NAME, META_SIMULTANEOUS_HANDS_AND_CONTROLLERS_EXTENSION_NAME, META_DETACHED_CONTROLLERS_EXTENSION_NAME, @@ -356,6 +357,7 @@ pub fn entry_point() { face_tracking: None, body_tracking: lobby_body_tracking_config, prefers_multimodal_input: true, + initial_marker_discovery_state: true, }; interaction_context .write() @@ -393,7 +395,7 @@ pub fn entry_point() { core_context.pause(); - xr_session.end().unwrap(); + xr_session.end().ok(); } xr::SessionState::EXITING | xr::SessionState::LOSS_PENDING => { break 'render_loop; @@ -431,10 +433,6 @@ pub fn entry_point() { core_context.send_proximity_state(event.is_user_present()); } - xr::Event::Unknown => { - // use event_storage.as_raw(), reinterpret as sys::BaseInStructure, get type - // and then reinterpret as the event struct - } _ => (), } } @@ -512,6 +510,9 @@ pub fn entry_point() { } else if config.passthrough.is_none() && passthrough_layer.is_some() { passthrough_layer = None; } + if let Some(context) = &interaction_context.read().marker_spatial_context { + context.set_discovery_enabled(config.marker_colocation); + } if let Some(stream) = &mut stream_context { stream.update_real_time_config(&config); diff --git a/alvr/client_openxr/src/lobby.rs b/alvr/client_openxr/src/lobby.rs index 0552adaa1b..86800a3a5c 100644 --- a/alvr/client_openxr/src/lobby.rs +++ b/alvr/client_openxr/src/lobby.rs @@ -2,7 +2,7 @@ use crate::{ graphics::{self, ProjectionLayerAlphaConfig, ProjectionLayerBuilder}, interaction::{self, InteractionContext}, }; -use alvr_common::{Pose, ViewParams, glam::UVec2, parking_lot::RwLock}; +use alvr_common::{DeviceMotion, Pose, ViewParams, glam::UVec2, parking_lot::RwLock}; use alvr_graphics::{GraphicsContext, LobbyRenderer, LobbyViewParams, SDR_FORMAT_GL}; use alvr_system_info::Platform; use openxr as xr; @@ -130,17 +130,23 @@ impl Lobby { &mut Pose::default(), ); - let additional_motions = self - .interaction_ctx - .read() - .body_source - .as_ref() - .map(|source| { + let mut additional_motions = vec![]; + if let Some(source) = &self.interaction_ctx.read().body_source { + additional_motions.extend( interaction::get_bd_motion_trackers(source, vsync_time) .iter() - .map(|(_, motion)| *motion) - .collect() - }); + .map(|(_, motion)| *motion), + ) + } + if let Some(context) = &mut self.interaction_ctx.write().marker_spatial_context + && let Some(marker_poses) = + interaction::get_marker_poses(context, &self.reference_space, vsync_time) + { + additional_motions.extend(marker_poses.into_iter().map(|(_, pose)| DeviceMotion { + pose, + ..Default::default() + })); + } let body_skeleton = self .interaction_ctx diff --git a/alvr/client_openxr/src/stream.rs b/alvr/client_openxr/src/stream.rs index 3cf144eecf..ab6afbc62f 100644 --- a/alvr/client_openxr/src/stream.rs +++ b/alvr/client_openxr/src/stream.rs @@ -530,7 +530,8 @@ fn stream_input_loop( let mut deadline = Instant::now(); let frame_interval = Duration::from_secs_f32(1.0 / refresh_rate); while running.value() { - let int_ctx = &*interaction_ctx.read(); + let int_ctx_lock = interaction_ctx.read(); + let int_ctx = &*int_ctx_lock; // Streaming related inputs are updated here. Make sure every input poll is done in this // thread if let Err(e) = xr_session.sync_actions(&[(&int_ctx.action_set).into()]) { @@ -628,6 +629,15 @@ fn stream_input_loop( device_motions.append(&mut interaction::get_bd_motion_trackers(source, now)); } + drop(int_ctx_lock); + + let markers = interaction_ctx + .write() + .marker_spatial_context + .as_mut() + .and_then(|ctx| interaction::get_marker_poses(ctx, stage_reference_space, now)) + .unwrap_or_default(); + // Even though the server is already adding the motion-to-photon latency, here we use // target_time as the poll_timestamp to compensate for the fact that video frames are sent // with the poll timestamp instead of the vsync time. This is to ensure correctness when @@ -642,9 +652,11 @@ fn stream_input_loop( ], face, body, + markers, }); - let button_entries = interaction::update_buttons(&xr_session, &int_ctx.button_actions); + let button_entries = + interaction::update_buttons(&xr_session, &interaction_ctx.read().button_actions); if !button_entries.is_empty() { core_ctx.send_buttons(button_entries); } diff --git a/alvr/common/src/average.rs b/alvr/common/src/average.rs index b0815e6ddf..e62decdf84 100644 --- a/alvr/common/src/average.rs +++ b/alvr/common/src/average.rs @@ -1,4 +1,5 @@ -use std::{collections::VecDeque, time::Duration}; +use glam::Vec2; +use std::{collections::VecDeque, num::NonZeroUsize, time::Duration}; pub struct SlidingWindowAverage { history_buffer: VecDeque, @@ -7,8 +8,11 @@ pub struct SlidingWindowAverage { impl SlidingWindowAverage { pub fn new(initial_value: T, max_history_size: usize) -> Self { + let mut history_buffer = VecDeque::with_capacity(max_history_size); + history_buffer.push_back(initial_value); + Self { - history_buffer: [initial_value].into_iter().collect(), + history_buffer, max_history_size, } } @@ -21,9 +25,9 @@ impl SlidingWindowAverage { self.history_buffer.push_back(sample); } - pub fn retain(&mut self, count: usize) { + pub fn retain(&mut self, count: NonZeroUsize) { self.history_buffer - .drain(0..self.history_buffer.len().saturating_sub(count)); + .drain(0..self.history_buffer.len().saturating_sub(count.get())); } } @@ -38,3 +42,29 @@ impl SlidingWindowAverage { self.history_buffer.iter().sum::() / self.history_buffer.len() as u32 } } + +impl SlidingWindowAverage { + pub fn get_average(&self) -> Vec2 { + self.history_buffer.iter().sum::() / self.history_buffer.len() as f32 + } +} + +pub struct AngleSlidingWindowAverage { + inner: SlidingWindowAverage, +} + +impl AngleSlidingWindowAverage { + pub fn new(initial_value: f32, max_history_size: usize) -> Self { + Self { + inner: SlidingWindowAverage::new(Vec2::from_angle(initial_value), max_history_size), + } + } + + pub fn submit_sample(&mut self, sample: f32) { + self.inner.submit_sample(Vec2::from_angle(sample)); + } + + pub fn get_average(&self) -> f32 { + self.inner.get_average().to_angle() + } +} diff --git a/alvr/common/src/lib.rs b/alvr/common/src/lib.rs index 7a679d646a..7ec96d4974 100644 --- a/alvr/common/src/lib.rs +++ b/alvr/common/src/lib.rs @@ -1,7 +1,7 @@ mod average; mod c_api; mod connection_result; -mod inputs; +pub mod inputs; mod logging; mod primitives; mod version; diff --git a/alvr/graphics/src/lobby.rs b/alvr/graphics/src/lobby.rs index bd4e5f232a..7cb789ea84 100644 --- a/alvr/graphics/src/lobby.rs +++ b/alvr/graphics/src/lobby.rs @@ -405,7 +405,7 @@ impl LobbyRenderer { view_params: [LobbyViewParams; 2], hand_data: [HandData; 2], body_skeleton: Option, - additional_motions: Option>, + additional_motions: Vec, render_background: bool, show_velocities: bool, ) { @@ -600,16 +600,14 @@ impl LobbyRenderer { } } - if let Some(motions) = &additional_motions { - for motion in motions { - draw_crosshair( - &mut pass, - motion, - view_proj, - show_velocities, - &[255, 255, 255, 255], - ); - } + for motion in &additional_motions { + draw_crosshair( + &mut pass, + motion, + view_proj, + show_velocities, + &[255, 255, 255, 255], + ); } if let Some(skeleton) = &body_skeleton { diff --git a/alvr/packets/src/lib.rs b/alvr/packets/src/lib.rs index 02594920d2..c8e358cf0d 100644 --- a/alvr/packets/src/lib.rs +++ b/alvr/packets/src/lib.rs @@ -3,9 +3,11 @@ use alvr_common::{ anyhow::Result, glam::{Quat, UVec2, Vec2}, semver::Version, + settings_schema::Switch, }; use alvr_session::{ - ClientsidePostProcessingConfig, CodecType, PassthroughMode, SessionConfig, Settings, + ClientsidePostProcessingConfig, CodecType, PassthroughMode, RecenteringMode, SessionConfig, + Settings, }; use serde::{Deserialize, Serialize}; use serde_json as json; @@ -229,6 +231,7 @@ pub struct TrackingData { pub hand_skeletons: [Option<[Pose; 26]>; 2], pub face: FaceData, pub body: Option, + pub markers: Vec<(String, Pose)>, } #[derive(Serialize, Deserialize)] @@ -327,6 +330,7 @@ pub enum FirewallRulesAction { pub struct RealTimeConfig { pub passthrough: Option, pub clientside_post_processing: Option, + pub marker_colocation: bool, pub ext_str: String, } @@ -339,6 +343,12 @@ impl RealTimeConfig { .clientside_post_processing .clone() .into_option(), + marker_colocation: matches!( + settings.headset.recentering_mode, + RecenteringMode::Stage { + marker_based_colocation: Switch::Enabled(_) + } + ), ext_str: String::new(), // No extensions for now } } diff --git a/alvr/server_core/src/bitrate.rs b/alvr/server_core/src/bitrate.rs index e934448610..769be3967e 100644 --- a/alvr/server_core/src/bitrate.rs +++ b/alvr/server_core/src/bitrate.rs @@ -5,6 +5,7 @@ use alvr_session::{ }; use std::{ collections::VecDeque, + num::NonZeroUsize, time::{Duration, Instant}, }; @@ -77,7 +78,8 @@ impl BitrateManager { || interval_ratio < 1.0 / config.framerate_reset_threshold_multiplier { // Clear most of the samples, keep some for stability - self.frame_interval_average.retain(5); + self.frame_interval_average + .retain(NonZeroUsize::new(5).unwrap()); self.update_needed = true; } } diff --git a/alvr/server_core/src/connection.rs b/alvr/server_core/src/connection.rs index c65767a683..7d7ab07a70 100644 --- a/alvr/server_core/src/connection.rs +++ b/alvr/server_core/src/connection.rs @@ -25,7 +25,7 @@ use alvr_packets::{ }; use alvr_session::{ BodyTrackingSinkConfig, CodecType, ControllersEmulationMode, FrameSize, H264Profile, - OpenvrConfig, SessionConfig, SocketProtocol, + OpenvrConfig, RecenteringMode, SessionConfig, SocketProtocol, }; use alvr_sockets::{ CONTROL_PORT, KEEPALIVE_INTERVAL, KEEPALIVE_TIMEOUT, PeerType, ProtoControlSocket, @@ -1194,10 +1194,11 @@ fn connection_pipeline( if !initial_settings.headset.tracking_ref_only { let session_manager_lock = SESSION_MANAGER.read(); let config = &session_manager_lock.settings().headset; - ctx.tracking_manager.write().recenter( - config.position_recentering_mode, - config.rotation_recentering_mode, - ); + if !matches!(config.recentering_mode, RecenteringMode::Stage { .. }) { + ctx.tracking_manager + .write() + .recenter(&config.recentering_mode); + } let area = packet.unwrap_or(Vec2::new(2.0, 2.0)); let wh = area.x * area.y; diff --git a/alvr/server_core/src/tracking/mod.rs b/alvr/server_core/src/tracking/mod.rs index 87ec76d431..c019913121 100644 --- a/alvr/server_core/src/tracking/mod.rs +++ b/alvr/server_core/src/tracking/mod.rs @@ -13,23 +13,23 @@ use crate::{ input_mapping::ButtonMappingManager, }; use alvr_common::{ - BODY_CHEST_ID, BODY_HIPS_ID, BODY_LEFT_ELBOW_ID, BODY_LEFT_FOOT_ID, BODY_LEFT_KNEE_ID, - BODY_RIGHT_ELBOW_ID, BODY_RIGHT_FOOT_ID, BODY_RIGHT_KNEE_ID, ConnectionError, - DEVICE_ID_TO_PATH, DeviceMotion, HAND_LEFT_ID, HAND_RIGHT_ID, HEAD_ID, Pose, ViewParams, - glam::{Quat, Vec3}, + AngleSlidingWindowAverage, ConnectionError, DEVICE_ID_TO_PATH, DeviceMotion, Pose, + SlidingWindowAverage, ViewParams, + glam::{Quat, Vec2, Vec3}, + inputs as inp, parking_lot::Mutex, }; use alvr_events::{EventType, TrackingEvent}; use alvr_packets::TrackingData; use alvr_session::{ - BodyTrackingConfig, HeadsetConfig, PositionRecenteringMode, RotationRecenteringMode, Settings, + BodyTrackingConfig, HeadsetConfig, MarkerColocationConfig, RecenteringMode, Settings, VMCConfig, settings_schema::Switch, }; use alvr_sockets::StreamReceiver; use std::{ cmp::Ordering, collections::{HashMap, VecDeque}, - f32::consts::PI, + f32::consts::{FRAC_PI_2, FRAC_PI_4, PI}, sync::Arc, time::Duration, }; @@ -42,6 +42,12 @@ pub enum HandType { Right = 1, } +struct RecenteringMarker { + string: String, + average_angle: AngleSlidingWindowAverage, + average_position: SlidingWindowAverage, +} + // todo: Move this struct to Settings and use it for every tracked device #[derive(Default)] struct MotionConfig { @@ -55,6 +61,8 @@ pub struct TrackingManager { last_head_pose: Pose, // client's reference space inverse_recentering_origin: Pose, // client's reference space device_motions_history: HashMap>, + recentering_marker: Option, + markers: HashMap, hand_skeletons_history: [VecDeque<(Duration, [Pose; 26])>; 2], max_history_size: usize, } @@ -65,32 +73,30 @@ impl TrackingManager { last_head_pose: Pose::IDENTITY, inverse_recentering_origin: Pose::IDENTITY, device_motions_history: HashMap::new(), + recentering_marker: None, + markers: HashMap::new(), hand_skeletons_history: [VecDeque::new(), VecDeque::new()], max_history_size, } } - pub fn recenter( - &mut self, - position_recentering_mode: PositionRecenteringMode, - rotation_recentering_mode: RotationRecenteringMode, - ) { - let position = match position_recentering_mode { - PositionRecenteringMode::Disabled => Vec3::ZERO, - PositionRecenteringMode::LocalFloor => { + pub fn recenter(&mut self, recentering_mode: &RecenteringMode) { + let position = match recentering_mode { + RecenteringMode::Stage { .. } => Vec3::ZERO, + RecenteringMode::LocalFloor => { let mut pos = self.last_head_pose.position; pos.y = 0.0; pos } - PositionRecenteringMode::Local { view_height } => { - self.last_head_pose.position - Vec3::new(0.0, view_height, 0.0) + RecenteringMode::Local { view_height } | RecenteringMode::Tilted { view_height } => { + self.last_head_pose.position - Vec3::new(0.0, *view_height, 0.0) } }; - let orientation = match rotation_recentering_mode { - RotationRecenteringMode::Disabled => Quat::IDENTITY, - RotationRecenteringMode::Yaw => { + let orientation = match recentering_mode { + RecenteringMode::Stage { .. } => Quat::IDENTITY, + RecenteringMode::LocalFloor | RecenteringMode::Local { .. } => { let mut rot = self.last_head_pose.orientation; // extract yaw rotation rot.x = 0.0; @@ -99,7 +105,7 @@ impl TrackingManager { rot } - RotationRecenteringMode::Tilted => self.last_head_pose.orientation, + RecenteringMode::Tilted { .. } => self.last_head_pose.orientation, }; self.inverse_recentering_origin = Pose { @@ -109,6 +115,71 @@ impl TrackingManager { .inverse(); } + pub fn recenter_from_marker(&mut self, config: &MarkerColocationConfig) { + if let Some(marker_pose) = self.markers.get(&config.qr_code_string) { + // Detect if the marker is vertical or horizontal, and use two different + // robust methods to extract the recentering orientation. + let marker_z_axis = marker_pose.orientation * Vec3::Z; + let angle_from_y = Vec3::angle_between(marker_z_axis, Vec3::Y); + + let marker_y_angle = if (angle_from_y - FRAC_PI_2).abs() < FRAC_PI_4 { + // The marker is vertical + Vec2::new(marker_z_axis.x, marker_z_axis.z) + .normalize() + .angle_to(Vec2::Y) // (this Y is on the XZ plane -> Z) + } else { + let marker_x_axis = marker_pose.orientation * Vec3::X; + Vec2::new(marker_x_axis.x, marker_x_axis.z) + .normalize() + .angle_to(Vec2::X) + }; + let marker_floor_position = Vec2::new(marker_pose.position.x, marker_pose.position.z); + + self.recentering_marker + .take_if(|rm| rm.string != config.qr_code_string); + let recentering_marker = if let Some(rm) = &mut self.recentering_marker { + rm.average_angle.submit_sample(marker_y_angle); + rm.average_position.submit_sample(marker_floor_position); + rm + } else { + self.recentering_marker.insert(RecenteringMarker { + string: config.qr_code_string.clone(), + average_angle: AngleSlidingWindowAverage::new( + marker_y_angle, + self.max_history_size, + ), + average_position: SlidingWindowAverage::new( + marker_floor_position, + self.max_history_size, + ), + }) + }; + + let average_angle = + Quat::from_rotation_y(recentering_marker.average_angle.get_average()); + let position = { + let marker_offset_2d = Vec2::from_array(config.floor_offset); + + let offset_2d = + recentering_marker.average_position.get_average() - marker_offset_2d; + Vec3::new(offset_2d.x, 0.0, offset_2d.y) + }; + alvr_common::debug!( + "Recentering from marker. Angle: {average_angle}, Position: {position}" + ); + + let recentering_origin = Pose { + position, + orientation: Quat::from_rotation_y(recentering_marker.average_angle.get_average()), + }; + + self.inverse_recentering_origin = recentering_origin.inverse(); + } + + // In case the marker is not found, abort recentering, we still want to use + // the last marker recentering origin. + } + pub fn recenter_pose(&self, pose: Pose) -> Pose { self.inverse_recentering_origin * pose } @@ -125,21 +196,21 @@ impl TrackingManager { device_motions: &[(u64, DeviceMotion)], ) { let mut device_motion_configs = HashMap::new(); - device_motion_configs.insert(*HEAD_ID, MotionConfig::default()); + device_motion_configs.insert(*inp::HEAD_ID, MotionConfig::default()); device_motion_configs.extend([ - (*BODY_CHEST_ID, MotionConfig::default()), - (*BODY_HIPS_ID, MotionConfig::default()), - (*BODY_LEFT_ELBOW_ID, MotionConfig::default()), - (*BODY_RIGHT_ELBOW_ID, MotionConfig::default()), - (*BODY_LEFT_KNEE_ID, MotionConfig::default()), - (*BODY_LEFT_FOOT_ID, MotionConfig::default()), - (*BODY_RIGHT_KNEE_ID, MotionConfig::default()), - (*BODY_RIGHT_FOOT_ID, MotionConfig::default()), + (*inp::BODY_CHEST_ID, MotionConfig::default()), + (*inp::BODY_HIPS_ID, MotionConfig::default()), + (*inp::BODY_LEFT_ELBOW_ID, MotionConfig::default()), + (*inp::BODY_RIGHT_ELBOW_ID, MotionConfig::default()), + (*inp::BODY_LEFT_KNEE_ID, MotionConfig::default()), + (*inp::BODY_LEFT_FOOT_ID, MotionConfig::default()), + (*inp::BODY_RIGHT_KNEE_ID, MotionConfig::default()), + (*inp::BODY_RIGHT_FOOT_ID, MotionConfig::default()), ]); if let Switch::Enabled(controllers) = &headset_config.controllers { device_motion_configs.insert( - *HAND_LEFT_ID, + *inp::HAND_LEFT_ID, MotionConfig { pose_offset: Pose::IDENTITY, linear_velocity_cutoff: controllers.linear_velocity_cutoff, @@ -148,7 +219,7 @@ impl TrackingManager { ); device_motion_configs.insert( - *HAND_RIGHT_ID, + *inp::HAND_RIGHT_ID, MotionConfig { pose_offset: Pose::IDENTITY, linear_velocity_cutoff: controllers.linear_velocity_cutoff, @@ -158,7 +229,7 @@ impl TrackingManager { } for &(device_id, mut motion) in device_motions { - if device_id == *HEAD_ID { + if device_id == *inp::HEAD_ID { self.last_head_pose = motion.pose; } @@ -266,6 +337,10 @@ impl TrackingManager { params.pose = self.inverse_recentering_origin.inverse() * params.pose; } } + + fn report_markers(&mut self, markers: Vec<(String, Pose)>) { + self.markers = markers.into_iter().collect(); + } } pub fn tracking_loop( @@ -336,7 +411,8 @@ pub fn tracking_loop( .into_option() }; - let device_motion_keys = { + let device_motion_keys; + { let mut tracking_manager_lock = ctx.tracking_manager.write(); let session_manager_lock = SESSION_MANAGER.read(); let headset_config = &session_manager_lock.settings().headset; @@ -355,7 +431,7 @@ pub fn tracking_loop( .extend_from_slice(&body::extract_default_trackers(skeleton)); } - let device_motion_keys = tracking + device_motion_keys = tracking .device_motions .iter() .map(|(id, _)| *id) @@ -373,6 +449,17 @@ pub fn tracking_loop( &tracking.device_motions, ); + if !tracking.markers.is_empty() { + tracking_manager_lock.report_markers(tracking.markers); + + if let RecenteringMode::Stage { + marker_based_colocation: Switch::Enabled(config), + } = &headset_config.recentering_mode + { + tracking_manager_lock.recenter_from_marker(config); + }; + } + if let Some(skeleton) = tracking.hand_skeletons[0] { tracking_manager_lock.report_hand_skeleton(HandType::Left, timestamp, skeleton); } @@ -403,8 +490,6 @@ pub fn tracking_loop( face: tracking.face, }))) } - - device_motion_keys }; // Handle hand gestures @@ -416,36 +501,36 @@ pub fn tracking_loop( ) { let mut hand_gesture_manager_lock = hand_gesture_manager.lock(); - if !device_motion_keys.contains(&*HAND_LEFT_ID) + if !device_motion_keys.contains(&*inp::HAND_LEFT_ID) && let Some(hand_skeleton) = tracking.hand_skeletons[0] { ctx.events_sender .send(ServerCoreEvent::Buttons( hand_gestures::trigger_hand_gesture_actions( gestures_button_mapping_manager, - *HAND_LEFT_ID, + *inp::HAND_LEFT_ID, &hand_gesture_manager_lock.get_active_gestures( &hand_skeleton, gestures_config, - *HAND_LEFT_ID, + *inp::HAND_LEFT_ID, ), gestures_config.only_touch, ), )) .ok(); } - if !device_motion_keys.contains(&*HAND_RIGHT_ID) + if !device_motion_keys.contains(&*inp::HAND_RIGHT_ID) && let Some(hand_skeleton) = tracking.hand_skeletons[1] { ctx.events_sender .send(ServerCoreEvent::Buttons( hand_gestures::trigger_hand_gesture_actions( gestures_button_mapping_manager, - *HAND_RIGHT_ID, + *inp::HAND_RIGHT_ID, &hand_gesture_manager_lock.get_active_gestures( &hand_skeleton, gestures_config, - *HAND_RIGHT_ID, + *inp::HAND_RIGHT_ID, ), gestures_config.only_touch, ), diff --git a/alvr/session/src/settings.rs b/alvr/session/src/settings.rs index 95247e68e7..c2d324a3f0 100644 --- a/alvr/session/src/settings.rs +++ b/alvr/session/src/settings.rs @@ -1220,21 +1220,37 @@ Currently this cannot be reliably estimated automatically. The correct value sho pub button_mapping_config: AutomaticButtonMappingConfig, } -#[derive(SettingsSchema, Serialize, Deserialize, Clone, Copy)] -pub enum PositionRecenteringMode { - Disabled, +#[derive(SettingsSchema, Serialize, Deserialize, Clone)] +pub struct MarkerColocationConfig { + #[schema(strings(display_string = "QR Code string"))] + pub qr_code_string: String, + + #[schema(strings( + help = r"Offset coordinate on the floor between the marker and the playspace origin. +The height doesn't need to be measured" + ))] + #[schema(suffix = "m")] + pub floor_offset: [f32; 2], +} + +#[derive(SettingsSchema, Serialize, Deserialize, Clone)] +pub enum RecenteringMode { + Stage { + #[schema(strings( + help = "Use a QR code to synchronize the playspace origin between players.", + notice = "Print at https://www.qr-code-generator.com" + ))] + marker_based_colocation: Switch, + }, LocalFloor, Local { #[schema(gui(slider(min = 0.0, max = 3.0)), suffix = "m")] view_height: f32, }, -} - -#[derive(SettingsSchema, Serialize, Deserialize, Clone, Copy)] -pub enum RotationRecenteringMode { - Disabled, - Yaw, - Tilted, + Tilted { + #[schema(gui(slider(min = 0.0, max = 3.0)), suffix = "m")] + view_height: f32, + }, } #[derive(SettingsSchema, Serialize, Deserialize, Clone, Copy)] @@ -1253,20 +1269,13 @@ This will be configurable in the future." #[derive(SettingsSchema, Serialize, Deserialize, Clone)] pub struct HeadsetConfig { #[schema(strings( - help = r#"Disabled: the playspace origin is determined by the room-scale guardian setup. -Local floor: the origin is on the floor and resets when long pressing the oculus button. -Local: the origin resets when long pressing the oculus button, and is calculated as an offset from the current head position."# - ))] - #[schema(flag = "real-time")] - pub position_recentering_mode: PositionRecenteringMode, - - #[schema(strings( - help = r#"Disabled: the playspace orientation is determined by the room-scale guardian setup. -Yaw: the forward direction is reset when long pressing the oculus button. -Tilted: the world gets tilted when long pressing the oculus button. This is useful for using VR while laying down."# + help = r"Stage: the playspace origin is determined by the room-scale guardian setup. Can be synchnonized using a marker. +Local floor: the origin is on the floor and resets when long pressing the recentering button. +Local: the origin resets when long pressing the recentering button, and is calculated as an offset from the current head position. +Tilted: the world gets tilted when long pressing the recentering button. This is useful for using VR while laying down." ))] #[schema(flag = "real-time")] - pub rotation_recentering_mode: RotationRecenteringMode, + pub recentering_mode: RecenteringMode, #[schema(flag = "steamvr-restart")] pub controllers: Switch, @@ -2095,12 +2104,22 @@ pub fn session_settings_default() -> SettingsDefault { }, }, }, - position_recentering_mode: PositionRecenteringModeDefault { - Local: PositionRecenteringModeLocalDefault { view_height: 1.5 }, - variant: PositionRecenteringModeDefaultVariant::LocalFloor, - }, - rotation_recentering_mode: RotationRecenteringModeDefault { - variant: RotationRecenteringModeDefaultVariant::Yaw, + recentering_mode: RecenteringModeDefault { + Stage: RecenteringModeStageDefault { + marker_based_colocation: SwitchDefault { + enabled: false, + content: MarkerColocationConfigDefault { + qr_code_string: String::new(), + floor_offset: ArrayDefault { + gui_collapsed: false, + content: [0.0, 0.0], + }, + }, + }, + }, + Local: RecenteringModeLocalDefault { view_height: 1.5 }, + Tilted: RecenteringModeTiltedDefault { view_height: 1.5 }, + variant: RecenteringModeDefaultVariant::LocalFloor, }, max_prediction_ms: 100, }, From bf0e6886a696a901094f499a7b7ce2cd77286085 Mon Sep 17 00:00:00 2001 From: Riccardo Zaglia Date: Sun, 7 Dec 2025 22:21:44 +1000 Subject: [PATCH 2/2] Fix impl + update every frame --- .../spatial_marker_tracking.rs | 390 +++++++++++------- alvr/client_openxr/src/interaction.rs | 29 +- alvr/client_openxr/src/lib.rs | 7 +- alvr/packets/src/lib.rs | 11 +- alvr/server_core/src/connection.rs | 10 +- alvr/server_core/src/tracking/mod.rs | 9 +- alvr/session/src/settings.rs | 64 +-- 7 files changed, 305 insertions(+), 215 deletions(-) diff --git a/alvr/client_openxr/src/extra_extensions/spatial_marker_tracking.rs b/alvr/client_openxr/src/extra_extensions/spatial_marker_tracking.rs index 76d91b2c90..e9e0e9cb6a 100644 --- a/alvr/client_openxr/src/extra_extensions/spatial_marker_tracking.rs +++ b/alvr/client_openxr/src/extra_extensions/spatial_marker_tracking.rs @@ -1,37 +1,52 @@ -use alvr_common::RelaxedAtomic; use openxr::{ self as xr, AsHandle, raw, sys::{self, Handle}, }; use std::{ + collections::{HashMap, HashSet}, ffi::{CStr, c_char}, - ptr, thread, + ptr, time::{Duration, Instant}, }; -const CAPABILITY: sys::SpatialCapabilityEXT = sys::SpatialCapabilityEXT::MARKER_TRACKING_QR_CODE; +const SPATIAL_CAPABILITY: sys::SpatialCapabilityEXT = + sys::SpatialCapabilityEXT::MARKER_TRACKING_QR_CODE; // Note: The Meta implementation is currently bugged and the buffer capacity cannot be changed once // the fist call of query_spatial_component_data is made. const MAX_MARKERS_COUNT: usize = 32; const DISCOVERY_TIMEOUT: Duration = Duration::from_secs(1); -pub struct QRCodesSpatialContext { - instance: xr::Instance, - spatial_entity_fns: raw::SpatialEntityEXT, - inner: sys::SpatialContextEXT, - enabled_components: Vec, +struct SpatialContextReadyData { + handle: sys::SpatialContextEXT, + discovery_snapshot_future: Option, + spatial_entities: HashMap, entity_ids: [sys::SpatialEntityIdEXT; MAX_MARKERS_COUNT], entity_states: [sys::SpatialEntityTrackingStateEXT; MAX_MARKERS_COUNT], bounded_2d_arr: [sys::SpatialBounded2DDataEXT; MAX_MARKERS_COUNT], marker_arr: [sys::SpatialMarkerDataEXT; MAX_MARKERS_COUNT], string_buffer: [c_char; 256], - discovery_enabled: RelaxedAtomic, +} + +enum SpatialContextState { + Creating(sys::FutureEXT), + Ready(Box), +} + +pub struct QRCodesSpatialContext { + session: xr::Session, + spatial_entity_fns: raw::SpatialEntityEXT, + enabled_components: Vec, + codes_to_track: HashSet, + context_state: SpatialContextState, discovery_timeout_deadline: Instant, - snapshot_future: Option, } impl QRCodesSpatialContext { - pub fn new(session: &xr::Session, initial_discovery_state: bool) -> xr::Result { + // If ids_to_track is empty, track all markers. This is used for the lobby + pub fn new( + session: &xr::Session, + strings_to_track: HashSet, + ) -> xr::Result { let spatial_entity_fns = session .instance() .exts() @@ -45,7 +60,6 @@ impl QRCodesSpatialContext { { return Err(sys::Result::ERROR_EXTENSION_NOT_PRESENT); } - alvr_common::error!("MarkerSpatialContext 1"); let enabled_components = vec![ sys::SpatialComponentTypeEXT::BOUNDED_2D, @@ -55,7 +69,7 @@ impl QRCodesSpatialContext { let qr_code_capability_configuration = sys::SpatialCapabilityConfigurationQrCodeEXT { ty: sys::SpatialCapabilityConfigurationQrCodeEXT::TYPE, next: ptr::null(), - capability: CAPABILITY, + capability: SPATIAL_CAPABILITY, enabled_component_count: enabled_components.len() as u32, enabled_components: enabled_components.as_ptr(), }; @@ -77,121 +91,170 @@ impl QRCodesSpatialContext { ))?; } - loop { - if super::check_future(session.instance(), create_context_future)? { - break; - } - thread::sleep(std::time::Duration::from_millis(1)); - } - - let completion = unsafe { - let mut completion = sys::CreateSpatialContextCompletionEXT::out(ptr::null_mut()); - super::xr_res((spatial_entity_fns.create_spatial_context_complete)( - session.as_handle(), - create_context_future, - completion.as_mut_ptr(), - ))?; - completion.assume_init() - }; - if completion.future_result != sys::Result::SUCCESS { - return Err(completion.future_result); - } - Ok(QRCodesSpatialContext { - instance: session.instance().clone(), + session: session.clone(), spatial_entity_fns, - inner: completion.spatial_context, enabled_components, - entity_ids: [xr::sys::SpatialEntityIdEXT::default(); MAX_MARKERS_COUNT], - entity_states: [xr::sys::SpatialEntityTrackingStateEXT::STOPPED; MAX_MARKERS_COUNT], - bounded_2d_arr: [xr::sys::SpatialBounded2DDataEXT::default(); MAX_MARKERS_COUNT], - marker_arr: [sys::SpatialMarkerDataEXT { - capability: CAPABILITY, - marker_id: 0, - data: sys::SpatialBufferEXT { - buffer_id: sys::SpatialBufferIdEXT::NULL, - buffer_type: sys::SpatialBufferTypeEXT::UNKNOWN, - }, - }; MAX_MARKERS_COUNT], - string_buffer: [0; 256], - discovery_enabled: RelaxedAtomic::new(initial_discovery_state), + codes_to_track: strings_to_track, + context_state: SpatialContextState::Creating(create_context_future), discovery_timeout_deadline: Instant::now(), - snapshot_future: None, }) } - pub fn set_discovery_enabled(&self, enabled: bool) { - self.discovery_enabled.set(enabled); - } - pub fn poll( &mut self, base_space: &xr::Space, time: xr::Time, ) -> xr::Result>> { - let new = Instant::now(); + let now = Instant::now(); + + let spatial_context_data = match &mut self.context_state { + SpatialContextState::Ready(data) => data, + + SpatialContextState::Creating(future) => { + if !super::check_future(self.session.instance(), *future)? { + return Ok(None); + } + + let completion = unsafe { + let mut completion = + sys::CreateSpatialContextCompletionEXT::out(ptr::null_mut()); + super::xr_res((self.spatial_entity_fns.create_spatial_context_complete)( + self.session.as_handle(), + *future, + completion.as_mut_ptr(), + ))?; + completion.assume_init() + }; + if completion.future_result != sys::Result::SUCCESS { + return Err(completion.future_result); + } + + self.context_state = + SpatialContextState::Ready(Box::new(SpatialContextReadyData { + handle: completion.spatial_context, + discovery_snapshot_future: None, + spatial_entities: HashMap::new(), + entity_ids: [sys::SpatialEntityIdEXT::NULL; MAX_MARKERS_COUNT], + entity_states: [sys::SpatialEntityTrackingStateEXT::STOPPED; + MAX_MARKERS_COUNT], + bounded_2d_arr: [sys::SpatialBounded2DDataEXT { + center: xr::Posef::IDENTITY, + extents: xr::Extent2Df::default(), + }; MAX_MARKERS_COUNT], + marker_arr: [sys::SpatialMarkerDataEXT { + capability: SPATIAL_CAPABILITY, + marker_id: 0, + data: sys::SpatialBufferEXT { + buffer_id: sys::SpatialBufferIdEXT::NULL, + buffer_type: sys::SpatialBufferTypeEXT::STRING, + }, + }; MAX_MARKERS_COUNT], + string_buffer: [0; 256], + })); + + // Custom enums don't have the method insert(), so we cannot get back a mutable + // reference directly. + return Ok(None); + } + }; - if self.snapshot_future.is_none() - && self.discovery_enabled.value() - && new > self.discovery_timeout_deadline + // Try getting a discovery snapshot or submit query for one + let snapshot_handle = if let &Some(future) = &spatial_context_data.discovery_snapshot_future { - let snapshot_create_info = sys::SpatialDiscoverySnapshotCreateInfoEXT { - ty: sys::SpatialDiscoverySnapshotCreateInfoEXT::TYPE, - next: ptr::null(), - component_type_count: self.enabled_components.len() as u32, - component_types: self.enabled_components.as_ptr(), - }; - - let mut create_snapshot_future: sys::FutureEXT = 0; - unsafe { - super::xr_res((self - .spatial_entity_fns - .create_spatial_discovery_snapshot_async)( - self.inner, - &snapshot_create_info, - &mut create_snapshot_future, - ))?; + if super::check_future(self.session.instance(), future)? { + spatial_context_data.discovery_snapshot_future = None; + + let completion_info = sys::CreateSpatialDiscoverySnapshotCompletionInfoEXT { + ty: sys::CreateSpatialDiscoverySnapshotCompletionInfoEXT::TYPE, + next: ptr::null(), + base_space: base_space.as_handle(), + time, + future, + }; + + let completion = unsafe { + let mut completion = + sys::CreateSpatialDiscoverySnapshotCompletionEXT::out(ptr::null_mut()); + super::xr_res((self + .spatial_entity_fns + .create_spatial_discovery_snapshot_complete)( + spatial_context_data.handle, + &completion_info, + completion.as_mut_ptr(), + ))?; + completion.assume_init() + }; + if completion.future_result != sys::Result::SUCCESS { + return Err(completion.future_result); + } + + Some(completion.snapshot) + } else { + None + } + } else { + if now > self.discovery_timeout_deadline { + let snapshot_create_info = sys::SpatialDiscoverySnapshotCreateInfoEXT { + ty: sys::SpatialDiscoverySnapshotCreateInfoEXT::TYPE, + next: ptr::null(), + component_type_count: self.enabled_components.len() as u32, + component_types: self.enabled_components.as_ptr(), + }; + + let mut create_snapshot_future: sys::FutureEXT = 0; + unsafe { + super::xr_res((self + .spatial_entity_fns + .create_spatial_discovery_snapshot_async)( + spatial_context_data.handle, + &snapshot_create_info, + &mut create_snapshot_future, + ))?; + } + + spatial_context_data.discovery_snapshot_future = Some(create_snapshot_future); + + self.discovery_timeout_deadline = now + DISCOVERY_TIMEOUT; } - self.snapshot_future = Some(create_snapshot_future); - self.discovery_timeout_deadline = new + DISCOVERY_TIMEOUT; - }; - - let &Some(future) = &self.snapshot_future else { - return Ok(None); + None }; - // Return if the future is not completed - if !super::check_future(&self.instance, future)? { - return Ok(None); - } + // Get snapshot for already discovered entities if no discovery happened + // Note: We are not generating a snapshot for the already discovered entities if we got a + // discovery snapshot. This is because the + let snapshot_handle = if let Some(handle) = snapshot_handle { + handle + } else { + let entities = spatial_context_data + .spatial_entities + .values() + .map(|(_, entity)| *entity) + .collect::>(); + let create_info = sys::SpatialUpdateSnapshotCreateInfoEXT { + ty: sys::SpatialUpdateSnapshotCreateInfoEXT::TYPE, + next: ptr::null(), + entity_count: entities.len() as u32, + entities: entities.as_ptr(), + component_type_count: 0, + component_types: ptr::null(), + base_space: base_space.as_handle(), + time, + }; - self.snapshot_future = None; + let mut snapshot_handle = sys::SpatialSnapshotEXT::NULL; + unsafe { + (self.spatial_entity_fns.create_spatial_update_snapshot)( + spatial_context_data.handle, + &create_info, + &mut snapshot_handle, + ); + } - let completion_info = sys::CreateSpatialDiscoverySnapshotCompletionInfoEXT { - ty: sys::CreateSpatialDiscoverySnapshotCompletionInfoEXT::TYPE, - next: ptr::null(), - base_space: base_space.as_handle(), - time, - future, + snapshot_handle }; - let completion = unsafe { - let mut completion = - sys::CreateSpatialDiscoverySnapshotCompletionEXT::out(ptr::null_mut()); - super::xr_res((self - .spatial_entity_fns - .create_spatial_discovery_snapshot_complete)( - self.inner, - &completion_info, - completion.as_mut_ptr(), - ))?; - completion.assume_init() - }; - if completion.future_result != sys::Result::SUCCESS { - return Err(completion.future_result); - } - let query_contition = sys::SpatialComponentDataQueryConditionEXT { ty: sys::SpatialComponentDataQueryConditionEXT::TYPE, next: ptr::null(), @@ -204,14 +267,14 @@ impl QRCodesSpatialContext { next: ptr::null_mut(), entity_id_capacity_input: MAX_MARKERS_COUNT as u32, entity_id_count_output: 0, - entity_ids: self.entity_ids.as_mut_ptr(), + entity_ids: spatial_context_data.entity_ids.as_mut_ptr(), entity_state_capacity_input: MAX_MARKERS_COUNT as u32, entity_state_count_output: 0, - entity_states: self.entity_states.as_mut_ptr(), + entity_states: spatial_context_data.entity_states.as_mut_ptr(), }; unsafe { super::xr_res((self.spatial_entity_fns.query_spatial_component_data)( - completion.snapshot, + snapshot_handle, &query_contition, &mut query_result, ))?; @@ -222,7 +285,7 @@ impl QRCodesSpatialContext { ty: sys::SpatialComponentBounded2DListEXT::TYPE, next: ptr::null_mut(), bound_count: marker_count, - bounds: self.bounded_2d_arr.as_mut_ptr(), + bounds: spatial_context_data.bounded_2d_arr.as_mut_ptr(), }; query_result.next = (&raw mut bounded_2d_list).cast(); @@ -230,13 +293,13 @@ impl QRCodesSpatialContext { ty: sys::SpatialComponentMarkerListEXT::TYPE, next: ptr::null_mut(), marker_count, - markers: self.marker_arr.as_mut_ptr(), + markers: spatial_context_data.marker_arr.as_mut_ptr(), }; bounded_2d_list.next = (&raw mut marker_list).cast(); unsafe { super::xr_res((self.spatial_entity_fns.query_spatial_component_data)( - completion.snapshot, + snapshot_handle, &query_contition, &mut query_result, ))?; @@ -244,56 +307,101 @@ impl QRCodesSpatialContext { let mut out_markers = vec![]; for idx in 0..query_result.entity_id_count_output as usize { - if self.entity_states[idx] != sys::SpatialEntityTrackingStateEXT::TRACKING - || self.marker_arr[idx].capability != CAPABILITY - || self.marker_arr[idx].data.buffer_id == sys::SpatialBufferIdEXT::NULL - || self.marker_arr[idx].data.buffer_type != sys::SpatialBufferTypeEXT::STRING + if spatial_context_data.entity_states[idx] + != sys::SpatialEntityTrackingStateEXT::TRACKING + || spatial_context_data.marker_arr[idx].capability != SPATIAL_CAPABILITY + || spatial_context_data.marker_arr[idx].data.buffer_id + == sys::SpatialBufferIdEXT::NULL + || spatial_context_data.marker_arr[idx].data.buffer_type + != sys::SpatialBufferTypeEXT::STRING { alvr_common::debug!( "Parsing marker failed! {:?} {:?} {:?} {:?}", - self.entity_states[idx], - self.marker_arr[idx].capability, - self.marker_arr[idx].data.buffer_id, - self.marker_arr[idx].data.buffer_type + spatial_context_data.entity_states[idx], + spatial_context_data.marker_arr[idx].capability, + spatial_context_data.marker_arr[idx].data.buffer_id, + spatial_context_data.marker_arr[idx].data.buffer_type ); continue; } - let get_info = sys::SpatialBufferGetInfoEXT { - ty: sys::SpatialBufferGetInfoEXT::TYPE, - next: ptr::null(), - buffer_id: self.marker_arr[idx].data.buffer_id, - }; + let entity_id = spatial_context_data.entity_ids[idx]; - let string = unsafe { - let mut _string_lenght = 0; - super::xr_res((self.spatial_entity_fns.get_spatial_buffer_string)( - completion.snapshot, - &get_info, - self.string_buffer.len() as u32, - &mut _string_lenght, - self.string_buffer.as_mut_ptr(), - ))?; - - CStr::from_ptr(self.string_buffer.as_ptr()) + let string = if let Some((string, _)) = spatial_context_data + .spatial_entities + .get(&spatial_context_data.entity_ids[idx]) + { + string.clone() + } else { + let get_info = sys::SpatialBufferGetInfoEXT { + ty: sys::SpatialBufferGetInfoEXT::TYPE, + next: ptr::null(), + buffer_id: spatial_context_data.marker_arr[idx].data.buffer_id, + }; + + unsafe { + let mut _string_lenght = 0; + super::xr_res((self.spatial_entity_fns.get_spatial_buffer_string)( + snapshot_handle, + &get_info, + spatial_context_data.string_buffer.len() as u32, + &mut _string_lenght, + spatial_context_data.string_buffer.as_mut_ptr(), + ))?; + }; + + let string = unsafe { CStr::from_ptr(spatial_context_data.string_buffer.as_ptr()) } .to_str() .map_err(|_| sys::Result::ERROR_SPATIAL_BUFFER_ID_INVALID_EXT)? - .to_owned() + .to_owned(); + + if self.codes_to_track.is_empty() || self.codes_to_track.contains(&string) { + let create_info = sys::SpatialEntityFromIdCreateInfoEXT { + ty: sys::SpatialEntityFromIdCreateInfoEXT::TYPE, + next: ptr::null(), + entity_id, + }; + + let mut spatial_entity = sys::SpatialEntityEXT::NULL; + unsafe { + super::xr_res((self.spatial_entity_fns.create_spatial_entity_from_id)( + spatial_context_data.handle, + &create_info, + &mut spatial_entity, + ))?; + }; + + spatial_context_data + .spatial_entities + .insert(entity_id, (string.clone(), spatial_entity)); + } + + string }; - let pose = self.bounded_2d_arr[idx].center; - + let pose = spatial_context_data.bounded_2d_arr[idx].center; out_markers.push((string, pose)); } + unsafe { + super::xr_res((self.spatial_entity_fns.destroy_spatial_snapshot)( + snapshot_handle, + ))?; + } + Ok(Some(out_markers)) } } impl Drop for QRCodesSpatialContext { fn drop(&mut self) { - unsafe { - (self.spatial_entity_fns.destroy_spatial_context)(self.inner); + if let SpatialContextState::Ready(data) = &self.context_state { + unsafe { + super::xr_res((self.spatial_entity_fns.destroy_spatial_context)( + data.handle, + )) + .ok(); + } } } } diff --git a/alvr/client_openxr/src/interaction.rs b/alvr/client_openxr/src/interaction.rs index 28e14067b5..a35bff7636 100644 --- a/alvr/client_openxr/src/interaction.rs +++ b/alvr/client_openxr/src/interaction.rs @@ -8,14 +8,11 @@ use crate::{ }; use alvr_common::{ glam::{Quat, Vec3}, - settings_schema::Switch, *, }; use alvr_graphics::HandData; use alvr_packets::{ButtonEntry, ButtonValue, FaceData, FaceExpressions, StreamConfig}; -use alvr_session::{ - BodyTrackingBDConfig, BodyTrackingSourcesConfig, FaceTrackingSourcesConfig, RecenteringMode, -}; +use alvr_session::{BodyTrackingBDConfig, BodyTrackingSourcesConfig, FaceTrackingSourcesConfig}; use openxr as xr; use std::{ collections::{HashMap, HashSet}, @@ -148,7 +145,7 @@ pub struct InteractionSourcesConfig { pub face_tracking: Option, pub body_tracking: Option, pub prefers_multimodal_input: bool, - pub initial_marker_discovery_state: bool, + pub markers_to_track: Option>, } impl InteractionSourcesConfig { @@ -172,12 +169,12 @@ impl InteractionSourcesConfig { .multimodal_tracking .as_option() .is_some_and(|c| c.enabled), - initial_marker_discovery_state: matches!( - config.settings.headset.recentering_mode, - RecenteringMode::Stage { - marker_based_colocation: Switch::Enabled(_) - } - ), + markers_to_track: config + .settings + .headset + .marker_colocation + .as_option() + .map(|c| HashSet::from_iter([c.qr_code_string.clone()])), } } } @@ -697,10 +694,12 @@ impl InteractionContext { } } - self.marker_spatial_context = check_ext_object( - "MarkerSpatialContext", - QRCodesSpatialContext::new(&self.xr_session, config.initial_marker_discovery_state), - ); + self.marker_spatial_context = config.markers_to_track.as_ref().and_then(|strings| { + check_ext_object( + "QRCodesSpatialContext", + QRCodesSpatialContext::new(&self.xr_session, strings.clone()), + ) + }); } } diff --git a/alvr/client_openxr/src/lib.rs b/alvr/client_openxr/src/lib.rs index cf8d5bc415..d5d20349af 100644 --- a/alvr/client_openxr/src/lib.rs +++ b/alvr/client_openxr/src/lib.rs @@ -26,7 +26,7 @@ use interaction::{InteractionContext, InteractionSourcesConfig}; use lobby::Lobby; use openxr as xr; use passthrough::PassthroughLayer; -use std::{ffi::CStr, path::Path, rc::Rc, sync::Arc, thread, time::Duration}; +use std::{collections::HashSet, ffi::CStr, path::Path, rc::Rc, sync::Arc, thread, time::Duration}; use stream::StreamContext; fn from_xr_vec3(v: xr::Vector3f) -> Vec3 { @@ -357,7 +357,7 @@ pub fn entry_point() { face_tracking: None, body_tracking: lobby_body_tracking_config, prefers_multimodal_input: true, - initial_marker_discovery_state: true, + markers_to_track: Some(HashSet::new()), }; interaction_context .write() @@ -510,9 +510,6 @@ pub fn entry_point() { } else if config.passthrough.is_none() && passthrough_layer.is_some() { passthrough_layer = None; } - if let Some(context) = &interaction_context.read().marker_spatial_context { - context.set_discovery_enabled(config.marker_colocation); - } if let Some(stream) = &mut stream_context { stream.update_real_time_config(&config); diff --git a/alvr/packets/src/lib.rs b/alvr/packets/src/lib.rs index c8e358cf0d..959a909272 100644 --- a/alvr/packets/src/lib.rs +++ b/alvr/packets/src/lib.rs @@ -3,11 +3,9 @@ use alvr_common::{ anyhow::Result, glam::{Quat, UVec2, Vec2}, semver::Version, - settings_schema::Switch, }; use alvr_session::{ - ClientsidePostProcessingConfig, CodecType, PassthroughMode, RecenteringMode, SessionConfig, - Settings, + ClientsidePostProcessingConfig, CodecType, PassthroughMode, SessionConfig, Settings, }; use serde::{Deserialize, Serialize}; use serde_json as json; @@ -330,7 +328,6 @@ pub enum FirewallRulesAction { pub struct RealTimeConfig { pub passthrough: Option, pub clientside_post_processing: Option, - pub marker_colocation: bool, pub ext_str: String, } @@ -343,12 +340,6 @@ impl RealTimeConfig { .clientside_post_processing .clone() .into_option(), - marker_colocation: matches!( - settings.headset.recentering_mode, - RecenteringMode::Stage { - marker_based_colocation: Switch::Enabled(_) - } - ), ext_str: String::new(), // No extensions for now } } diff --git a/alvr/server_core/src/connection.rs b/alvr/server_core/src/connection.rs index 7d7ab07a70..bf06b44b63 100644 --- a/alvr/server_core/src/connection.rs +++ b/alvr/server_core/src/connection.rs @@ -25,7 +25,7 @@ use alvr_packets::{ }; use alvr_session::{ BodyTrackingSinkConfig, CodecType, ControllersEmulationMode, FrameSize, H264Profile, - OpenvrConfig, RecenteringMode, SessionConfig, SocketProtocol, + OpenvrConfig, SessionConfig, SocketProtocol, }; use alvr_sockets::{ CONTROL_PORT, KEEPALIVE_INTERVAL, KEEPALIVE_TIMEOUT, PeerType, ProtoControlSocket, @@ -1194,11 +1194,9 @@ fn connection_pipeline( if !initial_settings.headset.tracking_ref_only { let session_manager_lock = SESSION_MANAGER.read(); let config = &session_manager_lock.settings().headset; - if !matches!(config.recentering_mode, RecenteringMode::Stage { .. }) { - ctx.tracking_manager - .write() - .recenter(&config.recentering_mode); - } + ctx.tracking_manager + .write() + .recenter(&config.recentering_mode); let area = packet.unwrap_or(Vec2::new(2.0, 2.0)); let wh = area.x * area.y; diff --git a/alvr/server_core/src/tracking/mod.rs b/alvr/server_core/src/tracking/mod.rs index c019913121..49bda4df4f 100644 --- a/alvr/server_core/src/tracking/mod.rs +++ b/alvr/server_core/src/tracking/mod.rs @@ -82,7 +82,7 @@ impl TrackingManager { pub fn recenter(&mut self, recentering_mode: &RecenteringMode) { let position = match recentering_mode { - RecenteringMode::Stage { .. } => Vec3::ZERO, + RecenteringMode::Stage => Vec3::ZERO, RecenteringMode::LocalFloor => { let mut pos = self.last_head_pose.position; pos.y = 0.0; @@ -95,7 +95,7 @@ impl TrackingManager { }; let orientation = match recentering_mode { - RecenteringMode::Stage { .. } => Quat::IDENTITY, + RecenteringMode::Stage => Quat::IDENTITY, RecenteringMode::LocalFloor | RecenteringMode::Local { .. } => { let mut rot = self.last_head_pose.orientation; // extract yaw rotation @@ -452,10 +452,7 @@ pub fn tracking_loop( if !tracking.markers.is_empty() { tracking_manager_lock.report_markers(tracking.markers); - if let RecenteringMode::Stage { - marker_based_colocation: Switch::Enabled(config), - } = &headset_config.recentering_mode - { + if let Some(config) = headset_config.marker_colocation.as_option() { tracking_manager_lock.recenter_from_marker(config); }; } diff --git a/alvr/session/src/settings.rs b/alvr/session/src/settings.rs index c2d324a3f0..7fc086d643 100644 --- a/alvr/session/src/settings.rs +++ b/alvr/session/src/settings.rs @@ -1220,28 +1220,9 @@ Currently this cannot be reliably estimated automatically. The correct value sho pub button_mapping_config: AutomaticButtonMappingConfig, } -#[derive(SettingsSchema, Serialize, Deserialize, Clone)] -pub struct MarkerColocationConfig { - #[schema(strings(display_string = "QR Code string"))] - pub qr_code_string: String, - - #[schema(strings( - help = r"Offset coordinate on the floor between the marker and the playspace origin. -The height doesn't need to be measured" - ))] - #[schema(suffix = "m")] - pub floor_offset: [f32; 2], -} - #[derive(SettingsSchema, Serialize, Deserialize, Clone)] pub enum RecenteringMode { - Stage { - #[schema(strings( - help = "Use a QR code to synchronize the playspace origin between players.", - notice = "Print at https://www.qr-code-generator.com" - ))] - marker_based_colocation: Switch, - }, + Stage, LocalFloor, Local { #[schema(gui(slider(min = 0.0, max = 3.0)), suffix = "m")] @@ -1253,6 +1234,20 @@ pub enum RecenteringMode { }, } +#[derive(SettingsSchema, Serialize, Deserialize, Clone)] +pub struct MarkerColocationConfig { + #[schema(strings(display_string = "QR Code string"))] + pub qr_code_string: String, + + #[schema(flag = "real-time")] + #[schema(strings( + help = r"Offset coordinate on the floor between the marker and the playspace origin. +The height of the marker doesn't need to be measured" + ))] + #[schema(suffix = "m")] + pub floor_offset: [f32; 2], +} + #[derive(SettingsSchema, Serialize, Deserialize, Clone, Copy)] pub struct MultimodalTracking { pub enabled: bool, @@ -1277,6 +1272,13 @@ Tilted: the world gets tilted when long pressing the recentering button. This is #[schema(flag = "real-time")] pub recentering_mode: RecenteringMode, + #[schema(strings( + string = "Marker-based co-location", + help = "Use a QR code to synchronize the playspace origin between players.", + notice = "Print at https://www.qr-code-generator.com" + ))] + pub marker_colocation: Switch, + #[schema(flag = "steamvr-restart")] pub controllers: Switch, @@ -2105,22 +2107,20 @@ pub fn session_settings_default() -> SettingsDefault { }, }, recentering_mode: RecenteringModeDefault { - Stage: RecenteringModeStageDefault { - marker_based_colocation: SwitchDefault { - enabled: false, - content: MarkerColocationConfigDefault { - qr_code_string: String::new(), - floor_offset: ArrayDefault { - gui_collapsed: false, - content: [0.0, 0.0], - }, - }, - }, - }, Local: RecenteringModeLocalDefault { view_height: 1.5 }, Tilted: RecenteringModeTiltedDefault { view_height: 1.5 }, variant: RecenteringModeDefaultVariant::LocalFloor, }, + marker_colocation: SwitchDefault { + enabled: false, + content: MarkerColocationConfigDefault { + qr_code_string: String::new(), + floor_offset: ArrayDefault { + gui_collapsed: false, + content: [0.0, 0.0], + }, + }, + }, max_prediction_ms: 100, }, connection: ConnectionConfigDefault {