Because it is fun, and why not?
Forked from Gerard Ferrandez's Pen yoga!.
A Pen by Anton Shtylman on CodePen.
| <!-- | |
| ─────█─▄▀█──█▀▄─█───── | |
| ────▐▌──────────▐▌──── | |
| ────█▌▀▄──▄▄──▄▀▐█──── | |
| ───▐██──▀▀──▀▀──██▌─── | |
| ──▄████▄──▐▌──▄████▄── | |
| --> |
| ;(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; | |
| } |
Because it is fun, and why not?
Forked from Gerard Ferrandez's Pen yoga!.
A Pen by Anton Shtylman on CodePen.