Home > front end >  Detect if Matter.js Body is Circle
Detect if Matter.js Body is Circle

Time:07-31

I often find myself needing to test if a particular Matter body is a circle or not, as in:

const compounds = Matter.Composite.allBodies(engine.world)
compounds.forEach(compound => compound.parts.forEach(part => {
  const isCircle = ???
  if (isCircle) console.log(part.id, 'is a circle')
  else console.log(part.id, 'is not a circle')
})

I can't find an official way to test if a Matter body was created as a circle. How can I test if a body was created with new Matter.Body.Circle versus another Body constructor?

CodePudding user response:

The other answer suggests looking for circle-specific properties. One problem is, these can change in future Matter.js releases. Also, it doesn't make for readable, intuitive code, and can lead to surprising bugs when additional body types wind up containing a property unexpectedly.

Better is to use the internal label property (also suggested in that answer), which should be stable and defaults to the seemingly-useful "Rectangle Body" and "Circle Body". For simple use cases, this works. Since it's possible to set the label to an object to store arbitrary custom data on the body, it's tempting to go further and use labels for just about everything.

However, I generally ignore labels. The reason is that it pushes too much of the client logic into a physics library that's not really designed for entity management. Furthermore, either of these approaches (labels or body-specific properties) involves iterating all of the bodies to filter out the type you're interested in.

Although no context was provided about the app, having to call allBodies often seems like a potential antipattern. It might be time to consider a redesign so you don't have to. What if you have 5 circles and 500 other bodies? Recursively iterating all 500 on every frame just to find the 5 is a huge waste of resources to achieve something that should be easy and efficient.

My preferred solution for entity management is to simply keep track of each type upon creation, putting them into data structures that are tuned to application-specific needs.

For example, the following script shows a method of efficiently determining body type by presenting the body as a key to a pre-built types map.

const engine = Matter.Engine.create();
engine.gravity.y = 0; // enable top-down
const map = {width: 300, height: 300};
const render = Matter.Render.create({
  element: document.querySelector("#container"),
  engine,
  options: {...map, wireframes: false},
});
const rnd = n => ~~(Math.random() * n);

const rects = [...Array(20)].map(() => Matter.Bodies.rectangle(
  rnd(map.width),  // x
  rnd(map.height), // y
  rnd(10)   15,    // w
  rnd(10)   15,    // h
  {
    angle: rnd(Math.PI * 2),
    render: {fillStyle: "pink"}
  }
));
const circles = [...Array(20)].map(() => Matter.Bodies.circle(
  rnd(map.width),  // x
  rnd(map.height), // y
  rnd(5)   10,     // r
  {render: {fillStyle: "red"}}
));
const walls = [
  Matter.Bodies.rectangle(
    0, map.height / 2, 20, map.height, {
      isStatic: true, render: {fillStyle: "yellow"}
    }
  ),
  Matter.Bodies.rectangle(
    map.width / 2, 0, map.width, 20, {
      isStatic: true, render: {fillStyle: "yellow"}
    }
  ),
  Matter.Bodies.rectangle(
    map.width, map.height / 2, 20, map.height, {
      isStatic: true, render: {fillStyle: "yellow"}
    }
  ),
  Matter.Bodies.rectangle(
    map.width / 2, map.height, map.width, 20, {
      isStatic: true, render: {fillStyle: "yellow"}
    }
  ),
];
const rectangle = Symbol("rectangle");
const circle = Symbol("circle");
const wall = Symbol("wall");
const types = new Map([
  ...rects.map(e => [e, rectangle]),
  ...circles.map(e => [e, circle]),
  ...walls.map(e => [e, wall]),
]);
const bodies = [...types.keys()];
const mouseConstraint = Matter.MouseConstraint.create(
  engine, {element: document.querySelector("#container")}
);
Matter.Composite.add(engine.world, [
  ...bodies, mouseConstraint
]);

const runner = Matter.Runner.create();
Matter.Events.on(runner, "tick", event => {
  const underMouse = Matter.Query.point(
    bodies,
    mouseConstraint.mouse.position
  );
  
  if (underMouse.length) {
    const descriptions = underMouse.map(e =>
      types.get(e).description
    );
    document.querySelector("#type-hover").textContent = `
      ${descriptions.join(", ")} hovered
    `;
  }
  else {
    document.querySelector("#type-hover").textContent = `
      [hover a body]
    `;
  }

  if (mouseConstraint.body) {
    document.querySelector("#type-click").textContent = `
      ${types.get(mouseConstraint.body).description} selected
    `;
  }
  else {
    document.querySelector("#type-click").textContent = `
      [click and drag a body]
    `;
  }
});

Matter.Render.run(render);
Matter.Runner.run(runner, engine);
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js"></script>

<h3 id="type-click">[click and drag a body]</h3>
<h3 id="type-hover">[hover a body]</h3>
<div id="container"></div>

If creating and destroying bodies can happen dynamically, a function would need to be written to handle data structure bookkeeping.

Another approach that might work well for some apps is to have a few type-specific sets or maps. This allows you to quickly access all entities of a particular type. This becomes particularly useful once you begin composing bodies as properties of custom classes.

There's no reason you can't have both structures--a reverse lookup that gives the type or custom class given a MJS body, and a structure that contains references to all entities or MJS bodies of a particlar type.

The reverse lookup could enable logic like "on click, take an action on a specific MJS body depending on its type" or "on click, locate my custom class/model associated with this MJS body", while collections support logic like "destroy all enemies".

Ideally, code shouldn't be doing much type-checking. With proper OOP design, you can implement classes that respond correctly to methods regardless of their type. For example, if you have Enemy and Ally classes that each respond to a click, you might create a method called handleClick in each class. This allows you to use code like clickedEntity.handleClick(); without having to know whether clickedEntity is an Enemy or Ally, thereby avoiding the need for a "get type" operation entirely.


For more design suggestions for Matter.js projects, see:

CodePudding user response:

You can console.log(a_circle) and check for something to identify a circle by. I think you can check for a_circle.circleRadius or a_circle.label=='Circle Body'

  • Related