如何将物理学应用于复杂形状?(matter.js + p5.js)

4
我一直在尝试使用matter.js + p5.js制作一个球坑模拟器,用作交互式网站背景。在Shiffman先生的视频的帮助下,我已经成功地将其与圆形形状结合使用,但我想将其提升到更高的水平,并使用自定义blob形状(取自客户的标志),并将相同的物理效应应用于它们。

blob shape I'm trying to emulate

我已经能够使用p5的Beginshape()和matter的bodies.fromVertices组合来呈现自定义形状。如你所见,这种方法有点可行,但物理效果很奇怪,即使我对两者使用了相同的顶点,碰撞也似乎不匹配。
我认为这可能与p5文档中的以下引用有关:
“在beginShape()中,translate()、rotate()和scale()等转换不起作用。”
但我不知道该怎么解决这个问题,因为我需要它们能够进行平移/旋转,以便物理效果正常...
有什么想法吗?非常感谢您的帮助!

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/poly-decomp@0.2.1/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>

注意:我最初尝试使用SVG代替自定义形状,但是我无法理解如何使其工作,所以放弃了。如果有人能帮我使用SVG解决这个问题,我会很高兴!另外,对于我的可怕的代码格式,我表示歉意 - 我正在学习;)

1个回答

3

这里的根本问题在于matter.js基于物体的质心而不是坐标系原点来定位物体,而你blob的顶点所在的坐标系原点显然不是其质心,因为所有顶点都是正数。你可以计算blob的质心并在绘制之前使用该偏移量:

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/poly-decomp@0.2.1/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>


哇,这太棒了,谢谢!关于中心缩放的问题,你说得很有道理。我以为rectMode(CENTER)会解决这个问题,但我猜它不支持自定义形状。不过这个解决方案非常好,虽然我不太懂数学(哈哈),但它完美地解决了我的问题,非常感谢!我可以再问一个问题吗?-> 有没有办法动态缩放这些形状?我的意思是 - 我想将blob的宽度缩放到视口宽度的10%,以便它能够适应不同的设备。 - theo
我认为这需要使用Matter.Body.scale(body, scaleX, scaleY, [point])函数,但是我无法实现它而不出现错误或没有结果。 - theo
1
啊,是的,rectMode只适用于'rect()'和'square()'函数。我已经更新了我的示例来缩放你的斑点。需要注意的是,你需要使用matter.js body调用Body.scale,并且在绘制形状时需要调用p5.js scale函数。 - Paul Wheeler
哦,我明白了,我觉得我快做出来了,但在比例尺函数中我把“this”写成了“this.body”,总是会忘记这一点。再次感谢保罗,这个问题困扰了我好几天 :) - theo
你能否使用 Matter.Vertices.centre(body.vertices) 代替? - CrazyVideoGamer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接