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
 1.0.0    JEM(ZRanger1) 07/30/2021  MIT License

*/ 

// 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[0] = random(1);   // x position 
    b[1] = random(1);   // y position

    b[2] = -0.5+random(1);   // x velocity
    b[3] = -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[0] += b[2] * speed;
    b[1] += b[3] * 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] < 0) { b[0] = 0; b[2] = -b[2]; continue; } 
    if (b[1] < 0) { b[1] = 0; b[3] = -b[3]; continue; }

    if (b[0] > 1) { b[0] = 1; b[2] = -b[2]; continue; }
    if (b[1] > 1) { b[1] = 1; b[3] = -b[3]; 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][0] - x,Points[i][1] - 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[0] = random(1);       // x position 
    b[1] = random(1);       // y position

    b[2] = random(0.02) - 0.05;       // x velocity
    b[3] = 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[0] = frac(b[0] + (b[2] * speed));
    b[1] = frac(b[1] + b[3]);

    // wrap around in the direction of the river's "current"
    if (b[0] < 0) { b[0] = 0.9998; } 
    else if (b[1] < 0) { b[1] = 0.9998;}
    
    // and bounce off the riverbank if we should hit.
    if (b[1] < 0) { b[1] = 0; b[3] = -b[3]; continue; }
    if (b[1] > 1) { b[1] = 1; b[3] = -b[3]; 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][0] - x),abs(Points[i][1] - 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():relaxed:

Someone has to say it,

Great Balls Of Fire