diff --git a/src/renderer/cursor_renderer/cursor_vfx.rs b/src/renderer/cursor_renderer/cursor_vfx.rs index b6150a8..ab14c40 100644 --- a/src/renderer/cursor_renderer/cursor_vfx.rs +++ b/src/renderer/cursor_renderer/cursor_vfx.rs @@ -2,14 +2,26 @@ use log::error; use skulpin::skia_safe::{paint::Style, BlendMode, Canvas, Color, Paint, Point, Rect}; use super::animation_utils::*; +use super::CursorSettings; use crate::editor::{Colors, Cursor}; use crate::settings::*; -use super::CursorSettings; pub trait CursorVfx { - fn update(&mut self, settings: &CursorSettings, current_cursor_destination: Point, dt: f32) -> bool; + fn update( + &mut self, + settings: &CursorSettings, + current_cursor_destination: Point, + dt: f32, + ) -> bool; fn restart(&mut self, position: Point); - fn render(&self, settings: &CursorSettings, canvas: &mut Canvas, cursor: &Cursor, colors: &Colors, font_size: (f32, f32)); + fn render( + &self, + settings: &CursorSettings, + canvas: &mut Canvas, + cursor: &Cursor, + colors: &Colors, + font_size: (f32, f32), + ); } #[derive(Clone, PartialEq)] @@ -94,7 +106,12 @@ impl PointHighlight { } impl CursorVfx for PointHighlight { - fn update(&mut self, settings: &CursorSettings, _current_cursor_destination: Point, dt: f32) -> bool { + fn update( + &mut self, + _settings: &CursorSettings, + _current_cursor_destination: Point, + dt: f32, + ) -> bool { self.t = (self.t + dt * 5.0).min(1.0); // TODO - speed config self.t < 1.0 } @@ -104,7 +121,14 @@ impl CursorVfx for PointHighlight { self.center_position = position; } - fn render(&self, settings: &CursorSettings, canvas: &mut Canvas, cursor: &Cursor, colors: &Colors, font_size: (f32, f32)) { + fn render( + &self, + settings: &CursorSettings, + canvas: &mut Canvas, + cursor: &Cursor, + colors: &Colors, + font_size: (f32, f32), + ) { if self.t == 1.0 { return; } @@ -207,7 +231,8 @@ impl CursorVfx for ParticleTrail { let travel_distance = travel.length(); // Increase amount of particles when cursor travels further // TODO -- particle count should not depend on font size - let particle_count = (travel_distance.powf(1.5) * settings.vfx_particle_density * 0.01) as usize; + let particle_count = + (travel_distance.powf(1.5) * settings.vfx_particle_density * 0.01) as usize; let prev_p = self.previous_cursor_dest; @@ -216,19 +241,25 @@ impl CursorVfx for ParticleTrail { for i in 0..particle_count { let t = i as f32 / (particle_count as f32 - 1.0); - let phase = t * 60.0; - let speed = match self.trail_mode { - TrailMode::Railgun => Point::new(phase.sin(), phase.cos()) * 20.0, - TrailMode::Torpedo => rng.rand_dir() * 10.0, + TrailMode::Railgun => { + let phase = t * 60.0; // TODO -- Hardcoded spiral curl + Point::new(phase.sin(), phase.cos()) * 20.0 // TODO -- Hardcoded spiral outward speed + } + TrailMode::Torpedo => rng.rand_dir_normalized() * 10.0, // TODO -- Hardcoded particle speed TrailMode::PixieDust => { - let base_dir = rng.rand_dir(); + let base_dir = rng.rand_dir_normalized(); let dir = Point::new(base_dir.x * 0.5, 0.4 + base_dir.y.abs()); - dir * 30.0 + dir * 30.0 // TODO -- hardcoded particle speed } }; - let pos = prev_p + travel * (t + 0.3 * rng.next_f32() / particle_count as f32); + // TODO -- Spawn position should be at the base of the cursor, not the + // middle + + // Distribute particles along the travel distance, with a random offset to make it + // look random + let pos = prev_p + travel * rng.next_f32(); self.add_particle(pos, speed, t * settings.vfx_particle_lifetime); } @@ -242,16 +273,23 @@ impl CursorVfx for ParticleTrail { fn restart(&mut self, _position: Point) {} - fn render(&self, settings: &CursorSettings, canvas: &mut Canvas, cursor: &Cursor, colors: &Colors, font_size: (f32, f32)) { + fn render( + &self, + settings: &CursorSettings, + canvas: &mut Canvas, + cursor: &Cursor, + colors: &Colors, + font_size: (f32, f32), + ) { let mut paint = Paint::new(skulpin::skia_safe::colors::WHITE, None); match self.trail_mode { TrailMode::Torpedo | TrailMode::Railgun => { paint.set_style(Style::Stroke); + paint.set_stroke_width(font_size.1 * 0.2); } _ => {} } - paint.set_stroke_width(font_size.1 * 0.2); let base_color: Color = cursor.background(&colors).to_color(); paint.set_blend_mode(BlendMode::SrcOver); @@ -266,8 +304,8 @@ impl CursorVfx for ParticleTrail { TrailMode::Torpedo | TrailMode::Railgun => font_size.0 * 0.5 * l, TrailMode::PixieDust => font_size.0 * 0.2, }; - let hr = radius * 0.5; + let hr = radius * 0.5; let rect = Rect::from_xywh(particle.pos.x - hr, particle.pos.y - hr, radius, radius); match self.trail_mode { @@ -295,34 +333,59 @@ impl RngState { inc: (0xDA3E39CB94B95BDBu64 << 1) | 1, } } - fn next(&mut self) -> u64 { - use std::num::Wrapping; - + fn next(&mut self) -> u32 { let old_state = self.state; - self.state = - (Wrapping(old_state) * Wrapping(6_364_136_223_846_793_005u64) + Wrapping(self.inc)).0; - let xorshifted = (old_state >> 18) ^ old_state >> 27; - let rot = (old_state >> 59) as i64; - (xorshifted >> rot as u64) | (xorshifted << ((-rot) & 31)) + + // Implementation copied from: + // https://rust-random.github.io/rand/src/rand_pcg/pcg64.rs.html#103 + let new_state = old_state + .wrapping_mul(6_364_136_223_846_793_005u64) + .wrapping_add(self.inc); + + self.state = new_state; + + const ROTATE: u32 = 59; // 64 - 5 + const XSHIFT: u32 = 18; // (5 + 32) / 2 + const SPARE: u32 = 27; // 64 - 32 - 5 + + let rot = (old_state >> ROTATE) as u32; + let xsh = (((old_state >> XSHIFT) ^ old_state) >> SPARE) as u32; + xsh.rotate_right(rot) } fn next_f32(&mut self) -> f32 { let v = self.next(); + // In C we'd do ldexp(v, -32) to bring a number in the range [0,2^32) down to [0,1) range. + // But as we don't have ldexp in Rust, we're implementing the same idea (subtracting 32 + // from the floating point exponent) manually. + + // First, extract exponent bits let float_bits = (v as f64).to_bits(); - let exponent = (float_bits >> 53) & ((1 << 10) - 1); - // Set exponent for 0-1 range + let exponent = (float_bits >> 52) & ((1 << 11) - 1); + + // Set exponent for [0-1) range let new_exponent = exponent.max(32) - 32; - let new_bits = (new_exponent << 53) | (float_bits & 0x801F_FFFF_FFFF_FFFFu64); + // Build the new f64 value from the old mantissa and sign, and the new exponent + let new_bits = (new_exponent << 52) | (float_bits & 0x801F_FFFF_FFFF_FFFFu64); f64::from_bits(new_bits) as f32 } + // Produces a random vector with x and y in the [-1,1) range + // Note: Vector is not normalized. fn rand_dir(&mut self) -> Point { let x = self.next_f32(); let y = self.next_f32(); Point::new(x * 2.0 - 1.0, y * 2.0 - 1.0) } + + fn rand_dir_normalized(&mut self) -> Point { + let mut v = self.rand_dir(); + v.normalize(); + v + } } +