comfy/trail.rs
1use crate::*;
2
3#[derive(Clone, Debug)]
4pub struct Trail {
5 pub positions: Vec<Vec2>,
6 pub last_vertex_at: Vec2,
7
8 pub z_index: i32,
9
10 pub is_enabled: bool,
11
12 pub trail_length: f32,
13 pub width: f32,
14 pub color_start: Color,
15 pub color_end: Color,
16
17 pub max_vertices: usize,
18 pub fade_start_distance: f32,
19 pub fade_end_distance: f32,
20 pub width_curve: Option<Curve>,
21 pub color_curve: Option<ColorCurve>,
22 pub texture: Option<TextureHandle>,
23 pub blend_mode: BlendMode,
24}
25
26impl Trail {
27 pub fn new(
28 width: f32,
29 trail_length: f32,
30 z_index: i32,
31 color_start: Color,
32 color_end: Color,
33 max_vertices: usize,
34 fade_start_distance: f32,
35 fade_end_distance: f32,
36 // width_curve: Option<Curve>,
37 color_curve: Option<ColorCurve>,
38 texture: Option<TextureHandle>,
39 blend_mode: BlendMode,
40 ) -> Self {
41 Self {
42 positions: vec![],
43 last_vertex_at: Vec2::ZERO,
44
45 z_index,
46
47 is_enabled: true,
48
49 trail_length,
50 width,
51 color_start,
52 color_end,
53
54 max_vertices,
55 fade_start_distance,
56 fade_end_distance,
57 width_curve: None,
58 color_curve,
59 texture,
60 blend_mode,
61 }
62 }
63
64 pub fn simple(
65 width: f32,
66 trail_length: f32,
67 z_index: i32,
68 color_start: Color,
69 color_end: Color,
70 ) -> Self {
71 Self::new(
72 width,
73 trail_length,
74 z_index,
75 color_start,
76 color_end,
77 100,
78 0.0,
79 0.0,
80 None,
81 None,
82 BlendMode::Additive,
83 )
84 }
85
86 pub fn update(&mut self, position: Vec2, _delta: f32) {
87 if self.is_enabled {
88 let distance = (position - self.last_vertex_at).length();
89
90 let min_vertex_distance =
91 self.trail_length / self.max_vertices as f32;
92
93 if self.positions.is_empty() {
94 self.positions.push(position);
95 self.last_vertex_at = position;
96 } else if distance > min_vertex_distance {
97 // The number of interpolation steps is the distance divided by min_vertex_distance, rounded up
98 let num_steps =
99 (distance / min_vertex_distance).ceil() as usize;
100 for i in 1..=num_steps {
101 // Compute the interpolated position
102 let t = i as f32 / num_steps as f32;
103 let interpolated_position =
104 self.last_vertex_at * (1.0 - t) + position * t;
105 self.positions.push(interpolated_position);
106 }
107
108 let mut total_distance = self.total_distance();
109
110 while self.positions.len() > 2 &&
111 total_distance > self.trail_length
112 {
113 self.positions.remove(0);
114 total_distance = self.total_distance();
115 }
116
117 self.last_vertex_at = position;
118
119 // self.positions.push(position);
120 //
121 // let mut total_distance = self.total_distance();
122 //
123 // while self.positions.len() > 2 &&
124 // total_distance > self.trail_length
125 // {
126 // self.positions.remove(0);
127 // total_distance = self.total_distance();
128 // }
129 //
130 // self.last_vertex_at = position;
131 }
132
133 // self.positions.push(position);
134 } else if let Some(first_position) = self.positions.first() {
135 if (*first_position - position).length() > self.trail_length {
136 self.positions.remove(0);
137 }
138 }
139 }
140
141 fn total_distance(&self) -> f32 {
142 self.positions.windows(2).map(|w| (w[0] - w[1]).length()).sum()
143 }
144
145 pub fn draw_mesh(&self) {
146 if self.positions.len() <= 1 {
147 return;
148 }
149
150 let tex = self.texture.unwrap_or(texture_id("1px"));
151 // let tex = texture_id("1px");
152
153 // let mut trail_length = 0.0;
154
155 let mut vertices = vec![];
156
157 for (i, (a, b)) in self.positions.iter().tuple_windows().enumerate() {
158 let n = self.positions.len() as f32;
159 // let step = 1.0 / n;
160 let pct = i as f32 / n;
161
162 let off = 2.0 * (get_unpaused_time() as f32 % 1.0);
163
164 let width_pct_a = self
165 .width_curve
166 .as_ref()
167 .map(|curve| curve.eval(pct))
168 .unwrap_or(pct);
169
170 // let width_pct_b = self
171 // .width_curve
172 // .as_ref()
173 // .map(|curve| curve.eval(pct + step))
174 // .unwrap_or(pct + step);
175
176 let color = self
177 .color_curve
178 .as_ref()
179 .map(|curve| curve.eval(pct))
180 .unwrap_or(self.color_start.lerp(self.color_end, 1.0 - pct));
181
182 // If the trail is not enabled, fade the color out based on the distance
183 let color = if self.is_enabled {
184 color
185 } else {
186 let distance_pct = (i as f32 / n).powi(2);
187 color.darken(distance_pct)
188 };
189
190 let p1 = Position::world(a.x, a.y);
191 let p2 = Position::world(b.x, b.y);
192
193 let (x1, y1) = p1.to_world().tuple();
194 let (x2, y2) = p2.to_world().tuple();
195
196 let dx = x2 - x1;
197 let dy = y2 - y1;
198
199 let nx = -dy;
200 let ny = dx;
201
202 let tlen = (nx * nx + ny * ny).sqrt();
203 if tlen < std::f32::EPSILON {
204 return;
205 }
206
207 let nxn = nx / tlen;
208 let nyn = ny / tlen;
209
210
211 let start_thickness = self.width * width_pct_a;
212 // let end_thickness = self.width * width_pct_b;
213
214 let tx1 = nxn * start_thickness * 0.5;
215 let ty1 = nyn * start_thickness * 0.5;
216
217 // let tx2 = nxn * end_thickness * 0.5;
218 // let ty2 = nyn * end_thickness * 0.5;
219
220 let z = self.z_index as f32;
221
222 // let wrapped_y_uv_start = uv_offset % 1.0;
223 // let wrapped_y_uv_end = (uv_offset + uv_size) % 1.0;
224
225 let start = off + pct;
226 // let uv_size = step;
227
228 // let start = uv_offset;
229 // let end = start + uv_size;
230
231 let top_left = vec3(x1 + tx1, y1 + ty1, z);
232 let bottom_left = vec3(x1 - tx1, y1 - ty1, z);
233
234 vertices.push(SpriteVertex::new(top_left, vec2(0.0, start), color));
235 vertices.push(SpriteVertex::new(
236 bottom_left,
237 vec2(1.0, start),
238 color,
239 ));
240
241 // draw_line_tex_y_uv_flex(
242 // // 1.0,
243 // // 1.0,
244 // // color * alpha,
245 // color,
246 // Some(tex),
247 // self.z_index,
248 // TextureParams {
249 // blend_mode: self.blend_mode,
250 // ..Default::default()
251 // },
252 // );
253 }
254
255 let indices = Self::generate_triangle_list_indices(vertices.len());
256
257 draw_mesh_ex(
258 Mesh {
259 // TODO: might want the average instead
260 origin: self
261 .positions
262 .last()
263 .copied()
264 .unwrap_or_default()
265 .extend(self.z_index as f32),
266 vertices: vertices.into(),
267 indices: indices.into(),
268 z_index: self.z_index,
269 texture: Some(tex),
270 y_sort_offset: 0.0,
271 },
272 BlendMode::Additive,
273 );
274 }
275
276 fn generate_triangle_list_indices(n: usize) -> Vec<u32> {
277 let mut indices = Vec::with_capacity(2 * (n - 2));
278 let mut is_even = true;
279
280 for i in 1..(n as u32 - 1) {
281 if is_even {
282 indices.push(i - 1);
283 indices.push(i);
284 indices.push(i + 1);
285 } else {
286 indices.push(i + 1);
287 indices.push(i);
288 indices.push(i - 1);
289 }
290
291 is_even = !is_even;
292 }
293
294 indices
295 }
296
297 // pub fn draw(&self) {
298 // if self.positions.len() <= 1 {
299 // return;
300 // }
301 //
302 // let tex = self.texture.unwrap_or(texture_id("trail"));
303 // // let tex = texture_id("1px");
304 //
305 // let mut trail_length = 0.0;
306 //
307 // // let mesh = vec![];
308 //
309 // for (i, (a, b)) in self.positions.iter().tuple_windows().enumerate() {
310 // let n = self.positions.len() as f32;
311 // let step = 1.0 / n;
312 // let pct = i as f32 / n;
313 //
314 // let off = 2.0 * get_unpaused_time() as f32;
315 //
316 // let width_pct_a = self
317 // .width_curve
318 // .as_ref()
319 // .map(|curve| curve.eval(pct))
320 // .unwrap_or(pct);
321 //
322 // let width_pct_b = self
323 // .width_curve
324 // .as_ref()
325 // .map(|curve| curve.eval(pct + step))
326 // .unwrap_or(pct + step);
327 //
328 // // let width_pct_a = pct;
329 // // let width_pct_b = pct + step;
330 //
331 // let color = self
332 // .color_curve
333 // .as_ref()
334 // .map(|curve| curve.eval(pct))
335 // .unwrap_or(self.color_start.lerp(self.color_end, 1.0 - pct));
336 //
337 // trail_length += (*b - *a).length();
338 //
339 // let fade_start = trail_length - self.fade_start_distance;
340 // let fade_end = trail_length - self.fade_end_distance;
341 //
342 // let alpha = if trail_length < self.fade_start_distance {
343 // 1.0
344 // } else if trail_length > self.fade_end_distance {
345 // 0.0
346 // } else {
347 // 1.0 - (fade_start / (fade_end - fade_start))
348 // };
349 //
350 // draw_line_tex_y_uv_flex(
351 // Position::world(a.x, a.y),
352 // Position::world(b.x, b.y),
353 // self.width * width_pct_a,
354 // self.width * width_pct_b,
355 // // 1.0,
356 // // 1.0,
357 // // color * alpha,
358 // color,
359 // Some(tex),
360 // off + pct,
361 // step,
362 // self.z_index,
363 // TextureParams {
364 // blend_mode: self.blend_mode,
365 // ..Default::default()
366 // },
367 // );
368 // }
369 // }
370
371 // pub fn draw(&self) {
372 // if self.positions.len() <= 1 {
373 // return;
374 // }
375 //
376 // // let tex = self.texture.unwrap_or(texture_id("trail"));
377 // let tex = texture_id("1px");
378 //
379 // let mut trail_length = 0.0;
380 //
381 // for (i, (a, b)) in self.positions.iter().tuple_windows().enumerate() {
382 // let n = self.positions.len() as f32;
383 // let step = 1.0 / n;
384 // let pct = i as f32 / n;
385 //
386 // let off = 2.0 * get_unpaused_time() as f32;
387 //
388 // let width_pct = self
389 // .width_curve
390 // .as_ref()
391 // .map(|curve| curve.eval(pct))
392 // .unwrap_or(pct);
393 //
394 // let width_pct_a = self
395 // .width_curve
396 // .as_ref()
397 // .map(|curve| curve.eval(pct))
398 // .unwrap_or(pct);
399 // let width_pct_b = self
400 // .width_curve
401 // .as_ref()
402 // .map(|curve| curve.eval(pct + step))
403 // .unwrap_or(pct + step);
404 //
405 // let color = self
406 // .color_curve
407 // .as_ref()
408 // .map(|curve| curve.eval(pct))
409 // .unwrap_or(self.color_start.lerp(self.color_end, 1.0 - pct));
410 //
411 // trail_length += (*b - *a).length();
412 //
413 // let fade_start = trail_length - self.fade_start_distance;
414 // let fade_end = trail_length - self.fade_end_distance;
415 //
416 // let alpha = if trail_length < self.fade_start_distance {
417 // 1.0
418 // } else if trail_length > self.fade_end_distance {
419 // 0.0
420 // } else {
421 // 1.0 - (fade_start / (fade_end - fade_start))
422 // };
423 //
424 // draw_line_tex_y_uv_flex(
425 // Position::world(a.x, a.y),
426 // Position::world(b.x, b.y),
427 // self.width * width_pct_a,
428 // self.width * width_pct_b,
429 // color * alpha,
430 // Some(tex),
431 // (off + pct)..(off + pct + step),
432 // self.z_index,
433 // TextureParams {
434 // blend_mode: self.blend_mode,
435 // ..Default::default()
436 // },
437 // );
438 //
439 // // draw_line_tex_y_uv(
440 // // Position::world(a.x, a.y),
441 // // Position::world(b.x, b.y),
442 // // self.width * width_pct,
443 // // color * alpha,
444 // // Some(tex),
445 // // (off + pct)..(off + pct + step),
446 // // self.z_index,
447 // // TextureParams {
448 // // blend_mode: self.blend_mode,
449 // // ..Default::default()
450 // // },
451 // // );
452 // }
453 // }
454}
455
456#[derive(Clone, Debug)]
457pub struct Curve {
458 pub points: Vec<(f32, f32)>,
459 pub wrap: bool,
460}
461
462impl Curve {
463 pub fn eval(&self, t: f32) -> f32 {
464 let len = self.points.len();
465 if len == 0 {
466 return 0.0;
467 }
468 if len == 1 {
469 return self.points[0].1;
470 }
471
472 let x_min = self.points.first().unwrap().0;
473 let x_max = self.points.last().unwrap().0;
474 let t_normalized = t * (x_max - x_min) + x_min;
475
476 if t_normalized <= x_min {
477 if self.wrap {
478 let (x0, y0) = self.points[len - 1];
479 let (x1, y1) = self.points[0];
480 return y0 + (t_normalized - x0) * (y1 - y0) / (x1 - x0);
481 } else {
482 return self.points[0].1;
483 }
484 }
485 if t_normalized >= x_max {
486 if self.wrap {
487 let (x0, y0) = self.points[len - 1];
488 let (x1, y1) = self.points[0];
489 return y0 + (t_normalized - x0) * (y1 - y0) / (x1 - x0);
490 } else {
491 return self.points[len - 1].1;
492 }
493 }
494 for i in 1..len {
495 if t_normalized <= self.points[i].0 {
496 let (x0, y0) = self.points[i - 1];
497 let (x1, y1) = self.points[i];
498 return y0 + (t_normalized - x0) * (y1 - y0) / (x1 - x0);
499 }
500 }
501
502 0.0
503 }
504}
505
506// impl Curve {
507// pub fn eval(&self, t: f32) -> f32 {
508// let len = self.points.len();
509// if len == 0 {
510// return 0.0;
511// }
512// if len == 1 {
513// return self.points[0].1;
514// }
515// if t <= self.points[0].0 {
516// if self.wrap {
517// let (x0, y0) = self.points[len - 1];
518// let (x1, y1) = self.points[0];
519// return y0 + (t - x0) * (y1 - y0) / (x1 - x0);
520// } else {
521// return self.points[0].1;
522// }
523// }
524// if t >= self.points[len - 1].0 {
525// if self.wrap {
526// let (x0, y0) = self.points[len - 1];
527// let (x1, y1) = self.points[0];
528// return y0 + (t - x0) * (y1 - y0) / (x1 - x0);
529// } else {
530// return self.points[len - 1].1;
531// }
532// }
533// for i in 1..len {
534// if t <= self.points[i].0 {
535// let (x0, y0) = self.points[i - 1];
536// let (x1, y1) = self.points[i];
537// return y0 + (t - x0) * (y1 - y0) / (x1 - x0);
538// }
539// }
540// return 0.0;
541// }
542// }
543
544#[derive(Clone, Debug)]
545pub struct ColorCurve {
546 pub gradient: Vec<(Color, f32)>,
547}
548
549impl ColorCurve {
550 pub fn new(gradient: Vec<(Color, f32)>) -> Self {
551 Self { gradient }
552 }
553
554 pub fn eval(&self, t: f32) -> Color {
555 if t <= 0.0 {
556 return self.gradient[0].0;
557 }
558 if t >= 1.0 {
559 return self.gradient.last().unwrap().0;
560 }
561
562 for i in 1..self.gradient.len() {
563 let (prev_color, prev_pos) = self.gradient[i - 1];
564 let (next_color, next_pos) = self.gradient[i];
565
566 if t <= next_pos {
567 let factor = (t - prev_pos) / (next_pos - prev_pos);
568 return prev_color.lerp(next_color, factor);
569 }
570 }
571
572 // This should never be reached, as t is already checked for >= 1.0
573 return self.gradient.last().unwrap().0;
574 }
575}