img = loadImage('https://upload.wikimedia.org/wikipedia/commons/1/17/BlankMap-World-noborders.png')
createCanvas(windowWidth, windowHeight)
warp = createFilterShader(`precision highp float;
float mod289(float x){return x - floor(x * (1.0 / 289.0)) * 289.0;}
vec4 mod289(vec4 x){return x - floor(x * (1.0 / 289.0)) * 289.0;}
vec4 perm(vec4 x){return mod289(((x * 34.0) + 1.0) * x);}
d = d * d * (3.0 - 2.0 * d);
vec4 b = a.xxyy + vec4(0.0, 1.0, 0.0, 1.0);
vec4 k2 = perm(k1.xyxy + b.zzww);
vec4 o1 = fract(k3 * (1.0 / 41.0));
vec4 o2 = fract(k4 * (1.0 / 41.0));
vec4 o3 = o2 * d.z + o1 * (1.0 - d.z);
vec2 o4 = o3.yw * d.x + o3.xz * (1.0 - d.x);
return o4.y * d.y + o4.x * (1.0 - d.y);
float noiseOctaves(vec3 n) {
for (int octave = 0; octave < 3; octave++) {
float scale = pow(2., float(octave));
res += noise(n * scale) / scale;
float map(float value, float min1, float max1, float min2, float max2) {
return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
vec2 coord = vTexCoord + (vec2(
noiseOctaves(vec3(vTexCoord * canvasSize * 0.005, seed)),
noiseOctaves(vec3(vTexCoord * canvasSize * 0.005, 100. + seed))
coord += vec2(sin(coord.y * 4. * fract(seed/1230.) + 10.*fract(seed/4560.)), sin(coord.x * 10. * fract(seed/1000.) + 10.*fract(seed/1000.)))*0.05;
gl_FragColor = texture2D(tex0, coord);
edges = createFilterShader(`precision highp float;
vec2 root = vTexCoord * canvasSize;
for (int i = 0; i < 12; i++) {
float angle = float(i)/12. * ${TWO_PI};
vec2 sample = root + r*vec2(cos(angle), sin(angle));
total += texture2D(tex0, sample/canvasSize).r;
gl_FragColor = total > 0.4 && total < 0.6 ? vec4(0.,0.,0.,1.) : vec4(1.);
image(img, 0, 0, width, height, 0, 0, img.width, img.height, CONTAIN)
warp.setUniform('seed', millis())
filter(BLUR, min(width, height)*0.01)
for (let i = 0; i < 2000; i++) {
const x = floor(random(width))
const y = floor(random(height))
if (pixels[(y*width + x)*4] < 100) {
pts.push(createVector(x, y))
const candidates = pts.map(p => [p])
while (candidates.length > 1) {
const string = candidates.shift()
const dists = candidates.map((other) => {
string.at(0).dist(other.at(0)),
string.at(0).dist(other.at(-1)),
string.at(-1).dist(other.at(0)),
string.at(-1).dist(other.at(-1)),
const minDist = min(dists)
return { minDist, dists }
const minDist = min(dists.map(d => d.minDist))
const idx = dists.map(d => d.minDist).indexOf(minDist)
const [other] = candidates.splice(idx, 1)
const distIdx = dists[idx].dists.indexOf(minDist)
candidates.push([...other.reverse(), ...string])
} else if (distIdx === 1) {
candidates.push([...other, ...string])
} else if (distIdx === 2) {
candidates.push([...string, ...other])
candidates.push([...string, ...other.reverse()])
curveVertex(candidates[0][0].x, candidates[0][0].y)
for (const pt of candidates[0]) {
curveVertex(candidates[0].at(-1).x, candidates[0].at(-1).y)