-
Notifications
You must be signed in to change notification settings - Fork 2k
Description
I've created a container that adds balls in it, but I want the balls not to 'melt' into each other as pictured in the attached video.
xp.mp4
Note, I checked #951 and #5 as they seem to address a similar issue but couldn't find a clear solution for my problem there.
I tried increasing position and velocity iterations but they only seem to slightly reduce clipping, and I'm worried that a high value will affect performance. I've also tried playing around with restitution, friction, frictionStatic and slop values to no success so far (although it's been trial and error as I'm new to MatterJS).
There's also a bug where the circles will occasionally fall through the floor, especially when I resize the window as it doesn't match the browser's new dimensions.
Would appreciate any help if you've faced a similar issue :)
My versions:
matter-js: 0.20.0
next: 14.2.17
node: 20.11.1
react: 18
My code:
import React, { useEffect, useRef, useState } from "react";
import Matter from "matter-js";
const STATIC_DENSITY = 15;
const PARTICLE_SIZE = 6;
interface MatterBallsType {
particleTrigger: number
}
const VISIBLE_RES = 1280
interface ExtendedRender extends Matter.Render {
engine: Matter.Engine;
}
export const MatterBalls = ({ particleTrigger }: MatterBallsType) => {
const boxRef = useRef<HTMLDivElement>(null);
const canvasRef = useRef<HTMLCanvasElement>(null);
const [isVisible, setIsVisible] = useState(false);
const [constraints, setConstraints] = useState<DOMRect>();
const [scene, setScene] = useState<ExtendedRender>();
const handleResize = () => {
const boundingBox = boxRef.current?.getBoundingClientRect();
if (boundingBox) {
setConstraints(boundingBox);
}
};
function addWalls(width: number, height: number, world: Matter.World) {
const leftWall = Matter.Bodies.rectangle(
-20,
height / 2,
40,
height * 2,
{
isStatic: true,
render: { fillStyle: "white" }
}
);
const rightWall = Matter.Bodies.rectangle(
width + 20,
height / 2,
40,
height * 2,
{
isStatic: true,
render: { fillStyle: "white" }
}
);
Matter.World.add(world, [
leftWall,
rightWall
]);
}
function addFloor(world: Matter.World) {
const floor = Matter.Bodies.rectangle(0, 0, 60, STATIC_DENSITY, {
isStatic: true,
label: "floor",
render: {
fillStyle: "transparent"
}
});
Matter.World.add(world, [
floor,
]);
}
useEffect(() => {
setIsVisible(window.innerWidth >= VISIBLE_RES);
const Engine = Matter.Engine;
const Render = Matter.Render;
const engine = Engine.create({});
if (!boxRef.current) return;
const { width, height } = boxRef.current.getBoundingClientRect();
engine.positionIterations = 10;
engine.velocityIterations = 10;
const render = Render.create({
element: boxRef.current,
engine: engine,
canvas: canvasRef.current!,
options: {
background: "transparent",
wireframes: false
}
});
addFloor(engine.world);
addWalls(width, height, engine.world);
Render.run(render);
const runner = Matter.Runner.create();
Matter.Runner.run(runner, engine);
setConstraints(boxRef.current.getBoundingClientRect());
setScene(render as ExtendedRender);
window.addEventListener("resize", handleResize);
}, []);
useEffect(() => {
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
useEffect(() => {
function addBall() {
if (!constraints || !scene) return;
const { width } = constraints;
const randomX = Math.floor(Math.random() * -width) + width;
const randomY = Math.floor(Math.random() * -10 - 1);
Matter.World.add(
scene.engine.world,
Matter.Bodies.circle(randomX, randomY, PARTICLE_SIZE, {
restitution: 0,
friction: 1,
frictionStatic: 0,
slop: 0,
render: {
fillStyle: 'white',
sprite : {
texture: '/White_Circle.svg',
xScale: 0.02,
yScale: 0.02
}
}
})
);
}
if (scene && isVisible && particleTrigger > 0) {
addBall();
addBall();
addBall();
addBall();
addBall();
}
}, [particleTrigger, scene, isVisible, constraints]);
useEffect(() => {
if (constraints && scene) {
const { width, height } = constraints;
scene.bounds.max.x = width;
scene.bounds.max.y = height;
scene.options.width = width;
scene.options.height = height;
scene.canvas.width = width;
scene.canvas.height = height;
const floor = scene.engine.world.bodies[0];
if (floor) {
Matter.Body.setPosition(floor, {
x: width / 2,
y: height + STATIC_DENSITY / 2
});
Matter.Body.setVertices(floor, [
{ x: 0, y: height },
{ x: width, y: height },
{ x: width, y: height + STATIC_DENSITY },
{ x: 0, y: height + STATIC_DENSITY }
]);
}
}
}, [scene, constraints]);
return (
<div
ref={boxRef}
style={{
position: "relative",
overflow: "hidden",
top: 0,
left: 0,
width: "100%",
height: "100%",
zIndex: 49
}}
className="border-l border-border"
>
<canvas ref={canvasRef} />
</div>
);
};