WIP: Porting the server to Bevy #31
17
.github/workflows/build.yml
vendored
17
.github/workflows/build.yml
vendored
@@ -3,7 +3,7 @@ name: Build
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- '*'
|
- "*"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_and_package:
|
build_and_package:
|
||||||
@@ -11,13 +11,13 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install runtime dependencies
|
- name: Install runtime dependencies
|
||||||
run: sudo apt install -y --no-install-recommends libxkbcommon-dev libstdc++6 libopenxr-dev libx11-dev libxfixes-dev libgl1-mesa-dev libegl1-mesa-dev libgbm-dev libfontconfig-dev libjsoncpp-dev libxcb1-dev libglx-dev libxcb-glx0-dev libdrm-dev libwayland-dev libfreetype-dev libpng-dev
|
run: sudo apt install -y --no-install-recommends libxkbcommon-dev libstdc++6 libopenxr-dev libx11-dev libxfixes-dev libgl1-mesa-dev libegl1-mesa-dev libgbm-dev libfontconfig-dev libjsoncpp-dev libxcb1-dev libglx-dev libxcb-glx0-dev libdrm-dev libwayland-dev libfreetype-dev libpng-dev
|
||||||
|
|
||||||
- name: Install build dependencies
|
- name: Install build dependencies
|
||||||
run: sudo apt install -y --no-install-recommends cmake ninja-build
|
run: sudo apt install -y --no-install-recommends cmake ninja-build libfuse2
|
||||||
|
|
||||||
- name: Set up Rust
|
- name: Set up Rust
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
@@ -27,12 +27,11 @@ jobs:
|
|||||||
- name: Build server
|
- name: Build server
|
||||||
run: cargo build --release
|
run: cargo build --release
|
||||||
|
|
||||||
|
|
||||||
- name: Install appimagetool
|
- name: Install appimagetool
|
||||||
run: |
|
run: |
|
||||||
wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-$(uname -m).AppImage -O /usr/local/bin/appimagetool; \
|
wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-$(uname -m).AppImage -O /usr/local/bin/appimagetool; \
|
||||||
chmod +x /usr/local/bin/appimagetool; \
|
chmod +x /usr/local/bin/appimagetool; \
|
||||||
sed -i 's|AI\x02|\x00\x00\x00|' /usr/local/bin/appimagetool
|
sed -i 's|AI\x02|\x00\x00\x00|' /usr/local/bin/appimagetool
|
||||||
- name: Install cargo-appimage
|
- name: Install cargo-appimage
|
||||||
run: cargo install cargo-appimage
|
run: cargo install cargo-appimage
|
||||||
|
|
||||||
@@ -40,7 +39,7 @@ jobs:
|
|||||||
run: cargo appimage
|
run: cargo appimage
|
||||||
|
|
||||||
- name: Upload AppImage
|
- name: Upload AppImage
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: appimage
|
name: appimage
|
||||||
path: '*.AppImage'
|
path: "target/appimage/stardust-xr-server.AppImage"
|
||||||
|
|||||||
4432
Cargo.lock
generated
4432
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
73
Cargo.toml
73
Cargo.toml
@@ -9,22 +9,35 @@ license = "GPLv2"
|
|||||||
repository = "https://github.com/StardustXR/stardust-xr-server/"
|
repository = "https://github.com/StardustXR/stardust-xr-server/"
|
||||||
homepage = "https://stardustxr.org"
|
homepage = "https://stardustxr.org"
|
||||||
|
|
||||||
|
[patch.crates-io]
|
||||||
|
tracing-tracy = { git = "https://github.com/nagisa/rust_tracy_client", tag = "tracy-client-v0.17.6" }
|
||||||
|
tracy-client = { git = "https://github.com/nagisa/rust_tracy_client", tag = "tracy-client-v0.17.6" }
|
||||||
|
tracy-client-sys = { git = "https://github.com/nagisa/rust_tracy_client", tag = "tracy-client-v0.17.6" }
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["codegen"]
|
members = ["codegen"]
|
||||||
|
|
||||||
[workspace.dependencies.stardust-xr]
|
[workspace.dependencies.stardust-xr]
|
||||||
git = "https://github.com/StardustXR/core.git"
|
git = "https://github.com/StardustXR/core.git"
|
||||||
|
branch = "dev"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "stardust-xr-server"
|
name = "stardust-xr-server"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["wayland"]
|
default = []
|
||||||
wayland = ["dep:smithay", "dep:wayland-scanner", "dep:wayland-backend"]
|
wayland = ["dep:smithay", "dep:wayland-scanner", "dep:wayland-backend"]
|
||||||
profile_tokio = ["dep:console-subscriber", "tokio/tracing"]
|
profile_tokio = ["dep:console-subscriber", "tokio/tracing"]
|
||||||
profile_app = ["dep:tracing-tracy"]
|
profile_app = ["dep:tracing-tracy"]
|
||||||
local_deps = ["stereokit-rust/force-local-deps"]
|
|
||||||
|
# Enable a small amount of optimization in the dev profile.
|
||||||
|
[profile.dev]
|
||||||
|
opt-level = 1
|
||||||
|
|
||||||
|
# Enable a large amount of optimization in the dev profile for dependencies.
|
||||||
|
[profile.dev.package."*"]
|
||||||
|
opt-level = 3
|
||||||
|
|
||||||
[package.metadata.appimage]
|
[package.metadata.appimage]
|
||||||
auto_link = true
|
auto_link = true
|
||||||
@@ -37,21 +50,24 @@ auto_link_exclude_list = [
|
|||||||
"libEGL*",
|
"libEGL*",
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile.dev.package."*"]
|
|
||||||
opt-level = 3
|
|
||||||
debug = true
|
|
||||||
strip = "none"
|
|
||||||
debug-assertions = true
|
|
||||||
overflow-checks = true
|
|
||||||
|
|
||||||
[profile.release]
|
|
||||||
opt-level = 3
|
|
||||||
debug = "line-tables-only"
|
|
||||||
strip = "none"
|
|
||||||
debug-assertions = true
|
|
||||||
overflow-checks = false
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
bevy = { version = "0.15.1", features = [
|
||||||
|
"wayland",
|
||||||
|
"mp3",
|
||||||
|
"wav",
|
||||||
|
"trace_tracy",
|
||||||
|
] }
|
||||||
|
bevy_mod_xr.git = "https://github.com/awtterpip/bevy_oxr"
|
||||||
|
bevy_mod_openxr.git = "https://github.com/awtterpip/bevy_oxr"
|
||||||
|
bevy_xr_utils.git = "https://github.com/awtterpip/bevy_oxr"
|
||||||
|
# bevy_mod_xr = { git = "https://github.com/Schmarni-Dev/bevy_openxr", branch = "pbr" }
|
||||||
|
# bevy_mod_openxr = { git = "https://github.com/Schmarni-Dev/bevy_openxr", branch = "pbr" }
|
||||||
|
# bevy_xr_utils = { git = "https://github.com/Schmarni-Dev/bevy_openxr", branch = "pbr" }
|
||||||
|
bevy_mod_meshtext.git = "https://github.com/Schmarni-Dev/bevy_mod_meshtext"
|
||||||
|
# bevy_sk.path = "../bevy_sk"
|
||||||
|
bevy_sk.git = "https://github.com/MalekiRe/bevy_sk"
|
||||||
|
openxr = "0.19"
|
||||||
|
winit = { version = "0.30", default-features = false, features = [] }
|
||||||
# small utility thingys
|
# small utility thingys
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
nanoid = "0.4.0"
|
nanoid = "0.4.0"
|
||||||
@@ -87,13 +103,16 @@ prisma = "0.1.1"
|
|||||||
libc = "0.2.155"
|
libc = "0.2.155"
|
||||||
nix = "0.29.0"
|
nix = "0.29.0"
|
||||||
input-event-codes = "6.2.0"
|
input-event-codes = "6.2.0"
|
||||||
zbus = { version = "4.4.0", default-features = false, features = ["tokio"] }
|
zbus = { version = "5.0.0", default-features = false, features = ["tokio"] }
|
||||||
directories = "5.0.1"
|
directories = "5.0.1"
|
||||||
xkbcommon-rs = "0.1.0"
|
xkbcommon-rs = "0.1.0"
|
||||||
|
thiserror = "2.0.9"
|
||||||
|
crossbeam-channel = "0.5.14"
|
||||||
|
atomicow = "1.0.0"
|
||||||
|
|
||||||
# wayland
|
# wayland
|
||||||
wayland-backend = { version = "0.3.7", optional = true, default-features = false }
|
wayland-scanner = { version = "0.31.2", optional = true }
|
||||||
wayland-scanner = { version = "0.31.4", optional = true }
|
wayland-backend = { version = "0.3.4", optional = true }
|
||||||
|
|
||||||
[dependencies.smithay]
|
[dependencies.smithay]
|
||||||
# git = "https://github.com/technobaboo/smithay.git"
|
# git = "https://github.com/technobaboo/smithay.git"
|
||||||
@@ -101,17 +120,15 @@ wayland-scanner = { version = "0.31.4", optional = true }
|
|||||||
git = "https://github.com/smithay/smithay.git"
|
git = "https://github.com/smithay/smithay.git"
|
||||||
# path = "../smithay"
|
# path = "../smithay"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["desktop", "backend_drm", "renderer_gl", "wayland_frontend"]
|
features = [
|
||||||
|
"desktop",
|
||||||
|
"backend_drm",
|
||||||
|
"backend_egl",
|
||||||
|
"renderer_gl",
|
||||||
|
"wayland_frontend",
|
||||||
|
]
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
|
|
||||||
[dependencies.stereokit-rust]
|
|
||||||
# path = "../StereoKit-rust"
|
|
||||||
git = "https://github.com/mvvvv/StereoKit-rust.git"
|
|
||||||
# git = "https://github.com/technobaboo/StereoKit-rust.git"
|
|
||||||
features = ["no-event-loop"]
|
|
||||||
default-features = false
|
|
||||||
|
|
||||||
[dependencies.stardust-xr]
|
[dependencies.stardust-xr]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
|
|||||||
@@ -25,10 +25,6 @@ pub fn codegen_field_protocol(_input: proc_macro::TokenStream) -> proc_macro::To
|
|||||||
codegen_protocol(FIELD_PROTOCOL)
|
codegen_protocol(FIELD_PROTOCOL)
|
||||||
}
|
}
|
||||||
#[proc_macro]
|
#[proc_macro]
|
||||||
pub fn codegen_data_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
|
||||||
codegen_protocol(DATA_PROTOCOL)
|
|
||||||
}
|
|
||||||
#[proc_macro]
|
|
||||||
pub fn codegen_audio_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
pub fn codegen_audio_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
codegen_protocol(AUDIO_PROTOCOL)
|
codegen_protocol(AUDIO_PROTOCOL)
|
||||||
}
|
}
|
||||||
@@ -53,6 +49,93 @@ pub fn codegen_item_panel_protocol(_input: proc_macro::TokenStream) -> proc_macr
|
|||||||
codegen_protocol(ITEM_PANEL_PROTOCOL)
|
codegen_protocol(ITEM_PANEL_PROTOCOL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn codegen_id_to_name_functions(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
|
let mut aspect_map: Vec<(u64, String)> = Vec::new();
|
||||||
|
let mut method_map: Vec<(u64, String)> = Vec::new();
|
||||||
|
let mut signal_map: Vec<(u64, String)> = Vec::new();
|
||||||
|
let protocols = [
|
||||||
|
ROOT_PROTOCOL,
|
||||||
|
NODE_PROTOCOL,
|
||||||
|
SPATIAL_PROTOCOL,
|
||||||
|
FIELD_PROTOCOL,
|
||||||
|
AUDIO_PROTOCOL,
|
||||||
|
DRAWABLE_PROTOCOL,
|
||||||
|
INPUT_PROTOCOL,
|
||||||
|
ITEM_PROTOCOL,
|
||||||
|
ITEM_CAMERA_PROTOCOL,
|
||||||
|
ITEM_PANEL_PROTOCOL,
|
||||||
|
];
|
||||||
|
for aspect in protocols
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|p| Protocol::parse(p).ok())
|
||||||
|
.flat_map(|v| v.aspects)
|
||||||
|
{
|
||||||
|
aspect_map.push((aspect.id, aspect.name));
|
||||||
|
for m in aspect.members.into_iter() {
|
||||||
|
match m._type {
|
||||||
|
MemberType::Signal => &mut signal_map,
|
||||||
|
MemberType::Method => &mut method_map,
|
||||||
|
}
|
||||||
|
.push((m.opcode, m.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let aspect_id_to_name = aspect_map
|
||||||
|
.iter()
|
||||||
|
.map(|(id, name)| {
|
||||||
|
quote! {
|
||||||
|
#id => #name,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.reduce(fold_tokens);
|
||||||
|
let aspect_id_to_name_fn = quote! {
|
||||||
|
pub const fn aspect_id_to_name(id: u64) -> &'static str {
|
||||||
|
match id {
|
||||||
|
#aspect_id_to_name
|
||||||
|
_ => "Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let method_id_to_name = method_map
|
||||||
|
.iter()
|
||||||
|
.map(|(id, name)| {
|
||||||
|
quote! {
|
||||||
|
#id => #name,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.reduce(fold_tokens);
|
||||||
|
let method_id_to_name_fn = quote! {
|
||||||
|
pub const fn method_id_to_name(id: u64) -> &'static str {
|
||||||
|
match id {
|
||||||
|
#method_id_to_name
|
||||||
|
_ => "Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let signal_id_to_name = signal_map
|
||||||
|
.iter()
|
||||||
|
.map(|(id, name)| {
|
||||||
|
quote! {
|
||||||
|
#id => #name,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.reduce(fold_tokens);
|
||||||
|
let signal_id_to_name_fn = quote! {
|
||||||
|
pub const fn signal_id_to_name(id: u64) -> &'static str {
|
||||||
|
match id {
|
||||||
|
#signal_id_to_name
|
||||||
|
_ => "Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
quote! {
|
||||||
|
#aspect_id_to_name_fn
|
||||||
|
#method_id_to_name_fn
|
||||||
|
#signal_id_to_name_fn
|
||||||
|
}.into()
|
||||||
|
}
|
||||||
|
|
||||||
fn codegen_protocol(protocol: &'static str) -> proc_macro::TokenStream {
|
fn codegen_protocol(protocol: &'static str) -> proc_macro::TokenStream {
|
||||||
let protocol = Protocol::parse(protocol).unwrap();
|
let protocol = Protocol::parse(protocol).unwrap();
|
||||||
let interface = protocol
|
let interface = protocol
|
||||||
@@ -64,11 +147,28 @@ fn codegen_protocol(protocol: &'static str) -> proc_macro::TokenStream {
|
|||||||
};
|
};
|
||||||
let aspect = generate_aspect(&Aspect {
|
let aspect = generate_aspect(&Aspect {
|
||||||
name: "interface".to_string(),
|
name: "interface".to_string(),
|
||||||
|
id: 0,
|
||||||
description: protocol.description.clone(),
|
description: protocol.description.clone(),
|
||||||
inherits: vec![],
|
inherits: vec![],
|
||||||
members: p.members,
|
members: p.members,
|
||||||
});
|
});
|
||||||
quote!(#node_id #aspect)
|
quote! {
|
||||||
|
#node_id
|
||||||
|
#aspect
|
||||||
|
pub struct Interface;
|
||||||
|
impl crate::nodes::AspectIdentifier for Interface {
|
||||||
|
impl_aspect_for_interface_aspect_id!{}
|
||||||
|
}
|
||||||
|
impl crate::nodes::Aspect for Interface {
|
||||||
|
impl_aspect_for_interface_aspect!{}
|
||||||
|
}
|
||||||
|
pub fn create_interface(client: &std::sync::Arc<crate::core::client::Client>) -> crate::core::error::Result<()>{
|
||||||
|
let node = crate::nodes::Node::from_id(client,INTERFACE_NODE_ID,false);
|
||||||
|
node.add_aspect(Interface);
|
||||||
|
node.add_to_scenegraph()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let custom_enums = protocol
|
let custom_enums = protocol
|
||||||
@@ -177,11 +277,12 @@ fn generate_aspect(aspect: &Aspect) -> TokenStream {
|
|||||||
Span::call_site(),
|
Span::call_site(),
|
||||||
);
|
);
|
||||||
let client_side_members = client_members
|
let client_side_members = client_members
|
||||||
.map(generate_member)
|
.map(|m| generate_member(aspect.id, m))
|
||||||
.reduce(fold_tokens)
|
.reduce(fold_tokens)
|
||||||
.map(|t| {
|
.map(|t| {
|
||||||
// TODO: properly import all dependencies
|
// TODO: properly import all dependencies
|
||||||
quote! {
|
quote! {
|
||||||
|
#[allow(clippy::all)]
|
||||||
pub mod #client_mod_name {
|
pub mod #client_mod_name {
|
||||||
use super::*;
|
use super::*;
|
||||||
#t
|
#t
|
||||||
@@ -190,11 +291,6 @@ fn generate_aspect(aspect: &Aspect) -> TokenStream {
|
|||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let aspect_trait_name = Ident::new(
|
|
||||||
&format!("{}Aspect", &aspect.name.to_case(Case::Pascal)),
|
|
||||||
Span::call_site(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let opcodes = aspect
|
let opcodes = aspect
|
||||||
.members
|
.members
|
||||||
.iter()
|
.iter()
|
||||||
@@ -219,31 +315,95 @@ fn generate_aspect(aspect: &Aspect) -> TokenStream {
|
|||||||
let alias_info = generate_alias_info(aspect);
|
let alias_info = generate_alias_info(aspect);
|
||||||
|
|
||||||
let server_side_members = server_members
|
let server_side_members = server_members
|
||||||
.map(generate_member)
|
.map(|m| generate_member(aspect.id, m))
|
||||||
.reduce(fold_tokens)
|
.reduce(fold_tokens)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let add_node_members = aspect
|
let aspect_trait_name = Ident::new(
|
||||||
|
&format!("{}Aspect", &aspect.name.to_case(Case::Pascal)),
|
||||||
|
Span::call_site(),
|
||||||
|
);
|
||||||
|
let run_signals = aspect
|
||||||
.members
|
.members
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|m| m.side == Side::Server)
|
.filter(|m| m.side == Side::Server)
|
||||||
.map(generate_handler)
|
.filter(|m| m._type == MemberType::Signal)
|
||||||
|
.map(|m| generate_run_member(&aspect_trait_name, MemberType::Signal, m))
|
||||||
|
.reduce(fold_tokens)
|
||||||
|
.unwrap_or_default();
|
||||||
|
let run_methods = aspect
|
||||||
|
.members
|
||||||
|
.iter()
|
||||||
|
.filter(|m| m.side == Side::Server)
|
||||||
|
.filter(|m| m._type == MemberType::Method)
|
||||||
|
.map(|m| generate_run_member(&aspect_trait_name, MemberType::Method, m))
|
||||||
.reduce(fold_tokens)
|
.reduce(fold_tokens)
|
||||||
.map(|members| {
|
|
||||||
quote! {
|
|
||||||
fn add_node_members(node: &crate::nodes::Node) {
|
|
||||||
#members
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let server_side_members = quote! {
|
let server_side_members = quote! {
|
||||||
|
#[allow(clippy::all)]
|
||||||
#[doc = #description]
|
#[doc = #description]
|
||||||
pub trait #aspect_trait_name {
|
pub trait #aspect_trait_name {
|
||||||
#add_node_members
|
|
||||||
#server_side_members
|
#server_side_members
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
quote!(#opcodes #alias_info #client_side_members #server_side_members)
|
let aspect_id_macro_name = Ident::new(
|
||||||
|
&format!(
|
||||||
|
"impl_aspect_for_{}_aspect_id",
|
||||||
|
aspect.name.to_case(Case::Snake)
|
||||||
|
),
|
||||||
|
Span::call_site(),
|
||||||
|
);
|
||||||
|
let aspect_macro_name = Ident::new(
|
||||||
|
&format!(
|
||||||
|
"impl_aspect_for_{}_aspect",
|
||||||
|
aspect.name.to_case(Case::Snake)
|
||||||
|
),
|
||||||
|
Span::call_site(),
|
||||||
|
);
|
||||||
|
let aspect_id = aspect.id;
|
||||||
|
let aspect_macro = quote! {
|
||||||
|
macro_rules! #aspect_id_macro_name {
|
||||||
|
() => {
|
||||||
|
const ID: u64 = #aspect_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
macro_rules! #aspect_macro_name {
|
||||||
|
() => {
|
||||||
|
fn as_any(self: Arc<Self>) -> Arc<dyn std::any::Any + Send + Sync + 'static> {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
#[allow(clippy::all)]
|
||||||
|
fn run_signal(
|
||||||
|
&self,
|
||||||
|
_calling_client: std::sync::Arc<crate::core::client::Client>,
|
||||||
|
_node: std::sync::Arc<crate::nodes::Node>,
|
||||||
|
_signal: u64,
|
||||||
|
_message: crate::nodes::Message
|
||||||
|
) -> Result<(), stardust_xr::scenegraph::ScenegraphError> {
|
||||||
|
match _signal {
|
||||||
|
#run_signals
|
||||||
|
_ => Err(stardust_xr::scenegraph::ScenegraphError::MemberNotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[allow(clippy::all)]
|
||||||
|
fn run_method(
|
||||||
|
&self,
|
||||||
|
_calling_client: std::sync::Arc<crate::core::client::Client>,
|
||||||
|
_node: std::sync::Arc<crate::nodes::Node>,
|
||||||
|
_method: u64,
|
||||||
|
_message: crate::nodes::Message,
|
||||||
|
_method_response: crate::nodes::MethodResponseSender,
|
||||||
|
) {
|
||||||
|
match _method {
|
||||||
|
#run_methods
|
||||||
|
_ => {
|
||||||
|
let _ = _method_response.send(Err(stardust_xr::scenegraph::ScenegraphError::MemberNotFound));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
quote!(#opcodes #alias_info #client_side_members #server_side_members #aspect_macro)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_alias_opcodes(aspect: &Aspect, side: Side, _type: MemberType) -> TokenStream {
|
fn generate_alias_opcodes(aspect: &Aspect, side: Side, _type: MemberType) -> TokenStream {
|
||||||
@@ -283,6 +443,7 @@ fn generate_alias_info(aspect: &Aspect) -> TokenStream {
|
|||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
|
#[allow(clippy::all)]
|
||||||
pub static ref #aspect_alias_info_name: crate::nodes::alias::AliasInfo = crate::nodes::alias::AliasInfo {
|
pub static ref #aspect_alias_info_name: crate::nodes::alias::AliasInfo = crate::nodes::alias::AliasInfo {
|
||||||
server_signals: vec![#local_signals],
|
server_signals: vec![#local_signals],
|
||||||
server_methods: vec![#local_methods],
|
server_methods: vec![#local_methods],
|
||||||
@@ -293,8 +454,8 @@ fn generate_alias_info(aspect: &Aspect) -> TokenStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_member(member: &Member) -> TokenStream {
|
fn generate_member(aspect_id: u64, member: &Member) -> TokenStream {
|
||||||
let id = member.opcode;
|
let opcode = member.opcode;
|
||||||
let name = Ident::new(&member.name.to_case(Case::Snake), Span::call_site());
|
let name = Ident::new(&member.name.to_case(Case::Snake), Span::call_site());
|
||||||
let description = &member.description;
|
let description = &member.description;
|
||||||
|
|
||||||
@@ -326,38 +487,38 @@ fn generate_member(member: &Member) -> TokenStream {
|
|||||||
.unwrap_or_else(|| quote!(()));
|
.unwrap_or_else(|| quote!(()));
|
||||||
|
|
||||||
match (side, _type) {
|
match (side, _type) {
|
||||||
(Side::Client, MemberType::Method) => {
|
|
||||||
quote! {
|
|
||||||
#[doc = #description]
|
|
||||||
pub async fn #name(#argument_decls) -> color_eyre::eyre::Result<(#return_type, Vec<std::os::fd::OwnedFd>)> {
|
|
||||||
_node.execute_remote_method_typed(#id, &(#argument_uses), vec![]).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(Side::Client, MemberType::Signal) => {
|
(Side::Client, MemberType::Signal) => {
|
||||||
quote! {
|
quote! {
|
||||||
#[doc = #description]
|
#[doc = #description]
|
||||||
pub fn #name(#argument_decls) -> color_eyre::eyre::Result<()> {
|
pub fn #name(#argument_decls) -> crate::core::error::Result<()> {
|
||||||
let serialized = stardust_xr::schemas::flex::serialize((#argument_uses))?;
|
let serialized = stardust_xr::schemas::flex::serialize((#argument_uses))?;
|
||||||
_node.send_remote_signal(#id, serialized)
|
_node.send_remote_signal(#aspect_id, #opcode, serialized)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(Side::Server, MemberType::Method) => {
|
(Side::Client, MemberType::Method) => {
|
||||||
quote! {
|
quote! {
|
||||||
#[doc = #description]
|
#[doc = #description]
|
||||||
fn #name(#argument_decls) -> impl std::future::Future<Output = color_eyre::eyre::Result<#return_type>> + Send + 'static;
|
pub async fn #name(#argument_decls) -> crate::core::error::Result<(#return_type, Vec<std::os::fd::OwnedFd>)> {
|
||||||
|
_node.execute_remote_method_typed(#aspect_id, #opcode, &(#argument_uses), vec![]).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(Side::Server, MemberType::Signal) => {
|
(Side::Server, MemberType::Signal) => {
|
||||||
quote! {
|
quote! {
|
||||||
#[doc = #description]
|
#[doc = #description]
|
||||||
fn #name(#argument_decls) -> color_eyre::eyre::Result<()>;
|
fn #name(#argument_decls) -> crate::core::error::Result<()>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Side::Server, MemberType::Method) => {
|
||||||
|
quote! {
|
||||||
|
#[doc = #description]
|
||||||
|
fn #name(#argument_decls) -> impl std::future::Future<Output = crate::core::error::Result<#return_type>> + Send + Sync + 'static;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn generate_handler(member: &Member) -> TokenStream {
|
fn generate_run_member(aspect_name: &Ident, _type: MemberType, member: &Member) -> TokenStream {
|
||||||
let opcode = member.opcode;
|
let opcode = member.opcode;
|
||||||
let member_name_ident = Ident::new(&member.name, Span::call_site());
|
let member_name_ident = Ident::new(&member.name, Span::call_site());
|
||||||
|
|
||||||
@@ -379,7 +540,10 @@ fn generate_handler(member: &Member) -> TokenStream {
|
|||||||
.clone()
|
.clone()
|
||||||
.zip(argument_types)
|
.zip(argument_types)
|
||||||
.map(|(argument_names, argument_types)| {
|
.map(|(argument_names, argument_types)| {
|
||||||
quote!(let (#argument_names): (#argument_types) = stardust_xr::schemas::flex::deserialize(_message.as_ref())?;)
|
quote!{
|
||||||
|
#[allow(unused_parens)]
|
||||||
|
let (#argument_names): (#argument_types) = stardust_xr::schemas::flex::deserialize(_message.as_ref())?;
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let serialize = generate_argument_serialize(
|
let serialize = generate_argument_serialize(
|
||||||
@@ -393,21 +557,19 @@ fn generate_handler(member: &Member) -> TokenStream {
|
|||||||
.map(|a| generate_argument_deserialize(&a.name, &a._type, a.optional))
|
.map(|a| generate_argument_deserialize(&a.name, &a._type, a.optional))
|
||||||
.reduce(|a, b| quote!(#a, #b))
|
.reduce(|a, b| quote!(#a, #b))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
match member._type {
|
match _type {
|
||||||
MemberType::Signal => quote! {
|
MemberType::Signal => quote! {
|
||||||
node.add_local_signal(#opcode, |_node, _calling_client, _message| {
|
#opcode => (move || {
|
||||||
#deserialize
|
#deserialize
|
||||||
Self::#member_name_ident(_node, _calling_client.clone(), #argument_uses)
|
<Self as #aspect_name>::#member_name_ident(_node, _calling_client.clone(), #argument_uses)
|
||||||
});
|
})().map_err(|e: crate::core::error::ServerError| stardust_xr::scenegraph::ScenegraphError::MemberError { error: e.to_string() }),
|
||||||
},
|
},
|
||||||
MemberType::Method => quote! {
|
MemberType::Method => quote! {
|
||||||
node.add_local_method(#opcode, |_node, _calling_client, _message, _method_response| {
|
#opcode => _method_response.wrap_async(async move {
|
||||||
_method_response.wrap_async(async move {
|
#deserialize
|
||||||
#deserialize
|
let result = <Self as #aspect_name>::#member_name_ident(_node, _calling_client.clone(), #argument_uses).await?;
|
||||||
let result = Self::#member_name_ident(_node, _calling_client.clone(), #argument_uses).await?;
|
Ok((#serialize, Vec::<std::os::fd::OwnedFd>::new()))
|
||||||
Ok((#serialize, Vec::new()))
|
}),
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -441,18 +603,18 @@ fn generate_argument_deserialize(
|
|||||||
}
|
}
|
||||||
if optional {
|
if optional {
|
||||||
let mapping = generate_argument_deserialize("o", argument_type, false);
|
let mapping = generate_argument_deserialize("o", argument_type, false);
|
||||||
return quote!(#name.map(|o| Ok::<_, color_eyre::eyre::Report>(#mapping)).transpose()?);
|
return quote!(#name.map(|o| Ok::<_, crate::core::error::ServerError>(#mapping)).transpose()?);
|
||||||
}
|
}
|
||||||
|
|
||||||
match argument_type {
|
match argument_type {
|
||||||
ArgumentType::Color => quote!(color::rgba_linear!(#name[0], #name[1], #name[2], #name[3])),
|
ArgumentType::Color => quote!(color::rgba_linear!(#name[0], #name[1], #name[2], #name[3])),
|
||||||
ArgumentType::Vec(v) => {
|
ArgumentType::Vec(v) => {
|
||||||
let mapping = generate_argument_deserialize("a", v, false);
|
let mapping = generate_argument_deserialize("a", v, false);
|
||||||
quote!(#name.into_iter().map(|a| Ok(#mapping)).collect::<color_eyre::eyre::Result<Vec<_>>>()?)
|
quote!(#name.into_iter().map(|a| Ok(#mapping)).collect::<crate::core::error::Result<Vec<_>>>()?)
|
||||||
}
|
}
|
||||||
ArgumentType::Map(v) => {
|
ArgumentType::Map(v) => {
|
||||||
let mapping = generate_argument_deserialize("a", v, false);
|
let mapping = generate_argument_deserialize("a", v, false);
|
||||||
quote!(#name.into_iter().map(|(k, a)| Ok((k, #mapping))).collect::<color_eyre::eyre::Result<rustc_hash::FxHashMap<String, _>>>()?)
|
quote!(#name.into_iter().map(|(k, a)| Ok((k, #mapping))).collect::<crate::core::error::Result<rustc_hash::FxHashMap<String, _>>>()?)
|
||||||
}
|
}
|
||||||
_ => quote!(#name),
|
_ => quote!(#name),
|
||||||
}
|
}
|
||||||
@@ -474,11 +636,11 @@ fn generate_argument_serialize(
|
|||||||
ArgumentType::Color => quote!([#name.c.r, #name.c.g, #name.c.b, #name.a]),
|
ArgumentType::Color => quote!([#name.c.r, #name.c.g, #name.c.b, #name.a]),
|
||||||
ArgumentType::Vec(v) => {
|
ArgumentType::Vec(v) => {
|
||||||
let mapping = generate_argument_serialize("a", v, false);
|
let mapping = generate_argument_serialize("a", v, false);
|
||||||
quote!(#name.into_iter().map(|a| Ok(#mapping)).collect::<color_eyre::eyre::Result<Vec<_>>>()?)
|
quote!(#name.into_iter().map(|a| Ok(#mapping)).collect::<crate::core::error::Result<Vec<_>>>()?)
|
||||||
}
|
}
|
||||||
ArgumentType::Map(v) => {
|
ArgumentType::Map(v) => {
|
||||||
let mapping = generate_argument_serialize("a", v, false);
|
let mapping = generate_argument_serialize("a", v, false);
|
||||||
quote!(#name.into_iter().map(|(k, a)| Ok((k, #mapping))).collect::<color_eyre::eyre::Result<rustc_hash::FxHashMap<String, _>>>()?)
|
quote!(#name.into_iter().map(|(k, a)| Ok((k, #mapping))).collect::<crate::core::error::Result<rustc_hash::FxHashMap<String, _>>>()?)
|
||||||
}
|
}
|
||||||
_ => quote!(#name),
|
_ => quote!(#name),
|
||||||
}
|
}
|
||||||
@@ -511,6 +673,7 @@ fn argument_type_option_name(argument_type: &ArgumentType) -> String {
|
|||||||
ArgumentType::Union(u) => u.clone(),
|
ArgumentType::Union(u) => u.clone(),
|
||||||
ArgumentType::Struct(s) => s.clone(),
|
ArgumentType::Struct(s) => s.clone(),
|
||||||
ArgumentType::Node { _type, .. } => _type.clone(),
|
ArgumentType::Node { _type, .. } => _type.clone(),
|
||||||
|
ArgumentType::Fd => "File Descriptor".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn generate_argument_type(
|
fn generate_argument_type(
|
||||||
@@ -607,6 +770,9 @@ fn generate_argument_type(
|
|||||||
quote!(std::sync::Arc<crate::nodes::Node>)
|
quote!(std::sync::Arc<crate::nodes::Node>)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ArgumentType::Fd => {
|
||||||
|
quote!(&std::os::fd::OwnedFd)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if optional {
|
if optional {
|
||||||
|
|||||||
12
justfile
Normal file
12
justfile
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
PREFIX := "usr"
|
||||||
|
BINARY := PREFIX / "bin"
|
||||||
|
DESTDIR := "/"
|
||||||
|
|
||||||
|
build:
|
||||||
|
cargo build --release
|
||||||
|
|
||||||
|
test:
|
||||||
|
cargo test
|
||||||
|
|
||||||
|
install:
|
||||||
|
install -Dm755 target/release/stardust-xr-server {{ DESTDIR }}{{ BINARY }}/stardust-xr-server
|
||||||
195
src/bevy_plugin.rs
Normal file
195
src/bevy_plugin.rs
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use bevy::{
|
||||||
|
app::MainScheduleOrder,
|
||||||
|
ecs::schedule::{ExecutorKind, ScheduleLabel},
|
||||||
|
math::bounding::Aabb3d,
|
||||||
|
pbr::{DefaultOpaqueRendererMethod, GpuMeshPreprocessPlugin, MeshRenderPlugin},
|
||||||
|
prelude::*,
|
||||||
|
render::extract_resource::ExtractResourcePlugin,
|
||||||
|
};
|
||||||
|
use bevy_mod_openxr::session::OxrSession;
|
||||||
|
use bevy_mod_xr::session::{session_available, XrFirst, XrSessionCreated};
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
use openxr::ReferenceSpaceType;
|
||||||
|
use stardust_xr::values::color::{color_space::LinearRgb, AlphaColor, Rgb};
|
||||||
|
|
||||||
|
use crate::DefaultMaterial;
|
||||||
|
|
||||||
|
pub struct StardustBevyPlugin;
|
||||||
|
|
||||||
|
pub static DESTROY_ENTITY: DestroySender = DestroySender(OnceCell::new());
|
||||||
|
|
||||||
|
pub struct DestroySender(OnceCell<crossbeam_channel::Sender<Entity>>);
|
||||||
|
impl Deref for DestroySender {
|
||||||
|
type Target = crossbeam_channel::Sender<Entity>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.0.get().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Resource, Deref)]
|
||||||
|
struct DestroyEntityReader(crossbeam_channel::Receiver<Entity>);
|
||||||
|
|
||||||
|
#[derive(Resource, Deref)]
|
||||||
|
pub struct DbusConnection(pub zbus::Connection);
|
||||||
|
|
||||||
|
#[derive(ScheduleLabel, Hash, Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub struct InputUpdate;
|
||||||
|
#[derive(ScheduleLabel, Hash, Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub struct StardustFirst;
|
||||||
|
impl Plugin for StardustBevyPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
let (tx, rx) = crossbeam_channel::unbounded();
|
||||||
|
DESTROY_ENTITY
|
||||||
|
.0
|
||||||
|
.set(tx)
|
||||||
|
.expect("unable to set destroy entity sender, yell at schmarni pls thx");
|
||||||
|
app.insert_resource(DestroyEntityReader(rx));
|
||||||
|
app.init_schedule(StardustExtract);
|
||||||
|
let labels = &mut app.world_mut().resource_mut::<MainScheduleOrder>().labels;
|
||||||
|
info!("test: {labels:?}");
|
||||||
|
labels.insert(labels.len() - 2, StardustExtract.intern());
|
||||||
|
app.add_systems(Startup, spawn_camera.run_if(not(session_available)));
|
||||||
|
app.add_systems(XrSessionCreated, make_view_space);
|
||||||
|
let mut schedule = Schedule::new(InputUpdate);
|
||||||
|
schedule.set_executor_kind(ExecutorKind::Simple);
|
||||||
|
app.add_schedule(schedule);
|
||||||
|
|
||||||
|
let mut schedule = Schedule::new(StardustFirst);
|
||||||
|
schedule.set_executor_kind(ExecutorKind::Simple);
|
||||||
|
app.add_schedule(schedule);
|
||||||
|
|
||||||
|
let labels = &mut app.world_mut().resource_mut::<MainScheduleOrder>().labels;
|
||||||
|
let xr_first_intern = (XrFirst).intern();
|
||||||
|
if labels.remove(0) != xr_first_intern {
|
||||||
|
panic!("first schedule was not XrFirst!");
|
||||||
|
}
|
||||||
|
labels.insert(0, (StardustFirst).intern());
|
||||||
|
app.add_systems(First, yeet_entities);
|
||||||
|
// app.add_observer(
|
||||||
|
// |trigger: Trigger<OnInsert, MeshMaterial3d<DefaultMaterial>>,
|
||||||
|
// query: Query<&MeshMaterial3d<DefaultMaterial>>,
|
||||||
|
// mut mats: ResMut<Assets<DefaultMaterial>>| {
|
||||||
|
// let Ok(handle) = query.get(trigger.entity()) else {
|
||||||
|
// return;
|
||||||
|
// };
|
||||||
|
// if let Some(mat) = mats.get_mut(handle) {
|
||||||
|
// if matches!(mat.alpha_mode, AlphaMode::Blend | AlphaMode::Mask(_)) {
|
||||||
|
// mat.alpha_mode = AlphaMode::AlphaToCoverage
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn yeet_entities(
|
||||||
|
mut cmds: Commands,
|
||||||
|
query: Query<Entity, With<TemporaryEntity>>,
|
||||||
|
reader: Res<DestroyEntityReader>,
|
||||||
|
) {
|
||||||
|
query.iter().for_each(|e| {
|
||||||
|
info!("yeeting component entities");
|
||||||
|
cmds.entity(e).despawn_recursive();
|
||||||
|
});
|
||||||
|
reader
|
||||||
|
.0
|
||||||
|
.try_iter()
|
||||||
|
.for_each(|e| cmds.entity(e).despawn_recursive());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_view_space(mut cmds: Commands, session: Res<OxrSession>) {
|
||||||
|
// idk what errors this function returns
|
||||||
|
let view_space = session
|
||||||
|
.create_reference_space(ReferenceSpaceType::VIEW, Transform::IDENTITY)
|
||||||
|
.unwrap();
|
||||||
|
// this locates the view space against the default reference space (stage i belive) and sets
|
||||||
|
// the transform relative to the XrTrackingRoot
|
||||||
|
cmds.spawn((view_space.0, ViewLocation));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_camera(mut cmds: Commands) {
|
||||||
|
cmds.spawn((Camera3d::default(), ViewLocation));
|
||||||
|
}
|
||||||
|
pub trait StardustAabb3dExt {
|
||||||
|
fn grown_box(&self, aabb: &Self, opt_box_transform: Option<impl Into<Mat4>>) -> Self;
|
||||||
|
fn grown_point(&self, pt: impl Into<Vec3>) -> Self;
|
||||||
|
}
|
||||||
|
impl StardustAabb3dExt for Aabb3d {
|
||||||
|
fn grown_box(&self, other: &Self, opt_box_transform: Option<impl Into<Mat4>>) -> Self {
|
||||||
|
let mat = opt_box_transform.map(|m| m.into());
|
||||||
|
let other_min = mat
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| v.transform_point3a(other.min))
|
||||||
|
.unwrap_or(other.min);
|
||||||
|
let other_max = mat
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| v.transform_point3a(other.max))
|
||||||
|
.unwrap_or(other.max);
|
||||||
|
let tmp = self.grown_point(other_min);
|
||||||
|
tmp.grown_point(other_max)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn grown_point(&self, pt: impl Into<Vec3>) -> Self {
|
||||||
|
let pt = pt.into();
|
||||||
|
let mut min = self.min;
|
||||||
|
let mut max = self.max;
|
||||||
|
if pt.x > max.x {
|
||||||
|
max.x = pt.x;
|
||||||
|
} else if pt.x < min.x {
|
||||||
|
min.x = pt.x;
|
||||||
|
}
|
||||||
|
if pt.y > max.y {
|
||||||
|
max.y = pt.y;
|
||||||
|
} else if pt.y < min.y {
|
||||||
|
min.y = pt.y;
|
||||||
|
}
|
||||||
|
if pt.z > max.z {
|
||||||
|
max.z = pt.z;
|
||||||
|
} else if pt.z < min.z {
|
||||||
|
min.z = pt.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
Aabb3d { min, max }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn convert_linear_rgba(c: AlphaColor<f32, Rgb<f32, LinearRgb>>) -> LinearRgba {
|
||||||
|
LinearRgba {
|
||||||
|
red: c.c.r,
|
||||||
|
green: c.c.g,
|
||||||
|
blue: c.c.b,
|
||||||
|
alpha: c.a,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(ScheduleLabel, Hash, Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct StardustExtract;
|
||||||
|
#[derive(Component, Hash, Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct TemporaryEntity;
|
||||||
|
#[derive(Component, Hash, Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[require(GlobalTransform)]
|
||||||
|
pub struct ViewLocation;
|
||||||
|
#[derive(Hash, Debug, Clone, Copy, PartialEq, Eq, Deref)]
|
||||||
|
pub struct MainWorldEntity(pub Entity);
|
||||||
|
|
||||||
|
pub struct DummyPbrPlugin;
|
||||||
|
|
||||||
|
impl Plugin for DummyPbrPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
let use_gpu_instance_buffer_builder = true;
|
||||||
|
app.init_asset::<StandardMaterial>();
|
||||||
|
app.add_plugins((
|
||||||
|
GpuMeshPreprocessPlugin {
|
||||||
|
use_gpu_instance_buffer_builder,
|
||||||
|
},
|
||||||
|
MeshRenderPlugin {
|
||||||
|
use_gpu_instance_buffer_builder,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
app.register_type::<DefaultOpaqueRendererMethod>()
|
||||||
|
.init_resource::<DefaultOpaqueRendererMethod>()
|
||||||
|
.add_plugins(ExtractResourcePlugin::<DefaultOpaqueRendererMethod>::default());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ use super::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
core::{registry::OwnedRegistry, task},
|
core::{registry::OwnedRegistry, task},
|
||||||
nodes::{
|
nodes::{
|
||||||
audio, data, drawable, fields, input, items,
|
audio, drawable, fields, input, items,
|
||||||
root::{ClientState, Root},
|
root::{ClientState, Root},
|
||||||
spatial, Node,
|
spatial, Node,
|
||||||
},
|
},
|
||||||
@@ -112,9 +112,8 @@ impl Client {
|
|||||||
fields::create_interface(&client)?;
|
fields::create_interface(&client)?;
|
||||||
drawable::create_interface(&client)?;
|
drawable::create_interface(&client)?;
|
||||||
audio::create_interface(&client)?;
|
audio::create_interface(&client)?;
|
||||||
data::create_interface(&client)?;
|
|
||||||
input::create_interface(&client)?;
|
input::create_interface(&client)?;
|
||||||
items::camera::create_interface(&client)?;
|
// items::camera::create_interface(&client)?;
|
||||||
items::panel::create_interface(&client)?;
|
items::panel::create_interface(&client)?;
|
||||||
|
|
||||||
let _ = client.state.set(state.apply_to(&client));
|
let _ = client.state.set(state.apply_to(&client));
|
||||||
|
|||||||
74
src/core/error.rs
Normal file
74
src/core/error.rs
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
use std::any::TypeId;
|
||||||
|
|
||||||
|
use color_eyre::eyre::Report;
|
||||||
|
use stardust_xr::{
|
||||||
|
messenger::MessengerError,
|
||||||
|
schemas::flex::{
|
||||||
|
flexbuffers::{DeserializationError, ReaderError},
|
||||||
|
FlexSerializeError,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub type Result<T, E = ServerError> = std::result::Result<T, E>;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum ServerError {
|
||||||
|
#[error("Internal: Unable to get client")]
|
||||||
|
NoClient,
|
||||||
|
#[error("Messenger does not exist for this node")]
|
||||||
|
NoMessenger,
|
||||||
|
#[error("Could not find resource")]
|
||||||
|
NoResource,
|
||||||
|
#[error("Messenger error: {0}")]
|
||||||
|
MessengerError(#[from] MessengerError),
|
||||||
|
#[error("Remote method error: {0}")]
|
||||||
|
RemoteMethodError(String),
|
||||||
|
#[error("Serialization error: {0}")]
|
||||||
|
SerializationError(#[from] FlexSerializeError),
|
||||||
|
#[error("Deserialization error: {0}")]
|
||||||
|
DeserializationError(#[from] DeserializationError),
|
||||||
|
#[error("Reader error: {0}")]
|
||||||
|
ReaderError(#[from] ReaderError),
|
||||||
|
#[error("Aspect {} does not exist for node", 0.to_string())]
|
||||||
|
NoAspect(TypeId),
|
||||||
|
#[error("{0}")]
|
||||||
|
Report(#[from] Report),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! bail {
|
||||||
|
($msg:literal $(,)?) => {
|
||||||
|
return Err($crate::core::error::ServerError::from(color_eyre::eyre::eyre!($msg)));
|
||||||
|
};
|
||||||
|
($err:expr $(,)?) => {
|
||||||
|
return Err($crate::core::error::ServerError::from(color_eyre::eyre::eyre!($err)));
|
||||||
|
};
|
||||||
|
($fmt:expr, $($arg:tt)*) => {
|
||||||
|
return Err($crate::core::error::ServerError::from(color_eyre::eyre::eyre!($fmt, $($arg)*)));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! ensure {
|
||||||
|
($cond:expr $(,)?) => {
|
||||||
|
if !$cond {
|
||||||
|
$crate::ensure!($cond, concat!("Condition failed: `", stringify!($cond), "`"))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($cond:expr, $msg:literal $(,)?) => {
|
||||||
|
if !$cond {
|
||||||
|
return Err($crate::core::error::ServerError::from(color_eyre::eyre::eyre!($msg)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($cond:expr, $err:expr $(,)?) => {
|
||||||
|
if !$cond {
|
||||||
|
return Err($crate::core::error::ServerError::from(color_eyre::eyre::eyre!($err)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($cond:expr, $fmt:expr, $($arg:tt)*) => {
|
||||||
|
if !$cond {
|
||||||
|
return Err($crate::core::error::ServerError::from(color_eyre::eyre::eyre!($fmt, $($arg)*)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
#[macro_export]
|
|
||||||
macro_rules! create_interface {
|
|
||||||
($iface:ident) => {
|
|
||||||
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
|
|
||||||
let node = Node::from_id(client, INTERFACE_NODE_ID, false);
|
|
||||||
<$iface as self::InterfaceAspect>::add_node_members(&node);
|
|
||||||
node.add_to_scenegraph()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -2,8 +2,9 @@ pub mod client;
|
|||||||
pub mod client_state;
|
pub mod client_state;
|
||||||
pub mod delta;
|
pub mod delta;
|
||||||
pub mod destroy_queue;
|
pub mod destroy_queue;
|
||||||
pub mod idl_utils;
|
pub mod error;
|
||||||
pub mod registry;
|
pub mod registry;
|
||||||
pub mod resource;
|
pub mod resource;
|
||||||
pub mod scenegraph;
|
pub mod scenegraph;
|
||||||
pub mod task;
|
pub mod task;
|
||||||
|
pub mod queued_mutex;
|
||||||
|
|||||||
119
src/core/queued_mutex.rs
Normal file
119
src/core/queued_mutex.rs
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
use std::{
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
sync::atomic::Ordering,
|
||||||
|
};
|
||||||
|
|
||||||
|
use parking_lot::{
|
||||||
|
lock_api::{MutexGuard, RwLockReadGuard},
|
||||||
|
Mutex, RawMutex, RawRwLock, RwLock,
|
||||||
|
};
|
||||||
|
use portable_atomic::AtomicU8;
|
||||||
|
|
||||||
|
pub struct QueuedMutex<T: Clone> {
|
||||||
|
mutable: Mutex<Option<T>>,
|
||||||
|
readers: AtomicU8,
|
||||||
|
inner: RwLock<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone + Default> Default for QueuedMutex<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new(T::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone> QueuedMutex<T> {
|
||||||
|
pub const fn new(value: T) -> QueuedMutex<T> {
|
||||||
|
Self {
|
||||||
|
mutable: Mutex::new(None),
|
||||||
|
readers: AtomicU8::new(0),
|
||||||
|
inner: RwLock::new(value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn lock(&self) -> QueuedMutexLockGuard<'_, T> {
|
||||||
|
let mut guard = self.mutable.lock();
|
||||||
|
if guard.is_none() {
|
||||||
|
guard.replace(self.inner.read().clone());
|
||||||
|
}
|
||||||
|
QueuedMutexLockGuard { mutex: self, guard }
|
||||||
|
}
|
||||||
|
pub fn read_now(&self) -> QueuedMutexReadGuard<'_, T> {
|
||||||
|
let guard = self.inner.read();
|
||||||
|
self.readers.add(1, Ordering::Relaxed);
|
||||||
|
QueuedMutexReadGuard {
|
||||||
|
mutex: self,
|
||||||
|
guard: Some(guard),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct QueuedMutexLockGuard<'a, T: Clone> {
|
||||||
|
mutex: &'a QueuedMutex<T>,
|
||||||
|
guard: MutexGuard<'a, RawMutex, Option<T>>,
|
||||||
|
}
|
||||||
|
impl<T: Clone> Deref for QueuedMutexLockGuard<'_, T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
match self.guard.as_ref() {
|
||||||
|
Some(v) => v,
|
||||||
|
None => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T: Clone> DerefMut for QueuedMutexLockGuard<'_, T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
match self.guard.as_mut() {
|
||||||
|
Some(v) => v,
|
||||||
|
None => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone> Drop for QueuedMutexLockGuard<'_, T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if self.mutex.readers.load(Ordering::Relaxed) != 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mut write_lock = self.mutex.inner.write();
|
||||||
|
*write_lock = match self.guard.take() {
|
||||||
|
Some(v) => v,
|
||||||
|
None => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct QueuedMutexReadGuard<'a, T: Clone> {
|
||||||
|
mutex: &'a QueuedMutex<T>,
|
||||||
|
guard: Option<RwLockReadGuard<'a, RawRwLock, T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone> Deref for QueuedMutexReadGuard<'_, T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
match self.guard.as_ref() {
|
||||||
|
Some(v) => v,
|
||||||
|
None => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone> Drop for QueuedMutexReadGuard<'_, T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
drop(self.guard.take());
|
||||||
|
if self
|
||||||
|
.mutex
|
||||||
|
.readers
|
||||||
|
.fetch_sub(1, Ordering::Relaxed)
|
||||||
|
.wrapping_sub(1)
|
||||||
|
!= 0
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut write_lock = self.mutex.inner.write();
|
||||||
|
if let Some(v) = self.mutex.mutable.lock().take() {
|
||||||
|
*write_lock = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
|
use crate::core::error::Result;
|
||||||
use crate::nodes::alias::get_original;
|
use crate::nodes::alias::get_original;
|
||||||
use crate::nodes::Node;
|
use crate::nodes::Node;
|
||||||
|
use crate::TOKIO;
|
||||||
use crate::{core::client::Client, nodes::Message};
|
use crate::{core::client::Client, nodes::Message};
|
||||||
use color_eyre::eyre::Result;
|
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
@@ -15,6 +16,8 @@ use std::sync::{Arc, Weak};
|
|||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
use tracing::{debug, debug_span};
|
use tracing::{debug, debug_span};
|
||||||
|
|
||||||
|
stardust_xr_server_codegen::codegen_id_to_name_functions!();
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Scenegraph {
|
pub struct Scenegraph {
|
||||||
pub(super) client: OnceCell<Weak<Client>>,
|
pub(super) client: OnceCell<Weak<Client>>,
|
||||||
@@ -59,26 +62,26 @@ impl MethodResponseSender {
|
|||||||
// ) {
|
// ) {
|
||||||
// let _ = self.0.send(map_method_return(result));
|
// let _ = self.0.send(map_method_return(result));
|
||||||
// }
|
// }
|
||||||
pub fn wrap_sync<F: FnOnce() -> color_eyre::eyre::Result<Message>>(self, f: F) {
|
pub fn wrap_sync<F: FnOnce() -> crate::core::error::Result<Message>>(self, f: F) {
|
||||||
self.send(f().map_err(|e| ScenegraphError::MethodError {
|
self.send(f().map_err(|e| ScenegraphError::MemberError {
|
||||||
error: e.to_string(),
|
error: e.to_string(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
pub fn wrap_async<T: Serialize>(
|
pub fn wrap_async<T: Serialize>(
|
||||||
self,
|
self,
|
||||||
f: impl Future<Output = color_eyre::eyre::Result<(T, Vec<OwnedFd>)>> + Send + 'static,
|
f: impl Future<Output = Result<(T, Vec<OwnedFd>)>> + Send + 'static,
|
||||||
) {
|
) {
|
||||||
tokio::task::spawn(async move { self.0.send(map_method_return(f.await)) });
|
TOKIO.spawn(async move { self.0.send(map_method_return(f.await)) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn map_method_return<T: Serialize>(
|
fn map_method_return<T: Serialize>(
|
||||||
result: color_eyre::eyre::Result<(T, Vec<OwnedFd>)>,
|
result: Result<(T, Vec<OwnedFd>)>,
|
||||||
) -> Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError> {
|
) -> Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError> {
|
||||||
let (value, fds) = result.map_err(|e| ScenegraphError::MethodError {
|
let (value, fds) = result.map_err(|e| ScenegraphError::MemberError {
|
||||||
error: e.to_string(),
|
error: e.to_string(),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let serialized_value = serialize(value).map_err(|e| ScenegraphError::MethodError {
|
let serialized_value = serialize(value).map_err(|e| ScenegraphError::MemberError {
|
||||||
error: format!("Internal: Serialization failed: {e}"),
|
error: format!("Internal: Serialization failed: {e}"),
|
||||||
})?;
|
})?;
|
||||||
Ok((serialized_value, fds))
|
Ok((serialized_value, fds))
|
||||||
@@ -86,19 +89,29 @@ fn map_method_return<T: Serialize>(
|
|||||||
impl scenegraph::Scenegraph for Scenegraph {
|
impl scenegraph::Scenegraph for Scenegraph {
|
||||||
fn send_signal(
|
fn send_signal(
|
||||||
&self,
|
&self,
|
||||||
node: u64,
|
node_id: u64,
|
||||||
|
aspect_id: u64,
|
||||||
method: u64,
|
method: u64,
|
||||||
data: &[u8],
|
data: &[u8],
|
||||||
fds: Vec<OwnedFd>,
|
fds: Vec<OwnedFd>,
|
||||||
) -> Result<(), ScenegraphError> {
|
) -> Result<(), ScenegraphError> {
|
||||||
let Some(client) = self.get_client() else {
|
let Some(client) = self.get_client() else {
|
||||||
return Err(ScenegraphError::SignalNotFound);
|
return Err(ScenegraphError::NodeNotFound);
|
||||||
};
|
};
|
||||||
debug_span!("Handle signal", node, method).in_scope(|| {
|
debug_span!(
|
||||||
self.get_node(node)
|
"Handle signal",
|
||||||
|
aspect_id,
|
||||||
|
aspect_name = aspect_id_to_name(aspect_id),
|
||||||
|
node_id,
|
||||||
|
method,
|
||||||
|
signal_name = signal_id_to_name(method)
|
||||||
|
)
|
||||||
|
.in_scope(|| {
|
||||||
|
self.get_node(node_id)
|
||||||
.ok_or(ScenegraphError::NodeNotFound)?
|
.ok_or(ScenegraphError::NodeNotFound)?
|
||||||
.send_local_signal(
|
.send_local_signal(
|
||||||
client,
|
client,
|
||||||
|
aspect_id,
|
||||||
method,
|
method,
|
||||||
Message {
|
Message {
|
||||||
data: data.to_vec(),
|
data: data.to_vec(),
|
||||||
@@ -109,23 +122,32 @@ impl scenegraph::Scenegraph for Scenegraph {
|
|||||||
}
|
}
|
||||||
fn execute_method(
|
fn execute_method(
|
||||||
&self,
|
&self,
|
||||||
node: u64,
|
node_id: u64,
|
||||||
|
aspect_id: u64,
|
||||||
method: u64,
|
method: u64,
|
||||||
data: &[u8],
|
data: &[u8],
|
||||||
fds: Vec<OwnedFd>,
|
fds: Vec<OwnedFd>,
|
||||||
response: oneshot::Sender<Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError>>,
|
response: oneshot::Sender<Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError>>,
|
||||||
) {
|
) {
|
||||||
let Some(client) = self.get_client() else {
|
let Some(client) = self.get_client() else {
|
||||||
let _ = response.send(Err(ScenegraphError::MethodNotFound));
|
let _ = response.send(Err(ScenegraphError::NodeNotFound));
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
debug!(node, method, "Handle method");
|
debug!(
|
||||||
let Some(node) = self.get_node(node) else {
|
aspect_id,
|
||||||
|
aspect_name = aspect_id_to_name(aspect_id),
|
||||||
|
node_id,
|
||||||
|
method,
|
||||||
|
method_name = method_id_to_name(method),
|
||||||
|
"Handle method"
|
||||||
|
);
|
||||||
|
let Some(node) = self.get_node(node_id) else {
|
||||||
let _ = response.send(Err(ScenegraphError::NodeNotFound));
|
let _ = response.send(Err(ScenegraphError::NodeNotFound));
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
node.execute_local_method(
|
node.execute_local_method(
|
||||||
client,
|
client,
|
||||||
|
aspect_id,
|
||||||
method,
|
method,
|
||||||
Message {
|
Message {
|
||||||
data: data.to_vec(),
|
data: data.to_vec(),
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ use color_eyre::eyre::Result;
|
|||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
|
|
||||||
|
use crate::TOKIO;
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
pub fn new<
|
pub fn new<
|
||||||
F: FnOnce() -> S,
|
F: FnOnce() -> S,
|
||||||
@@ -13,7 +15,7 @@ pub fn new<
|
|||||||
async_future: A,
|
async_future: A,
|
||||||
) -> Result<JoinHandle<O>> {
|
) -> Result<JoinHandle<O>> {
|
||||||
#[cfg(not(feature = "profile_tokio"))]
|
#[cfg(not(feature = "profile_tokio"))]
|
||||||
let result = Ok(tokio::task::spawn(async_future));
|
let result = Ok(TOKIO.spawn(async_future));
|
||||||
#[cfg(feature = "profile_tokio")]
|
#[cfg(feature = "profile_tokio")]
|
||||||
let result = tokio::task::Builder::new()
|
let result = tokio::task::Builder::new()
|
||||||
.name(name_fn().as_ref())
|
.name(name_fn().as_ref())
|
||||||
|
|||||||
476
src/main.rs
476
src/main.rs
@@ -1,41 +1,87 @@
|
|||||||
#![allow(clippy::empty_docs)]
|
#![allow(clippy::empty_docs)]
|
||||||
|
pub mod bevy_plugin;
|
||||||
mod core;
|
mod core;
|
||||||
mod nodes;
|
mod nodes;
|
||||||
mod objects;
|
mod objects;
|
||||||
|
pub mod oxr_render_plugin;
|
||||||
mod session;
|
mod session;
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
mod wayland;
|
mod wayland;
|
||||||
|
|
||||||
use crate::core::destroy_queue;
|
use crate::core::destroy_queue;
|
||||||
use crate::nodes::items::camera;
|
// use crate::nodes::items::camera;
|
||||||
use crate::nodes::{audio, drawable, input};
|
use crate::nodes::input;
|
||||||
|
|
||||||
|
use bevy::a11y::AccessibilityPlugin;
|
||||||
|
use bevy::app::{
|
||||||
|
App, AppExit, PluginGroup, PostUpdate, ScheduleRunnerPlugin, Startup,
|
||||||
|
TerminalCtrlCHandlerPlugin, Update,
|
||||||
|
};
|
||||||
|
use bevy::asset::{load_internal_binary_asset, AssetApp as _, AssetPlugin, AssetServer, Handle};
|
||||||
|
use bevy::audio::AudioPlugin;
|
||||||
|
use bevy::color::Color;
|
||||||
|
use bevy::core_pipeline::{CorePipelinePlugin, Skybox};
|
||||||
|
use bevy::gizmos::GizmoPlugin;
|
||||||
|
use bevy::gltf::GltfPlugin;
|
||||||
|
use bevy::image::Image;
|
||||||
|
use bevy::input::InputPlugin;
|
||||||
|
use bevy::pbr::PbrPlugin;
|
||||||
|
use bevy::prelude::{
|
||||||
|
on_event, Camera3d, ClearColor, Commands, Entity, EventReader, HierarchyPlugin, ImagePlugin,
|
||||||
|
IntoSystemConfigs, Local, Query, Res, ResMut, Resource, TransformPlugin, With, World,
|
||||||
|
};
|
||||||
|
use bevy::render::pipelined_rendering::PipelinedRenderingPlugin;
|
||||||
|
use bevy::render::RenderPlugin;
|
||||||
|
use bevy::scene::ScenePlugin;
|
||||||
|
use bevy::text::{Font, FontLoader};
|
||||||
|
use bevy::time::Time;
|
||||||
|
use bevy::window::WindowPlugin;
|
||||||
|
use bevy::winit::{WakeUp, WinitPlugin};
|
||||||
|
use bevy::MinimalPlugins;
|
||||||
|
use bevy_mod_meshtext::MeshTextPlugin;
|
||||||
|
use bevy_mod_openxr::action_set_syncing::{OxrActionSyncingPlugin, OxrSyncActionSet};
|
||||||
|
use bevy_mod_openxr::exts::OxrExtensions;
|
||||||
|
use bevy_mod_openxr::features::overlay::{OxrOverlaySessionEvent, OxrOverlaySettings};
|
||||||
|
use bevy_mod_openxr::init::{should_run_frame_loop, OxrInitPlugin};
|
||||||
|
use bevy_mod_openxr::render::{update_cameras, OxrRenderPlugin};
|
||||||
|
use bevy_mod_openxr::resources::{OxrFrameState, OxrFrameWaiter, OxrGraphicsInfo};
|
||||||
|
use bevy_mod_openxr::session::OxrSession;
|
||||||
|
use bevy_mod_openxr::spaces::OxrSpaceExt;
|
||||||
|
use bevy_mod_openxr::types::{AppInfo, Version};
|
||||||
|
use bevy_mod_openxr::{add_xr_plugins, openxr_session_running};
|
||||||
|
use bevy_mod_xr::session::{XrFirst, XrPreDestroySession, XrSessionCreated};
|
||||||
|
use bevy_mod_xr::spaces::XrPrimaryReferenceSpace;
|
||||||
|
use bevy_plugin::{DbusConnection, DummyPbrPlugin, InputUpdate, StardustBevyPlugin, StardustFirst};
|
||||||
|
use bevy_sk::skytext::SphericalHarmonicsPlugin;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use core::client::Client;
|
use core::client::Client;
|
||||||
use core::task;
|
use core::task;
|
||||||
use directories::ProjectDirs;
|
use directories::ProjectDirs;
|
||||||
|
use nodes::audio::StardustSoundPlugin;
|
||||||
|
use nodes::drawable::lines::BevyLinesPlugin;
|
||||||
|
use nodes::drawable::model::StardustModelPlugin;
|
||||||
|
use nodes::drawable::text::StardustTextPlugin;
|
||||||
|
use objects::input::sk_controller::StardustControllerPlugin;
|
||||||
|
use objects::input::sk_hand::StardustHandPlugin;
|
||||||
use objects::ServerObjects;
|
use objects::ServerObjects;
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
|
use openxr::OverlaySessionCreateFlagsEXTX;
|
||||||
|
use oxr_render_plugin::StardustOxrRenderPlugin;
|
||||||
use session::{launch_start, save_session};
|
use session::{launch_start, save_session};
|
||||||
|
use stardust_xr::schemas::dbus::object_registry::ObjectRegistry;
|
||||||
use stardust_xr::server;
|
use stardust_xr::server;
|
||||||
|
use std::ops::Deref;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
|
||||||
use stereokit_rust::material::Material;
|
|
||||||
use stereokit_rust::shader::Shader;
|
|
||||||
use stereokit_rust::sk::{sk_quit, AppMode, DepthMode, OriginMode, QuitReason, SkSettings};
|
|
||||||
use stereokit_rust::system::{LogLevel, Renderer};
|
|
||||||
use stereokit_rust::tex::{SHCubemap, Tex, TexFormat, TexType};
|
|
||||||
use stereokit_rust::ui::Ui;
|
|
||||||
use stereokit_rust::util::{Color128, Time};
|
|
||||||
use tokio::net::UnixListener;
|
use tokio::net::UnixListener;
|
||||||
use tokio::sync::Notify;
|
use tokio::sync::Notify;
|
||||||
use tracing::metadata::LevelFilter;
|
use tracing::{debug_span, error, info, warn};
|
||||||
use tracing::{debug_span, error, info};
|
|
||||||
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
||||||
use zbus::fdo::ObjectManager;
|
use zbus::fdo::ObjectManager;
|
||||||
use zbus::Connection;
|
use zbus::Connection;
|
||||||
|
|
||||||
|
pub type DefaultMaterial = bevy_sk::vr_materials::PbrMaterial;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Parser)]
|
#[derive(Debug, Clone, Parser)]
|
||||||
#[clap(author, version, about, long_about = None)]
|
#[clap(author, version, about, long_about = None)]
|
||||||
struct CliArgs {
|
struct CliArgs {
|
||||||
@@ -43,6 +89,10 @@ struct CliArgs {
|
|||||||
#[clap(short, long, action)]
|
#[clap(short, long, action)]
|
||||||
flatscreen: bool,
|
flatscreen: bool,
|
||||||
|
|
||||||
|
/// Force Pipelined Rending, improving fps at the cost of latency
|
||||||
|
#[clap(short, long, action)]
|
||||||
|
pipelined_rendering: bool,
|
||||||
|
|
||||||
/// If monado insists on emulating them, set this flag...we want the raw input
|
/// If monado insists on emulating them, set this flag...we want the raw input
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
disable_controllers: bool,
|
disable_controllers: bool,
|
||||||
@@ -68,11 +118,26 @@ struct CliArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static STARDUST_INSTANCE: OnceCell<String> = OnceCell::new();
|
static STARDUST_INSTANCE: OnceCell<String> = OnceCell::new();
|
||||||
|
static TOKIO: RuntimeWrapper = RuntimeWrapper(OnceCell::new());
|
||||||
|
|
||||||
// #[tokio::main]
|
struct RuntimeWrapper(OnceCell<tokio::runtime::Runtime>);
|
||||||
#[tokio::main(flavor = "current_thread")]
|
impl Deref for RuntimeWrapper {
|
||||||
async fn main() {
|
type Target = tokio::runtime::Runtime;
|
||||||
color_eyre::install().unwrap();
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.0.get().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> color_eyre::Result<AppExit> {
|
||||||
|
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()?;
|
||||||
|
TOKIO.0.set(runtime).unwrap();
|
||||||
|
TOKIO.block_on(setup())
|
||||||
|
}
|
||||||
|
async fn setup() -> color_eyre::Result<AppExit> {
|
||||||
|
color_eyre::install()?;
|
||||||
|
|
||||||
let registry = tracing_subscriber::registry();
|
let registry = tracing_subscriber::registry();
|
||||||
|
|
||||||
@@ -137,13 +202,26 @@ async fn main() {
|
|||||||
.await
|
.await
|
||||||
.expect("Couldn't add the object manager");
|
.expect("Couldn't add the object manager");
|
||||||
|
|
||||||
|
let object_registry = ObjectRegistry::new(&dbus_connection).await.expect(
|
||||||
|
"Couldn't make the object registry to find all objects with given interfaces in d-bus",
|
||||||
|
);
|
||||||
|
|
||||||
let sk_ready_notifier = Arc::new(Notify::new());
|
let sk_ready_notifier = Arc::new(Notify::new());
|
||||||
let stereokit_loop = tokio::task::spawn_blocking({
|
let stereokit_loop = tokio::task::spawn_blocking({
|
||||||
let sk_ready_notifier = sk_ready_notifier.clone();
|
let sk_ready_notifier = sk_ready_notifier.clone();
|
||||||
let project_dirs = project_dirs.clone();
|
let project_dirs = project_dirs.clone();
|
||||||
let cli_args = cli_args.clone();
|
let cli_args = cli_args.clone();
|
||||||
let dbus_connection = dbus_connection.clone();
|
let dbus_connection = dbus_connection.clone();
|
||||||
move || stereokit_loop(sk_ready_notifier, project_dirs, cli_args, dbus_connection)
|
move || {
|
||||||
|
bevy_loop(
|
||||||
|
sk_ready_notifier,
|
||||||
|
project_dirs,
|
||||||
|
cli_args,
|
||||||
|
dbus_connection,
|
||||||
|
object_registry,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
});
|
});
|
||||||
sk_ready_notifier.notified().await;
|
sk_ready_notifier.notified().await;
|
||||||
let mut startup_children = project_dirs
|
let mut startup_children = project_dirs
|
||||||
@@ -151,10 +229,7 @@ async fn main() {
|
|||||||
.map(|project_dirs| launch_start(&cli_args, project_dirs))
|
.map(|project_dirs| launch_start(&cli_args, project_dirs))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
tokio::select! {
|
let exit = stereokit_loop.await?;
|
||||||
_ = stereokit_loop => (),
|
|
||||||
_ = tokio::signal::ctrl_c() => unsafe {sk_quit(QuitReason::SystemClose)},
|
|
||||||
}
|
|
||||||
info!("Stopping...");
|
info!("Stopping...");
|
||||||
if let Some(project_dirs) = project_dirs {
|
if let Some(project_dirs) = project_dirs {
|
||||||
save_session(&project_dirs).await;
|
save_session(&project_dirs).await;
|
||||||
@@ -164,63 +239,210 @@ async fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
info!("Cleanly shut down Stardust");
|
info!("Cleanly shut down Stardust");
|
||||||
|
Ok(exit)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stereokit_loop(
|
fn bevy_loop(
|
||||||
sk_ready_notifier: Arc<Notify>,
|
sk_ready_notifier: Arc<Notify>,
|
||||||
project_dirs: Option<ProjectDirs>,
|
project_dirs: Option<ProjectDirs>,
|
||||||
args: CliArgs,
|
args: CliArgs,
|
||||||
dbus_connection: Connection,
|
dbus_connection: Connection,
|
||||||
) {
|
object_registry: ObjectRegistry,
|
||||||
let sk = SkSettings::default()
|
headless: bool,
|
||||||
.app_name("Stardust XR")
|
) -> AppExit {
|
||||||
.mode(if args.flatscreen {
|
let mut bevy_app = App::new();
|
||||||
AppMode::Simulator
|
// let base = (DefaultPlugins)
|
||||||
} else {
|
// .build()
|
||||||
AppMode::XR
|
// .disable::<PipelinedRenderingPlugin>()
|
||||||
})
|
// .disable::<LogPlugin>()
|
||||||
.depth_mode(DepthMode::D32)
|
// .set({
|
||||||
.log_filter(match EnvFilter::from_default_env().max_level_hint() {
|
// let mut plugin = WinitPlugin::<WakeUp>::default();
|
||||||
Some(LevelFilter::ERROR) => LogLevel::Error,
|
// plugin.run_on_any_thread = true;
|
||||||
Some(LevelFilter::WARN) => LogLevel::Warning,
|
// plugin
|
||||||
Some(LevelFilter::INFO) => LogLevel::Inform,
|
// });
|
||||||
Some(LevelFilter::DEBUG) => LogLevel::Diagnostic,
|
let mut base = (MinimalPlugins)
|
||||||
Some(LevelFilter::TRACE) => LogLevel::Diagnostic,
|
.build()
|
||||||
Some(LevelFilter::OFF) => LogLevel::None,
|
.disable::<ScheduleRunnerPlugin>()
|
||||||
None => LogLevel::Warning,
|
.add(TransformPlugin)
|
||||||
})
|
.add(HierarchyPlugin)
|
||||||
.overlay_app(args.overlay_priority.is_some())
|
.add(InputPlugin)
|
||||||
.overlay_priority(args.overlay_priority.unwrap_or(u32::MAX))
|
.add(AccessibilityPlugin);
|
||||||
.disable_desktop_input_window(true)
|
base = match headless {
|
||||||
.origin(OriginMode::Local)
|
true => {
|
||||||
.init()
|
base.add(ScheduleRunnerPlugin {
|
||||||
.expect("StereoKit failed to initialize");
|
// In OpenXR framepacing we trust (else this will eat all of the cpu)
|
||||||
info!("Init StereoKit");
|
run_mode: bevy::app::RunMode::Loop { wait: None },
|
||||||
|
})
|
||||||
Renderer::multisample(0);
|
|
||||||
Material::default().shader(Shader::pbr_clip());
|
|
||||||
Ui::enable_far_interact(false);
|
|
||||||
|
|
||||||
// Skytex/light stuff
|
|
||||||
{
|
|
||||||
if let Some(sky) = project_dirs
|
|
||||||
.as_ref()
|
|
||||||
.map(|dirs| dirs.config_dir().join("skytex.hdr"))
|
|
||||||
.filter(|f| f.exists())
|
|
||||||
.and_then(|p| SHCubemap::from_cubemap_equirectangular(p, true, 100).ok())
|
|
||||||
{
|
|
||||||
sky.render_as_sky();
|
|
||||||
} else {
|
|
||||||
Renderer::skytex(Tex::gen_color(
|
|
||||||
Color128::BLACK,
|
|
||||||
1,
|
|
||||||
1,
|
|
||||||
TexType::Cubemap,
|
|
||||||
TexFormat::RGBA32,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
false => base.add(WindowPlugin::default()).add({
|
||||||
|
let mut plugin = WinitPlugin::<WakeUp>::default();
|
||||||
|
plugin.run_on_any_thread = true;
|
||||||
|
plugin
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
base = base
|
||||||
|
.add(TerminalCtrlCHandlerPlugin)
|
||||||
|
// might want to modify this in the future?
|
||||||
|
.add(AssetPlugin::default())
|
||||||
|
// will be replaced by bevy_mod_openxr when using OpenXR
|
||||||
|
.add(RenderPlugin::default())
|
||||||
|
.add(ImagePlugin::default())
|
||||||
|
.add(CorePipelinePlugin)
|
||||||
|
// very unsure what is needed here
|
||||||
|
.add(PbrPlugin {
|
||||||
|
// hoping that there is very little overdraw in stardust
|
||||||
|
prepass_enabled: false,
|
||||||
|
add_default_deferred_lighting_plugin: false,
|
||||||
|
use_gpu_instance_buffer_builder: false,
|
||||||
|
})
|
||||||
|
// .add(DummyPbrPlugin)
|
||||||
|
.add(ScenePlugin)
|
||||||
|
.add(GltfPlugin::default())
|
||||||
|
.add(AudioPlugin::default())
|
||||||
|
.add(GizmoPlugin)
|
||||||
|
.add(bevy_sk::vr_materials::SkMaterialPlugin {
|
||||||
|
replace_standard_material: true,
|
||||||
|
})
|
||||||
|
.add(bevy_sk::hand::HandPlugin)
|
||||||
|
.add(SphericalHarmonicsPlugin);
|
||||||
|
|
||||||
|
if args.pipelined_rendering {
|
||||||
|
base = base.add(PipelinedRenderingPlugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if args.flatscreen {
|
||||||
|
bevy_app.add_plugins(base);
|
||||||
|
} else {
|
||||||
|
bevy_app.add_plugins(
|
||||||
|
add_xr_plugins(base)
|
||||||
|
.set(OxrInitPlugin {
|
||||||
|
app_info: AppInfo {
|
||||||
|
name: "Stardust XR".into(),
|
||||||
|
version: Version(0, 44, 1),
|
||||||
|
},
|
||||||
|
exts: {
|
||||||
|
let mut exts = OxrExtensions::default();
|
||||||
|
exts.enable_hand_tracking();
|
||||||
|
if args.overlay_priority.is_some() {
|
||||||
|
exts.enable_extx_overlay();
|
||||||
|
}
|
||||||
|
exts
|
||||||
|
},
|
||||||
|
blend_modes: Some(vec![
|
||||||
|
openxr::EnvironmentBlendMode::ALPHA_BLEND,
|
||||||
|
openxr::EnvironmentBlendMode::OPAQUE,
|
||||||
|
]),
|
||||||
|
synchronous_pipeline_compilation: false,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.disable::<OxrRenderPlugin>()
|
||||||
|
.disable::<OxrActionSyncingPlugin>()
|
||||||
|
.add_after::<OxrInitPlugin>(StardustOxrRenderPlugin),
|
||||||
|
);
|
||||||
|
if let Some(priority) = args.overlay_priority {
|
||||||
|
bevy_app.insert_resource(OxrOverlaySettings {
|
||||||
|
session_layer_placement: priority,
|
||||||
|
flags: OverlaySessionCreateFlagsEXTX::EMPTY,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
bevy_app.add_event::<OxrSyncActionSet>();
|
||||||
|
bevy_app.add_plugins(bevy_xr_utils::hand_gizmos::HandGizmosPlugin);
|
||||||
|
}
|
||||||
|
bevy_app
|
||||||
|
.init_asset::<Font>()
|
||||||
|
.init_asset_loader::<FontLoader>();
|
||||||
|
bevy_app.add_plugins(MeshTextPlugin);
|
||||||
|
bevy_app.add_plugins(StardustBevyPlugin);
|
||||||
|
bevy_app.add_plugins((
|
||||||
|
BevyLinesPlugin,
|
||||||
|
StardustModelPlugin,
|
||||||
|
StardustHandPlugin,
|
||||||
|
StardustTextPlugin,
|
||||||
|
StardustSoundPlugin,
|
||||||
|
StardustControllerPlugin,
|
||||||
|
));
|
||||||
|
load_internal_binary_asset!(
|
||||||
|
bevy_app,
|
||||||
|
Handle::default(),
|
||||||
|
"nodes/drawable/assets/FiraMono-subset.ttf",
|
||||||
|
|bytes: &[u8], _path: String| { Font::try_from_bytes(bytes.to_vec()).unwrap() }
|
||||||
|
);
|
||||||
|
#[derive(Resource)]
|
||||||
|
struct SkyTexture(Handle<Image>);
|
||||||
|
// Skytex/light stuff
|
||||||
|
bevy_app.add_systems(
|
||||||
|
Startup,
|
||||||
|
move |assests: Res<AssetServer>, mut cmds: Commands| {
|
||||||
|
if let Some(sky) = project_dirs
|
||||||
|
.as_ref()
|
||||||
|
.map(|dirs| dirs.config_dir().join("skytex.hdr"))
|
||||||
|
.filter(|f| f.exists())
|
||||||
|
.map(|p| assests.load(p))
|
||||||
|
{
|
||||||
|
cmds.insert_resource(SkyTexture(sky));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
#[derive(Resource)]
|
||||||
|
struct RenderBackground(bool);
|
||||||
|
fn update_background(
|
||||||
|
graphics_info: Res<OxrGraphicsInfo>,
|
||||||
|
mut overlay_events: EventReader<OxrOverlaySessionEvent>,
|
||||||
|
mut last_hidden: Local<bool>,
|
||||||
|
cams: Query<Entity, With<Camera3d>>,
|
||||||
|
mut cmds: Commands,
|
||||||
|
) {
|
||||||
|
let env_hidden = graphics_info.blend_mode != openxr::EnvironmentBlendMode::OPAQUE
|
||||||
|
&& overlay_events
|
||||||
|
.read()
|
||||||
|
.last()
|
||||||
|
.map(
|
||||||
|
|OxrOverlaySessionEvent::MainSessionVisibilityChanged { visible, flags: _ }| {
|
||||||
|
*visible
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap_or(true);
|
||||||
|
if env_hidden && !*last_hidden {
|
||||||
|
cams.iter().for_each(|e| {
|
||||||
|
cmds.entity(e).remove::<Skybox>();
|
||||||
|
});
|
||||||
|
let _span = debug_span!("spawn");
|
||||||
|
cmds.insert_resource(ClearColor(Color::NONE));
|
||||||
|
}
|
||||||
|
*last_hidden = env_hidden;
|
||||||
|
}
|
||||||
|
bevy_app.add_systems(XrSessionCreated, update_background);
|
||||||
|
bevy_app.add_systems(
|
||||||
|
PostUpdate,
|
||||||
|
(|mut objects: ResMut<ServerObjects>,
|
||||||
|
ref_space: Res<XrPrimaryReferenceSpace>,
|
||||||
|
session: Res<OxrSession>| {
|
||||||
|
objects
|
||||||
|
.ref_space
|
||||||
|
.replace(unsafe { ref_space.as_openxr_space(&session) });
|
||||||
|
objects.view_space.replace(
|
||||||
|
session
|
||||||
|
.deref()
|
||||||
|
.deref()
|
||||||
|
.create_reference_space(
|
||||||
|
openxr::ReferenceSpaceType::VIEW,
|
||||||
|
openxr::Posef::IDENTITY,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.run_if(on_event::<bevy_mod_xr::session::XrSessionCreatedEvent>),
|
||||||
|
);
|
||||||
|
bevy_app.add_systems(XrPreDestroySession, |mut objetcs: ResMut<ServerObjects>| {
|
||||||
|
objetcs.ref_space = None;
|
||||||
|
objetcs.view_space = None;
|
||||||
|
});
|
||||||
|
bevy_app.add_systems(
|
||||||
|
Update,
|
||||||
|
update_background.run_if(on_event::<OxrOverlaySessionEvent>),
|
||||||
|
);
|
||||||
|
bevy_app.insert_resource(DbusConnection(dbus_connection.clone()));
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
let mut wayland = wayland::Wayland::new().expect("Could not initialize wayland");
|
let mut wayland = wayland::Wayland::new().expect("Could not initialize wayland");
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
@@ -228,62 +450,84 @@ fn stereokit_loop(
|
|||||||
sk_ready_notifier.notify_waiters();
|
sk_ready_notifier.notify_waiters();
|
||||||
info!("Stardust ready!");
|
info!("Stardust ready!");
|
||||||
|
|
||||||
let mut objects = ServerObjects::new(
|
let objects = ServerObjects::new(dbus_connection.clone());
|
||||||
dbus_connection.clone(),
|
fn sync_sets(session: Res<OxrSession>, mut events: EventReader<OxrSyncActionSet>) {
|
||||||
&sk,
|
let sets = events
|
||||||
args.disable_controllers,
|
.read()
|
||||||
args.disable_hands,
|
.map(|v| &v.0)
|
||||||
);
|
.map(openxr::ActiveActionSet::new)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if sets.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let mut last_frame_delta = Duration::ZERO;
|
if let Err(err) = session.sync_actions(&sets) {
|
||||||
let mut sleep_duration = Duration::ZERO;
|
warn!("error while syncing actionsets: {}", err.to_string());
|
||||||
while let Some(token) = sk.step() {
|
}
|
||||||
let _span = debug_span!("StereoKit step");
|
}
|
||||||
|
|
||||||
|
bevy_app.insert_resource(objects);
|
||||||
|
|
||||||
|
fn bevy_step(world: &mut World) {
|
||||||
|
let _span = debug_span!("Bevy step");
|
||||||
let _span = _span.enter();
|
let _span = _span.enter();
|
||||||
|
// camera::update(token);
|
||||||
camera::update(token);
|
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
wayland.frame_event();
|
wayland.frame_event();
|
||||||
destroy_queue::clear();
|
destroy_queue::clear();
|
||||||
|
|
||||||
objects.update(&sk, token);
|
let time = world.get_resource_mut::<OxrFrameState>().map(|mut s| {
|
||||||
|
let t = openxr::Time::from_nanos(
|
||||||
|
s.predicted_display_time.as_nanos() + s.predicted_display_period.as_nanos(),
|
||||||
|
);
|
||||||
|
s.predicted_display_time = t;
|
||||||
|
t
|
||||||
|
});
|
||||||
|
world.run_schedule(XrFirst);
|
||||||
|
if world
|
||||||
|
.run_system_cached(openxr_session_running)
|
||||||
|
.unwrap_or(true)
|
||||||
|
{
|
||||||
|
let _ = world.run_system_cached(sync_sets);
|
||||||
|
}
|
||||||
|
let thread = world
|
||||||
|
.run_system_cached(should_run_frame_loop)
|
||||||
|
.unwrap_or(true)
|
||||||
|
.then(|| world.remove_resource::<OxrFrameWaiter>())
|
||||||
|
.flatten()
|
||||||
|
.map(|mut waiter| {
|
||||||
|
TOKIO.spawn_blocking(move || {
|
||||||
|
let _span = debug_span!("frame eeping").entered();
|
||||||
|
let result = waiter.wait();
|
||||||
|
(waiter, result)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
world.run_schedule(InputUpdate);
|
||||||
|
debug_span!("update_objects").in_scope(|| {
|
||||||
|
let session = world.remove_resource::<OxrSession>();
|
||||||
|
let mut objects = world.remove_resource::<ServerObjects>().unwrap();
|
||||||
|
objects.update(session.as_deref(), time);
|
||||||
|
world.insert_resource(objects);
|
||||||
|
if let Some(session) = session {
|
||||||
|
world.insert_resource(session);
|
||||||
|
}
|
||||||
|
});
|
||||||
input::process_input();
|
input::process_input();
|
||||||
nodes::root::Root::send_frame_events(Time::get_step_unscaled());
|
nodes::root::Root::send_frame_events(world.resource::<Time>().delta_secs_f64());
|
||||||
adaptive_sleep(
|
if let Some((waiter, Ok(state))) = thread.map(|t| TOKIO.block_on(t).unwrap()) {
|
||||||
&mut last_frame_delta,
|
world.insert_resource(OxrFrameState(state));
|
||||||
&mut sleep_duration,
|
world.insert_resource(waiter);
|
||||||
Duration::from_micros(250),
|
if let Err(err) = world.run_system_cached(update_cameras) {
|
||||||
);
|
error!("error while running oxr update_cameras system: {err}");
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
wayland.update();
|
|
||||||
drawable::draw(token);
|
|
||||||
audio::update();
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Cleanly shut down StereoKit");
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
drop(wayland);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn adaptive_sleep(
|
|
||||||
last_frame_delta: &mut Duration,
|
|
||||||
sleep_duration: &mut Duration,
|
|
||||||
sleep_duration_increase: Duration,
|
|
||||||
) {
|
|
||||||
let frame_delta = Duration::from_secs_f64(Time::get_step_unscaled());
|
|
||||||
if *last_frame_delta < frame_delta {
|
|
||||||
if let Some(frame_delta_delta) = frame_delta.checked_sub(*last_frame_delta) {
|
|
||||||
if let Some(new_sleep_duration) = sleep_duration.checked_sub(frame_delta_delta) {
|
|
||||||
*sleep_duration = new_sleep_duration;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
#[cfg(feature = "wayland")]
|
||||||
*sleep_duration += sleep_duration_increase;
|
wayland.update();
|
||||||
}
|
}
|
||||||
|
bevy_app.add_systems(StardustFirst, bevy_step);
|
||||||
|
let out = bevy_app.run();
|
||||||
|
|
||||||
debug_span!("Sleep", ?sleep_duration, ?frame_delta, ?last_frame_delta).in_scope(|| {
|
#[cfg(feature = "wayland")]
|
||||||
*last_frame_delta = frame_delta;
|
drop(wayland);
|
||||||
std::thread::sleep(*sleep_duration); // to give clients a chance to even update anything before drawing
|
out
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use super::{Aspect, Node};
|
use super::{Aspect, AspectIdentifier, Node};
|
||||||
use crate::core::{client::Client, registry::Registry};
|
use crate::core::{client::Client, error::Result, registry::Registry};
|
||||||
use color_eyre::eyre::Result;
|
|
||||||
use std::{
|
use std::{
|
||||||
ops::Add,
|
ops::Add,
|
||||||
sync::{Arc, Weak},
|
sync::{Arc, Weak},
|
||||||
@@ -68,8 +67,31 @@ impl Alias {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl AspectIdentifier for Alias {
|
||||||
|
const ID: u64 = 0;
|
||||||
|
}
|
||||||
impl Aspect for Alias {
|
impl Aspect for Alias {
|
||||||
const NAME: &'static str = "Alias";
|
fn as_any(self: Arc<Self>) -> Arc<dyn std::any::Any + Send + Sync + 'static> {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
fn run_signal(
|
||||||
|
&self,
|
||||||
|
_calling_client: Arc<Client>,
|
||||||
|
_node: Arc<Node>,
|
||||||
|
_signal: u64,
|
||||||
|
_message: super::Message,
|
||||||
|
) -> Result<(), stardust_xr::scenegraph::ScenegraphError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn run_method(
|
||||||
|
&self,
|
||||||
|
_calling_client: Arc<Client>,
|
||||||
|
_node: Arc<Node>,
|
||||||
|
_method: u64,
|
||||||
|
_message: super::Message,
|
||||||
|
_response: crate::core::scenegraph::MethodResponseSender,
|
||||||
|
) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_original(node: Arc<Node>, stop_on_disabled: bool) -> Option<Arc<Node>> {
|
pub fn get_original(node: Arc<Node>, stop_on_disabled: bool) -> Option<Arc<Node>> {
|
||||||
@@ -106,7 +128,7 @@ impl AliasList {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.find(move |node| links_to(node.clone(), original.clone()))
|
.find(move |node| links_to(node.clone(), original.clone()))
|
||||||
}
|
}
|
||||||
pub fn get_from_aspect<A: Aspect>(&self, aspect: &A) -> Option<Arc<Node>> {
|
pub fn get_from_aspect<A: AspectIdentifier>(&self, aspect: &A) -> Option<Arc<Node>> {
|
||||||
self.0.get_valid_contents().into_iter().find(|node| {
|
self.0.get_valid_contents().into_iter().find(|node| {
|
||||||
let Some(node) = get_original(node.clone(), false) else {
|
let Some(node) = get_original(node.clone(), false) else {
|
||||||
return false;
|
return false;
|
||||||
@@ -120,7 +142,7 @@ impl AliasList {
|
|||||||
pub fn get_aliases(&self) -> Vec<Arc<Node>> {
|
pub fn get_aliases(&self) -> Vec<Arc<Node>> {
|
||||||
self.0.get_valid_contents()
|
self.0.get_valid_contents()
|
||||||
}
|
}
|
||||||
pub fn remove_aspect<A: Aspect>(&self, aspect: &A) {
|
pub fn remove_aspect<A: AspectIdentifier>(&self, aspect: &A) {
|
||||||
self.0.retain(|node| {
|
self.0.retain(|node| {
|
||||||
let Some(original) = get_original(node.clone(), false) else {
|
let Some(original) = get_original(node.clone(), false) else {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,109 +1,175 @@
|
|||||||
use super::{Aspect, Node};
|
use super::{Aspect, AspectIdentifier, Node};
|
||||||
use crate::core::client::Client;
|
use crate::core::client::Client;
|
||||||
use crate::core::destroy_queue;
|
use crate::core::destroy_queue;
|
||||||
|
use crate::core::error::{Result, ServerError};
|
||||||
use crate::core::registry::Registry;
|
use crate::core::registry::Registry;
|
||||||
use crate::core::resource::get_resource_file;
|
use crate::core::resource::get_resource_file;
|
||||||
use crate::create_interface;
|
use crate::nodes::spatial::{Spatial, Transform, SPATIAL_ASPECT_ALIAS_INFO};
|
||||||
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
|
use bevy::app::{App, Plugin, PostUpdate, PreUpdate};
|
||||||
use crate::nodes::spatial::{Spatial, Transform};
|
use bevy::asset::AssetServer;
|
||||||
use color_eyre::eyre::{eyre, Result};
|
use bevy::audio::{AudioPlayer, AudioSink, AudioSinkPlayback, PlaybackSettings, Volume};
|
||||||
use glam::{vec3, Vec4Swizzles};
|
use bevy::prelude::{Commands, Deref, Entity, Query, Res, Resource, Transform as BevyTransform};
|
||||||
|
use color_eyre::eyre::eyre;
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use parking_lot::Mutex;
|
|
||||||
use stardust_xr::values::ResourceID;
|
use stardust_xr::values::ResourceID;
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
use std::ops::DerefMut;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{ffi::OsStr, path::PathBuf};
|
use std::{ffi::OsStr, path::PathBuf};
|
||||||
use stereokit_rust::sound::{Sound as SkSound, SoundInst};
|
|
||||||
|
|
||||||
static SOUND_REGISTRY: Registry<Sound> = Registry::new();
|
static SOUND_REGISTRY: Registry<Sound> = Registry::new();
|
||||||
|
|
||||||
|
pub struct StardustSoundPlugin;
|
||||||
|
impl Plugin for StardustSoundPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
let (tx, rx) = crossbeam_channel::unbounded();
|
||||||
|
_ = SOUND_EVENT_SENDER.set(tx);
|
||||||
|
app.insert_resource(SoundEventReader(rx));
|
||||||
|
let (tx, rx) = crossbeam_channel::unbounded();
|
||||||
|
_ = SPAWN_SOUND_SENDER.set(tx);
|
||||||
|
app.insert_resource(SpawnSoundReader(rx));
|
||||||
|
app.add_systems(PostUpdate, update_sound_state);
|
||||||
|
app.add_systems(PreUpdate, spawn_sounds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_sounds(reader: Res<SpawnSoundReader>, mut cmds: Commands, asset_server: Res<AssetServer>) {
|
||||||
|
for sound in reader.try_iter() {
|
||||||
|
let e = cmds
|
||||||
|
.spawn((
|
||||||
|
BevyTransform::default(),
|
||||||
|
AudioPlayer::new(asset_server.load(sound.pending_audio_path.as_path())),
|
||||||
|
PlaybackSettings {
|
||||||
|
mode: bevy::audio::PlaybackMode::Once,
|
||||||
|
volume: Volume::new(sound.volume),
|
||||||
|
speed: 1.0,
|
||||||
|
paused: true,
|
||||||
|
spatial: true,
|
||||||
|
spatial_scale: None,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.id();
|
||||||
|
let _ = sound.entity.set(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_sound_state(
|
||||||
|
mut query: Query<&mut AudioSink>,
|
||||||
|
mut transforms: Query<&mut BevyTransform>,
|
||||||
|
mut cmds: Commands,
|
||||||
|
reader: Res<SoundEventReader>,
|
||||||
|
) {
|
||||||
|
for (entity, action) in reader.try_iter() {
|
||||||
|
match action {
|
||||||
|
SoundAction::Stop => {
|
||||||
|
let Ok(sink) = query.get_mut(entity) else {
|
||||||
|
error!("no audio sink to stop?");
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
sink.stop()
|
||||||
|
}
|
||||||
|
SoundAction::Play => {
|
||||||
|
// Idk let's hope this works?
|
||||||
|
cmds.entity(entity).remove::<AudioSink>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for sound in SOUND_REGISTRY.get_valid_contents() {
|
||||||
|
let Some(entity) = sound.entity.get().copied() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Ok(mut transform) = transforms.get_mut(entity) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
*transform = BevyTransform::from_matrix(sound.space.global_transform());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
stardust_xr_server_codegen::codegen_audio_protocol!();
|
stardust_xr_server_codegen::codegen_audio_protocol!();
|
||||||
pub struct Sound {
|
pub struct Sound {
|
||||||
space: Arc<Spatial>,
|
space: Arc<Spatial>,
|
||||||
|
|
||||||
volume: f32,
|
volume: f32,
|
||||||
pending_audio_path: PathBuf,
|
pending_audio_path: PathBuf,
|
||||||
sk_sound: OnceCell<SkSound>,
|
entity: OnceCell<Entity>,
|
||||||
instance: Mutex<Option<SoundInst>>,
|
}
|
||||||
stop: Mutex<Option<()>>,
|
static SPAWN_SOUND_SENDER: OnceCell<crossbeam_channel::Sender<Arc<Sound>>> = OnceCell::new();
|
||||||
play: Mutex<Option<()>>,
|
#[derive(Resource, Deref)]
|
||||||
|
struct SpawnSoundReader(crossbeam_channel::Receiver<Arc<Sound>>);
|
||||||
|
static SOUND_EVENT_SENDER: OnceCell<crossbeam_channel::Sender<(Entity, SoundAction)>> =
|
||||||
|
OnceCell::new();
|
||||||
|
#[derive(Resource, Deref)]
|
||||||
|
struct SoundEventReader(crossbeam_channel::Receiver<(Entity, SoundAction)>);
|
||||||
|
pub enum SoundAction {
|
||||||
|
// Pause and Resume?
|
||||||
|
Stop,
|
||||||
|
Play,
|
||||||
}
|
}
|
||||||
impl Sound {
|
impl Sound {
|
||||||
pub fn add_to(node: &Arc<Node>, resource_id: ResourceID) -> Result<Arc<Sound>> {
|
pub fn add_to(node: &Arc<Node>, resource_id: ResourceID) -> Result<Arc<Sound>> {
|
||||||
let pending_audio_path = get_resource_file(
|
let pending_audio_path = get_resource_file(
|
||||||
&resource_id,
|
&resource_id,
|
||||||
&*node.get_client().ok_or_else(|| eyre!("Client not found"))?,
|
&*node.get_client().ok_or(ServerError::NoClient)?,
|
||||||
&[OsStr::new("wav"), OsStr::new("mp3")],
|
&[OsStr::new("wav"), OsStr::new("mp3")],
|
||||||
)
|
)
|
||||||
.ok_or_else(|| eyre!("Resource not found"))?;
|
.ok_or(ServerError::NoResource)?;
|
||||||
let sound = Sound {
|
let sound = Sound {
|
||||||
space: node.get_aspect::<Spatial>().unwrap().clone(),
|
space: node.get_aspect::<Spatial>().unwrap().clone(),
|
||||||
volume: 1.0,
|
volume: 1.0,
|
||||||
pending_audio_path,
|
pending_audio_path,
|
||||||
sk_sound: OnceCell::new(),
|
entity: OnceCell::new(),
|
||||||
instance: Mutex::new(None),
|
|
||||||
stop: Mutex::new(None),
|
|
||||||
play: Mutex::new(None),
|
|
||||||
};
|
};
|
||||||
let sound_arc = SOUND_REGISTRY.add(sound);
|
let sound_arc = SOUND_REGISTRY.add(sound);
|
||||||
node.add_aspect_raw(sound_arc.clone());
|
node.add_aspect_raw(sound_arc.clone());
|
||||||
<Sound as SoundAspect>::add_node_members(node);
|
if let Some(sender) = SPAWN_SOUND_SENDER.get() {
|
||||||
|
sender
|
||||||
|
.send(sound_arc.clone())
|
||||||
|
.map_err(|_| eyre!("Unable to Spawn Audio Node"))?;
|
||||||
|
}
|
||||||
Ok(sound_arc)
|
Ok(sound_arc)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
fn update(&self) {
|
impl AspectIdentifier for Sound {
|
||||||
let sound = self
|
impl_aspect_for_sound_aspect_id! {}
|
||||||
.sk_sound
|
|
||||||
.get_or_init(|| SkSound::from_file(self.pending_audio_path.clone()).unwrap());
|
|
||||||
if self.stop.lock().take().is_some() {
|
|
||||||
if let Some(instance) = self.instance.lock().take() {
|
|
||||||
instance.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if self.instance.lock().is_none() && self.play.lock().take().is_some() {
|
|
||||||
let instance = sound.play(vec3(0.0, 0.0, 0.0), Some(self.volume));
|
|
||||||
self.instance.lock().replace(instance);
|
|
||||||
}
|
|
||||||
if let Some(instance) = self.instance.lock().deref_mut() {
|
|
||||||
instance.position(self.space.global_transform().w_axis.xyz());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
impl Aspect for Sound {
|
impl Aspect for Sound {
|
||||||
const NAME: &'static str = "Sound";
|
impl_aspect_for_sound_aspect! {}
|
||||||
}
|
}
|
||||||
impl SoundAspect for Sound {
|
impl SoundAspect for Sound {
|
||||||
fn play(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
|
fn play(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
|
||||||
let sound = node.get_aspect::<Sound>().unwrap();
|
let sound = node.get_aspect::<Sound>().unwrap();
|
||||||
sound.play.lock().replace(());
|
if let Some((sender, entity)) = SOUND_EVENT_SENDER
|
||||||
|
.get()
|
||||||
|
.and_then(|s| Some((s, *sound.entity.get()?)))
|
||||||
|
{
|
||||||
|
sender
|
||||||
|
.send((entity, SoundAction::Play))
|
||||||
|
.map_err(|_| eyre!("Unable to Play Audio"))?
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn stop(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
|
fn stop(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
|
||||||
let sound = node.get_aspect::<Sound>().unwrap();
|
let sound = node.get_aspect::<Sound>().unwrap();
|
||||||
sound.stop.lock().replace(());
|
if let Some((sender, entity)) = SOUND_EVENT_SENDER
|
||||||
|
.get()
|
||||||
|
.and_then(|s| Some((s, *sound.entity.get()?)))
|
||||||
|
{
|
||||||
|
sender
|
||||||
|
.send((entity, SoundAction::Stop))
|
||||||
|
.map_err(|_| eyre!("Unable to Stop Audio"))?
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Drop for Sound {
|
impl Drop for Sound {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if let Some(sk_sound) = self.sk_sound.take() {
|
if let Some(sk_sound) = self.entity.take() {
|
||||||
destroy_queue::add(sk_sound);
|
destroy_queue::add(sk_sound);
|
||||||
}
|
}
|
||||||
SOUND_REGISTRY.remove(self);
|
SOUND_REGISTRY.remove(self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update() {
|
impl InterfaceAspect for Interface {
|
||||||
for sound in SOUND_REGISTRY.get_valid_contents() {
|
|
||||||
sound.update()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
create_interface!(AudioInterface);
|
|
||||||
struct AudioInterface;
|
|
||||||
impl InterfaceAspect for AudioInterface {
|
|
||||||
#[doc = "Create a sound node. WAV and MP3 are supported."]
|
#[doc = "Create a sound node. WAV and MP3 are supported."]
|
||||||
fn create_sound(
|
fn create_sound(
|
||||||
_node: Arc<Node>,
|
_node: Arc<Node>,
|
||||||
|
|||||||
@@ -1,276 +0,0 @@
|
|||||||
use super::alias::AliasList;
|
|
||||||
use super::fields::Field;
|
|
||||||
use super::spatial::{parse_transform, Spatial};
|
|
||||||
use super::{Alias, Aspect, Node};
|
|
||||||
use crate::core::client::Client;
|
|
||||||
use crate::core::registry::Registry;
|
|
||||||
use crate::create_interface;
|
|
||||||
use crate::nodes::fields::FIELD_ALIAS_INFO;
|
|
||||||
use crate::nodes::spatial::Transform;
|
|
||||||
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
|
|
||||||
use color_eyre::eyre::{bail, ensure, eyre, Result};
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
use slotmap::{DefaultKey, Key, KeyData, SlotMap};
|
|
||||||
use stardust_xr::schemas::flex::flexbuffers;
|
|
||||||
use stardust_xr::values::Datamap;
|
|
||||||
use std::sync::{Arc, Weak};
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref KEYMAPS: Mutex<SlotMap<DefaultKey, String>> = Mutex::new(SlotMap::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: probably just use d-bus for this stuff (custom protocol for exporting spatials as refs) because the mask stuff is just too confusing
|
|
||||||
|
|
||||||
static PULSE_SENDER_REGISTRY: Registry<PulseSender> = Registry::new();
|
|
||||||
pub static PULSE_RECEIVER_REGISTRY: Registry<PulseReceiver> = Registry::new();
|
|
||||||
|
|
||||||
pub fn get_mask(datamap: &Datamap) -> Result<flexbuffers::MapReader<&[u8]>> {
|
|
||||||
flexbuffers::Reader::get_root(datamap.raw().as_slice())
|
|
||||||
.map_err(|_| eyre!("Mask is not a valid flexbuffer"))?
|
|
||||||
.get_map()
|
|
||||||
.map_err(|_| eyre!("Mask is not a valid map"))
|
|
||||||
}
|
|
||||||
pub fn mask_matches(mask_map_lesser: &Datamap, mask_map_greater: &Datamap) -> bool {
|
|
||||||
(|| -> Result<_> {
|
|
||||||
for key in get_mask(mask_map_lesser)?.iter_keys() {
|
|
||||||
let lesser_key = get_mask(mask_map_lesser)?.index(key)?;
|
|
||||||
let greater_key = get_mask(mask_map_greater)?.index(key)?;
|
|
||||||
// otherwise zero-length vectors don't count the same as a single type vector
|
|
||||||
if lesser_key.flexbuffer_type().is_heterogenous_vector()
|
|
||||||
&& lesser_key.as_vector().is_empty()
|
|
||||||
&& greater_key.flexbuffer_type().is_vector()
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if !lesser_key.flexbuffer_type().is_null()
|
|
||||||
&& lesser_key.flexbuffer_type() != greater_key.flexbuffer_type()
|
|
||||||
{
|
|
||||||
return Err(flexbuffers::ReaderError::InvalidPackedType {}.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})()
|
|
||||||
.is_ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
stardust_xr_server_codegen::codegen_data_protocol!();
|
|
||||||
|
|
||||||
pub struct PulseSender {
|
|
||||||
node: Weak<Node>,
|
|
||||||
pub mask: Datamap,
|
|
||||||
aliases: AliasList,
|
|
||||||
field_aliases: AliasList,
|
|
||||||
}
|
|
||||||
impl PulseSender {
|
|
||||||
pub fn add_to(node: &Arc<Node>, mask: Datamap) -> Result<Arc<PulseSender>> {
|
|
||||||
let sender = PulseSender {
|
|
||||||
node: Arc::downgrade(node),
|
|
||||||
mask,
|
|
||||||
aliases: AliasList::default(),
|
|
||||||
field_aliases: AliasList::default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// <PulseSender as PulseSenderAspect>::add_node_members(node);
|
|
||||||
let sender = PULSE_SENDER_REGISTRY.add(sender);
|
|
||||||
node.add_aspect_raw(sender.clone());
|
|
||||||
for receiver in PULSE_RECEIVER_REGISTRY.get_valid_contents() {
|
|
||||||
sender.handle_new_receiver(&receiver);
|
|
||||||
}
|
|
||||||
Ok(sender.clone())
|
|
||||||
}
|
|
||||||
fn handle_new_receiver(&self, receiver: &PulseReceiver) {
|
|
||||||
if !mask_matches(&self.mask, &receiver.mask) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let Some(tx_node) = self.node.upgrade() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let Some(tx_client) = tx_node.get_client() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let Some(rx_node) = receiver.node.upgrade() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
// Receiver itself
|
|
||||||
let Ok(rx_alias) = Alias::create(
|
|
||||||
&rx_node,
|
|
||||||
&tx_client,
|
|
||||||
PULSE_RECEIVER_ASPECT_ALIAS_INFO.clone(),
|
|
||||||
Some(&self.aliases),
|
|
||||||
) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Receiver's field
|
|
||||||
let Ok(rx_field_alias) = Alias::create(
|
|
||||||
&rx_node
|
|
||||||
.get_aspect::<PulseReceiver>()
|
|
||||||
.unwrap()
|
|
||||||
.field
|
|
||||||
.spatial
|
|
||||||
.node()
|
|
||||||
.unwrap(),
|
|
||||||
&tx_client,
|
|
||||||
FIELD_ALIAS_INFO.clone(),
|
|
||||||
Some(&self.aliases),
|
|
||||||
) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let _ = pulse_sender_client::new_receiver(&tx_node, &rx_alias, &rx_field_alias);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_drop_receiver(&self, receiver: &PulseReceiver) {
|
|
||||||
let Some(node) = receiver.node.upgrade() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
self.aliases.remove_aspect(receiver);
|
|
||||||
self.field_aliases.remove_aspect(receiver.field.as_ref());
|
|
||||||
let Some(tx_node) = self.node.upgrade() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let _ = pulse_sender_client::drop_receiver(&tx_node, node.get_id());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Aspect for PulseSender {
|
|
||||||
const NAME: &'static str = "PulseSender";
|
|
||||||
}
|
|
||||||
impl PulseSenderAspect for PulseSender {}
|
|
||||||
impl Drop for PulseSender {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
PULSE_SENDER_REGISTRY.remove(self);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PulseReceiver {
|
|
||||||
pub node: Weak<Node>,
|
|
||||||
pub field: Arc<Field>,
|
|
||||||
pub mask: Datamap,
|
|
||||||
}
|
|
||||||
impl PulseReceiver {
|
|
||||||
pub fn add_to(
|
|
||||||
node: &Arc<Node>,
|
|
||||||
field: Arc<Field>,
|
|
||||||
mask: Datamap,
|
|
||||||
) -> Result<Arc<PulseReceiver>> {
|
|
||||||
let receiver = PulseReceiver {
|
|
||||||
node: Arc::downgrade(node),
|
|
||||||
field,
|
|
||||||
mask,
|
|
||||||
};
|
|
||||||
let receiver = PULSE_RECEIVER_REGISTRY.add(receiver);
|
|
||||||
|
|
||||||
<PulseReceiver as PulseReceiverAspect>::add_node_members(node);
|
|
||||||
node.add_aspect_raw(receiver.clone());
|
|
||||||
for sender in PULSE_SENDER_REGISTRY.get_valid_contents() {
|
|
||||||
sender.handle_new_receiver(&receiver);
|
|
||||||
}
|
|
||||||
Ok(receiver)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Aspect for PulseReceiver {
|
|
||||||
const NAME: &'static str = "PulseReceiver";
|
|
||||||
}
|
|
||||||
impl PulseReceiverAspect for PulseReceiver {
|
|
||||||
fn send_data(
|
|
||||||
node: Arc<Node>,
|
|
||||||
_calling_client: Arc<Client>,
|
|
||||||
sender: Arc<Node>,
|
|
||||||
data: Datamap,
|
|
||||||
) -> Result<()> {
|
|
||||||
let this_receiver = node.get_aspect::<PulseReceiver>().unwrap();
|
|
||||||
|
|
||||||
ensure!(
|
|
||||||
mask_matches(&this_receiver.mask, &data),
|
|
||||||
"Message ({data:?}) does not contain the same keys as the receiver's mask ({:?})",
|
|
||||||
this_receiver.mask
|
|
||||||
);
|
|
||||||
pulse_receiver_client::data(&node, &sender, &data)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Drop for PulseReceiver {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
PULSE_RECEIVER_REGISTRY.remove(self);
|
|
||||||
for sender in PULSE_SENDER_REGISTRY.get_valid_contents() {
|
|
||||||
sender.handle_drop_receiver(self);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
create_interface!(DataInterface);
|
|
||||||
struct DataInterface;
|
|
||||||
impl InterfaceAspect for DataInterface {
|
|
||||||
fn create_pulse_sender(
|
|
||||||
_node: Arc<Node>,
|
|
||||||
calling_client: Arc<Client>,
|
|
||||||
id: u64,
|
|
||||||
parent: Arc<Node>,
|
|
||||||
transform: Transform,
|
|
||||||
mask: Datamap,
|
|
||||||
) -> Result<()> {
|
|
||||||
get_mask(&mask)?;
|
|
||||||
let node = Node::from_id(&calling_client, id, true);
|
|
||||||
let parent = parent.get_aspect::<Spatial>()?;
|
|
||||||
let transform = transform.to_mat4(true, true, false);
|
|
||||||
|
|
||||||
let node = node.add_to_scenegraph()?;
|
|
||||||
Spatial::add_to(&node, Some(parent.clone()), transform, false);
|
|
||||||
PulseSender::add_to(&node, mask)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_pulse_receiver(
|
|
||||||
_node: Arc<Node>,
|
|
||||||
calling_client: Arc<Client>,
|
|
||||||
id: u64,
|
|
||||||
parent: Arc<Node>,
|
|
||||||
transform: Transform,
|
|
||||||
field: Arc<Node>,
|
|
||||||
mask: Datamap,
|
|
||||||
) -> Result<()> {
|
|
||||||
get_mask(&mask)?;
|
|
||||||
let node = Node::from_id(&calling_client, id, true);
|
|
||||||
let parent = parent.get_aspect::<Spatial>()?;
|
|
||||||
let transform = parse_transform(transform, true, true, false);
|
|
||||||
let field = field.get_aspect::<Field>()?;
|
|
||||||
|
|
||||||
let node = node.add_to_scenegraph()?;
|
|
||||||
Spatial::add_to(&node, Some(parent.clone()), transform, false);
|
|
||||||
PulseReceiver::add_to(&node, field, mask)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn register_keymap(
|
|
||||||
_node: Arc<Node>,
|
|
||||||
_calling_client: Arc<Client>,
|
|
||||||
keymap: String,
|
|
||||||
) -> Result<u64> {
|
|
||||||
let mut keymaps = KEYMAPS.lock();
|
|
||||||
if let Some(found_keymap_id) = keymaps
|
|
||||||
.iter()
|
|
||||||
.filter(|(_k, v)| *v == &keymap)
|
|
||||||
.map(|(k, _v)| k)
|
|
||||||
.last()
|
|
||||||
{
|
|
||||||
return Ok(found_keymap_id.data().as_ffi());
|
|
||||||
}
|
|
||||||
|
|
||||||
let key = keymaps.insert(keymap);
|
|
||||||
Ok(key.data().as_ffi())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_keymap(
|
|
||||||
_node: Arc<Node>,
|
|
||||||
_calling_client: Arc<Client>,
|
|
||||||
keymap_id: u64,
|
|
||||||
) -> Result<String> {
|
|
||||||
let keymaps = KEYMAPS.lock();
|
|
||||||
let Some(keymap) = keymaps.get(KeyData::from_ffi(keymap_id).into()) else {
|
|
||||||
bail!("Could not find keymap. Try registering it")
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(keymap.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BIN
src/nodes/drawable/assets/FiraMono-subset.ttf
Normal file
BIN
src/nodes/drawable/assets/FiraMono-subset.ttf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,16 +1,26 @@
|
|||||||
use super::{Line, LinesAspect};
|
use super::{Line, LinesAspect};
|
||||||
|
use crate::core::error::Result;
|
||||||
|
use crate::DefaultMaterial;
|
||||||
use crate::{
|
use crate::{
|
||||||
|
bevy_plugin::{StardustExtract, TemporaryEntity, ViewLocation},
|
||||||
core::{client::Client, registry::Registry},
|
core::{client::Client, registry::Registry},
|
||||||
nodes::{spatial::Spatial, Aspect, Node},
|
nodes::{spatial::Spatial, Node},
|
||||||
};
|
};
|
||||||
use color_eyre::eyre::Result;
|
use bevy::{
|
||||||
use glam::Vec3;
|
app::Plugin,
|
||||||
|
asset::{Assets, RenderAssetUsages},
|
||||||
|
color::{Color, ColorToComponents, Srgba},
|
||||||
|
math::{bounding::Aabb3d, Isometry3d},
|
||||||
|
pbr::MeshMaterial3d,
|
||||||
|
prelude::{
|
||||||
|
AlphaMode, Commands, GlobalTransform, Mesh, Mesh3d, ResMut, Single, Transform, With,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use glam::{Vec3, Vec3A};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use prisma::Lerp;
|
use prisma::Lerp;
|
||||||
use std::{collections::VecDeque, sync::Arc};
|
use std::{collections::VecDeque, sync::Arc};
|
||||||
use stereokit_rust::{
|
use tracing::{debug_span, info};
|
||||||
maths::Bounds, sk::MainThreadToken, system::LinePoint as SkLinePoint, util::Color128,
|
|
||||||
};
|
|
||||||
|
|
||||||
static LINES_REGISTRY: Registry<Lines> = Registry::new();
|
static LINES_REGISTRY: Registry<Lines> = Registry::new();
|
||||||
|
|
||||||
@@ -19,76 +29,126 @@ pub struct Lines {
|
|||||||
data: Mutex<Vec<Line>>,
|
data: Mutex<Vec<Line>>,
|
||||||
}
|
}
|
||||||
impl Lines {
|
impl Lines {
|
||||||
|
#[tracing::instrument]
|
||||||
pub fn add_to(node: &Arc<Node>, lines: Vec<Line>) -> Result<Arc<Lines>> {
|
pub fn add_to(node: &Arc<Node>, lines: Vec<Line>) -> Result<Arc<Lines>> {
|
||||||
let _ = node
|
*node
|
||||||
.get_aspect::<Spatial>()
|
.get_aspect::<Spatial>()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.bounding_box_calc
|
.bounding_box_calc
|
||||||
.set(|node| {
|
.lock() = {
|
||||||
let mut bounds = Bounds::default();
|
if let Ok(lines) = node.get_aspect::<Lines>() {
|
||||||
if let Ok(lines) = node.get_aspect::<Lines>() {
|
Aabb3d::from_point_cloud(Isometry3d::IDENTITY, {
|
||||||
for line in &*lines.data.lock() {
|
let _span = debug_span!("add_to data lock").entered();
|
||||||
for point in &line.points {
|
lines
|
||||||
bounds.grown_point(Vec3::from(point.point));
|
.data
|
||||||
}
|
.lock()
|
||||||
}
|
.iter()
|
||||||
}
|
.flat_map(|line| line.points.iter())
|
||||||
bounds
|
.map(|point| Vec3A::from(point.point))
|
||||||
});
|
})
|
||||||
|
} else {
|
||||||
|
Aabb3d::new(Vec3A::ZERO, Vec3A::ZERO)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let lines = LINES_REGISTRY.add(Lines {
|
let lines = LINES_REGISTRY.add(Lines {
|
||||||
space: node.get_aspect::<Spatial>()?.clone(),
|
space: node.get_aspect::<Spatial>()?.clone(),
|
||||||
data: Mutex::new(lines),
|
data: Mutex::new(lines),
|
||||||
});
|
});
|
||||||
<Lines as LinesAspect>::add_node_members(node);
|
|
||||||
node.add_aspect_raw(lines.clone());
|
node.add_aspect_raw(lines.clone());
|
||||||
|
|
||||||
Ok(lines)
|
Ok(lines)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&self, token: &MainThreadToken) {
|
fn draw(&self, mesh: &mut Mesh, view: &GlobalTransform) -> Transform {
|
||||||
let transform_mat = self.space.global_transform();
|
let transform_mat = self.space.global_transform();
|
||||||
|
let _span = debug_span!("draw data lock").entered();
|
||||||
let data = self.data.lock().clone();
|
let data = self.data.lock().clone();
|
||||||
|
drop(_span);
|
||||||
|
let global_to_view = view.compute_matrix().inverse();
|
||||||
|
let local_to_view = transform_mat.inverse() * global_to_view;
|
||||||
|
let view_to_local = local_to_view.inverse();
|
||||||
for line in &data {
|
for line in &data {
|
||||||
let mut points: VecDeque<SkLinePoint> = line
|
let mut points: VecDeque<BevyLinePoint> = line
|
||||||
.points
|
.points
|
||||||
.iter()
|
.iter()
|
||||||
.map(|p| SkLinePoint {
|
.map(|p| BevyLinePoint {
|
||||||
pt: transform_mat.transform_point3(Vec3::from(p.point)).into(),
|
pt: transform_mat.transform_point3(Vec3::from(p.point)),
|
||||||
thickness: p.thickness,
|
thickness: p.thickness,
|
||||||
color: Color128::new(p.color.c.r, p.color.c.g, p.color.c.b, p.color.a).into(),
|
color: Srgba::new(p.color.c.r, p.color.c.g, p.color.c.b, p.color.a),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
if line.cyclic && !points.is_empty() {
|
if line.cyclic && !points.is_empty() {
|
||||||
let first = line.points.first().unwrap();
|
let first = line.points.first().unwrap();
|
||||||
let last = line.points.last().unwrap();
|
let last = line.points.last().unwrap();
|
||||||
|
|
||||||
let color = Color128 {
|
let color = Srgba {
|
||||||
r: first.color.c.r.lerp(&last.color.c.r, 0.5),
|
red: first.color.c.r.lerp(&last.color.c.r, 0.5),
|
||||||
g: first.color.c.g.lerp(&last.color.c.g, 0.5),
|
green: first.color.c.g.lerp(&last.color.c.g, 0.5),
|
||||||
b: first.color.c.b.lerp(&last.color.c.b, 0.5),
|
blue: first.color.c.b.lerp(&last.color.c.b, 0.5),
|
||||||
a: first.color.a.lerp(&last.color.a, 0.5),
|
alpha: first.color.a.lerp(&last.color.a, 0.5),
|
||||||
};
|
};
|
||||||
let connect_point = SkLinePoint {
|
let connect_point = BevyLinePoint {
|
||||||
pt: transform_mat
|
pt: transform_mat.transform_point3(
|
||||||
.transform_point3(Vec3::from(first.point).lerp(Vec3::from(last.point), 0.5))
|
Vec3::from(first.point).lerp(Vec3::from(last.point), 0.5),
|
||||||
.into(),
|
),
|
||||||
thickness: (first.thickness + last.thickness) * 0.5,
|
thickness: (first.thickness + last.thickness) * 0.5,
|
||||||
color: color.into(),
|
color,
|
||||||
};
|
};
|
||||||
points.push_front(connect_point);
|
points.push_front(connect_point);
|
||||||
points.push_back(connect_point);
|
points.push_back(connect_point);
|
||||||
}
|
}
|
||||||
stereokit_rust::system::Lines::add_list(token, points.make_contiguous());
|
let mut last_points: Option<(Vec3A, Vec3, Srgba)> = None;
|
||||||
|
let mut vertecies: Vec<[f32; 3]> = Vec::new();
|
||||||
|
let mut colors: Vec<[f32; 4]> = Vec::new();
|
||||||
|
let mut normals: Vec<[f32; 3]> = Vec::new();
|
||||||
|
for point in points.into_iter() {
|
||||||
|
let pt_view = local_to_view.transform_point3a(point.pt.into());
|
||||||
|
let point1_view = pt_view + (Vec3A::Y * (point.thickness / 2.0));
|
||||||
|
let point2_view = pt_view + (Vec3A::NEG_Y * (point.thickness / 2.0));
|
||||||
|
let point1 = view_to_local.transform_point3a(point1_view);
|
||||||
|
let point2 = view_to_local.transform_point3a(point2_view);
|
||||||
|
if let Some((last1, last2, last_color)) = last_points.take() {
|
||||||
|
let normal = view_to_local.transform_vector3a(Vec3A::Z).to_array();
|
||||||
|
for _ in 0..6 {
|
||||||
|
normals.push(normal);
|
||||||
|
}
|
||||||
|
vertecies.push(last1.to_array());
|
||||||
|
vertecies.push(point1.to_array());
|
||||||
|
vertecies.push(last2.to_array());
|
||||||
|
|
||||||
|
vertecies.push(last2.to_array());
|
||||||
|
vertecies.push(point1.to_array());
|
||||||
|
vertecies.push(point2.to_array());
|
||||||
|
|
||||||
|
colors.push(last_color.to_f32_array());
|
||||||
|
colors.push(point.color.to_f32_array());
|
||||||
|
colors.push(last_color.to_f32_array());
|
||||||
|
|
||||||
|
colors.push(last_color.to_f32_array());
|
||||||
|
colors.push(point.color.to_f32_array());
|
||||||
|
colors.push(point.color.to_f32_array());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertecies);
|
||||||
|
mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, colors);
|
||||||
|
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
|
||||||
}
|
}
|
||||||
|
GlobalTransform::from(transform_mat).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Aspect for Lines {
|
#[derive(Clone, Copy)]
|
||||||
const NAME: &'static str = "Lines";
|
struct BevyLinePoint {
|
||||||
|
pt: Vec3,
|
||||||
|
color: Srgba,
|
||||||
|
thickness: f32,
|
||||||
}
|
}
|
||||||
impl LinesAspect for Lines {
|
impl LinesAspect for Lines {
|
||||||
|
#[tracing::instrument]
|
||||||
fn set_lines(node: Arc<Node>, _calling_client: Arc<Client>, lines: Vec<Line>) -> Result<()> {
|
fn set_lines(node: Arc<Node>, _calling_client: Arc<Client>, lines: Vec<Line>) -> Result<()> {
|
||||||
let lines_aspect = node.get_aspect::<Lines>()?;
|
let lines_aspect = node.get_aspect::<Lines>()?;
|
||||||
|
let _span = debug_span!("set_lines data lock").entered();
|
||||||
*lines_aspect.data.lock() = lines;
|
*lines_aspect.data.lock() = lines;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -99,12 +159,57 @@ impl Drop for Lines {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_all(token: &MainThreadToken) {
|
pub fn draw_all(
|
||||||
for lines in LINES_REGISTRY.get_valid_contents() {
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<DefaultMaterial>>,
|
||||||
|
mut cmds: Commands,
|
||||||
|
hmd: Option<Single<&GlobalTransform, With<ViewLocation>>>,
|
||||||
|
) {
|
||||||
|
let Some(hmd) = hmd else { return };
|
||||||
|
let material = DefaultMaterial {
|
||||||
|
color: Color::WHITE,
|
||||||
|
alpha_mode: AlphaMode::Blend,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let mat_handle = materials.add(material);
|
||||||
|
let _span = debug_span!("line registry get valid contents").entered();
|
||||||
|
let vec = LINES_REGISTRY.get_valid_contents();
|
||||||
|
drop(_span);
|
||||||
|
info!("len {}", vec.len());
|
||||||
|
for lines in vec {
|
||||||
|
let _span = debug_span!("outer if span").entered();
|
||||||
if let Some(node) = lines.space.node() {
|
if let Some(node) = lines.space.node() {
|
||||||
if node.enabled() {
|
let _span = debug_span!("lines_data lock").entered();
|
||||||
lines.draw(token);
|
let w = lines.data.lock().is_empty();
|
||||||
|
drop(_span);
|
||||||
|
let _span = debug_span!("node enabled check").entered();
|
||||||
|
let e = node.enabled();
|
||||||
|
drop(_span);
|
||||||
|
let _span = debug_span!("if span").entered();
|
||||||
|
if e && !w {
|
||||||
|
let _span = debug_span!("create line mesh").entered();
|
||||||
|
info!("spawning line");
|
||||||
|
// Does this rebuild the mesh every frame? yes, is this problematic? probably,
|
||||||
|
// would a shader work better? yes, do i care? not right now
|
||||||
|
let mut mesh = Mesh::new(
|
||||||
|
bevy::render::mesh::PrimitiveTopology::TriangleList,
|
||||||
|
RenderAssetUsages::RENDER_WORLD,
|
||||||
|
);
|
||||||
|
let transform = lines.draw(&mut mesh, &hmd);
|
||||||
|
let mesh_handle = meshes.add(mesh);
|
||||||
|
cmds.spawn((
|
||||||
|
Mesh3d(mesh_handle),
|
||||||
|
MeshMaterial3d(mat_handle.clone()),
|
||||||
|
TemporaryEntity,
|
||||||
|
transform,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub struct BevyLinesPlugin;
|
||||||
|
impl Plugin for BevyLinesPlugin {
|
||||||
|
fn build(&self, app: &mut bevy::prelude::App) {
|
||||||
|
app.add_systems(StardustExtract, draw_all);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,55 +1,57 @@
|
|||||||
pub mod lines;
|
pub mod lines;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
pub mod shader_manipulation;
|
|
||||||
pub mod shaders;
|
|
||||||
pub mod text;
|
pub mod text;
|
||||||
|
|
||||||
use self::{lines::Lines, model::Model, text::Text};
|
use self::{lines::Lines, model::Model, text::Text};
|
||||||
use super::{
|
use super::{
|
||||||
spatial::{Spatial, Transform},
|
spatial::{Spatial, Transform},
|
||||||
Node,
|
Aspect, AspectIdentifier, Node,
|
||||||
|
};
|
||||||
|
use crate::core::{
|
||||||
|
client::Client,
|
||||||
|
error::{Result, ServerError},
|
||||||
|
resource::get_resource_file,
|
||||||
};
|
};
|
||||||
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
|
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
|
||||||
use crate::{
|
use model::ModelPart;
|
||||||
core::{client::Client, resource::get_resource_file},
|
|
||||||
create_interface,
|
|
||||||
};
|
|
||||||
use color_eyre::eyre::{self, Result};
|
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use stardust_xr::values::ResourceID;
|
use stardust_xr::values::ResourceID;
|
||||||
use std::{ffi::OsStr, path::PathBuf, sync::Arc};
|
use std::{ffi::OsStr, path::PathBuf, sync::Arc};
|
||||||
use stereokit_rust::{sk::MainThreadToken, system::Renderer, tex::SHCubemap};
|
|
||||||
|
|
||||||
// #[instrument(level = "debug", skip(sk))]
|
|
||||||
pub fn draw(token: &MainThreadToken) {
|
|
||||||
lines::draw_all(token);
|
|
||||||
model::draw_all(token);
|
|
||||||
text::draw_all(token);
|
|
||||||
|
|
||||||
if let Some(skytex) = QUEUED_SKYTEX.lock().take() {
|
|
||||||
if let Ok(skytex) = SHCubemap::from_cubemap_equirectangular(skytex, true, 100) {
|
|
||||||
Renderer::skytex(skytex.tex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(skylight) = QUEUED_SKYLIGHT.lock().take() {
|
|
||||||
if let Ok(skylight) = SHCubemap::from_cubemap_equirectangular(skylight, true, 100) {
|
|
||||||
Renderer::skylight(skylight.sh);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static QUEUED_SKYLIGHT: Mutex<Option<PathBuf>> = Mutex::new(None);
|
static QUEUED_SKYLIGHT: Mutex<Option<PathBuf>> = Mutex::new(None);
|
||||||
static QUEUED_SKYTEX: Mutex<Option<PathBuf>> = Mutex::new(None);
|
static QUEUED_SKYTEX: Mutex<Option<PathBuf>> = Mutex::new(None);
|
||||||
|
|
||||||
stardust_xr_server_codegen::codegen_drawable_protocol!();
|
stardust_xr_server_codegen::codegen_drawable_protocol!();
|
||||||
create_interface!(DrawableInterface);
|
|
||||||
|
|
||||||
pub struct DrawableInterface;
|
impl AspectIdentifier for Lines {
|
||||||
impl InterfaceAspect for DrawableInterface {
|
impl_aspect_for_lines_aspect_id! {}
|
||||||
|
}
|
||||||
|
impl Aspect for Lines {
|
||||||
|
impl_aspect_for_lines_aspect! {}
|
||||||
|
}
|
||||||
|
impl AspectIdentifier for Model {
|
||||||
|
impl_aspect_for_model_aspect_id! {}
|
||||||
|
}
|
||||||
|
impl Aspect for Model {
|
||||||
|
impl_aspect_for_model_aspect! {}
|
||||||
|
}
|
||||||
|
impl AspectIdentifier for ModelPart {
|
||||||
|
impl_aspect_for_model_part_aspect_id! {}
|
||||||
|
}
|
||||||
|
impl Aspect for ModelPart {
|
||||||
|
impl_aspect_for_model_part_aspect! {}
|
||||||
|
}
|
||||||
|
impl AspectIdentifier for Text {
|
||||||
|
impl_aspect_for_text_aspect_id! {}
|
||||||
|
}
|
||||||
|
impl Aspect for Text {
|
||||||
|
impl_aspect_for_text_aspect! {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InterfaceAspect for Interface {
|
||||||
fn set_sky_tex(_node: Arc<Node>, calling_client: Arc<Client>, tex: ResourceID) -> Result<()> {
|
fn set_sky_tex(_node: Arc<Node>, calling_client: Arc<Client>, tex: ResourceID) -> Result<()> {
|
||||||
let resource_path = get_resource_file(&tex, &calling_client, &[OsStr::new("hdr")])
|
let resource_path = get_resource_file(&tex, &calling_client, &[OsStr::new("hdr")])
|
||||||
.ok_or(eyre::eyre!("Could not find resource"))?;
|
.ok_or(ServerError::NoResource)?;
|
||||||
QUEUED_SKYTEX.lock().replace(resource_path);
|
QUEUED_SKYTEX.lock().replace(resource_path);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -60,7 +62,7 @@ impl InterfaceAspect for DrawableInterface {
|
|||||||
light: ResourceID,
|
light: ResourceID,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let resource_path = get_resource_file(&light, &calling_client, &[OsStr::new("hdr")])
|
let resource_path = get_resource_file(&light, &calling_client, &[OsStr::new("hdr")])
|
||||||
.ok_or(eyre::eyre!("Could not find resource"))?;
|
.ok_or(ServerError::NoResource)?;
|
||||||
QUEUED_SKYLIGHT.lock().replace(resource_path);
|
QUEUED_SKYLIGHT.lock().replace(resource_path);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,285 +1,391 @@
|
|||||||
use super::{MaterialParameter, ModelAspect, ModelPartAspect, MODEL_PART_ASPECT_ALIAS_INFO};
|
use super::{MaterialParameter, ModelAspect, ModelPartAspect, MODEL_PART_ASPECT_ALIAS_INFO};
|
||||||
|
use crate::bevy_plugin::DESTROY_ENTITY;
|
||||||
use crate::core::client::Client;
|
use crate::core::client::Client;
|
||||||
|
use crate::core::error::{Result, ServerError};
|
||||||
use crate::core::registry::Registry;
|
use crate::core::registry::Registry;
|
||||||
use crate::core::resource::get_resource_file;
|
use crate::core::resource::get_resource_file;
|
||||||
use crate::nodes::alias::{Alias, AliasList};
|
use crate::nodes::alias::{Alias, AliasList};
|
||||||
use crate::nodes::spatial::Spatial;
|
use crate::nodes::spatial::Spatial;
|
||||||
use crate::nodes::{Aspect, Node};
|
use crate::nodes::Node;
|
||||||
use color_eyre::eyre::{bail, eyre, Result};
|
use crate::DefaultMaterial;
|
||||||
|
use bevy::app::{Plugin, PostUpdate, PreUpdate, Update};
|
||||||
|
use bevy::asset::{AssetServer, Assets, Handle};
|
||||||
|
use bevy::color::{Color, LinearRgba, Srgba};
|
||||||
|
use bevy::core::Name;
|
||||||
|
use bevy::gltf::GltfAssetLabel;
|
||||||
|
use bevy::image::Image;
|
||||||
|
use bevy::math::bounding::Aabb3d;
|
||||||
|
use bevy::pbr::MeshMaterial3d;
|
||||||
|
use bevy::prelude::{
|
||||||
|
AlphaMode, BuildChildrenTransformExt, Children, Commands, Component, Deref, Entity, Has,
|
||||||
|
HierarchyQueryExt, Parent, Query, Res, ResMut, Resource, Transform, Visibility, With, Without,
|
||||||
|
};
|
||||||
|
use bevy::reflect::GetField;
|
||||||
|
use bevy::render::primitives::Aabb;
|
||||||
|
use bevy::scene::SceneRoot;
|
||||||
use glam::{Mat4, Vec2, Vec3};
|
use glam::{Mat4, Vec2, Vec3};
|
||||||
use once_cell::sync::{Lazy, OnceCell};
|
use once_cell::sync::OnceCell;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use stardust_xr::values::ResourceID;
|
use stardust_xr::values::ResourceID;
|
||||||
|
use tracing::{error, info, warn};
|
||||||
|
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::path::PathBuf;
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
use stereokit_rust::material::Transparency;
|
|
||||||
use stereokit_rust::maths::Bounds;
|
|
||||||
use stereokit_rust::sk::MainThreadToken;
|
|
||||||
use stereokit_rust::{material::Material, model::Model as SKModel, tex::Tex, util::Color128};
|
|
||||||
|
|
||||||
pub struct MaterialWrapper(pub Material);
|
|
||||||
impl Hash for MaterialWrapper {
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
self.0.get_shader().0.as_ptr().hash(state);
|
|
||||||
for param in self.0.get_all_param_info() {
|
|
||||||
param.to_string().hash(state)
|
|
||||||
}
|
|
||||||
self.0.get_chain().map(MaterialWrapper).hash(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl PartialEq for MaterialWrapper {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
if self.0.get_shader().0.as_ptr() != other.0.get_shader().0.as_ptr() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if self.0.get_all_param_info().count() != other.0.get_all_param_info().count() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for self_param in self.0.get_all_param_info() {
|
|
||||||
let Some(other_param) = other
|
|
||||||
.0
|
|
||||||
.get_all_param_info()
|
|
||||||
.get_data(self_param.get_name(), self_param.get_type())
|
|
||||||
else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
if self_param.to_string() != other_param.to_string() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.0.get_chain().map(MaterialWrapper) == other.0.get_chain().map(MaterialWrapper)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Eq for MaterialWrapper {}
|
|
||||||
unsafe impl Send for MaterialWrapper {}
|
|
||||||
unsafe impl Sync for MaterialWrapper {}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct MaterialRegistry(Mutex<FxHashMap<u64, String>>);
|
|
||||||
impl MaterialRegistry {
|
|
||||||
fn add_or_get(&self, material: Arc<MaterialWrapper>) -> Arc<MaterialWrapper> {
|
|
||||||
let mut lock = self.0.lock();
|
|
||||||
let hash = {
|
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
|
||||||
material.hash(&mut hasher);
|
|
||||||
hasher.finish()
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(id) = lock.get(&hash) {
|
|
||||||
if let Ok(existing) = Material::find(id) {
|
|
||||||
return Arc::new(MaterialWrapper(existing));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lock.insert(hash, material.0.get_id().to_string());
|
|
||||||
material
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static MATERIAL_REGISTRY: Lazy<MaterialRegistry> = Lazy::new(MaterialRegistry::default);
|
|
||||||
static MODEL_REGISTRY: Registry<Model> = Registry::new();
|
static MODEL_REGISTRY: Registry<Model> = Registry::new();
|
||||||
static HOLDOUT_MATERIAL: OnceCell<Arc<MaterialWrapper>> = OnceCell::new();
|
static HOLDOUT_MATERIAL: OnceCell<Arc<DefaultMaterial>> = OnceCell::new();
|
||||||
|
|
||||||
impl MaterialParameter {
|
impl MaterialParameter {
|
||||||
fn apply_to_material(&self, client: &Client, material: &Material, parameter_name: &str) {
|
fn apply_to_material(
|
||||||
let mut params = material.get_all_param_info();
|
&self,
|
||||||
|
client: &Client,
|
||||||
|
material: &mut DefaultMaterial,
|
||||||
|
parameter_name: &str,
|
||||||
|
asset_server: &AssetServer,
|
||||||
|
) {
|
||||||
match self {
|
match self {
|
||||||
MaterialParameter::Bool(val) => {
|
MaterialParameter::Bool(val) => {
|
||||||
params.set_bool(parameter_name, *val);
|
let name = parameter_name;
|
||||||
|
if let Some(field) = material.get_field_mut::<bool>(name) {
|
||||||
|
*field = *val;
|
||||||
|
} else {
|
||||||
|
warn!("unknown bool material parameter name: {name}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
MaterialParameter::Int(val) => {
|
MaterialParameter::Int(val) => {
|
||||||
params.set_int(parameter_name, &[*val]);
|
if let Some(field) = material.get_field_mut::<i32>(parameter_name) {
|
||||||
|
*field = *val;
|
||||||
|
} else {
|
||||||
|
warn!("unknown i32 material parameter name: {parameter_name}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
MaterialParameter::UInt(val) => {
|
MaterialParameter::UInt(val) => {
|
||||||
params.set_uint(parameter_name, &[*val]);
|
if let Some(field) = material.get_field_mut::<u32>(parameter_name) {
|
||||||
}
|
*field = *val;
|
||||||
MaterialParameter::Float(val) => {
|
} else {
|
||||||
params.set_float(parameter_name, *val);
|
warn!("unknown u32 material parameter name: {parameter_name}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
MaterialParameter::Float(val) => match parameter_name {
|
||||||
|
"cutoff" => {
|
||||||
|
// should this only set the value if AlphaMode is already AlphaMode::Mask?
|
||||||
|
material.alpha_mode = AlphaMode::Mask(*val);
|
||||||
|
}
|
||||||
|
"metallic" => {
|
||||||
|
material.metallic = *val;
|
||||||
|
}
|
||||||
|
name => {
|
||||||
|
if let Some(field) = material.get_field_mut::<f32>(name) {
|
||||||
|
*field = *val;
|
||||||
|
} else {
|
||||||
|
warn!("unknown f32 material parameter name: {name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
MaterialParameter::Vec2(val) => {
|
MaterialParameter::Vec2(val) => {
|
||||||
params.set_vec2(parameter_name, Vec2::from(*val));
|
if let Some(field) = material.get_field_mut::<Vec2>(parameter_name) {
|
||||||
|
*field = (*val).into();
|
||||||
|
} else {
|
||||||
|
warn!("unknown vec2 material parameter name: {parameter_name}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
MaterialParameter::Vec3(val) => {
|
MaterialParameter::Vec3(val) => {
|
||||||
params.set_vec3(parameter_name, Vec3::from(*val));
|
if let Some(field) = material.get_field_mut::<Vec3>(parameter_name) {
|
||||||
}
|
*field = (*val).into();
|
||||||
MaterialParameter::Color(val) => {
|
} else {
|
||||||
params.set_color(
|
warn!("unknown vec3 material parameter name: {parameter_name}");
|
||||||
parameter_name,
|
}
|
||||||
Color128::new(val.c.r, val.c.g, val.c.b, val.a),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
MaterialParameter::Color(val) => match parameter_name {
|
||||||
|
"color" => {
|
||||||
|
material.color = Srgba::new(val.c.r, val.c.g, val.c.b, val.a).into()
|
||||||
|
}
|
||||||
|
"emission_factor" => {
|
||||||
|
material.emission_factor = LinearRgba::new(val.c.r, val.c.g, val.c.b, val.a).into()
|
||||||
|
}
|
||||||
|
name => {
|
||||||
|
if let Some(field) = material.get_field_mut::<Color>(name) {
|
||||||
|
*field = LinearRgba::new(val.c.r, val.c.g, val.c.b, val.a).into();
|
||||||
|
} else {
|
||||||
|
warn!("unknown color material parameter name: {name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
MaterialParameter::Texture(resource) => {
|
MaterialParameter::Texture(resource) => {
|
||||||
let Some(texture_path) =
|
let Some(texture_path) =
|
||||||
get_resource_file(resource, client, &[OsStr::new("png"), OsStr::new("jpg")])
|
get_resource_file(resource, client, &[OsStr::new("png"), OsStr::new("jpg")])
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if let Ok(tex) = Tex::from_file(texture_path, true, None) {
|
let image = asset_server.load::<Image>(texture_path);
|
||||||
params.set_texture(parameter_name, &tex);
|
match parameter_name {
|
||||||
|
"diffuse" => {
|
||||||
|
material.diffuse_texture.replace(image);
|
||||||
|
}
|
||||||
|
"emission" => {
|
||||||
|
material.emission_texture.replace(image);
|
||||||
|
}
|
||||||
|
"normal" => {
|
||||||
|
error!("TODO: implement Normal Map texture in bevy_sk");
|
||||||
|
// material.n.replace(image);
|
||||||
|
}
|
||||||
|
"occlusion" => {
|
||||||
|
material.occlusion_texture.replace(image);
|
||||||
|
}
|
||||||
|
// TODO: impl metalic and roughness textures, they are combined in bevy
|
||||||
|
name => {
|
||||||
|
if let Some(field) = material.get_field_mut::<Option<Handle<Image>>>(name) {
|
||||||
|
field.replace(image);
|
||||||
|
} else {
|
||||||
|
warn!("unknown texture material parameter name: {name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
error!("TODO: implement texture changing");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ModelPart {
|
pub struct ModelPart {
|
||||||
id: i32,
|
entity: OnceCell<Entity>,
|
||||||
path: String,
|
path: String,
|
||||||
space: Arc<Spatial>,
|
space: Arc<Spatial>,
|
||||||
model: Weak<Model>,
|
model: Weak<Model>,
|
||||||
pending_material_parameters: Mutex<FxHashMap<String, MaterialParameter>>,
|
pending_material_parameters: Mutex<FxHashMap<String, MaterialParameter>>,
|
||||||
pending_material_replacement: Mutex<Option<Arc<MaterialWrapper>>>,
|
pending_material_replacement: Mutex<Option<Arc<DefaultMaterial>>>,
|
||||||
aliases: AliasList,
|
aliases: AliasList,
|
||||||
}
|
}
|
||||||
impl ModelPart {
|
|
||||||
fn create_for_model(model: &Arc<Model>, sk_model: &SKModel) {
|
|
||||||
HOLDOUT_MATERIAL.get_or_init(|| {
|
|
||||||
let mut mat = Material::copy(&Material::unlit());
|
|
||||||
mat.transparency(Transparency::None);
|
|
||||||
mat.color_tint(Color128::BLACK_TRANSPARENT);
|
|
||||||
Arc::new(MaterialWrapper(mat))
|
|
||||||
});
|
|
||||||
|
|
||||||
let nodes = sk_model.get_nodes();
|
#[derive(Component, Clone)]
|
||||||
for part in nodes.all() {
|
pub struct StardustModel(Weak<Model>);
|
||||||
ModelPart::create(model, &part);
|
#[derive(Component, Clone)]
|
||||||
}
|
pub struct UnprocessedModel;
|
||||||
|
pub struct StardustModelPlugin;
|
||||||
|
impl Plugin for StardustModelPlugin {
|
||||||
|
fn build(&self, app: &mut bevy::prelude::App) {
|
||||||
|
let (tx, rx) = crossbeam_channel::unbounded();
|
||||||
|
let _ = LOAD_MODEL_SENDER.set(tx);
|
||||||
|
app.insert_resource(LoadModelReader(rx));
|
||||||
|
app.add_systems(Update, create_model_parts_for_loaded_models);
|
||||||
|
app.add_systems(PreUpdate, load_models);
|
||||||
|
app.add_systems(PostUpdate, update_models);
|
||||||
|
app.add_systems(PostUpdate, update_model_parts);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
static LOAD_MODEL_SENDER: OnceCell<crossbeam_channel::Sender<(PathBuf, Arc<Model>)>> =
|
||||||
|
OnceCell::new();
|
||||||
|
#[derive(Resource, Deref)]
|
||||||
|
struct LoadModelReader(crossbeam_channel::Receiver<(PathBuf, Arc<Model>)>);
|
||||||
|
|
||||||
fn create(model: &Arc<Model>, part: &stereokit_rust::model::ModelNode) -> Option<Arc<Self>> {
|
fn update_models(mut query: Query<(&StardustModel, &mut Visibility, &mut Transform)>) {
|
||||||
let mut parts = model.parts.lock();
|
for (model, mut vis, mut transform) in query.iter_mut() {
|
||||||
let parent_part = part
|
let Some(model) = model.0.upgrade() else {
|
||||||
.get_parent()
|
continue;
|
||||||
.and_then(|part| parts.iter().find(|p| p.id == *part.get_id()));
|
|
||||||
|
|
||||||
let stardust_model_part = model.space.node()?;
|
|
||||||
let client = stardust_model_part.get_client()?;
|
|
||||||
let mut part_path = parent_part
|
|
||||||
.map(|n| n.path.clone() + "/")
|
|
||||||
.unwrap_or_default();
|
|
||||||
part_path += part.get_name().unwrap();
|
|
||||||
|
|
||||||
let node = client.scenegraph.add_node(Node::generate(&client, false));
|
|
||||||
let spatial_parent = parent_part
|
|
||||||
.map(|n| n.space.clone())
|
|
||||||
.unwrap_or_else(|| model.space.clone());
|
|
||||||
|
|
||||||
let local_transform = unsafe { part.get_local_transform().m };
|
|
||||||
let space = Spatial::add_to(
|
|
||||||
&node,
|
|
||||||
Some(spatial_parent),
|
|
||||||
Mat4::from_cols_array(&local_transform),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
let _ = space.bounding_box_calc.set(|node| {
|
|
||||||
let Ok(model_part) = node.get_aspect::<ModelPart>() else {
|
|
||||||
return Bounds::default();
|
|
||||||
};
|
|
||||||
let Some(model) = model_part.model.upgrade() else {
|
|
||||||
return Bounds::default();
|
|
||||||
};
|
|
||||||
let Some(sk_model) = model.sk_model.get() else {
|
|
||||||
return Bounds::default();
|
|
||||||
};
|
|
||||||
let model_nodes = sk_model.get_nodes();
|
|
||||||
let Some(model_node) = model_nodes.get_index(model_part.id) else {
|
|
||||||
return Bounds::default();
|
|
||||||
};
|
|
||||||
let Some(sk_mesh) = model_node.get_mesh() else {
|
|
||||||
return Bounds::default();
|
|
||||||
};
|
|
||||||
sk_mesh.get_bounds()
|
|
||||||
});
|
|
||||||
|
|
||||||
let model_part = Arc::new(ModelPart {
|
|
||||||
id: *part.get_id(),
|
|
||||||
path: part_path,
|
|
||||||
space,
|
|
||||||
model: Arc::downgrade(model),
|
|
||||||
pending_material_parameters: Mutex::new(FxHashMap::default()),
|
|
||||||
pending_material_replacement: Mutex::new(None),
|
|
||||||
aliases: AliasList::default(),
|
|
||||||
});
|
|
||||||
<ModelPart as ModelPartAspect>::add_node_members(&node);
|
|
||||||
node.add_aspect_raw(model_part.clone());
|
|
||||||
parts.push(model_part.clone());
|
|
||||||
Some(model_part)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn replace_material(&self, replacement: Arc<MaterialWrapper>) {
|
|
||||||
let shared_material = MATERIAL_REGISTRY.add_or_get(replacement);
|
|
||||||
self.pending_material_replacement
|
|
||||||
.lock()
|
|
||||||
.replace(shared_material);
|
|
||||||
}
|
|
||||||
/// only to be run on the main thread
|
|
||||||
pub fn replace_material_now(&self, replacement: &Material) {
|
|
||||||
let Some(model) = self.model.upgrade() else {
|
|
||||||
return;
|
|
||||||
};
|
};
|
||||||
let Some(sk_model) = model.sk_model.get() else {
|
*transform = Transform::from_matrix(model.space.global_transform());
|
||||||
return;
|
if let Some(node) = model.space.node() {
|
||||||
};
|
*vis = match node.enabled() {
|
||||||
let nodes = sk_model.get_nodes();
|
true => Visibility::Inherited,
|
||||||
let Some(mut part) = nodes.get_index(self.id) else {
|
false => Visibility::Hidden,
|
||||||
return;
|
|
||||||
};
|
|
||||||
let shared_material =
|
|
||||||
MATERIAL_REGISTRY.add_or_get(Arc::new(MaterialWrapper(replacement.copy())));
|
|
||||||
part.material(&shared_material.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&self) {
|
|
||||||
let Some(model) = self.model.upgrade() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let Some(sk_model) = model.sk_model.get() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let Some(node) = model.space.node() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let nodes = sk_model.get_nodes();
|
|
||||||
let Some(mut part) = nodes.get_index(self.id) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
part.model_transform(Spatial::space_to_space_matrix(
|
|
||||||
Some(&self.space),
|
|
||||||
Some(&model.space),
|
|
||||||
));
|
|
||||||
|
|
||||||
let Some(client) = node.get_client() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(material_replacement) = self.pending_material_replacement.lock().take() {
|
|
||||||
part.material(&material_replacement.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
'mat_params: {
|
|
||||||
let mut material_parameters = self.pending_material_parameters.lock();
|
|
||||||
if !material_parameters.is_empty() {
|
|
||||||
let Some(material) = part.get_material() else {
|
|
||||||
break 'mat_params;
|
|
||||||
};
|
|
||||||
let new_material = material.copy();
|
|
||||||
for (parameter_name, parameter_value) in material_parameters.drain() {
|
|
||||||
parameter_value.apply_to_material(&client, &new_material, ¶meter_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
let shared_material =
|
|
||||||
MATERIAL_REGISTRY.add_or_get(Arc::new(MaterialWrapper(new_material)));
|
|
||||||
part.material(&shared_material.0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Aspect for ModelPart {
|
|
||||||
const NAME: &'static str = "ModelPart";
|
fn load_models(rx: Res<LoadModelReader>, mut cmds: Commands, asset_server: Res<AssetServer>) {
|
||||||
|
for (path, model) in rx.try_iter() {
|
||||||
|
let handle = asset_server.load(GltfAssetLabel::Scene(0).from_asset(path));
|
||||||
|
let entity = cmds
|
||||||
|
.spawn((
|
||||||
|
SceneRoot(handle),
|
||||||
|
StardustModel(Arc::downgrade(&model)),
|
||||||
|
UnprocessedModel,
|
||||||
|
))
|
||||||
|
.id();
|
||||||
|
let _ = model.entity.set(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_model_parts(
|
||||||
|
models: Query<&StardustModel, Without<UnprocessedModel>>,
|
||||||
|
mut mats: ResMut<Assets<DefaultMaterial>>,
|
||||||
|
mut part_query: Query<(
|
||||||
|
&mut Transform,
|
||||||
|
&mut MeshMaterial3d<DefaultMaterial>,
|
||||||
|
&mut Visibility,
|
||||||
|
Has<Parent>,
|
||||||
|
)>,
|
||||||
|
mut cmds: Commands,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
|
) {
|
||||||
|
for model in &models {
|
||||||
|
let Some(model) = model.0.upgrade() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
for part in model.parts.lock().iter() {
|
||||||
|
let Some((entity, (mut transform, mut mat, mut vis, has_parent))) = part
|
||||||
|
.entity
|
||||||
|
.get()
|
||||||
|
.and_then(|e| Some((*e, part_query.get_mut(*e).ok()?)))
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if has_parent {
|
||||||
|
cmds.entity(entity).remove_parent_in_place();
|
||||||
|
}
|
||||||
|
*transform = Transform::from_matrix(part.space.global_transform());
|
||||||
|
if let Some(node) = part.space.node() {
|
||||||
|
*vis = match node.enabled() {
|
||||||
|
true => Visibility::Inherited,
|
||||||
|
false => Visibility::Hidden,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: find all materials with identical parameters and batch them into 1 material again
|
||||||
|
'mat_params: {
|
||||||
|
let mut material_parameters = part.pending_material_parameters.lock();
|
||||||
|
if !material_parameters.is_empty() {
|
||||||
|
let Some(material) = mats.get(&mat.0) else {
|
||||||
|
break 'mat_params;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut new_material = material.clone();
|
||||||
|
let Some(client) = part.space.node().and_then(|v| v.get_client()) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
for (parameter_name, parameter_value) in material_parameters.drain() {
|
||||||
|
parameter_value.apply_to_material(
|
||||||
|
&client,
|
||||||
|
&mut new_material,
|
||||||
|
¶meter_name,
|
||||||
|
&asset_server,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
mat.0 = mats.add(new_material);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_path(
|
||||||
|
entity: Entity,
|
||||||
|
query: &Query<(&Parent, &Name), Without<SceneRoot>>,
|
||||||
|
mut in_vec: Vec<String>,
|
||||||
|
) -> Vec<String> {
|
||||||
|
let Ok((parent, name)) = query.get(entity) else {
|
||||||
|
return in_vec;
|
||||||
|
};
|
||||||
|
in_vec.push(name.to_string());
|
||||||
|
get_path(parent.get(), query, in_vec)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_model_parts_for_loaded_models(
|
||||||
|
query: Query<(Entity, &StardustModel), (With<UnprocessedModel>, With<Children>)>,
|
||||||
|
children: Query<&Children>,
|
||||||
|
gltf_model_parts: Query<(Entity, &Transform, &Aabb), Without<SceneRoot>>,
|
||||||
|
name_query: Query<(&Parent, &Name), Without<SceneRoot>>,
|
||||||
|
mut cmds: Commands,
|
||||||
|
) {
|
||||||
|
for (entity, model) in &query {
|
||||||
|
info!("creating parts!");
|
||||||
|
let Some(model) = model.0.upgrade() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
// let mut parts = model.parts.lock();
|
||||||
|
let mut parts = Vec::<Arc<ModelPart>>::new();
|
||||||
|
for (entity, transform, aabb) in children
|
||||||
|
.iter_descendants_depth_first(entity)
|
||||||
|
.filter_map(|e| gltf_model_parts.get(e).ok())
|
||||||
|
{
|
||||||
|
let mut path_parts = get_path(entity, &name_query, Vec::new());
|
||||||
|
path_parts.remove(0);
|
||||||
|
path_parts.reverse();
|
||||||
|
let part_path = path_parts.join("/");
|
||||||
|
path_parts.pop();
|
||||||
|
let parent_path = path_parts.join("/");
|
||||||
|
let parent_part = parts.iter().find(|v| v.path == parent_path);
|
||||||
|
|
||||||
|
let Some(stardust_model_part) = model.space.node() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(client) = stardust_model_part.get_client() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let model_part = model
|
||||||
|
.parts
|
||||||
|
.lock()
|
||||||
|
.iter()
|
||||||
|
.find(|v| v.path == part_path)
|
||||||
|
.cloned()
|
||||||
|
.map(|v| {
|
||||||
|
*v.space.bounding_box_calc.lock() = Aabb3d::new(aabb.center, aabb.half_extents);
|
||||||
|
if v.entity.set(entity).is_err() {
|
||||||
|
error!(
|
||||||
|
"trying to set entity for already init model part?!
|
||||||
|
please yell at schmarni if you see this"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(err) = v.space.set_spatial_parent(Some(
|
||||||
|
parent_part.map(|n| &n.space).unwrap_or_else(|| {
|
||||||
|
info!("model is spatial parent");
|
||||||
|
&model.space
|
||||||
|
}),
|
||||||
|
)) {
|
||||||
|
error!("error setting spatial parent for existing model part: {err}");
|
||||||
|
}
|
||||||
|
v.space.set_local_transform(transform.compute_matrix());
|
||||||
|
info!("not fresh {}", &v.path);
|
||||||
|
v
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
let node = client.scenegraph.add_node(Node::generate(&client, false));
|
||||||
|
let spatial_parent =
|
||||||
|
parent_part.map(|n| n.space.clone()).unwrap_or_else(|| {
|
||||||
|
info!("model is spatial parent");
|
||||||
|
model.space.clone()
|
||||||
|
});
|
||||||
|
|
||||||
|
let space = Spatial::add_to(
|
||||||
|
&node,
|
||||||
|
Some(spatial_parent),
|
||||||
|
transform.compute_matrix(),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
*space.bounding_box_calc.lock() = Aabb3d::new(aabb.center, aabb.half_extents);
|
||||||
|
|
||||||
|
let model_part = Arc::new(ModelPart {
|
||||||
|
entity: OnceCell::from(entity),
|
||||||
|
path: part_path,
|
||||||
|
space,
|
||||||
|
model: Arc::downgrade(&model),
|
||||||
|
pending_material_parameters: Mutex::new(FxHashMap::default()),
|
||||||
|
pending_material_replacement: Mutex::new(None),
|
||||||
|
aliases: AliasList::default(),
|
||||||
|
});
|
||||||
|
node.add_aspect_raw(model_part.clone());
|
||||||
|
info!("fresh {}", &model_part.path);
|
||||||
|
model_part
|
||||||
|
});
|
||||||
|
parts.push(model_part.clone());
|
||||||
|
}
|
||||||
|
cmds.entity(entity).remove::<UnprocessedModel>();
|
||||||
|
info!("created parts! {}", parts.len());
|
||||||
|
*model.parts.lock() = parts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModelPart {
|
||||||
|
pub fn replace_material(&self, replacement: Arc<DefaultMaterial>) {
|
||||||
|
self.pending_material_replacement
|
||||||
|
.lock()
|
||||||
|
.replace(replacement);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl ModelPartAspect for ModelPart {
|
impl ModelPartAspect for ModelPart {
|
||||||
#[doc = "Set this model part's material to one that cuts a hole in the world. Often used for overlays/passthrough where you want to show the background through an object."]
|
#[doc = "Set this model part's material to one that cuts a hole in the world. Often used for overlays/passthrough where you want to show the background through an object."]
|
||||||
@@ -309,60 +415,31 @@ impl ModelPartAspect for ModelPart {
|
|||||||
pub struct Model {
|
pub struct Model {
|
||||||
space: Arc<Spatial>,
|
space: Arc<Spatial>,
|
||||||
_resource_id: ResourceID,
|
_resource_id: ResourceID,
|
||||||
sk_model: OnceCell<SKModel>,
|
entity: OnceCell<Entity>,
|
||||||
parts: Mutex<Vec<Arc<ModelPart>>>,
|
parts: Mutex<Vec<Arc<ModelPart>>>,
|
||||||
}
|
}
|
||||||
impl Model {
|
impl Model {
|
||||||
pub fn add_to(node: &Arc<Node>, resource_id: ResourceID) -> Result<Arc<Model>> {
|
pub fn add_to(node: &Arc<Node>, resource_id: ResourceID) -> Result<Arc<Model>> {
|
||||||
let pending_model_path = get_resource_file(
|
let pending_model_path = get_resource_file(
|
||||||
&resource_id,
|
&resource_id,
|
||||||
&*node.get_client().ok_or_else(|| eyre!("Client not found"))?,
|
&*node.get_client().ok_or(ServerError::NoClient)?,
|
||||||
&[OsStr::new("glb"), OsStr::new("gltf")],
|
&[OsStr::new("glb"), OsStr::new("gltf")],
|
||||||
)
|
)
|
||||||
.ok_or_else(|| eyre!("Resource not found"))?;
|
.ok_or(ServerError::NoResource)?;
|
||||||
|
|
||||||
let model = Arc::new(Model {
|
let model = Arc::new(Model {
|
||||||
space: node.get_aspect::<Spatial>().unwrap().clone(),
|
space: node.get_aspect::<Spatial>().unwrap().clone(),
|
||||||
_resource_id: resource_id,
|
_resource_id: resource_id,
|
||||||
sk_model: OnceCell::new(),
|
entity: OnceCell::new(),
|
||||||
parts: Mutex::new(Vec::default()),
|
parts: Mutex::new(Vec::default()),
|
||||||
});
|
});
|
||||||
<Model as ModelAspect>::add_node_members(node);
|
|
||||||
MODEL_REGISTRY.add_raw(&model);
|
MODEL_REGISTRY.add_raw(&model);
|
||||||
|
if let Some(sender) = LOAD_MODEL_SENDER.get() {
|
||||||
// technically doing this in anything but the main thread isn't a good idea but dangit we need those model nodes ASAP
|
let _ = sender.send((pending_model_path, model.clone()));
|
||||||
let sk_model = SKModel::copy(SKModel::from_file(
|
}
|
||||||
pending_model_path.to_str().unwrap(),
|
|
||||||
None,
|
|
||||||
)?);
|
|
||||||
ModelPart::create_for_model(&model, &sk_model);
|
|
||||||
let _ = model.sk_model.set(sk_model);
|
|
||||||
node.add_aspect_raw(model.clone());
|
node.add_aspect_raw(model.clone());
|
||||||
Ok(model)
|
Ok(model)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&self, token: &MainThreadToken) {
|
|
||||||
let Some(sk_model) = self.sk_model.get() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let parts = self.parts.lock();
|
|
||||||
for model_node in &*parts {
|
|
||||||
model_node.update();
|
|
||||||
}
|
|
||||||
drop(parts);
|
|
||||||
|
|
||||||
if let Some(node) = self.space.node() {
|
|
||||||
if node.enabled() {
|
|
||||||
sk_model.draw(token, self.space.global_transform(), None, None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO: proper hread safety in stereokit_rust (probably just bind stereokit directly)
|
|
||||||
unsafe impl Send for Model {}
|
|
||||||
unsafe impl Sync for Model {}
|
|
||||||
impl Aspect for Model {
|
|
||||||
const NAME: &'static str = "Model";
|
|
||||||
}
|
}
|
||||||
impl ModelAspect for Model {
|
impl ModelAspect for Model {
|
||||||
#[doc = "Bind a model part to the node with the ID input."]
|
#[doc = "Bind a model part to the node with the ID input."]
|
||||||
@@ -371,13 +448,37 @@ impl ModelAspect for Model {
|
|||||||
calling_client: Arc<Client>,
|
calling_client: Arc<Client>,
|
||||||
id: u64,
|
id: u64,
|
||||||
part_path: String,
|
part_path: String,
|
||||||
) -> color_eyre::eyre::Result<()> {
|
) -> Result<()> {
|
||||||
let model = node.get_aspect::<Model>()?;
|
let model = node.get_aspect::<Model>()?;
|
||||||
let parts = model.parts.lock();
|
let mut parts = model.parts.lock();
|
||||||
let Some(part) = parts.iter().find(|p| p.path == part_path) else {
|
let part =
|
||||||
let paths = parts.iter().map(|p| &p.path).collect::<Vec<_>>();
|
parts
|
||||||
bail!("Couldn't find model part at path {part_path}, all available paths: {paths:?}",)
|
.iter()
|
||||||
};
|
.find(|p| p.path == part_path)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
let paths = parts.iter().map(|p| &p.path).collect::<Vec<_>>();
|
||||||
|
error!("Couldn't find model part at path {part_path}, all available paths: {paths:?}");
|
||||||
|
|
||||||
|
let node = calling_client
|
||||||
|
.scenegraph
|
||||||
|
.add_node(Node::generate(&calling_client, false));
|
||||||
|
|
||||||
|
let space = Spatial::add_to(&node, None, Mat4::IDENTITY, false);
|
||||||
|
|
||||||
|
let model_part = Arc::new(ModelPart {
|
||||||
|
entity: OnceCell::new(),
|
||||||
|
path: part_path,
|
||||||
|
space,
|
||||||
|
model: Arc::downgrade(&model),
|
||||||
|
pending_material_parameters: Mutex::new(FxHashMap::default()),
|
||||||
|
pending_material_replacement: Mutex::new(None),
|
||||||
|
aliases: AliasList::default(),
|
||||||
|
});
|
||||||
|
node.add_aspect_raw(model_part.clone());
|
||||||
|
parts.push(model_part.clone());
|
||||||
|
model_part
|
||||||
|
});
|
||||||
Alias::create_with_id(
|
Alias::create_with_id(
|
||||||
&part.space.node().unwrap(),
|
&part.space.node().unwrap(),
|
||||||
&calling_client,
|
&calling_client,
|
||||||
@@ -388,14 +489,18 @@ impl ModelAspect for Model {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl Drop for ModelPart {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(e) = self.entity.get() {
|
||||||
|
_ = DESTROY_ENTITY.send(*e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
impl Drop for Model {
|
impl Drop for Model {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
|
if let Some(e) = self.entity.get() {
|
||||||
|
_ = DESTROY_ENTITY.send(*e);
|
||||||
|
}
|
||||||
MODEL_REGISTRY.remove(self);
|
MODEL_REGISTRY.remove(self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_all(token: &MainThreadToken) {
|
|
||||||
for model in MODEL_REGISTRY.get_valid_contents() {
|
|
||||||
model.draw(token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,137 +0,0 @@
|
|||||||
#![allow(dead_code)]
|
|
||||||
use smithay::backend::renderer::gles::{
|
|
||||||
ffi::{self, Gles2, FRAGMENT_SHADER, VERTEX_SHADER},
|
|
||||||
GlesError,
|
|
||||||
};
|
|
||||||
use stereokit_rust::shader::{Shader, _ShaderT};
|
|
||||||
use tracing::error;
|
|
||||||
|
|
||||||
struct FfiAssetHeader {
|
|
||||||
asset_type: i32,
|
|
||||||
asset_state: i32,
|
|
||||||
id: u64,
|
|
||||||
index: u64,
|
|
||||||
refs: i32,
|
|
||||||
debug: *mut u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FfiSkgShader {
|
|
||||||
meta: *mut u8,
|
|
||||||
vertex: u32,
|
|
||||||
pixel: u32,
|
|
||||||
program: u32,
|
|
||||||
compute: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FfiShader {
|
|
||||||
header: FfiAssetHeader,
|
|
||||||
shader: FfiSkgShader,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn compile_shader(
|
|
||||||
gl: &ffi::Gles2,
|
|
||||||
variant: ffi::types::GLuint,
|
|
||||||
src: &str,
|
|
||||||
) -> Result<ffi::types::GLuint, GlesError> {
|
|
||||||
let shader = gl.CreateShader(variant);
|
|
||||||
if shader == 0 {
|
|
||||||
return Err(GlesError::CreateShaderObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
gl.ShaderSource(
|
|
||||||
shader,
|
|
||||||
1,
|
|
||||||
&src.as_ptr() as *const *const u8 as *const *const ffi::types::GLchar,
|
|
||||||
&(src.len() as i32) as *const _,
|
|
||||||
);
|
|
||||||
gl.CompileShader(shader);
|
|
||||||
|
|
||||||
let mut status = ffi::FALSE as i32;
|
|
||||||
gl.GetShaderiv(shader, ffi::COMPILE_STATUS, &mut status as *mut _);
|
|
||||||
if status == ffi::FALSE as i32 {
|
|
||||||
let mut max_len = 0;
|
|
||||||
gl.GetShaderiv(shader, ffi::INFO_LOG_LENGTH, &mut max_len as *mut _);
|
|
||||||
|
|
||||||
let mut error = Vec::with_capacity(max_len as usize);
|
|
||||||
let mut len = 0;
|
|
||||||
gl.GetShaderInfoLog(
|
|
||||||
shader,
|
|
||||||
max_len as _,
|
|
||||||
&mut len as *mut _,
|
|
||||||
error.as_mut_ptr() as *mut _,
|
|
||||||
);
|
|
||||||
error.set_len(len as usize);
|
|
||||||
|
|
||||||
error!(
|
|
||||||
"[GL] {}",
|
|
||||||
std::str::from_utf8(&error).unwrap_or("<Error Message no utf8>")
|
|
||||||
);
|
|
||||||
|
|
||||||
gl.DeleteShader(shader);
|
|
||||||
return Err(GlesError::ShaderCompileError);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(shader)
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn link_program(
|
|
||||||
gl: &ffi::Gles2,
|
|
||||||
vert: ffi::types::GLuint,
|
|
||||||
frag: ffi::types::GLuint,
|
|
||||||
) -> Result<ffi::types::GLuint, GlesError> {
|
|
||||||
let program = gl.CreateProgram();
|
|
||||||
gl.AttachShader(program, vert);
|
|
||||||
gl.AttachShader(program, frag);
|
|
||||||
gl.LinkProgram(program);
|
|
||||||
// gl.DetachShader(program, vert);
|
|
||||||
// gl.DetachShader(program, frag);
|
|
||||||
// gl.DeleteShader(vert);
|
|
||||||
// gl.DeleteShader(frag);
|
|
||||||
|
|
||||||
let mut status = ffi::FALSE as i32;
|
|
||||||
gl.GetProgramiv(program, ffi::LINK_STATUS, &mut status as *mut _);
|
|
||||||
if status == ffi::FALSE as i32 {
|
|
||||||
let mut max_len = 0;
|
|
||||||
gl.GetProgramiv(program, ffi::INFO_LOG_LENGTH, &mut max_len as *mut _);
|
|
||||||
|
|
||||||
let mut error = Vec::with_capacity(max_len as usize);
|
|
||||||
let mut len = 0;
|
|
||||||
gl.GetProgramInfoLog(
|
|
||||||
program,
|
|
||||||
max_len as _,
|
|
||||||
&mut len as *mut _,
|
|
||||||
error.as_mut_ptr() as *mut _,
|
|
||||||
);
|
|
||||||
error.set_len(len as usize);
|
|
||||||
|
|
||||||
error!(
|
|
||||||
"[GL] {}",
|
|
||||||
std::str::from_utf8(&error).unwrap_or("<Error Message no utf8>")
|
|
||||||
);
|
|
||||||
|
|
||||||
gl.DeleteProgram(program);
|
|
||||||
return Err(GlesError::ProgramLinkError);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(program)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn shader_inject(
|
|
||||||
c: &Gles2,
|
|
||||||
sk_shader: &mut Shader,
|
|
||||||
vert_str: &str,
|
|
||||||
frag_str: &str,
|
|
||||||
) -> Result<(), GlesError> {
|
|
||||||
let gl_vert = compile_shader(c, VERTEX_SHADER, vert_str)?;
|
|
||||||
let gl_frag = compile_shader(c, FRAGMENT_SHADER, frag_str)?;
|
|
||||||
let gl_prog = link_program(c, gl_vert, gl_frag)?;
|
|
||||||
|
|
||||||
let shader = sk_shader.0.as_mut() as *mut _ShaderT as *mut FfiShader;
|
|
||||||
if let Some(shader) = shader.as_mut() {
|
|
||||||
shader.shader.vertex = gl_vert;
|
|
||||||
shader.shader.pixel = gl_frag;
|
|
||||||
shader.shader.program = gl_prog;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
// Simula shader with fancy lanzcos sampling
|
|
||||||
pub const UNLIT_SHADER_BYTES: &[u8] = include_bytes!("assets/shaders/shader_unlit_gamma.hlsl.sks");
|
|
||||||
|
|
||||||
// Simula shader with fancy lanzcos sampling
|
|
||||||
pub const PANEL_SHADER_BYTES: &[u8] = include_bytes!("assets/shaders/shader_unlit_simula.hlsl.sks");
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
#include "stereokit.hlsli"
|
|
||||||
|
|
||||||
//--name = sk/unlit
|
|
||||||
//--diffuse = white
|
|
||||||
//--uv_offset = 0.0, 0.0
|
|
||||||
//--uv_scale = 1.0, 1.0
|
|
||||||
Texture2D diffuse : register(t0);
|
|
||||||
SamplerState diffuse_s : register(s0);
|
|
||||||
float2 uv_scale;
|
|
||||||
float2 uv_offset;
|
|
||||||
|
|
||||||
struct vsIn {
|
|
||||||
float4 pos : SV_Position;
|
|
||||||
float3 norm : NORMAL0;
|
|
||||||
float2 uv : TEXCOORD0;
|
|
||||||
};
|
|
||||||
struct psIn {
|
|
||||||
float4 pos : SV_POSITION;
|
|
||||||
float2 uv : TEXCOORD0;
|
|
||||||
uint view_id : SV_RenderTargetArrayIndex;
|
|
||||||
};
|
|
||||||
|
|
||||||
psIn vs(vsIn input, uint id : SV_InstanceID) {
|
|
||||||
psIn o;
|
|
||||||
o.view_id = id % sk_view_count;
|
|
||||||
id = id / sk_view_count;
|
|
||||||
|
|
||||||
float3 world = mul(float4(input.pos.xyz, 1), sk_inst[id].world).xyz;
|
|
||||||
o.pos = mul(float4(world, 1), sk_viewproj[o.view_id]);
|
|
||||||
|
|
||||||
o.uv = (input.uv + uv_offset) * uv_scale;
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
float4 ps(psIn input) : SV_TARGET {
|
|
||||||
float4 col = diffuse.Sample(diffuse_s, input.uv);
|
|
||||||
col.rgb = pow(col.rgb, float3(2.2));
|
|
||||||
|
|
||||||
return col;
|
|
||||||
}
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
#include "stereokit.hlsli"
|
|
||||||
|
|
||||||
// Port of https://github.com/SimulaVR/Simula/blob/master/addons/godot-haskell-plugin/TextShader.tres to StereoKit and HLSL.
|
|
||||||
|
|
||||||
//--name = stardust/text_shader
|
|
||||||
//--diffuse = white
|
|
||||||
//--uv_offset = 0.0, 0.0
|
|
||||||
//--uv_scale = 1.0, 1.0
|
|
||||||
//--fcFactor = 1.0
|
|
||||||
//--ripple = 4.0
|
|
||||||
//--alpha_min = 0.0
|
|
||||||
//--alpha_max = 1.0
|
|
||||||
Texture2D diffuse : register(t0);
|
|
||||||
SamplerState diffuse_s : register(s0);
|
|
||||||
float4 diffuse_i;
|
|
||||||
float2 uv_scale;
|
|
||||||
float2 uv_offset;
|
|
||||||
float fcFactor;
|
|
||||||
float ripple;
|
|
||||||
float alpha_min;
|
|
||||||
float alpha_max;
|
|
||||||
|
|
||||||
struct vsIn {
|
|
||||||
float4 pos : SV_Position;
|
|
||||||
float3 norm : NORMAL0;
|
|
||||||
float2 uv : TEXCOORD0;
|
|
||||||
};
|
|
||||||
struct psIn {
|
|
||||||
float4 pos : SV_POSITION;
|
|
||||||
float2 uv : TEXCOORD0;
|
|
||||||
uint view_id : SV_RenderTargetArrayIndex;
|
|
||||||
};
|
|
||||||
|
|
||||||
psIn vs(vsIn input, uint id : SV_InstanceID) {
|
|
||||||
psIn o;
|
|
||||||
o.view_id = id % sk_view_count;
|
|
||||||
id = id / sk_view_count;
|
|
||||||
|
|
||||||
float3 world = mul(float4(input.pos.xyz, 1), sk_inst[id].world).xyz;
|
|
||||||
o.pos = mul(float4(world, 1), sk_viewproj[o.view_id]);
|
|
||||||
|
|
||||||
o.uv = (input.uv + uv_offset) * uv_scale;
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
|
|
||||||
float map(float value, float min1, float max1, float min2, float max2) {
|
|
||||||
return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// float gaussian(float x, float t) {
|
|
||||||
// float PI = 3.14159265358;
|
|
||||||
// return exp(-x*x/(2.0 * t*t))/(sqrt(2.0*PI)*t);
|
|
||||||
// }
|
|
||||||
|
|
||||||
float besselI0(float x) {
|
|
||||||
return 1.0 + pow(x, 2.0) * (0.25 + pow(x, 2.0) * (0.015625 + pow(x, 2.0) * (0.000434028 + pow(x, 2.0) * (6.78168e-6 + pow(x, 2.0) * (6.78168e-8 + pow(x, 2.0) * (4.7095e-10 + pow(x, 2.0) * (2.40281e-12 + pow(x, 2.0) * (9.38597e-15 + pow(x, 2.0) * (2.8969e-17 + 7.24226e-20 * pow(x, 2.0))))))))));
|
|
||||||
}
|
|
||||||
|
|
||||||
float kaiser(float x, float alpha) {
|
|
||||||
if (x > 1.0) {
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
return besselI0(alpha * sqrt(1.0-x*x));
|
|
||||||
}
|
|
||||||
|
|
||||||
float4 lowpassFilter(Texture2D tex, sampler2D texSampler, float2 uv, float alpha) {
|
|
||||||
float PI = 3.14159265358;
|
|
||||||
|
|
||||||
float4 q = float4(0.0);
|
|
||||||
|
|
||||||
float2 dx_uv = ddx(uv);
|
|
||||||
float2 dy_uv = ddy(uv);
|
|
||||||
//float width = sqrt(max(dot(dx_uv, dx_uv), dot(dy_uv, dy_uv)));
|
|
||||||
float2 width = abs(float2(dx_uv.x, dy_uv.y));
|
|
||||||
|
|
||||||
float2 pixelWidth = floor(width * diffuse_i.xy);
|
|
||||||
float2 aspectRatio = normalize(pixelWidth);
|
|
||||||
|
|
||||||
float2 xyf = uv * diffuse_i.xy;
|
|
||||||
int2 xy = int2(xyf);
|
|
||||||
|
|
||||||
pixelWidth = clamp(pixelWidth, float2(1.0), float2(2.0));
|
|
||||||
|
|
||||||
int2 start = xy - int2(pixelWidth);
|
|
||||||
int2 end = xy + int2(pixelWidth);
|
|
||||||
|
|
||||||
float4 outColor = float4(0.0);
|
|
||||||
|
|
||||||
float qSum = 0.0;
|
|
||||||
|
|
||||||
for (int v = start.y; v <= end.y; v++) {
|
|
||||||
for (int u = start.x; u <= end.x; u++) {
|
|
||||||
float kx = fcFactor * (xyf.x - float(u))/pixelWidth.x;
|
|
||||||
float ky = fcFactor * (xyf.y - float(v))/pixelWidth.y;
|
|
||||||
|
|
||||||
//float lanczosValue = gaussian(kx, fcx);
|
|
||||||
float lanczosValue = kaiser(sqrt(kx*kx + ky*ky), alpha);
|
|
||||||
|
|
||||||
q += tex.Sample(texSampler, (float2(u, v)+float2(0.5))/diffuse_i.xy) * lanczosValue;
|
|
||||||
// q += tex.Load(int3(u, v, 0)) * lanczosValue;
|
|
||||||
qSum += lanczosValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return q/qSum;
|
|
||||||
}
|
|
||||||
|
|
||||||
float4 ps(psIn input) : SV_TARGET {
|
|
||||||
float gamma = 2.2;
|
|
||||||
// float4 col = diffuse.Sample(diffuse_s, input.uv);
|
|
||||||
|
|
||||||
// float4 col = lowpassFilter(diffuse, diffuse_s, diffuse_i.xy, float2(1.0 - input.uv.x, input.uv.y), ripple);
|
|
||||||
float4 col = lowpassFilter(diffuse, diffuse_s, input.uv, ripple);
|
|
||||||
// float4 col = diffuse.Sample(diffuse_s, input.uv);
|
|
||||||
col.rgb = pow(col.rgb, float3(gamma));
|
|
||||||
col.a = map(col.a, 0, 1, alpha_min, alpha_max);
|
|
||||||
|
|
||||||
return col;
|
|
||||||
}
|
|
||||||
@@ -1,139 +1,143 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
core::{client::Client, destroy_queue, registry::Registry, resource::get_resource_file},
|
bevy_plugin::{convert_linear_rgba, DESTROY_ENTITY},
|
||||||
nodes::{spatial::Spatial, Aspect, Node},
|
core::{
|
||||||
|
client::Client,
|
||||||
|
error::{Result, ServerError},
|
||||||
|
registry::Registry,
|
||||||
|
resource::get_resource_file,
|
||||||
|
},
|
||||||
|
nodes::{spatial::Spatial, Node},
|
||||||
|
DefaultMaterial,
|
||||||
};
|
};
|
||||||
use color_eyre::eyre::{eyre, Result};
|
use bevy::{
|
||||||
use glam::{vec3, Mat4, Vec2};
|
app::{App, Plugin, PostUpdate, PreUpdate},
|
||||||
|
asset::{AssetServer, Assets},
|
||||||
|
pbr::MeshMaterial3d,
|
||||||
|
prelude::{Commands, Deref, Entity, Query, Res, ResMut, Resource, Transform},
|
||||||
|
};
|
||||||
|
use bevy_mod_meshtext::{HorizontalLayout, MeshText, MeshTextFont, VerticalLayout};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::{ffi::OsStr, path::PathBuf, sync::Arc};
|
use std::{ffi::OsStr, path::PathBuf, sync::Arc};
|
||||||
use stereokit_rust::{
|
use tracing::info_span;
|
||||||
font::Font,
|
|
||||||
sk::MainThreadToken,
|
|
||||||
system::{TextAlign, TextFit, TextStyle as SkTextStyle},
|
|
||||||
util::{Color128, Color32},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{TextAspect, TextStyle};
|
use super::{TextAspect, TextStyle};
|
||||||
|
|
||||||
static TEXT_REGISTRY: Registry<Text> = Registry::new();
|
static TEXT_REGISTRY: Registry<Text> = Registry::new();
|
||||||
|
|
||||||
fn convert_align(x_align: super::XAlign, y_align: super::YAlign) -> TextAlign {
|
const fn convert_align_x(x_align: super::XAlign) -> HorizontalLayout {
|
||||||
match (x_align, y_align) {
|
match x_align {
|
||||||
(super::XAlign::Left, super::YAlign::Top) => TextAlign::TopLeft,
|
super::XAlign::Left => HorizontalLayout::Left,
|
||||||
(super::XAlign::Left, super::YAlign::Center) => TextAlign::CenterLeft,
|
super::XAlign::Center => HorizontalLayout::Centered,
|
||||||
(super::XAlign::Left, super::YAlign::Bottom) => TextAlign::BottomLeft,
|
super::XAlign::Right => HorizontalLayout::Right,
|
||||||
(super::XAlign::Center, super::YAlign::Top) => TextAlign::Center,
|
|
||||||
(super::XAlign::Center, super::YAlign::Center) => TextAlign::Center,
|
|
||||||
(super::XAlign::Center, super::YAlign::Bottom) => TextAlign::BottomCenter,
|
|
||||||
(super::XAlign::Right, super::YAlign::Top) => TextAlign::TopRight,
|
|
||||||
(super::XAlign::Right, super::YAlign::Center) => TextAlign::CenterRight,
|
|
||||||
(super::XAlign::Right, super::YAlign::Bottom) => TextAlign::BottomRight,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const fn convert_align_y(y_align: super::YAlign) -> VerticalLayout {
|
||||||
|
match y_align {
|
||||||
|
super::YAlign::Top => VerticalLayout::Top,
|
||||||
|
super::YAlign::Center => VerticalLayout::Centered,
|
||||||
|
super::YAlign::Bottom => VerticalLayout::Bottom,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct StardustTextPlugin;
|
||||||
|
impl Plugin for StardustTextPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
let (tx, rx) = crossbeam_channel::unbounded();
|
||||||
|
_ = SPAWN_TEXT_SENDER.set(tx);
|
||||||
|
app.insert_resource(SpawnTextReader(rx));
|
||||||
|
app.add_systems(PostUpdate, update_text);
|
||||||
|
app.add_systems(PreUpdate, spawn_text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_text(mut surface_query: Query<&mut Transform>) {
|
||||||
|
for text in TEXT_REGISTRY.get_valid_contents() {
|
||||||
|
let Some(mut transform) = text
|
||||||
|
.entity
|
||||||
|
.get()
|
||||||
|
.and_then(|v| surface_query.get_mut(*v).ok())
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
// let data = text.data.lock();
|
||||||
|
|
||||||
|
*transform = Transform::from_matrix(text.space.global_transform());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_text(
|
||||||
|
reader: Res<SpawnTextReader>,
|
||||||
|
mut cmds: Commands,
|
||||||
|
mut mats: ResMut<Assets<DefaultMaterial>>,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
|
) {
|
||||||
|
for text in reader.try_iter() {
|
||||||
|
let _span = info_span!("spawning text").entered();
|
||||||
|
let _span2 = info_span!("text data lock").entered();
|
||||||
|
let data = text.data.lock();
|
||||||
|
drop(_span2);
|
||||||
|
let _span2 = info_span!("text str lock").entered();
|
||||||
|
let str = text.text.lock().clone();
|
||||||
|
drop(_span2);
|
||||||
|
let mat = mats.add(DefaultMaterial {
|
||||||
|
color: convert_linear_rgba(data.color).into(),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let font = text
|
||||||
|
.font_path
|
||||||
|
.as_ref()
|
||||||
|
.map(|p| asset_server.load(p.as_path()));
|
||||||
|
let mut text_entity = cmds.spawn((
|
||||||
|
MeshText {
|
||||||
|
text: atomicow::CowArc::Owned(str),
|
||||||
|
height: data.character_height,
|
||||||
|
depth: 0.0,
|
||||||
|
},
|
||||||
|
MeshMaterial3d(mat),
|
||||||
|
convert_align_x(data.text_align_x),
|
||||||
|
convert_align_y(data.text_align_y),
|
||||||
|
));
|
||||||
|
if let Some(font) = font {
|
||||||
|
text_entity.insert(MeshTextFont(font));
|
||||||
|
}
|
||||||
|
|
||||||
|
let entity = text_entity.id();
|
||||||
|
|
||||||
|
let _span = info_span!("setting OneCells").entered();
|
||||||
|
let _ = text.entity.set(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static SPAWN_TEXT_SENDER: OnceCell<crossbeam_channel::Sender<Arc<Text>>> = OnceCell::new();
|
||||||
|
#[derive(Resource, Deref)]
|
||||||
|
struct SpawnTextReader(crossbeam_channel::Receiver<Arc<Text>>);
|
||||||
|
|
||||||
pub struct Text {
|
pub struct Text {
|
||||||
space: Arc<Spatial>,
|
space: Arc<Spatial>,
|
||||||
font_path: Option<PathBuf>,
|
font_path: Option<PathBuf>,
|
||||||
style: OnceCell<SkTextStyle>,
|
text: Mutex<Arc<str>>,
|
||||||
|
|
||||||
text: Mutex<String>,
|
|
||||||
data: Mutex<TextStyle>,
|
data: Mutex<TextStyle>,
|
||||||
|
entity: OnceCell<Entity>,
|
||||||
}
|
}
|
||||||
impl Text {
|
impl Text {
|
||||||
pub fn add_to(node: &Arc<Node>, text: String, style: TextStyle) -> Result<Arc<Text>> {
|
pub fn add_to(node: &Arc<Node>, text: String, style: TextStyle) -> Result<Arc<Text>> {
|
||||||
let client = node.get_client().ok_or_else(|| eyre!("Client not found"))?;
|
let client = node.get_client().ok_or(ServerError::NoClient)?;
|
||||||
let text = TEXT_REGISTRY.add(Text {
|
let text = TEXT_REGISTRY.add(Text {
|
||||||
space: node.get_aspect::<Spatial>().unwrap().clone(),
|
space: node.get_aspect::<Spatial>().unwrap().clone(),
|
||||||
font_path: style.font.as_ref().and_then(|res| {
|
font_path: style.font.as_ref().and_then(|res| {
|
||||||
get_resource_file(res, &client, &[OsStr::new("ttf"), OsStr::new("otf")])
|
get_resource_file(res, &client, &[OsStr::new("ttf"), OsStr::new("otf")])
|
||||||
}),
|
}),
|
||||||
style: OnceCell::new(),
|
text: Mutex::new(text.into()),
|
||||||
|
|
||||||
text: Mutex::new(text),
|
|
||||||
data: Mutex::new(style),
|
data: Mutex::new(style),
|
||||||
|
entity: OnceCell::new(),
|
||||||
});
|
});
|
||||||
<Text as TextAspect>::add_node_members(node);
|
|
||||||
node.add_aspect_raw(text.clone());
|
node.add_aspect_raw(text.clone());
|
||||||
|
if let Some(sender) = SPAWN_TEXT_SENDER.get() {
|
||||||
|
let _ = sender.send(text.clone());
|
||||||
|
}
|
||||||
|
|
||||||
Ok(text)
|
Ok(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&self, token: &MainThreadToken) {
|
|
||||||
let style =
|
|
||||||
self.style
|
|
||||||
.get_or_try_init(|| -> Result<SkTextStyle, color_eyre::eyre::Error> {
|
|
||||||
let font = self
|
|
||||||
.font_path
|
|
||||||
.as_deref()
|
|
||||||
.and_then(|path| Font::from_file(path).ok())
|
|
||||||
.unwrap_or_default();
|
|
||||||
Ok(SkTextStyle::from_font(font, 1.0, Color32::WHITE))
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Ok(style) = style {
|
|
||||||
let text = self.text.lock();
|
|
||||||
let data = self.data.lock();
|
|
||||||
let transform = self.space.global_transform()
|
|
||||||
* Mat4::from_scale(vec3(
|
|
||||||
data.character_height,
|
|
||||||
data.character_height,
|
|
||||||
data.character_height,
|
|
||||||
));
|
|
||||||
if let Some(bounds) = &data.bounds {
|
|
||||||
stereokit_rust::system::Text::add_in(
|
|
||||||
token,
|
|
||||||
&*text,
|
|
||||||
transform,
|
|
||||||
Vec2::from(bounds.bounds) / data.character_height,
|
|
||||||
match bounds.fit {
|
|
||||||
super::TextFit::Wrap => TextFit::Wrap,
|
|
||||||
super::TextFit::Clip => TextFit::Clip,
|
|
||||||
super::TextFit::Squeeze => TextFit::Squeeze,
|
|
||||||
super::TextFit::Exact => TextFit::Exact,
|
|
||||||
super::TextFit::Overflow => TextFit::Overflow,
|
|
||||||
},
|
|
||||||
Some(*style),
|
|
||||||
Some(Color128::new(
|
|
||||||
data.color.c.r,
|
|
||||||
data.color.c.g,
|
|
||||||
data.color.c.b,
|
|
||||||
data.color.a,
|
|
||||||
)),
|
|
||||||
data.bounds
|
|
||||||
.as_ref()
|
|
||||||
.map(|b| convert_align(b.anchor_align_x, b.anchor_align_y)),
|
|
||||||
Some(convert_align(data.text_align_x, data.text_align_y)),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
stereokit_rust::system::Text::add_at(
|
|
||||||
token,
|
|
||||||
&*text,
|
|
||||||
transform,
|
|
||||||
Some(*style),
|
|
||||||
Some(Color128::new(
|
|
||||||
data.color.c.r,
|
|
||||||
data.color.c.g,
|
|
||||||
data.color.c.b,
|
|
||||||
data.color.a,
|
|
||||||
)),
|
|
||||||
data.bounds
|
|
||||||
.as_ref()
|
|
||||||
.map(|b| convert_align(b.anchor_align_x, b.anchor_align_y)),
|
|
||||||
Some(convert_align(data.text_align_x, data.text_align_y)),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Aspect for Text {
|
|
||||||
const NAME: &'static str = "Text";
|
|
||||||
}
|
}
|
||||||
impl TextAspect for Text {
|
impl TextAspect for Text {
|
||||||
fn set_character_height(
|
fn set_character_height(
|
||||||
@@ -148,25 +152,15 @@ impl TextAspect for Text {
|
|||||||
|
|
||||||
fn set_text(node: Arc<Node>, _calling_client: Arc<Client>, text: String) -> Result<()> {
|
fn set_text(node: Arc<Node>, _calling_client: Arc<Client>, text: String) -> Result<()> {
|
||||||
let this_text = node.get_aspect::<Text>()?;
|
let this_text = node.get_aspect::<Text>()?;
|
||||||
*this_text.text.lock() = text;
|
*this_text.text.lock() = text.into();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Drop for Text {
|
impl Drop for Text {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if let Some(style) = self.style.take() {
|
if let Some(e) = self.entity.get() {
|
||||||
destroy_queue::add(style);
|
let _ = DESTROY_ENTITY.send(*e);
|
||||||
}
|
}
|
||||||
TEXT_REGISTRY.remove(self);
|
TEXT_REGISTRY.remove(self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_all(token: &MainThreadToken) {
|
|
||||||
for text in TEXT_REGISTRY.get_valid_contents() {
|
|
||||||
if let Some(node) = text.space.node() {
|
|
||||||
if node.enabled() {
|
|
||||||
text.draw(token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ use super::spatial::{
|
|||||||
Spatial, SPATIAL_REF_GET_LOCAL_BOUNDING_BOX_SERVER_OPCODE,
|
Spatial, SPATIAL_REF_GET_LOCAL_BOUNDING_BOX_SERVER_OPCODE,
|
||||||
SPATIAL_REF_GET_RELATIVE_BOUNDING_BOX_SERVER_OPCODE, SPATIAL_REF_GET_TRANSFORM_SERVER_OPCODE,
|
SPATIAL_REF_GET_RELATIVE_BOUNDING_BOX_SERVER_OPCODE, SPATIAL_REF_GET_TRANSFORM_SERVER_OPCODE,
|
||||||
};
|
};
|
||||||
use super::{Aspect, Node};
|
use super::{Aspect, AspectIdentifier, Node};
|
||||||
use crate::core::client::Client;
|
use crate::core::client::Client;
|
||||||
use crate::create_interface;
|
use crate::core::error::Result;
|
||||||
use crate::nodes::spatial::Transform;
|
use crate::nodes::spatial::Transform;
|
||||||
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
|
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
|
||||||
use crate::nodes::spatial::SPATIAL_REF_ASPECT_ALIAS_INFO;
|
use crate::nodes::spatial::SPATIAL_REF_ASPECT_ALIAS_INFO;
|
||||||
use color_eyre::eyre::{OptionExt, Result};
|
use color_eyre::eyre::OptionExt;
|
||||||
use glam::{vec2, vec3, vec3a, Vec3, Vec3A, Vec3Swizzles};
|
use glam::{vec2, vec3, vec3a, Vec3, Vec3A, Vec3Swizzles};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
@@ -145,15 +145,65 @@ impl Field {
|
|||||||
shape: Mutex::new(shape),
|
shape: Mutex::new(shape),
|
||||||
};
|
};
|
||||||
let field = node.add_aspect(field);
|
let field = node.add_aspect(field);
|
||||||
<Field as FieldRefAspect>::add_node_members(node);
|
node.add_aspect(FieldRef);
|
||||||
<Field as FieldAspect>::add_node_members(node);
|
|
||||||
Ok(field)
|
Ok(field)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Aspect for Field {
|
impl AspectIdentifier for Field {
|
||||||
const NAME: &'static str = "Field";
|
impl_aspect_for_field_aspect_id! {}
|
||||||
}
|
}
|
||||||
impl FieldRefAspect for Field {
|
impl Aspect for Field {
|
||||||
|
impl_aspect_for_field_aspect! {}
|
||||||
|
}
|
||||||
|
impl FieldAspect for Field {
|
||||||
|
fn set_shape(node: Arc<Node>, _calling_client: Arc<Client>, shape: Shape) -> Result<()> {
|
||||||
|
let field = node.get_aspect::<Field>()?;
|
||||||
|
*field.shape.lock() = shape;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn export_field(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<u64> {
|
||||||
|
let id = rand::random();
|
||||||
|
EXPORTED_FIELDS.lock().insert(id, node);
|
||||||
|
Ok(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl FieldTrait for Field {
|
||||||
|
fn spatial_ref(&self) -> &Spatial {
|
||||||
|
&self.spatial
|
||||||
|
}
|
||||||
|
fn local_distance(&self, p: Vec3A) -> f32 {
|
||||||
|
match self.shape.lock().clone() {
|
||||||
|
Shape::Box(size) => {
|
||||||
|
let q = vec3(
|
||||||
|
p.x.abs() - (size.x * 0.5_f32),
|
||||||
|
p.y.abs() - (size.y * 0.5_f32),
|
||||||
|
p.z.abs() - (size.z * 0.5_f32),
|
||||||
|
);
|
||||||
|
let v = vec3a(q.x.max(0_f32), q.y.max(0_f32), q.z.max(0_f32));
|
||||||
|
v.length() + q.x.max(q.y.max(q.z)).min(0_f32)
|
||||||
|
}
|
||||||
|
Shape::Cylinder(CylinderShape { length, radius }) => {
|
||||||
|
let d = vec2(p.xy().length().abs() - radius, p.z.abs() - (length * 0.5));
|
||||||
|
d.x.max(d.y).min(0.0) + d.max(vec2(0.0, 0.0)).length()
|
||||||
|
}
|
||||||
|
Shape::Sphere(radius) => p.length() - radius,
|
||||||
|
Shape::Torus(TorusShape { radius_a, radius_b }) => {
|
||||||
|
let q = vec2(p.xz().length() - radius_a, p.y);
|
||||||
|
q.length() - radius_b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FieldRef;
|
||||||
|
impl AspectIdentifier for FieldRef {
|
||||||
|
impl_aspect_for_field_ref_aspect_id! {}
|
||||||
|
}
|
||||||
|
impl Aspect for FieldRef {
|
||||||
|
impl_aspect_for_field_ref_aspect! {}
|
||||||
|
}
|
||||||
|
impl FieldRefAspect for FieldRef {
|
||||||
async fn distance(
|
async fn distance(
|
||||||
node: Arc<Node>,
|
node: Arc<Node>,
|
||||||
_calling_client: Arc<Client>,
|
_calling_client: Arc<Client>,
|
||||||
@@ -205,56 +255,14 @@ impl FieldRefAspect for Field {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl FieldAspect for Field {
|
|
||||||
fn set_shape(node: Arc<Node>, _calling_client: Arc<Client>, shape: Shape) -> Result<()> {
|
|
||||||
let field = node.get_aspect::<Field>()?;
|
|
||||||
*field.shape.lock() = shape;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn export_field(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<u64> {
|
impl InterfaceAspect for Interface {
|
||||||
let id = rand::random();
|
|
||||||
EXPORTED_FIELDS.lock().insert(id, node);
|
|
||||||
Ok(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl FieldTrait for Field {
|
|
||||||
fn spatial_ref(&self) -> &Spatial {
|
|
||||||
&self.spatial
|
|
||||||
}
|
|
||||||
fn local_distance(&self, p: Vec3A) -> f32 {
|
|
||||||
match self.shape.lock().clone() {
|
|
||||||
Shape::Box(size) => {
|
|
||||||
let q = vec3(
|
|
||||||
p.x.abs() - (size.x * 0.5_f32),
|
|
||||||
p.y.abs() - (size.y * 0.5_f32),
|
|
||||||
p.z.abs() - (size.z * 0.5_f32),
|
|
||||||
);
|
|
||||||
let v = vec3a(q.x.max(0_f32), q.y.max(0_f32), q.z.max(0_f32));
|
|
||||||
v.length() + q.x.max(q.y.max(q.z)).min(0_f32)
|
|
||||||
}
|
|
||||||
Shape::Cylinder(CylinderShape { length, radius }) => {
|
|
||||||
let d = vec2(p.xy().length().abs() - radius, p.z.abs() - (length * 0.5));
|
|
||||||
d.x.max(d.y).min(0.0) + d.max(vec2(0.0, 0.0)).length()
|
|
||||||
}
|
|
||||||
Shape::Sphere(radius) => p.length() - radius,
|
|
||||||
Shape::Torus(TorusShape { radius_a, radius_b }) => {
|
|
||||||
let q = vec2(p.xz().length() - radius_a, p.y);
|
|
||||||
q.length() - radius_b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
create_interface!(FieldInterface);
|
|
||||||
pub struct FieldInterface;
|
|
||||||
impl InterfaceAspect for FieldInterface {
|
|
||||||
async fn import_field_ref(
|
async fn import_field_ref(
|
||||||
_node: Arc<Node>,
|
_node: Arc<Node>,
|
||||||
calling_client: Arc<Client>,
|
calling_client: Arc<Client>,
|
||||||
uid: u64,
|
uid: u64,
|
||||||
) -> Result<Arc<Node>> {
|
) -> Result<Arc<Node>> {
|
||||||
EXPORTED_FIELDS
|
Ok(EXPORTED_FIELDS
|
||||||
.lock()
|
.lock()
|
||||||
.get(&uid)
|
.get(&uid)
|
||||||
.map(|s| {
|
.map(|s| {
|
||||||
@@ -266,7 +274,7 @@ impl InterfaceAspect for FieldInterface {
|
|||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
})
|
})
|
||||||
.ok_or_eyre("Couldn't find spatial with that ID")
|
.ok_or_eyre("Couldn't find spatial with that ID")?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_field(
|
fn create_field(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use super::{InputHandlerAspect, INPUT_HANDLER_REGISTRY, INPUT_METHOD_REGISTRY};
|
use super::{InputHandlerAspect, INPUT_HANDLER_REGISTRY, INPUT_METHOD_REGISTRY};
|
||||||
use crate::nodes::{alias::AliasList, fields::Field, spatial::Spatial, Aspect, Node};
|
use crate::nodes::{alias::AliasList, fields::Field, spatial::Spatial, Node};
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@@ -23,9 +23,6 @@ impl InputHandler {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Aspect for InputHandler {
|
|
||||||
const NAME: &'static str = "InputHandler";
|
|
||||||
}
|
|
||||||
impl InputHandlerAspect for InputHandler {}
|
impl InputHandlerAspect for InputHandler {}
|
||||||
impl PartialEq for InputHandler {
|
impl PartialEq for InputHandler {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
|||||||
@@ -4,15 +4,14 @@ use super::{
|
|||||||
INPUT_METHOD_REGISTRY,
|
INPUT_METHOD_REGISTRY,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
core::{client::Client, registry::Registry},
|
core::{client::Client, error::Result, registry::Registry},
|
||||||
nodes::{
|
nodes::{
|
||||||
alias::{Alias, AliasList},
|
alias::{Alias, AliasList},
|
||||||
fields::{Field, FIELD_ALIAS_INFO},
|
fields::{Field, FIELD_ALIAS_INFO},
|
||||||
spatial::Spatial,
|
spatial::Spatial,
|
||||||
Aspect, Node,
|
Node,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use color_eyre::eyre::Result;
|
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use stardust_xr::values::Datamap;
|
use stardust_xr::values::Datamap;
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
@@ -45,13 +44,12 @@ impl InputMethod {
|
|||||||
internal_capture_requests: Registry::new(),
|
internal_capture_requests: Registry::new(),
|
||||||
captures: Registry::new(),
|
captures: Registry::new(),
|
||||||
};
|
};
|
||||||
<InputMethod as InputMethodRefAspect>::add_node_members(node);
|
|
||||||
<InputMethod as InputMethodAspect>::add_node_members(node);
|
|
||||||
for handler in INPUT_HANDLER_REGISTRY.get_valid_contents() {
|
for handler in INPUT_HANDLER_REGISTRY.get_valid_contents() {
|
||||||
method.handle_new_handler(&handler);
|
method.handle_new_handler(&handler);
|
||||||
}
|
}
|
||||||
let method = INPUT_METHOD_REGISTRY.add(method);
|
let method = INPUT_METHOD_REGISTRY.add(method);
|
||||||
node.add_aspect_raw(method.clone());
|
node.add_aspect_raw(method.clone());
|
||||||
|
node.add_aspect(InputMethodRef);
|
||||||
Ok(method)
|
Ok(method)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,25 +152,6 @@ impl InputMethod {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Aspect for InputMethod {
|
|
||||||
const NAME: &'static str = "InputMethod";
|
|
||||||
}
|
|
||||||
impl InputMethodRefAspect for InputMethod {
|
|
||||||
#[doc = "Have the input handler that this method reference came from capture the method for the next frame."]
|
|
||||||
fn request_capture(
|
|
||||||
node: Arc<Node>,
|
|
||||||
_calling_client: Arc<Client>,
|
|
||||||
handler: Arc<Node>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let input_method = node.get_aspect::<InputMethod>()?;
|
|
||||||
let input_handler = handler.get_aspect::<InputHandler>()?;
|
|
||||||
|
|
||||||
input_method
|
|
||||||
.internal_capture_requests
|
|
||||||
.add_raw(&input_handler);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl InputMethodAspect for InputMethod {
|
impl InputMethodAspect for InputMethod {
|
||||||
#[doc = "Set the spatial input component of this input method. You must keep the same input data type throughout the entire thing."]
|
#[doc = "Set the spatial input component of this input method. You must keep the same input data type throughout the entire thing."]
|
||||||
fn set_input(
|
fn set_input(
|
||||||
@@ -231,3 +210,21 @@ impl Drop for InputMethod {
|
|||||||
INPUT_METHOD_REGISTRY.remove(self);
|
INPUT_METHOD_REGISTRY.remove(self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct InputMethodRef;
|
||||||
|
impl InputMethodRefAspect for InputMethodRef {
|
||||||
|
#[doc = "Have the input handler that this method reference came from capture the method for the next frame."]
|
||||||
|
fn request_capture(
|
||||||
|
node: Arc<Node>,
|
||||||
|
_calling_client: Arc<Client>,
|
||||||
|
handler: Arc<Node>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let input_method = node.get_aspect::<InputMethod>()?;
|
||||||
|
let input_handler = handler.get_aspect::<InputHandler>()?;
|
||||||
|
|
||||||
|
input_method
|
||||||
|
.internal_capture_requests
|
||||||
|
.add_raw(&input_handler);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,15 +8,17 @@ mod tip;
|
|||||||
|
|
||||||
pub use handler::*;
|
pub use handler::*;
|
||||||
pub use method::*;
|
pub use method::*;
|
||||||
|
use tracing::debug_span;
|
||||||
|
|
||||||
use super::fields::Field;
|
use super::fields::Field;
|
||||||
use super::spatial::Spatial;
|
use super::spatial::Spatial;
|
||||||
use crate::create_interface;
|
use super::Aspect;
|
||||||
|
use super::AspectIdentifier;
|
||||||
|
use crate::core::error::Result;
|
||||||
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
|
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
|
||||||
use crate::nodes::spatial::SPATIAL_REF_ASPECT_ALIAS_INFO;
|
use crate::nodes::spatial::SPATIAL_REF_ASPECT_ALIAS_INFO;
|
||||||
use crate::{core::client::Client, nodes::Node};
|
use crate::{core::client::Client, nodes::Node};
|
||||||
use crate::{core::registry::Registry, nodes::spatial::Transform};
|
use crate::{core::registry::Registry, nodes::spatial::Transform};
|
||||||
use color_eyre::eyre::Result;
|
|
||||||
use stardust_xr::values::Datamap;
|
use stardust_xr::values::Datamap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@@ -25,6 +27,25 @@ pub static INPUT_HANDLER_REGISTRY: Registry<InputHandler> = Registry::new();
|
|||||||
|
|
||||||
stardust_xr_server_codegen::codegen_input_protocol!();
|
stardust_xr_server_codegen::codegen_input_protocol!();
|
||||||
|
|
||||||
|
impl AspectIdentifier for InputHandler {
|
||||||
|
impl_aspect_for_input_handler_aspect_id! {}
|
||||||
|
}
|
||||||
|
impl Aspect for InputHandler {
|
||||||
|
impl_aspect_for_input_handler_aspect! {}
|
||||||
|
}
|
||||||
|
impl AspectIdentifier for InputMethod {
|
||||||
|
impl_aspect_for_input_method_aspect_id! {}
|
||||||
|
}
|
||||||
|
impl Aspect for InputMethod {
|
||||||
|
impl_aspect_for_input_method_aspect! {}
|
||||||
|
}
|
||||||
|
impl AspectIdentifier for InputMethodRef {
|
||||||
|
impl_aspect_for_input_method_ref_aspect_id! {}
|
||||||
|
}
|
||||||
|
impl Aspect for InputMethodRef {
|
||||||
|
impl_aspect_for_input_method_ref_aspect! {}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait InputDataTrait {
|
pub trait InputDataTrait {
|
||||||
fn transform(&mut self, method: &InputMethod, handler: &InputHandler);
|
fn transform(&mut self, method: &InputMethod, handler: &InputHandler);
|
||||||
fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32;
|
fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32;
|
||||||
@@ -47,9 +68,7 @@ impl InputDataTrait for InputDataType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
create_interface!(InputInterface);
|
impl InterfaceAspect for Interface {
|
||||||
pub struct InputInterface;
|
|
||||||
impl InterfaceAspect for InputInterface {
|
|
||||||
#[doc = "Create an input method node"]
|
#[doc = "Create an input method node"]
|
||||||
fn create_input_method(
|
fn create_input_method(
|
||||||
_node: Arc<Node>,
|
_node: Arc<Node>,
|
||||||
@@ -122,6 +141,7 @@ pub fn process_input() {
|
|||||||
.clone()
|
.clone()
|
||||||
// filter out methods without the handler in their handler order
|
// filter out methods without the handler in their handler order
|
||||||
.filter(|a| {
|
.filter(|a| {
|
||||||
|
let _span = debug_span!("handlder_order lock").entered();
|
||||||
a.handler_order
|
a.handler_order
|
||||||
.lock()
|
.lock()
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
@@ -1,22 +1,19 @@
|
|||||||
use super::{
|
use super::{create_item_acceptor_flex, register_item_ui_flex, Item, ItemType};
|
||||||
create_item_acceptor_flex, register_item_ui_flex, Item, ItemAcceptor, ItemInterface, ItemType,
|
use crate::bail;
|
||||||
};
|
use crate::core::error::Result;
|
||||||
use crate::nodes::items::ITEM_ACCEPTOR_ASPECT_ALIAS_INFO;
|
use crate::nodes::items::ITEM_ACCEPTOR_ASPECT_ALIAS_INFO;
|
||||||
use crate::nodes::items::ITEM_ASPECT_ALIAS_INFO;
|
use crate::nodes::items::ITEM_ASPECT_ALIAS_INFO;
|
||||||
|
use crate::nodes::Aspect;
|
||||||
|
use crate::nodes::AspectIdentifier;
|
||||||
use crate::{
|
use crate::{
|
||||||
core::{client::Client, registry::Registry, scenegraph::MethodResponseSender},
|
core::{client::Client, registry::Registry, scenegraph::MethodResponseSender},
|
||||||
create_interface,
|
|
||||||
nodes::{
|
nodes::{
|
||||||
drawable::{
|
drawable::{model::ModelPart, shaders::UNLIT_SHADER_BYTES},
|
||||||
model::{MaterialWrapper, ModelPart},
|
|
||||||
shaders::UNLIT_SHADER_BYTES,
|
|
||||||
},
|
|
||||||
items::TypeInfo,
|
items::TypeInfo,
|
||||||
spatial::{Spatial, Transform},
|
spatial::{Spatial, Transform},
|
||||||
Message, Node,
|
Message, Node,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use color_eyre::eyre::{bail, eyre, Result};
|
|
||||||
use glam::Mat4;
|
use glam::Mat4;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use mint::{ColumnMatrix4, Vector2};
|
use mint::{ColumnMatrix4, Vector2};
|
||||||
@@ -25,20 +22,8 @@ use parking_lot::Mutex;
|
|||||||
|
|
||||||
use stardust_xr::schemas::flex::{deserialize, serialize};
|
use stardust_xr::schemas::flex::{deserialize, serialize};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use stereokit_rust::{
|
|
||||||
material::{Material, Transparency},
|
|
||||||
shader::Shader,
|
|
||||||
sk::MainThreadToken,
|
|
||||||
system::Renderer,
|
|
||||||
tex::{Tex, TexFormat, TexType},
|
|
||||||
util::Color128,
|
|
||||||
};
|
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
pub struct TexWrapper(pub Tex);
|
|
||||||
unsafe impl Send for TexWrapper {}
|
|
||||||
unsafe impl Sync for TexWrapper {}
|
|
||||||
|
|
||||||
stardust_xr_server_codegen::codegen_item_camera_protocol!();
|
stardust_xr_server_codegen::codegen_item_camera_protocol!();
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub(super) static ref ITEM_TYPE_INFO_CAMERA: TypeInfo = TypeInfo {
|
pub(super) static ref ITEM_TYPE_INFO_CAMERA: TypeInfo = TypeInfo {
|
||||||
@@ -48,6 +33,12 @@ lazy_static! {
|
|||||||
ui: Default::default(),
|
ui: Default::default(),
|
||||||
items: Registry::new(),
|
items: Registry::new(),
|
||||||
acceptors: Registry::new(),
|
acceptors: Registry::new(),
|
||||||
|
add_acceptor_aspect: |node| {
|
||||||
|
node.add_aspect(CameraItemAcceptor);
|
||||||
|
},
|
||||||
|
add_ui_aspect: |node| {
|
||||||
|
node.add_aspect(CameraItemUi);
|
||||||
|
},
|
||||||
new_acceptor_fn: |node, acceptor, acceptor_field| {
|
new_acceptor_fn: |node, acceptor, acceptor_field| {
|
||||||
let _ = camera_item_ui_client::create_acceptor(node, acceptor, acceptor_field);
|
let _ = camera_item_ui_client::create_acceptor(node, acceptor, acceptor_field);
|
||||||
}
|
}
|
||||||
@@ -62,30 +53,27 @@ struct FrameInfo {
|
|||||||
pub struct CameraItem {
|
pub struct CameraItem {
|
||||||
space: Arc<Spatial>,
|
space: Arc<Spatial>,
|
||||||
frame_info: Mutex<FrameInfo>,
|
frame_info: Mutex<FrameInfo>,
|
||||||
sk_tex: OnceCell<TexWrapper>,
|
sk_tex: OnceCell<()>,
|
||||||
sk_mat: OnceCell<Arc<MaterialWrapper>>,
|
sk_mat: OnceCell<Arc<()>>,
|
||||||
applied_to: Registry<ModelPart>,
|
applied_to: Registry<ModelPart>,
|
||||||
apply_to: Registry<ModelPart>,
|
apply_to: Registry<ModelPart>,
|
||||||
}
|
}
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
impl CameraItem {
|
impl CameraItem {
|
||||||
pub fn add_to(node: &Arc<Node>, proj_matrix: Mat4, px_size: Vector2<u32>) {
|
pub fn add_to(node: &Arc<Node>, proj_matrix: Mat4, px_size: Vector2<u32>) {
|
||||||
Item::add_to(
|
let item = Arc::new(CameraItem {
|
||||||
node,
|
space: node.get_aspect::<Spatial>().unwrap().clone(),
|
||||||
&ITEM_TYPE_INFO_CAMERA,
|
frame_info: Mutex::new(FrameInfo {
|
||||||
ItemType::Camera(CameraItem {
|
proj_matrix,
|
||||||
space: node.get_aspect::<Spatial>().unwrap().clone(),
|
px_size,
|
||||||
frame_info: Mutex::new(FrameInfo {
|
|
||||||
proj_matrix,
|
|
||||||
px_size,
|
|
||||||
}),
|
|
||||||
sk_tex: OnceCell::new(),
|
|
||||||
sk_mat: OnceCell::new(),
|
|
||||||
applied_to: Registry::new(),
|
|
||||||
apply_to: Registry::new(),
|
|
||||||
}),
|
}),
|
||||||
);
|
sk_tex: OnceCell::new(),
|
||||||
// <CameraItem as CameraItemAspect>::node_methods(node);
|
sk_mat: OnceCell::new(),
|
||||||
|
applied_to: Registry::new(),
|
||||||
|
apply_to: Registry::new(),
|
||||||
|
});
|
||||||
|
Item::add_to(node, &ITEM_TYPE_INFO_CAMERA, ItemType::Camera(item.clone()));
|
||||||
|
node.add_aspect_raw(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn frame_flex(
|
fn frame_flex(
|
||||||
@@ -97,7 +85,7 @@ impl CameraItem {
|
|||||||
response.wrap_sync(move || {
|
response.wrap_sync(move || {
|
||||||
let ItemType::Camera(_camera) = &node.get_aspect::<Item>().unwrap().specialization
|
let ItemType::Camera(_camera) = &node.get_aspect::<Item>().unwrap().specialization
|
||||||
else {
|
else {
|
||||||
return Err(eyre!("Wrong item type?"));
|
bail!("Wrong item type?");
|
||||||
};
|
};
|
||||||
Ok(serialize(())?.into())
|
Ok(serialize(())?.into())
|
||||||
});
|
});
|
||||||
@@ -109,7 +97,7 @@ impl CameraItem {
|
|||||||
message: Message,
|
message: Message,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let ItemType::Camera(camera) = &node.get_aspect::<Item>().unwrap().specialization else {
|
let ItemType::Camera(camera) = &node.get_aspect::<Item>().unwrap().specialization else {
|
||||||
bail!("Wrong item type?")
|
bail!("Wrong item type?");
|
||||||
};
|
};
|
||||||
let model_part_node =
|
let model_part_node =
|
||||||
calling_client.get_node("Model part", deserialize(&message.data).unwrap())?;
|
calling_client.get_node("Model part", deserialize(&message.data).unwrap())?;
|
||||||
@@ -167,9 +155,31 @@ impl CameraItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl AspectIdentifier for CameraItem {
|
||||||
|
impl_aspect_for_camera_item_aspect_id! {}
|
||||||
|
}
|
||||||
|
impl Aspect for CameraItem {
|
||||||
|
impl_aspect_for_camera_item_aspect! {}
|
||||||
|
}
|
||||||
impl CameraItemAspect for CameraItem {}
|
impl CameraItemAspect for CameraItem {}
|
||||||
|
|
||||||
impl CameraItemAcceptorAspect for ItemAcceptor {
|
pub struct CameraItemUi;
|
||||||
|
impl AspectIdentifier for CameraItemUi {
|
||||||
|
impl_aspect_for_camera_item_ui_aspect_id! {}
|
||||||
|
}
|
||||||
|
impl Aspect for CameraItemUi {
|
||||||
|
impl_aspect_for_camera_item_ui_aspect! {}
|
||||||
|
}
|
||||||
|
impl CameraItemUiAspect for CameraItemUi {}
|
||||||
|
|
||||||
|
pub struct CameraItemAcceptor;
|
||||||
|
impl AspectIdentifier for CameraItemAcceptor {
|
||||||
|
impl_aspect_for_camera_item_acceptor_aspect_id! {}
|
||||||
|
}
|
||||||
|
impl Aspect for CameraItemAcceptor {
|
||||||
|
impl_aspect_for_camera_item_acceptor_aspect! {}
|
||||||
|
}
|
||||||
|
impl CameraItemAcceptorAspect for CameraItemAcceptor {
|
||||||
fn capture_item(node: Arc<Node>, _calling_client: Arc<Client>, item: Arc<Node>) -> Result<()> {
|
fn capture_item(node: Arc<Node>, _calling_client: Arc<Client>, item: Arc<Node>) -> Result<()> {
|
||||||
super::acceptor_capture_item_flex(node, item)
|
super::acceptor_capture_item_flex(node, item)
|
||||||
}
|
}
|
||||||
@@ -184,8 +194,7 @@ pub fn update(token: &MainThreadToken) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
create_interface!(ItemInterface);
|
impl InterfaceAspect for Interface {
|
||||||
impl InterfaceAspect for ItemInterface {
|
|
||||||
#[doc = "Create a camera item at a specific location"]
|
#[doc = "Create a camera item at a specific location"]
|
||||||
fn create_camera_item(
|
fn create_camera_item(
|
||||||
_node: Arc<Node>,
|
_node: Arc<Node>,
|
||||||
@@ -206,19 +215,21 @@ impl InterfaceAspect for ItemInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[doc = "Register this client to manage camera items and create default 3D UI for them."]
|
#[doc = "Register this client to manage camera items and create default 3D UI for them."]
|
||||||
fn register_camera_item_ui(_node: Arc<Node>, calling_client: Arc<Client>) -> Result<()> {
|
fn register_camera_item_ui(node: Arc<Node>, calling_client: Arc<Client>) -> Result<()> {
|
||||||
|
node.add_aspect(CameraItemUi);
|
||||||
register_item_ui_flex(calling_client, &ITEM_TYPE_INFO_CAMERA)
|
register_item_ui_flex(calling_client, &ITEM_TYPE_INFO_CAMERA)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc = "Create an item acceptor to allow temporary ownership of a given type of item. Creates a node at `/item/camera/acceptor/<name>`."]
|
#[doc = "Create an item acceptor to allow temporary ownership of a given type of item. Creates a node at `/item/camera/acceptor/<name>`."]
|
||||||
fn create_camera_item_acceptor(
|
fn create_camera_item_acceptor(
|
||||||
_node: Arc<Node>,
|
node: Arc<Node>,
|
||||||
calling_client: Arc<Client>,
|
calling_client: Arc<Client>,
|
||||||
id: u64,
|
id: u64,
|
||||||
parent: Arc<Node>,
|
parent: Arc<Node>,
|
||||||
transform: Transform,
|
transform: Transform,
|
||||||
field: Arc<Node>,
|
field: Arc<Node>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
node.add_aspect(CameraItemAcceptor);
|
||||||
create_item_acceptor_flex(
|
create_item_acceptor_flex(
|
||||||
calling_client,
|
calling_client,
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
pub mod camera;
|
// TODO: reimplement with bevy
|
||||||
|
// pub mod camera;
|
||||||
pub mod panel;
|
pub mod panel;
|
||||||
|
|
||||||
use self::camera::CameraItem;
|
// use self::camera::CameraItem;
|
||||||
use self::panel::PanelItemTrait;
|
use self::panel::PanelItemTrait;
|
||||||
use super::alias::AliasList;
|
use super::alias::AliasList;
|
||||||
use super::fields::{Field, FIELD_ALIAS_INFO};
|
use super::fields::{Field, FIELD_ALIAS_INFO};
|
||||||
use super::spatial::Spatial;
|
use super::spatial::Spatial;
|
||||||
use super::{Alias, Aspect, Node};
|
use super::{Alias, Aspect, AspectIdentifier, Node};
|
||||||
use crate::core::client::Client;
|
use crate::core::client::Client;
|
||||||
|
use crate::core::error::Result;
|
||||||
use crate::core::registry::Registry;
|
use crate::core::registry::Registry;
|
||||||
|
use crate::ensure;
|
||||||
use crate::nodes::alias::AliasInfo;
|
use crate::nodes::alias::AliasInfo;
|
||||||
use crate::nodes::spatial::Transform;
|
use crate::nodes::spatial::Transform;
|
||||||
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
|
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
|
||||||
use color_eyre::eyre::{ensure, Result};
|
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
@@ -47,6 +49,8 @@ pub struct TypeInfo {
|
|||||||
pub ui: Mutex<Weak<ItemUI>>,
|
pub ui: Mutex<Weak<ItemUI>>,
|
||||||
pub items: Registry<Item>,
|
pub items: Registry<Item>,
|
||||||
pub acceptors: Registry<ItemAcceptor>,
|
pub acceptors: Registry<ItemAcceptor>,
|
||||||
|
pub add_ui_aspect: fn(node: &Node),
|
||||||
|
pub add_acceptor_aspect: fn(node: &Node),
|
||||||
pub new_acceptor_fn: fn(node: &Node, acceptor: &Arc<Node>, acceptor_field: &Arc<Node>),
|
pub new_acceptor_fn: fn(node: &Node, acceptor: &Arc<Node>, acceptor_field: &Arc<Node>),
|
||||||
}
|
}
|
||||||
impl Hash for TypeInfo {
|
impl Hash for TypeInfo {
|
||||||
@@ -81,7 +85,6 @@ impl Item {
|
|||||||
};
|
};
|
||||||
let item = type_info.items.add(item);
|
let item = type_info.items.add(item);
|
||||||
|
|
||||||
<Item as ItemAspect>::add_node_members(node);
|
|
||||||
if let Some(ui) = type_info.ui.lock().upgrade() {
|
if let Some(ui) = type_info.ui.lock().upgrade() {
|
||||||
ui.handle_create_item(&item);
|
ui.handle_create_item(&item);
|
||||||
}
|
}
|
||||||
@@ -108,8 +111,11 @@ impl Item {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl AspectIdentifier for Item {
|
||||||
|
impl_aspect_for_item_aspect_id! {}
|
||||||
|
}
|
||||||
impl Aspect for Item {
|
impl Aspect for Item {
|
||||||
const NAME: &'static str = "Item";
|
impl_aspect_for_item_aspect! {}
|
||||||
}
|
}
|
||||||
impl ItemAspect for Item {
|
impl ItemAspect for Item {
|
||||||
fn release(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
|
fn release(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
|
||||||
@@ -129,19 +135,19 @@ impl Drop for Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub enum ItemType {
|
pub enum ItemType {
|
||||||
Camera(CameraItem),
|
// Camera(CameraItem),
|
||||||
Panel(Arc<dyn PanelItemTrait>),
|
Panel(Arc<dyn PanelItemTrait>),
|
||||||
}
|
}
|
||||||
impl ItemType {
|
impl ItemType {
|
||||||
fn send_ui_item_created(&self, node: &Node, item: &Arc<Node>) {
|
fn send_ui_item_created(&self, node: &Node, item: &Arc<Node>) {
|
||||||
match self {
|
match self {
|
||||||
ItemType::Camera(c) => c.send_ui_item_created(node, item),
|
// ItemType::Camera(c) => c.send_ui_item_created(node, item),
|
||||||
ItemType::Panel(p) => p.send_ui_item_created(node, item),
|
ItemType::Panel(p) => p.send_ui_item_created(node, item),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn send_acceptor_item_created(&self, node: &Node, item: &Arc<Node>) {
|
fn send_acceptor_item_created(&self, node: &Node, item: &Arc<Node>) {
|
||||||
match self {
|
match self {
|
||||||
ItemType::Camera(c) => c.send_acceptor_item_created(node, item),
|
// ItemType::Camera(c) => c.send_acceptor_item_created(node, item),
|
||||||
ItemType::Panel(p) => p.send_acceptor_item_created(node, item),
|
ItemType::Panel(p) => p.send_acceptor_item_created(node, item),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -284,8 +290,11 @@ impl ItemUI {
|
|||||||
.remove_aspect(acceptor.field.as_ref());
|
.remove_aspect(acceptor.field.as_ref());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl AspectIdentifier for ItemUI {
|
||||||
|
impl_aspect_for_item_ui_aspect_id! {}
|
||||||
|
}
|
||||||
impl Aspect for ItemUI {
|
impl Aspect for ItemUI {
|
||||||
const NAME: &'static str = "Item";
|
impl_aspect_for_item_ui_aspect! {}
|
||||||
}
|
}
|
||||||
impl Drop for ItemUI {
|
impl Drop for ItemUI {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
@@ -342,8 +351,11 @@ impl ItemAcceptor {
|
|||||||
let _ = item_acceptor_client::release_item(&node, alias.id);
|
let _ = item_acceptor_client::release_item(&node, alias.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl AspectIdentifier for ItemAcceptor {
|
||||||
|
impl_aspect_for_item_acceptor_aspect_id! {}
|
||||||
|
}
|
||||||
impl Aspect for ItemAcceptor {
|
impl Aspect for ItemAcceptor {
|
||||||
const NAME: &'static str = "ItemAcceptor";
|
impl_aspect_for_item_acceptor_aspect! {}
|
||||||
}
|
}
|
||||||
impl ItemAcceptorAspect for ItemAcceptor {}
|
impl ItemAcceptorAspect for ItemAcceptor {}
|
||||||
impl Drop for ItemAcceptor {
|
impl Drop for ItemAcceptor {
|
||||||
@@ -364,6 +376,7 @@ pub fn register_item_ui_flex(
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let ui = Node::from_id(&calling_client, type_info.ui_node_id, true).add_to_scenegraph()?;
|
let ui = Node::from_id(&calling_client, type_info.ui_node_id, true).add_to_scenegraph()?;
|
||||||
ItemUI::add_to(&ui, type_info)?;
|
ItemUI::add_to(&ui, type_info)?;
|
||||||
|
(type_info.add_ui_aspect)(&ui);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn create_item_acceptor_flex(
|
fn create_item_acceptor_flex(
|
||||||
@@ -381,6 +394,7 @@ fn create_item_acceptor_flex(
|
|||||||
let node = Node::from_id(&calling_client, id, true).add_to_scenegraph()?;
|
let node = Node::from_id(&calling_client, id, true).add_to_scenegraph()?;
|
||||||
Spatial::add_to(&node, Some(space.clone()), transform, false);
|
Spatial::add_to(&node, Some(space.clone()), transform, false);
|
||||||
ItemAcceptor::add_to(&node, type_info, field);
|
ItemAcceptor::add_to(&node, type_info, field);
|
||||||
|
(type_info.add_acceptor_aspect)(&node);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,6 +405,3 @@ fn acceptor_capture_item_flex(node: Arc<Node>, item: Arc<Node>) -> Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ItemInterface;
|
|
||||||
// create_interface!(ItemInterface);
|
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
use super::{create_item_acceptor_flex, register_item_ui_flex, ItemAcceptor, ItemInterface};
|
// use super::camera::CameraItemAcceptor;
|
||||||
|
use super::{create_item_acceptor_flex, register_item_ui_flex};
|
||||||
|
use crate::bail;
|
||||||
|
use crate::core::error::Result;
|
||||||
use crate::nodes::items::ITEM_ACCEPTOR_ASPECT_ALIAS_INFO;
|
use crate::nodes::items::ITEM_ACCEPTOR_ASPECT_ALIAS_INFO;
|
||||||
use crate::nodes::items::ITEM_ASPECT_ALIAS_INFO;
|
use crate::nodes::items::ITEM_ASPECT_ALIAS_INFO;
|
||||||
|
use crate::nodes::{Aspect, AspectIdentifier};
|
||||||
use crate::{
|
use crate::{
|
||||||
core::{
|
core::{
|
||||||
client::{get_env, state, Client, INTERNAL_CLIENT},
|
client::{get_env, state, Client, INTERNAL_CLIENT},
|
||||||
registry::Registry,
|
registry::Registry,
|
||||||
},
|
},
|
||||||
create_interface,
|
|
||||||
nodes::{
|
nodes::{
|
||||||
drawable::model::ModelPart,
|
drawable::model::ModelPart,
|
||||||
items::{Item, ItemType, TypeInfo},
|
items::{Item, ItemType, TypeInfo},
|
||||||
@@ -14,10 +17,11 @@ use crate::{
|
|||||||
Node,
|
Node,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use color_eyre::eyre::Result;
|
|
||||||
use glam::Mat4;
|
use glam::Mat4;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use mint::Vector2;
|
use mint::Vector2;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use slotmap::{DefaultKey, Key, KeyData, SlotMap};
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
use tracing::{debug, info};
|
use tracing::{debug, info};
|
||||||
|
|
||||||
@@ -32,6 +36,7 @@ impl Default for Geometry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
pub static ref KEYMAPS: Mutex<SlotMap<DefaultKey, String>> = Mutex::new(SlotMap::default());
|
||||||
pub static ref ITEM_TYPE_INFO_PANEL: TypeInfo = TypeInfo {
|
pub static ref ITEM_TYPE_INFO_PANEL: TypeInfo = TypeInfo {
|
||||||
type_name: "panel",
|
type_name: "panel",
|
||||||
alias_info: PANEL_ITEM_ASPECT_ALIAS_INFO.clone(),
|
alias_info: PANEL_ITEM_ASPECT_ALIAS_INFO.clone(),
|
||||||
@@ -39,6 +44,12 @@ lazy_static! {
|
|||||||
ui: Default::default(),
|
ui: Default::default(),
|
||||||
items: Registry::new(),
|
items: Registry::new(),
|
||||||
acceptors: Registry::new(),
|
acceptors: Registry::new(),
|
||||||
|
add_acceptor_aspect: |node| {
|
||||||
|
node.add_aspect(PanelItemUi);
|
||||||
|
},
|
||||||
|
add_ui_aspect: |node| {
|
||||||
|
node.add_aspect(PanelItemAcceptor);
|
||||||
|
},
|
||||||
new_acceptor_fn: |node, acceptor, acceptor_field| {
|
new_acceptor_fn: |node, acceptor, acceptor_field| {
|
||||||
let _ = panel_item_ui_client::create_acceptor(node, acceptor, acceptor_field);
|
let _ = panel_item_ui_client::create_acceptor(node, acceptor, acceptor_field);
|
||||||
}
|
}
|
||||||
@@ -65,7 +76,7 @@ pub trait Backend: Send + Sync + 'static {
|
|||||||
scroll_steps: Option<Vector2<f32>>,
|
scroll_steps: Option<Vector2<f32>>,
|
||||||
);
|
);
|
||||||
|
|
||||||
fn keyboard_keys(&self, surface: &SurfaceId, keymap_id: u64, keys: Vec<i32>);
|
fn keyboard_key(&self, surface: &SurfaceId, keymap_id: u64, key: u32, pressed: bool);
|
||||||
|
|
||||||
fn touch_down(&self, surface: &SurfaceId, id: u32, position: Vector2<f32>);
|
fn touch_down(&self, surface: &SurfaceId, id: u32, position: Vector2<f32>);
|
||||||
fn touch_move(&self, id: u32, position: Vector2<f32>);
|
fn touch_move(&self, id: u32, position: Vector2<f32>);
|
||||||
@@ -115,7 +126,7 @@ impl<B: Backend> PanelItem<B> {
|
|||||||
&ITEM_TYPE_INFO_PANEL,
|
&ITEM_TYPE_INFO_PANEL,
|
||||||
ItemType::Panel(generic_panel_item),
|
ItemType::Panel(generic_panel_item),
|
||||||
);
|
);
|
||||||
<Self as PanelItemAspect>::add_node_members(&node);
|
node.add_aspect_raw(panel_item.clone());
|
||||||
|
|
||||||
(node, panel_item)
|
(node, panel_item)
|
||||||
}
|
}
|
||||||
@@ -197,9 +208,12 @@ impl<B: Backend> PanelItem<B> {
|
|||||||
panel_item_client::destroy_child(&node, id);
|
panel_item_client::destroy_child(&node, id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl<B: Backend> AspectIdentifier for PanelItem<B> {
|
||||||
// make these stupid vectors u32 in the protocol somehow!!!!!!!1
|
impl_aspect_for_panel_item_aspect_id! {}
|
||||||
|
}
|
||||||
|
impl<B: Backend> Aspect for PanelItem<B> {
|
||||||
|
impl_aspect_for_panel_item_aspect! {}
|
||||||
|
}
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
impl<B: Backend> PanelItemAspect for PanelItem<B> {
|
impl<B: Backend> PanelItemAspect for PanelItem<B> {
|
||||||
#[doc = "Apply the cursor as a material to a model."]
|
#[doc = "Apply the cursor as a material to a model."]
|
||||||
@@ -341,19 +355,20 @@ impl<B: Backend> PanelItemAspect for PanelItem<B> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[doc = "Send a series of key presses and releases (positive keycode for pressed, negative for released)."]
|
#[doc = "Send a series of key presses and releases (positive keycode for pressed, negative for released)."]
|
||||||
fn keyboard_keys(
|
fn keyboard_key(
|
||||||
node: Arc<Node>,
|
node: Arc<Node>,
|
||||||
_calling_client: Arc<Client>,
|
_calling_client: Arc<Client>,
|
||||||
surface: SurfaceId,
|
surface: SurfaceId,
|
||||||
keymap_id: u64,
|
keymap_id: u64,
|
||||||
keys: Vec<i32>,
|
key: u32,
|
||||||
|
pressed: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let Some(panel_item) = panel_item_from_node(&node) else {
|
let Some(panel_item) = panel_item_from_node(&node) else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
panel_item
|
panel_item
|
||||||
.backend()
|
.backend()
|
||||||
.keyboard_keys(&surface, keymap_id, keys);
|
.keyboard_key(&surface, keymap_id, key, pressed);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,7 +420,23 @@ impl<B: Backend> PanelItemAspect for PanelItem<B> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PanelItemAcceptorAspect for ItemAcceptor {
|
pub struct PanelItemUi;
|
||||||
|
impl AspectIdentifier for PanelItemUi {
|
||||||
|
impl_aspect_for_panel_item_ui_aspect_id! {}
|
||||||
|
}
|
||||||
|
impl Aspect for PanelItemUi {
|
||||||
|
impl_aspect_for_panel_item_ui_aspect! {}
|
||||||
|
}
|
||||||
|
impl PanelItemUiAspect for PanelItemUi {}
|
||||||
|
|
||||||
|
pub struct PanelItemAcceptor;
|
||||||
|
impl AspectIdentifier for PanelItemAcceptor {
|
||||||
|
impl_aspect_for_panel_item_acceptor_aspect_id! {}
|
||||||
|
}
|
||||||
|
impl Aspect for PanelItemAcceptor {
|
||||||
|
impl_aspect_for_panel_item_acceptor_aspect! {}
|
||||||
|
}
|
||||||
|
impl PanelItemAcceptorAspect for PanelItemAcceptor {
|
||||||
fn capture_item(node: Arc<Node>, _calling_client: Arc<Client>, item: Arc<Node>) -> Result<()> {
|
fn capture_item(node: Arc<Node>, _calling_client: Arc<Client>, item: Arc<Node>) -> Result<()> {
|
||||||
super::acceptor_capture_item_flex(node, item)
|
super::acceptor_capture_item_flex(node, item)
|
||||||
}
|
}
|
||||||
@@ -435,22 +466,23 @@ impl<B: Backend> Drop for PanelItem<B> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
create_interface!(ItemInterface);
|
impl InterfaceAspect for Interface {
|
||||||
impl InterfaceAspect for ItemInterface {
|
|
||||||
#[doc = "Register this client to manage the items of a certain type and create default 3D UI for them."]
|
#[doc = "Register this client to manage the items of a certain type and create default 3D UI for them."]
|
||||||
fn register_panel_item_ui(_node: Arc<Node>, calling_client: Arc<Client>) -> Result<()> {
|
fn register_panel_item_ui(node: Arc<Node>, calling_client: Arc<Client>) -> Result<()> {
|
||||||
|
// node.add_aspect(CameraItemAcceptor);
|
||||||
register_item_ui_flex(calling_client, &ITEM_TYPE_INFO_PANEL)
|
register_item_ui_flex(calling_client, &ITEM_TYPE_INFO_PANEL)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc = "Create an item acceptor to allow temporary ownership of a given type of item. Creates a node at `/item/<item_type>/acceptor/<name>`."]
|
#[doc = "Create an item acceptor to allow temporary ownership of a given type of item. Creates a node at `/item/<item_type>/acceptor/<name>`."]
|
||||||
fn create_panel_item_acceptor(
|
fn create_panel_item_acceptor(
|
||||||
_node: Arc<Node>,
|
node: Arc<Node>,
|
||||||
calling_client: Arc<Client>,
|
calling_client: Arc<Client>,
|
||||||
id: u64,
|
id: u64,
|
||||||
parent: Arc<Node>,
|
parent: Arc<Node>,
|
||||||
transform: Transform,
|
transform: Transform,
|
||||||
field: Arc<Node>,
|
field: Arc<Node>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
node.add_aspect(PanelItemAcceptor);
|
||||||
create_item_acceptor_flex(
|
create_item_acceptor_flex(
|
||||||
calling_client,
|
calling_client,
|
||||||
id,
|
id,
|
||||||
@@ -460,4 +492,36 @@ impl InterfaceAspect for ItemInterface {
|
|||||||
field,
|
field,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn register_keymap(
|
||||||
|
_node: Arc<Node>,
|
||||||
|
_calling_client: Arc<Client>,
|
||||||
|
keymap: String,
|
||||||
|
) -> Result<u64> {
|
||||||
|
let mut keymaps = KEYMAPS.lock();
|
||||||
|
if let Some(found_keymap_id) = keymaps
|
||||||
|
.iter()
|
||||||
|
.filter(|(_k, v)| *v == &keymap)
|
||||||
|
.map(|(k, _v)| k)
|
||||||
|
.last()
|
||||||
|
{
|
||||||
|
return Ok(found_keymap_id.data().as_ffi());
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = keymaps.insert(keymap);
|
||||||
|
Ok(key.data().as_ffi())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_keymap(
|
||||||
|
_node: Arc<Node>,
|
||||||
|
_calling_client: Arc<Client>,
|
||||||
|
keymap_id: u64,
|
||||||
|
) -> Result<String> {
|
||||||
|
let keymaps = KEYMAPS.lock();
|
||||||
|
let Some(keymap) = keymaps.get(KeyData::from_ffi(keymap_id).into()) else {
|
||||||
|
bail!("Could not find keymap. Try registering it");
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(keymap.clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
192
src/nodes/mod.rs
192
src/nodes/mod.rs
@@ -1,6 +1,5 @@
|
|||||||
pub mod alias;
|
pub mod alias;
|
||||||
pub mod audio;
|
pub mod audio;
|
||||||
pub mod data;
|
|
||||||
pub mod drawable;
|
pub mod drawable;
|
||||||
pub mod fields;
|
pub mod fields;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
@@ -10,10 +9,10 @@ pub mod spatial;
|
|||||||
|
|
||||||
use self::alias::Alias;
|
use self::alias::Alias;
|
||||||
use crate::core::client::Client;
|
use crate::core::client::Client;
|
||||||
|
use crate::core::error::{Result, ServerError};
|
||||||
|
use crate::core::queued_mutex::QueuedMutex;
|
||||||
use crate::core::registry::Registry;
|
use crate::core::registry::Registry;
|
||||||
use crate::core::scenegraph::MethodResponseSender;
|
use crate::core::scenegraph::MethodResponseSender;
|
||||||
use color_eyre::eyre::{eyre, Result};
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
use portable_atomic::{AtomicBool, Ordering};
|
use portable_atomic::{AtomicBool, Ordering};
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
@@ -21,6 +20,7 @@ use spatial::Spatial;
|
|||||||
use stardust_xr::messenger::MessageSenderHandle;
|
use stardust_xr::messenger::MessageSenderHandle;
|
||||||
use stardust_xr::scenegraph::ScenegraphError;
|
use stardust_xr::scenegraph::ScenegraphError;
|
||||||
use stardust_xr::schemas::flex::{deserialize, serialize};
|
use stardust_xr::schemas::flex::{deserialize, serialize};
|
||||||
|
use tracing::debug_span;
|
||||||
use std::any::{Any, TypeId};
|
use std::any::{Any, TypeId};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::os::fd::OwnedFd;
|
use std::os::fd::OwnedFd;
|
||||||
@@ -46,11 +46,29 @@ impl AsRef<[u8]> for Message {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Signal = fn(Arc<Node>, Arc<Client>, Message) -> Result<()>;
|
|
||||||
pub type Method = fn(Arc<Node>, Arc<Client>, Message, MethodResponseSender);
|
|
||||||
|
|
||||||
stardust_xr_server_codegen::codegen_node_protocol!();
|
stardust_xr_server_codegen::codegen_node_protocol!();
|
||||||
|
|
||||||
|
pub struct Owned;
|
||||||
|
impl AspectIdentifier for Owned {
|
||||||
|
impl_aspect_for_owned_aspect_id! {}
|
||||||
|
}
|
||||||
|
impl Aspect for Owned {
|
||||||
|
impl_aspect_for_owned_aspect! {}
|
||||||
|
}
|
||||||
|
impl OwnedAspect for Owned {
|
||||||
|
fn set_enabled(node: Arc<Node>, _calling_client: Arc<Client>, enabled: bool) -> Result<()> {
|
||||||
|
node.set_enabled(enabled);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn destroy(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
|
||||||
|
if node.destroyable {
|
||||||
|
node.destroy();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct OwnedNode(pub Arc<Node>);
|
pub struct OwnedNode(pub Arc<Node>);
|
||||||
impl Drop for OwnedNode {
|
impl Drop for OwnedNode {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
@@ -64,8 +82,6 @@ pub struct Node {
|
|||||||
client: Weak<Client>,
|
client: Weak<Client>,
|
||||||
message_sender_handle: Option<MessageSenderHandle>,
|
message_sender_handle: Option<MessageSenderHandle>,
|
||||||
|
|
||||||
local_signals: Mutex<FxHashMap<u64, Signal>>,
|
|
||||||
local_methods: Mutex<FxHashMap<u64, Method>>,
|
|
||||||
aliases: Registry<Alias>,
|
aliases: Registry<Alias>,
|
||||||
aspects: Aspects,
|
aspects: Aspects,
|
||||||
destroyable: bool,
|
destroyable: bool,
|
||||||
@@ -87,41 +103,47 @@ impl Node {
|
|||||||
client: Arc::downgrade(client),
|
client: Arc::downgrade(client),
|
||||||
message_sender_handle: client.message_sender_handle.clone(),
|
message_sender_handle: client.message_sender_handle.clone(),
|
||||||
id,
|
id,
|
||||||
local_signals: Default::default(),
|
|
||||||
local_methods: Default::default(),
|
|
||||||
aliases: Default::default(),
|
aliases: Default::default(),
|
||||||
aspects: Default::default(),
|
aspects: Default::default(),
|
||||||
destroyable,
|
destroyable,
|
||||||
};
|
};
|
||||||
<Node as OwnedAspect>::add_node_members(&node);
|
node.aspects.add(Owned);
|
||||||
node
|
node
|
||||||
}
|
}
|
||||||
pub fn add_to_scenegraph(self) -> Result<Arc<Node>> {
|
pub fn add_to_scenegraph(self) -> Result<Arc<Node>> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.get_client()
|
.get_client()
|
||||||
.ok_or_else(|| eyre!("Internal: Unable to get client"))?
|
.ok_or(ServerError::NoClient)?
|
||||||
.scenegraph
|
.scenegraph
|
||||||
.add_node(self))
|
.add_node(self))
|
||||||
}
|
}
|
||||||
pub fn add_to_scenegraph_owned(self) -> Result<OwnedNode> {
|
pub fn add_to_scenegraph_owned(self) -> Result<OwnedNode> {
|
||||||
Ok(OwnedNode(
|
Ok(OwnedNode(
|
||||||
self.get_client()
|
self.get_client()
|
||||||
.ok_or_else(|| eyre!("Internal: Unable to get client"))?
|
.ok_or(ServerError::NoClient)?
|
||||||
.scenegraph
|
.scenegraph
|
||||||
.add_node(self),
|
.add_node(self),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
pub fn enabled(&self) -> bool {
|
pub fn enabled(&self) -> bool {
|
||||||
self.enabled.load(Ordering::Relaxed)
|
let bool = {
|
||||||
&& if let Ok(spatial) = self.get_aspect::<Spatial>() {
|
let _span = debug_span!("load atomic bool").entered();
|
||||||
spatial
|
self.enabled.load(Ordering::Relaxed)
|
||||||
.global_transform()
|
};
|
||||||
.to_scale_rotation_translation()
|
bool && if let Ok(spatial) = {
|
||||||
.0
|
let _span = debug_span!("get spatial aspect").entered();
|
||||||
.length_squared() > 0.0
|
self.get_aspect::<Spatial>()
|
||||||
} else {
|
} {
|
||||||
true
|
let _span = debug_span!("check if scale is zero").entered();
|
||||||
}
|
spatial
|
||||||
|
.global_transform()
|
||||||
|
.to_scale_rotation_translation()
|
||||||
|
.0
|
||||||
|
.length_squared()
|
||||||
|
> 0.0
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub fn set_enabled(&self, enabled: bool) {
|
pub fn set_enabled(&self, enabled: bool) {
|
||||||
self.enabled.store(enabled, Ordering::Relaxed)
|
self.enabled.store(enabled, Ordering::Relaxed)
|
||||||
@@ -146,60 +168,58 @@ impl Node {
|
|||||||
// Ok(serialize(pid)?.into())
|
// Ok(serialize(pid)?.into())
|
||||||
// }
|
// }
|
||||||
|
|
||||||
pub fn add_local_signal(&self, id: u64, signal: Signal) {
|
pub fn add_aspect<A: AspectIdentifier>(&self, aspect: A) -> Arc<A> {
|
||||||
self.local_signals.lock().insert(id, signal);
|
|
||||||
}
|
|
||||||
pub fn add_local_method(&self, id: u64, method: Method) {
|
|
||||||
self.local_methods.lock().insert(id, method);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_aspect<A: Aspect>(&self, aspect: A) -> Arc<A> {
|
|
||||||
self.aspects.add(aspect)
|
self.aspects.add(aspect)
|
||||||
}
|
}
|
||||||
pub fn add_aspect_raw<A: Aspect>(&self, aspect: Arc<A>) {
|
pub fn add_aspect_raw<A: AspectIdentifier>(&self, aspect: Arc<A>) {
|
||||||
self.aspects.add_raw(aspect)
|
self.aspects.add_raw(aspect)
|
||||||
}
|
}
|
||||||
pub fn get_aspect<A: Aspect>(&self) -> Result<Arc<A>> {
|
pub fn get_aspect<A: AspectIdentifier>(&self) -> Result<Arc<A>> {
|
||||||
self.aspects.get()
|
self.aspects.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_local_signal(
|
pub fn send_local_signal(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
calling_client: Arc<Client>,
|
calling_client: Arc<Client>,
|
||||||
|
aspect_id: u64,
|
||||||
method: u64,
|
method: u64,
|
||||||
message: Message,
|
message: Message,
|
||||||
) -> Result<(), ScenegraphError> {
|
) -> Result<(), ScenegraphError> {
|
||||||
if let Ok(alias) = self.get_aspect::<Alias>() {
|
if let Ok(alias) = self.get_aspect::<Alias>() {
|
||||||
if !alias.info.server_signals.iter().any(|e| *e == method) {
|
if !alias.info.server_signals.iter().any(|e| *e == method) {
|
||||||
return Err(ScenegraphError::SignalNotFound);
|
return Err(ScenegraphError::MemberNotFound);
|
||||||
}
|
}
|
||||||
alias
|
alias
|
||||||
.original
|
.original
|
||||||
.upgrade()
|
.upgrade()
|
||||||
.ok_or(ScenegraphError::BrokenAlias)?
|
.ok_or(ScenegraphError::BrokenAlias)?
|
||||||
.send_local_signal(calling_client, method, message)
|
.send_local_signal(calling_client, aspect_id, method, message)
|
||||||
} else {
|
} else {
|
||||||
let signal = self
|
let aspect = self
|
||||||
.local_signals
|
.aspects
|
||||||
|
.0
|
||||||
.lock()
|
.lock()
|
||||||
.get(&method)
|
.get(&aspect_id)
|
||||||
.cloned()
|
.ok_or(ScenegraphError::AspectNotFound)?
|
||||||
.ok_or(ScenegraphError::SignalNotFound)?;
|
.clone();
|
||||||
signal(self, calling_client, message).map_err(|error| ScenegraphError::SignalError {
|
aspect
|
||||||
error: error.to_string(),
|
.run_signal(calling_client, self.clone(), method, message)
|
||||||
})
|
.map_err(|error| ScenegraphError::MemberError {
|
||||||
|
error: error.to_string(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn execute_local_method(
|
pub fn execute_local_method(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
calling_client: Arc<Client>,
|
calling_client: Arc<Client>,
|
||||||
|
aspect_id: u64,
|
||||||
method: u64,
|
method: u64,
|
||||||
message: Message,
|
message: Message,
|
||||||
response: MethodResponseSender,
|
response: MethodResponseSender,
|
||||||
) {
|
) {
|
||||||
if let Ok(alias) = self.get_aspect::<Alias>() {
|
if let Ok(alias) = self.get_aspect::<Alias>() {
|
||||||
if !alias.info.server_methods.iter().any(|e| *e == method) {
|
if !alias.info.server_methods.iter().any(|e| *e == method) {
|
||||||
response.send(Err(ScenegraphError::MethodNotFound));
|
response.send(Err(ScenegraphError::MemberNotFound));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let Some(alias) = alias.original.upgrade() else {
|
let Some(alias) = alias.original.upgrade() else {
|
||||||
@@ -208,6 +228,7 @@ impl Node {
|
|||||||
};
|
};
|
||||||
alias.execute_local_method(
|
alias.execute_local_method(
|
||||||
calling_client,
|
calling_client,
|
||||||
|
aspect_id,
|
||||||
method,
|
method,
|
||||||
Message {
|
Message {
|
||||||
data: message.data.clone(),
|
data: message.data.clone(),
|
||||||
@@ -216,14 +237,19 @@ impl Node {
|
|||||||
response,
|
response,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
let Some(method) = self.local_methods.lock().get(&method).cloned() else {
|
let Some(aspect) = self.aspects.0.lock().get(&aspect_id).cloned() else {
|
||||||
response.send(Err(ScenegraphError::MethodNotFound));
|
response.send(Err(ScenegraphError::AspectNotFound));
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
method(self, calling_client, message, response);
|
aspect.run_method(calling_client, self.clone(), method, message, response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn send_remote_signal(&self, method: u64, message: impl Into<Message>) -> Result<()> {
|
pub fn send_remote_signal(
|
||||||
|
&self,
|
||||||
|
aspect_id: u64,
|
||||||
|
method: u64,
|
||||||
|
message: impl Into<Message>,
|
||||||
|
) -> Result<()> {
|
||||||
let message = message.into();
|
let message = message.into();
|
||||||
self.aliases
|
self.aliases
|
||||||
.get_valid_contents()
|
.get_valid_contents()
|
||||||
@@ -233,6 +259,7 @@ impl Node {
|
|||||||
.for_each(|node| {
|
.for_each(|node| {
|
||||||
// Beware! file descriptors will not be sent to aliases!!!
|
// Beware! file descriptors will not be sent to aliases!!!
|
||||||
let _ = node.send_remote_signal(
|
let _ = node.send_remote_signal(
|
||||||
|
aspect_id,
|
||||||
method,
|
method,
|
||||||
Message {
|
Message {
|
||||||
data: message.data.clone(),
|
data: message.data.clone(),
|
||||||
@@ -241,12 +268,13 @@ impl Node {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
if let Some(handle) = self.message_sender_handle.as_ref() {
|
if let Some(handle) = self.message_sender_handle.as_ref() {
|
||||||
handle.signal(self.id, method, &message.data, message.fds)?;
|
handle.signal(self.id, aspect_id, method, &message.data, message.fds)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub async fn execute_remote_method_typed<S: Serialize, D: DeserializeOwned>(
|
pub async fn execute_remote_method_typed<S: Serialize, D: DeserializeOwned>(
|
||||||
&self,
|
&self,
|
||||||
|
aspect_id: u64,
|
||||||
method: u64,
|
method: u64,
|
||||||
input: S,
|
input: S,
|
||||||
fds: Vec<OwnedFd>,
|
fds: Vec<OwnedFd>,
|
||||||
@@ -254,13 +282,13 @@ impl Node {
|
|||||||
let message_sender_handle = self
|
let message_sender_handle = self
|
||||||
.message_sender_handle
|
.message_sender_handle
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or(eyre!("Messenger does not exist for this node"))?;
|
.ok_or(ServerError::NoMessenger)?;
|
||||||
|
|
||||||
let serialized = serialize(input)?;
|
let serialized = serialize(input)?;
|
||||||
let result = message_sender_handle
|
let result = message_sender_handle
|
||||||
.method(self.id, method, &serialized, fds)?
|
.method(self.id, aspect_id, method, &serialized, fds)?
|
||||||
.await
|
.await
|
||||||
.map_err(|e| eyre!(e))?;
|
.map_err(ServerError::RemoteMethodError)?;
|
||||||
|
|
||||||
let (message, fds) = result.into_components();
|
let (message, fds) = result.into_components();
|
||||||
let deserialized: D = deserialize(&message)?;
|
let deserialized: D = deserialize(&message)?;
|
||||||
@@ -271,57 +299,57 @@ impl Debug for Node {
|
|||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("Node")
|
f.debug_struct("Node")
|
||||||
.field("id", &self.id)
|
.field("id", &self.id)
|
||||||
.field("local_signals", &self.local_signals.lock().keys())
|
|
||||||
.field("local_methods", &self.local_methods.lock().keys())
|
|
||||||
.field("destroyable", &self.destroyable)
|
.field("destroyable", &self.destroyable)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl OwnedAspect for Node {
|
|
||||||
fn set_enabled(node: Arc<Node>, _calling_client: Arc<Client>, enabled: bool) -> Result<()> {
|
|
||||||
node.set_enabled(enabled);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn destroy(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
|
|
||||||
if node.destroyable {
|
|
||||||
node.destroy();
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Drop for Node {
|
impl Drop for Node {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
// Debug breakpoint
|
// Debug breakpoint
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait AspectIdentifier: Aspect {
|
||||||
|
const ID: u64;
|
||||||
|
}
|
||||||
pub trait Aspect: Any + Send + Sync + 'static {
|
pub trait Aspect: Any + Send + Sync + 'static {
|
||||||
const NAME: &'static str;
|
fn as_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync + 'static>;
|
||||||
|
fn run_signal(
|
||||||
|
&self,
|
||||||
|
calling_client: Arc<Client>,
|
||||||
|
node: Arc<Node>,
|
||||||
|
signal: u64,
|
||||||
|
message: Message,
|
||||||
|
) -> Result<(), stardust_xr::scenegraph::ScenegraphError>;
|
||||||
|
fn run_method(
|
||||||
|
&self,
|
||||||
|
calling_client: Arc<Client>,
|
||||||
|
node: Arc<Node>,
|
||||||
|
method: u64,
|
||||||
|
message: Message,
|
||||||
|
response: MethodResponseSender,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct Aspects(Mutex<FxHashMap<TypeId, Arc<dyn Any + Send + Sync + 'static>>>);
|
struct Aspects(QueuedMutex<FxHashMap<u64, Arc<dyn Aspect>>>);
|
||||||
|
|
||||||
impl Aspects {
|
impl Aspects {
|
||||||
fn add<A: Aspect>(&self, t: A) -> Arc<A> {
|
fn add<A: AspectIdentifier>(&self, t: A) -> Arc<A> {
|
||||||
let aspect = Arc::new(t);
|
let aspect = Arc::new(t);
|
||||||
self.add_raw(aspect.clone());
|
self.add_raw(aspect.clone());
|
||||||
aspect
|
aspect
|
||||||
}
|
}
|
||||||
fn add_raw<A: Aspect>(&self, aspect: Arc<A>) {
|
fn add_raw<A: AspectIdentifier>(&self, aspect: Arc<A>) {
|
||||||
self.0.lock().insert(Self::type_key::<A>(), aspect);
|
self.0.lock().insert(A::ID, aspect);
|
||||||
}
|
}
|
||||||
fn get<A: Aspect>(&self) -> Result<Arc<A>> {
|
fn get<A: Aspect + AspectIdentifier>(&self) -> Result<Arc<A>> {
|
||||||
self.0
|
self.0
|
||||||
.lock()
|
.read_now()
|
||||||
.get(&Self::type_key::<A>())
|
.get(&A::ID)
|
||||||
.and_then(|a| Arc::downcast(a.clone()).ok())
|
.cloned()
|
||||||
.ok_or(eyre!("Couldn't get aspect {}", A::NAME.to_lowercase()))
|
.map(|a| a.as_any())
|
||||||
}
|
.and_then(|a| Arc::downcast(a).ok())
|
||||||
|
.ok_or(ServerError::NoAspect(TypeId::of::<A>()))
|
||||||
fn type_key<A: 'static>() -> TypeId {
|
|
||||||
TypeId::of::<A>()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Drop for Aspects {
|
impl Drop for Aspects {
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
use super::spatial::Spatial;
|
use super::spatial::Spatial;
|
||||||
use super::Node;
|
use super::{Aspect, AspectIdentifier, Node};
|
||||||
|
use crate::bail;
|
||||||
use crate::core::client::Client;
|
use crate::core::client::Client;
|
||||||
use crate::core::client_state::ClientStateParsed;
|
use crate::core::client_state::ClientStateParsed;
|
||||||
|
use crate::core::error::Result;
|
||||||
use crate::core::registry::Registry;
|
use crate::core::registry::Registry;
|
||||||
use crate::nodes::spatial::SPATIAL_REF_ASPECT_ALIAS_INFO;
|
use crate::nodes::spatial::SPATIAL_REF_ASPECT_ALIAS_INFO;
|
||||||
use crate::session::connection_env;
|
use crate::session::connection_env;
|
||||||
use color_eyre::eyre::{bail, Result};
|
|
||||||
use glam::Mat4;
|
use glam::Mat4;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -23,14 +24,14 @@ pub struct Root {
|
|||||||
impl Root {
|
impl Root {
|
||||||
pub fn create(client: &Arc<Client>, transform: Mat4) -> Result<Arc<Self>> {
|
pub fn create(client: &Arc<Client>, transform: Mat4) -> Result<Arc<Self>> {
|
||||||
let node = Node::from_id(client, 0, false);
|
let node = Node::from_id(client, 0, false);
|
||||||
<Self as RootAspect>::add_node_members(&node);
|
|
||||||
let node = node.add_to_scenegraph()?;
|
let node = node.add_to_scenegraph()?;
|
||||||
let _ = Spatial::add_to(&node, None, transform, false);
|
let _ = Spatial::add_to(&node, None, transform, false);
|
||||||
|
let root_aspect = node.add_aspect(Root {
|
||||||
Ok(ROOT_REGISTRY.add(Root {
|
node: node.clone(),
|
||||||
node,
|
|
||||||
connect_instant: Instant::now(),
|
connect_instant: Instant::now(),
|
||||||
}))
|
});
|
||||||
|
ROOT_REGISTRY.add_raw(&root_aspect);
|
||||||
|
Ok(root_aspect)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_frame_events(delta: f64) {
|
pub fn send_frame_events(delta: f64) {
|
||||||
@@ -54,6 +55,12 @@ impl Root {
|
|||||||
Ok(root_client::save_state(&self.node).await?.0)
|
Ok(root_client::save_state(&self.node).await?.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl AspectIdentifier for Root {
|
||||||
|
impl_aspect_for_root_aspect_id! {}
|
||||||
|
}
|
||||||
|
impl Aspect for Root {
|
||||||
|
impl_aspect_for_root_aspect! {}
|
||||||
|
}
|
||||||
impl RootAspect for Root {
|
impl RootAspect for Root {
|
||||||
async fn get_state(_node: Arc<Node>, calling_client: Arc<Client>) -> Result<ClientState> {
|
async fn get_state(_node: Arc<Node>, calling_client: Arc<Client>) -> Result<ClientState> {
|
||||||
let Some(state) = calling_client.state.get() else {
|
let Some(state) = calling_client.state.get() else {
|
||||||
@@ -92,7 +99,7 @@ impl RootAspect for Root {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[doc = "Cleanly disconnect from the server"]
|
#[doc = "Cleanly disconnect from the server"]
|
||||||
fn disconnect(_node: Arc<Node>, calling_client: Arc<Client>) -> color_eyre::eyre::Result<()> {
|
fn disconnect(_node: Arc<Node>, calling_client: Arc<Client>) -> Result<()> {
|
||||||
calling_client.disconnect(Ok(()));
|
calling_client.disconnect(Ok(()));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,21 +3,22 @@ pub mod zone;
|
|||||||
use self::zone::Zone;
|
use self::zone::Zone;
|
||||||
use super::alias::Alias;
|
use super::alias::Alias;
|
||||||
use super::fields::{Field, FieldTrait};
|
use super::fields::{Field, FieldTrait};
|
||||||
use super::Aspect;
|
use super::{Aspect, AspectIdentifier};
|
||||||
|
use crate::bail;
|
||||||
|
use crate::bevy_plugin::StardustAabb3dExt;
|
||||||
use crate::core::client::Client;
|
use crate::core::client::Client;
|
||||||
|
use crate::core::error::Result;
|
||||||
use crate::core::registry::Registry;
|
use crate::core::registry::Registry;
|
||||||
use crate::create_interface;
|
|
||||||
use crate::nodes::{Node, OWNED_ASPECT_ALIAS_INFO};
|
use crate::nodes::{Node, OWNED_ASPECT_ALIAS_INFO};
|
||||||
use color_eyre::eyre::{eyre, OptionExt, Result};
|
use bevy::math::bounding::{Aabb3d, BoundingVolume};
|
||||||
use glam::{vec3a, Mat4, Quat, Vec3};
|
use color_eyre::eyre::OptionExt;
|
||||||
|
use glam::{vec3a, Mat4, Quat, Vec3, Vec3A};
|
||||||
use mint::Vector3;
|
use mint::Vector3;
|
||||||
use once_cell::sync::OnceCell;
|
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
use stereokit_rust::maths::Bounds;
|
|
||||||
|
|
||||||
stardust_xr_server_codegen::codegen_spatial_protocol!();
|
stardust_xr_server_codegen::codegen_spatial_protocol!();
|
||||||
impl Transform {
|
impl Transform {
|
||||||
@@ -38,6 +39,12 @@ impl Transform {
|
|||||||
Mat4::from_scale_rotation_translation(scale.into(), rotation.into(), position.into())
|
Mat4::from_scale_rotation_translation(scale.into(), rotation.into(), position.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl AspectIdentifier for Zone {
|
||||||
|
impl_aspect_for_zone_aspect_id! {}
|
||||||
|
}
|
||||||
|
impl Aspect for Zone {
|
||||||
|
impl_aspect_for_zone_aspect! {}
|
||||||
|
}
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
pub static ref EXPORTED_SPATIALS: Mutex<FxHashMap<u64, Arc<Node>>> = Mutex::new(FxHashMap::default());
|
pub static ref EXPORTED_SPATIALS: Mutex<FxHashMap<u64, Arc<Node>>> = Mutex::new(FxHashMap::default());
|
||||||
@@ -52,7 +59,7 @@ pub struct Spatial {
|
|||||||
transform: Mutex<Mat4>,
|
transform: Mutex<Mat4>,
|
||||||
zone: Mutex<Weak<Zone>>,
|
zone: Mutex<Weak<Zone>>,
|
||||||
children: Registry<Spatial>,
|
children: Registry<Spatial>,
|
||||||
pub bounding_box_calc: OnceCell<fn(&Node) -> Bounds>,
|
pub bounding_box_calc: Mutex<Aabb3d>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Spatial {
|
impl Spatial {
|
||||||
@@ -64,7 +71,7 @@ impl Spatial {
|
|||||||
transform: Mutex::new(transform),
|
transform: Mutex::new(transform),
|
||||||
zone: Mutex::new(Weak::new()),
|
zone: Mutex::new(Weak::new()),
|
||||||
children: Registry::new(),
|
children: Registry::new(),
|
||||||
bounding_box_calc: OnceCell::default(),
|
bounding_box_calc: Mutex::new(Aabb3d::new(Vec3A::ZERO, Vec3A::ZERO)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn add_to(
|
pub fn add_to(
|
||||||
@@ -74,16 +81,14 @@ impl Spatial {
|
|||||||
zoneable: bool,
|
zoneable: bool,
|
||||||
) -> Arc<Spatial> {
|
) -> Arc<Spatial> {
|
||||||
let spatial = Spatial::new(Arc::downgrade(node), parent.clone(), transform);
|
let spatial = Spatial::new(Arc::downgrade(node), parent.clone(), transform);
|
||||||
<Spatial as SpatialAspect>::add_node_members(node);
|
|
||||||
if zoneable {
|
if zoneable {
|
||||||
ZONEABLE_REGISTRY.add_raw(&spatial);
|
ZONEABLE_REGISTRY.add_raw(&spatial);
|
||||||
}
|
}
|
||||||
if let Some(parent) = parent {
|
if let Some(parent) = parent {
|
||||||
parent.children.add_raw(&spatial);
|
parent.children.add_raw(&spatial);
|
||||||
}
|
}
|
||||||
<Spatial as SpatialRefAspect>::add_node_members(node);
|
|
||||||
<Spatial as SpatialAspect>::add_node_members(node);
|
|
||||||
node.add_aspect_raw(spatial.clone());
|
node.add_aspect_raw(spatial.clone());
|
||||||
|
node.add_aspect(SpatialRef);
|
||||||
spatial
|
spatial
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,17 +103,12 @@ impl Spatial {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// the output bounds are probably way bigger than they need to be
|
// the output bounds are probably way bigger than they need to be
|
||||||
pub fn get_bounding_box(&self) -> Bounds {
|
pub fn get_bounding_box(&self) -> Aabb3d {
|
||||||
let Some(node) = self.node() else {
|
let mut bounds = *self.bounding_box_calc.lock();
|
||||||
return Bounds::default();
|
|
||||||
};
|
|
||||||
let mut bounds = self
|
|
||||||
.bounding_box_calc
|
|
||||||
.get()
|
|
||||||
.map(|b| (b)(&node))
|
|
||||||
.unwrap_or_default();
|
|
||||||
for child in self.children.get_valid_contents() {
|
for child in self.children.get_valid_contents() {
|
||||||
bounds.grown_box(child.get_bounding_box(), child.local_transform());
|
let b = child.get_bounding_box();
|
||||||
|
let t = child.local_transform();
|
||||||
|
bounds = bounds.grown_box(&b, Some(t));
|
||||||
}
|
}
|
||||||
bounds
|
bounds
|
||||||
}
|
}
|
||||||
@@ -205,7 +205,7 @@ impl Spatial {
|
|||||||
.map(|parent| self.is_ancestor_of((*parent).clone()))
|
.map(|parent| self.is_ancestor_of((*parent).clone()))
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
if is_ancestor {
|
if is_ancestor {
|
||||||
return Err(eyre!("Setting spatial parent would cause a loop"));
|
bail!("Setting spatial parent would cause a loop");
|
||||||
}
|
}
|
||||||
self.set_parent(parent);
|
self.set_parent(parent);
|
||||||
|
|
||||||
@@ -220,7 +220,7 @@ impl Spatial {
|
|||||||
.map(|parent| self.is_ancestor_of((*parent).clone()))
|
.map(|parent| self.is_ancestor_of((*parent).clone()))
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
if is_ancestor {
|
if is_ancestor {
|
||||||
return Err(eyre!("Setting spatial parent would cause a loop"));
|
bail!("Setting spatial parent would cause a loop");
|
||||||
}
|
}
|
||||||
|
|
||||||
self.set_local_transform(Spatial::space_to_space_matrix(
|
self.set_local_transform(Spatial::space_to_space_matrix(
|
||||||
@@ -241,8 +241,11 @@ impl Spatial {
|
|||||||
.unwrap_or(f32::MAX)
|
.unwrap_or(f32::MAX)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl AspectIdentifier for Spatial {
|
||||||
|
impl_aspect_for_spatial_aspect_id! {}
|
||||||
|
}
|
||||||
impl Aspect for Spatial {
|
impl Aspect for Spatial {
|
||||||
const NAME: &'static str = "Spatial";
|
impl_aspect_for_spatial_aspect! {}
|
||||||
}
|
}
|
||||||
impl SpatialRefAspect for Spatial {
|
impl SpatialRefAspect for Spatial {
|
||||||
async fn get_local_bounding_box(
|
async fn get_local_bounding_box(
|
||||||
@@ -253,8 +256,8 @@ impl SpatialRefAspect for Spatial {
|
|||||||
let bounds = this_spatial.get_bounding_box();
|
let bounds = this_spatial.get_bounding_box();
|
||||||
|
|
||||||
Ok(BoundingBox {
|
Ok(BoundingBox {
|
||||||
center: Vec3::from(bounds.center).into(),
|
center: Vec3::from(bounds.center()).into(),
|
||||||
size: Vec3::from(bounds.dimensions).into(),
|
size: Vec3::from(bounds.half_size() * 2.0).into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,18 +270,18 @@ impl SpatialRefAspect for Spatial {
|
|||||||
let relative_spatial = relative_to.get_aspect::<Spatial>()?;
|
let relative_spatial = relative_to.get_aspect::<Spatial>()?;
|
||||||
let center = Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial))
|
let center = Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial))
|
||||||
.transform_point3([0.0; 3].into());
|
.transform_point3([0.0; 3].into());
|
||||||
let mut bounds = Bounds {
|
let mut bounds = Aabb3d::new(center, Vec3A::ZERO);
|
||||||
center: center.into(),
|
bounds = bounds.grown_box(
|
||||||
dimensions: [0.0; 3].into(),
|
&this_spatial.get_bounding_box(),
|
||||||
};
|
Some(Spatial::space_to_space_matrix(
|
||||||
bounds.grown_box(
|
Some(&this_spatial),
|
||||||
this_spatial.get_bounding_box(),
|
Some(&relative_spatial),
|
||||||
Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial)),
|
)),
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(BoundingBox {
|
Ok(BoundingBox {
|
||||||
center: Vec3::from(bounds.center).into(),
|
center: Vec3::from(bounds.center()).into(),
|
||||||
size: Vec3::from(bounds.dimensions).into(),
|
size: Vec3::from(bounds.half_size() * 2.0).into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,6 +392,73 @@ impl Drop for Spatial {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct SpatialRef;
|
||||||
|
impl AspectIdentifier for SpatialRef {
|
||||||
|
impl_aspect_for_spatial_ref_aspect_id! {}
|
||||||
|
}
|
||||||
|
impl Aspect for SpatialRef {
|
||||||
|
impl_aspect_for_spatial_ref_aspect! {}
|
||||||
|
}
|
||||||
|
impl SpatialRefAspect for SpatialRef {
|
||||||
|
async fn get_local_bounding_box(
|
||||||
|
node: Arc<Node>,
|
||||||
|
_calling_client: Arc<Client>,
|
||||||
|
) -> Result<BoundingBox> {
|
||||||
|
let this_spatial = node.get_aspect::<Spatial>()?;
|
||||||
|
let bounds = this_spatial.get_bounding_box();
|
||||||
|
|
||||||
|
Ok(BoundingBox {
|
||||||
|
center: Vec3::from(bounds.center()).into(),
|
||||||
|
size: Vec3::from(bounds.half_size() * 2.0).into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_relative_bounding_box(
|
||||||
|
node: Arc<Node>,
|
||||||
|
_calling_client: Arc<Client>,
|
||||||
|
relative_to: Arc<Node>,
|
||||||
|
) -> Result<BoundingBox> {
|
||||||
|
let this_spatial = node.get_aspect::<Spatial>()?;
|
||||||
|
let relative_spatial = relative_to.get_aspect::<Spatial>()?;
|
||||||
|
let center = Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial))
|
||||||
|
.transform_point3([0.0; 3].into());
|
||||||
|
let mut bounds = Aabb3d::new(center, [0.0; 3]);
|
||||||
|
bounds = bounds.grown_box(
|
||||||
|
&this_spatial.get_bounding_box(),
|
||||||
|
Some(Spatial::space_to_space_matrix(
|
||||||
|
Some(&this_spatial),
|
||||||
|
Some(&relative_spatial),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(BoundingBox {
|
||||||
|
center: Vec3::from(bounds.center()).into(),
|
||||||
|
size: Vec3::from(bounds.half_size() * 2.0).into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_transform(
|
||||||
|
node: Arc<Node>,
|
||||||
|
_calling_client: Arc<Client>,
|
||||||
|
relative_to: Arc<Node>,
|
||||||
|
) -> Result<Transform> {
|
||||||
|
let this_spatial = node.get_aspect::<Spatial>()?;
|
||||||
|
let relative_spatial = relative_to.get_aspect::<Spatial>()?;
|
||||||
|
|
||||||
|
let (scale, rotation, position) = Spatial::space_to_space_matrix(
|
||||||
|
Some(this_spatial.as_ref()),
|
||||||
|
Some(relative_spatial.as_ref()),
|
||||||
|
)
|
||||||
|
.to_scale_rotation_translation();
|
||||||
|
|
||||||
|
Ok(Transform {
|
||||||
|
translation: Some(position.into()),
|
||||||
|
rotation: Some(rotation.into()),
|
||||||
|
scale: Some(scale.into()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_transform(transform: Transform, position: bool, rotation: bool, scale: bool) -> Mat4 {
|
pub fn parse_transform(transform: Transform, position: bool, rotation: bool, scale: bool) -> Mat4 {
|
||||||
let position = position
|
let position = position
|
||||||
.then_some(transform.translation)
|
.then_some(transform.translation)
|
||||||
@@ -406,8 +476,7 @@ pub fn parse_transform(transform: Transform, position: bool, rotation: bool, sca
|
|||||||
Mat4::from_scale_rotation_translation(scale.into(), rotation.into(), position.into())
|
Mat4::from_scale_rotation_translation(scale.into(), rotation.into(), position.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SpatialInterface;
|
impl InterfaceAspect for Interface {
|
||||||
impl InterfaceAspect for SpatialInterface {
|
|
||||||
fn create_spatial(
|
fn create_spatial(
|
||||||
_node: Arc<Node>,
|
_node: Arc<Node>,
|
||||||
calling_client: Arc<Client>,
|
calling_client: Arc<Client>,
|
||||||
@@ -445,7 +514,7 @@ impl InterfaceAspect for SpatialInterface {
|
|||||||
calling_client: Arc<Client>,
|
calling_client: Arc<Client>,
|
||||||
uid: u64,
|
uid: u64,
|
||||||
) -> Result<Arc<Node>> {
|
) -> Result<Arc<Node>> {
|
||||||
EXPORTED_SPATIALS
|
Ok(EXPORTED_SPATIALS
|
||||||
.lock()
|
.lock()
|
||||||
.get(&uid)
|
.get(&uid)
|
||||||
.map(|s| {
|
.map(|s| {
|
||||||
@@ -457,8 +526,6 @@ impl InterfaceAspect for SpatialInterface {
|
|||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
})
|
})
|
||||||
.ok_or_eyre("Couldn't find spatial with that ID")
|
.ok_or_eyre("Couldn't find spatial with that ID")?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
create_interface!(SpatialInterface);
|
|
||||||
|
|||||||
@@ -3,14 +3,13 @@ use super::{
|
|||||||
ZONEABLE_REGISTRY,
|
ZONEABLE_REGISTRY,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
core::{client::Client, registry::Registry},
|
core::{client::Client, error::Result, registry::Registry},
|
||||||
nodes::{
|
nodes::{
|
||||||
alias::{get_original, Alias, AliasList},
|
alias::{get_original, Alias, AliasList},
|
||||||
fields::{Field, FieldTrait},
|
fields::{Field, FieldTrait},
|
||||||
Aspect, Node,
|
Node,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use color_eyre::eyre::Result;
|
|
||||||
use glam::vec3a;
|
use glam::vec3a;
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
|
|
||||||
@@ -73,7 +72,6 @@ impl Zone {
|
|||||||
intersecting: AliasList::default(),
|
intersecting: AliasList::default(),
|
||||||
captured: AliasList::default(),
|
captured: AliasList::default(),
|
||||||
});
|
});
|
||||||
<Zone as ZoneAspect>::add_node_members(node);
|
|
||||||
node.add_aspect_raw(zone.clone());
|
node.add_aspect_raw(zone.clone());
|
||||||
zone
|
zone
|
||||||
}
|
}
|
||||||
@@ -124,9 +122,6 @@ impl Zone {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Aspect for Zone {
|
|
||||||
const NAME: &'static str = "Zone";
|
|
||||||
}
|
|
||||||
impl ZoneAspect for Zone {
|
impl ZoneAspect for Zone {
|
||||||
fn update(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
|
fn update(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
|
||||||
let zone = node.get_aspect::<Zone>()?;
|
let zone = node.get_aspect::<Zone>()?;
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ use glam::{vec3, Mat4};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use stardust_xr::values::Datamap;
|
use stardust_xr::values::Datamap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use stereokit_rust::system::Input;
|
|
||||||
|
|
||||||
#[derive(Default, Deserialize, Serialize)]
|
#[derive(Default, Deserialize, Serialize)]
|
||||||
pub struct EyeDatamap {
|
pub struct EyeDatamap {
|
||||||
@@ -49,58 +48,59 @@ impl EyePointer {
|
|||||||
pointer,
|
pointer,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// TODO: implement eyetracking in bevy_mod_openxr, then reimplement with that
|
||||||
pub fn update(&self) {
|
pub fn update(&self) {
|
||||||
let ray = Input::get_eyes();
|
// let ray = Input::get_eyes();
|
||||||
self.spatial
|
// self.spatial
|
||||||
.set_local_transform(Mat4::from_rotation_translation(
|
// .set_local_transform(Mat4::from_rotation_translation(
|
||||||
ray.orientation.into(),
|
// ray.orientation.into(),
|
||||||
ray.position.into(),
|
// ray.position.into(),
|
||||||
));
|
// ));
|
||||||
{
|
// {
|
||||||
// Set pointer input datamap
|
// // Set pointer input datamap
|
||||||
*self.pointer.datamap.lock() = Datamap::from_typed(EyeDatamap { eye: 2 }).unwrap();
|
// *self.pointer.datamap.lock() = Datamap::from_typed(EyeDatamap { eye: 2 }).unwrap();
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// send input to all the input handlers that are the closest to the ray as possible
|
// // send input to all the input handlers that are the closest to the ray as possible
|
||||||
let rx = INPUT_HANDLER_REGISTRY
|
// let rx = INPUT_HANDLER_REGISTRY
|
||||||
.get_valid_contents()
|
// .get_valid_contents()
|
||||||
.into_iter()
|
// .into_iter()
|
||||||
// filter out all the disabled handlers
|
// // filter out all the disabled handlers
|
||||||
.filter(|handler| {
|
// .filter(|handler| {
|
||||||
let Some(node) = handler.spatial.node() else {
|
// let Some(node) = handler.spatial.node() else {
|
||||||
return false;
|
// return false;
|
||||||
};
|
// };
|
||||||
node.enabled()
|
// node.enabled()
|
||||||
})
|
// })
|
||||||
// ray march to all the enabled handlers' fields
|
// // ray march to all the enabled handlers' fields
|
||||||
.map(|handler| {
|
// .map(|handler| {
|
||||||
let result = handler.field.ray_march(Ray {
|
// let result = handler.field.ray_march(Ray {
|
||||||
origin: vec3(0.0, 0.0, 0.0),
|
// origin: vec3(0.0, 0.0, 0.0),
|
||||||
direction: vec3(0.0, 0.0, -1.0),
|
// direction: vec3(0.0, 0.0, -1.0),
|
||||||
space: self.spatial.clone(),
|
// space: self.spatial.clone(),
|
||||||
});
|
// });
|
||||||
(vec![handler], result)
|
// (vec![handler], result)
|
||||||
})
|
// })
|
||||||
// make sure the field isn't at the pointer origin and that it's being hit
|
// // make sure the field isn't at the pointer origin and that it's being hit
|
||||||
.filter(|(_, result)| result.deepest_point_distance > 0.01 && result.min_distance < 0.0)
|
// .filter(|(_, result)| result.deepest_point_distance > 0.01 && result.min_distance < 0.0)
|
||||||
// .inspect(|(_, result)| {
|
// // .inspect(|(_, result)| {
|
||||||
// dbg!(result);
|
// // dbg!(result);
|
||||||
// })
|
// // })
|
||||||
// now collect all handlers that are same distance if they're the closest
|
// // now collect all handlers that are same distance if they're the closest
|
||||||
.reduce(|(mut handlers_a, result_a), (handlers_b, result_b)| {
|
// .reduce(|(mut handlers_a, result_a), (handlers_b, result_b)| {
|
||||||
if (result_a.deepest_point_distance - result_b.deepest_point_distance).abs() < 0.001
|
// if (result_a.deepest_point_distance - result_b.deepest_point_distance).abs() < 0.001
|
||||||
{
|
// {
|
||||||
// distance is basically the same
|
// // distance is basically the same
|
||||||
handlers_a.extend(handlers_b);
|
// handlers_a.extend(handlers_b);
|
||||||
(handlers_a, result_a)
|
// (handlers_a, result_a)
|
||||||
} else if result_a.deepest_point_distance < result_b.deepest_point_distance {
|
// } else if result_a.deepest_point_distance < result_b.deepest_point_distance {
|
||||||
(handlers_a, result_a)
|
// (handlers_a, result_a)
|
||||||
} else {
|
// } else {
|
||||||
(handlers_b, result_b)
|
// (handlers_b, result_b)
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
.map(|(rx, _)| rx)
|
// .map(|(rx, _)| rx)
|
||||||
.unwrap_or_default();
|
// .unwrap_or_default();
|
||||||
self.pointer.set_handler_order(rx.iter());
|
// self.pointer.set_handler_order(rx.iter());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
|
use super::{get_sorted_handlers, CaptureManager, DistanceCalculator};
|
||||||
use crate::{
|
use crate::{
|
||||||
core::client::INTERNAL_CLIENT,
|
core::client::INTERNAL_CLIENT,
|
||||||
nodes::{
|
nodes::{
|
||||||
data::{
|
fields::{Field, FieldTrait, Ray, EXPORTED_FIELDS},
|
||||||
mask_matches, pulse_receiver_client, PulseSender, KEYMAPS, PULSE_RECEIVER_REGISTRY,
|
input::{InputDataType, InputMethod, Pointer},
|
||||||
},
|
items::panel::KEYMAPS,
|
||||||
fields::{FieldTrait, Ray},
|
|
||||||
input::{InputDataType, InputHandler, InputMethod, Pointer, INPUT_HANDLER_REGISTRY},
|
|
||||||
spatial::Spatial,
|
spatial::Spatial,
|
||||||
Node, OwnedNode,
|
Node, OwnedNode,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use bevy::input::keyboard::Key;
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use glam::{vec3, Mat4, Vec3};
|
use glam::{vec3, Mat4, Vec3};
|
||||||
use mint::Vector2;
|
use mint::Vector2;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use slotmap::{DefaultKey, Key as SlotKey};
|
use slotmap::{DefaultKey, Key as SlotKey};
|
||||||
use stardust_xr::values::Datamap;
|
use stardust_xr::{
|
||||||
|
schemas::dbus::{interfaces::FieldRefProxy, object_registry::ObjectRegistry},
|
||||||
|
values::Datamap,
|
||||||
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use stereokit_rust::system::{Input, Key};
|
use zbus::Connection;
|
||||||
use xkbcommon_rs::{xkb_keymap::CompileFlags, Context, Keymap, KeymapFormat};
|
|
||||||
|
|
||||||
use super::{get_sorted_handlers, CaptureManager, DistanceCalculator};
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
struct MouseEvent {
|
struct MouseEvent {
|
||||||
@@ -46,12 +46,14 @@ impl Default for MouseEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
#[zbus::proxy(
|
||||||
pub struct KeyboardEvent {
|
interface = "org.stardustxr.XKBv1",
|
||||||
pub keyboard: (),
|
default_service = "org.stardustxr.XKBv1"
|
||||||
pub xkbv1: (),
|
)]
|
||||||
pub keymap_id: u64,
|
trait KeyboardHandler {
|
||||||
pub keys: Vec<i32>,
|
async fn keymap(&self, keymap_id: u64) -> zbus::Result<()>;
|
||||||
|
async fn key_state(&self, key: u32, pressed: bool) -> zbus::Result<()>;
|
||||||
|
async fn reset(&self) -> zbus::Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
@@ -62,8 +64,6 @@ pub struct MousePointer {
|
|||||||
pointer: Arc<InputMethod>,
|
pointer: Arc<InputMethod>,
|
||||||
capture_manager: CaptureManager,
|
capture_manager: CaptureManager,
|
||||||
mouse_datamap: MouseEvent,
|
mouse_datamap: MouseEvent,
|
||||||
keyboard_datamap: KeyboardEvent,
|
|
||||||
keyboard_sender: Arc<PulseSender>,
|
|
||||||
}
|
}
|
||||||
impl MousePointer {
|
impl MousePointer {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> Result<Self> {
|
||||||
@@ -75,19 +75,13 @@ impl MousePointer {
|
|||||||
Datamap::from_typed(MouseEvent::default())?,
|
Datamap::from_typed(MouseEvent::default())?,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let context = Context::new(0).unwrap();
|
// let context = Context::new(0).unwrap();
|
||||||
let keymap = KEYMAPS.lock().insert(
|
// let keymap = KEYMAPS.lock().insert(
|
||||||
Keymap::new_from_names(context, None, CompileFlags::NO_FLAGS)
|
// Keymap::new_from_names(context, None, CompileFlags::NO_FLAGS)
|
||||||
.unwrap()
|
// .unwrap()
|
||||||
.get_as_string(KeymapFormat::TextV1)
|
// .get_as_string(KeymapFormat::TextV1)
|
||||||
.unwrap(),
|
// .unwrap(),
|
||||||
);
|
// );
|
||||||
|
|
||||||
let keyboard_sender = PulseSender::add_to(
|
|
||||||
&node.0,
|
|
||||||
Datamap::from_typed(KeyboardEvent::default()).unwrap(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
Ok(MousePointer {
|
Ok(MousePointer {
|
||||||
node,
|
node,
|
||||||
@@ -95,220 +89,255 @@ impl MousePointer {
|
|||||||
pointer,
|
pointer,
|
||||||
capture_manager: CaptureManager::default(),
|
capture_manager: CaptureManager::default(),
|
||||||
mouse_datamap: Default::default(),
|
mouse_datamap: Default::default(),
|
||||||
keyboard_datamap: KeyboardEvent {
|
keymap: DefaultKey::default(),
|
||||||
keyboard: (),
|
|
||||||
xkbv1: (),
|
|
||||||
keymap_id: keymap.data().as_ffi(),
|
|
||||||
keys: vec![],
|
|
||||||
},
|
|
||||||
keymap,
|
|
||||||
keyboard_sender,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn update(&mut self) {
|
// pub fn update(&mut self, dbus_connection: &Connection, object_registry: &ObjectRegistry) {
|
||||||
let mouse = Input::get_mouse();
|
// let mouse = Input::get_mouse();
|
||||||
|
//
|
||||||
|
// let ray = mouse.get_ray();
|
||||||
|
// self.spatial.set_local_transform(
|
||||||
|
// Mat4::look_to_rh(
|
||||||
|
// Vec3::from(ray.position),
|
||||||
|
// Vec3::from(ray.direction),
|
||||||
|
// vec3(0.0, 1.0, 0.0),
|
||||||
|
// )
|
||||||
|
// .inverse(),
|
||||||
|
// );
|
||||||
|
// {
|
||||||
|
// // Set pointer input datamap
|
||||||
|
// self.mouse_datamap = MouseEvent {
|
||||||
|
// select: Input::key(Key::MouseLeft).is_active() as u32 as f32,
|
||||||
|
// middle: Input::key(Key::MouseCenter).is_active() as u32 as f32,
|
||||||
|
// context: Input::key(Key::MouseRight).is_active() as u32 as f32,
|
||||||
|
// grab: Input::key(Key::MouseBack).is_active() as u32 as f32,
|
||||||
|
// scroll_continuous: [0.0, mouse.scroll_change / 120.0].into(),
|
||||||
|
// scroll_discrete: [0.0, mouse.scroll_change / 120.0].into(),
|
||||||
|
// raw_input_events: vec![],
|
||||||
|
// };
|
||||||
|
// *self.pointer.datamap.lock() = Datamap::from_typed(&self.mouse_datamap).unwrap();
|
||||||
|
// }
|
||||||
|
// self.target_pointer_input();
|
||||||
|
// self.send_keyboard_input(dbus_connection, object_registry);
|
||||||
|
|
||||||
let ray = mouse.get_ray();
|
|
||||||
self.spatial.set_local_transform(
|
|
||||||
Mat4::look_to_rh(
|
|
||||||
Vec3::from(ray.position),
|
|
||||||
Vec3::from(ray.direction),
|
|
||||||
vec3(0.0, 1.0, 0.0),
|
|
||||||
)
|
|
||||||
.inverse(),
|
|
||||||
);
|
|
||||||
{
|
|
||||||
// Set pointer input datamap
|
|
||||||
self.mouse_datamap = MouseEvent {
|
|
||||||
select: Input::key(Key::MouseLeft).is_active() as u32 as f32,
|
|
||||||
middle: Input::key(Key::MouseCenter).is_active() as u32 as f32,
|
|
||||||
context: Input::key(Key::MouseRight).is_active() as u32 as f32,
|
|
||||||
grab: Input::key(Key::MouseBack).is_active() as u32 as f32,
|
|
||||||
scroll_continuous: [0.0, mouse.scroll_change / 120.0].into(),
|
|
||||||
scroll_discrete: [0.0, mouse.scroll_change / 120.0].into(),
|
|
||||||
raw_input_events: vec![],
|
|
||||||
};
|
|
||||||
*self.pointer.datamap.lock() = Datamap::from_typed(&self.mouse_datamap).unwrap();
|
|
||||||
}
|
|
||||||
self.target_pointer_input();
|
|
||||||
self.send_keyboard_input();
|
|
||||||
}
|
|
||||||
fn target_pointer_input(&mut self) {
|
fn target_pointer_input(&mut self) {
|
||||||
let distance_calculator: DistanceCalculator = |space, data, field| {
|
// let distance_calculator: DistanceCalculator = |space, data, field| {
|
||||||
let result = field.ray_march(Ray {
|
// let result = field.ray_march(Ray {
|
||||||
origin: vec3(0.0, 0.0, 0.0),
|
// origin: vec3(0.0, 0.0, 0.0),
|
||||||
direction: vec3(0.0, 0.0, -1.0),
|
// direction: vec3(0.0, 0.0, -1.0),
|
||||||
space: space.clone(),
|
// space: space.clone(),
|
||||||
});
|
// });
|
||||||
let valid =
|
// let valid =
|
||||||
result.deepest_point_distance > 0.0 && result.min_distance.is_sign_negative();
|
// result.deepest_point_distance > 0.0 && result.min_distance.is_sign_negative();
|
||||||
valid.then_some(result.deepest_point_distance)
|
// valid.then_some(result.deepest_point_distance)
|
||||||
};
|
// };
|
||||||
|
//
|
||||||
self.capture_manager.update_capture(&self.pointer);
|
// self.capture_manager.update_capture(&self.pointer);
|
||||||
self.capture_manager
|
// self.capture_manager
|
||||||
.set_new_capture(&self.pointer, distance_calculator);
|
// .set_new_capture(&self.pointer, distance_calculator);
|
||||||
self.capture_manager.apply_capture(&self.pointer);
|
// self.capture_manager.apply_capture(&self.pointer);
|
||||||
|
//
|
||||||
if self.capture_manager.capture.is_some() {
|
// if self.capture_manager.capture.is_some() {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
let sorted_handlers = get_sorted_handlers(&self.pointer, distance_calculator);
|
// let sorted_handlers = get_sorted_handlers(&self.pointer, distance_calculator);
|
||||||
self.pointer.set_handler_order(sorted_handlers.iter());
|
// self.pointer.set_handler_order(sorted_handlers.iter());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_keyboard_input(&mut self) {
|
pub fn send_keyboard_input(
|
||||||
let rx = PULSE_RECEIVER_REGISTRY
|
&mut self,
|
||||||
.get_valid_contents()
|
dbus_connection: &Connection,
|
||||||
.into_iter()
|
object_registry: &ObjectRegistry,
|
||||||
.filter(|rx| mask_matches(&rx.mask, &self.keyboard_sender.mask))
|
) {
|
||||||
.map(|rx| {
|
// let keyboard_handlers = object_registry.get_objects("org.stardustxr.XKBv1");
|
||||||
let result = rx.field.ray_march(Ray {
|
//
|
||||||
origin: vec3(0.0, 0.0, 0.0),
|
// // Spawn async task to handle keyboard input
|
||||||
direction: vec3(0.0, 0.0, -1.0),
|
// tokio::spawn({
|
||||||
space: self.spatial.clone(),
|
// let keyboard_handlers = keyboard_handlers.clone();
|
||||||
});
|
// let spatial = self.spatial.clone();
|
||||||
(rx, result)
|
// let keymap_id = self.keymap.data().as_ffi();
|
||||||
})
|
// let dbus_connection = dbus_connection.clone();
|
||||||
.filter(|(_rx, result)| {
|
//
|
||||||
result.deepest_point_distance > 0.0 && result.min_distance < 0.05
|
// async move {
|
||||||
})
|
// let mut closest_handler = None;
|
||||||
.reduce(|(rx_a, result_a), (rx_b, result_b)| {
|
// let mut closest_distance = f32::MAX;
|
||||||
if result_a.deepest_point_distance < result_b.deepest_point_distance {
|
//
|
||||||
(rx_a, result_a)
|
// let mut join_set = JoinSet::new();
|
||||||
} else {
|
// for handler in &keyboard_handlers {
|
||||||
(rx_b, result_b)
|
// let handler = handler.clone();
|
||||||
}
|
// let dbus_connection = dbus_connection.clone();
|
||||||
})
|
// join_set.spawn(async move {
|
||||||
.map(|(rx, _)| rx);
|
// timeout(Duration::from_millis(1), async {
|
||||||
|
// let field_ref = handler
|
||||||
if let Some(rx) = rx {
|
// .to_typed_proxy::<FieldRefProxy>(&dbus_connection)
|
||||||
let keys = (8_u32..254)
|
// .await
|
||||||
.map(|i| unsafe { std::mem::transmute(i) })
|
// .ok()?;
|
||||||
.filter_map(|k| Some((map_key(k)?, Input::key(k))))
|
// let uid = field_ref.uid().await.ok()?;
|
||||||
.filter_map(|(i, k)| {
|
// Some((handler, uid))
|
||||||
if k.is_just_active() {
|
// })
|
||||||
Some(i as i32)
|
// .await
|
||||||
} else if k.is_just_inactive() {
|
// .ok()
|
||||||
Some(-(i as i32))
|
// .flatten()
|
||||||
} else {
|
// });
|
||||||
None
|
// }
|
||||||
}
|
// while let Some(Ok(Some((handler, field_ref_id)))) = join_set.join_next().await {
|
||||||
})
|
// let exported_fields = EXPORTED_FIELDS.lock();
|
||||||
.collect();
|
// let Some(field_ref_node) = exported_fields.get(&field_ref_id) else {
|
||||||
|
// println!("didn't find a thing :(");
|
||||||
self.keyboard_datamap.keys = keys;
|
// continue;
|
||||||
if !self.keyboard_datamap.keys.is_empty() {
|
// };
|
||||||
pulse_receiver_client::data(
|
// // println!("still sendin stuff :)");
|
||||||
&rx.node.upgrade().unwrap(),
|
// let Ok(field_ref) = field_ref_node.get_aspect::<Field>() else {
|
||||||
&self.node.0,
|
// continue;
|
||||||
&Datamap::from_typed(&self.keyboard_datamap).unwrap(),
|
// };
|
||||||
)
|
// drop(exported_fields);
|
||||||
.unwrap();
|
//
|
||||||
}
|
// let result = field_ref.ray_march(Ray {
|
||||||
}
|
// origin: vec3(0.0, 0.0, 0.0),
|
||||||
|
// direction: vec3(0.0, 0.0, -1.0),
|
||||||
|
// space: spatial.clone(),
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// if result.deepest_point_distance > 0.0
|
||||||
|
// && result.min_distance < 0.05
|
||||||
|
// && result.deepest_point_distance < closest_distance
|
||||||
|
// {
|
||||||
|
// closest_distance = result.deepest_point_distance;
|
||||||
|
// closest_handler = Some(handler);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// let Some(handler) = closest_handler else {
|
||||||
|
// return;
|
||||||
|
// };
|
||||||
|
// let Ok(keyboard_handler) = handler
|
||||||
|
// .to_typed_proxy::<KeyboardHandlerProxy>(&dbus_connection)
|
||||||
|
// .await
|
||||||
|
// else {
|
||||||
|
// return;
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// // Register keymap first
|
||||||
|
// let _ = keyboard_handler.keymap(keymap_id).await;
|
||||||
|
//
|
||||||
|
// // Send key states
|
||||||
|
// for i in 8_u32..254 {
|
||||||
|
// let key = unsafe { std::mem::transmute::<u32, stereokit_rust::system::Key>(i) };
|
||||||
|
// let Some(mapped_key) = map_key(key) else {
|
||||||
|
// continue;
|
||||||
|
// };
|
||||||
|
// let input_state = Input::key(key);
|
||||||
|
// if input_state.is_just_active() {
|
||||||
|
// let _ = keyboard_handler.key_state(mapped_key, true).await;
|
||||||
|
// } else if input_state.is_just_inactive() {
|
||||||
|
// let _ = keyboard_handler.key_state(mapped_key, false).await;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_key(key: Key) -> Option<u32> {
|
fn map_key(key: ()) -> Option<u32> {
|
||||||
match key {
|
None
|
||||||
Key::Backspace => Some(input_event_codes::KEY_BACKSPACE!()),
|
// match key {
|
||||||
Key::Tab => Some(input_event_codes::KEY_TAB!()),
|
// Key::Backspace => Some(input_event_codes::KEY_BACKSPACE!()),
|
||||||
Key::Return => Some(input_event_codes::KEY_ENTER!()),
|
// Key::Tab => Some(input_event_codes::KEY_TAB!()),
|
||||||
Key::Shift => Some(input_event_codes::KEY_LEFTSHIFT!()),
|
// Key::Enter => Some(input_event_codes::KEY_ENTER!()),
|
||||||
Key::Ctrl => Some(input_event_codes::KEY_LEFTCTRL!()),
|
// Key::Shift => Some(input_event_codes::KEY_LEFTSHIFT!()),
|
||||||
Key::Alt => Some(input_event_codes::KEY_LEFTALT!()),
|
// Key::Control => Some(input_event_codes::KEY_LEFTCTRL!()),
|
||||||
Key::CapsLock => Some(input_event_codes::KEY_CAPSLOCK!()),
|
// Key::Alt => Some(input_event_codes::KEY_LEFTALT!()),
|
||||||
Key::Esc => Some(input_event_codes::KEY_ESC!()),
|
// Key::CapsLock => Some(input_event_codes::KEY_CAPSLOCK!()),
|
||||||
Key::Space => Some(input_event_codes::KEY_SPACE!()),
|
// Key::Escape => Some(input_event_codes::KEY_ESC!()),
|
||||||
Key::End => Some(input_event_codes::KEY_END!()),
|
// Key::Space => Some(input_event_codes::KEY_SPACE!()),
|
||||||
Key::Home => Some(input_event_codes::KEY_HOME!()),
|
// Key::End => Some(input_event_codes::KEY_END!()),
|
||||||
Key::Left => Some(input_event_codes::KEY_LEFT!()),
|
// Key::Home => Some(input_event_codes::KEY_HOME!()),
|
||||||
Key::Right => Some(input_event_codes::KEY_RIGHT!()),
|
// Key::ArrowLeft => Some(input_event_codes::KEY_LEFT!()),
|
||||||
Key::Up => Some(input_event_codes::KEY_UP!()),
|
// Key::ArrowRight => Some(input_event_codes::KEY_RIGHT!()),
|
||||||
Key::Down => Some(input_event_codes::KEY_DOWN!()),
|
// Key::ArrowUp => Some(input_event_codes::KEY_UP!()),
|
||||||
Key::PageUp => Some(input_event_codes::KEY_PAGEUP!()),
|
// Key::ArrowDown => Some(input_event_codes::KEY_DOWN!()),
|
||||||
Key::PageDown => Some(input_event_codes::KEY_PAGEDOWN!()),
|
// Key::PageUp => Some(input_event_codes::KEY_PAGEUP!()),
|
||||||
Key::PrintScreen => Some(input_event_codes::KEY_PRINT!()),
|
// Key::PageDown => Some(input_event_codes::KEY_PAGEDOWN!()),
|
||||||
Key::KeyInsert => Some(input_event_codes::KEY_INSERT!()),
|
// Key::PrintScreen => Some(input_event_codes::KEY_PRINT!()),
|
||||||
Key::Del => Some(input_event_codes::KEY_DELETE!()),
|
// Key::Insert => Some(input_event_codes::KEY_INSERT!()),
|
||||||
Key::Key0 => Some(input_event_codes::KEY_0!()),
|
// Key::Delete => Some(input_event_codes::KEY_DELETE!()),
|
||||||
Key::Key1 => Some(input_event_codes::KEY_1!()),
|
// Key::Num => Some(input_event_codes::KEY_0!()),
|
||||||
Key::Key2 => Some(input_event_codes::KEY_2!()),
|
// Key::Key1 => Some(input_event_codes::KEY_1!()),
|
||||||
Key::Key3 => Some(input_event_codes::KEY_3!()),
|
// Key::Key2 => Some(input_event_codes::KEY_2!()),
|
||||||
Key::Key4 => Some(input_event_codes::KEY_4!()),
|
// Key::Key3 => Some(input_event_codes::KEY_3!()),
|
||||||
Key::Key5 => Some(input_event_codes::KEY_5!()),
|
// Key::Key4 => Some(input_event_codes::KEY_4!()),
|
||||||
Key::Key6 => Some(input_event_codes::KEY_6!()),
|
// Key::Key5 => Some(input_event_codes::KEY_5!()),
|
||||||
Key::Key7 => Some(input_event_codes::KEY_7!()),
|
// Key::Key6 => Some(input_event_codes::KEY_6!()),
|
||||||
Key::Key8 => Some(input_event_codes::KEY_8!()),
|
// Key::Key7 => Some(input_event_codes::KEY_7!()),
|
||||||
Key::Key9 => Some(input_event_codes::KEY_9!()),
|
// Key::Key8 => Some(input_event_codes::KEY_8!()),
|
||||||
Key::A => Some(input_event_codes::KEY_A!()),
|
// Key::Key9 => Some(input_event_codes::KEY_9!()),
|
||||||
Key::B => Some(input_event_codes::KEY_B!()),
|
// Key::A => Some(input_event_codes::KEY_A!()),
|
||||||
Key::C => Some(input_event_codes::KEY_C!()),
|
// Key::B => Some(input_event_codes::KEY_B!()),
|
||||||
Key::D => Some(input_event_codes::KEY_D!()),
|
// Key::C => Some(input_event_codes::KEY_C!()),
|
||||||
Key::E => Some(input_event_codes::KEY_E!()),
|
// Key::D => Some(input_event_codes::KEY_D!()),
|
||||||
Key::F => Some(input_event_codes::KEY_F!()),
|
// Key::E => Some(input_event_codes::KEY_E!()),
|
||||||
Key::G => Some(input_event_codes::KEY_G!()),
|
// Key::F => Some(input_event_codes::KEY_F!()),
|
||||||
Key::H => Some(input_event_codes::KEY_H!()),
|
// Key::G => Some(input_event_codes::KEY_G!()),
|
||||||
Key::I => Some(input_event_codes::KEY_I!()),
|
// Key::H => Some(input_event_codes::KEY_H!()),
|
||||||
Key::J => Some(input_event_codes::KEY_J!()),
|
// Key::I => Some(input_event_codes::KEY_I!()),
|
||||||
Key::K => Some(input_event_codes::KEY_K!()),
|
// Key::J => Some(input_event_codes::KEY_J!()),
|
||||||
Key::L => Some(input_event_codes::KEY_L!()),
|
// Key::K => Some(input_event_codes::KEY_K!()),
|
||||||
Key::M => Some(input_event_codes::KEY_M!()),
|
// Key::L => Some(input_event_codes::KEY_L!()),
|
||||||
Key::N => Some(input_event_codes::KEY_N!()),
|
// Key::M => Some(input_event_codes::KEY_M!()),
|
||||||
Key::O => Some(input_event_codes::KEY_O!()),
|
// Key::N => Some(input_event_codes::KEY_N!()),
|
||||||
Key::P => Some(input_event_codes::KEY_P!()),
|
// Key::O => Some(input_event_codes::KEY_O!()),
|
||||||
Key::Q => Some(input_event_codes::KEY_Q!()),
|
// Key::P => Some(input_event_codes::KEY_P!()),
|
||||||
Key::R => Some(input_event_codes::KEY_R!()),
|
// Key::Q => Some(input_event_codes::KEY_Q!()),
|
||||||
Key::S => Some(input_event_codes::KEY_S!()),
|
// Key::R => Some(input_event_codes::KEY_R!()),
|
||||||
Key::T => Some(input_event_codes::KEY_T!()),
|
// Key::S => Some(input_event_codes::KEY_S!()),
|
||||||
Key::U => Some(input_event_codes::KEY_U!()),
|
// Key::T => Some(input_event_codes::KEY_T!()),
|
||||||
Key::V => Some(input_event_codes::KEY_V!()),
|
// Key::U => Some(input_event_codes::KEY_U!()),
|
||||||
Key::W => Some(input_event_codes::KEY_W!()),
|
// Key::V => Some(input_event_codes::KEY_V!()),
|
||||||
Key::X => Some(input_event_codes::KEY_X!()),
|
// Key::W => Some(input_event_codes::KEY_W!()),
|
||||||
Key::Y => Some(input_event_codes::KEY_Y!()),
|
// Key::X => Some(input_event_codes::KEY_X!()),
|
||||||
Key::Z => Some(input_event_codes::KEY_Z!()),
|
// Key::Y => Some(input_event_codes::KEY_Y!()),
|
||||||
Key::Numpad0 => Some(input_event_codes::KEY_NUMERIC_0!()),
|
// Key::Z => Some(input_event_codes::KEY_Z!()),
|
||||||
Key::Numpad1 => Some(input_event_codes::KEY_NUMERIC_1!()),
|
// Key::Numpad0 => Some(input_event_codes::KEY_NUMERIC_0!()),
|
||||||
Key::Numpad2 => Some(input_event_codes::KEY_NUMERIC_2!()),
|
// Key::Numpad1 => Some(input_event_codes::KEY_NUMERIC_1!()),
|
||||||
Key::Numpad3 => Some(input_event_codes::KEY_NUMERIC_3!()),
|
// Key::Numpad2 => Some(input_event_codes::KEY_NUMERIC_2!()),
|
||||||
Key::Numpad4 => Some(input_event_codes::KEY_NUMERIC_4!()),
|
// Key::Numpad3 => Some(input_event_codes::KEY_NUMERIC_3!()),
|
||||||
Key::Numpad5 => Some(input_event_codes::KEY_NUMERIC_5!()),
|
// Key::Numpad4 => Some(input_event_codes::KEY_NUMERIC_4!()),
|
||||||
Key::Numpad6 => Some(input_event_codes::KEY_NUMERIC_6!()),
|
// Key::Numpad5 => Some(input_event_codes::KEY_NUMERIC_5!()),
|
||||||
Key::Numpad7 => Some(input_event_codes::KEY_NUMERIC_7!()),
|
// Key::Numpad6 => Some(input_event_codes::KEY_NUMERIC_6!()),
|
||||||
Key::Numpad8 => Some(input_event_codes::KEY_NUMERIC_8!()),
|
// Key::Numpad7 => Some(input_event_codes::KEY_NUMERIC_7!()),
|
||||||
Key::Numpad9 => Some(input_event_codes::KEY_NUMERIC_9!()),
|
// Key::Numpad8 => Some(input_event_codes::KEY_NUMERIC_8!()),
|
||||||
Key::F1 => Some(input_event_codes::KEY_F1!()),
|
// Key::Numpad9 => Some(input_event_codes::KEY_NUMERIC_9!()),
|
||||||
Key::F2 => Some(input_event_codes::KEY_F2!()),
|
// Key::F1 => Some(input_event_codes::KEY_F1!()),
|
||||||
Key::F3 => Some(input_event_codes::KEY_F3!()),
|
// Key::F2 => Some(input_event_codes::KEY_F2!()),
|
||||||
Key::F4 => Some(input_event_codes::KEY_F4!()),
|
// Key::F3 => Some(input_event_codes::KEY_F3!()),
|
||||||
Key::F5 => Some(input_event_codes::KEY_F5!()),
|
// Key::F4 => Some(input_event_codes::KEY_F4!()),
|
||||||
// Key::F6 => Some(input_event_codes::KEY_F6!()),
|
// Key::F5 => Some(input_event_codes::KEY_F5!()),
|
||||||
// Key::F7 => Some(input_event_codes::KEY_F7!()),
|
// // Key::F6 => Some(input_event_codes::KEY_F6!()),
|
||||||
// Key::F8 => Some(input_event_codes::KEY_F8!()),
|
// // Key::F7 => Some(input_event_codes::KEY_F7!()),
|
||||||
Key::F9 => Some(input_event_codes::KEY_F9!()),
|
// // Key::F8 => Some(input_event_codes::KEY_F8!()),
|
||||||
Key::F10 => Some(input_event_codes::KEY_F10!()),
|
// Key::F9 => Some(input_event_codes::KEY_F9!()),
|
||||||
Key::F11 => Some(input_event_codes::KEY_F11!()),
|
// Key::F10 => Some(input_event_codes::KEY_F10!()),
|
||||||
Key::F12 => Some(input_event_codes::KEY_F12!()),
|
// Key::F11 => Some(input_event_codes::KEY_F11!()),
|
||||||
Key::Comma => Some(input_event_codes::KEY_COMMA!()),
|
// Key::F12 => Some(input_event_codes::KEY_F12!()),
|
||||||
Key::Period => Some(input_event_codes::KEY_DOT!()),
|
// Key::Comma => Some(input_event_codes::KEY_COMMA!()),
|
||||||
Key::SlashFwd => Some(input_event_codes::KEY_SLASH!()),
|
// Key::Period => Some(input_event_codes::KEY_DOT!()),
|
||||||
Key::SlashBack => Some(input_event_codes::KEY_BACKSLASH!()),
|
// Key::SlashFwd => Some(input_event_codes::KEY_SLASH!()),
|
||||||
Key::Semicolon => Some(input_event_codes::KEY_SEMICOLON!()),
|
// Key::SlashBack => Some(input_event_codes::KEY_BACKSLASH!()),
|
||||||
Key::Apostrophe => Some(input_event_codes::KEY_APOSTROPHE!()),
|
// Key::Semicolon => Some(input_event_codes::KEY_SEMICOLON!()),
|
||||||
Key::BracketOpen => Some(input_event_codes::KEY_LEFTBRACE!()),
|
// Key::Apostrophe => Some(input_event_codes::KEY_APOSTROPHE!()),
|
||||||
Key::BracketClose => Some(input_event_codes::KEY_RIGHTBRACE!()),
|
// Key::BracketOpen => Some(input_event_codes::KEY_LEFTBRACE!()),
|
||||||
Key::Minus => Some(input_event_codes::KEY_MINUS!()),
|
// Key::BracketClose => Some(input_event_codes::KEY_RIGHTBRACE!()),
|
||||||
Key::Equals => Some(input_event_codes::KEY_EQUAL!()),
|
// Key::Minus => Some(input_event_codes::KEY_MINUS!()),
|
||||||
Key::Backtick => Some(input_event_codes::KEY_GRAVE!()),
|
// Key::Equals => Some(input_event_codes::KEY_EQUAL!()),
|
||||||
Key::LCmd => Some(input_event_codes::KEY_LEFTMETA!()),
|
// Key::Backtick => Some(input_event_codes::KEY_GRAVE!()),
|
||||||
Key::RCmd => Some(input_event_codes::KEY_RIGHTMETA!()),
|
// Key::LCmd => Some(input_event_codes::KEY_LEFTMETA!()),
|
||||||
Key::Multiply => Some(input_event_codes::KEY_NUMERIC_STAR!()),
|
// Key::RCmd => Some(input_event_codes::KEY_RIGHTMETA!()),
|
||||||
Key::Add => Some(input_event_codes::KEY_KPPLUS!()),
|
// Key::Multiply => Some(input_event_codes::KEY_NUMERIC_STAR!()),
|
||||||
Key::Subtract => Some(input_event_codes::KEY_MINUS!()),
|
// Key::Add => Some(input_event_codes::KEY_KPPLUS!()),
|
||||||
Key::Decimal => Some(input_event_codes::KEY_DOT!()),
|
// Key::Subtract => Some(input_event_codes::KEY_MINUS!()),
|
||||||
Key::Divide => Some(input_event_codes::KEY_SLASH!()),
|
// Key::Decimal => Some(input_event_codes::KEY_DOT!()),
|
||||||
_ => None,
|
// Key::Divide => Some(input_event_codes::KEY_SLASH!()),
|
||||||
}
|
// _ => None,
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use super::{get_sorted_handlers, CaptureManager};
|
use super::{get_sorted_handlers, CaptureManager};
|
||||||
use crate::{
|
use crate::{
|
||||||
|
bevy_plugin::{DbusConnection, InputUpdate},
|
||||||
core::client::INTERNAL_CLIENT,
|
core::client::INTERNAL_CLIENT,
|
||||||
nodes::{
|
nodes::{
|
||||||
fields::{Field, FieldTrait},
|
fields::{Field, FieldTrait},
|
||||||
@@ -8,19 +9,40 @@ use crate::{
|
|||||||
Node, OwnedNode,
|
Node, OwnedNode,
|
||||||
},
|
},
|
||||||
objects::{ObjectHandle, SpatialRef},
|
objects::{ObjectHandle, SpatialRef},
|
||||||
|
DefaultMaterial,
|
||||||
|
};
|
||||||
|
use bevy::{
|
||||||
|
app::{App, Plugin},
|
||||||
|
asset::{embedded_asset, AssetServer, Assets, Handle},
|
||||||
|
color::LinearRgba,
|
||||||
|
gltf::GltfAssetLabel,
|
||||||
|
pbr::MeshMaterial3d,
|
||||||
|
prelude::{
|
||||||
|
Children, Commands, Component, IntoSystemConfigs as _, Mesh, Query, Res, ResMut, Transform,
|
||||||
|
},
|
||||||
|
scene::SceneRoot,
|
||||||
|
utils::default,
|
||||||
|
};
|
||||||
|
use bevy_mod_openxr::{
|
||||||
|
helper_traits::{ToQuat, ToVec2, ToVec3},
|
||||||
|
openxr_session_running,
|
||||||
|
resources::{OxrFrameState, Pipelined},
|
||||||
|
session::OxrSession,
|
||||||
|
spaces::{OxrSpaceExt, OxrSpaceLocationFlags},
|
||||||
|
};
|
||||||
|
use bevy_mod_xr::{
|
||||||
|
hands::HandSide,
|
||||||
|
session::XrSessionCreated,
|
||||||
|
spaces::{XrPrimaryReferenceSpace, XrSpace},
|
||||||
};
|
};
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use glam::{Mat4, Vec2, Vec3};
|
use glam::{Mat4, Vec2, Vec3};
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
use openxr::{ActionSet, Posef};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use stardust_xr::values::Datamap;
|
use stardust_xr::values::Datamap;
|
||||||
use std::sync::Arc;
|
use std::{ops::Deref, sync::Arc};
|
||||||
use stereokit_rust::{
|
use tracing::{debug_span, error};
|
||||||
material::Material,
|
|
||||||
model::Model,
|
|
||||||
sk::MainThreadToken,
|
|
||||||
system::{Handed, Input},
|
|
||||||
util::Color128,
|
|
||||||
};
|
|
||||||
use zbus::Connection;
|
use zbus::Connection;
|
||||||
|
|
||||||
#[derive(Default, Debug, Deserialize, Serialize)]
|
#[derive(Default, Debug, Deserialize, Serialize)]
|
||||||
@@ -32,97 +54,217 @@ struct ControllerDatamap {
|
|||||||
scroll: Vec2,
|
scroll: Vec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SkController {
|
pub struct StardustControllerPlugin;
|
||||||
object_handle: ObjectHandle<SpatialRef>,
|
impl Plugin for StardustControllerPlugin {
|
||||||
input: Arc<InputMethod>,
|
fn build(&self, app: &mut App) {
|
||||||
handed: Handed,
|
embedded_asset!(app, "src/objects/input", "cursor.glb");
|
||||||
model: Model,
|
app.add_systems(XrSessionCreated, spawn_controllers);
|
||||||
material: Material,
|
app.add_systems(
|
||||||
capture_manager: CaptureManager,
|
InputUpdate,
|
||||||
datamap: ControllerDatamap,
|
update_controllers.run_if(openxr_session_running),
|
||||||
}
|
|
||||||
impl SkController {
|
|
||||||
pub fn new(connection: &Connection, handed: Handed) -> Result<Self> {
|
|
||||||
let (spatial, object_handle) = SpatialRef::create(
|
|
||||||
connection,
|
|
||||||
&("/org/stardustxr/Controller/".to_string()
|
|
||||||
+ match handed {
|
|
||||||
Handed::Left => "left",
|
|
||||||
_ => "right",
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
let model = Model::copy(Model::from_memory(
|
|
||||||
"cursor.glb",
|
|
||||||
include_bytes!("cursor.glb"),
|
|
||||||
None,
|
|
||||||
)?);
|
|
||||||
let model_nodes = model.get_nodes();
|
|
||||||
let mut model_node = model_nodes.visuals().next().unwrap();
|
|
||||||
let material = Material::copy(&model_node.get_material().unwrap());
|
|
||||||
model_node.material(&material);
|
|
||||||
let tip = InputDataType::Tip(Tip::default());
|
|
||||||
let input = InputMethod::add_to(
|
|
||||||
&spatial.node().unwrap(),
|
|
||||||
tip,
|
|
||||||
Datamap::from_typed(ControllerDatamap::default())?,
|
|
||||||
)?;
|
|
||||||
Ok(SkController {
|
|
||||||
object_handle,
|
|
||||||
input,
|
|
||||||
handed,
|
|
||||||
model,
|
|
||||||
material,
|
|
||||||
capture_manager: CaptureManager::default(),
|
|
||||||
datamap: Default::default(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
pub fn update(&mut self, token: &MainThreadToken) {
|
}
|
||||||
let controller = Input::controller(self.handed);
|
|
||||||
let input_node = self.input.spatial.node().unwrap();
|
|
||||||
input_node.set_enabled(controller.tracked.is_active());
|
|
||||||
if input_node.enabled() {
|
|
||||||
let world_transform = Mat4::from_rotation_translation(
|
|
||||||
controller.aim.orientation.into(),
|
|
||||||
controller.aim.position.into(),
|
|
||||||
);
|
|
||||||
self.material
|
|
||||||
.color_tint(if self.capture_manager.capture.is_none() {
|
|
||||||
Color128::new_rgb(1.0, 1.0, 1.0)
|
|
||||||
} else {
|
|
||||||
Color128::new_rgb(0.0, 1.0, 0.75)
|
|
||||||
});
|
|
||||||
self.model.draw(
|
|
||||||
token,
|
|
||||||
world_transform * Mat4::from_scale(Vec3::ONE * 0.02),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
self.input.spatial.set_local_transform(world_transform);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.datamap = ControllerDatamap {
|
fn update_controllers(
|
||||||
select: controller.trigger,
|
mut mats: ResMut<Assets<DefaultMaterial>>,
|
||||||
middle: controller.stick_click.is_active() as u32 as f32,
|
mut query: Query<(&mut SkController, &mut Transform)>,
|
||||||
context: controller.is_x2_pressed() as u32 as f32,
|
time: Res<OxrFrameState>,
|
||||||
grab: controller.grip,
|
base_space: Res<XrPrimaryReferenceSpace>,
|
||||||
scroll: controller.stick.into(),
|
session: ResMut<OxrSession>,
|
||||||
|
pipelined: Option<Res<Pipelined>>,
|
||||||
|
) {
|
||||||
|
for (mut controller, mut transform) in query.iter_mut() {
|
||||||
|
let input_node = controller.input.spatial.node().unwrap();
|
||||||
|
let time = if pipelined.is_some() {
|
||||||
|
openxr::Time::from_nanos(
|
||||||
|
time.predicted_display_time.as_nanos() + time.predicted_display_period.as_nanos(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
time.predicted_display_time
|
||||||
};
|
};
|
||||||
*self.input.datamap.lock() = Datamap::from_typed(&self.datamap).unwrap();
|
let location = (|| {
|
||||||
|
let location = match session.locate_space(
|
||||||
|
&XrSpace::from_raw_openxr_space(controller.space.as_raw()),
|
||||||
|
&base_space,
|
||||||
|
time,
|
||||||
|
) {
|
||||||
|
Err(err) => {
|
||||||
|
error!("issues locating controller space: {err}");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Ok(val) => val,
|
||||||
|
};
|
||||||
|
let flags = OxrSpaceLocationFlags(location.location_flags);
|
||||||
|
|
||||||
|
input_node.set_enabled(flags.pos_tracked() && flags.rot_tracked());
|
||||||
|
if flags.pos_valid() && flags.rot_valid() {
|
||||||
|
Some(Mat4::from_rotation_translation(
|
||||||
|
location.pose.orientation.to_quat(),
|
||||||
|
location.pose.position.to_vec3(),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
.unwrap_or(Mat4::IDENTITY);
|
||||||
|
if input_node.enabled() {
|
||||||
|
let world_transform = location;
|
||||||
|
if let Some(mat) = controller.material.get().and_then(|v| mats.get_mut(v)) {
|
||||||
|
mat.color = if controller.capture_manager.capture.is_none() {
|
||||||
|
LinearRgba::rgb(1.0, 1.0, 1.0)
|
||||||
|
} else {
|
||||||
|
LinearRgba::rgb(0.0, 1.0, 0.75)
|
||||||
|
}
|
||||||
|
.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
*transform =
|
||||||
|
Transform::from_matrix(world_transform * Mat4::from_scale(Vec3::ONE * 0.02));
|
||||||
|
controller
|
||||||
|
.input
|
||||||
|
.spatial
|
||||||
|
.set_local_transform(world_transform);
|
||||||
|
}
|
||||||
|
// controller.datamap.select = controller
|
||||||
|
// .actions
|
||||||
|
// .trigger
|
||||||
|
// .state(&session, openxr::Path::NULL)
|
||||||
|
// .map(|v| v.current_state)
|
||||||
|
// .unwrap_or_default();
|
||||||
|
// controller.datamap.grab = controller
|
||||||
|
// .actions
|
||||||
|
// .grip
|
||||||
|
// .state(&session, openxr::Path::NULL)
|
||||||
|
// .map(|v| v.current_state)
|
||||||
|
// .unwrap_or_default();
|
||||||
|
// controller.datamap.scroll = controller
|
||||||
|
// .actions
|
||||||
|
// .stick
|
||||||
|
// .state(&session, openxr::Path::NULL)
|
||||||
|
// .map(|v| v.current_state.to_vec2())
|
||||||
|
// .unwrap_or_default();
|
||||||
|
*controller.input.datamap.lock() = Datamap::from_typed(&controller.datamap).unwrap();
|
||||||
|
|
||||||
|
// remove the capture when it's removed from captures list
|
||||||
let distance_calculator = |space: &Arc<Spatial>, _data: &InputDataType, field: &Field| {
|
let distance_calculator = |space: &Arc<Spatial>, _data: &InputDataType, field: &Field| {
|
||||||
Some(field.distance(space, [0.0; 3].into()).abs())
|
Some(field.distance(space, [0.0; 3].into()).abs())
|
||||||
};
|
};
|
||||||
|
|
||||||
self.capture_manager.update_capture(&self.input);
|
let input = controller.input.clone();
|
||||||
self.capture_manager
|
controller.capture_manager.update_capture(&input);
|
||||||
.set_new_capture(&self.input, distance_calculator);
|
controller
|
||||||
self.capture_manager.apply_capture(&self.input);
|
.capture_manager
|
||||||
|
.set_new_capture(&input, distance_calculator);
|
||||||
|
controller.capture_manager.apply_capture(&input);
|
||||||
|
|
||||||
if self.capture_manager.capture.is_some() {
|
if controller.capture_manager.capture.is_some() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let sorted_handlers = get_sorted_handlers(&self.input, distance_calculator);
|
let sorted_handlers = get_sorted_handlers(&input, distance_calculator);
|
||||||
self.input.set_handler_order(sorted_handlers.iter());
|
controller.input.set_handler_order(sorted_handlers.iter());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn spawn_controllers(
|
||||||
|
connection: Res<DbusConnection>,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
|
session: Res<OxrSession>,
|
||||||
|
mut cmds: Commands,
|
||||||
|
) {
|
||||||
|
let handle = asset_server.load(GltfAssetLabel::Scene(0).from_asset("embedded://cursor.glb"));
|
||||||
|
for handed in [HandSide::Left, HandSide::Right] {
|
||||||
|
let side = match handed {
|
||||||
|
HandSide::Left => "left",
|
||||||
|
HandSide::Right => "right",
|
||||||
|
};
|
||||||
|
let _span = debug_span!("create SpatialRef").entered();
|
||||||
|
let (spatial, object_handle) = SpatialRef::create(
|
||||||
|
&connection,
|
||||||
|
&("/org/stardustxr/Controller/".to_string() + side),
|
||||||
|
);
|
||||||
|
drop(_span);
|
||||||
|
let tip = InputDataType::Tip(Tip::default());
|
||||||
|
|
||||||
|
let _span = debug_span!("create input method").entered();
|
||||||
|
let Ok(input) = (|| -> color_eyre::Result<Arc<InputMethod>> {
|
||||||
|
Ok(InputMethod::add_to(
|
||||||
|
&spatial.node().unwrap(),
|
||||||
|
tip,
|
||||||
|
Datamap::from_typed(ControllerDatamap::default())?,
|
||||||
|
)?)
|
||||||
|
})() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
drop(_span);
|
||||||
|
let _span = debug_span!("create actions").entered();
|
||||||
|
let actions = {
|
||||||
|
let set = session
|
||||||
|
.instance()
|
||||||
|
.create_action_set(
|
||||||
|
&format!("controller-{side}"),
|
||||||
|
&format!("{side} controller"),
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
Actions {
|
||||||
|
set: set.clone(),
|
||||||
|
trigger: set
|
||||||
|
.create_action(&format!("trigger-{side}"), &format!("{side} trigger"), &[])
|
||||||
|
.unwrap(),
|
||||||
|
grip: set
|
||||||
|
.create_action(&format!("grip-{side}"), &format!("{side} grip"), &[])
|
||||||
|
.unwrap(),
|
||||||
|
stick: set
|
||||||
|
.create_action(&format!("stick-{side}"), &format!("{side} stick"), &[])
|
||||||
|
.unwrap(),
|
||||||
|
pose: set
|
||||||
|
.create_action(&format!("pose-{side}"), &format!("{side} pose"), &[])
|
||||||
|
.unwrap(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
drop(_span);
|
||||||
|
let _span = debug_span!("spawn").entered();
|
||||||
|
cmds.spawn((
|
||||||
|
SceneRoot(handle.clone()),
|
||||||
|
SkController {
|
||||||
|
object_handle,
|
||||||
|
input,
|
||||||
|
handed,
|
||||||
|
material: OnceCell::new(),
|
||||||
|
datamap: Default::default(),
|
||||||
|
space: actions
|
||||||
|
.pose
|
||||||
|
.create_space(
|
||||||
|
session.deref().deref().clone(),
|
||||||
|
openxr::Path::NULL,
|
||||||
|
Posef::IDENTITY,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
actions,
|
||||||
|
capture_manager: default(),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
#[require(Transform)]
|
||||||
|
pub struct SkController {
|
||||||
|
object_handle: ObjectHandle<SpatialRef>,
|
||||||
|
input: Arc<InputMethod>,
|
||||||
|
handed: HandSide,
|
||||||
|
material: OnceCell<Handle<DefaultMaterial>>,
|
||||||
|
datamap: ControllerDatamap,
|
||||||
|
space: openxr::Space,
|
||||||
|
actions: Actions,
|
||||||
|
capture_manager: CaptureManager,
|
||||||
|
}
|
||||||
|
struct Actions {
|
||||||
|
set: openxr::ActionSet,
|
||||||
|
trigger: openxr::Action<f32>,
|
||||||
|
grip: openxr::Action<f32>,
|
||||||
|
stick: openxr::Action<openxr::Vector2f>,
|
||||||
|
pose: openxr::Action<openxr::Posef>,
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::bevy_plugin::{DbusConnection, InputUpdate};
|
||||||
use crate::core::client::INTERNAL_CLIENT;
|
use crate::core::client::INTERNAL_CLIENT;
|
||||||
use crate::nodes::fields::{Field, FieldTrait};
|
use crate::nodes::fields::{Field, FieldTrait};
|
||||||
use crate::nodes::input::{InputDataType, InputHandler, INPUT_HANDLER_REGISTRY};
|
use crate::nodes::input::{InputDataType, InputHandler, INPUT_HANDLER_REGISTRY};
|
||||||
@@ -8,135 +9,128 @@ use crate::nodes::{
|
|||||||
Node,
|
Node,
|
||||||
};
|
};
|
||||||
use crate::objects::{ObjectHandle, SpatialRef};
|
use crate::objects::{ObjectHandle, SpatialRef};
|
||||||
|
use crate::DefaultMaterial;
|
||||||
|
use bevy::app::{Plugin, PostUpdate};
|
||||||
|
use bevy::asset::{AssetServer, Assets, Handle};
|
||||||
|
use bevy::prelude::{
|
||||||
|
Commands, Component, Entity, Gizmos, IntoSystemConfigs as _, Query, Res, ResMut,
|
||||||
|
};
|
||||||
|
use bevy::utils::default;
|
||||||
|
use bevy_mod_openxr::helper_traits::{ToQuat, ToVec3};
|
||||||
|
use bevy_mod_openxr::openxr_session_running;
|
||||||
|
use bevy_mod_openxr::resources::{OxrFrameState, Pipelined};
|
||||||
|
use bevy_mod_openxr::session::OxrSession;
|
||||||
|
use bevy_mod_openxr::spaces::OxrSpaceLocationFlags;
|
||||||
|
use bevy_mod_xr::hands::{HandBone, HandSide};
|
||||||
|
use bevy_mod_xr::session::XrSessionCreated;
|
||||||
|
use bevy_mod_xr::spaces::XrPrimaryReferenceSpace;
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use glam::{Mat4, Quat, Vec3};
|
use glam::{Mat4, Quat, Vec3};
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use stardust_xr::values::Datamap;
|
use stardust_xr::values::Datamap;
|
||||||
use std::f32::INFINITY;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use stereokit_rust::sk::{DisplayMode, MainThreadToken, Sk};
|
use tracing::error;
|
||||||
use stereokit_rust::system::{HandJoint, HandSource, Handed, Input, LinePoint, Lines};
|
|
||||||
use stereokit_rust::util::Color128;
|
|
||||||
use zbus::Connection;
|
use zbus::Connection;
|
||||||
|
|
||||||
use super::{get_sorted_handlers, CaptureManager};
|
use super::{get_sorted_handlers, CaptureManager};
|
||||||
|
|
||||||
fn convert_joint(joint: HandJoint) -> Joint {
|
fn update_joint(joint: &mut Joint, oxr_joint: openxr::HandJointLocation) {
|
||||||
Joint {
|
let flags = OxrSpaceLocationFlags(oxr_joint.location_flags);
|
||||||
position: Vec3::from(joint.position).into(),
|
if flags.pos_valid() && flags.rot_valid() {
|
||||||
rotation: Quat::from(joint.orientation).into(),
|
*joint = convert_joint(oxr_joint);
|
||||||
radius: joint.radius,
|
|
||||||
distance: 0.0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Deserialize, Serialize)]
|
pub struct StardustHandPlugin;
|
||||||
struct HandDatamap {
|
impl Plugin for StardustHandPlugin {
|
||||||
pinch_strength: f32,
|
fn build(&self, app: &mut bevy::prelude::App) {
|
||||||
grab_strength: f32,
|
app.add_systems(XrSessionCreated, create_hands);
|
||||||
}
|
app.add_systems(
|
||||||
|
InputUpdate,
|
||||||
pub struct SkHand {
|
(
|
||||||
_node: OwnedNode,
|
update_hands.run_if(openxr_session_running),
|
||||||
palm_spatial: Arc<Spatial>,
|
draw_hand_gizmos,
|
||||||
palm_object: ObjectHandle<SpatialRef>,
|
)
|
||||||
handed: Handed,
|
.chain(),
|
||||||
input: Arc<InputMethod>,
|
|
||||||
capture_manager: CaptureManager,
|
|
||||||
datamap: HandDatamap,
|
|
||||||
}
|
|
||||||
impl SkHand {
|
|
||||||
pub fn new(connection: &Connection, handed: Handed) -> Result<Self> {
|
|
||||||
let (palm_spatial, palm_object) = SpatialRef::create(
|
|
||||||
connection,
|
|
||||||
&("/org/stardustxr/Hand/".to_string()
|
|
||||||
+ match handed {
|
|
||||||
Handed::Left => "left",
|
|
||||||
_ => "right",
|
|
||||||
} + "/palm"),
|
|
||||||
);
|
);
|
||||||
let _node = Node::generate(&INTERNAL_CLIENT, false).add_to_scenegraph_owned()?;
|
|
||||||
Spatial::add_to(&_node.0, None, Mat4::IDENTITY, false);
|
|
||||||
let hand = InputDataType::Hand(Hand {
|
|
||||||
right: handed == Handed::Right,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
let datamap = Datamap::from_typed(HandDatamap::default())?;
|
|
||||||
let input = InputMethod::add_to(&_node.0, hand, datamap)?;
|
|
||||||
|
|
||||||
Input::hand_visible(handed, false);
|
|
||||||
Ok(SkHand {
|
|
||||||
_node,
|
|
||||||
palm_spatial,
|
|
||||||
palm_object,
|
|
||||||
handed,
|
|
||||||
input,
|
|
||||||
capture_manager: CaptureManager::default(),
|
|
||||||
datamap: Default::default(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
pub fn update(&mut self, sk: &Sk, token: &MainThreadToken) {
|
}
|
||||||
let sk_hand = Input::hand(self.handed);
|
|
||||||
let real_hand = Input::hand_source(self.handed) as u32 == HandSource::Articulated as u32;
|
|
||||||
if let InputDataType::Hand(hand) = &mut *self.input.data.lock() {
|
|
||||||
let input_node = self.input.spatial.node().unwrap();
|
|
||||||
input_node.set_enabled(
|
|
||||||
(real_hand || sk.get_active_display_mode() == DisplayMode::Flatscreen)
|
|
||||||
&& sk_hand.tracked.is_active(),
|
|
||||||
);
|
|
||||||
if input_node.enabled() {
|
|
||||||
hand.thumb.tip = convert_joint(sk_hand.fingers[0][4]);
|
|
||||||
hand.thumb.distal = convert_joint(sk_hand.fingers[0][3]);
|
|
||||||
hand.thumb.proximal = convert_joint(sk_hand.fingers[0][2]);
|
|
||||||
hand.thumb.metacarpal = convert_joint(sk_hand.fingers[0][1]);
|
|
||||||
|
|
||||||
for (finger, mut sk_finger) in [
|
fn draw_hand_gizmos(mut gizmos: Gizmos, query: Query<&SkHand>) {
|
||||||
(&mut hand.index, sk_hand.fingers[1]),
|
for hand in query.iter() {
|
||||||
(&mut hand.middle, sk_hand.fingers[2]),
|
gizmos.axes(hand.palm_spatial.global_transform(), 0.05);
|
||||||
(&mut hand.ring, sk_hand.fingers[3]),
|
}
|
||||||
(&mut hand.little, sk_hand.fingers[4]),
|
}
|
||||||
] {
|
|
||||||
sk_finger[4].radius = 0.0;
|
|
||||||
finger.tip = convert_joint(sk_finger[4]);
|
|
||||||
finger.distal = convert_joint(sk_finger[3]);
|
|
||||||
finger.intermediate = convert_joint(sk_finger[2]);
|
|
||||||
finger.proximal = convert_joint(sk_finger[1]);
|
|
||||||
finger.metacarpal = convert_joint(sk_finger[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
hand.palm.position = Vec3::from(sk_hand.palm.position).into();
|
fn update_hands(
|
||||||
hand.palm.rotation = Quat::from(sk_hand.palm.orientation).into();
|
mut mats: ResMut<Assets<DefaultMaterial>>,
|
||||||
hand.palm.radius =
|
mut query: Query<&mut SkHand>,
|
||||||
(sk_hand.fingers[2][0].radius + sk_hand.fingers[2][1].radius) * 0.5;
|
time: Res<OxrFrameState>,
|
||||||
|
base_space: Res<XrPrimaryReferenceSpace>,
|
||||||
self.palm_spatial
|
session: ResMut<OxrSession>,
|
||||||
.set_local_transform(Mat4::from_rotation_translation(
|
pipelined: Option<Res<Pipelined>>,
|
||||||
hand.palm.rotation.into(),
|
) {
|
||||||
hand.palm.position.into(),
|
let time = if pipelined.is_some() {
|
||||||
));
|
openxr::Time::from_nanos(
|
||||||
|
time.predicted_display_time.as_nanos() + time.predicted_display_period.as_nanos(),
|
||||||
hand.wrist.position = Vec3::from(sk_hand.wrist.position).into();
|
)
|
||||||
hand.wrist.rotation = Quat::from(sk_hand.wrist.orientation).into();
|
} else {
|
||||||
hand.wrist.radius =
|
time.predicted_display_time
|
||||||
(sk_hand.fingers[0][0].radius + sk_hand.fingers[4][0].radius) * 0.5;
|
};
|
||||||
|
for mut hand in &mut query {
|
||||||
hand.elbow = None;
|
let joints = session
|
||||||
|
.locate_hand_joints(&hand.hand_tracker, &base_space, time)
|
||||||
self.draw(
|
.unwrap();
|
||||||
token,
|
if let InputDataType::Hand(hand_input) = &mut *hand.input.data.lock() {
|
||||||
if self.capture_manager.capture.is_none() {
|
let input_node = hand.input.spatial.node().unwrap();
|
||||||
Color128::new_rgb(1.0, 1.0, 1.0)
|
input_node.set_enabled(joints.is_some());
|
||||||
} else {
|
if let Some(joints) = joints.as_ref() {
|
||||||
Color128::new_rgb(0.0, 1.0, 0.75)
|
update_joint(
|
||||||
},
|
&mut hand_input.thumb.tip,
|
||||||
hand,
|
joints[HandBone::ThumbTip as usize],
|
||||||
);
|
);
|
||||||
|
update_joint(
|
||||||
|
&mut hand_input.thumb.distal,
|
||||||
|
joints[HandBone::ThumbDistal as usize],
|
||||||
|
);
|
||||||
|
update_joint(
|
||||||
|
&mut hand_input.thumb.proximal,
|
||||||
|
joints[HandBone::ThumbProximal as usize],
|
||||||
|
);
|
||||||
|
update_joint(
|
||||||
|
&mut hand_input.thumb.metacarpal,
|
||||||
|
joints[HandBone::ThumbMetacarpal as usize],
|
||||||
|
);
|
||||||
|
|
||||||
|
for (finger, finger_index) in [
|
||||||
|
(&mut hand_input.index, 6),
|
||||||
|
(&mut hand_input.middle, 11),
|
||||||
|
(&mut hand_input.ring, 16),
|
||||||
|
(&mut hand_input.little, 21),
|
||||||
|
] {
|
||||||
|
update_joint(&mut finger.tip, joints[finger_index + 4]);
|
||||||
|
update_joint(&mut finger.distal, joints[finger_index + 3]);
|
||||||
|
update_joint(&mut finger.intermediate, joints[finger_index + 2]);
|
||||||
|
update_joint(&mut finger.proximal, joints[finger_index + 1]);
|
||||||
|
update_joint(&mut finger.metacarpal, joints[finger_index]);
|
||||||
|
}
|
||||||
|
update_joint(&mut hand_input.palm, joints[HandBone::Palm as usize]);
|
||||||
|
hand.palm_spatial
|
||||||
|
.set_local_transform(Mat4::from_rotation_translation(
|
||||||
|
hand_input.palm.rotation.into(),
|
||||||
|
hand_input.palm.position.into(),
|
||||||
|
));
|
||||||
|
update_joint(&mut hand_input.wrist, joints[HandBone::Wrist as usize]);
|
||||||
|
|
||||||
|
hand_input.elbow = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.datamap.pinch_strength = sk_hand.pinch_activation;
|
if let Some(joints) = joints.as_ref() {
|
||||||
self.datamap.grab_strength = sk_hand.grip_activation;
|
hand.datamap.pinch_strength = pinch_activation(joints);
|
||||||
*self.input.datamap.lock() = Datamap::from_typed(&self.datamap).unwrap();
|
hand.datamap.grab_strength = grip_activation(joints);
|
||||||
|
*hand.input.datamap.lock() = Datamap::from_typed(&hand.datamap).unwrap();
|
||||||
|
}
|
||||||
let distance_calculator = |space: &Arc<Spatial>, data: &InputDataType, field: &Field| {
|
let distance_calculator = |space: &Arc<Spatial>, data: &InputDataType, field: &Field| {
|
||||||
let InputDataType::Hand(hand) = data else {
|
let InputDataType::Hand(hand) = data else {
|
||||||
return None;
|
return None;
|
||||||
@@ -154,84 +148,148 @@ impl SkHand {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
self.capture_manager.update_capture(&self.input);
|
let input = hand.input.clone();
|
||||||
self.capture_manager
|
hand.capture_manager.update_capture(&input);
|
||||||
.set_new_capture(&self.input, distance_calculator);
|
hand.capture_manager
|
||||||
self.capture_manager.apply_capture(&self.input);
|
.set_new_capture(&input, distance_calculator);
|
||||||
|
hand.capture_manager.apply_capture(&input);
|
||||||
|
|
||||||
if self.capture_manager.capture.is_some() {
|
if hand.capture_manager.capture.is_some() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let sorted_handlers = get_sorted_handlers(&self.input, distance_calculator);
|
let sorted_handlers = get_sorted_handlers(&hand.input, distance_calculator);
|
||||||
self.input.set_handler_order(sorted_handlers.iter());
|
hand.input.set_handler_order(sorted_handlers.iter());
|
||||||
}
|
|
||||||
|
|
||||||
fn draw(&self, token: &MainThreadToken, color: Color128, hand: &Hand) {
|
|
||||||
// thumb
|
|
||||||
Lines::add_list(
|
|
||||||
token,
|
|
||||||
&[
|
|
||||||
joint_to_line_point(&hand.thumb.tip, color),
|
|
||||||
joint_to_line_point(&hand.thumb.distal, color),
|
|
||||||
joint_to_line_point(&hand.thumb.proximal, color),
|
|
||||||
joint_to_line_point(&hand.thumb.metacarpal, color),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
// index
|
|
||||||
Lines::add_list(
|
|
||||||
token,
|
|
||||||
&[
|
|
||||||
joint_to_line_point(&hand.index.tip, color),
|
|
||||||
joint_to_line_point(&hand.index.distal, color),
|
|
||||||
joint_to_line_point(&hand.index.intermediate, color),
|
|
||||||
joint_to_line_point(&hand.index.proximal, color),
|
|
||||||
joint_to_line_point(&hand.index.metacarpal, color),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
// middle
|
|
||||||
Lines::add_list(
|
|
||||||
token,
|
|
||||||
&[
|
|
||||||
joint_to_line_point(&hand.middle.tip, color),
|
|
||||||
joint_to_line_point(&hand.middle.distal, color),
|
|
||||||
joint_to_line_point(&hand.middle.intermediate, color),
|
|
||||||
joint_to_line_point(&hand.middle.proximal, color),
|
|
||||||
joint_to_line_point(&hand.middle.metacarpal, color),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
// ring
|
|
||||||
Lines::add_list(
|
|
||||||
token,
|
|
||||||
&[
|
|
||||||
joint_to_line_point(&hand.ring.tip, color),
|
|
||||||
joint_to_line_point(&hand.ring.distal, color),
|
|
||||||
joint_to_line_point(&hand.ring.intermediate, color),
|
|
||||||
joint_to_line_point(&hand.ring.proximal, color),
|
|
||||||
joint_to_line_point(&hand.ring.metacarpal, color),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
// palm
|
|
||||||
Lines::add_list(
|
|
||||||
token,
|
|
||||||
&[
|
|
||||||
joint_to_line_point(&hand.wrist, color),
|
|
||||||
joint_to_line_point(&hand.thumb.metacarpal, color),
|
|
||||||
joint_to_line_point(&hand.index.metacarpal, color),
|
|
||||||
joint_to_line_point(&hand.middle.metacarpal, color),
|
|
||||||
joint_to_line_point(&hand.ring.metacarpal, color),
|
|
||||||
joint_to_line_point(&hand.little.metacarpal, color),
|
|
||||||
joint_to_line_point(&hand.wrist, color),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn joint_to_line_point(joint: &Joint, color: Color128) -> LinePoint {
|
const PINCH_MAX: f32 = 0.11;
|
||||||
LinePoint {
|
const PINCH_ACTIVACTION_DISTANCE: f32 = 0.01;
|
||||||
pt: Vec3::from(joint.position).into(),
|
// based on https://github.com/StereoKit/StereoKit/blob/ca2be7d45f4f4388e8df7542e9a0313bcc45946e/StereoKitC/hands/input_hand.cpp#L375-L394
|
||||||
thickness: joint.radius * 2.0,
|
fn pinch_activation(joints: &[openxr::HandJointLocation; openxr::HAND_JOINT_COUNT]) -> f32 {
|
||||||
color: color.into(),
|
let combined_radius =
|
||||||
|
joints[HandBone::ThumbTip as usize].radius + joints[HandBone::IndexTip as usize].radius;
|
||||||
|
let pinch_dist = joints[HandBone::ThumbTip as usize]
|
||||||
|
.pose
|
||||||
|
.position
|
||||||
|
.to_vec3()
|
||||||
|
.distance(joints[HandBone::IndexTip as usize].pose.position.to_vec3())
|
||||||
|
- combined_radius;
|
||||||
|
(1.0 - ((pinch_dist - PINCH_ACTIVACTION_DISTANCE) / (PINCH_MAX - PINCH_ACTIVACTION_DISTANCE)))
|
||||||
|
.clamp(0.0, 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
const GRIP_MAX: f32 = 0.11;
|
||||||
|
const GRIP_ACTIVACTION_DISTANCE: f32 = 0.01;
|
||||||
|
// based on https://github.com/StereoKit/StereoKit/blob/ca2be7d45f4f4388e8df7542e9a0313bcc45946e/StereoKitC/hands/input_hand.cpp#L375-L394
|
||||||
|
fn grip_activation(joints: &[openxr::HandJointLocation; openxr::HAND_JOINT_COUNT]) -> f32 {
|
||||||
|
let combined_radius = joints[HandBone::RingTip as usize].radius
|
||||||
|
+ joints[HandBone::RingMetacarpal as usize].radius;
|
||||||
|
let grip_dist = joints[HandBone::RingTip as usize]
|
||||||
|
.pose
|
||||||
|
.position
|
||||||
|
.to_vec3()
|
||||||
|
.distance(
|
||||||
|
joints[HandBone::RingMetacarpal as usize]
|
||||||
|
.pose
|
||||||
|
.position
|
||||||
|
.to_vec3(),
|
||||||
|
) - combined_radius;
|
||||||
|
(1.0 - ((grip_dist - GRIP_ACTIVACTION_DISTANCE) / (GRIP_MAX - GRIP_ACTIVACTION_DISTANCE)))
|
||||||
|
.clamp(0.0, 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_hands(connection: Res<DbusConnection>, session: Res<OxrSession>, mut cmds: Commands) {
|
||||||
|
for handed in [HandSide::Left, HandSide::Right] {
|
||||||
|
let hand = (|| -> color_eyre::Result<_> {
|
||||||
|
let side = match handed {
|
||||||
|
HandSide::Left => "left",
|
||||||
|
HandSide::Right => "right",
|
||||||
|
};
|
||||||
|
let (palm_spatial, palm_object) = SpatialRef::create(
|
||||||
|
&connection,
|
||||||
|
&("/org/stardustxr/Hand/".to_string() + side + "/palm"),
|
||||||
|
);
|
||||||
|
let _node = Node::generate(&INTERNAL_CLIENT, false).add_to_scenegraph_owned()?;
|
||||||
|
Spatial::add_to(&_node.0, None, Mat4::IDENTITY, false);
|
||||||
|
let hand = InputDataType::Hand(Hand {
|
||||||
|
right: matches!(handed, HandSide::Right),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let datamap = Datamap::from_typed(HandDatamap::default())?;
|
||||||
|
let input = InputMethod::add_to(&_node.0, hand, datamap)?;
|
||||||
|
|
||||||
|
let tracker = session.create_hand_tracker(match handed {
|
||||||
|
HandSide::Left => openxr::Hand::LEFT,
|
||||||
|
HandSide::Right => openxr::Hand::RIGHT,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(SkHand {
|
||||||
|
_node,
|
||||||
|
palm_spatial,
|
||||||
|
palm_object,
|
||||||
|
handed,
|
||||||
|
input,
|
||||||
|
capture_manager: default(),
|
||||||
|
datamap: Default::default(),
|
||||||
|
material: OnceCell::new(),
|
||||||
|
vis_entity: OnceCell::new(),
|
||||||
|
hand_tracker: tracker,
|
||||||
|
})
|
||||||
|
})();
|
||||||
|
let hand = match hand {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(err) => {
|
||||||
|
error!("error while creating hand: {err}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
cmds.spawn(hand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_joint(joint: openxr::HandJointLocation) -> Joint {
|
||||||
|
Joint {
|
||||||
|
position: joint.pose.position.to_vec3().into(),
|
||||||
|
rotation: joint.pose.orientation.to_quat().into(),
|
||||||
|
radius: joint.radius,
|
||||||
|
distance: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Deserialize, Serialize)]
|
||||||
|
struct HandDatamap {
|
||||||
|
pinch_strength: f32,
|
||||||
|
grab_strength: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct SkHand {
|
||||||
|
_node: OwnedNode,
|
||||||
|
palm_spatial: Arc<Spatial>,
|
||||||
|
palm_object: ObjectHandle<SpatialRef>,
|
||||||
|
handed: HandSide,
|
||||||
|
input: Arc<InputMethod>,
|
||||||
|
datamap: HandDatamap,
|
||||||
|
material: OnceCell<Handle<DefaultMaterial>>,
|
||||||
|
vis_entity: OnceCell<Entity>,
|
||||||
|
hand_tracker: openxr::HandTracker,
|
||||||
|
capture_manager: CaptureManager,
|
||||||
|
}
|
||||||
|
impl SkHand {
|
||||||
|
fn compare_distance(&self, field: &Field) -> f32 {
|
||||||
|
let InputDataType::Hand(hand) = &*self.input.data.lock() else {
|
||||||
|
return f32::INFINITY;
|
||||||
|
};
|
||||||
|
let spatial = &self.input.spatial;
|
||||||
|
let thumb_tip_distance = field.distance(spatial, hand.thumb.tip.position.into());
|
||||||
|
let index_tip_distance = field.distance(spatial, hand.index.tip.position.into());
|
||||||
|
let middle_tip_distance = field.distance(spatial, hand.middle.tip.position.into());
|
||||||
|
let ring_tip_distance = field.distance(spatial, hand.ring.tip.position.into());
|
||||||
|
|
||||||
|
(thumb_tip_distance * 0.3)
|
||||||
|
+ (index_tip_distance * 0.4)
|
||||||
|
+ (middle_tip_distance * 0.15)
|
||||||
|
+ (ring_tip_distance * 0.15)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,25 +7,26 @@ use crate::{
|
|||||||
spatial::{Spatial, EXPORTED_SPATIALS},
|
spatial::{Spatial, EXPORTED_SPATIALS},
|
||||||
Node, OwnedNode,
|
Node, OwnedNode,
|
||||||
},
|
},
|
||||||
|
TOKIO,
|
||||||
};
|
};
|
||||||
|
use bevy::prelude::Resource;
|
||||||
|
use bevy_mod_openxr::helper_traits::{ToQuat, ToVec3};
|
||||||
use glam::{vec3, Mat4};
|
use glam::{vec3, Mat4};
|
||||||
use input::{
|
use input::{
|
||||||
eye_pointer::EyePointer, mouse_pointer::MousePointer, sk_controller::SkController,
|
eye_pointer::EyePointer, mouse_pointer::MousePointer, sk_controller::SkController,
|
||||||
sk_hand::SkHand,
|
sk_hand::SkHand,
|
||||||
};
|
};
|
||||||
|
use openxr::SpaceLocationFlags;
|
||||||
use play_space::PlaySpaceBounds;
|
use play_space::PlaySpaceBounds;
|
||||||
|
use stardust_xr::schemas::dbus::object_registry::ObjectRegistry;
|
||||||
use std::{marker::PhantomData, sync::Arc};
|
use std::{marker::PhantomData, sync::Arc};
|
||||||
use stereokit_rust::{
|
use tracing::info;
|
||||||
sk::{DisplayMode, MainThreadToken, Sk},
|
|
||||||
system::{Handed, Input, Key, World},
|
|
||||||
util::Device,
|
|
||||||
};
|
|
||||||
use zbus::{interface, object_server::Interface, zvariant::OwnedObjectPath, Connection};
|
use zbus::{interface, object_server::Interface, zvariant::OwnedObjectPath, Connection};
|
||||||
|
|
||||||
pub mod input;
|
pub mod input;
|
||||||
pub mod play_space;
|
pub mod play_space;
|
||||||
|
|
||||||
enum Inputs {
|
pub(crate) enum Inputs {
|
||||||
XR {
|
XR {
|
||||||
controller_left: SkController,
|
controller_left: SkController,
|
||||||
controller_right: SkController,
|
controller_right: SkController,
|
||||||
@@ -41,39 +42,54 @@ enum Inputs {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ControllerInput {
|
||||||
|
pose: openxr::Space,
|
||||||
|
select: openxr::Action<f32>,
|
||||||
|
grab: openxr::Action<f32>,
|
||||||
|
scroll: openxr::Action<openxr::Vector2f>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource)]
|
||||||
pub struct ServerObjects {
|
pub struct ServerObjects {
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
hmd: (Arc<Spatial>, ObjectHandle<SpatialRef>),
|
hmd: (Arc<Spatial>, ObjectHandle<SpatialRef>),
|
||||||
play_space: Option<(Arc<Spatial>, ObjectHandle<SpatialRef>)>,
|
play_space: Option<(Arc<Spatial>, ObjectHandle<SpatialRef>)>,
|
||||||
inputs: Inputs,
|
inputs: Option<Inputs>,
|
||||||
disable_controllers: bool,
|
pub view_space: Option<openxr::Space>,
|
||||||
disable_hands: bool,
|
pub ref_space: Option<openxr::Space>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct TrackingRefs {
|
||||||
|
view_space: openxr::Space,
|
||||||
|
ref_space: openxr::Space,
|
||||||
|
left_controller_space: openxr::Space,
|
||||||
|
right_controller_space: openxr::Space,
|
||||||
|
left_hand_tracker: Option<openxr::HandTracker>,
|
||||||
|
right_hand_tracker: Option<openxr::HandTracker>,
|
||||||
|
}
|
||||||
|
|
||||||
impl ServerObjects {
|
impl ServerObjects {
|
||||||
pub fn new(
|
pub fn new(connection: Connection) -> ServerObjects {
|
||||||
connection: Connection,
|
|
||||||
sk: &Sk,
|
|
||||||
disable_controllers: bool,
|
|
||||||
disable_hands: bool,
|
|
||||||
) -> ServerObjects {
|
|
||||||
let hmd = SpatialRef::create(&connection, "/org/stardustxr/HMD");
|
let hmd = SpatialRef::create(&connection, "/org/stardustxr/HMD");
|
||||||
|
|
||||||
let play_space = (World::has_bounds()
|
// TODO: implement in bevy_mod_openxr
|
||||||
&& World::get_bounds_size().x != 0.0
|
// let play_space = (World::has_bounds()
|
||||||
&& World::get_bounds_size().y != 0.0)
|
// && World::get_bounds_size().x != 0.0
|
||||||
.then(|| SpatialRef::create(&connection, "/org/stardustxr/PlaySpace"));
|
// && World::get_bounds_size().y != 0.0)
|
||||||
if play_space.is_some() {
|
// .then(|| SpatialRef::create(&connection, "/org/stardustxr/PlaySpace"));
|
||||||
let dbus_connection = connection.clone();
|
// let play_space = None;
|
||||||
tokio::task::spawn(async move {
|
// if play_space.is_some() {
|
||||||
PlaySpaceBounds::create(&dbus_connection).await;
|
// let dbus_connection = connection.clone();
|
||||||
dbus_connection
|
// TOKIO.spawn(async move {
|
||||||
.request_name("org.stardustxr.PlaySpace")
|
// PlaySpaceBounds::create(&dbus_connection).await;
|
||||||
.await
|
// dbus_connection
|
||||||
.unwrap();
|
// .request_name("org.stardustxr.PlaySpace")
|
||||||
});
|
// .await
|
||||||
}
|
// .unwrap();
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
tokio::task::spawn({
|
TOKIO.spawn({
|
||||||
let connection = connection.clone();
|
let connection = connection.clone();
|
||||||
async move {
|
async move {
|
||||||
connection
|
connection
|
||||||
@@ -87,100 +103,124 @@ impl ServerObjects {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let inputs = if sk.get_active_display_mode() == DisplayMode::MixedReality {
|
// let inputs = if sk.get_active_display_mode() == DisplayMode::MixedReality {
|
||||||
Inputs::XR {
|
// Inputs::XR {
|
||||||
controller_left: SkController::new(&connection, Handed::Left).unwrap(),
|
// controller_left: SkController::new(&connection, Handed::Left).unwrap(),
|
||||||
controller_right: SkController::new(&connection, Handed::Right).unwrap(),
|
// controller_right: SkController::new(&connection, Handed::Right).unwrap(),
|
||||||
hand_left: SkHand::new(&connection, Handed::Left).unwrap(),
|
// hand_left: SkHand::new(&connection, Handed::Left).unwrap(),
|
||||||
hand_right: SkHand::new(&connection, Handed::Right).unwrap(),
|
// hand_right: SkHand::new(&connection, Handed::Right).unwrap(),
|
||||||
eye_pointer: Device::has_eye_gaze()
|
// // TODO: implement in bevy_mod_openxr
|
||||||
.then(EyePointer::new)
|
// eye_pointer: false.then(EyePointer::new).transpose().unwrap(),
|
||||||
.transpose()
|
// }
|
||||||
.unwrap(),
|
// } else {
|
||||||
}
|
// Inputs::MousePointer(MousePointer::new().unwrap())
|
||||||
} else {
|
// };
|
||||||
Inputs::MousePointer(MousePointer::new().unwrap())
|
|
||||||
};
|
|
||||||
|
|
||||||
ServerObjects {
|
ServerObjects {
|
||||||
connection,
|
connection,
|
||||||
hmd,
|
hmd,
|
||||||
play_space,
|
play_space: None,
|
||||||
inputs,
|
inputs: None,
|
||||||
disable_controllers,
|
ref_space: None,
|
||||||
disable_hands,
|
view_space: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, sk: &Sk, token: &MainThreadToken) {
|
pub(crate) fn update(
|
||||||
let hmd_pose = Input::get_head();
|
&mut self,
|
||||||
self.hmd
|
session: Option<&openxr::Session<openxr::AnyGraphics>>,
|
||||||
.0
|
time: Option<openxr::Time>,
|
||||||
.set_local_transform(Mat4::from_scale_rotation_translation(
|
) {
|
||||||
vec3(1.0, 1.0, 1.0),
|
if let (Some(session), Some(ref_space), Some(time)) =
|
||||||
hmd_pose.orientation.into(),
|
(session, self.ref_space.as_ref(), time)
|
||||||
hmd_pose.position.into(),
|
{
|
||||||
));
|
'hmd: {
|
||||||
|
if let Some(view) = self.view_space.as_ref() {
|
||||||
if let Some(play_space) = self.play_space.as_ref() {
|
let hmd_pose = match view.locate(ref_space, time) {
|
||||||
let pose = World::get_bounds_pose();
|
Ok(v) => v,
|
||||||
play_space
|
Err(err) => {
|
||||||
.0
|
tracing::error!("error while locating hmd: {err}");
|
||||||
.set_local_transform(Mat4::from_rotation_translation(
|
break 'hmd;
|
||||||
pose.orientation.into(),
|
}
|
||||||
pose.position.into(),
|
};
|
||||||
));
|
if hmd_pose.location_flags.contains(
|
||||||
}
|
SpaceLocationFlags::POSITION_TRACKED
|
||||||
|
| SpaceLocationFlags::ORIENTATION_TRACKED,
|
||||||
if sk.get_active_display_mode() != DisplayMode::MixedReality {
|
) {
|
||||||
if Input::key(Key::F6).is_just_inactive() {
|
self.hmd
|
||||||
self.inputs = Inputs::MousePointer(MousePointer::new().unwrap());
|
.0
|
||||||
}
|
.set_local_transform(Mat4::from_scale_rotation_translation(
|
||||||
// if Input::key(Key::F7).is_just_inactive() {
|
vec3(1.0, 1.0, 1.0),
|
||||||
// self.inputs = Inputs::Controllers((
|
hmd_pose.pose.orientation.to_quat(),
|
||||||
// SkController::new(Handed::Left).unwrap(),
|
hmd_pose.pose.position.to_vec3(),
|
||||||
// SkController::new(Handed::Right).unwrap(),
|
));
|
||||||
// ));
|
}
|
||||||
// }
|
}
|
||||||
if Input::key(Key::F8).is_just_inactive() {
|
|
||||||
self.inputs = Inputs::Hands {
|
|
||||||
left: SkHand::new(&self.connection, Handed::Left).unwrap(),
|
|
||||||
right: SkHand::new(&self.connection, Handed::Right).unwrap(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if let Some(play_space) = self.play_space.as_ref() {
|
||||||
|
// let pose = World::get_bounds_pose();
|
||||||
|
// play_space
|
||||||
|
// .0
|
||||||
|
// .set_local_transform(Mat4::from_rotation_translation(
|
||||||
|
// pose.orientation.into(),
|
||||||
|
// pose.position.into(),
|
||||||
|
// ));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if sk.get_active_display_mode() != DisplayMode::MixedReality {
|
||||||
|
// if Input::key(Key::F6).is_just_inactive() {
|
||||||
|
// self.inputs = Inputs::MousePointer(MousePointer::new().unwrap());
|
||||||
|
// }
|
||||||
|
// // if Input::key(Key::F7).is_just_inactive() {
|
||||||
|
// // self.inputs = Inputs::Controllers((
|
||||||
|
// // SkController::new(Handed::Left).unwrap(),
|
||||||
|
// // SkController::new(Handed::Right).unwrap(),
|
||||||
|
// // ));
|
||||||
|
// // }
|
||||||
|
// if Input::key(Key::F8).is_just_inactive() {
|
||||||
|
// self.inputs = Inputs::Hands {
|
||||||
|
// left: SkHand::new(&self.connection, Handed::Left).unwrap(),
|
||||||
|
// right: SkHand::new(&self.connection, Handed::Right).unwrap(),
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
match &mut self.inputs {
|
match &mut self.inputs {
|
||||||
Inputs::XR {
|
Some(Inputs::XR {
|
||||||
controller_left,
|
controller_left,
|
||||||
controller_right,
|
controller_right,
|
||||||
hand_left,
|
hand_left,
|
||||||
hand_right,
|
hand_right,
|
||||||
eye_pointer,
|
eye_pointer,
|
||||||
} => {
|
}) => {
|
||||||
if !self.disable_controllers {
|
// controller_left.update(token);
|
||||||
controller_left.update(token);
|
// controller_right.update(token);
|
||||||
controller_right.update(token);
|
// hand_left.update(sk, token);
|
||||||
}
|
// hand_right.update(sk, token);
|
||||||
if !self.disable_hands {
|
// if let Some(eye_pointer) = eye_pointer {
|
||||||
hand_left.update(sk, token);
|
// eye_pointer.update();
|
||||||
hand_right.update(sk, token);
|
// }
|
||||||
}
|
|
||||||
if let Some(eye_pointer) = eye_pointer {
|
|
||||||
eye_pointer.update();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Inputs::MousePointer(mouse_pointer) => mouse_pointer.update(),
|
Some(Inputs::MousePointer(mouse_pointer)) => {}
|
||||||
// Inputs::Controllers((left, right)) => {
|
// Inputs::Controllers((left, right)) => {
|
||||||
// left.update(token);
|
// left.update(token);
|
||||||
// right.update(token);
|
// right.update(token);
|
||||||
// }
|
// }
|
||||||
Inputs::Hands { left, right } => {
|
Some(Inputs::Hands { left, right }) => {
|
||||||
left.update(sk, token);
|
// left.update(sk, token);
|
||||||
right.update(sk, token);
|
// right.update(sk, token);
|
||||||
}
|
}
|
||||||
|
None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn set_inputs(&mut self, inputs: Inputs) {
|
||||||
|
self.inputs = Some(inputs);
|
||||||
|
}
|
||||||
|
pub fn unset_inputs(&mut self, inputs: Inputs) {
|
||||||
|
self.inputs = None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ObjectHandle<I: Interface>(Connection, OwnedObjectPath, PhantomData<I>);
|
pub struct ObjectHandle<I: Interface>(Connection, OwnedObjectPath, PhantomData<I>);
|
||||||
@@ -188,7 +228,7 @@ impl<I: Interface> Drop for ObjectHandle<I> {
|
|||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
let connection = self.0.clone();
|
let connection = self.0.clone();
|
||||||
let object_path = self.1.clone();
|
let object_path = self.1.clone();
|
||||||
tokio::task::spawn(async move {
|
TOKIO.spawn(async move {
|
||||||
connection.object_server().remove::<I, _>(object_path);
|
connection.object_server().remove::<I, _>(object_path);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -202,7 +242,7 @@ impl SpatialRef {
|
|||||||
let uid: u64 = rand::random();
|
let uid: u64 = rand::random();
|
||||||
EXPORTED_SPATIALS.lock().insert(uid, node.0.clone());
|
EXPORTED_SPATIALS.lock().insert(uid, node.0.clone());
|
||||||
|
|
||||||
tokio::task::spawn({
|
TOKIO.spawn({
|
||||||
let connection = connection.clone();
|
let connection = connection.clone();
|
||||||
let path = path.to_string();
|
let path = path.to_string();
|
||||||
async move {
|
async move {
|
||||||
@@ -244,7 +284,7 @@ impl FieldRef {
|
|||||||
let uid: u64 = rand::random();
|
let uid: u64 = rand::random();
|
||||||
EXPORTED_FIELDS.lock().insert(uid, node.0.clone());
|
EXPORTED_FIELDS.lock().insert(uid, node.0.clone());
|
||||||
|
|
||||||
tokio::task::spawn({
|
TOKIO.spawn({
|
||||||
let connection = connection.clone();
|
let connection = connection.clone();
|
||||||
let path = path.to_string();
|
let path = path.to_string();
|
||||||
async move {
|
async move {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use stereokit_rust::system::World;
|
|
||||||
use zbus::{interface, Connection, ObjectServer};
|
use zbus::{interface, Connection, ObjectServer};
|
||||||
|
|
||||||
pub struct PlaySpaceBounds;
|
pub struct PlaySpaceBounds;
|
||||||
@@ -13,14 +12,16 @@ impl PlaySpaceBounds {
|
|||||||
}
|
}
|
||||||
#[interface(name = "org.stardustxr.PlaySpace")]
|
#[interface(name = "org.stardustxr.PlaySpace")]
|
||||||
impl PlaySpaceBounds {
|
impl PlaySpaceBounds {
|
||||||
|
// TODO: reimplement under bevy
|
||||||
#[zbus(property)]
|
#[zbus(property)]
|
||||||
fn bounds(&self) -> Vec<(f64, f64)> {
|
fn bounds(&self) -> Vec<(f64, f64)> {
|
||||||
let bounds = World::get_bounds_size();
|
// let bounds = World::get_bounds_size();
|
||||||
vec![
|
// vec![
|
||||||
((bounds.x).into(), (bounds.y).into()),
|
// ((bounds.x).into(), (bounds.y).into()),
|
||||||
((bounds.x).into(), (-bounds.y).into()),
|
// ((bounds.x).into(), (-bounds.y).into()),
|
||||||
((-bounds.x).into(), (-bounds.y).into()),
|
// ((-bounds.x).into(), (-bounds.y).into()),
|
||||||
((-bounds.x).into(), (bounds.y).into()),
|
// ((-bounds.x).into(), (bounds.y).into()),
|
||||||
]
|
// ]
|
||||||
|
vec![]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
77
src/oxr_render_plugin.rs
Normal file
77
src/oxr_render_plugin.rs
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
use bevy::{
|
||||||
|
app::{App, Plugin, PostUpdate},
|
||||||
|
prelude::{resource_added, IntoSystemConfigs as _, TransformSystem},
|
||||||
|
render::{
|
||||||
|
extract_resource::ExtractResourcePlugin, pipelined_rendering::PipelinedRenderingPlugin,
|
||||||
|
Render, RenderApp,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use bevy_mod_openxr::{
|
||||||
|
init::should_run_frame_loop,
|
||||||
|
layer_builder::ProjectionLayer,
|
||||||
|
render::{
|
||||||
|
begin_frame, clean_views, end_frame, init_views, insert_texture_views, locate_views,
|
||||||
|
release_image, update_views, update_views_render_world, wait_image,
|
||||||
|
},
|
||||||
|
resources::{
|
||||||
|
OxrFrameState, OxrGraphicsInfo, OxrRenderLayers, OxrSwapchainImages, OxrViews, Pipelined,
|
||||||
|
},
|
||||||
|
session::OxrSession,
|
||||||
|
};
|
||||||
|
use bevy_mod_xr::session::{XrPreDestroySession, XrRenderSet, XrSessionCreated};
|
||||||
|
|
||||||
|
pub struct StardustOxrRenderPlugin;
|
||||||
|
|
||||||
|
impl Plugin for StardustOxrRenderPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
if app.is_plugin_added::<PipelinedRenderingPlugin>() {
|
||||||
|
app.init_resource::<Pipelined>();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.add_plugins((
|
||||||
|
ExtractResourcePlugin::<OxrFrameState>::default(),
|
||||||
|
ExtractResourcePlugin::<OxrGraphicsInfo>::default(),
|
||||||
|
ExtractResourcePlugin::<OxrSwapchainImages>::default(),
|
||||||
|
ExtractResourcePlugin::<OxrViews>::default(),
|
||||||
|
))
|
||||||
|
.add_systems(XrPreDestroySession, clean_views)
|
||||||
|
.add_systems(
|
||||||
|
XrSessionCreated,
|
||||||
|
init_views.run_if(resource_added::<OxrSession>),
|
||||||
|
)
|
||||||
|
.add_systems(
|
||||||
|
PostUpdate,
|
||||||
|
(locate_views, update_views)
|
||||||
|
.before(TransformSystem::TransformPropagate)
|
||||||
|
.chain()
|
||||||
|
.run_if(should_run_frame_loop),
|
||||||
|
)
|
||||||
|
.init_resource::<OxrViews>();
|
||||||
|
|
||||||
|
let render_app = app.sub_app_mut(RenderApp);
|
||||||
|
|
||||||
|
render_app
|
||||||
|
.add_systems(XrPreDestroySession, clean_views)
|
||||||
|
.add_systems(
|
||||||
|
Render,
|
||||||
|
(
|
||||||
|
begin_frame,
|
||||||
|
insert_texture_views,
|
||||||
|
locate_views,
|
||||||
|
update_views_render_world,
|
||||||
|
wait_image,
|
||||||
|
)
|
||||||
|
.chain()
|
||||||
|
.in_set(XrRenderSet::PreRender)
|
||||||
|
.run_if(should_run_frame_loop),
|
||||||
|
)
|
||||||
|
.add_systems(
|
||||||
|
Render,
|
||||||
|
(release_image, end_frame)
|
||||||
|
.chain()
|
||||||
|
.run_if(should_run_frame_loop)
|
||||||
|
.in_set(XrRenderSet::PostRender),
|
||||||
|
)
|
||||||
|
.insert_resource(OxrRenderLayers(vec![Box::new(ProjectionLayer)]));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -87,13 +87,12 @@ impl UnownedFd {
|
|||||||
}
|
}
|
||||||
impl Drop for UnownedFd {
|
impl Drop for UnownedFd {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.0.take().unwrap().into_inner().into_raw_fd();
|
let _ = self.0.take().unwrap().into_inner().into_raw_fd();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Wayland {
|
pub struct Wayland {
|
||||||
display: Arc<DisplayWrapper>,
|
display: Arc<DisplayWrapper>,
|
||||||
pub socket_name: Option<String>,
|
|
||||||
join_handle: JoinHandle<Result<()>>,
|
join_handle: JoinHandle<Result<()>>,
|
||||||
renderer: GlesRenderer,
|
renderer: GlesRenderer,
|
||||||
output: Output,
|
output: Output,
|
||||||
@@ -133,7 +132,6 @@ impl Wayland {
|
|||||||
|
|
||||||
Ok(Wayland {
|
Ok(Wayland {
|
||||||
display,
|
display,
|
||||||
socket_name,
|
|
||||||
join_handle,
|
join_handle,
|
||||||
renderer,
|
renderer,
|
||||||
output,
|
output,
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
use super::{state::WaylandState, surface::CoreSurface, utils::WlSurfaceExt};
|
use super::{state::WaylandState, surface::CoreSurface, utils::WlSurfaceExt};
|
||||||
use crate::{
|
use crate::{
|
||||||
core::task,
|
core::task,
|
||||||
nodes::{
|
nodes::items::panel::{Backend, Geometry, PanelItem, KEYMAPS},
|
||||||
data::KEYMAPS,
|
|
||||||
items::panel::{Backend, Geometry, PanelItem},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use mint::Vector2;
|
use mint::Vector2;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
@@ -203,7 +200,7 @@ impl SeatWrapper {
|
|||||||
pointer.frame(&mut state);
|
pointer.frame(&mut state);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn keyboard_keys(&self, surface: WlSurface, keymap_id: u64, keys: Vec<i32>) {
|
pub fn keyboard_key(&self, surface: WlSurface, keymap_id: u64, key: u32, pressed: bool) {
|
||||||
let Some(state) = self.wayland_state.upgrade() else {
|
let Some(state) = self.wayland_state.upgrade() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -226,20 +223,18 @@ impl SeatWrapper {
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for key in keys {
|
keyboard.input(
|
||||||
keyboard.input(
|
&mut state.lock(),
|
||||||
&mut state.lock(),
|
key,
|
||||||
key.unsigned_abs(),
|
if pressed {
|
||||||
if key > 0 {
|
KeyState::Pressed
|
||||||
KeyState::Pressed
|
} else {
|
||||||
} else {
|
KeyState::Released
|
||||||
KeyState::Released
|
},
|
||||||
},
|
SERIAL_COUNTER.next_serial(),
|
||||||
SERIAL_COUNTER.next_serial(),
|
0,
|
||||||
0,
|
|_, _, _| FilterResult::Forward::<()>,
|
||||||
|_, _, _| FilterResult::Forward::<()>,
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn touch_down(&self, surface: WlSurface, id: u32, position: Vector2<f32>) {
|
pub fn touch_down(&self, surface: WlSurface, id: u32, position: Vector2<f32>) {
|
||||||
|
|||||||
@@ -4,13 +4,16 @@ use super::{
|
|||||||
surface::CoreSurface,
|
surface::CoreSurface,
|
||||||
utils::*,
|
utils::*,
|
||||||
};
|
};
|
||||||
use crate::nodes::{
|
use crate::{
|
||||||
drawable::model::ModelPart,
|
core::error::Result,
|
||||||
items::panel::{
|
nodes::{
|
||||||
Backend, ChildInfo, Geometry, PanelItem, PanelItemInitData, SurfaceId, ToplevelInfo,
|
drawable::model::ModelPart,
|
||||||
|
items::panel::{
|
||||||
|
Backend, ChildInfo, Geometry, PanelItem, PanelItemInitData, SurfaceId, ToplevelInfo,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use color_eyre::eyre::{eyre, Result};
|
use color_eyre::eyre::eyre;
|
||||||
use mint::Vector2;
|
use mint::Vector2;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
@@ -515,11 +518,11 @@ impl Backend for XdgBackend {
|
|||||||
self.seat.pointer_scroll(scroll_distance, scroll_steps)
|
self.seat.pointer_scroll(scroll_distance, scroll_steps)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keyboard_keys(&self, surface: &SurfaceId, keymap_id: u64, keys: Vec<i32>) {
|
fn keyboard_key(&self, surface: &SurfaceId, keymap_id: u64, key: u32, pressed: bool) {
|
||||||
let Some(surface) = self.wl_surface_from_id(surface) else {
|
let Some(surface) = self.wl_surface_from_id(surface) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
self.seat.keyboard_keys(surface, keymap_id, keys)
|
self.seat.keyboard_key(surface, keymap_id, key, pressed)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn touch_down(&self, surface: &SurfaceId, id: u32, position: Vector2<f32>) {
|
fn touch_down(&self, surface: &SurfaceId, id: u32, position: Vector2<f32>) {
|
||||||
|
|||||||
Reference in New Issue
Block a user