stereokit_rust/tools/
xr_fb_render_model.rs

1//! XR_FB_render_model extension implementation (HorizonOS)
2//!
3//! This module provides access to the OpenXR XR_FB_render_model extension,
4//! which allows applications to retrieve render models for controllers and other devices.
5//! <https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#XR_FB_render_model>
6
7use std::ffi::{CString, c_char};
8use std::ptr;
9
10use openxr_sys::{RenderModelCapabilitiesRequestFB, 
11    Instance, Path, RenderModelBufferFB, RenderModelFlagsFB, RenderModelKeyFB, RenderModelLoadInfoFB,
12    RenderModelPathInfoFB, RenderModelPropertiesFB, Result as XrResult, Session, StructureType,
13    pfn::{EnumerateRenderModelPathsFB, GetRenderModelPropertiesFB, LoadRenderModelFB, PathToString, StringToPath},
14};
15
16use crate::maths::units::CM;
17use crate::maths::{Matrix, Quat, Vec3};
18use crate::model::AnimMode;
19use crate::{
20    model::Model, 
21    prelude::*,
22    system::{Backend, BackendOpenXR, BackendXRType, Handed, Input, Log},
23};
24
25
26/// Extension name for XR_FB_render_model
27pub const XR_FB_RENDER_MODEL_EXTENSION_NAME: &str = "XR_FB_render_model";
28
29/// Render model properties (simplified for easier use)
30#[derive(Debug, Clone)]
31pub struct RenderModelProperties {
32    pub vendor_id: u32,  
33    pub model_name: String,
34    pub model_version: u32,
35    pub flags: u64,
36}
37
38/// Main struct for XR_FB_render_model extension
39pub struct XrFbRenderModel {
40    with_log: bool,
41    xr_enumerate_render_model_paths: Option<EnumerateRenderModelPathsFB>,
42    xr_get_render_model_properties: Option<GetRenderModelPropertiesFB>,
43    xr_load_render_model: Option<LoadRenderModelFB>,
44    xr_string_to_path: Option<StringToPath>,
45    xr_path_to_string: Option<PathToString>,
46    instance: Instance,
47    session: Session,
48
49    // Cached controller models
50    left_controller_data: Option<Model>,
51    right_controller_data: Option<Model>,
52}
53
54impl XrFbRenderModel {
55    /// Creates a new XrFbRenderModel instance if the extension is supported
56    pub fn new(with_log: bool) -> Option<Self> {
57        if Backend::xr_type() != BackendXRType::OpenXR || !BackendOpenXR::ext_enabled(XR_FB_RENDER_MODEL_EXTENSION_NAME)
58        {
59            return None;
60        }
61
62        let instance = Instance::from_raw(BackendOpenXR::instance());
63        let session = Session::from_raw(BackendOpenXR::session());
64
65        let xr_enumerate_render_model_paths =
66            BackendOpenXR::get_function::<EnumerateRenderModelPathsFB>("xrEnumerateRenderModelPathsFB");
67        let xr_get_render_model_properties =
68            BackendOpenXR::get_function::<GetRenderModelPropertiesFB>("xrGetRenderModelPropertiesFB");
69        let xr_load_render_model = BackendOpenXR::get_function::<LoadRenderModelFB>("xrLoadRenderModelFB");
70        let xr_string_to_path = BackendOpenXR::get_function::<StringToPath>("xrStringToPath");
71        let xr_path_to_string = BackendOpenXR::get_function::<PathToString>("xrPathToString");
72
73        if xr_enumerate_render_model_paths.is_none()
74            || xr_get_render_model_properties.is_none()
75            || xr_load_render_model.is_none()
76            || xr_string_to_path.is_none()
77            || xr_path_to_string.is_none()
78        {
79            Log::warn("❌ Failed to load all XR_FB_render_model functions");
80            return None;
81        }
82
83        Some(Self {
84            with_log,
85            xr_enumerate_render_model_paths,
86            xr_get_render_model_properties,
87            xr_load_render_model,
88            xr_string_to_path,
89            xr_path_to_string,
90            instance,
91            session,
92            left_controller_data: None,
93            right_controller_data: None,
94        })
95    }
96
97    fn path_to_string(&self, path: Path) -> Option<String> {
98        let path_to_string_fn = self.xr_path_to_string?;
99
100        let mut buffer_count_output = 0u32;
101        let result = unsafe { path_to_string_fn(self.instance, path, 0, &mut buffer_count_output, ptr::null_mut()) };
102
103        if result != XrResult::SUCCESS || buffer_count_output == 0 {
104            return None;
105        }
106
107        let mut buffer = vec![0u8; buffer_count_output as usize];
108        let result = unsafe {
109            path_to_string_fn(
110                self.instance,
111                path,
112                buffer_count_output,
113                &mut buffer_count_output,
114                buffer.as_mut_ptr() as *mut c_char,
115            )
116        };
117
118        if result == XrResult::SUCCESS {
119            if let Some(&0) = buffer.last() {
120                buffer.pop();
121            }
122            String::from_utf8(buffer).ok()
123        } else {
124            None
125        }
126    }
127
128    /// Enumerates available render model paths
129    pub fn enumerate_render_model_paths(&self) -> Result<Vec<String>, XrResult> {
130        let enumerate_fn = self.xr_enumerate_render_model_paths.ok_or(XrResult::ERROR_FUNCTION_UNSUPPORTED)?;
131
132        let mut path_count = 0u32;
133        let result = unsafe { enumerate_fn(self.session, 0, &mut path_count, ptr::null_mut()) };
134
135        if result != XrResult::SUCCESS {
136            return Err(result);
137        }
138
139        if path_count == 0 {
140            return Ok(Vec::new());
141        }
142
143        let mut path_infos = vec![
144            RenderModelPathInfoFB {
145                ty: StructureType::RENDER_MODEL_PATH_INFO_FB,
146                next: ptr::null_mut(),
147                path: Path::from_raw(0),
148            };
149            path_count as usize
150        ];
151
152        let result = unsafe { enumerate_fn(self.session, path_count, &mut path_count, path_infos.as_mut_ptr()) };
153
154        if result != XrResult::SUCCESS {
155            return Err(result);
156        }
157
158        let mut paths = Vec::new();
159        for path_info in path_infos {
160            if let Some(path_string) = self.path_to_string(path_info.path) {
161                paths.push(path_string);
162            }
163        }
164
165        Ok(paths)
166    }
167
168    /// Gets render model properties for a given model path
169    pub fn get_render_model_properties(&self, model_path: &str) -> Result<RenderModelProperties, XrResult> {
170        let get_properties_fn = self.xr_get_render_model_properties.ok_or(XrResult::ERROR_FUNCTION_UNSUPPORTED)?;
171        let string_to_path_fn = self.xr_string_to_path.ok_or(XrResult::ERROR_FUNCTION_UNSUPPORTED)?;
172
173        let c_string = CString::new(model_path).map_err(|_| XrResult::ERROR_VALIDATION_FAILURE)?;
174        let mut path = Path::from_raw(0);
175        let result = unsafe { string_to_path_fn(self.instance, c_string.as_ptr(), &mut path) };
176
177        if result != XrResult::SUCCESS {
178            return Err(result);
179        }
180
181        let mut properties = RenderModelPropertiesFB {
182            ty: StructureType::RENDER_MODEL_PROPERTIES_FB,
183            next: ptr::null_mut(),
184            vendor_id: 0,
185            model_name: [0; 64],
186            model_key: RenderModelKeyFB::from_raw(0),
187            model_version: 0,
188            flags: RenderModelFlagsFB::from_raw(0),
189        };
190
191        let mut cap_req = RenderModelCapabilitiesRequestFB {
192            ty: StructureType::RENDER_MODEL_CAPABILITIES_REQUEST_FB,
193            next: ptr::null_mut(),
194            flags: RenderModelFlagsFB::SUPPORTS_GLTF_2_0_SUBSET_2,
195
196        };
197
198        properties.next = &mut cap_req as *mut _ as *mut _;
199
200        let result = unsafe { get_properties_fn(self.session, path, &mut properties) };
201
202        if result != XrResult::SUCCESS //
203            // && result != XrResult::RENDER_MODEL_UNAVAILABLE_FB //
204            // && result != XrResult::SESSION_LOSS_PENDING //
205        {
206            return Err(result);
207        }
208
209        let model_name = unsafe {
210            let name_ptr = properties.model_name.as_ptr() as *const c_char;
211            let c_str = std::ffi::CStr::from_ptr(name_ptr);
212            c_str.to_string_lossy().into_owned()
213        };
214
215        Ok(RenderModelProperties {
216            vendor_id: properties.vendor_id,
217            model_name,
218            model_version: properties.model_version,
219            flags: properties.flags.into_raw(),
220        })
221    }
222
223
224    /// Loads render model data for a given model path
225    pub fn load_render_model(&self, model_path: &str) -> Result<Vec<u8>, XrResult> {
226        let load_fn = self.xr_load_render_model.ok_or(XrResult::ERROR_FUNCTION_UNSUPPORTED)?;
227        let get_properties_fn = self.xr_get_render_model_properties.ok_or(XrResult::ERROR_FUNCTION_UNSUPPORTED)?;
228        let string_to_path_fn = self.xr_string_to_path.ok_or(XrResult::ERROR_FUNCTION_UNSUPPORTED)?;
229
230        let c_string = CString::new(model_path).map_err(|_| XrResult::ERROR_VALIDATION_FAILURE)?;
231        let mut path = Path::from_raw(0);
232        let result = unsafe { string_to_path_fn(self.instance, c_string.as_ptr(), &mut path) };
233
234        if result != XrResult::SUCCESS {
235            return Err(result);
236        }
237
238        let mut properties = RenderModelPropertiesFB {
239            ty: StructureType::RENDER_MODEL_PROPERTIES_FB,
240            next: ptr::null_mut(),
241            vendor_id: 0,
242            model_name: [0; 64],
243            model_key: RenderModelKeyFB::from_raw(0),
244            model_version: 0,
245            flags: RenderModelFlagsFB::from_raw(0),
246        };
247
248        let mut cap_req = RenderModelCapabilitiesRequestFB {
249            ty: StructureType::RENDER_MODEL_CAPABILITIES_REQUEST_FB,
250            next: ptr::null_mut(),
251            flags: RenderModelFlagsFB::SUPPORTS_GLTF_2_0_SUBSET_2,
252
253        };
254
255        properties.next = &mut cap_req as *mut _ as *mut _;
256
257        let result = unsafe { get_properties_fn(self.session, path, &mut properties) };
258        if result != XrResult::SUCCESS && result != XrResult::RENDER_MODEL_UNAVAILABLE_FB && result != XrResult::SESSION_LOSS_PENDING {
259            return Err(result);
260        }
261
262        let model_key = properties.model_key;
263
264        let load_info =
265            RenderModelLoadInfoFB { ty: StructureType::RENDER_MODEL_LOAD_INFO_FB, next: ptr::null_mut(), model_key };
266
267        let mut buffer = RenderModelBufferFB {
268            ty: StructureType::RENDER_MODEL_BUFFER_FB,
269            next: ptr::null_mut(),
270            buffer_capacity_input: 0,
271            buffer_count_output: 0,
272            buffer: ptr::null_mut(),
273        };
274
275        let result = unsafe { load_fn(self.session, &load_info, &mut buffer) };
276        if result != XrResult::SUCCESS {
277            return Err(result);
278        }
279
280        let buffer_size = buffer.buffer_count_output;
281        if buffer_size == 0 {
282            return Ok(Vec::new());
283        }
284
285        let mut data_buffer = vec![0u8; buffer_size as usize];
286        buffer.buffer_capacity_input = buffer_size;
287        buffer.buffer = data_buffer.as_mut_ptr();
288
289        let result = unsafe { load_fn(self.session, &load_info, &mut buffer) };
290        if result != XrResult::SUCCESS {
291            return Err(result);
292        }
293
294        data_buffer.truncate(buffer.buffer_count_output as usize);
295
296        Ok(data_buffer)
297    }
298
299    /// Get cached controller model using specified path and hand, loading it if necessary
300    pub fn get_controller_model(
301        &mut self,
302        handed: Handed,
303        model_path: &str,
304    ) -> Result<&Model, Box<dyn std::error::Error>> {
305        let needs_loading = match handed {
306            Handed::Left => self.left_controller_data.is_none(),
307            Handed::Right => self.right_controller_data.is_none(),
308            Handed::Max => return Err("Invalid handed value: Max is not a valid controller".into()),
309        };
310
311        if needs_loading {
312            let data = self.load_render_model(model_path)?;
313            let model = Model::from_memory(format!("{model_path}.gltf"), &data, None)?;
314
315            if let Some(mut n) = model.get_nodes().get_root_node() {
316                let new_rotation = Quat::from_angles(0.0, 0.0, 0.0);
317                let transf = Matrix::t_r(Vec3::new(0.0, 0.0 * CM, 0.0 * CM), new_rotation);
318                n.local_transform(transf);
319            }
320
321            match handed {
322                Handed::Left => self.left_controller_data = Some(model),
323                Handed::Right => self.right_controller_data = Some(model),
324                Handed::Max => unreachable!(),
325            }
326        }
327
328        match handed {
329            Handed::Left => Ok(self.left_controller_data.as_ref().unwrap()),
330            Handed::Right => Ok(self.right_controller_data.as_ref().unwrap()),
331            Handed::Max => unreachable!(),
332        }
333    }
334
335    /// Loads and configures controller models for both hands using specified paths
336    pub fn setup_controller_models(&mut self, left_path: &str, right_path: &str, with_animation: bool) -> Result<(), XrResult> {
337        // Load and set right controller model using specified path
338        let with_log = self.with_log;
339        if let Ok(right_model) = self.get_controller_model(Handed::Right, right_path) {
340            Input::set_controller_model(Handed::Right, Some(right_model));
341            if with_log {
342                Log::info(format!("   Right controller model loaded and configured from path: {}", right_path));
343            }
344
345            // Launch animation 0 in Loop mode if with_animation is true
346            if with_animation {
347                right_model.get_anims().play_anim_idx(0, AnimMode::Loop);
348                if right_model.get_anims().get_count() > 1 { 
349                    Log::warn("⚠️ Right controller model has more than one animation, only the first will be played in loop"); 
350                }
351                if with_log {
352                    Log::info("✅ Right controller animation started");
353                }
354            }
355        } else {
356            Log::warn(format!("❌ Failed to load right controller model from path: {}", right_path));
357            return Err(XrResult::ERROR_RUNTIME_FAILURE);
358        }
359
360        // Load and set left controller model using specified path
361        if let Ok(left_model) = self.get_controller_model(Handed::Left, left_path) {
362            Input::set_controller_model(Handed::Left, Some(left_model));
363            if with_log {
364                Log::info(format!("   Left controller model loaded and configured from path: {}", left_path));
365            }
366
367            // Launch animation 0 in Loop mode if with_animation is true
368            if with_animation {
369                left_model.get_anims().play_anim_idx(0, AnimMode::Loop);
370                if with_log {
371                    Log::info("✅ Left controller animation started");
372                }
373            }
374        } else {
375            Log::warn(format!("❌ Failed to load left controller model from path: {}", left_path));
376            return Err(XrResult::ERROR_RUNTIME_FAILURE);
377        }
378
379        Ok(())
380    }
381
382    /// Disables controller models by setting them to None
383    pub fn disable_controller_models(&mut self) {
384        use crate::system::{Handed, Input};
385
386        Input::set_controller_model(Handed::Right, None);
387        Input::set_controller_model(Handed::Left, None);
388        self.left_controller_data = None;
389        self.right_controller_data = None;
390    }
391
392    /// Explores and logs information about all available render models
393    pub fn explore_render_models(&self) -> Result<(), XrResult> {
394        if let Ok(paths) = self.enumerate_render_model_paths() {
395            for path in paths {
396                Log::diag(format!("   Render model: <{}>", path));
397                match self.get_render_model_properties(&path) {
398                    Ok(properties) => {
399                        Log::diag(format!("     Model: {:?}", properties.model_name));
400                        Log::diag(format!("     Vendor ID: {}", properties.vendor_id));
401                        Log::diag(format!("     Model version: {}", properties.model_version));
402                        Log::diag(format!("     Model flags: 0x{:?}", properties.flags));
403                    }
404                    Err(e) => {
405                        Log::diag(format!("     No properties for model: {}: {:?}", path, e));
406                    }
407                }
408            }
409        }
410        Ok(())
411    }
412
413    /// Sets the animation time for a specific controller
414    ///
415    /// # Arguments
416    /// * `handed` - Which controller to modify (Left or Right)
417    /// * `time` - The animation time in seconds
418    #[allow(unused, dead_code)]
419    pub fn set_controller_anim_time(&mut self, handed: Handed, time: f32) {
420        match handed {
421            Handed::Left => {
422                if let Some(ref left_model) = self.left_controller_data {
423                    left_model.get_anims().anim_time(time);
424                } 
425            }
426            Handed::Right => {
427                if let Some(ref right_model) = self.right_controller_data {
428                    right_model.get_anims().anim_time(time);
429                } 
430            }
431            Handed::Max => {}
432        }
433    }
434}
435
436/// Convenience function to check if XR_FB_render_model extension is available
437pub fn is_fb_render_model_extension_available() -> bool {
438    Backend::xr_type() == BackendXRType::OpenXR && BackendOpenXR::ext_enabled(XR_FB_RENDER_MODEL_EXTENSION_NAME)
439}
440
441/// Event key for enabling/disabling controller drawing
442pub const DRAW_CONTROLLER: &str = "draw_controller";
443
444const LEFT_SHIFT: f32 = 0.04; // Left hand animation timing offset for synchronization
445
446/// IStepper implementation for XR_FB_render_model integration with StereoKit
447///
448/// This stepper provides controller model rendering and animations using the OpenXR XR_FB_render_model extension.
449/// You can configure the model paths for left and right controllers using the public properties or setter methods.
450///
451/// ### Events this stepper is listening to:
452/// * `DRAW_CONTROLLER` - Event that triggers when controller rendering is enabled ("true") or disabled ("false").
453///
454/// ### Examples
455/// ```
456/// # stereokit_rust::test_init_sk!(); // !!!! Get a proper way to initialize sk !!!!
457/// use stereokit_rust::{
458///     tools::xr_fb_render_model::{XrFbRenderModelStepper, is_fb_render_model_extension_available, DRAW_CONTROLLER},
459///     system::{Input, Handed},
460///     prelude::*,
461/// };
462///
463/// // Check if the extension is available before using the stepper
464/// if is_fb_render_model_extension_available() {
465///     let mut stepper = XrFbRenderModelStepper::default();
466///     
467///     // Optional: customize controller model paths
468///     stepper.left_controller_model_path = "/model_fb/controller/left".to_string();
469///     stepper.right_controller_model_path = "/model_fb/controller/right".to_string();
470///     
471///     // Add the stepper to StereoKit
472///     sk.send_event(StepperAction::add_default::<XrFbRenderModelStepper>("animate_controller"));
473///     
474///     // Enable controller rendering
475///     sk.send_event(StepperAction::event("animate_controller", DRAW_CONTROLLER, "true"));
476///     
477///     filename_scr = "screenshots/xr_fb_render_model.jpeg"; fov_scr = 45.0;
478///     test_steps!( // !!!! Get a proper main loop !!!!
479///         // The stepper will automatically render controllers with animations
480///         // based on input state (trigger, grip, etc.)
481///         if iter == number_of_steps / 2 {
482///             // Disable controller rendering halfway through
483///             sk.send_event(StepperAction::event("main", DRAW_CONTROLLER, "false"));
484///         }
485///     );
486/// }
487/// ```
488///
489/// # Animation System
490/// The stepper maps controller inputs to specific animation time codes:
491/// - **Stick directions**: 8 cardinal points (1.18-1.64 range)
492/// - **Trigger pressure**: Variable animation (0.6-0.66 range)
493/// - **Grip pressure**: Variable animation (0.82-0.88 range)
494/// - **Button combinations**: Discrete animations (0.18, 0.32, 0.46, 0.98)
495///
496/// When multiple inputs are active, the step rotation system cycles through
497/// available animations using the `animation_time_code` property.
498#[derive(IStepper)]
499pub struct XrFbRenderModelStepper {
500    id: StepperId,
501    sk_info: Option<Rc<RefCell<SkInfo>>>,
502    enabled: bool,
503    shutdown_completed: bool,
504
505    /// Path to the left controller's render model in the OpenXR runtime
506    /// Default: "/model_fb/controller/left" (Meta Quest controllers)
507    pub left_controller_model_path: String,
508    
509    /// Path to the right controller's render model in the OpenXR runtime  
510    /// Default: "/model_fb/controller/right" (Meta Quest controllers)
511    pub right_controller_model_path: String,
512
513    xr_render_model: Option<XrFbRenderModel>,
514    is_enabled: bool,
515
516    /// Animation time code for manual control and step rotation system
517    /// Used in animation_analyser for development and in set_animation for step cycling
518    pub animation_time_code: f32,
519    
520    /// Controls whether animations are executed in the draw method
521    /// When false, controllers will be rendered but remain static
522    pub with_animation: bool,
523}
524
525impl Default for XrFbRenderModelStepper {
526    fn default() -> Self {
527        Self {
528            id: "XrFbRenderModelStepper".to_string(),
529            sk_info: None,
530            enabled: true,
531            shutdown_completed: false,
532
533            xr_render_model: None,
534            is_enabled: false,
535            animation_time_code: 0.0,
536            with_animation: true,
537
538            // Default model paths for Meta Quest controllers
539            left_controller_model_path: "/model_fb/controller/left".to_string(),
540            right_controller_model_path: "/model_fb/controller/right".to_string(),
541        }
542    }
543}
544
545unsafe impl Send for XrFbRenderModelStepper {}
546
547impl XrFbRenderModelStepper {
548    /// Called from IStepper::initialize here you can abort the initialization by returning false
549    fn start(&mut self) -> bool {
550        // Initialize XR render model system
551        //Log::info("🔧 Initializing XR_FB_render_model...");
552        if !is_fb_render_model_extension_available() {
553            Log::err("⚠️ XR_FB_render_model extension not available");
554            return false; 
555        }
556        match XrFbRenderModel::new(false) {
557            Some(xr_model) => {
558                // Explore available models
559                // if let Err(e) = xr_model.explore_render_models() {
560                //     Log::warn(format!("❌ Failed to explore XR_FB_render_models: {:?}", e));
561                // }
562                self.xr_render_model = Some(xr_model);
563                true
564            }
565            None => {
566                Log::err("❌ XR_FB_render_model extension not available");
567                false 
568            }
569        }
570    }
571
572    /// Called from IStepper::step, here you can check the event report
573    fn check_event(&mut self, _id: &StepperId, key: &str, value: &str) {
574        if key == DRAW_CONTROLLER {
575            match value {
576                "true" => {
577                    if !self.is_enabled {
578                        self.is_enabled = true;
579
580                        // Load controller models if available
581                        if let Some(ref mut xr_model) = self.xr_render_model {
582                            // Setup controller models for both hands using configured paths
583                            if let Err(e) = xr_model.setup_controller_models(
584                                &self.left_controller_model_path,
585                                &self.right_controller_model_path,
586                                self.with_animation,
587                            ) {
588                                Log::warn(format!("❌ DRAW_CONTROLLER Failed to setup controller models: {:?}", e));
589                            } else {
590                                Log::info("DRAW_CONTROLLER `true`: Controller models setup completed");
591                            }
592                        } else {
593                            Log::warn("❌ DRAW_CONTROLLER `true` error: XR_FB_render_model not initialized");
594                        }
595                    }
596                }
597                _=> {
598                    self.is_enabled = false;
599
600                    // Disable controller models and animations
601                    if let Some(ref mut xr_model) = self.xr_render_model {
602                        xr_model.disable_controller_models();
603                        Log::info("DRAW_CONTROLLER `false`: Controller drawing disabled");
604                    } else {
605                        Log::warn("❌ DRAW_CONTROLLER `false` error: XR_FB_render_model not initialized");
606                    }
607                }
608
609            }
610        }
611    }
612
613    /// Set animation based on controller button states with step rotation system
614    ///
615    /// This function maps controller input states to specific animation times:
616    /// - Stick directions (8 cardinal points): 1.18-1.64 range
617    /// - Trigger pressure: 0.6-0.66 range (variable based on pressure)  
618    /// - Grip pressure: 0.82-0.88 range (variable based on pressure)
619    /// - Button combinations: 0.18, 0.32, 0.46, 0.98
620    /// 
621    /// Uses a step rotation system via animation_time_code to cycle through
622    /// multiple simultaneous animations when several inputs are active.
623    ///
624    /// # Arguments
625    /// * `handed` - Which controller to animate (Left or Right)
626    /// * `controller` - Reference to the controller input state
627    /// * `shift` - Time shift to apply (LEFT_SHIFT for left hand, 0.0 for right)
628    fn set_animation(&mut self, handed: Handed, controller: &crate::system::Controller, shift: f32) {
629        let mut animation_times = vec![];
630
631        if handed == Handed::Left {
632            self.animation_time_code = (self.animation_time_code.max(0.0) + 1.0) % 4.0;
633        }
634
635        if let Some(ref mut xr_render_model) = self.xr_render_model {
636            // Check stick direction first (8 cardinal points)
637            let stick_threshold = 0.25; // Threshold for stick activation
638            if controller.stick.magnitude() > stick_threshold {
639                let x = controller.stick.x;
640                let y = controller.stick.y;
641
642                // Map to 8 cardinal directions based on x/y dominance
643                let animation_time = 
644                    // Horizontal directions dominate
645                    if x > 0.3 {
646                        // right side
647                        if y > 0.3 {
648                            1.58  // Right-up direction
649                        } else if y < -0.3 {
650                            1.64  // Right-down direction
651                        } else {
652                            1.38  // Pure right direction
653                        } 
654                    } else if x < -0.3 {
655                        // left side
656                        if y > 0.3 {
657                            1.52  // Left-up direction
658                        } else if y < -0.3 {
659                            1.46  // Left-down direction
660                        } else {
661                            1.32  // Pure left direction
662                        }
663                    } else {
664                        // Center (vertical movements)
665                        if y > 0.3 {
666                            1.18  // Pure up direction
667                        } else if y < -0.3 {
668                            1.26  // Pure down direction
669                        } else {
670                            -1.0  // Center position (no movement)
671                        }
672                    };
673                if animation_time > 0.0 { animation_times.push(animation_time); }
674            }
675            // Button-based animations with different combinations
676            if controller.trigger > 0.1 {
677                let animation_time = 0.6 + 0.06 * controller.trigger; // Variable trigger animation
678                animation_times.push(animation_time);
679            }
680            if controller.grip > 0.1 {
681                let animation_time = 0.82 + 0.06 * controller.grip; // Variable grip animation
682                animation_times.push(animation_time);
683            }
684
685            // Discrete button animations
686            let mut animation_time= -1.0;
687            if controller.is_x1_pressed() && controller.is_x2_pressed() {
688                animation_time = 0.46; // Both X/Y or A/B buttons pressed
689            } else if controller.is_x1_pressed() {
690                animation_time = 0.18; // Single X or A button pressed
691            } else if controller.is_x2_pressed() {
692                animation_time = 0.32; // Single Y or B button pressed
693            } else if controller.is_stick_clicked() {
694                animation_time = -1.0; // Stick click (no animation found)
695            } else if Input::get_controller_menu_button().is_active() {
696                animation_time = 0.98; // System/menu button pressed
697            }
698
699            if animation_time > 0.0 { animation_times.push(animation_time); }
700
701            if animation_times.is_empty() {
702                // No active inputs detected, use default idle animation
703                xr_render_model.set_controller_anim_time(handed, 4.4);
704            } else {
705                // Multiple animations available - use step rotation to cycle through them
706                let step_sel = self.animation_time_code % animation_times.len() as f32;
707                for (i, animation_time) in animation_times.into_iter().enumerate() {
708                    if i as f32 == step_sel{
709                        xr_render_model.set_controller_anim_time(handed, animation_time + shift);
710                        break;
711                    }
712                }
713            }
714        }
715    }
716
717    /// Called from IStepper::step, after check_event here you can draw your UI and scene
718    fn draw(&mut self, _token: &MainThreadToken) {
719        if !self.is_enabled {
720            return;
721        }
722
723        // Animation analysis helper (disabled by default)
724        if false {
725            self.animation_analyser(_token);
726        } else if self.with_animation {
727            // Execute controller animations based on current input states
728            if self.xr_render_model.is_some() {
729                // Apply animations to both controllers independently
730                let left_controller = Input::controller(Handed::Left);
731                self.set_animation(Handed::Left, &left_controller, LEFT_SHIFT);
732
733                let right_controller = Input::controller(Handed::Right);
734                self.set_animation(Handed::Right, &right_controller, 0.0);
735            }
736        }
737    }
738
739    /// Animation analysis helper for finding correct animation time codes
740    ///
741    /// This is a development tool that displays the current animation time code
742    /// in 3D space and allows manual advancement through animations using joystick clicks.
743    /// Useful for discovering and documenting animation time codes for different poses.
744    ///
745    /// # Arguments
746    /// * `_token` - Main thread token for safe UI operations
747    fn animation_analyser(&mut self, _token: &MainThreadToken) {
748        // Advance animation time on joystick button press
749        if Input::controller(Handed::Right).stick_click.is_just_active()
750            || Input::controller(Handed::Left).stick_click.is_just_active()
751        {
752            self.animation_time_code += 0.02;
753
754            // Reset to 0 when reaching 6 seconds maximum
755            if self.animation_time_code >= 6.0 {
756                self.animation_time_code = 0.0;
757            }
758
759            Log::diag(format!("Animation time code: {:.1}s", self.animation_time_code));
760        }
761
762        // Display current animation time code in 3D space
763        use crate::maths::{Matrix, Quat, Vec3};
764        use crate::system::Text;
765
766        let text_content = format!("Animation Time: {:.2}s\nPress joystick to advance", self.animation_time_code);
767        let position = Vec3::new(0.0, 1.5, -0.8);
768        let rotation = Quat::from_angles(0.0, 180.0, 0.0); // Rotated toward Z-axis
769        let transform = Matrix::t_r(position, rotation);
770
771        Text::add_at(_token, &text_content, transform, None, None, None, None, None, None, None);
772
773        // Apply current time code to both controllers for analysis
774        if let Some(ref mut xr_render_model) = self.xr_render_model {
775            xr_render_model.set_controller_anim_time(Handed::Left, self.animation_time_code);
776            xr_render_model.set_controller_anim_time(Handed::Right, self.animation_time_code);
777        }
778    }
779
780    /// Called from IStepper::shutdown(triggering) then IStepper::shutdown_done(waiting for true response),
781    /// here you can close your resources
782    fn close(&mut self, triggering: bool) -> bool {
783        if triggering {
784            // Disable controller models
785            if let Some(ref mut xr_model) = self.xr_render_model {
786                xr_model.disable_controller_models();
787            }
788
789            self.xr_render_model = None;
790            self.shutdown_completed = true;
791            true
792        } else {
793            self.shutdown_completed
794        }
795    }
796}