Skip to main content

animation/
animation.rs

1use primback::prelude::*;
2
3struct AnimationApp {
4    linear_pos: Animated<Vec3>,
5    exp_pos: Animated<Vec3>,
6    spring_pos: Animated<Vec3>,
7    target_pos: Vec3,
8    timer: f32,
9    rng: Random,
10}
11
12impl PrimbackApp for AnimationApp {
13    fn init(&mut self, update_ctx: &mut UpdateContext) {
14        let camera = Camera::look_at(vec3(0.0, 4.0, 8.0), vec3(0.0, 1.0, 0.0), Vec3::Y);
15        update_ctx.set_camera(camera);
16
17        let light = Light {
18            direction: vec3(0.5, -1.0, -0.5).normalize(),
19            color: Color::new(1.0, 1.0, 1.0),
20            ambient: Color::new(0.3, 0.3, 0.35),
21        };
22        update_ctx.set_light(light);
23    }
24
25    fn update(&mut self, update_ctx: &mut UpdateContext) {
26        if update_ctx.is_key_pressed(KeyCode::Escape) {
27            update_ctx.exit();
28        }
29
30        let dt = update_ctx.delta_time();
31        self.timer += dt;
32
33        // Change target position every 2.5 seconds
34        if self.timer >= 2.5 {
35            self.timer = 0.0;
36            // Generate a random position in the XZ plane with Y=1.0
37            let x = self.rng.range(-4.0, 4.0);
38            let z = self.rng.range(-2.0, 2.0);
39            self.target_pos = vec3(x, 1.0, z);
40
41            // Update targets of animated positions
42            self.linear_pos.set_target(self.target_pos);
43            self.exp_pos.set_target(self.target_pos);
44            self.spring_pos.set_target(self.target_pos);
45
46            // Play synth click sound
47            update_ctx.play_synth(Synth::click());
48        }
49
50        // Update the animated states
51        self.linear_pos.update(dt);
52        self.exp_pos.update(dt);
53        self.spring_pos.update(dt);
54    }
55
56    fn draw(&mut self, draw_ctx: &mut DrawContext) {
57        draw_ctx.clear(Color::new(0.07, 0.07, 0.09));
58
59        // Draw flat dark ground
60        draw_ctx.draw_shape(
61            Shape::plane(),
62            Transform::new_position(vec3(0.0, 0.0, 0.0)).scale(vec3(12.0, 1.0, 8.0)),
63            Color::new(0.12, 0.12, 0.14),
64        );
65
66        // Draw Target Position indicator (small red sphere)
67        draw_ctx.draw_shape(
68            Shape::sphere().radius(0.15),
69            Transform::new_position(self.target_pos),
70            Color::new(0.9, 0.1, 0.1),
71        );
72
73        // Helper to draw connecting lines from shape's default start positions (approximate columns)
74        // so users can visually see the movement paths
75
76        // 1. Draw Linear sphere (Magenta)
77        let linear_val = self.linear_pos.current();
78        draw_ctx.draw_shape(
79            Shape::sphere().radius(0.4),
80            Transform::new_position(vec3(linear_val.x, linear_val.y + 0.5, linear_val.z)),
81            Color::new(0.8, 0.2, 0.8),
82        );
83        // Draw 3D text label above the linear sphere (using screen projections!)
84        if let Some(screen_pos) = draw_ctx.camera().world_to_screen(
85            vec3(linear_val.x, linear_val.y + 1.2, linear_val.z),
86            draw_ctx.render_size(),
87        ) {
88            let label = Text::new("Linear").scale(1.0);
89            let size = draw_ctx.measure_text(&label);
90            draw_ctx.draw_text(
91                label,
92                RectTransform::new_position(screen_pos - vec2(size.x * 0.5, 0.0)),
93                Color::new(0.8, 0.2, 0.8),
94            );
95        }
96
97        // 2. Draw Exp sphere (Yellow/Gold)
98        let exp_val = self.exp_pos.current();
99        draw_ctx.draw_shape(
100            Shape::sphere().radius(0.4),
101            Transform::new_position(vec3(exp_val.x, exp_val.y, exp_val.z)),
102            Color::new(0.9, 0.7, 0.1),
103        );
104        if let Some(screen_pos) = draw_ctx.camera().world_to_screen(
105            vec3(exp_val.x, exp_val.y + 0.7, exp_val.z),
106            draw_ctx.render_size(),
107        ) {
108            let label = Text::new("Exp").scale(1.0);
109            let size = draw_ctx.measure_text(&label);
110            draw_ctx.draw_text(
111                label,
112                RectTransform::new_position(screen_pos - vec2(size.x * 0.5, 0.0)),
113                Color::new(0.9, 0.7, 0.1),
114            );
115        }
116
117        // 3. Draw Spring sphere (Cyan)
118        let spring_val = self.spring_pos.current();
119        draw_ctx.draw_shape(
120            Shape::sphere().radius(0.4),
121            Transform::new_position(vec3(spring_val.x, spring_val.y - 0.5, spring_val.z)),
122            Color::new(0.1, 0.7, 0.8),
123        );
124        if let Some(screen_pos) = draw_ctx.camera().world_to_screen(
125            vec3(spring_val.x, spring_val.y + 0.2, spring_val.z),
126            draw_ctx.render_size(),
127        ) {
128            let label = Text::new("Spring").scale(1.0);
129            let size = draw_ctx.measure_text(&label);
130            draw_ctx.draw_text(
131                label,
132                RectTransform::new_position(screen_pos - vec2(size.x * 0.5, 0.0)),
133                Color::new(0.1, 0.7, 0.8),
134            );
135        }
136
137        // 2D Overlay
138        draw_ctx.draw_text(
139            Text::new("Interpolation Comparison Demo").scale(1.0),
140            RectTransform::new_position(vec2(15.0, 15.0)),
141            Color::WHITE,
142        );
143        draw_ctx.draw_text(
144            Text::new("Comparing Linear vs Exponential vs Spring dynamics").scale(1.0),
145            RectTransform::new_position(vec2(15.0, 35.0)),
146            Color::new(0.6, 0.6, 0.7),
147        );
148    }
149}
150
151fn main() {
152    let config = PrimbackConfig {
153        window_title: "Primback - Animation Demo".to_string(),
154        ..Default::default()
155    };
156
157    let start_pos = vec3(-3.0, 1.0, 0.0);
158
159    // Initialize the Animated positions with different modes
160    let linear_pos = Animated::new(start_pos, AnimationMode::linear().speed(3.0));
161    let exp_pos = Animated::new(start_pos, AnimationMode::exp().speed(4.0));
162    let spring_pos = Animated::new(
163        start_pos,
164        AnimationMode::spring().stiffness(40.0).damping(5.0),
165    );
166
167    Primback::run(
168        config,
169        AnimationApp {
170            linear_pos,
171            exp_pos,
172            spring_pos,
173            target_pos: start_pos,
174            timer: 2.0, // Trigger immediate target switch after startup
175            rng: Random::new_random(),
176        },
177    );
178}