Skip to content

How do I make circle bodies not 'clip/melt' into each other? #1332

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
jeremylee34 opened this issue Feb 24, 2025 · 0 comments
Open

How do I make circle bodies not 'clip/melt' into each other? #1332

jeremylee34 opened this issue Feb 24, 2025 · 0 comments

Comments

@jeremylee34
Copy link

jeremylee34 commented Feb 24, 2025

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>
  );
};
@jeremylee34 jeremylee34 changed the title How do I make circle bodies not 'melt' into each other? How do I make circle bodies not 'clip/melt' into each other? Feb 24, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant