Loading files
e5b871ed5This file is pinned to the selected release. Download the original to open it in your design tool.
// ═══════════════════════════════════════════════════════════
// PatternFlow - Shared noise helpers (Perlin 2D, fractal noise)
// Stateless; safe to call from any pattern's draw().
// License: MIT
// ═══════════════════════════════════════════════════════════
#pragma once
// Shared verbatim with loadable pattern modules — see the note in core_math.h.
#ifndef PF_MODULE_BUILD
#include <Arduino.h>
#endif
#include <math.h>
#include <stdint.h>
#include "core_math.h" // ifloor: floorf is a libm call on this target
namespace PFNoise {
inline const uint8_t perm[512] = {
151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,
8,99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,
35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71,
134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,
55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,89,
18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,
226,250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,
17,182,189,28,42,223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,
155,167,43,172,9,129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,
104,218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,
235,249,14,239,107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,
45,127,4,150,254,138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,
215,61,156,180,
151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,
8,99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,
35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71,
134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,
55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,89,
18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,
226,250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,
17,182,189,28,42,223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,
155,167,43,172,9,129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,
104,218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,
235,249,14,239,107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,
45,127,4,150,254,138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,
215,61,156,180
};
inline float fade(float t) {
return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f);
}
inline float grad2(int hash, float x, float y) {
int h = hash & 7;
float u = (h < 4) ? x : y;
float v = (h < 4) ? y : x;
return ((h & 1) ? -u : u) + ((h & 2) ? -2.0f * v : 2.0f * v);
}
inline float lerpF(float a, float b, float t) {
return a + (b - a) * t;
}
// Classic 2D Perlin. NOT normalised to -1..1: grad2 returns +/-u +/- 2v, so
// its gradient vectors are (1,2)-shaped with length sqrt(5), and the field
// runs to about +/-1.51. Perlin's own grad() has no factor of two; this is
// the artefact of a 3D routine adapted to 2D, and it is left alone because
// changing it changes every pattern that has ever used it. The common idiom
// (n + 1) * 0.5 therefore does NOT give 0..1 - it clips at both ends. Scale
// by 1/1.51, or clamp, if you need a bounded value.
// Pinned by toolchain/check_math.py.
inline float perlin2D(float x, float y) {
// One floor per axis; the fractional part is the same subtraction it
// always was, from the integer already in hand.
const int xi = PFMath::ifloor(x);
const int yi = PFMath::ifloor(y);
int X = xi & 255;
int Y = yi & 255;
x -= (float)xi;
y -= (float)yi;
float u = fade(x);
float v = fade(y);
int A = perm[X] + Y;
int B = perm[X + 1] + Y;
float n00 = grad2(perm[A], x, y);
float n10 = grad2(perm[B], x - 1.0f, y);
float n01 = grad2(perm[A + 1], x, y - 1.0f);
float n11 = grad2(perm[B + 1], x - 1.0f, y - 1.0f);
return lerpF(lerpF(n00, n10, u), lerpF(n01, n11, u), v);
}
inline float fractal2D(float x, float y, int octaves, float roughness) {
float sum = 0.0f, amp = 1.0f, maxAmp = 0.0f, freq = 1.0f;
for (int i = 0; i < octaves; i++) {
sum += perlin2D(x * freq, y * freq) * amp;
maxAmp += amp;
amp *= roughness;
freq *= 2.0f;
}
return sum / maxAmp;
}
// O(1) integer-cell hash → 0..1, for "random value per grid cell" (voronoi
// seeds, cell colors/phases, glitch cells). Use this INSTEAD of the classic
// fract(sin(gx * 127.1 + gy * 311.7) * 43758.5453) formula: that one needs a
// full-precision sinf — feeding it PFMath::fastSin amplifies the sin LUT's
// tiny error ~44,000× and destroys the hash (visible banding/correlation) —
// while this is two table reads with no trig at all.
inline float cellHash(int gx, int gy) {
return (float)perm[(perm[gx & 255] + (gy & 255)) & 255] * (1.0f / 255.0f);
}
// Seeded variant so multiple layers/uses in one pattern decorrelate.
//
// It did not decorrelate anything. The seed was added to gx before the same
// hash, so cellHash(x, y, s) was cellHash(x + s, y) at every one of 262,144
// lattice points - two layers seeded 0 and 1 were one field and a copy of it
// slid a cell sideways. preset_0713 shows the cost: it draws seeds 7 through 13
// for the speed, phase and brightness of each firefly, and got one sequence
// offset by one index each time, so every "independent" parameter of a firefly
// was the same random stream in disguise.
//
// The seed cannot enter by addition ANYWHERE in this expression, and that is the
// non-obvious part. Adding it to the final index is the same as adding it to gy;
// adding it to both coordinates just translates diagonally. Measured over the
// lattice against every shift within +/-4 cells, the obvious repair - seed on gx
// and gy both - still correlates 1.0000 with the unseeded field. The seed has to
// pass through the table, because perm[] is the only non-linear step available:
// perm[v + k] bears no relation to perm[v]. Same measurement for what is written
// below: 0.0128, which is the noise floor of the measurement.
//
// Two extra lookups into a 512-byte table that is already hot. The two-argument
// overload above is untouched, so valueNoise2D, perlin2D and fractal2D render
// exactly as before; only calls that pass a seed change.
inline float cellHash(int gx, int gy, int seed) {
const int k = perm[seed & 255];
return (float)perm[(perm[(perm[(gx + k) & 255] + (gy & 255)) & 255] + k) & 255] *
(1.0f / 255.0f);
}
// Smooth value noise: smoothstep-blended cellHash lattice, output 0..1.
// Cheaper than perlin2D (no gradient dot products) — a good default for soft
// organic fields; reach for perlin2D/fractal2D when you need richer structure.
inline float valueNoise2D(float x, float y) {
int X = PFMath::ifloor(x);
int Y = PFMath::ifloor(y);
float fx = x - (float)X;
float fy = y - (float)Y;
float ux = fx * fx * (3.0f - 2.0f * fx);
float uy = fy * fy * (3.0f - 2.0f * fy);
float n00 = cellHash(X, Y);
float n10 = cellHash(X + 1, Y);
float n01 = cellHash(X, Y + 1);
float n11 = cellHash(X + 1, Y + 1);
return lerpF(lerpF(n00, n10, ux), lerpF(n01, n11, ux), uy);
}
} // namespace PFNoise