|
|
@@ -0,0 +1,179 @@ |
|
|
console.clear(); |
|
|
|
|
|
const PI = Math.PI, |
|
|
PI2 = PI * 2, |
|
|
RATE = 30, |
|
|
CVS1 = document.querySelector('canvas:nth-child(1)'), |
|
|
CVS2 = document.querySelector('canvas:nth-child(2)'), |
|
|
CTX1 = CVS1.getContext('2d'), |
|
|
CTX2 = CVS2.getContext('2d'), |
|
|
CVS_DI = CVS1.width, |
|
|
// How many cols/rows |
|
|
COUNT = 8, |
|
|
// Padding |
|
|
PEN_PAD = CVS_DI * 0.025, |
|
|
// Radius |
|
|
PEN_RAD = CVS_DI / ((COUNT + 1) * 2), |
|
|
// Stroke line width |
|
|
STROKE = 2, |
|
|
// Indicator radius |
|
|
TIP_RAD = 6, |
|
|
// Coloring |
|
|
COLOR = '#FFF', |
|
|
COLOR_OFF = '#333'; |
|
|
|
|
|
// An input "Pen", influences the x or y of a Group's output |
|
|
class Pen { |
|
|
constructor({ rate, x, y }) { |
|
|
this.di = PEN_RAD * 2; |
|
|
this.rad = PEN_RAD * 0.7; |
|
|
this.x = x * this.di; |
|
|
this.y = y * this.di; |
|
|
this.cx = this.x + this.di * 0.5; |
|
|
this.cy = this.y + this.di * 0.5; |
|
|
this.rateName = rate; |
|
|
this.rate = RATE * rate; |
|
|
this.position = 0; |
|
|
} |
|
|
|
|
|
tick() { |
|
|
this.progress = this.position / this.rate; |
|
|
this._calc(); |
|
|
this.position++; |
|
|
} |
|
|
|
|
|
// Setting the coordinates for the tip of the pen |
|
|
_calc() { |
|
|
let deg = 360 * this.progress; |
|
|
this.tipX = this.cx + this.rad * Math.cos(deg * PI / 180); |
|
|
this.tipY = this.cy + this.rad * Math.sin(deg * PI / 180); |
|
|
} |
|
|
} |
|
|
|
|
|
// Takes an x pen and y pen and produces an output |
|
|
class Group { |
|
|
constructor(penX, penY) { |
|
|
this.penX = penX; |
|
|
this.penY = penY; |
|
|
this.tracker = {}; |
|
|
this.progress = 0; |
|
|
} |
|
|
|
|
|
tick() { |
|
|
// setting the last coordinates for the output line |
|
|
this.lastX = this.x; |
|
|
this.lastY = this.y; |
|
|
// Setting the current coordinates |
|
|
this.x = this.penX.tipX + this.penY.x; |
|
|
this.y = this.penY.tipY + this.penX.y; |
|
|
// Storing the combination so we never redraw |
|
|
let key = `${this.lastX}-${this.lastY}-${this.x}-${this.y}`; |
|
|
// Setting draw to false if this has already been drawn |
|
|
if (this.tracker[key]) this.draw = false; |
|
|
// Setting draw to true then storing that this has been drawn |
|
|
else { this.tracker[key] = true; this.draw = true }; |
|
|
// Draw the output and the Pens |
|
|
this._draw(); |
|
|
this.progress++; |
|
|
} |
|
|
|
|
|
_draw() { |
|
|
this._drawPen(this.penX); |
|
|
this._drawPen(this.penY); |
|
|
this._drawOutput(); |
|
|
} |
|
|
|
|
|
// Take a pen and draw it, its rate, and an indicator of current position |
|
|
_drawPen({ rad, di, x, y, cx, cy, tipX, tipY, rateName }) { |
|
|
// the circle |
|
|
CTX1.lineWidth = STROKE; |
|
|
CTX1.strokeStyle = COLOR_OFF; |
|
|
CTX1.beginPath(); |
|
|
CTX1.arc(cx, cy, rad, 0, PI2, false); |
|
|
CTX1.stroke(); |
|
|
|
|
|
// The current position |
|
|
CTX1.fillStyle = COLOR; |
|
|
CTX1.strokeStyle = COLOR; |
|
|
CTX1.beginPath(); |
|
|
CTX1.arc(tipX, tipY, TIP_RAD, 0, PI2, false); |
|
|
CTX1.fill(); |
|
|
|
|
|
// the rate number |
|
|
CTX1.fillStyle = COLOR_OFF; |
|
|
CTX1.font = `lighter ${rad * 0.5}px Helvetica`; |
|
|
CTX1.textAlign = 'center'; |
|
|
CTX1.textBaseline = 'middle'; |
|
|
CTX1.fillText(rateName, cx, cy); |
|
|
} |
|
|
|
|
|
// Draw the output of the two pens |
|
|
_drawOutput() { |
|
|
// If it hasnt already been drawn yet, |
|
|
// draw the path on the canvas that isnt erased each frame |
|
|
if (this.draw) { |
|
|
CTX2.lineWidth = STROKE; |
|
|
CTX2.strokeStyle = COLOR; |
|
|
CTX2.beginPath(); |
|
|
CTX2.moveTo(this.lastX, this.lastY); |
|
|
CTX2.lineTo(this.x, this.y); |
|
|
CTX2.stroke(); |
|
|
} |
|
|
|
|
|
// Draw the indicator on the refreshing canvas |
|
|
CTX1.fillStyle = COLOR; |
|
|
CTX1.beginPath(); |
|
|
CTX1.arc(this.x, this.y, TIP_RAD, 0, PI2, false); |
|
|
CTX1.fill(); |
|
|
} |
|
|
} |
|
|
|
|
|
let pensX = [], |
|
|
pensY = [], |
|
|
groups = []; |
|
|
|
|
|
// Generating the pens |
|
|
for (let x = 0; x < COUNT; x++) { |
|
|
let penX = new Pen({ rate: x + 1, x: x + 1, y: 0 }), |
|
|
penY = new Pen({ rate: x + 1, x: 0, y: x + 1 }); |
|
|
pensX.push(penX); |
|
|
pensY.push(penY); |
|
|
} |
|
|
|
|
|
// Generating the groups |
|
|
for (let x = 0; x < pensY.length; x++) |
|
|
for (let y = 0; y < pensX.length; y++) |
|
|
groups.push(new Group(pensX[x], pensY[y])); |
|
|
|
|
|
// Start running |
|
|
run(); |
|
|
|
|
|
function run() { |
|
|
// Clear the refreshing canvas |
|
|
CTX1.clearRect(0, 0, CVS_DI, CVS_DI); |
|
|
|
|
|
// For each pen |
|
|
for (let i = 0; i < pensX.length; i++) { |
|
|
// Tick them forward to get new coords |
|
|
pensX[i].tick(); |
|
|
pensY[i].tick(); |
|
|
|
|
|
// Draw the axis |
|
|
CTX1.lineWidth = STROKE; |
|
|
CTX1.strokeStyle = COLOR_OFF; |
|
|
CTX1.beginPath(); |
|
|
CTX1.moveTo(pensX[i].tipX, 0); |
|
|
CTX1.lineTo(pensX[i].tipX, CVS_DI); |
|
|
CTX1.stroke(); |
|
|
CTX1.beginPath(); |
|
|
CTX1.moveTo(0, pensY[i].tipY); |
|
|
CTX1.lineTo(CVS_DI, pensY[i].tipY); |
|
|
CTX1.stroke(); |
|
|
} |
|
|
|
|
|
// Tick each group (draws them as well) |
|
|
groups.forEach(group => group.tick()); |
|
|
|
|
|
// Run it again |
|
|
requestAnimationFrame(run); |
|
|
} |