Skip to main content

lumen_engine/node/processing/
time_remap.rs

1use crate::node::{NodeId, NodeProperty, PortRef};
2
3use crate::gpu::{
4    BoundFrame, CompiledOutput, FrameBindContext, FrameBinding, GpuCompileNode, GpuFrameBindNode,
5};
6
7/// Evaluates a raster input at another frame.
8#[derive(Debug, Clone, lumen_macros::Node)]
9#[node(kind = "time_remap", name = "Time Remap", category = "processing")]
10pub struct TimeRemap {
11    pub id: NodeId,
12    /// Source frame to sample.
13    #[property(kind = "float", min = 0, step = 1)]
14    pub frame: NodeProperty,
15    /// Enables looping between the configured loop bounds.
16    #[property(kind = "bool")]
17    pub loop_enabled: NodeProperty,
18    /// First frame in the loop range.
19    #[property(kind = "int", min = 0, step = 1)]
20    pub loop_start: NodeProperty,
21    /// Last frame in the loop range.
22    #[property(kind = "int", min = 0, step = 1)]
23    pub loop_end: NodeProperty,
24    #[input()]
25    pub source: PortRef,
26}
27
28#[derive(Debug, Clone, Copy)]
29pub struct TimeRemapSettings {
30    pub frame: f64,
31    pub loop_enabled: bool,
32    pub loop_start: i64,
33    pub loop_end: i64,
34}
35
36impl Default for TimeRemap {
37    fn default() -> Self {
38        Self {
39            id: NodeId::new(0),
40            frame: NodeProperty::Float(0.0),
41            loop_enabled: NodeProperty::Bool(false),
42            loop_start: NodeProperty::Int(0),
43            loop_end: NodeProperty::Int(0),
44            source: PortRef::empty(),
45        }
46    }
47}
48
49pub fn remap_frame(settings: TimeRemapSettings) -> u32 {
50    let mut frame = settings.frame.max(0.0).floor() as i64;
51    if settings.loop_enabled && settings.loop_end > settings.loop_start {
52        let span = settings.loop_end - settings.loop_start;
53        frame = settings.loop_start + (frame - settings.loop_start).rem_euclid(span);
54    }
55    frame.max(0) as u32
56}
57
58pub fn resolve_settings(
59    node_id: NodeId,
60    frame: &NodeProperty,
61    loop_enabled: &NodeProperty,
62    loop_start: &NodeProperty,
63    loop_end: &NodeProperty,
64    ctx: &crate::expr::ExpressionContext<'_>,
65) -> crate::Result<TimeRemapSettings> {
66    Ok(TimeRemapSettings {
67        frame: frame.resolve_float(node_id, "frame", ctx)?,
68        loop_enabled: loop_enabled.resolve_bool(node_id, "loop_enabled", ctx)?,
69        loop_start: loop_start.resolve_int(node_id, "loop_start", ctx)?,
70        loop_end: loop_end.resolve_int(node_id, "loop_end", ctx)?,
71    })
72}
73
74impl GpuCompileNode for TimeRemap {
75    fn compile_gpu(
76        &self,
77        ctx: &mut crate::gpu::CompileContext<'_>,
78        port: &PortRef,
79    ) -> crate::Result<CompiledOutput> {
80        if port.port != "output" {
81            return Err(ctx.missing_output(self.id, &port.port));
82        }
83        ctx.push_frame_binding(FrameBinding::TimeRemap {
84            node_id: self.id,
85            frame: self.frame.clone(),
86            loop_enabled: self.loop_enabled.clone(),
87            loop_start: self.loop_start.clone(),
88            loop_end: self.loop_end.clone(),
89        });
90        let target_frame = remap_frame(resolve_settings(
91            self.id,
92            &self.frame,
93            &self.loop_enabled,
94            &self.loop_start,
95            &self.loop_end,
96            &ctx.expr_context(self.id, "frame"),
97        )?);
98        ctx.with_frame_context(target_frame, |ctx| ctx.compile_port(&self.source))
99    }
100}
101
102impl GpuFrameBindNode for TimeRemap {
103    fn bind_gpu_frame(
104        &self,
105        ctx: &FrameBindContext<'_>,
106        binding: &FrameBinding,
107        _bound: &mut BoundFrame,
108    ) -> crate::Result<()> {
109        let FrameBinding::TimeRemap {
110            node_id,
111            frame,
112            loop_enabled,
113            loop_start,
114            loop_end,
115        } = binding
116        else {
117            return Ok(());
118        };
119        let _ = remap_frame(resolve_settings(
120            *node_id,
121            frame,
122            loop_enabled,
123            loop_start,
124            loop_end,
125            &ctx.expr_context(*node_id, "frame"),
126        )?);
127        Ok(())
128    }
129}