fireworks display

Recently I embarked on a fun little side project. There have been just too many rampant fireworks going on outside and I was feeling a little left out, so I decided to make some of my own.

Given I have no experience with anything like OpenGL at all, and was feeling like learning termion since I consistently use tui with a termion backend and only sort of understand how it works, I decided terminal fireworks would be the way to go.

This little post traces my short journey to making my own terminal fireworks in case you want to do it yourself and some logical extensions if you want to keep building it afterwards. All code is in rust because rust.

If you are using rust and following along, you’ll probably want these dependencies:

[dependencies]
termion = "1.5.5"
rand = "0.7"

This little doc provides some code, but does not give all the code required to make it work. For that, you’ll need to write it yourself 😎

My final result is here

Setting up

When working with applications that have UIs, you almost always want to have your rendering separate from input. I didn’t know how to do this for my terminal typeracer application and it has haunted me ever since. Terminal typeracer still works fine without it since it only needs to update on keypress (for now at least), but for an application meant to exclusively work with graphics that stops when you hit a key, async rendering was required.

A simple async rendering pipeline

Supposedly there is an async stdin directive termion provides, but it feels way too magical, so I ended up doing this manually. Plus it’s a good learning experience, so two for one!

The first part of actually rendering with termion is that you need to steal stdout. Then somewhere else, steal stdin and read and process events as they come in.

use std::io::{stdin, stdout, Error};
use termion::{event::*, input::TermRead};

fn main() -> Result<Box<dyn Error>> {
  // steal stdout, we can do stuff with it later
  let mut stdout = stdout().into_raw_mode()?;

  // steal stdin, C-c will break out of the loop
  for c in stdin().events() {
  let evt = c?;
  match evt {
    Event::Key(Key::Ctrl('c')) => break,
    _ => {}
  }

}

Now one of these has to go in a separate thread. If you’re like me, you might think it makes more sense to have the rendering work go on a separate thread. Unfortunately as I found out, this is a very bad idea.

Termion sets the terminal to raw mode and if you release the handle to raw mode in another thread, the terminal will never look the same at the end. It will switch the terminal out of raw mode, but there will be all sorts of character aberrations. I tried all sorts of weird ways to get around this because I thought it made more sense, but take my word for it here, it’s impossible.

So let’s set this up so it up so your terminal does not look evil when it’s done.

The first step is really just spinning up a thread and setting up a receiver so the two threads can chat. I’m using rust’s standard mpsc for this. It works much like Go’s channels. This is required since we’ll need a way to ask the renderer to terminate from our stdin thread.

use std::{
  io::{stdin, stdout, Error},
  sync::{
    mpsc,
    mpsc::{Receiver, Sender},
  },
  thread,
};
use termion::{event::*, input::TermRead};


fn main() {
  let (tx, rx): (Sender<bool>, Receiver<bool>) = mpsc::channel();
  thread::spawn(|| capture_input(rx));
  render(rx);
}

// capture keystrokes and ask renderer to stop when ^C is hit
fn capture_input(sender: Sender<bool>) { ... }

// render fireworks display every so often
render(receiver: Receiver<bool>) { ... }

With just those few lines of code you’re off to the races. Async stdin! While I haven’t checked, I’m fairly certain this is exactly how termion’s implementation works.

Capturing keypresses

The implementation is not novel or interesting. The whole thing is 18 lines and when it captures a C-c, it asks the renderer to stop.

Rendering

The rest of this will talk about the rendering pipeline, and the design of making a fun terminal firework render.

Firework Setup

Fireworks aren’t stagnant, they move around, shimmy and shine, and do all sorts of funky stuff. For our naive firework implementation, we’ll want to define some states our firework can get in, and we can handle each differently.

For my basic fireworks, I decided these were a reasonable set of states:

pub enum FireworkState {
  Flying,    // Just a tail - going up
  Exploding, // Bursting outwards
  Falling,   // bursting out has stopped, fall from the tips and fade from center to tips
  Fading,    // tips disappear to fading at 2x the rate fade falls
  Gone,      // nothing, a blank canvas, the inner machinations of my mind
}

We’ll then want each firework to have a set of these states until there is naught but Gone left.

We can generate these on the fly, or we can do it all at once up front. I found it a little more convenient to generate them all up front, and at some point I’d be interested in making a backwards/forwards control on my fireworks, so that’s what I went with.

For the sake of testing I wanted the fireworks to be consistent, but for the sake of 🎉 fun 🎉 I wanted them to be random. The compromise was using seeded random.

It was one of the first times I’d ever manually seeded a rand value besides one time I had to use C++ in college. Rust doesn’t make it too hard though:

use rand::StdRng;

// make a new firework
pub fn new(seed: u64) -> Self {
  let rng = StdRng::seed_from_u64(seed);
  // who care
}

Using this seed, we are able to now ensure all random values are generated deterministically.

Note: rng objects are mutable and work kind of like iterators. If we decide to only pass the seed around, all fireworks would be generated identically, thus we want to pass around the cloned rng object to get an increased level of randomness.

Finally we set some random thresholds, generate a list of states for our firework object, and we’re off to the races.

Each phase and how they are rendered

Now that we have each of our firework phases clearly defined, it’s time to talk about what each one does at a lower level.

The center

This is the smallest part, and not even a phase, but worth mentioning because most everything will use it for absolute or relative positioning. Before anything is really made, I decide the firework starting point (I also use it as the center). It’s just a point with an y value of 0, and an x value of range(0, u64::MAX)

(We’re Soaring…) Flying

Flying is the most simple of the components. It starts with a single point:

*

And after each phase, it grows one up. So:

Two phases:

*
*

Three phases:

*
*
*

Very simple.

I considered making the bottom of the tail shrink ever 2 or 3 phases, but figured it was unnecessary because of the next phase.

Exploding

Exploding is more fun and takes a little more effort. But let’s do the simple stuff first.

The exploding action should make the tail shrink by one from the bottom. This helps signify the explosion is starting.

The actual exploding part is a little more complex though. Given the low pixel nature of a terminal, I decided the best way forward was to explode out two different sections at different intervals. The cross section was to be exploded on every exploding interval, while diagonal exploding out points were to happen every two intervals.

For instance… (note, spaces added because my font thinks *** is a ligature)

Phase 1:

  *
* * *
  *

Phase 2:

    *
  * * *
* * * * *
  * * *
    *

Phase 3:

     *
     *
   * * *
* * * * * *
   * * *
     *
     *

Phase 4:

       *
       *
    *  *  *
     * * *
* * * * * * * *
     * * *
   *   *   *
       *
       *

You can see it looks a little bit awkward for tiny explosions, and starts to look more normal around phase 4. Phase 4+ looks like a nice little explosion and works much better with falling as you’ll see when there are staggered cross and diagonal explosions.

Falling

At this point the tail should be gone, but if it isn’t, we remove it. Good riddance.

We also begin to collapse the explosion inward.

       *
       *
    *  *  *
     *   *
* * *     * * *
     *   *
   *   *   *
       *
       *

With each phase removing from the inside out until the edges are reached.

This too follows the two phased cross => cross + diagonal => cross => …

Falling isn’t called falling because the inside of the fireworks collapses though, it also falls!

It’s a little hard to show without extra color with the last diagram, so I will show a fully collapsed explosion to illustrate.

       *
        
    *     *
          
*             *
          
   *       *
        
       *

This is what a fully collapsed firework (with no falling steps yet) looks like. For falling, each of these points draws downward, like an inverse tail.

       *
       *
    *     *
    *     *
*             *
*             *
   *       *
   *       *
       *
       *

With a not fully collapsed firework, this looks extra cool. Let’s add two more to fully show the effect.

       *
       *
    *  *  *
    *  *  *
*   *     *   *
*   *     *   *
*  *       *  *
*  *       *  *
   *   *   *
   *   *   *
       *
       *

Pretty neat, right?

This strategy sort of looks a little funky when working with smaller explosions, but works quite well for explosions of size 4 and larger.

Fading

Finally comes fading (well technically Gone is the last step, but it just deletes everything, so it isn’t worth talking about).

Fading is quite simple. We take the end points from the explosion:

       *
        
    *     *
          
*             *
          
   *       *
        
       *

And delete from those, to the final point we created during the Falling phase 1 at a time.

This goes on for as long as we generated falling states for.

Eventually your firework disappears and that’s it!

Drawing everything

So now we’ve defined the firework and its stages. We have more or less implemented a firework, how are we going to get it onto a user’s screen?

Firework setup

The first order of business is storing all of the data from state to state transition and then keeping it all in a nice bundle for convenience.

For me that meant defining a struct to hold point data:

pub struct Point {
  pub x: u16,
  pub y: u16,
}

And then making a bunch of other structs for each state:

struct TailPoints {
  tail: Vec<Point>,
}

struct ExplodingPoints {
  explosion_iter: u8,
  perpendicular_tips: Vec<Point>,
  diagonal_tips: Vec<Point>,
  explosion: Vec<Point>,
}

// ...you get the point

Some contain more or less data depending on what they need to do, but that’s more of an implementation detail.

From there I have one unifying struct called a Firework that holds them all:

pub struct Firework {
  total_state: IntoIter<FireworkState>,
  tail_points: TailPoints,
  exploding_points: ExplodingPoints,
  falling_points: FallingPoints,
  // ...and a few other implementation details
}

When a state is completed, we want to round up all of the points for the renderer and I do that with a trait called Drawable

pub trait Drawable {
  fn draw(&self) -> &[Point];
}

I implement this for each of the states: tail_points, exploding_points, … etc.

And then for the renderer (we’ll get to that part in a moment) implement a nice method in the Firework to round up the drawables:

  pub fn drawables(&self) -> [&dyn Drawable; 3] {
    [&self.tail_points, &self.exploding_points, &self.falling_points]
  }

Nice! Look at that, basically zero cost collection of drawables. No heap allocated values and maximum precision.

We’ll randomly generate fireworks every few ticks, and every tick, grab the drawables for all of the generated fireworks.

Actually drawing to the screen

The actual drawing implementation is not particularly novel. I used standard termion directives and just drew every point to the screen, but it is still worth talking about.

This is really all it takes:

for drawable in drawables.iter() {
  let points = drawable.draw();
  for point in points.iter() {
    if point.y < width {
      write!(
        stdout,
        "{}{}*",
        Goto(point.x % length, width - point.y),
        termion::cursor::Hide,
      )?;
    }
  }
}

You’ll note a couple of things here:

  • we % point.x by length
  • we subtract point.y from width

The former is done so we can have our x coordinate anywhere and it means we get screen wrapping of fireworks for free.

The latter is done because termion actually draws the grid like so:

  0
  ──────➤
0┃
 ┃
 ┃
 ┃
 ▼

If we don’t subtract the width from y, the fireworks come out upside down!

Maybe if you’re living in the underworld it would make sense, but I figure most people aren’t.

Congrats, you’re basically done!

With one caveat

If you’ve built up to this point, you’ll notice that when you run fireworks, nothing really disappears. You’ll just get a mess on your screen.

Actually you’ll notice first that there’s no output. Make sure to sleep for a few milliseconds between ticks! But once you do that, you’ll see a mess on your terminal.

And that’s because these fireworks don’t clear magically. We’ll have to do that automatically, or manually.

The easy/automatic solution is just to ask the terminal to clear itself before each draw. termion provides a nice utility for this:

write!(stdout, "{}", termion::clear::All)?;

Not only is this boring though, it’s slower than we need it to be! We know the points that need to be cleared, don’t we? So why don’t we just ask the renderer to clear them before drawing new ones?

I did this by extending the Drawable trait (oh god, I almost wrote interface, my Java job is getting to me):

pub trait Drawable {
  fn draw(&self) -> &[Point];
  fn clear(&self) -> &[Point];
}

And then before transitioning each struct to the next step, save the old set of points. For instance:

struct TailPoints {
  tail: Vec<Point>,
  old_tail: Vec<Point>,
}

Return old_tail when the clear method is called and draw the “clear” points as spaces before you draw in the * for the firework itself.

And there you go! Congratulations, you have fireworks!

Extras you can add

We only added basic, colorless fireworks, but there’s way more you can add! In my implementation, I also added color. Here are some other things that might be neat to add to the colorless example we made above:

  • single color fireworks
  • fireworks that change color (rainbow if you’re feeling extra fun)
  • making the fireworks asymmetrical
  • different varieties of fireworks (not just our blooming one)

For reference: This was my implementation of terminal fireworks which implements colors.

Anyway if you made it this far, thanks a bunch for coming by :)