Matter.js — How to get the dimension of an image to set Bodies sizes?

不想你离开。 提交于 2021-02-04 20:49:49

问题


I am trying to programmatically set the width and heights of the chained bodies in matter.js. Unfortunately, I am only getting 0 as values and I am unsure why. My guess is that the images are not being loaded fast enough to provide those values. How can I load those dimensions before the images are loaded?

Pseudo-code

  • Several bodies from Array
  • Get the width and height of each image in the Array
  • Use this value to set the Bodies dimensions

Code

var playA = Composites.stack(
  percentX(25) - assetSize / 2,
  percentY(25),
  1,
  6,
  5,
  5,
  function (x, y) {
    iA++;

    var imgWidth;
    var imgHeight;

    var img = new Image();
    img.src = String(design[iA]);

    var imgWidth = 0;
    var imgHeight = 0;

    img.onload = function a() {
      imgWidth = img.naturalWidth;
      imgHeight = img.naturalHeight;

      console.log(String(design[iA]), imgWidth, imgHeight);
    };
    console.log(String(design[iA]), imgHeight, imgWidth); // I can't access the values here.

    return Bodies.rectangle(x, y, imgWidth, imgHeight, {
      // collisionFilter: { group: group },
      friction: 1,
      render: {
        sprite: {
          texture: design[iA],
          xScale: (assetSize / 100) * 0.46,
          yScale: (assetSize / 100) * 0.46
        }
      }
    });
  }
);

Composites.chain(playA, 0.3, 0, -0.5, 0, {
  stiffness: 1,
  length: 10,
  render: { type: "line", visible: false }
});

回答1:


If you know the dimensions and can populate an array beforehand, the solution is potentially straightforward since Matter.js loads images given a URL string, with the caveat that the engine doesn't wait for the loads before running.

Here's a minimal example of iterating over width/height pairs in an array and passing these properties into the rectangle calls which I'll use as a stepping stone to the example that matches your use case.

var engine = Matter.Engine.create();
var render = Matter.Render.create({
  element: document.body,
  engine: engine,
  options: {
    width: 450,
    height: 250,
    wireframes: false, // required for images
  }
});
Matter.Render.run(render);

var runner = Matter.Runner.create();
Matter.Runner.run(runner, engine);

var imgSizes = [[56, 48], [45, 50], [35, 50], [60, 63]];
var stack = Matter.Composites.stack(
  // xx, yy, columns, rows, columnGap, rowGap, cb
  150, 50, 4, 1, 0, 0,
  function (x, y, i) {
    var w = imgSizes[i][0];
    var h = imgSizes[i][1];
    return Matter.Bodies.rectangle(x, y, w, h, {
      render: {
        sprite: {
          texture: "http://placekitten.com/" + w + "/" + h 
        }
      }
    });
  }
);
Matter.Composites.chain(stack, 0.5, 0, -0.5, 0, {
  stiffness: 0.75,
  length: 10,
  render: {type: "line", visible: true}
});

Matter.World.add(engine.world, [
  stack,
  Matter.Bodies.rectangle(225, 0, 450, 25, {
    isStatic: true
  }),
  Matter.Bodies.rectangle(450, 150, 25, 300, {
    isStatic: true
  }),
  Matter.Bodies.rectangle(0, 150, 25, 300, {
    isStatic: true
  }),
  Matter.Bodies.rectangle(225, 250, 450, 25, {
    isStatic: true
  })
]);

var mouse = Matter.Mouse.create(render.canvas);
var mouseConstraint = Matter.MouseConstraint.create(engine, {
  mouse: mouse,
  constraint: {
    stiffness: 0.2,
    render: {visible: true}
  }
});
Matter.World.add(engine.world, mouseConstraint);
render.mouse = mouse;
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.14.2/matter.min.js" integrity="sha512-pi0tSRZdlNRZeANPwdAIHRAYg6gZZV6QlAiyHXn5TYqLzBKE9jlttO/QgYLMhISD6oNv2kPsVelx+n5nw0FqKA==" crossorigin="anonymous"></script>

Now, if you need to load images using onload and use their dimensions, you'll need to use promises or put all code dependent on these images into the sequence of onload callback(s) as described in the canonical How do I return the response from an asynchronous call?.

The failing pattern is:

const getSomethingAsync = cb => setTimeout(() => cb("something"), 0);

let data = null;
getSomethingAsync(result => {
  data = result;
  console.log("this runs last");
});
console.log(data); // guaranteed to be null, not "something"
// more logic that is supposed to depend on data

The fix is:

const getSomethingAsync = cb => setTimeout(() => cb("something"), 0);

getSomethingAsync(data => {
  console.log(data);
  // logic that depends on the data from `getSomethingAsync`
});

console.log("this will run first");
// logic that doesn't depend on data from `getSomethingAsync`

Since you're juggling multiple onloads, you can promisify the onloads to make them easier to work with. I have a couple examples of doing this here and here agnostic of matter.js.

Here's an example of using promises to load images applied to your general problem. Again, I'll use my own code so that it's runnable and reproducible, but the pattern should be easy to extrapolate to your project.

The idea is to first load the images using a series of promises which are resolved when onload handlers fire, then use Promise.all to chain a then which runs the MJS initializer callback only when all images are loaded. The widths and heights are then accessible to your matter.js code within the callback.

As a side benefit, this ensures images are loaded by the time MJS runs.

var initializeMJS = function (images) {
  var engine = Matter.Engine.create();
  var render = Matter.Render.create({
    element: document.body,
    engine: engine,
    options: {
      width: 450,
      height: 250,
      wireframes: false, // required for images
    }
  });
  Matter.Render.run(render);

  var runner = Matter.Runner.create();
  Matter.Runner.run(runner, engine);

  var stack = Matter.Composites.stack(
    // xx, yy, columns, rows, columnGap, rowGap, cb
    150, 50, 4, 1, 0, 0,
    function (x, y, i) {
      var w = images[i].width;
      var h = images[i].height;
      return Matter.Bodies.rectangle(x, y, w, h, {
        render: {
          sprite: {
            texture: images[i].src
          }
        }
      });
    }
  );
  Matter.Composites.chain(stack, 0.5, 0, -0.5, 0, {
    stiffness: 0.75,
    length: 10,
    render: {type: "line", visible: true}
  });

  Matter.World.add(engine.world, [
    stack,
    Matter.Bodies.rectangle(225, 0, 450, 25, {
      isStatic: true
    }),
    Matter.Bodies.rectangle(450, 150, 25, 300, {
      isStatic: true
    }),
    Matter.Bodies.rectangle(0, 150, 25, 300, {
      isStatic: true
    }),
    Matter.Bodies.rectangle(225, 250, 450, 25, {
      isStatic: true
    })
  ]);

  var mouse = Matter.Mouse.create(render.canvas);
  var mouseConstraint = Matter.MouseConstraint.create(engine, {
    mouse: mouse,
    constraint: {
      stiffness: 0.2,
      render: {visible: true}
    }
  });
  Matter.World.add(engine.world, mouseConstraint);
  render.mouse = mouse;
};

var imageSizes = [[56, 48], [45, 50], [35, 50], [60, 63]];
var imageURLs = imageSizes.map(function (e) {
  return "http://placekitten.com/" + e[0] + "/" + e[1];
});

Promise.all(imageURLs.map(function (e) {
    return new Promise(function (resolve, reject) {
      var img = new Image();
      img.onload = function () {
        resolve(this);
      };
      img.onerror = reject;
      img.src = e;
    })
  }))
  .then(initializeMJS)
;
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.14.2/matter.min.js" integrity="sha512-pi0tSRZdlNRZeANPwdAIHRAYg6gZZV6QlAiyHXn5TYqLzBKE9jlttO/QgYLMhISD6oNv2kPsVelx+n5nw0FqKA==" crossorigin="anonymous"></script>


来源:https://stackoverflow.com/questions/64371523/matter-js-how-to-get-the-dimension-of-an-image-to-set-bodies-sizes

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!