begin refactor
This commit is contained in:
3
.cargo/config.toml
Normal file
3
.cargo/config.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[target.wasm32-unknown-unknown]
|
||||
runner = "wasm-server-runner"
|
||||
rustflags = ["--cfg","web_sys_unstable_apis",]
|
||||
35
.github/workflows/build.yml
vendored
35
.github/workflows/build.yml
vendored
@@ -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
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
**/target
|
||||
**/Cargo.lock
|
||||
**/runtime_libs/arm64-v8a/*
|
||||
.vscode
|
||||
\.DS_Store
|
||||
|
||||
86
Cargo.toml
86
Cargo.toml
@@ -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"
|
||||
|
||||
3
examples/android/.gitignore
vendored
3
examples/android/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
/target
|
||||
/Cargo.lock
|
||||
/runtime_libs
|
||||
@@ -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"
|
||||
@@ -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
|
||||
Binary file not shown.
@@ -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
|
||||
@@ -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(),
|
||||
));
|
||||
}
|
||||
@@ -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
|
||||
@@ -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```
|
||||
@@ -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
|
||||
@@ -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?")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fn main() {
|
||||
demo::main();
|
||||
}
|
||||
@@ -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()
|
||||
},));
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
221
examples/xr.rs
221
examples/xr.rs
@@ -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?")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
35
src/backend/graphics/mod.rs
Normal file
35
src/backend/graphics/mod.rs
Normal 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 {})
|
||||
}
|
||||
@@ -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
53
src/backend/mod.rs
Normal 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
159
src/backend/oxr.rs
Normal 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
132
src/backend/oxr_utils.rs
Normal 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
63
src/backend/traits.rs
Normal 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
56
src/backend/web_utils.rs
Normal 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
90
src/backend/webxr.rs
Normal 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
15
src/error.rs
Normal 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 {}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
68
src/input.rs
68
src/input.rs
@@ -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),
|
||||
})
|
||||
}
|
||||
}
|
||||
397
src/lib.rs
397
src/lib.rs
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
186
src/resources.rs
186
src/resources.rs
@@ -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
191
src/types.rs
Normal 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,
|
||||
}
|
||||
364
src/xr_init.rs
364
src/xr_init.rs
@@ -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)
|
||||
}
|
||||
@@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
});
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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"),
|
||||
}
|
||||
}
|
||||
@@ -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(_) => (),
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
Reference in New Issue
Block a user