Skip to content

Instantly share code, notes, and snippets.

@V1rtuousCycle
Created August 15, 2015 16:58
Show Gist options
  • Save V1rtuousCycle/43550e133b44f35eb7d2 to your computer and use it in GitHub Desktop.
Save V1rtuousCycle/43550e133b44f35eb7d2 to your computer and use it in GitHub Desktop.
yoga!
<!--
─────█─▄▀█──█▀▄─█─────
────▐▌──────────▐▌────
────█▌▀▄──▄▄──▄▀▐█────
───▐██──▀▀──▀▀──██▌───
──▄████▄──▐▌──▄████▄──
-->
;(function() {
'use strict';
// variables
var DEBUG = false;
var points = null;
var constraints = null;
var skins = null;
var kGravity = 1.5;
var kFriction = 0.98;
var base = 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/222599/';
Math.sign = Math.sign || function(x) {
return x > 0 ? 1 : -1;
};
// main loop
function run() {
requestAnimationFrame(run);
ctx.clearRect(0, 0, canvas.width, canvas.height);
// dragging
pointer.dragging();
// static points
scene.static();
// draw scene
ctx.save();
var s = skins;
while (s) s = s.draw();
ctx.restore();
// verlet integration
var p = points;
while (p) p = p.integrate();
// cursor
pointer.cursor();
// solve constraints
var c = constraints;
while (c) c = c.solve();
// show constraints and points
if (DEBUG) {
c = constraints;
while (c) c = c.draw();
}
}
// point 2D constructor
function Point (x, y, radius, mass, gravity) {
this.x = canvas.width * 0.5 + x;
this.y = y;
this.oldX = this.x;
this.oldY = y;
this.radius = radius || 1;
this.mass = mass || 1.0;
this.friction = kFriction;
this.gravity = gravity || kGravity;
this.next = null;
points = iterator.link (points, this);
}
// set position
Point.prototype.position = function (x, y) {
this.x = x;
this.y = y;
};
// verlet integration
Point.prototype.integrate = function () {
var x = this.x;
var y = this.y;
this.x += (this.x - this.oldX) * this.friction;
this.y += (this.y - this.oldY) * this.friction + this.gravity;
this.oldX = x;
this.oldY = y;
// bottom + friction
if (this.y > canvas.height - this.radius) {
this.x = x;
this.y = canvas.height - this.radius;
}
// cursor style
if (!pointer.draggable) {
var dx = this.x - pointer.x;
var dy = this.y - pointer.y;
if (Math.sqrt(dx * dx + dy * dy) < this.radius * 2) pointer.draggable = true;
}
return this.next;
};
// calculate distance between 2 points
Point.prototype.dist = function (p) {
var dx = this.x - p.x;
var dy = this.y - p.y;
return Math.sqrt(dx * dx + dy * dy);
};
// Angled Constraint constructor
function AngleConstraint (p1, p2, p3, angle, range, force) {
this.p1 = p1;
this.p2 = p2;
this.p3 = p3;
this.len1 = p1.dist(p2);
this.len2 = p2.dist(p3);
this.angle = angle;
this.range = range;
this.force = force || 0.2;
constraints = iterator.link (constraints, this);
}
// solve 2 vectors angled (+ stick) constraint
// http://stackoverflow.com/questions/16336702/ragdoll-joint-angle-constraints
AngleConstraint.prototype.solve = function () {
var a, b, c, e, m, m1, m2, m3, x1, y1, cos, sin;
a = Math.atan2(this.p2.y - this.p1.y, this.p2.x - this.p1.x);
b = Math.atan2(this.p3.y - this.p2.y, this.p3.x - this.p2.x);
c = this.angle - (b - a);
c = c > Math.PI ? (c - 2 * Math.PI) : (c < -Math.PI ? (c + 2 * Math.PI) : c);
e = (Math.abs(c) > this.range) ? (-Math.sign(c) * this.range + c) * this.force : 0;
m = this.p1.mass + this.p2.mass;
m1 = this.p1.mass / m;
m2 = this.p2.mass / m;
cos = Math.cos(a - e);
sin = Math.sin(a - e);
x1 = this.p1.x + (this.p2.x - this.p1.x) * m2;
y1 = this.p1.y + (this.p2.y - this.p1.y) * m2;
this.p1.x = x1 - cos * this.len1 * m2;
this.p1.y = y1 - sin * this.len1 * m2;
this.p2.x = x1 + cos * this.len1 * m1;
this.p2.y = y1 + sin * this.len1 * m1;
a = Math.atan2(this.p2.y - this.p3.y, this.p2.x - this.p3.x) + e;
m = this.p2.mass + this.p3.mass;
m2 = this.p2.mass / m;
m3 = this.p3.mass / m;
cos = Math.cos(a);
sin = Math.sin(a);
x1 = this.p3.x + (this.p2.x - this.p3.x) * m2;
y1 = this.p3.y + (this.p2.y - this.p3.y) * m2;
this.p3.x = x1 - cos * this.len2 * m2;
this.p3.y = y1 - sin * this.len2 * m2;
this.p2.x = x1 + cos * this.len2 * m3;
this.p2.y = y1 + sin * this.len2 * m3;
return this.next;
};
// draw angle constraint (DEBUG mode)
AngleConstraint.prototype.draw = function () {
ctx.beginPath();
ctx.moveTo (this.p1.x, this.p1.y);
ctx.lineTo(this.p2.x, this.p2.y);
ctx.lineTo(this.p3.x, this.p3.y);
ctx.stroke();
ctx.beginPath();
ctx.arc(this.p1.x, this.p1.y, this.p1.radius * 2, 0, Math.PI * 2);
ctx.stroke();
ctx.beginPath();
ctx.arc(this.p2.x, this.p2.y, this.p2.radius * 2, 0, Math.PI * 2);
ctx.stroke();
ctx.beginPath();
ctx.arc(this.p3.x, this.p3.y, this.p3.radius * 2, 0, Math.PI * 2);
ctx.stroke();
return this.next;
};
// simple stick constraint constructor
function Constraint (p1, p2, force, len) {
this.p1 = p1;
this.p2 = p2;
this.len = len || p1.dist(p2);
this.force = force || 2;
constraints = iterator.link (constraints, this);
}
// solve stick constraint
Constraint.prototype.solve = function () {
var d, dx, dy, s1, s2, tm;
dx = this.p1.x - this.p2.x;
dy = this.p1.y - this.p2.y;
d = Math.sqrt(dx * dx + dy * dy);
tm = this.p1.mass + this.p2.mass;
d = (d - (d + (this.len - d) * this.force)) / d * 0.5;
s1 = d * (this.p1.mass / tm);
s2 = d * (this.p2.mass / tm);
this.p1.x = this.p1.x - dx * s2;
this.p1.y = this.p1.y - dy * s2;
this.p2.x = this.p2.x + dx * s1;
this.p2.y = this.p2.y + dy * s1;
return this.next;
};
// draw constraint (DEBUG mode)
Constraint.prototype.draw = function () {
ctx.beginPath();
ctx.moveTo (this.p1.x, this.p1.y);
ctx.lineTo(this.p2.x, this.p2.y);
ctx.stroke();
ctx.beginPath();
ctx.arc(this.p1.x, this.p1.y, this.p1.radius * 2, 0, Math.PI * 2);
ctx.stroke();
ctx.beginPath();
ctx.arc(this.p2.x, this.p2.y, this.p2.radius * 2, 0, Math.PI * 2);
ctx.stroke();
return this.next;
};
// skin constructor
function Skin (img, p1, p2, offsetX, offsetY, width, height, angle) {
this.p1 = p1;
this.p2 = p2;
this.img = new Image();
this.img.src = base + img;
this.ox = offsetX;
this.oy = offsetY;
this.w = width;
this.h = height;
this.angle = angle || 0;
this.ctx = ctx;
skins = iterator.link(skins, this);
}
// draw skin
Skin.prototype.draw = function () {
var a = Math.atan2((this.p2.y - this.p1.y), (this.p2.x - this.p1.x));
var cos = Math.cos(a + this.angle);
var sin = Math.sin(a + this.angle);
this.ctx.setTransform(cos, sin, -sin, cos, this.p1.x, this.p1.y);
this.ctx.drawImage(this.img, -this.ox, -this.oy, this.w, this.h);
return this.next;
};
// Stroke constructor
function Stroke (color, p1, p2) {
this.p1 = p1;
this.p2 = p2;
skins = iterator.link(skins, this);
}
// draw stroke
Stroke.prototype.draw = function () {
ctx.beginPath();
ctx.strokeStyle = this.color;
ctx.moveTo(this.p1.x, this.p1.y);
ctx.lineTo(this.p2.x, this.p2.y);
ctx.stroke();
return this.next;
};
// set canvas
var canvas = {
width: 0,
height: 0,
elem: document.createElement('canvas'),
resize: function () {
this.width = this.elem.width = this.elem.offsetWidth;
this.height = this.elem.height = this.elem.offsetHeight;
},
init: function () {
var ctx = canvas.elem.getContext('2d');
document.body.appendChild(canvas.elem);
window.addEventListener('resize', canvas.resize.bind(canvas), false);
return ctx;
}
};
var ctx = canvas.init();
// iterator
var iterator = {
last: null,
link: function (list, object) {
if (!list) list = object;
if (this.last) this.last.next = object;
this.last = object;
return list;
}
};
// set pointer / touch
var pointer = {
x: 0,
y: 0,
drag: null,
draggable: false,
cursor: function () {
canvas.elem.className = this.drag ? 'dragging' : (this.draggable ? 'draggable' : 'default');
},
dragging: function () {
this.draggable = false;
if (pointer.drag) {
this.drag.x += (this.x - this.drag.x) / 20;
this.drag.y += (this.y - this.drag.y) / 20;
}
},
pointer: function (e) {
var pointer;
if (e.targetTouches) {
e.preventDefault();
pointer = e.targetTouches[0]
} else pointer = e;
return pointer;
},
addEvents: function () {
[
[window, 'mousemove,touchmove', function (e) {
var pointer = this.pointer(e);
this.x = pointer.clientX;
this.y = pointer.clientY;
}],
[canvas.elem, 'mousedown,touchstart', function (e) {
if (!this.drag) {
var pointer = this.pointer(e);
this.x = pointer.clientX;
this.y = pointer.clientY;
var dm = 9999, p = points;
while (p) {
var dx = p.x - this.x;
var dy = p.y - this.y;
var d = Math.sqrt(dx * dx + dy * dy);
if (d < p.radius * 2) {
if (d < dm) {
dm = d;
this.drag = p;
}
}
p = p.next;
}
}
}],
[window, 'mouseup,touchend,touchcancel', function () {
this.drag = null;
}]
].forEach(function (e) {
for (var i = 0, events = e[1].split(','); i < events.length; i++) {
e[0].addEventListener(events[i], e[2].bind(this), false );
}
}.bind(this));
}
};
// add events
pointer.addEvents();
// resize
canvas.resize();
// scene definition
var scene = {
points: {
p0: [-70, -80, 20, 1],
p1: [0 , -200, 40, 1],
p2: [ 0, 100, 20, 1],
p3: [-46, 100, 20, 1],
p4: [ 75, -75, 20, 1],
p5: [ 50, 104, 20, 1],
p6: [ 50, 244, 20, 0.5],
p7: [ -46, 244, 20, 0.5],
p8: [ 50, 450, 15, 0.35],
p9: [-46, 450, 15, 0.35],
p10: [ -181, -80, 20, 0.5],
p11: [ 187, -75, 20, 0.5],
p12: [ -340, -80, 12, 0.35, -0.02],
p13: [ 345, -75, 12, 0.35, -0.02],
p14: [ 0, -80],
p15: [ 0, -40],
cloudLeft1: [ 0, 0, 0, 0],
cloudLeft2: [ 0, 0, 0, 0],
cloudRight1: [ 0, 0, 0, 0],
cloudRight2: [ 0, 0, 0, 0],
mong10: [0, -50 - canvas.height / 6],
mong11: [0, 20, 30],
mong12: [0, 160, 30],
mong20: [0, -50 - canvas.height / 6],
mong21: [0, 20, 30],
mong22: [0, 160, 30],
mong30: [0, -150, 0, 1, 0],
mong31: [0, canvas.height / 2 - 150, 0, 1, -0.25],
mong32: [0, canvas.height / 2, 80, 1, -0.25]
},
static: function () {
this.points.cloudLeft1.position(0, 0);
this.points.cloudLeft2.position(100, 0);
this.points.cloudRight1.position(canvas.width - 183, 0);
this.points.cloudRight2.position(canvas.width, 0);
this.points.mong10.position(300, -100);
this.points.mong20.position(canvas.width - 300, -100);
this.points.mong30.position(300, canvas.height + 100);
},
constraints: [
['p0','p1'],
['p1','p2'],
['p2','p3'],
['p0','p2'],
['p1','p3'],
['p1','p4'],
['p5','p2'],
['p1','p5'],
['p2','p4'],
['p0','p4'],
['p3','p5'],
['p3','p4'],
['p0','p10'],
['p4','p11'],
['p10','p12'],
['p11','p13'],
['p14','p3'],
['p14','p0'],
['p14','p4'],
['p14','p5'],
['p14','p15'],
['p14','p1'],
['p14','p2'],
['mong10','mong11'],
['mong11','mong12'],
['mong20','mong21'],
['mong21','mong22'],
['mong30','mong31'],
['mong31','mong32'],
['mong12','p14', 0, canvas.width / 6],
['mong22','p14', 0, canvas.width / 6]
],
angleConstraints: [
['p0', 'p3', 'p7', 1.2, Math.PI / 1.8, 0.2],
['p4', 'p5', 'p6', -1.2, Math.PI / 1.8, 0.2],
['p3', 'p7', 'p9', -1.2, Math.PI / 1.8, 0.2],
['p5', 'p6', 'p8', 1.2, Math.PI / 1.8, 0.2]
],
strokes: [
['#000', 'mong30', 'mong31'],
['#000', 'mong10', 'mong11'],
['#000', 'mong20', 'mong21'],
['#000', 'mong12', 'p14'],
['#000', 'mong22', 'p14']
],
// pics credits Lindsey Carr - https://www.pinterest.com/catheyhaynie/lindsey-carr/
images: [
['gd14.png', 'cloudLeft1', 'cloudLeft2', 0, 0, 195, 188],
['gd15.png', 'cloudRight1', 'cloudRight2', 0, 0, 183, 188],
['gd16.png', 'mong31', 'mong32', 305, 305 / 2, 325, 305, Math.PI],
['gd20.png', 'mong11', 'mong12', 0, 102 / 2, 150, 102],
['gd19.png', 'mong21', 'mong22', 0, 102 / 2, 144, 102],
['gd6.png', 'p6', 'p8', 20, 50, 246, 84, 0.16],
['gd12.png', 'p5', 'p6', 24, 35, 188, 71],
['gd13.png', 'p7', 'p9', 20, 35, 246, 84, -0.16],
['gd5.png', 'p3', 'p7', 24, 35, 189, 69],
['gd1.png', 'p2', 'p1', 30, 181 / 2, 375, 181],
['gd18.png', 'p14', 'p15', 5, 140 / 6, 157 / 2, 140 / 3],
['gd2.png', 'p0', 'p10', 22, 26, 148, 49],
['gd2.png', 'p4', 'p11', 22, 26, 148, 49],
['gd3.png', 'p11', 'p13', 17, 22, 199, 43],
['gd11.png', 'p10', 'p12', 17, 22, 199, 43]
],
build: function () {
var p, i, c, o, s;
iterator.last = null;
for (p in scene.points) {
o = scene.points[p];
scene.points[p] = new Point (o[0], o[1], o[2], o[3], o[4]);
}
// build stick constraints
iterator.last = null;
for (i = 0; i < scene.constraints.length; i++) {
c = scene.constraints[i];
new Constraint (scene.points[c[0]], scene.points[c[1]], c[2], c[3]);
}
// build angle constraints
for (i = 0; i < scene.angleConstraints.length; i++) {
c = scene.angleConstraints[i];
new AngleConstraint (scene.points[c[0]], scene.points[c[1]], scene.points[c[2]], c[3], c[4], c[5]);
}
// build strokes
iterator.last = null;
for (i = 0; i < scene.strokes.length; i++) {
s = scene.strokes[i];
new Stroke (s[0], scene.points[s[1]], scene.points[s[2]]);
}
// build images
for (i = 0; i < scene.images.length; i++) {
s = scene.images[i];
new Skin (s[0], scene.points[s[1]], scene.points[s[2]], s[3], s[4], s[5], s[6], s[7]);
}
}
};
// build scene
scene.build();
// start animation
run();
}());
html {
overflow: hidden;
touch-action: none;
content-zooming: none;
}
body {
position: absolute;
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background: #000;
}
canvas {
position:absolute;
width:100%;
height:100%;
background: #c9c4b5;
}
.draggable {
cursor: pointer;
cursor: -webkit-grab;
}
.dragging {
cursor: move;
cursor: -webkit-grabbing;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment