xxxxxxxxxx
205
let physics;
let threads = [];
let p = ['#2B2D42', '#8D99AE', '#EF233C', '#D80032'];
let numStickyLines = 50;
let numThreads = 10;
let sz = 200;
let fonts = [];
let minFontSize = 24;
let maxFontSize = 58;
let numFonts = 10; // Number of different font sizes
let lockThreshold = 0.1; // Distance threshold to lock particles
let stickyLines = [];
let words = [];
let particleMass = 30;
let currentWordIndex = 0;
let fontName = 'data/RobotoSlab-VariableFont_wght.ttf';
let springStrength = 0.01;
let gravity = new Vec2D(0.005, 0);
let lockDecay = 2000;
let coolDown = 2000;
let wordFilePath = 'data/words.txt'; // Path to the words file
function preload() {
for (let i = 0; i < numFonts; i++) {
let fontSize = minFontSize + i * (maxFontSize - minFontSize) / (numFonts - 1);
let font = loadFont(fontName, font => {
fonts.push({ font: font, size: fontSize });
}, () => {
});
}
}
function setup() {
createCanvas(windowWidth, windowHeight, P2D);
windCenter = new Vec2D(width / 2, height / 2);
physics = new VerletPhysics2D();
let gb = new GravityBehavior(gravity);
physics.addBehavior(gb);
// Initialize sticky lines
for (let i = 0; i < numStickyLines; i++) {
let x = random(width);
let y = random(height);
let length = random(10, 200);
let angle = random(TWO_PI); // Angle in radians
let x2 = x + length * cos(angle);
let y2 = y + length * sin(angle);
stickyLines.push([new toxi.geom.Vec2D(x, y), new toxi.geom.Vec2D(x2, y2)]);
}
words = loadSentences(wordFilePath);
}
function draw() {
background('#EDF2F4');
stroke(0);
strokeWeight(4);
//grid.display(startX, startY, cellSize, 0x1A8FE3, 0xD11149, true);
strokeWeight(1);
let octaves = 4; // Number of octaves
let persistence = 0.5; // Amplitude decrease factor per octave
let baseFrequency = 0.01; // Base frequency for noise
let windY = map(noiseWithOctaves(frameCount * baseFrequency, octaves, persistence), 0, 1, -1, 1);
let wind = new toxi.geom.Vec2D(0, windY);
let directForceScale = 0.01; // Initial value for direct force scale
let constantForce = 0.01; // Initial value for constant force
let damping = 0.001; // Damping factor to reduce oscillations
for (let i = threads.length - 1; i >= 0; i--) {
let t = threads[i];
t.updateLocks();
t.applyWindForce(wind, directForceScale, constantForce);
t.applyDamping(damping);
t.display();
if (t.isOffScreen()) {
t.removeFromSimulation(); // Remove particles and springs from physics simulation
threads.splice(i, 1); // Remove the thread from the list
}
}
physics.update();
// Check for particles near the lock line
checkAndLockParticles();
}
function createAndLockThread(x1, y1, x2, y2, c, strength, word) {
let numParts = word.length; // Use the length of the word as numParts
if (numParts == 0) {
numParts = 1; // Ensure at least one part to avoid division by zero
}
let t = new Thread(x1, y1, x2, y2, numParts, c, strength, word);
threads.push(t);
}
function checkAndLockParticles() {
for (let t of threads) {
for (let i = 0; i < t.particles.length; i++) {
let p = t.particles[i];
for (let line of stickyLines) {
if (isNearLine(p, line[0], line[1], lockThreshold)) {
t.lockParticle(i);
break;
}
}
}
}
}
function isNearLine(p, a, b, threshold) {
// Vector from a to p
let ap = p.sub(a);
// Vector from a to b
let ab = b.sub(a);
// Calculate the magnitude squared of ab
let ab2 = ab.x * ab.x + ab.y * ab.y;
// Calculate the dot product of ap and ab
let ap_ab = ap.dot(ab);
// Compute the parameterized position
let t = ap_ab / ab2;
// Clamp t from 0 to 1 to stay within the segment
t = max(0, min(1, t));
// Find the closest point on the segment
let closest = a.add(ab.scale(t));
// Calculate the distance from p to the closest point
let distanceToSegment = p.distanceTo(closest);
return distanceToSegment <= threshold;
}
function loadSentences(filename) {
let sentenceList = [];
let lines = loadStrings(filename, (lines) => {
let text = '';
for (let line of lines) {
text += line + ' '; // Append each line with a space
}
// Split the text into sentences using a regex pattern
let sentences = text.trim().split(/(?<=[.?!\r\n\f])\s/);
for (let sentence of sentences) {
sentenceList.push(sentence.trim());
}
words = sentenceList; // Assign to global variable
setupThreads(); // Call setupThreads after loading sentences
});
return sentenceList;
}
function setupThreads() {
for (let i = 0; i < numThreads; i++) {
let y = random(height);
let x = -600;
let cIdx = int(random(p.length));
let word = getNextWord(); // Get the next word from the list
let sz = word.length * 10;
createAndLockThread(x, y, x + sz / 2, y + sz, p[cIdx], springStrength, word);
}
}
function getNextWord() {
if (currentWordIndex >= words.length) {
currentWordIndex = 0; // Reset index if it exceeds the list size
words = words.concat(loadSentences(wordFilePath)); // Load next set of words
}
let word = words[currentWordIndex];
currentWordIndex++;
return word;
}
function noiseWithOctaves(x, octaves, persistence) {
let total = 0;
let frequency = 1;
let amplitude = 1;
let maxValue = 0; // Used for normalizing result to 0.0 - 1.0
for (let i = 0; i < octaves; i++) {
total += noise(x * frequency) * amplitude;
maxValue += amplitude;
amplitude *= persistence;
frequency *= 2;
}
return total / maxValue;
}