Lines in a swirl force field
I let the parameters go too far. The original piece is without background canvas.
Log in to post a comment.
// You can find the Turtle API reference here: https://turtletoy.net/syntax const totalSimulationSteps = 2000; // min=0 max=4000 step = 10 const lineAmount = 6000;// min=1 max=15000 step = 10 const batchEachWalk = 20;// min=1 max=105 step = 5 const drawingOpacity = -0.2; // min=-1.0 max=1.0 step = 0.05 const circleRadius = 72;// min=1 max=105 step = 1 const bDrawBackground = 0;// min=0 max=1 step = 1 Canvas.setpenopacity(drawingOpacity); // Global code will be evaluated once. const turtle = new Turtle(); function line(x1,y1,x2,y2){turtle.penup();turtle.goto(x1,y1);turtle.pendown();turtle.goto(x2,y2);} function quad(left,right,top,bottom){line(left,top,left,bottom);line(left,bottom,right,bottom);line(right,bottom,right,top);line(right,top,left,top);} function square(x,y,size){let sz = size*0.5;quad(x-sz,x+sz,y-sz,y+sz);} function circle(x,y,radius,extent=undefined){turtle.penup();turtle.goto(x,y);turtle.pendown();turtle.circle(radius,extent);} // function InCircle(x,y,cx,cy,radius){let dx = x - cx;let dy = y - cy;return dx*dx + dy*dy <= radius*radius;} // function Halton(index, base){let result = 0;let invBase = 1.0 / base;let frac = invBase;while(index>0){result += (index%base)*frac;index /= base;frac *= invBase;}return result;} // function Halton2D(index,base1,base2,range){let HaltonX = Halton(index,base1)-0.5;let HaltonY = Halton(index,base2)-0.5;let x = HaltonX * range;let y = HaltonY * range;return [x,y];} function randomInRange(min, max) {return Math.random() * (max - min) + min;} function randomPointOutsideCircle(canvasSize, r) { const halfCanvasSize = canvasSize *0.5; while (true) { const x = randomInRange(-halfCanvasSize, halfCanvasSize); const y = randomInRange(-halfCanvasSize, halfCanvasSize); const distance = Math.sqrt(x*x + y*y); if (distance >= r) { return { x:x, y:y }; } } } const s = 10; // Side length of the square const r = 4; // Radius of the circle const point = randomPointOutsideCircle(s, r); console.log(point); const canvasSize = 256; const squareSize = 20; function drawRandomRect( ){ const coord = randomPointOutsideCircle(canvasSize, circleRadius*1.2); square(coord.x,coord.y,squareSize); } const forceScale = 0.5; const numWalks = lineAmount/batchEachWalk; const rotationCenters = [ { x: 0, y: 0 } ]; // Define the Particle class class Particle { constructor(x, y) { this.position = { x: x, y: y }; this.oldPosition = { x: x, y: y }; this.bFree = true; } getVelocity() { return { x: this.position.x - this.oldPosition.x, y: this.position.y - this.oldPosition.y }; } } class Ribbon { constructor(startX, startY, numSegments, segmentLength, simulationSteps,constraintIterations) { this.particles = []; this.numSegments = numSegments; this.segmentLength = segmentLength; this.simulationSteps = simulationSteps; this.constraintIterations = constraintIterations let l = Math.sqrt(startX*startX + startY*startY); let dx = startX/l; let dy = startY/l; for (let i = 0; i <= numSegments; i++) { this.particles.push(new Particle(startX + i * dx * segmentLength, startY + i * dy * segmentLength)); } this.particles[0].bFree = false; } calculateExternalForce(particle) { let force = { x: 0, y: 0 }; let closestCenter = null; let closestDistance = Infinity; // Find the closest rotation center for (let center of rotationCenters) { let dx = particle.position.x - center.x; let dy = particle.position.y - center.y; let distance = Math.sqrt(dx * dx + dy * dy); if (distance < closestDistance) { closestDistance = distance; closestCenter = center; } } // Calculate force based on the closest center if (closestCenter) { let dx = particle.position.x - closestCenter.x; let dy = particle.position.y - closestCenter.y; force.x = -forceScale* dy; force.y = forceScale* dx; } return force; } verletIntegrate(dt) { const substepTime = 0.01; const substepTimeSqr = dt * dt; for (let particle of this.particles) { if(particle.bFree){ let tempPos = { ...particle.position }; let velocity = particle.getVelocity(); let externalForce = this.calculateExternalForce(particle); particle.position.x += velocity.x + externalForce.x*substepTimeSqr; particle.position.y += velocity.y + externalForce.y*substepTimeSqr; particle.oldPosition = tempPos; } } } solveDistanceConstraint(p1, p2, desiredDistance) { let dx = p2.position.x - p1.position.x; let dy = p2.position.y - p1.position.y; let currentDistance = Math.sqrt(dx * dx + dy * dy); let difference = desiredDistance - currentDistance; let normalizedDX = dx / currentDistance; let normalizedDY = dy / currentDistance; let offsetX = normalizedDX * difference; let offsetY = normalizedDY * difference; if (p1.bFree && p2.bFree) { p1.position.x -= offsetX * 0.5; p1.position.y -= offsetY * 0.5; p2.position.x += offsetX * 0.5; p2.position.y += offsetY * 0.5; } else if (p1.bFree) { p1.position.x -= offsetX; p1.position.y -= offsetY; } else if (p2.bFree) { p2.position.x += offsetX; p2.position.y += offsetY; } } solveConstraints(){ for (let iterationIdx = 0; iterationIdx < this.constraintIterations; iterationIdx++) { for (let i = 0; i < this.numSegments; i++) { this.solveDistanceConstraint(this.particles[i], this.particles[i + 1], this.segmentLength); } } } simulate() { for(let step = 0; step < this.simulationSteps; step++) { this.verletIntegrate(0.001) this.solveConstraints() } } } function randomPointInCircle(R) { let r = Math.sqrt(Math.random()) * R; // Square root ensures uniform distribution let theta = Math.random() * 2 * Math.PI; let x = r * Math.cos(theta); let y = r * Math.sin(theta); return { x, y }; } // Randomize parameters within a legitimate range function randomRibbon() { let point = randomPointInCircle(circleRadius); let startX = point.x; let startY = point.y; let numSegments = Math.floor(20*Math.random()) + 20; let segmentLength = 0.1*Math.random()+0.5; let simulationSteps = totalSimulationSteps; let constraintIterations = 5; return new Ribbon(startX, startY,numSegments, segmentLength, simulationSteps, constraintIterations); } // draw one 'simulated' line each walk function walk(i) { for(let k=0;k<batchEachWalk;k++){ let ribbon = randomRibbon(); ribbon.simulate(); for (let i = 0; i <= ribbon.numSegments; i++) { if (i < ribbon.numSegments) { turtle.penup(); turtle.goto(ribbon.particles[i].position.x, ribbon.particles[i].position.y); turtle.pendown(); turtle.goto(ribbon.particles[i + 1].position.x, ribbon.particles[i + 1].position.y); } } if(bDrawBackground){ drawRandomRect(); } } return i < numWalks; }