# New Patterns: Things you can do with Voronoi distance!

I’ve been playing with different ways of rendering Voronoi distance fields – the distance from each pixel to one or more “control points”.

To produce a “normal” Voronoi diagram, you’d paint each pixel the color of the closest control point. The two patterns I just posted to the library show a couple of the many other things you can do with Voronoi distance. Here’s the source:

Metaballs of Fire 2D
``````/*
Metaballs - blobs of fire that combine and split as
they move around the display.

Requires 2D display and appropriate mapping function.

Version  Author        Date        Comment

*/

// array of vectors for each point
var maxPoints = 8;
var Points = array(maxPoints);

// current settings
export var numPoints = 5;
export var speed = 0.05;
export var splatter = 1.75;

// UI
export function sliderNumberOfPoints(v) {
var n;
n = floor(4 + (v * (maxPoints - 4)));
if (n != numPoints) {
numPoints = n;
splatter = 1.5+(numPoints - 4)/7.8;
initPoints();
}
}

export function sliderSpeed(v) {
speed = 0.15 * v;
}

// create control point vectors with random position,
// direction and speed
function initPoints() {
for (var i = 0; i < numPoints; i++) {
var b = Points[i];

b = random(1);   // x position
b = random(1);   // y position

b = -0.5+random(1);   // x velocity
b = -0.5+random(1);   // y velocity
}
}

// allocate and initialize control point descriptors
function createPoints() {
for (var i = 0; i < maxPoints; i++) {
Points[i] = array(4);
}

initPoints();
}

// move points, bouncing them off the "walls" of the display.
function bounce() {
for (var i = 0; i < numPoints; i++) {
var b = Points[i];

// move point according to velocity component of its vector
b += b * speed;
b += b * speed;

// bounce off walls by flipping vector element sign when we hit.
// If we hit a wall, we exit early, trading precision in
// corners for speed.  We'll catch it in a frame or two anyway
if (b < 0) { b = 0; b = -b; continue; }
if (b < 0) { b = 0; b = -b; continue; }

if (b > 1) { b = 1; b = -b; continue; }
if (b > 1) { b = 1; b = -b; continue; }
}
}

// initialize animated points
createPoints();

// move the control points around the display.
export function beforeRender(delta) {
bounce();
}

// calculate voronoi distance field -- for every pixel, find the distance
// to the nearest control point, and choose to color (or not) based on the
// minimum distances.
export function render2D(index,x,y) {
var minDistance,i,r,h,v;

// this is just like normal voronoi distance, except instead of comparing pairs
// of control points, we compare their product to build the metaball distance field.
minDistance = 1;
for (i = 0; i < numPoints; i++) {
r = minDistance * hypot(Points[i] - x,Points[i] - y) * splatter;
minDistance = min(r,minDistance);
}

if (minDistance >= 0.082) {
rgb(0,0,0);
} else {
hsv(0.082-minDistance,1,1.2-(wave(5*minDistance)));
}
}
``````
Ice Floes 2D
``````/* Ice Floes 2D

A river filled with floating ice!  Uses Voronoi distance to simulate
blocks of ice drifting and turning in the current.

Requires a 2D display and appropriate mapping function.

Version  Author     Date        Comment
1.0.1    ZRanger1   7/30/2021   Faster
*/

// animation control
var frameTimer = 9999;
var simulationSpeed = 60;

// array of vectors for each point
var numPoints = 4;
var Points = array(numPoints);
export var speed = .575;

// UI
export function sliderSpeed(v) {
speed =  2 * v;
}

// create vectors with random position, direction, speed and color.
function initPoints() {
for (var i = 0; i < numPoints; i++) {
var b = Points[i];

b = random(1);       // x position
b = random(1);       // y position

b = random(0.02) - 0.05;       // x velocity
b = 0.015 * (random(1) - 0.5) ; // y velocity
}
}

// allocate and initialize point descriptors
function createPoints() {
for (var i = 0; i < numPoints; i++) {
Points[i] = array(4);
}
initPoints();
}

// move objects
function doRiver(delta) {
for (var i = 0; i < numPoints; i++) {
var b = Points[i];

// move point according to velocity component of its vector
b = frac(b + (b * speed));
b = frac(b + b);

// wrap around in the direction of the river's "current"
if (b < 0) { b = 0.9998; }
else if (b < 0) { b = 0.9998;}

// and bounce off the riverbank if we should hit.
if (b < 0) { b = 0; b = -b; continue; }
if (b > 1) { b = 1; b = -b; continue; }
}
}

function wrappedEuclid(dx,dy) {
if (dx > 0.5) { dx = 1-dx; }
if (dy > 0.5) { dy = 1-dy; }
return hypot(dx,dy);
}

// initialize animated points
createPoints();

export function beforeRender(delta) {
frameTimer += delta;

if (frameTimer > simulationSpeed) {
doRiver(frameTimer);
frameTimer = 0;
}
}

// for each pixel, find the nearest point. We do this exhaustively with no
// attempt to optimize, because we also need to deal with the pixels that are
// about equally close to two or more points.  These pixels become the "cracks"
// in our ice floes.
export function render2D(index,x,y) {
var minDistance,i,r,h,v;

minDistance = 1;

for (i = 0; i < numPoints; i++) {
// calculate euclidean distance to nearest control point
r = wrappedEuclid(abs(Points[i] - x),abs(Points[i] - y));

if (r <= minDistance) {
// if distances are very similar, mark boundary pixels by coloring
// them dark blue.
h = (abs(r - minDistance) < 0.12) ? 0.6667 : 0.55 + (r * .15);
minDistance = r;
}
}

// draw pixel
var bri = 1-minDistance; bri = bri*bri*bri;
hsv(h,(h == 0.6667) ? 1 : 1.21-bri,bri)
}
``````

And here are videos. Not the best I’ve ever made – we’ve been having fairly amazing windstorms all week, and I made and uploaded these between power outages. But they get the point across.

3 Likes

Super cool! I have a similar thingy for bouncing fuzzy balls on a cylindrical project I have on the backburner but I’ll pull it out and try making the Voronoi code work in that topology.

Oh wait! `wrappedEuclid()` Someone has to say it,

Great Balls Of Fire