Add matrix code animation
This commit is contained in:
parent
5a73c5598a
commit
e5389416b6
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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user