Aristotle

Currently live on fxhash at www.fxhash.xyz/generative/slug/aristotle

This experiment is live at www.funkyvector.com/#/home/design:aristotle

Famous last words, the creator.

It’s time to tessellate!

The easiest tessellation is squares. The second easiest is triangles, and here we are. Animating triangles was the goal. The idea was similar to hexagon tile, but with 3 less sides and without the morphing colour idea (hexagon_tile picks neighbours and randomly chooses to adopt their colour or morph it). Instead, gradients were applied to each tile and each tile would be animated in and out of existence with a time based animation algorithm.

The other variation from usual FunkyVector graphics which are typically canvas based was to use Scalable Vector Graphics (SVG). Oh, vector, my friend! So easy to render, so easy to manipulate, so easy to animate with CSS transforms and transitions… but, no, not so friendly. It seems impossible to optimise any SVG performance when frame rates are challenged by too many operations. Even with the hardware acceleration of CSS transforms.

Aside from the swirly animations which are once again sin + cos with multiple oscillators, the other interesting feature is the gradient generation. Each variant of the piece has 3 sets of colour schemes, which internally have 16 gradients. These are defined inside SVG’s <defs> attribute.

    const createGradient = (gradientIndex) => {
      const numColours = 2;
      // following logic is good for gradients with 3 or more colours, seems over complicated for 2, but 2 colour gradients look better.
      // basically want to spread gradients from -100% -> 200% so they are smoother
      const start = r.getNumber(-100, 0);
      const end = r.getNumber(100, 200);
      const delta = end - start;
      const stops = Array(numColours)
        .fill()
        .map((_, colourIndex) => {
          const zeroToOne = colourIndex / (numColours - 1);
          const offset = start + zeroToOne * delta;
          return `<stop offset="${offset}%" stop-color="${c.getRandomColour()}"/>`;
        })
        .join("");
      return `<linearGradient id="Gradient${gradientIndex}" x1="0" x2="1" y1="0" y2="0">${stops}</linearGradient>`;
    };

Each triangle picks a Gradient# to use. Thanks SVG, that was easy!

The deadline: World Tessellation Day

This experiment, started in late 2021 is now ready for publishing. Definitely spawned by the Non Fungible Token (NFT) woop-doo, but the NFT craze looks like it’s fading away…

Prediction: NFTs won’t become popular again.

Never the less, this project had to be done. And there was a deadline in mind, not at the time of creation, but come midyear 2022, World Tessellation Day was looming on June 17th (M. C. Escher was born on this date in 1898).

Other recent tessellation

When it comes to tessellation, there are an infinite number of awesome ways to combine regular and irregular 2d objects into a pattern with no gaps. I had some fun with the L shaped Tetris piece a while ago, unfortunately there was a glitch in the algorithm, but only rarely seen. See last image.

Now this is the muddle I like!
Proper flows
Ooops!
This one shows the error in the algorithm. Some pieces wouldn’t fit. Nice side effect though.

Pseudorandom Number Generator (PRNG) Error

The biggest hiccup was the identifying of an error in the FunkyVector pseudo random number generator, which was copy/paste off somewhere on the internet, because bit shifting, large numbers and, well, deterministic random generators are not my forte.

Turns out feeding in a seed as a float rather than an integer caused my rand function to interpret it as a string – a dot (.) is apparently not a valid symbol in a number, duh! This resulted in identical outputs from different hashes on fxhash.xyz. You can see them if you look at Interpolated. (hint #9 & #11)

Thanks to @sho for identifying this! There might be a fix that is backwards compatible but with 1000s of generated images online and with a promise that the FunkyVector app will always generate live links forever, this is risky.

Turns out the fxhash PRNG algorithm has a mild issue too, the same first random number (a call to fxrand()) will occur within 1000 iterations using any seed hash (fxhash), but luckily it will not result in two consecutive numbers which are identical.

const outputs = [];
let i = 0;
while (i < 1e5) {
  const {fxrand} = once(); // once is a fxhash 'factory', basically the fxhash.xyz boilerplate.
  const x = fxrand();
  const y = fxrand();

  const stash = x; // this has a duplicate in <1000 iterations.
  // const stash = y; // as does the second call, ignoring x.
  // const stash = `${x}:${y}`; // but with two consecutive calls, no worries.

  if (outputs.includes(stash)) {
    const foundIndex = outputs.indexOf(stash);
    console.log("found", stash, outputs[foundIndex], foundIndex, i);
    throw new Error("duplicate");
  }
  outputs.push(stash);

  if (i % 1e4 == 0) {
    console.log("progress", i);
  }

  i++;
}

The above outputs something like the following, in this case iteration 155 and 276 have the same first value:

progress 0
found 0.9891815185546875 0.9891815185546875 155 276
testfxhash.js:49
    throw new Error("duplicate");
    ^
Error: duplicate

Instead, commenting out stash = x and enabling const stash = `${x}:${y}` results in:

progress 0
progress 10000
progress 20000
progress 30000
progress 40000
progress 50000
progress 60000
progress 70000
progress 80000
progress 90000
all good after 100000 iterations

Side note, this last test also takes a hell of a lot longer since the array lookup is searching for a long string of numbers rather than finding a number in output.

How was FunkyVector made?

This post was originally written in 2016ish but never published… pressing publish now!

The basic concept was simple. The complexities were intense.

Standardised experiments

On 18 Dec 2014 I decided to publish a bunch of experimental graphics. I have been doing computer graphics in various forms for 25 years, but most usable code is made after circa 2005. There are various reasons to make code open source, in this case it was the attempt to maintain a level of professionalism that public facing code demands. In doing so, a standardised approach was needed, something rarely found in anything experimental. Hopefully I would comment crazy stuff and credit lifted stuff (many snippets have been ported, many one liners are copy+paste).

The first step was to create a structure for the existing generative graphics to adhere to and in doing so common interface for them to expose. An ongoing work in progress, the code is currently haphazard at best, but it used to be a disaster. Some common problems (often found elsewhere) were abstracted into modules like dom (that works in node too), colours (get/set palettes, get/mix colours), geom (line intersections), etc. Basically trying to reduce the reinventing the wheel. Ever had to draw a circle in canvas? I don’t know how many times I have typed something similar to this:

var circleRads = Math.PI * 2;
ctx.drawCircle = function(x, y, r) {
  ctx.arc(x, y, r, 0, circleRads, false);
}

FunkyVector was made as an extension of these experiments so the actual art that FunkyVector uses is still open source; each art piece is simply an experiment that exposes a set of properties. The experiments are loaded by FunkyVector which harnesses their algorithms and displays them in a handy user interface for exposure.

Reproducing on demand

To make a randomly generated graphic reproducible a seeded random implementation was required. Given any random input (originally integer, now optionally alphanumeric) an experiment would produce the exact same results at any resolution. Although a seeded random wasn’t entirely new to me, as is often the case, the real world application of one was!

Out of the box, most experiments flourished with the newly introduced random engine, but very soon into the project some abnormalities surfaced. Computer programs are predictable, and what seemed like strange errors at first would always turn out to be human error: forgetting to reset the random seed or allowing an extra call to the native random() to accidentally slip in, or leaving time based calculations in place. However after fixing these bugs, in some experiments the graphics would sometimes not play fair. After much investigation it turned out to be rounding errors. The winning solution was to adopt a similar approach to WebGL (et al.) fragment shaders in using a domain from zero to one for coordinates: all maths was calculated within the domain of 0 tp 1, and only on the final pass where pixels are actually rendered was values scaled to canvas size.

The first major problem was solved.

Rendering in the browser.

Never an innovator, rarely an early adopter, I like to use things that are at least somewhat established. Not so with canvas, I was on that shit like my mum’s nipple (footnote to having a child) . The vast majority of the experiments were done using canvas’s 2d context. I use canvas all the time, I have loved this concept since flash BitmapData days. This part was easy, and well documented.

Rendering on the server was not so easy.

Thanks to the cool folks it was possible though (respect to nodecanvas contributors). Node canvas is an implementation of canvas that runs in node built on Cairo (follow the rabbit hole). The api is identical to the Dom canvas, and since it runs in node one can make a canvas on a remote server, save it to disk or whatever you like. There is no window, document

The hard part was getting it running on a server, with the fun installation hurdles, and hooking up the application go renser these images oj demand. There still is lots of room for improvement within this code: concurrent rendering? Hah, nice one. Definitely achievable if there was a need for it, but not trivial.

What else?

Things I never wrote about 🙁

Webgl Rendering

Vector Bot

Backwards compatibility

Making it work on fucking phones

stripe + printful

print dimensions

print api