In Task #10, we did Showers, it’s now time for flowers…
Make something that blooms, grows, or otherwise reminds us of a flower garden.
Short and sweet this time. The usual applies, of course.
In Task #10, we did Showers, it’s now time for flowers…
Make something that blooms, grows, or otherwise reminds us of a flower garden.
Short and sweet this time. The usual applies, of course.
In early this week with Infinity Flower 2D. Generates a new species of flower every few seconds… forever!
It requires PB3, but runs on any mapped 2D array - doesn’t require a rectangular matrix. It’s in the library, and source is below the video (hidden to avoid spoilers.)
/* Infinity Flower
Creates a colorful new flower species every couple of seconds!
Requires a 2D display and appropriate mapping function.
MIT License
Version Author Date
1.0.0 JEM(ZRanger1) 05/07/2021
*/
var radius = array(pixelCount); // holds every pixel's distance from center
var angle = array(pixelCount); // every pixel's positive radial angle from center
var lifespan = 1500; // how long we display each flower
var transitionLength = 1000; // how long the spin/generate phase lasts
var speciesTimer = 9999; // accumulator for frame timing
var drawFrame = renderFirstPass; // on first pass, calculate per-pixel stats
var prerender = beforeNormal; // normal renderer
var tolerance; // comparison tolerance for angles
var maxPetals = 12; // this seems like plenty...
var numPetals; // petals in current species
var petalShape; // rough width of petal
var petalLength; // radius of each petal
var centerHue; // color of flower center
var centerSize; // radius of flower center
var centerBri; // max brightness of flower center
var colorVariant; // enables additional petal coloring
var petals = array(maxPetals); // holds information on each petal
var p1 = array(2); // scratch x,y point array for calculation
// allocate space for petal information. Fields are, in order:
// x,y,angle,hue.
function allocate() {
for (i = 0; i < maxPetals;i++) {
petals[i] = array(4);
}
}
// Create a new flower species with (constrained) randomized parameters.
function initialize() {
numPetals = max(2,floor(random(maxPetals)));
petalLength = 0.35 + random(0.3);
petalShape = 3+random(4);
petalHue = random(1);
centerHue = petalHue + 0.61803; // golden ratio conjugate makes good contrasting colors
centerSize = (random(1) < 0.7) ? 0.11 : 0.1;
colorVariant = random(1) > 0.5;
var hueVariance = (0.1 * (-0.5 + random(1)))
var petalAngle = 2.39996; // golden angle separates petals
p1[0] = 0.5; p1[1] = petalLength + 0.5;
for (i = 0; i < numPetals;i++) {
petals[i][3] = petalHue + ((i % 2) * hueVariance);
setRotationAngle(petalAngle);
rotateVector2D(p1,petals[i]);
petals[i][2] = petalAngle;
petalAngle += 2.39996;
}
setRotationAngle(0);
}
// wrapper for atan2 that returns positive angle 0-PI2
function positiveAtan2(x,y) {
var rad = atan2(x,y);
return (rad >= 0) ? rad : rad + PI2;
}
// set angle for subsequent 2D rotation calls
function setRotationAngle(angle) {
cosT = cos(angle); sinT = sin(angle);
}
var cosT = 0; var sinT = 0;
function rotateVector2D(vIn, vOut) {
var x = vIn[0] - 0.5; var y = vIn[1] - 0.5;
vOut[0] = (cosT * x) - (sinT * y) + 0.5;
vOut[1] = (sinT * x) + (cosT * y) + 0.5;
}
function renderFirstPass(index,x,y) {
x -= 0.5; y -= 0.5;
radius[index] = hypot((x),(y));
angle[index] = positiveAtan2(x,y);
if (index == (pixelCount - 1)) drawFrame = renderNormal;
}
function renderNormal(index,x,y) {
var h,v,pWidth;
// if pixel is outside max radius, nothing to do
if (radius[index] > petalLength) {
rgb(0,0,0);
return;
}
// color and shade flower center pixels
if (radius[index] < centerSize) {
hsv(centerHue,1,centerBri * wave(-0.25+(radius[index]/centerSize*0.9)));
return;
}
// determine for the width of petal at a given radius.
// angular comparison tolerance has to decrease with radius to compensate for
// fixed pixel pitch in display.
tolerance = 0.06*(0.707/radius[index])
pWidth = tolerance * petalShape * (wave(-0.25+(radius[index]/petalLength)));
// color pixel if it lies on a petal.
for (i = 0; i < numPetals; i++) {
v = PI-abs(PI - abs(angle[index] - petals[i][2]));
v = (v <= pWidth) ? max(0.01,v) : 0;
if (v > 0) {
h = petals[i][3] + (colorVariant * radius[index] * 0.7);
break;
}
}
hsv(h, 1-(v*0.33), v)
}
// beforeRender() function that runs when we're building a new flower
function beforeTransition(delta) {
speciesTimer += delta;
// when transition is done, stop spinning and switch back to the
// normal display beforeRender function.
if (speciesTimer >= transitionLength) {
initialize();
speciesTimer = 0;
centerBri = 1;
prerender = beforeNormal;
}
centerBri = 1-(speciesTimer / transitionLength);
for (i = 0; i < numPetals; i++) {
rotateVector2D(petals[i],petals[i]);
petals[i][2] = positiveAtan2(petals[i][0]-0.5,petals[i][1]-0.5);
}
}
// beforeRender() function that displays the current flower species.
function beforeNormal(delta) {
speciesTimer += delta;
// when we're ready to build a new species, start the spinning effect
// and set the beforeRender function to the transition
// generator.
if (speciesTimer >= lifespan) {
prerender = beforeTransition;
setRotationAngle((random(1) < 50) ? 1 : -1);
speciesTimer = 0;
}
}
// main entry point
allocate();
initialize();
// system callbacks
export function beforeRender(delta) {
prerender(delta);
}
export function render2D(index,x,y) {
drawFrame(index,x,y);
}
Wow. Very nice. This is an awesome one.
Thanks, @scruffynerf! This makes me laugh because it just kind of accidentally evolved into something fun along the way. It looks way better live than on the video. Everyone who’s seen it here just stands there and stares at it, waiting for a pretty flower to come up.
I’m building one in a picture frame for my 5 year old twin nieces!
Fractal Flowers. And fractals besides.
/*
Fractal Flower
2021 Ben Hencke
*/
//*********** Settings ***********/
//set up the source matrix dimensions - match to your display for best results
//or set lower for a pixelated mosaic
var width = 24
var height = 24
//globals for dynamic settings
//*********** Settings ***********/
export var iterations = 5
export var drawLevels = 4 //skip drawing some starting iterations
export var scale = .035
export var speed = 7
export var fade = .9
export var angleRange1 = 1
export var angleRange2 = 1
export var replicas = 5
export var spacing = .2
export var useWhite = true
export var usePinwheel = true
export var wrapWorld = false
//globals for calculations
//*********** Settings ***********/
var pixels = array(width * height)
var hues = array(width * height)
var color, branchAngle1, branchAngle2, h, v
//*********** Globals for watching ***********/
export var iter //see how many iterations are run
export var maxValue //the brightest pixel
export var valueFactor = 20 //used to adjust brightness automatically
//*********** UI Controls ***********/
export function sliderIterations(v) {iterations = 1 + floor(v*8)}
export function sliderDrawLevels(v) {drawLevels = 1 + floor(v*8)}
export function sliderScale(v) {scale = v * v * .1}
export function sliderSpeed(v) {speed = 1 + ceil(v * 10) * 3}
export function sliderAngleRange1(v) {angleRange1 = v * 2}
export function sliderAngleRange2(v) {angleRange2 = v * 2}
export function sliderTrails(v) {fade = v && (v * .5 + .5)}
export function sliderReplicas(v) {replicas = 1 + floor(v*12)}
export function sliderSpacing(v) {spacing = v/2}
export function sliderWhiteMode(v) {useWhite = v > .5}
export function sliderPinwheelMode(v) {usePinwheel = v > .5}
export function sliderWrapMode(v) {wrapWorld = v > .5}
//*********** Utility Functions ***********/
//map an x and y into a 1D array
function getIndex(x, y) {
var res = floor(x*width) + floor(y*height)*width
return res
}
function blendHue(h1, v1, h2, v2) {
v = v1+v2
//rotate hues so that they are closer numerically
if (h2 - h1 > .5)
h2 -= 1
if (h1 - h2 > .5)
h1 -= 1
//average the hues, weighted by brightness
h = (h1 * v1 + h2 * v2) / v
}
//*********** Fractal Implementation ***********/
function fractal(x, y, a, i) {
var index, l
iter++ //keep track of how many calls we've made
//move coordinates in direction vector for our angle
//each iteration travels a smaller distance
//but don't travel for the first iteration
if (i < iterations) {
l = i * scale + scale
x += sin(a) * l;
y += cos(a) * l;
}
//make coordinates "wrap" around to the other side
if (wrapWorld) {
x = mod(x,.99999)
y = mod(y,.99999)
}
//skip earlier levels, and only draw "on screen"
if(i <= drawLevels && x >= 0 && x <= .99999 && y >= 0 && y <= .999999) {
index = getIndex(x,y)
// blendHue(hues[index], pixels[index], iter * .004 + color , hues[index] + 1)
blendHue(hues[index], pixels[index], i * .1 + color, 1)
hues[index] = h
pixels[index] = v
}
if (--i > 0) {
//if there are more iterations left, recurse to this function adding rotations for each branch
fractal(x, y, a + branchAngle1, i)
fractal(x, y, a + branchAngle2, i)
}
}
//*********** Rendering ***********/
export function beforeRender(delta) {
var startingAngle, i
//update globals used by the fractal
color = time(1 / speed)
branchAngle1 = -1 + sin(wave(time(4.4 / speed))*PI2) * PI * angleRange1
branchAngle2 = .5 + sin(wave(-time(11 / speed))*PI2) * PI * angleRange2
startingAngle = sin(time(3 / speed) * PI2) * PI
iter = 0
if (replicas > 1) {
for (i = 0; i < replicas; i++) {
if (usePinwheel) {
//pinwheel - rotate petal in place
fractal(0.5 + spacing * sin(i/replicas * PI2), 0.5 + spacing * cos(i/replicas * PI2), startingAngle + i/replicas * PI2, iterations)
} else {
//roate petals around center
fractal(0.5 + spacing * sin(i/replicas * PI2 + startingAngle), 0.5 + spacing * cos(i/replicas * PI2 + startingAngle), 0*startingAngle + i/replicas * PI2, iterations)
}
}
} else {
//for a single fractal instance, ignore spacing and draw in center.
fractal(.5,.5, startingAngle, iterations)
}
//adjust valueFactor to scale brightness based on the last maxValue found in the previous render
//this helps bring out more detail when many fractal dots overlap
//do this gradially over time to avoid flickering
valueFactor = clamp(valueFactor*.95 + maxValue*.05, 1, 100)
maxValue = 0
}
export function render2D(index, x, y) {
index = getIndex(x, y) //figure out index from coordinate
v = pixels[index]
//fade out pixel values
pixels[index] = v * fade
maxValue = max(maxValue, v) //keep track of the brightest pixel
v = v / valueFactor //scale brightness down
v = v*v //give things a bit more contrast
if (useWhite)
s = 1 - v //highlight bright pixels by shifting toward white
else
s = 1
hsv(hues[index], s, v)
}
Here’s a mesmerizing 13 minutes of playing with sliders:
(Video recording stalled out a little at places, but animation was smooth in person)
Wow. I think this is the most complex/varied generative art we’ve seen on PB so far.
@wizard, that is awesome! That’s what it looks like when you step through the stargate!
If you look back over the tasks, you can see they’ve been stretching everybody’s thinking about what you can ask a Pixelblaze to do.
From about the snake onward, we’re seeing complex and visually rich 2d stuff that I don’t see many people doing on any other LED platform. I love it! Let’s keep going!
A thought: A lot of folks have joined us since the v3 launch. It might worth starting in 1D again with a “series 2” at some point, to expand the range of features and techniques, and give newcomers a more accessible entry point.
I was planning on doing some revisiting of the older tasks, including providing a working solution (and maybe a howto) for each, if there wasn’t a good base solution.
I agree, going back to 1D ‘tasks’ could be useful, in fact, I was thinking about considering alternative display methods (like backlighting, where the lights are not visible directly, but only as a wall reflection) So likely, I’ll do things in waves (so to speak), and do some 1Ds, then more 2Ds, maybe work up towards a easy cheap 3D setup (which is a different discussion)
and btw, @zranger1 your above code inspired the next task, an old favorite topic of mine.
Speaking of waves… I’d like to suggest a constrained task for 1D. The idea would be to explore a particular function, like wave()
- e.g. given this starting template, using only time()
and wave()
and basic math operators.
export function render(index) {
var h, s, v, x
x = index/pixelCount
// make a pattern using wave() and time() and basic math operators
// what happens when you set one to wave(x + time(.1)) you get a moving wave!
// or wave(x + wave(time(.1))) a wave that moves in a wave (back and forth)!
// or wave(x + time(.1)) + wave(x - time(.06)) waves that move into each other
// try even more crazy combinations of wave an time and x, and in different aspects
// of the color by using them in h, s, or v
h = 0
s = 1
v = 1
hsv(h, s, v*v)
}
Sort of like functional golfing, but with the idea that you get really familiar with a few of the basic animation tools. Thoughts?
Yeah, that sorta matches the approaches I was thinking… The more I play with Tixy (and tixy like stuff) where the abstraction down to a single line allows you to see how small changes and using variables/math does all of the hard work…
I have a task lined up this weekend… And that’ll be #16, so 4 months now of tasks… So going back to a 1D for a while makes sense.
Super cool that the Academy of Wizardry and Enchantment has been going for 4 months already! Thanks for driving things, professor @Scruffynerf!
Another free book for those looking to delve deep:
http://algorithmicbotany.org/papers/#abop
About this book: