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.

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
position1
vàposition2
. - Dòng 3-10: Tạo ra 2 Ray Casting.
Tạo sao lại cần tạo ra 2 Ray Casting:
- 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. - Với Ray Casting đầu tiên là position1 và position2 và Ray Casting thứ hai là
position2
vàposition1
.
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

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.

Chú ý
- 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. - 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.
- 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. - 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
Thao khảo
https://www.wikipedia.org