lumen_engine/node/processing/
time_remap.rs1use crate::node::{NodeId, NodeProperty, PortRef};
2
3use crate::gpu::{
4 BoundFrame, CompiledOutput, FrameBindContext, FrameBinding, GpuCompileNode, GpuFrameBindNode,
5};
6
7#[derive(Debug, Clone, lumen_macros::Node)]
9#[node(kind = "time_remap", name = "Time Remap", category = "processing")]
10pub struct TimeRemap {
11 pub id: NodeId,
12 #[property(kind = "float", min = 0, step = 1)]
14 pub frame: NodeProperty,
15 #[property(kind = "bool")]
17 pub loop_enabled: NodeProperty,
18 #[property(kind = "int", min = 0, step = 1)]
20 pub loop_start: NodeProperty,
21 #[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}