Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to apply physics to complex shapes? (matter.js + p5.js)

I've been trying to make a ball pit sim using matter.js + p5.js to use as an interactive website background.

With some help from Mr Shiffman's video I've managed to get it working well with circle shapes, but I want to take it to the next level and use custom blob shapes (taken from the client's logo) and apply the same physics to them.

blob shape I'm trying to emulate

I've been able to get the custom shapes to render using a combination of p5's Beginshape() and matter's bodies.fromVertices. It sort of works as you can see but the physics is very weird, and the collisions don't seem to match even though I used the same vertices for both.

I think it may be to do with this quote from the p5 docs

Transformations such as translate(), rotate(), and scale() do not work within beginShape().

but I don't know what I can do to get around this as I need them to be able to translate / rotate for the physics to work...

Any ideas? help much appreciated!

Codepen

    var fric = .6;
    var rest = .7
    //blob creation function
    function Blob(x, y) {
        var options = {
            friction: fric,
            restitution: rest
        }
        this.body = Bodies.fromVertices(x,y, blob, options);;
        World.add(world, this.body);
        
        this.show = function() {
            var pos = this.body.position;
            var angle = this.body.angle;
            
            push();
            translate(pos.x, pos.y);
            rotate(angle);
            rectMode(CENTER);
            strokeWeight(0);
            fill('#546B2E')
            beginShape();
              curveVertex(10.4235750010825,77.51573373392407);
              curveVertex(3.142478233002126,70.89274677890447);
              curveVertex(0.09197006398718799,61.45980047762196);
              curveVertex(1.1915720013184474,51.59196924554452);
              curveVertex(4.497757286928595,42.162760563619436);
              curveVertex(5.252622102311041,32.216346235505895);
              curveVertex(4.731619980811491,22.230638463608106);
              curveVertex(4.748780859149178,12.256964518539956);
              curveVertex(8.728313738681376,3.3252404103204602);
              curveVertex(17.998080279150148,0.07532797415084502);
              curveVertex(27.955564903146588,0.6294681264134124);
              curveVertex(37.68448491855515,2.8865688476481735);
              curveVertex(46.899804284802386,6.733477319787068);
              curveVertex(55.386932458422265,12.031766230704845);
              curveVertex(62.886098235421045,18.623827217916812);
              curveVertex(69.13243582467831,26.40824364010799);
              curveVertex(73.70136375533966,35.2754654128657);
              curveVertex(75.90839243871912,44.99927633563314);
              curveVertex(74.84120838749334,54.8784706257129);
              curveVertex(70.09272040861401,63.61579878615303);
              curveVertex(62.590342401896606,70.15080526550207);
              curveVertex(53.62552650480876,74.54988781923045);
              curveVertex(44.08788115809841,77.55817639102708);
              curveVertex(34.30859814694884,79.58860716640554);
              curveVertex(24.334764892578125,80.23994384765624);
              curveVertex(14.444775242328642,78.88621691226959);
            endShape(CLOSE);
            pop();
        }
    }

var clientHeight = document.getElementById('physBox').clientHeight;
var clientWidth = document.getElementById('physBox').clientWidth;

var Engine = Matter.Engine,
    World = Matter.World,
    Bodies = Matter.Bodies,
    Common = Matter.Common,
    Composite = Matter.Composite,
    Mouse = Matter.Mouse,
    MouseConstraint = Matter.MouseConstraint,
    Vertices = Matter.Vertices;

var blob = Vertices.fromPath('10.4235750010825 77.51573373392407 3.142478233002126 70.89274677890447 0.09197006398718799 61.45980047762196 1.1915720013184474 51.59196924554452 4.497757286928595 42.162760563619436 5.252622102311041 32.216346235505895 4.731619980811491 22.230638463608106 4.748780859149178 12.256964518539956 8.728313738681376 3.3252404103204602 17.998080279150148 0.07532797415084502 27.955564903146588 0.6294681264134124 37.68448491855515 2.8865688476481735 46.899804284802386 6.733477319787068 55.386932458422265 12.031766230704845 62.886098235421045 18.623827217916812 69.13243582467831 26.40824364010799 73.70136375533966 35.2754654128657 75.90839243871912 44.99927633563314 74.84120838749334 54.8784706257129 70.09272040861401 63.61579878615303 62.590342401896606 70.15080526550207 53.62552650480876 74.54988781923045 44.08788115809841 77.55817639102708 34.30859814694884 79.58860716640554 24.334764892578125 80.23994384765624 14.444775242328642 78.88621691226959');
    
var engine;
var world;
var blobs =[];

var ground;
var ceiling;
var wallLeft;
var wallRight;

var mConstraint;
//start sim after x time
setTimeout(function setup() {

    var cnv = createCanvas(clientWidth, clientHeight);
    cnv.parent("physBox");
    
    engine = Engine.create();
    world = engine.world;
    Engine.run(engine);
    //add ground
    ground = Bodies.rectangle(clientWidth/2, clientHeight+500, clientWidth, 1000, { isStatic: true });
    World.add(world, ground);
    //add ceiling
    ceiling = Bodies.rectangle(clientWidth/2, -clientHeight-500, clientWidth, 1000, { isStatic: true });
    World.add(world, ceiling);
    //add left wall
    wallLeft = Bodies.rectangle(-500, clientHeight/2, 1000, clientHeight*2, { isStatic: true });
    World.add(world, wallLeft);
    //add right wall
    wallRight = Bodies.rectangle(clientWidth+500, clientHeight/2, 1000, clientHeight*2, { isStatic: true });
    World.add(world, wallRight);
    //create x bodies
        for (var i = 0; i < 4; i++) {
        blobs.push(new Blob(clientWidth/2, 100));
        }
    //mouse controls
    var options = {
        mouse: canvasmouse
    }
    var canvasmouse = Mouse.create(cnv.elt);
    mConstraint = MouseConstraint.create(engine);
    World.add(world,mConstraint);
    
}, 2000);    

function draw() {

    background('#EEF2FD');
    //show all bodies
    for (var i = 0; i < blobs.length; i++) {
        blobs[i].show();
    }
    
}
    
body, html {
  overflow: hidden;
  padding:0;
  margin:0;
}
h1 {
  font-family: sans-serif;
  font-size: 4vw;
  background: none;
  position: absolute;
  margin-top:10%;
  margin-left: 10%;
  user-select: none;
}
#physBox {
      width: 100%;
    height: 100vh;
    padding: 0;
    left: 0;
    z-index: -1;
    box-sizing: border-box;
    
      -webkit-touch-callout: none; /* iOS Safari */
    -webkit-user-select: none; /* Safari */
     -khtml-user-select: none; /* Konqueror HTML */
       -moz-user-select: none; /* Old versions of Firefox */
        -ms-user-select: none; /* Internet Explorer/Edge */
            user-select: none; /* Non-prefixed version, currently
                                  supported by Chrome, Edge, Opera and Firefox */
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/decomp.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.17.1/matter.min.js"></script>

<section id="physBox">
  <h1> Welcome to my website</h1>
</section>

Note: I originally tried doing this all with SVGs instead of custom shapes but struggled to understand how to make it work so I gave up, if someone could help me solve this using SVGs instead i'd be happy with that! Also, apologies for my terrible code formatting - I'm learning ;)

like image 392
theo Avatar asked Oct 15 '25 05:10

theo


1 Answers

The root issue here is that matter.js positions objects based on their center of mass rather than the origin of the coordinate system that your vertices are in, and it is clear that the origin of the coordinate system for you blob vertices is not its center of mass since all of your vertices are positive. You can calculate the center of mass for your blob and then use that offset before drawing:

const fric = 0.6;
const rest = 0.7;

const {
  Engine,
  Runner,
  World,
  Bodies,
  Body,
  Common,
  Composite,
  Mouse,
  MouseConstraint,
  Vertices
} = Matter;

const blob = Vertices.fromPath('10.4235750010825 77.51573373392407 3.142478233002126 70.89274677890447 0.09197006398718799 61.45980047762196 1.1915720013184474 51.59196924554452 4.497757286928595 42.162760563619436 5.252622102311041 32.216346235505895 4.731619980811491 22.230638463608106 4.748780859149178 12.256964518539956 8.728313738681376 3.3252404103204602 17.998080279150148 0.07532797415084502 27.955564903146588 0.6294681264134124 37.68448491855515 2.8865688476481735 46.899804284802386 6.733477319787068 55.386932458422265 12.031766230704845 62.886098235421045 18.623827217916812 69.13243582467831 26.40824364010799 73.70136375533966 35.2754654128657 75.90839243871912 44.99927633563314 74.84120838749334 54.8784706257129 70.09272040861401 63.61579878615303 62.590342401896606 70.15080526550207 53.62552650480876 74.54988781923045 44.08788115809841 77.55817639102708 34.30859814694884 79.58860716640554 24.334764892578125 80.23994384765624 14.444775242328642 78.88621691226959');

// from http://paulbourke.net/geometry/polygonmesh/
function computeArea(vertices) {
  let area = 0;
  for (let i = 0; i < vertices.length - 1; i++) {
    let v = vertices[i];
    let vn = vertices[i + 1];
    area += (v.x * vn.y - vn.x * v.y) / 2;
  }

  return area;
}

function computeCenter(vertices) {
  let area = computeArea(vertices);
  let cx = 0,
    cy = 0;
  for (let i = 0; i < vertices.length - 1; i++) {
    let v = vertices[i];
    let vn = vertices[i + 1];
    cx += (v.x + vn.x) * (v.x * vn.y - vn.x * v.y) / (6 * area);
    cy += (v.y + vn.y) * (v.x * vn.y - vn.x * v.y) / (6 * area);
  }

  return {
    x: cx,
    y: cy
  };
}

const center = computeCenter(blob);

let engine;
let world;
let blobs = [];

let ground;
let ceiling;
let wallLeft;
let wallRight;

let mConstraint;

//blob creation function
function Blob(x, y) {
  let options = {
    friction: fric,
    restitution: rest
  }

  this.body = Bodies.fromVertices(x, y, blob, options);
  World.add(world, this.body);
  
  // Scales the body around the center
  Body.scale(this.body, 0.5, 0.5);

  this.show = function() {
    var pos = this.body.position;
    var angle = this.body.angle;

    push();
    translate(pos.x, pos.y);
    rotate(angle);
    scale(0.5, 0.5);
    translate(-center.x, -center.y);
    strokeWeight(0);
    fill('#546B2E')
    beginShape();
    for (const {
        x,
        y
      } of blob) {
      curveVertex(x, y);
    }
    endShape(CLOSE);
    pop();

    // Alternately, when drawing your blobs you could use 
    // the bodies vertices, but it looks like these are
    // converted into a convex polygon.
    push();
    stroke('red');
    strokeWeight(1);
    noFill();
    beginShape();
    for (const {
        x,
        y
      } of this.body.vertices) {
      curveVertex(x, y);
    }
    endShape(CLOSE);
    pop();
  }
}

//start sim after x time
function setup() {
  const cnv = createCanvas(windowWidth, Math.max(windowHeight, 300));

  engine = Engine.create();
  world = engine.world;

  const runner = Runner.create();
  Runner.run(runner, engine);
  //add ground
  ground = Bodies.rectangle(width / 2, height, width, 50, {
    isStatic: true
  });
  World.add(world, ground);
  //add ceiling
  ceiling = Bodies.rectangle(width / 2, 0, width, 50, {
    isStatic: true
  });
  World.add(world, ceiling);
  //add left wall
  wallLeft = Bodies.rectangle(0, height / 2, 50, height, {
    isStatic: true
  });
  World.add(world, wallLeft);
  //add right wall
  wallRight = Bodies.rectangle(width, height / 2, 50, height, {
    isStatic: true
  });
  World.add(world, wallRight);
  //create x bodies
  for (let i = 0; i < 4; i++) {
    blobs.push(new Blob(random(50, width - 100), random(50, height - 100)));
  }
}

function draw() {
  background('#EEF2FD');
  //show all bodies
  for (var i = 0; i < blobs.length; i++) {
    blobs[i].show();
  }
}

function mouseClicked() {
  blobs.push(new Blob(mouseX, mouseY));
}
html,
body {
  margin: 0;
  overflow-x: hidden;
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/decomp.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.17.1/matter.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
like image 89
Paul Wheeler Avatar answered Oct 17 '25 19:10

Paul Wheeler



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!