feat: tons of infoooooo

This commit is contained in:
Nova 2024-04-20 09:58:15 -04:00
parent 7cdce1acd7
commit d0937d7949
8 changed files with 150 additions and 142 deletions

47
Cargo.lock generated
View file

@ -139,10 +139,10 @@ dependencies = [
name = "color_cube"
version = "0.1.0"
dependencies = [
"glam 0.27.0",
"manifest-dir-macros",
"mint",
"stardust-xr-fusion",
"stardust-xr-molecules",
"tokio",
]
@ -250,6 +250,15 @@ dependencies = [
"mint",
]
[[package]]
name = "glam"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e05e7e6723e3455f4818c7b26e855439f7546cf617ef669d1adedb8669e5cb9"
dependencies = [
"mint",
]
[[package]]
name = "global_counter"
version = "0.2.2"
@ -320,15 +329,6 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lerp"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecc56a024593ecbcacb6bb4f8f4ace719eb08ae9b701535640ef3efb0e706260"
dependencies = [
"num-traits",
]
[[package]]
name = "libc"
version = "0.2.153"
@ -367,15 +367,6 @@ dependencies = [
"syn 2.0.60",
]
[[package]]
name = "map-range"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bffa558d7f51b549670be5ff6db164cd9be428c035cbf4e3f782db3da66845a5"
dependencies = [
"num-traits",
]
[[package]]
name = "memchr"
version = "2.7.2"
@ -852,24 +843,6 @@ dependencies = [
"stardust-xr-schemas",
]
[[package]]
name = "stardust-xr-molecules"
version = "0.45.0"
source = "git+https://github.com/StardustXR/molecules#263e19e10fb4af839809e4d1f79717247909175c"
dependencies = [
"color-rs",
"glam 0.25.0",
"lazy_static",
"lerp",
"map-range",
"mint",
"rustc-hash",
"serde",
"stardust-xr-fusion",
"tokio",
"tracing",
]
[[package]]
name = "stardust-xr-schemas"
version = "1.5.3"

View file

@ -12,4 +12,4 @@ mint = "0.5.9"
tokio = { version = "1.37.0", features = ["full"] }
stardust-xr-fusion = { git = "https://github.com/StardustXR/core" }
stardust-xr-molecules = { git = "https://github.com/StardustXR/molecules" }
glam = { version = "0.27.0", features = ["mint"] }

Binary file not shown.

Binary file not shown.

Binary file not shown.

3
rustfmt.toml Normal file
View file

@ -0,0 +1,3 @@
# this file makes sure the coding conventions that suit the project well are enforced
hard_tabs = true
imports_granularity = "Crate"

View file

@ -1,83 +1,121 @@
use stardust_xr_fusion::drawable::Model;
use glam::Vec3;
use stardust_xr_fusion::drawable::{MaterialParameter, Model, ModelPart, ModelPartAspect};
use stardust_xr_fusion::input::InputMethodAspect;
use stardust_xr_fusion::{
client::{Client, FrameInfo},
core::values::{rgba_linear, Color, ResourceID},
fields::BoxField,
input::{InputData, InputHandler, InputHandlerAspect, InputHandlerHandler, InputMethod},
node::NodeType,
spatial::{SpatialAspect, Transform},
HandlerWrapper,
client::{Client, FrameInfo},
core::values::{rgba_linear, Color, ResourceID},
fields::BoxField,
input::{
InputData, InputDataType, InputHandler, InputHandlerAspect, InputHandlerHandler,
InputMethod,
},
node::NodeType,
spatial::{SpatialAspect, Transform},
HandlerWrapper,
};
use stardust_xr_molecules::{Grabbable, GrabbableSettings};
use std::collections::HashMap;
// you need to handle every type of input method, multimodal after all!
fn is_grabbing(input: &InputData) -> bool {
match &input.input {
InputDataType::Hand(h) => {
// hands have pinch_strength and grab_strenght data but i figured you should know how to get raw joints
Vec3::from(h.index.tip.position).distance(Vec3::from(h.thumb.tip.position)) < 0.005
}
// pointers and tips (tips are like controllers, pens, spatial cursors) have datamaps full of abstract actions (e.g. select) and raw input info
_ => input.datamap.with_data(|d| d.idx("select").as_f32()) > 0.9,
}
}
fn interact_point(input: &InputData) -> Vec3 {
match &input.input {
InputDataType::Pointer(p) => [0.0; 3].into(),
// since we're outputting a glam vec3 (glam is a math library that's similar to stereokit's) we gotta convert using `Vec3::from()` or `.into()` to convert to the inferred type
InputDataType::Hand(h) => h.index.tip.position.into(),
InputDataType::Tip(t) => t.origin.into(),
}
}
pub struct ColorCube {
model: Model,
field: BoxField,
handler: InputHandler,
inputs: HashMap<InputData, InputMethod>,
hover_color: Color,
model: Model,
circle: ModelPart,
field: BoxField,
handler: InputHandler,
inputs: HashMap<InputData, InputMethod>,
hover_color: Color,
}
impl ColorCube {
pub async fn create(client: &Client) -> HandlerWrapper<InputHandler, Self> {
let model = Model::create(
client.get_root(),
Transform::identity(),
&ResourceID::new_namespaced("color_cube", "color_cube"),
)
.unwrap();
pub async fn create(client: &Client) -> HandlerWrapper<InputHandler, Self> {
// make the model
let model = Model::create(
// everything in stardust is relative to something else, client root is a great start!
client.get_root(),
// identity transform relative to it, we can assume the root's up direction will match gravity but not its Y rotation
Transform::identity(),
// we want to use a namespaced resource so it can be themed! since this might have its asset swapped you should generally use the model as a base for sizing and positioning of things
&ResourceID::new_namespaced("color_cube", "color_cube"),
)
// unwrap basically will shut down the program (nicely) if this fails, given it's an essential part of this client that's ok
.unwrap();
// to set material properties we have to get the model part (same as stereokit model node)
let circle = model.model_part("Circle").unwrap();
let bounds = model.get_local_bounding_box().await.unwrap();
let field = BoxField::create(&model, Transform::none(), [0.4; 3]).unwrap();
let hover_color = rgba_linear!(0.5, 0.5, 0.5, 1.0);
// get the bounding box (center and size) of the model and its children. You have to call `await` because this is async and we don't want to cause the whole program to block waiting for a response.
let bounds = model.get_local_bounding_box().await.unwrap();
// now make the box field (fields because unlike colliders they have more information than "colliding" or "not colliding").
let field = BoxField::create(
&model,
// we also use the bounds size and center here
Transform::from_translation(bounds.center),
bounds.size,
)
.unwrap();
let hover_color = rgba_linear!(0.5, 0.5, 0.5, 1.0);
let handler = InputHandler::create(&model, Transform::none(), &field).unwrap();
let color_cube = ColorCube {
model,
field,
handler: handler.alias(),
inputs: Default::default(),
hover_color,
};
handler.wrap(color_cube).unwrap()
}
// input data is all spatially relative to the handler itself, but you can move the field independent of the handler. Makes certain things like grabbing much easier when you can put the grab input handler at the client root.
let handler = InputHandler::create(&model, Transform::none(), &field).unwrap();
let color_cube = ColorCube {
model,
circle,
field,
handler: handler.alias(),
inputs: Default::default(),
hover_color,
};
// because the server could send events for the client to process related to the input handler, we bundle them together in a HandlerWrapper so they are synced up and locked together
handler.wrap(color_cube).unwrap()
}
pub fn update(&mut self, info: &FrameInfo) {
// for (data, method) in self.inputs.iter() {
// data.datamap.with_data(|data| {
// data.idx("color_hover").as_f32()
// });
// data.captured = true;
// method.capture(&self.handler).unwrap();
// }
pub fn update(&mut self, info: &FrameInfo) {
// we have all the input methods (e.g. hands) in range queued up in `self.inputs`.
self.inputs.clear();
}
// iterate through every input method for this frame
for (data, method) in self.inputs.iter() {
if is_grabbing(data) {
// call this method when you want to make sure the hand is only trying to interact with this handler
method.request_capture(&self.handler).unwrap();
}
// then check every frame until it is successfully captured (but it might not be ever)
if data.captured {
// since we don't want the program to quit if this fails, use `let _ = ` to ignore the result
let _ = self
.circle
.set_material_parameter("color", MaterialParameter::Color(self.hover_color));
}
// we gotta remove that color too sometime lol
}
// fn hover(size: Vector2<f32>, point: Vector3<f32>) -> bool {
// point.x.abs() < size.x && point.y.abs() < size.y
// }
// fn hover_action(input: &InputData, state: &State) -> bool {
// match &input.input {
// InputDataType::Pointer(_) => input.distance < 0.0,
// InputDataType::Hand(h) => Self::hover(state.size, h.index.tip.position),
// InputDataType::Tip(t) => Self::hover(state.size, t.origin),
// }
// }
// fn hover_point(&self, input: &InputData) -> Vector3<f32> {
// let hover_point = match &input.input {
// InputDataType::Pointer(p) => [0.0; 3].into(),
// InputDataType::Hand(h) => h.index.tip.position,
// InputDataType::Tip(t) => t.origin,
// };
// clear the queue for next time
self.inputs.clear();
// return hover_point;
// }
// we could also use the following to iterate over every input and clear the map at the same time
for (data, method) in self.inputs.drain() {}
}
}
// the stardust server sends a message to fusion (the library) which then calls these methods
impl InputHandlerHandler for ColorCube {
fn input(&mut self, input: InputMethod, data: InputData) {
self.inputs.insert(data, input);
}
// an input method is sending data to you this frame, when `RootHandler::frame` is called you can assume it's all been sent
fn input(&mut self, input: InputMethod, data: InputData) {
self.inputs.insert(data, input);
}
}

View file

@ -3,52 +3,46 @@ use color_cube::ColorCube;
use manifest_dir_macros::directory_relative_path;
use stardust_xr_fusion::{
client::{Client, FrameInfo, RootHandler},
input::InputHandler,
HandlerWrapper,
client::{Client, ClientState, FrameInfo, RootHandler},
input::InputHandler,
HandlerWrapper,
};
struct Root(HandlerWrapper<InputHandler, ColorCube>);
// rust doesn't have inheritance so we add a trait to a struct to implement functionality
impl RootHandler for Root {
fn frame(&mut self, info: FrameInfo) {
self.0.lock_wrapped().update(&info);
// self.grabbable.update(&info).unwrap();
// self.input_handler.lock_wrapped().update_actions([
// &mut self.color_hover,
// ]);
// for input_data in self
// .color_hover
// .started
// if self.color_hover.currently_acting.is_empty() {
// self.model.set_local_transform(
// Transform::from_scale([1.1; 3])
// ).unwrap();
// } else {
// self.model.set_local_transform(
// Transform::from_scale([1.0; 3])
// ).unwrap();
// }
}
fn save_state(&mut self) -> stardust_xr_fusion::client::ClientState {
todo!("implement save state")
}
fn frame(&mut self, info: FrameInfo) {
// delta time in seconds, very handy :)
dbg!(info.delta);
// elapsed time since the client started, server doesn't send this over but it's calculated locally
dbg!(info.elapsed);
// you need to call .lock_wrapped() because the ColorCube struct is inside a mutex inside the HandlerWrapper
self.0.lock_wrapped().update(&info);
}
// the goal here is to put everything you need in `ClientState` so on next launch you could restore it from `Client::get_state()`
// this todo will make the program panic when the server is about to shut it down meaning it will not be launched on restore
// basically this isn't spatially persistent
fn save_state(&mut self) -> ClientState {
todo!("implement save state")
}
}
#[tokio::main]
async fn main() {
let (client, event_loop) = Client::connect_with_async_loop().await.unwrap();
client.set_base_prefixes(&[directory_relative_path!("res")]);
// automatically connects to the first stardust server it can
let (client, event_loop) = Client::connect_with_async_loop().await.unwrap();
// all usages of ResourceID will be relative to the given directory first and foremost excluding themes. This can be overridden using the STARDUST_RES_PREFIXES env var at build time (comma separated absolute paths)
client.set_base_prefixes(&[directory_relative_path!("res")]);
let _root = client
.wrap_root(Root(ColorCube::create(&client).await))
.unwrap();
let _root = client
.wrap_root(Root(ColorCube::create(&client).await))
.unwrap();
tokio::select! {
_ = tokio::signal::ctrl_c() => (),
_ = event_loop => panic!("server crashed"),
}
// async waits for either thing to happen
tokio::select! {
// maybe someone tried to close the program via ctrl+c
_ = tokio::signal::ctrl_c() => (),
// this is kinda a bug since the server has no official "shutting down" signal
_ = event_loop => panic!("server crashed"),
}
}