begin refactor

This commit is contained in:
awtterpip
2023-12-11 12:59:37 -06:00
parent 7947dd097c
commit 037f719329
48 changed files with 1069 additions and 6904 deletions

3
.cargo/config.toml Normal file
View File

@@ -0,0 +1,3 @@
[target.wasm32-unknown-unknown]
runner = "wasm-server-runner"
rustflags = ["--cfg","web_sys_unstable_apis",]

View File

@@ -1,35 +0,0 @@
name: Build
on:
push:
branches:
- "main"
paths-ignore:
- "/docs"
- "README.md"
pull_request:
paths-ignore:
- "/docs"
- "README.md"
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: "Checkout"
uses: actions/checkout@v3
- name: "Cache"
uses: Swatinem/rust-cache@v2
- name: "External dependencies"
run: sudo apt-get install -y libasound2-dev portaudio19-dev build-essential libpulse-dev libdbus-1-dev libudev-dev libopenxr-loader1 libopenxr-dev
- name: "Checks"
run: |
cargo update
#cargo fmt --check
#cargo clippy --no-deps --tests -- -D warnings
#cargo rustdoc -- -D warnings
- name: "Build"
run: |
cargo build
cargo build --examples
- name: "Test"
run: |
cargo test --verbose

1
.gitignore vendored
View File

@@ -1,4 +1,5 @@
**/target
**/Cargo.lock
**/runtime_libs/arm64-v8a/*
.vscode
\.DS_Store

View File

@@ -6,38 +6,78 @@ description = "Community crate for OpenXR in Bevy"
repository = "https://github.com/awtterpip/bevy_oxr"
license = "MIT/Apache-2.0"
[features]
default = ["linked"]
default = ["linked", "vulkan"]
linked = ["openxr/linked"]
[workspace]
members = ["examples/android", "examples/demo"]
vulkan = ["dep:ash"]
[dependencies]
anyhow = "1.0.75"
ash = "0.37.3"
bevy = "0.12"
futures-lite = "2.0.1"
mint = "0.5.9"
bevy = "0.12.1"
paste = "1.0.14"
wgpu = "0.17.1"
wgpu-core = { version = "0.17.1", features = ["vulkan"] }
wgpu-hal = "0.17.1"
[target.'cfg( target_family = "unix" )'.dependencies]
[target.'cfg(target_family = "unix")'.dependencies]
openxr = { version = "0.17.1", features = ["mint"] }
[target.'cfg(not(target_family = "unix"))'.dependencies]
[target.'cfg(target_family = "windows")'.dependencies]
openxr = { version = "0.17.1", features = ["mint", "static"] }
[dev-dependencies]
bevy = "0.12"
color-eyre = "0.6.2"
bevy_rapier3d = { git = "https://github.com/devil-ira/bevy_rapier", branch = "bevy-0.12" }
[target.'cfg(not(target_family = "wasm"))'.dependencies]
ash = { version = "0.37.3", optional = true }
[[example]]
name = "xr"
path = "examples/xr.rs"
[profile.release]
debug = true
[target.'cfg(target_family = "wasm")'.dependencies]
wasm-bindgen = "0.2.87"
web-sys = { version = "0.3.61", features = [
# STANDARD
'console',
'Document',
'Element',
'Headers',
'Navigator',
'Window',
# IO
# 'Url',
# WEBGL
'Gpu',
'HtmlCanvasElement',
'WebGl2RenderingContext',
'WebGlFramebuffer',
## XR
'DomPointReadOnly',
'XrWebGlLayer',
'XrBoundedReferenceSpace',
'XrEye',
'XrFrame',
'XrHandedness',
'XrInputSource',
'XrInputSourceArray',
'XrInputSourceEvent',
'XrInputSourceEventInit',
'XrInputSourcesChangeEvent',
'XrJointPose',
'XrJointSpace',
'XrPose',
'XrReferenceSpace',
'XrReferenceSpaceEvent',
'XrReferenceSpaceEventInit',
'XrReferenceSpaceType',
'XrRenderState',
'XrRenderStateInit',
'XrRigidTransform',
'XrSession',
'XrSessionEvent',
'XrSessionEventInit',
'XrSessionInit',
'XrSessionMode',
'XrSpace',
'XrTargetRayMode',
'XrView',
'XrViewerPose',
'XrViewport',
'XrVisibilityState',
'XrWebGlLayer',
'XrWebGlLayerInit',
'XrSystem',
] }
wasm-bindgen-futures = "0.4"

View File

@@ -1,3 +0,0 @@
/target
/Cargo.lock
/runtime_libs

View File

@@ -1,73 +0,0 @@
[package]
name = "bevy_openxr_android"
version = "0.1.0"
edition = "2021"
description = "Example for building an Android OpenXR app with Bevy"
publish = false
license = "MIT OR Apache-2.0"
[lib]
name = "bevy_openxr_android"
crate-type = ["rlib", "cdylib"]
[target.'cfg(not(target_os="android"))'.dependencies.bevy_oxr]
path = "../../"
default-features = true
[dependencies]
bevy_oxr = { path = "../..", default-features = false }
bevy = "0.12"
openxr = { git = "https://github.com/Ralith/openxrs", features = ["mint"] }
[profile.release]
lto = "fat"
codegen-units = 1
panic = "abort"
# This metadata is used by `cargo-apk` - `xbuild` uses the `manifest.yaml` instead.
[package.metadata.android]
package = "org.bevyengine.example_openxr_android"
build_targets = ["aarch64-linux-android"]
runtime_libs = "runtime_libs"
apk_name = "bevyopenxr"
# assets = "assets"
# res = "assets/android-res"
icon = "@mipmap/ic_launcher"
label = "Bevy Openxr Android"
strip = "strip"
# [package.metadata.android.application]
# icon = "@mipmap/ic_launcher"
# label = "Bevy Example"
[package.metadata.android.sdk]
target_sdk_version = 32
[package.metadata.android.application.activity]
theme = "@android:style/Theme.Black.NoTitleBar.Fullscreen"
config_changes = "density|keyboard|keyboardHidden|navigation|orientation|screenLayout|screenSize|uiMode"
launch_mode = "singleTask"
orientation = "landscape"
resizeable_activity = false
[[package.metadata.android.application.activity.intent_filter]]
actions = ["android.intent.action.MAIN"]
categories = [
"com.oculus.intent.category.VR",
"android.intent.category.LAUNCHER",
]
# !! IMPORTANT !!
#
# When creating your own apps, make sure to generate your own keystore, rather than using our example one!
# You can use `keytool` like so:
# keytool -genkey -v -keystore my-release-key.keystore -keyalg RSA -keysize 2048 -validity 10000
#
# For more information on key signing and why it's so important, check out this article:
# https://developer.android.com/studio/publish/app-signing
#
# !! IMPORTANT !!
[package.metadata.android.signing.release]
path = "./hotham_examples.keystore"
keystore_password = "chomsky-vigilant-spa"

View File

@@ -1,67 +0,0 @@
# Bevy OpenXR Android example
## Setup
Get libopenxr_loader.so from the Oculus OpenXR Mobile SDK and add it to `examples/android/runtime_libs/arm64-v8a`
https://developer.oculus.com/downloads/package/oculus-openxr-mobile-sdk/
`examples/android/runtime_libs/arm64-v8a/libopenxr_loader.so`
Also, install either `cargo-apk` (marked as deprecated):
```sh
cargo install cargo-apk
```
or, install `xbuild` as it supersedes `cargo-apk`. Note that the `--git` is
very important here.
```sh
cargo install --git https://github.com/rust-mobile/xbuild
```
## Run
Running on Meta Quest can be done with https://github.com/rust-mobile/cargo-apk.
```sh
cargo apk run --release
```
But cargo-apk is deprecated in favour of xbuild https://github.com/rust-mobile/xbuild.
```sh
# List devices and copy device string "adb:***"
x devices
# Run on this device
x run --release --device adb:***
```
There is [manifest.yaml](./manifest.yaml) example required by xbuild.
Interface for this manifest can be found as AndroidConfig struct in https://github.com/rust-mobile/xbuild/blob/master/xbuild/src/config.rs
## Notes
### Relase mode
More optimisations enabled in Cargo.toml for the release mode.
This gives more performance but longer build time.
```toml
[profile.release]
lto = "fat"
codegen-units = 1
panic = "abort"
```
### Cargo apk
If you see error like `Error: String `` is not a PID`, try to install cargo apk with a fix in branch.
```sh
cargo install --git https://github.com/rust-mobile/cargo-apk --branch=adb-logcat-uid
```
### Temporary JNIEnv log
This message is logged every frame. It's not yet fixed.
```sh
I JniUtils-inl: Creating temporary JNIEnv. This is a heavy operation and should be infrequent. To optimize, use JNI AttachCurrentThread on calling threa
```
### Android keystore
Release mode requires keystore. See Cargo.toml `package.metadata.android.signing.release`.
When creating your own apps, make sure to generate your own keystore, rather than using our example one!
You can use `keytool` like so:
```sh
keytool -genkey -v -keystore my-release-key.keystore -keyalg RSA -keysize 2048 -validity 10000
```
For more information on key signing and why it's so important, check out this article:
https://developer.android.com/studio/publish/app-signing

View File

@@ -1,27 +0,0 @@
android:
runtime_libs:
- "runtime_libs"
manifest:
package: "org.bevyengine.example_openxr_android"
application:
label: "Bevy Openxr Android"
theme: "@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen"
meta_data:
- name: "com.oculus.intent.category.VR"
value: "vr_only"
- name: "com.samsung.android.vr.application.mode"
value: "vr_only"
- name: "com.oculus.supportedDevices"
value: "quest|quest2|quest3|questpro"
activities:
- config_changes: "density|keyboard|keyboardHidden|navigation|orientation|screenLayout|screenSize|uiMode"
launch_mode: "singleTask"
orientation: "landscape"
intent_filters:
- actions:
- "android.intent.action.MAIN"
categories:
- "com.oculus.intent.category.VR"
- "android.intent.category.LAUNCHER"
sdk:
target_sdk_version: 32

View File

@@ -1,83 +0,0 @@
use bevy::diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin};
use bevy::prelude::*;
use bevy::transform::components::Transform;
use bevy_oxr::xr_input::debug_gizmos::OpenXrDebugRenderer;
use bevy_oxr::xr_input::prototype_locomotion::{proto_locomotion, PrototypeLocomotionConfig};
use bevy_oxr::xr_input::trackers::{
OpenXRController, OpenXRLeftController, OpenXRRightController, OpenXRTracker,
};
use bevy_oxr::DefaultXrPlugins;
#[bevy_main]
fn main() {
App::new()
.add_plugins(DefaultXrPlugins)
.add_plugins(OpenXrDebugRenderer)
.add_plugins(LogDiagnosticsPlugin::default())
.add_plugins(FrameTimeDiagnosticsPlugin)
.add_systems(Startup, setup)
.add_systems(Update, proto_locomotion)
.add_systems(Startup, spawn_controllers_example)
.insert_resource(PrototypeLocomotionConfig::default())
.run();
}
/// set up a simple 3D scene
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// plane
commands.spawn(PbrBundle {
mesh: meshes.add(shape::Plane::from_size(5.0).into()),
material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
..default()
});
// cube
commands.spawn(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 0.1 })),
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
transform: Transform::from_xyz(0.0, 0.5, 0.0),
..default()
});
// cube
commands.spawn(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 0.1 })),
material: materials.add(Color::rgb(0.8, 0.0, 0.0).into()),
transform: Transform::from_xyz(0.0, 0.5, 1.0),
..default()
});
// light
commands.spawn(PointLightBundle {
point_light: PointLight {
intensity: 1500.0,
shadows_enabled: true,
..default()
},
transform: Transform::from_xyz(4.0, 8.0, 4.0),
..default()
});
// camera
// commands.spawn((Camera3dBundle {
// transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
// ..default()
// },));
}
fn spawn_controllers_example(mut commands: Commands) {
//left hand
commands.spawn((
OpenXRLeftController,
OpenXRController,
OpenXRTracker,
SpatialBundle::default(),
));
//right hand
commands.spawn((
OpenXRRightController,
OpenXRController,
OpenXRTracker,
SpatialBundle::default(),
));
}

View File

@@ -1,21 +0,0 @@
[package]
name = "demo"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["rlib", "cdylib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bevy = "0.12"
bevy_oxr = { path = "../../", default-features = false }
bevy_rapier3d = { git = "https://github.com/devil-ira/bevy_rapier", branch = "bevy-0.12" }
color-eyre = "0.6.2"
[target.'cfg(not(target_os="android"))'.dependencies.bevy_oxr]
path = "../../"
# May need to be more specific. needs to be false at least on linux without an active runtime to run in flat
default-features = true

View File

@@ -1,11 +0,0 @@
## setup
install xbuild ```cargo install --git https://github.com/rust-mobile/xbuild```
run ```x doctor``` and install all required depencencies
download the [openxr loader](https://developer.oculus.com/downloads/package/oculus-openxr-mobile-sdk/) and put it in the runtime_libs/arm64-v8a folder
## how to run
run ```x devices```
and get the device name that looks something like this ```adb:1WD523S``` (probably a bit longer)
then ```run x run --release```

View File

@@ -1,34 +0,0 @@
android:
runtime_libs:
- "runtime_libs"
manifest:
package: "org.bevyengine.demo_openxr_android"
uses_feature:
- name: "android.hardware.vr.headtracking"
required: true
- name: "oculus.software.handtracking"
required: false
- name: "com.oculus.experimental.enabled"
required: true
uses_permission:
- name: "com.oculus.permission.HAND_TRACKING"
application:
label: "Bevy Openxr Android"
theme: "@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen"
meta_data:
- name: "com.samsung.android.vr.application.mode"
value: "vr_only"
- name: "com.oculus.supportedDevices"
value: "quest|quest2|quest3"
activities:
- config_changes: "density|keyboard|keyboardHidden|navigation|orientation|screenLayout|screenSize|uiMode"
launch_mode: "singleTask"
orientation: "landscape"
intent_filters:
- actions:
- "android.intent.action.MAIN"
categories:
- "com.oculus.intent.category.VR"
- "android.intent.category.LAUNCHER"
sdk:
target_sdk_version: 32

View File

@@ -1,791 +0,0 @@
use std::{f32::consts::PI, ops::Mul, time::Duration};
use bevy::{
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
ecs::schedule::ScheduleLabel,
input::{keyboard::KeyCode, Input},
log::info,
prelude::{
bevy_main, default, shape, App, Assets, Color, Commands, Component, Entity, Event,
EventReader, EventWriter, FixedUpdate, Gizmos, GlobalTransform, IntoSystemConfigs,
IntoSystemSetConfigs, Mesh, PbrBundle, PostUpdate, Quat, Query, Res, ResMut, Resource,
Schedule, SpatialBundle, StandardMaterial, Startup, Transform, Update, Vec3, Vec3Swizzles,
With, Without, World,
},
time::{Fixed, Time, Timer, TimerMode},
transform::TransformSystem,
};
use bevy_oxr::{
input::XrInput,
xr_init::{XrEnableRequest, XrEnableStatus, xr_only},
resources::{XrFrameState, XrInstance, XrSession},
xr_input::{
actions::XrActionSets,
debug_gizmos::OpenXrDebugRenderer,
hands::common::{HandInputDebugRenderer, HandResource, HandsResource, OpenXrHandInput},
hands::HandBone,
interactions::{
draw_interaction_gizmos, draw_socket_gizmos, interactions, socket_interactions,
update_interactable_states, InteractionEvent, Touched, XRDirectInteractor,
XRInteractable, XRInteractableState, XRInteractorState, XRSelection,
},
oculus_touch::OculusController,
prototype_locomotion::{proto_locomotion, PrototypeLocomotionConfig},
trackers::{OpenXRController, OpenXRLeftController, OpenXRRightController, OpenXRTracker},
Hand,
},
DefaultXrPlugins,
};
fn input_stuff(
keys: Res<Input<KeyCode>>,
status: Res<XrEnableStatus>,
mut request: EventWriter<XrEnableRequest>,
) {
if keys.just_pressed(KeyCode::Space) {
match status.into_inner() {
XrEnableStatus::Enabled => request.send(XrEnableRequest::TryDisable),
XrEnableStatus::Disabled => request.send(XrEnableRequest::TryEnable),
XrEnableStatus::Waiting => (),
}
}
}
mod setup;
use crate::setup::setup_scene;
use bevy_rapier3d::prelude::*;
#[bevy_main]
pub fn main() {
color_eyre::install().unwrap();
info!("Running bevy_openxr demo");
let mut app = App::new();
app.add_systems(Update, input_stuff)
//lets get the usual diagnostic stuff added
.add_plugins(LogDiagnosticsPlugin::default())
.add_plugins(FrameTimeDiagnosticsPlugin)
//lets get the xr defaults added
.add_plugins(DefaultXrPlugins)
//lets add the debug renderer for the controllers
.add_plugins(OpenXrDebugRenderer)
//rapier goes here
.add_plugins(RapierPhysicsPlugin::<NoUserData>::default().with_default_system_setup(false))
// .add_plugins(RapierDebugRenderPlugin::default())
//lets setup the starting scene
.add_systems(Startup, setup_scene)
.add_systems(Startup, spawn_controllers_example) //you need to spawn controllers or it crashes TODO:: Fix this
//add locomotion
.add_systems(Update, proto_locomotion.run_if(xr_only()))
.insert_resource(PrototypeLocomotionConfig::default())
//lets add the interaction systems
.add_event::<InteractionEvent>()
.add_systems(Update, prototype_interaction_input.run_if(xr_only()))
.add_systems(Update, interactions.before(update_interactable_states))
.add_systems(Update, update_interactable_states)
.add_systems(
Update,
socket_interactions.before(update_interactable_states),
)
//add the grabbable system
.add_systems(Update, update_grabbables.after(update_interactable_states))
//draw the interaction gizmos
.add_systems(
Update,
draw_interaction_gizmos.run_if(xr_only()).after(update_interactable_states),
)
.add_systems(Update, draw_socket_gizmos.after(update_interactable_states))
//add our cube spawning system
.add_event::<SpawnCubeRequest>()
.insert_resource(SpawnCubeTimer(Timer::from_seconds(
0.25,
bevy::time::TimerMode::Once,
)))
.add_systems(Update, request_cube_spawn.run_if(xr_only()))
.add_systems(Update, cube_spawner.after(request_cube_spawn))
//test capsule
.add_systems(Startup, spawn_capsule)
//physics hands
.add_plugins(OpenXrHandInput)
.add_plugins(HandInputDebugRenderer)
.add_systems(Startup, spawn_physics_hands)
.add_systems(
FixedUpdate,
update_physics_hands.before(PhysicsSet::SyncBackend),
)
.add_event::<GhostHandEvent>()
.add_systems(Update, handle_ghost_hand_events.after(update_grabbables))
.insert_resource(GhostTimers {
left: Timer::from_seconds(0.25, TimerMode::Once),
right: Timer::from_seconds(0.25, TimerMode::Once),
})
.add_systems(Update, watch_ghost_timers.before(handle_ghost_hand_events));
//configure rapier sets
let mut physics_schedule = Schedule::new(PhysicsSchedule);
physics_schedule.configure_sets(
(
PhysicsSet::SyncBackend,
PhysicsSet::StepSimulation,
PhysicsSet::Writeback,
)
.chain()
.before(TransformSystem::TransformPropagate),
);
app.configure_sets(
PostUpdate,
(
PhysicsSet::SyncBackend,
PhysicsSet::StepSimulation,
PhysicsSet::Writeback,
)
.chain()
.before(TransformSystem::TransformPropagate),
);
//add rapier systems
physics_schedule.add_systems((
RapierPhysicsPlugin::<NoUserData>::get_systems(PhysicsSet::SyncBackend)
.in_set(PhysicsSet::SyncBackend),
RapierPhysicsPlugin::<NoUserData>::get_systems(PhysicsSet::StepSimulation)
.in_set(PhysicsSet::StepSimulation),
RapierPhysicsPlugin::<NoUserData>::get_systems(PhysicsSet::Writeback)
.in_set(PhysicsSet::Writeback),
));
app.add_schedule(physics_schedule) // configure our fixed timestep schedule to run at the rate we want
.insert_resource(Time::<Fixed>::from_duration(Duration::from_secs_f32(
FIXED_TIMESTEP,
)))
.add_systems(FixedUpdate, run_physics_schedule)
.add_systems(Startup, configure_physics);
app.run();
}
//fixed timesteps?
const FIXED_TIMESTEP: f32 = 1. / 90.;
// A label for our new Schedule!
#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone)]
struct PhysicsSchedule;
fn run_physics_schedule(world: &mut World) {
world.run_schedule(PhysicsSchedule);
}
fn configure_physics(mut rapier_config: ResMut<RapierConfiguration>) {
rapier_config.timestep_mode = TimestepMode::Fixed {
dt: FIXED_TIMESTEP,
substeps: 1,
}
}
fn spawn_controllers_example(mut commands: Commands) {
//left hand
commands.spawn((
OpenXRLeftController,
OpenXRController,
OpenXRTracker,
SpatialBundle::default(),
XRDirectInteractor,
XRInteractorState::default(),
XRSelection::default(),
Hand::Left,
));
//right hand
commands.spawn((
OpenXRRightController,
OpenXRController,
OpenXRTracker,
SpatialBundle::default(),
XRDirectInteractor,
XRInteractorState::default(),
XRSelection::default(),
Hand::Right,
));
}
fn spawn_capsule(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
commands.spawn((
PbrBundle {
mesh: meshes.add(Mesh::from(shape::Capsule {
radius: 0.033,
depth: 0.115,
..default()
})),
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
transform: Transform::from_xyz(0.0, 2.0, 0.0),
..default()
},
// Collider::capsule_y(0.0575, 0.034),
Collider::capsule(
Vec3 {
x: 0.0,
y: -0.0575,
z: 0.0,
},
Vec3 {
x: 0.0,
y: 0.0575,
z: 0.0,
},
0.034,
),
RigidBody::Dynamic,
));
}
#[derive(Component, PartialEq, Debug, Clone, Copy)]
pub enum PhysicsHandBone {
Palm,
Wrist,
ThumbMetacarpal,
ThumbProximal,
ThumbDistal,
ThumbTip,
IndexMetacarpal,
IndexProximal,
IndexIntermediate,
IndexDistal,
IndexTip,
MiddleMetacarpal,
MiddleProximal,
MiddleIntermediate,
MiddleDistal,
MiddleTip,
RingMetacarpal,
RingProximal,
RingIntermediate,
RingDistal,
RingTip,
LittleMetacarpal,
LittleProximal,
LittleIntermediate,
LittleDistal,
LittleTip,
}
#[derive(Component, PartialEq)]
pub enum BoneInitState {
True,
False,
}
fn spawn_physics_hands(mut commands: Commands) {
//here we go
let hands = [Hand::Left, Hand::Right];
let bones = [
PhysicsHandBone::Palm,
PhysicsHandBone::Wrist,
PhysicsHandBone::ThumbMetacarpal,
PhysicsHandBone::ThumbProximal,
PhysicsHandBone::ThumbDistal,
PhysicsHandBone::ThumbTip,
PhysicsHandBone::IndexMetacarpal,
PhysicsHandBone::IndexProximal,
PhysicsHandBone::IndexIntermediate,
PhysicsHandBone::IndexDistal,
PhysicsHandBone::IndexTip,
PhysicsHandBone::MiddleMetacarpal,
PhysicsHandBone::MiddleProximal,
PhysicsHandBone::MiddleIntermediate,
PhysicsHandBone::MiddleDistal,
PhysicsHandBone::MiddleTip,
PhysicsHandBone::RingMetacarpal,
PhysicsHandBone::RingProximal,
PhysicsHandBone::RingIntermediate,
PhysicsHandBone::RingDistal,
PhysicsHandBone::RingTip,
PhysicsHandBone::LittleMetacarpal,
PhysicsHandBone::LittleProximal,
PhysicsHandBone::LittleIntermediate,
PhysicsHandBone::LittleDistal,
PhysicsHandBone::LittleTip,
];
let radius = 0.010;
let left_hand_membership_group = Group::GROUP_1;
let right_hand_membership_group = Group::GROUP_2;
let floor_membership = Group::GROUP_3;
for hand in hands.iter() {
let hand_membership = match hand {
Hand::Left => left_hand_membership_group,
Hand::Right => right_hand_membership_group,
};
let mut hand_filter: Group = Group::ALL;
hand_filter.remove(hand_membership);
hand_filter.remove(floor_membership);
for bone in bones.iter() {
//spawn the thing
commands.spawn((
SpatialBundle::default(),
Collider::capsule(
Vec3 {
x: 0.0,
y: -0.0575,
z: 0.0,
},
Vec3 {
x: 0.0,
y: 0.0575,
z: 0.0,
},
radius,
),
RigidBody::Dynamic,
Velocity::default(),
CollisionGroups::new(hand_membership, Group::from_bits(0b0001).unwrap()),
// SolverGroups::new(self_group, interaction_group),
bone.clone(),
BoneInitState::False,
hand.clone(),
));
}
}
}
pub enum MatchingType {
PositionMatching,
VelocityMatching,
}
fn update_physics_hands(
hands_res: Option<Res<HandsResource>>,
mut bone_query: Query<(
&mut Transform,
&mut Collider,
&PhysicsHandBone,
&mut BoneInitState,
&Hand,
&mut Velocity,
)>,
hand_query: Query<(&Transform, &HandBone, &Hand, Without<PhysicsHandBone>)>,
time: Res<Time>,
mut gizmos: Gizmos,
) {
let matching = MatchingType::VelocityMatching;
//sanity check do we even have hands?
match hands_res {
Some(res) => {
//config stuff
let radius = 0.010;
for mut bone in bone_query.iter_mut() {
let hand_res = match bone.4 {
Hand::Left => res.left,
Hand::Right => res.right,
};
//lets just do the Right ThumbMetacarpal for now
let result = get_start_and_end_entities(hand_res, bone.2);
if let Some((start_entity, end_entity)) = result {
//now we need their transforms
let start_components = hand_query.get(start_entity);
let end_components = hand_query.get(end_entity);
let direction = end_components.unwrap().0.translation
- start_components.unwrap().0.translation;
if direction.length() < 0.001 {
//i hate this but we need to skip init if the length is zero
return;
}
match *bone.3 {
BoneInitState::True => {
match matching {
MatchingType::PositionMatching => {
//if we are init then we just move em?
*bone.0 = start_components
.unwrap()
.0
.clone()
.looking_at(end_components.unwrap().0.translation, Vec3::Y);
}
MatchingType::VelocityMatching => {
//calculate position difference
let diff = (start_components.unwrap().0.translation
- bone.0.translation)
/ time.delta_seconds();
bone.5.linvel = diff;
//calculate angular velocity?
// gizmos.ray(bone.0.translation, bone.0.forward(), Color::WHITE);
let desired_forward = start_components
.unwrap()
.0
.clone()
.looking_at(end_components.unwrap().0.translation, Vec3::Y)
.rotation;
// gizmos.ray(
// bone.0.translation,
// desired_forward.mul_vec3(-Vec3::Z),
// Color::GREEN,
// );
let cross =
bone.0.forward().cross(desired_forward.mul_vec3(-Vec3::Z));
// gizmos.ray(
// bone.0.translation,
// cross,
// Color::RED,
// );
bone.5.angvel = cross / time.delta_seconds();
}
}
}
BoneInitState::False => {
//build a new collider?
*bone.1 = Collider::capsule(
Vec3::splat(0.0),
Vec3 {
x: 0.0,
y: 0.0,
z: -direction.length(),
},
radius,
);
*bone.3 = BoneInitState::True;
}
}
}
}
}
None => info!("hand states resource not initialized yet"),
}
}
fn get_start_and_end_entities(
hand_res: HandResource,
bone: &PhysicsHandBone,
) -> Option<(Entity, Entity)> {
match bone {
PhysicsHandBone::Palm => return None,
PhysicsHandBone::Wrist => return None,
PhysicsHandBone::ThumbMetacarpal => {
return Some((hand_res.thumb.metacarpal, hand_res.thumb.proximal))
}
PhysicsHandBone::ThumbProximal => {
return Some((hand_res.thumb.proximal, hand_res.thumb.distal))
}
PhysicsHandBone::ThumbDistal => return Some((hand_res.thumb.distal, hand_res.thumb.tip)),
PhysicsHandBone::ThumbTip => return None,
PhysicsHandBone::IndexMetacarpal => {
return Some((hand_res.index.metacarpal, hand_res.index.proximal))
}
PhysicsHandBone::IndexProximal => {
return Some((hand_res.index.proximal, hand_res.index.intermediate))
}
PhysicsHandBone::IndexIntermediate => {
return Some((hand_res.index.intermediate, hand_res.index.distal))
}
PhysicsHandBone::IndexDistal => return Some((hand_res.index.distal, hand_res.index.tip)),
PhysicsHandBone::IndexTip => return None,
PhysicsHandBone::MiddleMetacarpal => {
return Some((hand_res.middle.metacarpal, hand_res.middle.proximal))
}
PhysicsHandBone::MiddleProximal => {
return Some((hand_res.middle.proximal, hand_res.middle.intermediate))
}
PhysicsHandBone::MiddleIntermediate => {
return Some((hand_res.middle.intermediate, hand_res.middle.distal))
}
PhysicsHandBone::MiddleDistal => {
return Some((hand_res.middle.distal, hand_res.middle.tip))
}
PhysicsHandBone::MiddleTip => return None,
PhysicsHandBone::RingMetacarpal => {
return Some((hand_res.ring.metacarpal, hand_res.ring.proximal))
}
PhysicsHandBone::RingProximal => {
return Some((hand_res.ring.proximal, hand_res.ring.intermediate))
}
PhysicsHandBone::RingIntermediate => {
return Some((hand_res.ring.intermediate, hand_res.ring.distal))
}
PhysicsHandBone::RingDistal => return Some((hand_res.ring.distal, hand_res.ring.tip)),
PhysicsHandBone::RingTip => return None,
PhysicsHandBone::LittleMetacarpal => {
return Some((hand_res.little.metacarpal, hand_res.little.proximal))
}
PhysicsHandBone::LittleProximal => {
return Some((hand_res.little.proximal, hand_res.little.intermediate))
}
PhysicsHandBone::LittleIntermediate => {
return Some((hand_res.little.intermediate, hand_res.little.distal))
}
PhysicsHandBone::LittleDistal => {
return Some((hand_res.little.distal, hand_res.little.tip))
}
PhysicsHandBone::LittleTip => return None,
};
}
fn get_hand_res(res: &Res<'_, HandsResource>, hand: Hand) -> HandResource {
match hand {
Hand::Left => res.left.clone(),
Hand::Right => res.right.clone(),
}
}
#[derive(Event, Default)]
pub struct SpawnCubeRequest;
#[derive(Resource)]
pub struct SpawnCubeTimer(Timer);
fn request_cube_spawn(
oculus_controller: Res<OculusController>,
frame_state: Res<XrFrameState>,
xr_input: Res<XrInput>,
instance: Res<XrInstance>,
session: Res<XrSession>,
mut writer: EventWriter<SpawnCubeRequest>,
time: Res<Time>,
mut timer: ResMut<SpawnCubeTimer>,
action_sets: Res<XrActionSets>,
) {
timer.0.tick(time.delta());
if timer.0.finished() {
//lock frame
let frame_state = *frame_state.lock().unwrap();
//get controller
let controller = oculus_controller.get_ref(&session, &frame_state, &xr_input, &action_sets);
//get controller triggers
let left_main_button = controller.a_button();
if left_main_button {
writer.send(SpawnCubeRequest::default());
timer.0.reset();
}
let right_main_button = controller.x_button();
if right_main_button {
writer.send(SpawnCubeRequest::default());
timer.0.reset();
}
}
}
fn cube_spawner(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut events: EventReader<SpawnCubeRequest>,
) {
for request in events.read() {
// cube
commands.spawn((
PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 0.1 })),
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
transform: Transform::from_xyz(0.0, 1.0, 0.0),
..default()
},
RigidBody::Dynamic,
Collider::cuboid(0.05, 0.05, 0.05),
ColliderDebugColor(Color::hsl(220.0, 1.0, 0.3)),
XRInteractable,
XRInteractableState::default(),
Grabbable,
Touched(false),
));
}
}
//TODO: find a real place for this
fn prototype_interaction_input(
oculus_controller: Res<OculusController>,
frame_state: Res<XrFrameState>,
xr_input: Res<XrInput>,
session: Res<XrSession>,
mut right_interactor_query: Query<
(&mut XRInteractorState),
(
With<XRDirectInteractor>,
With<OpenXRRightController>,
Without<OpenXRLeftController>,
),
>,
mut left_interactor_query: Query<
(&mut XRInteractorState),
(
With<XRDirectInteractor>,
With<OpenXRLeftController>,
Without<OpenXRRightController>,
),
>,
action_sets: Res<XrActionSets>,
) {
//lock frame
let frame_state = *frame_state.lock().unwrap();
//get controller
let controller = oculus_controller.get_ref(&session, &frame_state, &xr_input, &action_sets);
//get controller triggers
let left_trigger = controller.trigger(Hand::Left);
let right_trigger = controller.trigger(Hand::Right);
//get the interactors and do state stuff
let mut left_state = left_interactor_query.single_mut();
if left_trigger > 0.8 {
*left_state = XRInteractorState::Selecting;
} else {
*left_state = XRInteractorState::Idle;
}
let mut right_state = right_interactor_query.single_mut();
if right_trigger > 0.8 {
*right_state = XRInteractorState::Selecting;
} else {
*right_state = XRInteractorState::Idle;
}
}
//this event is for transitioning the physics hand in an out of existent so we can drop things better
#[derive(Event)]
pub struct GhostHandEvent {
pub hand: Hand,
pub desired_state: bool, //true for no interactions, false for normal interactions
}
#[derive(Resource)]
pub struct GhostTimers {
pub left: Timer,
pub right: Timer,
}
pub fn handle_ghost_hand_events(
mut events: EventReader<GhostHandEvent>,
mut bones: Query<(&Hand, &mut CollisionGroups, With<PhysicsHandBone>)>,
) {
for event in events.read() {
// info!(
// "Ghost hand Event: {:?}, {:?}",
// event.hand, event.desired_state
// );
//do work
for mut bone in bones.iter_mut() {
match *bone.0 == event.hand {
true => match event.desired_state {
true => bone.1.filters = Group::NONE,
false => bone.1.filters = Group::from_bits(0b0001).unwrap(),
},
false => (),
}
}
}
}
pub fn watch_ghost_timers(
mut timers: ResMut<GhostTimers>,
mut writer: EventWriter<GhostHandEvent>,
time: Res<Time>,
) {
//tick both timers
timers.left.tick(time.delta());
timers.right.tick(time.delta());
//if they finish send events to make the hands physical again
if timers.left.just_finished() {
writer.send(GhostHandEvent {
hand: Hand::Left,
desired_state: false,
});
}
if timers.right.just_finished() {
writer.send(GhostHandEvent {
hand: Hand::Right,
desired_state: false,
});
}
}
#[derive(Component)]
pub struct Grabbable;
pub fn update_grabbables(
mut events: EventReader<InteractionEvent>,
mut grabbable_query: Query<(
Entity,
&mut Transform,
With<Grabbable>,
Without<XRDirectInteractor>,
Option<&mut RigidBody>,
)>,
mut interactor_query: Query<(
&GlobalTransform,
&XRInteractorState,
&mut XRSelection,
&Hand,
Without<Grabbable>,
)>,
mut writer: EventWriter<GhostHandEvent>,
mut timers: ResMut<GhostTimers>,
) {
//so basically the idea is to try all the events?
for event in events.read() {
// info!("some event");
match grabbable_query.get_mut(event.interactable) {
Ok(mut grabbable_transform) => {
// info!("we got a grabbable");
//now we need the location of our interactor
match interactor_query.get_mut(event.interactor) {
Ok(mut interactor_transform) => {
match *interactor_transform.2 {
XRSelection::Empty => {
match interactor_transform.1 {
XRInteractorState::Idle => match grabbable_transform.4 {
Some(mut thing) => {
*thing = RigidBody::Dynamic;
*interactor_transform.2 = XRSelection::Empty;
}
None => (),
},
XRInteractorState::Selecting => {
// info!("its a direct interactor?");
match grabbable_transform.4 {
Some(mut thing) => {
*thing = RigidBody::KinematicPositionBased;
*interactor_transform.2 =
XRSelection::Full(grabbable_transform.0);
//raise enter ghost hand event
writer.send(GhostHandEvent {
hand: *interactor_transform.3,
desired_state: true,
});
}
None => (),
}
*grabbable_transform.1 =
interactor_transform.0.compute_transform();
}
}
}
XRSelection::Full(ent) => {
info!("nah bro we holding something");
match grabbable_transform.0 == ent {
true => {
*grabbable_transform.1 =
interactor_transform.0.compute_transform();
}
false => {}
}
match interactor_transform.1 {
XRInteractorState::Idle => {
*interactor_transform.2 = XRSelection::Empty;
//reset timers to make hands physical again
match *interactor_transform.3 {
Hand::Left => timers.left.reset(),
Hand::Right => timers.right.reset(),
}
}
XRInteractorState::Selecting => {}
}
}
}
}
Err(_) => {
// info!("not a direct interactor")
}
}
}
Err(_) => {
// info!("not a grabbable?")
}
}
}
}

View File

@@ -1,3 +0,0 @@
fn main() {
demo::main();
}

View File

@@ -1,80 +0,0 @@
use bevy::{
prelude::{
shape, Assets, Camera3dBundle, Color, Commands, Mesh, PbrBundle, PointLight,
PointLightBundle, ResMut, SpatialBundle, StandardMaterial, Transform, Vec3,
},
transform::TransformBundle,
utils::default,
};
use bevy_oxr::xr_input::interactions::{Touched, XRInteractable, XRInteractableState};
use bevy_rapier3d::{
prelude::{Collider, RigidBody, Group, CollisionGroups},
render::ColliderDebugColor,
};
use crate::Grabbable;
/// set up a simple 3D scene
pub fn setup_scene(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
/*
* workbench plane
*/
let ground_size = 2.5;
let ground_height = 0.825;
let ground_thickness = 0.05;
// plane
commands.spawn((
PbrBundle {
mesh: meshes.add(shape::Plane::from_size(5.0).into()),
material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
transform: Transform::from_xyz(0.0, ground_height, 0.0),
..default()
},
RigidBody::Fixed,
Collider::cuboid(ground_size, ground_thickness, ground_size),
CollisionGroups::new(Group::GROUP_3, Group::ALL),
));
// cube
commands.spawn((
PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 0.1 })),
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
transform: Transform::from_xyz(0.0, 1.0, 0.0),
..default()
},
RigidBody::Dynamic,
Collider::cuboid(0.05, 0.05, 0.05),
ColliderDebugColor(Color::hsl(220.0, 1.0, 0.3)),
XRInteractable,
XRInteractableState::default(),
Grabbable,
Touched(false),
));
// light
// commands.spawn(PointLightBundle {
// point_light: PointLight {
// intensity: 1500.0,
// shadows_enabled: true,
// ..default()
// },
// transform: Transform::from_xyz(4.0, 8.0, 4.0),
// ..default()
// });
// camera
commands.spawn((Camera3dBundle {
transform: Transform::from_xyz(0.25, 1.25, 0.0).looking_at(
Vec3 {
x: -0.548,
y: -0.161,
z: -0.137,
},
Vec3::Y,
),
..default()
},));
}

View File

@@ -1,188 +0,0 @@
use bevy::diagnostic::LogDiagnosticsPlugin;
use bevy::prelude::*;
use bevy::transform::components::Transform;
use bevy_oxr::resources::XrViews;
use bevy_oxr::xr_input::hands::common::{HandInputDebugRenderer, OpenXrHandInput};
use bevy_oxr::xr_input::interactions::{
InteractionEvent, XRDirectInteractor, XRInteractorState, XRRayInteractor, XRSocketInteractor,
};
use bevy_oxr::xr_input::prototype_locomotion::{proto_locomotion, PrototypeLocomotionConfig};
use bevy_oxr::xr_input::trackers::{
AimPose, OpenXRController, OpenXRLeftController, OpenXRRightController, OpenXRTracker,
OpenXRTrackingRoot,
};
use bevy_oxr::xr_input::Vec3Conv;
use bevy_oxr::DefaultXrPlugins;
use wgpu::{Extent3d, TextureDimension, TextureFormat};
fn main() {
color_eyre::install().unwrap();
App::new()
.add_plugins(DefaultXrPlugins)
.add_plugins(LogDiagnosticsPlugin::default())
.add_systems(Startup, setup)
.add_systems(Update, (proto_locomotion, pull_to_ground).chain())
.insert_resource(PrototypeLocomotionConfig::default())
.add_systems(Startup, spawn_controllers_example)
.add_plugins(OpenXrHandInput)
.add_plugins(HandInputDebugRenderer)
.add_event::<InteractionEvent>()
.run();
}
#[derive(Component)]
struct Globe {
radius: f32,
}
/// Creates a colorful test pattern
fn uv_debug_texture() -> Image {
const TEXTURE_SIZE: usize = 8;
let mut palette: [u8; 32] = [
255, 102, 159, 255, 255, 159, 102, 255, 236, 255, 102, 255, 121, 255, 102, 255, 102, 255,
198, 255, 102, 198, 255, 255, 121, 102, 255, 255, 236, 102, 255, 255,
];
let mut texture_data = [0; TEXTURE_SIZE * TEXTURE_SIZE * 4];
for y in 0..TEXTURE_SIZE {
let offset = TEXTURE_SIZE * y * 4;
texture_data[offset..(offset + TEXTURE_SIZE * 4)].copy_from_slice(&palette);
palette.rotate_right(4);
}
Image::new_fill(
Extent3d {
width: TEXTURE_SIZE as u32,
height: TEXTURE_SIZE as u32,
depth_or_array_layers: 1,
},
TextureDimension::D2,
&texture_data,
TextureFormat::Rgba8UnormSrgb,
)
}
/// set up a simple 3D scene
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut images: ResMut<Assets<Image>>,
) {
// plane
let radius = 5.0;
commands.spawn((
PbrBundle {
mesh: meshes.add(
shape::UVSphere {
radius,
sectors: 10,
stacks: 10,
}
.try_into()
.unwrap(),
),
material: materials.add(StandardMaterial {
base_color_texture: Some(images.add(uv_debug_texture())),
..default()
}),
transform: Transform::from_xyz(0.0, -radius, 0.0),
..default()
},
Globe { radius },
));
// cube
commands.spawn(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 0.1 })),
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
transform: Transform::from_xyz(0.0, 0.5, 0.0),
..default()
});
// socket
commands.spawn((
SpatialBundle {
transform: Transform::from_xyz(0.0, 0.5, 1.0),
..default()
},
XRInteractorState::Selecting,
XRSocketInteractor,
));
// light
commands.spawn(PointLightBundle {
point_light: PointLight {
intensity: 1500.0,
shadows_enabled: true,
..default()
},
transform: Transform::from_xyz(4.0, 8.0, 4.0),
..default()
});
// camera
commands.spawn((Camera3dBundle {
transform: Transform::from_xyz(0.25, 1.25, 0.0).looking_at(
Vec3 {
x: -0.548,
y: -0.161,
z: -0.137,
},
Vec3::Y,
),
..default()
},));
}
fn spawn_controllers_example(mut commands: Commands) {
//left hand
commands.spawn((
OpenXRLeftController,
OpenXRController,
OpenXRTracker,
SpatialBundle::default(),
XRRayInteractor,
AimPose(Transform::default()),
XRInteractorState::default(),
));
//right hand
commands.spawn((
OpenXRRightController,
OpenXRController,
OpenXRTracker,
SpatialBundle::default(),
XRDirectInteractor,
XRInteractorState::default(),
));
}
fn pull_to_ground(
time: Res<Time>,
mut tracking_root_query: Query<&mut Transform, (With<OpenXRTrackingRoot>, Without<Globe>)>,
globe: Query<(&Transform, &Globe), Without<OpenXRTrackingRoot>>,
views: ResMut<XrViews>,
) {
let mut root = tracking_root_query.single_mut();
let (globe_pos, globe) = globe.single();
// Get player position (position of playground + position within playground)
let v = views.lock().unwrap();
let Some(view) = v.get(0) else { return };
let mut hmd_translation = view.pose.position.to_vec3();
hmd_translation.y = 0.0;
let local = root.translation;
let offset = root.rotation.mul_vec3(hmd_translation);
let global = offset + local;
let adjustment_rate = (time.delta_seconds() * 10.0).min(1.0);
// Lower player onto sphere
let up = (global - globe_pos.translation).normalize();
let diff = up * globe.radius + globe_pos.translation - offset - root.translation;
root.translation += diff * adjustment_rate;
// Rotate player to be upright on sphere
let angle_diff = Quat::from_rotation_arc(root.up(), up);
let point = root.translation + offset;
root.rotate_around(point, Quat::IDENTITY.slerp(angle_diff, adjustment_rate));
}

View File

@@ -1,221 +0,0 @@
use bevy::diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin};
use bevy::prelude::*;
use bevy::transform::components::Transform;
use bevy_oxr::input::XrInput;
use bevy_oxr::resources::{XrFrameState, XrSession};
use bevy_oxr::xr_input::actions::XrActionSets;
use bevy_oxr::xr_input::hands::common::{HandInputDebugRenderer, OpenXrHandInput};
use bevy_oxr::xr_input::interactions::{
draw_interaction_gizmos, draw_socket_gizmos, interactions, socket_interactions,
update_interactable_states, InteractionEvent, Touched, XRDirectInteractor, XRInteractable,
XRInteractableState, XRInteractorState, XRRayInteractor, XRSocketInteractor,
};
use bevy_oxr::xr_input::oculus_touch::OculusController;
use bevy_oxr::xr_input::prototype_locomotion::{proto_locomotion, PrototypeLocomotionConfig};
use bevy_oxr::xr_input::trackers::{
AimPose, OpenXRController, OpenXRLeftController, OpenXRRightController, OpenXRTracker,
};
use bevy_oxr::xr_input::Hand;
use bevy_oxr::DefaultXrPlugins;
fn main() {
color_eyre::install().unwrap();
info!("Running `openxr-6dof` skill");
App::new()
.add_plugins(DefaultXrPlugins)
//.add_plugins(OpenXrDebugRenderer) //new debug renderer adds gizmos to
.add_plugins(LogDiagnosticsPlugin::default())
.add_plugins(FrameTimeDiagnosticsPlugin)
.add_systems(Startup, setup)
.add_systems(Update, proto_locomotion)
.insert_resource(PrototypeLocomotionConfig::default())
.add_systems(Startup, spawn_controllers_example)
.add_plugins(OpenXrHandInput)
.add_plugins(HandInputDebugRenderer)
.add_systems(
Update,
draw_interaction_gizmos.after(update_interactable_states),
)
.add_systems(Update, draw_socket_gizmos.after(update_interactable_states))
.add_systems(Update, interactions.before(update_interactable_states))
.add_systems(
Update,
socket_interactions.before(update_interactable_states),
)
.add_systems(Update, prototype_interaction_input)
.add_systems(Update, update_interactable_states)
.add_systems(Update, update_grabbables.after(update_interactable_states))
.add_event::<InteractionEvent>()
.run();
}
/// set up a simple 3D scene
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// plane
commands.spawn(PbrBundle {
mesh: meshes.add(shape::Plane::from_size(5.0).into()),
material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
..default()
});
// cube
commands.spawn(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 0.1 })),
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
transform: Transform::from_xyz(0.0, 0.5, 0.0),
..default()
});
// socket
commands.spawn((
SpatialBundle {
transform: Transform::from_xyz(0.0, 0.5, 1.0),
..default()
},
XRInteractorState::Selecting,
XRSocketInteractor,
));
// light
commands.spawn(PointLightBundle {
point_light: PointLight {
intensity: 1500.0,
shadows_enabled: true,
..default()
},
transform: Transform::from_xyz(4.0, 8.0, 4.0),
..default()
});
// camera
commands.spawn((Camera3dBundle {
transform: Transform::from_xyz(0.25, 1.25, 0.0).looking_at(
Vec3 {
x: -0.548,
y: -0.161,
z: -0.137,
},
Vec3::Y,
),
..default()
},));
//simple interactable
commands.spawn((
SpatialBundle {
transform: Transform::from_xyz(0.0, 1.0, 0.0),
..default()
},
XRInteractable,
XRInteractableState::default(),
Grabbable,
Touched(false),
));
}
fn spawn_controllers_example(mut commands: Commands) {
//left hand
commands.spawn((
OpenXRLeftController,
OpenXRController,
OpenXRTracker,
SpatialBundle::default(),
XRRayInteractor,
AimPose(Transform::default()),
XRInteractorState::default(),
));
//right hand
commands.spawn((
OpenXRRightController,
OpenXRController,
OpenXRTracker,
SpatialBundle::default(),
XRDirectInteractor,
XRInteractorState::default(),
));
}
fn prototype_interaction_input(
oculus_controller: Res<OculusController>,
frame_state: Res<XrFrameState>,
xr_input: Res<XrInput>,
session: Res<XrSession>,
mut right_interactor_query: Query<
(&mut XRInteractorState),
(
With<XRDirectInteractor>,
With<OpenXRRightController>,
Without<OpenXRLeftController>,
),
>,
mut left_interactor_query: Query<
(&mut XRInteractorState),
(
With<XRRayInteractor>,
With<OpenXRLeftController>,
Without<OpenXRRightController>,
),
>,
action_sets: Res<XrActionSets>,
) {
//lock frame
let frame_state = *frame_state.lock().unwrap();
//get controller
let controller = oculus_controller.get_ref(&session, &frame_state, &xr_input, &action_sets);
//get controller triggers
let left_trigger = controller.trigger(Hand::Left);
let right_trigger = controller.trigger(Hand::Right);
//get the interactors and do state stuff
let mut left_state = left_interactor_query.single_mut();
if left_trigger > 0.8 {
*left_state = XRInteractorState::Selecting;
} else {
*left_state = XRInteractorState::Idle;
}
let mut right_state = right_interactor_query.single_mut();
if right_trigger > 0.8 {
*right_state = XRInteractorState::Selecting;
} else {
*right_state = XRInteractorState::Idle;
}
}
#[derive(Component)]
pub struct Grabbable;
pub fn update_grabbables(
mut events: EventReader<InteractionEvent>,
mut grabbable_query: Query<(&mut Transform, With<Grabbable>, Without<XRDirectInteractor>)>,
interactor_query: Query<(&GlobalTransform, &XRInteractorState, Without<Grabbable>)>,
) {
//so basically the idea is to try all the events?
for event in events.read() {
// info!("some event");
match grabbable_query.get_mut(event.interactable) {
Ok(mut grabbable_transform) => {
// info!("we got a grabbable");
//now we need the location of our interactor
match interactor_query.get(event.interactor) {
Ok(interactor_transform) => {
match interactor_transform.1 {
XRInteractorState::Idle => (),
XRInteractorState::Selecting => {
// info!("its a direct interactor?");
*grabbable_transform.0 = interactor_transform.0.compute_transform();
}
}
}
Err(_) => {
// info!("not a direct interactor")
}
}
}
Err(_) => {
// info!("not a grabbable?")
}
}
}
}

View File

@@ -0,0 +1,35 @@
#[cfg(feature = "vulkan")]
pub mod vulkan;
use bevy::window::RawHandleWrapper;
use openxr::{FrameStream, FrameWaiter, Instance, Swapchain, ViewConfigurationType};
use crate::{error::XrError, types::GraphicsFeatures};
use super::OXrSession;
const VIEW_TYPE: ViewConfigurationType = ViewConfigurationType::PRIMARY_STEREO;
pub enum OXrGraphics {
#[cfg(feature = "vulkan")]
Vulkan {
swapchain: Swapchain<openxr::Vulkan>,
frame_stream: FrameStream<openxr::Vulkan>,
frame_waiter: FrameWaiter,
},
}
pub fn init_oxr_graphics(
instance: Instance,
graphics: GraphicsFeatures,
window: Option<RawHandleWrapper>,
) -> Result<OXrSession, XrError> {
#[cfg(feature = "vulkan")]
if graphics.vulkan {
if let Ok(session) = vulkan::init_oxr_graphics(instance, window) {
return Ok(session);
}
}
Err(XrError {})
}

View File

@@ -1,80 +1,24 @@
use std::ffi::{c_void, CString};
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, Mutex};
use anyhow::Context;
use ash::vk::{self, Handle};
use bevy::log::info;
use bevy::math::uvec2;
use bevy::prelude::*;
use bevy::render::renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue};
use bevy::render::renderer::{RenderAdapter, RenderAdapterInfo, RenderQueue};
use bevy::window::RawHandleWrapper;
use openxr as xr;
use wgpu::Instance;
use openxr::Instance;
use crate::input::XrInput;
use crate::resources::{
Swapchain, SwapchainInner, XrEnvironmentBlendMode, XrFormat, XrFrameState, XrFrameWaiter,
XrInstance, XrResolution, XrSession, XrSessionRunning, XrSwapchain, XrViews,
};
use crate::VIEW_TYPE;
use crate::backend::graphics::{OXrGraphics, VIEW_TYPE};
use crate::backend::OXrSession;
use crate::error::XrError;
pub fn initialize_xr_graphics(
pub fn init_oxr_graphics(
xr_instance: Instance,
window: Option<RawHandleWrapper>,
// Horrible hack to get the Handtacking extension Loaded, Replace with good system to load
// any extension at some point
) -> anyhow::Result<(
RenderDevice,
RenderQueue,
RenderAdapterInfo,
RenderAdapter,
Instance,
XrInstance,
XrSession,
XrEnvironmentBlendMode,
XrResolution,
XrFormat,
XrSessionRunning,
XrFrameWaiter,
XrSwapchain,
XrInput,
XrViews,
XrFrameState,
// Horrible hack to get the Handtacking extension Loaded, Replace with good system to load
// any extension at some point
)> {
) -> Result<OXrSession, XrError> {
use wgpu_hal::{api::Vulkan as V, Api};
let xr_entry = super::xr_entry()?;
#[cfg(target_os = "android")]
xr_entry.initialize_android_loader()?;
let available_extensions = xr_entry.enumerate_extensions()?;
assert!(available_extensions.khr_vulkan_enable2);
info!("available xr exts: {:#?}", available_extensions);
let mut enabled_extensions = xr::ExtensionSet::default();
enabled_extensions.khr_vulkan_enable2 = true;
#[cfg(target_os = "android")]
{
enabled_extensions.khr_android_create_instance = true;
}
enabled_extensions.ext_hand_tracking = available_extensions.ext_hand_tracking;
// enabled_extensions.ext_hand_joints_motion_range = available_extensions.ext_hand_joints_motion_range;
let available_layers = xr_entry.enumerate_layers()?;
info!("available xr layers: {:#?}", available_layers);
let xr_instance = xr_entry.create_instance(
&xr::ApplicationInfo {
application_name: "Ambient",
..Default::default()
},
&enabled_extensions,
&[],
)?;
info!("created instance");
let instance_props = xr_instance.properties()?;
let xr_system_id = xr_instance.system(xr::FormFactor::HEAD_MOUNTED_DISPLAY)?;
info!("created system");
@@ -113,10 +57,10 @@ pub fn initialize_xr_graphics(
);
}
let vk_entry = unsafe { ash::Entry::load() }?;
let vk_entry = unsafe { ash::Entry::load() }.map_err(|_| XrError {})?;
let flags = wgpu_hal::InstanceFlags::empty();
let extensions =
<V as Api>::Instance::required_extensions(&vk_entry, vk_target_version, flags)?;
let extensions = <V as Api>::Instance::required_extensions(&vk_entry, vk_target_version, flags)
.map_err(|_| XrError {})?;
let device_extensions = vec![
ash::extensions::khr::Swapchain::name(),
ash::extensions::khr::DrawIndirectCount::name(),
@@ -131,7 +75,7 @@ pub fn initialize_xr_graphics(
let vk_instance = unsafe {
let extensions_cchar: Vec<_> = extensions.iter().map(|s| s.as_ptr()).collect();
let app_name = CString::new("Ambient")?;
let app_name = CString::new("Bevy").map_err(|_| XrError {})?;
let vk_app_info = vk::ApplicationInfo::builder()
.application_name(&app_name)
.application_version(1)
@@ -148,11 +92,8 @@ pub fn initialize_xr_graphics(
.enabled_extension_names(&extensions_cchar) as *const _
as *const _,
)
.context("XR error creating Vulkan instance")
.unwrap()
.map_err(vk::Result::from_raw)
.context("Vulkan error creating Vulkan instance")
.unwrap();
.map_err(|_| XrError {})?
.map_err(|_| XrError {})?;
ash::Instance::load(
vk_entry.static_fn(),
@@ -186,7 +127,8 @@ pub fn initialize_xr_graphics(
flags,
false,
Some(Box::new(())),
)?
)
.map_err(|_| XrError {})?
};
let wgpu_features = wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES
@@ -196,7 +138,7 @@ pub fn initialize_xr_graphics(
let wgpu_exposed_adapter = wgpu_vk_instance
.expose_adapter(vk_physical_device)
.context("failed to expose adapter")?;
.ok_or(XrError {})?;
let enabled_extensions = wgpu_exposed_adapter
.adapter
@@ -232,9 +174,8 @@ pub fn initialize_xr_graphics(
vk_physical_device.as_raw() as _,
&info as *const _ as *const _,
)
.context("XR error creating Vulkan device")?
.map_err(vk::Result::from_raw)
.context("Vulkan error creating Vulkan device")?;
.map_err(|_| XrError {})?
.map_err(|_| XrError {})?;
ash::Device::load(vk_instance.fp_v1_0(), vk::Device::from_raw(vk_device as _))
};
@@ -249,7 +190,8 @@ pub fn initialize_xr_graphics(
family_info.queue_family_index,
0,
)
}?;
}
.map_err(|_| XrError {})?;
(
wgpu_open_device,
@@ -278,9 +220,10 @@ pub fn initialize_xr_graphics(
},
None,
)
}?;
}
.map_err(|_| XrError {})?;
let (session, frame_wait, frame_stream) = unsafe {
let (session, frame_waiter, frame_stream) = unsafe {
xr_instance.create_session::<xr::Vulkan>(
xr_system_id,
&xr::vulkan::SessionCreateInfo {
@@ -312,12 +255,12 @@ pub fn initialize_xr_graphics(
views[0].recommended_image_rect_height,
);
let handle = session
let swapchain = session
.create_swapchain(&xr::SwapchainCreateInfo {
create_flags: xr::SwapchainCreateFlags::EMPTY,
usage_flags: xr::SwapchainUsageFlags::COLOR_ATTACHMENT
| xr::SwapchainUsageFlags::SAMPLED,
format: wgpu_to_vulkan(swapchain_format).as_raw() as _,
format: wgpu_to_vulkan(swapchain_format).ok_or(XrError {})?.as_raw() as _,
// The Vulkan graphics pipeline we create is not set up for multisampling,
// so we hardcode this to 1. If we used a proper multisampling setup, we
// could set this to `views[0].recommended_swapchain_sample_count`.
@@ -329,9 +272,9 @@ pub fn initialize_xr_graphics(
mip_count: 1,
})
.unwrap();
let images = handle.enumerate_images().unwrap();
let images = swapchain.enumerate_images().unwrap();
let buffers = images
let buffers: Vec<_> = images
.into_iter()
.map(|color_image| {
let color_image = vk::Image::from_raw(color_image);
@@ -381,101 +324,155 @@ pub fn initialize_xr_graphics(
})
.collect();
Ok((
wgpu_device.into(),
RenderQueue(Arc::new(wgpu_queue)),
RenderAdapterInfo(wgpu_adapter.get_info()),
RenderAdapter(Arc::new(wgpu_adapter)),
wgpu_instance,
xr_instance.clone().into(),
session.clone().into_any_graphics().into(),
blend_mode.into(),
resolution.into(),
swapchain_format.into(),
AtomicBool::new(false).into(),
Mutex::new(frame_wait).into(),
Swapchain::Vulkan(SwapchainInner {
stream: Mutex::new(frame_stream),
handle: Mutex::new(handle),
buffers,
image_index: Mutex::new(0),
})
.into(),
XrInput::new(xr_instance, session.into_any_graphics())?,
Mutex::default().into(),
Mutex::new(xr::FrameState {
predicted_display_time: xr::Time::from_nanos(1),
predicted_display_period: xr::Duration::from_nanos(1),
should_render: true,
})
.into(),
// Horrible hack to get the Handtacking extension Loaded, Replace with good system to load
// any extension at some point
))
OXrSession {
graphics: OXrGraphics::Vulkan {
swapchain,
frame_stream,
frame_waiter,
},
device: wgpu_device.into(),
queue: RenderQueue(Arc::new(wgpu_queue)),
adapter_info: RenderAdapterInfo(wgpu_adapter.get_info()),
adapter: RenderAdapter(Arc::new(wgpu_adapter)),
session: session.into_any_graphics(),
blend_mode,
resolution,
format: swapchain_format,
buffers,
image_index: Mutex::new(0),
instance: xr_instance,
};
todo!()
}
fn wgpu_to_vulkan(format: wgpu::TextureFormat) -> vk::Format {
use vk::Format;
match format {
wgpu::TextureFormat::R8Unorm => Format::R8_UNORM,
wgpu::TextureFormat::R8Snorm => Format::R8_SNORM,
wgpu::TextureFormat::R8Uint => Format::R8_UINT,
wgpu::TextureFormat::R8Sint => Format::R8_SINT,
wgpu::TextureFormat::R16Uint => Format::R16_UINT,
wgpu::TextureFormat::R16Sint => Format::R16_SINT,
wgpu::TextureFormat::R16Unorm => Format::R16_UNORM,
wgpu::TextureFormat::R16Snorm => Format::R16_SNORM,
wgpu::TextureFormat::R16Float => Format::R16_SFLOAT,
wgpu::TextureFormat::Rg8Unorm => Format::R8G8_UNORM,
wgpu::TextureFormat::Rg8Snorm => Format::R8G8_SNORM,
wgpu::TextureFormat::Rg8Uint => Format::R8G8_UINT,
wgpu::TextureFormat::Rg8Sint => Format::R8G8_SINT,
wgpu::TextureFormat::R32Uint => Format::R32_UINT,
wgpu::TextureFormat::R32Sint => Format::R32_SINT,
wgpu::TextureFormat::R32Float => Format::R32_SFLOAT,
wgpu::TextureFormat::Rg16Uint => Format::R16G16_UINT,
wgpu::TextureFormat::Rg16Sint => Format::R16G16_SINT,
wgpu::TextureFormat::Rg16Unorm => Format::R16G16_UNORM,
wgpu::TextureFormat::Rg16Snorm => Format::R16G16_SNORM,
wgpu::TextureFormat::Rg16Float => Format::R16G16_SFLOAT,
wgpu::TextureFormat::Rgba8Unorm => Format::R8G8B8A8_UNORM,
wgpu::TextureFormat::Rgba8UnormSrgb => Format::R8G8B8A8_SRGB,
wgpu::TextureFormat::Rgba8Snorm => Format::R8G8B8A8_SNORM,
wgpu::TextureFormat::Rgba8Uint => Format::R8G8B8A8_UINT,
wgpu::TextureFormat::Rgba8Sint => Format::R8G8B8A8_SINT,
wgpu::TextureFormat::Bgra8Unorm => Format::B8G8R8A8_UNORM,
wgpu::TextureFormat::Bgra8UnormSrgb => Format::B8G8R8A8_SRGB,
wgpu::TextureFormat::Rgb9e5Ufloat => Format::E5B9G9R9_UFLOAT_PACK32, // this might be the wrong type??? i can't tell
wgpu::TextureFormat::Rgb10a2Unorm => Format::A2R10G10B10_UNORM_PACK32,
wgpu::TextureFormat::Rg11b10Float => panic!("this texture type invokes nothing but fear within my soul and i don't think vulkan has a proper type for this"),
wgpu::TextureFormat::Rg32Uint => Format::R32G32_UINT,
wgpu::TextureFormat::Rg32Sint => Format::R32G32_SINT,
wgpu::TextureFormat::Rg32Float => Format::R32G32_SFLOAT,
wgpu::TextureFormat::Rgba16Uint => Format::R16G16B16A16_UINT,
wgpu::TextureFormat::Rgba16Sint => Format::R16G16B16A16_SINT,
wgpu::TextureFormat::Rgba16Unorm => Format::R16G16B16A16_UNORM,
wgpu::TextureFormat::Rgba16Snorm => Format::R16G16B16A16_SNORM,
wgpu::TextureFormat::Rgba16Float => Format::R16G16B16A16_SFLOAT,
wgpu::TextureFormat::Rgba32Uint => Format::R32G32B32A32_UINT,
wgpu::TextureFormat::Rgba32Sint => Format::R32G32B32A32_SINT,
wgpu::TextureFormat::Rgba32Float => Format::R32G32B32A32_SFLOAT,
wgpu::TextureFormat::Stencil8 => Format::S8_UINT,
wgpu::TextureFormat::Depth16Unorm => Format::D16_UNORM,
wgpu::TextureFormat::Depth24Plus => Format::X8_D24_UNORM_PACK32,
wgpu::TextureFormat::Depth24PlusStencil8 => Format::D24_UNORM_S8_UINT,
wgpu::TextureFormat::Depth32Float => Format::D32_SFLOAT,
wgpu::TextureFormat::Depth32FloatStencil8 => Format::D32_SFLOAT_S8_UINT,
wgpu::TextureFormat::Etc2Rgb8Unorm => Format::ETC2_R8G8B8_UNORM_BLOCK,
wgpu::TextureFormat::Etc2Rgb8UnormSrgb => Format::ETC2_R8G8B8_SRGB_BLOCK,
wgpu::TextureFormat::Etc2Rgb8A1Unorm => Format::ETC2_R8G8B8A1_UNORM_BLOCK,
wgpu::TextureFormat::Etc2Rgb8A1UnormSrgb => Format::ETC2_R8G8B8A1_SRGB_BLOCK,
wgpu::TextureFormat::Etc2Rgba8Unorm => Format::ETC2_R8G8B8A8_UNORM_BLOCK,
wgpu::TextureFormat::Etc2Rgba8UnormSrgb => Format::ETC2_R8G8B8A8_SRGB_BLOCK,
wgpu::TextureFormat::EacR11Unorm => Format::EAC_R11_UNORM_BLOCK,
wgpu::TextureFormat::EacR11Snorm => Format::EAC_R11_SNORM_BLOCK,
wgpu::TextureFormat::EacRg11Unorm => Format::EAC_R11G11_UNORM_BLOCK,
wgpu::TextureFormat::EacRg11Snorm => Format::EAC_R11G11_SNORM_BLOCK,
wgpu::TextureFormat::Astc { .. } => panic!("please god kill me now"),
_ => panic!("fuck no")
}
fn wgpu_to_vulkan(format: wgpu::TextureFormat) -> Option<vk::Format> {
// Copied with minor modification from:
// https://github.com/gfx-rs/wgpu/blob/a7defb723f856d946d6d220e9897d20dbb7b8f61/wgpu-hal/src/vulkan/conv.rs#L5-L151
// license: MIT OR Apache-2.0
use ash::vk::Format as F;
use wgpu::TextureFormat as Tf;
use wgpu::{AstcBlock, AstcChannel};
Some(match format {
Tf::R8Unorm => F::R8_UNORM,
Tf::R8Snorm => F::R8_SNORM,
Tf::R8Uint => F::R8_UINT,
Tf::R8Sint => F::R8_SINT,
Tf::R16Uint => F::R16_UINT,
Tf::R16Sint => F::R16_SINT,
Tf::R16Unorm => F::R16_UNORM,
Tf::R16Snorm => F::R16_SNORM,
Tf::R16Float => F::R16_SFLOAT,
Tf::Rg8Unorm => F::R8G8_UNORM,
Tf::Rg8Snorm => F::R8G8_SNORM,
Tf::Rg8Uint => F::R8G8_UINT,
Tf::Rg8Sint => F::R8G8_SINT,
Tf::Rg16Unorm => F::R16G16_UNORM,
Tf::Rg16Snorm => F::R16G16_SNORM,
Tf::R32Uint => F::R32_UINT,
Tf::R32Sint => F::R32_SINT,
Tf::R32Float => F::R32_SFLOAT,
Tf::Rg16Uint => F::R16G16_UINT,
Tf::Rg16Sint => F::R16G16_SINT,
Tf::Rg16Float => F::R16G16_SFLOAT,
Tf::Rgba8Unorm => F::R8G8B8A8_UNORM,
Tf::Rgba8UnormSrgb => F::R8G8B8A8_SRGB,
Tf::Bgra8UnormSrgb => F::B8G8R8A8_SRGB,
Tf::Rgba8Snorm => F::R8G8B8A8_SNORM,
Tf::Bgra8Unorm => F::B8G8R8A8_UNORM,
Tf::Rgba8Uint => F::R8G8B8A8_UINT,
Tf::Rgba8Sint => F::R8G8B8A8_SINT,
Tf::Rgb10a2Unorm => F::A2B10G10R10_UNORM_PACK32,
Tf::Rg11b10Float => F::B10G11R11_UFLOAT_PACK32,
Tf::Rg32Uint => F::R32G32_UINT,
Tf::Rg32Sint => F::R32G32_SINT,
Tf::Rg32Float => F::R32G32_SFLOAT,
Tf::Rgba16Uint => F::R16G16B16A16_UINT,
Tf::Rgba16Sint => F::R16G16B16A16_SINT,
Tf::Rgba16Unorm => F::R16G16B16A16_UNORM,
Tf::Rgba16Snorm => F::R16G16B16A16_SNORM,
Tf::Rgba16Float => F::R16G16B16A16_SFLOAT,
Tf::Rgba32Uint => F::R32G32B32A32_UINT,
Tf::Rgba32Sint => F::R32G32B32A32_SINT,
Tf::Rgba32Float => F::R32G32B32A32_SFLOAT,
Tf::Depth32Float => F::D32_SFLOAT,
Tf::Depth32FloatStencil8 => F::D32_SFLOAT_S8_UINT,
Tf::Depth24Plus | Tf::Depth24PlusStencil8 | Tf::Stencil8 => return None, // Dependent on device properties
Tf::Depth16Unorm => F::D16_UNORM,
Tf::Rgb9e5Ufloat => F::E5B9G9R9_UFLOAT_PACK32,
Tf::Bc1RgbaUnorm => F::BC1_RGBA_UNORM_BLOCK,
Tf::Bc1RgbaUnormSrgb => F::BC1_RGBA_SRGB_BLOCK,
Tf::Bc2RgbaUnorm => F::BC2_UNORM_BLOCK,
Tf::Bc2RgbaUnormSrgb => F::BC2_SRGB_BLOCK,
Tf::Bc3RgbaUnorm => F::BC3_UNORM_BLOCK,
Tf::Bc3RgbaUnormSrgb => F::BC3_SRGB_BLOCK,
Tf::Bc4RUnorm => F::BC4_UNORM_BLOCK,
Tf::Bc4RSnorm => F::BC4_SNORM_BLOCK,
Tf::Bc5RgUnorm => F::BC5_UNORM_BLOCK,
Tf::Bc5RgSnorm => F::BC5_SNORM_BLOCK,
Tf::Bc6hRgbUfloat => F::BC6H_UFLOAT_BLOCK,
Tf::Bc6hRgbFloat => F::BC6H_SFLOAT_BLOCK,
Tf::Bc7RgbaUnorm => F::BC7_UNORM_BLOCK,
Tf::Bc7RgbaUnormSrgb => F::BC7_SRGB_BLOCK,
Tf::Etc2Rgb8Unorm => F::ETC2_R8G8B8_UNORM_BLOCK,
Tf::Etc2Rgb8UnormSrgb => F::ETC2_R8G8B8_SRGB_BLOCK,
Tf::Etc2Rgb8A1Unorm => F::ETC2_R8G8B8A1_UNORM_BLOCK,
Tf::Etc2Rgb8A1UnormSrgb => F::ETC2_R8G8B8A1_SRGB_BLOCK,
Tf::Etc2Rgba8Unorm => F::ETC2_R8G8B8A8_UNORM_BLOCK,
Tf::Etc2Rgba8UnormSrgb => F::ETC2_R8G8B8A8_SRGB_BLOCK,
Tf::EacR11Unorm => F::EAC_R11_UNORM_BLOCK,
Tf::EacR11Snorm => F::EAC_R11_SNORM_BLOCK,
Tf::EacRg11Unorm => F::EAC_R11G11_UNORM_BLOCK,
Tf::EacRg11Snorm => F::EAC_R11G11_SNORM_BLOCK,
Tf::Astc { block, channel } => match channel {
AstcChannel::Unorm => match block {
AstcBlock::B4x4 => F::ASTC_4X4_UNORM_BLOCK,
AstcBlock::B5x4 => F::ASTC_5X4_UNORM_BLOCK,
AstcBlock::B5x5 => F::ASTC_5X5_UNORM_BLOCK,
AstcBlock::B6x5 => F::ASTC_6X5_UNORM_BLOCK,
AstcBlock::B6x6 => F::ASTC_6X6_UNORM_BLOCK,
AstcBlock::B8x5 => F::ASTC_8X5_UNORM_BLOCK,
AstcBlock::B8x6 => F::ASTC_8X6_UNORM_BLOCK,
AstcBlock::B8x8 => F::ASTC_8X8_UNORM_BLOCK,
AstcBlock::B10x5 => F::ASTC_10X5_UNORM_BLOCK,
AstcBlock::B10x6 => F::ASTC_10X6_UNORM_BLOCK,
AstcBlock::B10x8 => F::ASTC_10X8_UNORM_BLOCK,
AstcBlock::B10x10 => F::ASTC_10X10_UNORM_BLOCK,
AstcBlock::B12x10 => F::ASTC_12X10_UNORM_BLOCK,
AstcBlock::B12x12 => F::ASTC_12X12_UNORM_BLOCK,
},
AstcChannel::UnormSrgb => match block {
AstcBlock::B4x4 => F::ASTC_4X4_SRGB_BLOCK,
AstcBlock::B5x4 => F::ASTC_5X4_SRGB_BLOCK,
AstcBlock::B5x5 => F::ASTC_5X5_SRGB_BLOCK,
AstcBlock::B6x5 => F::ASTC_6X5_SRGB_BLOCK,
AstcBlock::B6x6 => F::ASTC_6X6_SRGB_BLOCK,
AstcBlock::B8x5 => F::ASTC_8X5_SRGB_BLOCK,
AstcBlock::B8x6 => F::ASTC_8X6_SRGB_BLOCK,
AstcBlock::B8x8 => F::ASTC_8X8_SRGB_BLOCK,
AstcBlock::B10x5 => F::ASTC_10X5_SRGB_BLOCK,
AstcBlock::B10x6 => F::ASTC_10X6_SRGB_BLOCK,
AstcBlock::B10x8 => F::ASTC_10X8_SRGB_BLOCK,
AstcBlock::B10x10 => F::ASTC_10X10_SRGB_BLOCK,
AstcBlock::B12x10 => F::ASTC_12X10_SRGB_BLOCK,
AstcBlock::B12x12 => F::ASTC_12X12_SRGB_BLOCK,
},
AstcChannel::Hdr => match block {
AstcBlock::B4x4 => F::ASTC_4X4_SFLOAT_BLOCK_EXT,
AstcBlock::B5x4 => F::ASTC_5X4_SFLOAT_BLOCK_EXT,
AstcBlock::B5x5 => F::ASTC_5X5_SFLOAT_BLOCK_EXT,
AstcBlock::B6x5 => F::ASTC_6X5_SFLOAT_BLOCK_EXT,
AstcBlock::B6x6 => F::ASTC_6X6_SFLOAT_BLOCK_EXT,
AstcBlock::B8x5 => F::ASTC_8X5_SFLOAT_BLOCK_EXT,
AstcBlock::B8x6 => F::ASTC_8X6_SFLOAT_BLOCK_EXT,
AstcBlock::B8x8 => F::ASTC_8X8_SFLOAT_BLOCK_EXT,
AstcBlock::B10x5 => F::ASTC_10X5_SFLOAT_BLOCK_EXT,
AstcBlock::B10x6 => F::ASTC_10X6_SFLOAT_BLOCK_EXT,
AstcBlock::B10x8 => F::ASTC_10X8_SFLOAT_BLOCK_EXT,
AstcBlock::B10x10 => F::ASTC_10X10_SFLOAT_BLOCK_EXT,
AstcBlock::B12x10 => F::ASTC_12X10_SFLOAT_BLOCK_EXT,
AstcBlock::B12x12 => F::ASTC_12X12_SFLOAT_BLOCK_EXT,
},
},
})
}

53
src/backend/mod.rs Normal file
View File

@@ -0,0 +1,53 @@
#[cfg(not(target_family = "wasm"))]
pub(crate) mod graphics;
#[cfg(not(target_family = "wasm"))]
mod oxr;
#[cfg(not(target_family = "wasm"))]
pub(crate) mod oxr_utils;
pub mod traits;
#[cfg(target_family = "wasm")]
pub(crate) mod web_utils;
#[cfg(target_family = "wasm")]
mod webxr;
#[cfg(not(target_family = "wasm"))]
pub use oxr::*;
#[cfg(target_family = "wasm")]
pub use webxr::*;
macro_rules! xr_inner {
($res:ty, $oxr:ty, $webxr:ty) => {
paste::paste! {
pub enum [<$res Inner>] {
#[cfg(not(target_family = "wasm"))]
OpenXR($oxr),
#[cfg(target_family = "wasm")]
WebXR($webxr),
}
#[cfg(not(target_family = "wasm"))]
impl From<$oxr> for $res {
fn from(value: $oxr) -> $res {
$res(std::rc::Rc::new([<$res Inner>]::OpenXR(value)))
}
}
#[cfg(target_family = "wasm")]
impl From<$webxr> for $res {
fn from(value: $webxr) -> $res {
$res(std::rc::Rc::new([<$res Inner>]::WebXR(value)))
}
}
}
};
}
use crate::resources::*;
xr_inner!(XrEntry, OXrEntry, WebXrEntry);
xr_inner!(XrInstance, OXrInstance, WebXrInstance);
xr_inner!(XrSession, OXrSession, WebXrSession);
xr_inner!(XrInput, OXrAction, WebXrAction);
xr_inner!(XrController, OXrController, WebXrActionSet);
xr_inner!(XrActionSpace, OXrActionSpace, WebXrActionSpace);
xr_inner!(XrReferenceSpace, OXrReferenceSpace, WebXrReferenceSpace);

159
src/backend/oxr.rs Normal file
View File

@@ -0,0 +1,159 @@
use std::rc::Rc;
use std::sync::Mutex;
use bevy::math::UVec2;
use bevy::render::renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue};
use bevy::utils::HashMap;
use openxr::{
ActionSet, AnyGraphics, ApplicationInfo, Entry, EnvironmentBlendMode, ExtensionSet, Instance,
Session,
};
use super::graphics::OXrGraphics;
use super::traits::*;
use super::{oxr_utils::*, XrControllerInner};
use crate::backend::graphics::init_oxr_graphics;
use crate::error::XrError;
use crate::resources::*;
use crate::types::*;
pub struct OXrEntry(Entry);
impl XrEntryTrait for OXrEntry {
fn get_xr_entry(&self) -> Result<XrEntry, XrError> {
Ok(OXrEntry(xr_entry()?).into())
}
fn get_available_features(&self) -> Result<FeatureList, XrError> {
let available_extensions = self.0.enumerate_extensions()?;
let mut feature_list = FeatureList::default();
feature_list.graphics.vulkan = available_extensions.khr_vulkan_enable2;
Ok(feature_list)
}
fn create_instance(&self, features: FeatureList) -> Result<XrInstance, XrError> {
let mut enabled_extensions = ExtensionSet::default();
enabled_extensions.khr_vulkan_enable2 = features.graphics.vulkan;
let instance = self.0.create_instance(
&ApplicationInfo {
application_name: "Ambient",
..Default::default()
},
&enabled_extensions,
&[],
)?;
Ok(OXrInstance {
enabled_features: features,
inner: instance,
}
.into())
}
}
pub struct OXrInstance {
inner: Instance,
enabled_features: FeatureList,
}
impl XrInstanceTrait for OXrInstance {
fn requested_features(&self) -> FeatureList {
self.enabled_features
}
fn create_session(&self, info: SessionCreateInfo) -> Result<XrSession, XrError> {
Ok(init_oxr_graphics(
self.inner.clone(),
self.enabled_features.graphics,
info.window,
)?
.into())
}
}
pub struct OXrSession {
pub(crate) instance: Instance,
pub(crate) graphics: OXrGraphics,
pub(crate) device: RenderDevice,
pub(crate) queue: RenderQueue,
pub(crate) adapter: RenderAdapter,
pub(crate) adapter_info: RenderAdapterInfo,
pub(crate) session: Session<AnyGraphics>,
pub(crate) blend_mode: EnvironmentBlendMode,
pub(crate) resolution: UVec2,
pub(crate) format: wgpu::TextureFormat,
pub(crate) buffers: Vec<wgpu::Texture>,
pub(crate) image_index: Mutex<usize>,
}
impl XrSessionTrait for OXrSession {
fn sync_controllers(&self, (left, _): (XrController, XrController)) -> Result<(), XrError> {
let XrControllerInner::OpenXR(controller) = &**left;
self.session
.sync_actions(&[(&controller.action_set).into()])?;
Ok(())
}
fn get_reference_space(&self, info: ReferenceSpaceInfo) -> Result<XrReferenceSpace, XrError> {
todo!()
}
fn create_controllers(
&self,
info: ActionSetCreateInfo,
) -> Result<(XrController, XrController), XrError> {
let instance = &self.instance;
let action_set = instance.create_action_set("controllers", "XR Controllers", 0)?;
let left_path = instance.string_to_path("/user/hand/left").unwrap();
let right_path = instance.string_to_path("/user/hand/right").unwrap();
let hand_subpaths = &[left_path, right_path];
use ActionPath::*;
let actions = &[
HandPose,
PointerPose,
GripPull,
TriggerPull,
TriggerTouch,
HapticFeedback,
PrimaryButton,
PrimaryButtonTouch,
SecondaryButton,
SecondaryButtonTouch,
MenuButton,
ThumbstickX,
ThumbstickY,
ThumbstickTouch,
ThumbstickClick,
ThumbrestTouch,
];
let action_map = Rc::new(create_actions(&action_set, actions, hand_subpaths)?);
Ok((
OXrController {
action_set: action_set.clone(),
actions: action_map.clone(),
side: Side::Left,
}
.into(),
OXrController {
action_set,
actions: action_map,
side: Side::Right,
}
.into(),
))
}
}
pub struct OXrAction {}
pub struct OXrController {
action_set: ActionSet,
actions: Rc<HashMap<ActionPath, TypedAction>>,
side: Side,
}
pub struct OXrActionSpace {}
pub struct OXrReferenceSpace {}

132
src/backend/oxr_utils.rs Normal file
View File

@@ -0,0 +1,132 @@
use crate::{
error::XrError,
types::{ActionPath, ActionSidedness, ActionType, ControllerType, Side},
};
use bevy::utils::HashMap;
use openxr::{Action, ActionSet, Binding, Entry, Haptic, Path, Posef};
#[cfg(feature = "linked")]
pub fn xr_entry() -> Result<openxr::Entry, XrError> {
Ok(Entry::linked())
}
#[cfg(not(feature = "linked"))]
pub fn xr_entry() -> Result<openxr::Entry, XrError> {
unsafe { Entry::load().map_err(|_| XrError {}) }
}
pub fn create_actions(
action_set: &ActionSet,
actions: &[ActionPath],
hand_subpaths: &[Path],
) -> Result<HashMap<ActionPath, TypedAction>, XrError> {
let mut action_map = HashMap::new();
for action in actions {
let subaction_paths = match action.sidedness() {
ActionSidedness::Single => &[],
ActionSidedness::Double => hand_subpaths,
};
let name = action.action_name();
let localized_name = action.pretty_action_name();
let typed_action = match action.action_type() {
ActionType::Bool => TypedAction::Bool(action_set.create_action(
name,
localized_name,
subaction_paths,
)?),
ActionType::Float => {
TypedAction::F32(action_set.create_action(name, localized_name, subaction_paths)?)
}
ActionType::Haptics => TypedAction::Haptic(action_set.create_action(
name,
localized_name,
subaction_paths,
)?),
ActionType::Pose => TypedAction::PoseF(action_set.create_action(
name,
localized_name,
subaction_paths,
)?),
};
action_map.insert(*action, typed_action);
}
Ok(action_map)
}
pub enum TypedAction {
F32(Action<f32>),
Bool(Action<bool>),
PoseF(Action<Posef>),
Haptic(Action<Haptic>),
}
impl TypedAction {
fn make_binding(&self, name: Path) -> Binding {
match self {
TypedAction::F32(a) => Binding::new(a, name),
TypedAction::Bool(a) => Binding::new(a, name),
TypedAction::PoseF(a) => Binding::new(a, name),
TypedAction::Haptic(a) => Binding::new(a, name),
}
}
}
impl ControllerType {
pub(crate) fn set_controller_bindings(
&self,
instance: &openxr::Instance,
bindings: &HashMap<ActionPath, TypedAction>,
) -> Result<(), XrError> {
match self {
ControllerType::OculusTouch => {
instance.suggest_interaction_profile_bindings(
instance.string_to_path("/interaction_profiles/oculus/touch_controller")?,
&[],
)?;
}
};
Ok(())
}
fn get_binding_paths(&self, path: ActionPath) -> &[&'static str] {
match self {
ControllerType::OculusTouch => match path {
ActionPath::HandPose => &[
"/user/hand/left/input/grip/pose",
"/user/hand/right/input/grip/pose",
],
ActionPath::PointerPose => &[
"/user/hand/left/input/aim/pose",
"/user/hand/right/input/aim/pose",
],
ActionPath::GripPull => &[
"/user/hand/left/input/squeeze/value",
"/user/hand/right/input/squeeze/value",
],
ActionPath::TriggerPull => &[
"/user/hand/left/input/trigger/value",
"/user/hand/right/input/trigger/value",
],
ActionPath::TriggerTouch => &[
"/user/hand/left/input/trigger/touch",
"/user/hand/right/input/trigger/touch",
],
ActionPath::HapticFeedback => &[
"/user/hand/left/output/haptic",
"/user/hand/right/output/haptic",
],
ActionPath::PrimaryButton => &[],
ActionPath::PrimaryButtonTouch => todo!(),
ActionPath::SecondaryButton => todo!(),
ActionPath::SecondaryButtonTouch => todo!(),
ActionPath::MenuButton => todo!(),
ActionPath::ThumbstickX => todo!(),
ActionPath::ThumbstickY => todo!(),
ActionPath::ThumbstickTouch => todo!(),
ActionPath::ThumbstickClick => todo!(),
ActionPath::ThumbrestTouch => todo!(),
},
}
}
}

63
src/backend/traits.rs Normal file
View File

@@ -0,0 +1,63 @@
use crate::error::XrError;
use crate::resources::*;
use crate::types::*;
use bevy::render::primitives::Aabb;
pub trait XrEntryTrait {
fn get_xr_entry(&self) -> Result<XrEntry, XrError>;
fn get_available_features(&self) -> Result<FeatureList, XrError>;
fn create_instance(&self, features: FeatureList) -> Result<XrInstance, XrError>;
}
pub trait XrInstanceTrait {
fn requested_features(&self) -> FeatureList;
fn create_session(&self, info: SessionCreateInfo) -> Result<XrSession, XrError>;
}
pub trait XrSessionTrait {
fn sync_controllers(&self, controllers: (XrController, XrController)) -> Result<(), XrError>;
fn get_reference_space(&self, info: ReferenceSpaceInfo) -> Result<XrReferenceSpace, XrError>;
fn create_controllers(
&self,
info: ActionSetCreateInfo,
) -> Result<(XrController, XrController), XrError>;
}
pub trait XrControllerTrait {
fn get_action_space(&self, info: ActionSpaceInfo) -> Result<XrActionSpace, XrError>;
fn get_action(&self, id: ActionId) -> Option<XrInput>;
}
pub trait XrInputTrait {
fn get_action_state(&self) -> ActionState;
fn get_action_bool(&self) -> Option<bool> {
if let ActionState::Bool(b) = self.get_action_state() {
Some(b)
} else {
None
}
}
fn get_action_float(&self) -> Option<f32> {
if let ActionState::Float(f) = self.get_action_state() {
Some(f)
} else {
None
}
}
fn get_action_haptic(&self) -> Option<Haptics> {
if let ActionState::Haptics(h) = self.get_action_state() {
Some(h)
} else {
None
}
}
}
pub trait XrActionSpaceTrait {
fn locate(&self, base: XrReferenceSpace) -> Pose;
}
pub trait XrReferenceSpaceTrait {
fn bounds(&self) -> Aabb;
}

56
src/backend/web_utils.rs Normal file
View File

@@ -0,0 +1,56 @@
use wasm_bindgen::{JsCast, JsValue};
use wasm_bindgen_futures::JsFuture;
use web_sys::{
js_sys::{Object, Promise, Reflect},
HtmlCanvasElement, WebGl2RenderingContext,
};
pub fn get_canvas(canvas_id: &str) -> Result<HtmlCanvasElement, JsValue> {
let query = format!("#{}", canvas_id);
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let canvas = document
.query_selector(&query)
.unwrap()
.expect("bevy_webxr - could not find canvas");
let canvas = canvas.dyn_into::<HtmlCanvasElement>()?;
Ok(canvas)
}
pub fn create_webgl_context(
xr_mode: bool,
canvas: &str,
) -> Result<WebGl2RenderingContext, JsValue> {
let canvas = get_canvas(canvas)?;
let gl: WebGl2RenderingContext = if xr_mode {
let gl_attribs = Object::new();
Reflect::set(
&gl_attribs,
&JsValue::from_str("xrCompatible"),
&JsValue::TRUE,
)?;
canvas
.get_context_with_context_options("webgl2", &gl_attribs)?
.unwrap()
.dyn_into()?
} else {
canvas.get_context("webgl2")?.unwrap().dyn_into()?
};
Ok(gl)
}
pub trait PromiseRes {
fn resolve<T: From<JsValue>>(self) -> Result<T, JsValue>;
}
impl PromiseRes for Promise {
fn resolve<T: From<JsValue>>(self) -> Result<T, JsValue> {
resolve_promise(self)
}
}
pub fn resolve_promise<T: From<JsValue>>(promise: Promise) -> Result<T, JsValue> {
bevy::tasks::block_on(async move { JsFuture::from(promise).await.map(Into::into) })
}

90
src/backend/webxr.rs Normal file
View File

@@ -0,0 +1,90 @@
use web_sys::XrRenderStateInit;
use web_sys::XrWebGlLayer;
use super::traits::*;
use super::web_utils::*;
use crate::error::XrError;
use crate::resources::*;
use crate::types::*;
pub const BEVY_CANVAS_ID: &str = "bevy_canvas";
pub const BEVY_CANVAS_QUERY: &str = "canvas[data-bevy-webxr=\"bevy_canvas\"]";
pub struct WebXrEntry(web_sys::XrSystem);
impl XrEntryTrait for WebXrEntry {
fn get_xr_entry(&self) -> Result<XrEntry, XrError> {
if let Some(window) = web_sys::window() {
Ok(WebXrEntry(window.navigator().xr()).into())
} else {
Err(XrError {})
}
}
fn get_available_features(&self) -> Result<FeatureList, XrError> {
Ok(FeatureList::default())
}
fn create_instance(&self, features: FeatureList) -> Result<XrInstance, XrError> {
Ok(WebXrInstance {
xr: self.0.clone(),
features,
}
.into())
}
}
pub struct WebXrInstance {
xr: web_sys::XrSystem,
features: FeatureList,
}
impl XrInstanceTrait for WebXrInstance {
fn requested_features(&self) -> FeatureList {
self.features
}
fn create_session(&self, info: SessionCreateInfo) -> Result<XrSession, XrError> {
let session = self
.xr
.request_session(web_sys::XrSessionMode::ImmersiveVr)
.resolve()?;
let gl = create_webgl_context(true, &info.canvas.expect("Expected canvas string"))?;
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);
Ok(WebXrSession(session).into())
}
}
pub struct WebXrSession(web_sys::XrSession);
impl XrSessionTrait for WebXrSession {
fn sync_actions(&self, action_set: XrActionSet) {
todo!()
}
fn get_reference_space(&self, _info: ReferenceSpaceInfo) -> Result<XrReferenceSpace, XrError> {
let space = self
.0
.request_reference_space(web_sys::XrReferenceSpaceType::BoundedFloor)
.resolve()?;
Ok(WebXrReferenceSpace(space).into())
}
fn create_action_set(&self, info: ActionSetCreateInfo) -> Result<XrActionSet, XrError> {
todo!()
}
}
pub struct WebXrActionSet(Vec<XrAction>);
pub struct WebXrAction(web_sys::XrInputSource);
pub struct WebXrActionSpace(web_sys::XrJointSpace);
pub struct WebXrReferenceSpace(web_sys::XrBoundedReferenceSpace);

15
src/error.rs Normal file
View File

@@ -0,0 +1,15 @@
pub struct XrError {}
#[cfg(target_family = "wasm")]
impl From<wasm_bindgen::JsValue> for XrError {
fn from(_: wasm_bindgen::JsValue) -> Self {
Self {}
}
}
#[cfg(not(target_family = "wasm"))]
impl From<openxr::sys::Result> for XrError {
fn from(_: openxr::sys::Result) -> Self {
Self {}
}
}

View File

@@ -1,44 +0,0 @@
mod vulkan;
use bevy::render::renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue};
use bevy::window::RawHandleWrapper;
use wgpu::Instance;
use crate::input::XrInput;
use crate::resources::{
XrEnvironmentBlendMode, XrFormat, XrFrameState, XrFrameWaiter, XrInstance, XrResolution,
XrSession, XrSessionRunning, XrSwapchain, XrViews,
};
use openxr as xr;
pub fn initialize_xr_graphics(
window: Option<RawHandleWrapper>,
) -> anyhow::Result<(
RenderDevice,
RenderQueue,
RenderAdapterInfo,
RenderAdapter,
Instance,
XrInstance,
XrSession,
XrEnvironmentBlendMode,
XrResolution,
XrFormat,
XrSessionRunning,
XrFrameWaiter,
XrSwapchain,
XrInput,
XrViews,
XrFrameState,
)> {
vulkan::initialize_xr_graphics(window)
}
pub fn xr_entry() -> anyhow::Result<xr::Entry> {
#[cfg(feature = "linked")]
let entry = Ok(xr::Entry::linked());
#[cfg(not(feature = "linked"))]
let entry = unsafe { xr::Entry::load().map_err(|e| anyhow::anyhow!(e)) };
entry
}

View File

@@ -1,68 +0,0 @@
use std::sync::Arc;
use bevy::prelude::*;
use openxr as xr;
#[derive(Clone, Resource)]
pub struct XrInput {
//pub action_set: xr::ActionSet,
//pub hand_pose: xr::Action<xr::Posef>,
//pub right_space: Arc<xr::Space>,
//pub left_space: Arc<xr::Space>,
pub stage: Arc<xr::Space>,
pub head: Arc<xr::Space>,
}
impl XrInput {
pub fn new(_instance: xr::Instance, session: xr::Session<xr::AnyGraphics>) -> xr::Result<Self> {
// let action_set = instance.create_action_set("input", "input pose information", 0)?;
// let left_hand_subaction_path = instance.string_to_path("/user/hand/left").unwrap();
// let right_hand_subaction_path = instance.string_to_path("/user/hand/right").unwrap();
// let left_hand_grip_pose_path = instance
// .string_to_path("/user/hand/left/input/grip/pose")
// .unwrap();
// let right_hand_grip_pose_path = instance
// .string_to_path("/user/hand/right/input/grip/pose")
// .unwrap();
// let hand_pose = action_set.create_action::<xr::Posef>(
// "hand_pose",
// "Hand Pose",
// &[left_hand_subaction_path, right_hand_subaction_path],
// )?;
// /* let left_action =
// action_set.create_action::<xr::Posef>("left_hand", "Left Hand Controller", &[])?;*/
// instance.suggest_interaction_profile_bindings(
// instance.string_to_path("/interaction_profiles/khr/simple_controller")?,
// &[
// xr::Binding::new(&hand_pose, right_hand_grip_pose_path),
// xr::Binding::new(&hand_pose, left_hand_grip_pose_path),
// ],
// )?;
//
// let right_space = hand_pose.create_space(
// session.clone(),
// right_hand_subaction_path,
// xr::Posef::IDENTITY,
// )?;
// let left_space = hand_pose.create_space(
// session.clone(),
// left_hand_subaction_path,
// xr::Posef::IDENTITY,
// )?;
let stage =
session.create_reference_space(xr::ReferenceSpaceType::STAGE, xr::Posef::IDENTITY)?;
let head = session
.create_reference_space(xr::ReferenceSpaceType::VIEW, xr::Posef::IDENTITY)
.unwrap();
//session.attach_action_sets(&[&action_set])?;
//session.attach_action_sets(&[])?;
Ok(Self {
//action_set,
//hand_pose,
// right_space: Arc::new(right_space),
// left_space: Arc::new(left_space),
stage: Arc::new(stage),
head: Arc::new(head),
})
}
}

View File

@@ -1,394 +1,5 @@
mod graphics;
pub mod input;
pub mod resource_macros;
pub mod backend;
pub mod error;
mod resource_macros;
pub mod resources;
pub mod xr_init;
pub mod xr_input;
use std::sync::{Arc, Mutex};
use crate::xr_init::RenderRestartPlugin;
use crate::xr_input::hands::hand_tracking::DisableHandTracking;
use crate::xr_input::oculus_touch::ActionSets;
use bevy::app::PluginGroupBuilder;
use bevy::ecs::system::{RunSystemOnce, SystemState};
use bevy::prelude::*;
use bevy::render::camera::{
CameraPlugin, ManualTextureView, ManualTextureViewHandle, ManualTextureViews,
};
use bevy::render::globals::GlobalsPlugin;
use bevy::render::mesh::morph::MorphPlugin;
use bevy::render::mesh::MeshPlugin;
use bevy::render::pipelined_rendering::PipelinedRenderingPlugin;
use bevy::render::render_asset::RenderAssetDependency;
use bevy::render::render_resource::ShaderLoader;
use bevy::render::renderer::{
render_system, RenderAdapter, RenderAdapterInfo, RenderDevice, RenderInstance, RenderQueue,
};
use bevy::render::settings::RenderCreation;
use bevy::render::view::{self, ViewPlugin, WindowRenderPlugin};
use bevy::render::{color, primitives, Render, RenderApp, RenderPlugin, RenderSet};
use bevy::window::{PresentMode, PrimaryWindow, RawHandleWrapper};
use input::XrInput;
use openxr as xr;
use resources::*;
use xr::FormFactor;
use xr_init::{
init_non_xr_graphics, update_xr_stuff, xr_only, RenderCreationData, XrEnableRequest,
XrEnableStatus, XrRenderData, XrRenderUpdate,
};
use xr_input::controllers::XrControllerType;
use xr_input::hands::emulated::HandEmulationPlugin;
use xr_input::hands::hand_tracking::{HandTrackingData, HandTrackingPlugin};
use xr_input::OpenXrInput;
const VIEW_TYPE: xr::ViewConfigurationType = xr::ViewConfigurationType::PRIMARY_STEREO;
pub const LEFT_XR_TEXTURE_HANDLE: ManualTextureViewHandle = ManualTextureViewHandle(1208214591);
pub const RIGHT_XR_TEXTURE_HANDLE: ManualTextureViewHandle = ManualTextureViewHandle(3383858418);
/// Adds OpenXR support to an App
pub struct OpenXrPlugin;
impl Default for OpenXrPlugin {
fn default() -> Self {
OpenXrPlugin
}
}
#[derive(Resource)]
pub struct FutureXrResources(
pub Arc<
Mutex<
Option<(
XrInstance,
XrSession,
XrEnvironmentBlendMode,
XrResolution,
XrFormat,
XrSessionRunning,
XrFrameWaiter,
XrSwapchain,
XrInput,
XrViews,
XrFrameState,
)>,
>,
>,
);
impl Plugin for OpenXrPlugin {
fn build(&self, app: &mut App) {
let mut system_state: SystemState<Query<&RawHandleWrapper, With<PrimaryWindow>>> =
SystemState::new(&mut app.world);
let primary_window = system_state.get(&app.world).get_single().ok().cloned();
if let Ok((
device,
queue,
adapter_info,
render_adapter,
instance,
xr_instance,
session,
blend_mode,
resolution,
format,
session_running,
frame_waiter,
swapchain,
input,
views,
frame_state,
)) = graphics::initialize_xr_graphics(primary_window.clone())
{
// std::thread::sleep(Duration::from_secs(5));
debug!("Configured wgpu adapter Limits: {:#?}", device.limits());
debug!("Configured wgpu adapter Features: {:#?}", device.features());
app.insert_resource(xr_instance.clone());
app.insert_resource(session.clone());
app.insert_resource(blend_mode.clone());
app.insert_resource(resolution.clone());
app.insert_resource(format.clone());
app.insert_resource(session_running.clone());
app.insert_resource(frame_waiter.clone());
app.insert_resource(swapchain.clone());
app.insert_resource(input.clone());
app.insert_resource(views.clone());
app.insert_resource(frame_state.clone());
let xr_data = XrRenderData {
xr_instance,
xr_session: session,
xr_blend_mode: blend_mode,
xr_resolution: resolution,
xr_format: format,
xr_session_running: session_running,
xr_frame_waiter: frame_waiter,
xr_swapchain: swapchain,
xr_input: input,
xr_views: views,
xr_frame_state: frame_state,
};
app.insert_resource(xr_data);
app.insert_resource(ActionSets(vec![]));
app.add_plugins(RenderPlugin {
render_creation: RenderCreation::Manual(
device,
queue,
adapter_info,
render_adapter,
RenderInstance(Arc::new(instance)),
),
});
app.insert_resource(XrEnableStatus::Enabled);
} else {
app.add_plugins(RenderPlugin::default());
app.insert_resource(XrEnableStatus::Disabled);
}
}
fn ready(&self, app: &App) -> bool {
app.world
.get_resource::<XrEnableStatus>()
.map(|frr| *frr != XrEnableStatus::Waiting)
.unwrap_or(true)
}
fn finish(&self, app: &mut App) {
// TODO: Split this up into the indevidual resources
if let Some(data) = app.world.get_resource::<XrRenderData>().cloned() {
let hands = data.xr_instance.exts().ext_hand_tracking.is_some()
&& data
.xr_instance
.supports_hand_tracking(
data.xr_instance
.system(FormFactor::HEAD_MOUNTED_DISPLAY)
.unwrap(),
)
.is_ok_and(|v| v);
if hands {
app.insert_resource(HandTrackingData::new(&data.xr_session).unwrap());
} else {
app.insert_resource(DisableHandTracking::Both);
}
let (left, right) = data.xr_swapchain.get_render_views();
let left = ManualTextureView {
texture_view: left.into(),
size: *data.xr_resolution,
format: *data.xr_format,
};
let right = ManualTextureView {
texture_view: right.into(),
size: *data.xr_resolution,
format: *data.xr_format,
};
app.add_systems(PreUpdate, xr_begin_frame.run_if(xr_only()));
let mut manual_texture_views = app.world.resource_mut::<ManualTextureViews>();
manual_texture_views.insert(LEFT_XR_TEXTURE_HANDLE, left);
manual_texture_views.insert(RIGHT_XR_TEXTURE_HANDLE, right);
drop(manual_texture_views);
let render_app = app.sub_app_mut(RenderApp);
render_app.insert_resource(data.xr_instance.clone());
render_app.insert_resource(data.xr_session.clone());
render_app.insert_resource(data.xr_blend_mode.clone());
render_app.insert_resource(data.xr_resolution.clone());
render_app.insert_resource(data.xr_format.clone());
render_app.insert_resource(data.xr_session_running.clone());
render_app.insert_resource(data.xr_frame_waiter.clone());
render_app.insert_resource(data.xr_swapchain.clone());
render_app.insert_resource(data.xr_input.clone());
render_app.insert_resource(data.xr_views.clone());
render_app.insert_resource(data.xr_frame_state.clone());
render_app.insert_resource(XrEnableStatus::Enabled);
render_app.add_systems(
Render,
(
post_frame
.run_if(xr_only())
.before(render_system)
.after(RenderSet::ExtractCommands),
end_frame.run_if(xr_only()).after(render_system),
),
);
}
}
}
pub struct DefaultXrPlugins;
impl PluginGroup for DefaultXrPlugins {
fn build(self) -> PluginGroupBuilder {
DefaultPlugins
.build()
.disable::<RenderPlugin>()
.disable::<PipelinedRenderingPlugin>()
.add_before::<RenderPlugin, _>(OpenXrPlugin)
.add_after::<OpenXrPlugin, _>(OpenXrInput::new(XrControllerType::OculusTouch))
.add_before::<OpenXrPlugin, _>(RenderRestartPlugin)
.add(HandEmulationPlugin)
.add(HandTrackingPlugin)
.set(WindowPlugin {
#[cfg(not(target_os = "android"))]
primary_window: Some(Window {
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()
})
}
}
pub fn xr_begin_frame(
instance: Res<XrInstance>,
session: Res<XrSession>,
session_running: Res<XrSessionRunning>,
frame_state: Res<XrFrameState>,
frame_waiter: Res<XrFrameWaiter>,
swapchain: Res<XrSwapchain>,
views: Res<XrViews>,
input: Res<XrInput>,
) {
{
let _span = info_span!("xr_poll_events");
while let Some(event) = instance.poll_event(&mut Default::default()).unwrap() {
use xr::Event::*;
match event {
SessionStateChanged(e) => {
// Session state change is where we can begin and end sessions, as well as
// find quit messages!
info!("entered XR state {:?}", e.state());
match e.state() {
xr::SessionState::READY => {
session.begin(VIEW_TYPE).unwrap();
session_running.store(true, std::sync::atomic::Ordering::Relaxed);
}
xr::SessionState::STOPPING => {
session.end().unwrap();
session_running.store(false, std::sync::atomic::Ordering::Relaxed);
}
xr::SessionState::EXITING | xr::SessionState::LOSS_PENDING => return,
_ => {}
}
}
InstanceLossPending(_) => return,
EventsLost(e) => {
warn!("lost {} XR events", e.lost_event_count());
}
_ => {}
}
}
}
{
let _span = info_span!("xr_wait_frame").entered();
*frame_state.lock().unwrap() = match frame_waiter.lock().unwrap().wait() {
Ok(a) => a,
Err(e) => {
warn!("error: {}", e);
return;
}
};
}
{
let _span = info_span!("xr_begin_frame").entered();
swapchain.begin().unwrap()
}
{
let _span = info_span!("xr_locate_views").entered();
*views.lock().unwrap() = session
.locate_views(
VIEW_TYPE,
frame_state.lock().unwrap().predicted_display_time,
&input.stage,
)
.unwrap()
.1;
}
}
pub fn post_frame(
resolution: Res<XrResolution>,
format: Res<XrFormat>,
swapchain: Res<XrSwapchain>,
mut manual_texture_views: ResMut<ManualTextureViews>,
) {
{
let _span = info_span!("xr_acquire_image").entered();
swapchain.acquire_image().unwrap()
}
{
let _span = info_span!("xr_wait_image").entered();
swapchain.wait_image().unwrap();
}
{
let _span = info_span!("xr_update_manual_texture_views").entered();
let (left, right) = swapchain.get_render_views();
let left = ManualTextureView {
texture_view: left.into(),
size: **resolution,
format: **format,
};
let right = ManualTextureView {
texture_view: right.into(),
size: **resolution,
format: **format,
};
manual_texture_views.insert(LEFT_XR_TEXTURE_HANDLE, left);
manual_texture_views.insert(RIGHT_XR_TEXTURE_HANDLE, right);
}
}
pub fn end_frame(
xr_frame_state: Res<XrFrameState>,
views: Res<XrViews>,
input: Res<XrInput>,
swapchain: Res<XrSwapchain>,
resolution: Res<XrResolution>,
environment_blend_mode: Res<XrEnvironmentBlendMode>,
) {
{
let _span = info_span!("xr_release_image").entered();
swapchain.release_image().unwrap();
}
{
let _span = info_span!("xr_end_frame").entered();
let result = swapchain.end(
xr_frame_state.lock().unwrap().predicted_display_time,
&*views.lock().unwrap(),
&input.stage,
**resolution,
**environment_blend_mode,
);
match result {
Ok(_) => {}
Err(e) => warn!("error: {}", e),
}
}
}
pub fn locate_views(
views: Res<XrViews>,
input: Res<XrInput>,
session: Res<XrSession>,
xr_frame_state: Res<XrFrameState>,
) {
let _span = info_span!("xr_locate_views").entered();
*views.lock().unwrap() = match session.locate_views(
VIEW_TYPE,
xr_frame_state.lock().unwrap().predicted_display_time,
&input.stage,
) {
Ok(this) => this,
Err(err) => {
warn!("error: {}", err);
return;
}
}
.1;
}
pub mod types;

View File

@@ -1,58 +1 @@
#[macro_export]
macro_rules! xr_resource_wrapper {
($wrapper_type:ident, $xr_type:ty) => {
#[derive(Clone, bevy::prelude::Resource)]
pub struct $wrapper_type($xr_type);
impl $wrapper_type {
pub fn new(value: $xr_type) -> Self {
Self(value)
}
}
impl std::ops::Deref for $wrapper_type {
type Target = $xr_type;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<$xr_type> for $wrapper_type {
fn from(value: $xr_type) -> Self {
Self::new(value)
}
}
};
}
#[macro_export]
macro_rules! xr_arc_resource_wrapper {
($wrapper_type:ident, $xr_type:ty) => {
#[derive(Clone, bevy::prelude::Resource)]
pub struct $wrapper_type(std::sync::Arc<$xr_type>);
impl $wrapper_type {
pub fn new(value: $xr_type) -> Self {
Self(std::sync::Arc::new(value))
}
}
impl std::ops::Deref for $wrapper_type {
type Target = $xr_type;
fn deref(&self) -> &Self::Target {
self.0.as_ref()
}
}
impl From<$xr_type> for $wrapper_type {
fn from(value: $xr_type) -> Self {
Self::new(value)
}
}
};
}
pub use xr_arc_resource_wrapper;
pub use xr_resource_wrapper;

View File

@@ -1,166 +1,32 @@
use std::sync::atomic::AtomicBool;
use std::sync::Mutex;
use std::rc::Rc;
use crate::resource_macros::*;
use bevy::prelude::*;
use openxr as xr;
use bevy::prelude::{Deref, DerefMut};
xr_resource_wrapper!(XrInstance, xr::Instance);
xr_resource_wrapper!(XrSession, xr::Session<xr::AnyGraphics>);
xr_resource_wrapper!(XrEnvironmentBlendMode, xr::EnvironmentBlendMode);
xr_resource_wrapper!(XrResolution, UVec2);
xr_resource_wrapper!(XrFormat, wgpu::TextureFormat);
xr_arc_resource_wrapper!(XrSessionRunning, AtomicBool);
xr_arc_resource_wrapper!(XrFrameWaiter, Mutex<xr::FrameWaiter>);
xr_arc_resource_wrapper!(XrSwapchain, Swapchain);
xr_arc_resource_wrapper!(XrFrameState, Mutex<xr::FrameState>);
xr_arc_resource_wrapper!(XrViews, Mutex<Vec<xr::View>>);
use crate::backend;
pub enum Swapchain {
Vulkan(SwapchainInner<xr::Vulkan>),
macro_rules! xr_resources {
(
$(
$(#[$attr:meta])*
$name:ident;
)*
) => {
paste::paste! {
$(
$(#[$attr])*
#[derive(Clone, Deref, DerefMut)]
pub struct $name(pub(crate) Rc<backend::[<$name Inner>]>);
)*
}
};
}
impl Swapchain {
pub(crate) fn begin(&self) -> xr::Result<()> {
match self {
Swapchain::Vulkan(swapchain) => swapchain.begin(),
}
}
pub(crate) fn get_render_views(&self) -> (wgpu::TextureView, wgpu::TextureView) {
match self {
Swapchain::Vulkan(swapchain) => swapchain.get_render_views(),
}
}
pub(crate) fn acquire_image(&self) -> xr::Result<()> {
match self {
Swapchain::Vulkan(swapchain) => swapchain.acquire_image(),
}
}
pub(crate) fn wait_image(&self) -> xr::Result<()> {
match self {
Swapchain::Vulkan(swapchain) => swapchain.wait_image(),
}
}
pub(crate) fn release_image(&self) -> xr::Result<()> {
match self {
Swapchain::Vulkan(swapchain) => swapchain.release_image(),
}
}
pub(crate) fn end(
&self,
predicted_display_time: xr::Time,
views: &[openxr::View],
stage: &xr::Space,
resolution: UVec2,
environment_blend_mode: xr::EnvironmentBlendMode,
) -> xr::Result<()> {
match self {
Swapchain::Vulkan(swapchain) => swapchain.end(
predicted_display_time,
views,
stage,
resolution,
environment_blend_mode,
),
}
}
}
pub struct SwapchainInner<G: xr::Graphics> {
pub(crate) stream: Mutex<xr::FrameStream<G>>,
pub(crate) handle: Mutex<xr::Swapchain<G>>,
pub(crate) buffers: Vec<wgpu::Texture>,
pub(crate) image_index: Mutex<usize>,
}
impl<G: xr::Graphics> SwapchainInner<G> {
fn begin(&self) -> xr::Result<()> {
self.stream.lock().unwrap().begin()
}
fn get_render_views(&self) -> (wgpu::TextureView, wgpu::TextureView) {
let texture = &self.buffers[*self.image_index.lock().unwrap()];
(
texture.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::D2),
array_layer_count: Some(1),
..Default::default()
}),
texture.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::D2),
array_layer_count: Some(1),
base_array_layer: 1,
..Default::default()
}),
)
}
fn acquire_image(&self) -> xr::Result<()> {
let image_index = self.handle.lock().unwrap().acquire_image()?;
*self.image_index.lock().unwrap() = image_index as _;
Ok(())
}
fn wait_image(&self) -> xr::Result<()> {
self.handle
.lock()
.unwrap()
.wait_image(xr::Duration::INFINITE)
}
fn release_image(&self) -> xr::Result<()> {
self.handle.lock().unwrap().release_image()
}
fn end(
&self,
predicted_display_time: xr::Time,
views: &[openxr::View],
stage: &xr::Space,
resolution: UVec2,
environment_blend_mode: xr::EnvironmentBlendMode,
) -> xr::Result<()> {
let rect = xr::Rect2Di {
offset: xr::Offset2Di { x: 0, y: 0 },
extent: xr::Extent2Di {
width: resolution.x as _,
height: resolution.y as _,
},
};
let swapchain = self.handle.lock().unwrap();
if views.len() == 0 {
warn!("views are len of 0");
return Ok(());
}
self.stream.lock().unwrap().end(
predicted_display_time,
environment_blend_mode,
&[&xr::CompositionLayerProjection::new().space(stage).views(&[
xr::CompositionLayerProjectionView::new()
.pose(views[0].pose)
.fov(views[0].fov)
.sub_image(
xr::SwapchainSubImage::new()
.swapchain(&swapchain)
.image_array_index(0)
.image_rect(rect),
),
xr::CompositionLayerProjectionView::new()
.pose(views[1].pose)
.fov(views[1].fov)
.sub_image(
xr::SwapchainSubImage::new()
.swapchain(&swapchain)
.image_array_index(1)
.image_rect(rect),
),
])],
)
}
xr_resources! {
XrEntry;
XrInstance;
XrSession;
XrInput;
XrController;
XrActionSpace;
XrReferenceSpace;
}

191
src/types.rs Normal file
View File

@@ -0,0 +1,191 @@
use bevy::{
render::renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue},
window::RawHandleWrapper,
};
/// This struct stores all of the render resources required to initialize the bevy render plugin
///
/// Returned from [XrSessionTrait::get_render_resources](crate::backend::traits::XrSessionTrait::get_render_resources)
pub struct RenderResources {
pub adapter: RenderAdapter,
pub adapter_info: RenderAdapterInfo,
pub device: RenderDevice,
pub queue: RenderQueue,
}
/// Information used to create the [XrSession](crate::resources::XrSession)
///
/// Passed into [XrInstanceTrait::create_session](crate::backend::traits::XrInstanceTrait::create_session)
pub struct SessionCreateInfo {
/// This field is required to be [Some] when using WebXR
pub canvas: Option<String>,
pub window: Option<RawHandleWrapper>,
}
#[derive(Clone, Copy)]
pub enum ControllerType {
OculusTouch,
}
#[derive(Clone)]
pub struct ActionSetCreateInfo {
pub controller: ControllerType,
// TODO!() allow custom fields
}
pub struct ReferenceSpaceInfo {}
pub struct ActionSpaceInfo {}
#[derive(Clone, Copy, Default)]
pub struct FeatureList {
pub graphics: GraphicsFeatures,
}
#[derive(Clone, Copy, Default)]
pub struct GraphicsFeatures {
pub vulkan: bool,
}
pub struct Pose;
pub struct Haptics;
pub enum ActionState {
Bool(bool),
Float(f32),
Haptics(Haptics),
Pose(Pose),
}
pub enum ActionType {
Bool,
Float,
Haptics,
Pose,
}
pub struct ActionId {
pub action_path: ActionPath,
pub side: Option<Side>,
}
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
pub enum ActionPath {
HandPose,
PointerPose,
GripPull,
TriggerPull,
TriggerTouch,
HapticFeedback,
PrimaryButton,
PrimaryButtonTouch,
SecondaryButton,
SecondaryButtonTouch,
MenuButton,
ThumbstickX,
ThumbstickY,
ThumbstickTouch,
ThumbstickClick,
ThumbrestTouch,
}
impl ActionPath {
pub fn action_name(&self) -> &'static str {
use ActionPath::*;
match self {
HandPose => "hand_pose",
PointerPose => "pointer_pose",
GripPull => "grip_pull",
TriggerPull => "trigger_pull",
TriggerTouch => "trigger_touch",
HapticFeedback => "haptic_feedback",
PrimaryButton => "primary_button",
PrimaryButtonTouch => "primary_button_touch",
SecondaryButton => "secondary_button",
SecondaryButtonTouch => "secondary_button_touch",
MenuButton => "menu_button",
ThumbstickX => "thumbstick_x",
ThumbstickY => "thumbstick_y",
ThumbstickTouch => "thumbstick_touch",
ThumbstickClick => "thumbstick_click",
ThumbrestTouch => "thumbrest_touch",
}
}
pub fn pretty_action_name(&self) -> &'static str {
use ActionPath::*;
match self {
HandPose => "Hand Pose",
PointerPose => "Pointer Pose",
GripPull => "Grip Pull",
TriggerPull => "Trigger Pull",
TriggerTouch => "Trigger Touch",
HapticFeedback => "Haptic Feedback",
PrimaryButton => "Primary Button",
PrimaryButtonTouch => "Primary Button Touch",
SecondaryButton => "Secondary Button",
SecondaryButtonTouch => "Secondary Button Touch",
MenuButton => "Menu Button",
ThumbstickX => "Thumbstick X",
ThumbstickY => "Thumbstick Y",
ThumbstickTouch => "Thumbstick Touch",
ThumbstickClick => "Thumbstick Click",
ThumbrestTouch => "Thumbrest Touch",
}
}
pub fn action_type(&self) -> ActionType {
use ActionPath::*;
match self {
HandPose => ActionType::Pose,
PointerPose => ActionType::Pose,
GripPull => ActionType::Float,
TriggerPull => ActionType::Float,
TriggerTouch => ActionType::Bool,
HapticFeedback => ActionType::Haptics,
PrimaryButton => ActionType::Bool,
PrimaryButtonTouch => ActionType::Bool,
SecondaryButton => ActionType::Bool,
SecondaryButtonTouch => ActionType::Bool,
MenuButton => ActionType::Bool,
ThumbstickX => ActionType::Float,
ThumbstickY => ActionType::Float,
ThumbstickTouch => ActionType::Bool,
ThumbstickClick => ActionType::Bool,
ThumbrestTouch => ActionType::Bool,
}
}
pub fn sidedness(&self) -> ActionSidedness {
use ActionPath::*;
match self {
HandPose => ActionSidedness::Double,
PointerPose => ActionSidedness::Double,
GripPull => ActionSidedness::Double,
TriggerPull => ActionSidedness::Double,
TriggerTouch => ActionSidedness::Double,
HapticFeedback => ActionSidedness::Double,
PrimaryButton => ActionSidedness::Double,
PrimaryButtonTouch => ActionSidedness::Double,
SecondaryButton => ActionSidedness::Double,
SecondaryButtonTouch => ActionSidedness::Double,
MenuButton => ActionSidedness::Double,
ThumbstickX => ActionSidedness::Double,
ThumbstickY => ActionSidedness::Double,
ThumbstickTouch => ActionSidedness::Double,
ThumbstickClick => ActionSidedness::Double,
ThumbrestTouch => ActionSidedness::Double,
}
}
}
pub enum ActionSidedness {
Single,
Double,
}
pub enum Side {
Left,
Right,
}

View File

@@ -1,364 +0,0 @@
// Just a lot of code that is meant for something way more complex but hey.
// maybe will work on that soon
use std::sync::Arc;
use bevy::{
ecs::schedule::{ExecutorKind, ScheduleLabel},
prelude::*,
render::{
extract_resource::{ExtractResource, ExtractResourcePlugin},
renderer::{
self, RenderAdapter, RenderAdapterInfo, RenderDevice, RenderInstance, RenderQueue,
},
settings::WgpuSettings,
},
window::{PrimaryWindow, RawHandleWrapper},
};
use wgpu::Instance;
use crate::{
graphics,
input::XrInput,
resources::{
XrEnvironmentBlendMode, XrFormat, XrFrameState, XrFrameWaiter, XrInstance, XrResolution,
XrSession, XrSessionRunning, XrSwapchain, XrViews,
},
};
#[derive(Resource, Clone)]
pub struct RenderCreationData {
pub device: RenderDevice,
pub queue: RenderQueue,
pub adapter_info: RenderAdapterInfo,
pub render_adapter: RenderAdapter,
pub instance: Arc<Instance>,
}
#[derive(Resource, Clone, ExtractResource)]
pub struct XrRenderData {
pub xr_instance: XrInstance,
pub xr_session: XrSession,
pub xr_blend_mode: XrEnvironmentBlendMode,
pub xr_resolution: XrResolution,
pub xr_format: XrFormat,
pub xr_session_running: XrSessionRunning,
pub xr_frame_waiter: XrFrameWaiter,
pub xr_swapchain: XrSwapchain,
pub xr_input: XrInput,
pub xr_views: XrViews,
pub xr_frame_state: XrFrameState,
}
#[derive(Event, Clone, Copy, Debug)]
pub enum XrEnableRequest {
TryEnable,
TryDisable,
}
#[derive(Resource, Event, Copy, Clone, PartialEq, Eq)]
pub enum XrEnableStatus {
Enabled,
Disabled,
Waiting,
}
#[derive(Resource, Event, Copy, Clone, PartialEq, Eq, Debug)]
pub enum XrNextEnabledState {
Enabled,
Disabled,
}
pub struct RenderRestartPlugin;
#[derive(Resource)]
pub struct ForceMain;
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
pub struct XrPreSetup;
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
pub struct XrSetup;
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
pub struct XrPrePostSetup;
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
pub struct XrPostSetup;
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
pub struct XrPreCleanup;
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
pub struct XrCleanup;
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
pub struct XrPostCleanup;
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
pub struct XrPreRenderUpdate;
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
pub struct XrRenderUpdate;
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
pub struct XrPostRenderUpdate;
pub fn xr_only() -> impl FnMut(Option<Res<'_, XrEnableStatus>>) -> bool {
resource_exists_and_equals(XrEnableStatus::Enabled)
}
impl Plugin for RenderRestartPlugin {
fn build(&self, app: &mut App) {
add_schedules(app);
app.add_plugins(ExtractResourcePlugin::<XrRenderData>::default())
.insert_resource(ForceMain)
.add_event::<XrEnableRequest>()
.add_event::<XrEnableStatus>()
.add_systems(PreStartup, xr_presetup.run_if(xr_only()))
.add_systems(Startup, xr_setup.run_if(xr_only()))
.add_systems(PostStartup, xr_postsetup.run_if(xr_only()))
.add_systems(
PostUpdate,
update_xr_stuff.run_if(on_event::<XrEnableRequest>()),
)
.add_systems(XrPreRenderUpdate, decide_next_xr_state)
.add_systems(XrPostRenderUpdate, clear_events)
.add_systems(
XrRenderUpdate,
(
cleanup_xr.run_if(resource_exists_and_equals(XrNextEnabledState::Disabled)),
// handle_xr_enable_requests,
apply_deferred,
setup_xr, /* .run_if(resource_exists_and_equals(XrEnableStatus::Enabled)) */
)
.chain(),
)
.add_systems(XrCleanup, cleanup_oxr_session);
}
}
fn clear_events(mut events: ResMut<Events<XrEnableRequest>>) {
events.clear();
}
fn add_schedules(app: &mut App) {
let schedules = [
Schedule::new(XrPreSetup),
Schedule::new(XrSetup),
Schedule::new(XrPrePostSetup),
Schedule::new(XrPostSetup),
Schedule::new(XrPreRenderUpdate),
Schedule::new(XrRenderUpdate),
Schedule::new(XrPostRenderUpdate),
Schedule::new(XrPreCleanup),
Schedule::new(XrCleanup),
Schedule::new(XrPostCleanup),
];
for mut schedule in schedules {
schedule.set_executor_kind(ExecutorKind::SingleThreaded);
schedule.set_apply_final_deferred(true);
app.add_schedule(schedule);
}
}
fn xr_presetup(world: &mut World) {
world.run_schedule(XrPreSetup);
}
fn xr_setup(world: &mut World) {
world.run_schedule(XrSetup);
}
fn xr_postsetup(world: &mut World) {
world.run_schedule(XrPrePostSetup);
world.run_schedule(XrPostSetup);
}
fn setup_xr(world: &mut World) {
world.run_schedule(XrPreSetup);
world.run_schedule(XrSetup);
world.run_schedule(XrPrePostSetup);
world.run_schedule(XrPostSetup);
}
fn cleanup_xr(world: &mut World) {
world.run_schedule(XrPreCleanup);
world.run_schedule(XrCleanup);
world.run_schedule(XrPostCleanup);
}
fn cleanup_oxr_session(xr_status: Option<Res<XrEnableStatus>>, session: Option<ResMut<XrSession>>) {
if let (Some(XrEnableStatus::Disabled), Some(s)) = (xr_status.map(|v| v.into_inner()), session)
{
s.into_inner().request_exit().unwrap();
}
}
pub fn update_xr_stuff(world: &mut World) {
world.run_schedule(XrPreRenderUpdate);
world.run_schedule(XrRenderUpdate);
world.run_schedule(XrPostRenderUpdate);
}
// fn handle_xr_enable_requests(
// primary_window: Query<&RawHandleWrapper, With<PrimaryWindow>>,
// mut commands: Commands,
// next_state: Res<XrNextEnabledState>,
// on_main: Option<NonSend<ForceMain>>,
// ) {
// // Just to force this system onto the main thread because of unsafe code
// let _ = on_main;
//
// commands.insert_resource(XrEnableStatus::Waiting);
// let (creation_data, xr_data) = match next_state.into_inner() {
// XrNextEnabledState::Enabled => {
// let (
// device,
// queue,
// adapter_info,
// render_adapter,
// instance,
// xr_instance,
// session,
// blend_mode,
// resolution,
// format,
// session_running,
// frame_waiter,
// swapchain,
// input,
// views,
// frame_state,
// ) = graphics::initialize_xr_graphics(primary_window.get_single().ok().cloned())
// .unwrap();
//
// commands.insert_resource(XrEnableStatus::Enabled);
// (
// RenderCreationData {
// device,
// queue,
// adapter_info,
// render_adapter,
// instance: Arc::new(instance),
// },
// Some(XrRenderData {
// xr_instance,
// xr_session: session,
// xr_blend_mode: blend_mode,
// xr_resolution: resolution,
// xr_format: format,
// xr_session_running: session_running,
// xr_frame_waiter: frame_waiter,
// xr_swapchain: swapchain,
// xr_input: input,
// xr_views: views,
// xr_frame_state: frame_state,
// }),
// )
// }
// XrNextEnabledState::Disabled => (
// init_non_xr_graphics(primary_window.get_single().ok().cloned()),
// None,
// ),
// };
//
// commands.insert_resource(creation_data.device);
// commands.insert_resource(creation_data.queue);
// commands.insert_resource(RenderInstance(creation_data.instance));
// commands.insert_resource(creation_data.adapter_info);
// commands.insert_resource(creation_data.render_adapter);
//
// if let Some(xr_data) = xr_data {
// // TODO: Remove this lib.rs:144
// commands.insert_resource(xr_data.clone());
//
// commands.insert_resource(xr_data.xr_instance);
// commands.insert_resource(xr_data.xr_session);
// commands.insert_resource(xr_data.xr_blend_mode);
// commands.insert_resource(xr_data.xr_resolution);
// commands.insert_resource(xr_data.xr_format);
// commands.insert_resource(xr_data.xr_session_running);
// commands.insert_resource(xr_data.xr_frame_waiter);
// commands.insert_resource(xr_data.xr_input);
// commands.insert_resource(xr_data.xr_views);
// commands.insert_resource(xr_data.xr_frame_state);
// commands.insert_resource(xr_data.xr_swapchain);
// } else {
// commands.remove_resource::<XrRenderData>();
//
// commands.remove_resource::<XrInstance>();
// commands.remove_resource::<XrSession>();
// commands.remove_resource::<XrEnvironmentBlendMode>();
// commands.remove_resource::<XrResolution>();
// commands.remove_resource::<XrFormat>();
// commands.remove_resource::<XrSessionRunning>();
// commands.remove_resource::<XrFrameWaiter>();
// commands.remove_resource::<XrSwapchain>();
// commands.remove_resource::<XrInput>();
// commands.remove_resource::<XrViews>();
// commands.remove_resource::<XrFrameState>();
// }
// }
fn decide_next_xr_state(
mut commands: Commands,
mut events: EventReader<XrEnableRequest>,
xr_status: Option<Res<XrEnableStatus>>,
) {
info!("hm");
let request = match events.read().next() {
Some(v) => v,
None => return,
};
info!("ok");
match (request, xr_status.as_deref()) {
(XrEnableRequest::TryEnable, Some(XrEnableStatus::Enabled)) => {
info!("Xr Already Enabled! ignoring request");
return;
}
(XrEnableRequest::TryDisable, Some(XrEnableStatus::Disabled)) => {
info!("Xr Already Disabled! ignoring request");
return;
}
(_, Some(XrEnableStatus::Waiting)) => {
info!("Already Handling Request! ignoring request");
return;
}
_ => {}
}
let r = match request {
XrEnableRequest::TryEnable => XrNextEnabledState::Enabled,
XrEnableRequest::TryDisable => XrNextEnabledState::Disabled,
};
info!("{:#?}", r);
commands.insert_resource(r);
}
pub fn init_non_xr_graphics(primary_window: Option<RawHandleWrapper>) -> RenderCreationData {
let settings = WgpuSettings::default();
let async_renderer = async move {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
// Probably a bad idea unwraping here but on the other hand no backends
backends: settings.backends.unwrap(),
dx12_shader_compiler: settings.dx12_shader_compiler.clone(),
});
let surface = primary_window.map(|wrapper| unsafe {
// SAFETY: Plugins should be set up on the main thread.
let handle = wrapper.get_handle();
instance
.create_surface(&handle)
.expect("Failed to create wgpu surface")
});
let request_adapter_options = wgpu::RequestAdapterOptions {
power_preference: settings.power_preference,
compatible_surface: surface.as_ref(),
..Default::default()
};
let (device, queue, adapter_info, render_adapter) =
renderer::initialize_renderer(&instance, &settings, &request_adapter_options).await;
debug!("Configured wgpu adapter Limits: {:#?}", device.limits());
debug!("Configured wgpu adapter Features: {:#?}", device.features());
RenderCreationData {
device,
queue,
adapter_info,
render_adapter,
instance: Arc::new(instance),
}
};
// No need for wasm in bevy_oxr web xr would be a different crate
futures_lite::future::block_on(async_renderer)
}

View File

@@ -1,328 +0,0 @@
use bevy::{prelude::*, utils::HashMap};
use openxr as xr;
use xr::{Action, Binding, Haptic, Posef};
use crate::{resources::{XrInstance, XrSession}, xr_init::XrPrePostSetup};
use super::oculus_touch::ActionSets;
pub struct OpenXrActionsPlugin;
impl Plugin for OpenXrActionsPlugin {
fn build(&self, app: &mut App) {
app.insert_resource(SetupActionSets {
sets: HashMap::new(),
});
app.add_systems(XrPrePostSetup, setup_oxr_actions);
}
}
pub fn setup_oxr_actions(world: &mut World) {
let actions = world.remove_resource::<SetupActionSets>().unwrap();
let instance = world.get_resource::<XrInstance>().unwrap();
let session = world.get_resource::<XrSession>().unwrap();
let left_path = instance.string_to_path("/user/hand/left").unwrap();
let right_path = instance.string_to_path("/user/hand/right").unwrap();
let hands = [left_path, right_path];
let mut oxr_action_sets = Vec::new();
let mut action_sets = XrActionSets { sets: default() };
// let mut action_bindings: HashMap<&'static str, Vec<xr::Path>> = HashMap::new();
let mut a_iter = actions.sets.into_iter();
let mut action_bindings: HashMap<
(&'static str, &'static str),
HashMap<&'static str, Vec<xr::Path>>,
> = HashMap::new();
while let Some((set_name, set)) = a_iter.next() {
let mut actions: HashMap<&'static str, TypedAction> = default();
let oxr_action_set = instance
.create_action_set(set_name, set.pretty_name, set.priority)
.expect("Unable to create action set");
for (action_name, action) in set.actions.into_iter() {
let typed_action = match action.action_type {
ActionType::F32 => TypedAction::F32(match action.handednes {
ActionHandednes::Single => oxr_action_set
.create_action(action_name, action.pretty_name, &[])
.expect(&format!("Unable to create action: {}", action_name)),
ActionHandednes::Double => oxr_action_set
.create_action(action_name, action.pretty_name, &hands)
.expect(&format!("Unable to create action: {}", action_name)),
}),
ActionType::Bool => TypedAction::Bool(match action.handednes {
ActionHandednes::Single => oxr_action_set
.create_action(action_name, action.pretty_name, &[])
.expect(&format!("Unable to create action: {}", action_name)),
ActionHandednes::Double => oxr_action_set
.create_action(action_name, action.pretty_name, &hands)
.expect(&format!("Unable to create action: {}", action_name)),
}),
ActionType::PoseF => TypedAction::PoseF(match action.handednes {
ActionHandednes::Single => oxr_action_set
.create_action(action_name, action.pretty_name, &[])
.expect(&format!("Unable to create action: {}", action_name)),
ActionHandednes::Double => oxr_action_set
.create_action(action_name, action.pretty_name, &hands)
.expect(&format!("Unable to create action: {}", action_name)),
}),
ActionType::Haptic => TypedAction::Haptic(match action.handednes {
ActionHandednes::Single => oxr_action_set
.create_action(action_name, action.pretty_name, &[])
.expect(&format!("Unable to create action: {}", action_name)),
ActionHandednes::Double => oxr_action_set
.create_action(action_name, action.pretty_name, &hands)
.expect(&format!("Unable to create action: {}", action_name)),
}),
};
actions.insert(action_name, typed_action);
for (device_path, bindings) in action.bindings.into_iter() {
for b in bindings {
info!("binding {} to {}", action_name, b);
action_bindings
.entry((set_name, action_name))
.or_default()
.entry(device_path)
.or_default()
.push(instance.string_to_path(b).unwrap());
}
}
}
oxr_action_sets.push(oxr_action_set);
action_sets.sets.insert(
set_name,
ActionSet {
// oxr_action_set,
actions,
enabled: true,
},
);
}
let mut b_indings: HashMap<&'static str, Vec<Binding>> = HashMap::new();
for (dev, mut bindings) in action_sets
.sets
.iter()
.flat_map(|(set_name, set)| {
set.actions
.iter()
.map(move |(action_name, a)| (set_name, action_name, a))
})
.zip([&action_bindings].into_iter().cycle())
.flat_map(move |((set_name, action_name, action), bindings)| {
bindings
.get(&(set_name.clone(), action_name.clone()))
.unwrap()
.iter()
.map(move |(dev, bindings)| (action.clone(), dev.clone(), bindings))
})
.map(|(action, dev, bindings)| {
info!("Hi");
(
dev,
bindings
.into_iter()
.map(move |binding| match &action {
TypedAction::F32(a) => Binding::new(a, *binding),
TypedAction::Bool(a) => Binding::new(a, *binding),
TypedAction::PoseF(a) => Binding::new(a, *binding),
TypedAction::Haptic(a) => Binding::new(a, *binding),
})
.collect::<Vec<_>>(),
)
})
{
b_indings.entry(dev).or_default().append(&mut bindings);
}
for (dev, bindings) in b_indings.into_iter() {
info!(dev);
instance
.suggest_interaction_profile_bindings(instance.string_to_path(dev).unwrap(), &bindings)
.expect("Unable to suggest interaction bindings!");
}
session
.attach_action_sets(&oxr_action_sets.iter().collect::<Vec<_>>())
.expect("Unable to attach action sets!");
world.insert_resource(ActionSets(oxr_action_sets));
world.insert_resource(action_sets);
}
pub enum ActionHandednes {
Single,
Double,
}
pub enum ActionType {
F32,
Bool,
PoseF,
Haptic,
}
pub enum TypedAction {
F32(Action<f32>),
Bool(Action<bool>),
PoseF(Action<Posef>),
Haptic(Action<Haptic>),
}
pub struct SetupAction {
pretty_name: &'static str,
action_type: ActionType,
handednes: ActionHandednes,
bindings: HashMap<&'static str, Vec<&'static str>>,
}
pub struct SetupActionSet {
pretty_name: &'static str,
priority: u32,
actions: HashMap<&'static str, SetupAction>,
}
impl SetupActionSet {
pub fn new_action(
&mut self,
name: &'static str,
pretty_name: &'static str,
action_type: ActionType,
handednes: ActionHandednes,
) {
self.actions.insert(
name,
SetupAction {
pretty_name,
action_type,
handednes,
bindings: default(),
},
);
}
pub fn suggest_binding(&mut self, device_path: &'static str, bindings: &[XrBinding]) {
for binding in bindings {
self.actions
.get_mut(binding.action)
.ok_or(anyhow::anyhow!("Missing Action: {}", binding.action))
.unwrap()
.bindings
.entry(device_path)
.or_default()
.push(binding.path);
}
}
}
pub struct XrBinding {
action: &'static str,
path: &'static str,
}
impl XrBinding {
pub fn new(action_name: &'static str, binding_path: &'static str) -> XrBinding {
XrBinding {
action: action_name,
path: binding_path,
}
}
}
#[derive(Resource)]
pub struct SetupActionSets {
sets: HashMap<&'static str, SetupActionSet>,
}
impl SetupActionSets {
pub fn add_action_set(
&mut self,
name: &'static str,
pretty_name: &'static str,
priority: u32,
) -> &mut SetupActionSet {
self.sets.insert(
name,
SetupActionSet {
pretty_name,
priority,
actions: HashMap::new(),
},
);
self.sets.get_mut(name).unwrap()
}
}
pub struct ActionSet {
// oxr_action_set: xr::ActionSet,
enabled: bool,
actions: HashMap<&'static str, TypedAction>,
}
#[derive(Resource)]
pub struct XrActionSets {
sets: HashMap<&'static str, ActionSet>,
}
impl XrActionSets {
pub fn get_action_f32(
&self,
action_set: &'static str,
action_name: &'static str,
) -> anyhow::Result<&Action<f32>> {
let action = self
.sets
.get(action_set)
.ok_or(anyhow::anyhow!("Action Set Not Found!"))?
.actions
.get(action_name)
.ok_or(anyhow::anyhow!("Action Not Found!"))?;
match action {
TypedAction::F32(a) => Ok(a),
_ => anyhow::bail!("wrong action type"),
}
}
pub fn get_action_bool(
&self,
action_set: &'static str,
action_name: &'static str,
) -> anyhow::Result<&Action<bool>> {
let action = self
.sets
.get(action_set)
.ok_or(anyhow::anyhow!("Action Set Not Found!"))?
.actions
.get(action_name)
.ok_or(anyhow::anyhow!("Action Not Found!"))?;
match action {
TypedAction::Bool(a) => Ok(a),
_ => anyhow::bail!("wrong action type"),
}
}
pub fn get_action_posef(
&self,
action_set: &'static str,
action_name: &'static str,
) -> anyhow::Result<&Action<Posef>> {
let action = self
.sets
.get(action_set)
.ok_or(anyhow::anyhow!("Action Set Not Found!"))?
.actions
.get(action_name)
.ok_or(anyhow::anyhow!("Action Not Found!"))?;
match action {
TypedAction::PoseF(a) => Ok(a),
_ => anyhow::bail!("wrong action type"),
}
}
pub fn get_action_haptic(
&self,
action_set: &'static str,
action_name: &'static str,
) -> anyhow::Result<&Action<Haptic>> {
let action = self
.sets
.get(action_set)
.ok_or(anyhow::anyhow!("Action Set Not Found!"))?
.actions
.get(action_name)
.ok_or(anyhow::anyhow!("Action Not Found!"))?;
match action {
TypedAction::Haptic(a) => Ok(a),
_ => anyhow::bail!("wrong action type"),
}
}
}

View File

@@ -1,14 +0,0 @@
use openxr::{Action, ActionTy};
pub struct Touchable<T: ActionTy> {
pub inner: Action<T>,
pub touch: Action<bool>,
}
pub struct Handed<T> {
pub left: T,
pub right: T,
}
#[derive(Copy, Clone)]
pub enum XrControllerType {
OculusTouch,
}

View File

@@ -1,355 +0,0 @@
use bevy::ecs::schedule::IntoSystemConfigs;
use bevy::log::{debug, info};
use bevy::prelude::{
Color, Gizmos, GlobalTransform, Plugin, Quat, Query, Res, Transform, Update, Vec2, Vec3, With,
Without,
};
use crate::xr_init::xr_only;
use crate::{
input::XrInput,
resources::{XrFrameState, XrSession},
};
use crate::xr_input::{
oculus_touch::{OculusController, OculusControllerRef},
Hand,
};
use super::{
actions::XrActionSets,
trackers::{OpenXRLeftController, OpenXRRightController, OpenXRTrackingRoot},
};
/// add debug renderer for controllers
#[derive(Default)]
pub struct OpenXrDebugRenderer;
impl Plugin for OpenXrDebugRenderer {
fn build(&self, app: &mut bevy::prelude::App) {
app.add_systems(Update, draw_gizmos.run_if(xr_only()));
}
}
#[allow(clippy::too_many_arguments, clippy::complexity)]
pub fn draw_gizmos(
mut gizmos: Gizmos,
oculus_controller: Res<OculusController>,
frame_state: Res<XrFrameState>,
xr_input: Res<XrInput>,
session: Res<XrSession>,
tracking_root_query: Query<
&mut Transform,
(
With<OpenXRTrackingRoot>,
Without<OpenXRLeftController>,
Without<OpenXRRightController>,
),
>,
left_controller_query: Query<
&GlobalTransform,
(
With<OpenXRLeftController>,
Without<OpenXRRightController>,
Without<OpenXRTrackingRoot>,
),
>,
right_controller_query: Query<
&GlobalTransform,
(
With<OpenXRRightController>,
Without<OpenXRLeftController>,
Without<OpenXRTrackingRoot>,
),
>,
action_sets: Res<XrActionSets>,
) {
// if let Some(hand_tracking) = hand_tracking {
// let handtracking_ref = hand_tracking.get_ref(&xr_input, &frame_state);
// if let Some(joints) = handtracking_ref.get_poses(Hand::Left) {
// for joint in joints.inner() {
// let trans = Transform::from_rotation(joint.orientation);
// gizmos.circle(
// joint.position,
// trans.forward(),
// joint.radius,
// Color::ORANGE_RED,
// );
// }
// }
// if let Some(joints) = handtracking_ref.get_poses(Hand::Right) {
// for joint in joints.inner() {
// let trans = Transform::from_rotation(joint.orientation);
// gizmos.circle(
// joint.position,
// trans.forward(),
// joint.radius,
// Color::LIME_GREEN,
// );
// }
// return;
// }
// }
//lock frame
let frame_state = *frame_state.lock().unwrap();
//get controller
let controller = oculus_controller.get_ref(&session, &frame_state, &xr_input, &action_sets);
let root = tracking_root_query.get_single();
match root {
Ok(position) => {
gizmos.circle(
position.translation
+ Vec3 {
x: 0.0,
y: 0.01,
z: 0.0,
},
Vec3::Y,
0.2,
Color::RED,
);
}
Err(_) => info!("too many tracking roots"),
}
//draw the hands
//left
let left_transform = left_controller_query.get_single();
match left_transform {
Ok(left_entity) => {
draw_hand_gizmo(&mut gizmos, &controller, Hand::Left, left_entity);
}
Err(_) => debug!("no left controller entity for debug gizmos"),
}
//right
let right_transform = right_controller_query.get_single();
match right_transform {
Ok(right_entity) => {
draw_hand_gizmo(&mut gizmos, &controller, Hand::Right, right_entity);
}
Err(_) => debug!("no right controller entity for debug gizmos"),
}
}
fn draw_hand_gizmo(
gizmos: &mut Gizmos,
controller: &OculusControllerRef<'_>,
hand: Hand,
hand_transform: &GlobalTransform,
) {
match hand {
Hand::Left => {
let left_color = Color::YELLOW_GREEN;
let off_color = Color::BLUE;
let touch_color = Color::GREEN;
let pressed_color = Color::RED;
let grip_quat_offset = Quat::from_rotation_x(-1.4);
let face_quat_offset = Quat::from_rotation_x(1.05);
let trans = hand_transform.compute_transform();
let controller_vec3 = trans.translation;
let controller_quat = trans.rotation;
let face_quat = controller_quat.mul_quat(face_quat_offset);
let face_quat_normal = face_quat.mul_vec3(Vec3::Z);
//draw grip
gizmos.rect(
controller_vec3,
controller_quat * grip_quat_offset,
Vec2::new(0.05, 0.1),
left_color,
);
let face_translation_offset = Quat::from_rotation_x(-1.7); //direction to move the face from the controller tracking point
let face_translation_vec3 = controller_vec3
+ controller_quat
.mul_quat(face_translation_offset)
.mul_vec3(Vec3::Y * 0.075); //distance to move face by
//draw face
gizmos.circle(
face_translation_vec3,
face_quat_normal,
0.04,
Color::YELLOW_GREEN,
);
//button b
let mut b_color = off_color;
if controller.y_button_touched() {
b_color = touch_color;
}
if controller.y_button() {
b_color = pressed_color;
}
let b_offset_quat = face_quat;
let b_translation_vec3 =
face_translation_vec3 + b_offset_quat.mul_vec3(Vec3::new(0.025, -0.01, 0.0));
gizmos.circle(b_translation_vec3, face_quat_normal, 0.0075, b_color);
//button a
let mut a_color = off_color;
if controller.x_button_touched() {
a_color = touch_color;
}
if controller.x_button() {
a_color = pressed_color;
}
let a_offset_quat = face_quat;
let a_translation_vec3 =
face_translation_vec3 + a_offset_quat.mul_vec3(Vec3::new(0.025, 0.01, 0.0));
gizmos.circle(a_translation_vec3, face_quat_normal, 0.0075, a_color);
//joystick
let joystick_offset_quat = face_quat;
let joystick_base_vec =
face_translation_vec3 + joystick_offset_quat.mul_vec3(Vec3::new(-0.02, 0.0, 0.0));
let mut joystick_color = off_color;
if controller.thumbstick_touch(Hand::Left) {
joystick_color = touch_color;
}
//base
gizmos.circle(joystick_base_vec, face_quat_normal, 0.014, joystick_color);
let stick = controller.thumbstick(Hand::Left);
let input = Vec3::new(stick.x, -stick.y, 0.0);
let joystick_top_vec = face_translation_vec3
+ joystick_offset_quat.mul_vec3(Vec3::new(-0.02, 0.0, -0.01))
+ joystick_offset_quat.mul_vec3(input * 0.01);
//top
gizmos.circle(joystick_top_vec, face_quat_normal, 0.005, joystick_color);
//trigger
let trigger_state = controller.trigger(Hand::Left);
let trigger_rotation = Quat::from_rotation_x(-0.75 * trigger_state);
let mut trigger_color = off_color;
if controller.trigger_touched(Hand::Left) {
trigger_color = touch_color;
}
let trigger_transform = Transform {
translation: face_translation_vec3
+ face_quat
.mul_quat(trigger_rotation)
.mul_vec3(Vec3::new(0.0, 0.0, 0.02)),
rotation: face_quat.mul_quat(trigger_rotation),
scale: Vec3 {
x: 0.01,
y: 0.02,
z: 0.03,
},
};
gizmos.cuboid(trigger_transform, trigger_color);
}
Hand::Right => {
//get right controller
let right_color = Color::YELLOW_GREEN;
let off_color = Color::BLUE;
let touch_color = Color::GREEN;
let pressed_color = Color::RED;
let grip_quat_offset = Quat::from_rotation_x(-1.4);
let face_quat_offset = Quat::from_rotation_x(1.05);
let trans = hand_transform.compute_transform();
let controller_vec3 = trans.translation;
let controller_quat = trans.rotation;
let face_quat = controller_quat.mul_quat(face_quat_offset);
let face_quat_normal = face_quat.mul_vec3(Vec3::Z);
let _squeeze = controller.squeeze(Hand::Right);
//info!("{:?}", squeeze);
//grip
gizmos.rect(
controller_vec3,
controller_quat * grip_quat_offset,
Vec2::new(0.05, 0.1),
right_color,
);
let face_translation_offset = Quat::from_rotation_x(-1.7); //direction to move the face from the controller tracking point
let face_translation_vec3 = controller_vec3
+ controller_quat
.mul_quat(face_translation_offset)
.mul_vec3(Vec3::Y * 0.075); //distance to move face by
//draw face
gizmos.circle(
face_translation_vec3,
face_quat_normal,
0.04,
Color::YELLOW_GREEN,
);
//button b
let mut b_color = off_color;
if controller.b_button_touched() {
b_color = touch_color;
}
if controller.b_button() {
b_color = pressed_color;
}
let b_offset_quat = face_quat;
let b_translation_vec3 =
face_translation_vec3 + b_offset_quat.mul_vec3(Vec3::new(-0.025, -0.01, 0.0));
gizmos.circle(b_translation_vec3, face_quat_normal, 0.0075, b_color);
//button a
let mut a_color = off_color;
if controller.a_button_touched() {
a_color = touch_color;
}
if controller.a_button() {
a_color = pressed_color;
}
let a_offset_quat = face_quat;
let a_translation_vec3 =
face_translation_vec3 + a_offset_quat.mul_vec3(Vec3::new(-0.025, 0.01, 0.0));
gizmos.circle(a_translation_vec3, face_quat_normal, 0.0075, a_color);
//joystick time
let joystick_offset_quat = face_quat;
let joystick_base_vec =
face_translation_vec3 + joystick_offset_quat.mul_vec3(Vec3::new(0.02, 0.0, 0.0));
let mut joystick_color = off_color;
if controller.thumbstick_touch(Hand::Right) {
joystick_color = touch_color;
}
//base
gizmos.circle(joystick_base_vec, face_quat_normal, 0.014, joystick_color);
let stick = controller.thumbstick(Hand::Right);
let input = Vec3::new(stick.x, -stick.y, 0.0);
let joystick_top_vec = face_translation_vec3
+ joystick_offset_quat.mul_vec3(Vec3::new(0.02, 0.0, -0.01))
+ joystick_offset_quat.mul_vec3(input * 0.01);
//top
gizmos.circle(joystick_top_vec, face_quat_normal, 0.005, joystick_color);
//trigger
let trigger_state = controller.trigger(Hand::Right);
let trigger_rotation = Quat::from_rotation_x(-0.75 * trigger_state);
let mut trigger_color = off_color;
if controller.trigger_touched(Hand::Right) {
trigger_color = touch_color;
}
let trigger_transform = Transform {
translation: face_translation_vec3
+ face_quat
.mul_quat(trigger_rotation)
.mul_vec3(Vec3::new(0.0, 0.0, 0.02)),
rotation: face_quat.mul_quat(trigger_rotation),
scale: Vec3 {
x: 0.01,
y: 0.02,
z: 0.03,
},
};
gizmos.cuboid(trigger_transform, trigger_color);
}
}
}

View File

@@ -1,520 +0,0 @@
use bevy::prelude::{Quat, Transform, Vec3};
use openxr::{Posef, Quaternionf, Vector3f};
use super::Hand;
pub fn get_simulated_open_hand_transforms(hand: Hand) -> [Transform; 26] {
let test_hand_bones: [Vec3; 26] = [
Vec3 {
x: 0.0,
y: 0.0,
z: 0.0,
}, //palm
Vec3 {
x: 0.0,
y: 0.0,
z: -0.04,
}, //wrist
Vec3 {
x: -0.02,
y: 0.00,
z: 0.015,
}, //thumb
Vec3 {
x: 0.0,
y: 0.0,
z: 0.03,
},
Vec3 {
x: 0.0,
y: 0.0,
z: 0.024,
},
Vec3 {
x: 0.0,
y: 0.0,
z: 0.024,
},
Vec3 {
x: -0.01,
y: -0.015,
z: 0.0155,
}, //index
Vec3 {
x: 0.0,
y: 0.0,
z: 0.064,
},
Vec3 {
x: 0.0,
y: 0.0,
z: 0.037,
},
Vec3 {
x: 0.0,
y: 0.0,
z: 0.02,
},
Vec3 {
x: 0.0,
y: 0.0,
z: 0.01,
},
Vec3 {
x: 0.0,
y: -0.02,
z: 0.016,
}, //middle
Vec3 {
x: 0.0,
y: 0.0,
z: 0.064,
},
Vec3 {
x: 0.0,
y: 0.0,
z: 0.037,
},
Vec3 {
x: 0.0,
y: 0.0,
z: 0.02,
},
Vec3 {
x: 0.0,
y: 0.0,
z: 0.01,
},
Vec3 {
x: 0.01,
y: -0.015,
z: 0.015,
}, //ring
Vec3 {
x: 0.0,
y: 0.0,
z: 0.064,
},
Vec3 {
x: 0.0,
y: 0.0,
z: 0.037,
},
Vec3 {
x: 0.0,
y: 0.0,
z: 0.02,
},
Vec3 {
x: 0.0,
y: 0.0,
z: 0.01,
},
Vec3 {
x: 0.02,
y: -0.01,
z: 0.015,
}, //little
Vec3 {
x: 0.0,
y: 0.0,
z: 0.064,
},
Vec3 {
x: 0.0,
y: 0.0,
z: 0.037,
},
Vec3 {
x: 0.0,
y: 0.0,
z: 0.02,
},
Vec3 {
x: 0.0,
y: 0.0,
z: 0.01,
},
];
let result = bones_to_transforms(test_hand_bones, hand);
return result;
}
fn bones_to_transforms(hand_bones: [Vec3; 26], hand: Hand) -> [Transform; 26] {
match hand {
Hand::Left => {
let mut result_array: [Transform; 26] = [Transform::default(); 26];
for (place, data) in result_array.iter_mut().zip(hand_bones.iter()) {
*place = Transform {
translation: Vec3 {
x: -data.x,
y: -data.y,
z: -data.z,
},
rotation: Quat::IDENTITY,
scale: Vec3::splat(1.0),
}
}
return result_array;
}
Hand::Right => {
let mut result_array: [Transform; 26] = [Transform::default(); 26];
for (place, data) in result_array.iter_mut().zip(hand_bones.iter()) {
*place = Transform {
translation: Vec3 {
x: data.x,
y: -data.y,
z: -data.z,
},
rotation: Quat::IDENTITY,
scale: Vec3::splat(1.0),
}
}
return result_array;
}
}
}
pub fn get_test_hand_pose_array() -> [Posef; 26] {
let test_hand_pose: [Posef; 26] = [
Posef {
position: Vector3f {
x: 0.0,
y: 0.0,
z: 0.0,
},
orientation: Quaternionf {
x: -0.267,
y: 0.849,
z: 0.204,
w: 0.407,
},
}, //palm
Posef {
position: Vector3f {
x: 0.02,
y: -0.040,
z: -0.015,
},
orientation: Quaternionf {
x: -0.267,
y: 0.849,
z: 0.204,
w: 0.407,
},
},
Posef {
position: Vector3f {
x: 0.019,
y: -0.037,
z: 0.011,
},
orientation: Quaternionf {
x: -0.744,
y: -0.530,
z: 0.156,
w: -0.376,
},
},
Posef {
position: Vector3f {
x: 0.015,
y: -0.014,
z: 0.047,
},
orientation: Quaternionf {
x: -0.786,
y: -0.550,
z: 0.126,
w: -0.254,
},
},
Posef {
position: Vector3f {
x: 0.004,
y: 0.003,
z: 0.068,
},
orientation: Quaternionf {
x: -0.729,
y: -0.564,
z: 0.027,
w: -0.387,
},
},
Posef {
position: Vector3f {
x: -0.009,
y: 0.011,
z: 0.072,
},
orientation: Quaternionf {
x: -0.585,
y: -0.548,
z: -0.140,
w: -0.582,
},
},
Posef {
position: Vector3f {
x: 0.027,
y: -0.021,
z: 0.001,
},
orientation: Quaternionf {
x: -0.277,
y: -0.826,
z: 0.317,
w: -0.376,
},
},
Posef {
position: Vector3f {
x: -0.002,
y: 0.026,
z: 0.034,
},
orientation: Quaternionf {
x: -0.277,
y: -0.826,
z: 0.317,
w: -0.376,
},
},
Posef {
position: Vector3f {
x: -0.023,
y: 0.049,
z: 0.055,
},
orientation: Quaternionf {
x: -0.244,
y: -0.843,
z: 0.256,
w: -0.404,
},
},
Posef {
position: Vector3f {
x: -0.037,
y: 0.059,
z: 0.067,
},
orientation: Quaternionf {
x: -0.200,
y: -0.866,
z: 0.165,
w: -0.428,
},
},
Posef {
position: Vector3f {
x: -0.045,
y: 0.063,
z: 0.073,
},
orientation: Quaternionf {
x: -0.172,
y: -0.874,
z: 0.110,
w: -0.440,
},
},
Posef {
position: Vector3f {
x: 0.021,
y: -0.017,
z: -0.007,
},
orientation: Quaternionf {
x: -0.185,
y: -0.817,
z: 0.370,
w: -0.401,
},
},
Posef {
position: Vector3f {
x: -0.011,
y: 0.029,
z: 0.018,
},
orientation: Quaternionf {
x: -0.185,
y: -0.817,
z: 0.370,
w: -0.401,
},
},
Posef {
position: Vector3f {
x: -0.034,
y: 0.06,
z: 0.033,
},
orientation: Quaternionf {
x: -0.175,
y: -0.809,
z: 0.371,
w: -0.420,
},
},
Posef {
position: Vector3f {
x: -0.051,
y: 0.072,
z: 0.045,
},
orientation: Quaternionf {
x: -0.109,
y: -0.856,
z: 0.245,
w: -0.443,
},
},
Posef {
position: Vector3f {
x: -0.06,
y: 0.077,
z: 0.051,
},
orientation: Quaternionf {
x: -0.075,
y: -0.871,
z: 0.180,
w: -0.450,
},
},
Posef {
position: Vector3f {
x: 0.013,
y: -0.017,
z: -0.015,
},
orientation: Quaternionf {
x: -0.132,
y: -0.786,
z: 0.408,
w: -0.445,
},
},
Posef {
position: Vector3f {
x: -0.02,
y: 0.025,
z: 0.0,
},
orientation: Quaternionf {
x: -0.132,
y: -0.786,
z: 0.408,
w: -0.445,
},
},
Posef {
position: Vector3f {
x: -0.042,
y: 0.055,
z: 0.007,
},
orientation: Quaternionf {
x: -0.131,
y: -0.762,
z: 0.432,
w: -0.464,
},
},
Posef {
position: Vector3f {
x: -0.06,
y: 0.069,
z: 0.015,
},
orientation: Quaternionf {
x: -0.071,
y: -0.810,
z: 0.332,
w: -0.477,
},
},
Posef {
position: Vector3f {
x: -0.069,
y: 0.075,
z: 0.02,
},
orientation: Quaternionf {
x: -0.029,
y: -0.836,
z: 0.260,
w: -0.482,
},
},
Posef {
position: Vector3f {
x: 0.004,
y: -0.022,
z: -0.022,
},
orientation: Quaternionf {
x: -0.060,
y: -0.749,
z: 0.481,
w: -0.452,
},
},
Posef {
position: Vector3f {
x: -0.028,
y: 0.018,
z: -0.015,
},
orientation: Quaternionf {
x: -0.060,
y: -0.749,
z: 0.481,
w: -0.452,
},
},
Posef {
position: Vector3f {
x: -0.046,
y: 0.042,
z: -0.017,
},
orientation: Quaternionf {
x: -0.061,
y: -0.684,
z: 0.534,
w: -0.493,
},
},
Posef {
position: Vector3f {
x: -0.059,
y: 0.053,
z: -0.015,
},
orientation: Quaternionf {
x: 0.002,
y: -0.745,
z: 0.444,
w: -0.498,
},
},
Posef {
position: Vector3f {
x: -0.068,
y: 0.059,
z: -0.013,
},
orientation: Quaternionf {
x: 0.045,
y: -0.780,
z: 0.378,
w: -0.496,
},
},
];
return test_hand_pose;
}

View File

@@ -1,286 +0,0 @@
use bevy::prelude::{
default, Color, Commands, Component, Deref, DerefMut, Entity, Gizmos, Plugin, PostUpdate,
Query, Resource, SpatialBundle, Startup, Transform,
};
use crate::xr_input::{Hand, trackers::OpenXRTracker};
use super::{HandBone, BoneTrackingStatus};
/// add debug renderer for controllers
#[derive(Default)]
pub struct OpenXrHandInput;
impl Plugin for OpenXrHandInput {
fn build(&self, app: &mut bevy::prelude::App) {
app.add_systems(Startup, spawn_hand_entities);
}
}
/// add debug renderer for controllers
#[derive(Default)]
pub struct HandInputDebugRenderer;
impl Plugin for HandInputDebugRenderer {
fn build(&self, app: &mut bevy::prelude::App) {
app.add_systems(PostUpdate, draw_hand_entities);
}
}
#[derive(Resource, Default, Clone, Copy)]
pub struct HandsResource {
pub left: HandResource,
pub right: HandResource,
}
#[derive(Clone, Copy)]
pub struct HandResource {
pub palm: Entity,
pub wrist: Entity,
pub thumb: ThumbResource,
pub index: IndexResource,
pub middle: MiddleResource,
pub ring: RingResource,
pub little: LittleResource,
}
impl Default for HandResource {
fn default() -> Self {
Self {
palm: Entity::PLACEHOLDER,
wrist: Entity::PLACEHOLDER,
thumb: Default::default(),
index: Default::default(),
middle: Default::default(),
ring: Default::default(),
little: Default::default(),
}
}
}
#[derive(Clone, Copy)]
pub struct ThumbResource {
pub metacarpal: Entity,
pub proximal: Entity,
pub distal: Entity,
pub tip: Entity,
}
impl Default for ThumbResource {
fn default() -> Self {
Self {
metacarpal: Entity::PLACEHOLDER,
proximal: Entity::PLACEHOLDER,
distal: Entity::PLACEHOLDER,
tip: Entity::PLACEHOLDER,
}
}
}
#[derive(Clone, Copy)]
pub struct IndexResource {
pub metacarpal: Entity,
pub proximal: Entity,
pub intermediate: Entity,
pub distal: Entity,
pub tip: Entity,
}
impl Default for IndexResource {
fn default() -> Self {
Self {
metacarpal: Entity::PLACEHOLDER,
proximal: Entity::PLACEHOLDER,
intermediate: Entity::PLACEHOLDER,
distal: Entity::PLACEHOLDER,
tip: Entity::PLACEHOLDER,
}
}
}
#[derive(Clone, Copy)]
pub struct MiddleResource {
pub metacarpal: Entity,
pub proximal: Entity,
pub intermediate: Entity,
pub distal: Entity,
pub tip: Entity,
}
impl Default for MiddleResource {
fn default() -> Self {
Self {
metacarpal: Entity::PLACEHOLDER,
proximal: Entity::PLACEHOLDER,
intermediate: Entity::PLACEHOLDER,
distal: Entity::PLACEHOLDER,
tip: Entity::PLACEHOLDER,
}
}
}
#[derive(Clone, Copy)]
pub struct RingResource {
pub metacarpal: Entity,
pub proximal: Entity,
pub intermediate: Entity,
pub distal: Entity,
pub tip: Entity,
}
impl Default for RingResource {
fn default() -> Self {
Self {
metacarpal: Entity::PLACEHOLDER,
proximal: Entity::PLACEHOLDER,
intermediate: Entity::PLACEHOLDER,
distal: Entity::PLACEHOLDER,
tip: Entity::PLACEHOLDER,
}
}
}
#[derive(Clone, Copy)]
pub struct LittleResource {
pub metacarpal: Entity,
pub proximal: Entity,
pub intermediate: Entity,
pub distal: Entity,
pub tip: Entity,
}
impl Default for LittleResource {
fn default() -> Self {
Self {
metacarpal: Entity::PLACEHOLDER,
proximal: Entity::PLACEHOLDER,
intermediate: Entity::PLACEHOLDER,
distal: Entity::PLACEHOLDER,
tip: Entity::PLACEHOLDER,
}
}
}
pub fn spawn_hand_entities(mut commands: Commands) {
let hands = [Hand::Left, Hand::Right];
let bones = HandBone::get_all_bones();
//hand resource
let mut hand_resource = HandsResource { ..default() };
for hand in hands.iter() {
for bone in bones.iter() {
let boneid = commands
.spawn((
SpatialBundle::default(),
bone.clone(),
OpenXRTracker,
hand.clone(),
BoneTrackingStatus::Emulated,
HandBoneRadius(0.1),
))
.id();
match hand {
Hand::Left => match bone {
HandBone::Palm => hand_resource.left.palm = boneid,
HandBone::Wrist => hand_resource.left.wrist = boneid,
HandBone::ThumbMetacarpal => hand_resource.left.thumb.metacarpal = boneid,
HandBone::ThumbProximal => hand_resource.left.thumb.proximal = boneid,
HandBone::ThumbDistal => hand_resource.left.thumb.distal = boneid,
HandBone::ThumbTip => hand_resource.left.thumb.tip = boneid,
HandBone::IndexMetacarpal => hand_resource.left.index.metacarpal = boneid,
HandBone::IndexProximal => hand_resource.left.index.proximal = boneid,
HandBone::IndexIntermediate => hand_resource.left.index.intermediate = boneid,
HandBone::IndexDistal => hand_resource.left.index.distal = boneid,
HandBone::IndexTip => hand_resource.left.index.tip = boneid,
HandBone::MiddleMetacarpal => hand_resource.left.middle.metacarpal = boneid,
HandBone::MiddleProximal => hand_resource.left.middle.proximal = boneid,
HandBone::MiddleIntermediate => hand_resource.left.middle.intermediate = boneid,
HandBone::MiddleDistal => hand_resource.left.middle.distal = boneid,
HandBone::MiddleTip => hand_resource.left.middle.tip = boneid,
HandBone::RingMetacarpal => hand_resource.left.ring.metacarpal = boneid,
HandBone::RingProximal => hand_resource.left.ring.proximal = boneid,
HandBone::RingIntermediate => hand_resource.left.ring.intermediate = boneid,
HandBone::RingDistal => hand_resource.left.ring.distal = boneid,
HandBone::RingTip => hand_resource.left.ring.tip = boneid,
HandBone::LittleMetacarpal => hand_resource.left.little.metacarpal = boneid,
HandBone::LittleProximal => hand_resource.left.little.proximal = boneid,
HandBone::LittleIntermediate => hand_resource.left.little.intermediate = boneid,
HandBone::LittleDistal => hand_resource.left.little.distal = boneid,
HandBone::LittleTip => hand_resource.left.little.tip = boneid,
},
Hand::Right => match bone {
HandBone::Palm => hand_resource.right.palm = boneid,
HandBone::Wrist => hand_resource.right.wrist = boneid,
HandBone::ThumbMetacarpal => hand_resource.right.thumb.metacarpal = boneid,
HandBone::ThumbProximal => hand_resource.right.thumb.proximal = boneid,
HandBone::ThumbDistal => hand_resource.right.thumb.distal = boneid,
HandBone::ThumbTip => hand_resource.right.thumb.tip = boneid,
HandBone::IndexMetacarpal => hand_resource.right.index.metacarpal = boneid,
HandBone::IndexProximal => hand_resource.right.index.proximal = boneid,
HandBone::IndexIntermediate => hand_resource.right.index.intermediate = boneid,
HandBone::IndexDistal => hand_resource.right.index.distal = boneid,
HandBone::IndexTip => hand_resource.right.index.tip = boneid,
HandBone::MiddleMetacarpal => hand_resource.right.middle.metacarpal = boneid,
HandBone::MiddleProximal => hand_resource.right.middle.proximal = boneid,
HandBone::MiddleIntermediate => {
hand_resource.right.middle.intermediate = boneid
}
HandBone::MiddleDistal => hand_resource.right.middle.distal = boneid,
HandBone::MiddleTip => hand_resource.right.middle.tip = boneid,
HandBone::RingMetacarpal => hand_resource.right.ring.metacarpal = boneid,
HandBone::RingProximal => hand_resource.right.ring.proximal = boneid,
HandBone::RingIntermediate => hand_resource.right.ring.intermediate = boneid,
HandBone::RingDistal => hand_resource.right.ring.distal = boneid,
HandBone::RingTip => hand_resource.right.ring.tip = boneid,
HandBone::LittleMetacarpal => hand_resource.right.little.metacarpal = boneid,
HandBone::LittleProximal => hand_resource.right.little.proximal = boneid,
HandBone::LittleIntermediate => {
hand_resource.right.little.intermediate = boneid
}
HandBone::LittleDistal => hand_resource.right.little.distal = boneid,
HandBone::LittleTip => hand_resource.right.little.tip = boneid,
},
}
}
}
commands.insert_resource(hand_resource);
}
#[derive(Debug, Component, DerefMut, Deref)]
pub struct HandBoneRadius(pub f32);
pub fn draw_hand_entities(
mut gizmos: Gizmos,
query: Query<(&Transform, &HandBone, &HandBoneRadius)>,
) {
for (transform, hand_bone, hand_bone_radius) in query.iter() {
let (_, color) = get_bone_gizmo_style(hand_bone);
gizmos.sphere(
transform.translation,
transform.rotation,
hand_bone_radius.0,
color,
);
}
}
pub(crate) fn get_bone_gizmo_style(hand_bone: &HandBone) -> (f32, Color) {
match hand_bone {
HandBone::Palm => (0.01, Color::WHITE),
HandBone::Wrist => (0.01, Color::GRAY),
HandBone::ThumbMetacarpal => (0.01, Color::RED),
HandBone::ThumbProximal => (0.008, Color::RED),
HandBone::ThumbDistal => (0.006, Color::RED),
HandBone::ThumbTip => (0.004, Color::RED),
HandBone::IndexMetacarpal => (0.01, Color::ORANGE),
HandBone::IndexProximal => (0.008, Color::ORANGE),
HandBone::IndexIntermediate => (0.006, Color::ORANGE),
HandBone::IndexDistal => (0.004, Color::ORANGE),
HandBone::IndexTip => (0.002, Color::ORANGE),
HandBone::MiddleMetacarpal => (0.01, Color::YELLOW),
HandBone::MiddleProximal => (0.008, Color::YELLOW),
HandBone::MiddleIntermediate => (0.006, Color::YELLOW),
HandBone::MiddleDistal => (0.004, Color::YELLOW),
HandBone::MiddleTip => (0.002, Color::YELLOW),
HandBone::RingMetacarpal => (0.01, Color::GREEN),
HandBone::RingProximal => (0.008, Color::GREEN),
HandBone::RingIntermediate => (0.006, Color::GREEN),
HandBone::RingDistal => (0.004, Color::GREEN),
HandBone::RingTip => (0.002, Color::GREEN),
HandBone::LittleMetacarpal => (0.01, Color::BLUE),
HandBone::LittleProximal => (0.008, Color::BLUE),
HandBone::LittleIntermediate => (0.006, Color::BLUE),
HandBone::LittleDistal => (0.004, Color::BLUE),
HandBone::LittleTip => (0.002, Color::BLUE),
}
}

View File

@@ -1,550 +0,0 @@
use std::f32::consts::PI;
use bevy::prelude::*;
use openxr::{ActionTy, HandJoint};
use super::common::{get_bone_gizmo_style, HandBoneRadius};
use crate::{
xr_init::{xr_only, XrSetup},
resources::{XrInstance, XrSession},
xr_input::{
actions::{
ActionHandednes, ActionType, SetupActionSet, SetupActionSets, XrActionSets, XrBinding,
},
hand_poses::get_simulated_open_hand_transforms,
trackers::{OpenXRLeftController, OpenXRRightController, OpenXRTrackingRoot},
Hand,
},
};
use super::{BoneTrackingStatus, HandBone};
pub enum TouchValue<T: ActionTy> {
None,
Touched(T),
}
pub struct HandEmulationPlugin;
impl Plugin for HandEmulationPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
Update,
update_hand_skeleton_from_emulated.run_if(xr_only()),
);
app.add_systems(XrSetup, setup_hand_emulation_action_set);
}
}
const HAND_ACTION_SET: &str = "hand_pose_approx";
fn setup_hand_emulation_action_set(mut action_sets: ResMut<SetupActionSets>) {
let mut action_set = action_sets.add_action_set(HAND_ACTION_SET, "Hand Pose Approximaiton", 0);
action_set.new_action(
"thumb_touch",
"Thumb Touched",
ActionType::Bool,
ActionHandednes::Double,
);
action_set.new_action(
"thumb_x",
"Thumb X",
ActionType::F32,
ActionHandednes::Double,
);
action_set.new_action(
"thumb_y",
"Thumb Y",
ActionType::F32,
ActionHandednes::Double,
);
action_set.new_action(
"index_touch",
"Index Finger Touched",
ActionType::Bool,
ActionHandednes::Double,
);
action_set.new_action(
"index_value",
"Index Finger Pull",
ActionType::F32,
ActionHandednes::Double,
);
action_set.new_action(
"middle_value",
"Middle Finger Pull",
ActionType::F32,
ActionHandednes::Double,
);
action_set.new_action(
"ring_value",
"Ring Finger Pull",
ActionType::F32,
ActionHandednes::Double,
);
action_set.new_action(
"little_value",
"Little Finger Pull",
ActionType::F32,
ActionHandednes::Double,
);
suggest_oculus_touch_profile(action_set);
}
pub struct EmulatedHandPoseData {}
fn suggest_oculus_touch_profile(action_set: &mut SetupActionSet) {
action_set.suggest_binding(
"/interaction_profiles/oculus/touch_controller",
&[
XrBinding::new("thumb_x", "/user/hand/left/input/thumbstick/x"),
XrBinding::new("thumb_x", "/user/hand/right/input/thumbstick/x"),
XrBinding::new("thumb_y", "/user/hand/left/input/thumbstick/y"),
XrBinding::new("thumb_y", "/user/hand/right/input/thumbstick/y"),
XrBinding::new("thumb_touch", "/user/hand/left/input/thumbstick/touch"),
XrBinding::new("thumb_touch", "/user/hand/right/input/thumbstick/touch"),
XrBinding::new("thumb_touch", "/user/hand/left/input/x/touch"),
XrBinding::new("thumb_touch", "/user/hand/left/input/y/touch"),
XrBinding::new("thumb_touch", "/user/hand/right/input/a/touch"),
XrBinding::new("thumb_touch", "/user/hand/right/input/b/touch"),
XrBinding::new("thumb_touch", "/user/hand/left/input/thumbrest/touch"),
XrBinding::new("thumb_touch", "/user/hand/right/input/thumbrest/touch"),
XrBinding::new("index_touch", "/user/hand/left/input/trigger/touch"),
XrBinding::new("index_value", "/user/hand/left/input/trigger/value"),
XrBinding::new("index_touch", "/user/hand/right/input/trigger/touch"),
XrBinding::new("index_value", "/user/hand/right/input/trigger/value"),
XrBinding::new("middle_value", "/user/hand/left/input/squeeze/value"),
XrBinding::new("middle_value", "/user/hand/right/input/squeeze/value"),
XrBinding::new("ring_value", "/user/hand/left/input/squeeze/value"),
XrBinding::new("ring_value", "/user/hand/right/input/squeeze/value"),
XrBinding::new("little_value", "/user/hand/left/input/squeeze/value"),
XrBinding::new("little_value", "/user/hand/right/input/squeeze/value"),
],
);
}
pub(crate) fn update_hand_skeleton_from_emulated(
session: Res<XrSession>,
instance: Res<XrInstance>,
action_sets: Res<XrActionSets>,
left_controller_transform: Query<&Transform, With<OpenXRLeftController>>,
right_controller_transform: Query<&Transform, With<OpenXRRightController>>,
tracking_root_transform: Query<&Transform, With<OpenXRTrackingRoot>>,
mut bones: Query<
(
&mut Transform,
&HandBone,
&Hand,
&BoneTrackingStatus,
&mut HandBoneRadius,
),
(
Without<OpenXRLeftController>,
Without<OpenXRRightController>,
Without<OpenXRTrackingRoot>,
),
>,
) {
//get the transforms outside the loop
let left = left_controller_transform.get_single();
let right = right_controller_transform.get_single();
let mut data: [[Transform; 26]; 2] = [[Transform::default(); 26]; 2];
for (subaction_path, hand) in [
(
instance.string_to_path("/user/hand/left").unwrap(),
Hand::Left,
),
(
instance.string_to_path("/user/hand/right").unwrap(),
Hand::Right,
),
] {
let thumb_curl = match action_sets
.get_action_bool(HAND_ACTION_SET, "thumb_touch")
.unwrap()
.state(&session, subaction_path)
.unwrap()
.current_state
{
true => 1.0,
false => 0.0,
};
let index_curl = action_sets
.get_action_f32(HAND_ACTION_SET, "index_value")
.unwrap()
.state(&session, subaction_path)
.unwrap()
.current_state;
let middle_curl = action_sets
.get_action_f32(HAND_ACTION_SET, "middle_value")
.unwrap()
.state(&session, subaction_path)
.unwrap()
.current_state;
let ring_curl = action_sets
.get_action_f32(HAND_ACTION_SET, "ring_value")
.unwrap()
.state(&session, subaction_path)
.unwrap()
.current_state;
let little_curl = action_sets
.get_action_f32(HAND_ACTION_SET, "little_value")
.unwrap()
.state(&session, subaction_path)
.unwrap()
.current_state;
match hand {
Hand::Left => match left {
Ok(hand_transform) => {
data[0] = update_hand_bones_emulated(
hand_transform,
hand,
thumb_curl,
index_curl,
middle_curl,
ring_curl,
little_curl,
);
}
Err(_) => debug!("no left controller transform for hand bone emulation"),
},
Hand::Right => match right {
Ok(hand_transform) => {
data[1] = update_hand_bones_emulated(
hand_transform,
hand,
thumb_curl,
index_curl,
middle_curl,
ring_curl,
little_curl,
);
}
Err(_) => debug!("no right controller transform for hand bone emulation"),
},
}
}
let trt = tracking_root_transform.single();
for (mut t, bone, hand, status, mut radius) in bones.iter_mut() {
match status {
BoneTrackingStatus::Emulated => {}
BoneTrackingStatus::Tracked => continue,
}
radius.0 = get_bone_gizmo_style(bone).0;
*t = data[match hand {
Hand::Left => 0,
Hand::Right => 1,
}][bone.get_index_from_bone()];
*t = t.with_scale(trt.scale);
*t = t.with_rotation(trt.rotation * t.rotation);
*t = t.with_translation(trt.transform_point(t.translation));
}
}
pub fn update_hand_bones_emulated(
controller_transform: &Transform,
hand: Hand,
thumb_curl: f32,
index_curl: f32,
middle_curl: f32,
ring_curl: f32,
little_curl: f32,
) -> [Transform; 26] {
let left_hand_rot = Quat::from_rotation_y(PI);
let hand_translation: Vec3 = controller_transform.translation;
let controller_quat: Quat = match hand {
Hand::Left => controller_transform.rotation.mul_quat(left_hand_rot),
Hand::Right => controller_transform.rotation,
};
let splay_direction = match hand {
Hand::Left => -1.0,
Hand::Right => 1.0,
};
//lets make a structure to hold our calculated transforms for now
let mut calc_transforms = [Transform::default(); 26];
//get palm quat
let y = Quat::from_rotation_y(-90.0 * PI / 180.0);
let x = Quat::from_rotation_x(-90.0 * PI / 180.0);
let palm_quat = controller_quat.mul_quat(y).mul_quat(x);
//get simulated bones
let hand_transform_array: [Transform; 26] = get_simulated_open_hand_transforms(hand);
//palm
let palm = hand_transform_array[HandJoint::PALM];
calc_transforms[HandJoint::PALM] = Transform {
translation: hand_translation + palm.translation,
..default()
};
//wrist
let wrist = hand_transform_array[HandJoint::WRIST];
calc_transforms[HandJoint::WRIST] = Transform {
translation: hand_translation + palm.translation + palm_quat.mul_vec3(wrist.translation),
..default()
};
//thumb
let thumb_joints = [
HandJoint::THUMB_METACARPAL,
HandJoint::THUMB_PROXIMAL,
HandJoint::THUMB_DISTAL,
HandJoint::THUMB_TIP,
];
let mut prior_start: Option<Vec3> = None;
let mut prior_quat: Option<Quat> = None;
let mut prior_vector: Option<Vec3> = None;
let splay = Quat::from_rotation_y(splay_direction * 30.0 * PI / 180.0);
let huh = Quat::from_rotation_x(-35.0 * PI / 180.0);
let splay_quat = palm_quat.mul_quat(huh).mul_quat(splay);
for bone in thumb_joints.iter() {
match prior_start {
Some(start) => {
let curl_angle: f32 = get_bone_curl_angle(*bone, thumb_curl);
let tp_lrot = Quat::from_rotation_y(splay_direction * curl_angle * PI / 180.0);
let tp_quat = prior_quat.unwrap().mul_quat(tp_lrot);
let thumb_prox = hand_transform_array[*bone];
let tp_start = start + prior_vector.unwrap();
let tp_vector = tp_quat.mul_vec3(thumb_prox.translation);
prior_start = Some(tp_start);
prior_quat = Some(tp_quat);
prior_vector = Some(tp_vector);
//store it
calc_transforms[*bone] = Transform {
translation: tp_start + tp_vector,
..default()
};
}
None => {
let thumb_meta = hand_transform_array[*bone];
let tm_start = hand_translation
+ palm_quat.mul_vec3(palm.translation)
+ palm_quat.mul_vec3(wrist.translation);
let tm_vector = palm_quat.mul_vec3(thumb_meta.translation);
prior_start = Some(tm_start);
prior_quat = Some(splay_quat);
prior_vector = Some(tm_vector);
//store it
calc_transforms[*bone] = Transform {
translation: tm_start + tm_vector,
..default()
};
}
}
}
//index
let thumb_joints = [
HandJoint::INDEX_METACARPAL,
HandJoint::INDEX_PROXIMAL,
HandJoint::INDEX_INTERMEDIATE,
HandJoint::INDEX_DISTAL,
HandJoint::INDEX_TIP,
];
let mut prior_start: Option<Vec3> = None;
let mut prior_quat: Option<Quat> = None;
let mut prior_vector: Option<Vec3> = None;
let splay = Quat::from_rotation_y(splay_direction * 10.0 * PI / 180.0);
let splay_quat = palm_quat.mul_quat(splay);
for bone in thumb_joints.iter() {
match prior_start {
Some(start) => {
let curl_angle: f32 = get_bone_curl_angle(*bone, index_curl);
let tp_lrot = Quat::from_rotation_x(curl_angle * PI / 180.0);
let tp_quat = prior_quat.unwrap().mul_quat(tp_lrot);
let thumb_prox = hand_transform_array[*bone];
let tp_start = start + prior_vector.unwrap();
let tp_vector = tp_quat.mul_vec3(thumb_prox.translation);
prior_start = Some(tp_start);
prior_quat = Some(tp_quat);
prior_vector = Some(tp_vector);
//store it
calc_transforms[*bone] = Transform {
translation: tp_start + tp_vector,
..default()
};
}
None => {
let thumb_meta = hand_transform_array[*bone];
let tm_start = hand_translation
+ palm_quat.mul_vec3(palm.translation)
+ palm_quat.mul_vec3(wrist.translation);
let tm_vector = palm_quat.mul_vec3(thumb_meta.translation);
prior_start = Some(tm_start);
prior_quat = Some(splay_quat);
prior_vector = Some(tm_vector);
//store it
calc_transforms[*bone] = Transform {
translation: tm_start + tm_vector,
..default()
};
}
}
}
//middle
let thumb_joints = [
HandJoint::MIDDLE_METACARPAL,
HandJoint::MIDDLE_PROXIMAL,
HandJoint::MIDDLE_INTERMEDIATE,
HandJoint::MIDDLE_DISTAL,
HandJoint::MIDDLE_TIP,
];
let mut prior_start: Option<Vec3> = None;
let mut prior_quat: Option<Quat> = None;
let mut prior_vector: Option<Vec3> = None;
let splay = Quat::from_rotation_y(splay_direction * 0.0 * PI / 180.0);
let splay_quat = palm_quat.mul_quat(splay);
for bone in thumb_joints.iter() {
match prior_start {
Some(start) => {
let curl_angle: f32 = get_bone_curl_angle(*bone, middle_curl);
let tp_lrot = Quat::from_rotation_x(curl_angle * PI / 180.0);
let tp_quat = prior_quat.unwrap().mul_quat(tp_lrot);
let thumb_prox = hand_transform_array[*bone];
let tp_start = start + prior_vector.unwrap();
let tp_vector = tp_quat.mul_vec3(thumb_prox.translation);
prior_start = Some(tp_start);
prior_quat = Some(tp_quat);
prior_vector = Some(tp_vector);
//store it
calc_transforms[*bone] = Transform {
translation: tp_start + tp_vector,
..default()
};
}
None => {
let thumb_meta = hand_transform_array[*bone];
let tm_start = hand_translation
+ palm_quat.mul_vec3(palm.translation)
+ palm_quat.mul_vec3(wrist.translation);
let tm_vector = palm_quat.mul_vec3(thumb_meta.translation);
prior_start = Some(tm_start);
prior_quat = Some(splay_quat);
prior_vector = Some(tm_vector);
//store it
calc_transforms[*bone] = Transform {
translation: tm_start + tm_vector,
..default()
};
}
}
}
//ring
let thumb_joints = [
HandJoint::RING_METACARPAL,
HandJoint::RING_PROXIMAL,
HandJoint::RING_INTERMEDIATE,
HandJoint::RING_DISTAL,
HandJoint::RING_TIP,
];
let mut prior_start: Option<Vec3> = None;
let mut prior_quat: Option<Quat> = None;
let mut prior_vector: Option<Vec3> = None;
let splay = Quat::from_rotation_y(splay_direction * -10.0 * PI / 180.0);
let splay_quat = palm_quat.mul_quat(splay);
for bone in thumb_joints.iter() {
match prior_start {
Some(start) => {
let curl_angle: f32 = get_bone_curl_angle(*bone, ring_curl);
let tp_lrot = Quat::from_rotation_x(curl_angle * PI / 180.0);
let tp_quat = prior_quat.unwrap().mul_quat(tp_lrot);
let thumb_prox = hand_transform_array[*bone];
let tp_start = start + prior_vector.unwrap();
let tp_vector = tp_quat.mul_vec3(thumb_prox.translation);
prior_start = Some(tp_start);
prior_quat = Some(tp_quat);
prior_vector = Some(tp_vector);
//store it
calc_transforms[*bone] = Transform {
translation: tp_start + tp_vector,
..default()
};
}
None => {
let thumb_meta = hand_transform_array[*bone];
let tm_start = hand_translation
+ palm_quat.mul_vec3(palm.translation)
+ palm_quat.mul_vec3(wrist.translation);
let tm_vector = palm_quat.mul_vec3(thumb_meta.translation);
prior_start = Some(tm_start);
prior_quat = Some(splay_quat);
prior_vector = Some(tm_vector);
//store it
calc_transforms[*bone] = Transform {
translation: tm_start + tm_vector,
..default()
};
}
}
}
//little
let thumb_joints = [
HandJoint::LITTLE_METACARPAL,
HandJoint::LITTLE_PROXIMAL,
HandJoint::LITTLE_INTERMEDIATE,
HandJoint::LITTLE_DISTAL,
HandJoint::LITTLE_TIP,
];
let mut prior_start: Option<Vec3> = None;
let mut prior_quat: Option<Quat> = None;
let mut prior_vector: Option<Vec3> = None;
let splay = Quat::from_rotation_y(splay_direction * -20.0 * PI / 180.0);
let splay_quat = palm_quat.mul_quat(splay);
for bone in thumb_joints.iter() {
match prior_start {
Some(start) => {
let curl_angle: f32 = get_bone_curl_angle(*bone, little_curl);
let tp_lrot = Quat::from_rotation_x(curl_angle * PI / 180.0);
let tp_quat = prior_quat.unwrap().mul_quat(tp_lrot);
let thumb_prox = hand_transform_array[*bone];
let tp_start = start + prior_vector.unwrap();
let tp_vector = tp_quat.mul_vec3(thumb_prox.translation);
prior_start = Some(tp_start);
prior_quat = Some(tp_quat);
prior_vector = Some(tp_vector);
//store it
calc_transforms[*bone] = Transform {
translation: tp_start + tp_vector,
..default()
};
}
None => {
let thumb_meta = hand_transform_array[*bone];
let tm_start = hand_translation
+ palm_quat.mul_vec3(palm.translation)
+ palm_quat.mul_vec3(wrist.translation);
let tm_vector = palm_quat.mul_vec3(thumb_meta.translation);
prior_start = Some(tm_start);
prior_quat = Some(splay_quat);
prior_vector = Some(tm_vector);
//store it
calc_transforms[*bone] = Transform {
translation: tm_start + tm_vector,
..default()
};
}
}
}
calc_transforms
}
fn get_bone_curl_angle(bone: HandJoint, curl: f32) -> f32 {
let mul: f32 = match bone {
HandJoint::INDEX_PROXIMAL => 0.0,
HandJoint::MIDDLE_PROXIMAL => 0.0,
HandJoint::RING_PROXIMAL => 0.0,
HandJoint::LITTLE_PROXIMAL => 0.0,
HandJoint::THUMB_PROXIMAL => 0.0,
HandJoint::THUMB_TIP => 0.1,
HandJoint::THUMB_DISTAL => 0.1,
HandJoint::THUMB_METACARPAL => 0.1,
_ => 1.0,
};
let curl_angle = -((mul * curl * 80.0) + 5.0);
return curl_angle;
}

View File

@@ -1,211 +0,0 @@
use bevy::prelude::*;
use openxr::{HandTracker, Result, SpaceLocationFlags};
use crate::{
input::XrInput,
resources::{XrFrameState, XrSession},
xr_input::{
hands::HandBone, trackers::OpenXRTrackingRoot, Hand, QuatConv,
Vec3Conv,
}, xr_init::xr_only,
};
use super::common::HandBoneRadius;
use super::BoneTrackingStatus;
#[derive(Resource, PartialEq)]
pub enum DisableHandTracking {
OnlyLeft,
OnlyRight,
Both,
}
pub struct HandTrackingPlugin;
#[derive(Resource)]
pub struct HandTrackingData {
left_hand: HandTracker,
right_hand: HandTracker,
}
impl HandTrackingData {
pub fn new(session: &XrSession) -> Result<HandTrackingData> {
let left = session.create_hand_tracker(openxr::HandEXT::LEFT)?;
let right = session.create_hand_tracker(openxr::HandEXT::RIGHT)?;
Ok(HandTrackingData {
left_hand: left,
right_hand: right,
})
}
pub fn get_ref<'a>(
&'a self,
input: &'a XrInput,
frame_state: &'a XrFrameState,
) -> HandTrackingRef<'a> {
HandTrackingRef {
tracking: self,
input,
frame_state,
}
}
}
pub struct HandTrackingRef<'a> {
tracking: &'a HandTrackingData,
input: &'a XrInput,
frame_state: &'a XrFrameState,
}
#[derive(Debug)]
pub struct HandJoint {
pub position: Vec3,
pub position_valid: bool,
pub position_tracked: bool,
pub orientation: Quat,
pub orientation_valid: bool,
pub orientation_tracked: bool,
pub radius: f32,
}
pub struct HandJoints {
inner: [HandJoint; 26],
}
impl HandJoints {
pub fn inner(&self) -> &[HandJoint; 26] {
&self.inner
}
}
impl HandJoints {
pub fn get_joint(&self, bone: HandBone) -> &HandJoint {
&self.inner[bone.get_index_from_bone()]
}
}
impl<'a> HandTrackingRef<'a> {
pub fn get_poses(&self, side: Hand) -> Option<HandJoints> {
self.input
.stage
.locate_hand_joints(
match side {
Hand::Left => &self.tracking.left_hand,
Hand::Right => &self.tracking.right_hand,
},
self.frame_state.lock().unwrap().predicted_display_time,
)
.unwrap()
.map(|joints| {
joints
.into_iter()
.map(|joint| HandJoint {
position: joint.pose.position.to_vec3(),
orientation: joint.pose.orientation.to_quat(),
position_valid: joint
.location_flags
.contains(SpaceLocationFlags::POSITION_VALID),
position_tracked: joint
.location_flags
.contains(SpaceLocationFlags::POSITION_TRACKED),
orientation_valid: joint
.location_flags
.contains(SpaceLocationFlags::ORIENTATION_VALID),
orientation_tracked: joint
.location_flags
.contains(SpaceLocationFlags::ORIENTATION_TRACKED),
radius: joint.radius,
})
.collect::<Vec<HandJoint>>()
.try_into()
.unwrap()
})
.map(|joints| HandJoints { inner: joints })
}
}
impl Plugin for HandTrackingPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
PreUpdate,
(
update_hand_bones.run_if(|dh: Option<Res<DisableHandTracking>>| {
!dh.is_some_and(|v| *v == DisableHandTracking::Both)
}).run_if(xr_only()),
update_tracking_state_on_disable,
),
);
}
}
fn update_tracking_state_on_disable(
mut is_off: Local<bool>,
disabled_tracking: Option<Res<DisableHandTracking>>,
mut tracking_states: Query<&mut BoneTrackingStatus>,
) {
if !*is_off
&& disabled_tracking
.as_ref()
.is_some_and(|t| **t == DisableHandTracking::Both)
{
tracking_states
.par_iter_mut()
.for_each(|mut state| *state = BoneTrackingStatus::Emulated);
}
*is_off = disabled_tracking
.as_ref()
.is_some_and(|t| **t == DisableHandTracking::Both);
}
pub fn update_hand_bones(
disabled_tracking: Option<Res<DisableHandTracking>>,
hand_tracking: Option<Res<HandTrackingData>>,
xr_input: Res<XrInput>,
xr_frame_state: Res<XrFrameState>,
root_query: Query<(&Transform, With<OpenXRTrackingRoot>, Without<HandBone>)>,
mut bones: Query<(
&mut Transform,
&Hand,
&HandBone,
&mut HandBoneRadius,
&mut BoneTrackingStatus,
)>,
) {
let hand_ref = match hand_tracking.as_ref() {
Some(h) => h.get_ref(&xr_input, &xr_frame_state),
None => {
warn!("No Handtracking data!");
return;
}
};
let (root_transform, _, _) = root_query.get_single().unwrap();
let left_hand_data = hand_ref.get_poses(Hand::Left);
let right_hand_data = hand_ref.get_poses(Hand::Right);
bones
.par_iter_mut()
.for_each(|(mut transform, hand, bone, mut radius, mut status)| {
match (&hand, disabled_tracking.as_ref().map(|d| d.as_ref())) {
(Hand::Left, Some(DisableHandTracking::OnlyLeft)) => {
*status = BoneTrackingStatus::Emulated;
return;
}
(Hand::Right, Some(DisableHandTracking::OnlyRight)) => {
*status = BoneTrackingStatus::Emulated;
return;
}
_ => {}
}
let bone_data = match (hand, &left_hand_data, &right_hand_data) {
(Hand::Left, Some(data), _) => data.get_joint(*bone),
(Hand::Right, _, Some(data)) => data.get_joint(*bone),
_ => {
*status = BoneTrackingStatus::Emulated;
return;
}
};
if *status == BoneTrackingStatus::Emulated {
*status = BoneTrackingStatus::Tracked;
}
radius.0 = bone_data.radius;
*transform = transform
.with_translation(root_transform.transform_point(bone_data.position))
.with_rotation(root_transform.rotation * bone_data.orientation)
});
}

View File

@@ -1,133 +0,0 @@
use bevy::{app::PluginGroupBuilder, prelude::*};
use self::{emulated::HandEmulationPlugin, hand_tracking::HandTrackingPlugin};
pub mod emulated;
pub mod hand_tracking;
pub mod common;
pub struct XrHandPlugins;
impl PluginGroup for XrHandPlugins {
fn build(self) -> PluginGroupBuilder {
PluginGroupBuilder::start::<Self>()
.add(HandTrackingPlugin)
.add(HandEmulationPlugin)
.build()
}
}
#[derive(Component, Debug, Clone, Copy, PartialEq)]
pub enum BoneTrackingStatus {
Emulated,
Tracked,
}
#[derive(Component, Debug, Clone, Copy)]
pub enum HandBone {
Palm,
Wrist,
ThumbMetacarpal,
ThumbProximal,
ThumbDistal,
ThumbTip,
IndexMetacarpal,
IndexProximal,
IndexIntermediate,
IndexDistal,
IndexTip,
MiddleMetacarpal,
MiddleProximal,
MiddleIntermediate,
MiddleDistal,
MiddleTip,
RingMetacarpal,
RingProximal,
RingIntermediate,
RingDistal,
RingTip,
LittleMetacarpal,
LittleProximal,
LittleIntermediate,
LittleDistal,
LittleTip,
}
impl HandBone {
pub fn is_finger(&self) -> bool {
match &self {
HandBone::Wrist => false,
HandBone::Palm => false,
_ => true,
}
}
pub fn is_metacarpal(&self) -> bool {
match &self {
HandBone::ThumbMetacarpal => true,
HandBone::IndexMetacarpal => true,
HandBone::MiddleMetacarpal => true,
HandBone::RingMetacarpal => true,
HandBone::LittleTip => true,
_ => false,
}
}
pub const fn get_all_bones() -> [HandBone; 26] {
[
HandBone::Palm,
HandBone::Wrist,
HandBone::ThumbMetacarpal,
HandBone::ThumbProximal,
HandBone::ThumbDistal,
HandBone::ThumbTip,
HandBone::IndexMetacarpal,
HandBone::IndexProximal,
HandBone::IndexIntermediate,
HandBone::IndexDistal,
HandBone::IndexTip,
HandBone::MiddleMetacarpal,
HandBone::MiddleProximal,
HandBone::MiddleIntermediate,
HandBone::MiddleDistal,
HandBone::MiddleTip,
HandBone::RingMetacarpal,
HandBone::RingProximal,
HandBone::RingIntermediate,
HandBone::RingDistal,
HandBone::RingTip,
HandBone::LittleMetacarpal,
HandBone::LittleProximal,
HandBone::LittleIntermediate,
HandBone::LittleDistal,
HandBone::LittleTip,
]
}
pub fn get_index_from_bone(&self) -> usize {
match &self {
HandBone::Palm => 0,
HandBone::Wrist => 1,
HandBone::ThumbMetacarpal => 2,
HandBone::ThumbProximal => 3,
HandBone::ThumbDistal => 4,
HandBone::ThumbTip => 5,
HandBone::IndexMetacarpal => 6,
HandBone::IndexProximal => 7,
HandBone::IndexIntermediate => 8,
HandBone::IndexDistal => 9,
HandBone::IndexTip => 10,
HandBone::MiddleMetacarpal => 11,
HandBone::MiddleProximal => 12,
HandBone::MiddleIntermediate => 13,
HandBone::MiddleDistal => 14,
HandBone::MiddleTip => 15,
HandBone::RingMetacarpal => 16,
HandBone::RingProximal => 17,
HandBone::RingIntermediate => 18,
HandBone::RingDistal => 19,
HandBone::RingTip => 20,
HandBone::LittleMetacarpal => 21,
HandBone::LittleProximal => 22,
HandBone::LittleIntermediate => 23,
HandBone::LittleDistal => 24,
HandBone::LittleTip => 25,
}
}
}

View File

@@ -1,381 +0,0 @@
use std::f32::consts::PI;
use bevy::log::info;
use bevy::prelude::{
Color, Component, Entity, Event, EventReader, EventWriter, Gizmos, GlobalTransform, Quat,
Query, Transform, Vec3, With, Without,
};
use super::trackers::{AimPose, OpenXRTrackingRoot};
#[derive(Component)]
pub struct XRDirectInteractor;
#[derive(Component)]
pub struct XRRayInteractor;
#[derive(Component)]
pub struct XRSocketInteractor;
#[derive(Component)]
pub struct Touched(pub bool);
#[derive(Component, Clone, Copy, PartialEq, PartialOrd, Debug)]
pub enum XRInteractableState {
Idle,
Hover,
Select,
}
impl Default for XRInteractableState {
fn default() -> Self {
XRInteractableState::Idle
}
}
#[derive(Component)]
pub enum XRInteractorState {
Idle,
Selecting,
}
impl Default for XRInteractorState {
fn default() -> Self {
XRInteractorState::Idle
}
}
#[derive(Component)]
pub enum XRSelection {
Empty,
Full(Entity),
}
impl Default for XRSelection {
fn default() -> Self {
XRSelection::Empty
}
}
#[derive(Component)]
pub struct XRInteractable;
pub fn draw_socket_gizmos(
mut gizmos: Gizmos,
interactor_query: Query<(
&GlobalTransform,
&XRInteractorState,
Entity,
&XRSocketInteractor,
)>,
) {
for (global, state, _entity, _socket) in interactor_query.iter() {
let mut transform = global.compute_transform().clone();
transform.scale = Vec3::splat(0.1);
let color = match state {
XRInteractorState::Idle => Color::BLUE,
XRInteractorState::Selecting => Color::PURPLE,
};
gizmos.cuboid(transform, color)
}
}
pub fn draw_interaction_gizmos(
mut gizmos: Gizmos,
interactable_query: Query<
(&GlobalTransform, &XRInteractableState),
(With<XRInteractable>, Without<XRDirectInteractor>),
>,
interactor_query: Query<
(
&GlobalTransform,
&XRInteractorState,
Option<&XRDirectInteractor>,
Option<&XRRayInteractor>,
Option<&AimPose>,
),
Without<XRInteractable>,
>,
tracking_root_query: Query<(&mut Transform, With<OpenXRTrackingRoot>)>,
) {
let root = tracking_root_query.get_single().unwrap().0;
for (global_transform, interactable_state) in interactable_query.iter() {
let transform = global_transform.compute_transform();
let color = match interactable_state {
XRInteractableState::Idle => Color::RED,
XRInteractableState::Hover => Color::YELLOW,
XRInteractableState::Select => Color::GREEN,
};
gizmos.sphere(transform.translation, transform.rotation, 0.1, color);
}
for (interactor_global_transform, interactor_state, direct, ray, aim) in interactor_query.iter()
{
let transform = interactor_global_transform.compute_transform();
match direct {
Some(_) => {
let mut local = transform.clone();
local.scale = Vec3::splat(0.1);
let quat = Quat::from_euler(
bevy::prelude::EulerRot::XYZ,
45.0 * (PI / 180.0),
0.0,
45.0 * (PI / 180.0),
);
local.rotation = quat;
let color = match interactor_state {
XRInteractorState::Idle => Color::BLUE,
XRInteractorState::Selecting => Color::PURPLE,
};
gizmos.cuboid(local, color);
}
None => (),
}
match ray {
Some(_) => match aim {
Some(aim) => {
let color = match interactor_state {
XRInteractorState::Idle => Color::BLUE,
XRInteractorState::Selecting => Color::PURPLE,
};
gizmos.ray(
root.translation + root.rotation.mul_vec3(aim.0.translation),
root.rotation.mul_vec3(aim.0.forward()),
color,
);
}
None => todo!(),
},
None => (),
}
}
}
#[derive(Event)]
pub struct InteractionEvent {
pub interactor: Entity,
pub interactable: Entity,
pub interactable_state: XRInteractableState,
}
pub fn socket_interactions(
interactable_query: Query<
(&GlobalTransform, &mut XRInteractableState, Entity),
(With<XRInteractable>, Without<XRSocketInteractor>),
>,
interactor_query: Query<
(
&GlobalTransform,
&XRInteractorState,
Entity,
&XRSocketInteractor,
),
Without<XRInteractable>,
>,
mut writer: EventWriter<InteractionEvent>,
) {
for interactable in interactable_query.iter() {
//for the interactables
for socket in interactor_query.iter() {
let interactor_global_transform = socket.0;
let xr_interactable_global_transform = interactable.0;
let interactor_state = socket.1;
//check for sphere overlaps
let size = 0.1;
if interactor_global_transform
.compute_transform()
.translation
.distance_squared(
xr_interactable_global_transform
.compute_transform()
.translation,
)
< (size * size) * 2.0
{
//check for selections first
match interactor_state {
XRInteractorState::Idle => {
let event = InteractionEvent {
interactor: socket.2,
interactable: interactable.2,
interactable_state: XRInteractableState::Hover,
};
writer.send(event);
}
XRInteractorState::Selecting => {
let event = InteractionEvent {
interactor: socket.2,
interactable: interactable.2,
interactable_state: XRInteractableState::Select,
};
writer.send(event);
}
}
}
}
}
}
pub fn interactions(
interactable_query: Query<
(&GlobalTransform, Entity),
(With<XRInteractable>, Without<XRDirectInteractor>),
>,
interactor_query: Query<
(
&GlobalTransform,
&XRInteractorState,
Entity,
Option<&XRDirectInteractor>,
Option<&XRRayInteractor>,
Option<&AimPose>,
),
Without<XRInteractable>,
>,
tracking_root_query: Query<(&mut Transform, With<OpenXRTrackingRoot>)>,
mut writer: EventWriter<InteractionEvent>,
) {
for (xr_interactable_global_transform, interactable_entity) in interactable_query.iter() {
for (interactor_global_transform, interactor_state, interactor_entity, direct, ray, aim) in
interactor_query.iter()
{
match direct {
Some(_) => {
//check for sphere overlaps
let size = 0.1;
if interactor_global_transform
.compute_transform()
.translation
.distance_squared(
xr_interactable_global_transform
.compute_transform()
.translation,
)
< (size * size) * 2.0
{
//check for selections first
match interactor_state {
XRInteractorState::Idle => {
let event = InteractionEvent {
interactor: interactor_entity,
interactable: interactable_entity,
interactable_state: XRInteractableState::Hover,
};
writer.send(event);
}
XRInteractorState::Selecting => {
let event = InteractionEvent {
interactor: interactor_entity,
interactable: interactable_entity,
interactable_state: XRInteractableState::Select,
};
writer.send(event);
}
}
}
}
None => (),
}
match ray {
Some(_) => {
//check for ray-sphere intersection
let sphere_transform = xr_interactable_global_transform.compute_transform();
let center = sphere_transform.translation;
let radius: f32 = 0.1;
//I hate this but the aim pose needs the root for now
let root = tracking_root_query.get_single().unwrap().0;
match aim {
Some(aim) => {
let ray_origin =
root.translation + root.rotation.mul_vec3(aim.0.translation);
let ray_dir = root.rotation.mul_vec3(aim.0.forward());
if ray_sphere_intersection(
center,
radius,
ray_origin,
ray_dir.normalize_or_zero(),
) {
//check for selections first
match interactor_state {
XRInteractorState::Idle => {
let event = InteractionEvent {
interactor: interactor_entity,
interactable: interactable_entity,
interactable_state: XRInteractableState::Hover,
};
writer.send(event);
}
XRInteractorState::Selecting => {
let event = InteractionEvent {
interactor: interactor_entity,
interactable: interactable_entity,
interactable_state: XRInteractableState::Select,
};
writer.send(event);
}
}
}
}
None => info!("no aim pose"),
}
}
None => (),
}
}
}
}
pub fn update_interactable_states(
mut events: EventReader<InteractionEvent>,
mut interactable_query: Query<
(Entity, &mut XRInteractableState, &mut Touched),
With<XRInteractable>,
>,
) {
//i very much dislike this
for (_entity, _state, mut touched) in interactable_query.iter_mut() {
*touched = Touched(false);
}
for event in events.read() {
//lets change the state
match interactable_query.get_mut(event.interactable) {
Ok((_entity, mut entity_state, mut touched)) => {
//since we have an event we were touched this frame, i hate this name
*touched = Touched(true);
if event.interactable_state > *entity_state {
// info!(
// "event.state: {:?}, interactable.state: {:?}",
// event.interactable_state, entity_state
// );
// info!("event has a higher state");
}
*entity_state = event.interactable_state;
}
Err(_) => {}
}
}
//lets go through all the untouched interactables and set them to idle
for (_entity, mut state, touched) in interactable_query.iter_mut() {
if !touched.0 {
*state = XRInteractableState::Idle;
}
}
}
fn ray_sphere_intersection(center: Vec3, radius: f32, ray_origin: Vec3, ray_dir: Vec3) -> bool {
let l = center - ray_origin;
let adj = l.dot(ray_dir);
let d2 = l.dot(l) - (adj * adj);
let radius2 = radius * radius;
if d2 > radius2 {
return false;
}
let thc = (radius2 - d2).sqrt();
let t0 = adj - thc;
let t1 = adj + thc;
if t0 < 0.0 && t1 < 0.0 {
return false;
}
// let distance = if t0 < t1 { t0 } else { t1 };
return true;
}

View File

@@ -1,137 +0,0 @@
pub mod actions;
pub mod controllers;
pub mod debug_gizmos;
pub mod hand_poses;
pub mod hands;
pub mod interactions;
pub mod oculus_touch;
pub mod prototype_locomotion;
pub mod trackers;
pub mod xr_camera;
use crate::xr_init::{XrPostSetup, XrSetup, xr_only};
use crate::resources::{XrInstance, XrSession};
use crate::xr_begin_frame;
use crate::xr_input::controllers::XrControllerType;
use crate::xr_input::oculus_touch::setup_oculus_controller;
use crate::xr_input::xr_camera::{xr_camera_head_sync, Eye, XRProjection, XrCameraBundle};
use bevy::app::{App, PostUpdate, Startup};
use bevy::log::{warn, info};
use bevy::prelude::{BuildChildren, Component, Deref, DerefMut, IntoSystemConfigs, Resource};
use bevy::prelude::{Commands, Plugin, PreUpdate, Quat, Res, SpatialBundle, Update, Vec3};
use bevy::render::camera::CameraProjectionPlugin;
use bevy::render::view::{update_frusta, VisibilitySystems};
use bevy::transform::TransformSystem;
use bevy::utils::HashMap;
use openxr::Binding;
use self::actions::{setup_oxr_actions, OpenXrActionsPlugin};
use self::oculus_touch::{post_action_setup_oculus_controller, ActionSets};
use self::trackers::{
adopt_open_xr_trackers, update_open_xr_controllers, OpenXRLeftEye, OpenXRRightEye,
OpenXRTrackingRoot,
};
#[derive(Copy, Clone)]
pub struct OpenXrInput {
pub controller_type: XrControllerType,
}
#[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq, Component)]
pub enum Hand {
Left,
Right,
}
impl OpenXrInput {
pub fn new(controller_type: XrControllerType) -> Self {
Self { controller_type }
}
}
impl Plugin for OpenXrInput {
fn build(&self, app: &mut App) {
app.add_plugins(CameraProjectionPlugin::<XRProjection>::default());
app.add_plugins(OpenXrActionsPlugin);
app.add_systems(
XrPostSetup,
post_action_setup_oculus_controller,
);
match self.controller_type {
XrControllerType::OculusTouch => {
app.add_systems(XrSetup, setup_oculus_controller);
}
}
//adopt any new trackers
app.add_systems(PreUpdate, adopt_open_xr_trackers.run_if(xr_only()));
app.add_systems(PreUpdate, action_set_system);
app.add_systems(PreUpdate, xr_camera_head_sync.run_if(xr_only()).after(xr_begin_frame));
//update controller trackers
app.add_systems(Update, update_open_xr_controllers.run_if(xr_only()));
app.add_systems(
PostUpdate,
update_frusta::<XRProjection>
.after(TransformSystem::TransformPropagate)
.before(VisibilitySystems::UpdatePerspectiveFrusta),
);
app.add_systems(XrSetup, setup_xr_cameras);
}
}
#[derive(Deref, DerefMut, Resource)]
pub struct InteractionProfileBindings(pub HashMap<&'static str, Vec<Binding<'static>>>);
fn setup_binding_recommendations(
mut commands: Commands,
instance: Res<XrInstance>,
bindings: Res<InteractionProfileBindings>,
) {
commands.remove_resource::<InteractionProfileBindings>();
}
fn setup_xr_cameras(mut commands: Commands) {
//this needs to do the whole xr tracking volume not just cameras
//get the root?
let tracking_root = commands
.spawn((SpatialBundle::default(), OpenXRTrackingRoot))
.id();
let right = commands
.spawn((XrCameraBundle::new(Eye::Right), OpenXRRightEye))
.id();
let left = commands
.spawn((XrCameraBundle::new(Eye::Left), OpenXRLeftEye))
.id();
commands.entity(tracking_root).push_children(&[right, left]);
}
fn action_set_system(action_sets: Res<ActionSets>, session: Res<XrSession>) {
let mut active_action_sets = vec![];
for i in &action_sets.0 {
active_action_sets.push(openxr::ActiveActionSet::new(i));
}
//info!("action sets: {:#?}", action_sets.0.len());
match session.sync_actions(&active_action_sets) {
Err(err) => {
warn!("{}", err);
}
_ => {}
}
}
pub trait Vec3Conv {
fn to_vec3(&self) -> Vec3;
}
impl Vec3Conv for openxr::Vector3f {
fn to_vec3(&self) -> Vec3 {
Vec3::new(self.x, self.y, self.z)
}
}
pub trait QuatConv {
fn to_quat(&self) -> Quat;
}
impl QuatConv for openxr::Quaternionf {
fn to_quat(&self) -> Quat {
Quat::from_xyzw(self.x, self.y, self.z, self.w)
}
}

View File

@@ -1,478 +0,0 @@
use crate::input::XrInput;
use crate::resources::{XrInstance, XrSession};
use crate::xr_input::controllers::Handed;
use crate::xr_input::Hand;
use bevy::prelude::{Commands, Res, ResMut, Resource};
use openxr::{
ActionSet, AnyGraphics, FrameState, Instance, Path, Posef, Session, Space, SpaceLocation,
SpaceVelocity,
};
use std::sync::OnceLock;
use super::actions::{ActionHandednes, ActionType, SetupActionSets, XrActionSets, XrBinding};
pub fn post_action_setup_oculus_controller(
action_sets: Res<XrActionSets>,
mut controller: ResMut<OculusController>,
instance: Res<XrInstance>,
session: Res<XrSession>,
) {
let s = Session::<AnyGraphics>::clone(&session);
let left_path = instance.string_to_path("/user/hand/left").unwrap();
let right_path = instance.string_to_path("/user/hand/right").unwrap();
let grip_action = action_sets
.get_action_posef("oculus_input", "hand_pose")
.unwrap();
let aim_action = action_sets
.get_action_posef("oculus_input", "pointer_pose")
.unwrap();
controller.grip_space = Some(Handed {
left: grip_action
.create_space(s.clone(), left_path, Posef::IDENTITY)
.unwrap(),
right: grip_action
.create_space(s.clone(), right_path, Posef::IDENTITY)
.unwrap(),
});
controller.aim_space = Some(Handed {
left: aim_action
.create_space(s.clone(), left_path, Posef::IDENTITY)
.unwrap(),
right: aim_action
.create_space(s.clone(), right_path, Posef::IDENTITY)
.unwrap(),
})
}
pub fn setup_oculus_controller(
mut commands: Commands,
instance: Res<XrInstance>,
action_sets: ResMut<SetupActionSets>,
) {
let oculus_controller = OculusController::new(action_sets).unwrap();
init_subaction_path(&instance);
commands.insert_resource(oculus_controller);
}
#[derive(Resource, Clone)]
pub struct ActionSets(pub Vec<ActionSet>);
pub struct OculusControllerRef<'a> {
oculus_controller: &'a OculusController,
action_sets: &'a XrActionSets,
session: &'a Session<AnyGraphics>,
frame_state: &'a FrameState,
xr_input: &'a XrInput,
}
static RIGHT_SUBACTION_PATH: OnceLock<Path> = OnceLock::new();
static LEFT_SUBACTION_PATH: OnceLock<Path> = OnceLock::new();
pub fn init_subaction_path(instance: &Instance) {
let _ = LEFT_SUBACTION_PATH.set(instance.string_to_path("/user/hand/left").unwrap());
let _ = RIGHT_SUBACTION_PATH.set(instance.string_to_path("/user/hand/right").unwrap());
}
pub fn subaction_path(hand: Hand) -> Path {
*match hand {
Hand::Left => LEFT_SUBACTION_PATH.get().unwrap(),
Hand::Right => RIGHT_SUBACTION_PATH.get().unwrap(),
}
}
impl OculusControllerRef<'_> {
pub fn grip_space(&self, hand: Hand) -> (SpaceLocation, SpaceVelocity) {
match hand {
Hand::Left => self
.oculus_controller
.grip_space
.as_ref()
.unwrap()
.left
.relate(
&self.xr_input.stage,
self.frame_state.predicted_display_time,
),
Hand::Right => self
.oculus_controller
.grip_space
.as_ref()
.unwrap()
.right
.relate(
&self.xr_input.stage,
self.frame_state.predicted_display_time,
),
}
.unwrap()
}
pub fn aim_space(&self, hand: Hand) -> (SpaceLocation, SpaceVelocity) {
match hand {
Hand::Left => self
.oculus_controller
.aim_space
.as_ref()
.unwrap()
.left
.relate(
&self.xr_input.stage,
self.frame_state.predicted_display_time,
),
Hand::Right => self
.oculus_controller
.aim_space
.as_ref()
.unwrap()
.right
.relate(
&self.xr_input.stage,
self.frame_state.predicted_display_time,
),
}
.unwrap()
}
pub fn squeeze(&self, hand: Hand) -> f32 {
let action = &self
.action_sets
.get_action_f32("oculus_input", "squeeze")
.unwrap();
action
.state(&self.session, subaction_path(hand))
.unwrap()
.current_state
}
pub fn trigger(&self, hand: Hand) -> f32 {
self.action_sets
.get_action_f32("oculus_input", "trigger")
.unwrap()
.state(&self.session, subaction_path(hand))
.unwrap()
.current_state
}
pub fn trigger_touched(&self, hand: Hand) -> bool {
self.action_sets
.get_action_bool("oculus_input", "trigger_touched")
.unwrap()
.state(&self.session, subaction_path(hand))
.unwrap()
.current_state
}
pub fn x_button(&self) -> bool {
self.action_sets
.get_action_bool("oculus_input", "x_button")
.unwrap()
.state(&self.session, Path::NULL)
.unwrap()
.current_state
}
pub fn x_button_touched(&self) -> bool {
self.action_sets
.get_action_bool("oculus_input", "x_button_touch")
.unwrap()
.state(&self.session, Path::NULL)
.unwrap()
.current_state
}
pub fn y_button(&self) -> bool {
self.action_sets
.get_action_bool("oculus_input", "y_button")
.unwrap()
.state(&self.session, Path::NULL)
.unwrap()
.current_state
}
pub fn y_button_touched(&self) -> bool {
self.action_sets
.get_action_bool("oculus_input", "y_button_touch")
.unwrap()
.state(&self.session, Path::NULL)
.unwrap()
.current_state
}
pub fn menu_button(&self) -> bool {
self.action_sets
.get_action_bool("oculus_input", "menu_button")
.unwrap()
.state(&self.session, Path::NULL)
.unwrap()
.current_state
}
pub fn a_button(&self) -> bool {
self.action_sets
.get_action_bool("oculus_input", "a_button")
.unwrap()
.state(&self.session, Path::NULL)
.unwrap()
.current_state
}
pub fn a_button_touched(&self) -> bool {
self.action_sets
.get_action_bool("oculus_input", "a_button_touch")
.unwrap()
.state(&self.session, Path::NULL)
.unwrap()
.current_state
}
pub fn b_button(&self) -> bool {
self.action_sets
.get_action_bool("oculus_input", "b_button")
.unwrap()
.state(&self.session, Path::NULL)
.unwrap()
.current_state
}
pub fn b_button_touched(&self) -> bool {
self.action_sets
.get_action_bool("oculus_input", "b_button_touch")
.unwrap()
.state(&self.session, Path::NULL)
.unwrap()
.current_state
}
pub fn thumbstick_touch(&self, hand: Hand) -> bool {
self.action_sets
.get_action_bool("oculus_input", "thumbstick_touch")
.unwrap()
.state(&self.session, subaction_path(hand))
.unwrap()
.current_state
}
pub fn thumbstick(&self, hand: Hand) -> Thumbstick {
Thumbstick {
x: self
.action_sets
.get_action_f32("oculus_input", "thumbstick_x")
.unwrap()
.state(&self.session, subaction_path(hand))
.unwrap()
.current_state,
y: self
.action_sets
.get_action_f32("oculus_input", "thumbstick_y")
.unwrap()
.state(&self.session, subaction_path(hand))
.unwrap()
.current_state,
click: self
.action_sets
.get_action_bool("oculus_input", "thumbstick_click")
.unwrap()
.state(&self.session, subaction_path(hand))
.unwrap()
.current_state,
}
}
pub fn thumbrest_touch(&self, hand: Hand) -> bool {
self.action_sets
.get_action_bool("oculus_input", "thumbrest_touch")
.unwrap()
.state(&self.session, subaction_path(hand))
.unwrap()
.current_state
}
}
#[derive(Copy, Clone, Debug)]
pub struct Thumbstick {
pub x: f32,
pub y: f32,
pub click: bool,
}
impl OculusController {
pub fn get_ref<'a>(
&'a self,
session: &'a Session<AnyGraphics>,
frame_state: &'a FrameState,
xr_input: &'a XrInput,
action_sets: &'a XrActionSets,
) -> OculusControllerRef {
OculusControllerRef {
oculus_controller: self,
session,
frame_state,
xr_input,
action_sets,
}
}
}
#[derive(Resource)]
pub struct OculusController {
pub grip_space: Option<Handed<Space>>,
pub aim_space: Option<Handed<Space>>,
}
impl OculusController {
pub fn new(mut action_sets: ResMut<SetupActionSets>) -> anyhow::Result<Self> {
let action_set =
action_sets.add_action_set("oculus_input", "Oculus Touch Controller Input", 0);
action_set.new_action(
"hand_pose",
"Hand Pose",
ActionType::PoseF,
ActionHandednes::Double,
);
action_set.new_action(
"pointer_pose",
"Pointer Pose",
ActionType::PoseF,
ActionHandednes::Double,
);
action_set.new_action(
"squeeze",
"Grip Pull",
ActionType::F32,
ActionHandednes::Double,
);
action_set.new_action(
"trigger",
"Trigger Pull",
ActionType::F32,
ActionHandednes::Double,
);
action_set.new_action(
"trigger_touched",
"Trigger Touch",
ActionType::Bool,
ActionHandednes::Double,
);
action_set.new_action(
"haptic_feedback",
"Haptic Feedback",
ActionType::Haptic,
ActionHandednes::Double,
);
action_set.new_action(
"x_button",
"X Button",
ActionType::Bool,
ActionHandednes::Single,
);
action_set.new_action(
"x_button_touch",
"X Button Touch",
ActionType::Bool,
ActionHandednes::Single,
);
action_set.new_action(
"y_button",
"Y Button",
ActionType::Bool,
ActionHandednes::Single,
);
action_set.new_action(
"y_button_touch",
"Y Button Touch",
ActionType::Bool,
ActionHandednes::Single,
);
action_set.new_action(
"a_button",
"A Button",
ActionType::Bool,
ActionHandednes::Single,
);
action_set.new_action(
"a_button_touch",
"A Button Touch",
ActionType::Bool,
ActionHandednes::Single,
);
action_set.new_action(
"b_button",
"B Button",
ActionType::Bool,
ActionHandednes::Single,
);
action_set.new_action(
"b_button_touch",
"B Button Touch",
ActionType::Bool,
ActionHandednes::Single,
);
action_set.new_action(
"menu_button",
"Menu Button",
ActionType::Bool,
ActionHandednes::Single,
);
action_set.new_action(
"thumbstick_x",
"Thumbstick X",
ActionType::F32,
ActionHandednes::Double,
);
action_set.new_action(
"thumbstick_y",
"Thumbstick y",
ActionType::F32,
ActionHandednes::Double,
);
action_set.new_action(
"thumbstick_touch",
"Thumbstick Touch",
ActionType::Bool,
ActionHandednes::Double,
);
action_set.new_action(
"thumbstick_click",
"Thumbstick Click",
ActionType::Bool,
ActionHandednes::Double,
);
action_set.new_action(
"thumbrest_touch",
"Thumbrest Touch",
ActionType::Bool,
ActionHandednes::Double,
);
let this = OculusController {
grip_space: None,
aim_space: None,
};
action_set.suggest_binding(
"/interaction_profiles/oculus/touch_controller",
&[
XrBinding::new("hand_pose", "/user/hand/left/input/grip/pose"),
XrBinding::new("hand_pose", "/user/hand/right/input/grip/pose"),
XrBinding::new("pointer_pose", "/user/hand/left/input/aim/pose"),
XrBinding::new("pointer_pose", "/user/hand/right/input/aim/pose"),
XrBinding::new("squeeze", "/user/hand/left/input/squeeze/value"),
XrBinding::new("squeeze", "/user/hand/right/input/squeeze/value"),
XrBinding::new("trigger", "/user/hand/left/input/trigger/value"),
XrBinding::new("trigger", "/user/hand/right/input/trigger/value"),
XrBinding::new("trigger_touched", "/user/hand/left/input/trigger/touch"),
XrBinding::new("trigger_touched", "/user/hand/right/input/trigger/touch"),
XrBinding::new("haptic_feedback", "/user/hand/left/output/haptic"),
XrBinding::new("haptic_feedback", "/user/hand/right/output/haptic"),
XrBinding::new("x_button", "/user/hand/left/input/x/click"),
XrBinding::new("x_button_touch", "/user/hand/left/input/x/touch"),
XrBinding::new("y_button", "/user/hand/left/input/y/click"),
XrBinding::new("y_button_touch", "/user/hand/left/input/y/touch"),
XrBinding::new("a_button", "/user/hand/right/input/a/click"),
XrBinding::new("a_button_touch", "/user/hand/right/input/a/touch"),
XrBinding::new("b_button", "/user/hand/right/input/b/click"),
XrBinding::new("b_button_touch", "/user/hand/right/input/b/touch"),
XrBinding::new("menu_button", "/user/hand/left/input/menu/click"),
XrBinding::new("thumbstick_x", "/user/hand/left/input/thumbstick/x"),
XrBinding::new("thumbstick_y", "/user/hand/left/input/thumbstick/y"),
XrBinding::new("thumbstick_x", "/user/hand/right/input/thumbstick/x"),
XrBinding::new("thumbstick_y", "/user/hand/right/input/thumbstick/y"),
XrBinding::new("thumbstick_click", "/user/hand/left/input/thumbstick/click"),
XrBinding::new(
"thumbstick_click",
"/user/hand/right/input/thumbstick/click",
),
XrBinding::new("thumbstick_touch", "/user/hand/left/input/thumbstick/touch"),
XrBinding::new(
"thumbstick_touch",
"/user/hand/right/input/thumbstick/touch",
),
XrBinding::new("thumbrest_touch", "/user/hand/left/input/thumbrest/touch"),
XrBinding::new("thumbrest_touch", "/user/hand/right/input/thumbrest/touch"),
],
);
Ok(this)
}
}

View File

@@ -1,182 +0,0 @@
use std::f32::consts::PI;
use bevy::{
prelude::*,
time::{Time, Timer, TimerMode},
};
use crate::{
input::XrInput,
resources::{XrFrameState, XrInstance, XrSession, XrViews},
};
use super::{
actions::XrActionSets, oculus_touch::OculusController, trackers::OpenXRTrackingRoot, Hand,
QuatConv, Vec3Conv,
};
pub enum LocomotionType {
Head,
Hand,
}
pub enum RotationType {
Smooth,
Snap,
}
#[derive(Resource)]
pub struct RotationTimer {
pub timer: Timer,
}
#[derive(Resource)]
pub struct PrototypeLocomotionConfig {
pub locomotion_type: LocomotionType,
pub locomotion_speed: f32,
pub rotation_type: RotationType,
pub snap_angle: f32,
pub smooth_rotation_speed: f32,
pub rotation_stick_deadzone: f32,
pub rotation_timer: RotationTimer,
}
impl Default for PrototypeLocomotionConfig {
fn default() -> Self {
Self {
locomotion_type: LocomotionType::Head,
locomotion_speed: 1.0,
rotation_type: RotationType::Smooth,
snap_angle: 45.0 * (PI / 180.0),
smooth_rotation_speed: 0.5 * PI,
rotation_stick_deadzone: 0.2,
rotation_timer: RotationTimer {
timer: Timer::from_seconds(1.0, TimerMode::Once),
},
}
}
}
pub fn proto_locomotion(
time: Res<Time>,
mut tracking_root_query: Query<(&mut Transform, With<OpenXRTrackingRoot>)>,
oculus_controller: Res<OculusController>,
frame_state: Res<XrFrameState>,
xr_input: Res<XrInput>,
instance: Res<XrInstance>,
session: Res<XrSession>,
views: ResMut<XrViews>,
mut gizmos: Gizmos,
config_option: Option<ResMut<PrototypeLocomotionConfig>>,
action_sets: Res<XrActionSets>,
) {
match config_option {
Some(_) => (),
None => {
info!("no locomotion config");
return;
}
}
//i hate this but im too tired to think
let mut config = config_option.unwrap();
//lock frame
let frame_state = *frame_state.lock().unwrap();
//get controller
let controller = oculus_controller.get_ref(&session, &frame_state, &xr_input, &action_sets);
let root = tracking_root_query.get_single_mut();
match root {
Ok(mut position) => {
//get the stick input and do some maths
let stick = controller.thumbstick(Hand::Left);
let input = stick.x * position.0.right() + stick.y * position.0.forward();
let reference_quat;
match config.locomotion_type {
LocomotionType::Head => {
let v = views.lock().unwrap();
let views = v.get(0);
match views {
Some(view) => {
reference_quat = view.pose.orientation.to_quat();
}
None => return,
}
}
LocomotionType::Hand => {
let grip = controller.grip_space(Hand::Left);
reference_quat = grip.0.pose.orientation.to_quat();
}
}
let (yaw, _pitch, _roll) = reference_quat.to_euler(EulerRot::YXZ);
let reference_quat = Quat::from_axis_angle(position.0.up(), yaw);
let locomotion_vec = reference_quat.mul_vec3(input);
position.0.translation +=
locomotion_vec * config.locomotion_speed * time.delta_seconds();
//now time for rotation
match config.rotation_type {
RotationType::Smooth => {
//once again with the math
let control_stick = controller.thumbstick(Hand::Right);
let rot_input = -control_stick.x; //why is this negative i dont know
if rot_input.abs() <= config.rotation_stick_deadzone {
return;
}
let smoth_rot = Quat::from_axis_angle(
position.0.up(),
rot_input * config.smooth_rotation_speed * time.delta_seconds(),
);
//apply rotation
let v = views.lock().unwrap();
let views = v.get(0);
match views {
Some(view) => {
let mut hmd_translation = view.pose.position.to_vec3();
hmd_translation.y = 0.0;
let local = position.0.translation;
let global = position.0.rotation.mul_vec3(hmd_translation) + local;
gizmos.circle(global, position.0.up(), 0.1, Color::GREEN);
position.0.rotate_around(global, smoth_rot);
}
None => return,
}
}
RotationType::Snap => {
//tick the timer
config.rotation_timer.timer.tick(time.delta());
if config.rotation_timer.timer.finished() {
//now we can snap turn?
//once again with the math
let control_stick = controller.thumbstick(Hand::Right);
let rot_input = -control_stick.x;
if rot_input.abs() <= config.rotation_stick_deadzone {
return;
}
let dir: f32 = match rot_input > 0.0 {
true => 1.0,
false => -1.0,
};
let smoth_rot =
Quat::from_axis_angle(position.0.up(), config.snap_angle * dir);
//apply rotation
let v = views.lock().unwrap();
let views = v.get(0);
match views {
Some(view) => {
let mut hmd_translation = view.pose.position.to_vec3();
hmd_translation.y = 0.0;
let local = position.0.translation;
let global = position.0.rotation.mul_vec3(hmd_translation) + local;
gizmos.circle(global, position.0.up(), 0.1, Color::GREEN);
position.0.rotate_around(global, smoth_rot);
}
None => return,
}
config.rotation_timer.timer.reset();
}
}
}
}
Err(_) => info!("too many tracking roots"),
}
}

View File

@@ -1,140 +0,0 @@
use bevy::log::{debug, info};
use bevy::prelude::{
Added, BuildChildren, Commands, Component, Entity, Query, Res, Transform, Vec3, With, Without,
};
use crate::{
input::XrInput,
resources::{XrFrameState, XrSession},
};
use super::{actions::XrActionSets, oculus_touch::OculusController, Hand, QuatConv, Vec3Conv};
#[derive(Component)]
pub struct OpenXRTrackingRoot;
#[derive(Component)]
pub struct OpenXRTracker;
#[derive(Component)]
pub struct OpenXRLeftEye;
#[derive(Component)]
pub struct OpenXRRightEye;
#[derive(Component)]
pub struct OpenXRHMD;
#[derive(Component)]
pub struct OpenXRLeftController;
#[derive(Component)]
pub struct OpenXRRightController;
#[derive(Component)]
pub struct OpenXRController;
#[derive(Component)]
pub struct AimPose(pub Transform);
pub fn adopt_open_xr_trackers(
query: Query<Entity, Added<OpenXRTracker>>,
mut commands: Commands,
tracking_root_query: Query<(Entity, With<OpenXRTrackingRoot>)>,
) {
let root = tracking_root_query.get_single();
match root {
Ok(thing) => {
// info!("root is");
for tracker in query.iter() {
info!("we got a new tracker");
commands.entity(thing.0).add_child(tracker);
}
}
Err(_) => info!("root isnt spawned yet?"),
}
}
pub fn update_open_xr_controllers(
oculus_controller: Res<OculusController>,
mut left_controller_query: Query<(
&mut Transform,
Option<&mut AimPose>,
With<OpenXRLeftController>,
Without<OpenXRRightController>,
)>,
mut right_controller_query: Query<(
&mut Transform,
Option<&mut AimPose>,
With<OpenXRRightController>,
Without<OpenXRLeftController>,
)>,
frame_state: Res<XrFrameState>,
xr_input: Res<XrInput>,
session: Res<XrSession>,
action_sets: Res<XrActionSets>,
) {
//lock dat frame?
let frame_state = *frame_state.lock().unwrap();
//get controller
let controller = oculus_controller.get_ref(&session, &frame_state, &xr_input, &action_sets);
//get left controller
let left_grip_space = controller.grip_space(Hand::Left);
let left_aim_space = controller.aim_space(Hand::Left);
let left_postion = left_grip_space.0.pose.position.to_vec3();
//TODO figure out how to not get the entity multiple times
let left_aim_pose = left_controller_query.get_single_mut();
//set aim pose
match left_aim_pose {
Ok(left_entity) => match left_entity.1 {
Some(mut pose) => {
*pose = AimPose(Transform {
translation: left_aim_space.0.pose.position.to_vec3(),
rotation: left_aim_space.0.pose.orientation.to_quat(),
scale: Vec3::splat(1.0),
});
}
None => (),
},
Err(_) => debug!("no left controlelr entity found"),
}
//set translation
let left_translation = left_controller_query.get_single_mut();
match left_translation {
Ok(mut left_entity) => left_entity.0.translation = left_postion,
Err(_) => (),
}
//set rotation
let left_rotataion = left_controller_query.get_single_mut();
match left_rotataion {
Ok(mut left_entity) => {
left_entity.0.rotation = left_grip_space.0.pose.orientation.to_quat()
}
Err(_) => (),
}
//get right controller
let right_grip_space = controller.grip_space(Hand::Right);
let right_aim_space = controller.aim_space(Hand::Right);
let right_postion = right_grip_space.0.pose.position.to_vec3();
let right_aim_pose = right_controller_query.get_single_mut();
match right_aim_pose {
Ok(right_entity) => match right_entity.1 {
Some(mut pose) => {
*pose = AimPose(Transform {
translation: right_aim_space.0.pose.position.to_vec3(),
rotation: right_aim_space.0.pose.orientation.to_quat(),
scale: Vec3::splat(1.0),
});
}
None => (),
},
Err(_) => debug!("no right controlelr entity found"),
}
//set translation
let right_translation = right_controller_query.get_single_mut();
match right_translation {
Ok(mut right_entity) => right_entity.0.translation = right_postion,
Err(_) => (),
}
//set rotation
let right_rotataion = right_controller_query.get_single_mut();
match right_rotataion {
Ok(mut right_entity) => {
right_entity.0.rotation = right_grip_space.0.pose.orientation.to_quat()
}
Err(_) => (),
}
}

View File

@@ -1,262 +0,0 @@
use crate::xr_input::{QuatConv, Vec3Conv};
use crate::{LEFT_XR_TEXTURE_HANDLE, RIGHT_XR_TEXTURE_HANDLE};
use bevy::core_pipeline::tonemapping::{DebandDither, Tonemapping};
use bevy::math::Vec3A;
use bevy::prelude::*;
use bevy::render::camera::{CameraProjection, CameraRenderGraph, RenderTarget};
use bevy::render::primitives::Frustum;
use bevy::render::view::{ColorGrading, VisibleEntities};
use openxr::Fovf;
#[derive(Bundle)]
pub struct XrCamerasBundle {
pub left: XrCameraBundle,
pub right: XrCameraBundle,
}
impl XrCamerasBundle {
pub fn new() -> Self {
Self::default()
}
}
impl Default for XrCamerasBundle {
fn default() -> Self {
Self {
left: XrCameraBundle::new(Eye::Left),
right: XrCameraBundle::new(Eye::Right),
}
}
}
#[derive(Bundle)]
pub struct XrCameraBundle {
pub camera: Camera,
pub camera_render_graph: CameraRenderGraph,
pub xr_projection: XRProjection,
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: Fovf,
}
impl Default for XRProjection {
fn default() -> Self {
Self {
near: 0.1,
far: 1000.,
fov: Default::default(),
}
}
}
impl XRProjection {
pub fn new(near: f32, far: f32, fov: Fovf) -> Self {
XRProjection { near, far, fov }
}
}
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
]
}
}
pub fn xr_camera_head_sync(
views: ResMut<crate::resources::XrViews>,
mut query: Query<(&mut Transform, &XrCameraType, &mut XRProjection)>,
) {
let mut f = || -> Option<()> {
//TODO calculate HMD position
for (mut transform, camera_type, mut xr_projection) in query.iter_mut() {
let view_idx = match camera_type {
XrCameraType::Xr(eye) => *eye as usize,
XrCameraType::Flatscreen => return None,
};
let v = views.lock().unwrap();
let view = v.get(view_idx)?;
xr_projection.fov = view.fov;
transform.rotation = view.pose.orientation.to_quat();
transform.translation = view.pose.position.to_vec3();
}
Some(())
};
let _ = f();
}