3
examples/android/.gitignore
vendored
3
examples/android/.gitignore
vendored
@@ -1,3 +0,0 @@
|
|||||||
/target
|
|
||||||
/Cargo.lock
|
|
||||||
/runtime_libs
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "bevy_openxr_android"
|
|
||||||
version = "0.1.0"
|
|
||||||
description = "Example for building an Android OpenXR app with Bevy"
|
|
||||||
edition.workspace = true
|
|
||||||
license.workspace = true
|
|
||||||
publish = false
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
name = "bevy_openxr_android"
|
|
||||||
crate-type = ["rlib", "cdylib"]
|
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
bevy.workspace = true
|
|
||||||
bevy_oxr.path = "../.."
|
|
||||||
openxr = { workspace = true, 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,39 +0,0 @@
|
|||||||
android:
|
|
||||||
runtime_libs:
|
|
||||||
- "runtime_libs"
|
|
||||||
manifest:
|
|
||||||
package: "org.bevyengine.example_openxr_android"
|
|
||||||
uses_feature:
|
|
||||||
- name: "android.hardware.vr.headtracking"
|
|
||||||
required: true
|
|
||||||
- name: "oculus.software.handtracking"
|
|
||||||
required: true
|
|
||||||
- name: "com.oculus.feature.PASSTHROUGH"
|
|
||||||
required: true
|
|
||||||
- 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.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|screenLayout"
|
|
||||||
launch_mode: "singleTask"
|
|
||||||
orientation: "landscape"
|
|
||||||
intent_filters:
|
|
||||||
- actions:
|
|
||||||
- "android.intent.action.MAIN"
|
|
||||||
categories:
|
|
||||||
- "com.oculus.intent.category.VR"
|
|
||||||
- "android.intent.category.LAUNCHER"
|
|
||||||
- "org.khronos.openxr.intent.category.IMMERSIVE_HMD"
|
|
||||||
sdk:
|
|
||||||
target_sdk_version: 32
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
use bevy::color::palettes;
|
|
||||||
use bevy::diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin};
|
|
||||||
use bevy::prelude::*;
|
|
||||||
use bevy::transform::components::Transform;
|
|
||||||
use bevy_oxr::graphics::extensions::XrExtensions;
|
|
||||||
use bevy_oxr::graphics::XrAppInfo;
|
|
||||||
use bevy_oxr::passthrough::{PausePassthrough, ResumePassthrough, XrPassthroughState};
|
|
||||||
use bevy_oxr::xr_init::xr_only;
|
|
||||||
use bevy_oxr::xr_input::hands::common::HandInputDebugRenderer;
|
|
||||||
use bevy_oxr::xr_input::hands::HandBone;
|
|
||||||
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() {
|
|
||||||
let mut xr_extensions = XrExtensions::default();
|
|
||||||
xr_extensions.enable_fb_passthrough();
|
|
||||||
xr_extensions.enable_hand_tracking();
|
|
||||||
App::new()
|
|
||||||
.add_plugins(DefaultXrPlugins {
|
|
||||||
reqeusted_extensions: xr_extensions,
|
|
||||||
app_info: XrAppInfo {
|
|
||||||
name: "Bevy OXR Android Example".into(),
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
// .add_plugins(OpenXrDebugRenderer)
|
|
||||||
.add_plugins(LogDiagnosticsPlugin::default())
|
|
||||||
.add_plugins(FrameTimeDiagnosticsPlugin)
|
|
||||||
.add_plugins(HandInputDebugRenderer)
|
|
||||||
.add_plugins(bevy_oxr::passthrough::EnablePassthroughStartup)
|
|
||||||
.add_systems(Startup, setup)
|
|
||||||
.add_systems(
|
|
||||||
Update,
|
|
||||||
(proto_locomotion, toggle_passthrough).run_if(xr_only()),
|
|
||||||
)
|
|
||||||
.add_systems(Update, debug_hand_render.run_if(xr_only()))
|
|
||||||
.add_systems(Startup, spawn_controllers_example)
|
|
||||||
.insert_resource(PrototypeLocomotionConfig::default())
|
|
||||||
.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn debug_hand_render(query: Query<&GlobalTransform, With<HandBone>>, mut gizmos: Gizmos) {
|
|
||||||
for transform in &query {
|
|
||||||
gizmos.sphere(
|
|
||||||
transform.translation(),
|
|
||||||
Quat::IDENTITY,
|
|
||||||
0.01,
|
|
||||||
palettes::css::RED,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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(Plane3d::new(Vec3::Y, Vec2::splat(2.5))),
|
|
||||||
material: materials.add(StandardMaterial::from(Color::srgb(0.3, 0.5, 0.3))),
|
|
||||||
..default()
|
|
||||||
});
|
|
||||||
// cube
|
|
||||||
commands.spawn(PbrBundle {
|
|
||||||
mesh: meshes.add(Cuboid::from_size(Vec3::splat(0.1)).mesh()),
|
|
||||||
material: materials.add(StandardMaterial::from(Color::srgb(0.8, 0.7, 0.6))),
|
|
||||||
transform: Transform::from_xyz(0.0, 0.5, 0.0),
|
|
||||||
..default()
|
|
||||||
});
|
|
||||||
// cube
|
|
||||||
commands.spawn(PbrBundle {
|
|
||||||
mesh: meshes.add(Mesh::from(Cuboid::from_size(Vec3::splat(0.1)))),
|
|
||||||
material: materials.add(StandardMaterial::from(Color::srgb(0.8, 0.0, 0.0))),
|
|
||||||
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()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: make this a vr button
|
|
||||||
fn toggle_passthrough(
|
|
||||||
keys: Res<ButtonInput<KeyCode>>,
|
|
||||||
passthrough_state: Res<XrPassthroughState>,
|
|
||||||
mut resume: EventWriter<ResumePassthrough>,
|
|
||||||
mut pause: EventWriter<PausePassthrough>,
|
|
||||||
) {
|
|
||||||
if keys.just_pressed(KeyCode::Space) {
|
|
||||||
match *passthrough_state {
|
|
||||||
XrPassthroughState::Unsupported => {}
|
|
||||||
XrPassthroughState::Running => {
|
|
||||||
pause.send_default();
|
|
||||||
}
|
|
||||||
XrPassthroughState::Paused => {
|
|
||||||
resume.send_default();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "demo"
|
|
||||||
version = "0.1.0"
|
|
||||||
description = "Demo using bevy_oxr"
|
|
||||||
edition.workspace = true
|
|
||||||
publish = false
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
crate-type = ["rlib", "cdylib"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
bevy.workspace = true
|
|
||||||
bevy_oxr.path = "../../"
|
|
||||||
bevy_rapier3d = "0.25"
|
|
||||||
color-eyre.workspace = 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,39 +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: true
|
|
||||||
- name: "com.oculus.feature.PASSTHROUGH"
|
|
||||||
required: true
|
|
||||||
- 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.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|screenLayout"
|
|
||||||
launch_mode: "singleTask"
|
|
||||||
orientation: "landscape"
|
|
||||||
intent_filters:
|
|
||||||
- actions:
|
|
||||||
- "android.intent.action.MAIN"
|
|
||||||
categories:
|
|
||||||
- "com.oculus.intent.category.VR"
|
|
||||||
- "android.intent.category.LAUNCHER"
|
|
||||||
- "org.khronos.openxr.intent.category.IMMERSIVE_HMD"
|
|
||||||
sdk:
|
|
||||||
target_sdk_version: 32
|
|
||||||
@@ -1,771 +0,0 @@
|
|||||||
mod setup;
|
|
||||||
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use bevy::{
|
|
||||||
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
|
||||||
ecs::schedule::ScheduleLabel,
|
|
||||||
log::info,
|
|
||||||
math::primitives::{Capsule3d, Cuboid},
|
|
||||||
prelude::{
|
|
||||||
bevy_main, default, App, Assets, Color, Commands, Component, Entity, Event, EventReader,
|
|
||||||
EventWriter, FixedUpdate, GlobalTransform, IntoSystemConfigs, IntoSystemSetConfigs, Mesh,
|
|
||||||
PbrBundle, PostUpdate, Query, Res, ResMut, Resource, Schedule, SpatialBundle,
|
|
||||||
StandardMaterial, Startup, Transform, Update, Vec3, With, Without, World,
|
|
||||||
},
|
|
||||||
render::mesh::Meshable,
|
|
||||||
time::{Fixed, Time, Timer, TimerMode},
|
|
||||||
transform::TransformSystem,
|
|
||||||
};
|
|
||||||
use bevy_oxr::{
|
|
||||||
graphics::{extensions::XrExtensions, XrAppInfo},
|
|
||||||
input::XrInput,
|
|
||||||
resources::{XrFrameState, XrSession},
|
|
||||||
xr_init::xr_only,
|
|
||||||
xr_input::{
|
|
||||||
actions::XrActionSets,
|
|
||||||
debug_gizmos::OpenXrDebugRenderer,
|
|
||||||
hands::common::{HandInputDebugRenderer, HandResource, HandsResource},
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
|
|
||||||
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();
|
|
||||||
let mut xr_extensions = XrExtensions::default();
|
|
||||||
xr_extensions.enable_fb_passthrough();
|
|
||||||
|
|
||||||
app
|
|
||||||
//lets get the usual diagnostic stuff added
|
|
||||||
.add_plugins(LogDiagnosticsPlugin::default())
|
|
||||||
.add_plugins(FrameTimeDiagnosticsPlugin)
|
|
||||||
//lets get the xr defaults added
|
|
||||||
.add_plugins(DefaultXrPlugins {
|
|
||||||
reqeusted_extensions: xr_extensions,
|
|
||||||
app_info: XrAppInfo {
|
|
||||||
name: "Bevy OXR Demo".into(),
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
//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(Capsule3d::new(0.033, 0.115).mesh()),
|
|
||||||
material: materials.add(StandardMaterial::from(Color::rgb(0.8, 0.7, 0.6))),
|
|
||||||
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>,
|
|
||||||
) {
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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>,
|
|
||||||
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() {
|
|
||||||
//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(Cuboid::from_size(Vec3::splat(0.1)).mesh()),
|
|
||||||
material: materials.add(StandardMaterial::from(Color::rgb(0.8, 0.7, 0.6))),
|
|
||||||
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>,
|
|
||||||
) {
|
|
||||||
//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, Option<&mut RigidBody>),
|
|
||||||
(Without<XRDirectInteractor>, With<Grabbable>),
|
|
||||||
>,
|
|
||||||
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.2 {
|
|
||||||
Some(mut thing) => {
|
|
||||||
*thing = RigidBody::Dynamic;
|
|
||||||
*interactor_transform.2 = XRSelection::Empty;
|
|
||||||
}
|
|
||||||
None => (),
|
|
||||||
},
|
|
||||||
XRInteractorState::Selecting => {
|
|
||||||
// info!("its a direct interactor?");
|
|
||||||
match grabbable_transform.2 {
|
|
||||||
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,81 +0,0 @@
|
|||||||
use bevy::{
|
|
||||||
math::primitives::{Cuboid, Plane3d},
|
|
||||||
prelude::{
|
|
||||||
Assets, Camera3dBundle, Color, Commands, Mesh, PbrBundle, ResMut, StandardMaterial,
|
|
||||||
Transform, Vec3,
|
|
||||||
},
|
|
||||||
render::mesh::Meshable,
|
|
||||||
utils::default,
|
|
||||||
};
|
|
||||||
use bevy_oxr::xr_input::interactions::{Touched, XRInteractable, XRInteractableState};
|
|
||||||
use bevy_rapier3d::{
|
|
||||||
prelude::{Collider, CollisionGroups, Group, RigidBody},
|
|
||||||
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(Plane3d::new(Vec3::Y).mesh()),
|
|
||||||
material: materials.add(StandardMaterial::from(Color::rgb(0.3, 0.5, 0.3))),
|
|
||||||
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(Cuboid::from_size(Vec3::splat(0.1))),
|
|
||||||
material: materials.add(StandardMaterial::from(Color::rgb(0.8, 0.7, 0.6))),
|
|
||||||
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(5.25, 5.25, 5.0).looking_at(
|
|
||||||
Vec3 {
|
|
||||||
x: -0.548,
|
|
||||||
y: -0.161,
|
|
||||||
z: -0.137,
|
|
||||||
},
|
|
||||||
Vec3::Y,
|
|
||||||
),
|
|
||||||
..default()
|
|
||||||
},));
|
|
||||||
}
|
|
||||||
@@ -1,187 +0,0 @@
|
|||||||
use bevy::diagnostic::LogDiagnosticsPlugin;
|
|
||||||
use bevy::prelude::*;
|
|
||||||
use bevy::render::render_asset::RenderAssetUsages;
|
|
||||||
use bevy::transform::components::Transform;
|
|
||||||
use bevy_oxr::graphics::XrAppInfo;
|
|
||||||
use bevy_oxr::prelude::XrSystems;
|
|
||||||
use bevy_oxr::resources::XrViews;
|
|
||||||
use bevy_oxr::xr_input::hands::common::HandInputDebugRenderer;
|
|
||||||
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 {
|
|
||||||
app_info: XrAppInfo {
|
|
||||||
name: "Bevy OXR Globe Example".into(),
|
|
||||||
},
|
|
||||||
..default()
|
|
||||||
})
|
|
||||||
.add_plugins(LogDiagnosticsPlugin::default())
|
|
||||||
.add_systems(Startup, setup)
|
|
||||||
.add_systems(Update, (proto_locomotion, pull_to_ground).chain().xr_only())
|
|
||||||
.insert_resource(PrototypeLocomotionConfig::default())
|
|
||||||
.add_systems(Startup, spawn_controllers_example)
|
|
||||||
.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,
|
|
||||||
RenderAssetUsages::RENDER_WORLD,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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(Sphere::new(radius)),
|
|
||||||
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(Cuboid::from_size(Vec3::splat(0.1)).mesh()),
|
|
||||||
material: materials.add(StandardMaterial::from(Color::srgb(0.8, 0.7, 0.6))),
|
|
||||||
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 Some(view) = views.first() 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));
|
|
||||||
}
|
|
||||||
252
examples/xr.rs
252
examples/xr.rs
@@ -1,252 +0,0 @@
|
|||||||
use bevy::diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin};
|
|
||||||
|
|
||||||
use bevy::prelude::*;
|
|
||||||
use bevy::transform::components::Transform;
|
|
||||||
use bevy_oxr::graphics::XrAppInfo;
|
|
||||||
use bevy_oxr::input::XrInput;
|
|
||||||
use bevy_oxr::resources::{XrFrameState, XrSession};
|
|
||||||
|
|
||||||
use bevy_oxr::xr_init::{xr_only, EndXrSession, StartXrSession, XrSetup};
|
|
||||||
use bevy_oxr::xr_input::actions::XrActionSets;
|
|
||||||
use bevy_oxr::xr_input::hands::common::HandInputDebugRenderer;
|
|
||||||
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 {
|
|
||||||
app_info: XrAppInfo {
|
|
||||||
name: "Bevy OXR Example".into(),
|
|
||||||
},
|
|
||||||
..default()
|
|
||||||
})
|
|
||||||
// .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.run_if(xr_only()))
|
|
||||||
.insert_resource(PrototypeLocomotionConfig::default())
|
|
||||||
.add_systems(XrSetup, spawn_controllers_example)
|
|
||||||
.add_plugins(HandInputDebugRenderer)
|
|
||||||
.add_systems(
|
|
||||||
Update,
|
|
||||||
draw_interaction_gizmos
|
|
||||||
.after(update_interactable_states)
|
|
||||||
.run_if(xr_only()),
|
|
||||||
)
|
|
||||||
.add_systems(
|
|
||||||
Update,
|
|
||||||
draw_socket_gizmos
|
|
||||||
.after(update_interactable_states)
|
|
||||||
.run_if(xr_only()),
|
|
||||||
)
|
|
||||||
.add_systems(
|
|
||||||
Update,
|
|
||||||
interactions
|
|
||||||
.before(update_interactable_states)
|
|
||||||
.run_if(xr_only()),
|
|
||||||
)
|
|
||||||
.add_systems(
|
|
||||||
Update,
|
|
||||||
socket_interactions.before(update_interactable_states),
|
|
||||||
)
|
|
||||||
.add_systems(Update, prototype_interaction_input.run_if(xr_only()))
|
|
||||||
.add_systems(Update, update_interactable_states)
|
|
||||||
.add_systems(Update, update_grabbables.after(update_interactable_states))
|
|
||||||
.add_systems(Update, start_stop_session)
|
|
||||||
.add_event::<InteractionEvent>()
|
|
||||||
.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start_stop_session(
|
|
||||||
keyboard: Res<ButtonInput<KeyCode>>,
|
|
||||||
mut start: EventWriter<StartXrSession>,
|
|
||||||
mut stop: EventWriter<EndXrSession>,
|
|
||||||
) {
|
|
||||||
if keyboard.just_pressed(KeyCode::KeyS) {
|
|
||||||
start.send_default();
|
|
||||||
}
|
|
||||||
if keyboard.just_pressed(KeyCode::KeyE) {
|
|
||||||
stop.send_default();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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(Plane3d::new(Vec3::Y, Vec2::splat(2.5)).mesh()),
|
|
||||||
material: materials.add(StandardMaterial::from(Color::srgb(0.3, 0.5, 0.3))),
|
|
||||||
..default()
|
|
||||||
});
|
|
||||||
// cube
|
|
||||||
commands.spawn(PbrBundle {
|
|
||||||
mesh: meshes.add(Cuboid::from_size(Vec3::splat(0.1)).mesh()),
|
|
||||||
material: materials.add(StandardMaterial::from(Color::srgb(0.8, 0.7, 0.6))),
|
|
||||||
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(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
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>,
|
|
||||||
) {
|
|
||||||
//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 = interactor_transform.0.compute_transform();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
// info!("not a direct interactor")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
// info!("not a grabbable?")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user