more webxr support
This commit is contained in:
@@ -12,12 +12,12 @@ linked = ["openxr/linked"]
|
|||||||
vulkan = ["dep:ash"]
|
vulkan = ["dep:ash"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
xr_api = { path = "./xr_api" }
|
|
||||||
async-std = "1.12.0"
|
async-std = "1.12.0"
|
||||||
bevy = "0.12.1"
|
bevy = "0.12.1"
|
||||||
paste = "1.0.14"
|
paste = "1.0.14"
|
||||||
wgpu = "0.17.1"
|
wgpu = "0.17.1"
|
||||||
wgpu-hal = "0.17.1"
|
wgpu-hal = "0.17.1"
|
||||||
|
winit = "0.28.7"
|
||||||
|
|
||||||
[target.'cfg(target_family = "unix")'.dependencies]
|
[target.'cfg(target_family = "unix")'.dependencies]
|
||||||
openxr = { version = "0.17.1", features = ["mint"] }
|
openxr = { version = "0.17.1", features = ["mint"] }
|
||||||
@@ -29,8 +29,9 @@ openxr = { version = "0.17.1", features = ["mint", "static"] }
|
|||||||
ash = { version = "0.37.3", optional = true }
|
ash = { version = "0.37.3", optional = true }
|
||||||
|
|
||||||
[target.'cfg(target_family = "wasm")'.dependencies]
|
[target.'cfg(target_family = "wasm")'.dependencies]
|
||||||
wasm-bindgen = "0.2.87"
|
js-sys = "0.3"
|
||||||
web-sys = { version = "0.3.61", features = [
|
wasm-bindgen = "0.2.91"
|
||||||
|
web-sys = { version = "0.3.68", features = [
|
||||||
# STANDARD
|
# STANDARD
|
||||||
'console',
|
'console',
|
||||||
'Document',
|
'Document',
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
//! A simple 3D scene with light shining over a cube sitting on a plane.
|
//! A simple 3D scene with light shining over a cube sitting on a plane.
|
||||||
|
|
||||||
use bevy::{
|
use bevy::prelude::*;
|
||||||
core_pipeline::clear_color::ClearColorConfig, prelude::*, render::camera::RenderTarget,
|
use bevy_oxr::webxr::XrInitPlugin;
|
||||||
};
|
|
||||||
use bevy_oxr::{DefaultXrPlugins, LEFT_XR_TEXTURE_HANDLE};
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins(DefaultXrPlugins)
|
.add_plugins((DefaultPlugins, XrInitPlugin))
|
||||||
.add_systems(Startup, setup)
|
.add_systems(Startup, setup)
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
142
src/actions.rs
Normal file
142
src/actions.rs
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
pub mod oculus_touch;
|
||||||
|
|
||||||
|
mod private {
|
||||||
|
use bevy::math::Vec2;
|
||||||
|
|
||||||
|
use crate::types::{Haptic, Pose};
|
||||||
|
|
||||||
|
pub trait Sealed {}
|
||||||
|
|
||||||
|
impl Sealed for bool {}
|
||||||
|
impl Sealed for f32 {}
|
||||||
|
impl Sealed for Vec2 {}
|
||||||
|
impl Sealed for Pose {}
|
||||||
|
impl Sealed for Haptic {}
|
||||||
|
}
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
pub trait ActionType: private::Sealed {}
|
||||||
|
|
||||||
|
impl<T: private::Sealed> ActionType for T {}
|
||||||
|
|
||||||
|
pub trait ActionPathTrait {
|
||||||
|
type PathType: ActionType;
|
||||||
|
fn path(&self) -> Cow<'_, str>;
|
||||||
|
fn name(&self) -> Cow<'_, str>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ActionPath<T: ActionType> {
|
||||||
|
path: &'static str,
|
||||||
|
name: &'static str,
|
||||||
|
_marker: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ActionType> ActionPathTrait for ActionPath<T> {
|
||||||
|
type PathType = T;
|
||||||
|
|
||||||
|
fn path(&self) -> Cow<'_, str> {
|
||||||
|
self.path.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> Cow<'_, str> {
|
||||||
|
self.name.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! actions {
|
||||||
|
// create path struct
|
||||||
|
(
|
||||||
|
$($subpath:literal),*
|
||||||
|
$id:ident {
|
||||||
|
path: $path:literal;
|
||||||
|
}
|
||||||
|
) => {};
|
||||||
|
|
||||||
|
// handle action path attrs
|
||||||
|
(
|
||||||
|
$($subpath:literal),*
|
||||||
|
$id:ident {
|
||||||
|
path: $path:literal;
|
||||||
|
name: $name:literal;
|
||||||
|
path_type: $path_type:ty;
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
paste::paste! {
|
||||||
|
pub const [<$id:snake:upper>]: crate::actions::ActionPath<$path_type> = crate::actions::ActionPath {
|
||||||
|
path: concat!($($subpath,)* $path),
|
||||||
|
name: $name,
|
||||||
|
_marker: std::marker::PhantomData,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// handle action path attrs
|
||||||
|
(
|
||||||
|
$($subpath:literal),*
|
||||||
|
$id:ident {
|
||||||
|
path: $path:literal;
|
||||||
|
name: $name:literal;
|
||||||
|
path_type: $path_type:ty;
|
||||||
|
$($children:tt)*
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
crate::path::actions! {
|
||||||
|
$($subpath),*
|
||||||
|
$id {
|
||||||
|
path: $path;
|
||||||
|
name: $name;
|
||||||
|
path_type: $path_type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
crate::path::actions! {
|
||||||
|
$($subpath),*
|
||||||
|
$id {
|
||||||
|
path: $path;
|
||||||
|
$($children)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// handle children
|
||||||
|
(
|
||||||
|
$($subpath:literal),*
|
||||||
|
$id:ident {
|
||||||
|
path: $path:literal;
|
||||||
|
$($children:tt)*
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
pub mod $id {
|
||||||
|
crate::actions::actions! {
|
||||||
|
$($subpath,)* $path
|
||||||
|
$($children)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// handle siblings
|
||||||
|
(
|
||||||
|
$($subpath:literal),*
|
||||||
|
$id:ident {
|
||||||
|
path: $path:literal;
|
||||||
|
$($attrs:tt)*
|
||||||
|
}
|
||||||
|
$($siblings:tt)*
|
||||||
|
) => {
|
||||||
|
crate::actions::actions! {
|
||||||
|
$($subpath),*
|
||||||
|
$id {
|
||||||
|
path: $path;
|
||||||
|
$($attrs)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
crate::actions::actions! {
|
||||||
|
$($subpath),*
|
||||||
|
$($siblings)*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) use actions;
|
||||||
238
src/actions/oculus_touch.rs
Normal file
238
src/actions/oculus_touch.rs
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
super::actions! {
|
||||||
|
"/user"
|
||||||
|
hand {
|
||||||
|
path: "/hand";
|
||||||
|
left {
|
||||||
|
path: "/left";
|
||||||
|
input {
|
||||||
|
path: "/input";
|
||||||
|
x {
|
||||||
|
path: "/x";
|
||||||
|
click {
|
||||||
|
path: "/click";
|
||||||
|
name: "x_click";
|
||||||
|
path_type: bool;
|
||||||
|
}
|
||||||
|
touch {
|
||||||
|
path: "/touch";
|
||||||
|
name: "x_touch";
|
||||||
|
path_type: bool;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
y {
|
||||||
|
path: "/y";
|
||||||
|
click {
|
||||||
|
path: "/click";
|
||||||
|
name: "y_click";
|
||||||
|
path_type: bool;
|
||||||
|
}
|
||||||
|
touch {
|
||||||
|
path: "/touch";
|
||||||
|
name: "y_touch";
|
||||||
|
path_type: bool;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
menu {
|
||||||
|
path: "/menu";
|
||||||
|
click {
|
||||||
|
path: "/click";
|
||||||
|
name: "menu_click";
|
||||||
|
path_type: bool;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
squeeze {
|
||||||
|
path: "/squeeze";
|
||||||
|
value {
|
||||||
|
path: "/value";
|
||||||
|
name: "left_grip_val";
|
||||||
|
path_type: f32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trigger {
|
||||||
|
path: "/trigger";
|
||||||
|
value {
|
||||||
|
path: "/value";
|
||||||
|
name: "left_trigger_val";
|
||||||
|
path_type: f32;
|
||||||
|
}
|
||||||
|
touch {
|
||||||
|
path: "/touch";
|
||||||
|
name: "left_trigger_touch";
|
||||||
|
path_type: bool;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thumbstick {
|
||||||
|
path: "/thumbstick";
|
||||||
|
x {
|
||||||
|
path: "/x";
|
||||||
|
name: "left_thumbstick_x";
|
||||||
|
path_type: f32;
|
||||||
|
}
|
||||||
|
y {
|
||||||
|
path: "/y";
|
||||||
|
name: "left_thumbstick_y";
|
||||||
|
path_type: f32;
|
||||||
|
}
|
||||||
|
click {
|
||||||
|
path: "/click";
|
||||||
|
name: "left_thumbstick_click";
|
||||||
|
path_type: bool;
|
||||||
|
}
|
||||||
|
touch {
|
||||||
|
path: "/touch";
|
||||||
|
name: "left_thumbstick_touch";
|
||||||
|
path_type: bool;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thumbrest {
|
||||||
|
path: "/thumbrest";
|
||||||
|
touch {
|
||||||
|
path: "/touch";
|
||||||
|
name: "left_thumbrest_touch";
|
||||||
|
path_type: bool;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
grip {
|
||||||
|
path: "/grip";
|
||||||
|
pose {
|
||||||
|
path: "/pose";
|
||||||
|
name: "left_grip_pose";
|
||||||
|
path_type: crate::types::Pose;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
aim {
|
||||||
|
path: "/aim";
|
||||||
|
pose {
|
||||||
|
path: "/pose";
|
||||||
|
name: "left_aim_pose";
|
||||||
|
path_type: crate::types::Pose;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output {
|
||||||
|
path: "/output";
|
||||||
|
haptic {
|
||||||
|
path: "/haptic";
|
||||||
|
name: "left_controller_haptic";
|
||||||
|
path_type: crate::types::Haptic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
right {
|
||||||
|
path: "/right";
|
||||||
|
input {
|
||||||
|
path: "/input";
|
||||||
|
a {
|
||||||
|
path: "/a";
|
||||||
|
click {
|
||||||
|
path: "/click";
|
||||||
|
name: "a_click";
|
||||||
|
path_type: bool;
|
||||||
|
}
|
||||||
|
touch {
|
||||||
|
path: "/touch";
|
||||||
|
name: "a_touch";
|
||||||
|
path_type: bool;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b {
|
||||||
|
path: "/b";
|
||||||
|
click {
|
||||||
|
path: "/click";
|
||||||
|
name: "b_click";
|
||||||
|
path_type: bool;
|
||||||
|
}
|
||||||
|
touch {
|
||||||
|
path: "/touch";
|
||||||
|
name: "b_touch";
|
||||||
|
path_type: bool;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
system {
|
||||||
|
path: "/system";
|
||||||
|
click {
|
||||||
|
path: "/click";
|
||||||
|
name: "system_click";
|
||||||
|
path_type: bool;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
squeeze {
|
||||||
|
path: "/squeeze";
|
||||||
|
value {
|
||||||
|
path: "/value";
|
||||||
|
name: "right_grip_val";
|
||||||
|
path_type: f32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trigger {
|
||||||
|
path: "/trigger";
|
||||||
|
value {
|
||||||
|
path: "/value";
|
||||||
|
name: "right_trigger_val";
|
||||||
|
path_type: f32;
|
||||||
|
}
|
||||||
|
touch {
|
||||||
|
path: "/touch";
|
||||||
|
name: "right_trigger_touch";
|
||||||
|
path_type: bool;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thumbstick {
|
||||||
|
path: "/thumbstick";
|
||||||
|
x {
|
||||||
|
path: "/x";
|
||||||
|
name: "right_thumbstick_x";
|
||||||
|
path_type: f32;
|
||||||
|
}
|
||||||
|
y {
|
||||||
|
path: "/y";
|
||||||
|
name: "right_thumbstick_y";
|
||||||
|
path_type: f32;
|
||||||
|
}
|
||||||
|
click {
|
||||||
|
path: "/click";
|
||||||
|
name: "right_thumbstick_click";
|
||||||
|
path_type: bool;
|
||||||
|
}
|
||||||
|
touch {
|
||||||
|
path: "/touch";
|
||||||
|
name: "right_thumbstick_touch";
|
||||||
|
path_type: bool;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thumbrest {
|
||||||
|
path: "/thumbrest";
|
||||||
|
touch {
|
||||||
|
path: "/touch";
|
||||||
|
name: "right_thumbrest_touch";
|
||||||
|
path_type: bool;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
grip {
|
||||||
|
path: "/grip";
|
||||||
|
pose {
|
||||||
|
path: "/pose";
|
||||||
|
name: "right_grip_pose";
|
||||||
|
path_type: crate::types::Pose;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
aim {
|
||||||
|
path: "/aim";
|
||||||
|
pose {
|
||||||
|
path: "/pose";
|
||||||
|
name: "right_aim_pose";
|
||||||
|
path_type: crate::types::Pose;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output {
|
||||||
|
path: "/output";
|
||||||
|
haptic {
|
||||||
|
path: "/haptic";
|
||||||
|
name: "right_controller_haptic";
|
||||||
|
path_type: crate::types::Haptic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
370
src/lib.rs
370
src/lib.rs
@@ -1,366 +1,4 @@
|
|||||||
use std::sync::Arc;
|
pub mod actions;
|
||||||
|
pub mod types;
|
||||||
use bevy::{
|
#[cfg(target_family = "wasm")]
|
||||||
app::PluginGroupBuilder,
|
pub mod webxr;
|
||||||
core_pipeline::tonemapping::{DebandDither, Tonemapping},
|
|
||||||
math::Vec3A,
|
|
||||||
prelude::*,
|
|
||||||
render::{
|
|
||||||
camera::{
|
|
||||||
CameraProjection, CameraProjectionPlugin, CameraRenderGraph, ManualTextureView,
|
|
||||||
ManualTextureViewHandle, ManualTextureViews, RenderTarget,
|
|
||||||
},
|
|
||||||
pipelined_rendering::PipelinedRenderingPlugin,
|
|
||||||
primitives::Frustum,
|
|
||||||
renderer::{render_system, RenderAdapter, RenderAdapterInfo, RenderInstance, RenderQueue},
|
|
||||||
view::{ColorGrading, VisibleEntities},
|
|
||||||
Render, RenderApp, RenderPlugin,
|
|
||||||
},
|
|
||||||
window::PresentMode,
|
|
||||||
};
|
|
||||||
use xr_api::prelude::*;
|
|
||||||
|
|
||||||
pub const LEFT_XR_TEXTURE_HANDLE: ManualTextureViewHandle = ManualTextureViewHandle(1208214591);
|
|
||||||
pub const RIGHT_XR_TEXTURE_HANDLE: ManualTextureViewHandle = ManualTextureViewHandle(3383858418);
|
|
||||||
|
|
||||||
pub struct XrPlugin;
|
|
||||||
|
|
||||||
impl Plugin for XrPlugin {
|
|
||||||
fn build(&self, app: &mut App) {
|
|
||||||
let instance = Entry::new()
|
|
||||||
.create_instance(ExtensionSet { vulkan: true })
|
|
||||||
.unwrap();
|
|
||||||
let session = instance
|
|
||||||
.create_session(SessionCreateInfo {
|
|
||||||
texture_format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let (device, queue, adapter_info, adapter, instance) =
|
|
||||||
session.get_render_resources().unwrap();
|
|
||||||
|
|
||||||
let input = session.create_input(Bindings::OculusTouch).unwrap();
|
|
||||||
|
|
||||||
let left_primary_button = input
|
|
||||||
.create_action(input::hand_left::PrimaryButton::CLICK)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let left_hand_pose = input.create_action(input::hand_left::Grip::POSE).unwrap();
|
|
||||||
|
|
||||||
app.insert_non_send_resource(left_primary_button);
|
|
||||||
app.insert_non_send_resource(left_hand_pose);
|
|
||||||
app.insert_non_send_resource(session.clone());
|
|
||||||
app.add_plugins((
|
|
||||||
RenderPlugin {
|
|
||||||
render_creation: bevy::render::settings::RenderCreation::Manual(
|
|
||||||
device.into(),
|
|
||||||
RenderQueue(Arc::new(queue)),
|
|
||||||
RenderAdapterInfo(adapter_info),
|
|
||||||
RenderAdapter(Arc::new(adapter)),
|
|
||||||
RenderInstance(Arc::new(instance)),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
CameraProjectionPlugin::<XRProjection>::default(),
|
|
||||||
));
|
|
||||||
|
|
||||||
app.add_systems(PreUpdate, begin_frame);
|
|
||||||
app.add_systems(Last, locate_views);
|
|
||||||
app.add_systems(Startup, setup);
|
|
||||||
let render_app = app.sub_app_mut(RenderApp);
|
|
||||||
render_app.insert_non_send_resource(session);
|
|
||||||
render_app.add_systems(Render, end_frame.after(render_system));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Bundle)]
|
|
||||||
pub struct XrCameraBundle {
|
|
||||||
pub camera: Camera,
|
|
||||||
pub camera_render_graph: CameraRenderGraph,
|
|
||||||
pub xr_projection: PerspectiveProjection,
|
|
||||||
pub visible_entities: VisibleEntities,
|
|
||||||
pub frustum: Frustum,
|
|
||||||
pub transform: Transform,
|
|
||||||
pub global_transform: GlobalTransform,
|
|
||||||
pub camera_3d: Camera3d,
|
|
||||||
pub tonemapping: Tonemapping,
|
|
||||||
pub dither: DebandDither,
|
|
||||||
pub color_grading: ColorGrading,
|
|
||||||
pub xr_camera_type: XrCameraType,
|
|
||||||
}
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Component)]
|
|
||||||
pub enum XrCameraType {
|
|
||||||
Xr(Eye),
|
|
||||||
Flatscreen,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
|
||||||
pub enum Eye {
|
|
||||||
Left = 0,
|
|
||||||
Right = 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl XrCameraBundle {
|
|
||||||
pub fn new(eye: Eye) -> Self {
|
|
||||||
Self {
|
|
||||||
camera: Camera {
|
|
||||||
order: -1,
|
|
||||||
target: RenderTarget::TextureView(match eye {
|
|
||||||
Eye::Left => LEFT_XR_TEXTURE_HANDLE,
|
|
||||||
Eye::Right => RIGHT_XR_TEXTURE_HANDLE,
|
|
||||||
}),
|
|
||||||
viewport: None,
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
camera_render_graph: CameraRenderGraph::new(bevy::core_pipeline::core_3d::graph::NAME),
|
|
||||||
xr_projection: Default::default(),
|
|
||||||
visible_entities: Default::default(),
|
|
||||||
frustum: Default::default(),
|
|
||||||
transform: Default::default(),
|
|
||||||
global_transform: Default::default(),
|
|
||||||
camera_3d: Default::default(),
|
|
||||||
tonemapping: Default::default(),
|
|
||||||
dither: DebandDither::Enabled,
|
|
||||||
color_grading: Default::default(),
|
|
||||||
xr_camera_type: XrCameraType::Xr(eye),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Component, Reflect)]
|
|
||||||
#[reflect(Component, Default)]
|
|
||||||
pub struct XRProjection {
|
|
||||||
pub near: f32,
|
|
||||||
pub far: f32,
|
|
||||||
#[reflect(ignore)]
|
|
||||||
pub fov: Fov,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for XRProjection {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
near: 0.1,
|
|
||||||
far: 1000.,
|
|
||||||
fov: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CameraProjection for XRProjection {
|
|
||||||
// =============================================================================
|
|
||||||
// math code adapted from
|
|
||||||
// https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/master/src/common/xr_linear.h
|
|
||||||
// Copyright (c) 2017 The Khronos Group Inc.
|
|
||||||
// Copyright (c) 2016 Oculus VR, LLC.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
// =============================================================================
|
|
||||||
fn get_projection_matrix(&self) -> Mat4 {
|
|
||||||
// symmetric perspective for debugging
|
|
||||||
// let x_fov = (self.fov.angle_left.abs() + self.fov.angle_right.abs());
|
|
||||||
// let y_fov = (self.fov.angle_up.abs() + self.fov.angle_down.abs());
|
|
||||||
// return Mat4::perspective_infinite_reverse_rh(y_fov, x_fov / y_fov, self.near);
|
|
||||||
|
|
||||||
let fov = self.fov;
|
|
||||||
let is_vulkan_api = false; // FIXME wgpu probably abstracts this
|
|
||||||
let near_z = self.near;
|
|
||||||
let far_z = -1.; // use infinite proj
|
|
||||||
// let far_z = self.far;
|
|
||||||
|
|
||||||
let tan_angle_left = fov.angle_left.tan();
|
|
||||||
let tan_angle_right = fov.angle_right.tan();
|
|
||||||
|
|
||||||
let tan_angle_down = fov.angle_down.tan();
|
|
||||||
let tan_angle_up = fov.angle_up.tan();
|
|
||||||
|
|
||||||
let tan_angle_width = tan_angle_right - tan_angle_left;
|
|
||||||
|
|
||||||
// Set to tanAngleDown - tanAngleUp for a clip space with positive Y
|
|
||||||
// down (Vulkan). Set to tanAngleUp - tanAngleDown for a clip space with
|
|
||||||
// positive Y up (OpenGL / D3D / Metal).
|
|
||||||
// const float tanAngleHeight =
|
|
||||||
// graphicsApi == GRAPHICS_VULKAN ? (tanAngleDown - tanAngleUp) : (tanAngleUp - tanAngleDown);
|
|
||||||
let tan_angle_height = if is_vulkan_api {
|
|
||||||
tan_angle_down - tan_angle_up
|
|
||||||
} else {
|
|
||||||
tan_angle_up - tan_angle_down
|
|
||||||
};
|
|
||||||
|
|
||||||
// Set to nearZ for a [-1,1] Z clip space (OpenGL / OpenGL ES).
|
|
||||||
// Set to zero for a [0,1] Z clip space (Vulkan / D3D / Metal).
|
|
||||||
// const float offsetZ =
|
|
||||||
// (graphicsApi == GRAPHICS_OPENGL || graphicsApi == GRAPHICS_OPENGL_ES) ? nearZ : 0;
|
|
||||||
// FIXME handle enum of graphics apis
|
|
||||||
let offset_z = 0.;
|
|
||||||
|
|
||||||
let mut cols: [f32; 16] = [0.0; 16];
|
|
||||||
|
|
||||||
if far_z <= near_z {
|
|
||||||
// place the far plane at infinity
|
|
||||||
cols[0] = 2. / tan_angle_width;
|
|
||||||
cols[4] = 0.;
|
|
||||||
cols[8] = (tan_angle_right + tan_angle_left) / tan_angle_width;
|
|
||||||
cols[12] = 0.;
|
|
||||||
|
|
||||||
cols[1] = 0.;
|
|
||||||
cols[5] = 2. / tan_angle_height;
|
|
||||||
cols[9] = (tan_angle_up + tan_angle_down) / tan_angle_height;
|
|
||||||
cols[13] = 0.;
|
|
||||||
|
|
||||||
cols[2] = 0.;
|
|
||||||
cols[6] = 0.;
|
|
||||||
cols[10] = -1.;
|
|
||||||
cols[14] = -(near_z + offset_z);
|
|
||||||
|
|
||||||
cols[3] = 0.;
|
|
||||||
cols[7] = 0.;
|
|
||||||
cols[11] = -1.;
|
|
||||||
cols[15] = 0.;
|
|
||||||
|
|
||||||
// bevy uses the _reverse_ infinite projection
|
|
||||||
// https://dev.theomader.com/depth-precision/
|
|
||||||
let z_reversal = Mat4::from_cols_array_2d(&[
|
|
||||||
[1f32, 0., 0., 0.],
|
|
||||||
[0., 1., 0., 0.],
|
|
||||||
[0., 0., -1., 0.],
|
|
||||||
[0., 0., 1., 1.],
|
|
||||||
]);
|
|
||||||
|
|
||||||
return z_reversal * Mat4::from_cols_array(&cols);
|
|
||||||
} else {
|
|
||||||
// normal projection
|
|
||||||
cols[0] = 2. / tan_angle_width;
|
|
||||||
cols[4] = 0.;
|
|
||||||
cols[8] = (tan_angle_right + tan_angle_left) / tan_angle_width;
|
|
||||||
cols[12] = 0.;
|
|
||||||
|
|
||||||
cols[1] = 0.;
|
|
||||||
cols[5] = 2. / tan_angle_height;
|
|
||||||
cols[9] = (tan_angle_up + tan_angle_down) / tan_angle_height;
|
|
||||||
cols[13] = 0.;
|
|
||||||
|
|
||||||
cols[2] = 0.;
|
|
||||||
cols[6] = 0.;
|
|
||||||
cols[10] = -(far_z + offset_z) / (far_z - near_z);
|
|
||||||
cols[14] = -(far_z * (near_z + offset_z)) / (far_z - near_z);
|
|
||||||
|
|
||||||
cols[3] = 0.;
|
|
||||||
cols[7] = 0.;
|
|
||||||
cols[11] = -1.;
|
|
||||||
cols[15] = 0.;
|
|
||||||
}
|
|
||||||
|
|
||||||
Mat4::from_cols_array(&cols)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, _width: f32, _height: f32) {}
|
|
||||||
|
|
||||||
fn far(&self) -> f32 {
|
|
||||||
self.far
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_frustum_corners(&self, z_near: f32, z_far: f32) -> [Vec3A; 8] {
|
|
||||||
let tan_angle_left = self.fov.angle_left.tan();
|
|
||||||
let tan_angle_right = self.fov.angle_right.tan();
|
|
||||||
|
|
||||||
let tan_angle_bottom = self.fov.angle_down.tan();
|
|
||||||
let tan_angle_top = self.fov.angle_up.tan();
|
|
||||||
|
|
||||||
// NOTE: These vertices are in the specific order required by [`calculate_cascade`].
|
|
||||||
[
|
|
||||||
Vec3A::new(tan_angle_right, tan_angle_bottom, 1.0) * z_near, // bottom right
|
|
||||||
Vec3A::new(tan_angle_right, tan_angle_top, 1.0) * z_near, // top right
|
|
||||||
Vec3A::new(tan_angle_left, tan_angle_top, 1.0) * z_near, // top left
|
|
||||||
Vec3A::new(tan_angle_left, tan_angle_bottom, 1.0) * z_near, // bottom left
|
|
||||||
Vec3A::new(tan_angle_right, tan_angle_bottom, 1.0) * z_far, // bottom right
|
|
||||||
Vec3A::new(tan_angle_right, tan_angle_top, 1.0) * z_far, // top right
|
|
||||||
Vec3A::new(tan_angle_left, tan_angle_top, 1.0) * z_far, // top left
|
|
||||||
Vec3A::new(tan_angle_left, tan_angle_bottom, 1.0) * z_far, // bottom left
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Resource)]
|
|
||||||
struct Cameras(Entity, Entity);
|
|
||||||
|
|
||||||
fn setup(mut commands: Commands) {
|
|
||||||
let left = commands.spawn(XrCameraBundle::new(Eye::Left)).id();
|
|
||||||
let right = commands.spawn(XrCameraBundle::new(Eye::Right)).id();
|
|
||||||
commands.insert_resource(Cameras(left, right));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn begin_frame(session: NonSend<Session>, action: NonSend<Action<Pose>>) {
|
|
||||||
session.begin_frame().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn locate_views(
|
|
||||||
session: NonSend<Session>,
|
|
||||||
mut manual_texture_views: ResMut<ManualTextureViews>,
|
|
||||||
cameras: Res<Cameras>,
|
|
||||||
mut transforms: Query<(&mut Transform)>,
|
|
||||||
) {
|
|
||||||
let (left_view, right_view) = session.locate_views().unwrap();
|
|
||||||
|
|
||||||
let left = ManualTextureView {
|
|
||||||
texture_view: left_view.texture_view().unwrap().into(),
|
|
||||||
size: left_view.resolution(),
|
|
||||||
format: left_view.format(),
|
|
||||||
};
|
|
||||||
let right = ManualTextureView {
|
|
||||||
texture_view: right_view.texture_view().unwrap().into(),
|
|
||||||
size: right_view.resolution(),
|
|
||||||
format: right_view.format(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Ok(mut transform) = transforms.get_mut(cameras.0) {
|
|
||||||
let Pose {
|
|
||||||
translation,
|
|
||||||
rotation,
|
|
||||||
} = left_view.pose();
|
|
||||||
|
|
||||||
transform.translation = translation;
|
|
||||||
transform.rotation = rotation;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(mut transform) = transforms.get_mut(cameras.1) {
|
|
||||||
let Pose {
|
|
||||||
translation,
|
|
||||||
rotation,
|
|
||||||
} = right_view.pose();
|
|
||||||
|
|
||||||
transform.translation = translation;
|
|
||||||
transform.rotation = rotation;
|
|
||||||
}
|
|
||||||
|
|
||||||
manual_texture_views.insert(RIGHT_XR_TEXTURE_HANDLE, right);
|
|
||||||
manual_texture_views.insert(LEFT_XR_TEXTURE_HANDLE, left);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn end_frame(session: NonSend<Session>) {
|
|
||||||
session.end_frame().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DefaultXrPlugins;
|
|
||||||
|
|
||||||
impl PluginGroup for DefaultXrPlugins {
|
|
||||||
fn build(self) -> PluginGroupBuilder {
|
|
||||||
DefaultPlugins
|
|
||||||
.build()
|
|
||||||
.disable::<RenderPlugin>()
|
|
||||||
.disable::<PipelinedRenderingPlugin>()
|
|
||||||
.add_before::<RenderPlugin, _>(XrPlugin)
|
|
||||||
.set(WindowPlugin {
|
|
||||||
#[cfg(not(target_os = "android"))]
|
|
||||||
primary_window: Some(Window {
|
|
||||||
transparent: true,
|
|
||||||
present_mode: PresentMode::AutoNoVsync,
|
|
||||||
..default()
|
|
||||||
}),
|
|
||||||
#[cfg(target_os = "android")]
|
|
||||||
primary_window: None,
|
|
||||||
#[cfg(target_os = "android")]
|
|
||||||
exit_condition: bevy::window::ExitCondition::DontExit,
|
|
||||||
#[cfg(target_os = "android")]
|
|
||||||
close_when_requested: true,
|
|
||||||
..default()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
8
src/types.rs
Normal file
8
src/types.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
use bevy::math::{Quat, Vec3};
|
||||||
|
|
||||||
|
pub struct Pose {
|
||||||
|
pub translation: Vec3,
|
||||||
|
pub rotation: Quat,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Haptic;
|
||||||
175
src/webxr.rs
Normal file
175
src/webxr.rs
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use bevy::app::{App, Plugin, PluginsState};
|
||||||
|
use bevy::ecs::entity::Entity;
|
||||||
|
use bevy::ecs::query::With;
|
||||||
|
use bevy::ecs::world::World;
|
||||||
|
use bevy::log::{error, info};
|
||||||
|
use bevy::render::RenderApp;
|
||||||
|
use bevy::window::PrimaryWindow;
|
||||||
|
use bevy::winit::WinitWindows;
|
||||||
|
use js_sys::Object;
|
||||||
|
use wasm_bindgen::closure::Closure;
|
||||||
|
use wasm_bindgen::{JsCast, JsValue};
|
||||||
|
use wasm_bindgen_futures::JsFuture;
|
||||||
|
use web_sys::{
|
||||||
|
HtmlCanvasElement, WebGl2RenderingContext, XrFrame, XrReferenceSpace, XrReferenceSpaceType,
|
||||||
|
XrRenderStateInit, XrSession, XrSessionMode, XrWebGlLayer,
|
||||||
|
};
|
||||||
|
use winit::platform::web::WindowExtWebSys;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct FutureXrSession(Rc<Mutex<Option<Result<(XrSession, XrReferenceSpace), JsValue>>>>);
|
||||||
|
|
||||||
|
pub struct XrInitPlugin;
|
||||||
|
|
||||||
|
impl Plugin for XrInitPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
let canvas = get_canvas(&mut app.world).unwrap();
|
||||||
|
let future_session = FutureXrSession(Default::default());
|
||||||
|
app.set_runner(webxr_runner);
|
||||||
|
app.insert_non_send_resource(future_session.clone());
|
||||||
|
bevy::tasks::IoTaskPool::get().spawn_local(async move {
|
||||||
|
let result = init_webxr(
|
||||||
|
canvas,
|
||||||
|
XrSessionMode::ImmersiveVr,
|
||||||
|
XrReferenceSpaceType::Local,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
*future_session.0.lock().unwrap() = Some(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ready(&self, app: &App) -> bool {
|
||||||
|
app.world
|
||||||
|
.get_non_send_resource::<FutureXrSession>()
|
||||||
|
.and_then(|fxr| fxr.0.try_lock().map(|locked| locked.is_some()).ok())
|
||||||
|
.unwrap_or(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish(&self, app: &mut App) {
|
||||||
|
info!("finishing");
|
||||||
|
|
||||||
|
if let Some(Ok((session, reference_space))) = app
|
||||||
|
.world
|
||||||
|
.remove_non_send_resource::<FutureXrSession>()
|
||||||
|
.and_then(|fxr| fxr.0.lock().unwrap().take())
|
||||||
|
{
|
||||||
|
app.insert_non_send_resource(session.clone())
|
||||||
|
.insert_non_send_resource(reference_space.clone());
|
||||||
|
app.sub_app_mut(RenderApp)
|
||||||
|
.insert_non_send_resource(session)
|
||||||
|
.insert_non_send_resource(reference_space);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn webxr_runner(mut app: App) {
|
||||||
|
fn set_timeout(f: &Closure<dyn FnMut()>, dur: Duration) {
|
||||||
|
web_sys::window()
|
||||||
|
.unwrap()
|
||||||
|
.set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||||
|
f.as_ref().unchecked_ref(),
|
||||||
|
dur.as_millis() as i32,
|
||||||
|
)
|
||||||
|
.expect("Should register `setTimeout`.");
|
||||||
|
}
|
||||||
|
let run_xr_inner = Rc::new(RefCell::new(None));
|
||||||
|
let run_xr: Rc<RefCell<Option<Closure<dyn FnMut()>>>> = run_xr_inner.clone();
|
||||||
|
*run_xr.borrow_mut() = Some(Closure::new(move || {
|
||||||
|
let app = &mut app;
|
||||||
|
if app.plugins_state() == PluginsState::Ready {
|
||||||
|
app.finish();
|
||||||
|
app.cleanup();
|
||||||
|
run_xr_app(std::mem::take(app));
|
||||||
|
} else {
|
||||||
|
set_timeout(
|
||||||
|
run_xr_inner.borrow().as_ref().unwrap(),
|
||||||
|
Duration::from_millis(1),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
set_timeout(run_xr.borrow().as_ref().unwrap(), Duration::from_millis(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_xr_app(mut app: App) {
|
||||||
|
let session = app.world.non_send_resource::<XrSession>().clone();
|
||||||
|
let inner_closure: Rc<RefCell<Option<Closure<dyn FnMut(f64, XrFrame)>>>> =
|
||||||
|
Rc::new(RefCell::new(None));
|
||||||
|
let closure = inner_closure.clone();
|
||||||
|
*closure.borrow_mut() = Some(Closure::new(move |_time, frame: XrFrame| {
|
||||||
|
let session = frame.session();
|
||||||
|
app.insert_non_send_resource(frame);
|
||||||
|
info!("update");
|
||||||
|
app.update();
|
||||||
|
session.request_animation_frame(
|
||||||
|
inner_closure
|
||||||
|
.borrow()
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.as_ref()
|
||||||
|
.unchecked_ref(),
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
session.request_animation_frame(closure.borrow().as_ref().unwrap().as_ref().unchecked_ref());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_canvas(world: &mut World) -> Option<HtmlCanvasElement> {
|
||||||
|
let window_entity = world
|
||||||
|
.query_filtered::<Entity, With<PrimaryWindow>>()
|
||||||
|
.get_single(world)
|
||||||
|
.ok()?;
|
||||||
|
let windows = world.get_non_send_resource::<WinitWindows>()?;
|
||||||
|
Some(windows.get_window(window_entity)?.canvas())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn init_webxr(
|
||||||
|
canvas: HtmlCanvasElement,
|
||||||
|
mode: XrSessionMode,
|
||||||
|
reference_type: XrReferenceSpaceType,
|
||||||
|
) -> Result<(XrSession, XrReferenceSpace), JsValue> {
|
||||||
|
let xr = web_sys::window().unwrap().navigator().xr();
|
||||||
|
|
||||||
|
let supports_session = JsFuture::from(xr.is_session_supported(mode)).await?;
|
||||||
|
if supports_session == false {
|
||||||
|
error!("XR session {:?} not supported", mode);
|
||||||
|
return Err(JsValue::from_str(&format!(
|
||||||
|
"XR session {:?} not supported",
|
||||||
|
mode
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("creating session");
|
||||||
|
let session: XrSession = JsFuture::from(xr.request_session(mode)).await?.into();
|
||||||
|
|
||||||
|
info!("creating gl");
|
||||||
|
let gl: WebGl2RenderingContext = {
|
||||||
|
let gl_attribs = Object::new();
|
||||||
|
js_sys::Reflect::set(
|
||||||
|
&gl_attribs,
|
||||||
|
&JsValue::from_str("xrCompatible"),
|
||||||
|
&JsValue::TRUE,
|
||||||
|
)?;
|
||||||
|
canvas
|
||||||
|
.get_context_with_context_options("webgl2", &gl_attribs)?
|
||||||
|
.ok_or(JsValue::from_str(
|
||||||
|
"Unable to create WebGL rendering context",
|
||||||
|
))?
|
||||||
|
.dyn_into()?
|
||||||
|
};
|
||||||
|
|
||||||
|
let xr_gl_layer = XrWebGlLayer::new_with_web_gl2_rendering_context(&session, &gl)?;
|
||||||
|
let mut render_state_init = XrRenderStateInit::new();
|
||||||
|
render_state_init.base_layer(Some(&xr_gl_layer));
|
||||||
|
session.update_render_state_with_state(&render_state_init);
|
||||||
|
info!("creating ref space");
|
||||||
|
let reference_space = JsFuture::from(session.request_reference_space(reference_type))
|
||||||
|
.await?
|
||||||
|
.into();
|
||||||
|
|
||||||
|
info!("finished");
|
||||||
|
Ok((session, reference_space))
|
||||||
|
}
|
||||||
@@ -8,7 +8,6 @@ default = ["linked"]
|
|||||||
linked = ["openxr/linked"]
|
linked = ["openxr/linked"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ash = "0.37.3"
|
|
||||||
futures = "0.3.29"
|
futures = "0.3.29"
|
||||||
glam = "0.24.1"
|
glam = "0.24.1"
|
||||||
hashbrown = "0.14"
|
hashbrown = "0.14"
|
||||||
@@ -20,6 +19,7 @@ wgpu-hal = "0.17.1"
|
|||||||
|
|
||||||
[target.'cfg(not(target_family = "wasm"))'.dependencies]
|
[target.'cfg(not(target_family = "wasm"))'.dependencies]
|
||||||
openxr = "0.17.1"
|
openxr = "0.17.1"
|
||||||
|
ash = "0.37.3"
|
||||||
|
|
||||||
[target.'cfg(target_family = "wasm")'.dependencies]
|
[target.'cfg(target_family = "wasm")'.dependencies]
|
||||||
wasm-bindgen = "0.2.87"
|
wasm-bindgen = "0.2.87"
|
||||||
|
|||||||
@@ -14,10 +14,7 @@ pub struct Entry(Rc<dyn EntryTrait>);
|
|||||||
impl Entry {
|
impl Entry {
|
||||||
/// Constructs a new Xr entry
|
/// Constructs a new Xr entry
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
#[cfg(target_family = "wasm")]
|
todo!()
|
||||||
return crate::backend::webxr::WebXrEntry::new().into();
|
|
||||||
#[cfg(not(target_family = "wasm"))]
|
|
||||||
return crate::backend::oxr::OXrEntry::new().into();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,12 +31,14 @@ pub trait SessionTrait {
|
|||||||
fn headset_location(&self) -> Result<Pose>;
|
fn headset_location(&self) -> Result<Pose>;
|
||||||
/// Request input modules with the specified bindings.
|
/// Request input modules with the specified bindings.
|
||||||
fn create_input(&self, bindings: Bindings) -> Result<Input>;
|
fn create_input(&self, bindings: Bindings) -> Result<Input>;
|
||||||
/// Blocks until a rendering frame is available, then returns the views for the left and right eyes.
|
/// Wait until a frame is ready to render to.
|
||||||
|
fn wait_frame(&self) -> Result<FrameData>;
|
||||||
|
/// Begin rendering work for the frame.
|
||||||
fn begin_frame(&self) -> Result<()>;
|
fn begin_frame(&self) -> Result<()>;
|
||||||
/// Locate the views of each eye.
|
/// Locate the views of each eye.
|
||||||
fn locate_views(&self) -> Result<(View, View)>;
|
fn locate_views(&self) -> Result<(View, View)>;
|
||||||
/// Submits rendering work for this frame.
|
/// Submits rendering work for this frame.
|
||||||
fn end_frame(&self) -> Result<()>;
|
fn end_frame(&self, data: FrameData) -> Result<()>;
|
||||||
/// Gets the resolution of a single eye.
|
/// Gets the resolution of a single eye.
|
||||||
fn resolution(&self) -> UVec2;
|
fn resolution(&self) -> UVec2;
|
||||||
/// Gets the texture format for the session.
|
/// Gets the texture format for the session.
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ impl SessionTrait for OXrSession {
|
|||||||
.into())
|
.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn begin_frame(&self) -> Result<()> {
|
fn wait_frame(&self) -> Result<FrameData> {
|
||||||
{
|
{
|
||||||
let mut bindings = self.bindings.lock().unwrap();
|
let mut bindings = self.bindings.lock().unwrap();
|
||||||
if !bindings.sessions_attached {
|
if !bindings.sessions_attached {
|
||||||
@@ -239,6 +239,10 @@ impl SessionTrait for OXrSession {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Ok(FrameData)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn begin_frame(&self) -> Result<()> {
|
||||||
{
|
{
|
||||||
let _span = info_span!("xr_begin_frame").entered();
|
let _span = info_span!("xr_begin_frame").entered();
|
||||||
self.swapchain.begin().unwrap()
|
self.swapchain.begin().unwrap()
|
||||||
@@ -292,7 +296,7 @@ impl SessionTrait for OXrSession {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn end_frame(&self) -> Result<()> {
|
fn end_frame(&self, data: FrameData) -> Result<()> {
|
||||||
{
|
{
|
||||||
let _span = info_span!("xr_release_image").entered();
|
let _span = info_span!("xr_release_image").entered();
|
||||||
self.swapchain.release_image().unwrap();
|
self.swapchain.release_image().unwrap();
|
||||||
|
|||||||
@@ -1,208 +1 @@
|
|||||||
use std::sync::{
|
|
||||||
mpsc::{channel, Sender},
|
|
||||||
Mutex,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
use wasm_bindgen::{closure::Closure, JsCast};
|
|
||||||
use wasm_bindgen_futures::js_sys;
|
|
||||||
use web_sys::{XrFrame, XrInputSource};
|
|
||||||
|
|
||||||
mod utils;
|
|
||||||
|
|
||||||
use utils::*;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct WebXrEntry(web_sys::XrSystem);
|
|
||||||
|
|
||||||
impl WebXrEntry {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self(
|
|
||||||
web_sys::window()
|
|
||||||
.expect("No window available in current environment")
|
|
||||||
.navigator()
|
|
||||||
.xr(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EntryTrait for WebXrEntry {
|
|
||||||
fn available_extensions(&self) -> Result<ExtensionSet> {
|
|
||||||
Ok(ExtensionSet::default())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_instance(&self, exts: ExtensionSet) -> Result<Instance> {
|
|
||||||
Ok(WebXrInstance {
|
|
||||||
entry: self.clone(),
|
|
||||||
exts,
|
|
||||||
}
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct WebXrInstance {
|
|
||||||
entry: WebXrEntry,
|
|
||||||
exts: ExtensionSet,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InstanceTrait for WebXrInstance {
|
|
||||||
fn entry(&self) -> Entry {
|
|
||||||
self.entry.clone().into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enabled_extensions(&self) -> ExtensionSet {
|
|
||||||
self.exts
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_session(&self, _info: SessionCreateInfo) -> Result<Session> {
|
|
||||||
Ok(WebXrSession {
|
|
||||||
instance: self.clone().into(),
|
|
||||||
session: self
|
|
||||||
.entry
|
|
||||||
.0
|
|
||||||
.request_session(web_sys::XrSessionMode::ImmersiveVr)
|
|
||||||
.resolve()
|
|
||||||
.map_err(|_| XrError::Placeholder)?,
|
|
||||||
end_frame_sender: Mutex::default(),
|
|
||||||
}
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct WebXrSession {
|
|
||||||
instance: Instance,
|
|
||||||
session: web_sys::XrSession,
|
|
||||||
end_frame_sender: Mutex<Option<Sender<()>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SessionTrait for WebXrSession {
|
|
||||||
fn instance(&self) -> &Instance {
|
|
||||||
&self.instance
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_input(&self, bindings: Bindings) -> Result<Input> {
|
|
||||||
Ok(WebXrInput {
|
|
||||||
devices: self.session.input_sources(),
|
|
||||||
bindings,
|
|
||||||
}
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn begin_frame(&self) -> Result<(View, View)> {
|
|
||||||
let mut end_frame_sender = self.end_frame_sender.lock().unwrap();
|
|
||||||
if end_frame_sender.is_some() {
|
|
||||||
Err(XrError::Placeholder)?
|
|
||||||
}
|
|
||||||
let (tx, rx) = channel::<()>();
|
|
||||||
let (tx_end, rx_end) = channel::<()>();
|
|
||||||
*end_frame_sender = Some(tx_end);
|
|
||||||
let on_frame: Closure<dyn FnMut(f64, XrFrame)> =
|
|
||||||
Closure::new(move |_time: f64, _frame: XrFrame| {
|
|
||||||
tx.send(()).ok();
|
|
||||||
rx_end.recv().ok();
|
|
||||||
});
|
|
||||||
|
|
||||||
self.session
|
|
||||||
.request_animation_frame(on_frame.as_ref().unchecked_ref());
|
|
||||||
|
|
||||||
rx.recv().ok();
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn end_frame(&self) -> Result<()> {
|
|
||||||
let mut end_frame_sender = self.end_frame_sender.lock().unwrap();
|
|
||||||
match std::mem::take(&mut *end_frame_sender) {
|
|
||||||
Some(sender) => sender.send(()).ok(),
|
|
||||||
None => Err(XrError::Placeholder)?,
|
|
||||||
};
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_render_resources(
|
|
||||||
&self,
|
|
||||||
) -> Option<(
|
|
||||||
wgpu::Device,
|
|
||||||
wgpu::Queue,
|
|
||||||
wgpu::AdapterInfo,
|
|
||||||
wgpu::Adapter,
|
|
||||||
wgpu::Instance,
|
|
||||||
)> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct WebXrInput {
|
|
||||||
devices: web_sys::XrInputSourceArray,
|
|
||||||
bindings: Bindings,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<web_sys::XrHandedness> for Handedness {
|
|
||||||
fn from(value: web_sys::XrHandedness) -> Self {
|
|
||||||
match value {
|
|
||||||
web_sys::XrHandedness::None => Handedness::None,
|
|
||||||
web_sys::XrHandedness::Left => Handedness::Left,
|
|
||||||
web_sys::XrHandedness::Right => Handedness::Right,
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WebXrInput {
|
|
||||||
fn get_controller(&self, handedness: Handedness) -> Option<web_sys::XrInputSource> {
|
|
||||||
js_sys::try_iter(&self.devices).ok()??.find_map(|dev| {
|
|
||||||
if let Ok(dev) = dev {
|
|
||||||
let dev: XrInputSource = dev.into();
|
|
||||||
if Into::<Handedness>::into(dev.handedness()) == handedness {
|
|
||||||
Some(dev)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InputTrait for WebXrInput {
|
|
||||||
fn get_haptics(&self, path: ActionPath) -> Result<Action<Haptic>> {
|
|
||||||
// let haptics = self
|
|
||||||
// .get_controller(path.handedness)
|
|
||||||
// .ok_or(XrError::Placeholder)?
|
|
||||||
// .gamepad()
|
|
||||||
// .ok_or(XrError::Placeholder)?
|
|
||||||
// .haptic_actuators()
|
|
||||||
// .iter()
|
|
||||||
// .next()
|
|
||||||
// .ok_or(XrError::Placeholder)?
|
|
||||||
// .into();
|
|
||||||
// Ok(WebXrHaptics(haptics, path).into())
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_pose(&self, _path: ActionPath) -> Result<Action<Pose>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_float(&self, _path: ActionPath) -> Result<Action<f32>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_bool(&self, _path: ActionPath) -> Result<Action<bool>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct OXrActionInput(openxr::Action);
|
|
||||||
|
|
||||||
pub struct WebXrHaptics(web_sys::GamepadHapticActuator, ActionPath);
|
|
||||||
|
|
||||||
impl ActionTrait for WebXrHaptics {
|
|
||||||
fn id(&self) -> ActionPath {
|
|
||||||
self.1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HapticTrait for WebXrHaptics {}
|
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ pub struct ExtensionSet {
|
|||||||
pub vulkan: bool,
|
pub vulkan: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct FrameData;
|
||||||
|
|
||||||
pub struct SessionCreateInfo {
|
pub struct SessionCreateInfo {
|
||||||
/// preferred texture format
|
/// preferred texture format
|
||||||
pub texture_format: wgpu::TextureFormat,
|
pub texture_format: wgpu::TextureFormat,
|
||||||
|
|||||||
Reference in New Issue
Block a user