Caterpillar walk in pixels

Hello coder magicians, i am looking for a pattern based on (or inspired by) the caterpillar walk as demonstrated in this video [Gut Check: Caterpillars Walk Gut First - YouTube]
Someone can help me out?

1 Like

Hey @jdhollan -

Two questions for you.

  1. Can you confirm you’re looking for this, but along a straight strip of LEDs? If so, how long is your strip (how many LEDs)? And what’s the spacial density (for example, 60 per meter)? Or, do you have a 2D matrix of pixels and want to show the gut-first dynamic in 2D?

  2. Can you give a sense for where you’re at in your coding journey? Are you looking for this one particular ready-made pattern but aren’t likely to pick up coding on Pixelblaze over the long run? Or do you already know your way around some languages but are stuck on the math for this particular problem?

Thanks Jeff. Yes, i mainly work with 144 l/m strips and a diffuser at 2 cm distance so the individual pixels mix with the adjacent colors. The length is not important, the caterpillar goes from A to B, B to A all 2D
I am into lighting hardware, so my personal coding skills and maths are limited, but once the course is set, friends of mine will be glad to finetune.

The end goal is to make a pixelblaze animated clock dial where the caterpillar is the seconds hand movement.

Here’s something quick to get you started.

Some limitations vs your description, as I understood it:

  • Points shift left and right, vs advancing
  • The total number of pixels and base frequency spacing need to be multiples to repeat a circular cycle correctly
  • The variable walking pulse width means there’s not a perfectly continuous motion (it pauses between pulses)
// Caterpillar
// Jeff Vyduna 2022, MIT License
// For: https://forum.electromage.com/t/caterpillar-walk-in-pixels/2701/2
// Inspiration: https://www.youtube.com/watch?v=ftaZpeG67vw&feature=youtu.be

var spacing = 30
var squeezeFactor = 4
var sharpness = 16
var t1
export function beforeRender(delta) {
  t1 = 2 * (time(.1) - .5)  // -1..1 ramp
}

var lastSign = 1 // 1 = positive or 0 = negative value
export function render(index) {
  var frac = index / pixelCount
  var freq = spacing + squeezeFactor * parabola((frac - t1), sharpness)
  var cycle = sin(freq * frac)
  
  // Detect zero crossings. Assumes stateful, index-ordered rendering.
  // Pixel is on if it follows a zerocrossing in a sinusoid
  v = (cycle >= 0) != lastSign
  lastSign = cycle >= 0
  
  hsv(0, 0, v)
}

// Parabolic pulse. In 0..1, out 0..1, peak centered at x = 0.5
// Higher k gives a narrower pulse
// https://www.desmos.com/calculator/okkks9osla
function parabola(x, k) {
  if (x <= 0 || x >= 1) return 0
  return pow(4 * x * (1 - x), k)
}
6 Likes

@jeff, I think we had just about the same idea - model the stretch/squash movement with a sine-ish waveform. I’m traveling, so it’s a little awkward, but here’s my first take:

// Caterpillar movement test
//
// Drives a multi-segment critter around the screen, using
// a sinusoid to play with the distance between segments.
//
// MIT License
// 12/2022 ZRanger1
//

var wormSize = 0.075;
var segSpacing = .34;
var segments = 5;
var speed = .61;

var timebase = 0;
var t1,t2,t3;
var xoffs = array(segments);
var yoffs = array(segments)

// center origin and scale coords so they range from -1 to 1
translate(-0.5,-0.5); 
scale(2,2)

export function beforeRender(delta) {
  timebase = (timebase + delta / 1000) % 3600;
  t1 = timebase * speed;  // movement speed
  t2 = time(0.1);         // color change speed
  
  // calculate segment distance stretch/squish
  t3 = 0.5+0.5*(1-wave(time(0.03)));

  // move the entire critter along a circular path
  for (i = 0;i < segments; i++ ) {
    var t = t1 + (segSpacing*t3 * (i+1));    
     xoffs[i] = 0.9 * sin(t)
     yoffs[i] = 0.9 * cos(t)
  }
}

export function render2D(index,x,y) {
  var b = 0;

  // Add the light contribution of each segment to the current pixel value
  for (i = 0; i < segments; i++) {
    var px = xoffs[i];
    var py = yoffs[i];

    b += wormSize / hypot(x-px,y-py) ;
  }
  
  b = smoothstep(0.5,5.4,b)  
  hsv(t2,1-b/6,b);
}
3 Likes

Looks like longitudinal waves to me…

numSegments = 5
forwardSpeed = 0.2
mean = 0.5
stddev = 0.2
backwardSpeed = 0.3
A = 0.1
lambda = 0.2
phi = 0

export function beforeRender(delta) {
  t1 = time(forwardSpeed)
  t2 = time(backwardSpeed)
  phi = time(0.3)
}

export function render(index) {
  // start with a travelling wave
  x = frac(t1 + displacement + index/pixelCount*numSegments)

  // set up an opposing wave.
  displacement = A * sin(PI2 * t2 / lambda - PI2 * t2 + phi)

  // turn it into a normal curve
  fx = exp(-1 * pow(x - displacement + 0*mean, 2) / (2*pow(stddev, 2))) / (stddev * sqrt(PI2))  

  hsv(0.3, 1, fx)
}
4 Likes

This is so nice to look at! I post some videos here. The setup is a ring with inner and outer rgb-strip. [Jacob on Instagram: "With the help of some smart coders at Electromages forum this CATERPILLAR movie got live!"]

2 Likes