santa/
animation.rs

1use rand::{Rng, SeedableRng};
2use rand_xorshift::XorShiftRng;
3
4use geom::{Distance, Duration, PolyLine, Pt2D, Time};
5use widgetry::{Color, Drawable, EventCtx, GeomBatch, GfxCtx, RewriteColor};
6
7pub struct Animator {
8    active: Vec<Animation>,
9    draw_mapspace: Drawable,
10    draw_screenspace: Option<Drawable>,
11}
12
13struct Animation {
14    start: Time,
15    end: Time,
16    effect: Effect,
17    screenspace: bool,
18}
19
20pub enum Effect {
21    Scale {
22        orig: GeomBatch,
23        center: Pt2D,
24        lerp_scale: (f64, f64),
25    },
26    FollowPath {
27        color: Color,
28        width: Distance,
29        pl: PolyLine,
30    },
31    Flash {
32        orig: GeomBatch,
33        alpha_scale: (f32, f32),
34        cycles: usize,
35    },
36}
37
38impl Animator {
39    pub fn new(ctx: &EventCtx) -> Animator {
40        Animator {
41            active: Vec::new(),
42            draw_mapspace: Drawable::empty(ctx),
43            draw_screenspace: None,
44        }
45    }
46
47    /// Pass in a future value for `now` to schedule a delayed effect
48    pub fn add(&mut self, now: Time, duration: Duration, effect: Effect) {
49        self.active.push(Animation {
50            start: now,
51            end: now + duration,
52            effect,
53            screenspace: false,
54        });
55    }
56
57    pub fn add_screenspace(&mut self, now: Time, duration: Duration, effect: Effect) {
58        self.active.push(Animation {
59            start: now,
60            end: now + duration,
61            effect,
62            screenspace: true,
63        });
64    }
65
66    pub fn event(&mut self, ctx: &mut EventCtx, now: Time) {
67        if self.active.is_empty() {
68            return;
69        }
70        let mut mapspace = GeomBatch::new();
71        let mut screenspace = GeomBatch::new();
72        self.active.retain(|anim| {
73            let pct = (now - anim.start) / (anim.end - anim.start);
74            if pct < 0.0 {
75                // Hasn't started yet
76                true
77            } else if pct > 1.0 {
78                false
79            } else {
80                if anim.screenspace {
81                    anim.effect.render(pct, &mut screenspace);
82                } else {
83                    anim.effect.render(pct, &mut mapspace);
84                }
85                true
86            }
87        });
88        self.draw_mapspace = ctx.upload(mapspace);
89        if screenspace.is_empty() {
90            self.draw_screenspace = None;
91        } else {
92            self.draw_screenspace = Some(ctx.upload(screenspace));
93        }
94    }
95
96    pub fn draw(&self, g: &mut GfxCtx) {
97        g.redraw(&self.draw_mapspace);
98        if let Some(ref d) = self.draw_screenspace {
99            g.fork_screenspace();
100            g.redraw(d);
101            g.unfork();
102        }
103    }
104
105    pub fn is_done(&self) -> bool {
106        self.active.is_empty()
107    }
108}
109
110impl Effect {
111    fn render(&self, pct: f64, batch: &mut GeomBatch) {
112        match self {
113            Effect::Scale {
114                ref orig,
115                center,
116                lerp_scale,
117            } => {
118                let scale = lerp_scale.0 + pct * (lerp_scale.1 - lerp_scale.0);
119                batch.append(orig.clone().scale(scale).centered_on(*center));
120            }
121            Effect::FollowPath {
122                color,
123                width,
124                ref pl,
125            } => {
126                if let Ok(pl) = pl.maybe_exact_slice(Distance::ZERO, pct * pl.length()) {
127                    batch.push(*color, pl.make_polygons(*width));
128                }
129            }
130            Effect::Flash {
131                ref orig,
132                alpha_scale,
133                cycles,
134            } => {
135                // -1 to 1
136                let shift = (pct * (*cycles as f64) * (2.0 * std::f64::consts::PI)).sin() as f32;
137                let midpt = (alpha_scale.0 + alpha_scale.1) / 2.0;
138                let half_range = (alpha_scale.1 - alpha_scale.0) / 2.0;
139                let alpha = midpt + shift * half_range;
140
141                batch.append(orig.clone().color(RewriteColor::ChangeAlpha(alpha)));
142            }
143        }
144    }
145}
146
147pub struct SnowEffect {
148    rng: XorShiftRng,
149    flakes: Vec<Snowflake>,
150
151    draw: Drawable,
152}
153
154struct Snowflake {
155    start: Time,
156    initial_pos: Pt2D,
157    fall_speed: f64,
158    swoop_period: f64,
159    max_swoop: f64,
160}
161
162impl Snowflake {
163    fn pos(&self, time: Time) -> Pt2D {
164        let arg =
165            (2.0 * std::f64::consts::PI) * (time - self.start).inner_seconds() / self.swoop_period;
166        let x = self.initial_pos.x() + self.max_swoop * arg.cos();
167        let y = self.initial_pos.y() + self.fall_speed * (time - self.start).inner_seconds();
168        Pt2D::new(x, y)
169    }
170}
171
172impl SnowEffect {
173    pub fn new(ctx: &mut EventCtx) -> SnowEffect {
174        let mut snow = SnowEffect {
175            rng: XorShiftRng::seed_from_u64(42),
176            flakes: Vec::new(),
177            draw: Drawable::empty(ctx),
178        };
179
180        let now = Time::START_OF_DAY;
181        // TODO Amp back up after fixing slow performance in debug mode
182        for _ in 0..20 {
183            let initial_pos = Pt2D::new(
184                snow.rng.gen_range(0.0..ctx.canvas.window_width),
185                snow.rng.gen_range(0.0..ctx.canvas.window_height),
186            );
187            let flake = snow.spawn_new(now, initial_pos);
188            snow.flakes.push(flake);
189        }
190        snow.event(ctx, now);
191
192        snow
193    }
194
195    fn spawn_new(&mut self, now: Time, initial_pos: Pt2D) -> Snowflake {
196        Snowflake {
197            start: now,
198            initial_pos,
199            // Pixels per second
200            // TODO It'd be neat to speed this up as time runs out
201            fall_speed: self.rng.gen_range(150.0..300.0),
202            swoop_period: self.rng.gen_range(1.0..5.0),
203            // Pixels
204            max_swoop: self.rng.gen_range(0.0..50.0),
205        }
206    }
207
208    pub fn event(&mut self, ctx: &mut EventCtx, now: Time) {
209        let shape = GeomBatch::load_svg(ctx, "system/assets/map/snowflake.svg").scale(0.1);
210
211        let mut batch = GeomBatch::new();
212        let prev_flakes = std::mem::take(&mut self.flakes);
213        let mut new_flakes = Vec::new();
214        for flake in prev_flakes {
215            let pt = flake.pos(now);
216            if pt.y() > ctx.canvas.window_height {
217                let initial_pos = Pt2D::new(self.rng.gen_range(0.0..ctx.canvas.window_width), 0.0);
218                new_flakes.push(self.spawn_new(now, initial_pos));
219            } else {
220                batch.append(shape.clone().translate(pt.x(), pt.y()));
221                new_flakes.push(flake);
222            }
223        }
224        self.flakes = new_flakes;
225        self.draw = ctx.upload(batch);
226    }
227
228    pub fn draw(&self, g: &mut GfxCtx) {
229        g.fork_screenspace();
230        g.redraw(&self.draw);
231        g.unfork();
232    }
233}