use haply::{
device_model::{
BubbleCenterSettings,
BubbleNavigationSettings,
CollisionDetectionSettings,
Inverse3Configure,
Linear3D,
NavigationConfigure,
SdfPrimitive,
},
http::InverseHttpClient,
HaplyDevice,
};
use std::io::Write;
use std::time::Duration;
use tokio::time::{ interval, sleep };
const USE_HTTP: bool = false;
const HTTP_BASE: &str = "http://localhost:10001";
const WS_URL: &str = "ws://localhost:10001/";
const SESSION: &str = ":-1";
fn bubble_nav_config() -> NavigationConfigure {
NavigationConfigure {
mode: "bubble".to_string(),
bubble: Some(BubbleNavigationSettings {
center: Some(BubbleCenterSettings {
position: Some(Linear3D { x: 0.0, y: -0.05, z: 0.18 }),
relative: Some(false),
follow: Some(false),
speed: None,
}),
shape: Some(SdfPrimitive::Sphere { r: 0.05 }),
velocity_zone_width: Some(0.03),
max_velocity: Some(0.5),
spring_surface: Some(7.0),
damping_surface: Some(0.7),
collision_detection: Some(CollisionDetectionSettings {
enabled: Some(false),
force_threshold: Some(1.0),
inflate_ratio: Some(2.0),
exit_ratio: Some(0.7),
}),
..Default::default()
}),
}
}
fn disabled_nav_config() -> NavigationConfigure {
NavigationConfigure {
mode: "disabled".to_string(),
bubble: None,
}
}
async fn apply_navigation_mode(
device: &mut HaplyDevice,
http: &InverseHttpClient,
inverse_id: &str,
enabled: bool,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let nav_config = if enabled { bubble_nav_config() } else { disabled_nav_config() };
if USE_HTTP {
http.set_navigation("inverse3", inverse_id, Some(SESSION), &nav_config).await?;
} else {
device.configure_inverse3(
inverse_id,
Inverse3Configure {
navigation: Some(nav_config),
..Default::default()
},
Some(true),
).await?;
}
Ok(())
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let mut device = HaplyDevice::new(HTTP_BASE, WS_URL).await?;
let http = InverseHttpClient::new(HTTP_BASE);
sleep(Duration::from_millis(300)).await;
let state = device.read_state().await?;
let inv = state.inverse3.first().ok_or("No Inverse3 device connected")?;
let id = inv.device_id.clone();
println!("Using Inverse3 device: {}", id);
println!("Enabling bubble navigation...");
apply_navigation_mode(&mut device, &http, &id, true).await?;
println!("Press VerseGrip Button A to toggle navigation (Ctrl+C to stop)");
let mut ticker = interval(Duration::from_millis(50));
let mut tick_count: u32 = 0;
let mut nav_enabled = true;
let mut last_button_a = false;
loop {
ticker.tick().await;
tick_count += 1;
let state = device.read_state().await?;
let button_a = state.custom_verse_grip
.iter()
.find_map(|d| d.state.buttons.map(|b| b.a))
.or_else(|| state.wireless_verse_grip.iter().find_map(|d| d.state.buttons.map(|b| b.a)))
.unwrap_or(false);
if button_a && !last_button_a {
nav_enabled = !nav_enabled;
apply_navigation_mode(&mut device, &http, &id, nav_enabled).await?;
println!(
"\nButton A pressed -> navigation {}",
if nav_enabled { "ENABLED (bubble)" } else { "DISABLED" }
);
}
last_button_a = button_a;
if tick_count % 10 == 0 {
device.send_force_full_render().await?;
} else {
device.send_command().await?;
}
if let Some(inv) = state.inverse3.first() {
let cursor = inv.state.cursor_position.unwrap_or_default();
let ws_pos = inv.state.transform
.as_ref()
.map(|t| t.position)
.unwrap_or_default();
let nav_center = inv.state.navigation
.as_ref()
.and_then(|n| n.bubble.as_ref())
.and_then(|b| b.center);
let nav_mode = inv.status.navigation
.as_ref()
.and_then(|s| s.mode.as_deref())
.unwrap_or("off");
print!(
"\rButtonA: {} NavEnabled: {} Cursor: ({:.4},{:.4},{:.4}) WS: ({:.4},{:.4},{:.4}) Nav: {:?} Mode: {} ",
button_a,
nav_enabled,
cursor.x,
cursor.y,
cursor.z,
ws_pos.x,
ws_pos.y,
ws_pos.z,
nav_center,
nav_mode
);
std::io::stdout().flush().unwrap();
}
}
}