Cool
Log in to post a comment.
const nElectrodes = 40; //min=2 max=40 step=1 const electrodeRadius = 3; //min=1 max=10 step=.5 const emitterPattern = 1; //min=0 max=1 step=1 (Regular, Random) const nElectronsPerElectrode = 300; //min=0 max=500 step=10 const electrodeLocation = 1; //min=0 max=1 step=1 (Random, Farthest candidate) const nCandidates = 10; //min=1 max=20 step=1 const border=10; //min=0 max=10 step=1 const chargeRange=0; //min=0 max=1.5 step=.1 const electrodeNegPos=1; const x=1; const a=23; //min=0 max=1 step=1 (Random, Alternating) if(nElectronsPerElectrode == 0) nElectronsPerElectrode = 1; if(electrodeLocation == 0) nCandidates = 1; // You can find the Turtle API reference here: https://turtletoy.net/syntax Canvas.setpenopacity(.4); // Global code will be evaluated once. const turtle = new Turtle(); const pi2 = 2 * Math.PI; function normalize2(a) { const length = len2(a); return scale2(a,length<0.0001?1:1/length); } function len2(a) { return Math.sqrt(lensq2(a)); } function lensq2(a) { return dot2(a,a); } function rot2(a) { return [Math.cos(a), -Math.sin(a), Math.sin(a), Math.cos(a)]; } function trans2(m, a) { return [m[0]*a[0]+m[2]*a[1], m[1]*a[0]+m[3]*a[1]]; } function scale2(a,b) { return [a[0]*b,a[1]*b]; } function add2(a,b) { return [a[0]+b[0],a[1]+b[1]]; } function sub2(a,b) { return [a[0]-b[0],a[1]-b[1]]; } function dist2(a,b) { return Math.hypot(...sub2(a,b)); } function lerp2(a,b,t) { return [a[0]*(1-t)+b[0]*t,a[1]*(1-t)+b[1]*t]; } function dot2(a,b) { return a[0]*b[0]+a[1]*b[1] } function mulf2(v, f) { return scale2(v,f); } function multiply2(a2x2, a) { return [(a[0]*a2x2[0])+(a[1]*a2x2[1]),(a[0]*a2x2[2])+(a[1]*a2x2[3])]; } class Electrode { constructor (xy, r, v) { this.pos = xy; this.radius = r; this.charge = v; this.emitterCount = 0; } draw(t) { for(let i = 0; i < 4; i++) { t.jump(this.pos[0], this.pos[1] - this.radius); t.circle(this.radius); t.jump(this.pos[0] - (this.radius / 2), this.pos[1]); t.goto(this.pos[0] + (this.radius / 2), this.pos[1]); if(this.charge >= 0) { t.jump(this.pos[0], this.pos[1] - (this.radius / 2)); t.goto(this.pos[0], this.pos[1] + (this.radius / 2)); } } } spawn() { let a = Math.random() * pi2; if(emitterPattern == '0') { a = pi2 * (this.emitterCount++ / nElectronsPerElectrode); } return new Electron( [this.pos[0] + (Math.cos(a) * this.radius), this.pos[1] + (Math.sin(a) * this.radius)] ); } } class Electron { constructor (xy) { this.pos = xy; this.t = new Turtle(); this.t.jump(this.pos); this.active = true; } step(electrodes) { if(!this.active) return; let force = [0,0]; let that = this; electrodes.forEach(function(i) { if(i == null) return; let dSquared = (that.t.pos()[0] - i.pos[0])**2 + (that.t.pos()[1] - i.pos[1])**2; let f = Math.sqrt(1/Math.sqrt(dSquared)) * -i.charge; let direction = normalize2(sub2(that.t.pos(), i.pos)); force = add2(force, scale2(direction, f)); }); //this.speed = add2(this.speed, force); let nextPos = add2(this.t.pos(), force); //electrodes.filter((i) => i.charge >= 0).forEach(function(i) { electrodes.forEach(function(i) { if( i.pos[0] - i.radius < nextPos[0] && nextPos[0] < i.pos[0] + i.radius && i.pos[1] - i.radius < nextPos[1] && nextPos[1] < i.pos[1] + i.radius ) { if(dist2(i.pos, nextPos) < i.radius) { nextPos = approxIntersection(i, that.t.pos(), nextPos); that.active = false; } } //console.log('something to make it slow') }); if( border > 0 && ( nextPos[0] < border - 100 || 100 - border < nextPos[0] || nextPos[1] < border - 100 || 100 - border < nextPos[1] )) { this.t.jump(nextPos); } else { this.t.goto(nextPos); } } } function approxIntersection(circle, sectionStart, sectionEnd) { let lr = Math.sqrt((sectionEnd[0] - circle.pos[0])**2 + (sectionEnd[1] - circle.pos[1])**2 ) if(lr > circle.radius) { return null; } let angle = 0; let dx = sectionEnd[0] - circle.pos[0]; let dy = sectionEnd[1] - circle.pos[1]; if(dx == 0) { angle = Math.PI * (dy > 0? .5: 1.5); } else { let dydx = dy/dx; angle = Math.atan(dy/dx); if(dx < 0) { angle += Math.PI } } return [ circle.pos[0] + circle.radius * Math.cos(angle), circle.pos[1] + circle.radius * Math.sin(angle) ] } const electrodes = []; const negposflip = [-1, 1].sort((i) => Math.random() < .5); let negpos = false; for(let i = 0; i < nElectrodes; i++) { let candidates = []; for(let j = 0; j < nCandidates; j++) { let x = (Math.random() * (200 - border - border - electrodeRadius - electrodeRadius)) - ((200 - border - border - electrodeRadius - electrodeRadius) / 2); let y = (Math.random() * (200 - border - border - electrodeRadius - electrodeRadius)) - ((200 - border - border - electrodeRadius - electrodeRadius) / 2); let candidate = [x, y]; let minDistance = 9999999; for(let k = 0; k < electrodes.width; k++) { let distance = dist2(electrodes[k].pos, candidate); if(distance < minDistance) { minDistance = distance; } } candidates.push([minDistance, candidate]); } candidates.sort((a, b) => (a[0] < b[0]) ? 1 : -1); electrodes.push(new Electrode( candidates[0][1], electrodeRadius, (.5 + (Math.random() * chargeRange)) * (electrodeNegPos == 1? (negpos == false? -1: 1): negposflip.length > 0? negposflip.pop() : Math.random() < .5? -1: 1) )); negpos = !negpos; } let electrons = []; let nElectrons = nElectronsPerElectrode; electrodes.forEach((i) => i.draw(turtle)); electrodes.filter((i) => i.charge < 0).forEach(function (i) { for(let j = 0; j < nElectrons; j++) { electrons.push(i.spawn()) } }); // The walk function will be called until it returns false. function walk(i) { electrons.forEach(function(j) { j.step(electrodes) }); electrons = electrons.filter((i) => i.active); return electrodes.length > 0 && i <50000; }