物理世界中的刚体-MatterJs(三)

MatterJs 中的刚体

文章基于 MatterJs ^0.17.1

我们在「上一章」介绍了matterJs的物理环境.
具备了环境, 我们就需要在环境中放置物体来丰富这个世界.
今天, 我们一起来看下物体是如何在物理环境中存在和运动的.

一、matterJs中的物体介绍

matterJs中的物体分为两类:

  • Body — 刚体
  • Composite — 复合体

只支持这两种形式的物体存在于matterJs的物理环境.
刚体是单一的物体body, 而复合体是由刚体body、约束constraint或者复合体composite组合而成.
刚体提供了最基础的一些具有固定形状的物理物体.
复合体使我们可以随意组合成我们需要的不规则物体, 为物理世界多样性提供了实现的可能.

matterJs中, World也是一个复合体, 具有复合体的一切方法.

World.create = Composite.create;
World.add = Composite.add;
World.remove = Composite.remove;
World.clear = Composite.clear;
World.addComposite = Composite.addComposite;
World.addBody = Composite.addBody;
World.addConstraint = Composite.addConstraint;
复制代码

刚体工厂Bodies提供了几种最基本的形状:

  • rectangle 矩形
  • trapezoid 梯形
  • circle 圆形
  • polygon 多边形
  • fromVertices 可扩展图形

复合体工厂Composites提供了几种复合体:

  • stack 堆叠
  • chain 链
  • mesh 网格
  • pyramid 锥体
  • newtonsCradle 牛顿摆球
  • car 小车
  • softBody 软体

二、刚体的独立个体-Body

「第一章」, 我们写了一个自由落体的小球, 其中的那个小球就是一个圆形的物理刚体.
首先, 我们先来看下一个刚体具备那些属性:

属性名 默认值 介绍
id 0 每个刚体唯一id, 在创建过程中自增
type ‘body’ 类型
label ‘Body’ 标识, 可以自己取名
parts [] 刚体的组成部分, 一般数组元素都是刚体本身
plugin {} 插件
angle 0 角度, 这里是弧度制
vertices Vertices.fromPath(‘L 0 0 L 40 0 L 40 40 L 0 40’) 计算顶点, 规定了一个刚体形状
position { x: 0, y: 0 } 位置, 以重心为计算点
force { x: 0, y: 0 } x, y向量上的受力分量
torque 0 力矩, 转距 M = F * L
speed 0 速度
angularSpeed 0 角速度
velocity { x: 0, y: 0 } 物理速度, x, y分量上的位移
angularVelocity 0 物理角速度
isStatic false 是否静态刚体
isSleeping false 刚体是否睡眠
density 0.001 刚体密度
restitution 0 力的衰减系数(弹性)
friction 0.1 摩擦力
frictionStatic 0.5 静滑动摩擦力
frictionAir 0.01 空气阻力
collisionFilter { category: 0x0001, mask: 0xFFFFFFFF, group: 0 } 碰撞过滤
bounds null 刚体边界, 具有最大值和最小值
circleRadius 0 圆形半径
timeScale 1 时间比例系数, 0 冻结, 大于1 加速 小于 1 慢动作
positionPrev null 上一次的刚体位置
anglePrev null 上一次的刚体角度
axes null
area 0 2d平面形状的面积
mass 0 刚体质量
inertia 0 惯性
events null 事件

刚体密度 = 刚体质量 / 刚体面积 body.density = body.mass / body.area.
角度 = 弧度 * Math.PI / 180.
restitution可以约定具有弹性的刚体弹起高度.
friction 0 — 刚体可以做摩擦力的无限滑动,1 — 对刚体施加力后其会立刻停止.
frictionStatic 0 — 刚体几乎是静止 值越大, 移动所需的力越大.
collisionFilter 碰撞规则, 后面会单独讲.
vertices决定了一个刚体的形状, 这就为我们自定义刚体提供了思路.
force受力的大小, 控制刚体移动方向.

接下来, 我们来看下刚体提供了那些方法, 我们可以做到什么操作:

1、 Body.nextGroup

函数: Body.nextGroup = (isNonColliding) => {}
参数:

  • isNonColliding 是否可以碰撞标识

返回自增或者自减的索引, isNonCollidingtrue时, 生成自减的索引, 刚体不能碰撞, 为false时, 生成自增的索引, 刚体可以碰撞.
举个?:
当我们设置group = Body.nextGroup(true)时, 圆形和我们的地面就不会碰撞了, 设置为false时可以.

var group = Body.nextGroup(true);
/** 创建圆形刚体 */
var _circle = Bodies.circle(200, 10, 20, {
    isStatic: false,
    collisionFilter: {
        group: group
    }
});
Composite.add(world, [
    Bodies.rectangle(400, 400, 800, 10, {
        isStatic: true,
        collisionFilter: {
            group: group
        }
    }),
    _circle
]);
复制代码

matterJs是如何判断碰撞的呢?

if (filterA.group === filterB.group && filterA.group !== 0)
            return filterA.group > 0;
复制代码

这样就判断为碰撞了.

2、Body.nextCategory

函数: Body.nextCategory = ()=>{}
参数:
category值的范围是[1, 2^31].
制定碰撞分组, 可以实现指定刚体间的碰撞.
该方法只是拿来生成collisionFilter属性指定的category.
使用categorymask配合使用判断分组碰撞.

举个例子?:
_circlecategoryrectanglemask一样的时候, 两者可以碰撞.
下面例子中的categoryAcategoryB两者的值不一样.

var categoryA = Body.nextCategory(),
    categoryB = Body.nextCategory();
/** 创建圆形刚体 */
var _circle = Bodies.circle(200, 10, 20, {
    isStatic: false,
    collisionFilter: {
        category: categoryA
    }
});
Composite.add(world, [
    Bodies.rectangle(400, 400, 800, 10, {
        isStatic: true,
        collisionFilter: {
            category: categoryB,
            mask: categoryB //categoryB
        }
    }),
    _circle
]);
复制代码

matterJs是如何判断分组碰撞的呢?

return (filterA.mask & filterB.category) !== 0 &&
        (filterB.mask & filterA.category) !== 0;
复制代码

3、Body.set

函数: Body.set = (body, settings, value) => {}
参数:

  • body 设置的刚体
  • settings 设置的属性值或者键值对
  • value 在settings 为一个值「string」的时候生效

matterJs在设置属性的时候, 会调用一系列的方法, 去设置相应的值.
那么有那些属性可以被设置呢?
下面列举一些可以被设置的值:

switch (property) {
    case 'isStatic':
        Body.setStatic(body, value);
        break;
    case 'isSleeping':
        Sleeping.set(body, value);
        break;
    case 'mass':
        Body.setMass(body, value);
        break;
    case 'density':
        Body.setDensity(body, value);
        break;
    case 'inertia':
        Body.setInertia(body, value);
        break;
    case 'vertices':
        Body.setVertices(body, value);
        break;
    case 'position':
        Body.setPosition(body, value);
        break;
    case 'angle':
        Body.setAngle(body, value);
        break;
    case 'velocity':
        Body.setVelocity(body, value);
        break;
    case 'angularVelocity':
        Body.setAngularVelocity(body, value);
        break;
    case 'parts':
        Body.setParts(body, value);
        break;
    case 'centre':
        Body.setCentre(body, value);
        break;
    default:
        body[property] = value;
}
复制代码

我们可以看到, 不是刚体的默认属性, 也是可以被设置的.

4、Body.rotate

函数: Body.rotate = (body, rotation, point) => { }
参数:

  • body 旋转的刚体
  • rotation 旋转的角度
  • point 相对于哪一个点旋转

相对于某个给定的位置旋转一定角度, 同时自转这个给定的角度.
若没有传入point, 则只是刚体自转rotation.
若传入point, 则相对于point旋转rotation, 同时刚体也自转rotation.

举个例子?:
矩形相对于{x: 200,y: 100}旋转30 * Math.PI / 180弧度.

/** 创建矩形刚体 */
var _rect = Bodies.rectangle(100, 100, 50, 50, {
    isStatic: true,
});
Composite.add(world, _rect);
Body.rotate(_rect, 30 * Math.PI / 180, {
    x: 200,
    y: 100
});
复制代码

如果rotation为0, 即使传入了point, 物体也没有移动或者旋转哦.

我们来看下matterJs是怎么处理的呢?

if (!point) {
    Body.setAngle(body, body.angle + rotation);
} else {
    var cos = Math.cos(rotation),
        sin = Math.sin(rotation),
        dx = body.position.x - point.x,
        dy = body.position.y - point.y;
    Body.setPosition(body, {
        x: point.x + (dx * cos - dy * sin),
        y: point.y + (dx * sin + dy * cos)
    });
    Body.setAngle(body, body.angle + rotation);
}
复制代码

可以看出, 我们是先设置了position, 再设置了angle.

5、 Body.scale

函数: Body.scale = (body, scaleX, scaleY, point) => { }
参数:

  • body 刚体
  • scaleX X方向缩放系数
  • scaleY Y方向缩放系数
  • point 缩放中心, 锚点

如果不给定缩放中心, 则默认是刚体的中心(重心).
举个例子?:
下面的矩形相对于{x: 200,y: 100}点x, y轴个缩放了50%.

var _rect = Bodies.rectangle(100, 100, 50, 50, {
    isStatic: true,
});
Composite.add(world, _rect);
Body.scale(_rect,0.5,0.5,{x: 200,y: 100});
复制代码

我们可以看到, 缩放的时候重新定义了刚体的顶点规则, 同时质量、惯性等属性也随之改变.

// scale vertices
Vertices.scale(part.vertices, scaleX, scaleY, point);
// update properties
part.axes = Axes.fromVertices(part.vertices);
part.area = Vertices.area(part.vertices);
Body.setMass(part, body.density * part.area);
// update inertia (requires vertices to be at origin)
Vertices.translate(part.vertices, { x: -part.position.x, y: -part.position.y });
Body.setInertia(part, Body._inertiaScale * Vertices.inertia(part.vertices, part.mass));
Vertices.translate(part.vertices, { x: part.position.x, y: part.position.y });
复制代码

6、Body.update

函数: Body.update = (body, deltaTime, timeScale, correction) => { }
参数:

  • body 刚体
  • deltaTime 每次更新的时间间隔 帧间隔, 16.67ms
  • timeScale 时间比例系数 0 冻结, 大于1 加速 小于 1 慢动作
  • correction 修正系数, 默认1

这个方法我们一般不主动调用, 它会在Engine.update中被调用.
主要用来更新刚体的位置、角度、速度等物理信息和几何变换.

7、Body.applyForce

函数: Body.applyForce = (body, position, force) => { }
参数:

  • body 刚体
  • position 施力位置 vector
  • force 施力的大小 vector

向刚体的position位置施加大小为force的力.force分为x和y方向上的分量.

举个例子?:
我们对刚体_rect施加一个力{ x: 0.1, y: 0.1 },物体会沿一条角度为45度的斜线移动.

var _rect = Bodies.rectangle(100, 100, 50, 50, {
    isStatic: false,
    mass: 1
});
Composite.add(world, _rect);
Body.applyForce(_rect,{x: 0,y: 0},{x: 0.1,y: 0.1});
复制代码

注意, 我们施加力的刚体或者复合体, 它的isStatic不能为true, 不能是静态刚体.
静态刚体不受力.
刚体受力的同时会产生力矩.

applyForce方法是如何生效的呢?

body.force.x += force.x;
body.force.y += force.y;
var offset = { x: position.x - body.position.x, y: position.y - body.position.y };
body.torque += offset.x * force.y - offset.y * force.x;
复制代码

至此, 刚体的主要属性和方法我们就讲完了.
单个刚体在matterJs中因为具备了一系列的属性和方法, 我们可以从容的操作他们做出一些有意思的场景.
那我们如何获得一些特殊的图形呢?
matterjs给我们提供了一个Bodies的工厂方法, 用于生成各种形状.

三、刚体的生产车间-Bodies

前面我们讲了一个单独的刚体需要具备的属性和方法.
有了核心技术, 我们就要开始生产正式的产品了, Bodies为我们提供了几种常用的模具(形状).
所有的形状都是通过Body.create()生成的.

1、矩形

函数: Bodies.rectangle = (x, y, width, height, options) => { }
参数:

  • x — x位置
  • y — y位置
  • width 宽度
  • height 高度
  • options 配置

矩形的具形方法:

Vertices.fromPath('L 0 0 L ' + width + ' 0 L ' + width + ' ' + height + ' L 0 ' + height)
复制代码

举个例子?:

/** 创建一个矩形 **/
var _rect = Bodies.rectangle(100, 100, 50, 50, {
    isStatic: false,
});
Composite.add(world, _rect);
复制代码

2、梯形

函数: Bodies.trapezoid = (x, y, width, height, slope, options) => { }
参数:

  • slope 斜率

梯形的具形方法:

if (slope < 0.5) {
    verticesPath = 'L 0 0 L ' + x1 + ' ' + (-height) + ' L ' + x2 + ' ' + (-height) + ' L ' + x3 + ' 0';
} else {
    verticesPath = 'L 0 0 L ' + x2 + ' ' + (-height) + ' L ' + x3 + ' 0';
}
var trapezoid = { 
    label: 'Trapezoid Body',
    position: { x: x, y: y },
    vertices: Vertices.fromPath(verticesPath)
};
复制代码

举个例子?:

/** 创建一个梯形 **/
var _trapezoid = Bodies.trapezoid(100, 100, 50, 50,0.2, {
    isStatic: true,
});
Composite.add(world, _trapezoid);
复制代码

3、圆形

函数: Bodies.circle = (x, y, radius, options, maxSides) => { }
参数:

  • radius 半径
  • maxSides 最大边

圆形是如何具形的呢?
它其实是使用的多边形的具形方法.

Bodies.polygon(x, y, sides, radius, Common.extend({}, circle, options))
复制代码

matterJs中的圆形其实是多边形, 最小10条边, 默认25边.

举个例子?:

/** 创建一个圆形 **/
var _cricle = Bodies.circle(50, 50, 50, {
    isStatic: true,
},80);
Composite.add(world, _cricle);
复制代码

有意思的是: matterJs有打算做一个真正的圆.

// TODO: true circle bodies
复制代码

4、多边形

函数: Bodies.polygon = (x, y, sides, radius, options) => { }
参数:

  • sides 边数
  • radius 半径

多边形是如何具现的呢?

for (var i = 0; i < sides; i += 1) {
    var angle = offset + (i * theta),
        xx = Math.cos(angle) * radius,
        yy = Math.sin(angle) * radius;
    path += 'L ' + xx.toFixed(3) + ' ' + yy.toFixed(3) + ' ';
}
var polygon = { 
    label: 'Polygon Body',
    position: { x: x, y: y },
    vertices: Vertices.fromPath(path)
};
复制代码

一系列的路径绘制, 如果边数sides小于3, 绘制的就是圆形.
举个例子?:
绘制了一个三角形.

/** 创建一个多边形**/
var _polygon = Bodies.polygon(50, 50, 3, 20, {
    isStatic: true,
});
Composite.add(world, _polygon);
复制代码

5、自定义形状

函数: Bodies.fromVertices = (x, y, vertexSets, options, flagInternal, removeCollinear, minimumArea, removeDuplicatePoints) => {}
参数:

  • vertexSets 一个或者多个顶点数组
  • flagInternal 是否具有内部标识
  • removeCollinear 0.01 简化同一边的时候的阈值
  • minimumArea 10 移除小部分时的阈值
  • removeDuplicatePoints 0.01 简化附近的点时的阈值

flagInternal 为true, 无内部标识, render时可以看出具有线形

具形方法有兴趣的可以自行阅读源码.

举个例子?:
我们来画一个五角星.

/**  自定义形状  **/
Common.setDecomp(decomp);
var star = Vertices.fromPath('50 0 63 38 100 38 69 59 82 100 50 75 18 100 31 59 0 38 37 38');
var _custom = Bodies.fromVertices(200, 200, star,{
    isStatic: true
},true,0.1,10);
Composite.add(world, _custom);
复制代码

五角星一共需要10个点.
decomp是一个插件, 用于绘制自定义形状.我这里直接用script引用.
使用一系列点位来形成一个封闭的形状.

四、总结

本章节介绍了matterJs中的刚体, 包括它的属性和方法.
需要注意的是每个刚体都有其独一无二的id. 同时拥有一些相似和不相似的属性.
我们可以使用Bodies工厂方法绘制一些具有形状的刚体:

  • 矩形
  • 梯形
  • 多边形
  • 圆形
  • 一些自定义形状

我们还可以对刚体进行一些操作:

  • 设置基础属性
  • 设置碰撞规则
  • 施加力来移动刚体

同时我们需要注意:

  • 静态刚体是不受任何物理作用的
  • 可以通过设置刚体更新来达到某些效果, 比如冻结、匀速, 还可以防止卡顿.

下一章我们将继续介绍matterJs中的物体, 如何去创建一个复杂的复合体. 以及如何去使用复合体搭建复杂的物理世界.

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享