SVG Animations and Live Reloading

Tags:

End result

Animated SVG

Why

For my video conference displays, I'd like to have a (subtly) animated background. Not too animated as to be distracting, but if you look close enough, you should be able to see that the background changes over time.

SVG animations

Instead of getting into Blender or ShaderToy, I decided to go with simple animated SVG. SVG has <animate> elements that allow me to specify how a single DOM node should animate over time.

In the end the background is just some Bezier curves morphing into another, and some CPU-gobbling filter effects for blur.

My goal is to create the illusion of waves travelling across the image by animating each path into the next one and having all these animations play in a loop. Here, only the second line is animated to animate into the third:

Static SVG

Workflow

I do the basic SVG editing in Inkscape, and luckily it keeps existing <animate> elements alive even if it does not know about them. Working in Inkscape makes it easy to get the layout of lines and trace existing images without having to guess the coordinates.

Inkscape does not display the animations, but that's fine, I add these later in a post-processing step.

<animate> tag

Static SVG

If all lines slowly animate into their next neighbour, this will create the intended illusion of lines flowing across the screen. The <animate> element is a good fit for my animation goal, with a small snag. There seems to be no easy way to tell SVG "animate from shape with id foo to id bar in 5 seconds". I can only describe "animate the shape of the element from this (explicit) path into another (explicit) path":

<path
 d="M 9.0454762,0 C -7.6337807,60.410349 -84.36489,310.51971 137.97241,386.67508..."
 id="line_009"
 ><animate
   attributeName="d"
   dur="20s"
   repeatCount="indefinite"
   keyTimes="0; 1"
   values="M 9.0454762,0 C -7.6337807,60.410349 -84.36489,310.51971 137.97241,386.67508...
           M parameters-of-next-line />
</path>

This animation between two paths is still workable, but I have to copy the path parameters into the animation parameters of each line and its preceding animation element whenever I change the shape of a line.

Perl

To update the paths in the animation elements, I wrote me a short Perl script that reads the XML, finds all lines that I want to animate, sorts them by X offset and adds an animation to them that animates the line into the next line:

my $dom = XML::LibXML->load_xml( location => 'lines.svg' );
my @lines = $dom->findnodes('//svg:*[@id="layer1"]//svg:path[@d]');

# Sort the lines
my %line_offset = map {
    0+$_ => $_->getAttribute( 'd' ) =~ /^\s*M\s*(-?[\d.]+),/i
} @lines;
@lines = sort {
    $line_offset{ 0+$a }
    <=>
    $line_offset{ 0+$b }
} @lines;

# Add animations
for my $i (0..$#lines-1) {
    my $line = $lines[ $i ];
    my $next = $lines[ $i+1 ];
    # Now, remove all animation nodes and other children:
    $line->removeChildNodes();

    my $ani = $dom->createElement('animate');
    $ani->setAttribute( 'attributeName' => 'd' );
    $ani->setAttribute( 'dur' => '20s' );
    $ani->setAttribute( 'repeatCount' => 'indefinite' );
    $ani->setAttribute( 'keyTimes' => '0; 1' );
    $ani->setAttribute( 'values' =>
        sprintf "%s; %s", $line->getAttribute('d'), $next->getAttribute('d') );
    $line->appendChild($ani);
}

open my $output, '>:raw', 'lines-animated.svg';
print $output $dom->toString;

Live reloading

I really prefer interactive tools when doing graphics, so to get away from manually reloading the SVG animation in the browser, I wanted live reloading of the browser tab whenever the file changed. Adding SVG support was a fairly simple addition to App::Mojo::AssetReloader, but work on that is not finished, as the animation resets on each reload. I need to think about how to preserve the current animation time. Likely this is "easily" done by saving the time from .getCurrentTime() and restoring it after reload via .setCurrentTime(). But then I also need to figure out how/when I want to reset the animation time to the start instead of keeping it.

Further ideas: WebMIDI integration / knob integration

Integrating the whole thing with WebMIDI would be fun for an all-in-one thing. Twisting a knob is so much more fun for adjusting animation parameters than manually editing a number via the keypad. I have MIDI and non-MIDI hardware though, so going a custom route is also interesting.

Review: Assassins Creed Origins

Tags:

Assassins Creed Origins

Assassins Creed Logo

I just finished playing Assassins Creed Origins ("the one in Egypt"). I've spent 40 hours on it, and the last 8 hours of that were a race to get the thing finished, because the story was Just Not Great.

The Good

The game looks gorgeous on an Nvidia 1080. It has great landscapes and the loading times are bearable. The in-game performance is great, and the sneaking/murdering gameplay is really fun. The other Assassins Creed game I played was Assassins Creed II, the one in Florence.

The Bad

The story. Oh god, it is bad. The story in the outer world is random filler, eating up loading time. The story in Egypt dredges on too long. It is about a failing marriage and two parents on a trip for revenge.

The gameplay in the open world is largely forgettable. There is little to discover and it is not fun to just ride around, discovering the landscape. The gameplay in fights is no fun either and most of the missions are not fun either. They take themselves mostly serious and there is no connection to the quest givers and their plight. Better writing could have helped there. The only good gameplay is the one you make for yourself by clearing out the military installations, sneaking around and killing people.

The world is large but boring. It is no fun to seek out (and potentially clear) all the question marks on the map.

The ugly

The game is connected to the UbiSoft launcher, even when you buy it through Steam. That launcher updates itself every time you launch it through Steam. It also requires a (burner) email address for launching the game.

The game is single-player but wants to be online all the time, to tell you about Assassins Creed Bucks and other microtransactions.

Now playing: Monster Train 2

Tags:

Monster Train 2

A fun strategic deck building game, Just like the first game. Different mechanics, different challenges. There is some kind of story.

The first Monster Train made me realize what people like about Magic the Gathering (the strategy and deck management) and does not have any of the downsides of Magic the Gathering (the lootbox money sink).

The story is nothing spectacular but has at least one good reveal.

On Monster Train I have 1.100 hours logged, so here's to another 1k hours .

More CSS features: light-dark()

Tags:

Providing a dark mode is good form nowadays. I like doing/having that too.

Light mode

Usually, a dark mode is done by switching to a second CSS sheet or having a CSS selector switching between a bright and a dark colour set using JS or the browser preferences. This can lead to some rule repetition:

.light-theme textarea {
    color: black;
    background-color: #8dfece;
}

.dark-theme textarea {
    color: #62b190;
    background-color: black;
}

Dark mode

CSS also has a function to do this color switching on an element without needing to repeat the declaration in a second rule: light-dark(color1, color2) will return color1 if the light environment is preferred by the user and color2 if the dark environment is preferred.

textarea {
    color: light-dark( black, black );
    background-color: light-dark( #8dfece, #62b190 );
}

You still might want to give the user an option to set their preference only for your website, storing that in a cookie. Bootstrap 5 itself has a fairly thorough, live-switching setup for that, but it's also somewhat long. There also is a short JS snippet on Github which is mildly simpler to integrate, but which only does the switching on page load or toggling of the user selection, and not when the user switches their browsers preference.

YouTube creator income breakdown

Tags:

For some time, I've been wondering what the income structure of YouTube (and other) video bloggers looks like. There always was some secrecy about it, likely imposed by the contracts the people have with YouTube.

This post talks about the revenue structure. The summary is that you get 1ct per view, but there is a minimum number of videos/views/hours you need to meet.

The main revenue still seems to come directly from merchandising, stream sponsors and other advertising that's separate in the advertising that YouTube sells.