A very simple Verlet physics engine...
A Pen by Gerard Ferrandez on CodePen.
| <canvas id="canvas"></canvas> |
| (function () { | |
| "use strict"; | |
| // animation loop | |
| var run = function () { | |
| requestAnimationFrame(run); | |
| // clear screen | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| // integration | |
| nodes.forEach(function (n) { | |
| n.integrate(); | |
| n.checkScreenLimits(); | |
| }) | |
| // dragging | |
| if (drag) { | |
| drag.x += (pointer.x - drag.x) / (drag.mass * 100); | |
| drag.y += (pointer.y - drag.y) / (drag.mass * 100); | |
| ctx.beginPath(); | |
| ctx.moveTo(drag.x, drag.y); | |
| ctx.lineTo(pointer.x, pointer.y); | |
| ctx.strokeStyle = "#0f0"; | |
| ctx.stroke(); | |
| } | |
| // solve constraints | |
| for (var i = 0; i < 5; i++) { | |
| constraints.forEach(function (n) { | |
| n.solve(); | |
| }); | |
| } | |
| // draw constraints | |
| constraints.forEach(function (n) { | |
| n.draw(); | |
| }); | |
| // draw nodes | |
| nodes.forEach(function (n) { | |
| n.draw(); | |
| }) | |
| } | |
| // Node constructor | |
| var Node = function (node) { | |
| this.x = node.x; | |
| this.y = node.y; | |
| this.w = node.w; | |
| this.oldX = node.x; | |
| this.oldY = node.y; | |
| this.mass = node.mass || 1.0; | |
| this.color = node.color; | |
| } | |
| // verlet integration | |
| Node.prototype.integrate = function () { | |
| var x = this.x; | |
| var y = this.y; | |
| this.x += this.x - this.oldX; | |
| this.y += this.y - this.oldY + 0.1; | |
| this.oldX = x; | |
| this.oldY = y; | |
| } | |
| // draw node | |
| Node.prototype.draw = function () { | |
| ctx.beginPath(); | |
| ctx.arc(this.x, this.y, this.w, 0, 2 * Math.PI); | |
| ctx.fillStyle = this.color; | |
| ctx.fill(); | |
| // drag | |
| if (pointer.isDown && !drag) { | |
| if (ctx.isPointInPath(pointer.x, pointer.y)) { | |
| drag = this; | |
| } | |
| } | |
| } | |
| Node.prototype.checkScreenLimits = function () { | |
| // bottom + friction | |
| if (this.y > canvas.height - this.w) { | |
| var d = this.y - canvas.height + this.w; | |
| this.x -= d * (this.x - this.oldX) / 10; | |
| this.y = canvas.height - this.w; | |
| } | |
| // top | |
| if (this.y < this.w) this.y = this.w; | |
| // left | |
| if (this.x > canvas.width - this.w) this.x = canvas.width - this.w; | |
| // right | |
| if (this.x < this.w) this.x = this.w; | |
| } | |
| // constraint constructor | |
| var Constraint = function (n0, n1) { | |
| this.n0 = n0; | |
| this.n1 = n1; | |
| var dx = n0.x - n1.x; | |
| var dy = n0.y - n1.y; | |
| this.dist = Math.sqrt(dx * dx + dy * dy); | |
| } | |
| // solve constraint | |
| Constraint.prototype.solve = function () { | |
| var dx = this.n0.x - this.n1.x; | |
| var dy = this.n0.y - this.n1.y; | |
| var currentDist = Math.sqrt(dx * dx + dy * dy); | |
| var delta = 0.5 * (currentDist - this.dist) / currentDist; | |
| dx *= delta; | |
| dy *= delta; | |
| var m1 = (this.n0.mass + this.n1.mass); | |
| var m2 = this.n0.mass / m1; | |
| m1 = this.n1.mass / m1; | |
| this.n1.x += dx * m2; | |
| this.n1.y += dy * m2; | |
| this.n0.x -= dx * m1; | |
| this.n0.y -= dy * m1; | |
| } | |
| // draw constraint | |
| Constraint.prototype.draw = function () { | |
| ctx.beginPath(); | |
| ctx.moveTo(this.n0.x, this.n0.y); | |
| ctx.lineTo(this.n1.x, this.n1.y); | |
| ctx.strokeStyle = "#f00"; | |
| ctx.stroke(); | |
| } | |
| /////////////////////////////////////////////// | |
| // prepare the canvas | |
| var canvas = ge1doot.canvas("canvas"); | |
| var ctx = canvas.ctx; | |
| var drag = null; | |
| // pointer | |
| var pointer = canvas.pointer; | |
| pointer.up = function () { | |
| drag = null; | |
| } | |
| // def variables | |
| var nodes = [], constraints = []; | |
| // def structure | |
| var init = function (json) { | |
| for (var i = 0; i < json.objects.length; i++) { | |
| var object = json.objects[i]; | |
| // load nodes | |
| for (var n in object.nodes) { | |
| var node = new Node(object.nodes[n]); | |
| object.nodes[n].id = node; | |
| nodes.push(node); | |
| } | |
| // define constraints | |
| for (var i = 0; i < object.constraints.length; i++) { | |
| constraints.push(new Constraint( | |
| object.nodes[object.constraints[i][0]].id, | |
| object.nodes[object.constraints[i][1]].id | |
| )); | |
| } | |
| } | |
| // start | |
| nodes[0].x += 2; | |
| run(); | |
| } | |
| return { | |
| init : init | |
| } | |
| })().init({ | |
| // structure | |
| objects: [{ | |
| nodes: { | |
| n0: {x:100, y:200, w:30, mass:1.0, color:"#fff"}, | |
| n1: {x:300, y:200, w:30, mass:1.0, color:"#fff"}, | |
| n2: {x:200, y:0, w:10, mass:0.1, color:"#f00"}, | |
| n3: {x:200, y:20, w:10, mass:0.1, color:"#fff"}, | |
| n4: {x:200, y:40, w:10, mass:0.1, color:"#fff"}, | |
| n5: {x:200, y:60, w:10, mass:0.1, color:"#fff"}, | |
| n6: {x:200, y:80, w:10, mass:0.1, color:"#fff"}, | |
| n7: {x:200, y:100, w:10, mass:0.1, color:"#fff"}, | |
| n8: {x:200, y:140, w:30, mass:1.0, color:"#f00"} | |
| }, | |
| constraints: [ | |
| ["n0", "n1"], | |
| ["n1", "n2"], | |
| ["n2", "n0"], | |
| ["n2", "n3"], | |
| ["n3", "n4"], | |
| ["n4", "n5"], | |
| ["n5", "n6"], | |
| ["n6", "n7"], | |
| ["n7", "n8"] | |
| ] | |
| }] | |
| }); |
| <script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/222599/canvas-pointer.js"></script> |
A very simple Verlet physics engine...
A Pen by Gerard Ferrandez on CodePen.
| html { | |
| overflow: hidden; | |
| touch-action: none; | |
| content-zooming: none; | |
| } | |
| body { | |
| position: absolute; | |
| margin: 0; | |
| padding: 0; | |
| background: #222; | |
| width: 100%; | |
| height: 100%; | |
| } | |
| #canvas { | |
| position:absolute; | |
| width:100%; | |
| height:100%; | |
| background:#000; | |
| } |