Add matrix code animation
This commit is contained in:
		
							
								
								
									
										264
									
								
								src/bin/matrix-code/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										264
									
								
								src/bin/matrix-code/main.rs
									
									
									
									
									
										Normal file
									
								
							@@ -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<f64>,
 | 
			
		||||
}
 | 
			
		||||
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<R: Rng>(&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::<f64>());
 | 
			
		||||
        }
 | 
			
		||||
        if rng.gen::<f64>() < 0.2 {
 | 
			
		||||
            let y = rng.gen_range(0..self.brightnesses.len());
 | 
			
		||||
            self.brightnesses[y] = 0.5 + 0.5 * rng.gen::<f64>();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    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<R: Rng>(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<R: Rng>(&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<i64, Color>);
 | 
			
		||||
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<Point2d> for Frame {
 | 
			
		||||
    type Output = Color;
 | 
			
		||||
    fn index(&self, p: Point2d) -> &Color {
 | 
			
		||||
        &self.0[p]
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
impl IndexMut<Point2d> for Frame {
 | 
			
		||||
    fn index_mut(&mut self, p: Point2d) -> &mut Color {
 | 
			
		||||
        &mut self.0[p]
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn send<T: Write>(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<dyn error::Error>> {
 | 
			
		||||
    let args = args().collect::<Vec<_>>();
 | 
			
		||||
    eprintln!("executing {}", args[0]);
 | 
			
		||||
 | 
			
		||||
    let x_size = args[1].parse::<usize>().unwrap();
 | 
			
		||||
    let y_size = args[2].parse::<usize>().unwrap();
 | 
			
		||||
    let arg = args[3].parse::<u64>().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::<Vec<Animation>>();
 | 
			
		||||
 | 
			
		||||
    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::<f64>() < p_new_animation {
 | 
			
		||||
                    let frames_per_step = {
 | 
			
		||||
                        if rng.gen::<f64>() < 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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user