Issue
I building a react website with matter.js. I am using the useEffect
hook to render stuff to the canvas with matter.js (I found most of this code here). However, when I try to draw anything else to the canvas, nothing appears. Everything matter.js-related works.
const scene = useRef()
const isDragging = useRef(false)
const engine = useRef(Engine.create())
useEffect(() => {
const cw = 1100;
const ch = 700;
const render = Render.create({
canvas: scene.current,
engine: engine.current,
options: {
width: cw,
height: ch,
wireframes: false,
background: 'transparent'
}
})
console.log("gravity " + engine.current.gravity.y + "x : " + engine.current.gravity.x)
let mouse = Mouse.create(render.canvas);
let mouseConstraint = MouseConstraint.create(engine.current, {
mouse: mouse,
constraint: {
render: {
visible: false
}
}
})
render.mouse = mouse;
World.add(engine.current.world, [
Bodies.rectangle(cw / 2, 0, cw, 20, {
isStatic: true,
density: 1
}),
Bodies.rectangle(cw / 2, ch, cw, 20, {
isStatic: true,
density: 1
}),
Bodies.rectangle(0, ch / 2, 20, ch, {
isStatic: true,
density: 1
}),
Bodies.rectangle(cw, ch / 2, 20, ch, {
isStatic: true,
density: 1
}),
mouseConstraint,
])
Runner.run(engine.current)
Render.run(render)
Events.on(mouseConstraint, "mousedown", function(event) {
handleSelections(mouseConstraint.body)
})
Events.on(mouseConstraint, "startdrag", function(event) {
isDragging.current = true
})
Events.on(mouseConstraint, "enddrag", function() {
isDragging.current = false
})
Events.on(engine.current, 'afterUpdate', function() {
countTen.current = countTen.current + 1;
if (countTen.current == 10) {
countTen.current = 0;
if (selectedObjectRef.current != null) {
setGraphingPos(selectedObjectRef.current.velocity.y * -1);
setTicks(ticks + 1)
}
}
})
// ******************* This part doesn't work **************************
scene.current.getContext('2d').beginPath();
scene.current.getContext('2d').arc(100, 100, 20, 0, 2 * Math.PI, false);
scene.current.getContext('2d').fillStyle = 'red';
scene.current.getContext('2d').fill();
// **********************************************************************
return () => {
Render.stop(render)
World.clear(engine.current.world)
Engine.clear(engine.current)
render.canvas.remove()
render.canvas = null
render.context = null
render.textures = {}
}
}, [])
<canvas ref={scene} onClick={ handleMouseDown} className='main-canvas'></canvas>
Any kind of help is much appreciated!
Solution
Currently, you're drawing one time, up front, on the same canvas that MJS wipes per frame. So, you draw your circle, then MJS wipes the canvas immediately when it renders its first frame. You never attempt to draw again as the engine and renderer run onward.
I see a few solutions (at least!). Which is most appropriate depends on your specific use case.
- Draw on the canvas on each frame inside the
afterRender
callback for therender
object. This is the simplest and most direct solution from where you are now and I provide an example below. - Add a second transparent canvas on top of the one you're providing to MJS. This is done with absolute positioning. Now you can do whatever you want in this overlay without interference from MJS.
- Run MJS headlessly, which gives you maximum control, as the MJS built-in renderer is mostly intended for protoyping (it's a physics engine, not a graphics engine). Once you're headless, you can render whatever you want, whenever you want, however you want. I have many demos of headless rendering in my other answers, both to the plain DOM and with React and p5.js.
As an aside, this doesn't appear related to React or useEffect
in any way; the same problem would arise even if you were injecting a plain canvas in vanilla JS.
Also, keep in mind that whatever you draw is totally unrelated to MJS other than the fact that it happens to be on the same canvas. Don't expect physics to work on it, unless you're using coordinates from a body MJS knows about.
Although you haven't provided a full component, here's a minimal proof-of-concept of the afterRender
approach mentioned above:
const {useEffect, useRef} = React;
const {Bodies, Engine, Events, Render, Runner, Composite} = Matter;
const Scene = () => {
const canvasRef = useRef();
useEffect(() => {
const cw = 200;
const ch = 200;
const engine = Engine.create();
const ctx = canvasRef.current.getContext("2d");
const render = Render.create({
canvas: canvasRef.current,
engine,
options: {
width: cw,
height: ch,
wireframes: false,
background: "transparent",
}
});
Events.on(render, "afterRender", () => {
const x = Math.cos(Date.now() / 300) * 80 + 100;
const y = Math.sin(Date.now() / 299) * 80 + 100;
ctx.beginPath();
ctx.arc(x, y, 20, 0, 2 * Math.PI);
ctx.fillStyle = "red";
ctx.fill();
});
Composite.add(engine.world, [
Bodies.rectangle(cw / 2, 0, cw, 20, {
isStatic: true,
density: 1,
}),
Bodies.rectangle(cw / 2, ch, cw, 20, {
isStatic: true,
density: 1,
}),
Bodies.rectangle(0, ch / 2, 20, ch, {
isStatic: true,
density: 1,
}),
Bodies.rectangle(cw, ch / 2, 20, ch, {
isStatic: true,
density: 1,
}),
]);
Runner.run(engine);
Render.run(render);
return () => {
Render.stop(render);
Composite.clear(engine.world);
Engine.clear(engine);
render.canvas.remove();
render.canvas = null;
render.context = null;
render.textures = {};
};
}, []);
return <canvas ref={canvasRef}></canvas>;
};
ReactDOM.render(<Scene />, document.querySelector("#app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js"></script>
<div id="app"></div>
Answered By - ggorlen
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.