Skip to content

Instantly share code, notes, and snippets.

@SH20RAJ
Last active November 2, 2020 04:51
Show Gist options
  • Save SH20RAJ/5c0eef4b9864db5553507a5023d5aa39 to your computer and use it in GitHub Desktop.
Save SH20RAJ/5c0eef4b9864db5553507a5023d5aa39 to your computer and use it in GitHub Desktop.
The Last Experience

The Last Experience

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 Gerard Ferrandez on CodePen.

License.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta charset="utf-8">
<meta name='viewport' content='width=device-width, initial-scale=1'>
<title>The Last Experience - Web Background</title>
<link rel="stylesheet" media="screen" href="https://static.codepen.io/assets/fullpage/fullpage-4de243a40619a967c0bf13b95e1ac6f8de89d943b7fc8710de33f681fe287604.css" />
<link href="https://fonts.googleapis.com/css?family=Lato:300,400,400italic,700,700italic,900,900italic" rel="stylesheet" />
<title>CodePen - The Last Experience</title>
<script>
if (document.location.search.match(/type=embed/gi)) {
window.parent.postMessage("resize", "*");
}
</script>
<style>
html { font-size: 15px; }
html, body { margin: 0; padding: 0; min-height: 100%; }
body { height:100%; display: flex; flex-direction: column; }
.referer-warning {
background: black;
box-shadow: 0 2px 5px rgba(0,0,0, 0.5);
padding: 0.75em;
color: white;
text-align: center;
font-family: 'Lato', 'Lucida Grande', 'Lucida Sans Unicode', Tahoma, Sans-Serif;
line-height: 1.2;
font-size: 1rem;
position: relative;
z-index: 2;
}
.referer-warning h1 { font-size: 1.2rem; margin: 0; }
.referer-warning a { color: #56bcf9; } /* $linkColorOnBlack */
</style>
</head>
<body class="">
<div id="result-iframe-wrap" role="main">
<iframe
id="result"
srcdoc="
<!DOCTYPE html>
<html lang=&quot;en&quot; >
<head>
<meta charset=&quot;UTF-8&quot;>
<link rel=&quot;apple-touch-icon&quot; type=&quot;image/png&quot; href=&quot;https://static.codepen.io/assets/favicon/apple-touch-icon-5ae1a0698dcc2402e9712f7d01ed509a57814f994c660df9f7a952f3060705ee.png&quot; />
<meta name=&quot;apple-mobile-web-app-title&quot; content=&quot;CodePen&quot;>
<link rel=&quot;shortcut icon&quot; type=&quot;image/x-icon&quot; href=&quot;https://static.codepen.io/assets/favicon/favicon-aec34940fbc1a6e787974dcd360f2c6b63348d4b1f4e06c77743096d55480f33.ico&quot; />
<link rel=&quot;mask-icon&quot; type=&quot;&quot; href=&quot;https://static.codepen.io/assets/favicon/logo-pin-8f3771b1072e3c38bd662872f6b673a722f4b3ca2421637d5596661b4e2132cc.svg&quot; color=&quot;#111&quot; />
<title>CodePen - The Last Experience</title>
<style>
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;
}
</style>
<script>
window.console = window.console || function(t) {};
</script>
<script>
if (document.location.search.match(/type=embed/gi)) {
window.parent.postMessage(&quot;resize&quot;, &quot;*&quot;);
}
</script>
</head>
<body translate=&quot;no&quot; >
<canvas></canvas><!--
o
\_/\o
( Oo) \|/
(_=-) .===O- ~~Z~A~P~~ -O-
/ \_/U' /|\
|| |_/
\\ |
{K ||
| PP
| ||
(__\\
-->
<script src=&quot;https://static.codepen.io/assets/common/stopExecutionOnTimeout-157cd5b220a5c80d4ff8e0e70ac069bffd87a61252088146915e8726e5d9f147.js&quot;></script>
<script id=&quot;rendered-js&quot; >
&quot;use strict&quot;;
///////////////// worker thread code ///////////////////
const theLastExperience = noWorkers => {
&quot;use strict&quot;;
// ---- robot structure ----
const struct = {
points: [
{
x: 0,
y: -4,
f(s, d) {
this.y -= 0.01 * s * ts;
} },
{
x: 0,
y: -16,
f(s, d) {
this.y -= 0.02 * s * d * ts;
} },
{
x: 0,
y: 12,
f(s, d) {
this.y += 0.02 * s * d * ts;
} },
{ x: -12, y: 0 },
{ x: 12, y: 0 },
{
x: -3,
y: 34,
f(s, d) {
if (d > 0) {
this.x += 0.01 * s * ts;
this.y -= 0.015 * s * ts;
} else {
this.y += 0.02 * s * ts;
}
} },
{
x: 3,
y: 34,
f(s, d) {
if (d > 0) {
this.y += 0.02 * s * ts;
} else {
this.x -= 0.01 * s * ts;
this.y -= 0.015 * s * ts;
}
} },
{
x: -28,
y: 0,
f(s, d) {
this.x += this.vx * 0.025 * ts;
this.y -= 0.001 * s * ts;
} },
{
x: 28,
y: 0,
f(s, d) {
this.x += this.vx * 0.025 * ts;
this.y -= 0.001 * s * ts;
} },
{
x: -3,
y: 64,
f(s, d) {
this.y += 0.015 * s * ts;
if (d > 0) {
this.y -= 0.01 * s * ts;
} else {
this.y += 0.05 * s * ts;
}
} },
{
x: 3,
y: 64,
f(s, d) {
this.y += 0.015 * s * ts;
if (d > 0) {
this.y += 0.05 * s * ts;
} else {
this.y -= 0.01 * s * ts;
}
} }],
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 }] };
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 % Math.round(20 / ts) === 0) this.dir = -this.dir;
if (this === pointer.dancerDrag &amp;&amp; this.size < 16 &amp;&amp; this.frame > 600) {
pointer.dancerDrag = null;
dancers.push(
new Robot(
this.color + 90,
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) link.update();
// ---- update points ----
for (const point of this.points) point.update(this);
// ---- 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 ----
this.points[3].x += (this.x - this.points[3].x) * 0.001;
}
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);
// ---- 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);
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);
ctx.restore();
}
}
}}
Robot.Link = class Link {
constructor(parent, p0, p1, dist, size, light, force, disk) {
this.p0 = p0;
this.p1 = p1;
this.distance = dist;
this.size = size;
this.light = light || 1.0;
this.force = force || 0.5;
this.image = this.stroke(
&quot;hsl(&quot; + parent.color + &quot; ,30%, &quot; + parent.light * this.light + &quot;%)&quot;,
true, disk, dist, size);
this.shadow = this.stroke(&quot;rgba(0,0,0,0.5)&quot;, false, disk, dist, size);
}
update() {
const p0 = this.p0;
const p1 = this.p1;
const dx = p1.x - p0.x;
const dy = p1.y - p0.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0.0) {
const tw = p0.w + p1.w;
const r1 = p1.w / tw;
const r0 = p0.w / tw;
const dz = (this.distance - dist) * this.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;
}
}
stroke(color, axis, disk, dist, size) {
let image;
if (noWorkers) {
image = document.createElement(&quot;canvas&quot;);
image.width = dist + size;
image.height = size;
} else {
image = new OffscreenCanvas(dist + size, size);
}
const ict = image.getContext(&quot;2d&quot;);
ict.beginPath();
ict.lineCap = &quot;round&quot;;
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 = &quot;#000&quot;;
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;
}};
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;
}
update(robot) {
// ---- dragging ----
if (robot === pointer.dancerDrag &amp;&amp; this === pointer.pointDrag) {
this.x += (pointer.x - this.x) * 0.1;
this.y += (pointer.y - this.y) * 0.1;
}
// ---- dance ----
if (robot !== pointer.dancerDrag) {
this.fn &amp;&amp; this.fn(16 * Math.sqrt(robot.size), robot.dir);
}
// ---- verlet integration ----
this.vx = this.x - this.px;
this.vy = this.y - this.py;
this.px = this.x;
this.py = this.y;
this.vx *= 0.995;
this.vy *= 0.995;
this.x += this.vx;
this.y += this.vy + 0.01 * ts;
}};
// ---- init ----
const dancers = [];
let ground = 1.0;
let canvas = { width: 0, height: 0, resize: true };
let ctx = null;
let pointer = { x: 0, y: 0, dancerDrag: null, pointDrag: null };
let ts = 1;
let lastTime = 0;
// ---- messages from the main thread ----
const message = e => {
switch (e.data.msg) {
case &quot;start&quot;:
canvas.elem = e.data.elem;
canvas.width = canvas.elem.width;
canvas.height = canvas.elem.height;
ctx = canvas.elem.getContext(&quot;2d&quot;);
initRobots();
requestAnimationFrame(run);
break;
case &quot;resize&quot;:
canvas.width = e.data.width;
canvas.height = e.data.height;
canvas.resize = true;
break;
case &quot;pointerMove&quot;:
pointer.x = e.data.x;
pointer.y = e.data.y;
break;
case &quot;pointerDown&quot;:
pointer.x = e.data.x;
pointer.y = e.data.y;
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) {
pointer.dancerDrag = dancer;
pointer.pointDrag = point;
dancer.frame = 0;
}
}
}
break;
case &quot;pointerUp&quot;:
pointer.dancerDrag = null;
break;}
};
// ---- resize screen ----
const resize = () => {
canvas.elem.width = canvas.width;
canvas.elem.height = canvas.height;
canvas.resize = false;
ground = canvas.height > 500 ? 0.85 : 1.0;
for (let i = 0; i < dancers.length; i++) {
dancers[i].x = (i + 2) * canvas.width / 9;
}
};
// ---- main loop ----
const run = time => {
requestAnimationFrame(run);
if (canvas.resize === true) resize();
// ---- adjust speed to screen freq ----
if (lastTime !== 0) {
const t = (time - lastTime) / 16;
ts += (t - ts) * 0.1;
if (ts > 1) ts = 1;
}
lastTime = time;
// ---- clear screen ----
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = &quot;#222&quot;;
ctx.fillRect(0, 0, canvas.width, canvas.height * 0.15);
ctx.fillRect(0, canvas.height * 0.85, canvas.width, canvas.height * 0.15);
// ---- animate robots ----
for (const dancer of dancers) {
dancer.update();
dancer.draw();
}
};
const initRobots = () => {
// ---- instanciate robots ----
ground = canvas.height > 500 ? 0.85 : 1.0;
for (let i = 0; i < 6; i++) {
dancers.push(
new Robot(
i * 360 / 7,
80,
Math.sqrt(Math.min(canvas.width, canvas.height)) / 6,
(i + 2) * canvas.width / 9,
canvas.height * 0.5 - 100,
struct));
}
};
// ---- main thread vs. worker
if (noWorkers) {
// ---- emulate postMessage interface ----
return {
postMessage(data) {
message({ data: data });
} };
} else {
// ---- worker messaging ----
onmessage = message;
}
};
///////////////// main thread code ///////////////////
let worker = null;
const createWorker = fn => {
const URL = window.URL || window.webkitURL;
return new Worker(URL.createObjectURL(new Blob([&quot;(&quot; + fn + &quot;)()&quot;])));
};
// ---- init canvas ----
const canvas = document.querySelector(&quot;canvas&quot;);
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
// ---- instanciate worker ----
if (window.Worker &amp;&amp; window.OffscreenCanvas) {
// instanciating background worker from a function
worker = createWorker(theLastExperience);
// cloning OffscreenCanvas
const offscreen = canvas.transferControlToOffscreen();
// sending data to worker
worker.postMessage({ msg: &quot;start&quot;, elem: offscreen }, [offscreen]);
} else {
// falling back execution to the main thread
worker = theLastExperience(true);
worker.postMessage({ msg: &quot;start&quot;, elem: canvas });
}
// ---- resize event ----
window.addEventListener(
&quot;resize&quot;,
() => {
worker.postMessage({
msg: &quot;resize&quot;,
width: canvas.offsetWidth,
height: canvas.offsetHeight });
},
false);
// ---- pointer events ----
const pointer = {
x: 0,
y: 0,
down(e) {
this.move(e);
worker.postMessage({
msg: &quot;pointerDown&quot;,
x: this.x,
y: this.y });
},
up(e) {
worker.postMessage({
msg: &quot;pointerUp&quot; });
},
move(e) {
if (e.targetTouches) {
e.preventDefault();
this.x = e.targetTouches[0].clientX;
this.y = e.targetTouches[0].clientY;
} else {
this.x = e.clientX;
this.y = e.clientY;
}
worker.postMessage({
msg: &quot;pointerMove&quot;,
x: this.x,
y: this.y });
} };
window.addEventListener(&quot;mousemove&quot;, e => pointer.move(e), false);
canvas.addEventListener(&quot;touchmove&quot;, e => pointer.move(e), false);
window.addEventListener(&quot;mousedown&quot;, e => pointer.down(e), false);
window.addEventListener(&quot;touchstart&quot;, e => pointer.down(e), false);
window.addEventListener(&quot;mouseup&quot;, e => pointer.up(e), false);
window.addEventListener(&quot;touchend&quot;, e => pointer.up(e), false);
//# sourceURL=pen.js
</script>
</body>
</html>
"
sandbox="allow-downloads allow-forms allow-modals allow-pointer-lock allow-popups allow-presentation allow-scripts allow-top-navigation-by-user-activation" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; microphone; midi; payment; vr" allowTransparency="true"
allowpaymentrequest="true" allowfullscreen="true" class="result-iframe">
</iframe>
</div>
</body>
</html>
<canvas></canvas><!--
o
\_/\o
( Oo) \|/
(_=-) .===O- ~~Z~A~P~~ -O-
/ \_/U' /|\
|| |_/
\\ |
{K ||
| PP
| ||
(__\\
-->
"use strict";
///////////////// worker thread code ///////////////////
const theLastExperience = noWorkers => {
"use strict";
// ---- robot structure ----
const struct = {
points: [
{
x: 0,
y: -4,
f(s, d) {
this.y -= 0.01 * s * ts;
}
},
{
x: 0,
y: -16,
f(s, d) {
this.y -= 0.02 * s * d * ts;
}
},
{
x: 0,
y: 12,
f(s, d) {
this.y += 0.02 * s * d * ts;
}
},
{ x: -12, y: 0 },
{ x: 12, y: 0 },
{
x: -3,
y: 34,
f(s, d) {
if (d > 0) {
this.x += 0.01 * s * ts;
this.y -= 0.015 * s * ts;
} else {
this.y += 0.02 * s * ts;
}
}
},
{
x: 3,
y: 34,
f(s, d) {
if (d > 0) {
this.y += 0.02 * s * ts;
} else {
this.x -= 0.01 * s * ts;
this.y -= 0.015 * s * ts;
}
}
},
{
x: -28,
y: 0,
f(s, d) {
this.x += this.vx * 0.025 * ts;
this.y -= 0.001 * s * ts;
}
},
{
x: 28,
y: 0,
f(s, d) {
this.x += this.vx * 0.025 * ts;
this.y -= 0.001 * s * ts;
}
},
{
x: -3,
y: 64,
f(s, d) {
this.y += 0.015 * s * ts;
if (d > 0) {
this.y -= 0.01 * s * ts;
} else {
this.y += 0.05 * s * ts;
}
}
},
{
x: 3,
y: 64,
f(s, d) {
this.y += 0.015 * s * ts;
if (d > 0) {
this.y += 0.05 * s * ts;
} else {
this.y -= 0.01 * s * ts;
}
}
}
],
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 }
]
};
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 % Math.round(20 / ts) === 0) this.dir = -this.dir;
if (this === pointer.dancerDrag && this.size < 16 && this.frame > 600) {
pointer.dancerDrag = null;
dancers.push(
new Robot(
this.color + 90,
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) link.update();
// ---- update points ----
for (const point of this.points) point.update(this);
// ---- 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 ----
this.points[3].x += (this.x - this.points[3].x) * 0.001;
}
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);
// ---- 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
);
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
);
ctx.restore();
}
}
}
}
Robot.Link = class Link {
constructor(parent, p0, p1, dist, size, light, force, disk) {
this.p0 = p0;
this.p1 = p1;
this.distance = dist;
this.size = size;
this.light = light || 1.0;
this.force = force || 0.5;
this.image = this.stroke(
"hsl(" + parent.color + " ,30%, " + parent.light * this.light + "%)",
true, disk, dist, size
);
this.shadow = this.stroke("rgba(0,0,0,0.5)", false, disk, dist, size);
}
update() {
const p0 = this.p0;
const p1 = this.p1;
const dx = p1.x - p0.x;
const dy = p1.y - p0.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0.0) {
const tw = p0.w + p1.w;
const r1 = p1.w / tw;
const r0 = p0.w / tw;
const dz = (this.distance - dist) * this.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;
}
}
stroke(color, axis, disk, dist, size) {
let image;
if (noWorkers) {
image = document.createElement("canvas");
image.width = dist + size;
image.height = size;
} else {
image = new OffscreenCanvas(dist + size, 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;
}
};
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;
}
update(robot) {
// ---- dragging ----
if (robot === pointer.dancerDrag && this === pointer.pointDrag) {
this.x += (pointer.x - this.x) * 0.1;
this.y += (pointer.y - this.y) * 0.1;
}
// ---- dance ----
if (robot !== pointer.dancerDrag) {
this.fn && this.fn(16 * Math.sqrt(robot.size), robot.dir);
}
// ---- verlet integration ----
this.vx = this.x - this.px;
this.vy = this.y - this.py;
this.px = this.x;
this.py = this.y;
this.vx *= 0.995;
this.vy *= 0.995;
this.x += this.vx;
this.y += this.vy + 0.01 * ts;
}
};
// ---- init ----
const dancers = [];
let ground = 1.0;
let canvas = { width: 0, height: 0, resize: true };
let ctx = null;
let pointer = { x: 0, y: 0, dancerDrag: null, pointDrag: null };
let ts = 1;
let lastTime = 0;
// ---- messages from the main thread ----
const message = e => {
switch (e.data.msg) {
case "start":
canvas.elem = e.data.elem;
canvas.width = canvas.elem.width;
canvas.height = canvas.elem.height;
ctx = canvas.elem.getContext("2d");
initRobots();
requestAnimationFrame(run);
break;
case "resize":
canvas.width = e.data.width;
canvas.height = e.data.height;
canvas.resize = true;
break;
case "pointerMove":
pointer.x = e.data.x;
pointer.y = e.data.y;
break;
case "pointerDown":
pointer.x = e.data.x;
pointer.y = e.data.y;
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) {
pointer.dancerDrag = dancer;
pointer.pointDrag = point;
dancer.frame = 0;
}
}
}
break;
case "pointerUp":
pointer.dancerDrag = null;
break;
}
};
// ---- resize screen ----
const resize = () => {
canvas.elem.width = canvas.width;
canvas.elem.height = canvas.height;
canvas.resize = false;
ground = canvas.height > 500 ? 0.85 : 1.0;
for (let i = 0; i < dancers.length; i++) {
dancers[i].x = (i + 2) * canvas.width / 9;
}
}
// ---- main loop ----
const run = (time) => {
requestAnimationFrame(run);
if (canvas.resize === true) resize();
// ---- adjust speed to screen freq ----
if (lastTime !== 0) {
const t = (time - lastTime) / 16;
ts += (t - ts) * 0.1;
if (ts > 1) ts = 1;
}
lastTime = time;
// ---- clear screen ----
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);
// ---- animate robots ----
for (const dancer of dancers) {
dancer.update();
dancer.draw();
}
};
const initRobots = () => {
// ---- instanciate robots ----
ground = canvas.height > 500 ? 0.85 : 1.0;
for (let i = 0; i < 6; i++) {
dancers.push(
new Robot(
i * 360 / 7,
80,
Math.sqrt(Math.min(canvas.width, canvas.height)) / 6,
(i + 2) * canvas.width / 9,
canvas.height * 0.5 - 100,
struct
)
);
}
};
// ---- main thread vs. worker
if (noWorkers) {
// ---- emulate postMessage interface ----
return {
postMessage(data) {
message({ data: data });
}
};
} else {
// ---- worker messaging ----
onmessage = message;
}
};
///////////////// main thread code ///////////////////
let worker = null;
const createWorker = fn => {
const URL = window.URL || window.webkitURL;
return new Worker(URL.createObjectURL(new Blob(["(" + fn + ")()"])));
};
// ---- init canvas ----
const canvas = document.querySelector("canvas");
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
// ---- instanciate worker ----
if (window.Worker && window.OffscreenCanvas) {
// instanciating background worker from a function
worker = createWorker(theLastExperience);
// cloning OffscreenCanvas
const offscreen = canvas.transferControlToOffscreen();
// sending data to worker
worker.postMessage({ msg: "start", elem: offscreen }, [offscreen]);
} else {
// falling back execution to the main thread
worker = theLastExperience(true);
worker.postMessage({ msg: "start", elem: canvas });
}
// ---- resize event ----
window.addEventListener(
"resize",
() => {
worker.postMessage({
msg: "resize",
width: canvas.offsetWidth,
height: canvas.offsetHeight
});
},
false
);
// ---- pointer events ----
const pointer = {
x: 0,
y: 0,
down(e) {
this.move(e);
worker.postMessage({
msg: "pointerDown",
x: this.x,
y: this.y
});
},
up(e) {
worker.postMessage({
msg: "pointerUp"
});
},
move(e) {
if (e.targetTouches) {
e.preventDefault();
this.x = e.targetTouches[0].clientX;
this.y = e.targetTouches[0].clientY;
} else {
this.x = e.clientX;
this.y = e.clientY;
}
worker.postMessage({
msg: "pointerMove",
x: this.x,
y: this.y
});
}
};
window.addEventListener("mousemove", e => pointer.move(e), false);
canvas.addEventListener("touchmove", e => pointer.move(e), false);
window.addEventListener("mousedown", e => pointer.down(e), false);
window.addEventListener("touchstart", e => pointer.down(e), false);
window.addEventListener("mouseup", e => pointer.up(e), false);
window.addEventListener("touchend", e => pointer.up(e), false);
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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment