xxxxxxxxxx
// Rorschach living cells
//
// Created by Nicolas Decoster for the
// topic "Botanical 🌴" of #WCCChallenge
//
// Use a SDF like approach with Perlin noise and symetries.
//
// At each step:
// * Fade out the previous content of canvas by applying
// white mask with a small alpha.
// * Use a large number of plotters
// * For each plotter:
// - go at a random position
// - pick a Perlin noise value based on the distance/direction
// from the center, and the time
// - pick a color on a linear palette based on this
// value
// - set the plotter radius: bigger at the center of the
// value range for the current color, smaller at color
// transitions
// - the plotter draw a circle at the given position, with
// the given color and radius.
const params = {
size: 600,
speed: 0.001,
scale: 0.008,
nPetals: 5,
maxPlotterRadius: 8,
// In frame count unit.
cycleDuration: 300,
}
const offsets = {
x: params.size/2,
y: params.size/2,
}
const min = (a, b) => a < b ? a : b
// Get plotter characteristics from a value in [0, 1] intervall.
//
// For the plotter color, an array of colors is equally mapped
// between 0 and 1.
//
// The plotter radius start from zero and increase to its maximum
// at the middle of the color range of values. And decreases after
// that to zero again. The idea is to have smaller plotters at
// color trransitions.
//
// Call drawPlottersPalette() to see a mapping of plotters for
// values from 0 to 1.
function getPlotter(value) {
const white = color(255, 255, 255)
const alpha = 255
const blue1 = color(150, 194, 225, alpha)
const blue2 = color(118, 151, 189, alpha)
const pink = color(252, 142, 172, alpha)
const colors = [
white,
pink,
blue2,
blue1,
blue2,
blue1,
pink,
blue2,
blue1,
white,
white,
]
const n = colors.length
// Size of an intervall.
const size = 1 / n
// Corresponding color index for the current value.
const index = Math.floor(value/size)
// Corresponding [0,1] in the current color intervall.
// I.e. if there are 2 colors, and value is 0.6, this will
// be 0.2. I.e size is 0.5, and 0.6 is at 20 % of the current
// color intervall (last color).
const valueInIntervall = (value - (index * size))/size
// To get the radius, with fading ones on color transition.
const center = 0.5
const radiusScale =
2 * (center - Math.abs(valueInIntervall - center))
return {
color: colors[index],
radius: params.maxPlotterRadius * radiusScale
}
}
// This will draw all plotters for values from 0 to 1, to see what
// they all look like.
function drawPlottersPalette() {
const n = 100
for (let i = 0; i < n; i++) {
const plotter = getPlotter(i/n)
fill(plotter.color)
noStroke()
const step = 5
circle(
50 + i*step,
50,
plotter.radius
)
}
}
// Apply some white shadowing on each frame to fade out previous
// plots.
function fadeOut() {
// Start a new plot every now and then.
if (frameCount % params.cycleDuration === 0) {
clear()
}
// Actually do the fade out.
fill(255, 10)
noStroke()
rect(0, 0, params.size, params.size)
}
function rotatePlotter(x0, y0, angle) {
const cos = Math.cos(angle)
const sin = Math.sin(angle)
const x = x0 * cos - y0 * sin
const y = x0 * sin + y0 * cos
return [x, y]
}
// *********************************************************
function setup() {
createCanvas(params.size, params.size)
// Comment to randomize the plot
noiseSeed(7821)
}
function draw() {
fadeOut()
// Uncomment the following to see the plotters palette at the
// top of the canvas.
// drawPlottersPalette()
const nPlotters = 2000
for (let i = 0; i < nPlotters; i++) {
const x = Math.random() * params.size - params.size/2
const y = Math.random() * params.size - params.size/2
const distanceToCenter = Math.hypot(x, y)
const directionToCenter = Math.atan2(x, y)
const distanceLike = Math.log(distanceToCenter/3)
const angleLike = 0.5 * Math.sin(directionToCenter * params.nPetals/2)
// The radius of the flower. Very small at the beginning of
// the cycle, growing for a short time, then a fix value.
const maxRadius = params.size * 0.3
const radius = maxRadius * min(
1,
(frameCount % params.cycleDuration)/(params.cycleDuration/3)
)
// Get plotter properties randommly based on its distance
// and direction to the center. In fact, those lines is
// the part that has the most impact in the final rendering.
const val = noise(
distanceLike,
angleLike,
frameCount * params.speed
)
const bloomingFactor = min(1, 2 * distanceToCenter/radius)
const plotter = getPlotter(val * bloomingFactor)
if (distanceToCenter > radius) {
// Fade out the plotter outside the radius
plotter.color.setAlpha(255 - 5 * (distanceToCenter - radius))
}
// Actually plot the dot
fill(plotter.color)
noStroke()
circle(
x + offsets.x,
y + offsets.y,
plotter.radius
)
}
}