From 47031c3abbfd55f49379d4c8764c80a759a5b095 Mon Sep 17 00:00:00 2001 From: exstrim401 Date: Tue, 11 Aug 2026 13:14:40 +0300 Subject: [PATCH] rendermodels: support standard pose components in GetComponentState SteamVR registers standard pose components (/pose/raw, /pose/tip) for every controller, for all render models. Games can look these up by their well-known names. They do not have to enumerate the components. xrizer accepted only its synthetic "xrizer_component". Thus a lookup for "tip" always returned false. The BigWalkVR mod marks a controller pose as invalid if GetComponentState(model, "tip") fails. Under xrizer, the hands never moved, but the buttons continued to work. Accept the standard pose component names (tip, base, handgrip, grip) for known render models. Return identity transforms, because our models contain their offsets. --- src/rendermodels.rs | 62 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/src/rendermodels.rs b/src/rendermodels.rs index 888ca2fa..8919255e 100644 --- a/src/rendermodels.rs +++ b/src/rendermodels.rs @@ -20,6 +20,18 @@ const RENDER_MODEL_COLOR: [u8; 4] = [32, 32, 32, 255]; // use a single static component for all rendermodels static DEFAULT_COMPONENT: &CStr = c"xrizer_component"; +// SteamVR registers these standard pose components for each controller, +// for all render models. The OpenVR wiki on Input Profiles says: +// "All tracked devices also get two pose components registered regardless +// of what render model they use: /pose/raw and /pose/tip" +// Some games (for example, the BigWalkVR mod) mark a controller pose as +// invalid if GetComponentState fails for these components. +static POSE_COMPONENTS: &[&CStr] = &[c"tip", c"base", c"handgrip", c"grip"]; + +fn is_supported_component(component_name: &CStr) -> bool { + component_name == DEFAULT_COMPONENT || POSE_COMPONENTS.contains(&component_name) +} + struct RenderModelObjLz4 { /// lz4-compressed obj data bytes: &'static [u8], @@ -190,7 +202,7 @@ impl vr::IVRRenderModels006_Interface for RenderModels { let render_model_name = unsafe { CStr::from_ptr(render_model_name) }; let component_name = unsafe { CStr::from_ptr(component_name) }; - component_name == DEFAULT_COMPONENT && get_render_model_data(render_model_name).is_some() + is_supported_component(component_name) && get_render_model_data(render_model_name).is_some() } fn GetComponentState( &self, @@ -210,7 +222,8 @@ impl vr::IVRRenderModels006_Interface for RenderModels { let render_model_name = unsafe { CStr::from_ptr(render_model_name) }; let component_name = unsafe { CStr::from_ptr(component_name) }; - if component_name != DEFAULT_COMPONENT || get_render_model_data(render_model_name).is_none() + if !is_supported_component(component_name) + || get_render_model_data(render_model_name).is_none() { // unsupported model or component return false; @@ -965,6 +978,51 @@ mod tests { assert_ne!(state.uProperties & is_static, 0); } + #[test] + fn get_component_state_supports_standard_pose_components() { + // SteamVR registers standard pose components (for example, "tip") + // for each controller. Some games (for example, the BigWalkVR mod) + // mark a controller pose as invalid if GetComponentState fails for + // these components. Thus GetComponentState must succeed for known + // render models. + let rm = RenderModels::default(); + let some_model = CString::new(*RENDER_MODELS.keys().next().unwrap()).unwrap(); + + for component in POSE_COMPONENTS { + let mut state = unsafe { std::mem::zeroed::() }; + assert!( + rm.GetComponentState( + some_model.as_ptr(), + component.as_ptr(), + ptr::null(), + ptr::null(), + &mut state, + ), + "GetComponentState failed for standard pose component {component:?}" + ); + assert!(rm.RenderModelHasComponent(some_model.as_ptr(), component.as_ptr())); + + // GetComponentState must fail for unknown models + assert!(!rm.GetComponentState( + c"unknown_model".as_ptr(), + component.as_ptr(), + ptr::null(), + ptr::null(), + &mut state, + )); + } + + // GetComponentState must fail for unknown component names + let mut state = unsafe { std::mem::zeroed::() }; + assert!(!rm.GetComponentState( + some_model.as_ptr(), + c"not_a_real_component".as_ptr(), + ptr::null(), + ptr::null(), + &mut state, + )); + } + #[test] fn load_and_free_texture_contract() { let rm = RenderModels::default();