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