bevy_animation_graph/nodes/
loop_node.rs1use crate::core::animation_graph::{PinMap, TimeUpdate};
2use crate::core::animation_node::{NodeLike, ReflectNodeLike};
3use crate::core::errors::GraphError;
4use crate::core::prelude::DataSpec;
5use crate::interpolation::prelude::InterpolateLinear;
6use crate::prelude::{PassContext, SpecContext};
7use bevy::prelude::*;
8
9#[derive(Reflect, Clone, Debug, Default)]
10#[reflect(Default, NodeLike)]
11pub struct LoopNode {
12 pub interpolation_period: f32,
13}
14
15impl LoopNode {
16 pub const IN_POSE: &'static str = "pose";
17 pub const IN_TIME: &'static str = "time";
18 pub const OUT_POSE: &'static str = "pose";
19
20 pub fn new(interpolation_period: f32) -> Self {
21 Self {
22 interpolation_period,
23 }
24 }
25}
26
27impl NodeLike for LoopNode {
28 fn duration(&self, mut ctx: PassContext) -> Result<(), GraphError> {
29 ctx.set_duration_fwd(None);
30 Ok(())
31 }
32
33 fn update(&self, mut ctx: PassContext) -> Result<(), GraphError> {
34 let input = ctx.time_update_fwd()?;
35 let duration = ctx.duration_back(Self::IN_TIME)?;
36
37 let Some(duration) = duration else {
38 ctx.set_time_update_back(Self::IN_TIME, input);
39 let pose_back = ctx.data_back(Self::IN_POSE)?.into_pose()?;
40 ctx.set_time(pose_back.timestamp);
41 ctx.set_data_fwd(Self::OUT_POSE, pose_back);
42
43 return Ok(());
44 };
45
46 let full_duration = duration + self.interpolation_period;
47
48 let prev_time = ctx.prev_time();
49
50 let (curr_time, t, fw_upd) = match input {
51 TimeUpdate::Delta(dt) => {
52 let curr_time = prev_time + dt;
53 let t = curr_time.rem_euclid(full_duration);
54
55 let fw_upd =
56 if prev_time.div_euclid(full_duration) != curr_time.div_euclid(full_duration) {
57 TimeUpdate::Absolute(t)
58 } else {
59 TimeUpdate::Delta(dt)
60 };
61
62 (curr_time, t, fw_upd)
63 }
64 TimeUpdate::Absolute(curr_time) => {
65 let t = curr_time.rem_euclid(full_duration);
66 (curr_time, t, TimeUpdate::Absolute(t))
67 }
68 TimeUpdate::PercentOfEvent { .. } => {
69 todo!("we probably want to return here and issue a warning")
70 }
71 };
72
73 ctx.set_time_update_back(Self::IN_TIME, fw_upd);
74 let mut pose = ctx.data_back(Self::IN_POSE)?.into_pose()?;
75
76 if t > duration && t < full_duration {
77 let mut ctx_temp = ctx.with_temp(true);
78 ctx_temp.set_time_update_back(Self::IN_TIME, TimeUpdate::Absolute(0.));
79 let start_pose = ctx_temp.data_back(Self::IN_POSE)?.into_pose()?;
80 let old_time = pose.timestamp;
83 let alpha = (t - duration) / self.interpolation_period;
84 pose = pose.interpolate_linear(&start_pose, alpha);
85 pose.timestamp = old_time;
86 }
87
88 let t_extra = curr_time.div_euclid(full_duration) * full_duration;
89 pose.timestamp += t_extra;
90 ctx.set_time(pose.timestamp);
91 ctx.set_data_fwd(Self::OUT_POSE, pose);
92
93 Ok(())
94 }
95
96 fn data_input_spec(&self, _ctx: SpecContext) -> PinMap<DataSpec> {
97 [(Self::IN_POSE.into(), DataSpec::Pose)].into()
98 }
99
100 fn data_output_spec(&self, _ctx: SpecContext) -> PinMap<DataSpec> {
101 [(Self::OUT_POSE.into(), DataSpec::Pose)].into()
102 }
103
104 fn time_input_spec(&self, _: SpecContext) -> PinMap<()> {
105 [(Self::IN_TIME.into(), ())].into()
106 }
107
108 fn time_output_spec(&self, _: SpecContext) -> Option<()> {
109 Some(())
110 }
111
112 fn display_name(&self) -> String {
113 "🔄 Loop".into()
114 }
115}