diff --git a/demo/index.html b/demo/index.html
index dd2eb04..95f4b7b 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -53,6 +53,7 @@
+
diff --git a/demo/js/Demo.js b/demo/js/Demo.js
index a0dea63..4ff0a57 100644
--- a/demo/js/Demo.js
+++ b/demo/js/Demo.js
@@ -172,6 +172,12 @@
init: Example.newtonsCradle,
sourceLink: sourceLinkRoot + '/newtonsCradle.js'
},
+ {
+ name: 'Ragdoll',
+ id: 'ragdoll',
+ init: Example.ragdoll,
+ sourceLink: sourceLinkRoot + '/ragdoll.js'
+ },
{
name: 'Pyramid',
id: 'pyramid',
diff --git a/examples/ragdoll.js b/examples/ragdoll.js
new file mode 100644
index 0000000..8b66c34
--- /dev/null
+++ b/examples/ragdoll.js
@@ -0,0 +1,498 @@
+var Example = Example || {};
+
+Example.ragdoll = function() {
+ var Engine = Matter.Engine,
+ Events = Matter.Events,
+ Render = Matter.Render,
+ Runner = Matter.Runner,
+ Body = Matter.Body,
+ Common = Matter.Common,
+ Composite = Matter.Composite,
+ Composites = Matter.Composites,
+ Constraint = Matter.Constraint,
+ MouseConstraint = Matter.MouseConstraint,
+ Mouse = Matter.Mouse,
+ World = Matter.World,
+ Bodies = Matter.Bodies,
+ Vector = Matter.Vector;
+
+ // create engine
+ var engine = Engine.create(),
+ world = engine.world;
+
+ // create renderer
+ var render = Render.create({
+ element: document.body,
+ engine: engine,
+ options: {
+ width: Math.min(document.documentElement.clientWidth, 800),
+ height: Math.min(document.documentElement.clientHeight, 600),
+ showAngleIndicator: true,
+ background: '#0f0f13'
+ }
+ });
+
+ Render.run(render);
+
+ // create runner
+ var runner = Runner.create();
+ Runner.run(runner, engine);
+
+ // create stairs
+ var stairCount = (render.bounds.max.y - render.bounds.min.y) / 50;
+
+ var stack = Composites.stack(0, 0, stairCount + 2, 1, 0, 0, function(x, y, column) {
+ return Bodies.rectangle(x - 50, y + column * 50, 100, 1000, {
+ isStatic: true,
+ render: {
+ fillStyle: '#222'
+ }
+ });
+ });
+
+ // create obstacles
+ var obstacles = Composites.stack(300, 0, 15, 3, 10, 10, function(x, y, column) {
+ var sides = Math.round(Common.random(1, 8)),
+ options = {
+ render: {
+ fillStyle: Common.choose(['#006BA6', '#0496FF', '#D81159', '#8F2D56'])
+ }
+ };
+
+ switch (Math.round(Common.random(0, 1))) {
+ case 0:
+ if (Common.random() < 0.8) {
+ return Bodies.rectangle(x, y, Common.random(25, 50), Common.random(25, 50), options);
+ } else {
+ return Bodies.rectangle(x, y, Common.random(80, 120), Common.random(25, 30), options);
+ }
+ case 1:
+ return Bodies.polygon(x, y, sides, Common.random(25, 50), options);
+ }
+ });
+
+ var ragdolls = Composite.create();
+
+ for (var i = 0; i < 1; i += 1) {
+ var ragdoll = Example.ragdoll.ragdoll(200, -1000 * i, 1.3);
+
+ Composite.add(ragdolls, ragdoll);
+ }
+
+ World.add(world, [stack, obstacles, ragdolls]);
+
+ var timeScaleTarget = 1,
+ counter = 0;
+
+ Events.on(engine, 'afterUpdate', function(event) {
+ // tween the timescale for slow-mo
+ if (mouse.button === -1) {
+ engine.timing.timeScale += (timeScaleTarget - engine.timing.timeScale) * 0.05;
+ } else {
+ engine.timing.timeScale = 1;
+ }
+
+ counter += 1;
+
+ // every 1.5 sec
+ if (counter >= 60 * 1.5) {
+
+ // flip the timescale
+ if (timeScaleTarget < 1) {
+ timeScaleTarget = 1;
+ } else {
+ timeScaleTarget = 0.05;
+ }
+
+ // reset counter
+ counter = 0;
+ }
+
+ for (var i = 0; i < stack.bodies.length; i += 1) {
+ var body = stack.bodies[i];
+
+ // animate stairs
+ Body.translate(body, {
+ x: -0.5 * engine.timing.timeScale,
+ y: -0.5 * engine.timing.timeScale
+ });
+
+ // loop stairs when they go off screen
+ if (body.position.x < -50) {
+ Body.setPosition(body, {
+ x: 50 * (stack.bodies.length - 1),
+ y: 25 + render.bounds.max.y + (body.bounds.max.y - body.bounds.min.y) * 0.5
+ });
+
+ Body.setVelocity(body, {
+ x: 0,
+ y: 0
+ });
+ }
+ }
+
+ for (i = 0; i < ragdolls.composites.length; i += 1) {
+ var ragdoll = ragdolls.composites[i],
+ bounds = Composite.bounds(ragdoll);
+
+ // move ragdolls back to the top of the screen
+ if (bounds.min.y > render.bounds.max.y + 100) {
+ Composite.translate(ragdoll, {
+ x: -bounds.min.x * 0.9,
+ y: -render.bounds.max.y - 400
+ });
+ }
+ }
+
+ for (i = 0; i < obstacles.bodies.length; i += 1) {
+ var body = obstacles.bodies[i],
+ bounds = body.bounds;
+
+ // move obstacles back to the top of the screen
+ if (bounds.min.y > render.bounds.max.y + 100) {
+ Body.translate(body, {
+ x: -bounds.min.x,
+ y: -render.bounds.max.y - 300
+ });
+ }
+ }
+ });
+
+ // add mouse control and make the mouse revolute
+ var mouse = Mouse.create(render.canvas),
+ mouseConstraint = MouseConstraint.create(engine, {
+ mouse: mouse,
+ constraint: {
+ stiffness: 0.6,
+ length: 0,
+ angularStiffness: 0,
+ render: {
+ visible: false
+ }
+ }
+ });
+
+ World.add(world, mouseConstraint);
+
+ // keep the mouse in sync with rendering
+ render.mouse = mouse;
+
+ // fit the render viewport to the scene
+ Render.lookAt(render, {
+ min: { x: 0, y: 0 },
+ max: { x: 800, y: 600 }
+ });
+
+ // context for MatterTools.Demo
+ return {
+ engine: engine,
+ runner: runner,
+ render: render,
+ canvas: render.canvas,
+ stop: function() {
+ Matter.Render.stop(render);
+ Matter.Runner.stop(runner);
+ }
+ };
+};
+
+Example.ragdoll.ragdoll = function(x, y, scale, options) {
+ scale = typeof scale === 'undefined' ? 1 : scale;
+
+ var Body = Matter.Body,
+ Bodies = Matter.Bodies,
+ Constraint = Matter.Constraint,
+ Composite = Matter.Composite,
+ Common = Matter.Common;
+
+ var headOptions = Common.extend({
+ label: 'head',
+ collisionFilter: {
+ group: Body.nextGroup(true)
+ },
+ chamfer: {
+ radius: [15 * scale, 15 * scale, 15 * scale, 15 * scale]
+ },
+ render: {
+ fillStyle: '#FFBC42'
+ }
+ }, options);
+
+ var chestOptions = Common.extend({
+ label: 'chest',
+ collisionFilter: {
+ group: Body.nextGroup(true)
+ },
+ chamfer: {
+ radius: [20 * scale, 20 * scale, 26 * scale, 26 * scale]
+ },
+ render: {
+ fillStyle: '#E0A423'
+ }
+ }, options);
+
+ var leftArmOptions = Common.extend({
+ label: 'left-arm',
+ collisionFilter: {
+ group: Body.nextGroup(true)
+ },
+ chamfer: {
+ radius: 10 * scale
+ },
+ render: {
+ fillStyle: '#FFBC42'
+ }
+ }, options);
+
+ var leftLowerArmOptions = Common.extend({}, leftArmOptions, {
+ render: {
+ fillStyle: '#E59B12'
+ }
+ });
+
+ var rightArmOptions = Common.extend({
+ label: 'right-arm',
+ collisionFilter: {
+ group: Body.nextGroup(true)
+ },
+ chamfer: {
+ radius: 10 * scale
+ },
+ render: {
+ fillStyle: '#FFBC42'
+ }
+ }, options);
+
+ var rightLowerArmOptions = Common.extend({}, rightArmOptions, {
+ render: {
+ fillStyle: '#E59B12'
+ }
+ });
+
+ var leftLegOptions = Common.extend({
+ label: 'left-leg',
+ collisionFilter: {
+ group: Body.nextGroup(true)
+ },
+ chamfer: {
+ radius: 10 * scale
+ },
+ render: {
+ fillStyle: '#FFBC42'
+ }
+ }, options);
+
+ var leftLowerLegOptions = Common.extend({}, leftLegOptions, {
+ render: {
+ fillStyle: '#E59B12'
+ }
+ });
+
+ var rightLegOptions = Common.extend({
+ label: 'right-leg',
+ collisionFilter: {
+ group: Body.nextGroup(true)
+ },
+ chamfer: {
+ radius: 10 * scale
+ },
+ render: {
+ fillStyle: '#FFBC42'
+ }
+ }, options);
+
+ var rightLowerLegOptions = Common.extend({}, rightLegOptions, {
+ render: {
+ fillStyle: '#E59B12'
+ }
+ });
+
+ var head = Bodies.rectangle(x, y - 60 * scale, 34 * scale, 40 * scale, headOptions);
+ var chest = Bodies.rectangle(x, y, 55 * scale, 80 * scale, chestOptions);
+ var rightUpperArm = Bodies.rectangle(x + 39 * scale, y - 15 * scale, 20 * scale, 40 * scale, rightArmOptions);
+ var rightLowerArm = Bodies.rectangle(x + 39 * scale, y + 25 * scale, 20 * scale, 60 * scale, rightLowerArmOptions);
+ var leftUpperArm = Bodies.rectangle(x - 39 * scale, y - 15 * scale, 20 * scale, 40 * scale, leftArmOptions);
+ var leftLowerArm = Bodies.rectangle(x - 39 * scale, y + 25 * scale, 20 * scale, 60 * scale, leftLowerArmOptions);
+ var leftUpperLeg = Bodies.rectangle(x - 20 * scale, y + 57 * scale, 20 * scale, 40 * scale, leftLegOptions);
+ var leftLowerLeg = Bodies.rectangle(x - 20 * scale, y + 97 * scale, 20 * scale, 60 * scale, leftLowerLegOptions);
+ var rightUpperLeg = Bodies.rectangle(x + 20 * scale, y + 57 * scale, 20 * scale, 40 * scale, rightLegOptions);
+ var rightLowerLeg = Bodies.rectangle(x + 20 * scale, y + 97 * scale, 20 * scale, 60 * scale, rightLowerLegOptions);
+
+ var chestToRightUpperArm = Constraint.create({
+ bodyA: chest,
+ pointA: {
+ x: 24 * scale,
+ y: -23 * scale
+ },
+ pointB: {
+ x: 0,
+ y: -8 * scale
+ },
+ bodyB: rightUpperArm,
+ stiffness: 0.6,
+ render: {
+ visible: false
+ }
+ });
+
+ var chestToLeftUpperArm = Constraint.create({
+ bodyA: chest,
+ pointA: {
+ x: -24 * scale,
+ y: -23 * scale
+ },
+ pointB: {
+ x: 0,
+ y: -8 * scale
+ },
+ bodyB: leftUpperArm,
+ stiffness: 0.6,
+ render: {
+ visible: false
+ }
+ });
+
+ var chestToLeftUpperLeg = Constraint.create({
+ bodyA: chest,
+ pointA: {
+ x: -10 * scale,
+ y: 30 * scale
+ },
+ pointB: {
+ x: 0,
+ y: -10 * scale
+ },
+ bodyB: leftUpperLeg,
+ stiffness: 0.6,
+ render: {
+ visible: false
+ }
+ });
+
+ var chestToRightUpperLeg = Constraint.create({
+ bodyA: chest,
+ pointA: {
+ x: 10 * scale,
+ y: 30 * scale
+ },
+ pointB: {
+ x: 0,
+ y: -10 * scale
+ },
+ bodyB: rightUpperLeg,
+ stiffness: 0.6,
+ render: {
+ visible: false
+ }
+ });
+
+ var upperToLowerRightArm = Constraint.create({
+ bodyA: rightUpperArm,
+ bodyB: rightLowerArm,
+ pointA: {
+ x: 0,
+ y: 15 * scale
+ },
+ pointB: {
+ x: 0,
+ y: -25 * scale
+ },
+ stiffness: 0.6,
+ render: {
+ visible: false
+ }
+ });
+
+ var upperToLowerLeftArm = Constraint.create({
+ bodyA: leftUpperArm,
+ bodyB: leftLowerArm,
+ pointA: {
+ x: 0,
+ y: 15 * scale
+ },
+ pointB: {
+ x: 0,
+ y: -25 * scale
+ },
+ stiffness: 0.6,
+ render: {
+ visible: false
+ }
+ });
+
+ var upperToLowerLeftLeg = Constraint.create({
+ bodyA: leftUpperLeg,
+ bodyB: leftLowerLeg,
+ pointA: {
+ x: 0,
+ y: 20 * scale
+ },
+ pointB: {
+ x: 0,
+ y: -20 * scale
+ },
+ stiffness: 0.6,
+ render: {
+ visible: false
+ }
+ });
+
+ var upperToLowerRightLeg = Constraint.create({
+ bodyA: rightUpperLeg,
+ bodyB: rightLowerLeg,
+ pointA: {
+ x: 0,
+ y: 20 * scale
+ },
+ pointB: {
+ x: 0,
+ y: -20 * scale
+ },
+ stiffness: 0.6,
+ render: {
+ visible: false
+ }
+ });
+
+ var headContraint = Constraint.create({
+ bodyA: head,
+ pointA: {
+ x: 0,
+ y: 25 * scale
+ },
+ pointB: {
+ x: 0,
+ y: -35 * scale
+ },
+ bodyB: chest,
+ stiffness: 0.6,
+ render: {
+ visible: false
+ }
+ });
+
+ var legToLeg = Constraint.create({
+ bodyA: leftLowerLeg,
+ bodyB: rightLowerLeg,
+ stiffness: 0.01,
+ render: {
+ visible: false
+ }
+ });
+
+ var person = Composite.create({
+ bodies: [
+ chest, head, leftLowerArm, leftUpperArm,
+ rightLowerArm, rightUpperArm, leftLowerLeg,
+ rightLowerLeg, leftUpperLeg, rightUpperLeg
+ ],
+ constraints: [
+ upperToLowerLeftArm, upperToLowerRightArm, chestToLeftUpperArm,
+ chestToRightUpperArm, headContraint, upperToLowerLeftLeg,
+ upperToLowerRightLeg, chestToLeftUpperLeg, chestToRightUpperLeg,
+ legToLeg
+ ]
+ });
+
+ return person;
+};
\ No newline at end of file