generative/app/8/index.js
2019-11-18 00:39:02 -05:00

210 lines
6.6 KiB
JavaScript

new p5(sketch => {
sketch.disableFriendlyErrors = true;
// reused dimensions and a seed
let seed, width, height, noiseResolution, overdraw, blurQuality;
const layers = {}; // offscreen layers
const shaders = {}; // shaders
const lib = {}; // libraries
const assets = {}; // fonts, images, sound files
sketch.preload = () => {
shaders.whiteNoise = sketch.loadShader(
"../shaders/base.vert",
"../shaders/white-noise.frag"
);
assets.stroke = sketch.loadImage("./brush-100x30.png");
};
sketch.setup = () => {
filenamePrefix = "seigler-p5-8-terra-firma-";
overdraw = 0.1;
width = Math.floor(sketch.windowWidth * (1 + overdraw));
height = Math.floor(sketch.windowHeight * (1 + overdraw));
noiseResolution = [0.2, 0.1, 0.05, 2];
blurQuality = 2;
window.onhashchange = () => {
seed = window.location.hash.substr(1);
generate();
};
seed = window.location.hash.substr(1);
sketch.colorMode(sketch.HSL, 1);
sketch.createCanvas(sketch.windowWidth, sketch.windowHeight);
layers.contours = sketch.createGraphics(width, height);
layers.contours.colorMode(sketch.HSL, 1);
layers.noise = sketch.createGraphics(width, height, sketch.WEBGL);
// layers.blur1 = sketch.createGraphics(width, height, sketch.WEBGL);
// layers.blur2 = sketch.createGraphics(width, height, sketch.WEBGL);
generate();
};
sketch.draw = () => {};
sketch.keyReleased = () => {
if (sketch.key == " ") {
seed = null;
generate();
} else if (sketch.key == "s") {
sketch.saveCanvas(filenamePrefix + seed + ".jpg", "jpg");
}
};
sketch.doubleClicked = () => {
seed = null;
generate();
};
let resizeTimer;
sketch.windowResized = () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => {
window.location.reload();
}, 100);
};
function generate() {
if (seed) {
sketch.randomSeed(seed);
} else {
const seed = Math.floor(sketch.random(1000000000000));
window.location.hash = seed;
sketch.randomSeed(seed);
}
sketch.noiseSeed(sketch.random(0, 1000000000));
lib.simplex = new SimplexNoise(sketch.random(0, 1000000000));
layers.contours.clear();
drawContours(layers.contours);
sketch.blendMode(sketch.BLEND);
sketch.background(0);
layers.noise.shader(shaders.whiteNoise);
shaders.whiteNoise.setUniform("u_resolution", [width, height]);
shaders.whiteNoise.setUniform("u_alpha", 0.05);
layers.noise.rect(0, 0, width, height);
sketch.image(
layers.contours,
Math.round((-width * overdraw) / 2),
Math.round((-height * overdraw) / 2)
);
sketch.blendMode(sketch.OVERLAY);
sketch.image(layers.noise, 0, 0);
}
function drawContours(layer) {
const noiseScale = 3 / (width + height);
const quant = Math.round(Math.sqrt(width * height) / 1440 * 100); // 100 at 1920*1080
layers.contours.imageMode(sketch.CENTER);
const dots = buildGrid(0, 0, width, height, 15 * quant);
layer.noStroke();
dots.forEach(({x, y}) => {
const {noise, angle} = noisePlus(x, y, noiseScale);
if (noise < 0.5) { // water
layer.tint(218/360, 0.8, 0.1 + sketch.random(0.05) + noise * 2 * 0.2);
} else { // land
layer.tint(119/360, 0.8, 0.1 + sketch.random(0.05) + (noise - 0.5) * 2 * 0.2);
}
const size = 200;
layer.translate(x, y);
layer.rotate(angle + (noise < 0.5 ? sketch.HALF_PI : 0));
layer.image(assets.stroke, 0, 0, size, size / 3);
layer.resetMatrix();
});
const trails = buildGrid(0, 0, width, height, 5 * quant);
layer.noTint();
layer.strokeCap(sketch.SQUARE);
trails.forEach((point) => {
const x = point.x + sketch.random(-50, 50);
const y = point.y + sketch.random(-50, 50);
const noise = fractalNoise(x, y, noiseScale);
for (let i = 0, max = quant/2, aX = x, aY = y; i < max; i++) {
const {angle} = noisePlus(aX, aY, noiseScale);
const dX = Math.cos(angle + sketch.HALF_PI) * 4;
const dY = Math.sin(angle + sketch.HALF_PI) * 4;
if (noise > 0.5) {
layer.stroke(0, 0.8);
} else {
layer.stroke(1, 0.5);
}
layer.strokeWeight(Math.sin(i/max*Math.PI)); // smooth 0 to 1 to 0
layer.line(aX, aY, aX + dX, aY + dY);
aX += dX;
aY += dY;
}
});
}
function buildGrid(minX, minY, maxX, maxY, approxPoints) {
const width = maxX - minX;
const height = maxY - minY;
const unit = Math.sqrt(width * height / Math.sin(Math.PI / 3) / approxPoints);
const rows =
Math.max(1, Math.round(height / unit / Math.sin(Math.PI / 3))) + 1;
const cols = Math.max(1, Math.round(width / unit)) + 1;
const grid = [];
for (let index = 0; index < rows * cols; index++) {
const col = index % cols;
const row = Math.floor(index / cols);
grid.push({
x: minX + (width / (cols - 1)) * col + (((row % 2) - 0.5) * unit) / 2,
y: minY + (height / (rows - 1)) * row
});
}
return grid;
}
function noisePlus(x, y, noiseScale = 2 / (width + height), noiseOffset = 0) {
const noise = fractalNoise(x, y, noiseScale, noiseOffset);
const dX = fractalNoise(x + 1, y, noiseScale, noiseOffset) - noise;
const dY = fractalNoise(x, y + 1, noiseScale, noiseOffset) - noise;
const angle = Math.atan2(dY, dX);
const length = sketch.dist(0, 0, dX, dY);
return ({noise, angle, length});
}
function fractalNoise(x, y, noiseScale = 2 / (width + height), noiseOffset = 0) {
return 0.5 + 0.5 * (
lib.simplex.noise2D(
noiseOffset + noiseScale * x,
noiseOffset + noiseScale * y
) +
0.5 * lib.simplex.noise2D(
noiseOffset + 2 * noiseScale * x,
noiseOffset + 2 * noiseScale * y
) +
0.25 * lib.simplex.noise2D(
noiseOffset + 4 * noiseScale * x,
noiseOffset + 4 * noiseScale * y
)
) / 1.75;
}
// give normally distributed values from 0-1 a uniform distribution
function flattenDistribution(x) {
return (
23.8615 * Math.pow(x, 5) -
59.6041 * Math.pow(x, 4) +
47.2472 * Math.pow(x, 3) -
11.3053 * Math.pow(x, 2) +
0.806219 * x -
0.00259101
);
}
// returns abs angle from a to b to c
function three_point_angle(A, B, C) {
const AB = Math.sqrt(Math.pow(B.x - A.x, 2) + Math.pow(B.y - A.y, 2));
const BC = Math.sqrt(Math.pow(B.x - C.x, 2) + Math.pow(B.y - C.y, 2));
const AC = Math.sqrt(Math.pow(C.x - A.x, 2) + Math.pow(C.y - A.y, 2));
return Math.acos((BC * BC + AB * AB - AC * AC) / (2 * BC * AB));
}
});