From e5389416b6382fb051f965c752814c067cc1eedd Mon Sep 17 00:00:00 2001 From: Juergen Stuber Date: Sun, 14 May 2023 23:27:19 +0200 Subject: [PATCH] Add matrix code animation --- src/bin/matrix-code/main.rs | 264 ++++++++++++++++++++++++++++++++++++ 1 file changed, 264 insertions(+) create mode 100644 src/bin/matrix-code/main.rs diff --git a/src/bin/matrix-code/main.rs b/src/bin/matrix-code/main.rs new file mode 100644 index 0000000..6472287 --- /dev/null +++ b/src/bin/matrix-code/main.rs @@ -0,0 +1,264 @@ +use core::ops::Index; +use core::ops::IndexMut; + +use std::convert::TryFrom; +use std::env::args; +use std::error; +use std::f64::consts::PI; +use std::io::stdout; +use std::io::Write; +use std::mem; +use std::thread::sleep; +use std::time::Duration; +use std::time::Instant; + +use rand::thread_rng; +use rand::Rng; + +use lowdim::bb2d; +use lowdim::p2d; +use lowdim::Array2d; +use lowdim::BBox2d; +use lowdim::Point2d; + +use pixelfoo::color::Color; + +#[derive(Clone, Debug)] +struct RunningState { + /// Time in frames since start of animation. + t: i64, + /// Number of frames per step in y direction. + frames_per_step: i64, + // Length of tail with constant brightness. + tail_full: i64, + // Length of tail with diminishing brightness. + tail_fade: i64, + /// Upper bound for the displayed y coordinate (exclusive). + y_end: i64, + /// Base brightness per pixel indexed by y. + brightnesses: Vec, +} +impl RunningState { + fn new(frames_per_step: i64, tail_full: i64, tail_fade: i64, y_end: i64) -> RunningState { + RunningState { + t: 0, + frames_per_step, + tail_full, + tail_fade, + y_end, + brightnesses: Vec::new(), + } + } + fn color(&self, y: i64) -> Color { + let head_y = self.head_y(); + if y > head_y { + Color::black() + } else if y == head_y { + Color::white() + } else if y == head_y - 1 { + let color = Color::white().interpolate(Color::green(), 0.3); + let a = self.brightnesses[y as usize]; + Color::black().interpolate(color, a) + } else if y >= head_y - self.tail_full { + let a = self.brightnesses[y as usize]; + Color::black().interpolate(Color::green(), a) + } else if y >= head_y - self.tail_full - self.tail_fade { + let p = (head_y - self.tail_full - y + 1) as f64; + let q = self.tail_fade as f64; + let a = (1.0 - p / q) * self.brightnesses[y as usize]; + Color::black().interpolate(Color::green(), a) + } else { + Color::black() + } + } + fn next_frame(&mut self, rng: &mut R) { + self.t += 1; + let y = usize::try_from(self.head_y()).unwrap(); + if y >= self.brightnesses.len() { + self.brightnesses.push(0.5 + 0.5 * rng.gen::()); + } + if rng.gen::() < 0.2 { + let y = rng.gen_range(0..self.brightnesses.len()); + self.brightnesses[y] = 0.5 + 0.5 * rng.gen::(); + } + } + fn head_y(&self) -> i64 { + self.t / self.frames_per_step + } + fn is_finished(&self) -> bool { + self.head_y() - self.tail_full - self.tail_full >= self.y_end + } +} + +#[derive(Clone, Debug, Default)] +enum AnimationState { + #[default] + Idle, + Running(RunningState), +} +impl AnimationState { + fn start(frames_per_step: i64, tail_full: i64, tail_fade: i64, y_end: i64) -> AnimationState { + let state = RunningState::new(frames_per_step, tail_full, tail_fade, y_end); + AnimationState::Running(state) + } + fn color(&self, y: i64) -> Color { + match self { + AnimationState::Idle => Color::black(), + AnimationState::Running(state) => state.color(y), + } + } + fn next_frame(mut self, rng: &mut R) -> AnimationState { + if let AnimationState::Running(state) = &mut self { + state.next_frame(rng); + } + match self { + AnimationState::Running(state) => { + if state.is_finished() { + AnimationState::Idle + } else { + AnimationState::Running(state) + } + } + _ => self, + } + } + fn is_idle(&self) -> bool { + matches!(self, AnimationState::Idle) + } +} + +#[derive(Clone, Debug)] +struct Animation { + state: AnimationState, +} +impl Animation { + fn new() -> Animation { + let state = AnimationState::Idle; + Animation { state } + } + fn color(&self, y: i64) -> Color { + self.state.color(y) + } + fn start(&mut self, frames_per_step: i64, tail_full: i64, tail_fade: i64, y_end: i64) { + self.state = AnimationState::start(frames_per_step, tail_full, tail_fade, y_end); + } + fn next_frame(&mut self, rng: &mut R) { + let state = mem::take(&mut self.state); + self.state = state.next_frame(rng); + } + fn is_idle(&self) -> bool { + self.state.is_idle() + } +} + +#[derive(Clone, Debug)] +struct Frame(Array2d); +impl Frame { + pub fn new(bbox: BBox2d, color: Color) -> Frame { + Frame(Array2d::new(bbox, color)) + } + pub fn bbox(&self) -> BBox2d { + self.0.bbox() + } +} +impl Index for Frame { + type Output = Color; + fn index(&self, p: Point2d) -> &Color { + &self.0[p] + } +} +impl IndexMut for Frame { + fn index_mut(&mut self, p: Point2d) -> &mut Color { + &mut self.0[p] + } +} + +fn send(w: &mut T, frame: &Frame) -> std::io::Result<()> { + for y in frame.bbox().y_range() { + for x in frame.bbox().x_range() { + let c = frame[p2d(x, y)]; + w.write_all(&c.rgb())?; + } + } + w.flush() +} + +const DEFAULT_ARG: u64 = 2; + +fn main() -> Result<(), Box> { + let args = args().collect::>(); + eprintln!("executing {}", args[0]); + + let x_size = args[1].parse::().unwrap(); + let y_size = args[2].parse::().unwrap(); + let arg = args[3].parse::().unwrap_or(DEFAULT_ARG); + eprintln!("screen size {}x{}, arg {}", x_size, y_size, arg); + + let mut rng = thread_rng(); + let bbox = bb2d(0..x_size as i64, 0..y_size as i64); + + let frame_duration = Duration::from_millis(50); + + let mut animations = (0..x_size) + .map(|_| Animation::new()) + .collect::>(); + + let p_slow_animation = 0.1; + + let frames_per_step_normal = 4; + let frames_per_step_slow = 8; + + let start_time = Instant::now(); + + let mut frame_count = 0; + let mut frame_time = start_time; + let mut next_frame_time = frame_time + frame_duration; + loop { + let mut frame = Frame::new(bbox, Color::black()); + + let a = PI * (frame_time - start_time).as_secs_f64() / 90.0; + let p_new_animation = 0.03 * a.sin().powf(2.0); + + // Update the animation + for animation in &mut animations { + if animation.is_idle() { + if rng.gen::() < p_new_animation { + let frames_per_step = { + if rng.gen::() < p_slow_animation { + frames_per_step_slow + } else { + frames_per_step_normal + } + }; + if frame_count % frames_per_step == 0 { + let size = rng.gen_range(15..=30); + animation.start(frames_per_step, size, 15, i64::try_from(y_size)?); + } + } + } else { + animation.next_frame(&mut rng); + } + } + + // Draw the state of the animations. + for x in 0..x_size { + for y in frame.bbox().y_range() { + let p = p2d(i64::try_from(x)?, y); + frame[p] = animations[x].color(y); + } + } + + let mut buf = Vec::with_capacity(x_size * y_size * 3); + send(&mut buf, &frame)?; + + let sleep_duration = next_frame_time.saturating_duration_since(Instant::now()); + sleep(sleep_duration); + + stdout().write_all(&buf)?; + stdout().flush()?; + + frame_time = next_frame_time; + next_frame_time += frame_duration; + frame_count += 1; + } +}