官术网_书友最值得收藏!

9.5 關節Joint

關節,或者叫連接器,是用來連接兩個剛體的對象,關節是一個很形象的比喻,就像人們身上的關節,如膝關節。在我們身邊還有很多這樣的例子,如可以推開的門、眼鏡的鏡架、汽車的輪子,這些把兩個物體連接在一起,又在一定范圍內可以移動或者旋轉的對象,都可以稱之為關節,關節是一種約束對象,對連接的兩個物體進行約束。Box2d提供了下面10種關節。

        enum b2JointType
        {
            e_unknownJoint,
            e_revoluteJoint,            //旋轉關節
            e_prismaticJoint,           //平移關節
            e_distanceJoint,            //距離關節
            e_pulleyJoint,              //滑輪關節
            e_mouseJoint,               //鼠標關節
            e_gearJoint,                //齒輪關節
            e_wheelJoint,               //滾輪關節
            e_weldJoint,                //焊接關節
            e_frictionJoint,            //摩擦關節
            e_ropeJoint                 //繩索關節
        };

9.5.1 使用關節

在詳細介紹每個關節之前,先來看一下在代碼中如何使用關節。創建一個關節的步驟和創建剛體十分相似,都是先填充一個關節定義結構,然后放到World中,由World來創建這個關節,每個關節都必須包含兩個剛體。

對于不同的關節,需要在userData中設置關節所需的信息,World的CreateJoint返回一個b2Joint指針,但b2Joint只是一個抽象類,World返回的b2Joint實際是一個b2DistanceJoint或b2RevoluteJoint之類的對象。

        struct b2JointDef
        {
            b2JointDef()
            {
              type = e_unknownJoint;
              userData = NULL;
              bodyA = NULL;
              bodyB = NULL;
              collideConnected = false;
          }
          ///關節的類型
          b2JointType type;
          ///特殊數據
          void* userData;
          ///第一個剛體
          b2Body* bodyA;
          ///第二個剛體
          b2Body* bodyB;
          ///兩個剛體是否會發生碰撞
          bool collideConnected;
        };

除了上面的設置,關節還有3個通用的概念。

? 錨點:與Cocos2d-x的錨點概念很相似,是運用于旋轉移動計算的一個點,如圖9-7所示。

圖9-7 錨點

? 限制:每個關節都有其限制條件,如旋轉角度、移動距離等(車輪和輪桿可以進行360°的旋轉,卻不能移動,人的大腿和小腿大約只能允許180°以內的旋轉,非正常情況不算在內)。

? 馬達:用來描述關節運動,并在物理模擬中制造關節的運動,可以是旋轉或者移動。

得到一個b2Joint對象之后,可以做哪些操作呢?可以獲取關節的信息,可以控制整個關節,如重新描述它的馬達,更新限制等。

9.5.2 旋轉關節RevoluteJoint

圖9-8 旋轉關節

如圖9-8所示,旋轉關節就像是一個點,把兩個剛體聯系起來,并且限制它們只能繞著這個點旋轉。這個點也可以是在剛體形狀以外的一個點。例如,電風扇的葉子都可以圍繞著中心的公共點旋轉。

使用旋轉關節的步驟如下,首先填充一個b2RevoluteJointDef結構體,然后調用World的CreateJoint()函數即可,我們來看一下b2RevoluteJointDef結構體。

        struct b2RevoluteJointDef : public b2JointDef
        {
            ///使用一個世界坐標作為兩個剛體的連接點,來初始化關節
            void Initialize(b2Body* bodyA, b2Body* bodyB, const b2Vec2& anchor);
            ///A對象的錨點,位于A對象的局部坐標系,是A的旋轉點
            b2Vec2 localAnchorA;
            ///B對象的錨點,位于B對象的局部坐標系,是B的旋轉點
            b2Vec2 localAnchorB;
            ///參照角度(一個旋轉90°的關節,參照角度為0時,返回的旋轉是90°,參照角度為
            10°時,返回100°)一般在設置連接器限制時,這個參數會比較有用。b2RevoluteJoint
            的GetJointAngle返回的是兩個連接器的相對角度
            float32 referenceAngle;
            ///是否啟用限制
            bool enableLimit;
            ///角度最低限制,逆時針方向
            float32 lowerAngle;
            ///角度最高限制,逆時針方向
            float32 upperAngle;
            ///是否啟用馬達
            bool enableMotor;
            ///馬達的目標速度(受限于最大扭力)
            float32 motorSpeed;
            ///馬達可以達到的最大扭力
            float32 maxMotorTorque;
        };

下面的代碼演示了如何創建一個旋轉關節。

        //添加旋轉關節
        b2RevoluteJointDef revdef;
        revdef.Initialize(boxbd, circlebd, b2Vec2(0.0f, 10.0f));
        //開啟馬達
        revdef.enableMotor = true;
        revdef.maxMotorTorque = 105;
        revdef.motorSpeed = 30.14f;
        m_world->CreateJoint(&revdef);

RevoluteJoint的Initialize()函數會使用兩個Body的錨點作為旋轉錨點,并且根據輸入的第3個參數,在世界坐標系中,根據當前兩個Body在世界坐標系的位置,作為兩個物體的公共頂點。如圖9-9和圖9-10演示了基于指定公共點的旋轉關節運動情況。

圖9-9 旋轉關節1

圖9-10 旋轉關節2

關于馬達和限制,在啟用馬達之后,第二個物體會圍繞公共點開始旋轉,馬達會達到motorSpeed的旋轉速度,這個旋轉的扭力矩不會高于maxMotorTorque。motorSpeed的單位是弧度,而maxMotorTorque的單位是N/m,一個力的單位。給馬達賦予一個正的速度,會讓其按照逆時針的方向旋轉,負的速度會讓其順時針旋轉。如圖9-11和圖9-12演示了馬達的開啟情況,在創建關節時,這里分別將四邊形和圓形作為第二個物體傳入。

圖9-11 四邊形馬達

圖9-12 圓形馬達

啟用限制可以限制旋轉的角度,可以限制一個超過360°的值,例如3600°,馬達在旋轉10圈之后,達到限制值會停下來,而手動讓其旋轉,也無法突破其限制值。

9.5.3 平移關節PrismaticJoint

平移關節可以將兩個物體之間的限制在一個特定的方向上平移,就像滑動門和垂直電梯,滑動面只可以向左右兩邊移動,而垂直電梯一般只能上下移動,平移關節阻止了物體的相對旋轉。

        //添加平移關節
        b2PrismaticJointDef pridef;
        pridef.Initialize(boxbd,  circlebd,  b2Vec2(0.0f,  20.0f),  b2Vec2(1.0f,
        0.0f));
        m_world->CreateJoint(&pridef);

b2PrismaticJointDef的Initialize()函數通過傳入兩個剛體,以及一個公共頂點,還有一個軸來初始化。公共頂點與旋轉關節的意義一樣,都是世界坐標系下的頂點。傳入的軸表示一個方向,用來限制兩個剛體的相對移動和旋轉,假設傳入的軸是(0, 0),那么兩個剛體的相對移動不受限制,但兩個物體不會產生相對旋轉,它還會令馬達和限制失效。上面的代碼為圓和正方形創建了一個只能在x軸上移動的平移關節,運行效果如圖9-13和圖9-14所示,兩個節點只能沿著相對x軸進行移動。

圖9-13 平移關節1

圖9-14 平移關節2

9.5.4 距離關節DistanceJoint

距離關節將兩個物體之間的距離保持在一定范圍內的關節。例如,用彈簧拴住兩個物體,也可以像一根棍子一樣,讓兩個物體的距離固定不變,如圖9-15所示。

圖9-15 距離關節

        //添加距離關節
        b2DistanceJointDef disdef;
        disdef.Initialize(boxbd, circlebd,
            b2Vec2(2.0f, 10.0f), b2Vec2(-2.0f, 10.0f));
        disdef.localAnchorA = b2Vec2(0.0f, 0.0f);
        disdef.localAnchorB = b2Vec2(0.0f, 0.0f);
        //震動頻率
        disdef.frequencyHz = 2.0f;
        //阻尼率
        disdef.dampingRatio = 0.0f;
        m_world->CreateJoint(&disdef);

通過設置frequency頻率和damping ratio可以獲得柔軟的效果,frequency表示震蕩的頻率,單位是Hz,相當于游戲刷新頻率的一半,阻尼率的取值一般在0~1之間,震蕩幅度從0~1由大變小,如圖9-16是使用柔軟的距離關節做出的網的效果,可以參考TestBed的Web示例。下面這個例子的frequency取值是2.0而damping ratio取值是0。

圖9-16 使用距離關節模擬網

9.5.5 滑輪關節PulleyJoint

滑輪關節可以用來創建一個滑輪的效果,滑輪的兩端連接著兩個繩子,繩子綁著兩個物體,當一個物體上升的時候,另一個物體就下降,如圖9-17和圖9-18可以很簡單清晰地描述出這個效果。

圖9-17 滑輪關節1

圖9-18 滑輪關節2

        //添加滑輪關節
        b2PulleyJointDef puldef;
        puldef.Initialize(boxbd, circlebd,
        b2Vec2(-2, 15), b2Vec2(2, 15),         //A和B滑輪懸掛點的世界坐標
        b2Vec2(-2, 10), b2Vec2(2, 10),         //A和B滑輪掛載點的世界坐標
            0.1f);                              //滑輪線的阻尼率
        m_world->CreateJoint(&puldef);

9.5.6 鼠標關節MouseJoint

鼠標關節主要用于方便我們用鼠標來拖動物體,它會先確定物體上的一個點,對這個點施加力,使其向鼠標的位置移動。被拖曳的物體可以自由旋轉,可以為鼠標關節設置最大力矩來決定力的大小,設置頻率和阻尼率,來達到彈簧和減震器的效果。

要實現用鼠標關節來拖曳一個剛體的效果,首先需要先實現單擊到剛體的判斷,并不是添加了鼠標關節之后,這個剛體就可以被單擊了。

因為Box2d并不關注單擊事件的實現,點擊判斷這個功能需要開發者自己來實現,在Box2d里面實現這個功能很簡單,可以參考Box2d TestBed里面的Test.cpp文件,里面繼承了b2QueryCallback類,用于查詢在一個包圍盒中的剛體,實現了ReportFixture()方法,在ReportFixture()中,會傳入一個b2Fixture,如果判斷你的鼠標(或手指)在這個對象的范圍內,那么返回false,并保存選中的Fixture,這時候會終止查詢,否則返回true。

        class QueryCallback : public b2QueryCallback
        {
        public:
            QueryCallback(const b2Vec2& point)
            {
              m_point = point;
              m_fixture = NULL;
            }

            bool ReportFixture(b2Fixture* fixture)
            {
              b2Body* body = fixture->GetBody();
              if (body->GetType() == b2_dynamicBody)
              {
                  bool inside = fixture->TestPoint(m_point);
                  if (inside)
                  {
                      m_fixture = fixture;
                      //We are done, terminate the query.
                      return false;
                  }
              }

              //Continue the query.
              return true;
            }

            b2Vec2 m_point;
            b2Fixture* m_fixture;
        };

在鼠標按下的時候,需要查詢鼠標是否單擊到動態剛體,(這里的MouseDown應該由TouchBegin系列函數調用)這時候用到了World的QueryAABB()函數來進行檢測,首先在鼠標單擊的地方,構造一個非常小的碰撞盒,然后執行查詢,這時候位于這個碰撞盒之內的Fixture會被傳入到查詢對象中,在查詢對象中進行一次過濾,只查詢動態剛體,并且判斷點是否在Fixture之內,這屬于邊界情況的過濾,剛體位于這個極小的碰撞盒之內,但單擊的坐標點并沒有落在這個Fixture之內。通過這層層的過濾,在單擊到剛體之后,就可以開始創建鼠標關節了。

        void Test::MouseDown(const b2Vec2& p)
        {
            m_mouseWorld = p;

            if (m_mouseJoint ! = NULL)
            {
              return;
            }

            //Make a small box.
            b2AABB aabb;
            b2Vec2 d;
            d.Set(0.001f, 0.001f);
            aabb.lowerBound = p - d;
            aabb.upperBound = p + d;

            //Query the world for overlapping shapes.
            QueryCallback callback(p);
            m_world->QueryAABB(&callback, aabb);

            if (callback.m_fixture)
            {

              b2Body* body = callback.m_fixture->GetBody();
              b2MouseJointDef md;
              md.bodyA = m_groundBody;
              md.bodyB = body;
              md.target = p;
              md.maxForce = 1000.0f * body->GetMass();
              m_mouseJoint = (b2MouseJoint*)m_world->CreateJoint(&md);
              body->SetAwake(true);
            }
        }

要創建一個鼠標關節很簡單,將地面(或者其他靜態剛體)設置為BodyA,然后將要拖動的剛體設置為BodyB,并設置要移動到的目標點,以及最大力矩,上面代碼中是用1000乘以剛體的質量作為最大力矩,還需要將拖動的剛體的狀態設置為Awake。

9.5.7 齒輪關節GearJoint

齒輪關節用于模擬現實中的齒輪,可以用一堆形狀來描述一個齒輪,然后用旋轉關節讓這個齒輪旋轉,也可以起到這樣一個效果,但并不高效,并且在排列齒輪牙齒的時候需要小心翼翼。有了齒輪關節,就可以直接用一個齒輪關節來實現一個齒輪,如圖9-19所示。

圖9-19 齒輪關節

齒輪關節和其他關節不一樣的地方在于,它是一個依賴其他關節的關節。一般的關節只是依賴于關節連接著的兩個剛體。通過一個關節的運動,來驅動另外一個關節。如果在刪除齒輪關節之前,刪除了其他關節,那么程序將會崩潰。在釋放的時候,需要先釋放齒輪關節,然后再釋放掛載在齒輪關節上的其他關節。

下面的代碼將創建兩個齒輪關節,以及兩個旋轉關節和一個平移關節,通過齒輪關節的旋轉帶動其他關節。

        //第一個小圓
        b2CircleShape circle1;
        circle1.m_radius = 1.0f;

        //第二個大圓
        b2CircleShape circle2;
        circle2.m_radius = 2.0f;

        //長方形
        b2PolygonShape box;
        box.SetAsBox(0.5f, 5.0f);

        //小圓剛體
        b2BodyDef bd1;
        bd1.type = b2_dynamicBody;
        bd1.position.Set(-3.0f, 12.0f);
        b2Body* body1 = m_world->CreateBody(&bd1);
        body1->CreateFixture(&circle1, 5.0f);

        //小圓和靜態地面用旋轉關節連接,小圓只可以旋轉,不能移動
        b2RevoluteJointDef jd1;
        jd1.bodyA = ground;
        jd1.bodyB = body1;
        jd1.localAnchorA = ground->GetLocalPoint(bd1.position);
        jd1.localAnchorB = body1->GetLocalPoint(bd1.position);
        jd1.referenceAngle = body1->GetAngle() - ground->GetAngle();
        m_joint1 = (b2RevoluteJoint*)m_world->CreateJoint(&jd1);

        //大圓剛體
        b2BodyDef bd2;
        bd2.type = b2_dynamicBody;
        bd2.position.Set(0.0f, 12.0f);
        b2Body* body2 = m_world->CreateBody(&bd2);
        body2->CreateFixture(&circle2, 5.0f);
        //大圓和靜態地面用旋轉關節連接
        b2RevoluteJointDef jd2;
        jd2.Initialize(ground, body2, bd2.position);
        m_joint2 = (b2RevoluteJoint*)m_world->CreateJoint(&jd2);

        //長方形剛體
        b2BodyDef bd3;
        bd3.type = b2_dynamicBody;
        bd3.position.Set(2.5f, 12.0f);
        b2Body* body3 = m_world->CreateBody(&bd3);
        body3->CreateFixture(&box, 5.0f);

        //平移關節限制長方形剛體的移動
        b2PrismaticJointDef jd3;
        jd3.Initialize(ground, body3, bd3.position, b2Vec2(0.0f, 1.0f));
        jd3.lowerTranslation = -5.0f;
        jd3.upperTranslation = 5.0f;
        jd3.enableLimit = true;
        m_joint3 = (b2PrismaticJoint*)m_world->CreateJoint(&jd3);

        //連接兩個圓形的齒輪關節
        //當一個小圓轉動時,另一個小圓會跟著轉動
        //小圓連接的關節旋轉一周,大圓連接的關節旋轉1/2周
        b2GearJointDef jd4;
        jd4.bodyA = body1;
        jd4.bodyB = body2;
        jd4.joint1 = m_joint1;
        jd4.joint2 = m_joint2;
        jd4.ratio = circle2.m_radius / circle1.m_radius;
        m_joint4 = (b2GearJoint*)m_world->CreateJoint(&jd4);

        //連接大圓和長方形的齒輪關節
        //當大圓所在的旋轉關節轉動時,會驅動平移關節跟著移動
        //平移關節發生移動時,也會驅動旋轉關節
        b2GearJointDef jd5;
        jd5.bodyA = body2;
        jd5.bodyB = body3;
        jd5.joint1 = m_joint2;
        jd5.joint2 = m_joint3;
        jd5.ratio = -1.0f / circle2.m_radius;
        m_joint5 = (b2GearJoint*)m_world->CreateJoint(&jd5);

圖9-20 齒輪關節

上面的代碼運行結果如下,拖動任意一個剛體,會導致關節發生平移運動或者旋轉運動,這時運動將會通過齒輪關節,傳達到另外一個關節上,按照ratio參數的比例,來賦予另外一個關節運動的能量。在設置齒輪關節的時候,最好先在腦海中想清楚,齒輪動起來是怎樣的。齒輪關節的創建本身很簡單,上面如此冗長的代碼,主要是創建了其他的關節。運行效果如圖9-20所示。

9.5.8 滾輪關節WheelJoint

滾輪關節用于模擬汽車的輪子,滾輪關節連接汽車和汽車輪子,汽車輪子可以滾動,滾輪關節還提供了一個彈簧效果,當發生車震的時候,汽車會上下震動,讀者可以想像一下一輛越野車在顛簸的路上前進,輪子在轉動,輪子和車身的距離不斷地放大、縮小,如圖9-21所示。

圖9-21 滾輪關節

滾輪關節相當于一個旋轉關節和一個帶彈簧的距離關節組合而成,如圖9-22所示的車輪子,當車子被甩起來的時候,輪子會被帶出一些,而當車子被壓下去的時候,輪子也會跟著陷下去。

圖9-22 使用滾輪關節模擬車輪

        b2WheelJointDef jd;
        //滾輪關節兩個剛體可以移動的方向是y軸,也就是上下移動
        b2Vec2 axis(0.0f, 1.0f);

        //car表示車身,wheel1和wheel2分別表示車的前后輪子,這是一輛兩輪車
        jd.Initialize(m_car, m_wheel1, m_wheel1->GetPosition(), axis);
        //順時針方向旋轉
        jd.motorSpeed = -10.0f;
        //這個輪子的馬力更大一些
        jd.maxMotorTorque = 20.0f;
        jd.enableMotor = true;
        jd.frequencyHz = 4.0f;
        jd.dampingRatio = 0.7f;
        m_spring1 = (b2WheelJoint*)m_world->CreateJoint(&jd);

        //第二個輪子
        jd.Initialize(m_car, m_wheel2, m_wheel2->GetPosition(), axis);
        jd.motorSpeed = -10.0f;
        jd.maxMotorTorque = 10.0f;
        jd.enableMotor = false;
        jd.frequencyHz = 4.0f;
        jd.dampingRatio = 0.7f;
        m_spring2 = (b2WheelJoint*)m_world->CreateJoint(&jd);

9.5.9 焊接關節WeldJoint

焊接關節嘗試限制兩個剛體之間所有的相對運動,但是焊接關節并不是很穩定,效果看起來有些柔軟,就像跳水運動員起跳時踩著的跳水板一樣。用焊接關節將一大堆動態剛體(小四邊形)依次連接起來,在受到力的作用下,搖搖晃晃,就是這種效果,如圖9-23所示。

圖9-23 焊接關節

上面是由若干小四邊形組成的一小塊板子,每兩個小四邊形都用Weld焊接關節連接起來,可以設置dampingRatio來設置它們之間連接在一起的彈性,使用方法非常簡單,設置好frequencyHz和dampingRatio,然后傳入兩個剛體,就可以直接創建焊接關節,Testbed的Cantilever很好地介紹了焊接關節的使用。

        b2PolygonShape shape;
        shape.SetAsBox(0.5f, 0.125f);

        b2FixtureDef fd;
        fd.shape = &shape;
        fd.density = 20.0f;
        //設置彈性

        b2WeldJointDef jd;
        jd.frequencyHz = 8.0f;
        jd.dampingRatio = 0.7f;

        //將所有物體連在一起
        b2Body* prevBody = ground;
        for (int32 i = 0; i < e_count; ++i)
        {
            b2BodyDef bd;
            bd.type = b2_dynamicBody;
            bd.position.Set(5.5f + 1.0f * i, 10.0f);
            b2Body* body = m_world->CreateBody(&bd);
            body->CreateFixture(&fd);

            if (i > 0)
            {
        //創建關節
              b2Vec2 anchor(5.0f + 1.0f * i, 10.0f);
              jd.Initialize(prevBody, body, anchor);
              m_world->CreateJoint(&jd);
            }

            prevBody = body;
        }

9.5.10 摩擦關節FrictionJoint

摩擦關節會制造“從上到下”的摩擦,提供了2D的角摩擦和平移摩擦,單從這句話來看,確實相當費解。想像一下,你拉著一輛車努力地向前沖,會受到一整輛車的摩擦力的影響。想像一下,你在天空中飛速翱翔,速度越快,會感覺到越強大的空氣阻力,差不多就是這種感覺了,如圖9-24所示。

圖9-24 摩擦關節

使用了摩擦關節的小方塊們,在被撞擊到的時候,會緩緩移動,下面例子的重力加速度設置為0。

        b2FrictionJointDef jd;
        jd.localAnchorA.SetZero();
        jd.localAnchorB.SetZero();
        jd.bodyA = ground;
        jd.bodyB = body;
        jd.collideConnected = true;
        jd.maxForce = mass * gravity;
        jd.maxTorque = mass * radius * gravity;

        m_world->CreateJoint(&jd);

通過設置maxForce來限制剛體移動的阻力,通過設置maxTorque來限制剛體旋轉時的阻力,具體可以參考Testbed的ApplyForce示例,Testbed是Box2d官方源碼的示例,相當于Box2d的cpp-tests(cpp-test是Cocos2d-x的示例集合,屬于Cocos2d-x的常識)。

9.5.11 繩索關節RopeJoint

繩索關節用來模擬繩子,繩子的兩端綁著兩個剛體,兩個剛體可以在繩子限定的范圍內自由地移動和旋轉,與現實世界使用的繩子沒什么兩樣,這是一根柔軟的,拉不斷的繩子,如圖9-25所示。

圖9-25 繩索關節

通過設置b2RopeJointDef的maxLength,可以限定繩子的長度,通過傳入兩個剛體,可以在這兩個剛體身上系一條繩子。

最后,在刪除的時候,必須先刪除關節,之后才可以刪除關節上的物體,否則程序會崩潰。

主站蜘蛛池模板: 司法| 遵化市| 辽中县| 华容县| 仙游县| 昂仁县| 渝北区| 都昌县| 东光县| 平江县| 蓝田县| 侯马市| 阳谷县| 黄骅市| 秦皇岛市| 扎囊县| 洮南市| 德州市| 咸阳市| 凤翔县| 巢湖市| 金堂县| 平定县| 池州市| 平潭县| 绥芬河市| 襄城县| 太康县| 湘乡市| 江油市| 驻马店市| 浦城县| 邳州市| 庆阳市| 德钦县| 淮南市| 库伦旗| 台湾省| 丘北县| 重庆市| 蚌埠市|