Skip to main content

sim_lib_view_spatial/
predict.rs

1//! Prediction clamp for stale Viture pose reprojection.
2
3use std::{cell::RefCell, rc::Rc};
4
5use sim_kernel::{Expr, Result};
6use sim_lib_view_device::{DeviceProfile, EncodedScene, LocalAdapter};
7
8use crate::{PoseView, Reprojector};
9
10/// Returns a pose whose prediction lead is bounded by `max_predict_ms`.
11pub fn clamp_predicted(pose: &PoseView, max_predict_ms: u64) -> PoseView {
12    let mut clamped = pose.clone();
13    clamped.predict_ns = clamped
14        .predict_ns
15        .min(max_predict_ms.saturating_mul(1_000_000));
16    clamped
17}
18
19/// Clamp-aware wrapper for Viture reprojection inside `StalePolicy::Predict`.
20///
21/// Fresh or merely stale poses reproject through the inner [`Reprojector`] with
22/// bounded prediction. A pose whose own age exceeds the prediction clamp returns
23/// the last good frame, so lost tracking cannot extrapolate into an unbounded
24/// warp.
25#[derive(Debug)]
26pub struct ClampedReprojector {
27    inner: Reprojector,
28    last_good: RefCell<Option<Rc<Expr>>>,
29}
30
31impl ClampedReprojector {
32    /// Builds a clamp-aware reprojector.
33    pub fn new(max_predict_ms: u64) -> Self {
34        Self {
35            inner: Reprojector::new(max_predict_ms),
36            last_good: RefCell::new(None),
37        }
38    }
39
40    /// Returns the maximum prediction lead in milliseconds.
41    pub fn max_predict_ms(&self) -> u64 {
42        self.inner.max_predict_ms
43    }
44
45    /// Returns the inner reprojector.
46    pub fn inner(&self) -> &Reprojector {
47        &self.inner
48    }
49}
50
51impl LocalAdapter for ClampedReprojector {
52    type State = PoseView;
53
54    fn adapt(
55        &self,
56        scene: &EncodedScene,
57        state: &Self::State,
58        profile: &DeviceProfile,
59    ) -> Result<Rc<Expr>> {
60        if pose_exceeds_clamp(state, self.max_predict_ms()) {
61            return Ok(self
62                .last_good
63                .borrow()
64                .clone()
65                .unwrap_or_else(|| scene.shared()));
66        }
67        let clamped = clamp_predicted(state, self.max_predict_ms());
68        let out = self.inner.adapt(scene, &clamped, profile)?;
69        self.last_good.replace(Some(Rc::clone(&out)));
70        Ok(out)
71    }
72}
73
74pub(crate) fn pose_exceeds_clamp(pose: &PoseView, max_predict_ms: u64) -> bool {
75    pose.age_ms > max_predict_ms
76}