canvas = {
const n = 50;
const minR = 80;
const maxR = 100;
const radiusDecay = 0.1
const color = d3.scaleSequential([maxR + 35, minR - 25], d3.interpolateCool)
const linew = d3.scaleLinear()
.domain([minR, maxR])
.range([3, 1])
const opacity = d3.scaleLinear()
.domain([minR, maxR])
.range([0.2, 0.01])
const circleColor = "rgba(25, 205, 58, 1)"
const centerColor = color(maxR)
const height = Math.ceil(width * screen.height / screen.width);
const margin = 20
let w = width - margin * 2
let x0 = margin
let h = height - margin * 2
let y0 = margin
const context = DOM.context2d(width, height);
context.strokeLinecap = "round"
context.globalCompositeOperation = "luminosity"
context.globalAlpha = 0.01;
const particles = Array.from({length: n}, (d,i) => {
let angle = Math.PI * 2 * i / n
let speed = 0.9
let dx = 1//Math.random() < 0.5 ? -1 : 1;
let dy = 1//Math.random() < 0.5 ? -1 : 1;
let ivx = 1 + Math.random()
let ivy = 1 + Math.random()
return {
x: margin + Math.random() * w,
y: margin + Math.random() * h,
dx: dx,
dy: dy,
ivx: ivx,
ivy: ivy,
vx: (dx * ivx * speed) * Math.cos(angle),
vy: (dy * ivy * speed) * Math.sin(angle),
r: (maxR - minR) * Math.random() + minR,
speed: speed,
angle: angle
}
});
context.canvas.style.background = "#111";
context.strokeStyle = "red";
while (true) {
const quadtree = d3.quadtree()
.x(d => d.x)
.y(d => d.y)
.addAll(particles);
// context.fillStyle = "rgba(5,5,5,1)"
context.fillStyle = "rgba(5,5,5,0.01)"
// context.fillStyle = "rgba(255,255,255,0.07)"
// context.fillRect(0,0, width, height)
// context.fillStyle = "blue";
context.font = '14px san-serif';
let lines = []
let p;
for(var i = 0; i < particles.length; i++) {
p = particles[i]
// Constrain to surface
if(p.x > x0 + w) {
if( Math.cos(p.angle) >= 0) {
p.dx = -1
} else {
p.dx = 1
}
}
if(p.y > y0 + h) {
if( Math.sin(p.angle) >= 0) {
p.dy = -1
} else {
p.dy = 1
}
}
if(p.x < x0) {
if( Math.cos(p.angle) >= 0) {
p.dx = 1
} else {
p.dx = -1
}
}
if(p.y < y0) {
if( Math.sin(p.angle) >= 0) {
p.dy = 1
} else {
p.dy = -1
}
}
let nx1 = p.x - p.r,
nx2 = p.x + p.r,
ny1 = p.y - p.r,
ny2 = p.y + p.r;
let touching = false;
// visit each squares in the quadtree
// x1 y1 x2 y2 constitues the coordinates of the square
// we want to check if each square is a leaf node (has data prop)
p.l = 0;
quadtree.visit((visited, x1, y1, x2, y2) => {
if (visited.data && (visited.data !== p)) {
let x = p.x - visited.data.x,
y = p.y - visited.data.y,
l = Math.sqrt(x * x + y * y),
r = p.r + visited.data.r;
if (l < r) { // if quadtree leaf and input node collides
// Change direction while touching another Element
touching = true;
// get direction from other point to me
// which we have here as x and y
// add a fraction of direction to p.x and p.y
// p.x += .01 * x
// p.y += .01 * y
p.r -= radiusDecay
// Process 4
lines.push([p, visited.data, l, r])
if(l > p.l) p.l = l;
}
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
});
if(touching) {
// Change direction while touching another Element
p.angle += Math.PI * 0.01
// p.speed += 0.015
// p.r -= 1
}
p.r += radiusDecay
if(p.r < minR) p.r = minR
if(p.r > maxR) p.r = maxR
// p.speed -= 0.005
// let maxspeed = 5
// if(p.speed > maxspeed) p.speed = maxspeed
// if(p.speed < -maxspeed) p.speed = -maxspeed
p.vx = p.dx * p.ivx * p.speed * Math.cos(p.angle)
p.vy = p.dy * p.ivy * p.speed * Math.sin(p.angle)
// Move in a straight line
p.x += p.vx
p.y += p.vy
// drawing the outlines of the circles
context.strokeStyle = p.l ? color(p.l) : circleColor
context.lineWidth = 20
context.beginPath()
context.arc(p.x, p.y, 2*p.r/3 - 2*minR/3, 0, Math.PI*2)
context.stroke()
context.strokeStyle = p.l ? color(p.l) : circleColor
context.lineWidth = 2
context.beginPath()
context.arc(p.x, p.y, p.r/3, 0, Math.PI*2)
context.stroke()
// drawing the outlines of the circles
context.strokeStyle = p.l ? color(p.l) : centerColor
context.lineWidth = 1
context.beginPath()
context.arc(p.x, p.y, minR - 1, 0, Math.PI*2)
context.stroke()
// debuging with text
// context.fillText("p.x: " + p.x, p.x, p.y)
// context.fillText("p.y: " + p.y, p.x, p.y + 12)
// context.fillText("x0: " + (x0 + w), p.x - 70, p.y)
// context.fillText("y0: " + (y0 + h), p.x - 70, p.y + 12)
// context.fillText("cos: " + Math.floor(10 * Math.cos(p.angle)), p.x + 50, p.y)
// context.fillText("sin: " + Math.floor(10 * Math.sin(p.angle)), p.x + 50, p.y + 12)
// context.fillText("p.x: " + p.x, p.x + 30, p.y)
// context.fillText("p.y: " + p.y, p.x + 30, p.y + 12)
}
lines.sort(function(a,b) {
return b[2] - a[2]
})
context.lineCap = "round"
lines.forEach(function(line) {
// Process 4
// color by distance
let p0 = line[0]
let p1 = line[1]
let l = line[2]
let c = d3.rgb(color(l))
let o = opacity(l)
context.strokeStyle = `rgba(${c.r}, ${c.g}, ${c.b}, ${o})`
// context.strokeStyle = `rgba(128,128,128, ${o})`
context.lineWidth = linew(l)
// draw a line between centers
context.beginPath()
context.moveTo(p0.x, p0.y)
context.lineTo(p1.x, p1.y)
context.stroke()
})
// particles.forEach(function(p) {
// // drawing the outlines of the circles
// context.strokeStyle = circleColor
// context.lineWidth = 2
// context.beginPath()
// context.arc(p.x, p.y, p.r, 0, Math.PI*2)
// context.stroke()
// // drawing the outlines of the circles
// context.strokeStyle = centerColor
// context.lineWidth = 1
// context.beginPath()
// context.arc(p.x, p.y, 3, 0, Math.PI*2)
// context.stroke()
// })
yield context.canvas;
}
}