HappyCoding:] ChildhoodAndy

思维世界的发展,在某种意义上说,就是对惊奇的不断摆脱。——爱因斯坦

Posts match “ Chipmunk ” tag:

理解Chipmunk2D-销关节约束

| Comments

这一节让我们来理解下Chipmunk2D中的销关节约束。

首先看文档中的一些解释

cpPinJoint *cpPinJointAlloc(void)
cpPinJoint *cpPinJointInit(cpPinJoint *joint, cpBody *a, cpBody *b, cpVect anchr1, cpVect anchr2)
cpConstraint *cpPinJointNew(cpBody *a, cpBody *b, cpVect anchr1, cpVect anchr2)

a和b是被连接的两个刚体,anchr1和anchr2是这两个刚体的锚点。当关节被创建的时候距离便被确定,如果你想要设定一个特定的距离,使用setter函数来重新设定该值。

getter/setter函数

  • cpVect cpPinJointGetAnchr1(const cpConstraint *constraint)
  • void cpPinJointSetAnchr1(cpConstraint *constraint, cpVect value)
  • cpVect cpPinJointGetAnchr2(const cpConstraint *constraint)
  • void cpPinJointSetAnchr2(cpConstraint *constraint, cpVect value)
  • cpFloat cpPinJointGetDist(const cpConstraint *constraint)
  • void cpPinJointSetDist(cpConstraint *constraint, cpFloat value)

在Cocos2DX中,销关节被封装成了PhysicsJointDistance,我们先来看看该类头文件。

PhysicsJointDistance类

PhysicsJointDistance
/** Set the fixed distance with two bodies */
class PhysicsJointDistance : public PhysicsJoint
{
public:
    static PhysicsJointDistance* construct(PhysicsBody* a, PhysicsBody* b, const Point& anchr1, const Point& anchr2);
    
    float getDistance() const;
    void setDistance(float distance);
    
protected:
    bool init(PhysicsBody* a, PhysicsBody* b, const Point& anchr1, const Point& anchr2);
    
protected:
    PhysicsJointDistance() {}
    virtual ~PhysicsJointDistance() {}
};

PhysicsJointDistance这个名字和销关节的工作机制还是很贴切的。(在Box2D中也有距离关节,b2DistanceJoint)。当关节被创建的时候,刚体a和刚体b的锚点距离就被定了下来。假如后面我们要对该距离进行修改,可以通过setDistance()方法来设定距离值。当设定的值不等于刚体之前的锚点间距时,我们会发现画面上刚体锚点的间距会发生突变,那是Chipmunk在按照你新设定的间距在修正。

1.注意:anchr1anchr2针对的是刚体a和刚体b的局部坐标系。

2.注意:PhysicsJointPin可不是销关节,其实是枢轴关节(PivotJoint)!PhysicsJointDistance才是我们这里说的销关节。

为了更好的理解销关节的工作机制,我做了几个演示来说明下:

图示1

图示2(多个销关节对绳索的模拟)

图示3

图示3 Github地址

详解Cocos2DX中Chipmunk碰撞过滤

| Comments

这节让我们来一起探讨下Chipmunk对碰撞过滤(collision filtering)的处理。碰撞过滤,顾名思义,就是要筛选出发生碰撞的一些刚体,将不会发生碰撞的刚体过滤出去,从而在后续回调中对碰撞进行处理。比如《AngryBird》里面,小鸟和箱子碰撞后,小鸟羽毛飞散、死亡,箱子爆破等的处理。

很多人更熟悉Box2D,为了更好的理解碰撞过滤,让我们先看瞅瞅Box2D是怎么实现碰撞过滤的,然后过渡到Chipmunk。

1.Box2D 碰撞过滤实现机制

在Box2D中,通过标志位和掩码的设计来实现碰撞过滤。其中有两个标志位和一个组别索引,分别是

  • categoryBits 类别标志位
  • maskBits 掩码标志位
  • groupIndex 组别索引

这三个属性在碰撞过滤机制中扮演着重要的角色。

过滤规则

  • 如果两个形状材质的组别索引相同为0,使用类别和掩码计算规则来确定是否碰撞
  • 如果两个形状材质的组别索引相同为正数,则直接确定为碰撞
  • 如果两个形状材质的组别索引相同为负数,则直接确定为不碰撞
  • 如果两个形状材质的组别索引不相同,使用类别和掩码计算规则来确定是否碰撞

额外的一些规则

  • 静态刚体的形状永远不会与其他静态刚体的形状发生碰撞
  • 同一刚体上的形状永远不会发生碰撞
  • 可以选择性的启用或者禁止被关节约束的刚体形状之间的碰撞

注:组别索引的过滤筛选要比类别和掩码标志位过滤筛选具有更高的优先级。

player1ShapeDef.filter.groupIndex = 1
player2ShapeDef.filter.groupIndex = 1
player3ShapeDef.filter.groupIndex = 2
player4ShapeDef.filter.groupIndex = -3
player5ShapeDef.filter.groupIndex = -3
player6ShapeDef.filter.groupIndex = 0
player7ShapeDef.filter.groupIndex = 0

根据上面的规则,我们知道

  • player1与player2碰撞
  • player4与player5不碰撞
  • player1与player3,player3与player4,player5与player7等等这些组别索引不同的形状材质,则要进一步根据类别和掩码计算来确定是否碰撞,后面我们马上会看到。
  • player6与player7组别索引相同为0,也要进一步根据类别和掩码计算来确定是否碰撞

类别标志位与掩码标志位的计算

Box2D支持16个类别,我们对于任何一种形状材质都可以设定类别标志位。通常我们可以用一个16进制来表示一个类别标志位,一共16位。比如0x0004,展开其实就是0x0000 0000 0000 0100

举个例子:

playerShapeDef.filter.categoryBits  = 0x0001
playerShapeDef.filter.maskBits      = 0x0002
monsterShapeDef.filter.categoryBits = 0x0002
monsterShapeDef.filter.maskBits     = 0x0001

计算规则:

  • 材质形状A的类别标志位材质形状B的掩码标志位进行"按位与"运算得到结果r1
  • 材质形状B的类别标志位材质形状A的掩码标志位进行"按位与"运算得到结果r2
  • r1与r2进行“逻辑与”,如果为true,则形状材质A与形状材质B则碰撞,false则不碰撞

我们根据上述规则得出结论,player与player之间不会碰撞,monster与monster之间也不会碰撞,但player与monster之间会发生碰撞。

2. Chipmunk2D 碰撞过滤实现

在Chipmunk中,一个shape具有grouplayer的属性,一起来看下在cpSpaceStep.c中的一个检测函数queryReject,即查询否定拒绝。

static inline cpBool
queryReject(cpShape *a, cpShape *b)
{
    return (
        // BBoxes must overlap
     !cpBBIntersects(a->bb, b->bb)
        // Don't collide shapes attached to the same body.
     || a->body == b->body
        // Don't collide objects in the same non-zero group
     || (a->group && a->group == b->group)
        // Don't collide objects that don't share at least on layer.
     || !(a->layers & b->layers)
        // Don't collide infinite mass objects
     || (a->body->m == INFINITY && b->body->m == INFINITY)
    );
}

根据上面的一些否定情况,我们总结出过滤规则:

  • 形状a与形状b的轴对齐包围盒如果没有发生碰撞,则不可能碰撞
  • 如果形状a和形状b同属于同一个刚体,则不会碰撞
  • 如果形状a和形状b在相同的非0组,则不会碰撞,同在0组,或者不相等则考虑碰撞
  • 如果形状a的层和形状b的层的按位与运算为0,即意味着不在一个“位面”上,则不会碰撞
  • 如果形状a和b从属的刚体的质量无限大,则不可能碰撞

这个是Chipmunk2D里面的碰撞机制,看起来和Box2D不太一样,啊哈?Cocos2dX对物理引擎进行了封装,碰撞过滤的实现和这里的方式却有所不同。封装的碰撞过滤接近了Box2D碰撞过滤的思路。让我们再来看下。

CCPhysicsShape/CCPhysicsBody类里有三个重要的属性,分别是

  • categoryBitmask

类别掩码,该掩码定义了刚体形状属于的类别。Chipmunk支持32种类别。通过对刚体或刚体形状设定categoryBitmask与contactTestBitmask,将两者按位与运算,我们便可以指定游戏中的哪些刚体之间可以有相互作用,并在相互作用后并进行后续的通知。(该通知直接影响到preSolve、postSolve、seperate等回调是否被调用)

默认值为0xFFFFFFFF

注意:相互作用并不等于就会产生碰撞反应,如传感器(sensor)就是一例。

  • contactTestBitmask

接触测试掩码,该掩码定义了哪些类别的刚体可以与本刚体(或刚体形状)产生相互作用。在物理空间中,每个刚体的类别掩码(categoryBitmask)会和其他刚体的接触测试掩码(contactTestBitmask)进行按位与运算,如果结果为非0值,便会产生一个PhysicsContact对象,并作为参数传入到physics world的代理方法内。为了性能考虑,我们只会设定我们关注的相互作用的的掩码。

默认值为0x00000000

  • collisionBitmask

碰撞掩码,该掩码定义了哪些类别的刚体可以与本刚体(或刚体形状)发生碰撞。当刚体彼此接触的时候,可能会发生碰撞反应。此时该刚体的碰撞掩码(collisionBitmask)会与另外一个刚体的类别(categoryBitmask)进行按位与运算,如果结果为非0值,该刚体就会受到碰撞影响。每个刚体都可以选择是否要受到碰撞影响。例如,你可以通过设定碰撞掩码来避免碰撞计算带来的刚体速度的改变。

默认值为0xFFFFFFFF

另外值得一提的是,封装后的CCPhysicsShape和CCPhysicsBody的group属性和Chipmunk2D的group对过滤规则的影响不一样!!!这里要注意下。上面总结的第三条是Chimunk2D的group的过滤规则,但在Cocos2DX封装之下的group,却采取了和Box2D一样的group过滤规则,即

  • 如果两个形状材质的组别索引相同为正数,则直接确定为碰撞
  • 如果两个形状材质的组别索引相同为负数,则直接确定为不碰撞
  • 组别索引的过滤筛选要比掩码过滤筛选具有更高的优先级。

之前我以为这是官方的一个bug,提过一个Issule给官方团队,见这里https://github.com/cocos2d/cocos2d-x/pull/6148。官方解释的原因是对物理引擎的封装要隐藏掉具体的使用哪个引擎的细节,而更关心的是友好的api,性能和功能性,另外一方面是对于有SpriteKit开发经验的开发者要更友好点。解释可以接受,但感觉怪怪的,这里的封装建构在Chipmunk2D之上,但group的过滤却是Box2D的规则。换个角度想,如果不叫group,或许更好接受点。

关于在Cocos2DX v3.x里面如何理解Chipmunk2D的碰撞过滤,可以参考这个简单的demo

思考:为什么ball1与ball2不碰撞,box1与ball1、ball2不碰撞,box2与ball1、ball2碰撞?改变他们的group会怎么样?对他们的一些掩码重新赋值会怎么样?朋友们可以尝试着设定不同的掩码来观察,方便理解其中的规则。

欢迎朋友们关注这个基础概念demo的项目,在学习过程的测试demo可以提交个pull request过来,一起来丰富这个项目。

参考

欢迎朋友们交流,HappyCoding:)

探讨Chipmunk2D碰撞查询

| Comments

探讨Chipmunk2D碰撞查询

先借用《游戏引擎架构》一书中的一段话,来解释下物理引擎中的碰撞查询是用来干啥的。

碰撞检测的另一任务是回答有关游戏世界中碰撞体积的假想问题。例如:
1)若从玩家武器的的某方向射出子弹,若能击中目标,那目标是什么?
2)汽车从A点移动至B点是否会碰到任何障碍物?
3)找出玩家在给定半径范围内的所有敌人对象。
一般而言,这些操作成为碰撞查询(collision query)。最常用的查询类型是碰撞投射(collision cast),或简称作投射(cast)。投射用于判断,若放置某假想物体于碰撞世界,并沿光线或线段移动,是否会碰到世界中的物体。投射与正常的碰撞检测操作有别,因为投射的实体并不真正存在于碰撞世界,它完全不会影响世界中的其他物体。这就是为什么我们称,碰撞投射是回答关于世界中碰撞体的假想问题。

在阅读本文前,朋友们可以先瞅下官方文档中对碰撞查询的一些相关说明。

【Chipmunk2D中文手册】 猛击这里

1.最近点查询

最近点查询允许你检查离给定点一定距离内是否存在着形状,找到形状上离给定点最近的点或者找到离给定点最近的形状。

(截图来自ChipmunkDEMO中的Segment Query)

(截图来自cocos2dx3.0 demo中的RayCast 模式:any(任意))

(截图来自cocos2dx3.0 demo中的RayCast 模式:nearest(最近点))

(截图来自cocos2dx3.0 demo中的RayCast 模式:multiple(多个点))


未完待续

PhysicsEditorExporter for QuickCocos2dx 使用说明

| Comments

PhysicsEditorExporter for QuickCocos2dx 使用说明

前言

写这篇文章的时候,我用的是quick-cocos2d-x 2.2.1rc版本,最近quick将要升级2.2.3,但不影响使用。

在quick-cocos2d-x引擎目录的tool文件夹内,有个physics_editor_exporter,这东西是干嘛用的呢?

用过PhysicsEditor的人可能清楚,这个软件可以对图形进行顶点数据编辑,然后选择目标引擎导出数据(下图序号第三位置处)。

目前默认支持的引擎都有Cocos2D,Corona,Sparrow,LibGDX,AndEngine,Moai,Box2d/Nape-flash等。我们可以自定义导出数据格式,来让软件导出。而Physics_editor_export for quick-cocos2d-x就是在Corona模板(该引擎采用lua语言进行开发,导出的数据为.lua格式)的基础上进行修改而成。

使用方法

打开PhysicsEditor的安装目录,找到Resources/exporters,发现里面正是各个引擎的数据导出模板,我们将quick-cocos2d-x/tool/physics_editor_exporter目录下的quick_chipmunk文件夹拷贝进该目录,然后重启PhysicsEditor,就可以看到导出引擎数据列表中已经有了quick-cocos2d-x chipmunk这么一项。

关于如何使用PhysicsEditor这里不做介绍了,google下,关于如何使用的文章蛮多。

编辑好我们的图片顶点,并且设置合适的参数(包括弹性、摩擦力、质量等参数)后,我们选择Publish As选择目录导出我们的数据。(选择quick-cocos2d-x chipmunk,默认会保存为.lua)

读者可以打开保存的.lua进行查看,感兴趣的可以查看Physics_editor_export的模板与导出的.lua之间是如果对应解析导出的。这项工作由软件完成。

廖大的这个版本有点小问题,我进行了一些修改并附带了一个sample。

  1. 模板中的将要导出的fixture字段修改为更适合Chipmunk引擎的shape字段
  2. 修正了多边形定点按照scaleFactor计算的bug

Github地址:https://github.com/ChipmunkCommunityCN/quick-x_physics_editor_exporter

Screenshot:

sample说明

  • res/fruits.pes为PhysicsEditor工程文件
  • res/fruitsPhysicsData.lua为导出的数据文件
  • 西瓜、草莓、菠萝、葡萄我在PE设置了碰撞类型依次为1、2、3、4,并在MainScene.lua中对类型1、2的碰撞进行了监听,如果发生碰撞,会将他们移除掉。

额外的说明

  1. 多边形顶点数据必须是按照顺时针
  2. Chipmunk不支持凹多边形,只支持凸多边形

以上两点都跟物理引擎其中涉及到的算法相关,这里不做深究。

这两点不用担心,PhysicsEditor都为我们做好了工作,当我们编辑的图形顶点形成的是凹多边形时,它会将之计算拆分为若干的凸多边形组合,导出的数据完全满足上面提到的两条。朋友们如果看导出的.lua文件会发现,polygons里面如果有若干数组的话,正是凹多边形拆分而来的凸多边形数据。

最后

如果有问题,欢迎留言,HappyCoding:)