I made a snake for my pentagonal hexecontahedron. I’m using the Perceptual Hue code from @jeff to expand on the reds and yellows.
It’s a simple algorithm — keep an array of cell indices, and randomly choose a new cell to go towards, preferring not to overlap yourself unless necessary — but choosing the next cell to enter is complicated because of the shape of the orb cells. I had to manually create the adjacency graph for the orb, which didn’t take too long.
I won’t be uploading this to the pattern library, but I’ll include it here for reference. Warning: not my best code
// Simple snake pattern
// Copyright 2022 Tom Clark
// MIT License — feel free to use it however you want
// The way it works is we have a head and an array of tail segments
// - The minimul snake length is 2 (including the head)
// - Snake grows at the neck (new segments are a clone of the first tail segment)
// - Snake shrinks at the end of the tail
// - Snake tries to not overlap itself when moving around, but can if it's trapped (NB there's a flicker then)
// - Colors are Perceptual Hue by @jeff on Electromage Forum
// how fast the snake moves from 100ms to 1000ms
export var x
export var delay
export function sliderDelay(n) {
delay = n*975 + 25
}
// how long the snake is
export var snake_length = 10
var tail_length = snake_length - 1
export function sliderSnakeLength(n) {
snake_length = floor(n*28)+2
tail_length = snake_length - 1
}
// the starting color aka the color of the head
export var color = 1
export function sliderHeadColor(n) {
color = n
}
// The remaining colors of the body, spread across the length of the snake.
// this maps 0, 0.25, 0.5, 0.75, 1.0 to 0, 0.5, 1.0, 1.5, 2.0 so there's more of the lower end to choose from.
export var palete = 1
export function sliderBodyPalette(n) {
if(n<0.5) {
palete = 2*n
} else {
palete = (n-0.5)*2+1
}
}
// initial values
var tail = array(10)
var head = 1
export var elapsed = 0
// Here we update the global state based on the sliders.
// We also keep track of when the next "frame" is aka when the snake moves into the next cell
export function beforeRender(delta) {
elapsed += delta
// enough time has passed to compute the next frame
if(elapsed > delay) {
elapsed = elapsed % delay
// resize the snake if necessary
if(tail_length < tail.length) { // shrink
tail = slice(tail, tail.length - tail_length, tail.length)
} else if(tail_length > tail.length) { // grow
new_tail = array(tail_length)
// new body segments are the same as the first tail segment (snake grows at the neck)
for(i = 0; i < tail_length - tail.length; i++) {
new_tail[i] = tail[0]
}
// copy the rest of the tail to the end of the new tail
for(i = 0; i < tail.length; i++) {
new_tail[i+ tail_length - tail.length - 1] = tail[i]
}
tail = new_tail
}
// grow
next = neighbor(tail, head)
for(i = 0; i < tail.length - 1; i++) {
tail[i] = tail[i+1]
}
tail[tail.length-1] = head
head = next
}
t1 = time(.1)
}
export function render(index) {
h = t1 + index
s = 1
v = 1
remaining = (delay-elapsed) / delay
// should we render this pixel? - we only render cells that are on the snake
for(i = 0; i < tail.length; i++) {
if(index == tail[i]) {
// fade this pixel based on the current frame time and the distance to the head
h = v = (remaining + i) / (tail.length+1)
hsv(colorize(h), s, v)
}
}
// head fades in from black - this causes a flicker when it overlaps itself
if(index == head) {
h = (remaining + tail.length) / (tail.length+1)
v = elapsed/delay
hsv(colorize(h), s, v)
}
}
// helper function
function colorize(h) {
return fixH(palete*h+color)
}
// randomly choose a new cell to go into, but keep randomly picking if we're already in that cell
// - give up after 60 iterations and overlap ourself
function neighbor(prev, curr) {
next = prev[0]
iterations = 0
while(iterations < pixelCount && included(prev, curr, next)) {
cell = neighbors[curr-1]
random_index = floor(random(cell.length))
next = cell[random_index]
iterations++
}
return next
}
// Array.includes?(tail, head, value)
function included(arr, extra, e) {
for(i = 0; i < arr.length; i++) {
if(e == arr[i]) {
return true
}
}
return e == extra
}
// Array.slice(arr, start, end)
function slice(a, s, e) {
if(s == e) { return [] }
sliced = array(e-s)
for(i = 0; i+s < e; i++) {
sliced[i] = a[i+s]
}
return sliced
}
/* fixH(pH) => h
Returns 0-1 hue values for hsv()
Takes a "perceptual hue" (pH) that aspires to progress evenly across a human-perceived rainbow
*/
var hMapSize = 10
var hMap = array(hMapSize+1)
// The values below were subjectively chosen for perceived equidistant color
hMap[0] = 0.00 // red
hMap[1] = 0.015 // orange
hMap[2] = 0.08 // yellow
hMap[3] = 0.30 // green
hMap[4] = 0.44 // cyan
hMap[5] = 0.65 // blue
hMap[6] = 0.70 // indigo
hMap[7] = 0.77 // purple
hMap[8] = 0.985 // pink
hMap[9] = 1.00 // red again - same as 0
hMap[10] = 1.00 // overflow bin
function fixH(pH) { // pH = "Perceptual Hue"
pH = pH % 1 // Wrap inputs
binWidth = 1/(hMapSize - 1) // A 10-point map divides hue's 0-1 phase into 9 arcs of length (binWidth) 0.111
bin = floor(pH / binWidth) // Calculate pH's starting bin index, 0..(hMapSize-1)
binPct = (pH % binWidth) / binWidth // Find pH's percentage into that bin index
base = hMap[bin] // base value in hsv()'s h unit
gap = hMap[bin + 1] - base // gap is the distance in hsv()'s h units between this base bin and the next
return base + binPct * gap // Interpolate the result between the base bin's h value and the next bin's
}
// Pentagonal Hexecontahedron cells. Some are missing because I've yet to close up the last 5.
var neighbors = [
[ 2, 5, 6,10,11],
[ 1, 3,11,15,16],
[ 2, 4,16,20,21],
[ 3, 5,21,25,26],
[ 1, 4, 6,26,29],
[ 1, 5, 7,10,29],
[ 6, 8,29,30,31],
[ 7, 9,31,32],
[ 8,10,12],
[ 1, 6, 9,11,12],
[ 1, 2,10,12,15],
[ 9,10,11,13],
[12,14,54],
[13,15,17,54,55],
[ 2,11,14,16,17],
[ 2, 3,15,17,20],
[14,15,16,18,55],
[17,19,43,51,55],
[18,20,22,42,43],
[ 3,16,19,21,22],
[ 3, 4,20,22,25],
[19,20,21,23,42],
[22,24,39,41,42],
[23,25,27,39,40],
[ 4,21,24,26,27],
[ 4, 5,25,27,29],
[24,25,26,28,40],
[27,30,35,36,40],
[ 5, 6, 7,26,30],
[ 7,28,29,31,35],
[ 7, 8,30,32,35],
[ 8,31,33],
[32,34,47,48],
[33,35,36,37,47],
[28,30,31,34,36],
[28,34,35,37,40],
[34,36,38,46,47],
[37,39,41,45,46],
[23,24,38,40,41],
[24,27,28,36,39],
[23,38,39,42,45],
[19,22,23,41,43],
[18,19,42,44,51],
[43,45,50,51,52],
[38,41,44,46,50],
[37,38,45,47,50],
[33,34,37,46,48],
[33,47,49],
[48,50,52,53],
[44,45,46,49,52],
[18,43,44,52,55],
[44,49,50,51,53],
[49,52,54],
[13,14,53,55],
[14,17,18,51,54],
]