Search…

Box2D - Phần 4: Joint (tiếp Theo)

30/09/202010 min read
Giới thiệu các loại joint có sẵn trong Box2D, các khái niệm, vai trò, và ví dụ cụ thể.

Bài viết giới thiệu các loại joint sau.

  • Mouse joint.
  • Revolue joint.
  • Pulley joint

Để giới thiệu về các joint này, tôi sử dụng cocos2d-x 3.4. Việc đầu tiêu là khởi tạo một project. Ở đây, sau khi khởi tạo sẽ giữ nguyên các tên file có sẵn trong project.

Thiết lập project

Tại file HelloWorldScene.h:

#include "Box2D/Box2D.h"

#define PTM_RATIO 32  
#define PI        3.141592653589793

Trong class HelloWorld thêm các thuộc tính.

private:
    b2Body* groundBody;
    b2Body* m_bodyA;
    b2Body* m_bodyB;
    b2Fixture* m_bodyAFixtures;

    b2MouseJoint* m_mouseJoint;
    b2RevoluteJoint* m_revoluteJoint;
    b2PulleyJoint* m_pylleyJoint;

Phương thước tạo bức tường bao quanh màn hình.

void HelloWorld::createWall() {
    b2BodyDef groundBodyDef;

	groundBodyDef.position.Set(screenSize.width / 2 / PTM_RATIO,
		screenSize.height / 2 / PTM_RATIO);

	groundBody = world->CreateBody(&groundBodyDef);

	b2PolygonShape groundBox;

	// bottom
	groundBox.SetAsBox(screenSize.width / 2 / PTM_RATIO, 0, b2Vec2(0, -screenSize.height / 2 / PTM_RATIO), 0);
	groundBody->CreateFixture(&groundBox, 0);

	// top
	//groundBox.SetAsBox(screenSize.width / 2 / PTM_RATIO, 0, b2Vec2(0, screenSize.height / 2 / PTM_RATIO), 0);
	//groundBody->CreateFixture(&groundBox, 0);

	// left
	groundBox.SetAsBox(0, screenSize.height / 2 / PTM_RATIO, b2Vec2(-screenSize.width / 2 / PTM_RATIO, 0), 0);
	groundBody->CreateFixture(&groundBox, 0);

	// right
	groundBox.SetAsBox(0, screenSize.height / 2 / PTM_RATIO, b2Vec2(screenSize.width / 2 / PTM_RATIO, 0), 0);
	groundBody->CreateFixture(&groundBox, 0);
}

Công việc tiếp theo là:

  • Tạo một physics world.
  • Thêm tính năng debug draw để vẽ hình dạng body của các đối tượng.
  • Thêm touchs event.

Mouse Joint

Là loại joint cho phép bạn có thể dùng chuột (win32) hay touch (thiết bị di động cảm ứng) vào world physics để tác động vào thế giới đó. Có tác dụng giúp tương tác với world physics đó. Ví dụ: Có một vật thể hình hộp, tôi muốn click chuột vào body của đối tượng và kéo nó di chuyển,... Sẽ cần dùng đến mouse joint.

Thiết lập và khởi tạo

Trong file HelloWorldScene.h thêm một phương thức mouseJoint(). Công việc là tạo ra một đối tượng có body là hình hộp.

void HelloWorld::mouseJoint() { 
    b2BodyDef bodyDef;
    bodyDef.type = b2_dynamicBody;
    b2FixtureDef fixtureDef;
    fixtureDef.density = 1;

    b2PolygonShape boxShape;
    boxShape.SetAsBox(100 / PTM_RATIO, 100 / PTM_RATIO); 

    bodyDef.position.Set(400 / PTM_RATIO, 400 / PTM_RATIO);
    fixtureDef.shape = &boxShape;
    m_bodyA = world->CreateBody(&bodyDef);
    m_bodyAFixtures = m_bodyA->CreateFixture(&fixtureDef);
}

Trong phương thức onTouchBegan(), có các đoạn code như sau:

bool HelloWorld::onTouchBegan(Touch* touch, Event* event) {
    auto location = touch->getLocation();
    b2Vec2 locationWorld = b2Vec2(location.x / PTM_RATIO, location.y / PTM_RATIO);

    if (m_bodyAFixtures->TestPoint(locationWorld)) {
        b2MouseJointDef md;
        md.bodyA = m_bodyA;
        md.bodyB = groundBody;
        md.target = locationWorld;
        md.collideConnected = true;
        md.maxForce = 1000.0f * m_bodyA->GetMass();

        m_mouseJoint = (b2MouseJoint *)world->CreateJoint(&md);
        m_bodyA->SetAwake(true);
    }
    return true;
}

Phân tích

  • Dòng 2: Lấy tọa độ click chuột (touchs) ở trên màn hình.
  • Dòng 3: Chuyển đổi đơn vị từ pixel sang đơn vị của Box2D.
  • Dòng 5: Kiểm tra rằng nếu tọa độ click chuột (touchs) có trùng với body của đối tượng m_bodyA hay không. Nếu đúng thì tiến hành việc khởi tạo mouse joint. Nhiệm vụ là giữ chuột và kéo đối tượng sẽ cung cấp cho đối tượng một lực và phương là phương của việc giữ chuột.

Ở phương thức onTouchMoved() có đoạn code sau:

void HelloWorld::onTouchMoved(Touch* touch, Event* event) {
	if (m_mouseJoint == NULL) return;

	auto location = touch->getLocation();
	b2Vec2 locationWorld = b2Vec2(location.x / PTM_RATIO, location.y / PTM_RATIO);

	m_mouseJoint->SetTarget(locationWorld);
}

Sau khi bạn hoàn thành các công việc trên, click chuột là chỉ được một lần, cần phải remove mouse joint này. Tại phương thức onToucshEnd() như sau:

void HelloWorld::onTouchEnded(Touch* touch, Event* event) {
	if (m_mouseJoint) {
		world->DestroyJoint(m_mouseJoint);
		m_mouseJoint = NULL;
	}
}

Buid và Run

ss_1

Revolute joint

Một revolute joint gắn hai đối tượng bởi pinning chúng với nhau bở một điển anchor mà qua đó, các đối tượng có thể xoay quanh. Một điều cực kì thú vị ở vậy, điều này sẽ có trong ở các joint khác, revolute joint có thể được đưa ra giới hạn để các bodies có thể xoay chỉ đến một điểm nhất định,có thể đưa vào một motor để bodiess xoay ở tốc độ nhất định, với một mô-men xoắn nhất định. Các ứng dụng phổ biến cho revolute khớp bao gồm:

  • Bánh xe hoặc con lăn.
  • Dây chuyền hoặc swingbridges (bằng cách sử dụng nhiều revolute khớp).
  • Búp bê rag.
  • Cửa quay, máy phóng, đòn bẩy.

Các thuộc tính của một revolute joint:

  • localAnchorA: Điểm trong body A xung quanh mà nó sẽ xoay.
  • localAnchorB: Điểm trong body B xung quanh mà nó sẽ xoay.
  • referenceAngle: Một góc giữa các body.
  • enableLimit: Giá trị cho biết các giới hạn có được hoạt động hay không.
  • lowerAngle: Góc giới hạn dưới.
  • upperAngle: Góc giới hạn trên.
  • enableMotor: Giá trị cho biết motors được hoạt động hay không.
  • motorSpeed: Tốc độ của motors.
  • maxMotorTorque: Mô-men xoắn cho phép motors tối đa có thể sử dụng

Local anchors

Là một điểm xác định mà xung quanh nó các bodies trong revolute point sẽ quay quanh. 

  • Hệ trục tọa độ để xách định local anchors của mỗi body chính là tâm của chính body đó. Với y hướng lên trên và x hướng sang phải. Vì vậy khi xác định tọa độ local anchors mong muốn, cần phải để ý các giá trị ở góc phần tư 2, 3 và 4 vì sẽ có giá trị âm (-).
  • Local anchors không nhất thiết phải ở trong phạm vi của body.

Có thể truy cập các giá trị của local anchors bằng phương thức GetAnchorA()GetAnchorB(). Chú ý là nó sẽ trả về tọa độ có giá trị trong hệ tọa độ local body.

Reference angle

Ban đầu đặt một giá trị góc giữa bodyAbodyB, các giá trị của reference angle chỉ có giá trị khi cần sử dụng nó qua phương thức GetJointAngel(). Ví dụ:

  • Mặc định reference angle của 2 đối tượng là 0, sao một khoảng thời gian giá trị của reference angle đó là 45.
  • Khi thiết lập giá trị reference angle của 2 đối tượng là 120. sau khoảng thời gian như trên cùng với một hiện tượng như nhau có được giá trị reference angle là 165. 
 revoluteJointDef.referenceAngle = 0;

Chú ý

  • Giả sử bodyB xoay ngược chiều kim đồng hồ trong quan hệ với bodyA. Nếu cả hai bodies đã được di chuyển hoặc luân chuyển trong cùng một cách, reference angle không thay đổi vì nó đại diện cho góc tương đối giữa các bodies. 
  • Tất cả các góc trong Box2D có đơn vị là radian.

Revolute joint limits

Với những thiết lập thuộc tính cho revolute joint thì hai body được xoay quanh local anchors vô thời hạn. Nhưng revolute joint có thể cũng được đưa ra giới hạn để hạn chế phạm vi của chuyển động. Một giới hạn góc thấp và góc cao có thể được xác định. Giả sử muốn phần trong ví dụ ở trên để được hạn chế để xoay trong vòng 45 độ của nó ban đầu thiết lập góc.

  revoluteJointDef.enableLimit = true;
  revoluteJointDef.lowerAngle = -45 * PI / 180;
  revoluteJointDef.upperAngle =  45 * PI / 180; 

Giá trị mặc định cho enableLimit là false. Cũng có thể nhận được hoặc thiết lập các thuộc tính giới hạn cho một phần sau khi nó đã được tạo ra, bằng cách sử dụng các chức năng:

  void EnableLimit(bool enabled);
  void SetLimits(float lower, float upper );
  
  bool IsLimitEnabled();
  float GetLowerLimit();
  float GetUpperLimit();

Revolute joint motor

Hành vi mặc định của revolute joint là xoay mà không kháng cự. Nếu muốn kiểm soát sự chuyển động của các bodies, có thể áp dụng mô-men xoắn hoặc góc xung để xoay chúng. Cũng có thể thiết lập một phần motor mà gây ra phần để cố gắng xoay tại một vận tốc góc cụ thể. Điều này là hữu ích nếu muốn để mô phỏng một chiếc xe,..

  • Vận tốc góc được chỉ định là chỉ là một tốc độ mục tiêu, nghĩa không có bảo đảm rằng phần thực sự sẽ đạt được vận tốc đó. Bằng cách cho các motors chung một mô-men xoắn tối đa cho phép, bạn có thể kiểm soát như thế nào một cách nhanh chóng phần có thể đạt được tốc độ mục tiêu.
  • Hành vi của mô-men xoắn tác động lên các khớp là giống như trong force và impulses. 
  revoluteJointDef.enableMotor = true;
  revoluteJointDef.maxMotorTorque = 20;
  revoluteJointDef.motorSpeed = 90 * PI /  180;

Giá trị mặc định cho enableMotor là false, cũng có thể nhận được hoặc thiết lập các thuộc tính motors sau khi nó đã được tạo ra, bằng cách sử dụng các phương thức.

  void EnableMotor(bool enabled);
  void SetMotorSpeed(float speed);
  void SetMaxMotorTorque(float torque);
  
  bool IsMotorEnabled();
  float GetMotorSpeed();
  float GetMotorTorque();

Chú ý

  • Với một thiết lập mô-men xoắn tối đa thấp khớp có thể mất một thời gian để đạt được tốc độ mong muốn. Nếu thực hiện Các cơ quan được kết nối nặng hơn, sẽ cần phải tăng giá trị thiết lập mô-men xoắn tối đa nếu bạn muốn giữ cùng một tỷ lệ tăng tốc.
  • Tốc độ động cơ có thể được thiết lập bằng 0 để làm cho khớp cố gắng đứng yên. Với một thiết lập mô-men xoắn tối đa thấp, điều này hoạt động như một phanh, dần dần làm chậm khớp đầu. Với một mô-men xoắn tối đa cao nó hoạt động để ngăn chặn sự chuyển động chung rất nhanh chóng, và đòi hỏi một lực lượng lớn để di chuyển các khớp, giống như một bánh xe rỉ sét.
  • Các bánh xe lái xe của một chiếc xe hơi hoặc xe có thể được mô phỏng bằng cách thay đổi hướng và kích thước của động cơ tăng tốc độ, thường thiết lập tốc độ mục tiêu là 0 khi muốn xe được dừng lại.

Ví dụ

Trong class HelloWorld thêm một phương thức để mô phỏng revolute là revoluteJoint()

void HelloWorld::revoluteJoint() {
  b2BodyDef bodyDef;
  bodyDef.type = b2_dynamicBody;
  b2FixtureDef fixtureDef;
  fixtureDef.density = 1;

  b2PolygonShape boxShape;
  boxShape.SetAsBox(5, 1);
  b2CircleShape circleShape;
  circleShape.m_radius = 1;     
  
  bodyDef.position.Set(400 / PTM_RATIO, 400 / PTM_RATIO);
  fixtureDef.shape = &boxShape;
  m_bodyA = m_world->CreateBody(&bodyDef);
  m_bodyAFixtures = m_bodyA->CreateFixture(&fixtureDef );
  
  bodyDef.position.Set(400 / PTM_RATIO, 400 / PTM_RATIO);
  bodyDef.type = b2_staticBody;
  fixtureDef.shape = &circleShape;
  m_bodyB = m_world->CreateBody(&bodyDef);
  m_bodyB->CreateFixture(&fixtureDef);

  b2RevoluteJointDef revoluteJointDef;
  revoluteJointDef.bodyA = m_bodyA;
  revoluteJointDef.bodyB = m_bodyB;
  revoluteJointDef.collideConnected = false;
  revoluteJointDef.localAnchorA.Set(2,0);
  revoluteJointDef.localAnchorB.Set(0,0);

  //revoluteJointDef.enableLimit = true;
  //revoluteJointDef.lowerAngle = -90 * PI / 180;
  //revoluteJointDef.upperAngle =  90 * PI / 180;

  revoluteJointDef.enableMotor = true;
  revoluteJointDef.maxMotorTorque = 200;
  revoluteJointDef.motorSpeed = 100;

  m_revoluteJoint = (b2RevoluteJoint*)m_world->CreateJoint(&revoluteJointDef );
}

Ngoài ra có thể thay thuộc tính của bodyD là dynamic body, sau đó sử dụng mouse joint để xem cách mà revolute joint hoạt động rõ hơn.

Buid và run

ss_2

Pulley joint

Giống như cái tên, pulley joint được sử dụng cho việc tạo ra hệ thống ròng rọc. Ở class HelloWorld có phương thức pulleyJoint() như sau.

void HelloWorld::pullleyJoint() {      
    b2BodyDef bodyDef;
	bodyDef.type = b2_dynamicBody;
	b2FixtureDef fixtureDef;
	fixtureDef.density = 1;

	b2PolygonShape boxShape;
	boxShape.SetAsBox(1, 1);
	b2CircleShape circleShape;
	circleShape.m_radius = 1;

	bodyDef.position.Set(300 / PTM_RATIO, 200 / PTM_RATIO);
	fixtureDef.shape = &boxShape;
	m_bodyA = world->CreateBody(&bodyDef);
	m_bodyAFixtures = m_bodyA->CreateFixture(&fixtureDef);

	bodyDef.position.Set(500 / PTM_RATIO, 200 / PTM_RATIO);
	bodyDef.type = b2_dynamicBody;
	fixtureDef.shape = &circleShape;
	m_bodyB = world->CreateBody(&bodyDef);
	m_bodyB->CreateFixture(&fixtureDef);

	b2Vec2 anchor1 = m_bodyA->GetWorldCenter();
	b2Vec2 anchor2 = m_bodyB->GetWorldCenter();
	b2Vec2 groundAnchor1 = b2Vec2(anchor1.x, anchor1.y + (300 / PTM_RATIO));
	b2Vec2 groundAnchor2 = b2Vec2(anchor2.x, anchor2.y + (300 / PTM_RATIO));
	float ratio = 1.0f;

	b2PulleyJointDef pulleyJointDef;

    pulleyJointDef.Initialize(m_bodyA, m_bodyB, groundAnchor1, groundAnchor2, anchor1, anchor2, ratio);
	pulleyJointDef.lengthA = 200 / PTM_RATIO;
	pulleyJointDef.lengthB = 200 / PTM_RATIO;

	m_pylleyJoint = (b2PulleyJoint*)world->CreateJoint(&pulleyJointDef);
}

Phân tích

  • Dòng 2-21: Khởi tạo bodies cho đối tượng m_bodyAm_bodyB.
  • Dòng 22-23: Thiết lập giá trị local anchor cho m_bodyAm_bodyB.
  • Dòng 24-25: Thiết lập giá trị goundAnchor1 groundAnchor2. Đây chính là các điểm của ròng rọc.
  • Dòng 31-33: Khởi tạo và thiết lập define để tạo pulley joint. Phương thức Initialize cũng là một cách để khởi tạo một define.
  • Dòng 35: Khởi tạo pulley joint.

Build và run

ss_3

Download

JointExample

IO Stream

IO Stream Co., Ltd

30 Trinh Dinh Thao, Hoa Thanh ward, Tan Phu district, Ho Chi Minh city, Vietnam
+84 28 22 00 11 12
developer@iostream.co

383/1 Quang Trung, ward 10, Go Vap district, Ho Chi Minh city
Business license number: 0311563559 issued by the Department of Planning and Investment of Ho Chi Minh City on February 23, 2012

©IO Stream, 2013 - 2024