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 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 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 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 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 fall_speed: self.rng.gen_range(150.0..300.0),
202 swoop_period: self.rng.gen_range(1.0..5.0),
203 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}