javascriptd3.jsmatter.js

How to add a Matter.js MouseConstraint to an svg viewbox element?


I'm trying to use Matter.js for it's physics engine while using D3.js for rendering the Matter physics bodies as SVG elements.

This demo is just a box dropping onto the ground. Everything works as expected up until I try to add a MouseConstraint. My mouse.element is the SVG viewbox, which is good, but clicking on the box doesn't do anything. The code below is in this codepen as well.

I made a very similar working example in another codepen using Matter's default renderer (which uses canvas) where the mouse interactions "just work" and you can click/drag the box around.

TLDR: What leg-work do I need to do here to make the MouseConstraint work without Matter's renderer? I don't use Javascript in my day-to-day (besides some D3) so this is all a little opaque to me.

Here's the non-working code:

const Engine = Matter.Engine
const Bodies = Matter.Bodies
const Composite = Matter.Composite
const World = Matter.World

const width = 400;
const height = 400;

// Helpers
vertices_to_points = function(vertices) {
  return vertices.map(vertex => `${vertex.x},${vertex.y}`).join(" ");
}

// Initialize D3 selection
const svg = d3.select("#container")
  .append("svg")
  .attr("viewBox", [0, 0, width, height])
  .attr("style", "max-width: 100%; height: auto;");

// Initialize physics objects
const engine = Engine.create();
const world = engine.world;
const bodies = world.bodies;

const box = Bodies.rectangle(width/2, height/2, 100, 100);
const ground = Bodies.rectangle(width/2, height - 25, width - 10, 40, { isStatic: true });
Composite.add(engine.world, [box, ground]);

// Render
bodies.forEach(body => {
  svg.append("polygon")
    .attr("id", `body-${body.id}`)
    .attr("points", vertices_to_points(body.vertices))
    .attr("fill", "white")
    .attr("stroke", "black")
    .attr("stroke-width", 2)
});

// Update + render loop
(function update() {
  window.requestAnimationFrame(update);
  Engine.update(engine, 5);
  
  // Move the vertices to sync the D3 <polygon> with the Matter
  // physics body.
  bodies.forEach(body => {
    svg.select(`#body-${body.id}`)
      .attr("points", vertices_to_points(body.vertices))
  });
})();

// Mouse interaction (DOES NOT WORK)
const mouse = Matter.Mouse.create(svg.node());
mouse.pixelRatio = 2;
const mouse_constraint = Matter.MouseConstraint.create(engine, {
  mouse: mouse
})
World.add(world, mouse_constraint);

Solution

  • Use the container for the mouse constraint:

    const mouse = Matter.Mouse.create(document.querySelector("#container"));
    

    If you only want the mouse to affect certain bodies, try masks.


    Note that World is deprecated, so you can use Composite instead.