Search…

Làm Sao Để Chia Tách, Cắt Một Đối Tượng với Box2D?

30/09/20208 min read
Hướng dẫn làm sao để chia tách, cắt một đối tượng với thư viện vật lý trò chơi Box2D trong Cocos2d-x.

Các bước được thực hiện trong bài viết:

  • Tạo ra một đối tượng.
  • Tạo ra một tia cắt (laser).
  • Cắt đối tượng đó ra làm hai (object1, object2).

Những kiến thức cần có:

  • Physics World.
  • Bodies.
  • Ray Casting.
ss_1

Hiện thực

Tạo ra tia cắt (Laser)

Để tạo ra tia cắt (laser) cần sử dụng Touches Event:

#define PTM_RATIO 32

void HelloWorld::onTouchEnded(Touch* touch, Event* event) { }

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

void HelloWorld::onTouchMoved(Touch* touch, Event* event) {
	auto location = touch->getLocation();
	position2.x = location.x / PTM_RATIO;
	position2.y = location.y / PTM_RATIO;
}

Phân tích

Tia cắt (laser) sẽ có hai điểm đầu và cuối:

  • position1: Điểm đầu, được xác định khi có Event Touch Began.
  • position2: Điểm cuối, được xác định khi có Event Touch Move.

Tiếp đến tạo ra nó, cần sử dụng Ray Casting để tạo ra một tia cắt.

debugDraw->DrawSegment(position1, position2, b2Color(0.8f, 0.8f, 0.8f));

if ((position1.x != position2.x) && (position1.y != position2.y))
{
	RayCastClosestCallback callback;
	m_world->RayCast(&callback, position1, position2);

	RayCastClosestCallback callback2;
	m_world->RayCast(&callback2, position2, position1);
}

Phân tích

  • Dòng 1: Vẽ lên màn hình một đoạn thẳng với hai điểm position1position2.
  • Dòng 3-10: Tạo ra 2 Ray Casting.

Tạo sao lại cần tạo ra 2 Ray Casting:

  1. Vì mỗi Ray Casting chỉ cắt đối tượng tại một điểm gần position1 của nó nhất. Nhưng khi cắt đối tượng cần tới 2 điểm cắt, vì vậy phải cần tới 2 Ray Casting.
  2. Với Ray Casting đầu tiên là position1 và position2 và Ray Casting thứ hai là position2position1.

Chia tách, cắt đối tượng.

Thực hiện với đoạn code sau:

void HelloWorld::update(float dt)
{
#define PTM_RATIO 32 // Dùng để chuyển đổi đơn vị Box sang Pixel và ngược lại
/* Một hàm tính toán xác định hướng của một điểm với một đường thẳng (đoạn thẳng)
điều này sẽ nói rõ hơn ở phía dưới. */
#define Calulate_Determinant(x1,y1,x2,y2,x3,y3) x1*y2 + x2*y3 + x3*y1 - y1*x2 - y2*x3 - y3*x1

//** Đoạn này có lẽ quen thuộc, chỉ là cập nhật cho Physics Wolrd trong games của bạn   
	int positionIterations = 10;  
	int velocityIterations = 10; 

	float deltaTime = dt; 

	std::vector<b2Body *>toDestroy;
	m_world->Step(dt, velocityIterations, positionIterations);

	for (b2Body *body = m_world->GetBodyList(); body != NULL; body = body->GetNext()) {
		if (body->GetUserData()) {
			Sprite *sprite = (Sprite *)body->GetUserData();
			sprite->setPosition(Point(body->GetPosition().x * PTM_RATIO, body->GetPosition().y * PTM_RATIO));
			sprite->setRotation(-1 * CC_RADIANS_TO_DEGREES(body->GetAngle()));
		} // end if
		m_world->ClearForces();
		m_world->DrawDebugData();
	} // end for
////////////////////////////////////////////////////////////////////////////////////////

	// Vẽ tia cắt (laser) lên màn hình.
	debugDraw->DrawSegment(position1, position2, b2Color(0.8f, 0.8f, 0.8f));

	if (cuting == true)
	{

/*Ở phần trên, đã tạo một đối tượng có tên box và là hình hộp. Bây giờ khi chia
 tách, cắt thì điều cần làm đầu tiên là phải xác định hình dạng cụ thể của nó là gì? */
		b2Fixture *originalFixture = box->GetFixtureList();

/*Các đối tượng mà được chia tách, cắt thì phải là một hình đa giác, sẽ phân tích rõ 
 ở phần dưới */
		b2PolygonShape *originalPolygon = (b2PolygonShape*)originalFixture->GetShape();

		// Lấy số lượng điểm (đỉnh) tạo thành đa giác, ở đây là box.
		int vertexCount = originalPolygon->GetVertexCount();

//
		if ((position1.x != position2.x) && (position1.y != position2.y))
		{
			// Tạo ra tia Ray Casting đầu tiên.
			RayCastClosestCallback callback;
			m_world->RayCast(&callback, position1, position2);

			// Tạo ra tia Ray Casting thứ hai.
			RayCastClosestCallback callback2;
			m_world->RayCast(&callback2, position2, position1);

/* Điều kiện là tia cắt (laser) phải cắt đối tượng.
Có nghĩa là hai tia Ray Casting được tạo ở trên sẽ phải có cắt đối tượng, lúc này hàm 
callback1 và callback2 được xác định cùng lúc. */
			if (callback.m_hit && callback2.m_hit) 
			{

/*  Tia cắt (laser) căt đối tượng tại 2 điểm, 2 điểm này chính là tạo độ của fration của 
Ray Casting đầu tiên và Ray Casting thứ hai với đối tượng Box. Ở mỗi điểm vẽ lên 
đó một hình tròn màu đỏ */
				debugDraw->DrawCircle(callback.m_point, 0.3f, b2Color(1.5f, 0.0f, 0.0f));
				debugDraw->DrawCircle(callback2.m_point, 0.3f, b2Color(1.5f, 0.0f, 0.0f));

/* Cần một nơi để lưu trữ các điểm(đỉnh) dùng để tạo thành hình dạng cho bodies khi
đối tượng box được cắt ra thành object1 và object2 */
				int vertexCountOfObject1 = 0;
				int vertexCountOfObject2 = 0;

				b2Vec2 *vertexOfObject1 = (b2Vec2*)calloc(24, sizeof(b2Vec2));
				b2Vec2 *vertexOfObject2 = (b2Vec2*)calloc(24, sizeof(b2Vec2));

/* 2 điểm đầu tiên của mỗi đối tượng phải là tạo độ của các fraction của Ray Casting với
đối tượng (box). Ở ví dụ này chính là điểm E và điểm F. Vì khi tạo một đối tượng có hình
dạng đa giác với một tập điểm ban đầu, bạn cần phải sắp xếp theo thứ tự ngược kim đồng hồ
vậy nên cần sắp xếp 2 điểm (đỉnh) ban đầu như ở dưới */
				b2Vec2 fraction1 = callback.m_point;
				b2Vec2 fraction2 = callback2.m_point;
				vertexOfObject1[vertexCountOfObject1++] = fraction1;
				vertexOfObject1[vertexCountOfObject1++] = fraction2;
				vertexOfObject2[vertexCountOfObject2++] = fraction2;
				vertexOfObject2[vertexCountOfObject2++] = fraction1;
/////////////////////////////////////////////////////////////////////////////////////////

/*Tiếp đến phân loại các điểm (đỉnh) của đối tượng bị cắt (box), cần phải biết điểm
(đỉnh) nào cần để tạo hình dạng body cho object1 và object2.
Sử dụng một phép tính toán giá trị determinant để kiểm tra và phân loại.*/
				for (int index = 0; index < vertexCount; index++)
				{
					b2Vec2 point = originalPolygon->GetVertex(index);

/*Nếu điểm (đỉnh) hiện tại mà bạn đang kiểm tra mà là điểm cắt của tia cắt (laser) với đối tượng
thì không cần phải kiểm tra nữa*/
					if (point == fraction1 || point == fraction2)
					{
						continue;
					}
					//
					else {
						float determinant;
						point.x = box->GetPosition().x + point.x;
						point.y = box->GetPosition().y + point.y;
						determinant = calculate_determinant_2x3(position1.x, position1.y, position2.x, position2.y, point.x, point.y);

/*Đây là một cách kiểm tra một điểm (đỉnh) có phía như thế nào so với một đoạn, đường thẳng.
Nếu giá trị determitan > 0 thì điểm (đỉnh) đó thuộc nửa trên (hay cùng phía với biểu thức
đường thằng >0), còn nếu giá trị determitan < 0 thì điểm (đỉnh) đó thuộc nửa dưới (hay ngược
phía với biểu thức đường thằng >0)*/
						if (determinant > 0)
						{
							debugDraw->DrawCircle(point, 0.3f, b2Color(0.8f, 0.8f, 0.8f));
							vertexOfObject1[vertexCountOfObject1++] = point;
						}
						else
						{
							debugDraw->DrawCircle(point, 0.3f, b2Color(0.f, 0.9f, 0.4f));
							vertexOfObject2[vertexCountOfObject2++] = point;
						} // end if
					} // end if
				} // end for

/*Tiếp đến sẽ tạo ra hai object từ các tập điểm đã tìm được. Đối tượng bị cắt là box
không cần thiết nữa, sau khi tạo xong các object thì sẽ remove nó ra khởi 
Physics World*/
      
				// Các tính chất của object mới được tạo giống như đối tượng bị cắt box
				b2FixtureDef myFixtureDef;
				myFixtureDef.density = originalFixture->GetDensity();
				myFixtureDef.friction = originalFixture->GetFriction();
				myFixtureDef.restitution = originalFixture->GetRestitution();

				// Khởi tạo object1
				b2Body *object1;
				b2PolygonShape polygonShape;
				polygonShape.Set(vertexOfObject1, vertexCountOfObject1);	
				myFixtureDef.shape = &polygonShape;
				b2BodyDef myBodyDef;
				myBodyDef.type = b2_dynamicBody;
				myBodyDef.position.Set(box->GetPosition().x / PTM_RATIO, box->GetPosition().y / PTM_RATIO);
				object1 = m_world->CreateBody(&myBodyDef);
				object1->CreateFixture(&myFixtureDef);
				///////////////////////////////////////////////////////////////////////////////

				// Khởi tạo object2
				b2Body *object2;
				b2PolygonShape polygonShape2;
				polygonShape2.Set(vertexOfObject2, vertexCountOfObject2);
				myFixtureDef.shape = &polygonShape2;

				b2BodyDef myBodyDef2;
				myBodyDef.type = b2_dynamicBody;
				myBodyDef.position.Set(box->GetPosition().x / PTM_RATIO, box->GetPosition().x / PTM_RATIO);
				object2 = m_world->CreateBody(&myBodyDef);
				object2->CreateFixture(&myFixtureDef);
				///////////////////////////////////////////////////////////////////////////////
				// 
				m_world->DestroyBody(box);

				delete[] vertexOfObject1;
				delete[] vertexOfObject2;
			} // end if
		} // end if

		cuting = false;
	} // end if
} // end function
ss_2

Phân tích

  • A-B-C-D: Là các điểm (đỉnh) của đối tượng được chia tách, cắt.
  • E-F: lần lượt là các điểm mà tia cắt (laser) cắt đối tượng. Tia Ray Casting đầu tiên (E, F), Ray Casting thứ hai (F,E). 
  • E-F-C-B-A: là các điểm (đỉnh) của object1. Được lưu tại vertexOfObject1.
  • E-F-D: là các điểm (đỉnh) của object2. Được lưu tại vertexOfObject2.
ss_3

Chú ý

  1. Khi khởi tạo một đa giác với một tập điểm, cần thật sự chú ý rằng một đa giác ít nhất phải có 3 điểm. Và nhiều nhất với thiết lập của Box2D là 8. Nếu muốn tăng số lượng điểm (đỉnh) của đa giác, cần truy cập vào file b2Settings.h và thay đổi giá trị b2_maxPolygonVertives bằng giá trị mong muốn. Ở ví dụ trên sử dụng giá trị 23.
  2. Khi khởi tạo một đa giác với một tập điểm, các tập điểm đó phải có thứ tự ngược chiều kim đồng hồ. Trong ví dụ trên tính toán đơn giản và điều kiện này luôn đúng. Nhưng với những hình dạng phức tạp hơn sẽ không đảm bảo điều đó, vì vậy cần phải có một phương thức sắp xếp các điểm (đỉnh) ngược chiều kim đồng hồ. Công việc rất đơn giản khi sắp xếp nó theo giá trị radian với gốc tọa độ. Có thể có những cách khác. 
  3. Vấn đề nảy sinh ra là khi một cắt một đối tượng hình tròn thì sao? Hiện tại có 2 cách giải quyết. Thứ nhất là sử dụng phần mềm Physics Editor để tạo hình dạng tròn cho đối tượng. Nhưng cần thật sự chú ý tới số lượng điểm đỉnh tối đa. Thứ hai là có thể can thiệp vào lớp b2PolygonShape để tạo ra một cách tạo vật có hình dạng tròn.
  4. Vấn để thực khó khăn nhất khi sử dụng Box2D đó chính là đơn vị, phải cẩn thận với việc chuyển đổi qua lại giữa đơn vị của Box2D và pixel.

Download

Cutting_Object_Example

Thao khảo

https://www.wikipedia.org

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