xxxxxxxxxx
447
/* DISCLAIMER. THIS CODE IS ALMOST ENTIRELY MADE BY CHATGPT.
https://chat.openai.com/share/bdfc7eb4-8c80-48ef-9915-3406eea813e2
*/
const shaderParticleVertex = `
precision mediump float;
attribute vec2 a_position;
attribute vec4 a_color;
varying vec4 v_color;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
v_color = a_color;
}
`;
const shaderParticleFragment = `
precision mediump float;
varying vec4 v_color;
void main() {
gl_FragColor = v_color;
}
`;
let particleShader;
let particleBuffer;
let colorBuffer;
function setupWebGL() {
let canvas = document.getElementById('defaultCanvas0');
let gl = canvas.getContext('webgl');
particleShader = createShaderCustom(gl, shaderParticleVertex, shaderParticleFragment);
gl.useProgram(particleShader.program);
particleBuffer = gl.createBuffer();
colorBuffer = gl.createBuffer();
}
function createShaderCustom(gl, vertex, fragment) {
let vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertex);
gl.compileShader(vertexShader);
let fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragment);
gl.compileShader(fragmentShader);
let program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
return {
program: program,
a_position: gl.getAttribLocation(program, 'a_position'),
a_color: gl.getAttribLocation(program, 'a_color')
};
}
function drawWebGL() {
let canvas = document.getElementById('defaultCanvas0');
let gl = canvas.getContext('webgl');
let positions = [];
let colors = [];
particles.forEach((particle, i) => {
particle.update();
let pos = [2 * particle.pos.x / windowWidth - 1, 1 - 2 * particle.pos.y / windowHeight];
let color = [
red(particle.color) / 255,
green(particle.color) / 255,
blue(particle.color) / 255,
alpha(particle.color) / 255
];
positions.push(pos);
colors.push(color);
});
gl.useProgram(particleShader.program);
gl.bindBuffer(gl.ARRAY_BUFFER, particleBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
gl.enableVertexAttribArray(particleShader.a_position);
gl.vertexAttribPointer(particleShader.a_position, 2, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
gl.enableVertexAttribArray(particleShader.a_color);
gl.vertexAttribPointer(particleShader.a_color, 4, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.POINTS, 0, particles.length);
if (particles.length > N) {
particles.length = N;
}
}
const N = 100; // Number of particles
const N1 = 25,
N2 = 75,
N3 = 35; // Sizes of the arrays governing movement
const MUTATION_RATE = 0.25;
const MUTATION_STEP = 0.3;
let particles;
let strategies;
class Particle {
constructor(parents = []) {
this.pos = createVector(width / 2, height / 2);
this.history = [];
this.weights = [
Array.from({length: N1}, () => random(-1, 1)),
Array.from({length: N2}, () => random(-1, 1)),
Array.from({length: N3}, () => random(-1, 1))
];
this.lifespan = 255;
this.color = color('white')
this.lifeExpander = parents.length > 0
? parents.map(p => p.lifeExpander).reduce((a, b) => a + b) / parents.length + random(-0.1, 0.1)
: random(0, 1);
}
setColor(c){
this.color = c;
}
update() {
let force = createVector(this.weights[0][frameCount % N1], this.weights[1][frameCount % N2]);
force.limit(2)
this.pos.add(force);
this.history.push([this.pos.x, this.pos.y]);
// If the particle is moving efficiently, increase its lifespan
if (this.efficiency > 0.5) {
this.lifespan += this.lifeExpander;
} else {
this.lifespan -= 1;
}
}
display() {
stroke(this.color);
strokeWeight(lerp(2, 0, this.lifespan / 255));
fill(this.color);
ellipse(this.pos.x, this.pos.y, lerp(0, 8, this.lifespan / 255));
}
}
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
setupWebGL();
background(0);
particles = Array.from({length: N}, () => new Particle());
strategies = [
{
apply: (analysis) => {
let newWeights = [
Array.from({length: N1}, () => analysis.crossCount * random(-1, 1)),
Array.from({length: N2}, () => analysis.crossCount * random(-1, 1)),
Array.from({length: N3}, () => analysis.crossCount * random(-1, 1))
];
return newWeights;
},
color: color(255, 0, 0, 150), // Red
topParticles: [],
},
{
apply: (analysis) => {
let newWeights = [
Array.from({length: N1}, () => analysis.curviness * random(-1, 1)),
Array.from({length: N2}, () => analysis.curviness * random(-1, 1)),
Array.from({length: N3}, () => analysis.curviness * random(-1, 1))
];
return newWeights;
},
color: color(0, 0, 255, 150), // Blue
topParticles: [],
},
{
/*
The "Square Crosser" strategy:
This strategy considers the square of the cross count
in its weight generation. This could lead to more erratic behaviour,
as particles that cross their own paths more often will have significantly
higher weights:
*/
apply: (analysis) => {
let newWeights = [
Array.from({length: N1}, () => Math.pow(analysis.crossCount, 2) * random(-1, 1)),
Array.from({length: N2}, () => Math.pow(analysis.crossCount, 2) * random(-1, 1)),
Array.from({length: N3}, () => Math.pow(analysis.crossCount, 2) * random(-1, 1))
];
return newWeights;
},
color: color(0, 255, 0, 150), // Green
topParticles: []
},
{
/*
The "Curvy Locator" strategy:
This strategy gives a higher weight to particles with a high
location variation and curviness. This could encourage more
movement and variability in paths:
*/
apply: (analysis) => {
let newWeights = [
Array.from({length: N1}, () => analysis.locVariation * analysis.curviness * random(-1, 1)),
Array.from({length: N2}, () => analysis.locVariation * analysis.curviness * random(-1, 1)),
Array.from({length: N3}, () => analysis.locVariation * analysis.curviness * random(-1, 1))
];
return newWeights;
},
color: color(255, 0, 255, 150), // Purple
topParticles: []
},
{
/*
The "Cross Locality" strategy:
This strategy gives a higher weight to particles with a high
cross count and location variation. This could encourage particles
to stay within a particular area while crossing their own paths frequently:
*/
apply: (analysis) => {
let newWeights = [
Array.from({length: N1}, () => analysis.locVariation * analysis.crossCount * random(-1, 1)),
Array.from({length: N2}, () => analysis.locVariation * analysis.crossCount * random(-1, 1)),
Array.from({length: N3}, () => analysis.locVariation * analysis.crossCount * random(-1, 1))
];
return newWeights;
},
color: color(255, 255, 0, 150), // Yellow
topParticles: []
}
];
}
function draw() {
background(0, 25);
particles.forEach((particle, i) => {
particle.update();
particle.display();
if (particle.lifespan <= 0) {
// Use analysis to generate new weights
let analysis = analyzeLine(particle.history);
let strategy = random(strategies);
let newWeights = strategy.apply(analysis);
let newParticle = new Particle();
newParticle.weights = newWeights;
newParticle.color = strategy.color;
particles[i] = newParticle;
}
});
}
function analyzeLine(points) {
let result = {
crossCount: 0,
curviness: 0,
locVariation: 0,
efficiency: 0
};
let regression = window.regression;
let previousSlope = null;
let sumX = 0;
let sumY = 0;
let sumXSquare = 0;
let sumYSquare = 0;
for(let i = 0; i < points.length - 1; i++) {
let pt1 = points[i];
let pt2 = points[i+1];
let line = regression.linear([[pt1[0], pt1[1]], [pt2[0], pt2[1]]]);
let slope = line.equation[0];
if (previousSlope !== null && slope * previousSlope < 0) {
result.crossCount++;
}
if (previousSlope !== null) {
let curviness = Math.abs(slope - previousSlope);
if (curviness > result.curviness) {
result.curviness = curviness;
}
}
sumX += pt1[0];
sumY += pt1[1];
sumXSquare += pt1[0] * pt1[0];
sumYSquare += pt1[1] * pt1[1];
previousSlope = slope;
}
let n = points.length;
let meanX = sumX / n;
let meanY = sumY / n;
let varX = sumXSquare / n - meanX * meanX;
let varY = sumYSquare / n - meanY * meanY;
result.locVariation = varX + varY;
let pathLength = points.length;
let straightLineDistance = dist(points[0][0], points[0][1], points[pathLength - 1][0], points[pathLength - 1][1]);
let efficiency = straightLineDistance / pathLength;
result.efficiency = efficiency
return result;
}
const elitismRate = 0.02; // 2% elitism rate
function draw() {
background(0, 25);
drawWebGL();
// Elitism: Preserve the best particles
let elites = [];
for (let strategy of strategies) {
elites.push([strategy.topParticles.slice(0, Math.ceil(N * elitismRate))]);
}
particles.forEach((particle, i) => {
particle.update();
particle.display();
if (particle.lifespan <= 0) {
let analysis = analyzeLine(particle.history);
let strategy = random(strategies);
let score = analysis.curviness + analysis.crossCount + analysis.locVariation;
let scaledScore = Math.pow(score, 2); // Square the score for scaling
// If particle is good enough, include it in the topParticles list
if (strategy.topParticles.length < 5 || scaledScore > strategy.topParticles[4].score) {
let index = strategy.topParticles.findIndex(p => p.score < scaledScore);
if (index !== -1) {
strategy.topParticles.splice(index, 0, {score: scaledScore, weights: particle.weights});
} else {
strategy.topParticles.push({score: scaledScore, weights: particle.weights});
}
if (strategy.topParticles.length > 5) {
strategy.topParticles.length = 5;
}
}
let newWeights;
if (strategy.topParticles.length > 1) {
// Choose 2 to 5 particles to base the new particle on
let numParents = Math.min(strategy.topParticles.length, 5);
let parentParticles = [];
for (let i = 0; i < numParents; i++) {
parentParticles.push(random(strategy.topParticles));
}
let parentWeights = parentParticles.map(p => p.weights);
newWeights = averageWeights(parentWeights);
} else {
newWeights = strategy.apply(analysis);
}
let newParticle = new Particle();
newParticle.weights = newWeights;
newParticle.color = strategy.color;
particles[i] = newParticle;
}
});
// Add the elites back into the population
for (let i = 0; i < elites.length; i++) {
for (let elite of elites[i]) {
let newParticle = new Particle();
newParticle.weights = elite.weights;
newParticle.color = strategies[i].color;
particles.push(newParticle);
}
}
// Trim the particles array if necessary
if (particles.length > N) {
particles.length = N;
}
}
// Utility function to get a random subset of array
function randomSubset(array, size) {
let shuffled = array.slice().sort(() => 0.5 - Math.random());
return shuffled.slice(0, size);
}
// Utility function to average weights and add some mutation
function averageWeights(weightsList) {
let N1Avg = [], N2Avg = [], N3Avg = [];
for (let weights of weightsList) {
for (let i = 0; i < N1; i++) {
N1Avg[i] = (N1Avg[i] || 0) + weights[0][i];
}
for (let i = 0; i < N2; i++) {
N2Avg[i] = (N2Avg[i] || 0) + weights[1][i];
}
for (let i = 0; i < N3; i++) {
N3Avg[i] = (N3Avg[i] || 0) + weights[2][i];
}
}
let size = weightsList.length;
return [
mutateWeights(N1Avg.map(val => val / size)),
mutateWeights(N2Avg.map(val => val / size)),
mutateWeights(N3Avg.map(val => val / size))
];
}
// Utility function to mutate weights
function mutateWeights(weights) {
for (let i = 0; i < weights.length; i++) {
if (random() < MUTATION_RATE) {
weights[i] += random(-MUTATION_STEP, MUTATION_STEP); // adjust mutation size as needed
}
}
return weights;
}