Circle Vortex
This project was made as an exercise to create generated Art. With this, I tried at first to mimic a soundwave-like circle, but with the many parameters, many different shapes are possible.
Log in to post a comment.
const randomSeed = 0; //min=0 max=1000 step=1
const circlePoints = 350; //min=3 max=1000 step=1
const circleRingNumber = 4; //min=1 max=10 step=1
const circleDistance = 20; //min=10 max=50 step=0.01
const circleDistanceChange = 3; //min=-10 max=10 step=0.1
const circleStartDistance = 10; //min=10 max=50 step=0.1
const circleThicknessMax = 4; //min=0 max=20 step=1
const subringsMax = 20; //min=0 max=50 step=1
const subringsRotationVariation = 0.0 //min=0 max=1 step=0.001
const subringsScaleVariation = 10 //min=0 max=10 step=0.001
const subringsJitterVariation = 0.0 //min=0 max=10 step=0.01
const noiseAmplitude = 8; //min=-50 max=50 step=0.01
const noiseFrequency = 6; //min=0.01 max=10 step=0.001
const noisePhase = 0; //min=0 max=6.2831 step=0.001
const amplitudeNoiseFrequency = 1; //min=1 max=10 step=0.001
const amplitudeNoisePhase = 0; //min=0 max=6.2831 step=0.001
const amplitudeNoiseOffset = -0.25; //min=-1 max=0 step=0.01
const amplitudeStart = 0.0; //min=0 max=3 step=0.01
const amplitudeEnd = 2.0; //min=0 max=3 step=0.01
Canvas.setpenopacity(1);
// Global code will be evaluated once.
const turtle = new Turtle();
turtle.penup();
const randomOffsets = getCircleOffsets(noisePhase);
for (let i=0; i < circleRingNumber; i++) {
const cd = circleStartDistance + (circleDistance + (circleDistanceChange * i) * i);
const circle = getCirclePointsPolarCoordinates(cd);
const circleAmplitude = mix(amplitudeStart, amplitudeEnd, i / (circleRingNumber));
const jitteredCircle = jitterCircle(circle, circleAmplitude);
const thickness = Math.max(Math.floor(rand(i, circleThicknessMax)), 1);
const thickCircle = getThickRing(jitteredCircle, thickness);
renderPolarCircle(thickCircle);
const subCircles = generateSubRings(jitteredCircle, i);
subCircles.map(circle => {
renderPolarCircle(circle);
})
}
function generateSubRings(inputCircle, seed) {
const numberOfRings = Math.floor(rand(seed, subringsMax));
const outputCircles = [];
for (let i = 0; i < numberOfRings; i++) {
const scaleNoise = (noise(i) + 0.25) * subringsScaleVariation;
const scaledCircle = inputCircle.map((vertex, index) => {
return shiftRadius(vertex, scaleNoise);
});
const rotationNoise = (noise(i) + 0.25)* subringsRotationVariation;
const rotatedCircle = scaledCircle.map((vertex, index) => {
return shiftAngle(vertex, rotationNoise);
});
const distortedCircle = rotatedCircle.map((vertex, index) => {
return {
r: vertex.r + noise(vertex.r) * subringsJitterVariation,
angle: vertex.angle
}
});
outputCircles.push(distortedCircle);
}
return outputCircles;
}
function renderPolarCircle(circle) {
const cartesianCircle = circle.map((vertex) => {
return toCartesianCoordinates(vertex)
});
renderPoints(cartesianCircle);
}
function renderPoints(points) {
const startPoint = points[0]
turtle.goto(startPoint.x, startPoint.y);
turtle.pendown();
for (const point of points) {
turtle.goto(point.x, point.y);
}
// close the loop
turtle.goto(startPoint.x, startPoint.y);
turtle.penup();
}
function toCartesianCoordinates(input) {
return {
x: input.r * Math.cos(input.angle),
y: input.r * Math.sin(input.angle)
}
}
function getCirclePointsPolarCoordinates(r) {
const output = []
for (let i=0; i<circlePoints; i++) {
const angle = i / circlePoints * Math.PI * 2;
output.push({r,angle});
}
return output;
}
function shiftRadius(vertex, amount) {
return {
r: vertex.r + amount,
angle: vertex.angle
}
}
function shiftAngle(vertex, amount) {
return {
r: vertex.r,
angle: vertex.angle + amount
}
}
function jitterCircle(circle, amount) {
return circle.map((vertex, index) => {
return shiftRadius(vertex, randomOffsets[index] * noiseAmplitude * amount);
});
}
function getThickRing(input, thickness=1) {
const startIndex = thickness * (-1);
const endIndex = thickness;
const output = [];
for (let i = startIndex; i < endIndex; i++) {
newInput = input.map((vertex) => {
return shiftRadius(vertex, i * 0.25)
});
output.push(...newInput);
}
return output;
}
function getCircleOffsets(phase) {
const output = []
for (let i=0; i<circlePoints; i++) {
const t = i / (circlePoints);
const angle = t * Math.PI * 2;
const nx = Math.cos(angle) * noiseFrequency + phase;
const ny = Math.sin(angle) * noiseFrequency + phase;
const ampNx = Math.cos(angle) * amplitudeNoiseFrequency + amplitudeNoisePhase;
const ampNy = Math.sin(angle) * amplitudeNoiseFrequency + amplitudeNoisePhase;
const offset = smoothStep(0, 1, noise2D(nx, ny) + amplitudeNoiseOffset);
const amplitudeInfluence = noise2D(ampNx, ampNy);
output.push((offset - 0.5) * 2 * amplitudeInfluence);
}
return output;
}
function fbm(x, octaves = 4) {
let value = 0;
let amplitude = 0.5;
let frequency = 1;
for (let i = 0; i < octaves; i++) {
value += noise(x * frequency) * amplitude;
frequency *= 2;
amplitude *= 0.5;
}
return value;
}
function noise(x) {
const i = Math.floor(x);
const f = x - i;
const y = mix(rand(i), rand(i + 1.0), smoothStep(0, 1, f));
return y;
}
function noise2D(x, y) {
const ix = Math.floor(x);
const iy = Math.floor(y);
const fx = x - ix;
const fy = y - iy;
const a = rand(ix + iy * 57);
const b = rand(ix + 1 + iy * 57);
const c = rand(ix + (iy + 1) * 57);
const d = rand(ix + 1 + (iy + 1) * 57);
const ux = smoothStep(0, 1, fx);
const uy = smoothStep(0, 1, fy);
const lerpX1 = mix(a, b, ux);
const lerpX2 = mix(c, d, ux);
return mix(lerpX1, lerpX2, uy);
}
function smoothStep(start, stop, t) {
const clampedT = clamp((t - start) / (stop - start), 0, 1)
return clampedT * clampedT * (3.0 - 2.0 * clampedT);
}
function clamp(x, min, max) {
return Math.min(Math.max(min, x), max);
}
function rand(input, factor = 1, bidirectional = false) {
const r = hash(input + randomSeed);
return bidirectional ? (r - 0.5) * 2 * factor : r * factor;
}
function hash(x) {
const s = Math.sin(x * 127.1) * 437578.5453;
return s - Math.floor(s);
}
function mix(inputA, inputB, t) {
return (1 - t) * inputA + t * inputB
}