ID photo of Ciro Santilli taken in 2013 right eyeCiro Santilli OurBigBook logoOurBigBook.com  Sponsor 中国独裁统治 China Dictatorship 新疆改造中心、六四事件、法轮功、郝海东、709大抓捕、2015巴拿马文件 邓家贵、低端人口、西藏骚乱
A multi-scenario demo.
js/matterjs/examples.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<script src="node_modules/matter-js/build/matter.js"></script>
<style>
body {
  background-color: white;
}
</style>
</head>
<body>
<h1 id="title"></h1>
<div id="demo"></div>
<div id="demo-doc"></div>
<div id="doc">
<p>Global controls: R: restart scenario | N: next scenario | P: previous scenario</p>
<p><a href="http://cirosantilli.com/_file/js/matterjs/example.html">More information.</a></p>
</div>
<div id="demo-debug"></div>
<script>
window.Matter || document.write('<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js" integrity="sha512-0z8URjGET6GWnS1xcgiLBZBzoaS8BNlKayfZyQNKz4IRp+s7CKXx0yz7Eco2+TcwoeMBa5KMwmTX7Kus7Fa5Uw==" crossorigin="anonymous" referrerpolicy="no-referrer"><\/script>')
</script>
<script>
// Globals
const demoElement = document.getElementById('demo')
const demoDocElement = document.getElementById('demo-doc')
const demoDebugElement = document.getElementById('demo-debug')
const titleElement = document.getElementById('title')
let scenarioIdx = 0
let engine
let render
const idToScenario = {}
const keysDown = new Set()
document.addEventListener('keydown', e => {
  if (e.repeat) { return }
  if (e.code === "ArrowUp") {
  } else if (e.code === "ArrowDown") {
  } else if (e.code === "KeyN") {
    scenarioIdx = (scenarioIdx + 1) % scenarios.length
    ;[engine, render] = reset(demoElement, render, engine, scenarioIdx)
  } else if (e.code === "KeyR") {
    ;[engine, render] = reset(demoElement, render, engine, scenarioIdx)
  } else if (e.code === "KeyP") {
    scenarioIdx--
    if (scenarioIdx === -1) {
      scenarioIdx = scenarios.length - 1
    }
    ;[engine, render] = reset(demoElement, render, engine, scenarioIdx)
  } else {
    keysDown.add(e.code);
  }
})
document.addEventListener('keyup', e => {
  keysDown.delete(e.code)
})
const globalKeyHandlers = {
  KeyN: () => {
    scenarioIdx = (scenarioIdx + 1) % scenarios.length
    ;[engine, render] = reset(demoElement, render, engine, scenarioIdx)
  },
  KeyR: () => {
    ;[engine, render] = reset(demoElement, render, engine, scenarioIdx)
  },
  KeyP: () => {
    scenarioIdx--
    if (scenarioIdx === -1) {
      scenarioIdx = scenarios.length - 1
    }
    ;[engine, render] = reset(demoElement, render, engine, scenarioIdx)
  },
}


// Matter.js global singletons
const Engine = Matter.Engine
const Render = Matter.Render
const Runner = Matter.Runner
const Bodies = Matter.Bodies
const Composite = Matter.Composite
const World = Matter.World
const V = Matter.Vector

// Scenarios.
const scenarios = [
  {
    id: 'falling-boxes-1',
    scene: () => {
      return {
        setup: (engine, render) => {
          const boxA = Bodies.rectangle(400, 200, 80, 80)
          const boxB = Bodies.rectangle(450, 50, 80, 80)
          const ground = Bodies.rectangle(400, 610, 810, 60, { isStatic: true })
          Composite.add(engine.world, [boxA, boxB, ground])
        },
      }
    },
  },
  {
    id: 'falling-boxes-2',
    scene: () => {
      return {
        setup: (engine, render) => {
          const boxA = Bodies.rectangle(400, 50, 80, 80)
          const boxB = Bodies.rectangle(450, 200, 80, 80)
          const boxC = Bodies.rectangle(650, 200, 80, 80)
          const ground = Bodies.rectangle(400, 610, 810, 60, { isStatic: true })
          Composite.add(engine.world, [boxA, boxB, boxC, ground])
        }
      }
    },
  },
  {
    id: 'sprites',
    scene: () => {
      return {
        // https://stackoverflow.com/questions/22686917/matter-js-change-colors
        renderOpts: { wireframes: false },
        setup: (engine, render) => {
          // By default it loops over some default colors.
          const boxA = Bodies.rectangle(100, 200, 80, 80)
          const boxB = Bodies.rectangle(200, 200, 80, 80)

          const fixedColor = Bodies.rectangle(300, 200, 80, 80, {
            render: {
              fillStyle: 'green',
              strokeStyle: 'yellow',
              lineWidth: 10,
            },
          })

          const ciro = Bodies.rectangle(
            400, 200, 80, 80,
            {
              render: {
                sprite: {
                  texture: 'https://raw.githubusercontent.com/cirosantilli/media/master/ID_photo_of_Ciro_Santilli_taken_in_2013_square_398.jpg',
                  // TODO set width/height exactly in pixels to make it more easily match the box?
                  // https://github.com/liabru/matter-js/issues/120
                  // https://stackoverflow.com/questions/30678438/matter-js-sprite-size
                  xScale: 80/400,
                  yScale: 80/400,
                }
              }
            }
          )
          const ground = Bodies.rectangle(400, 610, 810, 60, {
            isStatic: true,
            render: {
              fillStyle: 'brown',
            },
          })
          Composite.add(engine.world, [boxA, boxB, fixedColor, ciro, ground])
        },
      }
    },
  },
  {
    id: 'top-down-asdw-fixed-viewport',
    scene: () => {
      const w = 600
      const h = 600

      // Ciro
      const ciroMoveForce = 0.01
      const ciroScale = 0.1
      const ciroW = w * ciroScale
      const ciroH = h * ciroScale
      const ciro = Bodies.rectangle(
        w/2, h/2, ciroW, ciroH,
        {
          density: 0.001,
          friction: 0,
          frictionAir: 0.04,
          render: {
            sprite: {
              texture: 'https://raw.githubusercontent.com/cirosantilli/media/master/ID_photo_of_Ciro_Santilli_taken_in_2013_square_398.jpg',
              xScale: ciroW/400,
              yScale: ciroH/400,
            }
          }
        }
      )

      // Stone
      const stoneR = w/40
      const stoneMap = new Map()
      function addStone(engine, retries=10) {
        for (let i = 0; i < retries; i++) {
          const otherBodies = Composite.allBodies(engine.world)
          const newStone = Bodies.circle(
            Math.random() * w, Math.random() * h, stoneR,
            {
              render: { fillStyle: 'gray' },
            }
          )
          Composite.add(engine.world, [newStone])
          if (Matter.Query.collides(newStone, otherBodies).length) {
            Composite.remove(engine.world, newStone)
          } else {
            stoneMap.set(newStone, undefined)
            return newStone
          }
        }
      }

      // Bin
      const buttonR = w/20
      function makeButton(x, y) {
        return Bodies.circle(
          x, y, buttonR,
          {
            isStatic: true,
            render: { fillStyle: 'purple' },
          }
        )
      }
      const buttons = [
        makeButton(w/4, h/4),
        makeButton(3*w/4, 3*h/4),
      ]
      const buttonMap = new Map()
      for (const button of buttons) {
        buttonMap.set(button, { count: 0, })
      }

      // Wall
      const wallExtra = 0.1
      const wallOpts = {
        isStatic: true,
        render: { fillStyle: 'brown', },
      }

      demoDocElement.innerHTML = '<p>Controls: W: up | S: down | A: left | D: right | Mouse drag: move objects</p>'

      return {
        afterRender: () => {
          const context = render.context
          context.font = "bold 24px verdana, sans-serif "
          context.textAlign = "center"
          context.textBaseline = "bottom"
          context.fillStyle = "#ffffff"
          context.fillText('Ciro', ciro.position.x, ciro.position.y - ciroH/2);
          for (const button of buttons) {
            context.fillText(`Bin ${buttonMap.get(button).count}`, button.position.x, button.position.y - buttonR)
          }
          for (const [stone, _] of stoneMap) {
            context.fillText('Stone', stone.position.x, stone.position.y - stoneR)
          }
        },
        beforeUpdate: () => {
          demoDebugElement.innerHTML = `<p>` +
            `x: ${ciro.position.x.toFixed(2)} | y: ${ciro.position.y.toFixed(2)}` +
            ` | vx: ${ciro.velocity.x.toFixed(2)} | vy: ${ciro.velocity.y.toFixed(2)}` +
            `</p>`
        },
        // https://stackoverflow.com/questions/29466684/disabling-gravity-in-matter-js
        engineOpts: { gravity: { y: 0 } },
        keyHandlers: {
          KeyW: () => {
            Matter.Body.applyForce(ciro, {
              x: ciro.position.x,
              y: ciro.position.y
            }, { x: 0, y: -ciroMoveForce } )
          },
          KeyS: () => {
            Matter.Body.applyForce(ciro, {
              x: ciro.position.x,
              y: ciro.position.y
            }, { x: 0, y: ciroMoveForce } )
          },
          KeyA: () => {
            Matter.Body.applyForce(ciro, {
              x: ciro.position.x,
              y: ciro.position.y
            }, { x: -ciroMoveForce, y: 0 } )
          },
          KeyD: () => {
            Matter.Body.applyForce(ciro, {
              x: ciro.position.x,
              y: ciro.position.y
            }, { x: ciroMoveForce, y: 0 } )
          },
        },
        renderOpts: {
          height: w,
          width: h,
          wireframes: false,
        },
        setup: (engine, render) => {
          // Two things touch each other, e.g. something and a button.
          Matter.Events.on(engine, 'collisionStart', function(event) {
            const pairs = event.pairs
            for (let i = 0, j = pairs.length; i != j; ++i) {
              const pair = pairs[i]

              // Button collision.
              let button, stone
              if (buttonMap.has(pair.bodyA)) { button = pair.bodyA }
              if (buttonMap.has(pair.bodyB)) { button = pair.bodyB }
              if (stoneMap.has(pair.bodyA)) { stone = pair.bodyA }
              if (stoneMap.has(pair.bodyB)) { stone = pair.bodyB }
              if (button && stone) {
                buttonMap.get(button).count++
                Composite.remove(engine.world, stone)
                stoneMap.delete(stone)
                addStone(engine)
              }
            }
          })
          Render.lookAt(render, {
            min: { x: 0, y: 0 },
            max: { x: w, y: h },
          })
          Composite.add(
            engine.world,
            [
              ciro,

              // Walls
              // N
              Bodies.rectangle(w/2, 0, w * (1 + wallExtra), h * wallExtra, wallOpts),
              // S
              Bodies.rectangle(w/2, h, w * (1 + wallExtra), h * wallExtra, wallOpts),
              // W
              Bodies.rectangle(0, h/2, w * wallExtra, h  * (1 + wallExtra), wallOpts),
              // E
              Bodies.rectangle(w, h/2, w * wallExtra, h  * (1 + wallExtra), wallOpts),
            ].concat(buttons)
          )
          for (let i = 0; i < 4; i++) {
            addStone(engine)
          }

          // Add mouse control
          const mouse = Matter.Mouse.create(render.canvas)
          const mouseConstraint = Matter.MouseConstraint.create(engine, {
            mouse,
            constraint: {
              angularStiffness: 0,
              render: {
                visible: true
              }
            }
          })
          Composite.add(engine.world, mouseConstraint)
          render.mouse = mouse;
        },
      }
    },
  },
  {
    id: 'top-down-asdw-fps',
    scene: () => {
      // Viewport width/height.
      const w = 600
      const h = 600
      // World width/height.
      const ww = 6000
      const wh = 6000

      // Ciro
      const ciroMoveForce = 0.01
      const ciroTorque = 0.1
      const ciroScale = 0.1
      const ciroW = w * ciroScale
      const ciroH = h * ciroScale
      const ciro = Bodies.rectangle(
        w/2, h/2, ciroW, ciroH,
        {
          density: 0.001,
          friction: 0,
          frictionAir: 0.04,
          render: {
            sprite: {
              texture: 'https://raw.githubusercontent.com/cirosantilli/media/master/ID_photo_of_Ciro_Santilli_taken_in_2013_square_398.jpg',
              xScale: ciroW/400,
              yScale: ciroH/400,
            }
          }
        }
      )

      // Stone
      const stoneR = w/40
      const stoneMap = new Map()
      function addStone(engine, retries=10) {
        for (let i = 0; i < retries; i++) {
          const otherBodies = Composite.allBodies(engine.world)
          const newStone = Bodies.circle(
            Math.random() * ww, Math.random() * wh, stoneR,
            {
              render: { fillStyle: 'gray' },
            }
          )
          Composite.add(engine.world, [newStone])
          if (Matter.Query.collides(newStone, otherBodies).length) {
            Composite.remove(engine.world, newStone)
          } else {
            stoneMap.set(newStone, undefined)
            return newStone
          }
        }
      }

      // Wall
      const wallExtra = 0.1
      const wallOpts = {
        isStatic: true,
        render: { fillStyle: 'brown', },
      }

      demoDocElement.innerHTML = '<p>Controls: W: forward | S: backwards | A: strafe left | D: strafe right | Q: rotate left | E: rotate right | Mouse drag: move objects</p>'

      Matter.Render.startViewTransform = function(render) {
        var boundsWidth = render.bounds.max.x - render.bounds.min.x,
            boundsHeight = render.bounds.max.y - render.bounds.min.y,
            boundsScaleX = boundsWidth / render.options.width,
            boundsScaleY = boundsHeight / render.options.height;

        // add lines:
        var w2 = render.canvas.width / 2;
        var h2 = render.canvas.height / 2;
        render.context.translate(w2, h2);
        render.context.rotate(-ciro.angle);
        render.context.translate(-w2, -h2);
        // /add lines.

        render.context.scale(1 / boundsScaleX, 1 / boundsScaleY);
        render.context.translate(-render.bounds.min.x, -render.bounds.min.y);
      }

      return {
        beforeRender: () => {
          Render.lookAt(render, {
            min: { x: ciro.position.x - w/2, y: ciro.position.y - h/2 },
            max: { x: ciro.position.x + w/2, y: ciro.position.y + h/2 },
          })
        },
        beforeUpdate: () => {
          demoDebugElement.innerHTML = `<p>` +
            `x: ${ciro.position.x.toFixed(2)} | y: ${ciro.position.y.toFixed(2)} | angle: ${((360 * ciro.angle/Math.PI) % 360).toFixed(2)}°` +
            ` | vx: ${ciro.velocity.x.toFixed(2)} | vy: ${ciro.velocity.y.toFixed(2)}` +
            `</p>`
        },
        // https://stackoverflow.com/questions/29466684/disabling-gravity-in-matter-js
        engineOpts: { gravity: { y: 0 } },
        keyHandlers: {
          KeyW: () => {
            Matter.Body.applyForce(ciro, {
              x: ciro.position.x,
              y: ciro.position.y
            }, V.rotate({ x: 0, y: -ciroMoveForce }, ciro.angle) )
          },
          KeyS: () => {
            Matter.Body.applyForce(ciro, {
              x: ciro.position.x,
              y: ciro.position.y
            }, V.rotate({ x: 0, y: ciroMoveForce }, ciro.angle) )
          },
          KeyA: () => {
            Matter.Body.applyForce(ciro, {
              x: ciro.position.x,
              y: ciro.position.y
            }, V.rotate({ x: -ciroMoveForce, y: 0 }, ciro.angle) )
          },
          KeyD: () => {
            Matter.Body.applyForce(ciro, {
              x: ciro.position.x,
              y: ciro.position.y
            }, V.rotate({ x: ciroMoveForce, y: 0 }, ciro.angle) )
          },
          KeyQ: () => {
            ciro.torque = -ciroTorque
          },
          KeyE: () => {
            ciro.torque = ciroTorque
          },
        },
        renderOpts: {
          height: w,
          width: h,
          wireframes: false,
          showPerformance: true
        },
        setup: (engine, render) => {
          Composite.add(
            engine.world,
            [
              ciro,

              // Walls
              // N
              Bodies.rectangle(ww/2, 0, ww * (1 + wallExtra), wh * wallExtra, wallOpts),
              // S
              Bodies.rectangle(ww/2, wh, ww * (1 + wallExtra), wh * wallExtra, wallOpts),
              // W
              Bodies.rectangle( 0, wh/2, ww * wallExtra, wh  * (1 + wallExtra), wallOpts),
              // E
              Bodies.rectangle(ww, wh/2, ww * wallExtra, wh  * (1 + wallExtra), wallOpts),
            ]
          )
          const nStones = Math.floor(ww*wh/(50*stoneR*stoneR))
          console.log(`nStones = ${nStones}`);
          for (let i = 0; i < nStones; i++) {
            addStone(engine)
          }

          // Add mouse control
          const mouse = Matter.Mouse.create(render.canvas)
          const mouseConstraint = Matter.MouseConstraint.create(engine, {
            mouse,
            constraint: {
              angularStiffness: 0,
              render: {
                visible: true
              }
            }
          })
          Composite.add(engine.world, mouseConstraint)
          render.mouse = mouse;
        },
      }
    },
  },
]
for (let i = 0; i < scenarios.length; i++) {
  const scenario = scenarios[i]
  idToScenario[scenario.id] = scenario
  scenario.idx = i
}
if (window.location.hash) {
  const scenario = idToScenario[window.location.hash.substr(1)]
  if (scenario) {
    scenarioIdx = scenario.idx
  }
}

function run(element, scenarioIdx) {
  demoDocElement.innerHTML = ''

  const scenario = scenarios[scenarioIdx]
  const scene = scenario.scene()
  const engine = Engine.create(scene.engineOpts || {})
  const render = Render.create({
    element,
    engine,
    options: scene.renderOpts || {},
  })
  const title = `${scenarioIdx}: ${scenario.id} - Matter.js demo`
  window.location.hash = scenario.id
  titleElement.innerHTML = title
  document.title = title
  scene.setup(engine, render)
  Render.run(render)
  Runner.run(Runner.create(), engine)
  const keyHandlers = Object.assign({}, scene.keyHandlers || {}, globalKeyHandlers)
  const afterRender = scene.afterRender
  if (afterRender) {
    Matter.Events.on(render, 'afterRender', afterRender)
  }
  const beforeRender = scene.beforeRender
  if (beforeRender) {
    Matter.Events.on(render, 'beforeRender', beforeRender)
  }
  Matter.Events.on(engine, 'beforeUpdate', event => {
    scene.beforeUpdate?.()
    ;[...keysDown].forEach(k => {
      keyHandlers[k]?.();
    });
  });
  return [engine, render]
}

function reset(element, render, engine, scenarioIdx) {
  // https://github.com/liabru/matter-js/issues/564
  Render.stop(render)
  World.clear(engine.world)
  Engine.clear(engine)
  render.canvas.remove()
  render.canvas = null
  render.context = null
  render.textures = {}
  Matter.Events.off(engine, 'afterRender')
  Matter.Events.off(engine, 'beforeRender')
  Matter.Events.off(engine, 'beforeUpdate')
  // https://stackoverflow.com/questions/38237506/rotate-camera-in-matter-js/60267606#60267606
  Matter.Render.startViewTransform = function(render) {
      var boundsWidth = render.bounds.max.x - render.bounds.min.x,
          boundsHeight = render.bounds.max.y - render.bounds.min.y,
          boundsScaleX = boundsWidth / render.options.width,
          boundsScaleY = boundsHeight / render.options.height;

      // add lines:
      var w2 = render.canvas.width / 2;
      var h2 = render.canvas.height / 2;
      render.context.translate(w2, h2);
      render.context.rotate(0);
      render.context.translate(-w2, -h2);
      // /add lines.

      render.context.scale(1 / boundsScaleX, 1 / boundsScaleY);
      render.context.translate(-render.bounds.min.x, -render.bounds.min.y);
  };

  return run(element, scenarioIdx)
}
;[engine, render] = run(demoElement, scenarioIdx)
</script>
</body>
</html>

Ancestors (12)

  1. Matter.js
  2. JavaScript physics engine
  3. JavaScript library
  4. JavaScript
  5. List of programming languages
  6. Programming language
  7. Software
  8. Computer
  9. Information technology
  10. Area of technology
  11. Technology
  12. Home