It appeared that the robots were dancing in perfect synchronous harmony. That is, until something unexplained happened and drove them to break apart.
A Pen by ByteKnight on CodePen.
<canvas></canvas> | |
<!-- | |
,--. ,--. | |
((O ))--((O )) | |
,'_`--'____`--'_`. | |
_: ____________ :_ | |
| | ||::::::::::|| | | | |
| | ||::::::::::|| | | | |
| | ||::::::::::|| | | | |
|_| |/__________\| |_| | |
|________________| | |
__..-' `-..__ | |
.-| : .----------------. : |-. | |
,\ || | |\______________/| | || /. | |
/`.\:| | || __ __ __ || | |;/,'\ | |
:`-._\;.| || '--''--''--' || |,:/_.-': | |
| : | || .----------. || | : | | |
| | | || '----SSt---' || | | | | |
| | | || _ _ _ || | | | | |
:,--.; | || (_) (_) (_) || | :,--.; | |
(`-'|) | ||______________|| | (|`-') | |
`--' | |/______________\| | `--' | |
|____________________| | |
`.________________,' | |
(_______)(_______) | |
(_______)(_______) | |
(_______)(_______) | |
(_______)(_______) | |
| || | | |
'--------''--------' | |
--> |
{ | |
class Robot { | |
constructor(color, light, size, x, y, struct) { | |
this.x = x; | |
this.points = []; | |
this.links = []; | |
this.frame = 0; | |
this.dir = 1; | |
this.size = size; | |
this.color = Math.round(color); | |
this.light = light; | |
// ---- create points ---- | |
for (const p of struct.points) { | |
this.points.push(new Robot.Point(size * p.x + x, size * p.y + y, p.f)); | |
} | |
// ---- create links ---- | |
for (const link of struct.links) { | |
const p0 = this.points[link.p0]; | |
const p1 = this.points[link.p1]; | |
const dx = p0.x - p1.x; | |
const dy = p0.y - p1.y; | |
this.links.push( | |
new Robot.Link( | |
this, | |
p0, | |
p1, | |
Math.sqrt(dx * dx + dy * dy), | |
link.size * size / 3, | |
link.lum, | |
link.force, | |
link.disk | |
) | |
); | |
} | |
} | |
update() { | |
if (++this.frame % 20 === 0) this.dir = -this.dir; | |
if ( | |
dancerDrag && | |
this === dancerDrag && | |
this.size < 16 && | |
this.frame > 600 | |
) { | |
dancerDrag = null; | |
dancers.push( | |
new Robot( | |
this.color, | |
this.light * 1.25, | |
this.size * 2, | |
pointer.x, | |
pointer.y - 100 * this.size * 2, | |
struct | |
) | |
); | |
dancers.sort(function(d0, d1) { | |
return d0.size - d1.size; | |
}); | |
} | |
// ---- update links ---- | |
for (const link of this.links) { | |
const p0 = link.p0; | |
const p1 = link.p1; | |
const dx = p0.x - p1.x; | |
const dy = p0.y - p1.y; | |
const dist = Math.sqrt(dx * dx + dy * dy); | |
if (dist) { | |
const tw = p0.w + p1.w; | |
const r1 = p1.w / tw; | |
const r0 = p0.w / tw; | |
const dz = (link.distance - dist) * link.force; | |
const sx = dx / dist * dz; | |
const sy = dy / dist * dz; | |
p1.x -= sx * r0; | |
p1.y -= sy * r0; | |
p0.x += sx * r1; | |
p0.y += sy * r1; | |
} | |
} | |
// ---- update points ---- | |
for (const point of this.points) { | |
// ---- dragging ---- | |
if (this === dancerDrag && point === pointDrag) { | |
point.x += (pointer.x - point.x) * 0.1; | |
point.y += (pointer.y - point.y) * 0.1; | |
} | |
// ---- dance ---- | |
if (this !== dancerDrag) { | |
point.fn && point.fn(16 * Math.sqrt(this.size), this.dir); | |
} | |
// ---- verlet integration ---- | |
point.vx = point.x - point.px; | |
point.vy = point.y - point.py; | |
point.px = point.x; | |
point.py = point.y; | |
point.vx *= 0.995; | |
point.vy *= 0.995; | |
point.x += point.vx; | |
point.y += point.vy + 0.01; | |
} | |
// ---- ground ---- | |
for (const link of this.links) { | |
const p1 = link.p1; | |
if (p1.y > canvas.height * ground - link.size * 0.5) { | |
p1.y = canvas.height * ground - link.size * 0.5; | |
p1.x -= p1.vx; | |
p1.vx = 0; | |
p1.vy = 0; | |
} | |
} | |
// ---- center position ---- | |
const delta = (this.x - this.points[0].x) * 0.0002; | |
this.points[9].x += delta; | |
this.points[10].x += delta; | |
} | |
draw() { | |
for (const link of this.links) { | |
if (link.size) { | |
const dx = link.p1.x - link.p0.x; | |
const dy = link.p1.y - link.p0.y; | |
const a = Math.atan2(dy, dx); | |
const d = Math.sqrt(dx * dx + dy * dy); | |
// ---- shadow ---- | |
ctx.save(); | |
ctx.translate(link.p0.x + link.size * 0.25, link.p0.y + link.size * 0.25); | |
ctx.rotate(a); | |
ctx.drawImage( | |
link.shadow, | |
-link.size * 0.5, | |
-link.size * 0.5, | |
d + link.size, | |
link.size | |
); | |
ctx.restore(); | |
// ---- stroke ---- | |
ctx.save(); | |
ctx.translate(link.p0.x, link.p0.y); | |
ctx.rotate(a); | |
ctx.drawImage( | |
link.image, | |
-link.size * 0.5, | |
-link.size * 0.5, | |
d + link.size, | |
link.size | |
); | |
ctx.restore(); | |
} | |
} | |
} | |
} | |
Robot.Link = class Link { | |
constructor(parent, p0, p1, dist, size, light, force, disk) { | |
// ---- cache strokes ---- | |
function stroke(color, axis) { | |
const image = document.createElement("canvas"); | |
image.width = dist + size; | |
image.height = size; | |
const ict = image.getContext("2d"); | |
ict.beginPath(); | |
ict.lineCap = "round"; | |
ict.lineWidth = size; | |
ict.strokeStyle = color; | |
if (disk) { | |
ict.arc(size * 0.5 + dist, size * 0.5, size * 0.5, 0, 2 * Math.PI); | |
ict.fillStyle = color; | |
ict.fill(); | |
} else { | |
ict.moveTo(size * 0.5, size * 0.5); | |
ict.lineTo(size * 0.5 + dist, size * 0.5); | |
ict.stroke(); | |
} | |
if (axis) { | |
const s = size / 10; | |
ict.fillStyle = "#000"; | |
ict.fillRect(size * 0.5 - s, size * 0.5 - s, s * 2, s * 2); | |
ict.fillRect(size * 0.5 - s + dist, size * 0.5 - s, s * 2, s * 2); | |
} | |
return image; | |
} | |
this.p0 = p0; | |
this.p1 = p1; | |
this.distance = dist; | |
this.size = size; | |
this.light = light || 1.0; | |
this.force = force || 0.5; | |
this.image = stroke( | |
"hsl(" + parent.color + " ,30%, " + parent.light * this.light + "%)", | |
true | |
); | |
this.shadow = stroke("rgba(0,0,0,0.5)"); | |
} | |
}; | |
Robot.Point = class Point { | |
constructor(x, y, fn, w) { | |
this.x = x; | |
this.y = y; | |
this.w = w || 0.5; | |
this.fn = fn || null; | |
this.px = x; | |
this.py = y; | |
this.vx = 0.0; | |
this.vy = 0.0; | |
} | |
}; | |
// ---- set canvas ---- | |
const canvas = { | |
init() { | |
this.elem = document.querySelector("canvas"); | |
this.resize(); | |
window.addEventListener("resize", () => this.resize(), false); | |
return this.elem.getContext("2d"); | |
}, | |
resize() { | |
this.width = this.elem.width = this.elem.offsetWidth; | |
this.height = this.elem.height = this.elem.offsetHeight; | |
ground = this.height > 500 ? 0.85 : 1.0; | |
for (let i = 0; i < dancers.length; i++) { | |
dancers[i].x = (i + 2) * canvas.width / 9; | |
} | |
} | |
}; | |
// ---- set pointer ---- | |
const pointer = { | |
init(canvas) { | |
this.x = 0; | |
this.y = 0; | |
window.addEventListener("mousemove", e => this.move(e), false); | |
canvas.elem.addEventListener("touchmove", e => this.move(e), false); | |
window.addEventListener("mousedown", e => this.down(e), false); | |
window.addEventListener("touchstart", e => this.down(e), false); | |
window.addEventListener("mouseup", e => this.up(e), false); | |
window.addEventListener("touchend", e => this.up(e), false); | |
}, | |
down(e) { | |
this.move(e); | |
for (const dancer of dancers) { | |
for (const point of dancer.points) { | |
const dx = pointer.x - point.x; | |
const dy = pointer.y - point.y; | |
const d = Math.sqrt(dx * dx + dy * dy); | |
if (d < 60) { | |
dancerDrag = dancer; | |
pointDrag = point; | |
dancer.frame = 0; | |
} | |
} | |
} | |
}, | |
up(e) { | |
dancerDrag = null; | |
}, | |
move(e) { | |
let touchMode = e.targetTouches, | |
pointer; | |
if (touchMode) { | |
e.preventDefault(); | |
pointer = touchMode[0]; | |
} else pointer = e; | |
this.x = pointer.clientX; | |
this.y = pointer.clientY; | |
} | |
}; | |
// ---- init ---- | |
const dancers = []; | |
let ground = 1.0; | |
const ctx = canvas.init(); | |
pointer.init(canvas); | |
let dancerDrag = null; | |
let pointDrag = null; | |
// ---- main loop ---- | |
const run = () => { | |
requestAnimationFrame(run); | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
ctx.fillStyle = "#222"; | |
ctx.fillRect(0, 0, canvas.width, canvas.height * 0.15); | |
ctx.fillRect(0, canvas.height * 0.85, canvas.width, canvas.height * 0.15); | |
for (const dancer of dancers) { | |
dancer.update(); | |
dancer.draw(); | |
} | |
}; | |
// ---- robot structure ---- | |
const struct = { | |
points: [ | |
{ | |
x: 0, | |
y: -4, | |
f(s, d) { | |
this.y -= 0.01 * s; | |
} | |
}, | |
{ | |
x: 0, | |
y: -16, | |
f(s, d) { | |
this.y -= 0.02 * s * d; | |
} | |
}, | |
{ | |
x: 0, | |
y: 12, | |
f(s, d) { | |
this.y += 0.02 * s * d; | |
} | |
}, | |
{ x: -12, y: 0 }, | |
{ x: 12, y: 0 }, | |
{ | |
x: -3, | |
y: 34, | |
f(s, d) { | |
if (d > 0) { | |
this.x += 0.01 * s; | |
this.y -= 0.015 * s; | |
} else { | |
this.y += 0.02 * s; | |
} | |
} | |
}, | |
{ | |
x: 3, | |
y: 34, | |
f(s, d) { | |
if (d > 0) { | |
this.y += 0.02 * s; | |
} else { | |
this.x -= 0.01 * s; | |
this.y -= 0.015 * s; | |
} | |
} | |
}, | |
{ | |
x: -28, | |
y: 0, | |
f(s, d) { | |
this.x += this.vx * 0.035; | |
this.y -= 0.001 * s; | |
} | |
}, | |
{ | |
x: 28, | |
y: 0, | |
f(s, d) { | |
this.x += this.vx * 0.035; | |
this.y -= 0.001 * s; | |
} | |
}, | |
{ | |
x: -3, | |
y: 64, | |
f(s, d) { | |
this.y += 0.015 * s; | |
if (d > 0) { | |
this.y -= 0.01 * s; | |
} else { | |
this.y += 0.05 * s; | |
} | |
} | |
}, | |
{ | |
x: 3, | |
y: 64, | |
f(s, d) { | |
this.y += 0.015 * s; | |
if (d > 0) { | |
this.y += 0.05 * s; | |
} else { | |
this.y -= 0.01 * s; | |
} | |
} | |
} | |
], | |
links: [ | |
{ p0: 3, p1: 7, size: 12, lum: 0.5 }, | |
{ p0: 1, p1: 3, size: 24, lum: 0.5 }, | |
{ p0: 1, p1: 0, size: 60, lum: 0.5, disk: 1 }, | |
{ p0: 5, p1: 9, size: 16, lum: 0.5 }, | |
{ p0: 2, p1: 5, size: 32, lum: 0.5 }, | |
{ p0: 1, p1: 2, size: 50, lum: 1 }, | |
{ p0: 6, p1: 10, size: 16, lum: 1.5 }, | |
{ p0: 2, p1: 6, size: 32, lum: 1.5 }, | |
{ p0: 4, p1: 8, size: 12, lum: 1.5 }, | |
{ p0: 1, p1: 4, size: 24, lum: 1.5 } | |
] | |
}; | |
// ---- instanciate robots ---- | |
for (let i = 0; i < 6; i++) { | |
dancers.push( | |
new Robot( | |
i * 360 / 7, | |
80, | |
4, | |
(i + 2) * canvas.width / 9, | |
canvas.height * ground - 340, | |
struct | |
) | |
); | |
} | |
run(); | |
} |
body, html { | |
position: absolute; | |
margin: 0; | |
padding: 0; | |
width: 100%; | |
height: 100%; | |
overflow: hidden; | |
} | |
canvas { | |
position: absolute; | |
width: 100%; | |
height: 100%; | |
background:#000; | |
cursor: pointer; | |
} |
It appeared that the robots were dancing in perfect synchronous harmony. That is, until something unexplained happened and drove them to break apart.
A Pen by ByteKnight on CodePen.