xxxxxxxxxx
215
let canvas;
let prevTime, currTime, eTime;
let rings, beads;
function setup() {
// We are still calling createCanvas like in the past, but now
// we are storing the result as a variable. This way we can
// call methods of the element, to set the position for instance.
canvas = createCanvas(600, 600);
// Here we call methods of each element to set the position
// and id, try changing these values.
// Use the inspector to look at the HTML generated from this
// code when you load the sketch in your browser.
//canvas.parent("paper");
//canvas.id("canvas0");
let cx = width / 2, cy = height/2;
rings = [];
rings[rings.length] = new Ring(cx, cy, 190); // [0]
rings[rings.length] = new Ring(cx-240, cy, 50); // [1]
rings[rings.length] = new Ring(cx, cy-110, 80); // [2]
rings[rings.length] = new Ring(cx, cy+80, 110); // [3]
rings[rings.length] = new Ring(cx+110, cy-190, 30); // [4]
rings[rings.length] = new Ring(cx+163, cy+163, 40); // [5]
rings[0].addJunction(new Junction(rings[0], PI, rings[1], 0, EXTERNAL));
rings[1].addJunction(new Junction(rings[1], 0, rings[0], PI, EXTERNAL));
rings[0].addJunction(new Junction(rings[0], 1.5 * PI, rings[2], 1.5 * PI, INTERNAL));
rings[2].addJunction(new Junction(rings[2], 1.5 * PI, rings[0], 1.5 * PI, INTERNAL));
rings[2].addJunction(new Junction(rings[2], 0.5 * PI, rings[3], 1.5 * PI, EXTERNAL));
rings[3].addJunction(new Junction(rings[3], 1.5 * PI, rings[2], 0.5 * PI, EXTERNAL));
rings[0].addJunction(new Junction(rings[0], 0.5 * PI, rings[3], 0.5 * PI, INTERNAL));
rings[3].addJunction(new Junction(rings[3], 0.5 * PI, rings[0], 0.5 * PI, INTERNAL));
rings[0].addJunction(new Junction(rings[0], 2.5 * TAU / 3, rings[4], TAU / 3, EXTERNAL));
rings[4].addJunction(new Junction(rings[4], TAU / 3, rings[0], 2.5 * TAU / 3, EXTERNAL));
rings[0].addJunction(new Junction(rings[0], TAU / 8, rings[5], 5 * TAU / 8, EXTERNAL));
rings[5].addJunction(new Junction(rings[5], 5 * TAU / 8, rings[0], TAU / 8, EXTERNAL));
beads = [];
for (let n = 0; n < 30; n++) {
beads[beads.length] = new Bead(rings[0], 0.01, CW);
beads[beads.length] = new Bead(rings[3], 0.01, CCW);
for (let b of beads) b.update(0.05);
}
currTime = prevTime = millis();
}
function draw() {
// Calculate the time elpsaed sonce that call to draw() and use
// it to update the bead position
currTime = millis();
eTime = (currTime - prevTime) / 1000;
prevTime = currTime;
for (let b of beads) b.update(eTime);
// Now render the frame
background(32);
for (let r of rings) r.show();
for (let b of beads) b.show();
}
/*
This method determines whether an angle falls inside the arc range and
if so its position inside the range. The range can be clockwise
(e > s) or counter-clockwise (s > e).
PRE-REQUISITES
The angle subtended by the arc must not exceed PI radians (180 degrees).
All parameters must be in the range >=0 and < TAU (2*PI)
RETURNS
>= 0 (start) and <= 1 (end) indicates position within arc range
undefined if s == e or not in arc range
*/
function whereInArc(s, e, c) {
let t = null, isInRange = false;
if (s !== e) {
// Range straddles zero? Then add TWO PI to any angle less than PI
let straddle0 = abs(e - s) > PI;
if (straddle0) {
s = s < PI ? s += TAU : s;
e = e < PI ? e += TAU : e;
c = c < PI ? c += TAU : c;
}
// Straddle c
let straddleC = (c - s) / (c - e) < 0;
if (straddleC) {
t = (c-s)/(e-s);
}
}
return t;
}
function normAngle(a) {
while (a < 0) a+= TAU;
while (a > TAU) a-= TAU;
return a;
}
// #####################################################
// Classes
// #####################################################
class Bead {
constructor(ring, startAngle, dir) {
this.ring = ring;
this.currAngle = startAngle;
this.dir = dir;
this.velocity = 320;
this.r = floor(random(128, 240));
this.g = floor(random(128, 240));
this.b = floor(random(128, 240));
}
update(elapsedTime) {
if (elapsedTime > 0) {
/*
calculate the next angle assuming no junction
if we hit a junction
change ring
current angle = junction angle on new ring
calculate new elapsed time based
recursive call to this method with new elapsed time
else
make the end angle current
*/
let deltaAngle = this.dir * elapsedTime * this.velocity / this.ring.r;
let nextAngle = normAngle(this.currAngle + deltaAngle);
// See if we have reached a junction
let found = this.ring.findJunction(this.currAngle, nextAngle);
if (found) {
this.ring = found.junction.toRing;
this.currAngle = found.junction.toAngle;
this.dir *= found.junction.juncType;
elapsedTime = elapsedTime * found.unused;
this.update(elapsedTime);
} else {
this.currAngle = nextAngle;
}
}
}
show() {
fill(this.r, this.g, this.b);
noStroke();
let x = this.ring.x + this.ring.r * cos(this.currAngle);
let y = this.ring.y + this.ring.r * sin(this.currAngle);
ellipse(x, y, 12, 12);
}
}
const INTERNAL = +1;
const EXTERNAL = -1;
class Junction {
constructor(fromRing, fromAngle, toRing, toAngle, juncType) {
this.fromRing = fromRing;
this.fromAngle = fromAngle;
this.toRing = toRing;
this.toAngle = toAngle;
this.juncType = juncType;
}
}
const PI = Math.PI;
const TAU = 2 * PI;
const CCW = -1, CW = 1;
class Ring {
constructor(x, y, r) {
this.x = x;
this.y = y;
this.r = r;
this.junctions = [];
}
show() {
stroke(128);
strokeWeight(1.25);
fill(255, 255, 255, 10);
ellipse(this.x, this.y, 2*this.r, 2*this.r);
}
addJunction(j) {
this.junctions[this.junctions.length] = j;
}
findJunction(s, e) {
let unUsed = 0, found = null;
for (let junction of this.junctions) {
let wia = whereInArc(s, e, junction.fromAngle);
if (wia && (1 - wia) > unUsed) {
unUsed = 1 - wia;
found = junction;
}
}
return found ? { "junction" : found, "unused" : unUsed } : null;
}
}